2、條件轉移指令
指令的匯編格式及功能根據條件碼的值轉移:
ja 大于時跳轉
jae 大于等于
jb 小于
jbe 小于等于
je 相等
jna 不大于
jnae 不大于或者等于
jnb 不小于
jnbe 不小于或等于
jne 不等于
jg 大于(有符號)
jge 大于等于(有符號)
jl 小于(有符號)
jle 小于等于(有符號)
jng 不大于(有符號)
jnge 不大于等于(有符號)
jnl 不小于
jnle 不小于等于
jns 無符號
jnz 非零
js 如果帶符號
jz 如果為零
例子:
mov eax, 1
cmp eax, 100
jle xiao_deng_yu_100
sub eax, 20
xiao_deng_yu_100:
add eax, 1
ret
這個例子中就是讓eax中存儲的值和100比較,如果小于等于則跳轉到xiao_deng_yu_100處。 從這個例子也可以看出:
-
- 條件轉移一般會和cmp比較指令配合使用,因為比較指令會改變狀態寄存器中的標志位,而jle等跳轉指令回去狀態寄存器中讀取這些標志位 。
- 2.跳轉指令不會返回到原來的指令地址處,后面講解函數跳轉的時候可以看到會返回到原來的指令地址處,根據返回地址。
條件跳轉指令這么多,怎么記住呢?這里面是有套路的: 首先,跳轉指令的前面都是字母j,關鍵是j后面的的字母,比如j后面是ne,對應的是jne跳轉指令,n和e分別對應not和equal,也就是“不相等”,也就是說在比較指令的結果為“不想等”的時候,就會跳轉。
a: above
e: equal
b: below
n: not
g: greater
l: lower
s: signed
z: zero
好了,這里列出來了j后面的字母所對應的含義。根據這些字母的組合,和上述大概的規則,你就能清楚怎么寫出這些跳轉指令了。當然,這里有“有符號”和“無符號”之分,后面有機會再扯,讀者也可以自行了解。
5.堆棧操作指令
我們知道,在一個函數作用域中會使用到一些寄存器,如果在這個函數中又調用了另外一個函數,那這些寄存器信息可能會被覆蓋掉,怎么辦呢? 首先CPU會在內存中開辟一塊空間,叫棧空間,CPU將這些寄存器壓入到棧中,叫入棧,待另外一個函數返回時,再將當前棧中的信息恢復回來,叫出棧。
1.入棧指令push
格式:push src
功能: 把數據src壓入到棧中
注意:src可以是寄存器或者內存中的數據。
2.出棧指令pop
格式:pop dst
功能: 把數據彈出到到棧中
注意:dst可以是通用寄存器和段寄存器,但不能是CS,可以是字存儲單元。
6.函數調用跳轉指令
格式:call 標號
功能:跳轉到另外一個函數去執行。
舉例:
eax_plus_1s:
add eax, 1
ret
main:
mov eax, 0
call eax_plus_1s
ret
這里的eax_plus_1s就是一個函數,使用call跳轉
call方法執行之前CPU還會做一個動作,就是將當前eip保存起來,然后再去跳轉,這是為了函數執行完成后可以找到回來執行的地址。
好了。有了以上基礎后,我們再來看C/C++中的hack環節。
hack
那我們就拿前面的舉得例子來講解下hack過程
1.前增++i和后增i++
我們打開vs中輸入下面一段代碼:
int main()
{
int a = 0;
int b = ++a;
return 0;
}
如何反匯編呢, 在main的兩個括號{}處打兩個斷點,然后執行程序,右擊選擇反匯編 。 得到的反匯編代碼如下:
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方法中沒有其他函數調用,所以CPU會按順序執行這段代碼。 我們將代碼分為3段: 段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)
這段代碼不用管太多,只是對函數棧的一個初始化的操作,ebp指向棧底,esp指向棧底,還有一些就是當前棧的基地址,返回地址等等信息。因為函數中可能還會調用函數,所以每個函數調用都會有自己的棧,也叫函數的棧幀,函數的深度就代表棧深,但是CPU中的寄存器又是共享的,函數a在使用這些寄存器的時候,函數b又要使用了,那怎么辦?方法就是 將寄存器中的數值壓入棧幀中保存起來,等函數b結束后,再從棧幀中恢復起來就好了 。
下面看重點代碼:
段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賦值到內存中的一個a變量地址中。
- 2.mov指令將變量a地址中的值賦值給eax,此時eax變為0.
- 3.add指令給eax+1,此時eax變為了1.
- 4.mov指令將eax的值傳遞到變量a中,此時a變為了1.
- 5.mov指令將a中的值傳遞給寄存器ecx,此時ecx值變為1.
- 6.mov指令將ecx的值傳遞給變量b地址,這樣b就變為了1。
以上過程需要注意點:
a的值變化是在b的值變化之前,所以++a是先a+1,然后再將a+1賦值給b。
下面我們再來看下這段代碼:
int main()
{
int a = 0;
int b = a++;
return 0;
}
反匯編后:同樣找到關鍵代碼:
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,此時eax變為了0
- 3.將eax傳遞給b,此時b變為了0
- 4.將a值傳遞給ecx,此時ecx變為了0
- 5.給ecx+1,此時ecx變為了1
- 6.將ecx值傳遞給a,此時a變為了1
這個過程可以看到CPU是先賦值給b,然后再讓a進行+1的操作。
通過對a++和++a的分析,也可以看到hack可以讓我們了解底層是如何執行我們寫的代碼的。
下面我們再來看一個函數調用過程:
2.函數體hack過程
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處將立即數4和3壓入到棧內。注意他是先push的右邊參數4,再push的左邊參數3。
- 3處調用call跳轉到maxab函數處,注意5處以后這個時候是處于另外一個函數棧內了。
- 5處將變量a的值3傳遞給eax,a的值是怎么得到的呢?看前面壓棧操作是先壓入右邊再壓入左邊,根據FILO規則,此時先找到的參數就是先出左邊3,再出右邊4.
- 6處讓eax也就是前面的a的值和b的值進行比較,比較結果會寫入到狀態寄存器中。
- 7處的jle是小余等于就跳轉,這個比較結果是在狀態寄存器中獲取到的,a是3,b是4,所以結果就是小余咯。條件成功跳轉到指定的0A32698h位置。0A32698h指向11處:00A32698 mov edx,dword ptr [b] //11
- 11處使用mov指令將b中的值傳遞給edx。
- 然后在12處將edx存入到ebp-0C4h位置,ebp是棧底,根據棧的特點,ebp-C4H其實是一個往棧頂走的一個操作,如果你觀察仔細,可以看到其實就是棧頂。
- 最終在13處將ebp-0C4h處的值傳遞給eax,最終返回eax。 前面也說過所有函數都使用eax進行返回。
上面講解了一個max函數的hack過程,如果你對hack過程還比較模糊,還可以使用vs提供的 內存監視器查看具體內存變化 :
好了,關于函數的hack分析過程就這里了,大部分操作其實還是要理解匯編一些基本指令以及CPU和內存,棧的模型等。
總結
本篇文章主要講解了hack的定義以及hack過程中需要了解的幾個基本知識: 如 寄存器,內存,CPU以及一些基本匯編指令 。并使用兩個例子來講解如何在C/C++中進行hack的過程。
-
C++
+關注
關注
22文章
2108瀏覽量
73632 -
匯編代碼
+關注
關注
0文章
23瀏覽量
7550 -
hacker
+關注
關注
0文章
4瀏覽量
1362
發布評論請先 登錄
相關推薦
評論