• win 64 ring0 inline hook


    以下内容参考黑客防线2012合订本316页

    1.首先要注意的问题

    inline hook 不能截断指令. 也就是说修改目标函数的指令实现跳转到自己的函数里面时, 不能截断掉目标函数的指令.

    因为在自己的函数里面还要调用原来的函数,但是原来的函数如果被截断那就没办法正常执行代码

    2.反汇编引擎. 

    用来动态解析内存中指令, 这里就是用来获取所需字节数的最少修改指令数所占的大小. 也就是防止出现指令截断.

    使用LDE64 (网上就有).

    uchar szShellCode[12800]={...};
    typedef int (*LDE_DISASM)(void *p, int dw);
    LDE_DISASM LDE;
    
    void LDE_init()
    {
        LDE=ExAllocatePool(NonPagedPool,12800);
        memcpy(LDE,szShellCode,12800);
    }

    使用:

    ULONG GetPatchSize(PUCHAR Address)
    {
        ULONG LenCount = 0, Len = 0;
        while (LenCount <= 14)    //至少需要14字节
        {
            Len = LDE(Address, 64);
            Address = Address + Len;
            LenCount = LenCount + Len;
        }
        return LenCount;
    }

    关于inline hook思路:

    首先声明全局变量来存储 A 代码.  A代码就是原始的前n字节.

    声明全局变量存储B代码.  是A代码+jmp C  . 这个C是原始函数+sizeof(A)

    A代码用来unhook的 , B代码用来从自己的函数跳回去原始函数继续执行.

    所以先获取跳转到自己函数的机器码. D

    这个机器码是通过以下实现的:

      

        这里跨4G跳转使用的方式是jmp qword ptr [rip], 对应的机器码是ff2500000000 6个字节
        当执行到这条指令时,假如这条指令地址是这样
        0000000000000000   jmp qword ptr [rip]
        那么又假如下一条指令这样:
        0000000000000006   cccccccccccccccc  (这里的汇编码为int3 int3 int3...int3 共8个)
        那么指令执行完jmp指令后,将跑到地址为cccccccccccccccc处的代码执行,而不是执行int3 指令.
        简单来说就是把rip当成普通寄存器使用了. 因此至少需要14字节的数据来跳转任意内存处.
        为什么是至少14字节呢? 因为指令不能截断,当hook时不能直接只改掉前14字节,这样可能会因为
        某条指令横跨第14字节,这样hook就会将这条指令截断. 因此需要通过反汇编引擎对字节码进行
        解析,直到解析的指令内容>=14字节.将这些指令内容大小作为修改的大小,多余的填充nop.

    然后将前n字节保存到A.

    设置好B

    再将D写入到原始函数前n字节.  这样就实现inline hook.

    在自己的函数里面可以通过调用B来执行正确的原始函数. 

    unhook就很简单, 将A写回原始函数前n字节即可. 测试结果:

    最后附上大佬的代码:(有自己写的一些注释)

    #include <ntddk.h>
    
    #define kmalloc(_s) ExAllocatePoolWithTag(NonPagedPool, _s, 'SYSQ')
    #define kfree(_p) ExFreePool(_p)
    
    typedef NTSTATUS(__fastcall *PSLOOKUPPROCESSBYPROCESSID)(HANDLE ProcessId, PEPROCESS *Process);
    ULONG64 my_eprocess = 0;            //待保护进程的eprocess
    ULONG pslp_patch_size = 0;        //PsLookupProcessByProcessId被修改了N字节
    PUCHAR pslp_head_n_byte = NULL;    //PsLookupProcessByProcessId的前N字节数组
    PVOID ori_pslp = NULL;            //PsLookupProcessByProcessId的原函数
    
    
    KIRQL WPOFFx64()
    {
        KIRQL irql = KeRaiseIrqlToDpcLevel();
        UINT64 cr0 = __readcr0();
        cr0 &= 0xfffffffffffeffff;
        __writecr0(cr0);
        _disable();
        return irql;
    }
    
    void WPONx64(KIRQL irql)
    {
        UINT64 cr0 = __readcr0();
        cr0 |= 0x10000;
        _enable();
        __writecr0(cr0);
        KeLowerIrql(irql);
    }
    
    //传入:被HOOK函数地址,原始数据,补丁长度
    VOID UnhookKernelApi(IN PVOID ApiAddress, IN PVOID OriCode, IN ULONG PatchSize)
    {
        KIRQL irql;
        irql = WPOFFx64();
        memcpy(ApiAddress, OriCode, PatchSize);
        WPONx64(irql);
    }
    
    NTSTATUS Proxy_PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process)
    {
        NTSTATUS st;
        st = ((PSLOOKUPPROCESSBYPROCESSID)ori_pslp)(ProcessId, Process);
        if (NT_SUCCESS(st))
        {
            if (*Process == (PEPROCESS)my_eprocess)
            {
                *Process = 0;
                st = STATUS_ACCESS_DENIED;
            }
        }
        return st;
    }
    
    void *GetFunctionAddr(PCWSTR FunctionName)
    {
        UNICODE_STRING UniCodeFunctionName;
        RtlInitUnicodeString(&UniCodeFunctionName, FunctionName);
        return MmGetSystemRoutineAddress(&UniCodeFunctionName);
    }
    /*
        这里跨4G跳转使用的方式是jmp qword ptr [rip], 对应的机器码是ff2500000000 6个字节
        当执行到这条指令时,假如这条指令地址是这样
        0000000000000000   jmp qword ptr [rip]
        那么又假如下一条指令这样:
        0000000000000006   cccccccccccccccc  (这里的汇编码为int3 int3 int3...int3 共8个)
        那么指令执行完jmp指令后,将跑到地址为cccccccccccccccc处的代码执行,而不是执行int3 指令.
        简单来说就是把rip当成普通寄存器使用了. 因此至少需要14字节的数据来跳转任意内存处.
        为什么是至少14字节呢? 因为指令不能截断,当hook时不能直接只改掉前14字节,这样可能会导致
        某条指令横跨第14字节,如果这样hook就会将这条指令截断. 因此需要通过反汇编引擎对字节码进行
        解析,直到解析的指令内容>=14字节.将这些指令内容大小作为修改的大小,多余的填充nop.
    
    */
    
    ULONG GetPatchSize(PUCHAR Address)
    {
        ULONG LenCount = 0, Len = 0;
        while (LenCount <= 14)    //至少需要14字节
        {
            Len = LDE(Address, 64);
            Address = Address + Len;
            LenCount = LenCount + Len;
        }
        return LenCount;
    }
    
    
    //传入:待HOOK函数地址,代理函数地址,接收跳回原始函数代码内容的地址的指针,接收补丁长度的指针;返回:原来头N字节的数据
    PVOID HookKernelApi(IN PVOID ApiAddress, IN PVOID Proxy_ApiAddress, OUT PVOID *Original_ApiAddress, OUT ULONG *PatchSize)
    {
        //这里面有2个是动态分配的,需要在程序卸载时释放掉,是head_n_byte,ori_func
        //它们被赋值到返回值和参数Original_ApiAddress了
        KIRQL irql;
        UINT64 tmpv;
        //一个是保存被hook函数前n字节码,一个是保存代理函数调用原始函数用的代码,不能直接调用原始函数,因为已经被改了.
        PVOID head_n_byte, ori_func;
    
        UCHAR jmp_code[] = "xFFx25x00x00x00x00xFFxFFxFFxFFxFFxFFxFFxFF";
        UCHAR jmp_code_orifunc[] = "xFFx25x00x00x00x00xFFxFFxFFxFFxFFxFFxFFxFF";
        //How many bytes shoule be patch
        *PatchSize = GetPatchSize((PUCHAR)ApiAddress);
        //step 1: Read current data
        head_n_byte = kmalloc(*PatchSize);
        irql = WPOFFx64();
        memcpy(head_n_byte, ApiAddress, *PatchSize);
        WPONx64(irql);
        //step 2: Create ori function
        ori_func = kmalloc(*PatchSize + 14);    //原始机器码+跳转机器码
        RtlFillMemory(ori_func, *PatchSize + 14, 0x90);
        tmpv = (ULONG64)ApiAddress + *PatchSize;    //跳转到没被打补丁的那个字节
        DbgPrint("ApiAddress is %p
    ", ApiAddress);
        DbgBreakPoint();
        memcpy(jmp_code_orifunc + 6, &tmpv, 8);
    
        memcpy((PUCHAR)ori_func, head_n_byte, *PatchSize);
        memcpy((PUCHAR)ori_func + *PatchSize, jmp_code_orifunc, 14);
        *Original_ApiAddress = ori_func;
        //step 3: fill jmp code
        tmpv = (UINT64)Proxy_ApiAddress;
        memcpy(jmp_code + 6, &tmpv, 8);
        //step 4: Fill NOP and hook
        irql = WPOFFx64();
        RtlFillMemory(ApiAddress, *PatchSize, 0x90);
        memcpy(ApiAddress, jmp_code, 14);
        WPONx64(irql);
        //return ori code
        
        return head_n_byte;
    }
    
    
    
    
    
    
    
    
    VOID HookPsLookupProcessByProcessId()
    {
        //pslp_head_n_byte和ori_pslp 最后需要释放掉
        pslp_head_n_byte = HookKernelApi(GetFunctionAddr(L"PsLookupProcessByProcessId"),
            (PVOID)Proxy_PsLookupProcessByProcessId,
            &ori_pslp,
            &pslp_patch_size);
    }
    
    VOID UnhookPsLookupProcessByProcessId()
    {
        UnhookKernelApi(GetFunctionAddr(L"PsLookupProcessByProcessId"),
            pslp_head_n_byte,
            pslp_patch_size);
    }
  • 相关阅读:
    第06组Alpha冲刺(4/6)
    第06组Alpha冲刺(3/6)
    第06组Alpha冲刺(2/6)
    第06组 Alpha冲刺 (1/6)
    08-js函数
    07-数组
    06-js分支
    05-js运算符
    04-js变量
    03-css3D转换
  • 原文地址:https://www.cnblogs.com/freesec/p/7623591.html
Copyright © 2020-2023  润新知