第34章 高级全局API钩取:旧连接控制

本文最后更新于:2022年5月27日 下午

第34章高级全局API钩取:旧连接控制

本章将学习更高级的全局API钩取技术,在示例中我们将钩取IE,使其试图连接到指定网站时转而连接到我的博客。

练习目标是钩取IE进程的API,在它连接到特定网站的过程中,将其连接到其他网站。无论是在IE地址栏中直接输入地址还是点击某个链接,IE都无法连接到被阻止的网站(把这当作拦恶意网页功能就比较容易理解)。

在防火墙层面实现恶意网页拦截功能会更有效果。本章示例仅供学习之用,在实际产品开发中实现恶意网页拦截功能时要充分考虑这一点

34.1 目标 API

API钩取的核心就是选择目标API,即要钩取的API,每个入在这一过程中都有各自不同的绝招。程序开发经验越丰富、API钩取经验越多,对选择目标API就越有利(当然,通过强大的网络检索功能也能解决大部分问题)。开始前先大致“猜测” 一下,只要钩取套接字库(ws2_32.dll)或微软提供的网络访问相关库(wininet.dll、 winhttp.dll)就可以了(钩取后者更容易)。

下面运行IE进行分析。首先使用Process Explorer查看IE加载了哪些DLL。

图34-1 IE中加载的库

从图34-1可以看到,IE不仅加载了ws2_32.dll,还加载了wininet.dll库。Wininet.dll提供的API中有个名为InternetConnect()的API (出处:MSDN ),顾名思义,该API用来连接某个网站。

1
2
3
4
5
6
7
8
9
10
HINTERNET InternetConnect(
[in] HINTERNET hInternet,
[in] LPCSTR lpszServerName,
[in] INTERNET_PORT nServerPort,
[in] LPCSTR lpszUserName,
[in] LPCSTR lpszPassword,
[in] DWORD dwService,
[in] DWORD dwFlags,
[in] DWORD_PTR dwContext
);

接下来验证Wininet.InternetConnect() API是否就是要钩取的API。

验证:调试旧进程

首先使用OllyDbg附加IE进程,然后在wininet!InternetConnectW() API处设置好断点(InternetConnectW() API是InternetConnect()的宽字符版本),如图34-2所示。

图34-2 在WININET.InternetConnectW()处设置断点

然后在IE地址栏中输入要连接的网站地址,如图34-3所示。

图34-3 IE地址栏

调试器暂停在设置的断点处,此时查看进程栈,如图34-4所示。

图34-4 断点位置的栈

从栈信息中可以看到,连接地址(IpszServerName )就是前面在正地址栏中输入的www.baidu.com地址。下面修改连接地址进行测试。

如图34-5所示,将 “www.baidu.com” 修改为 “www.reversecore.com” 字符串。

图34-5 修改连接地址

上面地址全为Unicode字符串,最后以2个字节的NULL (0000)结束(在HEX 窗口中向字符串末尾输入00 00即可)。

修改连接地址后运行调试器,它会在设置断点的wininet!ImernetConnectW()处反复暂停,这是因为一个网站往往由多个链接地址组成。删除断点后继续运行(仅在第一次调用InternetConnectW()时操作一次栈即可)。最终,IE浏览器会连接到修改后的www.reversecore.com网站,而不是先前的www.google.com网站,如图34-6所示。

因此,钩取wininet!InternetConnectW()后,修改IpszServerName参数即可控制IE要连接的网站。看来,钩取!InternetConnectW()API是个非常好的选择。原理相当简单,因为IE使用wininet.dll库,所以很容易钩取API。以上这些就是常用的AM钩取方法,但具体实现时有一点需要考虑,IE8具 有独特的进程结构,钩取时要使用全局API钩取技术。

34.2 旧进程结构

重新运行IE浏览器,打开多个选项卡(tab ),分别连接到不同网站,如图34-7所示。

图34-7 IE选项卡

使用Process Explorer查看IE进程结构,如图34-8所示。

图34-8 iexplorer.exe进程结构

从图34-7与图34-8中可以看到,IE中有2个选项卡,共有3个IE进程(iexplore.exe )在运行。并且PID为4184的iexplore.exe进程与其他iexplore.exe进程形成了父子关系。从IE进程结构来看,IE应用程序为父进程(PID: 3784),它管理着各选项卡对应的子进程。

