逆向入門——彙編基礎
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
不會影響標誌位。