逆向入门——汇编基础

0x00 IA32处理器体系结构

微机的基本结构

指令执行周期

当指令使用了内存操作数时还需要两个额外的步骤:取操作数和存储输出操作数。
机器指令的执行;

  1. 取指令
  2. 解码
  3. 执行

操作模式

保护模式:处理器的基本模式。
虚拟8086模式:多任务环境中执行是地址模式的软件。
实地址模式:用于运行那些需要直接访问系统内存和硬件设备的MS-DOS程序。
系统管理模式:实现电源管理和系统安全等功能的机制。

基本执行环境

基本寄存器

寄存器中数据在内存中存放数据遵循高高低低的原则

8个通用寄存器

EAX EBX ECX EDX
EBP ESP ESI EDI

6个段寄存器

一个处理器状态标志寄存器(EFLAGS)和一个指令指针(EIP)寄存器。

ESP:栈地址寄存器 任意时刻指向栈顶元素
EBP:扩展帧指针寄存器 指向堆栈上的函数参数和局部变量
EIP:指令指针寄存器
EIP寄存器不能作为MOV指令的⽬标操作数

EFLAGS:由控制CPU的操作或反映CPU某些运算的结果的独立二进制位构成

状态标志

Name
CF 进位标志 进位或借位时CF=1
AC 辅助进位标志 低4位进位或借位时A=1
PF 奇偶标志 偶数P=1
ZF 零标志 结果为0则Z=1
SF 符号标志 S = 符号位值(补码时0=正,1=负)
OF 溢出标志 运算结果超界时O=1
DF Direction Flag
IF Intertupt Flag
TF Trace Flag

内存管理

实地址模式

可以寻址1MB内存 0~FFFFF

20位线性地址

linear address or abssolute address is 20 bits,range from 0 to FFFFF
用段-偏移地址表示

  • CS:16位代码段
  • DS:16位数据段
  • SS:16位堆栈段
  • ES,FS,GS可指向其他数据段

保护模式

可以寻址4GB内存 0~FFFFFFFF
段寄存器指向段描述符表,操作系统使用段描述符表定位程序使用的段的位置。

0x01 汇编语言基础

补码的表示法

  • 正数的补码:与源码相同
  • 负数的补码:反码加1

寻址方式


基本元素

16进制数第一个是字母时要在前面加0

指令

一条汇编指令包括4个部分:

  • 标号(可选)
  • 助记符
  • 操作数
  • 注释(可选)

INVOKE

相当于call,调用函数或过程

伪指令

伪指令课用于定义变量、宏以及过程,可用于执行命名段以及执行其他与汇编器相关任务。
.data? :指明未初始化的数据段

NOP指令

占用一个字节的存储,什么也不做。

程序模板

TITLE Program Template
; 程序描述:
; 作者:
; 创建日期:
; 修改:
; 日期:  修改者:
INCLUDE Irvine32.inc
.data
	;(在此插入变量)
.code
main PROC
	;(在此插入可执行代码)
	exit
main ENDP
	;(在此插入其他子程序)
END main

汇编-链接-执行


定义数据

字符常量/字符串常量

  • 以单引号或双引号括起来的单个/一串字符
  • 存储为对应字符的ASCII码
后缀 含义
d 十进制
b 二进制
q 八进制
h 十六进制

数据定义语句

初始值可以用?表示不确定,可以是表达式。
可以指定多个初始值,用逗号隔开,变量名代表第一个初始值的偏移。

DUP可以为多个数据项分配存储空间。
V1 BYTE 10 dup (0)V1占用10个字节空间,初值均为0

符号常量

等号伪指令:名字=表达式

计算数组和字符串大小:
list BYTE 10, 20, 30
ListSize = ($ - list)

list word 10,20,30,40
ListSize = ($-list)/2

myString_len = ($ - myString)
EQU和TEXTEQU伪指令:

将符号名和整数表达式,文本联系起来。

name EQU expression
name EQU symbol
name EQU <text>

rowSize = 5
count TEXTEQU %(rowSize * 5)
move TEXTEQU <mov>
setupAL TEXTEQU <move al, count> 

setupAL将被汇编成mov al, 10

0x02 数据传送,寻址,算术运算

小尾(小端)顺序

intel处理器使用小端顺序存储,最低字节存储在最低地址单元
Val DWORD 12345678h

数据传送指令

操作数类型

  • 立即操作数(immediate)
  • 寄存器操作数(register)
  • 内存操作数(memory)

MOV指令
MOV destination, source

  • 两个操作数尺寸必须一致
  • 不能同时为内存操作数
  • 目的操作数不能是CS, EIP,IP
  • 立即数不能直接送至段寄存器

MOVZX
复制较小值至较大值中。
低八位原样复制,高八位补0扩展,仅适用于无符号整数。
MOVSX
低八位原样复制,高八位补F扩展,仅适用于有符号整数。
LAHF/SAHF
LAHF将标志局存起EFLAGS的低8位复制到AH寄存器,SAHF是将AH复制到EFLAGS
XCHG指令
交换两个操作数的内容。
XCHG reg, reg
XCHG reg, mem
XCHG mem, reg

算数指令

名称 作用 影响标志位
INC 加1 AF OF PF SF ZF 不影响CF
DEC 减1 AF OF PF SF ZF 不影响CF
ADD 相加 CF ZF SF OF AF PF
SUB 相减 CF ZF SF OF AF PF
NEG 取相反数 CF ZF SF OF AF PF

加减法影响标志位

INC和DEC不会影响CF标志位

NEG影响的标志位和ADD SUB一样

名称 作用
CF进位位 无符号数是无溢出
OF溢出位 有符号数有无溢出
ZF零标位 判断结果是否为0
SF符号位 结果正负
PF奇偶标志 最低有效字节内1的个数是否为偶数
AC辅助进位标志 最低有效字节的第三位向高位进位

加减法算术运算指令的操作数自身不区分有无符号数,程序通过判断不同的标志位来实现对有符号数和无符号数的处理。

和数据相关的操作符和伪指令

名称 作用
OFFSET 取偏移地址
ALIGN 设置对齐值
PTR 重载默认尺寸
TYPE 返回单个元素大小
LENGTHOF 计算数组中元素的数目
SIZEOF 返回LENGTHOF*TYPE
LABEL 插入一个标号并赋予尺寸

加逗号可以多行定义

LABEL不会分配存储空间

JMP和LOOP

JMP

无条件转移与条件转移

JMP 目的地址
功能:接着从目的地址开始执行指令

  • 目的地址一般为标号
  • 通常在当前过程内跳转

LOOP

LOOP 目的地址
功能:将ecx的值减1,接着与0比较,如果不等于0,就执行目的地址开始的指令,如果等于0 ,则不跳转,接着执行紧跟在LOOP指令后的指令

  • 通常,ecx里的值就是循环次数。但如果初值为0,因是先减1再判断是否等于0,所以,实际实际循环次数就是 1 00 00 00 00 H
  • LOOPD也使用ecx控制循环,LOOPW使用cx控制循环。
  • 实模式下,使用的是cx作为控制循环的寄存器

实例

数组求和:

INCLUDE irvine32.inc
.data 
  vb1 byte  1 , 2 , 3 
.code
  main proc
      mov esi  , offset vb1
      mov ecx , lengthof vb1
      mov al    , 0 
   L1:
      add  al   , [ esi ]
      add  esi , type vb1
      loop L1
      exit
  main endp
end main 

复制字符串:

INCLUDE irvine32.inc
.data 
  s1  byte  "source string",0
  s2  byte sizeof s1 dup(0) 
.code
  main proc
      mov esi , 0
      mov ecx , sizeof s1
   L1:
      mov al , s1[ esi ]
      mov s2[esi] , al
      inc esi  
      loop L1
      exit
   main endp
End main 

寻址方式总结

操作数寻址方式

数据寻址的基本方式:

  1. 立即寻址
  2. 寄存器寻址
  3. 存储器寻址

存储器寻址有六种类型

用寄存器作为指针并操纵寄存器的值。操作数使用间接寻址则叫间接操作数。

0x03 过程

堆栈操作

运行时栈

运行时栈是CPU直接管理的内存数组,使用到两个寄存器:SS和ESP

  • 保护模式下,SS是段选择子,应用程序不应该修改它
  • ESP是指向栈的特定位置的一个32位偏移值
  • 一般不会直接修改ESP,而是通过使用CALL,RET,PUSH,POP等指令,由这些指令间接操作ESP。
  • ESP指向最后压入到栈的数据
  • 实模式下,使用的SS和SP

PUSH
PUSH r/m16
PUSH r/m32
PUSH imm32

压栈,将操作数放入堆栈中:

  1. 将ESP减4
  2. 将要压入的32位值拷贝到ESP指向的内存。

对于32位操作数,ESP减4,存到栈中的内容为双字;对于16位操作数,ESP减2,存到栈中的内容为字
POP
POP r/m16
POP r/m32

