Skip to content

Commit 4642556

Browse files
feliamEric Hennenfent
authored andcommitted
Symbol friendly fake hash replacement for evm sha3 (#1549)
* Okay, boring fakehash added * Correctness oriented default * blkn * sha3type sound default * Stack is not simplified by default * better log * read/write mem black * . * Delete test workspace
1 parent e36baf5 commit 4642556

File tree

6 files changed

+86
-56
lines changed

6 files changed

+86
-56
lines changed

manticore/core/state.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -311,13 +311,19 @@ def must_be_true(self, expr):
311311
self._constraints, expr == False
312312
)
313313

314-
def solve_one(self, *exprs, constrain=False):
314+
def solve_one(self, expr, constrain=False):
315+
"""
316+
A version of solver_one_n for a single expression. See solve_one_n.
317+
"""
318+
return self.solve_one_n(expr, constrain=constrain)[0]
319+
320+
def solve_one_n(self, *exprs, constrain=False):
315321
"""
316322
Concretize a symbolic :class:`~manticore.core.smtlib.expression.Expression` into
317323
one solution.
318324
319-
:param exprs: Symbolic value to concretize. An iterable of manticore.core.smtlib.Expression
320-
:param bool constrain: If True, constrain expr to concretized value
325+
:param exprs: An iterable of manticore.core.smtlib.Expression
326+
:param bool constrain: If True, constrain expr to solved solution value
321327
:return: Concrete value or a tuple of concrete values
322328
:rtype: int
323329
"""
@@ -334,8 +340,6 @@ def solve_one(self, *exprs, constrain=False):
334340
if isinstance(value, bytearray):
335341
value = bytes(value)
336342
values.append(value)
337-
if len(exprs) == 1:
338-
values = values[0]
339343
return values
340344

341345
def solve_n(self, expr, nsolves):

manticore/ethereum/detectors.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -661,27 +661,29 @@ class DetectUninitializedMemory(Detector):
661661
IMPACT = DetectorClassification.MEDIUM
662662
CONFIDENCE = DetectorClassification.HIGH
663663

664-
def did_evm_read_memory_callback(self, state, offset, value):
664+
def did_evm_read_memory_callback(self, state, offset, value, size):
665665
initialized_memory = state.context.get("{:s}.initialized_memory".format(self.name), set())
666666
cbu = True # Can be unknown
667667
current_contract = state.platform.current_vm.address
668668
for known_contract, known_offset in initialized_memory:
669669
if current_contract == known_contract:
670-
cbu = Operators.AND(cbu, offset != known_offset)
670+
for offset_i in range(offset, offset + size):
671+
cbu = Operators.AND(cbu, offset_i != known_offset)
671672
if state.can_be_true(cbu):
672673
self.add_finding_here(
673674
state,
674675
"Potentially reading uninitialized memory at instruction (address: %r, offset %r)"
675676
% (current_contract, offset),
676677
)
677678

678-
def did_evm_write_memory_callback(self, state, offset, value):
679+
def did_evm_write_memory_callback(self, state, offset, value, size):
679680
current_contract = state.platform.current_vm.address
680681

681682
# concrete or symbolic write
682-
state.context.setdefault("{:s}.initialized_memory".format(self.name), set()).add(
683-
(current_contract, offset)
684-
)
683+
for offset_i in range(offset, offset + size):
684+
state.context.setdefault("{:s}.initialized_memory".format(self.name), set()).add(
685+
(current_contract, offset)
686+
)
685687

686688

687689
class DetectUninitializedStorage(Detector):

manticore/ethereum/manticore.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class Sha3Type(Enum):
5050

5151
concretize = "concretize"
5252
symbolicate = "symbolicate"
53+
fake = "fake"
5354

