WIN32 DLL注入的基本原理
没想到入坑WIN32居然是从这个地方,由于对WIN32不太了解,所以摸索起来比较困难。sad师傅推荐了《windows核心编程》这本书,个人感觉还是挺好的,就是得耐下心多读读才能领会。
0x00 Win下进程的内存结构
程序在运行时会把自身的二进制文件加入到内存中,其中相关的函数、变量等都会在内存中对应固定的地址。但是程序功能一般不会仅靠单独一个二进制文件实现,有时还需要调用系统提供的API来进行一些操作。而这通常是靠引用预先集成了许多函数的DLL(动态链接库)文件,并调用其中的函数来实现。由于DLL不包含在程序的二进制文件中,所以需要在运行的时候由操作系统加入到进程的内存空间中。
那么如果我们能够编写一个工具,实现将我们自己编写的DLL注入到另一个不同的进程的内存空间中,就相当于有了间接控制这个进程的能力。(在此处只讨论如何注入)
0x01 几个要用到的win32 API
进程相关
- OpenProcess 获得要注入进程的句柄
- VirtualAllocEx在远程进程中开辟出一段内存
- WriteProcessMemory将Dll的名字写入第二步开辟出的内存中
- CreateRemoteThread将LoadLibraryA作为线程函数,参数为Dll的名称,创建新线程
- CloseHandle关闭线程句柄
权限相关
- OpenProcessToken打开进程令牌
- LookupPrivilegeValue
- AdjustTokenPrivileges
具体参数可以百度或者谷歌搜索,也可以参考sad师傅的博客关于这些API的介绍:
https://www.jianshu.com/p/044931d7e4d6
0x02 思路
- 提升进程权限(非必须)
- 利用进程PID获得目标进程的句柄
- 在目标进程中开辟出一段内存写入需要调用的DLL的路径(因为后续步骤加载DLL时所用的参数需要在同一内存空间中)
- 获得 LoadLibraryA 在目标进程中的地址(通常在所有进程中是一样的),利用 LoadLibraryA 作为线程函数,DLL路径地址作为参数,在目标进程中创建一个线程用来加载DLL
- 完成注入后关闭相关句柄
0x03 代码实现
进程提权
int EnableDebugPriv(const char* name)
{
HANDLE hToken;
TOKEN_PRIVILEGES tp;
LUID luid;
//打开进程令牌环
//GetCurrentProcess()获取当前进程句柄
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
//获得进程本地唯一ID
LookupPrivilegeValue(NULL, (LPCWSTR)name, &luid);
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
tp.Privileges[0].Luid = luid;
//调整权限
int ret = AdjustTokenPrivileges(hToken, 0, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL);
return ret;
}
DLL注入
int remoteInjection(const DWORD PID) {
HANDLE hRemoteProcess;
HANDLE hRemoteThread;
char* pszLibFileRemote;
printf("DEFAULT DLL PATH: %s\n", DLLname);
if (!EnableDebugPriv((const char*)SE_DEBUG_NAME)) {
cout << "* FAIL TO: Get SEDEBUG privilege" << endl;
return 0;
}
else {
cout << "* SUCCESS TO: Get SEDEBUG privilege" << endl;
}
/*拿到目标进程句柄*/
hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS, false, PID);
if (hRemoteProcess) {
cout << "* SUCCESS TO: Open process" << endl;
cout << "Got Handle: " << hRemoteProcess << endl;
}
else {
cout << "* FAIL TO: Open process" << endl;
return 0;
}
/*开辟一段内存*/
pszLibFileRemote = (char *)VirtualAllocEx(hRemoteProcess, NULL, strlen(DLLname) + 10, MEM_COMMIT, PAGE_READWRITE);
if (pszLibFileRemote) {
cout << "* SUCCESS TO: Allocate remote memory space" << endl;
printf("Remote addr: 0x%p\n", (long long)pszLibFileRemote);
}
else {
cout << "* FAIL TO: Allocate remote memory space" << endl;
return 0;
}
/*写入DLL路径到目标进程*/
if (WriteProcessMemory(hRemoteProcess, pszLibFileRemote, (void *)DLLname, strlen(DLLname) + 10, NULL)) {
cout << "* SUCCESS TO: Write memory" << endl;
}
else {
cout << "* FAIL TO: Write memory" << endl;
return 0;
}
//PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)LoadLibraryA;
/*获得LoadLibraryA函数的地址*/
PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("kernel32"), "LoadLibraryA");
if (pfnStartAddr == NULL) {
cout << "* FAIL TO: Get LoadLibraryA() addr" << endl;
return 0;
}
else
{
cout << "* SUCCESS TO: Get LoadLibraryA() addr" << endl;
printf("LoadLibraryA addr: 0x%p\n", (long long)pfnStartAddr);
}
/*在目标进程中创建线程加载DLL*/
hRemoteThread = CreateRemoteThread(hRemoteProcess, NULL, 0, pfnStartAddr, pszLibFileRemote, 0, NULL);
//在这一步注入了自己创建的DLL
if (hRemoteThread) {
cout << "* SUCCESS TO: Create remote thread" << endl;
}
else {
cout << "* FILE TO: Create remote thread" << endl;
return 0;
}
/*等待线程结束*/
WaitForSingleObject(hRemoteThread, INFINITE);
/*关闭句柄*/
CloseHandle(hRemoteProcess);
CloseHandle(hRemoteThread);
return 1;
}
完整项目源码
github: https://github.com/yikesoftware/Remote_DLL_Injection
本项目在vs2019下编写
项目提供了:
- 用于注入的程序源码
- 带有注入成功弹窗提示的测试用DLL源码
- 被注入的测试程序源码
注意
部分受保护进程可能会出现注入失败