angr基础学习

参考:angr
AngrCTF_FITM/笔记/03/Angr_CTF从入门到精通(三).md at master · ZERO-A-ONE/AngrCTF_FITM

angr_explore

00_angr_find

IDA分析结果:
在这里插入图片描述
逻辑简单,输入,complex_function进行加密,加密结果等于JACEJGCS就成功
在这里插入图片描述
angr:

import angr
import sys
def Go():path_to_binary = "00_angr_find"project = angr.Project(path_to_binary, auto_load_libs = False)# 模拟程序运行状态,获取程序上下文init_state = project.factory.entry_state()# 模拟程序向下执行simulation = project.factory.simgr(init_state)print_good_address = 0x8048675# 设置出口simulation.explore(find=print_good_address)if simulation.found:# 找到的正确路径solution_state = simulation.found[0]# 打印找到的输入solution = solution_state.posix.dumps(sys.stdin.fileno())print("[+] Success! Solution is: {}".format(solution.decode("utf-8")))else:raise Exception("Could not find the solution")if __name__ == "__main__":Go()

代码过程:

  • 创建 project
  • 设置 state
  • 新建符号量 : BVS (bitvector symbolic ) 或 BVV (bitvector value)
  • 把符号量设置到内存或者其他地方
  • 设置 Simulation Managers , 进行路径探索的对象
  • 运行,探索满足路径需要的值
  • 约束求解,获取执行结果

输出:

[+] Success! Solution is: JXWVXRKX

01_angr_avoid

主函数太大无法编译,但是可以分析,目标是到达 maybe_good,不能到达 avoid_me:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
angr代码:

import angr
import sysdef angr_process():project = angr.Project("01_angr_avoid")# 获取程序快照(上下文)init_state = project.factory.entry_state()# 获取一个模拟状态,让程序下一步执行simgr = project.factory.simgr(init_state)avoid_me = 0x80485a8maybe_good = 0x80485b5# explore 返回一个SM,所有找到路径simgr.explore(find = maybe_good, avoid = avoid_me)if simgr.found:# 选择第一个成功的路径solution_state = simgr.found[0]print(solution_state)solution_dump = solution_state.posix.dumps(sys.stdin.fileno())print("[+] Success! Solution is:{}".format(solution_dump.decode("utf-8")))if __name__ == "__main__":angr_process()

02_angr_find_condition

在这里插入图片描述
angr代码:

import angrdef is_successful(state):return b'Good Job.' in state.posix.dumps(1)def should_avoid(state):return b'Try again.' in state.posix.dumps(1)proj = angr.Project('02_angr_find_condition')
state = proj.factory.entry_state()
simgr = proj.factory.simgr(state)
simgr.explore(find=is_successful, avoid=should_avoid)
print(simgr.found[0].posix.dumps(0))

这里通过is_successful函数判断当前状态的输出流中是否包含b’Good Job.',如果包含则表示到达目的地址,ok,此时可以打印对应的输入了。should_avoid函数同理。

除了传入一个函数以外,我们也可以使用lambda表达式来简化代码:

import angrproj = angr.Project('dist/02_angr_find_condition')
state = proj.factory.entry_state()
simgr = proj.factory.simgr(state)
# simgr.explore(find=is_successful, avoid=should_avoid)
simgr.explore(find=lambda state : b'Good Job.' in state.posix.dumps(1),avoid=lambda state: b'Try again.' in state.posix.dumps(1)
)
print(simgr.found[0].posix.dumps(0))

angr_symbolic

angr在默认情况下只会符号化从标准输入流中读取的数据,而实际情况往往需要我们符号化其他数据,如寄存器、某块内存甚至是某个文件。这一节我们来学习如何手动符号化并且利用angr内置的约束求解器对符号值进行约束求解。

03_angr_symbolic_registers

在这里插入图片描述
这里用 get_user_input 获取用户输入,也可以用之前的方法解出:

import angrproj = angr.Project('03_angr_symbolic_registers')
state = proj.factory.entry_state()
simgr = proj.factory.simgr(state)
simgr.explore(find=lambda state : b'Good Job.' in state.posix.dumps(1),avoid=lambda state: b'Try again.' in state.posix.dumps(1)
)
print(simgr.found[0].posix.dumps(0))

但为了体现本节的主题,即手动符号化与约束求解,我们尝试换一种方法来解决这道题。

要准确进行符号化,我们得从汇编着手分析:

在这里插入图片描述
可以看到 get_user_input 函数获取到用户输入后,将输入的内容保存在三个寄存器里,然后通过寄存器赋值给其他变量。所以这里考虑符号化寄存器。
让程序能够达到 Good Job 位置。
在这里插入图片描述
angr代码:

import angr
import claripyproj = angr.Project('03_angr_symbolic_registers')
state = proj.factory.blank_state(addr=0x8048980)
# 符号化寄存器
password0 = claripy.BVS('password0', 32)
password1 = claripy.BVS('password1', 32)
password2 = claripy.BVS('password2', 32)
# 在状态中设置寄存器符号化
state.regs.eax = password0
state.regs.ebx = password1
state.regs.edx = password2
simgr = proj.factory.simgr(state)
simgr.explore(find=0x80489E6)
#solver = simgr.found[0].solver
#print(f'password0: {hex(solver.eval(password0))}')
#print(f'password1: {hex(solver.eval(password1))}')
#print(f'password2: {hex(solver.eval(password2))}')if simgr.found:for solve in simgr.found:solution_state = solvesolution0 = format(solution_state.solver.eval(password0), 'x')solution1 = format(solution_state.solver.eval(password1), 'x')solution2 = format(solution_state.solver.eval(password2), 'x')solution = solution0 + " " + solution1 + " " + solution2print("[+] Success! Solution is: {}".format(solution))

注意这里的初始state是通过blank_state函数而不是entry_state函数获得的。
因为在0x8048980之前的指令对我们的求解其实是没有任何作用的,包括get_user_input函数,因为接下来我们就要将get_user_input函数的结果符号化了,而不是让angr自动帮我们符号化通过scanf读取的数据。

