• Angr-CTF学习笔记14-17


    14 题主要是把程序逻辑分离在一个执行程序和动态链接库,我们直接对动态链接库中的_validate 函数进行符号执行,解决的solver.py 如下:

    1. def main(argv):
    2. path_to_binary = sys.argv[1] # 注意我们是要load so 库而不是执行程序
    3. base = 0x400000 # base 基址是随意定的,可以随意修改
    4. project = angr.Project(path_to_binary, load_options={
    5. 'main_opts' : {
    6. 'custom_base_addr' : base
    7. }
    8. })
    9. buffer_pointer = claripy.BVV(0x3000000, 32) # 创建一个buffer 指针值
    10. validate_function_address = base + 0x6D7
    11. initial_state = project.factory.call_state(validate_function_address, buffer_pointer,claripy.BVV(8, 32)) # 调用validate_function,因为函数声明validata_function(buffer_point,buffer_length) ,所以我们构造出调用validata_function(0x3000000,0x8) .
    12. password = claripy.BVS('password', 8 * 8) # 创建一个求解对象,大小为8 字节
    13. initial_state.memory.store(buffer_pointer, password) # 保存到0x30000000
    14. simulation = project.factory.simgr(initial_state)
    15. simulation.explore(find = base + 0x783) # 执行到validate 函数的RETN 指令
    16. if simulation.found:
    17. solution_state = simulation.found[0]
    18. solution_state.add_constraints(solution_state.regs.eax != 0) # 记得,我们要求validate 函数的返回值为1 的时候就是有解的,那么我们就需要在求解的时候添加上这么一个求解约束条件EAX 不能为False .
    19. solution = solution_state.se.eval(password)
    20. print(solution)

    15_angr_arbitrary_read

    汇编代码:

    1. .text:080484C9 main proc near ; DATA XREF: _start+17↑o
    2. .text:080484C9
    3. .text:080484C9 input_buffer = byte ptr -1Ch ; 注意这个buffer 的大小是16 字节
    4. .text:080484C9 try_again_string_point= dword ptr -0Ch
    5. .text:080484C9 var_4 = dword ptr -4
    6. .text:080484C9 argc = dword ptr 8
    7. .text:080484C9 argv = dword ptr 0Ch
    8. .text:080484C9 envp = dword ptr 10h
    9. .text:080484C9
    10. .text:080484C9 ; __unwind {
    11. .text:080484C9 lea ecx, [esp+4]
    12. .text:080484CD and esp, 0FFFFFFF0h
    13. .text:080484D0 push dword ptr [ecx-4]
    14. .text:080484D3 push ebp
    15. .text:080484D4 mov ebp, esp
    16. .text:080484D6 push ecx
    17. .text:080484D7 sub esp, 24h
    18. .text:080484DA mov eax, try_again
    19. .text:080484DF mov [ebp+try_again_string_point], eax ; 把字符串try_again 的指针保存的局部变量try_again_string_point
    20. .text:080484E2 sub esp, 0Ch
    21. .text:080484E5 push offset aEnterThePasswo ; "Enter the password: "
    22. .text:080484EA call _printf
    23. .text:080484EF add esp, 10h
    24. .text:080484F2 sub esp, 4
    25. .text:080484F5 lea eax, [ebp+input_buffer]
    26. .text:080484F8 push eax
    27. .text:080484F9 push offset check_key
    28. .text:080484FE push offset aU20s ; "%u %20s"
    29. .text:08048503 call ___isoc99_scanf ; 用户input 两个输入:check_key 和20 字节的input_buffer
    30. .text:08048508 add esp, 10h
    31. .text:0804850B mov eax, ds:check_key
    32. .text:08048510 cmp eax, 228BF7Eh
    33. .text:08048515 jz short loc_8048531
    34. .text:08048517 cmp eax, 3AD516Ah
    35. .text:0804851C jnz short loc_8048542 ; 这里根据check_key 的输入来进行跳转到不同的puts 中
    36. .text:0804851E mov eax, try_again
    37. .text:08048523 sub esp, 0Ch
    38. .text:08048526 push eax ; s
    39. .text:08048527 call _puts
    40. .text:0804852C add esp, 10h
    41. .text:0804852F jmp short loc_8048553
    42. .text:08048531 ; ---------------------------------------------------------------------------
    43. .text:08048531
    44. .text:08048531 loc_8048531: ; CODE XREF: main+4C↑j
    45. .text:08048531 mov eax, [ebp+try_again_string_point] ; 我们知道,input_buffer 的大小为16 字节,但是scanf() 输入时是20 字节,所以可以导致try_again_string_point 可以被覆盖,于是需要满足条件input_buffer = 0x228BF7E ,我们就可以控制puts 的输出了.
    46. .text:08048534 sub esp, 0Ch
    47. .text:08048537 push eax ; s
    48. .text:08048538 call _puts
    49. .text:0804853D add esp, 10h
    50. .text:08048540 jmp short loc_8048553
    51. .text:08048542 ; ---------------------------------------------------------------------------
    52. .text:08048542
    53. .text:08048542 loc_8048542: ; CODE XREF: main+53↑j
    54. .text:08048542 mov eax, try_again
    55. .text:08048547 sub esp, 0Ch
    56. .text:0804854A push eax ; s
    57. .text:0804854B call _puts
    58. .text:08048550 add esp, 10h
    59. .text:08048553
    60. .text:08048553 loc_8048553: ; CODE XREF: main+66↑j
    61. .text:08048553 ; main+77↑j
    62. .text:08048553 nop

    从代码主要逻辑可以知道,我们关键的一点在于检查puts() 函数是否接受到了可控的输入.

    1. def main(argv):
    2. path_to_binary = argv[1]
    3. project = angr.Project(path_to_binary)
    4. initial_state = project.factory.entry_state()
    5. class ReplacementScanf(angr.SimProcedure): # 实现Scanf Hook 函数
    6. def run(self, format_string, check_key_address,input_buffer_address):
    7. scanf0 = claripy.BVS('scanf0', 4 * 8) # check_key
    8. scanf1 = claripy.BVS('scanf1', 20 * 8) # input_buffer
    9. for char in scanf1.chop(bits=8):
    10. self.state.add_constraints(char >= '0', char <= 'z') # 对input_buffer 的输入约束
    11. self.state.memory.store(check_key_address, scanf0, endness=project.arch.memory_endness)
    12. self.state.memory.store(input_buffer_address, scanf1,endness=project.arch.memory_endness) # 保存求解变量到指定的内存中
    13. self.state.globals['solution0'] = scanf0 # 保存这两个变量到state 中,后续求解需要用到
    14. self.state.globals['solution1'] = scanf1
    15. scanf_symbol = '__isoc99_scanf'
    16. project.hook_symbol(scanf_symbol, ReplacementScanf()) # Hook scanf 函数
    17. def check_puts(state):
    18. puts_parameter = state.memory.load(state.regs.esp + 4, 4, endness=project.arch.memory_endness) # 获取puts() 函数的参数
    19. if state.se.symbolic(puts_parameter): # 检查这个参数是否为符号化对象
    20. good_job_string_address = 0x4D525854B
    21. copied_state = state.copy() # 复制执行状态上下文进行约束求解,不影响原理的执行上下文
    22. copied_state.add_constraints(puts_parameter == good_job_string_address) # puts 的参数地址是否可以被指定为0x4D525854B ,如果可以的话,那就证明这个值是可控的
    23. if copied_state.satisfiable(): # 判断添加了上面这个约束是否有解
    24. state.add_constraints(puts_parameter == good_job_string_address) # 如果有解的话就保存到我们执行的那个状态对象
    25. return True
    26. else:
    27. return False
    28. else:
    29. return False
    30. simulation = project.factory.simgr(initial_state)
    31. def is_successful(state):
    32. puts_address = 0x8048370 # 当程序执行到puts() 函数时,我们就认为路径探索到了这里,然后再去通过check_puts() 判断这里是否存在漏洞,告诉Angr这是不是我们需要找的那条执行路径
    33. if state.addr == puts_address:
    34. return check_puts(state)
    35. else:
    36. return False
    37. simulation.explore(find=is_successful)
    38. if simulation.found:
    39. solution_state = simulation.found[0]
    40. solution0 = solution_state.se.eval(solution_state.globals['solution0'])
    41. solution1 = solution_state.se.eval(solution_state.globals['solution1'],cast_to=bytes) # 输出字符串序列化的内容
    42. print(solution0,solution1)

    Angr函数使用总结:

    state.copy() => 复制状态上下文

    state.satisfiable() => 判断当前的所有约束是否有解

    solution_state.se.eval(求解变量,cast_to=bytes) => 序列化变量内容为字符串

    16_angr_arbitrary_write

    汇编代码:

    1. .text:08048569 ; int __cdecl main(int argc, const char **argv, const char **envp)
    2. .text:08048569 public main
    3. .text:08048569 main proc near ; DATA XREF: _start+17↑o
    4. .text:08048569
    5. .text:08048569 input_buffer = byte ptr -1Ch
    6. .text:08048569 target_buffer = dword ptr -0Ch
    7. .text:08048569 var_4 = dword ptr -4
    8. .text:08048569 argc = dword ptr 8
    9. .text:08048569 argv = dword ptr 0Ch
    10. .text:08048569 envp = dword ptr 10h
    11. .text:08048569
    12. .text:08048569 ; __unwind {
    13. .text:08048569 lea ecx, [esp+4]
    14. .text:0804856D and esp, 0FFFFFFF0h
    15. .text:08048570 push dword ptr [ecx-4]
    16. .text:08048573 push ebp
    17. .text:08048574 mov ebp, esp
    18. .text:08048576 push ecx
    19. .text:08048577 sub esp, 24h
    20. .text:0804857A mov [ebp+target_buffer], offset unimportant_buffer
    21. .text:08048581 sub esp, 4
    22. .text:08048584 push 10h ; n
    23. .text:08048586 push 0 ; c
    24. .text:08048588 lea eax, [ebp+input_buffer]
    25. .text:0804858B push eax ; s
    26. .text:0804858C call _memset ; 清空input_buffer 的内容
    27. .text:08048591 add esp, 10h
    28. .text:08048594 sub esp, 4
    29. .text:08048597 push 0Ch ; n
    30. .text:08048599 push offset src ; "PASSWORD"
    31. .text:0804859E push offset password_buffer ; dest
    32. .text:080485A3 call _strncpy ; 复制PASSWORD 到全局内存password_buffer
    33. .text:080485A8 add esp, 10h
    34. .text:080485AB sub esp, 0Ch
    35. .text:080485AE push offset aEnterThePasswo ; "Enter the password: "
    36. .text:080485B3 call _printf
    37. .text:080485B8 add esp, 10h
    38. .text:080485BB sub esp, 4
    39. .text:080485BE lea eax, [ebp+input_buffer]
    40. .text:080485C1 push eax
    41. .text:080485C2 push offset check_key
    42. .text:080485C7 push offset aU20s ; "%u %20s"
    43. .text:080485CC call ___isoc99_scanf ; scanf("%u %20s",check_key,input_buffer) .注意input_buffer 的大小是20 字节,栈上的input_buffer 默认的大小是16 字节,最后4 字节可以覆盖target_buffer .
    44. .text:080485D1 add esp, 10h
    45. .text:080485D4 mov eax, ds:check_key
    46. .text:080485D9 cmp eax, 1A25D71h
    47. .text:080485DE jz short loc_80485E9
    48. .text:080485E0 cmp eax, 1CB7D43h
    49. .text:080485E5 jz short loc_8048601 ; 根据check_key 的输入来跳转到不同的_strncpy
    50. .text:080485E7 jmp short loc_8048618
    51. .text:080485E9 ; ---------------------------------------------------------------------------
    52. .text:080485E9
    53. .text:080485E9 loc_80485E9: ; CODE XREF: main+75↑j
    54. .text:080485E9 sub esp, 4
    55. .text:080485EC push 10h ; n
    56. .text:080485EE lea eax, [ebp+input_buffer]
    57. .text:080485F1 push eax ; src
    58. .text:080485F2 push offset unimportant_buffer ; dest
    59. .text:080485F7 call _strncpy
    60. .text:080485FC add esp, 10h
    61. .text:080485FF jmp short loc_804862E
    62. .text:08048601 ; ---------------------------------------------------------------------------
    63. .text:08048601
    64. .text:08048601 loc_8048601: ; CODE XREF: main+7C↑j
    65. .text:08048601 mov eax, [ebp+target_buffer] ; 注意这个是MOV 指令,意思是获取EBP + target_buffer 这个地址的内容保存到EAX 中
    66. .text:08048604 sub esp, 4
    67. .text:08048607 push 10h ; n
    68. .text:08048609 lea edx, [ebp+input_buffer] ; 注意这个是LEA 指令,意思是计算出EBP + input_buffer 的地址保存到EBX 中
    69. .text:0804860C push edx ; src
    70. .text:0804860D push eax ; dest
    71. .text:0804860E call _strncpy ; 漏洞点在这里,strncpy(*target_buffer,input_buffer) ,也就是说input_buffer 最后四字节可以控制对任意地址的_strncpy() .总结起来就是strncpy(input_buffer[ -4 : ],input_buffer,0x10) .
    72. .text:08048613 add esp, 10h
    73. .text:08048616 jmp short loc_804862E
    74. .text:08048618 ; ---------------------------------------------------------------------------
    75. .text:08048618
    76. .text:08048618 loc_8048618: ; CODE XREF: main+7E↑j
    77. .text:08048618 sub esp, 4
    78. .text:0804861B push 10h ; n
    79. .text:0804861D lea eax, [ebp+input_buffer]
    80. .text:08048620 push eax ; src
    81. .text:08048621 push offset unimportant_buffer ; dest
    82. .text:08048626 call _strncpy
    83. .text:0804862B add esp, 10h
    84. .text:0804862E
    85. .text:0804862E loc_804862E: ; CODE XREF: main+96↑j
    86. .text:0804862E ; main+AD↑j
    87. .text:0804862E nop
    88. .text:0804862F sub esp, 4
    89. .text:08048632 push 8 ; n
    90. .text:08048634 push offset key_string ; "KZYRKMKE"
    91. .text:08048639 push offset password_buffer ; s1
    92. .text:0804863E call _strncmp ; 我们知道了上面有一个任意地址写之后,我们就需要改写key_string 或者password_buffer 一致,让_strncmp() 返回0 ,跳转到puts("Good Job")
    93. .text:08048643 add esp, 10h
    94. .text:08048646 test eax, eax
    95. .text:08048648 jz short loc_804865C
    96. .text:0804864A sub esp, 0Ch
    97. .text:0804864D push offset s ; "Try again."
    98. .text:08048652 call _puts
    99. .text:08048657 add esp, 10h
    100. .text:0804865A jmp short loc_804866C
    101. .text:0804865C ; ---------------------------------------------------------------------------
    102. .text:0804865C
    103. .text:0804865C loc_804865C: ; CODE XREF: main+DF↑j
    104. .text:0804865C sub esp, 0Ch
    105. .text:0804865F push offset aGoodJob ; "Good Job."
    106. .text:08048664 call _puts
    107. .text:08048669 add esp, 10h

    汇编代码中的注释已经把整体的逻辑和漏洞原理讲解得差不多了,那么我们就需要做两个判断:一是判断input_buffer 后四字节是否可控;二是前八字节是否可以控制内容为"KZYRKMKE" 或者"PASSWORD" .那么得到的solver.py 代码如下:

    1. def main(argv):
    2. path_to_binary = argv[1]
    3. project = angr.Project(path_to_binary)
    4. initial_state = project.factory.entry_state()
    5. class ReplacementScanf(angr.SimProcedure):
    6. def run(self, format_string, check_key ,input_buffer):
    7. scanf0 = claripy.BVS('scanf0', 4 * 8)
    8. scanf1 = claripy.BVS('scanf1', 20 * 8)
    9. for char in scanf1.chop(bits=8):
    10. self.state.add_constraints(char >= '0', char <= 'z')
    11. self.state.memory.store(check_key, scanf0, endness=project.arch.memory_endness)
    12. self.state.memory.store(input_buffer, scanf1, endness=project.arch.memory_endness)
    13. self.state.globals['solution0'] = scanf0
    14. self.state.globals['solution1'] = scanf1
    15. scanf_symbol = '__isoc99_scanf'
    16. project.hook_symbol(scanf_symbol, ReplacementScanf())
    17. def check_strncpy(state):
    18. strncpy_dest = state.memory.load(state.regs.esp + 4, 4, endness=project.arch.memory_endness) # 获取strncpy() 的参数,strncpy_dest ..
    19. strncpy_src = state.memory.load(state.regs.esp + 8, 4, endness=project.arch.memory_endness)
    20. strncpy_len = state.memory.load(state.regs.esp + 12, 4, endness=project.arch.memory_endness)
    21. src_contents = state.memory.load(strncpy_src, strncpy_len) # 因为参数中只保存了地址,我们需要根据这个地址去获取内容
    22. if state.se.symbolic(strncpy_dest) and state.se.symbolic(src_contents) : # 判断dest 和src 的内容是不是符号化对象
    23. if state.satisfiable(extra_constraints=(src_contents[ -1 : -64 ] == 'KZYRKMKE' ,strncpy_dest == 0x4D52584C)): # 尝试求解,其中strncpy_dest == 0x4D52584C 的意思是判断dest 是否可控为password 的地址;src_contents[ -1 : -64 ] == 'KZYRKMKE' 是判断input_buffer 的内容是否可控为'KZYRKMKE' ,因为这块内存是倒序,所以需要通过[ -1 : -64 ] 倒转(contentes 的内容是比特,获取8 字节的大小为:8*8 = 64),然后判断该值是否为字符串'KZYRKMKE'
    24. state.add_constraints(src_contents[ -1 : -64 ] == 'KZYRKMKE',strncpy_dest == 0x4D52584C)
    25. return True
    26. else:
    27. return False
    28. else:
    29. return False
    30. simulation = project.factory.simgr(initial_state)
    31. def is_successful(state):
    32. strncpy_address = 0x8048410
    33. if state.addr == strncpy_address:
    34. return check_strncpy(state)
    35. else:
    36. return False
    37. simulation.explore(find=is_successful)
    38. if simulation.found:
    39. solution_state = simulation.found[0]
    40. solution0 = solution_state.se.eval(solution_state.globals['solution0'])
    41. solution1 = solution_state.se.eval(solution_state.globals['solution1'],cast_to=bytes)
    42. print(solution0,solution1)

    Angr函数使用总结:

    state.satisfiable(extra_constraints=(条件1,条件2)) => 合并多个条件计算是否存在满足约束的解(注意两个或多个条件之间是And 合并判断,不是Or )

    17_angr_arbitrary_jump

    汇编代码:

    1. .text:4D525886 ; int __cdecl main(int argc, const char **argv, const char **envp)
    2. .text:4D525886 public main
    3. .text:4D525886 main proc near ; DATA XREF: _start+17↑o
    4. .text:4D525886
    5. .text:4D525886 var_C = dword ptr -0Ch
    6. .text:4D525886 var_4 = dword ptr -4
    7. .text:4D525886 argc = dword ptr 8
    8. .text:4D525886 argv = dword ptr 0Ch
    9. .text:4D525886 envp = dword ptr 10h
    10. .text:4D525886
    11. .text:4D525886 ; __unwind {
    12. .text:4D525886 lea ecx, [esp+4]
    13. .text:4D52588A and esp, 0FFFFFFF0h
    14. .text:4D52588D push dword ptr [ecx-4]
    15. .text:4D525890 push ebp
    16. .text:4D525891 mov ebp, esp
    17. .text:4D525893 push ecx
    18. .text:4D525894 sub esp, 14h
    19. .text:4D525897 mov [ebp+var_C], 0
    20. .text:4D52589E sub esp, 0Ch
    21. .text:4D5258A1 push offset aEnterThePasswo ; "Enter the password: "
    22. .text:4D5258A6 call _printf
    23. .text:4D5258AB add esp, 10h
    24. .text:4D5258AE call read_input ; 小细节,注意read_input 是stdcall 的调用方法
    25. .text:4D5258B3 sub esp, 0Ch
    26. .text:4D5258B6 push offset aTryAgain ; "Try again."
    27. .text:4D5258BB call _puts
    28. .text:4D5258C0 add esp, 10h
    29. .text:4D5258C3 mov eax, 0
    30. .text:4D5258C8 mov ecx, [ebp+var_4]
    31. .text:4D5258CB leave
    32. .text:4D5258CC lea esp, [ecx-4]
    33. .text:4D5258CF retn

    main() 函数的逻辑很简单,printf() 输出Enter the password: 然后调用read_input() 函数.继续阅读read_input() 函数的代码:

    1. .text:4D525869 read_input proc near ; CODE XREF: main+28↓p
    2. .text:4D525869
    3. .text:4D525869 input_buffer = byte ptr -2Bh ; input_buffer 大小为0x2B
    4. .text:4D525869
    5. .text:4D525869 ; __unwind {
    6. .text:4D525869 push ebp
    7. .text:4D52586A mov ebp, esp
    8. .text:4D52586C sub esp, 38h ; 栈空间在这里分配
    9. .text:4D52586F sub esp, 8
    10. .text:4D525872 lea eax, [ebp+input_buffer]
    11. .text:4D525875 push eax
    12. .text:4D525876 push offset format ; "%s"
    13. .text:4D52587B call ___isoc99_scanf ; 注意scanf() 的输入长度是没有限制的
    14. .text:4D525880 add esp, 10h
    15. .text:4D525883 nop
    16. .text:4D525884 leave
    17. .text:4D525885 retn

    看完read_input() 的代码之后,我们知道这是一个典型的栈溢出覆盖RET 地址的题目,最后要让RET 地址返回到这个位置

    1. .text:4D525849 print_good proc near
    2. .text:4D525849 ; __unwind {
    3. .text:4D525849 push ebp
    4. .text:4D52584A mov ebp, esp
    5. .text:4D52584C sub esp, 8
    6. .text:4D52584F sub esp, 0Ch
    7. .text:4D525852 push offset s ; "Good Job."
    8. .text:4D525857 call _puts
    9. .text:4D52585C add esp, 10h
    10. .text:4D52585F sub esp, 0Ch
    11. .text:4D525862 push 0 ; status
    12. .text:4D525864 call _exit

    Angr-CTF 解题脚本已经不能在当前的Angr 版本中正常执行了,修改的方法是Hook scanf() 在input_buffer 中构造Vector 进行求解.

    1. def main(argv):
    2. path_to_binary = argv[1]
    3. project = angr.Project(path_to_binary)
    4. initial_state = project.factory.entry_state()
    5. simulation = project.factory.simgr(
    6. initial_state,
    7. save_unconstrained=True,
    8. stashes={
    9. 'active' : [initial_state],
    10. 'unconstrained' : [],
    11. 'found' : [],
    12. 'not_needed' : []
    13. }
    14. )
    15. class ReplacementScanf(angr.SimProcedure):
    16. def run(self, format_string, input_buffer_address):
    17. input_buffer = claripy.BVS('input_buffer', 64 * 8) # 设置一个较大的input_buffer
    18. for char in input_buffer.chop(bits=8):
    19. self.state.add_constraints(char >= '0', char <= 'z')
    20. self.state.memory.store(input_buffer_address, input_buffer, endness=project.arch.memory_endness)
    21. self.state.globals['solution'] = input_buffer
    22. scanf_symbol = '__isoc99_scanf'
    23. project.hook_symbol(scanf_symbol, ReplacementScanf()) # 对scanf() 做Hook
    24. while (simulation.active or simulation.unconstrained) and (not simulation.found): #
    25. for unconstrained_state in simulation.unconstrained:
    26. def should_move(s):
    27. return s is unconstrained_state
    28. simulation.move('unconstrained', 'found', filter_func=should_move) # 保存
    29. simulation.step() # 步进执行
    30. if simulation.found:
    31. solution_state = simulation.found[0]
    32. solution_state.add_constraints(solution_state.regs.eip == 0x4D525849) # 判断EIP 地址是否可控
    33. solution = solution_state.se.eval(solution_state.globals['solution'],cast_to = bytes) # 生成Payload
    34. print(solution)

    点击关注,共同学习!安全狗的自我修养

    github haidragon

  • 相关阅读:
    【ML特征工程】第 7 章 :通过K-Means 模型堆叠进行非线性特征化
    CSS特效002:花样的鼠标悬停效果
    html5怎么实现语音搜索
    【最简便方法】element-plus/element-ui走马灯配置图片以及图片自适应
    利用Velero对K8S备份还原与集群迁移实战
    配置Jetson扩展头--配置CSI相机
    【网络层】MTU、IP数据报分片、IP详解、NAT
    汉威科技亮相上海传感器展并发表主题演讲,智能传感器大有可为
    [附源码]计算机毕业设计JAVA闲置物品线上交易系统
    这三大特性,让 G1 取代了 CMS!
  • 原文地址:https://blog.csdn.net/sinat_35360663/article/details/127592628