《逆向工程核心原理》之DLL注入

Posted Zer0o

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《逆向工程核心原理》之DLL注入相关的知识,希望对你有一定的参考价值。

DLL注入

DLL注入指的是向运行中的其他进程强制插入特定的DLL文件。从技术细节来说,DLL注入命令其他进程自行调用LoadLibrary() API,加载(Loading)用户指定的DLL文件。DLL注入与一般DLL加载的区别在于,加载的目标进程是其自身或其他进程。下图描述了DLL注入的概念。

从图中可以看到,myhack.dll已被强制插入notepad进程(本来notepad并不会加载myhack.dll )。加载到notepad.exe进程中的myhack.dll与已经加载到notepad.exe进程中的DLL(kemel32.dll、user32.dll) —样,拥有访问notepad.exe进程内存的(正当的)权限,这样用户就可以做任何想做的事了(比如:向notepad添加通信功能以实现Messenger、文本网络浏览器等)。
DLL被加载到进程后会自动运行DllMain()函数,用户可以把想执行的代码放到DllMain()函数,每当加载DLL时,添加的代码就会自然而然得到执行。利用该特性可修复程序Bug,或向程序添加新功能。

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

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved)

    switch( dwReason )
    
        case DLL_PROCESS_ATTACH:
            // 添加想执行的代码
            break;
        case DLL_THREAD_ATTACH:
            break;
        case DLL_THREAD_DETACH:
            break;	
        case DLL_PROCESS_DETACH:
            break;	
    

    return TRUE;

DLL注入示例

使用LoadLibrary() API加载某个DLL时,该DLL中的DllMain()函数就会被调用执行。DLL注入的工作原理就是从外部促使目标进程调用LoadLibrary() API (与一般DLL加载相同),所以会强制调用执行DLL的DllMain()函数。并且,被注入的DLL拥有目标进程内存的访问权限,用户可以随意操作(修复Bug、添加功能等)。下面看一些使用DLL注入技术的示例。

改善功能与修复Bug

DLL注入技术可用于改善功能与修复Bug。没有程序对应的源码,或直接修改程序比较困难时,就可以使用DLL注入技术为程序添加新功能(类似于插件),或者修改有问题的代码、数据。

消息钩取

Windows OS默认提供的消息钩取功能应用的就是一种DLL注入技术。与常规的DLL注入唯一的区别是,OS会直接将已注册的钩取DLL注入目标进程。

我曾经从网上下载过一个Hex Editor,它不支持鼠标滚轮滑动,所以我用消息钩取技术为其添加了鼠标滚轮支持。虽然可以下载更多、更好用的Hex Editor,但是利用学到的技术改善、扩展程序功能是一种非常妙的体验。这样不仅能解决问题,还锻炼了 我们灵活应用技术的能力(此后我就开始对使用逆向技术改善已有程序的功能产生了浓厚兴趣)。

API 钩取

API钩取广泛应用于实际的项目开发,而进行API钩取时经常使用DLL注入技术。先创建好DLL形态的钩取函数,再将其轻松注入要钩取的目标进程,这样就完成了API钩取。这灵活运用了 “被注入的DLL拥有目标进程内存访问权限”这一特性。

其他应用程序

DLL注入技术也应用于监视、管理PC用户的应用程序。比如,用来阻止特定程序(像游戏、股票交易等)运行、禁止访问有害网站,以及监视PC的使用等。管理员(或者父母)主要安装这类拦截/阻断应用程序来管理/监视。受管理/监视的一方当然千方百计地想关闭这些监视程序,但由于这些监视程序采用DLL注入技术,它们可以隐藏在正常进程中运行,所以管理员一般不用担心被发现或被终止(若用户强制终止Windows系统进程,也会一并关闭系统,最后也算达成了拦截/阻断这一目标)。

恶意代码

恶意代码制作者们是不会置这么好的技术于不顾的,他们积极地把DLL注入技术运用到自己制作的恶意代码中。这些入把自己编写的恶意代码隐藏到正常进程(winlogon.exe、services.exe、svchost.exe、explorer.exe等),打开后门端口( Backdoor port ),尝试从外部连接,或通过键盘偷录(Keylogging)功能将用户的个入信息盗走。只有了解恶意代码制作者们使用的手法,才能拿出相应对策

DLL注入的实现方法

向某个进程注入DLL时主要使用以下三种方法:

  • 创建远程线程(CreateRemoteThread() API)
  • 使用注册表(AppInit_DLLs值)
  • 消息钩取(SetWindowsHookEx() API)

使用CreateRemoteThread()函数进行DLL注入

练习示例myhack.dll

本示例将把myhack.dl注入notepad.exe进程,被注入的myhack.dll功能是联网下载http://www.naver.com/index.html文件
首先打开32位notepad++.exe(必须是32位,否则实验不能出来index.html)

