0%

angrLAB笔记

实验开始前还得先把文件编译一下

在相应目录下,使用如下命令,如

1
python generate.py 1234 00_angr_find

贴一个angr中文文档angr (非官方)

angr-官方,英文的。angr-api,英语苦手泪目。

(本题中,有些导入的库不一定那个用得到,但是题目数量多,懒得增删改,就一次性导入了…)

00_angr_find

先上伪代码

image-20220308172612146

当然,其实这整个实验,汇编语言是很重要的,伪代码是为了帮助理清程序逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.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:

(这题可以直接逆来着)

发现程序中正确结果的走向,有good job语句,利用angr的explore,让angr找到相对应路径,执行即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import angr
import sys
import claripy
from Crypto.Util.number import long_to_bytes

def main():
path_to_binary = "E:\\LAB\\angr\\angr\program\\00_angr_find"#打开二进制文件
project = angr.Project(path_to_binary, auto_load_libs=False)#创建对象
initial_state = project.factory.entry_state()#state初始化
simulation = project.factory.simgr(initial_state)

print_good_address = 0x8048678
simulation.explore(find=print_good_address)#寻找地址对象

if simulation.found:
solution_state = simulation.found[0]
solution = solution_state.posix.dumps(sys.stdin.fileno())
print("THE Answer is: {}".format(solution))
else:
raise Exception('Could not find the solution')
if __name__ == "__main__":
main()

01_angr_avoid

这题因为太大反汇编不了,所以还是得看汇编。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.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

.....
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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

有俩个函数,avoid me和maybegood。字面意思,正确寻址应该在后者里。

查找到goodjob地址为0x080485E0

要避开的tryagain在0x080485F2

(其实这题就把上一题的脚本拿来改个地址也可以得出答案,不过会慢很多,因为不会避开avoidme函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import angr
import sys
import claripy
from Crypto.Util.number import long_to_bytes

def main():
path_to_binary = "E:\\LAB\\angr\\angr\program\\01_angr_avoid"
project = angr.Project(path_to_binary, auto_load_libs=False)
initial_state = project.factory.entry_state()
simulation = project.factory.simgr(initial_state)

avoid_me_address = 0x080485A8#avoidme函数首地址,不推介用tryagain来避开,应为没有避开avoidme函数,时间要很久。
maybe_good_address = 0x080485E0

simulation.explore(find=maybe_good_address, avoid=avoid_me_address)

if simulation.found:
solution_state = simulation.found[0]
solution = solution_state.posix.dumps(sys.stdin.fileno())
print("ANSWER is: {}".format(solution))
else:
raise Exception('Could not find the solution')
if __name__ == "__main__":
main()

这个脚本相比于上一道题,在于explore中多了一个avoid=要排除执行的路径。

02_angr_find_condition

image-20220308190311979

首先乍一看,多个goodjob,这题肯定就不能直接寻址goodjob了,不唯一。

image-20220308190505546

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
.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

当然,这题想要直接逆也是可以的,不过我们还是用angr

首先是思路:够狠上面一样题的是,还是应该回避tryagain一类的失败路径,然后再设置两个回调函数,当返回tryagain时,回避路径,反之则不回避。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import angr
import sys
import claripy
from Crypto.Util.number import long_to_bytes

def main():
path_to_binary = "E:\\LAB\\angr\\angr\program\\02_angr_find_condition"
project = angr.Project(path_to_binary, auto_load_libs=False)
initial_state = project.factory.entry_state()
simulation = project.factory.simgr(initial_state)

def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())#获取模拟执行的控制台输出
if b'Good Job.' in stdout_output:#根据模拟控制台得输出结果来判断,下面同理。
return True#发现路径
else:
return False#忽略路径

def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
if b'Try again.' in stdout_output:
return True
else:
return False

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("The ANSWER is: {}".format(solution))
else:
raise Exception('Could not find the solution')

if __name__ == "__main__":
main()

03_angr_symbolic_registers

image-20220308211050475

上面是伪代码。

主函数汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
.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

可以看到,四个函数调用。第一个就是让我们输入三个数;eax,ebx,edx分别存放输入数据,也是接下来三个函数的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
.text:0804889A get_user_input  proc near               ; CODE XREF: main+21↓p
.text:0804889A
.text:0804889A var_18 = dword ptr -18h
.text:0804889A var_14 = dword ptr -14h
.text:0804889A var_10 = dword ptr -10h
.text:0804889A var_C = dword ptr -0Ch
.text:0804889A
.text:0804889A ; __unwind {
.text:0804889A push ebp
.text:0804889B mov ebp, esp
.text:0804889D sub esp, 18h
.text:080488A0 mov ecx, large gs:14h
.text:080488A7 mov [ebp+var_C], ecx
.text:080488AA xor ecx, ecx
.text:080488AC lea ecx, [ebp+var_10]
.text:080488AF push ecx
.text:080488B0 lea ecx, [ebp+var_14]
.text:080488B3 push ecx
.text:080488B4 lea ecx, [ebp+var_18]
.text:080488B7 push ecx
.text:080488B8 push offset aXXX ; "%x %x %x"
.text:080488BD call ___isoc99_scanf
.text:080488C2 add esp, 10h
.text:080488C5 mov ecx, [ebp+var_18]
.text:080488C8 mov eax, ecx
.text:080488CA mov ecx, [ebp+var_14]
.text:080488CD mov ebx, ecx
.text:080488CF mov ecx, [ebp+var_10]
.text:080488D2 mov edx, ecx
.text:080488D4 nop
.text:080488D5 mov ecx, [ebp+var_C]
.text:080488D8 xor ecx, large gs:14h
.text:080488DF jz short locret_80488E6
.text:080488E1 call ___stack_chk_fail
.text:080488E6 ; ---------------------------------------------------------------------------
.text:080488E6
.text:080488E6 locret_80488E6: ; CODE XREF: get_user_input+45↑j
.text:080488E6 leave
.text:080488E7 retn
.text:080488E7 ; } // starts at 804889A
.text:080488E7 get_user_input endp

我们看接下来三个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
.text:08048509 complex_function_1 proc near            ; CODE XREF: main+35↓p
.text:08048509
.text:08048509 arg_0 = dword ptr 8
.text:08048509
.text:08048509 ; __unwind {
.text:08048509 push ebp
.text:0804850A mov ebp, esp
.text:0804850C xor [ebp+arg_0], 0A78D4A5Fh
.text:08048513 add [ebp+arg_0], 3EC98793h
.text:0804851A xor [ebp+arg_0], 51FCDF96h
.text:08048521 add [ebp+arg_0], 546F1B35h
.text:08048528 add [ebp+arg_0], 7CD06332h
.text:0804852F add [ebp+arg_0], 3BEEDF0Bh
.text:08048536 add [ebp+arg_0], 5824146Ch
.text:0804853D add [ebp+arg_0], 15CBC4FFh
.text:08048544 mov ecx, [ebp+arg_0]
.text:08048547 sub ecx, 46C32D9h
.text:0804854D mov [ebp+arg_0], ecx
.text:08048550 xor [ebp+arg_0], 9B99C626h
.text:08048557 add [ebp+arg_0], 6590A23Dh
.text:0804855E xor [ebp+arg_0], 0D16C9C3Ch
.text:08048565 xor [ebp+arg_0], 0D73D3031h
.text:0804856C xor [ebp+arg_0], 37EB59D3h
.text:08048573 mov ecx, [ebp+arg_0]
.text:08048576 sub ecx, 5DDF68E6h
.text:0804857C mov [ebp+arg_0], ecx
.text:0804857F mov ecx, [ebp+arg_0]
.text:08048582 sub ecx, 64F2CB17h
.text:08048588 mov [ebp+arg_0], ecx
.text:0804858B xor [ebp+arg_0], 0F1C347B6h
.text:08048592 xor [ebp+arg_0], 0BB664966h
.text:08048599 xor [ebp+arg_0], 0DCD816D2h
.text:080485A0 xor [ebp+arg_0], 0A523DD67h
.text:080485A7 add [ebp+arg_0], 2C64C6B3h
.text:080485AE mov ecx, [ebp+arg_0]
.text:080485B1 sub ecx, 632C11C3h
.text:080485B7 mov [ebp+arg_0], ecx
.text:080485BA add [ebp+arg_0], 220BCB4Ch
.text:080485C1 xor [ebp+arg_0], 0C13B368Ah
.text:080485C8 xor [ebp+arg_0], 7323522h
.text:080485CF mov ecx, [ebp+arg_0]
.text:080485D2 sub ecx, 7FA5CE75h
.text:080485D8 mov [ebp+arg_0], ecx
.text:080485DB add [ebp+arg_0], 2BA5F91Ah
.text:080485E2 xor [ebp+arg_0], 0CEF3925Eh
.text:080485E9 xor [ebp+arg_0], 879B8252h
.text:080485F0 add [ebp+arg_0], 6D4F923Bh
.text:080485F7 mov ecx, [ebp+arg_0]
.text:080485FA sub ecx, 491AF770h
.text:08048600 mov [ebp+arg_0], ecx
.text:08048603 xor [ebp+arg_0], 96C75B6h
.text:0804860A xor [ebp+arg_0], 51EC989Bh
.text:08048611 xor [ebp+arg_0], 0DE67C3E3h
.text:08048618 add [ebp+arg_0], 2C688544h
.text:0804861F xor [ebp+arg_0], 6A479F09h
.text:08048626 xor [ebp+arg_0], 0D8B0FF0Dh
.text:0804862D xor [ebp+arg_0], 37172519h
.text:08048634 mov ecx, [ebp+arg_0]
.text:08048637 mov eax, ecx
.text:08048639 pop ebp
.text:0804863A retn
.text:0804863A ; } // starts at 8048509
.text:0804863A complex_function_1 endp

逻辑简单,就是异或。

来说整体思路:

首先是三个参数,要异或过后符合要求,输出goodjob。由于这题没有指出最后要比较的三个数的具体的值,所以我们不能套用前面那的方法。此处应该控制输入的三个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import angr
import sys
import claripy
from Crypto.Util.number import long_to_bytes

def main():
path_to_binary = "E:\\LAB\\angr\\angr\program\\03_angr_symbolic_registers"
project = angr.Project(path_to_binary)
start_addr = 0x0804890E
initial_state = project.factory.blank_state(addr=start_addr)

passwd_size_in_bits = 32
passwd0 = claripy.BVS('passwd0', passwd_size_in_bits)#开始初始化符号位向量
passwd1 = claripy.BVS('passwd1', passwd_size_in_bits)
passwd2 = claripy.BVS('passwd2', passwd_size_in_bits)

initial_state.regs.eax = passwd0#更新寄存器内容
initial_state.regs.ebx = passwd1
initial_state.regs.edx = passwd2

simulation = project.factory.simgr(initial_state)

def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
if b'Good Job.' in stdout_output:
return True
else:
return False

def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
if b'Try again.' in stdout_output:
return True
else:
return False

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

if simulation.found:
for i in simulation.found:
solution_state = i
solution0 = format(solution_state.solver.eval(passwd0), 'x')
solution1 = format(solution_state.solver.eval(passwd1), 'x')
solution2 = format(solution_state.solver.eval(passwd2), 'x')
solution = solution0 + " " + solution1 + " " + solution2
print("The ANSWER is: {}".format(solution))

else:
raise Exception('Could not find the solution')

if __name__ == "__main__":
main()

04_angr_symbolic_stack

image-20220308215004547

可以明确,关键在handle_user函数里面

image-20220308215133653

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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]
.text:0804869A sub esp, 0Ch
.text:0804869D push eax
.text:0804869E call complex_function0
.text:080486A3 add esp, 10h
.text:080486A6 mov [ebp+var_C], eax
.text:080486A9 mov eax, [ebp+var_10]
.text:080486AC sub esp, 0Ch
.text:080486AF push eax
.text:080486B0 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
.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
.text:080486F3 ; } // starts at 8048679
.text:080486F3 handle_user endp

我们可以看一下complex_function0的函数汇编代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
.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:08048590 complex_function0 endp

其实可以看出,跟上一题的思路其实相差不大。所以本题,我们也需要控制两个参数。

故,思路如下:

开始地址设置在输入函数之后,传参数给complex之前。通过complex函数内部可以知道,esp寄存器的值给了ebp(两个complex函数都是这样)。然后我们设置两个参数并将其压入栈。之后的操作和上一题大同小异

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import angr
import claripy
from Crypto.Util.number import long_to_bytes
import sys

pro =angr.Project("E:\\LAB\\angr\\angr\program\\04_angr_symbolic_stack")

start_addr = 0x08048697
init_state = pro.factory.blank_state(addr=start_addr)

init_state.regs.ebp = init_state.regs.esp

passwd0 = claripy.BVS("passwd0", 32)
passwd1 = claripy.BVS("passwd1", 32)


init_state.regs.esp -= 8
init_state.stack_push(passwd0)
init_state.stack_push(passwd1)


simulation = pro.factory.simgr(init_state)

def success(state):
output = state.posix.dumps(1)
return b"Good Job." in output

def abort(state):
output = state.posix.dumps(1)
return b"Try again." in output

simulation.explore(find=success, avoid=abort)

if simulation.found:
res = simulation.found[0]
solution0 = res.solver.eval(passwd0)
solution1 = res.solver.eval(passwd1)

solution = " ".join(map(str, [solution0, solution1]))
print("The Answer is :{}".format(solution))
else:
print("No Answer!")

05_angr_symbolic_memory

image-20220310171010963

还是先贴上伪代码

接着看汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
main            proc near               ; DATA XREF: _start+17↑o
.text:080485A8
.text:080485A8 var_C = dword ptr -0Ch
.text:080485A8 var_4 = dword ptr -4
.text:080485A8 argc = dword ptr 8
.text:080485A8 argv = dword ptr 0Ch
.text:080485A8 envp = dword ptr 10h
.text:080485A8
.text:080485A8 ; __unwind {
.text:080485A8 lea ecx, [esp+4]
.text:080485AC and esp, 0FFFFFFF0h
.text:080485AF push dword ptr [ecx-4]
.text:080485B2 push ebp
.text:080485B3 mov ebp, esp
.text:080485B5 push ecx
.text:080485B6 sub esp, 14h
.text:080485B9 sub esp, 4
.text:080485BC push 21h ; '!' ; n
.text:080485BE push 0 ; c
.text:080485C0 push offset user_input ; s
.text:080485C5 call _memset
.text:080485CA add esp, 10h
.text:080485CD sub esp, 0Ch
.text:080485D0 push offset aEnterThePasswo ; "Enter the password: "
.text:080485D5 call _printf
.text:080485DA add esp, 10h
.text:080485DD sub esp, 0Ch
.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]
.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
.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:08048686 ; } // starts at 80485A8
.text:08048686 main endp

思路跟上一题差不多,函数开始地址还是设在相应的位置0x08048601

然后是初始化符号位向量,且注意四个数据的地址,然后是一如既往的设置成功的路径和避免的路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import angr
import claripy
from Crypto.Util.number import long_to_bytes
import sys

project =angr.Project("E:\\LAB\\angr\\angr\program\\05_angr_symbolic_memory")

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 ]))

solution = " ".join(map(str, [solution0, solution1]))
print("The Answer is :{}".format(solution))
else:
print("No Answer!")

06_angr_symbolic_dynamic_memory

image-20220312101104842

伪代码如上。

看一下程序逻辑,输入两个数据,经过处理,比较,相等则通过。要注意buffer两个数据是存放在堆区的。、

所以在

main函数部分汇编代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
.text:08048691                 call    ___isoc99_scanf
.text:08048696 add esp, 10h
.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

再来看看complex的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
.text:080485A9 complex_function proc near              ; CODE XREF: main+BA↓p
.text:080485A9 ; main+EC↓p
.text:080485A9
.text:080485A9 arg_0 = dword ptr 8
.text:080485A9 arg_4 = dword ptr 0Ch
.text:080485A9
.text:080485A9 ; __unwind {
.text:080485A9 push ebp
.text:080485AA mov ebp, esp
.text:080485AC sub esp, 8
.text:080485AF cmp [ebp+arg_0], 40h ; '@'
.text:080485B3 jle short loc_80485BB
.text:080485B5 cmp [ebp+arg_0], 5Ah ; 'Z'
.text:080485B9 jle short loc_80485D5
.text:080485BB
.text:080485BB loc_80485BB: ; CODE XREF: complex_function+A↑j
.text:080485BB sub esp, 0Ch
.text:080485BE push offset s ; "Try again."
.text:080485C3 call _puts
.text:080485C8 add esp, 10h
.text:080485CB sub esp, 0Ch
.text:080485CE push 1 ; status
.text:080485D0 call _exit
.text:080485D5 ; ---------------------------------------------------------------------------
.text:080485D5
.text:080485D5 loc_80485D5: ; CODE XREF: complex_function+10↑j
.text:080485D5 mov eax, [ebp+arg_0]
.text:080485D8 lea ecx, [eax-41h]
.text:080485DB mov edx, [ebp+arg_4]
.text:080485DE mov eax, edx
.text:080485E0 add eax, eax
.text:080485E2 add eax, edx
.text:080485E4 shl eax, 2
.text:080485E7 add eax, edx
.text:080485E9 add ecx, eax
.text:080485EB mov edx, 4EC4EC4Fh
.text:080485F0 mov eax, ecx
.text:080485F2 imul edx
.text:080485F4 sar edx, 3
.text:080485F7 mov eax, ecx
.text:080485F9 sar eax, 1Fh
.text:080485FC sub edx, eax
.text:080485FE mov eax, edx
.text:08048600 imul eax, 1Ah
.text:08048603 sub ecx, eax
.text:08048605 mov eax, ecx
.text:08048607 add eax, 41h ; 'A'
.text:0804860A leave
.text:0804860B retn
.text:0804860B ; } // starts at 80485A9
.text:0804860B complex_function endp

本题跟上面一题脚本的主要区别在于,要设置数据保存的地址。而malloc是在堆区中分配的,而没有明确地址。故脚本中我们要指出地址来存放数据(随意指定,只要不在代码区等影响程序运行的地方就行)。以及需要指定数据在内存中是以小端序存储的(angr默认是大端序)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import angr
import claripy
from Crypto.Util.number import long_to_bytes
import sys

project =angr.Project("E:\\LAB\\angr\\angr\program\\06_angr_symbolic_dynamic_memory")

start_addr = 0x08048699
initial_state = project.factory.blank_state(addr=start_addr)

password0 = claripy.BVS('password0', 64)
password1 = claripy.BVS('password1', 64)
fake0_addr = 0x4444440
fake1_addr = 0x4444450

buffer0_addr = 0x09FD92AC
buffer1_addr = 0x09FD92B4
initial_state.memory.store(buffer0_addr,fake0_addr,endness=project.arch.memory_endness)#endness=...就是指定存储端序
initial_state.memory.store(buffer1_addr,fake1_addr,endness=project.arch.memory_endness)

initial_state.memory.store(fake0_addr, password0)
initial_state.memory.store(fake1_addr, password1)

simulation = project.factory.simgr(initial_state)

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

def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b'Try again.' in 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)
solution = ' '.join(map('{:x}'.format, [ solution0, solution1 ]))

print("The Answer is :{}".format(solution))
else:
print("No Answer!")

07_angr_symbolic_file

image-20220312112246653

伪代码整体逻辑为输入数据后,经过ignoerme处理,并保存在文件里MRXJ…txt,然后读取文件的数据,经过计算然后比较。

再看看ignoreme的伪代码

image-20220312113844308

main函数部分汇编代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
.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
.text:08048939 add esp, 10h
.text:0804893C mov [ebp+var_C], 0
.text:08048943 jmp short loc_8048972
.text:08048945 ; ----------------------------------------------------------------------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import angr
import claripy
from Crypto.Util.number import long_to_bytes
import sys

project =angr.Project("E:\\LAB\\angr\\angr\program\\07_angr_symbolic_file")

start_addr = 0x080488EA#这个位置并没有符号化文件名

filename = 'MRXJKZYR.txt'
symbolic_file_size_bytes = 64

password = claripy.BVS('password', symbolic_file_size_bytes * 8)
password_file = angr.SimFile(filename, content=password, size=symbolic_file_size_bytes)
#模拟读取文件

initial_state = project.factory.blank_state(addr=start_addr,fs{filename:password_file}) #构建状态上下文里的文件数据系统
simulation = project.factory.simgr(initial_state)

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

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

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

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

solution = long_to_bytes(solution_state.solver.eval(password))
print("The Answer is :{}".format(solution))

else:
print("No Answer!")

08_angr_constraints

image-20220312115905211

惯例伪代码

逻辑很简单输入数据经过变化后与passwd比较,不过是在函数中比较。

main部分汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
.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
.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
.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

complex的代码跟刚开始两道差不多,就不放了

思路:

开始地址还是设在输入后调用complex前,0x8048625

然后构造输入,从后面的比较函数的参数来看,buffer是16(0x10)个字节,80比特大小。再者就是buffer的地址

然后是循环的计算,在8048669位置结束,从下一行开始,就是往检查函数传参了。

需要注意的是,在检查函数里,是一个一个字符进行比较,这样就会让程序每次执行if语句16次,然后计算该走的分支,如此一算就是2^16,这就会产生路径爆炸问题。故,通过添加约束条件来避免这个问题。

列出脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import angr
import claripy
from Crypto.Util.number import long_to_bytes
import sys

project =angr.Project("E:\\LAB\\angr\\angr\program\\08_angr_constraints")

start_addr = 0x08048625
initial_state = project.factory.blank_state(addr=start_addr)

buffer = claripy.BVS('buffer', 16*8)
buffer_addr = 0x0804A050
initial_state.memory.store(buffer_addr, buffer)

simulation = project.factory.simgr(initial_state)

