首页
社区
课程
招聘
[原创]【DLL注入编写与分析系列之一】x64平台SSDT HOOK之NtResumeThread注入
发表于: 2022-2-7 21:29 15865

[原创]【DLL注入编写与分析系列之一】x64平台SSDT HOOK之NtResumeThread注入

2022-2-7 21:29
15865

本文的思路、代码、调试参考了大量资料,历经艰难的调试后,现在可直接用VS2019在win7_sp1_x64系统上运行。
编写本文的目的,重点在于解释代码编写的思路,做到举一反三的目的,为软件逆向、代码HOOK打开思路,提升逆向代码编写水平。

System Service Descript Table(SSDT):主要处理 Kernel32.dll中的系统调用,如openProcess,ReadFile等,主要在ntoskrnl.exe中实现(微软有给出 ntoskrnl源代码)
因为x64位中ssdt表是加密的,ssdt中的每一项占4个字节但并不是对应的系统服务的地址,因为x64中地址为64位而ssdt每一项只有4个字节32位所以无法直接存放服务的地址。其实际存储的4个字节的前28位表示的是对应的系统服务相对于SSDT表基地址的偏移,而后4位如果对应的服务的参数个数小于4则其值为0,不小于4则为参数个数减去4。所以我们在ssdt hook时向ssdt表项中填入的函数得在ntoskrnl.exe模块中,原因是因为函数到SSDT表基地址的偏移大小小于4个字节。所以我们选取一个ntoskrnl.exe中很少使用的函数KeBugCheckEx作为中转函数,将需要hook的ssdt项的改为KeBugCheckEx函数,然后在inlinehook KeBugCheck函数,jmp到我们的函数中进行过滤。

MSR(Model Specific Register)是x86架构中的概念,指的是在x86架构处理器中,一系列用于控制CPU运行、功能开关、调试、跟踪程序执行、监测CPU性能等方面的寄存器。
MSR寄存器的雏形开始于Intel 80386和80486处理器,到Intel Pentium处理器的时候,Intel就正式引入RDMSR和WRMSR两个指令用于读和写MSR寄存器,这个时候MSR就算被正式引入。在引入RDMSR和WRMSR指令的同时,也引入了CPUID指令,该指令用于指明具体的CPU芯片中,哪些功能是可用的,或者这些功能对应的MSR寄存器是否存在,软件可以通过CPUID指令查询某些功能是否在当前CPU上是否支持。
去AMD的官网,下载AMD芯片手册:
https://developer.amd.com/resources/developer-guides-manuals/
之后,查找MSRC000_0082,如下图:

可见,C000_0082是SYSCALL_Target_Address,在windbg里面,我们看看这个地址是什么。

KeServiceDescriptorTableShadow就是SSDT表的地址。记住这里,后面代码中要用到。

x64下面常用的跳转指令:
1、mov rax,addr;jmp rax
48 B8 qword_addr -> mov rax,qword_addr
FF E0 -> jmp rax
2、push / ret
68 dword_addr -> push dword_addr
c3 -> ret
注意:push的立即数是dword,但由于对齐的关系,实际占用64位。如果跳转地址超过2GB,要使用其他指令。push的立即数不能是64位。
3、jmp、call
FF15 dword_offset qword_addr -> call[qword_addr]
FF25 dword_offset qword_addr -> jmp [qword_addr]
简单说明下:
在下条指令偏移dword_offset处,是要跳转的地址qword_addr。当dword_offset=0x00000000时,qword_addr就在指令的第6个字节。
但是在x86下,:
FF15 dword_addr -> call [dword_addr]
FF25 dword_addr -> jmp [dword_addr]
没有偏移,后面直接跟着绝对地址。

我在写shellcode的时候,出现了movaps错误,一直以为代码有问题,后来经过排查、搜索,发现问题是由于在shellcode里面call函数之前,要保证rsp是16字节对齐的。我在这里耽误了一些时间的,你在使用的时候,尤其要注意。

首先要明白,win7_sp1_x64下,CreateProcess的创建流程是这样的:

此时,主进程的大部分创建工作已经完成(尤其是ntdll.dll已经加载,尤其重要),主线程刚刚被创建,此时还没有加载除ntdll.dll之外的其他dll。而GetModuleHandle和GetProcAddress函数在kernel32.dll里,所以需要自己去实现这两个函数,获取dll的句柄和函数地址。kernel32.dll是在进程创建完成、主线程初始化输入表时才载入的。

###2.6.1 GetModuleHandle实现
GetModuleHandle是为了获取dll基址,现在要获取ntdll.dll基址,是通过暴力搜索内存实现的。
主要用到的两个函数是:
ZwQueryVirtualMemory
ZwQuerySystemInformation
其中,
ZwQuerySystemInformation用到的类型是SystemEmulationBasicInformation。很重要,我找了很多资料,才找到这个参数。
ZwQueryVirtualMemory用到的类型是MemoryBasicInformation,和另外一个未文档化的参数MEMORY_SECTION_NAME。

此时要注意的是x64和x86获取系统的信息稍微有所不同:
x86:ZwQuerySystemInformation第一个参数是SystemBasicInformation

###2.6.1 GetProcAddress实现
导出表有一个结构,是IMAGE_EXPORT_DIRECTORY,主要注意里面的三个字段:
1、AddressOfNames
2、AddressOfNameOrdinals
3、AddressOfFunctions
4、NumberOfNames

代码实现的基本思路是:
循环NumberOfNames次,在AddressOfName地址里面查找targetFunc,记住找到函数时的循环次数i,通过AddressOfNameOrdinals找到函数的索引号,比如是b,最后函数地址为AddressOfFunctions[b]。
代码基本流程如下:

通过循环ImageExportDirectory->NumberOfNames次,比较名字是否和NtResumeThread相等,不等于循环,等于的话,就通过AddressOfNameOrdinals[i] 得到函数索引FunctionOrdinal,然后函数地址就等于
FunctionAddress = (PVOID)((UINT8*)MappingBaseAddress + AddressOfFunctions[FunctionOrdinal])

这里的实现是为了得到ntdll!ldrLoadDLL函数地址。

Hook函数nt!NtResumeThread的SSDT表,修改为KeBugCheckEx函数地址,然后InLine HOOK到FakeResumeThead函数去执行,最后执行shellcode。

通过PUCHAR StartSearchAddress =(PUCHAR)__readmsr(0xC0000082);作为起始地址,然后在PAGE_SIZE范围搜索特征码(4c\8d\15),然后地址加上偏移就是SSDT表地址了。

在win7_sp1_x64系统,反汇编ntdll可知:

从函数地址偏移4个或1个字节得到此函数在SSDT表的索引。

原始函数地址 = SSDT基址 + 函数索引号得到的偏移地址>>4

A、通过__readcr0寄存器关闭写保护
B、InlineHook:把KeBugCheck的前面14个字节保存,然后改成跳转到shellcode执行。跳转汇编:
"\xFF\x25\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";
C、计算KeBugCheck在SSDT表中基于ServerTableBase的偏移值,然后填入之前NtResumeThread的索引位置。
这样,执行NtResumeThread函数,实际先执行KeBugCheck函数,又由于KeBugCheck被HOOK了,在KeBugCheck函数里面执行shellcode。

在这个函数里,我们主要做的事情如下:
1、通过PsGetProcessImageFileName函数,获取要注入进程的名字;
2、暴力搜索内存,获得ntdll.dll的基址,通过导出表获取ldrLoadDLL地址;
2、通过KeStackAttachProcess函数附加到进程;
3、通过PsGetContextThread函数获取进程的rip
4、通过PsSetContextThread函数修改进程的rip。

shellcode结构体如下:

汇编代码如下:

主要功能是:
1、加载要注入的dll;
2、通过ret指令返回之前线程的rip,跳转到之前线程的指令处执行;
3、重点是,call函数的时候rsp要16字节对齐,否则会出现movaps错误;
4、之所以push3个rax,一个是用于占位,一个是用于让rsp实现16字节对齐,一个是保存rax寄存器。

代码暂不上传。已经把关键点说得很详细了,我相信根据看雪x86的代码,你也能够写出x64系统下的代码了。当然,贴子如果精华了,或者需要的同学很多,再上传代码。

5: kd> rdmsr c0000082
msr[c0000082] = fffff800`0408eec0
5: kd> uf fffff800`0408eec0
Flow analysis was incomplete, some code may be missing
nt!KiSystemCall64:
fffff800`0408eec0 0f01f8          swapgs
fffff800`0408eec3 654889242510000000 mov   qword ptr gs:[10h],rsp
fffff800`0408eecc 65488b2425a8010000 mov   rsp,qword ptr gs:[1A8h]
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
fffff800`0408eff2 4c8d1547a92300  lea  r10,[nt!KeServiceDescriptorTable (fffff800`042c9940)]
fffff800`0408eff9 4c8d1d80a92300  lea r11,[nt!KeServiceDescriptorTableShadow (fffff800`042c9980)]
fffff800`0408f000 f7830001000080000000 test dword ptr [rbx+100h],80h
5: kd> rdmsr c0000082
msr[c0000082] = fffff800`0408eec0
5: kd> uf fffff800`0408eec0
Flow analysis was incomplete, some code may be missing
nt!KiSystemCall64:
fffff800`0408eec0 0f01f8          swapgs
fffff800`0408eec3 654889242510000000 mov   qword ptr gs:[10h],rsp
fffff800`0408eecc 65488b2425a8010000 mov   rsp,qword ptr gs:[1A8h]
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
fffff800`0408eff2 4c8d1547a92300  lea  r10,[nt!KeServiceDescriptorTable (fffff800`042c9940)]
fffff800`0408eff9 4c8d1d80a92300  lea r11,[nt!KeServiceDescriptorTableShadow (fffff800`042c9980)]
fffff800`0408f000 f7830001000080000000 test dword ptr [rbx+100h],80h
Kernel32!CreateProcess
Kernel32!CreateProcessW
Kernel32!CreateProcessInternalW
ntdll!NtCreateProcessEx
ntdll!NtCreateThread
ntdll!NtResumeThread
Kernel32!CreateProcess
Kernel32!CreateProcessW
Kernel32!CreateProcessInternalW
ntdll!NtCreateProcessEx
ntdll!NtCreateThread
ntdll!NtResumeThread
 
 
ImageExportDirectory=NtHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress
ImageExportDirectory=NtHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress
AddressOfFunctions =MappingBaseAddress + ImageExportDirectory->AddressOfFunctions);
AddressOfNames = MappingBaseAddress + ImageExportDirectory->AddressOfNames);
AddressOfNameOrdinals =MappingBaseAddress+ImageExportDirectory->AddressOfNameOrdinals);
AddressOfFunctions =MappingBaseAddress + ImageExportDirectory->AddressOfFunctions);
AddressOfNames = MappingBaseAddress + ImageExportDirectory->AddressOfNames);
AddressOfNameOrdinals =MappingBaseAddress+ImageExportDirectory->AddressOfNameOrdinals);
 
kd> rdmsr c0000082
   msr[c0000082] = f ffff800`04038bc0
   kd> uf ffff800`04038bc0
   nt!KiSystemCall64:
   ......
   nt!KiSystemServiceRepeat:
   fffff800`03f07a72 4c8d1587be1f00  lea     r10,[nt!KeServiceDescriptorTable (fffff800`040be940)]
   fffff800`03f07a79 4c8d1d40bf1f00  lea     r11,[nt!KeServiceDescriptorTableShadow (fffff800`040be980)]
