Angr-CTF学习笔记1-5

admin 2022年10月29日23:51:01评论30 views字数 22124阅读73分44秒阅读模式

Angr-CTF

Angr-CTF学习笔记1-5

如何使用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(地址,数据) => 初始化内存地址中的数据


原文始发于微信公众号(安全狗的自我修养):Angr-CTF学习笔记1-5

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年10月29日23:51:01
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Angr-CTF学习笔记1-5http://cn-sec.com/archives/1380420.html

发表评论

匿名网友 填写信息