深入浅出angr--angrctf(一)

深入浅出angr–angrctf(一)

00_angr_find

这里根据angr_ctf中00_angr_find的例子来对angr工具进行应用

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

v6 = __readgsdword(0x14u);
printf("Enter the password: ");
__isoc99_scanf("%8s", s1);
for ( i = 0; i <= 7; ++i )
s1[i] = complex_function(s1[i], i);
if ( !strcmp(s1, "ILIUFVJF") )
puts("Good Job.");
else
puts("Try again.");
return 0;
}

上面是使用IDA反汇编代码得到的伪代码,代码逻辑是使用complex_function()对输入进行处理后做比较,关键代码是 if ( !strcmp(s1, “ILIUFVJF”) ),判断s1是否和”ILIUFVJF”相等。

下面为complex_function()的伪代码

1
2
3
4
5
6
7
8
9
int __cdecl complex_function(int a1, int a2)
{
if ( a1 <= 64 || a1 > 90 )
{
puts("Try again.");
exit(1);
}
return (3 * a2 + a1 - 65) % 26 + 65;
}

上述代码就是对s1的字符进行置换

手工逆向

我们先自己构造逆向代码反推输入,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
str = "ILIUFVJF"
str1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
flag =""
def complex_function(a1,a2):
if a1>65 and a1>90:
print("invaild a1 value")
return (3*a2+a1-65)%26+65
for i in range(len(str)):
for j in range(len(str1)):
str3=chr(complex_function(ord(str1[j]),i))
print(str3)
if str[i]==str3:
print("Found!")
flag+=str1[j]
else:
print("Not Found")
print(flag)

得到输入为IICLTGRK

angr

下面使用angr工具对其进行求解

首先获取项目执行地址,如下

1
2
3
4
5
import angr

pro = angr.Project("./00_angr_find")
init_state = pro.factory.entry_state()
print(init_state)

输入为**<SimState @ 0x8048450>**

在IDA中查看该地址为_start位置

指定需要找到的地址,这里为”good job”的地址0x0804868F

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import angr
import sys

def main(argv):
pro = angr.Project("./00_angr_find")
init_state = pro.factory.entry_state()
print(init_state)
# 3 创建模拟管理器Simulation Managers
simulation = pro.factory.simgr(init_state)

# 4 开始执行直到到达希望的解决方案或者探索完所有的可能的路径
# 0x0804867D为希望得到的输出的虚拟地址
# simulation.explore(find=0x0804867D)
simulation.explore(find=0x0804868F)

# 5 如果找到则生成simulation.found,可以打印出这个状态。
if simulation.found: # 检查是否发现了解决方案
solution_state = simulation.found[0]
print(solution_state.posix.dumps(sys.stdin.fileno())) # 代表该状态执行路径的所有输入
else:
raise Exception('Cannot find')

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

最后输出所有满足该执行状态的输入

和前面我们自己写的代码相同,可以看出angr工具的便捷

01_angr_avoid

使用IDA打开该程序反汇编失败

这里我们手工是无法对其进行逆向代码的,所以使用angr工具优势就来了

首先定位程序入口是否为**<SimState @ 0x8048430>**为_start位置

该题与上一题的区别就是多了个avoid_me

avoid_me代码如下,有一个should_succeed置零的操作,

而should_succeed是个定量为1,所以是一定不执行的,意思就是避免这条路径,使得爆破更加准确

avoid_me()为0x80485BF

“God Job”的地址为0x080D463E

编写代码如下

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

def main(argv):
pro = angr.Project("./01_angr_avoid")
init_state = pro.factory.entry_state()
print(init_state)
simulation = pro.factory.simgr(init_state)
good_address = 0x080485F7
flase_address = 0x80485BF
simulation.explore(find=good_address,avoid=flase_address)

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

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

找到solutions为JLVUSGJZ

02_angr_find_condition

使用IDA对其进行反汇编,伪代码如下

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+18h] [ebp-40h]
int j; // [esp+1Ch] [ebp-3Ch]
char s1[20]; // [esp+24h] [ebp-34h] BYREF
char s2[20]; // [esp+38h] [ebp-20h] BYREF
unsigned int v8; // [esp+4Ch] [ebp-Ch]

v8 = __readgsdword(0x14u);
for ( i = 0; i <= 19; ++i )
s2[i] = 0;
qmemcpy(s2, "CAWMCZTB", 8);
printf("Enter the password: ");
__isoc99_scanf("%8s", s1);
for ( j = 0; j <= 7; ++j )
s1[j] = complex_function(s1[j], j + 8);
if ( !strcmp(s1, s2) )
puts("Good Job.");
else
puts("Try again.");
return 0;
}

上述代码就是对输入进行变换后与”CAWMCZTB”进行比较作判断

手工逆向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
str = "CAWMCZTB"
str1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
flag =""
def complex_function(a1,a2):
if a1>65 and a1>90:
print("invaild a1 value")
return (31*a2+a1-65)%26+65
for i in range(len(str)):
for j in range(len(str1)):
str3=chr(complex_function(ord(str1[j]),i+8))
print(str3)
if str[i]==str3:
print("Found!")
flag+=str1[j]
else:
print("Not Found")
print(flag)

得到flag值为OHYJUMBE

angr

首先想到直接找到”Good Job”字符串的地址,然后使用angr来爆破条件约束项获取输入。下面我们先来找所需条件项的地址

可以再IDA中有很多引用”Good Job”字符串,无法唯一定位字符串地址。这个例子就是针对存在多个正确或者错误地址时,可以通过angr根据条件字符串是否在输出结果来寻找正确的输入,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import sys

import angr

def main(argv):
project = angr.Project("./02_angr_find_condition")
init_state = project.factory.entry_state()
simulation = project.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]
print(solution_state.posix.dumps(sys.stdin.fileno()))
else:
raise Exception("Could not find the solution!")




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

解得为OHYJUMBE

03_angr_symbolic_registers

使用IDA获取其反汇编代码

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 v3; // 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: ");
v3 = get_user_input();
v7 = HIDWORD(v3);
v5 = complex_function_1(v3);
v6 = complex_function_2();
v8 = complex_function_3(v7);
if ( v5 || v6 || v8 )
puts("Try again.");
else
puts("Good Job.");
return 0;
}

上述代码get_user_input()获取输入变量v1、v2、v3,返回v1赋值给v3作为输入变量,最后通过complex_function_1、complex_function_2、complex_function_3这三个函数处理后得到v5、v6和v8,再根据这三者的值作为条件得到判断。

在IDA中可以看到,调用输入函数后将三个值分别保存在eax、ebx、edx三个寄存器中,后面angr也需要对三个寄存器生成数据进行爆破

这里开始想的是使用00_angr_find的方式,但由于angr不支持多个输入,如下提示

1
2
3
# Angr doesn't currently support reading multiple things with scanf (Ex: 
# scanf("%u %u).) You will have to tell the simulation engine to begin the
# program after scanf is called and manually inject the symbols into registers.

只能在输入调用过后,在寄存器中插入对象

生成的angr代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# Angr doesn't currently support reading multiple things with scanf (Ex: 
# scanf("%u %u).) You will have to tell the simulation engine to begin the
# program after scanf is called and manually inject the symbols into registers.

import angr
import claripy
import sys

def main(argv):
project = angr.Project("./03_angr_symbolic_registers")

# Sometimes, you want to specify where the program should start. The variable
# start_address will specify where the symbolic execution engine should begin.
# Note that we are using blank_state, not entry_state.
# (!)
start_address = 0x80488c7 # :integer (probably hexadecimal)
initial_state = project.factory.blank_state(
addr=start_address,
add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
)

# Create a symbolic bitvector (the datatype Angr uses to inject symbolic
# values into the binary.) The first parameter is just a name Angr uses
# to reference it.
# You will have to construct multiple bitvectors. Copy the two lines below
# and change the variable names. To figure out how many (and of what size)
# you need, dissassemble the binary and determine the format parameter passed
# to scanf.
# (!)
password0_size_in_bits = 32 # :integer
password0 = claripy.BVS('password0', password0_size_in_bits)

password1_size_in_bits = 32 # :integer
password1 = claripy.BVS('password1', password1_size_in_bits)

password2_size_in_bits = 32 # :integer
password2 = claripy.BVS('password2', password2_size_in_bits)

# Set a register to a symbolic value. This is one way to inject symbols into
# the program.
# initial_state.regs stores a number of convenient attributes that reference
# registers by name. For example, to set eax to password0, use:
#
# initial_state.regs.eax = password0
#
# You will have to set multiple registers to distinct bitvectors. Copy and
# paste the line below and change the register. To determine which registers
# to inject which symbol, dissassemble the binary and look at the instructions
# immediately following the call to scanf.
# (!)
initial_state.regs.eax = password0
initial_state.regs.ebx = password1
initial_state.regs.edx = password2

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]

# Solve for the symbolic values. If there are multiple solutions, we only
# care about one, so we can use eval, which returns any (but only one)
# solution. Pass eval the bitvector you want to solve for.
# (!)
solution0 = solution_state.solver.eval(password0)
solution1 = solution_state.solver.eval(password1)
solution2 = solution_state.solver.eval(password2)

# Aggregate and format the solutions you computed above, and then print
# the full string. Pay attention to the order of the integers, and the
# expected base (decimal, octal, hexadecimal, etc).
solution = ' '.join(map('{:x}'.format, [ solution0, solution1, solution2 ])) # :string
print(solution)
else:
raise Exception('Could not find the solution')

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

得到三个输入e9b37483 7aab5fde 8f5b48ea

04_angr_symbolic_stack

IDA反汇编伪代码如下

1
2
3
4
5
6
int __cdecl main(int argc, const char **argv, const char **envp)
{
printf("Enter the password: ");
handle_user();
return 0;
}

进入关键函数handle_user()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int handle_user()
{
int result; // eax
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] == 2083606899 && v1 == -99478518 )
result = puts("Good Job.");
else
result = puts("Try again.");
return result;
}

手工逆向

就是两个异或操作,代码如下

1
2
3
4
5
v2 = 0xBF2406
v1 = 0xFAD61F01
v2 = v2 ^ 0x7C315173
v1 = v1 ^ 0xFA12140A
print("{}:{}".format(hex(v1),hex(v2)))

得到**0xc40b0b:0x7c8e7575 **/ 12847883:2089710965

angr

这个挑战是去处理栈,这里v2存在数组越界,导致栈溢出

但这里不是去构造栈溢出,而是构造符号执行,我们来看下wiki解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
This challenge requires dealing with the stack, so you have to pay extra
# careful attention to where you start, otherwise you will enter a condition
# where the stack is set up incorrectly. In order to determine where after
# scanf to start, we need to look at the dissassembly of the call and the
# instruction immediately following it:
# sub $0x4,%esp
# lea -0x10(%ebp),%eax
# push %eax
# lea -0xc(%ebp),%eax
# push %eax
# push $0x80489c3
# call 8048370 <__isoc99_scanf@plt>
# add $0x10,%esp
# Now, the question is: do we start on the instruction immediately following
# scanf (add $0x10,%esp), or the instruction following that (not shown)?
# Consider what the 'add $0x10,%esp' is doing. Hint: it has to do with the
# scanf parameters that are pushed to the stack before calling the function.
# Given that we are not calling scanf in our Angr simulation, where should we
# start?

与第三题的区别在于参数使用了栈而不是使用寄存器

  • 与32位程序参数调用顺序有关
  • 上题是将eax、ebx和edx依次压入栈,直接修改寄存器值即可;而本题需要手动布置栈结构

还是因为Angr不支持多个输入参数,而参数都存入栈中,所以我们可以跳过输入,直接操作栈来遍历结果,根据上面的提示what the ‘add $0x10,%esp’ is doing,在IDA中的反汇编代码位置如下

该句位于0x080486AB在scanf函数调用之后,销毁函数栈,所以要恢复堆栈。所以注入要从这里开始。