IE 7开始引入“选项卡”这一概念,进程结构也发生了如上所示的变化。这种新进程结构下,每个选项卡都是一个独立运行的进程,其中一个选项卡发生错误,不会影响到其他选项卡或父进程(IE本身)(最新的网页浏览器中都使用了这项技术)。

像这样,IE应用程序中每个选项卡对应的子iexplore.exe进程实际负载网络连接,创建选项卡进程时,(相关进程的)API就会被执行钩取操作,即采用全局API钩取技术钩取。否则,在新选项卡中连接网站时将无法钩取。前面我们介绍了通过钩取kernel32!CreateProcess() API实现全局API钩取的方法,并且说明了使用CreateProcess() API钩取这一方法的限制条件。

本章将介绍一种更安全、更方便的全局API钩取方法,采用该方法可有效消除因使用CreateProcess() API钩取技术实现全局API钩取而产生的不便。这种新方法是,钩取ntdll!ZwResumeThread()API,创建进程之后,主线程被Resume (恢复运行)时,可以钩取目标API。

34.3关于全局API钩取的概念

下面对全局API钩取进行简单整理。通过前面的学习,我们已经能对特定进程的指定API进
行简单的钩取操作。

34.3.1 常规API钩取

使用常规API钩取方法时,每当(要钩取的)目标进程被创建时,都要钩取指定API。图34-9描述了通过DLL注入技术实施常规API钩取操作的情形。

图34-9 常规API钩取

图34-9中要钩取的目标进程为Test.exe(PID: 2492 )。使用InjDll.exe程序将Hook.dll注入Test.exe进程,然后钩取指定API①。若后面生成了另外一个Test.exe进程(PID: 3796),则必须先向它注入Hook.dll才能(对PID为3796的进程)实现正常的API钩取操作②。也就是说,每当要钩取的目标进程生成时都要手动钩取API。

34.3.2全局API钩取

接下来看一下全局API钩取的操作过程,如图34-10所示。

图34-10 全局API钩取

InjDll.exe负责将gHook.dll注入Explorer.exe进程(Windows操作系统的基本Shell )。请注意,我们要钩取的进程不是Test.exe,而是启动运行Test.exe的Explorer.exe进程,这是最核心的部分。gHook.dll扩展了图34-9中Hook.dll的功能,它钩取创建子进程的API,每当子进程被创建时,它都会将自身(gHook.dll)注入新创建的进程(参考图34-10 )。所以向Explorer.exe进程(Windows Shell)注入1次gHook.dll后,随后Explorer.exe创建的所有子进程都会自动注入gHook.dll。这就是自动API钩取的基本概念,将这一概念扩展应用到系统中运行的所有进程,就形成了全局API钩取。

除Explorer.exe之外,其他进程也可以创建子进程。所以要完美实现全局API钩取,必须钩取当前运行的所有进程。但是基于系统稳定性与减少不必要系统开销的考虑,常常(根据实际要求)仅钩取特定进程(示例中对IE的钩取就是典型例子)。

到此我们已经梳理了全局API钩取的概念。下面分析钩取哪些API才能使全局API钩取实现起来更容易。

34.4 ntdll!ZwResumeThread() API

首先,想想创建子进程的API有哪些,创建进程的API中最具代表性的绝对是kernel32!CreateProcess() API。下面编写一个简单的程序来测试CreateProcess() API,代码如下所示。

所有源代码均使用MS Visual C++Express Edition 2010工具编写而成,在Windows 7 32位& IE 8中通过测试。

编译代码34-1,生成cptest.exe可执行文件。调试这个文件可以把握与进程创建相关的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
// cptest.cpp

#include "windows.h"
#include "tchar.h"

void main(){
STARTUPINFO si = {0,};
PROCESS_INFORMATION pi = {0,};
TCHAR szCmd[MAX_PATH] = {0,};

si.cb = sizeof(STARTUPINFO);
_tcscpy(szCmd, L"notepad.exe");
if(!CreateProcess( NULL, // lpApplicationName
szCmd, // lpCommandLine
NULL, // lpProcessAttributes
NULL, // lpThreadAttributes
FALSE, // blnheritHandles
NORMAL_PRIORITY_CLASS, // dwCreationFlags
NULL, // lpEnvironment
NULL, // lpCurrentDirectory
&si, // lpStartupInfo
&pi)) // lpProcessInformation
return;
if(pi.hProcess != NULL)
CloseHandle(pi.hProcess);
}

CreateProcessW是CreateProcess 的宽字符(UNICODE)版本。

图34-11是调用cptest.exe的kernel32!CreateProcessW()的代码。

图34-11 调用CreateProcessW()的代码

跟踪进入kernel32!CreateProcessW(StepInto(F7)),可以看到在其内部又调用了kernel32!CreateProcessInternelW(),如图34-12所示。

图34-12 调用CreateProcessInternalW()

在图34-12中查看下方栈内存,可以看到它与图34-11中的栈(函数参数)几乎是一样的。继续跟踪进入kernel32!CreateProcessInternelW(),如图34-13所示。

图34-13 CreateProcessInternalW()代码

kernel32!CreateProcessInternelW()是一个相当大的函数。在代码窗口中向下拖动滑动条,就会岀现调用ntdll!ZwCreateUserProcess()的代码,如图34-14所示。

这里没找到Zw,找的是Nt

图34-14调用ZwCreateUserProcess()的代码

在图34-14中查看下方的栈,可以看到它与图34-12中的栈有着非常大的不同。第二个参数(Arg2)是一个结构体,查看左侧的Hex dump窗口可以发现,结构体成员中,地址12F954存储的12FD3C是字符串(“notepad”)的地址(参考图34-11 中的栈)。调用Ntdll!ZwCreateUserProcess()时子进程就会被挂起(Suspend ),暂停运行,如图34-15所示。

图34-15 notepad.exe被挂起

notepad.exe进程已经生成,但是其EP代码尚未运行。在图34-14代码中继续执行,就会出现调用ntdll!ZwResumeThread() API 的代码,如图34-16所示。

图34-16 调用ZwResumeThread()函数的代码

顾名思义,ntdlllZwResumeThread()函数就是用来恢复运行线程的。该线程即是子进程(notepad.exe)的主线程。所以调用执行该API时,子进程的EP代码才会执行,如图34-17所示。

图34-17 恢复运行的notepad.exe

综上所述,CreateProcessW() API的调用流程整理如下:

1
2
3
4
kernel32!CreateProcessW
kernel32!CreateProcessInternalW
ntdll!ZwCreateUserProcess //创建进程(主线程处于挂起状态)
ntdll!ZwResumeThread //主线程被恢复运行(运行进程)

创建子进程的过程中最后被调用的API是ntdll!ZwResumeThread()。所以钩取该API,在子进程的EP代码运行之前,拦截获取控制权,然后钩取指定API。ntdll!ZwResumeThread()是尚未公开的API,函数定义(出处:MSDN)如下:

1
2
3
4
NTSTATUS NtResumeThread(
IN HANDLE ThreadHandle,
OUT PULONG SuspendCount OPTIONAL
);

用户模式中 ntdll!ZwResumeThread() API 与 ntdll!NtResumeThread() API 虽然名称不同,但其实是同一函数。

前面介绍的4个API(CreateProcessW、CreateProcessInternalW、ZwCreateUserProcess、ZwResumeThread)中,无论钩取哪个API,都能实现我们的目标—全局API钩取(下面练习中将钩取ntdll.ZwResumeThread() API )。

若钩取位于上层的CreateProcessW()函数,则在某个特定情形下(如直接调用CreateProcessInternelW时)可能导致无法正常钩取。所以最好钩取CreateProcessInternelW()下层的函数(各有优缺点,建议各位都尝试一下)。

34.5练习示例:控制旧网络连接

下面做个练习,目标是控制IE的网络连接。钩取IE进程的特定API,用IE连接指定网站时,使之连接到另外一个网站(www.reversecore.com)。此外,在IE中添加新选项卡,同时比较新添加的进程的情形,进一步了解全局API钩取技术。

本练习示例在Windows XP SP3、Windows 7 32位操作系统及IE 8中通过测试。

示例练习中,我们将向目标进程注入redirect.dll来实现API钩取。redirect.dll钩取下面2个API。

1
2
wininet!InternetConnectW():钩取后可以控制IE进程的连接地址。
ntdll!ZwResumeThread():钩取后实现全局API钩取。

34.5.1 运行IE

首先运行IE浏览器,然后使用Process Explorer查看运行中的IE进程的结构。

从图34-18中可以看到,IE进程以父子进程的形式运行。只要钩取父进程ntdinZwResumeThread()API,那么后面生成的所有子IE进程都会自动钩取。

图34-18 iexplore.exe进程

34.5.2 注入 DLL

首先在命令行窗口中使用InjDll.exe命令,将redirect.dll文件注入IE进程(iexplore.exe),如图34-19所示。

图34-19 运行InjDll.exe-将redirect.dir注入iexplore.exe

使用Process Explorer工具查看redirect.dll文件是否正常注入IE进程,如图34-20所示。

图34-20 査看redirect.dll成功注入

InjDll.exe是专门用于注入的程序。更多相关说明请参考第44章“DLL注入专用工具”。

34.5.3 创建新选项卡

在IE浏览器中创建新选项卡,如图34-21所示。

图34-21 新建IE选项卡

使用Process Explorer工具可以看到,redirect.dll已经成功注入新选项卡进程(PID:3472),如图34-22所示。

图34-22 向新IE进程注入redirect.dll

由此可见,通过钩取ntdll!ZwResumeThread()API成功实现了全局API钩取。

34.5.4尝试连接网站

在IE任意一个选项卡中尝试连接下列网站。
www.naver.com
www.daum.net
www.nate.com
www.yahoo.com

如图34-23所示,虽然地址栏中输入的是yahoo,但是浏览器实际跳转到了网站ReverseCore。

图34-23 被钩取的IE进程

34.5.5 卸载 DLL

下面从IE进程卸载(Unloading) redirect.dll文件,如图34-24所示。

图34-24 从iexplore.exe进程卸载redirect.dll

使用Process Explorer工具可以看到redirect.dll已成功卸载(参考图34-25 )。

图34-25 redirect.dll成功卸载

现在使用IE浏览器重新连接Naver,可以看到IE正常连接,如图34-26所示。

图34-26 “脱钩”后的IE浏览器

34.5.6 课外练习

请各位使用前面介绍的InjDll.exe与redirect.dll文件再多做些课外练习。练习做得多了,才能 真正理解全局API钩取技术的原理与含义。

  • 钩取所有进程。

  • 仅钩取explorer.exe (以后运行IE )。

34.6 示例源代码

本节讲解主要函数(为讲解方便,代码中省略了异常处理部分)。

34.6.1 DIIMain()

首先看看DllMain()函数。

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
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
char szCurProc[MAX_PATH] = {0,};
char *p = NULL;

switch( fdwReason )
{
case DLL_PROCESS_ATTACH :
DebugLog("DllMain() : DLL_PROCESS_ATTACH\n");

GetModuleFileNameA(NULL, szCurProc, MAX_PATH);
p = strrchr(szCurProc, '\\');
if( (p != NULL) && !_stricmp(p+1, "iexplore.exe") )
{
DebugLog("DllMain() : current process is [iexplore.exe]\n");

// 钩取 wininet!InternetConnectW() API 之前预先加载 wininet.dll
if( NULL == LoadLibrary(L"wininet.dll") )
{
DebugLog("DllMain() : LoadLibrary() failed!!! [%d]\n",
GetLastError());
}
}

// hook
hook_by_code("ntdll.dll", "ZwResumeThread",
(PROC)NewZwResumeThread, g_pZWRT);
hook_by_code("wininet.dll", "InternetConnectW",
(PROC)NewInternetConnectW, g_pICW);
break;

case DLL_PROCESS_DETACH :
DebugLog("DllMain() : DLL_PROCESS_DETACH\n");

// unhook
unhook_by_code("ntdll.dll", "ZwResumeThread",
g_pZWRT);
unhook_by_code("wininet.dll", "InternetConnectW",
g_pICW);
break;
}

return TRUE;
}

DllMain()函数的核心功能是ntdll!ZwResumeThread()与wininet!InternetConnectW() API的“挂钩/脱钩”功能。其中有条语句显得比较特别,若运行的进程名为iexplorer.exe时,则加载wininet.dll文件。iexplorer.exe进程正常运行时,自然会加载wininet.dll,为什么还要特意增加一条语句加载它呢?这与全局API钩取的特性有关。钩取ntdll!ZwResumeThread()API时,需要在相关进程的主线程开始之前拦截控制权,此时,我们要钩取的wininetdll模块可能尚未加载。若模块未加载就钩取其内部API,将导致失败。为防止出现这类问题,进程为iexplore.exe时,钩取wininet! InternetConnectW() AJPI之前必须加载wininet.dll文件。

