【笔记】【THM】Malware Analysis(恶意软件分析)
探索恶意软件的世界,分析恶意软件如何感染系统并造成破坏。
恶意软件分析就像猫捉老鼠的游戏。恶意软件的作者一直在设计新的技术来躲避恶意软件分析师的眼睛,而恶意软件分析师也一直在寻找识别和抵消这些技术的方法。在这个模块中,我们将开始学习恶意软件分析的旅程,从基础知识到理解恶意软件作者使用的常见技术。最后,我们将学习一些工具,这些工具使恶意软件分析师能够在识别恶意软件作者的意图并击败他们方面获得立足点。
x86体系结构概述
本文相关的TryHackMe实验房间链接:TryHackMe | x86 Architecture Overview
本文相关内容:x86架构的速成课程,使我们能够进行恶意软件逆向工程。
介绍
恶意软件通常通过滥用系统的设计方式来工作。因此,为了理解恶意软件的工作原理,我们必须知道它们运行的系统的架构。在这个房间里,我们将从恶意软件分析的角度对x86架构进行简要概述。请注意,我们可能会跳过很多关于x86架构的细节,但这是因为它们与恶意软件分析无关。
(补充:X86架构(The X86 architecture)是微处理器执行的计算机语言指令集,指一个intel通用计算机系列的标准编号缩写,也标识一套通用的计算机指令集合。)
学习目标
总而言之,我们将在这个房间里讨论以下主题。
- CPU体系结构及其组成概述
- 不同类型的CPU寄存器及其使用
- 程序查看的内存布局
- 栈布局和栈寄存器
现在,让我们深入讨论并了解上述主题。
CPU架构概述
目前使用最广泛的CPU体系结构来源于冯·诺依曼体系结构。下图展示了该架构的简要概述。
这个图显示了中央处理器(CPU)由三个组成部分:算术逻辑单元(ALU),控制单元(CU)和寄存器。CPU与CPU外部的内存和I/O设备交互。
让我们了解一下上图中提到的每个组件。
控制单元(Control Unit):
控制单元从主内存中获取指令,如图所示,主内存在CPU外部。要执行的下一条指令的地址存储在一个称为指令指针或IP的寄存器中。在32位系统中,这个寄存器称为EIP,而在64位系统中,它称为RIP。
算术逻辑单元(Arithmetic Logic Unit):
算术逻辑单元执行从存储器中取出的指令。然后,执行指令的结果存储在寄存器或内存中。
寄存器(Registers):
寄存器是CPU的存储器。寄存器通常比位于CPU外部的主内存小得多,通过直接访问CPU来放置重要数据,有助于节省执行指令的时间。
内存(Memory):
内存,也称为主存或随机存取存储器(RAM),包含程序运行所需的所有代码和数据。当用户执行一个程序时,它的代码和数据被加载到内存中,CPU从内存中一次访问一条指令。
I / O设备(Input/Output devices):
I/O设备或输入/输出设备是与计算机交互的所有其他设备。这些设备包括键盘、鼠标、显示器、打印机、大容量存储设备(如硬盘和usb)等。
简而言之,当一个程序必须被执行时,它被加载到内存中。从那里,控制单元每次使用指令指针寄存器获取一条指令,算术逻辑单元执行它。结果存储在寄存器或内存中。
问题
1.程序运行所需的代码和数据存储在计算机体系结构的哪一部分?
2.CPU的哪一部分存储少量数据?
3.算术运算在哪个单元中执行?
WP
1.程序运行所需的代码和数据存储在内存中
2.CPU的寄存器存储少量数据
3.算术运算在算术逻辑单元中执行
寄存器概述
寄存器是CPU的存储介质。CPU可以比任何其他存储介质更快地从寄存器访问数据;然而,它有限的大小意味着必须有效地使用它。为此,寄存器分为下列不同类型。
- Instruction Pointer 指令指针
- General Purpose Registers通用寄存器
- Status Flag Registers 状态标志寄存器
- Segment Registers 段寄存器
让我们在下面逐一查看这些寄存器:
指令指针:
指令指针是一个寄存器,包含了CPU要执行的下一条指令的地址。它也称为程序计数器。它最初是Intel 8086处理器(术语x86源于此)中的一个16位寄存器,缩写为IP。在32位处理器中,指令指针变成32位寄存器,称为EIP或扩展指令指针。在64位系统中,这个寄存器成为称为RIP(这里的R代表寄存器)的64位寄存器。
通用寄存器
x86系统中的通用寄存器都是32位寄存器。顾名思义,它们在CPU执行指令期间使用。在64位系统中,这些寄存器被扩展为64位寄存器。它们包含下列寄存器。
EAX或RAX:
这是累加器寄存器。算术运算的结果通常存储在这个寄存器中。在32位系统中,存在32位EAX寄存器,而在64位系统中存在64位RAX寄存器。这个寄存器的最后16位可以通过寻址AX来访问。类似地,它也可以在8位中寻址,通过使用AL为低8位,AH为高8位。
EBX或RBX:
该寄存器也称为基址寄存器,通常用于存储基址以引用偏移量。与EAX/RAX类似,它可以被寻址为64位RBX、32位EBX、16位BX、8位BH和BL寄存器。
ECX或RCX:
该寄存器也称为计数器寄存器,常用于循环等计数操作。与上述两个寄存器类似,它可以被寻址为64位RCX、32位ECX、16位CX和8位CH和CL寄存器。
EDX或RDX:
该寄存器也称为数据寄存器。它经常用于乘法/除法运算。与上述寄存器类似,它可以被寻址为64位RDX、32位EDX、16位DX和8位DH和DL寄存器。
ESP或RSP:
这个寄存器称为栈指针(Stack Pointer)。它指向栈的顶部,与栈段寄存器联合使用。它是一个32位寄存器,在32位系统中称为ESP,在64位系统中称为RSP。它不能用较小的寄存器寻址。
EBP或RBP:
这个寄存器称为基指针(Base Pointer)。它用于访问栈传递的参数。它也与栈段寄存器联合使用。它是一个32位寄存器,在32位系统中称为EBP,在64位系统中称为RBP。
ESI 或 RSI:
这个寄存器称为源索引寄存器。它用于字符串操作。它与数据段(DS)寄存器一起用作偏移量。它是一个32位寄存器,在32位系统中称为ESI,在64位系统中称为RSI。
EDI 或 RDI
该寄存器称为目标索引寄存器。它也用于字符串操作。它与额外的Segment (ES)寄存器一起用作偏移量。它是一个32位寄存器,在32位系统中称为EDI,在64位系统中称为RDI。
R8-R15:
这些64位通用寄存器在32位系统中不存在。它们被引入64位系统。它们还可以以32位、16位和8位模式寻址。例如,对于R8寄存器,我们可以使用R8D进行低32位寻址,使用R8W进行低16位寻址,使用R8B进行低8位寻址。在这里,后缀D代表Double-word, W代表Word, B代表Byte。
问题
1.哪个寄存器保存要执行的下一条指令的地址?
2.32位系统中的哪个寄存器也称为计数器寄存器?
3.上面讨论的寄存器中,哪些不存在于32位系统中?
WP
1.指令指针保存要执行的下一条指令的地址
2.32位系统中的ECX寄存器也称为计数器寄存器
3.上面讨论的寄存器中,R8-R5不存在于32位系统中
寄存器-续
状态标志寄存器:
执行命令时,有时需要一些关于执行状态的指示。这就是状态标志的作用。用于32位系统的一个32位状态标志寄存器,称为“EFLAGS”;它在64位系统中扩展为64位,在64位系统中称为“RFLAGS”。状态标志寄存器由单个位标志组成,可以是1或0。下面讨论一些必要的标志。
Zero Flag零标志:
缩写由ZF表示,表示最后执行的指令的结果是0。例如,如果执行一条从自身减去一个RAX的指令,结果将是0。在这种情况下,ZF将被设置为1。
Carry Flag进位标志:
缩写由CF表示,表示最后执行的指令导致的数字对目标来说太大或太小。例如,如果我们将0xFFFFFFFF和0x00000001相加,并将结果存储在一个32位寄存器中,那么结果对寄存器来说就太大了。在这种情况下,CF将被设置为1。
Sign Flag符号标志:
缩写由SF表示,表示操作的结果是否为负数或最高有效位是否设置为1。如果满足这些条件,SF设置为1;否则,将其设置为0。
Trap Flag陷阱标志:
缩写由TF表示,表示处理器是否处于调试模式。设置TF时,CPU将出于调试目的一次执行一条指令。这可以被恶意软件用来识别它们是否在调试器中运行。
通用寄存器 | 段寄存器 | 状态寄存器 | 指令指针 |
---|---|---|---|
RAX, EAX, AX, AH, AL | CS | EFLAG | EIP,RIP |
RBX, EBX, BX, BH, BL | SS | ||
RCX, ECX, CX, CH, CL | DS | ||
RDX,EDX,DX,DH,DL | ES | ||
RBP, EBP, BP | FS | ||
RSP, ESP, SP | GS | ||
RSI、ESI、SI | |||
RDI, EDI, DI | |||
R8-R15 |
段寄存器:
段寄存器是16位寄存器,它将平面内存空间转换为不同的段,以便于寻址。有6个段寄存器,解释如下:
- 代码段(Code Segment, CS):代码段寄存器指向内存中的代码段。
- 数据段:数据段(Data Sgment, DS)寄存器指向内存中程序的数据段。
- 栈段(Stack Segment, SS):栈段寄存器指向程序在内存中的栈。
- 额外段(ES、FS和GS):这些额外的段寄存器指向不同的数据段。这些和DS寄存器将程序的内存划分为四个不同的数据段。
问题
1.程序使用哪个标志来标识它是否在调试器中运行?
2.当操作中的最高位设置为1时,将设置哪个标志?
3.哪个段寄存器包含了指向内存中代码段的指针?
WP
1.程序使用陷阱标志来标识它是否在调试器中运行
2.当操作中的最高位设置为1时,将设置符号标志
3.代码段寄存器包含了指向内存中代码段的指针
内存概述
当一个程序被加载到Windows操作系统的内存中时,它看到的是内存的一个抽象视图。这意味着程序不能访问整个内存;相反,它只能访问自己的内存。对于这个程序来说,这就是它所需要的所有内存。为简洁起见,我们不会深入操作系统如何执行抽象的细节。我们将从程序的角度来看待内存,因为这与我们进行恶意软件的逆向工程更相关。
这里的图表是一个程序的典型内存布局的概述。可以看到,内存分为不同的部分,即栈、堆、代码和数据。虽然我们已经以特定的顺序展示了这四个部分,但这可能与它们在任何时候的顺序不同,例如,代码部分可以位于数据部分之下。
我们可以在下面找到这四个部分的简要概述。
Code代码:
代码部分,顾名思义,包含程序的代码。具体来说,本节指的是可移植可执行文件中的text部分,其中包括CPU执行的指令。这部分内存具有执行权限,这意味着CPU可以执行程序内存中的这部分数据。
Data数据:
Data部分包含了已初始化的数据,这些数据不是变量,而是常量。它指的是可移植可执行文件中的data部分。它通常包含全局变量和其他在程序执行期间不应该改变的数据。
Heap堆:
堆,也称为动态内存,包含了程序执行过程中创建和销毁的变量和数据。创建变量时,会在运行时为该变量分配内存。当该变量被删除时,内存被释放。因此命名为动态内存。
Stack栈:
从恶意软件分析的角度来看,栈是内存的重要组成部分之一。这部分内存包含局部变量、传递给程序的参数,以及调用该程序的父进程的返回地址。由于返回地址与CPU指令的控制流相关,栈经常成为恶意软件劫持控制流的目标。您可以查看缓冲区溢出空间来了解这是如何发生的。我们将在下一个任务中介绍有关栈的更多细节。
问题
1.当程序加载到内存中时,它是否有系统内存的完整视图?
2.内存中的哪一部分包含代码?
3.哪个内存部分包含与程序控制流相关的信息?
WP
1.当程序加载到内存中时,它没有系统内存的完整视图
2.内存中的Code部分包含代码
3.内存Stack部分包含与程序控制流相关的信息
堆栈的布局
栈是程序内存的一部分,其中包含传递给程序的参数、局部变量和程序的控制流。这使得该栈在恶意软件分析和逆向工程方面非常重要。恶意软件经常利用栈劫持程序的控制流。因此,理解栈、它的布局及其工作方式非常重要。
栈是后进先出(LIFO)内存。这意味着最后压入栈的元素是第一个弹出的元素。例如,如果我们将A、B和C压入栈中,当我们弹出这些元素时,第一个弹出的将是C,然后才是B和A。CPU使用两个寄存器来跟踪堆栈。一个是栈指针(ESP或RSP),另一个是基指针(EBP或RBP)。
The Stack Pointer栈指针:
栈指针指向栈的顶部。当有新元素被压入栈时,栈指针的位置会改变,以考虑刚被压入栈的新元素。类似地,当一个元素从栈弹出时,栈指针会调整自己以反映这一变化。
The Base Pointer基指针:
任何程序的基指针都是不变的。这是当前程序栈跟踪其局部变量和参数的引用地址。
Old Base Pointer and Return Address旧基指针和返回地址:
基指针下面是调用程序(调用当前程序的程序)的旧基指针。在旧基指针下面是返回地址,即当前程序执行结束后,指令指针将返回的地址。
劫持控制流的一种常见技术是溢出栈上的局部变量(Local Var),从而用恶意软件作者选择的地址覆盖返回地址。这种技术称为栈缓冲区溢出(Stack Buffer Overflow)。
Arguments参数:
传递给函数的参数在函数开始执行之前被压入栈。这些参数就在栈上的返回地址下面。
函数序言和尾声:
当函数被调用时,栈已经为函数的执行做好了准备。这意味着参数在函数开始执行之前被压入栈。之后,返回地址和旧基指针被压入栈。一旦这些元素被压入,基指针的地址就会改变到栈的顶部(此时将是调用函数的栈指针)。在函数执行时,栈指针会根据函数的需求移动。这部分代码将参数、返回地址和基指针压入栈中,并重新排列栈和基指针,这部分代码称为函数序言。
类似地,当函数退出时,旧的基指针从栈弹出到基指针上。返回地址弹出到指令指针,栈指针重新排列,指向栈顶。执行该操作的代码部分称为函数尾声(Function Epilogue)。
点击任务顶部的View Site按钮,在分屏视图中启动静态站点。现在,跳到附加的静态站点并通过正确地排列堆栈找到标志。
问题
按照附接的静态站点中的说明找到flag
WP
打开网址启动静态站点
按照顺序排列即可得到flag