6.2.3 re CodegateCTF2017 angrybird

    看题目就知道,这是一个会让我们抓狂的程序,事实也确实如此。

    1. $

    一运行就退出,应该是需要程序流上有问题。

    main 函数的开头有一些坑需要 patch,才能使程序正常运行,然后经过很多很多轮的运算和判断,可以看到 main 函数长达 18555 行:

    1. [0x00400600]> pd 60 @ main
    2. / (fcn) main 18555
    3. | main ();
    4. | : ; DATA XREF from 0x0040061d (entry0)
    5. | : 0x00400761 55 push rbp
    6. | : 0x00400762 4889e5 mov rbp, rsp
    7. | : 0x00400765 4883c480 add rsp, 0xffffffffffffff80
    8. | : 0x00400769 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
    9. | : 0x00400772 488945f8 mov qword [local_8h], rax
    10. | : 0x00400776 31c0 xor eax, eax ; eax 0
    11. | : 0x00400778 83f800 cmp eax, 0 ; 比较 eax 0
    12. | `=< 0x0040077b 0f845ffeffff je sym.imp.exit ; eax == 0 时退出,所以需要将 je 换成 jne,或者把上一行的 0 换成 1
    13. | 0x00400781 48c745901860. mov qword [local_70h], reloc.strncmp_24 ; 0x606018
    14. | 0x00400789 48c745982060. mov qword [local_68h], reloc.puts_32 ; 0x606020
    15. | 0x00400791 48c745a02860. mov qword [local_60h], reloc.__stack_chk_fail_40 ; 0x606028
    16. | 0x00400799 48c745a83860. mov qword [local_58h], reloc.__libc_start_main_56 ; 0x606038
    17. | 0x004007a1 b800000000 mov eax, 0
    18. | 0x004007a6 e84bffffff call sub.you_should_return_21_not_1_:__6f6 ; 该函数中需要返回 21
    19. | 0x004007ab 89458c mov dword [local_74h], eax ; [local_74] = 21
    20. | 0x004007ae b800000000 mov eax, 0
    21. | 0x004007b3 e854ffffff call sub.stack_check_70c ; 栈检查函数,直接 nop 掉,或者进入函数修改逻辑
    22. | 0x004007b8 b800000000 mov eax, 0
    23. | 0x004007bd e868ffffff call sub.hello_72a
    24. | 0x004007c2 488b15a75820. mov rdx, qword [obj.stdin] ; [0x606070:8]=0 ; 从标准输入读入
    25. | 0x004007c9 8b4d8c mov ecx, dword [local_74h]
    26. | 0x004007cc 488d45b0 lea rax, [local_50h]
    27. | 0x004007d0 89ce mov esi, ecx ; esi = 21
    28. | 0x004007d2 4889c7 mov rdi, rax
    29. | 0x004007d5 e8f6fdffff call sym.imp.fgets ; char *fgets(char *s, int size, FILE *stream) ; patch 成功后就能调用 fgets
    30. | 0x004007da 0fb655b0 movzx edx, byte [local_50h] ; 读入的第一个字符
    31. | 0x004007de 0fb645b1 movzx eax, byte [local_4fh] ; 读入的第二个字符
    32. | 0x004007e2 31d0 xor eax, edx
    33. | 0x004007e4 8845d0 mov byte [local_30h], al
    34. | 0x004007e7 0fb645d0 movzx eax, byte [local_30h]
    35. | 0x004007eb 3c0f cmp al, 0xf ; 15 ; 对处理后的输入字符做判断
    36. | ,=< 0x004007ed 7f14 jg 0x400803 ; 若不满足条件,跳转失败,程序退出
    37. | | 0x004007ef bf94504000 mov edi, str.melong ; 0x405094 ; "melong"
    38. | | 0x004007f4 e897fdffff call sym.imp.puts ; int puts(const char *s)
    39. | | 0x004007f9 bf01000000 mov edi, 1
    40. | | 0x004007fe e8ddfdffff call sym.imp.exit ; void exit(int status)
    41. | | ; JMP XREF from 0x004007ed (main)
    42. | `-> 0x00400803 0fb655b0 movzx edx, byte [local_50h] ; 第二轮运算
    43. | 0x00400807 0fb645b1 movzx eax, byte [local_4fh]
    44. | 0x0040080b 21d0 and eax, edx
    45. | 0x0040080d 8845d0 mov byte [local_30h], al
    46. | 0x00400810 0fb645d0 movzx eax, byte [local_30h]
    47. | 0x00400814 3c50 cmp al, 0x50 ; 'P' ; 80
    48. | ,=< 0x00400816 7e14 jle 0x40082c
    49. | | 0x00400818 bf94504000 mov edi, str.melong ; 0x405094 ; "melong"
    50. | | 0x0040081d e86efdffff call sym.imp.puts ; int puts(const char *s)
    51. | | 0x00400822 bf01000000 mov edi, 1
    52. | | 0x00400827 e8b4fdffff call sym.imp.exit ; void exit(int status)
    53. | `-> 0x0040082c c645d000 mov byte [local_30h], 0 ; 第三轮运算
    54. | 0x00400830 0fb645d0 movzx eax, byte [local_30h]
    55. | 0x00400834 3c01 cmp al, 1 ; 1
    56. | ,=< 0x00400836 7e14 jle 0x40084c
    57. | | 0x00400838 bf94504000 mov edi, str.melong ; 0x405094 ; "melong"
    58. | | 0x0040083d e84efdffff call sym.imp.puts ; int puts(const char *s)
    59. | | 0x00400847 e894fdffff call sym.imp.exit ; void exit(int status)
    60. | | ; JMP XREF from 0x00400836 (main)
    61. | `-> 0x0040084c 0fb655c2 movzx edx, byte [local_3eh] ; n 轮运算
    62. | 0x00400850 0fb645b1 movzx eax, byte [local_4fh]
    63. | 0x00400854 21d0 and eax, edx
    64. | 0x00400856 8845d0 mov byte [local_30h], al
    65. | 0x00400859 0fb645d0 movzx eax, byte [local_30h]

    第一处 patch,将指令 je 改成 jne

    1. [0x00400600]> s 0x0040077b
    2. [0x0040077b]> pd 1
    3. | `=< 0x0040077b 0f845ffeffff je sym.imp.exit
    4. [0x0040077b]> wx 0f85
    5. [0x0040077b]> pd 1
    6. | `=< 0x0040077b 0f855ffeffff jne sym.imp.exit

    第二处 patch,函数 sub.you_should_return_21_not_1_:__6f6

    1. [0x00606060]> s 0x0040070a
    2. [0x0040070a]> pd 1
    3. | 0x0040070a 5d pop rbp
    4. [0x0040070a]> wx c9
    5. [0x0040070a]> pd 1
    6. | 0x0040070a c9 leave

    第三处 patch,将调用 sub.stack_check_70c 的指令直接 nop 掉:

    1. [0x00606060]> pdf @ sub.stack_check_70c
    2. / (fcn) sub.stack_check_70c 30
    3. | sub.stack_check_70c ();
    4. | : ; CALL XREF from 0x004007b3 (main)
    5. | : 0x0040070c 55 push rbp
    6. | : 0x0040070d 4889e5 mov rbp, rsp
    7. | : 0x00400710 bf82504000 mov edi, str.stack_check ; 0x405082 ; "stack check"
    8. | : 0x00400715 e876feffff call sym.imp.puts ; int puts(const char *s)
    9. | : 0x0040071a 678b0424 mov eax, dword [esp]
    10. | : 0x0040071e 83f800 cmp eax, 0
    11. | `=< 0x00400721 0f85b9feffff jne sym.imp.exit
    12. | 0x00400727 90 nop
    13. | 0x00400728 5d pop rbp
    14. \ 0x00400729 c3 ret
    15. [0x00400600]> s 0x004007b3
    16. [0x004007b3]> pd 1
    17. | 0x004007b3 e854ffffff call sub.stack_check_70c
    18. [0x004007b3]> wx 9090909090
    19. [0x004007b3]> pd 5
    20. | 0x004007b3 90 nop
    21. | 0x004007b4 90 nop
    22. | 0x004007b5 90 nop
    23. | 0x004007b6 90 nop
    24. | 0x004007b7 90 nop

    第四处 patch 是将 sub.hello_72a 函数中的 je 改成 jne

    1. [0x0040077b]> pdf @ sub.hello_72a
    2. / (fcn) sub.hello_72a 55
    3. | sub.hello_72a ();
    4. | ; var int local_8h @ rbp-0x8
    5. | ; CALL XREF from 0x004007bd (main)
    6. | 0x0040072a 55 push rbp
    7. | 0x0040072b 4889e5 mov rbp, rsp
    8. | 0x0040072e 4883ec10 sub rsp, 0x10
    9. | 0x0040073a 488b45f8 mov rax, qword [local_8h]
    10. | 0x0040073e ba05000000 mov edx, 5
    11. | 0x00400743 be8e504000 mov esi, str.hello ; 0x40508e ; "hello"
    12. | 0x00400748 4889c7 mov rdi, rax
    13. | 0x0040074b e830feffff call sym.imp.strncmp ; int strncmp(const char *s1, const char *s2, size_t n) ; 如果相等则返回 0
    14. | 0x00400750 85c0 test eax, eax
    15. | ,=< 0x00400752 740a je 0x40075e ; 如果 eax 0,则跳转
    16. | | 0x00400754 bf01000000 mov edi, 1
    17. | | 0x00400759 e882feffff call sym.imp.exit ; void exit(int status)
    18. | | ; JMP XREF from 0x00400752 (sub.hello_72a)
    19. | `-> 0x0040075e 90 nop
    20. | 0x0040075f c9 leave
    21. \ 0x00400760 c3 ret

    总的来说就是修改了下面几个地方:

    这样程序的运行就正常了,它从标准输入读入字符,进行一系列的判断,由于程序执行流非常长,我们不可能一个一个地去 patch。radare2 里输入命令 VV @ main 可以看到下面的东西:

    1. [0x00400761]> pd -20 @ main+18555
    2. | 0x00404f8e d00f ror byte [rdi], 1
    3. | 0x00404f90 b645 mov dh, 0x45 ; 'E' ; 69
    4. | 0x00404f92 d03c78 sar byte [rax + rdi*2], 1
    5. | ,=< 0x00404f95 7e14 jle 0x404fab
    6. | | 0x00404f97 bf94504000 mov edi, str.melong ; 0x405094 ; "melong"
    7. | | 0x00404f9c e8efb5ffff call sym.imp.puts ; int puts(const char *s)
    8. | | 0x00404fa1 bf01000000 mov edi, 1
    9. | | 0x00404fa6 e835b6ffff call sym.imp.exit ; void exit(int status)
    10. | | ; JMP XREF from 0x00404f95 (main)
    11. | `-> 0x00404fab 488d45b0 lea rax, [local_50h]
    12. | 0x00404faf 4889c6 mov rsi, rax
    13. | 0x00404fb2 bf9b504000 mov edi, str.you_typed_:__s_n ; 0x40509b ; "you typed : %s\n"
    14. | 0x00404fb7 b800000000 mov eax, 0
    15. | 0x00404fbc e8efb5ffff call sym.imp.printf ; int printf(const char *format)
    16. | 0x00404fc1 b800000000 mov eax, 0
    17. | 0x00404fc6 488b4df8 mov rcx, qword [local_8h]
    18. | 0x00404fca 6448330c2528. xor rcx, qword fs:[0x28]
    19. | ,=< 0x00404fd3 7405 je 0x404fda
    20. | | 0x00404fd5 e8c6b5ffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
    21. | | ; JMP XREF from 0x00404fd3 (main)
    22. | `-> 0x00404fda c9 leave ; 选择一个目标地址
    23. \ 0x00404fdb c3 ret

    因为每次错误退出之前,都会调用 puts 函数,所以应该避免其出现,将地址设置为参数 avoid。

    1. [0x00400600]> is~puts
    2. vaddr=0x00400590 paddr=0x00000590 ord=002 fwd=NONE sz=16 bind=GLOBAL type=FUNC name=imp.puts

    对于使用 angr 来说,上面的 patch 完全没有必要,只要选择一个合适的初始化地址,如 0x004007da,也就是 fget 函数的下一条指令,就可以跑出结果:

    1. import angr
    2. main = 0x004007da
    3. find = 0x00404fda # leave;ret
    4. avoid = 0x00400590 # puts@plt
    5. p = angr.Project('./angrybird_org')
    6. init = p.factory.blank_state(addr=main)
    7. pg = p.factory.simgr(init, threads=4)
    8. ex = pg.explore(find=find, avoid=avoid)
    9. final = ex.found[0].state
    10. flag = final.posix.dumps(0)
    11. print "Flag:", final.posix.dumps(1)

    Bingo!!!(不能保证每次都有效,多试几次)

    然后用我们 patch 过的程序来验证 flag:

    1. $ ./angrybird_mod
    2. you should return 21 not 1 :(
    3. Im_so_cute&pretty_:)
    4. you typed : Im_so_cute&pretty_:)

    同样需要一定的运气才能通过,祝好运:)

    参考资料