2017-06-02 10:10:58
阅读:323次
点赞(0)
收藏
来源: exploit-db.com
作者:興趣使然的小胃
翻译:興趣使然的小胃
预估稿费:200RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
一、简介
1.1 什么是后门
在本文中,后门植入指的是使看似无害的可执行文件(即PE文件,Portable Executable)执行恶意载荷的一种技术。包括启动calc.exe、添加用户账户以及获取远程shell等都可以算是恶意载荷。任何能够自给自足的代码都可称为shellcode。
虽然杀毒软件的绕过技术并不是本文的重点,但本文也会通过迭代分析来证明这种后门植入手段在绕过杀毒软件方面的有效性。虽然这一主题已经被人反复提起,但之前的分析没有涉及ASLR以及已有代码洞(code cave)的利用,而本文刚好覆盖了这两方面内容。
Josh Pitts开发了The Backdoor Factory这个工具,可对可执行文件进行自动化地后门部署。感谢他帮我做了一些分析,然而,不要过度依赖这个工具,毕竟知道如何手动部署后门并不是一件坏事。
那么,什么是后门植入的最佳目标呢?
由于我们希望可执行文件最终创建反弹型(或者绑定型)shell,因此当用户发现程序产生网络流量或者请求添加防火墙例外时,程序不应该引起他们的警觉。因此,这种情况下后门的最佳目标可以是NetCat、SSH/Telnet客户端以及其他程序。
另外一类目标可以是软件破解工具,肯定有用户想在不付费的前提下玩某些游戏,因此他们会下载经过破解的.exe文件。他们对这类文件比较信任,认为游戏破解版除了不需要付费意外,并不会有其他可疑行为。
我们会在本文中以PsExec(Sysinternal工具集中的一个工具)这个工具作为目标。之所以将它作为目标,原因有几点,比如系统管理员经常使用这个工具、该工具的确有网络流量,也会与其他机器通信。它最初的目的也是加载和执行二进制程序,因此当使用它创建绑定/反弹型shell时也不会那么可疑。最有趣的是,Sophos杀毒软件将PsExec检测为恶意软件(这是什么鬼?),因此我们不会将Sophos杀软的检测结果考虑在内。
读懂本文需要事先掌握哪些知识?
其实这些知识都是我自学的,因此可能读者也不需要掌握太多背景知识。但如果读者想要掌握本文的全部知识点,复现所有实验,我建议可以先掌握有关x86汇编、shellcode编写、调试器(特别是OllyDbg/Immunity)的使用以及驻留技术等相关知识。
1.2 实验环境
为了保护我们的系统,我们在修改和执行载荷的时候应该使用虚拟机。读者可以根据自己的喜好使用任何工具,我在本文中使用的系统及工具如下所示:
虚拟机1:windows 7 SP1(x86)
Immunity Debugger (http://debugger.immunityinc.com/ID_register.py)
LordPE (http://www.malware-analyzer.com/pe-tools)
XVI32 (http://www.chmaas.handshake.de/delphi/freeware/xvi32/xvi32.htm)
Stud_PE (http://www.cgsoftlabs.ro/dl.html)
Netcat (可以在Kali系统的/usr/share/windows-binaries/目录中找到)
PsExec (https://technet.microsoft.com/en-ca/sysinternals/bb897553.aspx)
虚拟机2:Kali linux(2016.2 32位版本已通过实验验证,但其他版本应该也可以)
该系统已经自带了我们需要的所有工具
1.3 PE结构简介
本节会重点介绍PE文件的某些字段,这些字段在下文讨论的后门植入中会有所涉及。读者可以参考此处资料了解更多细节,但这里我们会重点介绍与后门植入有关的那些字段。
在Windows 7虚拟机中,使用LordPE载入PsExec.exe,可以看到如下界面:
相关字段为:
入口点(EntryPoint):距离基址的虚拟偏移量,指向需要运行的第一条指令。
镜像基址(ImageBase):用于映射可执行文件的首选基址,默认值为0x00400000,但可以被覆盖。如果使用ASLR编译,该值会被忽略。
块对齐(SectionAlignment):内存中区块的对齐值,这个值不能小于内存页面大小(4096字节)。内存中的块大小必须是该值的整数倍。
文件块对齐(FileAlignment):原始文件中区块的对齐值,通常为512字节。
标志字(Magic):文件特征的另一种说法(这里并没有什么魔法(magical))。
区段数目(NumberOfSections):在文件头之后定义的区段数目,稍后再讨论这个值。
校验和(Checksum):镜像文件的校验和。
可选头部大小(SizeOfOptionalHeader):正如字面含义所言。可选头部中包含某些数据,如首选镜像基址、入口点、校验和以及其他字段。
点击“区段(Sections)”按钮,如下所示:
正如“区段数目”中所显示的数值,该文件有5个区段。
.text段包含可执行代码,因此默认情况下该区段需要为可读以及可执行。.data段以及.rdata段包含只读数据,设置可执行标识就可以执行这个区段中的数据。.rsrc段包含资源数据,.reloc段通常不是必需区段,除非内存中的基址存在冲突。
现在来看一下其他字段:
VOffset:载入内存后,区段距离镜像基址的偏移量。
VSize:载入内存后,区段的大小。
ROffset:磁盘中文件的实际偏移量,你可以使用二进制编辑器工具查看这个值。
Flags:包含定义区段“权限”的那些标志。为了便于查看,你可以右键点击某个选定的区段,选择“编辑区段头部”,然后再点击“Flags”文本框右侧的小按钮。
1.4 代码洞(Code Caves)
Drew Benton写过一篇介绍代码洞(code caves)的非常好的文章,在这篇文章中,他将代码洞定义为“一种重定向机制,程序执行流程会被重定向到另一个位置,然后再返回到之前离开的那个区域”。对于后门植入而言,代码洞指的就是一个全新的或者没被使用过的死亡空间,我们可以将自定义代码放在这段空间中,然后在没有破坏可执行文件的前提下,将程序执行流程重定向到这个位置。
我们将回顾几种技术:
1、添加全新的区段
优点:可用空间很大
缺点:增加二进制文件的大小,更为可疑。
2、使用现有的死亡空间
优点:文件大小没有改变,不是那么可疑。
缺点:空间可能非常小,可能需要改变区段权限,才能执行代码。
这篇文章中没有涉及以下两个技术点:
1、拓展最后一个区段
优点:区段数目没有改变
缺点:二进制文件大小增加,文件更为可疑,严重依赖最后一个区段。与添加新区段相比没有优势。
2、代码洞跳转
优点:更为灵活,可以单独或混合使用现有的技术,隐蔽性可能更好。
缺点:需要将载荷切割成更小的单元,可能需要更改多个区段的权限。
1.5 地址空间布局随机化(ASLR)
地址空间布局随机化(Address Space Layout Randomization,ASLR)是一种安全机制,可以将可执行文件/DLL文件以及其他内存段(如栈或者堆)的位置随机化处理。这种机制可以避免基于固定函数/代码段跳转的漏洞利用技术。
当在支持ASLR的操作系统上使用/DYNAMICBASE选项编译PE或者DLL时,程序不需要考虑.reloc段(前文提到过)。当修改程序指令时,我们不能使用固定跳转方式,相反我们必须使用在当前指令和下一条指令之间的相对偏移量(后文会详细介绍)。
如果你想查看ASLR,你可以使用Immunity工具加载PsExec,然后查看“内存(Memory)”选项卡(ALT+M):
基址为0113 0000。重新启动程序(你需要关闭Immunity):
基址变成了0016 0000。目前关于ASLR你只需要了解这些信息就可以。
1.6 文件偏移量及RVA
正如前文讨论过的,当某个PE文件被载入到内存中时,它在内存中的映射方式与在硬盘中的映射方式有所不同,我们需要记住这几个名词,下文会涉及到这几个词:
文件偏移量(File Offset):当前文件中的位置,可以使用十六进制编辑器查看这个值。
基址(Base Address):载入内存中的起始地址。默认首选值为0x00400000,但如果开启了ASLR,每次加载时这个值都会发生变化。
虚拟地址(Virtual Address):载入内存中的段地址,虚拟地址包含程序的起始基址。
相对虚拟地址(Relative Virtual Address,RVA):与虚拟地址含义相同,不过需要减去基址。
入口点位于9DE6,不过这是一个RVA地址,因此映射到内存中后,这个地址应该为: + 。类似地,我们看到的ImageBase是一个首选值,如果该值被占用,PE加载器会去查找另一个可用的地址。如果启用了ASLR,这个值会被完全忽略掉。
使用Immunity加载PsExec,你会看到如下信息;
接下来,转到内存窗口(ALT+M):
当程序被载入内存后,内存中区段的映射方式与文件中的区段映射方式有所不同。如果你查看Size这一列,你会发现所有区段的大小都是4096的倍数(读者是否还记得SectionAlignment这个字段?)。BaseAddress为0x00F0000,我们可以查看PE头部中的起始地址,也可以查看Owner列中的值来获取BaseAddress。
我们还可以看一下SizeOfHeader字段,这个字段位于400h处,然而它被映射到1000h字节处,因此.text段的FileOffset字段与它的RVA地址之间存在600h字节的偏移量。附录中的公式2会告诉你如何计算这个值。
二、手动植入后门
2.1 控制执行流程
一个最基本的后门可以按照如下步骤实现:
1、劫持代码执行:执行后门的最简单的方法就是使用JMP代码洞替换ModuleEntryPoint处的指令。JMP代码洞可能会覆盖不止一处指令,因此需要将这些指令以及紧随其后的指令保存下来。
2、保存当前状态:二进制程序的执行对后门隐藏来说至关重要,我们需要保存所有位于寄存器和标志中的值。我们可以使用PUSHAD和PUSHFD完成这一任务,注意观察ESP寄存器的值。
3、执行恶意载荷:现在我们可以安全地执行shellcode。
4、对齐栈:shellcode可能会将数据推送到栈中。由于我们需要恢复寄存器以及标志的值,因此ESP可能需要对齐。我们需要将该值与步骤3之后的ESP值进行对比,然后再对齐(ADD ESP,对齐)。
5、恢复状态:使用POPFD/POPAD恢复状态。需要以相反顺序完成这个操作,因为栈是LIFO(后进先出)结构。
6、执行被覆盖的代码:我们在步骤1覆盖了某些指令,是时候恢复这些指令了。
7、继续执行流程:最后一个步骤是跳转到程序正常执行流程中的下一条指令,执行该指令,恢复程序的正常执行流程。
后门植入前后的程序结构如以下两图所示:
2.2 典型的后门植入方法
第一种方法是在原始PE文件的末尾添加一个全新的区段,通常一个Meterpreter载荷大约为350个字节,我们可以使用Stud_PE工具来创建一个新的区段。
请注意:我之所以使用Stud_PE,而不使用LordPE+十六进制编辑器,原因在于后者对我来说有时候会出错,你可以按照自己的喜好选择合适的工具。
打开Stud_PE,将PsExec.exe拖到窗口中,转到Sections标签页,右键选择“New Section“,按照下图所示填入对应字段:
区段添加完毕后,你可以看到如下信息:
该区段的默认标志为RWX,因为区段应该为可读且可执行的,如果在内存中修改这个区段,我们还需要设置区段的可写标志。
非常好,区段添加成功,双击这个区段,你会看到一堆null值。
我们的载荷应该存放在这个区域。在继续下一步之前,我们需要看检查这个文件的可疑程度。我们可以使用VirusTotal.com这个网站,探测常用杀软厂商对这个文件的扫描结果。虽然这个网站会共享样本数据,但不共享样本的另一个网(NoDistribute.com)貌似发生了故障,认为这个文件不可疑(0/35的检出率)。此外,我不介意分享这个文件,因此这样做对我来说没有什么损失。
请注意:Sophos反病毒软件默认情况下会将PsExec标记为恶意软件,因此我会忽略Sophos的检测结果。
添加一个额外的区段后,3/59的杀毒软件认为它是可疑的。现在让我们继续往前走。
接下来,我们需要劫持第一条指令,跳转到我们新创建的区段,为此,我们需要.test段、第一条CALL指令以及下一跳指令的RVA地址。
001C9DE6>$E815770000CALLPsExec_b.001D1500 001C9DEB.^E97BFEFFFFJMPPsExec_b.001C9C6B 001D1500的RVA地址为RVA_11500。001C9DEB的RVA地址为RVA_9DEB,.test的RVA地址为RVA_7D0000。请注意:如果JMP代码洞覆盖了不止一条指令,你同样需要考虑其他情况。幸运的是,CALL PsExec_b.001D1500操作码的大小满足JMP代码洞的需求。
我们将使用nasm_shell.rb(属于Metasploit框架的一部分)来获取正确的指令。如果你使用“JMP .test_section_start“的方式,程序可能会正常工作一次,但待跳转的地址会被硬编码到文件中,重启后就不能正常工作。
为了能从9DE6跳转到7 D000,我们需要使用的偏移量是7 321A。
复制生成的操作码(E915320700),回到Immunity。在第一条指令上右键,依次选择“Binary“、”Binary Paste“。
单步执行(F7),你会跳转到.test区段的起始地址:
重启调试器(CTRL+F2),粘贴新的操作码,右键依次选择“Copy To Executable“、”All Modification“。在新窗口中,右键选择”Save File“。将文件保存为PsExec_bkdr1.exe。
打开新创建的可执行文件,你可以看到新的覆盖后的指令(你认为指令变了?再仔细看看)。接下来,转到新创建的区段,让我们添加一些代码。
1、使用PUSHFD/PUSHAD将值存放到寄存器/标志中。
2、填充大约400个NOP指令(为shellcode和栈对齐预留的空间)。
3、使用POPAD/POPFD。
4、覆盖(多条)指令(也就是劫持ModuleEntryPoint)。
5、JMP到下一条指令。
最后,你需要恢复寄存器/标志的值。
内存看起来应该如下所示:
RVA_7D000-RVA_7D001:PUSHAD/PUSHFD RVA_7D002-RVA_7D1FD:NOP指令(为shellcode和栈对齐预留的空间) RVA_7D1FE-RVA_7D1FF:POPFD/POPAD(栈为后进先出结构)从011D D200(RVA_7D200)开始,我们希望添加如下几条指令:
CALLRVA_11500 JMPRVA_9DEB我们需要使用 “CALL RVA_11500“指令,通过nasm_shell很容易就能做到:
复制操作码,确保你已经为新的指令选择了足够大的空间。
从RVA_7D205跳转到RVA_9DEB的情况与之类似:
最后的改动应该与下图类似:
将改动保存到PsExec_bkdr2.exe中。可执行文件运行起来应该与原始文件一样,因为代码洞正确处理了程序的执行流程。快速扫描后,我们发现该文件的检出率为9/66。需要注意的是,此时此刻,这个可执行文件还没有包含任何恶意载荷。
现在我们可以使用msfvenom生成我们的载荷,我们使用的是windows/shell_reverse_tcp这个载荷。
非常需要注意的是:
1、程序如果执行默认的EXITFUNC,那么关闭shell后进程就会退出,我们不希望这种情况发生。我们可以使用“EXITFUNC=none“,这样执行流程就不会被暂停。
2、我们需要修改生成的载荷,因为它会以-1这个参数调用WaitForSingleObject(即无限等待)。我们也不希望这种情况发生。
在PUSHAD/PUSHFD命令后选择足够大的空间,将生成的载荷粘贴进去。在载荷结尾附近,修改这些命令,避免程序执行被暂停[WaitForSingleObject(-1)]:
请在PUSHFD/PUSHAD执行后以及载荷执行后关注ESP的状态,确保栈对齐。对我而言,我需要添加一条“ADD ESP, 1FC“指令。保存所有的修改,避免前功尽弃。
在Windows主机上启动一个netcat监听端,执行这个程序。你会获得一个shell。
成功了!虽然检测率已经提高到了17/60。
为了降低检测率,我们需要经过不断尝试,经历各种错误。我尝试了以下几种方法:
1、使用MSF对载荷进行编码(之前我们使用的是原始载荷):结果不妙,大多数杀软已经熟悉MSF编码处理技巧。
2、修改校验和:额,大多数杀软直接忽视了这个值。
3、压缩二进制程序(使用UPX加壳):很好,检测率降到了11/60。
目标快完成了,让我们尝试略有不同的另一种方法。
三、劫持已有的代码洞
之前的方法有几个缺点:1)文件大小发生明显的改变,2)刚开始已经有3个杀软把它标为可疑程序,即使只添加了最基本的逻辑处理流程(还没有添加载荷时),这个数量变成了9。让我们使用二进制文件中已经存在的一个空的代码洞来解决这个问题。
需要注意的是,代码洞的查找工作必须在文件形态时完成,不能等到文件被加载到内存中再开始查找。
我们会使用如下命令完成这一任务:
root@kali:~/Desktop#backdoor-factory-fPsExec.exe-c-l500-q-f: 输入文件
-c: 查找代码洞
-l: 最小的代码洞大小值
-q: 静默模式
额,我看到的到底是什么鬼?
1、BDFactory至少找到了3个代码洞,可以存放我们的后门。
2、这3个代码洞都位于.data段。
3、代码洞的开始和结束地址都是原始的文件偏移地址,为了使用这些代码洞,我们必须获取它们的RVA地址。
4、PointerToRawData/End of Raw Data:代表.data段开始/结尾的原始文件偏移值。
让我们试一下第一个代码洞,因为它位于.data区域,我们需要设置.data区域的可执行标志(使用LoadPE即可)。仅仅将.data区域设为X标志后,这个文件的检出率就变为2/60:
接下来,我们需要使用公式3,计算代码洞1偏移地址的RVA地址:
RVA = 代码洞所在区段的VOffset + 代码段的ROffset 代码洞所在区段的ROffset 当前地址 = 0x29000 + 0x272e5 – 0x27200 = RVA_290E5
使用RVA_290E8这个值:
将第一条指令替换为载荷:
将改动保存到PsExec2_bkdr.exe中,重新运行并单步执行。
现在,让我们重复相同的步骤,即PUSHFD/POPFD、添加大约400个NOP指令、POPFD/POPAD、CALL RVA_11500然后JMP RVA_9DEB。
这次修改后检测率变为5/58,与上次同一阶段的检测率相比少了4个检测结果。
接下来,我们重复之前的工作,粘贴MSF载荷,调整栈。保存更改后,再次扫描。
虽然13/60不是一个特别好的结果,但结果至少优于之前添加新区段的17/60的检测率。需要注意的是,我们并没有使用任何方式对MSF载荷进行编码、加密或者混淆。
四、人为因素
现在我们已经弃用了添加新区段的方法,我们还可以做些什么吗?我们在前面两种方法中都在入口点使用了JMP代码洞方法,这是一个好方法,能确保载荷被执行,但杀软同样也会跟踪代码的执行流程,导致检测率明显提高。
如果我们通过与用户交互来触发代码洞呢?毕竟杀毒软件不像人类那样可以充分地与可执行程序进行交互或传递参数(可能永远也达不到这个高度)。毕竟对于PsExec来说,这个程序需要传入参数,否则就会打印程序使用说明。
让我们看一下,PsExec在常规输入下的行为:
如果我们的后门hook某个字符串的打印动作会怎么样?我们可以在某个字符串被载入内存时设置断点,触发我们的后门。
Immumity这个工具支持传入命令行参数。
查找程序中所有引用的文本字符串。
右键,选择“Follow in Disassembly“,跳转到汇编代码的相应位置。
你可能会碰到某个异常,忽略它即可。让我们跟进第二个CALL(即CALL PsExec2_.003182AF)。在RET之前有某些未使用的空间可以利用,我们为什么不让程序在这里跳转到我们的载荷呢?
在RVA_8334处,我们可以跳转到我们的代码洞中(RVA_290E8)。劫持RETN指令有什么好处呢?我们可以直接使用它,而不用去在意下一跳指令是什么。
请注意:不要忘记修补入口点指令,我们再也不需要从那个位置跳转到我们的代码段。保存更改,然后启动监听器。
杀软检测结果如何?
只有9/60,这是目前最低的检测率!可能是静态分析还有点问题,且知名的杀软还是会检测出MSF载荷。
4.1 更多的杀软绕过方法
根据前文的内容,我们来思考下还有什么方法能够降低检测率。我尝试了以下方法:
1、使用strip函数处理二进制文件:结果没变化(9/60)。
2、去掉损坏的证书信息:结果不好,检测率提升到了18/60。
3、使用自定义的异或算法处理一个最小的MSF载荷。
所使用的载荷:
msfvenom-pwindows/shell_reverse_tcp-b"\x00"–smallest检测率:5/60,这是目前为止最低的检测率!
如果我们弃用MSF载荷,使用不是那么可疑的exploit-db的载荷呢?我使用的是这个载荷,没有进行编码处理。
结果非常好,只有3/59的检测率!当然我们可能还有提升的空间(比如使用加密方法?),但现在结果已经足够好了。
4.2 如何保护自己
我建议我们自己编译源码,编写自己的工具,不要信任任何人。否则,还是放弃保护自己的念头吧。
当然,现实点的建议是:
1、只从信任的源上下载程序。
2、检查校验和/哈希值。
3、给自己的系统打补丁,经常更新自己的病毒库。
4、注意提示信息,正常情况下calc.exe肯定不会请求添加防火墙例外的。
5、希望一切顺利,不要无脑单击到底。
感谢读者阅读本文。
五、附录
5.1 公式
1、(模块入口点) = (基址) + (入口点)
2、EntryPoint在文件中的偏移 = EntryPoint – ( )
3、代码洞的RVA地址 = 代码洞所在区段的虚拟偏移 + 代码洞的原始偏移 – 代码洞所在区段的原始偏移
5.2 代码仓库
1、https://github.com/abatchy17/Introduction-To-Backdooring
2、https://github.com/abatchy17/SLAE
六、参考资料
[1] https://en.wikibooks.org/wiki/X86_Disassembly/Windows_Executable_Files [2] https://sector876.blogspot.com/2013/03/backdooring-pe-files-part-1.html [3] https://pentest.blog/art-of-anti-detection-2-pe-backdoor-manufacturing/ [4] https://github.com/secretsquirrel/the-backdoor-factory [5] http://blog.sevagas.com/IMG/pdf/BypassAVDynamics.pdf [6] https://www.codeproject.com/Articles/20240/The-Beginners-Guide-to-Codecaves本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://www.exploit-db.com/docs/42061.pdf