• 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
    ",(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);
    }
  • 相关阅读:
    面向对象-类与实例化对象
    面向对象-面向对象与面向过程优缺点
    pyCharn文件模板变量
    安装pycrypto以及报错处理
    文件-读、写、追加已经文件操作
    文件-读取与编码检测
    运算-三元运算
    三方工具-sqlacodegen
    函数-生成器之斐波拉契数列
    python内存-fromkeys
  • 原文地址:https://www.cnblogs.com/lanrenxinxin/p/4692013.html
Copyright © 2020-2023  润新知