学逆向论坛

找回密码
立即注册

只需一步,快速开始

发新帖

116

积分

0

好友

25

主题
发表于 2020-6-15 00:42:14 | 查看: 4001| 回复: 0

相关题目:


前言
  angr已经不在是从前的angr了。
  前一阵子为了学习ollvm混淆,纠结了很久angr的版本,可以说之前的angr很乱,版本稍一改动api就变了,早在暑假就听闻angr会有很大的变动,最近偶然间看到最新版的angr,于是又萌生了学习一番的冲动,毕竟angr在CTF解题中有许多应用。
基本原理  什么是符号执行,参阅Wiki
  angr官网
  API文档
  CTF应用
例题  官方给出了很多例题,这都是很好的学习资源呐!
  搭好环境后我们可以先来测试一个官方给的例子是否有效,
  就先测试耗时比较短的一题Whitehat CTF 2015 - Crypto 400

深入浅出Angr(一)

深入浅出Angr(一)
  可以看到喜人的输出结果。

深入浅出Angr(一)

深入浅出Angr(一)
比较  改版之后的不同,我这刚好有早之前的angr的示例脚本,拿来比较着学习。
  旧版(7.x.x.x)
#!/usr/bin/env python
# coding: utf-8
import angr
import time

def main():
# Load the binary. This is a 64-bit C++ binary, pretty heavily obfuscated.
p = angr.Project('wyvern')

# This block constructs the initial program state for analysis.
# Because we're going to have to step deep into the C++ standard libraries
# for this to work, we need to run everyone's initializers. The full_init_state
# will do that. In order to do this peformantly, we will use the unicorn engine!
st = p.factory.full_init_state(args=['./wyvern'], add_options=angr.options.unicorn)

# It's reasonably easy to tell from looking at the program in IDA that the key will
# be 29 bytes long, and the last byte is a newline.

# Constrain the first 28 bytes to be non-null and non-newline:
for _ in xrange(28):
k = st.posix.files[0].read_from(1)
st.solver.add(k != 0)
st.solver.add(k != 10)

# Constrain the last byte to be a newline
k = st.posix.files[0].read_from(1)
st.solver.add(k == 10)

# Reset the symbolic stdin's properties and set its length.
st.posix.files[0].seek(0)
st.posix.files[0].length = 29

# Construct a SimulationManager to perform symbolic execution.
# Step until there is nothing left to be stepped.
sm = p.factory.simulation_manager(st)
sm.run()

# Get the stdout of every path that reached an exit syscall. The flag should be in one of these!
out = ''
for pp in sm.deadended:
out = pp.posix.dumps(1)
if 'flag{' in out:
return filter(lambda s: 'flag{' in s, out.split())[0]

# Runs in about 15 minutes!

def test():
assert main() == 'flag{dr4g0n_or_p4tric1an_it5_LLVM}'

if __name__ == "__main__":
before = time.time()
print main()
after = time.time()
print "Time elapsed: {}".format(after - before)
  最新版8.18.10.25
#!/usr/bin/env python
# coding: utf-8
import angr
import claripy
import time

def main():
# Load the binary. This is a 64-bit C++ binary, pretty heavily obfuscated.
# its correct emulation by angr depends heavily on the libraries it is loaded with,
# so if this script fails, try copying to this dir the .so files from our binaries repo:
# [url]https://github.com/angr/binaries/tree/master/tests/x86_64[/url]
p = angr.Project('wyvern')

# It's reasonably easy to tell from looking at the program in IDA that the key will
# be 29 bytes long, and the last byte is a newline. Let's construct a value of several
# symbols that we can add constraints on once we have a state.

flag_chars = [claripy.BVS('flag_%d' % i, 8) for i in range(28)]
flag = claripy.Concat(*flag_chars + [claripy.BVV(b'\n')])

# This block constructs the initial program state for analysis.
# Because we're going to have to step deep into the C++ standard libraries
# for this to work, we need to run everyone's initializers. The full_init_state
# will do that. In order to do this peformantly, we will use the unicorn engine!
st = p.factory.full_init_state(
args=['./wyvern'],
add_options=angr.options.unicorn,
stdin=flag,
)

# Constrain the first 28 bytes to be non-null and non-newline:
for k in flag_chars:
st.solver.add(k != 0)
st.solver.add(k != 10)

# Construct a SimulationManager to perform symbolic execution.
# Step until there is nothing left to be stepped.
sm = p.factory.simulation_manager(st)
sm.run()

# Get the stdout of every path that reached an exit syscall. The flag should be in one of these!
out = b''
for pp in sm.deadended:
out = pp.posix.dumps(1)
if b'flag{' in out:
return next(filter(lambda s: b'flag{' in s, out.split()))

# Runs in about 15 minutes!

def test():
assert main() == b'flag{dr4g0n_or_p4tric1an_it5_LLVM}'

if __name__ == "__main__":
before = time.time()
print(main())
after = time.time()
print("Time elapsed: {}".format(after - before))
  试着运行旧版的脚本,如下报错

深入浅出Angr(一)

深入浅出Angr(一)
  可见st.posix.files中的files方法已经被移除了,之前了解过angr的同学应该都知道,这一段代码是用来做条件约束的,那么新版是如何进行约束的呢?
