安全路透社
当前位置:安全路透社 > 安全客 > 正文

【技术分享】反逆向工程——使恶意软件难以逆向的技术

http://p6.qhimg.com/t01f989e9ec18296e03.jpg

前言


在攻防对抗的博弈中,恶意软件的作者总是想方设法阻止反病毒公司、安全研究人员对其编写的恶意软件进行检测、分析.在对抗中,技术的进步螺旋式上升,那么编写恶意软件的人都采用了哪些技术来对抗分析?这些技术又是如何发展的?反病毒公司如何应对这种挑战?作者 Bartosz Wójcik 给出了自己的观察。

逆向工程是指不访问其源代码来分析已编译好的程序的方法。 在这篇文章中,我想说明这是使用恶意软件的编写者阻碍病毒和其他恶意软件分析的方法,我也会解释反病毒公司和反病毒软件是怎么处理的。 
为了使反病毒公司的分析变得困难,首先你需要知道反病毒公司是如何在实验室中分析恶意软件的。最常用的方法如下:

在虚拟环境中测试恶意软件

在沙盒和模拟其中测试恶意软件

监控恶意软件对系统所做的更改

静态分析

动态分析

生成恶意软件的签名

同时,在用户计算机上运行的反病毒软件可以执行以下检查来帮助判断这个恶意程序的实际意图:

