【逆向学习】Windows平台下的DLL注入

CTF · 2023-03-14 · 1271 人浏览

DLL注入

通过DLL注入可以对其他进程进行hook/热补丁/修复BUG,同时也是渗透其他进程的有效方法。

DLL被加载到进程后会自动运行DIIMain()函数,用户可以把想执行的代码放到DIIMain()函数,每当加载DLL时,添加的代码就会自然而然得到执行。利用该特性可修复程序Bug,或向程序添加新功能。

本篇博客将使用Inline Hook和DLL注入技术实现控制程序执行流

DLL注入流程

使用MS编译dll动态链接库后,可以通过一些常见的方式进行DLL的注入:

  1. 创建远程线程(CreateRemoteThread() API)
  2. 使用注册表(AppInit_DLLs值)
  3. 消息勾取(SetWindowsHookEx() API)

通过这些方法编写注入程序,然后对写有Inline Hook逻辑的DLL程序进行加载并且注入进目标进程

代码编写

DLL编写

DLL加载到进程后会运行DllMain()函数,通过这个特点,我们可以修改上一篇博客中的Inline Hook代码为如下代码

其中,DllMain()参数中的ul_reason_for_call与之前TLS_Callback学习博客中的dwReason是相同的。因此,在进程启动的时候我们调用InlineHook()对MessageBoxA()进行hook

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

#include <windows.h>
#include <stdio.h>
#include <iostream>
#include <tchar.h>

//修改API入口为 jmp xxx 使得程序能跳转到自己的函数
BYTE __NewCode[7] = { 0xE9, 0x0, 0x0, 0x0, 0x0 };
BYTE __OldCode[7] = { 0 };

FARPROC Messagebox_FARPROC;
HMODULE g_hMod;

int WINAPI MyMessageBoxA(
    HWND hWnd,          // handle to owner window
    LPCTSTR lpText,     // text in message box
    LPCTSTR lpCaption,  // message box title
    UINT uType          // message box style
);


void InlineHook()
{
    DWORD dwOldProtect = 0;
    //首先获取user32.dll的模块句柄
    HMODULE user32_HMODULE = LoadLibraryA("user32.dll");
    //利用GetProcAddress获取MessageBoxA导出函数的地址
    Messagebox_FARPROC = GetProcAddress(user32_HMODULE, "MessageBoxA");
    //利用ReadProcessMemory读取内存中MessageBoxA的前5字节
    if (!ReadProcessMemory(GetCurrentProcess(), Messagebox_FARPROC, __OldCode, 6, NULL)) {
        printf("[!] ReadProcessMemory Failed");
    }
    printf("%x%x%x%x%x\n", __OldCode[0], __OldCode[1], __OldCode[2], __OldCode[3], __OldCode[4]);
    //E9跳转为jmp地址到目标地址的间隔,地址需要进行计算,计算出来后赋值给newcode的后4位
    *((ULONG*)(__NewCode + 1)) = (ULONG)&MyMessageBoxA - ((ULONG)Messagebox_FARPROC + 5);

    //memcpy(&__NewCode[1], &JmpAddress, 4);

    printf("JmpAddress : %x\n", *((ULONG*)(__NewCode + 1)));
    //将权限替换为可读写执行
    VirtualProtect(Messagebox_FARPROC, 6, PAGE_EXECUTE_READWRITE, &dwOldProtect);
    //直接写入内存
    WriteProcessMemory(GetCurrentProcess(), Messagebox_FARPROC, __NewCode, 5, NULL);
    //恢复之前的权限
    VirtualProtect(Messagebox_FARPROC, 6, dwOldProtect, &dwOldProtect);

}

int WINAPI MyMessageBoxA(
    HWND hWnd,          // handle to owner window
    LPCTSTR lpText,     // text in message box
    LPCTSTR lpCaption,  // message box title
    UINT uType          // message box style
)
{
    printf("Hook Successful\n");
    
    //恢复原本的MessageBoxA,否则不能调用
    WriteProcessMemory(GetCurrentProcess(), Messagebox_FARPROC, __OldCode, 5, NULL);
    //可以使用参数进行调用
    Sleep(3000);
    MessageBoxA(NULL, (const char*)lpText, (const char*)lpCaption, MB_OK);
    MessageBoxA(NULL, "Xunflash", "Hook successful", MB_OK);
    //恢复hook
    WriteProcessMemory(GetCurrentProcess(), Messagebox_FARPROC, __NewCode, 5, NULL);
    return 0;
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{

    HANDLE hThread = NULL;
    g_hMod = (HMODULE)hModule;
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
        InlineHook();
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;


    //InlineHook();
    //调用MessageBoxA
    //MessageBoxA(NULL, "Hello World", "Title", MB_OK);
}

被注入程序编写

简单的调用MessageBoxA即可

#include <windows.h>
#include <stdio.h>

int main()
{
    MessageBoxA(NULL, "test", "test123", S_OK); 
}

注入

使用代码注入器注入

这里我们先使用Github上开源的代码注入器Xenos对程序进行注入,测试效果

image-20230312145826326

可以看到已经注入成功

image-20230312145909551

使用CreateRemoteThread() API注入

引用《逆向工程核心原理》p204页,利用CreateRemoteThread() API进行注入的代码注入器

具体步骤:

  1. OpenProcess获取已运行起来的目标进程句柄
  2. 在目标进程中分配DLL文件名大小的内存
  3. 将DLL路径写入分配的内存
  4. 利用GetModuleHandle获取kernal32的句柄,并使用GetProcAddress获取LoadLibraryW() API的地址
  5. 利用CreateRemoteThread在目标进程上启动线程运行LoadLibraryW,并且参数为刚刚在内存中写入的DLL路径,实现加载DLL
#include<Windows.h>
#include<tchar.h>

BOOL InjectDll(DWORD dwPid, LPCTSTR szDllPath) {
    HANDLE hProcess = NULL, hThread = NULL;
    HMODULE hMod = NULL;
    LPVOID pRemoteBuf = NULL;
    DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR);
    LPTHREAD_START_ROUTINE pThreadProc;
    //1.使用dwPid获取 目标进程句柄
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
    if (!hProcess)
    {
        _tprintf(L"OpenProcess(%d)v failed!!! [%d]\n", dwPid, GetLastError());
        return FALSE;
    }
    //2.在目标进程中分配szDllName大小的内存,dll文件的路径
    pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
    //3.将dll路径写入分配的内存
    WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);
    //4.获取LoadLibraryW()API的地址
    hMod = GetModuleHandle(L"kernel32.dll");
    pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");
    //5.在目标程序进程中运行线程
    //pThreadProc为目标程序进程内存中的LoadLibraryW地址
    //pRemoteBuf为写入到目标程序进程内存中的myhack.dll字符串地址
    hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);

    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);
    CloseHandle(hProcess);
    return TRUE;

}
int _tmain(int argc, TCHAR* argv[]) {
    if (argc != 3)
    {
        //检查输入程序的参数是否为3个
        _tprintf(L"USAGE: %s Pid Dll_Path \n", argv[0]);
    }
    if (InjectDll((DWORD)_tstol(argv[1]), argv[2]))
    {
        _tprintf(L"InjectDll(\"%s\") Success!!!\n", argv[2]);
    }
    else
    {
        _tprintf(L"InjectDll(\"%s\") Failed!!!\n", argv[2]);
    }
    return 0;
}

