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,485 @@
import windows.com
import windows.generated_def as gdef
def generate_simple_getter(function, restype, extract_value=True, doc=None):
def value_getter(self):
res = restype()
getattr(self, function)(res)
if extract_value:
return res.value
return res
return property(value_getter, doc=doc)
def add_simple_setter(getter, function, restype):
@getter.setter
def value_setter(self, value):
resvalue = restype(value)
return getattr(self, function)(resvalue)
return value_setter
class TaskCollectionType(object):
ITEM_TYPE = None
count = generate_simple_getter("get_Count", gdef.LONG)
def get_item_type(self):
return self.ITEM_TYPE
def get_item(self, index):
"""Return elements nb ``index``. Collection index starts at 1"""
if index == 0:
raise IndexError("<{0}> Index start as 1".format(type(self).__name__))
index = self.get_index(index)
res = self.get_item_type()()
self.get_Item(index, res)
return res
def get_index(self, index):
return index
def items_generator(self):
for i in range(self.count):
# Start index is 1
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa446901(v=vs.85).aspx
yield self.get_item(1 + i)
@property
def items(self):
"""Return the list of item in the collection
:type: :class:`list`
"""
return list(self.items_generator())
def __iter__(self):
return self.items_generator()
def __getitem__(self, index): # Allow subclasses to only overwrite 'get_item' to rewrite both behavior
return self.get_item(index)
# Need to-do the doc=xx tricks to have the documentation in the 'AbstractAction' subclasses
class AbstractAction(object):
type_doc = """The type of action
:type: :class:`~windows.generated_def.winstructs.TASK_ACTION_TYPE`
"""
type = generate_simple_getter("get_Type", gdef.TASK_ACTION_TYPE, doc=type_doc)
id_doc = """The action id
:type: :class:`~windows.generated_def.winstructs.BSTR`
"""
id = generate_simple_getter("get_Id", gdef.BSTR, doc=id_doc)
class Action(gdef.IAction, AbstractAction):
"""Describe an action performed by a task"""
ACTION_SUBTYPE = {}
@property
def subtype(self):
"""Return the :class:`Action`-subtype according to :data:`AbstractAction.type`"""
subinterface = self.ACTION_SUBTYPE[self.type] # KeyError ?
return self.query(subinterface)
class ExecAction(gdef.IExecAction, AbstractAction):
"""Represent an action of type
:data:`~windows.generated_def.winstructs._TASK_ACTION_TYPE.TASK_ACTION_EXEC`"""
path = generate_simple_getter("get_Path", gdef.BSTR)
path = add_simple_setter(path, "put_Path", gdef.BSTR)
"""[R-W] The path of the programm to execute"""
arguments = generate_simple_getter("get_Arguments", gdef.BSTR)
arguments = add_simple_setter(arguments, "put_Arguments", gdef.BSTR)
"""[R-W] The arguments for the command to execute"""
working_directory = generate_simple_getter("get_WorkingDirectory", gdef.BSTR)
"""The working directory for the command to execute"""
# Register action subtype
Action.ACTION_SUBTYPE[gdef.TASK_ACTION_EXEC] = ExecAction
class ComHandlerAction(gdef.IComHandlerAction, AbstractAction):
"""Represent an action of type
:data:`~windows.generated_def.winstructs._TASK_ACTION_TYPE.TASK_ACTION_COM_HANDLER`"""
classid = generate_simple_getter("get_ClassId", gdef.BSTR)
classid = add_simple_setter(classid, "put_ClassId", gdef.BSTR)
"""The CLSID of the COM server executed
:type: :class:`~windows.generated_def.winstructs.BSTR`
"""
data = generate_simple_getter("get_Data", gdef.BSTR)
data = add_simple_setter(data, "put_Data", gdef.BSTR)
"""The DATA for the COM class
:type: :class:`~windows.generated_def.winstructs.BSTR`
"""
# Register action subtype
Action.ACTION_SUBTYPE[gdef.TASK_ACTION_COM_HANDLER] = ComHandlerAction
class EmailAction(gdef.IEmailAction, AbstractAction):
pass
Action.ACTION_SUBTYPE[gdef.TASK_ACTION_SEND_EMAIL] = EmailAction
class ShowMessageAction(gdef.IShowMessageAction, AbstractAction):
pass
Action.ACTION_SUBTYPE[gdef.TASK_ACTION_SHOW_MESSAGE] = ShowMessageAction
class Trigger(gdef.ITrigger):
"""A task trigger"""
type = generate_simple_getter("get_Type", gdef.TASK_TRIGGER_TYPE2)
"""The type of trigger
:type: :class:`~windows.generated_def.winstructs.TASK_TRIGGER_TYPE2`
"""
class ActionCollection(gdef.IActionCollection, TaskCollectionType):
ITEM_TYPE = Action
def create(self, action_type):
"""Create a new action of type ``action_type``
:rtype: A subclass of :class:`Action`
"""
res = self.ITEM_TYPE()
self.Create(action_type, res)
return res.subtype
def get_item(self, index):
item = super(ActionCollection, self).get_item(index)
# Need to Release() item ?
return item.subtype
class TriggerCollection(gdef.ITriggerCollection, TaskCollectionType):
ITEM_TYPE = Trigger
class TaskRegistrationInfo(gdef.IRegistrationInfo):
"""Provides the administrative information that can be used to describe the task.
This information includes details such as a description of the task,
the author of the task, the date the task is registered,
and the security descriptor of the task.
"""
author = generate_simple_getter("get_Author", gdef.BSTR)
"""The author of the task"""
description = generate_simple_getter("get_Description", gdef.BSTR)
"""The description of the task"""
date = generate_simple_getter("get_Date", gdef.BSTR)
"""The registration date of the task"""
source = generate_simple_getter("get_Source", gdef.BSTR)
"""Where the task originated from.
For example, a task may originate from a component, service, application, or user.
"""
documentation = generate_simple_getter("get_Documentation", gdef.BSTR)
"""Any additional documentation for the task"""
uri = generate_simple_getter("get_URI", gdef.BSTR)
"""the URI of the task."""
version = generate_simple_getter("get_Version", gdef.BSTR)
"""The version number of the task."""
# Return WindowsError: [Error -2147467263] Not implemented
# xml = generate_simple_getter("get_XmlText", gdef.BSTR)
sddl = generate_simple_getter("get_SecurityDescriptor", windows.com.Variant)
@property
def security_descriptor(self):
sddl = self.sddl
if not sddl:
return None
return windows.security.SecurityDescriptor.from_string(sddl)
class TaskPrincipal(gdef.IPrincipal):
"""Provides the security credentials for a principal.
These security credentials define the security context for the tasks that are associated with the principal.
"""
name = generate_simple_getter("get_DisplayName", gdef.BSTR)
name = add_simple_setter(name, "put_DisplayName", gdef.BSTR)
"""The name of the principal"""
id = generate_simple_getter("get_Id", gdef.BSTR)
id = add_simple_setter(id, "put_Id", gdef.BSTR)
"""the identifier of the principal."""
user_id = generate_simple_getter("get_UserId", gdef.BSTR)
user_id = add_simple_setter(user_id, "put_UserId", gdef.BSTR)
"""the user identifier that is required to run the task"""
group_id = generate_simple_getter("get_GroupId", gdef.BSTR)
group_id = add_simple_setter(group_id, "put_GroupId", gdef.BSTR)
"""the user group that is required to run the task"""
run_level = generate_simple_getter("get_RunLevel", gdef.TASK_RUNLEVEL_TYPE)
"""the privilege level that is required to run the tasks
:type: :class:`~windows.generated_def.winstructs.TASK_RUNLEVEL_TYPE`
"""
logon_type = generate_simple_getter("get_LogonType", gdef.TASK_LOGON_TYPE)
""" logon method that is required to run the task
:type: :class:`~windows.generated_def.winstructs.TASK_LOGON_TYPE`
"""
class TaskDefinition(gdef.ITaskDefinition):
"""The definition of a task"""
actions = generate_simple_getter("get_Actions", ActionCollection, extract_value=False)
"""The list of actions of the task
:type: :class:`ActionCollection`
"""
triggers = generate_simple_getter("get_Triggers", TriggerCollection, extract_value=False)
"""The list of triggers of the task
:type: :class:`TriggerCollection`
"""
registration_info = generate_simple_getter("get_RegistrationInfo", TaskRegistrationInfo, extract_value=False)
"""The registration information of the task
:type: :class:`TaskRegistrationInfo`
"""
principal = generate_simple_getter("get_Principal", TaskPrincipal, extract_value=False)
"""The principal that provides the security credentials for the task.
These security credentials define the security context for the tasks that are associated with the principal.
:type: :class:`TaskPrincipal`
"""
xml = generate_simple_getter("get_XmlText", gdef.BSTR)
"""The XML representig the task definition
:type: :class:`str`
"""
class Task(gdef.IRegisteredTask):
"""A scheduled task"""
name = generate_simple_getter("get_Name", gdef.BSTR)
"""The name of the task"""
path = generate_simple_getter("get_Path", gdef.BSTR)
"""The path of the task"""
state = generate_simple_getter("get_State", gdef.TASK_STATE)
"""The state of the task
:type: :class:`~windows.generated_def.winstructs.TASK_STATE`
"""
enabled = generate_simple_getter("get_Enabled", gdef.VARIANT_BOOL)
"""``True`` is the task is enabled"""
last_runtime = generate_simple_getter("get_LastRunTime", gdef.DATE)
"""Gets the last time the registered task was last run."""
next_runtime = generate_simple_getter("get_NextRunTime", gdef.DATE)
"""Gets the next time the registered task will be run."""
definition = generate_simple_getter("get_Definition", TaskDefinition, extract_value=False)
"""The definition of the task
:type: :class:`TaskDefinition`
"""
xml = generate_simple_getter("get_Xml", gdef.BSTR)
"""The XML representig the task
:type: :class:`str`
"""
def run(self, params=None, flags=gdef.TASK_RUN_NO_FLAGS, sessionid=0, user=None):
if params is None: params = gdef.VARIANT() # Empty variant
result = gdef.IRunningTask()
self.RunEx(params, flags, sessionid, user, result)
return result
def get_security_descriptor(self, secinfo):
res = gdef.BSTR()
self.GetSecurityDescriptor(secinfo, res)
return res.value
def __repr__(self):
return """<{0} "{1}" at {2:#x}>""".format(type(self).__name__, self.name, id(self))
class TaskCollection(gdef.IRegisteredTaskCollection, TaskCollectionType):
ITEM_TYPE = Task
def get_index(self, index):
vindex = windows.com.Variant()
vindex.vt = gdef.VT_I4
vindex._VARIANT_NAME_3.lVal = index
return vindex
class TaskService(gdef.ITaskService):
"""The task scheduler"""
def create(self, flags=0):
"""Create a new :class:`TaskDefinition` that can be used to create/register a new scheduled task
:rtype: :class:`TaskDefinition`
"""
res = TaskDefinition()
self.NewTask(flags, res)
return res
def connect(self, server=None, user=None, domain=None, password=None):
if server is None: server = gdef.VARIANT() # Empty variant
if user is None: user = gdef.VARIANT() # Empty variant
if domain is None: domain = gdef.VARIANT() # Empty variant
if password is None: password = gdef.VARIANT() # Empty variant
self.Connect(server, user, domain, password)
def folder(self, name):
"""Return the :class:`TaskFolder` with ``name``
:rtype: :class:`TaskFolder`
"""
folder = TaskFolder()
self.GetFolder(name, folder)
return folder
__call__ = folder # use the same 'API' than the registry
"""Alias for :func:`folder`"""
@property
def root(self):
r"""The root ``\`` :class:`TaskFolder`"""
return self.folder("\\")
class TaskFolder(gdef.ITaskFolder):
"""A folder of tasks"""
path = generate_simple_getter("get_Path", gdef.BSTR)
name = generate_simple_getter("get_Name", gdef.BSTR)
@property
def folders(self):
"""The list of sub-folders
:type: :class:`TaskFolderCollection`
"""
res = TaskFolderCollection()
self.GetFolders(0, res)
return res
def register(self, name, taskdef, flags=gdef.TASK_CREATE, userid=None, password=None, logonType=gdef.TASK_LOGON_NONE, ssid=None):
"""Register the task definition ``taskdef`` as a new task with ``name``
:rtype: :class:`Task`
"""
new_task = Task()
if userid is None: userid = gdef.VARIANT() # Empty variant
if password is None: password = gdef.VARIANT() # Empty variant
if ssid is None: ssid = gdef.VARIANT() # Empty variant
self.RegisterTaskDefinition(name, taskdef, flags, userid, password, logonType, ssid, new_task)
return new_task
@property
def tasks(self, flags=gdef.TASK_ENUM_HIDDEN):
"""The list of tasks in the folder
:type: :class:`TaskCollection`
"""
tasks = TaskCollection()
self.GetTasks(flags, tasks)
return tasks
def get_task(self, name):
"""Retrieve the task with ``name`` in the current folder
:rtype: :class:`Task`
"""
res = Task()
self.GetTask(name, res)
return res
def delete_task(self, name):
"""Delete the task with ``name`` in the current folder"""
return self.DeleteTask(name, 0)
def folder(self, name):
"""Return the :class:`TaskFolder` with ``name``"""
folder = TaskFolder()
self.GetFolder(name, folder)
return folder
def create_folder(self, name):
"""Create a new sub-:class:`TaskFolder` with ``name``"""
folder = TaskFolder()
self.CreateFolder(name, gdef.VARIANT(), folder)
return folder
def delete_folder(self, name):
"""Delete the sub-folder with ``name`` in the current folder"""
return self.DeleteFolder(name, 0)
__getitem__ = get_task
""" Alias for :func:`get_task`"""
__delitem__ = delete_task
""" Alias for :func:`delete_task`"""
__call__ = folder # use the same 'API' than the registry
""" Alias for :func:`folder`"""
def __repr__(self):
return """<{0} "{1}" at {2:#x}>""".format(type(self).__name__, self.path, id(self))
class TaskFolderCollection(gdef.ITaskFolderCollection, TaskCollectionType):
ITEM_TYPE = TaskFolder
def get_index(self, index):
vindex = windows.com.Variant()
vindex.vt = gdef.VT_I4
vindex._VARIANT_NAME_3.lVal = index
return vindex
# windows.com.init()
# clsid_task_scheduler = gdef.IID.from_string("0f87369f-a4e5-4cfc-bd3e-73e6154572dd")
# x = TaskService()
# emptvar = gdef.VARIANT()
# windows.com.create_instance(clsid_task_scheduler, x)
# x.connect()
# folder = x.folder("\\")
# assert folder.value
# tasks = folder.tasks
# for task in tasks.items:
# print(task.name)
# for action in task.definition.actions.items:
# print(" * {0}".format(action.type))
# subtype = action.subtype
# print(" * Path: {0}".format(subtype.path))
# print(" * Args: {0}".format(subtype.arguments))
# print(" * WDir: {0}".format(subtype.working_directory))
# Test creation
# ntd = x.create()
# actions = ntd.actions
# nea = actions.create(gdef.TASK_ACTION_EXEC).subtype
# nea.path = "MY_BINARY"
# nea.arguments = "MY_ARGUMENTS"
# folder.register("PROUT", ntd)
# path = gdef.BSTR()
# e.get_Path(path)
# print(path)