Angr_ctf:1~13练习
文章目录
- Angr_ctf:1~13练习
#、angr 入门
angr,符号执行,利用python开发的二进制程序分析框架。
linux安装
安装依赖库
sudo apt-get install python-dev libffi-dev build-essential virtualenvwrapper
python3安装angr
pip install angr
然后再在python控制台导入库就说明安装完成。
>>> import angr
然后下面就先进行Angr_Ctf练习,来熟悉angr
跟着B站up的视频做的:angr符号执行练习,他每个练习出了一个视频,讲的真的很清楚。
angr_ctf地址:https://github.com/jakespringer/angr_ctf
所有代码均是python3,两个环境
win10-vscode-python3.8
ubuntu-puthon3.8
00_angr_find
(1)总结
第0题完全是为了让我们熟悉angr的用法,给出了最简单的样例,类似模板。
(2)练习
第00道题比较简单,程序本身也不复杂,有明显的分支。
angr符号执行时我们可以直接忽略算法,只关注分支结果。
写angr求解。
#win10环境-vscode-python3
import angr
p = angr.Project("D:\\user\\Desktop\\2021.Study\\8.19-python_angr\\dist\\00\\00_angr_find")#导入程序
init_state = p.factory.entry_state()#相当于获取程序的入口点
sm = p.factory.simulation_manager(init_state)#从程序的入口点开始符号执行
print(sm.explore(find=0x08048678))#探索,寻找这个地址,结果打印出来,一个类,显示有几个解。
if sm.found:
found_state = sm.found[0]#结果存在这个数组里,一般唯一解,所以下标0
print(found_state.posix.dumps(0))#0代表输入 即解,1代表输出
else:
print("Could not find the solution")
获得解:JXWVXRKX
测试下
正确!
01_angr_avoid
(1)总结
第01道题主要是熟悉在sm.explore()
这个方法里,除了有find
表示我们要探索的地址外,还有avoid
表示我们要避免的地址。可以有效提高angr的效率。
(2)练习
IDA打开,如果f5的话,要反编译很久也没有成功,
所以直接看看汇编(因为不是很难。。。),其实也不是,只需要IDActrl+f12
查找字符串,找到结尾的表示正确字符串和表示错误字符串,然后再按X键查看交叉引用,就直接过去了。。。
所以sm.explore(find=0x080485e0,avoid=0x080485F2)
,多了一个avoid,可以适当提高效率。
下面直接给出完成脚本。
#win10环境-vscode-python3
import angr
p = angr.Project("D:\\user\\Desktop\\2021.Study\\8.19-python_angr\\dist\\01\\01_angr_avoid")#导入程序
init_state = p.factory.entry_state()#相当于获取程序的入口点
sm = p.factory.simulation_manager(init_state)#从程序的入口点开始符号执行
print(sm.explore(find=0x080485e0,avoid=0x080485F2))#探索,寻找这个地址,结果打印出来,一个类,显示有几个解。
if sm.found:
found_state = sm.found[0]#结果存在这个数组里,一般唯一解,所以下标0
print(found_state.posix.dumps(0))#0代表输入 即解,1代表输出
else:
print("Could not find the solution")
这里是在ubuntu18的python3里面测试的。
然后就打印正确结果即可。
’HUJOZMYS’
然后再在原题测试一下:
成功!
02_angr_condition
(1)总结
第2道题也不难,主要是让我们熟悉另一种写法。
顾名思义condition
是条件、情况的意思,第2题就是给出了条件、情况的概念。
即:我们在sm.explore(find=***,avoid=***)
的时候,其中***
可以是一个情况,比如输出情况(结尾输出的字符串)。
好了,具体怎么操作,下面来练习这道题。
(2)练习
IDA打开,f5,结构比较简单,注意结果输出。
给出新得,condition的写法:
def is_good(state):
return b'Good Job.' in state.posix.dumps(1)
def is_bad(state):
return b'Try again.'in state.posix.dumps(1)
sm.explore(find=is_good,avoid=is_bad)
所以,写出完整对应脚本,(导入sys,通过命令行传路径参数)
import angr
import sys
def main(argv):
bin_path = argv[1]#传入命令行参数
print(bin_path)
p = angr.Project(bin_path)
init_state = p.factory.entry_state()
sm = p.factory.simulation_manager(init_state)
def is_good(state):
return b'Good Job.' in state.posix.dumps(1)
def is_bad(state):
return b'Try again.'in state.posix.dumps(1)
sm.explore(find=is_good,avoid=is_bad)
if sm.found:
found_state = sm.found[0]
print('Solution:{}'.format(found_state.posix.dumps(0)))
if __name__ == '__main__':
main(sys.argv)#传入命令行参数
执行结果:
测试一下:
成功!
03_angr_symbolic_registers
(1)总结
第3题给出的样例,输入是分开的,也就是说angr可以分开输入,同时同个输入,通过符号向量库claripy
,固定变量的位数(类似z3
的向量求解)。
同时,由于angr是基于unicorn开发(Unicorn可以执行任意一段代码数据),可以将符号向量直接传入angr给定的寄存器如init_state.regs.eax
。即可以不用从entry_state()
开始执行,而是通过blank_state(addr=start_addr)
方法,从任意位置start_addr
开始执行。然后angr符号执行完成后,可以直接查看寄存器取出输入结果。
具体怎么操作,下面来练习第3题。
(2)练习
IDA打开,找到输出结果condition,以及关注输入
进入输入函数,发现要分开一次输入三个16进制数
查看这部分的汇编,
根据调用约定理清思路,函数参数是从右往左一次压入堆栈的,所以看一看出,寄存器eax、ebx、edx依次存放的是参数1、参数2、参数3(从左往右1-2-3)。
所以,来到主函数
分析完之后就可以写angr脚本了,(我给了注释)
import angr
import sys
import claripy#新建符号向量
def main(argv):
bin_path = argv[1]
#D:\\user\\Desktop\\2021.Study\\8.19-python_angr\\dist\\03\\03_angr_symbolic_registers
p = angr.Project(bin_path)
start_addr=0x08048980#自定义程序起始地址
init_state = p.factory.blank_state(addr=start_addr)#从自定义的起始地址开始启动
pass1 = claripy.BVS('pass1',32)#新建一个32位的符号向量,对应题目中寄存器长度32位
pass2 = claripy.BVS('pass2',32)
pass3 = claripy.BVS('pass3',32)
init_state.regs.eax = pass1#初始化寄存器,类似z3
init_state.regs.ebx = pass2
init_state.regs.edx = pass3
sm = p.factory.simulation_manager(init_state)#启动仿真模拟
#定义condition
def is_good(state):
return b'Good Job.' in state.posix.dumps(1)
def is_bad(state):
return b'Try again.' in state.posix.dumps(1)
sm.explore(find=is_good,avoid=is_bad)#探索
if sm.found:
found_state = sm.found[0]
password1 = found_state.solver.eval(pass1)#类似z3求解,求解对应单个值
password2 = found_state.solver.eval(pass2)
password3 = found_state.solver.eval(pass3)
print("Solution:{:x} {:x} {:x}".format(password1,password2,password3))
else:
print('No solution found!')
if __name__ == '__main__':
main(sys.argv)
通过命令行带地址参数执行
结果带入原题测试一下
成功!
04_angr_symbolic_stack
(1)总结
第04题给出的样例也是输入也是分开的,与03题不同的是,04题输入的变量,存储在局部堆栈,所以04题练习的就是利用angr,例如:init_state.stack_push(init_state.regs.ebp)
,来构造堆栈,并且是模拟题目中的堆栈分布,当然,也会创建符号向量。
具体怎么操作,下面来练习第04题。
(2)练习
IDA打开,梳理一下结构,发现要输入的变量,存储在堆栈,并且每次使用都在堆栈取值。
这时,就不能单纯利用寄存器求值了。
所以这里可以利用angr构造堆栈,然后将符号向量压入堆栈,就可以完全模拟题目中的堆栈分布结构。
分析完之后,就可以写angr脚本了
import angr
import sys
def main(argv):
bin_path = argv[1]
p = angr.Project(bin_path)
start_addr = 0x08048697
init_state = p.factory.blank_state(addr=start_addr)
#初始化堆栈
init_state.stack_push(init_state.regs.ebp)
init_state.regs.esp = init_state.regs.ebp
#模拟题目中的堆栈空间,两个两个变量分别是[ebp-0xc]、[ebp-0x10]
#所以[ebp-4]、[ebp-8]这两个位置其实是我们不用的
padding_size = 8
init_state.regs.esp -= padding_size
#创建符号向量
pass1 = init_state.solver.BVS('pass1',32)
pass2 = init_state.solver.BVS('pass2',32)
#将符号向量压入堆栈,
#也是模拟题目中的堆栈空间,两个两个变量分别是[ebp-0xc]、[ebp-0x10]
init_state.stack_push(pass1)
init_state.stack_push(pass2)
#构造完成,开始启动仿真
#simgr()方法其实可以看作等价于simulation_manager()方法,只是简洁便于写
sm = p.factory.simgr(init_state)
def is_good(state):
return b'Good Job.' in state.posix.dumps(1)
def is_bad(state):
return b'Try again.' in state.posix.dumps(1)
sm.explore(find=is_good,avoid=is_bad)
if sm.found:
found_state = sm.found[0]
#求解符号向量
password1 = found_state.solver.eval(pass1)
password2 = found_state.solver.eval(pass2)
print('Solution:{} {}'.format(password1,password2))
else:
print('Solution not found!')
if __name__ == '__main__':
main(sys.argv)
然后在命令行带参数执行。
最后,测试一下结果
正确!
05_angr_symbolic_memory
(1)总结
第05题是让输入四个8位字符串,变量既不是存储与寄存器,也不是堆栈,直接是在内存中。
所以05题给出的知识点就是利用angr通过init_state.memory.store(addr,p)
将符号向量p引入到对应地址aadr中!
(2)练习
IDA打开,查看主结构,让分别输入4个8字符的字符串,并且分别存储与不同的内存。
同时,启动地址也是跳过scanf
下面就可以写对应的angr脚本了
import angr
import sys
def main(argv):
bin_path = argv[1]
p = angr.Project(bin_path)
start_addr = 0x08048601
init_state = p.factory.blank_state(addr=start_addr)
#创建符号向量
#题目中输入8个字符,对应长度64位
p1 = init_state.solver.BVS('p1',64)
p2 = init_state.solver.BVS('p2',64)
p3 = init_state.solver.BVS('p3',64)
p4 = init_state.solver.BVS('p4',64)
#关键:
# 将符号向量引入到内存中
p1_addr = 0x0A1BA1C0
p2_addr = 0x0A1BA1C8
p3_addr = 0x0A1BA1D0
p4_addr = 0x0A1BA1D8
init_state.memory.store(p1_addr,p1)
init_state.memory.store(p2_addr,p2)
init_state.memory.store(p3_addr,p3)
init_state.memory.store(p4_addr,p4)
#启动仿真模拟
sm = p.factory.simulation_manager(init_state)
def is_good(state):
return b'Good Job.' in state.posix.dumps(1)
def is_bad(state):
return b'Try again.' in state.posix.dumps(1)
sm.explore(find=is_good,avoid=is_bad)
if sm.found:
found_state = sm.found[0]
#将符号向量从内存中取出,cast_to为bytes型
pass1 = found_state.solver.eval(p1,cast_to=bytes).decode('utf-8')
pass2 = found_state.solver.eval(p2,cast_to=bytes).decode('utf-8')
pass3 = found_state.solver.eval(p3,cast_to=bytes).decode('utf-8')
pass4 = found_state.solver.eval(p4,cast_to=bytes).decode('utf-8')
print("Solution:{} {} {} {}".format(pass1,pass2,pass3,pass4))
else:
raise Exception("Solution not found")
if __name__ == '__main__':
main(sys.argv)
命令行带参数执行。
最后测试一下
成功!!
06_angr_symbolic_dynamic_memory
(1)总结
第06题的是让分别输入两个8字符的字符串,与前面题不同的是,06题是通过指向堆区的指针来存储输入的数据,既不是寄存器、堆栈,也不是固定的内存,因为是通过malloc
申请的堆区内存空间,具有随机性。
所以06题引入的知识点就是通过我们自己在内存中挑一块固定的内存,用来充当malloc
申请的堆区空间,然后把这块内存空间的地址付给题目中的buffer0
、buffer1
指针,让这两个指针指向我们自己挑的固定的地址,再把创建的符号向量引入这一块固定的地址,然后等待模拟仿真结束之后,再把解出符号向量的值,即可!
(2)练习
IDA打开,查看整体结构,是让我们分别输入两个8字符的字符串,是通过指向堆区的指针来存储输入的数据,既不是寄存器、堆栈,也不是固定的内存,因为是通过malloc
申请的堆区内存空间,具有随机性。
就像偷梁换柱一样。
下面,就写angr脚本,进一步理解。
import angr
import sys
def main(argv):
bin_path = argv[1]
p = angr.Project(bin_path)
start_addr = 0x08048699
init_state = p.factory.blank_state(addr=start_addr)
#我们自己申请的地址,用于代替题目中malloc的堆区空间
#(为了防止这个地址被命中,我们直接往大了写。。。。)(具体我也不是很透彻。。。)
print(init_state.regs.esp)#0x7fff0000
buffer0 = init_state.regs.esp-0x100
buffer1 = init_state.regs.esp-0x200
#题目中存储堆区地址的指针buffer0、buffer1的地址
buffer0_addr = 0x0ABCC8A4
buffer1_addr = 0x0ABCC8AC
#把参数二的值写到参数一对应的地址,第三个参数是指定小端存储
init_state.memory.store(buffer0_addr,buffer0,endness=p.arch.memory_endness)
init_state.memory.store(buffer1_addr,buffer1,endness=p.arch.memory_endness)
#创建符号向量
p1 = init_state.solver.BVS('p1',8*8)
p2 = init_state.solver.BVS('p2',8*8)
#把符号向量引入到我们自己“申请”的空间
init_state.memory.store(buffer0,p1)
init_state.memory.store(buffer1,p2)
#启动仿真模拟
sm = p.factory.simgr(init_state)
def is_good(state):
return b'Good Job.' in state.posix.dumps(1)
def is_bad(state):
return b'Try again.' in state.posix.dumps(1)
sm.explore(find=is_good,avoid=is_bad)
if sm.found:
found_state = sm.found[0]
pass1 = found_state.solver.eval(p1,cast_to=bytes).decode('utf-8')
pass2 = found_state.solver.eval(p2,cast_to=bytes).decode('utf-8')
print('Solution:{} {}'.format(pass1,pass2))
else:
raise Exception("Solution not found!")
if __name__ == '__main__':
main(sys.argv)
命令行带参数执行。
最后测试一下
成功!!
07_angr_symbolic_file
(1)总结
第07题只是让输入字符串,不过题目有文件读取,这道题给的知识点就是利用angr模拟文件读取,同样也是会有符号化向量,将其引入我们模拟读取的文件。
(2)练习
IDA打开,分析大致文件结构,发现程序让我们输入字符串buffer
,然后打开一个文件OJKSQYDP.txt
,将buffer
写入,再关闭文件OJKSQYDP.txt
。
我们要做的,就是模拟这个文件,然后创造符号向量代替我们输入的字符串信息,再将其写入到模拟的文件,即可实现前面这一部分代码的功能。
下面就是写利用angr模拟文件的代码
import angr
import sys
def main(argv):
bin_path = argv[1]
p = angr.Project(bin_path)
start_addr = 0x080488D9
init_state = p.factory.blank_state(addr=start_addr)
filename = "OJKSQYDP.txt"
file_size = 0x40 #字节
#创建符号化向量
password = init_state.solver.BVS("password",file_size*8)
#SimFile是构造文件信息,包括文件名,文件内容和文件大小,同时将创建的符号化向量引入文件,文件大小是按位算的
sim_file = angr.storage.SimFile(filename,content=password,size=file_size*8)
#angr.fs.insert是将文件插入到文件系统中,需要文件名与符号化的文件
init_state.fs.insert(filename,sim_file)
#启动模拟仿真
sm = p.factory.simgr(init_state)
def is_good(state):
return b'Good Job.' in state.posix.dumps(1)
def is_bad(state):
return b'Try again.' in state.posix.dumps(1)
sm.explore(find=is_good,avoid=is_bad)
if sm.found:
found_state = sm.found[0]
password = found_state.solver.eval(password,cast_to=bytes).decode('utf-8')
print("Solution:{}".format(password))
else:
raise Exception("Solution not found!")
if __name__ == '__main__':
main(sys.argv)
命令行带参数执行一下
测试一下:
成功!!
08_angr_constraints
(1)总结
第08题涉及路径爆炸的问题,路径爆炸大致是复杂度很高,angr实现起来太难了。比如在比较的时候,一个字符一个字符循环比较,比较路径过多,就会出现路径爆炸。
为了解决这个问题,angr给出的方法是避开循环比较,在比较之前停下来,添加约束,例如check_state.add_constraints(desired_string == check_bvs)
,最后通过约束求解,相当于z3约束求解。
(2)练习
IDA打开,就让输入16个字符,
查看下面11行的check函数
下面就写angr脚本感受一下!
import angr
import sys
def main(argv):
bin_path = argv[1]
p = angr.Project(bin_path)
start_addr = 0x08048625#这个启动的位置就是scanf之后
init_state = p.factory.blank_state(addr = start_addr)
#创建符号向量
buffer_addr = 0x0804A050#题目中buffer的地址
password = init_state.solver.BVS("password",16*8)#题目中可以看到循环16次说明16个字符
#将符号向量引入对应内存
init_state.memory.store(buffer_addr,password)
#启动仿真模拟
sm = p.factory.simgr(init_state)
#为了防止路径爆炸,就在check函数开始的地方停止,然后通过约束求解,避开循环比较
check_addr = 0x08048565#题目中check函数的开头
sm.explore(find=check_addr)
if sm.found:
check_state = sm.found[0]
#在内存中取出之前的符号向量
check_str_addr = buffer_addr
check_str_size = 0x10
check_bvs = check_state.memory.load(check_str_addr,check_str_size)
desired_string = "AUPDNNPROEZRJWKB"#用于比较的字符串,题目中的password
#添加约束
check_state.add_constraints(desired_string == check_bvs)
#求解约束的符号向量
password = check_state.solver.eval(password,cast_to=bytes).decode('utf-8')
print("Solution:{}".format(password))
else:
raise Exception("Solution not found!")
if __name__ == '__main__':
main(sys.argv)
命令行带参数执行一下
测试一下:
成功!!
09_angr_hooks
(1)总结
第09题是输入两个16字符,准确的说应该是先输入16字符然后check函数验证,再输入16字符再strcmp函数验证,check函数与08题的一一致。因为是分成两次输入所以不方便通过约束符号向量求解,所以第09题给出的方法是hook掉check函数,等待仿真模拟结束之后直接查看输入即可。
(2)练习
IDA打开,查看整体结构
hook的含义不必多说,
hook的过程是当程序执行到check函数时,我们自定义check机制,通过取出buffer对应地址的值,来比较check字符串。
下面就写脚本感受一下。
import angr
import sys
import claripy
def main(argv):
bin_path = argv[1]
p = angr.Project(bin_path)
init_state = p.factory.entry_state()#因为要连续输入两次,就直接从入口点开始
#利用angr来hook掉题目中的check函数,防止路径爆炸
check_addr = 0x080486B3#要hook的函数地址
check_skip_size = 5#字节数,一般call指令都是5个字节
#hook的部分 @代表“修饰器” 具体看这篇文章https://www.yiibai.com/python/decorator.html
@p.hook(check_addr,length=check_skip_size)
def check_hook(state): #hook题目中check函数的函数
user_input_addr = 0x0804A054#check函数的参数,即我们输入的字符串buffer的地址。
user_input_length = 0x10
#取出符号向量
user_input_bvs = state.memory.load(
user_input_addr,
user_input_length
)
#用于check的字符串
desired = "XYMKBKUHNIQYNQXE"
#因为函数的返回值存储去eax
#所以这里通过claripy.If(),
# 如果相等,则执行参数2,将eax置为1
# 如果不等,则执行参数3,将eax置为0
state.regs.eax = claripy.If(
desired == user_input_bvs,
claripy.BVV(1,32),
claripy.BVV(0,32)
)
#启动仿真模拟
sm = p.factory.simgr(init_state)
def is_good(state):
return b'Good Job.' in state.posix.dumps(1)
def is_bad(state):
return b'Try again.' in state.posix.dumps(1)
sm.explore(find=is_good,avoid=is_bad)
if sm.found:
found_state = sm.found[0]
print("Solution:{}".format(found_state.posix.dumps(0)))
else:
raise Exception("Solution not found!")
if __name__ == '__main__':
main(sys.argv)
命令行带参数执行
测试一下:
成功!
10_angr_simprocedures
(1)总结
第10题也是hook,是在09题的基础上延伸的,09题只hook一个地址,但当一个check函数多次重复使用的时候,每个地址都hook,那工程量就比较大,第10题提供了以函数名hook的hook方式,可以有效解决一个check函数多次重复使用的情形。
(2)练习
IDA打开,f5反编译出来结构和09题差不多,一样的check函数,(反编译出来只有一个?)
但查看其流程图,发现竟然有很多个相同的check函数被重复调用(可能IDA没有成功f5反编译出来)
所以,如果以函数名来hook,那么效率就会高很多。
下面写脚本感受一下
import angr
import sys
import claripy
def main(argv):
bin_path = argv[1]
p = angr.Project(bin_path)
init_state = p.factory.entry_state()
class mySimPro(angr.SimProcedure):
#第一个参数self固定,后续参数模拟题目中要hook的函数拥有的参数。
#check_equals_ORSDDWXHZURJRBDH((int)s, 0x10u)
#slef包含state成员
def run(self,user_input_addr,user_input_length):
angr_bvs = self.state.memory.load(
user_input_addr,
user_input_length
)
desired_string = "ORSDDWXHZURJRBDH"
return claripy.If(
desired_string == angr_bvs,
claripy.BVV(1,32),
claripy.BVV(0,32)
)
#以函数名hook函数
check_symbol = "check_equals_ORSDDWXHZURJRBDH"
#第二参数mySimPro是个类,继承angr.SimProcedures,提供的run方法就是我们用于hook的替代函数,
p.hook_symbol(check_symbol,mySimPro())
sm = p.factory.simgr(init_state)
def is_good(state):
return b'Good Job.' in state.posix.dumps(1)
def is_bad(state):
return b'Try again.' in state.posix.dumps(1)
sm.explore(find=is_good,avoid=is_bad)
if sm.found:
found_state = sm.found[0]
print("Solution:{}".format(found_state.posix.dumps(0).decode('utf-8')))
else:
raise Exception("Solution not found!")
if __name__ == '__main__':
main(sys.argv)
命令行带参数执行一下
测试一下:
成功!
11_angr_sim_scanf
(1)总结
(2)练习
12_angr_veritesting
(1)总结
(2)练习
13_angr_static_binary
(1)总结
(2)练习
Pro是个类,继承angr.SimProcedures,提供的run方法就是我们用于hook的替代函数,
p.hook_symbol(check_symbol,mySimPro())
sm = p.factory.simgr(init_state)
def is_good(state):
return b'Good Job.' in state.posix.dumps(1)
def is_bad(state):
return b'Try again.' in state.posix.dumps(1)
sm.explore(find=is_good,avoid=is_bad)
if sm.found:
found_state = sm.found[0]
print("Solution:{}".format(found_state.posix.dumps(0).decode('utf-8')))
else:
raise Exception("Solution not found!")
if name == ‘main’:
main(sys.argv)
命令行带参数执行一下
[外链图片转存中...(img-H5VNNn7C-1635175824885)]
测试一下:
[外链图片转存中...(img-eampEc7F-1635175824885)]
成功!
## 11_angr_sim_scanf
### (1)总结
### (2)练习
## 12_angr_veritesting
### (1)总结
### (2)练习
## 13_angr_static_binary
### (1)总结
### (2)练习