[原创] Angr 使用技巧速通笔记(一)
2023-4-12 19:29:15 Author: bbs.pediy.com(查看原文) 阅读量:8 收藏

在基本了解了模糊测试以后,接下来就开始看看一直心心念念的符号执行吧。听群友说这个东西的概念在九几年就有了,算是个老东西,不过 Angr 本身倒是挺新的,看看这个工具能不能有什么收获吧。

按照计划,一方面是 Angr 的使用技巧,另一方面是 Angr 的源代码阅读。不过因为两者的内容都挺多的,所以本篇只写使用技巧部分,如果未来有这样的预订,或许还会有另外一篇。希望以我这种菜鸡水平也能看得懂吧。

Angr 其实并不是真正被运行起来的,它就向一个虚拟机,会读取每一条命令并在虚拟机中模拟该命令的行为。我们类比到更加常用的 z3 库中,每个寄存器都可以相当与 z3 中的一个变量,在模拟执行的过程中,这个变量会被延伸为一个表达式,而当我们成功找到了目标地址之后,通过表达式就可以求解对应的初值应该是什么了。

Angr 被称之为 IR-Based 类的符号执行引擎,他会对输入的二进制重建对应的 CFG ,在完成重建后开始模拟执行。而对于分支语句,就需要分支出两个不同的情况:跳转不跳转 。在一般情况下,这不会引发问题,但是我们可以考虑如下的代码:

循环本身是一个死循环,尽管我们靠自己的思维能够理解,它会在未来的某一个跳出循环,但符号执行引擎却不知道这件事,因此每一次遇到判断跳转都需要进行分叉,最后这个路径就会无限增长,最后把内存挤爆,然后程序崩溃。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

>>> obj=project.loader.main_object

>>> obj.plt 

{'strcmp': 134513616, 'printf': 134513632, '__stack_chk_fail': 134513648, 'puts': 134513664, 'exit': 134513680, '__libc_start_main': 134513696, '__isoc99_scanf': 134513 

712, '__gmon_start__': 134513728}

>>> obj.sections 

<Regions: [<Unnamed | offset 0x0, vaddr 0x0, size 0x0>, <.interp | offset 0x154, vaddr 0x8048154, size 0x13>, <.note.ABI-tag | offset 0x168, vaddr 0x8048168, size 0x20

, <.note.gnu.build-id | offset 0x188, vaddr 0x8048188, size 0x24>, <.gnu.hash | offset 0x1ac, vaddr 0x80481ac, size 0x20>, <.dynsym | offset 0x1cc, vaddr 0x80481cc, siz 

e 0xa0>, <.dynstr | offset 0x26c, vaddr 0x804826c, size 0x91>, <.gnu.version | offset 0x2fe, vaddr 0x80482fe, size 0x14>, <.gnu.version_r | offset 0x314, vaddr 0x804831 

4, size 0x40>, <.rel.dyn | offset 0x354, vaddr 0x8048354, size 0x8>, <.rel.plt | offset 0x35c, vaddr 0x804835c, size 0x38>, <.init | offset 0x394, vaddr 0x8048394, size 

0x23>, <.plt | offset 0x3c0, vaddr 0x80483c0, size 0x80>, <.plt.got | offset 0x440, vaddr 0x8048440, size 0x8>, <.text | offset 0x450, vaddr 0x8048450, size 0x4ea2>, < 

.fini | offset 0x52f4, vaddr 0x804d2f4, size 0x14>, <.rodata | offset 0x5308, vaddr 0x804d308, size 0x39>, <.eh_frame_hdr | offset 0x5344, vaddr 0x804d344, size 0x3c>, 

<.eh_frame | offset 0x5380, vaddr 0x804d380, size 0x110>, <.init_array | offset 0x5f08, vaddr 0x804ef08, size 0x4>, <.fini_array | offset 0x5f0c, vaddr 0x804ef0c, size 

0x4>, <.jcr | offset 0x5f10, vaddr 0x804ef10, size 0x4>, <.dynamic | offset 0x5f14, vaddr 0x804ef14, size 0xe8>, <.got | offset 0x5ffc, vaddr 0x804effc, size 0x4>, <.go 

t.plt | offset 0x6000, vaddr 0x804f000, size 0x28>, <.data | offset 0x6028, vaddr 0x804f028, size 0x15>, <.bss | offset 0x603d, vaddr 0x804f03d, size 0x3>, <.comment | 

offset 0x603d, vaddr 0x0, size 0x34>, <.shstrtab | offset 0x67fa, vaddr 0x0, size 0x10a>, <.symtab | offset 0x6074, vaddr 0x0, size 0x4d0>, <.strtab | offset 0x6544, va 

ddr 0x0, size 0x2b6>]>

我们知道,在一般情况下,加载程序都会将 auto_load_libs 置为 False ,这是因为如果将外部库一并加载,那么 Angr 就也会跟着一起去分析那些库了,这对性能的消耗是比较大的。

而对于一些比较常规的函数,比如说 mallocprintfstrcpy 等,Angr 内置了一些替代函数去 hook 这些系统库函数,因此即便不去加载 libc.so.6 ,也能保证分析的正确性。这部分内容接下来会另说。

我们知道,加载一个二进制程序只是符号执行能够开始的第一步,为了实现符号执行,我们还需要为这个二进制程序去构建符号、执行流等操作。这些操作会由 Angr 帮我们完成,而它也提供一些方法能够让我们获取到它构造的一些细节。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

>>> project.factory.block(project.entry) 

<Block for 0x8048450, 33 bytes> 

>>> project.factory.block(project.entry).pp() 

        _start: 

8048450  xor     ebp, ebp 

8048452  pop     esi 

8048453  mov     ecx, esp 

8048455  and     esp, 0xfffffff0 

8048458  push    eax 

8048459  push    esp 

804845a  push    edx 

804845b  push    __libc_csu_fini 

8048460  push    __libc_csu_init 

8048465  push    ecx 

8048466  push    esi 

8048467  push    main 

804846c  call    __libc_start_main

>>> project.factory.block(project.entry).instruction_addrs 

(134513744, 134513746, 134513747, 134513749, 134513752, 134513753, 134513754, 134513755, 134513760, 134513765, 134513766, 134513767, 134513772)

可以看出 Angrcall 指令作为一个基本块的结尾。在 Angr 中,它所识别的基本块和 IDA 里看见的 CFG 有些许不同,它会把所有的跳转都尽可能的当作一个基本块的结尾。

通过 state.regs.eip 可以看出,所有的寄存器都会替换为一个符号。该符号可以由模块自行推算,也可以人为的进行更改。也正因如此,Angr 能够通过条件约束对符号的值进行解方程,从而去计算输入,比如说:

这些构造函数都能通过参数 addr 来指定初始时的 rip/eip 地址。而 call_state 可以用这种方式来构造传参:call_state(addr, arg1, arg2, ...)

因此如果程序中调用了这部分函数,默认情况下就会由 angr.procedures.libc 中实现的函数进行接管。但是请务必注意,官方文档中也有提及,一部分函数的实现并不完善,比如说对 scanf 的格式化字符串支持并不是很好,因此有的时候需要自己编写函数来 hook 它。


文章来源: https://bbs.pediy.com/thread-276834.htm
如有侵权请联系:admin#unsafe.sh