angr入门小试

angr 入门小试

angr 入门

defcamp_quals 2015: r100

该题接收输入,通过sub_4006FD函数校验输入,通过返回值来确定执行后续哪个分支

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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
__int64 result; // rax
char s[264]; // [rsp+0h] [rbp-110h] BYREF
unsigned __int64 v5; // [rsp+108h] [rbp-8h]

v5 = __readfsqword(0x28u);
printf("Enter the password: ");
if ( !fgets(s, 255, stdin) )
return 0LL;
if ( (unsigned int)sub_4006FD((__int64)s) ) // 关键点 - 验证算法
{
puts("Incorrect password!"); // addr: 040085A
result = 1LL;
}
else
{
puts("Nice!"); // addr: 0x0400849
result = 0LL;
}
return result;
}

// 验证算法
__int64 __fastcall sub_4006FD(__int64 a1)
{
int i; // [rsp+14h] [rbp-24h]
__int64 v3[4]; // [rsp+18h] [rbp-20h]

v3[0] = (__int64)"Dufhbmf";
v3[1] = (__int64)"pG`imos";
v3[2] = (__int64)"ewUglpt";
for ( i = 0; i <= 11; ++i ) // 只用了前12字节
{
if ( *(char *)(v3[i % 3] + 2 * (i / 3)) - *(char *)(i + a1) != 1 )
return 1LL;
}
return 0LL;
}

基本解法

1
2
3
4
5
6
7
8
9
10
import angr
import sys

proj = angr.Project('r100')
state = proj.factory.full_init_state()
simgr = proj.factory.simulation_manager(state)

simgr.explore(find=0x0400849, avoid=0x040085A)
if simgr.found:
print(simgr.found[0].posix.dumps(sys.stdin.fileno()))
  • 通过angr.Project('r100')创建proj对象
  • proj.factory.full_init_state()进行完整初始化state
  • 利用创建的state初始化simulation_manager对象simgr
  • 调用simgr.explore方法,其中find参数用来标记找到的正确路径,avoid用来标记需要排除的路径,多个路径可以用[addr1, addr2]表示
  • 当找到路径时,simgr.found非空,则可以选择打印simgr.found[0]路径,posix.dumps(sys.stdin.fileno())表示获取POSIX标准输入(sys.stdin.fileno()也可用0来代替)

此解法运行结果及时间如下:

1
2
3
4
5
6
pwn@ubuntu:~/workspace$ time python3 exp.py 
b'Code_Talkers\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf1\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xb5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xe5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xe5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xd5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\x00'

real 0m4.912s
user 0m4.688s
sys 0m0.224s

优化解法

  • angr.Project('r100', auto_load_libs=False)通过auto_load_libs参数指定是否自动加载分析依赖库,这里因为此程序只依赖了libc,angr已经做了相应的处理,所以不需要再次加载
  • 使用blank_state(addr)来替换full_init_state,这里传入main函数地址,直接从main函数开始不影响接下来的验证
  • 由于传入的state直接指定了main函数作为起点,故C运行库还未初始化,该题中调用的printffgets等函数会直接返回一个不确定值,所以要Hook这两个函数

angr hook

在angr中预先实现了一些函数的hook,它们在angr/procedures下,通过angr.SIM_PROCEDURES[包名][函数名]得到

user hook

使用@project.hook(addr, length)装饰器来定义实现一个用户Hook,参数addr表示需要hook的地址,length表示hook的长度,即在inline hook中,替换指令的字节数,示例如下,详细API说明间文档:

1
2
3
>>> @project.hook(0x1234, length=5)
... def set_rax(state):
... state.regs.rax = 1
Hooking Symbol

hook符号地址,使用proj对象的hook_symbol方法来hook符号,hook函数必须以类的形式存在,且必须继承自angr.SimProcedure并重写类的run方法,示例如下,详细API说明间文档:

1
2
3
4
5
6
7
8
>>> class NotVeryRand(SimProcedure):
... def run(self, return_values=None):
... rand_idx = self.state.globals.get('rand_idx', 0) % len(return_values)
... out = return_values[rand_idx]
... self.state.globals['rand_idx'] = rand_idx + 1
... return out

>>> project.hook_symbol('rand', NotVeryRand(return_values=[413, 612, 1025, 1111]))

优化后代码

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

# hook fgets函数,根据题目中只用了前12个字节,这里就直接拿12字节内容拷贝到缓冲区上
class Myfgets(angr.SimProcedure):
def run(self, s, num, f):
simfd = self.state.posix.get_fd(sys.stdin.fileno())
data, ret_size = simfd.read_data(12)
self.state.memory.store(s, data)
return ret_size

proj = angr.Project('r100', auto_load_libs=False)
state = proj.factory.blank_state(addr=0x04007E8)

# hook printf直接返回,replace=True代表替换angr自己事先hook的函数,这里printf不影响就直接给返回
proj.hook_symbol('printf', angr.SIM_PROCEDURES['stubs']['ReturnUnconstrained'](), replace=True)
# hook fgets,使用自己的hook函数
proj.hook_symbol('fgets', Myfgets(), replace=True)

# setting state
simgr = proj.factory.simulation_manager(state)

simgr.explore(find=0x0400849, avoid=0x040085A)
if simgr.found:
print(simgr.found[0].posix.dumps(sys.stdin.fileno()))

执行结果及时间

1
2
3
4
5
6
pwn@ubuntu:~/workspace$ time python3 exp.py 
b'Code_Talkers'

real 0m2.142s
user 0m2.021s
sys 0m0.123s

defcon 2016 quals: baby-re

基本解法

该程序通过13次scanf输入13个4字节整数,然后调用CheckSolution函数进行校验,此函数体积非常大。下面直接hook掉程序中的printffflush函数让其直接返回,然后hook掉scanf函数,让其读取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
import angr
import sys

class MyHook(angr.SimProcedure):
def run(self, fmt, n):
simfd = self.state.posix.get_fd(sys.stdin.fileno())
data, ret_size = simfd.read_data(4)
self.state.memory.store(n, data)
return 1

proj = angr.Project('baby-re', auto_load_libs=False)
state = proj.factory.blank_state(addr=0x04025E7)

# hook
proj.hook_symbol('printf', angr.SIM_PROCEDURES['stubs']['ReturnUnconstrained'](), replace=True)
proj.hook_symbol('fflush', angr.SIM_PROCEDURES['stubs']['ReturnUnconstrained'](), replace=True)
proj.hook_symbol('__isoc99_scanf', MyHook(), replace=True)

# setting state
simgr = proj.factory.simulation_manager(state)

simgr.explore(find=0x04028E9, avoid=0x0402941)
if simgr.found:
print(simgr.found[0].posix.dumps(sys.stdin.fileno()))

s = simgr.found[0].posix.dumps(sys.stdin.fileno())
d = []
for i in range(0, 13):
d.append(int.from_bytes(s[i * 4 : i * 4 + 4], byteorder='little'))
print(d)

运行结果及时间如下:

1
2
3
4
5
6
7
pwn@ubuntu:~/workspace$ time python3 exp.py 
b'M\x00\x00\x00a\x00\x00\x00t\x00\x00\x00h\x00\x00\x00 \x00\x00\x00i\x00\x00\x00s\x00\x00\x00 \x00\x00\x00h\x00\x00\x00a\x00\x00\x00r\x00\x00\x00d\x00\x00\x00!\x00\x00\x00'
[77, 97, 116, 104, 32, 105, 115, 32, 104, 97, 114, 100, 33]

real 0m49.306s
user 0m49.011s
sys 0m0.296s

优化解法

  1. simgr开启LAZY_SOLVES选项,该选项可不在运行时实时检查当前条件能否到达目标位置。虽然这样无法规避一些无解的情况,但可以显著提高效率

    1
    simgr.one_active.options.add(angr.options.LAZY_SOLVES)
  2. 使用claripy模块模拟输入,claripy.BVS可创建符号变量,第一个参数为符号变量名,第二个参数为变量位数

  3. 在使用符号变量后,不能通过posix.dump来获取标准输入,而应该获取符号变量的值,simgr.one_found.solver.eval函数可以获取符号变量的值

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


class MyHook(angr.SimProcedure):
def run(self, fmt, n):
simfd = self.state.posix.get_fd(sys.stdin.fileno())
data, ret_size = simfd.read_data(4)
self.state.memory.store(n, data)
return 1

proj = angr.Project('baby-re', auto_load_libs=False)
state = proj.factory.blank_state(addr=0x04028E0)

# input,创建13个符号变量以模拟输入
flag_input = [claripy.BVS('flag_%d' % i, 32) for i in range(13)]

# 将符号变量的值放入栈中(方便),并在调用CheckSolution函数时传参(rdi指向栈顶)
for i in range(13):
state.mem[state.regs.rsp + i * 4].dword = flag_input[i]
state.regs.rdi = state.regs.rsp

# setting state
simgr = proj.factory.simulation_manager(state)
simgr.one_active.options.add(angr.options.LAZY_SOLVES)

simgr.explore(find=0x04028E9, avoid=0x0402941)
# 通过simgr.one_found.solver.eval获取符号变量的值
flag = ''.join(chr(simgr.one_found.solver.eval(c)) for c in flag_input)
print(flag)

运行结果及时间如下:

1
2
3
4
5
6
pwn@ubuntu:~/workspace$ time python3 exp.py 
Math is hard!

real 0m8.981s
user 0m8.738s
sys 0m0.244s
打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021 lzeroyuee
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信