本文在amd64平台使用以下编译器进行分析
gcc version 9.3.0 (Ubuntu 9.3.0-10ubuntu2)
在c中,定义一个函数,往往是确定的参数类型和个数,并返回一个确定的类型 但每个程序员都写过的 printf(“hello world”); 却调用了一个非常异端的函数
int printf(const char* format, ...); printf作为一个格式化字符串函数,除了接收一个format串,还可以接收任意数量的任意类型的参数用来格式化,是个非常典型的变参函数
type VarArgFunc(type FixedArg1, type FixedArg2,...); 要了解格式化字符串的工作原理,绕不开变参这个点,而这也是格式化字符串漏洞的核心机制
如何在c中定义一个像printf一样的变参函数:
#include <stdarg.h>int VarArgFunc(int dwFixedArg, ...){ va_list pArgs = NULL; va_start(pArgs, dwFixedArg); int dwVarArg = va_arg(pArgs, int); va_end(pArgs); } //可在头文件中声明函数为extern int VarArgFunc(int dwFixedArg, ...);,调用时用VarArgFunc(FixedArg, VarArg); 其实熟悉c函数调用约定(Calling convention)的话我们不难知道,函数的参数传递是约定好的,因此即使我们不定义显式定义一个经过编译器检查分配的形参,对应的寄存器和栈空间仍然可以被视作实参 首先以i386下__cdecl的典型栈传参为例,所有的参数按照从右到左的顺序依次压入栈,返回值在EAX中 并且由调用者清理栈
register int i=1; int j=2; calc(i,j,3); 主调函数(caller)
push EAX ; 压入寄存器中的值 push byte[EBP+20] ; 压入栈上局部变量的值 (FASM/TASM syntax) push 3 ; 压入立即数 call calc ; 被调函数(callee)...