飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 835|回复: 0

[C/C++] LogNT32 - 在没有预定义的头列表的情况下跟踪所有 ntdll 函数调用

[复制链接]
  • TA的每日心情
    开心
    2019-3-15 11:00
  • 签到天数: 262 天

    [LV.8]以坛为家I

    发表于 2022-2-27 10:01:06 | 显示全部楼层 |阅读模式
    本帖最后由 梦幻的彼岸 于 2022-2-27 10:06 编辑

    翻译
    功能:在没有预定义的头列表的情况下跟踪所有 ntdll 函数调用

    我创建了一个小工具,可以让我们在ntdll.dll级别对32位exe文件进行快速分析。这对于各种目的都很有用——监视潜在的恶意exe,或者深入了解高级Windows APIs(如ShellExecute)是如何工作的。

    由于这不是一个攻击性的测试工具,我也将在这个版本中包含二进制文件(本文底部的链接)。

    该工具在用户模式下运行,并记录所有ntdll.dll函数调用。我不想依赖ntdll函数定义的硬编码列表,所以我想出了一些技巧,允许我们以通用的方式记录函数参数。这些方法将在本文中进一步描述。

    请看下面的演示:
    lognt32.gif
    默认情况下,这个程序拦截ntdll.dll内的每个导出——这意味着目标可执行文件将运行得非常慢。可以使用命令行过滤器(排除/仅包括特定导出)来设置过滤器,以提高性能和可读性。

    LogNT32 consists of 2 parts - LogNT32.exe and LogNT32.dll.

    LogNT32.exe

    这只是启动目标可执行文件并将主模块(LogNT32.dll)加载到远程进程中的加载程序。它还处理LogNT32命令行参数(过滤选项),这些参数在日志模块加载后被传递给日志模块。没有使用特殊的注入技术——这只是使用了标准的CreateRemoteThread / LoadLibrary方法,因为这里不需要隐藏。
    将LogNT32.dll模块加载到目标进程后,该程序会连续读取输出日志文件,并实时将其打印到控制台。

    LogNT32.dll

    如上所述,这个模块被注入到目标流程中。
    在主程序执行开始前,采取以下初始化步骤:

    1.创建ntdll中所有可执行部分的cloned。仅复制.text部分在这里不足够-ntdll的某些版本。dll还导出名为RT的单独可执行部分中的一些函数。这将被用作ntdll.dll的无污染(未拦截)副本。
    2.使用RtlAddVectoredExceptionHandler添加异常处理程序。
    3.读取ntdll中的导出地址表。ntdll.dll为除KiUserExceptionDispatcher之外的每个函数添加断点(0xCC)。
    4.向KiUserExceptionDispatcher函数添加一条JMP指令——这将直接重定向到KiUserExceptionDispatcher的“cloned”版本。我们不能在KiUserExceptionDispatcher上设置断点,否则将发生无限循环。
    5.处理用户指定的过滤器-设置标志以包括或排除特定的导出。
    6.主程序开始执行。

    当目标进程调用ntdll.dll中的函数时,会发生以下一系列事件:

    1.初始断点命中——自定义异常处理程序捕获EXCEPTION_BREAKPOINT异常。
    2. 存储此函数调用的堆栈指针(esp)和返回地址([esp])。
    3. 使用 Dr0 调试寄存器在此函数调用的返回地址上设置硬件断点。
    4. 在导出列表中查找当前函数名(通过指令指针)。
    5. 添加一个日志条目以指示函数调用的开始。
    6. 继续执行。
    7. 函数返回时,应触发返回地址上的硬件断点并引发EXCEPTION_SINGLE_STEP异常。
    8. 存储返回值(eax)。
    9. 从当前堆栈指针中减去步骤#2 中的原始堆栈指针。将此值除以 4 (DWORD) 并减去 1(忽略返回地址)以计算提供给函数的参数数量。这是因为 WinAPI 函数使用 stdcall 调用约定——这也是为什么这个概念只适用于 32 位程序,x64 使用不同的调用约定。
    10. 遍历每个参数并检查它是否包含字符串值。此工具检查 OBJECT_ATTRIBUTES、ANSI_STRING 和 UNICODE_STRING 结构中的字符串。执行各种检查以计算这是否是有效的字符串参数。
    11、添加日志条目,表示函数调用结束,包括返回值和参数值。

    这个程序为每个线程维护一个内部调用堆栈。链中每个调用的“depth”显示在日志条目中。跟踪调用堆栈非常重要,以确保硬件断点始终设置为链中的下一个预期返回地址。

    需要进行各种其他检查,以确保一切按预期进行。例如,在递归函数调用中,在返回地址上设置硬件断点之前,我们添加了一个单步标志——这可以防止无限循环。

    Windows程序中的执行流并不总是线性的。内核可以临时接管现有的用户模式线程来执行回调。这些回调函数通常用于GUI应用程序中的消息处理。当内核请求用户模式回调时,目标线程指令指针设置为KiUserCallbackDispatcher。此函数在用户模式下执行请求的回调(来自PEB中的KernelCallbackTable表),最后在完成时调用NtCallbackReturn以返回内核模式。然后恢复原始线程环境,并继续执行。

    为了维持调用堆栈,我们需要跟踪每个内核回调的开始和结束时间。这是因为KiUserCallbackDispatcher函数没有返回地址,这意味着每个新的内核回调都需要一个临时调用堆栈。更复杂的是,多个回调可以嵌套在其内部。这意味着我们需要维持多层调用堆栈,并在每次回调结束时始终返回到链中的下一个条目。

    注意:当我在本机32位系统上测试时(而不是在64位安装上测试WoW64),我注意到NtCallbackReturn很少用于返回内核。相反,user32.dll内部有一个非导出函数(姑且称之为User32CallbackReturn)直接执行int 0x2B指令返回内核。这意味着我们不能通过单独挂钩NtCallbackReturn来检测回调返回。幸运的是,在所有32位版本的user32.dll中,User32CallbackReturn函数似乎是相同的:
    [Asm] 纯文本查看 复制代码
    mov eax, dword ptr [esp + 4]
    int 0x2B
    retn 4
    在32位操作系统上,这个程序在user32.dll的代码段中搜索这个函数,并手动添加一个拦截来模拟NtCallbackReturn的行为。

    我们不想从异常处理程序中调用任何拦截的函数,所以我们使用一个“safe”函数指针列表,这些指针直接指向“clean”ntdll.dll副本。使用c运行时函数通常是安全的,但是一些实现调用ntdll.dll函数(例如wcstombs-> RtlUnicodeToMultiByteN)应该避免。为了完整起见,我在异常处理程序中使用了所有外部函数调用的“safe”版本。

    LogNT32包含一个预定义的要忽略的函数列表(不考虑命令行过滤器)。这个文件名为LogNT32_IgnoreList.txt,如果它不存在,它会被创建并预先填充。这包含RtlEnterCriticalSection和RtlLeaveCriticalSection等常用函数,可以根据需要进行修改。  
    我还添加了一个名为string_only的命令行参数。如果指定了此参数,则只记录包含字符串参数值的函数调用。由于字符串值通常是最有用的监控字段,这可以从输出日志中去除很多“noise”。
    由于该工具依赖于ntdll.dll的hooked exports,它不会检测恶意软件中可能使用的“direct”系统调用——这一功能超出了该工具的范围。
    如之前所述,该工具对于记录高级WinAPI函数非常有用。例如,我们可以记录以下程序:
    [C++] 纯文本查看 复制代码
    int main()
    {
        ShellExecute(NULL, "open", "notepad.exe", NULL, NULL, SW_SHOW);
     
        return 0;
    }
    这会创建一个很大的输出文件,但是我在下面提取了一个简短的示例,显示了ShellExecute打开notepad.exe的App Paths注册表项:
    [Bash shell] 纯文本查看 复制代码
    [23/02/2022 20:42:59] [TID:9172] [CALL_BEGIN]   RtlInitUnicodeStringEx [RETN_ADDR:0x77995AD4]
    [23/02/2022 20:42:59] [TID:9172] [CALL_END]     RtlInitUnicodeStringEx(0x0019F420 <Software\Microsoft\Windows\CurrentVersion\App Paths\notepad.exe>, 0x0019F4A4) [RETN_VALUE:0x00000000]
    [23/02/2022 20:42:59] [TID:9172] [CALL_BEGIN]   NtOpenKeyEx [RETN_ADDR:0x77995F84]
    [23/02/2022 20:42:59] [TID:9172] [CALL_END]     NtOpenKeyEx(0x0019F764, 0x00020019, 0x0019F36C <Software\Microsoft\Windows\CurrentVersion\App Paths\notepad.exe>, 0x00000000) [RETN_VALUE:0xC0000034]
    [23/02/2022 20:42:59] [TID:9172] [CALL_BEGIN]   RtlNtStatusToDosError [RETN_ADDR:0x77995F99]
    [23/02/2022 20:42:59] [TID:9172] [CALL_END]     RtlNtStatusToDosError(0xC0000034) [RETN_VALUE:0x00000002]
    [23/02/2022 20:42:59] [TID:9172] [CALL_BEGIN]   RtlNtStatusToDosError [RETN_ADDR:0x77996008]
    [23/02/2022 20:42:59] [TID:9172] [CALL_END]     RtlNtStatusToDosError(0xC0000034) [RETN_VALUE:0x00000002]
    [23/02/2022 20:42:59] [TID:9172] [CALL_BEGIN]   RtlAcquireSRWLockExclusive [RETN_ADDR:0x77995B2C]
    [23/02/2022 20:42:59] [TID:9172] [CALL_END]     RtlAcquireSRWLockExclusive(0x77A6A760) [RETN_VALUE:0x00000000]
    [23/02/2022 20:42:59] [TID:9172] [CALL_BEGIN]   RtlReleaseSRWLockExclusive [RETN_ADDR:0x77995B55]
    [23/02/2022 20:42:59] [TID:9172] [CALL_END]     RtlReleaseSRWLockExclusive(0x77A6A760) [RETN_VALUE:0x00000001]
    [23/02/2022 20:42:59] [TID:9172] [CALL_BEGIN]   NtOpenProcessToken [RETN_ADDR:0x73F1BEB1]
    [23/02/2022 20:42:59] [TID:9172] [CALL_END]     NtOpenProcessToken(0xFFFFFFFF, 0x00000008, 0x0019F710) [RETN_VALUE:0x00000000]
    [23/02/2022 20:42:59] [TID:9172] [CALL_BEGIN]   RtlNtStatusToDosError [RETN_ADDR:0x73F1BEB8]
    [23/02/2022 20:42:59] [TID:9172] [CALL_END]     RtlNtStatusToDosError(0x00000000) [RETN_VALUE:0x00000000]
    [23/02/2022 20:42:59] [TID:9172] [CALL_BEGIN]   NtQueryInformationToken [RETN_ADDR:0x73F1BF5B]
    [23/02/2022 20:42:59] [TID:9172] [CALL_END]     NtQueryInformationToken(0x00000440, 0x00000012, 0x0019F6E4, 0x00000004, 0x0019F6E0) [RETN_VALUE:0x00000000]
    [23/02/2022 20:42:59] [TID:9172] [CALL_BEGIN]   RtlNtStatusToDosError [RETN_ADDR:0x73F1BF08]
    [23/02/2022 20:42:59] [TID:9172] [CALL_END]     RtlNtStatusToDosError(0x00000000) [RETN_VALUE:0x00000000]
    [23/02/2022 20:42:59] [TID:9172] [CALL_BEGIN]   NtClose [RETN_ADDR:0x73F1BED9]
    [23/02/2022 20:42:59] [TID:9172] [CALL_END]     NtClose(0x00000440) [RETN_VALUE:0x00000000]
    [23/02/2022 20:42:59] [TID:9172] [CALL_BEGIN]   RtlQueryPackageClaims [RETN_ADDR:0x73E7111E]
    [23/02/2022 20:42:59] [TID:9172] [CALL_END]     RtlQueryPackageClaims(0xFFFFFFFA, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x0019F7B8, 0x0019F7C0) [RETN_VALUE:0xC0000225]
    [23/02/2022 20:42:59] [TID:9172] [CALL_BEGIN]   RtlInitUnicodeStringEx [RETN_ADDR:0x77995484]
    [23/02/2022 20:42:59] [TID:9172] [CALL_END]     RtlInitUnicodeStringEx(0x0019F83C <SetWorkingDirectoryFromTarget>, 0x73DCA844) [RETN_VALUE:0x00000000]
    [23/02/2022 20:42:59] [TID:9172] [CALL_BEGIN]   NtQueryKey [RETN_ADDR:0x779964BB]
    [23/02/2022 20:42:59] [TID:9172] [CALL_END]     NtQueryKey(0x00000312, 0x00000003, 0x0019F598, 0x00000188, 0x0019F590) [RETN_VALUE:0x00000000]
    [23/02/2022 20:42:59] [TID:9172] [CALL_BEGIN]   RtlInitUnicodeStringEx [RETN_ADDR:0x77996532]
    [23/02/2022 20:42:59] [TID:9172] [CALL_END]     RtlInitUnicodeStringEx(0x0019F540 <\REGISTRY\MACHINE\SOFTWARE\Classes\exefile\shell\open\>, 0x0019F59C) [RETN_VALUE:0x00000000]
    [23/02/2022 20:42:59] [TID:9172] [CALL_BEGIN]   RtlAppendUnicodeStringToString [RETN_ADDR:0x77996551]
    [23/02/2022 20:42:59] [TID:9172] [CALL_END]     RtlAppendUnicodeStringToString(0x0019F540 <\REGISTRY\MACHINE\SOFTWARE\Classes\exefile\shell\open\>, 0x0019F57C) [RETN_VALUE:0x00000000]
    下载下面的二进制文件:

    Download zip

    以下是完整代码:
    代码过大上传附件保存
    LogNT32.EXE代码
    LogNT32.EXE.txt (11.73 KB, 下载次数: 0)
    LogNT32.DLL代码
    LogNT32.DLL.txt (70.7 KB, 下载次数: 0)

    PYG19周年生日快乐!
    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

    快速回复 返回顶部 返回列表