2、條件轉(zhuǎn)移指令
指令的匯編格式及功能根據(jù)條件碼的值轉(zhuǎn)移:
ja 大于時(shí)跳轉(zhuǎn)
jae 大于等于
jb 小于
jbe 小于等于
je 相等
jna 不大于
jnae 不大于或者等于
jnb 不小于
jnbe 不小于或等于
jne 不等于
jg 大于(有符號(hào))
jge 大于等于(有符號(hào))
jl 小于(有符號(hào))
jle 小于等于(有符號(hào))
jng 不大于(有符號(hào))
jnge 不大于等于(有符號(hào))
jnl 不小于
jnle 不小于等于
jns 無(wú)符號(hào)
jnz 非零
js 如果帶符號(hào)
jz 如果為零
例子:
mov eax, 1
cmp eax, 100
jle xiao_deng_yu_100
sub eax, 20
xiao_deng_yu_100:
add eax, 1
ret
這個(gè)例子中就是讓eax中存儲(chǔ)的值和100比較,如果小于等于則跳轉(zhuǎn)到xiao_deng_yu_100處。 從這個(gè)例子也可以看出:
-
- 條件轉(zhuǎn)移一般會(huì)和cmp比較指令配合使用,因?yàn)楸容^指令會(huì)改變狀態(tài)寄存器中的標(biāo)志位,而jle等跳轉(zhuǎn)指令回去狀態(tài)寄存器中讀取這些標(biāo)志位 。
- 2.跳轉(zhuǎn)指令不會(huì)返回到原來(lái)的指令地址處,后面講解函數(shù)跳轉(zhuǎn)的時(shí)候可以看到會(huì)返回到原來(lái)的指令地址處,根據(jù)返回地址。
條件跳轉(zhuǎn)指令這么多,怎么記住呢?這里面是有套路的: 首先,跳轉(zhuǎn)指令的前面都是字母j,關(guān)鍵是j后面的的字母,比如j后面是ne,對(duì)應(yīng)的是jne跳轉(zhuǎn)指令,n和e分別對(duì)應(yīng)not和equal,也就是“不相等”,也就是說(shuō)在比較指令的結(jié)果為“不想等”的時(shí)候,就會(huì)跳轉(zhuǎn)。
a: above
e: equal
b: below
n: not
g: greater
l: lower
s: signed
z: zero
好了,這里列出來(lái)了j后面的字母所對(duì)應(yīng)的含義。根據(jù)這些字母的組合,和上述大概的規(guī)則,你就能清楚怎么寫(xiě)出這些跳轉(zhuǎn)指令了。當(dāng)然,這里有“有符號(hào)”和“無(wú)符號(hào)”之分,后面有機(jī)會(huì)再扯,讀者也可以自行了解。
5.堆棧操作指令
我們知道,在一個(gè)函數(shù)作用域中會(huì)使用到一些寄存器,如果在這個(gè)函數(shù)中又調(diào)用了另外一個(gè)函數(shù),那這些寄存器信息可能會(huì)被覆蓋掉,怎么辦呢? 首先CPU會(huì)在內(nèi)存中開(kāi)辟一塊空間,叫棧空間,CPU將這些寄存器壓入到棧中,叫入棧,待另外一個(gè)函數(shù)返回時(shí),再將當(dāng)前棧中的信息恢復(fù)回來(lái),叫出棧。
1.入棧指令push
格式:push src
功能: 把數(shù)據(jù)src壓入到棧中
注意:src可以是寄存器或者內(nèi)存中的數(shù)據(jù)。
2.出棧指令pop
格式:pop dst
功能: 把數(shù)據(jù)彈出到到棧中
注意:dst可以是通用寄存器和段寄存器,但不能是CS,可以是字存儲(chǔ)單元。
6.函數(shù)調(diào)用跳轉(zhuǎn)指令
格式:call 標(biāo)號(hào)
功能:跳轉(zhuǎn)到另外一個(gè)函數(shù)去執(zhí)行。
舉例:
eax_plus_1s:
add eax, 1
ret
main:
mov eax, 0
call eax_plus_1s
ret
這里的eax_plus_1s就是一個(gè)函數(shù),使用call跳轉(zhuǎn)
call方法執(zhí)行之前CPU還會(huì)做一個(gè)動(dòng)作,就是將當(dāng)前eip保存起來(lái),然后再去跳轉(zhuǎn),這是為了函數(shù)執(zhí)行完成后可以找到回來(lái)執(zhí)行的地址。
好了。有了以上基礎(chǔ)后,我們?cè)賮?lái)看C/C++中的hack環(huán)節(jié)。
hack
那我們就拿前面的舉得例子來(lái)講解下hack過(guò)程
1.前增++i和后增i++
我們打開(kāi)vs中輸入下面一段代碼:
int main()
{
int a = 0;
int b = ++a;
return 0;
}
如何反匯編呢, 在main的兩個(gè)括號(hào){}處打兩個(gè)斷點(diǎn),然后執(zhí)行程序,右擊選擇反匯編 。 得到的反匯編代碼如下:
int main()
//段1:初始化
{
001947C0 push ebp
001947C1 mov ebp,esp
001947C3 sub esp,0D8h
001947C9 push ebx
001947CA push esi
001947CB push edi
001947CC lea edi,[ebp-18h]
001947CF mov ecx,6
001947D4 mov eax,0CCCCCCCCh
001947D9 rep stos dword ptr es:[edi]
001947DB mov ecx,offset _BCEF6C65_20221229-demo-cAndCpp@cpp (01A8052h)
001947E0 call @__CheckForDebuggerJustMyCode@4 (0173F1Eh)
//段2:
int a = 0;
001947E5 mov dword ptr [a],0
int b = ++a;
001947EC mov eax,dword ptr [a]
001947EF add eax,1
001947F2 mov dword ptr [a],eax
001947F5 mov ecx,dword ptr [a]
001947F8 mov dword ptr [b],ecx
return 0;
001947FB xor eax,eax
//段3:反初始化
}
001947FD pop edi
001947FE pop esi
001947FF pop ebx
00194800 add esp,0D8h
00194806 cmp ebp,esp
00194808 call __RTC_CheckEsp (0173AC8h)
0019480D mov esp,ebp
0019480F pop ebp
00194810 ret
這段匯編代碼怎么讀呢?由于main方法中沒(méi)有其他函數(shù)調(diào)用,所以CPU會(huì)按順序執(zhí)行這段代碼。 我們將代碼分為3段: 段1:首先來(lái)看前面這段代碼:
001947C0 push ebp
001947C1 mov ebp,esp
001947C3 sub esp,0D8h
001947C9 push ebx
001947CA push esi
001947CB push edi
001947CC lea edi,[ebp-18h]
001947CF mov ecx,6
001947D4 mov eax,0CCCCCCCCh
001947D9 rep stos dword ptr es:[edi]
001947DB mov ecx,offset _BCEF6C65_20221229-demo-cAndCpp@cpp (01A8052h)
001947E0 call @__CheckForDebuggerJustMyCode@4 (0173F1Eh)
這段代碼不用管太多,只是對(duì)函數(shù)棧的一個(gè)初始化的操作,ebp指向棧底,esp指向棧底,還有一些就是當(dāng)前棧的基地址,返回地址等等信息。因?yàn)楹瘮?shù)中可能還會(huì)調(diào)用函數(shù),所以每個(gè)函數(shù)調(diào)用都會(huì)有自己的棧,也叫函數(shù)的棧幀,函數(shù)的深度就代表?xiàng)I?,但是CPU中的寄存器又是共享的,函數(shù)a在使用這些寄存器的時(shí)候,函數(shù)b又要使用了,那怎么辦?方法就是 將寄存器中的數(shù)值壓入棧幀中保存起來(lái),等函數(shù)b結(jié)束后,再?gòu)臈谢謴?fù)起來(lái)就好了 。
下面看重點(diǎn)代碼:
段2:
int a = 0;
001947E5 mov dword ptr [a],0 //1
int b = ++a;
001947EC mov eax,dword ptr [a] //2
001947EF add eax,1 //3
001947F2 mov dword ptr [a],eax //4
001947F5 mov ecx,dword ptr [a] //5
001947F8 mov dword ptr [b],ecx //6
return 0;
001947FB xor eax,eax //7
分析:
- 1.mov指令將0賦值到內(nèi)存中的一個(gè)a變量地址中。
- 2.mov指令將變量a地址中的值賦值給eax,此時(shí)eax變?yōu)?.
- 3.add指令給eax+1,此時(shí)eax變?yōu)榱?.
- 4.mov指令將eax的值傳遞到變量a中,此時(shí)a變?yōu)榱?.
- 5.mov指令將a中的值傳遞給寄存器ecx,此時(shí)ecx值變?yōu)?.
- 6.mov指令將ecx的值傳遞給變量b地址,這樣b就變?yōu)榱?。
以上過(guò)程需要注意點(diǎn):
a的值變化是在b的值變化之前,所以++a是先a+1,然后再將a+1賦值給b。
下面我們?cè)賮?lái)看下這段代碼:
int main()
{
int a = 0;
int b = a++;
return 0;
}
反匯編后:同樣找到關(guān)鍵代碼:
int a = 0;
00AB47E5 mov dword ptr [a],0 //1
int b = a++;
00AB47EC mov eax,dword ptr [a] //2
00AB47EF mov dword ptr [b],eax //3
00AB47F2 mov ecx,dword ptr [a] //4
00AB47F5 add ecx,1 //5
00AB47F8 mov dword ptr [a],ecx //6
return 0;
00AB47FB xor eax,eax
}
分析:
- 1.給a賦值為0
- 2.將a傳遞給eax,此時(shí)eax變?yōu)榱?
- 3.將eax傳遞給b,此時(shí)b變?yōu)榱?
- 4.將a值傳遞給ecx,此時(shí)ecx變?yōu)榱?
- 5.給ecx+1,此時(shí)ecx變?yōu)榱?
- 6.將ecx值傳遞給a,此時(shí)a變?yōu)榱?
這個(gè)過(guò)程可以看到CPU是先賦值給b,然后再讓a進(jìn)行+1的操作。
通過(guò)對(duì)a++和++a的分析,也可以看到hack可以讓我們了解底層是如何執(zhí)行我們寫(xiě)的代碼的。
下面我們?cè)賮?lái)看一個(gè)函數(shù)調(diào)用過(guò)程:
2.函數(shù)體hack過(guò)程
int maxab(int a,int b) {
return a > b ? a : b;
}
int main()
{
maxab(3, 4);
return 0;
}
反匯編后:
maxab(3, 4);
00A347E5 push 4 //1
00A347E7 push 3 //2
00A347E9 call maxab (0A13794h) //3
00A347EE add esp,8 //4
int maxab(int a,int b) {
return a > b ? a : b;
00A32685 mov eax,dword ptr [a] //5
00A32688 cmp eax,dword ptr [b] //6
00A3268B jle __$EncStackInitStart+2Ch (0A32698h) //7
00A3268D mov ecx,dword ptr [a] //8
00A32690 mov dword ptr [ebp-0C4h],ecx //9
00A32696 jmp __$EncStackInitStart+35h (0A326A1h) //10
00A32698 mov edx,dword ptr [b] //11
00A3269B mov dword ptr [ebp-0C4h],edx //12
00A326A1 mov eax,dword ptr [ebp-0C4h] //13
}
分析:
- 1和2處將立即數(shù)4和3壓入到棧內(nèi)。注意他是先push的右邊參數(shù)4,再push的左邊參數(shù)3。
- 3處調(diào)用call跳轉(zhuǎn)到maxab函數(shù)處,注意5處以后這個(gè)時(shí)候是處于另外一個(gè)函數(shù)棧內(nèi)了。
- 5處將變量a的值3傳遞給eax,a的值是怎么得到的呢?看前面壓棧操作是先壓入右邊再壓入左邊,根據(jù)FILO規(guī)則,此時(shí)先找到的參數(shù)就是先出左邊3,再出右邊4.
- 6處讓eax也就是前面的a的值和b的值進(jìn)行比較,比較結(jié)果會(huì)寫(xiě)入到狀態(tài)寄存器中。
- 7處的jle是小余等于就跳轉(zhuǎn),這個(gè)比較結(jié)果是在狀態(tài)寄存器中獲取到的,a是3,b是4,所以結(jié)果就是小余咯。條件成功跳轉(zhuǎn)到指定的0A32698h位置。0A32698h指向11處:00A32698 mov edx,dword ptr [b] //11
- 11處使用mov指令將b中的值傳遞給edx。
- 然后在12處將edx存入到ebp-0C4h位置,ebp是棧底,根據(jù)棧的特點(diǎn),ebp-C4H其實(shí)是一個(gè)往棧頂走的一個(gè)操作,如果你觀察仔細(xì),可以看到其實(shí)就是棧頂。
- 最終在13處將ebp-0C4h處的值傳遞給eax,最終返回eax。 前面也說(shuō)過(guò)所有函數(shù)都使用eax進(jìn)行返回。
上面講解了一個(gè)max函數(shù)的hack過(guò)程,如果你對(duì)hack過(guò)程還比較模糊,還可以使用vs提供的 內(nèi)存監(jiān)視器查看具體內(nèi)存變化 :
好了,關(guān)于函數(shù)的hack分析過(guò)程就這里了,大部分操作其實(shí)還是要理解匯編一些基本指令以及CPU和內(nèi)存,棧的模型等。
總結(jié)
本篇文章主要講解了hack的定義以及hack過(guò)程中需要了解的幾個(gè)基本知識(shí): 如 寄存器,內(nèi)存,CPU以及一些基本匯編指令 。并使用兩個(gè)例子來(lái)講解如何在C/C++中進(jìn)行hack的過(guò)程。
-
C++
+關(guān)注
關(guān)注
21文章
2090瀏覽量
73406 -
匯編代碼
+關(guān)注
關(guān)注
0文章
23瀏覽量
7524 -
hacker
+關(guān)注
關(guān)注
0文章
4瀏覽量
1354
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論