查看原文
其他

逆向工程之函数识别

计算机与网络安全 计算机与网络安全 2022-06-01

一次性进群,长期免费索取教程,没有付费教程。

教程列表见微信公众号底部菜单

进微信群回复公众号:微信群;QQ群:460500587


微信公众号:计算机与网络安全

ID:Computer-network

函数是高级语言中的一种代码封装方法,它把一段可以被反复调用的代码封装起来,以避免写程序时的重复劳动。函数在逻辑上具有相对独立的内存环境,一个函数既可以接受多个参数也可以不接受任何参数,既可以返回执行结果也可以不返回任何结果,但是它必须保证在执行完毕后能返回到调用代码的正确位置上继续执行。


因此从逆向工程的角度来看,函数要想在执行完毕后能返回到正确的位置并继续执行其他代码,就要求主程序在调用函数时应首先保存返回地址并传递给函数,其次要考虑的就是参数传入与栈平衡问题(也可以理解为参数与局部变量的保存问题)。


对于堆栈平衡问题我们可以以一个假设来更加形象地认识它。

我们知道函数的参数与局部变量都是保存在栈中的,一般情况下使用栈寄存器ESP加上偏移即可访问到我们需要的数据。但是如果此时我们调用了一个函数,它很有可能会拥有自己的参数与局部变量,因此免不了要对堆栈进行一定的操作,如果此时不加任何处理就返回到主程序中,就会引起ESP的混乱,导致主程序无法正确访问自己的参数与局部变量。


对于这种情况,我们必须要进行处理,但是究竟应该由谁来处理呢?是调用者还是被调用的函数本身?很显然我们需要一个约定。


正是因为有了这个问题,才诞生了4个函数调用约定,不但规定了堆栈由谁来清理,还规定了调用函数时参数的入栈顺序。这4个约定分别是C规范约定、Pascal规范约定、快速调用约定与标准调用规范约定。具体调用约定内容如表1所示。

以下代码演示了其中3种调用约定,具体调用方式的示例如代码清单1所示。

代码清单1  调用方式示例

这是一个非常简单的小程序,主函数使用printf打印出了3个函数的返回结果,这3个函数则分别使用了3个不同的调用约定。我们一起看看Debug版的OllyDBG反汇编代码,如代码清单2所示。

代码清单2  Debug版的反汇编代码示例

通过代码清单2的示例我们看到,采用不同的调用方式,其反汇编代码不同。但是它们也有一些相同点,以下是就这些函数的相同点做的一个总结。


几乎全部函数调用方式都会用栈来传递参数,只有使用快速调用约定后且参数少于等于2时才会全部采用寄存器传参。


每个函数的起始部分几乎都是一样的,都是以push ebp与mov ebp,esp这两条汇编指令开始的。


很显然,每个函数都是由call指令调用的,且以retn指令结尾的。


这些特征为我们日后快速识别定位函数提供了很好的参照。但是要注意,并不是所有的函数都只有用call指令才能调用,使用lea、push加jmp的组合可以达到相同的目的。例如我们可以将call Demo.013A1127转换为以下形式:

除此之外,函数的特征也是可以被人为控制的。Visual C++为用户提供了一个naked标识,可以用来创建裸函数,使用naked标识创建的裸函数将不包含任何用户代码以外的指令,即便是函数末尾的retn也要用户自己来实现。代码清单3就是一个使用裸函数的例子。

代码清单3  使用裸函数的例子

由此看来,并不是所有的特征都是一成不变的,但是以上特殊情况在正规环境下很少碰到,除非是程序作者有意想混淆自己的代码。

微信公众号:计算机与网络安全

ID:Computer-network

【推荐书籍】

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存