• 逆向工程部分


    六、逆向工程

    (一)对抗反汇编

    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_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionAeDebug"位置的值,默认的是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进行字符串检测等静态分析

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

  • 相关阅读:
    6. (在第五步的基础上展开)实现模板推送发送
    5. (全局唯一接口调用凭据)获取Access token
    3. openid的获取
    2. 验证服务器地址的有效性
    Java后端开发规范
    4. (自定义菜单和删除全部菜单)Springboot读取静态json文件
    Docker私有仓库搭建与部署
    Docker容器基础学习一
    运维日志切割--logrotate
    zookeeper学习
  • 原文地址:https://www.cnblogs.com/zlgxzswjy/p/5835471.html
Copyright © 2020-2023  润新知