首页
社区
课程
招聘
[原创]格式化字符串漏洞解析
发表于: 2020-9-9 16:22 19171

[原创]格式化字符串漏洞解析

2020-9-9 16:22
19171

格式化字符串漏洞常见的标志为printf(&str),其中str中的内容是可控的。printf在解析format参数时,会自动从栈上format字符串结束的位置,按顺序读取格式化字符串对应的参数。如图所示,执行的命令为printf("%s %d %d %d %x",buf, 1, 2, 3),紧随格式化字串后压入栈上的参数为4个,但格式化字串有五个参数,printf在解析第五个参数%x时,会继续往栈上读取,造成了信息泄露:

image-20200829113304904

checksec信息如下,保护全开:

IDA中主函数逻辑如下:首先判断用户名是否为root,然后从系统中读取一个随机数,判断用户的输入与随机数是否相等。随机数输入的长度限制为0x50,告别了栈溢出的可能,随机数输入错误1次后exit_flag会置0,在下一次输入错误后程序会退出。唯一的利用点在于程序中存在printf(&s),而s是可控的,因此可以用格式化字符串的任意地址读功能获取随机数:

在输入中输入格式化字符串%x,程序会打印栈上的信息:

此时寄存器与栈的布局如下所示:

通过观察我们可以发现,泄露出来的数据依次为RSI RDX RCX R8 R9 RSP+0x8 RSP+0x10 RSP+0x18的内容,在64位系统中,函数前6个参数通过寄存器传参,对应RSI RDX RCX R8 R9,函数不会泄露RDI,即格式化字符串本身的地址内容。当寄存器的内容不足以填满格式化字符串的参数时,printf会继续往栈上索引,从RSP+0x8,即main函数的栈基址开始读取,刚好在第8个参数泄露了位于rsp+10h的随机数0x6db2adca20d558ab

现在我们知道怎么计算偏移来读取任意地址的信息了,如果读取离当前RSP很远的信息,比如偏移了100个%p,可以使用$占位符减少输入,$的含义为输出对应位置的参数,比如%8$p输出第8个%p的数据:

再康康栈上的信息,刚好对应之前讲的第8个%p的输出内容:

pwngbd提供了一种方便的函数fmtarg,使用格式为fmtarg addr。在进入printf函数时断下,调用fmtarg后可以自动计算格式化参数与addr的偏移。fmtarg在计算index时将RDI也算了进去,后面会自动减一作为%$p的参数:

现在我们已经可以泄露随机数,接下来利用思路就很简单了。第一轮利用格式化字符串漏洞读取随机数,第二轮直接将获取的随机数作为输入,即可“成为root”。完整exp如下:

程序输出如下:

printf除了能将数据输出至标准输出,还能将数据输出至某一地址。printf通过%n、%hn、%hhn三个参数将已打印的字符个数输出至格式化参数对应的地址中,如:

可以通过格式化串中的输出占位符来调整输出字符串的个数:

%n一次写入四个字节,%hn一次写入两个字节,%hhn一次只写入一个字节。如果写入的字节数大于格式化字符串所对应的最大字节数,则发生溢出置0。在空间足够的情况下,推荐使用%hhn进行写入,一来可以避免sprintf等函数末尾自动填充\0,二来通过溢出修改写入字节(如0x64 -> 0x32)所需的字符数较少,不会卡死。如果空间有限,则需酌情考虑使用其他格式字串或更换方法:

与其他格式化字符串一样,%n系列也可以通过 $运算符来进行偏移,从而实现任意地址写的功能。下面我们通过一个简单的实例来康康如何进行写入,demo源码如下:

程序执行到printf前,栈上的数据分布如下:

我们的目标是修改位于0x7fffffffdb50变量的值,注意,%n参数对应的是指针,我们需要借用一层跳板来执行解引用后修改操作,即传入0x7fffffffdb58这一指向0x7fffffffdb50的指针。使用fmtarg得出该地址与格式化字符串的偏移为7:fmtarg 0x7fffffffdb58 The index of format argument : 8 ("\%7$p"),对应源码中%7$hnn%65c将打印栈上的垃圾数据,用于控制输出长度,进而控制修改的值。程序执行完后,t的值被修改成65:

以2020强网杯”Siri“一题为例。check信息如下,保护全开:

拖进IDA查看程序逻辑,首先是main函数:

函数中看似有个可控Input_buffer放在栈上,但程序中read函数写死了读取长度刚好为Buffer的大小,因此无法利用。