鉴于很难找到32位的notepad,下面贴个32位notepad++.exe下载地址
链接:https://pan.baidu.com/s/1ErLkLlJQ28gN2H_DBghq_w?pwd=fire
提取码:fire

和一个DLL文件(此DLL的作用是下载一个.html)以及将DLL注入进notepad的EXE文件:

首先我们打开notepad文件,利用process explorer查看notepad文件的PID:

打开管理员cmd(否则会报错,权限不够),在InjectDll.exe目录下,输入指令:InjectDll.exe 12440 C:\\Users\\Zer0\\Desktop\\bin\\myhack.dll

cmd显示我们注册成功,然后我们在process explorer上查看dll是否被注入成功(如果用的是64位记事本,那么很有可能即使cmd出现success的情况,在process explorer中也找不到myhack.dll,同时目录也不会生成index.html),在View菜单中,选择Show Lower Pane与Lower Pane Views - DLLs项。就可以看到:

可以看到myhack.dll已经被成功加载进去了。然后我们打开dll注入成功后的index.html,查看url是否被成功下载。

出现这个页面就是DLL注入成功了!

分析示例源码

以下介绍的源代码是用Micosoft Visual C++ Express 2010编写的,在Windows XP/732位操作系统中通过测试。

Myhack.cpp
先分析一下myhack.dll源代码(myhack.cpp )。

#include "windows.h"
#include "tchar.h"

#pragma comment(lib, "urlmon.lib")

#define DEF_URL     	(L"http://www.naver.com/index.html")
#define DEF_FILE_NAME   (L"index.html")

HMODULE g_hMod = NULL;

DWORD WINAPI ThreadProc(LPVOID lParam)

    TCHAR szPath[_MAX_PATH] = 0,;

    if( !GetModuleFileName( g_hMod, szPath, MAX_PATH ) )
        return FALSE;
	
    TCHAR *p = _tcsrchr( szPath, \'\\\\\' );
    if( !p )
        return FALSE;
	//下载指定网站的index.html
    _tcscpy_s(p+1, _MAX_PATH, DEF_FILE_NAME);

    URLDownloadToFile(NULL, DEF_URL, szPath, 0, NULL);

    return 0;


BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)

    HANDLE hThread = NULL;

    g_hMod = (HMODULE)hinstDLL;

    switch( fdwReason )
    
    case DLL_PROCESS_ATTACH : 	//加载时
        OutputDebugString(L"<myhack.dll> Injection!!!"); //输出调试字符串
        hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL); //创建线程
        CloseHandle(hThread);
        break;
    

    return TRUE;

在DllMain()函数中可以看到,该DLL被加载(DLL_PROCESS_ATTACH)时,先输出一个调试字符串(“myhack.dll Injection!!!”),然后创建线程调用函数(ThreadProc)。在ThreadProc()函数中通过调用urlmon!URLDownloadToFile() API来下载指定网站的index.html文件。前面提到过,向进程注入DLL后就会调用执行该DLL的DllMain()函数。所以当myhack.dll注入notepad.exe进程后,最终会调用执行URLDownloadToFile()API。
InjectDII.cpp
InjectDll.exe程序用来将myhack.dll注入notepad.exe进程,下面看一下其源代码。

#include "windows.h"
#include "tchar.h"

BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) 

    TOKEN_PRIVILEGES tp;
    HANDLE hToken;
    LUID luid;

    if( !OpenProcessToken(GetCurrentProcess(),
                          TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, 
			              &hToken) )
    
        _tprintf(L"OpenProcessToken error: %u\\n", GetLastError());
        return FALSE;
    

    if( !LookupPrivilegeValue(NULL,           // lookup privilege on local system
                              lpszPrivilege,  // privilege to lookup 
                              &luid) )        // receives LUID of privilege
    
        _tprintf(L"LookupPrivilegeValue error: %u\\n", GetLastError() ); 
        return FALSE; 
    

    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    if( bEnablePrivilege )
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    else
        tp.Privileges[0].Attributes = 0;

    // Enable the privilege or disable all privileges.
    if( !AdjustTokenPrivileges(hToken, 
                               FALSE, 
                               &tp, 
                               sizeof(TOKEN_PRIVILEGES), 
                               (PTOKEN_PRIVILEGES) NULL, 
                               (PDWORD) NULL) )
     
        _tprintf(L"AdjustTokenPrivileges error: %u\\n", GetLastError() ); 
        return FALSE; 
     

    if( GetLastError() == ERROR_NOT_ALL_ASSIGNED )
    
        _tprintf(L"The token does not have the specified privilege. \\n");
        return FALSE;
     

    return TRUE;


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 获取目标进程(notepad.exe)句柄(PROCESS_ALL_ACCESS权限),然后就可以用 hProcess 控制进程.
    if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
            //OpenProcess调用API,借助PID获取目标权限
        _tprintf(L"OpenProcess(%d) failed!!! [%d]\\n", dwPID, GetLastError());
        return FALSE;
    

    // #2. 在目标进程(notepad.exe) 内存中分配 szDllName 大小的内存,返回 pRemoteBuf 作为该缓冲区的地址.
    pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);

    // #3. 将 myhack.dll 路径写入刚刚分配的缓冲区.
    WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);

    // #4. 获取 LoadLibraryW() API 地址,kernel32.dll在每个进程中的加载地址相同(这个特性就是我们要利用的).
    hMod = GetModuleHandle(L"kernel32.dll");
    pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");
	
    // #5. 在 notepad.exe 中运行线程
    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)
    
        _tprintf(L"USAGE : %s <pid> <dll_path>\\n", argv[0]);
        return 1;
    

    // change privilege
    if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
        return 1;

    // inject dll
    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;

main()函数的主要功能是检查输入程序的参数,然后调用InjectDll()函数。InjectDll()函数是用 来实施DLL注入的核心函数,其功能是命令目标进程(notepadexe )自行调用LoadLibrary(“myhack.dll”)API。下面逐行详细查看InjectDll()函数。

  • 获取目标进程句柄
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)

调用OpenProcessO API,借助程序运行时以参数形式传递过来的dwPID值,获取notepad.exe进程的句柄(PROCESS_ALL_ACCESS权限)。得到PROCESS_ALL_ACCESS权限后,就可以使用获取的句柄(hProcess )控制对应进程(notepad.exe )0

  • 将要注入的DLL路径写入目标进程内存
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEMCOMMIT, PAGEREADWRITE);

需要把即将加载的DLL文件的路径(字符串)告知目标进程(notepacLexe )。因为任何内存空间都无法进行写入操作,故先使用VirtualAllocEx() API在目标进程(notepad.exe)的内存空间中分配一块缓冲区,且指定该缓冲区的大小为DLL文件路径字符串的长度(含Terminating NULL )即可。

VirtualAllocEx()函数的返回值(pRemoteBuf)为分配所得缓冲区的地址。该地址并不是程序(Inject.exe )自身进程的内存地址,而是hProcess句柄所指目标进程(notepad.exe)的内存地址,请务必牢记这一点。

WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID) szDUName, dwBufSize, NULL);

使用WriteProcessMemory() API将DLL路径字符串(“C:\\work\\dummy.dll”)写入分配所得缓冲区(pRemoteBuf)地址。WriteProcessMemoryO API所写的内存空间也是hProcess句柄所指的目标进程(notepad.exe )的内存空间。这样,要注入的DLL文件的路径就被写入目标进程(notepad.exe )的内存空间。

Windows操作系统提供了调试API,借助它们可以访问其他进程的内存空间。其中具有代表性的有 VirtualAllocEx()、VirtualFreeEx()、WriteProcessMemory()、ReadProcessMemory()等。

  • 获取LoadLibraryW() API地址
hMod = GetModuleHandle("kernel32 .dll");
pThreadProc = (LPTHREAD_STARTROUTINE)GetProcAddress(hMod, "LoadLibraryW");

调用LoadLibrary() API前先要获取其地址(LoadLibraryW()是LoadLibrary()的Unicode字符串版本)。
最重要的是理解好以上代码的含义。我们的目标明明是获取加载到notepaclexe进程的kernel32.dll的LoadLibraryW() API的起始地址,但上面的代码却用来获取加载到InjectDll.exe进程的kernel32.dll的LoadLibraryW() API的起始地址。如果加载到notepad.exe进程中的kemel32.dll的地址与加载到InjectDll.exe进程中的kemel32.dll的地址相同,那么上面的代码就不会有什么问题。但 是如果kemel32.dll在每个进程中加载的地址都不同,那么上面的代码就错了,执行时会发生内存引用错误。
其实在Windows系统中,kernel32.dll在每个进程中的加载地址都是相同的。
《Windows核心编程》一书中对此进行了介绍,此后这一特性被广泛应用于DLL注入技术。

根据OS类型、语言、版本不同,kernel32.dll加载的地址也不同。并且Vista/7中应用了新的ASLR功能,每次启动时,系统DLL加载的地址都会改变。但是在系统运行期间它都会被映射(Mapping)到每个进程的相同地址。Windows操作系统中,DLL首次进入内存称为“加载”(Loading),以后其他进程需要使用相同DLL时不必再次加载,只要将加载过的DLL代码与资源映射一下即可,这种映射技术有利于提高内存的使用效率。

像上面这样,OS核心DLL会被加载到自身固有的地址,DLL注入利用的就是Windows OS的这一特性(该特性也可能会被恶意使用,成为Windows安全漏洞)。所以,导入InjectDll.exe进程中的LoadLibraryW()地址与导入notepad.exe进程中的LoadLibraryW()地址是相同的。

一般而言,DLL文件的ImageBase默认为0x10000000,依次加载a.dll与b.dll时,先加载的a.dll被正常加载到0x10000000地址处,后加载的b.dll无法再被加载到此,而是加载到其他空白地址空间,也就是说,该过程中发生了 DLL重定位(因为a.dll已经先被加载到它默认的地址处)。
若kemel32.dll加载到各个进程时地址各不相同,那么上述代码肯定是错误的。但实际在Windows操作系统中,kemel32.dll不管在哪个进程都会被加载至相同地址。为什么会这样呢?我借助PEView软件查看了 Windows操作系统的核心DLL文件的 ImageBase值,罗列如下表(Windows XP SP3版本,根据Windows更新不同,各值会有变化)。

微软整理了一份OS核心DLL文件的ImageBase值,防止各DLL文件加载时出现区域重合,这样加载DLL就不会发生DLL重定位了。

  • 在目标进程中运行远程线程(Remote Thread)
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
pThreadProc = notepad.exe进程内存中的LoadLibrary()地址
pRemoteBuf = notepad.exe进程内存中的 “c:\\work\\myhack.dll” 字符串地址

一切准备就绪后,最后向notepad.exe发送一个命令,让其调用LoadLibraryW() API函数加载指定的DLL文件即可,遗憾的是Windows并未直接提供执行这一命令的API。但是我们可以另辟蹊径,使用CreateRemoteThread()这个API (在DLL注入时几乎总会用到)。CreateRemoteThread()API用来在目标进程中执行其创建出的线程,其函数原型如下:

CreateRemoteThread(
    _In_ HANDLE hProcess,
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
    _In_ SIZE_T dwStackSize,
    _In_ LPTHREAD_START_ROUTINE lpStartAddress,
    _In_opt_ LPVOID lpParameter,
    _In_ DWORD dwCreationFlags,
    _Out_opt_ LPDWORD lpThreadId
    );

除第一个参数hProcess外,其他参数与CreateThread()函数完全一样。hProcess参数是要执行线程的目标进程(或称“远程进程”、“宿主进程”)的句柄。IpStartAddress与IpParameter参数分别给出线程函数地址与线程参数地址。需要注意的是,这2个地址都应该在目标进程虚拟内存空间中(这样目标进程才能认识它们)。
初次接触DLL注入技术的读者朋友可能会头昏脑涨、不知所云。本来想向其他进程注入DLL文件,这里为何突然出现线程运行函数呢?仔细观察线程函数ThreadProc()与LoadLibrary()API,可以从中得到一些启示。

// 这里直接搬运了PTHREAD_START_ROUTINE的定义更为准确
typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(
    LPVOID lpThreadParameter
    );
typedef PTHREAD_START_ROUTINE LPTHREAD_START_ROUTINE;


HINSTANCE LoadLibrary(
LPCTSTR lpLibFileName
 // address of filename of executable module
);

两函数都有一个4字节的参数,并返回一个4字节的值。也就是说,二者形态结构完全一样,灵感即源于此。调用CreateRemoteThread()时,只要将LoadLibrary()函数的地址传递给第四个参数IpStartAddress,把要注入的DLL的路径字符串地址传递给第五个参数IpParameter即可(必须是目标进程的虚拟内存空间中的地址)。由于前面已经做好了一切准备,现在调用该函数使目标进程加载指定的DLL文件就行了。
其实,CreateRemoteThread()函数最主要的功能就是驱使目标进程调用LoadLibrary()函数,进而加载指定的DLL文件。

调试方法

首先我们打开一个未注入myhack.dll的notepad

然后利用OD的attach(File->Attach)功能,将notepad(附加)加载进OD

再点Options->Debugging options,将Events中的Break on new module(DLL)打对勾,这样,每当有新的DLL被加载到notepad.exe进程,都会在该DLL的EP处暂停。如此一来,进行DLL注入时也会在该DLL的EP处暂停。

接着我们使用InjectDll.exe将myhack.dll文件注入notepad.exe进程,输入命令后回车



此时调试器将暂停

这时候od暂停的并不是myhack.dll的EP,这是因为加载myhack.dll前,需要先加载它导入的所有DLL文件。我们一直F9运行,直到在myhack.dll的EP处暂停。

双击跟进,这就是myhack.dll的EP地址,如果想跟进调试,在调试前要先取消对"break on new module(DLL)"的勾选。

使用注册表修改AppInit_DLLs实现注入

进行DLL注入的第二种方法是使用注册表,WindowsOS的注册表中默认提供了AppInit_DLLs与LoadAppInit_DLLs两个注册表项

只要将要注入DLL的路径写入AppInit_DLLs项目,并在LoadAppInit_DLLs中设置值为1,重启时,系统就会将指定的DLL注入到所有运行进程中。主要原理是User32.dll被加载到进程时,会读取AppInit_DLLs注册表项,若值为1,就调用LoadLibrary()函数加载用户DLL。所以严格来说,是将注入DLL加载到使用user32.dll的进程中。
注:Windows XP会忽略LoadAppInit_DLLs注册表项。

分析myhack2.dll的源码

// myhack2.cpp
// 主要作用是以隐藏模式运行IE,连接到指定网站

#include "windows.h"
#include "tchar.h"

#define DEF_CMD  L"c:\\\\Program Files\\\\Internet Explorer\\\\iexplore.exe" 
#define DEF_ADDR L"http://www.naver.com"
#define DEF_DST_PROC L"notepad.exe"

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)

    TCHAR szCmd[MAX_PATH]  = 0,;
    TCHAR szPath[MAX_PATH] = 0,;
    TCHAR *p = NULL;
    STARTUPINFO si = 0,;
    PROCESS_INFORMATION pi = 0,;

    si.cb = sizeof(STARTUPINFO);
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_HIDE;

    switch( fdwReason )
    
    case DLL_PROCESS_ATTACH : 
        if( !GetModuleFileName( NULL, szPath, MAX_PATH ) )
            break;

        if( !(p = _tcsrchr(szPath, \'\\\\\')) )
            break;

        if( _tcsicmp(p+1, DEF_DST_PROC) )
            break;

        wsprintf(szCmd, L"%s %s", DEF_CMD, DEF_ADDR);
        if( !CreateProcess(NULL, (LPTSTR)(LPCTSTR)szCmd, 
                            NULL, NULL, FALSE, 
                            NORMAL_PRIORITY_CLASS, 
                            NULL, NULL, &si, &pi) )
            break;

        if( pi.hProcess != NULL )
            CloseHandle(pi.hProcess);

        break;
    

    return TRUE;

将上述dll文件复制到某个位置,修改注册表项,将AppInit_DLLs项的值修改为待注入DLL的绝对路径,然后修改LoadAppInit_DLLs注册表项的值为1,重启,运行notepad.exe,就会看到DLL已经被注入。

练习示例myhack2.dll

我踩的坑+未解之谜

这里有一个坑
在众多的DLL注入方法中,使用注册表注入是最简单的一种方式。但是有一点要注意: 被注入的进程时64位,那么注入的DLL也应该是64位,32位对应32位。
注入32位进程,应该修改的注册表键为:

# 将下面注册表的键对应的值设置为要注入的 DLL的路径
HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows\\AppInit_DLLs 
# 将下面注册表的键对应的值设置为 1
HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows\\LoadAppInit_DLLs 

注入64位进程,应该修改的注册表键为:

# 将下面注册表的键对应的值设置为要注入的 DLL的路径
HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\WindowsNT\\CurrentVersion\\Windows\\AppInit_DLLs
# 将下面注册表的键对应的值设置为 1
HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\WindowsNT\\CurrentVersion\\Windows\\LoadAppInit_DLLs

按照32位的走一遍

上面说了notepad++.exe是32位,于是就按照32位的顺序走
将要注入的dll文件(myhack2.dll)复制到合适位置,我的复制到了‪
(C:\\Users\\Zer0\\Desktop\\1234\\myhack2.dll)
根据注册表目录
将下面注册表的键对应的值设置为 1
HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows\\AppInit_DLLs
将下面注册表的键对应的值设置为要注入的 DLL的路径
HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows\\LoadAppInit_DLLs

然而重启系统,使修改生效,之后,并没有找到myhack2.dll

百思不得其解
索性试一下修改64位的注册表

重启,打开notepad++.exe,用process explorer查看,还是没有被注入
(就离谱,猜测还是Windows系统问题
But!第二天我先修改32位注册表,重启,发现还不行,又修改64位注册表,重启,先打开吾爱破解od,调试notepad++.exe,看到myhack2.dll,赶紧去process explorer看看,发现出了!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!




我以为是打开od调试了的缘故,于是!我又重启了,没打开od,直接打开notepad++.exe,用process explorer查看,发现依然有myhack2.dll,表示注入成功与打开od调试没有关系!

一整个迷幻住······坐等大佬解释QAQ

AppInit_DLLs注册表键非常强大,通过它几乎可以向所有进程注入DLL文件。若被注入的DLL出现问题(Bug),则有可能导致Windows无法正常启动,所以修改该AppInit DLLs前务必彻查。

《逆向工程核心原理》学习笔记:DLL注入

目录

前言

继续学习《逆向工程核心原理》,本篇笔记是第三部分:DLL注入,主要包括三种DLL注入、DLL卸载、修改PE、代码注入等内容

一、windows消息钩取

1、钩子

钩子(Hook):截取信息时所用的手段

以键盘消息为例,常规流程如下:

  • 发生键盘输入事件时,WM_KEYDOWN消息被添加到[OS message queue]
  • OS判断哪个应用发生了事件,然后从[OS message queue]取出消息,添加到相应应用的[application message queue]
  • 应用监视自己的[application message queue],发现新的WM_KEYDOWN消息后,调用相应的时间处理程序

在此过程中,消息钩子可以截取消息,修改消息,如下图所示:

2、SetWindowsHookEx()

SetWindowsHookEx() API可以实现消息钩子,定义如下:

  • 钩子过程(hook procedure)是系统调用的回调函数
  • 安装钩子时,钩子过程需要在DLL内部,该DLL的示例句柄(instance handle)即hMod
  • 线程ID如果为0,则钩子为“全局钩子”

用SetWindowsHookEx()设置好钩子后,在某个进程中生成指定消息时,操作系统会将相关DLL文件强制注入相应进程

3、键盘消息钩取

如下图所示:

  • KeyHook.dll是个含有钩子过程的DLL文件
  • HookMain.exe是个加载KeyHook.dll,并使用SetWindowsHookEx()安装键盘钩子的程序

一个钩子HookMain.exe的源码

//HookMain.exe

#include "stdio.h"
#include "conio.h"
#include "windows.h"

#define	DEF_DLL_NAME		"KeyHook.dll"
#define	DEF_HOOKSTART		"HookStart"
#define	DEF_HOOKSTOP		"HookStop"

typedef void (*PFN_HOOKSTART)();
typedef void (*PFN_HOOKSTOP)();

void main()

	HMODULE			hDll = NULL;
	PFN_HOOKSTART	HookStart = NULL;
	PFN_HOOKSTOP	HookStop = NULL;
	char			ch = 0;

    // 加载KeyHook.dll
	hDll = LoadLibraryA(DEF_DLL_NAME);
    if( hDll == NULL )
    
        printf("LoadLibrary(%s) failed!!! [%d]", DEF_DLL_NAME, GetLastError());
        return;
    

    // 获取导出函数地址
	HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART);
	HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP);

    // 开始
	HookStart();

    // “q”退出
	printf("press 'q' to quit!\\n");
	while( _getch() != 'q' )	;

    // 结束
	HookStop();
	
    // 卸载 KeyHook.dll 
	FreeLibrary(hDll);

其中KeyHook.dll的源码

// KeyHook.dll

#include "stdio.h"
#include "windows.h"

#define DEF_PROCESS_NAME		"notepad.exe"

HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;
HWND g_hWnd = NULL;

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved)

	switch( dwReason )
	
        case DLL_PROCESS_ATTACH:
			g_hInstance = hinstDLL;
			break;

        case DLL_PROCESS_DETACH:
			break;	
	

	return TRUE;


LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)

	char szPath[MAX_PATH] = 0,;
	char *p = NULL;

	if( nCode >= 0 )
	
		// bit 31 : 0 => press, 1 => release
		if( !(lParam & 0x80000000) ) //释放键盘按键时
		
			GetModuleFileNameA(NULL, szPath, MAX_PATH);
			p = strrchr(szPath, '\\\\');

            // 比较当前进程名称,如果是 notepad.exe 则消息不会传给应用程序
			if( !_stricmp(p + 1, DEF_PROCESS_NAME) )
				return 1;
		
	

    //反之,调用 CallNextHookEx() 消息传给应用程序
	return CallNextHookEx(g_hHook, nCode, wParam, lParam);


#ifdef __cplusplus
extern "C" 
#endif
	__declspec(dllexport) void HookStart()
	
		g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
	

	__declspec(dllexport) void HookStop()
	
		if( g_hHook )
		
			UnhookWindowsHookEx(g_hHook);
			g_hHook = NULL;
		
	
#ifdef __cplusplus

#endif

调用导出函数HookStart()时,SetWindowsHookEx()会将KeyboadProc()添加到键盘钩链

4、调试练习

(1)调试HookMain.exe

OD打开


由于我们知道运行之后,会出现press 'q' to quit!字符串
所以可以 右键-Search for-All referenced text strings

可以看到地址0040104D处应该就是Main函数


在地址00401000处设置断点,然后开始调试

  • 在地址00401006处调用了LoadLibrary(KeyHook.dll)
  • 在地址0040104B处调用了KeyHook.HookStart(),跟踪如下


这是Hookstart()函数

  • 在地址100010EF可以看到 CALL SetWindowsHookExW()
  • SetWindowsHookExW() 的第二个参数lpfn是10001020

(2)调试KeyHook.dll

开启如图所示这项,有DLL装载时,会自动暂停调试

后面不多说,简单讲就是

  • OD打开notepad.exe
  • 运行HookMain.exe
  • OD跳出 Executable modules 窗口
  • 根据上一小节的地址10001020找到钩子

二、DLL注入

DLL注入:向运行中的其他进程强制插入特定的DLL文件,如下图所示

  • 原理:从外部促使目标进程调用LoadLibrary() API,从而强制调用DLL的DllMain()
  • 注入的DLL拥有目标进程内存的访问权限

一些用处

  • 修复Bug
  • 改善功能
  • 消息钩取
  • API钩取
  • 监视进程
  • 恶意代码
  • ……

注入方法

  • 创建远程线程 CreateRemoteThread() API
  • 使用注册表 AppInit_DLLs 值
  • 消息钩取 SetWindowsHookEx() API (就是上面消息钩取所用的)

下面记下前两种方法

1、CreateRemoteThread()

书中给了个例子:用InjectDll.exe把myhack.dll注入notepad.exe
两个文件源码如下:

// myhack.dll

#include "windows.h"
#include "tchar.h"

#pragma comment(lib, "urlmon.lib")

#define DEF_URL     	(L"http://www.naver.com/index.html")
#define DEF_FILE_NAME   (L"index.html")

HMODULE g_hMod = NULL;

DWORD WINAPI ThreadProc(LPVOID lParam)

    TCHAR szPath[_MAX_PATH] = 0,;

    if( !GetModuleFileName( g_hMod, szPath, MAX_PATH ) )
        return FALSE;
	
    TCHAR *p = _tcsrchr( szPath, '\\\\' );
    if( !p )
        return FALSE;
	//下载指定网站的index.html文件
    _tcscpy_s(p+1, _MAX_PATH, DEF_FILE_NAME);

    URLDownloadToFile(NULL, DEF_URL, szPath, 0, NULL); 

    return 0;


BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)

    HANDLE hThread = NULL;

    g_hMod = (HMODULE)hinstDLL;

    switch( fdwReason )
    
    case DLL_PROCESS_ATTACH :  //加载时
        OutputDebugString(L"<myhack.dll> Injection!!!"); //输出调试字符串
        hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL); //创建线程
        CloseHandle(hThread);
        break;
    

    return TRUE;

// InjectDll.exe

#include "windows.h"
#include "tchar.h"

BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) 

    TOKEN_PRIVILEGES tp;
    HANDLE hToken;
    LUID luid;

    if( !OpenProcessToken(GetCurrentProcess(),
                          TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, 
			              &hToken) )
    
        _tprintf(L"OpenProcessToken error: %u\\n", GetLastError());
        return FALSE;
    

    if( !LookupPrivilegeValue(NULL,           // lookup privilege on local system
                              lpszPrivilege,  // privilege to lookup 
                              &luid) )        // receives LUID of privilege
    
        _tprintf(L"LookupPrivilegeValue error: %u\\n", GetLastError() ); 
        return FALSE; 
    

    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    if( bEnablePrivilege )
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    else
        tp.Privileges[0].Attributes = 0;

    // Enable the privilege or disable all privileges.
    if( !AdjustTokenPrivileges(hToken, 
                               FALSE, 
                               &tp, 
                               sizeof(TOKEN_PRIVILEGES), 
                               (PTOKEN_PRIVILEGES) NULL, 
                               (PDWORD) NULL) )
     
        _tprintf(L"AdjustTokenPrivileges error: %u\\n", GetLastError() ); 
        return FALSE; 
     

    if( GetLastError() == ERROR_NOT_ALL_ASSIGNED )
    
        _tprintf(L"The token does not have the specified privilege. \\n");
        return FALSE;
     

    return TRUE;


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 获取目标进程(notepad.exe)句柄(PROCESS_ALL_ACCESS权限),然后就可以用 hProcess 控制进程.
    if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
    
        _tprintf(L"OpenProcess(%d) failed!!! [%d]\\n", dwPID, GetLastError());
        return FALSE;
    

    // #2. 在目标进程(notepad.exe) 内存中分配 szDllName 大小的内存,返回 pRemoteBuf 作为该缓冲区的地址.
    pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);

    // #3. 将 myhack.dll 路径写入刚刚分配的缓冲区.
    WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);

    // #4. 获取 LoadLibraryW() API 地址,kernel32.dll在每个进程中的加载地址相同(这个特性就是我们要利用的).
    hMod = GetModuleHandle(L"kernel32.dll");
    pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");
	
    // #5. 在 notepad.exe 中运行线程
    hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL); //CreateRemoteThread()驱使进程调用LoadLibrary(),进而加载指定的DLL文件
    WaitForSingleObject(hThread, INFINITE);	

    CloseHandle(hThread);
    CloseHandle(hProcess);

    return TRUE;


int _tmain(int argc, TCHAR *argv[])

    if( argc != 3)
    
        _tprintf(L"USAGE : %s <pid> <dll_path>\\n", argv[0]);
        return 1;
    

    // change privilege
    if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
        return 1;

    // inject dll
    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;

2、AppInit_DLLs

将要注入的DLL路径写入AppInit_DLLs,并把LoadAppInit_DLLs设置为1
重启后,指定的DLL会注入所有的进程

// myhack2.cpp

#include "windows.h"
#include "tchar.h"

#define DEF_CMD  L"c:\\\\Program Files\\\\Internet Explorer\\\\iexplore.exe" 
#define DEF_ADDR L"http://www.naver.com"
#define DEF_DST_PROC L"notepad.exe"

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)

    TCHAR szCmd[MAX_PATH]  = 0,;
    TCHAR szPath[MAX_PATH] = 0,;
    TCHAR *p = NULL;
    STARTUPINFO si = 0,;
    PROCESS_INFORMATION pi = 0,;

    si.cb = sizeof(STARTUPINFO);
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_HIDE;

    switch( fdwReason )
    
    case DLL_PROCESS_ATTACH : 
        if( !GetModuleFileName( NULL, szPath, MAX_PATH ) )
            break;
   
        if( !(p = _tcsrchr(szPath, '\\\\')) )
            break;

        if( _tcsicmp(p+1, DEF_DST_PROC) )
            break;

        wsprintf(szCmd, L"%s %s", DEF_CMD, DEF_ADDR);
        if( !CreateProcess(NULL, (LPTSTR)(LPCTSTR)szCmd, 
                            NULL, NULL, FALSE, 
                            NORMAL_PRIORITY_CLASS, 
                            NULL, NULL, &si, &pi) )
            break;

        if( pi.hProcess != NULL )
            CloseHandle(pi.hProcess);

        break;
    
   
    return TRUE;


三、DLL卸载

DLL卸载(DLL Ejection):将强制插入进程的DLL弹出的技术

原理:驱使目标进程调用FreeLibrary() API

例子:EjectDll.exe卸载上面加载到notepad.exe的myhack.dll,代码如下:

// EjectDll.exe

#include "windows.h"
#include "tlhelp32.h"
#include "tchar.h"

#define DEF_PROC_NAME	(L"notepad.exe")
#define DEF_DLL_NAME	(L"myhack.dll")

DWORD FindProcessID(LPCTSTR szProcessName)

    DWORD dwPID = 0xFFFFFFFF;
    HANDLE hSnapShot = INVALID_HANDLE_VALUE;
    PROCESSENTRY32 pe;

    // Get the snapshot of the system
    pe.dwSize = sizeof( PROCESSENTRY32 );
    hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL );

    // find process
    Process32First(hSnapShot, &pe);
    do
    
        if(!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))
        
            dwPID = pe.th32ProcessID;
            break;
        
    
    while(Process32Next(hSnapShot, &pe));

    CloseHandle(hSnapShot);

    return dwPID;


BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) 

    TOKEN_PRIVILEGES tp;
    HANDLE hToken;
    LUID luid;

    if( !OpenProcessToken(GetCurrentProcess(),
                          TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, 
			              &hToken) )
    
        _tprintf(L"OpenProcessToken error: %u\\n", GetLastError());
        return FALSE;
    

    if( !LookupPrivilegeValue(NULL,           // lookup privilege on local system
                              lpszPrivilege,  // privilege to lookup 
                              &luid) )        // receives LUID of privilege
    
        _tprintf(L"LookupPrivilegeValue error: %u\\n", GetLastError() ); 
        return FALSE; 
    

    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    if( bEnablePrivilege )
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    else
        tp.Privileges[0].Attributes = 0;

    // Enable the privilege or disable all privileges.
    if( !AdjustTokenPrivileges(hToken, 
                               FALSE, 
                               &tp, 
                               sizeof(TOKEN_PRIVILEGES), 
                               (PTOKEN_PRIVILEGES) NULL, 
                               (PDWORD) NULL) )
     
        _tprintf《逆向工程核心原理》学习笔记:DLL注入

《逆向工程核心原理》学习笔记:DLL注入

《逆向工程核心原理》学习笔记:API钩取

《逆向工程核心原理》学习笔记:API钩取

《逆向工程核心原理》学习笔记:API钩取

《逆向工程核心原理》学习笔记:API钩取