逆向工程核心原理——第二十三章
第二十三章 DLL注入
DLL注入,是指向运行中的其他进程强制插入特定的DLL文件。常用于渗透其他进程,DLL注入可以实现API钩取、改写程序、修复BUG等。
可以看到,notepad.exe进程本来是会加载myhack.dll的,但由于我们强制注入了myhack.dll,所以现在他会加载myhack.dll。
DLL注入实验
接下来书本上进行了一个DLL注入的实例:
首先我们准备一个notepad文件(记事本),这将是我们进行注入的文件:
和一个DLL文件(此DLL的作用是下载一个.html)已经将DLL注入进notepad的EXE文件:
首先我们打开notepad文件,利用process explorer查看notepad文件的PID:
打开cmd,输入指令:'''InjectDLL.exe 1580 c:\work\myhack.dll'''
cmd显示我们注册成功,然后我们在process explorer上查看dll是否被注入成功,在View菜单中,选择Show Lower Pane与Lower Pane Views - DLLs项。就可以看到:
可以看到myhack.dll已经被成功加载进去了。然后我们打开dll所在文件夹,查看url是否被成功下载。
这样,就说明我们DLL注入成功了。
接下来我们来看看dll文件和exe文件的代码:
// 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;
_tcscpy_s(p + 1, _MAX_PATH, DEF_FILE_NAME); //参数准备
URLDownloadToFile(NULL, DEF_URL, szPath, 0, NULL); //调用函数进行URL下载
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!!!");
//创建远程线程进行download
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
// 需要注意,切记随手关闭句柄,保持好习惯
CloseHandle(hThread);
break;
}
return TRUE;
}
我是使用vs2019进行编译的,在创建新项目的时候,我选择的是“动态链接库(DLL)”
vs不知道从哪个本开始,会加入一个预编译头,例如这里的“pch.h”的头文件:
这个头文件,会存放一些使用者预设的一些代码。
这里如果我们删除这个头文件,则会报错提示你加入头文件:
但如果加入了这个头文件,则会报错提示很多变量找不到标识符:
解决方案就是在项目——>属性中进行更改:
在属性的预编译头中选择不使用预编译头,就可以了
然后是exe文件:
// InjectDll.cpp
#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. 使用OpenProcess函数获取目标进程句柄(PROCESS_ALL_ACCESS权限)
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
{
_tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
return FALSE;
}
// #2. 使用VirtualAllocEx函数在目标进程中分配内存,大小为szDllName
// VirtualAllocEx函数返回的是hProcess指向的目标进程的分配所得缓冲区的内存地址
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
// #3. 将myhack.dll路径 ("c:\\myhack.dll")写入目标进程中分配到的内存
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);
// #4. 获取LoadLibraryA() API的地址
// 这里主要利用来了kernel32.dll文件在每个进程中的加载地址都相同这一特点,所以不管是获取加载到
// InjectDll.exe还是notepad.exe进程的kernel32.dll中的LoadLibraryW函数的地址都是一样的。这里的加载地
// 址相同指的是在同一次系统运行中,如果再次启动系统kernel32.dll的加载地址会变,但是每个进程的
// kernerl32.dll的加载地址还是一样的。
hMod = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");
// #5. 在目标进程notepad.exe中运行远程线程
// pThreadProc = notepad.exe进程内存中的LoadLibraryW()地址
// pRemoteBuf = notepad.exe进程内存中待加载注入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)
{
_tprintf(L"USAGE : %s <pid> <dll_path>\n", argv[0]);
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;
}
这一部分代码,我在编译时并没有遇到问题,编译平台也是VS2019
接下来是利用OD调试myhack.dll:
首先我们打开一个未注入myhack.dll的notepad:
然后利用OD的attach功能,将notepad(附加)加载进OD(注意,OD只能加载32的程序,我这里是64位系统,所以使用Xdbg进行调试)。
notepad加载进来后,会首先暂停,这里我们在选项中进行设置:
打开DLL加载,这样程序在加载DLL时,OD(Xdbg,虽然使用的是Xdbg,但在后面我都会用OD记录)就会自动断下来。
接着我们打开InjectDLL.exe,将myhack.dll加载进notepad,回车的瞬间,OD断了下来。
这里OD断下来的,不是myhack.dll,因为在加载myhack.dll之前,会先加载其他的dll,所以我们只需要F9运行到myhack.dll就行: