深入浅出angr--angrctf(二)

深入浅出angr–angrctf(二)

08_angr_constraints

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [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 + 134520896) = complex_function(*(char *)(i + 134520896), 15 - i);
if ( check_equals_OSIWHBXIFOQVSBZB(&buffer, 16) )
puts("Good Job.");
else
puts("Try again.");
return 0;
}

这里比较调用了check_equals_OSIWHBXIFOQVSBZB()函数,代码如下

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

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

上述代码就是将buffer的每一个字节与password的每一个字节作比较,人工很容易就能反推出原来的buffer,但是angr不能,只能一个一个去遍历,总共会有2^16=65536种分支,造成路径爆炸,这需要太长时间才能遍历得到结果。

这个挑战的方法是在check_equals_OSIWHBXIFOQVSBZB()之前遍历满足当前的buffer,手动添加约束条件来替代一个一个字节导致的分支,脚本如下

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

def main(argv):
project = angr.Project("./08_angr_constraints")
start_addr = 0x0804863c
init_state = project.factory.blank_state(addr=start_addr)

password = claripy.BVS("password",16*8)
init_state.memory.store(0x0804a040,password)

simulation = project.factory.simgr(init_state)
#符号输入化

#当寻找到check_equals()时,停止,自行进行约束判断
check_addr = 0x08048683
simulation.explore(find=check_addr)

#给函数进行参数初始化
if simulation.found:
solution_state = simulation.found[0]
constrained_parameter_address = 0x804a040
constrained_parameter_size_bytes = 16
constrained_parameter_bitvector = solution_state.memory.load(
constrained_parameter_address,
constrained_parameter_size_bytes
)
constrained_parameter_desired_value = 'OSIWHBXIFOQVSBZB'.encode()
solution_state.add_constraints(constrained_parameter_bitvector == constrained_parameter_desired_value)
solution = solution_state.solver.eval(password,cast_to=bytes).decode()
print(solution)
else:
raise Exception("Could not find the solution!")




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

得到原来的值为ZEVKWROAYILRPZYB

当然这里也可以使用Veritesting快速遍历得到

09_angr_hooks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int __cdecl main(int argc, const char **argv, const char **envp)
{
_BOOL4 v3; // 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 + 134520900) = complex_function(*(char *)(i + 0x804A044), 18 - i);
equals = check_equals_OSIWHBXIFOQVSBZB((int)buffer, 0x10u);
for ( j = 0; j <= 15; ++j )
*(_BYTE *)(j + 134520884) = complex_function(*(char *)(j + 0x804A034), j + 9);
__isoc99_scanf("%16s", buffer);
v3 = equals && !strncmp(buffer, password, 0x10u);
equals = v3;
if ( v3 )
puts("Good Job.");
else
puts("Try again.");
return 0;
}

和上面一样check_equals_OSIWHBXIFOQVSBZB()造成路径爆炸,所以执行到该函数需要停下,然后进行hook覆写check_equals()添加约束条件来得到其返回值,然后继续进行符号执行。我们来看看hook在官方文档的介绍

hook代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
check_equals_called_address = 0x80486ca
instruction_to_skip_length = 5
@project.hook(check_equals_called_address,length=instruction_to_skip_length)
def skip_check_equals_(state):
user_input_buffer_address = 0x804a044 # :integer, probably hexadecimal
user_input_buffer_length = 16

user_input_string = state.memory.load(
user_input_buffer_address,
user_input_buffer_length
)


check_against_string = 'OSIWHBXIFOQVSBZB'.encode() # :string

state.regs.eax = claripy.If(
user_input_string == check_against_string,
claripy.BVV(1, 32),
claripy.BVV(0, 32)
)

这里不能使用08那种做法的原因是由于需要进行多次输入,在找到约束条件之后无法再次进行符号执行。

整体代码如下:

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

def main(argv):
pro = angr.Project("./09_angr_hooks")
init_state = pro.factory.entry_state()


check_equal_addr = 0x080486ca
instruction_skip_length = 5
@pro.hook(check_equal_addr,length=instruction_skip_length)
def skip_check_equals_(state):
input_buffer_addr = 0x0804a044
buffer_length = 16
input_buffer_string = state.memory.load(input_buffer_addr,buffer_length)
check_string = 'OSIWHBXIFOQVSBZB'.encode()
state.regs.eax = claripy.If(input_buffer_string==check_string,claripy.BVV(1,32),claripy.BVV(0,32))

simulation = pro.factory.simgr(init_state)

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

def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return 'Try again.'.encode() in 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()).decode()
print(solution)
else:
raise Exception('Could not find the solution')

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

得到两次的输入为QREPXOHPJPOQKQLKNOBMULEMGMLNHNIH

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, "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(s[i], 18 - i);
if ( check_equals_OSIWHBXIFOQVSBZB(s, 16) )
puts("Good Job.");
else
puts("Try again.");
return 0;
}

这个看起来和08是相同的,但是这里s是位于堆栈里面的,08是位于bss段的。这里使用SimProcedure创建一个类去hook不同的类,与上一个相同格式进行hhok,只是针对hook多个类时方便进行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
32
33
34
35
36
37
38
39
40
41
42
import angr
import claripy
import sys

def main(argv):
pro = angr.Project("./10_angr_simprocedures")
init_state = pro.factory.entry_state()

class ReplacementCheckEquals(angr.SimProcedure):
def run(self, check_addr, length):
input_buffer_addr = check_addr
input_buffer_length = length

input_buffer_string = self.state.memory.load(input_buffer_addr,input_buffer_length)
check_against_string = 'OSIWHBXIFOQVSBZB'.encode()
return claripy.If(input_buffer_string==check_against_string,claripy.BVV(1,32),claripy.BVV(0,32))

check_equals_symbol = 'check_equals_OSIWHBXIFOQVSBZB' # :string
pro.hook_symbol(check_equals_symbol, ReplacementCheckEquals())


simulation = pro.factory.simgr(init_state)

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

def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return 'Try again.'.encode() in 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()).decode()
print(solution)
else:
raise Exception('Could not find the solution')

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

得到MTMDRONBBNSAAMNS

SimProcedure旨在完全替换所挂钩的任何函数,最初用例是替换库函数

我看代码的时候有个疑问就是ReplacementCheckEquals()中并没有指定明确的参数,具体是这么操作的呢?

SimProcedure运行时将通过调用约定自动从程序状态中提取这些参数(被hook函数的参数),然后调用run函数,当在run函数中获得的返回也同样被置于被hook函数的返回值,这就是hook的操作。

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, "LOGMHXHI", 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;
}

这道题是对_isoc99_scanf()使用SimProcedure进行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
51
import angr
import sys
import claripy

def main(argv):
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 Replacementscanf(angr.SimProcedure):
def run(self,string,password0_addr,password1_addr):
password0 = claripy.BVS('password0',32)
password1 = claripy.BVS('password1',32)

self.state.memory.store(password0_addr, password0, endness=project.arch.memory_endness)
self.state.memory.store(password1_addr, password1, endness=project.arch.memory_endness)

self.state.globals['solution0'] = password0
self.state.globals['solution1'] = password1

replace_function = "__isoc99_scanf"
project.hook_symbol(replace_function,Replacementscanf())

simulation = project.factory.simgr(initial_state)

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

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

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

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

# Grab whatever you set aside in the globals dict.
stored_solutions0 = solution_state.globals['solution0']
stored_solutions1 = solution_state.globals['solution1']
solution = f'{solution_state.solver.eval(stored_solutions0)} {solution_state.solver.eval(stored_solutions1)}'
print(solution)
else:
raise Exception('Could not find the solution')


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

得到结果为1447907916 1146768724

12_angr_veritesting

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // ebx
int v5; // [esp-14h] [ebp-60h]
int v6; // [esp-10h] [ebp-5Ch]
int v7; // [esp-Ch] [ebp-58h]
int v8; // [esp-8h] [ebp-54h]
int v9; // [esp-4h] [ebp-50h]
const char **v10; // [esp+0h] [ebp-4Ch]
int v11; // [esp+4h] [ebp-48h]
int v12; // [esp+8h] [ebp-44h]
int v13; // [esp+Ch] [ebp-40h]
int v14; // [esp+10h] [ebp-3Ch]
int v15; // [esp+10h] [ebp-3Ch]
int v16; // [esp+14h] [ebp-38h]
int i; // [esp+14h] [ebp-38h]
int v18; // [esp+18h] [ebp-34h]
_DWORD v19[9]; // [esp+1Ch] [ebp-30h] BYREF
unsigned int v20; // [esp+40h] [ebp-Ch]
int *v21; // [esp+44h] [ebp-8h]