出栈,从堆栈中取出操作数放到指令中的操作数中

  1. 将ESP所指向内存中的内容取出放到操作数中
  2. 将ESP加4

对于32位操作数,是先从栈中拷贝双字到操作数中,然后ESP加4;对于16位操作数,是先从栈中拷贝字到操作数中,然后ESP加2。

PUSHFD 把32位标志寄存器压入堆栈
POPFD 从堆栈中弹出32位值到标志寄存器中
两指令无操作数
实模式下标志寄存器是16位的,入栈出栈指令分别是PUSHF,POPF。
PUSHAD 把八个32位通用寄存器按序全部压入堆栈
POPAD是以上序反序从堆栈中依次弹出值到八个32位通用寄存器中

过程定义

PROC

main proc
...
main endp

一般过程需要返回指令ret,起始过程需要调ExitProcess

CALL和RET

call 过程名
将EIP压栈(即当前CALL指令的下一条指令的地址),然后将过程名所在地址赋给EIP(相当于跳转到过程名所在的代码处)
RET
RET指令是从栈中取出32位地址,赋给EIP。

使用寄存器传递过程参数

.data
dArray DD  1, 2 , 3
dSum  DD ?
.code
Main proc
       mov ebx , offset dArray
       mov ecx , lengthof dArray
       call SumOf
       mov dSum, eax
       exit
Main endp
SumOf proc
    push ebx
    push ecx
    mov eax , 0 
L2: add eax , [ebx]
    add ebx , 4
    loop L2
    pop ecx
    pop ebx     
    ret
SumOf endp
End main

0x04 条件处理

布尔和比较指令

名称 作用
AND
OR
XOR 异或
NOT
TEST 与,不改变目的操作数只改变标志位
BT,BTC,BTR,BTS 求补/清零/置位
尺寸相同
AND, OR,XOR总是清除溢出标志和进位标志(CF和OF)
NOT不影响任何标志位

实例

小写转大写:

同一字母的大写字母和小写字母的ASCII码的区别只在第5位不同,其他各位相同,小写字母第5位为1,大写字母第5位为0
如要把小写转大写,则可将小写的ASCII码与11011111B相与

.data
aName byte “Abraham” , 0
nameSize=($-aName)-1
.code
Main proc
     mov ecx , nameSize
     mov esi , 0
L1:AND  aName[esi] , 11011111B
     inc esi
     loop L1
Main endp
End Main
将0-9之间的整数转换为对应数字符号的ASCII码
.data
aNum byte 1,3,2,0
numSize=($-aNum)-1
.code
Main proc
     mov ecx , numSize
     mov esi , 0
L1:OR  aNum[esi] , 110000B
     inc esi
     loop L1
     exit
Main endp
End Main

CMP
功能:对两个操作数作相减运算,不修改操作数,但会影响标志位。会修改OF、SF、ZF、CF、AF、PF。

设置和清除单个CPU状态标志

条件跳转

基于特定标志位

为真时跳转 为假时跳转 相关标志位
JZ JNZ ZF
JC JNC CF
JO JNO OF
JS JNS SF
JP JNP PF

基于相等比较

助记符 描述
JE 相等跳转 同JZ
JNE 不相等跳转 同JNZ
JCXZ CX=0跳转
JECXZ ECX=0跳转

基于无符号数比较

助记符 描述
JA 大于跳转
JB 小于跳转
JAE 大于等于
JBE 小于等于
JNA 不大于
JNB 不小于
JNBE 同JA
JNAE 同JB

基于有符号数比较

助记符 描述
JG 大于跳转
JL 小于跳转
JGE 大于等于
JLE 小于等于
JNG 不大于
JNL 不小于
JNLE 同JG
JNGE 同JL

实例

将最小有符号数存到AX:

     Mov ax,v1
     Cmp ax,v2
     JL   L1
     mov ax,v2
L1:cmp ax,v3
     JL    L2
     mov ax, v3
L2:

数组的顺序查找
查找第一个非0值

INCLUDE Irvine32.inc

.data
intArray SWORD 0,0,0,0,5,20,35,-12,66,4,0
noneMsg BYTE "A non-zero value was not found", 0
.code
main PROC
	mov		ebx, OFFSET intArray
	mov		ecx, LENGTHOF intArray
L1: cmp		WORD PTR [ebx], 0
	jne		found
	add		ebx, 2
	loop	L1
	jmp		notFound
found:
	movsx	eax, WORD PTR[ebx]
	call	WriteInt
	jmp		quit
notFound:
	mov		edx, OFFSET noneMsg
	call	WriteString
quit:
	call	Crlf
	exit
