寒假的时候写的,4月才发现没有发到博客:(
Windows注入技术 复现
对于《Windows黑客编程》一书中的各种注入技术进行复现和总结
环境:
- OS:windows10
- IDE:Visual Studio 2022
全局钩子注入
Windows中的大部分应用程序依赖于消息机制,其中含有消息过程函数,而Windows操作系统的钩子机制用于截获和监视系统中的消息。
依据钩子的作用范围,可以分为局部钩子和全局钩子:
- 局部钩子:针对某个线程
- 全局钩子:作用于整个系统的消息
WinAPI介绍
关于全局钩子注入Win32 API介绍:
1 | HHOOK WINAPI SetWindowsHookEx{ |
将程序定义的钩子函数(HOOKPROC lpfn
)安装到挂钩链中,安装钩子程序可以监视系统是否存在某些类型的事件,这些事件与特定线程或调用线程所在桌面中的所有线程相关联
操作过程
生成DLL文件
新建一个DLL空项目
预编译头pch.h中设置
该文件是预编译标头文件,仅仅编译一次
1 |
|
在pch.cpp文件中实现上述函数
1 | // pch.cpp: 与预编译标头对应的源文件 |
然后在DLL的入口文件dllmain.cpp中设置
1 | // dllmain.cpp : 定义 DLL 应用程序的入口点。 |
在Release,X86下生成:
可以发现对应文件夹下生成dll文件
测试
新建一个CPP空项目
1 | // 全局钩子注入 |
远程线程注入
远线程注入是指一个进程在另一个进程中创建线程的技术
核心函数主要如下:
- LoadLibrary:将指定的DLL文件动态加载到进程空间
- CreateRemoteThread:在目标进程的虚拟地址空间中创建运行的线程
大致原理:
- 获得LoadLibrary函数的地址
虽然Windows的ASLR机制会使得LoadLibrary在每次开机时的地址不同,但对于同个时候Windows的kernel32.dll的加载基址在各个进程中都是相同的,因此LoadLibrary也是相同的。
- 写入注入的DLL路径字符串
通过OpenProcess打开进程获得句柄,然后调用VirtualAllocEx在目标进程中申请一块内存空间,再调用WriteProcessMemory将恶意DLL路径写入到目标进程的空间地址中
WinAPI介绍
- OpenProcess
打开现有的本地进程,获得句柄
1 | HANDLE WINAPI OpenProcess( |
- VirtualAllocEx
对指定进程的虚拟地址空间内保留、提交或更改内存的状态
1 | LPVOID WINAPI VirtualAllocEx( |
- WriteProcessMemory
在指定进程的指定可访问的内存区域写入数据
1 | BOOL WINAPI WriteProcessMemory( |
- CreateRemoteThread
在指定进程中创建运行的线程
1 | HANDLE WINAPI CreateRemoteThread( |
操作过程
生成DLL文件
dllmain.cpp文件
1 |
|
然后直接生成dll
测试
核心函数在于远程线程注入_RemoteThreadInjection
中:
- 函数原型设计:我们需要得知远程线程注入的目标进程PID以及执行线程的对应DLL文件位置(采用宽字符串)
1 | DWORD _RemoteThreadInjection(DWORD _Pid, LPCWSTR DllName) |
- OpenProcess打开进程获得句柄
1 | hprocess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid); |
- VirtualAllocEx API申请内存,该内存存储
_tcslen(DllName)
截取的宽字符串长度不包括字符串终止符,所以需要加1,并乘上宽字符的大小以计算出字节数
1 | // 在注入的进程中申请内存 |
- 申请完之后就是使用WriteProcessMemory写数据
1 | BOOL isWriteSuccess = ::WriteProcessMemory(hprocess, pDllAddr, DllName, _Size, NULL); |
- 获取LoadLibraryW的地址
1 | pThread = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"),"LoadLibraryW"); |
- 创建线程,并传入参数
1 | hThread = ::CreateRemoteThread(hProcess, NULL, 0, addr, pDllAddr, 0, NULL); |
- 释放资源等操作
完整源码:
1 | // 远程线程注入 |
注入之前:
注入之后:可以观察到其实不止多了DLLInjection.dll文件
突破SESSION 0 隔离的远线程注入
在远程线程注入中,将目标进程改为一些系统服务进程时会发现失败,这是由于存在SESSION 0隔离的安全机制,传统的远程线程注入并不能突破SESSION 0隔离
Session 0隔离机制
在早期Windows版本中(WinXP,WinServer2003以及之前),所有服务都与登录到控制台的第一个用户(超级管理员)处于同一个会话中,该会话即所谓的Session 0,此时其他非系统服务等同于拥有超级管理员权限来执行,那么此时遭遇劫持就直接最高权限了。
此后在Windows内核6.0版本后引入了Session 0隔离机制,只有系统进程和服务才会处于session 0中运行,用户登录到会话1,后续用户登录到2,3 …
Session 0隔离机制使得当创建一个进程后不立即执行,而是先挂起进程,在查看运行的进程所在的会话层之后再决定是否恢复进程运行。
WinAPI介绍
原理与远程线程注入的原理大致相同,但是使用的WinAPI是比CreateRemoteThread更为底层的ZwCreateThreadEx函数来创建线程
1 | // win64 |
操作过程
1 | // 突破session 0隔离机制注入DLL |
选择D盾开刀
CS上线
APC注入
APC机制
在Windows系统中,APC机制是一种并发机制,用于异步IO或者定时器,每个线程都会维护一个线程APC队列,通过QueueUserAPC函数把一个APC函数压入APC队列中。当处于用户模式的APC压入线程APC队列后,该线程并不直接调用APC函数,除非该函数处于可通知的状态,调用顺序为先入先出。
WinAPI介绍
QueueUserAPC函数:将用户模式中的异步过程调用(APC)对象添加到指定线程的APC队列中
1 | DWORD WINAPI QueueUserAPC( |
操作过程
通过OpenProcess函数打开目标进程,获取目标进程的句柄
遍历线程快照,获取所有线程ID
调用VirtualAllocEx函数在目标进程中申请内存,再调用WriteProcessMemory写入DLL路径
遍历获取的线程ID,并调用OpenThread函数以THREAD_ALL_ACCESS访问权限打开线程,获取线程句柄
并调用QueueUserAPC函数向每个线程插入APC函数,设置APC函数的地址为LoadLibraryA函数的地址,APC函数参数为上述DLL路径地址
唤醒任意线程,即可执行APC,完成DLL的APC注入
1 | // APCInjection.cpp |
拿D盾开刀:
还是CS上线