def introduce_taint(self, taint, instruction): if not instruction["error"] and instruction["depth"] - 1 < len( self.callstack): records = self.callstack[instruction["depth"] - 1] mutator = SymbolicTaintAnalyzer.stack_taint_table[ instruction["op"]] for i in range(1, mutator[1] + 1): if not records[-1].stack[-i]: records[-1].stack[-i] = [taint] else: record = list(records[-1].stack[-i]) if not taint in record: record.insert(0, taint) records[-1].stack[-i] = record if instruction["op"] in [ "CALL", "CALLCODE", "DELEGATECALL", "STATICCALL" ]: if not records[-1].output: records[-1].output = [] records[-1].output += [taint] elif instruction["op"] == "CALLDATACOPY": records[-1].memory[convert_stack_value_to_int( instruction["stack"][-1])] = [] records[-1].memory[convert_stack_value_to_int( instruction["stack"][-1])].append(taint)
def mutate_sha3(record, instruction): record.stack.pop() offset = convert_stack_value_to_int(instruction["stack"][-1]) record.stack.pop() size = convert_stack_value_to_int(instruction["stack"][-2]) value = SymbolicTaintAnalyzer.extract_taint_from_memory( record.memory, offset, size) record.stack.append(value)
def mutate_copy(record, op, instruction): if op == "EXTCODECOPY": record.stack.pop() index = convert_stack_value_to_int(instruction["stack"][-2]) else: index = convert_stack_value_to_int(instruction["stack"][-1]) record.stack.pop() record.stack.pop() record.memory[index] = record.stack.pop() record.memory = collections.OrderedDict(sorted(record.memory.items()))
def detect_transaction_order_dependency(self, current_instruction, tainted_record, individual, transaction_index): if current_instruction["op"] == "SSTORE": if tainted_record and tainted_record.stack and tainted_record.stack[ -2] and is_expr(tainted_record.stack[-2][0]): index = convert_stack_value_to_int( current_instruction["stack"][-1]) if index not in self.sstores: self.sstores[index] = ( tainted_record.stack[-2][0], individual.chromosome[transaction_index]["arguments"] [0], individual.solution[transaction_index] ["transaction"]["from"], current_instruction["pc"]) elif current_instruction["op"] == "SLOAD": index = convert_stack_value_to_int( current_instruction["stack"][-1]) if index in self.sstores and self.sstores[index][ 1] != individual.chromosome[transaction_index][ "arguments"][0]: self.sloads[index] = ( self.sstores[index][0], individual.chromosome[transaction_index]["arguments"][0], individual.solution[transaction_index]["transaction"] ["from"], self.sstores[index][3]) elif current_instruction["op"] == "CALL": if tainted_record and tainted_record.stack and tainted_record.stack[ -3] and is_expr(tainted_record.stack[-3][0]): for index in self.sloads: if index in self.sstores and self.sloads[index][ 0] == tainted_record.stack[-3][0] and self.sloads[ index][1] == individual.chromosome[ transaction_index]["arguments"][0]: return self.sloads[index][3] if tainted_record and tainted_record.stack and tainted_record.stack[ -2]: value = convert_stack_value_to_int( current_instruction["stack"][-3]) if value > 0 or tainted_record and tainted_record.stack and tainted_record.stack[ -3]: for i in range(transaction_index + 1, len(individual.chromosome)): if self.sstores and individual.chromosome[ transaction_index][ "arguments"] == individual.chromosome[i][ "arguments"] and individual.solution[ transaction_index]["transaction"][ "from"] != individual.solution[ i]["transaction"]["from"]: return list(self.sstores.values())[0][-1] return None
def detect_unhandled_exception(self, previous_instruction, current_instruction, tainted_record): # Register all exceptions if previous_instruction and previous_instruction["op"] in [ "CALL", "CALLCODE", "DELEGATECALL", "STATICCALL" ] and convert_stack_value_to_int( current_instruction["stack"][-1]) == 1: if tainted_record and tainted_record.stack and tainted_record.stack[ -1] and is_expr(tainted_record.stack[-1][0]): self.exceptions[tainted_record.stack[-1] [0]] = previous_instruction["pc"] # Remove all handled exceptions elif current_instruction["op"] == "JUMPI" and self.exceptions: if tainted_record and tainted_record.stack and tainted_record.stack[ -2] and is_expr(tainted_record.stack[-2][0]): for var in get_vars(tainted_record.stack[-2][0]): if var in self.exceptions: del self.exceptions[var] # Report all unhandled exceptions at termination elif current_instruction["op"] in [ "RETURN", "STOP", "SUICIDE", "SELFDESTRUCT" ] and self.exceptions: for exception in self.exceptions: return self.exceptions[exception] return None
def check_taint(self, instruction, source=None): if not instruction["error"] and instruction["depth"] - 1 < len( self.callstack): records = self.callstack[instruction["depth"] - 1] if len(records) < 2: return None mutator = SymbolicTaintAnalyzer.stack_taint_table[ instruction["op"]] values = [] for i in range(0, mutator[0]): if not records[-2].stack: break if i + 1 < len(records[-2].stack): record = records[-2].stack[-(i + 1)] if instruction[ "op"] in SymbolicTaintAnalyzer.memory_access: if not i in SymbolicTaintAnalyzer.memory_access[ instruction["op"]]: if record: values += record else: if record: values += record if instruction["op"] in SymbolicTaintAnalyzer.memory_access: mutator = SymbolicTaintAnalyzer.memory_access[ instruction["op"]] offset = convert_stack_value_to_int( instruction["stack"][-(mutator[0] + 1)]) size = convert_stack_value_to_int( instruction["stack"][-(mutator[1] + 1)]) taint = SymbolicTaintAnalyzer.extract_taint_from_memory( records[-2].memory, offset, size) if taint: values += taint if source: for value in values: if value == source: return records[-2] else: if values: return records[-2] return None
def detect_reentrancy(self, tainted_record, current_instruction): # Remember sloads if current_instruction["op"] == "SLOAD": if tainted_record and tainted_record.stack and tainted_record.stack[ -1]: storage_index = convert_stack_value_to_int( current_instruction["stack"][-1]) self.sloads[storage_index] = current_instruction["pc"] # Remember calls with more than 2300 gas and where the value is larger than zero/symbolic or where destination is symbolic elif current_instruction["op"] == "CALL" and self.sloads: gas = convert_stack_value_to_int(current_instruction["stack"][-1]) value = convert_stack_value_to_int( current_instruction["stack"][-3]) if gas > 2300 and (value > 0 or tainted_record and tainted_record.stack and tainted_record.stack[-3]): self.calls.add(current_instruction["pc"]) if gas > 2300 and tainted_record and tainted_record.stack and tainted_record.stack[ -2]: self.calls.add(current_instruction["pc"]) for pc in self.sloads.values(): if pc < current_instruction["pc"]: return current_instruction["pc"] # Check if this sstore is happening after a call and if it is happening after an sload which shares the same storage index elif current_instruction["op"] == "SSTORE" and self.calls: if tainted_record and tainted_record.stack and tainted_record.stack[ -1]: storage_index = convert_stack_value_to_int( current_instruction["stack"][-1]) if storage_index in self.sloads: for pc in self.calls: if pc < current_instruction["pc"]: return pc # Clear sloads and calls from previous transactions elif current_instruction["op"] in [ "STOP", "RETURN", "REVERT", "ASSERTFAIL", "INVALID", "SUICIDE", "SELFDESTRUCT" ]: self.sloads = {} self.calls = set() return None
def detect_block_dependency(self, tainted_record, current_instruction, previous_branch): # Check for a call with transfer of ether (check if amount is greater than zero or symbolic) if current_instruction["op"] == "CALL" and (convert_stack_value_to_int(current_instruction["stack"][-3]) or tainted_record and tainted_record.stack[-3]) or \ current_instruction["op"] in ["SELFDESTRUCT", "SUICIDE", "CREATE", "DELEGATECALL"]: # Check if there is a block dependency by analyzing previous branch expression for expression in previous_branch: if "blockhash" in str(expression) or \ "coinbase" in str(expression) or \ "timestamp" in str(expression) or \ "number" in str(expression) or \ "difficulty" in str(expression) or \ "gaslimit" in str(expression): self.block_dependency = True # Check if block related information flows into condition elif current_instruction and current_instruction["op"] in [ "LT", "GT", "SLT", "SGT", "EQ" ]: if tainted_record and tainted_record.stack: if tainted_record.stack[-1]: for expression in tainted_record.stack[-1]: if "blockhash" in str(expression) or \ "coinbase" in str(expression) or \ "timestamp" in str(expression) or \ "number" in str(expression) or \ "difficulty" in str(expression) or \ "gaslimit" in str(expression): self.block_dependency = True if tainted_record.stack[-2]: for expression in tainted_record.stack[-2]: if "blockhash" in str(expression) or \ "coinbase" in str(expression) or \ "timestamp" in str(expression) or \ "number" in str(expression) or \ "difficulty" in str(expression) or \ "gaslimit" in str(expression): self.block_dependency = True # Register block related information elif current_instruction["op"] in [ "BLOCKHASH", "COINBASE", "TIMESTAMP", "NUMBER", "DIFFICULTY", "GASLIMIT" ]: self.block_instruction = current_instruction["pc"] # Check if execution stops withour exception if self.block_dependency and current_instruction["op"] in [ "STOP", "SELFDESTRUCT", "RETURN" ]: return self.block_instruction return None
def detect_integer_overflow(self, previous_instruction, current_instruction, tainted_record): if previous_instruction and previous_instruction["op"] == "ADD": a = convert_stack_value_to_int(previous_instruction["stack"][-2]) b = convert_stack_value_to_int(previous_instruction["stack"][-1]) c = convert_stack_value_to_int(current_instruction["stack"][-1]) if a + b != c: if tainted_record and tainted_record.stack and tainted_record.stack[-1]: if get_vars(tainted_record.stack[-1][0]): self.overflows[previous_instruction["pc"]] = get_vars(tainted_record.stack[-1][0]) elif previous_instruction and previous_instruction["op"] == "MUL": a = convert_stack_value_to_int(previous_instruction["stack"][-2]) b = convert_stack_value_to_int(previous_instruction["stack"][-1]) c = convert_stack_value_to_int(current_instruction["stack"][-1]) if a * b != c: if tainted_record and tainted_record.stack and tainted_record.stack[-1]: if get_vars(tainted_record.stack[-1][0]): self.overflows[previous_instruction["pc"]] = get_vars(tainted_record.stack[-1][0]) elif previous_instruction and previous_instruction["op"] == "SUB": a = convert_stack_value_to_int(previous_instruction["stack"][-2]) b = convert_stack_value_to_int(previous_instruction["stack"][-1]) c = convert_stack_value_to_int(current_instruction["stack"][-1]) if a - b != c: if tainted_record and tainted_record.stack and tainted_record.stack[-1]: if get_vars(tainted_record.stack[-1][0]): self.underflows[previous_instruction["pc"]] = get_vars(tainted_record.stack[-1][0]) if current_instruction and current_instruction["op"] == "SSTORE": if tainted_record and tainted_record.stack and tainted_record.stack[-2]: for pc in self.overflows: for var1 in get_vars(tainted_record.stack[-2][0]): for var2 in self.overflows[pc]: if var1 == var2: return pc, "overflow" for pc in self.underflows: for var1 in get_vars(tainted_record.stack[-2][0]): for var2 in self.underflows[pc]: if var1 == var2: return pc, "underflow" return None, None
def detect_leaking_ether(self, current_instruction, taint_record, individual, transaction_index, previous_branch): if current_instruction["op"] == "STOP": if individual.solution[transaction_index]["transaction"][ "value"] > 0: self.spenders.add(individual.solution[transaction_index] ["transaction"]["from"]) if transaction_index in self.leaks: if individual.solution[transaction_index]["transaction"][ "from"] not in self.spenders: return self.leaks[transaction_index] elif current_instruction["op"] == "CALL": to = "0x" + convert_stack_value_to_hex( current_instruction["stack"][-2]).lstrip("0") # Check if the destination of the call is an attacker if to in settings.ATTACKER_ACCOUNTS and to == individual.solution[ transaction_index]["transaction"]["from"]: # Check if the value of the call is larger than zero or the contract balance if convert_stack_value_to_int( current_instruction["stack"][-3] ) > 0 or taint_record and taint_record.stack[-3] and is_expr( taint_record.stack[-3][0]) and "balance" in str( taint_record.stack[-3][0]): # Check if the destination did not spend ether if not to in self.spenders: # Check if the destination was not previously passed as argument by a trusted user address_passed_as_argument = False for i in range(transaction_index): for argument in individual.chromosome[i][ "arguments"]: if argument in settings.ATTACKER_ACCOUNTS and individual.solution[ i]["transaction"][ "from"] not in settings.ATTACKER_ACCOUNTS: address_passed_as_argument = True if not address_passed_as_argument: self.leaks[ transaction_index] = current_instruction["pc"] return None
def detect_integer_overflow(self, mfe, tainted_record, previous_instruction, current_instruction, individual, transaction_index): if previous_instruction and previous_instruction[ "op"] == "NOT" and current_instruction and current_instruction[ "op"] == "ADD": self.compiler_value_negation = True # Addition elif previous_instruction and previous_instruction["op"] == "ADD": a = convert_stack_value_to_int(previous_instruction["stack"][-2]) b = convert_stack_value_to_int(previous_instruction["stack"][-1]) #print(convert_stack_value_to_int(previous_instruction["stack"][-2])) #print(convert_stack_value_to_int(previous_instruction["stack"][-1])) if a + b != convert_stack_value_to_int( current_instruction["stack"] [-1]) and not self.compiler_value_negation: #print("!!!!!!!!") #print("!!!!!!!!") #print("addition overflow") #print("!!!!!!!!") #print("!!!!!!!!") if tainted_record and tainted_record.stack and tainted_record.stack[ -1]: index = ''.join( str(taint) for taint in tainted_record.stack[-1]) if "calldataload" in index or "callvalue" in index: _function_hash = individual.chromosome[ transaction_index]["arguments"][0] _is_string = False for _argument_index in [ int(a.split("_")[-1]) for a in index.split() if a.startswith("calldataload_" + str(transaction_index) + "_") ]: if individual.generator.interface[_function_hash][ _argument_index] == "string": _is_string = True if not _is_string: self.overflows[index] = previous_instruction["pc"] # Multiplication elif previous_instruction and previous_instruction["op"] == "MUL": a = convert_stack_value_to_int(previous_instruction["stack"][-2]) b = convert_stack_value_to_int(previous_instruction["stack"][-1]) if a * b != convert_stack_value_to_int( current_instruction["stack"][-1]): """print(convert_stack_value_to_int(previous_instruction["stack"][-2])) print(convert_stack_value_to_int(previous_instruction["stack"][-1])) print(convert_stack_value_to_hex(previous_instruction["stack"][-1])) print(convert_stack_value_to_int(current_instruction["stack"][-1])) print(convert_stack_value_to_hex(current_instruction["stack"][-1])) print(" ")""" #print("Multiplication overflow") if tainted_record and tainted_record.stack and tainted_record.stack[ -1]: index = ''.join( str(taint) for taint in tainted_record.stack[-1]) #print("yes") if "calldataload" in index or "callvalue" in index: #print("added") self.overflows[index] = previous_instruction["pc"] #tainted_record.stack[-2] = [BitVec("_".join(["overflow", hex(previous_instruction["pc"])]), 256)] #index = ''.join(str(taint) for taint in tainted_record.stack[-2]) #self.overflows[index] = previous_instruction["pc"] #else: # print("nope") #print("") # Subtraction elif previous_instruction and previous_instruction["op"] == "SUB": a = convert_stack_value_to_int(previous_instruction["stack"][-1]) b = convert_stack_value_to_int(previous_instruction["stack"][-2]) if a - b != convert_stack_value_to_int( current_instruction["stack"][-1]): if tainted_record and tainted_record.stack and tainted_record.stack[ -1]: index = ''.join( str(taint) for taint in tainted_record.stack[-1]) self.underflows[index] = previous_instruction["pc"] else: tainted_record = mfe.symbolic_taint_analyzer.get_tainted_record( index=-1) if tainted_record: tainted_record.stack[-2] = [ BitVec( "_".join([ "underflow", hex(previous_instruction["pc"]) ]), 256) ] index = ''.join( str(taint) for taint in tainted_record.stack[-2]) self.underflows[index] = previous_instruction["pc"] # Check if overflow flows into storage if current_instruction and current_instruction["op"] == "SSTORE": #print("sstore") if tainted_record and tainted_record.stack and tainted_record.stack[ -2]: # Storage value index = ''.join( str(taint) for taint in tainted_record.stack[-2]) #print("sstore index") #print(index) if index in self.overflows: return self.overflows[index], "overflow" if index in self.underflows: return self.underflows[index], "underflow" # Check if overflow flows into call elif current_instruction and current_instruction["op"] == "CALL": if tainted_record and tainted_record.stack and tainted_record.stack[ -3]: # Call value #print(tainted_record.stack) index = ''.join( str(taint) for taint in tainted_record.stack[-3]) if index in self.overflows: #print("!!!!!!!") #print("!!!!!!!") #print("yolo") #print("!!!!!!!") #print("!!!!!!!") return self.overflows[index], "overflow" if index in self.underflows: return self.underflows[index], "underflow" # Check if overflow flows into condition elif current_instruction and current_instruction["op"] in [ "LT", "GT", "SLT", "SGT", "EQ" ]: #print(tainted_record.stack) if tainted_record and tainted_record.stack: if tainted_record.stack[-1]: # First operand index = ''.join( str(taint) for taint in tainted_record.stack[-1]) if index in self.overflows: return self.overflows[index], "overflow" if index in self.underflows: return self.underflows[index], "underflow" if tainted_record.stack[-2]: # Second operand index = ''.join( str(taint) for taint in tainted_record.stack[-2]) if index in self.overflows: return self.overflows[index], "overflow" if index in self.underflows: return self.underflows[index], "underflow" return None, None
def mutate_mstore(record, instruction): record.stack.pop() index, value = convert_stack_value_to_int( instruction["stack"][-1]), record.stack.pop() record.memory[index] = value record.memory = collections.OrderedDict(sorted(record.memory.items()))
def mutate_mload(record, instruction): record.stack.pop() index = convert_stack_value_to_int(instruction["stack"][-1]) record.stack.append( SymbolicTaintAnalyzer.extract_taint_from_memory( record.memory, index, 32))
def get_operand(record, instruction, index): if record.stack[-1]: return simplify(record.stack[-index][0]) return BitVecVal( convert_stack_value_to_int(instruction["stack"][-index]), 256)
def mutate_stack_symbolically(record, mutator, instruction): if instruction["op"] in [ # Arithmetic Operations "ADD", "MUL", "SUB", "DIV", "SDIV", "MOD", "SMOD", "ADDMOD", "MULMOD", "EXP", "SHL", "SHR", "SAR", # Comparison Operations "LT", "GT", "SLT", "SGT", "EQ", "ISZERO", # Bitwise Logic Operations "AND", "OR", "XOR", "NOT" ]: # Detect loops if instruction["pc"] not in SymbolicTaintAnalyzer.visited_pcs: SymbolicTaintAnalyzer.visited_pcs.add(instruction["pc"]) else: for i in range(mutator[0]): record.stack.pop() record.stack.append(False) return # First Operand op1 = None if mutator[0] > 0: if record.stack[-1]: op1 = simplify(record.stack[-1][0]) else: op1 = BitVecVal( convert_stack_value_to_int(instruction["stack"][-1]), 256) # Second Operand op2 = None if mutator[0] > 1: if record.stack[-2]: op2 = simplify(record.stack[-2][0]) else: op2 = BitVecVal( convert_stack_value_to_int(instruction["stack"][-2]), 256) # Third Operand op3 = None if mutator[0] > 2: if record.stack[-3]: op3 = simplify(record.stack[-3][0]) else: op3 = BitVecVal( convert_stack_value_to_int(instruction["stack"][-3]), 256) # Check if at least one of the operands is a symbolic expression if record and ((is_expr(op1) and record.stack[-1]) or (is_expr(op2) and record.stack[-2]) or (is_expr(op3) and record.stack[-3])): # Pop old values from stack for i in range(mutator[0]): record.stack.pop() # Push new symbolic expression to stack # Arithmetic Operations if instruction["op"] == "ADD": if is_fixed(op1) and op1.as_long() == 0: record.stack.append([op2]) elif is_fixed(op2) and op2.as_long() == 0: record.stack.append([op1]) else: record.stack.append([op1 + op2]) elif instruction["op"] == "MUL": if (is_fixed(op1) and op1.as_long() == 0) or \ (is_fixed(op2) and op2.as_long() == 0): record.stack.append([BIT_VEC_VAL_ZERO]) else: record.stack.append([op1 * op2]) elif instruction["op"] == "SUB": record.stack.append([op1 - op2]) elif instruction["op"] == "DIV": if (is_fixed(op1) and op1.as_long() == 0) or \ (is_fixed(op2) and op2.as_long() == 0): record.stack.append([BIT_VEC_VAL_ZERO]) else: record.stack.append([UDiv(op1, op2)]) elif instruction["op"] == "SDIV": if (is_fixed(op1) and op1.as_long() == 0) or \ (is_fixed(op2) and op2.as_long() == 0): record.stack.append([BIT_VEC_VAL_ZERO]) else: record.stack.append([op1 / op2]) elif instruction["op"] == "MOD": record.stack.append( [BIT_VEC_VAL_ZERO if op2 == 0 else URem(op1, op2)]) elif instruction["op"] == "SMOD": record.stack.append( [BIT_VEC_VAL_ZERO if op2 == 0 else SRem(op1, op2)]) elif instruction["op"] == "ADDMOD": record.stack.append( [URem(URem(op1, op3) + URem(op2, op3), op3)]) elif instruction["op"] == "MULMOD": record.stack.append( [URem(URem(op1, op3) * URem(op2, op3), op3)]) elif instruction["op"] == "EXP": if is_bv_value(op1) and is_bv_value(op2): record.stack.append([ BitVecVal( pow(op1.as_long(), op2.as_long(), 2**256), 256) ]) else: record.stack.append(False) elif instruction["op"] == "SHL": record.stack.append([op1 << op2]) elif instruction["op"] == "SHR": record.stack.append([LShR(op1, op2)]) elif instruction["op"] == "SAR": record.stack.append([op1 >> op2]) # Comparison Operations elif instruction["op"] == "LT": record.stack.append( [If(ULT(op1, op2), BIT_VEC_VAL_ONE, BIT_VEC_VAL_ZERO)]) elif instruction["op"] == "GT": record.stack.append( [If(UGT(op1, op2), BIT_VEC_VAL_ONE, BIT_VEC_VAL_ZERO)]) elif instruction["op"] == "SLT": record.stack.append( [If(op1 < op2, BIT_VEC_VAL_ONE, BIT_VEC_VAL_ZERO)]) elif instruction["op"] == "SGT": record.stack.append( [If(op1 > op2, BIT_VEC_VAL_ONE, BIT_VEC_VAL_ZERO)]) elif instruction["op"] == "EQ": record.stack.append( [If(op1 == op2, BIT_VEC_VAL_ONE, BIT_VEC_VAL_ZERO)]) elif instruction["op"] == "ISZERO": record.stack.append( [If(op1 == 0, BIT_VEC_VAL_ONE, BIT_VEC_VAL_ZERO)]) # Bitwise Logic Operations elif instruction["op"] == "AND": if (is_fixed(op1) and op1.as_long() == 0) or \ (is_fixed(op2) and op2.as_long() == 0): record.stack.append([BIT_VEC_VAL_ZERO]) else: record.stack.append([op1 & op2]) elif instruction["op"] == "OR": record.stack.append([op1 | op2]) elif instruction["op"] == "XOR": if is_fixed(op1) and op1.as_long() == 0: record.stack.append([op2]) elif is_fixed(op2) and op2.as_long() == 0: record.stack.append([op1]) else: record.stack.append([op1 ^ op2]) elif instruction["op"] == "NOT": record.stack.append([~op1]) else: SymbolicTaintAnalyzer.mutate_stack(record, mutator) else: SymbolicTaintAnalyzer.mutate_stack(record, mutator)