for _ in xrange(28):
k = st.posix.files[0].read_from(1)
st.solver.add(k != 0)
st.solver.add(k != 10)
  在angr==8.18.10.25中使用了claripy模块进行输入以及条件约束。
flag_chars = [claripy.BVS('flag_%d' % i, 8) for i in range(28)]
flag = claripy.Concat(*flag_chars + [claripy.BVV(b'\n')])

for k in flag_chars:
st.solver.add(k != 0)
st.solver.add(k != 10)
  虽然形式发生了变化,但是理解起来是一样的。
  只希望angr的api不要再经常变动了,这对于想学习angr的同学来说可真不友好
初涉angr  我们从比较简单的一题开始看起
defcamp_r100  载入IDA

深入浅出Angr(一)

深入浅出Angr(一)
  逻辑很清楚了,sub_4006FD函数中只有一个简单的运算,对于angr这么点运算量实在是微不足道。
import angr

def main():
p = angr.Project("r100",auto_load_libs=True)
simgr = p.factory.simulation_manager(p.factory.full_init_state())
simgr.explore(find=0x400844, avoid=0x400855)

return simgr.found[0].posix.dumps(0).strip(b'\0\n')

def test():
assert main().startswith(b'Code_Talkers')

if __name__ == '__main__':
print(main())
  使用p.factory.simulation_manager创建一个simulation_manager进行模拟执行,其中传入一个SimState.
  SimState对象通常有三种
1. blank_state(**kwargs)
返回一个未初始化的state,此时需要主动设置入口地址,以及自己想要设置的参数。
2. entry_state(**kwargs)
返回程序入口地址的state,通常来说都会使用该状态
3. full_init_state(**kwargs)
同entry_state(**kwargs) 类似,但是调用在执行到达入口点之前应该调用每个初始化函数
  除此之外还需要了解一下auto_load_libs参数,该参数用来设置是否自动载入依赖的库,如果设置为True会自动载入依赖的库,然后分析到库函数调用时也会进入库函数,这样会增加分析的工作量。如果为False,程序调用函数时,会直接返回一个不受约束的符号值。
  simgr.explore(find=0x400844, avoid=0x400855)然后使用simgr.explore进行模拟执行find是想要执行分支,avoid是不希望执行的分支。

深入浅出Angr(一)

深入浅出Angr(一)
  执行完之后找到一个符合条件的分支。

深入浅出Angr(一)

深入浅出Angr(一)
  <SimulationManager with 12 avoid, 1 found, 2 active>,此时相关的状态已经保存在了simgr当中,我们可以通过simgr.found来访问所有符合条件的分支。

深入浅出Angr(一)

深入浅出Angr(一)
  此时我们可以试想一下,我们该如何获取angr正确执行到0x400844分支所进行的输入呢?
  在官方的文档上我们可以知道SimState都有哪些参数和方法。
  不过我有时候更习惯的是help(simgr.found[0])

深入浅出Angr(一)

深入浅出Angr(一)
  可以比较清楚的知道SimState中保存了当前状态的寄存器,内存,输入输出等信息,这里我们需要使用posix,接下来在看一下posix都由哪些方法可以供我们使用。

深入浅出Angr(一)

深入浅出Angr(一)
  我比较关注的是dump方法,它可以返回相应文件描述符的内容,很明显为了获取输入,应该使用dump(0),因此完整的代码就应该如下simgr.found[0].posix.dumps(0)
  到此,虽然题目解决了,但是相信大家也都有许多很多困惑的地方,<SimulationManager with 12 avoid, 1 found, 2 active>中avoid 和 active又是什么呢?
  我试着探究一番。逐个打印出simgr.avoid的输入,发现有趣的地方。

深入浅出Angr(一)

深入浅出Angr(一)
  可以看到这12个avoid就是angr在模拟执行到0x400855分支的过程。
  再来看看active

深入浅出Angr(一)

深入浅出Angr(一)
  其中0x4007e4对应的是一个死循环。

深入浅出Angr(一)

深入浅出Angr(一)
  至此我们已经掌握了angr的基本用法了,用来解决一般的题目没有任何问题,我们只需设置好find 和 avoid就可以了。
  !但是要想真正发挥出angr的作用,可不仅仅如此,我们需要知道如何进行条件约束,以及如何hook,从而在有限的时间内,充分发挥出angr的用处。
  下一篇了解一下angr如何进行命令行传参并求解



温馨提示:
1.如果您喜欢这篇帖子,请给作者点赞评分,点赞会增加帖子的热度,评分会给作者加学币。(评分不会扣掉您的积分,系统每天都会重置您的评分额度)。
2.回复帖子不仅是对作者的认可,还可以获得学币奖励,请尊重他人的劳动成果,拒绝做伸手党!
3.发广告、灌水回复等违规行为一经发现直接禁言,如果本帖内容涉嫌违规,请点击论坛底部的举报反馈按钮,也可以在【投诉建议】板块发帖举报。
已有 1 人评分学币 理由
roger + 1 感谢您的热心解答,学到了!

总评分: 学币 + 1   查看全部评分

小黑屋|手机版|站务邮箱|学逆向论坛 ( 粤ICP备2021023307号 )|网站地图

GMT+8, 2024-11-22 02:55 , Processed in 0.131768 second(s), 43 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表