天天看點

通過修改PE加載DLL

練習檔案

  1. 直接修改TextView.exe檔案,使其在運作時自動加載myhack3.dll檔案。
  2. TextView.exe是一個非常簡單的文本檢視程式,隻要用滑鼠将要檢視的文本檔案(myhack3.cpp)拖動到其中,即可通過它檢視文本檔案的内容。
    通過修改PE加載DLL
  3. 使用PEView工具檢視TextView.exe可執行檔案的IDT(import Directory Table,導入目錄表),TexeView.exe中直接導入的DLL檔案為KERNEL32.dll、USER32.dll、GDI32.dll、SHELL32.dll.
    通過修改PE加載DLL
  4. TextView_patched.exe是修改TextView.exe檔案的IDT後得到的檔案,即在IDT中添加了導入myhack3.dll的部分,運作時會自動導入myhack3.dll檔案,使用PEView工具檢視TextView_patched.exe的IDT。
    通過修改PE加載DLL
  5. 運作程式,程式會自動加載myhack3.dll,嘗試連接配接Google網站,下載下傳網站的index.html,并将其放到TextView_Patched.exe程式。
    通過修改PE加載DLL
    通過修改PE加載DLL

源代碼 - myhack3.cpp

  1. 分析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"



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;
}
           
  • DllMain()函數的功能非常簡單,建立線程運作指定的線程過程,線上程過程(ThreadProc)中調用DownLoadURL()與DropFIle()函數,下載下傳指定網頁并将其拖放到文本檢視程式。
  1. DownloadURL()
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;
}
           
  • DownloadURL()函數會下載下傳參數szURL中指定的網頁檔案,并将其儲存到szFile目錄。
  • 上述示例中DownloadURL()函數使用internetOpen()、InternetOpenUrl()、InternetReadFile() API對URLDownloadToFile() API的簡單實作。
  1. DropFile()
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;
}
           
  • DropFile()函數的主要功能是,使用PID擷取視窗句柄,在調用postMessage(WM_DROPFILES)API将消息放入消息隊列。
  1. dummy()
__declspec(dllexport) void dummy()
{
    return;
}
#ifdef __cplusplus
}
#endif
           
  • dummy()函數是myhack3.dll檔案項外部提供服務的導出函數,它沒有任何功能,為了保持形式上的完整性,是myhack3.dll能夠順利添加到TextView.exe檔案的導入表。
  • 在PE檔案中導入某個DLL,實質就是在檔案僞造内調用該DLL提供的導出函數,PE檔案頭記錄着DLL名稱、函數名稱等資訊,是以,myhack3.dll至少要提供1個以上的導出函數才能保持形式上的完整性。

修改TextView.exe檔案的準備工作

  1. PE檔案中導入的DLL資訊以結構體清單形式存儲在IDT中,隻要将myhack3.dll添加到清單末尾就可以了,當然,此前要确認一下IDT中有無足夠空間。
  2. 使用PEView檢視TextView.exe的IDT位址,PE檔案頭的IMAGE_OPTIONAL_HEADER結構體中導入表的RVA值即為IDT的RVA,IDT的位址為84CC,接下來,在PEView中直接檢視IDT。
    通過修改PE加載DLL
  3. TextView.exe的IDT存在于.rdata節區,IDT是由IMAGE_IMPORT_DESCRIPTOR(簡稱IID)結構體組成的數組,且數組末尾以NULL結構體結束,由于每個導入的DLL檔案都對應一個IID結構體,每個IID結構圖大小為14個位元組.
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            // 0 for terminating null import descriptor
        DWORD   OriginalFirstThunk;         // 包含指向IMAGE_DATA(輸入名稱表)RVA 的結構數組
    };
    DWORD   TimeDateStamp;                  //當可執行檔案不與被導入的DLL進行綁定時,此字段為0
    DWORD   ForwarderChain;                 //第一個被轉向的API索引
    DWORD   Name;                           //指向被導入的DLL 名稱
    DWORD   FirstThunk;                     //指向輸入位址表(IAT)RVA,IAT是一個IMAGE_THUNK_DATA結構的數組
} IMAGE_IMPORT_DESCRIPTOR;
           
  1. 使用PEView工具欄将視圖改為File Offset,可以看到IDT的檔案偏移為76CC.
    通過修改PE加載DLL
    通過修改PE加載DLL
  • IDT的檔案偏移為76CC~772F,整個大小為64(十六進制)位元組,共有5個IID結構體,其中最後一個為NULL結構體,從圖中可以看出IDT尾部存在其他資料,沒有足夠空間來添加myhack3.dll的IID結構體。
  1. 先把整個IDT轉移到其他更廣闊的位置,然後再添加新的IID,确定移動的目标位置時,可以使用下main三種方式:
  • 查找檔案中的空白區域
  • 增加檔案最後一個節區的大小。
  • 在檔案末尾添加新節區。
  1. .rdata節區尾部恰好存在大片空白區域,一般來說,節區或檔案末尾都存在空白區域,PE檔案這種空白區域稱為Null-Padding區域。
    通過修改PE加載DLL
  • 把原IDT移動到該Null-Padding區域(RVA:8C60~8DFF)中合适位置就行了,在此之前,先要确認一下該區域是否全是空白可用區域(Null-padding區域),并不是檔案中的所有區域都會被無條件加載到程序的虛拟記憶體,隻有節區頭中明确記錄的區域才會被加載。
    通過修改PE加載DLL
    通過修改PE加載DLL
  • .rdata節區在磁盤檔案中的大小為2E00,而檔案執行後被加載到記憶體時,程式實際使用的資料大小為2C56,剩餘未被使用的區域大小為1AA,在這段空白區域建立IDT是不會由什麼問題的。
  • 我們要在RVA:8C80(RAW:7E80)位置建立IDT。

