逆向之彙編筆記

eqera發表於2020-12-30

一. 通用暫存器

資料暫存器 EAX, EBX, ECX, EDX (Data Register)

資料暫存器主要用來儲存運算元和運算結果等資訊,從而節省讀取運算元所需佔用匯流排和訪問儲存器的時間。   

32位CPU有4個32位的通用暫存器EAX、EBX、ECX和EDX。對低16位資料的存取,不會影響高16位的資料。這些低16位暫存器分別命名為:AX、BX、CX和DX,它和先前的CPU中的暫存器相一致。4個16位暫存器又可分割成8個獨立的8位暫存器:

  • AX可分為AH和AL.

  • BX可分為BH和BL.

  • CX可分為CH和CL.

  • DX可分為DH和DL.

每個暫存器都有自己的名稱,可獨立存取。程式設計師可利用資料暫存器的這種“可分可合”的特性,靈活地處理字/位元組的資訊。

暫存器AX通常稱為累加器(Accumulator),用累加器進行的操作可能需要更少時間。累加器可用於乘、除、輸入/輸出等操作,它們的使用頻率很高;

暫存器BX稱為基地址暫存器(Base Register)。它可作為儲存器指標來使用;

暫存器CX稱為計數暫存器(Count Register)。在迴圈和字串操作時,要用它來控制迴圈次數;在位操作中,當移多位時,要用CL來指明移位的位數;

暫存器DX稱為資料暫存器(Data Register)。在進行乘、除運算時,它可作為預設的運算元參與運算,也可用於存放I/O的埠地址。

指標暫存器 EBP, ESP (Pointer Register)32位CPU有2個32位通用暫存器EBP和ESP。其低16位對應先前CPU中的BP和SP,對低16位資料的存取,不影響高16位的資料。

指標暫存器主要用於存放堆疊記憶體儲單元的偏移量,用它們可實現多種儲存器運算元的定址方式,為以不同的地址形式訪問儲存單元提供方便。指標暫存器不可分割成8位暫存器。作為通用暫存器,也可儲存算術邏輯運算的運算元和運算結果。

暫存器BP稱為基址指標暫存器(Base Pointer);

暫存器SP稱為堆疊指標暫存器(Stack Pointer)。

變址暫存器 ESI, EDI (Index Register)32位CPU有2個32位通用暫存器ESI和EDI。其低16位對應先前CPU中的SI和DI,對低16位資料的存取,不影響高16位的資料。

變址暫存器主要用於存放儲存單元在段內的偏移量,用它們可實現多種儲存器運算元的定址方式,為以不同的地址形式訪問儲存單元提供方便。 變址暫存器不可分割成8位暫存器。作為通用暫存器,也可儲存算術邏輯運算的運算元和運算結果。 暫存器SI稱為源變址暫存器 (Source Index);

暫存器DI稱為目的變址暫存器(Destination Index)。

附表:

MOV指令

語法:MOV r/m8,r8

MOV r/m16,r16 r 通用暫存器

MOV r/m32,r三二 m 代表記憶體

MOV r8,r/m8 imm代表立即數

MOV r16,r/m16 r8代表8位通用暫存器

MOV r三二,/r/m32 m8 代表8位記憶體

MOV r8,imm8 imm8代表8位立即數

MOV r16,imm16

MOV r三二,imm32

ADD指令:加法

SUB指令:減法

AND指令:與運算

OR指令: 或運算

XOR指令:異或運算

NOT指令:非運算 該運算的語法操作物件只是源運算元

二. 記憶體讀寫

BYTE 位元組 = 8 (BIT) WORD 字 = 16(BIT) DWORD 雙字 = 32(BIT)

  1. 記憶體格式

    每個記憶體單元的寬度是8 [編號]稱為地址

  2. 從指定記憶體中寫入讀取資料

    mov dword ptr ds:[0x0012FF34],0x12345678

    mov eax,dword ptr ds:[0x12FF34]

    dword:要讀寫多少, 此時是4位元組

    ptr:point 代表後面是一個指標

    ds:段暫存器

    [編號] 記憶體地址 必須為32位

三. 記憶體定址

1.定址公式一: (立即數)

讀取記憶體的值:

MOV EAX,DWORD PTR DS:[0X13FFC四] 從地址中讀取DWORD(四個位元組的值):所以其實是 0X13FFC四,0X13FFC50X13FF,C6,0X13FFC7的值取到EAX

MOV EAX,DWORD PTR DS:[0X13FFC8]

寫資料於記憶體:

MOV DWORD PTR DS:[0X13FFC四],EAX

獲取記憶體編號:

LEA EAX,DWORD PTR DS:[0x13ffc四]:

2.定址公式二:[reg]reg代表暫存器 可以是8個通用暫存器中的任意一個

讀取記憶體的值:

MOV ECX,0X13FFD0 :先將地址給ECX

MOV EAX,DWORD PTR DS:[ECX] :將ECX中的值給EAX

向記憶體寫資料

MOV EDX,0X13FFD8

MOV DWORD PTR DS:[EDX],0X87654321

獲取記憶體編號

LEA EAX ,DOWRD PTR DS:[EDX] 將EDX的值(是一個地址)給EAX

MOV EAX,DWORD PTR DS:[EDX]

3.定址公式三. [reg+立即數]

讀記憶體的值

MOV ECX,0x13FFD0

MOV EAX,DWORD PTR DS:[ECX+4]

向記憶體中寫資料

MOV EDX,0x13FFD8

MOV DWORD PTR DS:[EDX+0xC],0x87654321

獲取記憶體編號

LEA EAX,DWORD PTR DS:[EDX+4] :獲取EDX中的地址值+4後給EAX

MOV EAX,DWORD PTR DS:[EDX+4]

還有其他的幾種情況

四. 堆疊

壓入資料

設定棧頂和棧底:

MOV EBX,0x13FFDC BASE

MOV EDX,0x13FFDC TOP

方式一:

MOV DWORD PTR DS:[EDX-4],0xAAAAAAAA

SUB EDX,4

方式二:

SUB EDX,4

MOV DWORD PTR DS:[EDX],0XBBBBBBBB

方式三:

MOV DWORD PRT DS:[EDX-4],0xDDDDDDDD

LEA EDX,DWORD PTR DS:[EDX-4]

方式四:

LEA EDX,DWORD PTR DS:[EDX-4]

MOV DWORD PTR DS:[EDX],0XEEEEEEEE

使用PUSH壓棧 POP出棧

PUSH 將後面的所跟的內容入棧 當跟的是記憶體的時候是將記憶體的值入棧

例如 PUSH DWORD PTR DS:[0XFFDA] 將0XFFDA內的值入棧

PUSHAD:將8個通用暫存器全部入棧,這樣可以隨便使用之後的暫存器

POPAD:將入棧了的8個暫存器全部出棧,恢復現場

五. 第一個CRACKME

需要的知識:PE結構,下斷點,WIN32 API,知道什麼是函式呼叫,熟悉堆疊,call,JCC,標誌暫存器

六. 暫存器

1.進位暫存器CF(Carry Flag):一般用於無符號數,如果運算結果的最高位發生進位或借位,則值為1沒否則為0

如:MOV AL,0xEF ADD AL,2

2.奇偶標誌PF(Parity Falg)用於反應運算結果中"1"的個數的奇偶性,若“1”的個數是偶數則PF的值為1,否則為0

MOV AL,3 ADD AL,3 ADD AL,2 執行觀察情況

3.輔助進位標誌AF,當發生下列情況的時候AF置1否則為0

(1) 在字操作時,發生低位元組向高位元組進位或借位時

(2) 在位元組操作時,發生低四位向高四位進位或借位時

MOV EAX,0x55EEFFFF(32位情況)

MOV AX,5EFE ADD AX,2 (16位情況)

4.零標誌位ZF,ZF用來反映運算結果是否是0,若運算結果是0,則其值位1,否則為0

XOR(異或:對應的位相同為0) EAX,EAX 可以用來EAX清零

MOV EAX,2 SUB EAX,2

5.符號標誌位SF:符號標誌位用於反應運算結果的符號位,他與運算結果的最高位相同

MOV AL,7F (7F:0111 1111)最高位是0

ADD AL,2F

6.溢位標誌位OF:OF用於反應有符號數加減運算得到的結果是否溢位,如果運算的結果超過當前運算位數所能表示的範圍則稱為溢位,OF置1

最高位進位與溢位的區別:

進位標誌表示無符號數運算結果是否超出範圍

溢位標誌表示有符號數運算結果是否超出範圍

首先:溢位主要是給有符號運算使用的,在有符號運算中,有如下規律:

正 + 正 = 正 如果結果是負數,則說明有溢位

負 + 負 = 負 如果結果是正數,則說明有溢位

正 + 負 永遠不會溢位

ADC指令:帶進位加法

格式:ADC R/M,R/M/IMM 兩邊不能同時為記憶體,資料寬度要一樣

ADC BYTE PTR DS:[0x12FFC四],2

SBB指令,帶借位減法

SBB BYTE PRE DS:[12FFC四],2

XCHG指令:交換資料

XCHG R/M,R/M

MOVS指令:移動資料 記憶體-記憶體(該指令是少有的可以源運算元和目的運算元同都可以是記憶體的指令)

MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI] 資料寬度為位元組,將ESI這個記憶體中的資料移動到EDI 簡寫為 MOVSB

MOVS WORD PTR ES:[EDI],WORD PTR DS:[ESI] 簡寫為WOVSW

MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[EDI] 簡寫為WOVSD

STOS指令:將AL/AX/EAX的值儲存到[EDI]所指定的記憶體單元 (注意此時如果資料寬度是DWORD 則EDI的值也根據 資料寬度+4或者-4)

` STOS BYTE PTE ES:[EDI]簡寫為STOSB

STOS WORD PTE ES:[EDI]簡寫為STOSW

STOS WORD PTE ES:[EDI]簡寫為STOSD

到底是選AL還是AX還是EAX取決於選擇的資料寬度是BYTE還是WORD還是DWORD

REP指令:按照計數暫存器(ECX)中指定的次數重複執行字串指令

MOV ECX,10

REP MOVSD 重複執行十次將ESI指定記憶體中的資料移動到EDI

JMP指令:無條件修改EIP(存放下條要執行的指令)的值,

MOV EIP,暫存器/立即數 簡寫為JMP 暫存器/立即數

CALL指令:也是修改EIP為指定的值,同時將原來指令的下一條指令入棧

RET指令:和CALL成對出現,用於返回到CALL之前的指令,本質就是POP EIP

CMP指令:比較兩個運算元,實際上相當於SUB指令,但是相減的結構並不儲存到第一個運算元中,只是根據相減的結果來改變零標誌位,即只是比較運算元但是不改變兩個暫存器的值,當兩個運算元相等的的時候,零標誌位置1.

CMP R/M,R/M/IMM 注意相比較的時候的資料寬度要一樣

TEST指令:該指令一定程度上和CMP指令類似,兩個數值執行與操作,結果不儲存(即不改變原來兩個暫存器的值),但是會改變相應的標誌位.常用於判斷某暫存器的值是否為0

TEST R/M,R/M/IMM

七. JCC條件跳轉指令

八. 重點! Win堆疊圖

windows堆疊的特點:1.先進後出,2.向低地址擴充套件 3.堆疊平衡:函式用完堆疊之後堆疊要回復原狀

https://www.cnblogs.com/Tkitn/p/12354131.html

OD:F8單步步過 F7單步步入

九. C語言

1. 窺探

VC6的使用以及除錯方法

簡單函式的堆疊彙編分析

對於一般的函式,編譯器會做很多的處理,例如劃分堆疊,儲存現場等等

C語言裸函式 void __declspec(nacked) Function(){},對於裸函式,編譯器是不會做任何的處理,但是在裸函式中需要寫ret返回語句確保整個程式不出錯

C語言中新增彙編程式碼的操作是 __asm{彙編程式碼}

//例如一個兩數相加的裸函式
int __declspec(nacked) Plus(int x,int y){
__asm{
//引數
//區域性變數
//返回值 
//保留呼叫前的堆疊
push ebp
//提升堆疊,其目的是位函式分配空間
mov ebp,esp
sub esp,0x40
//保留現場
push ebx
push esi
push edi
//開始填充緩衝區
mov eax,0xCCCCCCCC
mov ecx,0x10
lea edi,dword ptr ds:[ebp-40]
rep stosd 
//函式的核心功能
mov eax,dword ptr ds:[ebp+8]
add eax,dword ptr ds:[ebp+0xc]
//回覆現場
pop dei
pop edi
pop ebx
//降低堆疊
mov esp,ebp
pop ebp         
ret
}
}
注意區域性變數是從ebp-4的位置開始儲存的

fastcall方法中引數超過兩個效果就不是很顯著了。fastcall快的原因是其引數直接在暫存器(ECX/EDX傳送前兩個)傳遞而不是堆疊

2. 資料型別

float在記憶體中的儲存方式

各個字母字元的儲存:ASCII表 擴充ASCII表

中文的GB2312

3. 記憶體與變數

1.全域性變數的識別:MOV 暫存器,byte/word/dword ptr ds:[0x123113];通過暫存器的寬度或者byte/word/dword來判斷全域性變數的寬度。全域性變數就是基址

2.區域性變數的反彙編識別:[ebp-4] [ebp-8] [ebp-0xC]

3.判斷一個函式到底有幾個引數,以及分別是什麼:

一般情況:

步驟一:觀察呼叫除的程式碼:

push 3

push 2

push 1

call 0x0040100f

步驟二:找到平衡堆疊的程式碼繼續論證:

call 0040100f

add esp,0Ch

或者函式內部做堆疊平衡:ret 4(一個引數)/8(兩個)/0xC(三個)/0x10(四個)

觀察步驟:

  1. 先不考慮ebp,esp

  2. 只找給別人賦值的暫存器eax/ecx/edx/ebx/esi/edi 但是注意源運算元是立即數的指令一定不是,因為引數是在函式呼叫之後從暫存器呼叫的

  3. 找到後追查來源,如果該暫存器中的值不是在函式記憶體賦值的,那一定是傳進來的引數

  4. 公式1:暫存器 + ret 4 = 引數個數

  5. 公式2:暫存器 + [ebp+8] + [ebp+0x] = 引數個數

簡單的一個小程式碼分析函式的變臉和引數情況

IF_BEGIN:

先執行各類影響標誌位的指令

jxx ElSE BEGIN

IF_END:

jmp END

ELSE_BEGIN

.........

ELSE_END

END

一維陣列 int arr[12] = {1,2,3,4,5,6,7,8,9,10,11}的反彙編

二維陣列反彙編int arr[3][4]= {{....},{....},{....}}:和上面一樣沒有區別

int arr[3][4]={

{1,2},

{5},

{9}

}的反彙編是這樣的:

編譯器對於多維陣列和一維陣列其實是一樣的

1.小於32位的區域性變數,空間在分配的時候,按照32位分配

2.使用的時候按照實際的寬度使用

3.不要定義Char/short型別的區域性i變數,因為沒有意義,反而會有多餘的堆疊操作

4.引數和區域性變數沒有本質的區別,都在棧中

5.完全可以把引數當作區域性變數使用

引數和區域性變數的區別就是:引數是函式呼叫的時候分配在暫存器或者堆疊中,區域性變數在函式執行的時候分配

注意:經過學習其實可以知道陣列越界的程式碼是可以執行的,例如int arr[5] = {1,2,3,4,5}

此時程式碼在有一個arr[6] = (int) Helloworld;此時會執行helloworld這個函式,這是因為彙編中此時helloworld函式的地址為[ebp+4],而[ebp+4]得值會到EIP中,EIP存放下一步要執行得指令地址。so~~~~

對齊原則:

原則1:資料成員對齊規則,結構的資料成員,第一個資料成員放在offset為0的地方,之後每個成員儲存的起始位置要從該成員的整數倍開始(比如32位機位4位元組,則從4的整數倍地址開始儲存)

原則2:結構體的總大小,也就是sizeof的結果必須是內部最大成員的整數倍,不足的對齊

原則3:如果一個結構裡面由某些結構體成員,則結構體成員要從其內部最大元素大小的整數倍地址開始儲存(struct a裡存有struct b,b中由char,int,double等元素,那b應該從8的整數倍開始)

原則4:對齊引數如果比結構體成員的sizeof值小,改成員的偏移量應該以此值為標準。即結構體成員的偏移量應該取二者最小值

tips:由上可知,在程式碼編寫的過程中根據資料型別由小到大的順序進行書寫可以節省空間。

為現有的型別定義一個新的名字:例如在定義typedef unsigned char BYTE;之後

再定義unsigned char i = 1;的時候可以直接寫成BYTE i;

或者typedef int vector[10]; 之後定義的時候直接可以:vector v; v[0]=1;v[1]=2;.......

或者直接定義一個結構體:

typedef struct student{
int x;
int y;
}stu;
//之後可以直接使用stu代替student
switch(x){
case 1:
printf("1");
break;
case 2;
printf("2");
break;
case 3:
printf("3");
break;
}

tips:分支少於4的時候,用switch沒有意義,編譯器會生成類似if-else之類的反彙編

case後面的常量可以是無序的,並不影響大表的生成

switch的本質是生成一個大表,當引數傳遞進來的時候通過比對尋找到指定分支的地址

https://blog.csdn.net/apollon_krj/article/details/76793914

1.帶*型別的變數賦值的時候只能是"完整寫法" :char* i; i=(char*)8;

即例如char** a; a = (char**)100; a++; 實際上宣告時*有兩個,-1之後剩一個*,則該變數大小是char* a 的大小,即4個位元組。而原本的char* 減去一*之後成了char型別,變數大小就是1位元組

注意該型別的加法減法都可以使用該結論,但是帶*型別不能乘除運算

4.兩個型別相同的帶*型別的變數可以進行減法操作

5.相減的結果要除以去掉一個*的資料的寬度

6.帶*型別的變數可以做比較操作

重點是注意不同*的數量的變數操作之時,變數的大小情況。

1.算數移位運算:

SAL:算數左移 SAL AL,1 左移一位,最高位移到CF位,最低位補0

SAR:算數右移 SAR AL,1 右移一位,最低位移到CF位,最高位補符號位

2.邏輯移位指令:

SHL:邏輯右移 最低位到CF,最低位補0

SHR:邏輯左移

3.迴圈移位指令:

ROL:迴圈左移 將最高位的數補到最低位 ,CF中是最高位拿出來的數

ROR:迴圈右移 將最低位的數補到最高位,CF中是最低位拿出來的數

4.帶進位的迴圈移位指令:

RCL:帶進位迴圈左移,將最高位的值放在CF,原來CF的值放到最低位

RCR:帶進位迴圈右移,將最低位的值放在CF,原來CF的值放到最高位

相關文章