示例#1
0
def find_strings():
    """
    Extracts and publishes EncodedString objects for the parameters following xor encryption function:

        void encrypt(char *s, char key)
        {
	        while (*s)
		        *s++ ^= key;
        }
    """
    for encrypt_func in decoderutils.re_find_functions(
            re.compile(r'\x8b\x45\x08\x0f\xbe\x08')):
        logger.info('Found XOR encrypt function at: 0x{:0x}'.format(
            encrypt_func.start_ea))
        for call_ea in encrypt_func.xrefs_to:
            logger.debug('Tracing {:0x}'.format(call_ea))
            # Extract arguments for call to xor function.
            tracer = function_tracing.get_tracer(call_ea)
            context, args = tracer.get_function_args(call_ea)
            enc_str_ptr, key = args
            encoded_string = decoderutils.EncodedString(
                enc_str_ptr, string_reference=call_ea, key=key)
            encoded_string.decoded_data = xor_decrypt(
                key, encoded_string.encoded_data)
            encoded_string.publish(rename=True, patch=False)
示例#2
0
def test_function_case_senstivity():
    """Tests issue with case sensitivity when hooking functions."""
    from kordesii.utils import function_tracing
    from kordesii.utils.function_tracing import builtin_funcs

    # Test with known builtin func
    assert builtin_funcs.get('lstrcpya') == builtin_funcs.strcpy
    assert builtin_funcs.get('lStrcpyA') == builtin_funcs.strcpy
    assert builtin_funcs.get('lstrcpyA') == builtin_funcs.strcpy

    def dummy(ctx, func_name, func_args):
        return

    # Test user defined with global tracer cache
    function_tracing.hook_tracers('SuperFunc', dummy)
    tracer = function_tracing.get_tracer(0x00401058)
    assert builtin_funcs.get('SuperFunc') is None
    assert builtin_funcs.get('SUPERfunc') is None
    assert builtin_funcs.get('superfunc') is None
    with builtin_funcs.hooks(tracer._hooks):
        assert builtin_funcs.get('SuperFunc') == dummy
        assert builtin_funcs.get('SUPERfunc') == dummy
        assert builtin_funcs.get('superfunc') == dummy

    # Test user defined with local tracer
    tracer = function_tracing.FunctionTracer(0x00401058)
    tracer.hook('SuperFunc', dummy)
    assert builtin_funcs.get('SuperFunc') is None
    assert builtin_funcs.get('SUPERfunc') is None
    assert builtin_funcs.get('superfunc') is None
    with builtin_funcs.hooks(tracer._hooks):
        assert builtin_funcs.get('SuperFunc') == dummy
        assert builtin_funcs.get('SUPERfunc') == dummy
        assert builtin_funcs.get('superfunc') == dummy
示例#3
0
def trace_arguments(ea):
    """
    This is a function that would be almost impossible to do proxied due to
    the complexities of function_tracing.
    """
    from kordesii.utils import function_tracing

    tracer = function_tracing.get_tracer(ea)

    strings = []
    for context in tracer.iter_context_at(ea, depth=1):
        assert context.ip == ea
        # mov     eax, [ebp+arg_0]
        strings.append(context.read_data(context.operands[1].value))

    return strings
