mirror of
https://github.com/naksyn/PythonMemoryModule
synced 2026-06-06 16:24:25 +00:00
command line support (partial) via PEB stomping
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.
This commit is contained in:
@@ -0,0 +1,355 @@
|
||||
import sys
|
||||
import struct
|
||||
import ctypes
|
||||
import functools
|
||||
from ctypes import HRESULT, byref, cast
|
||||
|
||||
import windows
|
||||
from windows import winproxy
|
||||
from windows.generated_def.winstructs import *
|
||||
|
||||
import windows.generated_def as gdef
|
||||
from windows.generated_def import RPC_C_IMP_LEVEL_IMPERSONATE, CLSCTX_INPROC_SERVER
|
||||
from windows.generated_def import interfaces
|
||||
from windows.generated_def.interfaces import generate_IID, IID
|
||||
|
||||
from windows.pycompat import int_types, basestring
|
||||
|
||||
# We have windows.com.COMImplementation
|
||||
# So we need windows.com.COMInterface
|
||||
COMInterface = interfaces.COMInterface
|
||||
|
||||
# Simple raw -> UUID
|
||||
# "-".join("{:02X}".format(c) for c in struct.unpack("<IHHHBBBBBB", x))
|
||||
|
||||
def init():
|
||||
"""Init COM with some default parameters"""
|
||||
try:
|
||||
t = winproxy.CoInitializeEx()
|
||||
except WindowsError as e:
|
||||
t = e.winerror
|
||||
if t:
|
||||
return t & 0xffffffff
|
||||
return initsecurity()
|
||||
|
||||
def initsecurity(): # Should take some parameters..
|
||||
return winproxy.CoInitializeSecurity(0, -1, None, 0, 0, RPC_C_IMP_LEVEL_IMPERSONATE, 0,0,0)
|
||||
|
||||
|
||||
class Dispatch(interfaces.IDispatch):
|
||||
def TypeInfoCount(self):
|
||||
count = gdef.UINT()
|
||||
self.GetTypeInfoCount(count)
|
||||
return count
|
||||
|
||||
def type_info(self, idx):
|
||||
type_info = TypeInfo()
|
||||
self.GetTypeInfo(idx, 0, type_info)
|
||||
return type_info
|
||||
|
||||
class TypeInfo(interfaces.ITypeInfo):
|
||||
def func(self, idx):
|
||||
res = gdef.LPFUNCDESC()
|
||||
self.GetFuncDesc(idx, res)
|
||||
return res
|
||||
|
||||
def attr(self):
|
||||
res = gdef.LPTYPEATTR()
|
||||
self.GetTypeAttr(res)
|
||||
return res
|
||||
|
||||
def names(self, memid):
|
||||
size = gdef.UINT()
|
||||
x = (gdef.BSTR * 10)(*tuple(gdef.BSTR() for i in range(10)))
|
||||
self.GetNames(memid, x, 10, size)
|
||||
return x[:size.value]
|
||||
|
||||
def docu(self, id):
|
||||
res = gdef.BSTR()
|
||||
self.GetDocumentation(id, res, None, None, None)
|
||||
return res
|
||||
|
||||
def create_instance(clsiid, targetinterface, custom_iid=None, context=CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER):
|
||||
"""A simple wrapper around ``CoCreateInstance <https://msdn.microsoft.com/en-us/library/windows/desktop/ms686615(v=vs.85).aspx>``"""
|
||||
if custom_iid is None:
|
||||
custom_iid = targetinterface.IID
|
||||
if isinstance(clsiid, basestring):
|
||||
clsiid = IID.from_string(clsiid)
|
||||
winproxy.CoCreateInstance(byref(clsiid), None, context, byref(custom_iid), byref(targetinterface))
|
||||
return targetinterface
|
||||
|
||||
|
||||
def resolve_progid(progid):
|
||||
clsid = CLSID()
|
||||
winproxy.CLSIDFromProgID(progid, clsid)
|
||||
# We just filed the CLSID: refresh the __repr__
|
||||
clsid.update_strid()
|
||||
return clsid
|
||||
|
||||
# Improved COM object
|
||||
# Todo: ctypes_generation extended struct ?
|
||||
class SafeArray(SAFEARRAY):
|
||||
@classmethod
|
||||
def of_type(cls, addr, t):
|
||||
self = cls.from_address(addr)
|
||||
self.elt_type = t
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def from_PSAFEARRAY(self, psafearray):
|
||||
res = cast(psafearray, POINTER(SafeArray))[0]
|
||||
return res
|
||||
|
||||
def to_list(self, t=None):
|
||||
if t is None:
|
||||
if hasattr(self, "elt_type"):
|
||||
t = self.elt_type
|
||||
else:
|
||||
raise ValueError("Missing type of the array")
|
||||
if self.cDims != 1:
|
||||
raise NotImplementedError("tagSAFEARRAY if dims != 1")
|
||||
|
||||
nb_element = self.rgsabound[0].cElements
|
||||
llbound = self.rgsabound[0].lLbound
|
||||
if self.cbElements != ctypes.sizeof(t):
|
||||
raise ValueError("Size of elements != sizeof(type)")
|
||||
data = [t.from_address(self.pvData + (i + llbound) * ctypes.sizeof(t)).value for i in range(nb_element)]
|
||||
return data
|
||||
|
||||
#VT_VALUE_TO_TYPE = {
|
||||
#VT_I2 : SHORT,
|
||||
#VT_I4 : LONG,
|
||||
#VT_BSTR : BSTR,
|
||||
#VT_VARIANT : VARIANT,
|
||||
#VT_UI1 : UCHAR,
|
||||
#VT_UI2 : USHORT,
|
||||
#VT_UI4 : DWORD,
|
||||
#VT_I8 : LONGLONG,
|
||||
#VT_UI8 : ULONG64,
|
||||
#VT_INT : INT,
|
||||
#VT_UINT : UINT,
|
||||
#VT_HRESULT : HRESULT,
|
||||
#VT_PTR : PVOID,
|
||||
#VT_LPSTR : LPCSTR,
|
||||
#VT_LPWSTR : LPWSTR,
|
||||
#}
|
||||
|
||||
# VARIANT type checker
|
||||
# Allow to guess a VARIANT_TYPE og a python value
|
||||
|
||||
def never_match(value):
|
||||
return False
|
||||
|
||||
def check_type_null(value):
|
||||
return value is None
|
||||
|
||||
def check_type_i4(value):
|
||||
# 31 ? as we may want to keep sign :)
|
||||
return isinstance(value, int_types) and (value).bit_length() <= 32
|
||||
|
||||
def check_type_i8(value):
|
||||
# 63 ? as we may want to keep sign :)
|
||||
return isinstance(value, int_types) and (value).bit_length() <= 64
|
||||
|
||||
def check_type_bstr(value):
|
||||
return isinstance(value, basestring)
|
||||
|
||||
def check_type_bool(value):
|
||||
return isinstance(value, bool)
|
||||
|
||||
def check_type_array(value):
|
||||
return True
|
||||
|
||||
|
||||
VARIAN_NAME_3_TYPE = [f[1] for f in VARIANT._fields_ if f[0] == "_VARIANT_NAME_3"][0]
|
||||
|
||||
empty = object()
|
||||
class Variant(VARIANT):
|
||||
def __init__(self, value=empty, type=None):
|
||||
if type is not None:
|
||||
self.set_value_and_type(value, type)
|
||||
return
|
||||
elif value is empty:
|
||||
self.vt = VT_EMPTY
|
||||
return
|
||||
self.guess_type_and_set_value(value)
|
||||
|
||||
# Copy raw-ctypes fields which is a descriptor :)
|
||||
rawvt = VARIANT.vt
|
||||
|
||||
# Most of the value in the colunm[1]
|
||||
# are attribute of the sub-union _VARIANT_NAME_3
|
||||
# This union must be ctypes-anonymous for this code to works
|
||||
# We want to access these directly from the VARIANT
|
||||
# to allow custom descriptor for complexe type to be referenced here
|
||||
CHECK_TYPE = [
|
||||
# Order is important
|
||||
# as VT_I4 check may match VT_BOOL values
|
||||
# VT_BOOL check must be before VT_I4 one
|
||||
(VT_BOOL, "boolVal", check_type_bool),
|
||||
(VT_I4, "lVal", check_type_i4),
|
||||
(VT_I8, "llVal", check_type_i8),
|
||||
(VT_BSTR, "bstrVal", check_type_bstr),
|
||||
(VT_NULL, None, check_type_null),
|
||||
(VT_EMPTY, None, never_match),
|
||||
(VT_DISPATCH, "pdispVal", never_match), # I cannot recognize DISPATCH ptr for now
|
||||
(VT_UNKNOWN, "punkVal", never_match), # recognise PFW ComInterface ?
|
||||
# Test: do not allow auto-creation of small int values
|
||||
# I don't know but a feel it may confuse some API expecting VT_I4
|
||||
(VT_I2, "iVal", never_match),
|
||||
(VT_UI1, "bVal", never_match),
|
||||
]
|
||||
|
||||
VARIANT_TYPE_BY_NAME = {f[0]: f[1] for f in VARIAN_NAME_3_TYPE._fields_}
|
||||
QUICK_CHECK_TYPE = {x: y for x,y, _ in CHECK_TYPE}
|
||||
|
||||
def get_vt(self):
|
||||
rawvt = super(Variant, self).vt
|
||||
return gdef.VARENUM.mapper[self.rawvt]
|
||||
|
||||
def set_vt(self, value):
|
||||
self.rawvt = value
|
||||
|
||||
vt = property(get_vt, set_vt)
|
||||
|
||||
def set_value_and_type(self, value, type):
|
||||
attr = self.QUICK_CHECK_TYPE[type]
|
||||
# No check: user must be careful about non-match value&type
|
||||
setattr(self, attr, value)
|
||||
self.vt = type
|
||||
|
||||
def get_value_based_on_type(self):
|
||||
rawvt = self.rawvt
|
||||
if rawvt & VT_ARRAY:
|
||||
realtype = rawvt & ~VT_ARRAY
|
||||
attr = self.QUICK_CHECK_TYPE[realtype]
|
||||
attrtype = self.VARIANT_TYPE_BY_NAME[attr]
|
||||
array = SafeArray.from_PSAFEARRAY(self._VARIANT_NAME_3.parray)
|
||||
return array.to_list(attrtype)
|
||||
attr = self.QUICK_CHECK_TYPE[rawvt]
|
||||
if attr is None:
|
||||
return None
|
||||
if attr == "punkVal":
|
||||
# Quick hack for COM interface type
|
||||
# Do something clean with CHECK_TYPE ?
|
||||
x = gdef.IUnknown(self.punkVal)
|
||||
x.AddRef()
|
||||
return x
|
||||
return getattr(self, attr)
|
||||
|
||||
def guess_type_and_set_value(self, value):
|
||||
for t, attr, check in self.CHECK_TYPE:
|
||||
try:
|
||||
checkres = check(value)
|
||||
except TypeError as e:
|
||||
continue
|
||||
if checkres:
|
||||
self.vt = t
|
||||
if attr is not None:
|
||||
setattr(self, attr, value)
|
||||
return True
|
||||
raise ValueError("Could not guess VT_TYPE for <{0}> of type <{1}>".format(value, type(value)))
|
||||
|
||||
value = property(get_value_based_on_type, guess_type_and_set_value)
|
||||
|
||||
# quick_check: bypass python lookup-limitation
|
||||
def generate_getter(vt_type, transfo=(lambda x:x), quick_check=QUICK_CHECK_TYPE):
|
||||
attr = quick_check[vt_type]
|
||||
@property
|
||||
def getter(self):
|
||||
if not self.rawvt == vt_type:
|
||||
raise ValueError("Invalid vt-type for attribute expected <{0}> got <{1}>".format(vt_type, self.vt))
|
||||
return transfo(getattr(self, attr))
|
||||
return getter
|
||||
|
||||
asbstr = generate_getter(VT_BSTR)
|
||||
aslong = generate_getter(VT_I4)
|
||||
asbool = generate_getter(VT_BOOL)
|
||||
asdispatch = generate_getter(VT_DISPATCH, transfo=interfaces.IDispatch)
|
||||
asshort = generate_getter(VT_I2)
|
||||
asbyte = generate_getter(VT_UI1)
|
||||
asunknown = generate_getter(VT_UNKNOWN)
|
||||
|
||||
def __repr__(self):
|
||||
return """<{0} of type {1}>""".format(type(self).__name__, self.vt)
|
||||
|
||||
# Deprecated: remove me when test pass :)
|
||||
# ImprovedVariant.MAPPER = {
|
||||
# VT_UI1: ImprovedVariant.asbyte.fget,
|
||||
# VT_I2: ImprovedVariant.asshort.fget,
|
||||
# VT_DISPATCH: ImprovedVariant.asdispatch.fget,
|
||||
# VT_BOOL: ImprovedVariant.asbool.fget,
|
||||
# VT_I4: ImprovedVariant.aslong.fget,
|
||||
# VT_BSTR: ImprovedVariant.asbstr.fget,
|
||||
# VT_EMPTY: (lambda x: None),
|
||||
# VT_NULL: (lambda x: None),
|
||||
# VT_UNKNOWN: ImprovedVariant.asunknown.fget,
|
||||
# (VT_ARRAY | VT_BSTR): ImprovedVariant.asbstr_array.fget,
|
||||
# (VT_ARRAY | VT_I4): ImprovedVariant.aslong_array.fget,
|
||||
# (VT_ARRAY | VT_UI1): ImprovedVariant.asbyte_array.fget,
|
||||
# (VT_ARRAY | VT_BOOL): ImprovedVariant.asbool_array.fget
|
||||
# }
|
||||
|
||||
|
||||
|
||||
class COMImplementation(object):
|
||||
"""The base class to implements COM object respecting a given interface"""
|
||||
IMPLEMENT = None
|
||||
|
||||
def get_index_of_method(self, method):
|
||||
# This code is horrible but not totally my fault
|
||||
# the PyCFuncPtrObject->index is not exposed to Python..
|
||||
# repr is: '<COM method offset 2: WinFunctionType at 0x035DDBE8>'
|
||||
rpr = repr(method)
|
||||
if not rpr.startswith("<COM method offset ") or ":" not in rpr:
|
||||
raise ValueError("Could not extract offset of {0}".format(rpr))
|
||||
return int(rpr[len("<COM method offset "): rpr.index(":")])
|
||||
|
||||
def extract_methods_order(self, interface):
|
||||
index_and_method = sorted((self.get_index_of_method(m),name, m) for name, m in interface._functions_.items())
|
||||
return index_and_method
|
||||
|
||||
def verify_implem(self, interface):
|
||||
for func_name in interface._functions_:
|
||||
implem = getattr(self, func_name, None)
|
||||
if implem is None:
|
||||
raise ValueError("<{0}> implementing <{1}> has no method <{2}>".format(type(self).__name__, self.IMPLEMENT.__name__, func_name))
|
||||
if not callable(implem):
|
||||
raise ValueError("{0} implementing <{1}>: <{2}> is not callable".format(type(self).__name__, self.IMPLEMENT.__name__, func_name))
|
||||
return True
|
||||
|
||||
def _create_vtable(self, interface):
|
||||
implems = []
|
||||
names = []
|
||||
for index, name, method in self.extract_methods_order(interface):
|
||||
func_implem = getattr(self, name)
|
||||
#'this' is a COM-interface of the type we are implementing
|
||||
types = [method.restype, interface] + list(method.argtypes)
|
||||
implems.append(ctypes.WINFUNCTYPE(*types)(func_implem))
|
||||
names.append(name)
|
||||
class Vtable(ctypes.Structure):
|
||||
_fields_ = [(name, ctypes.c_void_p) for name in names]
|
||||
return Vtable(*[ctypes.cast(x, ctypes.c_void_p) for x in implems]), implems
|
||||
|
||||
def __init__(self):
|
||||
self.verify_implem(self.IMPLEMENT)
|
||||
vtable, implems = self._create_vtable(self.IMPLEMENT)
|
||||
self.vtable = vtable
|
||||
self.implems = implems
|
||||
self.vtable_pointer = ctypes.pointer(self.vtable)
|
||||
self._as_parameter_ = ctypes.addressof(self.vtable_pointer)
|
||||
|
||||
def QueryInterface(self, this, piid, result):
|
||||
"""Default ``QueryInterface`` implementation that returns ``self`` if piid is the implemented interface"""
|
||||
if piid[0] in (gdef.IUnknown.IID, self.IMPLEMENT.IID):
|
||||
result[0] = this
|
||||
return 1
|
||||
return E_NOINTERFACE
|
||||
|
||||
def AddRef(self, *args):
|
||||
"""Default ``AddRef`` implementation that returns ``1``"""
|
||||
return 1
|
||||
|
||||
def Release(self, *args):
|
||||
"""Default ``Release`` implementation that returns ``1``"""
|
||||
return 0
|
||||
Reference in New Issue
Block a user