跟着AngrCTF学Angr(2)

第二部分主要是hook相关的内容的学习

PS:

angr == 8.20.1.7

二进制文件和脚本存储于https://github.com/ycdxsb/Challenges/tree/master/angr_ctf

添加条件约束#

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

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

由于符号执行存在路径爆炸问题,在一些情况下,会出现路径爆炸的问题,比如上面的示例中,本来字符串一起比较即可,但由于实现中单字节比较,就会出现2的16次方的分支,因此选择自己添加条件约束是十分明智的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import angr
import sys
import claripy


def main(filepath):
project = angr.Project(filepath)
start_address = 0x8048625
init_state = project.factory.blank_state(addr=start_address)

password = claripy.BVS('password', 0x10*8)
password_addr = 0x804A050
init_state.memory.store(password_addr, password)
sim = project.factory.simgr(init_state)

def find(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b"Good Job." in stdout_output

def avoid(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b"Try again." in stdout_output

sim.explore(find=0x804866C)
if sim.found:
solution_state = sim.found[0]

constrained_parameter_address = 0x804A050
constrained_parameter_size_bytes = 0x10
constrained_parameter_bitvector = solution_state.memory.load(
constrained_parameter_address,
constrained_parameter_size_bytes
)

constrained_parameter_desired_value = "DQCSFFYXVUJIKEBQ"
constraint_expression = constrained_parameter_bitvector == constrained_parameter_desired_value
solution_state.add_constraints(
constrained_parameter_bitvector == constrained_parameter_desired_value)
solution = solution_state.se.eval(
password, cast_to=bytes) # 求解得到满足上述条件的输入
print(solution)
else:
raise Exception('Could not find the solution')


if __name__ == "__main__":
if(len(sys.argv) != 2):
print('usage:python angr_basic.py filepath')
filepath = sys.argv[1]
main(filepath)

简单Hook#

  • 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
signed int i; // [esp+8h] [ebp-10h]
signed int j; // [esp+Ch] [ebp-Ch]

qmemcpy(password, "JRZSQFKOVMSDNCWS", 16);
memset(buffer, 0, 0x11u);
printf("Enter the password: ");
__isoc99_scanf("%16s", buffer);
for ( i = 0; i <= 15; ++i )
*(_BYTE *)(i + 0x804A054) = complex_function(*(char *)(i + 0x804A054), 18 - i);
equals = check_equals_JRZSQFKOVMSDNCWS((int)buffer, 0x10u);
for ( j = 0; j <= 15; ++j )
*(_BYTE *)(j + 0x804A044) = complex_function(*(char *)(j + 0x804A044), 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;
}

同样是为了解决路径爆炸问题,但由于后续还有其他操作,所以我们在这里需要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
import angr
import claripy
import sys


def main(filepath):
project = angr.Project(filepath)
init_state = project.factory.entry_state()
sim = project.factory.simgr(init_state)

check_equals_called_address = 0x80486B3
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 = 0x804A054
user_input_buffer_length = 0x10
user_input_string = state.memory.load(
user_input_buffer_address, user_input_buffer_length)
check_against_string = "JRZSQFKOVMSDNCWS"
state.regs.eax = claripy.If(
user_input_string == check_against_string, claripy.BVV(1, 32), claripy.BVV(0, 32))

simulation = project.factory.simgr(init_state)

def find(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b"Good Job." in stdout_output

def avoid(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b"Try again." in stdout_output

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

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__':
if(len(sys.argv) != 2):
print('usage:python angr_basic.py filepath')
filepath = sys.argv[1]
main(filepath)

Hook时,首先要确定位置和hook掉函数占用的指令数,然后自己实现hook的函数,需要注意的是,返回值在eax寄存器中,需要进行符号化处理

进阶Hook#

  • 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)
{
signed int i; // [esp+20h] [ebp-28h]
char s[17]; // [esp+2Bh] [ebp-1Dh]
unsigned int v6; // [esp+3Ch] [ebp-Ch]

v6 = __readgsdword(0x14u);
memcpy(&password, "JWRJJJAJTWHCQHPZ", 0x10u);
memset(s, 0, 0x11u);
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_JWRJJJAJTWHCQHPZ((int)s, 0x10u) )
puts("Good Job.");
else
puts("Try again.");
return 0;
}

在一些时候,如果需要Hook的函数存在于多个地方,我们不可能一个一个的去Hook,所以按函数的符号名Hook是比较科学的方法,虽然示例中只出现了一次,但这次我们通过函数符号进行Hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import angr
import claripy
import sys


def main(filepath):
project = angr.Project(filepath)
init_state = project.factory.entry_state()
sim = project.factory.simgr(init_state)

class ReplacementCheckEquals(angr.SimProcedure):
def run(self,data_addr,length):
user_input_string = self.state.memory.load(data_addr,length)
check_against_string = "JWRJJJAJTWHCQHPZ"
return claripy.If(user_input_string==check_against_string,claripy.BVV(1,32),claripy.BVV(0,32))

check_equals_symbol = "check_equals_JWRJJJAJTWHCQHPZ"
project.hook_symbol(check_equals_symbol,ReplacementCheckEquals())

simulation = project.factory.simgr(init_state)

def find(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b"Good Job." in stdout_output

def avoid(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b"Try again." in stdout_output

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

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__':
if(len(sys.argv) != 2):
print('usage:python angr_basic.py filepath')
filepath = sys.argv[1]
main(filepath)

Hook scanf函数#

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

v8 = __readgsdword(0x14u);
memset(s, 0, 0x14u);
*(_DWORD *)s = 1329940303;
v7 = 1179340618;
for ( i = 0; i <= 7; ++i )
s[i] = complex_function(s[i], i);
printf("Enter the password: ");
__isoc99_scanf("%u %u", buffer0, buffer1);
v3 = !strncmp(buffer0, s, 4u) && !strncmp(buffer1, (const char *)&v7, 4u);
if ( v3 )
puts("Good Job.");
else
puts("Try again.");
return 0;
}

和前面很像,但这次需要hook我们最常用的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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import angr
import claripy
import sys


def main(filepath):
project = angr.Project(filepath)
init_state = project.factory.entry_state()
sim = project.factory.simgr(init_state)

class ReplacementScanf(angr.SimProcedure):
def run(self,format_string,buffer0_addr,buffer1_addr):
buffer0 = claripy.BVS('buffer0',4*8)
buffer1 = claripy.BVS('buffer1',4*8)
self.state.memory.store(buffer0_addr,buffer0,endness=project.arch.memory_endness)
self.state.memory.store(buffer1_addr,buffer1,endness=project.arch.memory_endness)
self.state.globals['solution0'] = buffer0
self.state.globals['solution1'] = buffer1

scanf_symbol = "__isoc99_scanf"
project.hook_symbol(scanf_symbol,ReplacementScanf())

simulation = project.factory.simgr(init_state)

def find(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b"Good Job." in stdout_output

def avoid(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b"Try again." in stdout_output

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

if simulation.found:
solution_state = simulation.found[0]
solution0 = solution_state.se.eval(solution_state.globals['solution0'])
solution1 = solution_state.se.eval(solution_state.globals['solution1'])
print(solution0,solution1)
else:
raise Exception('Could not find the solution')


if __name__ == '__main__':
if(len(sys.argv) != 2):
print('usage:python angr_basic.py filepath')
filepath = sys.argv[1]
main(filepath)

需要将符号数据存入buffer0和buffer1中,同时由于变量在对象内声明并且最后需要在对象外使用,因此通过state类的globals实现存取

Veritesting模式#

  • 12_angr_veritesting

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

def main(filepath):
project = angr.Project(filepath)
init_state = project.factory.entry_state()
sim = project.factory.simgr(init_state,veritesting=True)

def find(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b"Good Job." in stdout_output

def avoid(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b"Try again." in stdout_output

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


if __name__=="__main__":
if(len(sys.argv)!=2):
print('usage:python angr_basic.py filepath')
filepath = sys.argv[1]
main(filepath)

在静态编译的二进制中进行Hook#

  • 13_angr_static_binary

在符号执行时,遇到一些libc中的函数,angr其实都会帮我们hook掉,不然会影响符号执行的速度。所以这里通过实例让我们自己试试使用angr的功能Hook一下,angr已经实现的hook如下:

https://github.com/angr/angr/tree/master/angr/procedures/libc

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 sys

def main(filepath):
project = angr.Project(filepath)
init_state = project.factory.entry_state()
sim = project.factory.simgr(init_state,veritesting=True)
project.hook(0x804EF40,angr.SIM_PROCEDURES['libc']['printf']())
project.hook(0x804EF80,angr.SIM_PROCEDURES['libc']['scanf']())
project.hook(0x804F550,angr.SIM_PROCEDURES['libc']['puts']())
project.hook(0x80491F0,angr.SIM_PROCEDURES['glibc']['__libc_start_main']())
def find(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b"Good Job." in stdout_output

def avoid(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b"Try again." in stdout_output

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


if __name__=="__main__":
if(len(sys.argv)!=2):
print('usage:python angr_basic.py filepath')
filepath = sys.argv[1]
main(filepath)

Hook自定义的动态链接库#

  • 14_angr_shared_library

这里需要我们Hook自定义的动态链接库中的函数,这里不需要我们去执行这个二进制文件,而是直接通过angr对动态库lib14_angr_shared_library.so中的validate函数进行符号执行。

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


def main(filepath):
base = 0x400000
validate_addr_offset = 0x6D7
project = angr.Project(filepath, load_options={
'main_opts': {
'custom_base_addr': base
}
})
password_addr = claripy.BVV(0xffffff00, 32) # pointer
validate_addr = base + validate_addr_offset

init_state = project.factory.call_state(
validate_addr, password_addr, claripy.BVV(8, 32))
password = claripy.BVS("password", 8*8)
init_state.memory.store(password_addr, password)
sim = project.factory.simgr(init_state)
success_addr = base+0x783

def find(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b"Good Job." in stdout_output

def avoid(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b"Try again." in stdout_output

sim.explore(find=success_addr)
if sim.found:
solution_state = sim.found[0]
solution_state.add_constraints(solution_state.regs.eax != 0)
solution = solution_state.se.eval(password,cast_to = bytes)
print(solution)
else:
raise Exception('Could not find the solution')


if __name__ == "__main__":
if(len(sys.argv) != 2):
print('usage:python angr_basic.py filepath')
filepath = sys.argv[1]
main(filepath)

之前的hook我们都知道函数的地址,而在动态库中,我们没法直接知道地址,所以需要制定base,并传入参数调用函数。由于只对这个函数进行符号执行,所以也要加入符号执行正确时的约束。

评论