可領全套安全課程、配套攻防靶場
![運用調用棧對軟件爆破,以後不用總是搜索字符串了](http://p2.ttnews.xyz/loading.gif)
棧
棧是操作系統在運行時自動初始化的一塊區域,是數據暫時存儲的的動態內存區域。
它的大小在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定位至調用語段:
![運用調用棧對軟件爆破,以後不用總是搜索字符串了](http://p2.ttnews.xyz/loading.gif)
簡單分析一下,易得:
<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
今天你知道了嗎?
加群,黑客技術大咖在線解答(群號評論區見)
閱讀更多 暗網視界 的文章