6.1.20 pwn 33C3CTF2016 babyfengshui

    32 位程序,开启了 canary 和 NX。

    在 Ubuntu-14.04 上玩一下,添加 user 和显示 user:

    1. 0: Add a user
    2. 1: Delete a user
    3. 2: Display a user
    4. 3: Update a user description
    5. 4: Exit
    6. Action: 0
    7. size of description: 10 # description 最大长度(desc_size)
    8. name: AAAA
    9. text length: 5 # description 实际长度(text_size)
    10. text: aaaa
    11. 0: Add a user
    12. 1: Delete a user
    13. 2: Display a user
    14. 3: Update a user description
    15. 4: Exit
    16. Action: 2
    17. index: 0
    18. name: AAAA
    19. description: aaaa

    对于 description 的调整只能在最大长度的范围内,否则程序退出:

    1. 0: Add a user
    2. 1: Delete a user
    3. 2: Display a user
    4. 3: Update a user description
    5. 4: Exit
    6. Action: 3
    7. index: 0
    8. text length: 20
    9. my l33t defenses cannot be fooled, cya!

    函数首先分配一个 description 的最大空间,然后分配 user 结构体空间,并将 user 放到 store 数组中,最后调用更新 description 的函数。

    1. struct user {
    2. char *desc;
    3. char name[0x7c];
    4. } user;
    5. struct user *store[50];

    store 放在 0x804b080,当前 user 个数 user_num 放在 0x804b069

    1. [0x080485c0]> pdf @ sub.free_905
    2. / (fcn) sub.free_905 138
    3. | sub.free_905 (int arg_8h);
    4. | ; var int local_1ch @ ebp-0x1c
    5. | ; var int local_ch @ ebp-0xc
    6. | ; arg int arg_8h @ ebp+0x8
    7. | ; CALL XREF from 0x08048b5f (main)
    8. | 0x08048905 push ebp
    9. | 0x08048906 mov ebp, esp
    10. | 0x08048908 sub esp, 0x28 ; '('
    11. | 0x0804890b mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8
    12. | 0x0804890e mov byte [local_1ch], al ; 将参数 i 放到 [local_1ch]
    13. | 0x08048911 mov eax, dword gs:[0x14] ; [0x14:4]=-1 ; 20
    14. | 0x08048917 mov dword [local_ch], eax
    15. | 0x0804891a xor eax, eax
    16. | 0x0804891c movzx eax, byte [0x804b069] ; [0x804b069:1]=0 ; 取出 user_num
    17. | ,=< 0x08048926 jae 0x8048978 ; i 大于等于 user_num 时函数返回
    18. | | 0x08048928 movzx eax, byte [local_1ch]
    19. | | 0x0804892c mov eax, dword [eax*4 + 0x804b080] ; 取出 store[i]
    20. | | 0x08048933 test eax, eax ; store[i] 0 是函数返回
    21. | ,==< 0x08048935 je 0x804897b
    22. | || 0x08048937 movzx eax, byte [local_1ch]
    23. | || 0x0804893b mov eax, dword [eax*4 + 0x804b080] ; [0x804b080:4]=0
    24. | || 0x08048944 sub esp, 0xc
    25. | || 0x08048947 push eax
    26. | || 0x08048948 call sym.imp.free ; free(store[i]->desc) 释放 description
    27. | || 0x0804894d add esp, 0x10
    28. | || 0x08048950 movzx eax, byte [local_1ch]
    29. | || 0x08048954 mov eax, dword [eax*4 + 0x804b080] ; 取出 store[i]
    30. | || 0x0804895b sub esp, 0xc
    31. | || 0x0804895e push eax
    32. | || 0x0804895f call sym.imp.free ; free(store[i]) 释放 user
    33. | || 0x08048964 add esp, 0x10
    34. | || 0x08048967 movzx eax, byte [local_1ch]
    35. | || 0x0804896b mov dword [eax*4 + 0x804b080], 0 ; store[i] 置为 0
    36. | ,===< 0x08048976 jmp 0x804897c
    37. | ||| ; JMP XREF from 0x08048926 (sub.free_905)
    38. | ||`-> 0x08048978 nop
    39. | ||,=< 0x08048979 jmp 0x804897c
    40. | ||| ; JMP XREF from 0x08048935 (sub.free_905)
    41. | |`--> 0x0804897b nop
    42. | | | ; JMP XREF from 0x08048979 (sub.free_905)
    43. | | | ; JMP XREF from 0x08048976 (sub.free_905)
    44. | `-`-> 0x0804897c mov eax, dword [local_ch]
    45. | 0x0804897f xor eax, dword gs:[0x14]
    46. | ,=< 0x08048986 je 0x804898d
    47. | | 0x08048988 call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
    48. | | ; JMP XREF from 0x08048986 (sub.free_905)
    49. | `-> 0x0804898d leave
    50. \ 0x0804898e ret

    删除的过程将 description 和 user 依次释放,并将 store[i] 置为 0。

    但是 user->desc 没有被置为 0,user_num 也没有减 1,似乎可能导致 UAF,但不知道怎么用。

    函数首先判断 store[i] 是否存在,如果是,就打印出 name 和 description。

    1. [0x080485c0]> pdf @ sub.text_length:_724
    2. / (fcn) sub.text_length:_724 242
    3. | sub.text_length:_724 (int arg_8h);
    4. | ; var int local_1ch @ ebp-0x1c
    5. | ; var int local_11h @ ebp-0x11
    6. | ; var int local_10h @ ebp-0x10
    7. | ; var int local_ch @ ebp-0xc
    8. | ; arg int arg_8h @ ebp+0x8
    9. | ; CALL XREF from 0x08048bdb (main)
    10. | ; CALL XREF from 0x080488e7 (sub.malloc_816)
    11. | 0x08048724 push ebp
    12. | 0x08048725 mov ebp, esp
    13. | 0x08048727 sub esp, 0x28 ; '('
    14. | 0x0804872a mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8
    15. | 0x0804872d mov byte [local_1ch], al ; 将参数 i 放到 [local_1ch]
    16. | 0x08048730 mov eax, dword gs:[0x14] ; [0x14:4]=-1 ; 20
    17. | 0x08048736 mov dword [local_ch], eax
    18. | 0x08048739 xor eax, eax
    19. | 0x0804873b movzx eax, byte [0x804b069] ; [0x804b069:1]=0 ; 取出 user_num
    20. | 0x08048742 cmp byte [local_1ch], al ; [0x2:1]=255 ; 2 ; 比较
    21. | ,=< 0x08048745 jae 0x80487ff ; i 大于等于 user_num 时函数返回
    22. | | 0x0804874b movzx eax, byte [local_1ch]
    23. | | 0x0804874f mov eax, dword [eax*4 + 0x804b080] ; 取出 store[i]
    24. | | 0x08048756 test eax, eax
    25. | ,==< 0x08048758 je 0x8048802 ; store[i] 0 时函数返回
    26. | || 0x0804875e mov dword [local_10h], 0 ; text_size 放到 [local_10h]
    27. | || 0x08048765 sub esp, 0xc
    28. | || 0x08048768 push str.text_length: ; 0x8048cb0 ; "text length: "
    29. | || 0x0804876d call sym.imp.printf ; int printf(const char *format)
    30. | || 0x08048775 sub esp, 4
    31. | || 0x08048778 lea eax, [local_11h]
    32. | || 0x0804877b push eax
    33. | || 0x0804877f push eax
    34. | || 0x08048780 push str.u_c ; 0x8048cbe ; "%u%c"
    35. | || 0x08048785 call sym.imp.__isoc99_scanf ; 读入 text_size
    36. | || 0x0804878a add esp, 0x10
    37. | || 0x0804878d movzx eax, byte [local_1ch]
    38. | || 0x08048791 mov eax, dword [eax*4 + 0x804b080] ; [0x804b080:4]=0
    39. | || 0x08048798 mov eax, dword [eax] ; 取出 store[i]->desc
    40. | || 0x0804879a mov edx, eax
    41. | || 0x0804879c mov eax, dword [local_10h] ; 取出 test_size
    42. | || 0x0804879f add edx, eax ; store[i]->desc + test_size
    43. | || 0x080487a1 movzx eax, byte [local_1ch]
    44. | || 0x080487a5 mov eax, dword [eax*4 + 0x804b080] ; 取出 store[i]
    45. | || 0x080487ac sub eax, 4 ; store[i] - 4
    46. | || 0x080487af cmp edx, eax ; 比较 (store[i]->desc + test_size) (store[i] - 4)
    47. | ,===< 0x080487b1 jb 0x80487cd ; 小于时跳转
    48. | ||| 0x080487b3 sub esp, 0xc ; 否则继续,程序退出
    49. | ||| 0x080487b6 push str.my_l33t_defenses_cannot_be_fooled__cya ; 0x8048cc4 ; "my l33t defenses cannot be fooled, cya!"
    50. | ||| 0x080487bb call sym.imp.puts ; int puts(const char *s)
    51. | ||| 0x080487c0 add esp, 0x10
    52. | ||| 0x080487c3 sub esp, 0xc
    53. | ||| 0x080487c6 push 1 ; 1
    54. | ||| 0x080487c8 call sym.imp.exit ; void exit(int status)
    55. | ||| ; JMP XREF from 0x080487b1 (sub.text_length:_724)
    56. | `---> 0x080487cd sub esp, 0xc
    57. | || 0x080487d0 push str.text: ; 0x8048cec ; "text: "
    58. | || 0x080487d5 call sym.imp.printf ; int printf(const char *format)
    59. | || 0x080487da add esp, 0x10
    60. | || 0x080487dd mov eax, dword [local_10h]
    61. | || 0x080487e0 lea edx, [eax + 1] ; test_size + 1
    62. | || 0x080487e3 movzx eax, byte [local_1ch]
    63. | || 0x080487e7 mov eax, dword [eax*4 + 0x804b080] ; [0x804b080:4]=0
    64. | || 0x080487ee mov eax, dword [eax] ; 取出 store[i]->desc
    65. | || 0x080487f0 sub esp, 8
    66. | || 0x080487f3 push edx
    67. | || 0x080487f4 push eax
    68. | || 0x080487f5 call sub.fgets_6bb ; 读入 test_size+1 个字符到 store[i]->desc
    69. | || 0x080487fa add esp, 0x10
    70. | ,===< 0x080487fd jmp 0x8048803
    71. | ||| ; JMP XREF from 0x08048745 (sub.text_length:_724)
    72. | ||`-> 0x080487ff nop
    73. | ||,=< 0x08048800 jmp 0x8048803
    74. | ||| ; JMP XREF from 0x08048758 (sub.text_length:_724)
    75. | |`--> 0x08048802 nop
    76. | | | ; JMP XREF from 0x08048800 (sub.text_length:_724)
    77. | | | ; JMP XREF from 0x080487fd (sub.text_length:_724)
    78. | `-`-> 0x08048803 mov eax, dword [local_ch]
    79. | 0x08048806 xor eax, dword gs:[0x14]
    80. | ,=< 0x0804880d je 0x8048814
    81. | | 0x0804880f call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
    82. | | ; JMP XREF from 0x0804880d (sub.text_length:_724)
    83. | `-> 0x08048814 leave
    84. \ 0x08048815 ret

    然而这种检查方式是有问题的,它基于 description 正好位于 user 前面这种设定。根据我们对堆分配器的理解,这个设定不一定成立,它们之间可能会包含其他已分配的堆块,从而绕过检查。

    所以我们首先添加两个 user,用于绕过检查。第 3 个 user 存放 “/bin/sh”。然后删掉第 1 个 user,并创建一个 description 很长的 user,其长度是第 1 个 user 的 description 长度加上 user 结构体长度。这时候检查就绕过了,我们可以在添加新 user 的时候修改 description 大小,造成堆溢出,并修改第 2 个 user 的 user->desc 为 free@got.plt,从而泄漏出 libc 地址。得到 system 地址后,此时修改第 2 个 user 的 description,其实是修改 free 的 GOT,所以我们将其改成 system@got.plt。最后删除第 3 个 user,触发 system(‘/bin/sh’),得到 shell。

    开启 ASLR。Bingo!!!

    1. $ python exp.py
    2. [+] Starting local process './babyfengshui': pid 2269
    3. [*] system address: 0xf75e23e0
    4. [*] Switching to interactive mode
    5. $ whoami

    完整的 exp 如下: