精通逆向工程(三)
原文:
annas-archive.org/md5/4edb9a969a22ca6c6dd931a00e8c6024译者:飞龙
第十一章:反分析技巧
反调试、反虚拟机(VM)、反仿真和反转储是一些试图阻止分析的技巧。在本章中,我们将尝试展示这些反分析方法的概念。为了帮助我们识别这些代码,我们将解释这些概念并展示实际的反汇编代码。能够识别这些技巧将帮助我们避开它们。通过初步的静态分析,我们将能够跳过这些代码。
在本章中,我们将实现以下学习成果:
-
识别反分析技巧
-
学习如何克服反分析技巧
反调试技巧
反调试技巧的目的是确保代码在没有调试器干预的情况下运行。假设我们有一个程序,其中包含反调试代码。该程序的行为就像它没有反调试代码一样运行。然而,当程序被调试时,情况就不同了。在调试时,我们会遇到直接退出程序或跳转到不合理代码的情况。这个过程在下图中有所说明:
开发反调试代码需要了解程序和系统的特征,无论是在正常运行还是在被调试时。例如,进程环境块(PEB)包含一个标志,该标志在程序在调试器下运行时被设置。另一个常见的技巧是使用 结构化异常处理程序(SEH)来继续在调试时强制产生错误异常的代码。为了更好地理解这些技巧的工作原理,我们来更详细地讨论这些技巧。
IsDebuggerPresent
IsDebuggerPresent 是一个 Kernel32 API 函数,它简单地告诉我们程序是否处于调试器下运行。结果存储在 eax 寄存器中,值为真(1)或假(0)。使用时,代码大致如下所示:
call IsDebuggerPresent
test eax, eax
jz notdebugged
同样的概念适用于 CheckRemoteDebuggerPresent API。不同之处在于,它检查的是另一个进程还是自身进程是否正在被调试。CheckRemoteDebuggerPresent 需要两个参数:一个进程句柄和一个输出变量,告诉我们该进程是否正在被调试。以下代码检查其自身进程是否正在被调试:
call GetCurrentProcess
push edi
push eax
call CheckRemoteDebuggerPresent
cmp dword ptr [edi], 1
jz beingdebugged
GetCurrentProcess API 用于检索正在运行的进程句柄。它通常返回一个 -1(0xFFFFFFFF)值,这是它自身进程的句柄。edi 寄存器应该是一个变量地址,用于存储 CheckRemoteDebuggerPresent 的输出结果。
PEB 中的调试标志
线程是执行的基本单位。进程本身作为线程实体运行,能够在同一进程空间中触发多个线程。当前正在运行的线程信息存储在线程环境块(TEB)中。TEB 也叫线程信息块(TIB),其中包含线程 ID、结构化错误处理框架、堆栈基地址和限制、以及指向有关线程所在进程的信息的地址。关于进程的信息存储在进程环境块(PEB)中。
PEB 包含诸如指向列出已加载模块的表的指针、用于运行进程的命令行参数、从 PE 头部提取的信息,以及是否被调试等信息。TIB 和 PEB 结构由微软在 docs.microsoft.com/en-us/windo… 中记录。
PEB有可以用来识别进程是否正在被调试的字段:BeingDebugged和NtGlobalFlag标志。在PEB中,它们位于以下位置:
| 偏移量 | 信息 |
|---|---|
0x02 | BeingDebugged(为真时为 1) - BYTE |
0x68 | GlobalNTFlag(通常在调试时为 0x70) - DWORD |
在内部,IsDebuggerPresent使用以下代码:
让我们检查一下IsDebuggerPresent代码的运行情况:
mov eax, dword ptr fs:[18]
上述行从线程信息块(TIB)中检索线程环境块(TEB)的地址。FS段包含TIB。TEB地址存储在TIB的偏移量0x18处。TIB存储在eax寄存器中。
下一行获取PEB地址并将其存储在eax寄存器中。PEB地址位于TEB的偏移量0x30处:
mov eax, dword ptr ds:[eax+30]
PEB偏移量2处的字节包含布尔值1或0,表示进程是否正在被调试:
movzx eax, byte ptr ds:[eax+2]
如果我们想创建自己的函数,并且应用了GlobalNTFlag,那么我们可以将代码写成这样:
mov eax, dword ptr fs:[18]
mov eax, dword ptr ds:[eax+0x30]
mov eax, dword ptr ds:[eax+0x68]
cmp eax, 0x70
setz al
and eax, 1
上述代码的前三行基本上是从PEB的偏移量0x68中检索GlobalNTFlag。
接下来的cmp指令会在eax的值等于0x70时将零标志设置为1:
cmp eax, 0x70
setz指令会根据ZF的值将al寄存器设置为0或1:
setz al
最后,and指令将仅保留eax寄存器的第一个位,从而清除寄存器,但保留一个值,值为1或0,用于表示真假:
and eax, 1
从NtQueryInformationProcess获取调试器信息
使用NtQueryInformationProcess函数查询进程信息为我们提供了另一种识别进程是否在调试中的方法。根据MSDN,NtQueryInformationProcess的语法声明如下:
NTSTATUS WINAPI NtQueryInformationProcess(
_In_ HANDLE ProcessHandle,
_In_ PROCESSINFOCLASS ProcessInformationClass,
_Out_ PVOID ProcessInformation,
_In_ ULONG ProcessInformationLength,
_Out_opt_ PULONG ReturnLength
);
有关此函数的更多信息,请参见 docs.microsoft.com/en-us/windows/desktop/api/winternl/nf-winternl-ntqueryinformationprocess。
根据第二个参数 PROCESSINFOCLASS 提供的 ID,返回具体的信息。PROCESSINFOCLASS 是一个列举的 ID 列表,我们希望查询的 ID 包含在内。为了确定进程是否正在被调试,我们需要以下 ID:
-
ProcessDebugPort (7) -
ProcessDebugObjectHandle (30) -
ProcessDebugFlags (31)
本质上,如果第三个参数 ProcessInformation 填充后的输出结果为非零值,则意味着该进程正在被调试。
定时技巧
通常,从地址 A 到地址 B 执行一段程序所需的时间不超过一秒钟。但如果这些指令正在被调试,人工调试可能需要每行约一秒钟。从地址 A 调试到地址 B 至少需要几秒钟。
本质上,这个概念就像一个计时器。如果几行代码执行的时间过长,技巧就会认为程序正在被调试。
定时技巧可以作为一种反调试方法应用于任何编程语言。设置计时器只需要一个能够读取时间的函数。以下是一些定时技巧在 x86 汇编中的实现示例:
rdtsc
mov ebx, eax
nop
nop
nop
nop
nop
nop
nop
nop
rdtsc
sub eax, ebx
cmp eax, 0x100000
jg exit
在 x86 处理器中意味着 读取时间戳计数器 (RDTSC)。每次处理器重置(无论是硬重置还是开机)时,时间戳计数器都会被重置为 0。时间戳计数器在每个处理器时钟周期中递增。在之前的 RDTSC 代码段中,第一个 RDTSC 指令的结果存储在 ebx 寄存器中。经过一系列的 nop 指令后,存储在 ebx 中的值与第二个 RDTSC 指令的结果相减。这就是计算第一和第二次 TSC 的差值。如果差值大于 0x100000,则跳转至退出。如果程序没有被逐行调试,差值应该约小于 0x500。
另一方面,GetSystemTime 和 GetLocalTime 这两个可以获取时间的 API 函数,也可以用来实现定时技巧。为了识别这些技巧,代码必须包含两个时间获取函数。
通过 SEH 传递代码执行
最流行的反调试技巧之一是通过 SEH 传递代码执行。它是 Windows 计算机病毒中常用的技巧。但在讨论该技巧如何用于反调试之前,我们先简要了解一下 SEH 的工作原理。
异常通常由错误引发,例如从无法访问的内存区域读取字节,或像除以零这样简单的操作。它们也可以由调试器中断引发,INT 3 和 INT 1。当异常发生时,系统会跳转到异常处理程序。通常,异常处理程序的工作是处理错误。
通常,这项工作会给出一个错误消息通知,导致程序优雅地终止。从编程的角度来看,这就是 try-except 或 try-catch 处理。以下是 Python 编程中异常处理的示例:
try:
print("Hello World!")
except:
print("Hello Error!")
一个 SEH 记录包含两个元素:异常处理程序的地址和下一个 SEH 记录的地址。下一个 SEH 记录包含指向下一个 SEH 记录的地址。总体来说,SEH 记录是彼此相连的,这被称为 SEH 链。如果当前处理程序无法处理该异常,则下一个处理程序将接管。如果 SEH 记录耗尽,可能会导致程序崩溃。这个过程如下所示:
如我们所见,最后一个 SEH 记录在 SEH 记录指针字段中包含一个 -1(对于 32 位地址空间为 0xFFFFFFFF)的值。
现在我们知道 SEH 是如何工作的,那么如何将其用于反调试呢?使用我们的 try-except Python 代码,滥用它可能会像这样:
x = 1
try:
x = x / 0
print("This message will not show up!")
except:
print("Hello World!")
我们所做的是强制引发一个错误(准确地说是除以零的错误)以引发异常。异常处理程序会显示 Hello World! 消息。那么,在 x86 汇编语言中它是如何工作的呢?
为了设置我们的新 SEH,我们首先需要找出当前 SEH 的位置。对于每个进程,Windows 操作系统会设置一个 SEH 链。当前的 SEH 记录可以从 TIB 的偏移量 0 获取,如 FS 段寄存器所示。
以下汇编代码将当前 SEH 记录的地址获取到 eax 寄存器:
mov eax, dword ptr FS:[0]
要更改处理程序,我们只需用我们的 SEH 记录更改当前 SEH 记录的地址,即 FS:[0]。假设处理代码的地址为 0x00401000,而当前的 SEH 记录位于 0x00200000,并且包含以下值:
| 下一个 SEH 记录 | 0xFFFFFFFF |
|---|---|
| 当前处理程序地址 | 0x78000000 |
接下来要做的是构建我们的 SEH 记录,并将其存储在栈中。通过 FS:[0] 返回 0x00200000 的值,而我们的处理程序位于 0x00401000,这里是从栈中构建 SEH 记录的一种方式:
push 0x00401000
push dword ptr FS:[0]
栈的状态应该类似于以下样子:
| ESP | 0x00200000 |
|---|---|
| ESP+4 | 0x00401000 |
我们需要做的就是将 FS:[0] 的值更新为该 SEH 记录的地址,这就是 ESP 寄存器的值(即栈顶):
mov dword ptr FS:[0], esp
前面的代码应该将我们的 SEH 添加到 SEH 链中。
引发异常
接下来要做的是开发一段代码,强制引发一个异常。我们有几种已知的方法来实现这一点:
-
使用调试断点(INT 3 / INT 1)
-
访问不可访问的内存空间
-
除零错误
SEH 反调试技巧的目的是将调试分析引导到一个错误。这使得分析师试图追溯可能导致错误的原因,从而浪费时间。而且,如果分析师熟悉 SEH,他就能很容易地找到处理程序所在的位置并在那里设置断点。
步骤调试之所以有效,是因为 Interrupt 1,而断点是通过 Interrupt 3 设置的。当代码执行遇到 INT 3 指令时,会发生调试异常。要调用 Interrupt 1 异常,必须首先设置陷阱标志。
当读取不可访问的内存时,会发生读取错误。已经有已知的内存区域,例如内核空间,这些区域不允许从用户模式进程直接访问。这些区域大多数被 PAGE_GUARD 标志保护。可以通过 VirtualAlloc 或 VirtualProtect 函数设置 PAGE_GUARD 标志。这意味着我们可以创建自己的不可访问内存区域。通常,进程空间中的 0 偏移区域是不可访问的。以下代码行将导致访问违规异常:
mov al, [0]
在数学中,实际的除以零操作是一个无限的任务。系统会明确识别此类错误并引发异常。以下是一个示例代码行:
mov eax, 1
xor cl, cl
div cl
前述代码的作用是将 eax 寄存器设置为 1,将 cl 寄存器设置为 0,然后用 cl 除以 eax,从而引发除零异常。
一个典型的 SEH 设置
基于我们所学的内容,让我们利用常规的代码流程,然后使用 SEH 作为反调试技巧。以下代码将是我们的原始代码:
push eax
mov eax, 0x12345678
mov ebx, 0x87654321
and eax, ebx
pop eax
在添加 SEH 反调试技巧后,代码看起来大致如下:
mov eax, dword ptr FS:[0]
push 0x00401000
push eax
mov dword ptr FS:[0], esp
mov al, [0]
RDTSC (with CPUID to force a VM Exit)
VMM instructions i.e. VMCALL
VMEXIT
0x00401000:
push eax
mov eax, 0x12345678
mov ebx, 0x87654321
and eax, ebx
pop eax
我们在这里做的是手动设置 SEH。幸运的是,Windows 还提供了一个可以设置异常处理程序的功能,称为向量化异常处理程序。注册新处理程序的 API 是 AddVectoredExceptionHandler。实现此功能的 C 语言源代码可以在 docs.microsoft.com/en-us/windows/desktop/debug/using-a-vectored-exception-handler 找到。
反虚拟机技巧
这个技巧的目的是当它检测到程序正在虚拟化环境中运行时退出程序。识别虚拟机环境的最典型方法是检查计算机中是否安装了特定的虚拟化软件痕迹。这些痕迹可能位于注册表或正在运行的服务中。我们列出了一些可以用来识别虚拟机内运行的特定痕迹。
虚拟机运行进程名称
程序确定自己是否在虚拟机中的最简单方法是识别运行进程的已知文件名。以下是每种流行虚拟机软件的列表:
| Virtualbox | VMWare | QEMU | Parallels | VirtualPC |
|---|
| vboxtray.exe vboxservice.exe
vboxcontrol.exe | vmtoolsd.exe vmwaretray.exe
vmwareuser
VGAuthService.exe
vmacthlp.exe | qemu-ga.exe | prl_cc.exe prl_tools.exe | vmsrvc.exe vmusrvc.exe |
虚拟机文件和目录的存在
确定至少存在一个虚拟机软件的文件,可以判断该程序是否正在虚拟机中运行。下表列出了可以用来识别程序是否在 VirtualBox 或 VMware 客户机中运行的文件:
| VirtualBox | VMWare |
|---|
| %programfiles%\oracle\virtualbox guest additions system32\drivers\VBoxGuest.sys
system32\drivers\VBoxMouse.sys
system32\drivers\VBoxSF.sys
system32\drivers\VBoxVideo.sys
system32\vboxdisp.dll
system32\vboxhook.dll
system32\vboxmrxnp.dll
system32\vboxogl.dll
system32\vboxoglarrayspu.dll
system32\vboxoglcrutil.dll
system32\vboxoglerrorspu.dll
system32\vboxoglfeedbackspu.dll
system32\vboxoglpackspu.dll
system32\vboxoglpassthroughspu.dll | %programfiles%\VMWare system32\drivers\vm3dmp.sys
system32\drivers\vmci.sys
system32\drivers\vmhgfs.sys
system32\drivers\vmmemctl.sys
system32\drivers\vmmouse.sys
system32\drivers\vmrawdsk.sys
system32\drivers\vmusbmouse.sys |
默认 MAC 地址
虚拟机默认 MAC 地址的前三个十六进制数字也可以用来识别。但当然,如果 MAC 地址被更改,这些方法就不适用了:
| VirtualBox | VMWare | Parallels |
|---|
| 08:00:27 | 00:05:69 00:0C:29
00:1C:14
00:50:56 | 00:1C:42 |
虚拟机创建的注册表项
软件的信息和配置通常保存在注册表中。这同样适用于虚拟机客户机软件,它会创建注册表项。以下是 VirtualBox 创建的注册表项的简短列表:
HARDWARE\ACPI\DSDT\VBOX__
HARDWARE\ACPI\FADT\VBOX__
HARDWARE\ACPI\RSDT\VBOX__
SOFTWARE\Oracle\VirtualBox Guest Additions
SYSTEM\ControlSet001\Services\VBoxGuest
SYSTEM\ControlSet001\Services\VBoxMouse
SYSTEM\ControlSet001\Services\VBoxService
SYSTEM\ControlSet001\Services\VBoxSF
SYSTEM\ControlSet001\Services\VBoxVideo
以下是已知来自 VMWare 的注册表项:
SOFTWARE\VMware, Inc.\VMware Tools
使用 Wine 模拟的 Linux 有如下注册表项:
SOFTWARE\Wine
也可以通过注册表识别出 Microsoft 的 Hyper-V:
SOFTWARE\Microsoft\Virtual Machine\Guest
虚拟机设备
这些是虚拟机创建的虚拟设备。以下是 VirtualBox 和 VMWare 创建的可访问设备:
| VirtualBox | VMWare |
|---|
| \\.\VBoxGuest \\.\VBoxTrayIPC
\\.\VBoxMiniRdrDN | \\.\HGFS \\.\vmci |
CPUID 结果
CPUID 是一条 x86 指令,用于返回正在运行的处理器的信息。在执行该指令之前,需要指定信息类型,这些信息被称为“叶子”,并存储在寄存器 EAX 中。根据叶子的不同,它会在寄存器 EAX、EBX、ECX 和 EDX 中返回值。每个寄存器中存储的每一位都可以指示某个 CPU 特性是否可用。关于返回的 CPU 信息的详细内容,可以查看 en.wikipedia.org/wiki/CPUID。
CPUID 返回的信息之一是一个标志,它表示系统是否在超虚拟机监控程序(Hypervisor)上运行。超虚拟机监控程序是一个 CPU 功能,支持运行虚拟机(VM)客户机。对于反虚拟机检测,如果启用此标志,意味着进程运行在虚拟机客户机中。
以下 x86 代码检查是否启用了超虚拟机监控程序标志:
mov eax, 1
cpuid
bt ecx, 31
jc inhypervisor
前面的代码从 CPUID 第 1 项获取信息。ecx 寄存器中的第 31 位结果被放置在进位标志中。如果该位被设置为 1,则表示系统在超虚拟机监控程序上运行。
除了超虚拟机监控程序的信息外,一些特定的虚拟机软件可以通过来宾操作系统识别。CPUID 指令可以返回一个唯一的字符串 ID,以识别客户机所运行的虚拟机软件。以下代码检查是否运行在 VMWare 客户机中:
mov eax, 0x40000000
cpuid
cmp ebx, 'awMV'
jne exit
cmp ecx, 'MVer'
jne exit
cmp edx, 'eraw'
jne exit
当ebx、ecx 和 edx 寄存器的值拼接在一起时,它会显示为 VMwareVMware。以下是其他虚拟机软件使用的已知字符串 ID 列表:
| VirtualBox 4.x | VMware | Hyper-V | KVM | Xen |
|---|---|---|---|---|
| VBoxVBoxVBox | VMwareVMware | Microsoft Hv | KVMKVMKVM | XenVMMXenVMM |
反仿真技巧
反仿真或反自动化分析是程序用来防止在代码执行过程中继续推进的一种方法,前提是它识别到自己正在被分析。程序的行为可以使用自动化分析工具如 Cuckoo Sandbox、Hybrid Analysis 和 ThreatAnalyzer 来记录和分析。这些技巧的核心在于能够确定程序运行的系统是由用户控制的,并且是用户设置的。
以下是一些区分用户控制的环境与自动化分析控制的系统之间差异的事项:
-
用户控制的系统具有鼠标移动。
-
用户控制的系统可能包含一个对话框,等待用户向下滚动然后点击按钮。
-
自动化分析系统的设置具有以下特点:
-
物理内存不足
-
磁盘空间过小
-
磁盘上的可用空间几乎耗尽
-
CPU 数量只有一个
-
屏幕分辨率过小
-
简单地设置一个需要用户手动输入的任务即可确定程序是否在用户控制的环境中运行。类似于反虚拟机检测,虚拟机客户机的设置将尽可能使用最低的资源要求,以免占用虚拟机主机的计算机资源。
另一个反分析技巧是检测是否运行分析工具。这些工具包括以下内容:
-
OllyDBG(
ollydbg.exe) -
WinDbg(
windbg.exe) -
IDA Pro(
ida.exe,idag.exe,ida64.exe,idag64.exe) -
SysInternals 套件工具,包括以下内容:
-
进程浏览器(
procexp.exe) -
进程监视器(
procmon.exe) -
Regmon(
regmon.exe) -
Filemon(
filemon.exe) -
TCPView(
tcpview.exe) -
Autoruns(
autoruns.exe,autorunsc.exe)
-
-
Wireshark(
wireshark.exe)
规避这些技巧的一种方法是通过自动化分析进行反制。例如,可以模拟鼠标移动,甚至读取对话框窗口属性,滚动和点击按钮。一个简单的反分析技巧是重命名我们用来监视行为的工具。
反转储技巧
这种方法并不会停止将内存转储到文件中。这个技巧实际上是通过让逆向工程师不容易理解转储的数据来起作用。以下是一些应用示例:
-
PE 头的部分内容已经被修改,因此进程转储会显示错误的属性。
-
PEB的部分内容,如SizeOfImage,已经被修改,因此进程转储工具会转储错误的数据。 -
转储对于查看解密后的数据非常有用。反转储技巧会在使用后重新加密解密的代码或数据。
为了克服这个技巧,我们可以识别或跳过修改数据的代码。对于重新加密的情况,我们也可以跳过重新加密的代码,使其保持在解密状态。
总结
恶意软件通过加入新技术来规避防病毒软件和逆向工程。这些技术包括进程空洞化、进程注入、进程替换、反调试和反分析。进程空洞化和进程替换技术基本上是用恶意程序替换合法进程的映像,从而伪装成合法进程。另一方面,进程注入技术则是将代码插入并在远程进程空间中运行。
反调试、反分析以及本章讨论的其他技巧是逆向工程的障碍。但了解这些技巧的概念能帮助我们克服它们。通过静态分析和死列表方法,我们可以识别并跳过这些复杂代码,或者在 SEH 的情况下,在异常处理程序处设置断点。
我们讨论了反调试技巧,以及它通过错误导致异常并将剩余代码停留在异常处理程序中的技术。我们还讨论了其他技巧,包括反虚拟机和反仿真技巧,这些技巧能够识别出当前环境是分析环境。
在下一章中,我们将运用这里学到的知识,进行一个可执行文件的逆向工程分析。
第十二章:Windows 可执行文件的实用逆向工程
逆向工程在处理恶意软件分析时非常常见。在本章中,我们将查看一个可执行程序,并使用我们目前所学的工具确定其实际行为流程。我们将直接从静态分析进入动态分析。这要求我们设置好实验环境,以便更容易跟进分析过程。
本章要分析的目标文件具有在实际恶意软件中看到的行为。无论文件是否恶意软件,我们在分析时都必须小心地在封闭环境中处理每个文件。让我们开始进行一些逆向工程。
本章将涵盖以下主题:
-
实用的静态分析
-
实用的动态分析
准备事项
我们即将分析的文件可以从github.com/PacktPublishing/Mastering-Reverse-Engineering/blob/master/ch12/whatami.zip下载。它是一个密码保护的压缩文件,密码是"infected",不带引号。
我们需要准备好 Windows 实验室环境。本章讨论的分析将程序运行在一个 VirtualBox 虚拟机中,虚拟机上运行 Windows 10 32 位操作系统。还需要准备以下工具:
-
IDA Pro 32 位版:可以从
github.com/PacktPublishing/Mastering-Reverse-Engineering/blob/master/tools/Disassembler%20Tools/32-bit%20idafree50.exe下载免费的版本。 -
x86dbg: 最新版本可以从
x64dbg.com下载。旧版本的副本可以从github.com/PacktPublishing/Mastering-Reverse-Engineering/blob/master/tools/Debuggers/x64dbg%20-%20snapshot_2018-04-05_00-33.zip下载。 -
Fakenet: 官方版本可以从
github.com/fireeye/flare-fakenet-ng下载。也可以从github.com/PacktPublishing/Mastering-Reverse-Engineering/tree/master/tools/FakeNet下载副本。 -
SysInternals 套件:
docs.microsoft.com/en-us/sysinternals/downloads/ -
Snowman:
derevenets.com/ -
HxD:
mh-nexus.de/en/hxd/ -
CFF Explorer:
ntcore.com/
在分析过程中,我们可能需要其他工具。如果你发现有更方便的工具,可以随时使用它们。
初步静态分析
为了帮助我们进行静态信息收集,以下是我们需要获取的信息列表:
-
文件属性(名称、大小、其他信息)
-
哈希值(MD5、SHA1)
-
文件类型(包括头信息)
-
字符串
-
死名单(标记需要信息的地方)
在初步分析的最后,我们需要总结所有获取的信息。
初始文件信息
为了获取文件名、文件大小、哈希值、文件类型和其他关于文件的信息,我们将使用 CFF Explorer。当打开文件时,可能会遇到一个错误消息,如下截图所示:
此错误是由 MS Windows 的病毒防护功能引起的。由于我们处于一个沙箱环境中(在虚拟化的客户环境下),禁用此功能应该是可以的。禁用此功能在生产环境中可能会暴露计算机被恶意软件攻击的风险。
要在 Windows 中禁用此功能,请选择“开始”->“设置”->“Windows 安全”->“病毒和威胁防护”->“病毒和威胁防护设置”。然后关闭实时保护。你还可以关闭云传递保护和自动样本提交,以防止任何安全设置阻止程序可能执行的操作。
以下截图显示了禁用实时保护后的状态:
使用 CFF Explorer 打开文件可以揭示很多信息,包括文件被 UPX 打包的打包者信息:
根据前面的结果,我们可以列出以下文件信息:
| 文件名 | whatami.exe |
|---|---|
| 文件大小 | 28,672 字节 |
| MD5 | F4723E35D83B10AD72EC32D2ECC61091 |
| SHA-1 | 4A1E8A976F1515CE3F7F86F814B1235B7D18A231 |
| 文件类型 | Win32 PE 文件 - 使用 UPX v3.0 打包 |
我们将需要下载 UPX 工具并尝试解压文件。UPX 工具可以从 upx.github.io/ 下载。使用 UPX,使用 "-d" 选项解压文件,方法如下:
upx -d whatami.exe
解压文件后的结果,如下所示,告诉我们文件最初的大小为 73,728 字节:
所以,如果我们重新打开文件在 CFF Explorer 中,我们的文件信息表将现在包含以下内容:
| 文件名 | whatami.exe |
|---|---|
| 文件大小 | 73,728 字节 |
| MD5 | 18F86337C492E834B1771CC57FB2175D |
| SHA-1 | C8601593E7DC27D97EFC29CBFF90612A265A248E |
| 文件类型 | Win32 PE 文件 - 由 Microsoft Visual C++ 8 编译 |
让我们看看使用 SysInternals 的 strings 工具可以找到哪些值得注意的字符串。Strings 是一个命令行工具,只需将文件名作为工具的参数传递,并将输出重定向到文件。以下是使用方法:
strings.exe whatami.exe > filestrings.txt
通过去除噪音字符串或与分析无关的文本,我们获得了以下内容:
!This program cannot be run in DOS mode.
Rich
.text
`.rdata
@.data
.rsrc
hey
how did you get here?
calc
ntdll.dll
NtUnmapViewOfSection
KERNEL32.DLL
MSVCR80.dll
USER32.dll
Sleep
FindResourceW
LoadResource
LockResource
SizeofResource
VirtualAlloc
FreeResource
IsDebuggerPresent
ExitProcess
CreateProcessA
GetThreadContext
ReadProcessMemory
GetModuleHandleA
GetProcAddress
VirtualAllocEx
WriteProcessMemory
SetThreadContext
ResumeThread
GetCurrentProcess
GetSystemTimeAsFileTime
GetCurrentProcessId
GetCurrentThreadId
GetTickCount
QueryPerformanceCounter
SetUnhandledExceptionFilter
TerminateProcess
GetStartupInfoW
UnhandledExceptionFilter
InterlockedCompareExchange
InterlockedExchange
_XcptFilter
exit
_wcmdln
_initterm
_initterm_e
_configthreadlocale
__setusermatherr
_adjust_fdiv
__p__commode
__p__fmode
_encode_pointer
__set_app_type
_crt_debugger_hook
?terminate@@YAXXZ
_unlock
__dllonexit
_lock
_onexit
_decode_pointer
_except_handler4_common
_invoke_watson
_controlfp_s
_exit
_cexit
_amsg_exit
??2@YAPAXI@Z
memset
__wgetmainargs
memcpy
UpdateWindow
ShowWindow
CreateWindowExW
RegisterClassExW
LoadStringW
MessageBoxA
WHATAMI
t<assembly manifestVersion="1.0">
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.VC80.CRT" version="8.0.50727.6195" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
</dependentAssembly>
</dependency>
</assembly>PAD
我们突出了多个文本字符串。因此,我们可能会期望通过使用MessageBoxA函数弹出多个消息。使用像LoadResource和LockResource这样的 API 时,我们也可能会遇到处理资源部分数据的代码。在看到像CreateProcess和ResumeThread这样的 API 后,可能会调用一个挂起的进程。使用IsDebuggerPresent API 时,也可能会遇到反调试技术。程序可能已经被编译成使用基于 GUI 的代码,通过CreateWindowExW和RegisterClassExW,但我们没有看到窗口消息循环函数:GetMessage、TranslateMessage和DispatchMessage。
这些都只是我们在进一步分析后可以更好理解的假设。现在,让我们尝试使用 IDA Pro 对该文件进行死列举。
死列举
在 IDA Pro 中打开whatami.exe后,自动分析识别出了WinMain函数。在接下来的截图中,我们可以看到将要执行的前三个 API 是LoadStringW、RegisterClassExW和CreateWindowEx:
当执行CreateWindowExW时,窗口的属性来自RegisterClassExW设置的配置。ClassName,作为窗口的名称,是通过LoadStringW从文件的文本字符串资源中获取的。然而,我们在这里关心的只是lpfnWindProc指向的代码。当执行CreateWindowExW时,lpfnWndProc参数指向的代码将被执行。
在继续之前,先看看sub_4010C0。我们来看看CreateWindowExW之后的代码:
上面的截图显示,在CreateWindowExW之后,ShowWindow和UpdateWindow是可能被执行的唯一 API。然而,确实没有预期中的窗口消息 API 来处理窗口活动。这使得我们假设程序的意图只是运行lpfnWndProc参数指向的地址处的代码。
双击dword_4010C0,即lpfnWndProc的地址,将显示一组 IDA Pro 尚未正确分析的字节。由于我们确定这个区域应该是代码,因此我们需要告诉 IDA Pro 它是代码。通过在地址0x004010C0按下'c',IDA Pro 将开始将字节转换为可读的汇编语言代码。当 IDA Pro 询问我们是否将其转换为代码时,选择是:
向下滚动,我们将在0x004011a0处遇到另一个无法识别的代码。只需执行相同的步骤:
再往下滚动会看到一些无法再转换的数据。这应该是代码的最后一部分。让我们告诉 IDA Pro 将这段代码处理为一个函数。操作方法是高亮选中从0x004010C0到0x004011C0的行,右键点击高亮部分,然后选择“创建函数...”将这段代码变成一个函数。
将代码转化为函数可以帮助我们的死链表查看代码的图形视图。为此,右键点击并选择图形视图。下图显示了该函数的第一组代码。我们关心的是rdtsc和cpuid指令的使用方式:
在第十一章,与 POC 恶意软件的识别中,在反调试技巧下,我们讨论了rdtsc被用作时间计算技巧。差异是在第二次rdtsc后计算的。在以下代码中,预期的持续时间应该小于或等于0x10000,即65,536个周期。如果我们能通过这个时间技巧,就会弹出一个消息框。
第 1 个叶子(设置在寄存器eax中)被传递给第一次执行cpuid指令。再次在第十一章中,cpuid可以用于反虚拟机技巧。结果被放置在寄存器 eax 中。接着是三条xor指令,最终交换eax和ecx寄存器的值。
xor ecx, eax
xor eax, ecx
xor ecx, eax
bt指令将第 31 位(0x1F)移动到进位标志。如果第 31 位被设置,则意味着我们正在一个超级管理程序环境中运行。在后续的调试过程中,我们需要注意这一行。我们希望使结果中第 31 位被设置为0。
这之后可能会紧接着用xor ecx, 20h检查第 5 位。如果第 5 位被设置,意味着 VMX(虚拟机扩展)指令可用。如果 VMX 指令可用,则意味着系统能够运行虚拟化。通常,VMX 仅在主机虚拟机上可用,程序可以假设它正在物理机上运行。对于位运算,如果ecx的第 5 位被设置,xor 20h应该使其归零。但如果ecx寄存器的其他位被设置,ecx寄存器的值就不会是零。我们在调试过程中也要特别注意这一点。
这里展示了两种主要的技巧——一个是时间技巧,另一个是反虚拟机技巧。总体而言,如果我们推测我们分析的内容,程序可以走两个方向:一个是loc_4010EF处的循环,它没有意义,另一个是MessageBoxA代码。
如果我们仔细看,会发现整个反调试和反虚拟机技巧都被pusha和popa指令包围。实际上,我们可以跳过整个技巧代码,直接跳到MessageBoxA代码,正如下面的截图所示:
MessageBoxA代码后面是读取RCDATA(0x0A)资源类型,且该资源的序号名称为0x88(136)的函数。使用 CFF Explorer,点击 Resource Editor 并展开 RCData。我们应该能够看到正在读取的数据,如下截图所示:
数据通过 memcpy 被复制到使用 VirtualAlloc 分配的内存空间中。分配的大小是 RCData 属性中指示的大小。可以通过在 CFF Explorer 中展开 Resource Directory 中的 RCData 来查看大小。复制的数据地址被存储在 edi 寄存器中。
我们还看到 IsDebuggerPresent 被使用了,这是另一种反调试技巧。跟随绿色线条最终会到达 ExitProcess。
以下截图是红线的去向:
loc_4011A0 处的循环似乎在解密数据。记住,数据的地址保存在寄存器 edi 中。解密算法使用 ror 0x0c(向右旋转 12 位)。解密后,数据地址被存储到寄存器 eax 中,然后调用 sub_4011D0 函数。
了解解密数据的位置和大小后,我们应该能够在调试过程中创建一个内存转储。
在 sub_4011D0 内部,存储在 eax 中的地址被转移到 esi 寄存器,随后转移到 edi 寄存器。然后我们看到一个调用 CreateProcessA 的函数来运行“calc”:
名为“calc”的进程实际上是 Windows 默认的计算器应用程序。CreateProcessA的第六个参数dwCreationFlags在这里是我们关注的重点。值为 4 表示 CREATE_SUSPENDED。计算器以挂起模式作为进程运行,这意味着它并未执行,而是仅仅在计算器自己的进程空间中加载。
如果我们要为 sub_4011D0 创建一个包含 API 函数顺序的框图,可能会得到如下图所示的结果。
这些 API 的顺序展示了一种名为进程空洞(process hollowing)的行为。进程空洞是一种常被恶意软件使用的技术,通过将其代码隐藏在合法进程下方来掩盖其存在。这项技术会创建一个处于挂起状态的进程,然后卸载其内存并用另一个进程映像替换它。在这个案例中,合法进程是计算器(Calculator)。
NtUnmapViewOfSection API 是一个从给定进程空间中卸载或移除 PE 映像布局的函数。这个 API 来自于 NTDLL.DLL 库文件。与使用 LoadLibrary 不同,这里使用了 GetModuleHandle。LoadLibrary 用于加载尚未加载的库,而 GetModuleHandle 用于检索已加载库的句柄。在这种情况下,程序假设 NTDLL.DLL 已经被加载。
以下截图展示了获取NtUnmapViewOfSection函数地址的反汇编代码:
资源部分的 RCData 解密数据会传递给 sub_4011D0。每次调用WriteProcessMemory都会从解密数据中读取数据块。鉴于此,我们预期解密数据应该是一个 Win32 PE 文件的内容。
总结一下,代码最初会创建一个窗口。然而,已注册的窗口属性几乎为空,只有回调函数 Wndproc。Wndproc 回调是窗口创建时最初执行的代码。因此,使用 RegisterClassEx 和 CreateWindow API 创建窗口实际上只是用来传递代码执行。换句话说,整个窗口创建过程只是 jmp 指令的简单等效。
这是另一张概述 Wndproc 回调中代码流的图示:
在 Wndproc 代码的第一部分,我们遇到了反调试(通过 rdtsc 做时间技巧)和反虚拟机(cpuid 位 31 和 5)技巧。一旦我们通过这些,弹出了一个消息框。资源中的 RCData 数据被复制到一个分配的内存中。我们又遇到了一个使用 IsDebuggerPresent API 的反调试技巧。数据被解密并传递给一个使用计算器的进程劫持代码。
我们接下来的分析目标是通过进程劫持执行的解密镜像。我们将直接开始调试。
调试
我们将使用 x86dbg 进行调试会话。记住,我们已经使用 UPX 解压了文件。最好打开解压后的版本,而不是原始的 whatami.exe 文件。打开压缩的文件也是可以的,但我们将需要调试 UPX 打包的代码。
与 IDA Pro 不同,x86dbg 无法识别 WinMain 函数,也就是程序的实际起始点。此外,打开文件后,指令指针可能还会处于 NTDLL 内存空间。为了避免在启动时进入 NTDLL 区域,我们可能需要在 x86dbg 中进行一些简单的配置更改。
选择“选项”->“首选项”。在“事件”标签下,取消勾选“系统断点”和“TLS 回调”。点击保存按钮,然后选择“调试”->“重启”。这应该将我们带到 whatami.exe 的入口点,地址为:0x004016B8。
既然我们已经通过 IDA Pro 知道了 WinMain 的地址,我们可以直接在该地址设置断点。WinMain 地址是 0x00401000。按下 CTRL+G,然后输入 0x00401000,再按 F2 设置断点,最后按 F9 运行程序。
这是此时我们应该达到的位置的截图:
在静态分析中,我们观察到使用了RegisterClassExW和CreateWindowExW来设置 WndProc 作为窗口处理程序,其中包含更多有趣的代码。请在 WndProc 地址0x004010c0处设置断点,然后按 F9 键。这将带我们进入如下截图,其中包含反调试和反虚拟机代码:
我们在这里突出了反调试和反虚拟机代码。代码从pushad指令开始,到popad指令结束。我们可以做的是跳过这些反调试和反虚拟机代码。按 F7 或 F8,直到我们到达地址0x004010C9。选择0x00401108这一行(即popad之后的行),然后右键单击它以弹出上下文菜单。选择“Set New Origin Here”。这样,指令指针(寄存器 EIP)就会定位到这个地址。
现在我们应该到达了使用MessageBoxA函数显示以下消息的代码。继续按F8,直到出现以下消息:
你需要点击“确定”按钮才能继续调试。接下来的代码将从资源部分获取RCData。继续按F8,直到我们到达0x0040117D这一行,调用memcpy。仔细观察传递给memcpy的三个参数,寄存器 edi 应包含要复制数据的源地址,寄存器eax应包含目标地址,而寄存器esi应包含要复制的数据大小。为了查看目标将包含的内存内容,在右侧窗格中选择EDI的值,然后右键单击它以显示上下文菜单。选择“Follow in Dump”。现在我们应该能够查看 Dump 1 的内存空间,如下图所示:
按F8键继续执行memcpy。以下截图显示了当前的位置:
继续按F8键,直到我们到达调用IsDebuggerPresent之后的行(0x00401192)。寄存器EAX应该被设置为1,表示“True”值。我们需要将其改为“False”,即零值。为此,双击寄存器EAX的值,然后将 1 改为 0。这样,代码就不会直接跳到ExitProcess调用。
接下来的代码将是解密例程。左侧窗格中的箭头显示了一个loopback代码。该算法使用ror指令。继续按F8键,同时观察 Dump 1。我们可以逐渐看到数据被解密,从一个MZ头开始。你可以在地址0x004011B7处设置断点,解密代码结束并完全解密的数据将显示如下:
解密的数据是一个大小为0x0D000(53,248 字节)的Win32 PE 文件。我们可以在这里做的是将此解密的内存转储到文件中。要执行此操作,请单击内存映射选项卡或选择视图->内存映射。这显示了进程内存空间,其中包含内存部分的地址和其相应大小。在我们的情况下,解密数据的内存地址是0x001B000。这个地址可能因分析而异。选择大小为0x00D000的解密数据的内存地址,右键单击以显示上下文菜单,然后选择转储内存到文件。请参考以下示例:
保存文件并使用 CFF Explorer 打开它。这为我们提供了以下文件信息:
| 文件大小 | 53,248 字节 |
|---|---|
| MD5 | DD073CBC4BE74CF1BD0379BA468AE950 |
| SHA-1 | 90068FF0C1C1D0A5D0AF2B3CC2430A77EF1B7FC4 |
| 文件类型 | Win32 PE 文件 - 由 Microsoft Visual C++ 8 编译 |
此外,查看导入目录显示了四个库模块:KERNEL32、ADVAPI32、WS2_32和URLMON。以下 CFF Explorer 截图显示从ADVAPI32导入了注册表和密码学 API:
存在WS2_32表示程序可能使用网络套接字函数。从URLMON导入的单个 API 是URLDownloadToFile。我们预计会下载一个文件。
回到我们的调试会话,还剩下两个调用指令。其中一个选择是调用ExitProcess,它将终止当前运行的进程。另一个是调用地址0x004011D0。使用F7进行调试步骤,使调试器进入调用指令。这是执行进程空心化例程的函数。下面的截图是我们在输入0x004011D0后应该所处的位置:
持续按F8,直到调用CreateProcessA之后。打开 Windows 任务管理器,查看进程列表。您应该看到calc.exe处于挂起状态,如下所示:
持续按 F8,直到我们到达调用ResumeThread(0x0040138C)的行。发生的是未知 PE 文件刚刚替换了计算器进程的映像。如果我们回顾一下sub_4011D0的块图,我们目前处于该程序的进程空心化行为中。虽然计算器处于挂起模式,但尚未执行任何代码。因此,在ResumeThread行上按下 F8 之前,我们将需要附加挂起的计算器,并在其 WinMain 地址或入口点处设置断点。为此,我们将需要打开另一个x86dbg调试器,然后选择文件->附加,并查找 calc。如果您看不到它,您将需要通过选择文件->重新启动以管理员身份运行。
让我们使用 IDA Pro 来帮助我们确定WinMain的地址。打开 IDA Pro 中的转储内存,并在自动分析后,我们将定位到WinMain函数。切换到文本视图,并记录下WinMain的地址,如下图所示:
在x86dbg中,将断点设置在0x004017A0,如以下截图所示:
现在我们准备在ResumeThread这一行按F8键。但在此之前,最好还是创建一个正在运行的虚拟机快照,以防万一出现问题:
到此为止,whatami.exe要执行的唯一 API 是ExitProcess。这意味着我们可以按F9让该进程结束。
在调用ResumeThread后,calc进程将被恢复并开始运行。但是由于未知的镜像处于调试器暂停状态,我们观察到calc镜像仍停留在附加的断点指令指针处。
未知镜像
此时,我们已经在 IDA Pro 中打开了内存转储,并且相同的未知镜像已映射到计算器进程中。我们将使用 IDA Pro 查看反汇编代码,并使用x86dbg进行调试。
在x86dbg中,我们已经在未知镜像的WinMain地址处设置了断点。然而,指令指针仍然停留在NTDLL地址处。按F9让其继续,直到我们到达WinMain。
详细查看WinMain的反汇编代码时,我们会注意到这里有一个 SEH 反调试机制:
call sub_4017CB跳转到一个子程序,里面有call $+5、pop eax和retn指令。call $+5调用下一行。记住,当执行call时,栈顶会包含返回地址。call sub_4017CB会将返回地址0x004017B3存储到栈顶。接着,call $+5会将0x004017D0存储到栈顶。由于pop eax,0x004017D0被放入 eax 寄存器。ret指令会返回到0x004017AD地址。随后,地址中存储的值加 2,结果是eax中的地址指向0x004017D2。这一定是正在设置的 SEH 处理程序地址。
我们可以通过 SEH,或者简单地在调试会话中跳过它。跳过它也非常简单,因为我们可以识别pushf/pusha和popa/popf指令,并执行与在whatami.exe进程中相同的操作。
通过 SEH 的过程应该也很简单。我们可以在处理程序地址0x004017D2处设置断点,并按F9直到到达处理程序。
我们可以选择其中的任何一个选项。在做出这样的决策时,最好先拍个虚拟机的快照。如果出现问题,我们可以通过恢复虚拟机快照来尝试两个选项。
我们的下一站是sub_401730。以下截图显示了sub_401730中的代码:
通过调试这段代码,可以发现使用了LoadLibraryA和GetProcAddress来获取MessageBoxA的地址。之后,它只是显示了一个消息。
下一行代码是一个反自动化分析的技巧。我们可以看到,两个GetTickCount的结果差值正在与值0x0493e0或300000进行比较。在GetTickCount的调用之间,还调用了一个 Sleep 函数。
一个 300000 的 Sleep 意味着 5 分钟。通常,自动化分析系统会将较长的 Sleep 时间改为非常短的时间。前面的代码希望确保 5 分钟的时间确实已经过去。作为调试这段代码的分析员,我们可以通过将指令指针设置在jb指令之后,简单地跳过这个技巧。
接下来是调用sub_401500,并传递两个参数:"mcdo.thecyberdung.net"和0x270F(9999)。这个例程包含了套接字 API。像之前一样,让我们列出我们将遇到的 API 序列。
对于网络套接字行为,我们需要关注的是gethostbyname、htons、send和recv的参数和结果。同样,在继续之前,建议此时先拍摄一个虚拟机快照。
继续逐步调试,直到我们到达gethostbyname的调用。我们可以通过查看gethostbyname的参数来获取程序连接的服务器。而这个服务器就是"mcdo.thecyberdung.net"。继续调用后,我们可能会遇到gethostbyname的结果问题。寄存器 EAX 中的结果是零。这意味着gethostbyname失败了,因为它未能将"mcdo.thecyberdung.net"解析为一个 IP 地址。我们需要做的是设置FakeNet来模拟互联网。恢复虚拟机快照,以便回到执行WSAStartup之前的状态。
在运行FakeNet之前,通过从 VirtualBox 菜单中选择“机器->设置->网络”来断开电缆。展开“高级”菜单并取消选中“Cable connected”。我们进行这个操作是为了确保在FakeNet重新配置网络时不会发生干扰。
以下截图显示了FakeNet成功运行。FakeNet可能需要以管理员权限运行。如果发生这种情况,只需以管理员身份运行它:
通过勾选虚拟机网络设置中的“Cable Connected”复选框来恢复电缆连接。为了验证一切正常,打开 Internet Explorer 并访问任何网站。结果页面应类似于以下截图:
现在,我们可以回到gethostbyname地址继续调试。此时,我们应该能在寄存器EAX中获得一个结果,同时FakeNet正在运行。
我们接下来要关注的 API 是htons。它将为我们提供程序将要连接的服务器的网络端口信息。传递给htons的参数存储在寄存器ECX中。这就是将使用的端口号,0x270F或 9999。
继续调试,我们遇到connect函数,实际的连接到服务器和指定端口在此开始。如果连接成功,connect函数会返回零给寄存器EAX。在我们的情况下,这里返回的是-1,说明连接失败。
这样做的原因是,FakeNet 只支持常用且已知的恶意软件端口。幸运的是,我们可以编辑 FakeNet 的配置文件并将端口 9999 添加到列表中。FakeNet 的配置文件FakeNet.cfg位于与 FakeNet 可执行文件相同的目录中。但是在更新此文件之前,我们需要先恢复到WSAStartup调用之前的快照。
使用记事本编辑FakeNet.cfg文件。寻找包含“RawListner”的那一行。如果没有找到,就将以下几行添加到配置文件中。
RawListener Port:9999 UseSSL:No
当这行被添加时,配置文件应该如下所示:
注意我们添加的RawListener行。添加之后,重新启动FakeNet,然后再次调试,直到我们到达connect API。这次我们希望connect函数能成功执行。
继续调试,直到我们到达send函数。send函数的第二个参数(看栈顶第二项)指向要发送的数据的地址。按下F8继续发送数据,并查看FakeNet的命令控制台。
我们已经标出了这个程序与FakeNet之间的通信。请记住,FakeNet在这里是远程服务器的模拟。发送的数据是“OLAH”。
继续调试,直到我们再次遇到send或recv函数。下一个函数是recv。
第二个参数是从服务器接收数据的缓冲区。显然,我们不指望FakeNet返回任何数据。我们可以做的是监控随后处理这个recv缓冲区数据的代码。但是为了让recv调用成功,返回值应该是一个非零值。我们必须在执行recv调用后修改寄存器 EAX 的值,就像下面的截图所示:
接下来的代码行将接收到的数据与一个字符串进行比较。请参见下面的反汇编代码,使用repe cmpsb指令进行字符串比较。该指令比较寄存器ESI和EDI指向的地址中的文本字符串。要比较的字节数存储在寄存器ECX中。假定接收到的数据位于寄存器ESI指向的地址。而字符串“jollibee”的地址则存储在寄存器EDI中。我们希望发生的情况是使两个字符串相等。
为了在调试会话中实现这一点,我们需要编辑接收到的数据地址上的字节,并将其设置为正在比较的 9 字符字符串。右键点击寄存器 ESI 的值,弹出上下文菜单,选择“Follow in Dump”。在 Dump 窗口中数据的第一个字节,右键点击并选择“Binary->Edit”。
这会弹出一个对话框(如下所示),在其中我们可以输入字符串“jollibee”:
按 F8 继续比较。这不应跳转到条件跳转指向的地址。继续调试直到我们到达另一个发送函数。再次查看要发送的数据,这是第二个参数指向的地址。然而,无论这是否成功,结果不会被处理。接下来的 API 通过closesocket和 WSACleanup 函数关闭连接,设置EAX为1,并从当前函数返回。EAX只会在最后一个发送函数之后被设置为1。
我们在下面的反汇编代码中突出显示了var_DBD,以查看在数据发送回服务器后,值1已被存储。
返回到WinMain函数后,最好进行一次虚拟机快照。
继续调试直到我们到达调用地址0x00401280。将有两个参数传递给该函数,值存储在EAX和ECX寄存器中。数据在Dump 1下被转储,如下所示:
输入函数0x00401280后,我们将只遇到一个 URLDownloadToFile 函数。该函数下载https://raw.githubusercontent.com/PacktPublishing/Mastering-Reverse-Engineering/master/ch12/manginasal并将其存储到名为unknown的文件中,如下截图所示:
这样做后,我们会遇到一个错误,无法下载文件。原因是我们仍处于模拟的互联网环境中。这一次,我们需要连接到真实的互联网。我们必须回到URLDownloadToFile函数之前的快照。
在 FakeNet 控制台中,按下CTRL + C退出工具。为了测试是否能够连接到互联网,请从互联网浏览器访问testmyids.com。结果应该与以下截图类似:
如果无法访问互联网,请检查 VirtualBox 的网络配置和 Windows 的网络设置。
网络连接正常后,程序应该能够成功下载文件。该文件的文件名为unknown。如果我们在 CFF Explorer 中加载此文件,我们将看到这些文件属性:
以下截图展示了通过选择 CFF Explorer 的 Hex Editor 查看文件内容:
该文件似乎是加密的。我们预计接下来的行为会处理这个文件。继续调试,直到我们到达地址0x004012e0。这个函数接受两个参数,一个是存储在EAX寄存器中的地址,另一个是压入栈中的地址。该函数从栈顶接收这些imagine参数字符串,以及从寄存器EAX接收unknown。
进入函数后发现正在读取文件"unknown"的内容。读取该文件到新分配的内存空间的反汇编代码如下:
持续按F8直到CloseHandle调用之后。接下来的代码展示了Cryptographic API 的使用。我们在这里再次列出这些 API 的顺序:
.text:0040137A call ds:CryptAcquireContextA
.text:0040139B call ds:CryptCreateHash
.text:004013C8 call ds:CryptHashData
.text:004013EC call ds:CryptDeriveKey
.text:004013FF call sub_401290
.text:0040147B call ds:CryptDecrypt
.text:0040149D call ds:CreateFileA
.text:004014AF call ds:WriteFile
.text:004014B6 call ds:CloseHandle
.text:004014BE call ds:Sleep
.text:004014D9 call ds:CryptDestroyKey
.text:004014E4 call ds:CryptDestroyHash
.text:004014F1 call ds:CryptReleaseContext
根据列表,似乎所有解密的内容都会存储在文件中。我们想要了解的有以下几点:
-
使用的加密算法
-
使用的加密密钥
-
存储数据的文件名称
要识别所使用的算法,我们应该监视CryptAcquireContextA函数中的参数。继续调试直到CryptAcquireContextA。第四个参数dwProvType应该告诉我们使用了哪种算法。dwProvType的值为0x18或 24。关于提供者类型值的列表,我们可以参考docs.microsoft.com/en-us/dotnet/api/system.security.permissions.keycontainerpermissionattribute.providertype。在这种情况下,24 定义为PROV_RSA_AES的值。因此,这里的加密算法使用的是RSA AES。
该算法使用的加密密钥应该是CryptHashData函数的第三个参数。请查看以下截图中的CryptHashData函数的第二个参数:
密钥是this0is0quite0a0long0cryptographic0key。
对于最后一条信息,我们需要监控CreateFileA,以获取解密数据可能被放置的文件名。在调试到CreateFileA时,我们应该看到第一个参数是输出文件名,"imagine"。CryptDecrypt函数接受加密数据的位置(第五个参数),并在同一位置进行解密。该过程以循环的形式运行,每一片解密后的数据都会附加到"imagine"文件中。
以下截图,IDA Pro 图形视图,显示了解密后的数据被附加到输出文件:
解密过程通过使用CryptDestroyKey、CryptDestroyHash和CryptReleaseContext来关闭加密句柄。
好奇吗?让我们使用 CFF Explorer 从"imagine"文件中提取信息:
使用 TrID 工具,我们获得了更有意义的文件类型,如下图所示:
该文件是一个PNG图像文件。
继续调试会话,持续按F8直到到达0x00401180地址的调用。按F7进入此函数。这揭示了此序列中注册表 API 的使用:
.text:004011BF call ds:RegOpenKeyExA
.text:004011E6 call esi ; RegQueryValueExA
.text:004011F3 call edi ; RegCloseKey
.text:00401249 call ds:RegOpenKeyA
.text:0040126A call esi ; RegQueryValueExA
.text:00401271 call edi ; RegCloseKey
基本上,注册表函数仅用于检索注册表中存在的某些值。下面显示的反汇编代码表明,第一个查询从HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice注册表项中检索ProgId的数据值:
如果我们查看注册表,这个位置指向当前登录用户使用的默认互联网浏览器的 ID。以下截图显示了Progid中设置的默认互联网浏览器 ID 的示例,FirefoxURL-308046B0AF4A39CB:
对于下一个注册表查询,RegOpenKeyExA打开HKEY_CLASSES_ROOT\FirefoxURL-308046B0AF4A39CB\shell\open\command注册表项,其中FirefoxURL-308046B0AF4A39CB是默认互联网浏览器的 ID:
随后的RegQueryValueExA有第二个参数lpValuename等于zero。请参考以下反汇编:
如果lpValuename等于0,则获取的数据将来自默认值。
查看注册表时,显示为(默认值),如下面所示:
因此,该函数执行的操作是获取默认互联网浏览器的命令行。
以下代码行解析了"imagine"文件的完整文件路径,然后将路径传递给最终函数sub_401000,然后退出进程:
在调试 sub_401000 时,我们遇到了一百多行代码,基本上是在移动测试字符串。但最终的bottomline是,它将使用 CreateProcessA 运行另一个进程。查看将传递给 CreateProcess 的参数时,第二个参数是命令行,它将执行的命令包含了默认浏览器的路径,并将 "imagine" 文件的完整路径作为参数。从以下截图可以看到,我们在 Dump 1 中转储了命令行:
结果是,使用默认的互联网浏览器打开 "imagine" 文件。显示以下截图:
分析总结
以下表格涉及我们发现的文件元素。
原始文件是一个 UPX 压缩的 Win32 可执行文件。
| 文件名 | whatami.exe |
|---|---|
| 文件大小 | 28,672 字节 |
| MD5 | F4723E35D83B10AD72EC32D2ECC61091 |
| SHA-1 | 4A1E8A976F1515CE3F7F86F814B1235B7D18A231 |
| 文件类型 | Win32 PE 文件 – 使用 UPX v3.0 压缩 |
UPX 解压版本为我们提供了关于该文件的新信息:
| 文件名 | whatami.exe |
|---|---|
| 文件大小 | 73,728 字节 |
| MD5 | 18F86337C492E834B1771CC57FB2175D |
| SHA-1 | C8601593E7DC27D97EFC29CBFF90612A265A248E |
| 文件类型 | Win32 PE 文件 – 由 Microsoft Visual C++ 8 编译 |
该程序通过进程空洞技术映射了一个未知的 PE 文件。该 PE 文件包含以下信息:
| 文件大小 | 53,248 字节 |
|---|---|
| MD5 | DD073CBC4BE74CF1BD0379BA468AE950 |
| SHA-1 | 90068FF0C1C1D0A5D0AF2B3CC2430A77EF1B7FC4 |
| 文件类型 | Win32 PE 文件 – 由 Microsoft Visual C++ 8 编译 |
从 raw.githubusercontent.com/PacktPublishing/Mastering-Reverse-Engineering/master/ch12/manginasal 下载的一个文件被作为未知文件存储。以下是该文件的信息:
| 文件名 | unknown |
|---|---|
| 文件大小 | 3,008 字节 |
| MD5 | 05213A14A665E5E2EEC31971A5542D32 |
| SHA-1 | 7ECCD8EB05A31AB627CDFA6F3CFE4BFFA46E01A1 |
| 文件类型 | 未知文件类型 |
该未知文件被解密并使用文件名 "imagine" 存储,包含以下文件信息:
| 文件名 | imagine |
|---|---|
| 文件大小 | 3,007 字节 |
| MD5 | 7AAF7D965EF8AEE002B8D72AF6855667 |
| SHA-1 | 4757E071CA2C69F0647537E5D2A6DB8F6F975D49 |
| 文件类型 | PNG 文件类型 |
为了回顾它执行的行为,以下是一步步的过程:
-
显示消息框:"
你是怎么来到这里的?" -
从资源部分解密一个 PE 映像
-
使用进程空洞技术将 "
calc" 替换为解密后的 PE 映像 -
显示消息框:"学习逆向工程很有趣。仅用于教育目的。这不是恶意软件。"
-
程序休眠 5 分钟
-
检查与 "
mcdo.thecyberdung.net:9999" 服务器的连接 -
从 raw.githubusercontent.com 下载该文件
-
解密下载的文件并将结果输出为 PNG 图像文件。
-
获取默认互联网浏览器的路径。
-
使用默认的互联网浏览器显示 PNG 图像文件。
总结
逆向工程软件需要时间和耐心。分析一款软件可能需要几天的时间。但随着练习和经验的积累,分析文件所需的时间会有所改善。
在这一章中,我们处理了一个可以使用我们所学工具逆向的文件。在调试器、反汇编器和 CFF Explorer、TriD 等工具的帮助下,我们能够提取文件信息和行为。此外,我们还学习了使用 FakeNet 模拟网络和互联网,当我们为套接字函数生成网络信息时,这对我们非常有用。
有很多障碍,包括反调试技巧。然而,对这些技巧的熟悉使我们能够绕过这些代码。
逆向工程中最重要的技巧之一是不断制作快照,以防遇到障碍。我们可以对每个功能所需的数据进行实验。
再次强调,逆向工程是一项需要耐心的工作,通过保存和加载快照,你可以“作弊”。
进一步阅读
DLL 注入 - en.wikipedia.org/wiki/DLL_injection
进程空洞化 - github.com/m0n0ph1/Process-Hollowing
第十三章:逆向不同类型的文件
到目前为止,我们一直在处理二进制可执行文件。在本章中,我们还将查看代码执行的其他方式。访问网站(HTML)和接收包含文档的电子邮件是恶意软件轻松进入目标系统的一些途径。
在本章中,我们将学习以下主题:
-
在 HTML 中调试脚本
-
理解 Office 文档中的宏
-
执行 PDF 分析
-
SWF 分析
HTML 脚本分析
几乎我们访问的每个网站都包含脚本。最常见的是包含 JavaScript 代码,这些代码通常会在点击网站上的“OK”按钮时触发,或者是在鼠标指针周围游动的那些艺术泡泡和星星。JavaScript 是站点开发者可以使用的最强大工具之一。它可以控制互联网浏览器所包含的元素。
除了 JavaScript,Visual Basic 脚本(VBScripts)也可以嵌入到 HTML 网站中。然而,VBScript 在最近的网络浏览器中已被默认禁用。这是因为 VBScript 在过去曾暴露出许多安全漏洞。此外,JavaScript 是许多互联网浏览器默认使用的语言。
网站的工作有两个方面,即服务器端和客户端。当访问一个网站时,我们看到的是客户端页面。所有后台脚本都在服务器端运行。例如,当访问一个网站时,服务器端程序发送 HTML 内容,包括文本、脚本、图像、Java 小应用程序和 Flash 文件。只有浏览器元素,如 HTML、JavaScript、Java 小应用程序和 SWF Flash,能够被互联网浏览器支持,才是服务器端程序创建并发送的对象。从本质上讲,我们可以分析的就是这些浏览器元素。
幸运的是,脚本是可读的文本文件。我们可以对 HTML 脚本进行静态分析。但像其他代码一样,逆向工程需要我们了解所使用的脚本语言。归根结底,我们需要学习 JavaScript 编程语言的基础。
让我们尝试逆向一个简单的 HTML 文件。你可以通过以下链接下载此 HTML 文件:github.com/PacktPublishing/Mastering-Reverse-Engineering/blob/master/ch13/demo_01.html。
只有在你有时间的时候才做这个。当逆向 HTML 文件时,建议你设置它的运行方式,像是在网站中查看,而不是作为一个 HTML 文件。
使用文本编辑器,如记事本,我们可以对 HTML 文件进行静态分析。其他文本编辑器,如 Notepad++ (notepad-plus-plus.org/),会更好,因为它可以显示脚本语法的颜色。这有助于我们区分脚本函数和数据,如以下截图所示:
要理解这段代码,互联网上有很多关于 HTML 编程的参考资料。以下是其中一个参考网站:www.w3schools.com/html/default.asp。我们需要关注的是在 script 标签内定义的脚本。在这里共有三个 JavaScript 脚本代码。第一个脚本包含以下代码:
alert("Hello reverser! --from a javascript code");
alert 函数用于显示消息框。消息内容应放在引号内。
第二个脚本包含以下代码:
alert("1 + 2 is equal to");
x = 1
y = 2
再次,脚本显示一个消息,然后将 1 赋值给变量 x,将 2 赋值给变量 y。
最后一个脚本包含以下代码:
alert("x + y");
这显示了另一个消息。这次,消息是 x 和 y 变量的和,结果应该是 3。即使脚本代码位于不同的标签中,最后运行的脚本中的变量值也应该会在后续脚本中得到反映。
为了证明这个行为,让我们通过在浏览器中运行该文件来动态分析它。
打开 Internet Explorer。我们也可以使用 Firefox 或 Chrome。将 demo_01.html 文件拖放到 Internet Explorer 中。加载完成后,应该会显示以下消息框:
如果浏览器禁用了 JavaScript 内容的运行,消息可能不会显示。通常会弹出安全提示,询问是否允许运行脚本代码。只需允许脚本运行即可:
随后将弹出以下消息框:
现在页面已经完全加载,按 F12 打开调试器控制台。选择调试器面板。这时应该会显示 HTML 脚本,如下所示:
在调试器中,将断点放在第 3 行,这是第一个 alert 函数。要设置断点,请点击行号左侧的空白区域。这样应该会创建一个红点,表示断点行。以下截图展示了三个脚本及其第一行标记的断点:
通过将焦点放在浏览器页面上并按下 F5 键来刷新浏览器。我们可能会调试 browsertools 脚本,这是一个 Internet Explorer 初始化脚本。以下截图展示了这一过程:
再次按下 F5 键,让调试器继续执行,直到我们到达断点。此时,我们应该已经到达第一个 alert 函数,如下所示:
我们可以按 F11 键进入脚本,或按 F10 键跳过当前行。这样做应该会弹出第一个消息框。继续按 F10 键,跳过接下来的脚本行。下一个脚本是另一个 alert 函数:
以下几行将1赋值给x,将2赋值给y。我们可以通过将这些变量添加到监视列表中来监控这些变量的变化,监视列表位于右侧面板。点击“添加监视”来添加我们可以监控的变量:
最后的函数是另一个alert函数,用于显示x和y的和。
让我们尝试用demo_02.html (github.com/PacktPublishing/Mastering-Reverse-Engineering/blob/master/ch13/demo_02.html)。
如果我们调试这个,它执行的行为与我们在demo_01.html中遇到的一样。不同之处在于,当我们从文本编辑器查看时,它看起来是被混淆的:
消息被转换为转义格式,使用每个 ASCII 字符的十六进制等价物。在上一章中,我们学习了Cyberchef,这是一种在线工具,可以用来去混淆这些类型的数据。由于这些数据是转义的,我们应该使用unescape操作来解码这些数据。在Cyberchef中,搜索unescape操作,然后将转义数据复制并粘贴到输入窗口中。我们应该得到一个解码后的输出,显示我们在消息中看到的确切文本,像这样:
分析 HTML 脚本并不复杂,尤其是因为几乎所有内容都可以被人类读取。我们需要理解的只是语法和脚本语言的功能。此外,这是使用调试工具动态分析脚本的一种方法,而这些工具在网络浏览器中是可以使用的。
MS Office 宏分析
Microsoft Office 有一种方法可以自动化一些简单的任务,例如创建格式化的表格或插入信头。这叫做 MS Office 宏。MS Office 宏利用了 Visual Basic for Application 语言,它与 Visual Basic 脚本使用相同的语言。然而,这些也可以被滥用来做更多的事情,比如下载文件、创建文件、添加注册表条目,甚至删除文件。
首先,我们需要静态工具来读取信息并从给定的 Office 文件中提取宏源代码。要打开 MS Office 文档,我们需要安装 Microsoft Office。另一个可以使用的工具是 OLE 工具,可以从www.decalage.info/en/python/oletools下载。这些工具集是 Python 脚本,需要在系统上安装 Python 2.7。Python 安装程序可以从www.python.org/下载。
我们要分析的第一个文件是 github.com/PacktPublishing/Mastering-Reverse-Engineering/blob/master/ch13/demo_01.doc。在命令行中输入以下代码,使用 olevba.py 分析 demo_01.doc:
python olevba.py demo_01.doc
这将提取有关VBA源代码及其源信息:
从上面的截图中我们可以看到,源代码包含两个子程序:autoopen() 和 autoclose()。olevba.py 也描述了这些与文档打开和关闭时事件绑定的子程序。
该源包含弹出消息的代码。现在,让我们尝试在 Microsoft Word 中打开文档。通过这样做,我们可能会看到 Microsoft Word 显示有关文档包含代码的安全警告。点击启用内容,以便查看宏可以做什么:
第一个消息立刻出现:
要调试代码,我们需要打开 VBA 编辑器。选择查看 -> 宏,这将打开宏对话框,您可以在其中选择任何宏名称并点击编辑按钮:
我们当前使用的是 Microsoft Office 2013,因此 VBA 编辑器的用户界面在其他版本中可能有所不同。在 VBA 编辑器中,我们现在应该可以看到源代码。按下F9键可以启用或禁用断点。按F8键进行逐步调试。F5用于继续运行代码。我们可以从任何子程序开始调试。选择调试菜单查看更多可用的调试功能:
关闭文档将弹出以下消息框:
现在,尝试分析 demo_02.doc。由于我们将研究如何推导出密码,这将是一个相当大的挑战。
记住,VBA 编辑器是宏开发者的控制台。在这里,宏程序被开发和调试。因此,为了逆向我们正在寻找的内容,我们可以操作源代码。
demo_02.doc的密码可以在本章的摘要部分找到。
PDF 文件分析
PDF 文件已经发展到可以运行特定操作并允许执行 JavaScript。对于 PDF 分析,我们可以提取事件信息并分析 JavaScript 将执行的操作。我们可以使用 Didier Stevens 的 PDF 工具集来帮助分析 PDF。这一工具集是基于 Python 的,因此我们需要安装 Python。PDF 工具可以从 blog.didierstevens.com/programs/pdf-tools/ 下载。如果你访问该网站,可以看到有关每个工具的描述。
让我们尝试使用工具分析 github.com/PacktPublishing/Mastering-Reverse-Engineering/blob/master/ch13/demo_01.pdf。使用 pdfid.py 执行以下命令:
python pdfid.py demo_01.pdf
以下截图显示了 pdfid 在 demo_01.pdf 上的结果:
在这里,我们可以看到它嵌入了 JavaScript 代码。现在让我们尝试使用 pdf-parser.py 文件,以便提取更多信息。PDF 文件中的某些元素可能已压缩,无法读取。pdf-parser 工具能够解压这些流。执行以下命令将 pdf-parser 的输出重定向到 demo_01.log 文件:
python pdf-parser.py demo_01.pdf > demo_01.log
pdf-parser 给出的输出与 demo_01.pdf 的内容基本相同。原因是没有 PDF 对象被解压缩。如果我们仔细查看输出内容,可以轻松识别出脚本代码的位置:
<<
/JS (app.alert({cMsg: "Reversing is fun!", cTitle: "Mastering Reverse Engineering"})
; )
/S /JavaScript
>>
因此,使用 Chrome 作为我们的 PDF 阅读器时,PDF 会显示以下消息框:
要调试 JavaScript,我们需要将其复制到一个单独的 JavaScript 或 HTML 文件中。我们可能还需要修复运行 JavaScript 运算符的语法。PDF 中的 JavaScript 代码可以转换为以下 HTML 代码:
<html>
<script>
alert("Reversing is fun!", "Mastering Reverse Engineering");
</script>
</html>
SWF 文件分析
ShockWave Flash 文件也可以包含代码。基本上,Flash 文件是合法编写的,按照一系列任务的顺序执行。但就像任何其他代码一样,它也可能被滥用来执行恶意活动。
我们要分析的 SWF 文件可以从 github.com/PacktPublishing/Mastering-Reverse-Engineering/blob/master/ch13/demo01.swf 下载。
在撰写本书时,用于分析 SWF 的主要工具是 JPEXS SWF 反编译器。除此之外,我们先来谈谈其他可以解析 SWF 文件的现有工具。这些工具如下:
-
SWFTools
-
FLASM
-
Flare
-
XXXSWF
SWFTools
SWFTools 是一套用于读取和构建 SWF 文件的工具。它可以从 www.swftools.org/ 下载。要成功安装 SWFTools,应该以管理员身份运行。工具在命令行中使用。这里有两个可以提取 SWF 文件信息的工具:swfdump 和 swfextract。这是 swfdump 给出的结果:
结果告诉我们该文件是 zlib 压缩的。还有一个名为 Main 的 DOABC 方法。DOABC 的存在也意味着嵌入了动作脚本。使用 HxD,我们可以验证文件是否被压缩。魔法头 CWS 表明 SWF 文件确实是压缩的。未压缩的 SWF 文件以 FWS 魔法字节开头:
另一个工具,swfextract,能够提取嵌入的视频或图像。demo01.swf不包含任何媒体,正如我们从以下截图中看到的:
SWFTools中的其他工具用于从 PDF、图像和视频构建SWF文件。
FLASM
FLASM是一个能够解压和反汇编SWF文件的工具。它可以从nowrap.de/flasm.html下载。我们使用-x参数解压了demo01.swf,并得到了以下输出:
之后,我们使用-d参数反汇编文件,并显示了关于SWF结构的信息:
我们在这里看不到任何反汇编或反编译的动作脚本。
Flare
这是一个能够反编译 ActionScript 代码的工具。它可以从nowrap.de/flare.html下载。然而,它可能无法完全支持AS2和AS3代码。只需将SWF文件传递给 Flare 工具,它将生成一个FLR文件。我们可以使用以下命令执行 Flare:
flare.exe demo01.swf
结果保存在demo01.flr中,包含以下输出:
movie 'demo01.swf' {
// flash 32, total frames: 1, frame rate: 30 fps, 800x600 px, compressed, network access alowed
metadata <rdf:RDF xmlns:rdf=\'http://www.w3.org/1999/02/22-rdf-syntax-ns#\'><rdf:Description rdf:about=\'\' xmlns:dc=\'http://purl.org/dc/elements/1.1\'><dc:format>application/x-shockwave-flash</dc:format><dc:title>Adobe Flex 4 Application</dc:title><dc:description>http://www.adobe.com/products/flex</dc:description><dc:publisher>unknown</dc:publisher><dc:creator>unknown</dc:creator><dc:language>EN</dc:language><dc:date>Oct 29, 2018</dc:date></rdf:Description></rdf:RDF>
// unknown tag 82 length 706
// unknown tag 76 length 9
}
它的结果与FLASM相同,没有反汇编任何动作脚本。
XXXSWF
该工具可以从github.com/viper-framework/xxxswf下载。它是一个 Python 脚本,接受以下参数:
Usage: xxxswf.py [options] <file.bad>
Options:
-h, --help show this help message and exit
-x, --extract Extracts the embedded SWF(s), names it MD5HASH.swf &
saves it in the working dir. No addition args needed
-y, --yara Scans the SWF(s) with yara. If the SWF(s) is
compressed it will be deflated. No addition args
needed
-s, --md5scan Scans the SWF(s) for MD5 signatures. Please see func
checkMD5 to define hashes. No addition args needed
-H, --header Displays the SWFs file header. No addition args needed
-d, --decompress Deflates compressed SWFS(s)
-r PATH, --recdir=PATH
Will scan a directory for files that contain SWFs.
Must provide path in quotes
-c, --compress Compress SWF using Zlib
-z, --zcompress Compress SWF using LZMA
我们尝试使用这个工具处理demo01.swf。在使用-H参数后,工具告诉我们该文件已被压缩。然后我们使用-d选项解压了文件,得到了一个解压后的SWF版本,保存在243781cd4047e8774c8125072de4edb1.swf文件中。最后,我们对解压后的文件使用了-H参数:
到目前为止,在没有yara和md5功能的情况下,最有用的功能是它能够搜索嵌入的 Flash 文件。这在检测包含嵌入 SWF 的SWF恶意软件时非常有用。
JPEXS SWF 反编译器
最常用的 SWF 文件分析工具之一是JPEXS SWF 反编译器。夜间版本可以从github.com/jindrapetrik/jpexs-decompiler下载。该工具能够反编译支持AS3的ActionScript。以下截图显示了JPEXS控制台:
除了能够反编译,它还具有一个可以与 Adobe Flash Player 的调试器进行设置的界面。安装 JPEXS 后,我们需要从www.adobe.com/support/flashplayer/debug_downloads.html下载flash player projector 内容调试器。
打开 JPEXS,然后选择设置->高级设置->路径。接着,浏览到下载的 Flash 可执行文件,填写 Flash Player 投影仪内容调试器路径。完成后点击确定:
这是一个重要的设置,它使我们能够调试反编译后的 ActionScript。你也可以通过从www.adobe.com/support/flashplayer/debug_downloads.html下载 Flash Player 投影仪来填写 Flash Player 投影仪路径。
打开 SWF 文件并展开左侧窗口中的对象树。在scripts对象下选择 Main。这样就会显示反编译后的 ActionScript,如下图所示:
这里是demo01.swf的反编译代码:
package
{
import flash.display.Sprite;
import flash.text.TextField;
public class Main extends Sprite
{
public function Main()
{
super();
trace("Hello World!");
var myText:TextField = new TextField();
myText.text = "Ahoy there!";
myText.textColor = 16711680;
myText.width = 100;
myText.height = 100;
addChild(myText);
var myText2:TextField = new TextField();
myText2.text = "Reversing is fun!\n--b0yb4w4n9";
myText.y = 100;
addChild(myText2);
}
}
}
点击调试按钮或Ctrl+F5,这应该会带我们进入调试控制台。在最左边的窗口中,显示的是反编译后的 ActionScript 的字节码等效物。
代码的作用是创建两个 TextFields,包含显示在 SWF 显示空间上的文本。
JPEXS 是一款具有我们希望用来分析 Flash 文件中代码的关键功能的工具。它具备字节码反汇编器、源代码反编译器和调试器。
总结
分析各种文件类型也采用与逆向工程相同的概念。在本章中,我们学习了文件格式所使用的脚本语言。如果我们有兴趣理解文件的头部和结构,我们还可以收集更多的信息。我们还了解到,只要可执行代码可以嵌入到文件中,就一定有方法可以分析它。虽然可能无法轻松进行动态分析,但至少可以进行静态分析。
我们讨论了如何调试嵌入在 HTML 脚本中的 JavaScript。实际上,我们可以分析我们访问的任何网站。我们还了解了可以用来提取 Microsoft Office 文档中宏代码的工具。恰好,我们也可以使用 VBA 编辑器调试这些宏代码。我们还研究了多种工具,用于从 PDF 文件中提取 JavaScript 代码。然后,我们使用 JPEXS 分析了 SWF 文件,这是一个强大的工具,具备反汇编器、反编译器和调试器。
逆向工程软件是一个实践中的概念。我们研究软件是什么以及它是如何工作的。我们还学习了执行文件中代码背后的低级语言。学习这门语言可能需要时间,但从中获得的知识和经验是值得的。
祝你逆向工程愉快!
P.S. demo_02.doc的密码是 burgersteak。
深入阅读
www.w3schools.com/html/default.asp:一个学习 HTML 脚本的优秀教程网站
www.javascriptobfuscator.com - 这是一个可以混淆 JavaScript 代码的在线网站