示例代码中,我们使用5字节修改技术钩取Wininet.InternetConnectW() API,当然使用7字节修改技术也是可以的。但是钩取ntdll.ZwResumeThread()API只能使用5字节修改技术(由于没有足够的空间,所以无法使用7字节修改技术)。关于7字节修改技术(“热补丁”)请参考第33章。

34.6.2 NewlnternetConnectW()

wininet!InternetConnectW()的钩取函数为NewInternetConnectW()函数,它负责监视IE的连接地址,IE尝试连接到特定网站时,将其转到我们指定的网站。以下是NewInternetConnectW()函数的代码。

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
HINTERNET WINAPI NewInternetConnectW
(
HINTERNET hInternet,
LPCWSTR lpszServerName,
INTERNET_PORT nServerPort,
LPCTSTR lpszUsername,
LPCTSTR lpszPassword,
DWORD dwService,
DWORD dwFlags,
DWORD_PTR dwContext
)
{
HINTERNET hInt = NULL;
FARPROC pFunc = NULL;
HMODULE hMod = NULL;

// unhook
if( !unhook_by_code("wininet.dll", "InternetConnectW", g_pICW) )
{
DebugLog("NewInternetConnectW() : unhook_by_code() failed!!!\n");
return NULL;
}

// call original API
hMod = GetModuleHandle(L"wininet.dll");
if( hMod == NULL )
{
DebugLog("NewInternetConnectW() : GetModuleHandle() failed!!! [%d]\n",
GetLastError());
goto __INTERNETCONNECT_EXIT;
}

pFunc = GetProcAddress(hMod, "InternetConnectW");
if( pFunc == NULL )
{
DebugLog("NewInternetConnectW() : GetProcAddress() failed!!! [%d]\n",
GetLastError());
goto __INTERNETCONNECT_EXIT;
}

if( !_tcsicmp(lpszServerName, L"www.naver.com") ||
!_tcsicmp(lpszServerName, L"www.daum.net") ||
!_tcsicmp(lpszServerName, L"www.nate.com") ||
!_tcsicmp(lpszServerName, L"www.yahoo.com") )
{
DebugLog("[redirect] naver, daum, nate, yahoo => reversecore\n");
hInt = ((PFINTERNETCONNECTW)pFunc)(hInternet,
L"www.reversecore.com",
nServerPort,
lpszUsername,
lpszPassword,
dwService,
dwFlags,
dwContext);
}
else
{
DebugLog("[no redirect]\n");
hInt = ((PFINTERNETCONNECTW)pFunc)(hInternet,
lpszServerName,
nServerPort,
lpszUsername,
lpszPassword,
dwService,
dwFlags,
dwContext);
}

__INTERNETCONNECT_EXIT:

// hook
if( !hook_by_code("wininet.dll", "InternetConnectW",
(PROC)NewInternetConnectW, g_pICW) )
{
DebugLog("NewInternetConnectW() : hook_by_code() failed!!!\n");
}

return hInt;
}

从以上代码可以看到,NewInternetConnectW()函数代码并不复杂。函数的第二个参数IpszServerName字符串即是要连接的网站地址。监视该连接地址,IE连接的是特定网站(Naver、Daum、Nate、Yahoo)时,就将连接地址修改为我的博客地址(ReverseCore )。

34.6.3 NewZwResumeThread()

