0%

angr的练习与提升

00_angr_find

了解 angr 基础方法后便可轻松求解:

1
2
3
4
5
6
7
8
9
10
import angr

p = angr.Project("./00_angr_find")
init_state = p.factory.entry_state()
sm = p.factory.simulation_manager(init_state)
sm.explore(find=0x08048678)
print("find: "+hex(len(sm.found)))
found_state = sm.found[0]
print(found_state.posix.dumps(1))
print(found_state.posix.dumps(0))

01_angr_avoid

main 函数过大,IDA 分析失败,传统逆向受阻,但可以用 angr 求解:

不过这个脚本分析的时间有点长,添加 avoid 可以进行加速:(告诉 angr 哪些地址不需要到达)

1
2
3
4
void avoid_me()
{
should_succeed = 0;
}
  • 逆向分析题目时,发现题目已经给出了提示,在 avoid 处写入此地址即可
1
2
3
4
5
6
7
int __cdecl maybe_good(char *s1, char *s2)
{
if ( should_succeed && !strncmp(s1, s2, 8u) )
return puts("Good Job.");
else
return puts("Try again.");
}
  • 同时,我们还可以把 Try again 处的地址写入 avoid
1
2
3
4
5
6
7
8
9
10
11
import angr

p = angr.Project("./01_angr_avoid")
init_state = p.factory.entry_state()
sm = p.factory.simulation_manager(init_state)
sm.explore(find=0x080485E0,avoid=[0x080485A8,0x080485F2])
print("find: "+hex(len(sm.found)))
print("avoid: "+hex(len(sm.avoid)))
found_state = sm.found[0]
print(found_state.posix.dumps(1))
print(found_state.posix.dumps(0))

02_angr_find_condition

这个题目与之前的有所不同,如果按照之前的思路来写脚本是跑不出答案的

用 IDA 分析一下就知道原因了:

  • 程序把 Good JobTry again 分为很多份,每个执行流分支都有一个,并且地址不同
  • 在这些 Good Job 中,肯定有一个是可以到达的,但是我们不确定是哪个,因此也不能确定要查找的地址

于是我们改变 angr 的写法,使其查找输出 Good Job 的那一个路径分支:(用同样的办法可以避免输出 Try again 的那些路径分支)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import angr

p = angr.Project("./02_angr_find_condition")
init_state = p.factory.entry_state()
sm = p.factory.simulation_manager(init_state)

def is_good(state):
return b"Good Job" in state.posix.dumps(1)

def is_bad(state):
return b"Try again" in state.posix.dumps(1)

sm.explore(find=is_good,avoid=is_bad)
print("find: "+hex(len(sm.found)))
print("avoid: "+hex(len(sm.avoid)))
found_state = sm.found[0]
print(found_state.posix.dumps(1))
print(found_state.posix.dumps(0))

03_angr_symbolic_registers

本题目有多个输入:

1
2
3
4
5
6
7
8
9
10
int get_user_input()
{
int v1; // [esp+0h] [ebp-18h] BYREF
int v2; // [esp+4h] [ebp-14h] BYREF
int v3[4]; // [esp+8h] [ebp-10h] BYREF

v3[1] = __readgsdword(0x14u);
__isoc99_scanf("%x %x %x", &v1, &v2, v3);
return v1;
}
  • 老版本的 angr 不能很好地处理多输入,因此需要用一些复杂的方法来解决问题(新版本的 angr 可以直接解出答案)

根据程序逻辑,输入的3个字符串的地址分别存于 eax ebx edx

1
2
3
4
5
6
7
8
9
.text:0804892A push    offset aXXX     ; "%x %x %x"
.text:0804892F call ___isoc99_scanf
.text:08048934 add esp, 10h
.text:08048937 mov ecx, [ebp+var_18]
.text:0804893A mov eax, ecx
.text:0804893C mov ecx, [ebp+var_14]
.text:0804893F mov ebx, ecx
.text:08048941 mov ecx, [ebp+var_10]
.text:08048944 mov edx, ecx

在 main 中把 eax ebx edx 放回栈,然后调用对应的函数进行加密

1
2
3
4
.text:0804897B call    get_user_input
.text:08048980 mov [ebp+var_14], eax
.text:08048983 mov [ebp+var_10], ebx
.text:08048986 mov [ebp+var_C], edx

既然 angr 不支持多输入,我们就可以直接把 get_user_input 跳过(从 0x08048980 开始执行程序),然后把符号向量放入 eax ebx edx

  • claripy 模块可以手动创建符号向量
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
import angr
import claripy

p = angr.Project("./03_angr_symbolic_registers")
init_state = p.factory.blank_state(addr=0x08048980)

password1 = claripy.BVS("password1",32)
password2 = claripy.BVS("password2",32)
password3 = claripy.BVS("password3",32)

init_state.regs.eax=password1
init_state.regs.ebx=password2
init_state.regs.edx=password3

sm = p.factory.simulation_manager(init_state)

def is_good(state):
return b"Good Job" in state.posix.dumps(1)

def is_bad(state):
return b"Try again" in state.posix.dumps(1)

sm.explore(find=is_good,avoid=is_bad)
print("find: "+hex(len(sm.found)))
print("avoid: "+hex(len(sm.avoid)))
found_state = sm.found[0]
answer1 = found_state.solver.eval(password1)
answer2 = found_state.solver.eval(password2)
answer3 = found_state.solver.eval(password3)
print("{:x} {:x} {:x}".format(answer1,answer2,answer3))

04_angr_symbolic_stack

本题目有多个输入:

1
2
3
4
5
6
7
8
9
10
11
12
13
int handle_user()
{
int v1; // [esp+8h] [ebp-10h] BYREF
int v2[3]; // [esp+Ch] [ebp-Ch] BYREF

__isoc99_scanf("%u %u", v2, &v1);
v2[0] = complex_function0(v2[0]);
v1 = complex_function1(v1);
if ( v2[0] == 0x773024D1 && v1 == 0xBC4311CF )
return puts("Good Job.");
else
return puts("Try again.");
}

这次输入的数据存放在栈中,因此需要将符号向量存放入栈中

这时我们要模拟栈帧构建的过程,把符号向量 push 到正确的位置上

1
2
3
4
5
6
7
8
9
10
.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] /* target1 */
.text:08048685 push eax
.text:08048686 lea eax, [ebp+var_C] /* target2 */
.text:08048689 push eax
.text:0804868A push offset aUU ; "%u %u"
.text:0804868F call ___isoc99_scanf
  • 目标就是把 target1target2 处的局部变量给替换为符号向量

先调试程序,分析输入执行完毕后的栈帧:

1
2
3
4
5
6
7
8
9
10
11
00:0000│ esp 0xffc983c0 —▸ 0x80487b3 ◂— 0x25207525 /* '%u %u' */
01:00040xffc983c4 —▸ 0xffc983dc ◂— 0x3d /* '=' */
02:00080xffc983c8 —▸ 0xffc983d8 ◂— 0x3d /* '=' */
03:000c│ 0xffc983cc ◂— 0x0
04:00100xffc983d0 —▸ 0xf7f24088 (environ) —▸ 0xffc984ac —▸ 0xffc9a15b ◂— 'HTTP_PROXY=http://127.0.0.1:7890/'
05:00140xffc983d4 —▸ 0xf7f75990 ◂— 0x0
06:0018│ edx 0xffc983d8 ◂— 0x3d /* target1 */
07:001c│ 0xffc983dc ◂— 0x3d /* target2 */
08:00200xffc983e0 —▸ 0x80487ce ◂— 0x65746e45 /* 'Enter the password: ' */
09:00240xffc983e4 —▸ 0xffc984a4 —▸ 0xffc9a12b ◂— '/home/yhellow/tools/angr/04_angr_symbolic_stack'
0a:0028│ ebp 0xffc983e8 —▸ 0xffc983f8 ◂— 0x0

我们只需要关注 target1 target2 的位置,把符号向量 push 到这里即可

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

p = angr.Project("./04_angr_symbolic_stack")
init_state = p.factory.blank_state(addr=0x08048694)

password1 = claripy.BVS("password1",32)
password2 = claripy.BVS("password2",32)

init_state.stack_push(init_state.regs.ebp)
init_state.regs.ebp = init_state.regs.esp
init_state.regs.esp -= 0x8
init_state.stack_push(password1)
init_state.stack_push(password2)
init_state.regs.esp -= 0x18

sm = p.factory.simulation_manager(init_state)

def is_good(state):
return b"Good Job" in state.posix.dumps(1)

def is_bad(state):
return b"Try again" in state.posix.dumps(1)

sm.explore(find=is_good,avoid=is_bad)
print("find: "+hex(len(sm.found)))
print("avoid: "+hex(len(sm.avoid)))
found_state = sm.found[0]
answer1 = found_state.solver.eval(password1)
answer2 = found_state.solver.eval(password2)
print("{:d} {:d}".format(answer1,answer2))

05_angr_symbolic_memory

本题目有多个输入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [esp+Ch] [ebp-Ch]

memset(user_input, 0, 0x21u);
printf("Enter the password: ");
__isoc99_scanf("%8s %8s %8s %8s", user_input, &unk_A1BA1C8, &unk_A1BA1D0, &unk_A1BA1D8);
for ( i = 0; i <= 31; ++i )
*(_BYTE *)(i + 0xA1BA1C0) = complex_function(*(char *)(i + 0xA1BA1C0), i);
if ( !strncmp(user_input, "NJPURZPCDYEAXCSJZJMPSOMBFDDLHBVN", 0x20u) )
puts("Good Job.");
else
puts("Try again.");
return 0;
}

这次输入的数据存放在全局变量中,直接在目标地址上写入符号向量即可(注意顺序)

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
import angr
import claripy

p = angr.Project("./05_angr_symbolic_memory")
init_state = p.factory.blank_state(addr=0x08048601)

password1 = claripy.BVS("password1",64)
password2 = claripy.BVS("password2",64)
password3 = claripy.BVS("password3",64)
password4 = claripy.BVS("password4",64)

password1_addr = 0x0A1BA1C0
password2_addr = 0x0A1BA1C8
password3_addr = 0x0A1BA1D0
password4_addr = 0x0A1BA1D8

init_state.memory.store(password1_addr,password1)
init_state.memory.store(password2_addr,password2)
init_state.memory.store(password3_addr,password3)
init_state.memory.store(password4_addr,password4)

sm = p.factory.simulation_manager(init_state)

def is_good(state):
return b"Good Job" in state.posix.dumps(1)

def is_bad(state):
return b"Try again" in state.posix.dumps(1)

sm.explore(find=is_good,avoid=is_bad)
print("find: "+hex(len(sm.found)))
print("avoid: "+hex(len(sm.avoid)))
found_state = sm.found[0]
answer1 = found_state.solver.eval(password1,cast_to=bytes).decode()
answer2 = found_state.solver.eval(password2,cast_to=bytes).decode()
answer3 = found_state.solver.eval(password3,cast_to=bytes).decode()
answer4 = found_state.solver.eval(password4,cast_to=bytes).decode()
print("{} {} {} {}".format(answer1,answer2,answer3,answer4))

06_angr_symbolic_dynamic_memory

1
2
3
4
5
6
buffer0 = (char *)malloc(9u);
buffer1 = (char *)malloc(9u);
memset(buffer0, 0, 9u);
memset(buffer1, 0, 9u);
printf("Enter the password: ");
__isoc99_scanf((int)"%8s %8s", (int)buffer0, (int)buffer1);
  • 这次是把数据输入到堆中

由于我们直接跳过了输入,导致 malloc 并没有执行,此时堆应该是没有初始化的

因此我们直接在栈上伪造一个堆空间,然后在 buffer0 buffer1 中写入我们伪造的地址:

  • 注意:angr 默认使用大端写,但写地址时需要指定 endness=p.arch.memory_endness 切换为小端写
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
import angr
import claripy

p = angr.Project("./06_angr_symbolic_dynamic_memory")
init_state = p.factory.blank_state(addr=0x08048696)

password1 = claripy.BVS("password1",64)
password2 = claripy.BVS("password2",64)

password1_addr = 0x0ABCC8A4
password2_addr = 0x0ABCC8AC

password1_fakeheap = init_state.regs.esp + 0x100
password2_fakeheap = init_state.regs.esp + 0x200

init_state.memory.store(password1_addr,password1_fakeheap,endness=p.arch.memory_endness)
init_state.memory.store(password2_addr,password2_fakeheap,endness=p.arch.memory_endness)
init_state.memory.store(password1_fakeheap,password1)
init_state.memory.store(password2_fakeheap,password2)

sm = p.factory.simulation_manager(init_state)

def is_good(state):
return b"Good Job" in state.posix.dumps(1)

def is_bad(state):
return b"Try again" in state.posix.dumps(1)

sm.explore(find=is_good,avoid=is_bad)
print("find: "+hex(len(sm.found)))
print("avoid: "+hex(len(sm.avoid)))
found_state = sm.found[0]
answer1 = found_state.solver.eval(password1,cast_to=bytes).decode()
answer2 = found_state.solver.eval(password2,cast_to=bytes).decode()
print("{} {}".format(answer1,answer2))

07_angr_symbolic_file

1
2
3
4
5
6
7
__isoc99_scanf("%64s", buffer);
ignore_me(buffer, 0x40u);
memset(buffer, 0, sizeof(buffer));
fp = fopen("OJKSQYDP.txt", "rb");
fread(buffer, 1u, 0x40u, fp);
fclose(fp);
unlink("OJKSQYDP.txt");
  • 程序先把输入的数据传输到文件中,然后打开文件从里面取出数据

这次的目标就是符号化一个文件,使用 init_state.fs.insert(file_name,SimFile) 可以把一个指定文件名的文件,设置为符号化文件

我们需要跳过输入,在 open 之前开始执行程序:

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
import angr
import claripy

p = angr.Project("./07_angr_symbolic_file")
init_state = p.factory.blank_state(addr=0x080488D3)

file_name = "OJKSQYDP.txt"
file_size = 0x40

password1 = claripy.BVS("password1",file_size)
SimFile = angr.storage.SimFile(file_name,content=password1,size=file_size)
init_state.fs.insert(file_name,SimFile)

sm = p.factory.simulation_manager(init_state)

def is_good(state):
return b"Good Job" in state.posix.dumps(1)

def is_bad(state):
return b"Try again" in state.posix.dumps(1)

sm.explore(find=is_good,avoid=is_bad)

print("find: "+hex(len(sm.found)))
print("avoid: "+hex(len(sm.avoid)))
found_state = sm.found[0]
answer1 = found_state.solver.eval(password1,cast_to=bytes).decode()
print("{}".format(answer1))

08_angr_constraints

本题目使用单字节对比:

1
2
3
4
5
6
7
8
9
10
11
12
13
_BOOL4 __cdecl check_equals_AUPDNNPROEZRJWKB(int a1, unsigned int a2)
{
int v3; // [esp+8h] [ebp-8h]
unsigned int i; // [esp+Ch] [ebp-4h]

v3 = 0;
for ( i = 0; i < a2; ++i )
{
if ( *(_BYTE *)(i + a1) == *(_BYTE *)(i + 0x804A040) )
++v3;
}
return v3 == a2;
}

angr 有一个名为“路径爆炸”的问题,这种单字节对比的函数会导致路径特别多,大大降低 angr 的运行速度,于是我们需要一种方法来减缓“路径爆炸”的影响

常见的做法就是把“对比函数”提取出来,手动为该函数添加 check_constraint(通过条件),然后再用约束器求解:

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
import angr
import claripy

p = angr.Project("./08_angr_constraints")
init_state = p.factory.blank_state(addr=0x08048622)

password = claripy.BVS("password",0x10*8)
password_addr = 0x0804A050

init_state.memory.store(password_addr,password)

check_addr = 0x08048565 # 08048673
check_key = "AUPDNNPROEZRJWKB"

sm = p.factory.simulation_manager(init_state)
sm.explore(find=check_addr)

if sm.found:
check_state = sm.found[0]
check_param1 = password_addr
check_param2 = 0x10

check_bvs = check_state.memory.load(check_param1,check_param2)
check_constraint = check_key == check_bvs
check_state.add_constraints(check_constraint)

answer = check_state.solver.eval(password,cast_to=bytes)
print("{}".format(answer.decode()))

09_angr_hooks

1
2
3
4
5
6
7
8
printf("Enter the password: ");
__isoc99_scanf("%16s", buffer);
for ( i = 0; i <= 15; ++i )
*(_BYTE *)(i + 0x804A054) = complex_function(*(char *)(i + 0x804A054), 18 - i);
equals = check_equals_XYMKBKUHNIQYNQXE((int)buffer, 0x10u);
for ( j = 0; j <= 15; ++j )
*(_BYTE *)(j + 0x804A044) = complex_function(*(char *)(j + 0x804A044), j + 9);
__isoc99_scanf("%16s", buffer);

本题目的执行路径比较混乱,如果用之前的方法来处理“路径爆炸”就会导致代码冗余(查找到目标“比较函数”后,需要判断该“比较函数”是否通过,然后再查找 Good Job

angr 还提供了另一种方法来应对“路径爆炸”:

  • 直接 hook 掉“比较函数”,利用自己实现的函数来替代它
  • 然后根据比较结果来操控 eax 寄存器
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
import angr
import claripy

p = angr.Project("./09_angr_hooks")
init_state = p.factory.entry_state()

password_addr = 0x0804A054

check_addr = 0x080486B3
check_skip = 4 + 1
check_key = "XYMKBKUHNIQYNQXE"

@p.hook(check_addr,length=check_skip)
def check_hook(state):
check_param1 = password_addr
check_param2 = 0x10
input_bvs = state.memory.load(check_param1,check_param2)

state.regs.eax = claripy.If(
check_key == input_bvs,
claripy.BVV(1,32),
claripy.BVV(0,32)
)

sm = p.factory.simulation_manager(init_state)

def is_good(state):
return b"Good Job" in state.posix.dumps(1)

def is_bad(state):
return b"Try again" in state.posix.dumps(1)

sm.explore(find=is_good,avoid=is_bad)

print("find: "+hex(len(sm.found)))
print("avoid: "+hex(len(sm.avoid)))
found_state = sm.found[0]
print(found_state.posix.dumps(0))

10_angr_simprocedures

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [esp+20h] [ebp-28h]
char s[17]; // [esp+2Bh] [ebp-1Dh] BYREF
unsigned int v6; // [esp+3Ch] [ebp-Ch]

v6 = __readgsdword(0x14u);
memcpy(&password, "ORSDDWXHZURJRBDH", 0x10u);
memset(s, 0, sizeof(s));
printf("Enter the password: ");
__isoc99_scanf("%16s", s);
for ( i = 0; i <= 15; ++i )
s[i] = complex_function(s[i], 18 - i);
if ( check_equals_ORSDDWXHZURJRBDH((int)s, 0x10u) )
puts("Good Job.");
else
puts("Try again.");
return 0;
}

这个题目看似可以用上一个方法来做,但稍微用 IDA 分析一下就会发现问题:

  • 程序为每个路径分支都提供了一个“比较函数”,我们根本不知道该 hook 掉哪一个

因此我们需要根据函数名来进行 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
import angr
import claripy

p = angr.Project("./10_angr_simprocedures")
init_state = p.factory.entry_state()

password_addr = 0x0804C048

class mySimPro(angr.SimProcedure):
def run(self,input_addr,input_length):
check_key = "ORSDDWXHZURJRBDH"
input_bvs = self.state.memory.load(input_addr,input_length)

return claripy.If(
check_key == input_bvs,
claripy.BVV(1,32),
claripy.BVV(0,32)
)

check_name = "check_equals_ORSDDWXHZURJRBDH"
p.hook_symbol(check_name,mySimPro())

sm = p.factory.simulation_manager(init_state)

def is_good(state):
return b"Good Job" in state.posix.dumps(1)

def is_bad(state):
return b"Try again" in state.posix.dumps(1)

sm.explore(find=is_good,avoid=is_bad)

print("find: "+hex(len(sm.found)))
print("avoid: "+hex(len(sm.avoid)))
found_state = sm.found[0]
print(found_state.posix.dumps(0))

11_angr_sim_scanf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [esp+20h] [ebp-28h]
char s[20]; // [esp+28h] [ebp-20h] BYREF
unsigned int v7; // [esp+3Ch] [ebp-Ch]

v7 = __readgsdword(0x14u);
memset(s, 0, sizeof(s));
qmemcpy(s, "SUQMKQFX", 8);
for ( i = 0; i <= 7; ++i )
s[i] = complex_function(s[i], i);
printf("Enter the password: ");
__isoc99_scanf("%u %u", buffer0, buffer1);
if ( !strncmp(buffer0, s, 4u) && !strncmp(buffer1, &s[4], 4u) )
puts("Good Job.");
else
puts("Try again.");
return 0;
}
  • 这个题目在输入的前面完成了加密
  • 并且让我们无法确定开始执行的地址

