逆向工程核心原理笔记-2

0x00 windows消息钩取

钩子:偷看或截取信息时所用的手段或工具。有一个例子非常形象:假设有一个非常重要的军事设施,其外围设置了3层岗哨以进行保护。外部人员若想进入,需要经过3层岗哨复杂的检查程序。若间谍在通往该军事设施的道路上私设一个岗哨,经过该岗哨的人员未起疑心,通过时履行同样的检查程序,那么间谍就可以坐享其成,轻松获取来往该岗哨的所有信息。

消息钩子。Windows操作系统向用户提供GUI(Graphic User Interface,图形用户界面),它以事件驱动(Event Driven)方式工作。发生此类事件时,OS会把事先定义好的消息发送给相应的应用程序,应用程序分析收到的信息后执行相应动作。也就是说,敲击键盘时,消息会从OS移动到应用程序。消息钩子就在此间偷看这些信息。

 Windows消息处理流如下所示:

1
2
3
1. 发生键盘输入事件时,WM_KEYDOWN消息被添加到[OS message queue]
2. OS判断哪个应用程序中发生了事件,然后从[OS message queue]取出消息,添加到相应应用程序[applicationmessage queue]中
3. 应用程序监视自身的[application message queue],发现新添加的WM_KEYDOWN消息后,调用相应的事件处理程序处理

 消息钩子示意图如下:

image-20230327154626414

 如上所示,钩链中的钩子会比应用程序先看到相应消息。在钩子函数内部,除了查看消息之外,还可以修改消息。钩链指的是:可以设置多个相同的消息钩子,并按照设置顺序依次调用这些钩子。相应的工具是SPY++(以前用过),它可以查看操作系统中来往的所有消息。

 使用SetWindowsHookEx() API可以轻松实现消息钩子,其定义如下所示:

image-20230327155345980

钩子过程(hook procedure)是由操作系统调用的回调函数。安装消息钩子时,钩子过程需要存在于某个DLL内部,且该DLL的示例句柄(instance handle)即是hMod。若dwThreadID被设置为0,则安装的钩子为全局钩子(Global Hook),它会影响到所有进程。

什么是回调函数?回调函数是指以参数的形式传递给其它代码的可执行代码,通常用于在某个事件发生时或某个任务完成后执行特定的操作。回调函数可以提高代码的灵活性和解耦性。

 下面给一个键盘钩子的例子,不理解的直接可以看例子:

image-20230327160041171

KeyHook.dll文件是一个含有钩子过程(KeyboardProc)的DLL文件。HookMain.exe是最先加载KeyHook.dll并安装键盘钩子的程序。HookMain.exe加载KeyHook.dll文件后使用SetWindowsHookEx()安装键盘钩子(KeyboardProc)。若其他进程中发生键盘输入事件,OS就会强制将KeyHook.dll加载到相应进程的内存,然后调用KeyboardProc函数。消息钩取技术常被用作DLL注入技术。

 下面是HookMain.exeKeyHook.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
// HookMain.cpp -> HookMain.exe
#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);
if( hDll == NULL )
{
printf("LoadLibrary(%s) failed!!! [%d]", DEF_DLL_NAME, GetLastError());
return;
}

// 获取 export 函数地址, PFN_HOOKSTART 与 PFN_HOOKSTOP 表示是回调函数
HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART);
HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP);

// 开始钩取
HookStart();

// 等待直到用户输入'q'
printf("press 'q' to quit!\n");
while( _getch() != 'q' );

// 停止钩取
HookStop();

// 卸载 KeyHook.dll
FreeLibrary(hDll);
}
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
// KeyHook.cpp -> KeyHook.dll
#include "stdio.h"
#include "windows.h"

#define DEF_PROCESS_NAME "notepad.exe"

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

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved)
{
switch( dwReason )
{
case DLL_PROCESS_ATTACH:
g_hInstance = hinstDLL;
break;

case DLL_PROCESS_DETACH:
break;
}

return TRUE;
}

LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
char szPath[MAX_PATH] = {0,};
char *p = NULL;
if(nCode == 0)
{
// bit 31 : 0 => press, 1 => release
// 按下按键时
if( !(lParam & 0x80000000) )
{
// 搜索当前可执行文件的路径,并保存到szPath中
GetModuleFileNameA(NULL, szPath, MAX_PATH);
p = strrchr(szPath, '\\');

// 比较当前进程名称,若为notepad.exe,则消息不会传递给应用程序
if( !_stricmp(p + 1, DEF_PROCESS_NAME))
return 1;
}
}
// 如果不是notepad.exe,则调用CallNextHookEx()函数,将消息传递给应用程序
return CallNextHookEx(g_hHook, nCode, wParam, lParam);

}