可能有同学还会有疑问:如果直接从0x8048980这个地址开始符号执行,那初始的状态是什么样的呢?根据x86汇编常识,像eax,ebx,ecx这样的寄存器是上下文无关的,也就是说一个函数内不会引用在函数外部设置的eax,ebx或者ecx的值,而是在函数内部对寄存器重新初始化并使用。
所以我们用blank_state获取的状态,即时初始的一些寄存器处于未初始化状态也是丝毫没有影响的,因为它们马上就会被初始化。至于esp,angr会给他们一个默认的初值,使接下来的函数调用不会爆炸:

state.regs.esp
<BV32 0x7fff0000>

ebp的初始值仍然是未初始化的,但对我们后续的符号执行没有影响,不管它就行:

state.regs.ebp
<BV32 reg_1c_3_32{UNINITIALIZED}>

04_angr_symbolic_stack

了解了对寄存器的符号化之后,我们再看看如何对栈空间内的数据进行符号化。
在这里插入图片描述
这次也要从scanf之后开始执行:
在这里插入图片描述

state = proj.factory.blank_state(addr=0x8048694)

之前我们说过通过blank_state获取初始状态,ebp的值是未约束的。在这题中,我们之后要向栈中push符号值,并且通过ebp索引这些符号值(比如[ebp+var_C]),所以我们得让ebp有一个正确的初值了。之所以说是正确的初值,是因为我们跳过了函数开头对栈的调整,因此我们还需要手动调整ebp的值:
ebp的值是什么不重要,我们只需要保证它和esp的偏移是正确的即可,对前面的汇编指令进行分析我们可以得出此时ebp的偏移量为:0x18+4+4+4+4=40:

state.regs.ebp = state.regs.esp + 40

然后对esp的值执行调整,使我们接下来push进去的符号值恰好在[ebp+var_10]和[ebp+var_C]这两个位置,记得push完之后要把esp调回来:

state.regs.esp = state.regs.ebp - 0x8
password0 = claripy.BVS(‘password0’, 32)
password1 = claripy.BVS(‘password1’, 32)
state.stack_push(password0)
state.stack_push(password1)
state.regs.esp = state.regs.ebp - 40

angr代码:

import angr
import claripyproj = angr.Project('../dist/04_angr_symbolic_stack')
state = proj.factory.blank_state(addr=0x8048694)
state.regs.ebp = state.regs.esp + 40
state.regs.esp = state.regs.ebp - 0x8
password0 = claripy.BVS('password0', 32)
password1 = claripy.BVS('password1', 32)
state.stack_push(password0)
state.stack_push(password1)
state.regs.esp = state.regs.ebp - 40
simgr = proj.factory.simgr(state)
simgr.explore(find=0x80486E1)
solver = simgr.found[0].solver
print(f'password0: {hex(solver.eval(password0))}')
print(f'password1: {hex(solver.eval(password1))}')

这个思路是完整的,但是可以简化,明显发现有两行代码冗余了:

state.regs.ebp = state.regs.esp + 40
state.regs.esp = state.regs.ebp - 0x8
==>
state.regs.esp = state.regs.esp + 32
...
state.regs.esp = state.regs.ebp - 40

再简化一点呢,其实咱不用自己去开栈,然后平栈,只要保障局部变量相对于ebp位置是对的就可以了。
angr代码:

import angr
import sys
import claripy
def Go():path_to_binary = "./04_angr_symbolic_stack" project = angr.Project(path_to_binary, auto_load_libs=False)start_address = 0x8048697initial_state = project.factory.blank_state(addr=start_address)# 让ebp = esp,存在疑问,这个状态下,ebp是未初始化的,esp是有值的initial_state.regs.ebp = initial_state.regs.esppasswd_size_in_bits = 32passwd0 = claripy.BVS('passwd0', passwd_size_in_bits)passwd1 = claripy.BVS('passwd1', passwd_size_in_bits)# 把栈提高8个字节,因为main访问两个输入值是通过ebp+c 和 ebp+0x10padding_length_in_bytes = 0x8initial_state.regs.esp -= padding_length_in_bytes# 符号化栈initial_state.stack_push(passwd0)initial_state.stack_push(passwd1)simgr = project.factory.simgr(initial_state)def is_successful(state):stdout_output = state.posix.dumps(1)if b'Good Job.\n' in stdout_output:return Trueelse: return Falsedef should_abort(state):stdout_output = state.posix.dumps(1)if b'Try again.\n' in  stdout_output:return Trueelse: return Falsesimgr.explore(find=is_successful, avoid=should_abort)if simgr.found:for i in simgr.found:solution_state = isolution0 = (solution_state.solver.eval(passwd0))solution1 = (solution_state.solver.eval(passwd1))print("[+] Success! Solution is: {0} {1}".format(solution0, solution1))#print(solution0, solution1)else:raise Exception('Could not find the solution')
Go()

咱用 0x8048697 开始执行,调用scanf的栈就不用管了,保证handle_user函数中参数与ebp之间的偏移正确就好了,handle_user函数里有两个参数[ebp+var_10]和[ebp+var_C],所以把esp先抬高 0x8 ,后面这将符号化的值压入栈,符号化的值就在[ebp+var_10]和[ebp+var_C]位置了,后面就可以直接使用了。

eval
  • solver.eval(expression) 将会解出一个可行解
  • solver.eval_one(expression)将会给出一个表达式的可行解,若有多个可行解,则抛出异常。
  • solver.eval_upto(expression, n)将会给出最多n个可行解,如果不足n个就给出所有的可行解。
  • solver.eval_exact(expression, n)将会给出n个可行解,如果解的个数不等于n个,将会抛出异常。
  • solver.min(expression)将会给出最小可行解
  • solver.max(expression)将会给出最大可行解

另外还有还有cast_to可以接收一个参数来指定把结果映射到哪种数据类型。目前这个参数只能是str,它将会以字符串形式展示返回的结果
参考:AngrCTF_FITM/笔记/02/Angr_CTF从入门到精通(二).md at master · ZERO-A-ONE/AngrCTF_FITM

