逆向工程核心原理--DLL注入

键盘消息钩取

KeyHook

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

HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;
HWND g_hWnd = NULL;

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
g_hInstance = hModule;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}


/*
LRESULT为长整形指针
每当需要处理键盘消息时就会调用此函数
参数一:nCode,如果code小于零,则挂钩过程必须将消息传递给CallNextHookEx函数而不进行进一步处理,并且应该返回CallNextHookEx返回的值。
参数二:wParam,生成击键消息的虚拟键代码
参数三:当第31位为0时则按键被按下,当为1时表示按键释放
*/
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
char szPath[MAX_PATH] = { 0, };
char* p = NULL;

if (nCode == 0)
{
/*
*/
if (!(lParam & 0x80000000))
{
GetModuleFileNameA(NULL, szPath, MAX_PATH);
p = strrchr(szPath, '\\');

if (!_stricmp(p + 1, DEF_PROCESS_NAME))
return 1;
}
}
//返回到钩链中的下一个函数
return CallNextHookEx(g_hHook, nCode, wParam, lParam);

}

//判断是否使用c++编写
#ifdef __cplusplus
//使用与C语言一致的编译过程
extern "C" {
#endif
/*
__declspec(dllexport)将DLL中的函数导出给其他应用程序使用
*/
__declspec(dllexport) void HookStart()
{
g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
}
__declspec(dllexport) void HookStop()
{
if (g_hHook)
{
UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
}
}
#ifdef __cplusplus
}
#endif // __cplusplus

HookMain

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

#define DEF_DLL_NAME "KeyHook.dll"
#define DEF_HOOKSTART "HookStart"
#define DEF_HOOKSTOP "HookStop"

typedef void (*PFN_HOOKSTART)();
typedef void (*PFN_HOOKSTOP)();

void main()
{
HMODULE hDll = NULL;
PFN_HOOKSTART HookStart = NULL;
PFN_HOOKSTOP HookStop = NULL;
char ch = 0;

//加载KeyHook.dll
hDll = LoadLibraryA(DEF_DLL_NAME);

//获取开始Hook的函数地址
HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART);
//获取停止Hook的函数地址
HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP);

//Hook开始
HookStart();
printf("press 'q' to quit!\n");
while (_getch() != 'q');
//Hook停止
HookStop();

//卸载DLL
FreeLibrary(hDll);

}

DLL注入

DLL注入指的是向运行中的其他进程强制插入特定的DLL文件。

DLL注入的实现方法

  • 创建远程线程(CreateRemoteThread()API)

  • 使用注册表(AppInit_DLLs值)

  • 消息钩取(SetWindowsHookEx()API)

创建远程线程

InjectDll

InjectDll需要传入需要注入的进程号,在该进程的空间内开辟一段区域用于创建线程运行LoadLibrary()方法载入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
#include<windows.h>
#include<tchar.h>

/*
* LPCTSTR含义
* L代表long长整型
* P代表指针
* C代表不可改变
* T代表char,若定义了UNICODE则代表wchar_t
* STR代表字符串
*/
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath)
{
HANDLE hProcess = NULL, hThread = NULL;
HMODULE hMod = NULL;
LPVOID pRemoteBuf = NULL;
DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR);

//函数指针
LPTHREAD_START_ROUTINE pThreadProc;

/*
* 打开现有的本地进程对象
* HANDLE OpenProcess(
* [in] DWORD dwDesiredAccess,对进程对象访问的权限 PROCESS_ALL_ACCESS即所有权限
* [in] BOOL bInheritHandle,进程是否继承句柄,FALSE为否
* [in] DWORD dwProcessId,进程号
* 返回值,打开进程的句柄
* );
*/
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
{
_tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError);
return FALSE;
}
/*
* 在指定进程的虚拟地址空间内保留、提交或更改内存区域的状态。
* LPVOID VirtualAllocEx(
* [in] HANDLE hProcess,进程句柄,该函数在该进程的虚拟地址空间内分配内存
* [in, optional] LPVOID lpAddress,为要分配的页面区域指定所需起始地址的指针
* [in] SIZE_T dwSize,要分配的内存区域的大小,以字节为单位
* [in] DWORD flAllocationType,内存分配的类型,MEM_COMMIT为指定的保留内存页面分配内存费用
* [in] DWORD flProtect,要分配的页面区域的内存保护,PAGE_READWRITE,启用对已提交页面区域的执行、只读或读/写访问。
* );
* 返回值,如果函数成功,则返回值非零。如果函数失败,则返回值为 0(零)
*/
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);

/*
* 将数据写入指定进程中的内存区域。要写入的整个区域必须可访问,否则操作将失败。
* BOOL WriteProcessMemory(
* [in] HANDLE hProcess,要修改的进程内存的句柄,句柄必须具有对进程的 PROCESS_VM_WRITE 和 PROCESS_VM_OPERATION 访问权限。
* [in] LPVOID lpBaseAddress,指向要写入数据的指定进程中的起始地址
* [in] LPCVOID lpBuffer,指向缓冲区的指针,该缓冲区包含要写入指定进程地址空间的数据
* [in] SIZE_T nSize,要写入指定进程的字节数
* [out] SIZE_T *lpNumberOfBytesWritten,指向变量的指针,该变量接收传输到指定进程的字节数,NULL则忽略该参数
* );
*/
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);

hMod = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");

/*
* 创建在另一个进程的虚拟地址空间中运行的线程
* HANDLE CreateRemoteThread(
* [in] HANDLE hProcess,要在其中创建线程的进程的句柄
* [in] LPSECURITY_ATTRIBUTES lpThreadAttributes,指向 SECURITY_ATTRIBUTES结构的指针,该结构指定新线程的安全描述符并确定子进程是否可以继承返回的句柄
* [in] SIZE_T dwStackSize,堆栈的初始大小,以字节为单位
* [in] LPTHREAD_START_ROUTINE lpStartAddress,指向要由线程执行的LPTHREAD_START_ROUTINE 类型的应用程序定义函数的指针
* [in] LPVOID lpParameter,指向要传递给线程函数的变量的指针
* [in] DWORD dwCreationFlags,控制线程创建的标志,0表示线程在创建后立即运行
* [out] LPDWORD lpThreadId,指向接收线程标识符的变量的指针
* );
* 如果函数成功,则返回值是新线程的句柄
*/
/*创建线程执行LoadLibraryW(DllPath)*/
hThread = CreateRemoteThread(
hProcess,
NULL,
0,
pThreadProc,
pRemoteBuf,
0,
NULL
);

/*等到线程执行操作*/
WaitForSingleObject(hThread, INFINITE);

CloseHandle(hThread);
CloseHandle(hProcess);

return TRUE;
}

int _tmain(int argc, TCHAR* argv[])
{
if (argc != 3)
{
_tprintf(L"USAG : %s pid dll_path\n", argv[0]);
return 1;
}

if (InjectDll((DWORD)_tstol(argv[1]), argv[2]))
_tprintf(L"InjectDll(\"s\") success!!!\n", argv[2]);
else
_tprintf(L"InjectDll(\"%s\") failed!!!\n", argv[2]);
return 0;
}

myhack

myhack.dll文件被注入时会在dll文件的目录下通过URLDownloadToFile方法下载指定页面

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

//这条语句必须加上否则调用URLDownloadToFile方法会报错
#pragma comment(lib,"urlmon.lib")

#define DEF_URL (L"https://www.baidu.com")
#define DEF_FILE_NAME (L"index.html")

HMODULE g_hMod = NULL;

