Merge branch 'develop' into fix/constraints-after-issues

fix/constraints-after-issues
Nathan 5 years ago committed by GitHub
commit 52d5afe0b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      docs/source/index.rst
  2. 63
      docs/source/mythx-analysis.rst
  3. 2
      docs/source/security-analysis.rst
  4. 2
      mythril/__version__.py
  5. 6
      mythril/analysis/templates/report_as_markdown.jinja2
  6. 4
      mythril/analysis/templates/report_as_text.jinja2
  7. 64
      mythril/ethereum/util.py
  8. 103
      mythril/interfaces/cli.py
  9. 7
      mythril/interfaces/old_cli.py
  10. 2
      mythril/laser/ethereum/util.py
  11. 12
      mythril/mythril/mythril_disassembler.py
  12. 111
      mythril/mythx/__init__.py
  13. 79
      mythril/solidity/soliditycontract.py
  14. 47
      mythril/support/signatures.py
  15. 3
      requirements.txt
  16. 3
      setup.py
  17. 6
      tests/disassembler_test.py

@ -9,6 +9,7 @@ Welcome to Mythril's documentation!
installation installation
security-analysis security-analysis
analysis-modules analysis-modules
mythx-analysis
mythril mythril

@ -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

@ -1,7 +1,7 @@
Security Analysis Security Analysis
================= =================
Run :code:`myth -x` with one of the input options described below will run the analysis modules in the `/analysis/modules <https://github.com/ConsenSys/mythril/tree/master/mythril/analysis/modules>`_ directory. Run :code:`myth analyze` with one of the input options described below will run the analysis modules in the `/analysis/modules <https://github.com/ConsenSys/mythril/tree/master/mythril/analysis/modules>`_ directory.
*********************** ***********************
Analyzing Solidity Code Analyzing Solidity Code

@ -4,4 +4,4 @@ This file is suitable for sourcing inside POSIX shell, e.g. bash as well
as for importing into Python. as for importing into Python.
""" """
__version__ = "v0.21.15" __version__ = "v0.21.16"

@ -6,15 +6,21 @@
- SWC ID: {{ issue['swc-id'] }} - SWC ID: {{ issue['swc-id'] }}
- Severity: {{ issue.severity }} - Severity: {{ issue.severity }}
- Contract: {{ issue.contract | default("Unknown") }} - Contract: {{ issue.contract | default("Unknown") }}
{% if issue.function %}
- Function name: `{{ issue.function }}` - Function name: `{{ issue.function }}`
{% endif %}
- PC address: {{ issue.address }} - PC address: {{ issue.address }}
{% if issue.min_gas_used or issue.max_gas_used %}
- Estimated Gas Usage: {{ issue.min_gas_used }} - {{ issue.max_gas_used }} - Estimated Gas Usage: {{ issue.min_gas_used }} - {{ issue.max_gas_used }}
{% endif %}
### Description ### Description
{{ issue.description.rstrip() }} {{ issue.description.rstrip() }}
{% if issue.filename and issue.lineno %} {% if issue.filename and issue.lineno %}
In file: {{ issue.filename }}:{{ issue.lineno }} In file: {{ issue.filename }}:{{ issue.lineno }}
{% elif issue.filename %}
In file: {{ issue.filename }}
{% endif %} {% endif %}
{% if issue.code %} {% if issue.code %}

@ -4,9 +4,13 @@
SWC ID: {{ issue['swc-id'] }} SWC ID: {{ issue['swc-id'] }}
Severity: {{ issue.severity }} Severity: {{ issue.severity }}
Contract: {{ issue.contract | default("Unknown") }} Contract: {{ issue.contract | default("Unknown") }}
{% if issue.function %}
Function name: {{ issue.function }} Function name: {{ issue.function }}
{% endif %}
PC address: {{ issue.address }} PC address: {{ issue.address }}
{% if issue.min_gas_used or issue.max_gas_used %}
Estimated Gas Usage: {{ issue.min_gas_used }} - {{ issue.max_gas_used }} Estimated Gas Usage: {{ issue.min_gas_used }} - {{ issue.max_gas_used }}
{% endif %}
{{ issue.description }} {{ issue.description }}
-------------------- --------------------
{% if issue.filename and issue.lineno %} {% if issue.filename and issue.lineno %}

@ -24,39 +24,44 @@ def safe_decode(hex_encoded_string):
return bytes.fromhex(hex_encoded_string) return bytes.fromhex(hex_encoded_string)
def get_solc_json(file, solc_binary="solc", solc_args=None): def get_solc_json(file, solc_binary="solc", solc_settings_json=None):
""" """
:param file: :param file:
:param solc_binary: :param solc_binary:
:param solc_args: :param solc_settings_json:
:return: :return:
""" """
cmd = [solc_binary, "--standard-json", "--allow-paths", "."]
cmd = [solc_binary, "--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime,ast"]
settings = json.loads(solc_settings_json) if solc_settings_json else {}
if solc_args: settings.update(
cmd.extend(solc_args.split()) {
if not "--allow-paths" in cmd: "outputSelection": {
cmd.extend(["--allow-paths", "."]) "*": {
else: "": ["ast"],
for i, arg in enumerate(cmd): "*": [
if arg == "--allow-paths": "metadata",
cmd[i + 1] += ",." "evm.bytecode",
"evm.deployedBytecode",
cmd.append(file) "evm.methodIdentifiers",
],
}
}
}
)
input_json = json.dumps(
{
"language": "Solidity",
"sources": {file: {"urls": [file]}},
"settings": settings,
}
)
try: try:
p = Popen(cmd, stdout=PIPE, stderr=PIPE) p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate(bytes(input_json, "utf8"))
stdout, stderr = p.communicate()
ret = p.returncode
if ret != 0:
raise CompilerError(
"Solc experienced a fatal error (code %d).\n\n%s"
% (ret, stderr.decode("UTF-8"))
)
except FileNotFoundError: except FileNotFoundError:
raise CompilerError( raise CompilerError(
"Compiler not found. Make sure that solc is installed and in PATH, or set the SOLC environment variable." "Compiler not found. Make sure that solc is installed and in PATH, or set the SOLC environment variable."
@ -64,10 +69,15 @@ def get_solc_json(file, solc_binary="solc", solc_args=None):
out = stdout.decode("UTF-8") out = stdout.decode("UTF-8")
if not len(out): result = json.loads(out)
raise CompilerError("Compilation failed.")
for error in result.get("errors", []):
if error["severity"] == "error":
raise CompilerError(
"Solc experienced a fatal error.\n\n%s" % error["formattedMessage"]
)
return json.loads(out) return result
def encode_calldata(func_name, arg_types, args): def encode_calldata(func_name, arg_types, args):

@ -16,6 +16,7 @@ import traceback
import mythril.support.signatures as sigs import mythril.support.signatures as sigs
from argparse import ArgumentParser, Namespace, RawTextHelpFormatter from argparse import ArgumentParser, Namespace, RawTextHelpFormatter
from mythril import mythx
from mythril.exceptions import AddressNotFoundError, CriticalError from mythril.exceptions import AddressNotFoundError, CriticalError
from mythril.mythril import ( from mythril.mythril import (
MythrilAnalyzer, MythrilAnalyzer,
@ -27,12 +28,14 @@ from mythril.__version__ import __version__ as VERSION
ANALYZE_LIST = ("analyze", "a") ANALYZE_LIST = ("analyze", "a")
DISASSEMBLE_LIST = ("disassemble", "d") DISASSEMBLE_LIST = ("disassemble", "d")
PRO_LIST = ("pro", "p")
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
COMMAND_LIST = ( COMMAND_LIST = (
ANALYZE_LIST ANALYZE_LIST
+ DISASSEMBLE_LIST + DISASSEMBLE_LIST
+ PRO_LIST
+ ( + (
"read-storage", "read-storage",
"leveldb-search", "leveldb-search",
@ -41,6 +44,7 @@ COMMAND_LIST = (
"version", "version",
"truffle", "truffle",
"help", "help",
"pro",
) )
) )
@ -70,7 +74,27 @@ def exit_with_error(format_, message):
sys.exit() sys.exit()
def get_input_parser() -> ArgumentParser: def get_runtime_input_parser() -> ArgumentParser:
"""
Returns Parser which handles input
:return: Parser which handles input
"""
parser = ArgumentParser(add_help=False)
parser.add_argument(
"-a",
"--address",
help="pull contract from the blockchain",
metavar="CONTRACT_ADDRESS",
)
parser.add_argument(
"--bin-runtime",
action="store_true",
help="Only when -c or -f is used. Consider the input bytecode as binary runtime code, default being the contract creation bytecode.",
)
return parser
def get_creation_input_parser() -> ArgumentParser:
""" """
Returns Parser which handles input Returns Parser which handles input
:return: Parser which handles input :return: Parser which handles input
@ -89,17 +113,6 @@ def get_input_parser() -> ArgumentParser:
metavar="BYTECODEFILE", metavar="BYTECODEFILE",
type=argparse.FileType("r"), type=argparse.FileType("r"),
) )
parser.add_argument(
"-a",
"--address",
help="pull contract from the blockchain",
metavar="CONTRACT_ADDRESS",
)
parser.add_argument(
"--bin-runtime",
action="store_true",
help="Only when -c or -f is used. Consider the input bytecode as binary runtime code, default being the contract creation bytecode.",
)
return parser return parser
@ -144,7 +157,10 @@ def get_utilities_parser() -> ArgumentParser:
:return: Parser which handles utility flags :return: Parser which handles utility flags
""" """
parser = argparse.ArgumentParser(add_help=False) parser = argparse.ArgumentParser(add_help=False)
parser.add_argument("--solc-args", help="Extra arguments for solc") parser.add_argument(
"--solc-json",
help="Json for the optional 'settings' parameter of solc's standard-json input",
)
parser.add_argument( parser.add_argument(
"--solv", "--solv",
help="specify solidity compiler version. If not present, will try to install it (Experimental)", help="specify solidity compiler version. If not present, will try to install it (Experimental)",
@ -158,7 +174,8 @@ def main() -> None:
rpc_parser = get_rpc_parser() rpc_parser = get_rpc_parser()
utilities_parser = get_utilities_parser() utilities_parser = get_utilities_parser()
input_parser = get_input_parser() runtime_input_parser = get_runtime_input_parser()
creation_input_parser = get_creation_input_parser()
output_parser = get_output_parser() output_parser = get_output_parser()
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Security analysis of Ethereum smart contracts" description="Security analysis of Ethereum smart contracts"
@ -172,7 +189,13 @@ def main() -> None:
analyzer_parser = subparsers.add_parser( analyzer_parser = subparsers.add_parser(
ANALYZE_LIST[0], ANALYZE_LIST[0],
help="Triggers the analysis of the smart contract", help="Triggers the analysis of the smart contract",
parents=[rpc_parser, utilities_parser, input_parser, output_parser], parents=[
rpc_parser,
utilities_parser,
creation_input_parser,
runtime_input_parser,
output_parser,
],
aliases=ANALYZE_LIST[1:], aliases=ANALYZE_LIST[1:],
formatter_class=RawTextHelpFormatter, formatter_class=RawTextHelpFormatter,
) )
@ -182,11 +205,25 @@ def main() -> None:
DISASSEMBLE_LIST[0], DISASSEMBLE_LIST[0],
help="Disassembles the smart contract", help="Disassembles the smart contract",
aliases=DISASSEMBLE_LIST[1:], aliases=DISASSEMBLE_LIST[1:],
parents=[rpc_parser, utilities_parser, input_parser], parents=[
rpc_parser,
utilities_parser,
creation_input_parser,
runtime_input_parser,
],
formatter_class=RawTextHelpFormatter, formatter_class=RawTextHelpFormatter,
) )
create_disassemble_parser(disassemble_parser) create_disassemble_parser(disassemble_parser)
pro_parser = subparsers.add_parser(
PRO_LIST[0],
help="Analyzes input with the MythX API (https://mythx.io)",
aliases=PRO_LIST[1:],
parents=[utilities_parser, creation_input_parser, output_parser],
formatter_class=RawTextHelpFormatter,
)
create_pro_parser(pro_parser)
read_storage_parser = subparsers.add_parser( read_storage_parser = subparsers.add_parser(
"read-storage", "read-storage",
help="Retrieves storage slots from a given address through rpc", help="Retrieves storage slots from a given address through rpc",
@ -234,6 +271,25 @@ def create_disassemble_parser(parser: ArgumentParser):
) )
def create_pro_parser(parser: ArgumentParser):
"""
Modify parser to handle mythx analysis
:param parser:
:return:
"""
parser.add_argument(
"solidity_files",
nargs="*",
help="Inputs file name and contract name. \n"
"usage: file1.sol:OptionalContractName file2.sol file3.sol:OptionalContractName",
)
parser.add_argument(
"--full",
help="Run a full analysis. Default: quick analysis",
action="store_true",
)
def create_read_storage_parser(read_storage_parser: ArgumentParser): def create_read_storage_parser(read_storage_parser: ArgumentParser):
""" """
Modify parser to handle storage slots Modify parser to handle storage slots
@ -564,6 +620,17 @@ def execute_command(
) )
print(storage) print(storage)
elif args.command in PRO_LIST:
mode = "full" if args.full else "quick"
report = mythx.analyze(disassembler.contracts, mode)
outputs = {
"json": report.as_json(),
"jsonv2": report.as_swc_standard_format(),
"text": report.as_text(),
"markdown": report.as_markdown(),
}
print(outputs[args.outform])
elif args.command in DISASSEMBLE_LIST: elif args.command in DISASSEMBLE_LIST:
if disassembler.contracts[0].code: if disassembler.contracts[0].code:
print("Runtime Disassembly: \n" + disassembler.contracts[0].get_easm()) print("Runtime Disassembly: \n" + disassembler.contracts[0].get_easm())
@ -694,12 +761,12 @@ def parse_args_and_execute(parser: ArgumentParser, args: Namespace) -> None:
config = set_config(args) config = set_config(args)
leveldb_search(config, args) leveldb_search(config, args)
query_signature = args.__dict__.get("query_signature", None) query_signature = args.__dict__.get("query_signature", None)
solc_args = args.__dict__.get("solc_args", None) solc_json = args.__dict__.get("solc_json", None)
solv = args.__dict__.get("solv", None) solv = args.__dict__.get("solv", None)
disassembler = MythrilDisassembler( disassembler = MythrilDisassembler(
eth=config.eth, eth=config.eth,
solc_version=solv, solc_version=solv,
solc_args=solc_args, solc_settings_json=solc_json,
enable_online_lookup=query_signature, enable_online_lookup=query_signature,
) )
if args.command == "truffle": if args.command == "truffle":

@ -229,7 +229,10 @@ def create_parser(parser: argparse.ArgumentParser) -> None:
default=10, default=10,
help="The amount of seconds to spend on " "the initial contract creation", help="The amount of seconds to spend on " "the initial contract creation",
) )
options.add_argument("--solc-args", help="Extra arguments for solc") options.add_argument(
"--solc-json",
help="Json for the optional 'settings' parameter of solc's standard-json input",
)
options.add_argument( options.add_argument(
"--phrack", action="store_true", help="Phrack-style call graph" "--phrack", action="store_true", help="Phrack-style call graph"
) )
@ -522,7 +525,7 @@ def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> Non
disassembler = MythrilDisassembler( disassembler = MythrilDisassembler(
eth=config.eth, eth=config.eth,
solc_version=args.solv, solc_version=args.solv,
solc_args=args.solc_args, solc_settings_json=args.solc_json,
enable_online_lookup=args.query_signature, enable_online_lookup=args.query_signature,
) )
if args.truffle: if args.truffle:

