0x01探索模板
import angr
import claripy
import sys
def main(argv):
path_to_binary = "15_angr_arbitrary_read"
project = angr.Project(path_to_binary)
# You can either use a blank state or an entry state; just make sure to start
# at the beginning of the program.
# (!)
initial_state = project.factory.entry_state()
# Again, scanf needs to be replaced.
class ReplacementScanf(angr.SimProcedure): #钩取scanf函数重新定义,获取到它的参数
#定义的输入第二个输入的数据为可见字符
# Hint: scanf("%u %20s")
def run(self, format_string, checksec_key_address,input_address):
scanf0 = claripy.BVS('scanf0', 4*8)
scanf1 = claripy.BVS('scanf1', 20*8)
for char in scanf1.chop(bits=8): #把整个数据分割每8位为一组
self.state.add_constraints(char >= '0', char <= 'z')#添加限制,__isoc99_scanf进来的v4的所有字符为可见字符
scanf0_address = checksec_key_address
self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
self.state.memory.store(input_address, scanf1, endness=project.arch.memory_endness) #保存求解变量到内存中
self.state.globals['solution0'] = scanf0
self.state.globals['solution1'] = scanf1 #保存这两个变量到state,结束的时候可以查看这里对应的输入
scanf_symbol = '__isoc99_scanf'# :string
project.hook_symbol(scanf_symbol, ReplacementScanf())#在这里执行hook
# We will call this whenever puts is called. The goal of this function is to
# determine if the pointer passed to puts is controllable by the user, such
# that we can rewrite it to point to the string "Good Job."
def check_puts(state): #执行到这里是刚执行完puts函数 获取现在puts函数的参数1.检查符号化 2.复制一份执行流添加限制判断在这个限制之下是否有解(添加的限制不能取消)
puts_parameter = state.memory.load(state.regs.esp+4,4, endness=project.arch.memory_endness) #情景是刚刚调用了puts,它的参数在esp+4位置处
if state.se.symbolic(puts_parameter):#检查这个参数是否为符号化对象
good_job_string_address = 0x4D52584B # :integer, probably hexadecimal Good Job.的地址
is_vulnerable_expression = puts_parameter==good_job_string_address # :boolean bitvector expression
#运行到这里所有输入得到的状态都已经有了,现在要做的是从这些输入中判断是否有一种输入满足输出的内容为Good job.
copied_state = state.copy() #复制执行状态的上下文
copied_state.add_constraints(is_vulnerable_expression) #复制的新进程添加限制为 puts参数为Good job.的地址
if copied_state.satisfiable():# 判断添加了上面这个约束是否有解
state.add_constraints(is_vulnerable_expression) # 如果有解的话就保存到我们执行的那个状态对象
return True
#复制一个执行状态对象用来探路,直到探到正确的路才让真正的执行流走到这里
else:
return False
else: # not state.se.symbolic(???)
return False
simulation = project.factory.simgr(initial_state)
def is_successful(state): #到这里刚刚执行完puts函数 相当于下断点
puts_address = 0x8048370 #puts@plt的地址(已经执行过了call)
if state.addr == puts_address: #程序执行到这里(也就是路径探测到这里)
# Return True if we determine this call to puts is exploitable.
return check_puts(state)
else:
# We have not yet found a call to puts; we should continue!
return False
simulation.explore(find=is_successful)
if simulation.found: #如果找到说明满足条件1.能够走到puts函数 2.走到puts函数的参数为格式化对象 3.判断输出的内容是否为Good job.
solution_state = simulation.found[0]
solution0 = solution_state.se.eval(solution_state.globals['solution0'],cast_to=bytes)
solution1 = solution_state.se.eval(solution_state.globals['solution1'], cast_to=bytes) # 输出字符串序列化的内容
print(solution0, solution1)
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
0x02获取输入(能运行到这里的输入)(解析结果)
获取满足条件的格式化输入
solution_state.posix.dumps(sys.stdin.fileno())
获取设置变量的值
与password1=claripy.BVS('password1',password_size_in_bits)
结合使用获取满足条件的设置的变量的值
solution0=solution_state.se.eval(password0)
在hook函数中的变量获取能走到某处的输入
self.state.globals['solution0'] = scanf0 #在hook函数里面声明这是个最终要解析的变量
stored_solutions0 = solution_state.globals['solution0']
solution0 = solution_state.se.eval(stored_solutions0) #最终的解析
解析结果字符串序列化
输出结果为字符串
solution0 = solution_state.se.eval(solution_state.globals['solution0'],cast_to=bytes)
添加限制条件
constrained_parameter_desired_value = "MRXJKZYRKMKENFZB" # :string 约束这些数据得到符合要求的
constraint_expression = constrained_parameter_bitvector == constrained_parameter_desired_value
solution_state.add_constraints(constraint_expression)#添加约束条件
)
0x03获取输出(运行到这里的时候的状态)
获取运行到某处程序的格式化输出
stdout_output=state.posix.dumps(sys.stdout.fileno())
获取运行到某处的某内存地址的内容
constrained_parameter_address = 0x0804A050 #获取对应内存区域在运行到这里的时候的值
constrained_parameter_size_bytes = 0x10
constrained_parameter_bitvector = solution_state.memory.load(
constrained_parameter_address,
constrained_parameter_size_bytes
)
获取运行到某处寄存器的值
用于判断运行结果是否符合条件,或者添加限制条件
solution_state.regs.eax != 0
运行到某处获取栈的数据
puts_parameter = state.memory.load(state.regs.esp+4,4, endness=project.arch.memory_endness) #情景是刚刚调用了puts,它的参数在esp+4位置处
获取运行到哪个地址
个人理解应该是类似下断点,不知道可不可以继续往下运行程序
strncpy_address = 0x08048410
if state.addr == strncpy_address:
0x04探索
设置探索成功与失败运行到的地址
这里开启了pie
print_good_address = 0x400000+0x000136D
will_not_succeed_address = 0x400000+0x000137E
simulation.explore(find=print_good_address, avoid=will_not_succeed_address)
设置成功和失败函数
def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return 'Good Job.'.encode() in stdout_output# :boolean
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return 'Try again.'.encode() in stdout_output
simulation.explore(find=is_successful, avoid=should_abort)
复制一份状态去探测看是否有满足限制条件的值
#运行到这里所有输入得到的状态都已经有了,现在要做的是从这些输入中判断是否有一种输入满足输出的内容为Good job.
copied_state = state.copy() #复制执行状态的上下文
copied_state.add_constraints(is_vulnerable_expression) #复制的新进程添加限制为 puts参数为Good job.的地址
if copied_state.satisfiable():# 判断添加了上面这个约束是否有解
state.add_constraints(is_vulnerable_expression) # 如果有解的话就保存到我们执行的那个状态对象
return True
#复制一个执行状态对象用来探路,直到探到正确的路才让真正的执行流走到这里
简单的虚拟探测功能和上一个一样
两个限制条件为and的关系,两个同时成立
if state.satisfiable(extra_constraints=(does_src_hold_password, does_dest_equal_buffer_address)): #尝试求解满足两个条件1.strncpy函数的目的地址为
#if ( !strncmp(password_buffer, "KZYRKMKE", 8u) )中password_buffer的地址 2.strncpy中的前8位数据的内容为KZYRKMKE
state.add_constraints(does_src_hold_password, does_dest_equal_buffer_address)# 尝试求解成功添加真正的限制
return True
0x05设置初始化变量
设置加载开始地址
start_address=0x804890E
initial_state=project.factory.blank_state(addr=start_address) #设置加载地址 blank_state
设置变量
是一堆变量
password_size_in_bits=32
password0=claripy.BVS('password0',password_size_in_bits) #设置二维向量三个
设置寄存器
initial_state.regs.eax=password0
设置栈数据
ida中的代码
int v1; // [esp+8h] [ebp-10h]
int v2; // [esp+Ch] [ebp-Ch]
__isoc99_scanf("%u %u", &v2, &v1);
initial_state.regs.ebp = initial_state.regs.esp
padding_length_in_bytes = 8 # :integer
initial_state.regs.esp -= padding_length_in_bytes #放入栈中
initial_state.stack_push(password0) # :bitvector (claripy.BVS, claripy.BVV, claripy.BV)
initial_state.stack_push(password1)
设置bss段内容(已知内存地址)
password0_address = 0x09FD92A0 #把数据放到
initial_state.memory.store(password0_address, password0)
从scanf0_address加载数据存到scanf0_address 最后一个参数应该表示读取多少
self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
设置堆内容(运行地址未知)
fake_heap_address0 = 0x40000
pointer_to_malloc_memory_address0 =0x09FD92AC
initial_state.memory.store(pointer_to_malloc_memory_address0, fake_heap_address0, endness=project.arch.memory_endness) #写一个任意的堆地址,把这个地址写入到变量
initial_state.memory.store(fake_heap_address0, password0) #设置堆具体内容
设置文件内容
password_file = angr.storage.SimFile(filename, content=password) #把内容放入文件
initial_state.fs.insert(filename, password_file)
0x06hook方法
hook一个地址
def skip_check_equals_(state):下面定义的代码就是要把check_equals_called_address地址处的难书hook成什么
check_equals_called_address = 0x80486B3 #这个地址是call的地址参数没有改变,只改变call
instruction_to_skip_length = 0x05 #不同系统下call指令的大小不同
@project.hook(check_equals_called_address, length=instruction_to_skip_length)
def skip_check_equals_(state): #hook成这个函数
hook一组函数
根据函数名字hook函数,直接获取被hook函数的参数
class ReplacementCheckEquals(angr.SimProcedure):
def run(self, to_check,check_data_length): #to_check,check_data_length代表着被hook的函数的两个参数
......
project.hook_symbol(check_equals_symbol, ReplacementCheckEquals()) #开始hook
0x07特殊操作
判断条件
claripy.If(条件,条件为True时的返回值,条件为False时的返回值) => 创建条件判断
state.regs.eax = claripy.If(
user_input_string == check_against_string,
claripy.BVV(1, 32),
claripy.BVV(0, 32)
)
分割数据给claripy.BVS(‘scanf0’, 4*8)生成的二位向量添加限制
for char in scanf1.chop(bits=8): #把整个数据分割每8位为一组
self.state.add_constraints(char >= '0', char <= 'z')#添加限制,__isoc99_scanf进来的v4的所有字符为可见字符
判断是否为符号化对象(运行结束找出一个数据进行检测要加的条件)
state.se.symbolic(puts_parameter):#检查这个参数是否为符号化对象
0x08解析特殊文件
输入有很多(比如30个字节)
simulation=project.factory.simgr(initial_state,veritesting=True)
对自己写的c库函数
用angr自带的hook函数hook为库函数
project.hook(0x804f350, angr.SIM_PROCEDURES['libc']['puts']())
project.hook(0x8048d10, angr.SIM_PROCEDURES['glibc']['__libc_start_main']())
用angr分析动态链接库文件
加载动态链接库,库函数
base = 0x400000
project = angr.Project(path_to_binary, load_options={
'main_opts' : {
'custom_base_addr' : base
}
}) #加载动态链接库
buffer_pointer=claripy.BVV(0x3000000,32)#创造一个32位的指针,值是任意的但这个值指向输入数据
validate_function_address = base+0x000006D7
initial_state = project.factory.call_state(validate_function_address, buffer_pointer,claripy.BVV(8,32))#要调用的库函数和他的参数
0x09注意点
用 state.memory.load(strncpy_src, strncpy_len)函数获取到的值
1.是倒序存储需要完全逆过来还原才能进行字符串比较
src_contents = state.memory.load(strncpy_src, strncpy_len)
password_string = "KZYRKMKE"# :string
does_src_hold_password = src_contents[-1:-64] == password_string #小端字节序存储用这种方式反转数据
2.需要检查是否是符号化对象
if state.se.symbolic(strncpy_dest):
是符号化对象