这里说的DLL注入 是将我们指定的DLL注入到指定的进程中,DLL卸载也就是将指定进程中的DLL卸载下来。在Windows提供的API中有 CreateRemoteThread函数 见名知意 创建远程线程函数,这的远程指定的垮进程,让远程进程执行我们指定的线程回调函数。这就提供操作其他进程的契机。
CreateRemoteThread 函数原型
// 函数是不是和CreateThread非常像,实际上内部调用的该函数
HANDLE WINAPI 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
);
我们现在思考下面3个问题
第一个问题:如何加载DLL文件?DLL的调用方式,可以以参考 C/C++:Windows编程—调用DLL程序的2种方法。将一个DLL文件注入到目标进程 使用的是LoadLibrary函数。该函数的形式和ThreadProc形式一样,即可将LoadLibrary函数地址作为线程回调函数,这样目标进程会执行该函数将我们指定的DLL文件加载到目的进程中。
//加载一个DLL文件到进程的地址空间
HMODULE LoadLibraryA(
LPCSTR lpLibFileName //dll文件的路径
);
第二个问题:DLL的路径参数呢?需要将DLL文件的路径,同样写到目标进程中。这里需要借助WriteProcessMemory函数,
BOOL WINAPI WriteProcessMemory(
// 进程句柄
_In_ HANDLE hProcess,
// 指定写入目标进程内存的起始地址
_In_ LPVOID lpBaseAddress,
// 写入的内容的缓冲区起始地址
_In_ LPCVOID lpBuffer,
// lpBuffer长度
_In_ SIZE_T nSize,
// 接受实际写入内容的长度
_Out_ SIZE_T *lpNumberOfBytesWritten
);
该函数非常强大,比如在破解方面,可以实现一个内存补丁,在开发方面,该函数可以用于修改目标进程中指定的值。
这样目标进程用LoadLibrary函数加载指定的DLL文件了。回到第二个问题 写入目标进程的起始地址应该是多少?目标进程中的内存块允许将DLL文件的路径写进去吗?这就引出了第3个问题
第三个问题:如何确定应该将DLL文件的完整路径写入目标进程的哪块地址呢?对于目标进程来说,不会事先准备一块地址让用户进行写入,用户做的是自己在目标进程中申请一块内存,然后把DLL文件的路径进程写入。在目标进程中申请内存的函数是VirtualAllocEx().
// suc,返回值是在目标进程申请到的内存起始地址
// err,NULL
LPVOID WINAPI VirtualAllocEx(
// 指定进程句柄
_In_ HANDLE hProcess,
// 在目标进程中申请内存的起始地址
_In_opt_ LPVOID lpAddress,
// 申请内存的长度
_In_ SIZE_T dwSize,
// 申请内存的状态类型,如MEM_COMMIT
_In_ DWORD flAllocationType,
// 申请内存的属性,如PAGE_READWRITE
_In_ DWORD flProtect
);
// 获取指定模块的模块句柄
HMODULE WINAPI GetModuleHandle(
_In_opt_ LPCTSTR lpModuleName
);
那么注入DLL的思路,就是是上面3个问题的思路。
卸载DLL库的API函数
https://docs.microsoft.com/zh-cn/windows/desktop/api/libloaderapi/nf-libloaderapi-freelibrary
BOOL FreeLibrary(
HMODULE hLibModule
);
思路还是和注入的一样,因为我们在目标进程使用LoadLibraryA将线程注入了,那么卸载DLL同样要在目标进程中执行!步骤同注入一样。由于FreeLibrary参数为HMODULE 实际上就是一个指针值。这个句柄已经加载就已经存在。所以并不需要项目标进程申请空间和写入数据。为什么LoadLibraryA需要在内存中申请参数空间呢?因为字符串 有空间,事先并没有,必须自己开辟空间,然后将字符串指针值传入。
事先准备了一个DLLMsgBox.dll,在DLL加载和卸载的时候 会有个弹框效果。这里我们注入到KuGou.exe进程中。
我们用上次写的进程管理器 查看KuGou.exe进程中的DLL
滑到最下面
已经被注入到进程中了。
我们进行卸载DLL
卸载完毕,再次查看DLL列表
已经被成功卸载了。
相关基础知识已经有了,下面我们来看核心代码
// 注入DLL
void WorkerThread::injectDLL()
{
/*
注入DLL的思路步骤:
1. 在目标进程中申请一块内存空间(使用VirtualAllocEx函数) 存放DLL的路径,方便后续执行LoadLibraryA
2. 将DLL路线写入到目标进程(使用WriteProcessMemory函数)
3. 获取LoadLibraryA函数地址(使用GetProcAddress),将其做为线程的回调函数
4. 在目标进程 创建线程并执行(使用CreateRemoteThread)
*/
HANDLE targetProc = OpenProcess(PROCESS_ALL_ACCESS,FALSE,m_pId);
if( targetProc == NULL )
{
qDebug() << "OpenProcess error";
return;
}
QString dllPath = m_dllPath;
const char* pChar = dllPath.toStdString().c_str();
int dllLen = dllPath.length();
// 1.目标进程申请空间
LPVOID pDLLPath = VirtualAllocEx(targetProc,NULL,dllLen,MEM_COMMIT,PAGE_READWRITE );
if( pDLLPath == NULL )
{
qDebug() << "VirtualAllocEx error";
return;
}
SIZE_T wLen = 0;
// 2.将DLL路径写进目标进程内存空间
int ret = WriteProcessMemory(targetProc,pDLLPath,pChar,dllLen,&wLen);
if( ret == 0 )
{
qDebug() << "WriteProcessMemory error";
return;
}
// 3.获取LoadLibraryA函数地址
FARPROC myLoadLibrary = GetProcAddress(GetModuleHandleA("kernel32.dll"),"LoadLibraryA");
if( myLoadLibrary == NULL )
{
qDebug() << "GetProcAddress error";
return;
}
// 4.在目标进程执行LoadLibrary 注入指定的线程
HANDLE tHandle = CreateRemoteThread(targetProc,NULL,NULL,
(LPTHREAD_START_ROUTINE)myLoadLibrary,pDLLPath,NULL,NULL);
if(tHandle == NULL)
{
qDebug() << "CreateRemoteThread error";
return ;
}
qDebug() << "注入,wait ..." ;
WaitForSingleObject(tHandle,INFINITY);
CloseHandle(tHandle);
CloseHandle(targetProc);
qDebug() << "注入,finish ...";
emit doInjectFinish();
}
// 卸载DLL
void WorkerThread::uninstallDLL()
{
/*
卸载步骤和注入DLL步骤实质差不多.
注入DLL是 在目标进程中执行LoadLibraryA
卸载DLL是 在目标进程中执行FreeLibrary函数,不同的是卸载不需要再目标进程中申请空间,
因为FreeLibrary参数为HMODULE 实际上就是一个指针值。这个句柄已经加载就已经存在。
*/
HANDLE targetProc = OpenProcess(PROCESS_ALL_ACCESS,FALSE,m_pId);
if( targetProc == NULL )
{
qDebug() << "OpenProcess error";
return;
}
QString dllPath = m_dllPath;
// 1. 获取卸载dll的模块句柄
HANDLE snapHandele = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE ,m_pId);
if( INVALID_HANDLE_VALUE == snapHandele)
{
qDebug() << "CreateToolhelp32Snapshot error" ;
return;
}
MODULEENTRY32 entry = {0};
entry.dwSize = sizeof(entry);// 长度必须赋值
BOOL ret = Module32First(snapHandele,&entry);
HMODULE dllHandle = NULL;
while (ret) {
QString dllName = QString::fromWCharArray(entry.szModule);
if(dllPath.endsWith(dllName))
{
dllHandle = entry.hModule;
qDebug() << dllName;
break;
}
ret = Module32Next(snapHandele,&entry);
}
CloseHandle(snapHandele);
if( dllHandle == NULL )
{
qDebug() << "dll 并未被加载";
return;
}
// 2.获取FreeLibrary函数地址
FARPROC myLoadLibrary = GetProcAddress(GetModuleHandleA("kernel32.dll"),"FreeLibrary");
if( myLoadLibrary == NULL )
{
qDebug() << "GetProcAddress error";
return;
}
// 3.在目标进程执行FreeLibrary 卸载指定的线程
HANDLE tHandle = CreateRemoteThread(targetProc,NULL,NULL,
(LPTHREAD_START_ROUTINE)myLoadLibrary,dllHandle,NULL,NULL);
if(tHandle == NULL)
{
qDebug() << "CreateRemoteThread error";
return ;
}
qDebug() << "卸载,wait ..." ;
WaitForSingleObject(tHandle,INFINITY);
CloseHandle(tHandle);
CloseHandle(targetProc);
qDebug() << "卸载,finish ...";
emit doUninstallFinish();
}
整个qt工程请在这里下载(含DLLMsgBox.dll)。