v21 = &argc;
v10 = argv;
v20 = __readgsdword(0x14u);
memset((char *)v19 + 3, 0, 0x21u);
printf("Enter the password: ");
((void (__stdcall *)(const char *, char *, int, int, int, int, int, const char **, int, int, int, int, int, int, _DWORD))__isoc99_scanf)(
"%32s",
(char *)v19 + 3,
v5,
v6,
v7,
v8,
v9,
v10,
v11,
v12,
v13,
v14,
v16,
v18,
v19[0]);
v15 = 0;
for ( i = 0; i <= 31; ++i )
{
v3 = *((char *)v19 + i + 3);
if ( v3 == complex_function(67, i + 232) )
++v15;
}
if ( v15 != 32 || (_BYTE)v20 )
puts("Try again.");
else
puts("Good Job.");
return 0;
}

根据wiki提示如下需要使用veritesting

1
2
# When you construct a simulation manager, you will want to enable Veritesting:
# project.factory.simgr(initial_state, veritesting=True)

什么是veritesting?

从高层面来说,有两种符号执行,一种是动态符号执行(Dynamic Symbolic Execution,简称DSE),另一种是静态符号执行(Static Symbolic Execution,简称SSE)。动态符号执行会去执行程序然后为每一条路径生成一个表达式。而静态符号执行将程序转换为表达式,每个表达式都表示任意条路径的属性。基于路径的DSE在生成表达式上引入了很多的开销,然而生成的表达式很容易求解。而SSE虽然生成表达式容易,但是表达式难求解。veritesting就是在这二者中做权衡,使得能够在引入低开销的同时,生成较易求解的表达式。

如下面例子,有着2^100条执行路径,如果每条路径都使用DES去分析的话,实现是不太现实的。实际上只需要两个测试样例,一个包含75B的字符串和一个没有B的字符串。

Veritest从DSE开始,一旦遇到一些简单的代码,就切换到SSE的模式。简单的代码指的是不含系统调用,间接跳转或者其他很难精确推断的语句。在SSE模式下,首先动态恢复控制流图,找到SSE容易分析的语句和难以分析的语句。然后SSE算法推断出从易分析的节点到难分析节点路径的影响。最后,Veritesting切换回DSE模式去处理静态不好解决的情况。

由于在上面代码中存在2^32条路径,这里使用Veritest进行遍历

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 sys

def main(argv):
project = angr.Project("./12_angr_veritesting")
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)


def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return 'Good Job.'.encode() in stdout_output # :boolean
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return 'Try again.'.encode() in 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()).decode())
else:
raise Exception('Could not find the solution')

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

得到输入为YNCRGVKZODSHWLAPETIXMBQFUJYNCRGV

13_angr_static_library

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

v8 = __readgsdword(0x14u);
for ( i = 0; i <= 19; ++i )
v7[i] = 0;
qmemcpy(v7, "ELZXQOOQ", 8);
printf("Enter the password: ");
_isoc99_scanf("%8s", v6);
for ( j = 0; j <= 7; ++j )
v6[j] = complex_function(v6[j], j);
if ( j_strcmp_ifunc(v6, v7) )
puts("Try again.");
else
puts("Good Job.");
return 0;
}

这题和第一题的代码没什么区别,但它是静态编译得来的二进制文件,将所有的库函数都写入二进制文件。angr对于库函数只会分出一条路径,而不关心库函数内部是怎样实现的,库函数内部的分支也不会增加angr路径上的分支数量。

在默认情况下angr会使用SimProcedures里面的符号函数摘要集替换库函数,本质上是在库函数上设置了Hooking,这些hook 函数高效地模仿库函数对状态的影响,就像我们之前在第8题中做的那样。因此angr不进入库函数内部的原因在于,它实际上执行的是hook函数,而hook函数只模仿了库函数对状态的影响,实际内部的操作并没有实现,因此也就不会产生额外分支。

Simprocedures是一个两层结构,第一层表示包名,第二层则是函数名

而此题库函数被静态编译进来了,默认启用的符号函数摘要集作用在动态链接库上,因此此时失效了,在该题中调用的库函数都会进入内部并产生相应的分支,这会大大降低angr的效率。此题的目的在于,手动使用符号函数摘要集替换程序中使用到的库函数

想要获取某个符号函数摘要集中的函数,可以使用下面的代码:

1
angr.SIM_PROCEDURES['libc']['scanf']()

就可以获取libc包中的scanf函数了,它是一个与之前第10题中我们创建的class Hook是同一个类。对于这样的hook函数,可以使用以下两种方式将它hook到目标函数上去:

1
2
3
project.hook(address_of_hooked, angr.SIM_PROCEDURES['libc']['scanf']())

project.hook_symbol('__isoc99_scanf',angr.SIM_PROCEDURES['libc']['scanf']())

一种是传递待hook函数的地址,还有一种是传递函数名。

此外在进入main函数之前,程序会先调用__libc_start_main,它也是库函数,而在创建状态时,如果使用entry_state(),则初始状态就已经经过了__libc_start_main的调用,所以最好也hook掉这个函数,或者使用blank_state手动从main函数开始。

14_angr_shared_library

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)
{
char s[16]; // [esp+1Ch] [ebp-1Ch] BYREF
unsigned int v5; // [esp+2Ch] [ebp-Ch]

v5 = __readgsdword(0x14u);
memset(s, 0, sizeof(s));
printf("Enter the password: ");
__isoc99_scanf("%8s", s);
if ( validate(s, 8) )
puts("Good Job.");
else
puts("Try again.");
return 0;
}

这题不是静态编译了,main函数的逻辑也和13题一样,但是用于混淆输入和比较的函数validate是通过动态链接库调用进来的,因此直接逆向查看动态链接库获取validate函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
_BOOL4 __cdecl validate(char *s1, int a2)
{
char v3; // al
char s2[20]; // [esp+4h] [ebp-24h] BYREF
int j; // [esp+18h] [ebp-10h]
int i; // [esp+1Ch] [ebp-Ch]

if ( a2 <= 7 )
return 0;
for ( i = 0; i <= 19; ++i )
s2[i] = 0;
qmemcpy(s2, "THHEYHIA", 8);
for ( j = 0; j <= 7; ++j )
{
v3 = complex_function(s1[j], j);
s1[j] = v3;
}
return strcmp(s1, s2) == 0;
}

思路如下:

  • 模拟validate的函数执行,传递一个符号变量参数
  • 使用explorer()在validate函数后探索路径
  • 添加状态约束,使用add_constraints()为状态添加约束项

模拟函数执行有两种方法,一种方法是使用blank_state()设置起始位置,并通过布置栈压栈操作来向函数传递参数,代码如下

1
2
3
4
5
6
init_state = p.factory.blank_state(addr=validate_addr)

init_state.regs.ebp = init_state.regs.esp
init_state.stack_push(8)
init_state.stack_push(password_addr)
init_state.stack_push(0)

还有一种方法是,angr提供了更方便的从函数处开始执行的方式

1
init_state = p.factory.call_state(func_addr, param1, param2)

最后还需要注意的一点是,动态链接库在加载时需要重定位,可以在建立项目时用load_options设定重定位的基址,就像这样:

1
2
3
4
5
p = angr.Project(path_to_binary,
auto_load_libs=False,
load_options={'main_opts': {
'custom_base_addr': base_addr
}})

如果不设立基址,通常angr会默认加载到0x400000处,在IDA中看到的各个指令的地址都只是相对地址,需要加上基址才能找到它们.

validate开始相对地址为0x670,结束调用地址为0x0000071C

将返回值保存在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
39
40
41

import angr
import claripy
import sys

def main(argv):
base = 0x4000000
project = angr.Project("./lib14_angr_shared_library.so", load_options={
'main_opts' : {
'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)

check_constraint_address = base + 0x71c
simulation.explore(find=check_constraint_address)

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

solution_state.add_constraints(solution_state.regs.eax == 1)
solution = solution_state.solver.eval(password,cast_to=bytes).decode()
print(solution)
else:
raise Exception('Could not find the solution')

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

得到结果为TSDLQKWZ

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; // [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 == 2358019 )
puts(s);
else
puts(try_again);
return 0;
}

代码逻辑是当key等于2358019时输出s,否则输出try_again。而s的初始值为try_again,并且这里的输入存在栈溢出可以对s进行覆盖为”Good Job.”。并且该字符串在0x4f534957地址处。

pwn

1
2
3
4
5
6
7
from pwn import *

p = process("./15_angr_arbitrary_read")
Good_addr =0x4f534957
payload = b'2358019'+b'a'*0x10+p32(Good_addr)
p.sendline(payload)
p.interactive()

覆盖成功输出

angr

求解步骤如下:

  1. 使用SimProcedur对输入符号化,并且为了避免路径太多造成的超时需设置限制,指定第二个输入为字符
  2. 要想找到目标状态,需要在puts之前停下来,这里也需要设置约束条件,让参数s等于“God Job.”的地址

在main函数中,对puts的调用如下

puts()对应的plt地址如下

由于angr是基于一个基本块执行的,这里可以选用0x08048548作为开始地址,但s是保存在堆栈中,此时并未调用puts函数保存堆栈,所以选用0x08048370作为地址,此时的参数地址保存在esp+4

代码如下

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

import angr
import claripy
import sys


def main(argv):
project = angr.Project('./15_angr_arbitrary_read')
initial_state = project.factory.entry_state(
add_options={angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
)
class ReplacementScanf(angr.SimProcedure):
def run(self, format_string, param0, param1):
scanf0 = claripy.BVS('scanf0', 32)
scanf1 = claripy.BVS('scanf1', 20 * 8)
for char in scanf1.chop(bits=8):
self.state.add_constraints(char >= 'A'.encode(), char <= 'Z'.encode())
self.state.memory.store(param0, scanf0, endness=project.arch.memory_endness)
self.state.memory.store(param1, scanf1)
self.state.globals['solutions'] = (scanf0, scanf1)
scanf_symbol = '__isoc99_scanf' # :string
project.hook_symbol(scanf_symbol, ReplacementScanf())
def check_puts(state):
puts_parameter = state.memory.load(state.regs.esp + 4, 4, endness=project.arch.memory_endness)
if state.solver.symbolic(puts_parameter):
good_job_string_address = 0x4f534957 # :integer, probably hexadecimal
is_vulnerable_expression = puts_parameter == good_job_string_address # :boolean bitvector expression
if state.satisfiable(extra_constraints=(is_vulnerable_expression,)):
# Before we return, let's add the constraint to the solver for real,
# instead of just querying whether the constraint _could_ be added.
state.add_constraints(is_vulnerable_expression)
return True
else:
return False
else: # not state.solver.symbolic(???)
return False

simulation = project.factory.simgr(initial_state)

# In order to determine if we have found a vulnerable call to 'puts', we need
# to run the function check_puts (defined above) whenever we reach a 'puts'
# call. To do this, we will look for the place where the instruction pointer,
# state.addr, is equal to the beginning of the puts function.
def is_successful(state):
# We are looking for puts. Check that the address is at the (very) beginning
# of the puts function. Warning: while, in theory, you could look for
# any address in puts, if you execute any instruction that adjusts the stack
# pointer, the stack diagram above will be incorrect. Therefore, it is
# recommended that you check for the very beginning of puts.
# (!)
puts_address = 0x8048370
if state.addr == puts_address:
# Return True if we determine this call to puts is exploitable.
return check_puts(state)
else:
# We have not yet found a call to puts; we should continue!
return False

simulation.explore(find=is_successful)

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

(scanf0, scanf1) = solution_state.globals['solutions']
solution = str(solution_state.solver.eval(scanf0)) + ' ' + solution_state.solver.eval(scanf1,
cast_to=bytes).decode()
print(solution)
else:
raise Exception('Could not find the solution')


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

得到两个参数2358019 AAAAAAAAAAAAAAAAWISO

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 == 6712341 )
strncpy(dest, s, 0x10u);
else
strncpy(unimportant_buffer, s, 0x10u);
if ( !strncmp(password_buffer, "NEDVTNOP", 8u) )
puts("Good Job.");
else
puts("Try again.");
return 0;
}

要满足两个条件key == 6712341和**password_buffer==”NEDVTNOP”**,所以对

strncpy()调用时添加上面两个约束条件,代码如下

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


import angr
import claripy
import sys

def main(argv):

project = angr.Project('./16_angr_arbitrary_write')


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

class ReplacementScanf(angr.SimProcedure):
# Hint: scanf("%u %20s")
def run(self, format_string, param0, param1):
# %u
scanf0 = claripy.BVS('scanf0', 32)

# %20s
scanf1 = claripy.BVS('scanf1', 20*8)

for char in scanf1.chop(bits=8):
self.state.add_constraints(char >= 48, char <= 96)

scanf0_address = param0
self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
scanf1_address = param1
self.state.memory.store(scanf1_address, scanf1)

self.state.globals['solutions'] = (scanf0, scanf1)

scanf_symbol = '__isoc99_scanf' # :string
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_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.solver.symbolic(src_contents) and state.solver.symbolic(strncpy_dest):
password_string = 'NEDVTNOP'.encode() # :string
buffer_address = 0x4d43523c # :integer, probably in hexadecimal
does_src_hold_password = src_contents[-1:-64] == password_string
does_dest_equal_buffer_address = strncpy_dest == buffer_address
if state.satisfiable(extra_constraints=(does_src_hold_password, does_dest_equal_buffer_address)):
state.add_constraints(does_src_hold_password, does_dest_equal_buffer_address)
return True
else:
return False
else: # not state.solver.symbolic(???)
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]

