├── LICENSE.txt ├── README.md ├── images ├── deobfuscated.png └── obfuscated.png ├── mod_utils.py ├── modeflattener.py ├── requirements.txt └── samples ├── check_passwd_flat ├── check_passwd_flat.patched ├── layers ├── layers.patched ├── quarkslab └── quarkslab.patched /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Suraj Malhotra 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MODeflattener 2 | ***Miasm's Ollvm Deflattener*** 3 | > MODeflattener deobfuscates control flow flattened functions obfuscated by [OLLVM](https://github.com/obfuscator-llvm/obfuscator) using [Miasm](https://github.com/cea-sec/miasm). 4 | https://mrt4ntr4.github.io/MODeflattener/ 5 | 6 | Flattened Flow | Deflattened Flow 7 | :-------------------------:|:-------------------------: 8 | ![obfuscated](./images/obfuscated.png) | ![deobfuscated](./images/deobfuscated.png) 9 | 10 | ## Installation 11 | ``` 12 | git clone https://github.com/mrT4ntr4/MODeflattener.git 13 | cd MODeflattener 14 | pip3 install -r requirements.txt 15 | ``` 16 | 17 | ## Usage 18 | ``` 19 | └──╼ $python3 modeflattener.py -h 20 | usage: modeflattener [-h] [-a] [-l LOG] filename patch_filename address 21 | 22 | positional arguments: 23 | filename file to deobfuscate 24 | patch_filename deobfuscated file name 25 | address obfuscated function address 26 | 27 | optional arguments: 28 | -h, --help show this help message and exit 29 | -a, --all find functions recursively and deobfuscate if flattened 30 | -l LOG, --log LOG logging level (default=INFO) 31 | ``` 32 | 33 | ### Supported Architectures 34 | - *x86* 35 | 36 | ### Bonus 37 | - [Tim Blazytko's flattening heuristic script](https://gist.github.com/mrphrazer/da32217f231e1dd842986f94aa6d9d37) 38 | While disassembling the specified function we can look out for other functions used by it and can make use of this script to automatically detect whether it is a flattened one and try to deobfuscate it. This has already been integrated into the tool! 39 | - [nop-hider idapython script](https://gist.github.com/JusticeRage/795badf81fe59454963a06070d132b06) 40 | This script hides the nop instructions from IDA graph view as the backbone is converted into a long nop chain after deobfuscation. 41 | 42 | 43 | # References 44 | [Dissecting LLVM Obfuscator - RPISEC](https://rpis.ec/blog/dissection-llvm-obfuscator-p1/) 45 | [Automated Detection of Control-flow Flattening - Tim Blazytko](https://synthesis.to/2021/03/03/flattening_detection.html) -------------------------------------------------------------------------------- /images/deobfuscated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrT4ntr4/MODeflattener/2877ef8459c621add8f82acaa14d247900d14bd3/images/deobfuscated.png -------------------------------------------------------------------------------- /images/obfuscated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrT4ntr4/MODeflattener/2877ef8459c621add8f82acaa14d247900d14bd3/images/obfuscated.png -------------------------------------------------------------------------------- /mod_utils.py: -------------------------------------------------------------------------------- 1 | from miasm.arch.x86.arch import mn_x86 2 | from future.utils import viewitems, viewvalues 3 | from miasm.core.utils import encode_hex 4 | from miasm.core.graph import DiGraph 5 | from miasm.ir.ir import * 6 | from miasm.expression.expression import * 7 | from miasm.analysis.ssa import get_phi_sources_parent_block, \ 8 | irblock_has_phi 9 | from miasm.analysis.data_flow import ReachingDefinitions,\ 10 | DiGraphDefUse 11 | 12 | import logging 13 | 14 | _log = logging.getLogger('modeflattener') 15 | 16 | asmb = lambda patch_str, loc_db: mn_x86.asm(mn_x86.fromstring(patch_str, loc_db, 32))[0] 17 | rel = lambda addr, patch_addr: hex(addr - patch_addr) 18 | 19 | 20 | def save_cfg(cfg, name): 21 | import subprocess 22 | open(name, 'w').write(cfg.dot()) 23 | subprocess.call(["dot", "-Tpng", name, "-o", name.split('.')[0]+'.png']) 24 | subprocess.call(["rm", name]) 25 | 26 | 27 | def patch_gen(instrs, loc_db, nop_addrs, link): 28 | final_patch = b"" 29 | start_addr = instrs[0].offset 30 | 31 | for instr in instrs: 32 | #omitting useless instructions 33 | if instr.offset not in nop_addrs: 34 | if instr.is_subcall(): 35 | #generate asm for fixed calls with relative addrs 36 | patch_addr = start_addr + len(final_patch) 37 | tgt = loc_db.get_location_offset(instr.args[0].loc_key) 38 | _log.info("CALL %#x" % tgt) 39 | call_patch_str = "CALL %s" % rel(tgt, patch_addr) 40 | _log.debug("call patch : %s" % call_patch_str) 41 | call_patch = asmb(call_patch_str, loc_db) 42 | final_patch += call_patch 43 | _log.debug("call patch asmb : %s" % encode_hex(call_patch)) 44 | else: 45 | #add the original bytes 46 | final_patch += instr.b 47 | 48 | patch_addr = start_addr + len(final_patch) 49 | _log.debug("jmps patch_addr : %#x", patch_addr) 50 | jmp_patches = b"" 51 | # cleaning the control flow by patching with real jmps locs 52 | if 'cond' in link: 53 | t_addr = int(link['true_next'], 16) 54 | f_addr = int(link['false_next'], 16) 55 | jcc = link['cond'].replace('CMOV', 'J') 56 | _log.info("%s %#x" % (jcc, t_addr)) 57 | _log.info("JMP %#x" % f_addr) 58 | 59 | patch1_str = "%s %s" % (jcc, rel(t_addr, patch_addr)) 60 | jmp_patches += asmb(patch1_str, loc_db) 61 | patch_addr += len(jmp_patches) 62 | 63 | patch2_str = "JMP %s" % (rel(f_addr, patch_addr)) 64 | jmp_patches += asmb(patch2_str, loc_db) 65 | _log.debug("jmp patches : %s; %s" % (patch1_str, patch2_str)) 66 | 67 | else: 68 | n_addr = int(link['next'], 16) 69 | _log.info("JMP %#x" % n_addr) 70 | 71 | patch_str = "JMP %s" % rel(n_addr, patch_addr) 72 | jmp_patches = asmb(patch_str, loc_db) 73 | 74 | _log.debug("jmp patches : %s" % patch_str) 75 | 76 | _log.debug("jmp patches asmb : %s" % encode_hex(jmp_patches)) 77 | final_patch += jmp_patches 78 | 79 | return final_patch 80 | 81 | 82 | def get_cff_info(asmcfg): 83 | preds = {} 84 | for blk in asmcfg.blocks: 85 | offset = asmcfg.loc_db.get_location_offset(blk.loc_key) 86 | preds[offset] = asmcfg.predecessors(blk.loc_key) 87 | # pre-dispatcher is the one with max predecessors 88 | pre_dispatcher = sorted(preds, key=lambda key: len(preds[key]), reverse=True)[0] 89 | # dispatcher is the one which suceeds pre-dispatcher 90 | dispatcher = asmcfg.successors(asmcfg.loc_db.get_offset_location(pre_dispatcher))[0] 91 | dispatcher = asmcfg.loc_db.get_location_offset(dispatcher) 92 | 93 | # relevant blocks are those which preceed the pre-dispatcher 94 | relevant_blocks = [] 95 | for loc in preds[pre_dispatcher]: 96 | offset = asmcfg.loc_db.get_location_offset(loc) 97 | relevant_blocks.append(get_block_father(asmcfg, offset)) 98 | 99 | return relevant_blocks, dispatcher, pre_dispatcher 100 | 101 | 102 | # do backwards search for jmp instruction to find start of relevant block 103 | def get_block_father(asmcfg, blk_offset): 104 | blk = asmcfg.getby_offset(blk_offset) 105 | checklist = [blk.loc_key] 106 | 107 | pred = asmcfg.predecessors(blk.loc_key)[0] 108 | while True: 109 | curr_bloc = asmcfg.loc_key_to_block(pred) 110 | if curr_bloc.lines[-1].name in ['JZ', 'JMP', 'JNZ']: 111 | break 112 | checklist.append(pred) 113 | pred = asmcfg.predecessors(curr_bloc.loc_key)[0] 114 | 115 | return asmcfg.loc_db.get_location_offset(checklist[-1]) 116 | 117 | 118 | def get_phi_vars(ircfg): 119 | res = [] 120 | blks = list(ircfg.blocks) 121 | irblock = (ircfg.blocks[blks[-1]]) 122 | 123 | if irblock_has_phi(irblock): 124 | for dst, sources in viewitems(irblock[0]): 125 | phi_vars = sources.args 126 | parent_blks = get_phi_sources_parent_block( 127 | ircfg, 128 | irblock.loc_key, 129 | phi_vars 130 | ) 131 | 132 | for var, loc in parent_blks.items(): 133 | irblock = ircfg.get_block(list(loc)[0]) 134 | for asg in irblock: 135 | dst, src = asg.items()[0] 136 | if dst == var: 137 | res += [int(src)] 138 | 139 | return res 140 | 141 | 142 | def find_var_asg(ircfg, var): 143 | val_list = [] 144 | res = {} 145 | for lbl, irblock in viewitems(ircfg.blocks): 146 | for assignblk in irblock: 147 | result = set(assignblk).intersection(var) 148 | if not result: 149 | continue 150 | else: 151 | dst, src = assignblk.items()[0] 152 | if isinstance(src, ExprInt): 153 | res['next'] = int(src) 154 | val_list += [int(src)] 155 | elif isinstance(src, ExprSlice): 156 | phi_vals = get_phi_vars(ircfg) 157 | res['true_next'] = phi_vals[0] 158 | res['false_next'] = phi_vals[1] 159 | val_list += phi_vals 160 | return res, val_list 161 | 162 | 163 | def find_state_var_usedefs(ircfg, search_var): 164 | var_addrs = set() 165 | reachings = ReachingDefinitions(ircfg) 166 | digraph = DiGraphDefUse(reachings) 167 | # the state var always a leaf 168 | for leaf in digraph.leaves(): 169 | if leaf.var == search_var: 170 | for x in (digraph.reachable_parents(leaf)): 171 | var_addrs.add(ircfg.get_block(x.label)[x.index].instr.offset) 172 | return var_addrs 173 | -------------------------------------------------------------------------------- /modeflattener.py: -------------------------------------------------------------------------------- 1 | from future.utils import viewitems, viewvalues 2 | from miasm.analysis.binary import Container 3 | from miasm.analysis.machine import Machine 4 | from miasm.core.locationdb import LocationDB 5 | from miasm.analysis.simplifier import * 6 | from miasm.expression.expression import * 7 | from miasm.core.asmblock import * 8 | from miasm.arch.x86.arch import mn_x86 9 | from miasm.core.utils import encode_hex 10 | 11 | from argparse import ArgumentParser 12 | import time 13 | import logging 14 | import pprint 15 | 16 | from mod_utils import * 17 | 18 | def setup_logger(loglevel): 19 | FORMAT = '[%(levelname)s] %(message)s' 20 | logging.basicConfig(format=FORMAT) 21 | logger = logging.getLogger('modeflattener') 22 | 23 | numeric_level = getattr(logging, loglevel.upper(), None) 24 | if not isinstance(numeric_level, int): 25 | raise ValueError('Invalid log level: %s' % loglevel) 26 | 27 | logger.setLevel(numeric_level) 28 | 29 | return logger 30 | 31 | # https://synthesis.to/2021/03/15/control_flow_analysis.html 32 | def calc_flattening_score(asm_graph): 33 | score = 0.0 34 | for head in asm_graph.heads_iter(): 35 | dominator_tree = asm_graph.compute_dominator_tree(head) 36 | for block in asm_graph.blocks: 37 | block_key = asm_graph.loc_db.get_offset_location(block.lines[0].offset) 38 | dominated = set( 39 | [block_key] + [b for b in dominator_tree.walk_depth_first_forward(block_key)]) 40 | if not any([b in dominated for b in asm_graph.predecessors(block_key)]): 41 | continue 42 | score = max(score, len(dominated)/len(asm_graph.nodes())) 43 | return score 44 | 45 | # callback to stop disassembling when it encounters any jump 46 | def stop_on_jmp(mdis, cur_bloc, offset_to_dis): 47 | jmp_instr_check = cur_bloc.lines[-1].name in ['JMP','JZ','JNZ'] 48 | 49 | if jmp_instr_check: 50 | cur_bloc.bto.clear() 51 | offset_to_dis.clear() 52 | 53 | def deflat(ad, func_info): 54 | main_asmcfg, main_ircfg = func_info 55 | 56 | # get flattening info 57 | relevant_blocks, dispatcher, pre_dispatcher = get_cff_info(main_asmcfg) 58 | dispatcher_blk = main_asmcfg.getby_offset(dispatcher) 59 | dispatcher_first_instr = dispatcher_blk.lines[0] 60 | state_var = dispatcher_first_instr.get_args_expr()[1] 61 | 62 | _log = logging.getLogger('modeflattener') 63 | _log.info('dispatcher: %#x' % dispatcher) 64 | _log.info('pre_dispatcher: %#x' % pre_dispatcher) 65 | _log.info('state_var: %s' % state_var) 66 | _log.info('relevant_blocks (%d) : '%len(relevant_blocks) 67 | + ', '.join([hex(addr) for addr in relevant_blocks])) 68 | print() 69 | 70 | backbone = {} 71 | fixed_cfg = {} 72 | val_list = [] 73 | rel_blk_info = {} 74 | 75 | machine = Machine(cont.arch) 76 | 77 | for addr in relevant_blocks: 78 | _log.debug("Getting info for relevant block @ %#x"%addr) 79 | loc_db = LocationDB() 80 | mdis = machine.dis_engine(cont.bin_stream, loc_db=loc_db) 81 | mdis.dis_block_callback = stop_on_jmp 82 | asmcfg = mdis.dis_multiblock(addr) 83 | 84 | lifter = machine.lifter_model_call(loc_db) 85 | ircfg = lifter.new_ircfg_from_asmcfg(asmcfg) 86 | ircfg_simplifier = IRCFGSimplifierCommon(lifter) 87 | ircfg_simplifier.simplify(ircfg, addr) 88 | 89 | # marking the instructions affecting the state variable as nop_addrs 90 | nop_addrs = find_state_var_usedefs(ircfg, state_var) 91 | rel_blk_info[addr] = (asmcfg, nop_addrs) 92 | 93 | head = loc_db.get_offset_location(addr) 94 | ssa_simplifier = IRCFGSimplifierSSA(lifter) 95 | ssa = ssa_simplifier.ircfg_to_ssa(ircfg, head) 96 | #we only use do_propagate_expressions ssa simp pass 97 | ssa_simplifier.do_propagate_expressions(ssa, head) 98 | #save_cfg(ircfg, 'ssa_%x'%addr) 99 | 100 | # find the possible values of the state variable 101 | var_asg, tmpval_list = find_var_asg(ircfg, {state_var}) 102 | _log.debug('%#x %s' % (addr, var_asg)) 103 | 104 | # adding all the possible values to a global list 105 | val_list += tmpval_list 106 | 107 | last_blk = list(asmcfg.blocks)[-1] 108 | # checking the type of relevant blocks on the basis of no. of possible values 109 | if len(var_asg) == 1: 110 | var_asg['next'] = hex(var_asg['next']) 111 | #map value of state variable in rel block 112 | fixed_cfg[hex(addr)] = var_asg 113 | elif len(var_asg) > 1: 114 | #extracting the condition from the last 3rd line 115 | cond_mnem = last_blk.lines[-3].name 116 | _log.debug('cond used: %s' % cond_mnem) 117 | var_asg['cond'] = cond_mnem 118 | var_asg['true_next'] = hex(var_asg['true_next']) 119 | var_asg['false_next'] = hex(var_asg['false_next']) 120 | # map the conditions and possible values dictionary to the cfg info 121 | fixed_cfg[hex(addr)] = var_asg 122 | elif len(last_blk.lines)==1 and len(var_asg)==0: 123 | #tail has a single instruction ie. jmp and no assignments 124 | tail = addr 125 | _log.debug("found backbone tail @ %#x" % addr) 126 | else: 127 | _log.error("no state variable assignments found for relevant block @ %#x" % addr) 128 | # return empty patches as deobfuscation failed!! 129 | return {} 130 | 131 | 132 | _log.debug('val_list: ' + ', '.join([hex(val) for val in val_list])) 133 | 134 | # get the value for reaching a particular relevant block 135 | for lbl, irblock in viewitems(main_ircfg.blocks): 136 | for assignblk in irblock: 137 | asg_items = assignblk.items() 138 | if asg_items: # do not enter if nop 139 | dst, src = asg_items[0] 140 | if isinstance(src, ExprOp): 141 | if src.op == 'FLAG_EQ_CMP': 142 | arg = src.args[1] 143 | if isinstance(arg, ExprInt): 144 | if int(arg) in val_list: 145 | cmp_val = int(arg) 146 | var, locs = irblock[-1].items()[0] 147 | true_dst = main_ircfg.loc_db.get_location_offset(locs.src1.loc_key) 148 | backbone[hex(cmp_val)] = hex(true_dst) 149 | 150 | _log.debug('***** BACKBONE *****\n' + pprint.pformat(backbone)) 151 | 152 | for offset, link in fixed_cfg.items(): 153 | if 'cond' in link: 154 | tval = fixed_cfg[offset]['true_next'] 155 | fval = fixed_cfg[offset]['false_next'] 156 | fixed_cfg[offset]['true_next'] = backbone[tval] 157 | fixed_cfg[offset]['false_next'] = backbone[fval] 158 | elif 'next' in link: 159 | fixed_cfg[offset]['next'] = backbone[link['next']] 160 | else: 161 | # the tail doesn't has any condition 162 | tail = int(offset, 16) 163 | 164 | # unmark tail as a relevant block 165 | rel_blk_info.pop(tail) 166 | _log.debug('removed tail @ %#x from relevant_blocks' % tail) 167 | 168 | _log.debug('******FIXED CFG*******\n' + pprint.pformat(fixed_cfg)) 169 | 170 | tail = main_asmcfg.getby_offset(tail).lines[-1] 171 | # get the backbone info from dispatcher and tail 172 | backbone_start, backbone_end = dispatcher, tail.offset + tail.l 173 | _log.debug('backbone_start = %#x, backbone_end = %#x' % (backbone_start, backbone_end)) 174 | 175 | patches = {} 176 | 177 | for addr in rel_blk_info.keys(): 178 | _log.info('=> cleaning relevant block @ %#x' % addr) 179 | asmcfg, nop_addrs = rel_blk_info[addr] 180 | link = fixed_cfg[hex(addr)] 181 | instrs = [instr for blk in asmcfg.blocks for instr in blk.lines] 182 | last_instr = instrs[-1] 183 | end_addr = last_instr.offset + last_instr.l 184 | # calculate original length of block before patching 185 | orig_len = end_addr - addr 186 | # nop the jmp to pre-dispatcher 187 | nop_addrs.add(last_instr.offset) 188 | _log.debug('nop_addrs: ' + ', '.join([hex(addr) for addr in nop_addrs])) 189 | patch = patch_gen(instrs, asmcfg.loc_db, nop_addrs, link) 190 | patch = patch.ljust(orig_len, b"\x90") 191 | patches[addr] = patch 192 | _log.debug('patch generated %s\n' % encode_hex(patch)) 193 | 194 | _log.info(">>> NOPing Backbone (%#x - %#x) <<<" % (backbone_start, backbone_end)) 195 | nop_len = backbone_end - backbone_start 196 | patches[backbone_start] = b"\x90" * nop_len 197 | 198 | return patches 199 | 200 | 201 | 202 | if __name__ == '__main__': 203 | parser = ArgumentParser("modeflattener") 204 | parser.add_argument('filename', help="file to deobfuscate") 205 | parser.add_argument('patch_filename', help="deobfuscated file name") 206 | parser.add_argument('address', help="obfuscated function address") 207 | parser.add_argument('-a', "--all", action="store_true", 208 | help="find and deobfuscate all flattened functions recursively") 209 | parser.add_argument('-l', "--log", help="logging level (default=INFO)", 210 | default='info') 211 | 212 | args = parser.parse_args() 213 | 214 | loglevel = args.log 215 | _log = setup_logger(loglevel) 216 | 217 | deobf_start_time = time.time() 218 | 219 | forg = open(args.filename, 'rb') 220 | fpatch = open(args.patch_filename, 'wb') 221 | fpatch.write(forg.read()) 222 | 223 | loc_db = LocationDB() 224 | 225 | global cont 226 | cont = Container.from_stream(open(args.filename, 'rb'), loc_db) 227 | 228 | supported_arch = ['x86_32', 'x86_64'] 229 | _log.info("Architecture : %s" % cont.arch) 230 | 231 | if cont.arch not in supported_arch: 232 | _log.error("Architecture unsupported : %s" % cont.arch) 233 | exit(1) 234 | 235 | section_ep = cont.bin_stream.bin.virt.parent.getsectionbyvad(cont.entry_point).sh 236 | bin_base_addr = section_ep.addr - section_ep.offset 237 | _log.info('bin_base_addr: %#x' % bin_base_addr) 238 | 239 | machine = Machine(cont.arch) 240 | mdis = machine.dis_engine(cont.bin_stream, loc_db=loc_db) 241 | 242 | ad = int(args.address, 0) 243 | todo = [(mdis, None, ad)] 244 | done = set() 245 | all_funcs = set() 246 | all_funcs_blocks = {} 247 | 248 | while todo: 249 | mdis, caller, ad = todo.pop(0) 250 | if ad in done: 251 | continue 252 | done.add(ad) 253 | asmcfg = mdis.dis_multiblock(ad) 254 | lifter = machine.lifter_model_call(mdis.loc_db) 255 | ircfg = lifter.new_ircfg_from_asmcfg(asmcfg) 256 | 257 | _log.info('found func @ %#x (%d)' % (ad, len(all_funcs))) 258 | 259 | all_funcs.add(ad) 260 | all_funcs_blocks[ad] = (asmcfg, ircfg) 261 | 262 | if args.all: 263 | for block in asmcfg.blocks: 264 | instr = block.get_subcall_instr() 265 | if not instr: 266 | continue 267 | for dest in instr.getdstflow(mdis.loc_db): 268 | if not dest.is_loc(): 269 | continue 270 | offset = mdis.loc_db.get_location_offset(dest.loc_key) 271 | todo.append((mdis, instr, offset)) 272 | 273 | for ad in all_funcs: 274 | asmcfg = all_funcs_blocks[ad][0] 275 | score = calc_flattening_score(asmcfg) 276 | if score > 0.9: 277 | print('-------------------------') 278 | print('| func : %#x |' % ad) 279 | print('-------------------------') 280 | fcn_start_time = time.time() 281 | patches = deflat(ad, all_funcs_blocks[ad]) 282 | 283 | if patches: 284 | for offset, data in patches.items(): 285 | fpatch.seek(offset - bin_base_addr) 286 | fpatch.write(data) 287 | 288 | fcn_end_time = time.time() - fcn_start_time 289 | _log.info("PATCHING SUCCESSFUL for function @ %#x (%.2f secs)\n" % (ad, fcn_end_time)) 290 | else: 291 | _log.error("PATCHING UNSUCCESSFUL for function @ %#x\n" % ad) 292 | 293 | else: 294 | _log.error("unable to deobfuscate func %#x (cff score = %f)\n" % (ad, score)) 295 | 296 | fpatch.close() 297 | deobf_end_time = time.time() - deobf_start_time 298 | 299 | _log.info("Deobfuscated file saved at '%s' (Total Time Taken : %.2f secs)" % (args.patch_filename, deobf_end_time)) 300 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | future==0.18.2 2 | git+git://github.com/cea-sec/miasm@069440e#egg=miasm 3 | pyparsing==2.4.7 4 | -------------------------------------------------------------------------------- /samples/check_passwd_flat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrT4ntr4/MODeflattener/2877ef8459c621add8f82acaa14d247900d14bd3/samples/check_passwd_flat -------------------------------------------------------------------------------- /samples/check_passwd_flat.patched: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrT4ntr4/MODeflattener/2877ef8459c621add8f82acaa14d247900d14bd3/samples/check_passwd_flat.patched -------------------------------------------------------------------------------- /samples/layers: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrT4ntr4/MODeflattener/2877ef8459c621add8f82acaa14d247900d14bd3/samples/layers -------------------------------------------------------------------------------- /samples/layers.patched: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrT4ntr4/MODeflattener/2877ef8459c621add8f82acaa14d247900d14bd3/samples/layers.patched -------------------------------------------------------------------------------- /samples/quarkslab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrT4ntr4/MODeflattener/2877ef8459c621add8f82acaa14d247900d14bd3/samples/quarkslab -------------------------------------------------------------------------------- /samples/quarkslab.patched: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrT4ntr4/MODeflattener/2877ef8459c621add8f82acaa14d247900d14bd3/samples/quarkslab.patched --------------------------------------------------------------------------------