【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,而远程线程的句柄参数为目标进程句柄