恶意代码分析实战_04反汇编速成

0. 概述

静态分析基础技术就像在解剖时只看到尸体表面。通过这些分析,你可以得到一些初步结论。但要了解整体情况,还需要更深层次的分析。

动态分析基础技术也有不足。比如,当恶意代码收到一个特殊设计的网络包时,你可以通过动态分析基础技术知道它的反应。但你却无法了解这个网络包的格式。

为了对恶意为了对恶意代码进行更深层次的分析,需要对恶意代码进行逆向分析。

1. 抽象层次

在经典的计算机体系结构中,往往将计算机系统表示为一些抽象层次,来隐藏其实现细节。比如,你可以在各种类型的硬件上运行Windows系统,因为对于操作系统而言,底层硬件已经被抽象了。

恶意代码分析过程中存在三种编码层次,如图1所示。恶意代码作者在高级语言层编写程序,然后使用汇编器生成可以被CPU执行的机器码。反过来,恶意代码分析师和逆向工程师在低级语言层操作,使用反汇编器生成可以阅读和分析的汇编代码,从而了解程序是如何运行的。

图1. 恶意代码分析过程中的编码层次

一般来说,计算机系统包括六个抽象层次,分别是硬件、微指令、机器码、低级语言、高级语言、解释语言。

  • 硬件:硬件层是唯一一个物理层,由电子电路组成。这些电路实现了XOR门、AND门、OR门和NOT门等逻辑运算器的复杂组合,称为数字逻辑。由于物理特性,硬件很难被软件操作。
  • 微指令:微指令层又称为固件,只能在特定的电路上执行。这层由一些微指令构成,它们从更高的机器层翻译而来,提供访问硬件的接口。当分析恶意代码时,我们通常不关心微指令,因为它们通常是为特定的计算机硬件设计的。
  • 机器码:机器码层由操作码(opcode)组成,操作码是一些16进制形式的数字,用于告诉处理器你想要它做什么。机器码一般由多条微指令实现,这样底层硬件就能实际执行代码了。而机器码本身又由高级编程语言编写的计算机程序编译而来。
  • 低级语言:低级语言是计算机体系结构指令集的人类易读版本,主要是汇编语言。恶意代码分析师使用这一层,因为对于人来说,机器码太难理解了。我们使用反汇编器来生成低级语言的文本,这些文本由一些简单的助记符组成,如mov和jmp。
  • 高级语言:大部分程序员使用高级语言。高级语言对机器层做了很强的抽象,从而可以很轻松地使用程序逻辑和流控制机制。高级语言包括C、C++等。它们被一个编译器经过称为编译的过程转化为机器码。
  • 解释语言:解释型语言位于最高层。很多程序员使用诸如C#、Perl、.Net、Java等解释语言。这一层的代码不会被编译为机器码,而是被翻译为字节码。字节码是特定于该语言的一种中间表示,它在解释器中执行。解释器是一个运行时将字节码实时翻译为可执行机器码的程序。相比于传统被编译的代码,解释器提供了一种自动的抽象层次,因为它可以独立于操作系统,自己处理错误和内存管理。

2. 逆向工程

恶意代码存储在磁盘上时,通常是机器码层的二进制形式。机器码是一种计算机可以快速高效运行的代码形式。而我们反汇编恶意代码,就是使用反汇编器,将恶意代码二进制文件作为输入,输出汇编语言代码。

汇编语言实际上是一类语言的统称。每种汇编语言的方言,都是用来对一类微处理器家族编程的,例如,x86、x64、SPARC、PowerPC、MIPS、ARM等。

x86,又称Intel IA-32,是大部分32位PC使用的体系结构,微软目前所有的32位Windows系统也都运行在x86上。此外,大部分运行在AMD64和Intel64体系结构上的Windows也都支持x86的32位二进制程序。正因如此,许多恶意代码是为x86编译的,而我们也将主要研究这些恶意代码。

3. x86体系结构

大部分计算机体系结构(包括x86)在内部实现上遵循冯诺依曼结构,这种结构包含3种硬件组件,如图2所示。

  • 中央处理单元(CPU),负责代码执行。CPU又包含一些组件,控制单元(control unit)使用指令指针(instruction pointer)的寄存器(registry)从内存取得要执行的指令,这个寄存器种存有指令的地址。寄存器是CPU中数据的基本存储单元,通过它,很多时候CPU不再需要访问内存,从而节省了时间。算数逻辑单元(arithmetic logic unit,ALU)执行从内存取来的指令,并将结果放到寄存器或内存中。一条条取指令、执行指令的过程不断重复,就形成了程序的运行。
  • 内存(Memery),负责存储所有的数据和代码。
  • 输入/输出系统(I/O),为硬盘、键盘、显示器等设备提供接口。

图2. 冯诺依曼结构

3.1 内存

一个程序的内存可以分为四个节,如图3所示。数据节包含一些值,这些值在程序初始加载时被放在这里,称为静态值,因为程序运行时它们可能并不发生变化,还可以称为全局值。代码节包含了在执行程序任务时CPU所取得的指令,这些代码决定了程序是做什么的,以及程序中的任务如何协调工作。堆节是为程序执行期间需要的动态内存准备的,用于创建(分配)新的值,以及消除(释放)不再需要的值。将其称为动态内存,是因为其内容在程序运行期间经常改变。栈节用于函数的局部变量和参数,以及控制程序执行流。

图3. 程序的基本内存布局

3.2 寄存器

寄存器是可以被CPU使用的少量数据存储器,访问其中内容的速度比访问其他存储器要快。x86处理器中有一组寄存器,可以用于临时存储或者作为工作区。表3列举了最常用的x86寄存器,这些寄存器可以分为通用寄存器、段寄存器、状态标志、指令指针四大类。

表3. x86寄存器
通用寄存器段寄存器标志寄存器指令指针
EAX(AX,  AH, AL)CSEFLAGSEIP
EBX(BX, BH, BL)SS
ECX(CX, CH, CL)DS
EDX(DX, DH, DL)ES
EBP(BP)FS
ESP(SP)GS
ESI(SI)

其中,通用寄存器为CPU执行期间使用的寄存器,段寄存器用于定位内存节,状态标志用于做出决定,指令指针用于定位要执行的下一条指令。

通用寄存器

通用寄存器一般用于存储数据或内存地址,而且经常交换着使用以完成程序。不过,虽然它们被称为通用寄存器,但它们并不完全通用。一些x86指令只能使用特定的寄存器。例如乘法和除法指令就只能使用EAX和EDX。

除了指令的定义,通用寄存还还被程序的一致特性使用。在编译代码时对寄存器的一致特性称为约定(convention)。具备编译器使用约定知识可以让恶意代码分析师更快地阅读代码,因为不需要浪费时间去搞清楚一些寄存器是如何被使用的。比如,EAX通常存储了一个函数调用的返回值,因此,如果你看到一个函数调用之后马上用到了EAX寄存器,可能就是在操作返回值了。

所有通用寄存器的大小都是32位,可以在汇编代码中以32位或16位引用。比如,EDX指向这个完整的32位寄存器,而DX指向EDX寄存器的低16位。有4个寄存器(EAX、EBX、ECX、EDX)还可以8位值的方式引用,从而使用其最低的8位,或次低的8位。例如,AL指向EAX寄存器的最低8位,而AH指向它的次低8位。

标志寄存器

EFLAGS寄存器是一个标志寄存器。在x86架构中,它是32位的,每一位是一个标志。在指向期间,每一位表示要么是置位(值为1),要么是清除(值为0),并由这些值来控制CPU的运算,或者给出某些CPU运算的值。对于恶意代码分析来说,最重要的一些标志如下:

  • ZF:当一个运算的结果等于0时,ZF被置位,否则被清除。
  • CF:当一个运算的结果相对于目标操作数太大或太小时,CF被置位,否则被清除。
  • SF:当一个运算的结果位负值,SF被置位;若结果为正数,SF被清除。对算数运算,当运算结果的最高位值为1时,SF也会被置位。
  • TF:用于调试,当它被置位时,x86处理器每次只执行一条指令。

指令指针

在x86架构中,EIP寄存器,又称为指令指针或程序计数器,保存了程序将要执行的下一条指令在内存中的地址。EIP的唯一作用就是告诉处理器接下来要做什么

3.3 栈

用于函数的内存、局部变量、流控制结构等被存储在栈中。栈是一种用压(push)和弹(pop)操作来刻画的数据结构。向栈中压入一些东西,然后再把它们弹出来,它是一种后入先出(LIFO)的结构。

x86架构有对栈的内建支持。用于这种支持的寄存器包括ESP和EBP。其中,ESP是栈指针,包含了指向栈顶的内存地址。一些东西被压入或弹出栈时,这个寄存器的值相应改变。EBP是栈基址寄存器,在一个函数中保持不变,因此程序把它当成定位器,用来确定局部变量和参数的位置。

与栈有关的指令包括push、pop、call、leave、enter、ret。在内存中,栈被分配成自顶向下的,最高的地址最先被使用。当一个值被压入栈时,使用低一点的地址,如图5所示。

图5. x86的栈布局

函数调用

函数是程序中的一段代码,执行一个特定任务,并于其他代码相对独立。主代码调用函数,并在其返回到主代码前,临时将执行权交给函数。程序如何使用栈,这对一个二进制文件是贯穿始终的问题。现在,我们将关注最常见的约定,称之为cdecl。

许多函数包含一段“序言”(prologue),它是函数开始处的少数几行代码,用于保存函数中要用到的栈和寄存器。相应的,在函数结尾的“结语”(epilogue)则将栈和这些寄存器恢复至函数被调用前的状态。函数调用最常见的实现流程,如图6所示。

图6. 函数的调用流程

栈帧的布局

栈被分配为自顶向下的,高内存地址先被使用。每一次函数调用,就生成一个新的栈帧。函数维护它自己的栈帧,知道它返回,这时调用者的栈帧也被恢复,执行权也返回给调用函数。栈帧的详细分布,如图7所示。

图7. 栈帧的分布

在上图中,EPS指向栈顶。在函数指向时,EBP保持不变,这样就可以通过EBP来引用局部变量和参数。在调用前被压入栈中的参数在图中位于栈的底部。接下来,它包含了由调用指令自动压入栈的返回地址,然后是老的EBP值,也就是调用者栈帧的EBP值。

3.4 指令

指令是汇编程序的构成块。在x86汇编语言中,一条指令由一个助记符,以及零个或多个操作数组成,如表1所示。助记符表示要执行的指令,如mov表示移动数据。操作数用于说明指令要使用的信息,如寄存器或数据。

表1. 指令格式
助记符目标操作数源操作数
movecx0x42

每条指令使用操作码告诉CPU程序要执行什么样的操作,反汇编器将操作码翻译成人类易读的指令

如表2所示。

表2. 指令操作码
指令mov ecx,0x42
操作码B942 00 00 00

用0x42000000表示0x42,是因为x86架构使用小端字节序。数据的字节序(endianness)是指在一个大数据项中,最高位(大端,big-endian)还是最低位(小端,little-endian)被排在第一位。一些恶意代码在网络通信时必须改变字节序,因为网络数据使用大端字节序,而x86程序使用小端字节序。因此,在(网络的)大端字节序下,IP地址127.0.0.1会被表示位0x7F000001,而在(本地内存的)小端字节序下,表示位0x0100007F。

操作数说明指令要使用的数据,包括立即数(immediate)、寄存器(registry)、内存地址(memory address)。其中,立即数操作数是一个固定值,如表1中的0x42。寄存器操作数指向寄存器,如表1中的ecx。内存地址操作数指向感兴趣的值所在的内存地址,一般由方括号内包含值、寄存器或方程式组成,如[eax]。

简单指令

最简单的指令是mov,用于将数据从一个位置移动到另一个位置。换而言之,它是用于读写内存的指令。mov指令可以将数据移动到寄存器或内存,其格式是:mov destination, source,如表4所示。

4. mov指令示例
指令描述
mov eax, ebx将EBX寄存器中的内容复制到EAX寄存器
mov eax, 0x42将立即数0x42复制到EAX
mov eax, [0x4037C4]将内存地址0x4037C4的4个字节复制到EAX寄存器
mov eax, [ebx]将EBX寄存器指向的内存地址处4个字节复制到EAX寄存器
mov eax, [ebx+esi*4]将ebx+esi*4等式结果指向的内存地址处4个字节复制到EAX

需要注意,由方括号括起来的操作数是对内存中数据的引用。例如,[ebx]指向内存中地址为EBX处的数据。表4中最后一个例子使用一个方程式来计算内存地址,这种方法可以节省空间,因为它不需要额外的指令来计算括起来的式子。除了计算内存地址以外,不允许像这样在指令里面做一个运算。例如,mov eax, ebx+esi*4(不加方括号)就是一条非法指令。

另外一条类似于mov的指令lea,它是“load effective address(加载有效地址)”的缩写。它的格式是lea destination, source。lea指令用来将一个内存地址赋值给目的操作数。例如,lea eax, [ebx+8]就是将EXB+8的值给EAX。相反的,mov eax, [ebx+8]则加载内存中地址为EXB+8处的数据。因此,lea eax, [ebx+8]mov eax, ebx+8指令实际上是等效的,然而采用这种寻址形式的mov指令是无效的。

图4. 用EBX寄存器访问内存 

指令mov eax,[ebx+8]lea eax,[ebx+8]可转化为mov eax, 0x00B30048lea eax, 0x00B30048。mov指令将内存中0x00B30048指向的数据0x20赋值给EAX,lea指令直接将0x00B30048赋值给EAX。

lea指令并非专门用于计算计算内存地址。它还被用来计算普通的值,因为它所需的指令更少。例如,lea ebx, [eax*5+5],其中eax是一个普通的数而不是内存地址。这条指令的功能等价于而不像= (eax+1)*5,但它使用的指令更少。

算数运算

加法和减法是从目标操作数中加上或减去一个值。加法指令的格式是add destination, value,减法指令的格式是sub destination, value。sub指令会修改标志ZF和CF,当减法结果为0时,ZF会被置位。当减法结果为负时,CF会被置位。inc和dec指令将一个寄存器加一和减一。

表5. 加法和减法指示示例
指令描述
sub eax, 0x10EAX寄存器值减去0x10
add eax, ebxEBX值加入EAX并将结果保存至EAX
inc edxEDX值递增1
dec ecxECX值递减1

乘法和出发都使用一个预先规定的寄存器,因此其指令很简单,就是指令码加上寄存器要去乘或者除的值。mul指令的格式是mul value,div指令的格式是div value。mul或div指令要操作的寄存器一般会在之前许多条指令的地方被赋值。因此你需要在程序的上下文中寻找。

mul value指令总是将eax乘上value。因此,EAX寄存器必须在乘法指令出现前就赋好值。乘法的结果以64位的形式分别存储与EDX和EAX,EDX存储高32位,EAX存储低32位。

div value指令与mul类似,但运算方向正好相反。它将EDX和EAX合起来存储的64位值除以value。因此,在做除法之前,EDX和EAX这两个寄存器必须赋值好。除法的商将存储到EAX,余数则存储在EDX中。程序员可以通过一个叫做模(modulo)的运算得到除法的余数,这个运算会被编译为在div指令后取EDX寄存器的值(因为除法保留了余数)。

表6. 乘法和除法指令示例
指令描述
mul 0x50EAX值乘以0x50,并将结果存入EDX:EAX寄存器中
div 0x75将EDX:EAX值除以0x75,并将结果存入EAX,将其余数存入EDX

x86架构还使用逻辑运算符,例如OR、AND、XOR。其相应指令的用法与add和sub类似,对源操作数和目的操作数做相应的操作,并将结果保存在目的操作数中。在反汇编时,经常会看到xor指令。例如,xor eax, eax就是一种将EAX寄存器快速置0的方法。

