首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

DLL 嵌入 EXE 技術:將動態連結庫作為資源內建至執行檔

     編輯:關於VC++

動機與應用場景

  • 避免使用者誤刪 DLL 導致程式無法執行
  • 防止 DLL 被第三方直接呼叫或逆向分析
  • 簡化部署:單一 EXE 檔案即可執行
  • 保護核心演算法與授權邏輯

Windows DLL 載入機制回顧

  1. LoadLibrary 將 DLL 映射至進程位址空間,回傳基址 (HMODULE)
  2. GetProcAddress 依匯出名稱/序數查詢函數虛擬位址
  3. 呼叫者透過函數指標間接調用

核心洞見:若能手動在記憶體中重建 DLL 映射映像,並解析其匯出表,即可直接呼叫函數,無需磁碟檔案。

完整實現流程

步驟 1:將 DLL 作為自訂資源嵌入

  • 資源類型:RT_RCDATA
  • 資源 ID:IDR_DATA1
  • 專案設定:資源編譯器 → 加入資源 → 選擇 DLL 檔案

步驟 2:從資源載入原始 DLL 映像


LPVOID pRawDll = NULL;
HMODULE hSelf = GetModuleHandle(NULL);
HRSRC hRes = FindResource(hSelf, MAKEINTRESOURCE(IDR_DATA1), RT_RCDATA);
if (hRes) {
    HGLOBAL hLoaded = LoadResource(hSelf, hRes);
    if (hLoaded) {
        DWORD size = SizeofResource(hSelf, hRes);
        pRawDll = LockResource(hLoaded); // 取得資源在記憶體位址
    }
}

步驟 3:核心函數 LoadPbDllFromMemory - 手動映射 DLL

此函數執行完整的 PE 載入器邏輯:


DWORD LoadPbDllFromMemory(LPVOID lpRawDll, LPVOID* lpImageDll) {
    // 1. 驗證 DOS/PE 標頭
    PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)lpRawDll;
    if (dos->e_magic != IMAGE_DOS_SIGNATURE) return 2;
    PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((BYTE*)dos + dos->e_lfanew);
    if (nt->Signature != IMAGE_NT_SIGNATURE) return 3;

    // 2. 計算所需記憶體大小 (對齊區塊)
    SYSTEM_INFO si; GetSystemInfo(&si);
    DWORD maxLen = 0;
    PIMAGE_SECTION_HEADER sec = IMAGE_FIRST_SECTION(nt);
    for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++) {
        DWORD end = sec[i].VirtualAddress + sec[i].SizeOfRawData;
        if (end > maxLen) maxLen = end;
    }
    DWORD pages = (maxLen + si.dwPageSize - 1) / si.dwPageSize;
    *lpImageDll = VirtualAlloc(NULL, pages * si.dwPageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (!*lpImageDll) return 6;

    // 3. 複製標頭與所有區塊至新位址
    DWORD hdrSize = (DWORD)sec - (DWORD)dos + sizeof(IMAGE_SECTION_HEADER) * nt->FileHeader.NumberOfSections;
    CopyMemory(*lpImageDll, lpRawDll, hdrSize);
    for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++) {
        DWORD dst = (DWORD)*lpImageDll + sec[i].VirtualAddress;
        DWORD src = (DWORD)lpRawDll + sec[i].PointerToRawData;
        CopyMemory((LPVOID)dst, (LPVOID)src, sec[i].SizeOfRawData);
    }

    // 4. 修正重定位表
    PIMAGE_BASE_RELOCATION reloc = (PIMAGE_BASE_RELOCATION)
        ((DWORD)*lpImageDll + nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
    if (reloc) {
        while (reloc->VirtualAddress) {
            PWORD entry = (PWORD)((BYTE*)reloc + sizeof(IMAGE_BASE_RELOCATION));
            WORD* end = (WORD*)((BYTE*)reloc + reloc->SizeOfBlock);
            while (entry < end) {
                if ((*entry >> 12) == IMAGE_REL_BASED_HIGHLOW) {
                    DWORD* fixup = (DWORD*)((BYTE*)*lpImageDll + reloc->VirtualAddress + (*entry & 0xFFF));
                    *fixup = (DWORD)*lpImageDll + (*fixup - nt->OptionalHeader.ImageBase);
                }
                entry++;
            }
            reloc = (PIMAGE_BASE_RELOCATION)((BYTE*)reloc + reloc->SizeOfBlock);
        }
    }

    // 5. 解析匯入表 (載入依賴 DLL、填寫 IAT)
    PIMAGE_IMPORT_DESCRIPTOR imp = (PIMAGE_IMPORT_DESCRIPTOR)
        ((DWORD)*lpImageDll + nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
    while (imp->Name) {
        char* dllName = (char*)((DWORD)*lpImageDll + imp->Name);
        HMODULE hDep = GetModuleHandleA(dllName) ?: LoadLibraryA(dllName);
        if (!hDep) return 9;

        DWORD* thunkRef = (DWORD*)((DWORD)*lpImageDll + imp->OriginalFirstThunk);
        DWORD* thunkAddr = (DWORD*)((DWORD)*lpImageDll + imp->FirstThunk);
        while (*thunkRef) {
            if (*thunkRef & IMAGE_ORDINAL_FLAG) {
                *thunkAddr = (DWORD)GetProcAddress(hDep, (LPCSTR)(*thunkRef & 0xFFFF));
            } else {
                PIMAGE_IMPORT_BY_NAME ibn = (PIMAGE_IMPORT_BY_NAME)((DWORD)*lpImageDll + *thunkRef);
                *thunkAddr = (DWORD)GetProcAddress(hDep, ibn->Name);
            }
            if (!*thunkAddr) return 10;
            thunkRef++; thunkAddr++;
        }
        imp++;
    }

    // 6. 設定區塊保護屬性
    // ... 略 (依 Characteristics 設定 PAGE_READONLY/EXECUTE_READ/READWRITE)

    // 7. 呼叫 DllMain(DLL_PROCESS_ATTACH)
    DLL_ENTRY_POINT entry = (DLL_ENTRY_POINT)((DWORD)*lpImageDll + nt->OptionalHeader.AddressOfEntryPoint);
    entry((HINSTANCE)*lpImageDll, DLL_PROCESS_ATTACH, NULL);

    return 0;
}

步驟 4:取得匯出函數位址 GetProcAddressDirectly


DWORD GetProcAddressDirectly(PIMAGE_DOS_HEADER dos, LPCSTR funcName) {
    PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((BYTE*)dos + dos->e_lfanew);
    DWORD expRVA = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
    if (!expRVA) return 0;

    PIMAGE_EXPORT_DIRECTORY exp = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)dos + expRVA);
    DWORD* names = (DWORD*)((BYTE*)dos + exp->AddressOfNames);
    WORD* ordinals = (WORD*)((BYTE*)dos + exp->AddressOfNameOrdinals);
    DWORD* funcs = (DWORD*)((BYTE*)dos + exp->AddressOfFunctions);

    for (DWORD i = 0; i < exp->NumberOfNames; i++) {
        char* name = (char*)((BYTE*)dos + names[i]);
        if (strcmp(name, funcName) == 0) {
            WORD ord = ordinals[i];
            return (DWORD)dos + funcs[ord];
        }
    }
    return 0;
}

步驟 5:呼叫函數與卸載


// 使用範例
typedef void (__stdcall *PFN_GETBIOS)(char* buffer);
PFN_GETBIOS pfn = (PFN_GETBIOS)GetProcAddressDirectly((PIMAGE_DOS_HEADER)hMemDll, "getbios");
if (pfn) {
    char bios[256];
    pfn(bios);
    MessageBox(bios);
}

// 卸載
void UnloadPbDllFromMemory(PIMAGE_DOS_HEADER dos) {
    PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((BYTE*)dos + dos->e_lfanew);
    DLL_ENTRY_POINT entry = (DLL_ENTRY_POINT)((BYTE*)dos + nt->OptionalHeader.AddressOfEntryPoint);
    entry((HINSTANCE)dos, DLL_PROCESS_DETACH, NULL);
    VirtualFree(dos, 0, MEM_RELEASE);
}

範例 DLL 功能

附帶 hardware.dll 提供硬體資訊查詢:

  • getmac - 網卡 MAC 位址
  • getserial - 硬碟序號
  • getcpuid - CPU ID
  • getbios - 主機板 BIOS (僅支援 AWARD BIOS,讀取 0xF0000 區域)
  • changeres - 變更螢幕解析度
  • DPGetDefaultPrinter - 取得預設印表機

限制與注意事項

  • 資源區塊問題:含資源區塊的 DLL 在 Win9x 可能映射失敗
  • TLS/靜態變數:未處理 Thread Local Storage 回呼
  • 安全性:DEP/ASLR 環境下需調整 VirtualAlloc 保護屬性
  • 相依性:被嵌入 DLL 的匯入依賴仍需存在於系統目錄

參考資料

  • MSDN: Peering Inside the PE: A Tour of the Win32 Portable Executable File Format
  • Microsoft Portable Executable and Common Object File Format Specification
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved