mirror of https://github.com/ConsenSys/mythril
commit
612fc4df34
@ -0,0 +1,63 @@ |
||||
MythX Analysis |
||||
================= |
||||
|
||||
Run :code:`myth pro` with one of the input options described below will run a `MythX analysis <https://mythx.io>`_ on the desired input. This includes a run of Mythril, the fuzzer Harvey, and the static analysis engine Maru and has some false-positive filtering only possible by combining the tool capabilities. |
||||
|
||||
************** |
||||
Authentication |
||||
************** |
||||
|
||||
In order to authenticate with the MythX API, set the environment variables ``MYTHX_PASSWORD`` and ``MYTHX_ETH_ADDRESS``. |
||||
|
||||
.. code-block:: bash |
||||
|
||||
$ export MYTHX_ETH_ADDRESS='0x0000000000000000000000000000000000000000' |
||||
$ export MYTHX_PASSWORD='password' |
||||
|
||||
*********************** |
||||
Analyzing Solidity Code |
||||
*********************** |
||||
|
||||
The input format is the same as a regular Mythril analysis. |
||||
|
||||
.. code-block:: bash |
||||
|
||||
$ myth pro ether_send.sol |
||||
==== Unprotected Ether Withdrawal ==== |
||||
SWC ID: 105 |
||||
Severity: High |
||||
Contract: Crowdfunding |
||||
Function name: withdrawfunds() |
||||
PC address: 730 |
||||
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. |
||||
-------------------- |
||||
In file: tests/testdata/input_contracts/ether_send.sol:21 |
||||
|
||||
msg.sender.transfer(address(this).balance) |
||||
|
||||
-------------------- |
||||
|
||||
If an input file contains multiple contract definitions, Mythril analyzes the *last* bytecode output produced by solc. You can override this by specifying the contract name explicitly: |
||||
|
||||
.. code-block:: bash |
||||
|
||||
myth pro OmiseGo.sol:OMGToken |
||||
|
||||
To specify a contract address, use :code:`-a <address>` |
||||
|
||||
**************************** |
||||
Analyzing On-Chain Contracts |
||||
**************************** |
||||
|
||||
Analyzing a mainnet contract via INFURA: |
||||
|
||||
.. code-block:: bash |
||||
|
||||
myth pro -a 0x5c436ff914c458983414019195e0f4ecbef9e6dd |
||||
|
||||
Adding the :code:`-l` flag will cause mythril to automatically retrieve dependencies, such as dynamically linked library contracts: |
||||
|
||||
.. code-block:: bash |
||||
|
||||
myth -v4 pro -l -a 0xEbFD99838cb0c132016B9E117563CB41f2B02264 |
@ -0,0 +1,108 @@ |
||||
from mythril.analysis.report import Issue |
||||
from mythril.analysis.solver import get_transaction_sequence |
||||
from mythril.exceptions import UnsatError |
||||
from mythril.laser.ethereum.state.annotation import StateAnnotation |
||||
from mythril.laser.ethereum.state.global_state import GlobalState |
||||
|
||||
|
||||
class PotentialIssue: |
||||
"""Representation of a potential issue""" |
||||
|
||||
def __init__( |
||||
self, |
||||
contract, |
||||
function_name, |
||||
address, |
||||
swc_id, |
||||
title, |
||||
bytecode, |
||||
detector, |
||||
severity=None, |
||||
description_head="", |
||||
description_tail="", |
||||
constraints=None, |
||||
): |
||||
""" |
||||
|
||||
:param contract: The contract |
||||
:param function_name: Function name where the issue is detected |
||||
:param address: The address of the issue |
||||
:param swc_id: Issue's corresponding swc-id |
||||
:param title: Title |
||||
:param bytecode: bytecode of the issue |
||||
:param detector: The detector the potential issue belongs to |
||||
:param gas_used: amount of gas used |
||||
:param severity: The severity of the issue |
||||
:param description_head: The top part of description |
||||
:param description_tail: The bottom part of the description |
||||
:param constraints: The non-path related constraints for the potential issue |
||||
""" |
||||
self.title = title |
||||
self.contract = contract |
||||
self.function_name = function_name |
||||
self.address = address |
||||
self.description_head = description_head |
||||
self.description_tail = description_tail |
||||
self.severity = severity |
||||
self.swc_id = swc_id |
||||
self.bytecode = bytecode |
||||
self.constraints = constraints or [] |
||||
self.detector = detector |
||||
|
||||
|
||||
class PotentialIssuesAnnotation(StateAnnotation): |
||||
def __init__(self): |
||||
self.potential_issues = [] |
||||
|
||||
|
||||
def get_potential_issues_annotation(state: GlobalState) -> PotentialIssuesAnnotation: |
||||
""" |
||||
Returns the potential issues annotation of the given global state, and creates one if |
||||
one does not already exist. |
||||
|
||||
:param state: The global state |
||||
:return: |
||||
""" |
||||
for annotation in state.annotations: |
||||
if isinstance(annotation, PotentialIssuesAnnotation): |
||||
return annotation |
||||
|
||||
annotation = PotentialIssuesAnnotation() |
||||
state.annotate(annotation) |
||||
return annotation |
||||
|
||||
|
||||
def check_potential_issues(state: GlobalState) -> None: |
||||
""" |
||||
Called at the end of a transaction, checks potential issues, and |
||||
adds valid issues to the detector. |
||||
|
||||
:param state: The final global state of a transaction |
||||
:return: |
||||
""" |
||||
annotation = get_potential_issues_annotation(state) |
||||
for potential_issue in annotation.potential_issues: |
||||
try: |
||||
transaction_sequence = get_transaction_sequence( |
||||
state, state.mstate.constraints + potential_issue.constraints |
||||
) |
||||
except UnsatError: |
||||
continue |
||||
|
||||
annotation.potential_issues.remove(potential_issue) |
||||
potential_issue.detector.cache.add(potential_issue.address) |
||||
potential_issue.detector.issues.append( |
||||
Issue( |
||||
contract=potential_issue.contract, |
||||
function_name=potential_issue.function_name, |
||||
address=potential_issue.address, |
||||
title=potential_issue.title, |
||||
bytecode=potential_issue.bytecode, |
||||
swc_id=potential_issue.swc_id, |
||||
gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), |
||||
severity=potential_issue.severity, |
||||
description_head=potential_issue.description_head, |
||||
description_tail=potential_issue.description_tail, |
||||
transaction_sequence=transaction_sequence, |
||||
) |
||||
) |
@ -0,0 +1,111 @@ |
||||
import sys |
||||
|
||||
import os |
||||
import time |
||||
|
||||
from mythx_models.exceptions import MythXAPIError |
||||
from typing import List, Dict, Any |
||||
|
||||
from mythril.analysis.report import Issue, Report |
||||
from mythril.solidity.soliditycontract import SolidityContract |
||||
|
||||
from pythx import Client |
||||
|
||||
import logging |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
TRIAL_ETH_ADDRESS = "0x0000000000000000000000000000000000000000" |
||||
TRIAL_PASSWORD = "trial" |
||||
|
||||
|
||||
def analyze(contracts: List[SolidityContract], analysis_mode: str = "quick") -> Report: |
||||
""" |
||||
Analyze contracts via the MythX API. |
||||
|
||||
:param contracts: List of solidity contracts to analyze |
||||
:param analysis_mode: The mode to submit the analysis request with. "quick" or "full" (default: "quick") |
||||
:return: Report with analyzed contracts |
||||
""" |
||||
assert analysis_mode in ("quick", "full"), "analysis_mode must be 'quick' or 'full'" |
||||
|
||||
c = Client( |
||||
eth_address=os.environ.get("MYTHX_ETH_ADDRESS", TRIAL_ETH_ADDRESS), |
||||
password=os.environ.get("MYTHX_PASSWORD", TRIAL_PASSWORD), |
||||
) |
||||
|
||||
if c.eth_address == TRIAL_ETH_ADDRESS: |
||||
print( |
||||
"You are currently running MythX in Trial mode. This mode reports only a partial analysis of your smart contracts, limited to three vulnerabilities. To get a more complete analysis, sign up for a free account at https://mythx.io." |
||||
) |
||||
|
||||
issues = [] # type: List[Issue] |
||||
|
||||
# TODO: Analyze multiple contracts asynchronously. |
||||
for contract in contracts: |
||||
source_codes = {} |
||||
source_list = [] |
||||
sources = {} # type: Dict[str, Any] |
||||
main_source = None |
||||
|
||||
try: |
||||
main_source = contract.input_file |
||||
for solidity_file in contract.solidity_files: |
||||
source_codes[solidity_file.filename] = solidity_file.data |
||||
for filename in contract.solc_json["sources"].keys(): |
||||
sources[filename] = {} |
||||
if source_codes[filename]: |
||||
sources[filename]["source"] = source_codes[filename] |
||||
sources[filename]["ast"] = contract.solc_json["sources"][filename][ |
||||
"ast" |
||||
] |
||||
|
||||
source_list.append(filename) |
||||
|
||||
source_list.sort( |
||||
key=lambda fname: contract.solc_json["sources"][fname]["id"] |
||||
) |
||||
except AttributeError: |
||||
# No solidity file |
||||
pass |
||||
|
||||
assert contract.creation_code, "Creation bytecode must exist." |
||||
try: |
||||
resp = c.analyze( |
||||
contract_name=contract.name, |
||||
analysis_mode=analysis_mode, |
||||
bytecode=contract.creation_code or None, |
||||
deployed_bytecode=contract.code or None, |
||||
sources=sources or None, |
||||
main_source=main_source, |
||||
source_list=source_list or None, |
||||
) |
||||
except MythXAPIError as e: |
||||
log.critical(e) |
||||
|
||||
while not c.analysis_ready(resp.uuid): |
||||
log.info(c.status(resp.uuid).analysis) |
||||
time.sleep(5) |
||||
|
||||
for issue in c.report(resp.uuid): |
||||
issue = Issue( |
||||
contract=contract.name, |
||||
function_name=None, |
||||
address=issue.locations[0].source_map.components[0].offset |
||||
if issue.locations |
||||
else -1, |
||||
swc_id=issue.swc_id[4:] or "None", # remove 'SWC-' prefix |
||||
title=issue.swc_title, |
||||
bytecode=contract.creation_code, |
||||
severity=issue.severity.capitalize(), |
||||
description_head=issue.description_short, |
||||
description_tail=issue.description_long, |
||||
) |
||||
issue.add_code_info(contract) |
||||
issues.append(issue) |
||||
|
||||
report = Report(contracts=contracts) |
||||
for issue in issues: |
||||
report.append_issue(issue) |
||||
|
||||
return report |
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue