首页
社区
课程
招聘
[原创]angr学习(一)
发表于: 2020-12-28 23:49 15895

[原创]angr学习(一)

2020-12-28 23:49
15895

最近开始学习angr,太多基础不会,看不懂论文啊。那么,首先在本文先学习angr的一些基本概念,然后在后面结合angr_ctf来学习更多细节,最后再来读论文。

angr是一个多架构的用于分析二进制文件的python框架,它结合了静态和动态符号分析能力,使得它能胜任多种任务。

angr功能:

angr构成(模块)

Relationship-between-angr-modules

image-20201227211451176

image-20201225142217665

在开始利用angr之前,我们先来对angr的一些基本概念以及如何构造一些基本的angr对象进行一个基本的了解。

使用angr的第一步总是将一个二进制文件加载到分析平台。

Project是angr中的控制基础。有了它就能对刚加载的可执行文件进行分析和仿真。几乎在angr中使用的每一个对象都依赖于Project的存在。

一个Project有一些基础属性:它的CPU架构、文件名、入口地址。

把二进制文件转换为它在虚拟地址空间中的表示是复杂的。angr有一个模块CLE来处理它。CLE的结果叫做加载器(loader),在属性.loader 中可用。我们先来简单了解loader,可以通过loader来查看二进制文件加载的共享库,以及执行对加载地址空间相关的基本查询。

在angr中有很多类,它们中的大多数都需要一个实例化的project。可以使用project.factory,而不用把project到处传递。factory有几个方便的构造函数,用于经常使用的常见对象。

首先,project.factory.block()用于通过给定的地址提取一个基本块(basic block)的代码(P.S. 关于angr的一个重要的点:angr以基本块为单位来分析代码)。使用它将得到一个Block对象,这个Block对象能告诉我们关于块代码的许多有趣的信息。

另外,还可以通过Block对象得到块代码的其他表示形式。

关于angr的另一个重要的点:Project对象只代表程序的一个“初始化镜像”,即Project 对象仅表示程序一开始的样子。而当我们再使用angr做执行操作时,实际上操作的是一个表示模拟的程序状态(simulated program state)的特殊对象SimState。SimState代表程序的一个实例镜像,模拟执行某个时刻的状态。

一个SimState对象包括程序内存、寄存器、文件系统数据…… 任何可以通过执行操作改变的“运行时数据(live data)”都存在于state中。这里我们先进行简单的了解,使用state.regs和state.mem来访问寄存器和内存。

这些结果(BV)不是Python的整数 ,而是bitvector(位向量)。可以把bitvector看成是一串比特序列表示的整数,angr使用bitvector来表示CPU数据。每个bitvector都有一个.length属性来描述它的位宽。下面是python整数和bitvector的转换。

我们可以把bitvector存储到寄存器和内存中;或者直接存储一个python整数,它会进行自动转换,把python整数转换为合适大小的bitvector。

对于mem接口:

最后,如果你继续读取更多的寄存器,还可能会遇到下面这样的情况。edi的值仍然是一个32位的bitvector,但是它没有一个具体的值,而是有一个名字。这被叫做符号变量,是符号执行的基础

符号执行是angr中极其重要的部分之一。

符号执行 (Symbolic Execution)是一种程序分析技术。它会遍历程序的所有可能的路径,从而得到让特定代码区域执行的输入。

符号执行的细节在后面再继续学习。

state表示程序运行中的一个点,那么必定存在一种方法来到达下一个点。Simulation Manager(仿真管理器)在angr中是对state进行操作的基本接口。

首先,我们创建一个simulation manager。构造函数可以接受一个state或者state列表。单个 Simulation Manager 可以包含多个存放state的 stash, 默认的stash 是 active stash,是使用我们传入的 state初始化的。

调用step() 会执行了一个基本块的符号执行! 我们可以再次查看active的stash,可以注意到它已被更新,但是初始的state没有改变。SimState对象在执行时被视为不可变的——你可以安全地使用单个State作为多轮执行的“基础”。

angr 内置了一些分析方法,用于提取程序信息。

例子:如何构造和使用一个控制流图

学完这些,后面结合angr_ctf继续学!

docker run -itd --name angr angr/angr
docker exec -it angr bash
su angr # root用户没有angr的环境,需切换到angr用户
docker run -itd --name angr angr/angr
docker exec -it angr bash
su angr # root用户没有angr的环境,需切换到angr用户
mkdir /opt/tools
cd /opt/tools
# 官方
# git clone https://github.com/slimm609/checksec.sh.git
# 加速
git clone https://gitclone.com/github.com/slimm609/checksec.sh.git
 
cd checksec.sh
# 建立链接
ln -s /opt/tools/checksec.sh/checksec /usr/local/bin/checksec
mkdir /opt/tools
cd /opt/tools
# 官方
# git clone https://github.com/slimm609/checksec.sh.git
# 加速
git clone https://gitclone.com/github.com/slimm609/checksec.sh.git
 
cd checksec.sh
# 建立链接
ln -s /opt/tools/checksec.sh/checksec /usr/local/bin/checksec
cd /opt
 
# 官方
# git clone https://github.com/jakespringer/angr_ctf.git
# 加速
git clone https://gitclone.com/github.com/jakespringer/angr_ctf.git
 
# 查看二进制文件
cd angr_ctf/dist/
cd /opt
 
# 官方
# git clone https://github.com/jakespringer/angr_ctf.git
# 加速
git clone https://gitclone.com/github.com/jakespringer/angr_ctf.git
 
# 查看二进制文件
cd angr_ctf/dist/
In [1]: import angr
 
In [2]: proj = angr.Project('./00_angr_find')
In [1]: import angr
 
In [2]: proj = angr.Project('./00_angr_find')
In [3]: import monkeyhex # 它会以十六进制格式化数值结果
 
In [4]: proj.arch
Out[4]: <Arch X86 (LE)>
 
In [5]: proj.entry
Out[5]: 0x8048450
 
In [6]: proj.filename
Out[6]: './00_angr_find'
In [3]: import monkeyhex # 它会以十六进制格式化数值结果
 
In [4]: proj.arch
Out[4]: <Arch X86 (LE)>
 
In [5]: proj.entry
Out[5]: 0x8048450
 
In [6]: proj.filename
Out[6]: './00_angr_find'
In [7]: proj.loader
Out[7]: <Loaded 00_angr_find, maps [0x8048000:0x8607fff]>
 
In [8]: proj.loader.shared_objects
Out[8]:
OrderedDict([('00_angr_find',
              <ELF Object 00_angr_find, maps [0x8048000:0x804a03f]>),
             ('libc.so.6',
              <ELF Object libc-2.31.so, maps [0x8100000:0x82edf0b]>),
             ('ld-linux.so.2',
              <ELF Object ld-2.31.so, maps [0x8300000:0x832c987]>),
             ('extern-address space',
              <ExternObject Object cle##externs, maps [0x8400000:0x8407fff]>),
             ('cle##tls',
              <ELFTLSObjectV2 Object cle##tls, maps [0x8500000:0x8514807]>)])
 
In [9]: proj.loader.min_addr
Out[9]: 0x8048000
 
In [10]: proj.loader.max_addr
Out[10]: 0x8607fff
 
In [11]: proj.loader.main_object # 加载了多个二进制程序,这是主对象
Out[11]: <ELF Object 00_angr_find, maps [0x8048000:0x804a03f]>
 
In [12]: proj.loader.main_object.execstack # 查询主对象是否开启了NX
Out[12]: False
 
In [13]: proj.loader.main_object.pic # 查询主对象是否开启了PIC
Out[13]: False
In [7]: proj.loader
Out[7]: <Loaded 00_angr_find, maps [0x8048000:0x8607fff]>
 
In [8]: proj.loader.shared_objects
Out[8]:
OrderedDict([('00_angr_find',
              <ELF Object 00_angr_find, maps [0x8048000:0x804a03f]>),
             ('libc.so.6',
              <ELF Object libc-2.31.so, maps [0x8100000:0x82edf0b]>),
             ('ld-linux.so.2',
              <ELF Object ld-2.31.so, maps [0x8300000:0x832c987]>),
             ('extern-address space',
              <ExternObject Object cle##externs, maps [0x8400000:0x8407fff]>),
             ('cle##tls',
              <ELFTLSObjectV2 Object cle##tls, maps [0x8500000:0x8514807]>)])
 
In [9]: proj.loader.min_addr
Out[9]: 0x8048000
 
In [10]: proj.loader.max_addr
Out[10]: 0x8607fff
 
In [11]: proj.loader.main_object # 加载了多个二进制程序,这是主对象
Out[11]: <ELF Object 00_angr_find, maps [0x8048000:0x804a03f]>
 
In [12]: proj.loader.main_object.execstack # 查询主对象是否开启了NX
Out[12]: False
 
In [13]: proj.loader.main_object.pic # 查询主对象是否开启了PIC
Out[13]: False
In [14]: block = proj.factory.block(proj.entry) # 从程序的入口处提取一个代码块
 
In [15]: block.pp() # 打印反汇编代码
0x8048450:    xor    ebp, ebp
0x8048452:    pop    esi
0x8048453:    mov    ecx, esp
0x8048455:    and    esp, 0xfffffff0
0x8048458:    push    eax
0x8048459:    push    esp
0x804845a:    push    edx
0x804845b:    push    0x8048710
0x8048460:    push    0x80486b0
0x8048465:    push    ecx
0x8048466:    push    esi
0x8048467:    push    0x80485c7
0x804846c:    call    0x8048420
 
In [16]: block  # 查看block对象
Out[16]: <Block for 0x8048450, 33 bytes>
 
In [17]: block.instructions # 块里有多少条指令
Out[17]: 0xd
 
In [18]: block.instruction_addrs # 块里所有指令对应的地址
Out[18]:
[0x8048450,
 0x8048452,
 0x8048453,
 0x8048455,
 0x8048458,
 0x8048459,
 0x804845a,
 0x804845b,
 0x8048460,
 0x8048465,
 0x8048466,
 0x8048467,
 0x804846c]
In [14]: block = proj.factory.block(proj.entry) # 从程序的入口处提取一个代码块
 
In [15]: block.pp() # 打印反汇编代码
0x8048450:    xor    ebp, ebp
0x8048452:    pop    esi
0x8048453:    mov    ecx, esp
0x8048455:    and    esp, 0xfffffff0
0x8048458:    push    eax
0x8048459:    push    esp
0x804845a:    push    edx
0x804845b:    push    0x8048710
0x8048460:    push    0x80486b0
0x8048465:    push    ecx
0x8048466:    push    esi
0x8048467:    push    0x80485c7
0x804846c:    call    0x8048420
 
In [16]: block  # 查看block对象
Out[16]: <Block for 0x8048450, 33 bytes>
 
In [17]: block.instructions # 块里有多少条指令
Out[17]: 0xd
 
In [18]: block.instruction_addrs # 块里所有指令对应的地址
Out[18]:
[0x8048450,
 0x8048452,
 0x8048453,
 0x8048455,
 0x8048458,
 0x8048459,
 0x804845a,
 0x804845b,
 0x8048460,
 0x8048465,
 0x8048466,
 0x8048467,
 0x804846c]
In [19]: block.capstone # capstone 反汇编
Out[19]: <CapstoneBlock for 0x8048450>
 
In [20]: block.vex # VEX IRSB
Out[20]: IRSB <0x21 bytes, 13 ins., <Arch X86 (LE)>> at 0x8048450
In [19]: block.capstone # capstone 反汇编
Out[19]: <CapstoneBlock for 0x8048450>
 
In [20]: block.vex # VEX IRSB
Out[20]: IRSB <0x21 bytes, 13 ins., <Arch X86 (LE)>> at 0x8048450
In [21]: state = proj.factory.entry_state() # 程序的入口点的状态
 
In [22]: state
Out[22]: <SimState @ 0x8048450>
In [21]: state = proj.factory.entry_state() # 程序的入口点的状态
 
In [22]: state
Out[22]: <SimState @ 0x8048450>
In [24]: state.regs.eip # 访问eip,获取当前指令的地址
Out[24]: <BV32 0x8048450>
 
In [25]: state.regs.eax # 访问eax
Out[25]: <BV32 0x1c>
 
In [27]: state.mem[proj.entry].int.resolved # interpret the memory at the entry point as a C int
Out[27]: <BV32 0x895eed31>
In [24]: state.regs.eip # 访问eip,获取当前指令的地址
Out[24]: <BV32 0x8048450>
 
In [25]: state.regs.eax # 访问eax
Out[25]: <BV32 0x1c>
 
In [27]: state.mem[proj.entry].int.resolved # interpret the memory at the entry point as a C int
Out[27]: <BV32 0x895eed31>
In [28]: bv = state.solver.BVV(0x1234, 32) # python整数0x1234转换为bitvector
 
In [29]: bv
Out[29]: <BV32 0x1234>
 
In [30]: state.solver.eval(bv) # bitvector转换为python整数
Out[30]: 0x1234
 
In [31]: bv.length # 查看位向量的位长度
Out[31]: 0x20
In [28]: bv = state.solver.BVV(0x1234, 32) # python整数0x1234转换为bitvector
 
In [29]: bv
Out[29]: <BV32 0x1234>
 
In [30]: state.solver.eval(bv) # bitvector转换为python整数
Out[30]: 0x1234
 
In [31]: bv.length # 查看位向量的位长度
Out[31]: 0x20
In [35]: state.regs.esi = state.solver.BVV(3, 32) # 存储bitvector到寄存器esi中
 
In [36]: state.regs.esi
Out[36]: <BV32 0x3>
 
In [37]: state.mem[0x1000].long = 4 # 直接存储一个整数到内存[0x1000]中
 
In [38]: state.mem[0x1000].long.resolved
Out[38]: <BV32 0x4>
 
In [39]: state.mem[0x1000].long.concrete
Out[39]: 0x4
In [35]: state.regs.esi = state.solver.BVV(3, 32) # 存储bitvector到寄存器esi中

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 6
支持
分享
打赏 + 2.00雪花
打赏次数 1 雪花 + 2.00
 
赞赏  orz1ruo   +2.00 2020/12/29 助人为乐~
最新回复 (3)
雪    币: 1431
活跃值: (4418)
能力值: ( LV9,RANK:220 )
在线值:
发帖
回帖
粉丝
2
写的非常好很棒的文章
2021-10-27 14:40
0
雪    币: 0
活跃值: (73)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
学习了,感谢师傅
2021-11-10 10:33
0
雪    币: 8764
活跃值: (5723)
能力值: ( LV13,RANK:296 )
在线值:
发帖
回帖
粉丝
4
mark
2022-5-2 21:06
0
游客
登录 | 注册 方可回帖
返回
//