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:
naksyn
2023-07-27 06:44:29 -07:00
parent 63ebe1c4ba
commit db1893910c
160 changed files with 70537 additions and 42 deletions
@@ -0,0 +1,134 @@
import sys
import ctypes
import _ctypes
import windows.generated_def as gdef
from windows.pycompat import basestring
## TESTING Improved Buffer code ###
## This code is not stable and WILL CHANGE ##
## Do not use for now :) ##
# Uses cases:
# Simple buffer
# String / Wstring / Bytes
# Resize array in struct
# Typed buffer : filed call & contains X struct S
# Having a ptr on struct with a buffer > sizeof(struct)
# Autocreate a good-typed buffer from a tuple of ctypes objects
# On peut vouloir creer un buffer avec 12 elts de type X
# Ou creer un buffer avec 12 elt de type X mais une sub-size de 1000
if sys.version_info.major >= 3:
long = int
class ImprovedCtypesBufferBase(object):
def cast(self, type):
return ctypes.cast(self, type)
def as_string(self):
return ctypes.cast(self, gdef.LPCSTR).value
def as_wstring(self):
return ctypes.cast(self, gdef.LPWSTR).value
def as_pvoid(self):
return self.cast(gdef.PVOID)
# Constructor
@classmethod
def from_size(cls, size):
raw_buffer = ctypes.c_buffer(size)
buffer = cls.from_buffer(raw_buffer)
buffer._raw_buffer_ = raw_buffer
return buffer
@property
def real_size(self):
real_buffer = getattr(self, "_raw_buffer_", self)
return ctypes.sizeof(real_buffer)
def __new__(cls, *args, **kwargs):
if "size" in kwargs:
buff = ctypes.create_string_buffer(kwargs["size"])
self = cls.from_buffer(buff)
self._raw_buffer_ = buff
return self
# Add a '_raw_buffer_' even when no explicit size ?
return super(ImprovedCtypesBufferBase, cls).__new__(cls, *args, **kwargs)
# Used in windows.crypto.sign_verify for test
class PartialBufferType(object):
def __init__(self, type, nbelt=None):
self.type = type
self.nbelt = None
@staticmethod
def create_real_implem(item_type, nbelt):
if isinstance(nbelt, long):
nbelt = int(nbelt)
assert isinstance(nbelt, int)
cls_name = "TypedBuffer<{0}><{1}>".format(item_type.__name__, nbelt)
class TmpImplemArrayName(ImprovedCtypesBufferBase, ctypes.Array):
_type_ = item_type
_length_ = nbelt
TmpImplemArrayName.__name__ = cls_name
return TmpImplemArrayName
def from_buffer(self, buffer): # size as kwargs ?
if len(buffer) % ctypes.sizeof(self.type):
raise NotImplementedError("Buffer size of not a multiple of sizeof({0})".format(self.type.__name__))
nbelt = int(len(buffer) / ctypes.sizeof(self.type))
return self.create_real_implem(self.type, nbelt).from_buffer(buffer)
def from_buffer_copy(self, buffer): # size as kwargs ?
if len(buffer) % ctypes.sizeof(self.type):
raise NotImplementedError("Buffer size of not a multiple of sizeof({0})".format(self.type.__name__))
nbelt = int(len(buffer) / ctypes.sizeof(self.type))
return self.create_real_implem(self.type, nbelt).from_buffer_copy(buffer)
def create(self, nbelt):
return self.create_real_implem(self.type, nbelt)
def __mul__(self, nbelt):
return self.create_real_implem(self.type, nbelt)
def __call__(self, *args, **kwargs):
if len(args) == 1: # String magic: explode string as arg
if isinstance(args[0], basestring):
args = args[0]
nbelt = kwargs.get("nbelt", max(len(args), 1))
return self.create_real_implem(self.type, nbelt)(*args, **kwargs)
# # Expose these predefined types ?
# CharBuffer = PartialBufferType(gdef.CHAR)
# WCharBuffer = PartialBufferType(gdef.WCHAR)
# ByteBuffer = PartialBufferType(gdef.BYTE)
def BUFFER(type, nbelt=None):
if nbelt is None:
return PartialBufferType(type) # Allow user to create custom sized buffer
return PartialBufferType.create_real_implem(type, int(nbelt))
def buffer(obj, eltclass=None):
if eltclass is None: # Guess
obj = list(obj)
item = obj[0]
eltclass = type(item) # All object must have the same type
dlen = len(obj)
return BUFFER(eltclass, dlen)(*obj)
def resized_array(array, newnbelt, newtype=None):
if newtype is None:
newtype = array._type_
btype = BUFFER(newtype, newnbelt)
new_array = btype.from_address(ctypes.addressof(array))
new_array._base_array_ = array # Keep a ref to prevent some gc
return new_array