【博文推薦】如何獲得C語言函數(shù)起始地址和返回地址
本博文出自51CTO博客gmxydm 博主,有任何問題請進入博主頁面互動討論! 博文地址:http://5412097.blog.51cto.com/5402097/1641374 |
在反外掛系統(tǒng)中,經(jīng)常會檢測函數(shù)的返回地址,確認函數(shù)的返回地址在規(guī)定的范圍之內(nèi),從而保證,游戲程序中的函數(shù),不被外掛所調(diào)用。這種檢查方式就涉及到一個基本的技術(shù)問題,如何獲得函數(shù)的返回地址?
例如下面的***段代碼:
- #include<stdio.h>
- int main()
- {
- getchar();
- return 0;
- }
非常簡單的一段程序,那么我們?nèi)绾潍@得該函數(shù)的起始地址和返回地址呢?起始地址獲取非常容易,如下:
- #include<stdio.h>
- int main()
- {
- printf("%0x\n",main);
- getchar();
- return 0;
- }
那么如何獲得函數(shù)的返回地址呢?這個就相對來說比較困難。我們先看***段代碼反匯編后的結(jié)果:
- #include<stdio.h>
- intmain()
- {
- 009919E0 push ebp
- 009919E1 mov ebp,esp
- 009919E3 sub esp,0C0h
- 009919E9 push ebx
- 009919EA push esi
- 009919EB push edi
- 009919EC lea edi,[ebp-0C0h]
- 009919F2 mov ecx,30h
- 009919F7 mov eax,0CCCCCCCCh
- 009919FC rep stos dword ptr es:[edi]
- getchar();
- 009919FE mov esi,esp
- 00991A00 call dword ptr [__imp__getchar (9982B0h)]
- 00991A06 cmp esi,esp
- 00991A08 call @ILT+295(__RTC_CheckEsp) (99112Ch)
- return 0;
- 00991A0D xor eax,eax
- }
- 00991A0F pop edi
- 00991A10 pop esi
- 00991A11 pop ebx
- 00991A12 add esp,0C0h
- 00991A18 cmp ebp,esp
- 00991A1A call @ILT+295(__RTC_CheckEsp) (99112Ch)
- 00991A1F mov esp,ebp
- 00991A21 pop ebp
- 00991A22 ret
代碼開始部分,先保存ebp的內(nèi)容,然后將ESP的內(nèi)容寫入EBP:
- 009919E0 push ebp
- 009919E1 mov ebp,esp
匯編指令call會做兩件事情,其一,將call指令后面的一條指令的地址壓入棧中,無條件跳轉(zhuǎn)到call指令的調(diào)用地指處,開始執(zhí)行子程序。
和call指令對應(yīng)的ret指令,則開始執(zhí)行call指令后面的一條指令。
那么,ret指令如何知道call指令后面一條指令的地址呢?因為call指令已經(jīng)將這條指令壓入到了棧中,所以ret指令可以找到call指令后的一條指令的地址。
既然ret指令可以找到call的返回地址,也就是call的下一條指令的地址,那么我們也可以找到?。。?/p>
main函數(shù)在執(zhí)行前以及執(zhí)行過程中,棧的分布如下:
通過以上幾張圖,我們可以清楚的看到,main函數(shù)的返回地址在[EBP+4]處。所以,獲得main函數(shù)的返回地址的代碼如下:
- #include<stdio.h>
- int main()
- {
- int re_addr;
- __asm
- {
- mov eax,dword ptr [ebp+4]
- mov re_addr,eax
- }
- printf("%0X\n",re_addr);
- getchar();
- return 0;
- }
其中__tmainCRTStartup()函數(shù)調(diào)用了main函數(shù),調(diào)用的匯編代碼如下:
mainret = main(argc, argv, envp);
00B81926 mov eax,dword ptr [envp (0B87140h)]
00B8192B push eax
00B8192C mov ecx,dword ptr [argv (0B87144h)]
00B81932 push ecx
00B81933 mov edx,dword ptr [argc (0B8713Ch)]
00B81939 push edx
00B8193A call @ILT+300(_main)(0B81131h)
00B8193F add esp,0Ch
00B81942 mov dword ptr [mainret (0B87154h)],eax
可以看出,call main指令后的一條指令的地址為:00B8193F,而我們獲得的main的返回地址如下:
B8193F
說明我們獲得的結(jié)果正確。
對于其他函數(shù)的情況類似,下面筆者就把獲得某個函數(shù)的返回值得功能,做成一個函數(shù),提供給大家,如下:
- #include<stdio.h>
- int Get_return_addr()
- {
- int re_addr;
- __asm
- {
- mov eax,dword ptr [ebp]
- mov ebx,dword ptr [eax+4]
- mov re_addr,ebx
- }
- returnre_addr;
- }
- int main()
- {
- intre_addr=Get_return_addr();
- printf("%0X\n",re_addr);
- getchar();
- return 0;
- }
Get_return_add函數(shù)中,為什么會多幾條匯編指令呢?大家可以自行思考。