第四課 通過修改PE載入DLL
下面通過例項來講解
下面分析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 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; }
下面看一下DropFile()
複習一下IID結構體的定義
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA 指向INT (PIMAGE_THUNK_DATA)
};
DWORD TimeDateStamp;
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name; //dll 名稱
DWORD FirstThunk; //指向引入函式真實地址單元處的RVA IAT
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
首先在空白位置建立新的IDT
刪除繫結匯入表
IAT中儲存的函式地址是dll未載入的地址,當PE檔案中不存在繫結匯入表時,IAT就與INT一樣,此時匯入表中的時間戳就為0;否則匯入表中的時間戳為-1時,dll的真正時間戳存放於繫結匯入表中(繫結匯入表地址存放在資料目錄的第12項,IAT是第13項)。
現在大多數情況,匯入表的TimeDateStamp都為0,而Windows早期的自帶軟體(如WinXP的notepad.exe)基本都採用了TimeDateStamp為-1的情況即包含繫結匯入表的情況。PE中包含匯入表的優點是程式啟動快,但是其缺點也十分明顯,當存在dll地址重定位和dll修改更新,則繫結匯入表也需要修改更新。
//最後一個結構全0表示繫結匯入表結束
typedef struct _IMAGE_BOUND_IMPORT_DESCRIPTOR {
DWORD TimeDateStamp; //表示繫結的時間戳,如果和PE頭中的TimeDateStamp不同則可能被修改過
WORD OffsetModuleName; //dll名稱地址
WORD NumberOfModuleForwarderRefs; //依賴dll個數
// Array of zero or more IMAGE_BOUND_FORWARDER_REF follows
} IMAGE_BOUND_IMPORT_DESCRIPTOR, *PIMAGE_BOUND_IMPORT_DESCRIPTOR;
NumberOfModuleForwarderRefs是指該dll自身依賴的dll的個數。值為n代表該結構後面緊跟了n個IMAGE_BOUND_FORWARDER_REF結構。之後才是匯入表匯入的下一個dll的結構。而IMAGE_BOUND_FORWARDER_REF結構體如下所示:
typedef struct _IMAGE_BOUND_FORWARDER_REF {
DWORD TimeDateStamp; //時間戳,同樣的作用檢查更新情況
WORD OffsetModuleName; //dll名稱地址
WORD Reserved; //保留
} IMAGE_BOUND_FORWARDER_REF, *PIMAGE_BOUND_FORWARDER_REF;
注意:這兩個結構體中所有的OffsetModuleName均不是相對於ImageBase的RVA也不是FOA,而是相對於繫結匯入表首地址的偏移地址,即:繫結匯入表首地址 + OffsetModuleName= RVA
繫結匯入表結構圖解如下所示:
建立新的IDT:
先使用Hex Editor完全複製原IDT(RAW:76CC~772F),然後覆蓋到IDT的新位置(RAW:7E80)
複製:
覆蓋:
在7ED0處寫入IID
詳解如下圖
修改IAT節區的屬性
三天才整完這一課,中途在使用winhex的時候出了點小差錯弄了很久,只要有耐心,成功之後會非常有成就感的