main ENDP
END main

条件循环指令

指令 循环条件
LOOPZ ECX>0 && ZF=1
LOOPE ECX>0 && ZF=1
LOOPNZ ECX>0 && ZF=0
LOOPNE ECX>0 && ZF=0
LOOPE和LOOPZ不影响任何状态标志
.data 
Array        SWORD    -3,-6,-1,-10,10,30,40,5
Sentinel   SWORD    0
.code
; …
    mov esi , offset array
    mov ecx , lengthof array
L1:test word ptr [esi],8000h
     pushfd            ; pushfd不修改标志位
     add esi , type array
     popfd
     loopnz   L1       ; 注意:loopnz不修改标志位
     jnz quit
     sub  esi , type array
Quit:

0x05 整数算术指令

移位和循环移位

指令 含义
SHL 逻辑左移
SHR 逻辑右移
SAL 算术左移
SAR 算术右移
ROL 循环左移
ROR 循环右移
RCL 带进位的循环左移
RCR 带进位的循环右移
SHLD 双精度左移
SHRD 双精度右移

逻辑移位和算术移位

SHL/SAL
SHL 目的操作数, 移位位数
功能:对目的操作数执行左移操作,最低位补0,移出的最高位送入进位标志CF,原来的进位位将丢失。SHL和SAL功能完全一样。

左移的SHL和SAL是等价的。算术移位不改变符号位,逻辑移位可能改变符号位
SHR
SHR 目的操作数, 移位位数
功能:将目的操作数逻辑右移,左边空出的位添0,右边最低位被移出,复制到CF位中
SHR可以实现无符号数的快速除法

SAR
有符号数的快速除法,右移过程中最高位保持不变

ROL/ROR/RCL/RCR
移出的位又送回另一端

SHLD/SHRD

应用

BinToAsc PROC  uses eax ebx ecx  esi
 ;将EAX中的数转换成二进制ASCII码存到ESI指向的数组中
  Add esi , 31
  Mov ecx ,32
Nxt:
    Mov bl,  al
    And bl , 1
    Add bl , 30H
    Mov [esi],bl
    Shr  eax,1
    Dec esi
  Loop nxt
  Ret
BinToAsc ENDP

乘法和除法指令

助记符 描述
MUL 无符号乘法
IMUL 有符号乘法
DIV 无符号除法
IDIV 有符号除法
应用
Mov al, 30h
Mov bl, 4h
Mul  bl   ;AX =0C0H,CF=0


Mov ax , 2000h
Mov bx ,100h
Mul  bx   ;DX:AX=0020 0000h,CF=1

Mov al, -4
Mov bl, 4
IMUL  bl             ;AX=0FFF0H,CF=0

Mov ax, 30h
Mov bx, 4h
IMul bx            ;DX:AX =0C0H,CF=0

Mov al, 48
Mov bl, 4
IMUL  bl             ;AX=00C0H(即十进制的192),CF=1
任意进制的码制转换
.data 
ASCIICHAR BYTE  '0123456789ABCDEFGHIJKLMNOPQRSTUVWZYX'
.code
ToASC PROC  uses eax ebx  ecx  esi  
;将EAX中的数按BL中指定的进制数,转换成ASCII字符串放到ESI指向的数组中
   mov ecx , 0   ;
   mov cl , bl      ; movzx  ecx, bl
   add esi , 31
nxt_ta:
   mov edx , 0 
   div   ecx
   mov bl,ASCIICHAR[edx]
   mov [esi],bl
   dec esi
   cmp eax , 0
   jnz nxt_ta	; jne
   ret 	
ToASC ENDP

0x06 高级过程

stack frame

给子过程传递参数的两种基本方式

  1. 通过寄存器传递
  • 执行效率高
  • 代码可能显得混乱
  • 寄存器数量有限
mov esi , offset array
mov ecx,lengthof array
mov ebx , type array
call DumpMem
  1. 通过堆栈传递
  • 方式灵活通用
  • 效率偏低
push offset array
push lengthof array
push type array
call DumpMem2

使用堆栈传递参数时压入了两类参数:

  • 值参数(变量或常量的值)
  • 引用/指针参数(变量的地址)
实例

传递值

.data 
val1 dword 5
val2 dword 6
.code
push val2
push val1
call AddTwo

AddTwo(val1,val2)

传递引用

.data 
val1 dword 5
val2 dword 6
.code
push offset val2
push offset val1
call AddTwo

AddTwo(&val1,&val2)

