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,130 @@
|
||||
import sys
|
||||
import ctypes
|
||||
|
||||
import windows
|
||||
import windows.utils as utils
|
||||
from . import native_exec
|
||||
from .generated_def import winfuncs
|
||||
from .generated_def.windef import PAGE_EXECUTE_READWRITE
|
||||
from .generated_def.winstructs import *
|
||||
|
||||
# TODO Not a big fan of importing 'meta' every load
|
||||
# Should do an Hook API that take the winproxy function (not generate every hook possible)
|
||||
import windows.generated_def.meta
|
||||
|
||||
|
||||
class Callback(object):
|
||||
"""Give type information to hook callback"""
|
||||
def __init__(self, *types):
|
||||
self.types = types
|
||||
|
||||
def __call__(self, func):
|
||||
func._types_info = self.types
|
||||
return func
|
||||
|
||||
|
||||
class KnownCallback(object):
|
||||
types = ()
|
||||
|
||||
def __call__(self, func):
|
||||
func._types_info = self.types
|
||||
return func
|
||||
|
||||
|
||||
def add_callback_to_module(callback):
|
||||
setattr(sys.modules[__name__], type(callback).__name__, callback)
|
||||
|
||||
# Generate IATCallback decorator for all known functions
|
||||
|
||||
|
||||
|
||||
for func in windows.generated_def.meta.functions:
|
||||
prototype = getattr(winfuncs, func + "Prototype")
|
||||
callback_name = func + "Callback"
|
||||
|
||||
class CallBackDeclaration(KnownCallback):
|
||||
types = (prototype._restype_,) + prototype._argtypes_
|
||||
|
||||
CallBackDeclaration.__name__ = callback_name
|
||||
add_callback_to_module(CallBackDeclaration())
|
||||
|
||||
|
||||
class IATHook(object):
|
||||
"""Look at my hook <3"""
|
||||
|
||||
def __init__(self, IAT_entry, callback, types=None):
|
||||
if types is None:
|
||||
if not hasattr(callback, "_types_info"):
|
||||
raise ValueError("Callback for IATHook has no type infomations")
|
||||
types = callback._types_info
|
||||
self.original_types = types
|
||||
self.callback_types = self.transform_arguments(self.original_types)
|
||||
self.entry = IAT_entry
|
||||
self.callback = callback
|
||||
## No more circular ref -> but stub is destroyed -> segv :(
|
||||
self.stub = ctypes.WINFUNCTYPE(*self.callback_types)(self.hook_callback)
|
||||
# stub = ctypes.WINFUNCTYPE(*self.callback_types)(self.hook_callback)
|
||||
# self.stub_addr = ctypes.cast(stub, PVOID) # Same problem as keep stub... (GC..)
|
||||
self.stub_addr = ctypes.cast(self.stub, PVOID).value # Same problem as keep stub... (GC..)
|
||||
# IAT_entry.stub = stub
|
||||
self.realfunction = ctypes.WINFUNCTYPE(*types)(IAT_entry.nonhookvalue)
|
||||
self.is_enable = False
|
||||
|
||||
def transform_arguments(self, types):
|
||||
res = []
|
||||
for type in types:
|
||||
if type in (ctypes.c_wchar_p, ctypes.c_char_p):
|
||||
res.append(ctypes.c_void_p)
|
||||
else:
|
||||
res.append(type)
|
||||
return res
|
||||
|
||||
def enable(self):
|
||||
"""Enable the IAT hook: you MUST keep a reference to the IATHook while the hook is enabled"""
|
||||
with utils.VirtualProtected(self.entry.addr, ctypes.sizeof(PVOID), PAGE_EXECUTE_READWRITE):
|
||||
self.entry.value = self.stub_addr
|
||||
self.is_enable = True
|
||||
self.entry.enabled = True
|
||||
|
||||
def disable(self):
|
||||
"""Disable the IAT hook"""
|
||||
with utils.VirtualProtected(self.entry.addr, ctypes.sizeof(PVOID), PAGE_EXECUTE_READWRITE):
|
||||
self.entry.value = self.entry.nonhookvalue
|
||||
self.is_enable = False
|
||||
self.entry.enabled = True
|
||||
|
||||
def hook_callback(self, *args):
|
||||
adapted_args = []
|
||||
for value, type in zip(args, self.original_types[1:]):
|
||||
if type == ctypes.c_wchar_p:
|
||||
adapted_args.append(ctypes.c_wchar_p(value))
|
||||
elif type == ctypes.c_char_p:
|
||||
adapted_args.append(ctypes.c_char_p((value)))
|
||||
else:
|
||||
adapted_args.append(value)
|
||||
|
||||
def real_function(*args):
|
||||
if args == ():
|
||||
args = adapted_args
|
||||
return self.realfunction(*args)
|
||||
return self.callback(*adapted_args, real_function=real_function)
|
||||
|
||||
## New simple hook API based on winproxy
|
||||
def setup_hook(target, hook, dll_to_hook):
|
||||
"TODO: Test and doc :D"
|
||||
dll_name, api_name = windows.winproxy.get_target(target)
|
||||
prototype = target.prototype
|
||||
hook._types_info = (prototype._restype_,) + prototype._argtypes_
|
||||
|
||||
if not dll_name.endswith(".dll"):
|
||||
dll_name += ".dll"
|
||||
# Get the peb of our process
|
||||
peb = windows.current_process.peb
|
||||
# Get the dll_to_hook
|
||||
module_to_hook = [m for m in peb.modules if m.name.lower() == dll_to_hook.lower()][0]
|
||||
# Get the iat entries for DLL dll_name
|
||||
adv_imports = module_to_hook.pe.imports[dll_name]
|
||||
# Get RegOpenKeyExA iat entry
|
||||
iat = [n for n in adv_imports if n.name == api_name][0]
|
||||
iat.set_hook(hook)
|
||||
return iat
|
||||
Reference in New Issue
Block a user