0x00 前言
DLL劫持是指劫持系统的DLL加载流程,即利用不同目录的加载顺序优先级,使本该加载正常DLL的流程被劫持,转而加载同名的恶意DLL。该手法常用于权限维持,权限提升等,而在近几年,利用DLL劫持通过白+黑(白名单程序加载恶意黑DLL)的方式获取目标权限上线的攻击方法也非常常见。由于白+黑的利用方式具有一定的免杀效果,所以在近几年的攻防演练中,一般都有白+黑钓鱼样本的身影。本文将详细解读DLL劫持的原理以及实现方法。
0x01 DLL加载流程
当一个可执行文件运行时,Windows加载器将可执行模块映射到进程的地址空间中,加载器分析可执行模块的输入表,并设法找出任何需要的DLL,并将它们映射到进程的地址空间中。当应用程序未指定加载DLL的完整路径时,将会按照以下顺序搜索DLL并进行加载。
Windows系统DLL搜索顺序:
*进程内存空间/KnownDlls
1、EXE所在目录/加载目录
2、当前目录GetCurrentDirectory()
3、系统目录GetSystemDirectory()
4、WINDOWS目录GetWindowsDirectory()
5、环境变量 PATH 所包含的目录
*为前置搜索,1-5为标准DLL搜索顺序。当应用程序加载DLL时如果仅指定DLL名称时,将按照1-5的顺序搜索DLL文件,不过在加载之前还需要判断是否满足以下前置条件,即不在标准搜索顺序的范围内:
当内存中已加载相同模块名称的DLL时,系统将直接加载该DLL,不会进行搜索。KnownDlls是Windows下的一种用来缓存经常用到的DLL文件的机制,也能在一定程度上防止DLL劫持。KnownDlls在进程启动时都会被预加载到进程内存空间。
KnownDlls记录的地方:HKLMSYSTEMCurrentControlSetControlSession ManagerKnownDLLs
安全DLL查找模式
Win7以后安全DLL查找模式默认是禁用的,启用的话,可以将注册表HKEY_LOCAL_MACHINESystemCurrentControlSetControlSession ManagerSafeDllSearchMode设为1。
开启安全DLL查找模式后,查找顺序中的当前目录会移到Windows目录之后。但该功能并不会影响大多数非KnownDlls的劫持,因为一般都会将恶意DLL与EXE文件放在同一目录。
当前目录与EXE所在目录区别
如果一个进程在设计的时候,未通过lpFileName指定当前目录这一参数,那么如果它是在进程所在的文件夹被点击启动的,进程的当前目录就是进程所在目录;如果是通过其他方式进行调用,那么当前目录就是其他方式所依赖的进程所在的目录。比如,从位于system32路径下的cmd窗口调用一个进程,那么进程的当前目录就是system32下而不是EXE文件所在的目录;如果一个进程是通过服务启动,那么进程的当前目录就是services.exe所在目录,即system32目录。
这两者的区别也可以理解为快捷方式属性中“起始位置”和“目标”的区别。
0x03 DLL劫持简介
利用DLL寻找的不同目录的先后顺序,劫持加载流程,使可执行文件转而加载恶意的同名DLL,就是DLL劫持。比如说,一个白名单程序a.exe,需要加载系统目录下的b.dll,我们在a.exe的同目录下放入一个恶意的DLL文件,将其也命名为b.dll,当运行a.exe时,就会加载同目录下的恶意DLL而不是系统目录下的DLL。
也有一些白+黑样本利用软件安装目录下的非系统DLL,将原DLL替换为同名的恶意文件,根据DLL标准加载顺序,投递的软件进程一定会加载同目录下的恶意DLL。
部分杀软在检测病毒的时候,首先会通过黑白名单校验,然后进行病毒特征库查询、上传云查杀等。在黑白名单校验阶段,如果是白名单中的应用则可以成功运行,白名单也就是具有有效数字签名的应用。所以白+黑的利用方式具有一定的免杀效果。
目前最常见的白+黑利用方法,就是攻击者收集带有数字签名白进程+白进程需要加载的DLL这种可被利用的组合,在白进程所在目录下放一个与原本要加载的DLL同名的恶意DLL,恶意DLL文件一般会设置为隐藏属性,与白进程一起打包投递。受害者一旦被诱导点击了程序,恶意DLL就会被加载,从而使攻击者获取到目标系统权限。
0x04 样本设计注意问题
1、不要劫持KnownDLLs。Know DLLs注册表项里的DLL在应用程序运行后就已经加入到了内存空间中,多个进程公用这些模块,必须具有非常高的权限才能修改,劫持难度非常大。
2、有时白程序的正常执行可能还需要其他非系统DLL,如果是这种情况,还需要将所需的其他DLL也一起打包。一般来说,除了被劫持的DLL之外,白程序正常运行起来需要的非系统自带DLL越少越好。
3、如果要在DllMain中运行shellcode,shellcode中不可调用LoadLibrary,否则会出现死锁导致程序无法正常运行(比如CS的shellcode)。
4、要避免第3条这种情况,可以在DllMain中创建一个新的线程来执行shellcode,但只对于运行后不会自动退出的程序有效,因为程序自身退出后会杀死自身所有的线程(shellcode运行的实质是形成一个死循环不断连接C2,在主线程中的shellcode运行起来之后可以保证程序不会自动终止)。也可以在导出函数中插入恶意代码,就没有了这个限制。
5、有的DLL还会依赖其他的非系统自带DLL,一环套一环,这样的比较麻烦最好不要选择。
6、恶意DLL的位数需要与原DLL相同,如果要插入shellcode,shellcode的位数也需要与DLL相同。
7、如果要在DLL的导出函数中实现恶意代码,最好选择程序会调用的第一个导出函数,否则程序可能会因为之前调用的导出函数未正常实现而报错退出。
0x05 DLL劫持实现
一般来说DLL加载有两种方式,导入表静态导入和LoadLibrary动态加载。导入表静态导入DLL一定会触发DLL搜索流程;未使用LoadLibraryEx()指定DLL绝对路径时,也会触发DLL搜索流程。
1、导入表静态导入
对于程序静态导入DLL的劫持,需要在恶意DLL中实现和导出目标DLL的导出函数,否则在程序执行阶段解析导入表导入函数时会报错退出。
一般我们需要在恶意的同名的DLL中实现并导出原来DLL中用到的导出函数,但导出函数的实现可以不与原DLL完全一致。一般会在导出函数的实现中或者DllMian中调用恶意代码,这种方法投递时需要有白程序+恶意DLL。
一般来说,一个DLL可能被多个程序加载,所以一个程序不一定会导入DLL中所有的导出函数,只需要实现和导出目标程序导入的几个导出函数即可。
找到一个适合劫持的目标,这里使用蓝信的主程序:LxMainNew.exe + Lxbase.dll。蓝信主程序的导入表只有这一个非系统DLL,是一个很好的劫持目标。
1.1 DllMain中插入恶意代码
创建一个的动态链接库项目,与原DLL位数要相同。实现一个弹出计算器的函数,在DllMain函数中调用它,shellcode的位数需要与程序位数一致(在DllMain中插入恶意代码比较简单,只要加载DLL就能执行):
实现导出函数,内容可以为空,因为我们要在DllMain中执行恶意代码,导出函数的实现不重要,只要导出表里面有就行。
构造导出表,需要将程序的导入表涉及的函数都进行导出,这里可以将所有导出函数的实现都指向上面那一步的我们实现的导出函数daochu()。导出函数的编号最好与原DLL中的对应函数的编号一致(十六进制编号需要在代码里转换成十进制):
整体的代码结构就是这样,然后将编译出的DLL命名为Lxbase.dll,与LxMainNew.exe放在同一目录下。运行LxMainNew.exe,成功执行shellcode:
LxMainNew.exe成功加载了同目录下的Lxbase.dll:
这里还可以验证EXE所在目录与当前目录的DLL搜索流程,我的电脑安装了蓝信应用程序,桌面上的快捷方式指定了LxMainNew.exe的进程所在目录(上)和进程当前目录(下):
将恶意的Lxbase.dll放在进程当前目录下,原来的Lxbase.dll依然在程序所在目录,也就是安装目录下。点击快捷方式,加载的是程序所在目录中的dll,可以正常启动蓝信程序:
将程序所在目录下的原Lxbase.dll删除,只保留程序当前目录下的恶意dll,再次点击快捷方式,发现加载的是当前目录下的dl,弹出了计算器:
这样就验证了在DLL的搜索流程中,当前目录是在EXE所在目录之后,两者是有区别的。
1.2 导出函数中插入恶意代码
根据上面提到的样本设计注意的问题,在DllMain主线程中不能使用LoadLibrary,有一定的局限性,比如有些情况不能运行CS的shellcode。但如果在导出函数中插入恶意代码,就没有了这个限制。但是由于我们并不知道每个导出函数的调用时机,不能确定程序运行之后一定会调用哪个函数,所以需要进行动态调试。
这里使用一个微信主程序的白利用:WeChat.exe+version.dll,该dll为系统自带,默认加载system32下的。
有的软件程序会对运行环境进行检查,将微信主程序单独拿出来运行时,提示环境异常,但是没关系,只要导出函数的调用时机在环境检查之前(经过调试发现确实如此),就可以利用。
在WeChat.exe导入的version.dll中的三个导出函数下断点,运行
发现首先断在了GetFileVersionInfoSizeW函数上,说明点击主程序之后,是有调用该函数的时机的,说明我们可以在这个导出函数的实现中插入恶意代码。
将GetFileVersionInfoSizeW函数导出时,使其实现指向运行CS的shellcode的函数,其他两个导出函数的实现可以是无实质内容的函数(但是得有函数)。
然后将生成的恶意version.dll与WeChat.exe放在同一目录下,点击WeChat,上线:
2、LoadLibrary动态导入
如果程序开发者没有指定LoadLibrary动态加载的DLL的绝对路径,就可以对程序动态导入的DLL进行劫持。这种情况下,不需要在恶意DLL中实现和导出原DLL的所有导出函数,可按需导出和实现。
扩展:如何指定DLL动态加载的绝对路径?
LoadLibraryEx(<span "="" class="character">"DLL绝对路径<span "="" class="character">", NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
通过指定LOAD_WITH_ALTERED_SEARCH_PATH,让系统DLL搜索顺序从指定DLL所在目录开始。
这里选择Windows自带的恶意软件清除工具mtr.exe作为目标。
将mrt.exe进行动态调式,设置DLL导入的断点,随着程序的运行,查看导入的DLL,与其静态导入表对比发现多了许多,说明该程序动态导入了很多DLL。在这些动态导入的DLL中,除了KnownDlls,其他的都有可能被劫持。
这里我选择了系统目录下的prosys.dll进行尝试。由于这个程序在跑完之后会退出,所以如果要使用CS的shellcode,不能在DllMain里插入恶意代码(CS的shellcode调用了LoadLibrary)。为了劫持稳定,需要找到程序在这个DLL里调用的第一个导出函数进行劫持。在这个DLL的所有导出函数上下断点。
调试发现propsys.dll中第一个调用的导出函数是PSCreateMemoryPropertyStore,可以在这个导出函数的实现中插入恶意代码。
由于是动态导入DLL,不像静态导入需要在程序执行初始阶段进行导入函数检查,所以只需要实现和导出需要用到的函数即可。不过也可以为了提高样本逆向分析的难度多实现一些无用的函数。
将生成的dll命名为propsys.dll,将mrt.exe拿出来与它放在同一目录,点击上线:
3、DLL代理转发
DLL代理转发是指在恶意DLL中将导出函数的实现指向原来的DLL中的导出函数,即不需要再在恶意DLL中进行导出函数的实现,只需要转发到原DLL即可。这种方法投递时,除了有白程序+恶意DLL,还需要有一个原DLL,由于恶意DLL被命名为原DLL的名称,原DLL需要被重命名。
DLL代理转发一般是在DllMain中实现恶意代码,这样可以实现在不影响程序正常执行的情况下执行恶意代码。也可以在导出函数中实现恶意代码,恶意的导出函数不进行转发代理即可。
这里选择网易云音乐的一个程序cloudmusic_reporter.exe + libcurl.dll进行测试。刚好这个程序是运行后不会自动退出的程序,可以在DllMain中创建一个新的线程来执行CS的shellcode。
首先进行导出函数的转发,我使用了SharpDllProxy(https://github.com/Flangvik/SharpDllProxy)这个工具,但是这个工具使用时需要一个shellcode的bin文件,DLL执行时需要从文件里面读取shellcode,投递时需要再带一个bin文件。这样动作有些大,可以自行修改代码直接把shellcode写在代码里。
给这个工具传入原始DLL和bin文件,会生成一个c代码文件和一个随机命名的DLL文件,代码文件是恶意DLL的代码,随机命名的DLL文件是原始的DLL文件。
恶意DLL的代码中将所有导出函数的实现都转发到了原DLL中。
在DllMain中创建一个新线程执行shellcode:
编译之后将DLL命名为libcurl.dll,与重命名的原DLL、cloudmusic_reporter.exe放在同一目录下,点击程序,上线:
将生成的dll命名为propsys.dll,将mrt.exe拿出来与它放在同一目录,点击上线:
注:本文内容仅用于交流学习,不可用于网络攻击等非法行为,否则造成的后果均与本文作者无关,维护网络安全人人有责~
原文始发于微信公众号(红蓝攻防研究实验室):一文读懂DLL劫持与白+黑样本原理
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论