diff --git a/pytesting/conftest.py b/pytesting/conftest.py index 0edeb06..ea1f71b 100644 --- a/pytesting/conftest.py +++ b/pytesting/conftest.py @@ -27,23 +27,19 @@ else: import sys +import weakref def generate_pop_and_exit_fixtures(proc_popers, ids=[], dwCreationFlags=DEFAULT_CREATION_FLAGS): @pytest.fixture(params=proc_popers, ids=ids) def pop_and_exit_process(request): proc_poper = request.param proc = proc_poper(dwCreationFlags=dwCreationFlags) - yield proc # provide the fixture value + yield weakref.proxy(proc) # provide the fixture value try: - print("EXIT PROC <{0}>".format(sys.getrefcount(proc))) - # if sys.getrefcount(proc) > 5: - # import pdb;pdb.set_trace() proc.exit(0) except WindowsError as e: if not proc.is_exit: raise - # import pdb;pdb.set_trace() - # proc.__del__() del proc return pop_and_exit_process @@ -59,13 +55,7 @@ else: dwCreationFlags=gdef.CREATE_SUSPENDED) -@pytest.fixture() -def check_for_gc_garbage(request): - garbage_before = set(gc.garbage) - yield - gc.collect() - new_garbage = set(gc.garbage) - garbage_before - assert not new_garbage, "Test generated uncollectable object ({0})".format(new_garbage) + class HandleDebugger(object): def __init__(self, pid): @@ -96,38 +86,29 @@ class HandleDebugger(object): current_process_hdebugger = HandleDebugger(windows.current_process.pid) current_process_hdebugger.refresh_handles() -# TST = current_process_hdebugger.refresh_handles() -RESULT = {} @pytest.fixture() def check_for_handle_leak(request): - # current_process_hdebugger.refresh_handles() - yield - # leaked_handles = current_process_hdebugger.get_new_handle() - # for lh in leaked_handles: - # RESULT[lh.wValue] = request.function.__name__ - print("HANDLE LEAK SAVE") - # assert not leaked_handles, "Test Leaked <{0}> handles of types ({1})".format(len(leaked_handles), set(h.type for h in leaked_handles)) + x = current_process_hdebugger.refresh_handles() + yield None + leaked_handles = current_process_hdebugger.get_new_handle(x) + try: + leaked_handles_types = set(h.type for h in leaked_handles) + except Exception as e: + leaked_handles = current_process_hdebugger.get_new_handle(x) + leaked_handles_types = set(h.type for h in leaked_handles) + # print("ERROR FOR TYPE, newleaked = {0}".format(current_process_hdebugger.get_new_handle(x))) + leaked_handles_types -= set(['EtwRegistration', 'Key', 'DebugObject', 'Event']) + assert not leaked_handles_types, "Test Leaked <{0}> handles of types ({1})".format(len(leaked_handles), leaked_handles_types) -@pytest.fixture(scope='session') -def check_for_handle_leak_final(request): - # x = current_process_hdebugger.get_handles() - print("CHECK HANDLE FINAL :D") - # current_process_hdebugger.refresh_handles() - yield - # leaked_handles = current_process_hdebugger.get_new_handle(x) - # import pdb;pdb.set_trace() - # print(leaked_handles) - - # leaked_handles = current_process_hdebugger.get_new_handle() - # import pdb;pdb.set_trace() - # assert not leaked_handles, "Test Leaked <{0}> handles of types ({1})".format(len(leaked_handles), set(h.type for h in leaked_handles)) - - -def pytest_unconfigure(*args, **kwargs): - import pdb;pdb.set_trace() - print(TST) - -# pytestmark = pytest.mark.usefixtures('check_for_handle_leak_final') \ No newline at end of file +@pytest.fixture() +def check_for_gc_garbage(request): + # print("GC CHECK") + garbage_before = set(gc.garbage) + yield None + gc.collect() + new_garbage = set(gc.garbage) - garbage_before + assert not new_garbage, "Test generated uncollectable object ({0})".format(new_garbage) + # print("GC CHECK END") \ No newline at end of file diff --git a/pytesting/test_debugger.py b/pytesting/test_debugger.py index 23936c9..1bc5551 100644 --- a/pytesting/test_debugger.py +++ b/pytesting/test_debugger.py @@ -11,7 +11,7 @@ import windows.native_exec.simple_x64 as x64 from conftest import generate_pop_and_exit_fixtures, pop_proc_32, pop_proc_64 from pfwtest import * -pytestmark = pytest.mark.usefixtures('check_for_gc_garbage') +# pytestmark = pytest.mark.usefixtures('check_for_gc_garbage', "check_for_handle_leak") proc32_debug = generate_pop_and_exit_fixtures([pop_proc_32], ids=["proc32dbg"], dwCreationFlags=gdef.DEBUG_PROCESS) proc64_debug = generate_pop_and_exit_fixtures([pop_proc_64], ids=["proc64dbg"], dwCreationFlags=gdef.DEBUG_PROCESS) @@ -106,9 +106,11 @@ def test_multiple_hwx_breakpoint(proc32_64_debug): # Used to verif we actually called the Breakpoints assert TSTBP.COUNTER == 4 - +# @check_for_gc_garbage +@check_for_handle_leak def test_four_hwx_breakpoint_fail(proc32_64_debug): """Check that setting 4HXBP in the same thread fails""" + # print("test_four_hwx_breakpoint_fail {0}".format(proc32_64_debug)) class TSTBP(windows.debug.HXBreakpoint): def __init__(self, addr, expec_before): self.addr = addr @@ -165,7 +167,8 @@ def test_hwx_breakpoint_are_on_all_thread(proc32_64_debug): # Used to verif we actually called the Breakpoints assert TSTBP.COUNTER == 2 -@check_for_handle_leak +# @check_for_handle_leak +# @check_for_gc_garbage @pytest.mark.parametrize("bptype", [windows.debug.Breakpoint, windows.debug.HXBreakpoint]) def test_simple_breakpoint_name_addr(proc32_64_debug, bptype): """Check breakpoint address resolution for format dll!api""" diff --git a/pytesting/test_process.py b/pytesting/test_process.py index 60af33e..a8d183a 100644 --- a/pytesting/test_process.py +++ b/pytesting/test_process.py @@ -63,8 +63,13 @@ class TestCurrentProcessWithCheckGarbage(object): assert isinstance(token.integrity, (int, long)) assert isinstance(token.is_elevated, (bool)) +@check_for_handle_leak +def test_yolo(proc32_64): + print(proc32_64) + print(proc32_64.handle) +@check_for_handle_leak @check_for_gc_garbage class TestProcessWithCheckGarbage(object): def test_pop_proc_32(self, proc32): diff --git a/windows/debug/breakpoints.py b/windows/debug/breakpoints.py index d8500f7..b7f4a45 100644 --- a/windows/debug/breakpoints.py +++ b/windows/debug/breakpoints.py @@ -73,8 +73,9 @@ class X64ArgumentRetriever(object): return proc.read_qword(thread.context.sp + 8 + (8 * nb)) ## Behaviour breakpoint ! -class FunctionParamDumpBP(Breakpoint): - def __init__(self, target=None, addr=None): +# class FunctionParamDumpBP(Breakpoint): +class FunctionParamDumpBPAbstract(object): + def __init__(self, addr=None, target=None): if target is None: try: target = self.TARGET @@ -82,7 +83,7 @@ class FunctionParamDumpBP(Breakpoint): raise ValueError("{0} bp without a must have a class attribute") if addr is None: addr = "{0}!{1}".format(target.target_dll, target.target_func) - super(FunctionParamDumpBP, self).__init__(addr) + super(FunctionParamDumpBPAbstract, self).__init__(addr) self.target = target self.target_args = target.prototype._argtypes_ self.target_params = target.params @@ -134,6 +135,11 @@ class FunctionParamDumpBP(Breakpoint): return self.extract_arguments_32bits(cproc, cthread) return self.extract_arguments_64bits(cproc, cthread) +class FunctionParamDumpBP(FunctionParamDumpBPAbstract, Breakpoint): + pass + +class FunctionParamDumpHXBP(FunctionParamDumpBPAbstract, HXBreakpoint): + pass class FunctionRetBP(Breakpoint): def __init__(self, addr, initial_breakpoint): diff --git a/windows/debug/debugger.py b/windows/debug/debugger.py index aed3b1e..918d5d7 100644 --- a/windows/debug/debugger.py +++ b/windows/debug/debugger.py @@ -295,8 +295,10 @@ class Debugger(object): raise ValueError("Cannot put {0} in {1} (DRx full)".format(bp, target)) empty_drx = str([pos for pos in range(4) if pos not in x][0]) ctx = target.context - ctx.EDr7.GE = 1 - ctx.EDr7.LE = 1 + # Windows DebugCtl aliasing in DR7 + # See https://www.codeproject.com/Articles/517466/Last-branch-records-and-branch-tracing + ctx.EDr7.LE = 0 # bit 8 of DR7 represents bit 0 of DebugCtl. This is the LBR bit. (last branch record, will explain) + ctx.EDr7.GE = 0 # bit 9 of DR7 represents bit 1 of DebugCtl. This is the BTF bit. (single-step on branches) setattr(ctx.EDr7, "L" + empty_drx, 1) setattr(ctx, "Dr" + empty_drx, addr) x[int(empty_drx)] = bp @@ -332,13 +334,15 @@ class Debugger(object): return PAGE_EXECUTE_READ if events == set("X"): # Might have problem if DEP is not enabled - if windows.winproxy.is_implemented(windows.winproxy.GetProcessDEPPolicy): + if target.bitness == 64: + has_DEP = True + elif windows.winproxy.is_implemented(windows.winproxy.GetProcessDEPPolicy): has_DEP = DWORD() permaned = LONG() windows.winproxy.GetProcessDEPPolicy(target.handle, has_DEP, permaned) has_DEP = has_DEP.value else: - has_DEP = 0 + has_DEP = False return PAGE_READWRITE if has_DEP else PAGE_NOACCESS raise ValueError("Unexpected set of event for Membp: {0}".format(events)) diff --git a/windows/injection.py b/windows/injection.py index dd47e0d..c459bdc 100644 --- a/windows/injection.py +++ b/windows/injection.py @@ -94,6 +94,11 @@ def perform_manual_getproc_loadlib_64(target, dll_name): raise InjectionFailedError("Injection of <{0}> failed".format(dll_name)) return True +def perform_manual_getproc_loadlib(target, *args, **kwargs): + if target.bitness == 32: + return perform_manual_getproc_loadlib_32(target, *args, **kwargs) + return perform_manual_getproc_loadlib_64(target, *args, **kwargs) + def load_dll_in_remote_process(target, dll_name): rpeb = target.peb diff --git a/windows/winobject/handle.py b/windows/winobject/handle.py index a514587..ca9af9b 100644 --- a/windows/winobject/handle.py +++ b/windows/winobject/handle.py @@ -56,7 +56,7 @@ class Handle(SYSTEM_HANDLE): winproxy.NtQueryObject(lh, ObjectTypeInformation, ctypes.byref(xxx), ctypes.sizeof(xxx), ctypes.byref(size_needed)) except WindowsError as e: if e.code != STATUS_INFO_LENGTH_MISMATCH: - print("ERROR WITH {0:x}".format(lh)) + # print("ERROR WITH {0:x}".format(lh)) raise size = size_needed.value buffer = ctypes.c_buffer(size)