路径爆炸

之前说过,angr在处理分支时,采取统统收集的策略,因此每当遇见一个分支,angr的路径数量就会乘2,这是一种指数增长,也就是所说的路径爆炸。

以下是路径爆炸的一个例子:

  1. char buffer[17];
    for(int i=0;i<16;i++){
  2. if(buffer[i]!='B'){
  3. return 0;
  4. }
  5. }
  6. return 1;

buffer的字符长度为16,当它等于16个连续的'B'时,程序返回1,然而angr在探索这样的路径时,会遇上16次if语句,也就相应地产生2的16次方个路径,但正确的答案只有一条路径能够达到(if全为false的那一条),这一条路径就被淹没在大量错误路径中,产生了路径爆炸。

因此当遇见大量分支(尤其是循环中嵌套的if语句)时,必须想办法缓解这种路径爆炸。

angr常用的缓解路径爆炸的方式有四种:

  • 重写可能导致路径爆炸的函数或代码片段,可以采用angr提供的hook
  • veritesting:结合动态符号执行和静态符号执行
  • Lazy Solve:跳过不必要的函数
  • Symbion:结合具体执行和符号执行

在angr_ctf中只涉及到了前两种方式,因此本篇着重说说第一种方式,以及简略介绍下第二种方式。angr缓解路径爆炸的策略,后续新开一篇详细谈谈。

重写代码片段

仍然以上一节路径爆炸中用的代码片段为例,字符串buffer要按位比较16次从而产生2的16次方条路径,它的路径大致如下图:

angr必须选择16次F才能达到目标状态。

然而根据逆向分析得出的结果来看,只要让buffer='B'*16就可以通过这段代码了,因此如果我们之间跳过这段代码,然后在跳过了这段代码的state中添加一个约束:buffer='B'*16,这样就不会产生路径了。

你可能会问,既然我们都通过逆向的手段看出来buffer等于16个B了,那为什么还要使用angr呢。

然而如果真正的目标状态在该代码片段的后面,这段会产生路径爆炸的代码如果不绕开的话,你很可能无法达到真正的目标状态。

Hook

Hook可以译为挂钩或者钩子,是逆向工程中改变程序运行流程的技术,可以让自己的代码运行在其他程序当中。通常说Hook函数,则是接收被Hook函数的参数,然后使用这些参数执行自己的代码,并将返回值返回(也可以没有返回值),至于被Hook的函数,可以选择让它继续执行,或者不执行。

听上去Hook技术很麻烦,让其他程序运行自己写的代码似乎挺不可思议的。Hook技术值得深入研究,但angr提供了简便的Hook接口,能够用于Hook被angr模拟执行的程序,它通常有两种方式:

  • 装饰器@project.hook
  • 函数project.hook_symbol

装饰器@project.hook

  1. @project.hook(hook_addr, length=ins_length)
  2. def hook_ins(state):
  3. #your code

请注意,这里的project.hook来自于你使用angr.Project方法创建的顶层模块(即project=angr.Project('filename'))。

该方法相当于在hook_addr处删去长度为length的指令,然后用hook_ins中的指令替代。

该装饰器需要传入待hook的地址,以及要跳过的指令的长度,然后在hook_ins中实现你的代码即可。

使用装饰器的方法,如果是对函数进行hook而不是对某一代码片段进行hook的话,无法接收函数的参数,需要根据情况来使用。

hook_symbol

假设程序中有一个这样的原始函数:

  1. int my_add(int a, int b){
  2. return a+b;
  3. }

那么这样的函数可以被下面的方式hook掉:

  1. class Replace_my_add(angr.SimProcedure):
  2. def run(self, a, b):
  3. return a - b
  4.  
  5. hooked_func_name = 'my_add'
  6. project.hook_symbol(hooked_func_name, Replace_my_add())

该方法相当于替换了原函数,为此你需要创建一个类,继承自angr.SimProcedure,类名随意。

然后在类中重写run方法,run的self后的参数与待hook的函数相同。

最后在主函数中,调用project.hook_symbol,并传入待hook的函数名以及新建的继承自SimProcedure的类的对象即可。

设立返回值

  • 对于装饰器

该方法相当于代码片段的替换,因此如果你选择跳过了某条函数调用语句(例如 call my_add这条汇编指令),那么就需要去查看该函数的返回值,或者说代码片段的最终计算结果,被保存在了什么地方。找到这个地方,然后使用内存或者寄存器操作,将返回值填入即可。

一般来说,32位程序函数的返回值会放在eax寄存器中

  • 对于hook_symbol

该方式相当于替换了函数的内容,因此直接在重写的run方法中使用return返回即可

返回值的时候,如果需要根据某个符号变量来决定返回的内容(例如判断一个符号变量等于某个字符串时返回1,否则返回0),此时不能用python的if语句来判断,然后分别返回1和0,这有两个问题:

  • 符号变量与其他内容进行比较(无论是符号变量、位向量、常数、变量),它的真值永远是false
  • 如果返回值后续参与了其他的路径选择,此时若只返回了值而不是带约束的符号变量,可能会造成约束丢失

这种情况下,需要使用claripy.If来返回值:

  1. state.regs.eax = claripy.If(buffer == target, claripy.BVV(1, 32),claripy.BVV(0, 32))

上面代码的意思是,当buffer==target时,将位向量<BVV32 1>传递给eax,否则将位向量<BVV32 0>传递给eax

而如果不这么做,你可能会写出这样的代码:

  1. if buffer==target:
  2. state.regs.eax = claripy.BVV(1,32)
  3. else:
  4. state.regs.eax = claripy.BVV(0,32)

这两种做法的区别在于,如果buffer或target中至少存在一个符号变量,下面的方式将永远判断为false。

此外,两种方式传递给eax的内容也不同,下面一种方式传递的是一个位向量,而上面的方式传递的是带约束的符号变量(之后实战中你会看到更多)。

Veritesting

veritesting意为路径归并,它在动态符号执行(DSE)和静态符号执行(SSE)之间切换,结合两种方式减少了路径爆炸的影响。

  • DSE:为路径生成公式,生成公式时负载很高,但公式易解
  • SSE:为语句生成公式,公式生成容易,也能覆盖更多路径,但更难解

详细可参见这篇论文:Enhancing symbolic execution with veritesting (acm.org)

在angr中,要启动veritesting,有以下两种方式:

  1. p = angr.Project('/bin/true')
  2. state = p.factory.entry_state()
  3.  
  4. #初始化SM时启动
  5. simgr = p.factory.simgr(state, veritesting=True)
  6.  
  7. #或者在SM初始化后启动
  8. simgr = p.factory.simgr(state)
  9. simgr.use_technique(angr.exploration_techniques.Veritesting())

state插件

之前提到的angr的求解器solver,以及操作内存、寄存器、文件系统的方法:memory、regs、fs等,实际上都是state的插件,下面说明两个其他常用的插件。

全局插件global

state.global实现了标准python dict接口,使用它你能够在模拟执行的state中存储任意数据,并且很方便的就能访问到。可以使用如下的语句访问全局变量:

  1. state.globals['x'] = a_symbol

全局变量名x不需要提前声明,但当你需要使用这个全局变量之前,需要对它赋值。

history插件

history记录了一个状态的执行路径,它是一个链表,每个节点代表一次执行的状态。你可以不断使用.parent来回溯这个链表,history的使用方式如下:

  • state.history.bbl_addrs:返回当前state执行的基本块的地址列表
  • state.history.parent.bbl_addrs:返回上一个state执行的基本块的地址列表
  • state.history.descriptions:描述state上每轮执行的状态的字符串列表
  • state.histroy.guards:到达当前state所走路径需要满足的条件的列表(也就是每次分支判断时为True还是False)
  • state.history.events:执行过程中发生的事件列表,比如符号跳转条件、弹出消息框等

此外,在每种方法前加上recent_,就可以获取列表中最近的那个地址,例如state.history.recent_bbl_addrs会返回当前state最近执行的基本块的地址。

实战:08_angr_constraints

先逆向查看逻辑

和之前一样,先对输入进行混淆,然后再判断。

但这里的问题在于,首先字符串的长度为16,此外,在check_equals函数中,采用的是按位比较的方式:

而字符串长度为16,那么就会进行16次比较,从而产生2的16次方条路径,这样angr就会陷入路径爆炸。

但能够注意到,check函数中实际上是在让buffer与password进行比较(0x0804a040就是password的地址),而password在main的开头被初始化为一串已知的字符串。

因此可以考虑让模拟运行在调用check函数之前停下来,然后添加一个符号约束(buffer==已知的字符串),之后再用求解器求解buffer即可。

angr脚本如下:

  1. import angr
  2. import claripy
  3. import time
  4.  
  5. def good(state):
  6. tag = b'Good' in state.posix.dumps(1)
  7. return True if tag else False
  8.  
  9. def bad(state):
  10. tag = b'Try' in state.posix.dumps(1)
  11. return True if tag else False
  12.  
  13. time_start = time.perf_counter()
  14.  
  15. path_to_binary = './dist/08_angr_constraints'
  16. p = angr.Project(path_to_binary, auto_load_libs=False)
  17. start_addr = 0x08048625
  18. init_state = p.factory.blank_state(addr=start_addr)
  19.  
  20. buffer = claripy.BVS('buffer', 16*8)
  21. # buffer的地址
  22. buffer_addr = 0x0804a050
  23. init_state.memory.store(buffer_addr, buffer)
  24.  
  25. simgr = p.factory.simgr(init_state)
  26.  
  27. # call check指令的地址
  28. check_addr = 0x08048565
  29. # 让程序模拟运行到call check时就标记为目标状态
  30. simgr.explore(find=check_addr)
  31.  
  32. if simgr.found:
  33. solution_state = simgr.found[0]
  34. # 由于buffer经过混淆函数混淆发生了变化
  35. # 因此需要从buffer的内存地址中取出buffer目前的值
  36. buffer_current = solution_state.memory.load(buffer_addr, 16)
  37. key = 'AUPDNNPROEZRJWKB'
  38.  
  39. # 为当前状态添加约束
  40. solution_state.solver.add(buffer_current == key)
  41.  
  42. # 求解buffer
  43. solution = solution_state.solver.eval(buffer, cast_to=bytes)
  44. print('[+]Success:', solution.decode())
  45.  
  46. else:
  47. raise Exception("Could not find solution")
  48.  
  49. print('program last: ', time.perf_counter() - time_start)

你大概有两个问题:

  • 为何要重新从buffer_addr中取出符号变量,不能直接使用buffer吗?
  • 在实战05中输入字符串的长度为32,然后使用了strncmp进行比较,strncmp的底层实现也是按位比较的,为何之前不用考虑路径爆炸的问题呢?

首先第一个问题,我们可以先将buffer打印出来,应该能得到一个位向量<BV128 buffer_0_128>。该位向量之后被我们保存在了buffer的地址当中。

然后打印一下buffer_current:

  1. _128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:0]))[63:32] >> 0x3[7:0] + 26 * 0xffffffbf + (buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:7] .. buffer_0_128[7:0]) >> 0x1f[7:0]>

会得到一个极为庞大的符号变量,或者说一个表达式,这是位向量<BV128 buffer_0_128>经过混淆函数混淆后的结果,因此要求解的应该是这个表达式。

我们创建的符号buffer被保存在了buffer的地址当中,然后模拟程序运行时,从地址中取出符号进行混淆,混淆的过程也被保存在buffer的地址中,因此在最后需要从这个地址里面取出混淆后的符号进行求解。

第二个问题,先说结论:angr在路径选择时只进入库函数一次,而不关心库函数内部的实现采用了多少次if。