重点:参数访问
.data 
Val1 dword 5
Val2 dword 6
.code
Push val2
Push val1
Call AddTwo
…
AddTwo proc
  push ebp
  Mov  ebp , esp
  mov  eax , [ebp + 12]     ;取得val2
  add  eax ,  [ebp + 8]       ;加上val1
  pop   ebp
  ret
AddTwo endp

堆栈清理

因为在调用子过程前,给堆栈压入了一些内容,在子过程返回时,必须调整堆栈指针。

  • 在调用完子过程后通过加法指令改变ESP值
  • 通过 RET imm 指令的形式
    add方法:
.data 
Val1 dword 5
Val2 dword 6
.code
Push  val2
Push  val1
Call    AddTwo
Add    esp , 8

ret方法,在子过程中调用:

.data 
Val1 dword 5
Val2 dword 6
.code
Push val2
Push val1
Call AddTwo
AddTwo proc
  push ebp
  mov ebp,esp
  mov eax,[ebp+12]
  add eax,[ebp+8]
  pop ebp
  ret  8
AddTwo endp

采用uses操作符保存寄存器,则要注意uses指令是将寄存器的压栈指令放在子过程的开始,即在堆栈帧里push ebp语句之前,这时,参数偏移地址计算将会受到影响

0x07 字符串和数组

CLD 清除方向标志
STD设置方向标志

MOVSB,MOVSW,MOVSD

指令 功能 ESI和EDI修改量
MOVSB 复制字节 1
MOVSW 复制字 2
MOVSD 复制双字 4

复制双字数组

.data 
  source dword 20 dup(0ffh)
  target  dword 20 dup(?)
.code
  ; … 
  cld
  mov ecx , lengthof source
  mov esi , offset source
  mov edi , offset target
  rep movsd    ;将source开始的20个双字复制到target中
  ; …

CMPSB,CMPSW,CMPSD

指令 操作
CMPSB 比较字节
CMPSW 比较字
CMPSD 比较双字

单个比较

.data 
  source dword 1234h
  target  dword  5678h
.code
  ; …
  mov esi , offset source
  mov edi , offset target
  cmpsd   ;比较双字
  ja L1     ;如果source>targe跳转至L1 
  jmp L2  ;如果source<=target跳转至L2,本例即是
  ; ….

字符串比较

.data
CmpsTestSource byte "ABCDE"
CmpsTestTarget  byte "AB   "
.code
CMPSTEST proc
  cld
  mov  esi , offset CmpsTestSource
  mov  edi , offset CmpsTestTarget
  mov  ecx, lengthof CmpsTestSource  ;最多比较次数,此例为5
  repe  cmpsb ; 比较到第三个字母时,因两者不等,重复不再继续,但当前串
                         ; 操作执行完,esi和edi还会增加。所以,最后,esi和edi会指向
                         ; 第四个字母的位置。
  ret
CMPSTEST endp

SCASB,SCASW,SCASD

将AL的值与EDI指向的内存内容相比较(相当于cmp AL , [edi]),即相当于是做查找操作,通常会跟重复前缀

  • 如果使用repe前缀,则将查找到EDI开始的内存中第一个不等于AL时中止重复;
  • 如果使用repne前缀,则将查找到EDI开始的内存中第一个等于AL时中止重复;
  • 当然,如果ecx减到0,也会结束查找
    SCASW是用AX作字查找,SCASD是用EAX作双字查找

扫描一个匹配字符

.data 
  alpha byte “ABCDEFGH”,0
.code
  mov edi , offset alpha
  mov al , ‘F’
  mov ecx , lengthof alpha
  cld
  repne scasb   ;不相等则重复,即找到第一个相等的
  jnz quit    ; 如果这个条件满足,表示是找完整个ecx长度,也没有找到
  dec edi   ;回减一,让edi指向找到第一个相等的位置
  …
Quit:

STOSB,STOSW,STOSD

把AL/AX/EAX的内容存储在EDI指向的内存单元中,同时EDI的值根据方向标志的值增加和减少。
Stosb是存储AL,stosw存储AX,stosd存储EAX 使用rep前缀可以对一段内存进行填充

LODSB,LODSW,LODSD

将从esi指向的内存内容取出存到累加器中,同时,修改esi的值。
lodsb是取出一个字节存到AL中,lodsw是取出一个字存到AX中,lodsd是取出一个双字存到EAX中。
该指令一般不会跟重复前缀

串操作指令对标志位的影响

cmpsscas指令会对标志位有影响,影响效果如同CMP指令。
movs,lods,stos不会影响标志位。

原文地址:https://www.cnblogs.com/twosmi1e/p/13326235.html