#ifdef __cplusplus
extern "C" {
#endif
// 声明要导出的函数
__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

 程序运行的时候,有点问题:当运行HookMain.exe时,当按下某个键后,程序会卡死,经过分析是CallNextHookEx(g_hHook, nCode, wParam, lParam);没有正常的将消息传递给应用程序。

 最后终于弄明白了,原来是WH_KEYBOARDWH_KEYBOARD_LL的问题,改成WH_KEYBOARD_LL就好了。他俩的区别是:WH_KEYBOARD_LL 在消息发送之前就已经处理了, 而WH_KEYBOARD是发送到注入线程以后。这样的话,如果是WH_KEYBOARD,那么所有程序都会首先注入DLL,然后才执行KeyboardProc函数,造成卡顿。

 程序运行机理如下:

1
2
3
4
1. 安装好键盘钩子后,无论哪个进程,只要发生键盘输人事件,OS就会强制将KeyHook.dll注入相应进程。
2. 加载了KeyHook.dll的进程中,发生键盘事件时会首先调用执行KeyHook.KeyboardProc()。
3. KeyboardProc()函数中发生键盘输人事件时,就会比较当前进程的名称与“notepad.exe”字符串,若相同,则返回1,终止KeyboardProc()函数,这意味着截获且删除消息。这样,键盘消息就不会传递到notepad.exe程序的消息队列。因此,notepad.exe未能接收到任何键盘消息,故无法输出。
4. 如果当前进程名称不是notepad.exe时,执行return CallNextHookEx(...)语句,消息会被传递到另一个应用程序。

 其中,KeyboardProc函数的定义如下:

image-20230327231559009

 上面3个参数中,wParam指用户按下的键盘按键的虚拟键值(virtual-key code)。对键盘而言,英文字母”A”与”a”具有完全相同的虚拟键值。lParam根据不同的位具有多种不同的含义。

0x02 恶意键盘记录器

 一些无关紧要的知识,略。

0x03 DLL注入

 DLL注入指的是向运行中的其他进程强制插入特定的DLL文件。从技术细节来说,DLL注入要求其他进程自动调用LoadLibrary() API,并加载用户指定的DLL文件。如下图所示:

image-20230328105619233

 当DLL被插入到进程后,进程后会自动运行DLLMain()函数,用户可以把想执行的代码放到DLLMain()函数,每当加载 DLL时,添加的代码就会自然而然得到执行。DLLMain()函数如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved)
{
switch(dwReason)
{
case DLL_PROCESS_ATTACH:
// 想要添加的代码
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

 DLL注入可以改善功能与修复Bug。没有程序对应的源码,就可以使用此技术为程序添加新功能(类似于插件),或者修改有问题的代码、数据。

 向某个进程注入DLL时主要使用以下方法:

1
2
3
1. 创建远程线程(CreateRemoteThread() API)
2. 使用注册表(AppInit_DLLs值)
3. 消息钩取(SetWindowsHookEx() API),上上节所述

CreateRemoteThread进行DLL注入

 例子P200,不再赘述,跟着做。

工具:DebugView(可用于监视本地系统上的调试输出)。

 这个例子有一个InjectDll.exe,通过InjectDll.exe process_pid C:\Work\myhack.dll,用来向notepad.exe进程注入myhack.dllmyhack.dll执行的操作是输出一个字符串(在DebugView中查看),然后将一个网页下载到本地。但是调试的时候DebugView没显示调试字符串,其他倒是正常。

 之后解决了上述问题,解决方法如下:以管理员身份运行debugview、在debugview中勾选Capture Win32Capture Global Win32选项、在注册表中设置Debug Print Filter键值,以启用DbgPrint输出、把debugview放到和运行文件同文件夹下。

 分析例子源代码:

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
// myhack.cpp -> myhack.dll
#include "windows.h"
#include "tchar.h"

// 要求编译器搜索urlmon.lib,以使用URLDownloadToFile函数
#pragma comment(lib, "urlmon.lib")

#define DEF_URL (L"http://www.naver.com/index.html")
#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 WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
HANDLE hThread = NULL;

g_hMod = (HMODULE)hinstDLL;

switch( fdwReason )
{
case DLL_PROCESS_ATTACH :
OutputDebugString(L"<myhack.dll> Injection!!!");
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
CloseHandle(hThread);
break;
}
return TRUE;
}

 DLL首先会自动运行DLLMain函数中可以看到,该DLL被加载(DLL_PROCESS_ATTACH)时先输出一个调试字符串(myhack.dll Injection!!),然后创建线程调用函数(ThreadProc)。在ThreadProc函数中通过调用urlmon!URLDownloadToFileAPI来下载指定网站的index.html文件。所以当myhack.dll注入到notepad.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
// InjectDll.cpp -> InjectDll.exe
// 用来将 myhack.dll 注入到 notepad.exe 进程
#include "windows.h"
#include "tchar.h"

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;

// Enable the privilege or disable all privileges.
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 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;

// #1. 使用dwPID获取目标进程(notepad.exe)的句柄
if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
{
_tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
return FALSE;
}

// #2. 在目标进程(notepad.exe)中分配 dwBufSize 大小的内存
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);