编写以下两个c程序并编译(我这里使用gcc编译成64位了,与题目的32位不符,但原理相同):

  1. #include<stdio.h>
  2.  
  3. char buffer[9];
  4. char passwd[9] = "abcdefgh";
  5.  
  6. int main(){
  7. scanf("%8s", buffer);
  8. if (strncmp(buffer, passwd ,8)==0){
  9. printf("good");
  10. }
  11. else{
  12. printf("bad");
  13. }
  14. }
  1. #include<stdio.h>
  2.  
  3. char buffer[9];
  4. char passwd[9] = "abcdefgh";
  5.  
  6. int check(int size){
  7. int i = 0;
  8. for(i;i<size;i++){
  9. if(buffer[i]!=passwd[i]){
  10. return 0;
  11. }
  12. }
  13. return 1;
  14. }
  15.  
  16. int main(){
  17. scanf("%8s", buffer);
  18. printf("%s\n",buffer);
  19. if (check(8)){
  20. printf("good");
  21. }
  22. else{
  23. printf("bad");
  24. }
  25. }

两个程序都接收8个字符,然后与abcdefgh进行比较,区别在于第一个程序使用库函数strncmp进行比较,而后一个程序则自己定义了一个check进行比较,两者比较的内部原理都是按位循环比较(strncmp还进行了一些额外处理,但基本原理仍然是按位循环比较,具体可参见strncmp源码)

然后,我们使用符号变量填充到buffer对应的地址中,让模拟执行从scanf后的语句开始执行,并以打印good为目标状态。

获取目标状态后,使用history插件打印出当前state执行的基本块,这些步骤的angr代码如下:

  1. import angr
  2. import claripy
  3. import time
  4.  
  5. def good(state):
  6. tag = b'good' in state.posix.dumps(1)
  7. return True if tag else False
  8.  
  9. def bad(state):
  10. tag = b'bad' in state.posix.dumps(1)
  11. return True if tag else False
  12.  
  13. start_time = time.perf_counter()
  14. p = angr.Project('./a', auto_load_libs=False)
  15. init_state = p.factory.blank_state(addr=0x40118b)
  16.  
  17. buffer_addr = 0x404060
  18. x = claripy.BVS('x', 8 * 9)
  19. init_state.memory.store(buffer_addr, x)
  20.  
  21. simgr = p.factory.simgr(init_state)
  22. simgr.explore(find=good, avoid=bad)
  23.  
  24. if simgr.found:
  25. solution_state = simgr.found[0]
  26. x_current = solution_state.memory.load(buffer_addr, 8)
      # 打印路径
  27. for address in solution_state.history.bbl_addrs:
  28. print(hex(address))
  29. result = solution_state.solver.eval(x_current, cast_to=bytes)
  30. print(result)
  31. else:
  32. raise Exception("Could not find solution")
  33.  
  34. print(time.perf_counter() - start_time)

两个程序的angr脚本相同,修改一下地址和文件名即可,此外还需要注意,如果编译时没加任何参数,装载的地址可能会跟你在IDA中看到的不一样,例如我使用angr时提示如下:

  1. WARNING | 2022-11-29 02:41:46,685 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.

它提示我们,程序被装载到基地址0x400000中了,而我们在IDA中看到的程序的地址main函数以0x1169开头,这是一个相对地址,因此在编写angr脚本时,要将这些相对地址加上0x400000.

此时打印出来两种判断方式的路径如下:

  1. #程序1
  2. 0x40118b 0x401040 0x500010 0x40119a 0x401030 0x500008 0x4011b8 0x401050 0x500018 0x4011ce 0x401030 0x500008 0x4011ec 0x4011f0 0x401050 0x500018
  3. #程序2
  4. 0x4011c9 0x401030 0x500008 0x4011d8 0x401159 0x401198 0x401169 0x401194 0x401169 0x401194 0x401169 0x401194 0x401169 0x401194 0x401169 0x401194 0x401169 0x401194 0x401169 0x401194 0x401169 0x401194 0x4011a0 0x4011e2 0x4011e6 0x401040 0x500010