修改TexTView.exe

  1. 将TextView.exe複制一份,重命名為TextView_Patch.exe。
  2. IMAGE_OPTIONAL_HEADER的導入表結構成員用來指出IDT的位置與大小。
    通過修改PE加載DLL
  • TextView.exe檔案中,導入表的RVA的值為84CC,接下來,将導入表的RVA值更改為新IDT的RVA值8C80,在Size原值64位元組的基礎上增加14個位元組,修改為78個位元組。
    通過修改PE加載DLL
  1. 删除綁定導入表
    通過修改PE加載DLL
  • BOUND IMPORT TABLE是一種提高DLL加載速度的技術。
  • 若想正常導入myhack3.dll,需要項綁定導入表添加資訊,但幸運的是,該綁定導入表是個可選項,不是必須存在的,是以可删除(修改其值為0)以擷取更大便利。綁定導入表完全不存在也沒關系,但若存在,且其内資訊記錄錯誤,則會在程式運作時引發錯誤。
  • 本執行個體TextView.exe,綁定導入表各項的值均為0,不需要在修改。
  1. 建立新的IDT
  • 複寫原來的資料到新的位置RAW:7E80,如圖所示:
    通過修改PE加載DLL
  • RAW:7ED0添加與myhack3.dll對應的IID:
    通過修改PE加載DLL
  1. 設定Name、INT、IAT
  • 前面添加的IID結構體成員擁有指向其他資料結構(INT、Name、IAT)的RVA的值,是以,必須準确設定這些資料結構才能保證TextView_Patch.exe檔案正常運作。
    通過修改PE加載DLL
  • 這些位址(RVA:8D00,8D10,8D20)就位于新建立的IDT(RVA:8C80)下方,我們輸入相應的值:
    通過修改PE加載DLL
  • 8CD0位址處存在myhack3.dll的IID結構體,其中3個主要成員(RVA of INT、RVA of Name、RVA of IAT)的值分别是實際INT、Name、IAT的指針。
  • INT是RVA數組,數組的各個元素都是一個RVA位址,該位址由導入函數的Ordinal(2個位元組)+Func Name String結構體組成,數組的末尾為NULL。
  • Name包含導入函數檔案名稱字元串,在8D10位址處可以看到“myhack3.dll"字元串。
  • IAT也是RVA數組,各元素既可以擁有與INT相同的值,也可以擁有其他不同值,反正實際運作時,PE裝載器會将虛拟記憶體中的IAT替換為實際函數的位址。
  1. 修改IAT節區的屬性值
  • 加載PE檔案到記憶體時,PE裝載器會修改IAT,寫入函數的實際位址,是以相關節區一定要擁有WRITE屬性,隻要這樣,PE裝載器才能正常寫入操作。
  • 使用PEView檢視.rdata節區頭:
    通過修改PE加載DLL
  • 向原屬性值40000040添加IMAGE_SCN_MEM_WRITE(80000000)屬性值,執行bit OR運算,最終屬性值變為C0000040.
    通過修改PE加載DLL

檢測驗證

  1. 使用PEView打開修改後的TextView_Patch.exe檔案,檢視IDT。
    通過修改PE加載DLL
  2. 向IDT導入myhack3.dll的IID結構已設定正常,myhack3.dll的dummy()函數被添加到INT。
    通過修改PE加載DLL
  3. 運作程式
    通過修改PE加載DLL

繼續閱讀