第一部分:基礎知識
彙編語言是一切程序的起點和終點,畢竟所有的高級語言都是建立在彙編基礎之上的。在許多高級語言中我們都需要相對明確的語法,但是在彙編中,我們會使用一些單詞縮寫和數字來表達程序。
I. 單元、位和字節
- ·BIT(位) - 電腦數據量的最小單元,可以是0或者1。
- 例:00000001 = 1;00000010 = 2;00000011 = 3
- ·BYTE(字節) - 一個字節包含8個位,所以一個字節最大值是255(0-255)。爲了方便閱讀,我們通常使用16進制來表示。
- ·WORD(字) - 一個字由兩個字節組成,共有16位。一個字的最大值是0FFFFh (或者是 65535d) (h代表16進制,d代表10進制)。
- ·DOUBLE WORD(雙字DWORD) - 一個雙字包含兩個字,共有32位。最大值爲0FFFFFFFF (或者是 4294967295d)。
- ·KILOBYTE(千字) - 千字節並不是1000個字節,而是1024 (32*32) 個字節。
- ·MEGABYTE - 兆字節同樣也不是一兆個字節,而是1024*1024=1,048,576 個字節
II. 寄存器
寄存器是計算機儲存數據的“特別地方”。你可以把寄存器看作一個小盒子,我們可以在裏面放很多東西:比如名字、數字、一段話……
如今Win+Intel CPU組成的計算機通常有9個32位寄存器 (w/o 標誌寄存器)。它們是:
- EAX: 累加器
- EBX: 基址寄存器
- ECX: 計數器
- EDX: 數據寄存器
- ESI: 源變址寄存器
- EDI: 目的變址寄存器
- EBP: 擴展基址指針寄存器
- ESP: 棧指針寄存器
- EIP: 指令指針寄存器
通常來說寄存器大小都是32位 (四個字節) 。它們可以儲存值爲從0-FFFFFFFF (無符號)的數據。起初大部分寄存器的名字都暗示了它們的功能,比如ECX=計數,但是現在你可以使用任意寄存器進行計數 (只有在一些自定義的部分,計數才必須用到ECX)。當我用到EAX、EBX、ECX、EDX、ESI和EDI這些寄存器時我纔會詳細解釋其功能,所以我們先講EBP、ESP、EIP。
- EBP: EBP在棧中運用最廣,剛開始沒有什麼需要特別注意的 ;)
- ESP: ESP指向棧區域的棧頂位置。棧是一個存放即將會被用到的數據的地方,你可以去搜索一下push/pop 指令瞭解更多棧知識。
- EIP: EIP指向下一個將會被執行的指令。
還有一件值得注意的事:有一些寄存器是16位甚至8位的,它們是不能直接尋址的。
通用寄存器包括:
8個32位:EAX ECX EDX EBX ESI EDI EBP ESP
8個16位:AX CX DX BX SI DI BP SP
8個8位: AH AL CH CL DH DL BH HL
16位段寄存器:CS DS ES GS SS
由CPU 控制改變的 32位EIP / 16位 IP
32位EFLAGS / 16位FLAGS
比如,EAX是這個32位寄存器的名字,EAX的低16位部分被稱作AX,AX又分爲高8位的AH和低8位的AL兩個獨立寄存器。
注意:即使不怎麼重要,你至少也要知道以下的寄存器
這些寄存器可以幫助我們區分大小:
i. 單字節(8位)寄存器: 顧名思義,這些寄存器都是一個字節 (8位) :
- AL and AH
- BL and BH
- CL and CH
- DL and DH
ii. 單字(16位)寄存器: 這些寄存器大小爲一個字 (=2 字節 = 16 位)。一個單字寄存器包含兩個單字節寄存器。我們通常根據它們的功能來區分它們。
1. 通用寄存器:
- AX-> 'accumulator'(累加器):用於進行數學運算。 (單字=16位) = AH + AL -> 其中‘+’號並不代表把它們代數相加。AH和AL寄存器是相互獨立的,只不過都是AX寄存器的一部分,所以你改變AH或AL (或者都改變) ,AX寄存器也會被改變。
- BX -> 'base'(基址寄存器):用來連接棧(之後會說明)
- CX -> 'counter'(計數器):
- DX -> 'data'(數據寄存器):大多數情況下用來存放數據
- DI -> 'destination index'(目的變址寄存器): 例如將一個字符串拷貝到DI
- SI -> 'source index'(源變址寄存器): 例如將一個字符串從SI拷貝
2. 索引寄存器(指針寄存器):
- BP -> 'base pointer'(基址指針寄存器):表示棧區域的基地址
- SP -> 'stack pointer'(棧指針寄存器):表示棧區域的棧頂地址
3. 段寄存器:
- CS -> 'code segment'(代碼段寄存器):用於存放應用程序代碼所在段的段基址(之後會說明)
- DS -> 'data segment'(數據段寄存器):用於存放數據段的段基址(以後會說明)
- ES -> 'extra segment'(附加段寄存器):用於存放程序使用的附加數據段的基地址
- SS -> 'stack segment'(棧段寄存器):用於存放棧段的段基址(以後會說明)
4. 指令指針寄存器:
IP -> 'instruction pointer'(指令指針寄存器):指向下一個指令 ;)
iii. 雙字(32位)寄存器:
2 字= 4 字節= 32 位, EAX、EBX、ECX、EDX、EDI……
如果16位寄存器前面加了‘E’,就代表它們是32位寄存器。例如,AX=16位,EAX=32位。
III. 標誌寄存器
標誌寄存器代表某種狀態。在32位CPU中有32個不同的標誌寄存器,不過不用擔心,我們只關心其中的3個:ZF、OF、CF。在逆向工程中,你瞭解了標誌寄存器就能知道程序在這一步是否會跳轉,標誌寄存器就是一個標誌,只能是0或者1,它們決定了是否要執行某個指令。
Z-Flag(零標誌):
ZF是破解中用得最多的寄存器(通常情況下佔了90%),它可以設成0或者1。若上一個運算結果爲0,則其值爲1,否則其值爲0。(你可能會問爲什麼‘CMP’可以操作ZF寄存器,這是因爲該指令在做比較操作(等於、不等於),那什麼時候結果是0什麼時候是1呢?待會再說)
The O-Flag(溢出標誌):
OF寄存器在逆向工程中大概佔了4%,當上一步操作改變了某寄存器的最高有效位時,OF寄存器會被設置成1。例如:EAX的值爲7FFFFFFFF,如果你此時再給EAX加1,OF寄存器就會被設置成1,因爲此時EAX寄存器的最高有效位改變了(你可以使用電腦自帶計算器將這個16進制轉化成2進制看看)。還有當上一步操作產生溢出時(即算術運算超出了有符號數的表示範圍),OF寄存器也會被設置成1。
The C-Flag(進位標誌):
進位寄存器的使用大概佔了1%,如果產生了溢出,就會被設置成1。例,假如某寄存器值爲FFFFFFFF,再加上1就會產生溢出,你可以用電腦自帶的計算器嘗試。
IV. 段偏移
內存中的一個段儲存了指令(CS)、數據(DS)、堆棧(SS)或者其他段(ES)。每個段都有一個偏移量,在32位應用程序下,這些偏移量由 00000000 到 FFFFFFFF。段和偏移量的標準形式如下:
段:偏移量 = 把它們放在一起就是內存中一個具體的地址。
可以這樣看:
一個段是一本書的某一頁:偏移量是一頁的某一行
V. 棧
棧是內存裏可以存放稍後會用到的東西的地方。可以把它看作一個箱子裏的一摞書,最後一本放進去的永遠是最先出來的。或者把棧看作一個放紙的盒子,盒子是棧,而每一張紙就代表了一個內存地址。總之記住這個規則:最後放的紙最先被拿出來。’push’命令就是向棧中壓入數據,‘pop’命令就是從棧中取出最後放入的數據並且把它存進具體的寄存器中。
VI. 指令 (字母表排序)
請注意,所有的值通常是以16進制形式儲存的。
大部分指令有兩個操作符 (例如:add EAX, EBX),有些是一個操作符 (例如:not EAX),還有一些是三個操作符 (例如:IMUL EAX、EDX、64)。如果你使用 “DWORD PTR [XXX]”就表示使用了內存中偏移量爲[XXX]的的數據。注意:字節在內存中儲存方式是倒過來的(Win+Intel的電腦上大部分採用”小端法”, WORD PTR [XXX](雙字節)和 BYTE PTR [XXX](單字節)也都遵循這一規定)。
大部分有兩個操作符的指令都是以下這些形式(以add指令舉例):
add eax,ebx ;; 寄存器, 寄存器
add eax,123 ;; 寄存器, 數值
add eax,dword ptr [404000] ;; 寄存器, Dword 指針 [數值]
add eax,dword ptr [eax] ;; 寄存器, Dword 指針 [寄存器值]
add eax,dword ptr [eax+00404000] ;; 寄存器, Dword 指針 [寄存器值+數值]
add dword ptr [404000],eax ;; Dword 指針[數值], 寄存器
add dword ptr [404000],123 ;; Dword 指針[數值], 數值
add dword ptr [eax],eax ;; Dword 指針[寄存器值], 寄存器
add dword ptr [eax],123 ;; Dword 指針[寄存器值], 數值
add dword ptr [eax+404000],eax ;; Dword 指針[寄存器值+數值], 寄存器
add dword ptr [eax+404000],123 ;; Dword 指針[寄存器值+數值], 數值
ADD (加)
語法: ADD 被加數, 加數
加法指令將一個數值加在一個寄存器上或者一個內存地址上。
add eax,123 = eax=eax+123;
加法指令對ZF、OF、CF都會有影響。
AND (邏輯與)
語法: AND 目標數, 原數
AND運算對兩個數進行邏輯與運算。
AND指令會清空OF,CF標記,設置ZF標記。
爲了更好地理解AND,這裏有兩個二進制數:
1001010110
0101001101
如果對它們進行AND運算,結果是0001000100
即同真爲真(1),否則爲假(0),你可以用計算器驗證。
CALL (調用)
語法:CALL something
CALL指令將當前的相對地址(IP)壓入棧中,並且調用CALL 後的子程序
CALL 可以這樣使用:
CALL 404000 ;; 最常見: CALL 地址
CALL EAX ;; CALL 寄存器 - 如果寄存器存的值爲404000,那就等同於第一種情況
CALL DWORD PTR [EAX] ;; CALL [EAX]偏移量所指向的地址
CALL DWORD PTR [EAX+5] ;; CALL [EAX+5]偏移量所指向的地址
CDQ
Syntax: CDQ
CDQ指令第一次出現時通常不好理解。它通常出現在除法前面,作用是將EDX的所有位變成EAX最高位的值,
比如當EAX>=80000000h時,其二進制最高位爲1,則EDX被32位全賦值爲1,即FFFFFFFF
若EAX<80000000,則其二進制最高位爲0,EDX爲00000000。
然後將EDX:EAX組成新數(64位):FFFFFFFF 80000000
CMP (比較)
語法: CMP 目標數, 原數
CMP指令比較兩個值並且標記CF、OF、ZF:
CMP EAX, EBX ;; 比較eax和ebx是否相等,如果相等就設置ZF爲1
CMP EAX,[404000] ;; 比較eax和偏移量爲[404000]的值是否相等
CMP [404000],EAX ;; 比較[404000]是否與eax相等
DEC (自減)
語法: DEC something
dec用來自減1,相當於c中的–
dec可以有以下使用方式:
dec eax ;; eax自減1
dec [eax] ;; 偏移量爲eax的值自減1
dec [401000] ;; 偏移量爲401000的值自減1
dec [eax+401000] ;; 偏移量爲eax+401000的值自減1
dec指令可以標記ZF、OF
DIV (除)
語法: DIV 除數
DIV指令用來將EAX除以除數(無符號除法),被除數通常是EAX,結果也儲存在EAX中,而被除數對除數取的模存在除數中。
例:
mov eax,64 ;; EAX = 64h = 100
mov ecx,9 ;; ECX = 9
div ecx ;; EAX除以ECX
在除法之後 EAX = 100/9 = 0B(十進制:11) 並且 ECX = 100 MOD 9 = 1
div指令可以標記CF、OF、ZF
IDIV (整除)
語法: IDIV 除數
IDIV執行方式同div一樣,不過IDIV是有符號的除法
idiv指令可以標記CF、OC、ZF
IMUL (整乘)
語法:IMUL 數值
IMUL 目標寄存器、數值、數值
IMUL 目標寄存器、數值
IMUL指令可以把讓EAX乘上一個數(INUL 數值)或者讓兩個數值相乘並把乘積放在目標寄存器中(IMUL 目標寄存器, 數值,數值)或者將目標寄存器乘上某數值(IMUL 目標寄存器, 數值)
如果乘積太大目標寄存器裝不下,那OF、CF都會被標記,ZF也會被標記
INC (自加)
語法: INC something
INC同DEC相反,它是將值加1
INC指令可以標記ZF、OF
INT
語法: int 目標數
INT 的目標數必須是產生一個整數(例如:int 21h),類似於call調用函數,INT指令是調用程序對硬件控制,不同的值對應着不同的功能。
具體參照硬件說明書。
JUMPS
這些都是最重要的跳轉指令和觸發條件(重要用*標記,最重要用**標記):
指令 條件 條件
JA* - 如果大於就跳轉(無符號) - CF=0 and ZF=0
JAE - 如果大於或等於就跳轉(無符號)- CF=0
JB* - 如果小於就跳轉(無符號) - CF=1
JBE - 如果小於或等於就跳轉(無符號)- CF=1 or ZF=1
JC - 如果CF被標記就了跳轉 - CF=1
JCXZ - 如果CX等於0就跳轉 - CX=0
JE** - 如果相等就跳轉 - ZF=1
JECXZ - 如果ECX等於0就跳轉 - ECX=0
JG* - 如果大於就跳轉(有符號) - ZF=0 and SF=OF (SF = Sign Flag)
JGE* - 如果大於或等於就跳轉(有符號) - SF=OF
JL* - 如果小於就跳轉(有符號) - SF != OF (!= is not)
JLE* - 如果小於或等於就跳轉(有符號 - ZF=1 and OF != OF
JMP** - 跳轉 - 強制跳轉
JNA - 如果不大於就跳轉(無符號) - CF=1 or ZF=1
JNAE - 如果不大於等於就跳轉(無符號) - CF=1
JNB - 如果不小於就跳轉(無符號) - CF=0
JNBE - 如果不小於等於就跳轉(無符號) - CF=0 and ZF=0
JNC - 如果CF未被標記就跳轉 - CF=0
JNE** - 如果不等於就跳轉 - ZF=0
JNG - 如果不大於就跳轉(有符號) - ZF=1 or SF!=OF
JNGE - 如果不大於等於就跳轉(有符號) - SF!=OF
JNL - 如果不小於就跳轉(有符號) - SF=OF
JNLE - 如果不小於等於就跳轉(有符號) - ZF=0 and SF=OF
JNO - 如果OF未被標記就跳轉 - OF=0
JNP - 如果PF未被標記就跳轉 - PF=0
JNS - 如果SF未被標記就跳轉 - SF=0
JNZ - 如果不等於0就跳轉 - ZF=0
JO - 如果OF被標記就跳轉 - OF=1
JP - 如果PF被標記就跳轉 - PF=1
JPE - 如果是偶數就跳轉 - PF=1
JPO - 如果是奇數就跳轉 - PF=0
JS - 如果SF被標記就跳轉 - SF=1
JZ - 如果等於0就跳轉 - ZF=1
LEA (有效地址傳送)
語法:LEA 目的數、源數
LEA可以看成和MOV差不多的指令LEA ,它本身的功能並沒有被太廣泛的使用,反而廣泛運用在快速乘法中:
lea eax,dword ptr [4*ecx+ebx]
將eax賦值爲 4*ecx+ebx
MOV (傳送)
語法: MOV 目的數,源數
這是一個很簡單的指令,MOV指令將源數賦值給目的數,並且源數值保持不變
這裏有一些MOV的變形:
MOVS/MOVSB/MOVSW/MOVSD EDI, ESI:這些變形能將ESI指向的內容傳送到EDI指向的內容中去
MOVSX:MOVSX指令將單字或者單字節擴展爲雙字或者雙字節傳送,原符號不變
MOVZX:MOVZX擴展單字節或單字爲雙字節或雙字並且用0填充剩餘部分(通俗來說就是將源數取出置於目的數,其他位用0填充)
MUL (乘法)
語法:MUL 數值
這個指令同IMUL一樣,不過MUL可以乘無符號數。
NOP (無操作)
語法:NOP
這個指令說明不做任何事
所以它在逆向中運用範圍最廣
OR (邏輯或)
語法:OR 目的數,源數
OR指令對兩個值進行邏輯或運算
這個指令會清空OF、CF標記,設置ZF標記
爲了更好的理解OR,思考下面二進制串:
1001010110
0101001101
如果對它們進行邏輯與運算,結果將是1101011111。
只有當兩邊同爲0時其結果爲0,否則就爲1。你可以用計算器嘗試計算。希望你能理解爲什麼,最好自己動手算一算
POP
語法:POP 目的地址
POP指令將棧頂第一個字傳送到目的地址。 每次POP後,ESP(棧指針寄存器)都會增加以指向新棧頂
PUSH
語法:PUSH 值
PUSH是POP的相反操作,它將一個值壓入棧並且減小棧頂指針值以指向新棧頂。
REP/REPE/REPZ/REPNE/REPNZ
語法: REP/REPE/REPZ/REPNE/REPNZ ins
重複上面的指令:直到CX=0。ins必須是一個操作符,比如CMPS、INS、LODS、MOVS、OUTS、SCAS 或 STOS
RET (返回)
語法:RET
RET digit
RET指令的功能是從一個代碼區域中退出到調用CALL的指令處。
RET digit在返回前會清理棧
SUB (減)
語法:SUB 目的數,源數
SUB與ADD相反,它將源數減去目的數,並將結果儲存在目的數中
SUB可以標記ZF、OF、CF
TEST
語法:TEST 操作符、操作符
這個指令99%都是用於”TEST EAX, EAX”,它執行與AND相同的功能,但是並不儲存數據。如果EAX=0就會標記ZF,如果EAX不是0,就會清空ZF
XOR
語法:XOR 目的數,源數
XOR指令對兩個數進行異或操作
這個指令清空OF、CF,但會標記ZF
爲了更好的理解,思考下面的二進制串:
1001010110
0101001101
如果異或它們,結果將是1100011011
如果兩個值相等,則結果爲0,否則爲1,你可以使用計算器驗算。
很多情況下我們會使用”XOR EAX, EAX”,這個操作是將EAX賦值爲0,因爲當一個值異或其自身,就過都是0。你最好自己動手嘗試下,這樣可以幫助你理解得更好。
VII. 邏輯操作符
下面都是通常的邏輯操作符:
好了,彙編基礎已經梳理完畢!