def test_cpu_context():
    """Tests function_tracer and and cpu_context."""
    import idc

    from kordesii.utils import function_tracing

    # Test on encryption function.
    tracer = function_tracing.get_tracer(0x00401024)
    context = tracer.context_at(0x00401024)

    operands = context.operands
    assert len(operands) == 2
    assert operands[0].text == '[ebp+arg_0]'
    assert operands[0].value == 0
    # arg_0 should be 8 bytes from stack pointer.
    assert operands[0].addr == context.registers.esp + 8 == 0x117f804
    assert operands[1].text == 'eax'
    assert operands[1].value == context.registers.eax == 1

    # Test get_original_location()
    # context = tracer.context_at(0x00401017)
    # data_ptr = context.registers.edx
    data_ptr = operands[0].addr
    assert context.get_variable_name(data_ptr) == '$ F401000.arg_0'
    ip, orig_location = context.get_original_location(data_ptr)
    assert ip is None  # ip is None, because arg_0 never gets copied explicictly.
    assert isinstance(orig_location, tuple)
    frame_id, stack_offset = orig_location
    assert idc.get_member_name(frame_id, stack_offset) == 'arg_0'

    # Now execute this instruction and see if arg_0 has be set with the 1 from eax.
    context.execute(context.ip)
    assert operands[0].value == 1

    # Test getting all possible values passed into arg_0 using using depth.
    strings = []
    for context in tracer.iter_context_at(0x00401003, depth=1):
        assert context.ip == 0x00401003
        # mov     eax, [ebp+arg_0]
        strings.append(context.read_data(context.operands[1].value))
    assert strings == [
        'Idmmn!Vnsme ', 'Vgqv"qvpkle"ukvj"ig{"2z20',
        'Wkf#rvj`h#aqltm#el{#ivnsp#lufq#wkf#obyz#gld-',
        'Keo$mw$wpvkjc$ej`$ehwk$cmraw$wle`a*',
        'Dfla%gpwkv%mji`v%lk%rjji%fijqm+',
        'Egru&ghb&biau&cgen&ngrc&rnc&irnct(',
        '\\cv}3g{v3pargv3qfg3w|}4g3qavrx3g{v3t\x7fr``=',
        'C\x7frer7c\x7fr7q{xxs7zve|7~d7cry7~yt\x7frd9', '+()./,-"#*',
        '`QFBWFsQL@FPPb', 'tSUdFS', '\x01\x13\x10n\x0e\x05\x14',
        '-",5 , v,tr4v,trv4t,v\x7f,ttt', '@AKJDGBA@KJGDBJKAGDC',
        '!\x1d\x10U\x05\x14\x06\x01U\x02\x1c\x19\x19U\x19\x1a\x1a\x1eU\x17\x07\x1c\x12\x1d\x01\x10\x07U\x01\x1a\x18\x1a\x07\x07\x1a\x02[',
        '4\x16\x05\x04W\x16\x19\x13W\x15\x02\x04\x04\x12\x04W\x04\x03\x16\x1b\x1b\x12\x13W\x1e\x19W\x04\x16\x19\x13W\x13\x05\x1e\x11\x03\x04Y',
        '.\x12\x1fZ\x10\x1b\x19\x11\x1f\x0eZ\x12\x0f\x14\x1dZ\x15\x14Z\x0e\x12\x1fZ\x18\x1b\x19\x11Z\x15\x1cZ\x0e\x12\x1fZ\r\x13\x1e\x1fZ\x19\x12\x1b\x13\x08T',
        'LMFOGHKNLMGFOHKFGNLKHNMLOKGNKGHFGLHKGLMHKGOFNMLHKGFNLMJNMLIJFGNMLOJIMLNGFJHNM'
    ]

    # Test pulling arguments from a call.
    tracer = function_tracing.get_tracer(0x0040103A)

    context = tracer.context_at(0x0040103A)
    operands = context.operands
    assert len(operands) == 1
    assert operands[0].is_func_ptr
    assert operands[0].value == 0x00401000
    # First, attempt to pull the arguments from the stack without get_function_args()
    first_arg_ptr = context.read_data(context.registers.esp,
                                      data_type=function_tracing.DWORD)
    second_arg = context.read_data(context.registers.esp + 4,
                                   data_type=function_tracing.BYTE)
    assert context.read_data(first_arg_ptr) == "Idmmn!Vnsme "
    assert second_arg == 1
    # Now try with get_function_args()
    args = context.get_function_args()
    assert len(args) == 2
    assert context.read_data(args[0]) == "Idmmn!Vnsme "
    assert args[1] == 1
