mirror of
https://github.com/naksyn/PythonMemoryModule
synced 2026-06-06 16:24:25 +00:00
db1893910c
This update include support to passing command line parameters to unmanaged exe via PEB stomping. This technique is not working with every executable since it depends on which functions are used to pass arguments. Generally, to get a universally working technique would be required to hook GetCommandlineA GetCommandlineW __getmainargs and __wgetmainargs since PEB stomping won't cover all cases, more details here: https://blog-30cm-tw.translate.goog/2020/08/windows-c-mainargc-argv.html?_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=it&_x_tr_pto=wapp However, during my testing I found that mimikatz and several go binaries are working just by doing PEB stomping. On the other hand, cmdline passing via PEB stomping alone to mingw and VS compiled binaries won't likely work.
376 lines
12 KiB
Python
376 lines
12 KiB
Python
import ctypes
|
|
import windows
|
|
from windows.generated_def.winstructs import *
|
|
import windows.generated_def.windef as windef
|
|
|
|
EXCEPTION_CONTINUE_SEARCH = (0x0)
|
|
EXCEPTION_CONTINUE_EXECUTION = (0xffffffff)
|
|
|
|
exception_type = [
|
|
"EXCEPTION_ACCESS_VIOLATION",
|
|
"EXCEPTION_DATATYPE_MISALIGNMENT",
|
|
"EXCEPTION_BREAKPOINT",
|
|
"EXCEPTION_SINGLE_STEP",
|
|
"EXCEPTION_ARRAY_BOUNDS_EXCEEDED",
|
|
"EXCEPTION_FLT_DENORMAL_OPERAND",
|
|
"EXCEPTION_FLT_DIVIDE_BY_ZERO",
|
|
"EXCEPTION_FLT_INEXACT_RESULT",
|
|
"EXCEPTION_FLT_INVALID_OPERATION",
|
|
"EXCEPTION_FLT_OVERFLOW",
|
|
"EXCEPTION_FLT_STACK_CHECK",
|
|
"EXCEPTION_FLT_UNDERFLOW",
|
|
"EXCEPTION_INT_DIVIDE_BY_ZERO",
|
|
"EXCEPTION_INT_OVERFLOW",
|
|
"EXCEPTION_PRIV_INSTRUCTION",
|
|
"EXCEPTION_IN_PAGE_ERROR",
|
|
"EXCEPTION_ILLEGAL_INSTRUCTION",
|
|
"EXCEPTION_NONCONTINUABLE_EXCEPTION",
|
|
"EXCEPTION_STACK_OVERFLOW",
|
|
"EXCEPTION_INVALID_DISPOSITION",
|
|
"EXCEPTION_GUARD_PAGE",
|
|
"EXCEPTION_INVALID_HANDLE",
|
|
"EXCEPTION_POSSIBLE_DEADLOCK",
|
|
]
|
|
|
|
# x -> x dict may seems strange but useful to get the Flags (with name) from the int
|
|
# exception_name_by_value[0x80000001] -> EXCEPTION_GUARD_PAGE(0x80000001L)
|
|
exception_name_by_value = dict([(x, x) for x in [getattr(windows.generated_def.windef, name) for name in exception_type]])
|
|
|
|
class EEXCEPTION_RECORDBase(object):
|
|
@property
|
|
def ExceptionCode(self):
|
|
"""The Exception code
|
|
|
|
:type: :class:`int`"""
|
|
real_code = super(EEXCEPTION_RECORDBase, self).ExceptionCode
|
|
return exception_name_by_value.get(real_code, windows.generated_def.windef.Flag("UNKNOW_EXCEPTION", real_code))
|
|
|
|
@property
|
|
def ExceptionAddress(self):
|
|
"""The Exception Address
|
|
|
|
:type: :class:`int`"""
|
|
x = super(EEXCEPTION_RECORDBase, self).ExceptionAddress
|
|
if x is None:
|
|
return 0x0
|
|
return x
|
|
|
|
class EEXCEPTION_RECORD(EEXCEPTION_RECORDBase, EXCEPTION_RECORD):
|
|
"""Enhanced exception record"""
|
|
|
|
fields = [f[0] for f in EXCEPTION_RECORD._fields_]
|
|
"""The fields of the structure"""
|
|
|
|
class EEXCEPTION_RECORD32(EEXCEPTION_RECORDBase, EXCEPTION_RECORD32):
|
|
"""Enhanced exception record (32bits)"""
|
|
|
|
fields = [f[0] for f in EXCEPTION_RECORD32._fields_]
|
|
"""The fields of the structure"""
|
|
|
|
class EEXCEPTION_RECORD64(EEXCEPTION_RECORDBase, EXCEPTION_RECORD64):
|
|
"""Enhanced exception record (64bits)"""
|
|
|
|
fields = [f[0] for f in EXCEPTION_RECORD64._fields_]
|
|
"""The fields of the structure"""
|
|
|
|
|
|
class EEXCEPTION_DEBUG_INFO32(ctypes.Structure):
|
|
"""Enhanced Debug info"""
|
|
_fields_ = windows.utils.transform_ctypes_fields(EXCEPTION_DEBUG_INFO, {"ExceptionRecord": EEXCEPTION_RECORD32})
|
|
|
|
fields = [f[0] for f in _fields_]
|
|
"""The fields of the structure"""
|
|
|
|
class EEXCEPTION_DEBUG_INFO64(ctypes.Structure):
|
|
"""Enhanced Debug info"""
|
|
_fields_ = windows.utils.transform_ctypes_fields(EXCEPTION_DEBUG_INFO, {"ExceptionRecord": EEXCEPTION_RECORD64})
|
|
|
|
fields = [f[0] for f in _fields_]
|
|
"""The fields of the structure"""
|
|
|
|
|
|
class EEflags(ctypes.Structure):
|
|
"Flag view of the Eflags register"
|
|
_fields_ = [("CF", DWORD, 1),
|
|
("RES_1", DWORD, 1),
|
|
("PF", DWORD, 1),
|
|
("RES_3", DWORD, 1),
|
|
("AF", DWORD, 1),
|
|
("RES_5", DWORD, 1),
|
|
("ZF", DWORD, 1),
|
|
("SF", DWORD, 1),
|
|
("TF", DWORD, 1),
|
|
("IF", DWORD, 1),
|
|
("DF", DWORD, 1),
|
|
("OF", DWORD, 1),
|
|
("IOPL_1", DWORD, 1),
|
|
("IOPL_2", DWORD, 1),
|
|
("NT", DWORD, 1),
|
|
("RES_15", DWORD, 1),
|
|
("RF", DWORD, 1),
|
|
("VM", DWORD, 1),
|
|
("AC", DWORD, 1),
|
|
("VIF", DWORD, 1),
|
|
("VIP", DWORD, 1),
|
|
("ID", DWORD, 1),
|
|
]
|
|
|
|
fields = [f[0] for f in _fields_]
|
|
"""The fields of the structure"""
|
|
|
|
def get_raw(self):
|
|
x = DWORD.from_address(ctypes.addressof(self))
|
|
return x.value
|
|
|
|
def set_raw(self, value):
|
|
x = DWORD.from_address(ctypes.addressof(self))
|
|
x.value = value
|
|
return None
|
|
|
|
def dump(self):
|
|
res = []
|
|
for name in [x[0] for x in self._fields_]:
|
|
if name.startswith("RES_"):
|
|
continue
|
|
if getattr(self, name):
|
|
res.append(name)
|
|
return "|".join(res)
|
|
|
|
def __repr__(self):
|
|
return hex(self)
|
|
|
|
def __hex__(self):
|
|
if self.raw == 0:
|
|
return "{0}({1})".format(type(self).__name__, hex(self.raw))
|
|
return "{0}({1}:{2})".format(type(self).__name__, hex(self.raw), self.dump())
|
|
|
|
raw = property(get_raw, set_raw)
|
|
"""Raw value of the eflags
|
|
|
|
:type: :class:`int`
|
|
"""
|
|
|
|
|
|
class EDr7(ctypes.Structure):
|
|
"Flag view of the DR7 register"
|
|
_fields_ = [("L0", DWORD, 1),
|
|
("G0", DWORD, 1),
|
|
("L1", DWORD, 1),
|
|
("G1", DWORD, 1),
|
|
("L2", DWORD, 1),
|
|
("G2", DWORD, 1),
|
|
("L3", DWORD, 1),
|
|
("G3", DWORD, 1),
|
|
("LE", DWORD, 1),
|
|
("GE", DWORD, 1),
|
|
("RES_1", DWORD, 3),
|
|
("GD", DWORD, 1),
|
|
("RES_1", DWORD, 2),
|
|
("RW0", DWORD, 2),
|
|
("LEN0", DWORD, 2),
|
|
("RW1", DWORD, 2),
|
|
("LEN1", DWORD, 2),
|
|
("RW2", DWORD, 2),
|
|
("LEN2", DWORD, 2),
|
|
("RW3", DWORD, 2),
|
|
("LEN3", DWORD, 2),
|
|
]
|
|
|
|
fields = [f[0] for f in _fields_]
|
|
"""The fields of the structure"""
|
|
|
|
class ECONTEXTBase(object):
|
|
"""DAT CONTEXT"""
|
|
default_dump = ()
|
|
pc_reg = ''
|
|
sp_reg = ''
|
|
func_result_reg = ''
|
|
special_reg_type = {}
|
|
|
|
|
|
def regs(self, to_dump=None):
|
|
"""Return the name and values of the registers
|
|
|
|
:returns: [(reg_name, value)] -- A :class:`list` of :class:`tuple`"""
|
|
res = []
|
|
if to_dump is None:
|
|
to_dump = self.default_dump
|
|
for name in to_dump:
|
|
value = getattr(self, name)
|
|
if name in self.special_reg_type:
|
|
value = self.special_reg_type[name](value)
|
|
res.append((name, value))
|
|
return res
|
|
|
|
def dump(self, to_dump=None):
|
|
"""Dump (print) the current context"""
|
|
regs = self.regs()
|
|
for name, value in regs:
|
|
print("{0} -> {1}".format(name, hex(value)))
|
|
return None
|
|
|
|
def get_pc(self):
|
|
return getattr(self, self.pc_reg)
|
|
|
|
def set_pc(self, value):
|
|
return setattr(self, self.pc_reg, value)
|
|
|
|
def get_sp(self):
|
|
return getattr(self, self.sp_reg)
|
|
|
|
def set_sp(self, value):
|
|
return setattr(self, self.sp_reg, value)
|
|
|
|
def get_func_result(self):
|
|
return getattr(self, self.func_result_reg)
|
|
|
|
def set_func_result(self, value):
|
|
return setattr(self, self.func_result_reg, value)
|
|
|
|
pc = property(get_pc, set_pc, None, "Program Counter register (EIP or RIP)")
|
|
sp = property(get_sp, set_sp, None, "Stack Pointer register (ESP or RSP)")
|
|
func_result = property(get_func_result, set_func_result, None, "Function Resultat register (EAX or RAX)")
|
|
|
|
@property
|
|
def EEFlags(self):
|
|
"""Enhanced view of the Eflags (you also have ``EFlags`` for the raw value)
|
|
|
|
:type: :class:`EEflags`
|
|
"""
|
|
off = type(self).EFlags.offset
|
|
x = EEflags.from_address(ctypes.addressof(self) + off)
|
|
x.self = self
|
|
return x
|
|
|
|
@property
|
|
def EDr7(self):
|
|
"""Enhanced view of the DR7 register (you also have ``Dr7`` for the raw value)
|
|
|
|
:type: :class:`EDr7`
|
|
"""
|
|
off = type(self).Dr7.offset
|
|
x = EDr7.from_address(ctypes.addressof(self) + off)
|
|
x.self = self
|
|
return x
|
|
|
|
class ECONTEXT32(ECONTEXTBase, CONTEXT32):
|
|
default_dump = ('Eip', 'Esp', 'Eax', 'Ebx', 'Ecx', 'Edx', 'Ebp', 'Edi', 'Esi', 'EFlags')
|
|
pc_reg = 'Eip'
|
|
sp_reg = 'Esp'
|
|
func_result_reg = 'Eax'
|
|
fields = [f[0] for f in CONTEXT32._fields_]
|
|
"""The fields of the structure"""
|
|
|
|
class ECONTEXTWOW64(ECONTEXTBase, WOW64_CONTEXT):
|
|
default_dump = ('Eip', 'Esp', 'Eax', 'Ebx', 'Ecx', 'Edx', 'Ebp', 'Edi', 'Esi', 'EFlags')
|
|
pc_reg = 'Eip'
|
|
sp_reg = 'Esp'
|
|
func_result_reg = 'Eax'
|
|
fields = [f[0] for f in WOW64_CONTEXT._fields_]
|
|
"""The fields of the structure"""
|
|
|
|
|
|
class ECONTEXT64(ECONTEXTBase, CONTEXT64):
|
|
default_dump = ('Rip', 'Rsp', 'Rax', 'Rbx', 'Rcx', 'Rdx', 'Rbp', 'Rdi', 'Rsi',
|
|
'R8', 'R9', 'R10', 'R11', 'R12', 'R13', 'R14', 'R15', 'EFlags')
|
|
pc_reg = 'Rip'
|
|
sp_reg = 'Rsp'
|
|
func_result_reg = 'Rax'
|
|
fields = [f[0] for f in CONTEXT64._fields_]
|
|
"""The fields of the structure"""
|
|
|
|
@classmethod
|
|
def new_aligned(cls):
|
|
"""Return a new :class:`ECONTEXT64` aligned on 16 bits
|
|
|
|
temporary workaround or horrible hack ? choose your side
|
|
"""
|
|
size = ctypes.sizeof(cls)
|
|
nb_qword = int((size + 8) / ctypes.sizeof(ULONGLONG))
|
|
buffer = (nb_qword * ULONGLONG)()
|
|
struct_address = ctypes.addressof(buffer)
|
|
if (struct_address & 0xf) not in [0, 8]:
|
|
raise ValueError("ULONGLONG array not aligned on 8")
|
|
if (struct_address & 0xf) == 8:
|
|
struct_address += 8
|
|
self = cls.from_address(struct_address)
|
|
# Keep the raw buffer alive
|
|
self._buffer = buffer
|
|
return self
|
|
|
|
def bitness():
|
|
"""Return 32 or 64"""
|
|
import platform
|
|
bits = platform.architecture()[0]
|
|
return int(bits[:2])
|
|
|
|
if bitness() == 32:
|
|
ECONTEXT = ECONTEXT32
|
|
else:
|
|
ECONTEXT = ECONTEXT64
|
|
|
|
|
|
class EEXCEPTION_POINTERS(ctypes.Structure):
|
|
_fields_ = [
|
|
("ExceptionRecord", ctypes.POINTER(EEXCEPTION_RECORD)),
|
|
("ContextRecord", ctypes.POINTER(ECONTEXT)),
|
|
]
|
|
|
|
def dump(self):
|
|
"""Dump (print) the EEXCEPTION_POINTERS"""
|
|
record = self.ExceptionRecord[0]
|
|
print("Dumping Exception: ")
|
|
print(" ExceptionCode = {0} at {1}".format(record.ExceptionCode, hex(record.ExceptionAddress)))
|
|
regs = self.ContextRecord[0].regs()
|
|
for name, value in regs:
|
|
print(" {0} -> {1}".format(name, hex(value)))
|
|
|
|
|
|
class VectoredException(object):
|
|
"""A decorator that create a callable which can be passed to :func:`AddVectoredExceptionHandler`"""
|
|
func_type = ctypes.WINFUNCTYPE(ctypes.c_uint, ctypes.POINTER(EEXCEPTION_POINTERS))
|
|
|
|
def __new__(cls, func):
|
|
self = object.__new__(cls)
|
|
self.func = func
|
|
v = self.func_type(self.decorator)
|
|
v.self = self
|
|
return v
|
|
|
|
def decorator(self, exception_pointers):
|
|
try:
|
|
return self.func(exception_pointers)
|
|
except BaseException as e:
|
|
import traceback
|
|
print("Ignored Python Exception in Vectored Exception: {0}".format(e))
|
|
traceback.print_exc()
|
|
return windef.EXCEPTION_CONTINUE_SEARCH
|
|
|
|
|
|
class VectoredExceptionHandler(object):
|
|
def __init__(self, pos, handler):
|
|
self.handler = VectoredException(handler)
|
|
self.pos = pos
|
|
|
|
def __enter__(self):
|
|
self.value = windows.winproxy.AddVectoredExceptionHandler(self.pos, self.handler)
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
windows.winproxy.RemoveVectoredExceptionHandler(self.value)
|
|
return False
|
|
|
|
class DumpContextOnException(VectoredExceptionHandler):
|
|
def __init__(self, exit=False):
|
|
self.exit = exit
|
|
super(DumpContextOnException, self).__init__(self.print_context_result)
|
|
|
|
def print_context_result(self, exception_pointers):
|
|
except_record = exception_pointers[0].ExceptionRecord[0]
|
|
exception_pointers[0].dump()
|
|
sys.stdout.flush()
|
|
if self.exit:
|
|
windows.current_process.exit()
|
|
return 0
|
|
|