예제 #1
0
def get_caller_cond(condition):
    #  checks if the condition has this format:
    #  (EQ (MASK_SHL, 160, 0, 0, 'CALLER'), (STORAGE, size, offset, stor_num))

    #  if it does, returns the storage data
    #
    #  also, if condition is IS_ZERO(EQ ...), it turns it into just (EQ ...)
    #  -- technically not correct, but this is a hackathon project, should be good enough :)

    if opcode(condition) != 'EQ':
        return None

    if condition[1] == ('MASK_SHL', 160, 0, 0, 'CALLER'):
        stor = condition[2]
    elif condition[2] == ('MASK_SHL', 160, 0, 0, 'CALLER'):
        stor = condition[1]
    else:
        return None

    if opcode(stor) == 'STORAGE' and len(stor) == 4:
        # len(stor) == 5 -> indexed storage array, not handling those now
        return stor
    elif type(stor) == int:
        return hex(stor)
    else:
        return 'unknown'
예제 #2
0
def walk_trace(trace, f=print, knows_true=None):
    '''
        
        walks the trace, calling function f(line, knows_true) for every line
        knows_true is a list of 'if' conditions that had to be met to reach a given
        line

    '''
    res = []
    knows_true = knows_true or []

    for line in trace:
        found = f(line, knows_true)

        if found is not None:
            res.append(found)

        if opcode(line) == 'IF':
            condition, if_true, if_false = line[1:]
            res.extend(walk_trace(if_true, f, knows_true + [condition]))
            res.extend(
                walk_trace(if_false, f, knows_true + [is_zero(condition)]))
            continue

        if opcode(line) == 'WHILE':
            condition, trace = line[1:]
            res.extend(walk_trace(trace, f, knows_true + [is_zero(condition)]))
            continue

        if opcode(line) == 'LOOP':
            trace, label = line[1:]
            res.extend(walk_trace(trace, f, knows_true))

    return res
예제 #3
0
def find_calls(line, _):
    # todo: delegatecalls
    # todo: selfdestructs
    if opcode(line) != 'CALL':
        return None

    _, addr, wei, _, _, _, _, f_name, f_params = line[1:]

    if addr == ('MASK_SHL', 160, 0, 0, 'CALLER'):
        # WARN: should check for knows_true, perhaps a caller can only be someone specific
        addr = 'anyone'
    elif opcode(addr) != 'STORAGE' or len(addr) > 4:
        addr = 'unknown'

    return (addr, wei, f_name, f_params)
예제 #4
0
def find_stor_req(line, knows_true):
    # for every line, check if it's (STORE, size, offset, stor_num, _, some value)
    # if it is, it means that the line writes to storage...

    if opcode(line) != 'STORE':
        return None

    _, size, offset, stor_num, arr_idx, value = line

    if len(arr_idx) > 0:
        # we're dealing only with storages that are not arrays for now
        return None

    # ok, so it's a storage write - let's backtrack through all the IFs we encountered
    # before, and see if there were checks for callers there

    callers = []
    for cond in knows_true:
        caller = get_caller_cond(cond)
        if caller is not None:
            callers.append(caller)

    if len(callers) == 0:
        callers = ['anyone']

    return ('STORAGE', size, offset, stor_num), callers
예제 #5
0
def add_role(name=None, value=None, definition=None):
    global roles

    if name is None:
        assert definition is not None
        if opcode(definition) == 'STORAGE':
            name = f'stor_{definition[3]}'
        else:
            name = str(definition)

    s = definition or name

    if s in roles:
        return

    roles[s] = {
        'name': name,
        'definition': definition,
        'setters': list(),
        'funcs': set(),
        'withdrawals': set(),
        'calls': set(),
        'value': value,
        'destructs': set(),
        'destructs_init': set(),
    }
예제 #6
0
def find_caller_req(line, _):
    # finds IFs: (IF (EQ caller, storage))

    if opcode(line) != 'IF':
        return None

    condition, if_true, if_false = line[1:]

    return get_caller_cond(condition) or get_caller_cond(is_zero(condition))