NewZwResumeThread()函数用来对ntdll!ZwResumeThread() 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
NTSTATUS WINAPI NewZwResumeThread(HANDLE ThreadHandle, PULONG SuspendCount)
{
NTSTATUS status, statusThread;
FARPROC pFunc = NULL, pFuncThread = NULL;
DWORD dwPID = 0;
static DWORD dwPrevPID = 0;
THREAD_BASIC_INFORMATION tbi;
HMODULE hMod = NULL;
TCHAR szModPath[MAX_PATH] = {0,};

DebugLog("NewZwResumeThread() : start!!!\n");

hMod = GetModuleHandle(L"ntdll.dll");
if( hMod == NULL )
{
DebugLog("NewZwResumeThread() : GetModuleHandle() failed!!! [%d]\n",
GetLastError());
return NULL;
}

// call ntdll!ZwQueryInformationThread()
pFuncThread = GetProcAddress(hMod, "ZwQueryInformationThread");
if( pFuncThread == NULL )
{
DebugLog("NewZwResumeThread() : GetProcAddress() failed!!! [%d]\n",
GetLastError());
return NULL;
}

statusThread = ((PFZWQUERYINFORMATIONTHREAD)pFuncThread)
(ThreadHandle, 0, &tbi, sizeof(tbi), NULL);
if( statusThread != STATUS_SUCCESS )
{
DebugLog("NewZwResumeThread() : pFuncThread() failed!!! [%d]\n",
GetLastError());
return NULL;
}

dwPID = (DWORD)tbi.ClientId.UniqueProcess;
if ( (dwPID != GetCurrentProcessId()) && (dwPID != dwPrevPID) )
{
DebugLog("NewZwResumeThread() => call InjectDll()\n");

dwPrevPID = dwPID;

// change privilege
if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
DebugLog("NewZwResumeThread() : SetPrivilege() failed!!!\n");

// get injection dll path
GetModuleFileName(GetModuleHandle(STR_MODULE_NAME),
szModPath,
MAX_PATH);

if( !InjectDll(dwPID, szModPath) )
DebugLog("NewZwResumeThread() : InjectDll(%d) failed!!!\n", dwPID);
}

// call ntdll!ZwResumeThread()
if( !unhook_by_code("ntdll.dll", "ZwResumeThread", g_pZWRT) )
{
DebugLog("NewZwResumeThread() : unhook_by_code() failed!!!\n");
return NULL;
}

pFunc = GetProcAddress(hMod, "ZwResumeThread");
if( pFunc == NULL )
{
DebugLog("NewZwResumeThread() : GetProcAddress() failed!!! [%d]\n",
GetLastError());
goto __NTRESUMETHREAD_END;
}

status = ((PFZWRESUMETHREAD)pFunc)(ThreadHandle, SuspendCount);
if( status != STATUS_SUCCESS )
{
DebugLog("NewZwResumeThread() : pFunc() failed!!! [%d]\n", GetLastError());
goto __NTRESUMETHREAD_END;
}

__NTRESUMETHREAD_END:

if( !hook_by_code("ntdll.dll", "ZwResumeThread",
(PROC)NewZwResumeThread, g_pZWRT) )
{
DebugLog("NewZwResumeThread() : hook_by_code() failed!!!\n");
}

DebugLog("NewZwResumeThread() : end!!!\n");

return status;
}

NewZwResumeThread()函数的第一个参数是要恢复运行的线程句柄(ThreadHandle)。前面说明中已经指出,该线程即是子进程的主线程。在NewZwResumeThread()函数的前半部分调用ZwQueryInformationThread() API,就是为了获取线程句柄所指线程(子进程的线程)所属的子进程的PID。像这样,通过线程句柄参数即可获得(刚刚创建的)子进程的PID,然后使用该PID就可以注入redirect.dll (钩取DLL)文件。相关子进程在主线程运行前就已经注入redirect.dll文件,自动实现了API钩取。最后正常调用ntdll!ZwResumeThread()API,将子进程的主线程恢复运行。这样,子进程就在API被钩取的状态下得以正常运行。

钩取 ntdll!ZwResumeThread() API 比构取 kernel32!CreateProcess() API 更强大、更方便。因为CreateProcess()在内部调用了 CreateProcessInteraal()0如果在程序中直接调用CreateProcessInternel(),则无法正常钩取(此时直接钩取CreateProcessInternel()反而更好)。像这样,越钩取低级API (ntdll.dll中提供的API),效果越好。但是大部分低级API尚未文档化,根据OS版本不同可能变化。相反,高级API (kernel32.dll级 别-公开的)一般不会变化,文档化做得也非常好,用来钩取是比较稳定的,但是钩取性能要差一些。所以,高级API钩取与低级API钩取各有长短,使用时要根据具体情况选择,这才是明智的做法。

34.7小结

本章分析了练习示例的源代码,进一步学习了全局API钩取的实现原理,并借此掌握了有关API钩取的所有知识。若想成为API钩取专家就要不断尝试,经历大量失败来积累丰富的实战经验。接触并解决各类问题才能逐渐提高代码逆向分析水平,相信大家会对此深有体会。


第34章 高级全局API钩取:旧连接控制
https://m0ck1ng-b1rd.github.io/1999/03/04/逆向工程核心原理/第34章 高级全局API钩取:IE连接控制/
作者
何语灵
发布于
1999年3月4日
许可协议