May 4, 2022

关于逆向工程核心原理-DLL注入式API钩取

(简称逆向工程核心原理第三十二章学习)

先贴下技术图表

image-20220504230332571

实验目标与工作原理

实验目标

让calc.exe显示的数字显示为中文数字!

所以我们该如何实验,可能跟着书很好实验,但非常喜欢书上选定目标API一段,当我们要自己实现些当然没有书的辅助,而是要我们自己的思考!

  1. 确定钩取技术(详细见技术图标)
  2. 选定目标API(最终的功能都是由某个或某些API实现)
  3. 不知道目标API怎么办,就是去google!

而这次我们的目标API是SetDlgItemTextW,而在其内部又调用了SetWindowsTextW()

PS: W代表该API是宽字符,A代码是ASCII码

SetWindowTextW

直接把calc.exe拉进dbg,ctrl + g直接找到这个API,开头下个断点

image-20220504225109934

观察栈的第二个参数即是我们指向要放到calc.exe的上的字符串地址,那么我们只要改这即可

PS: 七的unicode是4E03,而是小端序我们要逆序放入(034E)

image-20220504225303305

工作原理

直接贴书上的图了,这是原始的正常IAT调用

image-20220504230450963

这是我们注入了DLL式API钩取后的样子

  1. 当注入的时候把SetWindowTextW的IAT值改成了我们自己的
  2. 跳到我们自己的MySetWindowTextW,修改成中文
  3. 再调用系统的API

image-20220504230749802

实验环节

参数:i 是注入dll,e 是卸载dll(一开始-i了半天…)

成功注入,不过现在还是韩文版的

image-20220504232053854

分析源码

InejectDll

两个参数,一个是卸载一个是加载,同样还是利用的CreateRemoteThread实现

(卸载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
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
#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 : InjectDll.exe <i|e> <PID> <dll_path>\n\n");
}


BOOL InjectDll(DWORD dwPID, LPCTSTR szDllName)
{
HANDLE hProcess, hThread;
LPVOID pRemoteBuf;
DWORD dwBufSize = (DWORD)(_tcslen(szDllName) + 1) * sizeof(TCHAR);
LPTHREAD_START_ROUTINE pThreadProc;

// 基于PID打开目标进程,返回进程句柄
if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
{
DWORD dwErr = GetLastError();
return FALSE;
}

// 在目标进程内存空间开了DLL名称的空间
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);

// 向目标进程的指定内存空间pRemoteBuf写入了DLL名称
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllName, dwBufSize, NULL);

// 获取LoadLibraryW的地址(因为是系统dll所以两个进程的地址都一样)
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
// 远程执行 加载指定DLL
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;

// 获取系统镜像,获取加载到指定进程的所有DLL名称
if( INVALID_HANDLE_VALUE == (hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID)) )
return FALSE;

// 存到me遍历比较
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;
}

// 再远程卸载dll
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 )
{
usage();
return 1;
}

// adjust privilege
_EnableNTPrivilege(SE_DEBUG_NAME, SE_PRIVILEGE_ENABLED);

// InjectDll.exe <i|e> <PID> <dll_path>
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;
}

hookiat

DllMain

要注意由于计算机已经加载了user32.dll,所以直接调用GetProcAddress(),在实际情况要确定目标进程的DLL,如果没有加载,要记得LoadLibrary()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch( fdwReason )
{
case DLL_PROCESS_ATTACH :
// 保存原来的SetWindowTextW地址
g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"),
"SetWindowTextW");

// # IAT钩取
// user32!SetWindowTextW() 变成 hookiat!MySetWindowText()
hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);
break;

case DLL_PROCESS_DETACH :
// # 脱钩
// calc.exe的SetWindowTextW恢复
hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc);
break;
}

return TRUE;
}

MySetWindowTextW

系统每次要调用SetWindowTextW会调用到这,改成中文数字就继续调用系统的函数

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
// 系统每次要调用SetWindowTextW会调用到这
BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)
{
// 不强制类型转换一下会报错
wchar_t* pNum = (wchar_t*)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];
}
}

// 修改好缓冲区,再调用真正的SetWindowTextW实现输出
return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);
}

hook_iat

一系列操作就是找到pfnOrg的IAT,然后pfnNew覆盖,由此本函数是用来挂钩和脱钩的

比较好奇的通过0x3C和0x80找到了IDT(动调看一下)

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
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, pAddr = ImageBase of calc.exe
// = VA to MZ signature (IMAGE_DOS_HEADER)
hMod = GetModuleHandle(NULL);
pAddr = (PBYTE)hMod;

// pAddr = VA to PE signature (IMAGE_NT_HEADERS)
pAddr += *((DWORD*)&pAddr[0x3C]);

// dwRVA = RVA to IMAGE_IMPORT_DESCRIPTOR Table
dwRVA = *((DWORD*)&pAddr[0x80]);

// pImportDesc = VA to IMAGE_IMPORT_DESCRIPTOR Table
pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);

// 这上面一系列操作就是找到IID表(在PEView中名为IDT)

for( ; pImportDesc->Name; pImportDesc++ )
{
// pImporyDesc-?Name是指向各个DLL表名,由此和第一个参数szDllName对比(user32.dll)
szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);
if( !_stricmp(szLibName, szDllName) )
{
// pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk
// = VA to IAT(Import Address Table)
// 这个即是user32.dll里的IAT表,里面存放着所有IAT
pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod +
pImportDesc->FirstThunk);

// pThunk->u1.Function = VA to API
for( ; pThunk->u1.Function; pThunk++ )
{
if( pThunk->u1.Function == (DWORD)pfnOrg )
{
// 由于计算机进程的IAT内存区域是只读的,所以这里是更改为可读写
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;
}

开调!

附加程序F9跑起来,开启DLL载入断点,开注,断住,一开始可能断到的不是HookIAT,F9跑两下就知道了

(看书上说是dbg1.10断dll有问题可能会断到其他位置,2.0用来dll断点啥的好用)

image-20220506150536341

可以通过字符串找到主函数

image-20220506151500630

在这里出现了代码优化的情况

image-20220506152714062

逐个比较是否是IAT,随后就更改了IAT的地址变成MySetWindowText

image-20220506153723719

在SetWindowTextW下个断点可以发现我们的数字已经成功变成七

image-20220506154817966

(dbg2界面好痛苦)

DASCTF X SU
🍬
HFCTF2022
🍪

About this Post

This post is written by P.Z, licensed under CC BY-NC 4.0.