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_optslib_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_optslib_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