使用AppInit_DLLs进行注入

此方式其实就是对注册表进行修改,让系统初始化程序的时候就加载上DLL。

只需要进入注册表中如下路径

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows

然后将LoadAppInit_DLL的值修改为1,并修改AppInit_DLLs的值为你想要注入的DLL的路径。重启电脑即可

这种方法实际上会向系统中所有程序都注入此DLL,可能导致系统出现问题。

使用SetWindowsHookEx()进行注入

这种方法利用了Windows的消息机制在事件发送到os之间设置一条钩链,来钩取不同的消息。这种方式只能勾取到SetWindowsHookEx中第一个参数指定的几种消息,如下摘取自MSDN(谷歌机翻)

价值意义
WH_CALLWNDPROC安装一个钩子过程,在系统将消息发送到目标窗口过程之前监视消息。有关详细信息,请参阅CallWndProc)挂钩过程。
WH_CALLWNDPROCRET安装一个钩子过程,在消息被目标窗口过程处理后监视消息。有关详细信息,请参阅CallWndRetProc挂钩过程。
WH_CBT安装一个钩子过程,它接收对 CBT 应用程序有用的通知。有关详细信息,请参阅CBTProc)挂钩过程。
WH_DEBUG安装一个对调试其他挂钩过程有用的挂钩过程。有关详细信息,请参阅DebugProc)挂钩过程。
WH_FOREGROUNDIDLE安装将在应用程序的前台线程即将变为空闲时调用的挂钩过程。此挂钩对于在空闲时间执行低优先级任务很有用。有关详细信息,请参阅ForegroundIdleProc)挂钩过程。
WH_GETMESSAGE安装一个挂钩过程,用于监视发布到消息队列的消息。有关详细信息,请参阅GetMsgProc)挂钩过程。
WH_JOURNALPLAYBACK安装一个挂钩过程,用于发布以前由WH_JOURNALRECORD挂钩过程记录的消息。有关详细信息,请参阅JournalPlaybackProc)挂钩过程。(Windows11已弃用)
WH_JOURNALRECORD安装一个挂钩过程,记录发布到系统消息队列的输入消息。这个钩子对于录制宏很有用。有关详细信息,请参阅JournalRecordProc)挂钩过程。(Windows11已弃用)
WH_KEYBOARD安装一个监视击键消息的挂钩过程。有关详细信息,请参阅KeyboardProc)挂钩过程。
WH_KEYBOARD_LL安装一个监视低级键盘输入事件的挂钩过程。有关详细信息,请参阅LowLevelKeyboardProc)挂钩过程。
WH_MOUSE安装一个监视鼠标消息的挂钩程序。有关详细信息,请参阅MouseProc)挂钩过程。
WH_MOUSE_LL安装一个钩子程序来监视低级鼠标输入事件。有关详细信息,请参阅LowLevelMouseProc)挂钩过程。
WH_MSGFILTER安装一个挂钩过程,用于监视由于对话框、消息框、菜单或滚动条中的输入事件而生成的消息。有关详细信息,请参阅MessageProc)挂钩过程。
WH_SHELL安装一个钩子过程,它接收对 shell 应用程序有用的通知。有关详细信息,请参阅ShellProc)挂钩过程。
WH_SYSMSGFILTER安装一个挂钩过程,用于监视由于对话框、消息框、菜单或滚动条中的输入事件而生成的消息。挂钩过程监视与调用线程位于同一桌面的所有应用程序的这些消息。有关详细信息,请参阅SysMsgProc)挂钩过程。

似乎也不能通过这种方式进行一些hook...普遍是通过这种方式对键盘输入事件进行监视,而后通过相应的回调函数对指定的进程进行拦截

详细过程可以参考其他文章。这里就不赘述

RE
  1. 伍德 2023-04-23

    您真的好厉害

Theme Jasmine by Kent Liao