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)
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
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
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"]