05_angr_symbolic_memory

在这里插入图片描述

输入保存在一个全局变量user_input里:
在这里插入图片描述
user_input是一个32字节长度的字符串。

angr代码:

import angr
import claripyproj = angr.Project('05_angr_symbolic_memory')
state = proj.factory.blank_state(addr=0x80485FE)
password0 = claripy.BVS('password0', 64)
password1 = claripy.BVS('password1', 64)
password2 = claripy.BVS('password2', 64)
password3 = claripy.BVS('password3', 64)
state.mem[0xA1BA1C0].uint64_t = password0
state.mem[0xA1BA1C0 + 8].uint64_t = password1
state.mem[0xA1BA1C0 + 16].uint64_t = password2
state.mem[0xA1BA1C0 + 24].uint64_t = password3
simgr = proj.factory.simgr(state)
simgr.explore(find=0x804866A)
solver = simgr.found[0].solver
print(f'password0: {solver.eval(password0, cast_to=bytes)}')
print(f'password1: {solver.eval(password1, cast_to=bytes)}')
print(f'password2: {solver.eval(password2, cast_to=bytes)}')
print(f'password3: {solver.eval(password3, cast_to=bytes)}')

操作连续内存也可以用state.memory 的 .load(addr, size) / .store(addr, val) 接口读写内存, size 以 bytes 为单位。

passwd0_address = 0xA1BA1C0
#passwd1_address = 0xA1BA1C8
#passwd2_address = 0xA1BA1D0
#passwd3_address = 0xA1BA1D8
initial_state.memory.store(passwd0_address, password0)
initial_state.memory.store(passwd0_address + 0x8,  password1)
initial_state.memory.store(passwd0_address + 0x10, password2)
initial_state.memory.store(passwd0_address + 0x18, password3)

06_angr_symbolic_dynamic_memory

在这里插入图片描述
输入buffer0和buffer1是malloc出来的指针,其中存放的地址不是确定的。
但我们仍然可以在内存中选定一块区域作为输入的地址,比如说在.bss段选定一块未使用的16字节区域,然后对输入进行符号化:

password0 = claripy.BVS('password0', 64)
password1 = claripy.BVS('password1', 64)
state.mem[0xABCC700].uint64_t = password0
state.mem[0xABCC700 + 8].uint64_t = password1

然后将buffer0和buffer1的地址设成我们刚刚选定的地址:

state.mem[0xABCC8A4].uint32_t = 0xABCC700
state.mem[0xABCC8AC].uint32_t = 0xABCC700 + 8

angr代码:

import angr
import claripyproj = angr.Project('06_angr_symbolic_dynamic_memory')
state = proj.factory.blank_state(addr=0x8048696)
password0 = claripy.BVS('password0', 64)
password1 = claripy.BVS('password1', 64)
state.mem[0xABCC700].uint64_t = password0
state.mem[0xABCC700 + 8].uint64_t = password1
state.mem[0xABCC8A4].uint32_t = 0xABCC700
state.mem[0xABCC8AC].uint32_t = 0xABCC700 + 8
simgr = proj.factory.simgr(state)
simgr.explore(find=0x8048759)
solver = simgr.found[0].solver
print(f'password0: {solver.eval(password0, cast_to=bytes)}')
print(f'password1: {solver.eval(password1, cast_to=bytes)}')

07_angr_symbolic_file

在这里插入图片描述
在这里插入图片描述

这题是先把输入写进文件,再从文件中读取输入。
在这题中我们要忽略scanf,直接对文件的内容进行符号化。要对文件内容进行符号化,首先我们要创建一个模拟的文件SimFile,文件名为’OJKSQYDP.txt’,内容为8字节的符号值,大小为0x40字节:

password0 = claripy.BVS('password0', 64)
sim_file = angr.SimFile(name='OJKSQYDP.txt', content=password0, size=0x40)

然后插入到state的文件系统(FileSystem)中,state的文件系统可以通过state.fs获得:

state.fs.insert('OJKSQYDP.txt', sim_file)

angr代码:

import angr
import claripyproj = angr.Project('../dist/07_angr_symbolic_file')
state = proj.factory.blank_state(addr=0x80488D3)
password0 = claripy.BVS('password0', 64)
sim_file = angr.SimFile(name='OJKSQYDP.txt', content=password0, size=0x40)
state.fs.insert('OJKSQYDP.txt', sim_file)
simgr = proj.factory.simgr(state)
simgr.explore(find=0x80489AD)
solver = simgr.found[0].solver
print(f'password0: {solver.eval(password0, cast_to=bytes)}')

angr_constraints

angr在符号执行的过程中,会将路径约束保存在SimState内置的约束求解器内。这一节我们学习如何手动添加约束并进行约束求解,以及应对路径爆炸的一种方法。

08_angr_constraints

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码将输入的buffer通过complex_function加密,在通过check检查密文是不是满足要求。

这题直接调用explore是跑不出来的,因为在check_equals函数中不是比对失败就立刻退出循环,而是一直循环到最后。总共有16轮循环,每次循环会产生比对成功和比对失败两个状态,所以符号执行总共会产生216个状态,导致路径爆炸:

simgr.explore(find=lambda state : b'Good Job.' in state.posix.dumps(1)) # 不行

可以对buffer内存添加约束求解,当buffer值等于"AUPDNNPROEZRJWKB"求解。

angr代码:

import angr
import claripyproj = angr.Project('../dist/08_angr_constraints')
state = proj.factory.blank_state(addr=0x8048622)
password = claripy.BVS('password', 16 * 8)
buffer_addr = 0x804A050
state.memory.store(buffer_addr, password)
simgr = proj.factory.simgr(state)
simgr.explore(find=0x8048669)
found = simgr.found[0]
found.add_constraints(found.memory.load(buffer_addr, 16) == b'AUPDNNPROEZRJWKB')
print(found.solver.eval(password, cast_to=bytes))

路径爆炸

路径爆炸的概念与数学中的指数爆炸概念类似,即某些情况下符号执行的路径/状态以指数级增长。

