|
|
@ -5,7 +5,8 @@ This includes classes representing accounts and their storage. |
|
|
|
import logging |
|
|
|
import logging |
|
|
|
from copy import copy, deepcopy |
|
|
|
from copy import copy, deepcopy |
|
|
|
from typing import Any, Dict, Union, Tuple, cast |
|
|
|
from typing import Any, Dict, Union, Tuple, cast |
|
|
|
|
|
|
|
from sha3 import keccak_256 |
|
|
|
|
|
|
|
from random import randint |
|
|
|
|
|
|
|
|
|
|
|
from mythril.laser.smt import ( |
|
|
|
from mythril.laser.smt import ( |
|
|
|
Array, |
|
|
|
Array, |
|
|
@ -16,6 +17,8 @@ from mythril.laser.smt import ( |
|
|
|
Extract, |
|
|
|
Extract, |
|
|
|
BaseArray, |
|
|
|
BaseArray, |
|
|
|
Concat, |
|
|
|
Concat, |
|
|
|
|
|
|
|
And, |
|
|
|
|
|
|
|
If |
|
|
|
) |
|
|
|
) |
|
|
|
from mythril.disassembler.disassembly import Disassembly |
|
|
|
from mythril.disassembler.disassembly import Disassembly |
|
|
|
from mythril.laser.smt import symbol_factory |
|
|
|
from mythril.laser.smt import symbol_factory |
|
|
@ -32,18 +35,6 @@ class StorageRegion: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ArrayStorageRegion(StorageRegion): |
|
|
|
class ArrayStorageRegion(StorageRegion): |
|
|
|
""" An ArrayStorageRegion is a storage region that leverages smt array theory to resolve expressions""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class IteStorageRegion(StorageRegion): |
|
|
|
|
|
|
|
""" An IteStorageRegion is a storage region that uses Ite statements to implement a storage""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Storage: |
|
|
|
|
|
|
|
"""Storage class represents the storage of an Account.""" |
|
|
|
"""Storage class represents the storage of an Account.""" |
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, concrete=False, address=None, dynamic_loader=None) -> None: |
|
|
|
def __init__(self, concrete=False, address=None, dynamic_loader=None) -> None: |
|
|
@ -57,13 +48,13 @@ class Storage: |
|
|
|
self._standard_storage = Array("Storage", 256, 256) |
|
|
|
self._standard_storage = Array("Storage", 256, 256) |
|
|
|
self._map_storage = {} # type: Dict[BitVec, BaseArray] |
|
|
|
self._map_storage = {} # type: Dict[BitVec, BaseArray] |
|
|
|
|
|
|
|
|
|
|
|
self.printable_storage = {} # type: Dict[BitVec, BitVec] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.dynld = dynamic_loader |
|
|
|
self.dynld = dynamic_loader |
|
|
|
self.address = address |
|
|
|
self.address = address |
|
|
|
|
|
|
|
|
|
|
|
@staticmethod |
|
|
|
@staticmethod |
|
|
|
def _sanitize(input_: BitVec) -> BitVec: |
|
|
|
def _sanitize(input_: BitVec) -> BitVec: |
|
|
|
|
|
|
|
if input_.potential_values: |
|
|
|
|
|
|
|
input_ = input_.potential_values |
|
|
|
if input_.size() == 512: |
|
|
|
if input_.size() == 512: |
|
|
|
return input_ |
|
|
|
return input_ |
|
|
|
if input_.size() > 512: |
|
|
|
if input_.size() > 512: |
|
|
@ -77,7 +68,7 @@ class Storage: |
|
|
|
item = self._sanitize(cast(BitVecFunc, item).input_) |
|
|
|
item = self._sanitize(cast(BitVecFunc, item).input_) |
|
|
|
value = storage[item] |
|
|
|
value = storage[item] |
|
|
|
if ( |
|
|
|
if ( |
|
|
|
(value.value == 0 or value.value is None) # 0 for Array, None for K |
|
|
|
value.value == 0 |
|
|
|
and self.address |
|
|
|
and self.address |
|
|
|
and item.symbolic is False |
|
|
|
and item.symbolic is False |
|
|
|
and self.address.value != 0 |
|
|
|
and self.address.value != 0 |
|
|
@ -94,10 +85,9 @@ class Storage: |
|
|
|
), |
|
|
|
), |
|
|
|
256, |
|
|
|
256, |
|
|
|
) |
|
|
|
) |
|
|
|
self.printable_storage[item] = storage[item] |
|
|
|
|
|
|
|
return storage[item] |
|
|
|
return storage[item] |
|
|
|
except ValueError as e: |
|
|
|
except ValueError: |
|
|
|
log.debug("Couldn't read storage at %s: %s", item, e) |
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
return simplify(storage[item]) |
|
|
|
return simplify(storage[item]) |
|
|
|
|
|
|
|
|
|
|
@ -132,24 +122,212 @@ class Storage: |
|
|
|
|
|
|
|
|
|
|
|
def __setitem__(self, key, value: Any) -> None: |
|
|
|
def __setitem__(self, key, value: Any) -> None: |
|
|
|
storage, is_keccak_storage = self._get_corresponding_storage(key) |
|
|
|
storage, is_keccak_storage = self._get_corresponding_storage(key) |
|
|
|
self.printable_storage[key] = value |
|
|
|
|
|
|
|
if is_keccak_storage: |
|
|
|
if is_keccak_storage: |
|
|
|
key = self._sanitize(key.input_) |
|
|
|
key = self._sanitize(key.input_) |
|
|
|
storage[key] = value |
|
|
|
storage[key] = value |
|
|
|
|
|
|
|
|
|
|
|
def __deepcopy__(self, memodict=dict()): |
|
|
|
def __deepcopy__(self, memodict=dict()): |
|
|
|
concrete = isinstance(self._standard_storage, K) |
|
|
|
concrete = isinstance(self._standard_storage, K) |
|
|
|
storage = Storage( |
|
|
|
storage = ArrayStorageRegion( |
|
|
|
concrete=concrete, address=self.address, dynamic_loader=self.dynld |
|
|
|
concrete=concrete, address=self.address, dynamic_loader=self.dynld |
|
|
|
) |
|
|
|
) |
|
|
|
storage._standard_storage = deepcopy(self._standard_storage) |
|
|
|
storage._standard_storage = deepcopy(self._standard_storage) |
|
|
|
storage._map_storage = deepcopy(self._map_storage) |
|
|
|
storage._map_storage = deepcopy(self._map_storage) |
|
|
|
storage.print_storage = copy(self.printable_storage) |
|
|
|
|
|
|
|
|
|
|
|
return storage |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class IteStorageRegion(StorageRegion): |
|
|
|
|
|
|
|
""" An IteStorageRegion is a storage region that uses Ite statements to implement a storage""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self) -> None: |
|
|
|
|
|
|
|
"""Constructor for Storage. |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
self.itedict = {} # type: Dict[Tuple[BitVecFunc, Any]] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __getitem__(self, item: BitVecFunc): |
|
|
|
|
|
|
|
storage = symbol_factory.BitVecVal(0, 256) |
|
|
|
|
|
|
|
for key, val in self.itedict.items(): |
|
|
|
|
|
|
|
storage = If(item == key, val, storage) |
|
|
|
|
|
|
|
return storage |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __setitem__(self, key: BitVecFunc, value): |
|
|
|
|
|
|
|
self.itedict[key] = value |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __deepcopy__(self, memodict={}): |
|
|
|
|
|
|
|
ite_copy = IteStorageRegion() |
|
|
|
|
|
|
|
ite_copy.itedict = copy(self.itedict) |
|
|
|
|
|
|
|
return ite_copy |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Storage: |
|
|
|
|
|
|
|
"""Storage class represents the storage of an Account.""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __init__( |
|
|
|
|
|
|
|
self, concrete=False, address=None, dynamic_loader=None, copy_call=False |
|
|
|
|
|
|
|
) -> None: |
|
|
|
|
|
|
|
"""Constructor for Storage. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:param concrete: bool indicating whether to interpret uninitialized storage as concrete versus symbolic |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
if copy_call: |
|
|
|
|
|
|
|
# This is done because it was costly create these instances |
|
|
|
|
|
|
|
self._array_region = None |
|
|
|
|
|
|
|
self._ite_region = None |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
self._array_region = ArrayStorageRegion(concrete, address, dynamic_loader) |
|
|
|
|
|
|
|
self._ite_region = IteStorageRegion() |
|
|
|
|
|
|
|
self._printable_storage = {} # type: Dict[BitVec, BitVec] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod |
|
|
|
|
|
|
|
def _array_condition(key: BitVec): |
|
|
|
|
|
|
|
return not isinstance(key, BitVecFunc) or ( |
|
|
|
|
|
|
|
isinstance(key, BitVecFunc) |
|
|
|
|
|
|
|
and key.func_name == "keccak256" |
|
|
|
|
|
|
|
and len(key.nested_functions) <= 1 |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __getitem__(self, key: BitVec) -> BitVec: |
|
|
|
|
|
|
|
ite_get = self._ite_region[cast(BitVecFunc, key)] |
|
|
|
|
|
|
|
array_get = self._array_region[key] |
|
|
|
|
|
|
|
if self._array_condition(key): |
|
|
|
|
|
|
|
return If(ite_get, ite_get, array_get) |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
return ite_get |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __setitem__(self, key: BitVec, value: Any) -> None: |
|
|
|
|
|
|
|
self._printable_storage[key] = value |
|
|
|
|
|
|
|
if self._array_condition(key): |
|
|
|
|
|
|
|
self._array_region[key] = value |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self._ite_region[cast(BitVecFunc, key)] = value |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __deepcopy__(self, memodict=dict()): |
|
|
|
|
|
|
|
storage = Storage(copy_call=True) |
|
|
|
|
|
|
|
storage._array_region = deepcopy(self._array_region) |
|
|
|
|
|
|
|
storage._ite_region = deepcopy(self._ite_region) |
|
|
|
|
|
|
|
storage._printable_storage = copy(self._printable_storage) |
|
|
|
return storage |
|
|
|
return storage |
|
|
|
|
|
|
|
|
|
|
|
def __str__(self) -> str: |
|
|
|
def __str__(self) -> str: |
|
|
|
# TODO: Do something better here |
|
|
|
# TODO: Do something better here |
|
|
|
return str(self.printable_storage) |
|
|
|
return str(self._printable_storage) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def concretize(self, models): |
|
|
|
|
|
|
|
for key, value in self._ite_region.itedict.items(): |
|
|
|
|
|
|
|
key_concrete = self._traverse_concretise(key, models) |
|
|
|
|
|
|
|
key.potential_values = key_concrete |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def calc_sha3(self, val, size): |
|
|
|
|
|
|
|
try: |
|
|
|
|
|
|
|
hex_val = hex(val.value)[2:] |
|
|
|
|
|
|
|
if len(hex_val) % 2 != 0: |
|
|
|
|
|
|
|
hex_val += "0" |
|
|
|
|
|
|
|
val = int(keccak_256(bytes.fromhex(hex_val)).hexdigest(), 16) |
|
|
|
|
|
|
|
except (AttributeError, TypeError): |
|
|
|
|
|
|
|
ran = hex(randint(0, 2 ** size - 1))[2:] |
|
|
|
|
|
|
|
if len(ran) % 2 != 0: |
|
|
|
|
|
|
|
ran += "0" |
|
|
|
|
|
|
|
val = int(keccak_256(bytes.fromhex(ran)).hexdigest(), 16) |
|
|
|
|
|
|
|
return symbol_factory.BitVecVal(val, 256) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _find_value(self, symbol, model): |
|
|
|
|
|
|
|
if model is None: |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
modify = symbol |
|
|
|
|
|
|
|
size = min(symbol.size(), 256) |
|
|
|
|
|
|
|
if symbol.size() > 256: |
|
|
|
|
|
|
|
index = simplify(Extract(255, 0, symbol)) |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
index = None |
|
|
|
|
|
|
|
if index and not index.symbolic: |
|
|
|
|
|
|
|
modify = Extract(511, 256, modify) |
|
|
|
|
|
|
|
modify = model.eval(modify.raw) |
|
|
|
|
|
|
|
try: |
|
|
|
|
|
|
|
modify = modify.as_long() |
|
|
|
|
|
|
|
except AttributeError: |
|
|
|
|
|
|
|
modify = randint(0, 2 ** modify.size() - 1) |
|
|
|
|
|
|
|
modify = symbol_factory.BitVecVal(modify, size) |
|
|
|
|
|
|
|
if index and not index.symbolic: |
|
|
|
|
|
|
|
modify = Concat(modify, index) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert modify.size() == symbol.size() |
|
|
|
|
|
|
|
return modify |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _traverse_concretise(self, key, models): |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
Breadth first Search |
|
|
|
|
|
|
|
:param key: |
|
|
|
|
|
|
|
:param model: |
|
|
|
|
|
|
|
:return: |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
if not isinstance(key, BitVecFunc): |
|
|
|
|
|
|
|
concrete_values = [self._find_value(key, model[0]) for model in models] |
|
|
|
|
|
|
|
if key.size() == 512: |
|
|
|
|
|
|
|
ex_key = Extract(511, 256, key) |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
ex_key = key |
|
|
|
|
|
|
|
potential_values = concrete_values |
|
|
|
|
|
|
|
key.potential_values = [] |
|
|
|
|
|
|
|
for i, val in enumerate(potential_values): |
|
|
|
|
|
|
|
key.potential_values.append( |
|
|
|
|
|
|
|
(val, And(models[i][1], BitVec(key.raw) == val)) |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return key.potential_values |
|
|
|
|
|
|
|
if key.size() == 512: |
|
|
|
|
|
|
|
val = simplify(Extract(511, 256, key)) |
|
|
|
|
|
|
|
concrete_vals = self._traverse_concretise(val, models) |
|
|
|
|
|
|
|
vals2 = self._traverse_concretise(Extract(255, 0, key), models) |
|
|
|
|
|
|
|
key.potential_values = [] |
|
|
|
|
|
|
|
i = 0 |
|
|
|
|
|
|
|
for val1, val2 in zip(concrete_vals, vals2): |
|
|
|
|
|
|
|
if val2 and val1: |
|
|
|
|
|
|
|
c_val = Concat(val1[0], val2[0]) |
|
|
|
|
|
|
|
condition = And( |
|
|
|
|
|
|
|
models[i][1], BitVec(key.raw) == c_val, val1[1], val2[1] |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
key.potential_values.append((c_val, condition)) |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
key.potential_values.append((None, None)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if isinstance(key.input_, BitVec) or ( |
|
|
|
|
|
|
|
isinstance(key.input_, BitVecFunc) and key.input_.func_name == "sha3" |
|
|
|
|
|
|
|
): |
|
|
|
|
|
|
|
self._traverse_concretise(key.input_, models) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if isinstance(key, BitVecFunc): |
|
|
|
|
|
|
|
if key.size() == 512: |
|
|
|
|
|
|
|
p1 = Extract(511, 256, key) |
|
|
|
|
|
|
|
if not isinstance(p1, BitVecFunc): |
|
|
|
|
|
|
|
p1 = Extract(255, 0, key) |
|
|
|
|
|
|
|
p1 = [ |
|
|
|
|
|
|
|
(self.calc_sha3(val[0], p1.input_.size()), val[1]) |
|
|
|
|
|
|
|
for val in p1.input_.potential_values |
|
|
|
|
|
|
|
] |
|
|
|
|
|
|
|
key.potential_values = [] |
|
|
|
|
|
|
|
for i, val in enumerate(p1): |
|
|
|
|
|
|
|
if val[0]: |
|
|
|
|
|
|
|
c_val = Concat(val[0], Extract(255, 0, key)) |
|
|
|
|
|
|
|
condition = And(models[i][1], val[1], BitVec(key.raw) == c_val) |
|
|
|
|
|
|
|
key.potential_values.append((c_val, condition)) |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
key.potential_values.append((None, None)) |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
key.potential_values = [] |
|
|
|
|
|
|
|
for i, val in enumerate(key.input_.potential_values): |
|
|
|
|
|
|
|
if val[0]: |
|
|
|
|
|
|
|
concrete_val = self.calc_sha3(val[0], key.input_.size()) |
|
|
|
|
|
|
|
condition = And( |
|
|
|
|
|
|
|
models[i][1], val[1], BitVec(key.raw) == concrete_val |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
key.potential_values.append((concrete_val, condition)) |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
key.potential_values.append((None, None)) |
|
|
|
|
|
|
|
if key.potential_values[0][0] is not None: |
|
|
|
|
|
|
|
assert key.size() == key.potential_values[0][0].size() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return key.potential_values |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Account: |
|
|
|
class Account: |
|
|
|