近年来,我们发现恶意使用直接系统调用来逃避安全产品挂钩的情况有所增加。这些挂钩用于监视可能存在恶意活动的 API 调用。
什么是API hook
API 挂钩是防病毒和 EDR 解决方案使用的一种技术,旨在实时监控进程和代码行为。无论何时产生一个新进程,EDR 负责将自己注入进程的内存,加载挂钩 DLL,以确定活动是否是恶意的。 通常,EDR 解决方案将挂钩 NTDLL.dll 中的 Windows API,因为 NTDLL.dll 库中的 API 是进行系统调用之前调用的最后一个 API,系统调用会将执行上下文切换到内核。 EDR 解决方案通常会针对已知恶意软件开发人员使用的 API,比如NtMapViewOfSection。
下图展示一个没有挂钩的 API 的示例:
将一个值从 rcx 移动到 r10寄存器中,然后将28(syscall 编号)移动到 eax 中,然后执行 syscall 命令。
被挂钩的API示例:
在这个被钩住的 API 中,指令被 jmp < address > 指令覆盖。如果我们遵循这个 jmp 指令,我们将被引导到以下位置:
在这里,一些值在 RCX 和 RSP 寄存器中被混合。然后我们调用一个位于7FFEB06F04F3的函数。如果我们调用这个函数,我们将跳转到00007FF4B0E31000的位置。经过进一步检查,我们发现所有寄存器的值都被保存到堆栈中,并可能在以后用于检查。如果任何东西被发现是恶意的,那么反病毒产品将终止程序的执行。
恶意软件如何规避的呢
直接系统调用规避方法是从ntdll.dll中读取系统调用号,将相应的系统调用号放入eax寄存器中,将函数参数放入堆栈中,然后使用syscall或int 0x2e命令直接进入内核。 这样,就不会调用 ntdll.dll 中的函数,该钩子对于检测恶意活动毫无用处。 这张照片显示了大多数恶意软件如何执行直接系统调用:
直接系统调用通常用于悄悄地将恶意代码注入其他进程。 在32位系统中,可以通过hook SSDT来监控内核中的系统调用。但是在 Windows Vista 及更高版本中(仅限 64 位)。 由于补丁保护机制,这是不可能的。
简要介绍恶意活动的痕迹
由于缺少钩子,我们无法跟踪从 ntdll.dll 调用的函数。 但我们能追踪到什么呢? 我们能得到什么? 让我们收集并检查证据,然后得出如何减轻这些恶意软件的结论。
我们记得,对于系统调用方法上下文中的代码注入,我们需要执行两项任务:
- 读取ntdll.dll中的系统调用号
- 执行功能(导致远程线程/排队 APC/进程创建)
下面举例三种常见的方式读取ntdll.dll,并且分析利弊、特征:
- 双重加载——使用包含 ntdll.dll 的新拷贝的部分从磁盘调用 NtMapViewOfSection。
优点:看起来 ntdll.dll 是由 Windows 加载程序以一种典型的方式加载的。
缺点:可以在加载模块中找到。
特征:可以通过 NtMapViewOfSection 上的钩子进行跟踪。
NTSTATUS Status; LARGE_INTEGER SectionOffset; SIZE_T ViewSize; PVOID ViewBase; HANDLE SectionHandle; OBJECT_ATTRIBUTES ObjectAttributes; UNICODE_STRING KnownDllsNtDllName; FARPROC Function; INIT_UNICODE_STRING( KnownDllsNtDllName, L"\\KnownDlls\\ntdll.dll" ); InitializeObjectAttributes( &ObjectAttributes, &KnownDllsNtDllName, OBJ_CASE_INSENSITIVE, 0, NULL ); Status = NtOpenSection( &SectionHandle, SECTION_MAP_EXECUTE | SECTION_MAP_READ | SECTION_QUERY, &ObjectAttributes ); if(!NT_SUCCESS(Status)) { SET_LAST_NT_ERROR(Status); printf("Unable to open section %ld\n", GetLastError()); goto cleanup; } // // Set the offset to start mapping from. // SectionOffset.LowPart = 0; SectionOffset.HighPart = 0; // // Set the desired base address and number of bytes to map. // ViewSize = 0; ViewBase = NULL; Status = NtMapViewOfSection( SectionHandle, NtCurrentProcess(), &ViewBase, 0, // ZeroBits 0, // CommitSize &SectionOffset, &ViewSize, ViewShare, 0, PAGE_EXECUTE_READ ); if(!NT_SUCCESS(Status)) { SET_LAST_NT_ERROR(Status); printf("Unable to