示例#5
0
    def parse_stack_strings(self, func):

        logger.debug("Processing function: 0x{:X}".format(func.start_ea))
        tracer = function_tracing.get_tracer(func.start_ea)

        waiting_for_call = []

        context = None
        for ea in func.heads():
            context = tracer.context_at(ea)
            if not context:
                continue
            context.execute()  # also include instruction we are looking at.

            # If we encounter a call, process pushed in variables.
            if idc.print_insn_mnem(ea) == "call":
                for ip, var in waiting_for_call:
                    self.process_string(context, var, ip)
                waiting_for_call = []
                continue

            # Look for instructions where a stack variable is being used for something other than
            # a move.
            # We can do this by only considering variables that are the last operand.
            operands = context.get_operands(ea)
            if not operands:
                continue
            addr = operands[-1].addr or operands[-1].value
            if not addr:
                continue
            var = context.variables.get(addr)
            if var and var.is_stack:
                # Ignore string if it comes from memory with no concatinations.
                if var.history and idc.is_loaded(var.history[0].addr):
                    continue

                # If instruction is a push, it is possible that the string will be populated
                # after this instruction. Therefore, wait for the function call be before processing.
                if idc.print_insn_mnem(ea) == "push":
                    waiting_for_call.append((ea, var))
                else:
                    self.process_string(context, var, ea)

        # Process any strings still waiting for a call.
        if context:
            for ip, var in waiting_for_call:
                self.process_string(context, var, ip)

        # Remove any substrings or strings that are too small.
        for addr, encoded_string in sorted(self.encoded_strings):
            if len(encoded_string.encoded_data) < 3:
                self.encoded_strings.remove((addr, encoded_string))
                continue
            for _addr, _encoded_string in self.encoded_strings[:]:
                # Remove dups
                if (_addr == addr and _encoded_string is not encoded_string
                        and _encoded_string.encoded_data
                        == encoded_string.encoded_data):
                    self.encoded_strings.remove((addr, encoded_string))
                    break
                # Remove substrings
                elif _addr < addr:
                    index = addr - _addr
                    substring = _encoded_string.encoded_data[
                        index:index + len(encoded_string.encoded_data)]
                    if substring == encoded_string.encoded_data:
                        self.encoded_strings.remove((addr, encoded_string))
                        break

        # Report found strings
        for _, encoded_string in sorted(self.encoded_strings):
            # Don't want to rename because the buffers could be reused for multiple strings.
            encoded_string.publish(rename=False, patch=False)
            # TODO: EncodedString should allow commenting without renaming.
            idc.set_cmt(
                encoded_string.string_reference,
                'Stack String: "{}"'.format(encoded_string.display_name), 0)
def test_function_signature():
    """Tests FunctionSignature object."""
    import idc
    from kordesii.utils import function_tracing
    from kordesii.utils import decoderutils

    xor_func_ea = 0x00401000
    tracer = function_tracing.get_tracer(xor_func_ea)

    # Basic tests.
    context = tracer.context_at(xor_func_ea)
    func_sig = context.get_function_signature(func_ea=xor_func_ea)
    assert func_sig.declaration == "_BYTE *__cdecl sub_401000(_BYTE *a1, char a2);"
    assert func_sig.arg_types == ("_BYTE * a1", "char a2")
    args = func_sig.args
    assert len(args) == 2
    assert args[0].name == "a1"
    assert args[0].type == "_BYTE *"
    assert args[0].value == 0
    assert args[1].name == "a2"
    assert args[1].type == "char"
    assert args[1].value == 0

    # Test that we can manipulate signature.
    func_sig.arg_types += ("int new_arg",)
    assert func_sig.declaration == "_BYTE *__cdecl sub_401000(_BYTE *a1, char a2, int new_arg);"
    args = func_sig.args
    assert len(args) == 3
    assert args[2].name == "new_arg"
    assert args[2].type == "int"
    assert args[2].value == 0

    # Now test using iter_function_args

    # First force an incorrect number of arguments.
    idc.SetType(xor_func_ea, " _BYTE *__cdecl sub_401000(_BYTE *a1)")
    func = decoderutils.SuperFunc_t(xor_func_ea)

    # Then test we can force 2 arguments anyway.
    results = []
    for ea in func.calls_to:
        tracer = function_tracing.get_tracer(ea)
        for context in tracer.iter_context_at(ea):
            # The function signature only gives 1 argument now.
            func_sig = context.get_function_signature()
            assert len(func_sig.args) == 1
            # But we force 2.
            args = context.get_function_args(num_args=2)
            assert len(args) == 2
            results.append(args)
    assert results == [
        [4243456, 1],
        [4243472, 2],
        [4243500, 3],
        [4243548, 4],
        [4243584, 5],
        [4243616, 6],
        [4243652, 19],
        [4243696, 23],
        [4243732, 26],
        [4243744, 35],
        [4243760, 39],
        [4243768, 64],
        [4243776, 70],
        [4243804, 115],
        [4243828, 117],
        [4243868, 119],
        [4243908, 122],
        [4243960, 127],
    ]

    # Test that we can force function signatures.
    with pytest.raises(RuntimeError):
        context.get_function_args(0xFFF)
    with pytest.raises(RuntimeError):
        context.get_function_signature(0xFFF)
    assert len(context.get_function_args(0xFFF, num_args=3)) == 3
    func_sig = context.get_function_signature(0xFFF, force=3)
    assert func_sig.declaration == "int __cdecl no_name();"
