From 38a2298dee07df68d518b27245364bbf5e4f5274 Mon Sep 17 00:00:00 2001 From: Clement Rouault Date: Thu, 28 Jan 2016 15:13:49 +0100 Subject: [PATCH] Add nativeutils.py with GetProcAddress64 + add instr to x86/x64 assembler + add merge of labels in multipleinstr (dummy function support) --- samples/native_utils.py | 49 +++++++++ windows/native_exec/nativeutils.py | 138 +++++++++++++++++++++++++ windows/native_exec/simple_x64.py | 62 ++++++++++- windows/native_exec/simple_x86.py | 45 +++++++- windows/native_exec/test_simple_x64.py | 33 +++++- windows/native_exec/test_simple_x86.py | 19 ++++ 6 files changed, 339 insertions(+), 7 deletions(-) create mode 100644 samples/native_utils.py create mode 100644 windows/native_exec/nativeutils.py diff --git a/samples/native_utils.py b/samples/native_utils.py new file mode 100644 index 0000000..d7e2826 --- /dev/null +++ b/samples/native_utils.py @@ -0,0 +1,49 @@ +import windows +import windows.test + +import windows.native_exec.simple_x64 as x64 +import windows.native_exec.nativeutils +from windows.generated_def.winstructs import * + +GetProcAddress64 = windows.native_exec.nativeutils.GetProcAddress64 + +dll = "KERNEL32.DLL\x00".encode("utf-16-le") +api = "LoadLibraryA\x00" +dll_to_load = "SUCE" + + +RemoteManualLoadLibray = x64.MultipleInstr() +c = RemoteManualLoadLibray +c += x64.Mov("R15", "RCX") +c += x64.Mov("RCX", x64.mem("[R15 + 0]")) +c += x64.Mov("RDX", x64.mem("[R15 + 8]")) +c += x64.Call(":FUNC_GETPROCADDRESS64") +c += x64.Mov("RCX", x64.mem("[R15 + 0x10]")) +c += x64.Push("RCX") +c += x64.Push("RCX") +c += x64.Push("RCX") +c += x64.Call("RAX") +c += x64.Pop("RCX") +c += x64.Pop("RCX") +c += x64.Pop("RCX") +c += x64.Ret() + +RemoteManualLoadLibray += GetProcAddress64 + + +calc= windows.test.pop_calc_64(dwCreationFlags=CREATE_SUSPENDED) + +addr = calc.virtual_alloc(0x1000) +addr2 = addr + len(dll) +addr3 = addr2 + len(api) +addr4 = addr3 + len(dll_to_load) + +calc.write_memory(addr, dll) +calc.write_memory(addr2, api) +calc.write_memory(addr3, dll_to_load) +calc.write_qword(addr4, addr) +calc.write_qword(addr4 + 8, addr2) +calc.write_qword(addr4 + 0x10, addr3) + +calc.execute(RemoteManualLoadLibray.get_code(), addr4) + diff --git a/windows/native_exec/nativeutils.py b/windows/native_exec/nativeutils.py new file mode 100644 index 0000000..7930d83 --- /dev/null +++ b/windows/native_exec/nativeutils.py @@ -0,0 +1,138 @@ +import windows + +import windows.native_exec.simple_x64 as x64 +from windows.generated_def.winstructs import * + + +StrlenW64 = x64.MultipleInstr() +StrlenW64 += x64.Label(":FUNC_STRLENW64") +StrlenW64 += x64.Push("RCX") +StrlenW64 += x64.Push("RDI") +StrlenW64 += x64.Mov("RDI", "RCX") +StrlenW64 += x64.Xor("RAX", "RAX") +StrlenW64 += x64.Xor("RCX", "RCX") +StrlenW64 += x64.Dec("RCX") +StrlenW64 += x64.Repne + x64.ScasW() +StrlenW64 += x64.Not("RCX") +StrlenW64 += x64.Dec("RCX") +StrlenW64 += x64.Mov("RAX", "RCX") +StrlenW64 += x64.Pop("RDI") +StrlenW64 += x64.Pop("RCX") +StrlenW64 += x64.Ret() + + +StrlenA64 = x64.MultipleInstr() +StrlenA64 += x64.Label(":FUNC_STRLENA64") +StrlenA64 += x64.Push("RCX") +StrlenA64 += x64.Push("RDI") +StrlenA64 += x64.Mov("RDI", "RCX") +StrlenA64 += x64.Xor("RAX", "rax") +StrlenA64 += x64.Xor("RCX", "RCX") +StrlenA64 += x64.Dec("RCX") +StrlenA64 += x64.Repne + x64.ScasB() +StrlenA64 += x64.Not("RCX") +StrlenA64 += x64.Dec("RCX") +StrlenA64 += x64.Mov("RAX", "RCX") +StrlenA64 += x64.Pop("RDI") +StrlenA64 += x64.Pop("RCX") +StrlenA64 += x64.Ret() + + +GetProcAddress64 = x64.MultipleInstr() +GetProcAddress64 += x64.Label(":FUNC_GETPROCADDRESS64") +GetProcAddress64 += x64.Push("RBX") +GetProcAddress64 += x64.Push("RCX") +GetProcAddress64 += x64.Push("RDX") +GetProcAddress64 += x64.Push("RSI") +GetProcAddress64 += x64.Push("RDI") +GetProcAddress64 += x64.Push("R8") +GetProcAddress64 += x64.Push("R9") +GetProcAddress64 += x64.Push("R10") +GetProcAddress64 += x64.Push("R11") +GetProcAddress64 += x64.Push("R12") +GetProcAddress64 += x64.Push("R13") +# Params : RCX -> libname +# Params : RDX -> API Name +GetProcAddress64 += x64.Mov("R11", "RCX") +GetProcAddress64 += x64.Mov("R12", "RDX") +GetProcAddress64 += x64.Mov("RAX", x64.mem("GS:[0x60]")) #PEB ! +GetProcAddress64 += x64.Mov("RAX", x64.mem("[RAX + 24] ")) # ; RAX = ldr (+ 6 for 64 cause of 2 ptr) +GetProcAddress64 += x64.Mov("RAX", x64.mem("[RAX + 32]")) # ; RAX on the first elt of the list (first module) +GetProcAddress64 += x64.Mov("RDX", "RAX") +GetProcAddress64 += x64.Label(":a_dest") +GetProcAddress64 += x64.Mov("RAX", "RDX") +GetProcAddress64 += x64.Mov("RBX", x64.mem("[RAX + 32]")) # RBX : first base ! (base of current module) +#GetProcAddress64 += x64.Mov("RBX ", x64.mem("[RAX + 32]")) # RBX : first base ! (base of current module) +GetProcAddress64 += x64.Cmp("RBX", 0) +GetProcAddress64 += x64.Jz(":NOT_FOUND") +GetProcAddress64 += x64.Mov("RCX", x64.mem("[RAX + 80]")) # RCX = NAME (UNICODE_STRING.Buffer) +GetProcAddress64 += x64.Call(":FUNC_STRLENW64") +GetProcAddress64 += x64.Mov("RDI", "RCX") +GetProcAddress64 += x64.Mov("RCX", "RAX") +GetProcAddress64 += x64.Mov("RSI", "R11") +#GetProcAddress64 += x64.Int3() +GetProcAddress64 += x64.Rep + x64.CmpsW() #;cmp with current dll name (unicode) +GetProcAddress64 += x64.Test("RCX", "RCX") +GetProcAddress64 += x64.Jz(":DLL_FOUND") +GetProcAddress64 += x64.Mov("RDX", x64.mem("[RDX]")) +GetProcAddress64 += x64.Jmp(":a_dest") +GetProcAddress64 += x64.Label(":DLL_FOUND") # here rbx = base +GetProcAddress64 += x64.Mov("EAX", x64.mem("[RBX + 60]")) # rax = PEBASE RVA +GetProcAddress64 += x64.Add("RAX", "RBX") # RAX = PEBASE +GetProcAddress64 += x64.Add("RAX", 24) # ;OPTIONAL HEADER +GetProcAddress64 += x64.Mov("ECX", x64.mem("[rax + 112]")) # ;rcx = RVA export dir +GetProcAddress64 += x64.Add("RCX", "RBX") # ;rcx = export_dir +GetProcAddress64 += x64.Mov("RAX", "RCX") # ;RAX = export_dir +GetProcAddress64 += x64.Push("RAX") # ;Save it for after function search +# ; EBX = BASE | EAX = EXPORT DIR +GetProcAddress64 += x64.Mov("ECX", x64.mem("[RAX + 24] ")) # rax = PEBASE RVA +GetProcAddress64 += x64.Mov("R13", "RCX") # ;r13 = NB names +GetProcAddress64 += x64.Mov("EDX", x64.mem("[RAX + 32] ")) # EDX = names array RVA +GetProcAddress64 += x64.Add("RDX", "RBX") # RDX = names array +GetProcAddress64 += x64.Xor("RCX", "RCX") +GetProcAddress64 += x64.Label(":SEARCH_LOOP") +GetProcAddress64 += x64.Mov("ESI", x64.mem("[RDX + RCX * 4]")) # ;Get function name RVA +GetProcAddress64 += x64.Add("RSI", "RBX") # ;Get name addr +GetProcAddress64 += x64.Push("RCX") # ;Save current index (could use x64 register) +GetProcAddress64 += x64.Mov("RCX", "R12") +GetProcAddress64 += x64.Call(":FUNC_STRLENA64") # TODO: mov outside the loop :D +GetProcAddress64 += x64.Mov("RCX", "RAX") +GetProcAddress64 += x64.Mov("RDI", "R12") +GetProcAddress64 += x64.Rep + x64.CmpsB() +GetProcAddress64 += x64.Mov("EAX", "ECX") +GetProcAddress64 += x64.Pop("RCX") +GetProcAddress64 += x64.Inc("RCX") +GetProcAddress64 += x64.Test("RAX", "RAX") +GetProcAddress64 += x64.Jnz(":SEARCH_LOOP") +# Func FOUND ! +GetProcAddress64 += x64.Dec("RCX") +GetProcAddress64 += x64.Pop("RAX") # ;Restore export_dir addr +GetProcAddress64 += x64.Mov("EDX", x64.mem("[RAX + 36]")) # ;EDX = AddressOfNameOrdinals RVX +GetProcAddress64 += x64.Add("RDX", "RBX") +GetProcAddress64 += x64.OperandSizeOverride + x64.Mov("ECX", x64.mem("[rdx + rcx * 2]")) # ; ecx = Ieme ordinal (short array) +GetProcAddress64 += x64.And('RCX', 0xffff) +GetProcAddress64 += x64.Mov("EDX", x64.mem("[RAX + 28]")) # ; AddressOfFunctions RVA +GetProcAddress64 += x64.Add("RDX", "RBX") +GetProcAddress64 += x64.Mov("EDX", x64.mem("[RDX + RCX * 4]")) +GetProcAddress64 += x64.Add("RDX", "RBX") +GetProcAddress64 += x64.Mov("RAX", "RDX") +GetProcAddress64 += x64.Pop("R13") +GetProcAddress64 += x64.Pop("R12") +GetProcAddress64 += x64.Pop("R11") +GetProcAddress64 += x64.Pop("R10") +GetProcAddress64 += x64.Pop("R9") +GetProcAddress64 += x64.Pop("R8") +GetProcAddress64 += x64.Pop("RDI") +GetProcAddress64 += x64.Pop("RSI") +GetProcAddress64 += x64.Pop("RDX") +GetProcAddress64 += x64.Pop("RCX") +GetProcAddress64 += x64.Pop("RBX") +GetProcAddress64 += x64.Ret() +GetProcAddress64 += x64.Label(":NOT_FOUND") +GetProcAddress64 += x64.Xor("RAX", "RAX") +GetProcAddress64 += x64.Ret() +# Ajout des dependances +GetProcAddress64 += StrlenW64 +GetProcAddress64 += StrlenA64 + + diff --git a/windows/native_exec/simple_x64.py b/windows/native_exec/simple_x64.py index 442d538..75a9d62 100644 --- a/windows/native_exec/simple_x64.py +++ b/windows/native_exec/simple_x64.py @@ -722,9 +722,6 @@ class Pop(Instruction): encoding = [(RawBits.from_int(5, 0x58 >> 3), X64RegisterSelector())] -class Call(Instruction): - encoding = [(RawBits.from_int(8, 0xff), Slash(2))] - class Xchg(Instruction): default_32_bits = True @@ -820,6 +817,10 @@ class JmpImm8(JmpImm): class JmpImm32(JmpImm): accept_as_Ximmediat = staticmethod(accept_as_32immediat) +class Call(JmpType): + encoding = [(RawBits.from_int(8, 0xe8), JmpImm32(5)), + (RawBits.from_int(8, 0xff), Slash(2))] + class Jmp(JmpType): encoding = [(RawBits.from_int(8, 0xeb), JmpImm8(2)), @@ -867,10 +868,17 @@ class Mov(Instruction): class Cmp(Instruction): default_32_bits = True + encoding = [(RawBits.from_int(8, 0x3d), RegisterRax(), Imm32()), (RawBits.from_int(8, 0x81), Slash(7), Imm32()), (RawBits.from_int(8, 0x3b), ModRM([ModRM_REG__REG, ModRM_REG64__MEM]))] +class Test(Instruction): + default_32_bits = True + refuse_reverse = True + encoding = [(RawBits.from_int(8, 0xf7), Slash(7), Imm32()), + (RawBits.from_int(8, 0x85), ModRM([ModRM_REG__REG, ModRM_REG64__MEM], has_direction_bit=False))] + class Xor(Instruction): default_32_bits = True @@ -880,6 +888,47 @@ class Xor(Instruction): class Nop(Instruction): encoding = [(RawBits.from_int(8, 0x90),)] +class Not(Instruction): + default_32_bits = True + encoding = [(RawBits.from_int(8, 0xF7), Slash(2))] + +class ScasB(Instruction): + default_32_bits = True + encoding = [(RawBits.from_int(8, 0xAE),)] + + +class ScasW(Instruction): + default_32_bits = True + encoding = [(RawBits.from_int(16, 0x66AF),)] + + +class ScasD(Instruction): + default_32_bits = True + encoding = [(RawBits.from_int(8, 0xAF),)] + +class ScasQ(Instruction): + encoding = [(RawBits.from_int(16, 0x48AF),)] + + +class CmpsB(Instruction): + default_32_bits = True + encoding = [(RawBits.from_int(8, 0xa6),)] + + +class CmpsW(Instruction): + default_32_bits = True + encoding = [(RawBits.from_int(16, 0x66A7),)] + + +class CmpsD(Instruction): + default_32_bits = True + encoding = [(RawBits.from_int(8, 0xa7),)] + + +class CmpsQ(Instruction): + default_32_bits = True + encoding = [(RawBits.from_int(16, 0x48A7),)] + class Retf(Instruction): default_32_bits = True @@ -922,7 +971,7 @@ class MultipleInstr(object): def get_code(self): if self.expected_labels: - raise ValueError("Unresolved labels: {self.expected_labels}".format(self=self)) + raise ValueError("Unresolved labels: {0}".format(self.expected_labels.keys())) return b"".join([bytes(x[1].get_code()) for x in sorted(self.instrs.items())]) def add_instruction(self, instruction): @@ -1036,7 +1085,12 @@ class MultipleInstr(object): self.size -= 1 def merge_shellcode(self, other): + shared_labels = set(self.labels) & set(other.labels) + if shared_labels: + raise ValueError("Cannot merge shellcode: shared labels {0}".format(shared_labels)) for offset, instr in sorted(other.instrs.items()): + for label_name in [name for name, label_offset in other.labels.items() if label_offset == offset]: + self.add_instruction(Label(label_name)) self.add_instruction(instr) def __iadd__(self, other): diff --git a/windows/native_exec/simple_x86.py b/windows/native_exec/simple_x86.py index 3b4de56..90ba2f6 100644 --- a/windows/native_exec/simple_x86.py +++ b/windows/native_exec/simple_x86.py @@ -580,6 +580,11 @@ class JmpImm32(JmpImm): # Instructions + +class Call(JmpType): + encoding = [(RawBits.from_int(8, 0xe8), JmpImm32(5)), + (RawBits.from_int(8, 0xff), Slash(2))] + class Jmp(JmpType): encoding = [(RawBits.from_int(8, 0xeb), JmpImm8(2)), (RawBits.from_int(8, 0xe9), JmpImm32(5))] @@ -674,6 +679,11 @@ class Cmp(Instruction): (RawBits.from_int(8, 0x3b), ModRM([ModRM_REG__REG, ModRM_REG__MEM]))] +class Test(Instruction): + encoding = [(RawBits.from_int(8, 0xf7), Slash(7), Imm32()), + (RawBits.from_int(8, 0x85), ModRM([ModRM_REG__REG, ModRM_REG__MEM], has_direction_bit=False))] + + class Out(Instruction): encoding = [(RawBits.from_int(8, 0xee), FixedRegister('DX'), FixedRegister('AL')), (RawBits.from_int(16, 0x66ef), FixedRegister('DX'), FixedRegister('AX')), # Fuck-it hardcoded prefix for now @@ -694,8 +704,7 @@ class Xchg(Instruction): encoding = [(RawBits.from_int(5, 0x90 >> 3), RegisterEax(), X86RegisterSelector()), (RawBits.from_int(5, 0x90 >> 3), X86RegisterSelector(), RegisterEax())] -class Call(Instruction): - encoding = [(RawBits.from_int(8, 0xff), Slash(2))] + class Cpuid(Instruction): @@ -706,9 +715,36 @@ class Ret(Instruction): encoding = [(RawBits.from_int(8, 0xc3),)] +class ScasB(Instruction): + encoding = [(RawBits.from_int(8, 0xAE),)] + +class ScasW(Instruction): + encoding = [(RawBits.from_int(16, 0x66AF),)] + +class ScasD(Instruction): + encoding = [(RawBits.from_int(8, 0xAF),)] + + +class CmpsB(Instruction): + default_32_bits = True + encoding = [(RawBits.from_int(8, 0xa6),)] + + +class CmpsW(Instruction): + default_32_bits = True + encoding = [(RawBits.from_int(16, 0x66A7),)] + + +class CmpsD(Instruction): + default_32_bits = True + encoding = [(RawBits.from_int(8, 0xa7),)] + + class Nop(Instruction): encoding = [(RawBits.from_int(8, 0x90),)] +class Not(Instruction): + encoding = [(RawBits.from_int(8, 0xF7), Slash(2))] class Retf(Instruction): encoding = [(RawBits.from_int(8, 0xcb),)] @@ -866,7 +902,12 @@ class MultipleInstr(object): self.size -= 1 def merge_shellcode(self, other): + shared_labels = set(self.labels) & set(other.labels) + if shared_labels: + raise ValueError("Cannot merge shellcode: shared labels {0}".format(shared_labels)) for offset, instr in sorted(other.instrs.items()): + for label_name in [name for name, label_offset in other.labels.items() if label_offset == offset]: + self.add_instruction(Label(label_name)) self.add_instruction(instr) def __iadd__(self, other): diff --git a/windows/native_exec/test_simple_x64.py b/windows/native_exec/test_simple_x64.py index bf02d6b..181d569 100644 --- a/windows/native_exec/test_simple_x64.py +++ b/windows/native_exec/test_simple_x64.py @@ -13,9 +13,10 @@ mnemonic_name_exception = {'movabs': 'mov'} class TestInstr(object): - def __init__(self, instr_to_test, immediat_accepted=None, must_fail=None, debug=False): + def __init__(self, instr_to_test, expected_result=None, immediat_accepted=None, must_fail=None, debug=False): self.instr_to_test = instr_to_test self.immediat_accepted = immediat_accepted + self.expected_result = expected_result self.must_fail = must_fail self.debug = debug @@ -40,6 +41,11 @@ class TestInstr(object): raise AssertionError("Trying to disas an instruction resulted in multiple disassembled instrs") capres = capres_list[0] print("{0} {1}".format(capres.mnemonic, capres.op_str)) + if self.expected_result is not None: + if "{0} {1}".format(capres.mnemonic, capres.op_str) == self.expected_result: + return True + else: + raise AssertionError("Expected result <{0}> got <{1}>".format(self.expected_result, "{0} {1}".format(capres.mnemonic, capres.op_str))) if len(res) != len(capres.bytes): raise AssertionError("Not all bytes have been used by the disassembler") self.compare_mnemo(capres) @@ -128,6 +134,7 @@ TestInstr(Mov, immediat_accepted=-1)('RCX', 0xffffffffffffffff) TestInstr(Mov)(mem('gs:[0x1122334455667788]'), 'RAX') TestInstr(Mov)(mem('[RAX]'), 0x11223344) TestInstr(Mov)(mem('[EAX]'), 0x11223344) +TestInstr(Mov)(mem('[RBX]'), 0x11223344) TestInstr(And)('RCX', 'RBX') TestInstr(And)('RAX', 0x11223344) @@ -143,6 +150,15 @@ TestInstr(Or)(mem('[RAX + 1]'), 'R8') TestInstr(Or)(mem('[EAX + 1]'), 'R8') TestInstr(Or)(mem('[RAX + 1]'), 'EAX') +# I really don't know why it's the inverse +# But I don't care, it's Test dude.. +TestInstr(Test, expected_result="test r11, rax")('RAX', 'R11') +TestInstr(Test, expected_result="test edi, eax")('EAX', 'EDI') +TestInstr(Test)('RCX', 'RCX') + +TestInstr(Test)(mem('[RDI + 0x100]'), 'RCX') + +assert Test(mem('[RDI + 0x100]'), 'RCX').get_code() == Test('RCX', mem('[RDI + 0x100]')).get_code() TestInstr(Push)('R15') @@ -168,6 +184,21 @@ TestInstr(Mov)(mem('[RBX + RCX + 0x10]'), 'ECX') TestInstr(Mov)(mem('[EBX + ECX + 0x10]'), 'ECX') TestInstr(Mov)(mem('[EBX + ECX + 0x10]'), 'R8') +TestInstr(Not)('RAX') +TestInstr(Not)(mem('[RAX]')) + + +TestInstr(ScasB, expected_result="scasb al, byte ptr [rdi]")() +TestInstr(ScasW, expected_result="scasw ax, word ptr [rdi]")() +TestInstr(ScasD, expected_result="scasd eax, dword ptr [rdi]")() +TestInstr(ScasQ, expected_result="scasq rax, qword ptr [rdi]")() + +TestInstr(CmpsB, expected_result="cmpsb byte ptr [rsi], byte ptr [rdi]")() +TestInstr(CmpsW, expected_result="cmpsw word ptr [rsi], word ptr [rdi]")() +TestInstr(CmpsD, expected_result="cmpsd dword ptr [rsi], dword ptr [rdi]")() +TestInstr(CmpsQ, expected_result="cmpsq qword ptr [rsi], qword ptr [rdi]")() + + TestInstr(Mov, must_fail=True)('RCX', 'ECX') TestInstr(Mov, must_fail=True)('RCX', mem('[ECX + RCX]')) diff --git a/windows/native_exec/test_simple_x86.py b/windows/native_exec/test_simple_x86.py index fbf02e7..f180cf6 100644 --- a/windows/native_exec/test_simple_x86.py +++ b/windows/native_exec/test_simple_x86.py @@ -140,6 +140,25 @@ TestInstr(Or)('EAX', 0x11223344) TestInstr(Or)('EAX', mem('[EAX + 1]')) TestInstr(Or)(mem('[EAX + EAX]'), 'EDX') +TestInstr(Not)('EAX') +TestInstr(Not)(mem('[EAX]')) + +TestInstr(ScasB, expected_result="scasb al, byte ptr es:[edi]")() +TestInstr(ScasW, expected_result="scasw ax, word ptr es:[edi]")() +TestInstr(ScasD, expected_result="scasd eax, dword ptr es:[edi]")() + +TestInstr(CmpsB, expected_result="cmpsb byte ptr [esi], byte ptr es:[edi]")() +TestInstr(CmpsW, expected_result="cmpsw word ptr [esi], word ptr es:[edi]")() +TestInstr(CmpsD, expected_result="cmpsd dword ptr [esi], dword ptr es:[edi]")() + + +TestInstr(Test)('EAX', 'EAX') +TestInstr(Test, expected_result="test edi, ecx ")('ECX', 'EDI') + +TestInstr(Test)(mem('[ECX + 0x100]'), 'ECX') + +assert Test(mem('[ECX + 0x100]'), 'ECX').get_code() == Test('ECX', mem('[ECX + 0x100]')).get_code() + assert Xchg('EAX', 'ECX').get_code() == Xchg('ECX', 'EAX').get_code()