// #3. 将 myhack.dll 的路径写入分配的内存 pRemoteBuf
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);

// #4. 获取 LoadLibraryA() API的地址
hMod = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");

// #5. 重点,在目标进程(notepad.exe)中运行线程
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"USAGE : %s <pid> <dll_path>\n", argv[0]);
return 1;
}

// change privilege,感觉是:能够调整程序的某些功能
if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
return 1;

// inject dll
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;
}

 上述代码的解释:

1
2
#1. 主要是使用OpenProcess()获得notepad.exe的句柄,句柄有PROCESS_ALL_ACCESS权限。
#2. pRemoteBuf指的是目标进程(notepad.exe)中的地址指针

 我们的目标明明是获取notepad.exe进程的kernel32.dllLoadLibraryW地址,但上面的代码获取InjectDll.exe进程的kernel32.dllLoadLibraryW地址。如果notepad.exe中的kernel32.dIl的地址与InjectDll.exe进程中的kernel32.dll的地址相同,那么代码就不会有什么问题。但是如果不同,那么上面的代码就错了,执行时会发生内存引用错误。其实在windows系统中,kernel32.dll在每个进程中的加载地址都是相同的。

注:调试API。

1
Windows 操作系统提供了调试 API,借助它们可以访问其他进程的内存空间。其中具有代表性的有VirtualAllocEx、VirtualFreeEx、WriteProcessMemory、ReadProcessMemory等。

CreateRemoteThread()原型如下:

image-20230328143930581

使用注册表进行DLL注入(AppInit_DLLs)

 Windows操作系统的注册表中默认提供了AppInt_DLLsLoadAppInit_DLLs两个注册表项。在注册表编辑器中,将要注入的DLL的路径字符串写入AppInit_DLLs项目,然后把LoadAppInit_DLLs的项目值设置为1。重启后,指定DLL会注入所有运行进程。

 上述方法的工作原理是:User32.dll被加载到进程时,会读取AppInit_DLLs注册表项。所以,相应DLL并不会被加载到所有进程,而只是加载至加载user32.dll的进程。

 下面分析myhack2.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
// myhack2.cpp -> myhack2.dll

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

#define DEF_CMD L"c:\\Program Files\\Internet Explorer\\iexplore.exe"
#define DEF_ADDR L"http://www.naver.com"
#define DEF_DST_PROC L"notepad.exe"

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
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;
si.wShowWindow = SW_HIDE;

switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
if( !GetModuleFileName(NULL, szPath, MAX_PATH ))
break;
if(!(p = _tcsrchr(szPath, '\\')))
break;
// 是否是 notepad.exe
if(_tcsicmp(p+1, DEF_DST_PROC))
break;
// 写到 szcmd 中
wsprintf(szCmd, L"%s %s", DEF_CMD, DEF_ADDR);
// 执行 szcmd 指令
if(!CreateProcess(NULL, (LPTSTR)(LPCTSTR)szCmd, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi) )
break;

if(pi.hProcess != NULL)
CloseHandle(pi.hProcess);
break;
}

return TRUE;
}

 可以看到,上述代码就是:如果打开了notepad.exe,就运行浏览器并访问http://www.naver.com但是书中说的是以隐藏模式运行浏览器,我并没有找到相关参数。

0x04 DLL卸载

 DLL卸载(DLL Ejection)是强制弹出进程中DLL的一种技术,其基本工作原理与使用CreateRemoteThread进行DLL注入的原理类似。前面使用CreateRemoteThread0API进行DLL注入的工作原理:驱使目标进程调用LoadLibrary。同样,DLL卸载工作原理:驱使目标进程调用FreeLibrary

注:每个Windows内核对象(Kernel Object)都拥有一个引用计数(Reference Count),代表对象被使用的次数。每调用一次LoadLibrary,引用计数会加1;每调用一次Freelibrary,引用计数会减1。

 下面分析EjectDll.exe程序,其用来从目标进程notepad.exe中卸载myhack.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
// EjectDll.cpp -> 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")

DWORD FindProcessID(LPCTSTR szProcessName)
{
DWORD dwPID = 0xFFFFFFFF;
HANDLE hSnapShot = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe;

// Get the snapshot of the system
// CreateToolhelp32Snapshot可以获取指定进程以及其使用的堆、模块和线程的快照
// 在此获得了所有进程的快照 hSnapShot
pe.dwSize = sizeof(PROCESSENTRY32);
hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);

