From fff2f377818a5bd4bdc306612e7a5838af34b134 Mon Sep 17 00:00:00 2001 From: hakril Date: Sat, 24 Nov 2018 21:48:03 +0100 Subject: [PATCH] Add WMI create-process sample + improved some sample feature/doc --- samples/rpc/lsass.py | 39 ++++++++++++++++++++------- samples/test_code.py | 50 ++++++++++++++++++++++++++++++----- samples/wmi/create_process.py | 36 +++++++++++++++++++++++++ tests/test_process.py | 19 ------------- tests/test_system.py | 14 +--------- tests/test_wmi.py | 14 ++++++++-- 6 files changed, 121 insertions(+), 51 deletions(-) create mode 100644 samples/wmi/create_process.py diff --git a/samples/rpc/lsass.py b/samples/rpc/lsass.py index 497ddd5..c8c340d 100644 --- a/samples/rpc/lsass.py +++ b/samples/rpc/lsass.py @@ -2,28 +2,40 @@ import windows.rpc from windows.rpc import ndr -# Ndr stuff -class NdrContext(ndr.NdrStructure): - MEMBERS = [ndr.NdrLong, ndr.NdrLong, ndr.NdrLong, ndr.NdrLong, ndr.NdrLong] - - class PLSAPR_OBJECT_ATTRIBUTES(ndr.NdrStructure): MEMBERS = [ndr.NdrLong, ndr.NdrUniquePTR(ndr.NdrWString), - ndr.NdrUniquePTR(ndr.NdrLong), # We dont care if the subtype as we will pass None + ndr.NdrUniquePTR(ndr.NdrLong), # We dont care of the subtype as we will pass None ndr.NdrLong, - ndr.NdrUniquePTR(ndr.NdrLong), # We dont care if the subtype as we will pass None - ndr.NdrUniquePTR(ndr.NdrLong)] # We dont care if the subtype as we will pass None + ndr.NdrUniquePTR(ndr.NdrLong), # We dont care of the subtype as we will pass None + ndr.NdrUniquePTR(ndr.NdrLong)] # We dont care of the subtype as we will pass None +## From: RPCVIEW +# long Proc44_LsarOpenPolicy2( +# [in][unique][string] wchar_t* arg_0, +# [in]struct Struct_364_t* arg_1, +# [in]long arg_2, +# [out][context_handle] void** arg_3); +# This function has a [out][context_handle] meaning it return a context_handle +# Context handle are represented by 5 NdrLong where the first one is always 0 +# PythonForWindows represent context_handle using NdrContextHandle class LsarOpenPolicy2Parameter(ndr.NdrParameters): MEMBERS = [ndr.NdrUniquePTR(ndr.NdrWString), PLSAPR_OBJECT_ATTRIBUTES, ndr.NdrLong] +## From: RPCVIEW +# long Proc2_LsarEnumeratePrivileges( +# [in][context_handle] void* arg_0, +# [in][out]long *arg_1, +# [out]struct Struct_110_t* arg_2, +# [in]long arg_3); +# This function has a [in][context_handle] meaning it expect a context_handle +# We can pass the NdrContextHandle returned by Proc44_LsarOpenPolicy2 class LsarEnumeratePrivilegesParameter(ndr.NdrParameters): - MEMBERS = [NdrContext, + MEMBERS = [ndr.NdrContextHandle, ndr.NdrLong, ndr.NdrLong] @@ -72,12 +84,19 @@ iid = client.bind('12345778-1234-abcd-ef00-0123456789ab', version=(0,0)) params = LsarOpenPolicy2Parameter.pack([None, (0, None, None, 0, None, None), 0x20000000]) res = client.call(iid, 44, params) ## Unpack the resulting handle -handle = NdrContext.unpack(ndr.NdrStream(res)) +handle = ndr.NdrContextHandle.unpack(ndr.NdrStream(res)) + +# As context_handle have 4 NdrLong of effective data +# We can represent them as GUID +# NdrContextHandle is just a wrapper packing/unpacking GUID and taking +# care of the leading NdrLong(0) in the actual ndr representation of context_handle +print("Context Handle is: {0}\n".format(handle)) ## Craft parameters and call 'LsarEnumeratePrivileges' x = LsarEnumeratePrivilegesParameter.pack([handle, 0, 10000]); res = client.call(iid, 2, x) +print("Privileges:") ## Unpack the resulting 'LSAPR_PRIVILEGE_ENUM_BUFFER' priviledges = LSAPR_PRIVILEGE_ENUM_BUFFER.unpack(ndr.NdrStream(res)) for priv in priviledges: diff --git a/samples/test_code.py b/samples/test_code.py index be4ad1b..890896b 100644 --- a/samples/test_code.py +++ b/samples/test_code.py @@ -51,16 +51,20 @@ def hexdump(string, start_addr=0): result += linebuf+"\n" return result +class StartStepBP(dbg.breakpoints.HXBreakpoint): + def trigger(self, dbg, exc): + dbg.del_bp(self) + dbg.on_single_step(exc) # Trigger single step processing class CodeTesteur(dbg.Debugger): - def __init__(self, process, code, register_start={}): + def __init__(self, process, code, register_start={}, steps=False): super(CodeTesteur, self).__init__(process) self.initial_code = code code += "\xcc" - code_addr = self.write_code_in_target(process, code) - register_start["pc"] = code_addr + self.code_addr = self.write_code_in_target(process, code) + register_start["pc"] = self.code_addr self.thread_exec = process.threads[0] self.context_exec = self.thread_exec.context self.setup_target_context(self.context_exec, register_start) @@ -71,6 +75,12 @@ class CodeTesteur(dbg.Debugger): self.thread_exec.set_context(self.context_exec) self.thread_exec.resume() self.init_breakpoint = False + # Test code + if steps: + self.steps = True + self.add_bp(StartStepBP(self.code_addr)) + self.last_context = self.context_exec + self.last_code = self.initial_code def write_code_in_target(self, process, code): addr = process.virtual_alloc(len(code)) @@ -83,6 +93,15 @@ class CodeTesteur(dbg.Debugger): raise ValueError("Unknown register to setup <{0}>".format(name)) setattr(ctx, name, value) + def on_single_step(self, x): + print("* New step !") + # print("EIP = {0:#x}".format(self.current_thread.context.pc)) + self.report_ctx_diff(self.last_context, self.current_thread.context) + self.last_context = self.current_thread.context + self.last_code = self.report_code_diff(self.last_code) + self.single_step() + print ("") + def on_exception(self, x): exc_code = x.ExceptionRecord.ExceptionCode exc_addr = x.ExceptionRecord.ExceptionAddress @@ -97,12 +116,18 @@ class CodeTesteur(dbg.Debugger): print("") else: print("<{0}> at <{1:#x}>".format(exc_code, exc_addr)) + if exc_code == EXCEPTION_ACCESS_VIOLATION: + exc_infos = x.ExceptionRecord.ExceptionInformation + read_write = "write" if exc_infos[0] else "read" + target_addr = exc_infos[1] + print(" * Access of type <{0}> at address <{1:#x}>".format(read_write, target_addr)) self.report_ctx_diff(self.context_exec, ctx) + self.report_code_diff(self.initial_code) self.current_process.exit() return def report_ctx_diff(self, start, now): - print("==DIFF==") + print("== DIFF ==") for name, start_value in start.regs(): now_value = getattr(now, name) if start_value != now_value: @@ -113,8 +138,19 @@ class CodeTesteur(dbg.Debugger): data = self.current_process.read_memory(now.sp, start.sp - now.sp) print(hexdump(data, start.sp)) + def report_code_diff(self, initial_code): + code_size = len(initial_code) + final_code = self.current_process.read_memory(self.code_addr, code_size) + if final_code == initial_code: + return initial_code + print("== Executable code DIFF == ") + print("Before:") + print(hexdump(initial_code, self.code_addr)) + print("After:") + print(hexdump(final_code, self.code_addr)) + return final_code -def test_code_x86(code, regs=None, raw=False, **kwargs): +def test_code_x86(code, regs=None, raw=False, steps=False, **kwargs): print("Testing x86 code") process = windows.test.pop_proc_32(dwCreationFlags=DEBUG_PROCESS) if raw: @@ -133,7 +169,7 @@ def test_code_x86(code, regs=None, raw=False, **kwargs): start_register[name] = value - x = CodeTesteur(process, code, start_register) + x = CodeTesteur(process, code, start_register, steps) x.loop() def test_code_x64(code, regs=None, raw=False, **kwargs): @@ -156,7 +192,6 @@ def test_code_x64(code, regs=None, raw=False, **kwargs): value = int(value.strip(), 0) start_register[name] = value - x = CodeTesteur(process, code, start_register) x.loop() @@ -165,6 +200,7 @@ parser = argparse.ArgumentParser(prog=__file__) parser.add_argument('--x64', action='store_const', dest="func", const=test_code_x64, default=test_code_x86, help='Code is x64') parser.add_argument('--raw', action='store_true', help='argument is raw assembled code (in hex)') +parser.add_argument('--steps', action='store_true', help='Get all step info') parser.add_argument('code', help='The code to execute') parser.add_argument('regs', nargs="?", help='The default values of the registers') diff --git a/samples/wmi/create_process.py b/samples/wmi/create_process.py new file mode 100644 index 0000000..a24c4de --- /dev/null +++ b/samples/wmi/create_process.py @@ -0,0 +1,36 @@ +import windows +import windows.com +import windows.generated_def as gdef + +def bstr_variant(s): + v = windows.com.Variant() + v.vt = gdef.VT_BSTR + v._VARIANT_NAME_3.bstrVal = s + return v + + +wmireq = windows.system.wmi["root\\cimv2"] +proc_class = wmireq.get_object("Win32_process") + +# # Method 1 +inparam = proc_class.get_method("Create").inparam.spawn_instance() +inparam["CommandLine"] = r"c:\windows\system32\notepad.exe trolol.exe" +# Create a test checking return value +xx = wmireq.exec_method(proc_class, "Create", inparam) +print(xx) +print(xx.as_dict()) + +## Method2 + +# class MyResult(gdef.IWbemCallResult): + # def result(self): + # res = type(proc_class)() + # self.GetResultObject(gdef.WBEM_INFINITE, res) + # return res + + +# proc = proc_class.spawn() +# cmdline = bstr_variant(r"c:\windows\system32\notepad.exe") +# proc.put_variant("CommandLine", cmdline) +# res = wmireq.put_instance(proc) + diff --git a/tests/test_process.py b/tests/test_process.py index 8d4fed0..9a3b439 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -59,13 +59,6 @@ class TestCurrentProcessWithCheckGarbage(object): sections[0].start sections[0].size - def test_token_info(self): - token = windows.current_process.token - assert isinstance(token.computername, basestring) - assert isinstance(token.username, basestring) - assert isinstance(token.integrity, (int, long)) - assert isinstance(token.is_elevated, (bool)) - def test_local_ProcessParameters_LSA_UNICODE_STRING(self): image_path_from_process_params = windows.current_process.peb.ProcessParameters.contents.ImagePathName.str.lower() image_path_from_module = windows.current_process.peb.modules[0].fullname.lower() @@ -418,18 +411,6 @@ class TestProcessWithCheckGarbage(object): image_path_from_module = proc32_64.peb.modules[0].fullname.lower() assert image_path_from_process_params == image_path_from_module - def test_lower_integrity(self, proc32): - # Lowering the integrity in remote process - # Because we don't want to mess with the token of our testing process - - proc32.execute_python("import windows") - # We stock the handle becase lowering the integrity - # will mess with token retrieval - proc32.execute_python("token = windows.current_process.token") - proc32.execute_python("token.integrity = 123") - # execute_python will raise this in our own process :) - proc32.execute_python("assert token.integrity == 123") - def test_remote_assertion_error(self, proc32): proc32.execute_python("assert 1 == 1") with pytest.raises(windows.injection.RemotePythonError): diff --git a/tests/test_system.py b/tests/test_system.py index 5f8f534..0d9ee7d 100644 --- a/tests/test_system.py +++ b/tests/test_system.py @@ -21,19 +21,7 @@ class TestSystemWithCheckGarbage(object): return windows.system.logicaldrives def test_wmi(self): - # Well, pytest initialize COM with its own parameters - # It might make our own com.init() in WMI fail and therefore not call - # CoInitializeSecurity. But looks like pytest/default COM-security parameters - # does not allow to perform the request we want.. - # So we try & do it ourself here. - - # Do co-reinit in conftest.py ? - try: - if windows.com.init(): # if init fail. Call CoInitializeSecurity ourself - windows.com.initsecurity() - except Exception as e: - pass - return windows.system.wmi.select("Win32_Process", "*") + return windows.system.wmi def test_handles(self): return windows.system.handles diff --git a/tests/test_wmi.py b/tests/test_wmi.py index 907e4a0..b70d142 100644 --- a/tests/test_wmi.py +++ b/tests/test_wmi.py @@ -12,6 +12,18 @@ from pfwtest import * # does not allow to perform the request we want.. # So we try & do it ourself here. + +pytestmark = pytest.mark.usefixtures("init_com_security") + +@pytest.fixture(scope="module") +def init_com_security(): + # Init com security if not done + try: + return windows.com.initsecurity() + except WindowsError: + pass + + wmimanager = windows.system.wmi @pytest.mark.parametrize("name, expected_cls", [ @@ -27,7 +39,6 @@ def test_wmimanager_getnamespace(name, expected_cls): def test_wmimanager_subnamespaces(): subnamespaces = wmimanager.get_subnamespaces("root") subnamespaces = [x.lower() for x in subnamespaces] - assert "cimv2" in subnamespaces assert "security" in subnamespaces assert "subscription" in subnamespaces @@ -74,7 +85,6 @@ def test_get_object(name, cls): # Todo: test # - put_instance -# - exec_method @pytest.mark.parametrize("cmdline", [r"c:\windows\notepad.exe trolol.exe"]) def test_exec_method_Win32_Process_create(cmdline):