从08_angr_constraints中我们发现,即使是非常简单的比较函数,也可能让angr产生指数级的路径(216条路径),耗费大量时间甚至根本跑不出来,这就是符号执行的重大缺陷之一——路径爆炸。这既是我们在用符号执行解决实际问题时需要注意的问题,也是一种可以用来抗符号执行的思路。

应对路径爆炸的方法有很多,甚至还有专门的论文来讲述缓解路径爆炸的方法,在上一题中我们学到了最简单的一种:避开会产生路径爆炸的函数,用手动添加约束替代,这是最简单,也是非常好用的一种方法。总的来说,读者们在使用符号执行引擎时,要识别那些会导致路径爆炸的代码,并巧妙绕过。

angr_hooks

09_angr_hooks

在这里插入图片描述
这一题的程序流程与上题类似,也是有一个会引起路径爆炸的比较函数。与上题不同的是,这题的比较函数不在整个加密流程的最后,所以我们不能再采用上题手动添加约束并求解的方法。
既然check_equals函数本身的流程非常简单,那么我们就可以用hook技术将check_equals函数替换为一个等效的并且不会导致路径爆炸的函数,然后再进行符号执行。

@proj.hook(addr=0x80486B3, length=5)  # check_equals_XYMKBKUHNIQYNQXE
def my_check_equals(state):buffer_addr = 0x804A054buffer = state.memory.load(buffer_addr, 16)state.regs.eax = claripy.If(buffer == b'XYMKBKUHNIQYNQXE', claripy.BVV(1, 32), claripy.BVV(0, 32))

这里的hook是对call指令进行了hook,而不是函数本身,length指的是跳过的字节数,call指令占5个字节,所以length=5:
在这里插入图片描述
angr代码:

import angr
import claripyproj = angr.Project('09_angr_hooks')@proj.hook(addr=0x80486B3, length=5)  # check_equals_XYMKBKUHNIQYNQXE
def my_check_equals(state):buffer_addr = 0x804A054buffer = state.memory.load(buffer_addr, 16)state.regs.eax = claripy.If(buffer == b'XYMKBKUHNIQYNQXE', claripy.BVV(1, 32), claripy.BVV(0, 32))state = proj.factory.entry_state()
simgr = proj.factory.simgr(state)
simgr.explore(find=lambda state : b'Good Job.' in state.posix.dumps(1),avoid=lambda state: b'Try again.' in state.posix.dumps(1)
)
print(simgr.found[0].posix.dumps(0))

10_angr_simprocedures

反编译结果:
在这里插入图片描述
这么看没问题,看看图结构发现有很多调用check函数的,HOOK call指令是行不通了。
在这里插入图片描述
所以要HOOK函数本身。
接下来我们就要引入一种对函数本身进行hook的方法——SimProcedures,定义一个SimProcedures的代码如下:

class MyCheckEquals(angr.SimProcedure):def run(self, buffer_addr, length):buffer = self.state.memory.load(buffer_addr, length)return claripy.If(buffer == b'ORSDDWXHZURJRBDH', claripy.BVV(1, 32), claripy.BVV(0, 32))

SimProcedure按字面意思来理解就是“模拟程序”,在这里我们用一个SimProcedure的子类MyCheckEquals模拟了check_equals_ORSDDWXHZURJRBDH函数的功能,SimProcedure中的run函数由子类实现,其接收的参数与C语言中的参数保持一致,返回为对应原函数的返回值。

定义好了SimProcedure之后,我们需要调用hook_symbol函数对程序中名为check_equals_ORSDDWXHZURJRBDH的函数进行hook:

proj.hook_symbol(symbol_name='check_equals_ORSDDWXHZURJRBDH', simproc=MyCheckEquals())

hook之后angr在符号执行的过程中将不会调用原先的check_equals_ORSDDWXHZURJRBDH函数,而且MyCheckEquals类中的run函数。然后我们就可以用老方法解决这道题了。

angr代码:

import angr
import claripyclass MyCheckEquals(angr.SimProcedure):def run(self, buffer_addr, length):buffer = self.state.memory.load(buffer_addr, length)return claripy.If(buffer == b'ORSDDWXHZURJRBDH', claripy.BVV(1, 32), claripy.BVV(0, 32))proj = angr.Project('10_angr_simprocedures')
proj.hook_symbol(symbol_name='check_equals_ORSDDWXHZURJRBDH', simproc=MyCheckEquals())
state = proj.factory.entry_state()
simgr = proj.factory.simgr(state)
simgr.explore(find=lambda state : b'Good Job.' in state.posix.dumps(1),avoid=lambda state: b'Try again.' in state.posix.dumps(1)
)
print(simgr.found[0].posix.dumps(0))

11_angr_sim_scanf

关于这一题,angr_ctf给的说法是angr不支持多个参数的scanf:

This time, the solution involves simply replacing scanf with our own version, since Angr does not support requesting multiple parameters with scanf.

然而实际上是可以的,可能以前的版本不行,毕竟angr版本迭代还是很快的:

import angrproj = angr.Project('../dist/11_angr_sim_scanf')
state = proj.factory.entry_state()
simgr = proj.factory.simgr(state)
simgr.explore(find=lambda state : b'Good Job.' in state.posix.dumps(1),avoid=lambda state: b'Try again.' in state.posix.dumps(1)
)
print(simgr.found[0].posix.dumps(0))

angr在angr/procedures中定义了很多模拟系统函数的SimProcedures。这些SimProcedures我们都可以通过angr.SIM_PROCEDURES来获得,用法如下:

proj.hook_symbol("__isoc99_scanf", angr.SIM_PROCEDURES['libc']['scanf']())

根据猜测,angr会根据导入表自动识别动态链接的库函数并用自己实现的SimProcedures进行替换的,所以这一步hook完全是多此一举。

angr代码:

import angrproj = angr.Project('11_angr_sim_scanf')
proj.hook_symbol("__isoc99_scanf", angr.SIM_PROCEDURES['libc']['scanf']())  # 多此一举
state = proj.factory.entry_state()
simgr = proj.factory.simgr(state)
simgr.explore(find=lambda state : b'Good Job.' in state.posix.dumps(1),avoid=lambda state: b'Try again.' in state.posix.dumps(1)
)
print(simgr.found[0].posix.dumps(0))