文件和片段的校验和(例如 MD5SHA1SHA2CRC32

不寻常的文件结构

文件中不寻常的值

文件片段的签名(启发式扫描)

字符串常量

应用程序的行为,被称为行为分析(监控访问系统文件、注册表等)

函数调用(调用哪些函数、提供了什么参数,调用顺序)

实验室和反病毒软件都是这么做的,这中间的每一步,都可能遇到专门设计好来阻止或减慢分析的障碍。


检测虚拟机


99%的情况下,恶意软件会被放在虚拟机中进行测试,例如VMware, VirtualBox,Virtual PC,Parallels 等。这是为了保护分析师自己的机器免受感染,这也是时有发生的! 在2011年,由于反病毒公司 ESET 一个雇员的错误,众所周知的(昂贵的)分析软件 IDA 被盗。 

通常在反病毒实验室中,恶意软件存储在没有执行权限的文件夹中,特别要防止感染文件或恶意文件的意外执行。虚拟机的使用也允许了额外工具的使用,例如比较感染后的系统映像和感染前的系统映像的区别,哪怕是对系统文件、注册表、其他系统组件极其微小的改变,都可以轻易发现。 

http://p0.qhimg.com/t01982f28528b7e46fd.png

运行在虚拟机中的恶意软件的网络流量可以被准确地跟踪(通过网络嗅探器,比如 Wireshark),这可以证明恶意软件将会和该恶意程序所属的僵尸网络的控制服务进行通信。 

由于这些原因,恶意软件的作者通常希望阻止恶意软件在虚拟机中运行,但是为了做到这一点,恶意软件必须检测到它在虚拟环境中运行了。虚拟机永远不能和真实计算机一样,因此它们具有那些“给予它们”的特性,例如:

具有仅在虚拟机中会存在的特定名称的虚拟硬件设备

对真实机器不完全、有限的仿真(IDTGDT 表)

含糊不清、没有文档记录的 API 在与主机层通信(Virtual PC 使用汇编指令 cmpxchg8b eax 这种魔法一般处理寄存器的方式,就可以确定它的存在)

附加的实用工具,比如 VMware Tools,他们的存在可以通过特定系统对象的名字得知(互斥体事件、类名、窗口名称等)

清单1-使用 API 接口确定 VWware 环境

BOOL IsVMware()
{
    BOOL bDetected = FALSE;
    __try
    {
        // check for the presence of VMware
        __asm
        {
            mov    ecx,0Ah
            mov    eax,'VMXh'
            mov    dx,'VX'
            in    eax,dx
            cmp    ebx,'VMXh'
            sete    al
            movzx   eax,al
            mov    bDetected,eax
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        // if an exception occurs
        // return FALSE
        return FALSE;
    }
    return bDetected;
}

清单2-通过 Windows 注册表判断虚拟环境

BOOL IsVM()
{
    HKEY hKey;
    int i;
    char szBuffer[64];
    char *szProducts[] = { "*VMWARE*", "*VBOX*", "*VIRTUAL*" };
    DWORD dwSize = sizeof(szBuffer) - 1;
    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\ControlSet001\\Services\\Disk\\Enum", 0, KEY_READ, &hKey) == ERROR_SUCCESS)
    {
        if (RegQueryValueEx(hKey, "0", NULL, NULL, (unsigned char *)szBuffer, &dwSize ) == ERROR_SUCCESS)
        {
            for (i = 0; i < _countof(szProducts); i++)
            {
                if (strstr(szBuffer, szProduct[i]))
                {
                    RegCloseKey(hKey);
                    return TRUE;
                }
        }
    }
    RegCloseKey(hKey);
}
  return FALSE;
}

清单3-检测 Virutal PC

    sub eax,eax     ; prepare magic values
    sub edx,edx     ; in registers
    sub ebx,ebx
    sub ecx,ecx
    db  0Fh, 0C7h, 0C8h ; 'cmpxchg8b eax' instruction
; if Virtual PC is present, after performing cmpxchg8b eax
; register EAX = 1 and there will be no exception
; otherwise an exception will occur

清单4-检测 VirutalBox

BOOL IsVirtualBox()
{
    BOOL bDetected = FALSE;
    // is the VirtualBox helper library
    // installed in the system?
    if (LoadLibrary("VBoxHook.dll") != NULL)
    {
        bDetected = TRUE;
    }
    // is the VirtualBox support device
    // installed in the system?
    if (CreateFile("\\\\.\\VBoxMiniRdrDN", GENERIC_READ, \
                   FILE_SHARE_READ, NULL, OPEN_EXISTING, \
                   FILE_ATTRIBUTE_NORMAL, NULL) \
                   != INVALID_HANDLE_VALUE)
    {
        bDetected = TRUE;
    }
    return bDetected;
}

讽刺的是,如果有检测虚拟环境存在的功能会提醒分析师这很可能就是一个恶意程序,他正在试图避免在一个安全的环境内被分析。不过,这种函数通常也被版权保护和内容保护软件使用。例如,现在常见的多媒体平台 Ipla.tv 在检测到正在虚拟环境中执行时拒绝播放节目。出于兴趣,我查看了一下这个播放器使用的库文件,不过在调试器的帮助下,禁用这个负责检测虚拟环境的功能是很容易的。 

一些软件发布商也阻止他们的应用程序在虚拟机中运行,这是一个非常实际的原因——盗版。他们将软件的运行限定在某台机器上(例如通过机器硬件配置文件生成许可证密钥)。当软件安装在虚拟机中时,可以很容易的将虚拟机映像复制到所需要的多个物理计算机上,并且这些计算机都会拥有相同的硬件配置文件,从而允许软件在多台计算机中运行了。


沙盒


沙盒就是一个和外部世界独立的安全环境,你可以在其中运行恶意程序并监视它们的活动。沙盒可以作为和主机系统相互独立地存在。最知名的是:

Cuckoo Sandbox

Anubis Sandbox

Norman Sandbox

Joe Sandbox

VIPRE ThreatAnalyzer

Buster Sandbox Analyzer

这些都是可以运行任何软件的虚拟环境,由于内置了监视工具,它们可以提供软件在启动后对系统所做更改的详细日志。通常他们基于仿真的 Windows 环境中,而且你可以很容易地检测到它们这些特性。在地下论坛上,你可以很容易的看到这种例子:

清单5-检测 Norman 沙盒

; load the address of the function DecodePointer(), which
; in a Norman Sandbox environment contains
; a different list of instructions
; to a real Windows system
; DecodePointer:
;.7C80644E| C8 00 00 00 enter   0,0
;.7C806452: 8B 45 08    mov eax,[ebp][18]
;.7C806455: 0F C8   bswap   eax
;.7C806457: C9      leave
;.7C806458: C2 04 00    retn    4
; load the address of DecodePointer
    mov eax,DecodePointer
    test    eax,eax
    je  _not_detected
; verify the byte signature
    cmp dword ptr[eax],000000C8h
    jne _not_detected
    cmp dword ptr[eax+4],0F08458Bh
    jne _not_detected
    cmp dword ptr[eax+8],04C2C9C8h
    jne _not_detected
; the presence of the Norman Sandbox environment has been detected
; terminate the application
    push    0
    call    ExitProcess
; continue the application
_not_detected:

清单中的沙盒原来大多数都是免费的,随着几个商业化后,只有少数几个继续免费,如 Anubis 沙盒。 

沙盒不需要模拟完整的系统,将沙盒集成到反病毒软件中,或者像 Sandboxie 这种特定应用程序的沙盒正在变得越来越受欢迎。它们允许在安全的操作环境中执行任何应用程序,以保护敏感的系统组件受到修改。 

http://p0.qhimg.com/t01f825f41d5aa1757a.png

Sandboxie 不会将修改写入磁盘,也不会修改任何系统文件,只会产生分析便于使用的外部文件。这种系统的运行依赖于系统底层的钩子(重写底层操作系统函数)以及使用额外的驱动程序、控制应用程序的行为。你可能已经猜到了,这些机制可以被恶意软件作者用来检测沙盒工具。

清单6-检测 Sandboxie 环境

BOOL IsSandboxie()
{
    // check if the Sandboxie helper library
    // is loaded in our process
    if (GetModuleHandle("SbieDll.dll") != NULL)
    {
        return TRUE;
    }
    return FALSE;
}


阻止代码分析


使用像 IDA 反汇编器和 HexRays 反编译器来分析可疑文件是业界通用的惯例。这些工具允许对编译好的程序进行汇编级别的分析,如果可能还可以还原成更高级别的语言。 

http://p3.qhimg.com/t0102fa3718e3269116.png

为了让这些程序执行静态分析,就必须访问未加密的可执行文件,恶意软件的作者可以基于此来让分析变得困难,甚至不可能,致使分析师必须在分析前做额外的工作。

加密

加密是最基本的手段,用来加密整个可执行文件。这些加密工具都有同一个目的,防止恶意软件被反病毒程序检测。这些加密工具在地下论坛中卖数十美元,并且每个客户会使用唯一的副本以避免被先用的反病毒程序的签名数据库检测到。 
加密工具的基础是:

加密整个可执行程序

将其附加到一个装载程序中,作为一个嵌入的资源,或者放在在文件的结尾(也就是所谓的叠加)

运行一个以这种方式准备好的程序后,可能发生两种情况,取决于恶意软件作者怎么做:简单情况下,文件被解密,解包到临时目录,并且从此处运行。更高级的做法是加载程序的代码段和数据段被取消内存映射,用解密后的可执行文件替换,之后开始运行。

加壳

UPX, FSG, MEW, ASpack, and .netshrink 这些壳都可以被用于减少可执行文件的大小。它们通常在可执行文件中插入一段结构,将一种压缩算法放在里面,制作一个装载程序。装载程序通常很小,常常用汇编语言编写。当这样一个打包好的程序运行时,由装载程序控制解压缩代码和数据、恢复可执行程序的文件结构,例如从导入表加载函数。然后从没打包时的原始程序入口点开始执行代码。 

http://p8.qhimg.com/t01c1823f192edc5056.png

这种压缩不会对真正的代码分析造成任何困难,它唯一能实现的事情就是在反病毒程序的潜在签名被改变了。现在可以自动脱壳大多数流行的加壳程序,无论是专用的脱壳模块还是通过仿真使用,并可以分析原始文件中的签名。

压缩

虽然加壳只能有助于减少可执行文件的大小,但是其受欢迎程度已经导致那些想使用加壳程序的人,还想让程序具有更难得到原始可执行代码的优势。因此混淆出现了,它们可以“争先恐后”地操作已经压缩好的程序,比如它们可能会更改节的名称、添加新的代码,然后启动解压缩程序。例如UPX-SCRAMBLER 和 UPolyX 以及非常流行的 UPX 压缩壳。

综合保护

演变的下一个阶段就是像 PELock, ASProtect, ExeCryptor 和 Armadillo 这种保护器。它们不仅能压缩可执行程序,还能添加代码来检测调试工具的存在,并且破坏或隐藏可执行程序的真实结构,使得恢复原始可执行程序的内容更加困难。保护器的一个重要部分是它们内置的强大加密算法,像 ECC(椭圆曲线加密)或 RSA 加密。 
通常对特定区域进行加密,这段区域以特殊标记指明。当程序运行,指定标记间区域的代码在没有正确的许可密钥时无法被解密,甚至直到其需要运行前仍然是加密的。运行时被暂时解密,一旦运行完毕就会返回加密状态。

清单7-在 PELock 中使用加密标志的例子

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include "pelock.h"
int main(int argc, char *argv[])
{
    // code between the markers DEMO_START and DEMO_END
    // will be encrypted in the protected file
    // and will not be accessible (or executed) without
    // the correct licence key
    DEMO_START
    printf("Welcome to the full version of my application!");
    DEMO_END
    printf("\n\nPress any key to continue...");
    getch();
    return 0;
}

这种程度的复杂保护对于破解者是一个现实的问题。虽然这对于软件作者来说一般是好的,但是这使得病毒分析师难以生存,因为这允许了恶意软件作者使用高级加密技术,这使恶意程序的行为分析特别困难。在这种系统中,你经常会发现多态的加密算法(受保护的程序每次都重新生成唯一的加密算法)、代码突变(一系列的程序集替换,这些替换更加复杂但是等效)、代码虚拟化(原生代码被字节码替换),随着将反调试招数都整合在一起,我们已经很难对其代码进行分析、了解它们是如何工作的了。 

保护的措施有:

重定向导入表-混淆使用的函数的真实地址-更难从受保护的程序中重建导入表

将文件的映像重定位到内存中的随机区域-阻止其从内存中直接将解密后的程序 dump 下来

检测调试和系统监控工具

以发现代码更改为目的主动监控引用程序代码

检测虚拟环境和沙盒

检测仿真器

通过模拟某些用来访问功能的函数来隐藏资源等结构

结合许多应用程序文件和库到一个可执行程序中,并且使用函数访问钩子来模拟存在

虚拟化

CodeVirtualizer 和 VMProtect 都是以上技术的接班人,这些工具可以让应用程序的原生代码与字节码进行替换,以这种方式保护的应用程序必须配备有执行字节码的虚拟机。 

分析那些已经替换过的字节码是非常困难的,因为他们不能直接被 IDA 和 OllyDbg 这种标准工具进行分析,这些工具不能得到中间代码的格式。更高级的虚拟机能够在每次保护可执行文件时生成唯一的字节码,从而进一步使分析受保护的应用程序变得更加复杂。 
在这些情况下,必须编写特殊的模块来将中间代码转换成 x86 代码。这是一个艰苦而又耗时的任务,而且上面提到的全部保护措施都可以与虚拟化结合起来,让分析变得更加不可能。

混淆

混淆的目的是让编译好的应用程序或者源代码都尽量变得不可能分析。 

混淆大多数用在那些被编译成字节码的应用程序中,比如 Java 和 .NET 应用程序,也有像 Pythia 这种为 Delphi 应用程序开发的混淆器。但是这些工具大多数都用于 .NET 应用程序。用 Visual Basic 编写的程序可以多达六个版本,从原生的指令到需要额外库和虚拟运行时的字节码。而 Visual Basic 的规格是不公开的,分析其应用程序是有挑战性的。然而,通过试错,创建了一些非官方工具来对这样的应用程序进行反编译。 
随着 C# 和 VB.NET 的出现,微软发布了虚拟机的规范。原来反编译这样程序的字节码是如此简单!知名的工具像 .NET Reflector 的出现,可以实现应用程序到源码的一键转换。这让软件作者很是头疼,没过多久,市场就出现了几十个工具,这些工具都能对 .NET 应用程序中的字节码(中间语言)进行混淆。这些混淆器一般都采用了如下技术:

修改指令序列执行顺序(从线性到无条件跳转的非线性)

.NET 代码的动态加密

加密 .NET 应用程序的资源

加密字符串值

编译好代码的额外虚拟化层

.NET 应用程序文件结构的蓄意破坏

现在存在的大量保护程序,大多采用类似的保护机制。混淆工具的泛滥导致许多去混淆工具的产生,以 2011 年 de4dot 的产生为终点,这是一个通用的 .NET 应用程序通用去混淆工具。它可以去除超过二十种最流行的混淆方案,像 SmartAssembly、.NET Reactor、Dotfuscator、Eazfuscator 和许多其他的方案,这是给软件安全行业一记响亮的耳光, de4dot 不断开发和维护以支持最新的软件保护方案。 
有趣的是,混淆器的价格要远比那些保护原生应用程序的软件要贵,在 .NET 平台上,复杂的 EXE 保护方案和代码虚拟化可以取得比混淆器更好的效果,最好的例子就是前面提到的 de4dot,仅此一点就可以扭转应用在 .NET 应用程序上的诸多保护措施。但是对于原生应用程序就没有如此通用的工具存在。


利益冲突


计算机程序保护的问题在保护系统提供商和反病毒公司之间已经成为了一种巨大的问题:合法的版权保护措施会被反病毒软件报告为病毒。保护系统的作者会因此失去他们的潜在客户,而看似反病毒公司和保护系统的斗争其实是与恶意程序的斗争。IEEE 协会试图规范保护系统制造者和反病毒公司之间的信息交流。该系统被称为 TAGGANT,将需要进行商业保护系统保护的每一个文件进行标记,并带有购买人或者公司的签名。有这样标记的文件将不再被反病毒程序标记为病毒,如果合法的保护方案被以某种方式应用在保护恶意程序中(例如恶意软件作者使用被盗的信用卡详细信息来购买保护方案),客户的签名就会被公开放在黑名单中,并且和该用户签名相关的所有文件都会被标记为潜在的恶意程序。


调试器检测


如果恶意程序较为复杂,像 IDA(反汇编)或 HexRays(反编译)此类的代码静态分析工具不能提供分析师判断恶意程序做了什么的信息,下一个分析师可以用的工具是调试器 – 允许逐步跟踪程序执行的工具。 

其中最流行的调试工具:内置于商业 IDA 反汇编器(32位和64位支持)的调试器,免费的 WinDbg,以及免费的(据我所知最流行的调试器)OllyDbg ,它允许在用户态下跟踪应用程序(OllyDbg 的的一个缺点是,它被限制为32位应用程序)。 

http://p2.qhimg.com/t01d1f564db2df831da.png

OllyDbg 的流行产生了一系列应对反调试器功能的改进和扩展,这些让各种应用程序的分析都变得容易。比如引入一种脚本语言,它可以自动完成调试器能做的工作,这让自动化脱所有已知种类的壳变得非常轻松。 
另一方面,又有很多种方法可以检测这个调试器,并且恶意软件的作者都很愿意使用它们,因为这样会使得他们的代码更加难以分析。

清单8-检测 OllyDby(作者 Walied Assar

int __cdecl Hhandler(EXCEPTION_RECORD* pRec,void*,unsigned char* pContext,void*)
{
    if (pRec->ExceptionCode==EXCEPTION_BREAKPOINT)
    {
        (*(unsigned long*)(pContext+0xB8))++;
        MessageBox(0,"Expected","waliedassar",0);
        ExitProcess(0);
    }
    return ExceptionContinueSearch;
}
void main()
{
    __asm
    {
        push offset Hhandler
        push dword ptr fs:[0]
        mov dword ptr fs:[0],esp
    }
    RaiseException(EXCEPTION_BREAKPOINT,0,1,0);
    __asm
    {
        pop dword ptr fs:[0]
        pop eax
    }
    MessageBox(0,"OllyDbg Detected","waliedassar",0);
}

几年前,有一个著名的调试器叫做 SoftICE,它是一个系统调试器(它可以跟踪用户模式的应用程序和系统驱动程序)。现在它已经被 WinDbg 取代了,虽然已经坚持了很多年的开发和升级。系统调试器(或者说内核模式调试器)可以在应用程序使用系统驱动时调用。比如一个 Rootkit,它主要的目的就是把负载埋进操作系统中(通常以用户模式开始),以此来隐蔽恶意软件的进程、对文件系统结构的破坏、隐藏网络通信。


迭代更新


你可以使用非常多的方法来检测恶意软件,无论是校验和、文件片段、特征字符串、函数调用顺序、使用非常用函数、行为分析、高级签名等。这些特性会引导反病毒软件将其分为恶意还是正常。 

这样是建立在假设恶意软件每次都表现出相同的特征的基础上的,修改这些特征中的某一部分就可以逃避检测,在不修改恶意软件源代码的情况下,怎么可以达到这种效果呢?

更换编译器

对编译选项的更改(比如打开或者关闭优化或者更改默认调用约定)会立即更改输出文件的结构

频繁更新和编译选项的更改相结合,将会导致在二进制级别上完全不同的生成文件,但他们可以表现出相同的功能


函数替换


俗话说:“条条大路通罗马”。在编写恶意软件时,有很多方法可以达到相同的效果。例如,为了获取对文件的访问,可以有很多 Windows 系统函数和附加库函数使用。

http://p6.qhimg.com/t01540e2c924391e0f1.jpg

还有其他几十个库也提供类似的功能,可以用作替代品。

清单9-几种读文件的方式

#include <windows.h>
#include <stdio.h>
// mode used to open a file
#define FILE_MODE 3
int main()
{
    #if FILE_MODE == 1
    HANDLE hFile = CreateFileA("notepad.exe", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    #elif FILE_MODE == 2
    HANDLE hFile = CreateFileW(L"notepad.exe", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    #elif FILE_MODE == 3
    FILE *hFile = fopen("notepad.exe", "rb+");
    #else
    //...
    #endif
    // close file handle
    #if FILE_MODE < 3
    CloseHandle(hFile);
    #else
    fclose(hFile);
    #endif
    return 0;
}

在特定函数被使用的时候,也许会被标记为“危险”,但是这种方式仍然创造了全新的代码结构,可以帮助恶意软件逃避检测。


脚本代替程序


某些任务可以由脚本语言,像 VBScript 或者 JScript,来执行,避开调用系统函数。恶意软件有时候也会包含脚本语言的解释器,比如 Flame 中的 Lua、IronPython 中的 Python、甚至是 PHP。一旦源代码被隔离,分析脚本总好过分析编译好的程序,但是恶意软件作者可选择的大量不同的语言为病毒分析师和反病毒公司造成了巨大困扰。


模块化设计


恶意软件并不总是立即安装在感染主机上,通常来说感染模块的规模相当小,感染后会连接到控制服务器来下载附加模块。此设计还帮助恶意软件作者可以轻松地添加新功能或修复 Bug。对于分析师来说,当恶意软件都在一个程序中时,事情反而变得容易了。当恶意软件分裂成不同的模块时,分析会变慢,特别是用不用的语言编写的不同模块,并且每个模块都是用不同的措施来进行保护。


小众语言


恶意软件往往会使用流行的语言来编写,在我看来这是因为:一、这种软件可以在许多版本的 Windows 上不需要其他系统组件就运行;二、有大量的教程和与其语言相关的源码实例,即使是新手也可以很容易的创建恶意程序。 

某些恶意软件作者更加高明,意识到使用流行的语言将会使他们开发的程序易于分析,这些语言都有现成的工具可以使用。病毒分析师每天都要处理这些流行的语言,他们配备并熟练使用相关工具,把这些程序分解成基本模块易如反掌。 
另一方面,用小众语言编写的程序例如函数式语言或者面向业务型语言,对分析师来说是个巨大的挑战。比如:

Haskell

Lisp

VisualFox Pro

Fortran

COBOL

非标准的 Visual Basic 编译器(像 DarkBasicPureBasic)

这样的程序通常有着相当复杂的结构,在许多情况下,他们的整个代码以一种字节码的形式存在,或者是需要分析许多额外的库、都不用提有多耗时。也有一些为这些小众的语言编写的反编译工具,比如 VisualFox Pro 应用程序反编译可以使用 ReFox,但是这样的工具在这个领域内是极其罕见的,因为需求太少了。 
著名的 Stuxnet 病毒是利用 C 语言和一个不起眼的面向对象框架写成的,分析师花了很长时间才搞清楚,它是用什么写成的,为什么有如此不寻常的结构。 

在这篇文章的末尾,你可以找到一个 Crackme,是用 Haskell 语言写成的,尽管这只是一个简单用来检查许可证密钥有效性的算法,但是分析起来仍然特别困难。


自动化软件


市场上有一些软件包允许使用者只需要少量的技术能力就可以创建一个能够完全访问系统文件、从网络上加载组件、访问 Windows 注册表的程序。例如:

AutoIt

Winbatch

Macro Express

使用这些工具创建的应用程序不太可能被检测到,因为他们的代码通常采用中间语言的形式,而且需要特定的反编译器来分析它的行为。


源码修改


源码修改是创建输出文件最高级的方法之一,那么怎样修改源码才能产生不同的输出文件呢?

使得源码突变,比如通过使用模版

在源文件中重新排序函数

改变各个功能的优化选项

将伪参数引入函数并伪造他们的用法(编译器可以优化删除未使用的参数)

在代码行之间引入垃圾(比如执行不必要的任务的垃圾指令、彼此跳转的指令、函数参数的不必要检验、对本地变量不必要的引用)

非线性执行代码(比如 switch)

结构定义的改变,即数据结构成员的随机排列或引入虚拟字段

所有这些更改都能在出书文件中引发重大变化。

清单10-在 Delphi 代码中增加垃圾指令

procedure TForm1.FormCreate(Sender: TObject);
begin
    // junk instructions (these instructions
    // do not have any impact on the behaviour
    // of the application)
    asm
        db 0EBh,02h,0Fh,078h
        db 0EBh,02h,0CDh,20h
        db 0EBh,02h,0Bh,059h
        db 0EBh,02h,038h,045h
        db 0E8h,01h,00h,00h,00h,0BAh,8Dh,64h,24h,004h
        db 07Eh,03h,07Fh,01h,0EEh
        db 0E8h,01h,00h,00h,00h,03Ah,8Dh,64h,24h,004h
        db 0EBh,02h,03Eh,0B8h
    end;
    // This line will be unreadable by
    // a disassembler, thanks to
    // the junk instructions
    Form1.Caption := 'Hello world';
    asm
        db 070h,03h,071h,01h,0Ch
        db 0EBh,02h,0Fh,037h
        db 072h,03h,073h,01h,080h
        db 0EBh,02h,0CDh,20h
        db 0EBh,02h,0Fh,0BBh
        db 078h,03h,079h,01h,0B7h
        db 0EBh,02h,094h,05Ch
        db 0C1h,0F0h,00h
    end;
end;

我见过修改源码最先进的工具是由 Syncrosoft 开发的 MCFACT 系统,用于保护知名音响制造公司 Steinberg 的 Cubase 套件。该系统会首先分析源码,然后将其转换为受保护的形式。

清单11-经过 MCFACT 系统后的 C++ 代码

//MCFACT_PROTECTED
unsigned int findInverse(unsigned int n)
{
    unsigned int test = 1;
    unsigned int result = 0;
    unsigned int mask = 1;
    while (test != 0)
    {
        if (mask & test)
        {
            result |= mask;
            //MCFACT_AUTHORIZED
            test -= n;
        }
        mask <<= 1;
        n <<= 1;
    }
    return result;
}
// code after protection
unsigned int findInverse(unsigned int n)
{
    ...
    class _calculations_cpp_test_0 test=((_init0_0::_init0)());
    class _calculations_cpp_result_0 result=((_init1_0::_init1)());
    class _calculations_cpp_mask_0 mask=((_init2_0::_init2)());
    _cycle1:{
        signed char tmp8;
        ((tmp8)=((IsNotEqual)((test), (_calculations_cpp_c_0_0))));
        if ((tmp8)) {
        {
        unsigned int tmp7;
        ((And)((mask), (test), (tmp7)));
        if ((tmp7)) {
        ((Or)((result), (mask), (result)));
        ((Sub)((test), (n), (test)));
        }
        ((ShiftLeftOne)((mask), (mask)));
        ((n)<<=(1U));
        }
        goto _cycle1;
    }
  }
  {
    unsigned int tmp9;
    ((Copy)((result), (tmp9)));
    return(tmp9);
  }
}

分析这样的代码是相当困难的,这个保护最终被 Team-AiR 攻破,根据其 NFO 文件显示,他们花了近 4000 小时,将近一年工作的一半。在此期间,软件发行商可以继续销售软件而不用考虑盗版的问题。Cloakware 现在是 Lrdeto 公司也在开发类似的保护系统,但是该项目被放弃了。我相信如果这种保护系统与先进的虚拟化技术相结合,对于用户和反病毒软件都是灾难性的。幸运的是,构建这种系统非常困难而且不常见。


数据和字符串加密


这是隐藏敏感信息时最常用的办法,加密通常用于包含那些感染文件的名字、控制服务器地址、甚至是 FTP 或者邮箱的密码。恶意软件与其控制服务之间的通信也常常是加密的,以保证其内容是秘密的。 

Rootkit 样本 Rustock 使用了 RC4 加密算法(这是恶意软件中最常见的一个),以保护自己的模块可供分析,在感染时,从计算机取得的硬件 ID 将作为其病毒的加密密钥,同时这个程序将不会在其他计算机上工作(由于硬件 ID 不匹配),同一时间在另一台计算机上进行分析也是不可能的(例如病毒被发送到了反病毒公司进行分析),必须得到被感染计算机的硬件 ID 才能将病毒代码解密。 
公认的加密算法 
AES 利用固定的数据表,软件(就像 PEiD 这种扫描器)分析可以立即检测到这种算法存在于可执行程序中,这会给分析师提醒,有什么东西加密了躲在代码中,值得调查。出于这个原因,恶意软件作者常常使用动态生成的加密算法,这样不会引起怀疑,例如:

清单12-动态生成的获得使用 StringEncrypt 服务的解密代码

// encrypted with https://www.stringencrypt.com (v1.1.0) [C/C++]
// wszLabel = "C/C++ String Encryption"
wchar_t wszLabel[24] = { 0x2976, 0x2AF9, 0x289C, 0x2B9F, 0x2BA2, 0x2D05, 0x2688, 0x316B,
                         0x336E, 0x33D1, 0x3214, 0x3337, 0x2CDA, 0x277D, 0x3200, 0x34A3,
                         0x32C6, 0x3269, 0x32AC, 0x312F, 0x3392, 0x3255, 0x3258, 0x609B };
for (unsigned int Qqhvj = 0, JWqBw = 0; Qqhvj < 24; Qqhvj++)
{
    JWqBw = wszLabel[Qqhvj];
    JWqBw -= 0xA50D;
    JWqBw += Qqhvj;
    JWqBw = (((JWqBw & 0xFFFF) >> 15) | (JWqBw << 1)) & 0xFFFF;
    JWqBw ^= 0xB0D1;
    JWqBw ++;
    JWqBw = (((JWqBw & 0xFFFF) >> 3) | (JWqBw << 13)) & 0xFFFF;
    JWqBw = ~JWqBw;
    JWqBw += 0xAF02;
    JWqBw ^= Qqhvj;
    JWqBw ^= 0x9FC1;
    JWqBw -= 0xA9E0;
    JWqBw = ~JWqBw;
    JWqBw ++;
    JWqBw = (((JWqBw & 0xFFFF) >> 3) | (JWqBw << 13)) & 0xFFFF;
    JWqBw --;
    wszLabel[Qqhvj] = JWqBw;
}
wprintf(wszLabel);

定时炸弹


有时候恶意软件被设计在一定时间段后激活,例如它可以检查本地时间然后在一个精确的日期后操作,这样的恶意软件有可能会避开反病毒软件(因为没有观察到系统更改)。因此,如果初步分析表明这个程序不是完全安全的,进一步观察是明确的做法。

清单13-一个特定的日期后激活程序

#include <windows.h>
void MrMalware(void)
{
    // malicious code
    MessageBox(NULL, "I'm a virus!", "Boo!", MB_ICONWARNING);
    return;
}
int main()
{
    SYSTEMTIME stLocalTime = { 0 };
    // obtain the current time
    GetLocalTime(&stLocalTime);
    // activate the malicious code
    // only after this date
    if (stLocalTime.wYear >= 2013 && \
        stLocalTime.wMonth >= 5 && \
        stLocalTime.wDay >= 2)
    {
        MrMalware();
    }
    return 0;
}

当我们怀疑一个程序中包含定时炸弹时怎么办?最直接的解决方案是向前或者向后调整系统时间,并使用监控工具监控系统的变化。尽管很简单,但是对发现定时炸弹很有效。


延迟执行


除了定时炸弹,有些恶意程序会采用延迟执行,就像定时器一样。基本思路是,程序启动后不会发生恶意行为,在设定的时间后就会发生。这是双重效果,首先可以误导分析师,在启动过程中没有任何反应,程序应该是安全的,阻止了其进一步进行分析。其二,延时执行可以欺骗反病毒软件的仿真器,仿真器都受到时间的限制,因为用户启动程序并等待了一个半小时后触发了加密代码的执行,对仿真器来说这已经毫无意义了。一个良好的仿真器会检测时间延迟(比如 sleep 函数、延迟循环等),但不可能将所有的时间延迟都考虑在内,Windows 中多种度量时间的办法意味着使用时间延迟对动态代码分析是一种好的防御措施。

清单14-在一定时间周期后激活的恶意代码

#include <windows.h>
DWORD dwTimerId = 0;
const DWORD dwTimerEventId = 666;
// callback function activated after a designated time
VOID CALLBACK MrMalware(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
{
    // malicious code
    MessageBox(NULL, "I'm a virus!", "Boo!", MB_ICONWARNING);
    return;
}
int main()
{
    // activate malicious code 5 seconds
    // after the program is launched
    dwTimerId = SetTimer(NULL, dwTimerEventId, 5 * 1000, MrMalware);
    MessageBox(NULL, "I'm a friendly program", "Hi!", MB_ICONINFORMATION);
    return 0;
}


加密签名


加密签名通常用来签名应用程序,以此保证拥有数字签名的应用程序来自受信任的来源。某些防病毒软件会放行那些有数字签名的应用程序。为了获得数字证书,个人和企业的信息都必须在证书中,必须被验证过。如果没有可以证明的文件(比如银行对账单、身份证的扫描件)是不可能获得合法机构的证书的。然而,数字证书确实会被黑客盗取,然后用于签署恶意软件。这样的例子并不多见,但这样会让那些为所有具有数字证书放行的反病毒软件造成问题。


在线扫描


恶意软件的作者一定非常想知道他的恶意软件被反病毒软件检测的情况,安装所有的反病毒软件不仅费事费时,它们之间还并不兼容。所以恶意软件的作者往往会使用在线服务来检测。如果你第一个想到的是 VirusToal最近被 Google 收购),你只了解了一点点。恶意软件的作者是不会使用这些网站(又例如 Jotti)的,提交给他们的所有文件都与反病毒公司共享,用来提高其反病毒产品的检测精度。恶意软件作者会使用像 NoVirusThanks 或 NoDistribute,它们都以不和反病毒公司合作而著称。


结论


从我的经验观察,恶意软件作者和分析师的战斗永远不会停止。有趣的是战争两边,一边是好的,像反病毒公司、为了合法版权保护的公司,另一边都是坏的,那些试图突破软件保护的人、那些写出可以被恶意软件利用的软件的人。从文章开始到结束,我们纵观工具随着时间的演变,我们可以推测出接下来会发生什么吗?我的看法是一切都会向代码虚拟化的方向前进,但是会比当前的方案更先进。这从当前市场上对原生应用程序保护工具以及 .NET 应用程序虚拟化工具的出现就可以看出。反病毒公司会如何处理呢?一如既往地,要依赖卓越的员工的杰出工作。


原文链接:https://www.pelock.com/articles/anti-reverse-engineering-malware-vs-antivirus-software

未经允许不得转载:安全路透社 » 【技术分享】反逆向工程——使恶意软件难以逆向的技术

赞 (0)
分享到:更多 ()

评论 0

评论前必须登录!

登陆 注册