def get_type(stordefs): sizes = set() offsets = set() for s in stordefs: if (m := match( s, ("stor", ":size", ":off", (":op", ":idx", ...)))) and m.op in ("map", "array"): sizes.add(m.size) if safe_le_op(0, m.off) is True: offsets.add(m.off)
def slice_exp(exp, left, right): size = sub_op(right, left) logger.debug(f"slicing {exp}, offset {left} bytes, until {right} bytes") # e.g. mem[32 len 10], 2, 4 == mem[34,2] if m := match(exp, ("mem", ("range", ":rleft", ":rlen"))): rleft, rlen = m.rleft, m.rlen if safe_le_op(add_op(left, size), rlen): return ("mem", ("range", add_op(rleft, left), size)) else: return None
def memloc_overwrite(memloc, split): # returns mem ranges excluding the ones that are *for sure* overwritten by 'split' # e.g. overwrites(('range', 64, 32), ('range', 70, 10)) -> [('range', 64, 6), (range, 80, 16)] # e.g. overwrites(('range', 64, 32), ('range', 70, 'unknown')) -> [('range', 64, 32)], bc. 'unknown' can be 0 op, m_left, m_len = memloc assert op == "range" op, s_left, s_len = split assert op == "range" m_right = add_op(m_left, m_len) s_right = add_op(s_left, s_len) if safe_le_op(m_right, s_left) is True: # split after memory - no overlap return [memloc] if safe_le_op(s_right, m_left) is True: # split before memory - no overlap return [memloc] left_len = sub_op(s_left, m_left) right_len = sub_op(m_right, s_right) range_left = ("range", m_left, left_len) range_right = ("range", s_right, right_len) left_ge_zero, right_ge_zero = safe_ge_zero(left_len), safe_ge_zero( right_len) if left_ge_zero is None or right_ge_zero is None: # we can't compare some numbers, conservatively return whole range return [memloc] res = [] if safe_ge_zero(left_len) is True and left_len != 0: res.append(range_left) if safe_ge_zero(right_len) is True and right_len != 0: res.append(range_right) return res
def apply_mask_to_range(memloc, size, offset): op, range_pos, range_len = memloc assert op == "range" size_bytes, size_bits = to_bytes(size) offset_bytes, offset_bits = to_bytes(offset) assert offset_bits == size_bits == 0, (offset_bits, size_bits) # for now assert safe_le_op(add_op(size_bytes, offset_bytes), range_len) is True, ( size_bytes, offset_bytes, range_len, ) # otherwise we need to learn to handle that range_pos = add_op(range_pos, sub_op(range_len, add_op(size_bytes, offset_bytes))) range_len = size_bytes # sub_op(range_len, add_op(offset_bytes, size_bytes)) return ("range", range_pos, range_len)
def _fill_mem(exp, split, split_val): if exp == ("mem", split): return split_val op, memloc = exp assert op == "mem" op, m_left, m_len = memloc assert op == "range" op, s_left, s_len = split assert op == "range" m_right = add_op(m_left, m_len) s_right = add_op(s_left, s_len) logger.debug(f"orig memloc: {m_left} len {m_len} right {m_right}") logger.debug(f"split memloc: {s_left} len {s_len} right {s_right}") if ( safe_le_op(m_right, s_left) is not False ): # if the split is before memory, or we can't compare - not replacing logger.debug("split before memory or can't compare - not replacing") return exp if safe_le_op(s_right, m_left) is not False: # -,,- after memory logger.debug("split after memory or can't compare - not replacing") return exp left = safe_max_op(s_left, m_left) right = safe_min_op(s_right, m_right) logger.debug(f"split begins at {left} ends at {right}") if left is None or right is None: return exp # if we can't figure out which one is smaller/larger, we're not replacing memloc, memloc_max = replace_max_with_MAX(memloc) split, split_max = replace_max_with_MAX(split) # 'max' op tends to mess up with all the algebra stuff, so we're replacing # it with a variable 'MAX' for the time being if split_max != memloc_max: logger.warning("different maxes") return exp # by now we know: # - the split overlaps memory for sure # - we know the boundaries of split # - so we now return data (before_split, split_val, after_split) res_left = slice_exp(exp, 0, sub_op(left, m_left)) if res_left is None: return exp logger.debug(f"value left untouched on left: {res_left}") res_right = slice_exp(exp, sub_op(right, m_left), sub_op(m_right, m_left)) if res_right is None: return exp logger.debug(f"value right untouched on right: {res_right}") res = [] if safe_gt_zero(sizeof(res_left)) is True: logger.debug("size of left untouched > 0, adding to output") res.append(res_left) elif safe_gt_zero(sizeof(res_left)) is None: logger.debug("we don't know if left size > 0, aborting") return exp center_in_start = sub_op(left, s_left) center_in_len = sub_op(right, s_left) logger.debug( f"inserted value offset {center_in_start}, length {center_in_len}") logger.debug(f"cutting this out of {split_val}") res_center = slice_exp(split_val, center_in_start, center_in_len) logger.debug(f"inserted value after slicing: {res_center}") if res_center is None: return exp if safe_ge_zero(sizeof(res_center)) is True: res.append(res_center) else: assert False, sizeof( res_center) # this shouldn't happen considering the above checks? if safe_ge_zero(sizeof(res_right)) is True: if sizeof(res_right) != 0: res.append(res_right) elif safe_ge_zero(sizeof(res_right)) is None: return exp assert None not in res return ("data", ) + tuple(res)
def splits_mem(memloc, split, memval, split_val=None): # returns memory values we can be confident of, after overwriting the split part of memory op, m_left, m_len = memloc assert op == "range" op, s_left, s_len = split assert op == "range" m_right = add_op(m_left, m_len) s_right = add_op(s_left, s_len) logger.debug(f"applying split [{s_left} (len {s_len}) {s_right}]") logger.debug(f" to [{m_left} (len {m_len}) {m_right}]") if not safe_ge_zero(s_len): s_len = "undefined" s_right = add_op(s_left, s_len) if safe_le_op(m_right, s_left) is True: # split after memory - no overlap return [(memloc, memval)] if safe_le_op(s_right, m_left) is True: # split before memory - no overlap return [(memloc, memval)] left = safe_max_op(s_left, m_left) right = safe_min_op(s_right, m_right) logger.debug(f"split overwrites memory from {left} to {right}") # left/right relative to beginning of memory location in_left = sub_op(left, m_left) in_right = sub_op(right, m_left) logger.debug(f"that is, relative to memloc {in_left} to {in_right}") if safe_le_op(in_left, m_len) is not True or left is None: logger.debug( f"we are not sure that m_len: {m_len} is bigger than beginning of split, returning []" ) return [] assert in_left == 0 if safe_le_op(right, m_left) else True val_left = slice_exp(memval, 0, in_left) if left is not None else None val_right = (slice_exp(memval, in_right, sub_op(m_right, m_left)) if right is not None else None) res = [] left_len = sub_op(left, m_left) # sizeof(val_left) right_len = sub_op(m_right, right) if safe_ge_zero( left_len) is True and left_len != 0 and val_left is not None: res.append((("range", m_left, left_len), val_left)) if split_val is not None: center_left = safe_max_op(m_left, s_left) center_right = safe_min_op(m_right, s_right) center_len = sub_op(center_right, center_left) if is_array(opcode(split_val)): # in ARRAY_OPCODES: # mem[a len b] = calldata[x len b] # log mem[c len d] # -> calldata[x+ c - a, center_len] arr_offset, arr_len = split_val[1:] center_offset = add_op(arr_offset, sub_op(center_left, s_left)) center_val = (opcode(split_val), center_offset, center_len) else: center_offset = sub_op(s_right, center_right) center_val = mask_op( split_val, size=mul_op(center_len, 8), offset=mul_op(center_offset, 8), shr=mul_op(center_offset, 8), ) center_range = ("range", center_left, center_len) if safe_ge_zero(center_len) and center_len != 0: res.append((center_range, center_val)) if safe_ge_zero( right_len) is True and right_len != 0 and val_right is not None: res.append((("range", right, right_len), val_right)) return res
lines.append(("store", off, 0, idx, 0)) # lines.append(('store', size, off, idx, ('storage', size, off, idx))) if size + off < 256: lines.append(("store", (256 - size - off), size + off, idx, 0)) return lines if m := match(line, ("store", 256, 0, ":idx", ":val")): size = 256 off = 0 idx, val = m.idx, m.val splitted = split_or(val) res = [] for s_size, s_off, s_val in splitted: if safe_le_op(off, s_off) and safe_le_op(add_op(s_size, s_off), add_op(off, size)): if s_val != ( "storage", s_size, s_off, idx, ): # ignore writing the same to the same storage res.append(("store", s_size, s_off, idx, s_val)) else: logger.warning("unusual store") return [line] return res else: return [line]
m.name): # in ('call.data', 'ext_call.return_data'): if m.size == 32: return m.name + f"[{pret(m.offset)}]" else: return m.name + f"[{pret(m.offset)} len {pret(m.size)}]" if ((m := match( exp, ( "mask_shl", ":size", ":offset", ":shl", ("stor", ":s_size", ":s_off", ":s_idx"), ), )) and safe_le_op(m.s_size, m.size) and m.shl == 0): return pret(("stor", m.s_size, m.s_off, m.s_idx)) if opcode(exp) == "stor": return pretty_stor(exp, add_color=add_color) if opcode(exp) == "type": return pretty_stor(exp, add_color=add_color) if opcode(exp) == "field": return pretty_stor(exp, add_color=add_color) if m := match(exp, ("cd", ":num")): if m.num == 0: return col("call.func_hash", C.green) parsed_exp = get_param_name(exp, add_color=add_color)