def do_equiv_instrs(instrs, gen_patched, all_diffs=None): """Check each instruction if it has an equivalent one and computes the changed bytes set. Optionally, it can generate a changed file with the equivalent instructions. Returns the list of changed bytes.""" changed_bytes = set() changed = [] for ins in instrs: if check_equiv(ins): changed.append(ins) diff = inp.get_diff(changed) if gen_patched: inp.patch(diff, "equiv") changed_bytes.update((ea for ea, orig, new in diff)) for ins in changed: if all_diffs != None: all_diffs.append(inp.get_diff([ins])) ins.reset_changed() return changed_bytes
def do_single_swaps(swaps, gen_patched, all_diffs=None): """Applies one swap at a time and optionally generates the patched .dll. Returns a set of the linear addresses of the changed bytes.""" changed_bytes = set() for i, swap in enumerate(swaps): success, changed = apply_swap_comb([swap]) if success: diff = inp.get_diff(changed) if gen_patched: inp.patch(diff, "swap-%06d" % i) if all_diffs != None: all_diffs.append(diff) changed_bytes.update((ea for ea, orig, curr in diff)) for ins in changed: ins.reset_changed() return changed_bytes
def get_entropy(f, g, live_regs, swaps, preservs, avail_regs): """Given a gadget, it returns the number of different ways that it can be broken. A second return value indicates whether this gadget can be eliminated (entropy).""" def _add_if_hit(g, diff, diffs): gdiff = tuple(d for d in diff if g.start <= d[0] < g.end) # d[0] is ea old_diffs_len = len(diffs) if len(gdiff) > 0: diffs.add(gdiff) return old_diffs_len != len(diffs) entropy = 1 eliminated = False # 1. swaps swap_diffs = set() gswaps = [] # filter swaps that actually hit the gadget gbytes = range(g.start, g.end) for s in swaps: # sbytes = [xrange(i.addr, i.addr+i.inst_len) for i in swap.get_instrs()] # if set(itertools.chain(*sbytes)) & set(xrange(g.start, g.end)): # gswaps.append(swap) for ins in s.get_instrs(): if any(ins.addr <= b < ins.addr + len(ins.bytes) for b in gbytes): gswaps.append(s) break # XXX use only at most 10 swaps here .. otherwise it takes forever for i, swap_comb in enumerate(swap.gen_swap_combinations(gswaps[:10])): success, changed = swap.apply_swap_comb(swap_comb) if not success: continue diff = inp.get_diff(changed) _add_if_hit(g, diff, swap_diffs) for ins in changed: ins.reset_changed() if i > 1000: print "killing after 1000 swap combinations" break entropy *= len(swap_diffs) + 1 eliminated = any(g.addrs[-1] == d[1] for d in itertools.chain(*swap_diffs)) print "\tswaps:", len(swap_diffs) # 2. preservs preserv_diffs = set() # 2.1 global substitution implicit = reduce(lambda x, y: x | y, [i.implicit for i in f.instrs if not i.f_exit], set()) preserv_regs = [ reg for reg, pushes, pops in preservs if reg not in implicit ] preserv_combs = [] if len(preserv_regs) >= 2: preserv_combs.extend(itertools.combinations(preserv_regs, 2)) preserv_combs.extend(itertools.product(preserv_regs, avail_regs)) for r1, r2 in preserv_combs: for ins in f.instrs: if not ins.swap_registers(r1, r2): break else: diff = inp.get_diff(f.instrs) _add_if_hit(g, diff, preserv_diffs) for ins in f.instrs: ins.reset_changed() # 2.2 reorder pushes/pops # XXX: a preserved register is used only after it's pushed! # and only before it's poped! # augment each ins in the preservs with blocks and find first/last preserv_blocks = set() for reg, pushes, pops in preservs: for ins in itertools.chain(pushes, pops): ins.block = next((b for b in f.blocks if ins in b.instrs), None) if not hasattr(ins.block, 'first'): ins.block.first = ins.block.instrs.index(ins) ins.block.last = ins.block.instrs.index(ins) else: ins.block.first = min(ins.block.first, ins.block.instrs.index(ins)) ins.block.last = max(ins.block.last, ins.block.instrs.index(ins)) preserv_blocks.add(ins.block) # group preservs in the same block and reorder them preservs_groups = [] for reg, pushes, pops in preservs: if len(preservs_groups) == 0: preservs_groups.append([(reg, pushes, pops)]) continue for group in preservs_groups: if (all(p1.block == p2.block for p1, p2 in zip(group[0][1], pushes)) and all(p1.block == p2.block for p1, p2 in zip(group[0][2], pops))): group.append((reg, pushes, pops)) break else: preservs_groups.append([(reg, pushes, pops)]) # do reorder of pushes/pops for group in (g for g in preservs_groups if len(g) > 1): for group_perm in itertools.permutations(group): for block in preserv_blocks: block.rinstrs = block.instrs[:] block.curr_first = block.first block.curr_last = block.last for reg, pushes, pops in group: for push in pushes: push.block.rinstrs.remove(push) push.block.rinstrs.insert(push.block.curr_first, push) push.block.curr_first += 1 for pop in reversed(pops): pop.block.rinstrs.remove(pop) pop.block.rinstrs.insert(pop.block.curr_last - 1, pop) pop.block.curr_last -= 1 diff = set() for block in preserv_blocks: diff.update(inp.get_block_diff(block)) _add_if_hit(g, diff, preserv_diffs) entropy *= len(preserv_diffs) + 1 eliminated = eliminated or any(g.addrs[-1] == d[1] for d in itertools.chain(*preserv_diffs)) print "\tpreservs:", len(preserv_diffs) # overlaping gadgets! if g.overlap: # 3. equiv equiv_instrs = [] for ins in f.instrs: if equiv.check_equiv(ins): diff = inp.get_diff([ins]) if any(g.start <= ea < g.end for ea, orig, curr in diff): equiv_instrs.append(ins) eliminated = eliminated or any(g.addrs[-1] == d[1] for d in diff) ins.reset_changed() sr_equiv = [ i for i in equiv_instrs if i.bytes[i.opc_off] in objs.same_regs ] entropy *= 2**(len(equiv_instrs) - len(sr_equiv)) entropy *= 5**len(sr_equiv) print "\tequiv (instrs):", len( equiv_instrs), "(%d sr_equiv)" % len(sr_equiv) # 4. reorder reorder_diffs = set() # find the block(s) of the gadget for block in f.blocks: if block.begin <= g.start < block.end or block.begin <= g.end < block.end or ( g.start <= block.begin and g.end >= block.end): # dag = reorder.BuildBBDependenceDAG(block) # for order in itertools.permutations(block.instrs): # # all should be forward edges, from left to right # if any(order.index(u) < order.index(v) for u, v in dag.edges()): # continue for order in reorder.gen_topological_sortings(block): block.rinstrs = order diff = inp.get_block_diff(block) _add_if_hit(g, diff, reorder_diffs) eliminated = any(g.addrs[-1] == d[1] for d in itertools.chain(*reorder_diffs)) entropy *= len(reorder_diffs) + 1 print "\treorder:", len(reorder_diffs) if not g.overlap: # 4. reorder in non-overlapping reorder_breakes = 0 # find the block(s) of the gadget for block in f.blocks: if block.begin <= g.start < block.end or block.begin <= g.end < block.end or ( g.start <= block.begin and g.end >= block.end): # print "will work on block:", block for order in reorder.gen_topological_sortings(block): # print "test this order:", order block.rinstrs = order # update the address of reordered instrs for i, rins in enumerate(block.rinstrs): if i == 0: rins.raddr = block.begin else: rins.raddr = block.rinstrs[i - 1].raddr + len( block.rinstrs[i - 1].bytes) for ins in (f.code[a] for a in g.addrs if a in f.code): if ins.addr != ins.raddr and ins.raddr < g.start or ins.raddr >= g.end: reorder_breakes += 1 break entropy *= reorder_breakes + 1 print "\treorder (non-overlapping):", reorder_breakes print "\tgadget entropy:", entropy, "eliminated:", eliminated return entropy, eliminated
def get_entropy(f, g, live_regs, swaps, preservs, avail_regs): """Given a gadget, it returns the number of different ways that it can be broken. A second return value indicates whether this gadget can be eliminated (entropy).""" def _add_if_hit(g, diff, diffs): gdiff = tuple(d for d in diff if g.start <= d[0] < g.end) # d[0] is ea old_diffs_len = len(diffs) if len(gdiff) > 0: diffs.add(gdiff) return old_diffs_len != len(diffs) entropy = 1 eliminated = False # 1. swaps swap_diffs = set() gswaps = [] # filter swaps that actually hit the gadget gbytes = range(g.start, g.end) for s in swaps: # sbytes = [xrange(i.addr, i.addr+i.inst_len) for i in swap.get_instrs()] # if set(itertools.chain(*sbytes)) & set(xrange(g.start, g.end)): # gswaps.append(swap) for ins in s.get_instrs(): if any(ins.addr <= b < ins.addr + len(ins.bytes) for b in gbytes): gswaps.append(s) break # XXX use only at most 10 swaps here .. otherwise it takes forever for i, swap_comb in enumerate(swap.gen_swap_combinations(gswaps[:10])): success, changed = swap.apply_swap_comb(swap_comb) if not success: continue diff = inp.get_diff(changed) _add_if_hit(g, diff, swap_diffs) for ins in changed: ins.reset_changed() if i > 1000: print "killing after 1000 swap combinations" break entropy *= len(swap_diffs) + 1 eliminated = any(g.addrs[-1] == d[1] for d in itertools.chain(*swap_diffs)) print "\tswaps:", len(swap_diffs) # 2. preservs preserv_diffs = set() # 2.1 global substitution implicit = reduce(lambda x, y: x | y, [i.implicit for i in f.instrs if not i.f_exit], set()) preserv_regs = [reg for reg, pushes, pops in preservs if reg not in implicit] preserv_combs = [] if len(preserv_regs) >= 2: preserv_combs.extend(itertools.combinations(preserv_regs, 2)) preserv_combs.extend(itertools.product(preserv_regs, avail_regs)) for r1, r2 in preserv_combs: for ins in f.instrs: if not ins.swap_registers(r1, r2): break else: diff = inp.get_diff(f.instrs) _add_if_hit(g, diff, preserv_diffs) for ins in f.instrs: ins.reset_changed() # 2.2 reorder pushes/pops # XXX: a preserved register is used only after it's pushed! # and only before it's poped! # augment each ins in the preservs with blocks and find first/last preserv_blocks = set() for reg, pushes, pops in preservs: for ins in itertools.chain(pushes, pops): ins.block = next((b for b in f.blocks if ins in b.instrs), None) if not hasattr(ins.block, 'first'): ins.block.first = ins.block.instrs.index(ins) ins.block.last = ins.block.instrs.index(ins) else: ins.block.first = min(ins.block.first, ins.block.instrs.index(ins)) ins.block.last = max(ins.block.last, ins.block.instrs.index(ins)) preserv_blocks.add(ins.block) # group preservs in the same block and reorder them preservs_groups = [] for reg, pushes, pops in preservs: if len(preservs_groups) == 0: preservs_groups.append([(reg, pushes, pops)]) continue for group in preservs_groups: if (all(p1.block == p2.block for p1, p2 in zip(group[0][1], pushes)) and all(p1.block == p2.block for p1, p2 in zip(group[0][2], pops))): group.append((reg, pushes, pops)) break else: preservs_groups.append([(reg, pushes, pops)]) # do reorder of pushes/pops for group in (g for g in preservs_groups if len(g) > 1): for group_perm in itertools.permutations(group): for block in preserv_blocks: block.rinstrs = block.instrs[:] block.curr_first = block.first block.curr_last = block.last for reg, pushes, pops in group: for push in pushes: push.block.rinstrs.remove(push) push.block.rinstrs.insert(push.block.curr_first, push) push.block.curr_first += 1 for pop in reversed(pops): pop.block.rinstrs.remove(pop) pop.block.rinstrs.insert(pop.block.curr_last - 1, pop) pop.block.curr_last -= 1 diff = set() for block in preserv_blocks: diff.update(inp.get_block_diff(block)) _add_if_hit(g, diff, preserv_diffs) entropy *= len(preserv_diffs) + 1 eliminated = eliminated or any(g.addrs[-1] == d[1] for d in itertools.chain(*preserv_diffs)) print "\tpreservs:", len(preserv_diffs) # overlaping gadgets! if g.overlap: # 3. equiv equiv_instrs = [] for ins in f.instrs: if equiv.check_equiv(ins): diff = inp.get_diff([ins]) if any(g.start <= ea < g.end for ea, orig, curr in diff): equiv_instrs.append(ins) eliminated = eliminated or any(g.addrs[-1] == d[1] for d in diff) ins.reset_changed() sr_equiv = [i for i in equiv_instrs if i.bytes[i.opc_off] in objs.same_regs] entropy *= 2 ** (len(equiv_instrs) - len(sr_equiv)) entropy *= 5 ** len(sr_equiv) print "\tequiv (instrs):", len(equiv_instrs), "(%d sr_equiv)" % len(sr_equiv) # 4. reorder reorder_diffs = set() # find the block(s) of the gadget for block in f.blocks: if block.begin <= g.start < block.end or block.begin <= g.end < block.end or ( g.start <= block.begin and g.end >= block.end): # dag = reorder.BuildBBDependenceDAG(block) # for order in itertools.permutations(block.instrs): # # all should be forward edges, from left to right # if any(order.index(u) < order.index(v) for u, v in dag.edges()): # continue for order in reorder.gen_topological_sortings(block): block.rinstrs = order diff = inp.get_block_diff(block) _add_if_hit(g, diff, reorder_diffs) eliminated = any(g.addrs[-1] == d[1] for d in itertools.chain(*reorder_diffs)) entropy *= len(reorder_diffs) + 1 print "\treorder:", len(reorder_diffs) if not g.overlap: # 4. reorder in non-overlapping reorder_breakes = 0 # find the block(s) of the gadget for block in f.blocks: if block.begin <= g.start < block.end or block.begin <= g.end < block.end or ( g.start <= block.begin and g.end >= block.end): # print "will work on block:", block for order in reorder.gen_topological_sortings(block): # print "test this order:", order block.rinstrs = order # update the address of reordered instrs for i, rins in enumerate(block.rinstrs): if i == 0: rins.raddr = block.begin else: rins.raddr = block.rinstrs[i - 1].raddr + len(block.rinstrs[i - 1].bytes) for ins in (f.code[a] for a in g.addrs if a in f.code): if ins.addr != ins.raddr and ins.raddr < g.start or ins.raddr >= g.end: reorder_breakes += 1 break entropy *= reorder_breakes + 1 print "\treorder (non-overlapping):", reorder_breakes print "\tgadget entropy:", entropy, "eliminated:", eliminated return entropy, eliminated
def do_reg_preservs(instrs, blocks, preservs, avail_regs, gen_patched, all_diffs=None): """Changes the preserved registers within the given instructions and optionally generates instances of the input file with these permuted register preservations. Returns the changed bytes set.""" changed_bytes = set() #f_exit instructions (ret) implicitly use the preserved registers.. exclude them! implicit = reduce(lambda x, y: x | y, [i.implicit for i in instrs if not i.f_exit], set()) regs = [reg for reg, pushes, pops in preservs if reg not in implicit] if not preservs or (len(regs) == 1 and not avail_regs): return changed_bytes # preserved registers that are implicitly used should not be touched # hmm .. rotation should still happen even when all regs are implicitly used.. #if not regs: # return changed_bytes if len(regs) == 1: #we know we have available regs.append(avail_regs.pop()) if len(regs) >= 2: # we do need the combinations here, because we want to change as much # as possible in case a register cannot be swapped (e.g. weird modrm/sib) for r1, r2 in itertools.combinations(regs, 2): for ins in instrs: if not ins.swap_registers(r1, r2): #print "MAYBEBUG: broke for", ins.disas, "in register preservations!", r1, r2, ins.regs break else: #no break! diff = inp.get_diff(instrs) if gen_patched: inp.patch(diff, "preserv-%s-%s" % (r1, r2)) if all_diffs != None: all_diffs.append(diff) changed_bytes.update((ea for ea, orig, curr in diff)) for ins in instrs: ins.reset_changed() # the second part bellow can be useful in cases where global swap # cannot be applied due to implicit uses # group preservs in the same block and reorder them # augment each ins in the preservs with blocks for reg, pushes, pops in preservs: for ins in itertools.chain(pushes, pops): ins.block = next((b for b in blocks if ins in b.instrs), None) preservs_groups = [] for reg, pushes, pops in preservs: if len(preservs_groups) == 0: preservs_groups.append([[reg, pushes, pops]]) continue for group in preservs_groups: if (all(p1.block == p2.block for p1, p2 in zip(group[0][1], pushes)) and all(p1.block == p2.block for p1, p2 in zip(group[0][2], pops))): group.append([reg, pushes, pops]) break else: preservs_groups.append([[reg, pushes, pops]]) #print "grouping!:", '\n'.join((str(g) for g in preservs_groups)) # reorder (rotate) the pushes/pops by placing the last one in the # first's position and shifting all the other instrs down # TODO: breaks if esp is used within the reordered block! (BIB @ 070012F1) for group in (g for g in preservs_groups if len(g) > 1): # group them by block #pushes_by_block = {} ins_by_block = {} for reg, pushes, pops in group: for ins in itertools.chain(pushes, pops): if (ins.block, ins.mnem) not in ins_by_block: ins_by_block[(ins.block, ins.mnem)] = [] ins_by_block[(ins.block, ins.mnem)].append(ins) # in each block, move the latest ins to the ealiest's position for (block, mnem), instrs in ins_by_block.iteritems(): instrs.sort(key=lambda x: x.addr) first = block.instrs.index(instrs[0]) last = block.instrs.index(instrs[-1]) block.rinstrs = block.instrs[:first] block.rinstrs.append(block.instrs[last]) block.rinstrs.extend(block.instrs[first:last]) block.rinstrs.extend(block.instrs[last + 1:]) diff = inp.get_block_diff(block) changed_bytes.update((ea for ea, orig, curr in diff)) #XXX should also generate patched files ... return changed_bytes
def do_reg_preservs(instrs, blocks, preservs, avail_regs, gen_patched, all_diffs=None): """Changes the preserved registers within the given instructions and optionally generates instances of the input file with these permuted register preservations. Returns the changed bytes set.""" changed_bytes = set() # f_exit instructions (ret) implicitly use the preserved registers.. exclude them! implicit = reduce(lambda x, y: x | y, [i.implicit for i in instrs if not i.f_exit], set()) regs = [reg for reg, pushes, pops in preservs if reg not in implicit] if not preservs or (len(regs) == 1 and not avail_regs): return changed_bytes # preserved registers that are implicitly used should not be touched # hmm .. rotation should still happen even when all regs are implicitly used.. # if not regs: # return changed_bytes if len(regs) == 1: # we know we have available regs.append(avail_regs.pop()) if len(regs) >= 2: # we do need the combinations here, because we want to change as much # as possible in case a register cannot be swapped (e.g. weird modrm/sib) for r1, r2 in itertools.combinations(regs, 2): for ins in instrs: if not ins.swap_registers(r1, r2): # print "MAYBEBUG: broke for", ins.disas, "in register preservations!", r1, r2, ins.regs break else: # no break! diff = inp.get_diff(instrs) if gen_patched: inp.patch(diff, "preserv-%s-%s" % (r1, r2)) if all_diffs != None: all_diffs.append(diff) changed_bytes.update((ea for ea, orig, curr in diff)) for ins in instrs: ins.reset_changed() # the second part bellow can be useful in cases where global swap # cannot be applied due to implicit uses # group preservs in the same block and reorder them # augment each ins in the preservs with blocks for reg, pushes, pops in preservs: for ins in itertools.chain(pushes, pops): ins.block = next((b for b in blocks if ins in b.instrs), None) preservs_groups = [] for reg, pushes, pops in preservs: if len(preservs_groups) == 0: preservs_groups.append([[reg, pushes, pops]]) continue for group in preservs_groups: if (all(p1.block == p2.block for p1, p2 in zip(group[0][1], pushes)) and all(p1.block == p2.block for p1, p2 in zip(group[0][2], pops))): group.append([reg, pushes, pops]) break else: preservs_groups.append([[reg, pushes, pops]]) # print "grouping!:", '\n'.join((str(g) for g in preservs_groups)) # reorder (rotate) the pushes/pops by placing the last one in the # first's position and shifting all the other instrs down # TODO: breaks if esp is used within the reordered block! (BIB @ 070012F1) for group in (g for g in preservs_groups if len(g) > 1): # group them by block # pushes_by_block = {} ins_by_block = {} for reg, pushes, pops in group: for ins in itertools.chain(pushes, pops): if (ins.block, ins.mnem) not in ins_by_block: ins_by_block[(ins.block, ins.mnem)] = [] ins_by_block[(ins.block, ins.mnem)].append(ins) # in each block, move the latest ins to the ealiest's position for (block, mnem), instrs in ins_by_block.iteritems(): instrs.sort(key=lambda x: x.addr) first = block.instrs.index(instrs[0]) last = block.instrs.index(instrs[-1]) block.rinstrs = block.instrs[:first] block.rinstrs.append(block.instrs[last]) block.rinstrs.extend(block.instrs[first:last]) block.rinstrs.extend(block.instrs[last + 1:]) diff = inp.get_block_diff(block) changed_bytes.update((ea for ea, orig, curr in diff)) # XXX should also generate patched files ... return changed_bytes