From 40711f33544149c6ece1aa65524a87178a5b14bc Mon Sep 17 00:00:00 2001 From: Razvan Deaconescu Date: Sat, 6 Jun 2020 17:35:16 +0300 Subject: [PATCH 1/4] Add 3-clause BSD license --- LICENSE | 30 ++++++++++++++++++++++++++++++ README.md | 2 ++ 2 files changed, 32 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c944389 --- /dev/null +++ b/LICENSE @@ -0,0 +1,30 @@ +BSD 3-Clause License + +Copyright (c) 2016, North Carolina State University and University POLITEHNICA +of Bucharest. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index ccede70..640ad9a 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ The reverser (in the `reverse-sandbox/` folder) runs on any Python running platf SandBlaster may be installed and run standalone, though we recommend installing and running it from within [iExtractor](https://github.com/malus-security/iExtractor). Check the [iExtractor documentation](https://github.com/malus-security/iExtractor/blob/master/README.md) for information. +iExtractor is open source software released under the 3-clause BSD license. + ## Installation SandBlaster requires Python for the reverser (in `reverse-sandbox/`), Bash for helper scripts (in `helpers/`) and tools from the [sandbox_toolkit](https://github.com/sektioneins/sandbox_toolkit) (in `tools/`). From 2318f0c97b6005da1ddbdd0825941817c11a580f Mon Sep 17 00:00:00 2001 From: Andrei-Calin Georgescu Date: Sun, 9 Aug 2020 10:39:50 +0300 Subject: [PATCH 2/4] Add quickfixes to update reverse-sandbox scripts for python3 Fixes errors that appear when trying to run scripts using python3. This also updates shebang to use Python3. --- .gitignore | 1 + reverse-sandbox/filters.py | 2 +- reverse-sandbox/operation_node.py | 16 ++++++++-------- reverse-sandbox/reverse_sandbox.py | 12 ++++++------ reverse-sandbox/reverse_string.py | 2 +- reverse-sandbox/sandbox_regex.py | 4 ++-- 6 files changed, 19 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index efa5a6f..c952d3c 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ TAGS *.log *.bin core +.DS_STORE diff --git a/reverse-sandbox/filters.py b/reverse-sandbox/filters.py index 46cc12a..89ca81a 100644 --- a/reverse-sandbox/filters.py +++ b/reverse-sandbox/filters.py @@ -6,7 +6,7 @@ def read_filters(): with open('filters.json') as data: temp = json.load(data) - for key, value in temp.iteritems(): + for key, value in temp.items(): filters[int(str(key), 16)] = value return filters diff --git a/reverse-sandbox/operation_node.py b/reverse-sandbox/operation_node.py index 9e1da69..8cfb057 100644 --- a/reverse-sandbox/operation_node.py +++ b/reverse-sandbox/operation_node.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 import sys import struct @@ -384,7 +384,7 @@ def convert_filter(self, convert_fn, f, regex_list, ios10_release, keep_builtin_ self.non_terminal.convert_filter(convert_fn, f, regex_list, ios10_release, keep_builtin_filters, global_vars) def str_debug(self): - ret = "(%02x) " % (self.offset) + ret = "(%02x) " % (int)(self.offset) if self.is_terminal(): ret += "terminal: " ret += str(self.terminal) @@ -419,7 +419,7 @@ def __eq__(self, other): return self.raw == other.raw def __hash__(self): - return self.offset + return (int)(self.offset) # Operation nodes processed so far. @@ -589,9 +589,9 @@ def print_operation_node_graph(g): return message = "" for node_iter in g.keys(): - message += "0x%x (%s) (%s) (decision: %s): [ " % (node_iter.offset, str(node_iter), g[node_iter]["type"], g[node_iter]["decision"]) + message += "0x%x (%s) (%s) (decision: %s): [ " % ((int)(node_iter.offset), str(node_iter), g[node_iter]["type"], g[node_iter]["decision"]) for edge in g[node_iter]["list"]: - message += "0x%x (%s) " % (edge.offset, str(edge)) + message += "0x%x (%s) " % ((int)(edge.offset), str(edge)) message += "]\n" logger.debug(message) @@ -1675,7 +1675,7 @@ def reduce_operation_node_graph(g): c_idx += 1 if c_idx >= l: break - rn = rg.get_vertice_by_value(g.keys()[c_idx]) + rn = rg.get_vertice_by_value(list(g.keys())[c_idx]) if not re.search("entitlement-value", str(rn)): break prevs_rv = rg.get_prev_vertices(rv) @@ -1715,7 +1715,7 @@ def main(): # Extract node for 'default' operation (index 0). default_node = find_operation_node_by_offset(operation_nodes, sb_ops_offsets[0]) - print "(%s default)" % (default_node.terminal) + print("(%s default)" % (default_node.terminal)) # For each operation expand operation node. #for idx in range(1, len(sb_ops_offsets)): @@ -1736,7 +1736,7 @@ def main(): else: if node.terminal: if node.terminal.type != default_node.terminal.type: - print "(%s %s)" % (node.terminal, operation) + print("(%s %s)" % (node.terminal, operation)) if __name__ == "__main__": diff --git a/reverse-sandbox/reverse_sandbox.py b/reverse-sandbox/reverse_sandbox.py index 14a811b..3febc45 100644 --- a/reverse-sandbox/reverse_sandbox.py +++ b/reverse-sandbox/reverse_sandbox.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ iOS/OS X sandbox decompiler @@ -154,7 +154,7 @@ def display_sandbox_profiles(f, re_table_offset, num_sb_ops, ios10_release): boundary = struct.unpack(" Date: Sun, 16 May 2021 00:01:20 +0300 Subject: [PATCH 3/4] Add dependency graph functionality SandBlaster can now be imported to return the decompiled profile as a dependency graph Changes: - Use absolute path for the logger config file - Add get_graph_for_profile and process_profile_graph functions to reverse_sandbox.py for returning the dependecy graph - Add get_dependency_graph and get_paths functions to operation_node.py that create the depenency graph. - Decode strings as ASCII instead of bytes in sandbox_filter.py - Remove a few unused variables --- reverse-sandbox/filters.py | 3 +- reverse-sandbox/operation_node.py | 43 ++++++++++- reverse-sandbox/regex_parser.py | 3 +- reverse-sandbox/reverse_sandbox.py | 111 +++++++++++++++++++++++++++-- reverse-sandbox/reverse_string.py | 3 +- reverse-sandbox/sandbox_filter.py | 10 +-- reverse-sandbox/sandbox_regex.py | 3 +- 7 files changed, 157 insertions(+), 19 deletions(-) diff --git a/reverse-sandbox/filters.py b/reverse-sandbox/filters.py index 89ca81a..dc0fb3e 100644 --- a/reverse-sandbox/filters.py +++ b/reverse-sandbox/filters.py @@ -1,9 +1,10 @@ +from os import path import json def read_filters(): temp = {} filters = {} - with open('filters.json') as data: + with open(path.join(path.dirname(__file__), "filters.json")) as data: temp = json.load(data) for key, value in temp.items(): diff --git a/reverse-sandbox/operation_node.py b/reverse-sandbox/operation_node.py index 8cfb057..eb917f2 100644 --- a/reverse-sandbox/operation_node.py +++ b/reverse-sandbox/operation_node.py @@ -1,12 +1,14 @@ #!/usr/bin/python3 +import copy import sys import struct import re +from os import path import logging import logging.config -logging.config.fileConfig("logger.config") +logging.config.fileConfig(path.join(path.dirname(__file__), "logger.config")) logger = logging.getLogger(__name__) class TerminalNode(): @@ -966,6 +968,34 @@ def recursive_xml_str(self, level, recursive_is_not): result_str += level*"\t" + "\n" return result_str + def get_paths(self, rg, paths, path): + next_vertices = rg.get_next_vertices(self) + if self.is_type_start(): + for next_node in next_vertices: + next_node._get_paths(rg, paths, path) + # TODO: modifica daca e entitlement + elif self.is_type_single() or self.is_type_require_entitlement(): + name, arg = self.value.values() + if self.is_not and arg: + to_add = f'require-not({name}({arg}))' + elif self.is_not: + to_add = f'require-not({name})' + elif arg: + to_add = f'{name}({arg})' + else: + to_add = name + path.append(to_add) + if not next_vertices: + paths.append(copy.deepcopy(path)) + else: + next_vertices[0]._get_paths(rg, paths, path) + path.pop() + elif self.is_type_require_all(): + next_vertices[0]._get_paths(rg, paths, path) + elif self.is_type_require_any(): + for next in next_vertices: + next._get_paths(rg, paths, path) + def __str__(self): return self.recursive_str(1, False) @@ -1648,6 +1678,13 @@ def dump_xml(self, operation, out_f): out_f.write("\t\t\n") out_f.write("\t\n") + def get_dependency_graph(self, default_node): + paths = [] + start_vertices = filter(lambda v: v.type != default_node.type, + self.get_start_vertices()) + for v in start_vertices: + v.get_paths(self, paths, []) + return paths def reduce_operation_node_graph(g): # Create reduced graph. @@ -1718,8 +1755,8 @@ def main(): print("(%s default)" % (default_node.terminal)) # For each operation expand operation node. - #for idx in range(1, len(sb_ops_offsets)): - for idx in range(10, 11): + for idx in range(1, len(sb_ops_offsets)): + # for idx in range(10, 11): offset = sb_ops_offsets[idx] operation = sb_ops[idx] node = find_operation_node_by_offset(operation_nodes, offset) diff --git a/reverse-sandbox/regex_parser.py b/reverse-sandbox/regex_parser.py index e1d842b..0f24190 100644 --- a/reverse-sandbox/regex_parser.py +++ b/reverse-sandbox/regex_parser.py @@ -1,6 +1,7 @@ +from os import path import logging -logging.config.fileConfig("logger.config") +logging.config.fileConfig(path.join(path.dirname(__file__), "logger.config")) logger = logging.getLogger(__name__) def parse_character(re, i, regex_list): diff --git a/reverse-sandbox/reverse_sandbox.py b/reverse-sandbox/reverse_sandbox.py index 3febc45..bcc5725 100644 --- a/reverse-sandbox/reverse_sandbox.py +++ b/reverse-sandbox/reverse_sandbox.py @@ -16,13 +16,13 @@ import logging.config import argparse import os -import re import operation_node import sandbox_filter import sandbox_regex -logging.config.fileConfig("logger.config") +logging.config.fileConfig( + os.path.join(os.path.dirname(__file__), "logger.config")) logger = logging.getLogger(__name__) @@ -141,7 +141,7 @@ def display_sandbox_profiles(f, re_table_offset, num_sb_ops, ios10_release): break start = f.tell() end = re_table_offset * 8 - num_operation_nodes = (end - start) / 8 + num_operation_nodes = (end - start) // 8 logger.info("number of operation nodes: %u" % num_operation_nodes) for i in range(0, num_profiles): @@ -151,7 +151,6 @@ def display_sandbox_profiles(f, re_table_offset, num_sb_ops, ios10_release): f.seek(8 + (num_sb_ops + 2) * 2 * i) name_offset = struct.unpack(" 0: + f.seek(re_table_offset * 8) + re_offsets_table = struct.unpack("<%dH" % re_table_count, f.read(2 * re_table_count)) + for offset in re_offsets_table: + f.seek(offset * 8) + re_length = struct.unpack(" Date: Wed, 16 Jun 2021 19:51:30 +0300 Subject: [PATCH 4/4] Add iOS 9+ compatibility --- reverse-sandbox/operation_node.py | 73 +++++++++++++++++++++--------- reverse-sandbox/reverse_sandbox.py | 68 ++++++++++++++++++++++++++-- 2 files changed, 115 insertions(+), 26 deletions(-) diff --git a/reverse-sandbox/operation_node.py b/reverse-sandbox/operation_node.py index eb917f2..e4999e8 100644 --- a/reverse-sandbox/operation_node.py +++ b/reverse-sandbox/operation_node.py @@ -968,33 +968,62 @@ def recursive_xml_str(self, level, recursive_is_not): result_str += level*"\t" + "\n" return result_str - def get_paths(self, rg, paths, path): + def _add_ent_to_paths(self, ent, path, paths): + to_add = f'require-not({ent})' if self.is_not else ent + path.append(to_add) + paths.append(copy.deepcopy(path)) + path.pop() + + def _is_node_ent_val(self): + return 'entitlement-value' in self.str_simple() or\ + self.is_type_require_all() or self.is_type_require_any() + + def _get_simple_filter(self): + name, arg = self.value.values() + filt = f'{name}({arg})' if arg else name + return f'require-not({filt})' if self.is_not else filt + + def _visit_next_nodes(self, next_vertices, rg, paths, path, state): + for n in next_vertices: + n._get_paths(rg, paths, path, state) + + def _handle_state(self, rg, next_vertices, paths, crt_path, state): + crt_path.append(self._get_simple_filter()) + if state == 'req-any' or not next_vertices: + paths.append(copy.deepcopy(crt_path)) + self._visit_next_nodes(next_vertices, rg, paths, crt_path, state) + crt_path.pop() + + def _get_paths(self, rg, paths, crt_path, state): next_vertices = rg.get_next_vertices(self) if self.is_type_start(): - for next_node in next_vertices: - next_node._get_paths(rg, paths, path) - # TODO: modifica daca e entitlement - elif self.is_type_single() or self.is_type_require_entitlement(): + self._visit_next_nodes(next_vertices, rg, paths, crt_path, 'req-any') + elif self.is_type_require_entitlement(): name, arg = self.value.values() - if self.is_not and arg: - to_add = f'require-not({name}({arg}))' - elif self.is_not: - to_add = f'require-not({name})' - elif arg: - to_add = f'{name}({arg})' - else: - to_add = name - path.append(to_add) - if not next_vertices: - paths.append(copy.deepcopy(path)) + if next_vertices: + for n in next_vertices: + if n._is_node_ent_val(): + ent_vals = [] + n._get_paths(rg, ent_vals, [], 'req-any') + for val in ent_vals: + ent = f'{name}({arg}, [{val[0]}])' + self._add_ent_to_paths(ent, crt_path, paths) + else: + crt_path.append(self._get_simple_filter()) + if state == 'req-any': + paths.append(copy.deepcopy(crt_path)) + crt_path.pop() + n._get_paths(rg, paths, crt_path, state) + if state == 'req-all': + crt_path.pop() else: - next_vertices[0]._get_paths(rg, paths, path) - path.pop() + self._add_ent_to_paths(f'{name}({arg})', crt_path, paths) + elif self.is_type_single(): + self._handle_state(rg, next_vertices, paths, crt_path, state) elif self.is_type_require_all(): - next_vertices[0]._get_paths(rg, paths, path) + next_vertices[0]._get_paths(rg, paths, crt_path, 'req-all') elif self.is_type_require_any(): - for next in next_vertices: - next._get_paths(rg, paths, path) + self._visit_next_nodes(next_vertices, rg, paths, crt_path, 'req-any') def __str__(self): return self.recursive_str(1, False) @@ -1683,7 +1712,7 @@ def get_dependency_graph(self, default_node): start_vertices = filter(lambda v: v.type != default_node.type, self.get_start_vertices()) for v in start_vertices: - v.get_paths(self, paths, []) + v._get_paths(self, paths, [], 'req-any') return paths def reduce_operation_node_graph(g): diff --git a/reverse-sandbox/reverse_sandbox.py b/reverse-sandbox/reverse_sandbox.py index bcc5725..5b7222e 100644 --- a/reverse-sandbox/reverse_sandbox.py +++ b/reverse-sandbox/reverse_sandbox.py @@ -30,7 +30,7 @@ def extract_string_from_offset(f, offset): """Extract string (literal) from given offset.""" f.seek(offset * 8) len = struct.unpack("