淹没返回地址

覆盖邻接变量的方法利用条件太过苛刻,需要源代码的结构符合漏洞利用才能实行。直接修改EBP或者函数返回地址的攻击则更为通用。

0x00 源码

由于键盘能够直接输入的字符ASCII范围有限,无法表达0x110x12等值,所以对代码稍作修改,通过读取文本文件输入。

#include <stdio.h>


#define PASSWORD "1234567"


int verify_password (char *password)
{
	int authenticated;
	char buffer[8];
	authenticated=strcmp(password,PASSWORD);
	strcpy(buffer,password);	// Overflow
	return authenticated;
}
main()
{
	int valid_flag=0;
	char password[1024];
	FILE * fp;
	if(!(fp=fopen("./password.txt","rw+")))
	{
		exit(0);
	}
	fscanf(fp,"%s",password);
	valid_flag = verify_password(password);
	if(valid_flag)
	{
		printf("Incorrect password!\n");
	}
	else
	{
		printf("Congratulation! You have passed the verification!\n");
	}
	fclose(fp);
}

使用Visual C++ 6.0编译,编译选项默认,Build版本为Debug。务必避开Visual Studio系列的GS编译选项。

0x01 分析

溢出工作包括:

  1. 了解栈中的情况,如函数地址距离缓冲区的偏移量等。虽然可以通过分析代码得到,但最好还是通过动态调试挖掘;
  2. 得到程序通过验证的指令地址,以便使程序直接跳转到这个分支;
  3. 在password.txt中的相应偏移处填写该地址。

首先反汇编得出验证通过分支的指令地址为0x00401122,函数verify_password0x00401102处被调用,在0x0040110A处将EAX中函数返回的值取出,在0x0040110D处与0比较,再决定跳转到哪个分支。

栈溢出——返回地址_动态调试

验证通过的分支从0x00401122处的参数压栈开始,如果把返回地址覆盖成该地址,那么0x00401102处的函数调用返回后,程序无论如何将跳转到验证通过的分支。

0x02 溢出

以4个字节为单位进行输入,为方便查看,每个单位都为“abcd”。

缓冲区buffer容量为8个字节,需要2个单位填充。按照栈帧结构,向下为变量authenticated,需要1个单位填充。再向下为前栈帧EBP,需要1个单位填充。再向下为函数返回地址,需要1个单位填充。

通过十六进制编辑方式制作password.txt:

栈溢出——返回地址_函数返回_02

注意:动态调试时显示的地址是经过转换而便于阅读的,其实际存储方式为小端存储。

之后通过OllyDbg调试运行该程序,最终的栈情况为:

局部变量名 内存地址 偏移3处的值 偏移2处的值 偏移1处的值 偏移0处的值
buffer[0~3] 0x0012FB14 0x61('a') 0x62('b') 0x63('c') 0x64('d')
buffer[4~7] 0x0012FB18 0x61('a') 0x62('b') 0x63('c') 0x64('d')
authenticated(修改前) 0x0012FB1C 0x00 0x00 0x00 0x01
authenticated(修改后) 0x0012FB1C 0x61('a') 0x62('b') 0x63('c') 0x64('d')
前栈帧EBP(修改前) 0x0012FB20 0x00 0x12 0xFF 0x80
前栈帧EBP(修改后) 0x0012FB20 0x61('a') 0x62('b') 0x63('c') 0x64('d')
返回地址(被覆盖前) 0x0012FB24 0x00 0x40 0x11 0x07
返回地址(被覆盖后) 0x0012FB24 0x00 0x40 0x11 0x22

执行情况:

栈溢出——返回地址_指令地址_03

由于EBP传入了无效值,程序崩溃,但成功到达验证通过的分支。