# double free & use-after-free https://bbs.pediy.com/thread-266757.htm 前天在寫llvm pass又重新看到的angr 想起以前看到有人寫 https://xz.aliyun.com/t/7275 double free & use-after-free漏洞挖掘,他這種寫法可能導致z3求解狀態過多 https://bbs.pediy.com/thread-266757.htm 在第二題的地方有寫如何避開過多狀態,來改裝一下 # testfile ```c= #include <stdio.h> #include <stdlib.h> char bss[0x10]={0}; int main(int argc, char const *argv[]) { char buf[0x10]={0}; int times=4; unsigned long *ptr=&bss; while(times--) { puts("input:"); read(0,buf,8); switch(atoi(buf)) { case 1: puts("malloc!"); *ptr=malloc(0x30); // printf("%p,%p,%p\n", &ptr,ptr,*ptr); break; case 2: if (*ptr) { puts("free!"); free(*ptr); } else { puts("fail to free"); return; } break; case 3: if (*ptr) { puts("edit!"); read(0,*ptr,8); } else { puts("fail to edit"); return; } break; case 4: if (*ptr) { puts("show!"); write(1,*ptr,8); } else { puts("fail to show"); return; } break; } } return 0; } ``` # python ```python= # Before you begin, here are a few notes about these capture-the-flag # challenges. # # Each binary, when run, will ask for a password, which can be entered via stdin # (typing it into the console.) Many of the levels will accept many different # passwords. Your goal is to find a single password that works for each binary. # # If you enter an incorrect password, the program will print "Try again." If you # enter a correct password, the program will print "Good Job." # # Each challenge will be accompanied by a file like this one, named # "scaffoldXX.py". It will offer guidance as well as the skeleton of a possible # solution. You will have to edit each file. In some cases, you will have to # edit it significantly. While use of these files is recommended, you can write # a solution without them, if you find that they are too restrictive. # # Places in the scaffoldXX.py that require a simple substitution will be marked # with three question marks (???). Places that require more code will be marked # with an ellipsis (...). Comments will document any new concepts, but will be # omitted for concepts that have already been covered (you will need to use # previous scaffoldXX.py files as a reference to solve the challenges.) If a # comment documents a part of the code that needs to be changed, it will be # marked with an exclamation point at the end, on a separate line (!). import angr import claripy from angr.sim_type import SimTypeTop,SimTypeLength from angr import sim_options as so from angrmanagement.utils.graph import to_supergraph from binascii import b2a_hex class malloc_hook(angr.procedures.libc.malloc.malloc): def run(self, sim_size): self.argument_types = {0: SimTypeLength(self.state.arch)} self.return_type = self.ty_ptr(SimTypeTop(sim_size)) addr=self.state.heap._malloc(sim_size) size=self.state.solver.eval(sim_size) if "malloc_list" in self.state.globals: malloc_list=self.state.globals["malloc_list"] else: self.state.globals["malloc_list"]={} malloc_list=self.state.globals["malloc_list"] malloc_list[addr]=size return addr class free_hook(angr.procedures.libc.free.free): def run(self, ptr): self.argument_types = {0: self.ty_ptr(SimTypeTop())} f_ptr=self.state.solver.eval(ptr) if "free_list" in self.state.globals: free_list=self.state.globals["free_list"] if f_ptr in free_list: print("double free:") print("stdout:\n",self.state.posix.dumps(1)) print("stdin:\n",self.state.posix.dumps(0)) else: self.state.globals["free_list"]={} free_list=self.state.globals["free_list"] if "malloc_list" in self.state.globals: malloc_list=self.state.globals["malloc_list"] if f_ptr in malloc_list: free_list[f_ptr]=malloc_list[f_ptr] return self.state.heap._free(ptr) ''' 获取避免执行到的地址列表 关键!可以大大提高angr符号执行速度 ''' test = [] def get_avoid_list(cfg, start, target): print("cfg") # print(len(start)) avoid_list=[] if start.addr == target: return (True,[]) if len(list(cfg.successors(start))) == 0: return (False, [start.addr]) else: if start not in test: test.append(start) succs = list(cfg.successors(start)) if len(succs) == 1: # print("cfg") print(start.addr) # avoid_list.append(start.addr) can_reach_target, avoid_list = get_avoid_list(cfg, succs[0], target) if can_reach_target: return (True, avoid_list) else: avoid_list.append(start.addr) return (False, avoid_list) elif len(succs) == 2: # print("cfg2") print(start.addr) can_reach_target0, avoid_list0 = get_avoid_list(cfg, succs[0], target) can_reach_target1, avoid_list1 = get_avoid_list(cfg, succs[1], target) if can_reach_target0 and can_reach_target1: return (True, []) elif not can_reach_target0 and not can_reach_target1: avoid_list = avoid_list0 + avoid_list1 avoid_list.append(start.addr) return (False, avoid_list) else: avoid_list = avoid_list0 + avoid_list1 return (True, avoid_list) else: exit(0) return (False, []) return (False, []) def check_addr(state,act): addr=act.addr.ast if isinstance(addr,int): return addr if isinstance(addr,claripy.ast.bv.BV): return state.solver.eval(addr) return 0 def Check_UAF_R(state): if "free_list" not in state.globals: if "before_free" in state.globals: before_free=state.globals["before_free"] else: state.globals["before_free"]=[] before_free=state.globals["before_free"] action_now=reversed(state.history.actions.hardcopy) for act in action_now: if act not in before_free: before_free.append(act) else: before_free=state.globals["before_free"] action_now=reversed(state.history.actions.hardcopy) action=[i for i in action_now if i not in before_free] malloc_list=state.globals["malloc_list"] free_list=state.globals["free_list"] for act in action: if act.type=='mem' and act.action=='read' : addr=check_addr(state,act) if addr==0: print("error addr:",act.addr) break for f in free_list: if f==addr: print("\n[========find a UAF read========]") print("[UAF-R]stdout:") print(state.posix.dumps(1)) print("[UAF-R]trigger arbitrary read input:") print(state.posix.dumps(0)) break def Check_UAF_W(state): if "free_list" not in state.globals: if "before_free" in state.globals: before_free=state.globals["before_free"] else: state.globals["before_free"]=[] before_free=state.globals["before_free"] action_now=reversed(state.history.actions.hardcopy) for act in action_now: if act not in before_free: before_free.append(act) else: before_free=state.globals["before_free"] action_now=reversed(state.history.actions.hardcopy) action=[i for i in action_now if i not in before_free] malloc_list=state.globals["malloc_list"] free_list=state.globals["free_list"] for act in action: if act.type=='mem' and act.action=='write' : addr=check_addr(state,act) if addr==0: print("error:",act.addr) break for f in free_list: if f==addr: print("\n[========find a UAF write========]") print("[UAF-W]stdout:") print(state.posix.dumps(1)) print("[UAF-W]trigger arbitrary write input:") print(state.posix.dumps(0)) break ''' 对目标函数进行符号执行,求解到达call system执行所需要的输入 ''' def explore_func(proj, target_func, target_block, target_cfg): new_list=[] payload_list = [] avoid_list=[] for x in target_cfg.nodes : new_list.append(x) try: # print(type(new_list[0])) can_reach_target, avoid_list = get_avoid_list(target_cfg,new_list[0], target_block) print(avoid_list) state = proj.factory.call_state(target_func) simgr = proj.factory.simgr(state,save_unconstrained=True) simgr.use_technique(angr.exploration_techniques.DFS()) extras = {so.REVERSE_MEMORY_NAME_MAP, so.TRACK_ACTION_HISTORY,so.ZERO_FILL_UNCONSTRAINED_MEMORY} state=proj.factory.entry_state(add_options=extras) simgr.explore(find=target_block, avoid=avoid_list) while simgr.active: for act in simgr.active: Check_UAF_R(act) Check_UAF_W(act) # for found in simgr.found: # payload_list.append(found.posix.dumps(0)) except Exception as ex: print(ex) # exit(0) return payload_list def get_system_addr(cfg): for func_addr in cfg.functions: func = cfg.functions.get(func_addr) if func.name == 'free': return func_addr+16 return None def explore_payload(bin_path): proj = angr.Project(bin_path, load_options={'auto_load_libs': False}) proj_cfg = proj.analyses.CFGFast() proj.hook_symbol('malloc',malloc_hook()) proj.hook_symbol('free',free_hook()) system_addr = get_system_addr(proj_cfg) if system_addr == None: return [] print(f'Found system function in {hex(system_addr)}.') payload_list=[] for func_addr in proj_cfg.functions: func = proj_cfg.functions.get(func_addr) cfg = func.transition_graph cfg = to_supergraph(cfg) print(func ) for node in cfg.nodes: block = proj.factory.block(node.addr) for inst in block.capstone.insns: print(inst) print(inst.op_str == hex(system_addr)) if inst.mnemonic == 'call' : print(inst.mnemonic ) print(inst.op_str) if inst.mnemonic == 'call' and inst.op_str == hex(system_addr): target_func = func_addr target_block = block.addr target_cfg = cfg print(f'Found target function in {hex(target_func)}') print(f'Found target block in {hex(target_block)}') payload_list += explore_func(proj, target_func, target_block, target_cfg) return payload_list if __name__ == '__main__': filename="./a.out" # p = angr.Project(filename,auto_load_libs=False)# payload_list = explore_payload(filename) # print(payload_list) # for payload in payload_list: # print('payload=' + b2a_hex(payload).decode()) # p.hook_symbol('malloc',malloc_hook()) # p.hook_symbol('free',free_hook()) # extras = {so.REVERSE_MEMORY_NAME_MAP, so.TRACK_ACTION_HISTORY,so.ZERO_FILL_UNCONSTRAINED_MEMORY} # state=p.factory.entry_state(add_options=extras) # simgr = p.factory.simulation_manager(state,save_unconstrained=True) # simgr.use_technique(angr.exploration_techniques.Spiller()) # while simgr.active: # simgr.step() ``` 比較要注意的是main進入點,有稍微小改, 整體思考方向就是,先透過cfg然後走訪第一個cfg的節點,從這些節點找到free的節點,然後記錄不可能到達free路徑的節點 這樣可以大大的減少求解所需要的路徑。 這樣不到一分鐘就可以求解五個以上的double free的路徑,當然實際狀況要靠這個去找到double free和use after free,還是可能遇到路徑過多的情況狀況,可能要找新的方式去避免,改天有想到再來改一下. ![](https://i.imgur.com/Rr8YQSE.png) 缺點的話,我目前還是在wsl記憶體超過4g就會掛了 ![](https://i.imgur.com/CgrOL9n.png) 記憶體狀態應該還可以更小不過,路徑狀態可能性太多了!