angr_ctf

前言

尝试学习angr在混淆下的应用,示例eg.exe源于封装好的现成solutions下

项目:https://github.com/jakespringer/angr_ctf

又是一个re✌:https://github.com/ZERO-A-ONE/AngrCTF_FITM/tree/master

ubuntu配置32位环境:

1
2
3
4
sudo dpkg --add-architecture i386
sudo apt install libc6:i386 libstdc++6:i386
sudo apt-get update
sudo apt install libncurses5-dev lib32z1

去除烦人警告

1
2
import logging
logging.getLogger('angr.storage.memory_mixins.default_filler_mixin').setLevel(logging.CRITICAL)

00_find

1
2
3
4
5
6
7
8
9
10
11
12
13
import angr
project=angr.Project('00_angr_find')
initial_state = project.factory.entry_state( #ZERO_FILL/SYMBOL_FILL 填充
add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
)
simulation = project.factory.simgr(initial_state) #创建simulation manager
simulation.explore(find=0x804868C,avoid=0x804867A)
if simulation.found:
solution_state = simulation.found[0]
print(solution_state.posix.dumps(0).decode())
else:
assert 'fail'

01_avoid

find地址附近即可,avoid改为avoid_me函数地址,绕过减少solver时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import angr
project = angr.Project('./01_angr_avoid')
initial_state = project.factory.entry_state(
add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
)
simgr = project.factory.simulation_manager()

find_address = 0x80485F4
avoid_address = 0x80485BF

simgr.explore(find=find_address, avoid=avoid_address)

if simgr.found:
solution_state = simgr.found[0]
print(solution_state.posix.dumps(0).decode())

#'JLVUSGJZ'

02_def_addr

上一关调用大量avoid_me,此关则是两个对应输出语句被大量调用,采用下述写法处理没有具体地址情况

eg.

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

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

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

project=angr.Project('./02_angr_find_condition')
initial_state=project.factory.entry_state(
add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
)
#simulation 模拟
simulation=project.factory.simgr(initial_state)
simulation.explore(find=success,avoid=failure)

if simulation.found:
state=simulation.found[0]
print(state.posix.dumps(0).decode())
# 0 = stdin(输入)
# 1 = stdout(正常输出)
# 2 = stderr(错误输出)

else:
assert False
# OHYJUMBE

03_register

angr不支持多个scanf输入,scanf调用结束后指定angr注入地址

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
_DWORD v3[4]; // [esp+8h] [ebp-10h] BYREF

v3[1] = __readgsdword(0x14u);
__isoc99_scanf("%x %x %x", &v1, &v2, v3);
return v1;
}

此关新增将符号值注入寄存器

补充:

方法 说明
entry_state() 从程序入口开始
blank_state(addr==…) 从指定地址开始,完全空白
full_init_state() 执行到 main
前的所有初始化(如构造函数),比 entry_state()
更完整
call_state(addr,arg11,arg2,…) 专门用于调用某个函数,自动设置参数和返回地址
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)
{
__int64 user_input; // rax
int v5; // [esp+4h] [ebp-14h]
int v6; // [esp+8h] [ebp-10h]
int v7; // [esp+Ch] [ebp-Ch]
int v8; // [esp+Ch] [ebp-Ch]

printf("Enter the password: ");
user_input = get_user_input();
v7 = HIDWORD(user_input);
v5 = complex_function_1(user_input);
v6 = complex_function_2();
v8 = complex_function_3(v7);
if ( v5 || v6 || v8 )
puts("Try again.");
else
puts("Good Job.");
return 0;
}

wk.

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

project=angr.Project('./03_angr_symbolic_registers')
start_address=0x080488C7
initial_state=project.factory.blank_state(
addr=start_address,
add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
)

hoge0_size=32 #32bit register
hoge1_size=32
hoge2_size=32
hoge0=claripy.BVS('hoge0',hoge0_size)
hoge1=claripy.BVS('hoge1',hoge1_size)
hoge2=claripy.BVS('hoge2',hoge2_size)

initial_state.regs.eax=hoge0
initial_state.regs.ebx=hoge1
initial_state.regs.edx=hoge2

simulation=project.factory.simgr(initial_state)
simulation.explore(find=0x0804892A,avoid=0x08048918)

if simulation.found:
state=simulation.found[0]
a0=state.solver.eval(hoge0)
a1=state.solver.eval(hoge1)
a2=state.solver.eval(hoge2)
a=''.join(map('{:08x}'.format,[a0,a1,a2]))
print(a)
else:
assert False
# e9b37483 7aab5fde 8f5b48ea

04_stack

此关跳过scanf的目的地在一个函数内部,需重新构建一个栈环境,像上一关直接将返回值返回给对应寄存器,栈的影响不重要,此关数据仍在栈中,需要构建模拟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
#一个字节在栈中占一个地址
# /-------- The stack --------\
# ebp -> | padding |
# |---------------------------|
# ebp - 0x01 | more padding |
# |---------------------------|
# ebp - 0x02 | even more padding |
# |---------------------------|
# . . .
# |---------------------------|
# ebp - 0x0b | password0, second byte |
# |---------------------------|
# ebp - 0x0c | password0, first byte |
# |---------------------------|
# ebp - 0x0d | password1, last byte |
# |---------------------------|
# . . .
# |---------------------------|
# ebp - 0x10 | password1, first byte |
# |---------------------------|
# . . .
# |---------------------------|
# esp -> | |
# \---------------------------/
#
# padding_len=8
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


project=angr.Project('./04_angr_symbolic_stack')
#老规矩绕过scanf
start_address=0x080486AE
initial_state=project.factory.blank_state(
addr=start_address,
add_options={
angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS
}
)

initial_state.regs.ebp=initial_state.regs.esp
hoge0=claripy.BVS('hoge0',32)
hoge1=claripy.BVS('hoge1',32)

initial_state.regs.esp-=8
initial_state.stack_push(hoge0)
initial_state.stack_push(hoge1)
simulation=project.factory.simgr(initial_state)

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

def fail(state):
output=state.posix.dumps(1)
return 'Try again.'.encode() in output

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

if simulation.found:
solution=simulation.found[0]
a0=solution.solver.eval(hoge0)
a1=solution.solver.eval(hoge1)
s=' '.join(map(str,[a0,a1]))
print(s)
else:
print('fail')
# 2089710965 12847883

05_memory_store

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_AB232C8, &unk_AB232D0, &unk_AB232D8);
for ( i = 0; i <= 31; ++i )
*(_BYTE *)(i + 0xAB232C0) = complex_function(*(char *)(i + 0xAB232C0), i);
if ( !strncmp(user_input, "OSIWHBXIFOQVSBZBISILSCLBIAXSEWUT", 0x20u) )
puts("Good Job.");
else
puts("Try again.");
return 0;
}

wk.

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

project=angr.Project('./05_angr_symbolic_memory')
start_address=0x08048618
initial_state=project.factory.blank_state(
addr=start_address,
add_options={
angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS
}
)
a=claripy.BVS('a',8*8) #%8s char=1 byte
b=claripy.BVS('b',64)
c=claripy.BVS('c',64)
d=claripy.BVS('d',64)

a_addr = 0xab232c0
initial_state.memory.store(a_addr, a)
b_addr = 0xab232c8
initial_state.memory.store(b_addr, b)
c_addr = 0xab232d0
initial_state.memory.store(c_addr, c)
d_addr = 0xab232d8
initial_state.memory.store(d_addr, d)

simulation=project.factory.simgr(initial_state)

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

def fail(state):
output=state.posix.dumps(1)
return 'Try again.'.encode() in output

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

if simulation.found:
solution=simulation.found[0]
q=solution.solver.eval(a,cast_to=bytes).decode()
w=solution.solver.eval(b,cast_to=bytes).decode()
e=solution.solver.eval(c,cast_to=bytes).decode()
r=solution.solver.eval(d,cast_to=bytes).decode()
s=' '.join([q,w,e,r])
print(s)
else:
print('fail')
# OJQVXIVX LLEAOODW UVCWUVVC AJXJMVKA

06_malloc_memory

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

buffer0 = (char *)malloc(9u);
buffer1 = (char *)malloc(9u);
memset(buffer0, 0, 9u);
memset(buffer1, 0, 9u);
printf("Enter the password: ");
__isoc99_scanf("%8s %8s", buffer0, buffer1);
for ( i = 0; i <= 7; ++i )
{
buffer0[i] = complex_function(buffer0[i], i);
buffer1[i] = complex_function(buffer1[i], i + 32);
}
if ( !strncmp(buffer0, "OSIWHBXI", 8u) && !strncmp(buffer1, "FOQVSBZB", 8u) )
puts("Good Job.");
else
puts("Try again.");
free(buffer0);
free(buffer1);
return 0;
}

改用地址指针,此关模拟heap堆

补充:

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
程序中调用了 malloc(9),
但 malloc 返回的内存块通常会按 16 字节对齐(在 32 位或 64 位系统上),
所以即使申请 8 字节,实际占用的空间是 16 字节(0x10)。

heap 是进程内存中的一块专门用于动态内存分配的区域。

特点:大小不固定,可以随着 malloc / free 动态扩展或收缩
由程序员手动管理(不像栈那样自动释放)
起始地址较低,向上增长(与栈相反)

Heap 区域(初始空闲)
+--------------------------------------------------+
| |
| Free Space |
| |
+--------------------------------------------------+
↑
malloc(8) 请求 8 字节

Heap 分配后
+------------------+-------------------------------+
| malloc(8) | Remaining Free |
| 返回 ptr | |
+------------------+-------------------------------+
↑
ptr → 指向这里
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

project=angr.Project('./06_angr_symbolic_dynamic_memory')
start_address=0x080486AF
initial_state=project.factory.blank_state(
addr=start_address,
add_options={
angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS
}
)
a=claripy.BVS('a',8*8) #%8s char=1 byte
b=claripy.BVS('b',64)

# *p 0xa2def74 --> 0x1111111
heap_addr0=0x1111111
pointer0=0xa2def74
# 目标架构,字节序调整 endness=project.arch.memory_endness
initial_state.memory.store(pointer0,heap_addr0,endness=project.arch.memory_endness, size=4)
heap_addr1=0x1111121
pointer1=0xa2def7c
initial_state.memory.store(pointer1,heap_addr1,endness=project.arch.memory_endness, size=4)

initial_state.memory.store(heap_addr0, a)
initial_state.memory.store(heap_addr1, b)
simulation=project.factory.simgr(initial_state)

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

def fail(state):
output=state.posix.dumps(1)
return 'Try again.'.encode() in output

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

if simulation.found:
solution=simulation.found[0]
q=solution.solver.eval(a,cast_to=bytes).decode()
w=solution.solver.eval(b,cast_to=bytes).decode()
s=' '.join([q,w])
print(s)
else:
print('fail')
# OFIJHOXV FBQISOZO

07_file

模拟文件读取

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
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // [esp-14h] [ebp-30h]
int v4; // [esp-10h] [ebp-2Ch]
int v5; // [esp-Ch] [ebp-28h]
int v6; // [esp-8h] [ebp-24h]
int v7; // [esp-4h] [ebp-20h]
int v8; // [esp+0h] [ebp-1Ch]
int i; // [esp+0h] [ebp-1Ch]

memset(buffer, 0, sizeof(buffer));
printf("Enter the password: ");
__isoc99_scanf("%64s", buffer, v3, v4, v5, v6, v7, v8);
ignore_me(buffer, 0x40u);
memset(buffer, 0, sizeof(buffer));
fp = fopen("FOQVSBZB.txt", "rb");
fread(buffer, 1u, 0x40u, fp);
fclose(fp);
unlink("FOQVSBZB.txt");
for ( i = 0; i <= 7; ++i )
*(_BYTE *)(i + 0x804A0A0) = complex_function(*(char *)(i + 134520992), i);
if ( strcmp(buffer, "OSIWHBXI") )
{
puts("Try again.");
exit(1);
}
puts("Good Job.");
exit(0);
}

wk.

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

project=angr.Project('./07_angr_symbolic_file')
start_address=0x080488BC
initial_state=project.factory.blank_state(
addr=start_address,
add_options={
angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS
}
)

filename='FOQVSBZB.txt'
symbolic_file_size_bytes = 8
password = claripy.BVS('password', symbolic_file_size_bytes * 8)
file=angr.storage.SimFile(filename,content=password)
initial_state.fs.insert(filename,file)

simulation=project.factory.simgr(initial_state)

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

def fail(state):
output=state.posix.dumps(1)
return 'Try again.'.encode() in output

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

