符号执行angr

前言

记:又一个逆向神器——angr,详情参考官方文档,这里面的example是ctf的一些题例

然后这里是一些angr解题的题库,我还没做过,以后慢慢练习

一个简单的demo,应该能大致了解angr

一些example

我自己也写了一个简单的demo,程序是这样的:

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
#include<stdio.h>
#include<string.h>
void success(){
printf("success\n");
}
void failed(){
printf("failed\n");
}
void change(char name[],int i){
if (name[i]=='l') name[i]='1' ;
else name[i]='2';
}
int main(void){
char name[10];
int i;
printf("pls enter the name:");
scanf("%s",name);
change(name,2);
change(name,3);
change(name,8);
if(!strcmp(name,"he11owor1d")){
success();
}else{
failed();
}
return 0;
}

利用angr来跑,目的就是要让其执行到success

1
2
3
4
5
6
7
8
9
10
11
import angr
p= angr.Project(r'\demo.exe',auto_load_libs=False) # 载入二进制文件,加上auto_load_libs=False 可以避免加载依赖库,减少分析工作量
state=p.factory.entry_state() # 设置执行的开始地址
# .entry_state() 返回程序入口的state
# .blank_state(addr=...) 返回一个未初始化的state,即未进行任何操作的addr处
# full_init_state(**kwargs) 与entry_state()类似,不过会调用每个库的初始化函数
s=p.factory.simulation_manager(state) #以该state建立一个模拟执行
s.explore(find=0x40154a,avoid=0x40154b) #find要到达的地址,avoid不去的地址
print(len(s.found)) # 可解路径的数量
print(s.found[0].posix.dumps(1)) # 到达第一个解的路径的所有标准输出
print(s.found[0].posix.dumps(0)) # 到达第一个解的所有标准输入

又一个example——DEFCON 2016 quals baby-re

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import claripy
import angr
p=angr.Project(r'.\baby-re',auto_load_libs=False)
state=p.factory.blank_state(addr=0x4028e0)
flag_chars=[claripy.BVS('flag_%d'%i,32) for i in range(13)]
# 用claripy.BVS创建符号变量,类似z3的BitVec,第一个参数是变量名,第二个参数是位宽
for i in range(13):
state.mem[state.regs.rsp+i*4].dword = flag_chars[i]

state.regs.rdi = state.regs.rsp #因为下面所设置的入口处之前rdi是存放所读入flag的地方,so把手动设置的输入赋给rdi
s=p.factory.simulation_manager(state)
s.one_active.options.add(angr.options.LAZY_SOLVES)
# 这个可以加快执行速度,但有时会因此报错
s.explore(find=0x40293f,aviod=0x402941)

if len(s.found)>0:
print('get!')
flag=''.join(chr(s.found[0].solver.eval(c)) for c in flag_chars)
print(flag)
else: print('Oh no')

angr的一些知识点

记得可能不全,可用性也不强,日后熟悉了再慢慢更新

hook

hook_symbol:angr提供hook一些库函数得接口,从而实现其功能,在angr/procedures中可以看到这些库

例:

1
2
3
4
5
6
7
8
9
10
class my_scanf(angr.SimProcedure):
def run(self, fmt, ptr):
self.state.mem[ptr].dword = flag_chars[self.state.globals['scanf_count']]
self.state.globals['scanf_count'] += 1

proj.hook_symbol('__isoc99_scanf', my_scanf(), replace=True)

#################又或者是######################

proj.hook_symbol('printf',angr.SIM_PROCEDURES['stubs']['ReturnUnconstrained'](),replace=True)

hook(addr=xx,hook=hook_demo,length=xx) :程序自定义的函数没法直接hook,但是可以hook调用这个函数的地址,然后以自己的逻辑写一个hook_demo,length是hook_demo后要跳过的指令的长度

例:

1
2
3
4
5
6
def my_change(state):
print('change hooked')
state.memory.rax=1
return

proj.hook(0x4015cb,hook=my_change,length=5)

ps:hook的是一个state,自己实现hook_demo中的一切操作都是基于传递过来的state

find 和 avoid

关于expolor里的find和avoid,都是可以是list类型,也就是可以批量avoid(一般不会批量find吧..)

而批量avoid的地址则可以通过idapython的提取出来,这个关于idapython,就迟些学了再记

还有一个比较好用的find的方法:直接以标准输出中的关键字符串为目标去find

就像这样s.explore(find=lambda st: b"Congrats" in st.posix.dumps(1))

pie保护

pie保护的程序,每次运行时的地址都会有变化,就像这样
1

在angr中,程序的基址是固定在0x400000处的

所以在相关地址提取的时候加上0x400000即可

PS:但是ida加载的时候,每次动调之后地址都会变,所以只能参考第一次刚加载进ida时的地址

Comments