@ -45,7 +45,7 @@ def get_instruction_index(
""" """
index = 0 index = 0
for instr in instruction_list: for instr in instruction_list:
if instr["address"] == address: if instr["address"] >= address:
return index return index
index += 1 index += 1
return None return None

@ -30,11 +30,11 @@ class MythrilDisassembler:
self, self,
eth: Optional[EthJsonRpc] = None, eth: Optional[EthJsonRpc] = None,
solc_version: str = None, solc_version: str = None,
solc_args: str = None, solc_settings_json: str = None,
enable_online_lookup: bool = False, enable_online_lookup: bool = False,
) -> None: ) -> None:
self.solc_binary = self._init_solc_binary(solc_version) self.solc_binary = self._init_solc_binary(solc_version)
self.solc_args = solc_args self.solc_settings_json = solc_settings_json
self.eth = eth self.eth = eth
self.enable_online_lookup = enable_online_lookup self.enable_online_lookup = enable_online_lookup
self.sigs = signatures.SignatureDB(enable_online_lookup=enable_online_lookup) self.sigs = signatures.SignatureDB(enable_online_lookup=enable_online_lookup)
@ -163,13 +163,15 @@ class MythrilDisassembler:
try: try:
# import signatures from solidity source # import signatures from solidity source
self.sigs.import_solidity_file( self.sigs.import_solidity_file(
file, solc_binary=self.solc_binary, solc_args=self.solc_args file,
solc_binary=self.solc_binary,
solc_settings_json=self.solc_settings_json,
) )
if contract_name is not None: if contract_name is not None:
contract = SolidityContract( contract = SolidityContract(
input_file=file, input_file=file,
name=contract_name, name=contract_name,
solc_args=self.solc_args, solc_settings_json=self.solc_settings_json,
solc_binary=self.solc_binary, solc_binary=self.solc_binary,
) )
self.contracts.append(contract) self.contracts.append(contract)
@ -177,7 +179,7 @@ class MythrilDisassembler:
else: else:
for contract in get_contracts_from_file( for contract in get_contracts_from_file(
input_file=file, input_file=file,
solc_args=self.solc_args, solc_settings_json=self.solc_settings_json,
solc_binary=self.solc_binary, solc_binary=self.solc_binary,
): ):
self.contracts.append(contract) self.contracts.append(contract)

