def test_jumpouts_and_indirectcalls(): expected_jumpouts = [("KPRCA_00034", 0x08050140, [0x0805014f])] exptected_unresolved_calls = [("KPRCA_00025", 0x8048ECC)] cfg_cache = {} for binary, function_addr, jmps in expected_jumpouts: if binary in cfg_cache: cfg = cfg_cache["binary"] else: filepath = os.path.join(bin_location, binary) backend = DetourBackend(filepath) cfg = backend.cfg cfg_cache["binary"] = cfg ff = cfg.functions[function_addr] nose.tools.assert_equal([a.addr for a in ff.jumpout_sites], jmps) for binary, function_addr in exptected_unresolved_calls: if binary in cfg_cache: cfg = cfg_cache["binary"] else: filepath = os.path.join(bin_location, binary) backend = DetourBackend(filepath) cfg = backend.cfg cfg_cache["binary"] = cfg ff = cfg.functions[function_addr] nose.tools.assert_true(ff.has_unresolved_calls)
def test_detect_syscall_wrapper(): filepath = os.path.join(bin_location, "CROMU_00071") backend = DetourBackend(filepath) cfg = backend.cfg legitimate_syscall_wrappers = set([(0x804d483, 1), (0x804d491, 2), (0x804d4b1, 3), (0x804d4d1, 4), (0x804d4f7, 5), (0x804d511, 6), (0x804d525, 7)]) syscall_wrappers = set([(ff.addr,cfg_utils.detect_syscall_wrapper(backend,ff)) \ for ff in cfg.functions.values() if cfg_utils.detect_syscall_wrapper(backend,ff)!=None]) print "syscall wrappers in CROMU_00071:" print map(lambda x: (hex(x[0]), x[1]), syscall_wrappers) nose.tools.assert_equal(syscall_wrappers, legitimate_syscall_wrappers) filepath = os.path.join(bin_location, "CROMU_00070") backend = DetourBackend(filepath) cfg = backend.cfg legitimate_syscall_wrappers = set([(0x804d690, 5), (0x804d66a, 4), (0x804d6be, 7), (0x804d6aa, 6), (0x804d61c, 1), (0x804d64a, 3), (0x804d62a, 2)]) syscall_wrappers = set([(ff.addr,cfg_utils.detect_syscall_wrapper(backend,ff)) \ for ff in cfg.functions.values() if cfg_utils.detect_syscall_wrapper(backend,ff)!=None]) print "syscall wrappers in CROMU_00070:" print map(lambda x: (hex(x[0]), x[1]), syscall_wrappers) nose.tools.assert_equal(syscall_wrappers, legitimate_syscall_wrappers)
def test_conflicting_symbols(self): filepath = os.path.join(self.bin_location, "printf_nopie") patches = [] backend = DetourBackend(filepath) patches.append(AddRODataPatch(b"0123456789abcdef", "aaa")) patches.append(AddRODataPatch(b"\n", "aaa")) exc = False try: backend.apply_patches(patches) except ValueError: exc = True self.assertTrue(exc) patches = [] backend = DetourBackend(filepath) patches.append(AddRODataPatch(b"0123456789abcdef", "aaa")) added_code = ''' nop ''' patches.append(AddCodePatch(added_code, "aaa")) exc = False try: backend.apply_patches(patches) except ValueError: exc = True self.assertTrue(exc)
def test_is_floatingpoint_function(): filepath = os.path.join(bin_location, "CROMU_00071") backend = DetourBackend(filepath) cfg = backend.cfg floatingpoint_functions = [ ff for ff in cfg.functions.values() if cfg_utils.is_floatingpoint_function(backend, ff) ] floatingpoint_functions = sorted(floatingpoint_functions, key=lambda f: f.addr) #print "floatingpoint_functions in CROMU_00071" #print "\n".join(map(lambda f:hex(f.addr),floatingpoint_functions)) first = floatingpoint_functions[0].addr ff = floatingpoint_functions[-1] if ff.ret_sites == None: last = ff.addr else: if len(ff.ret_sites) == 0: last = ff.addr else: last = max([e.addr for e in ff.blocks]) print hex(first), hex(last) real_start = 0x804d5c6 real_end = 0x0804D78b nose.tools.assert_true(first == real_start) nose.tools.assert_true(last <= real_end) nose.tools.assert_true(last > real_end - 0x20) #I allow some imprecision filepath = os.path.join(bin_location, "CROMU_00070") backend = DetourBackend(filepath) cfg = backend.cfg floatingpoint_functions = [ ff for ff in cfg.functions.values() if cfg_utils.is_floatingpoint_function(backend, ff) ] floatingpoint_functions = sorted(floatingpoint_functions, key=lambda f: f.addr) #print "floatingpoint_functions in CROMU_00071" #print "\n".join(map(lambda f:hex(f.addr),floatingpoint_functions)) first = floatingpoint_functions[0].addr ff = floatingpoint_functions[-1] if ff.ret_sites == None: last = ff.addr else: if len(ff.ret_sites) == 0: last = ff.addr else: last = max([e.addr for e in ff.blocks]) print hex(first), hex(last) real_start = 0x0804D75f real_end = 0x0804D924 nose.tools.assert_true(first == real_start) nose.tools.assert_true(last <= real_end) nose.tools.assert_true(last > real_end - 0x20) #I allow some imprecision
def run_test(self, filename, patches, set_oep=None, inputvalue=None, expected_output=None, expected_returnCode=None, try_without_cfg=True): filepath = os.path.join(self.bin_location, filename) pipe = subprocess.PIPE with patcherex.utils.tempdir() as td: tmp_file = os.path.join(td, "patched") backend = DetourBackend(filepath, try_without_cfg=try_without_cfg) backend.apply_patches(patches) if set_oep: backend.set_oep(backend.name_map[set_oep]) backend.save(tmp_file) p = subprocess.Popen([ self.qemu_location, "-L", "/usr/mips64-linux-gnuabi64", tmp_file ], stdin=pipe, stdout=pipe, stderr=pipe) res = p.communicate(inputvalue) if expected_output: self.assertEqual(res[0], expected_output) if expected_returnCode: self.assertEqual(p.returncode, expected_returnCode) return backend
def test_fullcfg_properties(): binaries = [ "KPRCA_00009","KPRCA_00025","NRFIN_00004","CROMU_00071", "CADET_00003", # "CROMU_00070", "EAGLE_00005", # "KPRCA_00019" ] # these are either "slides" into a call or jump to the beginning of a call # ("KPRCA_00025",0x804b041) is a very weird case, but Fish convinced me that it is correct legittimate_jumpouts = [("KPRCA_00025",0x80480bf),("KPRCA_00025",0x804b041), ("KPRCA_00025",0x804bd85),("KPRCA_00025",0x804c545),("KPRCA_00025",0x804c5b5), ("KPRCA_00025", 0x804c925), ("KPRCA_00019",0x8048326), ("KPRCA_00019",0x8048b41),("KPRCA_00019",0x804882e),("KPRCA_00019",0x8048cd1), ("KPRCA_00019",0x8048cca),("KPRCA_00019",0x8049408), ("KPRCA_00019", 0x8048846), ("KPRCA_00019", 0x804884b), ("KPRCA_00019", 0x804885f), ("KPRCA_00019", 0x804886f), ("KPRCA_00019", 0x8048877), ("CROMU_00071", 0x804d77d), ("CROMU_00071", 0x804d783), ] for binary in binaries: print "testing",binary,"..." filepath = os.path.join(bin_location, binary) backend = DetourBackend(filepath) cfg = backend.cfg node_addrs_dict = defaultdict(set) for k,ff in cfg.functions.iteritems(): for node_addr in ff.block_addrs_set: node_addrs_dict[node_addr].add(ff) # check that endpoints are the union of callouts, rets, and jumpouts endpoint_union = set(ff.callout_sites).union(set(ff.ret_sites).union(set(ff.jumpout_sites))) nose.tools.assert_equal(set(ff.endpoints),endpoint_union) # check that we do not encounter any unexpected jumpout if not ff.is_syscall and ff.returning and not ff.has_unresolved_calls and \ not ff.has_unresolved_jumps and ff.startpoint != None and ff.endpoints: if not cfg_utils.is_floatingpoint_function(backend,ff): if len(ff.jumpout_sites) > 0: unexpected_jumpout = [(binary,int(jo.addr)) for jo in ff.jumpout_sites \ if (binary,int(jo.addr)) not in legittimate_jumpouts] if len(unexpected_jumpout)>0: print "unexpected jumpouts in",binary,map(lambda x:hex(x[1]),unexpected_jumpout) nose.tools.assert_equal(len(unexpected_jumpout),0) # check that every node only belongs to a single function for k,v in node_addrs_dict.iteritems(): if len(v)>1: print "found node in multiple functions:",hex(k),repr(v) nose.tools.assert_equal(len(v),1) # check that every node only appears once in the cfg nn = set() instruction_set = set() for n in cfg.nodes(): nose.tools.assert_true(n.addr not in nn) nn.add(n.addr) # check that every instruction appears only in one node for iaddr in n.instruction_addrs: nose.tools.assert_true(iaddr not in instruction_set) instruction_set.add(iaddr)
def generate_medium_detour_binary(self, test_bin=True): try: nr = NetworkRules() backend = DetourBackend(self.infile) patches = [] patches.extend(IndirectCFI(self.infile, backend).get_patches()) patches.extend( TransmitProtection(self.infile, backend).get_patches()) patches.extend(ShiftStack(self.infile, backend).get_patches()) patches.extend(Adversarial(self.infile, backend).get_patches()) patches.extend(Backdoor(self.infile, backend).get_patches()) # patches.extend(NxStack(self.infile,backend).get_patches()) patches.extend( MallocExtPatcher(self.infile, backend).get_patches()) patches.extend( StackRetEncryption(self.infile, backend).get_patches()) patches.extend( UninitializedPatcher(self.infile, backend).get_patches()) patches.extend( NoFlagPrintfPatcher(self.infile, backend).get_patches()) backend.apply_patches(patches) final_content = backend.get_final_content() if test_bin: test_bin_with_qemu(self.infile, final_content) res = (final_content, "") except PatcherexError, e: traceback.print_exc(e) res = (None, None)
def run_test(self, file, patches, set_oep=None, inputs=None, expected_output=None, expected_returnCode=None): filepath = os.path.join(self.bin_location, file) pipe = subprocess.PIPE with patcherex.utils.tempdir() as td: tmp_file = os.path.join(td, "patched") backend = DetourBackend(filepath) backend.apply_patches(patches) if set_oep: backend.set_oep(backend.name_map[set_oep]) backend.save(tmp_file) p = subprocess.Popen([tmp_file], stdin=pipe, stdout=pipe, stderr=pipe) res = p.communicate(inputs) if expected_output: self.assertEqual(res[0], expected_output) if expected_returnCode: self.assertEqual(p.returncode, expected_returnCode) return backend
def test_0b32aa01_01(): print "Testing test_0b32aa01_01..." filepath = os.path.join(bin_location, "0b32aa01_01_2") backend = DetourBackend(filepath) cfg = backend.cfg legitimate_functions = set([ 0x80480a0, 0x8048230, 0x8048400, 0x80484f0, 0x80485fc, 0x8048607, 0x8048615, 0x8048635, 0x80486c3, 0x80486a9L, 0x8048613L, 0x8048655L, 0x804867bL, 0x80486deL, 0x8048695L ]) non_syscall_functions = [ v for k, v in cfg.functions.iteritems() if not v.is_syscall ] #check startpoints, I know that sometimes they could be None, but this should not happen in CADET_00003 function_entrypoints = set( [f.startpoint.addr for f in non_syscall_functions]) print "additional:", map(hex, function_entrypoints - legitimate_functions) print "skipped:", map(hex, legitimate_functions - function_entrypoints) nose.tools.assert_equal(function_entrypoints == legitimate_functions, True) sane_functions = [ v for k, v in cfg.functions.iteritems() if is_sane_function(v) ] function_entrypoints = set([f.startpoint.addr for f in sane_functions]) print "additional:", map(hex, function_entrypoints - legitimate_functions) print "skipped:", map(hex, legitimate_functions - function_entrypoints) nose.tools.assert_equal(function_entrypoints == legitimate_functions, True) #all sane functions ends with ret in CADET_00003 for ff in sane_functions: node = cfg.get_any_node(ff.addr, is_syscall=False) nose.tools.assert_equal(node != None, True) nose.tools.assert_equal(len(node.instruction_addrs) > 0, True) node = cfg.get_any_node(ff.addr + 1, is_syscall=False, anyaddr=True) nose.tools.assert_equal(node != None, True) nose.tools.assert_equal(len(node.instruction_addrs) > 0, True) nose.tools.assert_equal(ff.startpoint != None, True) nose.tools.assert_equal(ff.ret_sites != None, True) if ff.addr == 0x080485FC or ff.addr == 0x8048607: nose.tools.assert_equal(ff.returning == False, True) if ff.returning: nose.tools.assert_equal(len(ff.ret_sites) > 0, True) for endpoint in ff.ret_sites: bb = backend.project.factory.block(endpoint.addr) last_instruction = bb.capstone.insns[-1] nose.tools.assert_equal(last_instruction.mnemonic == u"ret", True) syscalls = [v for k, v in cfg.functions.iteritems() if v.is_syscall] for ff in syscalls: bb1 = cfg.get_any_node(ff.addr) nose.tools.assert_equal(len(bb1.predecessors) >= 1, True) bb2 = bb1.predecessors[0] bb = backend.project.factory.block(bb2.addr) ii = bb.capstone.insns[-1] nose.tools.assert_equal(ii.mnemonic == u"int" and ii.op_str == u"0x80", True)
def generate_fidget_bitflip_binary(self): nr = NetworkRules() midfile = self.infile + '.fidget' + str(random.randrange(0, 1000)) fidget_it(self.infile, midfile) backend = DetourBackend(midfile) cp = Bitflip(midfile, backend) patches1 = cp.get_patches() backend.apply_patches(patches1) return (backend.get_final_content(), nr.get_bitflip_rule())
def execute(self, patches, binary, output_expected=None): with patcherex.utils.tempdir() as td: tmp_file = os.path.join(td, "patched") #backend operations backend = DetourBackend(self.binary_path + binary) backend.apply_patches(patches) backend.save(tmp_file) #run the patched binary pipe = subprocess.PIPE p = subprocess.Popen([tmp_file], stdin=pipe, stdout=pipe, stderr=pipe) res = p.communicate() #check the results self.assertEqual(res[0], output_expected)
def generate_medium_lief_detour_binary(self, test_bin=True): try: backend = DetourBackend(self.infile) patches = [] patches.extend( StackRetEncryption(self.infile, backend).get_patches()) backend.apply_patches(patches) final_content = backend.get_final_content() if test_bin: test_bin_with_qemu(self.infile, final_content) res = (final_content, "") except PatcherexError, e: traceback.print_exc(e) res = (None, None)
def test_setlongjmp_detection(): solutions = [ ("CADET_00003",0x80486c8,0x80486e3), ("CROMU_00008",0x804C3AC,0x804C3C7), ("Ofast/CROMU_00008",0x804ABD7,0x804ABF2), ] for tbin, setjmp, longjmp in solutions: filepath = os.path.join(bin_location, tbin) backend = DetourBackend(filepath) cfg = backend.cfg for k,ff in cfg.functions.items(): msg = "detection failure in %s (%#x vs %#x)" if cfg_utils.is_setjmp(backend,ff): nose.tools.assert_equal(setjmp,ff.addr,"setjmp " + msg %(tbin,setjmp,ff.addr)) elif cfg_utils.is_longjmp(backend,ff): nose.tools.assert_equal(longjmp,ff.addr,"longjmp " + msg %(tbin,setjmp,ff.addr))
def test_EAGLE_00005_bb(): filepath = os.path.join(bin_location, "EAGLE_00005") backend = DetourBackend(filepath) cfg = backend.cfg # import IPython; IPython.embed() bbs = [(0x0804A73C,3),(0x0804BB3D,1),(0x0804A0E5,6),(0x0804A101,3),(0x0804B145,1),(0x0804BB42,2)] for addr,ni in bbs: n = cfg.model.get_any_node(addr) nose.tools.assert_true(n != None) nose.tools.assert_true(len(n.instruction_addrs) == ni) caller_map = [ (0x8048D28,set([0x8048685,0x804877b])), (0x8048FEA,set([0x80483d2])), ] inv_callsites = map_callsites(cfg) for b,clist in caller_map: nose.tools.assert_true(is_last_returning_block(b,cfg,backend.project)) node_addresses = set([n.addr for n in last_block_to_callers(b,cfg,inv_callsites)]) print(hex(b), "<--", map(hex, node_addresses)) nose.tools.assert_equal(clist,node_addresses)
parser = ArgumentParser() parser.add_argument("original") parser.add_argument("patched") args = parser.parse_args() try: os.unlink(args.patched) except OSError: pass try: os.unlink(args.patched + ".bin") except OSError: pass backend = DetourBackend(args.original, variant="stm32") patches = [] typedef = ''' typedef unsigned short uint16_t; typedef unsigned char uint8_t; ''' transmit_code = ''' void rx_brake_routine( unsigned char buff[], void *bumper ){ uint16_t speed_value; uint8_t brake_switch; speed_value = (buff[3] << 8) + buff[2]; brake_switch = (buff[4] & 0b00001100) >> 2; ((unsigned char*)bumper)[13] = (brake_switch) ? 1 : 0; if ( ((unsigned char*)bumper)[13] ) {
from argparse import ArgumentParser from patcherex.backends.detourbackend import DetourBackend from patcherex.patches import * parser = ArgumentParser() parser.add_argument("original") parser.add_argument("patched") args = parser.parse_args() backend = DetourBackend(args.original) patches = [] typedef = ''' typedef unsigned short uint16_t; typedef unsigned char uint8_t; ''' transmit_code = ''' void rx_brake_routine( uint8_t buff[], void *bumper ){ uint16_t speed_value; uint8_t brake_switch; speed_value = (buff[3] << 8) + buff[2]; brake_switch = (buff[4] & 0b00001100) >> 2; ((uint8_t*)bumper)[5] = (brake_switch) ? 1 : 0; if ( ((uint8_t*)bumper)[5] ) { if ((speed_value > 0) && ( !((uint8_t*)bumper)[4]) ){ ((uint8_t*)bumper)[6] = 1; } } else {
from argparse import ArgumentParser from patcherex.backends.detourbackend import DetourBackend from patcherex.patches import * parser = ArgumentParser() parser.add_argument("original") parser.add_argument("patched") args = parser.parse_args() backend = DetourBackend(args.original, replace_note_segment=True) patches = [] # regenerate cfg with specified main function address backend.cfg = backend.project.analyses.CFGFast(normalize=True, data_references=True, force_complete_scan=False, function_starts={0x11169, }) backend.ordered_nodes = backend._get_ordered_nodes(backend.cfg) ################################################################ # - Patch encrypt(0x1254C) and decrypt(0x125B8) # 12c12 # < #include "EVP_des_ede3_cbc.h" # --- # > #include "EVP_aes_256_cbc.h" ################################################################ EVP_aes_256_cbc_addr = hex(0x47f90) # 0x12564: "bl EVP_des_ede3_cbc" in encrypt() patches.append(InlinePatch(0x12564, "bl " + EVP_aes_256_cbc_addr)) # 0x125D0: "bl EVP_des_ede3_cbc" in decrypt() patches.append(InlinePatch(0x125D0, "bl " + EVP_aes_256_cbc_addr))
def test_CADET_00003(): print "Testing test_CADET_00003..." filepath = os.path.join(bin_location, "CADET_00003") backend = DetourBackend(filepath) cfg = backend.cfg #how to get the list of functions from the IDA list: #print "["+",\n".join(map(hex,hex,[int(l.split()[2],16) for l in a.split("\n") if l.strip()]))+"]" legitimate_functions = set([ 0x80480a0, 0x8048230, 0x8048400, 0x80484f0, 0x80485fc, 0x804860c, 0x804861a, 0x804863a, 0x8048705, 0x8048735, 0x8048680L, 0x80486e3L, 0x80486c8L, 0x80486aeL, 0x8048618L, 0x804865aL, 0x804869aL ]) non_syscall_functions = [ v for k, v in cfg.functions.iteritems() if not v.is_syscall ] #check startpoints, I know that sometimes they could be None, but this should not happen in CADET_00003 function_entrypoints = set( [f.startpoint.addr for f in non_syscall_functions]) print "additional:", map(hex, function_entrypoints - legitimate_functions) print "skipped:", map(hex, legitimate_functions - function_entrypoints) nose.tools.assert_equal(function_entrypoints == legitimate_functions, True) sane_functions = [ v for k, v in cfg.functions.iteritems() if is_sane_function(v) ] function_entrypoints = set([f.startpoint.addr for f in sane_functions]) print "additional:", map(hex, function_entrypoints - legitimate_functions) print "skipped:", map(hex, legitimate_functions - function_entrypoints) nose.tools.assert_equal(function_entrypoints == legitimate_functions, True) #something which was wrong in the past n = cfg.get_any_node(0x80485EC) nose.tools.assert_true(len(n.instruction_addrs) == 1) nose.tools.assert_true(n.instruction_addrs[0] == 0x80485EC) #all sane functions ends with ret in CADET_00003 for ff in sane_functions: node = cfg.get_any_node(ff.addr, is_syscall=False) nose.tools.assert_equal(node != None, True) nose.tools.assert_equal(len(node.instruction_addrs) > 0, True) node = cfg.get_any_node(ff.addr + 1, is_syscall=False, anyaddr=True) nose.tools.assert_equal(node != None, True) nose.tools.assert_equal(len(node.instruction_addrs) > 0, True) nose.tools.assert_equal(ff.startpoint != None, True) nose.tools.assert_equal(ff.ret_sites != None, True) if ff.addr == 0x080485FC or ff.addr == 0x804860C: nose.tools.assert_equal(ff.returning == False, True) if ff.returning: nose.tools.assert_equal(len(ff.ret_sites) > 0, True) for endpoint in ff.ret_sites: bb = backend.project.factory.block(endpoint.addr) last_instruction = bb.capstone.insns[-1] nose.tools.assert_equal(last_instruction.mnemonic == u"ret", True) syscalls = [v for k, v in cfg.functions.iteritems() if v.is_syscall] for ff in syscalls: bb1 = cfg.get_any_node(ff.addr) nose.tools.assert_equal(len(bb1.predecessors) >= 1, True) bb2 = bb1.predecessors[0] bb = backend.project.factory.block(bb2.addr) ii = bb.capstone.insns[-1] nose.tools.assert_equal(ii.mnemonic == u"int" and ii.op_str == u"0x80", True) endpoint_set = set( map(lambda x: (x.addr, x.size), cfg.functions[0x08048230].endpoints)) nose.tools.assert_equal(set([(0x080483F4, 12), (0x080483D5, 20)]), endpoint_set) ret_set = set( map(lambda x: (x.addr, x.size), cfg.functions[0x08048230].ret_sites)) nose.tools.assert_equal(set([(0x080483F4, 12)]), ret_set) # the following is a case of a bb that should be split by normalization # because the bb is split by a "subsequent" jump bb = cfg.get_any_node(0x804824F) nose.tools.assert_equal(bb != None, True) nose.tools.assert_equal(bb.size == 13, True) bb = cfg.get_any_node(0x08048230) nose.tools.assert_equal(bb != None, True) nose.tools.assert_equal(bb.size == 31, True)
# parse command line arguments parser = ArgumentParser() parser.add_argument("original") parser.add_argument("patched") parser.add_argument("patchinfo", nargs="?") args = parser.parse_args() # setup logging options logging.getLogger("angr").propagate = False logging.getLogger("cle").propagate = False logging.getLogger("pyvex").propagate = False logging.getLogger("patcherex").setLevel(logging.DEBUG) # initialize backend backend = DetourBackend(args.original, replace_note_segment=True, try_reuse_unused_space=True) patches = [] # replace the original hash function (using CRC-32) with a modified version of it (using SHA-256) digest_message = ''' #include <stdlib.h> typedef void EVP_MD_CTX; void digest_message(const unsigned char *message, size_t message_len, unsigned char **digest, unsigned int *digest_len) { EVP_MD_CTX *mdctx; if((mdctx = EVP_MD_CTX_new()) == NULL) handleErrors(); if(1 != EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL))