├── backend ├── requirements.txt ├── start_server.sh ├── secure_check │ ├── backdoor_detector │ │ ├── MadMax │ │ │ ├── bin │ │ │ │ ├── config.ini │ │ │ │ ├── analyze.sh │ │ │ │ ├── disassemble │ │ │ │ └── decompile │ │ │ ├── requirements.txt │ │ │ └── src │ │ │ │ ├── default_config.ini │ │ │ │ ├── patterns.py │ │ │ │ ├── evm_cfg.py │ │ │ │ ├── blockparse.py │ │ │ │ ├── settings.py │ │ │ │ ├── lattice.py │ │ │ │ ├── cfg.py │ │ │ │ ├── opcodes.py │ │ │ │ └── function.py │ │ ├── lib │ │ │ ├── safemathlib.sol │ │ │ ├── safemath.sol │ │ │ ├── types.dl │ │ │ ├── opcodes.dl │ │ │ ├── edb.dl │ │ │ └── vandal.dl │ │ └── backdoor.dl │ ├── README.md │ └── main.py └── server.py ├── img ├── hint.png ├── init.png ├── report.png ├── store.png ├── marketplace.png └── configuration.png ├── .gitignore ├── vscode-extension ├── static │ ├── img │ │ └── logo.png │ ├── css │ │ └── highlight │ │ │ └── default.min.css │ └── template │ │ └── report.ejs ├── .vscodeignore ├── .vscode │ ├── extensions.json │ ├── tasks.json │ ├── settings.json │ └── launch.json ├── .eslintrc.json ├── tsconfig.json ├── src │ ├── common │ │ └── index.ts │ ├── extension.ts │ └── commands │ │ └── index.ts └── package.json ├── CHANGELOG.md ├── .github └── workflows │ └── main.yml ├── README.md └── LICENSE /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask 2 | werkzeug 3 | -------------------------------------------------------------------------------- /img/hint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FISCO-BCOS/SCStudio/HEAD/img/hint.png -------------------------------------------------------------------------------- /img/init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FISCO-BCOS/SCStudio/HEAD/img/init.png -------------------------------------------------------------------------------- /img/report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FISCO-BCOS/SCStudio/HEAD/img/report.png -------------------------------------------------------------------------------- /img/store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FISCO-BCOS/SCStudio/HEAD/img/store.png -------------------------------------------------------------------------------- /img/marketplace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FISCO-BCOS/SCStudio/HEAD/img/marketplace.png -------------------------------------------------------------------------------- /img/configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FISCO-BCOS/SCStudio/HEAD/img/configuration.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | examples 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | __pycache__/ 7 | test/ 8 | -------------------------------------------------------------------------------- /backend/start_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export FLASK_APP=server.py 4 | flask run --host=0.0.0.0 --port=7898 5 | -------------------------------------------------------------------------------- /vscode-extension/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FISCO-BCOS/SCStudio/HEAD/vscode-extension/static/img/logo.png -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | **v1.0.0** 4 | (2021-03-01) 5 | 6 | **新增** 7 | 8 | - VS Code插件,提供合约安全分析入口 9 | - 后端检测服务 10 | -------------------------------------------------------------------------------- /vscode-extension/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | .gitignore 6 | .yarnrc 7 | vsc-extension-quickstart.md 8 | **/tsconfig.json 9 | **/.eslintrc.json 10 | **/*.map 11 | **/*.ts 12 | -------------------------------------------------------------------------------- /vscode-extension/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/MadMax/bin/config.ini: -------------------------------------------------------------------------------- 1 | # Settings can be defined here. Default settings from 2 | # src/default_config.ini will be used unless overridden 3 | # here or in a command line argument. 4 | 5 | # Config must go under this [settings] section header. 6 | [settings] 7 | analytics = True 8 | -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/MadMax/bin/analyze.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ "$#" -ne 2 ]; then 3 | echo "Usage: analyze.sh bytecode_file datalog_file" 4 | exit 5 | fi 6 | set -x 7 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" 8 | rm -rf facts-tmp 9 | $DIR/decompile -o CALL JUMPI SSTORE SLOAD MLOAD MSTORE -d -n -t facts-tmp $1 10 | souffle -F facts-tmp $2 11 | rm -rf facts-tmp 12 | -------------------------------------------------------------------------------- /vscode-extension/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /vscode-extension/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off", 11 | } -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/lib/safemathlib.sol: -------------------------------------------------------------------------------- 1 | 2 | library SafeMathLib { 3 | 4 | function times(uint a, uint b) returns (uint) { 5 | uint c = a * b; 6 | assert(a == 0 || c / a == b); 7 | return c; 8 | } 9 | 10 | function minus(uint a, uint b) returns (uint) { 11 | assert(b <= a); 12 | return a - b; 13 | } 14 | 15 | function plus(uint a, uint b) returns (uint) { 16 | uint c = a + b; 17 | assert(c >= a); 18 | return c; 19 | } 20 | } 21 | 22 | // using SafeMathLib for uint; -------------------------------------------------------------------------------- /vscode-extension/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/naming-convention": "warn", 13 | "@typescript-eslint/semi": "warn", 14 | "curly": "warn", 15 | "eqeqeq": "warn", 16 | "no-throw-literal": "warn", 17 | "semi": "off" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/MadMax/requirements.txt: -------------------------------------------------------------------------------- 1 | # This is a Python pip requirements file 2 | # See: https://pip.pypa.io/en/stable/reference/pip_install/#requirements-file-format 3 | 4 | # Sphinx is used for documentation generation 5 | Sphinx==1.4.6 6 | 7 | # We use the Read The Docs theme for Sphinx 8 | sphinx_rtd_theme==0.1.9 9 | 10 | # networkx and pydotplus are used for creating DOT representations of CFGs 11 | networkx==1.11 12 | pydotplus==2.0.2 13 | 14 | # Unit testing uses pytest 15 | pytest==3.0.2 16 | 17 | # termcolor for ANSI colours in terminal output 18 | termcolor==1.1.0 19 | -------------------------------------------------------------------------------- /backend/secure_check/README.md: -------------------------------------------------------------------------------- 1 | # Prerequisite 2 | 3 | ## JDK 8 4 | 5 | ## python 3.6 6 | 7 | ## SmartCheck 8 | 9 | sudo apt install nodejs 10 | sudo apt install npm 11 | 12 | npm config set registry https://registry.npm.taobao.org 13 | npm install @smartdec/smartcheck -g 14 | dos2unix secure_check/backdoor_detector/MadMax/bin/analyze.sh 15 | chmod +x secure_check/backdoor_detector/MadMax/bin/decompile 16 | dos2unix secure_check/backdoor_detector/MadMax/bin/decompile 17 | 18 | ## Souffle 19 | wget https://github.com/souffle-lang/souffle/releases/download/2.0.2/souffle_2.0.2-1_amd64.deb 20 | dpkg -i souffle_2.0.2-1_amd64.deb -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/lib/safemath.sol: -------------------------------------------------------------------------------- 1 | 2 | library SafeMath { 3 | function mul(uint256 a, uint256 b) internal constant returns (uint256) { 4 | uint256 c = a * b; 5 | assert(a == 0 || c / a == b); 6 | return c; 7 | } 8 | 9 | function div(uint256 a, uint256 b) internal constant returns (uint256) { 10 | uint256 c = a / b; 11 | return c; 12 | } 13 | 14 | function sub(uint256 a, uint256 b) internal constant returns (uint256) { 15 | assert(b <= a); 16 | return a - b; 17 | } 18 | 19 | function add(uint256 a, uint256 b) internal constant returns (uint256) { 20 | uint256 c = a + b; 21 | assert(c >= a); 22 | return c; 23 | } 24 | } 25 | 26 | // using SafeMath for uint256; 27 | -------------------------------------------------------------------------------- /vscode-extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true, /* enable all strict type-checking options */ 12 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | "strictPropertyInitialization": false 17 | }, 18 | "exclude": [ 19 | "node_modules", 20 | ".vscode-test" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /vscode-extension/static/css/highlight/default.min.css: -------------------------------------------------------------------------------- 1 | .hljs{display:block;overflow-x:auto;padding:.5em;background:#f0f0f0}.hljs,.hljs-subst{color:#444}.hljs-comment{color:#888}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#bc6060}.hljs-literal{color:#78a960}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700} -------------------------------------------------------------------------------- /vscode-extension/src/common/index.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export async function postRequest( 4 | request: string, 5 | serverAddress: string, 6 | data: Object, 7 | retryCount: number = 3 8 | ) { 9 | while (true) { 10 | try { 11 | let response: Response = await axios 12 | .post(`http://${serverAddress}/${request}`, data) 13 | .then((response) => { 14 | if (response.status !== 200) { 15 | throw new Error(response.statusText); 16 | } 17 | return response.data; 18 | }); 19 | return response; 20 | } catch (error) { 21 | retryCount -= 1; 22 | if (retryCount === 0) { 23 | throw new Error(error.code); 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the main branch 8 | push: 9 | branches: [ main ] 10 | pull_request: 11 | branches: [ main ] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | autopep8: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: autopep8 23 | id: autopep8 24 | uses: peter-evans/autopep8@v1 25 | with: 26 | args: --exit-code --recursive -d -vv --aggressive . 27 | - name: Fail if autopep8 made changes 28 | if: steps.autopep8.outputs.exit-code == 2 29 | run: exit 1 30 | -------------------------------------------------------------------------------- /vscode-extension/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/out/**/*.js" 17 | ], 18 | "preLaunchTask": "${defaultBuildTask}" 19 | }, 20 | { 21 | "name": "Extension Tests", 22 | "type": "extensionHost", 23 | "request": "launch", 24 | "args": [ 25 | "--extensionDevelopmentPath=${workspaceFolder}", 26 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 27 | ], 28 | "outFiles": [ 29 | "${workspaceFolder}/out/test/**/*.js" 30 | ], 31 | "preLaunchTask": "${defaultBuildTask}" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/lib/types.dl: -------------------------------------------------------------------------------- 1 | // BSD 3-Clause License 2 | // 3 | // Copyright (c) 2016, 2017, The University of Sydney. All rights reserved. 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are met: 7 | // 8 | // * Redistributions of source code must retain the above copyright notice, this 9 | // list of conditions and the following disclaimer. 10 | // 11 | // * Redistributions in binary form must reproduce the above copyright notice, 12 | // this list of conditions and the following disclaimer in the documentation 13 | // and/or other materials provided with the distribution. 14 | // 15 | // * Neither the name of the copyright holder nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | .type Statement 31 | .type Variable 32 | .type Opcode 33 | .type Value 34 | -------------------------------------------------------------------------------- /backend/server.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import logging 4 | import json 5 | import uuid 6 | 7 | from flask import Flask, request 8 | 9 | 10 | app = Flask(__name__) 11 | cur_dir = os.path.dirname(__file__) 12 | 13 | # Uses absolute path to mount volume in Docker 14 | contracts_dir = "/tmp/contracts" 15 | secure_check_dir = os.path.join(cur_dir, "secure_check") 16 | 17 | 18 | @app.route("/contract_analysis", methods=["POST"]) 19 | def contract_analysis(): 20 | if not os.path.exists(contracts_dir): 21 | os.mkdir(contracts_dir) 22 | 23 | timeout = request.json["timeout"] 24 | content = request.json["content"] 25 | contract_id = str(uuid.uuid4()).replace("-", "") 26 | 27 | contract_dir = os.path.join(contracts_dir, contract_id) 28 | os.mkdir(contract_dir) 29 | with open(os.path.join(contract_dir, contract_id + ".sol"), "w", encoding="utf-8") as f: 30 | f.write(content) 31 | 32 | try: 33 | subprocess.run( 34 | f"python3 {os.path.join(secure_check_dir, 'main.py')} {contract_dir} {timeout}", 35 | check=True, 36 | shell=True) 37 | except Exception as e: 38 | logging.warning( 39 | f"[execute_command]error occurs, message: {e}") 40 | return { 41 | "status": -1, 42 | "msg": str(e), 43 | "vulnerabilities": [] 44 | } 45 | 46 | result_file = os.path.join(contract_dir, "final_report.json") 47 | if not os.path.exists(result_file): 48 | return { 49 | "status": -1, 50 | "msg": "Failed to generate report, maybe you need to try again", 51 | "vulnerabilities": [] 52 | } 53 | 54 | result = open(result_file).read() 55 | return { 56 | "status": 0, 57 | "msg": "", 58 | "vulnerabilities": json.loads(result) 59 | } 60 | 61 | 62 | logging.basicConfig( 63 | format='[%(levelname)s][%(asctime)s]%(message)s', level=logging.DEBUG) 64 | -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/lib/opcodes.dl: -------------------------------------------------------------------------------- 1 | // BSD 3-Clause License 2 | // 3 | // Copyright (c) 2016, 2017, The University of Sydney. All rights reserved. 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are met: 7 | // 8 | // * Redistributions of source code must retain the above copyright notice, this 9 | // list of conditions and the following disclaimer. 10 | // 11 | // * Redistributions in binary form must reproduce the above copyright notice, 12 | // this list of conditions and the following disclaimer in the documentation 13 | // and/or other materials provided with the distribution. 14 | // 15 | // * Neither the name of the copyright holder nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | // Place opcodes into various categories. 31 | .decl unaryArith(opcode:Opcode) 32 | unaryArith("ISZERO"). 33 | unaryArith("NOT"). 34 | 35 | .decl binArith(opcode:Opcode) 36 | binArith("ADD"). 37 | binArith("MUL"). 38 | binArith("SUB"). 39 | binArith("DIV"). 40 | binArith("SDIV"). 41 | binArith("MOD"). 42 | binArith("SMOD"). 43 | binArith("EXP"). 44 | binArith("SIGNEXTEND"). 45 | binArith("LT"). 46 | binArith("GT"). 47 | binArith("SLT"). 48 | binArith("SGT"). 49 | binArith("EQ"). 50 | binArith("AND"). 51 | binArith("OR"). 52 | binArith("XOR"). 53 | binArith("BYTE"). 54 | 55 | .decl ternArith(opcode:Opcode) 56 | ternArith("ADDMOD"). 57 | ternArith("MULMOD"). 58 | 59 | .decl runtimeKnowable(opcode:Opcode) 60 | runtimeKnowable("ADDRESS"). 61 | runtimeKnowable("ORIGIN"). 62 | runtimeKnowable("CALLER"). 63 | runtimeKnowable("CALLVALUE"). 64 | runtimeKnowable("CALLDATASIZE"). 65 | runtimeKnowable("CODESIZE"). 66 | runtimeKnowable("GASPRICE"). 67 | 68 | .decl isThrow(stmt:Statement) 69 | 70 | isThrow(stmt) :- 71 | op(stmt, "THROW"); 72 | op(stmt, "THROWI"). 73 | -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/lib/edb.dl: -------------------------------------------------------------------------------- 1 | // BSD 3-Clause License 2 | // 3 | // Copyright (c) 2016, 2017, The University of Sydney. All rights reserved. 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are met: 7 | // 8 | // * Redistributions of source code must retain the above copyright notice, this 9 | // list of conditions and the following disclaimer. 10 | // 11 | // * Redistributions in binary form must reproduce the above copyright notice, 12 | // this list of conditions and the following disclaimer in the documentation 13 | // and/or other materials provided with the distribution. 14 | // 15 | // * Neither the name of the copyright holder nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | // INPUT 31 | 32 | .decl entry(s:Statement) // statements without predecessors 33 | .decl edge(h:Statement, t:Statement) // There is a CFG edge from h to t 34 | .decl def(var:Variable, stmt:Statement) // var is defined by stmt 35 | .decl use(var:Variable, stmt:Statement, i:number) // var is used by stmt as argument i 36 | .decl op(stmt:Statement, op:Opcode) // stmt's opcode is op 37 | .decl value(var:Variable, val:Value) // A variable's possible value set if known 38 | 39 | // Requires dominator, CALL, JUMPI, SSTORE relations. 40 | .decl op_CALL(stmt:Statement, gas:Variable, target:Variable, value:Variable, data_start:Variable, data_length:Variable, return_start:Variable, return_length:Variable) 41 | .decl op_JUMPI(stmt:Statement, dest:Variable, cond:Variable) 42 | .decl op_SSTORE(stmt:Statement, loc:Variable, val:Variable) 43 | 44 | // Dominance relations 45 | // [p]dom(s, d) => s is [post-]dominated by d 46 | // x is dominated by y if all paths from the root to x must go through y 47 | // Post-domination is domination in the reversed cfg with an auxiliary node connected to all 48 | // exit nodes. 49 | .decl dom(s:Statement, d:Statement) 50 | .decl pdom(s:Statement, d:Statement) 51 | 52 | .input edge 53 | .input entry 54 | .input def 55 | .input use 56 | .input op 57 | .input value 58 | .input op_CALL 59 | .input op_JUMPI 60 | .input op_SSTORE 61 | .input dom 62 | .input pdom 63 | -------------------------------------------------------------------------------- /vscode-extension/static/template/report.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Analysis Report 7 | 11 | 15 | 16 | 19 | 20 | 21 |
22 |
23 | Powered by SCStudio, a unified smart contract development platform 24 | produced by WeBank and Tsinghua University. 25 |
26 |

Contract Code

27 |
<%- codeLines.join("\n") %>
28 |             
29 |

Overview

30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | <% for(let i = 0; i < vulnerabilities.length; ++i) { %> 41 | 42 | 43 | 44 | 45 | 46 | <% } %> 47 | 48 |
VulnerabilitiesLinesLevel
<%- vulnerabilities[i].name %> <%- vulnerabilities[i].lineNo %> <%- vulnerabilities[i].level %>
49 |
50 | <% for(let i = 0; i < vulnerabilities.length; ++i) { %> 51 |

52 | Vulnerability: <%- vulnerabilities[i].name %> 54 |

55 |
56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
LinesLevelSWC ID
<%- vulnerabilities[i].lineNo %> <%- vulnerabilities[i].level %> <%- vulnerabilities[i].swcId %>
72 |
73 |

Description

74 |

<%-vulnerabilities[i].description %>

75 |

Advice

76 |

<%-vulnerabilities[i].advice %>

77 |

Code Snippets

78 | 83 | 84 |
85 | 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SCStudio: Making smart contract development more secure 2 | 3 | SCStudio 是一款针对 Solidity 合约的安全分析工具。在[Visual Studio Code](https://code.visualstudio.com/)(VS Code)开发环境下,开发者可通过 SCStudio 提供的 VS Code 扩展,在合约的开发过程中使用 SCStudio 进行实时安全性检查。SCStudio 由[清华大学软件系统安全保障小组](http://www.wingtecher.com/)开发并贡献。 4 | 5 | _当前,本项目由[fCorleone](https://github.com/fCorleone)(清华大学)、[renardbebe](https://github.com/renardbebe)(清华大学)及 [vita-dounai](https://github.com/vita-dounai)(微众银行)负责维护。 如果你在使用过程中遇到了任何问题,请移步至[issue 区](https://github.com/FISCO-BCOS/SCStudio/issues)提交 issue。_ 6 | 7 | ## 一、快速开始 8 | 9 | ### 1.1 安装 Visual Studio Code 插件 10 | 11 | 启动 Visual Studio Code 后,点击左侧侧边栏中“扩展”一项,在弹出扩展列表顶部的搜索框中键入“SCStudio”并搜索,在搜索结果中选择 SCStudio 进行安装。安装完毕后,遵循 Visual Studio Code 的提示重新载入窗口。 12 | 13 | ![marketplace](img/marketplace.png) 14 | 15 | ### 1.2 配置 16 | 17 | 在 Visual Studio Code 的顶部菜单栏中依次点击`文件`、`首选项`、`设置`、`扩展`、`SCStudio`,可对 SCStudio 进行配置。当前 SCStudio 提供下列配置项: 18 | 19 | ![Configuration](img/configuration.png) 20 | 21 | - `Max Waiting Time`:在 SCStudio 对合约代码进行安全性检测时,会涉及网络交互、符号执行等较为耗时的过程。根据合约的复杂程度,检测过程可能会持续数秒至数分钟不等。为避免 SCStudio 陷入无尽等待,可以通过该配置项指定 SCStudio 的最大超时时间(以秒为单位)。该配置项默认设置为 60 秒。特别地,`Max Waiting Time`可配置为 0,此时启动检测过程后,SCStudio 将会持续等待,直至后端检测服务返回分析结果或网络出现异常; 22 | 23 | - `Server Address`:此配置项用于配置后端检测服务的服务器地址,包括 IP 地址及其端口。当该配置项为空时,SCStudio 会将合约代码提交至用于试用服务器进行分析检测,此时需要有可用的外部网络连接。也可以按照[部署本地检测服务](#二部署本地检测服务)一节中的说明,在本地搭建后端检测服务,并将`Server Address`配置为本地检测服务的地址。`Server Address`配置项的格式为`:<端口>`,例如“127.0.0.1:7898”。 24 | 25 | 当配置更新后,需要对配置文件进行保存以使配置项生效。 26 | 27 | ### 1.3 使用 28 | 29 | 当在 Visual Studio Code 中新建或打开一个后缀名为“.sol”的文件后,SCStudio 插件将自动载入并初始化。当初始化完成后,VS Code 右下角的状态栏中将显示“SCStudio: Ready”字样: 30 | 31 | ![Status](img/init.png) 32 | 33 | 此时 SCStudio 进入就绪(Ready)状态,你可以通过以下四种方式触发 SCStudio 对当前编辑窗口中的合约代码进行安全性检测: 34 | 35 | - 命令:打开 VS Code 命令栏(Windows 及 Linux 下可通过 `Ctrl + Shift + P`快捷键、macOS 可通过 `Command + Shift + P`快捷键),并执行“SCStudio: Analyze Contract”或“SCStudio: Analyze Contract Without Compiling”命令即可开始对合约代码进行分析。两种命令的区别仅在于后者不会对合约代码进行自动编译。一般而言选择“SCStudio: Analyze Contract”可获得更多错误提示; 36 | 37 | - 右键菜单:在编辑窗口中点击鼠标右键,在弹出的菜单中点击“Analyze Contract”或“Analyze Contract Without Compiling”,其效果分别等同于执行“SCStudio: Analyze Contract”或“SCStudio: Analyze Contract Without Compiling”命令; 38 | 39 | - 状态栏:可以直接点击 VS Code 右下角状态栏中“SCStudio: Ready”字样,点击后 SCStudio 将开始执行“SCStudio: Analyze Contract”命令; 40 | 41 | - 快捷键:`Ctrl` + `F10`(macOS 下为`Command` + `F10`)可执行“SCStudio: Analyze Contract”命令;`Ctrl` + `Shift` + `F1`(macOS 下为`Command` + `Shift` + `F10`)可执行“SCStudio: Analyze Contract Without Compiling”命令。 42 | 43 | 需要注意的是,当 SCStudio 开始对合约进行分析后,SCStudio 将由就绪状态转变为分析(Analyzing)状态,此时 VS Code 右下角状态栏中将显示“SCStudio: Analyzing”字样及对应动画,此时状态栏暂时无法点击、右键菜单中 SCStudio 相关菜单项暂时不可用,同时相关命令及快捷键也将暂时失效,直至分析过程结束、SCStudio 重新进入就绪状态。 44 | 45 | 当 SCStudio 检测到合约代码中存在安全性问题后,会通过彩带形式进行显式提示。当鼠标悬停于彩带上时,会显示错误详情、修复建议等信息,如下图所示; 46 | 47 | ![Hint](img/hint.png) 48 | 49 | 除彩带提示外,当合约代码中存在安全性问题时,SCStudio 会以通知的形式询问是否需要将检测报告保存至本地: 50 | 51 | ![Store](img/store.png) 52 | 53 | 当选择“Yes”后,SCStudio 将打开文件浏览器,可在文件浏览器中选择报告的存放目录。选择完毕后,SCStudio 将在指定目录生成一份 HTML 格式的检测报告,报告文件的名称形如“VulnerabilitiesReport\_{date}.html”,其中`{data}`为生成报告时的日期及时间。检测报告提供了界面更加友好的错误展示,你可以使用浏览器打开检测报告并进行浏览: 54 | 55 | ![Report](img/report.png) 56 | 57 | ### 二、部署本地检测服务 58 | 59 | 若`Server Address`配置项设置为空,则在合约代码检测的过程中,SCStudio 会将合约代码提交至试用服务器以进行检测。由于试用服务器的计算资源有限,此过程可能会较不稳定或耗时较长。同时,由于试用服务器运行于公网环境,因此可能会造成合约内容的外泄。若对用户体验或隐私性有较高要求,推荐在本地部署检测服务。 60 | 61 | 检测服务依赖于 Docker,因此部署服务前需要在本地预先[安装](https://www.docker.com/get-started)Docker,当前检测服务能够运行于 macOS 、 Linux 或安装有 WSL2 的 Windows 环境中,部署过程如下: 62 | 63 | ```bash 64 | # 安装Mythril 65 | docker pull mythril/myth 66 | # 安装Oyente 67 | docker pull qspprotocol/oyente-0.4.25 68 | # 根据合约中要求的Solidity编译器版本安装Solidity编译器 69 | # 此处以安装0.4.26版本的Solidity编译器为例 70 | docker pull ethereum/solc:0.4.26 71 | # 安装检测服务 72 | docker pull fiscoorg/scstudio_backend:latest 73 | # 运行检测服务 74 | docker run -v /var/run/docker.sock:/var/run/docker.sock -v /tmp:/tmp -p 8001:7898 -it --rm fiscoorg/scstudio_backend:latest 75 | cd backend/ && ./start_server.sh 76 | ``` 77 | 78 | 容器内的检测服务固定监听**容器内的**7898 端口,可以在执行`docker run`命令时修改`-p`选项参数指定宿主与容器间的端口映射。在上述示例中,宿主的 8001 端口将会被映射至容器的 7898 端口,因此需要将`Server Address`配置项修改为“127.0.0.1:8001”,SCStudio 便能够正常访问本地的检测服务。 79 | -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/MadMax/src/default_config.ini: -------------------------------------------------------------------------------- 1 | # BSD 3-Clause License 2 | # 3 | # Copyright (c) 2016, 2017, The University of Sydney. All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # * Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # * Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # * Neither the name of the copyright holder nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | # The user can change the settings here (which are the defaults), or 31 | # override them in bin/config.ini, or by providing appropriate command line 32 | # flags. 33 | # Fuller descriptions of what these settings do are provided in src/settings.py 34 | 35 | # Don't modify this section header. 36 | [DEFAULT] 37 | 38 | # The maximum number of times to perform the graph analysis step. 39 | # A negative value means no maximum. 40 | max_iterations = 3 41 | 42 | # Begin breaking out of the analysis loop if the time spent exceeds this value. 43 | # A negative value means no maximum. 44 | bailout_seconds = 5 45 | 46 | # Upon completion of the analysis, if there are blocks unreachable from the 47 | # contract root, remove them. 48 | remove_unreachable = False 49 | 50 | # Upon completion of the analysis, if there are blocks unreachable from the 51 | # contract root, merge them. 52 | merge_unreachable = True 53 | 54 | # Raise an exception if an empty stack is popped. 55 | die_on_empty_pop = False 56 | 57 | # Do not apply changes to exit stacks after a symbolic overflow occurrs 58 | # in their blocks. 59 | skip_stack_on_overflow = True 60 | 61 | # Reinitialise all blocks' exit stacks to be empty. 62 | reinit_stacks = True 63 | 64 | # After completing the analysis, propagate entry stack values into blocks. 65 | hook_up_stack_vars = True 66 | 67 | # Connect any new edges that can be inferred after performing the analysis. 68 | hook_up_jumps = True 69 | 70 | # JUMPIs with known conditions become JUMPs (or are deleted). 71 | mutate_jumps = False 72 | 73 | # JUMP and JUMPI instructions with invalid destinations become THROW and 74 | # THROWIs. 75 | generate_throws = False 76 | 77 | # Mutate jumps in the final analysis phase. 78 | final_mutate_jumps = False 79 | 80 | # generate throws in the final analysis phase. 81 | final_generate_throws = True 82 | 83 | # Hook up stack vars and/or hook up jumps after each block rather than after 84 | # the whole analysis is complete. 85 | mutate_blockwise = True 86 | 87 | # If stacks start growing without bound, reduce the maximum stack size in order 88 | # to hasten convergence. 89 | clamp_large_stacks = True 90 | 91 | # Stack sizes will not be clamped smaller than this value. 92 | clamp_stack_minimum = 10 93 | 94 | # If any computed variable's number of possible values exceeds a given 95 | # threshold, widen its value to Top. 96 | widen_variables = True 97 | 98 | # Widen if the size of a given variable exceeds this value, 99 | # and widen_variables is True. 100 | widen_threshold = 10 101 | 102 | # If True, apply arithmetic operations to variables with multiple values; 103 | # otherwise, only apply them to variables whose value is definite. 104 | set_valued_ops = True 105 | 106 | # If true, dataflow analysis will return a dict of information about 107 | # the contract, otherwise return an empty dict. 108 | analytics = False 109 | 110 | # Attempt to extract solidity functions. 111 | extract_functions = True 112 | 113 | # Tag block names with the function(s) they belong to. 114 | mark_functions = False 115 | 116 | # If true, then unrecognised opcodes and invalid disassembly 117 | # will not be skipped, but will result in an error. 118 | strict = False 119 | 120 | -------------------------------------------------------------------------------- /vscode-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scstudio-vscode-extension", 3 | "displayName": "SCStudio", 4 | "description": "Making smart contract development more secure", 5 | "version": "1.0.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/FISCO-BCOS/SCStudio.git" 9 | }, 10 | "icon": "static/img/logo.png", 11 | "publisher": "vita-dounai", 12 | "contributors": [ 13 | { 14 | "name": "Li Chenxi", 15 | "url": "https://github.com/vita-dounai" 16 | }, 17 | { 18 | "name": "Ren Meng", 19 | "url": "https://github.com/renardbebe" 20 | }, 21 | { 22 | "name": "Ma Fuchen", 23 | "url": "https://github.com/fCorleone" 24 | }, 25 | { 26 | "name": "Ouyang Lerong ", 27 | "url": "https://github.com/summerwithnostory" 28 | } 29 | ], 30 | "license": "Apache-2.0", 31 | "engines": { 32 | "vscode": "^1.53.0" 33 | }, 34 | "categories": [ 35 | "Programming Languages", 36 | "Linters" 37 | ], 38 | "activationEvents": [ 39 | "onLanguage:solidity" 40 | ], 41 | "main": "./out/extension.js", 42 | "contributes": { 43 | "configuration": { 44 | "title": "SCStudio", 45 | "properties": { 46 | "SCStudio.serverAddress": { 47 | "type": "string", 48 | "pattern": "^$|^((\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\s*:\\s*(\\d|[1-9]\\d{1,3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])$", 49 | "markdownDescription": "The address of the backend server, which is in `{IP}:{port}` format" 50 | }, 51 | "SCStudio.maxWaitingTime": { 52 | "type": "integer", 53 | "default": 60, 54 | "minimum": 0, 55 | "markdownDescription": "Sets the timeout for contract analysis, `0` means waiting infinitely" 56 | } 57 | } 58 | }, 59 | "commands": [ 60 | { 61 | "command": "scstudio.analyze", 62 | "title": "Analyze Contract", 63 | "category": "SCStudio", 64 | "enablement": "scstudio:isReady && resourceExtname == .sol" 65 | }, 66 | { 67 | "command": "scstudio.analyzeWithoutCompiling", 68 | "title": "Analyze Contract Without Compiling", 69 | "category": "SCStudio", 70 | "enablement": "scstudio:isReady && resourceExtname == .sol" 71 | } 72 | ], 73 | "keybindings": [ 74 | { 75 | "command": "scstudio.analyze", 76 | "key": "ctrl+f10", 77 | "mac": "cmd+f10", 78 | "when": "editorTextFocus && resourceExtname == .sol" 79 | }, 80 | { 81 | "command": "scstudio.analyzeWithoutCompiling", 82 | "key": "ctrl+shift+f10", 83 | "mac": "cmd+shift+f10", 84 | "when": "editorTextFocus && resourceExtname == .sol" 85 | } 86 | ], 87 | "menus": { 88 | "editor/context": [ 89 | { 90 | "when": "editorFocus && resourceExtname == .sol", 91 | "command": "scstudio.analyze", 92 | "group": "scstudio@1" 93 | }, 94 | { 95 | "when": "editorFocus && resourceExtname == .sol", 96 | "command": "scstudio.analyzeWithoutCompiling", 97 | "group": "scstudio@2" 98 | } 99 | ] 100 | } 101 | }, 102 | "scripts": { 103 | "vscode:prepublish": "npm run compile", 104 | "compile": "tsc -p ./", 105 | "watch": "tsc -watch -p ./", 106 | "pretest": "npm run compile && npm run lint", 107 | "lint": "eslint src --ext ts", 108 | "test": "node ./out/test/runTest.js" 109 | }, 110 | "devDependencies": { 111 | "@types/vscode": "^1.53.0", 112 | "@types/glob": "^7.1.3", 113 | "@types/mocha": "^8.0.4", 114 | "@types/node": "^12.11.7", 115 | "eslint": "^7.19.0", 116 | "@typescript-eslint/eslint-plugin": "^4.14.1", 117 | "@typescript-eslint/parser": "^4.14.1", 118 | "glob": "^7.1.6", 119 | "mocha": "^8.2.1", 120 | "typescript": "^4.1.3", 121 | "vscode-test": "^1.5.0" 122 | }, 123 | "dependencies": { 124 | "@types/ejs": "^3.0.6", 125 | "@types/strftime": "^0.9.2", 126 | "axios": "^0.21.1", 127 | "ejs": "^3.1.6", 128 | "strftime": "^0.10.0" 129 | }, 130 | "extensionDependencies": [ 131 | "philhindle.errorlens", 132 | "JuanBlanco.solidity" 133 | ] 134 | } 135 | -------------------------------------------------------------------------------- /vscode-extension/src/extension.ts: -------------------------------------------------------------------------------- 1 | // The module 'vscode' contains the VS Code extensibility API 2 | // Import the module and reference it with the alias vscode in your code below 3 | import * as vscode from "vscode"; 4 | import * as commands from "./commands"; 5 | 6 | enum WorkingMode { 7 | /* eslint-disable-next-line @typescript-eslint/naming-convention */ 8 | Online, 9 | /* eslint-disable-next-line @typescript-eslint/naming-convention */ 10 | Offline, 11 | } 12 | 13 | class Configuration { 14 | maxWaitingTime: number; 15 | serverAddress: string; 16 | 17 | private constructor() { 18 | this.loadConfig(); 19 | } 20 | 21 | // Singleton pattern 22 | private static instance: Configuration; 23 | 24 | public static getInstance() { 25 | if (!Configuration.instance) { 26 | Configuration.instance = new Configuration(); 27 | } 28 | return Configuration.instance; 29 | } 30 | 31 | public loadConfig() { 32 | let config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration( 33 | "SCStudio" 34 | ); 35 | this.maxWaitingTime = config["maxWaitingTime"]; 36 | 37 | let serverAddress: string = config["serverAddress"]; 38 | if (serverAddress === "") { 39 | // Avoids code security checking 40 | this.serverAddress = 41 | ["116", "63", "184", "110"].join(".") + ":7898"; 42 | } else { 43 | this.serverAddress = serverAddress; 44 | } 45 | } 46 | } 47 | 48 | enum Status { 49 | /* eslint-disable-next-line @typescript-eslint/naming-convention */ 50 | Ready, 51 | /* eslint-disable-next-line @typescript-eslint/naming-convention */ 52 | Analyzing, 53 | } 54 | 55 | const ANALYZE_CMD: string = "scstudio.analyze"; 56 | const ANALYZE_WITHOUT_COMPILING: string = "scstudio.analyzeWithoutCompiling"; 57 | 58 | // this method is called when your extension is activated 59 | // your extension is activated the very first time the command is executed 60 | export function activate(context: vscode.ExtensionContext) { 61 | vscode.workspace.onDidChangeConfiguration(() => { 62 | let config = Configuration.getInstance(); 63 | config.loadConfig(); 64 | }); 65 | 66 | let diagnosticsCollection = vscode.languages.createDiagnosticCollection( 67 | "scstudio" 68 | ); 69 | let statusBarItem: vscode.StatusBarItem = vscode.window.createStatusBarItem( 70 | vscode.StatusBarAlignment.Right, 71 | 100 72 | ); 73 | 74 | vscode.workspace.onDidChangeTextDocument(async (event) => { 75 | let uri = event.document.uri; 76 | let changes = event.contentChanges; 77 | if (changes.length === 0) { 78 | return; 79 | } 80 | let oriDiagnostics = diagnosticsCollection.get(uri); 81 | if (!oriDiagnostics || oriDiagnostics.length === 0) { 82 | return; 83 | } 84 | 85 | let newDiagnostics: vscode.Diagnostic[] = []; 86 | for (let change of changes) { 87 | for (let oriDiagnostic of oriDiagnostics) { 88 | let changeStartLine = change.range.start.line; 89 | let changeEndLine = change.range.end.line; 90 | // For now, diagnostic information must exist in a single line 91 | let diagnosticLine = oriDiagnostic.range.start.line; 92 | if ( 93 | diagnosticLine < changeStartLine || 94 | diagnosticLine > changeEndLine 95 | ) { 96 | newDiagnostics.push(oriDiagnostic); 97 | } 98 | } 99 | } 100 | diagnosticsCollection.set(uri, newDiagnostics); 101 | }); 102 | 103 | let updateStatus = (status: Status) => { 104 | if (status === Status.Analyzing) { 105 | vscode.commands.executeCommand( 106 | "setContext", 107 | "scstudio:isReady", 108 | false 109 | ); 110 | 111 | statusBarItem.text = `$(sync~spin) SCStudio: Analyzing`; 112 | statusBarItem.command = undefined; 113 | statusBarItem.show(); 114 | } else if (status === Status.Ready) { 115 | vscode.commands.executeCommand( 116 | "setContext", 117 | "scstudio:isReady", 118 | true 119 | ); 120 | 121 | statusBarItem.text = `$(check) SCStudio: Ready`; 122 | statusBarItem.command = ANALYZE_CMD; 123 | statusBarItem.show(); 124 | } 125 | }; 126 | 127 | context.subscriptions.push( 128 | vscode.commands.registerCommand(ANALYZE_CMD, () => { 129 | updateStatus(Status.Analyzing); 130 | let config = Configuration.getInstance(); 131 | let document = vscode.window.activeTextEditor!.document; 132 | commands 133 | .analyzeContract( 134 | context, 135 | diagnosticsCollection, 136 | document, 137 | config.serverAddress, 138 | config.maxWaitingTime, 139 | true 140 | ) 141 | .then(() => { 142 | updateStatus(Status.Ready); 143 | }); 144 | }) 145 | ); 146 | context.subscriptions.push( 147 | vscode.commands.registerCommand(ANALYZE_WITHOUT_COMPILING, () => { 148 | updateStatus(Status.Analyzing); 149 | let config = Configuration.getInstance(); 150 | let document = vscode.window.activeTextEditor!.document; 151 | commands 152 | .analyzeContract( 153 | context, 154 | diagnosticsCollection, 155 | document, 156 | config.serverAddress, 157 | config.maxWaitingTime, 158 | false 159 | ) 160 | .then(() => { 161 | updateStatus(Status.Ready); 162 | }); 163 | }) 164 | ); 165 | 166 | updateStatus(Status.Ready); 167 | console.log("SCStudio extension initialized"); 168 | } 169 | 170 | // this method is called when your extension is deactivated 171 | export function deactivate() {} 172 | -------------------------------------------------------------------------------- /vscode-extension/src/commands/index.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import path = require("path"); 3 | import strftime = require("strftime"); 4 | import fs = require("fs"); 5 | import ejs = require("ejs"); 6 | import { postRequest } from "../common"; 7 | 8 | interface Vulnerability { 9 | advice: string; 10 | description: string; 11 | level: string; 12 | lineNo: number[]; 13 | name: string; 14 | swcId: string; 15 | } 16 | 17 | interface Response { 18 | msg: string; 19 | status: number; 20 | vulnerabilities: Map; 21 | } 22 | 23 | function updateDiagnostics( 24 | document: vscode.TextDocument, 25 | diagnosticCollection: vscode.DiagnosticCollection, 26 | vulnerabilities: Vulnerability[] 27 | ) { 28 | let diagnostics: vscode.Diagnostic[] = []; 29 | for (let vulnerability of vulnerabilities) { 30 | let advice: string; 31 | if (!vulnerability.advice) { 32 | advice = "null"; 33 | } else { 34 | advice = vulnerability.advice; 35 | } 36 | 37 | let swcId: string; 38 | if (!vulnerability.swcId) { 39 | swcId = "null"; 40 | } else { 41 | swcId = vulnerability.swcId; 42 | } 43 | let message = `(SCStudio) ${vulnerability.name} 44 | - Description 45 | ${vulnerability.description} 46 | - Advice 47 | ${advice} 48 | - SWC ID 49 | ${swcId}`; 50 | let severity = vscode.DiagnosticSeverity.Error; 51 | if (vulnerability.level !== "error") { 52 | severity = vscode.DiagnosticSeverity.Warning; 53 | } 54 | 55 | for (let line of vulnerability.lineNo) { 56 | let range = document.lineAt(line - 1).range; 57 | let diagnostic = new vscode.Diagnostic(range, message, severity); 58 | diagnostics.push(diagnostic); 59 | } 60 | } 61 | 62 | diagnosticCollection.set(document.uri, diagnostics); 63 | } 64 | 65 | export async function analyzeContract( 66 | context: vscode.ExtensionContext, 67 | diagnosticCollection: vscode.DiagnosticCollection, 68 | document: vscode.TextDocument, 69 | serverAddress: string, 70 | timeout: number, 71 | needCompiling: boolean 72 | ) { 73 | if (needCompiling) { 74 | await vscode.extensions.getExtension("JuanBlanco.solidity")!.activate(); 75 | } 76 | 77 | await vscode.extensions 78 | .getExtension("philhindle.errorlens")! 79 | .activate() 80 | .then(() => { 81 | vscode.commands.executeCommand("ErrorLens.enable"); 82 | }); 83 | 84 | diagnosticCollection.clear(); 85 | let originalDiagnostics = vscode.languages.getDiagnostics(document.uri); 86 | // Clear original diagnostics, this method can be referenced at 87 | // https://stackoverflow.com/questions/1232040/how-do-i-empty-an-array-in-javascript 88 | originalDiagnostics.splice(0, originalDiagnostics.length); 89 | 90 | vscode.window.showInformationMessage( 91 | `This contract had been submitted to analysis.`, 92 | "Dismiss" 93 | ); 94 | 95 | let respBody: Response; 96 | try { 97 | let data = { 98 | content: document.getText(), 99 | timeout, 100 | }; 101 | respBody = await postRequest("contract_analysis", serverAddress, data); 102 | } catch (error) { 103 | vscode.window.showErrorMessage( 104 | `SCStudio: Unable to reach backend server, due to \`${error.message}\`` 105 | ); 106 | return; 107 | } 108 | 109 | if (Object.keys(respBody!.vulnerabilities).length === 0) { 110 | vscode.window.showInformationMessage( 111 | "SCStudio: No security issues detected in this contract.", 112 | "Dismiss" 113 | ); 114 | return; 115 | } 116 | 117 | let vulnerabilities: Vulnerability[] = Object.values( 118 | respBody!.vulnerabilities 119 | ); 120 | updateDiagnostics(document, diagnosticCollection, vulnerabilities); 121 | vscode.window 122 | .showWarningMessage( 123 | "SCStudio: Some security issues detected in this contract. Do you need to export the HTML report to local?", 124 | "Yes", 125 | "No" 126 | ) 127 | .then((selection) => { 128 | if (selection === "Yes") { 129 | vscode.window 130 | .showOpenDialog({ 131 | canSelectFiles: false, 132 | canSelectFolders: true, 133 | canSelectMany: false, 134 | title: "Select a location to store the report", 135 | }) 136 | .then((uri) => { 137 | if (uri !== undefined) { 138 | let dir = uri[0].fsPath; 139 | let date = strftime("%Y%m%d%H%M%S", new Date()); 140 | let filePath = path.join( 141 | dir, 142 | `VulnerabilitiesReport_${date}.html` 143 | ); 144 | 145 | let codeLines = document.getText().split("\n"); 146 | let resourcePath = path.join( 147 | context.extensionPath, 148 | "static" 149 | ); 150 | 151 | ejs.renderFile( 152 | path.join( 153 | resourcePath, 154 | "template", 155 | "report.ejs" 156 | ), 157 | { 158 | resourcePath, 159 | vulnerabilities, 160 | codeLines, 161 | } 162 | ).then((htmlReport) => { 163 | fs.writeFileSync(filePath, htmlReport); 164 | // Create and show panel 165 | const panel = vscode.window.createWebviewPanel( 166 | "Analysis Report", 167 | "Analysis Report", 168 | vscode.ViewColumn.One, 169 | {} 170 | ); 171 | 172 | // And set its HTML content 173 | panel.webview.html = htmlReport; 174 | }); 175 | } 176 | }); 177 | } 178 | }); 179 | } 180 | -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/MadMax/src/patterns.py: -------------------------------------------------------------------------------- 1 | # BSD 3-Clause License 2 | # 3 | # Copyright (c) 2016, 2017, The University of Sydney. All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # * Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # * Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # * Neither the name of the copyright holder nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | """patterns.py: abstract base classes for the various design patterns""" 31 | 32 | import abc 33 | import inspect 34 | 35 | 36 | class Visitable(abc.ABC): 37 | """ 38 | Provides an interface for an object which can accept a :obj:`Visitor`. 39 | """ 40 | 41 | def accept(self, visitor: 'Visitor'): 42 | """ 43 | Accepts a :obj:`Visitor` and calls :obj:`Visitor.visit` 44 | 45 | Args: 46 | visitor: the :obj:`Visitor` to accept 47 | """ 48 | visitor.visit(self) 49 | 50 | 51 | class Visitor(abc.ABC): 52 | """ 53 | Visitor design pattern abstract base class. 54 | """ 55 | 56 | @abc.abstractmethod 57 | def visit(self, target: Visitable, *args, **kwargs): 58 | """ 59 | Visits the given object. 60 | 61 | Args: 62 | target: object to visit. 63 | """ 64 | 65 | def can_visit(self, type_): 66 | """ 67 | Checks if this :obj:`Visitor` can visit an object of the given `type_`. 68 | By default a :obj:`Visitor` can visit all types, so subclasses of 69 | :obj:`Visitor` should override this method if necessary. 70 | 71 | Args: 72 | type_ (type): a valid Python :obj:`type` to be checked. 73 | 74 | Returns: 75 | True (by default) 76 | """ 77 | return True 78 | 79 | 80 | class DynamicVisitor(Visitor): 81 | """ 82 | Visitor base class which dynamically calls a specialised visit method based 83 | on the target's type at runtime. 84 | 85 | Example: 86 | Subclassing :obj:`DynamicVisitor`:: 87 | 88 | class PrinterDynamicVisitor(DynamicVisitor): 89 | def visit_str(self, string:str): 90 | print(string) 91 | 92 | def visit_int(self, integer:int): 93 | print("{:08b}".format(integer)) 94 | 95 | def visit_object(self, obj:object): 96 | print(obj) 97 | 98 | pdv = PrinterDynamicVisitor() 99 | pdv.visit("hello") 100 | pdv.visit(5) 101 | """ 102 | 103 | def __init__(self): 104 | super().__init__() 105 | 106 | # Don't allow instantiation of DynamicVisitor itself 107 | if isinstance(self, DynamicVisitor): 108 | raise NotImplementedError("DynamicVisitor must be sub-classed") 109 | 110 | def visit(self, target: Visitable, *args, **kwargs): 111 | """ 112 | Dispatches to a method called visit_TYPE where TYPE is the dynamic type 113 | (or the nearest parent type) of the `target`. 114 | 115 | Args: 116 | target: object to visit. 117 | *args: arguments to be passed to the type-specific visit method. 118 | **kwargs: optional/keyword arguments to be passed to the type-specific 119 | visit method. 120 | """ 121 | # Try to find a visit method for our target's type 122 | visit_method = self.__get_visit_method(type(target)) 123 | 124 | # If we found a visit method, call it and return its returned value 125 | if visit_method is not None: 126 | return visit_method(target, *args, **kwargs) 127 | 128 | # If no matching visit_TYPE method exists, call _no_visit_found 129 | return self._no_visit_found(target, *args, **kwargs) 130 | 131 | def can_visit(self, type_): 132 | """ 133 | Checks if this :obj:`DynamicVisitor` can visit an object of the given 134 | `type_`. 135 | 136 | Args: 137 | type_ (type): a valid Python :obj:`type` to be checked. 138 | 139 | Returns: 140 | True if the current :obj:`DynamicVisitor` can visit the specified 141 | `type_` or False otherwise. 142 | """ 143 | return self.__get_visit_method(type_) is not None 144 | 145 | def __get_visit_method(self, type_): 146 | """ 147 | Returns a visit method for the given type_, or None if none could be 148 | found. 149 | """ 150 | # Try all the type names in the target's MRO 151 | for base in inspect.getmro(type_): 152 | visit_name = "visit_{}".format(base.__name__) 153 | 154 | # If we found a matching visit_TYPE method, return it 155 | if hasattr(self, visit_name): 156 | visit_method = getattr(self, visit_name) 157 | return visit_method 158 | 159 | # Not found => return None 160 | return None 161 | 162 | def _no_visit_found(self, target, *args, **kwargs): 163 | """ 164 | Called when no matching visit_TYPE method exists for the target's type. 165 | Raises a TypeError by default and should be overridden if different 166 | behaviour is desired by a derived class. 167 | 168 | Args: 169 | target: object passed to :obj:`visit` 170 | *args: arguments passed to :obj:`visit` 171 | **kwargs: keyword arguments passed to :obj:`visit` 172 | 173 | Raises: 174 | TypeError 175 | """ 176 | raise TypeError("could not find a visit method for target type {}" 177 | .format(type(target).__name__)) 178 | -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/MadMax/bin/disassemble: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # BSD 3-Clause License 4 | # 5 | # Copyright (c) 2016, 2017, The University of Sydney. All rights reserved. 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright notice, this 11 | # list of conditions and the following disclaimer. 12 | # 13 | # * Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # 17 | # * Neither the name of the copyright holder nor the names of its 18 | # contributors may be used to endorse or promote products derived from 19 | # this software without specific prior written permission. 20 | # 21 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | # Standard lib imports 33 | import src.settings as settings 34 | import src.blockparse as blockparse 35 | import src.opcodes as opcodes 36 | import argparse 37 | import logging 38 | import sys 39 | import traceback 40 | from os.path import abspath, dirname, join 41 | 42 | # Prepend .. to $PATH so the project modules can be imported below 43 | src_path = join(dirname(abspath(__file__)), "..") 44 | sys.path.insert(0, src_path) 45 | 46 | # Local project imports 47 | 48 | # Colour scheme for prettified output 49 | # See: https://pypi.python.org/pypi/termcolor 50 | PC_COL = "white" 51 | OP_COL = "cyan" 52 | OP_HALT_COL = "red" 53 | OP_FLOW_COL = "magenta" 54 | OP_JDEST_COL = "green" 55 | VAL_COL = "yellow" 56 | COMMENT_COL = "white" 57 | 58 | # Configure argparse 59 | parser = argparse.ArgumentParser(description="An EVM bytecode disassembler") 60 | 61 | parser.add_argument("-p", 62 | "--prettify", 63 | action="store_true", 64 | default=False, 65 | help="colourize the disassembly and separate block " 66 | "boundaries with a newline") 67 | 68 | parser.add_argument("-s", 69 | "--strict", 70 | action="store_true", 71 | default=False, 72 | help="halt and produce no output when malformed " 73 | "input is given") 74 | 75 | parser.add_argument("-o", 76 | "--outfile", 77 | type=argparse.FileType("w"), 78 | default=sys.stdout, 79 | help="file to which decompiler output should be written " 80 | "(stdout by default).") 81 | 82 | parser.add_argument("-v", 83 | "--verbose", 84 | action="store_true", 85 | help="emit verbose debug output to stderr.") 86 | 87 | parser.add_argument("-vv", 88 | "--prolix", 89 | action="store_true", 90 | help="emit debug output to stderr at higher verbosity " 91 | "level.") 92 | 93 | parser.add_argument("infile", 94 | nargs="*", 95 | type=argparse.FileType("r"), 96 | default=sys.stdin, 97 | help="file from which decompiler input should be read " 98 | "(stdin by default).") 99 | 100 | # Parse the arguments. 101 | args = parser.parse_args() 102 | 103 | # Set up logger, with appropriate log level depending on verbosity. 104 | log_level = logging.WARNING 105 | if args.prolix: 106 | log_level = logging.DEBUG 107 | elif args.verbose: 108 | log_level = logging.INFO 109 | logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level) 110 | 111 | if args.prettify: 112 | from termcolor import colored 113 | 114 | # Initialise settings. 115 | settings.import_config(settings._CONFIG_LOC_) 116 | settings.strict = args.strict 117 | 118 | 119 | def format_pc(pc): 120 | pc = "0x{:02x}".format(pc) 121 | if args.prettify: 122 | pc = colored(pc, PC_COL) 123 | return pc 124 | 125 | 126 | def format_opcode(opcode): 127 | op = "{:<6}".format(opcode.name) 128 | if args.prettify: 129 | if opcode.halts(): 130 | op = colored(op, OP_HALT_COL) 131 | elif opcode.alters_flow(): 132 | op = colored(op, OP_FLOW_COL) 133 | elif opcode == opcodes.JUMPDEST: 134 | op = colored(op, OP_JDEST_COL) 135 | else: 136 | op = colored(op, OP_COL) 137 | return op 138 | 139 | 140 | def format_value(value): 141 | if value is None: 142 | return str() 143 | value = "0x{:02x}".format(value) 144 | if args.prettify: 145 | value = colored(value, VAL_COL) 146 | return value 147 | 148 | 149 | try: 150 | for i, infile in enumerate(args.infile): 151 | if hasattr(infile, 'name'): 152 | logging.info("Processing %s", infile.name) 153 | 154 | # for multiple input files, comment above each output with the 155 | # path of its file 156 | if hasattr(args.infile, '__len__') and len(args.infile) > 1: 157 | fname_comment = "; Disassembly from\n; {}\n".format(infile.name) 158 | if args.prettify: 159 | fname_comment = colored(fname_comment, COMMENT_COL, 160 | attrs=['dark']) 161 | print(fname_comment, file=args.outfile) 162 | 163 | # join the bytecode all into one string 164 | bytecode = ''.join(l.strip() for l in infile if len(l.strip()) > 0) 165 | 166 | # parse bytecode and create basic blocks 167 | blocks = blockparse.EVMBytecodeParser(bytecode).parse() 168 | 169 | # Print disassembly from each block 170 | for b in blocks: 171 | for op in b.evm_ops: 172 | print(format_pc(op.pc), 173 | format_opcode(op.opcode), 174 | format_value(op.value), 175 | file=args.outfile) 176 | 177 | if args.prettify: 178 | print("", file=args.outfile) 179 | 180 | # for multiple input files, separate output of each file with a newline 181 | if hasattr(args.infile, '__len__') and i + 1 < len(args.infile): 182 | print("", file=args.outfile) 183 | 184 | # ValueError happens with invalid hexadecimal 185 | except ValueError as e: 186 | logging.exception("Problem while disassembling.") 187 | sys.exit(1) 188 | 189 | # LookupError happens with invalid opcodes 190 | except LookupError as e: 191 | if settings.strict: 192 | logging.exception("Invalid opcode during disassembly (strict).") 193 | sys.exit(1) 194 | else: 195 | logging.debug(traceback.format_exc()) 196 | 197 | # Catch a Control-C and exit with UNIX failure status 1 198 | except KeyboardInterrupt: 199 | logging.info(traceback.format_exc()) 200 | logging.error("\nInterrupted by user") 201 | sys.exit(1) 202 | -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/MadMax/src/evm_cfg.py: -------------------------------------------------------------------------------- 1 | # BSD 3-Clause License 2 | # 3 | # Copyright (c) 2016, 2017, The University of Sydney. All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # * Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # * Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # * Neither the name of the copyright holder nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | """evm_cfg.py: Classes for processing disasm output and building a CFG""" 31 | 32 | import typing as t 33 | 34 | import src.cfg as cfg 35 | import src.opcodes as opcodes 36 | 37 | 38 | class EVMBasicBlock(cfg.BasicBlock): 39 | """ 40 | Represents a single basic block in the control flow graph (CFG), including 41 | its parent and child nodes in the graph structure. 42 | """ 43 | 44 | def __init__(self, entry: int = None, exit: int = None, 45 | evm_ops: t.List['EVMOp'] = None): 46 | """ 47 | Creates a new basic block containing operations between the 48 | specified entry and exit instruction counters (inclusive). 49 | 50 | Args: 51 | entry: block entry point program counter 52 | exit: block exit point program counter 53 | evm_ops: a sequence of operations that constitute this BasicBlock's code. Default empty. 54 | """ 55 | super().__init__(entry, exit) 56 | 57 | self.evm_ops = evm_ops if evm_ops is not None else [] 58 | """List of EVMOps contained within this EVMBasicBlock""" 59 | 60 | self.fallthrough = None 61 | """ 62 | The block that this one falls through to on the false branch 63 | of a JUMPI, if it exists. This should already appear in self.succs; 64 | this just distinguishes which one is the false branch. 65 | """ 66 | # TODO: maybe not vital, but this should interact properly with 67 | # procedure cloning 68 | 69 | def __str__(self): 70 | """Returns a string representation of this block and all ops in it.""" 71 | super_str = super().__str__() 72 | op_seq = "\n".join(str(op) for op in self.evm_ops) 73 | return "\n".join([super_str, self._STR_SEP, op_seq]) 74 | 75 | def split(self, entry: int) -> 'EVMBasicBlock': 76 | """ 77 | Splits current block into a new block, starting at the specified 78 | entry op index. Returns a new EVMBasicBlock with no preds or succs. 79 | 80 | Args: 81 | entry: unique index of EVMOp from which the block should be split. The 82 | EVMOp at this index will become the first EVMOp of the new BasicBlock. 83 | """ 84 | # Create the new block. 85 | new = type(self)(entry, self.exit, self.evm_ops[entry - self.entry:]) 86 | 87 | # Update the current node. 88 | self.exit = entry - 1 89 | self.evm_ops = self.evm_ops[:entry - self.entry] 90 | 91 | # Update the block pointer in each line object 92 | self.__update_evmop_refs() 93 | new.__update_evmop_refs() 94 | 95 | return new 96 | 97 | def __update_evmop_refs(self): 98 | # Update references back to parent block for each opcode 99 | # This needs to be done when a block is split 100 | for op in self.evm_ops: 101 | op.block = self 102 | 103 | 104 | class EVMOp: 105 | """ 106 | Represents a single EVM operation. 107 | """ 108 | 109 | def __init__(self, pc: int, opcode: opcodes.OpCode, value: int = None): 110 | """ 111 | Create a new EVMOp object from the given params which should correspond to 112 | disasm output. 113 | 114 | Args: 115 | pc: program counter of this operation 116 | opcode: VM operation code 117 | value: constant int value or default None in case of non-PUSH operations 118 | 119 | Each line of disasm output is structured as follows: 120 | 121 | PC OPCODE => CONSTANT 122 | 123 | where: 124 | - PC is the program counter 125 | - OPCODE is an object representing an EVM instruction code 126 | - CONSTANT is a hexadecimal value with 0x notational prefix 127 | - is a variable number of spaces 128 | 129 | For instructions with no hard-coded constant data (i.e. non-PUSH 130 | instructions), the disasm output only includes PC and OPCODE; i.e. 131 | 132 | PC OPCODE 133 | 134 | If None is passed to the value parameter, the instruction is assumed to 135 | contain no CONSTANT (as in the second example above). 136 | """ 137 | 138 | self.pc = pc 139 | """Program counter of this operation""" 140 | 141 | self.opcode = opcode 142 | """VM operation code""" 143 | 144 | self.value = value 145 | """Constant int value or None""" 146 | 147 | self.block = None 148 | """EVMBasicBlock object to which this line belongs""" 149 | 150 | def __str__(self): 151 | if self.value is None: 152 | return "{0} {1}".format(hex(self.pc), self.opcode) 153 | else: 154 | return "{0} {1} {2}".format( 155 | hex(self.pc), self.opcode, hex(self.value)) 156 | 157 | def __repr__(self): 158 | return "<{0} object {1}: {2}>".format( 159 | self.__class__.__name__, 160 | hex(id(self)), 161 | self.__str__() 162 | ) 163 | 164 | 165 | def blocks_from_ops(ops: t.Iterable[EVMOp]) -> t.Iterable[EVMBasicBlock]: 166 | """ 167 | Process a sequence of EVMOps and create a sequence of EVMBasicBlocks. 168 | 169 | Args: 170 | ops: sequence of EVMOps to be put into blocks. 171 | 172 | Returns: 173 | List of BasicBlocks from the input ops, in arbitrary order. 174 | """ 175 | blocks = [] 176 | 177 | # details for block currently being processed 178 | entry, exit = (0, len(ops) - 1) if len(ops) > 0 \ 179 | else (None, None) 180 | current = EVMBasicBlock(entry, exit) 181 | 182 | # Linear scan of all EVMOps to create initial EVMBasicBlocks 183 | for i, op in enumerate(ops): 184 | op.block = current 185 | current.evm_ops.append(op) 186 | 187 | # Flow-altering opcodes indicate end-of-block 188 | if op.opcode.alters_flow(): 189 | new = current.split(i + 1) 190 | blocks.append(current) 191 | 192 | # Mark all JUMPs as unresolved 193 | if op.opcode in (opcodes.JUMP, opcodes.JUMPI): 194 | current.has_unresolved_jump = True 195 | 196 | # Process the next sequential block in our next iteration 197 | current = new 198 | 199 | # JUMPDESTs indicate the start of a block. 200 | # A JUMPDEST should be split on only if it's not already the first 201 | # operation in a block. In this way we avoid producing empty blocks if 202 | # JUMPDESTs follow flow-altering operations. 203 | elif op.opcode == opcodes.JUMPDEST and len(current.evm_ops) > 1: 204 | new = current.split(i) 205 | blocks.append(current) 206 | current = new 207 | 208 | # Always add last block if its last instruction does not alter flow 209 | elif i == len(ops) - 1: 210 | blocks.append(current) 211 | 212 | return blocks 213 | -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/MadMax/src/blockparse.py: -------------------------------------------------------------------------------- 1 | # BSD 3-Clause License 2 | # 3 | # Copyright (c) 2016, 2017, The University of Sydney. All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # * Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # * Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # * Neither the name of the copyright holder nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | """blockparse.py: Parse operation sequences and construct basic blocks""" 31 | 32 | import abc 33 | import logging 34 | import typing as t 35 | 36 | import src.cfg as cfg 37 | import src.evm_cfg as evm_cfg 38 | import src.opcodes as opcodes 39 | import src.settings as settings 40 | 41 | ENDIANNESS = "big" 42 | """ 43 | The endianness to use when parsing hexadecimal or binary files. 44 | """ 45 | 46 | 47 | class BlockParser(abc.ABC): 48 | @abc.abstractmethod 49 | def __init__(self, raw: object): 50 | """ 51 | Constructs a new BlockParser for parsing the given raw input object. 52 | 53 | Args: 54 | raw: parser-specific object containing raw input to be parsed. 55 | """ 56 | 57 | self._raw = raw 58 | """raw: parser-specific object containing raw input to be parsed.""" 59 | 60 | self._ops = [] 61 | """ 62 | List of program operations extracted from the raw input object. 63 | Indices from this list are used as unique identifiers for program 64 | operations when constructing BasicBlocks. 65 | """ 66 | 67 | @abc.abstractmethod 68 | def parse(self) -> t.Iterable[cfg.BasicBlock]: 69 | """ 70 | Parses the raw input object and returns an iterable of BasicBlocks. 71 | """ 72 | self._ops = [] 73 | return self._ops 74 | 75 | 76 | class EVMDasmParser(BlockParser): 77 | def __init__(self, dasm: t.Iterable[str]): 78 | """ 79 | Parses raw EVM disassembly lines and creates corresponding EVMBasicBlocks. 80 | This does NOT follow jumps or create graph edges - it just parses the 81 | disassembly and creates the blocks. 82 | 83 | Args: 84 | dasm: iterable of raw disasm output lines to be parsed by this instance 85 | """ 86 | super().__init__(dasm) 87 | 88 | def parse(self): 89 | """ 90 | Parses the raw input object containing EVM disassembly 91 | and returns an iterable of EVMBasicBlocks. 92 | """ 93 | 94 | super().parse() 95 | 96 | # Construct a list of EVMOp objects from the raw input disassembly 97 | # lines, ignoring the first line of input (which is the bytecode's hex 98 | # representation when using Ethereum's disasm tool). Any line which does 99 | # not produce enough tokens to be valid disassembly after being split() is 100 | # also ignored. 101 | for i, l in enumerate(self._raw): 102 | if len(l.split()) == 1: 103 | logging.debug( 104 | "Line %s: invalid disassembly:\n %s", 105 | i + 1, 106 | l.rstrip()) 107 | if settings.strict: 108 | raise RuntimeError( 109 | "Line {}: invalid disassembly {}".format( 110 | i + 1, l)) 111 | continue 112 | elif len(l.split()) < 1: 113 | if settings.strict: 114 | logging.warning("Line %s: empty disassembly.", i + 1) 115 | raise RuntimeError( 116 | "Line {}: empty disassembly.".format( 117 | i + 1)) 118 | continue 119 | 120 | try: 121 | self._ops.append(self.evm_op_from_dasm(l)) 122 | except (ValueError, LookupError, NotImplementedError) as e: 123 | logging.debug( 124 | "Line %s: invalid disassembly:\n %s", 125 | i + 1, 126 | l.rstrip()) 127 | if settings.strict: 128 | raise e 129 | 130 | return evm_cfg.blocks_from_ops(self._ops) 131 | 132 | @staticmethod 133 | def evm_op_from_dasm(line: str) -> evm_cfg.EVMOp: 134 | """ 135 | Creates and returns a new EVMOp object from a raw line of disassembly. 136 | 137 | Args: 138 | line: raw line of output from Ethereum's disasm disassembler. 139 | 140 | Returns: 141 | evm_cfg.EVMOp: the constructed EVMOp 142 | """ 143 | toks = line.replace("=>", " ").split() 144 | 145 | # Convert hex PCs to ints 146 | if toks[0].startswith("0x"): 147 | toks[0] = int(toks[0], 16) 148 | 149 | if len(toks) > 2: 150 | val = int(toks[2], 16) 151 | try: 152 | return evm_cfg.EVMOp( 153 | int(toks[0]), opcodes.opcode_by_name(toks[1]), val) 154 | except LookupError as e: 155 | return evm_cfg.EVMOp( 156 | int(toks[0]), opcodes.missing_opcode(val), val) 157 | elif len(toks) > 1: 158 | return evm_cfg.EVMOp(int(toks[0]), opcodes.opcode_by_name(toks[1])) 159 | else: 160 | raise NotImplementedError( 161 | "Could not parse unknown disassembly format:" + 162 | "\n {}".format(line)) 163 | 164 | 165 | class EVMBytecodeParser(BlockParser): 166 | def __init__(self, bytecode: t.Union[str, bytes]): 167 | """ 168 | Parse EVM bytecode directly into basic blocks. 169 | 170 | Args: 171 | bytecode: EVM bytecode, either as a hexadecimal string or a bytes 172 | object. If given as a hex string, it may optionally start with 0x. 173 | """ 174 | super().__init__(bytecode) 175 | 176 | if isinstance(bytecode, str): 177 | bytecode = bytes.fromhex(bytecode.replace("0x", "")) 178 | else: 179 | bytecode = bytes(bytecode) 180 | 181 | self._raw = bytecode 182 | 183 | # Track the program counter as we traverse the bytecode 184 | self.__pc = 0 185 | 186 | def __consume(self, n): 187 | bytes_ = self._raw[self.__pc: self.__pc + n] 188 | self.__pc += n 189 | return bytes_ 190 | 191 | def __has_more_bytes(self): 192 | return self.__pc < len(self._raw) 193 | 194 | def parse(self) -> t.Iterable[evm_cfg.EVMBasicBlock]: 195 | """ 196 | Parses the raw input object containing EVM bytecode 197 | and returns an iterable of EVMBasicBlocks. 198 | """ 199 | 200 | super().parse() 201 | 202 | while self.__has_more_bytes(): 203 | pc = self.__pc 204 | byte = int.from_bytes(self.__consume(1), ENDIANNESS) 205 | const, const_size = None, 0 206 | 207 | try: 208 | # try to resolve the byte to an opcode 209 | op = opcodes.opcode_by_value(byte) 210 | 211 | except LookupError as e: 212 | # oops, unknown opcode 213 | if settings.strict: 214 | logging.warning( 215 | "(strict) Invalid opcode at PC = %#02x: %s", pc, str(e)) 216 | raise e 217 | # not strict, so just log: 218 | logging.debug("Invalid opcode at PC = %#02x: %s", pc, str(e)) 219 | op = opcodes.missing_opcode(byte) 220 | const = byte 221 | 222 | # push codes have an argument 223 | if op.is_push(): 224 | const_size = op.push_len() 225 | 226 | # for opcodes with an argument, consume the argument 227 | if const_size > 0: 228 | const = int.from_bytes(self.__consume(const_size), ENDIANNESS) 229 | 230 | self._ops.append(evm_cfg.EVMOp(pc, op, const)) 231 | 232 | # build basic blocks from the sequence of opcodes 233 | return evm_cfg.blocks_from_ops(self._ops) 234 | -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/MadMax/src/settings.py: -------------------------------------------------------------------------------- 1 | # BSD 3-Clause License 2 | # 3 | # Copyright (c) 2016, 2017, The University of Sydney. All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # * Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # * Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # * Neither the name of the copyright holder nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | """settings.py: dataflow analysis settings. 31 | 32 | The user can change these settings in bin/config.ini or by providing command 33 | line flags to override them. 34 | Default settings are stored in src/default_config.ini. 35 | 36 | max_iterations: 37 | The maximum number of graph analysis iterations. 38 | Lower is faster, but potentially less precise. 39 | A negative value means no limit. No limit by default. 40 | 41 | bailout_seconds: 42 | Begin to terminate the analysis loop if it's looking to take more time 43 | than specified. Bailing out early may mean the analysis is not able 44 | to reach a fixed-point, so the results may be less precise. 45 | This is not a hard cap, as subsequent analysis steps are required, 46 | and at least one iteration will always be performed. 47 | A negative value means no cap on the running time. 48 | No cap by default. 49 | 50 | remove_unreachable: 51 | Upon completion of the analysis, if there are blocks unreachable from the 52 | contract root, remove them. False by default. 53 | 54 | merge_unreachable: 55 | Upon completion of the analysis, if there are blocks unreachable from the 56 | contract root, merge them. True by default. 57 | 58 | die_on_empty_pop: 59 | Raise an exception if an empty stack is popped. False by default. 60 | 61 | skip_stack_on_overflow: 62 | Do not apply changes to exit stacks after a symbolic overflow occurrs 63 | in their blocks. True by default. 64 | 65 | reinit_stacks: 66 | Reinitialise all blocks' exit stacks to be empty. True by default. 67 | 68 | hook_up_stack_vars: 69 | After completing the analysis, propagate entry stack values into blocks. 70 | True by default. 71 | 72 | hook_up_jumps: 73 | Connect any new edges that can be inferred after performing the analysis. 74 | True by default. 75 | 76 | mutate_jumps: 77 | JUMPIs with known conditions become JUMPs (or are deleted). 78 | For example, a JUMPI with a known-true condition becomes a JUMP. 79 | False by default. 80 | 81 | generate_throws: 82 | JUMP and JUMPI instructions with invalid destinations become THROW and 83 | THROWIs. False by default. 84 | 85 | final_mutate_jumps: 86 | Mutate jumps in the final analysis phase. False by default. 87 | 88 | final_generate_throws: 89 | generate throws in the final analysis phase. True by default. 90 | 91 | mutate_blockwise: 92 | Hook up stack vars and/or hook up jumps after each block rather than after 93 | the whole analysis is complete. True by default. 94 | 95 | clamp_large_stacks: 96 | If stacks start growing deeper without more of the program's control flow 97 | graph being inferred for sufficiently many iterations, we can freeze the 98 | maximum stack size in order to save computation. 99 | True by default. 100 | 101 | clamp_stack_minimum: 102 | Stack sizes will not be clamped smaller than this value. Default value is 20. 103 | 104 | widen_variables: 105 | If any computed variable's number of possible values exceeds a given 106 | threshold, widen its value to Top. True by default. 107 | 108 | widen_threshold: 109 | Whenever the result of an operation may take more than this number of 110 | possible values, then widen the result variable's value to the Top lattice 111 | value (treat its value as unconstrained). 112 | Default value is 10. 113 | 114 | set_valued_ops: 115 | If True, apply arithmetic operations to variables with multiple values; 116 | otherwise, only apply them to variables whose value takes only one 117 | value. 118 | Disable to gain speed at the cost of precision. True by default. 119 | 120 | analytics: 121 | If True, dataflow analysis will return a dict of information about 122 | the contract, otherwise return an empty dict. 123 | Disabling this might yield a slight speed improvement. False by default. 124 | 125 | extract_functions: 126 | If True, attempt to extract solidity functions. 127 | 128 | mark_functions: 129 | If true, tag block names with the function(s) they belong to. 130 | 131 | strict: 132 | If true, then unrecognised opcodes and invalid disassembly 133 | will not be skipped, but will result in an error. 134 | 135 | Note: If we have already reached complete information about our stack CFG 136 | structure and stack states, we can use die_on_empty_pop and reinit_stacks 137 | to discover places where empty stack exceptions will be thrown. 138 | """ 139 | 140 | # The settings - these are None until initialised by import_config 141 | from os.path import dirname, normpath, join 142 | import sys 143 | import logging 144 | max_iterations = None 145 | bailout_seconds = None 146 | remove_unreachable = None 147 | merge_unreachable = None 148 | die_on_empty_pop = None 149 | skip_stack_on_overflow = None 150 | reinit_stacks = None 151 | hook_up_stack_vars = None 152 | hook_up_jumps = None 153 | mutate_jumps = None 154 | generate_throws = None 155 | final_mutate_jumps = None 156 | final_generate_throws = None 157 | mutate_blockwise = None 158 | clamp_large_stacks = None 159 | clamp_stack_minimum = None 160 | widen_variables = None 161 | widen_threshold = None 162 | set_valued_ops = None 163 | analytics = None 164 | extract_functions = None 165 | mark_functions = None 166 | strict = None 167 | 168 | # A reference to this module for retrieving its members; import sys like 169 | # this so that it does not appear in _names_. 170 | _module_ = __import__("sys").modules[__name__] 171 | 172 | # The names of all the settings defined above. 173 | _names_ = [s for s in dir(_module_) if not (s.startswith("_") or s in [ 174 | "dirname", "join", "normpath", "sys", "logging"])] 175 | 176 | # Set up the types of the various settings, so they can be converted 177 | # correctly when being read from config. 178 | _types_ = {n: ("int" if n in ["max_iterations", "bailout_seconds", 179 | "clamp_stack_minimum", "widen_threshold"] 180 | else "bool") for n in _names_} 181 | 182 | # A stack for saving and restoring setting configurations. 183 | _stack_ = [] 184 | 185 | # Imports and definitions appearing below the definition of _names_ 186 | # do not appear in that list, by design. Don't move them up. 187 | 188 | _dir_ = dirname(__file__) 189 | 190 | # Default settings are stored here. 191 | _DEFAULT_LOC_ = normpath(join(_dir_, "../src/default_config.ini")) 192 | 193 | # User settings are located here, and will override default settings. 194 | _CONFIG_LOC_ = normpath(join(_dir_, "../bin/config.ini")) 195 | 196 | 197 | def _get_dict_(): 198 | """ 199 | Return the current module's dictionary of members so the settings can be 200 | dynamically accessed by name. 201 | """ 202 | return _module_.__dict__ 203 | 204 | 205 | def save(): 206 | """Push the current setting configuration to the stack.""" 207 | sd = _get_dict_() 208 | _stack_.append({n: sd[n] for n in _names_}) 209 | 210 | 211 | def restore(): 212 | """Restore the setting configuration from the top of the stack.""" 213 | _get_dict_().update(_stack_.pop()) 214 | 215 | 216 | def set_from_string(setting_name: str, value: str): 217 | """ 218 | Assign to the named setting the given value, first converting that value 219 | to the type appropriate for that setting. 220 | Names and values are not case sensitive. 221 | """ 222 | name = setting_name.lower() 223 | val = value.lower() 224 | 225 | if name not in _names_: 226 | logging.error('Unrecognised setting "%s".', setting_name) 227 | sys.exit(1) 228 | 229 | if _types_[name] == "int": 230 | _get_dict_()[name] = int(val) 231 | elif _types_[name] == "bool": 232 | if val in {"1", "yes", "true", "on"}: 233 | _get_dict_()[name] = True 234 | elif val in {"0", "no", "false", "off"}: 235 | _get_dict_()[name] = False 236 | else: 237 | logging.error( 238 | 'Cannot interpret value "%s" as boolean for setting "%s"', 239 | value, 240 | setting_name) 241 | sys.exit(1) 242 | else: 243 | logging.error('Unknown type "%s" for setting "%s".', setting_name) 244 | sys.exit(1) 245 | 246 | 247 | def import_config(filepath: str = _CONFIG_LOC_): 248 | """ 249 | Import settings from the given configuration file. 250 | This should be called before running the decompiler. 251 | """ 252 | import configparser 253 | config = configparser.ConfigParser() 254 | with open(_DEFAULT_LOC_) as default: 255 | config.read_file(default) 256 | config.read(filepath) 257 | print(_names_) 258 | for name in _names_: 259 | set_from_string(name, config.get("settings", name)) 260 | -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/MadMax/bin/decompile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # BSD 3-Clause License 4 | # 5 | # Copyright (c) 2016, 2017, The University of Sydney. All rights reserved. 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright notice, this 11 | # list of conditions and the following disclaimer. 12 | # 13 | # * Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # 17 | # * Neither the name of the copyright holder nor the names of its 18 | # contributors may be used to endorse or promote products derived from 19 | # this software without specific prior written permission. 20 | # 21 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | # Standard lib imports 33 | from src import settings, tac_cfg, dataflow, exporter 34 | import os 35 | import sys 36 | import argparse 37 | import logging 38 | from os.path import abspath, dirname, join 39 | 40 | # Prepend .. to $PATH so the project modules can be imported below 41 | src_path = join(dirname(abspath(__file__)), "..") 42 | sys.path.insert(0, src_path) 43 | 44 | # Local project imports 45 | 46 | # Version string to display with -v 47 | VERSION = """\ 48 | +------------------------------+ 49 | | Vandal EVM Decompiler v0.0.3 | 50 | | (c) The University of Sydney | 51 | +------------------------------+\ 52 | """ 53 | 54 | 55 | # Define a version() function in case we want dynamic version strings later 56 | def version(): 57 | return VERSION 58 | 59 | 60 | # Configure argparse 61 | parser = argparse.ArgumentParser( 62 | description="An EVM bytecode disassembly decompiler that generates " 63 | "three-address code for program analysis. Use config.ini " 64 | "to set further configuration options.") 65 | 66 | parser.add_argument("-a", 67 | "--disassembly", 68 | action="store_true", 69 | default=False, 70 | help="decompile dissassembled input. Overrides '-b'.") 71 | 72 | parser.add_argument("-b", 73 | "--bytecode", 74 | action="store_true", 75 | default=False, 76 | help="disassemble and decompile bytecode input. This is " 77 | "the default mode.") 78 | 79 | parser.add_argument("-i", 80 | "--instruction_facts", 81 | nargs="?", 82 | const="", 83 | metavar="DIR", 84 | default=None, 85 | help="produce facts for original bytecode input.") 86 | 87 | parser.add_argument("-g", 88 | "--graph", 89 | nargs="?", 90 | const="cfg.html", 91 | metavar="FILE", 92 | default=None, 93 | help="generate a visual representation of basic block " 94 | "relationships with the format inferred from the " 95 | "given output filename (cfg.dot by default). " 96 | "Non-DOT formats like pdf, png, etc. require " 97 | "Graphviz to be installed. Use html to generate " 98 | "an interactive web page of the graph.") 99 | 100 | parser.add_argument("-t", 101 | "--tsv", 102 | nargs="?", 103 | const="", 104 | metavar="DIR", 105 | default=None, 106 | help="generate tab-separated .facts files for Souffle " 107 | "that represents the control flow graph. " 108 | "Write files to the specified directory, which " 109 | "will be recursively created if it does not exist " 110 | "(current working directory by default).") 111 | 112 | parser.add_argument("-d", 113 | "--dominators", 114 | action="store_true", 115 | default=False, 116 | help="If producing tsv output, also include graph " 117 | "dominator relations.") 118 | 119 | parser.add_argument("-o", 120 | "--opcodes", 121 | nargs="*", 122 | default=[], 123 | metavar="OPCODE", 124 | help="If producing tsv output, also include relations " 125 | "encoding all occurrences of the specified " 126 | "list of opcodes. Opcode X will be stored in " 127 | "op_X.facts.") 128 | 129 | parser.add_argument("-c", 130 | "--config", 131 | metavar="CFG_STRING", 132 | help="override settings from the configuration files " 133 | "in the format \"key1=value1, key2=value2...\" " 134 | "(with the quotation marks).") 135 | 136 | parser.add_argument("-C", 137 | "--config_file", 138 | default=settings._CONFIG_LOC_, 139 | metavar="FILE", 140 | help="read the settings from the given file; " 141 | "any given settings will override the defaults. " 142 | "Read from config.ini if not set.") 143 | 144 | parser.add_argument("-v", 145 | "--verbose", 146 | action="store_true", 147 | help="emit verbose debug output to stderr.") 148 | 149 | parser.add_argument("-vv", 150 | "--prolix", 151 | action="store_true", 152 | help="higher verbosity level, including extra debug " 153 | "messages from dataflow analysis and elsewhere.") 154 | 155 | parser.add_argument("-n", 156 | "--no_out", 157 | action="store_true", 158 | help="do not output decompiled graph.") 159 | 160 | parser.add_argument("-V", 161 | "--version", 162 | action="store_true", 163 | help="show program's version number and exit.") 164 | 165 | parser.add_argument("infile", 166 | nargs="?", 167 | type=argparse.FileType("r"), 168 | default=sys.stdin, 169 | help="file from which decompiler input should be read " 170 | "(stdin by default).") 171 | 172 | parser.add_argument("outfile", 173 | nargs="?", 174 | type=argparse.FileType("w"), 175 | default=sys.stdout, 176 | help="file to which decompiler output should be written " 177 | "(stdout by default).") 178 | 179 | # Parse the arguments. 180 | args = parser.parse_args() 181 | 182 | # Set up logger, with appropriate log level depending on verbosity. 183 | log_level = logging.WARNING 184 | if args.prolix: 185 | log_level = logging.DEBUG 186 | elif args.verbose: 187 | log_level = logging.INFO 188 | logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level) 189 | 190 | # Handle --version 191 | if args.version: 192 | print(version()) 193 | sys.exit(1) 194 | 195 | # Always show version for log_level >= LOW 196 | logging.info("\n" + version()) 197 | 198 | # Initialise data flow settings. 199 | settings.import_config(args.config_file) 200 | 201 | # Override config file with any provided settings. 202 | if args.config is not None: 203 | pairs = [pair.split("=") 204 | for pair in args.config.replace(" ", "").split(",")] 205 | for k, v in pairs: 206 | settings.set_from_string(k, v) 207 | 208 | # Build TAC CFG from input file 209 | try: 210 | logging.info("Reading from '%s'.", args.infile.name) 211 | if args.disassembly: 212 | cfg = tac_cfg.TACGraph.from_dasm(args.infile) 213 | else: 214 | cfg = tac_cfg.TACGraph.from_bytecode(args.infile) 215 | logging.info("Initial CFG generation completed.") 216 | 217 | # Catch a Control-C and exit with UNIX failure status 1 218 | except KeyboardInterrupt: 219 | logging.critical("\nInterrupted by user") 220 | sys.exit(1) 221 | 222 | # Initialise data flow settings. 223 | settings.import_config(args.config_file) 224 | 225 | # Override config file with any provided settings. 226 | if args.config is not None: 227 | pairs = [pair.split("=") 228 | for pair in args.config.replace(" ", "").split(",")] 229 | for k, v in pairs: 230 | settings.set_from_string(k, v) 231 | 232 | # Run data flow analysis 233 | anal_results = dataflow.analyse_graph(cfg) 234 | print(anal_results["funcs"]) 235 | 236 | # Generate output using the requested exporter(s) 237 | if not args.no_out: 238 | logging.info("Writing string output.") 239 | print(exporter.CFGStringExporter(cfg).export(), file=args.outfile) 240 | 241 | if args.graph is not None: 242 | exporter.CFGDotExporter(cfg).export(args.graph) 243 | 244 | if args.tsv is not None: 245 | logging.info("Writing TSV output.") 246 | exporter.CFGTsvExporter(cfg).export(output_dir=args.tsv, 247 | dominators=args.dominators, 248 | out_opcodes=args.opcodes) 249 | if args.instruction_facts is not None: 250 | logging.info("Writing TSV output.") 251 | exporter.InstructionTsvExporter(cfg).export( 252 | output_dir=args.instruction_facts) 253 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/lib/vandal.dl: -------------------------------------------------------------------------------- 1 | // BSD 3-Clause License 2 | // 3 | // Copyright (c) 2016, 2017, The University of Sydney. All rights reserved. 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are met: 7 | // 8 | // * Redistributions of source code must retain the above copyright notice, this 9 | // list of conditions and the following disclaimer. 10 | // 11 | // * Redistributions in binary form must reproduce the above copyright notice, 12 | // this list of conditions and the following disclaimer in the documentation 13 | // and/or other materials provided with the distribution. 14 | // 15 | // * Neither the name of the copyright holder nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | #include "types.dl" 31 | #include "edb.dl" 32 | #include "opcodes.dl" 33 | 34 | // LOGIC 35 | 36 | // p reaches q if there is a path from p to q in the CFG. 37 | .decl reaches(p:Statement, q:Statement) 38 | reaches(p, p) :- 39 | op(p, _). 40 | 41 | reaches(p, q) :- 42 | edge(p, q). 43 | 44 | reaches(p, q) :- 45 | edge(p, u), 46 | reaches(u, q). 47 | 48 | // The value of x depends on the value of y 49 | // I.e. y is used to calculate x, either directly as an input of the operation 50 | // that defined x, or transitively. 51 | .decl depends(x:Variable, y:Variable) 52 | depends(x, x) :- 53 | use(x, _, _). 54 | 55 | depends(x, x) :- 56 | def(x, _). 57 | 58 | depends(x, y) :- 59 | def(x, stmt), 60 | use(y, stmt, _). 61 | 62 | depends(x, z) :- 63 | depends(x, y), 64 | depends(y, z). 65 | 66 | // x controls whether y is executed. 67 | .decl controls(x:Statement, y:Statement) 68 | controls(x, y) :- 69 | edge(x, w1), 70 | edge(x, w2), 71 | pdom(w1, y), 72 | !reaches(w2, y). 73 | 74 | controls(x, y) :- 75 | pdom(x, y), 76 | op(x, "THROWI"). 77 | //controls(x, z) :- controls(x, y), controls(y, z). // We probably don't want transitivity here? 78 | 79 | // var is the guard variable of some conditional jump or throw stmt 80 | .decl conditionVar(var: Variable, stmt:Statement) 81 | 82 | conditionVar(var, stmt) :- 83 | op(stmt, "THROWI"), 84 | use(var, stmt, _). 85 | 86 | conditionVar(var, stmt) :- 87 | op_JUMPI(stmt, _, var). 88 | 89 | // x controls whether y executes by the value in cond 90 | .decl controlsWith(x:Statement, y:Statement, cond:Variable) 91 | 92 | controlsWith(x, y, cond) :- 93 | controls(x, y), 94 | conditionVar(cond, x). 95 | 96 | // var holds the result of executing the ORIGIN opcode 97 | .decl originResult(var:Variable, stmt:Statement) 98 | originResult(var, stmt) :- 99 | op(stmt, "ORIGIN"), 100 | def(var, stmt). 101 | 102 | // var holds the result of executing the GAS opcode 103 | .decl gasResult(var:Variable, stmt:Statement) 104 | gasResult(var, stmt) :- 105 | op(stmt, "GAS"), 106 | def(var, stmt). 107 | 108 | // var is the result of a CALL operation, stmt 109 | .decl callResult(var:Variable, stmt:Statement) 110 | callResult(var, stmt) :- 111 | op(stmt, "CALL"), 112 | def(var, stmt). 113 | 114 | .decl stateReadResult(var:Variable, stmt:Statement) 115 | stateReadResult(var, stmt) :- 116 | op(stmt, "SLOAD"), 117 | def(var, stmt). 118 | 119 | // variable is used to store to state at stmt 120 | .decl stateWriteUse(var:Variable, stmt:Statement) 121 | stateWriteUse(var, stmt) :- 122 | op(stmt, "SSTORE"), 123 | use(var, stmt, _). 124 | 125 | // Disjunction of previous relations. 126 | .decl usedInStateOrCond(var:Variable, stmt:Statement) 127 | usedInStateOrCond(var, stmt) :- 128 | conditionVar(var, stmt). 129 | 130 | usedInStateOrCond(var, stmt) :- 131 | stateWriteUse(var, stmt). 132 | 133 | 134 | // statement is a binary operator and has two distinct argument variables 135 | .decl distinctArgs2(stmt:Statement, op1:Variable, op2:Variable) 136 | distinctArgs2(stmt, op1, op2) :- 137 | op(stmt, opcode), 138 | binArith(opcode), 139 | use(op1, stmt, _), 140 | use(op2, stmt, _), 141 | op1 != op2. 142 | 143 | // statement is a ternary operator and has at least two distinct argument variables 144 | .decl twoDistinctArgs3(stmt:Statement, op1:Variable, op2:Variable) 145 | twoDistinctArgs3(stmt, op1, op2) :- 146 | op(stmt, opcode), 147 | ternArith(opcode), 148 | use(op1, stmt, _), 149 | use(op2, stmt, _), 150 | op1 != op2. 151 | 152 | // statement is a ternary operator and has three distinct argument variables 153 | .decl distinctArgs3(stmt:Statement, op1:Variable, op2:Variable, op3:Variable) 154 | distinctArgs3(stmt, op1, op2, op3) :- 155 | op(stmt, opcode), 156 | ternArith(opcode), 157 | use(op1, stmt, _), 158 | use(op2, stmt, _), 159 | use(op3, stmt, _), 160 | op1 != op2, 161 | op1 != op3, 162 | op2 != op3. 163 | 164 | // Note: distinctArgs3 => twoDistinctArgs3 but not vice versa. 165 | 166 | .decl manipulable(var:Variable) 167 | // A variable with a known value is "manipulable" in the sense that we can predict its value. 168 | manipulable(var) :- 169 | value(var, _). 170 | 171 | // We will also consider address values which have been masked out to be manipulable, as they are zero in their meaningful portion. 172 | manipulable(var) :- 173 | manipulableAddress(var). 174 | 175 | // A variable is "manipulable" if it can be set by the transaction source 176 | manipulable(var) :- 177 | def(var, stmt), 178 | op(stmt, opcode), 179 | runtimeKnowable(opcode). 180 | 181 | // Or if it's a call data load whose address is itself also settable by the transaction source. 182 | manipulable(var) :- 183 | def(var, stmt), 184 | op(stmt, "CALLDATALOAD"), 185 | use(addressVar, stmt, _), 186 | manipulable(addressVar). 187 | 188 | // Transitively, if it's the result of applying an operation to manipulable operands. 189 | // unary operators 190 | manipulable(var) :- 191 | def(var, stmt), 192 | op(stmt, opcode), 193 | unaryArith(opcode), 194 | use(operand, stmt, _), 195 | manipulable(operand). 196 | 197 | // binary operators 198 | manipulable(var) :- 199 | def(var, stmt), 200 | op(stmt, opcode), 201 | binArith(opcode), 202 | distinctArgs2(stmt, op1, op2), 203 | manipulable(op1), 204 | manipulable(op2). 205 | 206 | manipulable(var) :- 207 | def(var, stmt), 208 | op(stmt, opcode), 209 | binArith(opcode), 210 | use(operand, stmt, _), 211 | manipulable(operand), 212 | !distinctArgs2(stmt, _, _). 213 | 214 | // ternary operators 215 | manipulable(var) :- 216 | def(var, stmt), 217 | op(stmt, opcode), 218 | ternArith(opcode), 219 | distinctArgs3(stmt, op1, op2, op3), 220 | manipulable(op1), 221 | manipulable(op2), 222 | manipulable(op3). 223 | 224 | manipulable(var) :- 225 | def(var, stmt), 226 | op(stmt, opcode), 227 | ternArith(opcode), 228 | twoDistinctArgs3(stmt, op1, op2), 229 | manipulable(op1), 230 | manipulable(op2), 231 | !distinctArgs3(stmt, _, _, _). 232 | 233 | manipulable(var) :- 234 | def(var, stmt), 235 | op(stmt, opcode), 236 | ternArith(opcode), 237 | use(operand, stmt, _), 238 | manipulable(operand), 239 | !twoDistinctArgs3(stmt, _, _). 240 | 241 | // A variable whose value is masked out in the lower 20 bytes is considered constant for addressing purposes 242 | .decl manipulableAddress(var:Variable) 243 | 244 | manipulableAddress(var) :- 245 | def(var, stmt), 246 | op(stmt, "AND"), 247 | use(mask, stmt, _), 248 | value(mask, "0xffffffffffffffffffffffff0000000000000000000000000000000000000000"). 249 | 250 | // Finally, if a variable was recovered from some storage location which can be written with a manipulable 251 | // value at some accessible program point, it's manipulable. 252 | // Possible improvements: check if the stateLocVar is manipulable, rather than just constant 253 | // check that the state location was not overwritten on some incoming path from the root 254 | // with an unmanipulable value. 255 | 256 | // var is read from a possibly-nonmanipulable-because-inaccessible state location written at storeStmt 257 | .decl possAccessManipState(var:Variable, storeStmt:Statement) 258 | 259 | possAccessManipState(var, storeStmt) :- 260 | stateReadResult(var, stateReadStmt), 261 | use(stateLocVar, stateReadStmt, _), 262 | op_SSTORE(storeStmt, stateLocVar2, stateVal), 263 | value(stateLocVar, stateLoc), 264 | value(stateLocVar2, stateLoc), 265 | manipulable(stateVal), 266 | !inaccessible(storeStmt). 267 | 268 | //manipulable(var) :- possAccessManipState(var, storeStmt), !inaccessible(storeStmt). 269 | //manipulable(var) :- possAccessManipState(var, storeStmt), 270 | //!controlsWith(_, storeStmt, _). 271 | //manipulable(var) :- possAccessManipState(var, storeStmt), 272 | //controlsWith(_, storeStmt, cond), manipulable(cond). 273 | 274 | 275 | // Check that all paths from root to a statment n MUST go through some node in a set S. 276 | 277 | // stmt is a SSTORE to location loc, writing a non-manipulable value or a constant. 278 | //.decl storenonManipAtLoc(stmt:Statement, loc:Value) 279 | //storenonManipAtLoc(stmt, loc) :- op_SSTORE(stmt, var, val), value(var, loc), !nonConstManip(val). 280 | 281 | //edgeNotIncidentOnLocStores(u, v, loc) :- edge(u, v), !storenonManipAtLoc(u, loc), !storenonManipAtLoc(v, loc). 282 | //entryReachesMinusLocStores(stmt, _) :- entry(stmt). 283 | //entryReachesMinusLocStores(stmt, loc) :- edgeNotIncidentOnLocStores(u, stmt, loc), entryReachesMinusLocStores(u, loc). 284 | 285 | 286 | // locOverWrittenBefore(loc, stmt) :- reaches(e, s), entry(e), storeAtLoc(s, loc), 287 | 288 | 289 | // A manipulable value which is non-constant, i.e. input-dependent. 290 | .decl nonConstManipulable(var:Variable) 291 | 292 | nonConstManipulable(var) :- 293 | manipulable(var), 294 | !value(var, _). 295 | 296 | // A statement is inaccessible if some condition controls it which is not manipulable by the caller. 297 | // Or if it can't be reached from the program entry point. 298 | .decl inaccessible(stmt:Statement) 299 | 300 | inaccessible(stmt) :- 301 | controlsWith(_, stmt, cond), 302 | !manipulable(cond). 303 | 304 | inaccessible(stmt) :- 305 | op(stmt, _), 306 | !reaches("0x0", stmt). 307 | 308 | .decl protectedByLoc(protectedStmt:Statement, sLoc:Value) 309 | protectedByLoc(protectedStmt, sLoc) :- 310 | controlsWith(controlStmt, protectedStmt, condVar), 311 | dom(setMutexStmt, controlStmt), 312 | dom(protectedStmt, setMutexStmt), 313 | depends(condVar, mutexResVar), 314 | stateReadResult(mutexResVar, readMutexStmt), 315 | op(readMutexStmt, "SLOAD"), 316 | use(readMutexLoc, readMutexStmt, _), 317 | op_SSTORE(setMutexStmt, setMutexLoc, _), 318 | value(readMutexLoc, sLoc), 319 | value(setMutexLoc, sLoc). 320 | 321 | // A statement is considered gassy if it uses some variable that depends on a GAS operation 322 | .decl gassy(stmt:Statement, var:Variable) 323 | 324 | gassy(stmt, var) :- 325 | use(var, stmt, _), 326 | depends(var, gasVar), 327 | gasResult(gasVar, _). 328 | 329 | .decl fromCallValue(var:Variable) 330 | 331 | fromCallValue(var) :- 332 | depends(var, callValVar), 333 | def(callValVar, callValStmt), 334 | op(callValStmt, "CALLVALUE"). 335 | 336 | .decl usefulValue(var:Variable) 337 | 338 | usefulValue(var) :- 339 | !value(var, "0x0"), 340 | !fromCallValue(var), 341 | def(var, _). 342 | 343 | .decl checkedCallThrows(callStmt:Statement) 344 | 345 | checkedCallThrows(callStmt) :- 346 | callResult(resVar, callStmt), 347 | depends(cond, resVar), 348 | controlsWith(_, throwStmt, cond), 349 | isThrow(throwStmt). 350 | 351 | checkedCallThrows(callStmt) :- 352 | callResult(resVar, callStmt), 353 | depends(cond, resVar), 354 | use(cond, throwStmt, _), 355 | isThrow(throwStmt). 356 | -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/MadMax/src/lattice.py: -------------------------------------------------------------------------------- 1 | # BSD 3-Clause License 2 | # 3 | # Copyright (c) 2016, 2017, The University of Sydney. All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # * Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # * Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # * Neither the name of the copyright holder nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | """lattice.py: define lattices for use in meet-over-paths calculations. 31 | 32 | We will take bottom elements to mean maximal value constraint 33 | (uninitialised, or empty set), while top elements will be taken to mean a 34 | maximally-unconstrained element (all possible values, universal set).""" 35 | 36 | import abc 37 | import functools 38 | import itertools 39 | import types 40 | import typing as t 41 | from copy import copy 42 | 43 | 44 | class LatticeElement(abc.ABC): 45 | def __init__(self, value): 46 | """ 47 | Construct a lattice element with the given value. 48 | 49 | Args: 50 | value: the value of this LatticeElement 51 | """ 52 | self.value = value 53 | 54 | @abc.abstractclassmethod 55 | def meet( 56 | cls, 57 | a: 'LatticeElement', 58 | b: 'LatticeElement') -> 'LatticeElement': 59 | """Return the infimum of the given elements.""" 60 | 61 | @classmethod 62 | def meet_all(cls, elements: t.Iterable['LatticeElement'], 63 | initial: 'LatticeElement' = None) -> 'LatticeElement': 64 | """ 65 | Return the infimum of the given iterable of elements. 66 | 67 | Args: 68 | elements: a sequence of elements whose common meet to obtain 69 | initial: an additional element to meet with the rest. 70 | An empty sequence will result in this value, if provided. 71 | """ 72 | if initial is not None: 73 | return functools.reduce( 74 | lambda a, b: cls.meet(a, b), 75 | elements, 76 | initial 77 | ) 78 | return functools.reduce( 79 | lambda a, b: cls.meet(a, b), 80 | elements, 81 | ) 82 | 83 | @abc.abstractclassmethod 84 | def join( 85 | cls, 86 | a: 'LatticeElement', 87 | b: 'LatticeElement') -> 'LatticeElement': 88 | """Return the infimum of the given elements.""" 89 | 90 | @classmethod 91 | def join_all(cls, elements: t.Iterable['LatticeElement'], 92 | initial: 'LatticeElement' = None) -> 'LatticeElement': 93 | """ 94 | Return the supremum of the given iterable of elements. 95 | 96 | Args: 97 | elements: a sequence of elements whose common join to obtain 98 | initial: an additional element to join with the rest. 99 | An empty sequence will result in this value, if provided. 100 | """ 101 | if initial is not None: 102 | return functools.reduce( 103 | lambda a, b: cls.join(a, b), 104 | elements, 105 | initial 106 | ) 107 | return functools.reduce( 108 | lambda a, b: cls.join(a, b), 109 | elements, 110 | ) 111 | 112 | def __eq__(self, other): 113 | return self.value == other.value 114 | 115 | def __str__(self): 116 | return str(self.value) 117 | 118 | def __repr__(self): 119 | return "<{0} object {1}, {2}>".format( 120 | self.__class__.__name__, 121 | hex(id(self)), 122 | str(self) 123 | ) 124 | 125 | 126 | class BoundedLatticeElement(LatticeElement): 127 | """An element from a lattice with defined Top and Bottom elements.""" 128 | TOP_SYMBOL = "⊤" 129 | BOTTOM_SYMBOL = "⊥" 130 | 131 | def __init__(self, value): 132 | """ 133 | Construct a bounded lattice element with the given value. 134 | 135 | Args: 136 | value: the value this lattice element should take. 137 | """ 138 | super().__init__(value) 139 | 140 | @classmethod 141 | def meet_all( 142 | cls, 143 | elements: t.Iterable['BoundedLatticeElement']) -> 'BoundedLatticeElement': 144 | """ 145 | Take the meet of all elements in the given sequence. 146 | An empty sequence produces Top. 147 | """ 148 | return super().meet_all(elements, cls.top()) 149 | 150 | @classmethod 151 | def join_all( 152 | cls, 153 | elements: t.Iterable['BoundedLatticeElement']) -> 'BoundedLatticeElement': 154 | """ 155 | Take the join of all elements in the given sequence. 156 | An empty sequence produces Bottom. 157 | """ 158 | return super().join_all(elements, cls.bottom()) 159 | 160 | @property 161 | def is_top(self): 162 | """True if this element is Top.""" 163 | return self.value == self._top_val() 164 | 165 | @property 166 | def is_bottom(self): 167 | """True if this element is Bottom.""" 168 | return self.value == self._bottom_val() 169 | 170 | def __str__(self): 171 | if self.is_top: 172 | return self.TOP_SYMBOL 173 | elif self.is_bottom: 174 | return self.BOTTOM_SYMBOL 175 | else: 176 | return str(self.value) 177 | 178 | @abc.abstractclassmethod 179 | def _top_val(cls): 180 | """Return the Top value of this lattice.""" 181 | 182 | @abc.abstractclassmethod 183 | def _bottom_val(cls): 184 | """Return the Bottom value of this lattice.""" 185 | 186 | @classmethod 187 | def top(cls) -> 'BoundedLatticeElement': 188 | """Return the Top lattice element.""" 189 | return cls(cls._top_val()) 190 | 191 | @classmethod 192 | def bottom(cls) -> 'BoundedLatticeElement': 193 | """Return the Bottom lattice element.""" 194 | return cls(cls._bottom_val()) 195 | 196 | def widen_to_top(self): 197 | """Set this element's value to Top without changing anything else.""" 198 | self.value = self._top_val() 199 | 200 | 201 | class IntLatticeElement(BoundedLatticeElement): 202 | """ 203 | An element of the lattice defined by augmenting 204 | the (unordered) set of integers with top and bottom elements. 205 | 206 | Integers are incomparable with one another, while Top and Bottom 207 | compare superior and inferior with every other element, respectively. 208 | """ 209 | 210 | def __init__(self, value: int): 211 | """ 212 | Args: 213 | value: the integer this element contains, if it is not Top or Bottom. 214 | """ 215 | super().__init__(value) 216 | 217 | def is_int(self) -> bool: 218 | """True iff this lattice element is neither Top nor Bottom.""" 219 | return not (self.is_top or self.is_bottom) 220 | 221 | def __add__(self, other): 222 | if self.is_int() and other.is_int(): 223 | return IntLatticeElement(self.value + other.value) 224 | return self.bottom() 225 | 226 | @classmethod 227 | def _top_val(cls): 228 | return cls.TOP_SYMBOL 229 | 230 | @classmethod 231 | def _bottom_val(cls): 232 | return cls.BOTTOM_SYMBOL 233 | 234 | @classmethod 235 | def meet( 236 | cls, 237 | a: 'IntLatticeElement', 238 | b: 'IntLatticeElement') -> 'IntLatticeElement': 239 | """Return the infimum of the given elements.""" 240 | 241 | if a.is_bottom or b.is_bottom: 242 | return cls.bottom() 243 | 244 | if a.is_top: 245 | return copy(b) 246 | if b.is_top: 247 | return copy(a) 248 | if a.value == b.value: 249 | return copy(a) 250 | 251 | return cls.bottom() 252 | 253 | @classmethod 254 | def join( 255 | cls, 256 | a: 'IntLatticeElement', 257 | b: 'IntLatticeElement') -> 'IntLatticeElement': 258 | """Return the supremum of the given elements.""" 259 | 260 | if a.is_top or b.is_top: 261 | return cls.top() 262 | 263 | if a.is_bottom: 264 | return copy(b) 265 | if b.is_bottom: 266 | return copy(a) 267 | if a.value == b.value: 268 | return copy(a) 269 | 270 | return cls.top() 271 | 272 | 273 | class SubsetLatticeElement(BoundedLatticeElement): 274 | """ 275 | A subset lattice element. The top element is the complete set of all 276 | elements, the bottom is the empty set, and other elements are subsets of top. 277 | """ 278 | 279 | def __init__(self, value: t.Iterable): 280 | """ 281 | Args: 282 | value: an iterable of elements which will compose the value of this 283 | lattice element. It will be converted to a set, so duplicate 284 | elements and ordering are ignored. 285 | """ 286 | super().__init__(set(value)) 287 | 288 | def __len__(self): 289 | if self.is_top: 290 | # TODO: determine if this is the right thing here. TOP has 291 | # unbounded size. 292 | return 0 293 | return len(self.value) 294 | 295 | def __iter__(self): 296 | if self.is_top: 297 | raise TypeError("Top lattice element cannot be iterated.") 298 | return iter(self.value) 299 | 300 | def map(self, f: types.FunctionType) -> 'SubsetLatticeElement': 301 | """ 302 | Return the result of applying a function to each of this element's values. 303 | 304 | Incidentally, this could be seen as special case of cartesian_map(). 305 | """ 306 | if self.is_top: 307 | return copy(self) 308 | return type(self)([f(val) for val in self.value]) 309 | 310 | @classmethod 311 | def cartesian_map(cls, f: types.FunctionType, 312 | elements: t.Iterable['SubsetLatticeElement']) \ 313 | -> 'SubsetLatticeElement': 314 | """ 315 | Apply the given function to each tuple of members in the product of the 316 | input elements, and return the resulting lattice element. 317 | 318 | The function's arity must match the number of input elements. 319 | For example, for a binary function, and input elements a, b, the result is 320 | the element defined by the set f(u, v) for each u in a, v in b. 321 | """ 322 | 323 | # Symbolic manipulations could be performed here as some operations might 324 | # constrain the results, even if some input set is unconstrained. 325 | if any([e.is_top for e in elements]): 326 | return cls.top() 327 | 328 | prod = itertools.product(*(list(e) for e in elements)) 329 | return cls([f(*args) for args in prod]) 330 | 331 | @classmethod 332 | def _top_val(cls): 333 | return set(cls.TOP_SYMBOL) 334 | 335 | @classmethod 336 | def _bottom_val(cls): 337 | return set() 338 | 339 | @classmethod 340 | def meet(cls, a: 'SubsetLatticeElement', 341 | b: 'SubsetLatticeElement') -> 'SubsetLatticeElement': 342 | """Return the set intersection of the given elements.""" 343 | if a.is_top: 344 | return copy(b) 345 | if b.is_top: 346 | return copy(a) 347 | 348 | return cls(a.value & b.value) 349 | 350 | @classmethod 351 | def join(cls, a: 'SubsetLatticeElement', 352 | b: 'SubsetLatticeElement') -> 'SubsetLatticeElement': 353 | """Return the set union of the given elements.""" 354 | if a.is_top or b.is_top: 355 | return cls.top() 356 | 357 | return cls(a.value | b.value) 358 | 359 | @property 360 | def is_const(self) -> bool: 361 | """True iff this variable has exactly one possible value.""" 362 | return self.is_finite and len(self) == 1 363 | 364 | @property 365 | def is_finite(self) -> bool: 366 | """ 367 | True iff this variable has a finite and nonzero number of possible values. 368 | """ 369 | return not (self.is_top or self.is_bottom) 370 | -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/MadMax/src/cfg.py: -------------------------------------------------------------------------------- 1 | # BSD 3-Clause License 2 | # 3 | # Copyright (c) 2016, 2017, The University of Sydney. All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # * Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # * Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # * Neither the name of the copyright holder nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | """cfg.py: Base classes for representing Control Flow Graphs (CFGs)""" 31 | 32 | import abc 33 | import typing as t 34 | 35 | import src.patterns as patterns 36 | 37 | 38 | class ControlFlowGraph(patterns.Visitable): 39 | """Abstract base class for a Control Flow Graph (CFG)""" 40 | 41 | __STR_SEP = "\n\n-----\n\n" 42 | 43 | @abc.abstractmethod 44 | def __init__(self): 45 | """Create a new empty ControlFlowGraph""" 46 | 47 | self.blocks = [] 48 | """List of BasicBlock objects""" 49 | 50 | self.root = None 51 | """The root BasicBlock object, or None for the empty graph""" 52 | 53 | def __len__(self): 54 | return len(self.blocks) 55 | 56 | def __str__(self): 57 | return self.__STR_SEP.join(str(b) for b in self.blocks) 58 | 59 | def remove_block(self, block: 'BasicBlock') -> None: 60 | """ 61 | Remove the given block from the graph, disconnecting all incident edges. 62 | """ 63 | if block == self.root: 64 | self.root = None 65 | 66 | for p in list(block.preds): 67 | self.remove_edge(p, block) 68 | for s in list(block.succs): 69 | self.remove_edge(block, s) 70 | 71 | self.blocks.remove(block) 72 | 73 | def add_block(self, block: 'BasicBlock') -> None: 74 | """ 75 | Add the given block to the graph, assuming it does not already exist. 76 | """ 77 | if block not in self.blocks: 78 | self.blocks.append(block) 79 | 80 | def has_edge(self, head: 'BasicBlock', tail: 'BasicBlock') -> bool: 81 | """ 82 | True iff the edge between head and tail exists in the graph. 83 | """ 84 | return tail in head.succs 85 | 86 | def remove_edge(self, head: 'BasicBlock', tail: 'BasicBlock') -> None: 87 | """Remove the CFG edge that goes from head to tail.""" 88 | if tail in head.succs: 89 | head.succs.remove(tail) 90 | if head in tail.preds: 91 | tail.preds.remove(head) 92 | 93 | def add_edge(self, head: 'BasicBlock', tail: 'BasicBlock'): 94 | """Add a CFG edge that goes from head to tail.""" 95 | if tail not in head.succs: 96 | head.succs.append(tail) 97 | if head not in tail.preds: 98 | tail.preds.append(head) 99 | 100 | def get_blocks_by_pc(self, pc: int) -> t.List['BasicBlock']: 101 | """Return the blocks whose spans include the given program counter value.""" 102 | blocks = [] 103 | for block in self.blocks: 104 | if block.entry <= pc <= block.exit: 105 | blocks.append(block) 106 | return blocks 107 | 108 | def get_block_by_ident(self, ident: str) -> 'BasicBlock': 109 | """Return the block with the specified identifier, if it exists.""" 110 | for block in self.blocks: 111 | if block.ident() == ident: 112 | return block 113 | return None 114 | 115 | def recalc_preds(self) -> None: 116 | """ 117 | Given a cfg where block successor lists are populated, 118 | also repopulate the predecessor lists, after emptying them. 119 | """ 120 | for block in self.blocks: 121 | block.preds = [] 122 | for block in self.blocks: 123 | for successor in block.succs: 124 | successor.preds.append(block) 125 | 126 | def reaches( 127 | self, 128 | block: 'BasicBlock', 129 | dests: t.Iterable['BasicBlock']) -> bool: 130 | """ 131 | Determines if a block can reach any of the given destination blocks 132 | 133 | Args: 134 | block: Any block that is part of the tac_cfg the class was initialised with 135 | dests: A list of dests to check reachability with 136 | """ 137 | if block in dests: 138 | return True 139 | queue = [block] 140 | traversed = [] 141 | while queue: 142 | curr_block = queue.pop() 143 | traversed.append(curr_block) 144 | for b in dests: 145 | if b in curr_block.succs: 146 | return True 147 | for b in curr_block.succs: 148 | if b not in traversed: 149 | queue.append(b) 150 | return False 151 | 152 | def transitive_closure(self, origin_addresses: t.Iterable[int]) \ 153 | -> t.Iterable['BasicBlock']: 154 | """ 155 | Return a list of blocks reachable from the input addresses. 156 | 157 | Args: 158 | origin_addresses: the input addresses blocks from which are reachable 159 | to be returned. 160 | """ 161 | 162 | # Populate the work queue with the origin blocks for the transitive 163 | # closure. 164 | queue = [] 165 | for address in origin_addresses: 166 | for block in self.get_blocks_by_pc(address): 167 | if block not in queue: 168 | queue.append(block) 169 | reached = [] 170 | 171 | # Follow all successor edges until we can find no more new blocks. 172 | while queue: 173 | block = queue.pop() 174 | reached.append(block) 175 | for succ in block.succs: 176 | if succ not in queue and succ not in reached: 177 | queue.append(succ) 178 | 179 | return reached 180 | 181 | def remove_unreachable_blocks(self, origin_addresses: t.Iterable[int] = [ 182 | 0]) -> t.Iterable['BasicBlock']: 183 | """ 184 | Remove all blocks not reachable from the program entry point. 185 | 186 | NB: if not all jumps have been resolved, unreached blocks may actually 187 | be reachable. 188 | 189 | Args: 190 | origin_addresses: default value: [0], entry addresses, blocks from which 191 | are unreachable to be deleted. 192 | 193 | Returns: 194 | An iterable of the blocks which were removed. 195 | """ 196 | 197 | reached = self.transitive_closure(origin_addresses) 198 | removed = [] 199 | for block in list(self.blocks): 200 | if block not in reached: 201 | removed.append(block) 202 | self.remove_block(block) 203 | return removed 204 | 205 | def edge_list(self) -> t.Iterable[t.Tuple['BasicBlock', 'BasicBlock']]: 206 | """ 207 | Returns: 208 | a list of the CFG's edges, with each edge in the form 209 | `(pred, succ)` where pred and succ are object references. 210 | """ 211 | return [(p, s) for p in self.blocks for s in p.succs] 212 | 213 | def sorted_traversal(self, key=lambda b: b.entry, 214 | reverse=False) -> t.Generator['BasicBlock', None, None]: 215 | """ 216 | Generator for a sorted shallow copy of BasicBlocks contained in this graph. 217 | 218 | Args: 219 | key: A function of one argument that is used to extract a comparison key 220 | from each block. By default, the comparison key is 221 | :obj:`BasicBlock.entry`. 222 | reverse: If set to `True`, then the blocks are sorted as if each 223 | comparison were reversed. Default is `False`. 224 | 225 | Returns: 226 | A generator of :obj:`BasicBlock` objects, yielded in order according to 227 | `key` and `reverse`. 228 | """ 229 | # Create a new list of sorted blocks and yield from it 230 | yield from sorted(self.blocks, key=key, reverse=reverse) 231 | 232 | def accept(self, visitor: patterns.Visitor, 233 | generator: t.Generator['BasicBlock', None, None] = None): 234 | """ 235 | Visitor design pattern: accepts a Visitor instance and visits every node 236 | in the CFG in an arbitrary order. 237 | 238 | Args: 239 | visitor: instance of a Visitor 240 | generator: generator from which :obj:`BasicBlock` objects will be 241 | retrieved when recursing. By default the blocks are recursed in 242 | an arbitrary order. 243 | """ 244 | super().accept(visitor) 245 | 246 | generator = generator or self.blocks 247 | 248 | if len(self.blocks) > 0 and visitor.can_visit(type(self.blocks[0])): 249 | for b in generator: 250 | b.accept(visitor) 251 | 252 | @property 253 | def has_unresolved_jump(self) -> bool: 254 | """True iff any block in this cfg contains an unresolved jump.""" 255 | return any(b.has_unresolved_jump for b in self.blocks) 256 | 257 | 258 | class BasicBlock(patterns.Visitable): 259 | """ 260 | Abstract base class for a single basic block (node) in a CFG. Each block has 261 | references to its predecessor and successor nodes in the graph structure. 262 | 263 | A BasicBlock must contain exactly one entry point at the start and 264 | exactly one exit point at the end, with no branching in between. 265 | That is, program flow must be linear/sequential within a basic block. 266 | 267 | Args: 268 | entry (int, default None): entry index. 269 | exit (int, default None): exit index. 270 | 271 | Raises: 272 | ValueError: if entry or exit is a negative int. 273 | """ 274 | 275 | _STR_SEP = "---" 276 | 277 | @abc.abstractmethod 278 | def __init__(self, entry: int = None, exit: int = None): 279 | if entry is not None and entry < 0: 280 | raise ValueError("entry must be a positive integer or zero") 281 | 282 | if exit is not None and exit < 0: 283 | raise ValueError("exit must be a positive integer or zero") 284 | 285 | self.entry = entry 286 | """Index of the first operation contained in this node.""" 287 | 288 | self.exit = exit 289 | """Index of the last operation contained in this node.""" 290 | 291 | self.preds = [] 292 | """List of nodes which pass control to this node (predecessors).""" 293 | 294 | self.succs = [] 295 | """List of nodes which receive control from this node (successors).""" 296 | 297 | self.has_unresolved_jump = False 298 | """True if the node contains a jump whose destination is a variable.""" 299 | 300 | self.ident_suffix = "" 301 | """ 302 | Extra information to be appended to this block's identifier. 303 | Used, for example, to differentiate duplicated blocks. 304 | """ 305 | 306 | def __len__(self): 307 | """Returns the number of lines of code contained within this block.""" 308 | if self.exit is None or self.entry is None: 309 | return 0 310 | return self.exit - self.entry 311 | 312 | def __str__(self): 313 | entry, exit = map(lambda n: hex(n) if n is not None else 'Unknown', 314 | (self.entry, self.exit)) 315 | b_id = self.ident() if self.entry is not None else "Unidentified" 316 | head = "Block {}\n[{}:{}]".format(b_id, entry, exit) 317 | pred = "Predecessors: [{}]".format( 318 | ", ".join(b.ident() for b in sorted(self.preds))) 319 | succ = "Successors: [{}]".format( 320 | ", ".join(b.ident() for b in sorted(self.succs))) 321 | unresolved = "\nHas unresolved jump." if self.has_unresolved_jump else "" 322 | return "\n".join([head, self._STR_SEP, pred, succ]) + unresolved 323 | 324 | def __lt__(self, other): 325 | """ 326 | Compare BasicBlocks based on their entry program counter values. 327 | """ 328 | if self.entry is None or other.entry is None: 329 | return False 330 | return (self.entry < other.entry) or (self.entry == 331 | other.entry and self.ident_suffix < other.ident_suffix) 332 | 333 | def ident(self) -> str: 334 | """ 335 | Returns this block's unique identifier, which is its entry value. 336 | 337 | Raises: 338 | ValueError if the block's entry is None. 339 | """ 340 | if self.entry is None: 341 | raise ValueError( 342 | "Can't compute ident() for block with unknown entry") 343 | return hex(self.entry) + self.ident_suffix 344 | -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/MadMax/src/opcodes.py: -------------------------------------------------------------------------------- 1 | # BSD 3-Clause License 2 | # 3 | # Copyright (c) 2016, 2017, The University of Sydney. All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # * Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # * Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # * Neither the name of the copyright holder nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | """opcodes.py: Definitions of all EVM opcodes, and related utility functions.""" 31 | 32 | 33 | class OpCode: 34 | """An EVM opcode.""" 35 | 36 | def __init__(self, name: str, code: int, pop: int, push: int): 37 | """ 38 | Args: 39 | name (str): Human-readable opcode. 40 | code (int): The instruction byte itself. 41 | pop (int): The number of stack elements this op pops. 42 | push (int): The number of stack elements this op pushes. 43 | """ 44 | self.name = name 45 | self.code = code 46 | self.pop = pop 47 | self.push = push 48 | 49 | def stack_delta(self) -> int: 50 | """Return the net effect on the stack size of running this operation.""" 51 | return self.push - self.pop 52 | 53 | def __str__(self) -> str: 54 | return self.name 55 | 56 | def __repr__(self) -> str: 57 | return "<{0} object {1}, {2}>".format( 58 | self.__class__.__name__, 59 | hex(id(self)), 60 | self.__str__() 61 | ) 62 | 63 | def __eq__(self, other) -> bool: 64 | return self.code == other.code 65 | 66 | def __hash__(self) -> int: 67 | return self.code.__hash__() 68 | 69 | def is_push(self) -> bool: 70 | """Predicate: opcode is a push operation.""" 71 | return PUSH1.code <= self.code <= PUSH32.code 72 | 73 | def is_swap(self) -> bool: 74 | """Predicate: opcode is a swap operation.""" 75 | return SWAP1.code <= self.code <= SWAP16.code 76 | 77 | def is_dup(self) -> bool: 78 | """Predicate: opcode is a dup operation.""" 79 | return DUP1.code <= self.code <= DUP16.code 80 | 81 | def is_log(self) -> bool: 82 | """Predicate: opcode is a log operation.""" 83 | return LOG0.code <= self.code <= LOG4.code 84 | 85 | def is_missing(self) -> bool: 86 | return self.code not in BYTECODES 87 | 88 | def is_invalid(self) -> bool: 89 | return (self.code == INVALID.code) or self.is_missing() 90 | 91 | def is_arithmetic(self) -> bool: 92 | """Predicate: opcode's result can be calculated from its inputs alone.""" 93 | return (ADD.code <= self.code <= SIGNEXTEND.code) or \ 94 | (LT.code <= self.code <= BYTE.code) 95 | 96 | def is_memory(self) -> bool: 97 | """Predicate: opcode operates on memory""" 98 | return MLOAD.code <= self.code <= MSTORE8.code 99 | 100 | def is_storage(self) -> bool: 101 | """Predicate: opcode operates on storage ('the tape')""" 102 | return SLOAD.code <= self.code <= SSTORE.code 103 | 104 | def is_call(self) -> bool: 105 | """Predicate: opcode calls an external contract""" 106 | return self in (CALL, CALLCODE, DELEGATECALL, STATICCALL,) 107 | 108 | def alters_flow(self) -> bool: 109 | """Predicate: opcode alters EVM control flow.""" 110 | return (self.code in (JUMP.code, JUMPI.code,)) or self.possibly_halts() 111 | 112 | def is_exception(self) -> bool: 113 | """Predicate: opcode causes the EVM to throw an exception.""" 114 | return (self.code in (THROW.code, THROWI.code, REVERT.code)) \ 115 | or self.is_invalid() 116 | 117 | def halts(self) -> bool: 118 | """Predicate: opcode causes the EVM to halt.""" 119 | halt_codes = ( 120 | STOP.code, 121 | RETURN.code, 122 | SELFDESTRUCT.code, 123 | THROW.code, 124 | REVERT.code, 125 | ) 126 | return (self.code in halt_codes) or self.is_invalid() 127 | 128 | def possibly_halts(self) -> bool: 129 | """Predicate: opcode MAY cause the EVM to halt. (halts + THROWI)""" 130 | return self.halts() or self.code == THROWI.code 131 | 132 | def push_len(self) -> int: 133 | """Return the number of bytes the given PUSH instruction pushes.""" 134 | return self.code - PUSH1.code + 1 if self.is_push() else 0 135 | 136 | def log_len(self) -> int: 137 | """Return the number of topics the given LOG instruction includes.""" 138 | return self.code - LOG0.code if self.is_log() else 0 139 | 140 | def pop_words(self) -> int: 141 | return self.pop 142 | 143 | def push_words(self) -> int: 144 | return self.push 145 | 146 | def ord(self) -> int: 147 | return self.code 148 | 149 | # Construct all EVM opcodes 150 | 151 | 152 | # Arithmetic Ops and STOP 153 | STOP = OpCode("STOP", 0x00, 0, 0) 154 | ADD = OpCode("ADD", 0x01, 2, 1) 155 | MUL = OpCode("MUL", 0x02, 2, 1) 156 | SUB = OpCode("SUB", 0x03, 2, 1) 157 | DIV = OpCode("DIV", 0x04, 2, 1) 158 | SDIV = OpCode("SDIV", 0x05, 2, 1) 159 | MOD = OpCode("MOD", 0x06, 2, 1) 160 | SMOD = OpCode("SMOD", 0x07, 2, 1) 161 | ADDMOD = OpCode("ADDMOD", 0x08, 3, 1) 162 | MULMOD = OpCode("MULMOD", 0x09, 3, 1) 163 | EXP = OpCode("EXP", 0x0a, 2, 1) 164 | SIGNEXTEND = OpCode("SIGNEXTEND", 0x0b, 2, 1) 165 | 166 | # Comparison and Bitwise Logic 167 | LT = OpCode("LT", 0x10, 2, 1) 168 | GT = OpCode("GT", 0x11, 2, 1) 169 | SLT = OpCode("SLT", 0x12, 2, 1) 170 | SGT = OpCode("SGT", 0x13, 2, 1) 171 | EQ = OpCode("EQ", 0x14, 2, 1) 172 | ISZERO = OpCode("ISZERO", 0x15, 1, 1) 173 | AND = OpCode("AND", 0x16, 2, 1) 174 | OR = OpCode("OR", 0x17, 2, 1) 175 | XOR = OpCode("XOR", 0x18, 2, 1) 176 | NOT = OpCode("NOT", 0x19, 1, 1) 177 | BYTE = OpCode("BYTE", 0x1a, 2, 1) 178 | 179 | SHA3 = OpCode("SHA3", 0x20, 2, 1) 180 | 181 | # Environmental Information 182 | ADDRESS = OpCode("ADDRESS", 0x30, 0, 1) 183 | BALANCE = OpCode("BALANCE", 0x31, 1, 1) 184 | ORIGIN = OpCode("ORIGIN", 0x32, 0, 1) 185 | CALLER = OpCode("CALLER", 0x33, 0, 1) 186 | CALLVALUE = OpCode("CALLVALUE", 0x34, 0, 1) 187 | CALLDATALOAD = OpCode("CALLDATALOAD", 0x35, 1, 1) 188 | CALLDATASIZE = OpCode("CALLDATASIZE", 0x36, 0, 1) 189 | CALLDATACOPY = OpCode("CALLDATACOPY", 0x37, 3, 0) 190 | CODESIZE = OpCode("CODESIZE", 0x38, 0, 1) 191 | CODECOPY = OpCode("CODECOPY", 0x39, 3, 0) 192 | GASPRICE = OpCode("GASPRICE", 0x3a, 0, 1) 193 | EXTCODESIZE = OpCode("EXTCODESIZE", 0x3b, 1, 1) 194 | EXTCODECOPY = OpCode("EXTCODECOPY", 0x3c, 4, 0) 195 | 196 | # Block Information 197 | BLOCKHASH = OpCode("BLOCKHASH", 0x40, 1, 1) 198 | COINBASE = OpCode("COINBASE", 0x41, 0, 1) 199 | TIMESTAMP = OpCode("TIMESTAMP", 0x42, 0, 1) 200 | NUMBER = OpCode("NUMBER", 0x43, 0, 1) 201 | DIFFICULTY = OpCode("DIFFICULTY", 0x44, 0, 1) 202 | GASLIMIT = OpCode("GASLIMIT", 0x45, 0, 1) 203 | 204 | # Stack, Memory, Storage, Flow 205 | POP = OpCode("POP", 0x50, 1, 0) 206 | MLOAD = OpCode("MLOAD", 0x51, 1, 1) 207 | MSTORE = OpCode("MSTORE", 0x52, 2, 0) 208 | MSTORE8 = OpCode("MSTORE8", 0x53, 2, 0) 209 | SLOAD = OpCode("SLOAD", 0x54, 1, 1) 210 | SSTORE = OpCode("SSTORE", 0x55, 2, 0) 211 | JUMP = OpCode("JUMP", 0x56, 1, 0) 212 | JUMPI = OpCode("JUMPI", 0x57, 2, 0) 213 | PC = OpCode("PC", 0x58, 0, 1) 214 | MSIZE = OpCode("MSIZE", 0x59, 0, 1) 215 | GAS = OpCode("GAS", 0x5a, 0, 1) 216 | JUMPDEST = OpCode("JUMPDEST", 0x5b, 0, 0) 217 | 218 | PUSH1 = OpCode("PUSH1", 0x60, 0, 1) 219 | PUSH2 = OpCode("PUSH2", 0x61, 0, 1) 220 | PUSH3 = OpCode("PUSH3", 0x62, 0, 1) 221 | PUSH4 = OpCode("PUSH4", 0x63, 0, 1) 222 | PUSH5 = OpCode("PUSH5", 0x64, 0, 1) 223 | PUSH6 = OpCode("PUSH6", 0x65, 0, 1) 224 | PUSH7 = OpCode("PUSH7", 0x66, 0, 1) 225 | PUSH8 = OpCode("PUSH8", 0x67, 0, 1) 226 | PUSH9 = OpCode("PUSH9", 0x68, 0, 1) 227 | PUSH10 = OpCode("PUSH10", 0x69, 0, 1) 228 | PUSH11 = OpCode("PUSH11", 0x6a, 0, 1) 229 | PUSH12 = OpCode("PUSH12", 0x6b, 0, 1) 230 | PUSH13 = OpCode("PUSH13", 0x6c, 0, 1) 231 | PUSH14 = OpCode("PUSH14", 0x6d, 0, 1) 232 | PUSH15 = OpCode("PUSH15", 0x6e, 0, 1) 233 | PUSH16 = OpCode("PUSH16", 0x6f, 0, 1) 234 | PUSH17 = OpCode("PUSH17", 0x70, 0, 1) 235 | PUSH18 = OpCode("PUSH18", 0x71, 0, 1) 236 | PUSH19 = OpCode("PUSH19", 0x72, 0, 1) 237 | PUSH20 = OpCode("PUSH20", 0x73, 0, 1) 238 | PUSH21 = OpCode("PUSH21", 0x74, 0, 1) 239 | PUSH22 = OpCode("PUSH22", 0x75, 0, 1) 240 | PUSH23 = OpCode("PUSH23", 0x76, 0, 1) 241 | PUSH24 = OpCode("PUSH24", 0x77, 0, 1) 242 | PUSH25 = OpCode("PUSH25", 0x78, 0, 1) 243 | PUSH26 = OpCode("PUSH26", 0x79, 0, 1) 244 | PUSH27 = OpCode("PUSH27", 0x7a, 0, 1) 245 | PUSH28 = OpCode("PUSH28", 0x7b, 0, 1) 246 | PUSH29 = OpCode("PUSH29", 0x7c, 0, 1) 247 | PUSH30 = OpCode("PUSH30", 0x7d, 0, 1) 248 | PUSH31 = OpCode("PUSH31", 0x7e, 0, 1) 249 | PUSH32 = OpCode("PUSH32", 0x7f, 0, 1) 250 | 251 | DUP1 = OpCode("DUP1", 0x80, 1, 2) 252 | DUP2 = OpCode("DUP2", 0x81, 2, 3) 253 | DUP3 = OpCode("DUP3", 0x82, 3, 4) 254 | DUP4 = OpCode("DUP4", 0x83, 4, 5) 255 | DUP5 = OpCode("DUP5", 0x84, 5, 6) 256 | DUP6 = OpCode("DUP6", 0x85, 6, 7) 257 | DUP7 = OpCode("DUP7", 0x86, 7, 8) 258 | DUP8 = OpCode("DUP8", 0x87, 8, 9) 259 | DUP9 = OpCode("DUP9", 0x88, 9, 10) 260 | DUP10 = OpCode("DUP10", 0x89, 10, 11) 261 | DUP11 = OpCode("DUP11", 0x8a, 11, 12) 262 | DUP12 = OpCode("DUP12", 0x8b, 12, 13) 263 | DUP13 = OpCode("DUP13", 0x8c, 13, 14) 264 | DUP14 = OpCode("DUP14", 0x8d, 14, 15) 265 | DUP15 = OpCode("DUP15", 0x8e, 15, 16) 266 | DUP16 = OpCode("DUP16", 0x8f, 16, 17) 267 | 268 | SWAP1 = OpCode("SWAP1", 0x90, 2, 2) 269 | SWAP2 = OpCode("SWAP2", 0x91, 3, 3) 270 | SWAP3 = OpCode("SWAP3", 0x92, 4, 4) 271 | SWAP4 = OpCode("SWAP4", 0x93, 5, 5) 272 | SWAP5 = OpCode("SWAP5", 0x94, 6, 6) 273 | SWAP6 = OpCode("SWAP6", 0x95, 7, 7) 274 | SWAP7 = OpCode("SWAP7", 0x96, 8, 8) 275 | SWAP8 = OpCode("SWAP8", 0x97, 9, 9) 276 | SWAP9 = OpCode("SWAP9", 0x98, 10, 10) 277 | SWAP10 = OpCode("SWAP10", 0x99, 11, 11) 278 | SWAP11 = OpCode("SWAP11", 0x9a, 12, 12) 279 | SWAP12 = OpCode("SWAP12", 0x9b, 13, 13) 280 | SWAP13 = OpCode("SWAP13", 0x9c, 14, 14) 281 | SWAP14 = OpCode("SWAP14", 0x9d, 15, 15) 282 | SWAP15 = OpCode("SWAP15", 0x9e, 16, 16) 283 | SWAP16 = OpCode("SWAP16", 0x9f, 17, 17) 284 | 285 | # Logging 286 | LOG0 = OpCode("LOG0", 0xa0, 2, 0) 287 | LOG1 = OpCode("LOG1", 0xa1, 3, 0) 288 | LOG2 = OpCode("LOG2", 0xa2, 4, 0) 289 | LOG3 = OpCode("LOG3", 0xa3, 5, 0) 290 | LOG4 = OpCode("LOG4", 0xa4, 6, 0) 291 | 292 | # System Operations 293 | CREATE = OpCode("CREATE", 0xf0, 3, 1) 294 | CALL = OpCode("CALL", 0xf1, 7, 1) 295 | CALLCODE = OpCode("CALLCODE", 0xf2, 7, 1) 296 | RETURN = OpCode("RETURN", 0xf3, 2, 0) 297 | DELEGATECALL = OpCode("DELEGATECALL", 0xf4, 6, 1) 298 | INVALID = OpCode("INVALID", 0xfe, 0, 0) 299 | SELFDESTRUCT = OpCode("SELFDESTRUCT", 0xff, 1, 0) 300 | 301 | # New Byzantinium OpCodes for block.number >= BYZANTIUM_FORK_BLKNUM 302 | REVERT = OpCode("REVERT", 0xfd, 2, 0) 303 | RETURNDATASIZE = OpCode("RETURNDATASIZE", 0x3d, 0, 1) 304 | RETURNDATACOPY = OpCode("RETURNDATACOPY", 0x3e, 3, 0) 305 | STATICCALL = OpCode("STATICCALL", 0xfa, 6, 1) 306 | 307 | # TAC Operations 308 | # These are not EVM opcodes, but they are used by the three-address code 309 | NOP = OpCode("NOP", -1, 0, 0) 310 | CONST = OpCode("CONST", -2, 0, 0) 311 | LOG = OpCode("LOG", -3, 0, 0) 312 | THROW = OpCode("THROW", -4, 0, 0) 313 | THROWI = OpCode("THROWI", -5, 0, 0) 314 | 315 | # Produce mappings from names and instruction codes to opcode objects 316 | OPCODES = { 317 | code.name: code 318 | for code in globals().values() 319 | if isinstance(code, OpCode) 320 | } 321 | """Dictionary mapping of opcode string names to EVM OpCode objects""" 322 | 323 | # Handle incorrect opcode name from go-ethereum disasm 324 | OPCODES["TXGASPRICE"] = OPCODES["GASPRICE"] 325 | 326 | BYTECODES = {code.code: code for code in OPCODES.values()} 327 | """Dictionary mapping of byte values to EVM OpCode objects""" 328 | 329 | 330 | def opcode_by_name(name: str) -> OpCode: 331 | """ 332 | Mapping: Retrieves the named OpCode object (case-insensitive). 333 | 334 | Throws: 335 | LookupError: if there is no opcode defined with the given name. 336 | """ 337 | name = name.upper() 338 | if name not in OPCODES: 339 | raise LookupError("No opcode named '{}'.".format(name)) 340 | return OPCODES[name] 341 | 342 | 343 | def opcode_by_value(val: int) -> OpCode: 344 | """ 345 | Mapping: Retrieves the OpCode object with the given value. 346 | 347 | Throws: 348 | LookupError: if there is no opcode defined with the given value. 349 | """ 350 | if val not in BYTECODES: 351 | raise LookupError("No opcode with value '0x{:02X}'.".format(val)) 352 | return BYTECODES[val] 353 | 354 | 355 | def missing_opcode(val: int) -> OpCode: 356 | """ 357 | Produces a new OpCode with the given value, as long as that is 358 | an unknown code. 359 | 360 | Throws: 361 | ValueError: if there is an opcode defined with the given value. 362 | """ 363 | if val in BYTECODES: 364 | raise ValueError("Opcode {} exists.") 365 | return OpCode("MISSING", val, 0, 0) 366 | -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/backdoor.dl: -------------------------------------------------------------------------------- 1 | #include "lib/vandal.dl" 2 | 3 | .type Block 4 | .type Function 5 | 6 | // INPUT 7 | 8 | // .decl edge(h:Statement, t:Statement) // There is a CFG edge from h to t 9 | // .input edge 10 | // .decl def(var:Variable, stmt:Statement) // var is defined by stmt 11 | // .input def 12 | // .decl use(var:Variable, stmt:Statement, i:number) // var is used by stmt as argument i 13 | // .input use 14 | // .decl op(stmt:Statement, op:Opcode) // stmt's opcode is op 15 | // .input op 16 | // .decl value(var:Variable, val:Value) // A variable's possible value set if known 17 | // .input value 18 | .decl isBlock(b: Block) 19 | .decl block(s: Statement, b: Block) 20 | .input block 21 | 22 | isBlock(b) :- block(_, b). 23 | 24 | .decl in_function(b:Block, t:Function) 25 | .input in_function 26 | 27 | .decl StatementInPublicFunction(s: Statement, f: Function) 28 | 29 | StatementInPublicFunction(s, f) :- 30 | block(s, b), 31 | in_function(b, f). 32 | 33 | // 34 | // *** CONTROL-FLOW ANALYSIS CONCEPTS *** 35 | // 36 | 37 | .decl Entry(s:Statement) 38 | .decl Exit(s:Statement) 39 | .decl IsStatement(s:Statement) 40 | 41 | .decl IsJump(s:Statement) 42 | IsJump(s) :- op(s, "JUMP"). 43 | IsJump(s) :- op(s, "JUMPI"). 44 | 45 | .decl JUMPDEST(s:Statement) 46 | JUMPDEST(s) :- op(s, "JUMPDEST"). 47 | 48 | .decl JUMP(stmt:Statement, dest:Variable) 49 | JUMP(stmt, dest) :- 50 | op(stmt, "JUMP"), 51 | use(dest, stmt, 1). 52 | 53 | 54 | IsStatement(s) :- op(s, _). 55 | 56 | Entry(s) :- IsStatement(s), !edge(_,s). 57 | Exit(s) :- IsStatement(s), !edge(s,_). 58 | 59 | .decl BasicBlockBegin(s:Statement) 60 | BasicBlockBegin(s) :- Entry(s). 61 | BasicBlockBegin(s) :- JUMPDEST(s). 62 | BasicBlockBegin(t) :- IsJump(s), edge(s,t). 63 | 64 | .decl NextInSameBasicBlock(s:Statement, next:Statement) 65 | NextInSameBasicBlock(s,next) :- 66 | edge(s,next), !BasicBlockBegin(next). 67 | NextInSameBasicBlock(s,next) :- 68 | BasicBlockHead(s,next), edge(s,next). 69 | 70 | 71 | .decl BasicBlockHead(s:Statement, head:Statement) 72 | BasicBlockHead(s,s) :- BasicBlockBegin(s). 73 | BasicBlockHead(s,h) :- BasicBlockHead(prev,h), NextInSameBasicBlock(prev,s). 74 | .plan 1:(2,1) 75 | 76 | 77 | .decl CanReach(s:Statement, t:Statement) 78 | 79 | CanReach(s,s) :- IsStatement(s). 80 | CanReach(s,t) :- edge(s,t). 81 | CanReach(s,t) :- CanReach(s,v), edge(v,t). 82 | 83 | 84 | /****************** Our Work ******************/ 85 | 86 | /* **** Declare a Dynamic Load **** */ 87 | /* 88 | // Declare a dynamic load 89 | .decl DynamicLoad(var: Variable) 90 | DynamicLoad(var) :- 91 | RuntimeKnowable(op), 92 | op(stmt,op), 93 | def(var,stmt). 94 | DynamicLoad(var) :- 95 | SLOAD(_,_,var). 96 | */ 97 | 98 | /* **** Declare an onlyOwner **** */ 99 | // Declare an onlyOwner 100 | .decl OnlyOwner(stmt: Statement) 101 | .output OnlyOwner 102 | 103 | OnlyOwner(stmtLoad) :- 104 | op(stmt,"CALLER"), 105 | block(stmt,b), 106 | op(stmtLoad, "SLOAD"), 107 | block(stmtLoad, b), 108 | op(stmt_,"EQ"), 109 | block(stmt_,b). 110 | 111 | 112 | /* **** Declare a Transfer **** */ 113 | 114 | /* balances[_to] += _amount */ 115 | .decl BalanceAddStmt(func: Function, s:Statement) 116 | BalanceAddStmt(func, stmt_add) :- 117 | 118 | // V70 = SHA3 0x0 0x40 119 | op(stmt_map2, "SHA3"), 120 | def(balance_to, stmt_map2), 121 | StatementInPublicFunction(stmt_map2, func), 122 | 123 | // V72 = S[V70] 124 | op(loadValStmt2, "SLOAD"), 125 | def(load_val2, loadValStmt2), 126 | use(balance_to, loadValStmt2, _), 127 | StatementInPublicFunction(loadValStmt2, func), 128 | 129 | // V73 = ADD V72 V30 130 | op(stmt_add, "ADD"), 131 | def(money_gain, stmt_add), 132 | use(load_val2, stmt_add, _), 133 | use(amount, stmt_add, _), 134 | StatementInPublicFunction(stmt_add, func), 135 | 136 | // S[V70] = V73 137 | op(storeValStmt2, "SSTORE"), 138 | use(balance_to, storeValStmt2, 1), 139 | use(money_gain, storeValStmt2, 2), 140 | StatementInPublicFunction(storeValStmt2, func). 141 | 142 | 143 | /* balances[_from] -= _amount */ 144 | .decl BalanceSubStmt(func: Function, s:Statement) 145 | BalanceSubStmt(func, stmt_sub) :- 146 | 147 | // V56 = SHA3 0x0 0x40 148 | op(stmt_map, "SHA3"), 149 | def(balance_from, stmt_map), 150 | StatementInPublicFunction(stmt_map, func), 151 | 152 | // V58 = S[V56] 153 | op(loadValStmt, "SLOAD"), 154 | def(load_val, loadValStmt), 155 | use(balance_from, loadValStmt, _), 156 | StatementInPublicFunction(loadValStmt, func), 157 | 158 | // V59 = SUB V58 V30 159 | op(stmt_sub, "SUB"), 160 | def(money_left, stmt_sub), 161 | use(load_val, stmt_sub, 1), 162 | use(amount, stmt_sub, 2), 163 | StatementInPublicFunction(stmt_sub, func), 164 | 165 | // S[V56] = V59 166 | op(storeValStmt, "SSTORE"), 167 | use(balance_to, storeValStmt, 1), 168 | use(money_left, storeValStmt, 2), 169 | StatementInPublicFunction(storeValStmt, func). 170 | 171 | 172 | /* var is a param of f */ 173 | //.decl SepcThreeParamOfFunction(f: Function, from_: Variable, to_: Variable, amount_: Variable) 174 | //SepcThreeParamOfFunction(f, from_, to_, amount_) :- 175 | .decl SepcThreeParamOfFunction(f: Function) 176 | SepcThreeParamOfFunction(f) :- 177 | op(stmt1, "CALLDATALOAD"), 178 | def(var1, stmt1), 179 | op(stmt2, "AND"), 180 | def(from_, stmt2), // address 181 | use(var1, stmt2, 2), 182 | 183 | op(stmt3, "CALLDATALOAD"), 184 | def(var2, stmt3), 185 | op(stmt4, "AND"), 186 | def(to_, stmt4), // address 187 | use(var2, stmt4, 2), 188 | 189 | var1 != var2, 190 | 191 | op(stmt5, "CALLDATALOAD"), 192 | def(amount_, stmt5), // uint 193 | 194 | // !!!!! fcorleone thinks we should make sure these stmts are in the same block not CanReach !!!!! 195 | CanReach(stmt1, stmt2), 196 | CanReach(stmt2, stmt3), 197 | CanReach(stmt3, stmt4), 198 | CanReach(stmt4, stmt5), 199 | 200 | // block(stmt1,b1), 201 | // block(stmt2,b1), 202 | // block(stmt3,b1), 203 | // block(stmt4,b1), 204 | // block(stmt5,b1), 205 | 206 | StatementInPublicFunction(stmt1, f), 207 | StatementInPublicFunction(stmt2, f), 208 | StatementInPublicFunction(stmt3, f), 209 | StatementInPublicFunction(stmt4, f), 210 | StatementInPublicFunction(stmt5, f). 211 | // StatementInPublicFunction(stmt6, f). 212 | 213 | /* var is a param of f */ 214 | //.decl SepcTwoParamOfFunction(f: Function, add_: Variable, amount_: Variable) 215 | //SepcTwoParamOfFunction(f, add_, amount_) :- 216 | .decl SepcTwoParamOfFunction(f: Function) 217 | SepcTwoParamOfFunction(f) :- 218 | AddressVariableForMemory(var1), // address 219 | def(var1, stmt1), 220 | 221 | op(stmt2, "CALLDATALOAD"), 222 | def(amount_, stmt2), // uint 223 | 224 | // !!!!! fcorleone thinks we should make sure these stmts are in the same block not CanReach !!!!! 225 | // block(stmt1,b1), 226 | // block(stmt2,b1), 227 | // block(stmt3,b1), 228 | // block(stmt4,b1), 229 | // block(stmt5,b1), 230 | 231 | StatementInPublicFunction(stmt1, f), 232 | StatementInPublicFunction(stmt2, f). 233 | 234 | //.decl Transfer(func: Function, from_: Variable, to_: Variable, amount_: Variable) 235 | .decl Transfer(func: Function) 236 | .output Transfer 237 | 238 | //Transfer(func, from_, to_, amount_) :- 239 | Transfer(func) :- 240 | // SepcThreeParamOfFunction(func, from_, to_, amount_), 241 | BalanceSubStmt(func, stmt_sub), 242 | BalanceAddStmt(func, stmt_add). 243 | 244 | 245 | .decl TransferWithoutSub(func: Function) 246 | //.output TransferWithoutSub 247 | 248 | TransferWithoutSub(func) :- 249 | SepcTwoParamOfFunction(func), 250 | !SepcThreeParamOfFunction(func), 251 | 252 | BalanceAddStmt(func, stmt_add), 253 | !Transfer(func). 254 | 255 | .decl TransferWithoutAdd(func: Function) 256 | //.output TransferWithoutAdd 257 | 258 | TransferWithoutAdd(func) :- 259 | SepcTwoParamOfFunction(func), 260 | !SepcThreeParamOfFunction(func), 261 | 262 | BalanceSubStmt(func, stmt_sub), 263 | !Transfer(func). 264 | 265 | /* **** Declare an address variable **** */ 266 | .decl AddressVariableForMemory(var_Add:Variable) 267 | //.output AddressVariableForMemory 268 | 269 | AddressVariableForMemory(var_Add) :- 270 | op(stmt_And, "AND"), 271 | def(And1Val, stmt_And), 272 | block(stmt_And,b0), 273 | op(stmt_And1, "AND"), 274 | use(And1Val, stmt_And1, _), 275 | block(stmt_And1,b0), 276 | def(var_Add, stmt_And1). 277 | 278 | /* **** Declare of ApproveStatement **** */ 279 | 280 | .decl ApproveMent(func_: Function) 281 | .output ApproveMent 282 | // SHA3's result is stored in memory for the fisrt dimension 283 | // Only test if there are two address in a block and 284 | ApproveMent(func_) :- 285 | // Two address variables used 286 | AddressVariableForMemory(var1), 287 | AddressVariableForMemory(var2), 288 | op(stmt1, "MSTORE"), 289 | use(var1, stmt1, _), 290 | op(stmt2, "MSTORE"), 291 | use(var2, stmt2, _), 292 | // Check whether in the same block so in the same function 293 | block(stmt1,b), 294 | block(stmt2,b), 295 | // Check for sub operation 296 | op(stmt_Map, "SHA3"), 297 | def(Offset, stmt_Map), 298 | op(stmtStore, "MSTORE"), 299 | use(Offset, stmtStore,_ ), 300 | 301 | op(stmt_Map2,"SHA3"), 302 | def(Element,stmt_Map2), 303 | // StatementInPublicFunction(stmt_map, func_), 304 | op(loadValStmt_, "SLOAD"), 305 | def(allow, loadValStmt_), 306 | use(Element, loadValStmt_, _), 307 | // StatementInPublicFunction(loadValStmt_, func_), 308 | op(stmt_sub_, "SUB"), 309 | use(allow, stmt_sub_, 1), 310 | block(stmt_sub_, b), 311 | StatementInPublicFunction(stmt_sub_, func_). 312 | 313 | // StatementInPublicFunction(stmt1, func_), 314 | // StatementInPublicFunction(stmt2, func_), 315 | 316 | /* **** Declare of Caller in a block **** */ 317 | .decl CallerinBlock(b: Block) 318 | 319 | CallerinBlock(b) :- 320 | op(stmt, "CALLER"), 321 | block(stmt, b). 322 | 323 | /* **** Declare of allowTransfer **** */ 324 | .decl allowTransfer(stmt: Statement) 325 | //.output allowTransfer 326 | 327 | allowTransfer(stmtAllow) :- 328 | op(stmtAllow, "SLOAD"), 329 | def(AllowValue, stmtAllow), 330 | op(stmtAllow2, "DIV"), 331 | use(AllowValue, stmtAllow2, _), 332 | def(AllowValue2,stmtAllow2), 333 | block(stmtAllow2, b1), 334 | op(stmtJump, "JUMPI"), 335 | use(JumpV, stmtJump, _), 336 | block(stmtJump, b1), 337 | !CallerinBlock(b1), 338 | depends(JumpV, AllowValue2). 339 | 340 | /* **** Declare of frozeFucntion **** */ 341 | .decl frozeFunction(func: Function) 342 | //.output frozeFunction 343 | 344 | frozeFunction(func) :- 345 | op(stmt1, "CALLDATALOAD"), 346 | def(var1, stmt1), 347 | op(stmt2, "AND"), 348 | use(var1, stmt2, 2), 349 | def(target, stmt2), // target 350 | 351 | op(stmt3, "CALLDATALOAD"), 352 | def(var2, stmt3), 353 | op(stmt4, "ISZERO"), 354 | use(var2, stmt4, _), 355 | def(froze, stmt4), // frozeFlag 356 | 357 | op(stmtSLOAD, "SLOAD"), 358 | def(Vtarget, stmtSLOAD), 359 | 360 | op(stmtOR,"OR"), 361 | use(V1, stmtOR, 1), 362 | use(V2, stmtOR, 2), 363 | def(VStore, stmtOR), 364 | 365 | depends(V1, froze), 366 | depends(V2, Vtarget), 367 | 368 | op(stmtStore, "SSTORE"), 369 | use(VStore, stmtStore, _), 370 | 371 | CanReach(stmt2, stmt4), 372 | CanReach(stmt4, stmtSLOAD), 373 | CanReach(stmtSLOAD, stmtOR), 374 | CanReach(stmtOR, stmtStore), 375 | 376 | StatementInPublicFunction(stmtStore, func). 377 | 378 | 379 | 380 | /* **** Declare of Backdoors **** */ 381 | // TYPE1 Arbitraytransfer (from, to, amount) 382 | .decl ArbitraryTransfer(func: Function) 383 | .output ArbitraryTransfer 384 | 385 | ArbitraryTransfer(func) :- 386 | // first it is a tranfer 387 | SepcThreeParamOfFunction(func), 388 | Transfer(func), 389 | // ownerOnly: require(msg.sender==owner) 390 | OnlyOwner(stmt), 391 | // withoutApprovement 392 | !ApproveMent(func), 393 | StatementInPublicFunction(stmt, func). 394 | 395 | // TYPE2 GenerateToken(to, amount) 396 | .decl GenerateToken(func: Function) 397 | .output GenerateToken 398 | 399 | GenerateToken(func) :- 400 | // first it is a transfer without from 401 | TransferWithoutSub(func), 402 | //ownerOnly: require(msg.sender == owner) 403 | OnlyOwner(stmt), 404 | StatementInPublicFunction(stmt, func). 405 | 406 | // TYPE3 DestroyToken(from, amount) 407 | .decl DestroyToken(func: Function) 408 | .output DestroyToken 409 | 410 | DestroyToken(func) :- 411 | // first it is a transfer without to 412 | TransferWithoutAdd(func), 413 | //ownerOnly: require(msg.sender == owner) 414 | OnlyOwner(stmt), 415 | StatementInPublicFunction(stmt, func). 416 | 417 | // TYPE4 FrozeAccount(account) 418 | .decl FrozeAccount(func: Function) 419 | .output FrozeAccount 420 | FrozeAccount(funcFroze) :- 421 | OnlyOwner(stmt), 422 | frozeFunction(funcFroze), 423 | StatementInPublicFunction(stmt, funcFroze). 424 | 425 | // TYPE5 DisableTransfer(bool) 426 | .decl DisableTransfer(func: Function) 427 | //.decl DisableTransfer(stmt: Statement) 428 | .output DisableTransfer 429 | 430 | DisableTransfer(funcAllow) :- 431 | //DisableTransfer(stmt) :- 432 | // A transfer with a modifier named allowTransfer 433 | SepcThreeParamOfFunction(func), 434 | Transfer(funcTransfer), 435 | allowTransfer(stmt1), 436 | StatementInPublicFunction(stmt1, funcTransfer), 437 | use(VarLoad, stmt1, _), 438 | 439 | // A function that is onlyowner to forbit transfer 440 | OnlyOwner(stmt), 441 | StatementInPublicFunction(stmt, funcAllow), 442 | op(stmtStore, "SSTORE"), 443 | CanReach(stmt,stmtStore), 444 | use(VarStore, stmtStore, _), 445 | 446 | // The Transfer function use the same place of the allowTransfer to check the value. 447 | value(VarStore, v1), 448 | value(VarLoad, v1). 449 | 450 | DisableTransfer(funcAllow) :- 451 | //DisableTransfer(stmt) :- 452 | // A transfer with a modifier named allowTransfer 453 | TransferWithoutAdd(funcTransfer), 454 | allowTransfer(stmt1), 455 | StatementInPublicFunction(stmt1, funcTransfer), 456 | use(VarLoad, stmt1, _), 457 | 458 | // A function that is onlyowner to forbit transfer 459 | OnlyOwner(stmt), 460 | StatementInPublicFunction(stmt, funcAllow), 461 | op(stmtStore, "SSTORE"), 462 | CanReach(stmt,stmtStore), 463 | use(VarStore, stmtStore, _), 464 | 465 | // The Transfer function use the same place of the allowTransfer to check the value. 466 | value(VarStore, v1), 467 | value(VarLoad, v1). 468 | 469 | DisableTransfer(funcAllow) :- 470 | //DisableTransfer(stmt) :- 471 | // A transfer with a modifier named allowTransfer 472 | TransferWithoutSub(funcTransfer), 473 | allowTransfer(stmt1), 474 | StatementInPublicFunction(stmt1, funcTransfer), 475 | use(VarLoad, stmt1, _), 476 | 477 | // A function that is onlyowner to forbit transfer 478 | OnlyOwner(stmt), 479 | StatementInPublicFunction(stmt, funcAllow), 480 | op(stmtStore, "SSTORE"), 481 | CanReach(stmt,stmtStore), 482 | use(VarStore, stmtStore, _), 483 | 484 | // The Transfer function use the same place of the allowTransfer to check the value. 485 | value(VarStore, v1), 486 | value(VarLoad, v1). 487 | 488 | -------------------------------------------------------------------------------- /backend/secure_check/main.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); 2 | # you may not use this file except in compliance with the License. 3 | # You may obtain a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | from signal import signal, SIGPIPE, SIG_DFL 13 | import os 14 | import sys 15 | import json 16 | import subprocess 17 | import logging 18 | import re 19 | from multiprocessing import Process 20 | from _vulnerabilities import VUL_MAPPING_TABLES, get_vul_info 21 | 22 | 23 | class VulsRecorder: 24 | def __init__(self): 25 | self._vuls = {} 26 | 27 | def __iter__(self): 28 | return iter(self._vuls.items()) 29 | 30 | def record(self, vul_id, line_no): 31 | if vul_id not in self._vuls: 32 | self._vuls[vul_id] = [] 33 | 34 | if line_no not in self._vuls[vul_id]: 35 | self._vuls[vul_id].append(line_no) 36 | 37 | 38 | def execute_command(contract_dir, cmd, timeout, output=None): 39 | cwd = os.getcwd() 40 | os.chdir(contract_dir) 41 | if output is not None: 42 | output = open(output, "w") 43 | else: 44 | output = open("/dev/null", "w") 45 | 46 | try: 47 | logging.debug(f"execute cmd: {cmd}") 48 | subprocess.run(cmd, timeout=timeout, check=True, 49 | stdout=output, shell=True) 50 | return True 51 | except subprocess.TimeoutExpired as e: 52 | logging.warning( 53 | f"[execute_command]timeout occurs when running `{cmd}`, timeout: {timeout}s") 54 | return False 55 | except Exception as e: 56 | logging.warning( 57 | f"[execute_command]error occurs when running `{cmd}`, output: {e}") 58 | return False 59 | finally: 60 | output.close() 61 | os.chdir(cwd) 62 | 63 | 64 | __SMART_CHECK_REPORT_FILE = "smart_check_report.txt" 65 | __MYTH_REPORT_FILE = "myth_report.txt" 66 | 67 | 68 | def run_smart_check(contract_dir, contract, timeout): 69 | cmd = f"smartcheck -p {contract}" 70 | execute_command(contract_dir, cmd, timeout, __SMART_CHECK_REPORT_FILE) 71 | 72 | 73 | def run_oyente(contract_dir, contract, timeout): 74 | cmd = f"docker run -v $(pwd):/project qspprotocol/oyente-0.4.25 -s /project/{contract} -j" 75 | execute_command(contract_dir, cmd, timeout) 76 | 77 | 78 | def indicate_compiler_version(contract_dir, contract): 79 | # Removes comments first. 80 | comment_regex = re.compile("\\/\\*[\\s\\S]*\\*\\/|\\/\\/.*") 81 | content = open(os.path.join(contract_dir, contract)).read() 82 | content = comment_regex.sub("", content) 83 | 84 | # Uses a specified regex expression to search first occurrence of `pragma solidity` 85 | # statement, and extract the version info inside it. This method can cover most cases. 86 | # TODO: Processes cases such as `pragma solididy <0.4.26;`. 87 | version = re.search( 88 | r"pragma\s+solidity.*?(\d+.\d+.\d+)\s*;", content, re.DOTALL) 89 | if version is None: 90 | logging.warning( 91 | "[run_myth]the contract has no `pragma solidity` stmt") 92 | return 93 | version = version.group(1) 94 | if version.startswith("0.4"): 95 | return "0.4.26" 96 | if version.startswith("0.5"): 97 | return "0.5.17" 98 | if version.startswith("0.6"): 99 | return "0.6.12" 100 | if version.startswith("0.7"): 101 | return "0.7.6" 102 | if version.startswith("0.8"): 103 | return "0.8.1" 104 | return None 105 | 106 | 107 | def run_myth(contract_dir, contract, timeout): 108 | # Mythrill uses latest solc to compile contract, but when the version 109 | # of latest solc is not match with compiler version which is indicated 110 | # by `pragma solidity` statement in contract, Mythrill will refuse to 111 | # check contract. To avoid being got stuck in above situation, we need 112 | # to find the compiler version requirement in contract, and pass it to 113 | # Mythrill via `--solv` option. 114 | version = indicate_compiler_version(contract_dir, contract) 115 | cmd_prefix = f"docker run -v $(pwd):/tmp mythril/myth analyze /tmp/{contract} -t 1" 116 | execute_command( 117 | contract_dir, 118 | f"{cmd_prefix} --solv {version}", 119 | timeout, 120 | __MYTH_REPORT_FILE) 121 | 122 | 123 | # `bdd` means backdoor detectors 124 | def run_bdd(contract_dir, contract, timeout): 125 | version = indicate_compiler_version(contract_dir, contract) 126 | tmp_file = contract + ".tmp" 127 | cmd = f"docker run -v $(pwd):/tmp ethereum/solc:{version} --bin-runtime /tmp/{contract} 2>/dev/null \ 128 | | tail -n 3" 129 | execute_command(contract_dir, cmd, timeout, tmp_file) 130 | 131 | sig_file = contract + ".sig" 132 | bin_file = contract + ".hex" 133 | cmd = f"sed -n \"1p\" {tmp_file} > {sig_file};\ 134 | sed -n \"3p\" {tmp_file} > {bin_file};\ 135 | rm -f {tmp_file}" 136 | execute_command(contract_dir, cmd, timeout) 137 | 138 | backdoor_detector_root = os.path.join(os.path.dirname( 139 | os.path.abspath(__file__)), "backdoor_detector") 140 | analyze_entry = os.path.sep.join( 141 | [backdoor_detector_root, "MadMax", "bin", "analyze.sh"]) 142 | backdoor_datalog = os.path.join(backdoor_detector_root, "backdoor.dl") 143 | cmd = " ".join(["bash", analyze_entry, bin_file, backdoor_datalog]) 144 | execute_command(contract_dir, cmd, timeout) 145 | 146 | 147 | def run_analysis_tools(contract_dir, contract, timeout): 148 | processes = [] 149 | args = (contract_dir, contract, timeout) 150 | processes.append(Process(target=run_smart_check, args=args)) 151 | processes.append(Process(target=run_oyente, args=args)) 152 | processes.append(Process(target=run_myth, args=args)) 153 | processes.append(Process(target=run_bdd, args=args)) 154 | 155 | for process in processes: 156 | process.start() 157 | for process in processes: 158 | process.join() 159 | 160 | 161 | def validate_report(report_path): 162 | if not os.path.exists(report_path): 163 | return False 164 | if not os.path.isfile(report_path): 165 | return False 166 | if os.path.getsize(report_path) == 0: 167 | return False 168 | return True 169 | 170 | 171 | def processing_myth_report(contract_dir, vuls_recorder): 172 | report_path = os.path.join(contract_dir, __MYTH_REPORT_FILE) 173 | if not validate_report(report_path): 174 | return 175 | 176 | vul_mapping_table = VUL_MAPPING_TABLES["myth"] 177 | with open(report_path) as f: 178 | lines = f.readlines() 179 | lines_num = len(lines) 180 | 181 | i = 0 182 | while i < lines_num: 183 | if '====' in lines[i]: 184 | vul_type = None 185 | line_no = None 186 | for j in range(i + 1, lines_num): 187 | if 'SWC ID:' in lines[j]: 188 | # Example: "SWC ID: 111" 189 | vul_type = lines[j][lines[j].rfind(':') + 1:].strip() 190 | if 'In file:' in lines[j]: 191 | # Example: "In file: /tmp/tx_origin.sol:20" 192 | line_no = int(lines[j][lines[j].rfind(':') + 1:-1]) 193 | if ('====' in lines[j]) or (j == (lines_num - 1)): 194 | i = j 195 | break 196 | if vul_type in vul_mapping_table: 197 | vul_id = vul_mapping_table[vul_type] 198 | vuls_recorder.record(vul_id, line_no) 199 | else: 200 | i += 1 201 | 202 | 203 | class LineScopes: 204 | class Scope: 205 | def __init__(self, start, end): 206 | self.start = start 207 | self.end = end 208 | 209 | def __init__(self, contract_dir, contract): 210 | lines = open(os.path.join(contract_dir, contract)).readlines() 211 | self.scopes = [] 212 | 213 | count = 0 214 | for line in lines: 215 | self.scopes.append(self.Scope(count, count + len(line))) 216 | count += len(line) 217 | 218 | def query(self, start_pos): 219 | pri = 0 220 | suf = len(self.scopes) 221 | 222 | while True: 223 | mid = (pri + suf) // 2 224 | scope = self.scopes[mid] 225 | if start_pos >= scope.start and start_pos <= scope.end: 226 | return mid + 1 227 | elif start_pos < scope.start: 228 | suf = mid 229 | elif start_pos > scope.end: 230 | pri = mid 231 | 232 | 233 | def processing_bdd_report(contract_dir, contract, vuls_recorder): 234 | version = indicate_compiler_version(contract_dir, contract) 235 | meta_file = contract + ".meta" 236 | cmd = f"docker run -v $(pwd):/tmp ethereum/solc:{version} --combined-json hashes,ast \ 237 | /tmp/{contract} 2>/dev/null" 238 | execute_command(contract_dir, cmd, None, meta_file) 239 | 240 | # Same to `sig_file` used in `run_bdd`. If you want to change this name, 241 | # remember to change both simultaneously. 242 | sig_file = contract + ".sig" 243 | contract_name = open(os.path.join(contract_dir, sig_file)).read() 244 | contract_name = re.search(r"======= (.*) =======", contract_name).group(1) 245 | 246 | selector2fn = {} 247 | fn2loc = {} 248 | line_scopes = LineScopes(contract_dir, contract) 249 | with open(os.path.join(contract_dir, meta_file)) as f: 250 | metadata = json.load(f) 251 | 252 | # Collects selectors of contract functions. 253 | # TODO: Supports function overloading 254 | hashes = metadata["contracts"][contract_name]["hashes"] 255 | for fn_name, hash in hashes.items(): 256 | selector2fn[hash] = fn_name.split("(")[0] 257 | 258 | # Collects line number of contract functions. 259 | sources = metadata["sources"] 260 | key = list(sources.keys())[0] 261 | ast = sources[key]["AST"] 262 | 263 | children = ast["children"] 264 | variable_decls = [ 265 | child for child in children if child["name"] == "VariableDeclaration"] 266 | for var in variable_decls: 267 | attrs = var["attributes"] 268 | if attrs["stateVariable"]: 269 | if attrs["visibility"] == "public": 270 | name = attrs["name"] 271 | del selector2fn[name] 272 | 273 | contracts = [child for child in children if child["name"] 274 | == "ContractDefinition"] 275 | contract = None 276 | for i in range(len(contracts)): 277 | if contracts[i]["attributes"]["name"] == contract_name.split( 278 | ":")[-1]: 279 | contract = contracts[i] 280 | assert(contract is not None) 281 | 282 | children = contract["children"] 283 | fns = [child for child in children if child["name"] 284 | == "FunctionDefinition"] 285 | for fn in fns: 286 | fn_name = fn["attributes"]["name"] 287 | scope = fn["src"] 288 | start_pos = int(scope.split(":")[0]) 289 | line_no = line_scopes.query(start_pos) 290 | fn2loc[fn_name] = line_no 291 | selectors = sorted(selector2fn.keys(), key=lambda x: int(x, 16)) 292 | 293 | # Get suspect function name from ".csv" reports 294 | reports = ['ArbitraryTransfer', 295 | 'GenerateToken', 296 | 'DestroyToken', 297 | 'FrozeAccount', 298 | 'DisableTransfer'] 299 | vul_mapping_table = VUL_MAPPING_TABLES["bdd"] 300 | 301 | for report in reports: 302 | report_path = os.path.join(contract_dir, report + ".csv") 303 | if not validate_report(report_path): 304 | continue 305 | 306 | with open(report_path, 'r') as f: 307 | lines = f.readlines() 308 | for line in lines: 309 | idx = int(line) 310 | selector = selectors[idx] 311 | fn_name = selector2fn[selector] 312 | line_no = fn2loc[fn_name] 313 | vul_id = vul_mapping_table[report] 314 | vuls_recorder.record(vul_id, line_no) 315 | 316 | 317 | def processing_oyente_report(contract_dir, vuls_recorder): 318 | report_path = contract_dir 319 | for f in os.listdir(report_path): 320 | path, ext = os.path.splitext(f) 321 | if ext == ".json" and path.find(".sol:") != -1: 322 | report_path = os.path.join(report_path, f) 323 | if not validate_report(report_path): 324 | return 325 | 326 | vul_mapping_table = VUL_MAPPING_TABLES["oynete"] 327 | with open(report_path) as f: 328 | data = json.load(f) 329 | line_no = -1 330 | 331 | for vul_type, info_list in data["vulnerabilities"].items(): 332 | for info in info_list: 333 | start_flag = ".sol:" 334 | start = info.find(start_flag) + len(start_flag) 335 | end = info[start:].find(':') 336 | line_no = int(info[start: start + end]) 337 | 338 | vul_id = vul_mapping_table[vul_type] 339 | vuls_recorder.record(vul_id, line_no) 340 | 341 | 342 | def processing_smart_check_report(contract_dir, vuls_recorder): 343 | report_path = os.path.join(contract_dir, __SMART_CHECK_REPORT_FILE) 344 | if not validate_report(report_path): 345 | return 346 | 347 | vul_mapping_table = VUL_MAPPING_TABLES['smart_check'] 348 | with open(report_path) as f: 349 | lines = f.readlines() 350 | lines_num = len(lines) 351 | 352 | i = 0 353 | while i < lines_num: 354 | if 'ruleId:' in lines[i]: 355 | vul_type = None 356 | line_no = None 357 | for j in range(i, lines_num): 358 | if 'ruleId:' in lines[j]: 359 | # Example: "ruleId: SOLIDITY_TX_ORIGIN" 360 | vul_type = lines[j][lines[j].rfind(':') + 2:-1] 361 | if 'line:' in lines[j]: 362 | # Example: "line: 10" 363 | line_no = int(lines[j][lines[j].rfind(':') + 2:-1]) 364 | if ('content:' in lines[j]) or (j == (lines_num - 1)): 365 | i = j 366 | break 367 | if vul_type in vul_mapping_table: 368 | vul_id = vul_mapping_table[vul_type] 369 | vuls_recorder.record(vul_id, line_no) 370 | else: 371 | i += 1 372 | 373 | 374 | def processing_reports(contract_dir, contract): 375 | vuls_recorder = VulsRecorder() 376 | processing_smart_check_report(contract_dir, vuls_recorder) 377 | processing_oyente_report(contract_dir, vuls_recorder) 378 | processing_myth_report(contract_dir, vuls_recorder) 379 | processing_bdd_report(contract_dir, contract, vuls_recorder) 380 | return vuls_recorder 381 | 382 | 383 | def infer_contract_name(contract_dir): 384 | return os.path.split(contract_dir)[1] + ".sol" 385 | 386 | 387 | if __name__ == '__main__': 388 | def abort(msg): 389 | print(msg) 390 | exit(-1) 391 | 392 | if sys.platform != "darwin" and sys.platform != "linux": 393 | abort("this program can only be executed on macOS or Linux platform") 394 | 395 | argc = len(sys.argv) 396 | if argc < 2 or argc > 3: 397 | abort( 398 | f"usage: python3 {sys.argv[0]} [timeout]") 399 | 400 | contract_dir = sys.argv[1] 401 | contract_dir = os.path.dirname(contract_dir + os.path.sep) 402 | if not os.path.exists(contract_dir) or not os.path.isdir(contract_dir): 403 | abort(f"the path is not exist: {contract_dir}") 404 | 405 | timeout = 60 406 | if argc == 3: 407 | arg = sys.argv[2] 408 | error = f"invalid timeout `{arg}`, which is expected to be a positive number" 409 | try: 410 | timeout = int(arg) 411 | if not timeout >= 0: 412 | abort(error) 413 | if timeout == 0: 414 | timeout = None 415 | except Exception as _: 416 | abort(error) 417 | 418 | signal(SIGPIPE, SIG_DFL) 419 | logging.basicConfig( 420 | format='[%(levelname)s][%(asctime)s]%(message)s', level=logging.DEBUG) 421 | 422 | contract = infer_contract_name(contract_dir) 423 | run_analysis_tools(contract_dir, contract, timeout) 424 | vuls_recorder = processing_reports(contract_dir, contract) 425 | out_json = {} 426 | for vul_id, lines in vuls_recorder: 427 | vul_info = get_vul_info(vul_id) 428 | if vul_info.level != "ignore": 429 | out_json[vul_id] = {} 430 | vul = out_json[vul_id] 431 | vul["name"] = vul_info.name 432 | vul["description"] = vul_info.desc 433 | vul["swcId"] = vul_info.swc 434 | vul["lineNo"] = lines 435 | vul["advice"] = vul_info.advice 436 | vul["level"] = vul_info.level 437 | js = json.dumps(out_json, sort_keys=True, indent=4, separators=(',', ':')) 438 | 439 | with open(os.path.join(contract_dir, "final_report.json"), "w") as f: 440 | json.dump(out_json, f, indent=1) 441 | -------------------------------------------------------------------------------- /backend/secure_check/backdoor_detector/MadMax/src/function.py: -------------------------------------------------------------------------------- 1 | # BSD 3-Clause License 2 | # 3 | # Copyright (c) 2016, 2017, The University of Sydney. All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # * Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # * Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # * Neither the name of the copyright holder nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | """function.py: 31 | Classes for identifying and exporting functions in the control flow graph. 32 | Tested and developed on Solidity version 0.4.11""" 33 | 34 | import typing as t 35 | 36 | import src.memtypes as memtypes 37 | import src.opcodes as opcodes 38 | import src.tac_cfg as tac_cfg 39 | 40 | 41 | class Function: 42 | """ 43 | A representation of a Solidity function with associated metadata. 44 | """ 45 | 46 | def __init__(self): 47 | self.body = [] 48 | self.start_block = None 49 | self.end_block = None 50 | self.mapping = {} # a mapping of preds to succs of the function body. 51 | self.signature = "" 52 | self.is_private = False 53 | 54 | def __str__(self): 55 | if self.is_private: 56 | sig = "Private function" 57 | else: 58 | if self.signature: 59 | sig = "Public function signature: " + self.signature 60 | else: 61 | sig = "Public fallback function" 62 | entry_block = "Entry block: " + self.start_block.ident() 63 | if self.end_block is not None: 64 | exit_block = "Exit block: " + self.end_block.ident() 65 | else: 66 | exit_block = "Exit block: None identified" 67 | body = "Body: " + ", ".join(b.ident() for b in sorted(self.body)) 68 | return "\n".join([sig, entry_block, exit_block, body]) 69 | 70 | 71 | class FunctionExtractor: 72 | """A class for extracting Solidity functions from an already generated TAC cfg.""" 73 | 74 | def __init__(self, cfg: tac_cfg.TACGraph): 75 | self.cfg = cfg # the tac_cfg that this operates on 76 | self.public_functions = [] 77 | self.private_functions = [] 78 | self.invoc_pairs = {} # a mapping from invocation sites to return addresses 79 | 80 | def __str__(self) -> str: 81 | """ 82 | Returns a string representation of all the functions in the graph 83 | after extraction has been performed. 84 | 85 | Returns: 86 | A string summary of all the functions in the control flow graph 87 | """ 88 | 89 | return "\n".join(["Function {}:\n{}\n".format(str(i), str(func)) 90 | for i, func in enumerate(self.functions)]) 91 | 92 | @staticmethod 93 | def mark_body(path: t.List[tac_cfg.TACBasicBlock], num: int) -> None: 94 | """ 95 | Marks every block in the path with the given number. 96 | Used for marking function bodies. 97 | """ 98 | for block in path: 99 | block.ident_suffix += "_F" + str(num) 100 | 101 | @property 102 | def functions(self): 103 | return self.public_functions + self.private_functions 104 | 105 | def extract(self) -> None: 106 | """Extracts private and public functions""" 107 | self.private_functions.extend(self.extract_private_functions()) 108 | self.public_functions.extend(self.extract_public_functions()) 109 | 110 | def mark_functions(self) -> None: 111 | """Mark extracted function bodies with unique identifier suffixes.""" 112 | for i, func in enumerate(self.functions): 113 | self.mark_body(func.body, i) 114 | 115 | def extract_public_functions(self) -> t.Iterable[Function]: 116 | """ 117 | Return a list of the solidity public functions exposed by the contract of this cfg. 118 | 119 | Returns: 120 | A list of the extracted functions. 121 | """ 122 | 123 | # Find the function signature variable holding call data 0, 124 | # at the earliest query to that location in the program. 125 | load_list = [] 126 | load_block = None 127 | 128 | for block in sorted(self.cfg.blocks): 129 | load_list = [op for op in block.tac_ops 130 | if op.opcode == opcodes.CALLDATALOAD 131 | and op.args[0].value.const_value == 0] 132 | if len(load_list) != 0: 133 | load_block = block 134 | break 135 | 136 | if len(load_list) == 0: 137 | return [] 138 | sig_var = load_list[0].lhs 139 | 140 | # Follow the signature until it's transformed into its final shape. 141 | for o in load_block.tac_ops: 142 | if not isinstance(o, tac_cfg.TACAssignOp) or id( 143 | sig_var) not in [id(a.value) for a in o.args]: 144 | continue 145 | if o.opcode == opcodes.EQ: 146 | break 147 | sig_var = o.lhs 148 | 149 | # Find all the places the function signature is compared to a constant 150 | func_sigs = [] 151 | fallthroughs = [] 152 | 153 | for b in self.cfg.blocks: 154 | for o in b.tac_ops: 155 | if not isinstance(o, tac_cfg.TACAssignOp) or id( 156 | sig_var) not in [id(a.value) for a in o.args]: 157 | continue 158 | if o.opcode == opcodes.EQ: 159 | sig = [ 160 | a.value for a in o.args if id( 161 | a.value) != id(sig_var)][0] 162 | 163 | # Append the non-fallthrough successor to the function sig 164 | # list 165 | for succ in [s for s in b.succs if s != b.fallthrough]: 166 | func_sigs.append((succ, hex(sig.const_value))) 167 | 168 | # Save the fallthrough location so the last one can be added as 169 | # the fallback function 170 | if b.fallthrough is not None: 171 | fallthroughs.append(b.fallthrough) 172 | 173 | # Add the fallback function 174 | if fallthroughs: 175 | func_sigs.append((fallthroughs[-1], "")) 176 | 177 | return [ 178 | self.get_public_function( 179 | s[0], 180 | signature=s[1]) for s in func_sigs] 181 | 182 | def get_public_function(self, block: tac_cfg.TACBasicBlock, 183 | signature: str = "") -> Function: 184 | """ 185 | Identifies the function starting with the given block 186 | 187 | Args: 188 | block: A BasicBlock to be used as the starting block for the function. 189 | signature: The solidity hashed function signature associated with the function to be extracted. 190 | 191 | Returns: 192 | A Function object containing the blocks composing the function body. 193 | """ 194 | body = [] 195 | queue = [block] 196 | end_block = None 197 | cur_block = None # A placeholder for prev_block 198 | jump = False # Keeps track of whether we have just jumped or not 199 | pre_jump_block = block 200 | while len(queue) > 0: 201 | prev_block = cur_block 202 | cur_block = queue.pop(0) 203 | 204 | # jump over function bodies 205 | for f in self.functions: 206 | if cur_block in f.body and not jump: 207 | cur_block = self.__jump_to_next_loc(cur_block, body, 208 | prev_block.exit_stack) 209 | if cur_block in f.body and jump: 210 | # If we jumped, the previous block is actually from before 211 | # the jump 212 | cur_block = self.__jump_to_next_loc( 213 | cur_block, body, pre_jump_block.exit_stack) 214 | jump = False 215 | # In case we didn't find a new block to jump to 216 | if cur_block is None: 217 | continue 218 | # Jump over other invocation sites after adding them to body 219 | if cur_block in self.invoc_pairs: 220 | jump = True 221 | pre_jump_block = cur_block 222 | if cur_block not in body: 223 | body.append(cur_block) 224 | cur_block = self.invoc_pairs[cur_block] 225 | # Since an invocation site always has a successor, 226 | # it can't be an end block for the function 227 | 228 | if len(cur_block.succs) == 0: 229 | end_block = cur_block 230 | if cur_block not in body: 231 | body.append(cur_block) 232 | for b in cur_block.succs: 233 | queue.append(b) 234 | f = Function() 235 | f.body = body 236 | f.start_block = block 237 | # Assuming that there will only be one end_block 238 | f.end_block = end_block 239 | f.signature = signature 240 | f.is_private = False 241 | return f 242 | 243 | def __jump_to_next_loc(self, block: tac_cfg.TACBasicBlock, 244 | body: t.List[tac_cfg.TACBasicBlock], 245 | exit_stack: memtypes.VariableStack) -> tac_cfg.TACBasicBlock: 246 | """ 247 | Helper method to jump over private functions during public function 248 | identification. Uses BFS to find the next available block that is not 249 | in a function. 250 | 251 | Args: 252 | block: The current block to be tested as being in the function body 253 | body: The body of the current function being identified. 254 | exit_stack: The stack of the previous block, or block before that after 255 | a previous jump over a function. If a block is in this 256 | exit_stack, then it is the next block in the flow of the 257 | function currently being identified. 258 | """ 259 | queue = [block] 260 | visited = [block] 261 | while len(queue) > 0: 262 | block = queue.pop() 263 | if len(block.succs) == 0: 264 | return block 265 | visited.append(block) 266 | in_func = False 267 | for f in self.functions: 268 | if block in f.body: 269 | in_func = True 270 | break 271 | # Check that the block is not related to another function, 272 | # and we haven't visited it yet, and that it is in the exit stack 273 | # We can discard blocks in the body of the function being identified 274 | # since we want to discover new blocks, not old ones 275 | if not in_func and block not in self.invoc_pairs.keys() and \ 276 | block.ident() in str(exit_stack) and block not in body: 277 | return block 278 | for succ in block.succs: 279 | if succ not in visited: 280 | queue.append(succ) 281 | return None 282 | 283 | def extract_private_functions(self) -> t.List[tac_cfg.TACBasicBlock]: 284 | """ 285 | Extracts private functions 286 | 287 | Returns: 288 | A list of of Function objects: the private functions identified in the cfg 289 | """ 290 | # Get invocation site -> return block mappings 291 | start_blocks = [] 292 | pair_list = [] 293 | for block in reversed(self.cfg.blocks): 294 | invoc_pairs = self.is_private_func_start(block) 295 | if invoc_pairs: 296 | start_blocks.append(block) 297 | pair_list.append(invoc_pairs) 298 | 299 | # Store invocation site - return pairs for later usage 300 | for d in pair_list: 301 | for key in d.keys(): 302 | self.invoc_pairs[key] = d[key] 303 | 304 | # Find the Function body itself 305 | private_funcs = list() 306 | for i, block in enumerate(start_blocks): 307 | return_blocks = list(pair_list[i].values()) 308 | f = self.find_func_body(block, return_blocks, pair_list) 309 | if not f or len( 310 | f.body) == 1: # Can't have a function with 1 block in EVM 311 | continue 312 | if f is not None: 313 | f.is_private = True 314 | private_funcs.append(f) 315 | return private_funcs 316 | 317 | def is_private_func_start(self, block: tac_cfg.TACBasicBlock) -> t.Dict[ 318 | tac_cfg.TACBasicBlock, tac_cfg.TACBasicBlock]: 319 | """ 320 | Determines the invocation and return points of the function beginning 321 | with the given block, if it exists. 322 | 323 | Args: 324 | block: a BasicBlock to be tested as the possible beginning of a function 325 | 326 | Returns: 327 | A mapping of invocation sites to return blocks of the function, 328 | if the given block is the start of a function. 329 | None if the block is not the beginning of a function. 330 | """ 331 | # if there are multiple paths converging, this is a possible function 332 | # start 333 | preds = list(sorted(block.preds)) 334 | if (len(preds) <= 1) or len(list(block.succs)) == 0: 335 | return None 336 | func_succs = [] # a list of what succs to look out for. 337 | func_mapping = {} # mapping of predecessors -> successors of function 338 | params = [] # list of parameters 339 | for pre in preds: 340 | if pre not in block.succs: 341 | # Check for at least 2 push opcodes and net stack gain 342 | push_count = 0 343 | for evm_op in pre.evm_ops: 344 | if evm_op.opcode.is_push(): 345 | push_count += 1 346 | if push_count <= 1: 347 | return None 348 | if len(pre.delta_stack) == 0: 349 | return None 350 | for val in list(pre.delta_stack): 351 | ref_block = self.cfg.get_block_by_ident(str(val)) 352 | # Ensure that the block pointed to by the block exists and 353 | # is reachable 354 | if ref_block is not None and self.cfg.reaches(pre, [ 355 | ref_block]): 356 | func_mapping[pre] = ref_block 357 | func_succs.append(ref_block) 358 | break 359 | params.append(val) 360 | if not func_succs or len(func_mapping) == 1: 361 | return None 362 | # We have our start 363 | return func_mapping 364 | 365 | def find_func_body(self, block: tac_cfg.TACBasicBlock, 366 | return_blocks: t.List[tac_cfg.TACBasicBlock], 367 | invoc_pairs: t.List[tac_cfg.TACBasicBlock]) -> 'Function': 368 | """ 369 | Assuming the block is a definite function start, identifies all paths 370 | from start to end using BFS 371 | 372 | Args: 373 | block: the start block of a function. 374 | return_blocks: the list of return blocks of the function. 375 | invoc_pairs: a mapping of all other invocation and return point pairs 376 | to allow jumping over other functions. 377 | 378 | Returns: 379 | A function object representing the function starting with the given block 380 | """ 381 | # Traverse down levels with BFS until we hit a block that has the return 382 | # addresses specified above 383 | body = [] 384 | queue = [block] 385 | end = False 386 | while len(queue) > 0: 387 | curr_block = queue.pop(0) 388 | # When we call a function, we just jump to the return address 389 | for entry in invoc_pairs: 390 | if curr_block in entry: 391 | body.append(curr_block) 392 | curr_block = entry[curr_block] 393 | cur_succs = [b for b in curr_block.succs] 394 | if set(return_blocks).issubset(set(cur_succs)): 395 | end = True 396 | if curr_block not in body and self.cfg.reaches( 397 | curr_block, return_blocks): 398 | body.append(curr_block) 399 | for b in curr_block.succs: 400 | if b not in return_blocks: 401 | queue.append(b) 402 | 403 | if end: 404 | f = Function() 405 | f.start_block = block 406 | poss_exit_blocks = [sorted(b.preds) for b in return_blocks] 407 | exit_blocks = set(poss_exit_blocks[0]) 408 | for preds in poss_exit_blocks: 409 | exit_blocks = exit_blocks & set(preds) 410 | # We assume the end_block is the last block in the disasm from all 411 | # candidates 412 | f.end_block = sorted(exit_blocks).pop() 413 | f.succs = sorted(return_blocks) 414 | f.preds = sorted(block.preds) 415 | f.body = body 416 | return f 417 | 418 | return None 419 | --------------------------------------------------------------------------------