Surveyor
作为符号执行的引擎,它跟踪可执行的路径,标志出向前进的路径和倒退的路径,并且优化资源配置.
surveyor类并不直接使用,一般会被开发者子类化后来用作他们自己的分析。
最常见的符号分析(类似探索从A达到B,并避开C)已经在Explore类中实现。
Explorer
surveyors.Explorer 是surveyor的子类,用来执行符号搜索。
它可以被指定从哪儿开始,执行到哪儿,避开哪儿,和跟踪哪条路径。
它也会尝试避免死循环。
import angr b= angr.Project('.../../...')
一个surveyor对象在程序的入口开始执行,通过“Project.initial_exit”来退出。
它通过使用“Project.initial_state”来创建一个状态块。
经常使用的SimExit,创建了一个自定义状态块,能够提供“start”的可选参数,或者“starts”的可选参数列表。
e=b.surveyors.Explorer()
现在我们可以运行几步了,打印一个Explorer对象来知道现在有多少条可行路径。
print e.step()
你可以使用'Explorer.run'来运行一段时间。
e.run(10)
不指定时间时,explorer对象一直运行知道跑完所有路径(但对于大部分程序来说不是这样的)。在本例子中,我们不会遇到,程序时并不无限循环。
e.run()
我们可以知道哪条路径可通,哪条是死路(不提供可行退出),哪条出错。但在一些实例中,一条给定路径存在多种情况(即出错又无法退出)。
print "%d paths are still running" % len(e.active) print "%d paths are backgrounded due to lack of resources" % len(e.spilled) print "%d paths are suspended due to user action" % len(e.suspended) print "%d paths had errors" % len(e.errored) print "%d paths deadended" % len(e.deadended)
到目前为止,所有我们讨论的都适用于所有的Surveyors。但是,更棒的是一个Explorer对象可以被告知要寻找时,或避免去某个区块。
例子:
创建一个Explorer对象尝试去找到0x4006ed,并避开0x4006fd和0x4006aa。
e=p.surveyors.Explorer(find=(0x4006ed,),avoid = (x04006aa,0x4006fd))
e.run()
打印我们发现的后门和避开了多少条路径
if len(e.found) >0: print "found backdoor path:",e.found[0] print "Avoided %d paths" % len(e.avoided)
Caller
Caller使被调函数更简单得计算出程序到底做了些什么
加载程序:
b = angr.Project('../../..')
初始化state,并得到我们的username和password 的符号表达式为了之后的检查。这里,我们多伪造一位因为我们知道username和password都是8字节。
p=b.factory.path() username= p.state.memory.load(0x1000,9) password = p.state.memory.load(0x2000,9)
调用关键函数,把username和password的指针传入
c=b.surveyors.Caller(0x400664,(0x1000,0x2000),start=p)
查看不同路径的返回
print tuple(c.iter_returns())
其中返回1的路径
print tuple(c.iter_returns(solution=1))
现在我们能看到要到达目标点所需要的username和password。
'c.map_se' 调用 state.se.any_n_str(或者所提供的任意其他函数)
credentials = username.concat(password) tuple(c.map_se('any_n_str',credentials,10,solution =1))
Inerrupting Surveyors
Surveyor对象保存它的内核状态在每个tick之后.在IPython中,你应该能够暂停一个 surveyor用Ctrl-C,然后检查到目前为止有什么结果,但这是一个不好的做法。有两个简洁的官方方式:SIGUSR1和SIGUSR2。
如果你发送SIGUSR1到运行surveyor的python进程,它会导致主循环 Surveyor.run()在当前Surveyor.step()结束时终止。然后可以分析结果。要继续运行surveyor,要调用angr.surveyor.resume_analyses()(清除 “signalled”标志),然后调用surveyor的run()函数。因为SIGUSR1会导致run()返回,这在脚本化分析中很少有用,因为程序的其余部分将在run()之后运行, 返回。相反,SIGUSR1意味着提供一个干净的替代Ctrl-C。 另一方面,将SIGUSR2发送到python进程会导致run()调用ipdb断点 后每个步骤()。这允许您调试,然后继续您的程序。确保运行angr.surveyor.disable_singlestep(),然后继续清除“信号”标志。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
path_group
path_group允许我们监视多条路径以一个确切的方式。路径被设置为“stashes”,我们可以控制前进、步过、合并,随意移动。
有不同种类的“stashes”。
Paths:
我们可以以不同的规律来跑不同的stash,并使其一起汇合。
import angr p=angr.Project('...',load_option={'auto_load_lib':false}) pg=p.factory.path_group()
当有可行路径时
while len(pg.active) > 0: pg.step() print (pg)
当我我们遇到死路后,我们怎么做?
path = pg.deadended[0] Print('path length: {0} steps'.format(path.length))
得到路径步骤:
Print('Trace:') For step in path.trace: Print(step)
得到路径中的限制:
print ('There are %d constraints.' % len(path.state.se.constraints))
得到在结束时的内存状态
print ('rax:{0}'.format (path.state.regs.rax)) assert path.state.se.any_int(path.state.regs.rip)==path.addr
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Pathgroup.Explorer
pathgroup被认为会取代 surveyors.Explorer,它更灵活更有效。
当path_group.Explorer运行中得到find区块,多条路径将会运行步进直到发现我们所要寻找的地址。路径到达avoided地址,如果发生,将会被记录在avoided stash中。如果一个行进的的路径到达一个我们感兴趣的地址,它会被stash进found stash,并且其他路径继续行进。我们可以研究这个found 路径,或者舍弃它让其他路径继续。
载入二进制文件
p=angr.Project('../../..')
我们创建一个path group
pg=p.factory.path_group()
现在,我们符号化执行直到发现一条路径符合我们的条件
pg.explore(find=lambda p:"Congrats" in p.state.posix.dumps(1))
现在,我们从状态块中得到了flag
s=pg.found[0].state print s.posix.dumps(1) flag=s.posix.dumps(0) print (flag)
在path_group执行时,路径会被归为不同类型的stash
stash | 描述 |
active | 包含了默认执行的路径(除非一个替换的stash被指定给path_group.step()) |
deadended | 一条进入deadended stash的路径,是指它因为一些原因而无法继续执行,包括没有有效指令、无效的指令指针、unsat状态的后续。 |
found | 一条到达found stash 的路径,是指它满足path_group.explore的find节所给出的条件 |
avoided | 是指它满足path_group.explore的avoid节所给出的条件 |
pruned | 当使用LAZY_SOLVES时,路径不会被检查是否会满足,除非它很重要。当state未指定LAZY_SOLVES时,路径一旦状态变为unsat后,层次就被划分了。所有从这个点派生的路径会被删除并把这个路径作为pruned stash。 |
errored | 当引起python异常时执行,这意味着angr或者自己的代码中有错误。 |
unconstrained | 如果path_group的构造函数被指定save_unconstrained参数,路径就确定为无约束的(其中指令指针被用户数据或一些其他符号数据源所控制)放在此处 |
unsat | 如果path_group的构造函数被指定save_unsat参数,路径就确定为不可满足的(他们具有矛盾的约束条件,例如同时输入“AA”和“BB”)放在此处 |
你可以使用path_group.move函数来在不同的stash中移动路径。这个函数接受很多参数来控制这些路径移动。