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,3 @@
|
||||
from .pythonutils import *
|
||||
from .winutils import *
|
||||
from .improved_buffer import *
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
"""utils fonctions non windows-related"""
|
||||
import sys
|
||||
import ctypes
|
||||
import _ctypes
|
||||
import windows.generated_def as gdef
|
||||
|
||||
from windows import winproxy
|
||||
from windows.dbgprint import dbgprint
|
||||
from windows.pycompat import basestring
|
||||
|
||||
|
||||
|
||||
def fixedpropety(f):
|
||||
cache_name = "_" + f.__name__
|
||||
|
||||
def prop(self):
|
||||
try:
|
||||
return getattr(self, cache_name)
|
||||
except AttributeError:
|
||||
setattr(self, cache_name, f(self))
|
||||
return getattr(self, cache_name)
|
||||
return property(prop, doc=f.__doc__)
|
||||
|
||||
# Slow fix of the typo :)
|
||||
fixedproperty = fixedpropety
|
||||
|
||||
# type replacement based on name
|
||||
def transform_ctypes_fields(struct, replacement):
|
||||
return [(name, replacement.get(name, type)) for name, type in struct._fields_]
|
||||
|
||||
|
||||
def print_ctypes_struct(struct, name="", hexa=False):
|
||||
sprint_method = getattr(struct, "__sprint__", None)
|
||||
if sprint_method is not None:
|
||||
# Allow function to accept 'hexa' param
|
||||
# But handle function that don't, So we can just do:
|
||||
# __sprint__ = __repr__
|
||||
print("{0} -> {1}".format(name, sprint_method()))
|
||||
return
|
||||
|
||||
if isinstance(struct, _ctypes._Pointer):
|
||||
if ctypes.cast(struct, ctypes.c_void_p).value is None:
|
||||
print("{0} -> NULL".format(name))
|
||||
return
|
||||
return print_ctypes_struct(struct[0], name + "<deref>", hexa=hexa)
|
||||
|
||||
if not hasattr(struct, "_fields_"):
|
||||
value = struct
|
||||
if hasattr(struct, "value"):
|
||||
value = struct.value
|
||||
|
||||
if isinstance(value, basestring):
|
||||
value = repr(value)
|
||||
if hexa and not isinstance(value, gdef.Flag):
|
||||
try:
|
||||
print("{0} -> {1}".format(name, hex(value)))
|
||||
return
|
||||
except TypeError:
|
||||
pass
|
||||
print("{0} -> {1}".format(name, value))
|
||||
return
|
||||
|
||||
for field in struct._fields_:
|
||||
if len(field) == 2:
|
||||
fname, ftype = field
|
||||
nb_bits = None
|
||||
elif len(field) == 3:
|
||||
fname, ftype, nb_bits = field
|
||||
else:
|
||||
raise ValueError("Unknown ctypes field entry format <{0}>".format(field))
|
||||
try:
|
||||
value = getattr(struct, fname)
|
||||
except Exception as e:
|
||||
print("Error while printing <{0}> : {1}".format(fname, e))
|
||||
continue
|
||||
print_ctypes_struct(value, "{0}.{1}".format(name, fname), hexa=hexa)
|
||||
|
||||
|
||||
def sprint(struct, name="struct", hexa=True):
|
||||
"""Print recursively the content of a :mod:`ctypes` structure"""
|
||||
return print_ctypes_struct(struct, name=name, hexa=hexa)
|
||||
|
||||
|
||||
class AutoHandle(object):
|
||||
"""An abstract class that allow easy handle creation/destruction/wait"""
|
||||
# Big bypass to prevent missing reference at programm exit..
|
||||
_close_function = ctypes.WinDLL("kernel32").CloseHandle
|
||||
def _get_handle(self):
|
||||
raise NotImplementedError("{0} is abstract".format(type(self).__name__))
|
||||
|
||||
@property
|
||||
def handle(self):
|
||||
"""An handle on the object
|
||||
|
||||
:type: HANDLE
|
||||
|
||||
.. note::
|
||||
The handle is automaticaly closed when the object is destroyed
|
||||
"""
|
||||
if hasattr(self, "_handle"):
|
||||
return self._handle
|
||||
self._handle = self._get_handle()
|
||||
dbgprint("Open handle {0} for {1}".format(hex(self._handle), self), "HANDLE")
|
||||
return self._handle
|
||||
|
||||
def wait(self, timeout=gdef.INFINITE):
|
||||
"""Wait for the object"""
|
||||
return winproxy.WaitForSingleObject(self.handle, timeout)
|
||||
|
||||
def __del__(self):
|
||||
# sys.path is not None -> check if python shutdown
|
||||
if hasattr(sys, "path") and sys.path is not None and hasattr(self, "_handle") and self._handle:
|
||||
# Prevent some bug where dbgprint might be None when __del__ is called in a closing process
|
||||
dbgprint("Closing Handle {0} for {1}".format(hex(self._handle), self), "HANDLE") if dbgprint is not None else None
|
||||
self._close_function(self._handle)
|
||||
@@ -0,0 +1,627 @@
|
||||
import ctypes
|
||||
import msvcrt
|
||||
import os
|
||||
import sys
|
||||
import code
|
||||
import math
|
||||
import datetime
|
||||
import warnings
|
||||
from collections import namedtuple
|
||||
|
||||
import windows
|
||||
from windows.dbgprint import dbgprint
|
||||
import windows.generated_def as gdef
|
||||
|
||||
from .. import winproxy
|
||||
from ..generated_def.winstructs import *
|
||||
|
||||
|
||||
# Function resolution !
|
||||
# should be in winproxy ?
|
||||
def get_func_addr(dll_name, func_name):
|
||||
# Load the DLL
|
||||
ctypes.WinDLL(dll_name)
|
||||
modules = windows.current_process.peb.modules
|
||||
if not dll_name.lower().endswith(".dll"):
|
||||
dll_name += ".dll"
|
||||
mod = [x for x in modules if x.name == dll_name][0]
|
||||
return mod.pe.exports[func_name]
|
||||
|
||||
|
||||
def get_remote_func_addr(target, dll_name, func_name):
|
||||
name_modules = [m for m in target.peb.modules if m.name == dll_name]
|
||||
if not len(name_modules):
|
||||
raise ValueError("Module <{0}> not loaded in target <{1}>".format(dll_name, target))
|
||||
mod = name_modules[0]
|
||||
return mod.pe.exports[func_name]
|
||||
|
||||
|
||||
def is_wow_64(hProcess):
|
||||
try:
|
||||
fnIsWow64Process = get_func_addr("kernel32.dll", "IsWow64Process")
|
||||
except winproxy.WinproxyError:
|
||||
return False
|
||||
IsWow64Process = ctypes.WINFUNCTYPE(BOOL, HANDLE, ctypes.POINTER(BOOL))(fnIsWow64Process)
|
||||
Wow64Process = BOOL()
|
||||
res = IsWow64Process(hProcess, ctypes.byref(Wow64Process))
|
||||
if res:
|
||||
return bool(Wow64Process)
|
||||
raise ctypes.WinError()
|
||||
|
||||
|
||||
def create_file_from_handle(handle, mode="r"):
|
||||
"""Return a Python :class:`file` around a ``Windows`` HANDLE"""
|
||||
flags = os.O_BINARY if "b" in mode else os.O_TEXT
|
||||
fd = msvcrt.open_osfhandle(handle, flags)
|
||||
kwargs = {}
|
||||
if windows.pycompat.is_py3 and flags == os.O_TEXT:
|
||||
# Buffering, encoding
|
||||
args = (100, "ascii")
|
||||
else:
|
||||
# Buffering
|
||||
args = (0,)
|
||||
# In py2 os.fdopen do not accept kwargs
|
||||
return os.fdopen(fd, mode, *args)
|
||||
|
||||
|
||||
def get_handle_from_file(f):
|
||||
"""Get the ``Windows`` HANDLE of a python :class:`file`"""
|
||||
return msvcrt.get_osfhandle(f.fileno())
|
||||
|
||||
|
||||
def create_console():
|
||||
"""Create a new console displaying STDOUT.
|
||||
Useful in injection of GUI process"""
|
||||
winproxy.AllocConsole()
|
||||
stdout_handle = winproxy.GetStdHandle(gdef.STD_OUTPUT_HANDLE)
|
||||
console_stdout = create_file_from_handle(stdout_handle, "w")
|
||||
sys.stdout = console_stdout
|
||||
|
||||
stdin_handle = winproxy.GetStdHandle(gdef.STD_INPUT_HANDLE)
|
||||
console_stdin = create_file_from_handle(stdin_handle, "r")
|
||||
sys.stdin = console_stdin
|
||||
|
||||
stderr_handle = winproxy.GetStdHandle(gdef.STD_ERROR_HANDLE)
|
||||
console_stderr = create_file_from_handle(stderr_handle, "w")
|
||||
sys.stderr = console_stderr
|
||||
|
||||
|
||||
def create_process(path, args=None, dwCreationFlags=0, show_windows=True):
|
||||
"""A convenient wrapper arround :func:`windows.winproxy.CreateProcessA`"""
|
||||
proc_info = PROCESS_INFORMATION()
|
||||
lpStartupInfo = None
|
||||
if show_windows:
|
||||
StartupInfo = STARTUPINFOW()
|
||||
StartupInfo.cb = ctypes.sizeof(StartupInfo)
|
||||
StartupInfo.dwFlags = 0
|
||||
lpStartupInfo = ctypes.byref(StartupInfo)
|
||||
lpCommandLine = None
|
||||
if isinstance(path, bytes):
|
||||
path = path.decode()
|
||||
if args:
|
||||
unicode_args = []
|
||||
for arg in args:
|
||||
if isinstance(arg, bytes):
|
||||
arg = arg.decode()
|
||||
unicode_args.append(arg)
|
||||
lpCommandLine = (" ".join(unicode_args))
|
||||
windows.winproxy.CreateProcessW(path, lpCommandLine=lpCommandLine, dwCreationFlags=dwCreationFlags, lpProcessInformation=ctypes.byref(proc_info), lpStartupInfo=lpStartupInfo)
|
||||
dbgprint("CreateProcessW new process handle {:#x}".format(proc_info.hProcess), "HANDLE")
|
||||
dbgprint("CreateProcessW new thread handle {:#x}".format(proc_info.hThread), "HANDLE")
|
||||
dbgprint("Automatic close of thread handle {:#x}".format(proc_info.hThread), "HANDLE")
|
||||
windows.winproxy.CloseHandle(proc_info.hThread) # Give access to a WinThread in addition of the WinProcess ?
|
||||
return windows.winobject.process.WinProcess(pid=proc_info.dwProcessId, handle=proc_info.hProcess)
|
||||
|
||||
|
||||
def device_io_control(handle, iocode, buffer):
|
||||
outbuffer = ctypes.c_buffer(0x1000)
|
||||
returned_size = gdef.DWORD()
|
||||
windows.winproxy.DeviceIoControl(handle, iocode, buffer, lpOutBuffer=outbuffer, lpBytesReturned=returned_size)
|
||||
return outbuffer[:returned_size.value]
|
||||
|
||||
|
||||
|
||||
def tmp_cp_as(path, token):
|
||||
proc_info = PROCESS_INFORMATION()
|
||||
windows.winproxy.CreateProcessAsUserA(token, path, lpCommandLine=None, dwCreationFlags=gdef.CREATE_NEW_CONSOLE, lpProcessInformation=ctypes.byref(proc_info), lpStartupInfo=None)
|
||||
return windows.winobject.process.WinProcess(pid=proc_info.dwProcessId, handle=proc_info.hProcess)
|
||||
|
||||
def find_handle(proc, value):
|
||||
return [h for h in windows.system.handles if h.dwProcessId == proc.pid and h.wValue == value]
|
||||
|
||||
def lookup_privilege_value(privilege_name):
|
||||
luid = LUID()
|
||||
winproxy.LookupPrivilegeValueA(None, privilege_name, byref(luid))
|
||||
return luid
|
||||
|
||||
def lookup_privilege_name(privilege_value):
|
||||
if isinstance(privilege_value, tuple):
|
||||
luid = LUID(privilege_value[1], privilege_value[0])
|
||||
privilege_value = luid
|
||||
size = DWORD(0x100)
|
||||
buff = ctypes.c_buffer(size.value)
|
||||
winproxy.LookupPrivilegeNameA(None, privilege_value, buff, size)
|
||||
return buff[:size.value]
|
||||
|
||||
|
||||
def lookup_sid(psid):
|
||||
"""Retrieves the name of the Computer/Domain and the name of the Account for a given SID
|
||||
|
||||
:returns: (:class:`unicode`, :class:`unicode`) - A tuple of two unicode strings
|
||||
"""
|
||||
usernamesize = gdef.DWORD(0x1000)
|
||||
computernamesize = gdef.DWORD(0x1000)
|
||||
username = ctypes.create_unicode_buffer(usernamesize.value)
|
||||
computername = ctypes.create_unicode_buffer(computernamesize.value)
|
||||
peUse = gdef.SID_NAME_USE()
|
||||
winproxy.LookupAccountSidW(None, psid, username, usernamesize, computername, computernamesize, peUse)
|
||||
return computername[:computernamesize.value], username[:usernamesize.value]
|
||||
|
||||
def enable_privilege(lpszPrivilege, bEnablePrivilege):
|
||||
"""
|
||||
Enable or disable a privilege::
|
||||
|
||||
enable_privilege(SE_DEBUG_NAME, True)
|
||||
"""
|
||||
tp = TOKEN_PRIVILEGES()
|
||||
luid = LUID()
|
||||
hToken = HANDLE()
|
||||
|
||||
winproxy.OpenProcessToken(winproxy.GetCurrentProcess(), TOKEN_ALL_ACCESS, byref(hToken))
|
||||
winproxy.LookupPrivilegeValueA(None, lpszPrivilege, byref(luid))
|
||||
tp.PrivilegeCount = 1
|
||||
tp.Privileges[0].Luid = luid
|
||||
if bEnablePrivilege:
|
||||
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED
|
||||
else:
|
||||
tp.Privileges[0].Attributes = 0
|
||||
winproxy.AdjustTokenPrivileges(hToken, False, byref(tp), sizeof(TOKEN_PRIVILEGES))
|
||||
winproxy.CloseHandle(hToken)
|
||||
if winproxy.GetLastError() == gdef.ERROR_NOT_ALL_ASSIGNED:
|
||||
raise ValueError("Failed to get privilege {0}".format(lpszPrivilege))
|
||||
return True
|
||||
|
||||
|
||||
def check_is_elevated():
|
||||
"""Return ``True`` if process is Admin"""
|
||||
hToken = HANDLE()
|
||||
elevation = TOKEN_ELEVATION()
|
||||
cbsize = DWORD()
|
||||
|
||||
winproxy.OpenProcessToken(winproxy.GetCurrentProcess(), TOKEN_ALL_ACCESS, byref(hToken))
|
||||
winproxy.GetTokenInformation(hToken, TokenElevation, byref(elevation), sizeof(elevation), byref(cbsize))
|
||||
winproxy.CloseHandle(hToken)
|
||||
return elevation.TokenIsElevated
|
||||
|
||||
|
||||
def check_debug():
|
||||
"""Check that kernel is in debug mode (beware of NOUMEX):
|
||||
|
||||
https://msdn.microsoft.com/en-us/library/windows/hardware/ff556253(v=vs.85).aspx#_______noumex______
|
||||
"""
|
||||
options = windows.system.registry(r'HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control')['SystemStartOptions']
|
||||
control = options.value
|
||||
if "DEBUG" not in control:
|
||||
# print "[-] Enable debug boot!"
|
||||
# print "> bcdedit /debug on"
|
||||
return False
|
||||
if "DEBUG=NOUMEX" not in control:
|
||||
pass
|
||||
# print "[*] Warning noumex not set!"
|
||||
# print "> bcdedit /set noumex on"
|
||||
return True
|
||||
|
||||
UNIX_EPOCH = datetime.datetime(1970, 1, 1, 0, 0)
|
||||
WINDOWS_EPOCH = datetime.datetime(1601, 1, 1, 0, 0)
|
||||
# https://docs.microsoft.com/en-us/cpp/atl-mfc-shared/date-type?view=vs-2019
|
||||
# Why keep it simple and have only one epoch ? :D
|
||||
# I don't want to name this "DATE_EPOCH" as everything is a DATE
|
||||
# So let's go with COMDATE as this structure seems very related to COM/AUTOMATION
|
||||
COMDATE_EPOCH = datetime.datetime(1899, 12, 30, 0, 0)
|
||||
|
||||
WIN_TO_UNIX_EPOCH_SECOND = int((UNIX_EPOCH - WINDOWS_EPOCH).total_seconds())
|
||||
WIN_TICK_PER_SECOND_INT = 10**7
|
||||
WIN_TICK_PER_SECOND_FLOAT = 10.0**7
|
||||
WIN_TO_UNIX_EPOCH_WIN_TICKS = WIN_TO_UNIX_EPOCH_SECOND * WIN_TICK_PER_SECOND_INT
|
||||
|
||||
# TODO: look in python stblib how filetime -> unix timestamp translation is down (os.stat code ?)
|
||||
|
||||
def unix_timestamp_from_filetime(filetime):
|
||||
# Round the filetime
|
||||
last_number = (filetime % 10)
|
||||
# We do some sort of "manual rounding cause of py2 vs py3
|
||||
# PY2: round(0.5) == 1
|
||||
# PY3: round(0.5) == 0
|
||||
if last_number == 5:
|
||||
rounding = 1
|
||||
else:
|
||||
rounding = round(last_number / 10.0)
|
||||
round_win_ticks = ((filetime // 10) + int(rounding)) * 10
|
||||
return round((round_win_ticks - WIN_TO_UNIX_EPOCH_WIN_TICKS) / WIN_TICK_PER_SECOND_FLOAT, 7)
|
||||
|
||||
def datetime_from_filetime(filetime):
|
||||
"""return a :class:`datetime.datetime` from a ``windows`` FILETIME int"""
|
||||
# Manual non-approx rounding as filetime will not have a perfect representation as Python float
|
||||
# We do some sort of "manual rounding cause of py2 vs py3
|
||||
# PY2: round(0.5) == 1
|
||||
# PY3: round(0.5) == 0
|
||||
last_number = (filetime % 10)
|
||||
if last_number == 5:
|
||||
rounding = 1
|
||||
else:
|
||||
rounding = round(last_number / 10.0)
|
||||
round_microsecond = (filetime // 10) + int(rounding)
|
||||
return WINDOWS_EPOCH + datetime.timedelta(microseconds=round_microsecond)
|
||||
|
||||
def filetime_from_datetime(dtime):
|
||||
"""Return the FILETIME value from a :class:`datetime.datetime` in a python :class:`int`"""
|
||||
return int((dtime - WINDOWS_EPOCH).total_seconds()) * WIN_TICK_PER_SECOND_INT
|
||||
|
||||
def datetime_from_comdate(comtime):
|
||||
# Hour values are expressed as the absolute value of the fractional part of the number.
|
||||
if comtime < 0:
|
||||
# The date timeline becomes discontinuous for date values less than 0 (before 30 December 1899). This is because the whole-number portion of the date value is treated as signed, while the fractional part is treated as unsigned.
|
||||
# other words, the whole-number part of the date value may be positive or negative, while the fractional part of the date value is always added to the overall logical date.
|
||||
# WTF :D
|
||||
dec, nb = math.modf(comtime)
|
||||
final_delta = nb + abs(dec)
|
||||
return COMDATE_EPOCH + datetime.timedelta(final_delta)
|
||||
return COMDATE_EPOCH + datetime.timedelta(comtime)
|
||||
|
||||
def datetime_from_systemtime(systime):
|
||||
return datetime.datetime(
|
||||
year=systime.wYear,
|
||||
month=systime.wMonth,
|
||||
day=systime.wDay,
|
||||
hour=systime.wHour,
|
||||
minute=systime.wMinute,
|
||||
second=systime.wSecond,
|
||||
microsecond=systime.wMilliseconds * 1000,
|
||||
)
|
||||
|
||||
class FixedInteractiveConsole(code.InteractiveConsole):
|
||||
def raw_input(self, prompt=">>>"):
|
||||
sys.stdout.write(prompt)
|
||||
return raw_input("")
|
||||
|
||||
|
||||
def pop_shell(locs=None):
|
||||
"""Pop a console with an InterativeConsole"""
|
||||
if locs is None:
|
||||
locs = globals()
|
||||
create_console()
|
||||
FixedInteractiveConsole(locs).interact()
|
||||
|
||||
def get_kernel_modules():
|
||||
warnings.warn("get_kernel_modules() will be removed: use windows.system.modules instead", DeprecationWarning)
|
||||
return windows.system.modules
|
||||
|
||||
class FileStreamInformation(gdef.FILE_STREAM_INFORMATION):
|
||||
@property
|
||||
def name(self):
|
||||
return gdef.LPWSTR(ctypes.addressof(self) + type(self).StreamName.offset).value
|
||||
|
||||
@property
|
||||
def next(self):
|
||||
if not self.NextEntryOffset:
|
||||
return None
|
||||
return type(self).from_address(ctypes.addressof(self) + self.NextEntryOffset)
|
||||
|
||||
|
||||
def all(self):
|
||||
return list(self)
|
||||
|
||||
def __iter__(self):
|
||||
while self:
|
||||
yield self
|
||||
self = self.next
|
||||
|
||||
def __repr__(self):
|
||||
return "<ADS name='{0}'>".format(self.name)
|
||||
|
||||
|
||||
ntqueryinformationfile_info_structs = {
|
||||
gdef.FileAccessInformation: gdef.FILE_ACCESS_INFORMATION,
|
||||
gdef.FileAlignmentInformation: gdef.FILE_ALIGNMENT_INFORMATION,
|
||||
gdef.FileAllInformation: gdef.FILE_ALL_INFORMATION,
|
||||
gdef.FileAttributeTagInformation: gdef.FILE_ATTRIBUTE_TAG_INFORMATION,
|
||||
gdef.FileBasicInformation: gdef.FILE_BASIC_INFORMATION,
|
||||
gdef.FileEaInformation: gdef.FILE_EA_INFORMATION ,
|
||||
gdef.FileInternalInformation: gdef.FILE_INTERNAL_INFORMATION,
|
||||
gdef.FileIoPriorityHintInformation: gdef.FILE_IO_PRIORITY_HINT_INFORMATION,
|
||||
gdef.FileModeInformation: gdef.FILE_MODE_INFORMATION,
|
||||
gdef.FileNetworkOpenInformation: gdef.FILE_NETWORK_OPEN_INFORMATION,
|
||||
gdef.FileNameInformation: gdef.FILE_NAME_INFORMATION,
|
||||
gdef.FilePositionInformation: gdef.FILE_POSITION_INFORMATION,
|
||||
gdef.FileStandardInformation: gdef.FILE_STANDARD_INFORMATION,
|
||||
gdef.FileIsRemoteDeviceInformation: gdef.FILE_IS_REMOTE_DEVICE_INFORMATION,
|
||||
gdef.FileStreamInformation: FileStreamInformation,
|
||||
}
|
||||
|
||||
def query_file_information(file_or_handle, file_info_class):
|
||||
if not isinstance(file_or_handle, windows.pycompat.int_types):
|
||||
file_or_handle = windows.utils.get_handle_from_file(file_or_handle)
|
||||
handle = file_or_handle
|
||||
io_status = gdef.IO_STATUS_BLOCK()
|
||||
info = ntqueryinformationfile_info_structs[file_info_class]()
|
||||
# Do helper for 'is_pointer' / get pointed_size & co ? (useful for winproxy)
|
||||
pinfo = ctypes.pointer(info)
|
||||
try:
|
||||
windows.winproxy.NtQueryInformationFile(handle, io_status, pinfo, ctypes.sizeof(info), FileInformationClass=file_info_class)
|
||||
except Exception as e:
|
||||
if not (e.winerror & 0xffffffff) == gdef.STATUS_BUFFER_OVERFLOW:
|
||||
raise
|
||||
# STATUS_BUFFER_OVERFLOW -> Guess we have a FILE_NAME_INFORMATION somewhere that need a bigger buffer
|
||||
if file_info_class == gdef.FileNameInformation:
|
||||
file_name_length = pinfo[0].FileNameLength
|
||||
elif file_info_class == gdef.FileAllInformation:
|
||||
file_name_length = pinfo[0].NameInformation.FileNameLength
|
||||
elif file_info_class == gdef.FileStreamInformation:
|
||||
file_name_length = 0x10000
|
||||
else:
|
||||
raise
|
||||
full_size = ctypes.sizeof(info) + file_name_length # We add a little too much size for the sake of simplicity
|
||||
buffer = ctypes.c_buffer(full_size)
|
||||
windows.winproxy.NtQueryInformationFile(handle, io_status, buffer, full_size, FileInformationClass=file_info_class)
|
||||
pinfo = ctypes.cast(buffer, ctypes.POINTER(ntqueryinformationfile_info_structs[file_info_class]))
|
||||
info = pinfo[0]
|
||||
# return list of ADS if FileStreamInformation ?
|
||||
return info
|
||||
|
||||
|
||||
class EAInfo(gdef.FILE_FULL_EA_INFORMATION):
|
||||
@property
|
||||
def name(self):
|
||||
return gdef.LPCSTR(ctypes.addressof(self) + type(self).EaName.offset).value
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
value_addr = ctypes.addressof(self) + type(self).EaName.offset + self.EaNameLength + 1 # +1 -> Name \x00
|
||||
return (ctypes.c_char * self.EaValueLength).from_address(value_addr)[:]
|
||||
|
||||
@property
|
||||
def next(self):
|
||||
# NextEntryOffset is Relative to our current offset
|
||||
if not self.NextEntryOffset:
|
||||
return None
|
||||
try: # First entry
|
||||
raw_buffer = self._b_base_._raw_buffer_
|
||||
except AttributeError as e:
|
||||
raw_buffer = self._raw_buffer_
|
||||
curoffset = getattr(self, "_raw_buffer_offset_", 0)
|
||||
new = type(self).from_buffer(raw_buffer, curoffset + self.NextEntryOffset)
|
||||
# Keep the underlying buffer easily accessible
|
||||
new._raw_buffer_ = raw_buffer
|
||||
new._raw_buffer_offset_ = curoffset + self.NextEntryOffset
|
||||
return new
|
||||
|
||||
|
||||
def __iter__(self):
|
||||
while self:
|
||||
yield self
|
||||
self = self.next
|
||||
|
||||
def __repr__(self):
|
||||
return '<{0} name="{1}">'.format(type(self).__name__, self.name)
|
||||
|
||||
|
||||
MAXIMUM_EA_SIZE = 0x0000ffff
|
||||
|
||||
def query_extended_attributes(file_or_handle):
|
||||
if isinstance(file_or_handle, file):
|
||||
file_or_handle = windows.utils.get_handle_from_file(file_or_handle)
|
||||
# Check EaSize
|
||||
x = windows.utils.query_file_information(file_or_handle, gdef.FileEaInformation)
|
||||
if not x.EaSize:
|
||||
return
|
||||
io_status = gdef.IO_STATUS_BLOCK()
|
||||
# Handle Win10 / Win7
|
||||
# Saw on Win10 -> EaSize > MAXIMUM_EA_SIZE
|
||||
# Saw on Win7 -> EaSize not enought (STATUS_BUFFER_OVERFLOW)
|
||||
buffsize = max(MAXIMUM_EA_SIZE, x.EaSize)
|
||||
buffer = windows.utils.BUFFER(EAInfo)(size=buffsize)
|
||||
windows.winproxy.NtQueryEaFile(file_or_handle, io_status, buffer, buffsize, False, None, 0, None, True)
|
||||
return buffer[0]
|
||||
|
||||
|
||||
|
||||
|
||||
ntqueryvolumeinformationfile_info_structs = {
|
||||
gdef.FileFsAttributeInformation: gdef.FILE_FS_ATTRIBUTE_INFORMATION,
|
||||
gdef.FileFsControlInformation: gdef.FILE_FS_CONTROL_INFORMATION,
|
||||
gdef.FileFsDeviceInformation: gdef.FILE_FS_DEVICE_INFORMATION,
|
||||
gdef.FileFsDriverPathInformation: gdef.FILE_FS_DRIVER_PATH_INFORMATION,
|
||||
gdef.FileFsFullSizeInformation: gdef.FILE_FS_FULL_SIZE_INFORMATION,
|
||||
gdef.FileFsObjectIdInformation: gdef.FILE_FS_OBJECTID_INFORMATION,
|
||||
gdef.FileFsSizeInformation: gdef.FILE_FS_SIZE_INFORMATION,
|
||||
gdef.FileFsVolumeInformation: gdef.FILE_FS_VOLUME_INFORMATION,
|
||||
gdef.FileFsSectorSizeInformation: gdef.FILE_FS_SECTOR_SIZE_INFORMATION,
|
||||
}
|
||||
|
||||
|
||||
# TODO: FileFsDriverPathInformation
|
||||
# TODO: Extended FILE_FS_VOLUME_INFORMATION that can read the real value of 'VolumeLabel'
|
||||
def query_volume_information(file_or_handle, volume_info_class):
|
||||
if not isinstance(file_or_handle, windows.pycompat.int_types):
|
||||
file_or_handle = get_handle_from_file(file_or_handle)
|
||||
handle = file_or_handle
|
||||
io_status = gdef.IO_STATUS_BLOCK()
|
||||
info = ntqueryvolumeinformationfile_info_structs[volume_info_class]()
|
||||
# Do helper for 'is_pointer' / get pointed_size & co ? (useful for winproxy)
|
||||
pinfo = ctypes.pointer(info)
|
||||
try:
|
||||
windows.winproxy.NtQueryVolumeInformationFile(handle, io_status, pinfo, ctypes.sizeof(info), FsInformationClass=volume_info_class)
|
||||
except WindowsError as e:
|
||||
# import pdb;pdb.set_trace()
|
||||
if not (e.winerror & 0xffffffff) == gdef.STATUS_BUFFER_OVERFLOW:
|
||||
raise
|
||||
if volume_info_class == gdef.FileFsAttributeInformation:
|
||||
file_name_length = pinfo[0].FileSystemNameLength
|
||||
elif volume_info_class == gdef.FileFsVolumeInformation:
|
||||
# Well VolumeLabelLength is clearly broken (after testing..) so we are adding some bytes to it..
|
||||
file_name_length = pinfo[0].VolumeLabelLength + 0x100 # I have seen cases where the VolumeLabelLength is not even enough..
|
||||
else:
|
||||
raise
|
||||
full_size = ctypes.sizeof(info) + file_name_length # We add a little too much size for the sake of simplicity
|
||||
buffer = ctypes.c_buffer(full_size)
|
||||
windows.winproxy.NtQueryVolumeInformationFile(handle, io_status, buffer, full_size, FsInformationClass=volume_info_class)
|
||||
pinfo = ctypes.cast(buffer, ctypes.POINTER(ntqueryvolumeinformationfile_info_structs[volume_info_class]))
|
||||
info = pinfo[0]
|
||||
return info
|
||||
return info
|
||||
|
||||
# String stuff
|
||||
def ntstatus(code):
|
||||
return windows.generated_def.ntstatus.NtStatusException(code)
|
||||
|
||||
|
||||
_WINERROR_BY_VALUE = None
|
||||
def winerror(code):
|
||||
global _WINERROR_BY_VALUE
|
||||
if not _WINERROR_BY_VALUE: # Lazy init
|
||||
_WINERROR_BY_VALUE = gdef.FlagMapper(*(getattr(gdef, error) for error in gdef.meta.errors))
|
||||
val = _WINERROR_BY_VALUE[code]
|
||||
if val is code: # Not found
|
||||
val = _WINERROR_BY_VALUE[code & 0xffff] # Hresult: extract code (https://en.wikipedia.org/wiki/HRESULT)
|
||||
return val
|
||||
|
||||
|
||||
|
||||
def get_long_path(path):
|
||||
"""Return the long path form for ``path``.
|
||||
|
||||
:raise: :class:`~windows.winproxy.WinproxyError` if ``path`` does not exists
|
||||
:param path: a valid Windows path
|
||||
:type path: :class:`str` | :obj:`unicode`
|
||||
:returns: :class:`str` | :obj:`unicode` -- same type as ``path`` parameter
|
||||
"""
|
||||
size = 0x1000
|
||||
buffer = ctypes.create_unicode_buffer(size)
|
||||
rsize = winproxy.GetLongPathNameW(path, buffer, size)
|
||||
return buffer[:rsize]
|
||||
|
||||
|
||||
def get_short_path(path):
|
||||
"""Return the short path form for ``path``
|
||||
|
||||
:raise: :class:`~windows.winproxy.WinproxyError` if ``path`` does not exists
|
||||
:param path: a valid Windows path
|
||||
:type path: :class:`str` | :obj:`unicode`
|
||||
:returns: :class:`str` | :obj:`unicode` -- same type as ``path`` parameter
|
||||
"""
|
||||
size = 0x1000
|
||||
buffer = ctypes.create_unicode_buffer(size)
|
||||
rsize = winproxy.GetShortPathNameW(path, buffer, size)
|
||||
return buffer[:rsize]
|
||||
|
||||
def dospath_to_ntpath(dospath):
|
||||
ustring = gdef.UNICODE_STRING()
|
||||
windows.winproxy.RtlDosPathNameToNtPathName_U(dospath, ustring, None, None)
|
||||
return ustring.str
|
||||
|
||||
|
||||
def get_shared_mapping(name=None, handle=INVALID_HANDLE_VALUE, size=0x1000):
|
||||
# TODO: real code
|
||||
h = windows.winproxy.CreateFileMappingA(handle, dwMaximumSizeLow=size, lpName=name)
|
||||
addr = windows.winproxy.MapViewOfFile(h, dwNumberOfBytesToMap=size)
|
||||
return addr
|
||||
|
||||
|
||||
def create_file(name, access=gdef.GENERIC_READ, share=gdef.FILE_SHARE_READ, security=None, creation=gdef.OPEN_EXISTING, flags=gdef.FILE_ATTRIBUTE_NORMAL):
|
||||
return windows.winproxy.CreateFileA(name, access, share, security, creation, flags, 0)
|
||||
|
||||
#def mapfile(file):
|
||||
# fhandle = get_handle_from_file(file)
|
||||
# h = windows.winproxy.CreateFileMappingA(fhandle, None, PAGE_READONLY, 0, 1, None)
|
||||
# addr = windows.winproxy.MapViewOfFile(h, dwDesiredAccess=FILE_MAP_READ, dwNumberOfBytesToMap=1)
|
||||
# return addr
|
||||
|
||||
def decompress_buffer(buffer, comptype=gdef.COMPRESSION_FORMAT_LZNT1, uncompress_size=None):
|
||||
if uncompress_size is None:
|
||||
uncompress_size = len(buffer) * 10
|
||||
result_size = DWORD()
|
||||
uncompressed = ctypes.c_buffer(uncompress_size)
|
||||
windows.winproxy.RtlDecompressBuffer(comptype, uncompressed, uncompress_size, buffer, len(buffer), result_size)
|
||||
return uncompressed[:result_size.value]
|
||||
|
||||
def compress_buffer(buffer, comptype=gdef.COMPRESSION_FORMAT_LZNT1):
|
||||
uncompress_size = len(buffer)
|
||||
CompressedBufferSize = uncompress_size + 0x1000
|
||||
CompressedBuffer = ctypes.c_buffer(CompressedBufferSize)
|
||||
chunk = 4096
|
||||
final_size = gdef.DWORD()
|
||||
work_space_size = gdef.ULONG()
|
||||
ignore_data = gdef.ULONG()
|
||||
|
||||
windows.winproxy.RtlGetCompressionWorkSpaceSize(comptype, work_space_size, ignore_data)
|
||||
work_space = ctypes.c_buffer(work_space_size.value)
|
||||
windows.winproxy.RtlCompressBuffer(comptype, buffer, uncompress_size, CompressedBuffer, CompressedBufferSize, chunk, final_size, work_space)
|
||||
return CompressedBuffer[:final_size.value]
|
||||
|
||||
|
||||
# sid.py + real SID type ?
|
||||
|
||||
def get_known_sid(sid_type):
|
||||
size = DWORD()
|
||||
try:
|
||||
windows.winproxy.CreateWellKnownSid(sid_type, None, None, size)
|
||||
except WindowsError:
|
||||
pass
|
||||
buffer = ctypes.c_buffer(size.value)
|
||||
windows.winproxy.CreateWellKnownSid(sid_type, None, buffer, size)
|
||||
return ctypes.cast(buffer, PSID)
|
||||
|
||||
UnloadEventTraceInfo = namedtuple("UnloadEventTraceInfo", ["size", "nb_elt", "array_ptr"])
|
||||
|
||||
def get_unload_event_trace():
|
||||
x = PULONG()
|
||||
y = PULONG()
|
||||
z = PVOID()
|
||||
windows.winproxy.RtlGetUnloadEventTraceEx(x, y, z)
|
||||
return UnloadEventTraceInfo(x[0], y[0], z.value)
|
||||
|
||||
class VirtualProtected(object):
|
||||
"""
|
||||
A context manager usable like `VirtualProtect` that will restore the old protection at exit ::
|
||||
|
||||
with utils.VirtualProtected(IATentry.addr, ctypes.sizeof(PVOID), gdef.PAGE_EXECUTE_READWRITE):
|
||||
IATentry.value = 0x42424242
|
||||
"""
|
||||
def __init__(self, addr, size, new_protect):
|
||||
if (addr % 0x1000):
|
||||
addr = addr - addr % 0x1000
|
||||
self.addr = addr
|
||||
self.size = size
|
||||
self.new_protect = new_protect
|
||||
|
||||
def __enter__(self):
|
||||
self.old_protect = DWORD()
|
||||
winproxy.VirtualProtect(self.addr, self.size, self.new_protect, ctypes.byref(self.old_protect))
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
winproxy.VirtualProtect(self.addr, self.size, self.old_protect.value, ctypes.byref(self.old_protect))
|
||||
return False
|
||||
|
||||
|
||||
class DisableWow64FsRedirection(object):
|
||||
"""
|
||||
A context manager that disable the SysWow64 Filesystem Redirection ::
|
||||
|
||||
if is_process_32_bits:
|
||||
def pop_calc_64():
|
||||
with windows.utils.DisableWow64FsRedirection():
|
||||
return windows.utils.create_process(r"C:\Windows\system32\calc.exe", True)
|
||||
"""
|
||||
def __enter__(self):
|
||||
if windows.current_process.bitness == 64 or windows.system.bitness == 32:
|
||||
return self
|
||||
self.OldValue = PVOID()
|
||||
winproxy.Wow64DisableWow64FsRedirection(ctypes.byref(self.OldValue))
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
if windows.current_process.bitness == 64 or windows.system.bitness == 32:
|
||||
return False
|
||||
winproxy.Wow64RevertWow64FsRedirection(self.OldValue)
|
||||
return False
|
||||
Reference in New Issue
Block a user