IDTHook 深入学习

在之前的一篇文章中介绍了替换IDT向量表中的地址来达到Hook的目的 IDT hook KiTrap03 但是这样很容易就可以被检测了。接下来要学习就是通过patch GDT来达到Hook IDT的目的。

首先,我们要了解一下,当触发INT 3号中断之后,CPU是如何找到接下来要执行的指令的地址。

CPU 在执行中断的时候,先会得到中断描述符表中该中断的中断例程(InterruptFunc ),然后得到该中断描述符中的段选择符,解析出段选择符对应的GDT(因为中断例程全都是在内核层中)中的地址(Base),然后执行:Base + InterruptFunc = 目标真正要执行的地址。

可能到这里还不太理解,我们以KiTrap03 3号中断为例来学习。

kd> !pcr
KPCR for Processor 0 at 83f6bc00:
    Major 1 Minor 1
    NtTib.ExceptionList: 83f680ac
        NtTib.StackBase: 00000000
       NtTib.StackLimit: 00000000
     NtTib.SubSystemTib: 801e4000
          NtTib.Version: 000bd40c
      NtTib.UserPointer: 00000001
          NtTib.SelfTib: 00000000

                SelfPcr: 83f6bc00
                   Prcb: 83f6bd20
                   Irql: 0000001f
                    IRR: 00000000
                    IDR: ffffffff
          InterruptMode: 00000000
                    IDT: 80b95400
                    GDT: 80b95000
                    TSS: 801e4000

          CurrentThread: 83f75380
             NextThread: 00000000
             IdleThread: 83f75380

              DpcQueue: 

我们可以看到当前处理器的控制块地址和IDT,GDT地址。

80b95400 83e78e000008efc0 83e78e000008f150 0000850000580000
80b95418 83e7ee000008f5c0 83e7ee000008f748 83e78e000008f8a8

我们可以看到3号中断的描述项的地址是0x80b95418 

typedef struct _IDTENTRY
{
    unsigned short LowOffset;
    unsigned short selector;
    unsigned char retention:5;
    unsigned char zero1:3;
    unsigned char gate_type:1;
    unsigned char zero2:1;
    unsigned char interrupt_gate_size:1;
    unsigned char zero3:1;
    unsigned char zero4:1;
    unsigned char DPL:2;
    unsigned char P:1;
    unsigned short HiOffset;
} IDTENTRY,*PIDTENTRY;
kd> db 80b95418
80b95418  c0 f5 08 00 00 ee e7 83

我们可以得到LowOffset = 0xf5c0,HiOffset = 83e7 所以3号中断的InterruptFunc  = 0x83e7f5c0。

段选择符selector = 0x08 二进制表示即1000。这里就又牵扯到另一个概念,段选择符。

比如我们通知都知道 FS:[0] 指向的地址在内核层是处理器控制块,在应用层是当前线程的TEB的首地址。

之前分析KiTrap03的汇编的时候有几条指令:

.text:00436C5F mov     ebx, 30h
.text:00436C64 mov     fs, bx
.text:00436C67 mov     ebx, large fs:0       ; fs对应处理器相关的_KPCR结构,kpcr,那么得到的是
.text:00436C67                               ; kpcr.NtTib.ExceptionList

这里,为什么fs=0x30了以后,fs:0指向的就是kpcr?

FS 寄存器在Windows下表示的不是我们通常意义上的段基址,而是段选择符。就是说CPU对于FS寄存器的寻址不是通常意义上的段基址+段偏移的方式,而是采用了另外的一种解析方式。我们这里还是以fs=0x30为例来讲解。

FS寄存器是16位寄存器,先大致了解一下每一位的意义:

0和1位:代表当前特权级,用户层:11     内核层:00::

2位:表指示位,0 表示在GDT(全局)中 ,1表示在LDT(局部)中:

3--15位:段索引。

先了解这么多就够了,然后回到我们的问题 FS=0x30   二进制表示就是110 0 00,特权级是0 内核态, GDT表,段索引是0x6。

我们之前有得到过GDT的地址,然后索引值为6的地址为0x80b95030

80b95000 0000000000000000 00cf9b000000ffff 00cf93000000ffff
80b95018 00cffb000000ffff 00cff3000000ffff 80008b1e400020ab
80b95030 834093f6bc003748 

 

kd> db 80b95030
80b95030  48 37 00 bc f6 93 40 83
typedef struct _KGDTENTRY                 // 3 elements, 0x8 bytes (sizeof)  
{                                                                            
    /*0x000*/     UINT16       LimitLow;                                                   
    /*0x002*/     UINT16       BaseLow;                                                    
    union                                 // 2 elements, 0x4 bytes (sizeof)  
    {                                                                        
        struct                            // 4 elements, 0x4 bytes (sizeof)  
        {                                                                    
            /*0x004*/             UINT8        BaseMid;                                            
            /*0x005*/             UINT8        Flags1;                                             
            /*0x006*/             UINT8        Flags2;                                             
            /*0x007*/             UINT8        BaseHi;                                             
        }Bytes;                                                              
        struct                            // 10 elements, 0x4 bytes (sizeof) 
        {                                                                    
            /*0x004*/             ULONG32      BaseMid : 8;     // 0 BitPosition                   
            /*0x004*/             ULONG32      Type : 5;        // 8 BitPosition                   
            /*0x004*/             ULONG32      Dpl : 2;         // 13 BitPosition                  
            /*0x004*/             ULONG32      Pres : 1;        // 15 BitPosition                  
            /*0x004*/             ULONG32      LimitHi : 4;     // 16 BitPosition                  
            /*0x004*/             ULONG32      Sys : 1;         // 20 BitPosition                  
            /*0x004*/             ULONG32      Reserved_0 : 1;  // 21 BitPosition                  
            /*0x004*/             ULONG32      Default_Big : 1; // 22 BitPosition                  
            /*0x004*/             ULONG32      Granularity : 1; // 23 BitPosition                  
            /*0x004*/             ULONG32      BaseHi : 8;      // 24 BitPosition                  
        }Bits;                                                               
    }HighWord;                                                               
}KGDTENTRY, *PKGDTENTRY;

我们对照着GDTENTRY的结构体,得出BaseLow = 0xbc00 , BaseMid = 0xf6 , BaseHi = 0x83,于是就得到了一个地址 0x83f6bc00 。

是不是很熟悉,没错,这就是我们KPCR的地址,也就是说CPU是按照段选择符的方式来解析FS寄存器的。

回到我们的问题,IDTENTRY中的selector域也是一个段选择符,它所解析出来的地址,就是我们要加上的Base,得到真正的中断例程的地址。

还是以 3号中断为例,selector = 0x08,二进制表示为1 0 00,也就是说是GDT表中的索引为1的项, 

kd> db 80b95000+0x8
80b95008  ff ff 00 00 00 9b cf 00

可以得出 BaseLow =  0 , BaseMid = 0 , BaseHi = 0 ,得出的Base = 0;

所以真的执行的例程地址就是我们的lpInterruptFunc 。

既然中断向量的真正执行地址要经过GDT的查询,那么如果我们替换GDT中的内容,使最后CPU得到的Base = NewKiTrap03 - KiTrap03,就可以达到对IDT隐蔽性Hook的目的。但是这样又会出现一个问题,很多IDT处理例程的selector都是0x08,就是说和KiTrap03的段索引是一样的,也就是说我们不能直接替换KiTrap03对应的GDT中的内容,而是应该找一个没有用过的GDT表项,然后将KiTrap03的selector的段索引指向我们选定的GDT表项。

 

typedef struct _KGDTENTRY                 // 3 elements, 0x8 bytes (sizeof)  
{                                                                            
    /*0x000*/     UINT16       LimitLow;                                                   
    /*0x002*/     UINT16       BaseLow;                                                    
    union                                 // 2 elements, 0x4 bytes (sizeof)  
    {                                                                        
        struct                            // 4 elements, 0x4 bytes (sizeof)  
        {                                                                    
            /*0x004*/             UINT8        BaseMid;                                            
            /*0x005*/             UINT8        Flags1;                                             
            /*0x006*/             UINT8        Flags2;                                             
            /*0x007*/             UINT8        BaseHi;                                             
        }Bytes;                                                              
        struct                            // 10 elements, 0x4 bytes (sizeof) 
        {                                                                    
            /*0x004*/             ULONG32      BaseMid : 8;     // 0 BitPosition                   
            /*0x004*/             ULONG32      Type : 5;        // 8 BitPosition                   
            /*0x004*/             ULONG32      Dpl : 2;         // 13 BitPosition                  
            /*0x004*/             ULONG32      Pres : 1;        // 15 BitPosition                  
            /*0x004*/             ULONG32      LimitHi : 4;     // 16 BitPosition                  
            /*0x004*/             ULONG32      Sys : 1;         // 20 BitPosition                  
            /*0x004*/             ULONG32      Reserved_0 : 1;  // 21 BitPosition                  
            /*0x004*/             ULONG32      Default_Big : 1; // 22 BitPosition                  
            /*0x004*/             ULONG32      Granularity : 1; // 23 BitPosition                  
            /*0x004*/             ULONG32      BaseHi : 8;      // 24 BitPosition                  
        }Bits;                                                               
    }HighWord;                                                               
}KGDTENTRY, *PKGDTENTRY;

typedef struct _IDTR{
    USHORT   IDT_limit;
    USHORT   IDT_LOWbase;
    USHORT   IDT_HIGbase;
}IDTR,*PIDTR;

typedef struct _IDTENTRY
{
    unsigned short LowOffset;
    unsigned short selector;
    unsigned char retention:5;
    unsigned char zero1:3;
    unsigned char gate_type:1;
    unsigned char zero2:1;
    unsigned char interrupt_gate_size:1;
    unsigned char zero3:1;
    unsigned char zero4:1;
    unsigned char DPL:2;
    unsigned char P:1;
    unsigned short HiOffset;
} IDTENTRY,*PIDTENTRY;

typedef struct _X86_KTRAP_FRAME {
    ULONG   DbgEbp;
    ULONG   DbgEip;
    ULONG   DbgArgMark;
    ULONG   DbgArgPointer;
    ULONG   TempSegCs;
    ULONG   TempEsp;
    ULONG   Dr0;
    ULONG   Dr1;
    ULONG   Dr2;
    ULONG   Dr3;
    ULONG   Dr6;
    ULONG   Dr7;
    ULONG   SegGs;
    ULONG   SegEs;
    ULONG   SegDs;
    ULONG   Edx;
    ULONG   Ecx;
    ULONG   Eax;
    ULONG   PreviousPreviousMode;
    ULONG   ExceptionList;
    ULONG   SegFs;
    ULONG   Edi;
    ULONG   Esi;
    ULONG   Ebx;
    ULONG   Ebp;
    ULONG   ErrCode;

    ULONG   Eip;
    ULONG   SegCs;
    ULONG   EFlags;
    ULONG   HardwareEsp;    // WARNING - segSS:esp are only here for stacks
    ULONG   HardwareSegSs;  // that involve a ring transition.
    ULONG   V86Es;          // these will be present for all transitions from
    ULONG   V86Ds;          // V86 mode
    ULONG   V86Fs;
    ULONG   V86Gs;
} X86_KTRAP_FRAME, *PX86_KTRAP_FRAME;

 

KIRQL  Irql;
ULONG_PTR g_jmp_offset = 0;
ULONG_PTR  OldBase;
PKGDTENTRY NewGDTAddr;
ULONG_PTR g_OrigKiTrap03;
unsigned short OldSelector;
IDTENTRY*  idt_entries;

__declspec(naked) void NewKiTrap03()
{
    __asm
    {
            push    0   ;ErrorCode
            push    ebp
            push    ebx
            push    esi
            push    edi
            push    fs
            mov     ebx,30h
            mov     fs,bx
            mov     ebx,dword ptr fs:[0]
            push    ebx
            sub     esp,4
            push    eax
            push    ecx
            push    edx
            push    ds
            push    es
            push    gs

            sub     esp,30h    //esp此时就指向陷阱帧

            push    esp         //FilterExceptionInfo自己清理了

            call   FilterExceptionInfo   //过滤函数

            add     esp , 0x30
            pop        gs
            pop        es
            pop        ds
            pop        edx
            pop        ecx
            pop        eax
            add        esp , 4
            pop        ebx
            pop        fs
            pop        edi
            pop        esi
            pop        ebx
            pop        ebp
            add     esp , 0x4
            jmp     g_OrigKiTrap03

    }
}