程序1中0x40119a所在的基本块就是调用strncmp的基本块,它只被执行了一次,因此在if语句中只会产生2个路径。

而程序2中check函数内部的循环语句所在的基本块0x401169和0x401194被执行了8次,从而产生了2的8次方个路径。

因此可以得到开头的结论,库函数内部如何实现,与angr无关,并不会产生额外的路径。

实战:09_angr_hooks

仍然先逆向查看逻辑

程序先接收一个输入,输入被混淆后与password进行比较,password被初始化为一个已知的字符串。

然后程序混淆password,再接收一个输入,将输入与混淆后的password进行比较。(两个for循环中的地址分别是buffer和password的地址)

第一次比较在check函数中按位比较,后一次比较则是使用了strncmp函数,因此路径爆炸发生在check函数中,对于strncmp则无须处理。

这次使用装饰器的方法来解决这个问题:

  1. 使用装饰器跳过调用check函数的指令,也就是call cehck_equals
  2. 在装饰器内部实现符号==AUPDNNPROEZRJWKB的约束
  3. 将符号约束作为返回值返回,查看汇编代码可以知道,check函数的返回值保存在了eax寄存器中

angr脚本如下:

  1. import angr
  2. import time
  3. import claripy
  4.  
  5. time_strat = time.perf_counter()
  6.  
  7. def good(state):
  8. tag = b'Good' in state.posix.dumps(1)
  9. return True if tag else False
  10.  
  11. def bad(state):
  12. tag = b'Try' in state.posix.dumps(1)
  13. return True if tag else False
  14.  
  15. path_to_binary = './dist/09_angr_hooks'
  16. p = angr.Project(path_to_binary, auto_load_libs=False)
  17. init_state = p.factory.entry_state()
  18. # call check_equal的地址
  19. check_addr = 0x080486b3
  20. # call check_equal的指令長度
  21. ins_size = 5
  22.  
  23. @p.hook(check_addr, length=ins_size)
  24. def skip_check(state):
  25. buffer_addr = 0x0804a054
  26. buffer = state.memory.load(buffer_addr, 16)
  27. target = 'XYMKBKUHNIQYNQXE'
  28. # 使用claripy.If返回一个符号约束
  29. tmp = claripy.If(buffer == target, claripy.BVV(1, 32), claripy.BVV(0, 32))
  30. print(type(tmp))
  31. print(tmp)
  32. state.regs.eax = tmp
  33.  
  34. simgr = p.factory.simgr(init_state)
  35. simgr.explore(find=good, avoid=bad)
  36.  
  37. if simgr.found:
  38. solution_state = simgr.found[0]
  39. flag = solution_state.posix.dumps(0)
  40. print(flag)
  41. else:
  42. raise Exception("Could not find solution")

打印出tmp的内容,其中最后的片段如下:

  1. packet_0_stdin_4_128[7:7] .. packet_0_stdin_4_128[7:7] .. packet_0_stdin_4_128[7:7] .. packet_0_stdin_4_128[7:7] .. packet_0_stdin_4_128[7:7] .. packet_0_stdin_4_128[7:7] .. packet_0_stdin_4_128[7:7] .. packet_0_stdin_4_128[7:7] .. packet_0_stdin_4_128[7:7] .. packet_0_stdin_4_128[7:7] .. packet_0_stdin_4_128[7:0]) >> 0x1f[7:0]) == 0x58594d4b424b55484e4951594e515845 then 0x1 else 0x0>

tmp前面的内容即buffer的内容,是经过混淆计算后的符号,而在最后可以看到,claripy.If创建了一个判断,因此实际上返回了一个约束,这就是为什么不适用python中的if然后传递一个<BVV32 1>的原因。

实战:10_angr_simprocedures

第十题的路径爆炸仍然发生在check_equals函数当中,与上一题的区别在于,此时输入的字符串被保存在局部变量当中,我们无法提前得知它的地址,因此如果要hook掉check函数,则必须获取check函数的参数。

因此需要使用hook_symbol的方式来hook掉check函数

使用方式之前说明了,只需要注意使用hook_symbol的方法本质上是创建了新的函数取代了原有函数,因此在返回值的时候使用return返回,而不用将值保存在eax当中(事实上重写的run方法中没有当前的state,你也保存不到eax里面)

angr脚本如下:

  1. import angr
  2. import time
  3. import claripy
  4.  
  5. time_strat = time.perf_counter()
  6.  
  7. def good(state):
  8. tag = b'Good' in state.posix.dumps(1)
  9. return True if tag else False
  10.  
  11. def bad(state):
  12. tag = b'Try' in state.posix.dumps(1)
  13. return True if tag else False
  14.  
  15. path_to_binary = './dist/10_angr_simprocedures'
  16. p = angr.Project(path_to_binary, auto_load_libs=False)
  17. init_state = p.factory.entry_state()
  18.  
  19. class Hook(angr.SimProcedure):
  20.  
  21. def run(self, addr, size):
  22. # 后两个参数即为check_equals函数的参数
  23. input_buffer = addr
  24. input_size = size
  25.  
  26. # 从内存中读取输入
  27. input_str = self.state.memory.load(input_buffer, input_size)
  28. right_str = 'ORSDDWXHZURJRBDH'
  29.  
  30. # 比较返回符号约束
  31. result = claripy.If(input_str == right_str, claripy.BVV(1, 32),
  32. claripy.BVV(0, 32))
  33.  
  34. return result
  35.  
  36. # funcname一定要相同,可在IDA中查看
  37. hooked_funcname = 'check_equals_ORSDDWXHZURJRBDH'
  38. p.hook_symbol(hooked_funcname, Hook())
  39.  
  40. simgr = p.factory.simgr(init_state)
  41. simgr.explore(find=good, avoid=bad)
  42.  
  43. if simgr.found:
  44. solution_state = simgr.found[0]
  45. flag = solution_state.posix.dumps(0)
  46. print(flag)

实战:11_angr_sim_scanf

此题与实战2-7中的题类似,实际上使用万能脚本就可以成功。出于练习的目的,跟着angr_ctf作者的思路,我们使用hook_symbol方法来hook掉scanf函数。

angr脚本如下,使用到了state的全局变量插件,用法很简单,用于进行约束求解。如果不使用全局变量的话,从buffer的地址中取出符号进行求解也是一样的(就像在实战8中做的那样)

  1. import angr
  2. import claripy
  3.  
  4. def good(state):
  5. tag = b'Good' in state.posix.dumps(1)
  6. return True if tag else False
  7.  
  8. def bad(state):
  9. tag = b'Try' in state.posix.dumps(1)
  10. return True if tag else False
  11.  
  12. path_to_binary = './dist/11_angr_sim_scanf'
  13. project = angr.Project(path_to_binary, auto_load_libs=False)
  14.  
  15. initial_state = project.factory.entry_state()
  16.  
  17. class ReplacementScanf(angr.SimProcedure):
  18. # 第一个参数是scanf的格式化字符串,接着是两个buffer地址
  19. def run(self, format_string, scanf0_address, scanf1_address):
  20. # 在run中完成scanf的功能,即向目标地址写入符号
  21. # 定义两个符号变量,用于写入buffer
  22. scanf0 = claripy.BVS('scanf0', 32)
  23. scanf1 = claripy.BVS('scanf1', 32)
  24.  
  25. # 写入内存,最后一个参数指定字节序
  26. self.state.memory.store(scanf0_address,
  27. scanf0,
  28. endness=project.arch.memory_endness)
  29. self.state.memory.store(scanf1_address,
  30. scanf1,
  31. endness=project.arch.memory_endness)
  32.  
  33. # 保存变量到全局,方便后续约束求解
  34. self.state.globals['solution0'] = scanf0
  35. self.state.globals['solution1'] = scanf1
  36.  
  37. # scanf的函数名称,可在ida中查看
  38. scanf_symbol = '__isoc99_scanf'
  39. project.hook_symbol(scanf_symbol, ReplacementScanf())
  40.  
  41. simulation = project.factory.simgr(initial_state)
  42.  
  43. simulation.explore(find=good, avoid=bad)
  44.  
  45. if simulation.found:
  46. solution_state = simulation.found[0]
  47.  
  48. # 对符号变量进行约束求解
  49. stored_solutions0 = solution_state.globals['solution0']
  50. stored_solutions1 = solution_state.globals['solution1']
  51. scanf0_solution = solution_state.solver.eval(stored_solutions0)
  52. scanf1_solution = solution_state.solver.eval(stored_solutions1)
  53.  
  54. print(scanf0_solution, scanf1_solution)
  55.  
  56. else:
  57. raise Exception('Could not find the solution')

