逆向工程部分

六、逆向工程

(一)对抗反汇编

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进行字符串检测等静态分析

条件允许的情况下,可以直接采用动态分析手段,观察恶意行为

posted @ 2016-09-02 19:58  隐念笎  阅读(1183)  评论(0编辑  收藏  举报