红队技术:绕过用户模式Hook,直接进行系统调用(一)
引言
绕过用户模式hook的动机最初始于提高进程注入的成功率。有些时候,进程注入被用于合法用途,例如,UI Automation和Active Accessibility就使用该技术来读写GUI进程的内存,而Spy++则使用它来记录进程之间发送和接收的窗口消息。但在大多数情况下,进程注入却被用于以下用途:
- 将代码隐藏在合法进程中以逃避检测,延长检测和删除时间
- 在另一个用户的上下文中执行代码或提升权限
- 修改内存,实现网络游戏作弊
然而,进程注入还有一个较少被提及的用途,那就是阻止上述所有操作。通常,从用户模式(UM)应用程序中实现进程注入需要完成以下步骤:
- 打开目标进程
- 分配新内存或使用现有内存来存储代码
- 将包含可选数据的代码写入目标进程
- 通过新线程或现有线程执行代码
尽管该技术实施起来相对简单,但红队队员、游戏作弊者和恶意软件开发人员如今却仍面临一些障碍:安全厂商安装的内核模式(KM)通知、微型过滤器驱动程序和UM hook。UM hook通常用于处理NTDLL内的系统调用,而NTDLL则是UM进程最接近内核的地方。通过获得对内核的完全访问权限,安全厂商就可以完全控制系统,以便轻松地阻止各种类型的恶意活动。但正如部分读者已经知道的那样,Windows自Vista版本以来就提供一种内置的安全功能,该功能称为PatchGuard(PG),可用于保护内核的关键区域免遭修改。这些内存区域包括:
- 系统服务描述符表(SSDT)
- 全局描述符表(GDT)
- 中断描述符表(IDT)
- 系统映像(ntoskrnl.exe、ndis.sys、hal.dll)
- 处理器MSR(系统调用)
PG(让安全厂商和恶意软件开发人员都大失所望)禁止任何软件对Windows内核进行扩展(即使是那些出于合法原因的软件)。在该功能推出之前,安全厂商给SSDT打补丁是司空见惯的事情(Sysinternals的RegMon的早期版本也使用了这种手法)。然而,现在微软的立场是,凡是对内核进行修改软件(无论是否为恶意软件)都可能导致可靠性、性能以及安全性受损。PG发布后,安全厂商不得不完全重新设计其反恶意软件解决方案。其中,绕过PG是一种可选方案,但对于旨在保护操作系统的软件来说,这并非一个安全、长久的解决方案。
在本文中,我们将为读者全面介绍可以用来绕过用户模式hook的最流行、最有效的各种技术,并指出每种技术对红队的优缺点。最后,我们将总结一些防御者可以用来保护或检测这些技术的方法。
内核模式通知
在探索UM hook绕过方法之前,首先需要了解的一点是,Windows已经简化了接收对于检测恶意软件很有用的事件的通知的方法,并且,利用这些通知,就不用在内核中打补丁或使用hook了。其中,比较常见的事件包括进程或线程的创建、终止,以及映像/DLL的映射等。
Microsoft建议安全供应商使用微型过滤器驱动程序来拦截、检查和选择性地阻止I/O事件。实际上,许多文件系统和网络功能都是通过NtDeviceIoControlFile系统调用实现的。
绕过方法
由于Microsoft没有为内核组件提供合法的方式来接收有关内存操作的通知,这迫使供应商不得不在每个进程中安装UM hook。为了应对这种情况,人们设计了各种绕过它们的技术,下面将对当前正在使用的一些技术进行简单的介绍,并给出对应的C语言源代码。
1.导出地址表(EAT)
恶意软件通常组合使用GetModuleHandle和GetProcAddress来解析系统调用的地址。另一种方法是在进程环境块(PEB)中手动定位ntdll.dll,并通过解析导出地址表(EAT)找到系统调用。下面给出用于解析EAT的示例代码。
static
LPVOID
WINAPI
GetProcAddressFromEAT(
LPVOIDDllbase,
const char*FunctionName)
{
PIMAGE_DOS_HEADER DosHeader;
PIMAGE_NT_HEADERS NtHeaders;
DWORD NumberOfNames,VirtualAddress;
PIMAGE_DATA_DIRECTORY DataDirectory;
PIMAGE_EXPORT_DIRECTORY ExportDirectory;
PDWORD Functions;
PDWORD Names;
PWORD Ordinals;
PCHAR Name;
LPVOID ProcAddress=NULL;
DosHeader =(PIMAGE_DOS_HEADER)Dllbase;
NtHeaders =RVA2VA(PIMAGE_NT_HEADERS, Dllbase, DosHeader->e_lfanew);
DataDirectory =(PIMAGE_DATA_DIRECTORY)NtHeaders->OptionalHeader.DataDirectory;
VirtualAddress= DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
if(VirtualAddress==0) return NULL;
ExportDirectory = RVA2VA(PIMAGE_EXPORT_DIRECTORY, Dllbase,VirtualAddress);
NumberOfNames =ExportDirectory->NumberOfNames;
if(NumberOfNames==0) return NULL;
Functions =RVA2VA(PDWORD,Dllbase, ExportDirectory->AddressOfFunctions);
Names = RVA2VA(PDWORD,Dllbase,ExportDirectory->AddressOfNames);
Ordinals = RVA2VA(PWORD, Dllbase,ExportDirectory->AddressOfNameOrdinals);
do {
Name =RVA2VA(PCHAR, Dllbase, Names[NumberOfNames-1]);
if(lstrcmpA(Name, FunctionName) == 0) {
ProcAddress = RVA2VA(LPVOID, Dllbase,Functions[Ordinals[NumberOfNames-1]]);
returnProcAddress;
}
} while(--NumberOfNames && ProcAddress == NULL);
returnProcAddress;
}
如果使用内存中已有的NTDLL基址,则无法绕过系统调用的任何UM hook。如果您希望绕过KERNEL32或KERNELbase的hook,不妨使用GetProcAddress,这样的话事情会简单得多。
通常情况下,Offsec工具会在调用这样的函数后会对系统调用进行unhook,然而,最近越来越多的安全厂商要么阻止unhook的尝试,要么在unhooking后不久就重新恢复hook。实际上,只要钩住NtProtectVirtualMemory函数,就可以轻松拦截覆盖hook的企图。
2. Dual-load 1(Section)
KnownDlls是对象命名空间中的一个目录,其中包含进程加载的最常见DLL的区段对象(section object)。它旨在通过减少可执行文件的加载时间来提高性能,并且可以通过打开区段名称knowndllsntdll.dll来将NTDLL的新副本映射到进程中。一旦映射了区段对象,我们就可以像前面方法中描述的那样解析系统调用的地址。不过,有一个内核通知是专门针对映像加载事件的,如果EDR或AV发现ntdll.dll被第二次加载,它就检查进程中是否存在恶意代码或至少通知用户存在可疑的活动。
虽然您可以使用NtOpenSection和NtMapViewOfSection加载新副本,但另一个问题是:它们可能已经被安装了hook。有些产品不会为NtMapViewOfSectionEx函数安装hook,但该函数只在Windows 10 1803后可用,并且,它仍然无法阻止发送与映射相关的内核通知。
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());
gotocleanup;
}
//
// Set theoffset to start mapping from.
//
SectionOffset.LowPart = 0;
SectionOffset.HighPart = 0;
//
// Set thedesired 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 map section %ld\n", GetLastError());
gotocleanup;
}
Function =(FARPROC)GetProcAddressFromEAT(Viewbase, "NtOpenProcess");
printf("NtOpenProcess : %p, %ld\n",Function, GetLastError());
cleanup:
if(Viewbase!= NULL) {
NtUnmapViewOfSection(
NtCurrentProcess(),
Viewbase
);
}
if(SectionHandle != NULL) {
NtClose(SectionHandle);
}
3. Dual-load 2 (Disk)
与前面的方法相比,多出来的一个步骤是,我们需要打开一个C:\Windows\System32\NTDLL.dll的文件句柄,并使用它来创建一个新的具有SEC_IMAGE页保护的区段对象。然后我们对该对象进行映射,以便进行读取或执行操作。但是,NtOpenFile、NtCreateFile可能已经被安装了hook,但即使没有的话,也无法解决前面方法中强调的问题。
NTSTATUS Status;
LARGE_INTEGER SectionOffset;
SIZE_T ViewSize;
PVOID Viewbase=NULL;
HANDLE FileHandle=NULL,SectionHandle=NULL;
object_ATTRIBUTES objectAttributes;
IO_STATUS_BLOCK StatusBlock;
UNICODE_STRING FileName;
FARPROC Function;
//
// Try openntdll.dll on disk for reading.
//
INIT_UNICODE_STRING(
FileName,
L"\\??\\C:\\Windows\\System32\\ntdll.dll"
);
InitializeobjectAttributes(
&objectAttributes,
&FileName,
OBJ_CASE_INSENSITIVE,
0,
NULL
);
Status =NtOpenFile(
&FileHandle,
FILE_READ_DATA,
&objectAttributes,
&StatusBlock,
FILE_SHARE_READ,
NULL
);
if(!NT_SUCCESS(Status)) {
SET_LAST_NT_ERROR(Status);
printf("NtOpenFile failed %ld\n", GetLastError());
gotocleanup;
}
//
// Createsection
//
Status =NtCreateSection(
&SectionHandle,
SECTION_ALL_ACCESS,
NULL,
NULL,
PAGE_READONLY,
SEC_IMAGE,
FileHandle
);
if(!NT_SUCCESS(Status)) {
SET_LAST_NT_ERROR(Status);
printf("NtCreateSection failed %ld\n", GetLastError());
gotocleanup;
}
//
// Set theoffset to start mapping from.
//
SectionOffset.LowPart = 0;
SectionOffset.HighPart = 0;
//
// Set thedesired 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 map section %ld\n", GetLastError());
gotocleanup;
}
Function =(FARPROC)GetProcAddressFromEAT(Viewbase, "NtOpenProcess");
printf("NtOpenProcess : %p, %ld\n", Function, GetLastError());
cleanup:
if(Viewbase!= NULL) {
NtUnmapViewOfSection(
NtCurrentProcess(),
Viewbase
);
}
if(SectionHandle != NULL) {
NtClose(SectionHandle);
}
if(FileHandle != NULL) {
NtClose(FileHandle);
}
小结
在本文中,我们将为读者全面介绍可以用来绕过用户模式hook的最流行、最有效的各种技术,并指出每种技术对红队的优缺点。最后,我们将总结一些防御者可以用来保护或检测这些技术的方法。由于篇幅较长,我们准备分篇发表,更多精彩内容,敬请期待!
(未完待续)
本文由secM整理并翻译,不代表白帽汇任何观点和立场
来源:
https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/
最新评论