逆向入门——汇编基础
0x00 IA32处理器体系结构
微机的基本结构

指令执行周期

当指令使用了内存操作数时还需要两个额外的步骤:取操作数和存储输出操作数。
机器指令的执行;
- 取指令
- 解码
- 执行
操作模式
保护模式:处理器的基本模式。
虚拟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
寻址方式总结
操作数寻址方式

数据寻址的基本方式:
- 立即寻址
- 寄存器寻址
- 存储器寻址
存储器寻址有六种类型
用寄存器作为指针并操纵寄存器的值。操作数使用间接寻址则叫间接操作数。


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
压栈,将操作数放入堆栈中:
- 将ESP减4
- 将要压入的32位值拷贝到ESP指向的内存。

对于32位操作数,ESP减4,存到栈中的内容为双字;对于16位操作数,ESP减2,存到栈中的内容为字
POP
POP r/m16
POP r/m32
出栈,从堆栈中取出操作数放到指令中的操作数中
- 将ESP所指向内存中的内容取出放到操作数中
- 将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
给子过程传递参数的两种基本方式
- 通过寄存器传递
- 执行效率高
- 代码可能显得混乱
- 寄存器数量有限
mov esi , offset array
mov ecx,lengthof array
mov ebx , type array
call DumpMem
- 通过堆栈传递
- 方式灵活通用
- 效率偏低
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中。
该指令一般不会跟重复前缀
串操作指令对标志位的影响
cmps和scas指令会对标志位有影响,影响效果如同CMP指令。
movs,lods,stos不会影响标志位。


浙公网安备 33010602011771号