addr_to_check_constraint = 0x08048669
simulation.explore(find=addr_to_check_constraint)

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

constrained_parameter_addr = 0x0804A050
constrained_parameter_size_bytes = 16
constrained_parameter_bitvector = solution_state.memory.load(constrained_parameter_addr, constrained_parameter_size_bytes) # 从内存中加载buffer

constrained_parameter_desired_value = 'MRXJKZYRKMKENFZB'

constrained_expression = constrained_parameter_bitvector == constrained_parameter_desired_value # 约束表达式

solution_state.add_constraints(constrained_expression) # 添加约束

solution = long_to_bytes(solution_state.se.eval(buffer))

print("The ANSWER is :{}".format(solution.decode("utf-8")))
else:
print("No Answer!")

09_angr_hooks

image-20220312155034471

程序逻辑大致为先后输入两个数据,各自经过一系列变化,然后比较。

可以看到本题依旧有同样的检查函数,那么同上题一样,相同原理也会产生路径爆炸问题,本题如其名,我们通过angr的hook来避免路径爆炸,所以,我们钩取检查函数,定义一个自己的检查函数来作为替换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import angr
import claripy
from Crypto.Util.number import long_to_bytes
import sys

project =angr.Project("E:\\LAB\\angr\\angr\program\\09_angr_hooks")

initial_state = project.factory.entry_state()

check_equals_called_address = 0x80486B3 # 检查函数地址
instruction_to_skip_length = 0x5 # call check_equals_MRXJKZYRKMKENFZB这条指令的长度

@project.hook(check_equals_called_address, length=instruction_to_skip_length)#钩取
def skip_check_equals_(state):
user_input_buffer_address = 0x804A054 # 输入的地址
user_input_buffer_length = 0x10 # 输入的长度

user_input_string = state.memory.load( # 加载输入的数据
user_input_buffer_address,
user_input_buffer_length
)

check_against_string = 'MRXJKZYRKMKENFZB'

state.regs.eax = claripy.If( # 添加约束
user_input_string == check_against_string, # 条件
claripy.BVV(1, 32), # 真则返回
claripy.BVV(0, 32) # 假则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)

if simulation.found:
solution_state = simulation.found[0]
solution = solution_state.posix.dumps(sys.stdin.fileno())
print("The ANSWER is :{}".format(solution.decode("utf-8")))
else:
print("No Answer!")

10_angr_simprocedures

image-20220312163054462

程序逻辑大同小异,不说了。

虽然说上一道和这一道没放汇编代码,但还是要看的,比如这道

image-20220312164107756

可以看到检查函数被调用了多次,那么我们无法通过准确的地址来进行钩取,故,我们需要通过函数名来钩取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import angr
import claripy
from Crypto.Util.number import long_to_bytes
import sys

project =angr.Project("E:\\LAB\\angr\\angr\program\\10_angr_simprocedures")

initial_state = project.factory.entry_state()

class ReplacementCheckEquals(angr.SimProcedure):

def run(self, check_data_address, check_data_length): #参数后俩个是要hook函数的参数
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'
project.hook_symbol(check_equals_symbol, ReplacementCheckEquals()) #通过函数名钩取检查函数并替换为我们的检查函数

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("The ANSWER is :{}".format(solution.decode("utf-8")))
else:
print("No Answer!")

本题脚本与上一题整体差别不大。主要在函数钩取方式上。

11_angr_sim_scanf

image-20220312164721082

程序逻辑很明了,将原有数据变化,然后输入两个数据(每个四字节)与变化的数据作比较。

查看汇编代码发现,有多次调用输入函数,并比较的代码。这就是本题的关键点。

其实跟上一题差不多,这次很多调用的是输入,不是检查,相同的方法,钩取输入函数。不过参数设置上需要注意,数据大小,以及将数据以小端形式保存到地址中。并且需要将两个输入数据设置为全局变量。(需要重新设置两个变量来存储,因为我们在函数中设置的只是自定义的输入函数的局部变量,要想传到main中,得设置全局变量)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import angr
import claripy
from Crypto.Util.number import long_to_bytes
import sys

project =angr.Project("E:\\LAB\\angr\\angr\program\\11_angr_sim_scanf")

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)#4字节,32比特
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("The ANSWER is :{}".format(solution0))
print("The ANSWER is :{}".format(solution1))
else:
print("No Answer!")

12_angr_veritesting

image-20220312170347821

输入数据,经过祖传complex函数变化,然后作比较。

其实这个for循环和if语句加起来相当于一个检查函数,而且会执行2^32次,路径爆炸是必须的。

这题除了避免路径爆炸外,其余的跟刚开始几道题差不多。

放脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import angr
import claripy
from Crypto.Util.number import long_to_bytes
import sys

project =angr.Project("E:\\LAB\\angr\\angr\program\\12_angr_veritesting")

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()))
else:
print("No Answer!")

本题主要是利用了project.factory.simgr() 函数提供的veritesting 参数来指定是否自动合并路径。

贴上大佬的简单原理解释

image-20220312172250586

13_angr_static_binary

image-20220312172829883

伪代码如上。也是祖传complex函数变化。

乍一看,跟第一题还很像。但是用第一题的脚本跑半天出不来。而且还报错。

点进printf,scanf这类函数可以发现,呈现的是函数的源代码,不再是之前的跳转命令。

这个程序是静态链接编译的,故才会包含printf,scanf这类libc函数的实现。

因此我们需要hook掉这些实现的libc函数,避免angr在这些libc函数里东跑西跑迷失。

本题的libc函数有printf,scanf,puts,还有一个glibc函数__libc_start_main

hook掉之后,脚本就跟第一题的一样了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import angr
import claripy
from Crypto.Util.number import long_to_bytes
import sys

project =angr.Project("E:\\LAB\\angr\\angr\program\\13_angr_static_binary")

initial_state = project.factory.entry_state()

simulation = project.factory.simgr(initial_state,veritesting = True)

project.hook(0x804ed40, angr.SIM_PROCEDURES['libc']['printf']()) # 获取Angr 内部实现的系统函数
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)

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]
print(solution_state.posix.dumps(sys.stdin.fileno()))
else:
print("No Answer!")

14_angr_shared_library

image-20220312180011638

…….

这题还有一个对应的.so文件,这就需要用angr来动态链接了。

反编译的时候会报错,寄。

image-20220312180837996

原因摘自其他大神博客

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

1
2
3
4
5
  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])

回到函数本身,这个validate是验证函数哈,但是不在v这个程序里,在.so文件动态链接着

所以我们用IDA打开.so文件,查看validate代码。

image-20220312181710748

…祖传complex,没什么好说的。

这题考点在于这个动态链接。我们对这个.so文件进行符号执行。(加载文件时加载.so文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from msilib.schema import Binary
import angr
import claripy
from Crypto.Util.number import long_to_bytes
import sys



base = 0x400000 # base 基址是随意定的,可以随意修改
project = angr.Project("E:\\LAB\\angr\\angr\program\\lib14_angr_shared_library.so", load_options={
'main_opts' : {
'custom_base_addr' : base
}
})

buf_pointer = claripy.BVV(0x03000000, 32) # 变量地址
validate_addr = base+0x6d7 # 函数地址
# validate(char* buffer, int length)
init_state = project.factory.call_state(validate_addr, buf_pointer, claripy.BVV(8, 32))

flag = claripy.BVS("flag", 8*8) # 要求解的输入
init_state.memory.store(buf_pointer, flag)

good = base+0x783 # validate返回地址
simu = project.factory.simgr(init_state)
simu.explore(find=good)

if simu.found:
solu_state = simu.found[0]
# 限制返回时的寄存器值不为0才是正确结果
solu_state.add_constraints(solu_state.regs.eax != 0)
flag = solu_state.solver.eval(flag, cast_to=bytes)
print(flag)
else:
print("No result!")

15_angr_arbitrary_read

image-20220312183629337

接下来重点看汇编代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
.text:080484C9 main            proc near               ; DATA XREF: _start+17↑o
.text:080484C9
.text:080484C9 var_1C = byte ptr -1Ch
.text:080484C9 s = 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+s], eax
.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+var_1C]
.text:080484F8 push eax
.text:080484F9 push offset key
.text:080484FE push offset aU20s ; "%u %20s"
.text:08048503 call ___isoc99_scanf
.text:08048508 add esp, 10h
.text:0804850B mov eax, ds:key
.text:08048510 cmp eax, 228BF7Eh
.text:08048515 jz short loc_8048531
.text:08048517 cmp eax, 3AD516Ah
.text:0804851C jnz short loc_8048542
.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+s]
.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
.text:08048554 mov eax, 0
.text:08048559 mov ecx, [ebp+var_4]
.text:0804855C leave
.text:0804855D lea esp, [ecx-4]
.text:08048560 retn
.text:08048560 ; } // starts at 80484C9
.text:08048560 main endp

输入的两个参数,key,v4,分别对应汇编代码中的s(var_c),var_1c,由此可见,两个参数相距0x10(16个字节大小,其实也就是说,var_1c(v4)是16个字节大小。)而输入会输入20个字节。必定会将key变量覆盖部分甚至全部。(其实%u就是占四个字节,这里key就刚好全部被覆盖了)

再会看程序逻辑,将key与0x228BF7E比较,相等则不输出tryagain,至此我们找到了输出s的方法

我们使用hook,钩取scanf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
from msilib.schema import Binary
import angr
import claripy
from Crypto.Util.number import long_to_bytes
import sys



binary_path = "E:\\LAB\\angr\\angr\program\\15_angr_arbitrary_read"
pro = angr.Project(binary_path)

init_state = pro.factory.entry_state()

# replace scanf
class ReplaceScanf(angr.SimProcedure):

def run(self, format_str, param0, param1):
scanf0 = claripy.BVS("0", 32)
scanf1 = claripy.BVS("1", 20*8)
# 限制单个字符的求解范围
for ch in scanf1.chop(bits=8):
self.state.add_constraints(ch >= "A", ch <= "Z")

self.state.memory.store(param0, scanf0, endness=pro.arch.memory_endness)
self.state.memory.store(param1, scanf1)
self.state.globals["solutions"] = (scanf0, scanf1)

scanf_sym = "__isoc99_scanf"
pro.hook_symbol(scanf_sym, ReplaceScanf())

# 通过检查puts的输出来确定是否是想要的结果
def check_puts(state):
puts_para = state.memory.load(state.regs.esp+4, 4, endness=pro.arch.memory_endness)
if state.solver.symbolic(puts_para):
good_str = 0x4D52584B
copy_state = state.copy()
copy_state.add_constraints(puts_para == good_str)
if copy_state.satisfiable():
# 先通过拷贝的状态判断是否满足,然后再直接在原状态增加限制
state.add_constraints(puts_para == good_str) # 如果有解的话就保存到我们执行的那个状态对象
return True
else:
return False
else:
return False

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

simu = pro.factory.simgr(init_state)
simu.explore(find=success)

if simu.found:
solu_state = simu.found[0]
(scanf0, scanf1) = solu_state.globals["solutions"]
flag = str(solu_state.solver.eval(scanf0)).encode("utf-8")
flag += b" " + solu_state.solver.eval(scanf1, cast_to=bytes)
print(flag)
else:
print("No result!")

16_angr_arbitrary_write

image-20220312190749217

汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
.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(s) = byte ptr -1Ch
.text:08048569 target_buffer(dest) = 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

这题,s和key的关系跟上题v4和key的关系一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
from msilib.schema import Binary
import angr
import claripy
from Crypto.Util.number import long_to_bytes
import sys



binary_path = "E:\\LAB\\angr\\angr\program\\16_angr_arbitrary_write"
project= angr.Project(binary_path)

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)
else:
print("No result!")

17_angr_arbitrary_jump

image-20220312192050484

image-20220312192112152

这么简单肯定出事儿,没有Goodjob。

汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
.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 ;
.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

good job

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.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
.text:4D525864 ; } // starts at 4D525849
.text:4D525864 print_good endp

reaad_input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
read_input      proc near               ; CODE XREF: main+28↓p
.text:4D525869
.text:4D525869 var_2B = byte ptr -2Bh
.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+var_2B]
.text:4D525875 push eax
.text:4D525876 push offset format ; "%s"
.text:4D52587B call ___isoc99_scanf
.text:4D525880 add esp, 10h
.text:4D525883 nop
.text:4D525884 leave
.text:4D525885 retn
.text:4D525885 ; } // starts at 4D525869
.text:4D525885 read_input endp

这道题和上面两道题其实是有相似之处的,输入的长度过长会造成溢出,会覆盖某些东西。

这个函数是当输入足够长时造成的栈溢出会覆盖retn的命令。

其实思路也已经出来了

我们hook输入函数,将输入的参数字节扩大,让它不覆盖retn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
from msilib.schema import Binary
import angr
import claripy
from Crypto.Util.number import long_to_bytes
import sys



binary_path = "E:\\LAB\\angr\\angr\program\\17_angr_arbitrary_jump"
project= angr.Project(binary_path)

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)

总结

丫的,终于写完了

感觉这个实验写完,能入个门,更多的还是得多多琢磨官方文档。