【winapi】 CreateRemoteThread 实例与详解
概述:
CreateRemoteThread的简单使用 Demo
在阅读之前,建议大家带着几个问题
0x01说明
打开一个 GUI 进程,并在进程中调用 MessageBox 弹出消息框。
关键说明:由于注入只能传递一个参数,因此不能直接在目标进程中调用
MessageBox. 因此至少需要创建一个只有一个入参的函数来传递MessageBox的相关调用。
步骤说明:
- 根据传递的进程名获取进程
PID - 根据
PID打开目标进程 - 申请内存写入调用函数的地址
- 申请内存写入调用函数的参数
- 创建远程线程调用写入的函数
0x02代码
调用 CreateRemoteThread
1 | |
调用 ZwCreateThreadEx
1 | |
ZwCreateThreadEx 分析
ZwCreateThreadEx 属于 native 函数,微软官方并没有给出相关文档,本小节分析内容参考本文引用文章。
1 | |
区分32位和64位操作系统:
1 | |
0x03 CreateRemoteThread 函数调用分析
如下所示为上述代码在调用 CreateRemoteThread 之后的堆栈情况。
1 | |
通过WinDbg查看函数 CreateRemoteThead 在用户模式下的调用流程,观察这个调用情况可以确定在用户模式下,这个函数涉及到了三个dll模块(KERNEL32、KERNELBASE、ntdll)。而 CreateRemoteThead 这个API在KERNEL32模块中真正的函数名是 CreateRemoteThreadStub ,通过这个KERNEL32中的 CreateRemoteThreadStub API将参数转发到KERNELBASE 模块中的 CreateRemoteThreadEx 中,然后在 KERNELBASE 中调用ntdll模块中的 NtCreateThreadEx API,进入内核。待内核处理结束后获取返回值,进行返回值的处理并返回结果。
系统调用以及 SSDT(System Service Descriptor Table)文章推荐:强烈建议可以阅读这篇文章 Windows-Internals/System Architecture and Components/System Service Descriptor Table.md at main · Faran-17/Windows-Internals
函数说明
根据上述已经有的堆栈,结合 IDA 查看下函数调用。
CreateRemoteThreadStub
如下所示,为 CreateRemoteThreadStub 汇编和反汇编的代码,可以看到在 CreateRemoteThreadStub 中增加了一个参数,并且 dwCreationFlags 参数进行了校验,与标记 0x1004 进行与操作,规避了无效参数,含义如下所示,而也就是增加的参数,会存在注入的dll不启动的情况。
值 含义 0 线程在创建后立即运行。 CREATE_SUSPENDED0x00000004 线程以挂起状态创建,在调用 ResumeThread 函数之前不会运行。 STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 dwStackSize 参数指定堆栈的初始保留大小。 如果未指定此标志, dwStackSize 将指定提交大小。
CreateRemoteThreadEx
CreateRemoteThread函数位于KernelBase.dll中,最终调用了NtCreateThreadEx函数
如下所示为反汇编的 CreateRemoteThreadEx 函数。
这里可以关注下第七个参数,始终为 1,这个参数表示创建的线程始终为挂起状态。参考:安全之路 —— 利用内核函数实现注入系统进程 - 倚剑问天 - 博客园
函数原型:
1 | |
NtCreateThreadEx
这里先补充一个知识点,``NtCreateThread与ZwCreateThread 的函数地址是一样的,NtCreateThreadEx与ZwCreateThreadEx的函数地址是一样的。区别就是 Nt 的为用户层函数,用户态调用时会使用 Nt 开头的,Zw 开头的会在内核调用。 可以利用ZwCreateThread` 实现系统进程注入。前文有[相关代码](#调用 ZwCreateThreadEx).
用户态一般不会直接调用这个接口
汇编说明:
mov eax, 0C7h:将值0c7h移动至eax寄存器,表示执行编号为0c7h的系统调用
syscall:这是执行系统调用的指令。系统调用编号和参数应在此之前已被设置好(在这种情况下,通过将 0xC7 和 rcx 的内容移动到 eax 和 r10)
这段代码表示执行编号为 0xC7 的系统调用,如果 ds:7FFE0308h 的最低位被设置,那么在系统调用前会先跳转到另一段代码(位于 loc_1800A0525,使用int 2Eh中断进入内核)。如果该位未被设置,那么直接执行系统调用
可以结合反汇编代码查看,如下所示:
1 | |
总结
以下是CreateRemoteThread函数的调用流程图
- 应用程序调用
CreateRemoteThread,这是一个由kernel32.dll提供的 Win32 API,用于在另一个进程的地址空间中创建新线程。 CreateRemoteThread内部调用CreateRemoteThreadEx,这是一个由KernelBase.dll提供的更底层的 API,提供了更多的选项,比如可以指定安全描述符,可以控制新线程是否立即开始运行等CreateRemoteThreadEx内部调用NtCreateThreadEx,这是由ntdll.dll提供的 Native API,也是用户空间可以直接调用的最底层的 API。NtCreateThreadEx函数设置好系统调用的参数后,执行syscall指令,切换到内核模式。- 在内核模式下,根据
syscall提供的系统调用编号,在 SSDT 表中查找对应的内核函数。 - 执行 SSDT 表中找到的函数,完成线程的创建。
flowchart TD
A(CreateRemoteThread) --> B
B(Kernel32!CreateRemoteThreadSutb) --> C
C(Kernel32!CreateRemoteThreadEx) --> D
D(ntdll!NtcreateThreadEx) --> F
F(nt!NtcreateThreadEx) --> E
E(syscall) --> G
subgraph 内核区
G(SSDT)
end
如下所示为一个略微详细的启动分析:
0x04 普通线程和远程线程的区别
可以看到普通线程函数CreateThread也调用了CreateRemoteThread函数,只不过其线程句柄参数的值为 -1,而远程线程的句柄参数为目标进程句柄