dll注入技术
什么是 dll 注入技术
在一个本不需要加载此 dll 的进程中强行使其加载 dll 文件,这就叫 dll 注入技术
在一个进程中注入自己的 dll,相当于在对方的进程中加入自己的代码,可以修改对方的程序,有游戏辅助、输入法、对软件行为的拦截等
dll 注入方式有哪些
- 注册表注入
- ComRes 注入
- APC 注入
- 消息钩子注入
- 远线程注入
- 依赖可信进程注入
- 劫持进程创建注入
- 输入法注入
掌握基本的 dll 注入技术
远程线程注入
若想加载一个 dll,可以自己调用
LoadLibrary
,若想让目标进程加载一个 dll,就要想办法让它调用LoadLibrary
,LoadLibrary
有一个参数,远程进程中创建线程的函数CreateRemoteThread
的参数_In_ LPTHREAD_START_ROUTINE lpStartAddress
的线程回调函数也有一个参数,故可在目标进程中创建一个线程,然后将LoadLibrary
函数的地址设置为线程的回调函数,再将一个 dll 文件的路径作为参数传递给线程回调函数1
2
3
4typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(
LPVOID lpThreadParameter
);
typedef PTHREAD_START_ROUTINE LPTHREAD_START_ROUTINE;1
2
3
4
5
6
7
8
9HMODULE WINAPI LoadLibrary(
_In_ LPCTSTR lpFileName
);
//lpFileName [in]
//模块的名称。这可以是库模块(.dll文件)或可执行模块(.exe文件)。指定的名称是模块的文件名,与模块定义(.def)文件中的LIBRARY关键字所指定的与库模块本身中存储的名称无关。
//如果字符串指定完整路径,则该函数仅搜索该模块的路径。
//如果字符串指定一个没有路径的模块名称或者相对路径,则该函数使用标准搜索策略来查找模块;
//如果该功能找不到该模块,则该功能失败。指定路径时,一定要使用反斜杠(\),而不是正斜杠(/)。
//如果字符串指定了没有路径的模块名称,并且省略了文件扩展名,则函数会将缺省库扩展名.dll附加到模块名称。要防止函数将.dll附加到模块名称,请在模块名称字符串中包含尾随点字符(.)。示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//远程线程注入
void Inject(const char* pDllPath, DWORD dwPid) {
//打开目标进程,得到句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (!hProcess) {
printf("句柄打开失败\n");
return;
}
//在目标进程中申请一块空间,能够存放dll文件的全路径
LPVOID lpBuf = VirtualAllocEx(hProcess, NULL, strlen(pDllPath),
MEM_COMMIT, PAGE_READWRITE);
if (!lpBuf) {
printf("内存申请失败\n");
CloseHandle(hProcess);
return;
}
//将dll文件的路径写入到目标进程申请的空间
DWORD dwWrite;
WriteProcessMemory(hProcess, lpBuf, pDllPath, strlen(pDllPath), &dwWrite);
//在目标进程中创建远程线程使其能够执行LoadLibrary
HANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL,
(LPTHREAD_START_ROUTINE)LoadLibraryA, lpBuf, NULL, NULL);
//等待线程结束,释放空间
WaitForSingleObject(hThread, -1);
VirtualFreeEx(hProcess, lpBuf, 0, MEM_RELEASE);
CloseHandle(hProcess);
CloseHandle(hThread);
}
int main()
{
DWORD dwPid;
printf("输入PID:");
scanf_s("%d", &dwPid);
Inject(DLL_PATH, dwPid);
return 0;
}创建一个 dll 程序,简单地在
dllmain.cpp
函数中添加一个函数MessageBox
1
2
3
4
5
6
7
8
9
10switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBox(0, L"99999999", 0, 0);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}代码注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35//注入代码
void InjectCode(const char* pCode, DWORD dwSize, DWORD dwPid) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (!hProcess) {
printf("句柄打开失败\n");
return;
}
LPVOID lpBuf = VirtualAllocEx(hProcess, NULL, strlen(pCode),
MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!lpBuf) {
printf("内存申请失败\n");
CloseHandle(hProcess);
return;
}
DWORD dwWrite;
WriteProcessMemory(hProcess, lpBuf, pCode, dwSize, &dwWrite);
HANDLE hTread = CreateRemoteThread(hProcess, NULL, NULL,
(LPTHREAD_START_ROUTINE)lpBuf, NULL, NULL, NULL);
WaitForSingleObject(hTread, -1);
VirtualFreeEx(hProcess, lpBuf, 0, MEM_RELEASE);
CloseHandle(hProcess);
CloseHandle(hTread);
}
int main()
{
DWORD dwAddr = (DWORD)MessageBoxW;
printf("MessageBoxW:0x%08X\n", (DWORD)MessageBoxW);//获取MessageBoxW的地址
char pCode[] = "\x6A\x00\x6A\x00\x6A\x00\x6A\x00\xB8\x99\x99\x99\x99\xFF\xD0";//弹出一个MessageBoxW对话框的机器码
*(int*)(pCode + 9) = dwAddr;//将正确的机器码写到pCode
DWORD dwPid;
printf("输入PID:");
scanf_s("%d", &dwPid);
InjectCode(pCode, sizeof(pCode), dwPid);
return 0;
}
- 64位注入:32位的 dll 正常情况下只能注入到32位程序中,64位的 dll 正常情况下也只能注入到64位程序中
Hook
什么是 Hook
Hook 技术主要指拦截程序原有的信息,数据,代码,使得能够对拦截的信息数据做处理,然后交给原有的程序去使用,截获到程序的关键信息,到达的效果就是能够修改程序的部分功能,
Hook 分类
Window 系统下有2类 Hook:
Windows 消息 Hook,Windows 提供的能够让程序员截获到的所有窗口程序消息的机制
消息 Hook 也是一种 dll 注入手段
自定义 Hook,非常普遍的 Hook 方式,也是通常意义所说的 Hook
- 修改程序代码,使得能够执行到 Hook 者提供的代码中,inline-Hook
- 修改存储函数地址的变量,当程序从变量中获取函数地址并调用时,就会调用到 Hook 者提供的代码,IAT-Hook
Hook 实现的原理
Windows 消息钩子
SetWindowsHookEx
函数能够实现的功能是截获系统中所有窗口程序的消息或某个线程的窗口消息截获到了消息就执行自己的代码,自己的代码需放置在一个 dll 中,消息钩子设置成功后,会将 dll 注入到目标进程中,从而使自己的回调函数能够在对方的进程中执行
窗口程序的消息是被某一个线程获取到的,哪一个线程创建了窗口,哪一个线程就能获得此窗口的消息,此线程在创建完窗口后就变成了 GUI 线程
1
2
3
4
5
6HHOOK SetWindowsHookExA(
int idHook, //要截获的是哪种类型的消息
HOOKPROC lpfn, //截获到消息之后,调用的回调函数
HINSTANCE hmod, //回调函数所在的模块,这个模块需要是一个dll。
DWORD dwThreadId //填0,截获系统中所有的窗口的消息,填线程ID,那就仅截获此线程的窗口消息
);1
2
3
4//当钩子使用完毕之后,卸载钩子
BOOL WINAPI UnhookWindowsHookEx(
_In_ HHOOK hhk //填充返回的句柄
);在钩子的消息拦截函数最后应调用
CallNextHookEx
函数,因为程序可能会有多个钩子,新添加的在最上方,为了不影响其他钩子的功能,需要调用此函数1
2
3
4
5
6WINUSERAPI LRESULT WINAPI CallNextHookEx(
_In_opt_ HHOOK hhk, //钩子的句柄
_In_ int nCode, //后面3个参数是回调函数的参数
_In_ WPARAM wParam,
_In_ LPARAM lParam
);在 VS 中添加动态链接库,添加以下函数,用来截获键盘消息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41// 消息钩子.cpp : 定义 DLL 应用程序的导出函数。
HHOOK g_hook;
LRESULT CALLBACK KeyboardProc(_In_ int code,//消息类型
_In_ WPARAM wParam, //虚拟码
_In_ LPARAM lParam //按键消息
) {
//判断是否是wParam与lParam都有键盘消息,是的话执行打印操作
if (code == HC_ACTION) {
//将256个虚拟键的状态拷贝到指定的缓冲区中,如果成功则继续
BYTE KeyState[256] = {};
if (GetKeyboardState(KeyState)) {
//得到第16-23位键盘虚拟码
LONG KeyInfo = lParam;
UINT KeyCode = (KeyInfo >> 16) & 0x00ff;
WCHAR wKeyCode = 0;
ToAscii((UINT)wParam, KeyCode, KeyState, (LPWORD)&wKeyCode, 0);
//打印
CHAR szInfo[512] = {};
sprintf_s(szInfo, _countof(szInfo), "Hook_%c", wKeyCode);
OutputDebugStringA(szInfo);
return 0;
}
}
return CallNextHookEx(g_hook, code, wParam, lParam);
}
//开启Hook函数
void OnHook() {
HMODULE hModule = GetModuleHandle(L"消息钩子.dll");
g_hook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, hModule, 0);
}
//关闭Hook函数
void UnHook() {
if (!g_hook)
UnhookWindowsHookEx(g_hook);
}在另一个程序中导出
1
2
3
4
5
6
7
8
9
10
int main()
{
OnHook();
Sleep(50000);
UnHook();
return 0;
}运行程序,在Notepad++中随便输入什么,然后打开OD,附加Notepad++,在模块中可以看到下图 ,说明注入成功
自定义 Hook
内联钩子,inline-Hook
任何位置都可以修改为 jmp,使其执行到此处时,能够跳转到我们自己的代码中去执行
被修改的指令是否是有用的,若有用,就需要在自己的代码中将有用的指令写一遍,使其在代码中能够执行,jmp 指令一般5个字节,故我们选取的指令最好也是5个字节,若不是,那么会发生指令截断,跳转回来时,需要考虑跳转到完整的指令的指令后去执行程序本身的代码
jmp 指令 OPCODE 的操作数 = 要跳转的目标地址 - Hook 点所在的地址 - 5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
char g_OldCode[5];
char g_NewCode[5] = { 0xE9 };//jmp
void OnHook();
void UnHook();
int WINAPI MyMessageBoxW(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType) {
lpText = L"Hook\n";
lpCaption = L"000";
//先卸载
UnHook();
//调用MessageBoxW
int nRet = MessageBoxW(hWnd, lpText, lpCaption, uType);
//再次挂上钩子
OnHook();
return nRet;
}
void OnHook() {
//保存原函数被修改的代码
memcpy(g_OldCode, MessageBoxW, 5);
//计算偏移量,即jmp指令的OPCODE操作数
DWORD dwOffset = (DWORD)MyMessageBoxW - (DWORD)MessageBoxW - 5;
//将MyMessageBoxW写入指令
memcpy(g_NewCode + 1, &dwOffset, 4);
//修改内存属性(代码段无可写属性)
DWORD dwOld;//用来保存原来的保护属性
VirtualProtect(MessageBoxW, 5, PAGE_EXECUTE_READWRITE, &dwOld);
memcpy(MessageBoxW, g_NewCode, 5);
VirtualProtect(MessageBoxW, 5, dwOld, &dwOld);//改回原来的属性
}
void UnHook() {
DWORD dwOld;
VirtualProtect(MessageBoxW, 5, PAGE_EXECUTE_READWRITE, &dwOld);
memcpy(MessageBoxW, g_OldCode, 5);//将原来的代码写回去
VirtualProtect(MessageBoxW, 5, dwOld, &dwOld);
}
int main()
{
MessageBoxW(0, 0, 0, 0);
OnHook();
MessageBoxW(0, 0, 0, 0);
return 0;
}还可将函数写到 dll 中并远程线程注入到其他程序
修改存储函数地址变量的钩子,在 dll 函数中写入以下代码,并远程线程注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89// dllmain.cpp : 定义 DLL 应用程序的入口点。
DWORD g_IatAddr;
DWORD g_FunOldAddr;
typedef int(WINAPI* FP_MessageBoxW)(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType);
int WINAPI MyMessageBoxW(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType) {
lpText = L"Hook\n";
lpCaption = L"000";
FP_MessageBoxW FpMsg = (FP_MessageBoxW)g_FunOldAddr;
return FpMsg(hWnd, lpText, lpCaption, uType);
}
void OnHook(const char* pFunName, const char* pDllName) {
//遍历导入表
HMODULE hMod = GetModuleHandle(NULL);
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)hMod;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)hMod);
DWORD dwImportRVA = pNt->OptionalHeader.DataDirectory[1].VirtualAddress;
PIMAGE_IMPORT_DESCRIPTOR pImport =
(PIMAGE_IMPORT_DESCRIPTOR)
(dwImportRVA + (DWORD)hMod);
while (pImport->Name) {
char* pDll = (char*)(pImport->Name + (DWORD)hMod);
if (!_stricmp(pDll, pDllName)) {//_stricmp不区分大小写
//遍历INT
IMAGE_THUNK_DATA* pInt = (IMAGE_THUNK_DATA*)
(pImport->OriginalFirstThunk + (DWORD)hMod);
IMAGE_THUNK_DATA* pIat = (IMAGE_THUNK_DATA*)
(pImport->FirstThunk + (DWORD)hMod);
while (pInt->u1.AddressOfData)
{
if (!IMAGE_SNAP_BY_ORDINAL(pInt->u1.AddressOfData))
{
PIMAGE_IMPORT_BY_NAME pName = (PIMAGE_IMPORT_BY_NAME)(pInt->u1.AddressOfData + (DWORD)hMod);
if (!_stricmp(pName->Name, pFunName))
{
//函数地址
g_FunOldAddr = pIat->u1.Function;
g_IatAddr = (DWORD)pIat;
//修改地址
//修改内存属性
DWORD dwOld;
VirtualProtect(pIat, 4, PAGE_READWRITE, &dwOld);
*(DWORD*)pIat = (DWORD)MyMessageBoxW;
VirtualProtect(pIat, 4, dwOld, &dwOld);
return;
}
}
//下一个函数
pInt++;
pIat++;
}
}
//下一个模块
pImport++;
}
}
void UnHook() {
DWORD dwOld;
VirtualProtect((LPVOID)g_IatAddr, 4, PAGE_READWRITE, &dwOld);
*(DWORD*)g_IatAddr = g_FunOldAddr;
VirtualProtect((LPVOID)g_IatAddr, 4, dwOld, &dwOld);
}
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
OnHook("MessageBoxW", "user32.dll");
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}