예제 #1
0
def prettify(exp, rem_bool=False, parentheses=True, top_level=False, add_color=False):

    col = partial(colorize, add_color=add_color)
    pret = partial(prettify, add_color=add_color, parentheses=False)

    if rem_bool:
        exp = simplify_bool(exp)
        if opcode(exp) == 'bool':
            return prettify(exp, rem_bool=rem_bool, parentheses=parentheses, top_level=top_level, add_color=add_color)

    if type(exp) == int and exp % (24 * 3600) == 0 and exp > 24 * 3600:
        exp = ('mul', exp//3600, 24, 3600)

    if type(exp) == int and exp % 3600 == 0 and exp > 3600:
        exp = ('mul', exp//3600, 3600)
        # also tried return col('seconds(', COLOR_GRAY) + '1 hour' + col(')', COLOR_GRAY)
        # but seemed less intuitive, e.g. 0xf64B584972FE6055a770477670208d737Fff282f calcMaxWithdraw
        # and 3600 every programmer should know, by heart, means 1 hour :)
        #
        # also, not tackling single minutes because too often they are not time related

    if type(exp) in (int, float):
        return pretty_num(exp, add_color)

    if opcode(exp) in precompiled.values():
        return f'{exp[0]}({pret(exp[1])})'

    if exp ~ ('arr', int:num, ('mask_shl', _, _, _, str:s)) \
            and len(s) == num+2:
            return s
예제 #2
0
    def handle_jumps(self, trace, line, condition):

        i, op = line[0], line[1]
        stack = self.stack

        if '--explain' in sys.argv and op in ('jump', 'jumpi', 'selfdestruct', 'stop', 'return', 'invalid', 'assert_fail', 'revert'):
            trace.append(C.asm(f'       {stack}'))
            trace.append('')
            trace.append(f'[{line[0]}] {C.asm(op)}')


        if op == 'jump':
            target = stack.pop()

            n = Node(self, start=target, safe=False, stack=tuple(self.stack.stack), condition=condition)

            trace.append(('jump', n))
            return trace

        if op == 'jumpi':
            target = stack.pop()
            if_condition = simplify_bool(stack.pop())

            tuple_stack = tuple(self.stack.stack)
            n_true = Node(self, start=target, safe=False, stack=tuple_stack, condition=if_condition)
            n_false = Node(self, start=self.loader.next_line(i), safe=True, stack=tuple_stack, condition=is_zero(if_condition))

            if self.just_fdests:
                if if_condition ~ ('eq', int:fx_hash, :is_cd) and str(('cd', 0)) in str(is_cd):
                    n_true.trace=[('funccall', fx_hash, target)]
                if if_condition ~ ('eq', :is_cd, int:fx_hash) and str(('cd', 0)) in str(is_cd):
                    n_true.trace=[('funccall', fx_hash, target)]
예제 #3
0
파일: folder.py 프로젝트: ytrezq/panoramix
def cleanup_ors(path):
    assert type(path) == list
    ret = []

    idx = 0
    while idx < len(path):
        if type(path[idx]) == list:

            path = path[:idx] + path[idx] + path[idx + 1 :]

        line = path[idx]

        if opcode(line) != "or":
            ret.append(line)

        elif len(line) == 2:  # one-sided or
            # clean up the inside, skip the next line in the main path
            condition = line[1][0]
            line = ("or", cleanup_ors(line[1]))
            ret.append(line)
            idx += 1

        elif len(line) == 3:  # two-sided or
            if len(line[1]) == 1:
                assert comp_bool(simplify_bool(line[1][0]), is_zero(line[2][0]))
                line = ("or", cleanup_ors(line[2]))
                ret.append(line)
            else:
                assert comp_bool(is_zero(line[1][0]), simplify_bool(line[2][0]))
                line = ("or", cleanup_ors(line[1]), cleanup_ors(line[2][1:]))
                ret.append(line)

        else:  # three-sided ors? madness!
            assert False

        idx += 1

    return ret
예제 #4
0
    def handle_jumps(self, trace, line, condition):

        i, op = line[0], line[1]
        stack = self.stack

        if "--explain" in sys.argv and op in (
                "jump",
                "jumpi",
                "selfdestruct",
                "stop",
                "return",
                "invalid",
                "assert_fail",
                "revert",
        ):
            trace.append(C.asm(f"       {stack}"))
            trace.append("")
            trace.append(f"[{line[0]}] {C.asm(op)}")

        if op in (
                "jump",
                "jumpi",
                "selfdestruct",
                "stop",
                "return",
                "invalid",
                "assert_fail",
                "revert",
        ):
            logger.debug("[%s] %s", i, op)

        if op == "jump":
            target = stack.pop()

            n = Node(
                self,
                start=target,
                safe=False,
                stack=tuple(self.stack.stack),
                condition=condition,
            )

            trace.append(("jump", n))
            return trace

        elif op == "jumpi":
            target = stack.pop()
            if_condition = simplify_bool(stack.pop())

            tuple_stack = tuple(self.stack.stack)
            n_true = Node(
                self,
                start=target,
                safe=False,
                stack=tuple_stack,
                condition=if_condition,
            )
            n_false = Node(
                self,
                start=self.loader.next_line(i),
                safe=True,
                stack=tuple_stack,
                condition=is_zero(if_condition),
            )

            if self.just_fdests:
                if ((m := match(if_condition, ("eq", ":fx_hash", ":is_cd")))
                        and str(("cd", 0)) in str(m.is_cd)
                        and isinstance(m.fx_hash, int)):
                    n_true.trace = [("funccall", m.fx_hash, target,
                                     tuple_stack)]
                if ((m := match(if_condition, ("eq", ":is_cd", ":fx_hash")))
                        and str(("cd", 0)) in str(m.is_cd)
                        and isinstance(m.fx_hash, int)):
                    n_true.trace = [("funccall", m.fx_hash, target,
                                     tuple_stack)]
예제 #5
0
    def analyse(self):
        assert len(self.trace) > 0

        def find_returns(exp):
            if opcode(exp) == 'return':
                return [exp]
            else:
                return []

        exp_text = []

        self.returns = find_f_list(self.trace, find_returns)

        exp_text.append(('possible return values', prettify(self.returns)))

        first = self.trace[0]

        if opcode(first) == 'if' and simplify_bool(first[1]) == 'callvalue'   \
                and (first[2][0] == ('revert', 0) or opcode(first[2][0]) == 'invalid'):
            self.trace = self.trace[0][3]
            self.payable = False
        elif opcode(first) == 'if' and simplify_bool(first[1]) == ('iszero', 'callvalue')   \
                and (first[3][0] == ('revert', 0) or opcode(first[3][0]) == 'invalid'):
            self.trace = self.trace[0][2]
            self.payable = False
        else:
            self.payable = True

        exp_text.append(('payable', self.payable))

        self.read_only = True
        for op in ['store', 'selfdestruct', 'call', 'delegatecall', 'codecall', 'create']:
            if f"'{op}'" in str(self.trace):
                self.read_only = False

        exp_text.append(('read_only', self.read_only))


        '''
            const func detection
        '''

        self.const = self.read_only
        for exp in ['storage', 'calldata', 'calldataload', 'store', 'cd']:
            if exp in str(self.trace) or len(self.returns)!=1:
                self.const = False

        if self.const:
            self.const = self.returns[0]
            if len(self.const) == 3 and opcode(self.const[2]) == 'data':
                self.const = self.const[2]
            if len(self.const) == 3 and opcode(self.const[2]) == 'mask_shl':
                self.const = self.const[2]
            if len(self.const) == 3 and type(self.const[2]) == int:
                self.const = self.const[2]
        else:
            self.const = None

        if self.const:
            exp_text.append(('const', self.const))

        '''
            getter detection
        '''

        self.getter = None
        self.simplify_string_getter_from_storage()
        if self.const is None and \
           self.read_only and \
           len(self.returns) == 1:
                ret = self.returns[0][1]
                if ret ~ ('bool', ('storage', _, _, :loc)):
예제 #6
0
    def analyse(self):
        assert len(self.trace) > 0

        def find_returns(exp):
            if opcode(exp) == "return":
                return [exp]
            else:
                return []

        exp_text = []

        self.returns = find_f_list(self.trace, find_returns)

        exp_text.append(("possible return values", prettify(self.returns)))

        first = self.trace[0]

        if (opcode(first) == "if" and simplify_bool(first[1]) == "callvalue"
                and (first[2][0] == ("revert", 0)
                     or opcode(first[2][0]) == "invalid")):
            self.trace = self.trace[0][3]
            self.payable = False
        elif (opcode(first) == "if"
              and simplify_bool(first[1]) == ("iszero", "callvalue")
              and (first[3][0] == ("revert", 0)
                   or opcode(first[3][0]) == "invalid")):
            self.trace = self.trace[0][2]
            self.payable = False
        else:
            self.payable = True

        exp_text.append(("payable", self.payable))

        self.read_only = True
        for op in [
                "store",
                "selfdestruct",
                "call",
                "delegatecall",
                "codecall",
                "create",
        ]:
            if f"'{op}'" in str(self.trace):
                self.read_only = False

        exp_text.append(("read_only", self.read_only))
        """
            const func detection
        """

        self.const = self.read_only
        for exp in ["storage", "calldata", "calldataload", "store", "cd"]:
            if exp in str(self.trace) or len(self.returns) != 1:
                self.const = False

        if self.const:
            self.const = self.returns[0]
            if len(self.const) == 3 and opcode(self.const[2]) == "data":
                self.const = self.const[2]
            if len(self.const) == 3 and opcode(self.const[2]) == "mask_shl":
                self.const = self.const[2]
            if len(self.const) == 3 and type(self.const[2]) == int:
                self.const = self.const[2]
        else:
            self.const = None

        if self.const:
            exp_text.append(("const", self.const))
        """
            getter detection
        """

        self.getter = None
        self.simplify_string_getter_from_storage()
        if self.const is None and self.read_only and len(self.returns) == 1:
            ret = self.returns[0][1]
            if match(ret, ("bool", ("storage", Any, Any, ":loc"))):
                self.getter = (
                    ret  # we have to be careful when using this for naming purposes,
                )
                # because sometimes the storage can refer to array length

            elif opcode(ret) == "mask_shl" and opcode(ret[4]) == "storage":
                self.getter = ret[4]
            elif opcode(ret) == "storage":
                self.getter = ret
            elif opcode(ret) == "data":
                terms = ret[1:]
                # for structs, we check if all the parts of the struct are storage from the same
                # location. if so, we return the location number

                t0 = terms[
                    0]  # 0xFAFfea71A6da719D6CAfCF7F52eA04Eb643F6De2 - documents
                if m := match(t0, ("storage", 256, 0, ":loc")):
                    loc = m.loc
                    for e in terms[1:]:
                        if not match(e,
                                     ("storage", 256, 0, ("add", Any, loc))):
                            break
                    else:
                        self.getter = t0

                # kitties getKitten - with more cases this and the above could be uniformed
                if self.getter is None:
                    prev_loc = -1
                    for e in terms:

                        def l2(x):
                            if m := match(x, ("sha3", ("data", Any, ":l"))):
                                if type(m.l) == int and m.l < 1000:
                                    return m.l
                            if (opcode(x) == "sha3" and type(x[1]) == int
                                    and x[1] < 1000):
                                return x[1]
                            return None

                        loc = find_f(e, l2)
                        if not loc or (prev_loc != -1 and prev_loc != loc):
                            break
                        prev_loc = loc

                    else:
                        self.getter = ("struct", ("loc", loc))