题目原本考点是:将 scanf 替换为我们自己的版本(老版本 angr 不支持使用 scanf 请求多个参数)

现在新版本的 angr 已经支持多个参数的 scanf 了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import angr

p = angr.Project("./11_angr_sim_scanf")
init_state = p.factory.entry_state()
sm = p.factory.simulation_manager(init_state)

def is_good(state):
return b"Good Job" in state.posix.dumps(1)

def is_bad(state):
return b"Try again" in state.posix.dumps(1)

sm.explore(find=is_good,avoid=is_bad)
print("find: "+hex(len(sm.found)))
print("avoid: "+hex(len(sm.avoid)))
found_state = sm.found[0]
print(found_state.posix.dumps(0))

12_angr_veritesting

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__isoc99_scanf(
(int)"%32s",
(int)v19 + 3,
v5,
v6,
v7,
v8,
v9,
(int)v10,
v11,
v12,
v13,
v14,
v16,
v18,
v19[0],
v19[1],
v19[2],
v19[3],
v19[4],
v19[5]);

一样的是针对 scanf 的问题

由于变量过长,需要在构造仿真管理器时添加 veritesting=True 选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import angr

p = angr.Project("./12_angr_veritesting")
init_state = p.factory.entry_state()
sm = p.factory.simulation_manager(init_state,veritesting=True)

def is_good(state):
return b"Good Job" in state.posix.dumps(1)

def is_bad(state):
return b"Try again" in state.posix.dumps(1)

sm.explore(find=is_good,avoid=is_bad)
print("find: "+hex(len(sm.found)))
print("avoid: "+hex(len(sm.avoid)))
found_state = sm.found[0]
print(found_state.posix.dumps(0))

13_angr_static_binary

这是一个静态的程序:

1
2
➜  angr file 13_angr_static_binary 
13_angr_static_binary: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=89d11f111deddc580fac3d22a1f6c352d1883cd5, not stripped

angr 在处理静态程序时,会进入到 libc 函数的内部进行分析,有些 libc 函数内部的执行分支很多,路径很长,会浪费大量的时间

我们需要做的工作就是用 hook 函数来替代原本的 libc 函数:

  • 当执行二进制文件时,main 函数不是调用的第一段代码,程序会首先调用在 _start 函数中的 __libc_start_main 以启动程序,此函数中发生的初始化
  • 使用 angr 可能需要很长时间,所以你应该用 SimProcedure 替换它
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
import angr

p = angr.Project("./13_angr_static_binary")
init_state = p.factory.entry_state()

printf_addr = 0x0804ED40
puts_addr = 0x0804F350
strcmp_addr = 0x08048280
scanf_addr = 0x0804ED80
exit_addr = 0x0804E3D0
__libc_start_main = 0x08048D10

p.hook(printf_addr, angr.SIM_PROCEDURES['libc']['printf']())
p.hook(puts_addr, angr.SIM_PROCEDURES['libc']['puts']())
p.hook(strcmp_addr, angr.SIM_PROCEDURES['libc']['strcmp']())
p.hook(scanf_addr, angr.SIM_PROCEDURES['libc']['scanf']())
p.hook(exit_addr, angr.SIM_PROCEDURES['libc']['exit']())
p.hook(__libc_start_main, angr.SIM_PROCEDURES['glibc']['__libc_start_main']())

sm = p.factory.simulation_manager(init_state)

def is_good(state):
return b"Good Job" in state.posix.dumps(1)

def is_bad(state):
return b"Try again" in state.posix.dumps(1)

sm.explore(find=is_good,avoid=is_bad)

print("find: "+hex(len(sm.found)))
print("avoid: "+hex(len(sm.avoid)))
found_state = sm.found[0]
print(found_state.posix.dumps(0))

14_angr_shared_library

1
2
3
4
5
6
7
8
memset(s, 0, sizeof(s));
printf("Enter the password: ");
__isoc99_scanf("%8s", s);
if ( validate((int)s, 8) )
puts("Good Job.");
else
puts("Try again.");
return 0;
1
2
3
4
int __cdecl validate(int a1, int a2)
{
return validate(a1, a2);
}
  • 这个 validate 是动态链接库中的函数
  • 执行二进制文件前,需要先将对应的动态链接库添加到 /lib 目录中
1
sudo cp -r lib14_angr_shared_library.so /lib

本题目有一个特点,就是是否输出 Good Job 完全由动态链接库决定,于是我们只需要测试动态链接库

angr 拥有分析动态链接库的能力,但需要在创建项目时指定程序的基地址(随便写一个都行),然后利用 p.factory.call_state 去调用 validate(不需要检查返回,只要程序执行到“通过”时该执行的地址就可以就说明输出 Good Job 了)

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 claripy

base_addr = 0x8000000
p = angr.Project("./lib14_angr_shared_library.so",load_options={
'main_opts' : {
'custom_base_addr' : base_addr
}
})

password_addr = claripy.BVV(0x3000000,32)
password = claripy.BVS("password",8*8)
validate_addr = base_addr+0x6D7

init_state = p.factory.call_state(validate_addr, password_addr, claripy.BVV(8, 32))
init_state.memory.store(password_addr, password)

sm = p.factory.simulation_manager(init_state)
sm.explore(find=0x783+base_addr)

print("find: "+hex(len(sm.found)))
print("avoid: "+hex(len(sm.avoid)))
found_state = sm.found[0]
answer = found_state.solver.eval(password,cast_to=bytes)
print("{}".format(answer.decode()))

15_angr_arbitrary_read

这个题目有一点不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[16]; // [esp+Ch] [ebp-1Ch] BYREF
char *s; // [esp+1Ch] [ebp-Ch]

s = try_again;
printf("Enter the password: ");
__isoc99_scanf("%u %20s", &key, v4);
if ( key == 0x27DFB7C )
puts(s);
else
puts(try_again);
return 0;
}
  • 输入有4字节的溢出,刚好可以覆盖 s(任意地址读)

除了绕过对应的密码检查,还需要调用 memory.load 方法将 puts 的第一个参数提取出来,与 Good Job 字符串所在的地址进行对比

  • 注意:本题目还需要把 scanf 给 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
49
50
import angr
import claripy

p = angr.Project("./15_angr_arbitrary_read")
init_state = p.factory.entry_state()
sm = p.factory.simulation_manager(init_state)

class mySimPro(angr.SimProcedure):
format_addr = 0x484F4A69
def run(self, format_addr, param0, param1):
password1 = claripy.BVS('password1', 32)
password2 = claripy.BVS('password2', 8*20)
for char in password2.chop(bits=8):
self.state.add_constraints(char >= 'A', char <= 'Z')

self.state.memory.store(param0, password1, endness=p.arch.memory_endness)
self.state.memory.store(param1, password2)
self.state.globals['password'] = (password1, password2)

scanf_symbol = '__isoc99_scanf'
p.hook_symbol(scanf_symbol, mySimPro())

def check_puts(state):
check_param = state.memory.load(state.regs.esp+4,4,endness=p.arch.memory_endness)
if state.se.symbolic(check_param):
goodjob_addr = 0x484F4A47
check_constraint = check_param == goodjob_addr
state.add_constraints(check_constraint)
if state.satisfiable():
return True
else:
return False
else:
return False

def is_good(state):
puts_address = 0x8048370
if state.addr == puts_address:
return check_puts(state)
else:
return False

sm.explore(find=is_good)
print("find: "+hex(len(sm.found)))
print("avoid: "+hex(len(sm.avoid)))
found_state = sm.found[0]
(password1, password2) = found_state.globals['password']
answer1 = (found_state.solver.eval(password1))
answer2 = (found_state.solver.eval(password2,cast_to=bytes))
print("{} {}".format(answer1, answer2))

16_angr_arbitrary_write

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[16]; // [esp+Ch] [ebp-1Ch] BYREF
char *dest; // [esp+1Ch] [ebp-Ch]

dest = unimportant_buffer;
memset(s, 0, sizeof(s));
strncpy(password_buffer, "PASSWORD", 0xCu);
printf("Enter the password: ");
__isoc99_scanf("%u %20s", &key, s);
if ( key == 0xB11403 )
strncpy(dest, s, 0x10u);
else
strncpy(unimportant_buffer, s, 0x10u);
if ( !strncmp(password_buffer, "NDYNWEUJ", 8u) )
puts("Good Job.");
else
puts("Try again.");
return 0;
}

和上一个题一样,只不过漏洞换成了任意写

在上一个题目的基础上进行修改,再添加一些检查就好了

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
import angr
import claripy

