[
  {
    "path": ".github/workflows/config/config.json",
    "content": "{\n\t\"default\": true,\n\t\"MD048\": { \"style\": \"backtick\" },\n\t\"MD046\": { \"style\": \"fenced\" },\n\t\"MD029\": { \"style\": \"one\" },\n\t\"line-length\": false,\n\t\"no-hard-tabs\": false\n}\n"
  },
  {
    "path": ".github/workflows/linter.yml",
    "content": "name: Linter\n\non: [push, pull_request]\n\njobs:\n  superlinter:\n    name: Super Linter\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v3\n        with:\n          # Full git history is needed to get a proper list of changed files within `super-linter`\n          fetch-depth: 0\n\n      - name: Lint Code Base\n        uses: github/super-linter@v4\n        env:\n          # Don't check already existent files\n          VALIDATE_ALL_CODEBASE: false\n          VALIDATE_GITHUB_ACTIONS: false\n          LINTER_RULES_PATH: /.github/workflows/\n          MARKDOWN_CONFIG_FILE: config/config.json\n          MARKDOWN_CUSTOM_RULE_GLOBS: rules/rules.js\n          DEFAULT_BRANCH: main\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/rules/common/inlineTokenChildren.js",
    "content": " class InlineTokenChildren {\n    constructor(token) {\n        if (token.type === \"inline\") {\n            this.root = token;\n            this.column = -1;\n            this.lineNumber = token.map[0];\n        } else {\n            throw new TypeError(\"wrong argument token type\");\n        }\n    }\n\n    *[Symbol.iterator]() {\n        for (let token of this.root.children) {\n            let { line, lineNumber } = token;\n            if (this.lineNumber !== lineNumber) {\n                this.column = -1;\n                this.lineNumber = lineNumber;\n            }\n            this.column = line.indexOf(token.content, this.column + 1);\n            yield { token, column: this.column + 1, lineNumber };\n        }\n    }\n}\n\nmodule.exports = { InlineTokenChildren };\n"
  },
  {
    "path": ".github/workflows/rules/common/wordPattern.js",
    "content": "class WordPattern {\n    constructor(pattern, parameters) {\n        const escapedDots = pattern.replace(/\\\\?\\./g, \"\\\\.\");\n        this.pattern = parameters && parameters.hasOwnProperty('noWordBoundary') ? escapedDots : \"\\\\b\" + escapedDots + \"\\\\b\";\n        const modifiers = parameters && parameters.hasOwnProperty('caseSensitive') && parameters.caseSensitive ? \"\" : \"i\";\n        this.regex = new RegExp(this.pattern, modifiers);\n        this.suggestion = parameters && parameters.hasOwnProperty('suggestion') ? parameters.suggestion : pattern;\n        this.stringRegex = new RegExp(\"^\" + escapedDots + \"$\", modifiers); // To match \"Category\" column words in changelogs, see case-sensitive.js\n        this.skipForUseCases = !!(parameters && parameters.hasOwnProperty('skipForUseCases'));\n    }\n\n    test(line) {\n        return new Match(line.match(this.regex));\n    }\n}\n\nclass Match {\n    constructor(match) {\n        this.match = match;\n    }\n\n    range() {\n        if (this.match) {\n            let column = this.match.index + 1;\n            let length = this.match[0].length;\n            if (this.match[2]) {\n                column += this.match[1].length;\n                length -= this.match[1].length;\n            }\n            return [column, length];\n        }\n        return null;\n    }\n\n    toString() {\n        return this.match ? this.match.toString() : \"null\";\n    }\n}\n\nmodule.exports = { WordPattern };\n"
  },
  {
    "path": ".github/workflows/rules/md101.js",
    "content": "const { InlineTokenChildren } = require(\"./common/inlineTokenChildren\");\nconst { WordPattern } = require(\"./common/wordPattern\");\n\nconst keywords = [\n    new WordPattern(\"iExtractor-manager\"),\n    new WordPattern(\"device-info\"),\n    new WordPattern(\"device-name\"),\n    new WordPattern(\"list_apps\"),\n    new WordPattern(\"decrypt_kcache\"),\n    new WordPattern(\"decrypt_fs\"),\n    new WordPattern(\"curl\"),\n    new WordPattern(\"wget\"),\n    new WordPattern(\"crontab\"),\n    new WordPattern(\"cron\"),\n    new WordPattern(\"netcat\"),\n    new WordPattern(\"ping\"),\n    new WordPattern(\"traceroute\"),\n    new WordPattern(\"sudo\"),\n    new WordPattern(\"(?<!(system |ISRG ))root(?! ca)\", { suggestion: \"root\" }),// match \"root\", but not \"root CA\", \"MacOS System Root\" and \"ISRG Root X1\"\n    new WordPattern(\"true\"),\n    new WordPattern(\"false\"),\n    new WordPattern(\"jps\"),\n    new WordPattern(\"name=value\"),\n    new WordPattern(\"key=value\"),\n    new WordPattern(\"time:value\"),\n    new WordPattern(\"atsd.log\"),\n    new WordPattern(\"start.log\"),\n    new WordPattern(\"logback.xml\"),\n    new WordPattern(\"graphite.conf\"),\n    new WordPattern(\"command_malformed.log\"),\n    new WordPattern(\"stdout\"),\n    new WordPattern(\"stderr\"),\n    new WordPattern(\"SIGTERM\"),\n    new WordPattern(\"NaN\"),\n    new WordPattern(\".png\", { noWordBoundary: true }),\n    new WordPattern(\".xml\", { noWordBoundary: true }),\n    new WordPattern(\".jar\", { noWordBoundary: true }),\n    new WordPattern(\".gz\", { noWordBoundary: true }),\n    new WordPattern(\".tar.gz\", { noWordBoundary: true }),\n    new WordPattern(\".zip\", { noWordBoundary: true }),\n    new WordPattern(\".txt\", { noWordBoundary: true }),\n    new WordPattern(\".csv\", { noWordBoundary: true }),\n    new WordPattern(\".json\", { noWordBoundary: true }),\n    new WordPattern(\".pdf\", { noWordBoundary: true }),\n    new WordPattern(\".html\", { noWordBoundary: true })\n\n];\n\nmodule.exports = {\n    names: [\"MD101\", \"backtick-keywords\"],\n    description: \"Keywords must be fenced and must be in appropriate case.\",\n    tags: [\"backtick\", \"code\", \"bash\"],\n    \"function\": (params, onError) => {\n        var inHeading = false;\n        var inLink = false;\n        for (let token of params.tokens) {\n            switch (token.type) {\n                case \"heading_open\":\n                    inHeading = true; break;\n                case \"heading_close\":\n                    inHeading = false; break;\n                case \"inline\":\n                    let children = new InlineTokenChildren(token);\n                    for (let { token: child, column, lineNumber } of children) {\n                        let isText = child.type === \"text\";\n                        switch (child.type) {\n                            case \"link_open\":\n                                inLink = true; break;\n                            case \"link_close\":\n                                inLink = false; break;\n                        }\n                        for (let k of keywords) {\n                            let anyCaseMatch = child.content.match(k.regex);\n                            if (anyCaseMatch != null) {\n                                let match = anyCaseMatch[0];\n                                let correct = k.suggestion;\n                                if ((!inHeading && !inLink && isText) || // Bad not fenced\n                                    (match !== correct)) { // Right fencing, wrong case\n                                    onError({\n                                        lineNumber,\n                                        detail: `Expected \\`${correct}\\`. Actual ${match}.`,\n                                        range: [column + anyCaseMatch.index, match.length]\n                                    })\n                                }\n                            }\n                        }\n                    }\n            }\n        }\n    }\n};\n"
  },
  {
    "path": ".github/workflows/rules/md102.js",
    "content": "const http_keywords = [\n    \"GET\",\n    \"POST\",\n    \"PUT\",\n    \"PATCH\",\n    \"DELETE\",\n    \"Content-Type\",\n    \"Content-Encoding\",\n    \"User-Agent\",\n    \"200 OK\",\n    \"401 Unauthorized\",\n    \"403 Forbidden\",\n    \"API_DATA_READ\",\n    \"API_DATA_WRITE\",\n    \"API_META_READ\",\n    \"API_META_WRITE\",\n    \"USER\",\n    \"EDITOR\",\n    \"ENTITY_GROUP_ADMIN\",\n    \"ADMIN\"\n];\nconst keywordsRegex = new RegExp(http_keywords.map(word => \"\\\\b\" + word + \"\\\\b\").join(\"|\"));\n\nconst { InlineTokenChildren } = require(\"./common/inlineTokenChildren\");\n\nmodule.exports = {\n    names: [\"MD102\", \"backtick-http\"],\n    description: \"HTTP keywords must be fenced.\",\n    tags: [\"backtick\", \"HTTP\", \"HTTPS\"],\n    \"function\": (params, onError) => {\n        var inHeading = false;\n        for (let token of params.tokens) {\n            switch (token.type) {\n                case \"heading_open\":\n                    inHeading = true; break;\n                case \"heading_close\":\n                    inHeading = false; break;\n                case \"inline\":\n                    if (!inHeading) {\n                        let children = new InlineTokenChildren(token);\n                        for (let { token: child, column, lineNumber } of children) {\n                            if (child.type === \"text\") {\n                                let exactCaseMatch = child.content.match(keywordsRegex);\n                                if (exactCaseMatch != null) {\n                                    let match = exactCaseMatch[0];\n                                    onError({\n                                        lineNumber,\n                                        detail: `Expected \\`${match}\\`. Actual ${match}.`,\n                                        range: [column + exactCaseMatch.index, match.length]\n                                    })\n                                }\n                            }\n                        }\n                    }\n            }\n        }\n    }\n};\n"
  },
  {
    "path": ".github/workflows/rules/md103.js",
    "content": "\"use strict\";\n\nmodule.exports = {\n  \"names\": [ \"MD103\", \"inline triple backticks\" ],\n  \"description\": \"inline triple backticks\",\n  \"tags\": [ \"backticks\" ],\n  \"function\": function rule(params, onError) {\n    for (const inline of params.tokens.filter(function filterToken(token) {\n      return token.type === \"inline\";\n    })) {\n        const index = inline.content.toLowerCase().indexOf(\"```\");\n        if (index !== -1) {\n          onError({\n            \"lineNumber\": inline.lineNumber,\n            \"context\": inline.content.substr(index - 1, 4),\n            \"detail\": \"Expected `. Actual ```\"\n          });\n        }\n      }\n  }\n};\n"
  },
  {
    "path": ".github/workflows/rules/md104.js",
    "content": "\"use strict\";\n\nmodule.exports = {\n  names: [\"MD104\", \"one line per sentence\"],\n  description: \"one line (and only one line) per sentence\",\n  tags: [\"sentences\"],\n  function: function rule(params, onError) {\n    for (const inline of params.tokens.filter(function filterToken(token) {\n      return token.type === \"inline\";\n    })) {\n      var actual_lines = inline.content.split(\"\\n\");\n      actual_lines.forEach((line, index, arr) => {\n\t\tlet outside = true;\n\t\tlet count = 0;\n\t\tArray.from(line).forEach((char) => {\n\t\t\tif ((char == \".\" || char == \"?\" || char == \"!\" || char == \";\" || char == \":\") && outside) {\n\t\t\t\tcount++;\n\t\t\t}\n\t\t\tif (char == \"`\") outside = !outside;\n\t\t\tif (char == \"[\") outside = false;\n\t\t\tif (char == \"(\") outside = false;\n\t\t\tif (char == \"]\") outside = true;\n\t\t\tif (char == \")\") outside = true;\n\t\t});\n        if (count > 1) {\n          onError({\n            lineNumber: inline.lineNumber + index,\n            detail:\n              \"Expected one sentence per line. Multiple end of sentence punctuation signs found on one line!\",\n          });\n        }\n      });\n    }\n  },\n};\n"
  },
  {
    "path": ".github/workflows/rules/rules.js",
    "content": "\"use strict\";\n\nconst rules = [\n\trequire(\"./md101.js\"),\n\trequire(\"./md102.js\"),\n\trequire(\"./md103.js\"),\n\trequire(\"./md104.js\"),\n];\nmodule.exports = rules;\n"
  },
  {
    "path": ".gitignore",
    "content": "*~\n*.o\n*.zip\n*.rar\n*.tar\n*gz\n*bz2\n*.obj\n*.a\n*.so\n*.lib\n*.dll\n*.swp\n*.swo\ntags\nTAGS\n*.exe\n*.class\n*.jar\n*.pyc\n*.log\n*.bin\ncore\n.DS_STORE\n"
  },
  {
    "path": ".gitmodules",
    "content": ""
  },
  {
    "path": "LICENSE",
    "content": "BSD 3-Clause License\n\nCopyright (c) 2016, North Carolina State University and University POLITEHNICA\nof Bucharest.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n   contributors may be used to endorse or promote products derived from\n   this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "# SandBlaster: Reversing the Apple Sandbox\n\nSandBlaster is a tool for reversing (decompiling) binary Apple sandbox profiles. Apple sandbox profiles are written in SBPL (*Sandbox Profile Language*), a Scheme-like language, and are then compiled into an undocumented binary format and shipped. Primarily used on iOS, sandbox profiles are present on macOS as well. SandBlaster is, to our knowledge, the first tool that reverses binary sandbox profiles to their original SBPL format. SandBlaster works on iOS from version 7 onwards including iOS 11.\n\nThe technical report [SandBlaster: Reversing the Apple Sandbox](https://arxiv.org/abs/1608.04303) presents extensive (though a bit outdated) information on SandBlaster internals.\n\nSandBlaster relied on previous work by [Dionysus Blazakis](https://github.com/dionthegod/XNUSandbox) and Stefan Esser's [code](https://github.com/sektioneins/sandbox_toolkit) and [slides](https://www.slideshare.net/i0n1c/ruxcon-2014-stefan-esser-ios8-containers-sandboxes-and-entitlements).\n\nThe reverser (in the `reverse-sandbox/` folder) and the helper tool (in the `helpers/` folder) run on any Python running platform.\n\nSandBlaster 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.\n\niExtractor is open source software released under the 3-clause BSD license.\n\n## Installation\n\nSandBlaster requires Python2 for the reverser (in `reverse-sandbox/`), Python3 with `lief` library for helper script (in `helpers/`).\n\nAfter cloning the SandBlaster repository, you have to install `lief` for Python3:\n```\npip3 install lief\n```\n\nIf the installation of `lief` fails you need compile to it. More information about how to compile it can be found on the [wiki page](https://lief.quarkslab.com/doc/stable/compilation.html).\n\n## Usage\n\nIn order to use SandBlaster you need access to the binary sandbox profiles and the sandbox operations, a set of strings that define sandbox-specific actions. Sandbox operations and sandbox profiles are extracted using the `helpers/extract_sandbox_data.py` script. Sandbox profiles are extracted from the kernel sandbox extension (as a bundle for iOS 4 and 9-11) or from kernel cache (as a bundle for iOS 12) or from the `sandboxd` file in the iOS filesystem (for iOS 5-8). Sandbox operations are extracted either from kernel extension (for iOS 4-11) or from kernel cache (for iOS 12).\n\nSo, as input data, SandBlaster requires the kernelcache, the kernel sandbox extension and the `sandboxd` file. Information and scripts on extracting them from a publicly available IPSW (*iPhone Software*) file is presented by [iExtractor](https://github.com/malus-security/iExtractor).\n\nBelow are the steps and commands to reverse the sandbox profiles for iOS 8.4.1, assuming the sandbox kernel extension (`com.apple.security.sandbox.kext`) and the `sandboxd` file are available:\n\n```\n# Extract sandbox operations from kernelcache.\ncd helpers/\n./extract_sandbox_data.py -o iPad2,1_8.4.1_12H321.sb_ops iPad2,1_8.4.1_12H321.com.apple.security.sandox.kext 8.4.1\n# Extract binary sandbox profile files from sandboxd.\nmkdir iPad2,1_8.4.1_12H321.sandbox_profiles\n./extract_sandbox_data.py -O iPad2,1_8.4.1_12H321.sandbox_profiles/ iPad2,1_8.4.1_12H321.sandboxd 8.4.1\n# Reverse all binary sandbox profiles.\ncd ../reverse-sandbox/\nmkdir iPad2,1_8.4.1_12H321.reversed_profiles\nfor i in ../helpers/iPad2,1_8.4.1_12H321.sandbox_profiles/*; do python reverse_sandbox.py -r 8.4.1 -o ../helpers/iPad2,1_8.4.1_12H321.sb_ops -d iPad2,1_8.4.1_12H321.reversed_profiles/ \"$i\"; done\n```\n\nBelow are the steps and commands to reverse the sandbox profiles for iOS 9.3, assuming the sandbox kernel extension (`com.apple.security.sandbox.kext`) is available:\n\n```\n# Extract sandbox operations from kernelcache.\ncd helpers/\n./extract_sandbox_data.py -o iPhone5,1_9.3_13E237.sb_ops iPhone5,1_9.3_13E237.com.apple.security.sandox.kext 9.3\n# Extract sandbox profile bundle from kernel sandbox extension.\n./extract_sandbox_data.py -O . iPhone5,1_9.3_13E237.com.apple.security.sandox.kext 9.3\ncd ../reverse-sandbox/\n# Reverse all binary sandbox profiles in sandbox bundle.\nmkdir iPhone5,1_9.3_13E237.reversed_profiles\n# Print all sandbox profiles in bundle.\npython reverse_sandbox.py -r 9.3 -o ../helpers/iPhone5,1_9.3_13E237.sb_ops -d iPhone5,1_9.3_13E237.reversed_profiles/ ../helpers/sandbox_bundle -psb\n# Do actual reversing.\npython reverse_sandbox.py -r 9.3 -o ../helpers/iPhone5,1_9.3_13E237.sb_ops -d iPhone5,1_9.3_13E237.reversed_profiles/ ../helpers/sandbox_bundle\n```\n\nThe extraction of the binary sandbox profiles differs between iOS <= 8 and iOS >= 9. Since iOS >= 9 the binary sandbox profiles are stored in a sandbox bundle in the kernel sandbox extension. The `helpers/extract_sandbox_data.py` script extracts them appropriately depending on the iOS version.\n\nThe `-psb` option for `reverse_sandbox.py` prints out the sandbox profiles part of a sandbox bundle without doing the actual reversing.\n\nThe `reverse_sandbox.py` script needs to be run in its directory (`reverse-sandbox/`) since it needs the other Python modules and the `logger.config` file.\n\n## Internals\n\nThe `helpers/` subfolder contains helper scripts that provide a nicer interface for the external tools.\n\nThe actual reverser is part of the `reverse-sandbox/` folder. Files here can be categorized as follows:\n\n  * The main script is `reverse_sandbox.py`. It parses the command line arguments, does basic parsing of the input binary file (extracts sections) and calls the appropriate functions from the other modules.\n  * The core of the implementation is `operation_node.py`. It provides functions to build the rules graph corresponding to the sandbox profile and to convert the graph to SBPL. It is called by `reverse_sandbox.py`.\n  * Sandbox filters (i.e. match rules inside sandbox profiles) are handled by the implementation in `sandbox_filter.py` and the configuration in `filters.json`, `filter_list.py` and `filters.py`. Filter specific functions are called by `operation_node.py`.\n  * Regular expression reversing is handled by `sandbox_regex.py` and `regex_parse.py`. `regex_parse.py` is the back end parser that converts the binary representation to a basic graph. `sandbox_regex.py` converts the graph representation (an automaton) to an actual regular expression (i.e. a string of characters and metacharacters). It is called by `reverse_sandbox.py` for parsing regular expressions, with the resulting regular expression list being passed to the functions exposed by `operation_node.py`; `operation_node.py` passes them on to sandbox filter handling files.\n  * The new format for storing strings since iOS 10 is handled by `reverse_string.py`. The primary `SandboxString` class in `reverse_string.py` is used in `sandbox_filter.py`.\n  * Logging is configured in the `logger.config` file. By default, `INFO` and higher level messages are printed to the console, while `DEBUG` and higher level messages are printed to the `reverse.log` file.\n\n## Supported iOS Versions\n\nSandBlaster works for iOS version 4 onwards including iOS 12. Apple has been making updates to the binary format of the sandbox profiles: since iOS 9 sandbox profiles are stored in a bundle, since iOS 10 strings are aggregated together in a specialied binary format. iOS 11 didn't bring any change to the format.\n\n## Community\n\nJoin us on [Discord](https://discord.gg/m3gjuyHYw9) for live discussions.\n"
  },
  {
    "path": "helpers/extract_sandbox_data.py",
    "content": "#!/usr/bin/env python3\n\nimport sys\nimport argparse\nimport struct\nimport lief\n\nCSTRING_SECTION = '__cstring'\nCONST_SECTION = '__const'\nDATA_SECTION = '__data'\n\n\ndef binary_get_word_size(binary: lief.MachO.Binary):\n    \"\"\"Gets the word size of the given binary\n\n    The Mach-O binary has 'magic' bytes. These bytes can be used for checking\n    whether the binary is 32bit or 64bit.\n    Note: iOS 4 and 5 are different to the other sandbox profiles as they have\n    no magic values.\n\n    Args:\n        binary: A sandbox profile in its binary form.\n\n    Returns:\n        4: for 32bit MachO binaries\n        8: for 64bit MachO binaries\n    \"\"\"\n\n    assert (binary.header.magic in\n            [lief.MachO.MACHO_TYPES.MAGIC, lief.MachO.MACHO_TYPES.MAGIC_64])\n\n    return 4 if binary.header.magic == lief.MachO.MACHO_TYPES.MAGIC else 8\n\n\ndef unpack(bytes_list):\n    \"\"\"Unpacks bytes\n\n    The information is stored as little endian so '<' is needed.\n    For 32bit 'I' is needed and for 64bit 'Q'.\n\n    Args:\n        bytes_list: A packed list of bytes.\n\n    Returns:\n        The unpacked 'higher-order' equivalent.\n    \"\"\"\n\n    if len(bytes_list) == 4:\n        return struct.unpack('<I', bytes(bytes_list))[0]\n\n    return struct.unpack('<Q', bytes(bytes_list))[0]\n\n\ndef binary_get_string_from_address(binary: lief.MachO.Binary, vaddr: int):\n    \"\"\"Returns the string from a given MachO binary at a given virtual address.\n\n        Note: The virtual address must be in the CSTRING section.\n\n        Args:\n            binary: A sandbox profile in its binary form.\n            vaddr: An address.\n\n        Returns:\n            A string with the content stored at the given virtual address.\n\n        Raises:\n            LIEF_ERR(\"Can't find a segment associated with the virtual address\n             0x{:x}\", address);\n    \"\"\"\n\n    section = get_section_from_segment(binary, \"__TEXT\", CSTRING_SECTION)\n    if not is_vaddr_in_section(vaddr, section):\n        return None\n\n    str = ''\n    while True:\n        try:\n            byte = binary.get_content_from_virtual_address(vaddr, 1)\n        except(Exception,):\n            return None\n\n        if byte is None or len(byte) == 0:\n            return None\n\n        byte = byte[0]\n        if byte == 0:\n            break\n\n        vaddr += 1\n        str += chr(byte)\n\n    return str\n\n\ndef untag_pointer(tagged_pointer):\n    \"\"\"Returns the untagged pointer.\n\n    On iOS 12 the first 16 bits(MSB) of a pointer are used to store extra\n    information. We say that the pointers from iOS 12 are tagged.\n    The pointers should have the 2 first bytes 0xffff, the next digits should\n    be fff0 and the pointed-to values should be multiple of 4.\n    More information can be found here:\n    https://bazad.github.io/2018/06/ios-12-kernelcache-tagged-pointers/\n\n    Args:\n        tagged_pointer: a pointer with the first 16 bits used to store extra\n                        information.\n\n    Returns:\n        A pointer with the 'tag' removed and starting with 0xffff\n        (the traditional way).\n    \"\"\"\n\n    return (tagged_pointer & ((1 << 48) -1)) | (0xffff << 48)\n\n\ndef get_section_from_segment(binary: lief.MachO.FatBinary,\n                             segment_name: str, section_name: str):\n    \"\"\"This can be used for retrieving const, cstring and data sections.\n    Const section contains two tables: one with the names of the sandbox\n    profile and one with the content of the sandbox profile.\n    This section is in the __DATA segment.\n\n    Constant string section (cstring) contains the names of the profiles.\n    This section is in the __TEXT segment.\n\n    Data section contains the structures describing the content of the\n    profiles and the content itself.\n    This section is in the __DATA segment.\n\n    Args:\n        binary: A sandbox profile in its binary form.\n        segment_name: The segment name (can be __DATA or __TEXT).\n        section_name: The section name (can be CSTRING_SECTION, CONST_SECTION,\n                      DATA_SECTION, all of them are macros)\n\n    Returns:\n        A binary section with the name given.\n    \"\"\"\n\n    seg = binary.get_segment(segment_name)\n\n    if seg:\n        sects = [s for s in seg.sections if s.name == section_name]\n        assert len(sects) == 1\n        return sects[0]\n\n    return None\n\n\ndef get_xref(binary: lief.MachO.Binary, vaddr: int):\n    \"\"\"Custom cross reference implementation which supports tagged pointers\n    from iOS 12. Searches for pointers in the given MachO binary to the given\n    virtual address.\n\n    Args:\n        binary: A sandbox profile in its binary form.\n        vaddr: An address.\n\n    Returns:\n        A list with all the pointers to the given virtual address.\n    \"\"\"\n\n    ans = []\n    word_size = binary_get_word_size(binary)\n    i = 0\n\n    for sect in binary.sections:\n        content = sect.content[:len(sect.content) - len(sect.content) % word_size]\n        content = [unpack(content[i:i + word_size])\n                   for i in range(0, len(content), word_size)]\n\n        if word_size == 8:\n            content = [untag_pointer(p) for p in content]\n\n        ans.extend((sect.virtual_address + i * word_size\n                    for i, p in enumerate(content) if p == vaddr))\n\n    return ans\n\n\ndef get_tables_section(binary: lief.MachO.Binary):\n    \"\"\"Searches for the section containing the sandbox operations table and\n    the sandbox binary profiles for older versions of iOS.\n\n    Args:\n        binary: A sandbox profile in its binary form.\n\n    Returns:\n        A binary section.\n    \"\"\"\n\n    str_sect = get_section_from_segment(binary, \"__TEXT\", CSTRING_SECTION)\n    strs = str_sect.search_all('default\\x00')\n\n    if len(strs) > 0:\n        vaddr_str = str_sect.virtual_address + strs[0]\n        xref_vaddrs = get_xref(binary, vaddr_str)\n\n        if len(xref_vaddrs) > 0:\n            sects = [binary.section_from_virtual_address(x) for x in xref_vaddrs]\n            sects = [s for s in sects if 'const' in s.name.lower()]\n            assert len(sects) >= 1 and all([sects[0] == s for s in sects])\n            return sects[0]\n\n    seg = binary.get_segment('__DATA')\n    if seg:\n        sects = [s for s in seg.sections if s.name == CONST_SECTION]\n        assert len(sects) <= 1\n\n        if len(sects) == 1:\n            return sects[0]\n\n    return binary.get_section(CONST_SECTION)\n\n\ndef is_vaddr_in_section(vaddr, section):\n    \"\"\"Checks if given virtual address is inside given section.\n\n    Args:\n        vaddr: A virtual address.\n        section: A section of the binary.\n\n    Returns:\n        True: if the address is inside the section\n        False: Otherwise\n    \"\"\"\n\n    return vaddr >= section.virtual_address \\\n        and vaddr < section.virtual_address + section.size\n\n\ndef unpack_pointer(addr_size, binary, vaddr):\n    \"\"\"Unpacks a pointer and untags it if it is necessary.\n\n    Args:\n        binary: A sandbox profile in its binary form.\n        vaddr: A virtual address.\n        addr_size: The size of an address (4 or 8).\n\n    Returns:\n        A pointer.\n    \"\"\"\n\n    ptr = unpack(\n        binary.get_content_from_virtual_address(vaddr, addr_size))\n    if addr_size == 8:\n        ptr = untag_pointer(ptr)\n    return ptr\n\n\ndef extract_data_tables_from_section(binary: lief.MachO.Binary, to_data, section):\n    \"\"\" Generic implementation of table search. A table is formed of adjacent\n    pointers to data.\n\n    Args:\n        binary: A sandbox profile in its binary form.\n        to_data: Function that checks if the data is valid. This function\n                 returns None for invalid data and anything else otherwise.\n        section: A section of the binary.\n\n    Returns:\n            An array of tables (arrays of data).\n    \"\"\"\n\n    addr_size = binary_get_word_size(binary)\n    start_addr = section.virtual_address\n    end_addr = section.virtual_address + section.size\n    tables = []\n    vaddr = start_addr\n\n    while vaddr <= end_addr - addr_size:\n        ptr = unpack_pointer(addr_size, binary, vaddr)\n\n        data = to_data(binary, ptr)\n        if data is None:\n            vaddr += addr_size\n            continue\n\n        table = [data]\n        vaddr += addr_size\n\n        while vaddr <= end_addr - addr_size:\n            ptr = unpack_pointer(addr_size, binary, vaddr)\n\n            data = to_data(binary, ptr)\n            if data is None:\n                break\n\n            table.append(data)\n            vaddr += addr_size\n\n        if table not in tables:\n            tables.append(table)\n\n        vaddr += addr_size\n\n    return tables\n\n\ndef extract_string_tables(binary: lief.MachO.Binary):\n    \"\"\"Extracts string tables from the given MachO binary.\n\n    Args:\n        binary: A sandbox profile in its binary form.\n\n    Returns:\n        The string tables.\n    \"\"\"\n\n    return extract_data_tables_from_section(binary,\n                                            binary_get_string_from_address,\n                                            get_tables_section(binary))\n\n\ndef extract_separated_profiles(binary, string_tables):\n    \"\"\"Extract separated profiles from given MachO binary. It requires all\n    string tables. This function is intended to be used for older version\n    of iOS(<=7) because in newer versions the sandbox profiles are bundled.\n\n    Args:\n        binary: A sandbox profile in its binary form.\n        string_tables: The extracted string tables.\n\n    Returns:\n        A zip object with profiles.\n    \"\"\"\n\n    def get_profile_names():\n        \"\"\"Extracts the profile names.\n\n            Returns:\n                A list with the names of the sandbox profiles.\n        \"\"\"\n\n        def transform(arr):\n            if len(arr) <= 3:\n                return None\n\n            ans = []\n            tmp = []\n            for val in arr:\n                if val in ['default', '0123456789abcdef']:\n                    ans.append(tmp)\n                    tmp = []\n                else:\n                    tmp.append(val)\n            ans.append(tmp)\n            return ans\n\n        def get_sol(posible):\n            ans = [arr for arr in posible\n                   if 'com.apple.sandboxd' in arr]\n            assert len(ans) == 1\n            return ans[0]\n\n        profile_names_v = [transform(v) for v in string_tables]\n        profile_names_v = [v for v in profile_names_v if v is not None]\n        profile_names_v = [x for v in profile_names_v for x in v]\n        return get_sol(profile_names_v)\n\n    def get_profile_contents():\n        \"\"\"Extracts the profile names.\n\n            Returns:\n                 The contents of the sandbox profiles.\n        \"\"\"\n\n        def get_profile_content(binary, vaddr):\n            addr_size = binary_get_word_size(binary)\n            section = get_section_from_segment(binary, \"__DATA\", DATA_SECTION)\n\n            if not is_vaddr_in_section(vaddr, section):\n                return None\n\n            data = binary.get_content_from_virtual_address(vaddr, 2 * addr_size)\n            if len(data) != 2 * addr_size:\n                return None\n\n            data_vaddr = unpack(data[:addr_size])\n            size = unpack(data[addr_size:])\n            if not is_vaddr_in_section(vaddr, section):\n                return None\n\n            data = binary.get_content_from_virtual_address(data_vaddr, size)\n            if len(data) != size:\n                return None\n            return bytes(data)\n\n        contents_v = [v for v in\n                      extract_data_tables_from_section(binary,\n                                                       get_profile_content,\n                                                       get_tables_section(binary))\n                      if len(v) > 3]\n\n        assert len(contents_v) == 1\n        return contents_v[0]\n\n    profile_names = get_profile_names()\n    profile_contents = get_profile_contents()\n\n    assert len(profile_names) == len(profile_contents)\n    return zip(profile_names, profile_contents)\n\n\ndef extract_sbops(string_tables):\n    \"\"\" Extracts sandbox operations from a given MachO binary.\n    If the sandbox profiles are stored either in sandboxd or sandbox kernel\n    extension, the operations are stored always in the kernel extension.\n    The sandbox operations are stored similar to the separated sandbox profiles\n    but this time we have only one table: the name table.\n\n    Args:\n        string_tables: The binary's string tables.\n\n    Returns:\n        The sandbox operations.\n    \"\"\"\n\n    def transform(arr):\n        if len(arr) <= 3:\n            return None\n\n        idxs = []\n        for idx, val in enumerate(arr):\n            if val == 'default':\n                idxs.append(idx)\n\n        return [arr[idx:] for idx in idxs]\n\n    def get_sol(possible):\n        assert len(possible) >= 1\n\n        sol = []\n        if len(possible) > 1:\n            cnt = min(len(arr) for arr in possible)\n            for vals in zip(*[val[:cnt] for val in possible]):\n                if not all(val == vals[0] for val in vals):\n                    break\n                sol.append(vals[0])\n        else:\n            sol.append(possible[0][0])\n            for pos in possible[0][1:]:\n                if pos in ['HOME', 'default']:\n                    break\n                sol.append(pos)\n\n        return sol\n\n    sbops_v = [transform(v) for v in string_tables]\n    sbops_v = [v for v in sbops_v if v is not None and v != []]\n    sbops_v = [x for v in sbops_v for x in v]\n\n    return get_sol(sbops_v)\n\n\ndef get_ios_major_version(version: str):\n    \"\"\"Extracts the major iOS version from a given version.\n\n        Args:\n            version: A string with the 'full' version.\n        Returns:\n            An integer with the major iOS version.\n\n    \"\"\"\n\n    return int(version.split('.')[0])\n\n\ndef findall(searching, pattern):\n    \"\"\"Finds all the substring in the given string.\n\n    Args:\n        searching: A string.\n        pattern: A pattern that needs to be searched in the searching string.\n\n    Returns:\n        The indexes of all substrings equal to pattern inside searching string.\n    \"\"\"\n\n    i = searching.find(pattern)\n    while i != -1:\n        yield i\n        i = searching.find(pattern, i + 1)\n\n\ndef check_regex(data: bytes, base_index: int, ios_version: int):\n    \"\"\" Checks if the regular expression (from sandbox profile) at offset\n    base_index from data is valid for newer versions of iOS(>=8).\n\n    Args:\n        data: An array of bytes.\n        base_index: The starting index.\n        ios_version: An integer representing the iOS version.\n\n    Returns:\n        True: if the regular expression is valid for iOS version >= 8.\n        False: otherwise.\n    \"\"\"\n\n    if base_index + 0x10 > len(data):\n        return False\n\n    if ios_version >= 13:\n        size = struct.unpack('<H', data[base_index: base_index + 0x2])[0]\n        version = struct.unpack('>I', data[base_index + 0x2: base_index + 0x6])[0]\n    else:\n        size = struct.unpack('<I', data[base_index: base_index + 0x4])[0]\n        version = struct.unpack('>I', data[base_index + 0x4: base_index + 0x8])[0]\n\n    if size > 0x1000 or size < 0x8 or base_index + size + 4 > len(data):\n        return False\n\n    if version != 3:\n        return False\n\n    if ios_version >= 13:\n        sub_size = struct.unpack('<H', data[base_index + 0x6: base_index + 0x8])[0]\n    else:\n        sub_size = struct.unpack('<H', data[base_index + 0x8: base_index + 0xa])[0]\n\n    return size == sub_size + 6\n\n\ndef unpack_for_newer_ios(base_index, count, data):\n    \"\"\"Unpacking for newer iOS versions (>= 13).\n\n    Args:\n        base_index: The starting index.\n        count: Bundle size.\n        data: An array of bytes.\n    Returns:\n        The new base index and an offset.\n    \"\"\"\n\n    re_offset = base_index + 12\n    op_nodes_count = struct.unpack('<H', data[base_index + 2:base_index + 4])[0]\n    sb_ops_count = struct.unpack('<H', data[base_index + 4:base_index + 6])[0]\n    sb_profiles_count = struct.unpack('<H', data[base_index + 6:base_index + 8])[0]\n    global_table_count = struct.unpack('<B', data[base_index + 10:base_index + 11])[0]\n    debug_table_count = struct.unpack('<B', data[base_index + 11:base_index + 12])[0]\n    # base_index will be now at the of op_nodes\n    base_index += 12 + (count + global_table_count + debug_table_count) * 2 + \\\n                  (2 + sb_ops_count) * 2 * sb_profiles_count + \\\n                  op_nodes_count * 8 + 4\n\n    return base_index, re_offset\n\n\ndef check_bundle(data: bytes, base_index: int, ios_version: int):\n    \"\"\"Checks if the sandbox profile bundle at offset base_index from data\n    is valid for the given ios_version. Note that sandbox profile bundles are\n    used for newer versions of iOS(>=8).\n\n    Args:\n        data: An array of bytes.\n        base_index: The starting index.\n        ios_version: An integer representing the iOS version.\n\n    Returns:\n        True: if the sandbox profile bundle is valid.\n        False: otherwise.\n    \"\"\"\n\n    if len(data) - base_index < 50:\n        return False\n    re_offset, aux = struct.unpack('<2H', data[base_index + 2:base_index + 6])\n\n    if ios_version >= 13:\n        count = struct.unpack('<H', data[base_index + 8:base_index + 10])[0]\n        if count < 0x10:\n            return False\n    elif ios_version >= 12:\n        count = (aux - re_offset) * 4\n        # bundle should be big\n        if count < 0x10:\n            return False\n    else:\n        count = aux\n\n    if count > 0x1000 or re_offset < 0x10:\n        return False\n\n    if ios_version >= 13:\n        base_index, re_offset = unpack_for_newer_ios(base_index, count, data)\n\n    else:\n        re_offset = base_index + re_offset * 8\n        if len(data) - re_offset < count * 2:\n            return False\n\n    for off_index in range(re_offset, re_offset + 2 * count, 2):\n        index = struct.unpack('<H', data[off_index:off_index + 2])[0]\n        if index == 0:\n            if off_index < re_offset + 2 * count - 4:\n                return False\n            continue\n\n        index = base_index + index * 8\n\n        if not check_regex(data, index, ios_version):\n            return False\n\n    return True\n\n\ndef extract_bundle_profiles(binary: lief.MachO.Binary, ios_version: int):\n    \"\"\"Extracts sandbox profile bundle from the given MachO binary which was\n    extracted from a device with provided ios version.\n\n    Args:\n        binary: A sandbox profile in its binary form.\n        ios_version: The major ios version.\n\n    Returns:\n        The sandbox profile bundle.\n    \"\"\"\n\n    matches = []\n    for section in binary.sections:\n        if section.name == '__text':\n            continue\n\n        content = bytes(section.content)\n        for index in findall(content, b'\\x00\\x80'):\n            if check_bundle(content, index, ios_version):\n                matches.append(content[index:])\n\n    assert len(matches) == 1\n    return matches[0]\n\n\ndef main(args):\n    if type(args.binary) == lief.MachO.FatBinary:\n        assert args.binary.size == 1\n        binary = args.binary.at(0)\n    else:\n        binary = args.binary\n\n    retcode = 0\n    string_tables = extract_string_tables(binary)\n\n    if args.sbops_file is not None:\n        sbops = extract_sbops(string_tables)\n        sbops_str = '\\n'.join(sbops)\n        if args.sbops_file == '-':\n            print(sbops_str)\n        else:\n            try:\n                with open(args.sbops_file, 'w') as file:\n                    file.write(sbops_str + '\\n')\n            except IOError as exception:\n                retcode = exception.errno\n                print(exception, file=sys.stderr)\n\n    if args.sbs_dir is not None:\n        if args.version <= 8:\n            profiles = extract_separated_profiles(binary, string_tables)\n            for name, content in profiles:\n                try:\n                    with open(args.sbs_dir + '/' + name + '.sb.bin', 'wb') as file:\n                        file.write(content)\n                except IOError as exception:\n                    retcode = exception.errno\n                    print(exception, file=sys.stderr)\n        else:\n            content = extract_bundle_profiles(binary, args.version)\n            try:\n                with open(args.sbs_dir + '/sandbox_bundle', 'wb') as file:\n                    file.write(content)\n            except IOError as exception:\n                retcode = exception.errno\n                print(exception, file=sys.stderr)\n    exit(retcode)\n\n\nif __name__ == '__main__':\n    parser = argparse.ArgumentParser(\n        description='Sandbox profiles and operations extraction tool(iOS <9)')\n    parser.add_argument('binary', metavar='BINARY', type=lief.MachO.parse,\n                        help='path to sandbox(seatbelt) kernel exenstion' +\n                        '(iOS 4-12) in order to extract sandbox operations OR ' +\n                        'path to sandboxd(iOS 5-8) / sandbox(seatbelt) kernel extension' +\n                        '(iOS 4 and 9-12) in order to extract sandbox profiles')\n    parser.add_argument('version', metavar='VERSION',\n                        type=get_ios_major_version, help='iOS version for given binary')\n    parser.add_argument('-o', '--output-sbops', dest='sbops_file', type=str,\n                        default=None,\n                        help='path to sandbox profile operations store file')\n    parser.add_argument('-O', '--output-profiles', dest='sbs_dir', type=str,\n                        default=None,\n                        help='path to directory in which sandbox profiles should be stored')\n\n    args = parser.parse_args()\n    exit(main(args))\n"
  },
  {
    "path": "reverse-sandbox/filters/filters_ios11.json",
    "content": "{\n    \"0x01\":{\n        \"name\":\"\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_with_type\"\n    },\n    \"0x02\":{\n        \"name\":\"mount-relative\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_with_type\"\n    },\n    \"0x03\":{\n        \"name\":\"xattr\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x04\":{\n        \"name\":\"file-mode\",\n        \"arg_process_fn\":\"get_filter_arg_octal_integer\"\n    },\n    \"0x05\":{\n        \"name\":\"ipc-posix-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x06\":{\n        \"name\":\"global-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x07\":{\n        \"name\":\"local-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x08\":{\n        \"name\":\"local\",\n        \"arg_process_fn\":\"get_filter_arg_network_address\"\n    },\n    \"0x09\":{\n        \"name\":\"remote\",\n        \"arg_process_fn\":\"get_filter_arg_network_address\"\n    },\n    \"0x0a\":{\n        \"name\":\"control-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x0b\":{\n        \"name\":\"socket-domain\",\n        \"arg_process_fn\":\"get_filter_arg_socket_domain\"\n    },\n    \"0x0c\":{\n        \"name\":\"socket-type\",\n        \"arg_process_fn\":\"get_filter_arg_socket_type\"\n    },\n    \"0x0d\":{\n        \"name\":\"socket-protocol\",\n        \"arg_process_fn\":\"get_filter_arg_integer\"\n    },\n    \"0x0e\":{\n        \"name\":\"target\",\n        \"arg_process_fn\":\"get_filter_arg_owner\"\n    },\n    \"0x0f\":{\n        \"name\":\"fsctl-command\",\n        \"arg_process_fn\":\"get_filter_arg_ctl\"\n    },\n    \"0x10\":{\n        \"name\":\"ioctl-command\",\n        \"arg_process_fn\":\"get_filter_arg_ctl\"\n    },\n    \"0x11\":{\n        \"name\":\"iokit-user-client-class\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x12\":{\n        \"name\":\"iokit-property\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x13\":{\n        \"name\":\"iokit-connection\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x14\":{\n        \"name\":\"device-major\",\n        \"arg_process_fn\":\"get_filter_arg_integer\"\n    },\n    \"0x15\":{\n        \"name\":\"device-minor\",\n        \"arg_process_fn\":\"get_filter_arg_integer\"\n    },\n    \"0x16\":{\n        \"name\":\"device-conforms-to\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_no_skip\"\n    },\n    \"0x17\":{\n        \"name\":\"extension\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_no_skip\"\n    },\n    \"0x18\":{\n        \"name\":\"extension-class\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x19\":{\n        \"name\":\"appleevent-destination\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x1a\":{\n        \"name\":\"debug-mode\",\n        \"arg_process_fn\":\"get_none\"\n    },\n    \"0x1b\":{\n        \"name\":\"right-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x1c\":{\n        \"name\":\"preference-domain\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x1d\":{\n        \"name\":\"vnode-type\",\n        \"arg_process_fn\":\"get_filter_arg_vnode_type\"\n    },\n    \"0x1e\":{\n        \"name\":\"require-entitlement\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_no_skip\"\n    },\n    \"0x1f\":{\n        \"name\":\"entitlement-value\",\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\n    },\n    \"0x20\":{\n        \"name\":\"entitlement-value\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x21\":{\n        \"name\":\"kext-bundle-id\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x22\":{\n        \"name\":\"info-type\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x23\":{\n        \"name\":\"notification-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x24\":{\n        \"name\":\"notification-payload\",\n        \"arg_process_fn\":\"get_filter_arg_integer\"\n    },\n    \"0x25\":{\n        \"name\":\"semaphore-owner\",\n        \"arg_process_fn\":\"get_filter_arg_owner\"\n    },\n    \"0x26\":{\n        \"name\":\"sysctl-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x27\":{\n        \"name\":\"process-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x28\":{\n        \"name\":\"rootless-boot-device-filter\",\n        \"arg_process_fn\":\"get_none\"\n    },\n    \"0x29\":{\n        \"name\":\"rootless-file-filter\",\n        \"arg_process_fn\":\"get_none\"\n    },\n    \"0x2a\":{\n        \"name\":\"rootless-disk-filter\",\n        \"arg_process_fn\":\"get_none\"\n    },\n    \"0x2b\":{\n        \"name\":\"rootless-proc-filter\",\n        \"arg_process_fn\":\"get_none\"\n    },\n    \"0x2c\":{\n        \"name\":\"privilege-id\",\n        \"arg_process_fn\":\"get_filter_arg_privilege_id\"\n    },\n    \"0x2d\":{\n        \"name\":\"process-attribute\",\n        \"arg_process_fn\":\"get_filter_arg_process_attribute\"\n    },\n    \"0x2e\":{\n        \"name\":\"uid\",\n        \"arg_process_fn\":\"get_filter_arg_integer\"\n    },\n    \"0x2f\":{\n        \"name\":\"nvram-variable\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x30\":{\n        \"name\":\"csr\",\n        \"arg_process_fn\":\"get_filter_arg_csr\"\n    },\n    \"0x31\":{\n        \"name\":\"host-special-port\",\n        \"arg_process_fn\":\"get_filter_arg_host_port\"\n    },\n    \"0x81\":{\n        \"name\":\"regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x82\":{\n        \"name\":\"mount-relative-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x83\":{\n        \"name\":\"xattr-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x85\":{\n        \"name\":\"ipc-posix-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x86\":{\n        \"name\":\"global-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x87\":{\n        \"name\":\"local-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x91\":{\n        \"name\":\"iokit-user-client-class-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x92\":{\n        \"name\":\"iokit-property-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x93\":{\n        \"name\":\"iokit-connection-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x98\":{\n        \"name\":\"extension-class-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x99\":{\n        \"name\":\"appleevent-destination-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x9b\":{\n        \"name\":\"right-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x9c\":{\n        \"name\":\"preference-domain-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0xa0\":{\n        \"name\":\"entitlement-value-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0xa1\":{\n        \"name\":\"kext-bundle-id-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0xa3\":{\n        \"name\":\"notification-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0xa6\":{\n        \"name\":\"sysctl-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0xa7\":{\n        \"name\":\"process-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    }\n}"
  },
  {
    "path": "reverse-sandbox/filters/filters_ios12.json",
    "content": "{\n    \"0x01\":{\n        \"name\":\"\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_with_type\"\n    },\n    \"0x02\":{\n        \"name\":\"mount-relative-literal\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_with_type\"\n    },\n    \"0x03\":{\n        \"name\":\"xattr\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x04\":{\n        \"name\":\"file-mode\",\n        \"arg_process_fn\":\"get_filter_arg_octal_integer\"\n    },\n    \"0x05\":{\n        \"name\":\"ipc-posix-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x06\":{\n        \"name\":\"global-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x07\":{\n        \"name\":\"local-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x08\":{\n        \"name\":\"local\",\n        \"arg_process_fn\":\"get_filter_arg_network_address\"\n    },\n    \"0x09\":{\n        \"name\":\"remote\",\n        \"arg_process_fn\":\"get_filter_arg_network_address\"\n    },\n    \"0x0a\":{\n        \"name\":\"control-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x0b\":{\n        \"name\":\"socket-domain\",\n        \"arg_process_fn\":\"get_filter_arg_socket_domain\"\n    },\n    \"0x0c\":{\n        \"name\":\"socket-type\",\n        \"arg_process_fn\":\"get_filter_arg_socket_type\"\n    },\n    \"0x0d\":{\n        \"name\":\"socket-protocol\",\n        \"arg_process_fn\":\"get_filter_arg_integer\"\n    },\n    \"0x0e\":{\n        \"name\":\"target\",\n        \"arg_process_fn\":\"get_filter_arg_owner\"\n    },\n    \"0x0f\":{\n        \"name\":\"fsctl-command\",\n        \"arg_process_fn\":\"get_filter_arg_ctl\"\n    },\n    \"0x10\":{\n        \"name\":\"ioctl-command\",\n        \"arg_process_fn\":\"get_filter_arg_ctl\"\n    },\n    \"0x11\":{\n        \"name\":\"iokit-user-client-class\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x12\":{\n        \"name\":\"iokit-property\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x13\":{\n        \"name\":\"iokit-connection\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x14\":{\n        \"name\":\"device-major\",\n        \"arg_process_fn\":\"get_filter_arg_integer\"\n    },\n    \"0x15\":{\n        \"name\":\"device-minor\",\n        \"arg_process_fn\":\"get_filter_arg_integer\"\n    },\n    \"0x16\":{\n        \"name\":\"device-conforms-to\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_no_skip\"\n    },\n    \"0x17\":{\n        \"name\":\"extension\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_no_skip\"\n    },\n    \"0x18\":{\n        \"name\":\"extension-class\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x19\":{\n        \"name\":\"appleevent-destination\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x1a\":{\n        \"name\":\"debug-mode\",\n        \"arg_process_fn\":\"get_none\"\n    },\n    \"0x1b\":{\n        \"name\":\"right-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x1c\":{\n        \"name\":\"preference-domain\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x1d\":{\n        \"name\":\"vnode-type\",\n        \"arg_process_fn\":\"get_filter_arg_vnode_type\"\n    },\n    \"0x1e\":{\n        \"name\":\"require-entitlement\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_no_skip\"\n    },\n    \"0x1f\":{\n        \"name\":\"entitlement-value\",\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\n    },\n    \"0x20\":{\n        \"name\":\"entitlement-value\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x21\":{\n        \"name\":\"kext-bundle-id\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x22\":{\n        \"name\":\"info-type\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x23\":{\n        \"name\":\"notification-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x24\":{\n        \"name\":\"notification-payload\",\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\n    },\n    \"0x25\":{\n        \"name\":\"semaphore-owner\",\n        \"arg_process_fn\":\"get_filter_arg_owner\"\n    },\n    \"0x26\":{\n        \"name\":\"sysctl-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x27\":{\n        \"name\":\"process-path\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x28\":{\n        \"name\":\"rootless-boot-device-filter\",\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\n    },\n    \"0x29\":{\n        \"name\":\"rootless-disk-filter\",\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\n    },\n    \"0x2a\":{\n        \"name\":\"privilege-id\",\n        \"arg_process_fn\":\"get_filter_arg_privilege_id\"\n    },\n    \"0x2b\":{\n        \"name\":\"process-attribute\",\n        \"arg_process_fn\":\"get_filter_arg_process_attribute\"\n    },\n    \"0x2c\":{\n        \"name\":\"uid\",\n        \"arg_process_fn\":\"get_filter_arg_integer\"\n    },\n    \"0x2d\":{\n        \"name\":\"nvram-variable\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x2e\":{\n        \"name\":\"csr\",\n        \"arg_process_fn\":\"get_filter_arg_csr\"\n    },\n    \"0x2f\":{\n        \"name\":\"host-special-port\",\n        \"arg_process_fn\":\"get_filter_arg_host_port\"\n    },\n    \"0x30\":{\n        \"name\":\"filesystem-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x31\":{\n        \"name\":\"boot-arg\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x32\":{\n        \"name\":\"xpc-service-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x33\":{\n        \"name\":\"signing-identifier\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x34\":{\n        \"name\":\"signal-number\",\n        \"arg_process_fn\":\"get_filter_arg_integer\"\n    },\n    \"0x35\":{\n        \"name\":\"target-signing-identifier\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x36\":{\n        \"name\":\"reboot-flags\",\n        \"arg_process_fn\":\"get_filter_arg_integer\"\n    },\n    \"0x37\":{\n        \"name\":\"datavault-disk-filter\",\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\n    },\n    \"0x38\":{\n        \"name\":\"extension-path-ancestor\",\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\n    },\n    \"0x39\":{\n        \"name\":\"file-attribute\",\n        \"arg_process_fn\":\"get_filter_arg_integer\"\n    },\n    \"0x3a\":{\n        \"name\":\"storage-class\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x3b\":{\n        \"name\":\"storage-class-extension\",\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\n    },\n    \"0x3c\":{\n        \"name\":\"iokit-usb-interface-class\",\n        \"arg_process_fn\":\"get_filter_arg_integer\"\n    },\n    \"0x3d\":{\n        \"name\":\"iokit-usb-interface-subclass\",\n        \"arg_process_fn\":\"get_filter_arg_integer\"\n    },\n    \"0x3e\":{\n        \"name\":\"ancestor-signing-identifier\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x3f\":{\n        \"name\":\"frequire-ancestor-with-entitlement\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x81\":{\n        \"name\":\"regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x82\":{\n        \"name\":\"mount-relative-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x83\":{\n        \"name\":\"xattr-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x85\":{\n        \"name\":\"ipc-posix-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x86\":{\n        \"name\":\"global-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x87\":{\n        \"name\":\"local-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x8a\":{\n        \"name\":\"control-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x91\":{\n        \"name\":\"iokit-user-client-class-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x92\":{\n        \"name\":\"iokit-property-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x93\":{\n        \"name\":\"iokit-connection-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x98\":{\n        \"name\":\"extension-class-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x99\":{\n        \"name\":\"appleevent-destination-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x9b\":{\n        \"name\":\"right-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x9c\":{\n        \"name\":\"preference-domain-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0xa0\":{\n        \"name\":\"entitlement-value-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0xa1\":{\n        \"name\":\"kext-bundle-id-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0xa2\":{\n        \"name\":\"info-type-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0xa3\":{\n        \"name\":\"notification-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0xa6\":{\n        \"name\":\"sysctl-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0xa7\":{\n        \"name\":\"process-path-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0xad\":{\n        \"name\":\"nvram-variable-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0xb0\":{\n        \"name\":\"filesystem-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0xb1\":{\n        \"name\":\"boot-arg-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0xb2\":{\n        \"name\":\"xpc-service-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0xb3\":{\n        \"name\":\"signing-identifier-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0xb5\":{\n        \"name\":\"target-signing-identifier-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0xbe\":{\n        \"name\":\"ancestor-signing-identifier-regex\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    }\n}\n"
  },
  {
    "path": "reverse-sandbox/filters/filters_ios13.json",
    "content": "{\r\n    \"0x01\":{\r\n        \"name\":\"\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_with_type\"\r\n    },\r\n    \"0x02\":{\r\n        \"name\":\"mount-relative-literal\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_with_type\"\r\n    },\r\n    \"0x03\":{\r\n        \"name\":\"xattr\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x04\":{\r\n        \"name\":\"file-mode\",\r\n        \"arg_process_fn\":\"get_filter_arg_octal_integer\"\r\n    },\r\n    \"0x05\":{\r\n        \"name\":\"ipc-posix-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x06\":{\r\n        \"name\":\"global-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x07\":{\r\n        \"name\":\"local-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x08\":{\r\n        \"name\":\"local\",\r\n        \"arg_process_fn\":\"get_filter_arg_network_address\"\r\n    },\r\n    \"0x09\":{\r\n        \"name\":\"remote\",\r\n        \"arg_process_fn\":\"get_filter_arg_network_address\"\r\n    },\r\n    \"0x0a\":{\r\n        \"name\":\"control-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x0b\":{\r\n        \"name\":\"socket-domain\",\r\n        \"arg_process_fn\":\"get_filter_arg_socket_domain\"\r\n    },\r\n    \"0x0c\":{\r\n        \"name\":\"socket-type\",\r\n        \"arg_process_fn\":\"get_filter_arg_socket_type\"\r\n    },\r\n    \"0x0d\":{\r\n        \"name\":\"socket-protocol\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x0e\":{\r\n        \"name\":\"target\",\r\n        \"arg_process_fn\":\"get_filter_arg_owner\"\r\n    },\r\n    \"0x0f\":{\r\n        \"name\":\"fsctl-command\",\r\n        \"arg_process_fn\":\"get_filter_arg_ctl\"\r\n    },\r\n    \"0x10\":{\r\n        \"name\":\"ioctl-command\",\r\n        \"arg_process_fn\":\"get_filter_arg_ctl\"\r\n    },\r\n    \"0x11\":{\r\n        \"name\":\"iokit-user-client-class\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x12\":{\r\n        \"name\":\"iokit-property\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x13\":{\r\n        \"name\":\"iokit-connection\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x14\":{\r\n        \"name\":\"device-major\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x15\":{\r\n        \"name\":\"device-minor\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x16\":{\r\n        \"name\":\"device-conforms-to\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_no_skip\"\r\n    },\r\n    \"0x17\":{\r\n        \"name\":\"extension\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_no_skip\"\r\n    },\r\n    \"0x18\":{\r\n        \"name\":\"extension-class\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x19\":{\r\n        \"name\":\"appleevent-destination\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x1a\":{\r\n        \"name\":\"debug-mode\",\r\n        \"arg_process_fn\":\"get_none\"\r\n    },\r\n    \"0x1b\":{\r\n        \"name\":\"right-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x1c\":{\r\n        \"name\":\"preference-domain\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x1d\":{\r\n        \"name\":\"vnode-type\",\r\n        \"arg_process_fn\":\"get_filter_arg_vnode_type\"\r\n    },\r\n    \"0x1e\":{\r\n        \"name\":\"require-entitlement\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_no_skip\"\r\n    },\r\n    \"0x1f\":{\r\n        \"name\":\"entitlement-value\",\r\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\r\n    },\r\n    \"0x20\":{\r\n        \"name\":\"entitlement-value\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x21\":{\r\n        \"name\":\"kext-bundle-id\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x22\":{\r\n        \"name\":\"info-type\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x23\":{\r\n        \"name\":\"notification-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x24\":{\r\n        \"name\":\"notification-payload\",\r\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\r\n    },\r\n    \"0x25\":{\r\n        \"name\":\"semaphore-owner\",\r\n        \"arg_process_fn\":\"get_filter_arg_owner\"\r\n    },\r\n    \"0x26\":{\r\n        \"name\":\"sysctl-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x27\":{\r\n        \"name\":\"process-path\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x28\":{\r\n        \"name\":\"rootless-boot-device-filter\",\r\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\r\n    },\r\n    \"0x29\":{\r\n        \"name\":\"rootless-disk-filter\",\r\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\r\n    },\r\n    \"0x2a\":{\r\n        \"name\":\"privilege-id\",\r\n        \"arg_process_fn\":\"get_filter_arg_privilege_id\"\r\n    },\r\n    \"0x2b\":{\r\n        \"name\":\"process-attribute\",\r\n        \"arg_process_fn\":\"get_filter_arg_process_attribute\"\r\n    },\r\n    \"0x2c\":{\r\n        \"name\":\"uid\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x2d\":{\r\n        \"name\":\"nvram-variable\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x2e\":{\r\n        \"name\":\"csr\",\r\n        \"arg_process_fn\":\"get_filter_arg_csr\"\r\n    },\r\n    \"0x2f\":{\r\n        \"name\":\"host-special-port\",\r\n        \"arg_process_fn\":\"get_filter_arg_host_port\"\r\n    },\r\n    \"0x30\":{\r\n        \"name\":\"filesystem-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x31\":{\r\n        \"name\":\"boot-arg\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x32\":{\r\n        \"name\":\"xpc-service-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x33\":{\r\n        \"name\":\"signing-identifier\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x34\":{\r\n        \"name\":\"signal-number\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x35\":{\r\n        \"name\":\"target-signing-identifier\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x36\":{\r\n        \"name\":\"reboot-flags\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x37\":{\r\n        \"name\":\"datavault-disk-filter\",\r\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\r\n    },\r\n    \"0x38\":{\r\n        \"name\":\"extension-path-ancestor\",\r\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\r\n    },\r\n    \"0x39\":{\r\n        \"name\":\"file-attribute\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x3a\":{\r\n        \"name\":\"storage-class\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_no_skip\"\r\n    },\r\n    \"0x3b\":{\r\n        \"name\":\"storage-class-extension\",\r\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\r\n    },\r\n    \"0x3c\":{\r\n        \"name\":\"iokit-usb-interface-class\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x3d\":{\r\n        \"name\":\"iokit-usb-interface-subclass\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x3e\":{\r\n        \"name\":\"ancestor-signing-identifier\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x3f\":{\r\n        \"name\":\"require-ancestor-with-entitlement\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_no_skip\"\r\n    },\r\n    \"0x40\":{\r\n        \"name\":\"persona-type\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x41\":{\r\n        \"name\":\"syscall-number\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x42\":{\r\n        \"name\":\"syscall-mask\",\r\n        \"arg_process_fn\":\"get_none\"\r\n    },\r\n    \"0x43\":{\r\n        \"name\":\"require-target-with-entitlement\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_no_skip\"\r\n    },\r\n    \"0x44\":{\r\n        \"name\":\"iokit-registry-entry-attribute\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x45\":{\r\n        \"name\":\"user-intent-extension\",\r\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\r\n    },\r\n    \"0x46\":{\r\n        \"name\":\"snapshot-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x81\":{\r\n        \"name\":\"regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x82\":{\r\n        \"name\":\"mount-relative-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x83\":{\r\n        \"name\":\"xattr-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x85\":{\r\n        \"name\":\"ipc-posix-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x86\":{\r\n        \"name\":\"global-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x87\":{\r\n        \"name\":\"local-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x8a\":{\r\n        \"name\":\"control-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x91\":{\r\n        \"name\":\"iokit-user-client-class-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x92\":{\r\n        \"name\":\"iokit-property-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x93\":{\r\n        \"name\":\"iokit-connection-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x98\":{\r\n        \"name\":\"extension-class-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x99\":{\r\n        \"name\":\"appleevent-destination-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x9b\":{\r\n        \"name\":\"right-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x9c\":{\r\n        \"name\":\"preference-domain-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xa0\":{\r\n        \"name\":\"entitlement-value-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xa1\":{\r\n        \"name\":\"kext-bundle-id-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xa2\":{\r\n        \"name\":\"info-type-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xa3\":{\r\n        \"name\":\"notification-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xa6\":{\r\n        \"name\":\"sysctl-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xa7\":{\r\n        \"name\":\"process-path-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xad\":{\r\n        \"name\":\"nvram-variable-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xb0\":{\r\n        \"name\":\"filesystem-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xb1\":{\r\n        \"name\":\"boot-arg-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xb2\":{\r\n        \"name\":\"xpc-service-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xb3\":{\r\n        \"name\":\"signing-identifier-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xb5\":{\r\n        \"name\":\"target-signing-identifier-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xbe\":{\r\n        \"name\":\"ancestor-signing-identifier-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xc6\":{\r\n        \"name\":\"snapshot-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    }\r\n}\r\n"
  },
  {
    "path": "reverse-sandbox/filters/filters_ios14.json",
    "content": "{\r\n    \"0x01\":{\r\n        \"name\":\"\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_with_type\"\r\n    },\r\n    \"0x02\":{\r\n        \"name\":\"mount-relative-literal\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_with_type\"\r\n    },\r\n    \"0x03\":{\r\n        \"name\":\"xattr\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x04\":{\r\n        \"name\":\"file-mode\",\r\n        \"arg_process_fn\":\"get_filter_arg_octal_integer\"\r\n    },\r\n    \"0x05\":{\r\n        \"name\":\"ipc-posix-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x06\":{\r\n        \"name\":\"global-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x07\":{\r\n        \"name\":\"local-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x08\":{\r\n        \"name\":\"local\",\r\n        \"arg_process_fn\":\"get_filter_arg_network_address\"\r\n    },\r\n    \"0x09\":{\r\n        \"name\":\"remote\",\r\n        \"arg_process_fn\":\"get_filter_arg_network_address\"\r\n    },\r\n    \"0x0a\":{\r\n        \"name\":\"control-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x0b\":{\r\n        \"name\":\"socket-domain\",\r\n        \"arg_process_fn\":\"get_filter_arg_socket_domain\"\r\n    },\r\n    \"0x0c\":{\r\n        \"name\":\"socket-type\",\r\n        \"arg_process_fn\":\"get_filter_arg_socket_type\"\r\n    },\r\n    \"0x0d\":{\r\n        \"name\":\"socket-protocol\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x0e\":{\r\n        \"name\":\"target\",\r\n        \"arg_process_fn\":\"get_filter_arg_owner\"\r\n    },\r\n    \"0x0f\":{\r\n        \"name\":\"fsctl-command\",\r\n        \"arg_process_fn\":\"get_filter_arg_ctl\"\r\n    },\r\n    \"0x10\":{\r\n        \"name\":\"ioctl-command\",\r\n        \"arg_process_fn\":\"get_filter_arg_ctl\"\r\n    },\r\n    \"0x11\":{\r\n        \"name\":\"iokit-user-client-class\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x12\":{\r\n        \"name\":\"iokit-property\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x13\":{\r\n        \"name\":\"iokit-connection\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x14\":{\r\n        \"name\":\"device-major\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x15\":{\r\n        \"name\":\"device-minor\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x16\":{\r\n        \"name\":\"device-conforms-to\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_no_skip\"\r\n    },\r\n    \"0x17\":{\r\n        \"name\":\"extension\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_no_skip\"\r\n    },\r\n    \"0x18\":{\r\n        \"name\":\"extension-class\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x19\":{\r\n        \"name\":\"appleevent-destination\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x1a\":{\r\n        \"name\":\"debug-mode\",\r\n        \"arg_process_fn\":\"get_none\"\r\n    },\r\n    \"0x1b\":{\r\n        \"name\":\"right-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x1c\":{\r\n        \"name\":\"preference-domain\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x1d\":{\r\n        \"name\":\"vnode-type\",\r\n        \"arg_process_fn\":\"get_filter_arg_vnode_type\"\r\n    },\r\n    \"0x1e\":{\r\n        \"name\":\"require-entitlement\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_no_skip\"\r\n    },\r\n    \"0x1f\":{\r\n        \"name\":\"entitlement-value\",\r\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\r\n    },\r\n    \"0x20\":{\r\n        \"name\":\"entitlement-value\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x21\":{\r\n        \"name\":\"kext-bundle-id\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x22\":{\r\n        \"name\":\"info-type\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x23\":{\r\n        \"name\":\"notification-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x24\":{\r\n        \"name\":\"notification-payload\",\r\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\r\n    },\r\n    \"0x25\":{\r\n        \"name\":\"semaphore-owner\",\r\n        \"arg_process_fn\":\"get_filter_arg_owner\"\r\n    },\r\n    \"0x26\":{\r\n        \"name\":\"sysctl-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x27\":{\r\n        \"name\":\"process-path\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x28\":{\r\n        \"name\":\"rootless-boot-device-filter\",\r\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\r\n    },\r\n    \"0x29\":{\r\n        \"name\":\"rootless-disk-filter\",\r\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\r\n    },\r\n    \"0x2a\":{\r\n        \"name\":\"privilege-id\",\r\n        \"arg_process_fn\":\"get_filter_arg_privilege_id\"\r\n    },\r\n    \"0x2b\":{\r\n        \"name\":\"process-attribute\",\r\n        \"arg_process_fn\":\"get_filter_arg_process_attribute\"\r\n    },\r\n    \"0x2c\":{\r\n        \"name\":\"uid\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x2d\":{\r\n        \"name\":\"nvram-variable\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x2e\":{\r\n        \"name\":\"csr\",\r\n        \"arg_process_fn\":\"get_filter_arg_csr\"\r\n    },\r\n    \"0x2f\":{\r\n        \"name\":\"host-special-port\",\r\n        \"arg_process_fn\":\"get_filter_arg_host_port\"\r\n    },\r\n    \"0x30\":{\r\n        \"name\":\"filesystem-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x31\":{\r\n        \"name\":\"boot-arg\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x32\":{\r\n        \"name\":\"xpc-service-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x33\":{\r\n        \"name\":\"signing-identifier\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x34\":{\r\n        \"name\":\"signal-number\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x35\":{\r\n        \"name\":\"target-signing-identifier\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x36\":{\r\n        \"name\":\"reboot-flags\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x37\":{\r\n        \"name\":\"datavault-disk-filter\",\r\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\r\n    },\r\n    \"0x38\":{\r\n        \"name\":\"extension-path-ancestor\",\r\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\r\n    },\r\n    \"0x39\":{\r\n        \"name\":\"file-attribute\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x3a\":{\r\n        \"name\":\"storage-class\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_no_skip\"\r\n    },\r\n    \"0x3b\":{\r\n        \"name\":\"storage-class-extension\",\r\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\r\n    },\r\n    \"0x3c\":{\r\n        \"name\":\"iokit-usb-interface-class\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x3d\":{\r\n        \"name\":\"iokit-usb-interface-subclass\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x3e\":{\r\n        \"name\":\"ancestor-signing-identifier\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x3f\":{\r\n        \"name\":\"require-ancestor-with-entitlement\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_no_skip\"\r\n    },\r\n    \"0x40\":{\r\n        \"name\":\"persona-type\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x41\":{\r\n        \"name\":\"syscall-number\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x42\":{\r\n        \"name\":\"syscall-mask\",\r\n        \"arg_process_fn\":\"get_none\"\r\n    },\r\n    \"0x43\":{\r\n        \"name\":\"require-target-with-entitlement\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_no_skip\"\r\n    },\r\n    \"0x44\":{\r\n        \"name\":\"iokit-registry-entry-attribute\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x45\":{\r\n        \"name\":\"user-intent-extension\",\r\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\r\n    },\r\n    \"0x46\":{\r\n        \"name\":\"snapshot-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x47\":{\r\n        \"name\":\"mach-derived-port-role\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x48\":{\r\n        \"name\":\"message-number\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x49\":{\r\n        \"name\":\"message-name\",\r\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\r\n    },\r\n    \"0x4a\":{\r\n        \"name\":\"iokit-method-number\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x4b\":{\r\n        \"name\":\"iokit-trap-number\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x4c\":{\r\n        \"name\":\"machtrap-number\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x4d\":{\r\n        \"name\":\"machtrap-mask\",\r\n        \"arg_process_fn\":\"get_none\"\r\n    },\r\n    \"0x4e\":{\r\n        \"name\":\"kernel-mig-routine\",\r\n        \"arg_process_fn\":\"get_filter_arg_integer\"\r\n    },\r\n    \"0x4f\":{\r\n        \"name\":\"kernel-mig-routine-mask\",\r\n        \"arg_process_fn\":\"get_none\"\r\n    },\r\n    \"0x81\":{\r\n        \"name\":\"regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x82\":{\r\n        \"name\":\"mount-relative-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x83\":{\r\n        \"name\":\"xattr-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x85\":{\r\n        \"name\":\"ipc-posix-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x86\":{\r\n        \"name\":\"global-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x87\":{\r\n        \"name\":\"local-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x8a\":{\r\n        \"name\":\"control-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x91\":{\r\n        \"name\":\"iokit-user-client-class-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x92\":{\r\n        \"name\":\"iokit-property-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x93\":{\r\n        \"name\":\"iokit-connection-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x98\":{\r\n        \"name\":\"extension-class-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x99\":{\r\n        \"name\":\"appleevent-destination-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x9b\":{\r\n        \"name\":\"right-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0x9c\":{\r\n        \"name\":\"preference-domain-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xa0\":{\r\n        \"name\":\"entitlement-value-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xa1\":{\r\n        \"name\":\"kext-bundle-id-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xa2\":{\r\n        \"name\":\"info-type-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xa3\":{\r\n        \"name\":\"notification-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xa6\":{\r\n        \"name\":\"sysctl-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xa7\":{\r\n        \"name\":\"process-path-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xad\":{\r\n        \"name\":\"nvram-variable-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xb0\":{\r\n        \"name\":\"filesystem-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xb1\":{\r\n        \"name\":\"boot-arg-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xb2\":{\r\n        \"name\":\"xpc-service-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xb3\":{\r\n        \"name\":\"signing-identifier-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xb5\":{\r\n        \"name\":\"target-signing-identifier-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xbe\":{\r\n        \"name\":\"ancestor-signing-identifier-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xc6\":{\r\n        \"name\":\"snapshot-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xc7\":{\r\n        \"name\":\"mach-derived-port-role-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    },\r\n    \"0xc9\":{\r\n        \"name\":\"message-name-regex\",\r\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\r\n    }\r\n}\r\n"
  },
  {
    "path": "reverse-sandbox/filters/filters_ios4.json",
    "content": "{\n    \"0x01\":{\n        \"name\":\"regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x02\":{\n        \"name\":\"xattr\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x03\":{\n        \"name\":\"file-mode\",\n        \"arg_process_fn\":\"get_filter_arg_octal_integer\"\n    },\n    \"0x04\":{\n        \"name\":\"ipc-posix-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x05\":{\n        \"name\":\"global-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x06\":{\n        \"name\":\"local-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x07\":{\n        \"name\":\"local\",\n        \"arg_process_fn\":\"get_filter_arg_network_address\"\n    },\n    \"0x08\":{\n        \"name\":\"remote\",\n        \"arg_process_fn\":\"get_filter_arg_network_address\"\n    },\n    \"0x09\":{\n        \"name\":\"socket-domain\",\n        \"arg_process_fn\":\"get_filter_arg_socket_domain\"\n    },\n    \"0x0a\":{\n        \"name\":\"target\",\n        \"arg_process_fn\":\"get_filter_arg_owner\"\n    },\n    \"0x0b\":{\n        \"name\":\"iokit-user-client-class-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x0c\":{\n        \"name\":\"extension\",\n        \"arg_process_fn\":\"get_none\"\n    },\n    \"0x0d\":{\n        \"name\":\"debug-mode\",\n        \"arg_process_fn\":\"get_none\"\n    }\n}\n"
  },
  {
    "path": "reverse-sandbox/filters/filters_ios5.json",
    "content": "{\n    \"0x01\":{\n        \"name\":\"\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_with_type\"\n    },\n    \"0x02\":{\n        \"name\":\"mount-relative\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_with_type\"\n    },\n    \"0x04\":{\n        \"name\":\"file-mode\",\n        \"arg_process_fn\":\"get_filter_arg_octal_integer\"\n    },\n    \"0x05\":{\n        \"name\":\"ipc-posix-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x06\":{\n        \"name\":\"global-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x07\":{\n        \"name\":\"local-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x08\":{\n        \"name\":\"local\",\n        \"arg_process_fn\":\"get_filter_arg_network_address\"\n    },\n    \"0x09\":{\n        \"name\":\"remote\",\n        \"arg_process_fn\":\"get_filter_arg_network_address\"\n    },\n    \"0x0a\":{\n        \"name\":\"control-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x0b\":{\n        \"name\":\"socket-domain\",\n        \"arg_process_fn\":\"get_filter_arg_socket_domain\"\n    },\n    \"0x0c\":{\n        \"name\":\"socket-type\",\n        \"arg_process_fn\":\"get_filter_arg_socket_type\"\n    },\n    \"0x0d\":{\n        \"name\":\"socket-protocol\",\n        \"arg_process_fn\":\"get_filter_arg_integer\"\n    },\n    \"0x0e\":{\n        \"name\":\"target\",\n        \"arg_process_fn\":\"get_filter_arg_owner\"\n    },\n    \"0x0f\":{\n        \"name\":\"iokit-user-client-class\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x10\":{\n        \"name\":\"iokit-property\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x11\":{\n        \"name\":\"iokit-connection\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x12\":{\n        \"name\":\"extension\",\n        \"arg_process_fn\":\"get_none\"\n    },\n    \"0x13\":{\n        \"name\":\"mach-extension\",\n        \"arg_process_fn\":\"get_none\"\n    },\n    \"0x14\":{\n        \"name\":\"appleevent-destination\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x15\":{\n        \"name\":\"debug-mode\",\n        \"arg_process_fn\":\"get_none\"\n    },\n    \"0x16\":{\n        \"name\":\"right-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x81\":{\n        \"name\":\"regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x82\":{\n        \"name\":\"mount-relative-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x83\":{\n        \"name\":\"xattr\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x85\":{\n        \"name\":\"ipc-posix-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x86\":{\n        \"name\":\"global-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x87\":{\n        \"name\":\"local-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x8a\":{\n        \"name\":\"control-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x8f\":{\n        \"name\":\"iokit-user-client-class-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x90\":{\n        \"name\":\"iokit-property-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x91\":{\n        \"name\":\"iokit-connection-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x93\":{\n        \"name\":\"extension-class-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x94\":{\n        \"name\":\"appleevent-destination-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x96\":{\n        \"name\":\"right-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    }\n}\n"
  },
  {
    "path": "reverse-sandbox/filters/filters_ios6.json",
    "content": "{\n    \"0x01\":{\n        \"name\":\"\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_with_type\"\n    },\n    \"0x02\":{\n        \"name\":\"mount-relative\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_with_type\"\n    },\n    \"0x04\":{\n        \"name\":\"file-mode\",\n        \"arg_process_fn\":\"get_filter_arg_octal_integer\"\n    },\n    \"0x05\":{\n        \"name\":\"ipc-posix-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x06\":{\n        \"name\":\"global-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x07\":{\n        \"name\":\"local-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x08\":{\n        \"name\":\"local\",\n        \"arg_process_fn\":\"get_filter_arg_network_address\"\n    },\n    \"0x09\":{\n        \"name\":\"remote\",\n        \"arg_process_fn\":\"get_filter_arg_network_address\"\n    },\n    \"0x0a\":{\n        \"name\":\"control-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x0b\":{\n        \"name\":\"socket-domain\",\n        \"arg_process_fn\":\"get_filter_arg_socket_domain\"\n    },\n    \"0x0c\":{\n        \"name\":\"socket-type\",\n        \"arg_process_fn\":\"get_filter_arg_socket_type\"\n    },\n    \"0x0d\":{\n        \"name\":\"socket-protocol\",\n        \"arg_process_fn\":\"get_filter_arg_integer\"\n    },\n    \"0x0e\":{\n        \"name\":\"target\",\n        \"arg_process_fn\":\"get_filter_arg_owner\"\n    },\n    \"0x0f\":{\n        \"name\":\"fsctl-command\",\n        \"arg_process_fn\":\"get_filter_arg_ctl\"\n    },\n    \"0x10\":{\n        \"name\":\"ioctl-command\",\n        \"arg_process_fn\":\"get_filter_arg_ctl\"\n    },\n    \"0x11\":{\n        \"name\":\"iokit-user-client-class\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x12\":{\n        \"name\":\"iokit-property\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x13\":{\n        \"name\":\"iokit-connection\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x14\":{\n        \"name\":\"extension\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_no_skip\"\n    },\n    \"0x15\":{\n        \"name\":\"extension-class\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x16\":{\n        \"name\":\"appleevent-destination\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x17\":{\n        \"name\":\"debug-mode\",\n        \"arg_process_fn\":\"get_none\"\n    },\n    \"0x18\":{\n        \"name\":\"right-name\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x19\":{\n        \"name\":\"preference-domain\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x1a\":{\n        \"name\":\"tty\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x1b\":{\n        \"name\":\"require-entitlement\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset_no_skip\"\n    },\n    \"0x1c\":{\n        \"name\":\"entitlement-value\",\n        \"arg_process_fn\":\"get_filter_arg_boolean\"\n    },\n    \"0x1d\":{\n        \"name\":\"entitlement-value\",\n        \"arg_process_fn\":\"get_filter_arg_string_by_offset\"\n    },\n    \"0x81\":{\n        \"name\":\"regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x82\":{\n        \"name\":\"mount-relative-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x83\":{\n        \"name\":\"xattr\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x85\":{\n        \"name\":\"ipc-posix-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x86\":{\n        \"name\":\"global-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x87\":{\n        \"name\":\"local-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x8a\":{\n        \"name\":\"control-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x91\":{\n        \"name\":\"iokit-user-client-class-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x92\":{\n        \"name\":\"iokit-property-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x93\":{\n        \"name\":\"iokit-connection-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x95\":{\n        \"name\":\"extension-class-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x96\":{\n        \"name\":\"appleevent-destination-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x98\":{\n        \"name\":\"right-name-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x99\":{\n        \"name\":\"preference-domain-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    },\n    \"0x9d\":{\n        \"name\":\"entitlement-value-regex\",\n        \"arg_process_fn\":\"get_filter_arg_regex_by_id\"\n    }\n}\n"
  },
  {
    "path": "reverse-sandbox/filters.py",
    "content": "import json\n\ndef read_filters(file_path):\n    temp = {}\n    filters = {}\n    with open(file_path) as data:\n        temp = json.load(data)\n\n        for key, value in temp.items():\n            filters[int(str(key), 16)] = value\n\n    return filters\n\n\nclass Filters(object):\n\n    filters_ios4 = read_filters('filters/filters_ios4.json')\n    filters_ios5 = read_filters('filters/filters_ios5.json')\n    filters_ios6 = read_filters('filters/filters_ios6.json')\n    filters_ios11 = read_filters('filters/filters_ios11.json')\n    filters_ios12 = read_filters('filters/filters_ios12.json')\n    filters_ios13 = read_filters('filters/filters_ios13.json')\n    filters_ios14 = read_filters('filters/filters_ios14.json')\n\n    @staticmethod\n    def get_filters(ios_major_version):\n        if ios_major_version <= 4:\n            return Filters.filters_ios4\n        if ios_major_version == 5:\n            return Filters.filters_ios5\n        if ios_major_version == 6:\n            return Filters.filters_ios6\n        if ios_major_version <= 11:\n            return Filters.filters_ios11\n        if ios_major_version <= 12:\n            return Filters.filters_ios12\n        if ios_major_version <= 13:\n            return Filters.filters_ios13\n        return Filters.filters_ios14\n\n    @staticmethod\n    def exists(ios_major_version, id):\n        return id in Filters.get_filters(ios_major_version)\n\n    @staticmethod\n    def get(ios_major_version, id):\n        return Filters.get_filters(ios_major_version).get(id, None)\n"
  },
  {
    "path": "reverse-sandbox/logger.config",
    "content": "[loggers]\nkeys=root\n\n[logger_root]\nlevel=NOTSET\nhandlers=file,screen\n\n[formatters]\nkeys=simple,complex\n\n[formatter_simple]\nformat=%(asctime)s - %(name)s - %(levelname)s - %(message)s\n\n[formatter_complex]\nformat=%(asctime)s - %(name)s - %(levelname)s - (%(module)s:%(lineno)d) - %(message)s\n\n[handlers]\nkeys=file,screen\n\n[handler_screen]\nclass=StreamHandler\nformatter=simple\nlevel=INFO\nargs=(sys.stderr,)\n\n[handler_file]\nclass=logging.FileHandler\nformatter=complex\nlevel=DEBUG\nargs=('reverse.log',)\n"
  },
  {
    "path": "reverse-sandbox/operation_node.py",
    "content": "#!/usr/bin/python3\n\nimport sys\nimport struct\nimport re\nimport logging\nimport logging.config\n\nlogging.config.fileConfig(\"logger.config\")\nlogger = logging.getLogger(__name__)\n\nclass TerminalNode():\n    \"\"\"Allow or Deny end node in binary sandbox format\n\n    A terminal node, when reached, either denies or allows the rule.\n    A node has a type (allow or deny) and a set of flags. Flags are\n    currently unused.\n    \"\"\"\n\n    TERMINAL_NODE_TYPE_ALLOW = 0x00\n    TERMINAL_NODE_TYPE_DENY = 0x01\n    type = None\n    flags = None\n\n    def __eq__(self, other):\n        return self.type == other.type and self.flags == other.flags\n\n    def __str__(self):\n        if self.type == self.TERMINAL_NODE_TYPE_ALLOW:\n            return \"allow\"\n        elif self.type == self.TERMINAL_NODE_TYPE_DENY:\n            return \"deny\"\n        else:\n            return \"unknown\"\n\n    def is_allow(self):\n        return self.type == self.TERMINAL_NODE_TYPE_ALLOW\n\n    def is_deny(self):\n        return self.type == self.TERMINAL_NODE_TYPE_DENY\n\n\nclass NonTerminalNode():\n    \"\"\"Intermediary node consisting of a filter to match\n\n    The non-terminal node, when matched, points to a new node, and\n    when unmatched, to another node.\n\n    A non-terminal node consists of the filter to match, its argument and\n    the match and unmatch nodes.\n    \"\"\"\n\n    filter_id = None\n    filter = None\n    argument_id = None\n    argument = None\n    match_offset = None\n    match = None\n    unmatch_offset = None\n    unmatch = None\n\n    def __eq__(self, other):\n        return self.filter_id == other.filter_id and self.argument_id == other.argument_id and self.match_offset == other.match_offset and self.unmatch_offset == other.unmatch_offset\n\n    def simplify_list(self, arg_list):\n        result_list = []\n        for a in arg_list:\n            if len(a) == 0:\n                continue\n            tmp_list = list(result_list)\n            match_found = False\n            for r in tmp_list:\n                if len(r) == 0:\n                    continue\n                if a == r or a+\"/\" == r or a == r+\"/\":\n                    match_found = True\n                    result_list.remove(r)\n                    if a[-1] == '/':\n                        result_list.append(a + \"^^^\")\n                    else:\n                        result_list.append(a + \"/^^^\")\n            if match_found == False:\n                result_list.append(a)\n\n        return result_list\n\n    def str_debug(self):\n        if self.filter:\n            if self.argument:\n                if type(self.argument) is list:\n                    if len(self.argument) == 1:\n                        ret_str = \"\"\n                    else:\n                        self.argument = self.simplify_list(self.argument)\n                        if len(self.argument) == 1:\n                            ret_str = \"\"\n                        else:\n                            ret_str = \"(require-any \"\n                    for s in self.argument:\n                        curr_filter = self.filter\n                        regex_added = False\n                        prefix_added = False\n                        if len(s) == 0:\n                            s = \".+\"\n                            if not regex_added:\n                                regex_added = True\n                                if self.filter == \"literal\":\n                                    curr_filter = \"regex\"\n                                else:\n                                    curr_filter += \"-regex\"\n                        else:\n                            if s[-4:] == \"/^^^\":\n                                curr_filter = \"subpath\"\n                                s = s[:-4]\n                            if '\\\\' in s or '|' in s or ('[' in s and ']' in s) or '+' in s:\n                                if curr_filter == \"subpath\":\n                                    s = s + \"/?\"\n                                if self.filter == \"literal\":\n                                    curr_filter = \"regex\"\n                                else:\n                                    curr_filter += \"-regex\"\n                                s = s.replace('\\\\\\\\.', '[.]')\n                                s = s.replace('\\\\.', '[.]')\n                            if \"${\" in s and \"}\" in s:\n                                if not prefix_added:\n                                    prefix_added = True\n                                    curr_filter += \"-prefix\"\n                        if \"regex\" in curr_filter:\n                            ret_str += '(%04x, %04x) (%s #\"%s\")\\n' % (self.match_offset, self.unmatch_offset, curr_filter, s)\n                        else:\n                            ret_str += '(%s \"%s\")\\n' % (curr_filter, s)\n                    if len(self.argument) == 1:\n                        ret_str = ret_str[:-1]\n                    else:\n                        ret_str = ret_str[:-1] + \")\"\n                    return ret_str\n                s = self.argument\n                curr_filter = self.filter\n                if not \"regex\" in curr_filter:\n                    if '\\\\' in s or '|' in s or ('[' in s and ']' in s) or '+' in s:\n                        if self.filter == \"literal\":\n                            curr_filter = \"regex\"\n                        else:\n                            curr_filter += \"-regex\"\n                        s = s.replace('\\\\\\\\.', '[.]')\n                        s = s.replace('\\\\.', '[.]')\n                if \"${\" in s and \"}\" in s:\n                    if not \"prefix\" in curr_filter:\n                        curr_filter += \"-prefix\"\n                return \"(%04x, %04x) (%s %s)\" % (self.match_offset, self.unmatch_offset, curr_filter, s)\n            else:\n                return \"(%04x, %04x) (%s)\" % (self.match_offset, self.unmatch_offset, self.filter)\n        return \"(%02x %04x %04x %04x)\" % (self.filter_id, self.argument_id, self.match_offset, self.unmatch_offset)\n\n    def __str__(self):\n        if self.filter:\n            if self.argument:\n                if type(self.argument) is list:\n                    if len(self.argument) == 1:\n                        ret_str = \"\"\n                    else:\n                        self.argument = self.simplify_list(self.argument)\n                        if len(self.argument) == 1:\n                            ret_str = \"\"\n                        else:\n                            ret_str = \"(require-any \"\n                    for s in self.argument:\n                        curr_filter = self.filter\n                        regex_added = False\n                        prefix_added = False\n                        if len(s) == 0:\n                            s = \".+\"\n                            if not regex_added:\n                                regex_added = True\n                                if self.filter == \"literal\":\n                                    curr_filter = \"regex\"\n                                else:\n                                    curr_filter += \"-regex\"\n                        else:\n                            if s[-4:] == \"/^^^\":\n                                curr_filter = \"subpath\"\n                                s = s[:-4]\n                            if '\\\\' in s or '|' in s or ('[' in s and ']' in s) or '+' in s:\n                                if curr_filter == \"subpath\":\n                                    s = s + \"/?\"\n                                if self.filter == \"literal\":\n                                    curr_filter = \"regex\"\n                                else:\n                                    curr_filter += \"-regex\"\n                                s = s.replace('\\\\\\\\.', '[.]')\n                                s = s.replace('\\\\.', '[.]')\n                            if \"${\" in s and \"}\" in s:\n                                if not prefix_added:\n                                    prefix_added = True\n                                    curr_filter += \"-prefix\"\n                        if \"regex\" in curr_filter:\n                            ret_str += '(%s #\"%s\")\\n' % (curr_filter, s)\n                        else:\n                            ret_str += '(%s \"%s\")\\n' % (curr_filter, s)\n                    if len(self.argument) == 1:\n                        ret_str = ret_str[:-1]\n                    else:\n                        ret_str = ret_str[:-1] + \")\"\n                    return ret_str\n                s = self.argument\n                curr_filter = self.filter\n                if not \"regex\" in curr_filter:\n                    if '\\\\' in s or '|' in s or ('[' in s and ']' in s) or '+' in s:\n                        if self.filter == \"literal\":\n                            curr_filter = \"regex\"\n                        else:\n                            curr_filter += \"-regex\"\n                        s = s.replace('\\\\\\\\.', '[.]')\n                        s = s.replace('\\\\.', '[.]')\n                if \"${\" in s and \"}\" in s:\n                    if not \"prefix\" in curr_filter:\n                        curr_filter += \"-prefix\"\n                return \"(%s %s)\" % (curr_filter, s)\n            else:\n                return \"(%s)\" % (self.filter)\n        return \"(%02x %04x %04x %04x)\" % (self.filter_id, self.argument_id, self.match_offset, self.unmatch_offset)\n\n    def str_not(self):\n        if self.filter:\n            if self.argument:\n                if type(self.argument) is list:\n                    if len(self.argument) == 1:\n                        ret_str = \"\"\n                    else:\n                        self.argument = self.simplify_list(self.argument)\n                        if len(self.argument) == 1:\n                            ret_str = \"\"\n                        else:\n                            ret_str = \"(require-all \"\n                    for s in self.argument:\n                        curr_filter = self.filter\n                        regex_added = False\n                        prefix_added = False\n                        if len(s) == 0:\n                            s = \".+\"\n                            if not regex_added:\n                                regex_added = True\n                                if self.filter == \"literal\":\n                                    curr_filter = \"regex\"\n                                else:\n                                    curr_filter += \"-regex\"\n                        else:\n                            if s[-4:] == \"/^^^\":\n                                curr_filter = \"subpath\"\n                                s = s[:-4]\n                            if '\\\\' in s or '|' in s or ('[' in s and ']' in s) or '+' in s:\n                                if curr_filter == \"subpath\":\n                                    s = s + \"/?\"\n                                if self.filter == \"literal\":\n                                    curr_filter = \"regex\"\n                                else:\n                                    curr_filter += \"-regex\"\n                                s = s.replace('\\\\\\\\.', '[.]')\n                                s = s.replace('\\\\.', '[.]')\n                            if \"${\" in s and \"}\" in s:\n                                if not prefix_added:\n                                    prefix_added = True\n                                    curr_filter += \"-prefix\"\n                        if \"regex\" in curr_filter:\n                            ret_str += '(require-not (%s #\"%s\"))\\n' % (curr_filter, s)\n                        else:\n                            ret_str += '(require-not (%s \"%s\"))\\n' % (curr_filter, s)\n                    if len(self.argument) == 1:\n                        ret_str = ret_str[:-1]\n                    else:\n                        ret_str = ret_str[:-1] + \")\"\n                    return ret_str\n                s = self.argument\n                curr_filter = self.filter\n                if not \"regex\" in curr_filter:\n                    if '\\\\' in s or '|' in s or ('[' in s and ']' in s) or '+' in s:\n                        if self.filter == \"literal\":\n                            curr_filter = \"regex\"\n                        else:\n                            curr_filter += \"-regex\"\n                        s = s.replace('\\\\\\\\.', '[.]')\n                        s = s.replace('\\\\.', '[.]')\n                if \"${\" in s and \"}\" in s:\n                    if not \"prefix\" in curr_filter:\n                        curr_filter += \"-prefix\"\n                return \"(%s %s)\" % (curr_filter, s)\n            else:\n                return \"(%s)\" % (self.filter)\n        return \"(%02x %04x %04x %04x)\" % (self.filter_id, self.argument_id, self.match_offset, self.unmatch_offset)\n\n    def values(self):\n        if self.filter:\n            return (self.filter, self.argument)\n        return (\"%02x\" % self.filter_id, \"%04x\" % (self.argument_id))\n\n    def is_entitlement_start(self):\n        return self.filter_id == 0x1e or self.filter_id == 0xa0\n\n    def is_entitlement(self):\n        return self.filter_id == 0x1e or self.filter_id == 0x1f or self.filter_id == 0x20 or self.filter_id == 0xa0\n\n    def is_last_regular_expression(self):\n        return self.filter_id == 0x81 and self.argument_id == num_regex-1\n\n    def convert_filter(self, convert_fn, f, regex_list, ios_major_version,\n            keep_builtin_filters, global_vars, base_addr):\n        (self.filter, self.argument) = convert_fn(f, ios_major_version,\n            keep_builtin_filters, global_vars, regex_list, self.filter_id,\n            self.argument_id, base_addr)\n\n    def is_non_terminal_deny(self):\n        if self.match.is_non_terminal() and self.unmatch.is_terminal():\n            return self.unmatch.terminal.is_deny()\n\n    def is_non_terminal_allow(self):\n        if self.match.is_non_terminal() and self.unmatch.is_terminal():\n            return self.unmatch.terminal.is_allow()\n\n    def is_non_terminal_non_terminal(self):\n        return self.match.is_non_terminal() and self.unmatch.is_non_terminal()\n\n    def is_allow_non_terminal(self):\n        if self.match.is_terminal() and self.unmatch.is_non_terminal():\n            return self.match.terminal.is_allow()\n\n    def is_deny_non_terminal(self):\n        if self.match.is_terminal() and self.unmatch.is_non_terminal():\n            return self.match.terminal.is_deny()\n\n    def is_deny_allow(self):\n        if self.match.is_terminal() and self.unmatch.is_terminal():\n            return self.match.terminal.is_deny() and self.unmatch.terminal.is_allow()\n\n    def is_allow_deny(self):\n        if self.match.is_terminal() and self.unmatch.is_terminal():\n            return self.match.terminal.is_allow() and self.unmatch.terminal.is_deny()\n\n\nclass OperationNode():\n    \"\"\"A rule item in the binary sandbox profile\n\n    It may either be a teminal node (end node) or a non-terminal node\n    (intermediary node). Each node type uses another class, as defined\n    above.\n    \"\"\"\n\n    OPERATION_NODE_TYPE_NON_TERMINAL = 0x00\n    OPERATION_NODE_TYPE_TERMINAL = 0x01\n    offset = None\n    raw = []\n    type = None\n    terminal = None\n    non_terminal = None\n\n    def __init__(self, offset):\n        self.offset = offset\n\n    def is_terminal(self):\n        return self.type == self.OPERATION_NODE_TYPE_TERMINAL\n\n    def is_non_terminal(self):\n        return self.type == self.OPERATION_NODE_TYPE_NON_TERMINAL\n\n    def parse_terminal(self, ios_major_version):\n        self.terminal = TerminalNode()\n        self.terminal.parent = self\n        self.terminal.type = \\\n            self.raw[2 if ios_major_version <12 else 1] & 0x01\n        self.terminal.flags = \\\n            self.raw[2 if ios_major_version <12 else 1] & 0xfe\n\n    def parse_non_terminal(self):\n        self.non_terminal = NonTerminalNode()\n        self.non_terminal.parent = self\n        self.non_terminal.filter_id = self.raw[1]\n        self.non_terminal.argument_id = self.raw[2] + (self.raw[3] << 8)\n        self.non_terminal.match_offset = self.raw[4] + (self.raw[5] << 8)\n        self.non_terminal.unmatch_offset = self.raw[6] + (self.raw[7] << 8)\n\n    def parse_raw(self, ios_major_version):\n        self.type = self.raw[0]\n        if self.is_terminal():\n            self.parse_terminal(ios_major_version)\n        elif self.is_non_terminal():\n            self.parse_non_terminal()\n\n    def convert_filter(self, convert_fn, f, regex_list, ios_major_version,\n            keep_builtin_filters, global_vars, base_addr):\n        if self.is_non_terminal():\n            self.non_terminal.convert_filter(convert_fn, f, regex_list,\n                ios_major_version, keep_builtin_filters, global_vars, base_addr)\n\n    def str_debug(self):\n        ret = \"(%02x) \" % (int)(self.offset)\n        if self.is_terminal():\n            ret += \"terminal: \"\n            ret += str(self.terminal)\n        if self.is_non_terminal():\n            ret += \"non-terminal: \"\n            ret += str(self.non_terminal)\n        return ret\n\n    def __str__(self):\n        ret = \"\"\n        if self.is_terminal():\n            ret += str(self.terminal)\n        if self.is_non_terminal():\n            ret += str(self.non_terminal)\n        return ret\n\n    def str_not(self):\n        ret = \"\"\n        if self.is_terminal():\n            ret += str(self.terminal)\n        if self.is_non_terminal():\n            ret += self.non_terminal.str_not()\n        return ret\n\n    def values(self):\n        if self.is_terminal():\n            return (None, None)\n        else:\n            return self.non_terminal.values()\n\n    def __eq__(self, other):\n        return self.raw == other.raw\n\n    def __hash__(self):\n        return struct.unpack('<I', ''.join([chr(v) for v in self.raw[:4]]))[0]\n\n\n# Operation nodes processed so far.\nprocessed_nodes = []\n\n# Number of regular expressions.\nnum_regex = 0\n\n# Operation nodes offset.\noperations_offset = 0\n\n\ndef has_been_processed(node):\n    global processed_nodes\n    return node in processed_nodes\n\n\ndef build_operation_node(raw, offset, ios_major_version):\n    global operations_offset\n    node = OperationNode((offset - operations_offset) / 8) # why offset / 8 ?\n    node.raw = raw\n    node.parse_raw(ios_major_version)\n    return node\n\n\ndef build_operation_nodes(f, num_operation_nodes, ios_major_version):\n    global operations_offset\n    operation_nodes = []\n\n    if ios_major_version <= 12:\n        operations_offset = 0\n    else:\n        operations_offset = f.tell()\n    for i in range(num_operation_nodes):\n        offset = f.tell()\n        raw = struct.unpack(\"<8B\", f.read(8))\n        operation_nodes.append(build_operation_node(raw, offset,\n            ios_major_version))\n\n    # Fill match and unmatch fields for each node in operation_nodes.\n    for i in range(len(operation_nodes)):\n        if operation_nodes[i].is_non_terminal():\n            for j in range(len(operation_nodes)):\n                if operation_nodes[i].non_terminal.match_offset == operation_nodes[j].offset:\n                    operation_nodes[i].non_terminal.match = operation_nodes[j]\n                if operation_nodes[i].non_terminal.unmatch_offset == operation_nodes[j].offset:\n                    operation_nodes[i].non_terminal.unmatch = operation_nodes[j]\n\n    return operation_nodes\n\n\ndef find_operation_node_by_offset(operation_nodes, offset):\n    for node in operation_nodes:\n        if node.offset == offset:\n            return node\n    return None\n\n\ndef ong_mark_not(g, node, parent_node, nodes_to_process):\n    g[node][\"not\"] = True\n    tmp = node.non_terminal.match\n    node.non_terminal.match = node.non_terminal.unmatch\n    node.non_terminal.unmatch = tmp\n    tmp_offset = node.non_terminal.match_offset\n    node.non_terminal.match_offset = node.non_terminal.unmatch_offset\n    node.non_terminal.unmatch_offset = tmp_offset\n\n\ndef ong_end_path(g, node, parent_node, nodes_to_process):\n    g[node][\"decision\"] = str(node.non_terminal.match.terminal)\n    g[node][\"type\"].add(\"final\")\n\n\ndef ong_add_to_path(g, node, parent_node, nodes_to_process):\n    if not has_been_processed(node.non_terminal.match):\n        g[node][\"list\"].add(node.non_terminal.match)\n        nodes_to_process.add((node, node.non_terminal.match))\n\n\ndef ong_add_to_parent_path(g, node, parent_node, nodes_to_process):\n    if not has_been_processed(node.non_terminal.unmatch):\n        if parent_node:\n            g[parent_node][\"list\"].add(node.non_terminal.unmatch)\n        nodes_to_process.add((parent_node, node.non_terminal.unmatch))\n\n\ndef build_operation_node_graph(node, default_node):\n    if node.is_terminal():\n        return None\n\n    if default_node.is_non_terminal():\n        return None\n\n    # If node is non-terminal and has already been processed, then it's a jump rule to a previous operation.\n    if has_been_processed(node):\n        return None\n\n    # Create operation node graph.\n    g = {}\n    nodes_to_process = set()\n    nodes_to_process.add((None, node))\n    while nodes_to_process:\n        (parent_node, current_node) = nodes_to_process.pop()\n        if not current_node in g.keys():\n            g[current_node] = {\"list\": set(), \"decision\": None,\n                \"type\": set([\"normal\"]), \"reduce\": None, \"not\": False}\n        if not parent_node:\n            g[current_node][\"type\"].add(\"start\")\n\n        if default_node.terminal.is_deny():\n            # In case of non-terminal match and deny as unmatch, add match to path.\n            if current_node.non_terminal.is_non_terminal_deny():\n                ong_add_to_path(g, current_node, parent_node, nodes_to_process)\n            # In case of non-terminal match and allow as unmatch, do a not (reverse), end match path and add unmatch to parent path.\n            elif current_node.non_terminal.is_non_terminal_allow():\n                ong_mark_not(g, current_node, parent_node, nodes_to_process)\n                ong_end_path(g, current_node, parent_node, nodes_to_process)\n                ong_add_to_parent_path(g, current_node, parent_node, nodes_to_process)\n            # In case of non-terminals, add match to path and unmatch to parent path.\n            elif current_node.non_terminal.is_non_terminal_non_terminal():\n                ong_add_to_path(g, current_node, parent_node, nodes_to_process)\n                ong_add_to_parent_path(g, current_node, parent_node, nodes_to_process)\n            # In case of allow as match and non-terminal unmatch, end path and add unmatch to parent path.\n            elif current_node.non_terminal.is_allow_non_terminal():\n                ong_end_path(g, current_node, parent_node, nodes_to_process)\n                ong_add_to_parent_path(g, current_node, parent_node, nodes_to_process)\n            # In case of deny as match and non-terminal unmatch, do a not (reverse), and add match to path.\n            elif current_node.non_terminal.is_deny_non_terminal():\n                ong_mark_not(g, current_node, parent_node, nodes_to_process)\n                ong_add_to_path(g, current_node, parent_node, nodes_to_process)\n            # In case of deny as match and allow as unmatch, do a not (reverse), and end match path (completely).\n            elif current_node.non_terminal.is_deny_allow():\n                ong_mark_not(g, current_node, parent_node, nodes_to_process)\n                ong_end_path(g, current_node, parent_node, nodes_to_process)\n            # In case of allow as match and deny as unmatch, end match path (completely).\n            elif current_node.non_terminal.is_allow_deny():\n                ong_end_path(g, current_node, parent_node, nodes_to_process)\n        elif default_node.terminal.is_allow():\n            # In case of non-terminal match and deny as unmatch, do a not (reverse), end match path and add unmatch to parent path.\n            if current_node.non_terminal.is_non_terminal_deny():\n                ong_mark_not(g, current_node, parent_node, nodes_to_process)\n                ong_end_path(g, current_node, parent_node, nodes_to_process)\n                ong_add_to_parent_path(g, current_node, parent_node, nodes_to_process)\n            # In case of non-terminal match and allow as unmatch, add match to path.\n            elif current_node.non_terminal.is_non_terminal_allow():\n                ong_add_to_path(g, current_node, parent_node, nodes_to_process)\n            # In case of non-terminals, add match to path and unmatch to parent path.\n            elif current_node.non_terminal.is_non_terminal_non_terminal():\n                ong_add_to_path(g, current_node, parent_node, nodes_to_process)\n                ong_add_to_parent_path(g, current_node, parent_node, nodes_to_process)\n            # In case of allow as match and non-terminal unmatch, do a not (reverse), and add match to path.\n            elif current_node.non_terminal.is_allow_non_terminal():\n                ong_mark_not(g, current_node, parent_node, nodes_to_process)\n                ong_add_to_path(g, current_node, parent_node, nodes_to_process)\n            # In case of deny as match and non-terminal unmatch, end path and add unmatch to parent path.\n            elif current_node.non_terminal.is_deny_non_terminal():\n                ong_end_path(g, current_node, parent_node, nodes_to_process)\n                ong_add_to_parent_path(g, current_node, parent_node, nodes_to_process)\n            # In case of deny as match and allow as unmatch, end match path (completely).\n            elif current_node.non_terminal.is_deny_allow():\n                ong_end_path(g, current_node, parent_node, nodes_to_process)\n            # In case of allow as match and deny as unmatch, do a not (reverse), and end match path (completely).\n            elif current_node.non_terminal.is_allow_deny():\n                ong_mark_not(g, current_node, parent_node, nodes_to_process)\n                ong_end_path(g, current_node, parent_node, nodes_to_process)\n\n    processed_nodes.append(node)\n    print_operation_node_graph(g)\n    g = clean_edges_in_operation_node_graph(g)\n    while True:\n        (g, more) = clean_nodes_in_operation_node_graph(g)\n        if more == False:\n            break\n    logger.debug(\"*** after cleaning nodes:\")\n    print_operation_node_graph(g)\n\n    return g\n\n\ndef print_operation_node_graph(g):\n    if not g:\n        return\n    message = \"\"\n    for node_iter in g.keys():\n        message += \"0x%x (%s) (%s) (decision: %s): [ \" % ((int)(node_iter.offset), str(node_iter), g[node_iter][\"type\"], g[node_iter][\"decision\"])\n        for edge in g[node_iter][\"list\"]:\n            message += \"0x%x (%s) \" % ((int)(edge.offset), str(edge))\n        message += \"]\\n\"\n    logger.debug(message)\n\n\ndef remove_edge_in_operation_node_graph(g, node_start, node_end):\n    if node_end in g[node_start][\"list\"]:\n        g[node_start][\"list\"].remove(node_end)\n    return g\n\n\ndef remove_node_in_operation_node_graph(g, node_to_remove):\n    for n in g[node_to_remove][\"list\"]:\n        g = remove_edge_in_operation_node_graph(g, node_to_remove, n)\n    node_list = list(g.keys())\n    for n in node_list:\n        if node_to_remove in g[n][\"list\"]:\n            g = remove_edge_in_operation_node_graph(g, n, node_to_remove)\n    del g[node_to_remove]\n    return g\n\n\npaths = []\ncurrent_path = []\n\n\ndef _get_operation_node_graph_paths(g, node):\n    global paths, current_path\n    logger.debug(\"getting path for \" + node.str_debug())\n    current_path.append(node)\n    debug_message = \"current_path: [ \"\n    for n in current_path:\n        debug_message += n.str_debug() + \", \"\n    debug_message += \"]\"\n    logger.debug(debug_message)\n    if \"final\" in g[node][\"type\"]:\n        copy_path = list(current_path)\n        paths.append(copy_path)\n    else:\n        for next_node in g[node][\"list\"]:\n            _get_operation_node_graph_paths(g, next_node)\n    current_path.pop()\n\n\ndef get_operation_node_graph_paths(g, start_node):\n    global paths, current_path\n    paths = []\n    current_path = []\n    _get_operation_node_graph_paths(g, start_node)\n    return paths\n\n\nnodes_traversed_for_removal = []\ndef _remove_duplicate_node_edges(g, node, start_list):\n    global nodes_traversed_for_removal\n    nodes_traversed_for_removal.append(node)\n\n    nexts = list(g[node][\"list\"])\n    for n in nexts:\n        if n in start_list:\n            g = remove_edge_in_operation_node_graph(g, node, n)\n        else:\n            if not n in nodes_traversed_for_removal:\n                _remove_duplicate_node_edges(g, n, start_list)\n\n\ndef remove_duplicate_node_edges(g, start_list):\n    for n in start_list:\n        logger.debug(\"removing from node: \" + n.str_debug())\n        _remove_duplicate_node_edges(g, n, start_list)\n\n\ndef clean_edges_in_operation_node_graph(g):\n    \"\"\"From the initial graph remove edges that are redundant.\n    \"\"\"\n    global nodes_traversed_for_removal\n    start_nodes = []\n    final_nodes = []\n    for node_iter in g.keys():\n        if \"start\" in g[node_iter][\"type\"]:\n            start_nodes.append(node_iter)\n        if \"final\" in g[node_iter][\"type\"]:\n            final_nodes.append(node_iter)\n\n    # Remove edges to start nodes.\n    for snode in start_nodes:\n        for node_iter in g.keys():\n            g = remove_edge_in_operation_node_graph(g, node_iter, snode)\n\n    for snode in start_nodes:\n        nodes_bag = [ snode ]\n        while True:\n            node = nodes_bag.pop()\n            nodes_traversed_for_removal = []\n            logger.debug(\"%%% going through \" + node.str_debug())\n            remove_duplicate_node_edges(g, g[node][\"list\"])\n            nodes_bag.extend(g[node][\"list\"])\n            if not nodes_bag:\n                break\n\n    # Traverse graph and built all paths. If end node and start node of\n    # two or more paths are similar, remove edges.\n    for snode in start_nodes:\n        logger.debug(\"traversing node \" + str(snode))\n        paths = get_operation_node_graph_paths(g, snode)\n        debug_message = \"for start node \" + str(snode) + str(\" paths are\")\n        for p in paths:\n            debug_message += \"[ \"\n            for n in p:\n                debug_message += n.str_debug() + \" \"\n            debug_message += \"]\\n\"\n        logger.debug(debug_message)\n\n        for i in range(0, len(paths)):\n            for j in range(i+1, len(paths)):\n                # Step over equal length paths.\n                if len(paths[i]) == len(paths[j]):\n                    continue\n                elif len(paths[i]) < len(paths[j]):\n                    p = paths[i]\n                    q = paths[j]\n                else:\n                    p = paths[j]\n                    q = paths[i]\n                # If similar final nodes, remove edge.\n                debug_message = \"\"\n                debug_message += \"short path: [\"\n                for n in p:\n                    debug_message += str(n)\n                debug_message += \"]\\n\"\n                debug_message += \"long path: [\"\n                for n in q:\n                    debug_message += str(n)\n                debug_message += \"]\"\n                if p[len(p)-1] == q[len(q)-1]:\n                    for k in range(0, len(p)):\n                        if p[len(p)-1-k] == q[len(q)-1-k]:\n                            continue\n                        else:\n                            g = remove_edge_in_operation_node_graph(g, q[len(q)-1-k], q[len(q)-k])\n                            break\n\n\n    return g\n\n\ndef clean_nodes_in_operation_node_graph(g):\n    made_change = False\n    node_list = list(g.keys())\n    for node_iter in node_list:\n        if \"final\" in g[node_iter][\"type\"]:\n            continue\n        if g[node_iter][\"list\"]:\n            continue\n        logger.warn(\"going to remove\" + str(node_iter))\n        made_change = True\n        g = remove_node_in_operation_node_graph(g, node_iter)\n    return (g, made_change)\n\n\nreplace_occurred = False\n\nclass ReducedVertice():\n    TYPE_SINGLE = \"single\"\n    TYPE_START = \"start\"\n    TYPE_REQUIRE_ANY = \"require-any\"\n    TYPE_REQUIRE_ALL = \"require-all\"\n    TYPE_REQUIRE_ENTITLEMENT = \"require-entitlement\"\n    type = TYPE_SINGLE\n    is_not = False\n    value = None\n    decision = None\n\n    def __init__(self, type=TYPE_SINGLE, value=None, decision=None, is_not=False):\n        self.type = type\n        self.value = value\n        self.decision = decision\n        self.is_not = is_not\n\n    def set_value(self, value):\n        self.value = value\n\n    def set_type(self, type):\n        self.type = type\n\n    def _replace_in_list(self, lst, old, new):\n        global replace_occurred\n        tmp_list = list(lst)\n        for i, v in enumerate(tmp_list):\n            if isinstance(v.value, list):\n                self._replace_in_list(v.value, old, new)\n            else:\n                if v == old:\n                    lst[i] = new\n                    replace_occurred = True\n                    return\n\n    def replace_in_list(self, old, new):\n        if isinstance(self.value, list):\n            self._replace_in_list(self.value, old, new)\n\n    def _replace_sublist_in_list(self, lst, old, new):\n        global replace_occurred\n        all_found = True\n        for v in old:\n            if v not in lst:\n                all_found = False\n                break\n        if all_found:\n            for v in old:\n                lst.remove(v)\n            lst.append(new)\n            replace_occurred = True\n            return\n\n        for i, v in enumerate(lst):\n            if isinstance(v.value, list):\n                self._replace_sublist_in_list(v.value, old, new)\n            else:\n                return\n\n    def replace_sublist_in_list(self, old, new):\n        if isinstance(self.value, list):\n            self._replace_sublist_in_list(self.value, old, new)\n\n    def set_decision(self, decision):\n        self.decision = decision\n\n    def set_type_single(self):\n        self.type = self.TYPE_SINGLE\n\n    def set_type_start(self):\n        self.type = self.TYPE_START\n\n    def set_type_require_entitlement(self):\n        self.type = self.TYPE_REQUIRE_ENTITLEMENT\n\n    def set_type_require_any(self):\n        self.type = self.TYPE_REQUIRE_ANY\n\n    def set_type_require_all(self):\n        self.type = self.TYPE_REQUIRE_ALL\n\n    def set_integrated_vertice(self, integrated_vertice):\n        (n, i) = self.value\n        self.value = (n, integrated_vertice)\n\n    def is_type_single(self):\n        return self.type == self.TYPE_SINGLE\n\n    def is_type_start(self):\n        return self.type == self.TYPE_START\n\n    def is_type_require_entitlement(self):\n        return self.type == self.TYPE_REQUIRE_ENTITLEMENT\n\n    def is_type_require_all(self):\n        return self.type == self.TYPE_REQUIRE_ALL\n\n    def is_type_require_any(self):\n        return self.type == self.TYPE_REQUIRE_ANY\n\n    def recursive_str(self, level, recursive_is_not):\n        result_str = \"\"\n        if self.is_type_single():\n            if self.is_not and not recursive_is_not:\n                value = str(self.value)\n                if \"(require-any\" in value:\n                    result_str = self.value.str_not()\n                else:\n                    result_str += \"(require-not \" + str(self.value) + \")\"\n            else:\n                result_str += str(self.value)\n        elif self.is_type_require_entitlement():\n            ent_str = \"\"\n            (n, i) = self.value\n            if i == None:\n                ent_str += str(n.value)\n            else:\n                ent_str += str(n.value)[:-1] + \" \"\n                ent_str += i.recursive_str(level, self.is_not)\n                ent_str += \")\"\n            if self.is_not:\n                result_str += \"(require-not \" + ent_str + \")\"\n            else:\n                result_str += ent_str\n        else:\n            if level == 1:\n                result_str += \"\\n\" + 13*' '\n            result_str += \"(\" + self.type\n            level += 1\n            for i, v in enumerate(self.value):\n                if i == 0:\n                    result_str += \" \" + v.recursive_str(level, recursive_is_not)\n                else:\n                    result_str += \"\\n\" + 13*level*' ' + v.recursive_str(level, recursive_is_not)\n            result_str += \")\"\n        return result_str\n\n    def recursive_str_debug(self, level, recursive_is_not):\n        result_str = \"\"\n        if self.is_type_single():\n            if self.is_not and not recursive_is_not:\n                result_str += \"(require-not \" + self.value.str_debug() + \")\"\n            else:\n                result_str += self.value.str_debug()\n        elif self.is_type_require_entitlement():\n            ent_str = \"\"\n            (n, i) = self.value\n            if i == None:\n                ent_str += n.value.str_debug()\n            else:\n                ent_str += n.value.str_debug()[:-1] + \" \"\n                ent_str += i.recursive_str_debug(level, self.is_not)\n                ent_str += \")\"\n            if self.is_not:\n                result_str += \"(require-not \" + ent_str + \")\"\n            else:\n                result_str += ent_str\n        else:\n            if level == 1:\n                result_str += \"\\n\" + 13*' '\n            result_str += \"(\" + self.type\n            level += 1\n            for i, v in enumerate(self.value):\n                if i == 0:\n                    result_str += \" \" + v.recursive_str_debug(level, recursive_is_not)\n                else:\n                    result_str += \"\\n\" + 13*level*' ' + v.recursive_str_debug(level, recursive_is_not)\n            result_str += \")\"\n        return result_str\n\n    def recursive_xml_str(self, level, recursive_is_not):\n        result_str = \"\"\n        if self.is_type_single():\n            if self.is_not and not recursive_is_not:\n                result_str += level*\"\\t\" + \"<require type=\\\"require-not\\\">\\n\"\n                (name, argument) = self.value.values()\n                if argument == None:\n                    result_str += (level+1)*\"\\t\" + \"<filter name=\\\"\" + str(name) + \"\\\" />\\n\"\n                else:\n                    arg = str(argument).replace('&', '&amp;').replace('\"', '&quot;').replace('\\'', '&apos;').replace('<', '&lt;').replace('>', '&gt;')\n                    result_str += (level+1)*\"\\t\" + \"<filter name=\\\"\" + str(name) + \"\\\" argument=\\\"\" + arg + \"\\\" />\\n\"\n                result_str += level*\"\\t\" + \"</require>\\n\"\n            else:\n                (name, argument) = self.value.values()\n                if argument == None:\n                    result_str += level*\"\\t\" + \"<filter name=\\\"\" + str(name) + \"\\\" />\\n\"\n                else:\n                    arg = str(argument).replace('&', '&amp;').replace('\"', '&quot;').replace('\\'', '&apos;').replace('<', '&lt;').replace('>', '&gt;')\n                    result_str += level*\"\\t\" + \"<filter name=\\\"\" + str(name) + \"\\\" argument=\\\"\" + arg + \"\\\" />\\n\"\n        elif self.is_type_require_entitlement():\n            if self.is_not:\n                result_str += level*\"\\t\" + \"<require type=\\\"require-not\\\">\\n\"\n                level += 1\n            result_str += level*\"\\t\" + \"<require type=\\\"require-entitlement\\\"\"\n            (n, i) = self.value\n            if i == None:\n                _tmp = str(n.value)[21:-1].replace('&', '&amp;').replace('\"', '&quot;').replace('\\'', '&apos;').replace('<', '&lt;').replace('>', '&gt;')\n                result_str += \" value=\\\"\" + _tmp + \"\\\" />\\n\"\n            else:\n                _tmp = str(n.value)[21:-1].replace('&', '&amp;').replace('\"', '&quot;').replace('\\'', '&apos;').replace('<', '&lt;').replace('>', '&gt;')\n                result_str += \" value=\\\"\" + _tmp + \"\\\">\\n\"\n                result_str += i.recursive_xml_str(level+1, self.is_not)\n                result_str += level*\"\\t\" + \"</require>\\n\"\n            if self.is_not:\n                level -= 1\n                result_str += level*\"\\t\" + \"</require>\\n\"\n        else:\n            result_str += level*\"\\t\" + \"<require type=\\\"\" + self.type + \"\\\">\\n\"\n            for i, v in enumerate(self.value):\n                result_str += v.recursive_xml_str(level+1, recursive_is_not)\n            result_str += level*\"\\t\" + \"</require>\\n\"\n        return result_str\n\n    def __str__(self):\n        return self.recursive_str(1, False)\n\n    def str_debug(self):\n        return self.recursive_str_debug(1, False)\n\n    def str_simple(self):\n        if self.is_type_single():\n            return self.value.str_debug()\n        elif self.is_type_require_any():\n            return \"require-any\"\n        elif self.is_type_require_all():\n            return \"require-all\"\n        elif self.is_type_require_entitlement():\n            return self.value.str_debug()[1:-1]\n        elif self.is_type_start():\n            return \"start\"\n        else:\n            return \"unknown-type\"\n\n    def str_print_debug(self):\n        if self.is_type_single():\n            return (self.value.str_debug(), None)\n        elif self.is_type_require_any():\n            return (\"(require-any\", \")\")\n        elif self.is_type_require_all():\n            return (\"(require-all\", \")\")\n        elif self.is_type_require_entitlement():\n            return (self.value.str_debug()[:-1], \")\")\n        elif self.is_type_start():\n            return (None, None)\n        else:\n            return (\"unknown-type\", None)\n\n    def str_print(self):\n        if self.is_type_single():\n            return (str(self.value), None)\n        elif self.is_type_require_any():\n            return (\"(require-any\", \")\")\n        elif self.is_type_require_all():\n            return (\"(require-all\", \")\")\n        elif self.is_type_require_entitlement():\n            return (str(self.value)[:-1], \")\")\n        elif self.is_type_start():\n            return (None, None)\n        else:\n            return (\"unknown-type\", None)\n\n    def str_print_not(self):\n        result_str = \"\"\n        if self.is_type_single():\n            if self.is_not:\n                value = str(self.value)\n                if \"(require-any\" in value:\n                    result_str = self.value.str_not()\n                else:\n                    result_str += \"(require-not \" + str(self.value) + \")\"\n        return result_str\n\n    def xml_str(self):\n        return self.recursive_xml_str(3, False)\n\n\nclass ReducedEdge():\n    start = None\n    end = None\n\n    def __init__(self, start=None, end=None):\n        self.start = start\n        self.end = end\n\n    def str_debug(self):\n        return self.start.str_debug() + \" -> \" + self.end.str_debug()\n\n    def str_simple(self):\n        #print \"start: %s\" % (self.start.str_simple())\n        #print \"end: %s\" % (self.end.str_simple())\n        return \"%s -----> %s\" % (self.start.str_simple(), self.end.str_simple())\n\n    def __str__(self):\n        return str(self.start) + \" -> \" + str(self.end)\n\n\nclass ReducedGraph():\n    vertices = []\n    edges = []\n    final_vertices = []\n    reduce_changes_occurred = False\n\n    def __init__(self):\n        self.vertices = []\n        self.edges = []\n        self.final_vertices = []\n        self.reduce_changes_occurred = False\n\n    def add_vertice(self, v):\n        self.vertices.append(v)\n\n    def add_edge(self, e):\n        self.edges.append(e)\n\n    def add_edge_by_vertices(self, v_start, v_end):\n        e = ReducedEdge(v_start, v_end)\n        self.edges.append(e)\n\n    def set_final_vertices(self):\n        self.final_vertices = []\n        for v in self.vertices:\n            is_final = True\n            for e in self.edges:\n                if v == e.start:\n                    is_final = False\n                    break\n            if is_final:\n                self.final_vertices.append(v)\n\n    def contains_vertice(self, v):\n        return v in self.vertices\n\n    def contains_edge(self, e):\n        return e in self.edges\n\n    def contains_edge_by_vertices(self, v_start, v_end):\n        for e in self.edges:\n            if e.start == v_start and e.end == v_end:\n                return True\n        return False\n\n    def get_vertice_by_value(self, value):\n        for v in self.vertices:\n            if v.is_type_single():\n                if v.value == value:\n                    return v\n\n    def get_edge_by_vertices(self, v_start, v_end):\n        for e in self.edges:\n            if e.start == v_start and e.end == v_end:\n                return e\n        return None\n\n    def remove_vertice(self, v):\n        edges_copy = list(self.edges)\n        for e in edges_copy:\n            if e.start == v or e.end == v:\n                self.edges.remove(e)\n        if v in self.vertices:\n            self.vertices.remove(v)\n\n    def remove_vertice_update_decision(self, v):\n        edges_copy = list(self.edges)\n        for e in edges_copy:\n            if e.start == v:\n                self.edges.remove(e)\n            if e.end == v:\n                e.start.decision = v.decision\n                self.edges.remove(e)\n        if v in self.vertices:\n            self.vertices.remove(v)\n\n    def remove_edge(self, e):\n        if e in self.edges:\n            self.edges.remove(e)\n\n    def remove_edge_by_vertices(self, v_start, v_end):\n        e = self.get_edge_by_vertices(v_start, v_end)\n        if e:\n            self.edges.remove(e)\n\n    def replace_vertice_in_edge_start(self, old, new):\n        global replace_occurred\n        for e in self.edges:\n            if e.start == old:\n                e.start = new\n                replace_occurred = True\n            else:\n                if isinstance(e.start.value, list):\n                    e.start.replace_in_list(old, new)\n                    if replace_occurred:\n                        e.start.decision = new.decision\n\n    def replace_vertice_in_edge_end(self, old, new):\n        global replace_occurred\n        for e in self.edges:\n            if e.end == old:\n                e.end = new\n                replace_occurred = True\n            else:\n                if isinstance(e.end.value, list):\n                    e.end.replace_in_list(old, new)\n                    if replace_occurred:\n                        e.end.decision = new.decision\n\n    def replace_vertice_in_single_vertices(self, old, new):\n        for v in self.vertices:\n            if len(self.get_next_vertices(v)) == 0 and len(self.get_prev_vertices(v)) == 0:\n                if isinstance(v.value, list):\n                    v.replace_in_list(old, new)\n\n    def replace_vertice_list(self, old, new):\n        for v in self.vertices:\n            if isinstance(v.value, list):\n                v.replace_sublist_in_list(old, new)\n            if set(self.get_next_vertices(v)) == set(old):\n                for n in old:\n                    self.remove_edge_by_vertices(v, n)\n                self.add_edge_by_vertices(v, new)\n            if set(self.get_prev_vertices(v)) == set(old):\n                for n in old:\n                    self.remove_edge_by_vertices(n, v)\n                self.add_edge_by_vertices(new, v)\n\n    def get_next_vertices(self, v):\n        next_vertices = []\n        for e in self.edges:\n            if e.start == v:\n                next_vertices.append(e.end)\n        return next_vertices\n\n    def get_prev_vertices(self, v):\n        prev_vertices = []\n        for e in self.edges:\n            if e.end == v:\n                prev_vertices.append(e.start)\n        return prev_vertices\n\n    def get_start_vertices(self):\n        start_vertices = []\n        for v in self.vertices:\n            if not self.get_prev_vertices(v):\n                start_vertices.append(v)\n        return start_vertices\n\n    def get_end_vertices(self):\n        end_vertices = []\n        for v in self.vertices:\n            if not self.get_next_vertices(v):\n                end_vertices.append(v)\n        return end_vertices\n\n    def reduce_next_vertices(self, v):\n        next_vertices = self.get_next_vertices(v)\n        if len(next_vertices) <= 1:\n            return\n        self.reduce_changes_occurred = True\n        new_vertice = ReducedVertice(\"require-any\", next_vertices, next_vertices[0].decision)\n        add_to_final = False\n        for n in next_vertices:\n            self.remove_edge_by_vertices(v, n)\n        self.replace_vertice_list(next_vertices, new_vertice)\n        for n in next_vertices:\n            if n in self.final_vertices:\n                self.final_vertices.remove(n)\n                add_to_final = True\n            # If no more next vertices, remove vertice.\n            if not self.get_next_vertices(n):\n                if n in self.vertices:\n                    self.vertices.remove(n)\n        self.add_edge_by_vertices(v, new_vertice)\n        self.add_vertice(new_vertice)\n        if add_to_final:\n            self.final_vertices.append(new_vertice)\n\n    def reduce_prev_vertices(self, v):\n        prev_vertices = self.get_prev_vertices(v)\n        if len(prev_vertices) <= 1:\n            return\n        self.reduce_changes_occurred = True\n        new_vertice = ReducedVertice(\"require-any\", prev_vertices, v.decision)\n        for p in prev_vertices:\n            self.remove_edge_by_vertices(p, v)\n        self.replace_vertice_list(prev_vertices, new_vertice)\n        for p in prev_vertices:\n            # If no more prev vertices, remove vertice.\n            if not self.get_prev_vertices(p):\n                if p in self.vertices:\n                    self.vertices.remove(p)\n        self.add_vertice(new_vertice)\n        self.add_edge_by_vertices(new_vertice, v)\n\n    def reduce_vertice_single_prev(self, v):\n        global replace_occurred\n        prev = self.get_prev_vertices(v)\n        if len(prev) != 1:\n            logger.debug(\"not a single prev for node\")\n            return\n        p = prev[0]\n        nexts = self.get_next_vertices(p)\n        if len(nexts) > 1 or nexts[0] != v:\n            logger.debug(\"multiple nexts for prev\")\n            return\n        require_all_vertices = []\n        if p.is_type_require_all():\n            require_all_vertices.extend(p.value)\n        else:\n            require_all_vertices.append(p)\n        if v.is_type_require_all():\n            require_all_vertices.extend(v.value)\n        else:\n            require_all_vertices.append(v)\n        new_vertice = ReducedVertice(\"require-all\", require_all_vertices, v.decision)\n        self.remove_edge_by_vertices(p, v)\n        replace_occurred = False\n        self.replace_vertice_in_edge_start(v, new_vertice)\n        self.replace_vertice_in_edge_end(p, new_vertice)\n        self.replace_vertice_in_single_vertices(p, new_vertice)\n        self.replace_vertice_in_single_vertices(v, new_vertice)\n        self.remove_vertice(p)\n        self.remove_vertice(v)\n        if not replace_occurred:\n            self.add_vertice(new_vertice)\n        if v in self.final_vertices:\n            self.final_vertices.remove(v)\n            self.final_vertices.append(new_vertice)\n\n    def reduce_vertice_single_next(self, v):\n        global replace_occurred\n        next = self.get_next_vertices(v)\n        if len(next) != 1:\n            return\n        n = next[0]\n        prevs = self.get_prev_vertices(n)\n        if len(prevs) > 1 or prevs[0] != v:\n            return\n        require_all_vertices = []\n        if v.is_type_require_all():\n            require_all_vertices.extend(v.value)\n        else:\n            require_all_vertices.append(v)\n        if n.is_type_require_all():\n            require_all_vertices.extend(n.value)\n        else:\n            require_all_vertices.append(n)\n        new_vertice = ReducedVertice(\"require-all\", require_all_vertices, n.decision)\n        self.remove_edge_by_vertices(v, n)\n        replace_occurred = False\n        self.replace_vertice_in_edge_start(n, new_vertice)\n        self.replace_vertice_in_edge_end(e, new_vertice)\n        self.replace_vertice_in_single_vertices(v, new_vertice)\n        self.replace_vertice_in_single_vertices(n, new_vertice)\n        self.remove_vertice(v)\n        self.remove_vertice(n)\n        if not replace_occurred:\n            self.add_vertice(new_vertice)\n        if n in self.final_vertices:\n            self.final_vertices.remove(n)\n            self.final_vertices.append(new_vertice)\n\n    def reduce_graph(self):\n        self.set_final_vertices()\n\n        logger.debug(\"before everything:\\n\" + self.str_simple())\n        # Do until no more changes.\n        while True:\n            self.reduce_changes_occurred = False\n            copy_vertices = list(self.vertices)\n            for v in copy_vertices:\n                self.reduce_next_vertices(v)\n            if self.reduce_changes_occurred == False:\n                break\n        logger.debug(\"after next:\\n\" + self.str_simple())\n        # Do until no more changes.\n        while True:\n            self.reduce_changes_occurred = False\n            copy_vertices = list(self.vertices)\n            for v in copy_vertices:\n                self.reduce_prev_vertices(v)\n            if self.reduce_changes_occurred == False:\n                break\n        logger.debug(\"after next/prev:\\n\" + self.str_simple())\n\n        # Reduce graph starting from final vertices. Keep going until\n        # final vertices don't change during an iteration.\n        while True:\n            copy_final_vertices = list(self.final_vertices)\n            for v in copy_final_vertices:\n                logger.debug(\"reducing single prev vertex: \" + v.str_debug())\n                self.reduce_vertice_single_prev(v)\n                logger.debug(\"### new graph is:\")\n                logger.debug(self.str_simple())\n            if set(copy_final_vertices) == set(self.final_vertices):\n                break\n        for e in self.edges:\n            v = e.end\n            logger.debug(\"reducing single prev vertex: \" + v.str_debug())\n            self.reduce_vertice_single_prev(v)\n        logger.debug(\"after everything:\\n\" + self.str_simple())\n\n    def reduce_graph_with_metanodes(self):\n        # Add require-any metanode if current node has multiple successors.\n        copy_vertices = list(self.vertices)\n        for v in copy_vertices:\n            nlist = self.get_next_vertices(v)\n            if len(nlist) >= 2:\n                new_node = ReducedVertice(\"require-any\", None, None)\n                self.add_vertice(new_node)\n                self.add_edge_by_vertices(v, new_node)\n                for n in nlist:\n                    self.remove_edge_by_vertices(v, n)\n                    self.add_edge_by_vertices(new_node, n)\n\n        start_list = self.get_start_vertices()\n        new_node = ReducedVertice(\"start\", None, None)\n        self.add_vertice(new_node)\n        for s in start_list:\n            self.add_edge_by_vertices(new_node, s)\n\n        # Add require-all metanode if current node has a require-any as a predecessor and is followed by another node.\n        copy_vertices = list(self.vertices)\n        for v in copy_vertices:\n            prev_vertices = list(self.get_prev_vertices(v))\n            next_vertices = list(self.get_next_vertices(v))\n            for p in prev_vertices:\n                if (p.is_type_require_any() or p.is_type_start()) and next_vertices:\n                    # Except for when a require-entitlement ending block.\n                    if v.is_type_require_entitlement():\n                        has_next_nexts = False\n                        for n in next_vertices:\n                            if n.is_type_require_any():\n                                for n2 in self.get_next_vertices(n):\n                                    if self.get_next_vertices(n2):\n                                        has_next_nexts = True\n                                        break\n                            else:\n                                if self.get_next_vertices(n):\n                                    has_next_nexts = True\n                                    break\n                        if not has_next_nexts:\n                            continue\n                    new_node = ReducedVertice(\"require-all\", None, None)\n                    self.add_vertice(new_node)\n                    self.remove_edge_by_vertices(p, v)\n                    self.add_edge_by_vertices(p, new_node)\n                    self.add_edge_by_vertices(new_node, v)\n\n    def str_simple_with_metanodes(self):\n        logger.debug(\"==== vertices:\\n\")\n        for v in self.vertices:\n            logger.debug(v.str_simple())\n        logger.debug(\"==== edges:\\n\")\n        for e in self.edges:\n            logger.debug(e.str_simple())\n\n    def str_simple(self):\n        message = \"==== vertices:\\n\"\n        for v in self.vertices:\n            message += \"decision: \" + str(v.decision) + \"\\t\" + v.str_debug() + \"\\n\"\n        message += \"==== final vertices:\\n\"\n        for v in self.final_vertices:\n            message += \"decision: \" + str(v.decision) + \"\\t\" + v.str_debug() + \"\\n\"\n        message += \"==== edges:\\n\"\n        for e in self.edges:\n            message += \"\\t\" + e.str_debug() + \"\\n\"\n        return message\n\n    def __str__(self):\n        result_str = \"\"\n        for v in self.vertices:\n            result_str += \"(\" + str(v.decision) + \" \"\n            if len(self.get_next_vertices(v)) == 0 and len(self.get_next_vertices(v)) == 0:\n                if v in self.final_vertices:\n                    result_str += str(v) + \"\\n\"\n            result_str += \")\\n\"\n        for e in self.edges:\n            result_str += str(e) + \"\\n\"\n        result_str += \"\\n\"\n        return result_str\n\n    def remove_builtin_filters(self):\n        copy_vertices = list(self.vertices)\n        for v in copy_vertices:\n            if re.search(\"###\\$\\$\\$\\*\\*\\*\", str(v)):\n                self.remove_vertice_update_decision(v)\n\n    def reduce_integrated_vertices(self, integrated_vertices):\n        if len(integrated_vertices) == 0:\n            return (None, None)\n        if len(integrated_vertices) > 1:\n            return (ReducedVertice(\"require-any\", integrated_vertices, integrated_vertices[0].decision), integrated_vertices[0].decision)\n        require_all_vertices = []\n        v = integrated_vertices[0]\n        decision = None\n        while True:\n            if not re.search(\"entitlement-value #t\", str(v)):\n                require_all_vertices.append(v)\n            next_vertices = self.get_next_vertices(v)\n            if decision == None and v.decision != None:\n                decision = v.decision\n            self.remove_vertice(v)\n            if v in self.final_vertices:\n                self.final_vertices.remove(v)\n            if next_vertices:\n                v = next_vertices[0]\n            else:\n                break\n        if len(require_all_vertices) == 0:\n            return (None, v.decision)\n        if len(require_all_vertices) == 1:\n            return (ReducedVertice(value=require_all_vertices[0].value, decision=require_all_vertices[0].decision, is_not=require_all_vertices[0].is_not), v.decision)\n        return (ReducedVertice(\"require-all\", require_all_vertices, require_all_vertices[len(require_all_vertices)-1].decision), v.decision)\n\n    def aggregate_require_entitlement(self, v):\n        next_vertices = []\n        prev_vertices = self.get_prev_vertices(v)\n        integrated_vertices = []\n        for n in self.get_next_vertices(v):\n            if not re.search(\"entitlement-value\", str(n)):\n                next_vertices.append(n)\n                break\n            integrated_vertices.append(n)\n            current_list = [ n ]\n            while current_list:\n                current = current_list.pop()\n                for n2 in self.get_next_vertices(current):\n                    if not re.search(\"entitlement-value\", str(n2)):\n                        self.remove_edge_by_vertices(current, n2)\n                        next_vertices.append(n2)\n                    else:\n                        current_list.append(n2)\n        new_vertice = ReducedVertice(type=\"require-entitlement\", value=(v, None), decision=None, is_not=v.is_not)\n        for p in prev_vertices:\n            self.remove_edge_by_vertices(p, v)\n            self.add_edge_by_vertices(p, new_vertice)\n        for n in next_vertices:\n            self.remove_edge_by_vertices(v, n)\n            self.add_edge_by_vertices(new_vertice, n)\n        for i in integrated_vertices:\n            self.remove_edge_by_vertices(v, i)\n        self.remove_vertice(v)\n        self.add_vertice(new_vertice)\n        if v in self.final_vertices:\n            self.final_vertices.remove(v)\n            self.final_vertices.append(new_vertice)\n        (new_integrate, decision) = self.reduce_integrated_vertices(integrated_vertices)\n        for i in integrated_vertices:\n            self.remove_vertice(i)\n            if i in self.final_vertices:\n                self.final_vertices.remove(i)\n        new_vertice.set_integrated_vertice(new_integrate)\n        new_vertice.set_decision(decision)\n\n    def aggregate_require_entitlement_nodes(self):\n        copy_vertices = list(self.vertices)\n        idx = 0\n        while idx < len(copy_vertices):\n            v = copy_vertices[idx]\n            if re.search(\"require-entitlement\", str(v)):\n                self.aggregate_require_entitlement(v)\n            idx += 1\n\n    def cleanup_filters(self):\n        self.remove_builtin_filters()\n        self.aggregate_require_entitlement_nodes()\n\n    def remove_builtin_filters_with_metanodes(self):\n        copy_vertices = list(self.vertices)\n        for v in copy_vertices:\n            if re.search(\"###\\$\\$\\$\\*\\*\\*\", v.str_simple()):\n                self.remove_vertice(v)\n            elif re.search(\"entitlement-value #t\", v.str_simple()):\n                self.remove_vertice(v)\n            elif re.search(\"entitlement-value-regex #\\\"\\.\\\"\", v.str_simple()):\n                v.value.non_terminal.argument = \"#\\\".+\\\"\"\n            elif re.search(\"global-name-regex #\\\"\\.\\\"\", v.str_simple()):\n                v.value.non_terminal.argument = \"#\\\".+\\\"\"\n            elif re.search(\"local-name-regex #\\\"\\.\\\"\", v.str_simple()):\n                v.value.non_terminal.argument = \"#\\\".+\\\"\"\n\n    def replace_require_entitlement_with_metanodes(self, v):\n        prev_list = self.get_prev_vertices(v)\n        next_list = self.get_next_vertices(v)\n        new_node = ReducedVertice(type=\"require-entitlement\", value=v.value, decision=None, is_not=v.is_not)\n        self.add_vertice(new_node)\n        self.remove_vertice(v)\n        for p in prev_list:\n            self.add_edge_by_vertices(p, new_node)\n        for n in next_list:\n            self.add_edge_by_vertices(new_node, n)\n\n    def aggregate_require_entitlement_with_metanodes(self):\n        copy_vertices = list(self.vertices)\n        for v in copy_vertices:\n            if re.search(\"require-entitlement\", str(v)):\n                self.replace_require_entitlement_with_metanodes(v)\n\n    def cleanup_filters_with_metanodes(self):\n        self.remove_builtin_filters_with_metanodes()\n        self.aggregate_require_entitlement_with_metanodes()\n\n    def print_vertices_with_operation(self, operation, out_f):\n        allow_vertices = [v for v in self.vertices if v.decision == \"allow\"]\n        deny_vertices = [v for v in self.vertices if v.decision == \"deny\"]\n        if allow_vertices:\n            out_f.write(\"(allow %s \" % (operation))\n            if len(allow_vertices) > 1:\n                for v in allow_vertices:\n                    out_f.write(\"\\n\" + 8*\" \" + str(v))\n            else:\n                out_f.write(str(allow_vertices[0]))\n            out_f.write(\")\\n\")\n        if deny_vertices:\n            out_f.write(\"(deny %s \" % (operation))\n            if len(deny_vertices) > 1:\n                for v in deny_vertices:\n                    out_f.write(\"\\n\" + 8*\" \" + str(v))\n            else:\n                out_f.write(str(deny_vertices[0]))\n            out_f.write(\")\\n\")\n\n    def print_vertices_with_operation_metanodes(self, operation, default_is_allow, out_f):\n        # Return if only start node in list.\n        if len(self.vertices) == 1 and self.vertices[0].is_type_start():\n            return\n        # Use reverse of default rule.\n        if default_is_allow:\n            out_f.write(\"(deny %s\" % (operation))\n        else:\n            out_f.write(\"(allow %s\" % (operation))\n        vlist = []\n        start_list = self.get_start_vertices()\n        start_list.reverse()\n        vlist.insert(0, (None, 0))\n        for s in start_list:\n            vlist.insert(0, (s, 1))\n        while True:\n            if not vlist:\n                break\n            (cnode, indent) = vlist.pop(0)\n            if not cnode:\n                out_f.write(\")\")\n                continue\n            (first, last) = cnode.str_print()\n            if first:\n                if cnode.is_not:\n                    if cnode.str_print_not() != \"\":\n                        out_f.write(\"\\n\" + indent * \"\\t\" + cnode.str_print_not())\n                    else:\n                        out_f.write(\"\\n\" + indent * \"\\t\" + \"(require-not \" + first)\n                        if cnode.is_type_require_any() or cnode.is_type_require_all() or cnode.is_type_require_entitlement():\n                            vlist.insert(0, (None, indent))\n                        else:\n                            out_f.write(\")\")\n                else:\n                    out_f.write(\"\\n\" + indent * \"\\t\" + first)\n            if last:\n                vlist.insert(0, (None, indent))\n            next_vertices_list = self.get_next_vertices(cnode)\n            if next_vertices_list:\n                if cnode.is_type_require_any() or cnode.is_type_require_all() or cnode.is_type_require_entitlement():\n                    indent += 1\n                next_vertices_list.reverse()\n                if cnode.is_type_require_entitlement():\n                    pos = 0\n                    for n in next_vertices_list:\n                        if (n.is_type_single() and not re.search(\"entitlement-value\", n.str_simple())) or \\\n                                n.is_type_require_entitlement():\n                            vlist.insert(pos + 1, (n, indent-1))\n                        else:\n                            vlist.insert(0, (n, indent))\n                            pos += 1\n                else:\n                    for n in next_vertices_list:\n                        vlist.insert(0, (n, indent))\n        out_f.write(\"\\n\")\n\n    def dump_xml(self, operation, out_f):\n        allow_vertices = [v for v in self.vertices if v.decision == \"allow\"]\n        deny_vertices = [v for v in self.vertices if v.decision == \"deny\"]\n        if allow_vertices:\n            out_f.write(\"\\t<operation name=\\\"%s\\\" action=\\\"allow\\\">\\n\" % (operation))\n            out_f.write(\"\\t\\t<filters>\\n\")\n            for v in allow_vertices:\n                out_f.write(v.xml_str())\n            out_f.write(\"\\t\\t</filters>\\n\")\n            out_f.write(\"\\t</operation>\\n\")\n        if deny_vertices:\n            out_f.write(\"\\t<operation name=\\\"%s\\\" action=\\\"deny\\\">\\n\" % (operation))\n            out_f.write(\"\\t\\t<filters>\\n\")\n            for v in deny_vertices:\n                out_f.write(v.xml_str())\n            out_f.write(\"\\t\\t</filters>\\n\")\n            out_f.write(\"\\t</operation>\\n\")\n\n\ndef reduce_operation_node_graph(g):\n    # Create reduced graph.\n    rg = ReducedGraph()\n    for node_iter in g.keys():\n        rv = ReducedVertice(value=node_iter, decision=g[node_iter][\"decision\"], is_not=g[node_iter][\"not\"])\n        rg.add_vertice(rv)\n\n    for node_iter in g.keys():\n        rv = rg.get_vertice_by_value(node_iter)\n        for node_next in g[node_iter][\"list\"]:\n            rn = rg.get_vertice_by_value(node_next)\n            rg.add_edge_by_vertices(rv, rn)\n\n    # Handle special case for require-not (require-enitlement (...)).\n    l = len(g.keys())\n    for idx, node_iter in enumerate(g.keys()):\n        rv = rg.get_vertice_by_value(node_iter)\n        if not re.search(\"require-entitlement\", str(rv)):\n            continue\n        if not rv.is_not:\n            continue\n        c_idx = idx\n        while True:\n            c_idx += 1\n            if c_idx >= l:\n                break\n            rn = rg.get_vertice_by_value(list(g.keys())[c_idx])\n            if not re.search(\"entitlement-value\", str(rn)):\n                break\n            prevs_rv = rg.get_prev_vertices(rv)\n            prevs_rn = rg.get_prev_vertices(rn)\n            if sorted(prevs_rv) != sorted(prevs_rn):\n                continue\n            for pn in prevs_rn:\n                rg.remove_edge_by_vertices(rn, pn)\n            rg.add_edge_by_vertices(rv, rn)\n\n    rg.cleanup_filters_with_metanodes()\n    for node_iter in g.keys():\n        rv = rg.get_vertice_by_value(node_iter)\n    rg.reduce_graph_with_metanodes()\n    return rg\n\n\ndef main():\n    if len(sys.argv) != 4:\n        print >> sys.stderr, \"Usage: %s binary_sandbox_file operations_file ios_version\" % (sys.argv[0])\n        sys.exit(-1)\n\n    ios_major_version = int(sys.argv[3].split('.')[0])\n    # Read sandbox operations.\n    sb_ops = [l.strip() for l in open(sys.argv[2])]\n    num_sb_ops = len(sb_ops)\n    logger.info(\"num_sb_ops:\", num_sb_ops)\n\n    f = open(sys.argv[1], \"rb\")\n    operation_nodes = build_operation_nodes(f, num_sb_ops, ios_major_version)\n\n    global num_regex\n    f.seek(4)\n    num_regex = struct.unpack(\"<H\", f.read(2))[0]\n    logger.debug(\"num_regex: %02x\" % (num_regex))\n    f.seek(6)\n    sb_ops_offsets = struct.unpack(\"<%dH\" % (num_sb_ops), f.read(2*num_sb_ops))\n\n    # Extract node for 'default' operation (index 0).\n    default_node = find_operation_node_by_offset(operation_nodes, sb_ops_offsets[0])\n    print(\"(%s default)\" % (default_node.terminal))\n\n    # For each operation expand operation node.\n    #for idx in range(1, len(sb_ops_offsets)):\n    for idx in range(10, 11):\n        offset = sb_ops_offsets[idx]\n        operation = sb_ops[idx]\n        node = find_operation_node_by_offset(operation_nodes, offset)\n        if not node:\n            logger.info(\"operation %s (index %d) has no operation node\", operation, idx)\n            continue\n        logger.debug(\"expanding operation %s (index %d, offset: %02x)\", operation, idx, offset)\n        g = build_operation_node_graph(node, default_node)\n        logger.debug(\"reducing operation %s (index %d, offset: %02x)\", operation, idx, offset)\n        print_operation_node_graph(g)\n        if g:\n            rg = reduce_operation_node_graph(g)\n            rg.print_vertices_with_operation(operation)\n        else:\n            if node.terminal:\n                if node.terminal.type != default_node.terminal.type:\n                    print(\"(%s %s)\" % (node.terminal, operation))\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
  },
  {
    "path": "reverse-sandbox/regex_parser_v1.py",
    "content": "import logging\nimport struct\n\nlogging.config.fileConfig(\"logger.config\")\nlogger = logging.getLogger(__name__)\n\ndef parse_character(node_type, node_arg, node_transition, node_idx):\n    value = chr(node_arg & 0xff)\n    if value == \".\":\n        value = \"[.]\"\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"character\",\n        \"value\": value}\n\ndef parse_end(node_type, node_arg, node_transition, node_idx):\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"end\",\n        \"value\": 0}\n\ndef parse_jump_forward(node_type, node_arg, node_transition, node_idx):\n    jump_to = node_arg\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"jump_forward\",\n        \"value\": jump_to}\n\ndef parse_jump_backward(node_type, node_arg, node_transition, node_idx):\n    jump_to = node_transition\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"jump_backward\",\n        \"value\": jump_to}\n\ndef parse_beginning_of_line(node_type, node_arg, node_transition, node_idx):\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"character\",\n        \"value\": \"^\"}\n\ndef parse_end_of_line(node_type, node_arg, node_transition, node_idx):\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"character\",\n        \"value\": \"$\"}\n\ndef parse_dot(node_type, node_arg, node_transition, node_idx):\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"character\",\n        \"value\": \".\"}\n\ndef parse_character_class(node_type, node_arg, node_transition, node_idx):\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"class\",\n        \"value\": node_arg}\n\ndef parse_character_neg_class(node_type, node_arg, node_transition, node_idx):\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"class_exclude\",\n        \"value\": node_arg}\n\ndef parse_parantheses_open(node_type, node_arg, node_transition, node_idx):\n    return parse_jump_backward(node_type, node_arg, node_transition,\n        node_idx)\n    '''\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"character\",\n        \"value\": \"(\"}\n    '''\n\ndef parse_parantheses_close(node_type, node_arg, node_transition, node_idx):\n    return parse_jump_backward(node_type, node_arg, node_transition,\n        node_idx)\n    '''\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"character\",\n        \"value\": \")\"}\n    '''\n\nnode_type_dispatch_table = {\n  0x10: parse_character,\n  0x22: parse_end,\n  0x23: parse_parantheses_close,\n  0x24: parse_parantheses_open,\n  0x25: parse_jump_forward,\n  0x30: parse_dot,\n  0x31: parse_jump_backward,\n  0x32: parse_beginning_of_line,\n  0x33: parse_end_of_line,\n  0x34: parse_character_class,\n  0x35: parse_character_neg_class,\n}\n\n\ndef node_parse(re, i, regex_list, node_idx):\n    node_type = struct.unpack('>I',\n        ''.join([chr(x) for x in re[i:i+4]]))[0]\n    node_transition = struct.unpack('>I',\n        ''.join([chr(x) for x in re[i+4:i+8]]))[0]\n    node_arg = struct.unpack('>I',\n        ''.join([chr(x) for x in re[i+8:i+12]]))[0]\n    i += 12\n\n    logger.debug('node idx:{:#010x} type: {:#02x} arg: {:#010x}' \\\n        ' transition: {:#010x}'.format(node_idx, node_type,node_arg,\n            node_transition))\n    assert(node_type in node_type_dispatch_table)\n    regex_list.append(\n        node_type_dispatch_table[node_type](\n            node_type, node_arg, node_transition, node_idx))\n    return i\n\ndef class_parse(re, i, classes, class_idx):\n    def transform(x):\n        c = chr(x)\n        if c in '[]-':\n            return '\\\\' + c\n        else:\n            return c\n\n    class_size = struct.unpack('>I',\n        ''.join([chr(x) for x in re[i:i+4]]))[0]\n    i += 0x4\n    content = struct.unpack('>{}I'.format(class_size),\n        ''.join([chr(x) for x in re[i:i+4*class_size]]))\n    i += 0x4 * class_size\n    assert(class_size % 2 == 0)\n\n    cls = ''\n    for idx in range(0, class_size, 2):\n        start = content[idx]\n        end = content[idx+1]\n        if start != end:\n            cls += '{}-{}'.format(transform(start), transform(end))\n        else:\n            cls += transform(start)\n\n    logger.debug('class idx = {:#x} size = {:#x} content=[{}]'.format(\n        class_idx, class_size, cls))\n    classes.append(cls)\n    return i\n\nclass RegexParser(object):\n\n    @staticmethod\n    def parse(re, i, regex_list):\n        node_count = struct.unpack('>I',\n            ''.join([chr(x) for x in re[i:i+0x4]]))[0]\n        logger.debug('node count = {:#x}'.format(node_count))\n\n        start_node = struct.unpack('>I',\n            ''.join([chr(x) for x in re[i+0x4:i+0x8]]))[0]\n        logger.debug('start node = {:#x}'.format(start_node))\n\n        end_node = struct.unpack('>I',\n            ''.join([chr(x) for x in re[i+0x8:i+0xC]]))[0]\n        logger.debug('end node = {:#x}'.format(end_node))\n\n        cclass_count = struct.unpack('>I',\n            ''.join([chr(x) for x in re[i+0xC:i+0x10]]))[0]\n        logger.debug('character class count = {:#x}'.format(cclass_count))\n\n        submatch_count = struct.unpack('>I',\n            ''.join([chr(x) for x in re[i+0x10:i+0x14]]))[0]\n        i += 0x14\n        logger.debug('submatch count = {:#x}'.format(submatch_count))\n\n\n        for node_idx in range(node_count):\n            i = node_parse(re, i, regex_list, node_idx)\n\n        classes = []\n        for class_idx in range(cclass_count):\n            i = class_parse(re, i, classes, class_idx)\n\n        for node in regex_list:\n            if node['type'] == 'class':\n                node['value'] = '[{}]'.format(classes[node['value']])\n            elif node['type'] == 'class_exclude':\n                node['value'] = '[{}]'.format(classes[node['value']])\n\n        regex_list[start_node]['start_node'] = True\n\n"
  },
  {
    "path": "reverse-sandbox/regex_parser_v2.py",
    "content": "import logging\nimport struct\n\nlogging.config.fileConfig(\"logger.config\")\nlogger = logging.getLogger(__name__)\n\ndef parse_character(node_type, node_arg, node_transition, node_idx):\n    value = chr(node_arg & 0xff)\n    if value == \".\":\n        value = \"[.]\"\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"character\",\n        \"value\": value}\n\ndef parse_end(node_type, node_arg, node_transition, node_idx):\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"end\",\n        \"value\": 0}\n\ndef parse_jump_forward(node_type, node_arg, node_transition, node_idx):\n    jump_to = node_arg\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"jump_forward\",\n        \"value\": jump_to}\n\ndef parse_jump_backward(node_type, node_arg, node_transition, node_idx):\n    jump_to = node_transition\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"jump_backward\",\n        \"value\": jump_to}\n\ndef parse_beginning_of_line(node_type, node_arg, node_transition, node_idx):\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"character\",\n        \"value\": \"^\"}\n\ndef parse_end_of_line(node_type, node_arg, node_transition, node_idx):\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"character\",\n        \"value\": \"$\"}\n\ndef parse_dot(node_type, node_arg, node_transition, node_idx):\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"character\",\n        \"value\": \".\"}\n\ndef parse_character_class(node_type, node_arg, node_transition, node_idx):\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"class\",\n        \"value\": node_arg}\n\ndef parse_character_neg_class(node_type, node_arg, node_transition, node_idx):\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"class_exclude\",\n        \"value\": node_arg}\n\ndef parse_parantheses_open(node_type, node_arg, node_transition, node_idx):\n    return parse_jump_backward(node_type, node_arg, node_transition,\n        node_idx)\n    '''\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"character\",\n        \"value\": \"(\"}\n    '''\n\ndef parse_parantheses_close(node_type, node_arg, node_transition, node_idx):\n    return parse_jump_backward(node_type, node_arg, node_transition,\n        node_idx)\n    '''\n    return {\n        \"pos\": node_idx,\n        \"nextpos\": node_transition,\n        \"type\": \"character\",\n        \"value\": \")\"}\n    '''\n\nnode_type_dispatch_table = {\n  0x10: parse_character,\n  0x22: parse_end,\n  0x25: parse_jump_forward,\n  0x26: parse_jump_forward,\n  0x27: parse_jump_forward,\n  0x28: parse_jump_forward,\n  0x30: parse_dot,\n  0x31: parse_jump_backward,\n  0x32: parse_beginning_of_line,\n  0x33: parse_end_of_line,\n  0x34: parse_character_class,\n  0x35: parse_character_neg_class,\n}\n\n\ndef node_parse(re, i, regex_list, node_idx):\n    node_type = struct.unpack('<B',\n        ''.join([chr(x) for x in re[i:i+1]]))[0]\n    node_transition = struct.unpack('<H',\n        ''.join([chr(x) for x in re[i+1:i+3]]))[0]\n    pad = struct.unpack('<B',\n        ''.join([chr(x) for x in re[i+3:i+4]]))[0]\n    node_arg = struct.unpack('<I',\n        ''.join([chr(x) for x in re[i+4:i+8]]))[0]\n    i += 8\n\n    logger.debug('node idx:{:#06x} type: {:#02x} arg: {:#010x}' \\\n        ' transition: {:#06x}'.format(node_idx, node_type,node_arg,\n            node_transition))\n\n    assert(pad == 0 or node_type == 0x22)\n    assert(node_type in node_type_dispatch_table)\n    regex_list.append(\n        node_type_dispatch_table[node_type](\n            node_type, node_arg, node_transition, node_idx))\n    return i\n\ndef classes_parse(re, i, cclass_count):\n    def transform(x):\n        c = chr(x)\n        if c in '[]-':\n            return '\\\\' + c\n        else:\n            return c\n    def transform_range(start, end):\n        if start != end:\n            return '{}-{}'.format(transform(start), transform(end))\n        return transform(start)\n    def transform_content(content):\n        cls = ''\n        assert(len(content) % 2 == 0)\n        for idx in range(0, len(content), 2):\n            start = content[idx]\n            end = content[idx+1]\n            cls += transform_range(start, end)\n        return cls\n\n    if cclass_count == 0:\n        return\n\n    classes_magic, classes_size = struct.unpack('<II',\n        ''.join([chr(x) for x in re[i:i+8]]))\n    i += 0x8\n    logger.debug('classes magic = {:#x} size = {:#x}'.format(\n        classes_magic, classes_size))\n    assert(len(re) - i == classes_size)\n    starts = struct.unpack('<{}I'.format(cclass_count),\n        ''.join([chr(x) for x in re[i:i+4*cclass_count]]))\n    i += 0x4 * cclass_count\n\n    lens = struct.unpack('<{}B'.format(cclass_count),\n        ''.join([chr(x) for x in re[i:i+cclass_count]]))\n    i += cclass_count\n\n    contents = [re[i+start:i+start+clen] for start, clen in zip(starts, lens)]\n    return [transform_content(content) for content in contents]\n\nclass RegexParser(object):\n\n    @staticmethod\n    def parse(re, i, regex_list):\n        magic = struct.unpack('<I',\n            ''.join([chr(x) for x in re[i:i+0x4]]))[0]\n        logger.debug('magic = {:#x}'.format(magic))\n\n        node_count = struct.unpack('<I',\n            ''.join([chr(x) for x in re[i+0x4:i+0x8]]))[0]\n        logger.debug('node count = {:#x}'.format(node_count))\n\n        start_node = struct.unpack('<I',\n            ''.join([chr(x) for x in re[i+0x8:i+0xC]]))[0]\n        logger.debug('start node = {:#x}'.format(start_node))\n\n        end_node = struct.unpack('<I',\n            ''.join([chr(x) for x in re[i+0xC:i+0x10]]))[0]\n        logger.debug('end node = {:#x}'.format(end_node))\n\n        cclass_count = struct.unpack('<I',\n            ''.join([chr(x) for x in re[i+0x10:i+0x14]]))[0]\n        logger.debug('character class count = {:#x}'.format(cclass_count))\n        i += 0x14\n\n        for node_idx in range(node_count):\n            i = node_parse(re, i, regex_list, node_idx)\n\n        classes = classes_parse(re, i, cclass_count)\n\n        for node in regex_list:\n            if node['type'] == 'class':\n                node['value'] = '[{}]'.format(classes[node['value']])\n            elif node['type'] == 'class_exclude':\n                node['value'] = '[{}]'.format(classes[node['value']])\n\n        regex_list[start_node]['start_node'] = True\n\n"
  },
  {
    "path": "reverse-sandbox/regex_parser_v3.py",
    "content": "import logging\nimport struct\n\nlogging.config.fileConfig(\"logger.config\")\nlogger = logging.getLogger(__name__)\n\ndef parse_character(re, i, regex_list):\n    value = chr(re[i+1])\n    if value == \".\":\n        value = \"[.]\"\n    regex_list.append({\n        \"pos\": i-6,\n        \"nextpos\": i+2-6,\n        \"type\": \"character\",\n        \"value\": value}\n        )\n    return i + 1\n\ndef parse_beginning_of_line(i, regex_list):\n    regex_list.append({\n        \"pos\": i-6,\n        \"nextpos\": i+1-6,\n        \"type\": \"character\",\n        \"value\": \"^\"}\n        )\n\ndef parse_end_of_line(i, regex_list):\n    regex_list.append({\n        \"pos\": i-6,\n        \"nextpos\": i+1-6,\n        \"type\": \"character\",\n        \"value\": \"$\"}\n        )\n\ndef parse_any_character(i, regex_list):\n    regex_list.append({\n        \"pos\": i-6,\n        \"nextpos\": i+1-6,\n        \"type\": \"character\",\n        \"value\": \".\"}\n        )\n\ndef parse_jump_forward(re, i, regex_list):\n    jump_to = re[i+1] + (re[i+2] << 8)\n    regex_list.append({\n        \"pos\": i-6,\n        \"nextpos\": i+3-6,\n        \"type\": \"jump_forward\",\n        \"value\": jump_to}\n        )\n    return i + 2\n\ndef parse_jump_backward(re, i, regex_list):\n    jump_to = re[i+1] + (re[i+2] << 8)\n    regex_list.append({\n        \"pos\": i-6,\n        \"nextpos\": i+3-6,\n        \"type\": \"jump_backward\",\n        \"value\": jump_to}\n        )\n    logger.debug(\"(0xa) i: %d (0x%x), re[i, i+1, i+2]: 0x%x, 0x%x, 0x%x\", i, i, re[i], re[i+1], re[i+2])\n    logger.debug(\"value: 0x%x\", jump_to)\n    return i+2\n\ndef parse_character_class(re, i, regex_list):\n    num = (re[i] >> 4)\n    i = i+1\n    logger.debug(\"i: %d, num: %d\", i, num)\n    values = []\n    value = \"[\"\n    for j in range(0, num):\n        values.append(re[i+2*j])\n        values.append(re[i+2*j+1])\n    first = values[0]\n    last = values[2*num-1]\n    # In case of excludes.\n    if (first > last):\n        node_type = \"class_exclude\"\n        value += \"^\"\n        for j in range(len(values)-1, 0, -1):\n            values[j] = values[j-1]\n        values[0] = last\n        for j in range(0, len(values)):\n            if j % 2 == 0:\n                values[j] = values[j]+1\n            else:\n                values[j] = values[j]-1\n    else:\n        node_type = \"class\"\n    for j in range(0, len(values), 2):\n        if values[j] < values[j+1]:\n            value += \"%s-%s\" % (chr(values[j]), chr(values[j+1]))\n        else:\n            value += \"%s\" % (chr(values[j]))\n    value += \"]\"\n    regex_list.append({\n        \"pos\": i-6-1,\n        \"nextpos\": i + 2 * num - 6,\n        \"type\": node_type,\n        \"value\": value\n        })\n    message = \"values: [\", \", \".join([hex(j) for j in values]), \"]\"\n    logger.debug(message)\n\n    return i + 2 * num - 1\n\ndef parse_end(re, i, regex_list):\n    regex_list.append({\n        \"pos\": i-6,\n        \"nextpos\": i+2-6,\n        \"type\": \"end\",\n        \"value\": 0\n        })\n    return i + 1\n\ndef parse(re, i, regex_list):\n    # Actual character.\n    if re[i] == 0x02:\n        i = parse_character(re, i, regex_list)\n    # Beginning of line.\n    elif re[i] == 0x19:\n        parse_beginning_of_line(i, regex_list)\n    # End of line.\n    elif re[i] == 0x29:\n        parse_end_of_line(i, regex_list)\n    # Any character.\n    elif re[i] == 0x09:\n        parse_any_character(i, regex_list)\n    # Jump forward.\n    elif re[i] == 0x2f:\n        i = parse_jump_forward(re, i, regex_list)\n    # Jump backward.\n    elif re[i] & 0xf == 0xa:\n        i = parse_jump_backward(re, i, regex_list)\n    # Character class.\n    elif re[i] & 0xf == 0xb:\n        i = parse_character_class(re, i, regex_list)\n    elif re[i] & 0xf == 0x5:\n        i = parse_end(re, i, regex_list)\n    else:\n        logger.warning(\"##########unknown\", hex(re[i]))\n\n    return i + 1\n\nclass RegexParser(object):\n\n    @staticmethod\n    def parse(re, i, regex_list):\n        length = struct.unpack('<H', ''.join([chr(x) for x in re[i:i+2]]))[0]\n        logger.debug(\"re.length: 0x%x\", length)\n        i += 2\n        assert(length == len(re)-i)\n        while i < len(re):\n            i = parse(re, i, regex_list)\n\n        regex_list[0][\"start_node\"]=True\n\n"
  },
  {
    "path": "reverse-sandbox/reverse_sandbox.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"\niOS/OS X sandbox decompiler\n\nHeavily inspired from Dion Blazakis' previous work\n    https://github.com/dionthegod/XNUSandbox/tree/master/sbdis\nExcellent information from Stefan Essers' slides and work\n    http://www.slideshare.net/i0n1c/ruxcon-2014-stefan-esser-ios8-containers-sandboxes-and-entitlements\n    https://github.com/sektioneins/sandbox_toolkit\n\"\"\"\n\nimport sys\nimport struct\nimport logging\nimport logging.config\nimport argparse\nimport os\nimport re\nimport operation_node\nimport sandbox_filter\nimport sandbox_regex\n\n\nlogging.config.fileConfig(\"logger.config\")\nlogger = logging.getLogger(__name__)\n\n\ndef extract_string_from_offset(f, offset, ios_version):\n    \"\"\"Extract string (literal) from given offset.\"\"\"\n    if ios_version >= 13:\n        f.seek(get_base_addr(f, ios_version) + offset * 8)\n        len = struct.unpack(\"<H\", f.read(2))[0]-1\n    else:\n        f.seek(offset * 8)\n        len = struct.unpack(\"<I\", f.read(4))[0]-1\n    return '%s' % f.read(len)\n\n\ndef create_operation_nodes(infile, regex_list, num_operation_nodes,\n        ios_major_version, keep_builtin_filters, global_vars):\n    # Read sandbox operations.\n    operation_nodes = operation_node.build_operation_nodes(infile,\n        num_operation_nodes, ios_major_version)\n    logger.info(\"operation nodes\")\n    \n    for idx, node in enumerate(operation_nodes):\n        logger.info(\"%d: %s\", idx, node.str_debug())\n    \n    for n in operation_nodes:\n        n.convert_filter(sandbox_filter.convert_filter_callback, infile,\n                    regex_list, ios_major_version, keep_builtin_filters,\n                    global_vars, get_base_addr(infile, ios_major_version))\n    logger.info(\"operation nodes after filter conversion\")\n    for idx, node in enumerate(operation_nodes):\n        logger.info(\"%d: %s\", idx, node.str_debug())\n\n    return operation_nodes\n\n\ndef process_profile(infile, outfname, sb_ops, ops_to_reverse, op_table, operation_nodes):\n    outfile = open(outfname, \"wt\")\n    outfile_xml = open(outfname + \".xml\", \"wt\")\n\n    # Print version.\n    outfile.write(\"(version 1)\\n\")\n\n    outfile_xml.write('<?xml version=\"1.0\" encoding=\"us-ascii\" standalone=\"yes\"?>\\n')\n    outfile_xml.write('<!DOCTYPE operations [\\n')\n    outfile_xml.write('<!ELEMENT operations (operation*)>\\n')\n    outfile_xml.write('<!ELEMENT operation (filters?)>\\n')\n    outfile_xml.write('<!ELEMENT filters (filter | require)*>\\n')\n    outfile_xml.write('<!ELEMENT require (filter | require)*>\\n')\n    outfile_xml.write('<!ELEMENT filter (#PCDATA)>\\n')\n    outfile_xml.write('<!ATTLIST operation\\n')\n    outfile_xml.write('\\tname CDATA #REQUIRED\\n')\n    outfile_xml.write('\\taction (deny|allow) #REQUIRED>\\n')\n    outfile_xml.write('<!ATTLIST require\\n')\n    outfile_xml.write('\\ttype (require-all|require-any|require-not|require-entitlement) #REQUIRED\\n')\n    outfile_xml.write('\\tvalue CDATA #IMPLIED>\\n')\n    outfile_xml.write('<!ATTLIST filter\\n')\n    outfile_xml.write('\\tname CDATA #REQUIRED\\n')\n    outfile_xml.write('\\targument CDATA #IMPLIED>\\n')\n    outfile_xml.write(']>\\n')\n    outfile_xml.write(\"<operations>\\n\")\n\n    # Extract node for 'default' operation (index 0).\n    default_node = operation_node.find_operation_node_by_offset(operation_nodes, op_table[0])\n    outfile.write(\"(%s default)\\n\" % (default_node.terminal))\n    outfile_xml.write(\"\\t<operation name=\\\"default\\\" action=\\\"%s\\\" />\\n\" % (default_node.terminal))\n\n    # For each operation expand operation node.\n    for idx in range(1, len(op_table)):\n        offset = op_table[idx]\n        operation = sb_ops[idx]\n        # Go past operations not in list, in case list is not empty.\n        if ops_to_reverse:\n            if operation not in ops_to_reverse:\n                continue\n        logger.info(\"parsing operation %s (index %d)\", operation, idx)\n        node = operation_node.find_operation_node_by_offset(operation_nodes, offset)\n        if not node:\n            logger.info(\"operation %s (index %d) has no operation node\", operation, idx)\n            continue\n        g = operation_node.build_operation_node_graph(node, default_node)\n        if g:\n            rg = operation_node.reduce_operation_node_graph(g)\n            rg.str_simple_with_metanodes()\n            rg.print_vertices_with_operation_metanodes(operation, default_node.terminal.is_allow(), outfile)\n            #rg.dump_xml(operation, outfile_xml)\n        else:\n            logger.info(\"no graph for operation %s (index %d)\", operation, idx)\n            if node.terminal and default_node.terminal:\n                if node.terminal.type != default_node.terminal.type:\n                    outfile.write(\"(%s %s)\\n\" % (node.terminal, operation))\n                    outfile_xml.write(\"\\t<operation name=\\\"%s\\\" action=\\\"%s\\\" />\\n\" % (operation, node.terminal))\n\n    outfile.close()\n    outfile_xml.write(\"</operations>\\n\")\n    outfile_xml.close()\n\ndef get_ios_major_version(release):\n    \"\"\"\n    Returns major version of release\n    \"\"\"\n    return int(release.split('.')[0])\n\ndef is_ios_more_than_10_release(release):\n    \"\"\"\n    Returns True if release is using newer (iOS >= 10) binary sandbox profile format.\n    \"\"\"\n    major_version = get_ios_major_version(release)\n    if major_version < 10:\n        return False\n    return True\n\n\ndef display_sandbox_profiles(f, re_table_offset, num_sb_ops, ios_version):\n    logger.info(\"Printing sandbox profiles from bundle\")\n    if ios_version >= 13:\n        f.seek(6)\n    elif ios_version >= 12:\n        f.seek(12)\n    elif ios_version >= 10:\n        f.seek(10)\n    else:\n        f.seek(6)\n    num_profiles = struct.unpack(\"<H\", f.read(2))[0]\n\n    if ios_version >= 13:\n        f.seek(2)\n        num_operation_nodes = struct.unpack(\"<H\", f.read(2))[0]\n        print(hex(num_operation_nodes))\n    else:\n        # Place file pointer to start of operation nodes area.\n        if ios_version >= 12:\n            f.seek(14 + (num_sb_ops + 2) * 2 * num_profiles)\n        elif ios_version >= 10:\n            f.seek(12 + (num_sb_ops + 2) * 2 * num_profiles)\n        else:\n            f.seek(8 + (num_sb_ops + 2) * 2 * num_profiles)\n        while True:\n            word = struct.unpack(\"<H\", f.read(2))[0]\n            if word != 0:\n                f.seek(-2, 1)\n                break\n        start = f.tell()\n        end = re_table_offset * 8\n        num_operation_nodes = (end - start) / 8\n\n    logger.info(\"number of operation nodes: %u\" % num_operation_nodes)\n\n    for i in range(0, num_profiles):\n        if ios_version >= 13:\n            f.seek(8)\n            regex_table_count = struct.unpack('<H', f.read(2))[0]\n            f.seek(10)\n            global_table_count = struct.unpack('<B', f.read(1))[0]\n            f.seek(11)\n            debug_table_count = struct.unpack('<B', f.read(1))[0]\n            f.seek(12 + (regex_table_count + global_table_count + \\\n                    debug_table_count) * 2 + (num_sb_ops + 2) * 2 * i)\n        elif ios_version >= 12:\n            f.seek(14 + (num_sb_ops + 2) * 2 * i)\n        elif ios_version >= 10:\n            f.seek(12 + (num_sb_ops + 2) * 2 * i)\n        else:\n            f.seek(8 + (num_sb_ops + 2) * 2 * i)\n\n        name_offset = struct.unpack(\"<H\", f.read(2))[0]\n        boundary = struct.unpack(\"<H\", f.read(2))[0]\n        name = extract_string_from_offset(f, name_offset, ios_version)\n\n        print(name)\n\n    logger.info(\"Found %d sandbox profiles.\" % num_profiles)\n\n\ndef get_global_vars(f, vars_offset, num_vars, base_offset):\n    global_vars = []\n    for i in range(0, num_vars):\n        if base_offset > 0:\n            f.seek(vars_offset + i*2)\n        else:\n            f.seek(vars_offset*8 + i*2)\n        current_offset = struct.unpack(\"<H\", f.read(2))[0]\n        f.seek(base_offset + current_offset * 8)\n        if base_offset > 0:\n            len = struct.unpack(\"<H\", f.read(2))[0]\n        else:\n            len = struct.unpack(\"<I\", f.read(4))[0]\n        s = f.read(len-1)\n        global_vars.append(s)\n    logger.info(\"global variables are {:s}\".format(\", \".join(s for s in global_vars)))\n    return global_vars\n\ndef get_base_addr(f, ios_version):\n    if ios_version >= 13:\n        # extract operation node table count\n        f.seek(2)\n        op_nodes_count = struct.unpack('<H', f.read(2))[0]\n\n        # extract sandbox operations count\n        f.seek(4)\n        sb_ops_count = struct.unpack('<H', f.read(2))[0]\n\n        # extract sandbox profile count\n        f.seek(6)\n        sb_profiles_count = struct.unpack('<H', f.read(2))[0]\n\n        # extract regular expressions count\n        f.seek(8)\n        regex_table_count = struct.unpack('<H', f.read(2))[0]\n\n        # extract global table count\n        f.seek(10)\n        global_table_count = struct.unpack('<B', f.read(1))[0]\n\n        # extract debug table count\n        f.seek(11)\n        debug_table_count = struct.unpack('<B', f.read(1))[0]\n\n        return 12 + (regex_table_count + global_table_count + debug_table_count)*2 \\\n                + (2 + sb_ops_count) * 2 * sb_profiles_count + op_nodes_count * 8 + 4\n    return 0\n\n\ndef main():\n    \"\"\"Reverse Apple binary sandbox file to SBPL (Sandbox Profile Language) format.\n\n    Sample run:\n        python reverse_sandbox.py -r 7.1.1 container.sb.bin\n        python reverse_sandbox.py -r 7.1.1 -d out container.sb.bin\n        python reverse_sandbox.py -r 7.1.1 -d out container.sb.bin -n network-inbound network-outbound\n        python reverse_sandbox.py -r 9.0.2 -d out sandbox_bundle_iOS_9.0 -n network-inbound network-outbound -p container\n    \"\"\"\n\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"filename\", help=\"path to the binary sandbox profile\")\n    parser.add_argument(\"-r\", \"--release\", help=\"iOS release version for sandbox profile\", required=True)\n    parser.add_argument(\"-o\", \"--operations_file\", help=\"file with list of operations\", required=True)\n    parser.add_argument(\"-p\", \"--profile\", nargs='+', help=\"profile to reverse (for bundles) (default is to reverse all operations)\")\n    parser.add_argument(\"-n\", \"--operation\", nargs='+', help=\"particular operation(s) to reverse (default is to reverse all operations)\")\n    parser.add_argument(\"-d\", \"--directory\", help=\"directory where to write reversed profiles (default is current directory)\")\n    parser.add_argument(\"-psb\", \"--print_sandbox_profiles\", action=\"store_true\", help=\"print sandbox profiles of a given bundle (only for iOS versions 9+)\")\n    parser.add_argument(\"-kbf\", \"--keep_builtin_filters\", help=\"keep builtin filters in output\", action=\"store_true\")\n\n    args = parser.parse_args()\n\n    if args.filename is None:\n        parser.print_usage()\n        print(\"no sandbox profile/bundle file to reverse\")\n        sys.exit(1)\n\n    # Read sandbox operations.\n    sb_ops = [l.strip() for l in open(args.operations_file)]\n    num_sb_ops = len(sb_ops)\n    logger.info(\"num_sb_ops: %d\", num_sb_ops)\n\n    ops_to_reverse = []\n    if args.operation:\n        for op in args.operation:\n            if op not in sb_ops:\n                parser.print_usage()\n                print(\"unavailable operation: {}\".format(op))\n                sys.exit(1)\n            ops_to_reverse.append(op)\n\n    if args.directory:\n        out_dir = args.directory\n    else:\n        out_dir = os.getcwd()\n\n    f = open(args.filename, \"rb\")\n\n    if get_ios_major_version(args.release) >= 6:\n        header = struct.unpack(\"<H\", f.read(2))[0]\n        logger.debug(\"header: 0x%x\", header)\n    else:\n        logger.debug(\"header: none for iOS <6; using 0\")\n        header = 0\n\n    if get_ios_major_version(args.release) >= 13:\n        re_table_offset = 12\n    else:\n        re_table_offset = struct.unpack(\"<H\", f.read(2))[0]\n    \n    if get_ios_major_version(args.release) >= 12:\n        f.seek(8)\n    re_table_count = struct.unpack(\"<H\", f.read(2))[0]\n\n    logger.debug(\"re_table_offset: 0x%x\", re_table_offset)\n    logger.debug(\"re_table_count: 0x%x\", re_table_count)\n\n    logger.debug(\"\\n\\nregular expressions:\\n\")\n    regex_list = []\n    if re_table_count > 0:\n        if get_ios_major_version(args.release) >= 13:\n            f.seek(re_table_offset)\n        else:\n            f.seek(re_table_offset * 8)\n        \n        re_offsets_table = struct.unpack(\"<%dH\" % re_table_count, f.read(2 * re_table_count))\n        for offset in re_offsets_table:\n            if get_ios_major_version(args.release) >= 13:\n                f.seek(get_base_addr(f, get_ios_major_version(args.release)) + offset * 8)\n                re_length = struct.unpack(\"<H\", f.read(2))[0]\n            else:\n                f.seek(offset * 8)\n                re_length = struct.unpack(\"<I\", f.read(4))[0]\n            \n            re = struct.unpack(\"<%dB\" % re_length, f.read(re_length))\n            logger.debug(\"total_re_length: 0x%x\", re_length)\n            re_debug_str = \"re: [\", \", \".join([hex(i) for i in re]), \"]\"\n            logger.debug(re_debug_str)\n            regex_list.append(sandbox_regex.parse_regex(re))\n    logger.debug(regex_list)\n\n    if args.print_sandbox_profiles:\n        if header == 0x8000:\n            display_sandbox_profiles(f, re_table_offset, num_sb_ops, get_ios_major_version(args.release))\n        else:\n            print(\"cannot print sandbox profiles list; filename {} is not a sandbox bundle\".format(args.filename))\n        sys.exit(0)\n\n    global_vars = None\n\n    # In case of sandbox profile bundle, go through each profile.\n    if header == 0x8000:\n        logger.info(\"using profile bundle\")\n        if get_ios_major_version(args.release) >= 13:\n            # get the regex table entries\n            f.seek(8)\n            regex_table_count = struct.unpack('<H', f.read(2))[0]\n            vars_offset = 12 + regex_table_count * 2\n            f.seek(10)\n            num_vars = struct.unpack(\"<B\", f.read(1))[0]\n            logger.info(\"{:d} global vars at offset 0x{:0x}\".format(num_vars, vars_offset))\n            global_vars = get_global_vars(f, vars_offset, num_vars, get_base_addr(f, get_ios_major_version(args.release)))\n            f.seek(6)\n        elif get_ios_major_version(args.release) >= 12:\n            f.seek(4)\n            vars_offset = struct.unpack(\"<H\", f.read(2))[0]\n            f.seek(10)\n            num_vars = struct.unpack(\"<B\", f.read(1))[0]\n            logger.info(\"{:d} global vars at offset 0x{:0x}\".format(num_vars, vars_offset))\n            global_vars = get_global_vars(f, vars_offset, num_vars, 0)\n            f.seek(12)\n        elif get_ios_major_version(args.release) >= 10:\n            f.seek(6)\n            vars_offset = struct.unpack(\"<H\", f.read(2))[0]\n            num_vars = struct.unpack(\"<H\", f.read(2))[0]\n            logger.info(\"{:d} global vars at offset 0x{:0x}\".format(num_vars, vars_offset))\n            global_vars = get_global_vars(f, vars_offset, num_vars, 0)\n            f.seek(10)\n        else:\n            f.seek(6)\n        num_profiles = struct.unpack(\"<H\", f.read(2))[0]\n        logger.info(\"number of profiles in bundle: %d\", num_profiles)\n\n        if get_ios_major_version(args.release) >= 13:\n            f.seek(2)\n            num_operation_nodes = struct.unpack(\"<H\", f.read(2))[0]\n            f.seek(get_base_addr(f, get_ios_major_version(args.release)) - num_operation_nodes*8)\n        else:\n            # Place file pointer to start of operation nodes area.\n            if get_ios_major_version(args.release) >= 12:\n                f.seek(14 + (num_sb_ops + 2) * 2 * num_profiles)\n            elif get_ios_major_version(args.release) >= 10:\n                f.seek(12 + (num_sb_ops + 2) * 2 * num_profiles)\n            else:\n                f.seek(8 + (num_sb_ops + 2) * 2 * num_profiles)\n            while True:\n                word = struct.unpack(\"<H\", f.read(2))[0]\n                if word != 0:\n                    f.seek(-2, 1)\n                    break\n            start = f.tell()\n            end = re_table_offset * 8\n            num_operation_nodes = (end - start) / 8\n        logger.info(\"number of operation nodes: %u\" % num_operation_nodes)\n\n        operation_nodes = create_operation_nodes(f, regex_list,\n            num_operation_nodes, get_ios_major_version(args.release),\n            args.keep_builtin_filters, global_vars)\n\n        for i in range(0, num_profiles):\n            if get_ios_major_version(args.release) >= 13:\n                f.seek(8)\n                regex_table_count = struct.unpack('<H', f.read(2))[0]\n                f.seek(10)\n                global_table_count = struct.unpack('<B', f.read(1))[0]\n                f.seek(11)\n                debug_table_count = struct.unpack('<B', f.read(1))[0]\n                f.seek(12 + (regex_table_count + global_table_count + \\\n                        debug_table_count) * 2 + (num_sb_ops + 2) * 2 * i)\n            elif get_ios_major_version(args.release) >= 12:\n                f.seek(14 + (num_sb_ops + 2) * 2 * i)\n            elif get_ios_major_version(args.release) >= 10:\n                f.seek(12 + (num_sb_ops + 2) * 2 * i)\n            else:\n                f.seek(8 + (num_sb_ops + 2) * 2 * i)\n\n            name_offset = struct.unpack(\"<H\", f.read(2))[0]\n            boundary = struct.unpack(\"<H\", f.read(2))[0]\n            name = extract_string_from_offset(f, name_offset,\n                                get_ios_major_version(args.release))\n\n            # Go past profiles not in list, in case list is defined.\n            if args.profile:\n                if name not in args.profile:\n                    continue\n            logger.info(\"profile name (offset 0x%x): %s\" % (name_offset, name))\n\n            if get_ios_major_version(args.release) >= 13:\n                f.seek(8)\n                regex_table_count = struct.unpack('<H', f.read(2))[0]\n                f.seek(10)\n                global_table_count = struct.unpack('<B', f.read(1))[0]\n                f.seek(11)\n                debug_table_count = struct.unpack('<B', f.read(1))[0]\n                f.seek(12 + (regex_table_count + global_table_count + \\\n                        debug_table_count) * 2 + (num_sb_ops + 2) * 2 * i + 4)\n            elif get_ios_major_version(args.release) >= 12:\n                f.seek(14 + (num_sb_ops + 2) * 2 * i + 4)\n            elif get_ios_major_version(args.release) >= 10:\n                f.seek(12 + (num_sb_ops + 2) * 2 * i + 4)\n            else:\n                f.seek(8 + (num_sb_ops + 2) * 2 * i + 4)\n            op_table = struct.unpack(\"<%dH\" % num_sb_ops, f.read(2 * num_sb_ops))\n            for idx in range(1, len(op_table)):\n                offset = op_table[idx]\n                operation = sb_ops[idx]\n                logger.info(\"operation %s (index %u) starts at node offset %u (0x%x)\", operation, idx, offset, offset)\n            out_fname = os.path.join(out_dir, name + \".sb\")\n            process_profile(f, out_fname, sb_ops, ops_to_reverse, op_table, operation_nodes)\n\n    else:\n        if get_ios_major_version(args.release) >= 12:\n            f.seek(4)\n            vars_offset = struct.unpack(\"<H\", f.read(2))[0]\n            f.seek(10)\n            num_vars = struct.unpack(\"<B\", f.read(1))[0]\n            logger.info(\"{:d} global vars at offset 0x{:0x}\".format(num_vars, vars_offset))\n            global_vars = get_global_vars(f, vars_offset, num_vars, 0)\n            f.seek(12)\n        elif get_ios_major_version(args.release) >= 10:\n            f.seek(6)\n            vars_offset = struct.unpack(\"<H\", f.read(2))[0]\n            num_vars = struct.unpack(\"<H\", f.read(2))[0]\n            logger.info(\"{:d} global vars at offset 0x{:0x}\".format(num_vars, vars_offset))\n            global_vars = get_global_vars(f, vars_offset, num_vars, 0)\n            f.seek(10)\n        elif get_ios_major_version(args.release) >= 6:\n            f.seek(6)\n        else:\n            f.seek(4)\n        op_table = struct.unpack(\"<%dH\" % num_sb_ops, f.read(2 * num_sb_ops))\n        for idx in range(1, len(op_table)):\n            offset = op_table[idx]\n            operation = sb_ops[idx]\n            logger.info(\"operation %s (index %u) starts at node offset %u (0x%x)\", operation, idx, offset, offset)\n\n        # Place file pointer to start of operation nodes area.\n        while True:\n            word = struct.unpack(\"<H\", f.read(2))[0]\n            if word != 0:\n                f.seek(-2, 1)\n                break\n        start = f.tell()\n        end = re_table_offset * 8\n        num_operation_nodes = (end - start) / 8\n        logger.info(\"number of operation nodes: %d ; start: %#x\" % (num_operation_nodes, start))\n\n        operation_nodes = create_operation_nodes(f, regex_list,\n            num_operation_nodes, get_ios_major_version(args.release),\n            args.keep_builtin_filters, global_vars)\n        out_fname = os.path.join(out_dir, os.path.splitext(os.path.basename(args.filename))[0])\n        process_profile(f, out_fname, sb_ops, ops_to_reverse, op_table, operation_nodes)\n\n    f.close()\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
  },
  {
    "path": "reverse-sandbox/reverse_string.py",
    "content": "import sys\nimport struct\nimport logging\nimport time\n\n\nlogging.config.fileConfig(\"logger.config\")\nlogger = logging.getLogger(__name__)\n\n\nclass ReverseStringState:\n    binary_string = \"\"\n    len = 0\n    pos = 0\n    base = \"\"\n    base_stack = []\n    token = \"\"\n    token_stack = []\n    output_strings = []\n    STATE_UNKNOWN = 0\n    STATE_TOKEN_BYTE_READ = 1\n    STATE_CONCAT_BYTE_READ = 2\n    STATE_CONCAT_SAVE_BYTE_READ = 3\n    STATE_END_BYTE_READ = 4\n    STATE_SPLIT_BYTE_READ = 5\n    STATE_TOKEN_READ = 6\n    STATE_RANGE_BYTE_READ = 7\n    STATE_CONSTANT_READ = 8\n    STATE_SINGLE_BYTE_READ = 9\n    STATE_PLUS_READ = 10\n    STATE_RESET_STRING = 11\n    state_stack = []\n    state = STATE_UNKNOWN\n    state_byte = 0x00\n\n    def __init__(self, binary_string):\n        self.binary_string = binary_string\n        self.len = 0\n        self.pos = 0\n        self.base = \"\"\n        self.token = \"\"\n        self.tokens = []\n        self.token_stack = []\n        self.base_stack = []\n        self.output_strings = []\n        self.state_stack = []\n        self.state = self.STATE_UNKNOWN\n        self.state_byte = 0x00\n\n    def update_state_unknown(self):\n        self.state_stack.append(self.state)\n        self.state = self.STATE_UNKNOWN\n\n    def update_state_token_byte_read(self):\n        self.state_stack.append(self.state)\n        self.state = self.STATE_TOKEN_BYTE_READ\n\n    def update_state_concat_byte_read(self):\n        self.state_stack.append(self.state)\n        self.state = self.STATE_CONCAT_BYTE_READ\n\n    def update_state_concat_save_byte_read(self):\n        self.state_stack.append(self.state)\n        self.state = self.STATE_CONCAT_SAVE_BYTE_READ\n\n    def update_state_end_byte_read(self):\n        self.state_stack.append(self.state)\n        self.state = self.STATE_END_BYTE_READ\n\n    def update_state_split_byte_read(self):\n        self.state_stack.append(self.state)\n        self.state = self.STATE_SPLIT_BYTE_READ\n\n    def update_state_range_byte_read(self):\n        self.state_stack.append(self.state)\n        self.state = self.STATE_RANGE_BYTE_READ\n\n    def update_state_token_read(self):\n        self.state_stack.append(self.state)\n        self.state = self.STATE_TOKEN_READ\n\n    def update_state_reset_string(self):\n        self.state_stack.append(self.state)\n        self.state = self.STATE_RESET_STRING\n\n    def update_state_constant_read(self):\n        self.state_stack.append(self.state)\n        self.state = self.STATE_CONSTANT_READ\n\n    def update_state_single_byte_read(self):\n        self.state_stack.append(self.state)\n        self.state = self.STATE_SINGLE_BYTE_READ\n\n    def update_state_plus_read(self):\n        self.state_stack.append(self.state)\n        self.state = self.STATE_PLUS_READ\n\n    def update_state(self, b):\n        self.state_byte = b\n        if b == 0x0a:\n            self.update_state_end_byte_read()\n        elif b == 0x0f:\n            self.update_state_concat_byte_read()\n        elif b >= 0x80:\n            self.update_state_split_byte_read()\n        elif b == 0x00 or b == 0x07:\n            self.update_state_unknown()\n        elif b == 0x05:\n            self.update_state_reset_string()\n        elif b == 0x08:\n            self.update_state_concat_save_byte_read()\n            # XXX: Read two bytes. I don't know what they do.\n            self.get_next_byte()\n            self.get_next_byte()\n        elif b >= 0x10 and b < 0x3f:\n            self.update_state_constant_read()\n        elif b == 0x0b:\n            self.update_state_range_byte_read()\n        elif b == 0x02:\n            self.update_state_plus_read()\n        elif b == 0x06:\n            self.update_state_reset_string()\n        else:\n            self.update_state_token_byte_read()\n\n    def get_next_byte(self):\n        if self.is_end():\n            return 0x00\n        b = struct.unpack(\"<B\", self.binary_string[self.pos:self.pos+1])[0]\n        logger.debug(\"read byte 0x{:02x}\".format(b))\n        self.pos += 1\n        return b\n\n    def get_length_minus_1(self):\n        b = struct.unpack(\"<B\", self.binary_string[self.pos-1:self.pos])[0]\n        logger.debug(\"b is 0x{:02x} ({:d})\".format(b, b))\n        if b == 0x04:\n            b = struct.unpack(\"<B\", self.binary_string[self.pos:self.pos+1])[0]\n            logger.debug(\"got larger length 0x{:02x} ({:d})\".format(b, b))\n            self.pos += 1\n            return b + 0x41\n        else:\n            logger.debug(\"got length 0x{:02x} ({:d})\".format(b, b))\n            return b - 0x3f\n\n    def read_token(self, substr_len):\n        self.token_stack.append(self.token)\n        self.token = self.binary_string[self.pos:self.pos+substr_len]\n        logger.debug(\"got token \\\"{:s}\\\"\".format(self.token))\n        self.pos += substr_len\n\n    def update_base(self):\n        self.base += self.token\n        self.token = \"\"\n        logger.debug(\"update base to \\\"{:s}\\\"\".format(self.base))\n\n    def update_base_stack(self):\n        self.base_stack.append(self.base)\n        self.update_base()\n\n    def end_current_token(self):\n        self.output_strings.append(self.base+self.token)\n        logger.debug(\"output string \\\"{:s}\\\"\".format(self.base+self.token))\n        self.token = \"\"\n\n    def get_last_byte(self):\n        return struct.unpack(\"<B\", self.binary_string[self.pos-1:self.pos])[0]\n\n    def get_substring(self, substr_len):\n        substr = self.binary_string[self.pos:self.pos+substr_len]\n        logger.debug(\" \".join(\"0x{:02x}\".format(ord(c)) for c in substr))\n        self.pos += substr_len\n        return substr\n\n    def end_with_subtokens(self, subtokens):\n        for s in subtokens:\n            self.output_strings.append(self.base+self.token+s)\n            logger.debug(\"output string with subtokens \\\"{:s}\\\"\".format(self.base+self.token+s))\n        self.token = \"\"\n\n    def is_end(self):\n        if self.pos >= len(self.binary_string):\n            return True\n        return False\n\n    def reset_base(self):\n        if len(self.base_stack) >= 1:\n            self.base = self.base_stack.pop()\n\n    def reset_base_full(self):\n        self.base_stack = []\n        self.base = \"\"\n\n\nclass SandboxString:\n    rss_stack = []\n\n\n    def parse_byte_string(self, s, global_vars):\n        rss = ReverseStringState(s)\n        base = \"\"\n        reset_base = False\n        tokens = []\n        token = \"\"\n\n        while True:\n            if rss.state == rss.STATE_UNKNOWN:\n                logger.debug(\"state is STATE_UNKNOWN\")\n                b = rss.get_next_byte()\n                rss.update_state(b)\n            elif rss.state == rss.STATE_TOKEN_READ:\n                logger.debug(\"state is STATE_TOKEN_READ\")\n                b = rss.get_next_byte()\n                rss.update_state(b)\n            elif rss.state == rss.STATE_TOKEN_BYTE_READ:\n                logger.debug(\"state is STATE_TOKEN_BYTE_READ\")\n                # String starts with length.\n                prev_state = rss.state_stack[len(rss.state_stack)-1]\n                if prev_state != rss.STATE_TOKEN_READ:\n                    token_len = rss.get_length_minus_1()\n                    rss.read_token(token_len)\n                    rss.update_state_token_read()\n                else:\n                    logger.warn(\"read token byte from token state\")\n                    break\n            elif rss.state == rss.STATE_CONSTANT_READ:\n                logger.debug(\"state is STATE_CONSTANT_READ\")\n                b = rss.get_last_byte()\n                if b >= 0x10 and b < 0x3f:\n                    rss.token = \"${\" + global_vars[b-0x10] + \"}\"\n                b = rss.get_next_byte()\n                rss.update_state(b)\n            elif rss.state == rss.STATE_CONCAT_BYTE_READ:\n                logger.debug(\"state is STATE_CONCAT_BYTE_READ\")\n                if rss.state_stack[len(rss.state_stack)-1] == rss.STATE_TOKEN_READ or \\\n                        rss.state_stack[len(rss.state_stack)-1] == rss.STATE_CONSTANT_READ or \\\n                        rss.state_stack[len(rss.state_stack)-1] == rss.STATE_RANGE_BYTE_READ or \\\n                        rss.state_stack[len(rss.state_stack)-1] == rss.STATE_SINGLE_BYTE_READ or \\\n                        rss.state_stack[len(rss.state_stack)-1] == rss.STATE_PLUS_READ:\n                    rss.update_base()\n                b = rss.get_next_byte()\n                rss.update_state(b)\n            elif rss.state == rss.STATE_CONCAT_SAVE_BYTE_READ:\n                logger.debug(\"state is STATE_CONCAT_SAVE_BYTE_READ\")\n                if rss.state_stack[len(rss.state_stack)-1] == rss.STATE_TOKEN_READ or \\\n                        rss.state_stack[len(rss.state_stack)-1] == rss.STATE_CONSTANT_READ or \\\n                        rss.state_stack[len(rss.state_stack)-1] == rss.STATE_RANGE_BYTE_READ or \\\n                        rss.state_stack[len(rss.state_stack)-1] == rss.STATE_SINGLE_BYTE_READ or \\\n                        rss.state_stack[len(rss.state_stack)-1] == rss.STATE_PLUS_READ:\n                    rss.update_base_stack()\n                b = rss.get_next_byte()\n                rss.update_state(b)\n            elif rss.state == rss.STATE_END_BYTE_READ:\n                logger.debug(\"state is STATE_END_BYTE_READ\")\n                rss.end_current_token()\n                rss.reset_base()\n                b = rss.get_next_byte()\n                rss.update_state(b)\n            elif rss.state == rss.STATE_RANGE_BYTE_READ:\n                logger.debug(\"state is STATE_RANGE_BYTE_READ\")\n                rss.update_base_stack()\n                b = rss.get_next_byte()\n                b_array = []\n                all_ascii = True\n                token = \"\"\n                for i in range(0, b+1):\n                    b1 = rss.get_next_byte()\n                    b2 = rss.get_next_byte()\n                    if b1 < 0x20 or b1 > 0x7f or b2 < 0x20 or b2 > 0x7f:\n                        all_ascii = False\n                    b_array.append((b1,b2))\n                if all_ascii == False:\n                    (b1, b2) = b_array[0]\n                    (b3, b4) = b_array[1]\n                    if b2 == 0xff and b3 == 0x00:\n                        if b1-1 == b4+1:    # single char exclude\n                            token = \"[^{:c}]\".format(b1-1)\n                        else:               # range exclude\n                            token = \"[^{:c}-{:c}]\".format(b4+1, b1-1)\n                    else:\n                        token = \"[TODO]\"\n                else:\n                    token = \"[\"\n                    for (b1, b2) in b_array:\n                        token += \"{:c}-{:c}\".format(b1, b2)\n                    token += \"]\"\n                rss.token = token\n                b = rss.get_next_byte()\n                rss.update_state(b)\n            elif rss.state == rss.STATE_SPLIT_BYTE_READ:\n                logger.debug(\"state is STATE_SPLIT_BYTE_READ\")\n                substr_len = rss.get_last_byte() - 0x7f\n                substr = rss.get_substring(substr_len)\n                subtokens = self.parse_byte_string(substr, global_vars)\n                rss.end_with_subtokens(subtokens)\n                b = rss.get_next_byte()\n                rss.update_state(b)\n            elif rss.state == rss.STATE_SINGLE_BYTE_READ:\n                logger.debug(\"state is STATE_SINGLE_BYTE_READ\")\n                rss.read_token(1)\n                b = rss.get_next_byte()\n                rss.update_state(b)\n            elif rss.state == rss.STATE_RESET_STRING:\n                logger.debug(\"state is STATE_RESET_STRING\")\n                rss.end_current_token()\n                rss.reset_base_full()\n                b = rss.get_next_byte()\n                rss.update_state(b)\n            elif rss.state == rss.STATE_PLUS_READ:\n                logger.debug(\"state is STATE_PLUS_READ\")\n                if rss.state_stack[len(rss.state_stack)-1] == rss.STATE_CONCAT_BYTE_READ:\n                    rss.token = \"+\"\n                    rss.update_base()\n                else:\n                    logger.warn(\"previous state is not concat\")\n                rss.read_token(1)\n                b = rss.get_next_byte()\n                rss.update_state(b)\n            else:\n                logger.warn(\"unknown state ({:d})\".format(rss.state))\n                break\n\n            if rss.is_end():\n                break\n\n        # String must end in a STATE_END_BYTE_READ byte.\n        if rss.state == rss.STATE_END_BYTE_READ:\n            logger.debug(\"state is STATE_END_BYTE_READ\")\n            rss.end_current_token()\n        elif rss.state == rss.STATE_UNKNOWN or rss.state == rss.STATE_CONCAT_BYTE_READ:\n            pass\n        elif rss.state_stack[len(rss.state_stack)-1] == rss.STATE_END_BYTE_READ:\n            pass\n        else:\n            logger.warn(\"last state is not STATE_END_BYTE_READ ({:d})\".format(rss.state))\n            logger.warn(\"previous state ({:d})\".format(rss.state_stack[len(rss.state_stack)-1]))\n\n        logger.info(\"initial string: \" + \" \".join(\"0x{:02x}\".format(ord(c)) for c in s))\n        logger.info(\"output_strings (num: {:d}): {:s}\".format(len(rss.output_strings), \",\".join('\"{:s}\"'.format(s) for s in rss.output_strings)))\n        return rss.output_strings\n\n\n    def __init__(self):\n        self.rss_stack = []\n\n\ndef main():\n    s = sys.stdin.read()\n    ss = SandboxString()\n    my_global_vars = [\"FRONT_USER_HOME\", \"HOME\", \"PROCESS_TEMP_DIR\"]\n    l = ss.parse_byte_string(s[4:], my_global_vars)\n    print(list(set(l)))\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
  },
  {
    "path": "reverse-sandbox/sandbox_filter.py",
    "content": "#!/usr/bin/env python\n\nimport struct\nimport re\nimport logging\nimport logging.config\nimport reverse_sandbox\nimport reverse_string\n\nfrom filters import Filters\n\n\nlogging.config.fileConfig(\"logger.config\")\nlogger = logging.getLogger(__name__)\n\nios_major_version = 4\nkeep_builtin_filters = False\nglobal_vars = []\nbase_addr = 0\n\ndef get_filter_arg_string_by_offset(f, offset):\n    \"\"\"Extract string (literal) from given offset.\"\"\"\n    f.seek(base_addr + offset * 8)\n    if ios_major_version >= 13:\n        len = struct.unpack(\"<H\", f.read(2))[0]\n        s = f.read(len)\n        logger.info(\"binary string is \" + s.encode(\"hex\"))\n        ss = reverse_string.SandboxString()\n        myss = ss.parse_byte_string(s, global_vars)\n        actual_string = \"\"\n        for sss in myss:\n            actual_string = actual_string + sss + \" \"\n        actual_string = actual_string[:-1]\n        logger.info(\"actual string is \" + actual_string)\n        return myss\n    len = struct.unpack(\"<I\", f.read(4))[0]\n    if ios_major_version >= 10:\n        f.seek(offset * 8)\n        s = f.read(4+len)\n        logger.info(\"binary string is \" + s.encode(\"hex\"))\n        ss = reverse_string.SandboxString()\n        myss = ss.parse_byte_string(s[4:], global_vars)\n        actual_string = \"\"\n        for sss in myss:\n            actual_string = actual_string + sss + \" \"\n        actual_string = actual_string[:-1]\n        logger.info(\"actual string is \" + actual_string)\n        return myss\n    type = struct.unpack(\"<B\", f.read(1))[0]\n    return '\"%s\"' % f.read(len)\n\n\ndef get_filter_arg_string_by_offset_with_type(f, offset):\n    \"\"\"Extract string from given offset and consider type byte.\"\"\"\n    global ios_major_version\n    global keep_builtin_filters\n    f.seek(base_addr + offset * 8)\n    if ios_major_version >= 13:\n        len = struct.unpack(\"<H\", f.read(2))[0]\n        s = f.read(len)\n        logger.info(\"binary string is \" + s.encode(\"hex\"))\n        ss = reverse_string.SandboxString()\n        myss = ss.parse_byte_string(s, global_vars)\n        append = \"literal\"\n        actual_string = \"\"\n        for sss in myss:\n            actual_string = actual_string + sss + \" \"\n        actual_string = actual_string[:-1]\n        logger.info(\"actual string is \" + actual_string)\n        return (append, myss)\n    len = struct.unpack(\"<I\", f.read(4))[0]\n    if ios_major_version >= 10:\n        f.seek(base_addr + offset * 8)\n        s = f.read(4+len)\n        logger.info(\"binary string is \" + s.encode(\"hex\"))\n        ss = reverse_string.SandboxString()\n        myss = ss.parse_byte_string(s[4:], global_vars)\n        append = \"literal\"\n        actual_string = \"\"\n        for sss in myss:\n            actual_string = actual_string + sss + \" \"\n        actual_string = actual_string[:-1]\n        logger.info(\"actual string is \" + actual_string)\n        return (append, myss)\n    type = struct.unpack(\"<B\", f.read(1))[0]\n    append = \"\"\n    if type == 0x00 or type == 0x04 or type == 0x02:\n        append += \"literal\"\n    elif type == 0x01 or type == 0x05:\n        append += \"subpath\"\n    elif type == 0x0c or type == 0x0e or type == 0x14 or type == 0x16:\n        append += \"literal-prefix\"\n    elif type == 0x0d or type == 0x15:\n        append += \"subpath-prefix\"\n    elif type == 0x06 or type == 0x24:\n        append += \"prefix\"\n    else:\n        logger.warn(\"Unknown type for string type: {} at offset {}\".format(type, offset * 8))\n    actual_string = f.read(len)\n    if actual_string == \"/private/var/tmp/launchd/sock\" and keep_builtin_filters == False:\n        return (append, \"###$$$***\")\n    return (append, '\"%s\"' % actual_string)\n\n\ndef get_filter_arg_string_by_offset_no_skip(f, offset):\n    \"\"\"Extract string from given offset and ignore type byte.\"\"\"\n    f.seek(base_addr + offset * 8)\n    if ios_major_version >= 13:\n        len = struct.unpack(\"<H\", f.read(2))[0]-1\n    else:\n        len = struct.unpack(\"<I\", f.read(4))[0]-1\n    return '\"%s\"' % f.read(len)\n\n\ndef get_filter_arg_network_address(f, offset):\n    \"\"\"Convert 4 bytes value to network address (host and port).\"\"\"\n    f.seek(base_addr + offset * 8)\n\n    host, port = struct.unpack(\"<HH\", f.read(4))\n    host_port_string = \"\"\n    if host == 0x1:\n        proto = \"ip4\"\n        host_port_string += \"*\"\n    elif host == 0x2:\n        proto = \"ip6\"\n        host_port_string += \"*\"\n    elif host == 0x3:\n        proto = \"ip\"\n        host_port_string += \"*\"\n    elif host == 0x5:\n        proto = \"tcp4\"\n        host_port_string += \"*\"\n    elif host == 0x6:\n        proto = \"tcp6\"\n        host_port_string += \"*\"\n    elif host == 0x7:\n        proto = \"tcp\"\n        host_port_string += \"*\"\n    elif host == 0x9:\n        proto = \"udp4\"\n        host_port_string += \"*\"\n    elif host == 0xa:\n        proto = \"udp6\"\n        host_port_string += \"*\"\n    elif host == 0xb:\n        proto = \"udp\"\n        host_port_string += \"*\"\n    elif host == 0x101:\n        proto = \"ip4\"\n        host_port_string += \"localhost\"\n    elif host == 0x102:\n        proto = \"ip6\"\n        host_port_string += \"localhost\"\n    elif host == 0x103:\n        proto = \"ip\"\n        host_port_string += \"localhost\"\n    elif host == 0x105:\n        proto = \"tcp4\"\n        host_port_string += \"localhost\"\n    elif host == 0x106:\n        proto = \"tcp6\"\n        host_port_string += \"localhost\"\n    elif host == 0x107:\n        proto = \"tcp\"\n        host_port_string += \"localhost\"\n    elif host == 0x109:\n        proto = \"udp4\"\n        host_port_string += \"localhost\"\n    elif host == 0x10a:\n        proto = \"udp6\"\n        host_port_string += \"localhost\"\n    elif host == 0x10b:\n        proto = \"udp\"\n        host_port_string += \"localhost\"\n    else:\n        proto = \"unknown\"\n        host_port_string += \"0x%x\" % host\n\n    if port == 0:\n        host_port_string += \":*\"\n    else:\n        host_port_string += \":%d\" % (port)\n    return '%s \"%s\"' % (proto, host_port_string)\n\n\ndef get_filter_arg_integer(f, arg):\n    \"\"\"Convert integer value to decimal string representation.\"\"\"\n    return '%d' % arg\n\n\ndef get_filter_arg_octal_integer(f, arg):\n    \"\"\"Convert integer value to octal string representation.\"\"\"\n    return '#o%04o' % arg\n\n\ndef get_filter_arg_boolean(f, arg):\n    \"\"\"Convert boolean value to scheme boolean string representation.\"\"\"\n    if arg == 1:\n        return '#t'\n    else:\n        return '#f'\n\n\nregex_list = []\ndef get_filter_arg_regex_by_id(f, regex_id):\n    \"\"\"Get regular expression by index.\"\"\"\n    global keep_builtin_filters\n    return_string = \"\"\n    global regex_list\n    for regex in regex_list[regex_id]:\n        if re.match(\"^/com\\\\\\.apple\\\\\\.sandbox\\$\", regex) and keep_builtin_filters == False:\n            return \"###$$$***\"\n        return_string += ' #\"%s\"' % (regex)\n    return return_string[1:]\n\n\ndef get_filter_arg_ctl(f, arg):\n    \"\"\"Convert integer value to IO control string.\"\"\"\n    letter = chr(arg >> 8)\n    number = arg & 0xff\n    return '(_IO \"%s\" %d)' % (letter, number)\n\n\ndef get_filter_arg_vnode_type(f, arg):\n    \"\"\"Convert integer to file (vnode) type string.\"\"\"\n    arg_types = {\n            0x01: \"REGULAR-FILE\",\n            0x02: \"DIRECTORY\",\n            0x03: \"BLOCK-DEVICE\",\n            0x04: \"CHARACTER-DEVICE\",\n            0x05: \"SYMLINK\",\n            0x06: \"SOCKET\",\n            0x07: \"FIFO\",\n            0xffff: \"TTY\"\n            }\n    if arg in arg_types.keys():\n        return '%s' % (arg_types[arg])\n    else:\n        return '%d' % arg\n\n\ndef get_filter_arg_owner(f, arg):\n    \"\"\"Convert integer to process owner string.\"\"\"\n    arg_types = {\n            0x01: \"self\",\n            0x02: \"pgrp\",\n            0x03: \"others\",\n            0x04: \"children\",\n            0x05: \"same-sandbox\"\n            }\n    if arg in arg_types.keys():\n        return '%s' % (arg_types[arg])\n    else:\n        return '%d' % arg\n\n\ndef get_filter_arg_socket_domain(f, arg):\n    \"\"\"Convert integer to socket domain string.\"\"\"\n    arg_types = {\n            0: \"AF_UNSPEC\",\n            1: \"AF_UNIX\",\n            2: \"AF_INET\",\n            3: \"AF_IMPLINK\",\n            4: \"AF_PUP\",\n            5: \"AF_CHAOS\",\n            6: \"AF_NS\",\n            7: \"AF_ISO\",\n            8: \"AF_ECMA\",\n            9: \"AF_DATAKIT\",\n            10: \"AF_CCITT\",\n            11: \"AF_SNA\",\n            12: \"AF_DECnet\",\n            13: \"AF_DLI\",\n            14: \"AF_LAT\",\n            15: \"AF_HYLINK\",\n            16: \"AF_APPLETALK\",\n            17: \"AF_ROUTE\",\n            18: \"AF_LINK\",\n            19: \"pseudo_AF_XTP\",\n            20: \"AF_COIP\",\n            21: \"AF_CNT\",\n            22: \"pseudo_AF_RTIP\",\n            23: \"AF_IPX\",\n            24: \"AF_SIP\",\n            25: \"pseudo_AF_PIP\",\n            27: \"AF_NDRV\",\n            28: \"AF_ISDN\",\n            29: \"pseudo_AF_KEY\",\n            30: \"AF_INET6\",\n            31: \"AF_NATM\",\n            32: \"AF_SYSTEM\",\n            33: \"AF_NETBIOS\",\n            34: \"AF_PPP\",\n            35: \"pseudo_AF_HDRCMPLT\",\n            36: \"AF_RESERVED_36\",\n            37: \"AF_IEEE80211\",\n            38: \"AF_UTUN\",\n            40: \"AF_MAX\"\n            }\n    if arg in arg_types.keys():\n        return '%s' % (arg_types[arg])\n    else:\n        return '%d' % arg\n\n\ndef get_filter_arg_socket_type(f, arg):\n    \"\"\"Convert integer to socket type string.\"\"\"\n    arg_types = {\n        0x01: \"SOCK_STREAM\",\n        0x02: \"SOCK_DGRAM\",\n        0x03: \"SOCK_RAW\",\n        0x04: \"SOCK_RDM\",\n        0x05: \"SOCK_SEQPACKET\"\n        }\n    if arg in arg_types.keys():\n        return '\"%s\"' % (arg_types[arg])\n    else:\n        return '%d' % arg\n\n\ndef get_none(f, arg):\n    \"\"\"Dumb callback function\"\"\"\n    return None\n\n\ndef get_filter_arg_privilege_id(f, arg):\n    \"\"\"Convert integer to privilege id string.\"\"\"\n    arg_types = {\n            1000: \"PRIV_ADJTIME\",\n            1001: \"PRIV_PROC_UUID_POLICY\",\n            1002: \"PRIV_GLOBAL_PROC_INFO\",\n            1003: \"PRIV_SYSTEM_OVERRIDE\",\n            1004: \"PRIV_HW_DEBUG_DATA\",\n            1005: \"PRIV_SELECTIVE_FORCED_IDLE\",\n            1006: \"PRIV_PROC_TRACE_INSPECT\",\n            1008: \"PRIV_KERNEL_WORK_INTERNAL\",\n            6000: \"PRIV_VM_PRESSURE\",\n            6001: \"PRIV_VM_JETSAM\",\n            6002: \"PRIV_VM_FOOTPRINT_LIMIT\",\n            10000: \"PRIV_NET_PRIVILEGED_TRAFFIC_CLASS\",\n            10001: \"PRIV_NET_PRIVILEGED_SOCKET_DELEGATE\",\n            10002: \"PRIV_NET_INTERFACE_CONTROL\",\n            10003: \"PRIV_NET_PRIVILEGED_NETWORK_STATISTICS\",\n            10004: \"PRIV_NET_PRIVILEGED_NECP_POLICIES\",\n            10005: \"PRIV_NET_RESTRICTED_AWDL\",\n            10006: \"PRIV_NET_PRIVILEGED_NECP_MATCH\",\n            11000: \"PRIV_NETINET_RESERVEDPORT\",\n            14000: \"PRIV_VFS_OPEN_BY_ID\",\n        }\n    if arg in arg_types.keys():\n        return '\"%s\"' % (arg_types[arg])\n    else:\n        return '%d' % arg\n\n\ndef get_filter_arg_process_attribute(f, arg):\n    \"\"\"Convert integer to process attribute string.\"\"\"\n    arg_types = {\n            0: 'is-plugin',\n            1: 'is-installer',\n            2: 'is-restricted',\n            3: 'is-initproc',\n        }\n    if arg in arg_types.keys():\n        return '%s' % (arg_types[arg])\n    else:\n        return '%d' % arg\n\n\ndef get_filter_arg_csr(f, arg):\n    \"\"\"Convert integer to csr string.\"\"\"\n    arg_types = {\n            1: 'CSR_ALLOW_UNTRUSTED_KEXTS',\n            2: 'CSR_ALLOW_UNRESTRICTED_FS',\n            4: 'CSR_ALLOW_TASK_FOR_PID',\n            8: 'CSR_ALLOW_KERNEL_DEBUGGER',\n            16: 'CSR_ALLOW_APPLE_INTERNAL',\n            32: 'CSR_ALLOW_UNRESTRICTED_DTRACE',\n            64: 'CSR_ALLOW_UNRESTRICTED_NVRAM',\n            128: 'CSR_ALLOW_DEVICE_CONFIGURATION',\n        }\n    if arg in arg_types.keys():\n        return '\"%s\"' % (arg_types[arg])\n    else:\n        return '%d' % arg\n\n\ndef get_filter_arg_host_port(f, arg):\n    \"\"\"Convert integer to host special port string.\"\"\"\n    arg_types = {\n            8: 'HOST_DYNAMIC_PAGER_PORT',\n            9: 'HOST_AUDIT_CONTROL_PORT',\n            10: 'HOST_USER_NOTIFICATION_PORT',\n            11: 'HOST_AUTOMOUNTD_PORT',\n            12: 'HOST_LOCKD_PORT',\n            13: 'unknown: 13',\n            14: 'HOST_SEATBELT_PORT',\n            15: 'HOST_KEXTD_PORT',\n            16: 'HOST_CHUD_PORT',\n            17: 'HOST_UNFREED_PORT',\n            18: 'HOST_AMFID_PORT',\n            19: 'HOST_GSSD_PORT',\n            20: 'HOST_TELEMETRY_PORT',\n            21: 'HOST_ATM_NOTIFICATION_PORT',\n            22: 'HOST_COALITION_PORT',\n            23: 'HOST_SYSDIAGNOSE_PORT',\n            24: 'HOST_XPC_EXCEPTION_PORT',\n            25: 'HOST_CONTAINERD_PORT',\n        }\n    if arg in arg_types.keys():\n        return '\"%s\"' % (arg_types[arg])\n    else:\n        return '%d' % arg\n\n\n\"\"\"An array (dictionary) of filter converting items\n\nA filter is identied by a filter id and a filter argument. They are\nboth stored in binary format (numbers) inside the binary sandbox\nprofile file.\n\nEach item in the dictionary is identied by the filter id (used in\nhexadecimal). The value of each item is the string form of the filter id\nand the callback function used to convert the binary form the filter\nargument to a string form.\n\nWhile there is a one-to-one mapping between the binary form and the\nstring form of the filter id, that is not the case for the filter\nargument. To convert the binary form of the filter argument to its\nstring form we use one of the callback functions above; almost all\ncallback function names start with get_filter_arg_.\n\"\"\"\n\ndef convert_filter_callback(f, ios_major_version_arg, keep_builtin_filters_arg,\n        global_vars_arg, re_list, filter_id, filter_arg, base_addr_arg):\n    \"\"\"Convert filter from binary form to string.\n\n    Binary form consists of filter id and filter argument:\n      * filter id is the index inside the filters array above\n      * filter argument is an actual parameter (such as a port number),\n        a file offset or a regular expression index\n\n    The string form consists of the name of the filter (as extracted\n    from the filters array above) and a string representation of the\n    filter argument. The string form of the filter argument if obtained\n    from the binary form through the use of the callback function (as\n    extracted frm the filters array above).\n\n    Function arguments are:\n      f: the binary sandbox profile file\n      regex_list: list of regular expressions\n      filter_id: the binary form of the filter id\n      filter_arg: the binary form of the filter argument\n    \"\"\"\n\n    global regex_list\n    global ios_major_version\n    global keep_builtin_filters\n    global global_vars\n    global base_addr\n    keep_builtin_filters = keep_builtin_filters_arg\n    ios_major_version = ios_major_version_arg\n    global_vars = global_vars_arg\n    regex_list = re_list\n    base_addr = base_addr_arg\n\n    if not Filters.exists(ios_major_version, filter_id):\n        logger.warn(\"filter_id {} not in keys\".format(filter_id))\n        return (None, None)\n    filter = Filters.get(ios_major_version, filter_id)\n    if not filter[\"arg_process_fn\"]:\n        logger.warn(\"no function for filter {}\".format(filter_id))\n        return (None, None)\n    if filter[\"arg_process_fn\"] == \"get_filter_arg_string_by_offset_with_type\":\n        (append, result) = globals()[filter[\"arg_process_fn\"]](f, filter_arg)\n        if filter_id == 0x01 and append == \"path\":\n            append = \"subpath\"\n        if result == None and filter[\"name\"] != \"debug-mode\":\n            logger.warn(\"result of calling string offset for filter {} is none\".format(filter_id))\n            return (None, None)\n        return (filter[\"name\"] + append, result)\n    result = globals()[filter[\"arg_process_fn\"]](f, filter_arg)\n    if result == None and not ((filter[\"name\"] in [\"debug-mode\", \"syscall-mask\", \"machtrap-mask\", \"kernel-mig-routine-mask\"]) or\n            (filter[\"name\"] in [\"extension\", \"mach-extension\"]\n                and ios_major_version <= 5)):\n        logger.warn(\"result of calling arg_process_fn for filter {} is none\".format(filter_id))\n        return (None, None)\n    return (filter[\"name\"], result)\n"
  },
  {
    "path": "reverse-sandbox/sandbox_regex.py",
    "content": "#!/usr/bin/env python3\n\nimport struct\nimport logging\nimport logging.config\n\nlogging.config.fileConfig(\"logger.config\")\nlogger = logging.getLogger(__name__)\n\nfrom regex_parser_v1 import RegexParser as RegexParserV1\nfrom regex_parser_v2 import RegexParser as RegexParserV2\nfrom regex_parser_v3 import RegexParser as RegexParserV3\n\nclass Node():\n    \"\"\"Representation of a node inside a regex non-deterministic automaton\n\n    The most important attribute is the node type, which may be any of\n    the four macros TYPE_... below.\n    \"\"\"\n\n    TYPE_JUMP_FORWARD = 1\n    TYPE_JUMP_BACKWARD = 2\n    TYPE_CHARACTER = 3\n    TYPE_END = 4\n    FLAG_WHITE = 1\n    FLAG_GREY = 2\n    FLAG_BLACK = 3\n    name = \"\"\n    type = None\n    value = None\n    flag = \"white\"\n\n    def __init__(self, name=None, type=None, value=''):\n        self.name = name\n        self.type = type\n        self.value = value\n        self.flag = self.FLAG_WHITE\n\n    def set_name(self, name):\n        self.name = name\n\n    def set_type_jump_forward(self):\n        self.type = self.TYPE_JUMP_FORWARD\n\n    def set_type_jump_backward(self):\n        self.type = self.TYPE_JUMP_BACKWARD\n\n    def set_type_character(self):\n        self.type = self.TYPE_CHARACTER\n\n    def set_type_end(self):\n        self.type = self.TYPE_END\n\n    def is_type_end(self):\n        return self.type == self.TYPE_END\n\n    def is_type_jump(self):\n        return self.type == self.TYPE_JUMP_BACKWARD or self.type == self.TYPE_JUMP_FORWARD\n\n    def is_type_jump_backward(self):\n        return self.type == self.TYPE_JUMP_BACKWARD\n\n    def is_type_jump_forward(self):\n        return self.type == self.TYPE_JUMP_FORWARD\n\n    def is_type_character(self):\n        return self.type == self.TYPE_CHARACTER\n\n    def set_value(self, value):\n        self.value = value\n\n    def set_flag_white(self):\n        self.flag = self.FLAG_WHITE\n\n    def set_flag_grey(self):\n        self.flag = self.FLAG_GREY\n\n    def set_flag_black(self):\n        self.flag = self.FLAG_BLACK\n\n    def __str__(self):\n        if self.type == self.TYPE_JUMP_BACKWARD:\n            return \"(%s: jump backward)\" % (self.name)\n        elif self.type == self.TYPE_JUMP_FORWARD:\n            return \"(%s: jump forward)\" % (self.name)\n        elif self.type == self.TYPE_END:\n            return \"(%s: end)\" % (self.name)\n        else:\n            return \"(%s: %s)\" % (self.name, self.value)\n\n\nclass Graph():\n    \"\"\"Representation of a regex NDA (Non-Deterministic Automaton)\n\n    Use this class to convert a regex list of items into its canonical\n    regular expression string.\n    \"\"\"\n\n    graph_dict = {}\n    canon_graph_dict = {}\n    node_list = []\n    start_node = None\n    end_states = []\n    start_state = 0\n    regex = []\n    unified_regex = \"\"\n\n    def __init__(self):\n        self.graph_dict = {}\n\n    def add_node(self, node, next_list=None):\n        self.graph_dict[node] = next_list\n\n    def has_node(self, node):\n        return node in graph_dict.keys()\n\n    def update_node(self, node, next_list):\n        self.graph_dict[node] = next_list\n\n    def add_new_next_to_node(self, node, next):\n        self.graph_dict[node].append(next)\n\n    def __str__(self):\n        # Get maximum node number.\n        max = -1\n        for node in self.graph_dict.keys():\n            if max < int(node.name):\n                max = int(node.name)\n\n        # Create graph list for ordered listing of nodes.\n        graph_list = [None] * (max+1)\n        for node in self.graph_dict.keys():\n            actual_string = str(node) + \":\"\n            for next_node in self.graph_dict[node]:\n                actual_string += \" \" + str(next_node)\n            graph_list[int(node.name)] = actual_string\n\n        # Store node graph in ret_string.\n        ret_string = \"\\n-- Node graph --\\n\"\n        for s in graph_list:\n            if s:\n                ret_string += s + \"\\n\"\n\n        # Store canonical graph in ret_string.\n        ret_string += \"\\n-- Canonical graph --\\n\"\n        for state in self.canon_graph_dict.keys():\n            if state == self.start_state:\n                ret_string += \"> \"\n            elif state in self.end_states:\n                ret_string += \"# \"\n            else:\n                ret_string += \"  \"\n            ret_string += \"%d: %s\\n\" % (state, self.canon_graph_dict[state])\n        ret_string += \"\\n\"\n        return ret_string\n\n    def get_node_for_idx(self, idx):\n        if idx >= len(self.node_list) or idx < 0:\n            return None\n        return self.node_list[idx]\n\n    def get_re_index_for_pos(self, regex_list, pos):\n        for idx, item in enumerate(regex_list):\n            if item[\"pos\"] == pos:\n                return idx\n        for idx, item in enumerate(regex_list):\n            if item[\"pos\"]-1 == pos:\n                return idx\n        return -1\n\n    def get_next_idx_for_regex_item(self, regex_list, regex_item):\n        result = self.get_re_index_for_pos(regex_list, regex_item[\"nextpos\"])\n        assert(result >= 0)\n        return result\n\n    def fill_from_regex_list(self, regex_list):\n        # First create list of nodes. No pointers/links at this point.\n        # Create a node for each item.\n        self.node_list = []\n        for idx, item in enumerate(regex_list):\n            node = Node(name=\"%s\" % (idx))\n            if item[\"type\"] == \"jump_backward\":\n                node.set_type_jump_backward()\n            elif item[\"type\"] == \"jump_forward\":\n                node.set_type_jump_forward()\n            elif item[\"type\"] == \"end\":\n                node.set_type_end()\n            else:\n                node.set_type_character()\n                node.set_value(item[\"value\"])\n\n            if 'start_node' in item and item['start_node'] == True:\n                assert(self.start_node == None)\n                self.start_node = node\n            self.node_list.append(node)\n\n        self.graph_dict = {}\n        for idx, node in enumerate(self.node_list):\n            # If node is end node ignore.\n            if node.is_type_end():\n                 self.graph_dict[node] = []\n            elif node.is_type_character():\n                next = self.get_node_for_idx(\n                    self.get_next_idx_for_regex_item(regex_list, regex_list[idx]))\n                if next:\n                    self.graph_dict[node] = [ next ]\n                else:\n                    self.graph_dict[node] = []\n            # Node is jump node.\n            elif node.is_type_jump_backward():\n                next_idx = self.get_re_index_for_pos(regex_list, regex_list[idx][\"value\"])\n                next = self.get_node_for_idx(next_idx)\n                if next:\n                    self.graph_dict[node] = [next]\n                else:\n                    self.graph_dict[node] = []\n            elif node.is_type_jump_forward():\n                next_idx1 = self.get_next_idx_for_regex_item(\n                    regex_list, regex_list[idx])\n                next_idx2 = self.get_re_index_for_pos(regex_list, regex_list[idx][\"value\"])\n                next1 = self.get_node_for_idx(next_idx1)\n                next2 = self.get_node_for_idx(next_idx2)\n                self.graph_dict[node] = []\n                if next1:\n                    self.graph_dict[node].append(next1)\n                if next2:\n                    self.graph_dict[node].append(next2)\n\n    def get_character_nodes(self, node):\n        node_list = []\n        for next in self.graph_dict[node]:\n            if next.is_type_character() or next.is_type_end():\n                node_list.append(next)\n            else:\n                node_list = list(set(node_list).union(self.get_character_nodes(next)))\n        return node_list\n\n    def find_node_type_jump(self, current_node, node, backup_dict):\n        if not current_node.is_type_jump():\n            return False\n        if current_node == node:\n            return True\n        if not self.graph_dict[current_node]:\n            return False\n        for next_node in backup_dict[current_node]:\n            if self.find_node_type_jump(next_node, node, backup_dict):\n                return True\n        return False\n\n    def reduce(self):\n        for node in self.graph_dict.keys():\n            if node.is_type_character():\n                self.graph_dict[node] = self.get_character_nodes(node)\n        old_dict = dict(self.graph_dict)\n        backup_dict = dict(self.graph_dict)\n        for node in old_dict.keys():\n            if node.is_type_jump():\n                if self.find_node_type_jump(self.start_node,\n                        node, backup_dict):\n                    continue\n                del self.graph_dict[node]\n\n    def get_edges(self, node):\n        edges = []\n        is_end_state = False\n        for next in self.graph_dict[node]:\n            if next.is_type_end():\n                is_end_state = True\n            else:\n                edges.append((next.value, int(next.name)))\n        return is_end_state, edges\n\n    def convert_to_canonical(self):\n        self.end_states = []\n        for node in self.graph_dict.keys():\n            if node.is_type_end():\n                continue\n            state_idx = int(node.name)\n            is_end_state, self.canon_graph_dict[state_idx] = self.get_edges(node)\n            if is_end_state == True:\n                self.end_states.append(state_idx)\n        for node in self.graph_dict.keys():\n            if node.name == \"0\":\n                self.start_state = -1\n                self.canon_graph_dict[-1] = [ (node.value, 0) ]\n        logger.debug(self.canon_graph_dict)\n        logger.debug(\"end_states:\")\n        logger.debug(self.end_states)\n        logger.debug(\"start_state:\")\n        logger.debug(self.start_state)\n\n    def need_use_plus(self, initial_string, string_to_add):\n        if not string_to_add.endswith(\"*\"):\n            return False\n\n        if string_to_add.startswith(\"(\") and string_to_add[-2:-1] == \")\":\n            actual_part = string_to_add[1:-2]\n        else:\n            actual_part = string_to_add[:-1]\n        if initial_string.endswith(actual_part):\n            return True\n        if initial_string.endswith(string_to_add):\n            return True\n\n        return False\n\n    def unify_two_strings(self, s1, s2):\n        # Find largest common starting substring.\n        lcss = \"\"\n        for i in range(1, len(s1)+1):\n            if s2.find(s1[:i], 0, i) != -1:\n                lcss = s1[:i]\n        if lcss:\n            s1 = s1[len(lcss):]\n            s2 = s2[len(lcss):]\n        # Find largest common ending substring.\n        lces = \"\"\n        for i in range(1, len(s1)+1):\n            if s2.find(s1[-i:], len(s2)-i, len(s2)) != -1:\n                lces = s1[-i:]\n        if lces:\n            s1 = s1[:len(s1)-len(lces)]\n            s2 = s2[:len(s2)-len(lces)]\n\n        if not s1 and not s2:\n            return lcss + lces\n\n        if s1 and s2:\n            return lcss + \"(\" + s1 + \"|\" + s2 + \")\" + lces\n\n        # Make s1 the empty string.\n        if not s2:\n            aux = s1\n            s1 = s2\n            s2 = aux\n\n        if s2[-1] == '+':\n            s2 = s2[:-1] + '*'\n        else:\n            if len(s2) > 1:\n                s2 = \"(\" + s2 + \")?\"\n            else:\n                s2 = s2 + '?'\n\n        return lcss + s2 + lces\n\n    def unify_strings(self, string_list):\n        unified = \"\"\n        if not string_list:\n            return None\n        if len(string_list) == 1:\n            return string_list[0]\n        # We now know we have multiple strings. Merge two at a time.\n        current = string_list[0]\n        for s in string_list[1:]:\n            current = self.unify_two_strings(current, s)\n        return current\n\n    def remove_state(self, state_to_remove):\n        itself_string = \"\"\n        for (next_string, next_state) in self.canon_graph_dict[state_to_remove]:\n            if next_state == state_to_remove:\n                if len(next_string) > 1:\n                    itself_string = \"(%s)*\" % next_string\n                else:\n                    itself_string = \"%s*\" % next_string\n\n        # Create list of to_strings indexed by to_states.\n        to_strings = {}\n        for to_state in self.canon_graph_dict.keys():\n            to_strings[to_state] = []\n            if to_state == state_to_remove:\n                continue\n            for (iter_to_string, iter_to_state) in self.canon_graph_dict[state_to_remove]:\n                if iter_to_state == to_state:\n                    to_strings[to_state].append(iter_to_string)\n\n        # Unify multiple strings leading to the same to_state.\n        unified_to_string = {}\n        for to_state in to_strings.keys():\n            unified_to_string[to_state] = self.unify_strings(to_strings[to_state])\n\n        # Go through all graph edges.\n        for from_state in self.canon_graph_dict.keys():\n            # Pass current state to remove.\n            if from_state == state_to_remove:\n                continue\n            items_to_remove_list = []\n            for (next_string, next_state) in self.canon_graph_dict[from_state]:\n                # Only if edge points to state_to_remove.\n                if next_state != state_to_remove:\n                    continue\n                # Plan edge to remove. Create new edge bypassing state_to_remove.\n                items_to_remove_list.append((next_string, next_state))\n                for to_state in self.canon_graph_dict.keys():\n                    if len(to_strings[to_state]) == 0:\n                        continue\n                    to_string = unified_to_string[to_state]\n                #for (to_string, to_state) in self.canon_graph_dict[state_to_remove]:\n                #    # If state points to itself, do not add edge.\n                #    if to_state == state_to_remove:\n                #        continue\n                    # Add new edge, consider if state points to itself.\n                    if self.need_use_plus(next_string, itself_string):\n                        self.canon_graph_dict[from_state].append((next_string + \"+\" + to_string, to_state))\n                        continue\n                    self.canon_graph_dict[from_state].append((next_string + itself_string + to_string, to_state))\n            for (next_string, next_state) in items_to_remove_list:\n                self.canon_graph_dict[from_state].remove((next_string, next_state))\n\n        del self.canon_graph_dict[state_to_remove]\n\n    def simplify(self):\n        tmp_dict = dict(self.canon_graph_dict)\n        for state in tmp_dict.keys():\n            if state != self.start_state and state not in self.end_states:\n                self.remove_state(state)\n\n    def combine_start_end_nodes(self):\n        working_strings = self.canon_graph_dict[self.start_state]\n        final_strings = []\n        string_added = True\n        while string_added == True:\n            string_added = False\n            initial_strings = working_strings\n            working_strings = []\n            for (start_string, start_next_state) in initial_strings:\n                if not start_next_state in self.end_states:\n                    continue\n                if self.canon_graph_dict[start_next_state]:\n                    for (next_string, next_state) in self.canon_graph_dict[start_next_state]:\n                        if next_state == start_next_state:\n                            next_string = \"(%s)*\" % next_string\n                            if self.need_use_plus(start_string, next_string):\n                                final_strings.append((start_string + \"+\", None))\n                            else:\n                                final_strings.append((start_string + next_string, None))\n                        else:\n                            final_strings.append((start_string + next_string, None))\n                            working_strings.append((start_string + next_string, next_state))\n                else:\n                    final_strings.append((start_string, None))\n                string_added = True\n        self.regex = [x[0] for x in final_strings]\n        self.unified_regex = self.unify_strings(self.regex)\n\n\n\ndef create_regex_list(re):\n    \"\"\"Convert binary regex to list of items.\n\n    Each item stores character position inside the binary regex (useful\n    for jumps), character type and the value (either character or\n    jump offset).\n    \"\"\"\n\n    regex_list = []\n\n    version = struct.unpack('>I', ''.join([chr(x) for x in re[:4]]))[0]\n    logger.debug(\"re.version: 0x%x\", version)\n\n    i = 4\n    if version == 1:\n        RegexParserV1.parse(re, i, regex_list)\n    elif version == 2:\n        RegexParserV2.parse(re, i, regex_list)\n    elif version == 3:\n        RegexParserV3.parse(re, i, regex_list)\n    else:\n        logger.critical(\"No parser available for regex version {:x}\".format(version))\n\n\n\n    return regex_list\n\n\ndef parse_regex(re):\n    \"\"\"Parse binary form for regular expression into canonical string.\n\n    The input binary format is the one stored in the sandbox profile\n    file. The out format is a canonical regular expression string using\n    standard ASCII characters and metacharacters such as ^, $, +, *, etc.\n    \"\"\"\n\n    regex_list = create_regex_list(re)\n    g = Graph()\n    g.fill_from_regex_list(regex_list)\n    g.reduce()\n    g.convert_to_canonical()\n    g.simplify()\n    g.combine_start_end_nodes()\n    logger.debug(g)\n    return g.regex\n\n\nimport sys\nimport struct\n\n\ndef main():\n    \"\"\"Parse regular expressions in binary file.\"\"\"\n\n    if len(sys.argv) != 2:\n        print >> sys.stderr, \"Usage: %s <regex-binary-file>\" % (sys.argv[0])\n        sys.exit(1)\n\n    with open(sys.argv[1]) as f:\n        re_count = struct.unpack(\"<H\", f.read(2))[0]\n        for i in range(re_count):\n            re_length = struct.unpack(\"<I\", f.read(4))[0]\n            re = struct.unpack(\"<%dB\" % re_length, f.read(re_length))\n            logger.info(\"total_re_length: 0x%x\", re_length)\n            re_debug_str = \"re: [\", \", \".join([hex(i) for i in re]), \"]\"\n            logger.info(re_debug_str)\n            print(parse_regex(re))\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
  }
]