方法2:自己hook scanf函数

import angr
import claripydef angr_process():project = angr.Project("11_angr_sim_scanf", auto_load_libs = False)init_state = project.factory.entry_state()class ReplacementScanf(angr.SimProcedure):def run(self, format_string, param0, param1):scanf0 = claripy.BVS("scanf0", 32)scanf1 = claripy.BVS("scanf1", 32)scanf0_address = param0scanf1_address = param1self.state.memory.store(scanf0_address, scanf0, endness = project.arch.memory_endness)self.state.memory.store(scanf1_address, scanf1, endness = project.arch.memory_endness)self.state.globals["solutions"] = (scanf0, scanf1)scanf_symbol = "__isoc99_scanf"project.hook_symbol(scanf_symbol, ReplacementScanf())simgr = project.factory.simgr(init_state)def is_successful(state):stdout_output = state.posix.dumps(1)if b'Good Job.\n' in stdout_output:return Trueelse: return Falsedef should_abort(state):stdout_output = state.posix.dumps(1)if b'Try again.\n' in  stdout_output:return Trueelse: return Falsesimgr.explore(find=is_successful, avoid=should_abort)if simgr.found:for i in simgr.found:solution_state = istored_solutions = solution_state.globals['solutions']scanf0_solution = solution_state.solver.eval(stored_solutions[0])scanf1_solution = solution_state.solver.eval(stored_solutions[1])print("[+] Success! Solution is: {0} {1}".format(scanf0_solution,scanf1_solution))else:raise Exception('Could not find the solution')angr_process()
globals

这里的关键我们都知道Python的变量生存周期,在这里scanf0scanf1是函数ReplacementScanf的局部变量,为了让函数外部也能获得我们输入的符号位向量,从而调用求解器获得答案,需要将这两个符号位向量变为全局变量,这里我们需要调用带有全局状态的globals插件中“保存”对我们的符号值的引用。globals插件允许使用列表,元组或多个键的字典来存储多个位向量

self.state.globals['solutions'] = (scanf0, scanf1)

angr_veritesting

前面两节我们分别学习了应对路径爆炸的两种方法——手动约束和hook,这一节我们来学习有效缓解路径爆炸的第三种方法——Veritesting。

Veritesting意为路径归并,出自2014年的一篇论文Enhancing Symbolic Execution with Veritesting,读者对归并的原理感兴趣的话可以阅读一下。

12_angr_veritesting

angr中实现了上述论文中提到的Veritesting技术,我们只需要在构建simgr的时候添加一个veritesting=True参数即可,代码如下:

simgr = proj.factory.simgr(state, veritesting=True)

在这里插入图片描述
这次使用veritesting缓解路径爆炸:

import angrproj = angr.Project('../dist/12_angr_veritesting')
state = proj.factory.entry_state()
simgr = proj.factory.simgr(state, veritesting=True)
# simgr = proj.factory.simgr(state)
# simgr.use_technique(angr.exploration_techniques.Veritesting())
simgr.explore(find=lambda state : b'Good Job.' in state.posix.dumps(1),avoid=lambda state: b'Try again.' in state.posix.dumps(1)
)
print(simgr.found[0].posix.dumps(0))

另外根据官方的说法:

Note that it frequenly doesn’t play nice with other techniques due to the invasive way it implements static symbolic execution.请注意,由于它实现静态符号执行的侵入性方式,它通常不能与其他技术很好地配合。

Versitesting通常与其他exploration techniques不兼容。

angr_library

13_angr_static_binary

在这里插入图片描述

通常,Angr会自动地用工作速度快得多的simprocedure代替标准库函数,但是这题中库函数都已经因为静态编译成了静态函数了,angr没法自动替换。要解决这题,需要手动Hook所有使用标准库的C函数,angr已经在simprocedure中为我们提供了这些静态函数, 这里列举一些常用的函数

angr.SIM_PROCEDURES['libc']['malloc']
angr.SIM_PROCEDURES['libc']['fopen']
angr.SIM_PROCEDURES['libc']['fclose']
angr.SIM_PROCEDURES['libc']['fwrite']
angr.SIM_PROCEDURES['libc']['getchar']
angr.SIM_PROCEDURES['libc']['strncmp']
angr.SIM_PROCEDURES['libc']['strcmp']
angr.SIM_PROCEDURES['libc']['scanf']
angr.SIM_PROCEDURES['libc']['printf']
angr.SIM_PROCEDURES['libc']['puts']
angr.SIM_PROCEDURES['libc']['exit']

angr代码:

import angrproj = angr.Project('../dist/13_angr_static_binary')proj.hook_symbol('printf', angr.SIM_PROCEDURES['libc']['printf']())
proj.hook_symbol('__isoc99_scanf',angr.SIM_PROCEDURES['libc']['scanf']())
proj.hook_symbol('strcmp', angr.SIM_PROCEDURES['libc']['strcmp']())
proj.hook_symbol('puts', angr.SIM_PROCEDURES['libc']['puts']())
proj.hook_symbol('__libc_start_main',angr.SIM_PROCEDURES['glibc']['__libc_start_main']())
state = proj.factory.entry_state()
simgr = proj.factory.simgr(state)
simgr.explore(find=lambda state : b'Good Job.' in state.posix.dumps(1),avoid=lambda state: b'Try again.' in state.posix.dumps(1)
)
print(simgr.found[0].posix.dumps(0))

这题解题真正需要用的函数也就printfscnafputs,即完成了angr需要的输出、输入、路径选择的功能,我们手动找到这几个函数的地址

这里比较容易忽略的一个函数就是__libc_start_main

让我们回忆一下在linux下一个c程序是如何启动的:

  1. execve 开始执行
  2. execve 内部会把bin程序加载后,就把.interp指定的 动态加载器加载
  3. 动态加载器把需要加载的so都加载起来,特别的把 libc.so.6 加载
  4. 调用到libc.so.6里的__libc_start_main函数,真正开始执行程序
  5. libc_start_main做了一些事后,调用到main()函数

所以程序是一定需要用到__libc_start_main,分析后得到地址:0x8048D10,于是得到代码:

project.hook(0x804ed40, angr.SIM_PROCEDURES['libc']['printf']())
project.hook(0x804ed80, angr.SIM_PROCEDURES['libc']['scanf']())
project.hook(0x804f350, angr.SIM_PROCEDURES['libc']['puts']())
project.hook(0x8048d10, angr.SIM_PROCEDURES['glibc']['__libc_start_main']())

14_angr_shared_library

接下来我们通过这题来了解如何对动态链接库中单个的函数进行符号执行,main函数的代码如下:
在这里插入图片描述
其中validate函数是动态链接库lib14_angr_shared_library.so的函数:
在这里插入图片描述
我们可以直接通过call_state创建一个函数调用的初始状态:

state = proj.factory.call_state(validate_addr, password, length)

angr代码:

import angr
import claripyproj = angr.Project('lib14_angr_shared_library.so', load_options={'main_opts' : {'base_addr' : 0x400000}
})
# 这是validate的参数char* s1,地址设为0x3000000
buffer_pointer = claripy.BVV(0x3000000, 32)
# 这是validate的参数length
length = claripy.BVV(8, 32)
# 这是validate函数地址
validate_addr = 0x4006D7
# 参数char* s1 存放的字符串内容地址
password = claripy.BVS('password', 8 * 8)state = proj.factory.call_state(validate_addr, buffer_pointer, length)
state.memory.store(buffer_pointer, password)simgr = proj.factory.simgr(state)
simgr.explore(find=0x400783)
found = simgr.found[0]
found.solver.add(found.regs.eax == 1)
print(found.solver.eval(password, cast_to=bytes))

动态链接库都是地址无关的可执行文件(position-independent executable,PIE),若不手动指定PIE的基质,angr会将符号执行的基址指定为默认的0x400000,并输出:

WARNING | 2021-10-07 00:15:28,674 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.

当然也可以自行指定基址,方法如下:

proj = angr.Project('../dist/lib14_angr_shared_library.so', load_options={'main_opts' : {'base_addr' : 0x400000}
})
re-binary 选项

如果你想要对一个特定的二进制对象设置一些选项,CLE也能满足你的需求在加载二进制文件时可以设置特定的参数,使用 main_optslib_opts 参数进行设置。

  • backend - 指定 backend
  • base_addr - 指定基址
  • entry_point - 指定入口点
  • arch - 指定架构

示例如下:

>>> angr.Project('examples/fauxware/fauxware', main_opts={'backend': 'blob', 'arch': 'i386'}, lib_opts={'libc.so.6': {'backend': 'elf'}})
<Project examples/fauxware/fauxware>

参数main_optslib_opts接收一个以python字典形式存储的选项组。main_opts接收一个形如{选项名1:选项值1,选项名2:选项值2……}的字典,而lib_opts接收一个库名到形如{选项名1:选项值1,选项名2:选项值2……}的字典的映射。

lib_opts是二级字典,原因是一个二进制文件可能加载多个库,而main_opts指定的是主程序加载参数,而主程序一般只有一个,因此是一级字典。

这些选项的内容因不同的后台而异,下面是一些通用的选项:

  • backend —— 使用哪个后台,可以是一个对象,也可以是一个名字(字符串)
  • custom_base_addr —— 使用的基地址
  • custom_entry_point —— 使用的入口点
  • custom_arch —— 使用的处理器体系结构的名字

angr_overflow

这是angr_ctf系列的最后一节,在这一节我们通过三个栈溢出的例子来学习angr在漏洞挖掘方向的简单应用。

15_angr_arbitrary_read

在这里插入图片描述

该题是通过puts输出的,所以我们可以将判断输出中是否包含"Good Job."的条件改为,puts的参数是否为"Good Job."的地址,即0x484F4A47。检测puts参数的代码如下,很好理解:

def check_puts(state):puts_parameter = state.memory.load(state.regs.esp + 4, 4, endness=proj.arch.memory_endness)if state.solver.symbolic(puts_parameter):# 如果puts的参数符号化了,说明v4的输入溢出到puts的参数了good_job_string_address = 0x484F4A47# 现在添加约束,要求溢出的数据跟goodjob字符串地址相同is_vulnerable_expression = puts_parameter == good_job_string_addresscopied_state = state.copy()copied_state.add_constraints(is_vulnerable_expression)if copied_state.satisfiable():state.add_constraints(is_vulnerable_expression)return Truereturn False

angr代码:

import angrdef check_puts(state):puts_parameter = state.memory.load(state.regs.esp + 4, 4, endness=proj.arch.memory_endness)if state.solver.symbolic(puts_parameter):good_job_string_address = 0x484F4A47is_vulnerable_expression = puts_parameter == good_job_string_addresscopied_state = state.copy()copied_state.add_constraints(is_vulnerable_expression)# 添加约束if copied_state.satisfiable():# satisfiable:是否满足state约束条件state.add_constraints(is_vulnerable_expression)return Truereturn Falsedef is_successful(state):puts_address = 0x08048370if state.addr == puts_address:#此时要去执行puts,但未进入puts,所以esp+4就是puts的参数return check_puts(state)return Falseproj = angr.Project('15_angr_arbitrary_read')
state = proj.factory.entry_state()
simgr = proj.factory.simgr(state)
simgr.explore(find=is_successful)
print(simgr.found[0].posix.dumps(0))

16_angr_arbitrary_write

在这题中我们可以通过scanf将dest覆盖成任意地址,然后通过第二个strncpy向dest中保存的地址写入任何数据,因此我们可以向任何地址写入任何数据,这是一个任意写漏洞。首先我们将dest覆盖成password_buffer的地址,然后再通过第二个strncpy往dest中写入"PASSWORD",最后输出"Good Job.“:
在这里插入图片描述
还是按照上题的思路,这回我们要check的是strncpy函数,约束条件为dest指向的地址为password_buffer,并且src为"NDYNWEUJ”。
angr代码:

