栈缠绕与万能函数

很早之前就知道栈缠绕的原理,很早之前就想要个万能函数,可以调用任意的函数,感觉很酷。在研究模拟java的动态load的时候,曾经做了个类似的,结果要求限制的挺多,最后自己只记得原理,代码早不知飞哪儿了。

前几天突然想,是否通过栈缠绕可以实现?然后做下实验,虽然碰了些疙瘩,但还算顺利,贴出成果:

 #include "stdio.h"
#include "stdlib.h"
#include "stdarg.h"
void func_1(int a, int b)
{
        printf("call func_1, a=%d,b=%d\n", a, b);
}
int func_2()
{
        printf("call func_2\n");
        return 1;
}

void func_3(const char* fmt, ...)
{
	char temp[255] = {0};
        va_list args;        
        va_start(args, fmt);
        _vsnprintf(temp, sizeof(temp) - 1, fmt, args);
        va_end(args);
		printf(temp);
}

typedef int (*func_ptr)(...);
int call_fun(func_ptr func, ...);
#define PUSH __asm push 0
int call_fun(func_ptr func, ...)
{
		*((int*)(&func)-1) = (*((int*)(&func)-1))^(*((int*)(&func)));
		*((int*)(&func)) = (*((int*)(&func)-1))^(*((int*)(&func)));
		*((int*)(&func)-1) = (*((int*)(&func)-1))^(*((int*)(&func)));
		return (int)func;
}

#define START_CALL PUSH

#define END_CALL

int main (int argc, char * argv[]) 
{
		START_CALL;
        call_fun((func_ptr)func_1, 1, 2);
		END_CALL;
		START_CALL;
       printf("func_2 return=%d\n", call_fun((func_ptr)func_2));
	   END_CALL;
	   START_CALL;
       call_fun((func_ptr)func_3, "printf this:%d, %s\n", 1, "ok");
	   END_CALL;
        return 0;
}
 

基本的内容就是上面的内容。下面是原理:

任意一个函数都可以通过call_fun来进行调用,只要知道函数指针,便可以进行调用。可以看到call_fun的声明,是一个不定参数的形式。

对于函数指针,也声明为不定参数,方便理解和转换。

最核心的内容分为两部分,一部分是实现,一部分是平衡。实现就是call_fun的函数实现。

对于小段对齐的C调用来讲,在内存中,call_fun函数的调用栈的内存图如下:

param2 参数2
param1 参数1
func 被调用的函数地址,参数0
ret 函数的返回地址

对于call_func函数的内部实现来讲,其实现的机制就是将ret和func在内存地址中的内容交换了一下,这一交换让程序表现的类似下面的关系:

call_func(fun_ptr func,...)
{
func(__VA_ARGS);
}

与上述不同的是,当func函数返回的时候,返回的地址是在调用call_func之后的,就像call_func调用返回一样,因此,call_func返回值是无任何作用的,只是用来欺骗编译器而已。

但这样一来,相当于在call_func结尾的时候自动执行了一次call调用,在函数返回的时候,栈指针POP了两次,而call_func函数的调用者并不知道这一情况,因此栈出现了不平衡现象。如下代码:

push param2
push param1
push func
call call_func
add esp, 3*4

在调用完call_func之后,add esp,2*4就可以了,但编译器自动生成的代码是add esp, 3*4,这样栈就不平衡了,我们需要手动平衡这一内容。这一部分由START_CALL宏负责。

START_CALL宏很简单,最终的结果是一条PUSH语句。由于我们手动的添加了一次PUSH,而编译器自动生成的代码对于我们的操作又多了一次POP,因此天下太平,栈平衡了。

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据