5455
def title(self):
5556
return self._name_.title()
@@ -64,7 +65,7 @@ def from_string(cls, name):
6465
consts.add(
6566
"sha3",
6667
default=Sha3Type.symbolicate,
67-
description="concretize: sound simple concretization\nsymbolicate(*): unsound symbolication with out of cycle false positive killing",
68+
description="concretize: sound simple concretization\nsymbolicate(*): unsound symbolication with an out of cycle false positive killing\nfake: using a symbol friendly fake hash (This potentially produces wrong testcases) ",
6869
)
6970
consts.add(
7071
"sha3timeout",
@@ -403,8 +404,6 @@ def __init__(self, workspace_url: str = None, policy: str = "random"):
403404
self.subscribe("on_symbolic_function", self._on_concretize)
404405
elif consts.sha3 is consts.sha3.symbolicate:
405406
self.subscribe("on_symbolic_function", self.on_unsound_symbolication)
406-
else:
407-
raise NotImplemented
408407

409408
self._accounts: Dict[str, EVMContract] = dict()
410409
self._serializer = PickleSerializer()
@@ -625,7 +624,7 @@ def solidity_create_contract(
625624
for state in self.ready_states:
626625
if state.platform.get_code(int(contract_account)):
627626
return contract_account
628-
logger.info("Failed to compile contract", contract_names)
627+
logger.info("Failed to compile contract %r", contract_names)
629628
return None
630629

631630
def get_nonce(self, address):
@@ -1182,15 +1181,15 @@ def on_unsound_symbolication(self, state, func, data, result):
11821181
# if we found another pair that matches use that instead
11831182
# the duplicated pair is not added to symbolic_pairs
11841183
if state.must_be_true(Operators.OR(x == data, y == value)):
1185-
constraint = simplify(Operators.AND(x == data, y == value))
1184+
constraint = Operators.AND(x == data, y == value)
11861185
state.constrain(constraint)
11871186
data, value = x, y
11881187
break
11891188
else:
11901189
# bijectiveness; new pair is added to symbolic_pairs
11911190
for x, y in symbolic_pairs:
11921191
if len(x) == len(data):
1193-
constraint = simplify((x == data) == (y == value))
1192+
constraint = (x == data) == (y == value)
11941193
else:
11951194
constraint = y != value
11961195
state.constrain(constraint) # bijective
@@ -1261,9 +1260,7 @@ def match(state, func, symbolic_pairs, concrete_pairs, start=None):
12611260
for x_concrete, y_concrete in concrete_pairs:
12621261
if len(x) == len(x_concrete): # If the size of the buffer wont
12631262
# match it does not matter
1264-
unseen = Operators.AND(
1265-
Operators.AND(x != x_concrete, y != y_concrete), unseen
1266-
)
1263+
unseen = Operators.AND(x != x_concrete, y != y_concrete, unseen)
12671264
# Search for a new unseen sha3 pair
12681265
with state as temp_state:
12691266
temp_state.constrain(unseen)

manticore/platforms/evm.py

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,7 @@ def extend_with_zeroes(b):
749749
# Used calldata size
750750
self._used_calldata_size = 0
751751
self._valid_jmpdests = set()
752+
self._sha3 = {}
752753

753754
@property
754755
def pc(self):
@@ -777,6 +778,7 @@ def gas(self):
777778

778779
def __getstate__(self):
779780
state = super().__getstate__()
781+
state["sha3"] = self._sha3
780782
state["memory"] = self.memory
781783
state["world"] = self._world
782784
state["constraints"] = self.constraints
@@ -800,6 +802,7 @@ def __getstate__(self):
800802
return state
801803

802804
def __setstate__(self, state):
805+
self._sha3 = state["sha3"]
803806
self._checkpoint_data = state["_checkpoint_data"]
804807
self._published_pre_instruction_events = state["_published_pre_instruction_events"]
805808
self._on_transaction = state["_on_transaction"]
@@ -940,6 +943,7 @@ def _push(self, value):
940943
value = simplify(value)
941944
if isinstance(value, Constant) and not value.taint:
942945
value = value.value
946+
943947
self.stack.append(value)
944948

945949
def _top(self, n=0):
@@ -1065,9 +1069,6 @@ def _pop_arguments(self):
10651069
for _ in range(current.pops):
10661070
arguments.append(self._pop())
10671071
# simplify stack arguments
1068-
for i, arg in enumerate(arguments):
1069-
if isinstance(arg, Constant) and not arg.taint:
1070-
arguments[i] = arg.value
10711072
return arguments
10721073

10731074
def _top_arguments(self):
@@ -1119,7 +1120,7 @@ def _checkpoint(self):
11191120
if self._checkpoint_data is None:
11201121
if not self._published_pre_instruction_events:
11211122
self._published_pre_instruction_events = True
1122-
self._publish("will_decode_instruction", self.pc)
1123+
# self._publish("will_decode_instruction", self.pc)
11231124
self._publish(
11241125
"will_evm_execute_instruction", self.instruction, self._top_arguments()
11251126
)
@@ -1154,7 +1155,7 @@ def _set_check_jmpdest(self, flag=True):
11541155
Note that at this point `flag` can be the conditional from a JUMPI
11551156
instruction hence potentially a symbolic value.
11561157
"""
1157-
self._check_jumpdest = simplify(flag)
1158+
self._check_jumpdest = flag
11581159

11591160
def _check_jmpdest(self):
11601161
"""
@@ -1164,7 +1165,13 @@ def _check_jmpdest(self):
11641165
Here, if symbolic, the conditional `self._check_jumpdest` would be
11651166
already constrained to a single concrete value.
11661167
"""
1167-
should_check_jumpdest = self._check_jumpdest
1168+
# If pc is already pointing to a JUMPDEST thre is no need to check.
1169+
pc = self.pc.value if isinstance(self.pc, Constant) else self.pc
1170+
if pc in self._valid_jumpdests:
1171+
self._check_jumpdest = False
1172+
return
1173+
1174+
should_check_jumpdest = simplify(self._check_jumpdest)
11681175
if isinstance(should_check_jumpdest, Constant):
11691176
should_check_jumpdest = should_check_jumpdest.value
11701177
elif issymbolic(should_check_jumpdest):
@@ -1176,11 +1183,10 @@ def _check_jmpdest(self):
11761183
should_check_jumpdest = should_check_jumpdest_solutions[0]
11771184

11781185
# If it can be solved only to False just set it False. If it can be solved
1179-
# only to True, process it and also se it to False
1186+
# only to True, process it and also set it to False
11801187
self._check_jumpdest = False
11811188

11821189
if should_check_jumpdest:
1183-
pc = self.pc.value if isinstance(self.pc, Constant) else self.pc
11841190
if pc not in self._valid_jumpdests:
11851191
raise InvalidOpcode()
11861192

@@ -1235,7 +1241,6 @@ def setstate(state, value):
12351241
last_pc, last_gas, instruction, arguments, fee, allocated = self._checkpoint()
12361242
result = self._handler(*arguments)
12371243
self._advance(result)
1238-
12391244
except ConcretizeGas as ex:
12401245

12411246
def setstate(state, value):
@@ -1324,19 +1329,13 @@ def _load(self, offset, size=1):
13241329
except Exception:
13251330
pass
13261331

1327-
for i in range(size):
1328-
self._publish(
1329-
"did_evm_read_memory", offset + i, Operators.EXTRACT(value, (size - i - 1) * 8, 8)
1330-
)
1332+
self._publish("did_evm_read_memory", offset, value, size)
13311333
return value
13321334

13331335
def _store(self, offset, value, size=1):
13341336
"""Stores value in memory as a big endian"""
13351337
self.memory.write_BE(offset, value, size)
1336-
for i in range(size):
1337-
self._publish(
1338-
"did_evm_write_memory", offset + i, Operators.EXTRACT(value, (size - i - 1) * 8, 8)
1339-
)
1338+
self._publish("did_evm_write_memory", offset, value, size)
13401339

13411340
def safe_add(self, a, b, *args):
13421341
a = Operators.ZEXTEND(a, 512)
@@ -1575,7 +1574,24 @@ def SHA3(self, start, size):
15751574
15761575
"""
15771576
data = self.read_buffer(start, size)
1578-
return self.world.symbolic_function(globalsha3, data)
1577+
1578+
if consts.sha3 == consts.sha3.fake:
1579+
# Fake hash selected. Replace with symbol friendly hash.
1580+
h = len(data) + 0x4141414141414141414141414141414141414141414141414141414141414100
1581+
for i in range(0, len(data), 32):
1582+
h <<= 16
1583+
h += data.read_BE(i, 32)
1584+
1585+
# Try to avoid some common incorrect cases
1586+
self.constraints.add(h != 0)
1587+
# Force fake collision resistance
1588+
for x, y in self._sha3.items():
1589+
self.constraints.add((data == x) == (h == y))
1590+
1591+
self._sha3[data] = h
1592+
return h
1593+
else:
1594+
return self.world.symbolic_function(globalsha3, data)
15791595

15801596
############################################################################
15811597
# Environmental Information
@@ -1646,7 +1662,7 @@ def CALLDATACOPY_gas(self, mem_offset, data_offset, size):
16461662
memfee = self._get_memfee(mem_offset, size)
16471663
return self.safe_add(copyfee, memfee)
16481664

1649-
# @concretized_args(size="SAMPLED")
1665+
# @concretized_args(size="ALL")
16501666
def CALLDATACOPY(self, mem_offset, data_offset, size):
16511667
"""Copy input data in current environment to memory"""
16521668
# calldata_overflow = const.calldata_overflow
@@ -1680,10 +1696,6 @@ def CALLDATACOPY(self, mem_offset, data_offset, size):
16801696
logger.info(f"Constraining CALLDATACOPY size to {cap}")
16811697
max_size = cap
16821698
self.constraints.add(Operators.ULE(size, cap))
1683-
# cond = Operators.OR(size == 0, size==4)
1684-
# for conc_size in range(8,max_size, 32):
1685-
# cond = Operators.OR(size==conc_size, cond)
1686-
# self.constraints.add(cond)
16871699

16881700
for i in range(max_size):
16891701
try:

tests/ethereum/EVM/test_EVMCALLDATALOAD.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from manticore.platforms import evm
55
from manticore.core import state
66
from manticore.core.smtlib import Operators, ConstraintSet
7+
from manticore.core.smtlib import simplify, to_constant
78
import os
89

910

@@ -76,8 +77,8 @@ def test_CALLDATALOAD_2(self):
7677
last_exception, last_returned = self._execute(new_vm)
7778
self.assertEqual(last_exception, None)
7879
self.assertEqual(new_vm.pc, 1)
79-
self.assertEqual(
80-
new_vm.stack,
80+
self.assertSequenceEqual(
81+
list(map(to_constant, new_vm.stack)),
8182
[29515630589904128245223976570842015727304113738300535931626442982409229107200],
8283
)
8384

@@ -101,8 +102,8 @@ def test_CALLDATALOAD_3(self):
101102
last_exception, last_returned = self._execute(new_vm)
102103
self.assertEqual(last_exception, None)
103104
self.assertEqual(new_vm.pc, 1)
104-
self.assertEqual(
105-
new_vm.stack,
105+
self.assertSequenceEqual(
106+
list(map(to_constant, new_vm.stack)),
106107
[29515630589904128245223976570842015727304113738300535931626442982409224847360],
107108
)
108109

@@ -171,7 +172,7 @@ def test_CALLDATALOAD_6(self):
171172
self.assertEqual(last_exception, None)
172173
self.assertEqual(new_vm.pc, 1)
173174
self.assertEqual(
174-
new_vm.stack,
175+
list(map(to_constant, new_vm.stack)),
175176
[29515630589904128245223976570842010042800435681475029265659040150473943285760],
176177
)
177178

0 commit comments

Comments
 (0)