今天接着上次说的远程线程注入来说下利用远程线程进行dll注入。
远程线程注入需要目标进程地址空间的某个函数作为线程的thread_func, 这样我们只能用目标进程中自定义的函数,那我们怎么样使用自定义的函数呢,这就需要使用DLL注入技术。
DLL注入原理很简单:exe文件都会链接kernel32.dll,利用kernel32.dll中的LoadLibrary可以把自己写的dll加载到目标进程(Windows核心编程原话: 对CreateRomoteThread的调用假定在本地进程和远程进程中,kernel32.dll被映射到地址空间中的同一内存地址)。DLL中有一系列的“”钩子“”, 可以在DLL被加载/卸载执行,这样我们自己写的代码就会被目标进程执行。
先自己写一个简单的DLL:
// base/external_dll.cc
#include <windows.h>
#ifndef BASE_EXTERNAL_API
#define BASE_EXTERNAL_API extern "C" __declspec(dllimport)
BOOL WINAPI DllMain(HINSTANCE hinstDLL, // handle to DLL module
DWORD fdwReason, // reason for calling function
LPVOID lpvReserved) // reserved
{
// Perform actions based on the reason for calling.
switch (fdwReason) {
case DLL_PROCESS_ATTACH:
// Initialize once for each new process.
// Return FALSE to fail DLL load.
MessageBoxA(NULL, "Dll Main Notice", "Notice", MB_OK);
break;
case DLL_THREAD_ATTACH:
// Do thread-specific initialization.
break;
case DLL_THREAD_DETACH:
// Do thread-specific cleanup.
break;
case DLL_PROCESS_DETACH:
if (lpvReserved != nullptr) {
break; // do not do cleanup if process termination scenario
}
// Perform any necessary cleanup.
break;
}
return TRUE; // Successful DLL_PROCESS_ATTACH.
}
#endif // BASE_EXTERNAL_API
DLL_PROCESS_ATTACH
会在进程通过LoadLibrary将DLL加载到进程空间的时候触发,这里我们简单弹出一个MessageBox提示DLL代码被执行。
使用控制台编译成DLL
g++ .\base\external_dll.cc -fPIC -shared -o external.dll
这里我们写一个client先在本进程加载下这个DLL验证下:
#include <windows.h>
constexpr char kDllFilePath[] =
"C:\\Users\\chuzhang\\Desktop\\DummyProgram\\external.dll";
int main() {
LoadLibraryA((LPCSTR)kDllFilePath);
return 0;
}
本地编译执行下,发现message box弹出来了,说明咱们自己写在DLL里面的代码执行了,下面咱们把这个DLL执行到其他进程去。
我们还是使用我们之前用的dummy_process.exe 作为注入目标:
// dummy_process.cc
#include <iostream>
#include <string>
#include <windows.h>
int main() {
int varInt = 123456;
std::string varString = "DefaultString";
char arrChar[] = "Long char array right there";
int *ptr2int = &varInt;
int **ptr2ptr = &ptr2int;
int ***ptr2ptr2 = &ptr2ptr;
while (true) {
std::cout << "Process ID: " << GetCurrentProcessId() << std::endl;
std::cout << std::endl;
std::cout << "varInt "
<< "(" << &varInt << ") = " << varInt << std::endl;
std::cout << "varString "
<< "(" << &varString << ") = " << varString << std::endl;
std::cout << "arrChar "
<< "(" << &arrChar << ") = " << arrChar << std::endl;
std::cout << std::endl;
std::cout << "ptr2int "
<< "(" << &ptr2int << ") = " << ptr2int << std::endl;
std::cout << "ptr2ptr "
<< "(" << &ptr2ptr << ") = " << ptr2ptr << std::endl;
std::cout << "ptr2ptr2 "
<< "(" << &ptr2ptr2 << ") = " << ptr2ptr2 << std::endl;
std::cout << std::endl;
std::cout << "Press ENTER to print again" << std::endl;
std::cout << std::endl;
std::cout << std::endl;
std::cout << "----------------------------" << std::endl;
getchar();
}
}
注入DLL的代码:
// inject_dll.cc
#include <windows.h>
constexpr char kDllFilePath[] =
"C:\\Users\\chuzhang\\Desktop\\DummyProgram\\external.dll";
constexpr int kProcessId = 20600;
int main() {
HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, false, kProcessId);
FARPROC func = GetProcAddress(GetModuleHandleA("kernel32"), "LoadLibraryA");
LPVOID remote_string = VirtualAllocEx(process, NULL, strlen(kDllFilePath) + 1,
MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(process, remote_string, kDllFilePath,
strlen(kDllFilePath) + 1, NULL);
HANDLE thread = CreateRemoteThread(
process, NULL, 0, LPTHREAD_START_ROUTINE(func), remote_string, 0, NULL);
WaitForSingleObject(thread, INFINITE);
CloseHandle(thread);
VirtualFreeEx(process, remote_string, strlen(kDllFilePath) + 1, MEM_DECOMMIT);
return 0;
}
我们要注意,CreateRemoteThread
不能直接写成:
HANDLE thread = CreateRemoteThread(
process, NULL, 0, LPTHREAD_START_ROUTINE(func), "C:\\Users\\chuzhang\\Desktop\\DummyProgram\\external.dll"", 0, NULL);
"C:\\Users\\chuzhang\\Desktop\\DummyProgram\\external.dll"
在当前进程的地址会被传入到CreateRemoteThread
,远程进程使用这个地址作为参数调用LoadLibraryA
, 目标进程可能会发生内存访问违规。
解决方案是:在目标进程通过VirtualAllocEx
在目标进程分配一块内存,这块内存大小为字符串大小加一(字符串末尾结束符),然后使用WriteProcessMemory
把DLL路径写入到新分配的内存里,这样我们可以得到目标进程内指向DLL路径字符串的指针,使用这个指针调用CreateRemoteThread
。
运行注入程序,可以在目标cmd看到messagebox:
其实最好不要在DLL_PROCESS_ATTACH里面直接写代码因为DllMain和其他代码share了同步锁,在这里进行资源访问请求可能引发死锁,最好是创建一个线程,然后干自己想干的事情:
#include <iostream>
#include <windows.h>
#ifndef BASE_EXTERNAL_API
#define BASE_EXTERNAL_API extern "C" __declspec(dllimport)
DWORD DoWork() {
std::cout << "hijack from dll";
return 0;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, // handle to DLL module
DWORD fdwReason, // reason for calling function
LPVOID lpvReserved) // reserved
{
// Perform actions based on the reason for calling.
switch (fdwReason) {
case DLL_PROCESS_ATTACH: {
// Initialize once for each new process.
// Return FALSE to fail DLL load.
HANDLE thread =
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&DoWork, NULL, 0, NULL);
CloseHandle(thread);
break;
}
case DLL_THREAD_ATTACH:
// Do thread-specific initialization.
break;
case DLL_THREAD_DETACH:
// Do thread-specific cleanup.
break;
case DLL_PROCESS_DETACH:
if (lpvReserved != nullptr) {
break; // do not do cleanup if process termination scenario
}
// Perform any necessary cleanup.
break;
}
return TRUE; // Successful DLL_PROCESS_ATTACH.
}
#endif // BASE_EXTERNAL_API