首页
社区
课程
招聘
[翻译]LogNT32 - 在没有预定义的头列表的情况下跟踪所有 ntdll 函数调用
发表于: 2022-2-27 09:43 5698

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

2022-2-27 09:43
5698

翻译

原文地址: 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         



[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2022-2-27 10:06 被梦幻的彼岸编辑 ,原因:
上传的附件:
收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//