Re——Hook

逆向分析之”花”。windows的Hook是程序设计中最为灵活多变的技巧之一。Hook有两种含义:1、系统提供的消息Hook机制;2、自定义的Hook编程技巧,

Hook(钩子)是WINDOWS提供的一种消息处理机制平台,是指在程序正常运
行中接受信息之前预先启动的函数,用来检查和修改传给该程序的信息,(钩子)实
际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,
在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。

钩取方式

如图,一般有三种API钩取方式:

下面都以Notepad.exe的WriteFile() API为例钩取

方式1——动态-进程内存

直接在API加载时调用钩取
动态-进程内存-代码-调试技术

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#include "windows.h"
#include "stdio.h"
LPVOID g_pfWriteFile = NULL; //writefile函数的地址
CREATE_PROCESS_DEBUG_INFO g_cpdi; //存放调试信息
BYTE g_chINT3 = 0xCC, g_chOrgByte = 0; //0xCC是int 3的机器码
//当创建进程调试事件时运行此
BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde)
{
// WriteFile() API Address 获取地址
g_pfWriteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");
// API Hook - WriteFile()
//不知道???
memcpy(&g_cpdi, &pde->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO));
//这里读出 WriteFile()的首地址并备份在g_chOrgByte,后面脱钩会用到
ReadProcessMemory(g_cpdi.hProcess, g_pfWriteFile,&g_chOrgByte, sizeof(BYTE), NULL);
//将0xCC(INT 3) 写入WriteFile() API Address的第一个字节
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,&g_chINT3, sizeof(BYTE), NULL);
return TRUE;
}
//当异常发生时,也就是 WriteFile()函数执行结束
BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde)
{
CONTEXT ctx; //记录hook的上下文
PBYTE lpBuffer = NULL; //用于临时缓冲区
DWORD dwNumOfBytesToWrite, dwAddrOfBuffer, i;
PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;
// BreakPoint exception (INT 3) 当断点是3时,这样就一定会先执行OnCreateProcessDebugEvent
if (EXCEPTION_BREAKPOINT == per->ExceptionCode)
{
// BP Address == WriteFile() Address
if (g_pfWriteFile == per->ExceptionAddress)
{
// #1. Unhook 恢复 WriteFile()的首地址的值,也就是改为刚才备份g_chOrgByte
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,&g_chOrgByte, sizeof(BYTE), NULL);
// #2. Thread Context 获取线程的上下文
ctx.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(g_cpdi.hThread, &ctx);
// #3. WriteFile() 函数参数param 2, 3 的值
// param 2 : ESP + 0x8 (这里是32位)
// param 3 : ESP + 0xC
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8),&dwAddrOfBuffer, sizeof(DWORD), NULL);
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC),&dwNumOfBytesToWrite, sizeof(DWORD), NULL);
// #4. 分配临时缓冲区
lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite + 1);
memset(lpBuffer, 0, dwNumOfBytesToWrite + 1);
// #5. WriteFile() 的输入 复制 缓冲区
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,lpBuffer, dwNumOfBytesToWrite, NULL);
printf("\n### original string ###\n%s\n", lpBuffer);
// #6. 将小写转换成大写
for (i = 0; i < dwNumOfBytesToWrite; i++)
{
if (0x61 <= lpBuffer[i] && lpBuffer[i] <= 0x7A)
lpBuffer[i] -= 0x20;
}
printf("\n### converted string ###\n%s\n", lpBuffer);
// #7. 将变换后缓冲区 的数据写入WriteFile()函数的缓冲区
WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,lpBuffer, dwNumOfBytesToWrite, NULL);
// #8. 释放缓冲区
free(lpBuffer);
// #9. 将上下文的EIP更改为 WriteFile()的首地址
ctx.Eip = (DWORD)g_pfWriteFile;
SetThreadContext(g_cpdi.hThread, &ctx);
// #10. 运行被调试的进程
ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
//释放当前的时间片,即放弃当前线程执行的CPU的时间片
Sleep(0);
// #11. API Hook
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,&g_chINT3, sizeof(BYTE), NULL);
return TRUE;
}
}
return FALSE;
}
void DebugLoop()
{
DEBUG_EVENT de;
DWORD dwContinueStatus;
// Waiting Debuggee event 等待调试事件的发生 ,循环调试
while (WaitForDebugEvent(&de, INFINITE))
{
dwContinueStatus = DBG_CONTINUE;
// Debuggee attach 属于SEH异常一种
if (CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode)
{
OnCreateProcessDebugEvent(&de); //跳到下一个函数
}
// 异常事件 属于SEH异常一种
else if (EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode)
{
//跳到下一个函数
if (OnExceptionDebugEvent(&de))
continue;
}
// Debuggee exit,被调试事件终止 属于SEH异常一种
else if (EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode)
{
break;
}
// Debuggee continue.再次调试事件
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
}
}
int main(int argc, char* argv[])
{
DWORD dwPID;
if (argc != 2)
{
printf("\nUSAGE : hook.exe <pid>\n");
return 1;
}
// Attach Process 开始调试
dwPID = atoi(argv[1]);
if (!DebugActiveProcess(dwPID))
{
printf("DebugActiveProcess(%d) failed!!!\n"
"Error Code = %d\n", dwPID, GetLastError());
return 1;
}
//循环调试
DebugLoop(); //跳到下一个函数
return 0;
}

