1. 程式人生 > 實用技巧 >Win32彙編 - 陣列與標誌位測試

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