在格式化字符串的边缘试探

格式化字符串

format string基本介绍

此漏洞由printf类函数在使用时直接使用用户可控字符串作为格式化字符串使用所导致。如printf(s),在运行时,用户可以通过给予s特殊的值造成程序崩溃或泄露内存地址,乃至任意地址写控制程序流程。

利用原理

获取参数


在使用printf函数时,第一个参数为格式化字符串指针,之后根据格式符向下寻找需要的参数并根据格式符进行解释。但是在printf(s)过程中,若字符串s中存在格式符,仍会按照次序将相应位置的变量进行解析并输出。

特殊用法

在利用格式化字符串漏洞的过程中有几个至关重要的特殊用法

%5$d -> 5$表示指定使用第五个参数
%240c -> 表示输出240个字符
%n -> 不产生输出,将再次之前已经输出的字符个数写入对应参数所指向的内存
%7$hhn -> 将输出字符个数写入第七个参数所指向的区域,写入一个字节(x86)(half half)

利用%n向指定内存中写是改变程序流程的关键,而其他格式符则用于泄露内存信息或为配合%n写数据。

例题  

 echo  


一个可以循环利用的格式化字符串,可以通过%n将printf的地址改写成为system函数的地址,下一次输入”/bin/sh”后执行printf(“/bin/sh”)而实际上调用了system函数,获得shell。

根据调试中的栈结构,0xffdd69b0位置存储格式化字符串指针,其下有指向IO_2_1_stdinGLOBAL_OFFSET_TABLE的指针。(如果有需要可以通过泄露这两个变量查询所使用的libc库,进而获得其他变量或函数的偏移。)
其下0xffdd69cc为输入的格式化串的起始地址,可以在格式化字符串中布局需要改写的内存地址,按照顺序目标地址所在位置分别为printf函数格式化字符串的第7, 8, 9 ,10个参数,即分别使用%7$hhn-%10$hhn逐字节覆写(一次输出太多字节容易导致链接断开,故使用hhn),除了起始16个字节为地址,其余使用%240c等进行占位.
使用pwntools的fmtstr_payload()函数自动生成利用字符串。

pwnlib.fmtstr.fmtstr_payload(offset, writes, numbwritten=0, write_size=’byte’) → str
Makes payload with given parameter. It can generate payload for 32 or 64 bits architectures. The size of the addr is taken from context.bits
Parameters:

  • offset (int) – the first formatter’s offset you control
  • writes (dict) – dict with addr, value {addr: value, addr2: value2}
  • numbwritten (int) – number of byte already written by the printf function
  • write_size (str) – must be byte, short or int. Tells if you want to write byte by byte, short by short or int by int (hhn, hn or n)

函数中offset即为栈中指向被改写区域指针相对格式化字符串指针的偏移(作为第几个参数),在上图中指向printf.got的指针位于格式化字符串指针下第七个的位置,offset即为7.
writes是一个*字典 *,为要改写的值和目标值,即用value的值替换掉内存中key指向的区域。
numbwritten即为在之前已经输出的字符数,write_size为mei每次改写的size,一般使用byte(hhn),以避免程序崩溃或连接断开。
所以本题payload可以直接使用函数生成
payload = fmtstr_payload(7, {printf_got: system_plt})
//好像是因为system函数未调用过,got中没有装载地址,同时printf非首次调用不再经过plt查询?我将下图中printf和system的got和plt任意调换均不能成功。


echo2

变成了64bit且开启了PIE保护。
stack
可以看到在调用printf函数时,栈中存储着很多与全局变量和函数相关的地址,可以利用格式化字符串泄露两个函数地址,从而查询使用的libc版本(inndy网站中提供了题目使用的链接库,故不需要此步骤),继而确定elf基址和libc的偏移量。
在尝试中发现stdout函数地址输出为nil,setbuf和init无法从libc中获取地址,所以采用c08处的main和be8处的__libc_start_main.
base
//这里有一点是本地的时候be8处地址为__libc_start_main+241,但是远程时,需要令接收到的值-240,才能满足基址后三位为000.
0x7fffffffcbe8 —▸ 0x7ffff7a5a2b1 (__libc_start_main+241) ◂— mov edi, eax
排除掉随机地址的障碍之后就可以像echo一样覆盖函数地址,但是由于64位程序中地址为0x7fffffffxxxx的形式,前两个字节为\x00,不能向printf函数中写入,可以覆写exit函数,但是传递’/bin/sh’有障碍。
这里可以利用libc库中固有的一个执行execv(‘/bin/sh’)的gadget。可以在libc中通过查找字符串/bin/sh找到。
gadget将此gadget的地址逐位写到exit_got,即可获得shell。有一点是fmtstr_payload生成的利用字符串会把地址放在前面,而地址中存在\x00会导致printf中断,所以不能正常使用,还是手动构造。

Ref:

利用小结(一)
利用小结(二)
CTAF Wiki
M4x的inndyWP
qazbnm456/ctf-course
pwnlib.fmtstr
安全技术精粹

generated by haroopad


作者:辣鸡小谱尼
出处:http://www.cnblogs.com/ZHijack/
如有转载,荣幸之至!请随手标明出处;

猜你喜欢

转载自www.cnblogs.com/ZHijack/p/8687182.html