예제 #7
0
def walk_trace(trace, f=print, knows_true=None):
    '''
        
        walks the trace, calling function f(line, knows_true) for every line
        knows_true is a list of 'if' conditions that had to be met to reach a given
        line

    '''
    res = []
    knows_true = knows_true or []

    for idx, line in enumerate(trace):
        found = f(line, knows_true)

        if found is not None:
            res.append(found)

        if opcode(line) == 'IF':
            condition, if_true, if_false = line[1:]
            res.extend(walk_trace(if_true, f, knows_true + [condition]))
            res.extend(
                walk_trace(if_false, f, knows_true + [is_zero(condition)]))

            assert idx == len(
                trace) - 1, trace  # IFs always end the trace tree
            break

        if opcode(line) == 'WHILE':
            condition, while_trace = line[1:]
            res.extend(
                walk_trace(while_trace, f, knows_true + [is_zero(condition)]))
            continue

        if opcode(line) == 'LOOP':
            loop_trace, label = line[1:]
            res.extend(walk_trace(loop_trace, f, knows_true))

    return res
예제 #8
0
def find_destructs(line, knows_true):
    # todo: delegatecalls
    # todo: selfdestructs
    if opcode(line) != 'SELFDESTRUCT':
        return None

    receiver = line[1]

    if receiver == ('MASK_SHL', 160, 0, 0, 'CALLER'):
        receiver = 'anyone'
    elif opcode(receiver) != 'STORAGE' or len(receiver) > 4:
        receiver = 'unknown'

    callers = []
    for cond in knows_true:
        caller = get_caller_cond(cond)
        if caller is not None:
            callers.append(caller)

    if len(callers) == 0:
        callers = ['anyone']

    return receiver, callers
예제 #9
0
def get_caller_cond(condition):

    if opcode(condition) != 'EQ':
        if opcode(condition) != 'ISZERO':
            return None
        else:
            condition = condition[1]

    if opcode(condition) != 'EQ':
        return None

    if condition[1] == ('MASK_SHL', 160, 0, 0, 'CALLER'):
        stor = condition[2]
    elif condition[2] == ('MASK_SHL', 160, 0, 0, 'CALLER'):
        stor = condition[1]
    else:
        return None

    if opcode(stor) == 'STORAGE' and len(stor) == 4:
        # len(stor) == 5 -> indexed storage array, not handling those now
        return stor
    else:
        return None
예제 #10
0
def find_stor_req(line, knows_true):
    if opcode(line) != 'STORE':
        return None

    size, offset, stor_num, arr_idx, value = line[1:]

    if len(arr_idx) > 0:
        # we're dealing only with storages that are not arrays
        return None

    callers = []
    for cond in knows_true:
        caller = get_caller_cond(cond)
        if caller is not None:
            callers.append(caller)

    if len(callers) == 0:
        callers = ['anyone']

    return ('STORAGE', size, offset, stor_num), callers
예제 #11
0
def find_opcodes(line, _):
    return opcode(line)
예제 #12
0
            callers.append(caller)

    if len(callers) == 0:
        callers = ['anyone']

    return ('STORAGE', size, offset, stor_num), callers


for f in functions.values():

    for (stor, callers) in walk_trace(f['trace'], find_stor_req):

        affected_roles = set()
        for r in roles:

            if opcode(r) != 'STORAGE':
                continue

            assert opcode(stor) == 'STORAGE'

            _, stor_size, stor_offset, stor_num = stor[:4]
            _, role_size, role_offset, role_num = r[:4]

            if stor_num == role_num and \
               role_offset <= stor_offset < role_offset + role_size:

                affected_roles.add(r)

                # ^ for a role (STORAGE, 160, 0, 1), will catch those:
                #
                #   (STORE, 160, 0, 1, value) (exact match)
예제 #13
0
        def find_storages(exp):
            if opcode(exp) == 'STORAGE':
                return exp

            if opcode(exp) == 'STORE':
                return ('STORAGE', ) + exp[1:4]
예제 #14
0
        callers = ['anyone']

    return ('STORAGE', size, offset, stor_num), callers


for f in functions.values():
    trace = f['trace']

    res = walk_trace(trace, find_stor_req)
    if len(res) > 0:
        for (stor, callers) in res:

            affected_roles = set()
            for r in roles:

                if opcode(r) != 'STORAGE':
                    continue

                stor_offset, stor_size, stor_num = stor[2], stor[1], stor[3]
                role_offset, role_size, role_num = r[2], r[1], r[3]

                if stor_offset >= role_offset and stor_offset < role_offset + role_size and stor_num == role_num:
                    affected_roles.add(r)

            # ^ we can't compare roles to storage writes, because that would miss all the partial writes
            # to a given storage. see 'digix' contract, and how setOwner is set there

            setter = (callers, f['name'])

            for role_id in affected_roles:
                if setter not in roles[role_id]['setters']: