首页: https://angr.io
项目存储库: GitHub - angr/angr: A powerful and user-friendly binary analysis platform!
文档: https://docs.angr.io
API 文档: angr documentation
练习项目: https://github.com/angr/angr-doc/tree/master
angr 是一款开源的python框架,是一个多架构开源二进制分析工具包,能够对二进制文件执行动态符号执行(如 Mayhem、KLEE 等)和各种静态分析。
直接 pip install angr就可以安装。
一、angr架构
CLE:
CLE是angr加载二进制文件的组件,在加载二进制文件时会分析并读取binary的信息,包括指令地址、shared library、arch information等;
python
import angr
b = angr.Project("./test")
VEX:
它会将指令转化为中间语言IR,分析IR并且模拟,搞清楚它是什么并且做了什么;
Claripy:
它是angr的求解引擎,根据程序所需要的输入设置符号变量以及收集限制式等等,在argr中多用于符号化。
二、核心概念
2.1、顶层接口
Project类是angr的主类,也是angr的开始,通过初始化该类的对象,可以将你想要分析的二进制文件加载进来,就像这样:
import angr
proj = angr**.Project("/bin/true")**
参数:
- thing:待分析的文件路径,它是唯一必须传入的参数
- auto_load_libs:是否用CLE自动解析共享库依赖关系,默认为on。
- except_missing_libs:当二进制文件有无法解析的共享库依赖项时,将引发异常,默认为on。
- force_load_libs:将字符串列表传递给force_load_libs,所列出的内容将作为未解析的共享库依赖项处理,
- skip_libs:将字符串列表传递给skip_libs,以防止将该名称的任何库解析为依赖项。
- main_opts是一个从选项名到选项值的映射
- lib_opts是一个从库名到将选项名映射到选项值的字典的映射 例如angr**.** Project**(** 'examples/fauxware/fauxware', main_opts**={** 'backend': 'blob', 'arch': 'i386'}, lib_opts**={** 'libc.so.6': { 'backend': 'elf'}}
任何附加的参数都会被传递到angr的加载器,即CLE.loader中(CLE 即 CLE Loads Everything的缩写)。
Project类中有许多方法和属性,例如架构、程序入口点、加载的文件名、大小端等等:
python
>>> print(proj.arch, hex(proj.entry), proj.filename, proj.arch.bits, proj.arch.memory_endness )
<Arch AMD64 (LE)> 0x4023c0 /bin/true 64 Iend_LE
2.2、Blocks
project.factory.block()用于 从给定的地址提取基本代码块。angr是以基本块为单位分析代码。
返回一个Block对象。
python
>>> block = proj.factory.block(proj.entry) # lift a block of code from the program's entry point
<Block for 0x401670, 42 bytes>
>>> block.pp() # 打印反汇编内容,地址显示16进制
4017b0 xor ebp, ebp
4017b2 mov r9, rdx
4017b5 pop rsi
4017b6 mov rdx, rsp
4017b9 and rsp, 0xfffffffffffffff0
4017bd push rax
4017be push rsp
4017bf lea r8, [0x4049f0]
4017c6 lea rcx, [0x404980]
4017cd lea rdi, [0x4016f0]
4017d4 call qword ptr [0x606fd8]
>>> block.instructions # 有多少条指令
11
>>> block.instruction_addrs # 指令的地址,返回10进制,若要返回16进制,在此之前加上 import monkeyhex # this will format numerical results in hexadecimal
(4200368, 4200370, 4200373, 4200374, 4200377, 4200381, 4200382, 4200383, 4200390, 4200397, 4200404)
此外,您可以使用 Block 对象来获取代码块的其他表示形式:
python
>>> block.capstone # capstone disassembly
<DisassemblerBlock for 0x4017b0>
>>> block.vex # VEX IRSB (that's a Python internal address, not a program address)
IRSB <0x2a bytes, 11 ins., <Arch AMD64 (LE)>> at 0x4017b0
2.3、状态State
Project实际上只是将二进制文件加载进来了,要执行它,实际上是对SimState对象进行操作,它是程序的状态。用docker来比喻,Project相当于开发环境,State则是使用开发环境制作的镜像。
2.3.1、创建状态
要创建状态,需要使用Project对象中的factory,它还可以用于创建模拟管理器和基本块。
state = proj**.factory.entry_state(****)**
返回值:
返回一个Simstate,SimState包含程序的内存、寄存器、文件系统、符号变量和约束内容等......任何可以通过执行更改的"实时数据"均在SimState。
预设状态有四种方式如下:
- entry_state() :构造一个准备在主二进制文件的入口点执行的状态。
- blank_state(addr=):**** 构造了一个"空白石板"空白状态,其大部分数据未初始化,状态中下一条指令为addr处的指令。当访问未初始化的数据时,将返回一个不受约束的符号值。
- full_init_state():**** 构造一个状态,该状态可以通过需要在主二进制的入口点之前运行的任何初始化程序执行,例如共享库构造函数或预初始化程序。当完成这些后,它将跳转到入口点。
- call_state():**** 构造准备执行给定函数的状态
entry_state和blank_state是常用的两种方式,后者通常用于跳过一些极大降低angr效率的指令,它们间的对比如下:
python
>>> state = p.factory.entry_state()
>>> print(state.regs.rax, state.regs.rip) # state.regs.rip:get the current instruction pointer
<BV64 0x1c> <BV64 0x4023c0>
>>> state = p.factory.blank_state(addr=0x4023c0)
>>> print(state.regs.rax, state.regs.rip)
<BV64 reg_rax_42_64{UNINITIALIZED}> <BV64 0x4023c0>
在blank_state方式中,我们仍将地址设定为程序的入口点,然而rax中的值由于没有初始化,它现在是一个名字,也即符号变量,这是符号执行的基础,后续在细说。
此外,可以看到寄存器中的数据类型并不是int,而是BV64,它是一个位向量(Bit Vector)。
2.3.2、BV与BVS
位向量(Bit Vector,BV )就是一串比特的序列,这于python中的int不同,例如python中的int提供了整数溢出上的包装。而位向量可以理解为CPU中使用的一串比特流,需要注意的是, angr封装的位向量有两个属性:值以及它的长度
以下是 如何从 Python 整数转换为 位向量 并再次转换回来: 每个位向量都有一个 .length 属性,描述其位宽
python
>>> bv = state.solver.BVV(0x1234, 32) # create a 32-bit-wide bitvector with value 0x1234
<BV32 0x1234> # BVV stands for bitvector value
>>> state.solver.eval(bv) # convert to Python int
4660
位向量相互之间能够进行运算,但 参与运算的位向量的长度必须相同
python
>>> one = state.solver.BVV(1,64)
>>> one_hundred = state.solver.BVV(100,64)
>>> short_nine = state.solver.BVV(9,27)
>>> print(one,one_hundred,short_nine)
<BV64 0x1> <BV64 0x64> <BV27 0x9>
>>> print(short_nine+1)
<BV27 0xa>
>>> print(one+one_hundred)
<BV64 0x65>
>>> print(one+short_nine)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.6/dist-packages/claripy/operations.py", line 50, in _op
raise ClaripyOperationError(msg)
claripy.errors.ClaripyOperationError: args' length must all be equal # 当长度不一样时,claripy会提示"length must all be equal",同时我们也得知,位向量运算的底层模块时claripy
如果一定要进行长度不相等位向量之间的运算,可以扩展位向量 ,使用zero_extend会用零扩展高位,而sign_extend会在此基础上带符号地进行扩展:
python
>>> print(one+short_nine.zero_extend(64-27))
<BV64 0xa>
接下来使用 BVS(Bit Vectort Symbol)创建一些符号变量:
python
>>> x = state.solver.BVS('x',64) # BVS的参数分别是符号变量名和长度
>>> y = state.solver.BVS('y',64)
>>> z = state.solver.BVS('notz',64)
>>> print(x,y,z)
<BV64 x_42_64> <BV64 y_43_64> <BV64 notz_44_64>
此时对符号变量进行运算,做比较判断,都不会得到一个具体的值,而是将这些操作统统保存到符号变量中:
python
>>> print(x+1)
<BV64 x_42_64 + 0x1>
2.3.3、状态操作
状态包含了程序运行时的一切信息,寄存器、内存的值、文件系统以及符号变量等
寄存器访问: 可以通过state.regs.寄存器名来访问和修改寄存器:
- state.regs.rip # get the current instruction pointer
- state.regs.rax
- state.regs.rbp = state.regs.rsp # 将寄存器rsp的值给rbp
栈访问: 栈访问涉及两个寄存器:ebp和esp,以及两个指令:push和pop,对于寄存器的访问与其他寄存器相同
- state.stack_push(value)
- state.stack_pop()
**内存访问:**使用以下两个指令对内存读写:
- 读内存-state.memory.load(addr, size=None, condition=None, fallback=None, **kwargs) # 程序所使用的大小端endness可以用proj.arch.memory_endness查询,因此在对默认值没有把握时,请让endness=p.arch.memory_endness。
- 写内存-state.memory.store(addr, data, size=None, condition=None, **kwargs)
文件访问 :angr提供SimFile类用来模拟文件,通过将SimFile对象插入到状态的文件系统中,在使用angr分析程序时就可以使用该文件。
python
filename = 'test.txt'
simfile = angr.storage.SimFile(name=filename, content=data, size=0x40)
state.fs.insert(filename, simfile)
上述指令能创建一个SimFile对象,文件名为test.txt,内容为data,输入的内容长度为0x40,单位为字节。之后,使用state.fs.insert方法,将SimFile对象插入到状态的文件系统中,在模拟运行程序时就可以使用这个文件了。
2.4、符号约束与求解
2.4.1、符号约束
每个符号本质上可以看做是一颗抽象语法树(AST), x = state.solver.BVS( ' x ' ,64 )生成的符号变量<BV64 x_42_64>可以看作是只有一层的AST,对它进行操作实际上是在扩展AST,这样的AST的构造规则如下:
- 如果AST只有根节点的话,name它必定是符号变量BVS或位向量BV
- 如果AST有多层,那么叶子节点为符号变量和位向量,其他节点为运算符
其中一个节点的左右孩子可以使用args来访问,节点本身存放的信息则使用op来访问。可以通过下面的例子来理解:
接下来对符号变量进行 比较判断:
结果不是一个位向量,而是一个符号化的布尔类型。
这些布尔类型的值可以通过is_true和is_false来判断,但对于上述有符号变量参与的布尔类型,它永远为false。
此外需要注意的是,直接使用比较符号比较两个位向量,通常是默认不带符号的,例子如下:
符号约束与状态相关,或者说一个state除了包含内存、寄存器中的值这些信息外,还包含了符号约束(也就是要到达当前状态符号变量所必须满足的条件)。
除了运行程序,SM根据分支搜集起来的符号约束之外,用户 可以自动手动添加约束:
此时,x必须满足大于5小于8,而y必须满足小于x。
2.4.2、符号求解
可以使用 **state.solver.eval(x)**来求解当前状态(即state)中的符号约束下x的值
此外,很明显能够看到,x应该是有多个值的,可以solver中的其他方法取出来:
- solver.eval(x):给出表达式的一个可能解
- sovler.eval_one(x):给出表达式的解,如果有多个解,将抛出错误
- solver.eval_upto(x,n) 给出表达式的至多n个解
- sovler.eval_taleast(x,n):给出表达式的n个解,如果解的数量少于n,则抛出错误
- solver.eval_exact(x,n):给出表达式的n个解,如果解的个数不为n,则抛出错误
- sovler.min(x) : 给出表达式的最小解
- sovler.max(x) : 给出表达式的最大解
这些方法还有两个可省略的 参数:
- extra_constraints:可以作为约束进行求解,但不会被添加到当前状态
- cast_to:将传递结果转换成指定数据类型, 目前只能是int和bytes ,例如state.solver.eval(state.solver.BVV(0x41424344, 32), cast_to=bytes) 将返回b'ABCD'
此外,如果将两个互相矛盾的约束加入到一个state当中,那么这个state就会被放到unsat这个stash里面,对这样的state进行求解会导致异常,可以使用 state.satisfiable()来检查是否有解。
2.5、模拟管理器(Simulation Manager)
proj.factory.entry_state()只是预设了程序分析开始时的状态,要分析程序就必须要让它到达下一个状态,这就需要模拟管理器的帮助(简称SM)。
使用以下指令能创建一个SM,它需要传入一个state或者state的列表作为参数
simgr = proj**.factory.simulation_manager(state****)**
SM中有许多列表,这些列表被称为**stash****,**它保存了处于某种状态的state,stash有如下几种:
- active :此存储区包含默认情况下将逐步执行的状态,除非指定了备用存储区;
- deadended :当一个状态由于某种原因不能继续执行时,例如没有合法指令,或者有非法指针,它就会进入死区隐藏;
- pruned :与solve的策略有关,当发现一个不可解的节点后,其后面所有的节点都优化掉放在pruned里;
- **unconstrained:**如果创建SM时启用了save_unconstrained,则被认定为不受约束的state会放在这,不受约束的state是指由用户数据或符号控制的指令指针(例如eip)
- unsat :如果创建SM时启用了save_unsat,则被认为不可满足(即,它们有相互矛盾的约束,比如输入必须同时为"AAAA"和"BBBB")的state会放在这里。
默认情况下,state会被存放在active中。
stash中的state可以通过move()方法来转移,将fulter_func筛选出来的state从from_stash转移到to_stash:
python
simgr.move(from_stash='deadended', to_stash='more_then_50', filter_func=lambda s: '100' in s.posix.dumps(1))
stash是一个列表,可以使用python支持的方式去遍历其中的元素,也可以使用常见的列表操作。但angr提供了一种更高级的方式,在stash名字前加上one_,可以得到stash中的第一个状态,加上mp_,可以得到一个mulpyplexed版本的stash。
解释一下上面代码中的 posix.dumps:
- state.posix.dumps(0):表示到达当前状态所对应的程序输入
- state.posix.dumps(1): 表示到达当前状态所对应的程序输出
上述代码就是将deadended中输出的字符串包含'100'的state转移到more_then_50这个stash中。
可以通过 **step()**方法来让处于active的state执行一个基本块, 这种操作不会改变state本身:
python
>>> state = p.factory.entry_state()
>>> simgr = p.factory.simgr(state)
>>> print(state.regs.rax, state.regs.rip)
<BV64 0x1c> <BV64 0x4023c0>
>>> print(simgr.one_active)
<SimState @ 0x4023c0>
>>> simgr.step()
<SimulationManager with 1 active>
>>> print(simgr.one_active)
<SimState @ 0x529240>
>>> print(state.regs.rax, state.regs.rip)
<BV64 0x1c> <BV64 0x4023c0>
2.6、 Analyses
angr 预先打包了几个内置分析: Analyses - angr documentation (docs-angr-io.translate.goog)
**>>>**proj **.**analyses . # Press TAB here in ipython to get an autocomplete-listing of everything:
作为一个非常简短的示例:以下是构建和使用快速控制流图的方法:
python
# Originally, when we loaded this binary it also loaded all its dependencies into the same virtual address space
# This is undesirable for most analysis.
>>> proj = angr.Project('/bin/true', auto_load_libs=False)
>>> cfg = proj.analyses.CFGFast()
<CFGFast Analysis Result at 0x7f03988ec7b8>
# cfg.graph is a networkx DiGraph full of CFGNode instances
# You should go look up the networkx APIs to learn how to use this!
>>> cfg.graph
<networkx.classes.digraph.DiGraph at 0x7f0398e7d6d8>
>>> len(cfg.graph.nodes())
1094
# To get the CFGNode for a given address, use cfg.get_any_node
>>> entry_node = cfg.get_any_node(proj.entry)
>>> len(list(cfg.graph.successors(entry_node)))
1
2.7、 探索技术(explorer techniques)
Simulation Managers - angr documentation (docs-angr-io.translate.goog)
可以 ++使用++ ++explorer()++ ++方法去执行某个状态,直到找到目标指令或者active中没有状态为止++ ,它有如下 参数:
- find:传入目标指令的 地址或地址列表,或者一个用于判断的函数,函数以state为形参,返回布尔值
- avoid:传入要避免的指令的地址或地址列表,或者一个用于判断的函数,用于减少路径
此外还有一些搜索策略,之后会集中讲解, 默认使用DFS(深度优先搜索)。
explorer找到的符合find的状态会被保存在 simgr.found 这个列表当中,可以遍历其中元素获取状态。
2.7.1、传入 一个用于判断的函数
看一个简单的crackme示例: https://github.com/angr/angr-doc/tree/master/examples/CSCI-4968-MBE/challenges/crackme0x00a
python
# 1、首先,我们加载二进制文件
>>> proj = angr.Project('examples/CSCI-4968-MBE/challenges/crackme0x00a/crackme0x00a')
# 2、接下来,我们创建一个SimulationManager
>>> simgr = proj.factory.simgr()
# 3、现在,符号执行直到找到与我们的条件匹配的state(即"win"条件)
>>> simgr.explore(find=lambda s: b"Congrats" in s.posix.dumps(1)) # 传入一个用于判断的函数
<SimulationManager with 1 active, 1 found>
# 4、现在,我们可以将flag从该state中取出!
>>> s = simgr.found[0]
>>> print(s.posix.dumps(1))
b'Enter password: Congrats!\n'
>>> print(s.posix.dumps(1).decode()) # bytes转成str
Enter password: Congrats!
>>> flag = s.posix.dumps(0)
>>> print(flag)
g00dJ0B!
2.7.2、传入 目标指令的地址
https://github.com/angr/angr-doc/blob/master/examples/CSCI-4968-MBE/challenges/crackme0x00a/solve.py
python
#!/usr/bin/env python3
# Author: David Manouchehri <manouchehri@protonmail.com>
# Modern Binary Exploitation
# http://security.cs.rpi.edu/courses/binexp-spring2015/
import angr
FIND_ADDR = 0x08048533 # mov dword [esp], str.Congrats_ ; [0x8048654:4]=0x676e6f43 LEA str.Congrats_ ; "Congrats!" @ 0x8048654
AVOID_ADDR = 0x08048554 # mov dword [esp], str.Wrong_ ; [0x804865e:4]=0x6e6f7257 LEA str.Wrong_ ; "Wrong!" @ 0x804865e
def main():
proj = angr.Project('crackme0x00a', load_options={"auto_load_libs": False})
sm = proj.factory.simulation_manager()
sm.explore(find=FIND_ADDR, avoid=AVOID_ADDR)
return sm.found[0].posix.dumps(0).split(b'\0')[0] # stdin
def test():
assert main() == b'g00dJ0B!'
if __name__ == '__main__':
print(main())
2.8、符号执行
angr通常作为符号执行工具使用。
符号执行就是给程序传递一个符号而不是具体值,让这个符号伴随程序执行,当碰见分支时,符号会全部进入各个分支。angr会保存所有分支以及分支后的所有分支,并且在分支时,保存进入该分支时的判断条件,通常这些判断条件是对符号的约束。
angr运行到目标状态时,就可以调用求解器对一路上搜集到的约束进行求解,最终得到某个符号能到达当前状态的输入值。
例如,程序接收一个int类型的输入,当这个输入大于0小于5时,就会执行某条保存在该程序中,我们希望执行的指令(例如一个后门函数backdoor),具体而言如下图所示
angr会沿着分支按照某种策略(默认DFS)进行状态搜索,当达到目标状态(也就是backdoor能够执行的状态),此时angr已经收集了两个约束(x>0 以及x<=5),那么angr就通过这两个约束对x进行求解,解出来的x值就是能够让程序执行backdoor的输入。
三、官方示例
利用angr分析程序时有个一般的流程:
- 导入angr
- 导入二进制文件,创建Project
- 预设状态state
- 定义符号变量bvs并与二进制文件相联系
- 建立simgr,用于管理state
- 运行,探索满足条件的路径
- 约束求解获取执行结果
直接用了 https://github.com/angr/angr-doc/tree/master/examples/sym-write 里面提供的例子
1、issue.c
cpp
#include <stdio.h>
char u=0;
int main(void)
{
int i, bits[2]={0,0};
for (i=0; i<8; i++) {
bits[(u&(1<<i))!=0]++;
}
if (bits[0]==bits[1]) {
printf("you win!");
}
else {
printf("you lose!");
}
return 0;
}
2、编译: gcc issue.c -o issue
3、编写python脚本
python
# -*- coding: utf-8 -*-
import angr
import claripy
def main():
proj = angr.Project('./issue',load_options={"auto_load_libs":False}) # 创建一个工程并导入二进制文件------issue,选择不自动加载依赖项(可选)
#以下是对二进制文件的一些基本操作(可选)
print(proj.arch) # 架构 <Arch X86 (LE)>
#proj.arch.memory_endess # 大小端
#proj.entry # 二进制程序入口点 134513520
#proj.filename # 程序名称以及位置 './issue'
#proj.loader # 是通过CLE模块将二进制对象加载并映射带单个内存空间 <Loaded issue, maps [0x8048000:0x8407fff]>
#proj.loader.min_addr # proj.loader 的低位地址 134512640
#proj.loader.max_addr # proj.loader 的高位地址 138444799
#proj.loader.all_objects # CLE加载的对象的完整列表
#proj.loader.shared_objects # 这是一个从共享对象名称到对象的字典映射
#proj.loader.all_elf_objects # 这是从ELF文件中加载的所有对象
#proj.loader.all_pe_objects # 加载一个windows程序
#proj.loader.main_object # 加载main对象
#proj.loader.main_object.execstack # 这个二进制文件是否有可执行堆栈
#proj.loader.main_object.pic # 这个二进制位置是否独立
#proj.loader.extern_object # 这是"externs对象",我们使用它来为未解析的导入和angr内部提供地址
#proj.loader.kernel_object # 此对象用于为模拟的系统调用提供地址
#proj.loader.find_object_containing(0x400000) # 获得对给定地址的对象的引用
#以下是对确定的对象进行基本操作(可选)
obj = proj.loader.main_object # 指定main对象
print(obj.entry) # 获取地址
#obj.min_addr, obj.max_addr # 地址的地位和高位
#obj.segments#检索该对象的段
#obj.sections#检索该对象的节
#obj.find_segment_containing(obj.entry) # 通过地址获得单独的段
#obj.find_section_containing(obj.entry)# 通过地址获得单独的节
#addr = obj.plt['abort'] # 通过符号获取地址
#obj.reverse_plt[addr] # 通过地址获取符号
#obj.linked_base
#obj.mapped_base # 显示对象的预链接基以及CLE实际映射到内存中的位置
state = proj.factory.entry_state(add_options={angr.options.SYMBOLIC_WRITE_ADDRESSES})
# 返回一个simstate,SimState包含程序的内存、寄存器、文件系统数据以及符号变量等......任何可以通过执行更改的"实时数据"均在SimState。
#.entry_state的替换:
# .entry_state()构造一个准备在主二进制文件的入口点执行的状态。
#.blank_state()构造了一个"空白石板"空白状态,其大部分数据未初始化。当访问未初始化的数据时,将返回一个不受约束的符号值。
#.full_init_state()构造一个状态,该状态可以通过需要在主二进制的入口点之前运行的任何初始化程序执行,例如共享库构造函数或预初始化程序。当完成这些后,它将跳转到入口点。
#.call_state()构造准备执行给定函数的状态。
#通过state来访问一些寄存器的数值(可选)
#state.regs.rip
#state.regs.rax
#state.regs.rbp = state.regs.rsp # 将寄存器rsp的值给rbp
#注意:这儿采用的bitvectors,并不是python值,后面会说明python和bitvectors的转换
u = claripy.BVS("u",8)#建立一个名称为u,8位宽的符号变量
#claripy.BVS和state.solver.BVS有什么区别还不清楚,需要验证
#可以通过.eval(A)的方法将A(bitvectors)转化位python int型
#a = state.solver.FPV(3.2, state.solver.fp.FSORT_DOUBLE)#通过FPV来创建浮点型向量
#raw_to_bv和raw_to_fp方法将位向量解释为浮点数,反之亦然:
state.memory.store(0x601041,u) # 使用.memory.store(addr,val)方法将数据val保存在地址位addr的内存中,0x601041代表的是二进制文件中的某个变量的地址
simgr = proj.factory.simulation_manager(state) # 使用的模拟管理器来管理状态或状态列表。state被组织成stash,可以forward, filter, merge, and move 。
#simgr.active # 存储操作
#simgr.step() # 执行一个基本块的符号执行,即就是所有状态向前推进一个基本块(类似单步操作,这儿是单块操作)
#simgr.active # 以列表的形式更新存储
#simgr.run() # .run()方法直接执行程序,直到一切结束,运行simgr会查看返回deadended数目
while len(simgr.active)!=2:
simgr.step()#循环运行直到通过.active来判断是否进入了分支,这是因为angr在遇到分支时,会将每个分支作为一个状态来保存。
return simgr.active[0].state.se.eval(u) # 返回结果为win的u值,要是想返回lose的u值,将active[0]中0变为1即可
if __name__ == "__main__":
print(repr(main())) # repr() 函数将对象转化为供解释器读取的形式。
4、solve.py
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Author: xoreaxeaxeax
Modified by David Manouchehri <manouchehri@protonmail.com>
Original at https://lists.cs.ucsb.edu/pipermail/angr/2016-August/000167.html
The purpose of this example is to show how to use symbolic write addresses.
"""
import angr
import claripy
def main():
p = angr.Project('./issue', load_options={"auto_load_libs": False})
# By default, all symbolic write indices are concretized.
state = p.factory.entry_state(add_options={angr.options.SYMBOLIC_WRITE_ADDRESSES})
u = claripy.BVS("u", 8)
state.memory.store(0x804a021, u)
sm = p.factory.simulation_manager(state)
def correct(state):
try:
return b'win' in state.posix.dumps(1) # state.posix.dumps(1):表示到达当前状态所对应的程序输出
except:
return False
def wrong(state):
try:
return b'lose' in state.posix.dumps(1)
except:
return False
sm.explore(find=correct, avoid=wrong) # 探索
# Alternatively, you can hardcode the addresses.
# sm.explore(find=0x80484e3, avoid=0x80484f5)
return sm.found[0].solver.eval_upto(u, 256)
def test():
good = set()
for u in range(256):
bits = [0, 0]
for i in range(8):
bits[u&(1<<i)!=0] += 1
if bits[0] == bits[1]:
good.add(u)
res = main()
assert set(res) == good
if __name__ == '__main__':
print(repr(main()))
四、参考
angr_ctf------从0学习angr(一):angr简介与核心概念 - Uiharu - 博客园 (cnblogs.com)
angr_ctf------从0学习angr(二):状态操作和约束求解 - Uiharu - 博客园 (cnblogs.com)
angr_ctf------从0学习angr(三):Hook与路径爆炸 - Uiharu - 博客园 (cnblogs.com)
angr_ctf------从0学习angr(四):库操作和溢出漏洞利用 - Uiharu - 博客园 (cnblogs.c