Qt:Windows编程—DLL注入与卸载

前言

这里说的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
);

实现思路

注入DLL

我们现在思考下面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个问题的思路。

  1. 先在目标进程 申请 注入DLL的路径长度的 内存空间(也是LoadLibrary参数的),使用VirtualAllocEx
  2. 将DLL路径 写入的目标进程,使用WriteProcessMemory
  3. 获取LoadLibraryA函数的地址,通过GetProcAddress,LoadLibraryA函数在kernel32.dll中使用GetModuleHandle
  4. 创建线程并执行,使用CreateRemoteThread在目标进程中执行LoadLibrary。至就完成了将DLL注入到指定的进程中了。

卸载DLL

卸载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进程中。
Qt:Windows编程—DLL注入与卸载_第1张图片
Qt:Windows编程—DLL注入与卸载_第2张图片
我们用上次写的进程管理器 查看KuGou.exe进程中的DLL
Qt:Windows编程—DLL注入与卸载_第3张图片
滑到最下面
Qt:Windows编程—DLL注入与卸载_第4张图片
已经被注入到进程中了。
我们进行卸载DLL
Qt:Windows编程—DLL注入与卸载_第5张图片
卸载完毕,再次查看DLL列表
Qt:Windows编程—DLL注入与卸载_第6张图片
已经被成功卸载了。

核心代码

相关基础知识已经有了,下面我们来看核心代码

注入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

// 卸载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)。

你可能感兴趣的:(【Language_C++】,【Language_Qt,】,【Windows编程】)