逆向工程核心原理--HOOK

记事本WriteFile()API钩取

通过HOOK地址,修改地址所对应的内容完成修改。

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
#include<windows.h>
#include "stdio.h"

//WriteFile函数的地址
LPVOID g_pfWriteFile = NULL;
//Debug进程信息
CREATE_PROCESS_DEBUG_INFO g_cpdi;
BYTE g_chINT3 = 0xCC, g_ch0rgByte = 0;
BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde)
{
//获取WriteFile函数的地址,这里获取的是调试进程Write函数的地址
//但是对于Windows OS的系统DLL来说,DLL加载地址为同一个
g_pfWriteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");
//将被调试进程的信息拷贝到g_cpdi中
memcpy(&g_cpdi, &pde->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO));
//读取被调试进程中WriteFile函数所在地址的指令,只读取一个字节
ReadProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_ch0rgByte, sizeof(BYTE), NULL);
//将被调试进程中WriteFile函数所在地址的指令的首个字节修改为INT3指令,实现断点
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_chINT3, sizeof(BYTE), NULL);
return TRUE;

}
BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde)
{
//包含特定于处理器的寄存器数据
CONTEXT ctx;
PBYTE lpBuffer = NULL;
DWORD dwNumberOfBytesToWrite, dwAddrOfBuffer, i;
//用于描述异常
PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;

//异常为断点异常
if (EXCEPTION_BREAKPOINT == per->ExceptionCode)
{
if (g_pfWriteFile == per->ExceptionAddress)
{
//将WriteFile函数所在地址指令恢复原来的状态,去除掉INT3指令
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_ch0rgByte, sizeof(BYTE), NULL);

//获取进程的上下文信息
ctx.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(g_cpdi.hThread, &ctx);

//读取WriteFile函数的第二个参数,即写入的缓冲区的地址
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8), &dwAddrOfBuffer, sizeof(DWORD), NULL);
//读取WriteFile函数的第三个参数,即需要写入的字节数
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xc), &dwNumberOfBytesToWrite, sizeof(DWORD), NULL);

//分配空间,待写入数据
lpBuffer = (PBYTE)malloc(dwNumberOfBytesToWrite + 1);
memset(lpBuffer, 0, dwNumberOfBytesToWrite + 1);

//将数据写入到临时缓冲区中
ReadProcessMemory(g_cpdi.hProcess, (PVOID)dwAddrOfBuffer, lpBuffer, dwNumberOfBytesToWrite, NULL);

printf("\n### original string : %s\n", lpBuffer);

//将小写转化为大写字母
for (i = 0; i < dwNumberOfBytesToWrite; i++)
{
if (lpBuffer[i] >= 0x61 && lpBuffer[i] <= 0x7a)
lpBuffer[i] -= 0x20;
}

printf("\n### converted string :%s\n", lpBuffer);
WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer, lpBuffer, dwNumberOfBytesToWrite, NULL);

free(lpBuffer);
//修改EIP指针为WriteFile函数,使得程序正常执行
ctx.Eip = (DWORD)g_pfWriteFile;
SetThreadContext(g_cpdi.hThread, &ctx);

//运行被调试进程
ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
Sleep(0);

//继续下钩子
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_chINT3, sizeof(BYTE), NULL);
return TRUE;
}
}
return FALSE;
}
void DebugLoop()
{
DEBUG_EVENT de;//描述调试事件
DWORD dwContinueStatus;

//等待正在调试的进程中发生调试事件
while (WaitForDebugEvent(&de, INFINITE))
{
dwContinueStatus = DBG_CONTINUE;
//被调试进程生产或者附加事件
if (de.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT)
{
OnCreateProcessDebugEvent(&de);
}
//异常事件,用于处理INT3指令
else if (de.dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
{
if (OnExceptionDebugEvent(&de))
continue;
}
//被调试进程中止事件
else if (EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode)
{
break;
}
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
}
}
int main(int argc, char* argv[])
{
DWORD dwPID;
if (argc != 2)
{
printf("\nUSAGE : hookdbg.exe pid\n");
return 1;
}
//输入进程号
dwPID = atoi(argv[1]);
//使调试器能够附加到活动进程并对其进行调试。
if (!DebugActiveProcess(dwPID))
{
printf("DebugActiveProcess(%d) failed!!!\n"
"Error Code = %d\n", dwPID, GetLastError());
return 1;
}
DebugLoop();
return 0;
}

计算器显示中文数字

hookiat

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
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include<stdio.h>
#include<wchar.h>

//定义了一个布尔型的函数指针
typedef BOOL(WINAPI* PFSETWINDOWTEXTW)(HWND hWnd, LPWSTR lpString);

//SetWindowTextW的地址
FARPROC g_pOrgFunc = NULL;

BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)
{
const 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++)
{
if (L'0' <= lpString[i] && lpString[i] <= L'9')
{
temp[0] = lpString[i];
nIndex = _wtoi(temp);
lpString[i] = pNum[nIndex];
}
}
return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);
}

BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg,PROC pfnNew)
{
HMODULE hMod;
LPCSTR szLibName;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc;//输入表
PIMAGE_THUNK_DATA pThunk;
DWORD dwOldProtect ,dwRVA;
PBYTE pAddr;

hMod = GetModuleHandle(NULL);//获取当前PE文件的地址
pAddr = (PBYTE)hMod;
//在MS-DOS头部找到e_lfanew字段,该字段存放着PE文件头的偏移地址
pAddr += *((DWORD*)&pAddr[0x3c]);

//相对于PE文件头偏移0x80存放着输入表的地址
dwRVA = *((DWORD*)&pAddr[0x80]);

pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod + dwRVA);

//遍历DLL的名称
for (; pImportDesc->Name; pImportDesc++)
{
szLibName = (LPSTR)((DWORD)hMod + pImportDesc->Name);

//判断DLL名称是否为user32.dll
if (!stricmp(szLibName, szDllName))
{
pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod + pImportDesc->FirstThunk);
for (; pThunk->u1.Function; pThunk++)
{
//判断函数地址是否为setWindowText函数地址
if (pThunk->u1.Function == (DWORD)pfnOrg)
{
//将内存空间权限修改为可读可写
VirtualProtect((LPVOID)&pThunk->u1.Function, 4, PAGE_EXECUTE_READWRITE, &dwOldProtect);
//修改IAT表内的地址
pThunk->u1.Function = (DWORD)pfnNew;
//将内存权限修改回来
VirtualProtect((LPVOID)&pThunk->u1.Function, 4, dwOldProtect, &dwOldProtect);
return TRUE;
}
}
}
}
return FALSE;

}

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
g_pOrgFunc = GetProcAddress(GetModuleHandleA("user32.dll"), "SetWindowTextW");
hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);
break;
case DLL_THREAD_ATTACH:
break; //break需要添加否则无法更换IAT地址
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
hook_iat("user32.dll", (PROC)MySetWindowTextW,g_pOrgFunc);
break;
}
return TRUE;
}

进程隐藏

通过修改API代码实现API钩取的技术。库文件被加载到内存后 ,在其目录映像中直接修改要钩取的API代码本身。

用户模式下检测进程的相关API通常分为两种

  • CreateToolhelp32Snapshot()
1
2
3
4
5
//通过获取进程信息为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程建立一个快照
HANDLE WINAPI CreateToolhelp32Snapshot(
DWORD dwFlags,
DWORD th32ProcessID
);
  • EnumProcess()
1
2
3
4
5
6
//检索系统中每个进程对象的进程标识符
EnumProcesses(
DWORD* pProcessIds, //指向接收进程标识符列表的数组的指针
DWORD cb, //pProcessIds数组的大小
DWORD* pBytesReturned //pProcessIds数组中返回的字节数
);

上述两种API都在内部调用了ntdll.ZwQuerySystemInformation() API

  • ZwQuerySystemInformation()
1
2
3
4
5
6
7
//检索指定的系统信息
NTSTATUS WINAPI ZwQuerySystemInformation(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,//要检索的系统信息的类型
_Inout_ PVOID SystemInformation,//指向接收请求信息的缓冲区的指针
_In_ ULONG SystemInformationLength,//参数指向的缓冲区的大小
_Out_opt_ PULONG ReturnLength//指向函数写入请求信息的实际大小的位置的可选指针
);

ProcExp.exe:进程查看器

taskmgr.exe:任务管理器

通过钩取上述两个进程可以将目标进程进行隐藏,但是该钩取方法存在缺陷:

  1. 检索进程的工具不仅仅是上述两个进程
  2. 若用户再次开启一个ProcExp.exe或者taskmgr.exe,则旧的ProcExp.exe或者taskmgr.exe被钩取可以达到隐藏进程的效果,但是新的ProcExp.exe或者taskmgr.exe未被钩取因此无法隐藏。

解决方法:全局钩取


逆向工程核心原理--HOOK
https://h0pe-ay.github.io/逆向工程核心原理--HOOK/
作者
hope
发布于
2023年6月27日
许可协议