逆向工程部分
六、逆向工程
(一)对抗反汇编
1、反汇编算法:
(1)线性反汇编算法:
遍历代码段,一次一条指令的先行反汇编,用已经反汇编的指令大小来决定下一个要反汇编的字节,而不考虑代码流的控制指令。
不能区分代码与数据,最容易被恶意代码挫败
(2)面向代码流的反汇编算法:
检查每一条指令,然后建立一个需要反汇编的地址列表,而不是盲目的反汇编整个缓冲区,也不假设代码段中仅包含指令而不包含数据
2、对抗反汇编技术
(1)相同目标的跳转指令
使用指向同一目的地址的两个连续条件跳转指令,如:
jz loc_001
jnz loc_001
此时连着两个跳转指令相当于一个jmp指令,无论如何都会跳转到loc_001这一位置
(2)固定条件的跳转指令
跳转条件总是相同的一条跳转指令构成的
(3)无效的反汇编指令
某些情况下,常规的汇编列表不能表达运行指令,成为无效的反汇编指令
3、混淆控制流图
(1)大量使用指针,可以大大降低反汇编器自动推导出程序流的信息量
(2)在idapro中添加代码的交叉引用
(3)滥用返回指针
(4)滥用结构化异常处理
4、挫败栈帧分析
(二)反调试技术
1、探测windows调试器
(1)使用windows api
【IsDebuggerPresent】:
如果进程灭有运行在调试器环境中,返回0;如果调试附加了进程,返回非0;
【CheckRemoteDebuggerPresent】:
功能与上一个函数相同,不过这个函数可以通过传参检查其他进程是否被调试;
【NtQueryInformationProcess】:
用来提取给定进程的信息,第一个参数为进程句柄,第二个参数指定所要提取的信息,如果指定为ProcessDebugPort(0x7),则显示该进程是否被调试,如果是则返回调试端口,否则返回0
【OutputDebugString】:
该函数的作用是在调试器中显示一个字符串,如果进程被调试,该函数调用成功,否则失败,通过判断该函数调用成功与否,可以判断进程是否被调试
使用setlasterror函数设定一个任意错误值,如果函数调用失败,则该错误值被修改,成功则未被修改
实例:
*******************************************************
DWORD errorValue=12345;
SetLastError(errorValue);
OutputDebugString("Test for Debugger");
if(GetLastError()==errorValue)
{
ExitProcess();
}
else
{
RunMaliciousPayload();
}
*******************************************************
(2)手动检测数据结构
windows api的检测通过被rootkit挂钩,可能会失效,有时候需要手动检测数据结构来进行判断是否被调试
【检测BeingDebugger属性】
通过检查进程PEB结构的fs:[30h+2]位置的BeingDebugged标志,确定是否被调试
绕过方法:
执行跳转指令前,手动修改零标志,强制执行跳转
手动设置BeingDebugged属性为0
【检测ProcessHeap属性】
Reserved4数组中未公开的位置,它被设置为加载器为进程分配的第一个堆的位置。第一个堆头部有一个属性字段,告诉内核是否在调试器中创建,ForceFlags和Flags属性
绕过方法:
手动修改ProcessHeap标志或者使用调试器隐藏调试插件
【检测NTGlobalFlag】
系统使用PEB结构偏移量为0x68处的一个未公开位置来决定如何创建堆结构,如果这个位置为0x70,则该进程运行在调试器中
绕过方法与上相同
(3)系统痕迹检测
可以检测注册表中"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug"位置的值,默认的是Dr.Watson,如果被修改,那么可能确定正在被调试
也可以通过查找文件系统,内存痕迹,进程列表去顶调试器是否存在
还有通过FindWindow来查找调试器
2、识别调试器的行为
(1)INT扫描:
设置断点的基本机制是用软件中断指令INT3(机器码为:0xCC)临时替换运行程序中的指令,无论何时,只要使用调试器设置断点,都会插入0xCC来修改代码,恶意代码可以通过检测自身代码是否被插入0xCC来识别调试器
绕过方法:使用硬件中断而不是软件终端
(2)执行代码校验和检查
执行恶意代码中机器码的CRC(循环冗余校验),或者MD5校验和检查
绕过方法:使用硬件断点
(3)始终检测
单步调试会大幅度降低程序运行速度,通过对时钟的检测,恶意代码可以辨别是否正在被调试
检测方法:
[1]、记录并比较一个操作前后的时间戳,如果存在严重滞后,则可判定为被调试
[2]、记录并比较触发异常前后的时间戳,如果存在严重延迟,则可判定为被调试
【使用rdstc指令】:
rdstc返回从系统启动到现在的时间,通过比较这个时间可以作为时钟检测
【使用QueryPerformanceCounter和GetTickCount】
这两个windows api功能与上一个相同,通过对比代码执行前后的时间,当时间差超出一定阈值(0x1A)后,可以判断是否被调试
绕过方法:在始终检测后设置断点,然后再执行单步调试,也可以通过修改比较结果,强制改变程序的控制流
3、干扰调试器的功能
(1)使用TLS回调
TLS回调被用来在程序入口点执行之前运行代码,所以这些代码可以再调试器中隐秘执行,可能会被调试器遗漏
一般情况下,正常的程序不使用.tls段,如果看到.tls段,则程序可能使用了反调试技术(PEview)
(2)使用异常
一般调试器默认不把异常传递给程序,在不把异常结果正确发挥到被调试进程的情况下,恶意代码内部的异常处理机制会检测到异常失效,从而判断出是否被调试
(3)插入中断
通过在合法指令序列中插入中断,让调试器误认为是其自身设置的中断,干扰调试器正常工作
【具体方法】:
插入INT 3设置软件断点;
插入INT 2D断点:内核调试器设置断点的方法;
插入ICE断点:通过intel为公开的指令:icebp,产生单步调试异常,而不执行先前设置的异常处理例程。利用这一点,恶意代码使用异常处理例程作为他的正常执行流程,而调试器则会被干扰
(三)反虚拟机技术
1、VMware痕迹
安装过vmtools的虚拟机有三个标准vmware进程:vmwareservice.exe、vmwaretray.exe、vmwareuser.exe,恶意代码通过在内存中遍历搜索字符串“vmware”,判断运行环境是否为虚拟机
通过vmtools的默认安装目录,注册表信息(虚拟硬盘驱动器、适配器、鼠标等注册信息)也可以判别是否处在虚拟机运行环境
通过检测本机MAC地址,判断网络适配器是否在VMware属性值范围内
【绕过方法】:
卸载vmtools时最常用的方法;
手动修补恶意代码对虚拟环境的探测:修改使跳转不被执行、修改比对的字符串
使用多处理器机器
2、查找漏洞指令
scoopyNG:免费开源的vmware探测工具
www.trapkit.de
(四)加壳与脱壳
加壳的目的:缩小程序的大小、阻碍对加壳程序的探测和分析
1、加壳基本概念:
(1)脱壳存根
原始程序被加壳后,被保存在新程序的一个或多个附加的节中,然后产生一个新的程序,这个新程序的程序入口点被指向脱壳存根,而不是原始代码。程序运行时,由脱壳存根加载原始程序运行
【脱壳存根的工作:】
将原始程序脱壳到内存中
解析原始可执行文件的所有导入函数
将可执行程序转移到原始程序的入口点
(2)最后脱壳的程序与原始程序不同,依然包含脱壳存根以及加壳程序添加的一些其他代码,脱壳后的程序包含一个被脱壳器重构的头部,并且与原始PE文件不完全相同
2、识别加壳程序
(1)加壳程序的标识
程序导入函数很少,有时仅有LoadLibrary和GetProcAddress
使用IDA打开程序时,只有少量代码被识别
ollydbg打开程序时收到被加壳的警告
程序节中有某些加壳器的标识(UPX)
程序拥有不正常的节,如.text节原始数据大小为0,但虚拟大小非0
加壳探测工具,PEID等可以探测程序是否加壳
(2)熵值计算
经过压缩、加密后的数据更加接近于随机数据,会有一个较高的熵值,通过计算熵值,可以判断是否被加壳
工具:Mandiant Red Curtain
3、查找OEP
(1)自动化工具查找OEP
ollydbg中的ollydump插件可以完成这个工作
(2)手动查找
寻找尾部跳转指令,这条指令就是从脱壳存根向OEP跳转的,通常是一条jmp指令
【尾部跳转的特征】:
位于代码尾部且链接到一个很远的位置,正常的跳转指令后会有一个返回,但尾部跳转之后可能会是一些毫无意义的代码,如0x00
正常的跳转指令通常被用在条件或循环语句中,跳转地址通常也在几百字节内,超过一定大小时,需要关注下是否是个尾部跳转指令
观察同一地址处的指令在运行前后的变化,如果由不可读变为可读,则有可能在该处发生了尾部跳转
4、常见壳的技巧与窍门
(1)UPX
为性能设计,而不是安全
开源、免费、易用,支持多个平台。具有很高的压缩速度和较小的空间占用
通过upx程序本身就可以对其进行脱壳(-d选项)
(2)PECompact
商业壳,为性能和速度而设计
加壳后脱壳比较困难,含有反调试异常和混淆代码
有相对明显的尾部跳转:jmp eax后有多个0x00字节
(3)ASPack
为安全而设计,使用了自我修改代码,让设置断点和分析变得困难
对其加壳过得程序设计断点会让程序立即结束,手动脱壳需要用到硬件断点
但有很多自动脱壳工具可以对其脱壳
(4)Petite
有反调试机制,发现OEP相对比较困难,为了干扰调试器使用了单步调试异常,可以通过将该异常传回到应用程序来解决这个问题
由于设计原因,被其加壳的程序,在没有脱壳的情况下也可以很容易的确定恶意代码使用了哪些dll
(5)WinUpack
有GUI终端,为优化压缩设计而不是安全,有很多自动化脱壳工具可以对其进行脱壳
(6)Themida
比较复杂,拥有多种功能,大部分功能是反调试与反逆向分析,是一个非常安全的壳,难以脱壳和分析
能够阻止虚拟机,调试器,PM的分析功能,拥有自己的内核模块,使得分析受到来自操作系统的限制
但由于功能复杂,加壳后的文件很大,而且Themida的代码会在原始程序运行后一直运行
可以考虑通过转储内存中运行程序的方法,转储被其加壳的程序,进行字符串检测这样的静态分析手段
5、不完全脱壳的情况下分析
尽管未被脱壳,ida仍可以加载它,可以通过ida分析程序的某一节,将这个段标记为代码,配合内存转储技术,对转储出来的程序拷贝运行strings进行字符串检测等静态分析
条件允许的情况下,可以直接采用动态分析手段,观察恶意行为