April 7, 2022

关于逆向工程核心原理-DLL卸载

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

DLL卸载是将强制插入进程的DLL弹出的一种技术,工作原理与使用CreateRemoteThread API进行DLL注入的原理想相同

DLL卸载工作原理

注入时工作原理:驱使目标进程调用LoadLibrary API

DLL卸载同理:驱使目标进程调用FreeLibrary API

PS: 每个内核对象都有拥有一个引用计数,调用十次LoadLibrary(“Pz.dll”),Pz.dll的引用计数就变为10,卸载Pz.dll需要调用十次FreeLibrary

因此卸载DLL时候要充分考虑引用次数这个因素

做下实验,首先这边是注进去了

image-20220407160345092

然后就被干掉了

image-20220407160536422

开审!

EjectDLL.cpp

先来学习下关键的几个API

1. CreateToolhelp32Snapshot

CreateToolhelp32Snapshot

1
2
3
4
// 获取系统快照
pe.dwSize = sizeof( PROCESSENTRY32 );
// 包括系统中的所有进程和线程 所以这边可以理解成先获取了系统快照
hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL );

image-20220407162827064

1
2
3
// dwPID = notepad 进程ID
// 使用 TH32CS_SNAPMODULE 参数,获取加载到notepad进程的DLL名称
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);

这边是获取了指定进程的所有模块(也就是notepad的DLL)

image-20220407163012333

2. Process32xx

检索有关系统快照的第一个进程信息

Process32First

检索有关系统快照记录的下一个进程的信息

Process32Next

这两个配合使用就可以找目标进程的PID

(注意这边创建快照句柄用的是TH32CS_SNAPALL)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 这里返回的是系统快照句柄 
hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL );

// 遍历查找目标进程
Process32First(hSnapShot, &pe);
do
{
// 反复比较是否与目标进程相同
if(!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))
{
dwPID = pe.th32ProcessID;
break;
}
}
while(Process32Next(hSnapShot, &pe));

3. Module32xx

检索有关与进程关联的第一个模块的信息

Module32First

检索有关与进程或线程关联的下一个模块的的信息

Module32Next

这两个配合使用就可以找到进程快照的目标DLL

(注意这边创建快照句柄用的是TH32CS_SNAPMODULE)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// dwPID = notepad 进程ID
// 使用 TH32CS_SNAPMODULE 参数,获取加载到notepad进程的DLL名称
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);

bMore = Module32First(hSnapshot, &me);
for( ; bMore ; bMore = Module32Next(hSnapshot, &me) )
{
if( !_tcsicmp((LPCTSTR)me.szModule, szDllName) ||
!_tcsicmp((LPCTSTR)me.szExePath, szDllName) )
{
bFound = TRUE;
break;
}
}

4. GetProcAddress

1
2
3
4
5
6
7
// 先获取DLL模块句柄

hModule = GetModuleHandle(L"kernel32.dll");

// 再从句柄忠获取FreeLibrary API地址

pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");

复习一下上次的,和上次同理

image-20220407165642348

5. CreatRemoteThread

函数简介:使用CreateRemoteThreadEx函数创建在另一个进程的虚拟地址空间中运行的线程

一句话

令hProcess(Notepad.exe)创建一个线程pThreadProc(FreeLibrary)参数为我们强制注入的MyHack.dll

(突然发现上次写的有点小问题)

1
2
3
4
5
6
7
// hProcess为目标进程
// pThreadProc为要执行的API地址
// me.modBaseAddr为要卸载的DLL的加载地址
hThread = CreateRemoteThread(hProcess, NULL, 0,
pThreadProc, me.modBaseAddr,
0, NULL);
WaitForSingleObject(hThread, INFINITE);

于是再来看看这简单的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
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// 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")

// 查看设置权限
BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
{
TOKEN_PRIVILEGES tp;
HANDLE hToken;
LUID luid;

// 查看设置权限
if( !OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&hToken) )
{
_tprintf(L"OpenProcessToken error: %u\n", GetLastError());
return FALSE;
}

// 查看本地系统的权限值
if( !LookupPrivilegeValue(NULL, // lookup privilege on local system
lpszPrivilege, // privilege to lookup
&luid) ) // receives LUID of privilege
{
_tprintf(L"LookupPrivilegeValue error: %u\n", GetLastError() );
return FALSE;
}

tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if( bEnablePrivilege )
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
tp.Privileges[0].Attributes = 0;

// 启动特权或禁止所有特权
if( !AdjustTokenPrivileges(hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES) NULL,
(PDWORD) NULL) )
{
_tprintf(L"AdjustTokenPrivileges error: %u\n", GetLastError() );
return FALSE;
}

if( GetLastError() == ERROR_NOT_ALL_ASSIGNED )
{
_tprintf(L"The token does not have the specified privilege. \n");
return FALSE;
}

return TRUE;
}

// 查找目标进程ID
DWORD FindProcessID(LPCTSTR szProcessName)
{
DWORD dwPID = 0xFFFFFFFF;
HANDLE hSnapShot = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe;

// 获取系统快照
pe.dwSize = sizeof( PROCESSENTRY32 );
// 包括系统中的所有进程和线程 所以这边可以理解成先获取了系统快照
hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL );

// 遍历查找目标进程
Process32First(hSnapShot, &pe);
do
{
// 反复比较是否与目标进程相同
if(!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))
{
dwPID = pe.th32ProcessID;
break;
}
}
while(Process32Next(hSnapShot, &pe));

CloseHandle(hSnapShot);

return dwPID;
}

// 卸载DLL
BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName)
{
BOOL bMore = FALSE, bFound = FALSE;
HANDLE hSnapshot, hProcess, hThread;
HMODULE hModule = NULL;
MODULEENTRY32 me = { sizeof(me) };
LPTHREAD_START_ROUTINE pThreadProc;

// dwPID = notepad 进程ID
// 使用 TH32CS_SNAPMODULE 参数,获取加载到notepad进程的DLL名称
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);

bMore = Module32First(hSnapshot, &me);
for( ; bMore ; bMore = Module32Next(hSnapshot, &me) )
{
// 为什么路径和名称都和名称比对?
if( !_tcsicmp((LPCTSTR)me.szModule, szDllName) ||
!_tcsicmp((LPCTSTR)me.szExePath, szDllName) )
{
bFound = TRUE;
break;
}
}

// 没找到就G
if( !bFound )
{
CloseHandle(hSnapshot);
return FALSE;
}

// 日常返回目标进程句柄 PROCESS_ALL_ACCESS并获取所有权限
if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
{
_tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
return FALSE;
}

// 先获取DLL模块句柄
hModule = GetModuleHandle(L"kernel32.dll");
// 再从句柄忠获取FreeLibrary API地址
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");

// hProcess为目标进程
// pThreadProc为要执行的API地址
// me.modBaseAddr为要卸载的DLL的加载地址
// 令hProcess(Notepad.exe)创建一个线程pThreadProc(FreeLibrary)参数为我们强制注入的MyHack.dll
hThread = CreateRemoteThread(hProcess, NULL, 0,
pThreadProc, me.modBaseAddr,
0, NULL);
WaitForSingleObject(hThread, INFINITE);

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

return TRUE;
}

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

// find process
dwPID = FindProcessID(DEF_PROC_NAME);
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) )
_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;
}

也就是查找就进程,再找到指定模块(DLL),再创建远程卸载即可!

(学习病毒分析去了 – 4.7 PM 05:09)

DASCTF X SU
🍬
HFCTF2022
🍪

About this Post

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