每一个程序都有一个符号表,angr 可以确保从每个导入符号都可以解析出地址,可以使用 angr 提供的 Project.hook_symbol API 来通过符号名来 Hook 函数所有的调用地址,这意味着可以用自己的代码替换函数,一个简单的例子:

>>> class NotVeryRand(SimProcedure):
...     def run(self, return_values=None):
...         rand_idx = self.state.globals.get('rand_idx', 0) % len(return_values)
...         out = return_values[rand_idx]
...         self.state.globals['rand_idx'] = rand_idx + 1
...         return out
 
>>> project.hook_symbol('rand', NotVeryRand(return_values=[413, 612, 1025, 1111]))
import angr
import claripy
import sys
 
def Go():
    path_to_binary = "./10_angr_simprocedures" 
    project = angr.Project(path_to_binary, auto_load_libs=False)
    initial_state = project.factory.entry_state()
 
    class ReplacementCheckEquals(angr.SimProcedure):
        def run(self, to_check, length):
            user_input_buffer_address = to_check
            user_input_buffer_length = length
            user_input_string = self.state.memory.load(
                user_input_buffer_address,
                user_input_buffer_length
            )
            check_against_string = 'ORSDDWXHZURJRBDH'
            return claripy.If(
                user_input_string == check_against_string, 
                claripy.BVV(1, 32), 
                claripy.BVV(0, 32)
            )
    
    check_equals_symbol = 'check_equals_ORSDDWXHZURJRBDH'
    project.hook_symbol(check_equals_symbol, ReplacementCheckEquals())
 
    simulation = project.factory.simgr(initial_state)
 
    def is_successful(state):
        stdout_output = state.posix.dumps(1)
        if b'Good Job.\n' in stdout_output:
            return True
        else: 
            return False
 
    def should_abort(state):
        stdout_output = state.posix.dumps(1)
        if b'Try again.\n' in  stdout_output:
            return True
        else: 
            return False
 
    simulation.explore(find=is_successful, avoid=should_abort)
 
    if simulation.found:
        for i in simulation.found:
            solution_state = i
            solution = solution_state.posix.dumps(0)
            print("[+] Success! Solution is: {0}".format(solution.decode('utf-8')))
            #print(solution0)
    else:
        raise Exception('Could not find the solution')
 
if __name__ == "__main__":
    Go()

运行一下查看结果

这里前面的部分都可以直接照抄上面一题的代码,关键是定义一个继承 angr.SimProcedure 的类,以利用 Angr 的 SimProcedures。

class ReplacementCheckEquals(angr.SimProcedure):

SimProcedure 用 Python 编写的我们自己的函数代替了原来函数。 除了用 Python 编写之外,该函数的行为与用 C 编写的任何函数基本相同。self 之后的任何参数都将被视为要替换的函数的参数, 参数将是符号位向量。 另外,Python 可以以常用的 Python 方式返回,Angr 将以与原来函数相同的方式对待它

我们先来看一下函数原型:

_BOOL4 __cdecl check_equals_ORSDDWXHZURJRBDH(char *to_check, unsigned int length)
{
  int v3; // [esp+8h] [ebp-8h]
  unsigned int i; // [esp+Ch] [ebp-4h]
 
  v3 = 0;
  for ( i = 0; i < length; ++i )
  {
    if ( to_check[i] == *(_BYTE *)(i + 0x804C048) )
      ++v3;
  }
  return v3 == length;
}

不难发现函数的第一个参数是待检测字符串首地址指针,然后就是字符串的长度,接下来我们就可以开始书写我们的模拟函数

def run(self, to_check, length):
    		#即第一个参数
            user_input_buffer_address = to_check
            #即第二个参数
            user_input_buffer_length = length
			#使用self.state在SimProcedure中查找系统状态,从该状态的内存中提取出数据
            user_input_string = self.state.memory.load(
                user_input_buffer_address,
                user_input_buffer_length
            )
            check_against_string = 'ORSDDWXHZURJRBDH'
            #如果符合条件则返回输入的符号位向量
            return claripy.If(
                user_input_string == check_against_string, 
                claripy.BVV(1, 32), 
                claripy.BVV(0, 32)
            )

Hook 上 check_equals 函数, angr 会自动查找与该函数符号关联的地址

check_equals_symbol = 'check_equals_WQNDNKKWAWOLXBAC' 
project.hook_symbol(check_equals_symbol, ReplacementCheckEquals())

对于 scanf 的 hook

import angr
import claripy
import sys
 
def Go():
    path_to_binary = "./11_angr_sim_scanf" 
    project = angr.Project(path_to_binary, auto_load_libs=False)
    initial_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 = param0
            self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
            scanf1_address = param1
            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())
 
    simulation = project.factory.simgr(initial_state)
    
    def is_successful(state):
        stdout_output = state.posix.dumps(1)
        if b'Good Job.\n' in stdout_output:
            return True
        else: 
            return False
 
    def should_abort(state):
        stdout_output = state.posix.dumps(1)
        if b'Try again.\n' in  stdout_output:
            return True
        else: 
            return False
 
    simulation.explore(find=is_successful, avoid=should_abort)
 
    if simulation.found:
        for i in simulation.found:
            solution_state = i
            stored_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))
            #print(scanf0_solution, scanf1_solution)
    else:
        raise Exception('Could not find the solution')
 
if __name__ == "__main__":
    Go()

运行一下查看结果

之前的步骤很多都和上一题一样,只不过在编写模拟的 scanf 函数的时候有一些不太一样

 class ReplacementScanf(angr.SimProcedure):
        def run(self, format_string, param0, param1):
            scanf0 = claripy.BVS('scanf0', 32)
            scanf1 = claripy.BVS('scanf1', 32)
 
            scanf0_address = param0
            self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
            scanf1_address = param1
            self.state.memory.store(scanf1_address, scanf1, endness=project.arch.memory_endness)            

还记得之前在 05_angr_symbolic_memory 我们学会的如何符号化内存吗?因为我们这里 Scanf 是要向内存写入数据的,于是我们利用使用 state.memory.store(addr, val) 接口将符号位向量写入两个字符串的内存区域

globals

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

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