逆向分析之 一.常见的汇编指令
本文于2027天之前发表,文中内容可能已经过时。
主要介绍什么是逆向分析,以及逆向分析过程中常用的汇编指令。
软件逆向的定义:
软件逆向工程是通过反汇编和调试等手段,分析计算机程序的二进制可执行代码从而获得程序的算法细节和实现原理的技术。
研究对象:没有公开源代码的计算机程序,主要是已经经过编译的二进制可执行代码(如win32平台上的PE文件,包括exe,dll等文件格式)。
软件逆向的分类:
(1) 系统级逆向:大范围分析观察,整体把握
(2) 代码级逆向:程序二进制码中提取设计理念和算法
软件逆向步骤:
(1) 研究保护方法,去除保护功能:解码/反汇编(目标二进制代码)
(2) 反汇编目标软件,定位功能函数:中间语言翻译(汇编或类汇编代码)
(3) 分析汇编代码:数据流分析(各级中间语言)
(4) 修改汇编代码或还原高级源代码:其他分析和优化(高级抽象代码)
软件逆向工具:
(1) Ollydbg:动态追踪工具,插件较好较多
(2) Windbg:用户态和内核态调试工具
(3) IDA:交互式反汇编器
(4) PEID:著名的查壳工具
(5) C32Asm:反汇编程序,可直接修改软件内部代码,有十六进制编辑模式
软件逆向的主要应用:
(1) 软件破解:破解软件的版权让用户不支付授权费用就可以使用软件的全部功能。
(2) 病毒和恶意程序的分析:恶意程序的传播机制和危害并设计出解,分析病毒解决办法。
(3) 系统漏洞分析:分析漏洞原理,设计补丁程序或者编写利用程序(Exploit)
(4) 分析不公开的文件格式,协议等
(5) 分析windows或mac平台上的硬件驱动程序编写linux下的相应驱动
(6) 挖掘消费电子产品的潜能
(7) 挖掘操作系统未文档化的API,发现更多内幕
(8) 计算机犯罪取证
软件开发基础知识:
目前通用的编程语言有两种形式:汇编语言和高级语言
汇编语言的实质和机器语言是相同的,都是直接对硬件操作,只不过指令采用英文缩写的标识符,更容易识别和记忆。汇编程序通常由三部分组成:指令,伪指令,宏指令。
高级语言所编写的程序不能直接被计算机识别,必须经过转换(目标代码即机器码)才能被执行,按照转换方式可将他们分为两类:
(1) 解释类:边翻译边执行
(2) 编译类:先翻译在执行
C语言的特点:
(1) c语言允许直接访问物理地址,可以直接对硬件进行操作
(2) c语言程序代码质量高,程序执行效率高
(3) c语言使用范围大,可移植性好
常见的汇编指令:
(1)汇编大多是指汇编语言,汇编程序。把汇编语言翻译成机器语言的过程称为汇编。在汇编语言中,用符号代替机器语言的二进制码,就把机器语言变成了汇编语言。
(2)用汇编语言编写的程序,机器不能直接识别,要由一种程序将汇编语言翻译成机器语言,这种起翻译作用的程序叫汇编程序,汇编程序是系统软件中语言处理的系统软件。
(3)由于汇编更接近机器语言,能够直接对硬件进行操作,生成的程序与其他的语言相比具有更高的运行速度,占用更小的内存,因此在一些对于时效性要求很高的程序,许多大型程序的核心模块以及工业控制方面大量应用。
(4)关于汇编语言的种类,可以说有多少中不同内核的CPU,就有多少种汇编语言。
(5)不同内核的CPU,必须有对应的汇编语言编译器将汇编语言编写的程序编译成对应CPU的机器语言代码,CPU才能正确识别和执行这些代码。
(6)不同架构的CPU的汇编指令集并不相同。
(7)不同的汇编程序有不同的汇编语言规定。
(8)常用的汇编程序有ASM,MASM,TASM,OPTASM等。
寄存器:
寄存器是存储信息的单元或者说是器件,这里讨论的寄存器都是CPU中的寄存器,位于CPU内部,而内存位于CPU外部。
对于一个汇编程序员来说,CPU中主要可以使用的也就是寄存器而已,汇编程序员可以使用指令来读写CPU中的寄存器,从而实现对于CPU的控制,当然,不同的CPU,寄存器的个数和结构都是不一样的。
8086 CPU中寄存器总共为14个,且均为16位(32位和64位均已16位为基础)
即 AX BX CX DX SP BP SI DI IP FLAG CS DS SS ES共14个。这14个寄存器按照一定的方式又分为通用寄存器,控制寄存器,段寄存器。
通用寄存器:
AX,BX,CX,DX称作为数据寄存器:
AX:累加寄存器,也称为累加器
BX:基地址寄存器
CX:计数器寄存器
DX:数据寄存器
SP和BP又称作为指针寄存器:
SP:堆栈指针寄存器
BP:基指针寄存器
SI和DI又称作为变址寄存器:
SI:源变址寄存器
DI:目的变址寄存器
控制寄存器:
IP:指令指针寄存器
FLAG:标志寄存器
段寄存器:
CS:代码段寄存器
DS:数据段寄存器
SS:堆栈段寄存器
ES:附加段寄存器
堆栈:
堆栈都是一种数据项按序排序的数据结构,只能在一端(称为栈顶)对数据项进行插入和删除。主要功能是暂时存放数据和地址,通常用来保护断电和现场。
堆:队列优先,先进先出
栈:先进后出
堆栈中定义了一些操作,两个最重要的是PUSH和POP。
PUSH操作在堆栈的顶部加入一个元素,POP操作相反,在堆栈顶部移去一个元素,并将堆栈的大小减一。
机器语言:用二进制编码表示每条指令,是计算机能直接识别和执行的语言。
汇编语言:是用助记符,符号和数字等来表示指令的程序设计语言,它与机器语言指令是一一对应的。
重点掌握常用指令功能及应用:
常用传送指令
加减法指令
逻辑运算和移位指令
控制转移指令
字符(串)输入输出功能调用
(1) 数据传送指令:MOV/XCHG,PUSH/POP,LEA
(2) 算数运算类指令:ADD/ADC/INC,SUB/SBB/DEC/CMP/NEG,MUL/IMUL,DIV/IDIV
(3) 位操作类指令:AND/OR/XOR/NOT/TEST
(4) 控制转移类指令:JMP/JCC/LOOP,CALL/RET,INT n
(5) 处理机控制类指令:NOP
Inter 8086指令系统共有117条基本指令:
可分为6个功能组:
(1) 数据传送类指令
(2) 算数运算类指令
(3) 位操作类指令
(4) 串操作类指令
(5) 控制转移类指令
(6) 处理机控制类指令
1. 数据传送类指令:
数据传送是计算机中最基本,最重要的一种操作,传送指令也是最常用的一类指令,传送指令把数据从一个位置传送到另一个位置。
除标志寄存器传送指令外,均不影响标志位。
重点掌握一下指令:
MOV XCHG PUSH POP LEA
(1) 传送指令MOV:把一个字节或操作数从源地址传送至目的地址。
(2) 交换指令XCHG:把两个地方的数据进行互换。
寄存器与寄存器之间对换数据
寄存器与存储器之间对换数据
不能在存储器与存储器之间对换数据
(3) 进栈指令PUSH:
Push r16/m16/seg 操作过程:1. SP<-SP-2 2.SS:[SP]<-r16/m16
同理POP是出栈指令,操作与PUSH相反。
2. 算数运算类指令:
四则运算是计算机经常进行的一种操作,算术运算指令实现二进制(十进制)数据的四则运算。
注意算术运算类指令对标志的影响
掌握ADD/ADC/INC , SUB/SBB/DEC/NEG/CMP
熟悉 MUL/IMUL , DIV/IDIV
理解 CBW/CWD , DAA/DAS , AAA/AAS/AAM/AAD
(1) 加法指令ADD:
功能:ADD指令将源与目的操作数相加,结果送到目的操作数
ADD指令按状态标志的定义相应设置状态标志
ADD reg, imm/reg/mem reg<-reg+imm/reg/mem
(2) 带进位加法指令ADC:
ADC指令将源与目的的操作数相加,在加上进位CF标志,结果送到目的操作数
ADC指令按状态标志的定义相应设置状态标志
ADC指令主要与ADD配合,实现多精度加法运算
ADD reg , imm/reg/mem
ADC mem , imm/reg
(3) 增量指令INC:
INC指令对操作数加1(增量)
INC指令不影响进位CF标志,按定义设置其他状态标志
INC reg/mem reg/mem<-reg/mem+1
(4) 减法指令SUB:
SUB指令将目的操作数减去源操作数,结果送到目的操作数
SUB指令按照定义相应设置状态标志
SUB reg , imm/reg/mem reg<-reg-imm/reg/mem
(5) 带借位减法指令SBB:
SBB指令将目的操作数减去源操作数,在减去借位CF(进位),结果送到目的操作数。
SBB指令主要与SUB配合,实现多精度减法运算
SBB reg , imm/reg/mem reg<-reg-imm/reg/mem-CF
(6) 减量指令DEC:
DEC指令对操作数减1(减量)
DEC reg/mem reg/mem<-reg/mem-1
INC指令和DEC指令都是单操作数指令,主要用于对计数器和地址指针的调整。
(7) 求补指令NEG:
NEG指令对操作数执行求补运算:用0减去操作数,然后结果返回操作数,求补运算也可以表达成,将操作数按位取反后加1
NEG指令对标志的影响与用0作减法的SUB指令一样
NEG reg/mem reg/mem<-0-reg/mem
(8) 比较指令CMP:
CMP指令将目的操作数减去源操作数,按照定义相应设置状态标志
CMP指令执行的功能与SUB指令类似,但结果不回送目的操作数。
CMP reg , imm/reg/mem reg—imm/reg/mem
3. 位操作指令:
位操作指令以二进制为基本单位进行数据的操作,这是一类常用的指令。
(1)逻辑运算指令:ADD(与) OR(或) XOR(异或) NOT(非) TEST(测试)
(2)移位指令:SHL(逻辑左移) SHR(逻辑右移) SAL(算术左移) SAR(算术右移)
(3)循环移位指令:ROL(左循环移位) ROR(右循环移位) RCL(带进位左循环移位) RCR(带进位右循环移位)
1)逻辑运算指令ADD:对两个操作数执行逻辑与运算,结果送到目的操作数。ADD des , src des<-des^src
2)逻辑运算指令OR:对两个操作数执行逻辑或运算,结果送到目的操作数,OR dest , src dest<-destv src
3)逻辑运算指令XOR:对两个操作数执行逻辑异或运算,结果送到目的操作数。XOR dest , src
4)逻辑运算指令NOT:对一个操作数执行逻辑非运算。NOT reg/mem,按位取反,原来的0的位变1,原来的1的位变0
4. 控制转移类指令:
用于实现分支,循环,过程等程序结构,是仅次于传送指令的常用指令。
重点掌握:JMP/JCC/LOOP , CALL/RET
INT n/IRET 常用系统功能调用
一般了解:LOOPZ/LOOPNZ INTO
(1) 无条件转移指令JMP:
JMP label;程序转向label标号指定的地址(标号要在程序其他位置标出)
只要执行无条件转移指令JMP,不需要任何条件,就使程序转到指定的目的地址处,从目标地址开始执行指令。
操作数是要转移到的目标地址。
转移原理:1.程序的执行地址,是由段寄存器CS和指令指针IP共同确定的,即当前指令的地址为CS:IP。2.程序的跳转是通过修改CS和IP的值来实现的。
(2) 条件转移指令JCC:
Jcc label ; 条件满足,发生转移:IP<-IP+8位位移量;条件不满足,顺序执行。
指定的条件cc如果成立,程序转移到由标号label指定的目标地址去执行指令,条件不成立,则程序将顺序执行下一条指令。
操作数label是短转移指令,要跳转的地址必须距当前IP地址-128~+127个单元的范围之内
Jcc指令不影响标志,但要利用标志,根据利用的标志位不同,16条指令分为3种情况:
1) 判断单个标志位状态
2) 比较无符号数高低
3) 比较有符号数大小
(3) 循环指令LOOP:
循环指令是一种特殊的转移指令,当满足某条件时,反复执行一系列操作,知道不满足为止。
格式: LOOP label
循环指令利用CX寄存器作为计数器
(4) 子程序指令:
子程序是完成特定功能的一段程序
当主程序(调用程序)需要执行这个功能时,采用CALL调用指令转移到该子程序的起始处执行
当运行完子程序功能后,采用RET返回指令回到主程序继续执行
子程序通常是与主程序分开完成特定功能的一段程序,程序中有时要反复的实现相同的功能只不过参数不同而已,把仅参数不同功能重复的程序编写成为子程序,执行这个功能时,就可以调用该子程序,执行完成后在返回主程序。
CALL指令分成4中类型:
1) CALL label; 段内调用,相对寻址。
2) CALL r16/m16;段内调用,间接寻址
3) CALL far ptr label;段间调用,直接寻址
4) CALL far ptr mem;段间调用,间接寻址
(5) 中断指令:
中断是一种改变程序执行顺序的方法,在程序运行时,遇到某些需要紧急处理的情况,如停电,数据的实时接收,溢出等,处理器暂停主程序的执行,转去执行中断处理程序。
中断分类:内部中断,外部中断
中断指令:INT
INT i8
IRET:中断返回指令,实现中断返回
INTO:溢出中断指令
5. 处理机控制类指令:
对CPU状态进行控制的指令。
NOP CS:SS:DS:ES
LOCK HLT ESC WAIT
(1) 空操作指令NOP:不执行任何操作,但占用一个字节存储单元,空耗一个指令执行周期。
(2) NOP常用于程序调试,在需要预留指令空间时用NOP填充,代码空间多余时也可以用NOP填充,还可以用NOP实现软件延时。
(3) 事实上,NOP和XCHG,AX,的指令代码一样都是90H
(4) 段超越前缀指令:在允许段超越的存储器操作数之前,使用段超越前缀指令,将采用指定的段寄存器寻址操作数。
CS:使用代码段的数据
SS:使用堆栈段的数据
DS:使用数据段的数据
ES:使用附加段的数据
6. 伪指令
没有对应的机器码的指令,最终不被CPU所执行。伪指令是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作。
segment和ends是一对成对使用的伪指令,这是在写可被编译器编译的汇编程序时,必须要用到的一对伪指令。
segment和ends的功能是定义一个段,segment说明一个段开始,ends说明一个段结束。
一个段必须有一个名称来标识,使用格式为:
段名 segment
段名 ends
一个汇编程序是由多个段组成的,这些段被用来存放代码,数据或当作栈空间来使用。
一个有意义的汇编程序中至少要有一个段,这个段用来存放代码。
end是一个汇编程序的结束标记,编译器在编译汇编程序的过程中,如果碰到了伪指令end,就结束对源程序的编译。
不要搞混end和ends