逆核系列No.8--DLL卸载

本节内容

DLL卸载是将强制插入进程的DLL弹出的一种技术,其基本原理用CreateRemoteThread API进行DLL注入的原理类似,区别是DLL注入时CreateRemoteThread 的回调函数是LoadLibrary API 而DLL卸载CreateRemoteThread 的回调函数是FreeLibrary

参考书:《逆向工程核心原理》

源码行为剖析

EjectDll -> main

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
// EjectDll.exe

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

#define DEF_PROC_NAME (L"notepad.exe")
#define DEF_DLL_NAME (L"myhack.dll")


int _tmain(int argc, TCHAR* argv[])
{
DWORD dwPID = 0xFFFFFFFF;

// find process
dwPID = FindProcessID(DEF_PROC_NAME); //看起始第十行代码,完成的功能是找到notepad.exe的pid
if( dwPID == 0xFFFFFFFF )
{
_tprintf(L"There is no <%s> process!\n", DEF_PROC_NAME);
return 1;
}

_tprintf(L"PID of \"%s\" is %d\n", DEF_PROC_NAME, dwPID);

// change privilege
if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
return 1;

// eject dll
if( EjectDll(dwPID, DEF_DLL_NAME) ) //No.88代码
_tprintf(L"EjectDll(%d, \"%s\") success!!!\n", dwPID, DEF_DLL_NAME);
else
_tprintf(L"EjectDll(%d, \"%s\") failed!!!\n", dwPID, DEF_DLL_NAME);

return 0;
}

main -> FindProcessID

main函数先是使用 API FindProcessID找到目标进程的pid,找到了对进程名称于pid进行输出,FindProcessID是在EjectDLL.exe源码中定义:

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
DWORD FindProcessID(LPCTSTR szProcessName)
{
DWORD dwPID = 0xFFFFFFFF;
HANDLE hSnapShot = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe;

// Get the snapshot of the system
pe.dwSize = sizeof( PROCESSENTRY32 );
hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL ); //第一个参数有规定,这里创造的快照是所有系统中的进程,返回值是一个handle类型

// find process
Process32First(hSnapShot, &pe); //取得快照中的第一项PROCESSENTRY32结构体
do
{
//比对第一个PROCESSENTRY32.szExeFile,该项是当前exe文件的名称,这里比对的事main函数传递进来的参数:notepad.exe
if(!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))
{
dwPID = pe.th32ProcessID; //比对成功则将dwPID设置为当前的进程
break;
}
}
while(Process32Next(hSnapShot, &pe)); //循环比对

CloseHandle(hSnapShot);

return dwPID;
}

在FindProcessID内部会使用APi CreateToolhelp32Snapshot返回系统中

1
2
3
4
HANDLE CreateToolhelp32Snapshot(
[in] DWORD dwFlags, //The portions of the system to be included in the snapshot.
[in] DWORD th32ProcessID //The process identifier of the process to be included in the snapshot. This parameter can be zero to indicate the current process.
);
Value Meaning
TH32CS_SNAPALL Includes all processes and threads in the system, plus the heaps and modules of the process specified in th32ProcessID. Equivalent to specifying the TH32CS_SNAPHEAPLIST,TH32CS_SNAPMODULE, TH32CS_SNAPPROCESS, andTH32CS_SNAPTHREAD values combined using an OR operation (‘|’).

CreateToolhelp32Snapshot会返回获取到的进程快照对应的句柄

接着使用Process32First & Process32Next遍历CreateToolhelp32Snapshot得到进程快照内容,依次赋给PROCESSENTRY32 pe,接下来从pe中获取当前的进程快照块中名称是否为目标进程“notepad.exe”,PROCESSENTRY32结构体原型如下:

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct tagPROCESSENTRY32 {
DWORD dwSize;
DWORD cntUsage;
DWORD th32ProcessID;
ULONG_PTR th32DefaultHeapID;
DWORD th32ModuleID;
DWORD cntThreads;
DWORD th32ParentProcessID;
LONG pcPriClassBase;
DWORD dwFlags;
CHAR szExeFile[MAX_PATH]; //当前进程名称
} PROCESSENTRY32;

若配对成功说明当前进程快照块是目标进程notepad.exe,则取他的dwPID = pe.th32ProcessID进行FindProcessID return


main -> SetPrivilege

回到main函数内部,会执行权限提升(后续操作需要),SetPrivilege(SE_DEBUG_NAME, TRUE),后续在补充,这里只需要先记住经过系统权限调整使得后续从目标进程弹出DLL的操作满足权限要求即可。


main -> EjectDll

随后进行关键的DLL弹出函数EjectDll(dwPID, DEF_DLL_NAME),参数是目标进程对应的PID,以及即将从目标进程中卸载的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
BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName)		//待操作的进程以及将要卸载的dll
{
BOOL bMore = FALSE, bFound = FALSE;
HANDLE hSnapshot, hProcess, hThread;
HMODULE hModule = NULL;
MODULEENTRY32 me = { sizeof(me) };
LPTHREAD_START_ROUTINE pThreadProc;

// dwPID = notepad.exe Process ID
// TH32CS_SNAPMODULE
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID); //快照dwPID进程载入的模块

bMore = Module32First(hSnapshot, &me);
for( ; bMore ; bMore = Module32Next(hSnapshot, &me) )
{
//比对载入的dll名称以及路径是否一致,锁定myhack.dll
if( !_tcsicmp((LPCTSTR)me.szModule, szDllName) ||
!_tcsicmp((LPCTSTR)me.szExePath, szDllName) )
{
bFound = TRUE; //匹配到了设置标志位bFound为true
break;
}
}

if( !bFound )
{
CloseHandle(hSnapshot); //匹配myhack.dll失败
return FALSE;
}

if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
{
_tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
return FALSE;
}
//寻找kernel32.dll.FreeLibrary API的地址,为卸载dll调用作准备
hModule = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
//在notepad.exe进程中开启线程,设置回调函数调用FreeLibrary API进行dll卸载
hThread = CreateRemoteThread(hProcess, NULL, 0,
pThreadProc, me.modBaseAddr,
0, NULL);
WaitForSingleObject(hThread, INFINITE);

CloseHandle(hThread);
CloseHandle(hProcess);
CloseHandle(hSnapshot);

return TRUE;
}


先调用CreateToolhelp32Snapshot API,在前面的分析也调用过,该API描述如下:

1
2
3
4
HANDLE CreateToolhelp32Snapshot(
[in] DWORD dwFlags,
[in] DWORD th32ProcessID
);

Takes a snapshot of the specified processes, as well as the heaps, modules, and threads used by these processes.

对指定的进程拍摄快照,保存进程相关信息的使用情况:诸如堆使用情况、模块载入情况、线程等

CreateToolhelp32Snapshot 第一个参数dwFlags,指明那些信息应该被包括进快照

Value Meaning
TH32CS_SNAPMODULE0x00000008 Includes all modules of the process specified in th32ProcessIDin the snapshot. To enumerate the modules, see Module32First. If the function fails with ERROR_BAD_LENGTH, retry the function until it succeeds.64-bit Windows: Using this flag in a 32-bit process includes the 32-bit modules of the process specified in th32ProcessID, while using it in a 64-bit process includes the 64-bit modules. To include the 32-bit modules of the process specified in th32ProcessID from a 64-bit process, use the TH32CS_SNAPMODULE32 flag.

因此语句:hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);的含义是返回目标进程dwPID载入的所有模块信息的句柄存放到hSnapshot中。

接着EjectDll函数执行下面的代码

1
2
3
4
5
6
7
8
9
10
11
bMore = Module32First(hSnapshot, &me);
for( ; bMore ; bMore = Module32Next(hSnapshot, &me) )
{
//比对载入的dll名称以及路径是否一致,锁定myhack.dll
if( !_tcsicmp((LPCTSTR)me.szModule, szDllName) ||
!_tcsicmp((LPCTSTR)me.szExePath, szDllName) )
{
bFound = TRUE; //匹配到了设置标志位bFound为true
break;
}
}

遍历hSnapshot 句柄中的所有模块,依次放入bMore,进行判断(路径 & dll名称都要准确),看是否是我们将要从目标进程中卸载的dll,贴一下(me变量)MODULEENTRY32结构体,这一步结束me中存放的是待卸载dll的信息

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct tagMODULEENTRY32 {
DWORD dwSize;
DWORD th32ModuleID;
DWORD th32ProcessID;
DWORD GlblcntUsage;
DWORD ProccntUsage;
BYTE *modBaseAddr;
DWORD modBaseSize;
HMODULE hModule;
char szModule[MAX_MODULE_NAME32 + 1];
char szExePath[MAX_PATH];
} MODULEENTRY32;

接下来hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID尝试以PROCESS_ALL_ACCESS权限打开目标进程

获取系统的kernel32.dll中的FreeLibrary API地址,调用CreateRemoteThread,设置回调函数以及传递参数,在线程中调用FreeLibrary 完成对目标进程中目标dll的卸载工作。

回顾

API– CreateToolhelp32Snapshot,对进程在系统中的信息执行快照

1
2
3
4
HANDLE CreateToolhelp32Snapshot(
[in] DWORD dwFlags,
[in] DWORD th32ProcessID
);

文中两次调用中第一个参数dwFlags有所区别:第一次 = TH32CS_SNAPALL,第二次 = TH32CS_SNAPMODULE,获取到的内容也不同。详情查看微软文档API如预期的执行会返回相应的句柄,可以对进行遍历。

第一次调用是为了找到目标进程PID,因此获取到的句柄时系统中所有进程的快照,使用类似如下代码遍历:

1
2
3
4
5
6
7
8
9
10
11
//hSnapShot是CreateToolhelp32Snapshot返回的句柄,其中存放进程快照信息
Process32First(hSnapShot, &pe);
do
{
if(!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))
{
dwPID = pe.th32ProcessID;
break;
}
}
while(Process32Next(hSnapShot, &pe)); //循环比对

第二次调用是在第一次执行获取了目标进程的PID后需要从其载入的DLL中找到待卸载DLL,使用类似如下代码遍历:

1
2
3
4
5
6
7
8
9
10
11
//hSnapshot存放CreateToolhelp32Snapshot返回的句柄,这次是目标进程中载入的所有模块快照信息
bMore = Module32First(hSnapshot, &me);
for( ; bMore ; bMore = Module32Next(hSnapshot, &me) )
{
if( !_tcsicmp((LPCTSTR)me.szModule, szDllName) ||
!_tcsicmp((LPCTSTR)me.szExePath, szDllName) )
{
bFound = TRUE; //匹配到了设置标志位bFound为true
break;
}
}

本节内容主要的内容在于查找目标进程以及目标进程中待卸载的DLL,真正完成卸载的部分并不算有难度。

Author: Victory+
Link: https://cvjark.github.io/2022/05/08/逆核系列No-8-DLL卸载/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.