import angr
import claripydef check_strncpy(state):dest = state.memory.load(state.regs.esp + 4, 4, endness=proj.arch.memory_endness)# 复制目的地字符串地址src = state.memory.load(state.regs.esp + 8, 4, endness=proj.arch.memory_endness)# 输入字符串地址src_content = state.memory.load(src, 8)# 输入字符串内容if state.solver.symbolic(src_content) and state.solver.symbolic(dest):# 输入的内容符号化,并且溢出到目的地址is_vulnerable_expression = claripy.And(src_content == b'NDYNWEUJ', dest == 0x57584344)# 约束要求输入字符串为'NDYNWEUJ',并且将dest改为了password_buffer的地址copied_state = state.copy()copied_state.add_constraints(is_vulnerable_expression)if copied_state.satisfiable():state.add_constraints(is_vulnerable_expression)return Truereturn Falsedef is_successful(state):strncpy_address = 0x08048410if state.addr == strncpy_address:return check_strncpy(state)return Falseproj = angr.Project('16_angr_arbitrary_write')
state = proj.factory.entry_state()
simgr = proj.factory.simgr(state)
simgr.explore(find=is_successful)
print(simgr.found[0].posix.dumps(0))

17_angr_arbitrary_jump

非常经典的ROP,可以覆盖返回值实现任意跳转
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当一条指令有太多可能的分支时,就会出现无约束状态。当指令指针完全是符号指针时,就会发生这种情况,这意味着用户输入可以控制计算机执行的代码的地址

mov user_input, eax
jmp eax

例如此题存在的栈溢出漏洞就可以让我们的程序进入无约束状态。一般情况下,当Angr遇到不受约束的状态时,它会将其抛出。在我们的例子中,我们希望利用无约束状态来跳转到我们选择的位置。我们将在稍后了解如何禁用Angr的默认行为。

求解步骤:

  • 初始化proj,让 angr 记录不受约束的状态
  • 开始step直到发现 eip 为符号的状态
  • 约束 eip 与 print_good 函数地址相同
  • 约束求解

angr代码:

import angr
import claripy
import sysdef Go():path_to_binary = "./17_angr_arbitrary_jump" project = angr.Project(path_to_binary)initial_state = project.factory.entry_state() class ReplacementScanf(angr.SimProcedure):def run(self, format_string, input_buffer_address):input_buffer = claripy.BVS('input_buffer', 64 * 8)  for char in input_buffer.chop(bits=8):self.state.add_constraints(char >= '0', char <= 'z')self.state.memory.store(input_buffer_address, input_buffer, endness=project.arch.memory_endness)self.state.globals['solution'] = input_bufferscanf_symbol = '__isoc99_scanf'project.hook_symbol(scanf_symbol, ReplacementScanf())simulation = project.factory.simgr(initial_state, save_unconstrained=True,stashes={'active' : [initial_state],'unconstrained' : [],'found' : [],'not_needed' : []})def check_vulnerable(state):return state.solver.symbolic(state.regs.eip)def has_found_solution():return simulation.founddef has_unconstrained_to_check():return simulation.unconstraineddef has_active():return simulation.activewhile (has_active() or has_unconstrained_to_check()) and (not has_found_solution()):for unconstrained_state in simulation.unconstrained:def should_move(s):return s is unconstrained_statesimulation.move('unconstrained', 'found', filter_func=should_move)simulation.step()if simulation.found:solution_state = simulation.found[0]solution_state.add_constraints(solution_state.regs.eip == 0x4d4c4749)solution = solution_state.solver.eval(solution_state.globals['solution'], cast_to=bytes)print(solution[::-1])else:raise Exception('Could not find the solution')if __name__ == '__main__':Go()

17真不太懂

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/73859.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软考-高级-系统架构设计师【考试备考资料下载】

计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试是原中国计算机软件专业技术资格和水平考试的完善与发展。计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试是由国家人力资源和社会保障部、工业和信息化部领导下的国家级考试。 计算机技术与软件专…

3. 第三放平台部署deepseek

有时候我们会发现使用deepseek服务器&#xff0c;异常卡顿&#xff0c;这是由于多方面原因造成的&#xff0c;比如说访问人数过多等。想要解决这个问题&#xff0c;我们可以选择第三方平台进行部署 第三方平台 我们可以选择的第三方平台很多&#xff0c;比如硅基流动、秘塔搜索…

1.4-蜜罐\堡垒机\API接口

1.4-蜜罐\堡垒机\API接口 蜜罐&#xff1a;用来钓鱼或诱惑测试人员的防护系统 bash <(curl -sS -L https://hfish.net/webinstall.sh) # 安装HFISH蜜罐堡垒机&#xff1a; 运维用的&#xff0c;统一管理运维平台;拿下堡垒机就很有可能等于拿下了多个平台 jumpServer一键安…

知识图引导的检索增强生成

摘要 检索增强生成&#xff08;RAG&#xff09;已经成为一种很有前途的技术&#xff0c;用于解决大型语言模型&#xff08;LLM&#xff09;生成的响应中的幻觉问题。现有的RAG研究主要集中在应用基于语义的方法来提取孤立的相关组块&#xff0c;忽略了它们之间的内在关系。在本…

【机器学习】imagenet2012 数据预处理数据预处理

【机器学习】数据预处理 1. 下载/解压数据2. 数据预处理3. 加载以及训练代码3.1 使用PIL等加载代码3.2 使用OpenCV的方式来一张张加载代码3.3 h5的方式来加载大文件 最后总结 这个数据大约 140个G,128w的训练集 1. 下载/解压数据 首先需要下载数据&#xff1a; 数据最后处理…

质量工程:数字化转型时代的质量体系重构

前言&#xff1a;质量理念的范式转移阅读原文 如果把软件开发比作建造摩天大楼&#xff1a; 传统测试 竣工后检查裂缝&#xff08;高成本返工&#xff09; 质量工程 从地基开始的全流程监理体系&#xff08;设计图纸→施工工艺→建材选择→竣工验收&#xff09; IEEE研究…

