Merge pull request #1335 from ConsenSys/refine_detection_architecture

Clean up detection modules
pull/1343/head
JoranHonig 5 years ago committed by GitHub
commit 29e011dc3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      mythril/analysis/__init__.py
  2. 6
      mythril/analysis/module/__init__.py
  3. 94
      mythril/analysis/module/base.py
  4. 81
      mythril/analysis/module/loader.py
  5. 1
      mythril/analysis/module/modules/__init__.py
  6. 16
      mythril/analysis/module/modules/arbitrary_jump.py
  7. 16
      mythril/analysis/module/modules/arbitrary_write.py
  8. 18
      mythril/analysis/module/modules/delegatecall.py
  9. 24
      mythril/analysis/module/modules/dependence_on_predictable_vars.py
  10. 16
      mythril/analysis/module/modules/deprecated_ops.py
  11. 16
      mythril/analysis/module/modules/ether_thief.py
  12. 17
      mythril/analysis/module/modules/exceptions.py
  13. 19
      mythril/analysis/module/modules/external_calls.py
  14. 46
      mythril/analysis/module/modules/integer.py
  15. 23
      mythril/analysis/module/modules/multiple_sends.py
  16. 16
      mythril/analysis/module/modules/state_change_external_calls.py
  17. 16
      mythril/analysis/module/modules/suicide.py
  18. 33
      mythril/analysis/module/modules/unchecked_retval.py
  19. 16
      mythril/analysis/module/modules/user_assertions.py
  20. 50
      mythril/analysis/module/util.py
  21. 0
      mythril/analysis/modules/__init__.py
  22. 79
      mythril/analysis/modules/base.py
  23. 139
      mythril/analysis/security.py
  24. 27
      mythril/analysis/symbolic.py
  25. 2
      mythril/interfaces/cli.py
  26. 10
      mythril/mythril/mythril_analyzer.py
  27. 2
      mythril/plugin/loader.py

@ -0,0 +1 @@
from mythril.analysis.symbolic import SymExecWrapper

@ -0,0 +1,6 @@
from mythril.analysis.module.base import EntryPoint, DetectionModule
from mythril.analysis.module.loader import ModuleLoader
from mythril.analysis.module.util import (
get_detection_module_hooks,
reset_callback_modules,
)

@ -0,0 +1,94 @@
""" Mythril Detection Modules
This module includes an definition of the DetectionModule interface.
DetectionModules implement different analysis rules to find weaknesses and vulnerabilities.
"""
import logging
from typing import List, Set, Optional
from mythril.analysis.report import Issue
from mythril.laser.ethereum.state.global_state import GlobalState
from abc import ABC, abstractmethod
from enum import Enum
# Get logger instance
log = logging.getLogger(__name__)
class EntryPoint(Enum):
""" EntryPoint Enum
This enum is used to signify the entry_point of detection modules.
See also the class documentation of DetectionModule
"""
POST = 1
CALLBACK = 2
class DetectionModule(ABC):
"""The base detection module.
All custom-built detection modules must inherit from this class.
There are several class properties that expose information about the detection modules
- name: The name of the detection module
- swc_id: The SWC ID associated with the weakness that the module detects
- description: A description of the detection module, and what it detects
- entry_point: Mythril can run callback style detection modules, or modules that search the statespace.
[IMPORTANT] POST entry points severely slow down the analysis, try to always use callback style modules
- pre_hooks: A list of instructions to hook the laser vm for (pre execution of the instruction)
- post_hooks: A list of instructions to hook the laser vm for (post execution of the instruction)
"""
name = "Detection Module Name"
swc_id = "SWC-000"
description = "Detection module description"
entry_point = EntryPoint.CALLBACK # type: EntryPoint
pre_hooks = [] # type: List[str]
post_hooks = [] # type: List[str]
def __init__(self) -> None:
self.issues = [] # type: List[Issue]
self.cache = set() # type: Set[int]
def reset_module(self):
""" Resets the storage of this module """
self.issues = []
def execute(self, target: GlobalState) -> Optional[List[Issue]]:
"""The entry point for execution, which is being called by Mythril.
:param target: The target of the analysis, either a global state (callback) or the entire statespace (post)
:return: List of encountered issues
"""
log.debug("Entering analysis module: {}".format(self.__class__.__name__))
result = self._execute(target)
log.debug("Exiting analysis module: {}".format(self.__class__.__name__))
return result
@abstractmethod
def _execute(self, target) -> Optional[List[Issue]]:
"""Module main method (override this)
:param target: The target of the analysis, either a global state (callback) or the entire statespace (post)
:return: List of encountered issues
"""
pass
def __repr__(self) -> str:
return (
"<"
"DetectionModule "
"name={0.name} "
"swc_id={0.swc_id} "
"pre_hooks={0.pre_hooks} "
"post_hooks={0.post_hooks} "
"description={0.description}"
">"
).format(self)

@ -0,0 +1,81 @@
from mythril.analysis.module.base import DetectionModule, EntryPoint
from mythril.support.support_utils import Singleton
from mythril.analysis.module.modules.arbitrary_jump import ArbitraryJump
from mythril.analysis.module.modules.arbitrary_write import ArbitraryStorage
from mythril.analysis.module.modules.delegatecall import DelegateCallModule
from mythril.analysis.module.modules.dependence_on_predictable_vars import (
PredictableDependenceModule,
)
from mythril.analysis.module.modules.deprecated_ops import DeprecatedOperationsModule
from mythril.analysis.module.modules.ether_thief import EtherThief
from mythril.analysis.module.modules.exceptions import ReachableExceptionsModule
from mythril.analysis.module.modules.external_calls import ExternalCalls
from mythril.analysis.module.modules.integer import IntegerOverflowUnderflowModule
from mythril.analysis.module.modules.multiple_sends import MultipleSendsModule
from mythril.analysis.module.modules.state_change_external_calls import StateChange
from mythril.analysis.module.modules.suicide import SuicideModule
from mythril.analysis.module.modules.unchecked_retval import UncheckedRetvalModule
from mythril.analysis.module.modules.user_assertions import UserAssertions
from mythril.analysis.module.base import EntryPoint
from typing import Optional, List
class ModuleLoader(object, metaclass=Singleton):
"""ModuleLoader
The module loader class implements a singleton loader for detection modules.
By default it will load the detection modules in the mythril package.
Additional detection modules can be loaded using the register_module function call implemented by the ModuleLoader
"""
def __init__(self):
self._modules = []
self._register_mythril_modules()
def register_module(self, detection_module: DetectionModule):
"""Registers a detection module with the module loader"""
if not isinstance(detection_module, DetectionModule):
raise ValueError("The passed variable is not a valid detection module")
self._modules.append(detection_module)
def get_detection_modules(
self,
entry_point: Optional[EntryPoint] = None,
white_list: Optional[List[str]] = None,
) -> List[DetectionModule]:
""" Gets registered detection modules
:param entry_point: If specified: only return detection modules with this entry point
:param white_list: If specified: only return whitelisted detection modules
:return: The selected detection modules
"""
result = self._modules[:]
if entry_point:
result = [module for module in result if module.entry_point == entry_point]
if white_list:
result = [module for module in result if module.name in white_list]
return result
def _register_mythril_modules(self):
self._modules.extend(
[
ArbitraryJump(),
ArbitraryStorage(),
DelegateCallModule(),
PredictableDependenceModule(),
DeprecatedOperationsModule(),
EtherThief(),
ReachableExceptionsModule(),
ExternalCalls(),
IntegerOverflowUnderflowModule(),
MultipleSendsModule(),
StateChange(),
SuicideModule(),
UncheckedRetvalModule(),
UserAssertions(),
]
)

@ -1,7 +1,7 @@
"""This module contains the detection code for Arbitrary jumps."""
import logging
from mythril.analysis.solver import get_transaction_sequence, UnsatError
from mythril.analysis.modules.base import DetectionModule, Issue
from mythril.analysis.module.base import DetectionModule, Issue, EntryPoint
from mythril.analysis.swc_data import ARBITRARY_JUMP
from mythril.laser.ethereum.state.global_state import GlobalState
@ -16,15 +16,11 @@ Search for any writes to an arbitrary storage slot
class ArbitraryJump(DetectionModule):
"""This module searches for JUMPs to an arbitrary instruction."""
def __init__(self):
""""""
super().__init__(
name="Jump to an arbitrary line",
swc_id=ARBITRARY_JUMP,
description=DESCRIPTION,
entrypoint="callback",
pre_hooks=["JUMP", "JUMPI"],
)
name = "Jump to an arbitrary line"
swc_id = ARBITRARY_JUMP
description = DESCRIPTION
entry_point = EntryPoint.CALLBACK
pre_hooks = ["JUMP", "JUMPI"]
def reset_module(self):
"""

@ -1,6 +1,6 @@
"""This module contains the detection code for arbitrary storage write."""
import logging
from mythril.analysis.modules.base import DetectionModule
from mythril.analysis.module.base import DetectionModule, EntryPoint
from mythril.analysis.potential_issues import (
get_potential_issues_annotation,
PotentialIssue,
@ -21,15 +21,11 @@ Search for any writes to an arbitrary storage slot
class ArbitraryStorage(DetectionModule):
"""This module searches for a feasible write to an arbitrary storage slot."""
def __init__(self):
""""""
super().__init__(
name="Arbitrary Storage Write",
swc_id=WRITE_TO_ARBITRARY_STORAGE,
description=DESCRIPTION,
entrypoint="callback",
pre_hooks=["SSTORE"],
)
name = "Arbitrary Storage Write"
swc_id = WRITE_TO_ARBITRARY_STORAGE
description = DESCRIPTION
entry_point = EntryPoint.CALLBACK
pre_hooks = ["SSTORE"]
def reset_module(self):
"""

@ -11,7 +11,7 @@ from mythril.laser.ethereum.transaction.symbolic import ACTORS
from mythril.laser.ethereum.transaction.transaction_models import (
ContractCreationTransaction,
)
from mythril.analysis.modules.base import DetectionModule
from mythril.analysis.module.base import DetectionModule, EntryPoint
from mythril.exceptions import UnsatError
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.laser.smt import symbol_factory, UGT
@ -22,15 +22,13 @@ log = logging.getLogger(__name__)
class DelegateCallModule(DetectionModule):
"""This module detects calldata being forwarded using DELEGATECALL."""
def __init__(self) -> None:
""""""
super().__init__(
name="DELEGATECALL Usage in Fallback Function",
swc_id=DELEGATECALL_TO_UNTRUSTED_CONTRACT,
description="Check for invocations of delegatecall(msg.data) in the fallback function.",
entrypoint="callback",
pre_hooks=["DELEGATECALL"],
)
name = "DELEGATECALL Usage in Fallback Function"
swc_id = DELEGATECALL_TO_UNTRUSTED_CONTRACT
description = (
"Check for invocations of delegatecall(msg.data) in the fallback function."
)
entry_point = EntryPoint.CALLBACK
pre_hooks = ["DELEGATECALL"]
def _execute(self, state: GlobalState) -> None:
"""

@ -3,7 +3,7 @@ dependence."""
import logging
from copy import copy
from mythril.analysis.modules.base import DetectionModule
from mythril.analysis.module.base import DetectionModule, EntryPoint
from mythril.analysis.report import Issue
from mythril.exceptions import UnsatError
from mythril.analysis import solver
@ -54,19 +54,15 @@ class PredictableDependenceModule(DetectionModule):
"""This module detects whether control flow decisions are made using predictable
parameters."""
def __init__(self) -> None:
""""""
super().__init__(
name="Dependence of Predictable Variables",
swc_id="{} {}".format(TIMESTAMP_DEPENDENCE, WEAK_RANDOMNESS),
description=(
"Check whether important control flow decisions are influenced by block.coinbase,"
"block.gaslimit, block.timestamp or block.number."
),
entrypoint="callback",
pre_hooks=["BLOCKHASH", "JUMPI"] + final_ops,
post_hooks=["BLOCKHASH"] + predictable_ops,
)
name = "Dependence of Predictable Variables"
swc_id = "{} {}".format(TIMESTAMP_DEPENDENCE, WEAK_RANDOMNESS)
description = (
"Check whether important control flow decisions are influenced by block.coinbase,"
"block.gaslimit, block.timestamp or block.number."
)
entry_point = EntryPoint.CALLBACK
pre_hooks = ["BLOCKHASH", "JUMPI"] + final_ops
post_hooks = ["BLOCKHASH"] + predictable_ops
def _execute(self, state: GlobalState) -> None:
"""

@ -4,7 +4,7 @@ from mythril.analysis.potential_issues import (
get_potential_issues_annotation,
)
from mythril.analysis.swc_data import DEPRECATED_FUNCTIONS_USAGE
from mythril.analysis.modules.base import DetectionModule
from mythril.analysis.module.base import DetectionModule, EntryPoint
from mythril.laser.ethereum.state.global_state import GlobalState
import logging
@ -18,15 +18,11 @@ Check for usage of deprecated opcodes
class DeprecatedOperationsModule(DetectionModule):
"""This module checks for the usage of deprecated op codes."""
def __init__(self):
""""""
super().__init__(
name="Deprecated Operations",
swc_id=DEPRECATED_FUNCTIONS_USAGE,
description=DESCRIPTION,
entrypoint="callback",
pre_hooks=["ORIGIN", "CALLCODE"],
)
name = "Deprecated Operations"
swc_id = DEPRECATED_FUNCTIONS_USAGE
description = DESCRIPTION
entry_point = EntryPoint.CALLBACK
pre_hooks = ["ORIGIN", "CALLCODE"]
def _execute(self, state: GlobalState) -> None:
"""