tell_storyfox_say无任何交互的功能:

而最后的leak函数中调用了sprintf,且输出缓冲区内容是可控的,比如输入Remind me to %d,在执行sprintf时参数会变成">>> OK, I'll remind you to %d",继而将内容输入printf作为参数——造成了格式化字符串的漏洞:

程序中所有的函数都通过leave retn返回,即所有变量都可以在main函数的栈中索引到。

程序中没有可以利用的shell函数,因此需要通过libc中的gadget来执行get shell操作。由于程序开启了PIE,首先我们的目标是获取程序栈的地址和libc的地址,才能进行下一步的利用。程序启动时通过vmmap获取程序当前的基址,然后加上IDA中调用printf函数的偏移,断下后通过fmtarg查看格式化字符串与目标数据的偏移:

0x7ffcf4ebb980存放了RBP的信息,RBP所指向的内容是main的栈基址;0x7ffcf4ebbaa8中存放了__libc_start_main偏移后的地址,经过处理可以得到libc的基址。随后利用one_gadget获取跳转目标:

信息收集完毕,接下来有两种思路可以利用。

printf在输出字符串长度过长时会自动调用malloc申请内存空间,而输出的格式化字符串的宽度是可控制的,因此可以通过修改malloc_hookone_gadget,然后通过诸如printf("%399999c")等超长格式化串来调用malloc,进而实现get_shell。由于获取到了libc的基址,malloc_hook的基址自然可以通过偏移减得,下面讲讲如何写入。

由于leak函数中限制输入长度为0x110malloc_hook的地址长度为6个字节,使用%hnn修改需要准备6个参数,总长度为0x30,即格式化字符串的总长度必须小于0x80;其次,由于传入的字符串包含了Remind me to前缀,为了内存对齐,输入的字符串长度应为0x80 - strlen("Remind me to ")。构造payload的思路为:格式化字符串(0x80)+malloc_hook地址6。(*输入的格式化字符串首先传入sprintf,因此需要确保内存与"Remind me to "对齐;如果采用">>> OK, I'll remind you to"进行计算,需要额外偏移两个字节 ),在printf下断使用fmtarg确定参数与目标内存地址的偏移。

构造payload的解析如下:

构造完payload执行至printf处,栈上的信息如下:

执行printf后,malloc_hook的地址被修改成了one_gadget,此时继续循环,发送%300000c长度的格式化字符串,即可get_shell

完整POC如下:

另一种利用思路为直接利用格式化字符串修改函数的返回地址,不需要泄露canary

完整POC如下:

有时候被__libc_start_main+231深深吸住了眼球,希望通过修改main函数的返回地址来完整滴控制程序的流程。不过相比起leak,修改main的返回地址要更麻烦些,因为main函数执行后是死循环,为了使函数返回还需要额外修改程序执行的指令。第一步与leak相同,获取返回地址信息并确定偏移:

main函数的返回地址修改完成后,还需要修改指令来使main函数强制退出,在IDA中该段反汇编如下:

调用完leak后,为了跳出循环,需要将144C处的指令地址修改为14C1来进行强制返回,这需要获取程序的基址——很简单,用main 中的返回地址对着指令手算偏移就行了:

定位指令地址后,直接写最低一个字节为C1即可实现强制跳转,完整POC如下:

 
 
 
 
 

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2020-9-12 15:24 被Chernobyl编辑 ,原因: debug
上传的附件:
  • Siri (13.97kb,45次下载)
  • pwn1 (10.27kb,45次下载)
收藏
免费 6
支持
分享
最新回复 (6)
雪    币: 14525
活跃值: (17543)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
2
mark,感谢分享
2020-9-9 16:54
0
雪    币: 201
活跃值: (234)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2020-9-9 17:08
0
雪    币: 4437
活跃值: (6233)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
4
原文件好像不能下载了
2020-9-28 14:22
0
雪    币: 252
活跃值: (758)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
5
楼主调试的时候用的那个软件啊,就是查看寄存器和栈布局的时候
2021-1-21 21:21
0
雪    币: 1193
活跃值: (803)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
6
wx_斯文败类 楼主调试的时候用的那个软件啊,就是查看寄存器和栈布局的时候
pwndbg
2021-1-25 17:51
0
雪    币: 164
活跃值: (1024)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
楼主,SIri的offset变量怎么选出来的66,没看懂
2024-1-6 09:24
0
游客
登录 | 注册 方可回帖
返回
//