Skip to content

haidragon/Angr-CTF-Learn-Note

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 

Repository files navigation

Angr-CTF

如何使用Angr-CTF

建议运行环境为Ubuntu 16.04 ,macOS 下安装Angr 存在一些Bug (比如说Angr 库的安装,Mach-O 文件格式的执行程序有Bug)

找到一个空白的目录,执行命令git clone https://github.com/jakespringer/angr_ctf.git 下载Angr-CTF 项目

如何编译程序

Angr-CTF 有很多题目,每一个目录是一个独立的题目,题目里面没有现成编译好的程序,需要我们手工来编译,我们以第一题为例子编译测试程序

root@sec:~/angr_ctf# cd 00_angr_find/
root@sec:~/angr_ctf/00_angr_find# python generate.py 1234 00_angr_find

generate.py 是程序生成脚本,它的原理是通过我们输入的一个随机数(这里是1234)来对.c.templite 文件进行混淆,然后编译输出到一个文件名(这里的文件名是00_angr_find ).

Angr-CTF 有一个解题的模版Python 文件(名字为scaffold00.py ),如果我们是在Python3 下安装的Angr 库,那么就需要使用Python3 来执行脚本,效果如下:

root@sec:~/angr_ctf/00_angr_find# python3 scaffold00.py 
WARNING | 2019-05-11 14:42:28,542 | angr.state_plugins.symbolic_memory | The program is accessing memory or registers with an unspecified value. This could indicate unwanted behavior.
WARNING | 2019-05-11 14:42:28,543 | angr.state_plugins.symbolic_memory | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by:
WARNING | 2019-05-11 14:42:28,543 | angr.state_plugins.symbolic_memory | 1) setting a value to the initial state
WARNING | 2019-05-11 14:42:28,543 | angr.state_plugins.symbolic_memory | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | 2019-05-11 14:42:28,543 | angr.state_plugins.symbolic_memory | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY_REGISTERS}, to suppress these messages.
WARNING | 2019-05-11 14:42:28,543 | angr.state_plugins.symbolic_memory | Filling register edi with 4 unconstrained bytes referenced from 0x80486b1 (__libc_csu_init+0x1 in test_code (0x80486b1))
WARNING | 2019-05-11 14:42:28,547 | angr.state_plugins.symbolic_memory | Filling register ebx with 4 unconstrained bytes referenced from 0x80486b3 (__libc_csu_init+0x3 in test_code (0x80486b3))
WARNING | 2019-05-11 14:42:30,063 | angr.state_plugins.symbolic_memory | Filling memory at 0x7fff0000 with 83 unconstrained bytes referenced from 0x9074ee0 (strcmp+0x0 in libc.so.6 (0x74ee0))
WARNING | 2019-05-11 14:42:30,064 | angr.state_plugins.symbolic_memory | Filling memory at 0x7ffeff60 with 4 unconstrained bytes referenced from 0x9074ee0 (strcmp+0x0 in libc.so.6 (0x74ee0))
b'FMKGABFY'

话不多说,接下来开始体验Angr 符号执行库强大的地方吧~

00_angr_find

汇编代码:

.text:0804864E                 push    offset s2       ; "FPQPMQXT"
.text:08048653                 lea     eax, [ebp+s1]
.text:08048656                 push    eax             ; s1
.text:08048657                 call    _strcmp
.text:0804865C                 add     esp, 10h
.text:0804865F                 test    eax, eax
.text:08048661                 jz      short loc_8048675
.text:08048663                 sub     esp, 0Ch
.text:08048666                 push    offset s        ; "Try again."
.text:0804866B                 call    _puts
.text:08048670                 add     esp, 10h
.text:08048673                 jmp     short loc_8048685
.text:08048675 ; ---------------------------------------------------------------------------
.text:08048675
.text:08048675 loc_8048675:                            ; CODE XREF: main+9A↑j
.text:08048675                 sub     esp, 0Ch
.text:08048678                 push    offset aGoodJob ; "Good Job."
.text:0804867D                 call    _puts
.text:08048682                 add     esp, 10h
.text:08048685
.text:08048685 loc_8048685:

使用explore() 函数探索路径,主要目的是要找到'Good Job'这条路径,所以在expolore(find=???)这里填写的是0x8048678这个地址,然后让Angr自己去执行寻找路径

  path_to_binary = './test_code'  # :string
  project = angr.Project(path_to_binary)
  initial_state = project.factory.entry_state()
  simulation = project.factory.simgr(initial_state)
  print_good_address = 0x8048678  # :integer (probably in hexadecimal)
  simulation.explore(find=print_good_address)

Angr函数使用总结:

angr.Project(执行的二进制文件地址) => 打开二进制文件

project.factory.entry_state() => 创建空白的执行上下文环境

project.factory.simgr(上下文对象) => 创建模拟器

simulation.explore(find = 搜索程序执行路径的地址) => 执行路径探索

01_angr_avoid

汇编代码:

.text:0804890F                 jz      short loc_804892E
.text:08048911                 call    avoid_me
.text:08048916                 sub     esp, 8
.text:08048919                 lea     eax, [ebp+var_20]
.text:0804891C                 push    eax
.text:0804891D                 lea     eax, [ebp+var_34]
.text:08048920                 push    eax
.text:08048921                 call    maybe_good
.text:08048926                 add     esp, 10h
.text:08048929                 jmp     loc_80D456F
.text:0804892E ; ---------------------------------------------------------------------------
.text:0804892E
.text:0804892E loc_804892E:                            ; CODE XREF: main+30D↑j
.text:0804892E                 sub     esp, 8
.text:08048931                 lea     eax, [ebp+var_20]
.text:08048934                 push    eax
.text:08048935                 lea     eax, [ebp+var_34]
.text:08048938                 push    eax
.text:08048939                 call    maybe_good
.text:0804893E                 add     esp, 10h
.text:08048941                 jmp     loc_80D456F
.text:08048946 ; ---------------------------------------------------------------------------
.text:08048946
.text:08048946 loc_8048946:                            ; CODE XREF: main+2E5↑j
.text:08048946                 call    avoid_me

.....

01_angr_avoid 有很多垃圾代码插入在main() 函数这里,我们没有办法直接在main() 函数的这些分支语句中定位准确的路径,所以我们需要换一个方式,来看一下maybe_good() 函数的代码

text:080485B5                 public maybe_good
.text:080485B5 maybe_good      proc near               ; CODE XREF: main+31F↓p
.text:080485B5                                         ; main+337↓p ...
.text:080485B5
.text:080485B5 arg_0           = dword ptr  8
.text:080485B5 arg_4           = dword ptr  0Ch
.text:080485B5
.text:080485B5 ; __unwind {
.text:080485B5                 push    ebp
.text:080485B6                 mov     ebp, esp
.text:080485B8                 sub     esp, 8
.text:080485BB                 movzx   eax, should_succeed
.text:080485C2                 test    al, al
.text:080485C4                 jz      short loc_80485EF
.text:080485C6                 sub     esp, 4
.text:080485C9                 push    8
.text:080485CB                 push    [ebp+arg_4]
.text:080485CE                 push    [ebp+arg_0]
.text:080485D1                 call    _strncmp
.text:080485D6                 add     esp, 10h
.text:080485D9                 test    eax, eax
.text:080485DB                 jnz     short loc_80485EF
.text:080485DD                 sub     esp, 0Ch
.text:080485E0                 push    offset aGoodJob ; "Good Job."
.text:080485E5                 call    _puts
.text:080485EA                 add     esp, 10h
.text:080485ED                 jmp     short loc_80485FF
.text:080485EF ; ---------------------------------------------------------------------------
.text:080485EF
.text:080485EF loc_80485EF:                            ; CODE XREF: maybe_good+F↑j
.text:080485EF                                         ; maybe_good+26↑j
.text:080485EF                 sub     esp, 0Ch
.text:080485F2                 push    offset aTryAgain ; "Try again."
.text:080485F7                 call    _puts
.text:080485FC                 add     esp, 10h
.text:080485FF
.text:080485FF loc_80485FF:                            ; CODE XREF: maybe_good+38↑j
.text:080485FF                 nop
.text:08048600                 leave
.text:08048601                 retn
.text:08048601 ; } // starts at 80485B5
.text:08048601 maybe_good      endp
.text:08048601

在maybe_good() 函数的实现里,发现和00_angr_find 一样的逻辑 — 一个分支和两个输出,那么我们就应该知道:"Good Job" 是我们要搜索的目标路径,"Try Again" 是我们要排除的路径,那么用explore() 函数来筛选,方式是用explore(find=0x80485E0,avoid=0x80485F2)来筛选,解答代码如下:

import angr
import sys

def main(argv):
  path_to_binary = './01_angr_avoid'
  project = angr.Project(path_to_binary)
  initial_state = project.factory.entry_state()
  simulation = project.factory.simgr(initial_state)

  print_good_address = 0x080485DD
  will_not_succeed_address = 0x80485EF
  simulation.explore(find=print_good_address, avoid=will_not_succeed_address)

  if simulation.found:
    solution_state = simulation.found[0]
    print(solution_state.posix.dumps(sys.stdin.fileno()))
  else:
    raise Exception('Could not find the solution')

if __name__ == '__main__':
  main(sys.argv)

Angr函数使用总结:

simulation.explore(find = 要搜索的路径地址, avoid = 要排除执行路径地址) => 路径探索

simulation.found => 搜索结果集合,这是一个python list 对象

solution_state.posix.dumps( => 获取Payload

02_angr_find_condition

汇编代码:

.text:0804876B loc_804876B:                            ; CODE XREF: main+112↑j
.text:0804876B                 cmp     [ebp+var_38], 0DEADBEEFh
.text:08048772                 jz      short loc_80487B5
.text:08048774                 sub     esp, 8
.text:08048777                 lea     eax, [ebp+s2]
.text:0804877A                 push    eax             ; s2
.text:0804877B                 lea     eax, [ebp+s1]
.text:0804877E                 push    eax             ; s1
.text:0804877F                 call    _strcmp
.text:08048784                 add     esp, 10h
.text:08048787                 test    eax, eax
.text:08048789                 jz      short loc_80487A0
.text:0804878B                 sub     esp, 0Ch
.text:0804878E                 push    offset s        ; "Try again."
.text:08048793                 call    _puts
.text:08048798                 add     esp, 10h
.text:0804879B                 jmp     loc_804D267
.text:080487A0 ; ---------------------------------------------------------------------------
.text:080487A0
.text:080487A0 loc_80487A0:                            ; CODE XREF: main+1C1↑j
.text:080487A0                 sub     esp, 0Ch
.text:080487A3                 push    offset aGoodJob ; "Good Job."
.text:080487A8                 call    _puts
.text:080487AD                 add     esp, 10h
.text:080487B0                 jmp     loc_804D267
.text:080487B5 ; ---------------------------------------------------------------------------
.text:080487B5
.text:080487B5 loc_80487B5:                            ; CODE XREF: main+1AA↑j
.text:080487B5                 sub     esp, 8
.text:080487B8                 lea     eax, [ebp+s2]
.text:080487BB                 push    eax             ; s2
.text:080487BC                 lea     eax, [ebp+s1]
.text:080487BF                 push    eax             ; s1
.text:080487C0                 call    _strcmp
.text:080487C5                 add     esp, 10h
.text:080487C8                 test    eax, eax
.text:080487CA                 jz      short loc_80487E1
.text:080487CC                 sub     esp, 0Ch
.text:080487CF                 push    offset s        ; "Try again."
.text:080487D4                 call    _puts
.text:080487D9                 add     esp, 10h
.text:080487DC                 jmp     loc_804D267
.text:080487E1 ; ---------------------------------------------------------------------------
.text:080487E1
.text:080487E1 loc_80487E1:                            ; CODE XREF: main+202↑j
.text:080487E1                 sub     esp, 0Ch
.text:080487E4                 push    offset aGoodJob ; "Good Job."
.text:080487E9                 call    _puts

02_angr_find_condition 主要是把逻辑判断通过混淆打乱在各个分支上,导致无法使用find 和avoid 直接对单个地址进行定位.explore() 的find 和avoid 可以通过传递回调函数来实现目的地址检验和排除判断.对于这样的混淆思路,解决方法是通过判断控制台输出数据是不是"Good Job" 和"Try again" 来确认执行到了成功还是失败分支.

def main(argv):
  path_to_binary = argv[1]
  project = angr.Project(path_to_binary)
  initial_state = project.factory.entry_state()
  simulation = project.factory.simgr(initial_state)

  def is_successful(state):
    stdout_output = state.posix.dumps(sys.stdout.fileno())

    return 'Good Job' in str(stdout_output) # :boolean

  def should_abort(state): 
    stdout_output = state.posix.dumps(sys.stdout.fileno())
    
    return 'Try again' in str(stdout_output)  # :boolean

  simulation.explore(find=is_successful, avoid=should_abort) 
    

Angr函数使用总结:

simulation.explore(find = 回调函数, avoid = 回调函数) => 路径探索

explore() 函数的回调函数格式为:

def recall_explore(state) :

​ ...

​ return True / False # True 意思是发现了该路径,False 则是忽略

state.posix.dumps(sys.stdout.fileno()) => 获取模拟执行的控制台输出

03_angr_symbolic_registers

汇编代码:

.text:080488E8 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:080488E8                 public main
.text:080488E8 main            proc near               ; DATA XREF: _start+17↑o
.text:080488E8
.text:080488E8 var_14          = dword ptr -14h
.text:080488E8 var_10          = dword ptr -10h
.text:080488E8 var_C           = dword ptr -0Ch
.text:080488E8 var_4           = dword ptr -4
.text:080488E8 argc            = dword ptr  8
.text:080488E8 argv            = dword ptr  0Ch
.text:080488E8 envp            = dword ptr  10h
.text:080488E8
.text:080488E8 ; __unwind {
.text:080488E8                 lea     ecx, [esp+4]
.text:080488EC                 and     esp, 0FFFFFFF0h
.text:080488EF                 push    dword ptr [ecx-4]
.text:080488F2                 push    ebp
.text:080488F3                 mov     ebp, esp
.text:080488F5                 push    ecx
.text:080488F6                 sub     esp, 14h
.text:080488F9                 sub     esp, 0Ch
.text:080488FC                 push    offset aEnterThePasswo ; "Enter the password: "
.text:08048901                 call    _printf
.text:08048906                 add     esp, 10h
.text:08048909                 call    get_user_input
.text:0804890E                 mov     [ebp+var_14], eax
.text:08048911                 mov     [ebp+var_10], ebx
.text:08048914                 mov     [ebp+var_C], edx
.text:08048917                 sub     esp, 0Ch
.text:0804891A                 push    [ebp+var_14]
.text:0804891D                 call    complex_function_1
.text:08048922                 add     esp, 10h
.text:08048925                 mov     ecx, eax
.text:08048927                 mov     [ebp+var_14], ecx
.text:0804892A                 sub     esp, 0Ch
.text:0804892D                 push    [ebp+var_10]
.text:08048930                 call    complex_function_2
.text:08048935                 add     esp, 10h
.text:08048938                 mov     ecx, eax
.text:0804893A                 mov     [ebp+var_10], ecx
.text:0804893D                 sub     esp, 0Ch
.text:08048940                 push    [ebp+var_C]
.text:08048943                 call    complex_function_3
.text:08048948                 add     esp, 10h
.text:0804894B                 mov     ecx, eax
.text:0804894D                 mov     [ebp+var_C], ecx
.text:08048950                 cmp     [ebp+var_14], 0
.text:08048954                 jnz     short loc_8048962
.text:08048956                 cmp     [ebp+var_10], 0
.text:0804895A                 jnz     short loc_8048962
.text:0804895C                 cmp     [ebp+var_C], 0
.text:08048960                 jz      short loc_8048974
.text:08048962
.text:08048962 loc_8048962:                            ; CODE XREF: main+6C↑j
.text:08048962                                         ; main+72↑j
.text:08048962                 sub     esp, 0Ch
.text:08048965                 push    offset s        ; "Try again."
.text:0804896A                 call    _puts
.text:0804896F                 add     esp, 10h
.text:08048972                 jmp     short loc_8048984
.text:08048974 ; ---------------------------------------------------------------------------
.text:08048974
.text:08048974 loc_8048974:                            ; CODE XREF: main+78↑j
.text:08048974                 sub     esp, 0Ch
.text:08048977                 push    offset aGoodJob ; "Good Job."
.text:0804897C                 call    _puts
.text:08048981                 add     esp, 10h
.text:08048984
.text:08048984 loc_8048984:                            ; CODE XREF: main+8A↑j
.text:08048984                 mov     ecx, 0
.text:08048989                 mov     eax, ecx
.text:0804898B                 mov     ecx, [ebp+var_4]
.text:0804898E                 leave
.text:0804898F                 lea     esp, [ecx-4]
.text:08048992                 retn

03_angr_symbolic_registers 主要是多个complex_function 生成数据然后和用户输入进行判断,然后把输入校验的结果在(0x8048950 - 0x8048960)这几个cmp + jz/jnz 判断中进行校验,因为这个时候有三个输入,所以需要分开来求解,我们先来看complex_function() 函数的调用部分

.text:0804890E                 mov     [ebp+var_14], eax
.text:08048911                 mov     [ebp+var_10], ebx
.text:08048914                 mov     [ebp+var_C], edx
.text:08048917                 sub     esp, 0Ch
.text:0804891A                 push    [ebp+var_14]
.text:0804891D                 call    complex_function_1
.text:08048922                 add     esp, 10h
.text:08048925                 mov     ecx, eax
.text:08048927                 mov     [ebp+var_14], ecx
.text:0804892A                 sub     esp, 0Ch
.text:0804892D                 push    [ebp+var_10]
.text:08048930                 call    complex_function_2
.text:08048935                 add     esp, 10h
.text:08048938                 mov     ecx, eax
.text:0804893A                 mov     [ebp+var_10], ecx
.text:0804893D                 sub     esp, 0Ch
.text:08048940                 push    [ebp+var_C]
.text:08048943                 call    complex_function_3

可以看到,EAX EBX EDX 分别是complex_function1-3 的输入参数,那么我们就需要求解EAX EBX EDX 的值.那么我们就需要从0x804890E 处开始执行代码,并在符合条件的路径("Good Job")处求解EAX EBX EDX 的值.

def main(argv):
  path_to_binary = argv[1]
  project = angr.Project(path_to_binary)
  start_address = 0x804890E  # :integer (probably hexadecimal)
  initial_state = project.factory.blank_state(addr=start_address)

  password0_size_in_bits = 4 * 8  #  因为complex_function 输出一个int 类型的数据,那就是32bits
  password0 = claripy.BVS('password0', password0_size_in_bits)
  password1 = claripy.BVS('password1', password0_size_in_bits)
  password2 = claripy.BVS('password2', password0_size_in_bits)

  initial_state.regs.eax = password0  #  告诉符号执行引擎这三个寄存器分别是complex_function 的参数
  initial_state.regs.ebx = password1
  initial_state.regs.edx = password2

  simulation = project.factory.simgr(initial_state)

  def is_successful(state):
    stdout_output = state.posix.dumps(sys.stdout.fileno())
    return 'Good Job' in str(stdout_output)  #  根据输出来判断执行路径

  def should_abort(state):
    stdout_output = state.posix.dumps(sys.stdout.fileno())
    return 'Try again.' in str(stdout_output)

  simulation.explore(find=is_successful, avoid=should_abort)

  if simulation.found:
    solution_state = simulation.found[0]

    solution0 = solution_state.se.eval(password0)
    solution1 = solution_state.se.eval(password1)
    solution2 = solution_state.se.eval(password2)

    solution = ' '.join(map('{:x}'.format, [ solution0, solution1, solution2 ]))  # :string
    print(solution)
  else:
    raise Exception('Could not find the solution')

Angr函数使用总结:

project.factory.blank_state(addr=start_address) => 创建自定义入口的状态上下文

initial_state.regs => 操作状态上下文的寄存器

claripy.BVS('变量名', 变量大小) => 创建求解变量

solution_state.se.eval(变量) => 求解符号变量

04_angr_symbolic_registers

汇编代码:

.text:080486F4 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:080486F4                 public main
.text:080486F4 main            proc near               ; DATA XREF: _start+17↑o
.text:080486F4
.text:080486F4 var_4           = dword ptr -4
.text:080486F4 argc            = dword ptr  8
.text:080486F4 argv            = dword ptr  0Ch
.text:080486F4 envp            = dword ptr  10h
.text:080486F4
.text:080486F4 ; __unwind {
.text:080486F4                 lea     ecx, [esp+4]
.text:080486F8                 and     esp, 0FFFFFFF0h
.text:080486FB                 push    dword ptr [ecx-4]
.text:080486FE                 push    ebp
.text:080486FF                 mov     ebp, esp
.text:08048701                 push    ecx
.text:08048702                 sub     esp, 4
.text:08048705                 sub     esp, 0Ch
.text:08048708                 push    offset aEnterThePasswo ; "Enter the password: "
.text:0804870D                 call    _printf
.text:08048712                 add     esp, 10h
.text:08048715                 call    handle_user
.text:0804871A                 mov     eax, 0
.text:0804871F                 mov     ecx, [ebp+var_4]
.text:08048722                 leave
.text:08048723                 lea     esp, [ecx-4]
.text:08048726                 retn

主要的代码逻辑在handle_user() 函数里面,再来看看代码

.text:08048679 handle_user     proc near               ; CODE XREF: main+21↓p
.text:08048679
.text:08048679 var_10          = dword ptr -10h
.text:08048679 var_C           = dword ptr -0Ch
.text:08048679
.text:08048679 ; __unwind {
.text:08048679                 push    ebp
.text:0804867A                 mov     ebp, esp
.text:0804867C                 sub     esp, 18h
.text:0804867F                 sub     esp, 4
.text:08048682                 lea     eax, [ebp+var_10]
.text:08048685                 push    eax
.text:08048686                 lea     eax, [ebp+var_C]
.text:08048689                 push    eax
.text:0804868A                 push    offset aUU      ; "%u %u"
.text:0804868F                 call    ___isoc99_scanf
.text:08048694                 add     esp, 10h
.text:08048697                 mov     eax, [ebp+var_C]   ; Argument complex_function0
.text:0804869A                 sub     esp, 0Ch
.text:0804869D                 push    eax
.text:0804869E                 call    complex_function0  ; Call complex_function0
.text:080486A3                 add     esp, 10h
.text:080486A6                 mov     [ebp+var_C], eax
.text:080486A9                 mov     eax, [ebp+var_10]  ; Argument complex_function1
.text:080486AC                 sub     esp, 0Ch
.text:080486AF                 push    eax
.text:080486B0                 call    complex_function1  ; Call complex_function1
.text:080486B5                 add     esp, 10h
.text:080486B8                 mov     [ebp+var_10], eax
.text:080486BB                 mov     eax, [ebp+var_C]
.text:080486BE                 cmp     eax, 0D3062A4Ch
.text:080486C3                 jnz     short loc_80486CF  ; Check Value with input
.text:080486C5                 mov     eax, [ebp+var_10]
.text:080486C8                 cmp     eax, 694E5BA0h
.text:080486CD                 jz      short loc_80486E1
.text:080486CF
.text:080486CF loc_80486CF:                            ; CODE XREF: handle_user+4A↑j
.text:080486CF                 sub     esp, 0Ch
.text:080486D2                 push    offset s        ; "Try again."
.text:080486D7                 call    _puts
.text:080486DC                 add     esp, 10h
.text:080486DF                 jmp     short loc_80486F1
.text:080486E1 ; ---------------------------------------------------------------------------
.text:080486E1
.text:080486E1 loc_80486E1:                            ; CODE XREF: handle_user+54↑j
.text:080486E1                 sub     esp, 0Ch
.text:080486E4                 push    offset aGoodJob ; "Good Job."
.text:080486E9                 call    _puts
.text:080486EE                 add     esp, 10h
.text:080486F1
.text:080486F1 loc_80486F1:                            ; CODE XREF: handle_user+66↑j
.text:080486F1                 nop
.text:080486F2                 leave
.text:080486F3                 retn

可以看到,现在complex_function 的参数是通过栈来传输的,complex_function0 主要的代码是运算一些数据保存到arg_0 中,所以我们才需要跟踪执行这个栈上的参数

.text:080484A9 complex_function0 proc near             ; CODE XREF: handle_user+25↓p
.text:080484A9
.text:080484A9 arg_0           = dword ptr  8
.text:080484A9
.text:080484A9 ; __unwind {
.text:080484A9                 push    ebp
.text:080484AA                 mov     ebp, esp
.text:080484AC                 xor     [ebp+arg_0], 0D53642BEh
.text:080484B3                 xor     [ebp+arg_0], 58FC2926h
.text:080484BA                 xor     [ebp+arg_0], 25596A36h
.text:080484C1                 xor     [ebp+arg_0], 0A7AFAA43h
.text:080484C8                 xor     [ebp+arg_0], 1559CAFEh
.text:080484CF                 xor     [ebp+arg_0], 0D8D89C66h
.text:080484D6                 xor     [ebp+arg_0], 6B8B30B6h
.text:080484DD                 xor     [ebp+arg_0], 0B5E7C180h
.text:080484E4                 xor     [ebp+arg_0], 1FA429F6h
.text:080484EB                 xor     [ebp+arg_0], 21C70AF4h
.text:080484F2                 xor     [ebp+arg_0], 0B7261E1Dh
.text:080484F9                 xor     [ebp+arg_0], 0ADD88AD8h
.text:08048500                 xor     [ebp+arg_0], 3E16A0F2h
.text:08048507                 xor     [ebp+arg_0], 0DF2308FBh
.text:0804850E                 xor     [ebp+arg_0], 2273AAFh
.text:08048515                 xor     [ebp+arg_0], 8E69AC70h
.text:0804851C                 xor     [ebp+arg_0], 0AC8924h
.text:08048523                 xor     [ebp+arg_0], 561B782h
.text:0804852A                 xor     [ebp+arg_0], 5A64A924h
.text:08048531                 xor     [ebp+arg_0], 0B118005Bh
.text:08048538                 xor     [ebp+arg_0], 61461EA2h
.text:0804853F                 xor     [ebp+arg_0], 0E0E04E79h
.text:08048546                 xor     [ebp+arg_0], 0A8DDACAAh
.text:0804854D                 xor     [ebp+arg_0], 82AF667Dh
.text:08048554                 xor     [ebp+arg_0], 0B3CB4464h
.text:0804855B                 xor     [ebp+arg_0], 43B7BB1Ah
.text:08048562                 xor     [ebp+arg_0], 0DF30F25Bh
.text:08048569                 xor     [ebp+arg_0], 4C0F3376h
.text:08048570                 xor     [ebp+arg_0], 0B2E462E5h
.text:08048577                 xor     [ebp+arg_0], 7BF4CFC3h
.text:0804857E                 xor     [ebp+arg_0], 0C2960388h
.text:08048585                 xor     [ebp+arg_0], 27071524h
.text:0804858C                 mov     eax, [ebp+arg_0]
.text:0804858F                 pop     ebp
.text:08048590                 retn
.text:08048590 ; } // starts at 80484A9

再回来看两个函数的调用的栈情况:

.text:08048697           >     mov     eax, [ebp+var_C]   ; Argument complex_function0
.text:0804869A                 sub     esp, 0Ch
.text:0804869D                 push    eax
.text:0804869E                 call    complex_function0  ; Call complex_function0
.text:080486A3                 add     esp, 10h
.text:080486A6                 mov     [ebp+var_C], eax
.text:080486A9           >     mov     eax, [ebp+var_10]  ; Argument complex_function1
.text:080486AC                 sub     esp, 0Ch
.text:080486AF                 push    eax
.text:080486B0                 call    complex_function1  ; Call complex_function1

此时我们可以知道,var_C 和var_10 都是在栈上是连续的,那么我们就需要构造两个连续的push data ,把password 保存到栈上,并调节esp - 8 .

def main(argv):
  path_to_binary = argv[1]
  project = angr.Project(path_to_binary)
  start_address = 0x8048697
  initial_state = project.factory.blank_state(addr=start_address)
  
  initial_state.regs.ebp = initial_state.regs.esp

  password0 = claripy.BVS('password0', 4 * 8)  #  int 
  password1 = claripy.BVS('password1', 4 * 8)
  
  padding_length_in_bytes = 8  # integer * 2
  initial_state.regs.esp -= padding_length_in_bytes

  initial_state.stack_push(password0)  # :bitvector (claripy.BVS, claripy.BVV, claripy.BV)
  initial_state.stack_push(password1)

  simulation = project.factory.simgr(initial_state)

  simulation.explore(find=0x80486E4,avoid=0x80486D2)

  if simulation.found:
    solution_state = simulation.found[0]

    solution0 = solution_state.se.eval(password0)
    solution1 = solution_state.se.eval(password1)
    solution = ' '.join(map('{:x}'.format, [ solution0, solution1 ]))  # :string
    print(solution)
  

05_angr_symbolic_memory

汇编代码:

.text:080485E0                 push    offset unk_9FD92B8
.text:080485E5                 push    offset unk_9FD92B0
.text:080485EA                 push    offset unk_9FD92A8
.text:080485EF                 push    offset user_input
.text:080485F4                 push    offset a8s8s8s8s ; "%8s %8s %8s %8s"
.text:080485F9                 call    ___isoc99_scanf  ;  用户输入
.text:080485FE                 add     esp, 20h
.text:08048601                 mov     [ebp+var_C], 0
.text:08048608                 jmp     short loc_8048637  ;  注意这里有一个循环
.text:0804860A ; ---------------------------------------------------------------------------
.text:0804860A
.text:0804860A loc_804860A:                            ; CODE XREF: main+93↓j
.text:0804860A                 mov     eax, [ebp+var_C]
.text:0804860D                 add     eax, 9FD92A0h
.text:08048612                 movzx   eax, byte ptr [eax]  ;  Argument complex_function
.text:08048615                 movsx   eax, al
.text:08048618                 sub     esp, 8
.text:0804861B                 push    [ebp+var_C]
.text:0804861E                 push    eax
.text:0804861F                 call    complex_function  ;  计算函数
.text:08048624                 add     esp, 10h
.text:08048627                 mov     edx, eax
.text:08048629                 mov     eax, [ebp+var_C]
.text:0804862C                 add     eax, 9FD92A0h
.text:08048631                 mov     [eax], dl
.text:08048633                 add     [ebp+var_C], 1
.text:08048637
.text:08048637 loc_8048637:                            ; CODE XREF: main+60↑j
.text:08048637                 cmp     [ebp+var_C], 1Fh
.text:0804863B                 jle     short loc_804860A
.text:0804863D                 sub     esp, 4
.text:08048640                 push    20h             ; n
.text:08048642                 push    offset s2       ; "THNJXTHBJUCDIMEEMLZNGMHISXAIXDQG"
.text:08048647                 push    offset user_input ; s1
.text:0804864C                 call    _strncmp
.text:08048651                 add     esp, 10h
.text:08048654                 test    eax, eax
.text:08048656                 jz      short loc_804866A  ; 判断输入和complex_function是否相等
.text:08048658                 sub     esp, 0Ch
.text:0804865B                 push    offset s        ; "Try again."
.text:08048660                 call    _puts
.text:08048665                 add     esp, 10h
.text:08048668                 jmp     short loc_804867A
.text:0804866A ; ---------------------------------------------------------------------------
.text:0804866A
.text:0804866A loc_804866A:                            ; CODE XREF: main+AE↑j
.text:0804866A                 sub     esp, 0Ch
.text:0804866D                 push    offset aGoodJob ; "Good Job."
.text:08048672                 call    _puts
.text:08048677                 add     esp, 10h
.text:0804867A
.text:0804867A loc_804867A:                            ; CODE XREF: main+C0↑j
.text:0804867A                 mov     eax, 0
.text:0804867F                 mov     ecx, [ebp+var_4]
.text:08048682                 leave
.text:08048683                 lea     esp, [ecx-4]
.text:08048686                 retn

那么现在我们的目标就是要关注下面这四块内存

.text:080485E0                 push    offset unk_9FD92B8
.text:080485E5                 push    offset unk_9FD92B0
.text:080485EA                 push    offset unk_9FD92A8
.text:080485EF                 push    offset user_input

每一块内存的大小是8Byte

.text:080485F4                 push    offset a8s8s8s8s ; "%8s %8s %8s %8s"
.text:080485F9                 call    ___isoc99_scanf  ;  用户输入

程序执行地址为0x8048601 ,在scanf 调整栈内存之后(.text:080485FE add esp, 20h)开始执行.

def main(argv):
  path_to_binary = argv[1]
  project = angr.Project(path_to_binary)

  start_address = 0x8048601
  initial_state = project.factory.blank_state(addr=start_address)

  password0 = claripy.BVS('password0', 8 * 8)
  password1 = claripy.BVS('password1', 8 * 8)
  password2 = claripy.BVS('password2', 8 * 8)
  password3 = claripy.BVS('password3', 8 * 8)

  password0_address = 0x9FD92A0
  initial_state.memory.store(password0_address, password0)
  password1_address = 0x9FD92A8
  initial_state.memory.store(password1_address, password1)
  password2_address = 0x9FD92B0
  initial_state.memory.store(password2_address, password2)
  password3_address = 0x9FD92B8
  initial_state.memory.store(password3_address, password3)

  simulation = project.factory.simgr(initial_state)

  def is_successful(state):
    stdout_output = state.posix.dumps(sys.stdout.fileno())
    return 'Good Job' in str(stdout_output)

  def should_abort(state):
    stdout_output = state.posix.dumps(sys.stdout.fileno())
    return 'Try again' in str(stdout_output)

  simulation.explore(find=is_successful, avoid=should_abort)

  if simulation.found:
    solution_state = simulation.found[0]
    solution0 = solution_state.se.eval(password0)
    solution1 = solution_state.se.eval(password1)
    solution2 = solution_state.se.eval(password2)
    solution3 = solution_state.se.eval(password3)
    solution = ' '.join(map('{:x}'.format, [ solution0, solution1,solution2,solution3 ]))

    print(solution)

Angr函数使用总结:

initial_state.memory.store(地址,数据) => 初始化内存地址中的数据

06_angr_symbolic_dynamic_memory

汇编代码:

.text:08048621                 push    9               ; size
.text:08048623                 call    _malloc
.text:08048628                 add     esp, 10h
.text:0804862B                 mov     ds:buffer0, eax
.text:08048630                 sub     esp, 0Ch
.text:08048633                 push    9               ; size
.text:08048635                 call    _malloc
.text:0804863A                 add     esp, 10h
.text:0804863D                 mov     ds:buffer1, eax
.text:08048642                 mov     eax, ds:buffer0
.text:08048647                 sub     esp, 4
.text:0804864A                 push    9               ; n
.text:0804864C                 push    0               ; c
.text:0804864E                 push    eax             ; s
.text:0804864F                 call    _memset
.text:08048654                 add     esp, 10h
.text:08048657                 mov     eax, ds:buffer1
.text:0804865C                 sub     esp, 4
.text:0804865F                 push    9               ; n
.text:08048661                 push    0               ; c
.text:08048663                 push    eax             ; s
.text:08048664                 call    _memset
.text:08048669                 add     esp, 10h
.text:0804866C                 sub     esp, 0Ch
.text:0804866F                 push    offset aEnterThePasswo ; "Enter the password: "
.text:08048674                 call    _printf
.text:08048679                 add     esp, 10h
.text:0804867C                 mov     edx, ds:buffer1
.text:08048682                 mov     eax, ds:buffer0
.text:08048687                 sub     esp, 4
.text:0804868A                 push    edx
.text:0804868B                 push    eax
.text:0804868C                 push    offset a8s8s    ; "%8s %8s"
.text:08048691                 call    ___isoc99_scanf

这次scanf() 有两个输入参数,数据保存的位置是通过全局变量的char* 指针来保存到buffer 中,大小为8 字节.

.text:08048699                 mov     [ebp+var_C], 0
.text:080486A0                 jmp     short loc_8048706
.text:080486A2 ; ---------------------------------------------------------------------------
.text:080486A2
.text:080486A2 loc_80486A2:                            ; CODE XREF: main+FE↓j
.text:080486A2                 mov     edx, ds:buffer0
.text:080486A8                 mov     eax, [ebp+var_C]
.text:080486AB                 lea     ebx, [edx+eax]
.text:080486AE                 mov     edx, ds:buffer0
.text:080486B4                 mov     eax, [ebp+var_C]
.text:080486B7                 add     eax, edx
.text:080486B9                 movzx   eax, byte ptr [eax]
.text:080486BC                 movsx   eax, al
.text:080486BF                 sub     esp, 8
.text:080486C2                 push    [ebp+var_C]
.text:080486C5                 push    eax
.text:080486C6                 call    complex_function
.text:080486CB                 add     esp, 10h
.text:080486CE                 mov     [ebx], al
.text:080486D0                 mov     edx, ds:buffer1
.text:080486D6                 mov     eax, [ebp+var_C]
.text:080486D9                 lea     ebx, [edx+eax]
.text:080486DC                 mov     eax, [ebp+var_C]
.text:080486DF                 lea     edx, [eax+20h]
.text:080486E2                 mov     ecx, ds:buffer1
.text:080486E8                 mov     eax, [ebp+var_C]
.text:080486EB                 add     eax, ecx
.text:080486ED                 movzx   eax, byte ptr [eax]
.text:080486F0                 movsx   eax, al
.text:080486F3                 sub     esp, 8
.text:080486F6                 push    edx
.text:080486F7                 push    eax
.text:080486F8                 call    complex_function
.text:080486FD                 add     esp, 10h
.text:08048700                 mov     [ebx], al
.text:08048702                 add     [ebp+var_C], 1
.text:08048706
.text:08048706 loc_8048706:                            ; CODE XREF: main+94↑j
.text:08048706                 cmp     [ebp+var_C], 7
.text:0804870A                 jle     short loc_80486A2

接下来就到了complex_function 运算的地方了,我们主要是对buffer0 和buffer1 指向的内存做求解.

.bss:09FD92AC buffer0         dd ?                    ; DATA XREF: main+1F↑w
.bss:09FD92AC                                         ; main+36↑r ...
.bss:09FD92B0                 public buffer3
.bss:09FD92B0 buffer3         db    ? ;
.bss:09FD92B1                 db    ? ;
.bss:09FD92B2                 db    ? ;
.bss:09FD92B3                 db    ? ;

所以,我们在利用 initial_state.memory.store() 构造内存时,还需要在里面填入指向保存数据的地址,Angr可以不用创建新内存(malloc),直接指向内存中一个任意位置即可,所以我们写0x4444440 和0x44444450 到buffer0 和buffer1 的内存中.

def main(argv):
  path_to_binary = argv[1]
  project = angr.Project(path_to_binary)

  start_address = 0x8048699
  initial_state = project.factory.blank_state(addr=start_address)

  password0 = claripy.BVS('password0', 4 * 8)
  password1 = claripy.BVS('password1', 4 * 8)

  fake_heap_address0 = 0x4444440
  pointer_to_malloc_memory_address0 = 0x9FD92AC
  initial_state.memory.store(pointer_to_malloc_memory_address0,fake_heap_address0, endness=project.arch.memory_endness)
  fake_heap_address1 = 0x4444450
  pointer_to_malloc_memory_address1 = 0x9FD92B0
  initial_state.memory.store(pointer_to_malloc_memory_address1,fake_heap_address1, endness=project.arch.memory_endness)

  initial_state.memory.store(fake_heap_address0, password0)
  initial_state.memory.store(fake_heap_address1, password1)

  simulation = project.factory.simgr(initial_state)

  simulation.explore(find=0x8048759, avoid=0x8048747)

  if simulation.found:
    solution_state = simulation.found[0]

    solution0 = solution_state.se.eval(password0)
    solution1 = solution_state.se.eval(password1)

    solution = ' '.join(map('{:x}'.format, [ solution0, solution1 ]))

    print(solution)

Angr函数使用总结:

initial_state.memory.store(地址,数据,endness = 数据字节顺序) => 设置初始化内存数据

project.arch.memory_endness => 指的是内存字节顺序

07_angr_symbolic_file

汇编代码:

.text:0804888E                 push    40h             ; n
.text:08048890                 push    0               ; c
.text:08048892                 push    offset buffer   ; s
.text:08048897                 call    _memset
.text:0804889C                 add     esp, 10h
.text:0804889F                 sub     esp, 0Ch
.text:080488A2                 push    offset aEnterThePasswo ; "Enter the password: "
.text:080488A7                 call    _printf
.text:080488AC                 add     esp, 10h
.text:080488AF                 sub     esp, 8
.text:080488B2                 push    offset buffer
.text:080488B7                 push    offset a64s     ; "%64s"
.text:080488BC                 call    ___isoc99_scanf
.text:080488C1                 add     esp, 10h
.text:080488C4                 sub     esp, 8
.text:080488C7                 push    40h             ; n
.text:080488C9                 push    offset buffer   ; int
.text:080488CE                 call    ignore_me
.text:080488D3                 add     esp, 10h
.text:080488D6                 sub     esp, 4
.text:080488D9                 push    40h             ; n
.text:080488DB                 push    0               ; c
.text:080488DD                 push    offset buffer   ; s
.text:080488E2                 call    _memset
.text:080488E7                 add     esp, 10h
.text:080488EA                 sub     esp, 8
.text:080488ED                 push    offset aRb      ; "rb"
.text:080488F2                 push    offset name     ; "MRXJKZYR.txt"
.text:080488F7                 call    _fopen
.text:080488FC                 add     esp, 10h
.text:080488FF                 mov     ds:fp, eax
.text:08048904                 mov     eax, ds:fp
.text:08048909                 push    eax             ; stream
.text:0804890A                 push    40h             ; n
.text:0804890C                 push    1               ; size
.text:0804890E                 push    offset buffer   ; ptr
.text:08048913                 call    _fread
.text:08048918                 add     esp, 10h
.text:0804891B                 mov     eax, ds:fp
.text:08048920                 sub     esp, 0Ch
.text:08048923                 push    eax             ; stream
.text:08048924                 call    _fclose
.text:08048929                 add     esp, 10h
.text:0804892C                 sub     esp, 0Ch
.text:0804892F                 push    offset name     ; "MRXJKZYR.txt"
.text:08048934                 call    _unlink

程序逻辑是用户输入一串Key之后,经过计算保存到文件里(在ignore_me() 里面实现),然后通过fread() 读取文件获取数据.Angr库中有一个模拟的文件系统,我们可以通过这个文件系统来模拟fread() 出来的数据,继续往下看汇编

.text:0804893C                 mov     [ebp+var_C], 0
.text:08048943                 jmp     short loc_8048972
.text:08048945 ; ---------------------------------------------------------------------------
.text:08048945
.text:08048945 loc_8048945:                            ; CODE XREF: main+FC↓j
.text:08048945                 mov     eax, [ebp+var_C]
.text:08048948                 add     eax, 804A0A0h
.text:0804894D                 movzx   eax, byte ptr [eax]  ;  生成数据
.text:08048950                 movsx   eax, al
.text:08048953                 sub     esp, 8
.text:08048956                 push    [ebp+var_C]
.text:08048959                 push    eax
.text:0804895A                 call    complex_function
.text:0804895F                 add     esp, 10h
.text:08048962                 mov     edx, eax
.text:08048964                 mov     eax, [ebp+var_C]
.text:08048967                 add     eax, 804A0A0h
.text:0804896C                 mov     [eax], dl
.text:0804896E                 add     [ebp+var_C], 1
.text:08048972
.text:08048972 loc_8048972:                            ; CODE XREF: main+C9↑j
.text:08048972                 cmp     [ebp+var_C], 7
.text:08048976                 jle     short loc_8048945
.text:08048978                 sub     esp, 4
.text:0804897B                 push    9               ; n
.text:0804897D                 push    offset s2       ; "UKNRZDIR"
.text:08048982                 push    offset buffer   ; s1
.text:08048987                 call    _strncmp
.text:0804898C                 add     esp, 10h
.text:0804898F                 test    eax, eax
.text:08048991                 jz      short loc_80489AD  ;  校验用户输入和生成数据
.text:08048993                 sub     esp, 0Ch
.text:08048996                 push    offset s        ; "Try again."
.text:0804899B                 call    _puts
.text:080489A0                 add     esp, 10h
.text:080489A3                 sub     esp, 0Ch
.text:080489A6                 push    1               ; status
.text:080489A8                 call    _exit
.text:080489AD ; ---------------------------------------------------------------------------
.text:080489AD
.text:080489AD loc_80489AD:                            ; CODE XREF: main+117↑j
.text:080489AD                 sub     esp, 0Ch
.text:080489B0                 push    offset aGoodJob ; "Good Job."
.text:080489B5                 call    _puts
.text:080489BA                 add     esp, 10h
.text:080489BD                 sub     esp, 0Ch
.text:080489C0                 push    0               ; status
.text:080489C2                 call    _exit

这段代码并没有什么特别之处,只是简单地对进行数据生成和校验然后输出判断结果.那么现在我们使用Angr的文件系统来进行求解

def main(argv):
  path_to_binary = argv[1]
  project = angr.Project(path_to_binary)

  start_address = 0x80488D6
  initial_state = project.factory.blank_state(addr=start_address)

  filename = 'MRXJKZYR.txt'  # :string
  symbolic_file_size_bytes = 0x40

  password = claripy.BVS('password', symbolic_file_size_bytes * 8)

  password_file = angr.storage.SimFile(filename, password, size=symbolic_file_size_bytes) # 模拟读文件,默认的文件内容是password,文件大小是symbolic_file_size_bytes

  symbolic_filesystem = {
    filename : password_file
  }
  initial_state.posix.fs = symbolic_filesystem # 构建状态上下文里的文件系统数据

  simulation = project.factory.simgr(initial_state)

  def is_successful(state):
    stdout_output = state.posix.dumps(sys.stdout.fileno())
    return 'Good Job' in str(stdout_output)

  def should_abort(state):
    stdout_output = state.posix.dumps(sys.stdout.fileno())
    return 'Try again' in str(stdout_output)

  simulation.explore(find=is_successful, avoid=should_abort)

  if simulation.found:
    solution_state = simulation.found[0]

    solution = solution_state.se.eval(password)#,cast_to=str)

    print(solution)

我们也可以继续用旧的内存跟踪的方法来做,示例代码如下

def main(argv):
  path_to_binary = argv[1]
  project = angr.Project(path_to_binary)

  start_address = 0x804893C
  initial_state = project.factory.blank_state(addr=start_address)

  password = claripy.BVS('password', 0x40 * 8)

  initial_state.memory.store(0x804A0A0, password)

  simulation = project.factory.simgr(initial_state)

  def is_successful(state):
    stdout_output = state.posix.dumps(sys.stdout.fileno())
    return 'Good Job' in str(stdout_output)

  def should_abort(state):
    stdout_output = state.posix.dumps(sys.stdout.fileno())
    return 'Try again' in str(stdout_output)

  simulation.explore(find=is_successful, avoid=should_abort)

  if simulation.found:
    solution_state = simulation.found[0]

    solution = solution_state.se.eval(password)

    print(solution)

注意事项:

在Angr-CTF 的解题Python 文件中,我们直接执行程序可能会遇到下面这个结果

Traceback (most recent call last): File "solve07.py", line 130, in main(sys.argv) File "solve07.py", line 91, in main password_file = angr.storage.SimFile(filename, file_options, content=symbolic_file_backing_memory, size=symbolic_file_size_bytes) TypeError: init() got multiple values for argument 'content'

这是因为Angr-CTF 是使用旧版的Angr 库来实现的,现在SimFile() 函数的声明已经被修改如下

SimFile(name, content=None**,** size=None**,** has_end=None**,** seekable=True**,** writable=True**,** ident=None**,** concrete=None**,** kwargs)

也就是说,SimFile 的第二个参数不再是file_options 了,所以我们可以看到SimFile() 提示content 参数被传递了两个参数;而且新版的SimFile() 函数的content 参数只接受字符串或者BitVector ,所以用solution 中的解题脚本会出问题是因为它传递的是内存对象而不是BitVector ,于是代码修改如下

password_file = angr.storage.SimFile(filename, password, size=symbolic_file_size_bytes)

Angr函数使用总结:

angr.storage.SimFile(文件名,文件内容, size = 文件大小) => 创建一个模拟文件,当有被执行的程序fopen 打开文件时,我们可以控制其里面的内容

initial_state.posix.fs => 状态上下文的文件系统对象

08_angr_constraints

汇编代码:

.text:080485C4                 mov     ds:password, 4A58524Dh  ;  这个是全局变量:0x804A040
.text:080485CE                 mov     ds:dword_804A044, 52595A4Bh
.text:080485D8                 mov     ds:dword_804A048, 454B4D4Bh
.text:080485E2                 mov     ds:dword_804A04C, 425A464Eh
.text:080485EC                 sub     esp, 4
.text:080485EF                 push    11h             ; n
.text:080485F1                 push    0               ; c
.text:080485F3                 push    offset buffer   ; s
.text:080485F8                 call    _memset
.text:080485FD                 add     esp, 10h
.text:08048600                 sub     esp, 0Ch
.text:08048603                 push    offset aEnterThePasswo ; "Enter the password: "
.text:08048608                 call    _printf
.text:0804860D                 add     esp, 10h
.text:08048610                 sub     esp, 8
.text:08048613                 push    offset buffer
.text:08048618                 push    offset a16s     ; "%16s"
.text:0804861D                 call    ___isoc99_scanf  ;  用户输入,这个也是全局变量:0x804A050
.text:08048622                 add     esp, 10h
.text:08048625                 mov     [ebp+var_C], 0
.text:0804862C                 jmp     short loc_8048663
.text:0804862E ; ---------------------------------------------------------------------------
.text:0804862E
.text:0804862E loc_804862E:                            ; CODE XREF: main+B4↓j
.text:0804862E                 mov     eax, 0Fh
.text:08048633                 sub     eax, [ebp+var_C]
.text:08048636                 mov     edx, eax
.text:08048638                 mov     eax, [ebp+var_C]
.text:0804863B                 add     eax, 804A050h
.text:08048640                 movzx   eax, byte ptr [eax]
.text:08048643                 movsx   eax, al
.text:08048646                 sub     esp, 8
.text:08048649                 push    edx
.text:0804864A                 push    eax
.text:0804864B                 call    complex_function  ;  对用户输入进行计算
.text:08048650                 add     esp, 10h
.text:08048653                 mov     edx, eax
.text:08048655                 mov     eax, [ebp+var_C]
.text:08048658                 add     eax, 804A050h
.text:0804865D                 mov     [eax], dl
.text:0804865F                 add     [ebp+var_C], 1
.text:08048663
.text:08048663 loc_8048663:                            ; CODE XREF: main+79↑j
.text:08048663                 cmp     [ebp+var_C], 0Fh
.text:08048667                 jle     short loc_804862E
.text:08048669                 sub     esp, 8
.text:0804866C                 push    10h
.text:0804866E                 push    offset buffer
.text:08048673                 call    check_equals_MRXJKZYRKMKENFZB  ;  check_equals() 函数是把buffer 和password 来对比
.text:08048678                 add     esp, 10h
.text:0804867B                 test    eax, eax
.text:0804867D                 jnz     short loc_8048691  ;  对比校验结果
.text:0804867F                 sub     esp, 0Ch
.text:08048682                 push    offset s        ; "Try again."
.text:08048687                 call    _puts
.text:0804868C                 add     esp, 10h
.text:0804868F                 jmp     short loc_80486A1
.text:08048691 ; ---------------------------------------------------------------------------
.text:08048691
.text:08048691 loc_8048691:                            ; CODE XREF: main+CA↑j
.text:08048691                 sub     esp, 0Ch
.text:08048694                 push    offset aGoodJob ; "Good Job."
.text:08048699                 call    _puts
.text:0804869E                 add     esp, 10h
.text:080486A1

主要的思路是把complex_function() 计算的结果和字符串MRXJKZYRKMKENFZB 来做对比,我们假定complex_function() 的输入是未知的,check_equals() 函数中对比的内容是已知的,那么我们的关注点就在于对输入进行求解.

首先第一步,我们需要在complex_function() 循环计算之后(地址0x804866C)就可以得到buffer 的符号执行内容,接下来我们需要根据buffer 的内容和对比的字符串MRXJKZYRKMKENFZB来计算是否有满足的解.

def main(argv):
  path_to_binary = argv[1]
  project = angr.Project(path_to_binary)

  start_address = 0x8048625  #  scanf() 之后的位置
  initial_state = project.factory.blank_state(addr=start_address)

  buffer = claripy.BVS('buffer', 0x10* 8)  #  buffer的大小是0x10 字节,也就是0x10 * 8 比特
  buffer_address = 0x804A050
  initial_state.memory.store(buffer_address, buffer)  #  buffer 是全局变量,地址是0x804A050

  simulation = project.factory.simgr(initial_state)
  address_to_check_constraint = 0x804866C
  simulation.explore(find=address_to_check_constraint)  #  compilex_function() 计算结束位置

  if simulation.found:
    solution_state = simulation.found[0]

    constrained_parameter_address = 0x804A050  #  计算约束的内存位置
    constrained_parameter_size_bytes = 0x10    #  计算约束的内存大小
    constrained_parameter_bitvector = solution_state.memory.load(  #  加载内存
      constrained_parameter_address,
      constrained_parameter_size_bytes
    )

    constrained_parameter_desired_value = 'MRXJKZYRKMKENFZB' #  Key-String

    solution_state.add_constraints(constrained_parameter_bitvector == constrained_parameter_desired_value)  #  添加约束条件使用求解器求解内存中的bitvector 和Key-String 相等时是否有满足的解

    solution = solution_state.se.eval(buffer)

    print(solution)

Angr函数使用总结:

solution_state.memory.load(内存地址,内存大小) => 加载内存

solution_state.add_constraints(约束条件) => 添加约束条件

09_angr_hooks

汇编代码:

.text:08048604                 mov     ds:password, 4A58524Dh
.text:0804860E                 mov     ds:dword_804A048, 52595A4Bh
.text:08048618                 mov     ds:dword_804A04C, 454B4D4Bh
.text:08048622                 mov     ds:dword_804A050, 425A464Eh  ;  Key-String
.text:0804862C                 sub     esp, 4
.text:0804862F                 push    11h             ; n
.text:08048631                 push    0               ; c
.text:08048633                 push    offset buffer   ; s
.text:08048638                 call    _memset         
.text:0804863D                 add     esp, 10h
.text:08048640                 sub     esp, 0Ch
.text:08048643                 push    offset aEnterThePasswo ; "Enter the password: "
.text:08048648                 call    _printf
.text:0804864D                 add     esp, 10h
.text:08048650                 sub     esp, 8
.text:08048653                 push    offset buffer
.text:08048658                 push    offset a16s     ; "%16s"
.text:0804865D                 call    ___isoc99_scanf ; 用户输入Key
.text:08048662                 add     esp, 10h
.text:08048665                 mov     [ebp+var_10], 0
.text:0804866C                 jmp     short loc_80486A3
.text:0804866E ; ---------------------------------------------------------------------------
.text:0804866E
.text:0804866E loc_804866E:                            ; CODE XREF: main+B4↓j
.text:0804866E                 mov     eax, 12h
.text:08048673                 sub     eax, [ebp+var_10]
.text:08048676                 mov     edx, eax
.text:08048678                 mov     eax, [ebp+var_10]
.text:0804867B                 add     eax, 804A054h   ;  这个地址是用户输入buffer
.text:08048680                 movzx   eax, byte ptr [eax]
.text:08048683                 movsx   eax, al
.text:08048686                 sub     esp, 8
.text:08048689                 push    edx
.text:0804868A                 push    eax
.text:0804868B                 call    complex_function  ;  对用户输入进行计算
.text:08048690                 add     esp, 10h
.text:08048693                 mov     edx, eax
.text:08048695                 mov     eax, [ebp+var_10]
.text:08048698                 add     eax, 804A054h
.text:0804869D                 mov     [eax], dl
.text:0804869F                 add     [ebp+var_10], 1
.text:080486A3
.text:080486A3 loc_80486A3:                            ; CODE XREF: main+79↑j
.text:080486A3                 cmp     [ebp+var_10], 0Fh
.text:080486A7                 jle     short loc_804866E  ;  这是一个循环complex_function() 计算
.text:080486A9                 sub     esp, 8
.text:080486AC                 push    10h
.text:080486AE                 push    offset buffer
.text:080486B3                 call    check_equals_MRXJKZYRKMKENFZB  ;  check_equals() 对比结果
.text:080486B8                 add     esp, 10h
.text:080486BB                 mov     ds:equals, eax  ;  check_equals 的结果保存在全局变量equals里
.text:080486C0                 mov     [ebp+var_C], 0
.text:080486C7                 jmp     short loc_80486FA
.text:080486C9 ; ---------------------------------------------------------------------------
.text:080486C9
.text:080486C9 loc_80486C9:                            ; CODE XREF: main+10B↓j
.text:080486C9                 mov     eax, [ebp+var_C]
.text:080486CC                 lea     edx, [eax+9]
.text:080486CF                 mov     eax, [ebp+var_C]
.text:080486D2                 add     eax, 804A044h  ;  这个是Key-String
.text:080486D7                 movzx   eax, byte ptr [eax]
.text:080486DA                 movsx   eax, al
.text:080486DD                 sub     esp, 8
.text:080486E0                 push    edx
.text:080486E1                 push    eax
.text:080486E2                 call    complex_function  ;  这次是对Key-String 进行计算了
.text:080486E7                 add     esp, 10h
.text:080486EA                 mov     edx, eax
.text:080486EC                 mov     eax, [ebp+var_C]
.text:080486EF                 add     eax, 804A044h
.text:080486F4                 mov     [eax], dl
.text:080486F6                 add     [ebp+var_C], 1
.text:080486FA
.text:080486FA loc_80486FA:                            ; CODE XREF: main+D4↑j
.text:080486FA                 cmp     [ebp+var_C], 0Fh
.text:080486FE                 jle     short loc_80486C9  ;  comp
.text:08048700                 sub     esp, 8
.text:08048703                 push    offset buffer
.text:08048708                 push    offset a16s     ; "%16s"
.text:0804870D                 call    ___isoc99_scanf
.text:08048712                 add     esp, 10h
.text:08048715                 mov     eax, ds:equals
.text:0804871A                 test    eax, eax
.text:0804871C                 jz      short loc_8048740  ;  对比第二次用户输入
.text:0804871E                 sub     esp, 4
.text:08048721                 push    10h             ; n
.text:08048723                 push    offset password ; s2
.text:08048728                 push    offset buffer   ; s1
.text:0804872D                 call    _strncmp
.text:08048732                 add     esp, 10h
.text:08048735                 test    eax, eax
.text:08048737                 jnz     short loc_8048740
.text:08048739                 mov     eax, 1
.text:0804873E                 jmp     short loc_8048745
.text:08048740 ; ---------------------------------------------------------------------------
.text:08048740
.text:08048740 loc_8048740:                            ; CODE XREF: main+129↑j
.text:08048740                                         ; main+144↑j
.text:08048740                 mov     eax, 0
.text:08048745
.text:08048745 loc_8048745:                            ; CODE XREF: main+14B↑j
.text:08048745                 mov     ds:equals, eax
.text:0804874A                 mov     eax, ds:equals
.text:0804874F                 test    eax, eax
.text:08048751                 jnz     short loc_8048765
.text:08048753                 sub     esp, 0Ch
.text:08048756                 push    offset s        ; "Try again."
.text:0804875B                 call    _puts
.text:08048760                 add     esp, 10h
.text:08048763                 jmp     short loc_8048775
.text:08048765 ; ---------------------------------------------------------------------------
.text:08048765
.text:08048765 loc_8048765:                            ; CODE XREF: main+15E↑j
.text:08048765                 sub     esp, 0Ch
.text:08048768                 push    offset aGoodJob ; "Good Job."
.text:0804876D                 call    _puts
.text:08048772                 add     esp, 10h  

由上面的代码我们可以知道基本的逻辑,第一部分是对用户输入进行complex_function() 计算,然后把计算结果传给check_equals() 检查对比;第二部分是把Key-String 传递给complex_function() 计算,再通过第二次用户输入来进行结果对比.

我们来看一下check_equals() 函数的代码,check_equals() 函数主要功能是对比complex_function() 函数计算结果和Key-String 进行对比,相等则返回1 ,不相等返回0 .

.text:080485A5 check_equals_MRXJKZYRKMKENFZB proc near ; CODE XREF: main+C0↓p
.text:080485A5
.text:080485A5 var_8           = dword ptr -8
.text:080485A5 var_4           = dword ptr -4
.text:080485A5 arg_0           = dword ptr  8
.text:080485A5 arg_4           = dword ptr  0Ch
.text:080485A5
.text:080485A5 ; __unwind {
.text:080485A5                 push    ebp
.text:080485A6                 mov     ebp, esp
.text:080485A8                 sub     esp, 10h
.text:080485AB                 mov     [ebp+var_8], 0
.text:080485B2                 mov     [ebp+var_4], 0
.text:080485B9                 jmp     short loc_80485DD
.text:080485BB ; ---------------------------------------------------------------------------
.text:080485BB
.text:080485BB loc_80485BB:                            ; CODE XREF: check_equals_MRXJKZYRKMKENFZB+3E↓j
.text:080485BB                 mov     edx, [ebp+var_4]
.text:080485BE                 mov     eax, [ebp+arg_0]
.text:080485C1                 add     eax, edx
.text:080485C3                 movzx   edx, byte ptr [eax]
.text:080485C6                 mov     eax, [ebp+var_4]
.text:080485C9                 add     eax, 804A044h  ;  Key-String ..
.text:080485CE                 movzx   eax, byte ptr [eax]
.text:080485D1                 cmp     dl, al
.text:080485D3                 jnz     short loc_80485D9
.text:080485D5                 add     [ebp+var_8], 1
.text:080485D9
.text:080485D9 loc_80485D9:                            ; CODE XREF: check_equals_MRXJKZYRKMKENFZB+2E↑j
.text:080485D9                 add     [ebp+var_4], 1
.text:080485DD
.text:080485DD loc_80485DD:                            ; CODE XREF: check_equals_MRXJKZYRKMKENFZB+14↑j
.text:080485DD                 mov     eax, [ebp+var_4]
.text:080485E0                 cmp     eax, [ebp+arg_4]
.text:080485E3                 jb      short loc_80485BB
.text:080485E5                 mov     eax, [ebp+var_8]
.text:080485E8                 cmp     eax, [ebp+arg_4]
.text:080485EB                 setz    al
.text:080485EE                 movzx   eax, al
.text:080485F1                 leave
.text:080485F2                 retn
.text:080485F2 ; } // starts at 80485A5

在这一题里,使用的方法是Angr Hook .那么我们需要设计一个Hook check_equals() 函数来模拟它的功能.Hook 的插入位置在.text:080486B3 call check_equals_MRXJKZYRKMKENFZB .

根据这些信息,构造的solver.py 代码如下:

def main(argv):
  path_to_binary = argv[1]
  project = angr.Project(path_to_binary)

  initial_state = project.factory.entry_state()

  check_equals_called_address = 0x80486B3  #  Hook Call Address
  instruction_to_skip_length = 0x5         #  Call instruction length

  @project.hook(check_equals_called_address, length=instruction_to_skip_length)
  def skip_check_equals_(state):
    user_input_buffer_address = 0x804A054  #  The input buffer address
    user_input_buffer_length = 0x10        #  input buffer length

    user_input_string = state.memory.load( #  load this buffer to check 
      user_input_buffer_address,
      user_input_buffer_length
    )

    check_against_string = 'MRXJKZYRKMKENFZB' # :string

    state.regs.eax = claripy.If(           #  Add a constraint .
      user_input_string == check_against_string,  #  Check condition
      claripy.BVV(1, 32),                  #  The condition is True than return a int value 1
      claripy.BVV(0, 32)                   #  The condition is False than return a int value 0
    )

  simulation = project.factory.simgr(initial_state)

  def is_successful(state):
    stdout_output = state.posix.dumps(sys.stdout.fileno())
    return 'Good Job' in str(stdout_output)

  def should_abort(state):
    stdout_output = state.posix.dumps(sys.stdout.fileno())
    return 'Try again' in str(stdout_output)

  simulation.explore(find=is_successful, avoid=should_abort)  #  Try Explore Execute Path

  if simulation.found:
    solution_state = simulation.found[0]
    solution = solution_state.posix.dumps(sys.stdin.fileno())  #  Get data ..

    print(solution)

Angr函数使用总结:

Hook回调函数格式:

@project.hook(Hook地址,执行完Hook函数后指令往后跳转n字节) def skip_check_equals_(state):

pass

claripy.If(条件,条件为True时的返回值,条件为False时的返回值) => 创建条件判断

claripy.BVV(值,值大小) => 创建一个数值

10_angr_simprocedures

汇编代码:

.text:08048678                 push    10h             ; n
.text:0804867A                 push    offset aMrxjkzyrkmkenf ; "MRXJKZYRKMKENFZB"
.text:0804867F                 push    offset password ; dest
.text:08048684                 call    _memcpy         ;  Key-String = MRXJKZYRKMKENFZB
.text:08048689                 add     esp, 10h
.text:0804868C                 sub     esp, 4
.text:0804868F                 push    11h             ; n
.text:08048691                 push    0               ; c
.text:08048693                 lea     eax, [ebp+s]
.text:08048696                 push    eax             ; s
.text:08048697                 call    _memset         ;  注意这次buffer 是在栈上的
.text:0804869C                 add     esp, 10h
.text:0804869F                 sub     esp, 0Ch
.text:080486A2                 push    offset aEnterThePasswo ; "Enter the password: "
.text:080486A7                 call    _printf
.text:080486AC                 add     esp, 10h
.text:080486AF                 sub     esp, 8
.text:080486B2                 lea     eax, [ebp+s]
.text:080486B5                 push    eax
.text:080486B6                 push    offset a16s     ; "%16s"
.text:080486BB                 call    ___isoc99_scanf ;  用户输入16 字节大小的内容
.text:080486C0                 add     esp, 10h
.text:080486C3                 mov     [ebp+var_28], 0
.text:080486CA                 jmp     short loc_8048701
.text:080486CC ; ---------------------------------------------------------------------------
.text:080486CC
.text:080486CC loc_80486CC:                            ; CODE XREF: main+C2↓j
.text:080486CC                 mov     eax, 12h
.text:080486D1                 sub     eax, [ebp+var_28]
.text:080486D4                 mov     edx, eax
.text:080486D6                 lea     ecx, [ebp+s]
.text:080486D9                 mov     eax, [ebp+var_28]
.text:080486DC                 add     eax, ecx
.text:080486DE                 movzx   eax, byte ptr [eax]
.text:080486E1                 movsx   eax, al
.text:080486E4                 sub     esp, 8
.text:080486E7                 push    edx
.text:080486E8                 push    eax
.text:080486E9                 call    complex_function  ;  这是复杂的complex_function() 计算操作
.text:080486EE                 add     esp, 10h
.text:080486F1                 mov     ecx, eax
.text:080486F3                 lea     edx, [ebp+s]
.text:080486F6                 mov     eax, [ebp+var_28]
.text:080486F9                 add     eax, edx
.text:080486FB                 mov     [eax], cl
.text:080486FD                 add     [ebp+var_28], 1
.text:08048701
.text:08048701 loc_8048701:                            ; CODE XREF: main+87↑j
.text:08048701                 cmp     [ebp+var_28], 0Fh
.text:08048705                 jle     short loc_80486CC
.text:08048707                 cmp     [ebp+var_24], 0DEADBEEFh
.text:0804870E                 jz      loc_8049847
.text:08048714                 cmp     [ebp+var_24], 0DEADBEEFh
.text:0804871B                 jnz     loc_8048FB4
.text:08048721                 cmp     [ebp+var_24], 0DEADBEEFh
.text:08048728                 jnz     loc_8048B71
.text:0804872E                 cmp     [ebp+var_24], 0DEADBEEFh
.text:08048735                 jnz     loc_8048956
.text:0804873B                 cmp     [ebp+var_24], 0DEADBEEFh
.text:08048742                 jz      loc_804884F
.text:08048748                 cmp     [ebp+var_24], 0DEADBEEFh
.text:0804874F                 jz      short loc_80487D0
.text:08048751                 cmp     [ebp+var_24], 0DEADBEEFh
.text:08048758                 jnz     short loc_8048795
.text:0804875A                 cmp     [ebp+var_24], 0DEADBEEFh
.text:08048761                 jz      short loc_804877C

代码的第一部分对用户输入做了一系列复杂的计算操作之后,然后就分别跳转到不同的位置,最后执行check_equals() 函数进行数据校验.

.text:08048758                 jnz     short loc_8048795
.text:0804875A                 cmp     [ebp+var_24], 0DEADBEEFh
.text:08048761                 jz      short loc_804877C
.text:08048763                 sub     esp, 8
.text:08048766                 push    10h
.text:08048768                 lea     eax, [ebp+s]
.text:0804876B                 push    eax
.text:0804876C                 call    check_equals_MRXJKZYRKMKENFZB
.text:08048771                 add     esp, 10h
.text:08048774                 mov     [ebp+var_2C], eax
.text:08048777                 jmp     loc_804A969
.text:0804877C ; ---------------------------------------------------------------------------
.text:0804877C
.text:0804877C loc_804877C:                            ; CODE XREF: main+11E↑j
.text:0804877C                 sub     esp, 8
.text:0804877F                 push    10h
.text:08048781                 lea     eax, [ebp+s]
.text:08048784                 push    eax
.text:08048785                 call    check_equals_MRXJKZYRKMKENFZB
.text:0804878A                 add     esp, 10h
.text:0804878D                 mov     [ebp+var_2C], eax
.text:08048790                 jmp     loc_804A969
.text:08048795 ; ---------------------------------------------------------------------------
.text:08048795
.text:08048795 loc_8048795:                            ; CODE XREF: main+115↑j
.text:08048795                 cmp     [ebp+var_24], 0DEADBEEFh
.text:0804879C                 jz      short loc_80487B7
.text:0804879E                 sub     esp, 8
.text:080487A1                 push    10h
.text:080487A3                 lea     eax, [ebp+s]
.text:080487A6                 push    eax
.text:080487A7                 call    check_equals_MRXJKZYRKMKENFZB
.text:080487AC                 add     esp, 10h
.text:080487AF                 mov     [ebp+var_2C], eax
.text:080487B2                 jmp     loc_804A969
.text:080487B7 ; ---------------------------------------------------------------------------
.text:080487B7
.text:080487B7 loc_80487B7:                            ; CODE XREF: main+159↑j
.text:080487B7                 sub     esp, 8
.text:080487BA                 push    10h
.text:080487BC                 lea     eax, [ebp+s]
.text:080487BF                 push    eax
.text:080487C0                 call    check_equals_MRXJKZYRKMKENFZB
.text:080487C5                 add     esp, 10h
.text:080487C8                 mov     [ebp+var_2C], eax
.text:080487CB                 jmp     loc_804A969

那么我们来看看check_equals() 的代码:

.text:080485F5 check_equals_MRXJKZYRKMKENFZB proc near ; CODE XREF: main+129↓p
.text:080485F5                                         ; main+142↓p ...
.text:080485F5
.text:080485F5 var_8           = dword ptr -8
.text:080485F5 var_4           = dword ptr -4
.text:080485F5 arg_0           = dword ptr  8
.text:080485F5 arg_4           = dword ptr  0Ch
.text:080485F5
.text:080485F5 ; __unwind {
.text:080485F5                 push    ebp
.text:080485F6                 mov     ebp, esp
.text:080485F8                 sub     esp, 10h
.text:080485FB                 mov     [ebp+var_8], 0
.text:08048602                 mov     [ebp+var_4], 0
.text:08048609                 jmp     short loc_804862D
.text:0804860B ; ---------------------------------------------------------------------------
.text:0804860B
.text:0804860B loc_804860B:                            ; CODE XREF: check_equals_MRXJKZYRKMKENFZB+3E↓j
.text:0804860B                 mov     edx, [ebp+var_4]
.text:0804860E                 mov     eax, [ebp+arg_0]
.text:08048611                 add     eax, edx
.text:08048613                 movzx   edx, byte ptr [eax]
.text:08048616                 mov     eax, [ebp+var_4]
.text:08048619                 add     eax, 804C048h   ;  Key-String 的地址
.text:0804861E                 movzx   eax, byte ptr [eax]
.text:08048621                 cmp     dl, al
.text:08048623                 jnz     short loc_8048629 
.text:08048625                 add     [ebp+var_8], 1
.text:08048629
.text:08048629 loc_8048629:                            ; CODE XREF: check_equals_MRXJKZYRKMKENFZB+2E↑j
.text:08048629                 add     [ebp+var_4], 1
.text:0804862D
.text:0804862D loc_804862D:                            ; CODE XREF: check_equals_MRXJKZYRKMKENFZB+14↑j
.text:0804862D                 mov     eax, [ebp+var_4]
.text:08048630                 cmp     eax, [ebp+arg_4]
.text:08048633                 jb      short loc_804860B  ;  这是一个for 循环的Buffer 内容校验逻辑
.text:08048635                 mov     eax, [ebp+var_8]
.text:08048638                 cmp     eax, [ebp+arg_4]
.text:0804863B                 setz    al
.text:0804863E                 movzx   eax, al
.text:08048641                 leave
.text:08048642                 retn

所以,我们需要Hook check_equals() 并模拟它的执行,这个题目和09 题不同之处在于,09 题我们可以通过一处指令Hook 来实现,但是10 题我们就不能这么做了,是因为Call check_equals() 的地址太多,用09 题的方式不方便,所以我们可以用Angr 的Hook Symbol 来实现对check_equals() 函数的Hook ,而不是想09 题那样只对指令进行Hook .代码如下:

def main(argv):
  path_to_binary = argv[1]
  project = angr.Project(path_to_binary)

  initial_state = project.factory.entry_state()

  class ReplacementCheckEquals(angr.SimProcedure):

    def run(self, check_data_address, check_data_length):  #  基本和09 题的逻辑一样,唯独不同的是我们可以获取check_equals() 的参数
      check_input_string = self.state.memory.load(
        check_data_address,
        check_data_length
      )

      check_against_string = 'MRXJKZYRKMKENFZB'

      return claripy.If(check_input_string == check_against_string, claripy.BVV(1, 32), claripy.BVV(0, 32))

  check_equals_symbol = 'check_equals_MRXJKZYRKMKENFZB' # :string
  project.hook_symbol(check_equals_symbol, ReplacementCheckEquals())  #  Hook Symbol

  simulation = project.factory.simgr(initial_state)

  def is_successful(state):
    stdout_output = state.posix.dumps(sys.stdout.fileno())
    return 'Good Job' in str(stdout_output)

  def should_abort(state):
    stdout_output = state.posix.dumps(sys.stdout.fileno())
    return 'Try again' in str(stdout_output)

  simulation.explore(find=is_successful, avoid=should_abort)

  if simulation.found:
    solution_state = simulation.found[0]

    solution = solution_state.posix.dumps(sys.stdin.fileno())
    print(solution)

Angr函数使用总结:

Hook 回调函数格式:

class ReplacementCheckEquals(angr.SimProcedure):

def run(self, Hook的函数参数列表):

​ ….

​ return 函数返回值 # 如果是void函数可以省略

project.hook_symbol(要Hook的函数名,SimProcedure类实例)

11_angr_sim_scanf

汇编代码:

.text:0804862A                 push    14h             ; n
.text:0804862C                 push    0               ; c
.text:0804862E                 lea     eax, [ebp+key_string]
.text:08048631                 push    eax             ; s
.text:08048632                 call    _memset
.text:08048637                 add     esp, 10h
.text:0804863A                 lea     eax, [ebp+key_string]
.text:0804863D                 mov     dword ptr [eax], 444E4848h
.text:08048643                 mov     dword ptr [eax+4], 50484156h  ;  初始化Key-String
.text:0804864A                 mov     [ebp+index], 0
.text:08048651                 jmp     short loc_8048680
.text:08048653 ; ---------------------------------------------------------------------------
.text:08048653
.text:08048653 loc_8048653:                            ; CODE XREF: main+8F↓j
.text:08048653                 lea     edx, [ebp+key_string]
.text:08048656                 mov     eax, [ebp+index]
.text:08048659                 add     eax, edx
.text:0804865B                 movzx   eax, byte ptr [eax]
.text:0804865E                 movsx   eax, al
.text:08048661                 sub     esp, 8
.text:08048664                 push    [ebp+index]
.text:08048667                 push    eax
.text:08048668                 call    complex_function  ;  complex_function() 对key-string 进行计算
.text:0804866D                 add     esp, 10h
.text:08048670                 mov     ecx, eax
.text:08048672                 lea     edx, [ebp+key_string]
.text:08048675                 mov     eax, [ebp+index]
.text:08048678                 add     eax, edx
.text:0804867A                 mov     [eax], cl
.text:0804867C                 add     [ebp+index], 1
.text:08048680
.text:08048680 loc_8048680:                            ; CODE XREF: main+5C↑j
.text:08048680                 cmp     [ebp+index], 7
.text:08048684                 jle     short loc_8048653 ; for (index =0 index <=7 ; ++ index )

程序第一步先对Key-String 进行运算,该题的难点在于main() 里面多个scanf() .

text:08048689                 push    offset aEnterThePasswo ; "Enter the password: "
.text:0804868E                 call    _printf
.text:08048693                 add     esp, 10h
.text:08048696                 cmp     [ebp+var_24], 0DEADBEEFh
.text:0804869D                 jnz     loc_804C196
.text:080486A3                 cmp     [ebp+var_24], 0DEADBEEFh
.text:080486AA                 jnz     loc_804A423
.text:080486B0                 cmp     [ebp+var_24], 0DEADBEEFh
.text:080486B7                 jz      loc_8049570
.text:080486BD                 cmp     [ebp+var_24], 0DEADBEEFh
.text:080486C4                 jnz     loc_8048E1D
.text:080486CA                 cmp     [ebp+var_24], 0DEADBEEFh
.text:080486D1                 jz      loc_8048A7A
.text:080486D7                 cmp     [ebp+var_24], 0DEADBEEFh
.text:080486DE                 jz      loc_80488AF
.text:080486E4                 cmp     [ebp+var_24], 0DEADBEEFh
.text:080486EB                 jnz     loc_80487D0
.text:080486F1                 cmp     [ebp+var_24], 0DEADBEEFh
.text:080486F8                 jnz     short loc_8048765
.text:080486FA                 sub     esp, 4
.text:080486FD                 push    offset buffer1
.text:08048702                 push    offset buffer0
.text:08048707                 push    offset aUU      ; "%u %u"
.text:0804870C                 call    ___isoc99_scanf
.text:08048711                 add     esp, 10h
.text:08048714                 cmp     [ebp+var_2C], 0
.text:08048718                 jz      short loc_8048758
.text:0804871A                 sub     esp, 4
.text:0804871D                 push    4               ; n
.text:0804871F                 lea     eax, [ebp+key_string]
.text:08048722                 push    eax             ; s2
.text:08048723                 push    offset buffer0  ; s1
.text:08048728                 call    _strncmp
.text:0804872D                 add     esp, 10h
.text:08048730                 test    eax, eax
.text:08048732                 jnz     short loc_8048758
.text:08048734                 sub     esp, 4
.text:08048737                 push    4               ; n
.text:08048739                 lea     eax, [ebp+key_string]
.text:0804873C                 add     eax, 4
.text:0804873F                 push    eax             ; s2
.text:08048740                 push    offset buffer1  ; s1
.text:08048745                 call    _strncmp
.text:0804874A                 add     esp, 10h
.text:0804874D                 test    eax, eax
.text:0804874F                 jnz     short loc_8048758
.text:08048751                 mov     eax, 1
.text:08048756                 jmp     short loc_804875D
.text:08048758 ; ---------------------------------------------------------------------------
.text:08048758
.text:08048758 loc_8048758:                            ; CODE XREF: main+123↑j
.text:08048758                                         ; main+13D↑j ...
.text:08048758                 mov     eax, 0
.text:0804875D
.text:0804875D loc_804875D:                            ; CODE XREF: main+161↑j
.text:0804875D                 mov     [ebp+var_2C], eax
.text:08048760                 jmp     loc_804FC81
.text:08048765 ; ---------------------------------------------------------------------------
.text:08048765
.text:08048765 loc_8048765:                            ; CODE XREF: main+103↑j
.text:08048765                 sub     esp, 4
.text:08048768                 push    offset buffer1
.text:0804876D                 push    offset buffer0
.text:08048772                 push    offset aUU      ; "%u %u"
.text:08048777                 call    ___isoc99_scanf
.text:0804877C                 add     esp, 10h
.text:0804877F                 cmp     [ebp+var_2C], 0
.text:08048783                 jz      short loc_80487C3
.text:08048785                 sub     esp, 4
.text:08048788                 push    4               ; n
.text:0804878A                 lea     eax, [ebp+key_string]
.text:0804878D                 push    eax             ; s2
.text:0804878E                 push    offset buffer0  ; s1
.text:08048793                 call    _strncmp
.text:08048798                 add     esp, 10h
.text:0804879B                 test    eax, eax
.text:0804879D                 jnz     short loc_80487C3
.text:0804879F                 sub     esp, 4
.text:080487A2                 push    4               ; n
.text:080487A4                 lea     eax, [ebp+key_string]
.text:080487A7                 add     eax, 4
.text:080487AA                 push    eax             ; s2
.text:080487AB                 push    offset buffer1  ; s1
.text:080487B0                 call    _strncmp
.text:080487B5                 add     esp, 10h
.text:080487B8                 test    eax, eax
.text:080487BA                 jnz     short loc_80487C3
.text:080487BC                 mov     eax, 1
.text:080487C1                 jmp     short loc_80487C8

我们的关注点在于scanf() ,这里标明了用户有两个输入,分别为4 字节.然后我们就需要Hook scanf() 来对buffer 进行符号构造,代码如下:

def main(argv):
  path_to_binary = argv[1]
  project = angr.Project(path_to_binary)

  initial_state = project.factory.entry_state()

  class ReplacementScanf(angr.SimProcedure):

    def run(self, format_string, scanf0_address, scanf1_address ):
      scanf0 = claripy.BVS('scanf0', 4 * 8)
      scanf1 = claripy.BVS('scanf1', 4 * 8)

      self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
      self.state.memory.store(scanf1_address, scanf1, endness=project.arch.memory_endness)

      self.state.globals['solution0'] = scanf0
      self.state.globals['solution1'] = scanf1

  scanf_symbol = '__isoc99_scanf'
  project.hook_symbol(scanf_symbol, ReplacementScanf())

  simulation = project.factory.simgr(initial_state)

  def is_successful(state):
    stdout_output = state.posix.dumps(sys.stdout.fileno())
    return 'Good Job' in str(stdout_output)

  def should_abort(state):
    stdout_output = state.posix.dumps(sys.stdout.fileno())
    return 'Try again' in str(stdout_output)

  simulation.explore(find=is_successful, avoid=should_abort)

  if simulation.found:
    solution_state = simulation.found[0]
    stored_solutions0 = solution_state.globals['solution0']
    stored_solutions1 = solution_state.globals['solution1']
    solution0 = solution_state.se.eval(stored_solutions0)
    solution1 = solution_state.se.eval(stored_solutions1)

    print(solution0,solution1)

12_angr_veritesting

汇编代码:

.text:080485ED                 push    21h             ; n
.text:080485EF                 push    0               ; c
.text:080485F1                 lea     eax, [ebp+input_buffer]
.text:080485F4                 push    eax             ; s
.text:080485F5                 call    _memset
.text:080485FA                 add     esp, 10h
.text:080485FD                 sub     esp, 0Ch
.text:08048600                 push    offset aEnterThePasswo ; "Enter the password: "
.text:08048605                 call    _printf
.text:0804860A                 add     esp, 10h
.text:0804860D                 sub     esp, 8
.text:08048610                 lea     eax, [ebp+input_buffer]
.text:08048613                 push    eax
.text:08048614                 push    offset a32s     ; "%32s"
.text:08048619                 call    ___isoc99_scanf  ;  用户输入一个32字节的Buffer
.text:0804861E                 add     esp, 10h
.text:08048621                 mov     [ebp+var_3C], 0
.text:08048628                 mov     [ebp+var_34], 0
.text:0804862F                 mov     [ebp+index], 0
.text:08048636                 jmp     short loc_8048666
.text:08048638 ; ---------------------------------------------------------------------------
.text:08048638
.text:08048638 loc_8048638:                            ; CODE XREF: main+A5↓j
.text:08048638                 lea     edx, [ebp+input_buffer]
.text:0804863B                 mov     eax, [ebp+index]
.text:0804863E                 add     eax, edx
.text:08048640                 movzx   eax, byte ptr [eax]
.text:08048643                 movsx   ebx, al
.text:08048646                 mov     eax, [ebp+index]
.text:08048649                 add     eax, 5Bh
.text:0804864C                 sub     esp, 8
.text:0804864F                 push    eax
.text:08048650                 push    4Fh
.text:08048652                 call    complex_function  ;  对用户输入进行计算
.text:08048657                 add     esp, 10h
.text:0804865A                 cmp     ebx, eax
.text:0804865C                 jnz     short loc_8048662
.text:0804865E                 add     [ebp+var_3C], 1
.text:08048662
.text:08048662 loc_8048662:                            ; CODE XREF: main+97↑j
.text:08048662                 add     [ebp+index], 1
.text:08048666
.text:08048666 loc_8048666:                            ; CODE XREF: main+71↑j
.text:08048666                 cmp     [ebp+index], 1Fh
.text:0804866A                 jle     short loc_8048638
.text:0804866C                 cmp     [ebp+var_3C], ' '
.text:08048670                 jnz     short loc_804868C
.text:08048672                 movzx   eax, byte ptr [ebp+var_C]
.text:08048676                 test    al, al
.text:08048678                 jnz     short loc_804868C  ;  对输入进行计算
.text:0804867A                 sub     esp, 0Ch
.text:0804867D                 push    offset aGoodJob ; "Good Job."
.text:08048682                 call    _puts
.text:08048687                 add     esp, 10h
.text:0804868A                 jmp     short loc_804869C
.text:0804868C ; ---------------------------------------------------------------------------
.text:0804868C
.text:0804868C loc_804868C:                            ; CODE XREF: main+AB↑j
.text:0804868C                                         ; main+B3↑j
.text:0804868C                 sub     esp, 0Ch
.text:0804868F                 push    offset s        ; "Try again."
.text:08048694                 call    _puts
.text:08048699                 add     esp, 10h

这个示例和01 题是一样的,唯独不同的一点是这个循环比之前的要大,导致直接用01 题的解题方法不能直接计算出结果,因为循环过大导致路径爆炸,所以在执行的时候会消耗很多资源.

幸运的是,project.factory.simgr() 函数提供veritesting 参数来指定是否要自动合并路径,避免路径爆炸的问题.具体细节参考论文:https://users.ece.cmu.edu/~dbrumley/pdf/Avgerinos%20et%20al._2014_Enhancing%20Symbolic%20Execution%20with%20Veritesting.pdf

import angr
import sys


project = angr.Project(sys.argv[1])
initial_state = project.factory.entry_state()
simulation = project.factory.simgr(initial_state,veritesting = True)

def is_successful(state):
  stdout_output = state.posix.dumps(sys.stdout.fileno())
  return 'Good Job.' in str(stdout_output)  # :boolean

def should_abort(state):
  stdout_output = state.posix.dumps(sys.stdout.fileno())
  return 'Try again.' in str(stdout_output)  # :boolean

simulation.explore(find = is_successful,avoid = should_abort)

if simulation.found :
  solution_state = simulation.found[0]
  print(solution_state.posix.dumps(sys.stdin.fileno()))

Angr函数使用总结:

project.factory.simgr(初始化状态,veritesting = True) => veritesting 默认为False

##13_angr_static_binary

汇编代码:

.text:08048953                 push    offset aEnterThePasswo ; "Enter the password: "
.text:08048958                 call    printf
.text:0804895D                 add     esp, 10h
.text:08048960                 sub     esp, 8
.text:08048963                 lea     eax, [ebp+s1]
.text:08048966                 push    eax
.text:08048967                 push    offset a8s      ; "%8s"
.text:0804896C                 call    __isoc99_scanf  ;  用户输入
.text:08048971                 add     esp, 10h
.text:08048974                 mov     [ebp+var_38], 0
.text:0804897B                 jmp     short loc_80489AA
.text:0804897D ; ---------------------------------------------------------------------------
.text:0804897D
.text:0804897D loc_804897D:                            ; CODE XREF: main+B0↓j
.text:0804897D                 lea     edx, [ebp+s1]
.text:08048980                 mov     eax, [ebp+var_38]
.text:08048983                 add     eax, edx
.text:08048985                 movzx   eax, byte ptr [eax]
.text:08048988                 movsx   eax, al
.text:0804898B                 sub     esp, 8
.text:0804898E                 push    [ebp+var_38]
.text:08048991                 push    eax
.text:08048992                 call    complex_function
.text:08048997                 add     esp, 10h
.text:0804899A                 mov     ecx, eax
.text:0804899C                 lea     edx, [ebp+s1]
.text:0804899F                 mov     eax, [ebp+var_38]
.text:080489A2                 add     eax, edx
.text:080489A4                 mov     [eax], cl
.text:080489A6                 add     [ebp+var_38], 1
.text:080489AA
.text:080489AA loc_80489AA:                            ; CODE XREF: main+7D↑j
.text:080489AA                 cmp     [ebp+var_38], 7
.text:080489AE                 jle     short loc_804897D  ;  使用for 循环不断调用complex_function() 对数据进行计算
.text:080489B0                 sub     esp, 8
.text:080489B3                 lea     eax, [ebp+s2]
.text:080489B6                 push    eax             ; s2
.text:080489B7                 lea     eax, [ebp+s1]
.text:080489BA                 push    eax             ; s1
.text:080489BB                 call    _strcmp
.text:080489C0                 add     esp, 10h
.text:080489C3                 test    eax, eax
.text:080489C5                 jz      short loc_80489D9
.text:080489C7                 sub     esp, 0Ch
.text:080489CA                 push    offset aTryAgain ; "Try again."
.text:080489CF                 call    puts
.text:080489D4                 add     esp, 10h
.text:080489D7                 jmp     short loc_80489E9
.text:080489D9 ; ---------------------------------------------------------------------------
.text:080489D9
.text:080489D9 loc_80489D9:                            ; CODE XREF: main+C7↑j
.text:080489D9                 sub     esp, 0Ch
.text:080489DC                 push    offset aGoodJob ; "Good Job."
.text:080489E1                 call    puts
.text:080489E6                 add     esp, 10h
.text:080489E9

这个示例的逻辑和01 题是一样的,主要不同的地方是在于这个程序是静态链接编译的,所以程序中包含了一些libc 的函数实现,但是这里可能会存在两个问题:1.这些函数里面隐藏一些出题人的坑;2.这些函数里面的实现可能会依赖其他的系统函数或者实现方式不相同.所以12 题主要是让我们通过Hook 的方式重定向函数中被调用的libc 的函数

首先,Linux 下启动main() 函数需要通过__libc_start_main 对程序进行初始化,然后再跳转到main() 函数;其次,在main() 函数里面调用了printf ,scanf ,puts ,所以我们需要通过Hook 来重定向它们.

幸运的是,我们不需要重新实现这些函数的实现,Angr 代码库里面已经帮我们实现了一部分libc 的函数库,所以我们只需要倒入它们即可.

import angr
import sys


project = angr.Project(sys.argv[1])
initial_state = project.factory.entry_state()
simulation = project.factory.simgr(initial_state,veritesting = True)

project.hook(0x804ed40, angr.SIM_PROCEDURES['libc']['printf']())
project.hook(0x804ed80, angr.SIM_PROCEDURES['libc']['scanf']())
project.hook(0x804f350, angr.SIM_PROCEDURES['libc']['puts']())
project.hook(0x8048d10, angr.SIM_PROCEDURES['glibc']['__libc_start_main']())

def is_successful(state):
  stdout_output = state.posix.dumps(sys.stdout.fileno())
  return 'Good Job.' in str(stdout_output)  # :boolean

def should_abort(state):
  stdout_output = state.posix.dumps(sys.stdout.fileno())
  return 'Try again.' in str(stdout_output)  # :boolean

simulation.explore(find = is_successful,avoid = should_abort)

if simulation.found :
  solution_state = simulation.found[0]
  print(solution_state.posix.dumps(sys.stdin.fileno()))

Angr函数使用总结:

angr.SIM_PROCEDURES[ 系统库名 ] [ 系统函数名 ] () => 获取Angr 内部实现的系统函数

14_angr_shared_library

编译14_angr_shared_library 存在一个小坑,就是在执行命令Python generate.py 1234 14_angr_shared_library 时会报错,内容如下:

root@sec:~/angr_ctf/14_angr_shared_library# python generate.py 1234 14_angr_shared_library
gcc: error: 14_angr_shared_library: No such file or directory

这是因为generate.py 里面有一个Bug ,在最后的一个gcc 编译命令因为-L 参数缺少了指定当前目录,导致在寻找lib14_angr_shared_library.so 的时候找到了系统库目录,所以gcc 抛出了这个找不到14_angr_shared_library: No such file or directory 的问题,代码修改如下:

  with tempfile.NamedTemporaryFile(delete=False, suffix='.c') as temp:
    temp.write(c_code)
    temp.seek(0)
-    os.system('gcc -m32 -I . -L ' + '/'.join(output_file.split('/')[0:-1]) + ' -o ' + output_file + ' ' + temp.name + ' -l' + output_file.split('/')[-1])
+    os.system('gcc -m32 -I . -L . ' + '/'.join(output_file.split('/')[0:-1]) + ' -o ' + output_file + ' ' + temp.name + ' -l' + output_file.split('/')[-1])

程序汇编代码如下:

.text:080486A2                 push    10h             ; n
.text:080486A4                 push    0               ; c
.text:080486A6                 lea     eax, [ebp+s]
.text:080486A9                 push    eax             ; s
.text:080486AA                 call    _memset
.text:080486AF                 add     esp, 10h
.text:080486B2                 sub     esp, 0Ch
.text:080486B5                 push    offset format   ; "Enter the password: "
.text:080486BA                 call    _printf
.text:080486BF                 add     esp, 10h
.text:080486C2                 sub     esp, 8
.text:080486C5                 lea     eax, [ebp+s]
.text:080486C8                 push    eax
.text:080486C9                 push    offset a8s      ; "%8s"
.text:080486CE                 call    ___isoc99_scanf ;  用户输入
.text:080486D3                 add     esp, 10h
.text:080486D6                 sub     esp, 8
.text:080486D9                 push    8
.text:080486DB                 lea     eax, [ebp+s]
.text:080486DE                 push    eax
.text:080486DF                 call    _validate       ;  调用验证
.text:080486E4                 add     esp, 10h
.text:080486E7                 test    eax, eax
.text:080486E9                 jz      short loc_80486FD
.text:080486EB                 sub     esp, 0Ch
.text:080486EE                 push    offset s        ; "Good Job."
.text:080486F3                 call    _puts

_validate() 函数是在另一个so 库中存在的,我们继续分析完当前程序的代码

.plt:08048550 _validate       proc near               ; CODE XREF: main+64↓p
.plt:08048550                 jmp     ds:off_804A020
.plt:08048550 _validate       endp

.got.plt:0804A020 off_804A020     dd offset validate 

extern:0804A04C                 extrn validate:near 

我们来分析一下lib14_angr_shared_library.so 的代码:

.text:000006D7                 public validate
.text:000006D7 validate        proc near               ; DATA XREF: LOAD:00000250↑o
.text:000006D7
.text:000006D7 s2              = byte ptr -24h
.text:000006D7 var_10          = dword ptr -10h
.text:000006D7 var_C           = dword ptr -0Ch
.text:000006D7 s1              = dword ptr  8
.text:000006D7 arg_4           = dword ptr  0Ch
.text:000006D7
.text:000006D7 ; __unwind {
.text:000006D7                 push    ebp
.text:000006D8                 mov     ebp, esp
.text:000006DA                 push    esi
.text:000006DB                 push    ebx
.text:000006DC                 sub     esp, 20h
.text:000006DF                 call    __x86_get_pc_thunk_bx
.text:000006E4                 add     ebx, 191Ch
.text:000006EA                 cmp     [ebp+arg_4], 7
.text:000006EE                 jg      short loc_6FA
.text:000006F0                 mov     eax, 0
.text:000006F5                 jmp     loc_77D
.text:000006FA ; ---------------------------------------------------------------------------
.text:000006FA

;  .....

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

def main(argv):
  path_to_binary = sys.argv[1]  #  注意我们是要load so 库而不是执行程序

  base = 0x400000  #  base 基址是随意定的,可以随意修改
  project = angr.Project(path_to_binary, load_options={
    'main_opts' : {
      'custom_base_addr' : base
    }
  })

  buffer_pointer = claripy.BVV(0x3000000, 32)  #  创建一个buffer 指针值
  validate_function_address = base + 0x6D7
  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) .

  password = claripy.BVS('password', 8 * 8)  #  创建一个求解对象,大小为8 字节
  initial_state.memory.store(buffer_pointer, password)  #  保存到0x30000000

  simulation = project.factory.simgr(initial_state)

  simulation.explore(find = base + 0x783)  #  执行到validate 函数的RETN 指令

  if simulation.found:
    solution_state = simulation.found[0]

    solution_state.add_constraints(solution_state.regs.eax != 0)  #  记得,我们要求validate 函数的返回值为1 的时候就是有解的,那么我们就需要在求解的时候添加上这么一个求解约束条件EAX 不能为False .
    solution = solution_state.se.eval(password)
    print(solution)

15_angr_arbitrary_read

汇编代码:

.text:080484C9 main            proc near               ; DATA XREF: _start+17↑o
.text:080484C9
.text:080484C9 input_buffer    = byte ptr -1Ch         ;  注意这个buffer 的大小是16 字节
.text:080484C9 try_again_string_point= dword ptr -0Ch
.text:080484C9 var_4           = dword ptr -4
.text:080484C9 argc            = dword ptr  8
.text:080484C9 argv            = dword ptr  0Ch
.text:080484C9 envp            = dword ptr  10h
.text:080484C9
.text:080484C9 ; __unwind {
.text:080484C9                 lea     ecx, [esp+4]
.text:080484CD                 and     esp, 0FFFFFFF0h
.text:080484D0                 push    dword ptr [ecx-4]
.text:080484D3                 push    ebp
.text:080484D4                 mov     ebp, esp
.text:080484D6                 push    ecx
.text:080484D7                 sub     esp, 24h
.text:080484DA                 mov     eax, try_again 
.text:080484DF                 mov     [ebp+try_again_string_point], eax  ;  把字符串try_again 的指针保存的局部变量try_again_string_point
.text:080484E2                 sub     esp, 0Ch
.text:080484E5                 push    offset aEnterThePasswo ; "Enter the password: "
.text:080484EA                 call    _printf
.text:080484EF                 add     esp, 10h
.text:080484F2                 sub     esp, 4
.text:080484F5                 lea     eax, [ebp+input_buffer]
.text:080484F8                 push    eax
.text:080484F9                 push    offset check_key
.text:080484FE                 push    offset aU20s    ; "%u %20s"
.text:08048503                 call    ___isoc99_scanf  ;  用户input 两个输入:check_key 和20 字节的input_buffer
.text:08048508                 add     esp, 10h
.text:0804850B                 mov     eax, ds:check_key
.text:08048510                 cmp     eax, 228BF7Eh
.text:08048515                 jz      short loc_8048531
.text:08048517                 cmp     eax, 3AD516Ah
.text:0804851C                 jnz     short loc_8048542  ;  这里根据check_key 的输入来进行跳转到不同的puts 中
.text:0804851E                 mov     eax, try_again
.text:08048523                 sub     esp, 0Ch
.text:08048526                 push    eax             ; s
.text:08048527                 call    _puts
.text:0804852C                 add     esp, 10h
.text:0804852F                 jmp     short loc_8048553
.text:08048531 ; ---------------------------------------------------------------------------
.text:08048531
.text:08048531 loc_8048531:                            ; CODE XREF: main+4C↑j
.text:08048531                 mov     eax, [ebp+try_again_string_point]  ;  我们知道,input_buffer 的大小为16 字节,但是scanf() 输入时是20 字节,所以可以导致try_again_string_point 可以被覆盖,于是需要满足条件input_buffer = 0x228BF7E ,我们就可以控制puts 的输出了.
.text:08048534                 sub     esp, 0Ch
.text:08048537                 push    eax             ; s
.text:08048538                 call    _puts
.text:0804853D                 add     esp, 10h
.text:08048540                 jmp     short loc_8048553
.text:08048542 ; ---------------------------------------------------------------------------
.text:08048542
.text:08048542 loc_8048542:                            ; CODE XREF: main+53↑j
.text:08048542                 mov     eax, try_again
.text:08048547                 sub     esp, 0Ch
.text:0804854A                 push    eax             ; s
.text:0804854B                 call    _puts
.text:08048550                 add     esp, 10h
.text:08048553
.text:08048553 loc_8048553:                            ; CODE XREF: main+66↑j
.text:08048553                                         ; main+77↑j
.text:08048553                 nop

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

def main(argv):
  path_to_binary = argv[1]
  project = angr.Project(path_to_binary)

  initial_state = project.factory.entry_state()

  class ReplacementScanf(angr.SimProcedure):  #  实现Scanf Hook 函数

    def run(self, format_string, check_key_address,input_buffer_address):
      scanf0 = claripy.BVS('scanf0', 4 * 8)   # check_key
      scanf1 = claripy.BVS('scanf1', 20 * 8)  # input_buffer

      for char in scanf1.chop(bits=8):
        self.state.add_constraints(char >= '0', char <= 'z')  #  对input_buffer 的输入约束

      self.state.memory.store(check_key_address, scanf0, endness=project.arch.memory_endness)
      self.state.memory.store(input_buffer_address, scanf1,endness=project.arch.memory_endness)  #  保存求解变量到指定的内存中

      self.state.globals['solution0'] = scanf0  #  保存这两个变量到state 中,后续求解需要用到
      self.state.globals['solution1'] = scanf1

  scanf_symbol = '__isoc99_scanf'
  project.hook_symbol(scanf_symbol, ReplacementScanf())  #  Hook scanf 函数

  def check_puts(state):
    puts_parameter = state.memory.load(state.regs.esp + 4, 4, endness=project.arch.memory_endness)  #  获取puts() 函数的参数

    if state.se.symbolic(puts_parameter):  #  检查这个参数是否为符号化对象
      good_job_string_address = 0x4D525854B

      copied_state = state.copy()  #  复制执行状态上下文进行约束求解,不影响原理的执行上下文

      copied_state.add_constraints(puts_parameter == good_job_string_address)  #  puts 的参数地址是否可以被指定为0x4D525854B ,如果可以的话,那就证明这个值是可控的

      if copied_state.satisfiable():  #  判断添加了上面这个约束是否有解
        state.add_constraints(puts_parameter == good_job_string_address)  #  如果有解的话就保存到我们执行的那个状态对象
        return True 
      else:
        return False
    else:
      return False

  simulation = project.factory.simgr(initial_state)
    
  def is_successful(state):
    puts_address = 0x8048370  #  当程序执行到puts() 函数时,我们就认为路径探索到了这里,然后再去通过check_puts() 判断这里是否存在漏洞,告诉Angr这是不是我们需要找的那条执行路径
    
    if state.addr == puts_address:
      return check_puts(state)
    else:
      return False

  simulation.explore(find=is_successful)

  if simulation.found:
    solution_state = simulation.found[0]

    solution0 = solution_state.se.eval(solution_state.globals['solution0'])
    solution1 = solution_state.se.eval(solution_state.globals['solution1'],cast_to=bytes)  #  输出字符串序列化的内容

    print(solution0,solution1)

Angr函数使用总结:

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

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

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

16_angr_arbitrary_write

汇编代码:

.text:08048569 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:08048569                 public main
.text:08048569 main            proc near               ; DATA XREF: _start+17↑o
.text:08048569
.text:08048569 input_buffer    = byte ptr -1Ch
.text:08048569 target_buffer   = dword ptr -0Ch
.text:08048569 var_4           = dword ptr -4
.text:08048569 argc            = dword ptr  8
.text:08048569 argv            = dword ptr  0Ch
.text:08048569 envp            = dword ptr  10h
.text:08048569
.text:08048569 ; __unwind {
.text:08048569                 lea     ecx, [esp+4]
.text:0804856D                 and     esp, 0FFFFFFF0h
.text:08048570                 push    dword ptr [ecx-4]
.text:08048573                 push    ebp
.text:08048574                 mov     ebp, esp
.text:08048576                 push    ecx
.text:08048577                 sub     esp, 24h
.text:0804857A                 mov     [ebp+target_buffer], offset unimportant_buffer
.text:08048581                 sub     esp, 4
.text:08048584                 push    10h             ; n
.text:08048586                 push    0               ; c
.text:08048588                 lea     eax, [ebp+input_buffer]
.text:0804858B                 push    eax             ; s
.text:0804858C                 call    _memset         ;  清空input_buffer 的内容
.text:08048591                 add     esp, 10h
.text:08048594                 sub     esp, 4
.text:08048597                 push    0Ch             ; n
.text:08048599                 push    offset src      ; "PASSWORD"
.text:0804859E                 push    offset password_buffer ; dest
.text:080485A3                 call    _strncpy        ;  复制PASSWORD 到全局内存password_buffer
.text:080485A8                 add     esp, 10h
.text:080485AB                 sub     esp, 0Ch
.text:080485AE                 push    offset aEnterThePasswo ; "Enter the password: "
.text:080485B3                 call    _printf
.text:080485B8                 add     esp, 10h
.text:080485BB                 sub     esp, 4
.text:080485BE                 lea     eax, [ebp+input_buffer]
.text:080485C1                 push    eax
.text:080485C2                 push    offset check_key
.text:080485C7                 push    offset aU20s    ; "%u %20s"
.text:080485CC                 call    ___isoc99_scanf  ;  scanf("%u %20s",check_key,input_buffer) .注意input_buffer 的大小是20 字节,栈上的input_buffer 默认的大小是16 字节,最后4 字节可以覆盖target_buffer .
.text:080485D1                 add     esp, 10h
.text:080485D4                 mov     eax, ds:check_key
.text:080485D9                 cmp     eax, 1A25D71h
.text:080485DE                 jz      short loc_80485E9
.text:080485E0                 cmp     eax, 1CB7D43h
.text:080485E5                 jz      short loc_8048601  ;  根据check_key 的输入来跳转到不同的_strncpy
.text:080485E7                 jmp     short loc_8048618
.text:080485E9 ; ---------------------------------------------------------------------------
.text:080485E9
.text:080485E9 loc_80485E9:                            ; CODE XREF: main+75↑j
.text:080485E9                 sub     esp, 4
.text:080485EC                 push    10h             ; n
.text:080485EE                 lea     eax, [ebp+input_buffer]
.text:080485F1                 push    eax             ; src
.text:080485F2                 push    offset unimportant_buffer ; dest
.text:080485F7                 call    _strncpy
.text:080485FC                 add     esp, 10h
.text:080485FF                 jmp     short loc_804862E
.text:08048601 ; ---------------------------------------------------------------------------
.text:08048601
.text:08048601 loc_8048601:                            ; CODE XREF: main+7C↑j
.text:08048601                 mov     eax, [ebp+target_buffer]  ;  注意这个是MOV 指令,意思是获取EBP + target_buffer 这个地址的内容保存到EAX 中
.text:08048604                 sub     esp, 4
.text:08048607                 push    10h             ; n
.text:08048609                 lea     edx, [ebp+input_buffer]  ;  注意这个是LEA 指令,意思是计算出EBP + input_buffer 的地址保存到EBX 中
.text:0804860C                 push    edx             ; src
.text:0804860D                 push    eax             ; dest
.text:0804860E                 call    _strncpy  ;  漏洞点在这里,strncpy(*target_buffer,input_buffer) ,也就是说input_buffer 最后四字节可以控制对任意地址的_strncpy() .总结起来就是strncpy(input_buffer[ -4 : ],input_buffer,0x10) .
.text:08048613                 add     esp, 10h
.text:08048616                 jmp     short loc_804862E
.text:08048618 ; ---------------------------------------------------------------------------
.text:08048618
.text:08048618 loc_8048618:                            ; CODE XREF: main+7E↑j
.text:08048618                 sub     esp, 4
.text:0804861B                 push    10h             ; n
.text:0804861D                 lea     eax, [ebp+input_buffer]
.text:08048620                 push    eax             ; src
.text:08048621                 push    offset unimportant_buffer ; dest
.text:08048626                 call    _strncpy
.text:0804862B                 add     esp, 10h
.text:0804862E
.text:0804862E loc_804862E:                            ; CODE XREF: main+96↑j
.text:0804862E                                         ; main+AD↑j
.text:0804862E                 nop
.text:0804862F                 sub     esp, 4
.text:08048632                 push    8               ; n
.text:08048634                 push    offset key_string       ; "KZYRKMKE"
.text:08048639                 push    offset password_buffer ; s1
.text:0804863E                 call    _strncmp        ;  我们知道了上面有一个任意地址写之后,我们就需要改写key_string 或者password_buffer 一致,让_strncmp() 返回0 ,跳转到puts("Good Job")
.text:08048643                 add     esp, 10h
.text:08048646                 test    eax, eax
.text:08048648                 jz      short loc_804865C
.text:0804864A                 sub     esp, 0Ch
.text:0804864D                 push    offset s        ; "Try again."
.text:08048652                 call    _puts
.text:08048657                 add     esp, 10h
.text:0804865A                 jmp     short loc_804866C
.text:0804865C ; ---------------------------------------------------------------------------
.text:0804865C
.text:0804865C loc_804865C:                            ; CODE XREF: main+DF↑j
.text:0804865C                 sub     esp, 0Ch
.text:0804865F                 push    offset aGoodJob ; "Good Job."
.text:08048664                 call    _puts
.text:08048669                 add     esp, 10h

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

def main(argv):
  path_to_binary = argv[1]
  project = angr.Project(path_to_binary)
  initial_state = project.factory.entry_state()

  class ReplacementScanf(angr.SimProcedure):

    def run(self, format_string, check_key ,input_buffer):
      scanf0 = claripy.BVS('scanf0', 4 * 8)
      scanf1 = claripy.BVS('scanf1', 20 * 8)

      for char in scanf1.chop(bits=8):
        self.state.add_constraints(char >= '0', char <= 'z')

      self.state.memory.store(check_key, scanf0, endness=project.arch.memory_endness)
      self.state.memory.store(input_buffer, scanf1, endness=project.arch.memory_endness)

      self.state.globals['solution0'] = scanf0
      self.state.globals['solution1'] = scanf1

  scanf_symbol = '__isoc99_scanf'
  project.hook_symbol(scanf_symbol, ReplacementScanf())

  def check_strncpy(state):
    strncpy_dest = state.memory.load(state.regs.esp + 4, 4, endness=project.arch.memory_endness)  #  获取strncpy() 的参数,strncpy_dest ..
    strncpy_src  = state.memory.load(state.regs.esp + 8, 4, endness=project.arch.memory_endness)
    strncpy_len  = state.memory.load(state.regs.esp + 12, 4, endness=project.arch.memory_endness)
    src_contents = state.memory.load(strncpy_src, strncpy_len)  #  因为参数中只保存了地址,我们需要根据这个地址去获取内容

    if state.se.symbolic(strncpy_dest) and state.se.symbolic(src_contents) :  #  判断dest 和src 的内容是不是符号化对象
      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'
        state.add_constraints(src_contents[ -1 : -64 ] == 'KZYRKMKE',strncpy_dest == 0x4D52584C)
        return True
      else:
        return False
    else:
      return False

  simulation = project.factory.simgr(initial_state)

  def is_successful(state):
    strncpy_address = 0x8048410

    if state.addr == strncpy_address:
      return check_strncpy(state)
    else:
      return False

  simulation.explore(find=is_successful)

  if simulation.found:
    solution_state = simulation.found[0]
    solution0 = solution_state.se.eval(solution_state.globals['solution0'])
    solution1 = solution_state.se.eval(solution_state.globals['solution1'],cast_to=bytes)

    print(solution0,solution1)

Angr函数使用总结:

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

17_angr_arbitrary_jump

汇编代码:

.text:4D525886 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:4D525886                 public main
.text:4D525886 main            proc near               ; DATA XREF: _start+17↑o
.text:4D525886
.text:4D525886 var_C           = dword ptr -0Ch
.text:4D525886 var_4           = dword ptr -4
.text:4D525886 argc            = dword ptr  8
.text:4D525886 argv            = dword ptr  0Ch
.text:4D525886 envp            = dword ptr  10h
.text:4D525886
.text:4D525886 ; __unwind {
.text:4D525886                 lea     ecx, [esp+4]
.text:4D52588A                 and     esp, 0FFFFFFF0h
.text:4D52588D                 push    dword ptr [ecx-4]
.text:4D525890                 push    ebp
.text:4D525891                 mov     ebp, esp
.text:4D525893                 push    ecx
.text:4D525894                 sub     esp, 14h
.text:4D525897                 mov     [ebp+var_C], 0
.text:4D52589E                 sub     esp, 0Ch
.text:4D5258A1                 push    offset aEnterThePasswo ; "Enter the password: "
.text:4D5258A6                 call    _printf
.text:4D5258AB                 add     esp, 10h
.text:4D5258AE                 call    read_input  ;  小细节,注意read_input 是stdcall 的调用方法
.text:4D5258B3                 sub     esp, 0Ch
.text:4D5258B6                 push    offset aTryAgain ; "Try again."
.text:4D5258BB                 call    _puts
.text:4D5258C0                 add     esp, 10h
.text:4D5258C3                 mov     eax, 0
.text:4D5258C8                 mov     ecx, [ebp+var_4]
.text:4D5258CB                 leave
.text:4D5258CC                 lea     esp, [ecx-4]
.text:4D5258CF                 retn

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

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

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

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

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

def main(argv):
  path_to_binary = argv[1]
  project = angr.Project(path_to_binary)
  initial_state = project.factory.entry_state()

  simulation = project.factory.simgr(
    initial_state,
    save_unconstrained=True,
    stashes={
      'active' : [initial_state],
      'unconstrained' : [],
      'found' : [],
      'not_needed' : []
    }
  )

  class ReplacementScanf(angr.SimProcedure):

    def run(self, format_string, input_buffer_address):
      input_buffer = claripy.BVS('input_buffer', 64 * 8)  #  设置一个较大的input_buffer

      for char in input_buffer.chop(bits=8):
        self.state.add_constraints(char >= '0', char <= 'z')

      self.state.memory.store(input_buffer_address, input_buffer, endness=project.arch.memory_endness)

      self.state.globals['solution'] = input_buffer

  scanf_symbol = '__isoc99_scanf'
  project.hook_symbol(scanf_symbol, ReplacementScanf())  #  对scanf() 做Hook

  while (simulation.active or simulation.unconstrained) and (not simulation.found):  #  
    for unconstrained_state in simulation.unconstrained:
      def should_move(s):
        return s is unconstrained_state
      
      simulation.move('unconstrained', 'found', filter_func=should_move)  #  保存

    simulation.step()  #  步进执行

  if simulation.found:
    solution_state = simulation.found[0]

    solution_state.add_constraints(solution_state.regs.eip == 0x4D525849)  #  判断EIP 地址是否可控

    solution = solution_state.se.eval(solution_state.globals['solution'],cast_to = bytes)  #  生成Payload
    print(solution)

About

The learn note of Angr-CTF ..

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published