scanf0, scanf1 = solution_state.globals['solutions']
solution = str(solution_state.solver.eval(scanf0)) + ' ' + solution_state.solver.eval(scanf1, cast_to=bytes).decode()
print(solution)
else:
raise Exception('Could not find the solution')

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

6712341 NEDVTNOPP@@@@@@@<RCM

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;
}

其中read_input()就是输入

1
2
3
4
5
6
int read_input()
{
char v1[43]; // [esp+Dh] [ebp-2Bh] BYREF

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

按照这个代码逻辑的话,程序只会输出”Try again.”。但__isoc99_scanf()存在栈溢出漏洞,可以覆盖其返回地址为puts()地址,输出”Good Job.”;或者其中还有个函数print_good(),地址为0x4D435250

pwn代码如下

1
2
3
4
5
6
from pwn import *

p = process("./17_angr_arbitrary_jump")
payload = b'a' *0x2F + p32(0x4D435250)
p.sendline(payload)
p.interactive()

这道题获取输入,使得某个state的eip变成想要的值。意味着让eip变成某个符号量,然后求解这个符号变量。angr对于这种情况,一般会将state放到unconstrained这个stash中,这个stash中的state会被angr默认丢弃以增加效率,而现在我们需要这个stash里面的state。可以在创建SM时保存,就像这样

1
simgr = p.factory.simgr(init_state, save_unconstrained=True)

处于unconstrained中的state,如果它在eip等于目标地址(也就是print_good的地址)时,能够有解(满足state.satisfiable为真),这样的状态就是目标状态了.angr在执行时,每次进入新的路径就会有新的state,为了判断这个state是否符合要求,这次不用explorer自动探索了,我们采用step单步执行,然后获取state进行判断。

代码如下

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

import angr
import claripy
import sys

def main(argv):
project = angr.Project('17_angr_arbitrary_jump')
initial_state = project.factory.entry_state()

class SimScanfProcedure(angr.SimProcedure):

def run(self, fmtstr, input_addr):
input_bvs = claripy.BVS('input_addr', 200 * 8)
for chr in input_bvs.chop(bits=8):
self.state.add_constraints(chr >= '0', chr <= 'z')
self.state.memory.store(input_addr, input_bvs)
self.state.globals['input_val'] = input_bvs


project.hook_symbol('__isoc99_scanf', SimScanfProcedure())

simulation = project.factory.simgr(
initial_state,
save_unconstrained=True,
stashes={
'active' : [initial_state],
'unconstrained' : [],
'found' : [],
'not_needed' : []
}
)
def has_found_solution():
return simulation.found
def has_unconstrained_to_check():
return simulation.unconstrained
def has_active():
return simulation.active

while (has_active() or has_unconstrained_to_check()) and (not has_found_solution()):
for unconstrained_state in simulation.unconstrained:
simulation.move('unconstrained', 'found')
simulation.step()

if simulation.found:
solution_state = simulation.found[0]
solution_state.add_constraints(solution_state.regs.eip == 0x4d435250)

# Solve for the symbolic_input
symbolic_input = solution_state.globals['input_val']
solution = solution_state.solver.eval(symbolic_input,cast_to=bytes).decode()
print(solution)
else:
raise Exception('Could not find the solution')

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

得到**@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@PRCM@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@**

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2024 John Doe
  • 访问人数: | 浏览次数:

让我给大家分享喜悦吧!

微信