【全栈开发】—— Paddle OCR 文字识别 + deepseek接入(基于python 最新!!!)

所有源码都在文章中&#xff0c;大家不要私信来要源码&#xff0c;当然&#xff0c;评论区欢迎交流技术 目录 Paddle OCR 配置环境 示例 deepseek接入 环境配置 api 调用代码 sliconflow Paddle OCR 配置环境 清华源下载 paddlepaddle&#xff1a; pip install paddlepaddle …

SAIL-RK3588J 核心板技术方案——高精度装配式建筑机器人控制‌

&#xff08;本方案契合《建筑机器人产业目录》政策要求&#xff09; 一、方案背景与政策支持‌ ‌政策驱动‌ 2025年2月《建筑机器人产业目录》明确将‌“高精度建筑机器人控制设备”‌纳入重点补贴范围&#xff0c;要求定位精度≤0.5mm、支持实时质检与多机协同&#xff0c…

OpenAI API - 快速入门开发

文章目录 开发者快速入门分析图像输入使用工具扩展模型提供闪电般的 AI 体验构建代理进一步探索 模型精选模型推理模型旗舰聊天模型成本优化模型实时模型旧版 GPT 模型DALLE文本转语音转写嵌入调度工具特定模型GPT 基础模型 Libraries创建和导出 API 密钥安装官方 SDKJavaScrip…

蓝桥杯省赛 棋盘 3533 二维差分+二维前缀和

传送门 0棋盘 - 蓝桥云课 const int N 2e3 10;int n,m; int a[N][N];void insert(int x11,int y11,int x22,int y22) {a[x11][y11] ;a[x11][y22 1] --;a[x22 1][y11] --;a[x22 1][y22 1] ; }void solve() {cin >> n >> m;for (int i 1;i < m;i ){int x11…

《C++Linux编程进阶:从0实现muduo 》-第6讲.C++死锁问题如何分析调试-原子操作,互斥量,条件变量的封装

重点内容 视频讲解&#xff1a;《CLinux编程进阶&#xff1a;从0实现muduo C网络框架系列》-第6讲.C死锁问题如何分析调试-原子操作,互斥量,条件变量的封装 代码改动 lesson6代码 实现&#xff1a;base/Atomic.h 实现&#xff1a;base/Mutex.h 实现&#xff1a;base/Condit…

洛谷题单1-P5708 【深基2.习2】三角形面积-python-流程图重构

题目描述 一个三角形的三边长分别是 a a a、 b b b、 c c c&#xff0c;那么它的面积为 p ( p − a ) ( p − b ) ( p − c ) \sqrt{p(p-a)(p-b)(p-c)} p(p−a)(p−b)(p−c) ​&#xff0c;其中 p 1 2 ( a b c ) p\frac{1}{2}(abc) p21​(abc)。输入这三个数字&#xff…

matplotlib标题比x,y轴字体大,明明标题字体更大?

原始代码&#xff1a; plt.xlabel(训练轮次&#xff08;Epochs&#xff09;, fontsize14, fontweightbold, fontpropertieschinese_font) # 设置中文字体、加大、加粗 plt.ylabel(R值, fontsize14, fontweightbold, fontpropertieschinese_font) # 设置中文字体、加大、加粗…

Baklib内容中台的核心优势是什么?

智能化知识管理引擎 Baklib的智能化知识管理引擎通过多源数据整合与智能分类技术&#xff0c;实现企业知识资产的自动化归集与动态更新。系统内置的语义分析算法可自动识别文档主题&#xff0c;结合自然语言处理技术生成结构化标签体系&#xff0c;大幅降低人工标注成本。针对…

Android学习总结之ContentProvider跨应用数据共享

在 Android 开发中&#xff0c;跨应用数据共享是构建开放生态的关键需求。作为四大组件之一&#xff0c;ContentProvider通过标准化接口和安全机制&#xff0c;成为实现这一需求的核心枢纽。本文将围绕其生命周期方法、核心机制、自定义实现及最佳实践展开&#xff0c;帮助开发…

计算机底层基石:原码、反码、补码、移码深度剖析

在计算机的世界里&#xff0c;所有数据最终都以二进制的形式进行存储与运算。原码、反码、补码和移码作为二进制数据的重要编码方式&#xff0c;对计算机实现高效数据处理起着关键作用。接下来&#xff0c;我们将深入剖析这几种编码。​ 一、原码​ 1.1 定义​ 原码是最简单…

Bitnode和Bitree有什么区别 为什么Bitree前多了*

Bitnode 和 Bitree 的区别在于它们的类型定义和用途&#xff1a; Bitnode: 这是一个结构体类型&#xff0c;表示二叉树中的一个节点。 它包含三个成员&#xff1a; data&#xff1a;存储节点的数据&#xff08;这里是 char 类型&#xff09;。 lchild&#xff1a;指向左子节点…

AI 时代,我们该如何写作?

当ChatGPT/DeepSeek能在几秒钟内产出一篇文章&#xff0c;而且生成能力日益精进&#xff0c;你是否也曾思考&#xff0c;我还能做什么&#xff1f; 当2024年AI开始进入人们的视野&#xff0c;我在CSDN 上的博客也悄然发生了变化&#xff0c;以前一篇文章发布后&#xff0c;阅读…

第三卷:覆舟山决战(73-108回)正反人物群像

第三卷&#xff1a;覆舟山决战&#xff08;73-108回&#xff09;正反人物群像 核心矛盾&#xff1a;寒门称帝→权力异化→历史循环 主题&#xff1a;通过人物群像展现屠龙者成魔的必然性与制度压迫的永恒性 一、正派阵营&#xff08;理想主义残余&#xff09; 1. 檀道济&…

vscode 通过Remote-ssh远程连接服务器报错 could not establish connection to ubuntu

vscode 通过Remote-ssh插件远程连接服务器报错 could not establish connection to ubuntu&#xff0c;并且出现下面的错误打印&#xff1a; [21:00:57.307] Log Level: 2 [21:00:57.350] SSH Resolver called for "ssh-remoteubuntu", attempt 1 [21:00:57.359] r…