DWORD WINAPI ThreadProc(LPVOID lParam)
{
TCHAR szPath[MAX_PATH] = { 0, };
if (!GetModuleFileName(g_hMod, szPath, MAX_PATH))
return FALSE;

TCHAR* p = _tcsrchr(szPath, '\\');
if (!p)
return FALSE;

_tcscpy_s(p + 1, MAX_PATH, DEF_FILE_NAME);
URLDownloadToFile(NULL,DEF_URL,szPath,0,NULL);
return 0;
}

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
HANDLE hThread = NULL;
g_hMod = hModule;
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
OutputDebugString(L"myhack.dll Injection!!!");
/*
* HANDLE CreateThread(
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,是否能被子进程继承,NULL为不能
[in] SIZE_T dwStackSize,初始堆栈大小,0表示默认值
[in] LPTHREAD_START_ROUTINE lpStartAddress,由线程执行的函数的指针
[in, optional] __drv_aliasesMem LPVOID lpParameter,要传递给线程的变量的指针
[in] DWORD dwCreationFlags,控制线程创建的标志,0代表线程在创建后立即执行
[out, optional] LPDWORD lpThreadId,指向接收线程标识符的变量的指针。NULL则不返回线程标识符
);
*/
hThread = CreateThread(NULL, 0, ThreadProc, NULL,0, NULL);
CloseHandle(hThread);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

使用注册表

Windows操作系统的注册表中默认提供了AppInt_DLLsLoadAppInit_DLLs两个注册表项。

image-20220127143759703

myhack2

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

#define DEF_CMD L""
#define DEF_ADDR L"https://www.baidu.com"
#define DEF_DST_PROC L"notepad.exe"


BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
TCHAR szCmd[MAX_PATH] = { 0, };
TCHAR szPath[MAX_PATH] = { 0, };
TCHAR* p = NULL;

//指定创建时进程的主窗口的窗口站、桌面、标准句柄和外观
STARTUPINFO si = { 0, };

//包含有关新创建的进程及其主线程的信息
PROCESS_INFORMATION pi = { 0, };

si.cb = sizeof(STARTUPINFO); //结构的大小,以字节为单位
si.dwFlags = STARTF_USESHOWWINDOW; //一个位域,用于确定进程创建窗口时是否使用某些 STARTUPINFO成员,STARTF_USESHOWWINDOW,wShowWindow成员包含附加信息
si.wShowWindow = SW_HIDE;//隐藏窗口并激活另一个窗口。

switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
if (!GetModuleFileName(NULL, szPath, MAX_PATH))
break;
if (!(p = _tcsrchr(szPath, '\\')))
break;
if (_tcsicmp(p + 1, DEF_DST_PROC))
break;
wsprintf(szCmd, L"%s %s", DEF_CMD,DEF_ADDR);
/*
* 创建一个新进程及其主线程
* BOOL CreateProcessA(
* [in, optional] LPCSTR lpApplicationName, 要执行的模块的名称。lpApplicationName参数可以是NULL。在这种情况下,模块名称必须是lpCommandLine字符串中第一个以空格分隔的标记。
* [in, out, optional] LPSTR lpCommandLine,要执行的命令行。
* [in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes,指向 SECURITY_ATTRIBUTES结构的指针,该结构确定返回的新进程对象的句柄是否可以被子进程继承。如果lpProcessAttributes为NULL,则不能继承句柄。
* [in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,指向 SECURITY_ATTRIBUTES结构的指针,该结构确定返回的新线程对象的句柄是否可以被子进程继承。如果lpThreadAttributes为 NULL,则不能继承句柄。
* [in] BOOL bInheritHandles,如果此参数为 TRUE,则调用进程中的每个可继承句柄都由新进程继承。如果参数为 FALSE,则不继承句柄
* [in] DWORD dwCreationFlags,控制优先级和进程创建的标志
* [in, optional] LPVOID lpEnvironment,指向新进程的环境块的指针。如果此参数为NULL,则新进程使用调用进程的环境。
* [in, optional] LPCSTR lpCurrentDirectory,进程当前目录的完整路径
* [in] LPSTARTUPINFOA lpStartupInfo,指向 STARTUPINFO或STARTUPINFOEX结构的指针
* [out] LPPROCESS_INFORMATION lpProcessInformation,指向 PROCESS_INFORMATION结构的指针,该结构接收有关新进程的标识信息
* );
*/
/*创建一个进程执行cmd的命令*/
if (!CreateProcess(NULL, (LPTSTR)(LPCTSTR)szCmd,
NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi))
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