def test_cpu_context():
    """Tests function_tracer and and cpu_context."""

    from kordesii.utils import function_tracing

    # Test on encryption function.
    tracer = function_tracing.get_tracer(0x00401024)
    context = tracer.context_at(0x00401024)

    operands = context.operands
    assert len(operands) == 2
    assert operands[0].text == "[ebp+arg_0]"
    assert operands[0].value == 0
    # arg_0 should be 8 bytes from stack pointer.
    assert operands[0].addr == context.registers.esp + 8 == 0x117F804
    assert operands[1].text == "eax"
    assert operands[1].value == context.registers.eax == 1

    # Test variables
    # context = tracer.context_at(0x00401017)
    # data_ptr = context.registers.edx
    data_ptr = operands[0].addr
    assert sorted(context.variables.names) == ["arg_0", "arg_4", "loc_401029"]
    assert data_ptr in context.variables
    var = context.variables[data_ptr]
    assert var.name == "arg_0"
    assert not var.history
    assert var.size == 4

    # Now execute this instruction and see if arg_0 has be set with the 1 from eax.
    context.execute(context.ip)
    assert operands[0].value == 1

    # Test getting all possible values passed into arg_0 using using depth.
    strings = []
    for context in tracer.iter_context_at(0x00401003, depth=1):
        assert context.ip == 0x00401003
        # mov     eax, [ebp+arg_0]
        strings.append(context.read_data(context.operands[1].value))
    assert strings == [
        b"Idmmn!Vnsme ",
        b'Vgqv"qvpkle"ukvj"ig{"2z20',
        b"Wkf#rvj`h#aqltm#el{#ivnsp#lufq#wkf#obyz#gld-",
        b"Keo$mw$wpvkjc$ej`$ehwk$cmraw$wle`a*",
        b"Dfla%gpwkv%mji`v%lk%rjji%fijqm+",
        b"Egru&ghb&biau&cgen&ngrc&rnc&irnct(",
        b"\\cv}3g{v3pargv3qfg3w|}4g3qavrx3g{v3t\x7fr``=",
        b"C\x7frer7c\x7fr7q{xxs7zve|7~d7cry7~yt\x7frd9",
        b'+()./,-"#*',
        b"`QFBWFsQL@FPPb",
        b"tSUdFS",
        b"\x01\x13\x10n\x0e\x05\x14",
        b'-",5 , v,tr4v,trv4t,v\x7f,ttt',
        b"@AKJDGBA@KJGDBJKAGDC",
        (
            b"!\x1d\x10U\x05\x14\x06\x01U\x02\x1c\x19\x19U\x19\x1a\x1a\x1eU\x17\x07\x1c"
            b"\x12\x1d\x01\x10\x07U\x01\x1a\x18\x1a\x07\x07\x1a\x02["
        ),
        (
            b"4\x16\x05\x04W\x16\x19\x13W\x15\x02\x04\x04\x12\x04W\x04\x03\x16\x1b\x1b"
            b"\x12\x13W\x1e\x19W\x04\x16\x19\x13W\x13\x05\x1e\x11\x03\x04Y"
        ),
        (
            b".\x12\x1fZ\x10\x1b\x19\x11\x1f\x0eZ\x12\x0f\x14\x1dZ\x15\x14Z\x0e\x12\x1f"
            b"Z\x18\x1b\x19\x11Z\x15\x1cZ\x0e\x12\x1fZ\r\x13\x1e\x1fZ\x19\x12\x1b\x13\x08T"
        ),
        b"LMFOGHKNLMGFOHKFGNLKHNMLOKGNKGHFGLHKGLMHKGOFNMLHKGFNLMJNMLIJFGNMLOJIMLNGFJHNM",
    ]

    # Test pulling arguments from a call.
    tracer = function_tracing.get_tracer(0x0040103A)

    context = tracer.context_at(0x0040103A)
    operands = context.operands
    assert len(operands) == 1
    assert operands[0].is_func_ptr
    assert operands[0].value == 0x00401000
    # First, attempt to pull the arguments from the stack without get_function_args()
    first_arg_ptr = context.read_data(context.registers.esp, data_type=function_tracing.DWORD)
    second_arg = context.read_data(context.registers.esp + 4, data_type=function_tracing.BYTE)
    assert context.read_data(first_arg_ptr) == b"Idmmn!Vnsme "
    assert second_arg == 1
    # Now try with get_function_args()
    args = context.get_function_args()
    assert len(args) == 2
    assert context.read_data(args[0]) == b"Idmmn!Vnsme "
    assert args[1] == 1

    assert sorted(context.variables.names) == ["aIdmmnVnsme", "sub_401000"]