进程注入
DLL注入
基本原理:将自定义DLL的路径传递给目标进程,并使目标进程调用 LoadLibrary
函数加载该DLL。
大致步骤:
获取远程进程的句柄:使用OpenProcess打开目标进程
分配内存空间:使用VirtualAllocEx在目标进程中分配内存
写入DLL路径:使用WriteProcessMemory将DLL路径写入目标进程的内存
加载DLL:使用CreateRemoteThread创建远程线程,调用LoadLibrary或CreateThread执行代码
代码示例:
#include <windows.h>
BOOL InjectDLL(DWORD dwProcessId, const char* szDllPath) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (hProcess == NULL) {
return FALSE;
}
LPVOID lpRemoteMemory = VirtualAllocEx(hProcess, NULL, strlen(szDllPath) + 1, MEM_COMMIT, PAGE_READWRITE);
if (lpRemoteMemory == NULL) {
CloseHandle(hProcess);
return FALSE;
}
if (!WriteProcessMemory(hProcess, lpRemoteMemory, szDllPath, strlen(szDllPath) + 1, NULL)) {
VirtualFreeEx(hProcess, lpRemoteMemory, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, lpRemoteMemory, 0, NULL);
if (hThread == NULL) {
VirtualFreeEx(hProcess, lpRemoteMemory, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
VirtualFreeEx(hProcess, lpRemoteMemory, 0, MEM_RELEASE);
CloseHandle(hProcess);
return TRUE;
}
OpenProcess
函数:
HANDLE OpenProcess(
[in] DWORD dwDesiredAccess, //对进程对象的访问
[in] BOOL bInheritHandle, //是否继承进程的HANDLE
[in] DWORD dwProcessId //要打开的本地进程的标志符
);
VirtualAllocxEx
函数:
LPVOID VirtualAllocEx(
[in] HANDLE hProcess, //进程的句柄
[in, optional] LPVOID lpAddress, //为要分配的页面区域指定所需起始地址的指针
[in] SIZE_T dwSize, //要分配的内存区域的大小
[in] DWORD flAllocationType, //内存分配的类型
[in] DWORD flProtect //内存保护
);
WriteProcessMemory
函数:
BOOL WriteProcessMemory(
[in] HANDLE hProcess, //要修改的进程内存的句柄
[in] LPVOID lpBaseAddress, //指向将数据写入到的指定进程中基址的指针
[in] LPCVOID lpBuffer, //指向缓冲区的指针,包含要写入指定进程的地址空间中的数据
[in] SIZE_T nSize, //要写入指定进程的字节数
[out] SIZE_T *lpNumberOfBytesWritten //指向变量的指针,该变量接收传输到指定进程的字节数
);
CreateRemoteThread
函数:
HANDLE CreateRemoteThread(
[in] HANDLE hProcess, //要在其中创建线程的进程句柄
[in] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] SIZE_T dwStackSize,
[in] LPTHREAD_START_ROUTINE lpStartAddress,
[in] LPVOID lpParameter,
[in] DWORD dwCreationFlags,
[out] LPDWORD lpThreadId
);
实验示例:
典型的DLL注入代码的模式,我们的主要任务主要是通过这些Windows API的参数获取到重要字符串:
- 注入了哪个目标进程
- 注入了哪些DLL的信息
- 注入的DLL要干什么
确定了我们的分析目标,就可以进行接下来的分析,
首先,尝试在OpenProcess
函数中找到对进程对象的访问dwDesiredAccess
,
可以看到这个参数的值是ecx寄存器的值,而ecx的值又来自于上一条mov指令,向上追踪找到这块流程,
深入分析sub_401000函数,发现explorer.exe痕迹,仔细分析这段函数逻辑,主要实现了打开一个指定进程ID的进程,获取进程的某些信息,并进行字符串比较。如果字符串比较结果为 explorer.exe,则返回 1,否则返回 0。
回答第一个问题,目标进程就是explorer.exe
。
这里有个问题,我尝试直接通过OD在目标寄存器上打断点读取数值,但不知道为什么读取不到。
要回答第二个问题,主要是读取WriteProcessMemory
函数的lpBuffer缓冲区的数值,
对eax寄存器追根溯源,
eax寄存器的值,即传入buffer中的值,就是在这调用了GetCurrentDirectoryA函数获取当前路径,并与Lab12-01.dll拼接传入。
后面就是将dll注入到本地进程中,并进行远程启动的过程。
可以在Procmon找到痕迹,用火绒剑更好。
第三个问题通过静态分析DLL或者直接运行EXE能得到答案,
直接载入DLL文件,直接查看IAT表,点进去看流程逻辑,这里省略了,
就是一个固定时间内循环弹出弹窗的小恶作剧了啦哈哈哈~
直接注入
基本原理:通过操作远程进程的内存,直接将代码(通常是shellcode)写入到目标进程的地址空间中,并且在目标进程中执行这些代码。
大致步骤:
获取远程进程的句柄:使用OpenProcess获取目标进程的句柄
分配内存空间:使用VirtualAllocEx在目标进程中分配内存
写入代码:使用WriteProcessMemory将代码写入内存空间
执行代码:使用 CreateRemoteThread 来在目标进程中创建新线程,并让这个线程执行注入的代码
tips:这里和DLL注入主要的区别在于代码执行的部分,DLL注入需要LoadLibrary等函数去导入并执行,主要依赖DLL文件,直接注入的代码可以直接运行,主要依赖于写入的内存。
实验示例:
以一段shellcode的直接注入为例,
我是直接使用交叉引用查找可疑API 函数,
这里跟前文一样分析,很快发现是想注入到iexplore.exe浏览器进程中,
我的目的是知道注入的什么东西,直接断到缓冲区的指针,发现shellcode缓冲区基址
查看00407030
的汇编指令,IDA中反汇编失败,全是数据,这里存在Shellcode编码,只能动态调试,
单步调试几条指令后,发现这里存在一个循环,是shellcode解码的过程,
跳过循环,程序会进入到00407048
攻击载荷部分,同时也可以发现,循环过程中这一段解码完成,
将解码完成的ShellCode尝试dump下来,
结构很简单,但是shellcode数据和代码混写的特性让IDA反汇编失败,这不影响我们分析,重点在于loc_4071B6
和sub_4070E3
,
loc_4071B6:
将单字节数据转换为双字节数据,得到ShellCode的哈希数据,
sub_4070E3:
这个函数将哈希数据传入以后,有一个明显的循环,怀疑是哈希处理函数,
ShellCode自包含特性:
1.寻找系统DLL文件基地址
2.自动解析符号标志
sub_4070C2和sub_407076函数就是完成了这两个过程,
sub_4070C2:
这个函数主要是找到了kernel32.dll文件的基地址,
具体的ShellCode如何在内存中找到Kernel32.dll过程,可以参考在网上找到的重要结构体神图。
简化版:
sub_407076:
重点也在循环部分,
jecxz
: 若 ecx
为零,则跳转到 loc_4070B8
,这是循环终止的条件。
dec ecx
: 每次迭代减小 ecx
的值,直到它为零。
mov esi, [ebx+ecx\*4]
: 从导入表中获取函数地址并储存在 esi
中。
add esi, ebp
: 将 RVA 转为实际地址。
这个函数主要是完成了根据哈希数组去查找DLL文件中特定的导出表函数,并返回该函数的地址。
打断点调试,调试打断点,
可以看到ShellCode主要是找到Kernel32.dll
和WS2_32.dll
文件,
主要解析这两个文件中的LoadLibraryA
、CreateProcessA
、TerminateProcess
、GetCurrentProcess
和WSAStartup
、WSASocketA
、connet
函数。
可以对ShellCode做一个行为分析:它想要创建一个新的线程,并且尝试创建套接字跟远程进程进行连接。
createprocess函数细节:
这里创建了一个cmd.exe进程,
connect函数细节:
02 00:协议类型。
34 12:源端口号:13330。
C0 A8 C8 02:IP 地址,可以转换为 192.168.200.2
。
总结:这个EXE可执行文件尝试将shellcode直接注入到浏览器进程中,进行反弹shell连接。
DLL劫持
基本原理:通过将DLL文件放在被目标程序加载的路径中,从而诱使程序加载并执行代码。
DLL文件的加载顺序:
一、Windows XP SP2之前 Windows查找DLL的目录以及对应的顺序:
1. 进程对应的应用程序所在目录;
2. 当前目录(Current Directory);
3. 系统目录(通过 GetSystemDirectory 获取);
4. 16位系统目录;
5. Windows目录(通过 GetWindowsDirectory 获取);
6. PATH环境变量中的各个目录;
二、在Windows xp sp2之后
Windows查找DLL的目录以及对应的顺序(SafeDllSearchMode 默认会被开启):
默认注册表为:
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode, //其键值为1
dll文件加载顺序:
1. 进程对应的应用程序所在目录(可理解为程序安装目录比如C:ProgramFilesuTorrent);
2. 系统目录(即%windir%system32);
3. 16位系统目录(即%windir%system);
4. Windows目录(即%windir%);
5. 当前目录(运行的某个文件所在目录,比如C:DocumentsandSettingsAdministratorDesktoptest);
6. PATH环境变量中的各个目录;
三、Windows7以上
系统没有了SafeDllSearchMode 而采用KnownDLLs,那么凡是此项下的DLL文件就会被禁止从EXE自身所在的目录下调用,而只能从系统目录即SYSTEM32目录下调用,其注册表位置:
计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SessionManager\KnownDLLs
根据dll文件的路径,去注册表里面找。
DLL劫持 vs DLL注入:
特点 | DLL 劫持 | DLL 注入 |
---|---|---|
工作原理 | 利用目标程序的 DLL 加载路径漏洞,强制程序加载恶意 DLL 文件。 | 通过调用 API(如 CreateRemoteThread ),将恶意 DLL 加载到目标进程的内存中,并执行其中的代码。 |
执行时机 | 恶意 DLL 会在目标程序启动时自动加载并执行。 | 恶意 DLL 可以在目标程序运行时随时加载并执行。 |
依赖条件 | 依赖于目标程序对 DLL 文件路径的搜索顺序漏洞,通常不需要修改目标程序的代码。 | 需要通过 API 操作目标进程的内存,通常需要更复杂的代码和攻击手段。 |
隐蔽性 | 相对隐蔽,因为恶意 DLL 会在程序启动时自动加载,不需要直接操作目标进程的内存。 | 相对显著,因为恶意代码需要通过注入手段操作目标进程的内存,可能更容易被检测。 |
控制能力 | 对目标程序的控制有限,通常只能够替换或篡改某些函数的行为。 | 可以完全控制目标进程,进行全面的行为修改、数据窃取等操作。 |
适用场景 | 适用于没有正确处理 DLL 路径的程序,攻击者通过将恶意 DLL 文件放置在程序可搜索到的位置来实现劫持。 | 适用于需要直接修改目标进程行为、执行恶意代码的场景,攻击者可以直接操控进程。 |
DLL劫持实现的方法多种多样,比如第三方软件劫持等等,我这里主要是对恶意软件的行为做分析,不详细介绍攻击手法。
了解了恶意程序做DLL劫持的基本原理,有助于我们遇到这种情况时去监控DLL加载路径、检查不匹配的DLL版本、分析程序行为和实时监控注册表及文件系统的变化。
进程替换
基本原理:直接用恶意代码替换目标进程的内存空间。
大致步骤:
挂起进程:利用CreateProcess设置进程CREATE_SUSPENDED挂起
释放内存:利用ZwUnmapViewOfSection或NtUnmapViewOfSection来取消映射目标进程的内存
分配内存:利用VirtualAllocEx为恶意软件分配新内存
写入内存:使用WriteProcessMemory将每个恶意软件的部分写入目标进程空间
恢复进程:使用SetThreadContext将entrypoint指向恶意代码,最后ResumeThread函数恢复进程
实验示例:
Sha256:eae72d803bf67df22526f50fc7ab84d838efb2865c27aef1a61592b1c520d144
在沙箱中的分析报告显示存在进程替换行为,
文件注入
PE注入
基本原理:将恶意代码或数据注入到PE文件的头部空白区域,例如DOS头、PE头或节表中,涉及到对PE文件结构的修改。
大致步骤:
打开PE文件:使用GetFileType函数找到目标PE文件
获取PE头:使用RtlImageNtHeader函数在PE图像中找到IMAGE_NT_HEADERS
插入代码:使用SetThreadContext函数设置代码上下文,并用WriteProcessMemory写入
修改PE头:使用SetFilePointer函数修改 PE 头的 AddressOfEntryPoint 字段,使其指向插入的代码
实验示例:
Sha256:ce8d7590182db2e51372a4a04d6a0927a65b2640739f9ec01cfd6c143b1110da
这个样本只有Antiy可以检测出DLLinject,
反射式注入
基本原理:将反射式加载器代码注入目标进程的内存,加载器负责将恶意代码映射到内存然后进行执行操作。
大致步骤:
打开进程、分配内存以及写入内存:OpenProcess,VirtualAllocEx,WriteProcessMemory
映射操作:CreateFileMapping,Nt/MapViewOfFile,memcpy,Nt/MapViewOfSection
执行代码:CreateThread,NtQueueApcThread,CreateRemoteThread,RtlCreateUserThread
总结
- 完成了病毒启动时进程注入、文件注入以及进程替换的部分理论学习;
- 详细分析了一段DLL注入的恶意代码和一段直接注入的恶意shellcode;
- 用沙箱完成了进程替换和PE注入的分析;