@ -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

@ -44,23 +44,28 @@ class SourceCodeInfo:
self.solc_mapping = mapping self.solc_mapping = mapping
def get_contracts_from_file(input_file, solc_args=None, solc_binary="solc"): def get_contracts_from_file(input_file, solc_settings_json=None, solc_binary="solc"):
""" """
:param input_file: :param input_file:
:param solc_args: :param solc_settings_json:
:param solc_binary: :param solc_binary:
""" """
data = get_solc_json(input_file, solc_args=solc_args, solc_binary=solc_binary) data = get_solc_json(
input_file, solc_settings_json=solc_settings_json, solc_binary=solc_binary
)
try: try:
for key, contract in data["contracts"].items(): for contract_name in data["contracts"][input_file].keys():
filename, name = key.split(":") if len(
if filename == input_file and len(contract["bin-runtime"]): data["contracts"][input_file][contract_name]["evm"]["deployedBytecode"][
"object"
]
):
yield SolidityContract( yield SolidityContract(
input_file=input_file, input_file=input_file,
name=name, name=contract_name,
solc_args=solc_args, solc_settings_json=solc_settings_json,
solc_binary=solc_binary, solc_binary=solc_binary,
) )
except KeyError: except KeyError:
@ -70,16 +75,22 @@ def get_contracts_from_file(input_file, solc_args=None, solc_binary="solc"):
class SolidityContract(EVMContract): class SolidityContract(EVMContract):
"""Representation of a Solidity contract.""" """Representation of a Solidity contract."""
def __init__(self, input_file, name=None, solc_args=None, solc_binary="solc"): def __init__(
data = get_solc_json(input_file, solc_args=solc_args, solc_binary=solc_binary) self, input_file, name=None, solc_settings_json=None, solc_binary="solc"
):
data = get_solc_json(
input_file, solc_settings_json=solc_settings_json, solc_binary=solc_binary
)
self.solidity_files = [] self.solidity_files = []
self.solc_json = data
self.input_file = input_file
for filename in data["sourceList"]: for filename, contract in data["sources"].items():
with open(filename, "r", encoding="utf-8") as file: with open(filename, "r", encoding="utf-8") as file:
code = file.read() code = file.read()
full_contract_src_maps = self.get_full_contract_src_maps( full_contract_src_maps = self.get_full_contract_src_maps(
data["sources"][filename]["AST"] contract["ast"]
) )
self.solidity_files.append( self.solidity_files.append(
SolidityFile(filename, code, full_contract_src_maps) SolidityFile(filename, code, full_contract_src_maps)
@ -91,32 +102,28 @@ class SolidityContract(EVMContract):
srcmap_constructor = [] srcmap_constructor = []
srcmap = [] srcmap = []
if name: if name:
for key, contract in sorted(data["contracts"].items()): contract = data["contracts"][input_file][name]
filename, _name = key.split(":") if len(contract["evm"]["deployedBytecode"]["object"]):
code = contract["evm"]["deployedBytecode"]["object"]
if ( creation_code = contract["evm"]["bytecode"]["object"]
filename == input_file srcmap = contract["evm"]["deployedBytecode"]["sourceMap"].split(";")
and name == _name srcmap_constructor = contract["evm"]["bytecode"]["sourceMap"].split(";")
and len(contract["bin-runtime"]) has_contract = True
):
code = contract["bin-runtime"]
creation_code = contract["bin"]
srcmap = contract["srcmap-runtime"].split(";")
srcmap_constructor = contract["srcmap"].split(";")
has_contract = True
break
# If no contract name is specified, get the last bytecode entry for the input file # If no contract name is specified, get the last bytecode entry for the input file
else: else:
for key, contract in sorted(data["contracts"].items()): for contract_name, contract in sorted(
filename, name = key.split(":") data["contracts"][input_file].items()
):
if filename == input_file and len(contract["bin-runtime"]): if len(contract["evm"]["deployedBytecode"]["object"]):
code = contract["bin-runtime"] name = contract_name
creation_code = contract["bin"] code = contract["evm"]["deployedBytecode"]["object"]
srcmap = contract["srcmap-runtime"].split(";") creation_code = contract["evm"]["bytecode"]["object"]
srcmap_constructor = contract["srcmap"].split(";") srcmap = contract["evm"]["deployedBytecode"]["sourceMap"].split(";")
srcmap_constructor = contract["evm"]["bytecode"]["sourceMap"].split(
";"
)
has_contract = True has_contract = True
if not has_contract: if not has_contract:
@ -139,8 +146,8 @@ class SolidityContract(EVMContract):
:return: The source maps :return: The source maps
""" """
source_maps = set() source_maps = set()
for child in ast["children"]: for child in ast["nodes"]:
if "contractKind" in child["attributes"]: if child.get("contractKind"):
source_maps.add(child["src"]) source_maps.add(child["src"])
return source_maps return source_maps

@ -1,5 +1,6 @@
"""The Mythril function signature database.""" """The Mythril function signature database."""
import functools import functools
import json
import logging import logging
import multiprocessing import multiprocessing
import os import os
@ -9,6 +10,7 @@ from collections import defaultdict
from subprocess import PIPE, Popen from subprocess import PIPE, Popen
from typing import List, Set, DefaultDict, Dict from typing import List, Set, DefaultDict, Dict
from mythril.ethereum.util import get_solc_json
from mythril.exceptions import CompilerError from mythril.exceptions import CompilerError
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -231,53 +233,20 @@ class SignatureDB(object, metaclass=Singleton):
return [] return []
def import_solidity_file( def import_solidity_file(
self, file_path: str, solc_binary: str = "solc", solc_args: str = None self, file_path: str, solc_binary: str = "solc", solc_settings_json: str = None
): ):
"""Import Function Signatures from solidity source files. """Import Function Signatures from solidity source files.
:param solc_binary: :param solc_binary:
:param solc_args: :param solc_settings_json:
:param file_path: solidity source code file path :param file_path: solidity source code file path
:return: :return:
""" """
cmd = [solc_binary, "--hashes", file_path] solc_json = get_solc_json(file_path, solc_binary, solc_settings_json)
if solc_args:
cmd.extend(solc_args.split())
try: for contract in solc_json["contracts"][file_path].values():
p = Popen(cmd, stdout=PIPE, stderr=PIPE) for name, hash in contract["evm"]["methodIdentifiers"].items():
stdout, stderr = p.communicate() self.add("0x" + hash, name)
ret = p.returncode
if ret != 0:
raise CompilerError(
"Solc has experienced a fatal error (code {}).\n\n{}".format(
ret, stderr.decode("utf-8")
)
)
except FileNotFoundError:
raise CompilerError(
(
"Compiler not found. Make sure that solc is installed and in PATH, "
"or the SOLC environment variable is set."
)
)
stdout = stdout.decode("unicode_escape").split("\n")
for line in stdout:
# the ':' need not be checked but just to be sure
if all(map(lambda x: x in line, ["(", ")", ":"])):
solc_bytes = "0x" + line.split(":")[0]
solc_text = line.split(":")[1].strip()
self.solidity_sigs[solc_bytes].append(solc_text)
log.debug(
"Signatures: found %d signatures after parsing" % len(self.solidity_sigs)
)
# update DB with what we've found
for byte_sig, text_sigs in self.solidity_sigs.items():
for text_sig in text_sigs:
self.add(byte_sig, text_sig)
@staticmethod @staticmethod
def lookup_online(byte_sig: str, timeout: int, proxies=None) -> List[str]: def lookup_online(byte_sig: str, timeout: int, proxies=None) -> List[str]:

@ -21,9 +21,10 @@ py-solc
pytest>=3.6.0 pytest>=3.6.0
pytest-cov pytest-cov
pytest_mock pytest_mock
requests requests>=2.22.0
rlp>=1.0.1 rlp>=1.0.1
transaction>=2.2.1 transaction>=2.2.1
z3-solver>=4.8.5.0 z3-solver>=4.8.5.0
pysha3 pysha3
matplotlib matplotlib
pythx

@ -28,7 +28,7 @@ REQUIRED = [
"py_ecc==1.6.0", "py_ecc==1.6.0",
"ethereum>=2.3.2", "ethereum>=2.3.2",
"z3-solver>=4.8.5.0", "z3-solver>=4.8.5.0",
"requests", "requests>=2.22.0",
"py-solc", "py-solc",
"plyvel", "plyvel",
"eth_abi==1.3.0", "eth_abi==1.3.0",
@ -49,6 +49,7 @@ REQUIRED = [
"persistent>=4.2.0", "persistent>=4.2.0",
"ethereum-input-decoder>=0.2.2", "ethereum-input-decoder>=0.2.2",
"matplotlib", "matplotlib",
"pythx",
] ]
TESTS_REQUIRE = ["mypy", "pytest>=3.6.0", "pytest_mock", "pytest-cov"] TESTS_REQUIRE = ["mypy", "pytest>=3.6.0", "pytest_mock", "pytest-cov"]

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save