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.
227 lines
8.3 KiB
Python
227 lines
8.3 KiB
Python
import os.path
|
|
import ctypes
|
|
from collections import namedtuple
|
|
|
|
import windows
|
|
from windows import winproxy
|
|
import windows.generated_def as gdef
|
|
|
|
|
|
def query_link(linkpath):
|
|
"""Resolve the link object with path ``linkpath``"""
|
|
obj_attr = gdef.OBJECT_ATTRIBUTES()
|
|
obj_attr.Length = ctypes.sizeof(obj_attr)
|
|
obj_attr.RootDirectory = 0
|
|
obj_attr.ObjectName = ctypes.pointer(gdef.LSA_UNICODE_STRING.from_string(linkpath))
|
|
obj_attr.Attributes = gdef.OBJ_CASE_INSENSITIVE
|
|
obj_attr.SecurityDescriptor = 0
|
|
obj_attr.SecurityQualityOfService = 0
|
|
res = gdef.HANDLE()
|
|
x = winproxy.NtOpenSymbolicLinkObject(res, gdef.DIRECTORY_QUERY | gdef.READ_CONTROL , obj_attr)
|
|
v = gdef.LSA_UNICODE_STRING.from_size(1000)
|
|
s = gdef.ULONG()
|
|
try:
|
|
winproxy.NtQuerySymbolicLinkObject(res, v, s)
|
|
except WindowsError as e:
|
|
if not (e.winerror & 0xffffffff) == gdef.STATUS_BUFFER_TOO_SMALL:
|
|
raise
|
|
# If our initial 1000 buffer is not enought (improbable) retry with correct size
|
|
v = gdef.LSA_UNICODE_STRING.from_size(s.value)
|
|
winproxy.NtQuerySymbolicLinkObject(res, v, s)
|
|
return v.str
|
|
|
|
|
|
class KernelObject(object):
|
|
"""Represent an object in the Object Manager namespace"""
|
|
def __init__(self, path, name, type=None):
|
|
self.path = path
|
|
self.name = name
|
|
if path and not path.endswith("\\"):
|
|
path += "\\"
|
|
self.fullname = path + name
|
|
self.type = type
|
|
|
|
@property
|
|
def target(self):
|
|
"""Resolve the target of a symbolic link object.
|
|
|
|
:rtype: :class:`str` or None if object is not a link
|
|
"""
|
|
try:
|
|
return query_link(self.fullname)
|
|
except windows.generated_def.ntstatus.NtStatusException as e:
|
|
if e.code != gdef.STATUS_OBJECT_TYPE_MISMATCH:
|
|
raise
|
|
return None
|
|
|
|
def items(self):
|
|
"""Return the list of tuple (object's name, object) in the current directory object.
|
|
|
|
:rtype: [(:class:`str`, :class:`KernelObject`)] -- A list of tuple
|
|
|
|
.. note::
|
|
|
|
the :class:`KernelObject` must be of type ``Directory`` or
|
|
it will raise :class:`~windows.generated_def.ntstatus.NtStatusException` with
|
|
code :data:`~windows.generated_def.STATUS_OBJECT_TYPE_MISMATCH`
|
|
"""
|
|
path = self.fullname
|
|
return [(name, KernelObject(path, name, typename)) for name, typename in self._directory_query_generator()]
|
|
|
|
def keys(self):
|
|
"""Return the list of objects' name in the current directory object.
|
|
|
|
:rtype: [:class:`str`] -- A list of name
|
|
|
|
.. note::
|
|
|
|
the :class:`KernelObject` must be of type ``Directory`` or
|
|
it will raise :class:`~windows.generated_def.ntstatus.NtStatusException` with
|
|
code :data:`~windows.generated_def.STATUS_OBJECT_TYPE_MISMATCH`
|
|
"""
|
|
return list(self)
|
|
|
|
def values(self):
|
|
"""Return the list of objects in the current directory object.
|
|
|
|
:rtype: [:class:`KernelObject`] -- A list of object
|
|
|
|
.. note::
|
|
|
|
the :class:`KernelObject` must be of type ``Directory`` or
|
|
it will raise :class:`~windows.generated_def.ntstatus.NtStatusException` with
|
|
code :data:`~windows.generated_def.STATUS_OBJECT_TYPE_MISMATCH`
|
|
"""
|
|
path = self.fullname
|
|
return [KernelObject(path, name, typename) for name, typename in self._directory_query_generator()]
|
|
|
|
def _open_directory(self):
|
|
path = self.fullname
|
|
utf16_len = len(path) * 2
|
|
obj_attr = gdef.OBJECT_ATTRIBUTES()
|
|
obj_attr.Length = ctypes.sizeof(obj_attr)
|
|
obj_attr.RootDirectory = None
|
|
obj_attr.ObjectName = ctypes.pointer(gdef.LSA_UNICODE_STRING.from_string(path))
|
|
obj_attr.Attributes = gdef.OBJ_CASE_INSENSITIVE
|
|
obj_attr.SecurityDescriptor = 0
|
|
obj_attr.SecurityQualityOfService = 0
|
|
res = gdef.HANDLE()
|
|
winproxy.NtOpenDirectoryObject(res, gdef.DIRECTORY_QUERY | gdef.READ_CONTROL , obj_attr)
|
|
return res.value
|
|
|
|
def _directory_query_generator(self):
|
|
handle = self._open_directory()
|
|
size = 0x1000
|
|
buf = ctypes.c_buffer(size)
|
|
rres = gdef.ULONG()
|
|
ctx = gdef.ULONG()
|
|
while True:
|
|
try:
|
|
# Restart == True has we don't save the buffer when resizing it for next call
|
|
winproxy.NtQueryDirectoryObject(handle, buf, size, False, True, ctypes.byref(ctx), rres)
|
|
break
|
|
except gdef.NtStatusException as e:
|
|
if e.code == gdef.STATUS_NO_MORE_ENTRIES:
|
|
return
|
|
if e.code == gdef.STATUS_MORE_ENTRIES:
|
|
# If the call did not extrack all data: retry with bigger buffer
|
|
size *= 2
|
|
buf = ctypes.c_buffer(size)
|
|
continue
|
|
raise
|
|
# Function -> _extract_objects ?
|
|
t = gdef.OBJECT_DIRECTORY_INFORMATION.from_buffer(buf)
|
|
t = gdef.POBJECT_DIRECTORY_INFORMATION(t)
|
|
res = {}
|
|
for v in t:
|
|
if v.Name.Buffer is None:
|
|
break
|
|
yield v.Name.str, v.TypeName.str
|
|
|
|
def __iter__(self):
|
|
"""Iter over the list of name in the Directory object.
|
|
|
|
:yield: :class:`str` -- The names of objects in the directory.
|
|
|
|
.. note::
|
|
|
|
the :class:`KernelObject` must be of type ``Directory`` or
|
|
it will raise :class:`~windows.generated_def.ntstatus.NtStatusException` with
|
|
code :data:`~windows.generated_def.STATUS_OBJECT_TYPE_MISMATCH`
|
|
"""
|
|
return (name for name, type in self._directory_query_generator())
|
|
|
|
def __repr__(self):
|
|
return """<{0} "{1}" (type="{2}")>""".format(type(self).__name__, self.fullname, self.type)
|
|
|
|
def get(self, name):
|
|
"""Retrieve the object ``name`` in the current directory.
|
|
|
|
:rtype: :class:`KernelObject`
|
|
"""
|
|
for objname, objtype in self._directory_query_generator():
|
|
if objname.lower() == name.lower():
|
|
return KernelObject(self.fullname, name, objtype)
|
|
raise KeyError("Could not find WinObject <{0}> under <{1}>".format(name, self.fullname))
|
|
|
|
def __getitem__(self, name):
|
|
"""Query object ``name`` from the directory, split and subquery on ``\\``::
|
|
|
|
>>> obj
|
|
<KernelObject "\Windows" (type="Directory")>
|
|
>>> obj["WindowStations"]["WinSta0"]
|
|
<KernelObject "\Windows\WindowStations" (type="Directory")>
|
|
>>> obj["WindowStations\\WinSta0"]
|
|
<KernelObject "\Windows\WindowStations" (type="Directory")>
|
|
|
|
:rtype: :class:`KernelObject`
|
|
:raise: :class:`KeyError` if ``name`` can not be found.
|
|
"""
|
|
if name.startswith("\\"):
|
|
# Are we the root directory ?
|
|
if not self.fullname == "\\" :
|
|
raise ValueError("Cannot query an object path begining by '\\' from an object other than '\\'")
|
|
elif name == "\\": # Ask for root ? return ourself
|
|
return self
|
|
else:
|
|
name = name[1:] # Strip the leading \ and go to normal case
|
|
obj = self
|
|
for part in name.split("\\"):
|
|
try:
|
|
obj = obj.get(part)
|
|
except gdef.NtStatusException as e:
|
|
if e.code == gdef.STATUS_OBJECT_TYPE_MISMATCH:
|
|
raise KeyError("Could not find object <{0}> under <{1}> because it is a <{2}>".format(
|
|
part, obj.name, obj.type))
|
|
raise # Something smart to do ?
|
|
return obj
|
|
|
|
|
|
class ObjectManager(object):
|
|
"""Represent the object manager.
|
|
|
|
.. note::
|
|
|
|
For now, it only offers the ``root`` :class:`KernelObject`. But I want a ``manager`` object accessible
|
|
from ``windows.system`` just like other API and not directly the ``root`` directory.
|
|
"""
|
|
|
|
@property
|
|
def root(self):
|
|
"""The root ``\\`` Directory
|
|
|
|
:type: :class:`KernelObject` -- The root :class:`KernelObject`
|
|
"""
|
|
return KernelObject("", "\\", "Directory")
|
|
|
|
def __getitem__(self, name):
|
|
"""Query ``name`` from the root ``\\`` directory::
|
|
|
|
object_manager["RPC Control"]["lsasspirpc"]
|
|
object_manager[r"\\RPC Control\\lsasspirpc"]
|
|
|
|
:rtype: :class:`KernelObject`
|
|
"""
|
|
return self.root[name]
|
|
|