// find process
// 枚举进程
Process32First(hSnapShot, &pe);
do
{
if(!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))
{
dwPID = pe.th32ProcessID;
break;
}
}
while(Process32Next(hSnapShot, &pe));

CloseHandle(hSnapShot);

return dwPID;
}


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

// 遍历比较 dll 名称与 myhack.dll
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;
}
// 获得 FreeLibrary 的地址
hModule = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
// 在目标进程中运行 FreeLibrary
// me.modBaseAddr 指的是 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,卸载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;
}

注:使用FreeLibrary的方法仅适用于卸载自己强制注入的DLL文件。PE文件直接导入的DLL文件是无法在进程运行过程中卸载的。

0x05 通过修改PE加载DLL

 除了前面所讲的DLL动态注入技术外,还可以采用手工修改可执行文件的方式加载用户指定的DLL文件。之后给出了一个例子,通过修改TextView.exe文件(主要是修改其IDT),在其运行时自动加载myhack3.dll文件,具体过程在P224中。这个例子与前面的PE头知识联动了,可以趁势复习一波。

 下面是myhack3.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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
// myhack3.cpp -> myhack3.dll
#include "stdio.h"
#include "windows.h"
#include "shlobj.h"
#include "Wininet.h"
#include "tchar.h"

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

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

HWND g_hWnd = NULL;

#ifdef __cplusplus
extern "C" {
#endif
// 出现在 IDT 中的 dummy export function...
__declspec(dllexport) void dummy()
{
return;
}
#ifdef __cplusplus
}
#endif

// 下载url文件
BOOL DownloadURL(LPCTSTR szURL, LPCTSTR szFile)
{
BOOL bRet = FALSE;
HINTERNET hInternet = NULL, hURL = NULL;
BYTE pBuf[DEF_BUF_SIZE] = {0,};
DWORD dwBytesRead = 0;
FILE *pFile = NULL;
errno_t err = 0;

// 初始化数据结构
hInternet = InternetOpen(L"ReverseCore", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if(NULL == hInternet)
{
OutputDebugString(L"InternetOpen() failed!");
return FALSE;
}
// 访问网址
hURL = InternetOpenUrl(hInternet, szURL, NULL, 0, INTERNET_FLAG_RELOAD, 0);
if(NULL == hURL)
{
OutputDebugString(L"InternetOpenUrl() failed!");
goto _DownloadURL_EXIT;
}
// 打开index.html文件
if(err = _tfopen_s(&pFile, szFile, L"wt"))
{
OutputDebugString(L"fopen() failed!");
goto _DownloadURL_EXIT;
}
// 写入index.html文件
while(InternetReadFile(hURL, pBuf, DEF_BUF_SIZE, &dwBytesRead) )
{
if(!dwBytesRead)
break;
fwrite(pBuf, dwBytesRead, 1, pFile);
}
bRet = TRUE;
_DownloadURL_EXIT:
if(pFile)
fclose(pFile);
if(hURL)
InternetCloseHandle(hURL);
if(hInternet)
InternetCloseHandle(hInternet);
return bRet;
}

BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam)
{
DWORD dwPID = 0;
GetWindowThreadProcessId(hWnd, &dwPID);
if(dwPID == (DWORD)lParam)
{
g_hWnd = hWnd;
return FALSE;
}
return TRUE;
}

HWND GetWindowHandleFromPID(DWORD dwPID)
{
// 枚举窗口
EnumWindows(EnumWindowsProc, dwPID);
return g_hWnd;
}

// 将下载的index.html文件拖放到被注入进程(TextView_Patch.exe)中并显示内容
BOOL DropFile(LPCTSTR wcsFile)
{
HWND hWnd = NULL;
DWORD dwBufSize = 0;
BYTE *pBuf = NULL;
DROPFILES *pDrop = NULL;
char szFile[MAX_PATH] = {0,};
HANDLE hMem = 0;
// szFile存index.html中的内容
WideCharToMultiByte(CP_ACP, 0, wcsFile, -1, szFile, MAX_PATH, NULL, NULL);

dwBufSize = sizeof(DROPFILES) + strlen(szFile) + 1;

if(!(hMem = GlobalAlloc(GMEM_ZEROINIT, dwBufSize)))
{
OutputDebugString(L"GlobalAlloc() failed!!!");
return FALSE;
}
// 将szFile中的内容放到pBuf中
pBuf = (LPBYTE)GlobalLock(hMem);
pDrop = (DROPFILES*)pBuf;
pDrop->pFiles = sizeof(DROPFILES);
strcpy_s((char*)(pBuf + sizeof(DROPFILES)), strlen(szFile)+1, szFile);
GlobalUnlock(hMem);
// 获得当前进程(TextView_Patch.exe)的pid
if(!(hWnd = GetWindowHandleFromPID(GetCurrentProcessId())))
{
OutputDebugString(L"GetWndHandleFromPID() failed!!!");
return FALSE;
}
// 将pBuf发送给TextView_Patch.exe
PostMessage(hWnd, WM_DROPFILES, (WPARAM)pBuf, NULL);
return TRUE;
}