shr和shl指令用于对寄存器做移位操作。shr指令的格式是,shr destination, count。shl指令的格式是,shl destination, count。shr和shl指令对目的操作数右移或左移,由count决定移多少位。移除目的操作数边界的位则会先移动到CF标志中。在移位时,使用0填充新的位。

循环移位指令ror和rol与移位指令类似,但移出的那一位会被填到另一端空出来的位上,即循环右移(ror)会将最低位循环移到最高位。左循环移位(rol)则相反。

表7. 常用的逻辑和移位算数运算指令
指令描述
xor eax, eax将EAX寄存器清零
or eax, 0x7575将EAX值进行与0x7575的OR操作

mov eax, 0xA

shl eax, 2

将EAX寄存器左移两位,将导致EAX=0x28

mov bl, oxA

ror bl, 2

将BL寄存器循环移位两位,将导致BL = 0x82

在分析恶意代码时,如果你遇到一个函数只有xor、or、and、shl、ror、shr、rol这样地指令,并且它们反复出现,看起来随机排列地样子,就可能是遇到一个加密或者压缩函数。大部分情况下,最好将其标记伪一个加密函数,然后进行后面地分析。

NOP指令

最后一个简单的指令nop什么事情也不做。当它出现时,直接指向下一条指令。nop指令实际上是xchg eax, eax的一个伪名字(eax与其自身的交换)。

这条指令的opcode是0x90。在缓冲区溢出攻击中,当攻击者无法完美地控制利用代码,就经常使用NOP滑板。它起到填充代码地作用,以降低shellcode可能在中间部分开始执行所造成地风险。

条件指令

所有的编程语言都能做比较,并根据比较结果做出决定。条件指令就是用来做比较的指令。常见的两个条件指令是test和cmp。

test指令与and指令的功能一样,但它并不会修改其使用的操作数。test指令只设置标志位。test指令执行之后,我们感兴趣的是ZF标志位。对某个东西与它自身的test经常被用于检查它是否是一个NULL值。

cmp指令与sub指令功能一样,但它不影响其操作数。cmp指令也是只用于设置标志位,其执行结果是,ZF和CF标志位可能发生变化。cmp指令与标志位的关系,如表8所示。

表8. cmp指令与标志位
cmp dst, srcZFCF
dst = src10
dst < src01
dst > src00

分支指令

分支指令是一串指令根据程序流有条件地执行。最常见的分支指令是跳转指令。程序中使用了大量的跳转指令,其中最简单的是jmp指令,它使得下一条要被执行的指令是其格式jmp location中指定位置的指令,又被称为无条件跳转,因为总会跳到目的位置去执行。条件跳转使用标志位来决定是跳转,还是执行下一条指令。有30多种不同类型的条件跳转,但只有少部分常见。常见的条件跳转指令,如表9所示。

表9. 条件跳转
指令描述
jz loc如果ZF=1,跳转至指定位置
jnz loc如果ZF=0,跳转至指定位置
je loc与jz类似,但通常在一条cmp指令后使用。如果源操作数与目的操作数相等则跳转。
jne loc与jnz类似,但通常在一条cmp指令后使用。如果源操作数与目的操作数不相等则跳转。
jg loc在一条cmp指令做有符号比较之后,如果目的操作数大于源操作数则跳转
jge loc在一条cmp指令做有符号比较之后,如果目的操作数大于或等于源操作数则跳转
ja loc与jg类似,但使用无符号比较
jae loc与jge类似,但使用无符号比较
jl loc在一条cmp指令做有符号比较之后,如果目的操作数小于源操作数则跳转
jle loc在一条cmp指令做有符号比较之后,如果目的操作数小于或等于源操作数则跳转
jb loc与jl类似,但使用无符号比较
jbe loc与jle类似,但使用无符号比较
jo loc如果前一条指令置位了溢出标志位(OF=1)则跳转
js loc如果符号标志位被置位(SF=1)则跳转
jecxz loc如果ECX=0则跳转

重复指令