实战:12_angr_veritesting

先逆向分析

可以看到,此题与之前的题目不同的地方在于,它不仅是按位进行比较的,而且是按位进行混淆后马上比较。之前的题目都是整体混淆后比较。因此路径爆炸同时出现在混淆和比较两个地方。

之前的做法是跳过循环比较,直接让混淆后的字符串与正确的字符串比较,但这里不能这样做,跳过循环比较就意味着跳过了混淆。

因此考虑使用路径归并的搜索策略veritesting,此题就是用来体验一把veritesting的强大功能的。

angr脚本如下,直接使用万能脚本,并且开启veritesting:

  1. import angr
  2. import claripy
  3. import time
  4.  
  5. start_time = time.perf_counter()
  6. p = angr.Project('./dist/12_angr_veritesting', auto_load_libs=False)
  7. init_state = p.factory.entry_state()
  8.  
  9. def good(state):
  10. tag = b'Good' in state.posix.dumps(1)
  11. return True if tag else False
  12.  
  13. def bad(state):
  14. tag = b'Try' in state.posix.dumps(1)
  15. return True if tag else False
  16.  
  17. simgr = p.factory.simgr(init_state, veritesting=True)
  18. simgr.explore(find=good, avoid=bad)
  19.  
  20. if simgr.found:
  21. solution_state = simgr.found[0]
  22. print(solution_state.posix.dumps(0).decode())
  23.  
  24. print(time.perf_counter() - start_time)

(不加veritesting直接能跑死机,大佬的智慧真是看不懂,但是真好用)

