篡改系统调用(TamperingSyscalls)
TamperingSyscalls是一个由两部分组成的新颖项目,包括参数欺骗和系统调用回传,这两个项目都滥用EH以颠覆EDR。这个项目由这两个项目组成,以便为直接的系统调用提供一个替代解决方案。
篡改系统调用。
- 设置一个全局EH,以后会用到。
SetUnhandledExceptionFilter( OneShotHardwareBreakpointHandler );
- 在syscall指令的地址上设置一个硬件断点,该指令在Dr0寄存器上有字节
0f05。我们可以通过这种快速的内存字节搜索来定位系统调用存根的地址。
BYTE stub[] = { 0x0F, 0x05 };
for( unsigned int i = 0; i < (unsigned int)25; i++ )
{
if( memcmp( (LPVOID)((DWORD_PTR)function + i), stub, 2 ) == 0 ) {
return (LPVOID)((DWORD_PTR)function + i);
}
}
- 然后我们可以调用这个函数,将<=4个参数传递为NULL(这些参数往往是比较重要的参数,持有进程句柄等信息),我们还将EnumState设置为这个函数对应的Enum(这样我们以后就可以修复参数)。
- 虽然EDR对我们的参数有充分的反省,但它不能自信地做出我们正在执行恶意行为的判断,因为我们传递了NULL作为第一个<=4个参数。
- 然后EDR将返回系统调用号码(SSN)并将其存储在RAX中。如果你只对检索系统调用感兴趣,请查看这个存储库的剥离分支。
- 然后程序流向系统调用指令{ 0x0F, 0x05 },这时会碰到我们之前设置的断点。这时将抛出一个SINGLE_STEP异常,由我们在步骤1中设置的异常处理程序来处理。
if( ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP )
- 这个异常处理程序将禁用Dr0的硬件断点,只有当Dr0和RIP匹配时,才会将Dr0寄存器的值设置为0(应该是当前的RIP)。
if( ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP )
{
if( ExceptionInfo->ContextRecord->Dr7 & 1 ) {
if( ExceptionInfo->ContextRecord->Rip == ExceptionInfo->ContextRecord->Dr0 ) {
ExceptionInfo->ContextRecord->Dr0 = 0;
- 然后我们将修复我们之前设置为NULL的其余寄存器。之所以是4,是因为这个x64的调用惯例决定了我们使用RCX、RDX、R8、R9作为前4个参数,其余的都在堆栈中设置。可以在堆栈上手动设置这些>4个参数,但这超出了本项目的范围,因为这需要内联汇编。之所以是R10而不是RCX,是因为在每个系统调用的开始,存根
mov r10, rcx,因为RCX寄存器在下一条指令中被销毁。
case NTMAPVIEWOFSECTION_ENUM:
ExceptionInfo->ContextRecord->R10 =
(DWORD_PTR)((NtMapViewOfSectionArgs*)(StateArray[EnumState].arguments))->SectionHandle;
ExceptionInfo->ContextRecord->Rdx =
(DWORD_PTR)((NtMapViewOfSectionArgs*)(StateArray[EnumState].arguments))->ProcessHandle;
ExceptionInfo->ContextRecord->R8 =
(DWORD_PTR)((NtMapViewOfSectionArgs*)(StateArray[EnumState].arguments))->BaseAddress;
ExceptionInfo->ContextRecord->R9 =
(DWORD_PTR)((NtMapViewOfSectionArgs*)(StateArray[EnumState].arguments))->ZeroBits;
我们可以看到在这个例子中,我们正在修复NtMapViewOfSection的参数。
如何做
如果你想开始伪造EDR遥测,可以修改p[FunctionName]的定义,它们目前被设置为NULL。
生成
要生成所需的函数,请使用gen.py 。这支持以下两种情况
- 逗号分隔的函数
python gen.py NtOpenSection,NtMapViewOfSection,NtUnmapViewOfSection
它将产生3个文件。TamperingSyscalls.cpp, TamperingSyscalls.h, and main.cpp。你可以#include "TamperingSyscalls.h" 到你的项目中。我们可以通过在p后面加上函数名来调用这些函数,例如pNtOpenSection(...);
限制条件
我们不能在NtSetThreadContext或它的变体上设置断点,因为它是用来设置调试寄存器的。有一段短暂的调试寄存器被设置的时间,但这是非常小的,我不认为我们会因为持有一个开放的Dr0而被发现。