angr_ctf
最近想学 llvm + vmp,看到这两技术都有用到angr,遂来学习一下
做了下Github上angr_ctf 的逆向题目 https://github.com/jakespringer/angr_ctf ,感觉它比我想象中的要强大
代码如下
0x01 一般模板
#1、 angr 一般模板 import angr import sys, time def found(simgr): if simgr.found: solution_state = simgr.found[0] correct_input = solution_state.posix.dumps(sys.stdin.fileno()) print(correct_input) else: raise Exception('not solution') def main(argv): path_to_binary = argv[1] p = angr.Project(path_to_binary) initial_state = p.factory.entry_state( add_options={angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} ) simgr = p.factory.simulation_manager(initial_state) good_address = 0x80485F7 #加上 0x400000 基质 again_address = (0x8048575,0x8048609) simgr.explore(find=good_address, avoid=again_address) found(simgr) if __name__ == '__main__': start = time.time() main(sys.argv) end = time.time() total = end - start print(total)
0x02 通过condition的方式选择分支
#2 、angr 通过condition的方式来选择find和avoid的分支 , 际上在选择分支的时候可以根据对应的输出结果来判断,在explore的时候传入函数,用于指定什么情况下选择该路径,什么时候跳过该路径 import angr import sys, time def found(simgr): if simgr.found: solution_state = simgr.found[0] correct_input = solution_state.posix.dumps(sys.stdin.fileno()) print(correct_input) else: raise Exception('not solution') def main(argv): path_to_binary = argv[1] p = angr.Project(path_to_binary) initial_state = p.factory.entry_state( add_options={angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} ) simgr = p.factory.simulation_manager(initial_state) # good_address = 0x80485F7 #加上 0x400000 基质 # again_address = (0x8048575,0x8048609) def good_address(state): # 我们在路径探寻的时候的输出进行判断 stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Good Job.' in stdout_output else False def again_address(state): stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Try again.' in stdout_output else False simgr.explore(find=good_address, avoid=again_address) found(simgr) if __name__ == '__main__': start = time.time() main(sys.argv) end = time.time() total = end - start print(total)
0x03 输入的参数存放在寄存器上
#3、 angr angr的入口位置不再是main函数入口,而是在程序的某一位置上 ### 传入的参数在寄存器中... entry_state() 构造一个已经准备好从函数入口点(main)执行的状态 blank_state() 构造一个“空状态”,它的大多数数据都是未初始化的。当使用未初始化的的数据时,一个不受约束的符号值将会被返回,记住当需要从程序中任意一点执行的时候使用 call_state() 构造一个已经准备好执行某个函数的状态 full_init_state() 构造一个已经执行过所有与需要执行的初始化函数,并准备从函数入口点执行的状态。比如,共享库构造函数(constructor)或预初始化器。当这些执行完之后,程序将会跳到入口点 import angr import sys, time import claripy ## claripy模块实际上是对python z3库的封装 def found(simgr , ttt): passwd0 = ttt[0] passwd1 = ttt[1] passwd2 = ttt[2] if simgr.found: solution_state = simgr.found[0] solution0 = format(solution_state.solver.eval(passwd0), 'x') # format(...,'x')是将16进制转换为字符串 solution1 = format(solution_state.solver.eval(passwd1), 'x') solution2 = format(solution_state.solver.eval(passwd2), 'x') solution = solution0 + " " + solution1 + " " + solution2 print("[+] Success! Solution is: {}".format(solution)) else: raise Exception('not solution') def main(argv): path_to_binary = argv[1] p = angr.Project(path_to_binary) # 不再是entry_state()了,通过给定的起始地址,在该处创建一个空白状态 start_address = 0x80488c7 #这个地址是将输入值传入寄存器的起始地址 initial_state = p.factory.blank_state(addr=start_address) passwd_size_in_bits = 32 passwd0 = claripy.BVS('passwd0', passwd_size_in_bits) passwd1 = claripy.BVS('passwd1', passwd_size_in_bits) passwd2 = claripy.BVS('passwd2', passwd_size_in_bits) # 将符号变量存储到寄存器中,在程序模拟执行阶段就由这些符号值代替实际输入执行,以遍历所有路径 initial_state.regs.eax = passwd0 initial_state.regs.ebx = passwd1 initial_state.regs.edx = passwd2 simgr = p.factory.simulation_manager(initial_state) #模拟器管理者 def good_address(state): # 我们在路径探寻的时候的输出进行判断 stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Good Job.' in stdout_output else False def again_address(state): stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Try again.' in stdout_output else False simgr.explore(find=good_address, avoid=again_address) found(simgr,(passwd0,passwd1,passwd2)) if __name__ == '__main__': start = time.time() main(sys.argv) end = time.time() total = end - start print(total)
0x04 输入的参数存放在栈中
#4、传入的参数在栈中 我们需要在初始化入口点的时候修改一下angr的模拟堆栈 import angr import sys, time import claripy ## claripy模块实际上是对python z3库的封装 def found(simgr , ttt): passwd0 = ttt[0] passwd1 = ttt[1] if simgr.found: solution_state = simgr.found[0] solution0 = format(solution_state.solver.eval(passwd0), 'x') # format(...,'x')是将16进制转换为字符串 solution1 = format(solution_state.solver.eval(passwd1), 'x') solution = solution0 + " " + solution1 print("[+] Success! Solution is: {}".format(solution)) else: raise Exception('not solution') def main(argv): path_to_binary = argv[1] p = angr.Project(path_to_binary) # 不再是entry_state()了,通过给定的起始地址,在该处创建一个空白状态 start_address = 0x80486AE #这个地址是scanf函数刚刚结束的位置 两个参数分别在 ebp - c 和 ebp - 0x10的位置 initial_state = p.factory.blank_state(addr=start_address) # # 由于要对栈做操作,需要先将esp指向ebp initial_state.regs.esp = initial_state.regs.ebp passwd_size_in_bits = 32 passwd0 = claripy.BVS('passwd0', passwd_size_in_bits) passwd1 = claripy.BVS('passwd1', passwd_size_in_bits) # 下面需要将符号变量注入栈中,为此需要先腾出两个变量的空间,即2*4=8字节 padding_length_in_bytes = 2*4 # :integer initial_state.regs.esp -= padding_length_in_bytes #esp + # 将变量压入栈中 initial_state.stack_push(passwd0) # :bitvector (claripy.BVS, claripy.BVV, claripy.BV) initial_state.stack_push(passwd1) # :bitvector (claripy.BVS, claripy.BVV, claripy.BV) simgr = p.factory.simulation_manager(initial_state) #模拟器管理者 def good_address(state): # 我们在路径探寻的时候的输出进行判断 stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Good Job.' in stdout_output else False def again_address(state): stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Try again.' in stdout_output else False simgr.explore(find=good_address, avoid=again_address) found(simgr,(passwd0,passwd1)) if __name__ == '__main__': start = time.time() main(sys.argv) end = time.time() total = end - start print(total)
0x05 传入的参数被存放在全局区中
import angr import sys, time import claripy ## claripy模块实际上是对python z3库的封装 from Crypto.Util.number import long_to_bytes def found(simgr , ttt): passwd0 = ttt[0] passwd1 = ttt[1] passwd2 = ttt[2] passwd3 = ttt[3] if simgr.found: solution_state = simgr.found[0] solution0 = solution_state.solver.eval(passwd0) # format(...,'x')是将16进制转换为字符串 solution1 = solution_state.solver.eval(passwd1) solution2 = solution_state.solver.eval(passwd2) solution3 = solution_state.solver.eval(passwd3) solution = long_to_bytes(solution0) + long_to_bytes(solution1) + long_to_bytes(solution2) + long_to_bytes(solution3) print("[+] Success! Solution is: {}".format(solution)) else: raise Exception('not solution') def main(argv): path_to_binary = argv[1] p = angr.Project(path_to_binary) # 不再是entry_state()了,通过给定的起始地址,在该处创建一个空白状态 start_address = 0x8048618 #这个地址是scanf函数刚刚结束的位置 initial_state = p.factory.blank_state(addr=start_address) initial_state.regs.esp = initial_state.regs.ebp passwd_size_in_bits = 64 passwd0 = claripy.BVS('passwd0', passwd_size_in_bits) passwd1 = claripy.BVS('passwd1', passwd_size_in_bits) passwd2 = claripy.BVS('passwd2', passwd_size_in_bits) passwd3 = claripy.BVS('passwd3', passwd_size_in_bits) passwd_address =0xAB232C0 initial_state.memory.store(passwd_address, passwd0) # store(addr, ...): 向指定内存写入数据 #load(addr,...): 读取指定地址的内存 initial_state.memory.store(passwd_address + 0x08, passwd1) initial_state.memory.store(passwd_address + 0x10, passwd2) initial_state.memory.store(passwd_address + 0x18, passwd3) simgr = p.factory.simulation_manager(initial_state) #模拟器管理者 def good_address(state): # 我们在路径探寻的时候的输出进行判断 stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Good Job.' in stdout_output else False def again_address(state): stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Try again.' in stdout_output else False simgr.explore(find=good_address, avoid=again_address) found(simgr,(passwd0,passwd1,passwd2,passwd3)) if __name__ == '__main__': start = time.time() main(sys.argv) end = time.time() total = end - start print(total)
0x06 输入的数据属于 malloc分配的堆区,且返回值储存在全局变量中
#6、 输入的数据属于 malloc分配的堆区,且返回值储存在全局变量中 , 这一关要注意小端序的存放 #全局变量是有固定地址的,另外他的值存储了malloc返回的地址,malloc返回的地址存储了string字符串,对应关系即为: 全局变量 -> malloc随机分配的地址fake_addr -> string import angr import sys, time import claripy ## claripy模块实际上是对python z3库的封装 from Crypto.Util.number import long_to_bytes def found(simgr , ttt): passwd0 = ttt[0] passwd1 = ttt[1] if simgr.found: solution_state = simgr.found[0] solution0 = solution_state.solver.eval(passwd0) # format(...,'x')是将16进制转换为字符串 solution1 = solution_state.solver.eval(passwd1) solution = long_to_bytes(solution0) + long_to_bytes(solution1) print("[+] Success! Solution is: {}".format(solution)) else: raise Exception('not solution') def main(argv): path_to_binary = argv[1] p = angr.Project(path_to_binary) # 不再是entry_state()了,通过给定的起始地址,在该处创建一个空白状态 start_address = 0x80486AF #这个地址是scanf函数刚刚结束的位置 initial_state = p.factory.blank_state(addr=start_address) passwd_size_in_bits = 64 passwd0 = claripy.BVS('passwd0', passwd_size_in_bits) passwd1 = claripy.BVS('passwd1', passwd_size_in_bits) buffer0 = 0xA2DEF74 buffer1 = 0xA2DEF7C fake_addr_buffer0 = 0xffffc93c fake_addr_buffer1 = 0xffffc94c initial_state.memory.store(buffer0,fake_addr_buffer0,endness=p.arch.memory_endness) #to specify to use the endianness of your architecture, use the parameter endness=project.arch.memory_endness. On x86, this is little-endian initial_state.memory.store(buffer1,fake_addr_buffer1,endness=p.arch.memory_endness) initial_state.memory.store(fake_addr_buffer0, passwd0) # store(addr, ...): 向指定内存写入数据 #load(addr,...): 读取指定地址的内存 initial_state.memory.store(fake_addr_buffer1, passwd1) simgr = p.factory.simulation_manager(initial_state) #模拟器管理者 def good_address(state): # 我们在路径探寻的时候的输出进行判断 stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Good Job.' in stdout_output else False def again_address(state): stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Try again.' in stdout_output else False simgr.explore(find=good_address, avoid=again_address) found(simgr,(passwd0,passwd1)) if __name__ == '__main__': start = time.time() main(sys.argv) end = time.time() total = end - start print(total) # [+] Success! Solution is: b'OFIJHOXVFBQISOZO' # 6.683165550231934
0x07 文件处理
## 7、 文件处理 # 在angr中,对文件系统进行模拟需要通过angr.storage.SimFile(name, content, size)对象,接收三个参数: # name: 要模拟的文件名称 # content:Optional:模拟的文件内容,通常传入的是一个BV(bitvector)对象,表示符号化的变量,也可以传入string # size:Optional:文件的大小 # 当完成文件的模拟后,就像我们生成一个符号变量之后需要将其添加到state中的指定位置(如寄存器,内存,栈)一样(将符号化变量添加到初始化状态中),生成的模拟文件也需要添加到指定的state当中,添加的方式有两种: # 利用state.fs.insert(filename, simfile)方法:传入文件名和相应的模拟文件对象(SimFile),类似于之前state.memory.store(fake_heap_address0, passwd0)这部分操作 # 利用state.pofix.fs选项以文件名的字典来预配置SimFile def main(argv): path_to_binary = argv[1] p = angr.Project(path_to_binary) # 不再是entry_state()了,通过给定的起始地址,在该处创建一个空白状态 start_address = 0x80488BC #这个地址是ignore函数刚刚结束的位置 initial_state = p.factory.blank_state(addr=start_address, add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} ) # 文件名 filename = 'FOQVSBZB.txt' #输入64字节 symbolic_file_size_bytes = 64 #创建BV password = claripy.BVS('password', symbolic_file_size_bytes * 8) #字节 = 位数 x 8 # 模拟文件,将符号变量作为content添加进去 simfile = angr.storage.SimFile(filename, content=password) # # 将模拟文件插入到初始化状态中 initial_state.fs.insert(filename,simfile) simgr = p.factory.simgr(initial_state) #模拟器管理者 def good_address(state): # 我们在路径探寻的时候的输出进行判断 stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Good Job.' in stdout_output else False def again_address(state): stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Try again.' in stdout_output else False simgr.explore(find=good_address, avoid=again_address) if simgr.found: solution_state = simgr.found[0] solution = solution_state.solver.eval(password,cast_to=bytes).decode() print(f"[+] Success! Solution is: {solution}") else: raise Exception('Could not find the solution') if __name__ == '__main__': start = time.time() main(sys.argv) end = time.time() total = end - start print(total) #[+] Success! Solution is: OBAXRUZT #8.293952941894531
0x08 利用约束进行路径优化
#8、路径优化 #这里,我们输入了16个字符,经过一些简单运算后,逐个与固定字符串比较,若比较时有一处不一样的话,则带循环结束后,返回0,输出Try again # 如果,我们继续用之前的方法,探寻存在Good Job的路径,那我们输入的每一个字符都会增加两倍的路径探寻,16个字符将会是 65536条路径,将会浪费大量资源, #但,实际需求上我们只需要把程序运行到字符串进行比较的位置,然后添加约束(字符串相等的约束),直接求解即可,我们压根不用执行到 Good Job !!! import angr import sys, time import claripy ## claripy模块实际上是对python z3库的封装 def main(argv): path_to_binary = argv[1] p = angr.Project(path_to_binary) # 不再是entry_state()了,通过给定的起始地址,在该处创建一个空白状态 start_address = 0x804863C address_to_check_constraint = 0x8048683 initial_state = p.factory.blank_state(addr=start_address, add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} ) #输入64字节 symbolic_file_size_bytes = 16 #创建BV password = claripy.BVS('password', symbolic_file_size_bytes * 8) #字节 = 位数 x 8 buff_address = 0x804A040 initial_state.memory.store(buff_address,password) simgr = p.factory.simgr(initial_state) #模拟器管理者 def good_address(state): # 我们在路径探寻的时候的输出进行判断 stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Good Job.' in stdout_output else False def again_address(state): stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Try again.' in stdout_output else False simgr.explore(find=address_to_check_constraint, avoid=again_address) if simgr.found: solution_state = simgr.found[0] solution = solution_state.solver.eval(password,cast_to=bytes).decode() print(f"[+] Success! Solution is: {solution}") constrained_parameter_address = buff_address constrained_parameter_size_bytes = 16 constrained_parameter_bitvector = solution_state.memory.load( constrained_parameter_address, constrained_parameter_size_bytes ) constrained_parameter_desired_value = b'OSIWHBXIFOQVSBZB' # :string (encoded) solution_state.add_constraints(constrained_parameter_bitvector == constrained_parameter_desired_value) ##为向量增加约束 solution = solution_state.solver.eval(password,cast_to=bytes).decode() print(f"[+] Success! Solution is: {solution}") else: raise Exception('Could not find the solution') if __name__ == '__main__': start = time.time() main(sys.argv) end = time.time() total = end - start print(total)
0x09 angr Users Hook
#9、 angr Hook 实现 # 这次输入了两次,拥有两次字符串比较,我们可以用上面的做法来做,但这里提供了更简单的方法 -- Hook # angr 中的Hook分为了Uesr Hook 和 Symbols Hook 参考 https://docs.angr.io/extending-angr/simprocedures#user-hooks # 这里我们使用 Uesr Hook Hook掉 080486CA 位置的字符串检查函数,然后自行比较,并将返回值传递给eax import angr import sys, time import claripy ## claripy模块实际上是对python z3库的封装 def main(argv): path_to_binary = argv[1] p = angr.Project(path_to_binary) initial_state = p.factory.entry_state( add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} ) #直接入口点 check_equals_called_address = 0x80486ca instruction_to_skip_length = 5 @p.hook(check_equals_called_address, length=instruction_to_skip_length) def skip_check_equals_(state): user_input_buffer_address = 0x804a044 # :integer, probably hexadecimal user_input_buffer_length = 16 user_input_string = state.memory.load( user_input_buffer_address, user_input_buffer_length ) check_against_string = 'OSIWHBXIFOQVSBZB'.encode() # :string state.regs.eax = claripy.If( # claripy.If(expression, ret_if_true, ret_if_false) 返回值要设置好宽度之类的再返回 user_input_string == check_against_string, claripy.BVV(1, 32), # claripy.BVS 创建符号向量 claripy.BVV创建位向量 # 符号表示 # BV:bitvector # 构造符号值或具体值的一个比特容器,它具有大小。 # 例子: # 建立一个32bit的符号值容器 "x": # claripy.BVS('x',32) # 建立一个32bit的具体值(0xc001b3475)容器: # claripy.BVV(0xc001b3a75,32) # 建立一个32bit的步进值,从1000到2000能被10整除的数: # claripy.SI(name='x',bits=32,lower_bound=1000,upper_bound=2000,stride=10) claripy.BVV(0, 32) ) simulation = p.factory.simgr(initial_state) def good_address(state): # 我们在路径探寻的时候的输出进行判断 stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Good Job.' in stdout_output else False def again_address(state): stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Try again.' in stdout_output else False simulation.explore(find=good_address, avoid=again_address) if simulation.found: solution_state = simulation.found[0] solution = solution_state.posix.dumps(sys.stdin.fileno()).decode() print(f"[+] Success! Solution is: {solution}") else: raise Exception('Could not find the solution') if __name__ == '__main__': start = time.time() main(sys.argv) end = time.time() total = end - start print(total) # [+] Success! Solution is: QREPXOHPJPOQKQLKNOBMULEMGMLNHNIH # 3.748581886291504
0x0A 符号Hook
# angr 10、 这道题和第八题一样,我们采取符号名 Hook 第九题Hook地址,第十题Hook符号 ,这里符号Hook有点覆写的感觉 #参考 https://docs.angr.io/extending-angr/simprocedures#user-hooks import angr import sys, time import claripy ## claripy模块实际上是对python z3库的封装 def main(argv): path_to_binary = argv[1] p = angr.Project(path_to_binary) initial_state = p.factory.entry_state( add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} ) #直接入口点 class ReplacementCheckEquals(angr.SimProcedure): def run(self,to_check,length): #self参数 原程序第一次参数 原程序第二个参数 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 = 'OSIWHBXIFOQVSBZB'.encode() return claripy.If( user_input_string == check_against_string, claripy.BVV(1, 32), claripy.BVV(0, 32) ) check_equals_symbol = 'check_equals_OSIWHBXIFOQVSBZB' # :string p.hook_symbol(check_equals_symbol, ReplacementCheckEquals()) simulation = p.factory.simgr(initial_state) def good_address(state): # 我们在路径探寻的时候的输出进行判断 stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Good Job.' in stdout_output else False def again_address(state): stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Try again.' in stdout_output else False simulation.explore(find=good_address, avoid=again_address) if simulation.found: solution_state = simulation.found[0] solution = solution_state.posix.dumps(sys.stdin.fileno()).decode() print(f"[+] Success! Solution is: {solution}") else: raise Exception('Could not find the solution') if __name__ == '__main__': start = time.time() main(sys.argv) end = time.time() total = end - start print(total) # [+] Success! Solution is: MTMDRONBBNSAAMNS # 1.842383623123169
0x0B Hook scanf函数或通过Hook scanf函数读取堆栈
# angr 11 、Hook scanf函数;Hook scanf函数并读取堆栈 这里采用了两种做法 import angr import sys, time import claripy ## claripy模块实际上是对python z3库的封装 def main(argv): path_to_binary = argv[1] p = angr.Project(path_to_binary) initial_state = p.factory.entry_state( add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} ) #直接入口点 class ReplacementCheckEquals(angr.SimProcedure): def run(self,format_string, scanf0_address,scanf1_address): ebp_addr = self.state.regs.ebp s_arr_addr = ebp_addr - 0x20 constrained_parameter_address = s_arr_addr constrained_parameter_size_bytes = 4 constrained_parameter_bitvector = self.state.memory.load( constrained_parameter_address, constrained_parameter_size_bytes ) print("~~~~~~ ",constrained_parameter_bitvector) ### 直接Hook 出堆栈也可以, 主意好小端序即可 # 定义两个符号变量,用于写入buffer scanf0 = claripy.BVS('scanf0', 32) scanf1 = claripy.BVS('scanf1', 32) # 写入内存,最后一个参数指定字节序 self.state.memory.store(scanf0_address, scanf0, endness=p.arch.memory_endness) self.state.memory.store(scanf1_address, scanf1, endness=p.arch.memory_endness) # 保存变量到全局,方便后续约束求解 self.state.globals['solution0'] = scanf0 self.state.globals['solution1'] = scanf1 scanf_symbol = '__isoc99_scanf' p.hook_symbol(scanf_symbol, ReplacementCheckEquals()) simulation = p.factory.simgr(initial_state) def good_address(state): # 我们在路径探寻的时候的输出进行判断 stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Good Job.' in stdout_output else False def again_address(state): stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Try again.' in stdout_output else False simulation.explore(find=good_address, avoid=again_address) if simulation.found: solution_state = simulation.found[0] stored_solutions0 = solution_state.globals['solution0'] stored_solutions1 = solution_state.globals['solution1'] solution = f'{solution_state.solver.eval(stored_solutions0)} {solution_state.solver.eval(stored_solutions1)}' print(f"[+] Success! Solution is: {solution}") else: raise Exception('Could not find the solution') if __name__ == '__main__': start = time.time() main(sys.argv) end = time.time() total = end - start print(total) # ~~~~~~ <BV32 0x4c524d56> # [+] Success! Solution is: 1447907916 1146768724 # 0.769453763961792
0x0C 利用 Veritesting 技术解决简单的路径爆破问题
# angr 12、Veritesting 技术解决路径爆炸问题 正常解法的话 2 ^ 32 条路径,直接起飞 # 这里Veritesting 静态符合执行与动态符号结合执行,减少了路径爆炸的影响 , 原理: https://www.wangan.com/p/7fygfgbcefb38cfc#Veritesting # 简而言之,Veritest 从 DSE 开始,一旦遇到一些简单的代码,就切换到 SSE 的模式。简单的代码指的是不含系统调用,间接跳转或者其他很难精确推断的语句。在 SSE 模式下,首先动态恢复控制流图,找到 SSE 容易分析的语句和难以分析的语句。然后 SSE 算法推断出从易分析的节点到难分析节点路径的影响。最后,Veritesting 切换回 DSE 模式去处理静态不好解决的情况。 import angr import sys, time import claripy ## claripy模块实际上是对python z3库的封装 def main(argv): path_to_binary = argv[1] p = angr.Project(path_to_binary) initial_state = p.factory.entry_state( add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} ) #直接入口点 simulation = p.factory.simgr(initial_state,veritesting = True) #开启veritesting def good_address(state): # 我们在路径探寻的时候的输出进行判断 stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Good Job.' in stdout_output else False def again_address(state): stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Try again.' in stdout_output else False simulation.explore(find=good_address, avoid=again_address) if simulation.found: solution_state = simulation.found[0] solution = solution_state.posix.dumps(sys.stdin.fileno()).decode() print(f"[+] Success! Solution is: {solution}") else: raise Exception('Could not find the solution') if __name__ == '__main__': start = time.time() main(sys.argv) end = time.time() total = end - start print(total) #[+] Success! Solution is: YNCRGVKZODSHWLAPETIXMBQFUJYNCRGV #4.00534200668335
0x0D angr解静态编译题目
# angr 13、angr解静态编译题目 题目中的标准函数如strcmp函数是静态编译的,能直接点开看到源码 ,通常,Angr在遇到标准函数库的时候会自动用工作速度快得多的simprocedure 库进行替代 # 但现在,题目中的程序没办法被自动替换,要解决这个问题,我们需要手动Hook所有使用标准库的C函数 ,我们只需要手动找到程序中用到静态函数的地址,将其利用simprocedure提供的函数Hook掉即可 import angr import sys, time import claripy ## claripy模块实际上是对python z3库的封装 def main(argv): path_to_binary = argv[1] p = angr.Project(path_to_binary) initial_state = p.factory.entry_state( add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} ) #直接入口点 p.hook(0x804fab0, angr.SIM_PROCEDURES['libc']['printf']()) p.hook(0x804fb10, angr.SIM_PROCEDURES['libc']['scanf']()) p.hook(0x80503f0, angr.SIM_PROCEDURES['libc']['puts']()) p.hook(0x8048d60, angr.SIM_PROCEDURES['glibc']['__libc_start_main']()) simulation = p.factory.simgr(initial_state) #开启veritesting def good_address(state): # 我们在路径探寻的时候的输出进行判断 stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Good Job.' in stdout_output else False def again_address(state): stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Try again.' in stdout_output else False simulation.explore(find=good_address, avoid=again_address) if simulation.found: solution_state = simulation.found[0] solution = solution_state.posix.dumps(sys.stdin.fileno()).decode() print(f"[+] Success! Solution is: {solution}") else: raise Exception('Could not find the solution') if __name__ == '__main__': start = time.time() main(sys.argv) end = time.time() total = end - start print(total) #[+] Success! Solution is: EADQYLAR #13.491897106170654
0x0E angr处理依赖共享库.so文件的程序
# 14、 angr处理依赖共享库.so文件的程序, 主程序中的作者自定义的函数 -- validate 被放到了一个.so文件中 # 要做这道题,我们需要处理好.so文件中函数的偏移, 我们使用call_state 空降.so文件库中的validate函数,并在最后find的时候用根据eax的值求约束 # 这里只处理.so文件就可以解出flag import angr import sys, time import claripy ## claripy模块实际上是对python z3库的封装 def main(argv): path_to_binary = argv[1] so_binary_name = "lib14_angr_shared_library.so" base = 0x4000000 #指定共享文件的基地址 p = angr.Project(so_binary_name,load_options={ 'main_opts' : { 'custom_base_addr' : base } }) buffer_pointer = claripy.BVV(0x3000000, 32) # 0x3000000 在下面用作地址保存 BVS向量 validate_function_address = base + 0x670 initial_state = p.factory.call_state(validate_function_address, buffer_pointer, claripy.BVV(8, 32), #函数地址 函数的第一个参数 函数的第二个参数 ##call_state的具体用法 参考 https://api.angr.io/angr.html?highlight=call_state add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS} ) password = claripy.BVS('password', 8*8) initial_state.memory.store(buffer_pointer, password) simulation = p.factory.simgr(initial_state) #开启veritesting success_address = base + 0x71c def good_address(state): # 我们在路径探寻的时候的输出进行判断 stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Good Job.' in stdout_output else False def again_address(state): stdout_output = state.posix.dumps(sys.stdout.fileno()) return True if b'Try again.' in stdout_output else False simulation.explore(find=success_address) if simulation.found: solution_state = simulation.found[0] solution_state.add_constraints(solution_state.regs.eax != 0) solution = solution_state.solver.eval(password,cast_to=bytes).decode() print(f"[+] Success! Solution is: {solution}") else: raise Exception('Could not find the solution') if __name__ == '__main__': start = time.time() main(sys.argv) end = time.time() total = end - start print(total) [+] Success! Solution is: TSDLQKWZ # 1.3254785537719727 # 这道题做完,我还是有一些疑惑,这里是直接加载了.so文件,也就是说,直接处理的.so文件,根本没有管主程序的事,那该怎么同时加载.so文件和主程序呢?????
参考链接
https://github.com/jakespringer/angr_ctf #angr_ctf 题目链接