消息钩取

  • SetWindowsHookEx()方法,示例如键盘消息钩取。

DLL卸载

DLL卸载(DLL Ejection)是将强制插入进程的DLL弹出的一种技术。

EjectDll

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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
#include<windows.h>
#include<TlHelp32.h>
#include<tchar.h>

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

DWORD FindProcessID(LPCTSTR szProcessName)
{
DWORD dwPID = 0xFFFFFFFF;
HANDLE hSnapShot = INVALID_HANDLE_VALUE;
//需要导入tlhelp32.h
//描述拍摄快照时驻留在系统地址空间中的进程列表中的条目
/*
* 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;
*/
PROCESSENTRY32 pe;

//获取系统快照
pe.dwSize = sizeof(PROCESSENTRY32);
/*
* 拍摄指定进程的快照,以及这些进程使用的堆、模块和线程
* HANDLE CreateToolhelp32Snapshot(
* [in] DWORD dwFlags, 要包含在快照中的系统部分,TH32CS_SNAPALL包括系统中的所有进程和线程,以及th32ProcessID中指定的进程的堆和模块
* [in] DWORD th32ProcessID,要包含在快照中的进程的进程标识符。此参数可以为零以指示当前进程
* );
*/

//将所有进程保存在hSnapShot
hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);

//查找进程
/*
* 检索有关系统快照中遇到的第一个进程的信息。
* BOOL Process32First(
* [in] HANDLE hSnapshot,从先前调用CreateToolhelp32Snapshot函数返回的快照句柄
* [in, out] LPPROCESSENTRY32 lppe,指向 PROCESSENTRY32结构的指针
* );
*/
Process32First(hSnapShot, &pe);

do
{
//pe.szExeFile 进程的可执行文件的名称
if (!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))
{
dwPID = pe.th32ProcessID;
break;
}
} while (Process32Next(hSnapShot, &pe));

CloseHandle(hSnapShot);

return dwPID;
}

BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
{
/*
* TOKEN_PRIVILEGES结构包含有关访问令牌的一组权限的信息
* typedef struct _TOKEN_PRIVILEGES {
* DWORD PrivilegeCount; Privileges数组中的条目数
* LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY];指定LUID_AND_ATTRIBUTES结构的数组 。每个结构都包含一个特权的 LUID和属性
* } TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;
*/
TOKEN_PRIVILEGES tp;
HANDLE hToken;
/*
* 描述适配器的本地标识符
* typedef struct _LUID {
* DWORD LowPart; 指定一个包含 id 的无符号小数的 DWORD。
* LONG HighPart;指定一个包含 id 的带符号高数的 LONG。
* } LUID, *PLUID;
*/
LUID luid;

/*
* OpenProcessToken函数打开与进程关联的访问令牌
* BOOL OpenProcessToken(
* [in] HANDLE ProcessHandle,打开其访问令牌的进程的句柄。该进程必须具有 PROCESS_QUERY_INFORMATION 访问权限
* [in] DWORD DesiredAccess,指定一个访问掩码,该掩码指定对访问令牌的请求访问类型
* [out] PHANDLE TokenHandle,指向句柄的指针,该句柄在函数返回时标识新打开的访问令牌
* );
*
* 检索当前进程的伪句柄。
* HANDLE GetCurrentProcess();
*/
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
_tprintf(L"OpenProcessToken error: %u\n", GetLastError());
return FALSE;
}

/*
* LookupPrivilegeValue函数检索指定系统上使用的本地唯一标识符 (LUID),以在本地表示指定的特权名称
* BOOL LookupPrivilegeValueA(
* [in, optional] LPCSTR lpSystemName,指向以空字符结尾的字符串的指针,该字符串指定在其上检索特权名称的系统名称。如果指定了空字符串,该函数将尝试在本地系统上查找权限名称。
* [in] LPCSTR lpName,指向以 null 结尾的字符串的指针,该字符串指定权限的名称
* [out] PLUID lpLuid,一个指向变量的指针,该变量接收由lpSystemName参数指定的系统上的权限已知的 LUID
* );
*/
if (!LookupPrivilegeValue(NULL, lpszPrivilege, &luid))
{
_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;

/*
* AdjustTokenPrivileges函数启用或禁用指定访问令牌中的权限
* BOOL AdjustTokenPrivileges(
* [in] HANDLE TokenHandle,包含要修改的权限的访问令牌的句柄
* [in] BOOL DisableAllPrivileges,指定函数是否禁用所有令牌的权限。如果此值为TRUE,该函数将禁用所有权限并忽略NewState参数。如果为FALSE ,则函数根据NewState参数指向的信息修改权限
* [in, optional] PTOKEN_PRIVILEGES NewState,指向 TOKEN_PRIVILEGES结构的指针,该结构指定特权数组及其属性
* [in] DWORD BufferLength,指定PreviousState参数指向的缓冲区的大小(以字节为单位)
* [out, optional] PTOKEN_PRIVILEGES PreviousState,一个指向缓冲区的指针,函数用TOKEN_PRIVILEGES结构填充该结构,该结构包含函数修改的任何特权的先前状态
* [out, optional] PDWORD ReturnLength,指向变量的指针,该变量接收由PreviousState参数指向的缓冲区的所需大小(以字节为单位),如果PreviousState为NULL ,则此参数可以为NULL。
* );
*/
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;
}

BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName)
{
BOOL bMore = FALSE, bFound = FALSE;
HANDLE hSnapshot, hProcess, hThread;
HMODULE hModule = NULL;

/*
* 描述属于指定进程的模块列表中的条目
* typedef struct tagMODULEENTRY32 {
* DWORD dwSize;结构的大小,以字节为单位
* DWORD th32ModuleID;该成员不再使用,并且始终设置为 1
* DWORD th32ProcessID;要检查其模块的进程的标识符
* DWORD GlblcntUsage;模块的加载计数,一般没有意义,通常等于 0xFFFF
* DWORD ProccntUsage;模块的加载计数(与GlblcntUsage相同),通常没有意义,通常等于 0xFFFF
* BYTE *modBaseAddr;拥有进程上下文中模块的基地址
* DWORD modBaseSize;模块的大小,以字节为单位
* HMODULE hModule;拥有进程上下文中的模块句柄
* char szModule[MAX_MODULE_NAME32 + 1];模块名称
* char szExePath[MAX_PATH];模块路径
* } MODULEENTRY32;
*/
MODULEENTRY32 me = { sizeof(me) };

LPTHREAD_START_ROUTINE pThreadProc;

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;
}
}
if (!bFound)
{
CloseHandle(hSnapshot);
return FALSE;
}

if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
{
_tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
return FALSE;
}
hModule = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");

hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL);
WaitForSingleObject(hThread, INFINITE);

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

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

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);

//将进程的权限改为调试
if (!SetPrivilege(SE_DEBUG_NAME, TRUE))
return 1;

//将myhack.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;
}

通过修改PE加载DLL

myhack3

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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include<stdio.h>
#include<tchar.h>
#include<ShlObj.h>
#include<WinInet.h>

#pragma comment(lib,"Wininet.lib")

#define DEF_BUF_SIZE (4096)
#define DEF_URL L"https://www.baidu.com"
#define DEF_INDEX_FILE L"index.html"

HWND g_hWnd = NULL;
BOOL DownloadURL(LPCTSTR szURL, LPCTSTR szFile)
{
BOOL bRet = FALSE;
//需要导入wininet.h
//typedef LPVOID HINTERNET;
HINTERNET hInternet = NULL, hURL = NULL;
BYTE pBuf[DEF_BUF_SIZE] = { 0, };
DWORD dwBytesRead = 0;
FILE* pFile = NULL;

//typedef int errno_t;
errno_t err = 0;

/*
* 初始化应用程序对 WinINet 函数的使用
* HINTERNET InternetOpenA(
* [in] LPCSTR lpszAgent,指向以null结尾的字符串的指针,该字符串指定调用 WinINet 函数的应用程序或实体的名称。此名称用作 HTTP 协议中的用户代理。
* [in] DWORD dwAccessType,所需的访问类型。INTERNET_OPEN_TYPE_PRECONFIG,从注册表中检索代理或直接配置。
* [in] LPCSTR lpszProxy,指向以null结尾的字符串的指针,该字符串指定代理服务器的名称。如果 dwAccessType未设置为 INTERNET_OPEN_TYPE_PROXY,此参数被忽略,应为NULL
* LPCTSTR lpszProxyBypass,指向以空字符结尾的字符串的长指针,该字符串包含主机名或 IP 地址或两者的可选列表,不应通过代理进行路由。如果此参数为 NULL,则该函数从注册表中读取绕过列表。
* [in] DWORD dwFlags,指定影响函数行为的各种选项。
* );
* 返回值:应用程序传递给后续 Win32 Internet 函数的有效句柄表示成功
*/
hInternet = InternetOpen(L"ReverseCore",
INTERNET_OPEN_TYPE_PRECONFIG,
NULL,
NULL,
0);

if (hInternet == NULL)
{
OutputDebugString(L"InternetOpen() failed!");
return FALSE;
}

/*
* 打开由完整的 FTP 或 HTTP URL 指定的资源
* HINTERNET InternetOpenUrlA(
* [in] HINTERNET hInternet, 当前 Internet 会话的句柄
* [in] LPCSTR lpszUrl,指向以null结尾的字符串变量的指针,该变量指定要开始读取的 URL
* [in] LPCSTR lpszHeaders,指向以null结尾的字符串的指针,该字符串指定要发送到 HTTP 服务器的标头
* [in] DWORD dwHeadersLength,附加标头的大小,以TCHARs为单位。
* [in] DWORD dwFlags,INTERNET_FLAG_RELOAD,强制从源服务器下载请求的文件、对象或目录列表,而不是从缓存中。
* [in] DWORD_PTR dwContext,一个指向变量的指针,该变量指定应用程序定义的值,该值连同返回的句柄一起传递给任何回调函数。
* );
*/
hURL = InternetOpenUrl(
hInternet,
szURL,
NULL,
0,
INTERNET_FLAG_RELOAD,
0);

if (hURL == NULL)
{
OutputDebugString(L"InternetOpenUrl() failed!");
goto _DownloadURL_EXIT;
}

if (_tfopen_s(&pFile, szFile, L"wt"))
{
OutputDebugString(L"fopen() failed!");
goto _DownloadURL_EXIT;
}

/*
* 从InternetOpenUrl、 FtpOpenFile或 HttpOpenRequest函数打开的句柄中读取数据
* BOOL InternetReadFile(
* [in] HINTERNET hFile,从先前调用 InternetOpenUrl、 FtpOpenFile或 HttpOpenRequest返回的句柄
* [out] LPVOID lpBuffer,指向接收数据的缓冲区的指针
* [in] DWORD dwNumberOfBytesToRead,要读取的字节数
* [out] LPDWORD lpdwNumberOfBytesRead,指向接收读取字节数的变量的指针
* );
*/

//将网络资源文件写入本地文件中
while (InternetReadFile(hURL, pBuf, DEF_BUF_SIZE, &dwBytesRead))
{
if (!dwBytesRead)
break;
/*
* 把 ptr 所指向的数组中的数据写入到给定流 stream 中
* size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
* ptr -- 这是指向要被写入的元素数组的指针
* size -- 这是要被写入的每个元素的大小,以字节为单位
* nmemb -- 这是元素的个数,每个元素的大小为 size 字节
* stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流
*/
fwrite(pBuf, dwBytesRead, 1, pFile);
}

_DownloadURL_EXIT:
if (pFile)
fclose(pFile);
if (hURL)
InternetCloseHandle(hURL);
if (hInternet)
InternetCloseHandle(hInternet);
return bRet;
}

/*
* 与EnumWindows或EnumDesktopWindows函数一起使用的应用程序定义的回调函数
* BOOL CALLBACK EnumWindowsProc(
* _In_ HWND   hwnd,顶级窗口的句柄
* _In_ LPARAM lParam,lParam [in] EnumWindows或EnumDesktopWindows中给出的应用程序定义的值
* );
*/
BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam)
{
DWORD dwPID = 0;
/*
* 检索创建指定窗口的线程的标识符,以及(可选)创建窗口的进程的标识符
* DWORD GetWindowThreadProcessId(
* [in] HWND hWnd,窗口的句柄。
* [out, optional] LPDWORD lpdwProcessId,指向接收进程标识符的变量的指针
* );
*/
GetWindowThreadProcessId(hWnd, &dwPID);

if (dwPID == (DWORD)lParam)
{
g_hWnd = hWnd;
return FALSE;
}
return TRUE;
}

HWND GetWindowHandleFromPID(DWORD dwPID)
{
/*
* 通过将每个窗口的句柄依次传递给应用程序定义的回调函数来枚举屏幕上的所有顶级窗口。EnumWindows一直持续到枚举最后一个顶级窗口或回调函数返回FALSE
* BOOL EnumWindows(
* [in] WNDENUMPROC lpEnumFunc,指向应用程序定义的回调函数的指针
* [in] LPARAM lParam,要传递给回调函数的应用程序定义的值
* );
*/
EnumWindows(EnumWindowsProc, dwPID);
return g_hWnd;
}

//需要#include<shlobj.h>
BOOL DropFile(LPCTSTR wcsFile)
{
HWND hWnd = NULL;
DWORD dwBufSize = 0;
BYTE* pBuf = NULL;
/*
* 定义CF_HDROP剪贴板格式
* typedef struct _DROPFILES {
* DWORD pFiles;文件列表从此结构开头的偏移量,以字节为单位
* POINT pt;落点。坐标取决于fNC
* BOOL fNC;非客户区标志。如果此成员为TRUE,则pt指定窗口非客户区中某个点的屏幕坐标。如果它是FALSE,pt指定客户区域中一个点的客户坐标
* BOOL fWide;指示文件是否包含 ANSI 或 Unicode 字符的值。如果该值为零,则文件包含 ANSI 字符。否则,它包含 Unicode 字符
* } DROPFILES, *LPDROPFILES;
*/
DROPFILES* pDrop = NULL;
char szFile[MAX_PATH] = { 0, };
HANDLE hMem = 0;

/*
* 将 UTF-16(宽字符)字符串映射到新字符串
* int WideCharToMultiByte(
* [in] UINT CodePage,用于执行转换的代码页,CP_ACP,系统默认的 Windows ANSI 代码页。
* [in] DWORD dwFlags,指示转换类型的标志
* [in] _In_NLS_string_(cchWideChar)LPCWCH lpWideCharStr,指向要转换的 Unicode 字符串的指针
* [in] int cchWideChar,lpWideCharStr指示的字符串的大小(以字符为单位) 。或者,如果字符串以 null 结尾,则可以将此参数设置为 -1
* [out, optional] LPSTR lpMultiByteStr,指向接收转换后字符串的缓冲区的指针
* [in] int cbMultiByte,lpMultiByteStr指示的缓冲区的大小(以字节为单位)
* [in, optional] LPCCH lpDefaultChar,指向在指定代码页中无法表示字符时要使用的字符的指针。如果函数要使用系统默认值,则应用程序将此参数设置为NULL
* [out, optional] LPBOOL lpUsedDefaultChar,指向一个标志的指针,该标志指示函数是否在转换中使用了默认字符。如果源字符串中的一个或多个字符不能在指定的代码页中表示,则该标志设置为TRUE 。否则,标志设置为FALSE。该参数可以设置为NULL。
* );
*/
WideCharToMultiByte(CP_ACP, 0, wcsFile, -1, szFile, MAX_PATH, NULL, NULL);

/*
* 锁定一个全局内存对象并返回一个指向对象内存块第一个字节的指针
* LPVOID GlobalLock(
* [in] HGLOBAL hMem,全局内存对象的句柄
* );
*/
pBuf = (LPBYTE)GlobalLock(hMem);
pDrop = (DROPFILES*)pBuf;
pDrop->pFiles = sizeof(DROPFILES);
strcpy_s((char*)(pBuf + sizeof(DROPFILES)), strlen(szFile) + 1, szFile);

GlobalUnlock(hMem);

if (!(hWnd = GetWindowHandleFromPID(GetCurrentProcessId())))
{
OutputDebugString(L"GetWndHandleFromPID() failed!!!");
return FALSE;
}

/*
* 在与创建指定窗口的线程关联的消息队列中放置(发布)一条消息,并在不等待线程处理消息的情况下返回。
* BOOL PostMessageA(
* [in, optional] HWND hWnd,一个窗口句柄,其窗口过程将接收消息
* [in] UINT Msg,要发布的消息
* [in] WPARAM wParam,其他特定于消息的信息
* [in] LPARAM lParam,其他特定于消息的信息
* );
*/
PostMessage(hWnd, WM_DROPFILES, (WPARAM)pBuf, NULL);
return TRUE;

}
DWORD WINAPI ThreadProc(LPVOID lParam)
{
TCHAR szPath[MAX_PATH] = { 0, };
TCHAR* p = NULL;

GetModuleFileName(NULL, szPath, sizeof(szPath));

if (p = _tcsrchr(szPath, L'\\'))
{
_tcscpy_s(p + 1, wcslen(DEF_INDEX_FILE) + 1, DEF_INDEX_FILE);
if (DownloadURL(DEF_URL, szPath))
{
DropFile(szPath);
}
return 0;
}
}

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
CloseHandle(CreateThread(NULL, 0, ThreadProc, NULL,0, NULL));
break;
}
return TRUE;
}



#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) void dummy()
{
return;
}
#ifdef __cplusplus
}
#endif

修改TextView.exe文件

IMAGE_IMPORT_DESCRIPTOR

1
2
3
4
5
6
7
8
9
10
11
typedef struct _IMAGE_IMPORT_DESCRIPTOR
{
uion{
DWORD Characteristics;
DWORD OriginalFirstThunk;//指向输入名称表
};
DWORD TimeDateStamp;//时间标志
DWORD ForwarderChain;//一般为0,在程序引用一个DLL中的API,而这个API又在引用其他DLL的API时使用
DWORD Name;//DLL名字的指针
DWORD FirstThunk;//包含输入地址表(IAT)的RVA
}IMAGE_IMPORT_DESCRIPTOR;

移动IDT

若文件中的IDT没有足够的空间新增IID时就需要将IDT移动。可以采用下列三种方式移动

  • 查找文件中的空白区域
  • 增加文件最后一节区的大小
  • 在文件末尾添加新节区

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