if simulation.found:
solution=simulation.found[0]
s=solution.solver.eval(password,cast_to=bytes).decode()
print(s)
else:
print('fail')

# OBAXRUZT

补充:

概念 类型 含义 是否符号化 典型用途
BVS claripy.BVS(name, size) 符号变量(Symbolic Variable) 是 表示未知输入(如密码、密钥)
BVV claripy.BVV(value, size) 具体值(Concrete Value) 否 表示已知常量(如 “hello”)
dumps(0) state.posix.dumps(0) 获取程序从 stdin 读入的数据 可能包含符号,但输出 concrete 查看输入内容、调试
solver.eval(...) state.solver.eval(expr) 求解符号表达式 的具体值 输入是符号,输出是 concrete 得到密码、密钥等答案

08_constrain

处理字符串

符号执行16轮相当于2^16次,手动给出指定地址绕过字符串处理

跳过字符串处理

blank设置具体参数

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

project=angr.Project('./08_angr_constraints')
start_addr=0x0804863C
initial_state=project.factory.blank_state(
addr=start_addr,
add_options={
angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS
}
)

passwd=claripy.BVS('passwd',8*16)
passwd_addr=0x804a040
initial_state.memory.store(passwd, passwd_addr) #默认端序

simulation=project.factory.simgr(initial_state)

check_addr=0x8048683
simulation.explore(addr=check_addr)

if simulation.found:
solution_state=simulation.found[0]
cmp_addr=0x804a040
bytes_size=16
target_bitvector=solution_state.load(addr=check_addr,size=bytes_size)
aim_value='OSIWHBXIFOQVSBZB'.encode()
solution_state.add(target_bitvector==aim_value)
solution=solution_state.solver.eval(passwd,cast_to=bytes).decode()
print(solution)

else:
raise Exception('No solution found')
# ZEVKWROAYILRPZYB

entry自行填充

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

project=angr.Project('./08_angr_constraints')
initial_state=project.factory.entry_state(
add_options={
angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS
}
)

simulation=project.factory.simgr(initial_state)

address_to_check_constraint = 0x8048683
simulation.explore(find=address_to_check_constraint)

if simulation.found:
solution=simulation.found[0]
constrained_parameter_address = 0x804a040
constrained_parameter_size_bytes = 16
constrained_parameter_bitvector = solution.memory.load(
constrained_parameter_address,
constrained_parameter_size_bytes
)

constrained_parameter_desired_value = 'OSIWHBXIFOQVSBZB'.encode()
solution.add_constraints(constrained_parameter_bitvector == constrained_parameter_desired_value)
print(solution.posix.dumps(0).decode())

else:
print('fail')
#ZEVKWROAYILRPZYB

09_hook

angr的hook方法

主逻辑如下,两次输入以及校验,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
int __cdecl main(int argc, const char **argv, const char **envp)
{
_BOOL4 equals; // eax
int i; // [esp+8h] [ebp-10h]
int j; // [esp+Ch] [ebp-Ch]

qmemcpy(password, "OSIWHBXIFOQVSBZB", 16);
memset(buffer, 0, 0x11u);
printf("Enter the password: ");
__isoc99_scanf("%16s", buffer);
for ( i = 0; i <= 15; ++i )
*(_BYTE *)(i + 0x804A044) = complex_function(*(char *)(i + 0x804A044), 18 - i);
::equals = check_equals_OSIWHBXIFOQVSBZB(buffer, 16);
for ( j = 0; j <= 15; ++j )
*(_BYTE *)(j + 0x804A034) = complex_function(*(char *)(j + 0x804A034), j + 9);
__isoc99_scanf("%16s", buffer);
equals = ::equals && !strncmp(buffer, password, 0x10u);
::equals = equals;
if ( equals )
puts("Good Job.");
else
puts("Try again.");
return 0;
}
//占五个opcode
// .text:080486CA 02C E8 ED FE FF FF
// call check_equals_OSIWHBXIFOQVSBZB

关于此处字符串比较不用绕过的解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 标准strcmp函数如下
int strncmp(const char *s1, const char *s2, size_t n) {
for (size_t i = 0; i < n; i++) {
if (s1[i] != s2[i]) {
return s1[i] - s2[i];
}
if (s1[i] == '\0') { // 如果遇到结束符
return 0;
}
}
return 0;
}
// 采用减法形式,而常规比较则为==流
//angr每轮执行后分支<和>=两种情况,次方叠加,此处仅为减法,无需优化

wk.

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

project=angr.Project('./09_angr_hooks')
initial_state=project.factory.entry_state(
add_options={
angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS
}
)

check_equal_addr=0x080486CA
instruction_len=5 #.text:080486CA 02C E8 ED FE FF FF call
# check_equals_OSIWHBXIFOQVSBZB
#装饰器写法,用于angr执行下方函数
@project.hook(check_equal_addr,length=instruction_len)
def skip_check_equal(state):
inputbuff_addr=0x804A044
len=16
input_string=state.memory.load(inputbuff_addr,len)
check_string='OSIWHBXIFOQVSBZB'.encode()
#angr下 if/else 写法
state.regs.eax=claripy.If(
input_string==check_string,
claripy.BVV(1,32),
claripy.BVV(0,32)
)

simulation=project.factory.simgr(initial_state)

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

def fail(state):
output = state.posix.dumps(1)
return 'Try again.'.encode() in output

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

if simulation.found:
solution_state=simulation.found[0]
solution=solution_state.posix.dumps(0).decode()
print(solution)
else:
print('fail')

# QREPXOHPJPOQKQLK
# NOBMULEMGMLNHNIH

python装饰器

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
check_equal_addr=0x080486CA
instruction_len=5
@project.hook(check_equal_addr,length=instruction_len)
def skip_check_equal(state):
inputbuff_addr=0x804A044
len=16
input_string=state.memory.load(inputbuff_addr,len)
check_string='OSIWHBXIFOQVSBZB'.encode()
#angr下 if/else 写法
state.regs.eax=claripy.If(
input_string==check_string,
claripy.BVV(1,32),
claripy.BVV(0,32)
)

#等价

def skip_check_equal(state):
inputbuff_addr = 0x804A044
len = 16
input_string = state.memory.load(inputbuff_addr, len)
check_string = 'OSIWHBXIFOQVSBZB'.encode()
# angr下 if/else 写法
state.regs.eax = claripy.If(
input_string == check_string,
claripy.BVV(1, 32),
claripy.BVV(0, 32)
)

# 将函数注册为 hook
project.hook(check_equal_addr, skip_check_equal, length=instruction_len)

10_simprocedures

多轮加密,利用simprocedures简化处理,替换调用函数,原理不变:

1.hook 函数本身的地址

2.memory.load(regs.esp + xx)读取栈中函数

3.设eax返回值

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]
_BYTE s[17]; // [esp+2Bh] [ebp-1Dh] BYREF
unsigned int v6; // [esp+3Ch] [ebp-Ch]

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

wk.

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

project=angr.Project('./10_angr_simprocedures')
initial_state=project.factory.entry_state(
add_options={
angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS
}
)

class replacement_check_equal(angr.SimProcedure):
def run(self,to_check,len):
input_addr=to_check
input_len=len
string=self.state.memory.load(input_addr,input_len)
aim_string='OSIWHBXIFOQVSBZB'.encode()
return claripy.If(
string==aim_string,
claripy.BVV(1,32),
claripy.BVV(0,32)
)
check_equal_symbol='check_equals_OSIWHBXIFOQVSBZB'
#函数()勿忘
project.hook_symbol(check_equal_symbol,replacement_check_equal())

simulation=project.factory.simgr(initial_state)

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

def fail(state):
output = state.posix.dumps(1)
return 'Try again.'.encode() in output

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

if simulation.found:
solution_state=simulation.found[0]
solution=solution_state.posix.dumps(0).decode()
print(solution)
else:
print('fail')
# MTMDRONBBNSAAMNS

11_sim_scanf

simprocedures处理多个输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.text:08048711 04C 83 EC 04                          sub     esp, 4
.text:08048714 050 68 F8 8A 06 08 push offset buffer1
.text:08048719 054 68 0C 3A 06 08 push offset buffer0
.text:0804871E 058 68 83 FD 04 08 push offset aUU ; "%u %u"
.text:08048723 05C E8 28 FD FF FF call ___isoc99_scanf
.text:08048728 05C 83 C4 10 add esp, 10h


objdump -d ./11_angr_sim_scanf | grep scanf #-d disassemble

./11_angr_sim_scanf: 文件格式 elf32-i386
08048450 <__isoc99_scanf@plt>:
8048723: e8 28 fd ff ff call 8048450 <__isoc99_scanf@plt>
804878e: e8 bd fc ff ff call 8048450 <__isoc99_scanf@plt>
8048802: e8 49 fc ff ff call 8048450 <__isoc99_scanf@plt>
804886d: e8 de fb ff ff call 8048450 <__isoc99_scanf@plt>
80488ee: e8 5d fb ff ff call 8048450 <__isoc99_scanf@plt>
8048959: e8 f2 fa ff ff call 8048450 <__isoc99_scanf@plt>

wk.

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

project=angr.Project('./11_angr_sim_scanf')
initial_state=project.factory.entry_state(
add_options={
angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS
}
)

class replacement_scanf(angr.SimProcedure):
def run(self,format_string,string1_addr,string2_addr):
string1=claripy.BVS('string1',32)
string2=claripy.BVS('string2',32)
self.state.memory.store(string1_addr,string1,endness=project.arch.memory_endness)
self.state.memory.store(string2_addr,string2,endness=project.arch.memory_endness)
self.state.globals['solution1']=string1
self.state.globals['solution2']=string2

scanf_symbol='__isoc99_scanf'
project.hook_symbol(scanf_symbol,replacement_scanf())
simulation=project.factory.simgr(initial_state)

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

def fail(state):
output = state.posix.dumps(1)
return 'Try again.'.encode() in output

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

if simulation.found:
solution_state=simulation.found[0]
s1=solution_state.globals['solution1']
s2=solution_state.globals['solution2']
solution=f'{solution_state.solver.eval(s1)} {solution_state.solver.eval(s2)}'
print(solution)
else:
print('fail')
# 1447907916 1146768724

12_veritesting

主要引入veritesting概念

“Enhancing Symbolic Execution with Veritesting” (Avgerinos et al., ICSE 2014)

核心思想:结合动态符号执行(concolic execution)与静态路径展开(static path enumeration),在遇到复杂控制流(如循环、深层嵌套条件)时,自动“展开”部分代码区域,生成多条路径,从而避免过早陷入某一条路径而错过其他可能的解。

eg.

1
2
3
4
5
6
7
8
9
10
11
simgr = project.factory.simgr(initial_state, veritesting=True)

simgr = project.factory.simgr(
initial_state,
veritesting=True,
veritesting_options={
'enable_function_inlining': False,
'max_iterations': 10,
'loop_unrolling_limit': 5
}
)

wk. 自己这里本地没跑通,github上跑通的为2020年样例,等佬研究

附官方的

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
# When you construct a simulation manager, you will want to enable Veritesting:
# project.factory.simgr(initial_state, veritesting=True)
# Hint: use one of the first few levels' solutions as a reference.

import angr
import sys

def main(argv):
path_to_binary = argv[1]
project = angr.Project(path_to_binary)
initial_state = project.factory.entry_state(
add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
)
simulation = project.factory.simgr(initial_state, veritesting=True)

# Define a function that checks if you have found the state you are looking
# for.
def is_successful(state):
# Dump whatever has been printed out by the binary so far into a string.
stdout_output = state.posix.dumps(sys.stdout.fileno())

# Return whether 'Good Job.' has been printed yet.
# (!)
return 'Good Job.'.encode() in stdout_output # :boolean

# Same as above, but this time check if the state should abort. If you return
# False, Angr will continue to step the state. In this specific challenge, the
# only time at which you will know you should abort is when the program prints
# "Try again."
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return 'Try again.'.encode() in stdout_output # :boolean

# Tell Angr to explore the binary and find any state that is_successful identfies
# as a successful state by returning True.
simulation.explore(find=is_successful, avoid=should_abort)

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

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

13_static_binary

未打包库函数的静态elf,手动加载

留意_libc_start_main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// positive sp value has been detected, the output may be wrong!
void __usercall __noreturn start(int stack_end_@<eax>, void (*rtld_fini)(void)@<edx>)
{
int argc; // esi
int stack_end__1; // [esp-4h] [ebp-4h] BYREF
char *ubp_av_; // [esp+0h] [ebp+0h] BYREF

argc = stack_end__1;
stack_end__1 = stack_end_;
_libc_start_main(
(int (*)(int, char **, char **))main,
argc,
&ubp_av_,
(void (*)(void))_libc_csu_init,
(void (*)(void))_libc_csu_fini,
rtld_fini,
&stack_end__1);
}

.text:080487F0
; void __usercall __noreturn start(int stack_end_@<eax>, void (*rtld_fini)(void)@<edx>)

wk.

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


project = angr.Project('./13_angr_static_binary')

initial_state = project.factory.entry_state(
add_options={angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
)

project.hook(0x804fab0, angr.SIM_PROCEDURES['libc']['printf']())
project.hook(0x804fb10, angr.SIM_PROCEDURES['libc']['scanf']())
project.hook(0x80503f0, angr.SIM_PROCEDURES['libc']['puts']())
project.hook(0x8048d60, angr.SIM_PROCEDURES['glibc']['__libc_start_main']())

simulation = project.factory.simgr(initial_state)

def success(state):
output = state.posix.dumps(1)
return 'Good Job.'.encode() in output # :boolean

def fail(state):
output = state.posix.dumps(1)
return 'Try again.'.encode() in output # :boolean

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

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

# EADQYLAR

14_so

学起来跟frida思路很想啊,包括后面的地址偏移

so文件下的validate函数

1
2
3
(base) ma5k@ma5k-Ubuntu24:~/angr_ctf-master/solutions/14_angr_shared_library$ checksec --file=14_angr_shared_library
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY FortifiedFortifiable FILE
Partial RELRO Canary found NX enabled No PIE No RPATH RW-RUNPATH 69 Symbols No 0 214_angr_shared_library

no pie,基址不变

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

path_to_binary = "./lib14_angr_shared_library.so"
#自定义base基址,此处0x4000000默认,避开低地址与堆栈地址
base = 0x4000000
project = angr.Project(path_to_binary, load_options={
'main_opts' : {
'custom_base_addr' : base
}
})

buffer_pointer = claripy.BVV(0x3000000, 32)
validate_function_address = base + 0x670
initial_state = project.factory.call_state(
validate_function_address,
buffer_pointer,
claripy.BVV(8, 32),
add_options = {
angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
)

password = claripy.BVS('password', 8*8)
initial_state.memory.store(buffer_pointer, password)
simulation = project.factory.simgr(initial_state)
#validate处retn
success_address = base + 0x71c
simulation.explore(find=success_address)

if simulation.found:
solution_state = simulation.found[0]
solution_state.add_constraints(solution_state.regs.eax != 0)
solution = solution_state.solver.eval(password, cast_to=bytes).decode()
print(solution)
else:
raise Exception('Could not find the solution')
# TSDLQKWZ

pre-binary 选项:

如果你想要对一个特定的二进制对象设置一些选项,CLE也能满足你的需求在加载二进制文件时可以设置特定的参数,使用 main_opts 和 lib_opts 参数进行设置。

  • backend - 指定 backend
  • base_addr - 指定基址
  • entry_point - 指定入口点
  • arch - 指定架构

示例如下:

1
2
>>> angr.Project('examples/fauxware/fauxware', main_opts={'backend': 'blob', 'arch': 'i386'}, lib_opts={'libc.so.6': {'backend': 'elf'}})
<Project examples/fauxware/fauxware>

参数main_opts和lib_opts接收一个以python字典形式存储的选项组。main_opts接收一个形如{选项名1:选项值1,选项名2:选项值2……}的字典,而lib_opts接收一个库名到形如{选项名1:选项值1,选项名2:选项值2……}的字典的映射。

lib_opts是二级字典,原因是一个二进制文件可能加载多个库,而main_opts指定的是主程序加载参数,而主程序一般只有一个,因此是一级字典。

这些选项的内容因不同的后台而异,下面是一些通用的选项:

  • backend —— 使用哪个后台,可以是一个对象,也可以是一个名字(字符串)
  • custom_base_addr —— 使用的基地址
  • custom_entry_point —— 使用的入口点
  • custom_arch —— 使用的处理器体系结构的名字

后记

后续关卡为无条件约束以及溢出类pwn利用了,不是很感兴趣,暂时学到这,完工


angr_ctf
https://alenirving.github.io/2025/11/04/angr_ctf/
作者
Ma5k
许可协议
CC-BY-NC-SA