From d6e8813100e2128f9fbe91f2eafbc425aa6f12d7 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Mon, 1 Aug 2022 13:08:03 +0100 Subject: [PATCH] Add partial abi support (#1655) * Fix issues with arbitrary jump dest * Support abi * Add partial abi support * Fix test * Fix test directory --- mythril/analysis/report.py | 25 +++++++++ tests/analysis/abi_decode_test.py | 56 +++++++++++++++++++ .../arbitrary_jump_test.py | 2 - 3 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 tests/analysis/abi_decode_test.py rename tests/{analysis_tests => analysis}/arbitrary_jump_test.py (96%) diff --git a/mythril/analysis/report.py b/mythril/analysis/report.py index a67334eb..403c9e62 100644 --- a/mythril/analysis/report.py +++ b/mythril/analysis/report.py @@ -1,7 +1,9 @@ """This module provides classes that make up an issue report.""" import logging +import re import json import operator +from eth_abi import decode_abi from jinja2 import PackageLoader, Environment from typing import Dict, List, Any, Optional import hashlib @@ -193,11 +195,34 @@ class Issue: # TODO: Check other mythx tools for dependency before supporting multiple possible function names if len(sig) > 0: step["name"] = sig[0] + step["resolved_input"] = Issue.resolve_input( + step["calldata"], sig[0] + ) else: step["name"] = "unknown" except ValueError: step["name"] = "unknown" + @staticmethod + def resolve_input(data, function_name): + """ + Adds decoded calldate to the tx sequence. + """ + data = data[10:] + + # Eliminates the first and last brackets + # Since signature such as func((bytes32,bytes32,uint8)[],(address[],uint32)) are valid + type_info = function_name[function_name.find("(") + 1 : -1] + type_info = re.split(r",\s*(?![^()]*\))", type_info) + + if len(data) % 64 > 0: + data += "0" * (64 - len(data) % 64) + try: + decoded_output = decode_abi(type_info, bytes.fromhex(data)) + return decoded_output + except Exception as e: + return None + class Report: """A report containing the content of multiple issues.""" diff --git a/tests/analysis/abi_decode_test.py b/tests/analysis/abi_decode_test.py new file mode 100644 index 00000000..32933225 --- /dev/null +++ b/tests/analysis/abi_decode_test.py @@ -0,0 +1,56 @@ +import pytest + +from mythril.analysis.report import Issue +from mythril.disassembler.disassembly import Disassembly +from mythril.laser.ethereum.state.environment import Environment +from mythril.laser.ethereum.state.account import Account +from mythril.laser.ethereum.state.machine_state import MachineState +from mythril.laser.ethereum.state.global_state import GlobalState +from mythril.laser.ethereum.state.world_state import WorldState +from mythril.laser.ethereum.instructions import Instruction +from mythril.laser.ethereum.transaction.transaction_models import MessageCallTransaction +from mythril.laser.smt import symbol_factory, simplify, LShR + + +test_data = ( + ( + "0xa9059cbb000000000000000000000000010801010101010120020101020401010408040402", + "func(uint256,uint256)", + ( + 5887484186314823854737699484601117092168074244, + 904625697166532776746648320380374280103671755200316906558262375061821325312, + ), + ), + ( + "0xa9059cbb00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002", + "func(uint256,uint256)", + (2, 2), + ), + ( + "0xa0cce1b3000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000", + "func(bytes32,(bytes32,bytes32,uint8,uint8)[],(address[],uint32))", + ( + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02", + ( + ( + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90", + 0, + 0, + ), + ( + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + 1, + 0, + ), + ), + (("0x0000000000000000000000000000000000000000",), 0), + ), + ), +) + + +@pytest.mark.parametrize("call_data, signature, expected", test_data) +def test_abi_decode(call_data, signature, expected): + assert Issue.resolve_input(call_data, signature) == expected diff --git a/tests/analysis_tests/arbitrary_jump_test.py b/tests/analysis/arbitrary_jump_test.py similarity index 96% rename from tests/analysis_tests/arbitrary_jump_test.py rename to tests/analysis/arbitrary_jump_test.py index f2d0f233..e1ff0fd7 100644 --- a/tests/analysis_tests/arbitrary_jump_test.py +++ b/tests/analysis/arbitrary_jump_test.py @@ -37,7 +37,6 @@ def get_global_state(constraints): world_state = WorldState() world_state.put_account(active_account) state = GlobalState(world_state, environment, None, MachineState(gas_limit=8000000)) - print(world_state.balances) state.world_state.transaction_sequence = [ MessageCallTransaction( world_state=world_state, @@ -56,7 +55,6 @@ def get_global_state(constraints): None, ) ) - print(state.world_state.transaction_sequence[0].call_data.calldatasize) state.mstate.stack = [symbol_factory.BitVecSym("jump_dest", 256)] state.world_state.constraints = Constraints(constraints)