重复指令是一组操作数据缓冲区的指令。数据缓冲区通常是一个字节数组的形式,也可以是单字或者双字。常见的数据缓冲区操作指令是movsx、cmpsx、stosx、scasx,其中x可以是b、w、d,分别表示字节、字、双字。在这些操作中,使用ESI和EDI寄存器。ESI是原索引寄存器,EDI是目的索引寄存器。还有ECX用作计数的变量。

这些指令还需要一个前缀,用于对长度超过1的数据做操作。movsb指令本身指挥移动一个字节,而不使用ECX寄存器,如表10所示。

表10. rep指令终止条件
指令描述
rep循环终止条件ECX = 0

repe, repz

循环终止条件ECX = 0 or ZF = 0
repne, repnz循环终止条件ECX = 0 or ZF = 1

在x86下,使用重复前缀来做多字节操作。rep指令会增加ESI和EDI这两个偏移,减少ECX寄存器。rep前缀会不断重复,直至ECX=0。repe/repz和repne/repnz前缀则不断重复,直至ECX=0或直至ZF=1或0。因此,大部分数据缓冲区操作指令中,ESI、EDI、ECX必须为rep指令的生效,而进行适当的初始化。

movsb指令用于将一串字节从一个位置移动到另一个位置。cmpsb指令用于比较两串字节,以确定其是否是相同的数据。scasb指令用于从一串字节中搜索一个值。stosb指令用于将值存储到EDI指向的地址,上述指令的具体描述见表11。

表11. rep指令示例
指令描述
repe cmpsb用于比较两块数据缓冲区。EDI和ESI必须被设置为两端缓冲区的地址,ECX必须被设置为缓冲区长度。当ECX=0或者发现缓冲区不一致的时候,必须停止比较。
rep stosb用于用一个给定的值初始化一块缓冲区中所有字节。EDI包含了缓冲区地址,AL则包含初始值。这个指令通常与xor eax, eax一起使用。
rep movsb一般用于赋值缓冲区中的字节。ESI需要被设为原缓冲区地址,EDI被设为目的缓冲区地址,ECX则必须为要赋值的长度。逐字节复制,直至ECX=0。
repne scasb用于在一段数据缓冲区中搜索一个字节。EDI徐志祥缓冲区地址,AL则包含要找的字节,ECX设为缓冲区长度。当ECX=0或找到该字节是,比较停止。
  • 17
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园整体解决方案是响应国家教育信息化政策,结合教育改革和技术创新的产物。该方案以物联网、大数据、人工智能和移动互联技术为基础,旨在打造一个安全、高效、互动且环保的教育环境。方案强调从数字化校园向智慧校园的转变,通过自动数据采集、智能分析和按需服务,实现校园业务的智能化管理。 方案的总体设计原则包括应用至上、分层设计和互联互通,确保系统能够满足不同用户角色的需求,并实现数据和资源的整合与共享。框架设计涵盖了校园安全、管理、教学、环境等多个方面,构建了一个全面的校园应用生态系统。这包括智慧安全系统、校园身份识别、智能排课及选课系统、智慧学习系统、精品录播教室方案等,以支持个性化学习和教学评估。 建设内容突出了智慧安全和智慧管理的重要性。智慧安全管理通过分布式录播系统和紧急预案一键启动功能,增强校园安全预警和事件响应能力。智慧管理系统则利用物联网技术,实现人员和设备的智能管理,提高校园运营效率。 智慧教学部分,方案提供了智慧学习系统和精品录播教室方案,支持专业级学习硬件和智能化网络管理,促进个性化学习和教学资源的高效利用。同时,教学质量评估中心和资源应用平台的建设,旨在提升教学评估的科学性和教育资源的共享性。 智慧环境建设则侧重于基于物联网的设备管理,通过智慧教室管理系统实现教室环境的智能控制和能效管理,打造绿色、节能的校园环境。电子班牌和校园信息发布系统的建设,将作为智慧校园的核心和入口,提供教务、一卡通、图书馆等系统的集成信息。 总体而言,智慧校园整体解决方案通过集成先进技术,不仅提升了校园的信息化水平,而且优化了教学和管理流程,为学生、教师和家长提供了更加便捷、个性化的教育体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值