Win32彙編 - 陣列與標誌位測試
整理複習組合語言的知識點,以前在學習《Intel組合語言程式設計 - 第五版》時沒有很認真的整理筆記,主要因為當時是以學習理解為目的沒有整理的很詳細,這次是我第三次閱讀此書,每一次閱讀都會有新的收穫,這次複習,我想把書中的重點,再一次做一個歸納與總結(注:16位彙編部分跳過),並且繼續嘗試寫一些有趣的案例,這些案例中所涉及的指令都是逆向中的重點,一些不重要的我就直接省略了,一來提高自己,二來分享知識,轉載請加出處,敲程式碼備註挺難受的。
陣列取值操作符: 陣列取值操作符是對陣列操作之前必須要掌握的,以下命令主要實現對陣列元素的統計,取偏移值等,後期陣列定址會用到.
.386p .model flat,stdcall option casemap:none include windows.inc include kernel32.inc includelib kernel32.lib .data WordVar1 WORD 1234h DwordVar2 DWORD 12345678h ArrayBT BYTE 1,2,3,4,5,6,7,8,9,0h ArrayDW DWORD 1000,2000,3000,4000,5000,6000,7000,8000,9000,0h ArrayTP DWORD 30 DUP(?) .code main PROC ; 使用 OFFSET 可返回資料標號的偏移地址,單位是位元組. ; 偏移地址代表標號距DS資料段基址的距離. xor eax,eax mov eax,offset WordVar1 mov eax,offset DwordVar2 ; 使用 PTR 可指定預設取出引數的大小(DWORD/WORD/BYTE) mov eax,dword ptr ds:[DwordVar2] ; eax = 12345678h xor eax,eax mov ax,word ptr ds:[DwordVar2] ; ax = 5678h mov ax,word ptr ds:[DwordVar2 + 2] ; ax = 1234h ; 使用 LENGTHOF 可以計算陣列元素的數量 xor eax,eax mov eax,lengthof ArrayDW ; eax = 10 mov eax,lengthof ArrayBT ; eax = 10 ; 使用 TYPE 可返回按照位元組計算的單個元素的大小. xor eax,eax mov eax,TYPE WordVar1 ; eax = 2 mov eax,TYPE DwordVar2 ; eax = 4 mov eax,TYPE ArrayDW ; eax = 4 ; 使用 SIZEOF 返回等於LENGTHOF(總元素數)和TYPE(每個元素佔用位元組)返回值的乘基. xor eax,eax mov eax,sizeof ArrayBT ; eax = 10 mov eax,sizeof ArrayTP ; eax = 120 invoke ExitProcess,0 main ENDP END main
陣列直接定址: 在宣告變數名稱的後面加上偏移地址即可實現直接定址,直接定址中可以通過立即數定址,也可以通過暫存器相加的方式定址,如果遇到雙字等還可以使用基址變址定址,這些定址都屬於直接定址.
.386p .model flat,stdcall option casemap:none include windows.inc include kernel32.inc includelib kernel32.lib .data ArrayB BYTE 10h,20h,30h,40h,50h ArrayW WORD 100h,200h,300h,400h ArrayDW DWORD 1h,2h,3h,4h,5h,6h,7h,8h,9h .code main PROC ; 針對位元組的定址操作 mov al,[ArrayB] ; al=10 mov al,[ArrayB+1] ; al=20 mov al,[ArrayB+2] ; al=30 ; 針對記憶體單元字儲存操作 mov bx,[ArrayW] ; bx=100 mov bx,[ArrayW+2] ; bx=200 mov bx,[ArrayW+4] ; bx=300 ; 針對記憶體單元雙字儲存操作 mov eax,[ArrayDW] ; eax=00000001 mov eax,[ArrayDW+4] ; eax=00000002 mov eax,[ArrayDW+8] ; eax=00000003 ; 基址加偏移定址: 通過迴圈eax的值進行定址,每次eax遞增2 mov esi,offset ArrayW mov eax,0 mov ecx,lengthof ArrayW s1: mov dx,word ptr ds:[esi + eax] add eax,2 loop s1 ; 基址變址定址: 迴圈取出陣列中的元素 mov esi,offset ArrayDW ; 陣列基址 mov eax,0 ; 定義為元素下標 mov ecx,lengthof ArrayDW ; 迴圈次數 s2: mov edi,dword ptr ds:[esi + eax * 4] ; 取出數值放入edi inc eax ; 陣列遞增 loop s2 invoke ExitProcess,0 main ENDP END main
陣列間接定址: 陣列中沒有固定的編號,處理此類陣列唯一可行的方法是用暫存器作為指標並操作暫存器的值,這種方法稱為間接定址,間接定址通常可通過ESI實現記憶體定址,也可通過ESP實現對堆疊的定址操作.
.386p .model flat,stdcall option casemap:none include windows.inc include kernel32.inc includelib kernel32.lib .data ArrayDW DWORD 1h,2h,3h,4h,5h,6h,7h,8h,9h .code main PROC ; 第一種: 通過使用ESI暫存器實現定址. mov esi,offset ArrayDW ; 取出陣列基地址 mov ecx,lengthof ArrayDW ; 取出陣列元素個數 s1: mov eax,dword ptr ds:[esi] ; 間接定址 add esi,4 ; 每次遞增4 loop s1 ; 第二種: 通過ESP堆疊暫存器,實現定址. mov eax,100 ; eax=1 mov ebx,200 ; ebx=2 mov ecx,300 ; ecx=3 push eax ; push 1 push ebx ; push 2 push ecx ; push 3 mov edx,[esp + 8] ; EDX = [ESP+8] = 1 mov edx,[esp + 4] ; EDX = [ESP+4] = 2 mov edx,[esp] ; EDX = [ESP] = 3 ; 第三種(高階版): 通過ESP堆疊暫存器,實現定址. push ebp mov ebp,esp ; 儲存棧地址 lea eax,dword ptr ds:[ArrayDW] ; 獲取到ArrayDW基地址 ; -> 先將資料壓棧 mov ecx,9 ; 迴圈9次 s2: push dword ptr ss:[eax] ; 將資料壓入堆疊 add eax,4 ; 每次遞增4位元組 loop s2 ; -> 在堆疊中取資料 mov eax,32 ; 此處是 4*9=36 36 - 4 = 32 mov ecx,9 ; 迴圈9次 s3: mov edx,dword ptr ss:[esp + eax] ; 尋找棧中元素 sub eax,4 ; 每次遞減4位元組 loop s3 add esp,36 ; 用完之後修正堆疊 pop ebp ; 恢復ebp invoke ExitProcess,0 main ENDP END main
比例因子定址: 通過使用比例因子,以下例子每個DWORD=4
位元組,且總元素下標=0-3
,得出比例因子3* type arrayDW
.
.386p
.model flat,stdcall
option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
ArrayW WORD 1h,2h,3h,4h,5h
ArrayDW DWORD 1h,2h,3h,4h,5h,6h,7h,8h,9h
TwoArray DWORD 10h,20h,30h,40h,50h
RowSize = ($ - TwoArray) ; 每行所佔空間 20 位元組
DWORD 60h,70h,80h,90h,0ah
DWORD 0bh,0ch,0dh,0eh,0fh
.code
main PROC
; 第一種比例因子定址
mov esi,0 ; 初始化因子
mov ecx,9 ; 設定迴圈次數
s1:
mov eax,ArrayDW[esi * 4] ; 通過因子定址,4 = DWORD
add esi,1 ; 遞增因子
loop s1
; 第二種比例因子定址
mov esi,0
lea edi,word ptr ds:[ArrayW]
mov ecx,5
s2:
mov ax,word ptr ds:[edi + esi * type ArrayW]
inc esi
loop s2
; 第三種二維陣列定址
row_index = 1
column_index = 2
mov ebx,offset TwoArray ; 陣列首地址
add ebx,RowSize * row_index ; 控制定址行
mov esi,column_index ; 控制行中第幾個
mov eax, dword ptr ds:[ebx + esi * TYPE TwoArray]
invoke ExitProcess,0
main ENDP
END main
通過比例因子可以模擬實現二維陣列定址操作,過程也很簡單.
.386p
.model flat,stdcall
option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
TwoArray DWORD 10h,20h,30h,40h,50h
RowSize = ($ - TwoArray) ; 每行所佔空間 20 位元組
DWORD 60h,70h,80h,90h,0ah
DWORD 0bh,0ch,0dh,0eh,0fh
.code
main PROC
lea esi,dword ptr ds:[TwoArray] ; 取基地址
mov eax,0 ; 控制外層迴圈變數
mov ecx,3 ; 外層迴圈次數
s1:
push ecx ; 儲存外迴圈次數
push eax
mov ecx,5 ; 內層迴圈數
s2: add eax,4 ; 每次遞增4
mov edx,dword ptr ds:[esi + eax] ; 定位到內層迴圈元素
loop s2
pop eax
pop ecx
add eax,20 ; 控制外層陣列
loop s1
invoke ExitProcess,0
main ENDP
END main
通過比例因子實現對陣列求和操作,程式碼如下:
.386p
.model flat,stdcall
option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
ArrayA DWORD 10h,20h,30h,40h,50h
ArrayB DWORD 10h,20h,30h,40h,50h
NewArray DWORD 5 dup(0)
.code
main PROC
; 迴圈讓陣列中的每一個數加10後回寫
mov ebx,0
mov ecx,5
s1:
mov eax,dword ptr ds:[ArrayA + ebx * 4]
add eax,10
mov dword ptr ds:[ArrayA + ebx * 4],eax
inc ebx
loop s1
; 迴圈讓陣列A與陣列B相加後賦值到陣列NewArray
mov ebx,0
mov ecx,5
s2:
mov esi,dword ptr ds:[ArrayA + ebx]
add esi,dword ptr ds:[ArrayB + ebx]
mov dword ptr ds:[NewArray + ebx],esi
add ebx,4
loop s2
invoke ExitProcess,0
main ENDP
END main
陣列指標定址: 變數地址的變數稱為指標變數(pointer variable),Intel處理器使用兩種基本型別的指標,即near(近指標)和far(遠指標),保護模式下使用Near指標,所以它被儲存在雙字變數中.
.386p
.model flat,stdcall
option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
ArrayA WORD 1h,2h,3h,4h,5h
ArrayB DWORD 1h,2h,3h,4h,5h
PtrA DWORD offset ArrayA ; 指標 PtrA --> ArrayA
PtrB DWORD offset ArrayB ; 指標 PTRB --> ArrayB
.code
main PROC
mov ebx,0 ; 定址因子
mov ecx,5 ; 迴圈次數
s1:
mov esi,dword ptr ds:[PtrA] ; 將指標指向PtrA
mov ax,word ptr ds:[esi + ebx * 2] ; 每次遞增2位元組
mov esi,dword ptr ds:[PtrB] ; 將指標指向PtrB
mov eax,dword ptr cs:[esi + ebx * 4] ; 每次遞增4位元組
inc esi ; 基地址遞增
inc ebx ; 因子遞增
loop s1
invoke ExitProcess,0
main ENDP
END main
常見標誌位測試: 標誌暫存器又稱程式狀態暫存器,其主要用於存放條件碼標誌,控制標誌和系統標誌的暫存器,標誌暫存器中存放的有條件標誌,也有控制標誌,這些標誌則會影響跳轉的實現,逆向中常見的標誌位有如下幾種.
.386p
.model flat,stdcall
option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.code
main PROC
; CF 進位標誌位: 當執行一個加法(或減法)運算,使最高位產生進位(或借位)時,CF為1;否則為0
mov ax,0ffffh
add ax,1 ; cf = 1 af = 1
; PF 奇偶標誌位: 當運算結果中,所有bit位(例:1001010)中1的個數為偶數時,則PF=1;為基數PF=0
mov eax,00000000b
add eax,00000111b ; pf = 0
mov eax,00000000b
add eax,00000011b ; pf = 1
; ZF 零標誌位: 若當前的運算結果為零,則ZF=1; 否則ZF=0
mov eax,2
sub eax,2 ; zf = 1 cf = 0 af = 0
; SF 符號標誌位: 若運算結果為負數,則SF=1;若為非負數則SF=0
mov eax,3e8h
sub eax,3e9h ; sf = 1 cf = 1 af = 1 zf = 0
; DF 方向標誌位: 當DF=0時為正向傳送資料(cld),否則為逆向傳送資料(std)
cld
mov eax,1 ; df = 0
std
mov eax,1 ; df = 1
; OF 溢位標誌位: 記錄是否產生了溢位,當補碼運算有溢位時OF=1;否則OF=0
mov al,64h
add al,64h ; of = 1 cf = 0 pf = 0 af = 0
invoke ExitProcess,0
main ENDP
END main
TEST 位與指令: 該指令在對運算元之間執行隱含與運算操作,並設定相應的標誌位,與AND指令唯一的不同在於,該指令只會設定相應的標誌,並不會替換目的運算元中的數值,常用於測試某些位是否被設定.
TEST指令可以同時檢測設定多個標誌位的值,該指令執行時總是清除溢位標誌和進位標誌,它修改符號標誌,基偶標誌,零標誌的方式與AND指令相同.
.386p
.model flat,stdcall
option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.code
main PROC
mov al,00001111b
test al,2 ; zf=0 pf=0
mov al,00100101b
test al,00001001b ; zf=0 pf=0
mov al,00100100b
test al,00001001b ; zf=1 pf=1
mov eax,0100h
test eax,eax ; zf=0
mov eax,0
test eax,eax ; zf=0
or al,80h ; 設定符號標誌 zf=0 pf=0 sf=1
and al,7fh ; 清除符號標誌 zf=1 pf=1 sf=0
mov al,0
or al,1 ; 清除符號標誌 zf=0 pf=0
stc ; 設定進位標誌 cf = 1
clc ; 清除進位標誌 cf = 0
mov al,07fh ; AL = +127
inc al ; 設定溢位標誌 AL = 80h (-128) of=1 af=1 sf=1
or eax,0 ; 清除溢位標誌
invoke ExitProcess,0
main ENDP
END main
CMP 比較指令: 該指令作用是在源運算元和目的運算元中間執行隱含的減法運算,兩個運算元都不會被修改,僅會影響標誌位的變化,CMP指令是高階語言實現程式邏輯的關鍵,也是彙編中非常重要的指令常與跳轉指令合用.
.386p
.model flat,stdcall
option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.code
main PROC
; 比較5和10
mov ax,5
cmp ax,10 ; 5-10 > zf=0 cf=1 pf=0 af=1 sf=1
; 比較兩個相同數
mov ax,1000
mov cx,1000
cmp cx,ax ; 1000-1000 > zf=1 cf=0 pf=1 sf=0
; 比較100和0
mov ax,100
cmp ax,0 ; 100-0 > zf=0 cf=0 pf=0
; 比較100和50
mov eax,100
mov ebx,50
cmp eax,ebx ; 100-50 > zf=0 cf=0 pf=0
; 比較-100和50
mov eax,-100
mov ebx,50
cmp eax,ebx ; -100-50 > sf=1 pf=1
; 比較-100和-50
mov eax,-100
mov ebx,-50
cmp eax,ebx ; -100--50 > cf=1 af=1 pf=0
invoke ExitProcess,0
main ENDP
END main
標誌跳轉指令: 跳轉指令分為多種,第一種常見的跳轉指令就是基於特定CPU的標誌暫存器來實現的跳轉形式.
.386p
.model flat,stdcall
option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.code
main PROC
; JZ/JE 當ZF置1時 也就是結果為零則跳轉 (leftOp - rightOp = 0)
mov eax,1
sub eax,1 ; zf=1 pf=1
je jump
mov eax,1
mov ebx,1
cmp eax,ebx ; zf=1
jz jump
; JNZ/JNE 當ZF置0時 也就是結果不為零則跳轉 (leftOp - rightOp != 0)
mov eax,2
sub eax,1
jnz jump ; zf=0 pf=0
mov eax,2
cmp eax,1
jne jump ; zf=0
; JC/JNC 當 CF=1/0 設定進位標誌則跳/未設定進位標誌則跳
mov al,0
cmp al,1
jc jump
jnc jump
; JO/JNO 當 OF=1/0 設定溢位標誌則跳/未設定溢位標誌則跳
mov al,0ffh
add al,1
jo jump
; JS/JNS 當 SF=1/0 設定符號標誌則跳/未設定符號標誌則跳
mov eax,1
cmp eax,1
js jump ; cf=0 af=0
; JP/JNP PF=1/0 設定奇偶標誌則跳(偶)/未設定奇偶標誌則跳(基)
mov al,00100100b
cmp al,0
jp jump ; zp=0
jump:
xor eax,eax
xor ebx,ebx
invoke ExitProcess,0
main ENDP
END main
比較跳轉標誌: 通過使用cmp eax,ebx
比較等式兩邊的值,影響相應的標誌暫存器中的值,從而決定是否要跳轉,常用的如下:
.386p
.model flat,stdcall
option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.code
main PROC
; JA(無符號)/JG(有符號) 跳轉標誌: (left > right) 大於則跳轉
mov eax,100
mov ebx,200
cmp ebx,eax ; 無符號 ebx > eax
ja jump ; zf=0 pf=0
mov eax,20
mov ebx,-100
cmp eax,ebx ; 有符號 eax > ebx
jg jump ; zf=0 cf=1 pf=1 af=1
; JAE(無符號)/JGE(有符號) 跳轉標誌: (left >= right) 大於或等於則跳轉
mov eax,50
mov ebx,20
cmp eax,ebx ; 無符號 eax >= ebx
jae jump ; jae 被替換成了jnb 小於則跳 (eax<ebx)
mov eax,50
mov ebx,-20
cmp eax,ebx ; 有符號 eax >= ebx
jge jump ; zf=0 af=1 pf=0 sf
; JB(無符號)/JL(有符號) 跳轉標誌:(left < right) 小於則跳轉
mov eax,10
mov ebx,20
cmp eax,ebx ; 無符號 eax < ebx
jb jump ; cf=0 af=0 pf=1
mov eax,-10
mov ebx,20
cmp eax,ebx ; 有符號 eax < ebx
jl jump
; JBE(無符號)/JLE(有符號) 跳轉標誌:(left <= right) 小於或等於則跳轉
mov eax,20
mov ebx,20
cmp eax,ebx ; 無符號 eax <= ebx
jbe jump ; zf=1
mov eax,-20
mov ebx,10
cmp eax,ebx ; 無符號 eax,ebx
jle jump ; sf=1
; JNB(不小於則跳 同JAE)/JNA(不大於則跳 同JBE) 跳轉標誌:(lef !>< right) 無符號
mov eax,10
mov ebx,5
cmp eax,ebx ; eax !< ebx
jnb jump
mov eax,5
mov ebx,10
cmp eax,ebx ; eax !> ebx
jbe jump
; JNB(不小於則跳 同JAE)/JNA(不大於則跳 同JBE) 跳轉標誌:(lef !>< right) 無符號
mov eax,10
mov ebx,5
cmp eax,ebx ; eax !< ebx
jnb jump ; zf=0 cf=0
mov eax,5
mov ebx,10
cmp eax,ebx ; eax !> ebx
jbe jump ; cf=1 af=1 zf=0
; JNL(不小於則跳 同JGE)/JNG(不大於則跳 同JLE) 跳轉標誌:(lef !>< right) 有符號
mov eax,10
mov ebx,-5
cmp eax,ebx ; eax !< ebx
jnl jump ; sf=0 cf=1 pf=1 af=1 zf=0
mov eax,-10
mov ebx,5
cmp eax,ebx ; eax !> ebx
jng jump ; sf=1
jump:
xor eax,eax
xor ebx,ebx
invoke ExitProcess,0
main ENDP
END main