運用調用棧對軟件爆破,以後不用總是搜索字符串了

可領全套安全課程、配套攻防靶場


運用調用棧對軟件爆破,以後不用總是搜索字符串了



棧是操作系統在運行時自動初始化的一塊區域,是數據暫時存儲的的動態內存區域。


它的大小在Windows操作系統下由PE文件結構中PE文件頭中IMAGE_OPTIONAL_HEADER結構中SizeOfStackReserve字段所定義。


在OD中,要想順利分析軟件的功能,在單步需要關注的便是寄存器和棧空間。下面,我們來看一下軟件是怎麼利用棧的。


函數的調用過程


我們用一個充滿函數的小軟件來研究函數的調用,源代碼如下:

<code>#include<iostream>#include<cstdio>#include<cstdlib>#include<windows.h>using namespace std;int function(int a,int b);//這是第一個函數,揭示控制檯應用程序函數調用過程int WINAPI winfunction(int a,int b);//winapi調用方式int _cdecl cfunction(int a,int b);//c函數方式int _stdcall cppfunction(int a,int b);//c++函數方式int _fastcall ffunction(int a,int b);//寄存器函數方式int _stdcall stdfunction(int a,int b);//其實就是WINAPI調用方式int myfunction(int a,int b);//遞歸調用演示int main(){  int a;int b;  printf("定位彙編代碼");  scanf("%d",&a);  scanf("%d",&b);//scanf方便找彙編代碼  winfunction(a,b);  cfunction(a,b);  cppfunction(a,b);  ffunction(a,b);  stdfunction(a,b);  myfunction(a,b);  return 0;}int function(int a,int b){  int c = a + b;//加法運算最簡單  return c;}int WINAPI winfunction(int a,int b){  int c = a + b;//加法運算最簡單  return c;}int _cdecl cfunction(int a,int b){  int c = a + b;//加法運算最簡單  return c;}int _stdcall cppfunction(int a,int b){  int c = a + b;//加法運算最簡單  return c;}int _fastcall ffunction(int a,int b){  int c = a + b;//加法運算最簡單  return c;}int _stdcall stdfunction(int a,int b){  int c = a + b;//加法運算最簡單  return c;}int myfunction(int a,int b){   a++;  b++;  myfunction(a,b);}/<windows.h>/<cstdlib>/<cstdio>/<iostream>/<code> 


將編譯出的軟件用OD載入,通過API定位至調用語段:

運用調用棧對軟件爆破,以後不用總是搜索字符串了

簡單分析一下,易得:

<code>0040159A  8B55 F0   mov edx,dword ptr ss:[ebp-0x10]  ; 將scanf的結果放入EDX(b)0040159D  8B45 F4   mov eax,dword ptr ss:[ebp-0xC]   ; 將scanf的結果放入EAX(a)004015A0  895424 04   mov dword ptr ss:[esp+0x4],edx   ; 將EDX內容壓入棧,就是我輸入的b(=2)004015A4  890424    mov dword ptr ss:[esp],eax   ; 將EAX內容壓入棧,就是我輸入的a(=1)004015A7  E8 81000000 call 未命名1.0040162D       ; WINAPI調用方式004015AC  83EC 08   sub esp,0x8        ; 恢復堆棧004015AF  8B55 F0   mov edx,dword ptr ss:[ebp-0x10]  ; 將scanf的結果放入EDX(b)004015B2  8B45 F4   mov eax,dword ptr ss:[ebp-0xC]   ; 將scanf的結果放入EAX(a)004015B5  895424 04   mov dword ptr ss:[esp+0x4],edx   ; 將EDX內容壓入棧,就是我輸入的b(=2)004015B9  890424    mov dword ptr ss:[esp],eax   ; 將EAX內容壓入棧,就是我輸入的a(=1)004015BC  E8 84000000 call 未命名1.00401645       ; c函數方式004015C1  8B55 F0   mov edx,dword ptr ss:[ebp-0x10]  ; 將scanf的結果放入EDX(b)004015C4  8B45 F4   mov eax,dword ptr ss:[ebp-0xC]   ; 將scanf的結果放入EAX(a)004015C7  895424 04   mov dword ptr ss:[esp+0x4],edx   ; 將EDX內容壓入棧,就是我輸入的b(=2)004015CB  890424    mov dword ptr ss:[esp],eax   ; 將EAX內容壓入棧,就是我輸入的a(=1)004015CE  E8 88000000 call 未命名1.0040165B       ; c++函數方式004015D3  83EC 08   sub esp,0x8        ; 恢復堆棧004015D6  8B55 F0   mov edx,dword ptr ss:[ebp-0x10]  ; 將scanf的結果放入EDX(b)004015D9  8B45 F4   mov eax,dword ptr ss:[ebp-0xC]   ; 將scanf的結果放入EAX(a)004015DC  89C1    mov ecx,eax        ; 將EAX寄存器的值放入ECX004015DE  E8 90000000 call 未命名1.00401673       ; 寄存器方式004015E3  8B55 F0   mov edx,dword ptr ss:[ebp-0x10]  ; 將scanf的結果放入EDX(b)004015E6  8B45 F4   mov eax,dword ptr ss:[ebp-0xC]   ; 將scanf的結果放入EAX(a)004015E9  895424 04   mov dword ptr ss:[esp+0x4],edx   ; 將EDX內容壓入棧,就是我輸入的b(=2)004015ED  890424    mov dword ptr ss:[esp],eax   ; 將EAX內容壓入棧,就是我輸入的a(=1)004015F0  E8 9A000000 call 未命名1.0040168F       ; stdcall調用方式004015F5  83EC 08   sub esp,0x8        ; 恢復堆棧004015F8  8B55 F0   mov edx,dword ptr ss:[ebp-0x10]  ; 將scanf的結果放入EDX(b)004015FB  8B45 F4   mov eax,dword ptr ss:[ebp-0xC]   ; 將scanf的結果放入EAX(a)004015FE  895424 04   mov dword ptr ss:[esp+0x4],edx   ; 將EDX內容壓入棧,就是我輸入的b(=2)00401602  890424    mov dword ptr ss:[esp],eax   ; 將EAX內容壓入棧,就是我輸入的a(=1)00401605  E8 9D000000 call 未命名1.004016A7       ; 遞歸調用/<code>


然後我們隨意跟進一個函數,如圖(我選擇的是C函數方式)

運用調用棧對軟件爆破,以後不用總是搜索字符串了

運用調用棧對軟件爆破,以後不用總是搜索字符串了


簡單分析,易得:

<code>00401645  55    push ebp         ; ebp入棧保護現場00401646  89E5    mov ebp,esp        ; 保存棧指針00401648  83EC 10   sub esp,0x10         ; 設置棧指針0040164B  8B55 08   mov edx,dword ptr ss:[ebp+0x8]   ; 從棧中讀入edx(a)0040164E  8B45 0C   mov eax,dword ptr ss:[ebp+0xC]   ; 從棧中讀入eax(b)00401651  01D0    add eax,edx        ; 將eax和ebx相加,讀入eax00401653  8945 FC   mov dword ptr ss:[ebp-0x4],eax   ; 保存eax的值入棧準備引用(c)00401656  8B45 FC   mov eax,dword ptr ss:[ebp-0x4]   ; 將c作為返回值放入eax00401659  C9    leave          ; 恢復棧0040165A  C3    retn           ; 函數返回/<code>

綜上所述,我們可以得到以下幾點:1.大多數調用協定都將棧作為參數傳遞的途徑2.在進入一個函數時執行call指令就是做了兩步:(1)將下一行指令地址壓入棧(2)跳轉到call後的地址3.將eax作為返回值4.return指令實際做的事:(1)跳轉到棧頂的那個地址(2)棧頂中的地址出棧

所以,函數為了返回必定要用棧,分析棧也就可以知道函數是從哪調用的。


<strong>正式開始


調用棧


棧不止可以用於函數調用,臨時數據的存儲都是用棧,但是,棧中為了函數調用的部分稱為調用棧,用調用棧可以分析函數是從哪裡調用的。


OD對調用棧的支持


快捷鍵(Alt+K)如圖便是OD的調用棧窗口

運用調用棧對軟件爆破,以後不用總是搜索字符串了

第一行是Main函數里的函數第二行是Main函數


調用棧找地址法原理


通過調用棧,我們可以知道函數是從哪裡調用的,便可以知道是哪裡觸發了函數,便可輕易找到是哪裡判斷了註冊碼是正確還是錯誤的。


實戰調用棧法


我又寫了一個小程序,源碼如下(驗證部分和我上次發帖的一樣):

<code>#include<iostream>#include<cstdio>#include<cstdlib>#include<string>#include<windows.h>using namespace std;int check(string username,string sn);//檢測註冊碼正確性int registe();//註冊界面int help();//幫助界面int main(){  cout<>choose;  if(choose==1){      registe();  }   else if(choose==2){      help();  }  else{      cout<>username;  cout<>sn;  check(username,sn);  system("pause");  return 0;}int check(string user,string sn){  for(int i=0;i<user.length>/<windows.h>/<string>/<cstdlib>/<cstdio>/<iostream>/<code>

OD載入,運行

運用調用棧對軟件爆破,以後不用總是搜索字符串了

在彈出錯誤框後,暫停,打開調用棧

運用調用棧對軟件爆破,以後不用總是搜索字符串了

運用調用棧對軟件爆破,以後不用總是搜索字符串了


可以看出,從我的exe調用的而非系統dll調用的最上層的調用來自:未命名1.004018B2,其中未命名1為我的程序名。進入004018B2

運用調用棧對軟件爆破,以後不用總是搜索字符串了

看到上面將序列號錯誤壓入了棧,所以我們判斷這個call顯示了這個彈窗


所以我們想要跳過這個call,而它上面就有一個je跳轉,我們改為jmp看一下。

運用調用棧對軟件爆破,以後不用總是搜索字符串了


至此,軟件爆破完成!感興趣的同學可以嘗試分析一下剩餘的語句的功能,對自己的能力提升是有幫助的。


結語


調用棧破解法很常用,學會了它,你就不用總是搜索字符串了。


現在,自己找個CrackMe或編譯我提供的源代碼試一試吧,祝你好運!


轉載自:https://mp.weixin.qq.com/s/OLUaqKJ4j_t8ydHhzjvJBw


今天你知道了嗎?

運用調用棧對軟件爆破,以後不用總是搜索字符串了

加群,黑客技術大咖在線解答(群號評論區見)


分享到:


相關文章: