Merge with develop

pull/946/head
Nikhil Parasaram 6 years ago
commit d74545f0a7
  1. 55
      .circleci/config.yml
  2. 2
      CONTRIBUTING.md
  3. 12
      Dockerfile
  4. 19
      README.md
  5. 7
      docs/source/module-list.rst
  6. 5
      mythril/analysis/call_helpers.py
  7. 23
      mythril/analysis/callgraph.py
  8. 143
      mythril/analysis/modules/delegatecall.py
  9. 305
      mythril/analysis/modules/dependence_on_predictable_vars.py
  10. 10
      mythril/analysis/modules/deprecated_ops.py
  11. 128
      mythril/analysis/modules/dos.py
  12. 7
      mythril/analysis/modules/ether_thief.py
  13. 11
      mythril/analysis/modules/exceptions.py
  14. 34
      mythril/analysis/modules/external_calls.py
  15. 26
      mythril/analysis/modules/integer.py
  16. 42
      mythril/analysis/modules/multiple_sends.py
  17. 172
      mythril/analysis/modules/state_change_external_calls.py
  18. 13
      mythril/analysis/modules/suicide.py
  19. 198
      mythril/analysis/modules/transaction_order_dependence.py
  20. 7
      mythril/analysis/modules/unchecked_retval.py
  21. 37
      mythril/analysis/report.py
  22. 49
      mythril/analysis/symbolic.py
  23. 8
      mythril/disassembler/disassembly.py
  24. 29
      mythril/ethereum/evmcontract.py
  25. 2
      mythril/ethereum/util.py
  26. 11
      mythril/interfaces/cli.py
  27. 23
      mythril/laser/ethereum/call.py
  28. 46
      mythril/laser/ethereum/instructions.py
  29. 21
      mythril/laser/ethereum/plugins/__init__.py
  30. 7
      mythril/laser/ethereum/plugins/implementations/__init__.py
  31. 4
      mythril/laser/ethereum/plugins/implementations/benchmark.py
  32. 3
      mythril/laser/ethereum/plugins/implementations/coverage/__init__.py
  33. 98
      mythril/laser/ethereum/plugins/implementations/coverage/coverage_plugin.py
  34. 43
      mythril/laser/ethereum/plugins/implementations/coverage/coverage_strategy.py
  35. 3
      mythril/laser/ethereum/plugins/implementations/mutation_pruner.py
  36. 23
      mythril/laser/ethereum/plugins/plugin.py
  37. 32
      mythril/laser/ethereum/plugins/plugin_factory.py
  38. 38
      mythril/laser/ethereum/plugins/plugin_loader.py
  39. 69
      mythril/laser/ethereum/state/account.py
  40. 3
      mythril/laser/ethereum/state/environment.py
  41. 2
      mythril/laser/ethereum/state/global_state.py
  42. 65
      mythril/laser/ethereum/state/world_state.py
  43. 4
      mythril/laser/ethereum/strategy/__init__.py
  44. 148
      mythril/laser/ethereum/svm.py
  45. 454
      mythril/laser/ethereum/taint_analysis.py
  46. 33
      mythril/laser/ethereum/transaction/symbolic.py
  47. 23
      mythril/laser/ethereum/transaction/transaction_models.py
  48. 7
      mythril/laser/smt/bool.py
  49. 721
      mythril/mythril.py
  50. 2
      mythril/mythril/mythril_analyzer.py
  51. 54
      mythril/solidity/soliditycontract.py
  52. 6
      mythril/support/loader.py
  53. 26
      mythril/support/source_support.py
  54. 19
      mythril/support/support_utils.py
  55. 2
      mythril/version.py
  56. 5
      requirements.txt
  57. 5
      setup.py
  58. 25
      tests/laser/evm_testsuite/evm_test.py
  59. 12
      tests/laser/test_transaction.py
  60. 6
      tests/laser/transaction/symbolic_test.py
  61. 19
      tests/native_test.py
  62. 4
      tests/report_test.py
  63. 30
      tests/taint_mutate_stack_test.py
  64. 36
      tests/taint_record_test.py
  65. 35
      tests/taint_result_test.py
  66. 99
      tests/taint_runner_test.py
  67. 6
      tests/testdata/outputs_expected/calls.sol.o.graph.html
  68. 31
      tests/testdata/outputs_expected/calls.sol.o.json
  69. 35
      tests/testdata/outputs_expected/calls.sol.o.jsonv2
  70. 37
      tests/testdata/outputs_expected/calls.sol.o.markdown
  71. 35
      tests/testdata/outputs_expected/calls.sol.o.text
  72. 36
      tests/testdata/outputs_expected/environments.sol.o.json
  73. 34
      tests/testdata/outputs_expected/environments.sol.o.markdown
  74. 30
      tests/testdata/outputs_expected/environments.sol.o.text
  75. 4
      tests/testdata/outputs_expected/ether_send.sol.o.graph.html
  76. 29
      tests/testdata/outputs_expected/ether_send.sol.o.json
  77. 41
      tests/testdata/outputs_expected/ether_send.sol.o.jsonv2
  78. 28
      tests/testdata/outputs_expected/ether_send.sol.o.markdown
  79. 23
      tests/testdata/outputs_expected/ether_send.sol.o.text
  80. 13
      tests/testdata/outputs_expected/kinds_of_calls.sol.o.json
  81. 17
      tests/testdata/outputs_expected/kinds_of_calls.sol.o.jsonv2
  82. 13
      tests/testdata/outputs_expected/kinds_of_calls.sol.o.markdown
  83. 11
      tests/testdata/outputs_expected/kinds_of_calls.sol.o.text
  84. 4
      tests/testdata/outputs_expected/metacoin.sol.o.jsonv2
  85. 4
      tests/testdata/outputs_expected/nonascii.sol.o.jsonv2
  86. 13
      tests/testdata/outputs_expected/overflow.sol.o.json
  87. 17
      tests/testdata/outputs_expected/overflow.sol.o.jsonv2
  88. 13
      tests/testdata/outputs_expected/overflow.sol.o.markdown
  89. 11
      tests/testdata/outputs_expected/overflow.sol.o.text
  90. 6
      tests/testdata/outputs_expected/returnvalue.sol.o.graph.html
  91. 12
      tests/testdata/outputs_expected/returnvalue.sol.o.json
  92. 12
      tests/testdata/outputs_expected/returnvalue.sol.o.jsonv2
  93. 16
      tests/testdata/outputs_expected/returnvalue.sol.o.markdown
  94. 16
      tests/testdata/outputs_expected/returnvalue.sol.o.text
  95. 166
      tests/testdata/outputs_expected/rubixi.sol.o.json
  96. 238
      tests/testdata/outputs_expected/rubixi.sol.o.markdown
  97. 177
      tests/testdata/outputs_expected/rubixi.sol.o.text
  98. 13
      tests/testdata/outputs_expected/underflow.sol.o.json
  99. 17
      tests/testdata/outputs_expected/underflow.sol.o.jsonv2
  100. 13
      tests/testdata/outputs_expected/underflow.sol.o.markdown
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,5 +1,9 @@
defaults: &defaults
docker:
# TODO: Try to replace this with `mythril/mythx-ci` image. That image is
# used for new integration testing, and it is build on top of the previous
# `mythril/dev_test_environment:0.0.43`, though a bit newer versions, thus
# there is a chance that it breaks some small things.
- image: mythril/dev_test_environment:0.0.43
version: 2
@ -79,6 +83,39 @@ jobs:
command: |
curl -I -X POST -H -d "https://circleci.com/api/v1/project/${ORGANIZATION}/${WEBHOOK_PROJECT}/tree/master?circle-token=${CIRCLE_TOKEN}" | head -n 1 | cut -d$' ' -f2
integration_tests:
docker:
- image: mythril/mythx-ci
working_directory: /home
steps:
- checkout:
path: /home/mythril-classic
- run:
name: Builds `mythril-classic`
command: cd mythril-classic && python3 setup.py install
- run:
name: Installs other MythX components
command: |
./install-mythx-components.sh pythx edelweiss harvey-cli \
harvey-tyro maestro maru maru-tyro mythril-api \
mythril-tyro tyro
- run:
background: true
name: Launches MythX platform
command: ./launch-mythx.sh
- run:
name: Waits for MythX to spin-up
command: sleep 15s
- run:
name: Quick Edelweiss test
command: /home/run-edelweiss-test.sh CircleCI/latest.quick.csv 5
# TODO: Temporary disabled
# - run:
# name: Full Edelweiss test
# environment:
# MYTHX_API_FULL_MODE: true
# command: /home/run-edelweiss-test.sh CircleCI/latest.full.csv
pypi_release:
<<: *defaults
@ -129,6 +166,16 @@ workflows:
filters:
tags:
only: /.*/
- integration_tests:
filters:
branches:
only:
- develop
- master
tags:
only: /v[0-9]+(\.[0-9]+)*/
requires:
- test
- pypi_release:
filters:
branches:
@ -136,13 +183,13 @@ workflows:
tags:
only: /v[0-9]+(\.[0-9]+)*/
requires:
- test
- integration_tests
- dockerhub_dev_release:
filters:
branches:
only: develop
# requires:
# - test
requires:
- integration_tests
- dockerhub_release:
filters:
branches:
@ -150,4 +197,4 @@ workflows:
tags:
only: /v[0-9]+(\.[0-9]+)*/
requires:
- test
- integration_tests

@ -10,7 +10,7 @@ If you have a small question or aren't sure if you should create an issue for yo
# Coding
If you want to help out with the development of Mythril then you can take a look at our issues or [Waffle board](https://waffle.io/ConsenSys/mythril).
Before you start working on an issue pkease stop by on Discord to message a collaborator, this way we can assign you to the issue making sure nobody does double work. We can also provide you with support through Discord if there are any questions during the development process.
Before you start working on an issue please stop by on Discord to message a collaborator, this way we can assign you to the issue making sure nobody does double work. We can also provide you with support through Discord if there are any questions during the development process.
## New ideas
Before you start working on a new idea, it's useful to create an issue on GitHub, that way we know what you want to implement and that you are working on it. Additionally, it might happen that your feature does not fit with our roadmap, in which case it would be unfortunate if you have already spent some time working on it.

@ -1,5 +1,8 @@
FROM ubuntu:bionic
# Space-separated version string without leading 'v' (e.g. "0.4.21 0.4.22")
ARG SOLC
RUN apt-get update \
&& apt-get install -y \
libsqlite3-0 \
@ -36,5 +39,12 @@ COPY . /opt/mythril
RUN cd /opt/mythril \
&& python setup.py install
COPY ./mythril/support/assets/signatures.db /root/.mythril/signatures.db
RUN useradd -m mythril
USER mythril
WORKDIR /home/mythril
RUN ( [ ! -z "${SOLC}" ] && set -e && for ver in $SOLC; do python -m solc.install v${ver}; done ) || true
COPY ./mythril/support/assets/signatures.db /home/mythril/.mythril/signatures.db
ENTRYPOINT ["/usr/local/bin/myth"]

@ -1,4 +1,4 @@
# Mythril Classic
# Mythril
<p align="center">
<img src="/static/mythril_new.png" height="320px"/>
@ -6,16 +6,13 @@
[![Discord](https://img.shields.io/discord/481002907366588416.svg)](https://discord.gg/E3YrVtG)
[![PyPI](https://badge.fury.io/py/mythril.svg)](https://pypi.python.org/pypi/mythril)
[![Read the Docs](https://readthedocs.org/projects/mythril-classic/badge/?version=master)](https://mythril-classic.readthedocs.io/en/master/)
![Master Build Status](https://img.shields.io/circleci/project/github/ConsenSys/mythril-classic/master.svg)
[![Waffle.io - Columns and their card count](https://badge.waffle.io/ConsenSys/mythril-classic.svg?columns=In%20Progress)](https://waffle.io/ConsenSys/mythril-classic/)
[![Read the Docs](https://readthedocs.org/projects/mythril/badge/?version=master)](https://mythril.readthedocs.io/en/master/)
![Master Build Status](https://img.shields.io/circleci/project/github/ConsenSys/mythril/master.svg)
[![Sonarcloud - Maintainability](https://sonarcloud.io/api/project_badges/measure?project=mythril&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=mythril)
[![Downloads](https://pepy.tech/badge/mythril)](https://pepy.tech/project/mythril)
[![Pypi Installs](https://pepy.tech/badge/mythril)](https://pepy.tech/project/mythril)
[![DockerHub Pulls](https://img.shields.io/docker/pulls/mythril/myth.svg?label=DockerHub&nbsp;Pulls)](https://cloud.docker.com/u/mythril/repository/docker/mythril/myth)
Mythril Classic is an open-source security analysis tool for Ethereum smart contracts. It uses symbolic analysis, taint analysis and control flow checking to detect a variety of security vulnerabilities. It's also an experimental tool designed for security pros. If you a smart contract developer you might prefer easier-to-use tools such as:
- [Sabre](https://github.com/b-mueller/sabre)
- [Truffle Security](https://github.com/ConsenSys/truffle-security)
Mythril is an open-source security analysis tool for Ethereum smart contracts. It uses symbolic analysis, taint analysis and control flow checking to detect a variety of security vulnerabilities. It's also used in the backend of [MythX API](https://mythx.io). If you are a smart contract developer, we recommend using [MythX tools](https://github.com/b-mueller/awesome-mythx-smart-contract-security) which are optimized for usability and cover a wider range of security issues.
Whether you want to contribute, need support, or want to learn what we have cooking for the future, our [Discord server](https://discord.gg/E3YrVtG) will serve your needs.
@ -37,12 +34,12 @@ See the [Wiki](https://github.com/ConsenSys/mythril/wiki/Installation-and-Setup)
## Usage
Instructions for using Mythril Classic are found on the [Wiki](https://github.com/ConsenSys/mythril-classic/wiki).
Instructions for using Mythril are found on the [Wiki](https://github.com/ConsenSys/mythril/wiki).
For support or general discussions please join the Mythril community on [Discord](https://discord.gg/E3YrVtG).
## Bulding the Documentation
Mythril Classic's documentation is contained in the `docs` folder and is published to [Read the Docs](https://mythril-classic.readthedocs.io/en/master/). It is based on Sphinx and can be built using the Makefile contained in the subdirectory:
Mythril's documentation is contained in the `docs` folder and is published to [Read the Docs](https://mythril.readthedocs.io/en/master/). It is based on Sphinx and can be built using the Makefile contained in the subdirectory:
```
cd docs

@ -35,7 +35,7 @@ The `exceptions module <https://github.com/ConsenSys/mythril-classic/blob/develo
External Calls
**************
The `external calls module <https://github.com/ConsenSys/mythril-classic/blob/develop/mythril/analysis/modules/external_calls.py>`_ detects `SWC-117 (Reentrancy) <https://smartcontractsecurity.github.io/SWC-registry/docs/SWC-117>`_ by detecting state changes after calls to external contracts.
The `external calls module <https://github.com/ConsenSys/mythril-classic/blob/develop/mythril/analysis/modules/external_calls.py>`_ warns about `SWC-117 (Reentrancy) <https://smartcontractsecurity.github.io/SWC-registry/docs/SWC-107>`_ by detecting calls to external contracts.
*******
Integer
@ -55,11 +55,12 @@ Suicide
The `suicide module <https://github.com/ConsenSys/mythril-classic/blob/develop/mythril/analysis/modules/suicide.py>`_ detects `SWC-106 (Unprotected SELFDESTRUCT) <https://smartcontractsecurity.github.io/SWC-registry/docs/SWC-106>`_.
****************************
Transaction Order Dependence
State Change External Calls
****************************
The `transaction order dependence module <https://github.com/ConsenSys/mythril-classic/blob/develop/mythril/analysis/modules/transaction_order_dependence.py>`_ detects `SWC-114 (Transaction Order Dependence / Race Conditions) <https://smartcontractsecurity.github.io/SWC-registry/docs/SWC-114>`_.
The `state change external calls module <https://github.com/ConsenSys/mythril-classic/blob/develop/mythril/analysis/modules/state_change_external_calls.py>`_ detects `SWC-107 (Reentrancy) <https://smartcontractsecurity.github.io/SWC-registry/docs/SWC-107>`_ by detecting state change after calls to an external contract.
****************
Unchecked Retval

@ -15,10 +15,9 @@ def get_call_from_state(state: GlobalState) -> Union[Call, None]:
instruction = state.get_current_instruction()
op = instruction["opcode"]
stack = state.mstate.stack
if op in ("CALL", "CALLCODE"):
if op in ("CALL", "CALLCODE", "STATICCALL"):
gas, to, value, meminstart, meminsz, memoutstart, memoutsz = (
get_variable(stack[-1]),
get_variable(stack[-2]),
@ -29,7 +28,7 @@ def get_call_from_state(state: GlobalState) -> Union[Call, None]:
get_variable(stack[-7]),
)
if to.type == VarType.CONCRETE and to.val < 5:
if to.type == VarType.CONCRETE and 0 < to.val < 5:
return None
if meminstart.type == VarType.CONCRETE and meminsz.type == VarType.CONCRETE:

@ -125,7 +125,7 @@ phrack_color = {
}
def extract_nodes(statespace, color_map):
def extract_nodes(statespace):
"""
:param statespace:
@ -133,6 +133,7 @@ def extract_nodes(statespace, color_map):
:return:
"""
nodes = []
color_map = {}
for node_key in statespace.nodes:
node = statespace.nodes[node_key]
instructions = [state.get_current_instruction() for state in node.states]
@ -164,10 +165,16 @@ def extract_nodes(statespace, color_map):
else "\n".join(code_split[:6]) + "\n(click to expand +)"
)
if node.get_cfg_dict()["contract_name"] not in color_map.keys():
color = default_colors[len(color_map) % len(default_colors)]
color_map[node.get_cfg_dict()["contract_name"]] = color
nodes.append(
{
"id": str(node_key),
"color": color_map[node.get_cfg_dict()["contract_name"]],
"color": color_map.get(
node.get_cfg_dict()["contract_name"], default_colors[0]
),
"size": 150,
"fullLabel": "\n".join(code_split),
"label": truncated_code,
@ -231,22 +238,12 @@ def generate_graph(
template = env.get_template("callgraph.html")
graph_opts = default_opts
accounts = statespace.accounts
if phrackify:
color_map = {accounts[k].contract_name: phrack_color for k in accounts}
graph_opts.update(phrack_opts)
else:
color_map = {
accounts[k].contract_name: default_colors[i % len(default_colors)]
for i, k in enumerate(accounts)
}
graph_opts["physics"]["enabled"] = physics
return template.render(
title=title,
nodes=extract_nodes(statespace, color_map),
nodes=extract_nodes(statespace),
edges=extract_edges(statespace),
phrackify=phrackify,
opts=graph_opts,

@ -1,19 +1,72 @@
"""This module contains the detection code for insecure delegate call usage."""
import re
import json
import logging
from typing import List
from copy import copy
from typing import List, cast
from mythril.analysis import solver
from mythril.analysis.swc_data import DELEGATECALL_TO_UNTRUSTED_CONTRACT
from mythril.analysis.ops import get_variable, VarType, Call, Variable
from mythril.analysis.report import Issue
from mythril.analysis.call_helpers import get_call_from_state
from mythril.analysis.modules.base import DetectionModule
from mythril.exceptions import UnsatError
from mythril.laser.ethereum.state.annotation import StateAnnotation
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.laser.smt import symbol_factory, UGT
from mythril.laser.smt import symbol_factory, UGT
log = logging.getLogger(__name__)
class DelegateCallAnnotation(StateAnnotation):
def __init__(self, call_state: GlobalState) -> None:
"""
Initialize DelegateCall Annotation
:param call_state: Call state
"""
self.call_state = call_state
self.return_value = call_state.new_bitvec(
"retval_{}".format(call_state.get_current_instruction()["address"]), 256
)
def get_issue(self, global_state: GlobalState, transaction_sequence: str) -> Issue:
"""
Returns Issue for the annotation
:param global_state: Global State
:param transaction_sequence: Transaction sequence
:return: Issue
"""
address = self.call_state.get_current_instruction()["address"]
logging.debug(
"[DELEGATECALL] Detected delegatecall to a user-supplied address : {}".format(
address
)
)
description_head = "The contract delegates execution to another contract with a user-supplied address."
description_tail = (
"The smart contract delegates execution to a user-supplied address. Note that callers "
"can execute arbitrary contracts and that the callee contract "
"can access the storage of the calling contract. "
)
return Issue(
contract=self.call_state.environment.active_account.contract_name,
function_name=self.call_state.environment.active_function_name,
address=address,
swc_id=DELEGATECALL_TO_UNTRUSTED_CONTRACT,
title="Delegatecall Proxy To User-Supplied Address",
bytecode=global_state.environment.code.bytecode,
severity="Medium",
description_head=description_head,
description_tail=description_tail,
debug=transaction_sequence,
gas_used=(
global_state.mstate.min_gas_used,
global_state.mstate.max_gas_used,
),
)
class DelegateCallModule(DetectionModule):
"""This module detects calldata being forwarded using DELEGATECALL."""
@ -24,7 +77,7 @@ class DelegateCallModule(DetectionModule):
swc_id=DELEGATECALL_TO_UNTRUSTED_CONTRACT,
description="Check for invocations of delegatecall(msg.data) in the fallback function.",
entrypoint="callback",
pre_hooks=["DELEGATECALL"],
pre_hooks=["DELEGATECALL", "RETURN", "STOP"],
)
def execute(self, state: GlobalState) -> list:
@ -43,59 +96,47 @@ def _analyze_states(state: GlobalState) -> List[Issue]:
:param state: the current state
:return: returns the issues for that corresponding state
"""
call = get_call_from_state(state)
if call is None:
return []
issues = [] # type: List[Issue]
issues = []
op_code = state.get_current_instruction()["opcode"]
annotations = cast(
List[DelegateCallAnnotation],
list(state.get_annotations(DelegateCallAnnotation)),
)
if call.type is not "DELEGATECALL":
return []
if call.node.function_name is not "fallback":
if len(annotations) == 0 and op_code in ("RETURN", "STOP"):
return []
state = call.state
address = state.get_current_instruction()["address"]
meminstart = get_variable(state.mstate.stack[-3])
if op_code == "DELEGATECALL":
gas = state.mstate.stack[-1]
to = state.mstate.stack[-2]
if meminstart.type == VarType.CONCRETE:
issues += _concrete_call(call, state, address, meminstart)
constraints = copy(state.mstate.constraints)
# Check whether we can also set the callee address
return issues
constraints += [
to == 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF,
UGT(gas, symbol_factory.BitVecVal(2300, 256)),
]
try:
solver.get_model(constraints)
state.annotate(DelegateCallAnnotation(call_state=state))
except UnsatError:
log.debug("[DELEGATECALL] Annotation skipped.")
def _concrete_call(
call: Call, state: GlobalState, address: int, meminstart: Variable
) -> List[Issue]:
"""
:param call: The current call's information
:param state: The current state
:param address: The PC address
:param meminstart: memory starting position
:return: issues
"""
if not re.search(r"calldata.*\[0", str(state.mstate.memory[meminstart.val])):
return []
issue = Issue(
contract=call.node.contract_name,
function_name=call.node.function_name,
address=address,
swc_id=DELEGATECALL_TO_UNTRUSTED_CONTRACT,
bytecode=state.environment.code.bytecode,
title="Delegatecall Proxy",
severity="Low",
description_head="The contract implements a delegatecall proxy.",
description_tail="The smart contract forwards the received calldata via delegatecall. Note that callers "
"can execute arbitrary functions in the callee contract and that the callee contract "
"can access the storage of the calling contract. "
"Make sure that the callee contract is audited properly.",
gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used),
)
target = hex(call.to.val) if call.to.type == VarType.CONCRETE else str(call.to)
issue.description += "DELEGATECALL target: {}".format(target)
return [issue]
else:
for annotation in annotations:
try:
transaction_sequence = solver.get_transaction_sequence(
state, state.mstate.constraints + [annotation.return_value == 1]
)
debug = json.dumps(transaction_sequence, indent=4)
issues.append(annotation.get_issue(state, transaction_sequence=debug))
except UnsatError:
continue
return issues
detector = DelegateCallModule()

@ -1,22 +1,53 @@
"""This module contains the detection code for predictable variable
dependence."""
import logging
import re
from mythril.analysis import solver
from mythril.analysis.call_helpers import get_call_from_state
from mythril.analysis.modules.base import DetectionModule
from mythril.analysis.ops import Call, VarType
from mythril.analysis.report import Issue
from mythril.analysis.swc_data import TIMESTAMP_DEPENDENCE, WEAK_RANDOMNESS
from mythril.exceptions import UnsatError
from mythril.analysis import solver
from mythril.laser.smt import ULT, symbol_factory
from mythril.analysis.swc_data import TIMESTAMP_DEPENDENCE, WEAK_RANDOMNESS
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.laser.ethereum.state.annotation import StateAnnotation
from typing import cast, List
import traceback
log = logging.getLogger(__name__)
predictable_ops = ["COINBASE", "GASLIMIT", "TIMESTAMP", "NUMBER"]
final_ops = ["CALL", "SUICIDE", "STOP", "RETURN"]
def is_prehook() -> bool:
"""Check if we are in prehook. One of Bernhard's trademark hacks!"""
return "pre_hook" in traceback.format_stack()[-4]
class PredictableValueAnnotation:
"""Symbol annotation used if a variable is initialized from a predictable environment variable."""
def __init__(self, operation: str) -> None:
self.operation = operation
class PredictablePathAnnotation(StateAnnotation):
"""State annotation used when a path is chosen based on a predictable variable."""
def __init__(self, operation: str, location: int) -> None:
self.operation = operation
self.location = location
class OldBlockNumberUsedAnnotation(StateAnnotation):
"""State annotation set in blockhash prehook if the input value is lower than the current block number."""
def __init__(self) -> None:
pass
class PredictableDependenceModule(DetectionModule):
"""This module detects whether Ether is sent using predictable
"""This module detects whether control flow decisions are made using predictable
parameters."""
def __init__(self) -> None:
@ -25,12 +56,12 @@ class PredictableDependenceModule(DetectionModule):
name="Dependence of Predictable Variables",
swc_id="{} {}".format(TIMESTAMP_DEPENDENCE, WEAK_RANDOMNESS),
description=(
"Check for CALLs that send >0 Ether as a result of computation "
"based on predictable variables such as block.coinbase, "
"block.gaslimit, block.timestamp, block.number"
"Check whether important control flow decisions are influenced by block.coinbase,"
"block.gaslimit, block.timestamp or block.number."
),
entrypoint="callback",
pre_hooks=["CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"],
pre_hooks=["BLOCKHASH", "JUMPI"] + final_ops,
post_hooks=["BLOCKHASH"] + predictable_ops,
)
def execute(self, state: GlobalState) -> list:
@ -39,186 +70,138 @@ class PredictableDependenceModule(DetectionModule):
:param state:
:return:
"""
log.debug("Executing module: DEPENDENCE_ON_PREDICTABLE_VARS")
self._issues.extend(_analyze_states(state))
return self.issues
detector = PredictableDependenceModule()
def _analyze_states(state: GlobalState) -> list:
"""
:param state:
:return:
"""
issues = []
call = get_call_from_state(state)
if call is None:
return []
if "callvalue" in str(call.value):
log.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] Skipping refund function")
return []
# We're only interested in calls that send Ether
if call.value.type == VarType.CONCRETE and call.value.val == 0:
return []
address = call.state.get_current_instruction()["address"]
description = (
"The contract sends Ether depending on the values of the following variables:\n"
)
# First check: look for predictable state variables in node & call recipient constraints
vars = ["coinbase", "gaslimit", "timestamp", "number"]
found = []
for var in vars:
for constraint in call.node.constraints[:] + [call.to]:
if var in str(constraint):
found.append(var)
if len(found):
for item in found:
description += "- block.{}\n".format(item)
if solve(call):
swc_id = TIMESTAMP_DEPENDENCE if item == "timestamp" else WEAK_RANDOMNESS
description += (
"Note that the values of variables like coinbase, gaslimit, block number and timestamp "
"are predictable and/or can be manipulated by a malicious miner. "
"Don't use them for random number generation or to make critical decisions."
)
issue = Issue(
contract=call.node.contract_name,
function_name=call.node.function_name,
address=address,
swc_id=swc_id,
bytecode=call.state.environment.code.bytecode,
title="Dependence on predictable environment variable",
severity="Low",
description_head="Sending of Ether depends on a predictable variable.",
description_tail=description,
gas_used=(
call.state.mstate.min_gas_used,
call.state.mstate.max_gas_used,
),
)
issues.append(issue)
if is_prehook():
# Second check: blockhash
opcode = state.get_current_instruction()["opcode"]
for constraint in call.node.constraints[:] + [call.to]:
if "blockhash" in str(constraint):
if "number" in str(constraint):
m = re.search(r"blockhash\w+(\s-\s(\d+))*", str(constraint))
if m and solve(call):
if opcode in final_ops:
found_item = m.group(1)
for annotation in state.annotations:
if found_item: # block.blockhash(block.number - N)
description = (
"The predictable expression 'block.blockhash(block.number - "
+ m.group(2)
+ ")' is used to determine Ether recipient"
)
if int(m.group(2)) > 255:
description += (
", this expression will always be equal to zero."
)
elif "storage" in str(
constraint
): # block.blockhash(block.number - storage_0)
description = (
"The predictable expression 'block.blockhash(block.number - "
+ "some_storage_var)' is used to determine Ether recipient"
)
else: # block.blockhash(block.number)
description = (
"The predictable expression 'block.blockhash(block.number)'"
+ " is used to determine Ether recipient"
)
description += ", this expression will always be equal to zero."
if isinstance(annotation, PredictablePathAnnotation):
description = (
"The "
+ annotation.operation
+ " is used in to determine a control flow decision. "
)
description += (
"Note that the values of variables like coinbase, gaslimit, block number and timestamp "
"are predictable and can be manipulated by a malicious miner. Also keep in mind that attackers "
"know hashes of earlier blocks. Don't use any of those environment variables for random number "
"generation or to make critical control flow decisions."
)
"""
Usually report low severity except in cases where the hash of a previous block is used to
determine control flow.
"""
severity = "Medium" if "hash" in annotation.operation else "Low"
"""
Note: We report the location of the JUMPI that lead to this path. Usually this maps to an if or
require statement.
"""
swc_id = (
TIMESTAMP_DEPENDENCE
if "timestamp" in annotation.operation
else WEAK_RANDOMNESS
)
issue = Issue(
contract=call.node.contract_name,
function_name=call.node.function_name,
address=address,
bytecode=call.state.environment.code.bytecode,
title="Dependence on Predictable Variable",
severity="Low",
description_head="Sending of Ether depends on the blockhash.",
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=annotation.location,
swc_id=swc_id,
bytecode=state.environment.code.bytecode,
title="Dependence on predictable environment variable",
severity=severity,
description_head="A control flow decision is made based on a predictable variable.",
description_tail=description,
swc_id=WEAK_RANDOMNESS,
gas_used=(
call.state.mstate.min_gas_used,
call.state.mstate.max_gas_used,
),
gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used),
)
issues.append(issue)
break
else:
r = re.search(r"storage_([a-z0-9_&^]+)", str(constraint))
if r: # block.blockhash(storage_0)
elif opcode == "JUMPI":
"""We actually can do better here by adding a constraint
blockhash_block_storage_0 == 0 and checking model
satisfiability.
# Look for predictable state variables in jump condition
When this is done, severity can be raised from
'Informational' to 'Warning'. Checking that storage
at given index can be tainted is not necessary,
since it usually contains block.number of the
'commit' transaction in commit-reveal workflow.
"""
for annotation in state.mstate.stack[-2].annotations:
index = r.group(1)
if index and solve(call):
description = (
"A block hash is calculated using the block.blockhash(uint blockNumber) method. "
"The block number is obtained from storage index {}".format(
index
)
if isinstance(annotation, PredictableValueAnnotation):
state.annotate(
PredictablePathAnnotation(
annotation.operation,
state.get_current_instruction()["address"],
)
issue = Issue(
contract=call.node.contract_name,
function_name=call.node.function_name,
address=address,
bytecode=call.state.environment.code.bytecode,
title="Dependence on Predictable Variable",
severity="Low",
description_head="Sending of Ether depends on the blockhash.",
description_tail=description,
swc_id=WEAK_RANDOMNESS,
gas_used=(
call.state.mstate.min_gas_used,
call.state.mstate.max_gas_used,
),
)
issues.append(issue)
break
return issues
)
break
elif opcode == "BLOCKHASH":
def solve(call: Call) -> bool:
"""
param = state.mstate.stack[-1]
:param call:
:return:
"""
try:
model = solver.get_model(call.node.constraints)
log.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] MODEL: " + str(model))
pretty_model = solver.pretty_print_model(model)
try:
constraint = [
ULT(param, state.environment.block_number),
ULT(
state.environment.block_number,
symbol_factory.BitVecVal(2 ** 255, 256),
),
]
# Why the second constraint? Because without it Z3 returns a solution where param overflows.
solver.get_model(constraint)
state.annotate(OldBlockNumberUsedAnnotation())
log.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] main model: \n%s" % pretty_model)
return True
except UnsatError:
pass
except UnsatError:
log.debug("[DEPENDENCE_ON_PREDICTABLE_VARS] no model found")
return False
else:
# we're in post hook
opcode = state.environment.code.instruction_list[state.mstate.pc - 1]["opcode"]
if opcode == "BLOCKHASH":
# if we're in the post hook of a BLOCKHASH op, check if an old block number was used to create it.
annotations = cast(
List[OldBlockNumberUsedAnnotation],
list(state.get_annotations(OldBlockNumberUsedAnnotation)),
)
if len(annotations):
state.mstate.stack[-1].annotate(
PredictableValueAnnotation("block hash of a previous block")
)
else:
# Always create an annotation when COINBASE, GASLIMIT, TIMESTAMP or NUMBER is executed.
state.mstate.stack[-1].annotate(
PredictableValueAnnotation(
"block.{} environment variable".format(opcode.lower())
)
)
return issues
detector = PredictableDependenceModule()

@ -30,13 +30,13 @@ def _analyze_state(state):
"Use of msg.origin is deprecated and the instruction may be removed in the future. "
"Use msg.sender instead.\nSee also: "
"https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin".format(
node.function_name
state.environment.active_function_name
)
)
swc_id = DEPRECATED_FUNCTIONS_USAGE
elif instruction["opcode"] == "CALLCODE":
log.debug("CALLCODE in function " + node.function_name)
log.debug("CALLCODE in function " + state.environment.active_function_name)
title = "Use of callcode"
description_head = "Use of callcode is deprecated."
description_tail = (
@ -45,10 +45,12 @@ def _analyze_state(state):
"therefore deprecated and may be removed in the future. Use the delegatecall method instead."
)
swc_id = DEPRECATED_FUNCTIONS_USAGE
else:
return
issue = Issue(
contract=node.contract_name,
function_name=node.function_name,
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=instruction["address"],
title=title,
bytecode=state.environment.code.bytecode,

@ -0,0 +1,128 @@
"""This module contains the detection code SWC-128 - DOS with block gas limit."""
import logging
from typing import Dict, cast, List
from mythril.analysis.swc_data import DOS_WITH_BLOCK_GAS_LIMIT
from mythril.analysis.report import Issue
from mythril.analysis.modules.base import DetectionModule
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.laser.ethereum.state.annotation import StateAnnotation
from mythril.laser.ethereum import util
log = logging.getLogger(__name__)
class LoopAnnotation(StateAnnotation):
def __init__(self, loop_start: int, loop_end: int) -> None:
self.loop_start = loop_start
self.loop_end = loop_end
def contains(self, address: int) -> bool:
return self.loop_start < address < self.loop_end
class DOS(DetectionModule):
"""This module consists of a makeshift loop detector that annotates the state with
a list of byte ranges likely to be loops. If a CALL or SSTORE detection is found in
one of the ranges it creates a low-severity issue. This is not super precise but
good enough to identify places that warrant a closer look. Checking the loop condition
would be a possible improvement.
"""
def __init__(self) -> None:
""""""
super().__init__(
name="DOS",
swc_id=DOS_WITH_BLOCK_GAS_LIMIT,
description="Check for DOS",
entrypoint="callback",
pre_hooks=["JUMPI", "CALL", "SSTORE"],
)
"""Keeps track of how often jump destinations are reached."""
self._jumpdest_count = {} # type: Dict[object, dict]
def execute(self, state: GlobalState) -> None:
"""
:param state:
:return:
"""
log.debug("Executing module: DOS")
self._issues.extend(self._analyze_states(state))
def _analyze_states(self, state: GlobalState) -> List[Issue]:
"""
:param state: the current state
:return: returns the issues for that corresponding state
"""
opcode = state.get_current_instruction()["opcode"]
address = state.get_current_instruction()["address"]
if opcode == "JUMPI":
target = util.get_concrete_int(state.mstate.stack[-1])
transaction = state.current_transaction
if state.current_transaction in self._jumpdest_count:
try:
self._jumpdest_count[transaction][target] += 1
if self._jumpdest_count[transaction][target] == 4:
annotation = (
LoopAnnotation(address, target)
if target > address
else LoopAnnotation(target, address)
)
state.annotate(annotation)
except KeyError:
self._jumpdest_count[transaction][target] = 0
else:
self._jumpdest_count[transaction] = {}
self._jumpdest_count[transaction][target] = 0
else:
annotations = cast(
List[LoopAnnotation], list(state.get_annotations(LoopAnnotation))
)
for annotation in annotations:
if annotation.contains(address):
operation = (
"A storage modification"
if opcode == "SSTORE"
else "An external call"
)
description_head = (
"Potential denial-of-service if block gas limit is reached."
)
description_tail = "{} is executed in a loop.".format(operation)
issue = Issue(
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=annotation.loop_start,
swc_id=DOS_WITH_BLOCK_GAS_LIMIT,
bytecode=state.environment.code.bytecode,
title="Potential denial-of-service if block gas limit is reached",
severity="Low",
description_head=description_head,
description_tail=description_tail,
gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used),
)
return [issue]
return []
detector = DOS()

@ -67,7 +67,6 @@ class EtherThief(DetectionModule):
:return:
"""
instruction = state.get_current_instruction()
node = state.node
if instruction["opcode"] != "CALL":
return []
@ -80,7 +79,7 @@ class EtherThief(DetectionModule):
eth_sent_total = symbol_factory.BitVecVal(0, 256)
constraints = copy(node.constraints)
constraints = copy(state.mstate.constraints)
for tx in state.world_state.transaction_sequence:
if tx.caller == 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF:
@ -101,8 +100,8 @@ class EtherThief(DetectionModule):
debug = json.dumps(transaction_sequence, indent=4)
issue = Issue(
contract=node.contract_name,
function_name=node.function_name,
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=instruction["address"],
swc_id=UNPROTECTED_ETHER_WITHDRAWAL,
title="Unprotected Ether Withdrawal",

@ -19,9 +19,8 @@ def _analyze_state(state) -> list:
:return:
"""
log.info("Exceptions module: found ASSERT_FAIL instruction")
node = state.node
log.debug("ASSERT_FAIL in function " + node.function_name)
log.debug("ASSERT_FAIL in function " + state.environment.active_function_name)
try:
address = state.get_current_instruction()["address"]
@ -34,12 +33,14 @@ def _analyze_state(state) -> list:
"Use `require()` for regular input checking."
)
transaction_sequence = solver.get_transaction_sequence(state, node.constraints)
transaction_sequence = solver.get_transaction_sequence(
state, state.mstate.constraints
)
debug = json.dumps(transaction_sequence, indent=4)
issue = Issue(
contract=node.contract_name,
function_name=node.function_name,
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=address,
swc_id=ASSERT_VIOLATION,
title="Exception State",

@ -5,9 +5,10 @@ from mythril.analysis import solver
from mythril.analysis.swc_data import REENTRANCY
from mythril.analysis.modules.base import DetectionModule
from mythril.analysis.report import Issue
from mythril.laser.smt import UGT, symbol_factory
from mythril.laser.smt import UGT, symbol_factory, Or, BitVec
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.exceptions import UnsatError
from copy import copy
import logging
import json
@ -28,14 +29,14 @@ def _analyze_state(state):
:param state:
:return:
"""
node = state.node
gas = state.mstate.stack[-1]
to = state.mstate.stack[-2]
address = state.get_current_instruction()["address"]
try:
constraints = node.constraints
constraints = copy(state.mstate.constraints)
transaction_sequence = solver.get_transaction_sequence(
state, constraints + [UGT(gas, symbol_factory.BitVecVal(2300, 256))]
)
@ -56,8 +57,8 @@ def _analyze_state(state):
)
issue = Issue(
contract=node.contract_name,
function_name=node.function_name,
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=address,
swc_id=REENTRANCY,
title="External Call To User-Supplied Address",
@ -70,6 +71,8 @@ def _analyze_state(state):
)
except UnsatError:
if _is_precompile_call(state):
return []
log.debug(
"[EXTERNAL_CALLS] Callee address cannot be modified. Reporting informational issue."
@ -83,8 +86,8 @@ def _analyze_state(state):
)
issue = Issue(
contract=node.contract_name,
function_name=state.node.function_name,
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=address,
swc_id=REENTRANCY,
title="External Call To Fixed Address",
@ -103,6 +106,23 @@ def _analyze_state(state):
return [issue]
def _is_precompile_call(global_state: GlobalState):
to = global_state.mstate.stack[-2] # type: BitVec
constraints = copy(global_state.mstate.constraints)
constraints += [
Or(
to < symbol_factory.BitVecVal(1, 256),
to > symbol_factory.BitVecVal(16, 256),
)
]
try:
solver.get_model(constraints)
return False
except UnsatError:
return True
class ExternalCalls(DetectionModule):
"""This module searches for low level calls (e.g. call.value()) that
forward all gas to the callee."""

@ -31,6 +31,8 @@ import logging
log = logging.getLogger(__name__)
DISABLE_EFFECT_CHECK = True
class OverUnderflowAnnotation:
""" Symbol Annotation used if a BitVector can overflow"""
@ -120,7 +122,7 @@ class IntegerOverflowUnderflowModule(DetectionModule):
c = Not(BVAddNoOverflow(op0, op1, False))
# Check satisfiable
model = self._try_constraints(state.node.constraints, [c])
model = self._try_constraints(state.mstate.constraints, [c])
if model is None:
return
@ -132,7 +134,7 @@ class IntegerOverflowUnderflowModule(DetectionModule):
c = Not(BVMulNoOverflow(op0, op1, False))
# Check satisfiable
model = self._try_constraints(state.node.constraints, [c])
model = self._try_constraints(state.mstate.constraints, [c])
if model is None:
return
@ -144,7 +146,7 @@ class IntegerOverflowUnderflowModule(DetectionModule):
c = Not(BVSubNoUnderflow(op0, op1, False))
# Check satisfiable
model = self._try_constraints(state.node.constraints, [c])
model = self._try_constraints(state.mstate.constraints, [c])
if model is None:
return
@ -172,7 +174,7 @@ class IntegerOverflowUnderflowModule(DetectionModule):
)
else:
constraint = op0.value ** op1.value >= 2 ** 256
model = self._try_constraints(state.node.constraints, [constraint])
model = self._try_constraints(state.mstate.constraints, [constraint])
if model is None:
return
annotation = OverUnderflowAnnotation(state, "exponentiation", constraint)
@ -286,19 +288,24 @@ class IntegerOverflowUnderflowModule(DetectionModule):
):
continue
node = ostate.node
try:
# This check can be disabled if the contraints are to difficult for z3 to solve
# within any reasonable time.
if DISABLE_EFFECT_CHECK:
constraints = ostate.mstate.constraints + [annotation.constraint]
else:
constraints = state.mstate.constraints + [annotation.constraint]
transaction_sequence = solver.get_transaction_sequence(
state, node.constraints + [annotation.constraint]
state, constraints
)
except UnsatError:
continue
_type = "Underflow" if annotation.operator == "subtraction" else "Overflow"
issue = Issue(
contract=node.contract_name,
function_name=node.function_name,
contract=ostate.environment.active_account.contract_name,
function_name=ostate.environment.active_function_name,
address=ostate.get_current_instruction()["address"],
swc_id=INTEGER_OVERFLOW_AND_UNDERFLOW,
bytecode=ostate.environment.code.bytecode,
@ -319,8 +326,7 @@ class IntegerOverflowUnderflowModule(DetectionModule):
@staticmethod
def _try_constraints(constraints, new_constraints):
"""
Tries new constraints
""" Tries new constraints
:return Model if satisfiable otherwise None
"""
try:

@ -1,27 +1,25 @@
"""This module contains the detection code to find multiple sends occurring in
a single transaction."""
from copy import copy
from typing import cast, List, Optional
from typing import cast, List
from mythril.analysis.ops import Call
from mythril.analysis.report import Issue
from mythril.analysis.swc_data import MULTIPLE_SENDS
from mythril.analysis.modules.base import DetectionModule
from mythril.laser.ethereum.state.annotation import StateAnnotation
from mythril.laser.ethereum.state.global_state import GlobalState
import logging
from mythril.analysis.call_helpers import get_call_from_state
log = logging.getLogger(__name__)
class MultipleSendsAnnotation(StateAnnotation):
def __init__(self) -> None:
self.calls = [] # type: List[Optional[Call]]
self.call_offsets = [] # type: List[int]
def __copy__(self):
result = MultipleSendsAnnotation()
result.calls = copy(self.calls)
result.call_offsets = copy(self.call_offsets)
return result
@ -55,7 +53,6 @@ def _analyze_state(state: GlobalState):
:param state: the current state
:return: returns the issues for that corresponding state
"""
node = state.node
instruction = state.get_current_instruction()
annotations = cast(
@ -63,46 +60,35 @@ def _analyze_state(state: GlobalState):
list(state.get_annotations(MultipleSendsAnnotation)),
)
if len(annotations) == 0:
log.debug("Creating annotation for state")
state.annotate(MultipleSendsAnnotation())
annotations = cast(
List[MultipleSendsAnnotation],
list(state.get_annotations(MultipleSendsAnnotation)),
)
calls = annotations[0].calls
call_offsets = annotations[0].call_offsets
if instruction["opcode"] in ["CALL", "DELEGATECALL", "STATICCALL", "CALLCODE"]:
call = get_call_from_state(state)
if call:
calls += [call]
call_offsets.append(state.get_current_instruction()["address"])
else: # RETURN or STOP
if len(calls) > 1:
description_tail = (
"Consecutive calls are executed at the following bytecode offsets:\n"
)
for offset in call_offsets[1:]:
for call in calls:
description_tail += "Offset: {}\n".format(
call.state.get_current_instruction()["address"]
)
description_tail += (
"Try to isolate each external call into its own transaction,"
" as external calls can fail accidentally or deliberately.\n"
description_tail = (
"This call is executed after a previous call in the same transaction. "
"Try to isolate each call, transfer or send into its own transaction."
)
issue = Issue(
contract=node.contract_name,
function_name=node.function_name,
address=instruction["address"],
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=offset,
swc_id=MULTIPLE_SENDS,
bytecode=state.environment.code.bytecode,
title="Multiple Calls in a Single Transaction",
severity="Medium",
description_head="Multiple sends are executed in one transaction.",
severity="Low",
description_head="Multiple calls are executed in the same transaction.",
description_tail=description_tail,
gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used),
)

@ -0,0 +1,172 @@
from mythril.analysis.swc_data import REENTRANCY
from mythril.analysis.modules.base import DetectionModule
from mythril.analysis.report import Issue
from mythril.laser.smt import symbol_factory, UGT, BitVec, Or
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.laser.ethereum.state.annotation import StateAnnotation
from mythril.analysis import solver
from mythril.exceptions import UnsatError
from typing import List, cast, Optional
from copy import copy
import logging
log = logging.getLogger(__name__)
DESCRIPTION = """
Check whether there is a state change of the contract after the execution of an external call
"""
class StateChangeCallsAnnotation(StateAnnotation):
def __init__(self, call_state: GlobalState, user_defined_address: bool) -> None:
self.call_state = call_state
self.state_change_states = [] # type: List[GlobalState]
self.user_defined_address = user_defined_address
def __copy__(self):
new_annotation = StateChangeCallsAnnotation(
self.call_state, self.user_defined_address
)
new_annotation.state_change_states = self.state_change_states[:]
return new_annotation
def get_issue(self, global_state: GlobalState) -> Optional[Issue]:
if not self.state_change_states:
return None
severity = "Medium" if self.user_defined_address else "Low"
address = global_state.get_current_instruction()["address"]
logging.debug(
"[EXTERNAL_CALLS] Detected state changes at addresses: {}".format(address)
)
description_head = (
"The contract account state is changed after an external call. "
)
description_tail = (
"Consider that the called contract could re-enter the function before this "
"state change takes place. This can lead to business logic vulnerabilities."
)
return Issue(
contract=global_state.environment.active_account.contract_name,
function_name=global_state.environment.active_function_name,
address=address,
title="State change after external call",
severity=severity,
description_head=description_head,
description_tail=description_tail,
swc_id=REENTRANCY,
bytecode=global_state.environment.code.bytecode,
)
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",
"SSTORE",
"DELEGATECALL",
"STATICCALL",
"CREATE",
"CREATE2",
"CALLCODE",
],
)
def execute(self, state: GlobalState):
self._issues.extend(self._analyze_state(state))
return self.issues
@staticmethod
def _add_external_call(global_state: GlobalState) -> None:
gas = global_state.mstate.stack[-1]
to = global_state.mstate.stack[-2]
try:
constraints = copy(global_state.mstate.constraints)
solver.get_model(
constraints
+ [
UGT(gas, symbol_factory.BitVecVal(2300, 256)),
Or(
to > symbol_factory.BitVecVal(16, 256),
to == symbol_factory.BitVecVal(0, 256),
),
]
)
# Check whether we can also set the callee address
try:
constraints += [to == 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF]
solver.get_model(constraints)
global_state.annotate(StateChangeCallsAnnotation(global_state, True))
except UnsatError:
global_state.annotate(StateChangeCallsAnnotation(global_state, False))
except UnsatError:
pass
@staticmethod
def _analyze_state(global_state: GlobalState) -> List[Issue]:
annotations = cast(
List[StateChangeCallsAnnotation],
list(global_state.get_annotations(StateChangeCallsAnnotation)),
)
op_code = global_state.get_current_instruction()["opcode"]
if len(annotations) == 0:
if op_code in ("SSTORE", "CREATE", "CREATE2"):
return []
if op_code in ("SSTORE", "CREATE", "CREATE2"):
for annotation in annotations:
annotation.state_change_states.append(global_state)
# Record state changes following from a transfer of ether
if op_code in ("CALL", "DELEGATECALL", "CALLCODE"):
value = global_state.mstate.stack[-3] # type: BitVec
if StateChange._balance_change(value, global_state):
for annotation in annotations:
annotation.state_change_states.append(global_state)
# Record external calls
if op_code in ("CALL", "DELEGATECALL", "CALLCODE"):
StateChange._add_external_call(global_state)
# Check for vulnerabilities
vulnerabilities = []
for annotation in annotations:
if not annotation.state_change_states:
continue
vulnerabilities.append(annotation.get_issue(global_state))
return vulnerabilities
@staticmethod
def _balance_change(value: BitVec, global_state: GlobalState) -> bool:
if not value.symbolic:
assert value.value is not None
return value.value > 0
else:
constraints = copy(global_state.mstate.constraints)
try:
solver.get_model(
constraints + [value > symbol_factory.BitVecVal(0, 256)]
)
return True
except UnsatError:
return False
detector = StateChange()

@ -48,13 +48,14 @@ class SuicideModule(DetectionModule):
def _analyze_state(self, state):
log.info("Suicide module: Analyzing suicide instruction")
node = state.node
instruction = state.get_current_instruction()
if self._cache_address.get(instruction["address"], False):
return []
to = state.mstate.stack[-1]
log.debug("[SUICIDE] SUICIDE in function " + node.function_name)
log.debug(
"[SUICIDE] SUICIDE in function " + state.environment.active_function_name
)
description_head = "The contract can be killed by anyone."
@ -62,7 +63,7 @@ class SuicideModule(DetectionModule):
try:
transaction_sequence = solver.get_transaction_sequence(
state,
node.constraints
state.mstate.constraints
+ [to == 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF],
)
description_tail = (
@ -71,7 +72,7 @@ class SuicideModule(DetectionModule):
)
except UnsatError:
transaction_sequence = solver.get_transaction_sequence(
state, node.constraints
state, state.mstate.constraints
)
description_tail = "Arbitrary senders can kill this contract."
@ -79,8 +80,8 @@ class SuicideModule(DetectionModule):
self._cache_address[instruction["address"]] = True
issue = Issue(
contract=node.contract_name,
function_name=node.function_name,
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=instruction["address"],
swc_id=UNPROTECTED_SELFDESTRUCT,
bytecode=state.environment.code.bytecode,

@ -1,198 +0,0 @@
"""This module contains the detection code to find the existence of transaction
order dependence."""
import copy
import logging
import re
from mythril.analysis import solver
from mythril.analysis.modules.base import DetectionModule
from mythril.analysis.ops import *
from mythril.analysis.report import Issue
from mythril.analysis.swc_data import TX_ORDER_DEPENDENCE
from mythril.exceptions import UnsatError
log = logging.getLogger(__name__)
class TxOrderDependenceModule(DetectionModule):
"""This module finds the existence of transaction order dependence."""
def __init__(self):
super().__init__(
name="Transaction Order Dependence",
swc_id=TX_ORDER_DEPENDENCE,
description=(
"This module finds the existance of transaction order dependence "
"vulnerabilities. The following webpage contains an extensive description "
"of the vulnerability: "
"https://consensys.github.io/smart-contract-best-practices/known_attacks/#transaction-ordering-dependence-tod-front-running"
),
)
def execute(self, statespace):
"""Executes the analysis module.
:param statespace:
:return:
"""
log.debug("Executing module: TOD")
issues = []
for call in statespace.calls:
# Do analysis
interesting_storages = list(self._get_influencing_storages(call))
changing_sstores = list(
self._get_influencing_sstores(statespace, interesting_storages)
)
description_tail = (
"A transaction order dependence vulnerability may exist in this contract. The value or "
"target of the call statement is loaded from a writable storage location."
)
# Build issue if necessary
if len(changing_sstores) > 0:
node = call.node
instruction = call.state.get_current_instruction()
issue = Issue(
contract=node.contract_name,
function_name=node.function_name,
address=instruction["address"],
title="Transaction Order Dependence",
bytecode=call.state.environment.code.bytecode,
swc_id=TX_ORDER_DEPENDENCE,
severity="Medium",
description_head="The call outcome may depend on transaction order.",
description_tail=description_tail,
gas_used=(
call.state.mstate.min_gas_used,
call.state.mstate.max_gas_used,
),
)
issues.append(issue)
return issues
# TODO: move to __init__ or util module
@staticmethod
def _get_states_with_opcode(statespace, opcode):
"""Gets all (state, node) tuples in statespace with opcode.
:param statespace:
:param opcode:
"""
for k in statespace.nodes:
node = statespace.nodes[k]
for state in node.states:
if state.get_current_instruction()["opcode"] == opcode:
yield state, node
@staticmethod
def _dependent_on_storage(expression):
"""Checks if expression is dependent on a storage symbol and returns
the influencing storages.
:param expression:
:return:
"""
pattern = re.compile(r"storage_[a-z0-9_&^]*[0-9]+")
return pattern.findall(str(simplify(expression)))
@staticmethod
def _get_storage_variable(storage, state):
"""Get storage z3 object given storage name and the state.
:param storage: storage name example: storage_0
:param state: state to retrieve the variable from
:return: z3 object representing storage
"""
index = int(re.search("[0-9]+", storage).group())
try:
return state.environment.active_account.storage[index]
except KeyError:
return None
def _can_change(self, constraints, variable):
"""Checks if the variable can change given some constraints.
:param constraints:
:param variable:
:return:
"""
_constraints = copy.deepcopy(constraints)
try:
model = solver.get_model(_constraints)
except UnsatError:
return False
try:
initial_value = int(str(model.eval(variable, model_completion=True)))
return (
self._try_constraints(constraints, [variable != initial_value])
is not None
)
except AttributeError:
return False
def _get_influencing_storages(self, call):
"""Examines a Call object and returns an iterator of all storages that
influence the call value or direction.
:param call:
"""
state = call.state
node = call.node
# Get relevant storages
to, value = call.to, call.value
storages = []
if to.type == VarType.SYMBOLIC:
storages += self._dependent_on_storage(to.val)
if value.type == VarType.SYMBOLIC:
storages += self._dependent_on_storage(value.val)
# See if they can change within the constraints of the node
for storage in storages:
variable = self._get_storage_variable(storage, state)
can_change = self._can_change(node.constraints, variable)
if can_change:
yield storage
def _get_influencing_sstores(self, statespace, interesting_storages):
"""Gets sstore (state, node) tuples that write to interesting_storages.
:param statespace:
:param interesting_storages:
"""
for sstore_state, node in self._get_states_with_opcode(statespace, "SSTORE"):
index, value = sstore_state.mstate.stack[-1], sstore_state.mstate.stack[-2]
try:
index = util.get_concrete_int(index)
except TypeError:
index = str(index)
if "storage_{}".format(index) not in interesting_storages:
continue
yield sstore_state, node
# TODO: remove
@staticmethod
def _try_constraints(constraints, new_constraints):
"""Tries new constraints.
:param constraints:
:param new_constraints:
:return Model if satisfiable otherwise None
"""
_constraints = copy.deepcopy(constraints)
for constraint in new_constraints:
_constraints.append(copy.deepcopy(constraint))
try:
model = solver.get_model(_constraints)
return model
except UnsatError:
return None
detector = TxOrderDependenceModule()

@ -61,7 +61,6 @@ class UncheckedRetvalModule(DetectionModule):
def _analyze_state(state: GlobalState) -> list:
instruction = state.get_current_instruction()
node = state.node
annotations = cast(
List[UncheckedRetvalAnnotation],
@ -80,7 +79,7 @@ def _analyze_state(state: GlobalState) -> list:
issues = []
for retval in retvals:
try:
solver.get_model(node.constraints + [retval["retval"] == 0])
solver.get_model(state.mstate.constraints + [retval["retval"] == 0])
except UnsatError:
continue
@ -91,8 +90,8 @@ def _analyze_state(state: GlobalState) -> list:
)
issue = Issue(
contract=node.contract_name,
function_name=node.function_name,
contract=state.environment.active_account.contract_name,
function_name=state.environment.active_function_name,
address=retval["address"],
bytecode=state.environment.code.bytecode,
title="Unchecked Call Return Value",

@ -4,13 +4,13 @@ import json
import operator
from jinja2 import PackageLoader, Environment
from typing import Dict, List
import _pysha3 as sha3
import hashlib
from mythril.solidity.soliditycontract import SolidityContract
from mythril.analysis.swc_data import SWC_TO_TITLE
from mythril.support.source_support import Source
from mythril.support.start_time import StartTime
from mythril.support.support_utils import get_code_hash
from time import time
log = logging.getLogger(__name__)
@ -63,20 +63,7 @@ class Issue:
self.lineno = None
self.source_mapping = None
self.discovery_time = time() - StartTime().global_start_time
try:
keccak = sha3.keccak_256()
keccak.update(
bytes.fromhex(bytecode[2:])
if bytecode[:2] == "0x"
else bytes.fromhex(bytecode)
)
self.bytecode_hash = "0x" + keccak.hexdigest()
except ValueError:
log.debug(
"Unable to change the bytecode to bytes. Bytecode: {}".format(bytecode)
)
self.bytecode_hash = ""
self.bytecode_hash = get_code_hash(bytecode)
@property
def as_dict(self):
@ -144,7 +131,7 @@ class Report:
loader=PackageLoader("mythril.analysis"), trim_blocks=True
)
def __init__(self, verbose=False, source=None, exceptions=None):
def __init__(self, verbose=False, contracts=None, exceptions=None):
"""
:param verbose:
@ -153,7 +140,8 @@ class Report:
self.verbose = verbose
self.solc_version = ""
self.meta = {}
self.source = source or Source()
self.source = Source()
self.source.get_source_from_contracts_list(contracts)
self.exceptions = exceptions or []
def sorted_issues(self):
@ -197,7 +185,7 @@ class Report:
return {}
logs = [] # type: List[Dict]
for exception in self.exceptions:
logs += [{"level": "error", "hidden": "true", "error": exception}]
logs += [{"level": "error", "hidden": "true", "msg": exception}]
return {"logs": logs}
def as_swc_standard_format(self):
@ -210,12 +198,7 @@ class Report:
for key, issue in self.issues.items():
if issue.bytecode_hash not in source_list:
idx = len(source_list)
source_list.append(issue.bytecode_hash)
else:
idx = source_list.index(issue.bytecode_hash)
idx = self.source.get_source_index(issue.bytecode_hash)
try:
title = SWC_TO_TITLE[issue.swc_id]
except KeyError:
@ -238,9 +221,9 @@ class Report:
result = [
{
"issues": _issues,
"sourceType": "raw-bytecode",
"sourceFormat": "evm-byzantium-bytecode",
"sourceList": source_list,
"sourceType": self.source.source_type,
"sourceFormat": self.source.source_format,
"sourceList": self.source.source_list,
"meta": meta_data,
}
]

@ -2,18 +2,22 @@
purposes."""
import copy
from mythril.analysis.security import get_detection_module_hooks, get_detection_modules
from mythril.laser.ethereum import svm
from mythril.laser.ethereum.plugins.plugin_factory import PluginFactory
from mythril.laser.ethereum.plugins.plugin_loader import LaserPluginLoader
from mythril.laser.ethereum.state.account import Account
from mythril.laser.ethereum.state.world_state import WorldState
from mythril.laser.ethereum.strategy.basic import (
BreadthFirstSearchStrategy,
DepthFirstSearchStrategy,
ReturnRandomNaivelyStrategy,
ReturnWeightedRandomStrategy,
BasicSearchStrategy,
)
from mythril.laser.ethereum.plugins.mutation_pruner import MutationPruner
from mythril.laser.smt import symbol_factory, BitVec
from typing import Union, List, Dict, Type
from mythril.solidity.soliditycontract import EVMContract, SolidityContract
from .ops import Call, SStore, VarType, get_variable
@ -28,7 +32,7 @@ class SymExecWrapper:
def __init__(
self,
contract,
address,
address: Union[int, str, BitVec],
strategy,
dynloader=None,
max_depth=22,
@ -51,8 +55,13 @@ class SymExecWrapper:
:param transaction_count:
:param modules:
"""
if isinstance(address, str):
address = symbol_factory.BitVecVal(int(address, 16), 256)
if isinstance(address, int):
address = symbol_factory.BitVecVal(address, 256)
if strategy == "dfs":
s_strategy = DepthFirstSearchStrategy
s_strategy = DepthFirstSearchStrategy # type: Type[BasicSearchStrategy]
elif strategy == "bfs":
s_strategy = BreadthFirstSearchStrategy
elif strategy == "naive-random":
@ -62,19 +71,11 @@ class SymExecWrapper:
else:
raise ValueError("Invalid strategy argument supplied")
account = Account(
address,
contract.disassembly,
dynamic_loader=dynloader,
contract_name=contract.name,
)
requires_statespace = (
compulsory_statespace or len(get_detection_modules("post", modules)) > 0
)
self.accounts = {address: account}
self.laser = svm.LaserEVM(
self.accounts,
dynamic_loader=dynloader,
max_depth=max_depth,
execution_timeout=execution_timeout,
@ -84,9 +85,10 @@ class SymExecWrapper:
requires_statespace=requires_statespace,
enable_iprof=enable_iprof,
)
mutation_plugin = MutationPruner()
mutation_plugin.initialize(self.laser)
plugin_loader = LaserPluginLoader(self.laser)
plugin_loader.load(PluginFactory.build_mutation_pruner_plugin())
plugin_loader.load(PluginFactory.build_instruction_coverage_plugin())
self.laser.register_hooks(
hook_type="pre",
@ -106,7 +108,16 @@ class SymExecWrapper:
creation_code=contract.creation_code, contract_name=contract.name
)
else:
self.laser.sym_exec(address)
account = Account(
address,
contract.disassembly,
dynamic_loader=dynloader,
contract_name=contract.name,
concrete_storage=False,
)
world_state = WorldState()
world_state.put_account(account)
self.laser.sym_exec(world_state=world_state, target_address=address.value)
if not requires_statespace:
return
@ -116,8 +127,8 @@ class SymExecWrapper:
# Generate lists of interesting operations
self.calls = []
self.sstors = {}
self.calls = [] # type: List[Call]
self.sstors = {} # type: Dict[int, Dict[str, List[SStore]]]
for key in self.nodes:
@ -194,7 +205,7 @@ class SymExecWrapper:
elif op == "SSTORE":
stack = copy.copy(state.mstate.stack)
address = state.environment.active_account.address
address = state.environment.active_account.address.value
index, value = stack.pop(), stack.pop()

@ -28,11 +28,15 @@ class Disassembly(object):
self.func_hashes = [] # type: List[str]
self.function_name_to_address = {} # type: Dict[str, int]
self.address_to_function_name = {} # type: Dict[int, str]
self.enable_online_lookup = enable_online_lookup
self.assign_bytecode(bytecode=code)
def assign_bytecode(self, bytecode):
self.bytecode = bytecode
# open from default locations
# control if you want to have online signature hash lookups
signatures = SignatureDB(enable_online_lookup=enable_online_lookup)
signatures = SignatureDB(enable_online_lookup=self.enable_online_lookup)
self.instruction_list = asm.disassemble(util.safe_decode(bytecode))
# Need to take from PUSH1 to PUSH4 because solc seems to remove excess 0s at the beginning for optimizing
jump_table_indices = asm.find_op_code_sequence(
[("PUSH1", "PUSH2", "PUSH3", "PUSH4"), ("EQ",)], self.instruction_list

@ -1,15 +1,14 @@
"""This module contains the class representing EVM contracts, aka Smart
Contracts."""
import re
import _pysha3 as sha3
import logging
log = logging.getLogger(__name__)
import persistent
from ethereum import utils
from ethereum import utils
from mythril.disassembler.disassembly import Disassembly
from mythril.support.support_utils import get_code_hash
log = logging.getLogger(__name__)
class EVMContract(persistent.Persistent):
@ -47,7 +46,7 @@ class EVMContract(persistent.Persistent):
:return: runtime bytecode hash
"""
return self._get_hash(self.code[2:])
return get_code_hash(self.code)
@property
def creation_bytecode_hash(self):
@ -55,23 +54,7 @@ class EVMContract(persistent.Persistent):
:return: Creation bytecode hash
"""
return self._get_hash(self.creation_code[2:])
@staticmethod
def _get_hash(code):
"""
:param code: bytecode
:return: Returns hash of the given bytecode
"""
try:
keccak = sha3.keccak_256()
keccak.update(bytes.fromhex(code[2:]))
return "0x" + keccak.hexdigest()
except ValueError:
log.debug(
"Unable to change the bytecode to bytes. Bytecode: {}".format(code)
)
return ""
return get_code_hash(self.creation_code)
def as_dict(self):
"""

@ -33,7 +33,7 @@ def get_solc_json(file, solc_binary="solc", solc_args=None):
:return:
"""
cmd = [solc_binary, "--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime"]
cmd = [solc_binary, "--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime,ast"]
if solc_args:
cmd.extend(solc_args.split())

@ -47,7 +47,7 @@ def exit_with_error(format_, message):
"sourceFormat": "",
"sourceList": [],
"meta": {
"logs": [{"level": "error", "hidden": "true", "error": message}]
"logs": [{"level": "error", "hidden": "true", "msg": message}]
},
}
]
@ -433,6 +433,7 @@ def execute_command(
contract=analyzer.contracts[0],
enable_physics=args.enable_physics,
phrackify=args.phrack,
transaction_count=args.transaction_count,
)
try:
@ -507,7 +508,7 @@ def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> Non
quick_commands(args)
config = set_config(args)
leveldb_search(config, args)
dissasembler = MythrilDisassembler(
disassembler = MythrilDisassembler(
eth=config.eth,
solc_version=args.solv,
solc_args=args.solc_args,
@ -515,16 +516,16 @@ def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> Non
)
if args.truffle:
try:
dissasembler.analyze_truffle_project(args)
disassembler.analyze_truffle_project(args)
except FileNotFoundError:
print(
"Build directory not found. Make sure that you start the analysis from the project root, and that 'truffle compile' has executed successfully."
)
sys.exit()
address = get_code(dissasembler, args)
address = get_code(disassembler, args)
execute_command(
disassembler=dissasembler, address=address, parser=parser, args=args
disassembler=disassembler, address=address, parser=parser, args=args
)
except CriticalError as ce:
exit_with_error(args.outform, str(ce))

@ -3,13 +3,12 @@ instructions.py to get the necessary elements from the stack and determine the
parameters for the new global state."""
import logging
import re
from typing import Union, List, cast, Callable
from z3 import Z3Exception
from mythril.laser.smt import BitVec
import mythril.laser.ethereum.util as util
from mythril.laser.ethereum import natives
from mythril.laser.ethereum.gas import OPCODE_GAS
from mythril.laser.smt import simplify, Expression, symbol_factory
import mythril.laser.ethereum.util as util
from mythril.laser.ethereum.state.account import Account
from mythril.laser.ethereum.state.calldata import (
BaseCalldata,
@ -17,8 +16,9 @@ from mythril.laser.ethereum.state.calldata import (
ConcreteCalldata,
)
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.laser.smt import BitVec
from mythril.laser.smt import simplify, Expression, symbol_factory
from mythril.support.loader import DynLoader
import re
"""
This module contains the business logic used by Instruction in instructions.py
@ -49,7 +49,6 @@ def get_call_parameters(
callee_account = None
call_data = get_call_data(global_state, memory_input_offset, memory_input_size)
if int(callee_address, 16) >= 5 or int(callee_address, 16) == 0:
callee_account = get_callee_account(
global_state, callee_address, dynamic_loader
@ -97,7 +96,7 @@ def get_callee_address(
# attempt to read the contract address from instance storage
try:
callee_address = dynamic_loader.read_storage(
environment.active_account.address, index
str(hex(environment.active_account.address.value)), index
)
# TODO: verify whether this happens or not
except:
@ -125,7 +124,7 @@ def get_callee_account(
accounts = global_state.accounts
try:
return global_state.accounts[callee_address]
return global_state.accounts[int(callee_address, 16)]
except KeyError:
# We have a valid call address, but contract is not in the modules list
log.debug("Module with address " + callee_address + " not loaded.")
@ -136,7 +135,7 @@ def get_callee_account(
log.debug("Attempting to load dependency")
try:
code = dynamic_loader.dynld(environment.active_account.address, callee_address)
code = dynamic_loader.dynld(callee_address)
except ValueError as error:
log.debug("Unable to execute dynamic loader because: {}".format(str(error)))
raise error
@ -146,7 +145,11 @@ def get_callee_account(
log.debug("Dependency loaded: " + callee_address)
callee_account = Account(
callee_address, code, callee_address, dynamic_loader=dynamic_loader
symbol_factory.BitVecVal(int(callee_address, 16), 256),
code,
callee_address,
dynamic_loader=dynamic_loader,
balances=global_state.world_state.balances,
)
accounts[callee_address] = callee_account

@ -941,9 +941,20 @@ class Instruction:
data = symbol_factory.BitVecVal(0, 1)
if data.symbolic:
annotations = []
for b in state.memory[index : index + length]:
if isinstance(b, BitVec):
annotations.append(b.annotations)
argument_str = str(state.memory[index]).replace(" ", "_")
result = symbol_factory.BitVecFuncSym(
"KECCAC[{}]".format(argument_str), "keccak256", 256, input_=data
"KECCAC[{}]".format(argument_str),
"keccak256",
256,
input_=data,
annotations=annotations,
)
log.debug("Created BitVecFunc hash.")
@ -1099,7 +1110,7 @@ class Instruction:
return [global_state]
try:
code = self.dynamic_loader.dynld(environment.active_account.address, addr)
code = self.dynamic_loader.dynld(addr)
except (ValueError, AttributeError) as e:
log.debug("error accessing contract storage due to: " + str(e))
state.stack.append(global_state.new_bitvec("extcodesize_" + str(addr), 256))
@ -1245,7 +1256,7 @@ class Instruction:
:param global_state:
:return:
"""
global_state.mstate.stack.append(global_state.new_bitvec("block_number", 256))
global_state.mstate.stack.append(global_state.environment.block_number)
return [global_state]
@StateTransition()
@ -1522,7 +1533,7 @@ class Instruction:
global_state.environment.active_account
)
global_state.accounts[
global_state.environment.active_account.address
global_state.environment.active_account.address.value
] = global_state.environment.active_account
global_state.environment.active_account.storage[index] = (
@ -1767,31 +1778,19 @@ class Instruction:
:param global_state:
"""
target = global_state.mstate.stack.pop()
account_created = False
transfer_amount = global_state.environment.active_account.balance()
# Often the target of the suicide instruction will be symbolic
# If it isn't then well transfer the balance to the indicated contract
if isinstance(target, BitVec) and not target.symbolic:
target = "0x" + hex(target.value)[-40:]
if isinstance(target, str):
try:
global_state.world_state[
target
].balance += global_state.environment.active_account.balance
except KeyError:
global_state.world_state.create_account(
address=target,
balance=global_state.environment.active_account.balance,
)
account_created = True
# If it isn't then we'll transfer the balance to the indicated contract
global_state.world_state[target].add_balance(transfer_amount)
global_state.environment.active_account = deepcopy(
global_state.environment.active_account
)
global_state.accounts[
global_state.environment.active_account.address
global_state.environment.active_account.address.value
] = global_state.environment.active_account
global_state.environment.active_account.balance = 0
global_state.environment.active_account.set_balance(0)
global_state.environment.active_account.deleted = True
global_state.current_transaction.end(global_state)
@ -1876,9 +1875,7 @@ class Instruction:
gas_price=environment.gasprice,
gas_limit=gas,
origin=environment.origin,
caller=symbol_factory.BitVecVal(
int(environment.active_account.address, 16), 256
),
caller=environment.active_account.address,
callee_account=callee_account,
call_data=call_data,
call_value=value,
@ -2123,7 +2120,6 @@ class Instruction:
"retval_" + str(instr["address"]), 256
)
global_state.mstate.stack.append(return_value)
global_state.mstate.constraints.append(return_value == 0)
return [global_state]
try:

@ -0,0 +1,21 @@
""" Laser plugins
This module contains everything to do with laser plugins
Laser plugins are a way of extending laser's functionality without complicating the core business logic.
Different features that have been implemented in the form of plugins are:
- benchmarking
- path pruning
Plugins also provide a way to implement optimisations outside of the mythril code base and to inject them.
The api that laser currently provides is still unstable and will probably change to suit our needs
as more plugins get developed.
For the implementation of plugins the following modules are of interest:
- laser.plugins.plugin
- laser.plugins.signals
- laser.svm
Which show the basic interfaces with which plugins are able to interact
"""
from mythril.laser.ethereum.plugins.signals import PluginSignal

@ -0,0 +1,7 @@
""" Plugin implementations
This module contains the implementation of some features
- benchmarking
- pruning
"""

@ -1,4 +1,5 @@
from mythril.laser.ethereum.svm import LaserEVM
from mythril.laser.ethereum.plugins.plugin import LaserPlugin
from time import time
import matplotlib.pyplot as plt
import logging
@ -6,7 +7,8 @@ import logging
log = logging.getLogger(__name__)
class BenchmarkPlugin:
# TODO: introduce dependency on coverage plugin
class BenchmarkPlugin(LaserPlugin):
"""Benchmark Plugin
This plugin aggregates the following information:

@ -0,0 +1,3 @@
from mythril.laser.ethereum.plugins.implementations.coverage.coverage_plugin import (
InstructionCoveragePlugin,
)

@ -0,0 +1,98 @@
from mythril.laser.ethereum.svm import LaserEVM
from mythril.laser.ethereum.plugins.plugin import LaserPlugin
from mythril.laser.ethereum.state.global_state import GlobalState
from typing import Dict, Tuple, List
import logging
log = logging.getLogger(__name__)
class InstructionCoveragePlugin(LaserPlugin):
"""InstructionCoveragePlugin
This plugin measures the instruction coverage of mythril.
The instruction coverage is the ratio between the instructions that have been executed
and the total amount of instructions.
Note that with lazy constraint solving enabled that this metric will be "unsound" as
reachability will not be considered for the calculation of instruction coverage.
"""
def __init__(self):
self.coverage = {} # type: Dict[str, Tuple[int, List[bool]]]
self.initial_coverage = 0
self.tx_id = 0
def initialize(self, symbolic_vm: LaserEVM):
"""Initializes the instruction coverage plugin
Introduces hooks for each instruction
:param symbolic_vm:
:return:
"""
self.coverage = {}
self.initial_coverage = 0
self.tx_id = 0
@symbolic_vm.laser_hook("stop_sym_exec")
def stop_sym_exec_hook():
# Print results
for code, code_cov in self.coverage.items():
cov_percentage = sum(code_cov[1]) / float(code_cov[0]) * 100
log.info(
"Achieved {:.2f}% coverage for code: {}".format(
cov_percentage, code
)
)
@symbolic_vm.laser_hook("execute_state")
def execute_state_hook(global_state: GlobalState):
# Record coverage
code = global_state.environment.code.bytecode
if code not in self.coverage.keys():
number_of_instructions = len(
global_state.environment.code.instruction_list
)
self.coverage[code] = (
number_of_instructions,
[False] * number_of_instructions,
)
self.coverage[code][1][global_state.mstate.pc] = True
@symbolic_vm.laser_hook("start_sym_trans")
def execute_start_sym_trans_hook():
self.initial_coverage = self._get_covered_instructions()
@symbolic_vm.laser_hook("stop_sym_trans")
def execute_stop_sym_trans_hook():
end_coverage = self._get_covered_instructions()
log.info(
"Number of new instructions covered in tx %d: %d"
% (self.tx_id, end_coverage - self.initial_coverage)
)
self.tx_id += 1
def _get_covered_instructions(self) -> int:
"""Gets the total number of covered instructions for all accounts in
the svm.
:return:
"""
total_covered_instructions = 0
for _, cv in self.coverage.items():
total_covered_instructions += sum(cv[1])
return total_covered_instructions
def is_instruction_covered(self, bytecode, index):
if bytecode not in self.coverage.keys():
return False
try:
return self.coverage[bytecode][index]
except IndexError:
return False

@ -0,0 +1,43 @@
from mythril.laser.ethereum.strategy import BasicSearchStrategy
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.laser.ethereum.plugins.implementations.coverage import (
InstructionCoveragePlugin,
)
class CoverageStrategy(BasicSearchStrategy):
"""Implements a instruction coverage based search strategy
This strategy is quite simple and effective, it prioritizes the execution of instructions that have previously been
uncovered. Once there is no such global state left in the work list, it will resort to using the super_strategy.
This strategy is intended to be used "on top of" another one
"""
def __init__(
self,
super_strategy: BasicSearchStrategy,
instruction_coverage_plugin: InstructionCoveragePlugin,
):
self.super_strategy = super_strategy
self.instruction_coverage_plugin = instruction_coverage_plugin
BasicSearchStrategy.__init__(
self, super_strategy.work_list, super_strategy.max_depth
)
def get_strategic_global_state(self) -> GlobalState:
"""
Returns the first uncovered global state in the work list if it exists,
otherwise super_strategy.get_strategic_global_state() is returned.
"""
for global_state in self.work_list:
if not self._is_covered(global_state):
self.work_list.remove(global_state)
return global_state
return self.super_strategy.get_strategic_global_state()
def _is_covered(self, global_state: GlobalState) -> bool:
""" Checks if the instruction for the given global state is already covered"""
bytecode = global_state.environment.code.bytecode
index = global_state.mstate.pc
return self.instruction_coverage_plugin.is_instruction_covered(bytecode, index)

@ -1,6 +1,7 @@
from mythril.laser.ethereum.state.annotation import StateAnnotation
from mythril.laser.ethereum.svm import LaserEVM
from mythril.laser.ethereum.plugins.signals import PluginSkipWorldState
from mythril.laser.ethereum.plugins.plugin import LaserPlugin
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.laser.ethereum.transaction.transaction_models import (
ContractCreationTransaction,
@ -17,7 +18,7 @@ class MutationAnnotation(StateAnnotation):
pass
class MutationPruner:
class MutationPruner(LaserPlugin):
"""Mutation pruner plugin
Let S be a world state from which T is a symbolic transaction, and S' is the resulting world state.

@ -0,0 +1,23 @@
from mythril.laser.ethereum.svm import LaserEVM
class LaserPlugin:
""" Base class for laser plugins
Functionality in laser that the symbolic execution process does not need to depend on
can be implemented in the form of a laser plugin.
Laser plugins implement the function initialize(symbolic_vm) which is called with the laser virtual machine
when they are loaded.
Regularly a plugin will introduce several hooks into laser in this function
Plugins can direct actions by raising Signals defined in mythril.laser.ethereum.plugins.signals
For example, a pruning plugin might raise the PluginSkipWorldState signal.
"""
def initialize(self, symbolic_vm: LaserEVM) -> None:
""" Initializes this plugin on the symbolic virtual machine
:param symbolic_vm: symbolic virtual machine to initialize the laser plugin on
"""
raise NotImplementedError

@ -0,0 +1,32 @@
from mythril.laser.ethereum.plugins.plugin import LaserPlugin
class PluginFactory:
""" The plugin factory constructs the plugins provided with laser """
@staticmethod
def build_benchmark_plugin(name: str) -> LaserPlugin:
""" Creates an instance of the benchmark plugin with the given name """
from mythril.laser.ethereum.plugins.implementations.benchmark import (
BenchmarkPlugin,
)
return BenchmarkPlugin(name)
@staticmethod
def build_mutation_pruner_plugin() -> LaserPlugin:
""" Creates an instance of the mutation pruner plugin"""
from mythril.laser.ethereum.plugins.implementations.mutation_pruner import (
MutationPruner,
)
return MutationPruner()
@staticmethod
def build_instruction_coverage_plugin() -> LaserPlugin:
""" Creates an instance of the instruction coverage plugin"""
from mythril.laser.ethereum.plugins.implementations.coverage import (
InstructionCoveragePlugin,
)
return InstructionCoveragePlugin()

@ -0,0 +1,38 @@
from mythril.laser.ethereum.svm import LaserEVM
from mythril.laser.ethereum.plugins.plugin import LaserPlugin
from typing import List
import logging
log = logging.getLogger(__name__)
class LaserPluginLoader:
"""
The LaserPluginLoader is used to abstract the logic relating to plugins.
Components outside of laser thus don't have to be aware of the interface that plugins provide
"""
def __init__(self, symbolic_vm: LaserEVM) -> None:
""" Initializes the plugin loader
:param symbolic_vm: symbolic virtual machine to load plugins for
"""
self.symbolic_vm = symbolic_vm
self.laser_plugins = [] # type: List[LaserPlugin]
def load(self, laser_plugin: LaserPlugin) -> None:
""" Loads the plugin
:param laser_plugin: plugin that will be loaded in the symbolic virtual machine
"""
log.info("Loading plugin: {}".format(str(laser_plugin)))
laser_plugin.initialize(self.symbolic_vm)
self.laser_plugins.append(laser_plugin)
def is_enabled(self, laser_plugin: LaserPlugin) -> bool:
""" Returns whether the plugin is loaded in the symbolic_vm
:param laser_plugin: plugin that will be checked
"""
return laser_plugin in self.laser_plugins

@ -2,11 +2,12 @@
This includes classes representing accounts and their storage.
"""
from copy import deepcopy, copy
from typing import Any, Dict, KeysView, Union
from z3 import ExprRef
from mythril.laser.smt import Array, symbol_factory, BitVec
from mythril.disassembler.disassembly import Disassembly
from mythril.laser.smt import symbol_factory
@ -30,7 +31,7 @@ class Storage:
except KeyError:
if (
self.address
and int(self.address[2:], 16) != 0
and self.address.value != 0
and (self.dynld and self.dynld.storage_loading)
):
try:
@ -46,9 +47,13 @@ class Storage:
return self._storage[item]
except ValueError:
pass
if self.concrete:
return symbol_factory.BitVecVal(0, 256)
self._storage[item] = symbol_factory.BitVecVal(0, 256)
self._storage[item] = symbol_factory.BitVecSym(
"storage_{}_{}".format(str(item), str(self.address)), 256
)
return self._storage[item]
def __setitem__(self, key: Union[int, str], value: Any) -> None:
@ -61,16 +66,23 @@ class Storage:
"""
return self._storage.keys()
def __deepcopy__(self, memodict={}):
storage = Storage(
concrete=self.concrete, address=self.address, dynamic_loader=self.dynld
)
storage._storage = copy(self._storage)
return storage
class Account:
"""Account class representing ethereum accounts."""
def __init__(
self,
address: str,
address: Union[BitVec, str],
code=None,
contract_name="unknown",
balance=None,
balances: Array = None,
concrete_storage=False,
dynamic_loader=None,
) -> None:
@ -84,37 +96,51 @@ class Account:
"""
self.nonce = 0
self.code = code or Disassembly("")
self.balance = (
balance
if balance
else symbol_factory.BitVecSym("{}_balance".format(address), 256)
self.address = (
address
if isinstance(address, BitVec)
else symbol_factory.BitVecVal(int(address, 16), 256)
)
self.storage = Storage(
concrete_storage, address=address, dynamic_loader=dynamic_loader
concrete_storage, address=self.address, dynamic_loader=dynamic_loader
)
# Metadata
self.address = address
self.contract_name = contract_name
self.deleted = False
self._balances = balances
self.balance = lambda: self._balances[self.address]
def __str__(self) -> str:
return str(self.as_dict)
def set_balance(self, balance: ExprRef) -> None:
def set_balance(self, balance: Union[int, BitVec]) -> None:
"""
:param balance:
"""
self.balance = balance
balance = (
symbol_factory.BitVecVal(balance, 256)
if isinstance(balance, int)
else balance
)
assert self._balances is not None
self._balances[self.address] = balance
def add_balance(self, balance: ExprRef) -> None:
def add_balance(self, balance: Union[int, BitVec]) -> None:
"""
:param balance:
"""
self.balance += balance
balance = (
symbol_factory.BitVecVal(balance, 256)
if isinstance(balance, int)
else balance
)
self._balances[self.address] += balance
@property
def as_dict(self) -> Dict:
@ -125,6 +151,17 @@ class Account:
return {
"nonce": self.nonce,
"code": self.code,
"balance": self.balance,
"balance": self.balance(),
"storage": self.storage,
}
def __deepcopy__(self, memodict={}):
new_account = Account(
address=self.address,
code=self.code,
contract_name=self.contract_name,
balances=self._balances,
)
new_account.storage = deepcopy(self.storage)
new_account.code = self.code
return new_account

@ -39,7 +39,8 @@ class Environment:
self.active_account = active_account
self.active_function_name = ""
self.address = symbol_factory.BitVecVal(int(active_account.address, 16), 256)
self.address = active_account.address
self.block_number = symbol_factory.BitVecSym("block_number", 256)
# Ib
self.code = active_account.code if code is None else code

@ -77,7 +77,7 @@ class GlobalState:
:return:
"""
return self.world_state.accounts
return self.world_state._accounts
# TODO: remove this, as two instructions are confusing
def get_current_instruction(self) -> Dict:

@ -3,6 +3,8 @@ from copy import copy
from random import randint
from typing import Dict, List, Iterator, Optional, TYPE_CHECKING
from mythril.laser.smt import symbol_factory, Array, BitVec
from ethereum.utils import mk_contract_address
from mythril.laser.ethereum.state.account import Account
from mythril.laser.ethereum.state.annotation import StateAnnotation
@ -22,18 +24,29 @@ class WorldState:
:param transaction_sequence:
:param annotations:
"""
self.accounts = {} # type: Dict[str, Account]
self._accounts = {} # type: Dict[int, Account]
self.balances = Array("balance", 256, 256)
self.node = None # type: Optional['Node']
self.transaction_sequence = transaction_sequence or []
self._annotations = annotations or []
def __getitem__(self, item: str) -> Account:
@property
def accounts(self):
return self._accounts
def __getitem__(self, item: BitVec) -> Account:
"""Gets an account from the worldstate using item as key.
:param item: Address of the account to get
:return: Account associated with the address
"""
return self.accounts[item]
try:
return self._accounts[item.value]
except KeyError:
new_account = Account(address=item, code=None, balances=self.balances)
self._accounts[item.value] = new_account
return new_account
def __copy__(self) -> "WorldState":
"""
@ -45,12 +58,19 @@ class WorldState:
transaction_sequence=self.transaction_sequence[:],
annotations=new_annotations,
)
new_world_state.accounts = copy(self.accounts)
new_world_state.balances = copy(self.balances)
for account in self._accounts.values():
new_world_state.put_account(copy(account))
new_world_state.node = self.node
return new_world_state
def create_account(
self, balance=0, address=None, concrete_storage=False, dynamic_loader=None
self,
balance=0,
address=None,
concrete_storage=False,
dynamic_loader=None,
creator=None,
) -> Account:
"""Create non-contract account.
@ -60,14 +80,22 @@ class WorldState:
:param dynamic_loader: used for dynamically loading storage from the block chain
:return: The new account
"""
address = address if address else self._generate_new_address()
address = (
symbol_factory.BitVecVal(address, 256)
if address
else self._generate_new_address(creator)
)
new_account = Account(
address,
balance=balance,
address=address,
balances=self.balances,
dynamic_loader=dynamic_loader,
concrete_storage=concrete_storage,
)
self._put_account(new_account)
if balance:
new_account.set_balance(symbol_factory.BitVecVal(balance, 256))
self.put_account(new_account)
return new_account
def create_initialized_contract_account(self, contract_code, storage) -> None:
@ -81,10 +109,10 @@ class WorldState:
"""
# TODO: Add type hints
new_account = Account(
self._generate_new_address(), code=contract_code, balance=0
self._generate_new_address(), code=contract_code, balances=self.balances
)
new_account.storage = storage
self._put_account(new_account)
self.put_account(new_account)
def annotate(self, annotation: StateAnnotation) -> None:
"""
@ -111,19 +139,24 @@ class WorldState:
"""
return filter(lambda x: isinstance(x, annotation_type), self.annotations)
def _generate_new_address(self) -> str:
def _generate_new_address(self, creator=None) -> BitVec:
"""Generates a new address for the global state.
:return:
"""
if creator:
# TODO: Use nounce
address = "0x" + str(mk_contract_address(creator, 0).hex())
return symbol_factory.BitVecVal(int(address, 16), 256)
while True:
address = "0x" + "".join([str(hex(randint(0, 16)))[-1] for _ in range(40)])
if address not in self.accounts.keys():
return address
if address not in self._accounts.keys():
return symbol_factory.BitVecVal(int(address, 16), 256)
def _put_account(self, account: Account) -> None:
def put_account(self, account: Account) -> None:
"""
:param account:
"""
self.accounts[account.address] = account
self._accounts[account.address.value] = account
account._balances = self.balances

@ -1,4 +1,6 @@
from abc import ABC, abstractmethod
from typing import List
from mythril.laser.ethereum.state.global_state import GlobalState
class BasicSearchStrategy(ABC):
@ -7,7 +9,7 @@ class BasicSearchStrategy(ABC):
__slots__ = "work_list", "max_depth"
def __init__(self, work_list, max_depth):
self.work_list = work_list
self.work_list = work_list # type: List[GlobalState]
self.max_depth = max_depth
def __iter__(self):

@ -9,12 +9,12 @@ from mythril.laser.ethereum.cfg import NodeFlags, Node, Edge, JumpType
from mythril.laser.ethereum.evm_exceptions import StackUnderflowException
from mythril.laser.ethereum.evm_exceptions import VmException
from mythril.laser.ethereum.instructions import Instruction
from mythril.laser.ethereum.state.account import Account
from mythril.laser.ethereum.iprof import InstructionProfiler
from mythril.laser.ethereum.plugins.signals import PluginSkipWorldState
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.laser.ethereum.state.world_state import WorldState
from mythril.laser.ethereum.strategy.basic import DepthFirstSearchStrategy
from mythril.laser.ethereum.time_handler import time_handler
from mythril.laser.ethereum.plugins.signals import PluginSkipWorldState
from mythril.laser.ethereum.transaction import (
ContractCreationTransaction,
TransactionEndSignal,
@ -22,7 +22,7 @@ from mythril.laser.ethereum.transaction import (
execute_contract_creation,
execute_message_call,
)
from mythril.laser.ethereum.iprof import InstructionProfiler
from mythril.laser.smt import symbol_factory
log = logging.getLogger(__name__)
@ -46,7 +46,6 @@ class LaserEVM:
def __init__(
self,
accounts: Dict[str, Account],
dynamic_loader=None,
max_depth=float("inf"),
execution_timeout=60,
@ -57,23 +56,18 @@ class LaserEVM:
enable_iprof=False,
) -> None:
"""
:param accounts:
:param dynamic_loader:
:param max_depth:
:param execution_timeout:
:param create_timeout:
:param strategy:
:param transaction_count:
Initializes the laser evm object
:param dynamic_loader: Loads data from chain
:param max_depth: Maximum execution depth this vm should execute
:param execution_timeout: Time to take for execution
:param create_timeout: Time to take for contract creation
:param strategy: Execution search strategy
:param transaction_count: The amount of transactions to execute
:param requires_statespace: Variable indicating whether the statespace should be recorded
:param enable_iprof: Variable indicating whether instruction profiling should be turned on
"""
world_state = WorldState()
world_state.accounts = accounts
# this sets the initial world state
self.world_state = world_state
self.open_states = [world_state]
self.coverage = {} # type: Dict[str, Tuple[int, List[bool]]]
self.open_states = [] # type: List[WorldState]
self.total_states = 0
self.dynamic_loader = dynamic_loader
@ -97,6 +91,10 @@ class LaserEVM:
self._add_world_state_hooks = [] # type: List[Callable]
self._execute_state_hooks = [] # type: List[Callable]
self._start_sym_trans_hooks = [] # type: List[Callable]
self._stop_sym_trans_hooks = [] # type: List[Callable]
self._start_sym_exec_hooks = [] # type: List[Callable]
self._stop_sym_exec_hooks = [] # type: List[Callable]
@ -104,23 +102,30 @@ class LaserEVM:
log.info("LASER EVM initialized with dynamic loader: " + str(dynamic_loader))
@property
def accounts(self) -> Dict[str, Account]:
"""
:return:
"""
return self.world_state.accounts
def sym_exec(
self, main_address=None, creation_code=None, contract_name=None
self,
world_state: WorldState = None,
target_address: int = None,
creation_code: str = None,
contract_name: str = None,
) -> None:
""" Starts symbolic execution
There are two modes of execution.
Either we analyze a preconfigured configuration, in which case the world_state and target_address variables
must be supplied.
Or we execute the creation code of a contract, in which case the creation code and desired name of that
contract should be provided.
:param world_state The world state configuration from which to perform analysis
:param target_address The address of the contract account in the world state which analysis should target
:param creation_code The creation code to create the target contract in the symbolic environment
:param contract_name The name that the created account should be associated with
"""
pre_configuration_mode = world_state is not None and target_address is not None
scratch_mode = creation_code is not None and contract_name is not None
if pre_configuration_mode == scratch_mode:
raise ValueError("Symbolic execution started with invalid parameters")
:param main_address:
:param creation_code:
:param contract_name:
"""
log.debug("Starting LASER execution")
for hook in self._start_sym_exec_hooks:
hook()
@ -128,11 +133,12 @@ class LaserEVM:
time_handler.start_execution(self.execution_timeout)
self.time = datetime.now()
if main_address:
log.info("Starting message call transaction to {}".format(main_address))
self._execute_transactions(main_address)
if pre_configuration_mode:
self.open_states = [world_state]
log.info("Starting message call transaction to {}".format(target_address))
self._execute_transactions(symbol_factory.BitVecVal(target_address, 256))
elif creation_code:
elif scratch_mode:
log.info("Starting contract creation transaction")
created_account = execute_contract_creation(
self, creation_code, contract_name
@ -158,10 +164,6 @@ class LaserEVM:
len(self.edges),
self.total_states,
)
for code, coverage in self.coverage.items():
cov = sum(coverage[1]) / float(coverage[0]) * 100
log.info("Achieved {:.2f}% coverage for code: {}".format(cov, code))
if self.iprof is not None:
log.info("Instruction Statistics:\n{}".format(self.iprof))
@ -170,42 +172,25 @@ class LaserEVM:
hook()
def _execute_transactions(self, address):
"""This function executes multiple transactions on the address based on
the coverage.
"""This function executes multiple transactions on the address
:param address: Address of the contract
:return:
"""
self.coverage = {}
for i in range(self.transaction_count):
initial_coverage = self._get_covered_instructions()
self.time = datetime.now()
log.info(
"Starting message call transaction, iteration: {}, {} initial states".format(
i, len(self.open_states)
)
)
for hook in self._start_sym_trans_hooks:
hook()
execute_message_call(self, address)
end_coverage = self._get_covered_instructions()
log.info(
"Number of new instructions covered in tx %d: %d"
% (i, end_coverage - initial_coverage)
)
def _get_covered_instructions(self) -> int:
"""Gets the total number of covered instructions for all accounts in
the svm.
:return:
"""
total_covered_instructions = 0
for _, cv in self.coverage.items():
total_covered_instructions += sum(cv[1])
return total_covered_instructions
for hook in self._stop_sym_trans_hooks:
hook()
def exec(self, create=False, track_gas=False) -> Union[List[GlobalState], None]:
"""
@ -284,7 +269,6 @@ class LaserEVM:
self._execute_pre_hook(op_code, global_state)
try:
self._measure_coverage(global_state)
new_global_states = Instruction(
op_code, self.dynamic_loader, self.iprof
).evaluate(global_state)
@ -375,7 +359,7 @@ class LaserEVM:
if not revert_changes:
return_global_state.world_state = copy(global_state.world_state)
return_global_state.environment.active_account = global_state.accounts[
return_global_state.environment.active_account.address
return_global_state.environment.active_account.address.value
]
# Execute the post instruction handler
@ -389,23 +373,6 @@ class LaserEVM:
return new_global_states
def _measure_coverage(self, global_state: GlobalState) -> None:
"""
:param global_state:
"""
code = global_state.environment.code.bytecode
number_of_instructions = len(global_state.environment.code.instruction_list)
instruction_index = global_state.mstate.pc
if code not in self.coverage.keys():
self.coverage[code] = (
number_of_instructions,
[False] * number_of_instructions,
)
self.coverage[code][1][instruction_index] = True
def manage_cfg(self, opcode: str, new_states: List[GlobalState]) -> None:
"""
@ -426,19 +393,6 @@ class LaserEVM:
self._new_node_state(
state, JumpType.CONDITIONAL, state.mstate.constraints[-1]
)
elif opcode in ("CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"):
assert len(new_states) <= 1
for state in new_states:
self._new_node_state(state, JumpType.CALL)
# Keep track of added contracts so the graph can be generated properly
if (
state.environment.active_account.contract_name
not in self.world_state.accounts.keys()
):
self.world_state.accounts[
state.environment.active_account.address
] = state.environment.active_account
elif opcode == "RETURN":
for state in new_states:
self._new_node_state(state, JumpType.RETURN)
@ -528,6 +482,10 @@ class LaserEVM:
self._start_sym_exec_hooks.append(hook)
elif hook_type == "stop_sym_exec":
self._stop_sym_exec_hooks.append(hook)
elif hook_type == "start_sym_trans":
self._start_sym_trans_hooks.append(hook)
elif hook_type == "stop_sym_trans":
self._stop_sym_trans_hooks.append(hook)
else:
raise ValueError(
"Invalid hook type %s. Must be one of {add_world_state}", hook_type

@ -1,454 +0,0 @@
"""This module implements classes needed to perform taint analysis."""
import copy
import logging
from typing import List, Tuple, Union
import mythril.laser.ethereum.util as helper
from mythril.analysis.symbolic import SymExecWrapper
from mythril.laser.ethereum.cfg import JumpType, Node
from mythril.laser.ethereum.state.environment import Environment
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.laser.smt import Expression
log = logging.getLogger(__name__)
class TaintRecord:
"""TaintRecord contains tainting information for a specific (state, node)
the information specifies the taint status before executing the operation
belonging to the state."""
def __init__(self):
"""Builds a taint record."""
self.stack = []
self.memory = {}
self.storage = {}
self.states = []
def stack_tainted(self, index: int) -> Union[bool, None]:
"""Returns taint value of stack element at index.
:param index:
:return:
"""
if index < len(self.stack):
return self.stack[index]
return None
def memory_tainted(self, index: int) -> bool:
"""Returns taint value of memory element at index.
:param index:
:return:
"""
if index in self.memory.keys():
return self.memory[index]
return False
def storage_tainted(self, index: int) -> bool:
"""Returns taint value of storage element at index.
:param index:
:return:
"""
if index in self.storage.keys():
return self.storage[index]
return False
def add_state(self, state: GlobalState) -> None:
"""Adds state with this taint record.
:param state:
"""
self.states.append(state)
def clone(self) -> "TaintRecord":
"""Clones this record.
:return:
"""
clone = TaintRecord()
clone.stack = copy.deepcopy(self.stack)
clone.memory = copy.deepcopy(self.memory)
clone.storage = copy.deepcopy(self.storage)
return clone
class TaintResult:
"""Taint analysis result obtained after having ran the taint runner."""
def __init__(self):
"""Create a new tains result."""
self.records = []
def check(self, state: GlobalState, stack_index: int) -> Union[bool, None]:
"""Checks if stack variable is tainted, before executing the
instruction.
:param state: state to check variable in
:param stack_index: index of stack variable
:return: tainted
"""
record = self._try_get_record(state)
if record is None:
return None
return record.stack_tainted(stack_index)
def add_records(self, records: List[TaintRecord]) -> None:
"""Adds records to this taint result.
:param records:
"""
self.records += records
def _try_get_record(self, state: GlobalState) -> Union[TaintRecord, None]:
"""Finds record belonging to the state.
:param state:
:return:
"""
for record in self.records:
if state in record.states:
return record
return None
class TaintRunner:
"""Taint runner, is able to run taint analysis on symbolic execution
result."""
@staticmethod
def execute(
statespace: SymExecWrapper, node: Node, state: GlobalState, initial_stack=None
) -> TaintResult:
"""Runs taint analysis on the statespace.
:param initial_stack:
:param statespace: symbolic statespace to run taint analysis on
:param node: taint introduction node
:param state: taint introduction state
:return: TaintResult object containing analysis results
"""
if initial_stack is None:
initial_stack = []
result = TaintResult()
transaction_stack_length = len(node.states[0].transaction_stack)
# Build initial current_node
init_record = TaintRecord()
init_record.stack = initial_stack
state_index = node.states.index(state)
# List of (Node, TaintRecord, index)
current_nodes = [(node, init_record, state_index)]
environment = node.states[0].environment
for node, record, index in current_nodes:
records = TaintRunner.execute_node(node, record, index)
result.add_records(records)
if len(records) == 0: # continue if there is no record to work on
continue
children = TaintRunner.children(
node, statespace, environment, transaction_stack_length
)
for child in children:
current_nodes.append((child, records[-1], 0))
return result
@staticmethod
def children(
node: Node,
statespace: SymExecWrapper,
environment: Environment,
transaction_stack_length: int,
) -> List[Node]:
"""
:param node:
:param statespace:
:param environment:
:param transaction_stack_length:
:return:
"""
direct_children = [
statespace.nodes[edge.node_to]
for edge in statespace.edges
if edge.node_from == node.uid and edge.type != JumpType.Transaction
]
children = []
for child in direct_children:
if all(
len(state.transaction_stack) == transaction_stack_length
for state in child.states
):
children.append(child)
elif all(
len(state.transaction_stack) > transaction_stack_length
for state in child.states
):
children += TaintRunner.children(
child, statespace, environment, transaction_stack_length
)
return children
@staticmethod
def execute_node(
node: Node, last_record: TaintRecord, state_index=0
) -> List[TaintRecord]:
"""Runs taint analysis on a given node.
:param node: node to analyse
:param last_record: last taint record to work from
:param state_index: state index to start from
:return: List of taint records linked to the states in this node
"""
records = [last_record]
for index in range(state_index, len(node.states)):
current_state = node.states[index]
records.append(TaintRunner.execute_state(records[-1], current_state))
return records[1:]
@staticmethod
def execute_state(record: TaintRecord, state: GlobalState) -> TaintRecord:
"""
:param record:
:param state:
:return:
"""
assert len(state.mstate.stack) == len(record.stack)
""" Runs taint analysis on a state """
record.add_state(state)
new_record = record.clone()
# Apply Change
op = state.get_current_instruction()["opcode"]
if op in TaintRunner.stack_taint_table.keys():
mutator = TaintRunner.stack_taint_table[op]
TaintRunner.mutate_stack(new_record, mutator)
elif op.startswith("PUSH"):
TaintRunner.mutate_push(op, new_record)
elif op.startswith("DUP"):
TaintRunner.mutate_dup(op, new_record)
elif op.startswith("SWAP"):
TaintRunner.mutate_swap(op, new_record)
elif op is "MLOAD":
TaintRunner.mutate_mload(new_record, state.mstate.stack[-1])
elif op.startswith("MSTORE"):
TaintRunner.mutate_mstore(new_record, state.mstate.stack[-1])
elif op is "SLOAD":
TaintRunner.mutate_sload(new_record, state.mstate.stack[-1])
elif op is "SSTORE":
TaintRunner.mutate_sstore(new_record, state.mstate.stack[-1])
elif op.startswith("LOG"):
TaintRunner.mutate_log(new_record, op)
elif op in ("CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"):
TaintRunner.mutate_call(new_record, op)
else:
log.debug("Unknown operation encountered: {}".format(op))
return new_record
@staticmethod
def mutate_stack(record: TaintRecord, mutator: Tuple[int, int]) -> None:
"""
:param record:
:param mutator:
"""
pop, push = mutator
values = []
for i in range(pop):
values.append(record.stack.pop())
taint = any(values)
for i in range(push):
record.stack.append(taint)
@staticmethod
def mutate_push(op: str, record: TaintRecord) -> None:
"""
:param op:
:param record:
"""
TaintRunner.mutate_stack(record, (0, 1))
@staticmethod
def mutate_dup(op: str, record: TaintRecord) -> None:
"""
:param op:
:param record:
"""
depth = int(op[3:])
index = len(record.stack) - depth
record.stack.append(record.stack[index])
@staticmethod
def mutate_swap(op: str, record: TaintRecord) -> None:
"""
:param op:
:param record:
"""
depth = int(op[4:])
l = len(record.stack) - 1
i = l - depth
record.stack[l], record.stack[i] = record.stack[i], record.stack[l]
@staticmethod
def mutate_mload(record: TaintRecord, op0: Expression) -> None:
"""
:param record:
:param op0:
:return:
"""
_ = record.stack.pop()
try:
index = helper.get_concrete_int(op0)
except TypeError:
log.debug("Can't MLOAD taint track symbolically")
record.stack.append(False)
return
record.stack.append(record.memory_tainted(index))
@staticmethod
def mutate_mstore(record: TaintRecord, op0: Expression) -> None:
"""
:param record:
:param op0:
:return:
"""
_, value_taint = record.stack.pop(), record.stack.pop()
try:
index = helper.get_concrete_int(op0)
except TypeError:
log.debug("Can't mstore taint track symbolically")
return
record.memory[index] = value_taint
@staticmethod
def mutate_sload(record: TaintRecord, op0: Expression) -> None:
"""
:param record:
:param op0:
:return:
"""
_ = record.stack.pop()
try:
index = helper.get_concrete_int(op0)
except TypeError:
log.debug("Can't MLOAD taint track symbolically")
record.stack.append(False)
return
record.stack.append(record.storage_tainted(index))
@staticmethod
def mutate_sstore(record: TaintRecord, op0: Expression) -> None:
"""
:param record:
:param op0:
:return:
"""
_, value_taint = record.stack.pop(), record.stack.pop()
try:
index = helper.get_concrete_int(op0)
except TypeError:
log.debug("Can't mstore taint track symbolically")
return
record.storage[index] = value_taint
@staticmethod
def mutate_log(record: TaintRecord, op: str) -> None:
"""
:param record:
:param op:
"""
depth = int(op[3:])
for _ in range(depth + 2):
record.stack.pop()
@staticmethod
def mutate_call(record: TaintRecord, op: str) -> None:
"""
:param record:
:param op:
"""
pops = 6
if op in ("CALL", "CALLCODE"):
pops += 1
for _ in range(pops):
record.stack.pop()
record.stack.append(False)
stack_taint_table = {
# instruction: (taint source, taint target)
"POP": (1, 0),
"ADD": (2, 1),
"MUL": (2, 1),
"SUB": (2, 1),
"AND": (2, 1),
"OR": (2, 1),
"XOR": (2, 1),
"NOT": (1, 1),
"BYTE": (2, 1),
"DIV": (2, 1),
"MOD": (2, 1),
"SDIV": (2, 1),
"SMOD": (2, 1),
"ADDMOD": (3, 1),
"MULMOD": (3, 1),
"EXP": (2, 1),
"SIGNEXTEND": (2, 1),
"LT": (2, 1),
"GT": (2, 1),
"SLT": (2, 1),
"SGT": (2, 1),
"EQ": (2, 1),
"ISZERO": (1, 1),
"CALLVALUE": (0, 1),
"CALLDATALOAD": (1, 1),
"CALLDATACOPY": (3, 0), # todo
"CALLDATASIZE": (0, 1),
"ADDRESS": (0, 1),
"BALANCE": (1, 1),
"ORIGIN": (0, 1),
"CALLER": (0, 1),
"CODESIZE": (0, 1),
"SHA3": (2, 1),
"GASPRICE": (0, 1),
"CODECOPY": (3, 0),
"EXTCODESIZE": (1, 1),
"EXTCODECOPY": (4, 0),
"RETURNDATASIZE": (0, 1),
"BLOCKHASH": (1, 1),
"COINBASE": (0, 1),
"TIMESTAMP": (0, 1),
"NUMBER": (0, 1),
"DIFFICULTY": (0, 1),
"GASLIMIT": (0, 1),
"JUMP": (1, 0),
"JUMPI": (2, 0),
"PC": (0, 1),
"MSIZE": (0, 1),
"GAS": (0, 1),
"CREATE": (3, 1),
"CREATE2": (4, 1),
"RETURN": (2, 0),
}

@ -2,25 +2,32 @@
symbolic values."""
import logging
from mythril.laser.smt import symbol_factory
from mythril.disassembler.disassembly import Disassembly
from mythril.laser.ethereum.cfg import Node, Edge, JumpType
from mythril.laser.ethereum.state.calldata import BaseCalldata, SymbolicCalldata
from mythril.laser.ethereum.state.account import Account
from mythril.laser.ethereum.state.calldata import SymbolicCalldata
from mythril.laser.ethereum.state.world_state import WorldState
from mythril.laser.ethereum.transaction.transaction_models import (
MessageCallTransaction,
ContractCreationTransaction,
get_next_transaction_id,
BaseTransaction,
)
from mythril.laser.smt import symbol_factory, Or, BitVec
log = logging.getLogger(__name__)
CREATOR_ADDRESS = 0xAFFEAFFEAFFEAFFEAFFEAFFEAFFEAFFEAFFEAFFE
ATTACKER_ADDRESS = 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF
ACTOR_ADDRESSES = [
symbol_factory.BitVecVal(0xAFFEAFFEAFFEAFFEAFFEAFFEAFFEAFFEAFFEAFFE, 256),
symbol_factory.BitVecVal(0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF, 256),
symbol_factory.BitVecVal(0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEE, 256),
]
def execute_message_call(laser_evm, callee_address: str) -> None:
def execute_message_call(laser_evm, callee_address: BitVec) -> None:
"""Executes a message call transaction from all open states.
:param laser_evm:
@ -46,7 +53,9 @@ def execute_message_call(laser_evm, callee_address: str) -> None:
origin=symbol_factory.BitVecSym(
"origin{}".format(next_transaction_id), 256
),
caller=symbol_factory.BitVecVal(ATTACKER_ADDRESS, 256),
caller=symbol_factory.BitVecSym(
"sender_{}".format(next_transaction_id), 256
),
callee_account=open_world_state[callee_address],
call_data=SymbolicCalldata(next_transaction_id),
call_value=symbol_factory.BitVecSym(
@ -69,12 +78,14 @@ def execute_contract_creation(
:return:
"""
# TODO: Resolve circular import between .transaction and ..svm to import LaserEVM here
open_states = laser_evm.open_states[:]
del laser_evm.open_states[:]
new_account = laser_evm.world_state.create_account(
0, concrete_storage=True, dynamic_loader=None
world_state = WorldState()
open_states = [world_state]
new_account = world_state.create_account(
0, concrete_storage=True, dynamic_loader=None, creator=CREATOR_ADDRESS
)
if contract_name:
new_account.contract_name = contract_name
@ -104,7 +115,7 @@ def execute_contract_creation(
return new_account
def _setup_global_state_for_execution(laser_evm, transaction) -> None:
def _setup_global_state_for_execution(laser_evm, transaction: BaseTransaction) -> None:
"""Sets up global state and cfg for a transactions execution.
:param laser_evm:
@ -114,6 +125,10 @@ def _setup_global_state_for_execution(laser_evm, transaction) -> None:
global_state = transaction.initial_global_state()
global_state.transaction_stack.append((transaction, None))
global_state.mstate.constraints.append(
Or(*[transaction.caller == actor for actor in ACTOR_ADDRESSES])
)
new_node = Node(
global_state.environment.active_account.contract_name,
function_name=global_state.environment.active_function_name,

@ -111,8 +111,19 @@ class BaseTransaction:
# Initialize the execution environment
global_state = GlobalState(self.world_state, environment, None)
global_state.environment.active_function_name = active_function
sender = environment.sender
receiver = environment.active_account.address
value = environment.callvalue
global_state.world_state.balances[sender] -= value
global_state.world_state.balances[receiver] += value
return global_state
def initial_global_state(self) -> GlobalState:
raise NotImplementedError
class MessageCallTransaction(BaseTransaction):
"""Transaction object models an transaction."""
@ -143,6 +154,7 @@ class MessageCallTransaction(BaseTransaction):
:param revert:
"""
self.return_data = return_data
raise TransactionEndSignal(global_state, revert)
@ -180,16 +192,19 @@ class ContractCreationTransaction(BaseTransaction):
:param revert:
"""
if (
not all([isinstance(element, int) for element in return_data])
return_data is None
or not all([isinstance(element, int) for element in return_data])
or len(return_data) == 0
):
self.return_data = None
raise TransactionEndSignal(global_state)
raise TransactionEndSignal(global_state, revert=revert)
contract_code = bytes.hex(array.array("B", return_data).tostring())
global_state.environment.active_account.code = Disassembly(contract_code)
self.return_data = global_state.environment.active_account.address
global_state.environment.active_account.code.assign_bytecode(contract_code)
self.return_data = str(
hex(global_state.environment.active_account.address.value)
)
assert global_state.environment.active_account.code.instruction_list != []
raise TransactionEndSignal(global_state, revert=revert)

@ -90,15 +90,16 @@ def And(*args: Union[Bool, bool]) -> Bool:
return Bool(z3.And([a.raw for a in args_list]), union)
def Or(a: Bool, b: Bool) -> Bool:
def Or(*args: Union[Bool, bool]) -> Bool:
"""Create an or expression.
:param a:
:param b:
:return:
"""
union = a.annotations + b.annotations
return Bool(z3.Or(a.raw, b.raw), annotations=union)
args_list = [arg if isinstance(arg, Bool) else Bool(arg) for arg in args]
union = [arg.annotations for arg in args_list]
return Bool(z3.Or([a.raw for a in args_list]), annotations=union)
def Not(a: Bool) -> Bool:

@ -1,721 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""mythril.py: Bug hunting on the Ethereum blockchain
http://www.github.com/b-mueller/mythril
"""
import codecs
import logging
import os
import platform
import re
import traceback
from pathlib import Path
from shutil import copyfile
from configparser import ConfigParser
import solc
from ethereum import utils
from solc.exceptions import SolcError
from mythril.ethereum import util
from mythril.ethereum.evmcontract import EVMContract
from mythril.ethereum.interface.rpc.client import EthJsonRpc
from mythril.ethereum.interface.rpc.exceptions import ConnectionError
from mythril.solidity.soliditycontract import SolidityContract, get_contracts_from_file
from mythril.support import signatures
from mythril.support.source_support import Source
from mythril.support.loader import DynLoader
from mythril.exceptions import CompilerError, NoContractFoundError, CriticalError
from mythril.analysis.symbolic import SymExecWrapper
from mythril.analysis.callgraph import generate_graph
from mythril.analysis.traceexplore import get_serializable_statespace
from mythril.analysis.security import fire_lasers, retrieve_callback_issues
from mythril.analysis.report import Report
from mythril.support.truffle import analyze_truffle_project
from mythril.ethereum.interface.leveldb.client import EthLevelDB
from mythril.laser.smt import SolverStatistics
from mythril.support.start_time import StartTime
log = logging.getLogger(__name__)
class Mythril(object):
"""Mythril main interface class.
1. create mythril object
2. set rpc or leveldb interface if needed
3. load contracts (from solidity, bytecode, address)
4. fire_lasers
.. code-block:: python
mythril = Mythril()
mythril.set_api_rpc_infura()
# (optional) other API adapters
mythril.set_api_rpc(args)
mythril.set_api_rpc_localhost()
mythril.set_api_leveldb(path)
# (optional) other func
mythril.analyze_truffle_project(args)
mythril.search_db(args)
# load contract
mythril.load_from_bytecode(bytecode)
mythril.load_from_address(address)
mythril.load_from_solidity(solidity_file)
# analyze
print(mythril.fire_lasers(args).as_text())
# (optional) graph
for contract in mythril.contracts:
# prints html or save it to file
print(mythril.graph_html(args))
# (optional) other funcs
mythril.dump_statespaces(args)
mythril.disassemble(contract)
mythril.get_state_variable_from_storage(args)
"""
def __init__(
self,
solv=None,
solc_args=None,
dynld=False,
enable_online_lookup=False,
onchain_storage_access=True,
):
self.solv = solv
self.solc_args = solc_args
self.dynld = dynld
self.onchain_storage_access = onchain_storage_access
self.enable_online_lookup = enable_online_lookup
self.mythril_dir = self._init_mythril_dir()
# tries mythril_dir/signatures.db by default (provide path= arg to make this configurable)
self.sigs = signatures.SignatureDB(
enable_online_lookup=self.enable_online_lookup
)
self.solc_binary = self._init_solc_binary(solv)
self.config_path = os.path.join(self.mythril_dir, "config.ini")
self.leveldb_dir = self._init_config()
self.eth = None # ethereum API client
self.eth_db = None # ethereum LevelDB client
self.contracts = [] # loaded contracts
@staticmethod
def _init_mythril_dir():
try:
mythril_dir = os.environ["MYTHRIL_DIR"]
except KeyError:
mythril_dir = os.path.join(os.path.expanduser("~"), ".mythril")
if not os.path.exists(mythril_dir):
# Initialize data directory
log.info("Creating mythril data directory")
os.mkdir(mythril_dir)
db_path = str(Path(mythril_dir) / "signatures.db")
if not os.path.exists(db_path):
# if the default mythril dir doesn't contain a signature DB
# initialize it with the default one from the project root
asset_dir = Path(__file__).parent / "support" / "assets"
copyfile(str(asset_dir / "signatures.db"), db_path)
return mythril_dir
def _init_config(self):
"""If no config file exists, create it and add default options.
Default LevelDB path is specified based on OS
dynamic loading is set to infura by default in the file
Returns: leveldb directory
"""
system = platform.system().lower()
leveldb_fallback_dir = os.path.expanduser("~")
if system.startswith("darwin"):
leveldb_fallback_dir = os.path.join(
leveldb_fallback_dir, "Library", "Ethereum"
)
elif system.startswith("windows"):
leveldb_fallback_dir = os.path.join(
leveldb_fallback_dir, "AppData", "Roaming", "Ethereum"
)
else:
leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, ".ethereum")
leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "geth", "chaindata")
if not os.path.exists(self.config_path):
log.info("No config file found. Creating default: " + self.config_path)
open(self.config_path, "a").close()
config = ConfigParser(allow_no_value=True)
config.optionxform = str
config.read(self.config_path, "utf-8")
if "defaults" not in config.sections():
self._add_default_options(config)
if not config.has_option("defaults", "leveldb_dir"):
self._add_leveldb_option(config, leveldb_fallback_dir)
if not config.has_option("defaults", "dynamic_loading"):
self._add_dynamic_loading_option(config)
with codecs.open(self.config_path, "w", "utf-8") as fp:
config.write(fp)
leveldb_dir = config.get(
"defaults", "leveldb_dir", fallback=leveldb_fallback_dir
)
return os.path.expanduser(leveldb_dir)
@staticmethod
def _add_default_options(config):
config.add_section("defaults")
@staticmethod
def _add_leveldb_option(config, leveldb_fallback_dir):
config.set("defaults", "#Default chaindata locations:")
config.set("defaults", "#– Mac: ~/Library/Ethereum/geth/chaindata")
config.set("defaults", "#– Linux: ~/.ethereum/geth/chaindata")
config.set(
"defaults",
"#– Windows: %USERPROFILE%\\AppData\\Roaming\\Ethereum\\geth\\chaindata",
)
config.set("defaults", "leveldb_dir", leveldb_fallback_dir)
@staticmethod
def _add_dynamic_loading_option(config):
config.set("defaults", "#– To connect to Infura use dynamic_loading: infura")
config.set(
"defaults",
"#– To connect to Rpc use "
"dynamic_loading: HOST:PORT / ganache / infura-[network_name]",
)
config.set(
"defaults", "#– To connect to local host use dynamic_loading: localhost"
)
config.set("defaults", "dynamic_loading", "infura")
def analyze_truffle_project(self, *args, **kwargs):
"""
:param args:
:param kwargs:
:return:
"""
return analyze_truffle_project(
self.sigs, *args, **kwargs
) # just passthru by passing signatures for now
@staticmethod
def _init_solc_binary(version):
"""Figure out solc binary and version.
Only proper versions are supported. No nightlies, commits etc (such as available in remix).
"""
if not version:
return os.environ.get("SOLC") or "solc"
# tried converting input to semver, seemed not necessary so just slicing for now
main_version = solc.main.get_solc_version_string()
main_version_number = re.match(r"\d+.\d+.\d+", main_version)
if main_version is None:
raise CriticalError(
"Could not extract solc version from string {}".format(main_version)
)
if version == main_version_number:
log.info("Given version matches installed version")
solc_binary = os.environ.get("SOLC") or "solc"
else:
solc_binary = util.solc_exists(version)
if solc_binary:
log.info("Given version is already installed")
else:
try:
solc.install_solc("v" + version)
solc_binary = util.solc_exists(version)
if not solc_binary:
raise SolcError()
except SolcError:
raise CriticalError(
"There was an error when trying to install the specified solc version"
)
log.info("Setting the compiler to %s", solc_binary)
return solc_binary
def set_api_leveldb(self, leveldb):
"""
:param leveldb:
:return:
"""
self.eth_db = EthLevelDB(leveldb)
self.eth = self.eth_db
return self.eth
def set_api_rpc_infura(self):
"""Set the RPC mode to INFURA on mainnet."""
self.eth = EthJsonRpc("mainnet.infura.io", 443, True)
log.info("Using INFURA for RPC queries")
def set_api_rpc(self, rpc=None, rpctls=False):
"""
:param rpc:
:param rpctls:
"""
if rpc == "ganache":
rpcconfig = ("localhost", 8545, False)
else:
m = re.match(r"infura-(.*)", rpc)
if m and m.group(1) in ["mainnet", "rinkeby", "kovan", "ropsten"]:
rpcconfig = (m.group(1) + ".infura.io", 443, True)
else:
try:
host, port = rpc.split(":")
rpcconfig = (host, int(port), rpctls)
except ValueError:
raise CriticalError(
"Invalid RPC argument, use 'ganache', 'infura-[network]' or 'HOST:PORT'"
)
if rpcconfig:
self.eth = EthJsonRpc(rpcconfig[0], int(rpcconfig[1]), rpcconfig[2])
log.info("Using RPC settings: %s" % str(rpcconfig))
else:
raise CriticalError("Invalid RPC settings, check help for details.")
def set_api_rpc_localhost(self):
"""Set the RPC mode to a local instance."""
self.eth = EthJsonRpc("localhost", 8545)
log.info("Using default RPC settings: http://localhost:8545")
def set_api_from_config_path(self):
"""Set the RPC mode based on a given config file."""
config = ConfigParser(allow_no_value=False)
config.optionxform = str
config.read(self.config_path, "utf-8")
if config.has_option("defaults", "dynamic_loading"):
dynamic_loading = config.get("defaults", "dynamic_loading")
else:
dynamic_loading = "infura"
if dynamic_loading == "infura":
self.set_api_rpc_infura()
elif dynamic_loading == "localhost":
self.set_api_rpc_localhost()
else:
self.set_api_rpc(dynamic_loading)
def search_db(self, search):
"""
:param search:
"""
def search_callback(_, address, balance):
"""
:param _:
:param address:
:param balance:
"""
print("Address: " + address + ", balance: " + str(balance))
try:
self.eth_db.search(search, search_callback)
except SyntaxError:
raise CriticalError("Syntax error in search expression.")
def contract_hash_to_address(self, hash):
"""
:param hash:
"""
if not re.match(r"0x[a-fA-F0-9]{64}", hash):
raise CriticalError("Invalid address hash. Expected format is '0x...'.")
print(self.eth_db.contract_hash_to_address(hash))
def load_from_bytecode(self, code, bin_runtime=False, address=None):
"""
:param code:
:param bin_runtime:
:param address:
:return:
"""
if address is None:
address = util.get_indexed_address(0)
if bin_runtime:
self.contracts.append(
EVMContract(
code=code,
name="MAIN",
enable_online_lookup=self.enable_online_lookup,
)
)
else:
self.contracts.append(
EVMContract(
creation_code=code,
name="MAIN",
enable_online_lookup=self.enable_online_lookup,
)
)
return address, self.contracts[-1] # return address and contract object
def load_from_address(self, address):
"""
:param address:
:return:
"""
if not re.match(r"0x[a-fA-F0-9]{40}", address):
raise CriticalError("Invalid contract address. Expected format is '0x...'.")
try:
code = self.eth.eth_getCode(address)
except FileNotFoundError as e:
raise CriticalError("IPC error: " + str(e))
except ConnectionError:
raise CriticalError(
"Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly."
)
except Exception as e:
raise CriticalError("IPC / RPC error: " + str(e))
else:
if code == "0x" or code == "0x0":
raise CriticalError(
"Received an empty response from eth_getCode. Check the contract address and verify that you are on the correct chain."
)
else:
self.contracts.append(
EVMContract(
code,
name=address,
enable_online_lookup=self.enable_online_lookup,
)
)
return address, self.contracts[-1] # return address and contract object
def load_from_solidity(self, solidity_files):
"""
:param solidity_files:
:return:
"""
address = util.get_indexed_address(0)
contracts = []
for file in solidity_files:
if ":" in file:
file, contract_name = file.split(":")
else:
contract_name = None
file = os.path.expanduser(file)
try:
# import signatures from solidity source
self.sigs.import_solidity_file(
file, solc_binary=self.solc_binary, solc_args=self.solc_args
)
if contract_name is not None:
contract = SolidityContract(
input_file=file,
name=contract_name,
solc_args=self.solc_args,
solc_binary=self.solc_binary,
)
self.contracts.append(contract)
contracts.append(contract)
else:
for contract in get_contracts_from_file(
input_file=file,
solc_args=self.solc_args,
solc_binary=self.solc_binary,
):
self.contracts.append(contract)
contracts.append(contract)
except FileNotFoundError:
raise CriticalError("Input file not found: " + file)
except CompilerError as e:
raise CriticalError(e)
except NoContractFoundError:
log.error(
"The file " + file + " does not contain a compilable contract."
)
return address, contracts
def dump_statespace(
self,
strategy,
contract,
address=None,
max_depth=None,
execution_timeout=None,
create_timeout=None,
enable_iprof=False,
):
"""
:param strategy:
:param contract:
:param address:
:param max_depth:
:param execution_timeout:
:param create_timeout:
:return:
"""
sym = SymExecWrapper(
contract,
address,
strategy,
dynloader=DynLoader(
self.eth,
storage_loading=self.onchain_storage_access,
contract_loading=self.dynld,
),
max_depth=max_depth,
execution_timeout=execution_timeout,
create_timeout=create_timeout,
enable_iprof=enable_iprof,
)
return get_serializable_statespace(sym)
def graph_html(
self,
strategy,
contract,
address,
max_depth=None,
enable_physics=False,
phrackify=False,
execution_timeout=None,
create_timeout=None,
enable_iprof=False,
):
"""
:param strategy:
:param contract:
:param address:
:param max_depth:
:param enable_physics:
:param phrackify:
:param execution_timeout:
:param create_timeout:
:return:
"""
sym = SymExecWrapper(
contract,
address,
strategy,
dynloader=DynLoader(
self.eth,
storage_loading=self.onchain_storage_access,
contract_loading=self.dynld,
),
max_depth=max_depth,
execution_timeout=execution_timeout,
create_timeout=create_timeout,
enable_iprof=enable_iprof,
)
return generate_graph(sym, physics=enable_physics, phrackify=phrackify)
def fire_lasers(
self,
strategy,
contracts=None,
address=None,
modules=None,
verbose_report=False,
max_depth=None,
execution_timeout=None,
create_timeout=None,
transaction_count=None,
enable_iprof=False,
):
"""
:param strategy:
:param contracts:
:param address:
:param modules:
:param verbose_report:
:param max_depth:
:param execution_timeout:
:param create_timeout:
:param transaction_count:
:return:
"""
all_issues = []
SolverStatistics().enabled = True
exceptions = []
for contract in contracts or self.contracts:
StartTime() # Reinitialize start time for new contracts
try:
sym = SymExecWrapper(
contract,
address,
strategy,
dynloader=DynLoader(
self.eth,
storage_loading=self.onchain_storage_access,
contract_loading=self.dynld,
),
max_depth=max_depth,
execution_timeout=execution_timeout,
create_timeout=create_timeout,
transaction_count=transaction_count,
modules=modules,
compulsory_statespace=False,
enable_iprof=enable_iprof,
)
issues = fire_lasers(sym, modules)
except KeyboardInterrupt:
log.critical("Keyboard Interrupt")
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)
exceptions.append(traceback.format_exc())
for issue in issues:
issue.add_code_info(contract)
all_issues += issues
log.info("Solver statistics: \n{}".format(str(SolverStatistics())))
source_data = Source()
source_data.get_source_from_contracts_list(self.contracts)
# Finally, output the results
report = Report(verbose_report, source_data, exceptions=exceptions)
for issue in all_issues:
report.append_issue(issue)
return report
def get_state_variable_from_storage(self, address, params=None):
"""
:param address:
:param params:
:return:
"""
if params is None:
params = []
(position, length, mappings) = (0, 1, [])
try:
if params[0] == "mapping":
if len(params) < 3:
raise CriticalError("Invalid number of parameters.")
position = int(params[1])
position_formatted = utils.zpad(utils.int_to_big_endian(position), 32)
for i in range(2, len(params)):
key = bytes(params[i], "utf8")
key_formatted = utils.rzpad(key, 32)
mappings.append(
int.from_bytes(
utils.sha3(key_formatted + position_formatted),
byteorder="big",
)
)
length = len(mappings)
if length == 1:
position = mappings[0]
else:
if len(params) >= 4:
raise CriticalError("Invalid number of parameters.")
if len(params) >= 1:
position = int(params[0])
if len(params) >= 2:
length = int(params[1])
if len(params) == 3 and params[2] == "array":
position_formatted = utils.zpad(
utils.int_to_big_endian(position), 32
)
position = int.from_bytes(
utils.sha3(position_formatted), byteorder="big"
)
except ValueError:
raise CriticalError(
"Invalid storage index. Please provide a numeric value."
)
outtxt = []
try:
if length == 1:
outtxt.append(
"{}: {}".format(
position, self.eth.eth_getStorageAt(address, position)
)
)
else:
if len(mappings) > 0:
for i in range(0, len(mappings)):
position = mappings[i]
outtxt.append(
"{}: {}".format(
hex(position),
self.eth.eth_getStorageAt(address, position),
)
)
else:
for i in range(position, position + length):
outtxt.append(
"{}: {}".format(
hex(i), self.eth.eth_getStorageAt(address, i)
)
)
except FileNotFoundError as e:
raise CriticalError("IPC error: " + str(e))
except ConnectionError:
raise CriticalError(
"Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly."
)
return "\n".join(outtxt)
@staticmethod
def disassemble(contract):
"""
:param contract:
:return:
"""
return contract.get_easm()
@staticmethod
def hash_for_function_signature(sig):
"""
:param sig:
:return:
"""
return "0x%s" % utils.sha3(sig)[:4].hex()

@ -166,7 +166,7 @@ class MythrilAnalyzer:
source_data = Source()
source_data.get_source_from_contracts_list(self.contracts)
# Finally, output the results
report = Report(verbose_report, source_data, exceptions=exceptions)
report = Report(verbose_report, contracts=self.contracts, exceptions=exceptions)
for issue in all_issues:
report.append_issue(issue)

@ -1,5 +1,7 @@
"""This module contains representation classes for Solidity files, contracts
and source mappings."""
from typing import Dict, Set
import mythril.laser.ethereum.util as helper
from mythril.ethereum.evmcontract import EVMContract
from mythril.ethereum.util import get_solc_json
@ -20,9 +22,16 @@ class SourceMapping:
class SolidityFile:
"""Representation of a file containing Solidity code."""
def __init__(self, filename, data):
def __init__(self, filename: str, data: str, full_contract_src_maps: Set[str]):
"""
Metadata class containing data regarding a specific solidity file
:param filename: The filename of the solidity file
:param data: The code of the solidity file
:param full_contract_src_maps: The set of contract source mappings of all the contracts in the file
"""
self.filename = filename
self.data = data
self.full_contract_src_maps = full_contract_src_maps
class SourceCodeInfo:
@ -69,7 +78,12 @@ class SolidityContract(EVMContract):
for filename in data["sourceList"]:
with open(filename, "r", encoding="utf-8") as file:
code = file.read()
self.solidity_files.append(SolidityFile(filename, code))
full_contract_src_maps = self.get_full_contract_src_maps(
data["sources"][filename]["AST"]
)
self.solidity_files.append(
SolidityFile(filename, code, full_contract_src_maps)
)
has_contract = False
@ -117,6 +131,19 @@ class SolidityContract(EVMContract):
super().__init__(code, creation_code, name=name)
@staticmethod
def get_full_contract_src_maps(ast: Dict) -> Set[str]:
"""
Takes a solc AST and gets the src mappings for all the contracts defined in the top level of the ast
:param ast: AST of the contract
:return: The source maps
"""
source_maps = set()
for child in ast["children"]:
if "contractKind" in child["attributes"]:
source_maps.add(child["src"])
return source_maps
def get_source_info(self, address, constructor=False):
"""
@ -140,6 +167,26 @@ class SolidityContract(EVMContract):
lineno = mappings[index].lineno
return SourceCodeInfo(filename, lineno, code, mappings[index].solc_mapping)
def _is_autogenerated_code(self, offset: int, length: int, file_index: int) -> bool:
"""
Checks whether the code is autogenerated or not
:param offset: offset of the code
:param length: length of the code
:param file_index: file the code corresponds to
:return: True if the code is internally generated, else false
"""
# Handle internal compiler files
if file_index == -1:
return True
# Handle the common code src map for the entire code.
if (
"{}:{}:{}".format(offset, length, file_index)
in self.solidity_files[file_index].full_contract_src_maps
):
return True
return False
def _get_solc_mappings(self, srcmap, constructor=False):
"""
@ -161,7 +208,8 @@ class SolidityContract(EVMContract):
if len(mapping) > 2 and len(mapping[2]) > 0:
idx = int(mapping[2])
if idx == -1:
if self._is_autogenerated_code(offset, length, idx):
lineno = None
else:
lineno = (

@ -58,17 +58,15 @@ class DynLoader:
return data
def dynld(self, contract_address, dependency_address):
def dynld(self, dependency_address):
"""
:param contract_address:
:param dependency_address:
:return:
"""
if not self.contract_loading:
raise ValueError("Cannot load contract when contract_loading flag is false")
log.debug("Dynld at contract " + contract_address + ": " + dependency_address)
log.debug("Dynld at contract " + dependency_address)
# Ensure that dependency_address is the correct length, with 0s prepended as needed.
dependency_address = (

@ -5,9 +5,7 @@ from mythril.ethereum.evmcontract import EVMContract
class Source:
"""Class to handle to source data"""
def __init__(
self, source_type=None, source_format=None, source_list=None, meta=None
):
def __init__(self, source_type=None, source_format=None, source_list=None):
"""
:param source_type: whether it is a solidity-file or evm-bytecode
:param source_format: whether it is bytecode, ethereum-address or text
@ -17,7 +15,7 @@ class Source:
self.source_type = source_type
self.source_format = source_format
self.source_list = source_list or []
self.meta = meta
self._source_hash = []
def get_source_from_contracts_list(self, contracts):
"""
@ -32,16 +30,34 @@ class Source:
self.source_format = "text"
for contract in contracts:
self.source_list += [file.filename for file in contract.solidity_files]
self._source_hash.append(contract.bytecode_hash)
self._source_hash.append(contract.creation_bytecode_hash)
elif isinstance(contracts[0], EVMContract):
self.source_format = "evm-byzantium-bytecode"
self.source_type = (
"raw-bytecode" if contracts[0].name == "MAIN" else "ethereum-address"
"ethereum-address"
if len(contracts[0].name) == 42 and contracts[0].name[0:2] == "0x"
else "raw-bytecode"
)
for contract in contracts:
if contract.creation_code:
self.source_list.append(contract.creation_bytecode_hash)
if contract.code:
self.source_list.append(contract.bytecode_hash)
self._source_hash = self.source_list
else:
assert False # Fail hard
def get_source_index(self, bytecode_hash: str) -> int:
"""
Find the contract index in the list
:param bytecode_hash: The contract hash
:return: The index of the contract in the _source_hash list
"""
# TODO: Add this part to exception logs
try:
return self._source_hash.index(bytecode_hash)
except ValueError:
self._source_hash.append(bytecode_hash)
return len(self._source_hash) - 1

@ -1,5 +1,9 @@
"""This module contains utility functions for the Mythril support package."""
from typing import Dict
import logging
import _pysha3 as sha3
log = logging.getLogger(__name__)
class Singleton(type):
@ -20,3 +24,18 @@ class Singleton(type):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
def get_code_hash(code: str) -> str:
"""
:param code: bytecode
:return: Returns hash of the given bytecode
"""
code = code[2:] if code[:2] == "0x" else code
try:
keccak = sha3.keccak_256()
keccak.update(bytes.fromhex(code))
return "0x" + keccak.hexdigest()
except ValueError:
log.debug("Unable to change the bytecode to bytes. Bytecode: {}".format(code))
return ""

@ -4,4 +4,4 @@ This file is suitable for sourcing inside POSIX shell, e.g. bash as well
as for importing into Python.
"""
VERSION = "v0.20.2" # NOQA
VERSION = "v0.20.6" # NOQA

@ -3,7 +3,7 @@ configparser>=3.5.0
coverage
py_ecc==1.4.2
eth_abi==1.3.0
eth-account>=0.1.0a2
eth-account>=0.1.0a2,<=0.3.0
ethereum>=2.3.2
ethereum-input-decoder>=0.2.2
eth-hash>=0.1.0
@ -25,5 +25,6 @@ pytest_mock
requests
rlp>=1.0.1
transaction>=2.2.1
z3-solver-mythril>=4.8.4.1
z3-solver>=4.8.5.0
pysha3
matplotlib

@ -75,13 +75,13 @@ setup(
"coloredlogs>=10.0",
"py_ecc==1.4.2",
"ethereum>=2.3.2",
"z3-solver-mythril>=4.8.4.1",
"z3-solver>=4.8.5.0",
"requests",
"py-solc",
"plyvel",
"eth_abi==1.3.0",
"eth-utils>=1.0.1",
"eth-account>=0.1.0a2",
"eth-account>=0.1.0a2,<=0.3.0",
"eth-hash>=0.1.0",
"eth-keyfile>=0.5.1",
"eth-keys>=0.2.0b3",
@ -97,6 +97,7 @@ setup(
"configparser>=3.5.0",
"persistent>=4.2.0",
"ethereum-input-decoder>=0.2.2",
"matplotlib",
],
tests_require=["mypy", "pytest>=3.6.0", "pytest_mock", "pytest-cov"],
python_requires=">=3.5",

@ -1,8 +1,9 @@
from mythril.laser.ethereum.svm import LaserEVM
from mythril.laser.ethereum.state.account import Account
from mythril.laser.ethereum.state.world_state import WorldState
from mythril.disassembler.disassembly import Disassembly
from mythril.laser.ethereum.transaction.concolic import execute_message_call
from mythril.laser.smt import Expression, BitVec
from mythril.laser.smt import Expression, BitVec, symbol_factory
from mythril.analysis.solver import get_model
from datetime import datetime
@ -116,26 +117,30 @@ def test_vmtest(
# Arrange
if test_name in ignored_test_names:
return
accounts = {}
world_state = WorldState()
for address, details in pre_condition.items():
account = Account(address)
account = Account(address, concrete_storage=True)
account.code = Disassembly(details["code"][2:])
account.balance = int(details["balance"], 16)
account.nonce = int(details["nonce"], 16)
for key, value in details["storage"].items():
account.storage[int(key, 16)] = int(value, 16)
accounts[address] = account
laser_evm = LaserEVM(accounts, requires_statespace=False)
world_state.put_account(account)
account.set_balance(int(details["balance"], 16))
laser_evm = LaserEVM()
laser_evm.open_states = [world_state]
# Act
laser_evm.time = datetime.now()
final_states = execute_message_call(
laser_evm,
callee_address=action["address"],
caller_address=action["caller"],
origin_address=binascii.a2b_hex(action["origin"][2:]),
callee_address=symbol_factory.BitVecVal(int(action["address"], 16), 256),
caller_address=symbol_factory.BitVecVal(int(action["caller"], 16), 256),
origin_address=symbol_factory.BitVecVal(int(action["origin"], 16), 256),
code=action["code"][2:],
gas_limit=int(action["gas"], 16),
data=binascii.a2b_hex(action["data"][2:]),
@ -163,7 +168,7 @@ def test_vmtest(
world_state = laser_evm.open_states[0]
for address, details in post_condition.items():
account = world_state[address]
account = world_state[symbol_factory.BitVecVal(int(address, 16), 256)]
assert account.nonce == int(details["nonce"], 16)
assert account.code.bytecode == details["code"][2:]

@ -1,6 +1,8 @@
from mythril.disassembler.disassembly import Disassembly
from mythril.laser.ethereum import svm
from mythril.laser.ethereum.state.account import Account
from mythril.laser.ethereum.state.world_state import WorldState
import mythril.laser.ethereum.cfg as cfg
@ -18,15 +20,17 @@ def test_intercontract_call():
)
callee_address = "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
world_state = WorldState()
caller_account = Account(caller_address, caller_code, contract_name="Caller")
callee_account = Account(callee_address, callee_code, contract_name="Callee")
world_state.put_account(callee_account)
world_state.put_account(caller_account)
accounts = {caller_address: caller_account, callee_address: callee_account}
laser = svm.LaserEVM(accounts)
laser = svm.LaserEVM()
# Act
laser.sym_exec(caller_address)
laser.sym_exec(world_state=world_state, target_address=int(caller_address, 16))
# Assert
# Initial node starts in contract caller

@ -9,6 +9,8 @@ from mythril.laser.ethereum.transaction import (
from mythril.laser.ethereum.svm import LaserEVM
from mythril.laser.ethereum.state.account import Account
from mythril.laser.ethereum.state.world_state import WorldState
from mythril.laser.smt import symbol_factory
import unittest.mock as mock
from unittest.mock import MagicMock
@ -29,7 +31,7 @@ def test_execute_message_call(mocked_setup: MagicMock):
laser_evm = LaserEVM({})
world_state = WorldState()
world_state.accounts["address"] = Account("address")
world_state.put_account(Account("0x0"))
laser_evm.open_states = [world_state]
laser_evm.exec = MagicMock()
@ -37,7 +39,7 @@ def test_execute_message_call(mocked_setup: MagicMock):
mocked_setup.side_effect = _is_message_call
# Act
execute_message_call(laser_evm, "address")
execute_message_call(laser_evm, symbol_factory.BitVecVal(0, 256))
# Assert
# laser_evm.exec.assert_called_once()

@ -3,6 +3,7 @@ from mythril.mythril import MythrilDisassembler
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 import svm
from tests import BaseTestCase
@ -29,13 +30,6 @@ IDENTITY_TEST[1] = (hex(476934798798347), False)
def _all_info(laser):
accounts = {}
for address, _account in laser.world_state.accounts.items():
account = _account.as_dict
account["code"] = account["code"].instruction_list
account["balance"] = str(account["balance"])
accounts[address] = account
nodes = {}
for uid, node in laser.nodes.items():
states = []
@ -66,7 +60,6 @@ def _all_info(laser):
edges = [edge.as_dict for edge in laser.edges]
return {
"accounts": accounts,
"nodes": nodes,
"edges": edges,
"total_states": laser.total_states,
@ -85,13 +78,13 @@ class NativeTests(BaseTestCase):
""""""
disassembly = SolidityContract(
"./tests/native_tests.sol",
solc_binary=MythrilDisassembler._init_solc_binary("0.5.0"),
solc_binary=MythrilDisassembler._init_solc_binary("0.5.3"),
).disassembly
account = Account("0x0000000000000000000000000000000000000000", disassembly)
accounts = {account.address: account}
laser = svm.LaserEVM(accounts, max_depth=100, transaction_count=1)
laser.sym_exec(account.address)
world_state = WorldState()
world_state.put_account(account)
laser = svm.LaserEVM(max_depth=100, transaction_count=1)
laser.sym_exec(world_state=world_state, target_address=account.address.value)
laser_info = str(_all_info(laser))

@ -32,14 +32,14 @@ def _generate_report(input_file):
contract = EVMContract(input_file.read_text(), enable_online_lookup=False)
sym = SymExecWrapper(
contract,
address=(util.get_indexed_address(0)),
address=0xAFFEAFFEAFFEAFFEAFFEAFFEAFFEAFFEAFFEAFFE,
strategy="dfs",
execution_timeout=30,
transaction_count=1,
)
issues = fire_lasers(sym)
report = Report()
report = Report(contracts=[contract])
for issue in issues:
issue.filename = "test-filename.sol"
report.append_issue(issue)

@ -1,30 +0,0 @@
from mythril.laser.ethereum.taint_analysis import *
def test_mutate_not_tainted():
# Arrange
record = TaintRecord()
record.stack = [True, False, False]
# Act
TaintRunner.mutate_stack(record, (2, 1))
# Assert
assert record.stack_tainted(0)
assert record.stack_tainted(1) is False
assert record.stack == [True, False]
def test_mutate_tainted():
# Arrange
record = TaintRecord()
record.stack = [True, False, True]
# Act
TaintRunner.mutate_stack(record, (2, 1))
# Assert
assert record.stack_tainted(0)
assert record.stack_tainted(1)
assert record.stack == [True, True]

@ -1,36 +0,0 @@
from mythril.laser.ethereum.taint_analysis import *
def test_record_tainted_check():
# arrange
record = TaintRecord()
record.stack = [True, False, True]
# act
tainted = record.stack_tainted(2)
# assert
assert tainted is True
def test_record_untainted_check():
# arrange
record = TaintRecord()
record.stack = [True, False, False]
# act
tainted = record.stack_tainted(2)
# assert
assert tainted is False
def test_record_untouched_check():
# arrange
record = TaintRecord()
# act
tainted = record.stack_tainted(3)
# assert
assert tainted is None

@ -1,35 +0,0 @@
from mythril.laser.ethereum.taint_analysis import *
from mythril.laser.ethereum.state.global_state import GlobalState
def test_result_state():
# arrange
taint_result = TaintResult()
record = TaintRecord()
state = GlobalState(2, None, None)
state.mstate.stack = [1, 2, 3]
record.add_state(state)
record.stack = [False, False, False]
# act
taint_result.add_records([record])
tainted = taint_result.check(state, 2)
# assert
assert tainted is False
assert record in taint_result.records
def test_result_no_state():
# arrange
taint_result = TaintResult()
record = TaintRecord()
state = GlobalState(2, None, None)
state.mstate.stack = [1, 2, 3]
# act
taint_result.add_records([record])
tainted = taint_result.check(state, 2)
# assert
assert tainted is None
assert record in taint_result.records

@ -1,99 +0,0 @@
import mock
import pytest
from pytest_mock import mocker
from mythril.laser.ethereum.taint_analysis import *
from mythril.laser.ethereum.cfg import Node, Edge
from mythril.laser.ethereum.state.account import Account
from mythril.laser.ethereum.state.environment import Environment
from mythril.laser.ethereum.state.machine_state import MachineState
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.laser.ethereum.svm import LaserEVM
def test_execute_state(mocker):
record = TaintRecord()
record.stack = [True, False, True]
state = GlobalState(None, None, None)
state.mstate.stack = [1, 2, 3]
mocker.patch.object(state, "get_current_instruction")
state.get_current_instruction.return_value = {"opcode": "ADD"}
# Act
new_record = TaintRunner.execute_state(record, state)
# Assert
assert new_record.stack == [True, True]
assert record.stack == [True, False, True]
def test_execute_node(mocker):
record = TaintRecord()
record.stack = [True, True, False, False]
state_1 = GlobalState(None, None, None)
state_1.mstate.stack = [1, 2, 3, 1]
state_1.mstate.pc = 1
mocker.patch.object(state_1, "get_current_instruction")
state_1.get_current_instruction.return_value = {"opcode": "SWAP1"}
state_2 = GlobalState(None, 1, None)
state_2.mstate.stack = [1, 2, 4, 1]
mocker.patch.object(state_2, "get_current_instruction")
state_2.get_current_instruction.return_value = {"opcode": "ADD"}
node = Node("Test contract")
node.states = [state_1, state_2]
# Act
records = TaintRunner.execute_node(node, record)
# Assert
assert len(records) == 2
assert records[0].stack == [True, True, False, False]
assert records[1].stack == [True, True, False]
assert state_2 in records[0].states
assert state_1 in record.states
def test_execute(mocker):
active_account = Account("0x00")
environment = Environment(active_account, None, None, None, None, None)
state_1 = GlobalState(None, environment, None, MachineState(gas_limit=8000000))
state_1.mstate.stack = [1, 2]
mocker.patch.object(state_1, "get_current_instruction")
state_1.get_current_instruction.return_value = {"opcode": "PUSH"}
state_2 = GlobalState(None, environment, None, MachineState(gas_limit=8000000))
state_2.mstate.stack = [1, 2, 3]
mocker.patch.object(state_2, "get_current_instruction")
state_2.get_current_instruction.return_value = {"opcode": "ADD"}
node_1 = Node("Test contract")
node_1.states = [state_1, state_2]
state_3 = GlobalState(None, environment, None, MachineState(gas_limit=8000000))
state_3.mstate.stack = [1, 2]
mocker.patch.object(state_3, "get_current_instruction")
state_3.get_current_instruction.return_value = {"opcode": "ADD"}
node_2 = Node("Test contract")
node_2.states = [state_3]
edge = Edge(node_1.uid, node_2.uid)
statespace = LaserEVM(None)
statespace.edges = [edge]
statespace.nodes[node_1.uid] = node_1
statespace.nodes[node_2.uid] = node_2
# Act
result = TaintRunner.execute(statespace, node_1, state_1, [True, True])
# Assert
print(result)
assert len(result.records) == 3
assert result.records[2].states == []
assert state_3 in result.records[1].states

File diff suppressed because one or more lines are too long

@ -5,14 +5,14 @@
"address": 661,
"contract": "Unknown",
"debug": "<DEBUG-DATA>",
"description": "The contract executes an external message call.\nAn external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully.",
"description": "A call to a user-supplied address is executed.\nThe callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state.",
"function": "thisisfine()",
"max_gas_used": 1254,
"min_gas_used": 643,
"severity": "Low",
"severity": "Medium",
"sourceMap": null,
"swc-id": "107",
"title": "External Call To Fixed Address"
"title": "External Call To User-Supplied Address"
},
{
"address": 661,
@ -31,14 +31,14 @@
"address": 779,
"contract": "Unknown",
"debug": "<DEBUG-DATA>",
"description": "The contract executes an external message call.\nAn external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully.",
"description": "A call to a user-supplied address is executed.\nThe callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state.",
"function": "callstoredaddress()",
"max_gas_used": 1298,
"min_gas_used": 687,
"severity": "Low",
"severity": "Medium",
"sourceMap": null,
"swc-id": "107",
"title": "External Call To Fixed Address"
"title": "External Call To User-Supplied Address"
},
{
"address": 779,
@ -57,14 +57,14 @@
"address": 858,
"contract": "Unknown",
"debug": "<DEBUG-DATA>",
"description": "The contract executes an external message call.\nAn external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully.",
"description": "A call to a user-supplied address is executed.\nThe callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state.",
"function": "reentrancy()",
"max_gas_used": 1320,
"min_gas_used": 709,
"severity": "Low",
"severity": "Medium",
"sourceMap": null,
"swc-id": "107",
"title": "External Call To Fixed Address"
"title": "External Call To User-Supplied Address"
},
{
"address": 858,
@ -79,6 +79,19 @@
"swc-id": "104",
"title": "Unchecked Call Return Value"
},
{
"address": 869,
"contract": "Unknown",
"debug": "<DEBUG-DATA>",
"description": "The contract account state is changed after an external call. \nConsider that the called contract could re-enter the function before this state change takes place. This can lead to business logic vulnerabilities.",
"function": "reentrancy()",
"max_gas_used": null,
"min_gas_used": null,
"severity": "Medium",
"sourceMap": null,
"swc-id": "107",
"title": "State change after external call"
},
{
"address": 912,
"contract": "Unknown",

@ -3,8 +3,8 @@
"issues": [
{
"description": {
"head": "The contract executes an external message call.",
"tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully."
"head": "A call to a user-supplied address is executed.",
"tail": "The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
@ -14,14 +14,14 @@
"sourceMap": "661:1:0"
}
],
"severity": "Low",
"severity": "Medium",
"swcID": "SWC-107",
"swcTitle": "Reentrancy"
},
{
"description": {
"head": "The contract executes an external message call.",
"tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully."
"head": "A call to a user-supplied address is executed.",
"tail": "The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
@ -31,14 +31,14 @@
"sourceMap": "779:1:0"
}
],
"severity": "Low",
"severity": "Medium",
"swcID": "SWC-107",
"swcTitle": "Reentrancy"
},
{
"description": {
"head": "The contract executes an external message call.",
"tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully."
"head": "A call to a user-supplied address is executed.",
"tail": "The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
@ -48,7 +48,7 @@
"sourceMap": "858:1:0"
}
],
"severity": "Low",
"severity": "Medium",
"swcID": "SWC-107",
"swcTitle": "Reentrancy"
},
@ -69,6 +69,23 @@
"swcID": "SWC-107",
"swcTitle": "Reentrancy"
},
{
"description": {
"head": "The contract account state is changed after an external call. ",
"tail": "Consider that the called contract could re-enter the function before this state change takes place. This can lead to business logic vulnerabilities."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "869:1:0"
}
],
"severity": "Medium",
"swcID": "SWC-107",
"swcTitle": "Reentrancy"
},
{
"description": {
"head": "The return value of a message call is not checked.",

@ -1,8 +1,8 @@
# Analysis results for test-filename.sol
## External Call To Fixed Address
## External Call To User-Supplied Address
- SWC ID: 107
- Severity: Low
- Severity: Medium
- Contract: Unknown
- Function name: `thisisfine()`
- PC address: 661
@ -10,8 +10,8 @@
### Description
The contract executes an external message call.
An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully.
A call to a user-supplied address is executed.
The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state.
## Unchecked Call Return Value
- SWC ID: 104
@ -26,9 +26,9 @@ An external function call to a fixed contract address is executed. Make sure tha
The return value of a message call is not checked.
External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states.
## External Call To Fixed Address
## External Call To User-Supplied Address
- SWC ID: 107
- Severity: Low
- Severity: Medium
- Contract: Unknown
- Function name: `callstoredaddress()`
- PC address: 779
@ -36,8 +36,8 @@ External calls return a boolean value. If the callee contract halts with an exce
### Description
The contract executes an external message call.
An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully.
A call to a user-supplied address is executed.
The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state.
## Unchecked Call Return Value
- SWC ID: 104
@ -52,9 +52,9 @@ An external function call to a fixed contract address is executed. Make sure tha
The return value of a message call is not checked.
External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states.
## External Call To Fixed Address
## External Call To User-Supplied Address
- SWC ID: 107
- Severity: Low
- Severity: Medium
- Contract: Unknown
- Function name: `reentrancy()`
- PC address: 858
@ -62,8 +62,8 @@ External calls return a boolean value. If the callee contract halts with an exce
### Description
The contract executes an external message call.
An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully.
A call to a user-supplied address is executed.
The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state.
## Unchecked Call Return Value
- SWC ID: 104
@ -78,6 +78,19 @@ An external function call to a fixed contract address is executed. Make sure tha
The return value of a message call is not checked.
External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states.
## State change after external call
- SWC ID: 107
- Severity: Medium
- Contract: Unknown
- Function name: `reentrancy()`
- PC address: 869
- Estimated Gas Usage: None - None
### Description
The contract account state is changed after an external call.
Consider that the called contract could re-enter the function before this state change takes place. This can lead to business logic vulnerabilities.
## External Call To User-Supplied Address
- SWC ID: 107
- Severity: Medium

@ -1,12 +1,12 @@
==== External Call To Fixed Address ====
==== External Call To User-Supplied Address ====
SWC ID: 107
Severity: Low
Severity: Medium
Contract: Unknown
Function name: thisisfine()
PC address: 661
Estimated Gas Usage: 643 - 1254
The contract executes an external message call.
An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully.
A call to a user-supplied address is executed.
The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state.
--------------------
==== Unchecked Call Return Value ====
@ -20,15 +20,15 @@ The return value of a message call is not checked.
External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states.
--------------------
==== External Call To Fixed Address ====
==== External Call To User-Supplied Address ====
SWC ID: 107
Severity: Low
Severity: Medium
Contract: Unknown
Function name: callstoredaddress()
PC address: 779
Estimated Gas Usage: 687 - 1298
The contract executes an external message call.
An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully.
A call to a user-supplied address is executed.
The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state.
--------------------
==== Unchecked Call Return Value ====
@ -42,15 +42,15 @@ The return value of a message call is not checked.
External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states.
--------------------
==== External Call To Fixed Address ====
==== External Call To User-Supplied Address ====
SWC ID: 107
Severity: Low
Severity: Medium
Contract: Unknown
Function name: reentrancy()
PC address: 858
Estimated Gas Usage: 709 - 1320
The contract executes an external message call.
An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully.
A call to a user-supplied address is executed.
The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state.
--------------------
==== Unchecked Call Return Value ====
@ -64,6 +64,17 @@ The return value of a message call is not checked.
External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states.
--------------------
==== State change after external call ====
SWC ID: 107
Severity: Medium
Contract: Unknown
Function name: reentrancy()
PC address: 869
Estimated Gas Usage: None - None
The contract account state is changed after an external call.
Consider that the called contract could re-enter the function before this state change takes place. This can lead to business logic vulnerabilities.
--------------------
==== External Call To User-Supplied Address ====
SWC ID: 107
Severity: Medium

@ -1,36 +0,0 @@
{
"error": null,
"issues": [
{
"address": 158,
"contract": "Unknown",
"debug": "<DEBUG-DATA>",
"description": "The arithmetic operation can result in integer overflow.\n",
"function": "_function_0x83f12fec",
"swc-id": "101",
"title": "Integer Overflow",
"type": "Warning"
},
{
"address": 278,
"contract": "Unknown",
"debug": "<DEBUG-DATA>",
"description": "The arithmetic operation can result in integer overflow.\n",
"function": "_function_0x83f12fec",
"swc-id": "101",
"title": "Integer Overflow",
"type": "Warning"
},
{
"address": 378,
"contract": "Unknown",
"debug": "<DEBUG-DATA>",
"description": "The substraction can result in an integer underflow.\n",
"function": "_function_0x83f12fec",
"swc-id": "101",
"title": "Integer Underflow",
"type": "Warning"
}
],
"success": true
}

@ -1,34 +0,0 @@
# Analysis results for test-filename.sol
## Integer Overflow
- SWC ID: 101
- Type: Warning
- Contract: Unknown
- Function name: `_function_0x83f12fec`
- PC address: 158
### Description
The arithmetic operation can result in integer overflow.
## Integer Overflow
- SWC ID: 101
- Type: Warning
- Contract: Unknown
- Function name: `_function_0x83f12fec`
- PC address: 278
### Description
The arithmetic operation can result in integer overflow.
## Integer Underflow
- SWC ID: 101
- Type: Warning
- Contract: Unknown
- Function name: `_function_0x83f12fec`
- PC address: 378
### Description
The substraction can result in an integer underflow.

@ -1,30 +0,0 @@
==== Integer Overflow ====
SWC ID: 101
Type: Warning
Contract: Unknown
Function name: _function_0x83f12fec
PC address: 158
The arithmetic operation can result in integer overflow.
--------------------
==== Integer Overflow ====
SWC ID: 101
Type: Warning
Contract: Unknown
Function name: _function_0x83f12fec
PC address: 278
The arithmetic operation can result in integer overflow.
--------------------
==== Integer Underflow ====
SWC ID: 101
Type: Warning
Contract: Unknown
Function name: _function_0x83f12fec
PC address: 378
The substraction can result in an integer underflow.
--------------------

File diff suppressed because one or more lines are too long

@ -1,5 +1,32 @@
{
"error": null,
"issues": [],
"issues": [
{
"address": 722,
"contract": "Unknown",
"debug": "<DEBUG-DATA>",
"description": "Anyone can withdraw ETH from the contract account.\nArbitrary senders other than the contract creator can withdraw ETH from the contract account without previously having sent an equivalent amount of ETH to it. This is likely to be a vulnerability.",
"function": "withdrawfunds()",
"max_gas_used": 1749,
"min_gas_used": 1138,
"severity": "High",
"sourceMap": null,
"swc-id": "105",
"title": "Unprotected Ether Withdrawal"
},
{
"address": 883,
"contract": "Unknown",
"debug": "<DEBUG-DATA>",
"description": "The binary addition can overflow.\nThe operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion.",
"function": "invest()",
"max_gas_used": 26883,
"min_gas_used": 6598,
"severity": "High",
"sourceMap": null,
"swc-id": "101",
"title": "Integer Overflow"
}
],
"success": true
}

@ -1,9 +1,46 @@
[
{
"issues": [],
"issues": [
{
"description": {
"head": "Anyone can withdraw ETH from the contract account.",
"tail": "Arbitrary senders other than the contract creator can withdraw ETH from the contract account without previously having sent an equivalent amount of ETH to it. This is likely to be a vulnerability."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "722:1:0"
}
],
"severity": "High",
"swcID": "SWC-105",
"swcTitle": "Unprotected Ether Withdrawal"
},
{
"description": {
"head": "The binary addition can overflow.",
"tail": "The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "883:1:0"
}
],
"severity": "High",
"swcID": "SWC-101",
"swcTitle": "Integer Overflow and Underflow"
}
],
"meta": {},
"sourceFormat": "evm-byzantium-bytecode",
"sourceList": [],
"sourceList": [
"0x3746c7c2ae7b0d4c3f8b1905df9a7ea169b9f93bec68a10a00b4c9d27a18c6fb"
],
"sourceType": "raw-bytecode"
}
]

@ -1,3 +1,27 @@
# Analysis results for None
# Analysis results for test-filename.sol
The analysis was completed successfully. No issues were detected.
## Unprotected Ether Withdrawal
- SWC ID: 105
- Severity: High
- Contract: Unknown
- Function name: `withdrawfunds()`
- PC address: 722
- Estimated Gas Usage: 1138 - 1749
### Description
Anyone can withdraw ETH from the contract account.
Arbitrary senders other than the contract creator can withdraw ETH from the contract account without previously having sent an equivalent amount of ETH to it. This is likely to be a vulnerability.
## Integer Overflow
- SWC ID: 101
- Severity: High
- Contract: Unknown
- Function name: `invest()`
- PC address: 883
- Estimated Gas Usage: 6598 - 26883
### Description
The binary addition can overflow.
The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion.

@ -1 +1,22 @@
The analysis was completed successfully. No issues were detected.
==== Unprotected Ether Withdrawal ====
SWC ID: 105
Severity: High
Contract: Unknown
Function name: withdrawfunds()
PC address: 722
Estimated Gas Usage: 1138 - 1749
Anyone can withdraw ETH from the contract account.
Arbitrary senders other than the contract creator can withdraw ETH from the contract account without previously having sent an equivalent amount of ETH to it. This is likely to be a vulnerability.
--------------------
==== Integer Overflow ====
SWC ID: 101
Severity: High
Contract: Unknown
Function name: invest()
PC address: 883
Estimated Gas Usage: 6598 - 26883
The binary addition can overflow.
The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion.
--------------------

@ -27,6 +27,19 @@
"swc-id": "111",
"title": "Use of callcode"
},
{
"address": 849,
"contract": "Unknown",
"debug": "<DEBUG-DATA>",
"description": "The contract delegates execution to another contract with a user-supplied address.\nThe smart contract delegates execution to a user-supplied address. Note that callers can execute arbitrary contracts and that the callee contract can access the storage of the calling contract. ",
"function": "_function_0x9b58bc26",
"max_gas_used": 35928,
"min_gas_used": 1176,
"severity": "Medium",
"sourceMap": null,
"swc-id": "112",
"title": "Delegatecall Proxy To User-Supplied Address"
},
{
"address": 849,
"contract": "Unknown",

@ -1,6 +1,23 @@
[
{
"issues": [
{
"description": {
"head": "The contract delegates execution to another contract with a user-supplied address.",
"tail": "The smart contract delegates execution to a user-supplied address. Note that callers can execute arbitrary contracts and that the callee contract can access the storage of the calling contract. "
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "849:1:0"
}
],
"severity": "Medium",
"swcID": "SWC-112",
"swcTitle": "Delegatecall to Untrusted Callee"
},
{
"description": {
"head": "Use of callcode is deprecated.",

@ -26,6 +26,19 @@ External calls return a boolean value. If the callee contract halts with an exce
Use of callcode is deprecated.
The callcode method executes code of another contract in the context of the caller account. Due to a bug in the implementation it does not persist sender and value over the call. It was therefore deprecated and may be removed in the future. Use the delegatecall method instead.
## Delegatecall Proxy To User-Supplied Address
- SWC ID: 112
- Severity: Medium
- Contract: Unknown
- Function name: `_function_0x9b58bc26`
- PC address: 849
- Estimated Gas Usage: 1176 - 35928
### Description
The contract delegates execution to another contract with a user-supplied address.
The smart contract delegates execution to a user-supplied address. Note that callers can execute arbitrary contracts and that the callee contract can access the storage of the calling contract.
## Unchecked Call Return Value
- SWC ID: 104
- Severity: Low

@ -20,6 +20,17 @@ Use of callcode is deprecated.
The callcode method executes code of another contract in the context of the caller account. Due to a bug in the implementation it does not persist sender and value over the call. It was therefore deprecated and may be removed in the future. Use the delegatecall method instead.
--------------------
==== Delegatecall Proxy To User-Supplied Address ====
SWC ID: 112
Severity: Medium
Contract: Unknown
Function name: _function_0x9b58bc26
PC address: 849
Estimated Gas Usage: 1176 - 35928
The contract delegates execution to another contract with a user-supplied address.
The smart contract delegates execution to a user-supplied address. Note that callers can execute arbitrary contracts and that the callee contract can access the storage of the calling contract.
--------------------
==== Unchecked Call Return Value ====
SWC ID: 104
Severity: Low

@ -3,7 +3,9 @@
"issues": [],
"meta": {},
"sourceFormat": "evm-byzantium-bytecode",
"sourceList": [],
"sourceList": [
"0x0e6f727bb3301e02d3be831bf34357522fd2f1d40e90dff8e2214553b06b5f6c"
],
"sourceType": "raw-bytecode"
}
]

@ -3,7 +3,9 @@
"issues": [],
"meta": {},
"sourceFormat": "evm-byzantium-bytecode",
"sourceList": [],
"sourceList": [
"0x11a78eb09819f505ba4f10747e6d1f7a44480e602c67573b7abac2f733a85d93"
],
"sourceType": "raw-bytecode"
}
]

@ -26,6 +26,19 @@
"sourceMap": null,
"swc-id": "101",
"title": "Integer Underflow"
},
{
"address": 725,
"contract": "Unknown",
"debug": "<DEBUG-DATA>",
"description": "The binary addition can overflow.\nThe operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion.",
"function": "sendeth(address,uint256)",
"max_gas_used": 78155,
"min_gas_used": 17019,
"severity": "High",
"sourceMap": null,
"swc-id": "101",
"title": "Integer Overflow"
}
],
"success": true

@ -34,6 +34,23 @@
"severity": "High",
"swcID": "SWC-101",
"swcTitle": "Integer Overflow and Underflow"
},
{
"description": {
"head": "The binary addition can overflow.",
"tail": "The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "725:1:0"
}
],
"severity": "High",
"swcID": "SWC-101",
"swcTitle": "Integer Overflow and Underflow"
}
],
"meta": {},

@ -25,3 +25,16 @@ The operands of the subtraction operation are not sufficiently constrained. The
The binary subtraction can underflow.
The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion.
## Integer Overflow
- SWC ID: 101
- Severity: High
- Contract: Unknown
- Function name: `sendeth(address,uint256)`
- PC address: 725
- Estimated Gas Usage: 17019 - 78155
### Description
The binary addition can overflow.
The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion.

@ -20,3 +20,14 @@ The binary subtraction can underflow.
The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion.
--------------------
==== Integer Overflow ====
SWC ID: 101
Severity: High
Contract: Unknown
Function name: sendeth(address,uint256)
PC address: 725
Estimated Gas Usage: 17019 - 78155
The binary addition can overflow.
The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion.
--------------------

File diff suppressed because one or more lines are too long

@ -5,27 +5,27 @@
"address": 196,
"contract": "Unknown",
"debug": "<DEBUG-DATA>",
"description": "The contract executes an external message call.\nAn external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully.",
"description": "A call to a user-supplied address is executed.\nThe callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state.",
"function": "callchecked()",
"max_gas_used": 1210,
"min_gas_used": 599,
"severity": "Low",
"severity": "Medium",
"sourceMap": null,
"swc-id": "107",
"title": "External Call To Fixed Address"
"title": "External Call To User-Supplied Address"
},
{
"address": 285,
"contract": "Unknown",
"debug": "<DEBUG-DATA>",
"description": "The contract executes an external message call.\nAn external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully.",
"description": "A call to a user-supplied address is executed.\nThe callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state.",
"function": "callnotchecked()",
"max_gas_used": 1232,
"min_gas_used": 621,
"severity": "Low",
"severity": "Medium",
"sourceMap": null,
"swc-id": "107",
"title": "External Call To Fixed Address"
"title": "External Call To User-Supplied Address"
},
{
"address": 285,

@ -3,8 +3,8 @@
"issues": [
{
"description": {
"head": "The contract executes an external message call.",
"tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully."
"head": "A call to a user-supplied address is executed.",
"tail": "The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
@ -14,14 +14,14 @@
"sourceMap": "196:1:0"
}
],
"severity": "Low",
"severity": "Medium",
"swcID": "SWC-107",
"swcTitle": "Reentrancy"
},
{
"description": {
"head": "The contract executes an external message call.",
"tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully."
"head": "A call to a user-supplied address is executed.",
"tail": "The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
@ -31,7 +31,7 @@
"sourceMap": "285:1:0"
}
],
"severity": "Low",
"severity": "Medium",
"swcID": "SWC-107",
"swcTitle": "Reentrancy"
},

@ -1,8 +1,8 @@
# Analysis results for test-filename.sol
## External Call To Fixed Address
## External Call To User-Supplied Address
- SWC ID: 107
- Severity: Low
- Severity: Medium
- Contract: Unknown
- Function name: `callchecked()`
- PC address: 196
@ -10,12 +10,12 @@
### Description
The contract executes an external message call.
An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully.
A call to a user-supplied address is executed.
The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state.
## External Call To Fixed Address
## External Call To User-Supplied Address
- SWC ID: 107
- Severity: Low
- Severity: Medium
- Contract: Unknown
- Function name: `callnotchecked()`
- PC address: 285
@ -23,8 +23,8 @@ An external function call to a fixed contract address is executed. Make sure tha
### Description
The contract executes an external message call.
An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully.
A call to a user-supplied address is executed.
The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state.
## Unchecked Call Return Value
- SWC ID: 104

@ -1,23 +1,23 @@
==== External Call To Fixed Address ====
==== External Call To User-Supplied Address ====
SWC ID: 107
Severity: Low
Severity: Medium
Contract: Unknown
Function name: callchecked()
PC address: 196
Estimated Gas Usage: 599 - 1210
The contract executes an external message call.
An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully.
A call to a user-supplied address is executed.
The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state.
--------------------
==== External Call To Fixed Address ====
==== External Call To User-Supplied Address ====
SWC ID: 107
Severity: Low
Severity: Medium
Contract: Unknown
Function name: callnotchecked()
PC address: 285
Estimated Gas Usage: 621 - 1232
The contract executes an external message call.
An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully.
A call to a user-supplied address is executed.
The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state.
--------------------
==== Unchecked Call Return Value ====

@ -1,166 +0,0 @@
{
"success": true,
"error": null,
"issues": [
{
"title": "Ether send",
"description": "In the function `_function_0x4229616d` a non-zero amount of Ether is sent to an address taken from storage slot 5.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\n\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.",
"function": "_function_0x4229616d",
"type": "Warning",
"address": 1599,
"debug": "<DEBUG-DATA>"
},
{
"title": "Ether send",
"description": "In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\n\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.",
"function": "_function_0xb4022950",
"type": "Warning",
"address": 1940,
"debug": "<DEBUG-DATA>"
},
{
"title": "Ether send",
"description": "In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\n\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.",
"function": "_function_0xb4022950",
"type": "Warning",
"address": 2582,
"debug": "<DEBUG-DATA>"
},
{
"title": "Exception state",
"description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ",
"function": "_function_0x57d4021b",
"type": "Informational",
"address": 1653,
"debug": "<DEBUG-DATA>"
},
{
"title": "Exception state",
"description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ",
"function": "_function_0x9dbc4f9b",
"type": "Informational",
"address": 2085,
"debug": "<DEBUG-DATA>"
},
{
"title": "Invariant branch condition",
"description": "Found a conditional jump which always follows the same branch",
"function": "fallback",
"type": "Informational",
"address": 3111,
"debug": "<DEBUG-DATA>"
},
{
"title": "Invariant branch condition",
"description": "Found a conditional jump which always follows the same branch",
"function": "fallback",
"type": "Informational",
"address": 3140,
"debug": "<DEBUG-DATA>"
},
{
"title": "Invariant branch condition",
"description": "Found a conditional jump which always follows the same branch",
"function": "fallback",
"type": "Informational",
"address": 2950,
"debug": "<DEBUG-DATA>"
},
{
"title": "Invariant branch condition",
"description": "Found a conditional jump which always follows the same branch",
"function": "fallback",
"type": "Informational",
"address": 1268,
"debug": "<DEBUG-DATA>"
},
{
"title": "Invariant branch condition",
"description": "Found a conditional jump which always follows the same branch",
"function": "_function_0x09dfdc71",
"type": "Informational",
"address": 310,
"debug": "<DEBUG-DATA>"
},
{
"title": "Invariant branch condition",
"description": "Found a conditional jump which always follows the same branch",
"function": "_function_0x09dfdc71",
"type": "Informational",
"address": 1316,
"debug": "<DEBUG-DATA>"
},
{
"title": "Invariant branch condition",
"description": "Found a conditional jump which always follows the same branch",
"function": "_function_0x253459e3",
"type": "Informational",
"address": 1375,
"debug": "<DEBUG-DATA>"
},
{
"title": "Invariant branch condition",
"description": "Found a conditional jump which always follows the same branch",
"function": "_function_0x4229616d",
"type": "Informational",
"address": 1511,
"debug": "<DEBUG-DATA>"
},
{
"title": "Invariant branch condition",
"description": "Found a conditional jump which always follows the same branch",
"function": "_function_0x57d4021b",
"type": "Informational",
"address": 1679,
"debug": "<DEBUG-DATA>"
},
{
"title": "Invariant branch condition",
"description": "Found a conditional jump which always follows the same branch",
"function": "_function_0x6fbaaa1e",
"type": "Informational",
"address": 618,
"debug": "<DEBUG-DATA>"
},
{
"title": "Invariant branch condition",
"description": "Found a conditional jump which always follows the same branch",
"function": "_function_0x8a5fb3ca",
"type": "Informational",
"address": 805,
"debug": "<DEBUG-DATA>"
},
{
"title": "Invariant branch condition",
"description": "Found a conditional jump which always follows the same branch",
"function": "_function_0x9dbc4f9b",
"type": "Informational",
"address": 2187,
"debug": "<DEBUG-DATA>"
},
{
"title": "Unchecked CALL return value",
"description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.",
"function": "_function_0x4229616d",
"type": "Informational",
"address": 1599,
"debug": "<DEBUG-DATA>"
},
{
"title": "Unchecked CALL return value",
"description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.",
"function": "_function_0xb4022950",
"type": "Informational",
"address": 1940,
"debug": "<DEBUG-DATA>"
},
{
"title": "Unchecked CALL return value",
"description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.",
"function": "_function_0xb4022950",
"type": "Informational",
"address": 2582,
"debug": "<DEBUG-DATA>"
}
]
}

@ -1,238 +0,0 @@
# Analysis results for test-filename.sol
## Ether send
- Type: Warning
- Contract: Unknown
- Function name: `_function_0x4229616d`
- PC address: 1599
### Description
In the function `_function_0x4229616d` a non-zero amount of Ether is sent to an address taken from storage slot 5.
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.
## Ether send
- Type: Warning
- Contract: Unknown
- Function name: `_function_0xb4022950`
- PC address: 1940
### Description
In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5.
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.
## Ether send
- Type: Warning
- Contract: Unknown
- Function name: `_function_0xb4022950`
- PC address: 2582
### Description
In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5.
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.
## Exception state
- Type: Informational
- Contract: Unknown
- Function name: `_function_0x57d4021b`
- PC address: 1653
### Description
A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking.
## Exception state
- Type: Informational
- Contract: Unknown
- Function name: `_function_0x9dbc4f9b`
- PC address: 2085
### Description
A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking.
## Tautology
- Type: Informational
- Contract: Unknown
- Function name: `fallback`
- PC address: 3111
### Description
Found a conditional jump which always follows the same branch, value: True
## Tautology
- Type: Informational
- Contract: Unknown
- Function name: `fallback`
- PC address: 3140
### Description
Found a conditional jump which always follows the same branch, value: True
## Tautology
- Type: Informational
- Contract: Unknown
- Function name: `fallback`
- PC address: 2950
### Description
Found a conditional jump which always follows the same branch, value: True
## Tautology
- Type: Informational
- Contract: Unknown
- Function name: `fallback`
- PC address: 1268
### Description
Found a conditional jump which always follows the same branch, value: True
## Tautology
- Type: Informational
- Contract: Unknown
- Function name: `_function_0x09dfdc71`
- PC address: 310
### Description
Found a conditional jump which always follows the same branch, value: False
## Tautology
- Type: Informational
- Contract: Unknown
- Function name: `_function_0x09dfdc71`
- PC address: 1316
### Description
Found a conditional jump which always follows the same branch, value: True
## Tautology
- Type: Informational
- Contract: Unknown
- Function name: `_function_0x253459e3`
- PC address: 1375
### Description
Found a conditional jump which always follows the same branch, value: True
## Tautology
- Type: Informational
- Contract: Unknown
- Function name: `_function_0x4229616d`
- PC address: 1511
### Description
Found a conditional jump which always follows the same branch, value: True
## Tautology
- Type: Informational
- Contract: Unknown
- Function name: `_function_0x57d4021b`
- PC address: 1679
### Description
Found a conditional jump which always follows the same branch, value: True
## Tautology
- Type: Informational
- Contract: Unknown
- Function name: `_function_0x6fbaaa1e`
- PC address: 618
### Description
Found a conditional jump which always follows the same branch, value: False
## Tautology
- Type: Informational
- Contract: Unknown
- Function name: `_function_0x8a5fb3ca`
- PC address: 805
### Description
Found a conditional jump which always follows the same branch, value: False
## Tautology
- Type: Informational
- Contract: Unknown
- Function name: `_function_0x9dbc4f9b`
- PC address: 2187
### Description
Found a conditional jump which always follows the same branch, value: True
## Unchecked CALL return value
- Type: Informational
- Contract: Unknown
- Function name: `_function_0x4229616d`
- PC address: 1599
### Description
The return value of an external call is not checked. Note that execution continue even if the called contract throws.
## Unchecked CALL return value
- Type: Informational
- Contract: Unknown
- Function name: `_function_0xb4022950`
- PC address: 1940
### Description
The return value of an external call is not checked. Note that execution continue even if the called contract throws.
## Unchecked CALL return value
- Type: Informational
- Contract: Unknown
- Function name: `_function_0xb4022950`
- PC address: 2582
### Description
The return value of an external call is not checked. Note that execution continue even if the called contract throws.

@ -1,177 +0,0 @@
==== Ether send ====
Type: Warning
Contract: Unknown
Function name: _function_0x4229616d
PC address: 1599
In the function `_function_0x4229616d` a non-zero amount of Ether is sent to an address taken from storage slot 5.
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.
--------------------
==== Ether send ====
Type: Warning
Contract: Unknown
Function name: _function_0xb4022950
PC address: 1940
In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5.
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.
--------------------
==== Ether send ====
Type: Warning
Contract: Unknown
Function name: _function_0xb4022950
PC address: 2582
In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5.
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.
There is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.
There is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.
--------------------
==== Exception state ====
Type: Informational
Contract: Unknown
Function name: _function_0x57d4021b
PC address: 1653
A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking.
--------------------
==== Exception state ====
Type: Informational
Contract: Unknown
Function name: _function_0x9dbc4f9b
PC address: 2085
A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking.
--------------------
==== Tautology ====
Type: Informational
Contract: Unknown
Function name: fallback
PC address: 3111
Found a conditional jump which always follows the same branch, value: True
--------------------
==== Tautology ====
Type: Informational
Contract: Unknown
Function name: fallback
PC address: 3140
Found a conditional jump which always follows the same branch, value: True
--------------------
==== Tautology ====
Type: Informational
Contract: Unknown
Function name: fallback
PC address: 2950
Found a conditional jump which always follows the same branch, value: True
--------------------
==== Tautology ====
Type: Informational
Contract: Unknown
Function name: fallback
PC address: 1268
Found a conditional jump which always follows the same branch, value: True
--------------------
==== Tautology ====
Type: Informational
Contract: Unknown
Function name: _function_0x09dfdc71
PC address: 310
Found a conditional jump which always follows the same branch, value: False
--------------------
==== Tautology ====
Type: Informational
Contract: Unknown
Function name: _function_0x09dfdc71
PC address: 1316
Found a conditional jump which always follows the same branch, value: True
--------------------
==== Tautology ====
Type: Informational
Contract: Unknown
Function name: _function_0x253459e3
PC address: 1375
Found a conditional jump which always follows the same branch, value: True
--------------------
==== Tautology ====
Type: Informational
Contract: Unknown
Function name: _function_0x4229616d
PC address: 1511
Found a conditional jump which always follows the same branch, value: True
--------------------
==== Tautology ====
Type: Informational
Contract: Unknown
Function name: _function_0x57d4021b
PC address: 1679
Found a conditional jump which always follows the same branch, value: True
--------------------
==== Tautology ====
Type: Informational
Contract: Unknown
Function name: _function_0x6fbaaa1e
PC address: 618
Found a conditional jump which always follows the same branch, value: False
--------------------
==== Tautology ====
Type: Informational
Contract: Unknown
Function name: _function_0x8a5fb3ca
PC address: 805
Found a conditional jump which always follows the same branch, value: False
--------------------
==== Tautology ====
Type: Informational
Contract: Unknown
Function name: _function_0x9dbc4f9b
PC address: 2187
Found a conditional jump which always follows the same branch, value: True
--------------------
==== Unchecked CALL return value ====
Type: Informational
Contract: Unknown
Function name: _function_0x4229616d
PC address: 1599
The return value of an external call is not checked. Note that execution continue even if the called contract throws.
--------------------
==== Unchecked CALL return value ====
Type: Informational
Contract: Unknown
Function name: _function_0xb4022950
PC address: 1940
The return value of an external call is not checked. Note that execution continue even if the called contract throws.
--------------------
==== Unchecked CALL return value ====
Type: Informational
Contract: Unknown
Function name: _function_0xb4022950
PC address: 2582
The return value of an external call is not checked. Note that execution continue even if the called contract throws.
--------------------

@ -26,6 +26,19 @@
"sourceMap": null,
"swc-id": "101",
"title": "Integer Underflow"
},
{
"address": 725,
"contract": "Unknown",
"debug": "<DEBUG-DATA>",
"description": "The binary addition can overflow.\nThe operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion.",
"function": "sendeth(address,uint256)",
"max_gas_used": 52861,
"min_gas_used": 11915,
"severity": "High",
"sourceMap": null,
"swc-id": "101",
"title": "Integer Overflow"
}
],
"success": true

@ -34,6 +34,23 @@
"severity": "High",
"swcID": "SWC-101",
"swcTitle": "Integer Overflow and Underflow"
},
{
"description": {
"head": "The binary addition can overflow.",
"tail": "The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "725:1:0"
}
],
"severity": "High",
"swcID": "SWC-101",
"swcTitle": "Integer Overflow and Underflow"
}
],
"meta": {},

@ -25,3 +25,16 @@ The operands of the subtraction operation are not sufficiently constrained. The
The binary subtraction can underflow.
The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion.
## Integer Overflow
- SWC ID: 101
- Severity: High
- Contract: Unknown
- Function name: `sendeth(address,uint256)`
- PC address: 725
- Estimated Gas Usage: 11915 - 52861
### Description
The binary addition can overflow.
The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion.

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save