目录
实验目的
格式化字符串漏洞是一个很古老的漏洞了,现在几乎已经见不到这类漏洞的身影,但是作为漏洞分析的初学者来说,还是很有必要研究一下的,因为这是基础啊!!!所以就有了这个实验了。我实验环境都搭好了,就差你来跟我搞二进制了!%>.<%
格式化字符串漏洞是由像printf(user_input)这样的代码引起的,其中user_input是用户输入的数据,具有Set-UID root权限的这类程序在运行的时候,printf语句将会变得非常危险,因为它可能会导致下面的结果:
1.使得程序崩溃。
2.任意一块内存读取数据。
3.修改任意一块内存里的数据。
最后一种结果是非常危险的,因为它允许用户修改set-UID root程序内部变量的值,从而改变这些程序的行为。
本实验将会提供一个具有格式化漏洞的程序,我们将制定一个计划来探索这些漏洞。
预备知识
在进行真正的格式化字符串攻击之前,我们需要了解一些基础知识,方便更好的理解该类漏洞。个人感觉我们还需要一些堆栈相关的基础知识才能更好的理解并运用格式化字符串漏洞。接下来我们就一起看一下栈相关的知识:说到栈我们不得不提的就是函数调用与参数传递,因为栈的作用就是动态的存储函数之间的调用关系,从而保证在被调用函数返回时能够回到母函数中继续执行。栈其实是一种数据结构,栈中的数据是先进后出(First In Last Out),常见的操作有两种:压栈(PUSH)和弹栈(POP),用于标识栈属性的也有两个:栈顶(TOP)和栈底(BASE)。
PUSH:为栈增加一个元素。
POP:从栈中取出一个元素。
TOP:标识栈顶的位置,并且是动态变化的,每进行一次push操作,它会自增1,反之,每进行一次pop操作,它会自减1。
BASE:标识栈底位置,它的位置是不会变动的。
函数调用时到底发生了什么呢,我们将通过下面的代码做一下简单的认识。示例代码:
int func_B(arg_B1,arg_B2)
{
int var_B;
var_B = arg_B1+arg_B2;
return var_B;
}
int func_A(arg_A1,arg_A2)
{
int var_A;
var_A = func_B(arg_A1,arg_A2);
return var_A;
}
int main (int argc, char **argv, char **envp)
{
int var_main;
var_main=func_A(1,2);
return var_main;
}
程序的执行过程如下图所示:
通过上图我们可以看到程序执行的流程:main–func_A–func_B–func_A–main,CPU在执行程序时是如何知道各个函数之间的调用关系呢,接下来我们将介绍一个新的名词:栈帧。当函数被调用时,系统栈会为这个函数开辟一个新的栈帧,这个栈帧中的内存空间被它所属的函数独占,当函数返回时,系统栈会弹出该函数所对应的栈帧。32位系统下提供了两个特殊的寄存器(ESP和EBP)识栈帧。
ESP:栈指针寄存器,存放一个指针,该指针指向栈顶。
EBP:基址指针寄存器,存放一个指针,该指针指向栈底。
CPU利用EBP(不是ESP)寄存器来访问栈内局部变量、参数、函数返回地址,程序运行过程中,ESP寄存器的值随时变化,如果以ESP的值为基准对栈内的局部变量、参数、返回地址进行访问显然是不可能的,所以在进行函数调用时,先把用作基准的ESP的值保存到EBP,这样以后无论ESP如何变化,都能够以EBP为基准访问到局部变量、参数以及返回地址。接下来将编译上述代码并进行调试,从而进一步了解函数调用以及参数传递的过程。
首先用gcc进行编译:
gcc -fno-stack-protector -o 1 1.c
用objdump进行反汇编查看: