(简称关于逆向工程核心原理的第二十五章学习)
通过PE文件加载DLL
其实就是通过修改导入表来加载DLL,简单说下实验要用的
TextView
呜呜呜最简单的win32编程,寒假为什么没有好好学,看着那些代码似曾相识,没时间后悔了
就是一个简单的文本查看程序简易版Notepad
正常版的导入表,基地址转换也不用多说了
TextView_Pacted
注进去咯,注意基地址也换了,因为把整个DLL导入表都换了个地方(后面细锁
实验时间!
可以发现启动时自动加载,当程序启动时自动导入MyHack3.dll,dll起了个线程自动下载了指定网站的页面
MyHack.dll
开审!
DllMain()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { switch( fdwReason ) { case DLL_PROCESS_ATTACH : CloseHandle(CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL)); break; } return TRUE; }
|
ThreadProc()
可以注意一下GetModuleFileName() API
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
| DWORD WINAPI ThreadProc(LPVOID lParam) { TCHAR szPath[MAX_PATH] = {0,}; TCHAR *p = NULL;
OutputDebugString(L"ThreadProc() start...");
GetModuleFileName(NULL, szPath, sizeof(szPath)); if( p = _tcsrchr(szPath, L'\\') ) { _tcscpy_s(p+1, wcslen(DEF_INDEX_FILE)+1, DEF_INDEX_FILE);
OutputDebugString(L"DownloadURL()"); if( DownloadURL(DEF_URL, szPath) ) { OutputDebugString(L"DropFlie()"); DropFile(szPath); } }
OutputDebugString(L"ThreadProc() end...");
return 0; }
|
DownloadURL()
DownloadURL()函数会下载参数szURL中指定的网页文件,保存到szPath目录
PS: 作者写的DownloadURL是通过InternetOpen() InternetOpenUrl() InternetReadFile() API对URLDownLoad()的简单实现
这些API均在wininet.dll实现
而URLDownloadToFile() API也就是之前用的,在urlmon.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
| 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; }
if( err = _tfopen_s(&pFile, szFile, L"wt") ) { OutputDebugString(L"fopen() failed!"); goto _DownloadURL_EXIT; }
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; }
|
DropFile()
DropFile() 函数将下载的index.html拖放到TextView_Patch.exe进程并显示其内容
使用PID获取TextView的主窗口句柄
调用postMessage(WM_DROPFILES)API将消息放入消息队列
获取窗口句柄
单独领出来讲一下获取进程PID方法,和上章的DLL卸载不一样,但应该实现的是一样的都是获取句柄
- GetCurrentProcessId获取了调用当前进程的进程标识符,然后传入了自写函数GetWindowHandleFromPID
- 里面调用了EnumWindows这个函数会枚举每个窗口句柄传入回调函数,也就是第一个参数EnumWindowsProc,第二个参数是成为回调函数的参数
- 在回调函数EnumWindowsProc里,会调用GetWindowThreadProcessId通过窗口句柄来创建当前窗口的进程标识符
- 再与我们传入的TextView的进程标识符进行比较,是的话就成功获取TextView的进程句柄
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
|
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; }
if( !(hWnd = GetWindowHandleFromPID(GetCurrentProcessId())) ) { OutputDebugString(L"GetWndHandleFromPID() failed!!!"); return FALSE; }
|
现在再来看下整个DropFile()
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
|
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; }
BOOL DropFile(LPCTSTR wcsFile) { HWND hWnd = NULL; DWORD dwBufSize = 0; BYTE *pBuf = NULL; DROPFILES *pDrop = NULL; char szFile[MAX_PATH] = {0,}; HANDLE hMem = 0;
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; }
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; }
PostMessage(hWnd, WM_DROPFILES, (WPARAM)pBuf, NULL);
return TRUE; }
|
dummy()
dummy函数是myhack3.dll的文件的向外提供服务的导出函数,无用,为了保持形式上的完整性
在PE文件中导入某个DLL,实质就是在文件代码内调用该DLL提供的导出函数
PE文件头中记录着DLL名称、函数名称等信息,因此,MyHack3.dll至少要向外提供1个以上导出函数才能保持形式上的完整性(DLL特性)
1 2 3 4 5 6 7 8 9 10
| #ifdef __cplusplus extern "C" { #endif __declspec(dllexport) void dummy() { return; } #ifdef __cplusplus } #endif
|
现在再来审计整个MyHack源码就没什么问题了,待会动调看看数据细节
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
| #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 __declspec(dllexport) void dummy() { return; } #ifdef __cplusplus } #endif
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; }
if( err = _tfopen_s(&pFile, szFile, L"wt") ) { OutputDebugString(L"fopen() failed!"); goto _DownloadURL_EXIT; }
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; }
BOOL DropFile(LPCTSTR wcsFile) { HWND hWnd = NULL; DWORD dwBufSize = 0; BYTE *pBuf = NULL; DROPFILES *pDrop = NULL; char szFile[MAX_PATH] = {0,}; HANDLE hMem = 0;
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; }
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; }
PostMessage(hWnd, WM_DROPFILES, (WPARAM)pBuf, NULL);
return TRUE; }
DWORD WINAPI ThreadProc(LPVOID lParam) { TCHAR szPath[MAX_PATH] = {0,}; TCHAR *p = NULL;
OutputDebugString(L"ThreadProc() start...");
GetModuleFileName(NULL, szPath, sizeof(szPath)); if( p = _tcsrchr(szPath, L'\\') ) { _tcscpy_s(p+1, wcslen(DEF_INDEX_FILE)+1, DEF_INDEX_FILE);
OutputDebugString(L"DownloadURL()"); 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 : CloseHandle(CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL)); break; } return TRUE; }
|
TextView.exe
现在聊下如何修改PE文件自动加载指定dll
1. 查看IDT是否又足够空间
可以发现五个IID结构体之后,最后一个是NULL结构体,后面也没有我们放入MyHack的空间
三个方法添加IID
- 查找文件中的空白区域
- 增加文件最后一个节区的大小
- 在文件末尾添加新节区
这边直接在rdata末尾添加了,可以发现映射到内存的是2C56,而文件大小有2E00,于是就把整个IID移到rdata结尾
2. 移动且增加导入表
准备把IID表移到8C80处,也就是文件的7E80处,所以得先把指向IID的地址修改成新地址
并且删除绑定导入表,BOUND IMPORT TABLE,这是一种提高DLL加载速度的技术,这是个可选项,如果不删需要向该表添加信息,删除只需要全部清零即可
上述搞完,就可以添加MyHack的导入表了
再贴下IID的结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; }; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR;
typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; BYTE Name[1]; } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
|
用下书上的解析
特别说下IAT,IAT也是RVA数组,各元素既可以拥有与INT相同的值,也可以拥有不同的值(若INT数据准确)
反正运行时,PE装载器会将虚拟内存中的IAT替换为该实际函数的地址
3. 大功告成?
还差最后一步修改IAT节区属性,PE装载器在修改IAT,写入函数的实际地址,所以相关节区一定要有WRITE
所以要向Characteristics or 8000 0000,就可以拥有写权限
现在就可以完美做出了和作者一样的TextView_Patch的版本了
一个疑问
但仔细想想是不是哪里不对,如果rdata原先没有写权限,可原来的IAT也在rdata节区,但程序能正常运行
而我们修改后也在rdata节区,却不行了
这是因为!PE头的IMAGE_OPTIONAL_HEADER结构体Data Directoy数组中存在IAT\
所以不想改写权限的就可以直接添加在这后面,并把IAT(SIZE)增加8个字节
把IAT地址改到这后面,再把原来的IAT数据删了
再到这边改成IAT函数的地址,注意后面跟个4字节的导出表截断
再修改一下IAT的大小,增加到15C,不过作者那份没改,可是并没有什么事情
大功告成!