VOID __stdcall FilterExceptionInfo(PX86_KTRAP_FRAME pTrapFrame)
{

    //eip的值减一过int3,汇编代码分析中dec, 
    DbgPrint("Eip:%x\r\n",(pTrapFrame->Eip)-1);
}

NTSTATUS
DriverEntry(IN PDRIVER_OBJECT pDriverObj, IN PUNICODE_STRING pRegistryString)
{
    ULONG    oriaddr=0;
    ULONG    newaddr=0;
    PKGDTENTRY GDT_Addr;
    KGDTENTRY  GDTInfo;
    PKGDTENTRY Gdt_Addr3e0;
    PKGDTENTRY Gdt_Addr8;
    ULONG    jmpoffset=0;
    IDTR    idt_info;        
    unsigned short selector;

#ifdef _DBG
    __asm int 3
#endif
    pDriverObj->DriverUnload = DriverUnLoad;

    __asm 
    {
        sidt  idt_info            
        push edx
        sgdt [esp-2]
        pop edx
        mov GDT_Addr,edx

    }
    idt_entries = (IDTENTRY*) MAKELONG(idt_info.IDT_LOWbase,idt_info.IDT_HIGbase);
    g_OrigKiTrap03 = MAKELONG(idt_entries[3].LowOffset,idt_entries[3].HiOffset);
    jmpoffset    =    (ULONG)NewKiTrap03 - g_OrigKiTrap03;
    selector = idt_entries[1].selector;
    //我选择的是索引为0x10的,空白的GDT表项
    NewGDTAddr = GDT_Addr + 0x10;

    //保存原来的
     memcpy((UCHAR*)&OldBase,(char*)(&(NewGDTAddr->BaseLow)),2);
     memcpy((UCHAR*)&OldBase+2,(char*)(&(NewGDTAddr->HighWord.Bytes.BaseMid)),1);
     memcpy((UCHAR*)&OldBase+3,(char*)(&(NewGDTAddr->HighWord.Bytes.BaseHi)),1);

    //修改
    WPOFF();
    memcpy((char*)(&(NewGDTAddr->BaseLow)),(UCHAR*)&jmpoffset,2);
    memcpy((char*)(&(NewGDTAddr->HighWord.Bytes.BaseMid)),(UCHAR*)(&jmpoffset)+2,1);
    memcpy((char*)(&(NewGDTAddr->HighWord.Bytes.BaseHi)),(UCHAR*)(&jmpoffset)+3,1);
    OldSelector = idt_entries[3].selector;
    idt_entries[3].selector  = 0x80;
    WPON();

    return  STATUS_SUCCESS;
}

void DriverUnLoad(PDRIVER_OBJECT pDriverObject)
{
    WPOFF();
    memcpy((char*)(&(NewGDTAddr->BaseLow)),(UCHAR*)(&OldBase),2);
    memcpy((char*)(&(NewGDTAddr->HighWord.Bytes.BaseMid)),(UCHAR*)(&OldBase)+2,1);
    memcpy((char*)(&(NewGDTAddr->HighWord.Bytes.BaseHi)),(UCHAR*)(&OldBase)+3,1);
    idt_entries[3].selector = OldSelector;
    WPON();
}


VOID WPOFF()
{
    ULONG_PTR cr0 = 0;
    Irql = KeRaiseIrqlToDpcLevel();
    cr0 =__readcr0();
    cr0 &= 0xfffffffffffeffff;
    __writecr0(cr0);

}

VOID WPON()
{

    ULONG_PTR cr0=__readcr0();
    cr0 |= 0x10000;
    __writecr0(cr0);
    KeLowerIrql(Irql);
}

 

posted on 2015-07-31 19:58  _懒人  阅读(4238)  评论(0编辑  收藏  举报

导航