Introduction

ASM 是 Assembly(組合語言)的縮寫。組合語言屬於低階語言,通常專為特定架構設計。相對於低階語言,高階語言更易於理解和使用。以下是兩者的比較:

層級優點缺點範例
高階語言易學易懂、除錯容易執行效率較低、佔用記憶體較多C、C++、Python
低階語言執行效率高、速度快相容性差、不易維護組合語言、機器語言

學習組合語言的原因之一是許多程式語言在執行前會先編譯成機器語言。儘管近年來的 PWN 題目通常會提供程式碼,但如果要挖掘漏洞或檢測產品韌體,仍需要進行程式碼的逆向分析。因此,我們會將編譯好的程式反組譯成組合語言,以便閱讀和理解。以下是一個 C 程式編譯過程的流程圖:

圖片1

你需要知道的 ASM

  • Registers
  • Flags
  • Sections
  • Instructions
  • Stack frame
  • Calling Convention

Registers(暫存器)

暫存器是處理運算時暫存數值的地方,具有讀寫速度快的特性。根據資料型態大小,暫存器可以分為以下四種:

  • QWORD: 64 bits, Quad Word
  • DWORD: 32 bits, Double Word
  • WORD: 16 bits
  • BYTE: 8 bits

根據功能,暫存器又可以分為以下四種:

名稱功用
通用暫存器運算、計數
區段暫存器指向記憶體區段
指標暫存器指向堆疊或陣列
旗標暫存器紀錄狀態(進位、溢位等)

通用暫存器

RegisterAX、BX、CX、DX、DI、SI、BP、SPR8 ~ R15
64 bit前綴加 R,例如:RAX、RDI前綴加 R,例如:R8、R9
32 bit前綴加 E,例如:EAX、EDI;後綴加 D,例如:R8D、R9D前綴加 E,例如:EAX、EDI;後綴加 D,例如:R8D、R9D
16 bit無變化,例如:AX、DI;後綴加 W,例如:R8W、R9W無變化,例如:AX、DI;後綴加 W,例如:R8W、R9W
8 bit後綴加 X(X 可替換為 H/L),例如:AH、BL、DIL、BPL;後綴加 B,例如:R8B、R9B後綴加 X(X 可替換為 H/L),例如:AH、BL、DIL、BPL;後綴加 B,例如:R8B、R9B

區段暫存器

名稱作用
CS指向 Code Segment
DS指向 Data Segment
SS指向 Stack Segment
ES、FS、GS指向 Data Segment(選擇性使用)

指標暫存器

名稱作用
SPStack Pointer,指向 stack 頂端
BPBase Pointer,指向 stack 的任何位置
IP指向目前執行指令的地址
SISource Index,指向資料來源
DIDestination Index,指向資料目的地

旗標暫存器

名稱作用
CFCarry Flag:最高位元有進位或借位,CF = 1;否則為 0
PFParity Flag:判斷運算後最低 8 位元的 1 的數量,奇數個 PF = 1;否則為 0
AFAuxiliary Flag:運算後第 3 位元產生進位或借位,AF = 1;否則為 0
ZFZero Flag:運算後結果為 0,則 ZF = 1;否則為 0
SFSign Flag:運算後,SF=1 表示負數;否則為 0
OFOverflow Flag:運算後結果溢位,OF=1;否則為 0
TFTrap Flag:用於 Debug,TF=1 時,每次執行一個指令
DFDirection Flag:字串運算,DF=0 從低位到高位;DF=1 反之
IFInterrupt Flag:IF=1 時,接受外部中斷;IF=0 則無法

語法格式

常見的格式有兩種:Intel 和 AT&T。如果有興趣,也可以使用 Compiler Explorer 將程式轉為 ASM 進行觀察。原則上,大家比較偏向使用 Intel 格式,因為它的語法接近正常的程式語言,且更易於閱讀。以下是兩種語法格式的比較:

Intel

1push rbp
2mov rbp, rsp
3pop rbp

AT&T

1push %rbp
2mov %rsp,%rbp
3pop %rbp

組成

名稱功用
Label標記,供指令進行跳轉
Section隔開常數變數、可變變數、程式碼區段
Instruction指令或偽指令
Operand運算子

常見指令

指令作用範例語法
push將資料放進 stackpush rbp
pop將資料從 stack 拿出pop rbp
mov將資料從 A 放入 Bmov B, A
ret回傳ret
lea將變數位址複製給暫存器lea rax, [rbp-0xc]
nop無任何操作nop

基本運算

指令作用範例語法
inc將變數加1inc rax
dec將變數減1dec rax
add將變數加指定數目add rax, 0x1
sub將變數減指定數目sub rax, 0x1
mul將變數相乘mul cx
div將變數相除div cx

位元運算

指令作用範例語法
and做 & 運算and al, 0x05
or做 | 運算or al, 0x05
xor做 ^ 運算xor al, 0x05
not做 ! 運算not al
neg計算二進位補數neg al
shl左移指定位數shl rax, 2
shr右移指定位數shr rax, 2
rol左循環位移指定位數rol rax, 2
ror右循環位移指定位數ror rax, 2

其他比較

指令作用範例語法
cmp由相減比較兩數,設定 ZF、CFcmp rax, rbx
test由 & 比較兩數,設定 SF、ZF、PFtest rax, rax
jmp跳轉到指定地址jmp 0x00001140

跳轉指令

組合語言有許多跳轉指令,統稱為 Jcc,Jump condition code,通常會搭配比較指令使用,不過因為指令有很多,所以就不一一列出介紹了,大家可以遇到的時候再上網查找

Stack Frame

簡單來說,如果一個程式會呼叫其他的函式,這些函式的資料會依次壓入堆疊,就像堆疊盤子一樣。當主函式 (main) 呼叫 func1func1 呼叫 func2,而 func2 再呼叫 func3,此時的堆疊會從上往下排列為:func3 -> func2 -> func1 -> main。每當一個函式結束時,控制權會根據堆疊中的返回地址依次傳回到前一個函式,直到返回主函式。

Calling Convention

函式呼叫不僅僅是跳轉到函式地址執行程式碼再跳回來,它還涉及參數的傳遞。不同的 Calling Convention 會規定參數如何傳遞給函式,例如,某些參數會透過暫存器傳遞,而其他參數則可能會依序壓入堆疊。這在控制執行流程時特別重要,因為了解每個暫存器存放的值以及如何操作 stack 是實現目標的關鍵。不同的指令集和編譯器可能會使用不同的 Calling Convention,因此熟悉這些規則在漏洞攻擊和逆向工程中是必須的,不過在此就不再多做說明不然讀者對於內容會相對抽象,之後講到其他章節會再說明。