由于要手动压栈,所以需要要先记录在scanf函数调用后的ebp地址,可以从上方看到销毁函数栈后,此时的ebp为esp值

1
initial_state.regs.ebp = initial_state.regs.esp

scanf调用格式如下**scanf(offset aUU,ebp+var_C,ebp+var_10)**,栈格式如下

根据上方的堆栈图可以看出ebp到将password压入栈之间的填充数据为8个字节,所以未压栈之前的esp为

1
2
padding_length_in_bytes = 8  # :integer
initial_state.regs.esp -= padding_length_in_bytes

然后将生成的password0和password1压栈处理

1
2
initial_state.stack_push(password0)  # :bitvector (claripy.BVS, claripy.BVV, claripy.BV)
initial_state.stack_push(password1)

整个代码如下

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

def main(argv):
pro = angr.Project("./04_angr_symbolic_stack")
start_addr = 0x080486AE
init_state = pro.factory.blank_state(addr=start_addr)

init_state.regs.ebp = init_state.regs.esp
init_state.regs.esp -= 8

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

init_state.stack_push(password0)
init_state.stack_push(password1)

simulation = pro.factory.simgr(init_state)
def is_successful(state):
out_put = state.posix.dumps(sys.stdout.fileno())
return "Good Job.".encode() in out_put
def is_false(state):
out_put = state.posix.dumps(sys.stdout.fileno())
return "Try again.".encode() in out_put
simulation = simulation.explore(find=is_successful,avoid=is_false)

if simulation.found:
solution_state = simulation.found[0]
solution0 = solution_state.solver.eval(password0)
solution1 = solution_state.solver.eval(password1)
solution = "{}:{}".format(str(solution0),str(solution1))
print(solution)
else:
raise Exception("Could not find solution")


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


得到输入2089710965:12847883

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

得到的伪代码中可以看到程序首先输入四个参数,然后进行字节变换后比较user_input和”OSIWHBXIFOQVSBZBISILSCLBIAXSEWUT”是否相同。外面来看看四个参数存储位置,位于bbs全局变量

所以循环体就是对四个参数进行变换

手工逆向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
str = "OSIWHBXIFOQVSBZBISILSCLBIAXSEWUT"
str1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
flag =""
def complex_function(a1,a2):
if a1>65 and a1>90:
print("invaild a1 value")
return (9*a2+a1-65)%26+65
for i in range(len(str)):
for j in range(len(str1)):
str3=chr(complex_function(ord(str1[j]),i))
print(str3)
if str[i]==str3:
print("Found!")
flag+=str1[j]
else:
print("Not Found")
print(flag)

得到原来的flag为OJQVXIVXLLEAOODWUVCWUVVCAJXJMVKA

angr

和前面不一样的是,这四个参数保存在bbs断全局变量中,所以只需要往bbs断里面存储我们构造的password即可进行遍历

1
2
3
4
5
6
7
0AB232C0 user_input

0AB232C8 unk_AB232C8

0AB232D0 unk_AB232D0

0AB232D8 unk_AB232D8

代码如下

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

def main(argv):
project = angr.Project("./05_angr_symbolic_memory")

start_address = 0x8048618
initial_state = project.factory.blank_state(
addr=start_address,
add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
)

# The binary is calling scanf("%8s %8s %8s %8s").
# (!)
password0 = claripy.BVS('password0', 8*8)
password1 = claripy.BVS('password1', 8*8)
password2 = claripy.BVS('password2', 8*8)
password3 = claripy.BVS('password3', 8*8)

# Determine the address of the global variable to which scanf writes the user
# input. The function 'initial_state.memory.store(address, value)' will write
# 'value' (a bitvector) to 'address' (a memory location, as an integer.) The
# 'address' parameter can also be a bitvector (and can be symbolic!).
# (!)
password0_address = 0xab232c0
initial_state.memory.store(password0_address, password0)
password1_address = 0xab232c8
initial_state.memory.store(password1_address, password1)
password2_address = 0xab232d0
initial_state.memory.store(password2_address, password2)
password3_address = 0xab232d8
initial_state.memory.store(password3_address, password3)


