1. 程式人生 > 實用技巧 >逆向入門——彙編基礎

逆向入門——彙編基礎

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不會影響標誌位。