如何获得C语言函数起始地址和返回地址

在反外挂系统中,经常会检测函数的返回地址,确认函数的返回地址在规定的范围之内,从而保证,游戏程序中的函数,不被外挂所调用。这种检查方式就涉及到一个基本的技术问题,如何获得函数的返回地址?

例如下面的第一段代码:

#include<stdio.h>
int main()
{
   getchar();
   return 0;
}

非常简单的一段程序,那么我们如何获得该函数的起始地址和返回地址呢?起始地址获取非常容易,如下:

#include<stdio.h>
int main()
{
   printf("%0x\n",main);
   getchar();
   return 0;
}

那么如何获得函数的返回地址呢?这个就相对来说比较困难。我们先看第一段代码反汇编后的结果:

#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的内容,然后将ESP的内容写入EBP:

009919E0  push       ebp

009919E1  mov        ebp,esp

汇编指令call会做两件事情,其一,将call指令后面的一条指令的地址压入栈中,无条件跳转到call指令的调用地指处,开始执行子程序。

和call指令对应的ret指令,则开始执行call指令后面的一条指令。

那么,ret指令如何知道call指令后面一条指令的地址呢?因为call指令已经将这条指令压入到了栈中,所以ret指令可以找到call指令后的一条指令的地址。

既然ret指令可以找到call的返回地址,也就是call的下一条指令的地址,那么我们也可以找到!!!

main函数在执行前以及执行过程中,栈的分布如下:

通过以上几张图,我们可以清楚的看到,main函数的返回地址在[EBP+4]处。所以,获得main函数的返回地址的代码如下:

#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()函数调用了main函数,调用的汇编代码如下:

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

说明我们获得的结果正确。

对于其他函数的情况类似,下面笔者就把获得某个函数的返回值得功能,做成一个函数,提供给大家,如下:

#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函数中,为什么会多几条汇编指令呢?大家可以自行思考。

时间: 2024-10-20 21:52:17

如何获得C语言函数起始地址和返回地址的相关文章

工具函数(代码块的大小,代码块起始地址,提升进程权限)

一些在编程中经常要用到的功能编写成函数,方便使用. [cpp] view plain copy #include <windows.h> //系统类型 typedef enum SystemType { WINDOWS_2000 = 1, //5.0 WINDOWS_XP, //5.1 WINDOWS__SERVER_2003, //5.2 WINDOWS__SERVER_2003_R2, //5.2 WINDOWS_VISTA, //6.0 WINDOWS__SERVER_2008, //6

gdb调试实战——调试可执行程序,计算缓冲区起始地址与函数foo返回地址的距离

缓冲区的起始地址,即变量buffer的起始地址是:0xbfffefd0 函数foo返回地址是:0xbfffeffc 因此缓冲区起始地址与函数foo返回地址的距离是: 0xbfffeffc -0xbfffefd0 =1c =28字节   下面是完整的调试过程: 从这里可以看到Lbuffer变量的值

一起talk C栗子吧(第一百二十三回:C语言实例--显示变量和函数的地址)

各位看官们,大家好,上一回中咱们说的是多线程的样例.这一回咱们说的样例是:显示变量和函数的地址. 闲话休提,言归正转.让我们一起talk C栗子吧! 在编敲代码时,有时候须要获取程序中变量和函数的地址.今天我们就来介绍下怎样获取它们的地址. 获取变量的地址 获取变量的地址时,仅仅须要使用取地址符对变量直接操作就能够.比如:int a = 1;那么变量a的地址就是&a.该方法适用于大部分变量,只是另一些特殊的变量不能使用该方法.接下来我们分别介绍怎样获取这些特殊变量的地址. 获取数组变量的地址 数

05. Go 语言函数

Go 语言函数 函数是组织好的.可重复使用的.用来实现单一或相关联功能的代码段,其可以提高应用的模块性和代码的重复利用率. Go 语言支持普通函数.匿名函数和闭包,从设计上对函数进行了优化和改进,让函数使用起来更加方便. Go 语言的函数属于"一等公民"(first-class),也就是说: 函数本身可以作为值进行传递. 支持匿名函数和闭包(closure). 函数可以满足接口. Go语言函数声明(函数定义) 函数构成了代码执行的逻辑结构,在Go语言中,函数的基本组成为:关键字 fun

C语言函数、函数指针解析

函数.函数指针的理解: 函数的定义: void myfunc(void) { } 函数的声明 void myfunc(void); 函数指针的定义.初始化.赋值: 定义:void (*funcp)(void); 初始化: void (*funcp)(void) = &myfunc; 赋值 void (*funcp)(void); funcp = &myfunc; 函数调用:(*funcp)(); funcp(); 也可以这样赋值:void (*funcp)(void); funcp = m

深入理解C语言函数指针(转)

本文转自:http://www.cnblogs.com/windlaughing/archive/2013/04/10/3012012.html 示例1: void myFun(int x); //声明也可写成:void myFun( int ); int main() { myFun(100);//一般的函数调用 return 0; } void myFun(int x) { printf("myFun: %d\n",x); } 我们一开始只是从功能上或者说从数学意义上理解myFun

C语言 函数指针定义三种方式

//函数指针 #include<stdio.h> #include<stdlib.h> #include<string.h> //函数指针类型跟数组类型非常相似 //函数名就是函数的地址,函数的指针,对函数名进行&取地址操作,还是函数名本身,这是C语言编译器的特殊处理 void test(int a){ printf("a=%d\n",a); } void ProtectA(){ //定义函数类型 typedef void(FunType)(

汇编1 ----C语言函数1

构造以下C程序并在合适位置插入breakpoints 在Visual Studio 2015 CTP6对其反汇编. 下面来分析 z = add(1, 2); 009C170E 6A 02 push 2 ????int z; ????z = add(1, 2); 009C1710 6A 01 push 1 009C1712 E8 8D FA FF FF call 009C11A4 009C1717 83 C4 08 add esp,8 009C171A 89 45 F8 mov dword ptr

(转)如何编写有多个返回值的C语言函数

1引言    笔者从事C语言教学多年,在教学中学生们常常会问到如何编写具有多个返回值的C语言函数.编写有多个返回值的函数是所有C语言教材里均没有提到的知识点,但在实际教学与应用的过程中我们都有可能会遇到这样的问题.有学生也尝试了不少方法:如把多个需要返回的值作相应的处理后变成一个可以用return语句返回的数据,再在主调函数中拆开返回的数据使之变成几个值:或者把需要返回多个值的一个函数分开几个函数去实现多个值的返回.这些方法虽然最终都能实现返回要求的多个值,但从程序算法的合理性与最优化方面去考虑