这是堕落天才原创的一种方法,其实就是改变sysenter地址里面代码的执行顺序,可以说就是inline hook。
在普通sysenter hook讲到一般的拦截方法就是通过rdmsr wrmsr两个指令把原来的sysenter地址改成自己的sysenter地址,然后再跳回原来的sysenter地址。但是,这种方法虽然使用简单,但是检测也简单。
一般的rootkit检测工具检测函数inline hook是通过检测长跳转指令0xE9的来判断跳转距离是不是超出函数所在的模块范围来确定的.但是实现跳转我们也可以借助寄存器或变量(用变量跳转需要涉及重定位问题,麻烦.所以一般用寄存器),这样跳转指令就不是0xE9了而是0xFF,这个绝大多数rootkit检测工具是检测不到的。
由于我们已经改变了KiFastCall函数头,所以我们只能把原来的函数头代码放到另外一个地方执行(动态分配内存,当然如果不考虑兼容性硬编码也没问题),然后再跳转回来.这里使用了”三级跳”,大概是这个样子.
sysenter->KiFastCall
JMP -> MyKiFastCall(这里进行拦截或什么的)
JMP -> KiFastCall head code (这里执行原来KiFastCall函数头代码)
JMP -> KiFastCall + N(已经执行指令长度)
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | #include <ntddk.h> ULONG oldSysenter; PUCHAR pMoveSysenterCode; UCHAR uOrigSysenterHead[8]; ULONG i = 0; ULONG GetOpCodeSize(PVOID Start); __declspec(naked) MyKiFastCallEntry() { __asm{ pop edi //为了堆栈平衡,这里pop mov i,eax } __asm{ pushad push fs push 0x30 pop fs //在内核态FS=0x30, 在用户态FS=0x3B //用户层堆栈环境转成内核层堆栈环境 } //上面是设置fs为30 //否则下面的DbgPrint就会出错 //可能是因为现在还在应用态,DbgPrint必须在fs设为30才能使用 //DbgPrint("sysenter hook and service id is %d\n",i); __asm{ pop fs popad jmp pMoveSysenterCode } } void HookSysenter(){ int nCopyLen = 0; int nPos = 0; //第一跳,从KiFastCall跳到MyKiFastCallEntry.并绕过rootkit检测工具检测 UCHAR HookCode[8] = { 0x57,//push edi 堆栈平衡 0xbf,0x00,0x00,0x00,0x00,//mov edi,xxxx 0xFF,0xE7 //jmp edi }; UCHAR JmpCode[] = {0xE9,0x00,0x00,0x00,0x00}; //jmp 0000 第三跳,从KiFastCall函数头代码跳转到原来KiFastCall+N __asm{ mov ecx,0x176 rdmsr mov oldSysenter,eax //得到KiFastCallEntry地址 } DbgPrint("oldSysenter address: %08x \n",oldSysenter); //我们要改写的函数至少需要8个字节,这里计算出实际要COPY的代码,为9个字节 nPos = oldSysenter; while(nCopyLen<8){ //我们要改写的函数头至少需要8字节 这里计算实际需要COPY的代码长度 因为我们不能把一条完整的指令打断 nCopyLen += GetOpCodeSize((PVOID)nPos); //参考1 nPos = oldSysenter + nCopyLen; } //开辟一块内存 pMoveSysenterCode = (PUCHAR)ExAllocatePool(NonPagedPool, 20); memset(pMoveSysenterCode,0x90,20); memset(uOrigSysenterHead,0x90,8); //备份原来的8字节 memcpy(uOrigSysenterHead,(PVOID)oldSysenter,8); //修正返回地址 = 跳转到的地址 - 当前地址 - 5 memcpy(pMoveSysenterCode,(PVOID)oldSysenter,nCopyLen); *((ULONG*)(JmpCode+1)) = (oldSysenter + nCopyLen) - ((ULONG)pMoveSysenterCode + nCopyLen) - 5; memcpy((PVOID)(pMoveSysenterCode+nCopyLen),JmpCode,5); ///把跳转代码COPY上去 *((ULONG*)(HookCode+2)) = (ULONG)MyKiFastCallEntry; //HOOK地址 DbgPrint("Saved sysenter code:0x%08X",pMoveSysenterCode); DbgPrint("MyKiFastCallEntry:0x%08X",MyKiFastCallEntry); __asm{ cli mov eax,cr0 and eax,not 10000h mov cr0,eax } memcpy((PVOID)oldSysenter,HookCode,8);//把改写原来函数头 __asm{ mov eax,cr0 or eax,10000h mov cr0,eax sti } } VOID OnUnload(IN PDRIVER_OBJECT DriverObject) { __asm{ cli mov eax,cr0 and eax,not 10000h mov cr0,eax } memcpy((PVOID)oldSysenter,uOrigSysenterHead,8);//把原来函数头的八个字节恢复 __asm{ mov eax,cr0 or eax,10000h mov cr0,eax sti } ExFreePool(pMoveSysenterCode); // 释放分配的内存 DbgPrint("Unload sysenterHook"); } NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath) { DbgPrint("Welcome to sysenterhook.sys"); DriverObject->DriverUnload = OnUnload; HookSysenter(); return STATUS_SUCCESS; } |
加载前:
kd> u 82847300
nt!KiFastCallEntry:
82847300 b923000000 mov ecx,23h
82847305 6a30 push 30h
82847307 0fa1 pop fs
82847309 8ed9 mov ds,cx
8284730b 8ec1 mov es,cx
8284730d 648b0d40000000 mov ecx,dword ptr fs:[40h]
82847314 8b6104 mov esp,dword ptr [ecx+4]
82847317 6a23 push 23h
加载后:
再来看看KiFastCallEntry里面的指令:
kd> u 82847300
nt!KiFastCallEntry:
82847300 57 push edi
82847301 bf10a3bf90 mov edi,offset hooksysenter!MyKiFastCallEntry (90bfa310)
82847306 ffe7 jmp edi
82847308 a18ed98ec1 mov eax,dword ptr ds:[C18ED98Eh]
8284730d 648b0d40000000 mov ecx,dword ptr fs:[40h]
82847314 8b6104 mov esp,dword ptr [ecx+4]
82847317 6a23 push 23h
82847319 52 push edx
前面8个字节已经替换成了自己的jmp语句,直接jmp到MyKiFastCallEntry 。
执行完我们自定义的函数后,又jmp到pMoveSysenterCode。
kd> u MyKiFastCallEntry l 10
hooksysenter!MyKiFastCallEntry [d:\vs2012\hooksysenter\hooksysenter\hooksysenter2.c @ 11]:
90bfa310 5f pop edi
90bfa311 a320c8bf90 mov dword ptr [hooksysenter!i (90bfc820)],eax
90bfa316 60 pushad
90bfa317 0fa0 push fs
90bfa319 6a30 push 30h
90bfa31b 0fa1 pop fs
90bfa31d 0fa1 pop fs
90bfa31f 61 popad
90bfa320 ff2530c8bf90 jmp dword ptr [hooksysenter!pMoveSysenterCode (90bfc830)]
pMoveSysenterCode里面保存的是原来被替换掉的指令:
kd> dd 90bfc830 l1
90bfc830 86297f58
kd> u 86297f58
86297f58 b923000000 mov ecx,23h
86297f5d 6a30 push 30h
86297f5f 0fa1 pop fs
86297f61 e9a3f35afc jmp nt!KiFastCallEntry+0x9 (82847309)
执行完原来被替换的指令之后,又跳回原始KiFastCall + N的位置继续执行。
不过这种还是能被检测到:
上面代码的GetOpCodeSize这个函数,是计算第N条完整汇编指令所占的长度。