每一个程序都有一个符号表,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 的变量生存周期,在这里 scanf0
和 scanf1
是函数 ReplacementScanf
的局部变量,为了让函数外部也能获得我们输入的符号位向量,从而调用求解器获得答案,需要将这两个符号位向量变为全局变量,这里我们需要调用带有全局状态的 globals 插件中“保存”对我们的符号值的引用。globals 插件允许使用列表,元组或多个键的字典来存储多个位向量
self.state.globals['solutions'] = (scanf0, scanf1)