新技术学习
1. 原理
需求
在一些情况中需要在指定的进程空间中添加入自己的代码,而此时由于需要添加的代码较多,使用shellcode不太方便。这时就可以使用远程线程注入的技术一次性添加入较为复杂的代码。
重要函数
- 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
);
核心函数之一,用于在指定进程中创建新线程,具体用法与CreateThread函数大致相同,多加了一个进程句柄的参数用于确定进程
- LoadLibraryA
HMODULE LoadLibraryA(
[in] LPCSTR lpLibFileName
);
核心函数之一,用于加载dll到当前进程空间中。其特殊点在于其位于kernel32.dll中,故大多数进程中因为优先加载kernel32的缘故,地址相同。此外,其返回值为模块句柄,创建新线程时线程退出码与句柄的格式相近,可以轻易转换
- VirtualAllocEx & WriteProcessMemory
这两个函数用于在目标进程中申请一小块区域存放待注入dll的名称,由于CreateRemoteThread时其线程参数需位于目标进程中,故需要将dll名称放入目标进程
- WaitForSingleObject & GetExitCodeThread
用于等待线程结束并获取线程退出码,以此来判断注入是否成功
- GetModuleHandle & GetProcAddress
用于获取kernel32和LoadLibraryA的地址,防止出现LoadLibraryA地址错误的情况
2. 实操
待注入的程序代码:
#include <iostream>
#include <Windows.h>
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
for (int i = 0; i < 10; i++) {
Sleep(500);
printf("Test++\n");
}
return 0;
}
int main() {
HANDLE hThread;
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
CloseHandle(hThread);
getchar();
return 0;
}
随便写的程序,注意到其中确实有创建线程
注入程序的代码:
#include <iostream>
#include <Windows.h>
BOOL LoadDll(DWORD dwProcessID, char* szDllPathName) {
BOOL bRet;
DWORD dwLength;
DWORD dwLoadAddr;
DWORD dwThreadID;
HANDLE hProcess;
HANDLE hThread;
LPVOID lpAllocAddr;
HMODULE hModule;
//获取进程句柄
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID);
if (hProcess == NULL) {
printf("OpenProcess 失败:%d\n", GetLastError());
return FALSE;
}
//计算Dll路径名长度,并且要加上0结尾的长度
dwLength = strlen(szDllPathName) + 1;
//在目标进程分配内存
lpAllocAddr = VirtualAllocEx(hProcess, NULL, dwLength, MEM_COMMIT, PAGE_READWRITE);
if (lpAllocAddr == NULL) {
printf("VirtualAllocEx 失败:%d\n", GetLastError());
CloseHandle(hProcess);
return FALSE;
}
//拷贝DLL路径名到目标进程的内存
bRet = WriteProcessMemory(hProcess, lpAllocAddr, szDllPathName, dwLength, NULL);
if (!bRet) {
printf("WriteProcessMemory 失败:%d\n", GetLastError());
CloseHandle(hProcess);
return FALSE;
}
WCHAR ker[] = { 'k', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l', '\0' };
//获取模块地址
hModule = GetModuleHandle((LPCWSTR)ker);
if (!hModule) {
printf("GetModuleHandle 失败:%d\n", GetLastError());
CloseHandle(hProcess);
return FALSE;
}
//获取LoadLibraryA函数地址
dwLoadAddr = (DWORD)GetProcAddress(hModule, "LoadLibraryA");
if (!dwLoadAddr) {
printf("GetProcAddress 失败:%d\n", GetLastError());
CloseHandle(hModule);
CloseHandle(hProcess);
return FALSE;
}
//创建远程线程,加载DLL
hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)dwLoadAddr, lpAllocAddr, 0, NULL);
if (!hThread) {
printf("GetProcAddress 失败:%d\n", GetLastError());
CloseHandle(hModule);
CloseHandle(hProcess);
return FALSE;
}
//关闭句柄
CloseHandle(hProcess);
return true;
}
int main(int argc, char* argv[]) {
LoadDll(13300, (char*)"D:\\Study\\C++_work\\VS\\DllTry1\\Debug\\DllTry1.dll");
printf("success\n");
getchar();
return 0;
}
其中main中LoadDll的第一个参数为手动输入,具体可以通过任务管理器查看进程对应pid。第二个参数为自行编写的dll,使用了绝对路径,dll的内容就不给了,随便整个dll其实都行
此外,由于此次为实验性质的代码,故未获取退出码,实验效果通过调试软件进行观察。
效果
首先运行目标程序,使用x32dbg查看其模块:
x32dbg脱离。然后获取pid后运行注入程序,显示成功后再使用x32dbg附加进行查看:
可见dll注入成功
思考
代码注入成功了,但是要如何调用呢?目前推测有两种方法:一是dll在加载时有一段可自动执行的代码,通过这个来执行dll内部的代码。二是目标程序中可能存在一些其他的漏洞,各种溢出之类的,通过特定漏洞来使eip转到指定位置进行执行
此外,该方法对于windows现在各种内存防护手段效果如何呢,本次实验中未对相关参数进行设置,在该方面有待进一步探究