angr学习【三】

前言

这次我研究了一下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只支持intstr类型
这里写图片描述
最终打印出正确的输入。
这里写图片描述
在测试的过程中我发现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中的例子都给看一遍。

猜你喜欢

转载自blog.csdn.net/qq_33438733/article/details/80315245
今日推荐