前言
这次我研究了一下angr的输入和输出,用几个例子作为示范,更深入的理解angr。
题目三
这次的程序和题目一是一样的。但是这次我尝试将success
输出出来。
首先我们需要更改find的地址。
# find=0x4005AF
for pp in res.found:
print pp.posix.dumps(0)
print "-----"
print pp.posix.dumps(1)
可以看到dumps(1)没有输出
#find=0x4005B4
这次成功的输出了success字符,由此可见。
res.found[0].posix.dumps(0)
代表该状态执行路径的所有输入
res.found[0].posix.dumps(1)
代表该状态执行路径的所有输出
这一点我会另写一篇文章进行补充
res.found[1]代表找到的第二种可以到达目的路径的state(通常很少出现)。OK!
那么我们还有没有其它方式可以输出success字符串呢,当然是可以的。
从程序的指令执行来看在0x4005AA处将s字符串赋值给了edi寄存器,那么当这条指令结束完成后,我们可以通过获取edi的值从而获取s字符串。
因此这时的约束条件为find=0x4005AF
,通过查阅官方文档可以知道获取寄存器值的方式。
addr=pp.memory.load(pp.regs.edi,endness='Iend_LE')
print addr
可以看到此时edi的值为0x73736563637573 这就是success的倒序,为什么会倒过来呢?因为我在加载的时候使用了endness选项。
官方的例子设置endness时使用的是s.memory.load(0x4000, 4, endness=archinfo.Endness.LE)
archinfo模块,我建议还是按着官方文档来,通过翻阅archinfo的文档可以知道arch info.Endness可以有三个值。
Variables:
LE – little endian, least significant byte is stored at lowest address
BE – big endian, most significant byte is stored at lowest address
ME – Middle-endian. Yep.
因为这里是直接将success的值附给了edi,因此使用大端显示,然后通过pp.solver.eval(addr,cast_to=str)
输出为字符串。
github上给出的内存存储和加载的例子可以看一下。
>>> s = proj.factory.blank_state()
>>> s.memory.store(0x4000, s.solver.BVV(0x0123456789abcdef0123456789abcdef, 128))
>>> s.memory.load(0x4004, 6) # load-size is in bytes
<BV48 0x89abcdef0123>
好!接下来的问题是如果程序使用过命令行进行输入,而且在赋值的时候,给的是字符串的地址那么这时该如何取出这些数据。这就需要我们修改下源代码。
#include<stdio.h>
#include<stdlib.h>
void success(){
printf("success\n");
}
void failed(){
printf("failed\n");
}
int main(int argc ,char *argv[]){
if(argc<2){
printf("please input eg");
exit(0);
}
char *word="jsk";
if(!strcmp(argv[1],word)){
success();
}else{
failed();
}
return 0;
}
这里通过命令行参数获取输入,这时如果复用之前的脚本还能够跑出结果么?
可以看到最终只有一个avoid的路径,那是因为我们没有构造命令行参数,也就没有输入,那么肯定就会执行exit
这条路径,所以我们需要使用claripy
模块构造输入。
github上给的说明是
It is usable!
General usage is similar to z3:
哈哈哈。可惜我不懂z3,可能也是个约束求解器。233333,这个文档比较好。
rax_start = claripy.BVS('rax_start', 8*8)
创建一个8字节的参数,名字为rax_start。这里可以参考angr学习【二】
pp.solver.eval(arg1,cast_to=str)
可以看一下eval都有哪些参数,经测试cast_to
只支持int
和str
类型
最终打印出正确的输入。
在测试的过程中我发现pp.posix.files[0]
是一个SimFile类型。
可以看到他有read方法,可以读取文件当前状态的数据。
当然我们既然已经学过利用内存来进行数据的存取,同样的也可以每次将我们的输入存放到指定内存中,当我们寻找到了指定路径时在从该内存中获取我们的输入。
mem_arg=state.memory.store(0x1000,arg1)
addr=pp.memory.load(0x1000,endness=archinfo.Endness.BE)
print pp.solver.eval(addr,cast_to=str)
好这时我已经成功获取了命令行的输入,接下来尝试获取程序的输出。
基础的步骤我就不做了23333
这里我通过寄存器来获取succcess字符串。定位到指定地址。首先输出一下edi看下。
emmmm,好像没有达到我想要测试的目的丫…23333.算了不改了。
其实我想说的是,memory.load用来加载指定内存中的数据,如果是指针,那么需要进行两次load,注意大端小端的问题,最后使用solver.eval进行转化。
脚本
附上完整代码(find的地址自己去改):
# -*- coding:utf-8 -*-
from angr import *
import logging,claripy,archinfo
logging.getLogger('angr.manager').setLevel(logging.DEBUG)
p = Project("test3",auto_load_libs=False)
arg1=claripy.BVS("arg1",16*8)
args=[p.filename,arg1]
state=p.factory.entry_state(args=args)
mem_arg=state.memory.store(0x1000,arg1)
sm=p.factory.simulation_manager(state,threads=4)
find=0x4005CF
avoid=[0x4005FD,0x400648]
res=sm.explore(find=find,avoid=avoid)
print res
for pp in res.found:
addr=pp.memory.load(0x1000,endness=archinfo.Endness.BE)
print pp.solver.eval(addr,cast_to=str)
print "---"
print pp.posix.dumps(0)
print pp.posix.dumps(1)
print "---"
print pp.solver.eval(arg1,cast_to=str)
print "---"
edi_addr=pp.memory.load(pp.regs.edi,endness=archinfo.Endness.BE)
print pp.solver.eval(edi_addr)
总结
写了挺多,例子很简单,自己也更深入的理解了angr的输入输出。接下来就需要刷一刷题了,把angr-doc/example中的例子都给看一遍。