def testAddSubSimple(self): for opcode in equiv.equiv_addsub8: imm, neg = '\x66', '\x9A' i = insn.Instruction(0, bytes(chr(opcode) + imm), 0) self.assertTrue(equiv.check_equiv(i)) nbytes = bytearray(chr(equiv.equiv_addsub8[opcode]) + neg) self.assertEqual(i.cbytes, nbytes) for opcode in equiv.equiv_addsub32: imm, neg = '\x66\x66\x66\x66', '\x9A\x99\x99\x99' i = insn.Instruction(0, bytes(chr(opcode) + imm), 0) self.assertTrue(equiv.check_equiv(i)) nbytes = bytearray(chr(equiv.equiv_addsub32[opcode]) + neg) self.assertEqual(i.cbytes, nbytes)
def testSameRegisters(self): for opcode in equiv.same_regs: for modrm in equiv.same_reg_modrms: i = insn.Instruction(0, bytes(chr(opcode) + chr(modrm)), 0) self.assertTrue(equiv.check_equiv(i)) nbytes = bytearray( chr(equiv.same_regs[opcode][0]) + chr(modrm)) self.assertEqual(i.cbytes, nbytes)
def testAddSubOpcodeExtension(self): ext_mask = 0b00101000 # modrm = mod 2b | reg 3b | rm 3b modrm_mod = 0xC0 # 11000000 src operand is a reg (test only this case) for opcode in (0x80, 0x81, 0x83): for modrm_rm, reg in enumerate(self.rm_regs): for ext in (0b101000, 0b000000): if opcode == 0x81: imm, neg = '\x66\x66\x66\x66', '\x9A\x99\x99\x99' else: imm, neg = '\x66', '\x9A' modrm = modrm_mod | ext | modrm_rm i = insn.Instruction(0, bytes(chr(opcode) + chr(modrm) + imm), 0) self.assertTrue(equiv.check_equiv(i)) nbytes = bytearray( chr(opcode) + chr(modrm ^ ext_mask) + neg) self.assertEqual(i.cbytes, nbytes)
def testBothRegisters(self): modrm_mod = 0xC0 # 11000000 src operand is a reg (test only this case) modrm_regs = [0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x38] dir_bit = 0b00000010 for modrm_rm, reg in enumerate(self.rm_regs): for modrm_reg in modrm_regs: for opcode in equiv.both_regs + tuple( (op ^ dir_bit for op in equiv.both_regs)): if opcode in equiv.same_regs: continue modrm_byte = modrm_mod | modrm_rm | modrm_reg i = insn.Instruction(0, bytes(chr(opcode) + chr(modrm_byte)), 0) self.assertTrue(equiv.check_equiv(i)) # swapped reg, rm! modrm_byte = modrm_mod | modrm_rm << 3 | modrm_reg >> 3 nbytes = bytearray(chr(opcode ^ dir_bit) + chr(modrm_byte)) self.assertEqual(i.cbytes, nbytes)
def test_XCHG_XORSUB(self): i = insn.Instruction(0, '\x87\xD8', 0) # xchg eax, ebx self.assertEqual(equiv.check_equiv(i), True) i = insn.Instruction(0, str(i.cbytes), 0) # xchg ebx, eax self.assertEqual(i.disas, "xchg ebx,eax")
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