第一章的时候大概讲了 Angr 的一些基本概念和使用,我思量着应该要弄点实际的东西来练练才能把这个工具用熟捻。最经典的使用案例无疑是 angr_ctf 中的那些了:a83K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6B7j5h3E0W2M7%4m8J5K9h3&6Y4k6i4u0Q4x3V1k6S2L8X3N6J5i4K6g2X3j5%4c8X3题目本身都不是很难,甚至大多都是能靠人力完成的工作。但是即便如此,自动化也有自动化的意义对不对。毕竟我们现在需要的不是马上就能用它解决各种难题,而是把简单的问题解决,然后才能开始做复杂问题。附件使用的是 35cK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6K9c8g2u0a6i4K6u0V1b7g2)9J5k6p5!0z5c8g2)9J5c8V1q4F1k6%4u0o6g2p5k6Q4y4h3k6r3d9g2c8y4 仓库下编译好的版本。因为原仓库下只有源代码,而且编译还需要另外去配环境,所以这里直接用了这位师傅编译好的附件。一般的基本流程如下:当然还是得从最简单的开始,题目本身是一个直接用 IDA 读就能读明白的简单程序,但出于练习目的,还是得手写一下脚本。首先需要创建项目:创建 state:创建 SM:搜索路径:探索路径时需要给出需要查找到的路径地址,这里我们通过 IDA 可以确定程序输出 “Good Job.” 时的地址为 0x08048675求解结果:简单说明一下代码。程序本身很大,IDA 虽然也有办法反编译,但是速度极慢,但用 Angr 设定好参数就很快了。前几个步骤是一样的:我们不妨试试,如果按照上一题的做法会如何:结果会发现等了很久也没有算出结果,因为分支实在太多了。因此要对代码做一点改进:其实只是给 explore 增加了一个 avoid 的参数。当代码模拟执行遇到了该地址时,将会把这段路径放入到 avoided 的一个列表中,用来表示被避开的路径,然后其他照旧,继续执行。之所以通过添加这样的操作就能够得到答案,其实很简单,是为了避免路径爆炸而必要的。我们可以用这么一个二插树来表示路径: 我们用 1 来表示正确的路径,0 表示错误的路径。可以看见,在这个树中一共有 8 条不同的路径,而正确的路径只有一个。假设所有涉及到 0 的路径都会进入到某个地址 x 处。那么如果没有使用 avoid 参数,Angr 就会遍历这 8 条路径,然后求解出最左的那条路径所需的输入。而如果我们添加了 avoid=x ,那么当 Angr 从根节点进入到右子树时,由于接下来立刻进入到 x 地址处,因此停止分析这条路径,将其加入到 avoided 中,从而将下面的 4 条路径全都舍弃,将所需的时间直接减少了一半。同理,当它进入左子树时,仍然存在分叉,而进入右子树的分叉会因为相同的原因被舍弃,从而再次减少一半的时间。在路径极其庞大的情况下,比如说 2^31 条路径,通过这种方法能够极大程度降低消耗。还是这个模板:这题的情况和 00_angr_find 有些不太一样。尽管 IDA 将它们反编译后的结果看起来很像,但是在汇编中却有很大差别: 可以看见,这行输出在 main 函数里到处都是,所以其实很难找到真正的那条路径的地址。同理的,“Try again.” 也一样,因此需要修改 find 参数:可以发现,find 参数除了能是一个具体的地址外,还可以是一个函数。该函数返回 True 时会将路径记录下来,返回 False 时则表示路径并非我们想找的。而区别路径的关键在于 state.posix.dumps(1) ,通过该方法,可以将 stdout 中的内容 dump 出来进行比较。如果输出包含了 Good Job. ,我们就认为是想要的路径。这样就能避开直接使用地址了。当然了,avoid 也可以这么用,读者可以自行试试。还是老三样:有些特殊的地方是,输入使用 get_user_input ,而该函数如下:前文曾提到过,Angr 对 scanf 这类使用格式化字符串的函数支持并不是很好,不过或许是最近的版本更新,直接这样写也同样能得到结果了:不过既然是学习,还是照例看看最标准的写法应该是什么吧。根据汇编可以看到,该函数的实际操作是将值储存在寄存器中:因此我们可以直接将该函数钩取,然后手动设置寄存器的值:由于现在我们再从 entry_point 进入了,而需要跳过 get_user_input 函数,因此使用 blank_state 来初始化状态,并将开始地址设定在该函数之后的第一条指令处。接下来创建三个位置的符号向量,将他们设定为寄存器:此处引入另外一个 claripy 包来创建符号向量: claripy.BVS(name,size) 。创建完成后即可生成 SM 并开始探索了。完成探索后,最后需要求解符号向量的值:到这一步其实就差不多轻车熟路一把梭搞定了:不过这道题实际上和上一题类似,但输入值储存在栈中,因此标准做法其实是将内存符号化进行求解:通过 state.memory.store(addr,value) 可以对内存进行符号化,从而在路径发现以后进行求解。这道题同样因为现在的 Angr 功能强大而不需要以前那样复杂的技巧了:而题目的本意是让我们将内存符号化,其实和上一题一样,直接对内存进行存储就行了:和上一题不同的地方在于,这次的存储位置为堆内存,我们不能直接给出一个地址然后去存储。一把梭还是可行的:而标准做法是:通过这题就能够理解符号执行的一个好处了。由于它并不是真的去执行,只是模拟执行代码而已,所以对地址本身没有限制,完全可以随意设定内存的使用方法。另外 endness 参数用于指定储存的端序,而 project.arch.memory_endness 将会反映程序所在平台的默认端序,此处为小端序。可以发现程序调用了 fopen 去打开文件,对于这种情况,Angr 也同样提供了模拟文件的系统。同样的,照旧一把梭也能搞定:不过还是来看看它的模拟文件系统吧:前几个还是照旧,但是也有一些新东西:angr.storage.SimFile 提供了一个模拟文件系统,通过 state.fs.insert 可以将该模拟出来的文件插入到 state 符号中。这样在模拟执行时就会用该文件替代真实情况下的文件了。而 angr.storage.SimFile 的 filename 参数表示文件名,content 参数表示文件内容,size 参数表示文件大小,单位为字节。在这里就能遇到之前所说的 “路径爆炸” 问题了。照例试试一把梭:会发现这次就没办法那么顺利得到答案了,Angr 求解了半天却一直没有给出 “yes” 的回答,因此这次我们必须手动去优化求解的过程。分析 check_equals_AUPDNNPROEZRJWKB 函数可以发现,该函数实际上是在对输入和 password 对比,而 password 的值是固定的 AUPDNNPROEZRJWKB 。因此第
...(已截断)
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-276860.htm
[原创] Angr 使用技巧速通笔记(二)
434 浏览
8 回复
学习了
牛的,我直接点赞收藏一套带走
大佬太强了
太细了
感谢分享
感谢分享
感谢分享
04的脚本不对 应该用state.stack_push()