前言
首先來說下 什么是hack ? hack字面意思“ 非法入侵 ”,那么在C/C++中其實就是 使用反匯編查看C/C++代碼對應的匯編代碼 。
那可能有人要問了,C/C++不是高級語言么,為什么還要看匯編代碼?理由嘛見仁見智,
個人理解有下面幾種:
- 1.C/C/C++應用不像java,python那樣,報錯信息可以在日志中一目了然,C/C++應用的報錯可以讓你查的懷疑人生,為什么呢?因為 報錯日志提供的信息可能會誤導你 ,比如真實錯誤是在100行,但是報錯信息會在第一行或者全局都有報錯,就很讓人上頭。。有了這個hack過程,就可以定位到可能報錯的地方,使用hack反編譯得到匯編代碼,匯編代碼可以讓你看清你寫的C/C/C++代碼對于CPU來說實際執行的行為。
- 2.當我們在調試C/C++應用的時候,經常會 有些疑惑 ,比如i++和++i為什么結果不同,又比如inline內聯函數有什么用處?原理是什么?等等,這些也可以使用hack來解釋,那有人要說了,這些我都知道啥意思啊,i++和++i前自增和后自增么,內聯函數原理就是編譯期將函數代碼拷貝到調用處嘛。可是這些都是別人告訴你的,那如果一個代碼段別人沒告訴你呢?百度是不是什么都可以找到的,特別是對于C/C++開發來說,信息更是少的可憐,此時你就會知道hack是多重要了。
- 3.如果你的C/C++應用需要實現一些性 能上優化 ,也可以使用hack過程查看CPU和內存的行為,從而對應用性能做一些提升,是不是很cool。。
既然有這么多用處,那么下面小余就來講解下如何在我們的應用中hack。
不過在hack前我們需要了解一些匯編語言基礎。
匯編語言基礎
首先我們得了解匯編語言中的幾個模塊:1.寄存器 2.內存 3.CPU
1.寄存器
寄存器是我們計算機中的最小存儲單位,在計算機金字塔的頂端, 屬于CPU的內部存儲 ,主要用來存儲CPU需要使用到的臨時數據。
CPU訪問寄存器的速度比訪問內存的速度快了100倍左右。
CPU中寄存器種類包括:通用寄存器,指令地址寄存器,狀態寄存器等等等等。。 這里只介紹幾個比較關鍵的寄存器:
1.通用寄存器
寄存器 | 原文 | 解釋 | 說明 |
---|---|---|---|
AX | accumulator | 累加寄存器 | 通常用來執行加法,函數調用的返回值一般也放在這里面 |
CX | counter | 計數寄存器 | 通常用來作為計數器,比如for循環 |
DX | data | 數據寄存器 | 數據存取 |
BX | base | 基址寄存器 | 讀寫I/O端口時,edx用來存放端口號 |
SP | stack pointer | 棧指針寄存器 | 棧頂指針,指向棧的頂部 |
BP | base pointer | 基址指針寄存器 | 棧底指針,指向棧的底部,通常用ebp+偏移量的形式來定位函數存放在棧中的局部變量 |
SI | source index | 源變址寄存器 | 字符串操作時,用于存放數據源的地址 |
DI | destination index | 目標變址寄存器 | 字符串操作時,用于存放目的地址的,和esi兩個經常搭配一起使用,執行字符串的復制等操作 |
這里說明幾點:
- 1.在具體hack的過程中你可能看到的是eax,ecx,edx等而不是AX,CX,DX,他們區別: 帶e開頭的說明是32位寄存器,也就是4個字節存儲的寄存器,而AX是eax的低16位的“子寄存器”, 這是早期一些寄存器是16位的,而為了兼容早期這些寄存器就使用“子寄存器”來標識。
- 2.匯編語言中,在函數體返回時,統一使用eax為返回值,這個為什么這樣?我也不清楚,可能是早期大家都這么用就約定下來了吧。
- 3.當我們在使用函數的時候,會將ebp的內容壓入棧底,作為棧底指針,指向棧的底部,局部變量使用ebp+偏移地址來定位。對于棧頂則使用esp寄存器來定位,關于函數棧會在后面講解。
2.eip寄存器
eip寄存器可以說是CPU中最關鍵的一個寄存器了,它指向了下一條要執行的指令所存放的地址, CPU工作其實就是取出eip中的地址中的指令,然后去執行這條指令,并將下一條指令的地址賦值給eip ,這樣CPU就可以依次執行完成所有的匯編代碼.
如果你要問我它是怎么找到下一條指令的地址的,首先正常執行指令是+1位下一條指令地址,如果碰到一些比如if語句,函數調用等情況,就會在上一條指令中得到這個地址,并賦值給eip,這個賦值動作是CPU自動完成的,開發是不能隨便更改的,這也變相的防止你搞出bug不是?
3.狀態寄存器
狀態寄存器中記錄了CPU執行過程中的一系列狀態,對于32位就有32個標志位,這些標志大部分都是由CPU自行設置:
寄存器標志位 | 解釋 |
---|---|
CF | 進位標志 |
PF | 奇偶標志 |
ZF | 零標志 |
SF | 符號標志 |
OF | 補碼溢出標志 |
TF | 跟蹤標志 |
IF | 中斷標志 |
…… |
4.指令寄存器:
根據指令在存貯器中的地址(由指令地址計數器給出),把指令從存貯器中取出來之后,需要有一個專門用于存放指令的地方,以便對指令進行分析和執行。 這個專門存放現行指令的部件就叫做指令寄存器 。
后面會具體匯編代碼會涉及到。
關于寄存器就講這么幾個吧,hack過程夠用了。
2.內存
這里講解的內存一般是指高速緩存(Cache)或者主存。
計算機在運行程序時,首先將程序從磁盤讀取到主存,然后CPU按規則從主存中取出指令,數據并執行指令,但是直接從主存(一般是DRAM)中讀寫是很慢的,所以引入了高速緩存(Cache)(使用的是SRAM)。
在程序運行前首先會試圖將指令,數據從主存中讀取到Cache中,然后在程序執行時直接訪問Cache, 如果指令和數據可以從Cache中讀取到,那么就說是“命中(hit)”,反之就是“不命中(miss)”, miss情況下需要從主存中讀取指令或者數據,這樣會直接影響CPU的性能,所以命中率對CPU來說至關重要。
內存在計算中又會分為:堆區,棧區,全局(靜態)區,常量區,代碼區等等。
這里講解下hack中比較關鍵的棧區。
棧區
- 棧中存放了局部變量,參數,函數的返回地址等
- 棧區由編譯器自動分配釋放,由操作系統自動管理,無須手動管理
- 棧區中的局部變量只在該函數作用域中有效,函數退出后就會被銷毀、
棧區就像一個桶一樣,桶底就是棧底為高地址區,桶的頂部為低地址, 桶中放東西當然是先放桶底然后依次疊加咯,也就是平時所說的FILO先進后出的模式。
3.CPU
1.CPU的功能
指令控制(程序的順序控制)
操作控制(一條指令由若干操作信號實現)
時間控制(指令各個操作實施時間的定時)
數據加工(算術運算和邏輯運算)
2.CPU的基本組成
中央處理器 CPU= 運算器 + 控制器
運算器:
ALU
累加器
暫存器
控制器
程序計數器 (PC) 、指令寄存器 (IR) 、數據緩沖器 (DR) 、地址寄存器 (AR) 、通用寄存器、狀態寄存器 (PSW) 、時序發生器、指令譯碼器 (ID) 、總線
CPU的工作分為以下 5 個階段:取指令階段、指令譯碼階段、執行指令階段、訪存取數和結果寫回。
- 取指令 (IF,instruction fetch),即將一條指令從主存儲器中取到指令寄存器的過程。程序計數器中的數值,用來指示當前指令在主存中的位置。當 一條指令被取出后,程序計數器(PC)中的數值將根據指令字長度自動遞增。
- 指令譯碼階段 (ID,instruction decode),取出指令后,指令譯碼器按照預定的指令格式,對取回的指令進行拆分和解釋,識別區分出不同的指令類 別以及各種獲取操作數的方法。現代CISC處理器會將拆分已提高并行率和效率
- 執行指令階段 (EX,execute),具體實現指令的功能。CPU的不同部分被連接起來,以執行所需的操作。
- 訪存取數階段 (MEM,memory),根據指令需要訪問主存、讀取操作數,CPU得到操作數在主存中的地址,并從主存中讀取該操作數用于運算。部分指令不需要訪問主存,則可以跳過該階段。
- 結果寫回階段 (WB,write back),作為最后一個階段,結果寫回階段把執行指令階段的運行結果數據“寫回”到某種存儲形式。結果數據一般會被寫到CPU的內部寄存器中,以便被后續的指令快速地存取;許多指令還會改變程序狀態字寄存器中標志位的狀態,這些標志位標識著不同的操作結果,可被用來影響程序的動作。 在指令執行完畢、結果數據寫回之后,若無意外事件(如結果溢出等)發生,計算機就從程序計數器中取得下一條指令地址,開始新一輪的循環,下一個指令周期將順序取出下一條指令。 [1] 許多復雜的CPU可以一次提取多個指令、解碼,并且同時執行。
一些基礎的匯編命令:
1.數據傳送指令
-
1.mov :傳送指令,也可以單純理解為賦值語句,這個語句在匯編中是最常用的。 格式:mov det,src
首先mov語句分以下幾種情況:
- 1.CPU內部寄存器之間數據傳送:mov ebp,esp ,將esp寄存器中的數據傳遞給ebp寄存器。
- 2.立即數傳遞到通用寄存器:mov ebp 3 ,將立即數3傳遞到ebp寄存器中。
- 3.立即數傳遞到內存存儲單元:mov [bx] 123h,將立即數傳遞到bx指向的內存地址中。
- 4.內存和CPU之間數據傳送:內存傳遞到CPU寄存器中:mov eax,dword ptr [a] ,CPU寄存器傳遞到內存中:mov dword ptr [a],eax。
2.加減運算指令
首先要了解,加減法運算是會改變狀態寄存器中的值的,如一個有符號數的運算高位溢出標志(OF),無符號的運算進位標志(CF)等。
加法指令:add、adc、inc
- 1.add
格式:add OPRD1,OPRD2 功能:OPRD1 = OPRD1 + OPRD2 舉例: add eax ecx;//得到的結果為eax = eax+ecx;
- 2.adc
格式:add OPRD1,OPRD2 功能:OPRD1 = OPRD1 + OPRD2 舉例: adc eax ecx;得到的結果為eax = eax+ecx+狀態寄存器中給的進位標志CF;
- 3.inc
格式:add OPRD 功能:OPRD = OPRD + 1 舉例: inc eax;//得到的結果為eax = eax+1;
減法指令:sub,dec,cmp,當然還有和adc對應的sbb 這里講解下sub和dec
- 1.sub
格式:sub OPRD1,OPRD2 功能:OPRD1 = OPRD1 - OPRD2 舉例: sub eax ecx;//得到的結果為eax = eax - ecx;
- 2.dec
格式:dec OPRD 功能:OPRD = OPRD - 1 舉例: dec eax;//得到的結果為eax = eax - 1;
- 3.cmp
格式:cmp OPRD1,OPRD2 功能:執行OPRD1 - OPRD2,但運算結果不運送到OPRD1 注:該指令通過OPRD - OPRD2影響標志位CF、ZF、SF、OF、AF、PF來判斷OPRD1和OPRD2的大小關系。 通過ZF判斷是否相等;如果是無符號數,通過CF可判斷大小;如果是有符號數,通過SF和OF判斷大小
3.邏輯運算和移位指令
1、邏輯運算指令:not、and、or、xor、test
- (1) NOT:非運算
格式:NOT OPRD 功能:把操作數OPRD取反,然后送回OPRD。 注: OPRD可以是通用寄存器,也可以是存儲器操作數,此指令對標志沒有影響
- (2) AND:與運算
格式:AND OPRD1,OPRD2 功能:對兩個操作數進行按位邏輯“與”運算,結果送到OPRD1中 注: 該指令執行后,CF=0,OF=0,標志PF、ZF、SF反映運算結果,AF未定義。 某個操作數與自身相與,值不變,但可以使CF置0。
- (3) OR:或運算
格式:OR OPRD1,OPRD2 功能:對兩個操作數進行按位邏輯“或”運算,結果送到OPRD1中 注: 該指令執行后,CF=0,OF=0,標志PF、ZF、SF反映運算結果,AF未定義。 某個操作數與自身相或,值不變,但可以使CF置0。
- (4) XOR:異或運算
格式:XOR OPRD1,OPRD2 功能:對兩個操作數進行按位邏輯“異或”運算,結果送到OPRD1中
2、一般移位指令:SAL/SHL,SAR/SHR
- (1) SAL/SHL(Shift Arithmetic Left / Shift Logic Left);算術左移/邏輯左移
格式:SAL OPRD,m SHL OPRD,m 功能:把操作數OPRD左移m位,每移動一位,右邊用0補足1位,移出的最高位進入標志位CF 注: 算術左移和邏輯左移進行相同的動作,為了方便提供了兩個助記符。
- (2) SAR(Shift Arithmetic Right) ;算數右移指令
格式:SAR OPRD,m 功能:操作數右移m位,同時每移1位,左邊的符號位保持不變,移出的最低位進入標志位CF 注: 對有符號數和無符號數,算數右移1位相當于除以2 如:SAR BH,1 ;(BH)= 80H,指令執行后(BH)= C0H
- (3) SHR(Shift Logic Right) ;邏輯右移指令
格式:SHR OPRD,m 功能:操作數右移m位,同時每移1位,左邊用0補足,移出的最低位進入標志位CF 注: 對無符號數,邏輯右移1位相當于除以2 如:SHR AH,1 ;(AH)= 80H,指令執行后(AH)= 40H
切記:算術移位和邏輯移位的區別:
對于左移不管是算術移位和邏輯移位,低位都補0; 對于右移的算術移位:對于有符號數,符號位不變,然后整體右移。
4.轉移指令
1、無條件轉移指令:JMP(Jump)
直接跳轉到Jump指定的地址, 跳轉指令也是改變當前eip的值來決定的 ,這樣下次取指令會去新的eip地址下取。
格式:JMP OPRD
功能:使控制指令無條件轉移到OPRD的內容給定的目標地址處。操作數OPRD可以是通用寄存器,也可以是字存儲單元
例如:
jmp cx ;CX寄存器的內容送IP
jmp word ptr [1234h] ;字存儲單元[1234h]的內容送IP
-
C++
+關注
關注
22文章
2108瀏覽量
73632 -
匯編代碼
+關注
關注
0文章
23瀏覽量
7550 -
hacker
+關注
關注
0文章
4瀏覽量
1362
發布評論請先 登錄
相關推薦
評論