def SHL(self): op0 = self._stack_pop() op1 = self._stack_pop() if not utils.is_symbol(op0) and not utils.is_symbol(op1): self._stack_push(op1 << op0) else: self._stack_push(Int("(" + op1 + " << " + op0 + ")"))
def SDIV(self): op0 = self._stack_pop() op1 = self._stack_pop() if not utils.is_symbol(op0) and not utils.is_symbol(op1): self._stack_push(int(op0 // op1)) # TODO 浮点运算丢失精度 else: self._stack_push(Int("(" + str(op0) + "//" + str(op1) + ")"))
def XOR(self): op0 = self._stack_pop() op1 = self._stack_pop() if utils.is_symbol(op0) or utils.is_symbol(op1): self._stack_push(Int(str(op0) + "^" + str(op1))) else: self._stack_push(op0 ^ op1)
def BYTE(self): op0 = self._stack_pop() op1 = self._stack_pop() if utils.is_symbol(op1) or utils.is_symbol(op0): self._stack_push(Int("byte_{0}".format(self._pc))) else: b = utils.int2bytes(op1, l_word=l_word, type_=int) self._stack_push(b[op0])
def SGT(self): op0 = self._stack_pop() op1 = self._stack_pop() if utils.is_symbol(op0) or utils.is_symbol(op1): # raise EvmExecutionException("SLT does not support symbolic execution") self._stack_push(Int("sgt_{0}".format(self._pc))) else: self._stack_push(Word(op0).sgt(Word(op1)))
def SMOD(self): op0 = self._stack_pop() op1 = self._stack_pop() if not utils.is_symbol(op0) and not utils.is_symbol(op1): self._stack_push(Word(op0).smod(Word(op1))) else: # raise EvmExecutionException("SMOD not support symbolic execution") self._stack_push(Int("smod_{0}".format(self._pc)))
def DIV(self): op0 = self._stack_pop() op1 = self._stack_pop() if not utils.is_symbol(op0) and not utils.is_symbol(op1): decimal.getcontext().prec = 65 self._stack_push(int(decimal.Decimal(op0) / decimal.Decimal(op1))) else: self._stack_push(op0 / op1)
def conflicts_with(self, other): # if self will modify the content of other if utils.is_symbol(self.start) or utils.is_symbol( self.length) or utils.is_symbol( other.start) or utils.is_symbol(other.length): return False if self.start <= other.start < self.start + self.length or other.start <= self.start < other.start + other.length: return True else: return False
def AND(self): op0 = self._stack_pop() op1 = self._stack_pop() if utils.is_symbol(op0) or utils.is_symbol(op1): address_mask = 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff if not utils.is_symbol(op0) and op0 == address_mask: self._stack_push(op1) elif not utils.is_symbol(op1) and op1 == address_mask: self._stack_push(op0) else: self._stack_push(Int(str(op0) + "&" + str(op1))) # self._stack_push(Int(str(op0) + "&" + str(op1))) else: self._stack_push(op0 & op1)
def NOT(self): # TODO bug fix op0 = self._stack_pop() if utils.is_symbol(op0): self._stack_push(Int("~" + str(op0))) else: self._stack_push(~op0)
def SIGNEXTEND(self): op0 = self._stack_pop() op1 = self._stack_pop() t = l_word * 8 - 8 * (op0 + 1) if utils.is_symbol(op1): # raise EvmExecutionException("SIGNEXTEND does not support symbolic execution") self._stack_push(Int("signextend_{0}".format(self._pc))) else: self._stack_push(Word(op1).sign_extend(t))
def op(self, instruction: Instruction, stack: Stack, immutable_storage_references: List[ImmutableStorageTracker]): # cases that the timestamp is used in conditional jump if instruction.opcode == "JUMPI": # 检查是否是时间戳与一个固定值进行比较 if utils.is_symbol(stack[-2]): for z3_ref in utils.extract_z3_ref(stack[-2]): if utils.is_z3_constant(z3_ref): # 是一个z3表达式中的常量 continue elif z3.eq(z3_ref, z3.Int("IHs")): # 是时间戳 continue else: for ref in immutable_storage_references: if ref.contains( len(stack) - 2) and utils.is_symbol( ref.storage_value) and utils.in_list( utils.extract_z3_ref( ref.storage_value), z3_ref): break else: # 不是一个不可变Storage变量 break # 是某一个不可变Storage变量 continue else: # 参与比较的所有变量都是常量(除了时间戳本身) self.pop(instruction.input_amount, len(stack)) return not_used_before = not self.used self.use(instruction, len(stack)) if not_used_before and self.used: self.dependency_addr = instruction.addr else: self.pop(instruction.input_amount, len(stack))
def op(self, instruction: Instruction, stack: Stack, immutable_storage_references): # cases that storage variables are used in the path condition of a CALL operation if instruction.opcode == "JUMPI": # make sure that the condition is the direct condition of CALL, WRONG, but may have false positives... # self.used = False # check if current condition contains Storage variable self.use(instruction, len(stack)) if self.used is True: self.after_used_in_condition = True elif instruction.opcode in [ "CALL", "STATICCALL", "DELEGATECALL", "CALLCODE" ]: self.checked_calls.append(instruction.addr) # check the gas forwarded self.pop(instruction.input_amount, len(stack)) self.new(len(stack) - instruction.input_amount) gas = stack[-1] value = stack[-3] # 检测目的地址是否和是一个不确定的值(mutable) for ref in immutable_storage_references: if ref.contains(len(stack) - 2): # 如果目的地址是一个确定的值,说明接收人是可信的 return if not utils.is_symbol(gas) and (int(gas) == 0 or int(gas) == 2300): self.gas_guarded = True return if '2300' in str(gas): self.gas_guarded = True return if value == 0: self.value0 = True return # There is no reentrancy bug only when some storage values used in path conditions # are changed after the condition and before the call operation if not self.changed_after_condition or not self.changed_before_call: self.buggy = True self.vulnerable_calls.append(instruction.addr) # clear detection history, be prepare for the next reentrancy self.after_call = False self.changed_before_call = False return self.after_call = True else: self.pop(instruction.input_amount, len(stack))
def get_status(self): resp = { "storage": [], "memory": [], "stack": [], } for key, value in self._evm.get_storage().items(): resp["storage"].append((hex(key), str(value))) for value in self._evm.get_memory().values(): resp["memory"].append( (hex(value.start), hex(value.length), str(value.content))) for item in reversed(self._evm.get_stack()): if utils.is_symbol(item): resp["stack"].append(str(item)) else: resp["stack"].append(hex(int(item))[2:].rjust(64, '0')) return resp
def _show_status(self): if len(self._evm.get_storage()) > 0: self._c_printer.print("Storage: ") for key, value in self._evm.get_storage().items(): self._c_printer.print("\t{0} => {1}".format(key, value)) self._c_printer.print() if len(self._evm.get_memory()) > 0: self._c_printer.print("Memory: ") for value in self._evm.get_memory().values(): self._c_printer.print( "\t{0}...{1} => {2}".format(value.start, value.start + value.length, value.content)) self._c_printer.print() self._c_printer.print("Stack: ") for item in reversed(self._evm.get_stack()): if utils.is_symbol(item): self._c_printer.print("\t" + str(item)) else: self._c_printer.print("\t" + hex(int(item))[2:].rjust(64, '0')) self._c_printer.print()
def SHA3(self): op0 = self._stack_pop() op1 = self._stack_pop() # m_slice = self._memory[int(op0):int(op0) + int(op1)] value = self._memory.load(op0, op1) global sha3_map if value in sha3_map.keys(): self._stack_push(sha3_map[value]) else: if utils.is_symbol(value): sha3_value = Int("SHA3_{0}".format(self._pc)) self._stack_push(sha3_value) sha3_map[value] = sha3_value return else: value1 = bytes(utils.int2bytes(value, type_=int)) keccak_hash = keccak.new(digest_bits=8 * l_word) keccak_hash.update(value1) hash_value = keccak_hash.hexdigest() self._stack_push(Word(hash_value)) sha3_map[value] = Word(hash_value) return
def _get_block_index(self, addr: int) -> int: if utils.is_symbol(addr): raise AnalyzerException("Do not support symbolic JUMP destination") for index, block in enumerate(self.blocks): if block.address == addr: return index
def _update_all_ref_tracker(self, instruction: Instruction): # update all the references for ref in self.call_result_references + self.timestamp_references + self.reentrancy_references: ref.update(instruction, self._stack, self.immutable_storage_references) # check if there are new references if instruction.opcode in [ 'CALL', "STATICCALL", "DELEGATECALL", "CALLCODE" ]: h = len(self._stack) - instruction.input_amount if len(self.reentrancy_references) == 0: # 如果之前没有任何Storage被读取,也就是为创建任何ReentrancyTracker,那么当前的这个Call就是有reentrancy bug的 tmp = ReentrancyTracker(instruction.addr, h, -1) tmp.buggy = True self.reentrancy_references.append(tmp) # 判断Call的目的地址是否是可变的(mutable) for ref in self.immutable_storage_references: if ref.contains(len(self._stack) - 2): break else: # 当前的目的地址包含在某一个mutable storage reference中 # 只有当目的地址不是一个确定值,也就是说不可靠的时候 # new call result reference is generated call_ref = CallResultTracker(instruction.addr, h) self.call_result_references.append(call_ref) elif instruction.opcode == "TIMESTAMP": # new timestamp reference is generated here ref = TimestampDepTracker(instruction.addr, len(self._stack)) self.timestamp_references.append(ref) elif instruction.opcode == "SLOAD": storage_addr = self._stack[-1] h = len(self._stack) - instruction.input_amount # 检查是否需要新建MutableStorageTracker if not utils.in_list(self.mutable_storage_addresses, storage_addr): # 是不可变的(immutable) for ref in self.immutable_storage_references: if utils.eq(ref.storage_addr, storage_addr): ref.new(h) ref.new_born = True break else: ref = ImmutableStorageTracker(instruction.addr, h, storage_addr, self._storage[storage_addr]) self.immutable_storage_references.append(ref) # 检查是否需要新建ReentrancyTracker for r in self.reentrancy_references: # check if there already exists the same reference try: if utils.is_symbol(storage_addr) and utils.is_symbol( r.storage_addr) and eq( simplify(r.storage_addr), simplify(storage_addr)) or not utils.is_symbol( storage_addr) and not utils.is_symbol( r.storage_addr ) and r.storage_addr == storage_addr: r.new(h) break except Exception as e: print(e) else: ref = ReentrancyTracker(instruction.addr, h, storage_addr) self.reentrancy_references.append(ref) # 更新mutable storage reference for ref in self.immutable_storage_references: if ref.new_born: ref.new_born = False else: ref.update(instruction, self._stack, None)
def exe_with_path_condition(self, instruction: Instruction, path_condition: list = []) -> PcPointer: self._update_all_ref_tracker(instruction) if instruction.opcode == "SSTORE": # save the value of every referred storage variable before SSTORE bak = {} for ref in self.reentrancy_references: bak[ref] = self._storage[ref.storage_addr] if instruction.opcode == "SSTORE" and self.pre_process: op0 = self._stack[-1] # 在可信条件下进行修改的Storage变量仍然是可信的(immutable) caller = z3.Int("Is") solver = z3.Solver() solver.add(path_condition) if "sat" == str(solver.check()): for storage_addr, storage_value in self._storage.get_storage( ).items(): if not utils.in_list(self.mutable_storage_addresses, storage_addr): # solver.add(caller & 0xffffffffffffffffffff != storage_value & 0xffffffffffffffffffff) mask = 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff if utils.is_symbol(storage_value): # solver.add(z3.Int(str("Is") + "&" + str(mask)) != z3.Int(str(storage_value) + "&" + str(mask))) # solver.add(z3.Int(str(mask) + "&" + str("Is")) != z3.Int(str(storage_value) + "&" + str(mask))) # solver.add(z3.Int(str("Is") + "&" + str(mask)) != z3.Int(str(mask) + "&" + str(storage_value))) # solver.add(z3.Int(str(mask) + "&" + str("Is")) != z3.Int(str(mask) + "&" + str(storage_value))) # solver.add(z3.Int(str("Is")) != z3.Int(str(storage_value))) solver.add( z3.Int(str("Is")) != z3.Int(str(storage_value)) ) else: # solver.add(z3.Int(str("Is") + "&" + str(mask)) != storage_value & mask) # solver.add(z3.Int(str(mask) + "&" + str("Is")) != storage_value & mask) solver.add( z3.Int(str("Is")) != storage_value & mask) if "sat" == str(solver.check()): # caller不为任意一个可信storage变量的时候仍然可能进行SSTORE,则说明被修改的storage变量是不可靠的 if not utils.in_list(self.mutable_storage_addresses, op0): self.mutable_storage_addresses.append(op0) pc_pointer = super().exe(instruction) if instruction.opcode == "SSTORE": # check if any referred storage variable is changed after SSTORE # if len(bak) != len(self._storage): # # 如果新增了Storage变量,那么一定是做修改了 # ref.storage_changed = True # if ref.after_used_in_condition: # ref.changed_after_condition = True # if not ref.after_call: # ref.changed_before_call = True # else: # 如果Storage变量的个数没变,那么就检查每一个变量的值有没有改变 for ref, value in bak.items(): if utils.is_symbol(value) is not utils.is_symbol(self._storage[ref.storage_addr]) or \ utils.is_symbol(value) and not eq(simplify(value), simplify(self._storage[ref.storage_addr])) or \ not utils.is_symbol(value) and value != self._storage[ref.storage_addr]: ref.storage_changed = True if ref.after_used_in_condition: ref.changed_after_condition = True if not ref.after_call: ref.changed_before_call = True return pc_pointer