p = angr.Project("./16_angr_arbitrary_write")
init_state = p.factory.entry_state()
sm = p.factory.simulation_manager(init_state)

class mySimPro(angr.SimProcedure):
format_addr = 0x08048721
def run(self, format_addr, param0, param1):
password1 = claripy.BVS('password1', 32)
password2 = claripy.BVS('password2', 8*20)
for char in password2.chop(bits=8):
self.state.add_constraints(char >= 'A', char <= 'Z')

self.state.memory.store(param0, password1, endness=p.arch.memory_endness)
self.state.memory.store(param1, password2)
self.state.globals['password'] = (password1, password2)

scanf_symbol = '__isoc99_scanf'
p.hook_symbol(scanf_symbol, mySimPro())

def check_strncpy(state):
check_param1 = state.memory.load(state.regs.esp+4,4,endness=p.arch.memory_endness)
check_param2 = state.memory.load(state.regs.esp+8,4,endness=p.arch.memory_endness)
check_param3 = state.memory.load(state.regs.esp+12,4,endness=p.arch.memory_endness)
check_bvs = state.memory.load(check_param2, check_param3)

if state.solver.symbolic(check_bvs) and state.solver.symbolic(check_param1):
target_addr = 0x57584344
check_key = "NDYNWEUJ"
check_constraint1 = check_param1 == target_addr
check_constraint2 = check_bvs[-1:-64] == check_key
if state.satisfiable(extra_constraints=(check_constraint1,check_constraint2)):
state.add_constraints(check_constraint1,check_constraint2)
return True
else:
return False
else:
return False

def is_good(state):
strncpy_address = 0x08048410
if state.addr == strncpy_address:
return check_strncpy(state)
else:
return False

sm.explore(find=is_good)
print("find: "+hex(len(sm.found)))
print("avoid: "+hex(len(sm.avoid)))
found_state = sm.found[0]
(password1, password2) = found_state.globals['password']
answer1 = (found_state.solver.eval(password1))
answer2 = (found_state.solver.eval(password2,cast_to=bytes))
print("{} {}".format(answer1, answer2))

17_angr_arbitrary_jump

1
2
3
4
5
6
7
int __cdecl main(int argc, const char **argv, const char **envp)
{
printf("Enter the password: ");
read_input();
puts("Try again.");
return 0;
}
1
2
3
4
5
6
int read_input()
{
char v1[32]; // [esp+28h] [ebp-20h] BYREF

return __isoc99_scanf("%s", v1);
}

这个题开始玩 pwn 了,要我们覆盖返回地址

因为本程序随时可能报错,所以我们要在创建仿真器时指定 save_unconstrained=True(angr 不抛出不受约束的状态)

我们需要的结果是无约束状态(因为 Good Job 根本不在程序的执行路径里),如果出现了约束状态下的解则求解失败,有待检查的状态才继续循环遍历所有的状态,最终的结果是找到了一个未约束状态

最后给这个状态加一个约束,使其等于 Good Job 的地址

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

p = angr.Project("./17_angr_arbitrary_jump")
init_state = p.factory.entry_state()
sm = p.factory.simulation_manager(init_state)

class mySimPro(angr.SimProcedure):
format_addr = 0x42585350
def run(self, format_addr, param0):
password = claripy.BVS('password', 8*64)
for char in password.chop(bits=8):
self.state.add_constraints(char >= 'A', char <= 'Z')

self.state.memory.store(param0, password, endness=p.arch.memory_endness)
self.state.globals['password'] = (password)

scanf_symbol = '__isoc99_scanf'
p.hook_symbol(scanf_symbol, mySimPro())

sm = p.factory.simgr(
init_state,
save_unconstrained=True,
stashes={
'active' : [init_state],
'unconstrained' : [],
'found' : [],
'not_needed' : []
}
)

while(1):
for unconstrained_state in sm.unconstrained:
def should_move(s):
return s is unconstrained_state
sm.move('unconstrained', 'found', filter_func=should_move)
sm.step()
if(sm.found):
print("get it")
break

print("find: "+hex(len(sm.found)))
found_state = sm.found[0]
found_state.add_constraints(found_state.regs.eip == 0x42585249)
password = found_state.globals['password']
answer = (found_state.solver.eval(password,cast_to=bytes))
print(answer[::-1])