kd> rdmsr c0000082
   msr[c0000082] = f ffff800`04038bc0
   kd> uf ffff800`04038bc0
   nt!KiSystemCall64:
   ......
   nt!KiSystemServiceRepeat:
   fffff800`03f07a72 4c8d1587be1f00  lea     r10,[nt!KeServiceDescriptorTable (fffff800`040be940)]
   fffff800`03f07a79 4c8d1d40bf1f00  lea     r11,[nt!KeServiceDescriptorTableShadow (fffff800`040be980)]
 
 
__OldNtResumeThreadAddress =
SSDTAddress->ServiceTableBase+SSDTAddress->ServiceTableBase[_FunctionIndexInSSDT]>>4
__OldNtResumeThreadAddress =
SSDTAddress->ServiceTableBase+SSDTAddress->ServiceTableBase[_FunctionIndexInSSDT]>>4
typedef struct _INJECT_DATA
{
    CHAR ShellCode[0xA0];
    /*offset = 0xA0*/PWCHAR PathToFile;//LdrLoadDll的第一个参数
    /*offset = 0xA8*/ULONG64 DllCharacteristics;
    /*offset = 0xB0*/PUNICODE_STRING pDllPath;//PUNICODE_STRING DllPath
    /*offset = 0xB8*/PHANDLE ModuleHandle; //Dll句柄
    /*offset = 0xC0*/ULONG64 AddrOfLdrLoadDll;//LdrLoadDll地址
    /*offset = 0xC8*/ULONG64 OriginalEIP;//原线程的EIP
    /*offset = 0xD0*/UNICODE_STRING usDllPath;//Dll路径
    /*offset = 0xE0*/WCHAR wDllPath[256];//Dll路径,也就是usDllPath中的Buffer
}INJECT_DATA;
typedef struct _INJECT_DATA
{

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2022-2-9 09:50 被ExploitCN编辑 ,原因:
收藏
免费 6
支持
分享
最新回复 (11)
雪    币: 1641
活跃值: (3601)
能力值: (RANK:15 )
在线值:
发帖
回帖
粉丝
2
写的太好了,精华!
2022-2-8 08:28
0
雪    币: 122
活跃值: (7471)
能力值: ( LV9,RANK:335 )
在线值:
发帖
回帖
粉丝
3
写的太好了,精华!
2022-2-8 08:31
0
雪    币: 665
活跃值: (1051)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
写的太好了,精华!
2022-2-8 11:48
0
雪    币: 52
活跃值: (186)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
写的太好了,精华!
2022-2-8 14:00
0
雪    币: 284
活跃值: (407)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
写的太好了,精华!
2022-2-8 17:19
0
雪    币: 1378
活跃值: (3067)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
写的太好了,精华!
2022-2-8 21:13
0
雪    币: 1095
活跃值: (4177)
能力值: ( LV5,RANK:69 )
在线值:
发帖
回帖
粉丝
8
写的太好了,精华!
2022-2-9 08:47
0
雪    币: 183
活跃值: (2427)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
9
写的太好了,精华!
2022-2-11 13:04
0
雪    币: 8851
活跃值: (4168)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
写的太好了,精华!
2022-6-14 09:36
0
雪    币: 6184
活跃值: (4191)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
11
写的太好了,精华!上代码了
2022-8-15 11:52
0
雪    币: 2325
活跃值: (2304)
能力值: ( LV6,RANK:89 )
在线值:
发帖
回帖
粉丝
12
逆天好评返现
2023-5-29 14:09
0
游客
登录 | 注册 方可回帖
返回
//