本文的思路、代码、调试参考了大量资料,历经艰难的调试后,现在可直接用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编辑
,原因: