-
-
[翻译]LogNT32 - 在没有预定义的头列表的情况下跟踪所有 ntdll 函数调用
-
发表于: 2022-2-27 09:43 5683
-
翻译
原文地址: https://www.x86matthew.com/view_post?id=lognt32
我创建了一个小工具,可以让我们在ntdll.dll级别对32位exe文件进行快速分析。这对于各种目的都很有用——监视潜在的恶意exe,或者深入了解高级Windows APIs(如ShellExecute)是如何工作的。
由于这不是一个攻击性的测试工具,我也将在这个版本中包含二进制文件(本文底部的链接)。
该工具在用户模式下运行,并记录所有ntdll.dll函数调用。我不想依赖ntdll函数定义的硬编码列表,所以我想出了一些技巧,允许我们以通用的方式记录函数参数。这些方法将在本文中进一步描述。
请看下面的演示:
默认情况下,这个程序拦截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函数似乎是相同的:
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函数非常有用。例如,我们可以记录以下程序:
int main() { ShellExecute(NULL, "open", "notepad.exe", NULL, NULL, SW_SHOW); return 0; }
这会创建一个很大的输出文件,但是我在下面提取了一个简短的示例,显示了ShellExecute打开notepad.exe的App Paths注册表项:
[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代码
https://bbs.pediy.com/upload/attach/202202/767964_6K2N3ZFU5UDKMZS.txt
//代码过大上传附件保存
LogNT32.DLL代码
https://bbs.pediy.com/upload/attach/202202/767964_JHUAQUPM2MVP9W7.txt
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
赞赏
- [原创]物联网安全:基础篇 4031
- 威胁情报小课堂:阻止活跃勒索软件的感染 2144
- [翻译]发现利用 Facebook 和 MS 管理控制台实施的 Kimsuky APT 攻击 6993
- 威胁情报小课堂:LockBit Black 2022
- 威胁情报小课堂:Nitrogen 2049