angr_ctf——从0学习angr(三):Hook与路径爆炸的更多相关文章

  1. tensorflow2.0 学习(三)

    用tensorflow2.0 版回顾了一下mnist的学习 代码如下,感觉这个版本下的mnist学习更简洁,更方便 关于tensorflow的基础知识,这里就不更新了,用到什么就到网上取搜索相关的知识 ...

  2. Servlet3.0学习总结(三)——基于Servlet3.0的文件上传

    在Servlet2.5中,我们要实现文件上传功能时,一般都需要借助第三方开源组件,例如Apache的commons-fileupload组件,在Servlet3.0中提供了对文件上传的原生支持,我们不 ...

  3. Spark2.0学习(三)--------核心API

    Spark核心API----------------- [SparkContext] 连接到spark集群,入口点. [HadoopRDD] 读取hadoop上的数据, [MapPartitionsR ...

  4. 【JMeter4.0学习(三)】之SoapUI创建WebService接口模拟服务端以及JMeter对SOAP协议性能测试脚本开发

    目录: 创建WebService接口模拟服务端 下载SoapUI 新建MathUtil.wsdl文件 创建一个SOAP项目 接口模拟服务端配置以及启动 JMeter对SOAP协议性能测试脚本开发 [阐 ...

  5. NPOI2.0学习(三)

    HSSFWorkbook wk = new HSSFWorkbook();//你用来操作的HSSFWorkbook的实例 ICellStyle cellStyle = wk.CreateCellSty ...

  6. Vue 2.0学习(三)指令与事件

    指令(Directives)是Vue.js模板中最常用的一项功能,它带有前缀v-,前面已经使用过v-html.v-pre等.指令的主要职责就是当表达式的值改变时,相应地将某些行为应用到DOM上. v- ...

  7. canvas学习总结三:绘制路径-线段

    Canvas绘图环境中有些属于立即绘制图形方法,有些绘图方法是基于路径的. 立即绘制图形方法仅有两个strokeRect(),fillRect(),虽然strokezText(),fillText() ...

  8. Servlet3.0学习总结——基于Servlet3.0的文件上传

    Servlet3.0学习总结(三)——基于Servlet3.0的文件上传 在Servlet2.5中,我们要实现文件上传功能时,一般都需要借助第三方开源组件,例如Apache的commons-fileu ...

  9. Vue2.0学习--Vue数据通信详解

    一.前言 组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用.组件间如何传递数据就显得至关重要.本文尽可能罗列出一些常见的数据传递方式,如p ...

  10. Oracle学习笔记三 SQL命令

    SQL简介 SQL 支持下列类别的命令: 1.数据定义语言(DDL) 2.数据操纵语言(DML) 3.事务控制语言(TCL) 4.数据控制语言(DCL)  

随机推荐

  1. Java实现6种常见排序

    1.冒泡排序(Bubble Sort) 第0轮 3 1 4 1 5 9 2 6 5 3 5 8 9 第1轮 1 3 1 4 5 2 6 5 3 5 8 9 9 第2轮 1 1 3 4 2 5 5 3 ...

  2. MQ系列6:消息的消费

    MQ系列1:消息中间件执行原理 MQ系列2:消息中间件的技术选型 MQ系列3:RocketMQ 架构分析 MQ系列4:NameServer 原理解析 MQ系列5:RocketMQ消息的发送模式 在之前 ...

  3. ExcelHelper ->NPOI插件

    调用: var ms = ExcelHelper.PrintInvoiceToExcel("观看时长", headline, items, (row, item) => { ...

  4. TomCat之负载均衡

    TomCat之负载均衡 本文讲述了tomcat当nginx负载均衡服务器配置步骤 以下是Tomcat负载均衡配置信息 1.修改nginx的nginx.conf文件 添加如下属性:localhost是名 ...

  5. jsp中使用Servlet查询SQLSERVER数据库中的表的信息,并且打印在屏幕上

    jsp中使用Servlet查询SQLSERVER数据库中的表的信息,并且打印在屏幕上 1.JavaBean的使用 package com.zheng; public class BookBean { ...

  6. JavaScript基础&实战(1)js的基本语法、标识符、数据类型

    文章目录 1.JavaScript简介 2.输出语句 2.1 代码块 2.2 测试结果 3.JS编写位置 3.1代码 3.2 测试结果 4.基本语法 4.1 代码 5.标识符 5.1 代码 6.数据类 ...

  7. 2流高手速成记(之四):SpringBoot整合redis及mongodb

    最近很忙,好不容易才抽出了时间,咱们接上回 上次我们主要讲了如何通过SpringBoot快速集成mybatis/mybatis-plus,以实现业务交互中的数据持久化,而这一切都是基于关系型数据库(S ...

  8. 『现学现忘』Git分支 — 40、分支基本操作(一)

    目录 1.创建分支 (1)创建分支 (2)图示理解 2.查看分支列表 3.分支切换 4.查看所有分支的最后一个提交 5.删除分支 1.创建分支 (1)创建分支 Git 是怎么创建新分支的呢? 很简单, ...

  9. python渗透测试入门——基础的网络编程工具

    <Python黑帽子--黑客与渗透测试编程之道学习>这本书是我在学习安全的过程中发现的在我看来十分优秀的一本书,业内也拥有很高的评价,所以在这里将自己的学习内容分享出来. 1.基础的网络编 ...

  10. 华为开发者大会HDC2022:HMS Core 持续创新,与开发者共创美好数智生活

    11月4日,华为开发者大会HDC2022在东莞松山湖拉开帷幕.HMS Core在本次大会上带来了包括音频编辑服务的高拟真歌声合成技术.视频编辑服务的智能提取精彩瞬间功能.3D Engine超大规模数字 ...