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[] = "\xFF\x25\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";
    UCHAR jmp_code_orifunc[] = "\xFF\x25\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";
    //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\n", 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);
}

 

转载于:https://www.cnblogs.com/freesec/p/7623591.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本人使用易语言 也有10几年时间,至今留下来的也只是一些怀念和情节,还记得上一次在本论坛发帖是在10年前,期间也只是零星来论坛看看。平常使用也纯属兴趣爱好,并非科班出身,一些见解如若有误,忘大神包含。我们知道目前易语言是支持32位编译,后期估计也不会有所改善了,这似乎已经成为一门被放弃的失败品。既然如此面对如今64位系统的普及,易语言爱好者面对64位程序的操作层面上就显得有些无奈和悲哀。好在有着一些执着的爱好者不想放弃,依然在鼓励解决这些问题,如本论坛的一个开源模块WOW64Ext,就为易语言操作64位模块进程提供了一些基本的封装,本人也借此基础上封装了几个功能,作为进一步扩展,有兴许的朋友可以继续完善。 一:浅谈64位进程远程hook技术 关于HOOK这个话题,网络上铺天盖地并无新鲜,故在此我就不讲述什么是HOOK这些无聊话题了,本文主要阐述一些64位下远程HOOK与32位的主要区别。首先我们来看看要实现一个远程HOOK的构成顺序:1:在目标进程申请内存空间,存放我们截断后的穿插代码与HOOK原代码2:修改HOOK目标位置的指令为跳转至1申请的内存空间中3:穿插代码中把我们需要的寄存器或其他通过通讯手段传达到我们程序的回调接口中去,在这个过程中如果只需要取值,穿插代码不需要等待,如果需要修改生效,穿插代码需要等待回调接口的返回,并把修改内容写回。4:穿插代码最后跳回到HOOK位置长度的下一跳指令,或指定的位置。5:完成整个HOOK过程了解了整个过程看上去似乎很简单,确实要做到这个过程是不难的,只是要做到相对完美要考虑的情况有很多。比如对跳转使用的选择情况:HOOK跳转代码肯定是越短越好,像32位JMP跳转只需要5字节即可,但是在64位进程中情况确截然不同。32位进程寻址能力为4字节,而64位进程寻址能力变成了8字节,然而64位汇编中所有的跳转直接寻址只支持4字节,这在32位中当然不是什么问题,因为32位最大寻址本来就不会超越4字节,不存在超限的说法:但64位中想要达到长转移,必须借用寄存器或地址偏移,那么一般在64位HOOK的跳转代码在不影响寄存器的情况下一般使用如下办法FFFFFFFFFFFFFFFF作为跳转目标地址:为了不影响寄存器必须提前压入一个寄存器 --------------------------Push raxMov rax, FFFFFFFFFFFFFFFF JMP rax 或 call rax 在内部要取回rax ,这里注意JMP和call的区别,最后平栈 -------------------------- Push rax Mov rax,FFFFFFFFFFFFFFFF Push rax Ret 在内部要取回rax ,最后平栈 有的朋友看到这要问了 我不能直接 JMP FFFFFFFFFFFFFFFF或者 push FFFFFFFFFFFFFFFF 啊,您要这么问,我实在不知如何回答你,表示无语,您还是直接下个源码玩玩算了。 其他类似的列子我就不一一举例了,总结也是差不多形态,以上列子共占用13字节长度,这还是堆栈放在了内部平,否则还要+1个字节长度,如果放弃其中一个寄存器可以-1个字节长度,所以一般网上现有的64位hook一般都在12字节以上,但是一个好用的hook要占用13字节的长度,对我而言无疑无法忍受,难道真的没有其他办法了吗,要保护寄存器且支持长转移,是不是还有其他办法,那么其实是有办法的,就是通过JMP [rip] 机器码形态为 FF 25 00 00 00 00 这句代码占用6字节,那么这是什么意思呢 FF 25 = jmp ,00 00 00 00为偏移长度 对一个支持2G的字节转移长度,JMP [rip]在调试器中可以解释为 jmp qword ptr ds:[0x地址],对了,也就是读取这个偏移位置中的8字节数值作为跳转地址转移过去,如果偏移为00 00 00 00 那么就代表 JMP [rip]的下一条指令处8字节数据。想到这你也许会问  那么这个意思不就是JMP [rip]6字节+8字节长度吗,对如果是连起来确实如此,但是我们可以给他个偏移啊,不就可以分开了吗,我们只需要搜索同模块的其他位置中00或CC等连续8字节无用代码位置,把跳转的地址写入其中,那么JMP [rip]就可以通过偏移读取到跳转地址了。我们也就能实现6字节的HOOK,这个方式的亮点是改写长度小,且不影响寄存器和rsp堆栈指针,也算是达到曲线救国的目的。 比如对穿插代码中数据传递的问题:我们要获得16个通用寄存器RAX—R15的每个值,这些值我们又如何传递过去。一般远程HOOK数据传递使用消息或者远线程,因为这两种方式汇编改写量小一点,相对容易实现,在这我们不讨论远线程,我们来看看消息传递,一般是两个函数的选

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值