// DLL初始调用的函数
DWORD WINAPI ThreadProc(LPVOID lParam)
{
TCHAR szPath[MAX_PATH] = {0,};
TCHAR *p = NULL;
// debugview 可以调试
OutputDebugString(L"ThreadProc() start...");

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

if(p = _tcsrchr(szPath, L'\\'))
{
// szPath = 当前路径 + index.html
_tcscpy_s(p+1, wcslen(DEF_INDEX_FILE)+1, DEF_INDEX_FILE);

OutputDebugString(L"DownloadURL()");
// 下载 google 首页到 szPath
if(DownloadURL(DEF_URL, szPath))
{
OutputDebugString(L"DropFlie()");
DropFile(szPath);
}
}

OutputDebugString(L"ThreadProc() end...");
return 0;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
// DLL运行时自动调用 ThreadProc
CloseHandle(CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL));
break;
}
return TRUE;
}

 上述dummy函数是myhack3.dll的导出函数,它没有任何功能,其存在是为了保持形式上的完整性,使myhack3.dll能够顺利添加到TextView.exe文件的导出表。在PE文件中导入某个DLL,实质就是在文件代码内调用该DLL提供的导出函数。PE文件头中记录着DLL名称、函数名称等信息。因此,myhack3.dll至少要向外提供1个以上的导出函数才能保持形式上的完整性。

之后的修改环节跟着书做就行,里面有很多注意事项,到时候修改的时候可以对照着看

 注:更改IDT时,需要保证映射到内存后,程序不会使用这段区域。PE文件尾部有些部分填充着 NULL,但这不意味着这些部分就是Null-Padding区域(空白可用区域),这些区域有可能是程序使用的区域。且并非所有Null-Padding区域都会加载到内存,只有分析节区头信息后才能判断。

 其中有一个细节值得注意:就是要更改.rdata节区头的属性,添加可写属性。但是,TextView.exe文件的IAT原来位于.rdata节区,且.rdata节区原本就没有可写属性,但程序仍能正常运行。可是对源程序进行修改之后,就要添加可写属性,这是为什么?因为原文件中就存在原始IAT,运行TextView.exe时根本不用对.rdata节区进行修改。

比较a.exeb.exe是否相同:fc a.exe b.exe /b

0x06 PE Tools

 功能强大的PE文件编辑工具,具有进程内存转储、PE文件头编辑、PE重建等功能,并且支持插件,带有插件编写示例,用户可以自己开发需要的插件。简单教程见P245。

进程内存转储(dump)

 转储(Dump),意为将内存中的内容转存到文件。这种转储技术主要用来查看正在运行的进程内存中的内容。

 文件是运行时解压缩文件时,其只有在内存中才以解压缩形态存在,此时借助转储技术可以轻松查看与源文件类似的代码与数据。然而,使用PE保护器时,文件在内存中仍处于压缩与加密状态,即便应用内存转储技术也无法准确把握文件内容。并且常因为使用Anti-Dump(反转储)技术而给转储带来困难。

 PE Tools 在进程转储操作时能有效绕开反转储技术。

0x07 代码注入(Code Injection)

 代码注入是一种向目标进程插入代码并使之运行的技术,它一般调用CreateRemoteThread(也可用于DLL注入)。此技术以远程线程形式运行插入代码,所以也被称为线程注入。下图展示了实现原理:

image-20230405102257126

 上图中,代码以线程过程(Thread Procedure)插入,而代码中使用的数据则以线程参数的形式传入。

DLL注入与代码注入的比较

 采用DLL注入技术时,整个DLL会被插入目标进程,代码与数据共存于内存,所以代码能够正常运行。而代码注入不仅向进程注入必要的代码,还必须将代码中使用的数据(参数,函数地址等)一同注入。因此,代码注入技术时要考虑的事项比DLL注入技术要多。

 代码注入的优点:(1)占用内存少。(2)难以查找痕迹。

 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
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
// CodeInjection.cpp -> CodeInjection.exe

#include "windows.h"
#include "stdio.h"

typedef struct _THREAD_PARAM
{
FARPROC pFunc[2]; // LoadLibraryA(), GetProcAddress()
char szBuf[4][128]; // "user32.dll", "MessageBoxA", "www.reversecore.com", "ReverseCore"
} THREAD_PARAM, *PTHREAD_PARAM;

// 定义了一个类型叫做 PFLOADLIBRARYA
// PFLOADLIBRARYA 是一个指向某函数的指针
// 某函数具有WINAPI调用约定,并返回HMODULE类型的值
// 某函数需要的参数是LPCSTR类型的值
// PFLOADLIBRARYA可以指向LoadLibraryA函数
typedef HMODULE (WINAPI *PFLOADLIBRARYA)
(
LPCSTR lpLibFileName
);

// 等同于GetProcAddress()函数
typedef FARPROC (WINAPI *PFGETPROCADDRESS)
(
HMODULE hModule,
LPCSTR lpProcName
);

// 等同于MessageBoxA()函数
typedef int (WINAPI *PFMESSAGEBOXA)
(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);

// 在目标进程中执行代码
DWORD WINAPI ThreadProc(LPVOID lParam)
{
PTHREAD_PARAM pParam = (PTHREAD_PARAM)lParam;
HMODULE hMod = NULL;
FARPROC pFunc = NULL;

// LoadLibrary()
hMod = ((PFLOADLIBRARYA)pParam->pFunc[0])(pParam->szBuf[0]); // "user32.dll"
if(!hMod)
return 1;

// GetProcAddress()
pFunc = (FARPROC)((PFGETPROCADDRESS)pParam->pFunc[1])(hMod, pParam->szBuf[1]); // "MessageBoxA"
if(!pFunc)
return 1;
// 前面为了找到目标进程中MessageBoxA的地址
// MessageBoxA()
((PFMESSAGEBOXA)pFunc)(NULL, pParam->szBuf[2], pParam->szBuf[3], MB_OK);

return 0;
}

// 向目标进程中注入代码与数据
BOOL InjectCode(DWORD dwPID)
{
HMODULE hMod = NULL;
THREAD_PARAM param = {0,};
HANDLE hProcess = NULL;
HANDLE hThread = NULL;
LPVOID pRemoteBuf[2] = {0,};
DWORD dwSize = 0;

hMod = GetModuleHandleA("kernel32.dll");

// set THREAD_PARAM
// 设置参数
param.pFunc[0] = GetProcAddress(hMod, "LoadLibraryA");
param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress");
strcpy_s(param.szBuf[0], "user32.dll");
strcpy_s(param.szBuf[1], "MessageBoxA");
strcpy_s(param.szBuf[2], "www.reversecore.com");
strcpy_s(param.szBuf[3], "ReverseCore");

// Open Process
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, // dwDesiredAccess
FALSE, // bInheritHandle
dwPID))) // dwProcessId
{
printf("OpenProcess() fail : err_code = %d\n", GetLastError());
return FALSE;
}

// Allocation for THREAD_PARAM
dwSize = sizeof(THREAD_PARAM);
if(!(pRemoteBuf[0] = VirtualAllocEx(hProcess, // hProcess
NULL, // lpAddress
dwSize, // dwSize
MEM_COMMIT, // flAllocationType
PAGE_READWRITE))) // flProtect
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}

// 向dwPID进程中写入所需的参数
if(!WriteProcessMemory(hProcess, // hProcess
pRemoteBuf[0], // lpBaseAddress
(LPVOID)&param, // lpBuffer
dwSize, // nSize
NULL)) // [out] lpNumberOfBytesWritten
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}

// Allocation for ThreadProc()
// 向dwPID进程中写入ThreadProc函数
dwSize = (DWORD)InjectCode - (DWORD)ThreadProc;
if(!(pRemoteBuf[1] = VirtualAllocEx(hProcess, // hProcess
NULL, // lpAddress
dwSize, // dwSize
MEM_COMMIT, // flAllocationType
PAGE_EXECUTE_READWRITE))) // flProtect
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}

if(!WriteProcessMemory(hProcess, // hProcess
pRemoteBuf[1], // lpBaseAddress
(LPVOID)ThreadProc, // lpBuffer
dwSize, // nSize
NULL)) // [out] lpNumberOfBytesWritten
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}

// 创建远程线程,以执行代码
if(!(hThread = CreateRemoteThread(hProcess, // hProcess
NULL, // lpThreadAttributes
0, // dwStackSize
(LPTHREAD_START_ROUTINE)pRemoteBuf[1], // lpStartAddress
pRemoteBuf[0], // lpParameter
0, // dwCreationFlags
NULL))) // lpThreadId
{
printf("CreateRemoteThread() fail : err_code = %d\n", GetLastError());
return FALSE;
}

WaitForSingleObject(hThread, INFINITE);

CloseHandle(hThread);
CloseHandle(hProcess);

return TRUE;
}

BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
{
... // 同DLL注入中的SetPrivilege
}

int main(int argc, char *argv[])
{
DWORD dwPID = 0;

if( argc != 2 )
{
printf("\n USAGE : %s <pid>\n", argv[0]);
return 1;
}

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

// code injection
dwPID = (DWORD)atol(argv[1]);
InjectCode(dwPID);

return 0;
}

 上述示例可以不传递LoadLibraryAGetProcAddress的地址,直接传递MessageBoxA的地址使用即可。上述示例的好处在于可以把相关库准确加载到指定进程。若将Windows套接字(Socket)中的ws2 32!connect地址传递给notepad.exe进程之后再使用,就会发生运行错误,因为notepad.exe默认不加载ws2_32.dll,但是默认加载user32.dll

 上述示例也基于某条件:加载到所有进程的kerel32.dll的地址都相同,所以CodeInjection.exe进程中的LoadLibraryAGetProcAddress地址与目标进程是一样的。

 之后跟着书上进行调试,具体见书P258。

0x08 使用汇编语言编写注入代码

为什么要用汇编?避免因编译器不同,而在调试时出现我们自己看不懂汇编代码的情况。

 跟着书做就行。

 首先写出ThreadProc函数的汇编(后面再解释为什么这样写)

image-20230405130046479

 上述汇编转为16进制为:

1
0x55,0x8b,0xec,0x8b,0x75,0x08,0x68,0x6c,0x6c,0x00,0x00,0x68,0x33,0x32,0x2e,0x64,0x68,0x75,0x73,0x65,0x72,0x54,0xff,0x16,0x68,0x6f,0x78,0x41,0x00,0x68,0x61,0x67,0x65,0x42,0x68,0x4d,0x65,0x73,0x73,0x54,0x50,0xff,0x56,0x04,0x6a,0x00,0xe8,0x0c,0x00,0x00,0x00,0x52,0x65,0x76,0x65,0x72,0x73,0x65,0x43,0x6f,0x72,0x65,0x00,0xe8,0x14,0x00,0x00,0x00,0x77,0x77,0x77,0x2e,0x72,0x65,0x76,0x65,0x72,0x73,0x65,0x63,0x6f,0x72,0x65,0x2e,0x63,0x6f,0x6d,0x00,0x6a,0x00,0xff,0xd0,0x33,0xc0,0x8b,0xe5,0x5d,0xc3

 再来看代码注入程序CodeInjection2.cpp

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

typedef struct _THREAD_PARAM
{
FARPROC pFunc[2]; // LoadLibraryA(), GetProcAddress()
} THREAD_PARAM, *PTHREAD_PARAM;

BYTE g_InjectionCode[] =
{
0x55, 0x8B, 0xEC, 0x8B, 0x75, 0x08, 0x68, 0x6C, 0x6C, 0x00,
0x00, 0x68, 0x33, 0x32, 0x2E, 0x64, 0x68, 0x75, 0x73, 0x65,
0x72, 0x54, 0xFF, 0x16, 0x68, 0x6F, 0x78, 0x41, 0x00, 0x68,
0x61, 0x67, 0x65, 0x42, 0x68, 0x4D, 0x65, 0x73, 0x73, 0x54,
0x50, 0xFF, 0x56, 0x04, 0x6A, 0x00, 0xE8, 0x0C, 0x00, 0x00,
0x00, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x43, 0x6F,
0x72, 0x65, 0x00, 0xE8, 0x14, 0x00, 0x00, 0x00, 0x77, 0x77,
0x77, 0x2E, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x63,
0x6F, 0x72, 0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x00, 0x6A, 0x00,
0xFF, 0xD0, 0x33, 0xC0, 0x8B, 0xE5, 0x5D, 0xC3
};

BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
{
... // 同上
}

BOOL InjectCode(DWORD dwPID)
{
HMODULE hMod = NULL;
THREAD_PARAM param = {0,};
HANDLE hProcess = NULL;
HANDLE hThread = NULL;
LPVOID pRemoteBuf[2] = {0,};

hMod = GetModuleHandleA("kernel32.dll");

// set THREAD_PARAM
// 获得LoadLibraryA与GetProcAddress的地址
param.pFunc[0] = GetProcAddress(hMod, "LoadLibraryA");
param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress");

// Open Process
// 打开目标进程
if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, // dwDesiredAccess
FALSE, // bInheritHandle
dwPID)) ) // dwProcessId
{
printf("OpenProcess() fail : err_code = %d\n", GetLastError());
return FALSE;
}

// Allocation for THREAD_PARAM
// 在目标进程中分配内存pRemoteBuf[0]
if( !(pRemoteBuf[0] = VirtualAllocEx(hProcess, // hProcess
NULL, // lpAddress
sizeof(THREAD_PARAM), // dwSize
MEM_COMMIT, // flAllocationType
PAGE_READWRITE)) ) // flProtect
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}

// 在pRemoteBuf[0]中写入param
// 在目标进程中写入LoadLibraryA与GetProcAddress的地址
if( !WriteProcessMemory(hProcess, // hProcess
pRemoteBuf[0], // lpBaseAddress
(LPVOID)&param, // lpBuffer
sizeof(THREAD_PARAM), // nSize
NULL) ) // [out] lpNumberOfBytesWritten
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}

// Allocation for ThreadProc()
// 分配内存
if( !(pRemoteBuf[1] = VirtualAllocEx(hProcess, // hProcess
NULL, // lpAddress
sizeof(g_InjectionCode), // dwSize
MEM_COMMIT, // flAllocationType
PAGE_EXECUTE_READWRITE)) ) // flProtect
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}
// 将ThreadProc的hex代码写入到pRemoteBuf[1]
if( !WriteProcessMemory(hProcess, // hProcess
pRemoteBuf[1], // lpBaseAddress
(LPVOID)&g_InjectionCode, // lpBuffer
sizeof(g_InjectionCode), // nSize
NULL) ) // [out] lpNumberOfBytesWritten
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}
// 创建远程线程
if( !(hThread = CreateRemoteThread(hProcess, // hProcess
NULL, // lpThreadAttributes
0, // dwStackSize
(LPTHREAD_START_ROUTINE)pRemoteBuf[1], // lpStartAddress
pRemoteBuf[0], // lpParameter
0, // dwCreationFlags
NULL)) ) // lpThreadId
{
printf("CreateRemoteThread() fail : err_code = %d\n", GetLastError());
return FALSE;
}

WaitForSingleObject(hThread, INFINITE);

CloseHandle(hThread);
CloseHandle(hProcess);

return TRUE;
}

int main(int argc, char *argv[])
{
DWORD dwPID = 0;

if( argc != 2 )
{
printf("\n USAGE : %s <pid>\n", argv[0]);
return 1;
}

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

// code injection
dwPID = (DWORD)atol(argv[1]);
InjectCode(dwPID);

return 0;
}

 下面详细分析这段二进制代码ThreadProc

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
004010ED    55               PUSH EBP
004010EE 8BEC MOV EBP,ESP ; 创建栈帧,558BEC,将ESP给EBP
004010F0 8B75 08 MOV ESI,DWORD PTR SS:[EBP+8] ; SS:[EBP+8]是CreateRemoteThread倒数第3个参数,也就是传入的参数

004010F3 68 6C6C0000 PUSH 6C6C ; "\0\0ll"
004010F8 68 33322E64 PUSH 642E3233 ; "d.23"
004010FD 68 75736572 PUSH 72657375 ; "resu"
00401102 54 PUSH ESP ; - "user32.dll"
00401103 FF16 CALL DWORD PTR DS:[ESI] ; LoadLibraryA("user32.dll"),返回的库地址保存在EAX中
00401105 68 6F784100 PUSH 41786F
0040110A 68 61676542 PUSH 42656761
0040110F 68 4D657373 PUSH 7373654D
00401114 54 PUSH ESP ; - "MessageBoxA"
00401115 50 PUSH EAX ; - hMod
00401116 FF56 04 CALL DWORD PTR DS:[ESI+4] ; GetProcAddress(hMod, "MessageBoxA"),返回的函数地址保存在EAX中
00401119 6A 00 PUSH 0 ; - MB_OK (0)
0040111B E8 0C000000 CALL 0040112C
00401120 <ASCII> ; - "ReverseCore", 0
0040112C E8 14000000 CALL 00401145
00401131 <ASCII> ; - "www.reversecore.com", 0
00401145 6A 00 PUSH 0 ; - hWnd (0)
00401147 FFD0 CALL EAX ; MessageBoxA(0, "www.reversecore.com", "ReverseCore", 0)
00401149 33C0 XOR EAX,EAX ; 将EAX设置为0,作为ThreadProc函数的返回值
0040114B 8BE5 MOV ESP,EBP
0040114D 5D POP EBP
0040114E C3 RETN

上述代码中,地址0040111B使用CALL指令将包含在代码间的字符串数据地址压入栈。其中00401120-0040112B"ReverseCore", 0所在区域,使用call指令之后,返回地址00401120被压入栈中,并且接着调用call 00401145,之后00401131也被压入栈中,这样,就完成了"ReverseCore""www.reversecore.com", 0的入栈。

留言

© 2024 wd-z711

⬆︎TOP