simulation = project.factory.simgr(initial_state)

def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return 'Good Job.'.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]

# Solve for the symbolic values. We are trying to solve for a string.
# Therefore, we will use eval, with named parameter cast_to=bytes
# which returns bytes that can be decoded to a string instead of an integer.
# (!)
solution0 = solution_state.solver.eval(password0,cast_to=bytes).decode()
solution1 = solution_state.solver.eval(password1,cast_to=bytes).decode()
solution2 = solution_state.solver.eval(password2,cast_to=bytes).decode()
solution3 = solution_state.solver.eval(password3,cast_to=bytes).decode()

solution = ' '.join([ solution0, solution1, solution2, solution3 ])

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

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

得到OJQVXIVX LLEAOODW UVCWUVVC AJXJMVKA

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

与上一题不一样的是这里也可以指定虚拟地址

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

def main(argv):
project = angr.Project("./06_angr_symbolic_dynamic_memory")

start_address = 0x80486af
initial_state = project.factory.blank_state(
addr=start_address,
add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
)

# The binary is calling scanf("%8s %8s").
# (!)
password0 = claripy.BVS('password0', 8*8)
password1 = claripy.BVS('password0', 8*8)

# Instead of telling the binary to write to the address of the memory
# allocated with malloc, we can simply fake an address to any unused block of
# memory and overwrite the pointer to the data. This will point the pointer
# with the address of pointer_to_malloc_memory_address0 to fake_heap_address.
# Be aware, there is more than one pointer! Analyze the binary to determine
# global location of each pointer.
# Note: by default, Angr stores integers in memory with big-endianness. To
# specify to use the endianness of your architecture, use the parameter
# endness=project.arch.memory_endness. On x86, this is little-endian.
# size=number of bytes being stored (e.g. 32-bit address = 4 bytes)
# (!)
fake_heap_address0 = 0x4444444
pointer_to_malloc_memory_address0 = 0xa2def74
initial_state.memory.store(pointer_to_malloc_memory_address0, fake_heap_address0, endness=project.arch.memory_endness, size=4)
fake_heap_address1 = 0x4444454
pointer_to_malloc_memory_address1 = 0xa2def7c
initial_state.memory.store(pointer_to_malloc_memory_address1, fake_heap_address1, endness=project.arch.memory_endness, size=4)

# Store our symbolic values at our fake_heap_address. Look at the binary to
# determine the offsets from the fake_heap_address where scanf writes.
# (!)
initial_state.memory.store(fake_heap_address0, password0)
initial_state.memory.store(fake_heap_address1, password1)

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]

solution0 = solution_state.solver.eval(password0,cast_to=bytes).decode()
solution1 = solution_state.solver.eval(password1,cast_to=bytes).decode()

solution = ' '.join([ solution0, solution1 ])

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

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

得到OFIJHOXV FBQISOZO

07_angr_symbolic_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((int)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 + 134520992) = complex_function(*(char *)(i + 134520992), i);
if ( strcmp(buffer, "OSIWHBXI") )
{
puts("Try again.");
exit(1);
}
puts("Good Job.");
exit(0);
}

上述代码是从FOQVSBZB.txt读取保存在buffer里面,随后对buffer进行变换,最后与”OSIWHBXI”作比较。这里不同的是从文件读取,用到的函数是

1
2
3
4
5
filename = "xxxxx"
password_file = angr.storage.SimFile(filename, content=password)

# Add the symbolic file we created to the symbolic filesystem.
initial_state.fs.insert(filename, password_file)

开始的状态地址应该从为buffer申请地址空间开始,也可以从申请完空间调用fopen()之间作为开始地址

则代码为

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


def main(argv):
project = angr.Project('07_angr_symbolic_file')

start_address = 0x80488bc
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' # :string
password = claripy.BVS('password', 64)

password_file = angr.storage.SimFile(filename, content=password)
initial_state.fs.insert(filename, password_file)

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]
solution = solution_state.solver.eval(password)

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


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

得到文件值为5711199125053463124

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

让我给大家分享喜悦吧!

微信