DLL进程注入之CreateRemoteThread()

Dll注入原理

Dll注入一般指向一个正在运行的程序注入一个线程,注入的线程运行我们的代码,而我们的代码就以DLL(动态链接库)的形式存在。但是程序不会平白无故的就加载我们的dll,这时候就需要使用我们强大的Windows API了,它为我们提供了大量的函数来附加和操纵其他进程。
API中的所有函数都包含于DLL文件之中。其中,最重要的是“Kernel32.dll”(包含管理内存,进程和线程相关的函数),“User32.dll”(大部分是用户接口函数),和“GDI32.dll”(绘制图形和显示文本相关的函数)。。

Dll注入代码实现

DLL的注入方法有很多,这里介绍一种最经典的注入方法,远程进程注入。
这种方法灵活性高,同时要求掌握的知识也很多,其核心思想就是在目标进程中开启一个线程调用LoadLibrary函数来加载我们想要注入的dll.大致流程

首先是OpenProcess函数

  HANDLE OpenProcess(
  DWORD dwDesiredAccess, //渴望得到的访问权限(标志)
  BOOL bInheritHandle, // 是否继承句柄
  DWORD dwProcessId// 进程标示符
        );

获得返回句柄之后再用VirtualAllocEx函数,在目标进程中开辟一块内存存放我们的dll的路径。

  LPVOID VirtualAllocEx( 
  HANDLE hProcess, //目标进程句柄
  LPVOID lpAddress, //保留页面的内存地址,一般用NULL自动分配
  SIZE_T dwSize, //欲分配的内存大小,字节单位
  DWORD flAllocationType, //为特定的页面区域分配内存中或磁盘

的页面文件中的物理存储
DWORD flProtect //受保护状态
);
之后使用WriteProcessMemory函数向目标内存写入dll地址

BOOL WINAPI WriteProcessMemory(
  _In_  HANDLE  hProcess, //由OpenProcess返回的进程句柄。
  _In_  LPVOID  lpBaseAddress, //要写的内存首地址
  _In_  LPCVOID lpBuffer, //指向要写的数据的指针。
  _In_  SIZE_T  nSize, //要写入的字节数。
  _Out_ SIZE_T  *lpNumberOfBytesWritten //返回值。返回实际写入的字节
);

用GetProcAddress函数获得LoadLibraryW函数的起始地址。LoadLibraryW函数位于Kernel32.dll中,再用CreateRemoteThread函数让目标进程执行LoadLibraryW来加载被注入的dll。函数结束将返回载入dll后的模块句柄。
注意:这里的LoadLibrary函数在底层实际调用有两种可能,如果目标程序使用的是ANSI编码方式,LoadLibrary实际调用的是LoadLibraryA,其参数字符串应当是ANSI编码;
  如果目标程序使用的是Unicode编码方式,LoadLibrary实际调用的是LoadLibraryW,其参数字符串应当是Unicode编码。
  这使得注入过程变得很麻烦,为了减少复杂性,不妨直接使用LoadLibraryA或LoadLibraryW而不是用LoadLibrary函数来避免这一麻烦。另外,即使使用的是LoadLibraryA,LoadLibraryA也会将传入的ANSI编码的字符串参数转换成Unicode编码后再调用LoadLibraryW。综上,不妨一致使用LoadLibraryW函数,并且字符串用Unicode编码即可。

HMODULE hModule = GetModuleHandle(L"kernel32.dll");
    if (!hModule)
    {
        printf("GetModuleHandle Error !\n");
        GetLastError();
        CloseHandle(hProcess);
        return FALSE;
    }
        // #6.获取LoadLibraryA 函数地址
    LPTHREAD_START_ROUTINE dwLoadAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "LoadLibraryW");
    if (!dwLoadAddr)
    {
        printf("GetProcAddress Error !\n");
        GetLastError();
        CloseHandle(hProcess);
        CloseHandle(hModule);
        return FALSE;
    }

完整代码

bool Inject(DWORD dwId, WCHAR* szPath)//参数1:目标进程PID  参数2:DLL路径
{
    //一、在目标进程中申请一个空间
    /*
    【1.1 获取目标进程句柄】
    参数1:想要拥有的进程权限(本例为所有能获得的权限)
    参数2:表示所得到的进程句柄是否可以被继承
    参数3:被打开进程的PID
    返回值:指定进程的句柄
    */
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwId);
    /*
    【1.2 在目标进程的内存里开辟空间】
    参数1:目标进程句柄
    参数2:保留页面的内存地址,一般用NULL自动分配
    参数3:欲分配的内存大小,字节单位
    参数4:MEM_COMMIT:为特定的页面区域分配内存中或磁盘的页面文件中的物理存储
    参数5:PAGE_READWRITE 区域可被应用程序读写
    返回值:执行成功就返回分配内存的首地址,不成功就是NULL
    */
    LPVOID pRemoteAddress = VirtualAllocEx(
        hProcess,
        NULL,
        wcslen(szPath) * 2,
        MEM_COMMIT,
        PAGE_READWRITE
    );

    //二、 把dll的路径写入到目标进程的内存空间中

    DWORD dwWriteSize = 0;
    /*
    【写一段数据到刚才给指定进程所开辟的内存空间里】
    参数1:OpenProcess返回的进程句柄
    参数2:准备写入的内存首地址
    参数3:指向要写的数据的指针(准备写入的东西)
    参数4:要写入的字节数(东西的长度+0/)
    参数5: 返回值。返回实际写入的字节
    */
    BOOL bRet = WriteProcessMemory(hProcess, pRemoteAddress, szPath, wcslen(szPath)*2, NULL);
    //三、 创建一个远程线程,让目标进程调用LoadLibrary

    /*
    参数1:该远程线程所属进程的进程句柄
    参数2:一个指向 SECURITY_ATTRIBUTES 结构的指针, 该结构指定了线程的安全属性
    参数3:线程栈初始大小,以字节为单位,如果该值设为0,那么使用系统默认大小
    参数4:在远程进程的地址空间中,该线程的线程函数的起始地址(也就是这个线程具体要干的活儿)
    参数5:传给线程函数的参数(刚才在内存里开辟的空间里面写入的东西)
    参数6:控制线程创建的标志。0(NULL)表示该线程在创建后立即运行
    参数7:指向接收线程标识符的变量的指针。如果此参数为NULL,则不返回线程标识符
    返回值:如果函数成功,则返回值是新线程的句柄。如果函数失败,则返回值为NULL
    */
    // #5.获取模块地址
    HMODULE hModule = GetModuleHandle(L"kernel32.dll");
    if (!hModule)
    {
        printf("GetModuleHandle Error !\n");
        GetLastError();
        CloseHandle(hProcess);
        return FALSE;
    }
        // #6.获取LoadLibraryA 函数地址
    LPTHREAD_START_ROUTINE dwLoadAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "LoadLibraryW");
    if (!dwLoadAddr)
    {
        printf("GetProcAddress Error !\n");
        GetLastError();
        CloseHandle(hProcess);
        CloseHandle(hModule);
        return FALSE;
    }
    // //7.创建远程线程,加载dll

    HANDLE hThread = CreateRemoteThread(
        hProcess,
        NULL,
        0,
        (LPTHREAD_START_ROUTINE)dwLoadAddr,
        pRemoteAddress,
        NULL,
        NULL
    );
    //WaitForSingleObject(hThread, -1); //当句柄所指的线程有信号的时候,才会返回

    ///*
    //四、 【释放申请的虚拟内存空间】
    //参数1:目标进程的句柄。该句柄必须拥有 PROCESS_VM_OPERATION 权限
    //参数2:指向要释放的虚拟内存空间首地址的指针
    //参数3:虚拟内存空间的字节数
    //参数4:MEM_DECOMMIT仅标示内存空间不可用,内存页还将存在。
    //       MEM_RELEASE这种方式很彻底,完全回收。
    //*/
    //VirtualFreeEx(hProcess, pRemoteAddress, 1, MEM_DECOMMIT);
    return 0;
}
DWORD GetPid(WCHAR* szName)
{
    HANDLE hprocessSnap = NULL;
    PROCESSENTRY32  pe32 = { 0 };
    hprocessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    /*if (hprocessSnap == (HANDLE)-1) { return 0; }*/
    pe32.dwSize = sizeof(PROCESSENTRY32);
    if (Process32First(hprocessSnap, &pe32))
    {
        do {
            if (!wcscmp(szName, pe32.szExeFile))
                return (int)pe32.th32ProcessID;
        } while (Process32Next(hprocessSnap, &pe32));
    }
    else
        CloseHandle(hprocessSnap);
    return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
    wchar_t wStr[] = L"C:\\Release\\Dnew.dll";
    DWORD dwId = 0;
    DWORD dwPid = 0;
    WCHAR S[] = L"sublime_text.exe";
    DWORD SS =  GetPid(S);
    printf("目标窗口的进程PID为:%d\n", SS);
    //参数1:目标进程的PID
    //参数2:想要注入DLL的路径
    Inject(SS, wStr);
    system("pause");
    return 0;
}

这里用sublime来测试,随便打开个文档,
之后再运行我们的注入程序

可以看到文档处多了个弹框

而且查看进程,也成功加载了我们的DLL(在这里只是弹窗)

附赠测试dll代码

#include <windows.h>
#include <pch.h>

DWORD WINAPI runBot(LPVOID lpParam) {
    // 此处可以写具体的bot代码
    return 1;
}


BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        MessageBoxA(NULL, "DLL Attached!\n", "Game Hacking", MB_OK | MB_TOPMOST);
        CreateThread(NULL, 0, &runBot, NULL, 0, NULL);
        break;
    }
    return TRUE;
}
posted @ 2021-03-09 17:34  Lushun  阅读(1630)  评论(1编辑  收藏  举报