├── .gitignore ├── README.md ├── am_graph.py ├── bogus_control_flow ├── __init__.py ├── debogus.py ├── samples │ ├── bin │ │ ├── target_arm │ │ ├── target_arm_bogus │ │ ├── target_arm_bogus_recovered │ │ ├── target_x86 │ │ ├── target_x86_bogus │ │ └── target_x86_bogus_recovered │ └── src │ │ └── target.c └── test.py ├── flat_control_flow ├── __init__.py ├── deflat.py ├── samples │ ├── bin │ │ ├── check_passwd_arm │ │ ├── check_passwd_arm64 │ │ ├── check_passwd_arm64_flat │ │ ├── check_passwd_arm64_flat_recovered │ │ ├── check_passwd_arm_flat │ │ ├── check_passwd_arm_flat_recovered │ │ ├── check_passwd_x8664 │ │ ├── check_passwd_x8664_flat │ │ └── check_passwd_x8664_flat_recovered │ └── src │ │ └── check_passwd.c └── test.py └── util.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # IPython 79 | profile_default/ 80 | ipython_config.py 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # celery beat schedule file 86 | celerybeat-schedule 87 | 88 | # SageMath parsed files 89 | *.sage.py 90 | 91 | # Environments 92 | .env 93 | .venv 94 | env/ 95 | venv/ 96 | ENV/ 97 | env.bak/ 98 | venv.bak/ 99 | 100 | # Spyder project settings 101 | .spyderproject 102 | .spyproject 103 | 104 | # Rope project settings 105 | .ropeproject 106 | 107 | # mkdocs documentation 108 | /site 109 | 110 | # mypy 111 | .mypy_cache/ 112 | .dmypy.json 113 | dmypy.json 114 | 115 | # Pyre type checker 116 | .pyre/ 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deobfuscation: recovering an OLLVM-protected program 2 | 3 | ## Flat_control_flow 4 | 5 | ### Description 6 | 7 | 基于`SnowGirls`的[deflat](https://github.com/SnowGirls/deflat),利用[angr](https://github.com/angr/angr)框架实现去除控制流平坦化,详细内容请参考[利用符号执行去除控制流平坦化](https://security.tencent.com/index.php/blog/msg/112) 。 8 | 9 | > 脚本仅依赖于`angr`框架,测试使用的`angr`版本为`8.19.4.5` 10 | 11 | ### Usage 12 | 13 | > `0x400530` 是函数`check_password()`的地址。 14 | 15 | ```shell 16 | (angr-dev) /deflat/flat_control_flow$ python3 deflat.py -f samples/bin/check_passwd_x8664_flat --addr 0x400530 17 | *******************relevant blocks************************ 18 | prologue: 0x400530 19 | main_dispatcher: 0x400554 20 | pre_dispatcher: 0x40099b 21 | retn: 0x40098f 22 | relevant_blocks: ['0x40086a', '0x40080d', '0x4008ee', '0x40094f', '0x40084e', '0x400819', '0x400886', '0x40095b', '0x4007ec', '0x40092e', '0x4008a9', '0x4008cc', '0x40091b', '0x40097c', '0x400837'] 23 | *******************symbolic execution********************* 24 | -------------------dse 0x40086a--------------------- 25 | -------------------dse 0x40080d--------------------- 26 | -------------------dse 0x4008ee--------------------- 27 | -------------------dse 0x40094f--------------------- 28 | -------------------dse 0x40084e--------------------- 29 | -------------------dse 0x400819--------------------- 30 | -------------------dse 0x400886--------------------- 31 | -------------------dse 0x40095b--------------------- 32 | -------------------dse 0x4007ec--------------------- 33 | -------------------dse 0x40092e--------------------- 34 | -------------------dse 0x4008a9--------------------- 35 | -------------------dse 0x4008cc--------------------- 36 | -------------------dse 0x40091b--------------------- 37 | -------------------dse 0x40097c--------------------- 38 | -------------------dse 0x400837--------------------- 39 | -------------------dse 0x400530--------------------- 40 | ************************flow****************************** 41 | 0x40084e: ['0x40086a', '0x40095b'] 42 | 0x40086a: ['0x400886', '0x40094f'] 43 | 0x400530: ['0x4007ec'] 44 | 0x4008a9: ['0x4008cc', '0x40094f'] 45 | 0x400886: ['0x4008a9', '0x40094f'] 46 | 0x4007ec: ['0x400819', '0x40080d'] 47 | 0x40091b: ['0x40098f'] 48 | 0x40080d: ['0x40084e'] 49 | 0x40092e: ['0x40094f'] 50 | 0x4008ee: ['0x40091b', '0x40092e'] 51 | 0x400819: ['0x400837'] 52 | 0x40094f: ['0x40097c'] 53 | 0x40095b: ['0x40097c'] 54 | 0x40097c: ['0x40098f'] 55 | 0x400837: ['0x4007ec'] 56 | 0x4008cc: ['0x4008ee', '0x40094f'] 57 | 0x40098f: [] 58 | ************************patch***************************** 59 | Successful! The recovered file: check_passwd_flat_recovered 60 | ``` 61 | 62 | ## Bogus_control_flow 63 | 64 | ### Description 65 | 66 | 利用[angr](https://github.com/angr/angr)框架去除虚假的控制流,详细内容请参考[Deobfuscation: recovering an OLLVM-protected program](https://blog.quarkslab.com/deobfuscation-recovering-an-ollvm-protected-program.html) 。 67 | 68 | 原文的主要思路是在进行符号执行时,对约束条件进行"精简",通过将`x * (x + 1) % 2 `替换为`0`,使得`(y < 10 || x * (x + 1) % 2 == 0)`恒成立,从而获取正确的基本块,避免死循环。 69 | 70 | 在使用[angr](https://github.com/angr/angr)框架解决该问题时,也可以按照上述思路进行。另外一种思路是直接将`x`或`y`的值设为`0`,同样可以使得上面的约束恒成立。在默认条件下,`x`和`y`的值会被初始化为0,无需手动进行设置。也就是说,可以直接利用符号执行来解决,而不会遇到死循环的问题。 71 | 72 | 通过符号执行,获取所有执行过的基本块之后,再进行`patch`去除冗余的基本块即可。 73 | 74 | > 对控制流进行精简后,通过`F5`查看伪代码,与源码基本一致。另外,可以在此基础上对控制流进行进一步精简,比如去除冗余的指令等。 75 | 76 | ### Usage 77 | 78 | > `0x080483e0 `是函数`target_function()`的地址。 79 | 80 | ```shell 81 | (angr-dev) /deflat/bogus_control_flow$ python3 debogus.py -f samples/bin/target_x86_bogus --addr 0x80483e0 82 | *******************symbolic execution********************* 83 | executed blocks: ['0x8048686', '0x804868b', '0x8048991', '0x8048592', '0x8048914', '0x8048715', '0x8048897', '0x8048720', '0x8048725', '0x80484ab', '0x804862c', '0x804842e', '0x80484b6', '0x80484bb', '0x80487bb', '0x80487c0', '0x80486c7', '0x8048950', '0x8048551', '0x80488d3', '0x8048955', '0x8048556', '0x8048856', '0x80489d8', '0x80488d8', '0x804885b', '0x80483e0', '0x80485e0', '0x8048761', '0x80485eb', '0x80485f0', '0x80484f7', '0x80487fc'] 84 | ************************patch****************************** 85 | Successful! The recovered file: ./target_bogus_recovered 86 | ``` 87 | 88 | ## Description 89 | 90 | ### Supported Arch 91 | 92 | 目前,脚本仅在以下架构的程序上进行测试: 93 | 94 | + `x86`系列:`x86`, `x86_64` 95 | + `arm`系列:`arm`(`armv7`), `arm64/aarch64`(`armv8`) 96 | 97 | ### Misc 98 | 99 | `am_graph.py`脚本来自于[angr-management/utils/graph.py](https://github.com/angr/angr-management/blob/master/angrmanagement/utils/graph.py),用于将`CFG`转换为`supergraph`,因为`angr`框架中`CFG`与`IDA`中的不太一样。 100 | 101 | > A super transition graph is a graph that looks like IDA Pro's CFG, where calls to returning functions do not terminate basic blocks. 102 | 103 | 通常在安装`angr`时,并不会安装`angr-managerment` (`angr`的GUI),所以这里直接将[angr-management/utils/graph.py](https://github.com/angr/angr-management/blob/master/angrmanagement/utils/graph.py)拷贝到当前目录,并重命名为`am_graph.py`. 104 | 105 | ## Requirements 106 | 107 | - `python3` 108 | - `angr` 109 | 110 | ## Reference 111 | 112 | + [deflat](https://github.com/SnowGirls/deflat) 113 | + [利用符号执行去除控制流平坦化](https://security.tencent.com/index.php/blog/msg/112) 114 | + [Deobfuscation: recovering an OLLVM-protected program](https://blog.quarkslab.com/deobfuscation-recovering-an-ollvm-protected-program.html) 115 | + [obfuscator-llvm wiki](https://github.com/obfuscator-llvm/obfuscator/wiki) 116 | 117 | -------------------------------------------------------------------------------- /am_graph.py: -------------------------------------------------------------------------------- 1 | 2 | import itertools 3 | from collections import defaultdict 4 | 5 | import networkx 6 | 7 | from angr.knowledge_plugins import Function 8 | 9 | 10 | def grouper(iterable, n, fillvalue=None): 11 | "Collect data into fixed-length chunks or blocks" 12 | args = [iter(iterable)] * n 13 | return itertools.izip_longest(*args, fillvalue=fillvalue) 14 | 15 | 16 | def to_supergraph(transition_graph): 17 | """ 18 | Convert transition graph of a function to a super transition graph. A super transition graph is a graph that looks 19 | like IDA Pro's CFG, where calls to returning functions do not terminate basic blocks. 20 | 21 | :param networkx.DiGraph transition_graph: The transition graph. 22 | :return: A converted super transition graph 23 | :rtype networkx.DiGraph 24 | """ 25 | 26 | # make a copy of the graph 27 | transition_graph = networkx.DiGraph(transition_graph) 28 | 29 | # remove all edges that transitions to outside 30 | for src, dst, data in list(transition_graph.edges(data=True)): 31 | if data['type'] in ('transition', 'exception') and data.get('outside', False) is True: 32 | transition_graph.remove_edge(src, dst) 33 | if transition_graph.in_degree(dst) == 0: 34 | transition_graph.remove_node(dst) 35 | 36 | edges_to_shrink = set() 37 | 38 | # Find all edges to remove in the super graph 39 | for src in transition_graph.nodes(): 40 | edges = transition_graph[src] 41 | 42 | # there are two types of edges we want to remove: 43 | # - call or fakerets, since we do not want blocks to break at calls 44 | # - boring jumps that directly transfer the control to the block immediately after the current block. this is 45 | # usually caused by how VEX breaks down basic blocks, which happens very often in MIPS 46 | 47 | 48 | 49 | if len(edges) == 1 and src.addr + src.size == next(iter(edges.keys())).addr: 50 | dst = next(iter(edges.keys())) 51 | dst_in_edges = transition_graph.in_edges(dst) 52 | if len(dst_in_edges) == 1: 53 | edges_to_shrink.add((src, dst)) 54 | continue 55 | 56 | if any(iter('type' in data and data['type'] not in ('fake_return', 'call') for data in edges.values())): 57 | continue 58 | 59 | for dst, data in edges.items(): 60 | if isinstance(dst, Function): 61 | continue 62 | if 'type' in data and data['type'] == 'fake_return': 63 | if all(iter('type' in data and data['type'] in ('fake_return', 'return_from_call') 64 | for _, _, data in transition_graph.in_edges(dst, data=True))): 65 | edges_to_shrink.add((src, dst)) 66 | break 67 | 68 | # Create the super graph 69 | super_graph = networkx.DiGraph() 70 | 71 | supernodes_map = {} 72 | 73 | function_nodes = set() # it will be traversed after all other nodes are added into the supergraph 74 | 75 | for node in transition_graph.nodes(): 76 | 77 | if isinstance(node, Function): 78 | function_nodes.add(node) 79 | # don't put functions into the supergraph 80 | continue 81 | 82 | dests_and_data = transition_graph[node] 83 | 84 | # make a super node 85 | if node in supernodes_map: 86 | src_supernode = supernodes_map[node] 87 | else: 88 | src_supernode = SuperCFGNode.from_cfgnode(node) 89 | supernodes_map[node] = src_supernode 90 | # insert it into the graph 91 | super_graph.add_node(src_supernode) 92 | 93 | if not dests_and_data: 94 | # might be an isolated node 95 | continue 96 | 97 | # Take src_supernode off the graph since we might modify it 98 | if src_supernode in super_graph: 99 | existing_in_edges = list(super_graph.in_edges(src_supernode, data=True)) 100 | existing_out_edges = list(super_graph.out_edges(src_supernode, data=True)) 101 | super_graph.remove_node(src_supernode) 102 | else: 103 | existing_in_edges = [ ] 104 | existing_out_edges = [ ] 105 | 106 | for dst, data in dests_and_data.items(): 107 | 108 | edge = (node, dst) 109 | 110 | if edge in edges_to_shrink: 111 | 112 | if dst in supernodes_map: 113 | dst_supernode = supernodes_map[dst] 114 | else: 115 | dst_supernode = None 116 | 117 | src_supernode.insert_cfgnode(dst) 118 | 119 | # update supernodes map 120 | supernodes_map[dst] = src_supernode 121 | 122 | # merge the other supernode 123 | if dst_supernode is not None: 124 | src_supernode.merge(dst_supernode) 125 | 126 | for src in dst_supernode.cfg_nodes: 127 | supernodes_map[src] = src_supernode 128 | 129 | # link all out edges of dst_supernode to src_supernode 130 | for dst_, data_ in super_graph[dst_supernode].items(): 131 | super_graph.add_edge(src_supernode, dst_, **data_) 132 | 133 | # link all in edges of dst_supernode to src_supernode 134 | for src_, _, data_ in super_graph.in_edges(dst_supernode, data=True): 135 | super_graph.add_edge(src_, src_supernode, **data_) 136 | 137 | if 'type' in data_ and data_['type'] in ('transition', 'exception'): 138 | if not ('ins_addr' in data_ and 'stmt_idx' in data_): 139 | # this is a hack to work around the issue in Function.normalize() where ins_addr and 140 | # stmt_idx weren't properly set onto edges 141 | continue 142 | src_supernode.register_out_branch(data_['ins_addr'], data_['stmt_idx'], data_['type'], 143 | dst_supernode.addr 144 | ) 145 | 146 | super_graph.remove_node(dst_supernode) 147 | 148 | else: 149 | if isinstance(dst, Function): 150 | # skip all functions 151 | continue 152 | 153 | # make a super node 154 | if dst in supernodes_map: 155 | dst_supernode = supernodes_map[dst] 156 | else: 157 | dst_supernode = SuperCFGNode.from_cfgnode(dst) 158 | supernodes_map[dst] = dst_supernode 159 | 160 | super_graph.add_edge(src_supernode, dst_supernode, **data) 161 | 162 | if 'type' in data and data['type'] in ('transition', 'exception'): 163 | if not ('ins_addr' in data and 'stmt_idx' in data): 164 | # this is a hack to work around the issue in Function.normalize() where ins_addr and 165 | # stmt_idx weren't properly set onto edges 166 | continue 167 | src_supernode.register_out_branch(data['ins_addr'], data['stmt_idx'], data['type'], 168 | dst_supernode.addr 169 | ) 170 | 171 | # add back the node (in case there are no edges) 172 | super_graph.add_node(src_supernode) 173 | # add back the old edges 174 | for src, _, data in existing_in_edges: 175 | super_graph.add_edge(src, src_supernode, **data) 176 | for _, dst, data in existing_out_edges: 177 | super_graph.add_edge(src_supernode, dst, **data) 178 | 179 | for node in function_nodes: 180 | in_edges = transition_graph.in_edges(node, data=True) 181 | 182 | for src, _, data in in_edges: 183 | if not ('ins_addr' in data and 'stmt_idx' in data): 184 | # this is a hack to work around the issue in Function.normalize() where ins_addr and 185 | # stmt_idx weren't properly set onto edges 186 | continue 187 | supernode = supernodes_map[src] 188 | supernode.register_out_branch(data['ins_addr'], data['stmt_idx'], data['type'], node.addr) 189 | 190 | return super_graph 191 | 192 | 193 | class OutBranch: 194 | def __init__(self, ins_addr, stmt_idx, branch_type): 195 | self.ins_addr = ins_addr 196 | self.stmt_idx = stmt_idx 197 | self.type = branch_type 198 | 199 | self.targets = set() 200 | 201 | def __repr__(self): 202 | if self.ins_addr is None: 203 | return "" % self.type 204 | return "" % (self.ins_addr, self.type) 205 | 206 | def add_target(self, addr): 207 | self.targets.add(addr) 208 | 209 | def merge(self, other): 210 | """ 211 | Merge with the other OutBranch descriptor. 212 | 213 | :param OutBranch other: The other item to merge with. 214 | :return: None 215 | """ 216 | 217 | assert self.ins_addr == other.ins_addr 218 | assert self.type == other.type 219 | 220 | o = self.copy() 221 | o.targets |= other.targets 222 | 223 | return o 224 | 225 | def copy(self): 226 | o = OutBranch(self.ins_addr, self.stmt_idx, self.type) 227 | o.targets = self.targets.copy() 228 | return o 229 | 230 | def __eq__(self, other): 231 | if not isinstance(other, OutBranch): 232 | return False 233 | 234 | return self.ins_addr == other.ins_addr and \ 235 | self.stmt_idx == other.stmt_idx and \ 236 | self.type == other.type and \ 237 | self.targets == other.targets 238 | 239 | def __hash__(self): 240 | return hash((self.ins_addr, self.stmt_idx, self.type)) 241 | 242 | 243 | class SuperCFGNode: 244 | def __init__(self, addr): 245 | self.addr = addr 246 | 247 | self.cfg_nodes = [ ] 248 | 249 | self.out_branches = defaultdict(dict) 250 | 251 | @property 252 | def size(self): 253 | return sum(node.size for node in self.cfg_nodes) 254 | 255 | @classmethod 256 | def from_cfgnode(cls, cfg_node): 257 | s = cls(cfg_node.addr) 258 | 259 | s.cfg_nodes.append(cfg_node) 260 | 261 | return s 262 | 263 | def insert_cfgnode(self, cfg_node): 264 | # TODO: Make it binary search/insertion 265 | for i, n in enumerate(self.cfg_nodes): 266 | if cfg_node.addr < n.addr: 267 | # insert before n 268 | self.cfg_nodes.insert(i, cfg_node) 269 | break 270 | elif cfg_node.addr == n.addr: 271 | break 272 | else: 273 | self.cfg_nodes.append(cfg_node) 274 | 275 | # update addr 276 | self.addr = self.cfg_nodes[0].addr 277 | 278 | def register_out_branch(self, ins_addr, stmt_idx, branch_type, target_addr): 279 | if ins_addr not in self.out_branches or stmt_idx not in self.out_branches[ins_addr]: 280 | self.out_branches[ins_addr][stmt_idx] = OutBranch(ins_addr, stmt_idx, branch_type) 281 | 282 | self.out_branches[ins_addr][stmt_idx].add_target(target_addr) 283 | 284 | def merge(self, other): 285 | """ 286 | Merge another supernode into the current one. 287 | 288 | :param SuperCFGNode other: The supernode to merge with. 289 | :return: None 290 | """ 291 | 292 | for n in other.cfg_nodes: 293 | self.insert_cfgnode(n) 294 | 295 | for ins_addr, outs in other.out_branches.items(): 296 | if ins_addr in self.out_branches: 297 | for stmt_idx, item in outs.items(): 298 | if stmt_idx in self.out_branches[ins_addr]: 299 | self.out_branches[ins_addr][stmt_idx].merge(item) 300 | else: 301 | self.out_branches[ins_addr][stmt_idx] = item 302 | 303 | else: 304 | item = next(iter(outs.values())) 305 | self.out_branches[ins_addr][item.stmt_idx] = item 306 | 307 | def __repr__(self): 308 | return "" % (self.addr, len(self.cfg_nodes), 309 | len(self.out_branches) 310 | ) 311 | 312 | def __hash__(self): 313 | return hash(('supercfgnode', self.addr)) 314 | 315 | def __eq__(self, other): 316 | if not isinstance(other, SuperCFGNode): 317 | return False 318 | 319 | return self.addr == other.addr 320 | -------------------------------------------------------------------------------- /bogus_control_flow/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cq674350529/deflat/34a43cbfc9ae5806f2cd6371e67551e51d681d3f/bogus_control_flow/__init__.py -------------------------------------------------------------------------------- /bogus_control_flow/debogus.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | sys.path.append("..") 5 | 6 | import argparse 7 | import struct 8 | import angr 9 | 10 | import am_graph 11 | from util import * 12 | 13 | import logging 14 | logging.getLogger('angr.state_plugins.symbolic_memory').setLevel(logging.ERROR) 15 | # logging.getLogger('angr.sim_manager').setLevel(logging.DEBUG) 16 | 17 | 18 | def main(): 19 | parser = argparse.ArgumentParser(description="debogus control flow script") 20 | parser.add_argument("-f", "--file", help="binary to analyze") 21 | parser.add_argument( 22 | "--addr", help="address of target function in hex format") 23 | args = parser.parse_args() 24 | 25 | if args.file is None or args.addr is None: 26 | parser.print_help() 27 | sys.exit(0) 28 | 29 | filename = args.file 30 | start = int(args.addr, 16) 31 | 32 | project = angr.Project(filename, load_options={'auto_load_libs': False}) 33 | cfg = project.analyses.CFGFast(normalize=True, force_complete_scan=False) 34 | target_function = cfg.functions.get(start) 35 | supergraph = am_graph.to_supergraph(target_function.transition_graph) 36 | 37 | base_addr = project.loader.main_object.mapped_base >> 12 << 12 38 | 39 | state = project.factory.blank_state(addr=target_function.addr, remove_options={ 40 | angr.sim_options.LAZY_SOLVES}) 41 | 42 | flow = set() 43 | flow.add(target_function.addr) 44 | 45 | print('*******************symbolic execution*********************') 46 | sm = project.factory.simulation_manager(state) 47 | sm.step() 48 | while len(sm.active) > 0: 49 | for active in sm.active: 50 | flow.add(active.addr) 51 | sm.step() 52 | 53 | print('executed blocks: ', list(map(hex, flow))) 54 | 55 | print('************************patch******************************') 56 | 57 | with open(filename, 'rb') as origin: 58 | origin_data = bytearray(origin.read()) 59 | origin_data_len = len(origin_data) 60 | 61 | patch_nodes = set() 62 | for node in supergraph.nodes(): 63 | if node.addr in patch_nodes: 64 | continue 65 | 66 | if node.addr not in flow: 67 | # patch unnecessary node 68 | file_offset = project.loader.main_object.addr_to_offset(node.addr) 69 | fill_nop(origin_data, file_offset, node.size, project.arch) 70 | else: 71 | suc_nodes = list(supergraph.successors(node)) 72 | jmp_targets = [] 73 | 74 | for suc_node in suc_nodes: 75 | if suc_node.addr in flow: 76 | jmp_targets.append(suc_node.addr) 77 | else: 78 | # patch unnecessary suc_node 79 | file_offset = project.loader.main_object.addr_to_offset(suc_node.addr) 80 | fill_nop(origin_data, file_offset, 81 | suc_node.size, project.arch) 82 | patch_nodes.add(suc_node.addr) 83 | 84 | # patch jmp instruction 85 | if len(suc_nodes) > 1 and len(jmp_targets) == 1: 86 | if project.arch.name in ARCH_X86: 87 | file_offset = project.loader.main_object.addr_to_offset(node.addr + node.size) - 6 88 | # nop + jmp 89 | patch_value = OPCODES['x86']['nop'] + ins_j_jmp_hex_x86(node.addr + node.size - 5, jmp_targets[0], 'jmp') 90 | patch_instruction(origin_data, file_offset, patch_value) 91 | elif project.arch.name in ARCH_ARM: 92 | file_offset = project.loader.main_object.addr_to_offset(node.addr + node.size) - 4 93 | patch_value = ins_b_jmp_hex_arm(node.addr + node.size - 4, jmp_targets[0], 'b') 94 | if project.arch.memory_endness == 'Iend_BE': 95 | patch_value = patch_value[::-1] 96 | patch_instruction(origin_data, file_offset, patch_value) 97 | elif project.arch.name in ARCH_ARM64: 98 | file_offset = project.loader.main_object.addr_to_offset(node.addr + node.size) - 4 99 | patch_value = ins_b_jmp_hex_arm64(node.addr + node.size - 4, jmp_targets[0], 'b') 100 | if project.arch.memory_endness == 'Iend_BE': 101 | patch_value = patch_value[::-1] 102 | patch_instruction(origin_data, file_offset, patch_value) 103 | 104 | assert len(origin_data) == origin_data_len, "Error: size of data changed!!!" 105 | 106 | recovery_file = filename + '_recovered' 107 | with open(recovery_file, 'wb') as recovery: 108 | recovery.write(origin_data) 109 | 110 | print('Successful! The recovered file: %s' % recovery_file) 111 | 112 | 113 | if __name__ == "__main__": 114 | main() 115 | -------------------------------------------------------------------------------- /bogus_control_flow/samples/bin/target_arm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cq674350529/deflat/34a43cbfc9ae5806f2cd6371e67551e51d681d3f/bogus_control_flow/samples/bin/target_arm -------------------------------------------------------------------------------- /bogus_control_flow/samples/bin/target_arm_bogus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cq674350529/deflat/34a43cbfc9ae5806f2cd6371e67551e51d681d3f/bogus_control_flow/samples/bin/target_arm_bogus -------------------------------------------------------------------------------- /bogus_control_flow/samples/bin/target_arm_bogus_recovered: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cq674350529/deflat/34a43cbfc9ae5806f2cd6371e67551e51d681d3f/bogus_control_flow/samples/bin/target_arm_bogus_recovered -------------------------------------------------------------------------------- /bogus_control_flow/samples/bin/target_x86: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cq674350529/deflat/34a43cbfc9ae5806f2cd6371e67551e51d681d3f/bogus_control_flow/samples/bin/target_x86 -------------------------------------------------------------------------------- /bogus_control_flow/samples/bin/target_x86_bogus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cq674350529/deflat/34a43cbfc9ae5806f2cd6371e67551e51d681d3f/bogus_control_flow/samples/bin/target_x86_bogus -------------------------------------------------------------------------------- /bogus_control_flow/samples/bin/target_x86_bogus_recovered: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cq674350529/deflat/34a43cbfc9ae5806f2cd6371e67551e51d681d3f/bogus_control_flow/samples/bin/target_x86_bogus_recovered -------------------------------------------------------------------------------- /bogus_control_flow/samples/src/target.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | unsigned int target_function(unsigned int n) 4 | { 5 | unsigned int mod = n % 4; 6 | unsigned int result = 0 ; 7 | 8 | if (mod == 0){ 9 | result = (n | 0xBAAAD0BF) * (2 ^ n); 10 | } else if (mod == 1){ 11 | result = (n & 0xBAAAD0BF) * (3 + n); 12 | } else if (mod == 2){ 13 | result = (n ^ 0xBAAAD0BF) * (4 | n); 14 | } else { 15 | result = (n + 0xBAAAD0BF) * (5 & n); 16 | } 17 | 18 | return result; 19 | } 20 | 21 | void main() 22 | { 23 | unsigned int value = 0x12345; 24 | unsigned int result = target_function(value); 25 | printf("result: 0x%x\n", result); 26 | } -------------------------------------------------------------------------------- /bogus_control_flow/test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('..') 3 | 4 | import os 5 | import nose 6 | import util 7 | 8 | test_location = os.path.dirname(os.path.realpath(__file__)) 9 | 10 | def run_debogus(binary, addr): 11 | os.system("python3 ./debogus.py -f %s --addr %#x" % (binary, addr)) 12 | 13 | 14 | def test_bogus_control_flow(): 15 | binary_path = os.path.join(test_location, 'samples', 'bin') 16 | 17 | bogus_binary_x86 = os.path.join(binary_path, 'target_x86_bogus') 18 | run_debogus(bogus_binary_x86, 0x080483E0) 19 | bogus_binary_x86_recovered = os.path.join(binary_path, 'target_x86_bogus_recovered') 20 | nose.tools.assert_equal(util.calc_md5(bogus_binary_x86_recovered), "ba56f7e5278a0c0ac32b7f4d2eb378f3") 21 | 22 | bogus_binary_arm = os.path.join(binary_path, 'target_arm_bogus') 23 | run_debogus(bogus_binary_arm, 0x83B4) 24 | bogus_binary_arm_recovered = os.path.join(binary_path, 'target_arm_bogus_recovered') 25 | nose.tools.assert_equal(util.calc_md5(bogus_binary_arm_recovered), "87343c8c6e1e3fec76f9c513d51c0144") 26 | 27 | 28 | if __name__ == "__main__": 29 | test_bogus_control_flow() 30 | -------------------------------------------------------------------------------- /flat_control_flow/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cq674350529/deflat/34a43cbfc9ae5806f2cd6371e67551e51d681d3f/flat_control_flow/__init__.py -------------------------------------------------------------------------------- /flat_control_flow/deflat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | sys.path.append("..") 5 | 6 | import argparse 7 | import angr 8 | import pyvex 9 | import claripy 10 | import struct 11 | from collections import defaultdict 12 | 13 | import am_graph 14 | from util import * 15 | 16 | import logging 17 | logging.getLogger('angr.state_plugins.symbolic_memory').setLevel(logging.ERROR) 18 | # logging.getLogger('angr.sim_manager').setLevel(logging.DEBUG) 19 | 20 | 21 | def get_relevant_nop_nodes(supergraph, pre_dispatcher_node, prologue_node, retn_node): 22 | # relevant_nodes = list(supergraph.predecessors(pre_dispatcher_node)) 23 | relevant_nodes = [] 24 | nop_nodes = [] 25 | for node in supergraph.nodes(): 26 | if supergraph.has_edge(node, pre_dispatcher_node) and node.size > 8: 27 | # XXX: use node.size is faster than to create a block 28 | relevant_nodes.append(node) 29 | continue 30 | if node.addr in (prologue_node.addr, retn_node.addr, pre_dispatcher_node.addr): 31 | continue 32 | nop_nodes.append(node) 33 | return relevant_nodes, nop_nodes 34 | 35 | 36 | def symbolic_execution(project, relevant_block_addrs, start_addr, hook_addrs=None, modify_value=None, inspect=False): 37 | 38 | def retn_procedure(state): 39 | ip = state.solver.eval(state.regs.ip) 40 | project.unhook(ip) 41 | return 42 | 43 | def statement_inspect(state): 44 | expressions = list( 45 | state.scratch.irsb.statements[state.inspect.statement].expressions) 46 | if len(expressions) != 0 and isinstance(expressions[0], pyvex.expr.ITE): 47 | state.scratch.temps[expressions[0].cond.tmp] = modify_value 48 | state.inspect._breakpoints['statement'] = [] 49 | 50 | if hook_addrs is not None: 51 | skip_length = 4 52 | if project.arch.name in ARCH_X86: 53 | skip_length = 5 54 | 55 | for hook_addr in hook_addrs: 56 | project.hook(hook_addr, retn_procedure, length=skip_length) 57 | 58 | state = project.factory.blank_state(addr=start_addr, remove_options={ 59 | angr.sim_options.LAZY_SOLVES}) 60 | if inspect: 61 | state.inspect.b( 62 | 'statement', when=angr.state_plugins.inspect.BP_BEFORE, action=statement_inspect) 63 | sm = project.factory.simulation_manager(state) 64 | sm.step() 65 | while len(sm.active) > 0: 66 | for active_state in sm.active: 67 | if active_state.addr in relevant_block_addrs: 68 | return active_state.addr 69 | sm.step() 70 | 71 | return None 72 | 73 | 74 | def main(): 75 | parser = argparse.ArgumentParser(description="deflat control flow script") 76 | parser.add_argument("-f", "--file", help="binary to analyze") 77 | parser.add_argument( 78 | "--addr", help="address of target function in hex format") 79 | args = parser.parse_args() 80 | 81 | if args.file is None or args.addr is None: 82 | parser.print_help() 83 | sys.exit(0) 84 | 85 | filename = args.file 86 | start = int(args.addr, 16) 87 | 88 | project = angr.Project(filename, load_options={'auto_load_libs': False}) 89 | # do normalize to avoid overlapping blocks, disable force_complete_scan to avoid possible "wrong" blocks 90 | cfg = project.analyses.CFGFast(normalize=True, force_complete_scan=False) 91 | base_addr = project.loader.main_object.mapped_base >> 12 << 12 92 | target_function = cfg.functions.get(start) 93 | if target_function is None: 94 | target_function = cfg.kb.functions.get_by_addr(base_addr + start) 95 | 96 | # A super transition graph is a graph that looks like IDA Pro's CFG 97 | supergraph = am_graph.to_supergraph(target_function.transition_graph) 98 | 99 | # get prologue_node and retn_node 100 | prologue_node = None 101 | for node in supergraph.nodes(): 102 | if supergraph.in_degree(node) == 0: 103 | prologue_node = node 104 | if supergraph.out_degree(node) == 0 and len(node.out_branches) == 0: 105 | retn_node = node 106 | 107 | if prologue_node is None or prologue_node.addr not in [start, base_addr + start]: 108 | print("Something must be wrong...") 109 | sys.exit(-1) 110 | 111 | main_dispatcher_node = list(supergraph.successors(prologue_node))[0] 112 | for node in supergraph.predecessors(main_dispatcher_node): 113 | if node.addr != prologue_node.addr: 114 | pre_dispatcher_node = node 115 | break 116 | 117 | relevant_nodes, nop_nodes = get_relevant_nop_nodes( 118 | supergraph, pre_dispatcher_node, prologue_node, retn_node) 119 | print('*******************relevant blocks************************') 120 | print('prologue: %#x' % prologue_node.addr) 121 | print('main_dispatcher: %#x' % main_dispatcher_node.addr) 122 | print('pre_dispatcher: %#x' % pre_dispatcher_node.addr) 123 | print('retn: %#x' % retn_node.addr) 124 | relevant_block_addrs = [node.addr for node in relevant_nodes] 125 | print('relevant_blocks:', [hex(addr) for addr in relevant_block_addrs]) 126 | 127 | print('*******************symbolic execution*********************') 128 | relevants = relevant_nodes 129 | relevants.append(prologue_node) 130 | relevants_without_retn = list(relevants) 131 | relevants.append(retn_node) 132 | relevant_block_addrs.extend([prologue_node.addr, retn_node.addr]) 133 | 134 | flow = defaultdict(list) 135 | patch_instrs = {} 136 | for relevant in relevants_without_retn: 137 | print('-------------------dse %#x---------------------' % relevant.addr) 138 | block = project.factory.block(relevant.addr, size=relevant.size) 139 | has_branches = False 140 | hook_addrs = set([]) 141 | for ins in block.capstone.insns: 142 | if project.arch.name in ARCH_X86: 143 | if ins.insn.mnemonic.startswith('cmov'): 144 | # only record the first one 145 | if relevant not in patch_instrs: 146 | patch_instrs[relevant] = ins 147 | has_branches = True 148 | elif ins.insn.mnemonic.startswith('call'): 149 | hook_addrs.add(ins.insn.address) 150 | elif project.arch.name in ARCH_ARM: 151 | if ins.insn.mnemonic != 'mov' and ins.insn.mnemonic.startswith('mov'): 152 | if relevant not in patch_instrs: 153 | patch_instrs[relevant] = ins 154 | has_branches = True 155 | elif ins.insn.mnemonic in {'bl', 'blx'}: 156 | hook_addrs.add(ins.insn.address) 157 | elif project.arch.name in ARCH_ARM64: 158 | if ins.insn.mnemonic.startswith('cset'): 159 | if relevant not in patch_instrs: 160 | patch_instrs[relevant] = ins 161 | has_branches = True 162 | elif ins.insn.mnemonic in {'bl', 'blr'}: 163 | hook_addrs.add(ins.insn.address) 164 | 165 | if has_branches: 166 | tmp_addr = symbolic_execution(project, relevant_block_addrs, 167 | relevant.addr, hook_addrs, claripy.BVV(1, 1), True) 168 | if tmp_addr is not None: 169 | flow[relevant].append(tmp_addr) 170 | tmp_addr = symbolic_execution(project, relevant_block_addrs, 171 | relevant.addr, hook_addrs, claripy.BVV(0, 1), True) 172 | if tmp_addr is not None: 173 | flow[relevant].append(tmp_addr) 174 | else: 175 | tmp_addr = symbolic_execution(project, relevant_block_addrs, 176 | relevant.addr, hook_addrs) 177 | if tmp_addr is not None: 178 | flow[relevant].append(tmp_addr) 179 | 180 | print('************************flow******************************') 181 | for k, v in flow.items(): 182 | print('%#x: ' % k.addr, [hex(child) for child in v]) 183 | 184 | print('%#x: ' % retn_node.addr, []) 185 | 186 | print('************************patch*****************************') 187 | with open(filename, 'rb') as origin: 188 | # Attention: can't transform to str by calling decode() directly. so use bytearray instead. 189 | origin_data = bytearray(origin.read()) 190 | origin_data_len = len(origin_data) 191 | 192 | recovery_file = filename + '_recovered' 193 | recovery = open(recovery_file, 'wb') 194 | 195 | # patch irrelevant blocks 196 | for nop_node in nop_nodes: 197 | fill_nop(origin_data, project.loader.main_object.addr_to_offset(nop_node.addr), 198 | nop_node.size, project.arch) 199 | 200 | # remove unnecessary control flows 201 | for parent, childs in flow.items(): 202 | if len(childs) == 1: 203 | parent_block = project.factory.block(parent.addr, size=parent.size) 204 | last_instr = parent_block.capstone.insns[-1] 205 | file_offset = project.loader.main_object.addr_to_offset(last_instr.address) 206 | # patch the last instruction to jmp 207 | if project.arch.name in ARCH_X86: 208 | fill_nop(origin_data, file_offset, 209 | last_instr.size, project.arch) 210 | patch_value = ins_j_jmp_hex_x86(last_instr.address, childs[0], 'jmp') 211 | elif project.arch.name in ARCH_ARM: 212 | patch_value = ins_b_jmp_hex_arm(last_instr.address, childs[0], 'b') 213 | if project.arch.memory_endness == "Iend_BE": 214 | patch_value = patch_value[::-1] 215 | elif project.arch.name in ARCH_ARM64: 216 | # FIXME: For aarch64/arm64, the last instruction of prologue seems useful in some cases, so patch the next instruction instead. 217 | if parent.addr in [start, base_addr + start]: 218 | file_offset += 4 219 | patch_value = ins_b_jmp_hex_arm64(last_instr.address+4, childs[0], 'b') 220 | else: 221 | patch_value = ins_b_jmp_hex_arm64(last_instr.address, childs[0], 'b') 222 | if project.arch.memory_endness == "Iend_BE": 223 | patch_value = patch_value[::-1] 224 | patch_instruction(origin_data, file_offset, patch_value) 225 | else: 226 | instr = patch_instrs[parent] 227 | file_offset = project.loader.main_object.addr_to_offset(instr.address) 228 | # patch instructions starting from `cmovx` to the end of block 229 | block_end_offset = project.loader.main_object.addr_to_offset(parent.addr + parent.size) 230 | fill_nop(origin_data, file_offset, block_end_offset - file_offset, project.arch) 231 | if project.arch.name in ARCH_X86: 232 | # patch the cmovx instruction to jx instruction 233 | patch_value = ins_j_jmp_hex_x86(instr.address, childs[0], instr.mnemonic[len('cmov'):]) 234 | patch_instruction(origin_data, file_offset, patch_value) 235 | 236 | file_offset += 6 237 | # patch the next instruction to jmp instrcution 238 | patch_value = ins_j_jmp_hex_x86(instr.address+6, childs[1], 'jmp') 239 | patch_instruction(origin_data, file_offset, patch_value) 240 | elif project.arch.name in ARCH_ARM: 241 | # patch the movx instruction to bx instruction 242 | bx_cond = 'b' + instr.mnemonic[len('mov'):] 243 | patch_value = ins_b_jmp_hex_arm(instr.address, childs[0], bx_cond) 244 | if project.arch.memory_endness == 'Iend_BE': 245 | patch_value = patch_value[::-1] 246 | patch_instruction(origin_data, file_offset, patch_value) 247 | 248 | file_offset += 4 249 | # patch the next instruction to b instrcution 250 | patch_value = ins_b_jmp_hex_arm(instr.address+4, childs[1], 'b') 251 | if project.arch.memory_endness == 'Iend_BE': 252 | patch_value = patch_value[::-1] 253 | patch_instruction(origin_data, file_offset, patch_value) 254 | elif project.arch.name in ARCH_ARM64: 255 | # patch the cset.xx instruction to bx instruction 256 | bx_cond = instr.op_str.split(',')[-1].strip() 257 | patch_value = ins_b_jmp_hex_arm64(instr.address, childs[0], bx_cond) 258 | if project.arch.memory_endness == 'Iend_BE': 259 | patch_value = patch_value[::-1] 260 | patch_instruction(origin_data, file_offset, patch_value) 261 | 262 | file_offset += 4 263 | # patch the next instruction to b instruction 264 | patch_value = ins_b_jmp_hex_arm64(instr.address+4, childs[1], 'b') 265 | if project.arch.memory_endness == 'Iend_BE': 266 | patch_value = patch_value[::-1] 267 | patch_instruction(origin_data, file_offset, patch_value) 268 | 269 | assert len(origin_data) == origin_data_len, "Error: size of data changed!!!" 270 | recovery.write(origin_data) 271 | recovery.close() 272 | print('Successful! The recovered file: %s' % recovery_file) 273 | 274 | 275 | if __name__ == '__main__': 276 | main() 277 | -------------------------------------------------------------------------------- /flat_control_flow/samples/bin/check_passwd_arm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cq674350529/deflat/34a43cbfc9ae5806f2cd6371e67551e51d681d3f/flat_control_flow/samples/bin/check_passwd_arm -------------------------------------------------------------------------------- /flat_control_flow/samples/bin/check_passwd_arm64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cq674350529/deflat/34a43cbfc9ae5806f2cd6371e67551e51d681d3f/flat_control_flow/samples/bin/check_passwd_arm64 -------------------------------------------------------------------------------- /flat_control_flow/samples/bin/check_passwd_arm64_flat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cq674350529/deflat/34a43cbfc9ae5806f2cd6371e67551e51d681d3f/flat_control_flow/samples/bin/check_passwd_arm64_flat -------------------------------------------------------------------------------- /flat_control_flow/samples/bin/check_passwd_arm64_flat_recovered: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cq674350529/deflat/34a43cbfc9ae5806f2cd6371e67551e51d681d3f/flat_control_flow/samples/bin/check_passwd_arm64_flat_recovered -------------------------------------------------------------------------------- /flat_control_flow/samples/bin/check_passwd_arm_flat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cq674350529/deflat/34a43cbfc9ae5806f2cd6371e67551e51d681d3f/flat_control_flow/samples/bin/check_passwd_arm_flat -------------------------------------------------------------------------------- /flat_control_flow/samples/bin/check_passwd_arm_flat_recovered: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cq674350529/deflat/34a43cbfc9ae5806f2cd6371e67551e51d681d3f/flat_control_flow/samples/bin/check_passwd_arm_flat_recovered -------------------------------------------------------------------------------- /flat_control_flow/samples/bin/check_passwd_x8664: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cq674350529/deflat/34a43cbfc9ae5806f2cd6371e67551e51d681d3f/flat_control_flow/samples/bin/check_passwd_x8664 -------------------------------------------------------------------------------- /flat_control_flow/samples/bin/check_passwd_x8664_flat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cq674350529/deflat/34a43cbfc9ae5806f2cd6371e67551e51d681d3f/flat_control_flow/samples/bin/check_passwd_x8664_flat -------------------------------------------------------------------------------- /flat_control_flow/samples/bin/check_passwd_x8664_flat_recovered: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cq674350529/deflat/34a43cbfc9ae5806f2cd6371e67551e51d681d3f/flat_control_flow/samples/bin/check_passwd_x8664_flat_recovered -------------------------------------------------------------------------------- /flat_control_flow/samples/src/check_passwd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int check_password(char *passwd) 6 | { 7 | int i, sum = 0; 8 | for (i = 0; ; i++) 9 | { 10 | if (!passwd[i]) 11 | { 12 | break; 13 | } 14 | sum += passwd[i]; 15 | } 16 | if (i == 4) 17 | { 18 | if (sum == 0x1a1 && passwd[3] > 'c' && passwd[3] < 'e' && passwd[0] == 'b') 19 | { 20 | if ((passwd[3] ^ 0xd) == passwd[1]) 21 | { 22 | return 1; 23 | } 24 | puts("Orz..."); 25 | } 26 | } 27 | else 28 | { 29 | puts("len error"); 30 | } 31 | return 0; 32 | } 33 | 34 | int main(int argc, char **argv) 35 | { 36 | if (argc != 2) 37 | { 38 | puts("error"); 39 | return 1; 40 | } 41 | if (check_password(argv[1])) 42 | { 43 | puts("Congratulation!"); 44 | } 45 | else 46 | { 47 | puts("error"); 48 | } 49 | return 0; 50 | } -------------------------------------------------------------------------------- /flat_control_flow/test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('..') 3 | 4 | import os 5 | import nose 6 | 7 | import util 8 | 9 | test_location = os.path.dirname(os.path.realpath(__file__)) 10 | 11 | 12 | def run_deflat(binary, addr): 13 | os.system("python3 ./deflat.py -f %s --addr %#x" % (binary, addr)) 14 | 15 | 16 | def test_flat_control_flow(): 17 | binary_path = os.path.join(test_location, 'samples', 'bin') 18 | 19 | flat_binary_x8664 = os.path.join(binary_path, 'check_passwd_x8664_flat') 20 | run_deflat(flat_binary_x8664, 0x400530) 21 | flat_binary_x8664_recovered = os.path.join(binary_path, 'check_passwd_x8664_flat_recovered') 22 | nose.tools.assert_equal(util.calc_md5(flat_binary_x8664_recovered), "e9a86d51e981a94d8756ecd94ffdc84a") 23 | 24 | flat_binary_arm = os.path.join(binary_path, 'check_passwd_arm_flat') 25 | run_deflat(flat_binary_arm, 0x83B0) 26 | flat_binary_arm_recovered = os.path.join(binary_path, 'check_passwd_arm_flat_recovered') 27 | nose.tools.assert_equal(util.calc_md5(flat_binary_arm_recovered), "5de3f39b502a87eab066a977b72da726") 28 | 29 | flat_binary_arm64 = os.path.join(binary_path, 'check_passwd_arm64_flat') 30 | run_deflat(flat_binary_arm64, 0x10000524C) 31 | flat_binary_arm64_recovered = os.path.join(binary_path, 'check_passwd_arm64_flat_recovered') 32 | nose.tools.assert_equal(util.calc_md5(flat_binary_arm64_recovered), "fb684b2f31a538c3bf103747f3b9c781") 33 | 34 | 35 | if __name__ == "__main__": 36 | test_flat_control_flow() 37 | -------------------------------------------------------------------------------- /util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import struct 4 | import hashlib 5 | 6 | ARCH_X86 = {"X86", "AMD64"} 7 | ARCH_ARM = {"ARMEL", "ARMHF"} 8 | ARCH_ARM64 = {'AARCH64'} 9 | 10 | OPCODES = { 11 | 'x86': 12 | { 13 | 'a': b'\x87', 'ae': b'\x83', 'b': b'\x82', 'be': b'\x86', 'c': b'\x82', 'e': b'\x84', 'z': b'\x84', 'g': b'\x8F', 'ge': b'\x8D', 'l': b'\x8C', 'le': b'\x8E', 'na': b'\x86', 'nae': b'\x82', 'nb': b'\x83', 'nbe': b'\x87', 'nc': b'\x83', 'ne': b'\x85', 'ng': b'\x8E', 'nge': b'\x8C', 'nl': b'\x8D', 'nle': b'\x8F', 'no': 'b\x81', 'np': b'\x8B', 'ns': b'\x89', 'nz': b'\x85', 'o': b'\x80', 'p': b'\x8A', 'pe': b'\x8A', 'po': b'\x8B', 's': b'\x88', 'nop': b'\x90', 'jmp': b'\xE9', 'j': b'\x0F' 14 | }, 15 | 'arm': 16 | { 17 | 'nop': b'\x00\xF0\x20\xE3', 'b': b'\xEA', 'blt': b'\xBA', 'beq': b'\x0A', 'bne': b'\x1A', 'bgt': b'\xCA', 'bhi': b'\x8A', 'bls': b'\x9A', 'ble': b'\xDA', 'bge': b'\xAA' 18 | }, 19 | 'arm64': 20 | { 21 | 'nop': b'\x1F\x20\x03\xD5', 'b': b'\x14', 'b_cond':{'eq': 0x0, 'ne': 0x1, 'hs': 0x2, 'lo': 0x3, 'mi': 0x4, 'pl': 0x5, 'vs': 0x6, 'vc': 0x7, 'hi': 0x8, 'ls': 0x9, 'ge': 0xA, 'lt': 0xB, 'gt':0xC, 'le':0xD} 22 | } 23 | } 24 | 25 | 26 | def fill_nop(data, start_addr, length, arch): 27 | if arch.name in ARCH_X86: 28 | for i in range(0, length): 29 | data[start_addr + i] = ord(OPCODES['x86']['nop']) 30 | elif arch.name in ARCH_ARM | ARCH_ARM64: 31 | if arch.name in ARCH_ARM: 32 | nop_value = OPCODES['arm']['nop'] 33 | else: 34 | nop_value = OPCODES['arm64']['nop'] 35 | 36 | if arch.memory_endness == "Iend_BE": 37 | nop_value = nop_value[::-1] 38 | for i in range(0, length, 4): 39 | data[start_addr+i] = nop_value[0] 40 | data[start_addr+i+1] = nop_value[1] 41 | data[start_addr+i+2] = nop_value[2] 42 | data[start_addr+i+3] = nop_value[3] 43 | 44 | 45 | def patch_instruction(data, offset, value): 46 | for i in range(len(value)): 47 | data[offset+i] = value[i] 48 | 49 | 50 | """ 51 | get the hex of j/b_jmp ins 52 | """ 53 | def ins_j_jmp_hex_x86(cur_addr, target_addr, j_cond): 54 | if j_cond == 'jmp': 55 | j_opcode = OPCODES['x86']['jmp'] 56 | j_ins_size = 5 57 | else: 58 | j_opcode = OPCODES['x86']['j'] + OPCODES['x86'][j_cond] 59 | j_ins_size = 6 60 | 61 | jmp_offset = target_addr - cur_addr - j_ins_size 62 | patch_ins_hex = j_opcode + struct.pack(' target_addr: 76 | patch_ins_hex = struct.pack('