@ -3,7 +3,7 @@ withdrawal."""
import logging
from copy import copy
from mythril.analysis.modules.base import DetectionModule
from mythril.analysis.module.base import DetectionModule, EntryPoint
from mythril.analysis.potential_issues import (
get_potential_issues_annotation,
PotentialIssue,
@ -35,15 +35,11 @@ class EtherThief(DetectionModule):
"""This module search for cases where Ether can be withdrawn to a user-
specified address."""
def __init__(self):
""""""
super().__init__(
name="Ether Thief",
swc_id=UNPROTECTED_ETHER_WITHDRAWAL,
description=DESCRIPTION,
entrypoint="callback",
pre_hooks=["CALL"],
)
name = "Ether Thief"
swc_id = UNPROTECTED_ETHER_WITHDRAWAL
description = DESCRIPTION
entry_point = EntryPoint.CALLBACK
pre_hooks = ["CALL"]
def reset_module(self):
"""

@ -1,9 +1,8 @@
"""This module contains the detection code for reachable exceptions."""
import logging
import json
from mythril.analysis import solver
from mythril.analysis.modules.base import DetectionModule
from mythril.analysis.module.base import DetectionModule, EntryPoint
from mythril.analysis.report import Issue
from mythril.analysis.swc_data import ASSERT_VIOLATION
from mythril.exceptions import UnsatError
@ -15,15 +14,11 @@ log = logging.getLogger(__name__)
class ReachableExceptionsModule(DetectionModule):
""""""
def __init__(self):
""""""
super().__init__(
name="Reachable Exceptions",
swc_id=ASSERT_VIOLATION,
description="Checks whether any exception states are reachable.",
entrypoint="callback",
pre_hooks=["ASSERT_FAIL"],
)
name = "Reachable Exceptions"
swc_id = ASSERT_VIOLATION
description = "Checks whether any exception states are reachable."
entry_point = EntryPoint.CALLBACK
pre_hooks = ["ASSERT_FAIL"]
def _execute(self, state: GlobalState) -> None:
"""

@ -9,10 +9,7 @@ from mythril.analysis.potential_issues import (
from mythril.analysis.swc_data import REENTRANCY
from mythril.laser.ethereum.state.constraints import Constraints
from mythril.laser.ethereum.transaction.symbolic import ACTORS
from mythril.laser.ethereum.transaction.transaction_models import (
ContractCreationTransaction,
)
from mythril.analysis.modules.base import DetectionModule
from mythril.analysis.module.base import DetectionModule, EntryPoint
from mythril.laser.smt import UGT, symbol_factory, Or, BitVec
from mythril.laser.ethereum.natives import PRECOMPILE_COUNT
from mythril.laser.ethereum.state.global_state import GlobalState
@ -52,15 +49,11 @@ class ExternalCalls(DetectionModule):
"""This module searches for low level calls (e.g. call.value()) that
forward all gas to the callee."""
def __init__(self):
""""""
super().__init__(
name="External calls",
swc_id=REENTRANCY,
description=DESCRIPTION,
entrypoint="callback",
pre_hooks=["CALL"],
)
name = "External calls"
swc_id = REENTRANCY
description = DESCRIPTION
entry_point = EntryPoint.CALLBACK
pre_hooks = ["CALL"]
def _execute(self, state: GlobalState) -> None:
"""

@ -9,7 +9,7 @@ from mythril.analysis.swc_data import INTEGER_OVERFLOW_AND_UNDERFLOW
from mythril.exceptions import UnsatError
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.laser.ethereum.state.annotation import StateAnnotation
from mythril.analysis.modules.base import DetectionModule
from mythril.analysis.module.base import DetectionModule, EntryPoint
from copy import copy
from mythril.laser.smt import (
@ -64,33 +64,31 @@ class OverUnderflowStateAnnotation(StateAnnotation):
class IntegerOverflowUnderflowModule(DetectionModule):
"""This module searches for integer over- and underflows."""
def __init__(self) -> None:
""""""
super().__init__(
name="Integer Overflow and Underflow",
swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW,
description=(
"For every SUB instruction, check if there's a possible state "
"where op1 > op0. For every ADD, MUL instruction, check if "
"there's a possible state where op1 + op0 > 2^32 - 1"
),
entrypoint="callback",
pre_hooks=[
"ADD",
"MUL",
"EXP",
"SUB",
"SSTORE",
"JUMPI",
"STOP",
"RETURN",
"CALL",
],
)
name = "Integer Overflow and Underflow"
swc_id = INTEGER_OVERFLOW_AND_UNDERFLOW
description = (
"For every SUB instruction, check if there's a possible state "
"where op1 > op0. For every ADD, MUL instruction, check if "
"there's a possible state where op1 + op0 > 2^32 - 1"
)
entry_point = EntryPoint.CALLBACK
pre_hooks = [
"ADD",
"MUL",
"EXP",
"SUB",
"SSTORE",
"JUMPI",
"STOP",
"RETURN",
"CALL",
]
def __init__(self) -> None:
"""
Cache satisfiability of overflow constraints
"""
super().__init__()
self._ostates_satisfiable = set() # type: Set[GlobalState]
self._ostates_unsatisfiable = set() # type: Set[GlobalState]

@ -6,7 +6,7 @@ from typing import cast, List
from mythril.analysis.report import Issue
from mythril.analysis.solver import get_transaction_sequence, UnsatError
from mythril.analysis.swc_data import MULTIPLE_SENDS
from mythril.analysis.modules.base import DetectionModule
from mythril.analysis.module.base import DetectionModule, EntryPoint
from mythril.laser.ethereum.state.annotation import StateAnnotation
from mythril.laser.ethereum.state.global_state import GlobalState
import logging
@ -27,22 +27,11 @@ class MultipleSendsAnnotation(StateAnnotation):
class MultipleSendsModule(DetectionModule):
"""This module checks for multiple sends in a single transaction."""
def __init__(self):
""""""
super().__init__(
name="Multiple Sends",
swc_id=MULTIPLE_SENDS,
description="Check for multiple sends in a single transaction",
entrypoint="callback",
pre_hooks=[
"CALL",
"DELEGATECALL",
"STATICCALL",
"CALLCODE",
"RETURN",
"STOP",
],
)
name = "Multiple Sends"
swc_id = MULTIPLE_SENDS
description = "Check for multiple sends in a single transaction"
entry_point = EntryPoint.CALLBACK
pre_hooks = ["CALL", "DELEGATECALL", "STATICCALL", "CALLCODE", "RETURN", "STOP"]
def _execute(self, state: GlobalState) -> None:
if state.get_current_instruction()["address"] in self.cache:

@ -3,7 +3,7 @@ from mythril.analysis.potential_issues import (
get_potential_issues_annotation,
)
from mythril.analysis.swc_data import REENTRANCY
from mythril.analysis.modules.base import DetectionModule
from mythril.analysis.module.base import DetectionModule, EntryPoint
from mythril.laser.ethereum.state.constraints import Constraints
from mythril.laser.smt import symbol_factory, UGT, BitVec, Or
from mythril.laser.ethereum.state.global_state import GlobalState
@ -99,15 +99,11 @@ class StateChange(DetectionModule):
"""This module searches for state change after low level calls (e.g. call.value()) that
forward gas to the callee."""
def __init__(self):
""""""
super().__init__(
name="State Change After External calls",
swc_id=REENTRANCY,
description=DESCRIPTION,
entrypoint="callback",
pre_hooks=CALL_LIST + STATE_READ_WRITE_LIST,
)
name = "State Change After External calls"
swc_id = REENTRANCY
description = DESCRIPTION
entry_point = EntryPoint.CALLBACK
pre_hooks = CALL_LIST + STATE_READ_WRITE_LIST
def _execute(self, state: GlobalState) -> None:
if state.get_current_instruction()["address"] in self.cache:

@ -2,7 +2,7 @@ from mythril.analysis import solver
from mythril.analysis.report import Issue
from mythril.analysis.swc_data import UNPROTECTED_SELFDESTRUCT
from mythril.exceptions import UnsatError
from mythril.analysis.modules.base import DetectionModule
from mythril.analysis.module.base import DetectionModule, EntryPoint
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.laser.ethereum.transaction.symbolic import ACTORS
from mythril.laser.ethereum.transaction.transaction_models import (
@ -23,14 +23,14 @@ class SuicideModule(DetectionModule):
"""This module checks if the contact can be 'accidentally' killed by
anyone."""
name = "Unprotected Selfdestruct"
swc_id = UNPROTECTED_SELFDESTRUCT
description = DESCRIPTION
entry_point = EntryPoint.CALLBACK
pre_hooks = ["SUICIDE"]
def __init__(self):
super().__init__(
name="Unprotected Selfdestruct",
swc_id=UNPROTECTED_SELFDESTRUCT,
description=DESCRIPTION,
entrypoint="callback",
pre_hooks=["SUICIDE"],
)
super().__init__()
self._cache_address = {}
def reset_module(self):

@ -6,7 +6,7 @@ from typing import cast, List, Union, Mapping
from mythril.analysis import solver
from mythril.analysis.report import Issue
from mythril.analysis.swc_data import UNCHECKED_RET_VAL
from mythril.analysis.modules.base import DetectionModule
from mythril.analysis.module.base import DetectionModule, EntryPoint
from mythril.exceptions import UnsatError
from mythril.laser.smt.bitvec import BitVec
@ -31,23 +31,20 @@ class UncheckedRetvalAnnotation(StateAnnotation):
class UncheckedRetvalModule(DetectionModule):
"""A detection module to test whether CALL return value is checked."""
def __init__(self):
super().__init__(
name="Unchecked Return Value",
swc_id=UNCHECKED_RET_VAL,
description=(
"Test whether CALL return value is checked. "
"For direct calls, the Solidity compiler auto-generates this check. E.g.:\n"
" Alice c = Alice(address);\n"
" c.ping(42);\n"
"Here the CALL will be followed by IZSERO(retval), if retval = ZERO then state is reverted. "
"For low-level-calls this check is omitted. E.g.:\n"
' c.call.value(0)(bytes4(sha3("ping(uint256)")),1);'
),
entrypoint="callback",
pre_hooks=["STOP", "RETURN"],
post_hooks=["CALL", "DELEGATECALL", "STATICCALL", "CALLCODE"],
)
name = "Unchecked Return Value"
swc_id = UNCHECKED_RET_VAL
description = (
"Test whether CALL return value is checked. "
"For direct calls, the Solidity compiler auto-generates this check. E.g.:\n"
" Alice c = Alice(address);\n"
" c.ping(42);\n"
"Here the CALL will be followed by IZSERO(retval), if retval = ZERO then state is reverted. "
"For low-level-calls this check is omitted. E.g.:\n"
' c.call.value(0)(bytes4(sha3("ping(uint256)")),1);'
)
entry_point = EntryPoint.CALLBACK
pre_hooks = ["STOP", "RETURN"]
post_hooks = ["CALL", "DELEGATECALL", "STATICCALL", "CALLCODE"]
def _execute(self, state: GlobalState) -> None:
"""

@ -6,7 +6,7 @@ from mythril.analysis.potential_issues import (
get_potential_issues_annotation,
)
from mythril.analysis.swc_data import ASSERT_VIOLATION
from mythril.analysis.modules.base import DetectionModule
from mythril.analysis.module.base import DetectionModule, EntryPoint
from mythril.laser.ethereum.state.global_state import GlobalState
import logging
import eth_abi
@ -28,15 +28,11 @@ assertion_failed_hash = (
class UserAssertions(DetectionModule):
"""This module searches for user supplied exceptions: emit AssertionFailed("Error")."""
def __init__(self):
""""""
super().__init__(
name="External calls",
swc_id=ASSERT_VIOLATION,
description=DESCRIPTION,
entrypoint="callback",
pre_hooks=["LOG1"],
)
name = "User assertions"
swc_id = ASSERT_VIOLATION
description = DESCRIPTION
entry_point = EntryPoint.CALLBACK
pre_hooks = ["LOG1"]
def _execute(self, state: GlobalState) -> None:
"""

@ -0,0 +1,50 @@
from collections import defaultdict
from typing import List, Optional, Callable, Mapping, Dict
import logging
from mythril.support.opcodes import opcodes
from mythril.analysis.module.base import DetectionModule, EntryPoint
from mythril.analysis.module.loader import ModuleLoader
log = logging.getLogger(__name__)
OP_CODE_LIST = [c[0] for _, c in opcodes.items()]
def get_detection_module_hooks(
modules: List[DetectionModule], hook_type="pre"
) -> Dict[str, List[Callable]]:
""" Gets a dictionary with the hooks for the passed detection modules
:param modules: The modules for which to retrieve hooks
:param hook_type: The type of hooks to retrieve (default: "pre")
:return: Dictionary with discovered hooks
"""
hook_dict = defaultdict(list) # type: Mapping[str, List[Callable]]
for module in modules:
hooks = module.pre_hooks if hook_type == "pre" else module.post_hooks
for op_code in map(lambda x: x.upper(), hooks):
# A hook can be either OP_CODE or START*
# When an entry like the second is encountered we hook all opcodes that start with START
if op_code in OP_CODE_LIST:
hook_dict[op_code].append(module.execute)
elif op_code.endswith("*"):
to_register = filter(lambda x: x.startswith(op_code[:-1]), OP_CODE_LIST)
for actual_hook in to_register:
hook_dict[actual_hook].append(module.execute)
else:
log.error(
"Encountered invalid hook opcode %s in module %s",
op_code,
module.name,
)
return dict(hook_dict)
def reset_callback_modules(module_names: Optional[List[str]] = None):
"""Clean the issue records of every callback-based module."""
modules = ModuleLoader().get_detection_modules(EntryPoint.CALLBACK, module_names)
for module in modules:
module.reset_module()

@ -1,79 +0,0 @@
"""This module contains the base class for all user-defined detection
modules."""
import logging
from typing import List, Set
from mythril.analysis.report import Issue
log = logging.getLogger(__name__)
class DetectionModule:
"""The base detection module.
All custom-built detection modules must inherit from this class.
"""
def __init__(
self,
name: str,
swc_id: str,
description: str,
entrypoint: str = "post",
pre_hooks: List[str] = None,
post_hooks: List[str] = None,
) -> None:
self.name = name
self.swc_id = swc_id
self.pre_hooks = pre_hooks if pre_hooks else []
self.post_hooks = post_hooks if post_hooks else []
self.description = description
if entrypoint not in ("post", "callback"):
log.error(
"Invalid entrypoint in module %s, must be one of {post, callback}",
self.name,
)
self.entrypoint = entrypoint
self.issues = [] # type: List[Issue]
self.cache = set() # type: Set[int]
def reset_module(self):
"""
Resets issues
"""
self.issues = []
def execute(self, statespace) -> None:
"""The entry point for execution, which is being called by Mythril.
:param statespace:
:return:
"""
log.debug("Entering analysis module: {}".format(self.__class__.__name__))
self._execute(statespace)
log.debug("Exiting analysis module: {}".format(self.__class__.__name__))
def _execute(self, statespace):
"""Module main method (override this)
:param statespace:
:return:
"""
raise NotImplementedError()
def __repr__(self) -> str:
return (
"<"
"DetectionModule "
"name={0.name} "
"swc_id={0.swc_id} "
"pre_hooks={0.pre_hooks} "
"post_hooks={0.post_hooks} "
"description={0.description}"
">"
).format(self)

@ -1,131 +1,46 @@
"""This module contains functionality for hooking in detection modules and
executing them."""
from collections import defaultdict
from mythril.support.opcodes import opcodes
from mythril.analysis import modules
import pkgutil
import importlib.util
from mythril.analysis.module import ModuleLoader, reset_callback_modules
from mythril.analysis.module.base import EntryPoint
from mythril.analysis.report import Issue
from typing import Optional, List
import logging
import os
import sys
log = logging.getLogger(__name__)
OPCODE_LIST = [c[0] for _, c in opcodes.items()]
def reset_callback_modules(module_names=(), custom_modules_directory=""):
"""Clean the issue records of every callback-based module."""
modules = get_detection_modules("callback", module_names, custom_modules_directory)
for module in modules:
module.detector.reset_module()
def get_detection_module_hooks(modules, hook_type="pre", custom_modules_directory=""):
hook_dict = defaultdict(list)
_modules = get_detection_modules(
entrypoint="callback",
include_modules=modules,
custom_modules_directory=custom_modules_directory,
)
for module in _modules:
hooks = (
module.detector.pre_hooks
if hook_type == "pre"
else module.detector.post_hooks
)
for op_code in map(lambda x: x.upper(), hooks):
if op_code in OPCODE_LIST:
hook_dict[op_code].append(module.detector.execute)
elif op_code.endswith("*"):
to_register = filter(lambda x: x.startswith(op_code[:-1]), OPCODE_LIST)
for actual_hook in to_register:
hook_dict[actual_hook].append(module.detector.execute)
else:
log.error(
"Encountered invalid hook opcode %s in module %s",
op_code,
module.detector.name,
)
return dict(hook_dict)
def get_detection_modules(entrypoint, include_modules=(), custom_modules_directory=""):
"""
:param entrypoint:
:param include_modules:
:return:
"""
module = importlib.import_module("mythril.analysis.modules.base")
module.log.setLevel(log.level)
include_modules = list(include_modules)
_modules = []
for loader, module_name, _ in pkgutil.walk_packages(modules.__path__):
if include_modules and module_name not in include_modules:
continue
if module_name != "base":
module = importlib.import_module("mythril.analysis.modules." + module_name)
module.log.setLevel(log.level)
if module.detector.entrypoint == entrypoint:
_modules.append(module)
if custom_modules_directory:
custom_modules_path = os.path.abspath(custom_modules_directory)
if custom_modules_path not in sys.path:
sys.path.append(custom_modules_path)
for loader, module_name, _ in pkgutil.walk_packages([custom_modules_path]):
if include_modules and module_name not in include_modules:
continue
def retrieve_callback_issues(white_list: Optional[List[str]] = None) -> List[Issue]:
""" Get the issues discovered by callback type detection modules"""
issues = [] # type: List[Issue]
for module in ModuleLoader().get_detection_modules(
entry_point=EntryPoint.CALLBACK, white_list=white_list
):
log.debug("Retrieving results for " + module.name)
issues += module.issues
if module_name != "base":
module = importlib.import_module(module_name, custom_modules_path)
module.log.setLevel(log.level)
if module.detector.entrypoint == entrypoint:
_modules.append(module)
reset_callback_modules(module_names=white_list)
log.info("Found %s detection modules", len(_modules))
return _modules
return issues
def fire_lasers(statespace, module_names=(), custom_modules_directory=""):
"""
def fire_lasers(statespace, white_list: Optional[List[str]] = None) -> List[Issue]:
""" Fire lasers at analysed statespace object
:param statespace:
:param module_names:
:return:
:param statespace: Symbolic statespace to analyze
:param white_list: Optionally whitelist modules to use for the analysis
:return: Discovered issues
"""
log.info("Starting analysis")
issues = []
for module in get_detection_modules(
entrypoint="post",
include_modules=module_names,
custom_modules_directory=custom_modules_directory,
):
log.info("Executing " + module.detector.name)
issues += module.detector.execute(statespace)
issues += retrieve_callback_issues(module_names, custom_modules_directory)
return issues
def retrieve_callback_issues(module_names=(), custom_modules_directory=""):
issues = []
for module in get_detection_modules(
entrypoint="callback",
include_modules=module_names,
custom_modules_directory=custom_modules_directory,
issues = [] # type: List[Issue]
for module in ModuleLoader().get_detection_modules(
entry_point=EntryPoint.POST, white_list=white_list
):
log.debug("Retrieving results for " + module.detector.name)
issues += module.detector.issues
log.info("Executing " + module.name)
issues += module.execute(statespace)
reset_callback_modules(
module_names=module_names, custom_modules_directory=custom_modules_directory
)
issues += retrieve_callback_issues(white_list)
return issues

@ -1,8 +1,12 @@
"""This module contains a wrapper around LASER for extended analysis
purposes."""
from mythril.analysis.security import get_detection_module_hooks, get_detection_modules
from mythril.analysis.module import (
EntryPoint,
DetectionModule,
ModuleLoader,
get_detection_module_hooks,
)
from mythril.laser.ethereum import svm
from mythril.laser.ethereum.iprof import InstructionProfiler
from mythril.laser.ethereum.state.account import Account
@ -26,7 +30,7 @@ from mythril.laser.ethereum.strategy.extensions.bounded_loops import (
BoundedLoopsStrategy,
)
from mythril.laser.smt import symbol_factory, BitVec
from typing import Union, List, Type, Optional
from typing import Union, List, Type, Optional, Tuple
from mythril.solidity.soliditycontract import EVMContract, SolidityContract
from .ops import Call, VarType, get_variable
@ -49,7 +53,7 @@ class SymExecWrapper:
loop_bound: int = 3,
create_timeout: Optional[int] = None,
transaction_count: int = 2,
modules=(),
modules: Optional[List[str]] = None,
compulsory_statespace: bool = True,
iprof: Optional[InstructionProfiler] = None,
disable_dependency_pruning: bool = False,
@ -100,7 +104,7 @@ class SymExecWrapper:
requires_statespace = (
compulsory_statespace
or len(get_detection_modules("post", modules, custom_modules_directory)) > 0
or len(ModuleLoader().get_detection_modules(EntryPoint.POST, modules)) > 0
)
if not contract.creation_code:
self.accounts = {hex(ACTORS.attacker.value): attacker_account}
@ -140,20 +144,17 @@ class SymExecWrapper:
world_state.put_account(account)
if run_analysis_modules:
analysis_modules = ModuleLoader().get_detection_modules(
EntryPoint.CALLBACK, modules
)
self.laser.register_hooks(
hook_type="pre",
hook_dict=get_detection_module_hooks(
modules,
hook_type="pre",
custom_modules_directory=custom_modules_directory,
),
hook_dict=get_detection_module_hooks(analysis_modules, hook_type="pre"),
)
self.laser.register_hooks(
hook_type="post",
hook_dict=get_detection_module_hooks(
modules,
hook_type="post",
custom_modules_directory=custom_modules_directory,
analysis_modules, hook_type="post"
),
)

@ -719,7 +719,7 @@ def execute_command(
report = analyzer.fire_lasers(
modules=[m.strip() for m in args.modules.strip().split(",")]
if args.modules
else [],
else None,
transaction_count=args.transaction_count,
)
outputs = {

@ -171,22 +171,18 @@ class MythrilAnalyzer:
enable_coverage_strategy=self.enable_coverage_strategy,
custom_modules_directory=self.custom_modules_directory,
)
issues = fire_lasers(sym, modules, self.custom_modules_directory)
issues = fire_lasers(sym, modules)
except KeyboardInterrupt:
log.critical("Keyboard Interrupt")
if self.iprof is not None:
log.info("Instruction Statistics:\n{}".format(self.iprof))
issues = retrieve_callback_issues(
modules, self.custom_modules_directory
)
issues = retrieve_callback_issues(modules)
except Exception:
log.critical(
"Exception occurred, aborting analysis. Please report this issue to the Mythril GitHub page.\n"
+ traceback.format_exc()
)
issues = retrieve_callback_issues(
modules, self.custom_modules_directory
)
issues = retrieve_callback_issues(modules)
exceptions.append(traceback.format_exc())
if self.iprof is not None:
log.info("Instruction Statistics:\n{}".format(self.iprof))

@ -1,4 +1,4 @@
from mythril.analysis.modules.base import DetectionModule
from mythril.analysis.module import DetectionModule
from mythril.plugin.interface import MythrilCLIPlugin, MythrilPlugin
from mythril.support.support_utils import Singleton

Loading…
Cancel
Save