若在生成exe文件是出现这个错误“GetModuleHandleW”: 不能将参数 1 从“const char [10]”转换为“LPCWSTR” 解决方法:
项目菜单->属性->配置属性->常规->项目默认值->字符集改为未设置

生成hook.exe 额,在调试的时候,运行hook.exe pid后发现nopad卡住啦,尴尬,怀疑可能是要在32位的系统下,然后又在32位的系统下运行,发现并不是系统问题。又怀疑是不是编译的时候开啦ALSR啦,关掉ALSR,还是不行,得看看代码啦。原来是代码错啦,这。。。。

运行结果:

总结: 这是代码动态注入,通过附件进程,触发异常,通过异常获取WriteFile()函数的信息,在WriteFile()函数首地址下断点,然后截取控制流程,再次触发异常,恢复WriteFile()函数首地址,并且获取线程的上下文,然后将WriteFile()函数读取的缓冲区读出,然后转换成目标数据后写入WriteFile()函数的缓冲区,恢复线程上下文,继续运行就可以hook成功啦。

方式2——IAT钩取

IAT的hook原理:在保持运行代码不变的前提下,将IAT中保存的API起始地址变为用户的起始地址。如图:
钩取前

钩取后

下面以计算器为例,讲解IAT的dll的hook.
hookiat.dll的hookiat.cpp

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
90
91
92
93
#include "stdio.h"
#include "wchar.h"
#include "windows.h"
typedef BOOL (WINAPI *PFSETWINDOWTEXTW)(HWND hWnd, LPWSTR lpString);
FARPROC g_pOrgFunc = NULL;
BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)
{
wchar_t* pNum = L"零一二三四五六七八九";
wchar_t temp[2] = {0,};
int i = 0, nLen = 0, nIndex = 0;
nLen = wcslen(lpString);
for(i = 0; i < nLen; i++)
{
// 将阿拉伯数字转化成中文
// lpString缓冲区 中文的字宽是的2字节
if( L'0' <= lpString[i] && lpString[i] <= L'9' )
{
temp[0] = lpString[i];
nIndex = _wtoi(temp);
lpString[i] = pNum[nIndex];
}
}
// 调用user32!SetWindowTextW() API
// 修改lpString缓冲区的数据
return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);
}
// hook_iat
BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)
{
HMODULE hMod; //hMod=ImageBase 基址
LPCSTR szLibName;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
PIMAGE_THUNK_DATA pThunk;
DWORD dwOldProtect, dwRVA;
PBYTE pAddr;
hMod = GetModuleHandle(NULL); //hMod= ImageBase of calc.exe 基址
pAddr = (PBYTE)hMod;
pAddr += *((DWORD*)&pAddr[0x3C]); // pAddr = VA to PE signature (IMAGE_NT_HEADERS)
dwRVA = *((DWORD*)&pAddr[0x80]); // dwRVA = RVA to IMAGE_IMPORT_DESCRIPTOR Table
pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA); // pImportDesc = VA to IMAGE_IMPORT_DESCRIPTOR Table
//上面几行代码首先从ImageBase开始,经由PE签名找到IDT,plmportDesc中存储着IID(IMAGE_IMPORT_DESCRIPTOR)结构体的起始地址,IDT是由IID组成的数组,要找的IAT,先要查找到这个位置,而在本例中,pImportDesc=0x1012B80,可以用PEview看见。但是这里为什么是80??? ,而我们要找user32.dll,它在下面,所以遍历查找
for( ; pImportDesc->Name; pImportDesc++ )
{
szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name); //获取user32.dll的库名
if( !_stricmp(szLibName, szDllName) ) // 查找要的user32.dll
{
//得到user32.dll库的IAT地址 ,接着在IAT查找需要的函数user32!SetWindowTextW()
pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod + pImportDesc->FirstThunk);
for( ; pThunk->u1.Function; pThunk++ )
{
if( pThunk->u1.Function == (DWORD)pfnOrg ) //如果找到user32!SetWindowTextW()
{
VirtualProtect((LPVOID)&pThunk->u1.Function, 4, PAGE_EXECUTE_READWRITE, &dwOldProtect);
pThunk->u1.Function = (DWORD)pfnNew;
VirtualProtect((LPVOID)&pThunk->u1.Function, 4, dwOldProtect, &dwOldProtect);
return TRUE;
}
}
}
}
return FALSE;
}
//dll主函数
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch( fdwReason )
{
case DLL_PROCESS_ATTACH :
//保存API原地址
g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"), "SetWindowTextW");
// # hook 钩取
// hookiat!MySetWindowText()钩取user32!SetWindowTextW()
hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);
break;
case DLL_PROCESS_DETACH :
// # unhook 脱钩
// calc.exe 的IAT 恢复
hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc);
break;
}
return TRUE;
}

我们再看看hookd.exe的cpp

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#include "stdio.h"
#include "windows.h"
#include "tlhelp32.h"
#include "winbase.h"
#include "tchar.h"
//使用方式
void usage()
{
printf("\nInjectDll.exe by ReverseCore\n"
"- blog : http://www.reversecore.com\n"
"- email : reversecore@gmail.com\n\n"
"- USAGE : hookdiat.exe <i|e> <PID> <dll_path>\n\n");
}
//钩取hook
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllName)
{
HANDLE hProcess, hThread;
LPVOID pRemoteBuf;
DWORD dwBufSize = (DWORD)(_tcslen(szDllName) + 1) * sizeof(TCHAR);
LPTHREAD_START_ROUTINE pThreadProc;
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
{
DWORD dwErr = GetLastError();
return FALSE;
}
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllName, dwBufSize, NULL);
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
//脱钩
BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName)
{
BOOL bMore = FALSE, bFound = FALSE;
HANDLE hSnapshot, hProcess, hThread;
MODULEENTRY32 me = { sizeof(me) };
LPTHREAD_START_ROUTINE pThreadProc;
if (INVALID_HANDLE_VALUE == (hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID)))
return FALSE;
bMore = Module32First(hSnapshot, &me);
for (; bMore; bMore = Module32Next(hSnapshot, &me))
{
if (!_tcsicmp(me.szModule, szDllName) || !_tcsicmp(me.szExePath, szDllName))
{
bFound = TRUE;
break;
}
}
if (!bFound)
{
CloseHandle(hSnapshot);
return FALSE;
}
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
{
CloseHandle(hSnapshot);
return FALSE;
}
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "FreeLibrary");
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
CloseHandle(hSnapshot);
return TRUE;
}
DWORD _EnableNTPrivilege(LPCTSTR szPrivilege, DWORD dwState)
{
DWORD dwRtn = 0;
HANDLE hToken;
if (OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
LUID luid;
if (LookupPrivilegeValue(NULL, szPrivilege, &luid))
{
BYTE t1[sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES)];
BYTE t2[sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES)];
DWORD cbTP = sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES);
PTOKEN_PRIVILEGES pTP = (PTOKEN_PRIVILEGES)t1;
PTOKEN_PRIVILEGES pPrevTP = (PTOKEN_PRIVILEGES)t2;
pTP->PrivilegeCount = 1;
pTP->Privileges[0].Luid = luid;
pTP->Privileges[0].Attributes = dwState;
if (AdjustTokenPrivileges(hToken, FALSE, pTP, cbTP, pPrevTP, &cbTP))
dwRtn = pPrevTP->Privileges[0].Attributes;
}
CloseHandle(hToken);
}
return dwRtn;
}
//主函数
int _tmain(int argc, TCHAR* argv[])
{
if (argc != 4) //参数是不是4个
{
usage();
return 1;
}
// adjust privilege
_EnableNTPrivilege(SE_DEBUG_NAME, SE_PRIVILEGE_ENABLED);
if (!_tcsicmp(argv[1], L"i")) //判断是钩取
InjectDll((DWORD)_tstoi(argv[2]), argv[3]);
else if (!_tcsicmp(argv[1], L"e")) //判断是脱钩
EjectDll((DWORD)_tstoi(argv[2]), argv[3]);
return 0;
}

运行结果:
钩取

脱钩

个人总结:通过PE文件的结构,先获取线程基址,从ImageBase开始,经由PE签名找到IDT:
ImageBase->IDT->IAT(dll)->函数

方式3——进程隐藏

进程隐藏就是将要隐藏的进程藏在其他的进程中,实现进程隐藏的关键不是进程本身,而是其他进程。其中用户模式下最常用的是ntdll.ZwQuerySystemInformation() API钩取技术。

假如我们要隐藏test.exe进程,那么就要钩取ProcExp.exe(进程查看器或taskmgr.exe任务管理器)的ntdll.ZwQuerySystemInformation() API,

2个问题

1、钩取的进程数
如果进程查看器和任务管理器多开几个,那么进程钩取一个,那是不可以的,所以要钩取系统中运行的所有进程。
2、新创进程
如果当钩取了系统中运行的所有进程,这时又新建一个ProcExp.exe,而这个进程又没有被钩取。

解决

对于上面2个问题,我们隐藏test.exe进程时需要钩取系统中运行的所有进程的ntdll.ZwQuerySystemInformation() API,并且对后面将要启动的所有进程也进行相同的操作(当然这是全自动的啦),这叫全局钩取。

来源

逆向工程核心原理

最后,文学还是很重要的,借助诗圣两句提高文学素养

绝代有佳人,幽居在空谷。
但见新人笑,那闻旧人哭。
——佳人

Donate
-------------本文结束感谢您的阅读-------------