ring0下的Inline hook

admin 2022年3月2日12:52:18评论75 views字数 5998阅读19分59秒阅读模式

首发于先知社区:

https://xz.aliyun.com/t/10913

前言

Inline hook是直接在以前的函数替里面修改指令,用一个跳转或者其他指令来达到挂钩的目的。这是相对普通的hook来说,因为普通的hook只是修改函数的调用地址,而不是在原来的函数体里面做修改。一般来说,普通的hook比较稳定使用。inline hook 更加高级一点,一般也跟难以被发现。ring3的Inline hook在之前已经实现过了,再看看ring0的Inline hook该如何实现。

探究及实现

这里本来调用链应该是OpenFile -> ZwOpenFile,这里在od里面应该可以看到,这里我就用windbg直接找到这个ring0函数。

首先在windbg里面定位到ZwOpenFile函数,可以看到它的偏移为0x74

ring0下的Inline hook

通过SSDT表找到所有的内核函数地址,再通过0x74偏移定位到ZwOpenFile函数

kd> dd KeServiceDescriptorTable
kd> dd 80505450 + 74 * 4
kd> u 8057b182
ring0下的Inline hook

我这里因为windbg的原因没有显示函数名称,到pchunter里面确认一下,地址确实是一样的

ring0下的Inline hook

那么我们要实现Inline hook,无论是使用E8 call,还是E9 jmp,都需要至少5个硬编码才能实现,所以这里我们找5个硬编码进行填入我们代码的操作,这里注意不能够找全局变量和重定位的地址,否则在进行还原的过程中可能地址已经发生改变导致程序不能够正常运行

mov ebp,esp
xor eax,eax
push eax
ring0下的Inline hook

首先我们写一个FilterNtOpenFile函数用来打印我们Inline hook后的一些信息,这里用到PsGetCurrentProcess获取进程的EPROCESS结构,在0x174偏移存放着进程名,我们通过打印进程名来查看一下哪些进程调用了NtOpenFile这个函数

ring0下的Inline hook

char* p = "r0 InlineHook";
void FilterNtOpenFile(char* p)
{
    KdPrint(("%srn", p));
    KdPrint(("name:%srn", (char*)PsGetCurrentProcess() + 0x174));
}

然后提供ServiceDescriptorEntry这个结构体并定义KeServiceDescriptorTable为 ntoskrnl.exe 所导出的全局变量

typedef struct ServiceDescriptorEntry {
    unsigned int* ServiceTableBase;
    unsigned int* ServiceCounterTableBase; 
    unsigned int NumberOfServices;
    unsigned char* ParamTableBase;
} ServiceDescriptorTableEntry_t, * PServiceDescriptorTableEntry_t;

__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;

这里我们再利用汇编来执行我们的汇编代码之后再jmp到原覆盖地址+5的地方,先用pushadpushfd保存寄存器

void _declspec(naked) NewNtOpenFile()
{
    __asm
    {
        pushad
        pushfd
        push p
        call FilterNtOpenFile
        popfd
        popad
        mov ebp, esp
        xor eax, eax
        push eax
        jmp ReAddress
    }
}

首先定义一个数组,用来存放E9jmp跳转的代码

UCHAR jmp_code[5] = "";

然后因为我们在8057b185这个地址开始覆盖,函数的起始地址为8057b182,所以偏移为3

ULONG ChangeAddr = 3;

然后通过KeServiceDescriptorTableServiceTableBase属性定位到NtOpenFile的起始地址,这里在PCHunter里面可以看到NtOpenFile的索引号为116

ring0下的Inline hook

StartAddr = KeServiceDescriptorTable.ServiceTableBase[116];

定义返回地址,用函数的开始地址+偏移+5即可得到返回地址

ReAddress = StartAddr + ChangeAddr + 5;

通过E9 jmp的计算公式还需要计算我们自己定义的函数newNtOpenKey相对于NtOpenFile的偏移量

ULONG jmpAddr = (ULONG)NewNtOpenFile - StartAddr - ChangeAddr - 5;

将跳转代码写入数组

jmp_code[0] = 0xE9;

*(ULONG*)&jmp_code[1] = jmpAddr;

这里就需要写入内存了,这里需要关闭页的只读保护,定义一个ShutPageProtect函数将CR0寄存器的WP位置0

//关闭页只读保护
__asm
    {
        push eax;
        mov eax, cr0;
        and eax, ~0x10000;    // 与0x10000想与后取反
        mov cr0, eax;
        pop eax;
        ret;
    }

关闭页保护之后首先将之前的硬编码保存,再进行覆盖

ShutPageProtect();
RtlCopyMemory(Old_code, (PVOID)(StartAddr + ChangeAddr), 5);
RtlCopyMemory((PVOID)(StartAddr + ChangeAddr), jmp_code, 5);

写入内存完毕之后再定义一个OpenPageProtect函数将CR0寄存器的WP恢复为1

void _declspec(naked) OpenPageProtect()
{
    __asm
    {
        push eax;
        mov eax, cr0;
        or eax, 0x10000;
        mov cr0, eax;
        pop eax;
        ret;
    }
}

那么到这里我们的hook代码就已经完成,因为我们已经将原来的硬编码存入了Old_code这个数组,这里我们编写UnHookNtOpenFile时利用RtlCopyMemory写会到原内存即可

void UnHookNtOpenFile()
{
    ULONG ChangeAddr = 3;

    ShutPageProtect();
    RtlCopyMemory((PVOID)(StartAddr + ChangeAddr), Old_code, 5);
    OpenPageProtect();

}

再就是加载驱动和卸载驱动,在加载驱动中调用HookNtOpenFile,在卸载驱动中调用UnHookNtOpenFile即可

//卸载驱动
void DriverUnload(DRIVER_OBJECT* obj)
{
    //卸载钩子
    UnHookNtOpenFile();

    KdPrint(("驱动卸载成功!n"));
}

/***驱动入口主函数***/
NTSTATUS DriverEntry(DRIVER_OBJECT* driver, UNICODE_STRING* path)
{
    KdPrint(("驱动启动成功!n"));

    //安装钩子
    HookNtOpenFile();

    driver->DriverUnload = DriverUnload;
    return STATUS_SUCCESS;
}

完整代码如下

#include <ntddk.h>

typedef struct ServiceDescriptorEntry {
    unsigned int* ServiceTableBase;
    unsigned int* ServiceCounterTableBase; 
    unsigned int NumberOfServices;
    unsigned char* ParamTableBase;
} ServiceDescriptorTableEntry_t, * PServiceDescriptorTableEntry_t;

__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;

// 关闭页只读保护
void ShutPageProtect();
// 开启页只读保护
void OpenPageProtect();
// 测试函数
void FilterNtOpenFile(char* p);
// 新NtOpenFile
void NewNtOpenFile();
// hook NtOpenFile
void HookNtOpenFile();
// unhook NtOpenFile
void UnHookNtOpenFile();


//关闭页只读保护
void _declspec(naked) ShutPageProtect()
{
    __asm
    {
        push eax;
        mov eax, cr0;
        and eax, ~0x10000;
        mov cr0, eax;
        pop eax;
        ret;
    }
}

//开启页只读保护
void _declspec(naked) OpenPageProtect()
{
    __asm
    {
        push eax;
        mov eax, cr0;
        or eax, 0x10000;
        mov cr0, eax;
        pop eax;
        ret;
    }
}

ULONG    StartAddr;
ULONG    ReAddress;
UCHAR    Old_code[5];

char* p = "r0 InlineHook";
void FilterNtOpenFile(char* p)
{
    KdPrint(("%srn", p));
    KdPrint(("name:%srn", (char*)PsGetCurrentProcess() + 0x174));
}

void _declspec(naked) NewNtOpenFile()
{
    __asm
    {
        pushad
        pushfd
        push p
        call FilterNtOpenFile
        popfd
        popad
        mov ebp, esp
        xor eax, eax
        push eax
        jmp ReAddress
    }
}

void HookNtOpenFile()
{
    // 存放跳转指令的数组
    UCHAR jmp_code[5] = "";
    // 在入口0x3处hook
    ULONG ChangeAddr = 3;
    // NtOpenFile函数的开始地址
    StartAddr = KeServiceDescriptorTable.ServiceTableBase[116];
    // 返回地址
    ReAddress = StartAddr + ChangeAddr + 5;
    // newNtOpenKey相对于NtOpenKey的偏移量
    ULONG jmpAddr = (ULONG)NewNtOpenFile - StartAddr - ChangeAddr - 5;

    // 使用jmp指令跳转,jmp = 0xE9
    jmp_code[0] = 0xE9;
    // 填入偏移地址
    *(ULONG*)&jmp_code[1] = jmpAddr;
    
    ShutPageProtect();

    RtlCopyMemory(Old_code, (PVOID)(StartAddr + ChangeAddr), 5);
    RtlCopyMemory((PVOID)(StartAddr + ChangeAddr), jmp_code, 5);

    OpenPageProtect();

}

void UnHookNtOpenFile()
{
    ULONG ChangeAddr = 3;

    ShutPageProtect();
    RtlCopyMemory((PVOID)(StartAddr + ChangeAddr), Old_code, 5);
    OpenPageProtect();

}

//卸载驱动
void DriverUnload(DRIVER_OBJECT* obj)
{
    //卸载钩子
    UnHookNtOpenFile();

    KdPrint(("驱动卸载成功!n"));
}

/***驱动入口主函数***/
NTSTATUS DriverEntry(DRIVER_OBJECT* driver, UNICODE_STRING* path)
{
    KdPrint(("驱动启动成功!n"));

    //安装钩子
    HookNtOpenFile();

    driver->DriverUnload = DriverUnload;
    return STATUS_SUCCESS;
}

实现效果如下

ring0下的Inline hook


往期推荐

DLL劫持详解

关于bypassuac的探究

r0下进程保护

进程强杀初探

记一次真实环境渗透(上)

记一次真实环境渗透(下)

基于filter的内存马

thinkphp 3.x反序列化分析


欢迎大家点赞,转发!

原文始发于微信公众号(红队蓝军):ring0下的Inline hook

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年3月2日12:52:18
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   ring0下的Inline hookhttps://cn-sec.com/archives/810336.html

发表评论

匿名网友 填写信息