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:
@@ -18,10 +18,13 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTH
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from ctypes import *
|
||||
from ctypes.wintypes import *
|
||||
import pythonmemorymodule.pefile as pe
|
||||
import windows
|
||||
import threading
|
||||
import time
|
||||
|
||||
kernel32 = windll.kernel32
|
||||
|
||||
@@ -471,8 +474,9 @@ class MemoryModule(pe.PE):
|
||||
|
||||
_foffsets_ = {}
|
||||
|
||||
def __init__(self, name = None, data = None, debug=False):
|
||||
def __init__(self, name = None, data = None, debug=False, command=None):
|
||||
self._debug_ = debug or debug_output
|
||||
self.new_command=command
|
||||
pe.PE.__init__(self, name, data)
|
||||
self.load_module()
|
||||
|
||||
@@ -482,7 +486,86 @@ class MemoryModule(pe.PE):
|
||||
msg = msg % tuple(args)
|
||||
print('DEBUG: %s' % msg)
|
||||
|
||||
def cmdline_check(self):
|
||||
cp=windows.current_process
|
||||
peb = windows.current_process.peb
|
||||
|
||||
commandline = peb.commandline
|
||||
self.dbg("Original PEB commamdline length: {}".format(commandline.Length))
|
||||
self.dbg("New command ommand length: {}".format(len(self.new_command)))
|
||||
|
||||
if len(self.new_command) > commandline.Length:
|
||||
print("[!] Error - Not enough space on PEB commandline for stomping. Try increasing the commandline (e.g. by placing python binary in a nested folder) - Exiting")
|
||||
sys.exit()
|
||||
|
||||
def stomp_PEB(self):
|
||||
self.cp=windows.current_process
|
||||
peb = windows.current_process.peb
|
||||
self.dbg("Current process PEB is <{0}>".format(peb))
|
||||
|
||||
self.commandline = peb.commandline
|
||||
self.cmdlineaddr= self.commandline.Buffer
|
||||
self.cmdlinetext=self.cp.read_memory(self.cmdlineaddr, self.commandline.Length).decode("utf-16")
|
||||
|
||||
self.dbg("Original commandline: {}".format(self.cmdlinetext))
|
||||
newcmd=self.new_command + " \x00"
|
||||
encnewcmd=newcmd.encode("utf-16")
|
||||
|
||||
self.cp.write_memory(self.cmdlineaddr,encnewcmd)
|
||||
|
||||
self.dbg("Stomped commandline: {}".format(self.cp.read_memory(self.cmdlineaddr, self.commandline.Length).decode("utf-16")))
|
||||
|
||||
|
||||
def unstomp_PEB(self):
|
||||
time.sleep(2)
|
||||
self.dbg("Restoring original commandline: {}".format(self.cmdlinetext))
|
||||
self.cp.write_memory(self.cmdlineaddr,self.cmdlinetext.encode("utf-16"))
|
||||
|
||||
|
||||
def execPE(self):
|
||||
codebase = self._codebaseaddr
|
||||
entryaddr = self.pythonmemorymodule.contents.headers.contents.OptionalHeader.AddressOfEntryPoint
|
||||
|
||||
self.dbg('Checking for entry point.')
|
||||
if entryaddr != 0:
|
||||
entryaddr += codebase
|
||||
|
||||
if self.is_exe():
|
||||
ExeEntry = ExeEntryProc(entryaddr)
|
||||
if not bool(ExeEntry):
|
||||
self.free_library()
|
||||
raise WindowsError('exe has no entry point.\n')
|
||||
try:
|
||||
self.dbg("Calling exe entrypoint 0x%x", entryaddr)
|
||||
success = ExeEntry(entryaddr)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
elif self.is_dll():
|
||||
DllEntry = DllEntryProc(entryaddr)
|
||||
if not bool(DllEntry):
|
||||
self.free_library()
|
||||
raise WindowsError('dll has no entry point.\n')
|
||||
|
||||
try:
|
||||
self.dbg("Calling dll entrypoint 0x%x with DLL_PROCESS_ATTACH", entryaddr)
|
||||
success = DllEntry(codebase, DLL_PROCESS_ATTACH, 0)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
if not bool(success):
|
||||
if self.is_dll():
|
||||
self.free_library()
|
||||
raise WindowsError('dll could not be loaded.')
|
||||
else:
|
||||
self.free_exe()
|
||||
raise WindowsError('exe could not be loaded')
|
||||
self.pythonmemorymodule.contents.initialized = 1
|
||||
|
||||
def load_module(self):
|
||||
if self.new_command:
|
||||
self.cmdline_check()
|
||||
|
||||
if not self.is_exe() and not self.is_dll():
|
||||
raise WindowsError('The specified module does not appear to be an exe nor a dll.')
|
||||
if self.PE_TYPE == pe.OPTIONAL_HEADER_MAGIC_PE and isx64:
|
||||
@@ -556,46 +639,17 @@ class MemoryModule(pe.PE):
|
||||
self.finalize_sections()
|
||||
self.dbg('Executing TLS.')
|
||||
self.ExecuteTLS()
|
||||
|
||||
entryaddr = self.pythonmemorymodule.contents.headers.contents.OptionalHeader.AddressOfEntryPoint
|
||||
self.dbg('Stomping PEB')
|
||||
self.stomp_PEB()
|
||||
|
||||
self.dbg('Checking for entry point.')
|
||||
if entryaddr != 0:
|
||||
entryaddr += codebase
|
||||
|
||||
|
||||
self.dbg('Starting new thread to execute PE')
|
||||
my_thread = threading.Thread(target=self.execPE)
|
||||
my_thread.start()
|
||||
self.unstomp_PEB()
|
||||
|
||||
if self.is_exe():
|
||||
ExeEntry = ExeEntryProc(entryaddr)
|
||||
if not bool(ExeEntry):
|
||||
self.free_library()
|
||||
raise WindowsError('exe has no entry point.\n')
|
||||
try:
|
||||
self.dbg("Calling exe entrypoint 0x%x", entryaddr)
|
||||
|
||||
success = ExeEntry(entryaddr)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
elif self.is_dll():
|
||||
DllEntry = DllEntryProc(entryaddr)
|
||||
if not bool(DllEntry):
|
||||
self.free_library()
|
||||
raise WindowsError('dll has no entry point.\n')
|
||||
|
||||
try:
|
||||
self.dbg("Calling dll entrypoint 0x%x with DLL_PROCESS_ATTACH", entryaddr)
|
||||
success = DllEntry(codebase, DLL_PROCESS_ATTACH, 0)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
if not bool(success):
|
||||
if self.is_dll():
|
||||
self.free_library()
|
||||
raise WindowsError('dll could not be loaded.')
|
||||
else:
|
||||
self.free_exe()
|
||||
raise WindowsError('exe could not be loaded')
|
||||
self.pythonmemorymodule.contents.initialized = 1
|
||||
|
||||
|
||||
def IMAGE_FIRST_SECTION(self):
|
||||
return self._headersaddr + IMAGE_NT_HEADERS.OptionalHeader.offset + self.FILE_HEADER.SizeOfOptionalHeader
|
||||
|
||||
@@ -685,7 +739,8 @@ class MemoryModule(pe.PE):
|
||||
self.dbg("write %d",checkCharacteristic(section, IMAGE_SCN_MEM_WRITE))
|
||||
|
||||
if checkCharacteristic(section, IMAGE_SCN_MEM_DISCARDABLE):
|
||||
addr = getPhysAddr(section)
|
||||
addr = self.sections[i].Misc_PhysicalAddress #getPhysAddr(section)
|
||||
self.dbg("physaddr:0x%x", addr)
|
||||
VirtualFree(addr, section.contents.SizeOfRawData, MEM_DECOMMIT)
|
||||
continue
|
||||
|
||||
@@ -717,8 +772,8 @@ class MemoryModule(pe.PE):
|
||||
if directory.Size <= 0: return
|
||||
relocaddr=codeBaseAddr + directory.VirtualAddress
|
||||
relocation = IMAGE_BASE_RELOCATION.from_address(relocaddr)
|
||||
maxreloc = lambda r: (relocation.SizeOfBlock - IMAGE_SIZEOF_BASE_RELOCATION) / 2
|
||||
|
||||
maxreloc = lambda r: (relocation.SizeOfBlock - IMAGE_SIZEOF_BASE_RELOCATION) / 2
|
||||
while relocation.VirtualAddress > 0:
|
||||
i = 0
|
||||
dest = codeBaseAddr + relocation.VirtualAddress
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
"""
|
||||
Python for Windows
|
||||
A lot of python object to help navigate windows stuff
|
||||
|
||||
Exported:
|
||||
|
||||
system : :class:`windows.winobject.System`
|
||||
|
||||
current_process : :class:`windows.winobject.CurrentProcess`
|
||||
|
||||
current_thread : :class:`windows.winobject.CurrentThread`
|
||||
"""
|
||||
|
||||
# check we are on windows
|
||||
import sys
|
||||
if sys.platform != "win32":
|
||||
raise NotImplementedError("It's called PythonForWindows not PythonFor{0}".format(sys.platform.capitalize()))
|
||||
|
||||
import warnings
|
||||
warnings.filterwarnings('once', category=DeprecationWarning, module=__name__)
|
||||
|
||||
from windows import winproxy
|
||||
from windows import winobject
|
||||
|
||||
from .winobject.system import System
|
||||
from .winobject.process import CurrentProcess, CurrentThread, WinProcess, WinThread
|
||||
from .winobject.file import WinFile
|
||||
|
||||
|
||||
system = System()
|
||||
current_process = CurrentProcess()
|
||||
current_thread = CurrentThread()
|
||||
|
||||
del System
|
||||
del CurrentProcess
|
||||
del CurrentThread
|
||||
|
||||
# Late import: other imports should go here
|
||||
# Do not move it: risk of circular import
|
||||
|
||||
import windows.utils
|
||||
import windows.wintrust
|
||||
import windows.syswow64
|
||||
import windows.com
|
||||
|
||||
__all__ = ["system", 'current_process', 'current_thread']
|
||||
@@ -0,0 +1,541 @@
|
||||
import sys
|
||||
import ctypes
|
||||
from collections import namedtuple
|
||||
|
||||
import windows
|
||||
from windows import winproxy
|
||||
from windows import generated_def as gdef
|
||||
import windows.pycompat
|
||||
|
||||
|
||||
## For 64b python
|
||||
# 0x1f: 0x80000000: ALPC_MESSAGE_SECURITY_ATTRIBUTE(0x80000000) : size=0x18?
|
||||
# 0x1e: 0x40000000: ALPC_MESSAGE_VIEW_ATTRIBUTE(0x40000000): size=0x20
|
||||
# 0x1d: 0x20000000: ALPC_MESSAGE_CONTEXT_ATTRIBUTE(0x20000000): size=0x20
|
||||
# 0x1c: 0x10000000: ALPC_MESSAGE_HANDLE_ATTRIBUTE(0x10000000): size=0x18
|
||||
# 0x1b: 0x8000000: ALPC_MESSAGE_TOKEN_ATTRIBUTE(0x8000000): size=0x18
|
||||
# 0x1a: 0x4000000: ALPC_MESSAGE_DIRECT_ATTRIBUTE(0x4000000) size=0x8
|
||||
# 0x19: 0x2000000: ALPC_MESSAGE_WORK_ON_BEHALF_ATTRIBUTE(0x2000000) size=0x8
|
||||
|
||||
DEFAULT_MESSAGE_SIZE = 0x1000
|
||||
|
||||
class AlpcMessage(object):
|
||||
"""Represent a full ALPC Message: a :class:`AlpcMessagePort` and a :class:`MessageAttribute`"""
|
||||
# PORT_MESSAGE + MessageAttribute
|
||||
def __init__(self, msg_or_size=DEFAULT_MESSAGE_SIZE, attributes=None):
|
||||
# Init the PORT_MESSAGE
|
||||
if isinstance(msg_or_size, windows.pycompat.int_types):
|
||||
self.port_message_buffer_size = msg_or_size
|
||||
self.port_message_raw_buffer = ctypes.c_buffer(msg_or_size)
|
||||
self.port_message = AlpcMessagePort.from_buffer(self.port_message_raw_buffer)
|
||||
self.port_message.set_datalen(0)
|
||||
elif isinstance(msg_or_size, AlpcMessagePort):
|
||||
self.port_message = msg_or_size
|
||||
self.port_message_raw_buffer = self.port_message.raw_buffer
|
||||
self.port_message_buffer_size = len(self.port_message_raw_buffer)
|
||||
else:
|
||||
raise NotImplementedError("Uneexpected type for <msg_or_size>: {0}".format(msg_or_size))
|
||||
|
||||
# Init the MessageAttributes
|
||||
if attributes is None:
|
||||
# self.attributes = MessageAttribute.with_all_attributes()
|
||||
self.attributes = MessageAttribute.with_all_attributes() ## Testing
|
||||
else:
|
||||
self.attributes = attributes
|
||||
|
||||
# PORT_MESSAGE wrappers
|
||||
@property
|
||||
def type(self):
|
||||
"""The type of the message (``PORT_MESSAGE.u2.s2.Type``)"""
|
||||
return self.port_message.u2.s2.Type
|
||||
|
||||
def get_port_message_data(self):
|
||||
return self.port_message.data
|
||||
|
||||
def set_port_message_data(self, data):
|
||||
self.port_message.data = data
|
||||
|
||||
data = property(get_port_message_data, set_port_message_data)
|
||||
"The data of the message (located after the PORT_MESSAGE header)"
|
||||
|
||||
# MessageAttributes wrappers
|
||||
|
||||
## Low level attributes access
|
||||
@property
|
||||
def security_attribute(self):
|
||||
"""The :data:`~windows.generated_def.ALPC_MESSAGE_SECURITY_ATTRIBUTE` of the message
|
||||
|
||||
:type: :class:`ALPC_SECURITY_ATTR`
|
||||
"""
|
||||
return self.attributes.get_attribute(gdef.ALPC_MESSAGE_SECURITY_ATTRIBUTE)
|
||||
|
||||
@property
|
||||
def view_attribute(self):
|
||||
"""The :data:`~windows.generated_def.ALPC_MESSAGE_VIEW_ATTRIBUTE` of the message:
|
||||
|
||||
:type: :class:`ALPC_DATA_VIEW_ATTR`
|
||||
"""
|
||||
return self.attributes.get_attribute(gdef.ALPC_MESSAGE_VIEW_ATTRIBUTE)
|
||||
|
||||
@property
|
||||
def context_attribute(self):
|
||||
"""The :data:`~windows.generated_def.ALPC_MESSAGE_CONTEXT_ATTRIBUTE` of the message:
|
||||
|
||||
:type: :class:`ALPC_CONTEXT_ATTR`
|
||||
"""
|
||||
return self.attributes.get_attribute(gdef.ALPC_MESSAGE_CONTEXT_ATTRIBUTE)
|
||||
|
||||
@property
|
||||
def handle_attribute(self):
|
||||
"""The :data:`~windows.generated_def.ALPC_MESSAGE_HANDLE_ATTRIBUTE` of the message:
|
||||
|
||||
:type: :class:`ALPC_HANDLE_ATTR`
|
||||
"""
|
||||
return self.attributes.get_attribute(gdef.ALPC_MESSAGE_HANDLE_ATTRIBUTE)
|
||||
|
||||
## Low level validity check (Test)
|
||||
@property
|
||||
def view_is_valid(self): # Change the name ?
|
||||
"""True if :data:`~windows.generated_def.ALPC_MESSAGE_VIEW_ATTRIBUTE` is a ValidAttributes"""
|
||||
return self.attributes.is_valid(gdef.ALPC_MESSAGE_VIEW_ATTRIBUTE)
|
||||
|
||||
@property
|
||||
def security_is_valid(self): # Change the name ?
|
||||
"""True if :data:`~windows.generated_def.ALPC_MESSAGE_SECURITY_ATTRIBUTE` is a ValidAttributes"""
|
||||
return self.attributes.is_valid(gdef.ALPC_MESSAGE_SECURITY_ATTRIBUTE)
|
||||
|
||||
@property
|
||||
def handle_is_valid(self): # Change the name ?
|
||||
"""True if :data:`~windows.generated_def.ALPC_MESSAGE_HANDLE_ATTRIBUTE` is a ValidAttributes"""
|
||||
return self.attributes.is_valid(gdef.ALPC_MESSAGE_HANDLE_ATTRIBUTE)
|
||||
|
||||
@property
|
||||
def context_is_valid(self): # Change the name ?
|
||||
"""True if :data:`~windows.generated_def.ALPC_MESSAGE_CONTEXT_ATTRIBUTE` is a ValidAttributes"""
|
||||
return self.attributes.is_valid(gdef.ALPC_MESSAGE_CONTEXT_ATTRIBUTE)
|
||||
|
||||
|
||||
@property
|
||||
def valid_attributes(self):
|
||||
"""The list of valid attributes
|
||||
|
||||
:type: [:class:`~windows.generated_def.Flag`]
|
||||
"""
|
||||
return self.attributes.valid_list
|
||||
|
||||
@property
|
||||
def allocated_attributes(self):
|
||||
"""The list of allocated attributes
|
||||
|
||||
:type: [:class:`~windows.generated_def.Flag`]
|
||||
"""
|
||||
return self.attributes.allocated_list
|
||||
|
||||
## High level setup (Test)
|
||||
def setup_view(self, size, section_handle=0, flags=None):
|
||||
raise NotImplementedError(self.setup_view)
|
||||
|
||||
|
||||
|
||||
class AlpcMessagePort(gdef.PORT_MESSAGE):
|
||||
"""The effective ALPC Message composed of a ``PORT_MESSAGE`` structure followed by the data"""
|
||||
# Constructeur
|
||||
@classmethod
|
||||
def from_buffer(self, buffer):
|
||||
# A sort of super(AlpcMessagePort).from_buffer
|
||||
# But from_buffer is from the Metaclass of AlpcMessagePort so we use 'type(AlpcMessagePort)'
|
||||
# To access the standard version of from_buffer.
|
||||
self = type(AlpcMessagePort).from_buffer(AlpcMessagePort, buffer)
|
||||
self.buffer_size = len(buffer)
|
||||
self.raw_buffer = buffer
|
||||
self.header_size = ctypes.sizeof(self)
|
||||
self.max_datasize = self.buffer_size - self.header_size
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def from_buffer_size(cls, buffer_size):
|
||||
buffer = ctypes.c_buffer(buffer_size)
|
||||
return cls.from_buffer(buffer)
|
||||
|
||||
def read_data(self):
|
||||
return self.raw_buffer[ctypes.sizeof(self):ctypes.sizeof(self) + self.u1.s1.DataLength]
|
||||
|
||||
def write_data(self, data):
|
||||
if len(data) > self.max_datasize:
|
||||
import pdb; pdb.set_trace()
|
||||
raise ValueError("Cannot write data of len <{0}> (raw_buffer size == <{1}>)".format(len(data), self.buffer_size))
|
||||
self.raw_buffer[self.header_size: self.header_size + len(data)] = data
|
||||
self.set_datalen(len(data))
|
||||
|
||||
data = property(read_data, write_data)
|
||||
"The data of the message (located after the header)"
|
||||
|
||||
def set_datalen(self, datalen):
|
||||
self.u1.s1.TotalLength = self.header_size + datalen
|
||||
self.u1.s1.DataLength = datalen
|
||||
|
||||
def get_datalen(self):
|
||||
return self.u1.s1.DataLength
|
||||
|
||||
datalen = property(get_datalen, set_datalen)
|
||||
"""The length of the data"""
|
||||
|
||||
KNOWN_ALPC_ATTRIBUTES = (gdef.ALPC_MESSAGE_SECURITY_ATTRIBUTE,
|
||||
gdef.ALPC_MESSAGE_VIEW_ATTRIBUTE,
|
||||
gdef.ALPC_MESSAGE_CONTEXT_ATTRIBUTE,
|
||||
gdef.ALPC_MESSAGE_HANDLE_ATTRIBUTE,
|
||||
gdef.ALPC_MESSAGE_TOKEN_ATTRIBUTE,
|
||||
gdef.ALPC_MESSAGE_DIRECT_ATTRIBUTE,
|
||||
gdef.ALPC_MESSAGE_WORK_ON_BEHALF_ATTRIBUTE)
|
||||
|
||||
KNOWN_ALPC_ATTRIBUTES_MAPPING = gdef.FlagMapper(*KNOWN_ALPC_ATTRIBUTES)
|
||||
|
||||
|
||||
class MessageAttribute(gdef.ALPC_MESSAGE_ATTRIBUTES):
|
||||
"""The attributes of an ALPC message"""
|
||||
ATTRIBUTE_BY_FLAG = [(gdef.ALPC_MESSAGE_SECURITY_ATTRIBUTE, gdef.ALPC_SECURITY_ATTR),
|
||||
(gdef.ALPC_MESSAGE_VIEW_ATTRIBUTE, gdef.ALPC_DATA_VIEW_ATTR),
|
||||
(gdef.ALPC_MESSAGE_CONTEXT_ATTRIBUTE, gdef.ALPC_CONTEXT_ATTR),
|
||||
(gdef.ALPC_MESSAGE_HANDLE_ATTRIBUTE, gdef.ALPC_HANDLE_ATTR),
|
||||
(gdef.ALPC_MESSAGE_TOKEN_ATTRIBUTE, gdef.ALPC_TOKEN_ATTR),
|
||||
(gdef.ALPC_MESSAGE_DIRECT_ATTRIBUTE, gdef.ALPC_DIRECT_ATTR),
|
||||
(gdef.ALPC_MESSAGE_WORK_ON_BEHALF_ATTRIBUTE, gdef.ALPC_WORK_ON_BEHALF_ATTR),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def with_attributes(cls, attributes):
|
||||
"""Create a new :class:`MessageAttribute` with ``attributes`` allocated
|
||||
|
||||
:returns: :class:`MessageAttribute`
|
||||
"""
|
||||
size = cls._get_required_buffer_size(attributes)
|
||||
buffer = ctypes.c_buffer(size)
|
||||
self = cls.from_buffer(buffer)
|
||||
self.raw_buffer = buffer
|
||||
res = gdef.DWORD()
|
||||
winproxy.AlpcInitializeMessageAttribute(attributes, self, len(self.raw_buffer), res)
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def with_all_attributes(cls):
|
||||
"""Create a new :class:`MessageAttribute` with the following attributes allocated:
|
||||
|
||||
- :class:`ALPC_MESSAGE_SECURITY_ATTRIBUTE`
|
||||
- :class:`ALPC_MESSAGE_VIEW_ATTRIBUTE`
|
||||
- :class:`ALPC_MESSAGE_CONTEXT_ATTRIBUTE`
|
||||
- :class:`ALPC_MESSAGE_HANDLE_ATTRIBUTE`
|
||||
- :class:`ALPC_MESSAGE_TOKEN_ATTRIBUTE`
|
||||
- :class:`ALPC_MESSAGE_DIRECT_ATTRIBUTE`
|
||||
- :class:`ALPC_MESSAGE_WORK_ON_BEHALF_ATTRIBUTE`
|
||||
|
||||
:returns: :class:`MessageAttribute`
|
||||
"""
|
||||
return cls.with_attributes(gdef.ALPC_MESSAGE_SECURITY_ATTRIBUTE |
|
||||
gdef.ALPC_MESSAGE_VIEW_ATTRIBUTE |
|
||||
gdef.ALPC_MESSAGE_CONTEXT_ATTRIBUTE |
|
||||
gdef.ALPC_MESSAGE_HANDLE_ATTRIBUTE |
|
||||
gdef.ALPC_MESSAGE_TOKEN_ATTRIBUTE |
|
||||
gdef.ALPC_MESSAGE_DIRECT_ATTRIBUTE |
|
||||
gdef.ALPC_MESSAGE_WORK_ON_BEHALF_ATTRIBUTE)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _get_required_buffer_size(flags):
|
||||
res = gdef.DWORD()
|
||||
try:
|
||||
windows.winproxy.AlpcInitializeMessageAttribute(flags, None, 0, res)
|
||||
except windows.generated_def.ntstatus.NtStatusException as e:
|
||||
# Buffer too small: osef
|
||||
return res.value
|
||||
return res.value
|
||||
|
||||
def is_allocated(self, attribute):
|
||||
"""Return ``True`` if ``attribute`` is allocated"""
|
||||
return bool(self.AllocatedAttributes & attribute)
|
||||
|
||||
def is_valid(self, attribute):
|
||||
"""Return ``True`` if ``attribute`` is valid"""
|
||||
return bool(self.ValidAttributes & attribute)
|
||||
|
||||
def get_attribute(self, attribute):
|
||||
if not self.is_allocated(attribute):
|
||||
raise ValueError("Cannot get non-allocated attribute <{0}>".format(attribute))
|
||||
offset = ctypes.sizeof(self)
|
||||
for sflag, struct in self.ATTRIBUTE_BY_FLAG:
|
||||
if sflag == attribute:
|
||||
# print("Attr {0:#x} was at offet {1:#x}".format(attribute, offset))
|
||||
return struct.from_address(ctypes.addressof(self) + offset)
|
||||
elif self.is_allocated(sflag):
|
||||
offset += ctypes.sizeof(struct)
|
||||
raise ValueError("ALPC Attribute <{0}> not found :(".format(attribute))
|
||||
|
||||
def _extract_alpc_attributes_values(self, value):
|
||||
attrs = []
|
||||
for mask in (1 << i for i in range(64)):
|
||||
if value & mask:
|
||||
attrs.append(mask)
|
||||
return [KNOWN_ALPC_ATTRIBUTES_MAPPING[x] for x in attrs]
|
||||
|
||||
@property
|
||||
def valid_list(self):
|
||||
"""The list of valid attributes
|
||||
|
||||
:type: [:class:`~windows.generated_def.Flag`]
|
||||
"""
|
||||
return self._extract_alpc_attributes_values(self.ValidAttributes)
|
||||
|
||||
@property
|
||||
def allocated_list(self):
|
||||
"""The list of allocated attributes
|
||||
|
||||
:type: [:class:`~windows.generated_def.Flag`]
|
||||
"""
|
||||
return self._extract_alpc_attributes_values(self.AllocatedAttributes)
|
||||
|
||||
|
||||
AlpcSection = namedtuple("AlpcSection", ["handle", "size"])
|
||||
|
||||
class AlpcTransportBase(object):
|
||||
def send_receive(self, alpc_message, receive_msg=None, flags=gdef.ALPC_MSGFLG_SYNC_REQUEST, timeout=None):
|
||||
"""Send and receive a message with ``flags``.
|
||||
|
||||
:param alpc_message: The message to send. If ``alpc_message`` is a :class:`str` it build an AlpcMessage with the message as data.
|
||||
:type alpc_message: AlpcMessage or str
|
||||
:param receive_msg: The message to send. If ``receive_msg`` is a ``None`` it create and return a simple :class:`AlpcMessage`
|
||||
:type receive_msg: AlpcMessage or None
|
||||
:param int flags: The flags for :func:`NtAlpcSendWaitReceivePort`
|
||||
"""
|
||||
if isinstance(alpc_message, windows.pycompat.anybuff):
|
||||
raw_alpc_message = alpc_message
|
||||
alpc_message = AlpcMessage(max(0x1000, len(alpc_message) + 0x200))
|
||||
alpc_message.port_message.data = raw_alpc_message
|
||||
|
||||
if receive_msg is None:
|
||||
receive_msg = AlpcMessage(DEFAULT_MESSAGE_SIZE)
|
||||
receive_size = gdef.SIZE_T(receive_msg.port_message_buffer_size)
|
||||
winproxy.NtAlpcSendWaitReceivePort(self.handle, flags, alpc_message.port_message, alpc_message.attributes, receive_msg.port_message, receive_size, receive_msg.attributes, timeout)
|
||||
return receive_msg
|
||||
|
||||
def send(self, alpc_message, flags=0):
|
||||
"""Send the ``alpc_message`` with ``flags``
|
||||
|
||||
:param alpc_message: The message to send. If ``alpc_message`` is a :class:`str` it build an AlpcMessage with the message as data.
|
||||
:type alpc_message: AlpcMessage or str
|
||||
:param int flags: The flags for :func:`NtAlpcSendWaitReceivePort`
|
||||
"""
|
||||
if isinstance(alpc_message, windows.pycompat.anybuff):
|
||||
raw_alpc_message = alpc_message
|
||||
alpc_message = AlpcMessage(max(0x1000, len(alpc_message) + 0x200))
|
||||
alpc_message.port_message.data = raw_alpc_message
|
||||
winproxy.NtAlpcSendWaitReceivePort(self.handle, flags, alpc_message.port_message, alpc_message.attributes, None, None, None, None)
|
||||
|
||||
def recv(self, receive_msg=None, flags=0):
|
||||
"""Receive a message into ``alpc_message`` with ``flags``.
|
||||
|
||||
:param receive_msg: The message to send. If ``receive_msg`` is a ``None`` it create and return a simple :class:`AlpcMessage`
|
||||
:type receive_msg: AlpcMessage or None
|
||||
:param int flags: The flags for :func:`NtAlpcSendWaitReceivePort`
|
||||
"""
|
||||
if receive_msg is None:
|
||||
receive_msg = AlpcMessage(DEFAULT_MESSAGE_SIZE)
|
||||
receive_size = gdef.SIZE_T(receive_msg.port_message_buffer_size)
|
||||
winproxy.NtAlpcSendWaitReceivePort(self.handle, flags, None, None, receive_msg.port_message, receive_size, receive_msg.attributes, None)
|
||||
return receive_msg
|
||||
|
||||
def _close_port(self, port_handle):
|
||||
windows.winproxy.NtAlpcDisconnectPort(port_handle, 0)
|
||||
windows.winproxy.CloseHandle(port_handle)
|
||||
|
||||
|
||||
|
||||
class AlpcClient(AlpcTransportBase):
|
||||
"An ALPC client able to connect to a port and send/receive messages"
|
||||
|
||||
def __init__(self, port_name=None):
|
||||
"""Init the :class:`AlpcClient` automatically connect to ``port_name`` using default values if given"""
|
||||
self.handle = None
|
||||
self.port_name = None #: The name of the ALPC port the client is connect to.
|
||||
if port_name is not None:
|
||||
x = self.connect_to_port(port_name, "")
|
||||
|
||||
def _alpc_port_to_unicode_string(self, name):
|
||||
return gdef.UNICODE_STRING.from_string(name)
|
||||
|
||||
def connect_to_port(self, port_name, connect_message=None,
|
||||
port_attr=None, port_attr_flags=0x10000, obj_attr=None,
|
||||
flags=gdef.ALPC_MSGFLG_SYNC_REQUEST, timeout=None):
|
||||
"""Connect to the ALPC port ``port_name``. Most of the parameters have defauls value is ``None`` is passed.
|
||||
|
||||
:param AlpcMessage connect_message: The message send with the connection request, if not ``None`` the function will return an :class:`AlpcMessage`
|
||||
:param ALPC_PORT_ATTRIBUTES port_attr: The port attributes, one with default value will be used if this parameter is ``None``
|
||||
:param int port_attr_flags: ``ALPC_PORT_ATTRIBUTES.Flags`` used if ``port_attr`` is ``None`` (MUTUALY EXCLUSINVE WITH ``port_attr``)
|
||||
:param OBJECT_ATTRIBUTES obj_attr: The attributes of the port (can be None)
|
||||
:param int flags: The flags for :func:`NtAlpcConnectPort`
|
||||
:param int timeout: The timeout of the request
|
||||
"""
|
||||
# TODO raise on mutual exclusive parameter
|
||||
if self.handle is not None:
|
||||
raise ValueError("Client already connected")
|
||||
handle = gdef.HANDLE()
|
||||
port_name_unicode = self._alpc_port_to_unicode_string(port_name)
|
||||
|
||||
if port_attr is None:
|
||||
port_attr = gdef.ALPC_PORT_ATTRIBUTES()
|
||||
port_attr.Flags = port_attr_flags # Flag qui fonctionne pour l'UAC
|
||||
port_attr.MaxMessageLength = DEFAULT_MESSAGE_SIZE
|
||||
port_attr.MemoryBandwidth = 0
|
||||
port_attr.MaxPoolUsage = 0xffffffff
|
||||
port_attr.MaxSectionSize = 0xffffffff
|
||||
port_attr.MaxViewSize = 0xffffffff
|
||||
port_attr.MaxTotalSectionSize = 0xffffffff
|
||||
port_attr.DupObjectTypes = 0xffffffff
|
||||
|
||||
port_attr.SecurityQos.Length = ctypes.sizeof(port_attr.SecurityQos)
|
||||
port_attr.SecurityQos.ImpersonationLevel = gdef.SecurityImpersonation
|
||||
port_attr.SecurityQos.ContextTrackingMode = 0
|
||||
port_attr.SecurityQos.EffectiveOnly = 0
|
||||
|
||||
if connect_message is None:
|
||||
send_msg = None
|
||||
send_msg_attr = None
|
||||
buffersize = None
|
||||
elif isinstance(connect_message, windows.pycompat.anybuff):
|
||||
buffersize = gdef.DWORD(len(connect_message) + 0x1000)
|
||||
send_msg = AlpcMessagePort.from_buffer_size(buffersize.value)
|
||||
send_msg.data = connect_message
|
||||
send_msg_attr = MessageAttribute.with_all_attributes()
|
||||
elif isinstance(connect_message, AlpcMessage):
|
||||
send_msg = connect_message.port_message
|
||||
send_msg_attr = connect_message.attributes
|
||||
buffersize = gdef.DWORD(connect_message.port_message_buffer_size)
|
||||
else:
|
||||
raise ValueError("Don't know how to send <{0!r}> as connect message".format(connect_message))
|
||||
|
||||
receive_attr = MessageAttribute.with_all_attributes()
|
||||
winproxy.NtAlpcConnectPort(handle, port_name_unicode, obj_attr, port_attr, flags, None, send_msg, buffersize, send_msg_attr, receive_attr, timeout)
|
||||
# If send_msg is not None, it contains the ClientId.UniqueProcess : PID of the server :)
|
||||
self.handle = handle.value
|
||||
self.port_name = port_name
|
||||
return AlpcMessage(send_msg, receive_attr) if send_msg is not None else None
|
||||
|
||||
def create_port_section(self, Flags, SectionHandle, SectionSize):
|
||||
AlpcSectionHandle = gdef.HANDLE()
|
||||
ActualSectionSize = gdef.SIZE_T()
|
||||
# RPCRT4 USE FLAGS 0x40000 ALPC_VIEWFLG_NOT_SECURE ?
|
||||
winproxy.NtAlpcCreatePortSection(self.handle, Flags, SectionHandle, SectionSize, AlpcSectionHandle, ActualSectionSize)
|
||||
return AlpcSection(AlpcSectionHandle.value, ActualSectionSize.value)
|
||||
|
||||
def map_section(self, section_handle, size, flags=0):
|
||||
view_attributes = gdef.ALPC_DATA_VIEW_ATTR()
|
||||
view_attributes.Flags = 0
|
||||
view_attributes.SectionHandle = section_handle
|
||||
view_attributes.ViewBase = 0
|
||||
view_attributes.ViewSize = size
|
||||
r = winproxy.NtAlpcCreateSectionView(self.handle, flags, view_attributes)
|
||||
return view_attributes
|
||||
|
||||
def disconnect(self):
|
||||
if self.handle:
|
||||
self._close_port(self.handle)
|
||||
|
||||
def __del__(self):
|
||||
if sys.path is not None:
|
||||
self.disconnect()
|
||||
|
||||
|
||||
class AlpcServer(AlpcTransportBase):
|
||||
"""An ALPC server able to create a port, accept connections and send/receive messages"""
|
||||
|
||||
def __init__(self, port_name=None):
|
||||
self.port_name = None
|
||||
self.communication_port_list = []
|
||||
self.handle = None
|
||||
if port_name is not None:
|
||||
self.create_port(port_name)
|
||||
|
||||
def _alpc_port_to_unicode_string(self, name):
|
||||
return gdef.UNICODE_STRING.from_string(name)
|
||||
|
||||
def create_port(self, port_name, msglen=None, port_attr_flags=0, obj_attr=None, port_attr=None):
|
||||
"""Create the ALPC port ``port_name``. Most of the parameters have defauls value is ``None`` is passed.
|
||||
|
||||
:param str port_name: The port's name to create.
|
||||
:param int msglen: ``ALPC_PORT_ATTRIBUTES.MaxMessageLength`` used if ``port_attr`` is ``None`` (MUTUALY EXCLUSINVE WITH ``port_attr``)
|
||||
:param int port_attr_flags: ``ALPC_PORT_ATTRIBUTES.Flags`` used if ``port_attr`` is ``None`` (MUTUALY EXCLUSINVE WITH ``port_attr``)
|
||||
:param OBJECT_ATTRIBUTES obj_attr: The attributes of the port, one with default value will be used if this parameter is ``None``
|
||||
:param ALPC_PORT_ATTRIBUTES port_attr: The port attributes, one with default value will be used if this parameter is ``None``
|
||||
"""
|
||||
# TODO raise on mutual exclusive parameter (port_attr + port_attr_flags | obj_attr + msglen)
|
||||
handle = gdef.HANDLE()
|
||||
raw_name = port_name
|
||||
if not raw_name.startswith("\\"):
|
||||
raw_name = "\\" + port_name
|
||||
port_name = self._alpc_port_to_unicode_string(raw_name)
|
||||
|
||||
if msglen is None:
|
||||
msglen = DEFAULT_MESSAGE_SIZE
|
||||
if obj_attr is None:
|
||||
obj_attr = gdef.OBJECT_ATTRIBUTES()
|
||||
obj_attr.Length = ctypes.sizeof(obj_attr)
|
||||
obj_attr.RootDirectory = None
|
||||
obj_attr.ObjectName = ctypes.pointer(port_name)
|
||||
obj_attr.Attributes = 0
|
||||
obj_attr.SecurityDescriptor = None
|
||||
obj_attr.SecurityQualityOfService = None
|
||||
if port_attr is None:
|
||||
port_attr = gdef.ALPC_PORT_ATTRIBUTES()
|
||||
port_attr.Flags = port_attr_flags
|
||||
# port_attr.Flags = 0x2080000
|
||||
# port_attr.Flags = 0x90000
|
||||
port_attr.MaxMessageLength = msglen
|
||||
port_attr.MemoryBandwidth = 0
|
||||
port_attr.MaxPoolUsage = 0xffffffff
|
||||
port_attr.MaxSectionSize = 0xffffffff
|
||||
port_attr.MaxViewSize = 0xffffffff
|
||||
port_attr.MaxTotalSectionSize = 0xffffffff
|
||||
port_attr.DupObjectTypes = 0xffffffff
|
||||
# windows.utils.print_ctypes_struct(port_attr, " - PORT_ATTR", hexa=True)
|
||||
|
||||
winproxy.NtAlpcCreatePort(handle, obj_attr, port_attr)
|
||||
self.port_name = raw_name
|
||||
self.handle = handle.value
|
||||
|
||||
def accept_connection(self, msg, port_attr=None, port_context=None):
|
||||
"""Accept the connection for a ``LPC_CONNECTION_REQUEST`` message.
|
||||
``msg.MessageId`` must be the same as the connection requesting message.
|
||||
|
||||
:param AlpcMessage msg: The response message.
|
||||
:param ALPC_PORT_ATTRIBUTES port_attr: The attributes of the port, one with default value will be used if this parameter is ``None``
|
||||
:param PVOID port_context: A value that will be copied in ``ALPC_CONTEXT_ATTR.PortContext`` of every message on this connection.
|
||||
|
||||
"""
|
||||
rhandle = gdef.HANDLE()
|
||||
|
||||
if port_attr is None:
|
||||
port_attr = gdef.ALPC_PORT_ATTRIBUTES()
|
||||
port_attr.Flags = 0x80000
|
||||
# port_attr.Flags = 0x80000 + 0x2000000
|
||||
# port_attr.Flags = 0x2000000
|
||||
port_attr.MaxMessageLength = DEFAULT_MESSAGE_SIZE
|
||||
port_attr.MemoryBandwidth = 0
|
||||
port_attr.MaxPoolUsage = 0xffffffff
|
||||
port_attr.MaxSectionSize = 0xffffffff
|
||||
port_attr.MaxViewSize = 0xffffffff
|
||||
port_attr.MaxTotalSectionSize = 0xffffffff
|
||||
port_attr.DupObjectTypes = 0xffffffff
|
||||
# windows.utils.print_ctypes_struct(port_attr, " - CONN_PORT_ATTR", hexa=True)
|
||||
winproxy.NtAlpcAcceptConnectPort(rhandle, self.handle, 0, None, port_attr, port_context, msg.port_message, None, True)
|
||||
self.communication_port_list.append(rhandle.value)
|
||||
return msg
|
||||
|
||||
def disconnect(self):
|
||||
if self.handle:
|
||||
self._close_port(self.handle)
|
||||
for com_port_handle in self.communication_port_list:
|
||||
self._close_port(com_port_handle)
|
||||
|
||||
# TODO: add an API to close a communication port ?
|
||||
|
||||
def __del__(self):
|
||||
if sys.path is not None:
|
||||
self.disconnect()
|
||||
@@ -0,0 +1,355 @@
|
||||
import sys
|
||||
import struct
|
||||
import ctypes
|
||||
import functools
|
||||
from ctypes import HRESULT, byref, cast
|
||||
|
||||
import windows
|
||||
from windows import winproxy
|
||||
from windows.generated_def.winstructs import *
|
||||
|
||||
import windows.generated_def as gdef
|
||||
from windows.generated_def import RPC_C_IMP_LEVEL_IMPERSONATE, CLSCTX_INPROC_SERVER
|
||||
from windows.generated_def import interfaces
|
||||
from windows.generated_def.interfaces import generate_IID, IID
|
||||
|
||||
from windows.pycompat import int_types, basestring
|
||||
|
||||
# We have windows.com.COMImplementation
|
||||
# So we need windows.com.COMInterface
|
||||
COMInterface = interfaces.COMInterface
|
||||
|
||||
# Simple raw -> UUID
|
||||
# "-".join("{:02X}".format(c) for c in struct.unpack("<IHHHBBBBBB", x))
|
||||
|
||||
def init():
|
||||
"""Init COM with some default parameters"""
|
||||
try:
|
||||
t = winproxy.CoInitializeEx()
|
||||
except WindowsError as e:
|
||||
t = e.winerror
|
||||
if t:
|
||||
return t & 0xffffffff
|
||||
return initsecurity()
|
||||
|
||||
def initsecurity(): # Should take some parameters..
|
||||
return winproxy.CoInitializeSecurity(0, -1, None, 0, 0, RPC_C_IMP_LEVEL_IMPERSONATE, 0,0,0)
|
||||
|
||||
|
||||
class Dispatch(interfaces.IDispatch):
|
||||
def TypeInfoCount(self):
|
||||
count = gdef.UINT()
|
||||
self.GetTypeInfoCount(count)
|
||||
return count
|
||||
|
||||
def type_info(self, idx):
|
||||
type_info = TypeInfo()
|
||||
self.GetTypeInfo(idx, 0, type_info)
|
||||
return type_info
|
||||
|
||||
class TypeInfo(interfaces.ITypeInfo):
|
||||
def func(self, idx):
|
||||
res = gdef.LPFUNCDESC()
|
||||
self.GetFuncDesc(idx, res)
|
||||
return res
|
||||
|
||||
def attr(self):
|
||||
res = gdef.LPTYPEATTR()
|
||||
self.GetTypeAttr(res)
|
||||
return res
|
||||
|
||||
def names(self, memid):
|
||||
size = gdef.UINT()
|
||||
x = (gdef.BSTR * 10)(*tuple(gdef.BSTR() for i in range(10)))
|
||||
self.GetNames(memid, x, 10, size)
|
||||
return x[:size.value]
|
||||
|
||||
def docu(self, id):
|
||||
res = gdef.BSTR()
|
||||
self.GetDocumentation(id, res, None, None, None)
|
||||
return res
|
||||
|
||||
def create_instance(clsiid, targetinterface, custom_iid=None, context=CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER):
|
||||
"""A simple wrapper around ``CoCreateInstance <https://msdn.microsoft.com/en-us/library/windows/desktop/ms686615(v=vs.85).aspx>``"""
|
||||
if custom_iid is None:
|
||||
custom_iid = targetinterface.IID
|
||||
if isinstance(clsiid, basestring):
|
||||
clsiid = IID.from_string(clsiid)
|
||||
winproxy.CoCreateInstance(byref(clsiid), None, context, byref(custom_iid), byref(targetinterface))
|
||||
return targetinterface
|
||||
|
||||
|
||||
def resolve_progid(progid):
|
||||
clsid = CLSID()
|
||||
winproxy.CLSIDFromProgID(progid, clsid)
|
||||
# We just filed the CLSID: refresh the __repr__
|
||||
clsid.update_strid()
|
||||
return clsid
|
||||
|
||||
# Improved COM object
|
||||
# Todo: ctypes_generation extended struct ?
|
||||
class SafeArray(SAFEARRAY):
|
||||
@classmethod
|
||||
def of_type(cls, addr, t):
|
||||
self = cls.from_address(addr)
|
||||
self.elt_type = t
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def from_PSAFEARRAY(self, psafearray):
|
||||
res = cast(psafearray, POINTER(SafeArray))[0]
|
||||
return res
|
||||
|
||||
def to_list(self, t=None):
|
||||
if t is None:
|
||||
if hasattr(self, "elt_type"):
|
||||
t = self.elt_type
|
||||
else:
|
||||
raise ValueError("Missing type of the array")
|
||||
if self.cDims != 1:
|
||||
raise NotImplementedError("tagSAFEARRAY if dims != 1")
|
||||
|
||||
nb_element = self.rgsabound[0].cElements
|
||||
llbound = self.rgsabound[0].lLbound
|
||||
if self.cbElements != ctypes.sizeof(t):
|
||||
raise ValueError("Size of elements != sizeof(type)")
|
||||
data = [t.from_address(self.pvData + (i + llbound) * ctypes.sizeof(t)).value for i in range(nb_element)]
|
||||
return data
|
||||
|
||||
#VT_VALUE_TO_TYPE = {
|
||||
#VT_I2 : SHORT,
|
||||
#VT_I4 : LONG,
|
||||
#VT_BSTR : BSTR,
|
||||
#VT_VARIANT : VARIANT,
|
||||
#VT_UI1 : UCHAR,
|
||||
#VT_UI2 : USHORT,
|
||||
#VT_UI4 : DWORD,
|
||||
#VT_I8 : LONGLONG,
|
||||
#VT_UI8 : ULONG64,
|
||||
#VT_INT : INT,
|
||||
#VT_UINT : UINT,
|
||||
#VT_HRESULT : HRESULT,
|
||||
#VT_PTR : PVOID,
|
||||
#VT_LPSTR : LPCSTR,
|
||||
#VT_LPWSTR : LPWSTR,
|
||||
#}
|
||||
|
||||
# VARIANT type checker
|
||||
# Allow to guess a VARIANT_TYPE og a python value
|
||||
|
||||
def never_match(value):
|
||||
return False
|
||||
|
||||
def check_type_null(value):
|
||||
return value is None
|
||||
|
||||
def check_type_i4(value):
|
||||
# 31 ? as we may want to keep sign :)
|
||||
return isinstance(value, int_types) and (value).bit_length() <= 32
|
||||
|
||||
def check_type_i8(value):
|
||||
# 63 ? as we may want to keep sign :)
|
||||
return isinstance(value, int_types) and (value).bit_length() <= 64
|
||||
|
||||
def check_type_bstr(value):
|
||||
return isinstance(value, basestring)
|
||||
|
||||
def check_type_bool(value):
|
||||
return isinstance(value, bool)
|
||||
|
||||
def check_type_array(value):
|
||||
return True
|
||||
|
||||
|
||||
VARIAN_NAME_3_TYPE = [f[1] for f in VARIANT._fields_ if f[0] == "_VARIANT_NAME_3"][0]
|
||||
|
||||
empty = object()
|
||||
class Variant(VARIANT):
|
||||
def __init__(self, value=empty, type=None):
|
||||
if type is not None:
|
||||
self.set_value_and_type(value, type)
|
||||
return
|
||||
elif value is empty:
|
||||
self.vt = VT_EMPTY
|
||||
return
|
||||
self.guess_type_and_set_value(value)
|
||||
|
||||
# Copy raw-ctypes fields which is a descriptor :)
|
||||
rawvt = VARIANT.vt
|
||||
|
||||
# Most of the value in the colunm[1]
|
||||
# are attribute of the sub-union _VARIANT_NAME_3
|
||||
# This union must be ctypes-anonymous for this code to works
|
||||
# We want to access these directly from the VARIANT
|
||||
# to allow custom descriptor for complexe type to be referenced here
|
||||
CHECK_TYPE = [
|
||||
# Order is important
|
||||
# as VT_I4 check may match VT_BOOL values
|
||||
# VT_BOOL check must be before VT_I4 one
|
||||
(VT_BOOL, "boolVal", check_type_bool),
|
||||
(VT_I4, "lVal", check_type_i4),
|
||||
(VT_I8, "llVal", check_type_i8),
|
||||
(VT_BSTR, "bstrVal", check_type_bstr),
|
||||
(VT_NULL, None, check_type_null),
|
||||
(VT_EMPTY, None, never_match),
|
||||
(VT_DISPATCH, "pdispVal", never_match), # I cannot recognize DISPATCH ptr for now
|
||||
(VT_UNKNOWN, "punkVal", never_match), # recognise PFW ComInterface ?
|
||||
# Test: do not allow auto-creation of small int values
|
||||
# I don't know but a feel it may confuse some API expecting VT_I4
|
||||
(VT_I2, "iVal", never_match),
|
||||
(VT_UI1, "bVal", never_match),
|
||||
]
|
||||
|
||||
VARIANT_TYPE_BY_NAME = {f[0]: f[1] for f in VARIAN_NAME_3_TYPE._fields_}
|
||||
QUICK_CHECK_TYPE = {x: y for x,y, _ in CHECK_TYPE}
|
||||
|
||||
def get_vt(self):
|
||||
rawvt = super(Variant, self).vt
|
||||
return gdef.VARENUM.mapper[self.rawvt]
|
||||
|
||||
def set_vt(self, value):
|
||||
self.rawvt = value
|
||||
|
||||
vt = property(get_vt, set_vt)
|
||||
|
||||
def set_value_and_type(self, value, type):
|
||||
attr = self.QUICK_CHECK_TYPE[type]
|
||||
# No check: user must be careful about non-match value&type
|
||||
setattr(self, attr, value)
|
||||
self.vt = type
|
||||
|
||||
def get_value_based_on_type(self):
|
||||
rawvt = self.rawvt
|
||||
if rawvt & VT_ARRAY:
|
||||
realtype = rawvt & ~VT_ARRAY
|
||||
attr = self.QUICK_CHECK_TYPE[realtype]
|
||||
attrtype = self.VARIANT_TYPE_BY_NAME[attr]
|
||||
array = SafeArray.from_PSAFEARRAY(self._VARIANT_NAME_3.parray)
|
||||
return array.to_list(attrtype)
|
||||
attr = self.QUICK_CHECK_TYPE[rawvt]
|
||||
if attr is None:
|
||||
return None
|
||||
if attr == "punkVal":
|
||||
# Quick hack for COM interface type
|
||||
# Do something clean with CHECK_TYPE ?
|
||||
x = gdef.IUnknown(self.punkVal)
|
||||
x.AddRef()
|
||||
return x
|
||||
return getattr(self, attr)
|
||||
|
||||
def guess_type_and_set_value(self, value):
|
||||
for t, attr, check in self.CHECK_TYPE:
|
||||
try:
|
||||
checkres = check(value)
|
||||
except TypeError as e:
|
||||
continue
|
||||
if checkres:
|
||||
self.vt = t
|
||||
if attr is not None:
|
||||
setattr(self, attr, value)
|
||||
return True
|
||||
raise ValueError("Could not guess VT_TYPE for <{0}> of type <{1}>".format(value, type(value)))
|
||||
|
||||
value = property(get_value_based_on_type, guess_type_and_set_value)
|
||||
|
||||
# quick_check: bypass python lookup-limitation
|
||||
def generate_getter(vt_type, transfo=(lambda x:x), quick_check=QUICK_CHECK_TYPE):
|
||||
attr = quick_check[vt_type]
|
||||
@property
|
||||
def getter(self):
|
||||
if not self.rawvt == vt_type:
|
||||
raise ValueError("Invalid vt-type for attribute expected <{0}> got <{1}>".format(vt_type, self.vt))
|
||||
return transfo(getattr(self, attr))
|
||||
return getter
|
||||
|
||||
asbstr = generate_getter(VT_BSTR)
|
||||
aslong = generate_getter(VT_I4)
|
||||
asbool = generate_getter(VT_BOOL)
|
||||
asdispatch = generate_getter(VT_DISPATCH, transfo=interfaces.IDispatch)
|
||||
asshort = generate_getter(VT_I2)
|
||||
asbyte = generate_getter(VT_UI1)
|
||||
asunknown = generate_getter(VT_UNKNOWN)
|
||||
|
||||
def __repr__(self):
|
||||
return """<{0} of type {1}>""".format(type(self).__name__, self.vt)
|
||||
|
||||
# Deprecated: remove me when test pass :)
|
||||
# ImprovedVariant.MAPPER = {
|
||||
# VT_UI1: ImprovedVariant.asbyte.fget,
|
||||
# VT_I2: ImprovedVariant.asshort.fget,
|
||||
# VT_DISPATCH: ImprovedVariant.asdispatch.fget,
|
||||
# VT_BOOL: ImprovedVariant.asbool.fget,
|
||||
# VT_I4: ImprovedVariant.aslong.fget,
|
||||
# VT_BSTR: ImprovedVariant.asbstr.fget,
|
||||
# VT_EMPTY: (lambda x: None),
|
||||
# VT_NULL: (lambda x: None),
|
||||
# VT_UNKNOWN: ImprovedVariant.asunknown.fget,
|
||||
# (VT_ARRAY | VT_BSTR): ImprovedVariant.asbstr_array.fget,
|
||||
# (VT_ARRAY | VT_I4): ImprovedVariant.aslong_array.fget,
|
||||
# (VT_ARRAY | VT_UI1): ImprovedVariant.asbyte_array.fget,
|
||||
# (VT_ARRAY | VT_BOOL): ImprovedVariant.asbool_array.fget
|
||||
# }
|
||||
|
||||
|
||||
|
||||
class COMImplementation(object):
|
||||
"""The base class to implements COM object respecting a given interface"""
|
||||
IMPLEMENT = None
|
||||
|
||||
def get_index_of_method(self, method):
|
||||
# This code is horrible but not totally my fault
|
||||
# the PyCFuncPtrObject->index is not exposed to Python..
|
||||
# repr is: '<COM method offset 2: WinFunctionType at 0x035DDBE8>'
|
||||
rpr = repr(method)
|
||||
if not rpr.startswith("<COM method offset ") or ":" not in rpr:
|
||||
raise ValueError("Could not extract offset of {0}".format(rpr))
|
||||
return int(rpr[len("<COM method offset "): rpr.index(":")])
|
||||
|
||||
def extract_methods_order(self, interface):
|
||||
index_and_method = sorted((self.get_index_of_method(m),name, m) for name, m in interface._functions_.items())
|
||||
return index_and_method
|
||||
|
||||
def verify_implem(self, interface):
|
||||
for func_name in interface._functions_:
|
||||
implem = getattr(self, func_name, None)
|
||||
if implem is None:
|
||||
raise ValueError("<{0}> implementing <{1}> has no method <{2}>".format(type(self).__name__, self.IMPLEMENT.__name__, func_name))
|
||||
if not callable(implem):
|
||||
raise ValueError("{0} implementing <{1}>: <{2}> is not callable".format(type(self).__name__, self.IMPLEMENT.__name__, func_name))
|
||||
return True
|
||||
|
||||
def _create_vtable(self, interface):
|
||||
implems = []
|
||||
names = []
|
||||
for index, name, method in self.extract_methods_order(interface):
|
||||
func_implem = getattr(self, name)
|
||||
#'this' is a COM-interface of the type we are implementing
|
||||
types = [method.restype, interface] + list(method.argtypes)
|
||||
implems.append(ctypes.WINFUNCTYPE(*types)(func_implem))
|
||||
names.append(name)
|
||||
class Vtable(ctypes.Structure):
|
||||
_fields_ = [(name, ctypes.c_void_p) for name in names]
|
||||
return Vtable(*[ctypes.cast(x, ctypes.c_void_p) for x in implems]), implems
|
||||
|
||||
def __init__(self):
|
||||
self.verify_implem(self.IMPLEMENT)
|
||||
vtable, implems = self._create_vtable(self.IMPLEMENT)
|
||||
self.vtable = vtable
|
||||
self.implems = implems
|
||||
self.vtable_pointer = ctypes.pointer(self.vtable)
|
||||
self._as_parameter_ = ctypes.addressof(self.vtable_pointer)
|
||||
|
||||
def QueryInterface(self, this, piid, result):
|
||||
"""Default ``QueryInterface`` implementation that returns ``self`` if piid is the implemented interface"""
|
||||
if piid[0] in (gdef.IUnknown.IID, self.IMPLEMENT.IID):
|
||||
result[0] = this
|
||||
return 1
|
||||
return E_NOINTERFACE
|
||||
|
||||
def AddRef(self, *args):
|
||||
"""Default ``AddRef`` implementation that returns ``1``"""
|
||||
return 1
|
||||
|
||||
def Release(self, *args):
|
||||
"""Default ``Release`` implementation that returns ``1``"""
|
||||
return 0
|
||||
@@ -0,0 +1,9 @@
|
||||
from windows.generated_def import X509_ASN_ENCODING, PKCS_7_ASN_ENCODING
|
||||
|
||||
DEFAULT_ENCODING = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING
|
||||
# Keep other imports here so sub-crypto file can import windows.crypto.DEFAULT_ENCODING
|
||||
from windows.crypto.certificate import *
|
||||
from windows.crypto.encrypt_decrypt import *
|
||||
from windows.crypto.sign_verify import *
|
||||
from windows.crypto.dpapi import *
|
||||
from windows.crypto.cryptmsg import CryptMessage
|
||||
@@ -0,0 +1,4 @@
|
||||
from windows import winproxy
|
||||
import windows.generated_def as gdef
|
||||
import windows.crypto
|
||||
|
||||
@@ -0,0 +1,585 @@
|
||||
import itertools
|
||||
import ctypes
|
||||
|
||||
import windows
|
||||
from windows import winproxy
|
||||
import windows.generated_def as gdef
|
||||
|
||||
from windows.crypto import DEFAULT_ENCODING
|
||||
|
||||
import windows.crypto.cryptmsg
|
||||
|
||||
|
||||
CRYPT_OBJECT_FORMAT_TYPE = [
|
||||
gdef.CERT_QUERY_OBJECT_FILE,
|
||||
gdef.CERT_QUERY_OBJECT_BLOB,
|
||||
gdef.CERT_QUERY_CONTENT_CERT,
|
||||
gdef.CERT_QUERY_CONTENT_CTL,
|
||||
gdef.CERT_QUERY_CONTENT_CRL,
|
||||
gdef.CERT_QUERY_CONTENT_SERIALIZED_STORE,
|
||||
gdef.CERT_QUERY_CONTENT_SERIALIZED_CERT,
|
||||
gdef.CERT_QUERY_CONTENT_SERIALIZED_CTL,
|
||||
gdef.CERT_QUERY_CONTENT_SERIALIZED_CRL,
|
||||
gdef.CERT_QUERY_CONTENT_PKCS7_SIGNED,
|
||||
gdef.CERT_QUERY_CONTENT_PKCS7_UNSIGNED,
|
||||
gdef.CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED,
|
||||
gdef.CERT_QUERY_CONTENT_PKCS10,
|
||||
gdef.CERT_QUERY_CONTENT_PFX,
|
||||
gdef.CERT_QUERY_CONTENT_CERT_PAIR,
|
||||
gdef.CERT_QUERY_CONTENT_PFX_AND_LOAD
|
||||
]
|
||||
|
||||
CRYPT_OBJECT_FORMAT_TYPE_DICT = gdef.FlagMapper(*CRYPT_OBJECT_FORMAT_TYPE)
|
||||
|
||||
## Move CryptObject to new .py ?
|
||||
|
||||
class CryptObject(object):
|
||||
"""Extract information from an CryptoAPI object.
|
||||
(see `CryptQueryObject <https://msdn.microsoft.com/en-us/library/windows/desktop/aa380264(v=vs.85).aspx>`_)
|
||||
|
||||
Current main use is extracting the signers certificates from a PE file.
|
||||
"""
|
||||
MSG_PARAM_KNOW_TYPES = {gdef.CMSG_SIGNER_INFO_PARAM: gdef.CMSG_SIGNER_INFO,
|
||||
gdef.CMSG_SIGNER_COUNT_PARAM: gdef.DWORD,
|
||||
gdef.CMSG_CERT_COUNT_PARAM: gdef.DWORD}
|
||||
|
||||
def __init__(self, filename, content_type=gdef.CERT_QUERY_CONTENT_FLAG_ALL):
|
||||
# No other API than filename for now..
|
||||
self.filename = filename
|
||||
|
||||
dwEncoding = gdef.DWORD()
|
||||
dwContentType = gdef.DWORD()
|
||||
dwFormatType = gdef.DWORD()
|
||||
hStore = CertificateStore()
|
||||
hMsg = windows.crypto.CryptMessage()
|
||||
|
||||
winproxy.CryptQueryObject(gdef.CERT_QUERY_OBJECT_FILE,
|
||||
gdef.LPWSTR(filename),
|
||||
# filename,
|
||||
content_type,
|
||||
gdef.CERT_QUERY_FORMAT_FLAG_BINARY,
|
||||
0,
|
||||
dwEncoding,
|
||||
dwContentType,
|
||||
dwFormatType,
|
||||
hStore,
|
||||
hMsg,
|
||||
None)
|
||||
|
||||
self.cert_store = hStore if hStore else None
|
||||
"""The :class:`CertificateStore` that includes all of the certificates, CRLs, and CTLs in the object"""
|
||||
self.crypt_msg = hMsg if hMsg else None #: yolo
|
||||
"""The :class:`CryptMessage` for any ``PKCS7`` content in the object"""
|
||||
self.encoding = dwEncoding
|
||||
self.content_type = CRYPT_OBJECT_FORMAT_TYPE_DICT[dwContentType.value]
|
||||
"""The type of the opened message"""
|
||||
|
||||
def _signers_and_certs_generator(self):
|
||||
if self.crypt_msg is None:
|
||||
return
|
||||
for signer in self.crypt_msg.signers:
|
||||
# We could directly extract the certificates from the 'crypt_msg' (I guess)
|
||||
# But 'CryptQueryObject' had the sympathy of already opening a CertificateStore
|
||||
# for us. So we use it.
|
||||
# I am open to counter-argument on this methodology.
|
||||
cert = self.cert_store.find(signer.Issuer, signer.SerialNumber)
|
||||
yield signer, cert
|
||||
|
||||
@property
|
||||
def signers_and_certs(self):
|
||||
"""The list of signer info and certificates signing the object.
|
||||
|
||||
:rtype: [(:class:`~windows.generated_def.winstructs.CMSG_SIGNER_INFO`, :class:`Certificate`)]
|
||||
|
||||
.. note::
|
||||
|
||||
:class:`~windows.generated_def.winstructs.CMSG_SIGNER_INFO` might be changed to a wrapping-subclass.
|
||||
"""
|
||||
return list(self._signers_and_certs_generator())
|
||||
|
||||
def __repr__(self):
|
||||
return '<{0} "{1}" content_type={2!r}>'.format(type(self).__name__, self.filename, self.content_type)
|
||||
|
||||
|
||||
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa382037(v=vs.85).aspx
|
||||
class CertificateStore(gdef.HCERTSTORE):
|
||||
"""A certificate store"""
|
||||
@property
|
||||
def certs(self):
|
||||
"""The list of certificates in the store
|
||||
|
||||
:type: [:class:`Certificate`] -- A list of certificate
|
||||
"""
|
||||
res = []
|
||||
last = None
|
||||
while True:
|
||||
try:
|
||||
cert = winproxy.CertEnumCertificatesInStore(self, last)
|
||||
except winproxy.WinproxyError as e:
|
||||
if (e.winerror & 0xffffffff) in (gdef.CRYPT_E_NOT_FOUND,):
|
||||
return tuple(res)
|
||||
raise
|
||||
# Need to duplicate as CertEnumCertificatesInStore will free the context 'last'
|
||||
ecert = windows.crypto.Certificate.from_pointer(cert)
|
||||
res.append(ecert.duplicate())
|
||||
last = ecert
|
||||
raise RuntimeError("Out of infinit loop")
|
||||
|
||||
def add_certificate(self, certificate):
|
||||
"""Add a certificate to the store"""
|
||||
winproxy.CertAddCertificateContextToStore(self, certificate, gdef.CERT_STORE_ADD_NEW, None)
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, filename):
|
||||
"""Create a new :class:`CertificateStore` from ``filename``"""
|
||||
res = winproxy.CertOpenStore(gdef.CERT_STORE_PROV_FILENAME_A, DEFAULT_ENCODING, None, gdef.CERT_STORE_OPEN_EXISTING_FLAG, filename)
|
||||
return ctypes.cast(res, cls)
|
||||
|
||||
def _yolo(self):
|
||||
x = winproxy.CertEnumCTLsInStore(self, None)
|
||||
title = None
|
||||
windows.winproxy.CryptUIDlgViewContext(gdef.CERT_STORE_CTL_CONTEXT, x, None, title, 0, None)
|
||||
return x
|
||||
|
||||
|
||||
# See https://msdn.microsoft.com/en-us/library/windows/desktop/aa388136(v=vs.85).aspx
|
||||
@classmethod
|
||||
def from_system_store(cls, store_name):
|
||||
"""Create a new :class:`CertificateStore` from system store ``store_name``
|
||||
(see `System Store Locations <https://msdn.microsoft.com/en-us/library/windows/desktop/aa388136(v=vs.85).aspx>`_)
|
||||
"""
|
||||
res = winproxy.CertOpenStore(gdef.CERT_STORE_PROV_SYSTEM_A, DEFAULT_ENCODING, None, gdef.CERT_SYSTEM_STORE_LOCAL_MACHINE | gdef.CERT_STORE_READONLY_FLAG, store_name)
|
||||
return ctypes.cast(res, cls)
|
||||
|
||||
@classmethod
|
||||
def new_in_memory(cls):
|
||||
"""Create a new temporary :class:`CertificateStore` in memory"""
|
||||
res = winproxy.CertOpenStore(gdef.CERT_STORE_PROV_MEMORY, DEFAULT_ENCODING, None, 0, None)
|
||||
return ctypes.cast(res, cls)
|
||||
|
||||
|
||||
# TODO: a more complete search API ?
|
||||
def find(self, issuer, serialnumber):
|
||||
"""Return the certificate that match `issuer` and `serialnumber`
|
||||
|
||||
:return: :class:`Certificate` -- ``None`` if certificate is not found
|
||||
"""
|
||||
# data = self.get_signer_data(index)
|
||||
cert_info = gdef.CERT_INFO()
|
||||
cert_info.Issuer = issuer
|
||||
cert_info.SerialNumber = serialnumber
|
||||
try:
|
||||
rawcertcontext = winproxy.CertFindCertificateInStore(self, DEFAULT_ENCODING, 0, gdef.CERT_FIND_SUBJECT_CERT, ctypes.byref(cert_info), None)
|
||||
except WindowsError as e:
|
||||
if not e.winerror & 0xffffffff == gdef.CRYPT_E_NOT_FOUND:
|
||||
raise
|
||||
return None
|
||||
return Certificate.from_pointer(rawcertcontext)
|
||||
|
||||
def __del__(self):
|
||||
return winproxy.CertCloseStore(self, 0)
|
||||
|
||||
|
||||
# PKCS12_NO_PERSIST_KEY -> do not save it in a key container on disk
|
||||
# Without it, a key container is created at 'C:\Users\USERNAME\AppData\Roaming\Microsoft\Crypto\RSA\S-1-5-21-3241049326-165485355-1070449050-1001'
|
||||
# More about this:
|
||||
# If you use 'PKCS12_NO_PERSIST_KEY' the key are indeed NOT STORED but there is a problem
|
||||
# If you use an algo like 'szOID_NIST_AES256_CBC' the function 'CryptDecryptMessage' won't be able to decrypt the message
|
||||
# Unless you also specify the 'PKCS12_ALWAYS_CNG_KSP' flags.
|
||||
|
||||
# My guess: somewhere 'CryptDecryptMessage' ask for each (CNG_KSP | CSP ?) to try to decrypt with the keys
|
||||
# BUT: as we DID NOT EXPORT the keys, they are not able to get the key from memory and expect them on disk.
|
||||
# By forcing PKCS12_ALWAYS_CNG_KSP we remove this as the key are directly linked to the correct CNG_KSP in the CertStore
|
||||
# Look like it's based on this part of the PFX:
|
||||
# Microsoft CSP Name: Microsoft Enhanced Cryptographic Provider v1.0
|
||||
# BUT this will not allow to decrypt RSA_RC4 ?
|
||||
|
||||
def import_pfx(pfx, password=None, flags=gdef.CRYPT_USER_KEYSET | gdef.PKCS12_NO_PERSIST_KEY | gdef.PKCS12_ALWAYS_CNG_KSP):
|
||||
"""Import the file ``pfx`` with the ``password``.
|
||||
|
||||
``default flags = PKCS12_NO_PERSIST_KEY | CRYPT_USER_KEYSET``.
|
||||
|
||||
``PKCS12_NO_PERSIST_KEY`` tells ``CryptoAPI`` to NOT save the keys in a on-disk container.
|
||||
|
||||
:return: :class:`CertificateStore`
|
||||
"""
|
||||
if isinstance(pfx, windows.pycompat.anybuff) or isinstance(pfx, bytearray):
|
||||
pfx = gdef.CRYPT_DATA_BLOB.from_string(pfx)
|
||||
cert_store = winproxy.PFXImportCertStore(pfx, password, flags)
|
||||
return CertificateStore(cert_store)
|
||||
|
||||
|
||||
class Certificate(gdef.CERT_CONTEXT):
|
||||
"""Represent a Certificate """
|
||||
|
||||
@property
|
||||
def raw_serial(self):
|
||||
"""The raw serial number of the certificate.
|
||||
|
||||
:type: [:class:`int`]: A list of int ``0 <= x <= 255``"""
|
||||
serial_number = self.pCertInfo[0].SerialNumber
|
||||
return [(c & 0xff) for c in serial_number.pbData[:serial_number.cbData][::-1]]
|
||||
|
||||
@property
|
||||
def serial(self):
|
||||
"""The string representation of the certificate's serial.
|
||||
|
||||
:type: :class:`str`
|
||||
"""
|
||||
serial_bytes = self.raw_serial
|
||||
return " ".join("{:02x}".format(x) for x in serial_bytes)
|
||||
|
||||
|
||||
def get_name(self, nametype=gdef.CERT_NAME_SIMPLE_DISPLAY_TYPE, param_type=0, flags=0):
|
||||
"""Retrieve the subject or issuer name of the certificate.
|
||||
See `CertGetNameStringA <https://msdn.microsoft.com/en-us/library/windows/desktop/aa376086(v=vs.85).aspx>`_
|
||||
|
||||
:returns: :class:`str`
|
||||
"""
|
||||
if nametype == gdef.CERT_NAME_RDN_TYPE:
|
||||
param_type = gdef.DWORD(param_type)
|
||||
param_type = gdef.LPDWORD(param_type)
|
||||
size = winproxy.CertGetNameStringA(self, nametype, flags, param_type, None, 0)
|
||||
namebuff = ctypes.c_buffer(size)
|
||||
size = winproxy.CertGetNameStringA(self, nametype, flags, param_type, namebuff, size)
|
||||
return namebuff[:-1]
|
||||
|
||||
|
||||
|
||||
name = property(get_name)
|
||||
"""The name of the certificate.
|
||||
|
||||
:type: :class:`str`"""
|
||||
|
||||
|
||||
def raw_hash(self):
|
||||
size = gdef.DWORD(100)
|
||||
buffer = ctypes.c_buffer(size.value)
|
||||
winproxy.CryptHashCertificate(None, 0, 0, self.pbCertEncoded, self.cbCertEncoded, ctypes.cast(buffer, gdef.LPBYTE), size)
|
||||
return buffer[:size.value]
|
||||
|
||||
@property
|
||||
def thumbprint(self):
|
||||
"""The thumbprint of the certificate (which is the sha1 of the encoded cert).
|
||||
|
||||
Example:
|
||||
|
||||
>>> x
|
||||
<Certificate "YOLO2" serial="6f 1d 3e 7d d9 77 59 a9 4c 1c 53 dc 80 db 0c fe">
|
||||
>>> x.thumbprint
|
||||
'E2 A2 DB 76 A1 DD 8E 70 0D C6 9F CB 71 CF 29 12 C6 D9 78 97'
|
||||
|
||||
:type: :class:`str`
|
||||
"""
|
||||
return " ".join("{:02X}".format(x) for x in bytearray(self.raw_hash()))
|
||||
|
||||
@property
|
||||
def distinguished_name(self):
|
||||
"""The distinguished name (DN) of the certificate.
|
||||
|
||||
Example:
|
||||
|
||||
>>> x
|
||||
<Certificate "Microsoft Windows Production PCA 2011" serial="61 07 76 56 00 00 00 00 00 08">
|
||||
>>> x.distinguished_name
|
||||
'C=US, S=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Windows Production PCA 2011'
|
||||
|
||||
:type: :class:`str`
|
||||
"""
|
||||
return self.get_name(gdef.CERT_NAME_RDN_TYPE, gdef.CERT_X500_NAME_STR)
|
||||
|
||||
@property
|
||||
def issuer(self):
|
||||
"""The name of the certificate's issuer.
|
||||
|
||||
:type: :class:`str`"""
|
||||
return self.get_name(flags=gdef.CERT_NAME_ISSUER_FLAG)
|
||||
|
||||
|
||||
@property
|
||||
def store(self):
|
||||
"""The certificate store that contains the certificate
|
||||
|
||||
:type: :class:`CertificateStore`
|
||||
"""
|
||||
return CertificateStore(self.hCertStore)
|
||||
|
||||
def get_raw_certificate_chains(self): # Rename to all_chains ?
|
||||
chain_context = EPCCERT_CHAIN_CONTEXT()
|
||||
|
||||
enhkey_usage = gdef.CERT_ENHKEY_USAGE()
|
||||
enhkey_usage.cUsageIdentifier = 0
|
||||
enhkey_usage.rgpszUsageIdentifier = None
|
||||
|
||||
cert_usage = gdef.CERT_USAGE_MATCH()
|
||||
cert_usage.dwType = gdef.USAGE_MATCH_TYPE_AND
|
||||
cert_usage.Usage = enhkey_usage
|
||||
|
||||
chain_para = gdef.CERT_CHAIN_PARA()
|
||||
chain_para.cbSize = ctypes.sizeof(chain_para)
|
||||
chain_para.RequestedUsage = cert_usage
|
||||
|
||||
winproxy.CertGetCertificateChain(None, self, None, self.hCertStore, ctypes.byref(chain_para), 0, None, ctypes.byref(chain_context))
|
||||
# Lower chains ?
|
||||
# winproxy.CertGetCertificateChain(None, self, None, self[0].hCertStore, ctypes.byref(chain_para), 0x80, None, ctypes.byref(chain_context))
|
||||
#return CertficateChain(chain_context)
|
||||
return chain_context
|
||||
|
||||
@property # fixedproperty ?
|
||||
def chains(self):
|
||||
"""The list of chain context available for this certificate. Each elements of this list is a list of ``Certificate`` that should
|
||||
go from the ``self`` certificate to a trusted certificate.
|
||||
|
||||
:type: [[:class:`Certificate`]] -- A list of chain (list) of :class:`Certificate`
|
||||
"""
|
||||
chain_context = self.get_raw_certificate_chains()
|
||||
res = []
|
||||
for chain in chain_context.chains:
|
||||
chain_res = [elt.cert for elt in chain.elements]
|
||||
res.append(chain_res)
|
||||
return res
|
||||
|
||||
# API Arround CertSelectCertificateChains ?
|
||||
# https://msdn.microsoft.com/en-us/library/windows/desktop/dd433797(v=vs.85).aspx
|
||||
|
||||
def duplicate(self):
|
||||
"""Duplicate the certificate by incrementing the internal refcount. (see `CertDuplicateCertificateContext <https://msdn.microsoft.com/en-us/library/windows/desktop/aa376045(v=vs.85).aspx>`_)
|
||||
|
||||
note: The object returned is ``self``
|
||||
|
||||
:return: :class:`Certificate`
|
||||
"""
|
||||
res = winproxy.CertDuplicateCertificateContext(self)
|
||||
# Check what the doc says: the pointer returned is actually the PCERT in parameter
|
||||
# Only the refcount is incremented
|
||||
# This postulate allow us to return 'self' directly
|
||||
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa376045(v=vs.85).aspx
|
||||
if not ctypes.addressof(res[0]) == ctypes.addressof(self):
|
||||
raise ValueError("CertDuplicateCertificateContext did not returned the argument (check doc)")
|
||||
return self
|
||||
|
||||
def view(self, title=None):
|
||||
return windows.winproxy.CryptUIDlgViewContext(gdef.CERT_STORE_CERTIFICATE_CONTEXT, ctypes.byref(self), None, title, 0, None)
|
||||
|
||||
KNOWN_PROPERTIES_VALUES = gdef.FlagMapper(
|
||||
gdef.CERT_KEY_PROV_HANDLE_PROP_ID,
|
||||
gdef.CERT_KEY_PROV_INFO_PROP_ID,
|
||||
gdef.CERT_SHA1_HASH_PROP_ID,
|
||||
gdef.CERT_MD5_HASH_PROP_ID,
|
||||
gdef.CERT_HASH_PROP_ID,
|
||||
gdef.CERT_KEY_CONTEXT_PROP_ID,
|
||||
gdef.CERT_KEY_SPEC_PROP_ID,
|
||||
gdef.CERT_IE30_RESERVED_PROP_ID,
|
||||
gdef.CERT_PUBKEY_HASH_RESERVED_PROP_ID,
|
||||
gdef.CERT_ENHKEY_USAGE_PROP_ID,
|
||||
gdef.CERT_CTL_USAGE_PROP_ID,
|
||||
gdef.CERT_NEXT_UPDATE_LOCATION_PROP_ID,
|
||||
gdef.CERT_FRIENDLY_NAME_PROP_ID,
|
||||
gdef.CERT_PVK_FILE_PROP_ID,
|
||||
gdef.CERT_DESCRIPTION_PROP_ID,
|
||||
gdef.CERT_ACCESS_STATE_PROP_ID,
|
||||
gdef.CERT_SIGNATURE_HASH_PROP_ID,
|
||||
gdef.CERT_SMART_CARD_DATA_PROP_ID,
|
||||
gdef.CERT_EFS_PROP_ID,
|
||||
gdef.CERT_FORTEZZA_DATA_PROP_ID,
|
||||
gdef.CERT_ARCHIVED_PROP_ID,
|
||||
gdef.CERT_KEY_IDENTIFIER_PROP_ID,
|
||||
gdef.CERT_AUTO_ENROLL_PROP_ID,
|
||||
gdef.CERT_PUBKEY_ALG_PARA_PROP_ID,
|
||||
gdef.CERT_CROSS_CERT_DIST_POINTS_PROP_ID,
|
||||
gdef.CERT_ISSUER_PUBLIC_KEY_MD5_HASH_PROP_ID,
|
||||
gdef.CERT_SUBJECT_PUBLIC_KEY_MD5_HASH_PROP_ID,
|
||||
gdef.CERT_ENROLLMENT_PROP_ID,
|
||||
gdef.CERT_DATE_STAMP_PROP_ID,
|
||||
gdef.CERT_ISSUER_SERIAL_NUMBER_MD5_HASH_PROP_ID,
|
||||
gdef.CERT_SUBJECT_NAME_MD5_HASH_PROP_ID,
|
||||
gdef.CERT_EXTENDED_ERROR_INFO_PROP_ID,
|
||||
gdef.CERT_RENEWAL_PROP_ID,
|
||||
gdef.CERT_ARCHIVED_KEY_HASH_PROP_ID,
|
||||
gdef.CERT_AUTO_ENROLL_RETRY_PROP_ID,
|
||||
gdef.CERT_AIA_URL_RETRIEVED_PROP_ID,
|
||||
gdef.CERT_AUTHORITY_INFO_ACCESS_PROP_ID,
|
||||
gdef.CERT_BACKED_UP_PROP_ID,
|
||||
gdef.CERT_OCSP_RESPONSE_PROP_ID,
|
||||
gdef.CERT_REQUEST_ORIGINATOR_PROP_ID,
|
||||
gdef.CERT_SOURCE_LOCATION_PROP_ID)
|
||||
|
||||
def enum_properties(self):
|
||||
prop = 0
|
||||
res = []
|
||||
while True:
|
||||
prop = winproxy.CertEnumCertificateContextProperties(self, prop)
|
||||
if not prop:
|
||||
return res
|
||||
res.append(self.KNOWN_PROPERTIES_VALUES[prop])
|
||||
raise RuntimeError("Unreachable code")
|
||||
|
||||
properties = property(enum_properties)
|
||||
"""The properties of the certificate
|
||||
|
||||
:type: [:class:`int` or :class:`~windows.generated_def.Flag`] -- A list of property ID
|
||||
"""
|
||||
|
||||
#def get_property(self):
|
||||
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa376079(v=vs.85).aspx
|
||||
# - Usefull:
|
||||
# CERT_SHA1_HASH_PROP_ID
|
||||
|
||||
def get_property(self, prop):
|
||||
"TODO: DOC :D + auto-type ?"
|
||||
datasize = gdef.DWORD()
|
||||
windows.winproxy.CertGetCertificateContextProperty(self, prop, None, datasize)
|
||||
buf = (gdef.BYTE * datasize.value)()
|
||||
windows.winproxy.CertGetCertificateContextProperty(self, prop, buf, datasize)
|
||||
return bytearray(buf)
|
||||
|
||||
|
||||
@property
|
||||
def encoded(self):
|
||||
"""The encoded certificate.
|
||||
|
||||
:type: :class:`bytearray`"""
|
||||
return bytearray(self.pbCertEncoded[:self.cbCertEncoded])
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
"""The version number of the certificate
|
||||
|
||||
:type: :class:`int`
|
||||
"""
|
||||
return self.pCertInfo[0].dwVersion
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, filename):
|
||||
"""Create a :class:`Certificate` from the file ``filename``
|
||||
|
||||
:return: :class:`Certificate`
|
||||
"""
|
||||
with open(filename, "rb") as f:
|
||||
data = f.read()
|
||||
buf = (ctypes.c_ubyte * len(data))(*bytearray(data))
|
||||
pcert = windows.winproxy.CertCreateCertificateContext(windows.crypto.DEFAULT_ENCODING, buf, len(data))
|
||||
return cls.from_pointer(pcert)
|
||||
|
||||
@classmethod
|
||||
def from_buffer(cls, data):
|
||||
"""Create a :class:`Certificate` from the buffer ``data``
|
||||
|
||||
:return: :class:`Certificate`
|
||||
"""
|
||||
buf = (ctypes.c_ubyte * len(data))(*bytearray(data))
|
||||
pcert = windows.winproxy.CertCreateCertificateContext(windows.crypto.DEFAULT_ENCODING, buf, len(data))
|
||||
return cls.from_pointer(pcert)
|
||||
|
||||
@classmethod
|
||||
def from_pointer(self, ptr):
|
||||
return ctypes.cast(ptr, ctypes.POINTER(Certificate))[0]
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Certificate):
|
||||
return NotImplemented
|
||||
return windows.winproxy.CertCompareCertificate(DEFAULT_ENCODING, self.pCertInfo, other.pCertInfo)
|
||||
|
||||
def __repr__(self):
|
||||
return '<{0} "{1}" serial="{2}">'.format(type(self).__name__, self.name, self.serial)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# class CertficateChain(object):
|
||||
# def __init__(self, pc_chain_context):
|
||||
# self.chain = pc_chain_context[0]
|
||||
#
|
||||
# def to_list(self):
|
||||
# res = []
|
||||
# for i in range(self.chain.rgpChain[0][0].cElement):
|
||||
# res.append(CertificateContext(self.chain.rgpChain[0][0].rgpElement[i][0].pCertContext[0]))
|
||||
# return res
|
||||
|
||||
|
||||
# Those classes are more of a POC than anything else
|
||||
# Should be the struct itself (like Certificate ?)
|
||||
class EPCCERT_CHAIN_CONTEXT(gdef.PCCERT_CHAIN_CONTEXT):
|
||||
_type_ = gdef.PCCERT_CHAIN_CONTEXT._type_
|
||||
|
||||
@property
|
||||
def chains(self):
|
||||
res = []
|
||||
# if (self[0].cLowerQualityChainContext):
|
||||
# print("LOL")
|
||||
# import pdb;pdb.set_trace()
|
||||
# if self[0].cChain > 1:
|
||||
# print("HAAAAA")
|
||||
# import pdb;pdb.set_trace()
|
||||
for i in range(self[0].cChain):
|
||||
simple_chain = ctypes.cast(self[0].rgpChain[i], EPCCERT_SIMPLE_CHAIN)
|
||||
res.append(simple_chain)
|
||||
return res
|
||||
|
||||
@property
|
||||
def all_cert(self):
|
||||
res = []
|
||||
for chain in self.chains:
|
||||
ch = []
|
||||
res.append(ch)
|
||||
for element in chain.elements:
|
||||
ch.append(element.cert)
|
||||
return res
|
||||
|
||||
class EPCCERT_SIMPLE_CHAIN(gdef.PCCERT_SIMPLE_CHAIN):
|
||||
_type_ = gdef.PCCERT_SIMPLE_CHAIN._type_
|
||||
|
||||
@property
|
||||
def elements(self):
|
||||
res = []
|
||||
for i in range(self[0].cElement):
|
||||
element = ctypes.cast(self[0].rgpElement[i], EPCERT_CHAIN_ELEMENT)
|
||||
res.append(element)
|
||||
return res
|
||||
|
||||
class EPCERT_CHAIN_ELEMENT(gdef.PCERT_CHAIN_ELEMENT):
|
||||
_type_ = gdef.PCERT_CHAIN_ELEMENT._type_
|
||||
|
||||
@property
|
||||
def cert(self):
|
||||
return Certificate.from_pointer(self[0].pCertContext)
|
||||
|
||||
|
||||
# Move this in another .py ?
|
||||
class CryptContext(gdef.HCRYPTPROV):
|
||||
""" A context manager arround ``CryptAcquireContextW`` & ``CryptReleaseContext``
|
||||
|
||||
.. note::
|
||||
see usage in sample :ref:`sample_crypto_encryption` (function ``genkeys``)
|
||||
"""
|
||||
_type_ = gdef.HCRYPTPROV._type_
|
||||
|
||||
def __init__(self, pszContainer=None, pszProvider=None, dwProvType=0, dwFlags=0, retrycreate=False):
|
||||
self.pszContainer = pszContainer
|
||||
self.pszProvider = pszProvider
|
||||
self.dwProvType = dwProvType
|
||||
self.dwFlags = dwFlags
|
||||
self.retrycreate = True
|
||||
#self.value = HCRYPTPROV()
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
self.acquire()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.release()
|
||||
|
||||
def acquire(self):
|
||||
try:
|
||||
return winproxy.CryptAcquireContextW(self, self.pszContainer, self.pszProvider, self.dwProvType, self.dwFlags)
|
||||
except WindowsError as e:
|
||||
if not self.retrycreate:
|
||||
raise
|
||||
return winproxy.CryptAcquireContextW(self, self.pszContainer, self.pszProvider, self.dwProvType, self.dwFlags | gdef.CRYPT_NEWKEYSET)
|
||||
|
||||
def release(self):
|
||||
return winproxy.CryptReleaseContext(self, False)
|
||||
@@ -0,0 +1,133 @@
|
||||
import ctypes
|
||||
|
||||
from windows import winproxy
|
||||
import windows.generated_def as gdef
|
||||
import windows.crypto
|
||||
|
||||
class CryptMessage(gdef.HCRYPTMSG):
|
||||
"""Represent a PKCS #7 message
|
||||
(see `Low-level Message Functions <https://msdn.microsoft.com/en-us/library/windows/desktop/aa380252(v=vs.85).aspx#low_level_message_functions>`_)
|
||||
"""
|
||||
MSG_PARAM_KNOW_TYPES = {gdef.CMSG_SIGNER_INFO_PARAM: gdef.CMSG_SIGNER_INFO,
|
||||
gdef.CMSG_SIGNER_COUNT_PARAM: gdef.DWORD,
|
||||
gdef.CMSG_CERT_COUNT_PARAM: gdef.DWORD,
|
||||
gdef.CMSG_ENVELOPE_ALGORITHM_PARAM: gdef.CRYPT_ALGORITHM_IDENTIFIER,
|
||||
gdef.CMSG_RECIPIENT_COUNT_PARAM: gdef.DWORD,
|
||||
gdef.CMSG_RECIPIENT_INFO_PARAM: gdef.CERT_INFO,
|
||||
}
|
||||
|
||||
|
||||
def get_param(self, param_type, index=0, raw=False):
|
||||
data_size = gdef.DWORD()
|
||||
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa380227(v=vs.85).aspx
|
||||
winproxy.CryptMsgGetParam(self, param_type, index, None, data_size)
|
||||
buffer = ctypes.c_buffer(data_size.value)
|
||||
winproxy.CryptMsgGetParam(self, param_type, index, buffer, data_size)
|
||||
if raw:
|
||||
return (buffer, data_size)
|
||||
|
||||
if param_type in self.MSG_PARAM_KNOW_TYPES:
|
||||
buffer = self.MSG_PARAM_KNOW_TYPES[param_type].from_buffer(buffer)
|
||||
if isinstance(buffer, gdef.DWORD): # DWORD -> return the Python int
|
||||
return buffer.value
|
||||
return buffer
|
||||
|
||||
# Certificate accessors
|
||||
|
||||
@property
|
||||
def nb_cert(self):
|
||||
"""The number of certificate embded in the :class:`CryptObject`
|
||||
|
||||
:type: :class:`int`
|
||||
"""
|
||||
return self.get_param(gdef.CMSG_CERT_COUNT_PARAM)
|
||||
|
||||
def get_raw_cert(self, index=0):
|
||||
return self.get_param(gdef.CMSG_CERT_PARAM, index)
|
||||
|
||||
def get_cert(self, index=0):
|
||||
"""Return embded :class:`Certificate` number ``index``.
|
||||
|
||||
.. note::
|
||||
|
||||
Not all embded certificate are directly used to sign the :class:`CryptObject`.
|
||||
"""
|
||||
return windows.crypto.Certificate.from_buffer(self.get_raw_cert(index))
|
||||
|
||||
@property
|
||||
def certs(self):
|
||||
"""The list of :class:`Certificate` embded in the message"""
|
||||
return [self.get_cert(i) for i in range(self.nb_cert)]
|
||||
|
||||
# Signers accessors
|
||||
|
||||
@property
|
||||
def nb_signer(self):
|
||||
"""The number of signers for the CryptObject
|
||||
|
||||
:type: :class:`int`
|
||||
"""
|
||||
try:
|
||||
return self.get_param(gdef.CMSG_SIGNER_COUNT_PARAM)
|
||||
except WindowsError as e:
|
||||
if (e.winerror & 0xffffffff) == gdef.CRYPT_E_INVALID_MSG_TYPE:
|
||||
return 0
|
||||
raise
|
||||
|
||||
|
||||
def get_signer_data(self, index=0):
|
||||
"""Returns the signer informations for signer nb ``index``
|
||||
|
||||
:return: :class:`~windows.generated_def.winstructs.CMSG_SIGNER_INFO`
|
||||
"""
|
||||
return self.get_param(gdef.CMSG_SIGNER_INFO_PARAM, index)
|
||||
|
||||
@property
|
||||
def signers(self):
|
||||
"""The list of :class:`~windows.generated_def.winstructs.CMSG_SIGNER_INFO` embed in the message"""
|
||||
return [self.get_signer_data(i) for i in range(self.nb_signer)]
|
||||
|
||||
@property
|
||||
def nb_recipient(self):
|
||||
"""TODO: DOC"""
|
||||
return self.get_param(gdef.CMSG_RECIPIENT_COUNT_PARAM)
|
||||
|
||||
|
||||
def get_recipient_data(self, index=0):
|
||||
"""TODO: DOC"""
|
||||
return self.get_param(gdef.CMSG_RECIPIENT_INFO_PARAM, index)
|
||||
|
||||
@property
|
||||
def recipients(self):
|
||||
"""TODO: DOC"""
|
||||
return [self.get_recipient_data(i) for i in range(self.nb_recipient)]
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
return self.get_param(gdef.CMSG_CONTENT_PARAM)[:]
|
||||
|
||||
@property
|
||||
def content_type(self):
|
||||
data = self.get_param(gdef.CMSG_INNER_CONTENT_TYPE_PARAM)
|
||||
assert data[-1] == "\x00", "CMSG_INNER_CONTENT_TYPE_PARAM not NULL TERMINATED"
|
||||
return data[:-1]
|
||||
|
||||
|
||||
def update(self, blob, final):
|
||||
# Test isinstance string ?
|
||||
if isinstance(blob, (windows.pycompat.anybuff, bytearray)):
|
||||
blob = windows.pycompat.raw_encode(blob)
|
||||
buffer = windows.utils.BUFFER(gdef.BYTE).from_buffer_copy(blob)
|
||||
return winproxy.CryptMsgUpdate(self, buffer, len(blob), final)
|
||||
return winproxy.CryptMsgUpdate(self, blob.pbData, blob.cbData, final)
|
||||
|
||||
# constructor
|
||||
@classmethod
|
||||
def from_buffer(self, data):
|
||||
hmsg = winproxy.CryptMsgOpenToDecode(windows.crypto.DEFAULT_ENCODING, 0, 0, None, None, None)
|
||||
newmsg = CryptMessage(hmsg)
|
||||
newmsg.update(data, final=True)
|
||||
return newmsg
|
||||
|
||||
def __del__(self):
|
||||
return winproxy.CryptMsgClose(self)
|
||||
@@ -0,0 +1,34 @@
|
||||
from windows import winproxy
|
||||
import windows.generated_def as gdef
|
||||
|
||||
__all__ = ["protect", "unprotect"]
|
||||
|
||||
|
||||
def protect(data, entropy=None, flags=gdef.CRYPTPROTECT_UI_FORBIDDEN):
|
||||
in_blob = gdef.DATA_BLOB.from_string(data)
|
||||
out_blob = gdef.DATA_BLOB()
|
||||
if entropy is not None:
|
||||
entropy = gdef.DATA_BLOB.from_string(entropy)
|
||||
winproxy.CryptProtectData(in_blob, pOptionalEntropy=entropy, dwFlags=flags, pDataOut=out_blob)
|
||||
encrypted_data = bytes(out_blob.data)
|
||||
# https://docs.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata
|
||||
# pDataOut: A pointer to a DATA_BLOB structure that receives the encrypted data.
|
||||
# When you have finished using the DATA_BLOB structure, free its pbData member by calling the LocalFree function.
|
||||
winproxy.LocalFree(out_blob.pbData)
|
||||
del out_blob
|
||||
return encrypted_data
|
||||
|
||||
|
||||
def unprotect(data, entropy=None, flags=gdef.CRYPTPROTECT_UI_FORBIDDEN):
|
||||
in_blob = gdef.DATA_BLOB.from_string(data)
|
||||
out_blob = gdef.DATA_BLOB()
|
||||
if entropy is not None:
|
||||
entropy = gdef.DATA_BLOB.from_string(entropy)
|
||||
winproxy.CryptUnprotectData(in_blob, pOptionalEntropy=entropy, dwFlags=flags, pDataOut=out_blob)
|
||||
decrypted_data = bytes(out_blob.data)
|
||||
# https://docs.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata
|
||||
# pDataOut: A pointer to a DATA_BLOB structure that receives the encrypted data.
|
||||
# When you have finished using the DATA_BLOB structure, free its pbData member by calling the LocalFree function.
|
||||
winproxy.LocalFree(out_blob.pbData)
|
||||
del out_blob
|
||||
return decrypted_data
|
||||
@@ -0,0 +1,119 @@
|
||||
from os import urandom
|
||||
|
||||
from windows import winproxy
|
||||
from windows.crypto import DEFAULT_ENCODING
|
||||
from windows.generated_def import *
|
||||
|
||||
__all__ = ["encrypt", "decrypt"]
|
||||
|
||||
def encode_init_vector(data):
|
||||
blob = CRYPT_DATA_BLOB.from_string(data)
|
||||
size = DWORD()
|
||||
buf = None
|
||||
winproxy.CryptEncodeObjectEx(DEFAULT_ENCODING, X509_OCTET_STRING, ctypes.byref(blob), 0, None, buf, size)
|
||||
buf = (BYTE * size.value)()
|
||||
winproxy.CryptEncodeObjectEx(DEFAULT_ENCODING, X509_OCTET_STRING, ctypes.byref(blob), 0, None, buf, size)
|
||||
return buf[:]
|
||||
|
||||
|
||||
class GenerateInitVector(object):
|
||||
def __repr__(self):
|
||||
return "GenerateInitVector()"
|
||||
|
||||
def generate_init_vector(self, algo):
|
||||
if algo in [szOID_OIWSEC_desCBC, szOID_RSA_DES_EDE3_CBC]:
|
||||
return urandom(8)
|
||||
if algo in [szOID_NIST_AES128_CBC, szOID_NIST_AES192_CBC, szOID_NIST_AES256_CBC]:
|
||||
return urandom(16)
|
||||
return None
|
||||
geninitvector = GenerateInitVector()
|
||||
|
||||
|
||||
def encrypt(cert_or_certlist, msg, algo=szOID_NIST_AES256_CBC, initvector=geninitvector):
|
||||
"""Encrypt ``msg`` one or many :class:`Certificate` using ``algo`` with the initial
|
||||
vector ``initvector``.
|
||||
|
||||
If ``geninitvector`` is left as it is, it will generate a random one.
|
||||
|
||||
Algorithms supported by ``GenerateInitVector`` are:
|
||||
|
||||
* ``szOID_OIWSEC_desCBC``
|
||||
* ``szOID_RSA_DES_EDE3_CBC``
|
||||
* ``szOID_NIST_AES128_CBC``
|
||||
* ``szOID_NIST_AES192_CBC``
|
||||
* ``szOID_NIST_AES256_CBC``
|
||||
|
||||
:param cert_or_certlist: One or many :class:`Certificate` used to encrypt the msg
|
||||
:type cert_or_certlist: :class:`Certificate` | [:class:`Certificate`]
|
||||
:return: :class:`bytearray`: The encrypted message
|
||||
"""
|
||||
alg_ident = CRYPT_ALGORITHM_IDENTIFIER()
|
||||
alg_ident.pszObjId = algo.encode("ascii")
|
||||
# We want to have automatique translation of Certificate -> PCERT_CONTEXT
|
||||
# In order to simple create the 'PCERT_CONTEXT[] certs'
|
||||
# For that we need a tuple of X * 1-item-tuple
|
||||
# as a (cert,) will be automaticly translatable to a PCERT_CONTEXT
|
||||
if isinstance(cert_or_certlist, CERT_CONTEXT):
|
||||
certlist = ((cert_or_certlist,),)
|
||||
else:
|
||||
certlist = tuple((c,) for c in cert_or_certlist)
|
||||
|
||||
# Set (compute if needed) the IV
|
||||
if initvector is None:
|
||||
alg_ident.Parameters.cbData = 0
|
||||
elif initvector is geninitvector:
|
||||
initvector = initvector.generate_init_vector(algo)
|
||||
if initvector is None:
|
||||
raise ValueError("I don't know how to generate an <initvector> for <{0}> please provide one (or None)".format(algo))
|
||||
initvector_encoded = encode_init_vector(initvector)
|
||||
alg_ident.Parameters = CRYPT_DATA_BLOB.from_string(initvector_encoded)
|
||||
else:
|
||||
initvector_encoded = encode_init_vector(initvector)
|
||||
alg_ident.Parameters = CRYPT_DATA_BLOB.from_string(initvector_encoded)
|
||||
|
||||
# Setup encryption parameters
|
||||
param = CRYPT_ENCRYPT_MESSAGE_PARA()
|
||||
param.cbSize = ctypes.sizeof(param)
|
||||
param.dwMsgEncodingType = DEFAULT_ENCODING
|
||||
param.hCryptProv = None
|
||||
param.ContentEncryptionAlgorithm = alg_ident
|
||||
param.pvEncryptionAuxInfo = None
|
||||
param.dwFlags = 0
|
||||
param.dwInnerContentType = 0
|
||||
|
||||
|
||||
certs = (PCERT_CONTEXT * len(certlist))(*certlist)
|
||||
#Ask the output buffer size
|
||||
size = DWORD()
|
||||
winproxy.CryptEncryptMessage(param, len(certs), certs, msg, len(msg), None, size)
|
||||
#Encrypt the msg
|
||||
buf = (BYTE * size.value)()
|
||||
winproxy.CryptEncryptMessage(param, len(certs), certs, msg, len(msg), buf, size)
|
||||
return bytearray(buf[:size.value])
|
||||
|
||||
|
||||
def decrypt(cert_store, encrypted):
|
||||
"""Try to decrypt the ``encrypted`` msg with any certificate in ``cert_store``.
|
||||
|
||||
If there is no certificate able to decrypt the message ``WinproxyError(winerror=0x8009200c)`` is raised.
|
||||
|
||||
:param cert_store:
|
||||
:type cert_store: :class:`CertificateStore`
|
||||
:return: :class:`str`: The decrypted message
|
||||
"""
|
||||
# Setup decryption parameters
|
||||
dparam = CRYPT_DECRYPT_MESSAGE_PARA()
|
||||
dparam.cbSize = ctypes.sizeof(dparam)
|
||||
dparam.dwMsgAndCertEncodingType = DEFAULT_ENCODING
|
||||
dparam.cCertStore = 1
|
||||
dparam.rghCertStore = (cert_store,)
|
||||
dparam.dwFlags = 0
|
||||
|
||||
#Ask the output buffer size
|
||||
buf = (BYTE * len(encrypted)).from_buffer_copy(encrypted)
|
||||
dcryptsize = DWORD()
|
||||
winproxy.CryptDecryptMessage(dparam, buf, ctypes.sizeof(buf), None, dcryptsize, None)
|
||||
#Decrypt the msg
|
||||
dcryptbuff = (BYTE * (dcryptsize.value + 0x1000))()
|
||||
winproxy.CryptDecryptMessage(dparam, buf, ctypes.sizeof(buf), dcryptbuff, dcryptsize, None)
|
||||
return bytes(bytearray(dcryptbuff[:dcryptsize.value]))
|
||||
@@ -0,0 +1,53 @@
|
||||
import windows
|
||||
from windows import winproxy
|
||||
from windows.generated_def import *
|
||||
from windows.crypto import DEFAULT_ENCODING, CertificateStore
|
||||
|
||||
|
||||
def generate_selfsigned_certificate(name="CN=DEFAULT", prov=None, key_info=None, flags=0, signature_algo=None):
|
||||
"""Generate a selfsigned certificate.
|
||||
|
||||
See `CertCreateSelfSignCertificate <https://msdn.microsoft.com/en-us/library/windows/desktop/aa376039(v=vs.85).aspx>`_
|
||||
|
||||
:return: :class:`windows.crypto.Certificate`
|
||||
"""
|
||||
size = ULONG(len(name) + 0x100)
|
||||
buffer = (ctypes.c_ubyte * size.value)()
|
||||
winproxy.CertStrToNameA(X509_ASN_ENCODING, name, CERT_OID_NAME_STR, None, buffer, size, None)
|
||||
blobname = CRYPT_DATA_BLOB(size.value, buffer)
|
||||
cert = winproxy.CertCreateSelfSignCertificate(prov, blobname, flags, key_info, signature_algo, None, None, None)
|
||||
return windows.crypto.Certificate.from_pointer(cert)
|
||||
|
||||
|
||||
def generate_key(prov, keytype=AT_KEYEXCHANGE, flags=CRYPT_EXPORTABLE):
|
||||
"""Generate a keypair if type ``keytype``.
|
||||
|
||||
:return: :class:`HCRYPTKEY`
|
||||
"""
|
||||
key = HCRYPTKEY()
|
||||
winproxy.CryptGenKey(prov, keytype, flags , key)
|
||||
return key
|
||||
# print(key[0])
|
||||
# print("[OK] Key created")
|
||||
# size = DWORD()
|
||||
# winproxy.CryptExportKey(key, None, PRIVATEKEYBLOB, 0, None, size)
|
||||
# buffer = (BYTE * size.value)()
|
||||
# print("needed size = {0}".format(size))
|
||||
# winproxy.CryptExportKey(key, None, PRIVATEKEYBLOB, 0, buffer, size)
|
||||
# print("[OK] Key in buffer")
|
||||
# keyraw = bytearray(buffer)
|
||||
# # openssl.exe rsa -in key.out -inform MS\PRIVATEKEYBLOB -text
|
||||
# save_as(keyraw, "key.out")
|
||||
# #res = ctypes.WinDLL("advapi32").CryptReleaseContext(prov, 0)
|
||||
# return key
|
||||
|
||||
def generate_pfx(hstore, password=None):
|
||||
"""Generate a pfx protected by ``password`` contaning the certificates in ``hstore``
|
||||
|
||||
:return: :class:`bytearray` -- The raw PFX
|
||||
"""
|
||||
blob = CRYPT_DATA_BLOB(0, None)
|
||||
winproxy.PFXExportCertStoreEx(hstore, blob, password, None, EXPORT_PRIVATE_KEYS | REPORT_NO_PRIVATE_KEY | REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY)
|
||||
blob.pbData = (ctypes.c_ubyte * blob.cbData)()
|
||||
winproxy.PFXExportCertStoreEx(hstore, blob, password, None, EXPORT_PRIVATE_KEYS | REPORT_NO_PRIVATE_KEY | REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY)
|
||||
return blob.data
|
||||
@@ -0,0 +1,70 @@
|
||||
import windows
|
||||
from windows import winproxy
|
||||
from windows.crypto import DEFAULT_ENCODING
|
||||
import windows.generated_def as gdef
|
||||
|
||||
import ctypes
|
||||
|
||||
__all__ = ["sign", "verify_signature"]
|
||||
|
||||
def sign(cert, msg, detached_signature=False, algo=gdef.szOID_RSA_SHA256RSA):
|
||||
# hash algorithm
|
||||
alg_hash = gdef.CRYPT_ALGORITHM_IDENTIFIER()
|
||||
alg_hash.pszObjId = algo.encode()
|
||||
|
||||
# Signing parameters
|
||||
sign_para = gdef.CRYPT_SIGN_MESSAGE_PARA()
|
||||
sign_para.cbSize = ctypes.sizeof(sign_para)
|
||||
sign_para.dwMsgEncodingType = DEFAULT_ENCODING
|
||||
sign_para.pSigningCert = gdef.PCERT_CONTEXT(cert)
|
||||
sign_para.HashAlgorithm = alg_hash
|
||||
sign_para.pvHashAuxInfo = None
|
||||
sign_para.cMsgCert = 0
|
||||
sign_para.rgpMsgCert = None
|
||||
sign_para.cMsgCrl = 0
|
||||
sign_para.rgpMsgCrl = None
|
||||
sign_para.cAuthAttr = 0
|
||||
sign_para.rgAuthAttr = None
|
||||
sign_para.cUnauthAttr = 0
|
||||
sign_para.rgUnauthAttr = None
|
||||
sign_para.dwFlags = 0
|
||||
sign_para.dwInnerContentType = 0
|
||||
sign_para.HashEncryptionAlgorithm = alg_hash
|
||||
sign_para.pvHashEncryptionAuxInfo = None
|
||||
|
||||
ByteBuffer = windows.utils.BUFFER(gdef.BYTE)
|
||||
|
||||
result_buffer = ByteBuffer(nbelt=0x2000)
|
||||
result_size = gdef.DWORD(len(result_buffer))
|
||||
buff = ByteBuffer(*bytearray(msg))
|
||||
buff_pr = windows.utils.BUFFER(gdef.LPBYTE, nbelt=1)(buff)
|
||||
buff_size = gdef.DWORD(len(msg))
|
||||
try:
|
||||
windows.winproxy.CryptSignMessage(sign_para, False, 1, buff_pr, buff_size, result_buffer, result_size)
|
||||
except WindowsError as e:
|
||||
if not e.winerror == gdef.ERROR_MORE_DATA:
|
||||
raise
|
||||
result_buffer = ByteBuffer(nbelt=result_size.value)
|
||||
windows.winproxy.CryptSignMessage(sign_para, False, 1, buff_pr, buff_size, result_buffer, result_size)
|
||||
return bytearray(result_buffer[:result_size.value])
|
||||
|
||||
|
||||
def verify_signature(cert, encoded_blob):
|
||||
# Verify parameters
|
||||
verif_param = gdef.CRYPT_KEY_VERIFY_MESSAGE_PARA()
|
||||
verif_param.cbSize = ctypes.sizeof(gdef.CRYPT_KEY_VERIFY_MESSAGE_PARA)
|
||||
verif_param.dwMsgEncodingType = windows.crypto.DEFAULT_ENCODING
|
||||
verif_param.hCryptProv = None
|
||||
# The public key used
|
||||
pubkey = cert.pCertInfo[0].SubjectPublicKeyInfo
|
||||
# Preparing in/out buffer/size
|
||||
signed_buffer = windows.utils.BUFFER(gdef.BYTE).from_buffer_copy(encoded_blob)
|
||||
decoded_buffer = windows.utils.BUFFER(gdef.BYTE).from_buffer_copy(encoded_blob)
|
||||
decoded_size = gdef.DWORD(len(decoded_buffer))
|
||||
winproxy.CryptVerifyMessageSignatureWithKey(verif_param,
|
||||
pubkey,
|
||||
signed_buffer,
|
||||
len(encoded_blob),
|
||||
decoded_buffer,
|
||||
decoded_size)
|
||||
return bytearray(decoded_buffer[:decoded_size.value])
|
||||
@@ -0,0 +1,53 @@
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import inspect
|
||||
|
||||
options = {'active': False, 'cats': None}
|
||||
# options = {'active': True, 'cats': ["HANDLE"]}
|
||||
|
||||
|
||||
def get_stack_func_name(lvl):
|
||||
info = inspect.stack()[lvl]
|
||||
return info[0], info[3]
|
||||
|
||||
|
||||
def do_dbgprint(msg, type=None):
|
||||
if ("ALL" in options['cats']) or type.upper() in options['cats']:
|
||||
frame, func = get_stack_func_name(2)
|
||||
logger = logging.getLogger(frame.f_globals['__name__'] + ":" + func)
|
||||
logger.debug(msg)
|
||||
|
||||
|
||||
def do_nothing(*args, **kwargs):
|
||||
return None
|
||||
|
||||
|
||||
def parse_option(s):
|
||||
if s[0] == "=":
|
||||
s = s[1:]
|
||||
if s:
|
||||
cats = [x.upper().strip() for x in s.split('-')]
|
||||
options['cats'] = cats
|
||||
|
||||
formt = 'DBG|%(name)s|%(message)s'
|
||||
logging.basicConfig(format=formt, level=logging.DEBUG)
|
||||
|
||||
try:
|
||||
if 'DBGPRINT' in os.environ:
|
||||
parse_option(os.environ['DBGPRINT'])
|
||||
dbgprint = do_dbgprint
|
||||
elif any([opt.startswith("--DBGPRINT") for opt in sys.argv]):
|
||||
dbgprint = do_dbgprint
|
||||
option_str = [opt for opt in sys.argv if opt.startswith("--DBGPRINT")][0]
|
||||
parse_option(option_str[len('--DBGPRINT'):])
|
||||
elif options["active"]:
|
||||
formt = 'DBG|%(name)s|%(message)s'
|
||||
logging.basicConfig(format=formt, level=logging.DEBUG)
|
||||
dbgprint = do_dbgprint
|
||||
else:
|
||||
dbgprint = do_nothing
|
||||
except Exception as e:
|
||||
dbgprint = do_nothing
|
||||
print("dbgprint Error: {0}({1})".format(type(e), e))
|
||||
x = type(e), e
|
||||
@@ -0,0 +1,5 @@
|
||||
from .debugger import Debugger, HXBreakpoint
|
||||
from .symboldbg import SymbolDebugger
|
||||
from .localdbg import LocalDebugger
|
||||
from .breakpoints import *
|
||||
from .breakpoints import *
|
||||
@@ -0,0 +1,258 @@
|
||||
from collections import OrderedDict
|
||||
|
||||
import windows
|
||||
from windows.generated_def.winstructs import *
|
||||
from windows.generated_def import windef
|
||||
from windows.winobject.process import WinProcess, WinThread
|
||||
from windows.pycompat import basestring
|
||||
|
||||
|
||||
STANDARD_BP = "BP"
|
||||
HARDWARE_EXEC_BP = "HXBP"
|
||||
MEMORY_BREAKPOINT = "MEMBP"
|
||||
|
||||
class Breakpoint(object):
|
||||
"""An standard (Int3) breakpoint (type == ``STANDARD_BP``)"""
|
||||
type = STANDARD_BP # REAL BP
|
||||
def __init__(self, addr):
|
||||
self.addr = addr
|
||||
|
||||
def apply_to_target(self, target):
|
||||
return isinstance(target, WinProcess)
|
||||
|
||||
def trigger(self, dbg, exception):
|
||||
"""Called when breakpoint is hit"""
|
||||
pass
|
||||
|
||||
|
||||
class ProxyBreakpoint(Breakpoint):
|
||||
def __init__(self, target, addr, type):
|
||||
self.target = target
|
||||
self.addr = addr
|
||||
self.type = type
|
||||
|
||||
def trigger(self, dbg, exception):
|
||||
return self.target(dbg, exception)
|
||||
|
||||
|
||||
class HXBreakpoint(Breakpoint):
|
||||
"""An hardware-execution breakpoint (type == ``HARDWARE_EXEC_BP``)"""
|
||||
type = HARDWARE_EXEC_BP
|
||||
|
||||
def apply_to_target(self, target):
|
||||
return isinstance(target, WinThread)
|
||||
|
||||
class MemoryBreakpoint(Breakpoint):
|
||||
"""A memory breakpoint (type == ``MEMORY_BREAKPOINT``)"""
|
||||
type = MEMORY_BREAKPOINT
|
||||
DEFAULT_EVENTS = "RWX"
|
||||
DEFAULT_SIZE = 0x1000
|
||||
def __init__(self, addr, size=None, events=None):
|
||||
"""``size``: the size of the memory breakpoint.
|
||||
|
||||
``events``: a string representing the events that interest the BP (any of "RWX")"""
|
||||
super(MemoryBreakpoint, self).__init__(addr)
|
||||
self.size = size if size is not None else self.DEFAULT_SIZE
|
||||
events = events if events is not None else self.DEFAULT_EVENTS
|
||||
self.events = set(events)
|
||||
|
||||
def trigger(self, dbg, exception):
|
||||
"""Called when breakpoint is hit"""
|
||||
pass
|
||||
|
||||
|
||||
## Arguments Helper (need to move this elsewhere)
|
||||
class X86ArgumentRetriever(object):
|
||||
def get_arg(self, nb, proc, thread):
|
||||
return proc.read_dword(thread.context.sp + 4 + (4 * nb))
|
||||
|
||||
def set_arg(self, nb, value, proc, thread):
|
||||
return proc.write_dword(thread.context.sp + 4 + (4 * nb), value)
|
||||
|
||||
class X64ArgumentRetriever(object):
|
||||
REG_ARGS = ["Rcx", "Rdx", "R8", "R9"]
|
||||
def get_arg(self, nb, proc, thread):
|
||||
if nb < len(self.REG_ARGS):
|
||||
return getattr(thread.context, self.REG_ARGS[nb])
|
||||
return proc.read_qword(thread.context.sp + 8 + (8 * nb))
|
||||
|
||||
def set_arg(self, nb, value, proc, thread):
|
||||
if nb < len(self.REG_ARGS):
|
||||
ctx = thread.context
|
||||
setattr(ctx, self.REG_ARGS[nb], value)
|
||||
return thread.set_context(ctx)
|
||||
return proc.write_qword(thread.context.sp + 8 + (8 * nb), value)
|
||||
|
||||
## Behaviour breakpoint !
|
||||
# class FunctionParamDumpBP(Breakpoint):
|
||||
class FunctionParamDumpBPAbstract(object):
|
||||
def __init__(self, addr=None, target=None):
|
||||
if target is None:
|
||||
try:
|
||||
target = self.TARGET
|
||||
except AttributeError as e:
|
||||
raise ValueError("{0} bp without a <target> must have a <TARGET> class attribute")
|
||||
if addr is None:
|
||||
addr = "{0}!{1}".format(target.target_dll, target.target_func)
|
||||
super(FunctionParamDumpBPAbstract, self).__init__(addr)
|
||||
self.target = target
|
||||
self.target_args = target.prototype._argtypes_
|
||||
self.target_params = target.params
|
||||
|
||||
def extract_arguments_32bits(self, cproc, cthread):
|
||||
x = windows.debug.X86ArgumentRetriever()
|
||||
res = OrderedDict()
|
||||
for i, (name, type) in enumerate(zip(self.target_params, self.target_args)):
|
||||
value = x.get_arg(i, cproc, cthread)
|
||||
rt = windows.remotectypes.transform_type_to_remote32bits(type)
|
||||
if issubclass(rt, windows.remotectypes.RemoteValue):
|
||||
t = rt(value, cproc)
|
||||
else:
|
||||
t = rt(value)
|
||||
# Will fail in py3..
|
||||
content = None
|
||||
try:
|
||||
content = t.contents
|
||||
except Exception as e:
|
||||
# contents will fail on basic type
|
||||
# Not really an expected behavior
|
||||
# But it works for now.. (and since a while)
|
||||
pass
|
||||
if content is None:
|
||||
t = t.value
|
||||
res[name[1]] = t
|
||||
return res
|
||||
|
||||
def extract_arguments_64bits(self, cproc, cthread):
|
||||
x = windows.debug.X64ArgumentRetriever()
|
||||
res = OrderedDict()
|
||||
for i, (name, type) in enumerate(zip(self.target_params, self.target_args)):
|
||||
value = x.get_arg(i, cproc, cthread)
|
||||
rt = windows.remotectypes.transform_type_to_remote64bits(type)
|
||||
if issubclass(rt, windows.remotectypes.RemoteValue):
|
||||
t = rt(value, cproc)
|
||||
else:
|
||||
t = rt(value)
|
||||
if not hasattr(t, "contents"):
|
||||
try:
|
||||
t = t.value
|
||||
except AttributeError:
|
||||
pass
|
||||
res[name[1]] = t
|
||||
return res
|
||||
|
||||
def extract_arguments(self, cproc, cthread):
|
||||
"""Extracts the functions parameters in an :class:`OrderedDict`"""
|
||||
if windows.current_process.bitness == 32:
|
||||
return self.extract_arguments_32bits(cproc, cthread)
|
||||
if cproc.bitness == 64:
|
||||
return self.extract_arguments_64bits(cproc, cthread)
|
||||
# SysWow process from a 64bits debugger, handle bitness with CS
|
||||
if cthread.context.SegCs == windows.syswow64.CS_32bits:
|
||||
return self.extract_arguments_32bits(cproc, cthread)
|
||||
return self.extract_arguments_64bits(cproc, cthread)
|
||||
|
||||
def arguments(self, dbg):
|
||||
"TEST PARAM DICT"
|
||||
if windows.current_process.bitness == 32:
|
||||
extractor = windows.debug.X86ArgumentRetriever()
|
||||
elif dbg.current_process.bitness == 64:
|
||||
extractor = windows.debug.X64ArgumentRetriever()
|
||||
elif dbg.current_thread.context.SegCs == windows.syswow64.CS_32bits:
|
||||
extractor = windows.debug.X86ArgumentRetriever()
|
||||
else:
|
||||
extractor = windows.debug.X64ArgumentRetriever()
|
||||
name_map = {name:i for i, name in enumerate(t[1] for t in self.target_params)}
|
||||
return FunctionParameterProxy(extractor, name_map, self.target_args, dbg)
|
||||
|
||||
class FunctionParameterProxy(object):
|
||||
# TODO: clean this + put more of the logic in the X64ArgumentRetriever
|
||||
def __init__(self, extractor, name_map, parameters_type, x):
|
||||
self.extractor = extractor
|
||||
self.name_map = name_map
|
||||
self.parameters_type = parameters_type
|
||||
self.x = x
|
||||
|
||||
def __getitem__(self, x):
|
||||
if isinstance(x, basestring):
|
||||
x = self.name_map[x]
|
||||
# import pdb;pdb.set_trace()
|
||||
argtype = self.parameters_type[x]
|
||||
value = self.extractor.get_arg(x, self.x.current_process, self.x.current_thread)
|
||||
rt = windows.remotectypes.transform_type_to_remote32bits(argtype)
|
||||
if issubclass(rt, windows.remotectypes.RemoteValue):
|
||||
t = rt(value, self.x.current_process)
|
||||
else:
|
||||
t = rt(value)
|
||||
if not hasattr(t, "contents"):
|
||||
try:
|
||||
t = t.value
|
||||
except AttributeError:
|
||||
pass
|
||||
return t
|
||||
|
||||
def __setitem__(self, x, value):
|
||||
if isinstance(x, basestring):
|
||||
x = self.name_map[x]
|
||||
try:
|
||||
ctypes.cast(value, PVOID)
|
||||
except ctypes.ArgumentError:
|
||||
pass
|
||||
value = getattr(value, "value", value)
|
||||
return self.extractor.set_arg(x, value, self.x.current_process, self.x.current_thread)
|
||||
|
||||
|
||||
|
||||
class FunctionParamDumpBP(FunctionParamDumpBPAbstract, Breakpoint):
|
||||
pass
|
||||
|
||||
class FunctionParamDumpHXBP(FunctionParamDumpBPAbstract, HXBreakpoint):
|
||||
pass
|
||||
|
||||
class FunctionRetBP(Breakpoint):
|
||||
def __init__(self, addr, initial_breakpoint):
|
||||
super(FunctionRetBP, self).__init__(addr)
|
||||
self.initial_breakpoint = initial_breakpoint
|
||||
|
||||
def trigger(self, dbg, exc):
|
||||
dbg.del_bp(self, targets=[dbg.current_process])
|
||||
return self.initial_breakpoint.ret_trigger(dbg, exc)
|
||||
|
||||
|
||||
class FunctionCallBP(Breakpoint):
|
||||
"""A Breakpoint that allow to trigger at the return of a function"""
|
||||
def break_on_ret(self, dbg, exception):
|
||||
"""Setup a breakpoint at the return address of the function, this breakpoint will call :func:`ret_trigger`"""
|
||||
return_addr = self.get_ret_addr(dbg, exception)
|
||||
dbg.add_bp(FunctionRetBP(return_addr, self), target=dbg.current_process)
|
||||
|
||||
def get_ret_addr(self, dbg, exception):
|
||||
"""Get the return address of the current target, only valid in the trigger() function."""
|
||||
cproc = dbg.current_process
|
||||
return dbg.current_process.read_ptr(dbg.current_thread.context.sp)
|
||||
|
||||
|
||||
def ret_trigger(self, dbg, exception):
|
||||
"""Called at the return of the function if :func:`break_on_ret` was called"""
|
||||
raise NotImplementedError("ret_trigger")
|
||||
|
||||
|
||||
class FunctionBP(FunctionCallBP, FunctionParamDumpBP):
|
||||
"""A breakpoint that accepts a function from :mod:`windows.winproxy` and able to:
|
||||
|
||||
- Extract the arguments of the functions
|
||||
- Break at the return of the function
|
||||
"""
|
||||
|
||||
class PrintBP(Breakpoint):
|
||||
def __init__(self, addr, format, func=None):
|
||||
super(PrintBP, self).__init__(addr)
|
||||
self.format = format
|
||||
self.func = func
|
||||
|
||||
def trigger(self, dbg, exc):
|
||||
thread = dbg.current_thread
|
||||
format_dict = {"dbg": dbg, "exc": exc, "proc": dbg.current_process, "thread": thread, "ctx": thread.context}
|
||||
if self.func:
|
||||
format_dict.update(self.func(**format_dict))
|
||||
print(self.format.format(**format_dict))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,305 @@
|
||||
from collections import defaultdict
|
||||
from contextlib import contextmanager
|
||||
|
||||
import windows
|
||||
import windows.winobject.exception as winexception
|
||||
|
||||
from windows import winproxy
|
||||
from windows.generated_def import windef
|
||||
from windows.generated_def.winstructs import *
|
||||
from .breakpoints import *
|
||||
|
||||
class FakeDebuggerCurrentThread(object):
|
||||
"""A pseudo thread representing the current thread at exception time"""
|
||||
def __init__(self, dbg):
|
||||
self.dbg = dbg
|
||||
|
||||
@property
|
||||
def tid(self):
|
||||
return windows.current_thread.tid
|
||||
|
||||
@property
|
||||
def context(self):
|
||||
"""!!! This context in-place modification will be effective without set_context"""
|
||||
return self.dbg.get_exception_context()
|
||||
|
||||
def set_context(self, context):
|
||||
# The context returned by 'self.context' already modify the return context in place..
|
||||
pass
|
||||
|
||||
class LocalDebugger(object):
|
||||
"""A debugger interface around :func:`AddVectoredExceptionHandler`.
|
||||
|
||||
Handle:
|
||||
|
||||
* Standard BP (int3)
|
||||
* Hardware-Exec BP (DrX)
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.breakpoints = {}
|
||||
self._memory_save = {}
|
||||
self._reput_breakpoint = {}
|
||||
self._hxbp_breakpoint = defaultdict(dict)
|
||||
|
||||
self.callback_vectored = winexception.VectoredException(self.callback)
|
||||
winproxy.AddVectoredExceptionHandler(0, self.callback_vectored)
|
||||
self.setup_hxbp_callback_vectored = winexception.VectoredException(self.setup_hxbp_callback)
|
||||
self.hxbp_info = None
|
||||
self.code = windows.native_exec.create_function(b"\xcc\xc3", [PVOID])
|
||||
self.veh_depth = 0
|
||||
self.current_exception = None
|
||||
self.exceptions_stack = [None]
|
||||
self.current_process = windows.current_process
|
||||
self.current_thread = FakeDebuggerCurrentThread(self)
|
||||
|
||||
@contextmanager
|
||||
def NewCurrentException(self, exc):
|
||||
try:
|
||||
self.exceptions_stack.append(exc)
|
||||
self.current_exception = exc
|
||||
self.veh_depth += 1
|
||||
yield exc
|
||||
finally:
|
||||
self.exceptions_stack.pop()
|
||||
self.current_exception = self.exceptions_stack[-1]
|
||||
self.veh_depth -= 1
|
||||
|
||||
def get_exception_code(self):
|
||||
"""Return ExceptionCode of current exception"""
|
||||
return self.current_exception[0].ExceptionRecord[0].ExceptionCode
|
||||
|
||||
def get_exception_context(self):
|
||||
"""Return context of current exception"""
|
||||
return self.current_exception[0].ContextRecord[0]
|
||||
|
||||
def single_step(self):
|
||||
"""Make the current thread to single step"""
|
||||
self.get_exception_context().EEFlags.TF = 1
|
||||
return windef.EXCEPTION_CONTINUE_EXECUTION
|
||||
|
||||
def _pass_breakpoint(self, addr, single_step):
|
||||
with windows.utils.VirtualProtected(addr, 1, PAGE_EXECUTE_READWRITE):
|
||||
windows.current_process.write_memory(addr, self._memory_save[addr])
|
||||
self._reput_breakpoint[windows.current_thread.tid] = self.breakpoints[addr], single_step
|
||||
return self.single_step()
|
||||
|
||||
def _local_resolve(self, addr):
|
||||
if not isinstance(addr, basestring):
|
||||
return addr
|
||||
dll, api = addr.split("!")
|
||||
dll = dll.lower()
|
||||
modules = {m.name[:-len(".dll")] if m.name.endswith(".dll") else m.name : m for m in windows.current_process.peb.modules}
|
||||
mod = None
|
||||
if dll in modules:
|
||||
mod = [modules[dll]]
|
||||
if not mod:
|
||||
return None
|
||||
# TODO: optim exports are the same for whole system (32 vs 64 bits)
|
||||
# I don't have to reparse the exports each time..
|
||||
# Try to interpret api as an int
|
||||
try:
|
||||
api_int = int(api, 0)
|
||||
return mod[0].baseaddr + api_int
|
||||
except ValueError:
|
||||
pass
|
||||
exports = mod[0].pe.exports
|
||||
if api not in exports:
|
||||
dbgprint("Error resolving <{0}> in local process".format(addr, target), "DBG")
|
||||
raise ValueError("Unknown API <{0}> in DLL {1}".format(api, dll))
|
||||
return exports[api]
|
||||
|
||||
def callback(self, exc):
|
||||
with self.NewCurrentException(exc):
|
||||
return self.handle_exception(exc)
|
||||
|
||||
def handle_exception(self, exc):
|
||||
exp_code = self.get_exception_code()
|
||||
context = self.get_exception_context()
|
||||
exp_addr = context.pc
|
||||
if exp_code == EXCEPTION_BREAKPOINT and exp_addr in self.breakpoints:
|
||||
res = self.breakpoints[exp_addr].trigger(self, exc)
|
||||
single_step = self.get_exception_context().EEFlags.TF # single step activated by breakpoint
|
||||
if exp_addr in self.breakpoints: # Breakpoint deleted itself ?
|
||||
return self._pass_breakpoint(exp_addr, single_step)
|
||||
return EXCEPTION_CONTINUE_EXECUTION
|
||||
|
||||
if exp_code == EXCEPTION_SINGLE_STEP and windows.current_thread.tid in self._reput_breakpoint:
|
||||
bp, single_step = self._reput_breakpoint[windows.current_thread.tid]
|
||||
self._memory_save[bp._addr] = windows.current_process.read_memory(bp._addr, 1)
|
||||
with windows.utils.VirtualProtected(bp._addr, 1, PAGE_EXECUTE_READWRITE):
|
||||
windows.current_process.write_memory(bp._addr, b"\xcc")
|
||||
del self._reput_breakpoint[windows.current_thread.tid]
|
||||
if single_step:
|
||||
return self.on_exception(exc)
|
||||
return windef.EXCEPTION_CONTINUE_EXECUTION
|
||||
elif exp_code == EXCEPTION_SINGLE_STEP and exp_addr in self._hxbp_breakpoint[windows.current_thread.tid]:
|
||||
res = self._hxbp_breakpoint[windows.current_thread.tid][exp_addr].trigger(self, exc)
|
||||
context.EEFlags.RF = 1
|
||||
return EXCEPTION_CONTINUE_EXECUTION
|
||||
return self.on_exception(exc)
|
||||
|
||||
def on_exception(self, exc):
|
||||
"""Called on exception"""
|
||||
if not self.get_exception_code() in winexception.exception_name_by_value:
|
||||
return windef.EXCEPTION_CONTINUE_SEARCH
|
||||
return windef.EXCEPTION_CONTINUE_EXECUTION
|
||||
|
||||
def del_bp(self, bp, targets=None):
|
||||
"""Delete a breakpoint"""
|
||||
# TODO: check targets..
|
||||
if bp.type == STANDARD_BP:
|
||||
with windows.utils.VirtualProtected(bp.addr, 1, PAGE_EXECUTE_READWRITE):
|
||||
windows.current_process.write_memory(bp.addr, self._memory_save[bp.addr])
|
||||
del self._memory_save[bp.addr]
|
||||
del self.breakpoints[bp.addr]
|
||||
return
|
||||
if bp.type == HARDWARE_EXEC_BP:
|
||||
threads_by_tid = {t.tid: t for t in windows.current_process.threads}
|
||||
for tid in self._hxbp_breakpoint:
|
||||
if bp.addr in self._hxbp_breakpoint[tid] and self._hxbp_breakpoint[tid][bp.addr] == bp:
|
||||
if tid == windows.current_thread.tid:
|
||||
self.remove_hxbp_self_thread(bp.addr)
|
||||
else:
|
||||
self.remove_hxbp_other_thread(bp.addr, threads_by_tid[tid])
|
||||
del self._hxbp_breakpoint[tid][bp.addr]
|
||||
return
|
||||
raise NotImplementedError("Unknow BP type {0}".format(bp.type))
|
||||
|
||||
def add_bp(self, bp, target=None):
|
||||
"""Add a breakpoint, bp is a "class:`Breakpoint`
|
||||
|
||||
If the ``bp`` type is ``STANDARD_BP``, target must be None.
|
||||
|
||||
If the ``bp`` type is ``HARDWARE_EXEC_BP``, target can be None (all threads), or some threads of the process
|
||||
"""
|
||||
if bp.type == HARDWARE_EXEC_BP:
|
||||
return self.add_bp_hxbp(bp, target)
|
||||
if bp.type != STANDARD_BP:
|
||||
raise NotImplementedError("Unknow BP type {0}".format(bp.type))
|
||||
if target not in [None, windows.current_process]:
|
||||
raise ValueError("LocalDebugger: STANDARD_BP doest not support targets {0}".format(targets))
|
||||
addr = self._local_resolve(bp.addr)
|
||||
bp._addr = addr
|
||||
self.breakpoints[addr] = bp
|
||||
self._memory_save[addr] = windows.current_process.read_memory(addr, 1)
|
||||
with windows.utils.VirtualProtected(addr, 1, PAGE_EXECUTE_READWRITE):
|
||||
windows.current_process.write_memory(addr, b"\xcc")
|
||||
return
|
||||
|
||||
def add_bp_hxbp(self, bp, targets=None):
|
||||
if bp.type != HARDWARE_EXEC_BP:
|
||||
raise NotImplementedError("Add non standard-BP in LocalDebugger")
|
||||
if targets is None:
|
||||
targets = windows.current_process.threads
|
||||
for thread in targets:
|
||||
if thread.owner.pid != windows.current_process.pid:
|
||||
raise ValueError("Cannot add HXBP to target in remote process {0}".format(thread))
|
||||
if thread.tid == windows.current_thread.tid:
|
||||
self.setup_hxbp_self_thread(bp.addr)
|
||||
else:
|
||||
self.setup_hxbp_other_thread(bp.addr, thread)
|
||||
self._hxbp_breakpoint[thread.tid][bp.addr] = bp
|
||||
|
||||
def setup_hxbp_callback(self, exc):
|
||||
with self.NewCurrentException(exc):
|
||||
exp_code = self.get_exception_code()
|
||||
if exp_code != windef.EXCEPTION_BREAKPOINT:
|
||||
return windef.EXCEPTION_CONTINUE_SEARCH
|
||||
context = self.get_exception_context()
|
||||
exp_addr = context.pc
|
||||
hxbp_used = self.setup_hxbp_in_context(context, self.data)
|
||||
windows.current_process.write_memory(exp_addr, b"\x90")
|
||||
# Raising in the VEH is a bad idea..
|
||||
# So better give the information to triggerer..
|
||||
if hxbp_used is not None:
|
||||
self.get_exception_context().func_result = exp_addr
|
||||
else:
|
||||
self.get_exception_context().func_result = 0
|
||||
return windef.EXCEPTION_CONTINUE_EXECUTION
|
||||
|
||||
def remove_hxbp_callback(self, exc):
|
||||
with self.NewCurrentException(exc):
|
||||
exp_code = self.get_exception_code()
|
||||
context = self.get_exception_context()
|
||||
exp_addr = context.pc
|
||||
hxbp_used = self.remove_hxbp_in_context(context, self.data)
|
||||
windows.current_process.write_memory(exp_addr, b"\x90")
|
||||
# Raising in the VEH is a bad idea..
|
||||
# So better give the information to triggerer..
|
||||
if hxbp_used is not None:
|
||||
self.get_exception_context().Eax = exp_addr
|
||||
else:
|
||||
self.get_exception_context().Eax = 0
|
||||
return windef.EXCEPTION_CONTINUE_EXECUTION
|
||||
|
||||
def setup_hxbp_in_context(self, context, addr):
|
||||
for i in range(4):
|
||||
is_used = getattr(context.EDr7, "L" + str(i))
|
||||
empty_drx = str(i)
|
||||
if not is_used:
|
||||
context.EDr7.GE = 1
|
||||
context.EDr7.LE = 1
|
||||
setattr(context.EDr7, "L" + empty_drx, 1)
|
||||
setattr(context, "Dr" + empty_drx, addr)
|
||||
return i
|
||||
return None
|
||||
|
||||
def remove_hxbp_in_context(self, context, addr):
|
||||
for i in range(4):
|
||||
target_drx = str(i)
|
||||
is_used = getattr(context.EDr7, "L" + str(i))
|
||||
draddr = getattr(context, "Dr" + target_drx)
|
||||
|
||||
if is_used and draddr == addr:
|
||||
setattr(context.EDr7, "L" + target_drx, 0)
|
||||
setattr(context, "Dr" + target_drx, 0)
|
||||
return i
|
||||
return None
|
||||
|
||||
def setup_hxbp_self_thread(self, addr):
|
||||
if self.current_exception is not None:
|
||||
x = self.setup_hxbp_in_context(self.get_exception_context(), addr)
|
||||
if x is None:
|
||||
raise ValueError("Could not setup HXBP")
|
||||
return
|
||||
|
||||
self.data = addr
|
||||
with winexception.VectoredExceptionHandler(1, self.setup_hxbp_callback):
|
||||
x = self.code()
|
||||
if x is None:
|
||||
raise ValueError("Could not setup HXBP")
|
||||
windows.current_process.write_memory(x, b"\xcc")
|
||||
return
|
||||
|
||||
def setup_hxbp_other_thread(self, addr, thread):
|
||||
thread.suspend()
|
||||
ctx = thread.context
|
||||
x = self.setup_hxbp_in_context(ctx, addr)
|
||||
if x is None:
|
||||
raise ValueError("Could not setup HXBP in {0}".format(thread))
|
||||
thread.set_context(ctx)
|
||||
thread.resume()
|
||||
|
||||
def remove_hxbp_self_thread(self, addr):
|
||||
if self.current_exception is not None:
|
||||
x = self.remove_hxbp_in_context(self.get_exception_context(), addr)
|
||||
if x is None:
|
||||
raise ValueError("Could not setup HXBP")
|
||||
return
|
||||
self.data = addr
|
||||
with winexception.VectoredExceptionHandler(1, self.remove_hxbp_callback):
|
||||
x = self.code()
|
||||
if x is None:
|
||||
raise ValueError("Could not remove HXBP")
|
||||
windows.current_process.write_memory(x, b"\xcc")
|
||||
return
|
||||
|
||||
def remove_hxbp_other_thread(self, addr, thread):
|
||||
thread.suspend()
|
||||
ctx = thread.context
|
||||
x = self.remove_hxbp_in_context(ctx, addr)
|
||||
if x is None:
|
||||
raise ValueError("Could not setup HXBP in {0}".format(thread))
|
||||
thread.set_context(ctx)
|
||||
thread.resume()
|
||||
@@ -0,0 +1,48 @@
|
||||
import windows
|
||||
import windows.generated_def as gdef
|
||||
from windows.pycompat import int_types
|
||||
|
||||
from . import Debugger
|
||||
from . import symbols
|
||||
|
||||
class SymbolDebugger(Debugger):
|
||||
"""A debugger using the symbol API (hence PDB) for name resolution.
|
||||
To use PDB, a correct version of dbghelp should be configured as well as ``_NT_SYMBOL_PATH``.
|
||||
(See :ref:`debug_symbols_module`)
|
||||
|
||||
This debugger add a ``current_resolver`` variable (A :class:`~windows.debug.symbols.ProcessSymbolHandler`) for the ``current_process``.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SymbolDebugger, self).__init__(*args, **kwargs)
|
||||
self._resolvers = {}
|
||||
|
||||
def _internal_on_load_dll(self, load_dll):
|
||||
path = self._get_loaded_dll(load_dll)
|
||||
# Path is used instead of name for naming the module (and can be set to whatever if using file handle)
|
||||
x = self.current_resolver.load_module(load_dll.hFile, path=path, addr=load_dll.lpBaseOfDll)
|
||||
|
||||
def _internal_on_create_process(self, create_process):
|
||||
# Create and setup a symbol resolver for the new process
|
||||
resolver = symbols.ProcessSymbolHandler(self.current_process)
|
||||
self._resolvers[self.current_process.pid] = resolver
|
||||
self.current_resolver = resolver
|
||||
|
||||
def _update_debugger_state(self, debug_event):
|
||||
super(SymbolDebugger, self)._update_debugger_state(debug_event)
|
||||
self.current_resolver = self._resolvers[debug_event.dwProcessId]
|
||||
|
||||
def _resolve(self, addr, target):
|
||||
if isinstance(addr, int_types):
|
||||
return addr
|
||||
if "+" in addr:
|
||||
symbol, deplacement = addr.split("+", 1)
|
||||
deplacement = int(deplacement, 0)
|
||||
else:
|
||||
symbol = addr
|
||||
deplacement = 0
|
||||
try:
|
||||
return self.current_resolver[symbol].addr + deplacement
|
||||
except WindowsError as e:
|
||||
if not e.winerror in (gdef.ERROR_NOT_FOUND, gdef.ERROR_MOD_NOT_FOUND):
|
||||
raise
|
||||
return None
|
||||
@@ -0,0 +1,744 @@
|
||||
import os.path
|
||||
import ctypes
|
||||
import copy
|
||||
import itertools
|
||||
from collections import namedtuple
|
||||
|
||||
import windows
|
||||
import windows.generated_def as gdef
|
||||
from windows import winproxy
|
||||
from windows.pycompat import basestring
|
||||
|
||||
DEFAULT_DBG_OPTION = gdef.SYMOPT_DEFERRED_LOADS + gdef.SYMOPT_UNDNAME
|
||||
|
||||
|
||||
def set_dbghelp_path(path):
|
||||
"""Set the path of the ``dbghelp.dll`` file to use. It allow to configure a different version of the DLL handling PDB downloading.
|
||||
|
||||
If ``path`` is a directory, the final ``dbghelp.dll`` will be computed as
|
||||
``path\<current_process_bitness>\dbghelp.dll``.
|
||||
|
||||
This allow to use the same script transparently in both 32b & 64b python interpreters.
|
||||
"""
|
||||
loaded_modules = [m.name.lower() for m in windows.current_process.peb.modules]
|
||||
if os.path.isdir(path):
|
||||
path = os.path.join(path, str(windows.current_process.bitness), "dbghelp.dll")
|
||||
if "dbghelp.dll" in loaded_modules:
|
||||
raise ValueError("setup_dbghelp_path should be called before any dbghelp function")
|
||||
# Change the DLL used by DbgHelpProxy
|
||||
winproxy.DbgHelpProxy.APIDLL = path
|
||||
return
|
||||
|
||||
# Load symbol config from ENV if present
|
||||
try:
|
||||
env_dbghelp_path = os.environ["PFW_DBGHELP_PATH"]
|
||||
# Setup the dbghelp path used by PFW
|
||||
set_dbghelp_path(env_dbghelp_path)
|
||||
except KeyError as e:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class SymbolInfoBase(object):
|
||||
"""Represent a Symbol.
|
||||
This class in based on the class `SYMBOL_INFO <https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/ns-dbghelp-symbol_info>`_
|
||||
with the handling on displacement embeded into it.
|
||||
"""
|
||||
# Init on ctypes struct is not always called
|
||||
# resolver & displacement should be set manually
|
||||
CHAR_TYPE = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.resolver = kwargs.get("resolver", None)
|
||||
#: POUET POUET
|
||||
self.displacement = kwargs.get("displacement", 0) #: POUET POUET
|
||||
|
||||
|
||||
def as_type(self):
|
||||
# assert self.Address == 0 ?
|
||||
return SymbolType(self.Index, self.ModBase, self.resolver)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name of the symbol"""
|
||||
if not self.NameLen:
|
||||
return None
|
||||
size = self.NameLen
|
||||
addr = ctypes.addressof(self) + type(self).Name.offset
|
||||
return (self.CHAR_TYPE * size).from_address(addr)[:]
|
||||
|
||||
@property
|
||||
def fullname(self):
|
||||
"""The fullname of the symbol in the windbg format ``mod!sym+displacement``"""
|
||||
return str(self)
|
||||
|
||||
@property
|
||||
def addr(self):
|
||||
"""The address of the symbol"""
|
||||
return self.Address + self.displacement
|
||||
|
||||
@property
|
||||
def start(self):
|
||||
"""The address of the start of the symbol
|
||||
If the symbol include a displacement, it is not taken into account
|
||||
"""
|
||||
return self.Address
|
||||
|
||||
@property # Fixed ?
|
||||
def module(self):
|
||||
"""The module containing the symbol
|
||||
|
||||
:type: :class:`SymbolModule`
|
||||
"""
|
||||
return self.resolver.get_module(self.ModBase)
|
||||
|
||||
@property
|
||||
def tag(self):
|
||||
"""The Tag of the module
|
||||
|
||||
:type: :class:`~windows.generated_def.winstructs.SymTagEnum`
|
||||
"""
|
||||
return gdef.SymTagEnum.mapper[self.Tag]
|
||||
|
||||
def __int__(self):
|
||||
"""An alias for ``addr``"""
|
||||
return self.addr
|
||||
|
||||
def __str__(self):
|
||||
"""The fullname of the symbol in the windbg format ``mod!sym+displacement``"""
|
||||
if self.displacement:
|
||||
return "{self.module.name}!{self.name}+{self.displacement:#x}".format(self=self)
|
||||
return "{self.module.name}!{self.name}".format(self=self)
|
||||
|
||||
def __repr__(self):
|
||||
if self.displacement:
|
||||
return '<{0} name="{1}" start={2:#x} displacement={3:#x} tag={4}>'.format(type(self).__name__, self.name, self.start, self.displacement, self.tag.name)
|
||||
return '<{0} name="{1}" start={2:#x} tag={3}>'.format(type(self).__name__, self.name, self.start, self.tag.name)
|
||||
|
||||
|
||||
class SymbolInfoA(gdef.SYMBOL_INFO, SymbolInfoBase):
|
||||
"""Represent a Symbol.
|
||||
This class in based on the class `SYMBOL_INFO <https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/ns-dbghelp-symbol_info>`_
|
||||
with the handling on displacement embeded into it.s
|
||||
|
||||
Exemple:
|
||||
|
||||
>>> sh = windows.debug.symbols.VirtualSymbolHandler()
|
||||
>>> mod = sh.load_file(r"c:\windows\system32\kernelbase.dll")
|
||||
>>> sym1 = sh["kernelbase!CreateFileW"]
|
||||
>>> sym2 = sh[int(sym1) + 3]
|
||||
>>> sym2
|
||||
<SymbolInfoA name="CreateFileW" start=0x100f20b0 displacement=0x3 tag=SymTagPublicSymbol>
|
||||
>>> hex(sym2.start)
|
||||
'0x100f20b0L'
|
||||
>>> hex(sym2.addr)
|
||||
'0x100f20b3L'
|
||||
>>> hex(sym2.displacement)
|
||||
'0x3L'
|
||||
>>> str(sym2)
|
||||
'kernelbase!CreateFileW+0x3'
|
||||
"""
|
||||
CHAR_TYPE = gdef.CHAR
|
||||
|
||||
class SymbolInfoW(gdef.SYMBOL_INFOW, SymbolInfoBase):
|
||||
CHAR_TYPE = gdef.WCHAR
|
||||
|
||||
# We use the A Api in our code (for now)
|
||||
SymbolInfo = SymbolInfoA
|
||||
|
||||
class SymbolType(object):
|
||||
def __init__(self, typeid, modbase, resolver):
|
||||
# Inheritance ?
|
||||
self.resolver = resolver
|
||||
self._typeid = typeid # Kind of a handle. Different of typeid property.
|
||||
self.modbase = modbase
|
||||
|
||||
def _get_type_info(self, typeinfo, ires=None):
|
||||
res = ires
|
||||
if res is None:
|
||||
res = TST_TYPE_RES_TYPE.get(typeinfo, gdef.DWORD)()
|
||||
windows.winproxy.SymGetTypeInfo(self.resolver.handle, self.modbase, self._typeid, typeinfo, ctypes.byref(res))
|
||||
if ires is not None:
|
||||
return ires
|
||||
newres = res.value
|
||||
if isinstance(res, gdef.LPWSTR):
|
||||
windows.winproxy.LocalFree(res)
|
||||
return newres
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._get_type_info(gdef.TI_GET_SYMNAME)
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self._get_type_info(gdef.TI_GET_LENGTH)
|
||||
|
||||
@property
|
||||
def tag(self):
|
||||
return self._get_type_info(gdef.TI_GET_SYMTAG)
|
||||
|
||||
# Diff type/typeid ?
|
||||
@property
|
||||
def type(self):
|
||||
return self.new_typeid(self._get_type_info(gdef.TI_GET_TYPE))
|
||||
|
||||
@property
|
||||
def typeid(self):
|
||||
return self.new_typeid(self._get_type_info(gdef.TI_GET_TYPEID))
|
||||
|
||||
@property
|
||||
def basetype(self):
|
||||
return gdef.BasicType.mapper[self._get_type_info(gdef.TI_GET_BASETYPE)]
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
return self.new_typeid(self._get_type_info(gdef.TI_GET_CLASSPARENTID))
|
||||
|
||||
@property
|
||||
def datakind(self):
|
||||
return gdef.DataKind.mapper[self._get_type_info(gdef.TI_GET_DATAKIND)]
|
||||
|
||||
@property
|
||||
def udtkind(self):
|
||||
return gdef.UdtKind.mapper[self._get_type_info(gdef.TI_GET_UDTKIND)]
|
||||
|
||||
@property
|
||||
def offset(self):
|
||||
return self._get_type_info(gdef.TI_GET_OFFSET)
|
||||
|
||||
@property
|
||||
def nb_children(self):
|
||||
return self._get_type_info(gdef.TI_GET_CHILDRENCOUNT)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self._get_type_info(gdef.TI_GET_VALUE)
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
count = self.nb_children
|
||||
class res_struct(ctypes.Structure):
|
||||
_fields_ = [("Count", gdef.ULONG), ("Start", gdef.ULONG), ("Types", (gdef.ULONG * count))]
|
||||
x = res_struct()
|
||||
x.Count = count
|
||||
x.Start = 0
|
||||
self._get_type_info(gdef.TI_FINDCHILDREN, x)
|
||||
return [self.new_typeid(ch) for ch in x.Types]
|
||||
|
||||
# Constructor
|
||||
@classmethod
|
||||
def from_symbol_info(cls, syminfo, resolver):
|
||||
return cls(syminfo.TypeIndex, syminfo.ModBase, resolver)
|
||||
|
||||
# Constructor
|
||||
def new_typeid(self, newtypeid):
|
||||
return type(self)(newtypeid, self.modbase, self.resolver)
|
||||
|
||||
def __repr__(self):
|
||||
if self.tag == gdef.SymTagBaseType:
|
||||
return '<{0} <basetype> {1!r}>'.format(type(self).__name__, self.basetype)
|
||||
elif self.tag == gdef.SymTagPointerType:
|
||||
target_type = self.type.name
|
||||
return '<{0} PTR TO "{1}" tag={2}>'.format(type(self).__name__, target_type, self.tag)
|
||||
return '<{0} name="{1}" tag={2}>'.format(type(self).__name__, self.name, self.tag)
|
||||
|
||||
|
||||
class SymbolModule(gdef.IMAGEHLP_MODULE64):
|
||||
"""Represent a loaded symbol module
|
||||
(see `MSDN IMAGEHLP_MODULE64 <https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/ns-dbghelp-imagehlp_module64>`_)
|
||||
|
||||
.. note::
|
||||
|
||||
This represent a module in the ``symbol space`` for symbol resolution.
|
||||
This can be completly virtual (particularly in the case of :class:`VirtualSymbolHandler`
|
||||
"""
|
||||
# Init on ctypes struct is not always called
|
||||
# resolver should be set manually
|
||||
def __init__(self, resolver):
|
||||
self.resolver = resolver
|
||||
|
||||
@property
|
||||
def addr(self):
|
||||
"""The load address of the module"""
|
||||
return self.BaseOfImage
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name of the module"""
|
||||
return self.ModuleName
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"""The full path and file name of the file from which symbols were loaded."""
|
||||
return self.LoadedImageName
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""The type of module (:class:`~windows.generated_def.winstructs.SYM_TYPE`),
|
||||
which can be one of:
|
||||
|
||||
=========== =========================
|
||||
SymCoff COFF symbols.
|
||||
SymCv CodeView symbols.
|
||||
SymDeferred Symbol loading deferred.
|
||||
SymDia DIA symbols.
|
||||
SymExport Symbols generated from a DLL export table.
|
||||
SymNone No symbols are loaded.
|
||||
SymPdb PDB symbols.
|
||||
SymSym .sym file.
|
||||
SymVirtual The virtual module created by SymLoadModuleEx with SLMFLAG_VIRTUAL.
|
||||
=========== =========================
|
||||
"""
|
||||
return self.SymType
|
||||
|
||||
@property
|
||||
def pdb(self):
|
||||
"""The local path of the loaded PDB if present
|
||||
|
||||
Exemple:
|
||||
>>> sh = windows.debug.symbols.VirtualSymbolHandler()
|
||||
>>> mod = sh.load_file(r"c:\windows\system32\kernelbase.dll")
|
||||
>>> mod.pdb
|
||||
'd:\\symbols\\wkernelbase.pdb\\017FA9C5278235B7E6BFBA74A9A5AAD91\\wkernelbase.pdb'
|
||||
"""
|
||||
LoadedPdbName = self.LoadedPdbName
|
||||
if not LoadedPdbName:
|
||||
return None
|
||||
return LoadedPdbName
|
||||
|
||||
def __repr__(self):
|
||||
pdb_basename = self.LoadedPdbName.split(b"\\")[-1]
|
||||
return '<{0} name="{1}" type={2} pdb="{3}" addr={4:#x}>'.format(type(self).__name__, self.name, self.type.value.name, pdb_basename, self.addr)
|
||||
|
||||
|
||||
# https://docs.microsoft.com/en-us/windows/win32/debug/symbol-handler-initialization
|
||||
class SymbolHandler(object):
|
||||
"""Base class of symbol handler"""
|
||||
|
||||
def __init__(self, handle, search_path=None, invade_process=False):
|
||||
# https://docs.microsoft.com/en-us/windows/desktop/api/dbghelp/nf-dbghelp-syminitialize
|
||||
# This value should be unique and nonzero, but need not be a process handle.
|
||||
# be sure to use the correct handle.
|
||||
self.handle = handle #: The handle of the symbol handler
|
||||
if not engine.options_already_setup:
|
||||
engine.set_options(DEFAULT_DBG_OPTION)
|
||||
winproxy.SymInitialize(handle, search_path, invade_process)
|
||||
|
||||
|
||||
def load_module(self, file_handle=None, path=None, name=None, addr=0, size=0, data=None, flags=0):
|
||||
"""Load a module at a given ``addr``. The module to load can be pass via a ``file_handle``
|
||||
or the direct ``path`` of the file to load.
|
||||
|
||||
:return: :class:`SymbolModule` -- The loaded module
|
||||
|
||||
.. note::
|
||||
|
||||
The logic of ``SymLoadModuleEx`` seems somewhat strange about the naming of the loaded module.
|
||||
A custom module ``name`` is only taken into account if the file is passed via a File handle.
|
||||
To make it more intuitive, if this function is call with a ``path`` and ``name`` and no ``file_handle``,
|
||||
it will open the path and directly call ``SymLoadModuleEx`` with a file handle and a name.
|
||||
"""
|
||||
|
||||
# Is that a bug in SymLoadModuleEx ?
|
||||
# To get a custom name for a module it use "path"
|
||||
# So we need to use file_handle and set a custom path
|
||||
# ! BUT it means we cannot get a custom name for a module where the path is not explicit and need to be searched
|
||||
if name is not None and file_handle is None and os.path.exists(path):
|
||||
try:
|
||||
f = open(path)
|
||||
file_handle = windows.utils.get_handle_from_file(f)
|
||||
path = name
|
||||
except Exception as e:
|
||||
pass
|
||||
# Expect a-string
|
||||
path = windows.pycompat.raw_encode(path)
|
||||
try:
|
||||
load_addr = winproxy.SymLoadModuleEx(self.handle, file_handle, path, name, addr, size, data, flags)
|
||||
except WindowsError as e:
|
||||
# if e.winerror == 0:
|
||||
# Already loaded ?
|
||||
# What if someone try to load another PE at the same BaseOfDll ?
|
||||
# return BaseOfDll
|
||||
raise
|
||||
return self.get_module(load_addr)
|
||||
|
||||
def load_file(self, path, name=None, addr=0, size=0, data=None, flags=0):
|
||||
"""Load the module ``path`` at ``addr``
|
||||
|
||||
:return: :class:`SymbolModule` -- The loaded module
|
||||
"""
|
||||
return self.load_module(path=path, name=name, addr=addr, size=size, data=data, flags=flags)
|
||||
|
||||
def unload(self, addr):
|
||||
"""Unload the module at ``addr``"""
|
||||
return winproxy.SymUnloadModule64(self.handle, addr)
|
||||
|
||||
|
||||
@staticmethod
|
||||
@ctypes.WINFUNCTYPE(gdef.BOOL, gdef.PCSTR, gdef.DWORD64, ctypes.py_object)
|
||||
def modules_aggregator(modname, modaddr, ctx):
|
||||
ctx.append(modaddr)
|
||||
return True
|
||||
|
||||
@property
|
||||
def modules(self):
|
||||
"""The list of loaded modules
|
||||
|
||||
:return: [:class:`SymbolModule`] -- A list of modules
|
||||
"""
|
||||
res = []
|
||||
windows.winproxy.SymEnumerateModules64(self.handle, self.modules_aggregator, res)
|
||||
return [self.get_module(addr) for addr in res]
|
||||
|
||||
|
||||
def get_module(self, base):
|
||||
modinfo = SymbolModule(self)
|
||||
modinfo.SizeOfStruct = ctypes.sizeof(modinfo)
|
||||
winproxy.SymGetModuleInfo64(self.handle, base, modinfo)
|
||||
return modinfo
|
||||
|
||||
def symbol_and_displacement_from_address(self, addr):
|
||||
displacement = gdef.DWORD64()
|
||||
max_len_size = 0x1000
|
||||
full_size = ctypes.sizeof(SymbolInfo) + (max_len_size - 1)
|
||||
buff = windows.utils.BUFFER(SymbolInfo)(size=full_size)
|
||||
sym = buff[0]
|
||||
sym.SizeOfStruct = ctypes.sizeof(SymbolInfo)
|
||||
sym.MaxNameLen = max_len_size
|
||||
winproxy.SymFromAddr(self.handle, addr, displacement, buff) # SymFromAddrW ?
|
||||
sym.resolver = self
|
||||
sym.displacement = displacement.value
|
||||
return sym
|
||||
|
||||
|
||||
def symbol_from_name(self, name):
|
||||
max_len_size = 0x1000
|
||||
full_size = ctypes.sizeof(SymbolInfo) + (max_len_size - 1)
|
||||
buff = windows.utils.BUFFER(SymbolInfo)(size=full_size)
|
||||
sym = buff[0]
|
||||
sym.SizeOfStruct = ctypes.sizeof(SymbolInfo)
|
||||
sym.MaxNameLen = max_len_size
|
||||
# Expect a-string
|
||||
name = windows.pycompat.raw_encode(name)
|
||||
windows.winproxy.SymFromName(self.handle, name, buff)
|
||||
sym.resolver = self
|
||||
sym.displacement = 0
|
||||
return sym
|
||||
|
||||
def resolve(self, name_or_addr):
|
||||
"""Resolve ``name_or_addr``.
|
||||
|
||||
If its an int -> Return the :class:`SymbolInfo` at the address.
|
||||
If its a string -> Return the :class:`SymbolInfo` corresponding to the symbol name
|
||||
|
||||
:return: :class:`SymbolInfo`
|
||||
|
||||
.. note::
|
||||
|
||||
``__getitem__`` is an alias for ``resolve()``
|
||||
|
||||
Exemple:
|
||||
|
||||
>>> sh = windows.debug.symbols.VirtualSymbolHandler()
|
||||
>>> mod = sh.load_file(r"c:\windows\system32\kernelbase.dll")
|
||||
>>> mod
|
||||
<SymbolModule name="kernelbase" type=SymPdb pdb="wkernelbase.pdb" addr=0x10000000>
|
||||
>>> sh.resolve("kernelbase!CreateFileInternal")
|
||||
<SymbolInfoA name="CreateFileInternal" addr=0x100f2120 tag=SymTagFunction>
|
||||
>>> sh[0x100f2042]
|
||||
<SymbolInfoA name="ReadFile" addr=0x100f1ee0 displacement=0x162 tag=SymTagFunction>
|
||||
>>> str(sh[0x100f2042])
|
||||
'kernelbase!ReadFile+0x162'
|
||||
"""
|
||||
# Only returns None if symbol is not Found ?
|
||||
if isinstance(name_or_addr, windows.pycompat.anybuff):
|
||||
return self.symbol_from_name(name_or_addr)
|
||||
try:
|
||||
return self.symbol_and_displacement_from_address(name_or_addr)
|
||||
except WindowsError as e:
|
||||
if e.winerror != gdef.ERROR_MOD_NOT_FOUND:
|
||||
raise
|
||||
# We could not resolve and address -> return None
|
||||
return None
|
||||
|
||||
__getitem__ = resolve
|
||||
"""Alias to resolve for simpler use"""
|
||||
|
||||
@staticmethod
|
||||
@ctypes.WINFUNCTYPE(gdef.BOOL, ctypes.POINTER(SymbolInfo), gdef.ULONG , ctypes.py_object)
|
||||
def simple_aggregator(info, size, ctx):
|
||||
sym = info[0]
|
||||
fullsize = sym.SizeOfStruct + sym.NameLen
|
||||
cpy = windows.utils.BUFFER(SymbolInfo)(size=fullsize)
|
||||
ctypes.memmove(cpy, info, fullsize)
|
||||
ctx.append(cpy[0])
|
||||
return True
|
||||
|
||||
def search(self, mask, mod=0, tag=0, options=gdef.SYMSEARCH_ALLITEMS, callback=None):
|
||||
"""Search the symbols matching ``mask`` (``Windbg`` like).
|
||||
|
||||
:return: [:class:`SymbolInfo`] -- A list of :class:`SymbolInfo`
|
||||
|
||||
>>> sh = windows.debug.symbols.VirtualSymbolHandler()
|
||||
>>> mod = sh.load_file(r"c:\windows\system32\kernelbase.dll")
|
||||
>>> sh.search("kernelbase!CreateFile*")
|
||||
[<SymbolInfoA name="CreateFileInternal" addr=0x100f2120 tag=SymTagFunction>,
|
||||
<SymbolInfoA name="CreateFileMoniker" addr=0x10117d80 tag=SymTagFunction>,
|
||||
<SymbolInfoA name="CreateFile2" addr=0x1011e690 tag=SymTagFunction>,
|
||||
...]
|
||||
"""
|
||||
res = []
|
||||
if callback is None:
|
||||
callback = self.simple_aggregator
|
||||
else:
|
||||
callback = ctypes.WINFUNCTYPE(gdef.BOOL, ctypes.POINTER(SymbolInfo), gdef.ULONG , ctypes.py_object)(callback)
|
||||
|
||||
addr = getattr(mod, "addr", mod) # Retrieve mod.addr, else us the value directly
|
||||
# Expect A-string
|
||||
mask = windows.pycompat.raw_encode(mask)
|
||||
windows.winproxy.SymSearch(self.handle, gdef.DWORD64(addr), 0, tag, mask, 0, callback, res, options)
|
||||
for sym in res:
|
||||
sym.resolver = self
|
||||
sym.displacement = 0
|
||||
return res
|
||||
|
||||
def get_symbols(self, addr, callback=None):
|
||||
res = []
|
||||
if callback is None:
|
||||
callback = self.simple_aggregator
|
||||
else:
|
||||
callback = ctypes.WINFUNCTYPE(gdef.BOOL, ctypes.POINTER(SymbolInfo), gdef.ULONG , ctypes.py_object)(callback)
|
||||
try:
|
||||
windows.winproxy.SymEnumSymbolsForAddr(self.handle, addr, callback, res)
|
||||
except WindowsError as e:
|
||||
if e.winerror == gdef.ERROR_MOD_NOT_FOUND:
|
||||
return []
|
||||
raise
|
||||
|
||||
for sym in res:
|
||||
sym.resolver = self
|
||||
sym.displacement = 0
|
||||
return res
|
||||
|
||||
# Type stuff
|
||||
def get_type(self, name, mod=0):
|
||||
max_len_size = 0x1000
|
||||
full_size = ctypes.sizeof(SymbolInfo) + (max_len_size - 1)
|
||||
buff = windows.utils.BUFFER(SymbolInfo)(size=full_size)
|
||||
buff[0].SizeOfStruct = ctypes.sizeof(SymbolInfo)
|
||||
buff[0].MaxNameLen = max_len_size
|
||||
windows.winproxy.SymGetTypeFromName(self.handle, mod, name, buff)
|
||||
return SymbolType.from_symbol_info(buff[0], resolver=self)
|
||||
|
||||
|
||||
# TODO: mets de l'huile pour w4kfu
|
||||
class StackWalker(object):
|
||||
def __init__(self, resolver, process=None, thread=None, context=None):
|
||||
self.resolver = resolver
|
||||
if process is None and thread is None:
|
||||
raise ValueError("At least a process or thread must be provided")
|
||||
if process is None:
|
||||
process = thread.owner
|
||||
self.process = process
|
||||
self.thread = thread
|
||||
self.context = context
|
||||
if windows.current_process.bitness == 32 and process.bitness == 64:
|
||||
raise NotImplementedError("StackWalking 64b does not seems to works from 32b process")
|
||||
|
||||
def _stack_frame_generator(self):
|
||||
ctx, machine = self._get_effective_context_and_machine()
|
||||
frame = self._setup_initial_frame_from_context(ctx, machine)
|
||||
thread_handle = self.thread.handle if self.thread else None
|
||||
while True:
|
||||
try:
|
||||
windows.winproxy.StackWalkEx(machine,
|
||||
# dbg.current_process.handle,
|
||||
self.resolver.handle,
|
||||
thread_handle,
|
||||
# 0,
|
||||
frame,
|
||||
ctypes.byref(ctx),
|
||||
None,
|
||||
winproxy.resolve(winproxy.SymFunctionTableAccess64),
|
||||
winproxy.resolve(winproxy.SymGetModuleBase64),
|
||||
None,
|
||||
0)
|
||||
except WindowsError as e:
|
||||
if not e.winerror:
|
||||
return # No_ERROR -> end of stack walking
|
||||
raise
|
||||
yield type(frame).from_buffer_copy(frame) # Make a copy ?
|
||||
|
||||
def __iter__(self):
|
||||
return self._stack_frame_generator()
|
||||
|
||||
# Autorise to force the retrieving of 32b stack when code is currently on 64b code ?
|
||||
def _get_effective_context_and_machine(self):
|
||||
ctx = self.context or self.thread.context
|
||||
if self.process.bitness == 32:
|
||||
# Process is 32b, so the context is inevitably x86
|
||||
return (ctx, gdef.IMAGE_FILE_MACHINE_I386)
|
||||
if windows.current_process.bitness == 32:
|
||||
# If we are 32b, we will only be able to handle x86 stack
|
||||
# ctx is obligatory a 32b one, as the case us32/target64 is handled
|
||||
# in __init__ with a NotImplementedError
|
||||
return (ctx, gdef.IMAGE_FILE_MACHINE_I386)
|
||||
if self.process.bitness == 64:
|
||||
# Process is 64b, so the context is inevitably x64
|
||||
return (ctx, gdef.IMAGE_FILE_MACHINE_AMD64)
|
||||
# Thing get a little more complicated here :)
|
||||
# We are a 64b process and target is 32b.
|
||||
# So we must find-out if we are in 32 or 64b world at the moment.
|
||||
# The context_syswow.SegCS give us the information
|
||||
# The context32.SegCs would be always 32
|
||||
ctxsyswow = dbg.current_thread.context_syswow
|
||||
if ctxsyswow.SegCs == gdef.CS_USER_32B:
|
||||
return (ctx, gdef.IMAGE_FILE_MACHINE_I386)
|
||||
return (ctxsyswow, gdef.IMAGE_FILE_MACHINE_AMD64)
|
||||
|
||||
def _setup_initial_frame_from_context(self, ctx, machine):
|
||||
frame = gdef.STACKFRAME_EX()
|
||||
frame.AddrPC.Mode = gdef.AddrModeFlat
|
||||
frame.AddrFrame.Mode = gdef.AddrModeFlat
|
||||
frame.AddrStack.Mode = gdef.AddrModeFlat
|
||||
frame.AddrPC.Offset = ctx.pc
|
||||
frame.AddrStack.Offset = ctx.sp
|
||||
if machine == gdef.IMAGE_FILE_MACHINE_I386:
|
||||
frame.AddrFrame.Offset = ctx.Ebp
|
||||
# Need RBP on 64b ?
|
||||
return frame
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class VirtualSymbolHandler(SymbolHandler):
|
||||
"""A SymbolHandler where its handle is not a valid process handle
|
||||
Allow to create/resolve symbol in a 'virtual' process
|
||||
But all API needing a real process handle will fail
|
||||
"""
|
||||
VIRTUAL_HANDLER_COUNTER = itertools.count(0x11223344)
|
||||
def __init__(self, search_path=None):
|
||||
handle = next(self.VIRTUAL_HANDLER_COUNTER)
|
||||
super(VirtualSymbolHandler, self).__init__(handle, search_path, False)
|
||||
|
||||
# The VirtualSymbolHandler is not based on an existing process
|
||||
# So load() in its simplest for should just take the path of the file to load
|
||||
load = SymbolHandler.load_file
|
||||
"""An alias for :func:`VirtualSymbolHandler.load_file`"""
|
||||
|
||||
def refresh(self):
|
||||
"""Do nothing for a :class:`VirtualSymbolHandler`"""
|
||||
return False
|
||||
|
||||
|
||||
class ProcessSymbolHandler(SymbolHandler):
|
||||
def __init__(self, process, search_path=None, invade_process=False):
|
||||
super(ProcessSymbolHandler, self).__init__(process.handle, search_path, invade_process)
|
||||
self.target = process
|
||||
|
||||
# The ProcessSymbolHandler is based on an existing process
|
||||
# So load() in its simplest form should be able to load the symbol for an existing
|
||||
# module that is already loaded
|
||||
# Question: should be able to load other module at other address ?
|
||||
def load(self, name):
|
||||
"""Load the :class:`SymbolModule` associated with the loaded module ``name`` (as found in the PEB)
|
||||
|
||||
:return: :class:`SymbolModule`
|
||||
|
||||
Exemple:
|
||||
|
||||
>>> sh = windows.debug.symbols.ProcessSymbolHandler(windows.test.pop_proc_64())
|
||||
<windows.debug.symbols.ProcessSymbolHandler object at 0x033A2C30>
|
||||
>>> sh
|
||||
<windows.debug.symbols.ProcessSymbolHandler object at 0x033A2C30>
|
||||
>>> sh.load("kernelbase.dll")
|
||||
<SymbolModule name="kernelbase" type=SymDeferred pdb="" addr=0x7ffb5b090000>
|
||||
>>> sh["kernelbase!CreateProcessA"]
|
||||
<SymbolInfoA name="CreateProcessA" start=0x7ffb5b2371f0 tag=SymTagPublicSymbol>
|
||||
"""
|
||||
mods = [x for x in self.target.peb.modules if x.name == name]
|
||||
if not mods:
|
||||
raise ValueError("Could not find module <{0}>".format(name))
|
||||
assert len(mods) == 1 # Load all if multiple match ?
|
||||
mod = mods[0]
|
||||
return self.load_module(addr=mod.baseaddr, path=mod.fullname)
|
||||
|
||||
def refresh(self):
|
||||
"""Update the list of loaded modules to match the modules present in the target process
|
||||
|
||||
.. note::
|
||||
This function only call `SymRefreshModuleList <https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symrefreshmodulelist>`_ for now.
|
||||
It seems that this function do not handle refreshing a 64b target from a 32b python
|
||||
|
||||
Also, on a 32b target from a 64b python it seems to only load symbols for the 64b modules (ntdll + syswow dll)
|
||||
|
||||
Exemple:
|
||||
|
||||
>>> sh = windows.debug.symbols.ProcessSymbolHandler(windows.test.pop_proc_64())
|
||||
>>> sh.modules
|
||||
[]
|
||||
>>> sh.refresh()
|
||||
44
|
||||
>>> sh.modules
|
||||
[<SymbolModule name="notepad" type=SymDeferred pdb="" addr=0x7ff772b80000>,
|
||||
<SymbolModule name="ntdll" type=SymDeferred pdb="" addr=0x7ffb5d860000>,
|
||||
<SymbolModule name="KERNEL32" type=SymDeferred pdb="" addr=0x7ffb5bb90000>,
|
||||
<SymbolModule name="KERNELBASE" type=SymDeferred pdb="" addr=0x7ffb5b090000>,
|
||||
...]
|
||||
"""
|
||||
return windows.winproxy.SymRefreshModuleList(self.handle)
|
||||
|
||||
|
||||
def stackwalk(self, ctx):
|
||||
pass
|
||||
|
||||
|
||||
class SymbolEngine(object):
|
||||
"""Represent the global symbol engine. Just a proxy to get/set global engine options
|
||||
|
||||
Its instance can be accessed using ``windows.debug.symbols.engine``
|
||||
|
||||
Exemple:
|
||||
|
||||
>>> windows.debug.symbols.engine.options
|
||||
6L
|
||||
>>> windows.debug.symbols.engine.options = gdef.SYMOPT_UNDNAME
|
||||
>>> windows.debug.symbols.engine.options
|
||||
2L
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
# use to now if we need to call the setup of options
|
||||
# At the first DbgHelp call
|
||||
self.options_already_setup = False
|
||||
|
||||
def set_options(self, options):
|
||||
self.options_already_setup = True
|
||||
return windows.winproxy.SymSetOptions(options)
|
||||
|
||||
def get_options(self):
|
||||
return windows.winproxy.SymGetOptions()
|
||||
|
||||
options = property(get_options, set_options)
|
||||
"""The options of the Symbol engine
|
||||
(`see options <https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetoptions#parameters>`_)
|
||||
|
||||
.. note::
|
||||
|
||||
Default options are: ``gdef.SYMOPT_DEFERRED_LOADS + gdef.SYMOPT_UNDNAME``
|
||||
"""
|
||||
|
||||
engine = SymbolEngine()
|
||||
"""The instance of the :class:`SymbolEngine`"""
|
||||
|
||||
|
||||
TST_TYPE_RES_TYPE = {
|
||||
gdef.TI_GET_SYMNAME: gdef.LPWSTR,
|
||||
gdef.TI_GET_LENGTH: gdef.ULONG64,
|
||||
gdef.TI_GET_ADDRESS: gdef.ULONG64,
|
||||
gdef.TI_GTIEX_REQS_VALID: gdef.ULONG64,
|
||||
gdef.TI_GET_SYMTAG: gdef.SymTagEnum,
|
||||
gdef.TI_GET_VALUE: windows.com.Variant,
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
from . import windef
|
||||
from . import winstructs
|
||||
|
||||
def bitness():
|
||||
"""Return 32 or 64"""
|
||||
import platform
|
||||
bits = platform.architecture()[0]
|
||||
return int(bits[:2])
|
||||
|
||||
# Use windows.current_process.bitness ? need to fix problem of this imported before the creation of windows.current_process
|
||||
if bitness() == 32:
|
||||
winstructs.CONTEXT = winstructs.CONTEXT32
|
||||
winstructs.PCONTEXT = winstructs.PCONTEXT32
|
||||
winstructs.LPCONTEXT = winstructs.LPCONTEXT32
|
||||
|
||||
winstructs.EXCEPTION_POINTERS = winstructs.EXCEPTION_POINTERS32
|
||||
winstructs.PEXCEPTION_POINTERS = winstructs.PEXCEPTION_POINTERS32
|
||||
|
||||
winstructs.SYSTEM_MODULE = winstructs.SYSTEM_MODULE32
|
||||
winstructs.SYSTEM_MODULE_INFORMATION = winstructs.SYSTEM_MODULE_INFORMATION32
|
||||
|
||||
winstructs.PALPC_PORT_ATTRIBUTES = winstructs.PALPC_PORT_ATTRIBUTES32
|
||||
winstructs.ALPC_PORT_ATTRIBUTES = winstructs.ALPC_PORT_ATTRIBUTES32
|
||||
|
||||
winstructs.PORT_MESSAGE = winstructs.PORT_MESSAGE32
|
||||
winstructs.PPORT_MESSAGE = winstructs.PPORT_MESSAGE32
|
||||
|
||||
# CFGMGR32
|
||||
winstructs.IRQ_RESOURCE = winstructs.IRQ_RESOURCE_32
|
||||
|
||||
# Socket
|
||||
windef.WSADATA = winstructs.WSADATA32
|
||||
windef.INVALID_SOCKET = windef.INVALID_SOCKET32
|
||||
|
||||
|
||||
else:
|
||||
winstructs.CONTEXT = winstructs.CONTEXT64
|
||||
winstructs.PCONTEXT = winstructs.PCONTEXT64
|
||||
winstructs.LPCONTEXT = winstructs.LPCONTEXT64
|
||||
|
||||
winstructs.EXCEPTION_POINTERS = winstructs.EXCEPTION_POINTERS64
|
||||
winstructs.PEXCEPTION_POINTERS = winstructs.PEXCEPTION_POINTERS64
|
||||
|
||||
winstructs.SYSTEM_MODULE = winstructs.SYSTEM_MODULE64
|
||||
winstructs.SYSTEM_MODULE_INFORMATION = winstructs.SYSTEM_MODULE_INFORMATION64
|
||||
|
||||
winstructs.PALPC_PORT_ATTRIBUTES = winstructs.PALPC_PORT_ATTRIBUTES64
|
||||
winstructs.ALPC_PORT_ATTRIBUTES = winstructs.ALPC_PORT_ATTRIBUTES64
|
||||
|
||||
winstructs.PORT_MESSAGE = winstructs.PORT_MESSAGE64
|
||||
winstructs.PPORT_MESSAGE = winstructs.PPORT_MESSAGE64
|
||||
|
||||
# CFGMGR32
|
||||
winstructs.IRQ_RESOURCE = winstructs.IRQ_RESOURCE_64
|
||||
|
||||
# Socket
|
||||
windef.WSADATA = winstructs.WSADATA64
|
||||
windef.INVALID_SOCKET = windef.INVALID_SOCKET64
|
||||
|
||||
from . import winfuncs
|
||||
from . import windef
|
||||
from . import interfaces
|
||||
|
||||
# Fuck it
|
||||
from .winstructs import *
|
||||
from .winfuncs import *
|
||||
from .windef import *
|
||||
from .interfaces import *
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
def pretty_print_ctypes_type(t):
|
||||
format = "{0}"
|
||||
if issubclass(t, ctypes.Array):
|
||||
format = "[{0}" + "* {0}]".format(t._length_)
|
||||
t = t._type_
|
||||
|
||||
if issubclass(t, ctypes._Pointer):
|
||||
format = format.format("Pointer({0})")
|
||||
t = t._type_
|
||||
|
||||
if issubclass(t, ctypes.Structure):
|
||||
return format.format(":class:`{0}`".format(t.__name__))
|
||||
return t
|
||||
|
||||
|
||||
def autodoc_ctypes_struct(struct):
|
||||
doc = ["fields:"]
|
||||
for name, type in struct._fields_:
|
||||
doc.append(" {0} -> {1}".format(name, pretty_print_ctypes_type(type)))
|
||||
|
||||
struct.__doc__ = "\n\n".join(doc)
|
||||
return struct
|
||||
@@ -0,0 +1,74 @@
|
||||
import sys
|
||||
|
||||
if sys.version_info.major >= 3:
|
||||
long = int
|
||||
|
||||
|
||||
class Flag(long):
|
||||
def __new__(cls, name, value):
|
||||
return super(Flag, cls).__new__(cls, value)
|
||||
|
||||
def __init__(self, name, value):
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return "{0}({1:#x})".format(self.name, self)
|
||||
|
||||
# Custom __str__ removed for multiple reason
|
||||
# Main one -> it breaks the json encoding of structure with flags :)
|
||||
# Moving to a new politic -> if people want the name in a string use {x!r}
|
||||
# The __str__ of security descriptor & guid will change soon as well :)
|
||||
|
||||
# __str__ = __repr__
|
||||
|
||||
# Fix pickling with protocol 2
|
||||
def __getnewargs__(self, *args):
|
||||
return self.name, long(self)
|
||||
|
||||
|
||||
class StrFlag(str):
|
||||
def __new__(cls, name, value):
|
||||
if isinstance(value, cls):
|
||||
return value
|
||||
return super(StrFlag, cls).__new__(cls, value)
|
||||
|
||||
def __init__(self, name, value):
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return "{0}({1})".format(self.name, str.__repr__(self))
|
||||
|
||||
# __str__ = __repr__
|
||||
|
||||
# Fix pickling with protocol 2
|
||||
def __getnewargs__(self, *args):
|
||||
return self.name, str.__str__(self)
|
||||
|
||||
|
||||
def make_flag(name, value):
|
||||
if isinstance(value, (int, long)):
|
||||
return Flag(name, value)
|
||||
return StrFlag(name, value)
|
||||
|
||||
|
||||
class FlagMapper(dict):
|
||||
def __init__(self, *values):
|
||||
self.update({x:x for x in values})
|
||||
|
||||
def __missing__(self, key):
|
||||
return key
|
||||
|
||||
|
||||
class FlagExatractor(object):
|
||||
def __init__(self, attr, values):
|
||||
self.attr = attr
|
||||
self.attrsize = attr.size * 8
|
||||
self.mapper = FlagMapper(*values)
|
||||
|
||||
def __get__(self, obj, type):
|
||||
if obj is None:
|
||||
return self
|
||||
# Retrieve the real value
|
||||
value = self.attr.__get__(obj)
|
||||
generator = (1 << i for i in range(self.attrsize))
|
||||
return [self.mapper[f] for f in generator if value & f]
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -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
|
||||
@@ -0,0 +1,438 @@
|
||||
import struct
|
||||
import ctypes
|
||||
import os
|
||||
import sys
|
||||
|
||||
import windows
|
||||
import windows.utils as utils
|
||||
import windows.generated_def as gdef
|
||||
|
||||
from .native_exec import simple_x86 as x86
|
||||
from .native_exec import simple_x64 as x64
|
||||
|
||||
from windows.generated_def import STATUS_THREAD_IS_TERMINATING
|
||||
from windows.native_exec.nativeutils import GetProcAddress64, GetProcAddress32
|
||||
from windows.dbgprint import dbgprint
|
||||
|
||||
|
||||
class InjectionFailedError(WindowsError):
|
||||
pass
|
||||
|
||||
def get_kernel32_dll_name():
|
||||
# Our injected shellcode search for 'kernel32.dll' with a strcmp
|
||||
# The BaseDllName of k32 might be 'KERNEL32.DLL' or 'kernel32.dll' on different system32
|
||||
# We base the name on our own loaded kernel32
|
||||
k32 = [m for m in windows.current_process.peb.modules if m.name == "kernel32.dll"]
|
||||
assert len(k32) == 1
|
||||
k32name = k32[0].BaseDllName.str
|
||||
return (k32name + "\x00").encode("utf-16-le")
|
||||
|
||||
def perform_manual_getproc_loadlib_32(target, dll_name):
|
||||
dll = get_kernel32_dll_name()
|
||||
api = "LoadLibraryA\x00"
|
||||
dll_to_load = dll_name + "\x00"
|
||||
|
||||
RemoteManualLoadLibray = x86.MultipleInstr()
|
||||
code = RemoteManualLoadLibray
|
||||
code += x86.Mov("ECX", x86.mem("[ESP + 4]"))
|
||||
code += x86.Push(x86.mem("[ECX + 4]"))
|
||||
code += x86.Push(x86.mem("[ECX]"))
|
||||
code += x86.Call(":FUNC_GETPROCADDRESS32")
|
||||
code += x86.Push(x86.mem("[ECX + 8]"))
|
||||
code += x86.Call("EAX") # LoadLibrary
|
||||
code += x86.Pop("ECX")
|
||||
code += x86.Pop("ECX")
|
||||
code += x86.Ret()
|
||||
|
||||
RemoteManualLoadLibray += GetProcAddress32
|
||||
|
||||
with target.allocated_memory(0x1000) as addr:
|
||||
addr2 = addr + len(dll)
|
||||
addr3 = addr2 + len(api)
|
||||
addr4 = addr3 + len(dll_to_load)
|
||||
|
||||
target.write_memory(addr, dll)
|
||||
target.write_memory(addr2, api)
|
||||
target.write_memory(addr3, dll_to_load)
|
||||
target.write_qword(addr4, addr)
|
||||
target.write_qword(addr4 + 4, addr2)
|
||||
target.write_qword(addr4 + 0x8, addr3)
|
||||
|
||||
t = target.execute(RemoteManualLoadLibray.get_code(), addr4)
|
||||
t.wait()
|
||||
if not t.exit_code:
|
||||
raise InjectionFailedError("Injection of <{0}> failed".format(dll_name))
|
||||
return True
|
||||
|
||||
def perform_manual_getproc_loadlib_64(target, dll_name):
|
||||
dll = get_kernel32_dll_name()
|
||||
api = "LoadLibraryA\x00"
|
||||
dll_to_load = dll_name + "\x00"
|
||||
|
||||
RemoteManualLoadLibray = x64.MultipleInstr()
|
||||
code = RemoteManualLoadLibray
|
||||
code += x64.Mov("R15", "RCX")
|
||||
code += x64.Mov("RCX", x64.mem("[R15 + 0]"))
|
||||
code += x64.Mov("RDX", x64.mem("[R15 + 8]"))
|
||||
code += x64.Call(":FUNC_GETPROCADDRESS64")
|
||||
code += x64.Mov("RCX", x64.mem("[R15 + 0x10]"))
|
||||
code += x64.Push("RCX")
|
||||
code += x64.Push("RCX")
|
||||
code += x64.Push("RCX")
|
||||
code += x64.Call("RAX") # LoadLibrary
|
||||
code += x64.Pop("RCX")
|
||||
code += x64.Pop("RCX")
|
||||
code += x64.Pop("RCX")
|
||||
code += x64.Ret()
|
||||
|
||||
RemoteManualLoadLibray += GetProcAddress64
|
||||
|
||||
with target.allocated_memory(0x1000) as addr:
|
||||
addr2 = addr + len(dll)
|
||||
addr3 = addr2 + len(api)
|
||||
addr4 = addr3 + len(dll_to_load)
|
||||
|
||||
target.write_memory(addr, dll)
|
||||
target.write_memory(addr2, api)
|
||||
target.write_memory(addr3, dll_to_load)
|
||||
target.write_qword(addr4, addr)
|
||||
target.write_qword(addr4 + 8, addr2)
|
||||
target.write_qword(addr4 + 0x10, addr3)
|
||||
|
||||
t = target.execute(RemoteManualLoadLibray.get_code(), addr4)
|
||||
t.wait()
|
||||
if not t.exit_code:
|
||||
raise InjectionFailedError("Injection of <{0}> failed".format(dll_name))
|
||||
return True
|
||||
|
||||
def generate_simple_LoadLibraryW_64(load_libraryW, remote_store):
|
||||
code = RemoteLoadLibrayStub = x64.MultipleInstr()
|
||||
code += x64.Mov("RAX", load_libraryW)
|
||||
code += (x64.Push("RDI") * 5) # Prepare stack
|
||||
code += x64.Call("RAX")
|
||||
code += (x64.Pop("RDI") * 5) # Clean stack
|
||||
code += x64.Mov(x64.deref(remote_store), "RAX")
|
||||
code += x64.Ret()
|
||||
return RemoteLoadLibrayStub.get_code()
|
||||
|
||||
|
||||
|
||||
def perform_manual_getproc_loadlib(target, *args, **kwargs):
|
||||
if target.bitness == 32:
|
||||
return perform_manual_getproc_loadlib_32(target, *args, **kwargs)
|
||||
return perform_manual_getproc_loadlib_64(target, *args, **kwargs)
|
||||
|
||||
|
||||
def load_dll_in_remote_process(target, dll_path):
|
||||
rpeb = target.peb
|
||||
if rpeb.Ldr:
|
||||
# LDR est parcourable, ca va etre deja plus simple..
|
||||
modules = rpeb.modules
|
||||
if any(mod.fullname.lower() == dll_path.lower() for mod in modules):
|
||||
# DLL already loaded
|
||||
dbgprint("DLL already present in target", "DLLINJECT")
|
||||
return False
|
||||
k32 = [mod for mod in modules if mod.name.lower() == "kernel32.dll"]
|
||||
if k32:
|
||||
# We have kernel32 \o/
|
||||
k32 = k32[0]
|
||||
try:
|
||||
load_libraryW = k32.pe.exports["LoadLibraryW"]
|
||||
except KeyError:
|
||||
raise ValueError("Kernel32 have no export <LoadLibraryA> (wtf)")
|
||||
|
||||
with target.allocated_memory(0x1000) as addr:
|
||||
if target.bitness == 32:
|
||||
target.write_memory(addr, (dll_path + "\x00").encode('utf-16le'))
|
||||
t = target.create_thread(load_libraryW, addr)
|
||||
t.wait()
|
||||
module_baseaddr = t.exit_code
|
||||
else:
|
||||
# For 64b target we need a special stub as the return value of
|
||||
# load_libraryW does not fit in t.exit_code (DWORD)
|
||||
retval_addr = addr
|
||||
target.write_ptr(retval_addr, 0)
|
||||
addr += ctypes.sizeof(ctypes.c_ulonglong)
|
||||
full_dll_name = (dll_path + "\x00").encode('utf-16le')
|
||||
target.write_memory(addr, full_dll_name)
|
||||
param_addr = addr
|
||||
addr += len(full_dll_name)
|
||||
shellcode_addr = addr
|
||||
shellcode = generate_simple_LoadLibraryW_64(load_libraryW, retval_addr)
|
||||
target.write_memory(shellcode_addr, shellcode)
|
||||
t = target.create_thread(shellcode_addr, param_addr)
|
||||
t.wait()
|
||||
module_baseaddr = target.read_ptr(retval_addr)
|
||||
|
||||
if not module_baseaddr:
|
||||
raise InjectionFailedError(u"Injection of <{0}> failed".format(dll_path))
|
||||
dbgprint("DLL Injected via LoadLibray", "DLLINJECT")
|
||||
# Cannot return the full return value of load_libraryW in 64b target.. (exit_code is a DWORD)
|
||||
return module_baseaddr
|
||||
# Hardcore mode
|
||||
# We don't have k32 or PEB->Ldr
|
||||
# Go inject a GetProcAddress(LoadLib) + LoadLib shellcode :D
|
||||
dbgprint("DLL Via manual getproc / loadlib", "DLLINJECT")
|
||||
if target.bitness == 32:
|
||||
return perform_manual_getproc_loadlib_32(target, dll_path)
|
||||
return perform_manual_getproc_loadlib_64(target, dll_path)
|
||||
|
||||
python_function_32_bits = {}
|
||||
|
||||
def generate_python_exec_shellcode_32(target, PyDll):
|
||||
pymodule = [mod for mod in target.peb.modules if mod.name == PyDll][0]
|
||||
base = pymodule.baseaddr
|
||||
if not python_function_32_bits:
|
||||
Py_exports = pymodule.pe.exports
|
||||
python_function_32_bits["PyEval_InitThreads"] = Py_exports["PyEval_InitThreads"] - base
|
||||
python_function_32_bits["Py_IsInitialized"] = Py_exports["Py_IsInitialized"] - base
|
||||
python_function_32_bits["PyGILState_Release"] = Py_exports["PyGILState_Release"] - base
|
||||
python_function_32_bits["PyGILState_Ensure"] = Py_exports["PyGILState_Ensure"] - base
|
||||
python_function_32_bits["PyEval_SaveThread"] = Py_exports["PyEval_SaveThread"] - base
|
||||
python_function_32_bits["Py_Initialize"] = Py_exports["Py_Initialize"] - base
|
||||
python_function_32_bits["PyRun_SimpleString"] = Py_exports["PyRun_SimpleString"] - base
|
||||
Py_exports = python_function_32_bits
|
||||
PyEval_InitThreads = Py_exports["PyEval_InitThreads"] + base
|
||||
Py_IsInitialized = Py_exports["Py_IsInitialized"] + base
|
||||
PyGILState_Release = Py_exports["PyGILState_Release"] + base
|
||||
PyGILState_Ensure = Py_exports["PyGILState_Ensure"] + base
|
||||
PyEval_SaveThread = Py_exports["PyEval_SaveThread"] + base
|
||||
Py_Initialize = Py_exports["Py_Initialize"] + base
|
||||
PyRun_SimpleString = Py_exports["PyRun_SimpleString"] + base
|
||||
|
||||
code = x86.MultipleInstr()
|
||||
code += x86.Mov('EAX', Py_IsInitialized)
|
||||
code += x86.Call('EAX')
|
||||
code += x86.Mov("EDI", "EAX")
|
||||
code += x86.Cmp("EAX", 0)
|
||||
code += x86.Jnz(":DO_ENSURE")
|
||||
code += x86.Mov('EAX', Py_Initialize)
|
||||
code += x86.Call('EAX')
|
||||
# https://docs.python.org/3/c-api/init.html#c.PyEval_InitThreads
|
||||
code += x86.Mov('EAX', PyEval_InitThreads)
|
||||
code += x86.Call('EAX')
|
||||
code += x86.Label(":DO_ENSURE")
|
||||
code += x86.Mov('EAX', PyGILState_Ensure)
|
||||
code += x86.Call('EAX')
|
||||
code += x86.Push('EAX')
|
||||
# Get the string to execute from parameters
|
||||
code += x86.Mov("EAX", x86.mem("[ESP + 0x8]"))
|
||||
code += x86.Push('EAX')
|
||||
code += x86.Mov('EAX', PyRun_SimpleString)
|
||||
code += x86.Call('EAX')
|
||||
code += x86.Mov("ESI", "EAX")
|
||||
code += x86.Mov('EAX', PyGILState_Release)
|
||||
code += x86.Call('EAX')
|
||||
code += x86.Pop('EAX')
|
||||
code += x86.Cmp("EDI", 0)
|
||||
code += x86.Jnz(":RETURN")
|
||||
# If PyEval_InitThreads was called (init done in this thread)
|
||||
# We must release the GIL
|
||||
code += x86.Mov('EAX', PyEval_SaveThread)
|
||||
code += x86.Call('EAX')
|
||||
code += x86.Label(":RETURN")
|
||||
code += x86.Mov("EAX", "ESI")
|
||||
code += x86.Pop("EDI")
|
||||
code += x86.Ret()
|
||||
return code.get_code()
|
||||
|
||||
|
||||
python_function_64_bits = {}
|
||||
|
||||
def generate_python_exec_shellcode_64(target, PyDll):
|
||||
pymodule = [mod for mod in target.peb.modules if mod.name == PyDll][0]
|
||||
base = pymodule.baseaddr
|
||||
if not python_function_64_bits:
|
||||
Py_exports = pymodule.pe.exports
|
||||
python_function_64_bits["PyEval_InitThreads"] = Py_exports["PyEval_InitThreads"] - base
|
||||
python_function_64_bits["Py_IsInitialized"] = Py_exports["Py_IsInitialized"] - base
|
||||
python_function_64_bits["PyGILState_Release"] = Py_exports["PyGILState_Release"] - base
|
||||
python_function_64_bits["PyGILState_Ensure"] = Py_exports["PyGILState_Ensure"] - base
|
||||
python_function_64_bits["PyEval_SaveThread"] = Py_exports["PyEval_SaveThread"] - base
|
||||
python_function_64_bits["Py_Initialize"] = Py_exports["Py_Initialize"] - base
|
||||
python_function_64_bits["PyRun_SimpleString"] = Py_exports["PyRun_SimpleString"] - base
|
||||
Py_exports = python_function_64_bits
|
||||
PyEval_InitThreads = Py_exports["PyEval_InitThreads"] + base
|
||||
Py_IsInitialized = Py_exports["Py_IsInitialized"] + base
|
||||
PyGILState_Release = Py_exports["PyGILState_Release"] + base
|
||||
PyGILState_Ensure = Py_exports["PyGILState_Ensure"] + base
|
||||
PyEval_SaveThread = Py_exports["PyEval_SaveThread"] + base
|
||||
Py_Initialize = Py_exports["Py_Initialize"] + base
|
||||
PyRun_SimpleString = Py_exports["PyRun_SimpleString"] + base
|
||||
|
||||
Reserve_space_for_call = x64.MultipleInstr([x64.Push('RDI')] * 4)
|
||||
Clean_space_for_call = x64.MultipleInstr([x64.Pop('RDI')] * 4)
|
||||
code = x64.MultipleInstr()
|
||||
# Do stack alignement
|
||||
code += x64.Push('RCX')
|
||||
code += Reserve_space_for_call
|
||||
code += x64.Mov('RAX', Py_IsInitialized)
|
||||
code += x64.Call('RAX')
|
||||
code += x64.Mov("RDI", "RAX")
|
||||
code += x64.Cmp("RAX", 0)
|
||||
code += x64.Jnz(":DO_ENSURE")
|
||||
code += x64.Mov('RAX', Py_Initialize)
|
||||
code += x64.Call('RAX')
|
||||
# https://docs.python.org/3/c-api/init.html#c.PyEval_InitThreads
|
||||
code += x64.Mov('RAX', PyEval_InitThreads)
|
||||
code += x64.Call('RAX')
|
||||
code += x64.Label(":DO_ENSURE")
|
||||
code += x64.Mov('RAX', PyGILState_Ensure)
|
||||
code += x64.Call('RAX')
|
||||
code += x64.Mov('R15', 'RAX')
|
||||
code += x64.Mov("RCX", x64.mem("[RSP + 0x20]"))
|
||||
code += x64.Mov('RAX', PyRun_SimpleString)
|
||||
code += x64.Call('RAX')
|
||||
code += x64.Mov('RCX', 'R15')
|
||||
code += x64.Mov('R15', 'RAX')
|
||||
code += x64.Mov('RAX', PyGILState_Release)
|
||||
code += x64.Call('RAX')
|
||||
code += x64.Cmp("RDI", 0)
|
||||
code += x64.Jnz(":RETURN")
|
||||
# If PyEval_InitThreads was called (init done in this thread)
|
||||
# We must release the GIL
|
||||
code += x64.Mov('RAX', PyEval_SaveThread)
|
||||
code += x64.Call('RAX')
|
||||
code += x64.Label(":RETURN")
|
||||
code += Clean_space_for_call
|
||||
# Remove stack alignement
|
||||
code += x64.Pop('RCX')
|
||||
code += x64.Mov("RAX", "R15")
|
||||
code += x64.Ret()
|
||||
return code.get_code()
|
||||
|
||||
|
||||
def inject_python_command(target, code_injected, PYDLL):
|
||||
"""Postulate: PYDLL is already loaded in target process"""
|
||||
PYCODE = code_injected + "\x00"
|
||||
# TODO: free this (how ? when ?)
|
||||
remote_python_code_addr = target.virtual_alloc(len(PYCODE))
|
||||
target.write_memory(remote_python_code_addr, PYCODE)
|
||||
shellcode_addr = getattr(target, "_execute_python_shellcode", None)
|
||||
if shellcode_addr is not None:
|
||||
return shellcode_addr, remote_python_code_addr
|
||||
if target.bitness == 32:
|
||||
shellcode_generator = generate_python_exec_shellcode_32
|
||||
else:
|
||||
shellcode_generator = generate_python_exec_shellcode_64
|
||||
|
||||
shellcode = shellcode_generator(target, PYDLL)
|
||||
shellcode_addr = target.virtual_alloc(len(shellcode))
|
||||
target.write_memory(shellcode_addr, shellcode)
|
||||
target._execute_python_shellcode = shellcode_addr
|
||||
return shellcode_addr, remote_python_code_addr
|
||||
|
||||
def get_dll_name_from_python_version():
|
||||
version = sys.version_info
|
||||
return "python{v.major}{v.minor}.dll".format(v=version)
|
||||
|
||||
def find_python_dll_to_inject(target_bitness):
|
||||
pydll_name = get_dll_name_from_python_version()
|
||||
if windows.current_process.bitness == target_bitness:
|
||||
# We can inject our own DLL
|
||||
pymodules = [m for m in windows.current_process.peb.modules if m.name == pydll_name]
|
||||
assert len(pymodules) == 1
|
||||
return pymodules[0].fullname
|
||||
# Okay, so we need to find the DLL to inject.
|
||||
# Problem is, for py3 the DLL is not un system32, so we need for search for it
|
||||
# Simpler solution is the registry
|
||||
# Add a check using %PATH% ?
|
||||
assert windows.system.bitness == 64, "How can we have process of different bitness on 32b system ?"
|
||||
if sys.version_info.major == 2:
|
||||
# Python2 DLL are located in system32/syswow64
|
||||
# We know that we are looking to DLL of the other bitness
|
||||
if windows.current_process.bitness == 32:
|
||||
# We need to check that the real system32\pythonXX.dll exists
|
||||
systempath = "sysnative"
|
||||
else:
|
||||
# We need to check that the wow64 system32\pythonXX.dll exists
|
||||
systempath = "syswow64"
|
||||
if os.path.exists(os.path.join(os.environ["windir"], systempath, pydll_name)):
|
||||
# In any way (32b ou 64b) the target process will load system32\pydll
|
||||
# If the target is 32b the wow64 layer will translate it
|
||||
return os.path.join(os.environ["windir"], "system32", pydll_name)
|
||||
# If not found this way -> may mean we have a install only for a user, give registry a try
|
||||
|
||||
# Python 3 dll must be located using the registry
|
||||
for base_key in "HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER":
|
||||
# Open the registry in 64b view regardless of current process bitness
|
||||
regbase = windows.system.registry(base_key, gdef.KEY_WOW64_64KEY | gdef.KEY_READ)
|
||||
# we cannot use sys.winver as we are looking for the OTHER version
|
||||
# But from Python <PCbuild/python.props> it looks like format is
|
||||
# {Major}.{Minor}{-32}(for 32b build)
|
||||
# This code do not handle -test version
|
||||
winver_base = sys.winver[:3] # major-minor
|
||||
if target_bitness == 64:
|
||||
pyinstallkeys = [regbase(r"SOFTWARE\Python\PythonCore")(winver_base)]
|
||||
else:
|
||||
pyinstallkeys = [regbase(r"SOFTWARE\Python\PythonCore")(winver_base + "-32"),
|
||||
regbase(r"SOFTWARE\WOW6432Node\Python\PythonCore")(winver_base + "-32")]
|
||||
for pyinstallkey in pyinstallkeys:
|
||||
if not pyinstallkey.exists:
|
||||
continue
|
||||
try:
|
||||
pyinstallpath = pyinstallkey("InstallPath")[""].value
|
||||
final_path = os.path.join(pyinstallpath, pydll_name)
|
||||
assert os.path.exists(final_path), "Could not find <{0}> pydll referenced from registry".format(final_path)
|
||||
return final_path
|
||||
except WindowsError as e:
|
||||
if e.winerror != gdef.ERROR_FILE_NOT_FOUND:
|
||||
raise
|
||||
# Not found
|
||||
continue
|
||||
# Could not find a valid installation
|
||||
raise ValueError("Could not find a path for python-dll <{0}>({1}bits)".format(sys.winver, target_bitness))
|
||||
|
||||
|
||||
|
||||
def execute_python_code(process, code):
|
||||
# Cache the value ?
|
||||
py_dll_name = get_dll_name_from_python_version()
|
||||
pydll_path = find_python_dll_to_inject(process.bitness)
|
||||
if sys.version_info.major == 3:
|
||||
# FOr py3, we may have a per-user install.
|
||||
# Meaning that the vcruntime140.dll will not be in the injected process path
|
||||
# Find it & load-it as well, it should be in the same directory as pythonxx.dll
|
||||
vc_runtime_dll = os.path.join(os.path.dirname(pydll_path), "vcruntime140.dll")
|
||||
load_dll_in_remote_process(process, vc_runtime_dll)
|
||||
# Try to inject the vcrunt
|
||||
load_dll_in_remote_process(process, pydll_path)
|
||||
shellcode, pythoncode = inject_python_command(process, code, py_dll_name)
|
||||
t = process.create_thread(shellcode, pythoncode)
|
||||
return t
|
||||
|
||||
|
||||
retrieve_exc = r"""
|
||||
import traceback
|
||||
import sys
|
||||
addr = {0}
|
||||
txt = "".join(traceback.format_exception(sys.last_type, sys.last_value, sys.last_traceback))
|
||||
import ctypes
|
||||
|
||||
size = ctypes.c_uint.from_address(addr)
|
||||
size.value = len(txt)
|
||||
buff = (ctypes.c_char * len(txt)).from_address(addr + ctypes.sizeof(ctypes.c_uint))
|
||||
buff[:] = txt.encode()
|
||||
"""
|
||||
|
||||
def retrieve_last_exception_data(process):
|
||||
with process.allocated_memory(0x1000) as mem:
|
||||
execute_python_code(process, retrieve_exc.format(mem)).wait()
|
||||
size = struct.unpack("<I", process.read_memory(mem, ctypes.sizeof(ctypes.c_uint)))[0]
|
||||
data = process.read_memory(mem + ctypes.sizeof(ctypes.c_uint), size)
|
||||
return data
|
||||
|
||||
class RemotePythonError(Exception):
|
||||
pass
|
||||
|
||||
def safe_execute_python(process, code):
|
||||
t = execute_python_code(process, code)
|
||||
t.wait() # Wait terminaison of the thread
|
||||
if t.exit_code == 0:
|
||||
return True
|
||||
if t.exit_code == STATUS_THREAD_IS_TERMINATING or process.is_exit:
|
||||
raise WindowsError("{0} died during execution of python command".format(process))
|
||||
if t.exit_code != 0xffffffff:
|
||||
raise ValueError("Unknown exit code {0}".format(hex(t.exit_code)))
|
||||
data = retrieve_last_exception_data(process)
|
||||
raise RemotePythonError(data)
|
||||
@@ -0,0 +1,3 @@
|
||||
from .native_function import create_function
|
||||
|
||||
__all__ = ["create_function"]
|
||||
@@ -0,0 +1,159 @@
|
||||
import ctypes
|
||||
import struct
|
||||
|
||||
import native_function
|
||||
import simple_x86 as x86
|
||||
import simple_x64 as x64
|
||||
from windows.generated_def.winstructs import *
|
||||
|
||||
|
||||
def _bitness():
|
||||
"""Returns 32 or 64"""
|
||||
import platform
|
||||
bits = platform.architecture()[0]
|
||||
return int(bits[:2])
|
||||
|
||||
|
||||
class X86CpuidResult(ctypes.Structure):
|
||||
"""Raw result of the CPUID instruction"""
|
||||
_fields_ = [("EAX", DWORD),
|
||||
("EBX", DWORD),
|
||||
("ECX", DWORD),
|
||||
("EDX", DWORD)]
|
||||
fields = [f[0] for f in _fields_]
|
||||
"""Fields of the Structure"""
|
||||
|
||||
class X64CpuidResult(ctypes.Structure):
|
||||
_fields_ = [("RAX", ULONG64),
|
||||
("RBX", ULONG64),
|
||||
("RCX", ULONG64),
|
||||
("RDX", ULONG64)]
|
||||
|
||||
|
||||
class X86IntelCpuidFamilly(ctypes.Structure):
|
||||
_fields_ = [("SteppingID", DWORD, 4),
|
||||
("ModelID", DWORD, 4),
|
||||
("FamilyID", DWORD, 4),
|
||||
("ProcessorType", DWORD, 2),
|
||||
("Reserved2", DWORD, 2),
|
||||
("ExtendedModel", DWORD, 4),
|
||||
("ExtendedFamily", DWORD, 8),
|
||||
("Reserved", DWORD, 2)]
|
||||
fields = [f[0] for f in _fields_]
|
||||
"""Fields of the Structure"""
|
||||
|
||||
|
||||
class X86AmdCpuidFamilly(ctypes.Structure):
|
||||
_fields_ = [("SteppingID", DWORD, 4),
|
||||
("ModelID", DWORD, 4),
|
||||
("FamilyID", DWORD, 4),
|
||||
("Reserved2", DWORD, 4),
|
||||
("ExtendedModel", DWORD, 4),
|
||||
("ExtendedFamily", DWORD, 8),
|
||||
("Reserved", DWORD, 2)]
|
||||
fields = [f[0] for f in _fields_]
|
||||
"""Fields of the Structure"""
|
||||
|
||||
cpuid32_code = x86.MultipleInstr()
|
||||
cpuid32_code += x86.Push('EDI')
|
||||
cpuid32_code += x86.Mov('EAX', x86.mem('[ESP + 0x8]'))
|
||||
cpuid32_code += x86.Mov('EDI', x86.mem('[ESP + 0xc]'))
|
||||
cpuid32_code += x86.Cpuid()
|
||||
cpuid32_code += x86.Mov(x86.mem('[EDI + 0x0]'), 'EAX')
|
||||
cpuid32_code += x86.Mov(x86.mem('[EDI + 0x4]'), 'EBX')
|
||||
cpuid32_code += x86.Mov(x86.mem('[EDI + 0x8]'), 'ECX')
|
||||
cpuid32_code += x86.Mov(x86.mem('[EDI + 0xc]'), 'EDX')
|
||||
cpuid32_code += x86.Pop('EDI')
|
||||
cpuid32_code += x86.Ret()
|
||||
do_cpuid32 = native_function.create_function(cpuid32_code.get_code(), [DWORD, DWORD, PVOID])
|
||||
|
||||
|
||||
cpuid64_code = x64.MultipleInstr()
|
||||
cpuid64_code += x64.Mov('RAX', 'RCX')
|
||||
cpuid64_code += x64.Mov('R10', 'RDX')
|
||||
cpuid64_code += x64.Cpuid()
|
||||
# For now assembler cannot do 32bits register in x64
|
||||
cpuid64_code += x64.Mov(x64.mem('[R10 + 0x00]'), 'RAX')
|
||||
cpuid64_code += x64.Mov(x64.mem('[R10 + 0x08]'), 'RBX')
|
||||
cpuid64_code += x64.Mov(x64.mem('[R10 + 0x10]'), 'RCX')
|
||||
cpuid64_code += x64.Mov(x64.mem('[R10 + 0x18]'), 'RDX')
|
||||
cpuid64_code += x64.Ret()
|
||||
do_cpuid64 = native_function.create_function(cpuid64_code.get_code(), [DWORD, DWORD, PVOID])
|
||||
|
||||
|
||||
def x86_cpuid(req):
|
||||
"""Performs a CPUID in 32bits mode
|
||||
|
||||
:rtype: :class:`X86CpuidResult`
|
||||
"""
|
||||
cpuid_res = X86CpuidResult()
|
||||
do_cpuid32(req, ctypes.addressof(cpuid_res))
|
||||
return cpuid_res
|
||||
|
||||
|
||||
def x64_cpuid(req):
|
||||
"""Performs a CPUID in 64bits mode
|
||||
|
||||
:rtype: :class:`X86CpuidResult`
|
||||
"""
|
||||
cpuid_res = X64CpuidResult()
|
||||
do_cpuid64(req, ctypes.addressof(cpuid_res))
|
||||
# For now assembler cannot do 32bits register in x64
|
||||
return X86CpuidResult(cpuid_res.RAX, cpuid_res.RBX, cpuid_res.RCX, cpuid_res.RDX)
|
||||
|
||||
|
||||
if _bitness() == 32:
|
||||
_do_cpuid = x86_cpuid
|
||||
else:
|
||||
_do_cpuid = x64_cpuid
|
||||
|
||||
def do_cpuid(req):
|
||||
"""Performs a CPUID for the current process bitness
|
||||
|
||||
:rtype: :class:`X86CpuidResult`
|
||||
"""
|
||||
return _do_cpuid(req)
|
||||
|
||||
|
||||
def get_vendor_id():
|
||||
"""Extracts the VendorId string from CPUID
|
||||
|
||||
:rtype: :class:`str`
|
||||
"""
|
||||
cpuid_res = do_cpuid(0)
|
||||
return struct.pack("<III", cpuid_res.EBX, cpuid_res.EDX, cpuid_res.ECX)
|
||||
|
||||
|
||||
# platform.processor() could do the trick
|
||||
def is_intel_proc():
|
||||
"""get_vendor_id() == 'GenuineIntel'"""
|
||||
return get_vendor_id() == "GenuineIntel"
|
||||
|
||||
|
||||
def is_amd_proc():
|
||||
"""get_vendor_id() == 'AuthenticAMD'"""
|
||||
return get_vendor_id() == "AuthenticAMD"
|
||||
|
||||
|
||||
def get_proc_family_model():
|
||||
"""Extracts the family and model based on vendorId
|
||||
|
||||
:rtype: (ComputedFamily, ComputedModel)
|
||||
"""
|
||||
cpuid_res = do_cpuid(1)
|
||||
if is_intel_proc():
|
||||
format = X86IntelCpuidFamilly
|
||||
elif is_amd_proc():
|
||||
format = X86AmdCpuidFamilly
|
||||
else:
|
||||
raise NotImplementedError("Cannot get familly information of proc <{0}>".format(get_vendor_id()))
|
||||
infos = format.from_buffer_copy(struct.pack("<I", cpuid_res.EAX))
|
||||
if infos.FamilyID == 0x6 or infos.FamilyID == 0x0F:
|
||||
ComputedModel = infos.ModelID + (infos.ExtendedModel << 4)
|
||||
else:
|
||||
ComputedModel = infos.ModelID
|
||||
if infos.FamilyID == 0x0F:
|
||||
ComputedFamily = infos.FamilyID + infos.ExtendedFamily
|
||||
else:
|
||||
ComputedFamily = infos.FamilyID
|
||||
return ComputedFamily, ComputedModel
|
||||
@@ -0,0 +1,87 @@
|
||||
import ctypes
|
||||
import mmap
|
||||
import platform
|
||||
import sys
|
||||
|
||||
import windows
|
||||
import windows.winproxy
|
||||
import windows.generated_def as gdef
|
||||
|
||||
from . import simple_x86 as x86
|
||||
from . import simple_x64 as x64
|
||||
|
||||
|
||||
class CustomAllocator(object):
|
||||
int_size = {'32bit': 4, '64bit': 8}
|
||||
|
||||
def __init__(self):
|
||||
self.maps = []
|
||||
self.cur_offset = 0
|
||||
self.cur_page_size = 0 # Force get_new_page on first request
|
||||
self.names = []
|
||||
|
||||
@classmethod
|
||||
def get_int_size(cls):
|
||||
bits = platform.architecture()[0]
|
||||
if bits not in cls.int_size:
|
||||
raise ValueError("Unknow platform bits <{0}>".format(bits))
|
||||
return cls.int_size[bits]
|
||||
|
||||
def get_new_page(self, size):
|
||||
addr = windows.winproxy.VirtualAlloc(0, size, 0x1000, gdef.PAGE_EXECUTE_READWRITE)
|
||||
mymap = (ctypes.c_char * size).from_address(addr)
|
||||
mymap.addr = addr
|
||||
self.maps.append(mymap)
|
||||
self.cur_offset = 0
|
||||
self.cur_page_size = size
|
||||
|
||||
def reserve_size(self, size):
|
||||
if size + self.cur_offset > self.cur_page_size:
|
||||
self.get_new_page((size + 0x1000) & ~0xfff)
|
||||
addr = self.maps[-1].addr + self.cur_offset
|
||||
self.cur_offset += size
|
||||
return addr
|
||||
|
||||
def reserve_int(self, nb_int=1):
|
||||
int_size = self.get_int_size()
|
||||
return self.reserve_size(int_size * nb_int)
|
||||
|
||||
def write_code(self, code):
|
||||
size = len(code)
|
||||
if size + self.cur_offset > self.cur_page_size:
|
||||
self.get_new_page((size + 0x1000) & ~0xfff)
|
||||
self.maps[-1][self.cur_offset: self.cur_offset + size] = code
|
||||
addr = self.maps[-1].addr + self.cur_offset
|
||||
self.cur_offset += size
|
||||
return addr
|
||||
|
||||
def close(self):
|
||||
maps = self.maps
|
||||
self.maps = []
|
||||
self.cur_offset = 0
|
||||
self.cur_page_size = 0
|
||||
if getattr(sys, "path", None) is None:
|
||||
# Path is None -> Python shutdown
|
||||
return
|
||||
for mymap in maps:
|
||||
windows.winproxy.VirtualFree(mymap.addr, dwFreeType=gdef.MEM_RELEASE)
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
allocator = CustomAllocator()
|
||||
|
||||
|
||||
def create_function(code, types, calling_convention=ctypes.CFUNCTYPE):
|
||||
"""Create a python function that call raw machine code
|
||||
|
||||
:param str code: Raw machine code that will be called
|
||||
:param list types: Return type and parameters type (see :mod:`ctypes`)
|
||||
:return: the created function
|
||||
:rtype: function
|
||||
"""
|
||||
func_type = calling_convention(*types)
|
||||
addr = allocator.write_code(code)
|
||||
res = func_type(addr)
|
||||
res.code_addr = addr
|
||||
return res
|
||||
@@ -0,0 +1,280 @@
|
||||
import windows
|
||||
|
||||
import windows.native_exec.simple_x64 as x64
|
||||
import windows.native_exec.simple_x86 as x86
|
||||
from windows.generated_def.winstructs import *
|
||||
|
||||
|
||||
StrlenW64 = x64.MultipleInstr()
|
||||
StrlenW64 += x64.Label(":FUNC_STRLENW64")
|
||||
StrlenW64 += x64.Push("RCX")
|
||||
StrlenW64 += x64.Push("RDI")
|
||||
StrlenW64 += x64.Mov("RDI", "RCX")
|
||||
StrlenW64 += x64.Xor("RAX", "RAX")
|
||||
StrlenW64 += x64.Xor("RCX", "RCX")
|
||||
StrlenW64 += x64.Dec("RCX")
|
||||
StrlenW64 += x64.Repne + x64.ScasW()
|
||||
StrlenW64 += x64.Not("RCX")
|
||||
StrlenW64 += x64.Dec("RCX")
|
||||
StrlenW64 += x64.Mov("RAX", "RCX")
|
||||
StrlenW64 += x64.Pop("RDI")
|
||||
StrlenW64 += x64.Pop("RCX")
|
||||
StrlenW64 += x64.Ret()
|
||||
|
||||
|
||||
StrlenA64 = x64.MultipleInstr()
|
||||
StrlenA64 += x64.Label(":FUNC_STRLENA64")
|
||||
StrlenA64 += x64.Push("RCX")
|
||||
StrlenA64 += x64.Push("RDI")
|
||||
StrlenA64 += x64.Mov("RDI", "RCX")
|
||||
StrlenA64 += x64.Xor("RAX", "RAX")
|
||||
StrlenA64 += x64.Xor("RCX", "RCX")
|
||||
StrlenA64 += x64.Dec("RCX")
|
||||
StrlenA64 += x64.Repne + x64.ScasB()
|
||||
StrlenA64 += x64.Not("RCX")
|
||||
StrlenA64 += x64.Dec("RCX")
|
||||
StrlenA64 += x64.Mov("RAX", "RCX")
|
||||
StrlenA64 += x64.Pop("RDI")
|
||||
StrlenA64 += x64.Pop("RCX")
|
||||
StrlenA64 += x64.Ret()
|
||||
|
||||
|
||||
GetProcAddress64 = x64.MultipleInstr()
|
||||
GetProcAddress64 += x64.Label(":FUNC_GETPROCADDRESS64")
|
||||
GetProcAddress64 += x64.Push("RBX")
|
||||
GetProcAddress64 += x64.Push("RCX")
|
||||
GetProcAddress64 += x64.Push("RDX")
|
||||
GetProcAddress64 += x64.Push("RSI")
|
||||
GetProcAddress64 += x64.Push("RDI")
|
||||
GetProcAddress64 += x64.Push("R8")
|
||||
GetProcAddress64 += x64.Push("R9")
|
||||
GetProcAddress64 += x64.Push("R10")
|
||||
GetProcAddress64 += x64.Push("R11")
|
||||
GetProcAddress64 += x64.Push("R12")
|
||||
GetProcAddress64 += x64.Push("R13")
|
||||
# Params : RCX -> libname
|
||||
# Params : RDX -> API Name
|
||||
GetProcAddress64 += x64.Mov("R11", "RCX")
|
||||
GetProcAddress64 += x64.Mov("R12", "RDX")
|
||||
GetProcAddress64 += x64.Mov("RAX", x64.mem("GS:[0x60]")) #PEB !
|
||||
GetProcAddress64 += x64.Mov("RAX", x64.mem("[RAX + 24] ")) # ; RAX = ldr (+ 6 for 64 cause of 2 ptr)
|
||||
GetProcAddress64 += x64.Mov("RAX", x64.mem("[RAX + 32]")) # ; RAX on the first elt of the list (first module)
|
||||
GetProcAddress64 += x64.Mov("RDX", "RAX")
|
||||
GetProcAddress64 += x64.Label(":a_dest")
|
||||
GetProcAddress64 += x64.Mov("RAX", "RDX")
|
||||
GetProcAddress64 += x64.Mov("RBX", x64.mem("[RAX + 32]")) # RBX : first base ! (base of current module)
|
||||
#GetProcAddress64 += x64.Mov("RBX ", x64.mem("[RAX + 32]")) # RBX : first base ! (base of current module)
|
||||
GetProcAddress64 += x64.Cmp("RBX", 0)
|
||||
GetProcAddress64 += x64.Jz(":DLL_NOT_FOUND")
|
||||
GetProcAddress64 += x64.Mov("RCX", x64.mem("[RAX + 80]")) # RCX = NAME (UNICODE_STRING.Buffer)
|
||||
GetProcAddress64 += x64.Call(":FUNC_STRLENW64")
|
||||
GetProcAddress64 += x64.Mov("RDI", "RCX")
|
||||
GetProcAddress64 += x64.Mov("RCX", "RAX")
|
||||
GetProcAddress64 += x64.Mov("RSI", "R11")
|
||||
GetProcAddress64 += x64.Rep + x64.CmpsW() #;cmp with current dll name (unicode)
|
||||
GetProcAddress64 += x64.Test("RCX", "RCX")
|
||||
GetProcAddress64 += x64.Jz(":DLL_FOUND")
|
||||
GetProcAddress64 += x64.Mov("RDX", x64.mem("[RDX]"))
|
||||
GetProcAddress64 += x64.Jmp(":a_dest")
|
||||
GetProcAddress64 += x64.Label(":DLL_FOUND") # here rbx = base
|
||||
GetProcAddress64 += x64.Mov("EAX", x64.mem("[RBX + 60]")) # rax = PEBASE RVA
|
||||
GetProcAddress64 += x64.Add("RAX", "RBX") # RAX = PEBASE
|
||||
GetProcAddress64 += x64.Add("RAX", 24) # ;OPTIONAL HEADER
|
||||
GetProcAddress64 += x64.Mov("ECX", x64.mem("[rax + 112]")) # ;rcx = RVA export dir
|
||||
GetProcAddress64 += x64.Add("RCX", "RBX") # ;rcx = export_dir
|
||||
GetProcAddress64 += x64.Mov("RAX", "RCX") # ;RAX = export_dir
|
||||
GetProcAddress64 += x64.Push("RAX") # ;Save it for after function search
|
||||
# ; EBX = BASE | EAX = EXPORT DIR
|
||||
GetProcAddress64 += x64.Mov("ECX", x64.mem("[RAX + 24] "))
|
||||
GetProcAddress64 += x64.Mov("R13", "RCX") # ;r13 = NB names
|
||||
GetProcAddress64 += x64.Mov("EDX", x64.mem("[RAX + 32] ")) # EDX = names array RVA
|
||||
GetProcAddress64 += x64.Add("RDX", "RBX") # RDX = names array
|
||||
GetProcAddress64 += x64.Xor("RCX", "RCX")
|
||||
GetProcAddress64 += x64.Label(":SEARCH_LOOP")
|
||||
GetProcAddress64 += x64.Cmp("RCX", "R13")
|
||||
GetProcAddress64 += x64.Jz(":API_NOT_FOUND")
|
||||
GetProcAddress64 += x64.Mov("ESI", x64.mem("[RDX + RCX * 4]")) # ;Get function name RVA
|
||||
GetProcAddress64 += x64.Add("RSI", "RBX") # ;Get name addr
|
||||
GetProcAddress64 += x64.Push("RCX") # ;Save current index (could use x64 register)
|
||||
GetProcAddress64 += x64.Mov("RCX", "R12")
|
||||
GetProcAddress64 += x64.Call(":FUNC_STRLENA64") # TODO: mov outside the loop :D
|
||||
GetProcAddress64 += x64.Mov("RCX", "RAX")
|
||||
GetProcAddress64 += x64.Mov("RDI", "R12")
|
||||
GetProcAddress64 += x64.Inc("RCX")
|
||||
GetProcAddress64 += x64.Rep + x64.CmpsB()
|
||||
GetProcAddress64 += x64.Mov("EAX", "ECX")
|
||||
GetProcAddress64 += x64.Pop("RCX")
|
||||
GetProcAddress64 += x64.Inc("RCX")
|
||||
GetProcAddress64 += x64.Test("RAX", "RAX")
|
||||
GetProcAddress64 += x64.Jnz(":SEARCH_LOOP")
|
||||
# Func FOUND !
|
||||
GetProcAddress64 += x64.Dec("RCX")
|
||||
GetProcAddress64 += x64.Pop("RAX") # ;Restore export_dir addr
|
||||
GetProcAddress64 += x64.Mov("EDX", x64.mem("[RAX + 36]")) # ;EDX = AddressOfNameOrdinals RVX
|
||||
GetProcAddress64 += x64.Add("RDX", "RBX")
|
||||
GetProcAddress64 += x64.OperandSizeOverride + x64.Mov("ECX", x64.mem("[rdx + rcx * 2]")) # ; ecx = Ieme ordinal (short array)
|
||||
GetProcAddress64 += x64.And('RCX', 0xffff)
|
||||
GetProcAddress64 += x64.Mov("EDX", x64.mem("[RAX + 28]")) # ; AddressOfFunctions RVA
|
||||
GetProcAddress64 += x64.Add("RDX", "RBX")
|
||||
GetProcAddress64 += x64.Mov("EDX", x64.mem("[RDX + RCX * 4]"))
|
||||
GetProcAddress64 += x64.Add("RDX", "RBX")
|
||||
GetProcAddress64 += x64.Mov("RAX", "RDX")
|
||||
GetProcAddress64 += x64.Label(":RETURN")
|
||||
GetProcAddress64 += x64.Pop("R13")
|
||||
GetProcAddress64 += x64.Pop("R12")
|
||||
GetProcAddress64 += x64.Pop("R11")
|
||||
GetProcAddress64 += x64.Pop("R10")
|
||||
GetProcAddress64 += x64.Pop("R9")
|
||||
GetProcAddress64 += x64.Pop("R8")
|
||||
GetProcAddress64 += x64.Pop("RDI")
|
||||
GetProcAddress64 += x64.Pop("RSI")
|
||||
GetProcAddress64 += x64.Pop("RDX")
|
||||
GetProcAddress64 += x64.Pop("RCX")
|
||||
GetProcAddress64 += x64.Pop("RBX")
|
||||
GetProcAddress64 += x64.Ret()
|
||||
GetProcAddress64 += x64.Label(":DLL_NOT_FOUND")
|
||||
GetProcAddress64 += x64.Mov("RAX", 0xfffffffffffffffe)
|
||||
GetProcAddress64 += x64.Jmp(":RETURN")
|
||||
GetProcAddress64 += x64.Label(":API_NOT_FOUND")
|
||||
GetProcAddress64 += x64.Pop("RAX")
|
||||
GetProcAddress64 += x64.Mov("RAX", 0xffffffffffffffff)
|
||||
GetProcAddress64 += x64.Jmp(":RETURN")
|
||||
# Ajout des dependances
|
||||
GetProcAddress64 += StrlenW64
|
||||
GetProcAddress64 += StrlenA64
|
||||
|
||||
|
||||
|
||||
###### 32 bits #######
|
||||
|
||||
|
||||
StrlenW32 = x86.MultipleInstr()
|
||||
StrlenW32 += x86.Label(":FUNC_STRLENW32")
|
||||
StrlenW32 += x86.Push("EDI")
|
||||
StrlenW32 += x86.Mov("EDI", x86.mem("[ESP + 8]"))
|
||||
StrlenW32 += x86.Push("ECX")
|
||||
StrlenW32 += x86.Xor("EAX", "EAX")
|
||||
StrlenW32 += x86.Xor("ECX", "ECX")
|
||||
StrlenW32 += x86.Dec("ECX")
|
||||
StrlenW32 += x86.Repne + x86.ScasW()
|
||||
StrlenW32 += x86.Not("ECX")
|
||||
StrlenW32 += x86.Dec("ECX")
|
||||
StrlenW32 += x86.Mov("EAX", "ECX")
|
||||
StrlenW32 += x86.Pop("ECX")
|
||||
StrlenW32 += x86.Pop("EDI")
|
||||
StrlenW32 += x86.Ret()
|
||||
|
||||
|
||||
StrlenA32 = x86.MultipleInstr()
|
||||
StrlenA32 += x86.Label(":FUNC_STRLENA32")
|
||||
StrlenA32 += x86.Push("EDI")
|
||||
StrlenA32 += x86.Mov("EDI", x86.mem("[ESP + 8]"))
|
||||
StrlenA32 += x86.Push("ECX")
|
||||
StrlenA32 += x86.Xor("EAX", "EAX")
|
||||
StrlenA32 += x86.Xor("ECX", "ECX")
|
||||
StrlenA32 += x86.Dec("ECX")
|
||||
StrlenA32 += x86.Repne + x86.ScasB()
|
||||
StrlenA32 += x86.Not("ECX")
|
||||
StrlenA32 += x86.Dec("ECX")
|
||||
StrlenA32 += x86.Mov("EAX", "ECX")
|
||||
StrlenA32 += x86.Pop("ECX")
|
||||
StrlenA32 += x86.Pop("EDI")
|
||||
StrlenA32 += x86.Ret()
|
||||
|
||||
|
||||
GetProcAddress32 = x86.MultipleInstr()
|
||||
GetProcAddress32 += x86.Label(":FUNC_GETPROCADDRESS32")
|
||||
GetProcAddress32 += x86.Push("EBX")
|
||||
GetProcAddress32 += x86.Push("ECX")
|
||||
GetProcAddress32 += x86.Push("EDI")
|
||||
GetProcAddress32 += x86.Push("ESI")
|
||||
GetProcAddress32 += x86.Push("EBP")
|
||||
GetProcAddress32 += x86.Mov("EAX", x86.mem("FS:[0x30]"))
|
||||
GetProcAddress32 += x86.Mov("EAX", x86.mem("[EAX + 0xC]"))
|
||||
GetProcAddress32 += x86.Mov("EAX", x86.mem("[EAX + 0xC]")) # ; RAX on the first elt of the list (first module)
|
||||
GetProcAddress32 += x86.Mov("EDX", "EAX")
|
||||
GetProcAddress32 += x86.Label(":a_dest")
|
||||
GetProcAddress32 += x86.Mov("EAX", "EDX")
|
||||
GetProcAddress32 += x86.Mov("EBX", x86.mem("[EAX + 0x18]")) # EBX : first base ! (base of current module)
|
||||
GetProcAddress32 += x86.Cmp("EBX", 0)
|
||||
GetProcAddress32 += x86.Jz(":DLL_NOT_FOUND")
|
||||
GetProcAddress32 += x86.Mov("ECX", x86.mem("[EAX + 0x30]")) # RCX = NAME (UNICODE_STRING.Buffer)
|
||||
GetProcAddress32 += x86.Push("ECX")
|
||||
GetProcAddress32 += x86.Call(":FUNC_STRLENW32")
|
||||
GetProcAddress32 += x86.Pop("EDI") # Current name
|
||||
GetProcAddress32 += x86.Mov("ECX", "EAX")
|
||||
GetProcAddress32 += x86.Mov("ESI", x86.mem("[ESP + 0x18]"))
|
||||
GetProcAddress32 += x86.Rep + x86.CmpsW()
|
||||
GetProcAddress32 += x86.Test("ECX", "ECX")
|
||||
GetProcAddress32 += x86.Jz(":DLL_FOUND")
|
||||
GetProcAddress32 += x86.Mov("EDX", x86.mem("[EDX]"))
|
||||
GetProcAddress32 += x86.Jmp(":a_dest")
|
||||
GetProcAddress32 += x86.Label(":DLL_FOUND")
|
||||
GetProcAddress32 += x86.Mov("EAX", x86.mem("[EBX + 0x3c]")) # rax = PEBASE RVA
|
||||
GetProcAddress32 += x86.Add("EAX", "EBX") # RAX = PEBASE
|
||||
GetProcAddress32 += x86.Add("EAX", 0x18) # ;OPTIONAL HEADER
|
||||
GetProcAddress32 += x86.Mov("ECX", x86.mem("[EAX + 0x60]")) # ;ecx = RVA export dir
|
||||
GetProcAddress32 += x86.Add("ECX", "EBX") # ;ecx = export_dir
|
||||
GetProcAddress32 += x86.Mov("EAX", "ECX")
|
||||
GetProcAddress32 += x86.Push("EAX") # Save it
|
||||
# ; EBX = BASE | EAX = EXPORT DIR
|
||||
GetProcAddress32 += x86.Mov("ECX", x86.mem("[EAX + 24] "))
|
||||
GetProcAddress32 += x86.Mov("EBP", "ECX") # ;EBP = NB names
|
||||
GetProcAddress32 += x86.Mov("EDX", x86.mem("[EAX + 32] ")) # EDX = names array RVA
|
||||
GetProcAddress32 += x86.Add("EDX", "EBX") # RDX = names array
|
||||
GetProcAddress32 += x86.Xor("ECX", "ECX")
|
||||
GetProcAddress32 += x86.Mov("ESI", x86.mem("[ESP + 0x20]"))
|
||||
GetProcAddress32 += x86.Label(":SEARCH_LOOP")
|
||||
GetProcAddress32 += x86.Cmp("ECX", "EBP")
|
||||
GetProcAddress32 += x86.Jz(":API_NOT_FOUND")
|
||||
GetProcAddress32 += x86.Mov("EDI", x86.mem("[EDX + ECX * 4]")) # ;Get function name RVA
|
||||
GetProcAddress32 += x86.Add("EDI", "EBX") # ;Get name addr
|
||||
GetProcAddress32 += x86.Push("ECX") # Save current index
|
||||
GetProcAddress32 += x86.Push("ESI")
|
||||
GetProcAddress32 += x86.Call(":FUNC_STRLENA32")
|
||||
GetProcAddress32 += x86.Mov("ECX", "EAX")
|
||||
GetProcAddress32 += x86.Push("EDI")
|
||||
GetProcAddress32 += x86.Call(":FUNC_STRLENA32")
|
||||
GetProcAddress32 += x86.Pop("EDI")
|
||||
GetProcAddress32 += x86.Cmp("EAX", "ECX")
|
||||
GetProcAddress32 += x86.Jnz(":ABORT_STRCMP")
|
||||
GetProcAddress32 += x86.Inc("ECX")
|
||||
GetProcAddress32 += x86.Rep + x86.CmpsB()
|
||||
GetProcAddress32 += x86.Label(":ABORT_STRCMP")
|
||||
GetProcAddress32 += x86.Pop("ESI")
|
||||
GetProcAddress32 += x86.Mov("EAX", "ECX")
|
||||
GetProcAddress32 += x86.Pop("ECX")
|
||||
GetProcAddress32 += x86.Inc("ECX")
|
||||
GetProcAddress32 += x86.Test("EAX", "EAX")
|
||||
GetProcAddress32 += x86.Jnz(":SEARCH_LOOP")
|
||||
|
||||
GetProcAddress32 += x86.Dec("ECX")
|
||||
#GetProcAddress32 += x86.Int3() # da poi(edx + (ecx * 4)) + ebx; da esi
|
||||
GetProcAddress32 += x86.Pop("EAX") # ;Restore export_dir addr
|
||||
GetProcAddress32 += x86.Mov("EDX", x86.mem("[EAX + 36]")) # ;EDX = AddressOfNameOrdinals RVX
|
||||
GetProcAddress32 += x86.Add("EDX", "EBX")
|
||||
#GetProcAddress32 += x86.Mov("ECX", x86.mem("[EDX + ECX * 2]"))
|
||||
GetProcAddress32 += x86.OperandSizeOverride + x86.Mov("ECX", x86.mem("[EDX + ECX * 2]"))
|
||||
# ; ecx = Ieme ordinal (short array)
|
||||
GetProcAddress32 += x86.And('ECX', 0xffff)
|
||||
GetProcAddress32 += x86.Mov("EDX", x86.mem("[EAX + 28]")) # ; AddressOfFunctions RVA
|
||||
GetProcAddress32 += x86.Add("EDX", "EBX")
|
||||
GetProcAddress32 += x86.Mov("EDX", x86.mem("[EDX + ECX * 4]"))
|
||||
GetProcAddress32 += x86.Add("EDX", "EBX")
|
||||
GetProcAddress32 += x86.Mov("EAX", "EDX")
|
||||
GetProcAddress32 += x86.Label(":RETURN")
|
||||
GetProcAddress32 += x86.Pop("EBP")
|
||||
GetProcAddress32 += x86.Pop("ESI")
|
||||
GetProcAddress32 += x86.Pop("EDI")
|
||||
GetProcAddress32 += x86.Pop("ECX")
|
||||
GetProcAddress32 += x86.Pop("EBX")
|
||||
GetProcAddress32 += x86.Ret()
|
||||
GetProcAddress32 += x86.Label(":DLL_NOT_FOUND")
|
||||
GetProcAddress32 += x86.Mov("EAX", 0xfffffffe)
|
||||
GetProcAddress32 += x86.Jmp(":RETURN")
|
||||
GetProcAddress32 += x86.Label(":API_NOT_FOUND")
|
||||
GetProcAddress32 += x86.Pop("EAX")
|
||||
GetProcAddress32 += x86.Mov("EAX", 0xffffffff)
|
||||
GetProcAddress32 += x86.Jmp(":RETURN")
|
||||
GetProcAddress32 += StrlenW32
|
||||
GetProcAddress32 += StrlenA32
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,453 @@
|
||||
import ctypes
|
||||
import windows
|
||||
import windows.hooks as hooks
|
||||
import windows.utils as utils
|
||||
|
||||
from windows.generated_def.winstructs import *
|
||||
from windows.utils import transform_ctypes_fields
|
||||
import windows.remotectypes as rctypes
|
||||
|
||||
IMAGE_ORDINAL_FLAG32 = 0x80000000
|
||||
IMAGE_ORDINAL_FLAG64 = 0x8000000000000000
|
||||
|
||||
|
||||
def get_structure_transformer_for_target(target, targetbitness=None):
|
||||
current_bitness = windows.current_process.bitness
|
||||
if target is None:
|
||||
ctypes_structure_transformer = lambda x:x
|
||||
create_structure_at = lambda structcls, addr: structcls.from_address(addr)
|
||||
return ctypes_structure_transformer, create_structure_at
|
||||
|
||||
if targetbitness is None:
|
||||
targetbitness = target.bitness
|
||||
|
||||
if targetbitness == 32 and current_bitness == 64:
|
||||
ctypes_structure_transformer = rctypes.transform_type_to_remote32bits
|
||||
elif targetbitness == 64 and current_bitness == 32:
|
||||
ctypes_structure_transformer = rctypes.transform_type_to_remote64bits
|
||||
elif targetbitness == current_bitness:
|
||||
ctypes_structure_transformer = rctypes.transform_type_to_remote
|
||||
else:
|
||||
raise NotImplementedError("Parsing {0} PE from {1} Process".format(targetedbitness, proc_bitness))
|
||||
|
||||
def create_structure_at(structcls, addr): # Il reste une closure sur 'target' ici !!
|
||||
return ctypes_structure_transformer(structcls)(addr, target)
|
||||
return ctypes_structure_transformer, create_structure_at
|
||||
|
||||
def get_pe_bitness(baseaddr, target):
|
||||
# We can force bitness as the field we access are bitness-independant
|
||||
pe = GetPEFile(baseaddr, target, force_bitness=32)
|
||||
machine = pe.get_NT_HEADER().FileHeader.Machine
|
||||
if machine == 0x14c:
|
||||
return 32
|
||||
elif machine == 0x8664:
|
||||
return 64
|
||||
else:
|
||||
raise ValueError("Unknow PE target machine <0x{0:x}>".format(machine))
|
||||
|
||||
|
||||
## == PEPARSE V2 ==
|
||||
|
||||
|
||||
import collections
|
||||
CtypesStructureTransformers = collections.namedtuple("CtypesStructureTransformers", ["ctypes_structure_transformer", "create_structure_at"])
|
||||
|
||||
def GetPEFile(baseaddr, target=None, force_bitness=None):
|
||||
"""Returns a :class:`PEFile` to explore a PE loaded at `baseaddr` in process `target`.
|
||||
|
||||
:rtype: :class:`PEFile`
|
||||
|
||||
.. note::
|
||||
|
||||
If target is ``None`` it refers to the current process
|
||||
"""
|
||||
proc_bitness = windows.current_process.bitness
|
||||
|
||||
if force_bitness is None:
|
||||
targetedbitness = get_pe_bitness(baseaddr, target)
|
||||
else:
|
||||
targetedbitness = force_bitness
|
||||
|
||||
transformers = get_structure_transformer_for_target(target, targetedbitness)
|
||||
#ctypes_structure_transformer, create_structure_at = transformers
|
||||
transfor_funcs = CtypesStructureTransformers(*transformers) # TODO: rename
|
||||
return PEFile(target, baseaddr, targetedbitness, transfor_funcs)
|
||||
|
||||
|
||||
class THUNK_DATA(ctypes.Union):
|
||||
_fields_ = [
|
||||
("Ordinal", PVOID),
|
||||
("AddressOfData", PVOID)
|
||||
]
|
||||
|
||||
# Special case for .NET PE32 rewrite as 64b
|
||||
# We may have a PE in a 64b process with a 32b IAT
|
||||
class THUNK_DATA_32(ctypes.Union):
|
||||
_fields_ = [
|
||||
("Ordinal", DWORD),
|
||||
("AddressOfData", DWORD)
|
||||
]
|
||||
|
||||
class IMPORT_BY_NAME(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("Hint", WORD),
|
||||
("Name", BYTE)
|
||||
]
|
||||
|
||||
|
||||
def get_string(target, addr):
|
||||
if target is None:
|
||||
return ctypes.c_char_p(addr).value.decode("latin1")
|
||||
return target.read_string(addr)
|
||||
|
||||
|
||||
class PESection(IMAGE_SECTION_HEADER):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
if self.target is None:
|
||||
name = get_string(self.target, ctypes.addressof(self.Name))[:8]
|
||||
else:
|
||||
name = get_string(self.target, self._base_addr)[:8]
|
||||
# Decode as UTF-8 as the MS doc say ?
|
||||
return name
|
||||
|
||||
@property
|
||||
def start(self):
|
||||
return self.baseaddr + self.VirtualAddress
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self.VirtualSize
|
||||
|
||||
def __repr__(self):
|
||||
return "<PESection \"{0}\">".format(self.name)
|
||||
|
||||
|
||||
@classmethod
|
||||
def create(cls, pefile, addr):
|
||||
self = pefile.transformers.create_structure_at(cls, addr)
|
||||
self.baseaddr = pefile.baseaddr
|
||||
self.target = pefile.target
|
||||
return self
|
||||
|
||||
|
||||
class IATPtr(PVOID):
|
||||
@classmethod
|
||||
def from_iatentry(cls, iat_entry):
|
||||
self = cls.from_address(iat_entry.addr)
|
||||
self.addr = iat_entry.addr
|
||||
self.nonhookvalue = iat_entry.nonhookvalue
|
||||
return self
|
||||
|
||||
class IATEntry(ctypes.Structure):
|
||||
"""Represent an entry in the IAT of a module
|
||||
Can be used to get resolved value and setup hook
|
||||
"""
|
||||
_fields_ = [
|
||||
("value", PVOID)]
|
||||
|
||||
@classmethod
|
||||
def create(cls, addr, ord, name, target, transformers):
|
||||
self = transformers.create_structure_at(cls, addr)
|
||||
self.addr = addr
|
||||
self.ord = ord
|
||||
self.name = name
|
||||
self.hook = None
|
||||
self.nonhookvalue = self.value
|
||||
self.target = target
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
return '<{0} "{1}" ordinal {2}>'.format(self.__class__.__name__, self.name, self.ord)
|
||||
|
||||
def set_hook(self, callback, types=None):
|
||||
"""Setup a hook on the entry and return it.
|
||||
You MUST keep a reference to the hook while the hook is enabled.
|
||||
|
||||
:param callback: the hook
|
||||
|
||||
.. note::
|
||||
|
||||
see :ref:`hook_protocol`
|
||||
|
||||
:rtype: :class:`windows.hooks.IATHook`
|
||||
|
||||
.. warning::
|
||||
|
||||
This works only for PEFile with the current process as target.
|
||||
"""
|
||||
if self.target is not None:
|
||||
raise NotImplementedError("Setting hook in remote process (use python code injection)")
|
||||
|
||||
hook = hooks.IATHook(self, callback, types)
|
||||
import weakref
|
||||
self.whook = weakref.ref(hook, self.on_destroy)
|
||||
self.hook = hook
|
||||
hook.enable()
|
||||
return hook
|
||||
|
||||
def on_destroy(self, *args):
|
||||
# We cannot know if the hook was enabled here..
|
||||
print("DESTROY: {0} -> ".format(args, self.enabled))
|
||||
# print(args[0]())
|
||||
|
||||
def remove_hook(self):
|
||||
"""Remove the hook on the entry"""
|
||||
if self.hook is None:
|
||||
return False
|
||||
self.hook.disable()
|
||||
self.hook = None
|
||||
return True
|
||||
|
||||
# def __del__(self):
|
||||
# print(self.hook)
|
||||
# if self.hook:
|
||||
# print("LOL BYE {0}".format(self.hook))
|
||||
|
||||
|
||||
class IMAGE_IMPORT_DESCRIPTOR(IMAGE_IMPORT_DESCRIPTOR): # TODO: use explicite name winstructs.IMAGE_IMPORT_DESCRIPTOR
|
||||
def get_INT(self, pe):
|
||||
THUNK_DATA_TYPE = THUNK_DATA
|
||||
if not self.OriginalFirstThunk:
|
||||
return None
|
||||
# We may have 32bits PE mapped in 32bits process (thanks to .NET PE)
|
||||
if self.target is None and pe.bitness != windows.current_process.bitness:
|
||||
assert windows.current_process.bitness == 64 and pe.bitness == 32, "Mapped 64b PE in current process 32b not handled"
|
||||
THUNK_DATA_TYPE = THUNK_DATA_32
|
||||
int_addr = self.OriginalFirstThunk + self.baseaddr
|
||||
int_entry = self.transformers.create_structure_at(THUNK_DATA_TYPE, int_addr)
|
||||
res = []
|
||||
while int_entry.Ordinal:
|
||||
if int_entry.Ordinal & self.IMAGE_ORDINAL_FLAG:
|
||||
res += [(int_entry.Ordinal & 0x7fffffff, None)]
|
||||
else:
|
||||
import_by_name = self.transformers.create_structure_at(IMPORT_BY_NAME, self.baseaddr + int_entry.AddressOfData)
|
||||
name_address = self.baseaddr + int_entry.AddressOfData + type(import_by_name).Name.offset
|
||||
name = get_string(self.target, name_address)
|
||||
res.append((import_by_name.Hint, name))
|
||||
int_addr += ctypes.sizeof(type(int_entry))
|
||||
int_entry = self.transformers.create_structure_at(THUNK_DATA_TYPE, int_addr)
|
||||
return res
|
||||
|
||||
def get_IAT(self, pe):
|
||||
THUNK_DATA_TYPE = THUNK_DATA
|
||||
if self.target is None and pe.bitness != windows.current_process.bitness:
|
||||
assert windows.current_process.bitness == 64 and pe.bitness == 32, "Mapped 64b PE in current process 32b not handled"
|
||||
THUNK_DATA_TYPE = THUNK_DATA_32
|
||||
iat_addr = self.FirstThunk + self.baseaddr
|
||||
iat_entry = self.transformers.create_structure_at(THUNK_DATA_TYPE, iat_addr)
|
||||
res = []
|
||||
while iat_entry.Ordinal:
|
||||
res.append(IATEntry.create(iat_addr, -1, "??", self.target, self.transformers))
|
||||
iat_addr += ctypes.sizeof(type(iat_entry))
|
||||
iat_entry = self.transformers.create_structure_at(THUNK_DATA_TYPE, iat_addr)
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def create(cls, pefile, addr):
|
||||
self = pefile.transformers.create_structure_at(cls, addr)
|
||||
self.baseaddr = pefile.baseaddr
|
||||
self.transformers = pefile.transformers
|
||||
self.IMAGE_ORDINAL_FLAG = pefile.IMAGE_ORDINAL_FLAG
|
||||
self.target = pefile.target
|
||||
return self
|
||||
|
||||
class IMAGE_EXPORT_DIRECTORY(IMAGE_EXPORT_DIRECTORY): # TODO: use explicite name winstructs._IMAGE_EXPORT_DIRECTORY
|
||||
def get_exports(self):
|
||||
NameOrdinals = self.transformers.create_structure_at((WORD * self.NumberOfNames), self.AddressOfNameOrdinals + self.baseaddr)
|
||||
NameOrdinals = list(NameOrdinals)
|
||||
Functions = self.transformers.create_structure_at((DWORD * self.NumberOfFunctions), self.AddressOfFunctions + self.baseaddr)
|
||||
Names = self.transformers.create_structure_at((DWORD * self.NumberOfNames), self.AddressOfNames + self.baseaddr)
|
||||
res = []
|
||||
for nb, func in enumerate(Functions):
|
||||
func += self.baseaddr
|
||||
if nb in NameOrdinals:
|
||||
name = get_string(self.target, Names[NameOrdinals.index(nb)] + self.baseaddr)
|
||||
# Export name should be ascii
|
||||
# Decode from ascii or return bytes ?
|
||||
# https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#export-address-table
|
||||
else:
|
||||
name = None
|
||||
res.append((nb, func, name))
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def create(cls, pefile, addr):
|
||||
self = pefile.transformers.create_structure_at(cls, addr)
|
||||
self.transformers = pefile.transformers
|
||||
self.target = pefile.target
|
||||
self.baseaddr = pefile.baseaddr
|
||||
return self
|
||||
|
||||
|
||||
class IMAGE_DOS_HEADER(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("e_magic", CHAR * 2),
|
||||
("e_cblp", WORD),
|
||||
("e_cp", WORD),
|
||||
("e_crlc", WORD),
|
||||
("e_cparhdr", WORD),
|
||||
("e_minalloc", WORD),
|
||||
("e_maxalloc", WORD),
|
||||
("e_ss", WORD),
|
||||
("e_sp", WORD),
|
||||
("e_csum", WORD),
|
||||
("e_ip", WORD),
|
||||
("e_cs", WORD),
|
||||
("e_lfarlc", WORD),
|
||||
("e_ovno", WORD),
|
||||
("e_res", WORD * 4),
|
||||
("e_oemid", WORD),
|
||||
("e_oeminfo", WORD),
|
||||
("e_res2", WORD * 10),
|
||||
("e_lfanew", DWORD),
|
||||
]
|
||||
|
||||
class PEFile(object):
|
||||
"""Represent a PE loaded in a process (current or remote)"""
|
||||
|
||||
def __init__(self, target, baseaddr, targetedbitness, transformers):
|
||||
self.target = target
|
||||
self.baseaddr = baseaddr
|
||||
self.bitness = targetedbitness
|
||||
self.transformers = transformers
|
||||
|
||||
if targetedbitness == 32:
|
||||
self.IMAGE_ORDINAL_FLAG = IMAGE_ORDINAL_FLAG32
|
||||
else:
|
||||
self.IMAGE_ORDINAL_FLAG = IMAGE_ORDINAL_FLAG64
|
||||
|
||||
def get_DOS_HEADER(self):
|
||||
return self.transformers.create_structure_at(IMAGE_DOS_HEADER, self.baseaddr)
|
||||
|
||||
def get_NT_HEADER(self):
|
||||
offset = self.get_DOS_HEADER().e_lfanew
|
||||
if self.bitness == 32:
|
||||
return self.transformers.create_structure_at(IMAGE_NT_HEADERS32, self.baseaddr + offset)
|
||||
return self.transformers.create_structure_at(IMAGE_NT_HEADERS64, self.baseaddr + offset)
|
||||
|
||||
|
||||
STANDARD_OPTIONAL_HEADER_TYPE_PER_MAGIC = {
|
||||
IMAGE_NT_OPTIONAL_HDR32_MAGIC: IMAGE_OPTIONAL_HEADER32,
|
||||
IMAGE_NT_OPTIONAL_HDR64_MAGIC: IMAGE_OPTIONAL_HEADER64,
|
||||
}
|
||||
|
||||
STANDARD_OPTIONAL_HEADER_SIZE_PER_MAGIC = (
|
||||
(IMAGE_NT_OPTIONAL_HDR32_MAGIC, ctypes.sizeof(IMAGE_OPTIONAL_HEADER32)),
|
||||
(IMAGE_NT_OPTIONAL_HDR64_MAGIC, ctypes.sizeof(IMAGE_OPTIONAL_HEADER64)),
|
||||
)
|
||||
|
||||
def get_OptionalHeader(self):
|
||||
# We can have a 32bits PE with a 64 bits OptionalHeader
|
||||
# Ex : PE32 .NET that allow to be loaded in 64b process
|
||||
# See: https://github.com/dotnet/runtime/blob/8bbe33819464216becffb7cf8b7ea8dd3bab5836/src/coreclr/src/vm/peimagelayout.cpp#L599
|
||||
# In this case the OptionalHeader is transformed in 64bits & OptionalHeader.Magic is changed accordingly
|
||||
# So we cannot just rely on get_NT_HEADER() to give us the correct OptionalHeader type. some re-check are required
|
||||
default_opth = self.get_NT_HEADER().OptionalHeader
|
||||
# Cannot juste compare types with type(default_opth) as it may be a remoteType
|
||||
current_opth_infos = (default_opth.Magic, ctypes.sizeof(default_opth))
|
||||
if current_opth_infos in self.STANDARD_OPTIONAL_HEADER_SIZE_PER_MAGIC:
|
||||
# The default OptionalHeader structure match what we expect based on the magic (most of the cases)
|
||||
return default_opth
|
||||
# Mismatch -> PE32 remapped as 64b (with OptionalHeader rewrite)
|
||||
# Remap the correct OptionalHeader
|
||||
opt_header_real_type = self.STANDARD_OPTIONAL_HEADER_TYPE_PER_MAGIC[default_opth.Magic]
|
||||
opt_header_addr = default_opth._base_addr if self.target else ctypes.addressof(default_opth)
|
||||
return self.transformers.create_structure_at(opt_header_real_type, opt_header_addr)
|
||||
|
||||
def get_DataDirectory(self):
|
||||
return self.get_OptionalHeader().DataDirectory
|
||||
|
||||
|
||||
def get_IMPORT_DESCRIPTORS(self):
|
||||
import_datadir = self.get_DataDirectory()[IMAGE_DIRECTORY_ENTRY_IMPORT]
|
||||
if import_datadir.VirtualAddress == 0:
|
||||
return []
|
||||
import_descriptor_addr = self.baseaddr + import_datadir.VirtualAddress
|
||||
current_import_descriptor = IMAGE_IMPORT_DESCRIPTOR.create(self, import_descriptor_addr)
|
||||
res = []
|
||||
while current_import_descriptor.FirstThunk:
|
||||
res.append(current_import_descriptor)
|
||||
import_descriptor_addr += ctypes.sizeof(IMAGE_IMPORT_DESCRIPTOR)
|
||||
current_import_descriptor = IMAGE_IMPORT_DESCRIPTOR.create(self, import_descriptor_addr)
|
||||
return res
|
||||
|
||||
def get_EXPORT_DIRECTORY(self):
|
||||
export_directory_rva = self.get_DataDirectory()[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress
|
||||
if export_directory_rva == 0:
|
||||
return None
|
||||
export_directory_addr = self.baseaddr + export_directory_rva
|
||||
exp_dir = IMAGE_EXPORT_DIRECTORY.create(self, export_directory_addr)
|
||||
return exp_dir
|
||||
|
||||
@utils.fixedpropety
|
||||
def sections(self):
|
||||
nt_header = self.get_NT_HEADER()
|
||||
nb_section = nt_header.FileHeader.NumberOfSections
|
||||
SizeOfOptionalHeader = self.get_NT_HEADER().FileHeader.SizeOfOptionalHeader
|
||||
if self.target is None:
|
||||
opt_header_addr = ctypes.addressof(self.get_NT_HEADER().OptionalHeader)
|
||||
else:
|
||||
opt_header_addr = self.get_NT_HEADER().OptionalHeader._base_addr
|
||||
base_section = opt_header_addr + SizeOfOptionalHeader
|
||||
#pe_section_type = IMAGE_SECTION_HEADER
|
||||
return [PESection.create(self, base_section + (sizeof(IMAGE_SECTION_HEADER) * i)) for i in range(nb_section)]
|
||||
#sections_array = self.transformers.create_structure_at((self.PESection * nb_section), base_section)
|
||||
#return list(sections_array)
|
||||
|
||||
@utils.fixedpropety
|
||||
def exports(self):
|
||||
"""The exports of the PE in a dict. Keys are ordinal (:class:`int`) and name (:class:`str`).
|
||||
The values are the addresses of the exports.
|
||||
|
||||
:type: {(:class:`int` or :class:`str`) : :class:`int`}"""
|
||||
res = {}
|
||||
exp_dir = self.get_EXPORT_DIRECTORY()
|
||||
export_datadir = self.get_DataDirectory()[IMAGE_DIRECTORY_ENTRY_EXPORT]
|
||||
export_start = self.baseaddr + export_datadir.VirtualAddress
|
||||
export_end = export_start + export_datadir.Size
|
||||
if exp_dir is None:
|
||||
return res
|
||||
raw_exports = exp_dir.get_exports()
|
||||
for id, rva_addr, rva_name in raw_exports:
|
||||
if export_start <= rva_addr < export_end:
|
||||
# Export proxy...
|
||||
# Contains the string to another Dll.Function
|
||||
rva_addr = get_string(self.target, rva_addr) # Put the string proxy instead
|
||||
|
||||
res[id] = rva_addr
|
||||
if rva_name is not None:
|
||||
res[rva_name] = rva_addr
|
||||
return res
|
||||
|
||||
@utils.fixedpropety
|
||||
def export_name(self):
|
||||
"""The Name attribute of the ``EXPORT_DIRECTORY``"""
|
||||
exp_dir = self.get_EXPORT_DIRECTORY()
|
||||
if exp_dir is None:
|
||||
return None
|
||||
if not exp_dir.Name:
|
||||
return None
|
||||
return get_string(self.target, self.baseaddr + exp_dir.Name)
|
||||
|
||||
# TODO: get imports by parsing other modules exports if no INT
|
||||
@utils.fixedpropety
|
||||
def imports(self):
|
||||
"""The imports of the PE in a dict.
|
||||
Keys are the names of DLL to import from and values are :class:`list`
|
||||
of :class:`IATEntry`
|
||||
|
||||
:type: {:class:`str` : [:class:`IATEntry`]}"""
|
||||
res = {}
|
||||
for import_descriptor in self.get_IMPORT_DESCRIPTORS():
|
||||
INT = import_descriptor.get_INT(self)
|
||||
IAT = import_descriptor.get_IAT(self)
|
||||
if INT is not None:
|
||||
for iat_entry, (ord, name) in zip(IAT, INT):
|
||||
# str(name.decode()) -> python2 and python3 compatible for str result
|
||||
iat_entry.ord = ord
|
||||
iat_entry.name = str(name) if name else ""
|
||||
name = get_string(self.target, self.baseaddr + import_descriptor.Name)
|
||||
res.setdefault(name.lower(), []).extend(IAT)
|
||||
return res
|
||||
@@ -0,0 +1,127 @@
|
||||
import windows
|
||||
from windows import winproxy
|
||||
import windows.generated_def as gdef
|
||||
import ctypes
|
||||
|
||||
from windows.pycompat import is_py3
|
||||
|
||||
if is_py3:
|
||||
from multiprocessing.connection import PipeConnection as native_PipeConnection
|
||||
else:
|
||||
from _multiprocessing import PipeConnection as native_PipeConnection
|
||||
|
||||
# Inspired from 'multiprocessing\connection.py'
|
||||
|
||||
def full_pipe_address(addr):
|
||||
"""Return the full address of the pipe `addr`"""
|
||||
if not isinstance(addr, bytes):
|
||||
addr = addr.encode("ascii")
|
||||
if addr.startswith(b"\\\\"):
|
||||
return addr
|
||||
return br"\\.\pipe" + "\\".encode() + addr
|
||||
|
||||
class PipeConnection(object): # Cannot inherit: crash the interpreter
|
||||
"""A wrapper arround :class:`_multiprocessing.PipeConnection` able to work as a ContextManager"""
|
||||
BUFFER_SIZE = 0x2000
|
||||
|
||||
def __init__(self, connection, name=None, server=False):
|
||||
self.handle = connection.fileno()
|
||||
self.connection = connection
|
||||
self.name = name
|
||||
self.server = server
|
||||
|
||||
@classmethod
|
||||
def from_handle(cls, phandle, *args, **kwargs):
|
||||
"""Create a :class:`PipeConnection` from pipe handle `phandle`"""
|
||||
connection = native_PipeConnection(phandle)
|
||||
return cls(connection, *args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def create(cls, addr, security_descriptor=None):
|
||||
"""Create a namedpipe pipe ``addr``
|
||||
|
||||
:returns type: :class:`PipeConnection`
|
||||
"""
|
||||
addr = full_pipe_address(addr)
|
||||
|
||||
security_attributes = None
|
||||
if security_descriptor is not None:
|
||||
if isinstance(security_descriptor, str):
|
||||
security_descriptor = windows.security.SecurityDescriptor.from_string(security_descriptor)
|
||||
security_attributes = gdef.SECURITY_ATTRIBUTES()
|
||||
security_attributes.nLength = ctypes.sizeof(security_attributes)
|
||||
security_attributes.lpSecurityDescriptor = security_descriptor # Accept as arg ?
|
||||
security_attributes.bInheritHandle = True # Accept as arg ?
|
||||
|
||||
|
||||
pipehandle = winproxy.CreateNamedPipeA(
|
||||
addr, gdef.PIPE_ACCESS_DUPLEX,
|
||||
gdef.PIPE_TYPE_MESSAGE | gdef.PIPE_READMODE_MESSAGE |
|
||||
gdef.PIPE_WAIT,
|
||||
gdef.PIPE_UNLIMITED_INSTANCES, cls.BUFFER_SIZE, cls.BUFFER_SIZE,
|
||||
gdef.NMPWAIT_WAIT_FOREVER, security_attributes
|
||||
)
|
||||
return cls.from_handle(pipehandle, name=addr, server=True)
|
||||
|
||||
@classmethod
|
||||
def connect(cls, addr):
|
||||
"""Connect to the named pipe ``addr``
|
||||
|
||||
:returns type: :class:`PipeConnection`
|
||||
"""
|
||||
addr = full_pipe_address(addr)
|
||||
pipehandle = winproxy.CreateFileA(addr, gdef.GENERIC_READ | gdef.GENERIC_WRITE, 0, None, gdef.OPEN_EXISTING, 0, None)
|
||||
winproxy.SetNamedPipeHandleState(pipehandle, gdef.ULONG(gdef.PIPE_READMODE_MESSAGE), None, None)
|
||||
return cls.from_handle(pipehandle, name=addr, server=False)
|
||||
|
||||
def send(self, *args, **kwargs):
|
||||
"""Send an object on the pipe"""
|
||||
return self.connection.send(*args, **kwargs)
|
||||
|
||||
def recv(self, *args, **kwargs):
|
||||
"""Send an object from the pipe"""
|
||||
return self.connection.recv(*args, **kwargs)
|
||||
|
||||
def wait_connection(self):
|
||||
"""Wait for a client process to connect to the named pipe"""
|
||||
return winproxy.ConnectNamedPipe(self.handle, None)
|
||||
|
||||
def get_security_descriptor(self):
|
||||
return windows.security.SecurityDescriptor.from_handle(self.handle)
|
||||
|
||||
def set_security_descriptor(self, sd):
|
||||
if isinstance(sd, basestring):
|
||||
sd = windows.security.SecurityDescriptor.from_string(sd)
|
||||
sd._apply_to_handle_and_type(self.handle)
|
||||
|
||||
security_descriptor = property(get_security_descriptor, set_security_descriptor)
|
||||
|
||||
def close(self):
|
||||
"""Close the handle of the pipe"""
|
||||
self.connection.close()
|
||||
self.handle = None
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
self.close()
|
||||
|
||||
def __repr__(self):
|
||||
return """<{0} name="{1}" server={2}>""".format(type(self).__name__, self.name, self.server)
|
||||
|
||||
|
||||
connect = PipeConnection.connect
|
||||
create = PipeConnection.create
|
||||
|
||||
def send_object(addr, obj):
|
||||
"""Send `obj` on pipe ``addr``"""
|
||||
with connect(addr) as np:
|
||||
np.send(obj)
|
||||
return None
|
||||
|
||||
def recv_object(addr):
|
||||
"""Receive an object from pipe ``addr``"""
|
||||
with create(addr) as np:
|
||||
np.wait_connection()
|
||||
return np.recv()
|
||||
@@ -0,0 +1,38 @@
|
||||
import sys
|
||||
|
||||
is_py3 = (sys.version_info.major >= 3)
|
||||
|
||||
if is_py3:
|
||||
def str_from_ascii_function(s):
|
||||
return s.decode("ascii")
|
||||
|
||||
int_types = int
|
||||
basestring = str
|
||||
anybuff = (str, bytes)
|
||||
|
||||
def raw_encode(s):
|
||||
if isinstance(s, str):
|
||||
return s.encode("latin1")
|
||||
return s
|
||||
|
||||
def raw_decode(s):
|
||||
if isinstance(s, bytes):
|
||||
return s.decode("latin1")
|
||||
return s
|
||||
|
||||
else: # py2.7
|
||||
def str_from_ascii_function(s):
|
||||
return s
|
||||
|
||||
int_types = (int, long)
|
||||
basestring = basestring
|
||||
anybuff = basestring
|
||||
|
||||
def raw_encode(s):
|
||||
if isinstance(s, unicode):
|
||||
return s.encode("latin1")
|
||||
return s
|
||||
|
||||
def raw_decode(s):
|
||||
# No unicode for now on py2
|
||||
return s
|
||||
@@ -0,0 +1,525 @@
|
||||
"""remote ctypes, a try to a ctypes wrapper that accept a target object for every ready operation
|
||||
Some code is copy-paste, might be userful to rewrite some part later"""
|
||||
|
||||
import _ctypes
|
||||
import ctypes
|
||||
import ctypes.wintypes
|
||||
import itertools
|
||||
from _ctypes import _SimpleCData
|
||||
|
||||
# No PFW deps in this file
|
||||
import sys
|
||||
is_py3 = (sys.version_info.major >= 3)
|
||||
if is_py3:
|
||||
int_types = int
|
||||
else:
|
||||
int_types = (int, long)
|
||||
|
||||
|
||||
# ## Utils ### #
|
||||
def is_pointer(x):
|
||||
return isinstance(x, _ctypes._Pointer)
|
||||
|
||||
|
||||
def is_pointer_type(x):
|
||||
return issubclass(x, _ctypes._Pointer)
|
||||
|
||||
|
||||
def is_array(x):
|
||||
return isinstance(x, _ctypes.Array)
|
||||
|
||||
|
||||
def is_array_type(x):
|
||||
return issubclass(x, _ctypes.Array)
|
||||
|
||||
|
||||
def is_structure_type(x):
|
||||
return issubclass(x, ctypes.Structure)
|
||||
|
||||
|
||||
def is_union_type(x):
|
||||
return issubclass(x, ctypes.Union)
|
||||
|
||||
# ### My types ### #
|
||||
|
||||
# # 64bits pointer types # #
|
||||
|
||||
# I know direct inheritance from _SimpleCData seems bad
|
||||
# But it seems to be the only way to have the normal
|
||||
# ctypes.Structure way of working (need to investigate)
|
||||
|
||||
|
||||
class c_void_p64(_SimpleCData):
|
||||
_type_ = "Q"
|
||||
|
||||
|
||||
class c_char_p64(_SimpleCData):
|
||||
_type_ = "Q"
|
||||
|
||||
|
||||
class c_wchar_p64(_SimpleCData):
|
||||
_type_ = "Q"
|
||||
|
||||
|
||||
# # 32bits pointer types # #
|
||||
class c_void_p32(_SimpleCData):
|
||||
_type_ = "I"
|
||||
|
||||
|
||||
class c_char_p32(_SimpleCData):
|
||||
_type_ = "I"
|
||||
|
||||
|
||||
class c_wchar_p32(_SimpleCData):
|
||||
_type_ = "I"
|
||||
|
||||
|
||||
# standard type translation
|
||||
# don't know how to handle size_t since it's non-distinguable from c_ulong
|
||||
# maybe force import before ctypes and modif stuff into ctypes ?
|
||||
|
||||
|
||||
# # Remote Value
|
||||
# Used by the RemoteStructure to access the target memory
|
||||
|
||||
class RemoteValue(object):
|
||||
@classmethod
|
||||
def from_buffer_with_target(cls, buffer, offset=0, target=None):
|
||||
x = cls.from_buffer(buffer)
|
||||
x.target = target
|
||||
return x
|
||||
|
||||
|
||||
class RemotePtr(RemoteValue):
|
||||
@property
|
||||
def raw_value(self):
|
||||
return ctypes.cast(self, ctypes.c_void_p).value
|
||||
|
||||
|
||||
class RemoteCCharP(RemotePtr, ctypes.c_char_p):
|
||||
@property
|
||||
def value(self):
|
||||
base = self.raw_value
|
||||
if not base:
|
||||
return None
|
||||
res = []
|
||||
for i in itertools.count():
|
||||
x = self.target.read_memory(base + (i * 0x100), 0x100)
|
||||
if b"\x00" in x:
|
||||
res.append(x.split(b"\x00", 1)[0])
|
||||
break
|
||||
res.append(x)
|
||||
return b"".join(res)
|
||||
|
||||
|
||||
class RemoteWCharP(RemotePtr, ctypes.c_char_p):
|
||||
@property
|
||||
def value(self):
|
||||
base = self.raw_value
|
||||
if not base:
|
||||
return None
|
||||
# Simple case where target in a WinProcess
|
||||
try:
|
||||
return self.target.read_wstring(base)
|
||||
except AttributeError as e:
|
||||
pass
|
||||
# Copy of Winprocess.read_wstring if target is a custom object
|
||||
read_size = 0x100
|
||||
readden = 0
|
||||
# I am trying to do something smart here..
|
||||
while True:
|
||||
try:
|
||||
x = self.read_memory(addr + readden, read_size)
|
||||
except winproxy.WinproxyError as e:
|
||||
if read_size == 2:
|
||||
raise
|
||||
# handle read_wstring at end of page
|
||||
# Of read failed: read only the half of size
|
||||
# read_size must remain a multiple of 2
|
||||
read_size = read_size / 2
|
||||
continue
|
||||
readden += read_size
|
||||
utf16_chars = ["".join(c) for c in zip(*[iter(x)] * 2)]
|
||||
if "\x00\x00" in utf16_chars:
|
||||
res.extend(utf16_chars[:utf16_chars.index("\x00\x00")])
|
||||
break
|
||||
res.extend(x)
|
||||
return "".join(res).decode('utf16')
|
||||
|
||||
|
||||
class RemoteStructurePointer(RemotePtr, ctypes.c_void_p):
|
||||
@classmethod
|
||||
def from_buffer_with_target_and_ptr_type(cls, buffer, offset=0, target=None, ptr_type=None):
|
||||
x = cls.from_buffer(buffer)
|
||||
x.target = target
|
||||
x.real_pointer_type = ptr_type
|
||||
return x
|
||||
|
||||
@property
|
||||
def contents(self):
|
||||
remote_pointed_type = RemoteStructure.from_structure(self.real_pointer_type._type_)
|
||||
return remote_pointed_type(self.raw_value, self.target)
|
||||
|
||||
def __repr__(self):
|
||||
return "<RemoteStructurePointer to {0}>".format(self.real_pointer_type._type_.__name__)
|
||||
|
||||
|
||||
def create_remote_array(subtype, len):
|
||||
|
||||
class RemoteArray(_ctypes.Array):
|
||||
_length_ = len
|
||||
_type_ = subtype
|
||||
|
||||
def __init__(self, addr, target):
|
||||
self._base_addr = addr
|
||||
self.target = target
|
||||
|
||||
def __getitem__(self, slice):
|
||||
# import pdb;pdb.set_trace()
|
||||
if not isinstance(slice, int_types):
|
||||
raise NotImplementedError("RemoteArray slice __getitem__")
|
||||
if slice >= len:
|
||||
raise IndexError("Access to {0} for a RemoteArray of size {1}".format(slice, len))
|
||||
item_addr = self._base_addr + (ctypes.sizeof(subtype) * slice)
|
||||
|
||||
# TODO: do better ?
|
||||
class TST(ctypes.Structure):
|
||||
_fields_ = [("TST", subtype)]
|
||||
return RemoteStructure.from_structure(TST)(item_addr, target=self.target).TST
|
||||
|
||||
def __getslice__(self, start, stop): # Still used even for python 2.7 wtf :F
|
||||
stop = min(stop, len)
|
||||
start = max(start, 0)
|
||||
# dummy implementation
|
||||
return [self[i] for i in range(start, stop)]
|
||||
|
||||
return RemoteArray
|
||||
|
||||
|
||||
# 64bits pointers
|
||||
|
||||
class RemotePtr64(RemoteValue):
|
||||
def __init__(self, value, target):
|
||||
self.target = target
|
||||
super(RemotePtr64, self).__init__(value)
|
||||
|
||||
@property
|
||||
def raw_value(self):
|
||||
# Bypass our own 'value' implementation
|
||||
# Even if we are a subclass of c_ulonglong
|
||||
my_addr = ctypes.addressof(self)
|
||||
return ctypes.c_ulonglong.from_address(my_addr).value
|
||||
|
||||
|
||||
class Remote_c_void_p64(RemotePtr64, c_void_p64):
|
||||
pass
|
||||
|
||||
|
||||
# base explanation:
|
||||
# RemotePtr64 for the good `raw_value` implem
|
||||
# RemoteCCharP for the good `value` implem
|
||||
# c_char_p64 for the good _type_ (ctypes size)
|
||||
class Remote_c_char_p64(c_char_p64, RemotePtr64, RemoteCCharP):
|
||||
def __repr__(self):
|
||||
return "<Remote_c_char_p64({0})>".format(self.raw_value)
|
||||
|
||||
|
||||
class Remote_w_char_p64(c_wchar_p64, RemotePtr64, RemoteWCharP):
|
||||
def __repr__(self):
|
||||
return "<Remote_c_wchar_p64({0})>".format(self.raw_value)
|
||||
|
||||
|
||||
class RemoteStructurePointer64(Remote_c_void_p64):
|
||||
@property
|
||||
def raw_value(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_buffer_with_target_and_ptr_type(cls, buffer, offset=0, target=None, ptr_type=None):
|
||||
x = cls.from_buffer(buffer)
|
||||
x.target = target
|
||||
x.real_pointer_type = ptr_type
|
||||
return x
|
||||
|
||||
@property
|
||||
def contents(self):
|
||||
remote_pointed_type = transform_type_to_remote64bits(self.real_pointer_type._sub_ctypes_)
|
||||
return remote_pointed_type(self.raw_value, self.target)
|
||||
|
||||
|
||||
type_32_64_translation_table = {
|
||||
ctypes.c_void_p: Remote_c_void_p64,
|
||||
ctypes.c_char_p: Remote_c_char_p64,
|
||||
ctypes.c_wchar_p: Remote_w_char_p64,
|
||||
}
|
||||
|
||||
|
||||
# 32bits pointers
|
||||
|
||||
class RemotePtr32(RemoteValue):
|
||||
def __init__(self, value, target):
|
||||
self.target = target
|
||||
super(RemotePtr32, self).__init__(value)
|
||||
|
||||
@property
|
||||
def raw_value(self):
|
||||
# Bypass our own 'value' implementation
|
||||
# Even if we are a subclass of c_ulonglong
|
||||
my_addr = ctypes.addressof(self)
|
||||
return ctypes.c_ulong.from_address(my_addr).value
|
||||
|
||||
|
||||
class Remote_c_void_p32(RemotePtr32, c_void_p32):
|
||||
pass
|
||||
|
||||
|
||||
# base explanation:
|
||||
# RemotePtr64 for the good `raw_value` implem
|
||||
# RemoteCCharP for the good `value` implem
|
||||
# c_char_p64 for the good _type_ (ctypes size)
|
||||
class Remote_c_char_p32(c_char_p32, RemotePtr32, RemoteCCharP):
|
||||
def __repr__(self):
|
||||
return "<Remote_c_char_p32({0})>".format(self.raw_value)
|
||||
|
||||
|
||||
class Remote_w_char_p32(c_wchar_p32, RemotePtr32, RemoteWCharP):
|
||||
def __repr__(self):
|
||||
return "<Remote_c_wchar_p32({0})>".format(self.raw_value)
|
||||
|
||||
|
||||
class RemoteStructurePointer32(Remote_c_void_p32):
|
||||
@property
|
||||
def raw_value(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_buffer_with_target_and_ptr_type(cls, buffer, offset=0, target=None, ptr_type=None):
|
||||
x = cls.from_buffer(buffer)
|
||||
x.target = target
|
||||
x.real_pointer_type = ptr_type
|
||||
return x
|
||||
|
||||
@property
|
||||
def contents(self):
|
||||
# What if we have a non-struct pointer
|
||||
# Like a Ptr(DWORD) we would like to get the underlying value
|
||||
realtype = self.real_pointer_type._sub_ctypes_
|
||||
# if _SimpleCData in realtype.__bases__:
|
||||
# A ctypes original value.
|
||||
# Returnthe real pointed value
|
||||
# Kind of a tricks for now compared to the real ctypes behavior
|
||||
# import pdb;pdb.set_trace()
|
||||
# if not self.value:
|
||||
# return None
|
||||
# targetptr = self.target.read_ptr(self.value)
|
||||
# if not targetptr:
|
||||
# return None
|
||||
# ss = self.target.read_memory(targetptr, ctypes.sizeof(realtype))
|
||||
# return realtype.from_buffer(bytearray(ss)).value
|
||||
remote_pointed_type = transform_type_to_remote32bits(self.real_pointer_type._sub_ctypes_)
|
||||
return remote_pointed_type(self.raw_value, self.target)
|
||||
|
||||
|
||||
type_64_32_translation_table = {
|
||||
ctypes.c_void_p: Remote_c_void_p32,
|
||||
ctypes.c_char_p: Remote_c_char_p32,
|
||||
ctypes.c_wchar_p: Remote_w_char_p32,
|
||||
}
|
||||
|
||||
|
||||
class RemoteStructureUnion(object):
|
||||
"""Target is a process object"""
|
||||
_reserved_name = ["_target", "_fields_", "_fields_dict_", "_base_addr", "_get_field_by_name",
|
||||
"_get_field_descrptor_by_name", "_handle_field_getattr", "_field_type_to_remote_type",
|
||||
"__getattribute__", "_fields_"]
|
||||
|
||||
_field_type_to_remote_type = {
|
||||
ctypes.c_char_p: RemoteCCharP,
|
||||
ctypes.c_wchar_p: RemoteWCharP,
|
||||
Remote_c_void_p64: Remote_c_void_p64,
|
||||
Remote_c_char_p64: Remote_c_char_p64,
|
||||
Remote_w_char_p64: Remote_w_char_p64,
|
||||
Remote_c_void_p32: Remote_c_void_p32,
|
||||
Remote_c_char_p32: Remote_c_char_p32,
|
||||
Remote_w_char_p32: Remote_w_char_p32
|
||||
}
|
||||
|
||||
def __init__(self, base_addr, target):
|
||||
self._target = target
|
||||
self._base_addr = base_addr
|
||||
self._fields_dict_ = dict(self._fields_)
|
||||
|
||||
def _get_field_by_name(self, fieldname):
|
||||
try:
|
||||
return self._fields_dict_[fieldname]
|
||||
except KeyError:
|
||||
raise AttributeError(fieldname + "is not a field of {0}".format(type(self)))
|
||||
|
||||
def _get_field_descrptor_by_name(self, fieldname):
|
||||
return getattr(type(self), fieldname) # ctypes metaclass fill this for us
|
||||
|
||||
def _handle_field_getattr(self, ftype, fosset, fsize):
|
||||
s = self._target.read_memory(self._base_addr + fosset, fsize)
|
||||
if ftype in self._field_type_to_remote_type:
|
||||
return self._field_type_to_remote_type[ftype].from_buffer_with_target(bytearray(s), target=self._target).value
|
||||
if issubclass(ftype, _ctypes._Pointer): # Pointer
|
||||
return RemoteStructurePointer.from_buffer_with_target_and_ptr_type(bytearray(s), target=self._target, ptr_type=ftype)
|
||||
if issubclass(ftype, RemotePtr64): # Pointer to remote64 bits process
|
||||
return RemoteStructurePointer64.from_buffer_with_target_and_ptr_type(bytearray(s), target=self._target, ptr_type=ftype)
|
||||
if issubclass(ftype, RemotePtr32): # Pointer to remote32 bits process
|
||||
return RemoteStructurePointer32.from_buffer_with_target_and_ptr_type(bytearray(s), target=self._target, ptr_type=ftype)
|
||||
if issubclass(ftype, RemoteStructureUnion): # Structure|Union already transfomed in remote
|
||||
return ftype(self._base_addr + fosset, self._target)
|
||||
if issubclass(ftype, ctypes.Structure): # Structure that must be transfomed
|
||||
return RemoteStructure.from_structure(ftype)(self._base_addr + fosset, self._target)
|
||||
if issubclass(ftype, ctypes.Union): # Union that must be transfomed
|
||||
return RemoteUnion.from_structure(ftype)(self._base_addr + fosset, self._target)
|
||||
if issubclass(ftype, _ctypes.Array): # Arrays
|
||||
# if this is a string: just cast the read value to string
|
||||
if ftype._type_ == ctypes.c_char: # Use issubclass instead ?
|
||||
return s.split(b"\x00", 1)[0]
|
||||
elif ftype._type_ == ctypes.c_wchar: # Use issubclass instead ?
|
||||
# Decode from utf16 -> size /=2 | put it in a wchar array | split at the first "\x00"
|
||||
return (ftype._type_ * (fsize / 2)).from_buffer_copy(s.decode('utf16'))[:].split("\x00", 1)[0] # Sorry..
|
||||
# I am pretty sur something smarter is possible..
|
||||
return create_remote_array(ftype._type_, ftype._length_)(self._base_addr + fosset, self._target)
|
||||
# Normal types
|
||||
# Follow the ctypes usage: if it's not directly inherited from _SimpleCData
|
||||
# We do not apply the .value
|
||||
# Seems weird but it's mandatory AND useful :D (in pe_parse)
|
||||
if _SimpleCData not in ftype.__bases__:
|
||||
return ftype.from_buffer(bytearray(s))
|
||||
return ftype.from_buffer(bytearray(s)).value
|
||||
|
||||
def __getattribute__(self, fieldname):
|
||||
if fieldname in type(self)._reserved_name: # Prevent recursion !
|
||||
return super(RemoteStructureUnion, self).__getattribute__(fieldname)
|
||||
try:
|
||||
t = self._get_field_by_name(fieldname)
|
||||
except AttributeError: # Not a real attribute
|
||||
return super(RemoteStructureUnion, self).__getattribute__(fieldname)
|
||||
descr = self._get_field_descrptor_by_name(fieldname)
|
||||
return self._handle_field_getattr(t, descr.offset, descr.size)
|
||||
|
||||
@classmethod
|
||||
def from_structure(cls, structcls):
|
||||
class MyStruct(cls, structcls): # inherit of structcls to keep property (see winobject.LoadedModule)
|
||||
_fields_ = structcls._fields_
|
||||
|
||||
MyStruct.__name__ = "Remote" + structcls.__name__
|
||||
return MyStruct
|
||||
|
||||
@classmethod
|
||||
def from_fields(cls, fields, base_cls=None):
|
||||
bases = [cls]
|
||||
if base_cls:
|
||||
bases.append(base_cls)
|
||||
# inherit of structcls to keep property (see winobject.LoadedModule)
|
||||
RemoteStruct = type("RemoteStruct", tuple(bases), {"_fields_": fields})
|
||||
if base_cls:
|
||||
RemoteStruct.__name__ = "Remote" + base_cls.__name__
|
||||
return RemoteStruct
|
||||
|
||||
|
||||
class RemoteStructure(RemoteStructureUnion, ctypes.Structure):
|
||||
pass
|
||||
|
||||
|
||||
class RemoteUnion(RemoteStructureUnion, ctypes.Union):
|
||||
pass
|
||||
|
||||
|
||||
remote_struct = RemoteStructure.from_structure
|
||||
|
||||
# ctypes 32 -> 64 methods
|
||||
def MakePtr64(type):
|
||||
class PointerToStruct64(Remote_c_void_p64):
|
||||
_sub_ctypes_ = (type)
|
||||
|
||||
@property
|
||||
def contents(self):
|
||||
return RemoteStructurePointer64.from_buffer_with_target_and_ptr_type(bytearray(self), target=self.target, ptr_type=self).contents
|
||||
|
||||
def __repr__(self):
|
||||
return "<RemotePtr64 to struct {0}>".format(type.__name__)
|
||||
return PointerToStruct64
|
||||
|
||||
def transform_structure_to_remote64bits(structcls):
|
||||
"""Create a remote structure for a 64bits target process"""
|
||||
new_fields = []
|
||||
for fname, ftype in structcls._fields_:
|
||||
ftype = transform_type_to_remote64bits(ftype)
|
||||
new_fields.append((fname, ftype))
|
||||
return RemoteStructure.from_fields(new_fields, base_cls=structcls)
|
||||
|
||||
def transform_union_to_remote64bits(structcls):
|
||||
"""Create a remote union for a 64bits target process"""
|
||||
new_fields = []
|
||||
for fname, ftype in structcls._fields_:
|
||||
ftype = transform_type_to_remote64bits(ftype)
|
||||
new_fields.append((fname, ftype))
|
||||
return RemoteUnion.from_fields(new_fields, base_cls=structcls)
|
||||
|
||||
def transform_type_to_remote64bits(ftype):
|
||||
if is_pointer_type(ftype):
|
||||
return MakePtr64(ftype._type_)
|
||||
if is_array_type(ftype):
|
||||
return create_remote_array(transform_type_to_remote64bits(ftype._type_), ftype._length_)
|
||||
if is_structure_type(ftype):
|
||||
return transform_structure_to_remote64bits(ftype)
|
||||
if is_union_type(ftype):
|
||||
return transform_union_to_remote64bits(ftype)
|
||||
# Normal types
|
||||
return type_32_64_translation_table.get(ftype, ftype)
|
||||
|
||||
|
||||
# ctypes 64 -> 32 methods
|
||||
def MakePtr32(type):
|
||||
class PointerToStruct32(Remote_c_void_p32):
|
||||
_sub_ctypes_ = (type)
|
||||
|
||||
# Not sur about this code..
|
||||
# Logic problem: why do I have PointerToStruct32 and RemoteStructurePointer32... ?
|
||||
@property
|
||||
def contents(self):
|
||||
return RemoteStructurePointer32.from_buffer_with_target_and_ptr_type(bytearray(self), target=self.target, ptr_type=self).contents
|
||||
|
||||
def __repr__(self):
|
||||
return "<RemotePtr32 to struct {0}>".format(type.__name__)
|
||||
|
||||
return PointerToStruct32
|
||||
|
||||
def transform_structure_to_remote32bits(structcls):
|
||||
"""Create a remote structure for a 32bits target process"""
|
||||
new_fields = []
|
||||
for fname, ftype in structcls._fields_:
|
||||
ftype = transform_type_to_remote32bits(ftype)
|
||||
new_fields.append((fname, ftype))
|
||||
return RemoteStructure.from_fields(new_fields, base_cls=structcls)
|
||||
|
||||
def transform_union_to_remote32bits(structcls):
|
||||
"""Create a remote union for a 32bits target process"""
|
||||
new_fields = []
|
||||
for fname, ftype in structcls._fields_:
|
||||
ftype = transform_type_to_remote32bits(ftype)
|
||||
new_fields.append((fname, ftype))
|
||||
return RemoteUnion.from_fields(new_fields, base_cls=structcls)
|
||||
|
||||
def transform_type_to_remote32bits(ftype):
|
||||
if issubclass(ftype, RemoteStructureUnion):
|
||||
return ftype
|
||||
if is_pointer_type(ftype):
|
||||
return MakePtr32(ftype._type_)
|
||||
if is_array_type(ftype):
|
||||
return create_remote_array(transform_type_to_remote32bits(ftype._type_), ftype._length_)
|
||||
if is_structure_type(ftype):
|
||||
return transform_structure_to_remote32bits(ftype)
|
||||
if is_union_type(ftype):
|
||||
return transform_union_to_remote32bits(ftype)
|
||||
# Normal types
|
||||
return type_64_32_translation_table.get(ftype, ftype)
|
||||
|
||||
if ctypes.sizeof(ctypes.c_void_p) == 4:
|
||||
transform_type_to_remote = transform_type_to_remote32bits
|
||||
if ctypes.sizeof(ctypes.c_void_p) == 8:
|
||||
transform_type_to_remote = transform_type_to_remote64bits
|
||||
@@ -0,0 +1,3 @@
|
||||
from . import ndr
|
||||
from .client import RPCClient
|
||||
from .epmapper import find_alpc_endpoint_and_connect, find_alpc_endpoints, construct_alpc_tower
|
||||
@@ -0,0 +1,180 @@
|
||||
import ctypes
|
||||
import struct
|
||||
|
||||
import windows.alpc as alpc
|
||||
import windows.com
|
||||
import windows.generated_def as gdef
|
||||
|
||||
if windows.pycompat.is_py3:
|
||||
buffer = bytes
|
||||
|
||||
|
||||
KNOW_REQUEST_TYPE = gdef.FlagMapper(gdef.RPC_REQUEST_TYPE_CALL, gdef.RPC_REQUEST_TYPE_BIND)
|
||||
KNOW_RESPONSE_TYPE = gdef.FlagMapper(gdef.RPC_RESPONSE_TYPE_FAIL, gdef.RPC_RESPONSE_TYPE_SUCCESS, gdef.RPC_RESPONSE_TYPE_BIND_OK)
|
||||
KNOWN_RPC_ERROR_CODE = gdef.FlagMapper(
|
||||
gdef.ERROR_INVALID_HANDLE,
|
||||
gdef.RPC_X_BAD_STUB_DATA,
|
||||
gdef.RPC_S_UNKNOWN_IF,
|
||||
gdef.RPC_S_PROTOCOL_ERROR,
|
||||
gdef.RPC_S_UNSUPPORTED_TRANS_SYN,
|
||||
gdef.RPC_S_PROCNUM_OUT_OF_RANGE)
|
||||
|
||||
NOT_USED = 0xBAADF00D
|
||||
|
||||
class ALPC_RPC_BIND(ctypes.Structure):
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
("request_type", gdef.DWORD),
|
||||
("UNK1", gdef.DWORD),
|
||||
("UNK2", gdef.DWORD),
|
||||
("target", gdef.RPC_IF_ID),
|
||||
("flags", gdef.DWORD),
|
||||
("if_nb_ndr32", gdef.USHORT),
|
||||
("if_nb_ndr64", gdef.USHORT),
|
||||
("if_nb_unkn", gdef.USHORT),
|
||||
("PAD", gdef.USHORT),
|
||||
("register_multiple_syntax", gdef.DWORD),
|
||||
("use_flow", gdef.DWORD),
|
||||
("UNK5", gdef.DWORD),
|
||||
("maybe_flow_id", gdef.DWORD),
|
||||
("UNK7", gdef.DWORD),
|
||||
("some_context_id", gdef.DWORD),
|
||||
("UNK9", gdef.DWORD),
|
||||
]
|
||||
|
||||
class ALPC_RPC_CALL(ctypes.Structure):
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
("request_type", gdef.DWORD),
|
||||
("UNK1", gdef.DWORD),
|
||||
("flags",gdef.DWORD),
|
||||
("request_id", gdef.DWORD),
|
||||
("if_nb", gdef.DWORD),
|
||||
("method_offset", gdef.DWORD),
|
||||
("UNK2", gdef.DWORD),
|
||||
("UNK3", gdef.DWORD),
|
||||
("UNK4", gdef.DWORD),
|
||||
("UNK5", gdef.DWORD),
|
||||
("UNK6", gdef.DWORD),
|
||||
("UNK7", gdef.DWORD),
|
||||
("ORPC_IPID", gdef.GUID)
|
||||
]
|
||||
|
||||
class RPCClient(object):
|
||||
"""A client for RPC-over-ALPC able to bind to interface and perform calls using NDR32 marshalling"""
|
||||
REQUEST_IDENTIFIER = 0x11223344
|
||||
def __init__(self, port):
|
||||
self.alpc_client = alpc.AlpcClient(port) #: The :class:`windows.alpc.AlpcClient` used to communicate with the server
|
||||
self.number_of_bind_if = 0 # if -> interface
|
||||
self.if_bind_number = {}
|
||||
|
||||
def bind(self, IID_str, version=(1,0)):
|
||||
"""Bind to the ``IID_str`` with the given ``version``
|
||||
|
||||
:returns: :class:`windows.generated_def.IID`
|
||||
"""
|
||||
IID = windows.com.IID.from_string(IID_str)
|
||||
request = self._forge_bind_request(IID, version, self.number_of_bind_if)
|
||||
response = self._send_request(request)
|
||||
# Parse reponse
|
||||
request_type = self._get_request_type(response)
|
||||
if request_type != gdef.RPC_RESPONSE_TYPE_BIND_OK:
|
||||
raise ValueError("Unexpected reponse type. Expected RESPONSE_TYPE_BIND_OK got {0}".format(KNOW_RESPONSE_TYPE[request_type]))
|
||||
iid_hash = hash(buffer(IID)[:]) # TODO: add __hash__ to IID
|
||||
self.if_bind_number[iid_hash] = self.number_of_bind_if
|
||||
self.number_of_bind_if += 1
|
||||
#TODO: attach version information to IID
|
||||
return IID
|
||||
|
||||
def forge_alpc_request(self, IID, method_offset, params, ipid=None):
|
||||
"""Craft an ALPC message containing an RPC request to call ``method_offset`` of interface ``IID`
|
||||
with ``params``.
|
||||
Can be used to craft request without directly sending it
|
||||
"""
|
||||
iid_hash = hash(buffer(IID)[:])
|
||||
interface_nb = self.if_bind_number[iid_hash] # TODO: add __hash__ to IID
|
||||
if len(params) > 0x900: # 0x1000 - size of meta-data
|
||||
request = self._forge_call_request_in_view(interface_nb, method_offset, params, ipid=ipid)
|
||||
else:
|
||||
request = self._forge_call_request(interface_nb, method_offset, params, ipid=ipid)
|
||||
return request
|
||||
|
||||
def call(self, IID, method_offset, params, ipid=None):
|
||||
"""Call method number ``method_offset`` of interface ``IID`` with mashalled ``params``
|
||||
|
||||
:param IID IID: An IID previously returned by :func:`bind`
|
||||
:param int method_offset:
|
||||
:param str params: The mashalled parameters (NDR32)
|
||||
:returns: :class:`str`
|
||||
"""
|
||||
request = self.forge_alpc_request(IID, method_offset, params, ipid=ipid)
|
||||
response = self._send_request(request)
|
||||
# Parse reponse
|
||||
request_type = self._get_request_type(response)
|
||||
if request_type != gdef.RPC_RESPONSE_TYPE_SUCCESS:
|
||||
raise ValueError("Unexpected reponse type. Expected RESPONSE_SUCCESS got {0}".format(KNOW_RESPONSE_TYPE[request_type]))
|
||||
|
||||
# windows.utils.sprint(ALPC_RPC_CALL.from_buffer_copy(response + "\x00" * 12))
|
||||
data = struct.unpack("<6I", response[:6 * 4])
|
||||
assert data[3] == self.REQUEST_IDENTIFIER
|
||||
return response[4 * 6:] # Should be the return value (not completly verified)
|
||||
|
||||
def _send_request(self, request):
|
||||
response = self.alpc_client.send_receive(request)
|
||||
return response.data
|
||||
|
||||
def _forge_call_request(self, interface_nb, method_offset, params, ipid=None):
|
||||
# TODO: differents REQUEST_IDENTIFIER for each req ?
|
||||
# TODO: what is this '0' ? (1 is also accepted) (flags ?)
|
||||
# request = struct.pack("<16I", gdef.RPC_REQUEST_TYPE_CALL, NOT_USED, 1, self.REQUEST_IDENTIFIER, interface_nb, method_offset, *[NOT_USED] * 10)
|
||||
req = ALPC_RPC_CALL()
|
||||
req.request_type = gdef.RPC_REQUEST_TYPE_CALL
|
||||
req.flags = 0
|
||||
req.request_id = self.REQUEST_IDENTIFIER
|
||||
req.if_nb = interface_nb
|
||||
req.method_offset = method_offset
|
||||
if ipid:
|
||||
req.ORPC_IPID = ipid
|
||||
this = gdef.ORPCTHIS()
|
||||
this.version = (5,7)
|
||||
this.flags = 1
|
||||
lthis = gdef.LOCALTHIS()
|
||||
return buffer(req)[:] + buffer(this)[:] + buffer(lthis)[:] + params
|
||||
return buffer(req)[:] + params
|
||||
|
||||
def _forge_call_request_in_view(self, interface_nb, method_offset, params, ipid=None):
|
||||
# import pdb;pdb.set_trace()
|
||||
# Version crade qui clean rien pour POC. GROS DOUTES :D
|
||||
raw_request = self._forge_call_request(interface_nb, method_offset, "")
|
||||
p = windows.alpc.AlpcMessage(0x2000)
|
||||
section = self.alpc_client.create_port_section(0x40000, 0, len(params))
|
||||
view = self.alpc_client.map_section(section[0], len(params))
|
||||
p.port_message.data = raw_request + windows.rpc.ndr.NdrLong.pack(len(params) + 0x200) + "\x00" * 40
|
||||
p.attributes.ValidAttributes |= gdef.ALPC_MESSAGE_VIEW_ATTRIBUTE
|
||||
p.view_attribute.Flags = 0x40000
|
||||
p.view_attribute.ViewBase = view.ViewBase
|
||||
p.view_attribute.SectionHandle = view.SectionHandle
|
||||
p.view_attribute.ViewSize = len(params)
|
||||
windows.current_process.write_memory(view.ViewBase, params) # Write NDR to view
|
||||
return p
|
||||
|
||||
def _forge_bind_request(self, uuid, syntaxversion, requested_if_nb):
|
||||
version_major, version_minor = syntaxversion
|
||||
req = ALPC_RPC_BIND()
|
||||
req.request_type = gdef.RPC_REQUEST_TYPE_BIND
|
||||
req.target = gdef.RPC_IF_ID(uuid, *syntaxversion)
|
||||
req.flags = gdef.BIND_IF_SYNTAX_NDR32
|
||||
req.if_nb_ndr32 = requested_if_nb
|
||||
req.if_nb_ndr64 = 0
|
||||
req.if_nb_unkn = 0
|
||||
req.register_multiple_syntax = False
|
||||
req.some_context_id = 0xB00B00B
|
||||
return buffer(req)[:]
|
||||
|
||||
def _get_request_type(self, response):
|
||||
"raise if request_type == RESPONSE_TYPE_FAIL"
|
||||
request_type = struct.unpack("<I", response[:4])[0]
|
||||
if request_type == gdef.RPC_RESPONSE_TYPE_FAIL:
|
||||
error_code = struct.unpack("<5I", response)[2]
|
||||
raise ValueError("RPC Response error {0} ({1})".format(error_code, KNOWN_RPC_ERROR_CODE.get(error_code, error_code)))
|
||||
return request_type
|
||||
@@ -0,0 +1,199 @@
|
||||
import struct
|
||||
from collections import namedtuple
|
||||
|
||||
import windows
|
||||
import windows.generated_def as gdef
|
||||
from windows.rpc import ndr
|
||||
from windows.dbgprint import dbgprint
|
||||
from windows.pycompat import basestring
|
||||
|
||||
|
||||
|
||||
class NdrTower(ndr.NdrStructure):
|
||||
MEMBERS = [ndr.NdrLong, ndr.NdrByteConformantArray]
|
||||
|
||||
@classmethod
|
||||
def post_unpack(cls, data):
|
||||
size = data[0]
|
||||
tower = data[1]
|
||||
return bytearray(struct.pack("<I", size)) + bytearray(tower)
|
||||
|
||||
|
||||
class NdrContext(ndr.NdrStructure):
|
||||
MEMBERS = [ndr.NdrLong, ndr.NdrLong, ndr.NdrLong, ndr.NdrLong, ndr.NdrLong]
|
||||
|
||||
|
||||
class NDRIID(ndr.NdrStructure):
|
||||
MEMBERS = [ndr.NdrByte] * 16
|
||||
|
||||
|
||||
class EptMapAuthParameters(ndr.NdrParameters):
|
||||
MEMBERS = [NDRIID,
|
||||
NdrTower,
|
||||
ndr.NdrUniquePTR(ndr.NdrSID),
|
||||
NdrContext,
|
||||
ndr.NdrLong]
|
||||
|
||||
|
||||
class Towers(ndr.NdrConformantVaryingArrays):
|
||||
MEMBER_TYPE = ndr.NdrUniquePTR(NdrTower)
|
||||
|
||||
|
||||
class EptMapAuthResults(ndr.NdrParameters):
|
||||
MEMBERS = [NdrContext,
|
||||
ndr.NdrLong,
|
||||
Towers]
|
||||
|
||||
UnpackTower = namedtuple("UnpackTower", ["protseq", "endpoint", "address", "object", "syntax"])
|
||||
|
||||
def parse_floor(stream):
|
||||
lhs_size = stream.partial_unpack("<H")[0]
|
||||
lhs = stream.read(lhs_size)
|
||||
rhs_size = stream.partial_unpack("<H")[0]
|
||||
rhs = stream.read(rhs_size)
|
||||
return lhs, rhs
|
||||
|
||||
def craft_floor(lhs, rhs):
|
||||
return struct.pack("<H", len(lhs)) + lhs + struct.pack("<H", len(rhs)) + rhs
|
||||
|
||||
def explode_alpc_tower(tower):
|
||||
stream = ndr.NdrStream(bytearray(tower))
|
||||
size = stream.partial_unpack("<I")[0]
|
||||
if size != len(stream.data):
|
||||
raise ValueError("Invalid tower size: indicate {0}, tower size {1}".format(size, len(stream.data)))
|
||||
floor_count = stream.partial_unpack("<H")[0]
|
||||
if floor_count != 4:
|
||||
raise ValueError("ALPC Tower are expected to have 4 floors ({0} instead)".format(floor_count))
|
||||
|
||||
# Floor 0
|
||||
lhs, rhs = parse_floor(stream)
|
||||
if not (lhs[0] == 0xd):
|
||||
raise ValueError("Floor 0: IID expected")
|
||||
iid = gdef.IID.from_buffer_copy(lhs[1:17])
|
||||
object = gdef.RPC_IF_ID(iid, lhs[17], lhs[18])
|
||||
|
||||
# Floor 1
|
||||
lhs, rhs = parse_floor(stream)
|
||||
if not (lhs[0] == 0xd):
|
||||
raise ValueError("Floor 0: IID expected")
|
||||
iid = gdef.IID.from_buffer_copy(lhs[1:17])
|
||||
syntax = gdef.RPC_IF_ID(iid, lhs[17], lhs[18])
|
||||
|
||||
# Floor 2
|
||||
lhs, rhs = parse_floor(stream)
|
||||
if (len(lhs) != 1 or lhs[0] != 0x0c):
|
||||
raise ValueError("Alpc Tower expects 0xc as Floor2 LHS (got {0:#x})".format(lhs[0]))
|
||||
|
||||
lhs, rhs = parse_floor(stream)
|
||||
if not (rhs[-1] == 0):
|
||||
rhs = rhs[:rhs.find("\x00")]
|
||||
# raise ValueError("ALPC Port name doest not end by \\x00")
|
||||
return UnpackTower("ncalrpc", bytes(rhs[:-1]), None, object, syntax)
|
||||
|
||||
# http://pubs.opengroup.org/onlinepubs/9629399/apdxi.htm#tagcjh_28
|
||||
# Octet 0 contains the hexadecimal value 0d. This is a reserved protocol identifier prefix that indicates that the protocol ID is UUID derived
|
||||
TOWER_PROTOCOL_IS_UUID = b"\x0d"
|
||||
TOWER_EMPTY_RHS = b"\x00\x00"
|
||||
TOWER_PROTOCOL_ID_ALPC = b"\x0c" # From RE
|
||||
|
||||
def construct_alpc_tower(object, syntax, protseq, endpoint, address):
|
||||
if address is not None:
|
||||
raise NotImplementedError("Construct ALPC Tower with address != None")
|
||||
if protseq != "ncalrpc":
|
||||
raise NotImplementedError("Construct ALPC Tower with protseq != 'ncalrpc'")
|
||||
# Floor 0
|
||||
floor_0_lsh = TOWER_PROTOCOL_IS_UUID + bytearray(object.Uuid) + struct.pack("<BB", object.VersMajor, object.VersMinor)
|
||||
floor_0_rsh = TOWER_EMPTY_RHS
|
||||
floor_0 = craft_floor(floor_0_lsh, floor_0_rsh)
|
||||
# Floor 1
|
||||
floor_1_lsh = TOWER_PROTOCOL_IS_UUID + bytearray(syntax.Uuid) + struct.pack("<BB", syntax.VersMajor, syntax.VersMinor)
|
||||
floor_1_rsh = TOWER_EMPTY_RHS
|
||||
floor_1 = craft_floor(floor_1_lsh, floor_1_rsh)
|
||||
# Floor 2
|
||||
floor_2_lsh = TOWER_PROTOCOL_ID_ALPC
|
||||
floor_2_rsh = TOWER_EMPTY_RHS
|
||||
floor_2 = craft_floor(floor_2_lsh, floor_2_rsh)
|
||||
# Floor 3
|
||||
if endpoint is None:
|
||||
floor_3_lsh = b"\xff"
|
||||
floor_3_rsh = TOWER_EMPTY_RHS
|
||||
floor_3 = craft_floor(floor_3_lsh, floor_3_rsh)
|
||||
else:
|
||||
floor_3_lsh = b"\x10"
|
||||
floor_3_rsh = endpoint
|
||||
floor_3 = craft_floor(floor_3_lsh, floor_3_rsh)
|
||||
towerarray = struct.pack("<H", 4) + floor_0 + floor_1 + floor_2 + floor_3
|
||||
return len(towerarray), bytearray(towerarray)
|
||||
|
||||
def find_alpc_endpoints(targetiid, version=(1,0), nb_response=1, sid=gdef.WinLocalSystemSid):
|
||||
"""Ask the EPMapper for ALPC endpoints of ``targetiid:version`` (maximum of ``nb_response``)
|
||||
|
||||
:param str targetiid: The IID of the requested interface
|
||||
:param (int,int) version: The version requested interface
|
||||
:param int nb_response: The maximum number of response
|
||||
:param WELL_KNOWN_SID_TYPE sid: The SID used to request the EPMapper
|
||||
|
||||
:returns: [:class:`~windows.rpc.epmapper.UnpackTower`] -- A list of :class:`~windows.rpc.epmapper.UnpackTower`
|
||||
"""
|
||||
|
||||
if isinstance(targetiid, basestring):
|
||||
targetiid = gdef.IID.from_string(targetiid)
|
||||
# Connect to epmapper
|
||||
client = windows.rpc.RPCClient(r"\RPC Control\epmapper")
|
||||
epmapperiid = client.bind("e1af8308-5d1f-11c9-91a4-08002b14a0fa", version=(3,0))
|
||||
|
||||
# Compute request tower
|
||||
## object
|
||||
rpc_object = gdef.RPC_IF_ID(targetiid, *version)
|
||||
## Syntax
|
||||
syntax_iid = gdef.IID.from_string("8a885d04-1ceb-11c9-9fe8-08002b104860")
|
||||
rpc_syntax = gdef.RPC_IF_ID(syntax_iid, 2, 0)
|
||||
## Forge tower
|
||||
tower_array_size, towerarray = construct_alpc_tower(rpc_object, rpc_syntax, "ncalrpc", b"", None)
|
||||
|
||||
# parameters
|
||||
local_system_psid = windows.utils.get_known_sid(sid)
|
||||
context = (0, 0, 0, 0, 0)
|
||||
|
||||
# Pack request
|
||||
fullreq = EptMapAuthParameters.pack([bytearray(targetiid),
|
||||
(tower_array_size, towerarray),
|
||||
local_system_psid,
|
||||
context,
|
||||
nb_response])
|
||||
# RPC Call
|
||||
response = client.call(epmapperiid, 7, fullreq)
|
||||
# Unpack response
|
||||
stream = ndr.NdrStream(response)
|
||||
unpacked = EptMapAuthResults.unpack(stream)
|
||||
# Looks like there is a memory leak here (in stream.data) if nb_response > len(unpacked[2])
|
||||
# Parse towers
|
||||
return [explode_alpc_tower(obj) for obj in unpacked[2]]
|
||||
|
||||
|
||||
def find_alpc_endpoint_and_connect(targetiid, version=(1,0), sid=gdef.WinLocalSystemSid):
|
||||
"""Ask the EPMapper for ALPC endpoints of ``targetiid:version`` and connect to one of them.
|
||||
|
||||
:param str targetiid: The IID of the requested interface
|
||||
:param (int,int) version: The version requested interface
|
||||
:param WELL_KNOWN_SID_TYPE sid: The SID used to request the EPMapper
|
||||
|
||||
:returns: A connected :class:`~windows.rpc.RPCClient`
|
||||
"""
|
||||
dbgprint("Finding ALPC endpoints for <{0}>".format(targetiid), "RPC")
|
||||
alpctowers = find_alpc_endpoints(targetiid, version, nb_response=50, sid=sid)
|
||||
dbgprint("ALPC endpoints list: <{0}>".format(alpctowers), "RPC")
|
||||
for tower in alpctowers:
|
||||
dbgprint("Trying to connect to endpoint <{0}>".format(tower.endpoint), "RPC")
|
||||
alpc_port = r"\RPC Control\{0}".format(tower.endpoint.decode())
|
||||
try:
|
||||
client = windows.rpc.RPCClient(alpc_port)
|
||||
except Exception as e:
|
||||
dbgprint("Could not connect to endpoint <{0}>: {1}".format(tower.endpoint, e), "RPC")
|
||||
continue
|
||||
break
|
||||
else:
|
||||
raise ValueError("Could not find a valid endpoint for target <{0}> version <{1}>".format(targetiid, version))
|
||||
dbgprint('Connected to ALPC port "{0}"'.format(alpc_port), "RPC")
|
||||
return client
|
||||
|
||||
@@ -0,0 +1,618 @@
|
||||
import windows
|
||||
import windows.generated_def as gdef
|
||||
|
||||
import struct
|
||||
|
||||
try:
|
||||
unichr # Py2/Py3 compat
|
||||
except NameError:
|
||||
unichr = chr
|
||||
|
||||
# http://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_07
|
||||
|
||||
## Array
|
||||
# A conformant array is an array in which the maximum number of elements is not known beforehand and therefore is included in the representation of the array.
|
||||
# A varying array is an array in which the actual number of elements passed in a given call varies and therefore is included in the representation of the array.
|
||||
|
||||
## Pointers
|
||||
|
||||
# NDR defines two classes of pointers that differ both in semantics and in representation
|
||||
# - reference pointers, which cannot be null and cannot be aliases
|
||||
# - full pointers, which can be null and can be an aliases
|
||||
# - unique pointers, which can be null and cannot be aliases, and are transmitted as full pointers.
|
||||
|
||||
|
||||
def pack_dword(x):
|
||||
return struct.pack("<I", x)
|
||||
|
||||
|
||||
def dword_pad(s):
|
||||
if (len(s) % 4) == 0:
|
||||
return s
|
||||
return s + (b"P" * (4 - len(s) % 4))
|
||||
|
||||
|
||||
class NdrUniquePTR(object):
|
||||
"""Create a UNIQUE PTR around a given Ndr type"""
|
||||
def __init__(self, subcls):
|
||||
self.subcls = subcls
|
||||
|
||||
def pack(self, data):
|
||||
subpack = self.subcls.pack(data)
|
||||
if subpack is None:
|
||||
return pack_dword(0)
|
||||
return pack_dword(0x02020202) + subpack
|
||||
|
||||
def unpack(self, stream):
|
||||
ptr = NdrLong.unpack(stream)
|
||||
if not ptr:
|
||||
return None
|
||||
return self.subcls.unpack(stream)
|
||||
|
||||
def pack_in_struct(self, data, id):
|
||||
if data is None:
|
||||
return pack_dword(0), None
|
||||
subpack = self.subcls.pack(data)
|
||||
if subpack is None:
|
||||
return pack_dword(0), None
|
||||
return pack_dword(0x01010101 * (id + 1)), subpack
|
||||
|
||||
def unpack_in_struct(self, stream):
|
||||
ptr = NdrLong.unpack(stream)
|
||||
if not ptr:
|
||||
return 0, NdrUnpackNone
|
||||
return ptr, self.subcls
|
||||
|
||||
def parse(self, stream):
|
||||
data = stream.partial_unpack("<I")
|
||||
if data[0] == 0:
|
||||
return None
|
||||
return self.subcls.parse(stream)
|
||||
|
||||
def get_alignment(self):
|
||||
# 14.3.2 Alignment of Constructed Types
|
||||
# Pointer alignment is always modulo 4.
|
||||
return 4
|
||||
|
||||
class NdrUnpackNone(object):
|
||||
@classmethod
|
||||
def unpack(cls, stream):
|
||||
return None
|
||||
|
||||
class NdrRef(object):
|
||||
# TESTING
|
||||
def __init__(self, subcls):
|
||||
self.subcls = subcls
|
||||
|
||||
def unpack(self, stream):
|
||||
ptr = NdrLong.unpack(stream)
|
||||
if not ptr:
|
||||
raise ValueError("Ndr REF cannot be NULL")
|
||||
return self.subcls.unpack(stream)
|
||||
|
||||
class NdrFixedArray(object):
|
||||
def __init__(self, subcls, size):
|
||||
self.subcls = subcls
|
||||
self.size = size
|
||||
|
||||
def pack(self, data):
|
||||
data = list(data)
|
||||
assert len(data) == self.size
|
||||
return dword_pad(b"".join([self.subcls.pack(elt) for elt in data]))
|
||||
|
||||
|
||||
def unpack(self, stream):
|
||||
return [self.subcls.unpack(stream) for i in range(self.size)]
|
||||
|
||||
def get_alignment(self):
|
||||
return self.subcls.get_alignment()
|
||||
|
||||
|
||||
class NdrSID(object):
|
||||
@classmethod
|
||||
def pack(cls, psid):
|
||||
"""Pack a PSID
|
||||
|
||||
:param PSID psid:
|
||||
"""
|
||||
subcount = windows.winproxy.GetSidSubAuthorityCount(psid)
|
||||
size = windows.winproxy.GetLengthSid(psid)
|
||||
sid_data = windows.current_process.read_memory(psid.value, size)
|
||||
return pack_dword(subcount[0]) + dword_pad(sid_data)
|
||||
|
||||
@classmethod
|
||||
def unpack(cls, stream):
|
||||
"""Unpack a PSID, partial implementation that returns a :class:`str` and not a PSID"""
|
||||
subcount = NdrLong.unpack(stream)
|
||||
return stream.read(8 + (subcount * 4))
|
||||
|
||||
@classmethod
|
||||
def get_alignment(self):
|
||||
# Not sur, but it seems to contain an array of long
|
||||
return 4
|
||||
|
||||
class NdrVaryingCString(object):
|
||||
@classmethod
|
||||
def pack(cls, data):
|
||||
"""Pack string ``data``. append ``\\x00`` if not present at the end of the string"""
|
||||
if data is None:
|
||||
return None
|
||||
if not data.endswith('\x00'):
|
||||
data += '\x00'
|
||||
l = len(data)
|
||||
result = struct.pack("<2I", 0, l)
|
||||
result += data
|
||||
return dword_pad(result)
|
||||
|
||||
@classmethod
|
||||
def get_alignment(self):
|
||||
# Not sur, but size is on 4 bytes so...
|
||||
return 4
|
||||
|
||||
class NdrWString(object):
|
||||
@classmethod
|
||||
def pack(cls, data):
|
||||
"""Pack string ``data``. append ``\\x00`` if not present at the end of the string"""
|
||||
if data is None:
|
||||
return None
|
||||
if not data.endswith('\x00'):
|
||||
data += '\x00'
|
||||
data = data.encode("utf-16-le")
|
||||
l = (len(data) // 2)
|
||||
result = struct.pack("<3I", l, 0, l)
|
||||
result += data
|
||||
return dword_pad(result)
|
||||
|
||||
@classmethod
|
||||
def unpack(cls, stream):
|
||||
stream.align(4)
|
||||
size1, zero, size2 = stream.partial_unpack("<3I")
|
||||
assert size1 == size2
|
||||
assert zero == 0
|
||||
s = stream.read(size1 * 2)
|
||||
return s.decode("utf-16-le")
|
||||
|
||||
@classmethod
|
||||
def get_alignment(self):
|
||||
# Not sur, but size is on 4 bytes so...
|
||||
return 4
|
||||
|
||||
class NdrCString(object):
|
||||
@classmethod
|
||||
def pack(cls, data):
|
||||
"""Pack string ``data``. append ``\\x00`` if not present at the end of the string"""
|
||||
if data is None:
|
||||
return None
|
||||
if not data.endswith('\x00'):
|
||||
data += '\x00'
|
||||
l = len(data)
|
||||
result = struct.pack("<3I", l, 0, l)
|
||||
result += data
|
||||
return dword_pad(result)
|
||||
|
||||
@classmethod
|
||||
def get_alignment(self):
|
||||
# Not sur, but size is on 4 bytes so...
|
||||
return 4
|
||||
|
||||
# @classmethod
|
||||
# def unpack(self, stream):
|
||||
# maxcount, offset, count = stream.partial_unpack("<3I")
|
||||
# return maxcount, offset, count
|
||||
|
||||
NdrUniqueCString = NdrUniquePTR(NdrCString)
|
||||
NdrUniqueWString = NdrUniquePTR(NdrWString)
|
||||
|
||||
class NdrLong(object):
|
||||
@classmethod
|
||||
def pack(cls, data):
|
||||
return struct.pack("<I", data)
|
||||
|
||||
@classmethod
|
||||
def unpack(self, stream):
|
||||
stream.align(4)
|
||||
return stream.partial_unpack("<I")[0]
|
||||
|
||||
@classmethod
|
||||
def get_alignment(self):
|
||||
return 4
|
||||
|
||||
class NdrHyper(object):
|
||||
@classmethod
|
||||
def pack(cls, data):
|
||||
return struct.pack("<Q", data)
|
||||
|
||||
@classmethod
|
||||
def unpack(self, stream):
|
||||
stream.align(8)
|
||||
return stream.partial_unpack("<Q")[0]
|
||||
|
||||
@classmethod
|
||||
def get_alignment(self):
|
||||
return 8
|
||||
|
||||
class NdrShort(object):
|
||||
@classmethod
|
||||
def pack(cls, data):
|
||||
return struct.pack("<H", data)
|
||||
|
||||
@classmethod
|
||||
def unpack(self, stream):
|
||||
return stream.partial_unpack("<H")[0]
|
||||
|
||||
@classmethod
|
||||
def get_alignment(self):
|
||||
return 2
|
||||
|
||||
|
||||
class NdrByte(object):
|
||||
@classmethod
|
||||
def pack(self, data):
|
||||
return struct.pack("<B", data)
|
||||
|
||||
@classmethod
|
||||
def unpack(self, stream):
|
||||
return stream.partial_unpack("<B")[0]
|
||||
|
||||
@classmethod
|
||||
def get_alignment(self):
|
||||
return 1
|
||||
|
||||
|
||||
class NdrGuid(object):
|
||||
@classmethod
|
||||
def pack(cls, data):
|
||||
if not isinstance(data, gdef.IID):
|
||||
data = gdef.IID.from_string(data)
|
||||
return bytes(bytearray(data))
|
||||
|
||||
@classmethod
|
||||
def unpack(self, stream):
|
||||
rawguid = stream.partial_unpack("16s")[0]
|
||||
return gdef.IID.from_buffer_copy(rawguid)
|
||||
|
||||
@classmethod
|
||||
def get_alignment(self):
|
||||
return 1
|
||||
|
||||
|
||||
class NdrContextHandle(object):
|
||||
@classmethod
|
||||
def pack(cls, data):
|
||||
if not isinstance(data, gdef.IID):
|
||||
data = gdef.IID.from_string(data)
|
||||
return bytes(struct.pack("<I", 0) + bytearray(data))
|
||||
|
||||
@classmethod
|
||||
def unpack(self, stream):
|
||||
attributes, rawguid = stream.partial_unpack("<I16s")
|
||||
return gdef.IID.from_buffer_copy(rawguid)
|
||||
|
||||
@classmethod
|
||||
def get_alignment(self):
|
||||
return 4
|
||||
|
||||
|
||||
|
||||
class NdrStructure(object):
|
||||
"""a NDR structure that tries to respect the rules of pointer packing, this class should be subclassed with
|
||||
an attribute ``MEMBERS`` describing the members of the class
|
||||
"""
|
||||
@classmethod
|
||||
def pack(cls, data):
|
||||
"""Pack data into the struct, ``data`` size must equals the number of members in the structure"""
|
||||
if not (len(data) == len(cls.MEMBERS)):
|
||||
print("Size mistach:")
|
||||
print(" * data size = {0}".format(len(data)))
|
||||
print(" * members size = {0}".format(len(cls.MEMBERS)))
|
||||
print(" * data {0}".format(data))
|
||||
print(" * members = {0}".format(cls.MEMBERS))
|
||||
raise ValueError("NdrStructure packing number elements mismatch: structure has <{0}> members got <{1}>".format(len(cls.MEMBERS), len(data)))
|
||||
conformant_size = []
|
||||
res = []
|
||||
res_size = 0
|
||||
pointed = []
|
||||
outstream = NdrWriteStream()
|
||||
pointed_to_pack = []
|
||||
# pointedoutstream = NdrWriteStream()
|
||||
for i, (member, memberdata) in enumerate(zip(cls.MEMBERS, data)):
|
||||
if hasattr(member, "pack_in_struct"):
|
||||
x, y = member.pack_in_struct(memberdata, i)
|
||||
assert len(x) == 4, "Pointer should be size 4"
|
||||
# Write the pointer
|
||||
outstream.align(4)
|
||||
outstream.write(x)
|
||||
if y is not None:
|
||||
# Store the info to the pointed to pack
|
||||
pointed_to_pack.append((member.subcls.get_alignment(), y))
|
||||
# pointedoutstream.write(y)
|
||||
elif hasattr(member, "pack_conformant"):
|
||||
size, data = member.pack_conformant(memberdata)
|
||||
outstream.align(member.get_alignment())
|
||||
outstream.write(data)
|
||||
conformant_size.append(size)
|
||||
# res.append(data)
|
||||
# res_size += len(data)
|
||||
else:
|
||||
packed_member = member.pack(memberdata)
|
||||
outstream.align(member.get_alignment())
|
||||
outstream.write(packed_member)
|
||||
# Pack the pointed to the stream
|
||||
for alignement, pointed_data in pointed_to_pack:
|
||||
outstream.align(alignement)
|
||||
outstream.write(pointed_data)
|
||||
return dword_pad(b"".join(conformant_size)) + outstream.get_data()
|
||||
|
||||
@classmethod
|
||||
def unpack(cls, stream):
|
||||
"""Unpack the structure from the stream"""
|
||||
conformant_members = [hasattr(m, "pack_conformant") for m in cls.MEMBERS]
|
||||
is_conformant = any(conformant_members)
|
||||
assert(conformant_members.count(True) <= 1), "Unpack conformant struct with more that one conformant MEMBER not implem"
|
||||
data = []
|
||||
if is_conformant:
|
||||
conformant_size = NdrLong.unpack(stream)
|
||||
post_subcls = []
|
||||
for i, member in enumerate(cls.MEMBERS):
|
||||
if conformant_members[i]:
|
||||
data.append(member.unpack_conformant(stream, conformant_size))
|
||||
else:
|
||||
if hasattr(member, "unpack_in_struct"):
|
||||
# print("[{0}] Dereferenced unpacking".format(i))
|
||||
ptr, subcls = member.unpack_in_struct(stream)
|
||||
if not ptr:
|
||||
data.append(None)
|
||||
else:
|
||||
data.append(ptr)
|
||||
post_subcls.append((i, subcls))
|
||||
# print(post_subcls)
|
||||
else:
|
||||
data.append(member.unpack(stream))
|
||||
# print("Applying deref unpack")
|
||||
for i, entry in post_subcls:
|
||||
new_data = entry.unpack(stream)
|
||||
if getattr(entry, "post_unpack", None):
|
||||
new_data = entry.post_unpack(new_data)
|
||||
data[i] = new_data
|
||||
|
||||
return cls.post_unpack(data)
|
||||
|
||||
@classmethod
|
||||
def post_unpack(cls, data):
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def get_alignment(self):
|
||||
return max([x.get_alignment() for x in self.MEMBERS])
|
||||
|
||||
|
||||
|
||||
class NdrParameters(object):
|
||||
"""a class to pack NDR parameters together to performs RPC call, this class should be subclassed with
|
||||
an attribute ``MEMBERS`` describing the members of the class
|
||||
"""
|
||||
@classmethod
|
||||
def pack(cls, data):
|
||||
if not (len(data) == len(cls.MEMBERS)):
|
||||
print("Size mistach:")
|
||||
print(" * data size = {0}".format(len(data)))
|
||||
print(" * members size = {0}".format(len(cls.MEMBERS)))
|
||||
print(" * data {0}".format(data))
|
||||
print(" * members = {0}".format(cls.MEMBERS))
|
||||
raise ValueError("NdrParameters packing number elements mismatch: structure has <{0}> members got <{1}>".format(len(cls.MEMBERS), len(data)))
|
||||
|
||||
|
||||
outstream = NdrWriteStream()
|
||||
for (member, memberdata) in zip(cls.MEMBERS, data):
|
||||
alignment = member.get_alignment()
|
||||
outstream.align(alignment)
|
||||
packed_member = member.pack(memberdata)
|
||||
outstream.write(packed_member)
|
||||
return outstream.get_data()
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def unpack(cls, stream):
|
||||
res = []
|
||||
for member in cls.MEMBERS:
|
||||
unpacked_member = member.unpack(stream)
|
||||
res.append(unpacked_member)
|
||||
return res
|
||||
|
||||
def get_alignment(self):
|
||||
raise ValueError("NdrParameters should always be top type in NDR description")
|
||||
|
||||
|
||||
class NdrConformantArray(object):
|
||||
MEMBER_TYPE = None
|
||||
@classmethod
|
||||
def pack(cls, data):
|
||||
ndrsize = NdrLong.pack(len(data))
|
||||
return dword_pad(ndrsize + b"".join([cls.MEMBER_TYPE.pack(memberdata) for memberdata in data]))
|
||||
|
||||
@classmethod
|
||||
def pack_conformant(cls, data):
|
||||
ndrsize = NdrLong.pack(len(data))
|
||||
ndrdata = dword_pad(b"".join([cls.MEMBER_TYPE.pack(memberdata) for memberdata in data]))
|
||||
return ndrsize, ndrdata
|
||||
|
||||
@classmethod
|
||||
def unpack(cls, stream):
|
||||
nbelt = NdrLong.unpack(stream)
|
||||
result = cls.unpack_conformant(stream, nbelt)
|
||||
return cls._post_unpack(result)
|
||||
|
||||
@classmethod
|
||||
def _post_unpack(cls, result):
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def unpack_conformant(cls, stream, size):
|
||||
res = [cls.MEMBER_TYPE.unpack(stream) for i in range(size)]
|
||||
stream.align(4)
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def get_alignment(self):
|
||||
# TODO: test on array of Hyper
|
||||
return max(4, self.MEMBER_TYPE.get_alignment())
|
||||
|
||||
|
||||
class NdrConformantVaryingArrays(object):
|
||||
MEMBER_TYPE = None
|
||||
@classmethod
|
||||
def pack(cls, data):
|
||||
ndrsize = NdrLong.pack(len(data))
|
||||
offset = NdrLong.pack(0)
|
||||
return dword_pad(ndrsize + offset + ndrsize + b"".join([cls.MEMBER_TYPE.pack(memberdata) for memberdata in data]))
|
||||
|
||||
@classmethod
|
||||
def unpack(cls, stream):
|
||||
maxcount = NdrLong.unpack(stream)
|
||||
offset = NdrLong.unpack(stream)
|
||||
count = NdrLong.unpack(stream)
|
||||
assert(offset == 0)
|
||||
# assert(maxcount == count)
|
||||
|
||||
result = []
|
||||
post_subcls = []
|
||||
for i in range(count):
|
||||
member = cls.MEMBER_TYPE
|
||||
if hasattr(member, "unpack_in_struct"):
|
||||
ptr, subcls = member.unpack_in_struct(stream)
|
||||
if not ptr:
|
||||
result.append(None)
|
||||
else:
|
||||
result.append(ptr)
|
||||
post_subcls.append((i, subcls))
|
||||
else:
|
||||
data = member.unpack(stream)
|
||||
result.append(data)
|
||||
# Unpack pointers
|
||||
for i, entry in post_subcls:
|
||||
data = entry.unpack(stream)
|
||||
result[i] = data
|
||||
|
||||
return cls._post_unpack(result)
|
||||
|
||||
@classmethod
|
||||
def _post_unpack(cls, result):
|
||||
return result
|
||||
|
||||
def get_alignment(self):
|
||||
# TODO: test on array of Hyper
|
||||
return max(4, self.MEMBER_TYPE.get_alignment())
|
||||
|
||||
|
||||
class NdrWcharConformantVaryingArrays(NdrConformantVaryingArrays):
|
||||
MEMBER_TYPE = NdrShort
|
||||
|
||||
@classmethod
|
||||
def _post_unpack(self, result):
|
||||
return u"".join(unichr(c) for c in result)
|
||||
|
||||
class NdrCharConformantVaryingArrays(NdrConformantVaryingArrays):
|
||||
MEMBER_TYPE = NdrByte
|
||||
|
||||
class NdrHyperConformantVaryingArrays(NdrConformantVaryingArrays):
|
||||
MEMBER_TYPE = NdrHyper
|
||||
|
||||
class NdrHyperConformantArray(NdrConformantArray):
|
||||
MEMBER_TYPE = NdrHyper
|
||||
|
||||
class NdrLongConformantArray(NdrConformantArray):
|
||||
MEMBER_TYPE = NdrLong
|
||||
|
||||
class NdrShortConformantArray(NdrConformantArray):
|
||||
MEMBER_TYPE = NdrShort
|
||||
|
||||
class NdrByteConformantArray(NdrConformantArray):
|
||||
MEMBER_TYPE = NdrByte
|
||||
|
||||
@classmethod
|
||||
def _post_unpack(self, result):
|
||||
return bytearray(result)
|
||||
|
||||
class NdrWcharConformantArray(NdrConformantArray):
|
||||
MEMBER_TYPE = NdrShort
|
||||
|
||||
@classmethod
|
||||
def _post_unpack(self, result):
|
||||
return bytearray(result)
|
||||
|
||||
class NdrGuidConformantArray(NdrConformantArray):
|
||||
MEMBER_TYPE = NdrGuid
|
||||
|
||||
|
||||
class NdrStream(object):
|
||||
"""A stream of bytes used for NDR unpacking"""
|
||||
def __init__(self, data):
|
||||
self.fulldata = data
|
||||
self.data = data
|
||||
|
||||
def partial_unpack(self, format):
|
||||
size = struct.calcsize(format)
|
||||
toparse = self.data[:size]
|
||||
self.data = self.data[size:]
|
||||
return struct.unpack(format, toparse)
|
||||
|
||||
def read_aligned_dword(self, size):
|
||||
aligned_size = size
|
||||
if size % 4:
|
||||
aligned_size = size + (4 - (size % 4))
|
||||
retdata = self.data[:size]
|
||||
self.data = self.data[aligned_size:]
|
||||
return retdata
|
||||
|
||||
def read(self, size):
|
||||
data = self.data[:size]
|
||||
self.data = self.data[size:]
|
||||
if len(data) < size:
|
||||
raise ValueError("Could not read {0} from stream".format(size))
|
||||
return data
|
||||
|
||||
def align(self, size):
|
||||
"""Discard some bytes to align the remaining stream on ``size``"""
|
||||
|
||||
already_read = len(self.fulldata) - len(self.data)
|
||||
if already_read % size:
|
||||
# Realign
|
||||
size_to_align = (size - (already_read % size))
|
||||
self.data = self.data[size_to_align:]
|
||||
# print("align {0}: {1}".format(size, size_to_align))
|
||||
return size_to_align
|
||||
# print("align {0}: 0".format(size))
|
||||
return 0
|
||||
|
||||
class NdrWriteStream(object):
|
||||
def __init__(self):
|
||||
self.data_parts = []
|
||||
self.data_size = 0
|
||||
|
||||
def get_data(self):
|
||||
data = b"".join(self.data_parts)
|
||||
assert len(data) == self.data_size
|
||||
return data
|
||||
|
||||
def write(self, data):
|
||||
self.data_parts.append(data)
|
||||
self.data_size += len(data)
|
||||
return None
|
||||
|
||||
def align(self, alignement):
|
||||
if self.data_size % alignement == 0:
|
||||
return
|
||||
topadsize = (alignement) - (self.data_size % alignement)
|
||||
self.write(b"P" * topadsize)
|
||||
return
|
||||
|
||||
def make_parameters(types, name=None):
|
||||
class NdrCustomParameters(NdrParameters):
|
||||
MEMBERS = types
|
||||
return NdrCustomParameters
|
||||
|
||||
def make_structure(types, name=None):
|
||||
class NdrCustomStructure(NdrStructure):
|
||||
MEMBERS = types
|
||||
return NdrCustomStructure
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,331 @@
|
||||
import struct
|
||||
import ctypes
|
||||
from ctypes import byref
|
||||
import codecs
|
||||
import functools
|
||||
import threading
|
||||
|
||||
import windows
|
||||
import windows.native_exec.simple_x86 as x86
|
||||
import windows.native_exec.simple_x64 as x64
|
||||
from .generated_def.winstructs import *
|
||||
from windows.winobject import process
|
||||
from windows import winproxy
|
||||
from .winproxy import NeededParameter
|
||||
from .pycompat import int_types
|
||||
|
||||
# Special code for syswow64 process
|
||||
CS_32bits = 0x23
|
||||
CS_64bits = 0x33
|
||||
|
||||
# Allow to keep per-thread state of asm stub
|
||||
class ThreadState(threading.local):
|
||||
def __init__(self): # Called once per thread
|
||||
self.allocator = windows.native_exec.native_function.CustomAllocator()
|
||||
self.raw_call_per_function = {}
|
||||
self.current_original_args = None
|
||||
|
||||
thread_state = ThreadState()
|
||||
|
||||
|
||||
def generate_64bits_execution_stub_from_syswow(x64shellcode):
|
||||
"""shellcode must NOT end by a ret"""
|
||||
current_process = windows.current_process
|
||||
if not current_process.is_wow_64:
|
||||
raise ValueError("Calling generate_64bits_execution_stub_from_syswow from non-syswow process")
|
||||
|
||||
transition64 = x64.MultipleInstr()
|
||||
transition64 += x64.Call(":TOEXEC")
|
||||
transition64 += x64.Mov("RDX", "RAX")
|
||||
transition64 += x64.Shr("RDX", 32)
|
||||
transition64 += x64.Retf32() # 32 bits return addr
|
||||
transition64 += x64.Label(":TOEXEC")
|
||||
x64shellcodeaddr = thread_state.allocator.write_code(transition64.get_code() + x64shellcode)
|
||||
|
||||
transition = x86.MultipleInstr()
|
||||
transition += x86.Call(CS_64bits, x64shellcodeaddr)
|
||||
# Reset the SS segment selector.
|
||||
# We need to do that due to a bug in AMD CPUs with RETF & SS
|
||||
# https://github.com/hakril/PythonForWindows/issues/10
|
||||
# http://blog.rewolf.pl/blog/?p=1484
|
||||
transition += x86.Mov("ECX", "SS")
|
||||
transition += x86.Mov("SS", "ECX")
|
||||
transition += x86.Ret()
|
||||
|
||||
stubaddr = thread_state.allocator.write_code(transition.get_code())
|
||||
exec_stub = ctypes.CFUNCTYPE(ULONG64)(stubaddr)
|
||||
return exec_stub
|
||||
|
||||
def execute_64bits_code_from_syswow(x64shellcode):
|
||||
return generate_64bits_execution_stub_from_syswow(x64shellcode)()
|
||||
|
||||
def generate_syswow64_call(target, errcheck=None):
|
||||
nb_args = len(target.prototype._argtypes_)
|
||||
target_addr = get_syswow_ntdll_exports()[target.__name__]
|
||||
argument_buffer_len = (nb_args * 8)
|
||||
argument_buffer = thread_state.allocator.reserve_size(argument_buffer_len)
|
||||
alignement_information = thread_state.allocator.reserve_size(8)
|
||||
|
||||
nb_args_on_stack = max(nb_args - 4, 0)
|
||||
|
||||
code_64b = x64.MultipleInstr()
|
||||
# Save registers
|
||||
|
||||
code_64b += x64.Push('RBX')
|
||||
code_64b += x64.Push('RCX')
|
||||
code_64b += x64.Push('RDX')
|
||||
code_64b += x64.Push('RSI')
|
||||
code_64b += x64.Push('RDI')
|
||||
code_64b += x64.Push('R8')
|
||||
code_64b += x64.Push('R9')
|
||||
code_64b += x64.Push('R10')
|
||||
code_64b += x64.Push('R11')
|
||||
code_64b += x64.Push('R12')
|
||||
code_64b += x64.Push('R13')
|
||||
|
||||
# Alignment stuff :)
|
||||
code_64b += x64.Mov('RCX', 'RSP')
|
||||
code_64b += x64.And('RCX', 0x0f)
|
||||
code_64b += x64.Mov(x64.deref(alignement_information), 'RCX')
|
||||
code_64b += x64.Sub('RSP', 'RCX')
|
||||
# retrieve argument from the argument buffer
|
||||
if nb_args >= 1:
|
||||
code_64b += x64.Mov('RCX', x64.create_displacement(disp=argument_buffer))
|
||||
if nb_args >= 2:
|
||||
code_64b += x64.Mov('RDX', x64.create_displacement(disp=argument_buffer + (8 * 1)))
|
||||
if nb_args >= 3:
|
||||
code_64b += x64.Mov('R8', x64.create_displacement(disp=argument_buffer + (8 * 2)))
|
||||
if nb_args >= 4:
|
||||
code_64b += x64.Mov('R9', x64.create_displacement(disp=argument_buffer + (8 * 3)))
|
||||
for i in range(nb_args_on_stack):
|
||||
code_64b += x64.Mov('RAX', x64.create_displacement(disp=argument_buffer + 8 * (nb_args - 1 - i)))
|
||||
code_64b += x64.Push('RAX')
|
||||
# reserve space for register (calling convention)
|
||||
code_64b += x64.Push('R9')
|
||||
code_64b += x64.Push('R8')
|
||||
code_64b += x64.Push('RDX')
|
||||
code_64b += x64.Push('RCX')
|
||||
# Call
|
||||
code_64b += x64.Mov('R13', target_addr)
|
||||
code_64b += x64.Call('R13')
|
||||
# Realign stack :)
|
||||
code_64b += x64.Add('RSP', x64.deref(alignement_information))
|
||||
# Clean stack
|
||||
code_64b += x64.Add('RSP', (4 + nb_args_on_stack) * 8)
|
||||
code_64b += x64.Pop('R13')
|
||||
code_64b += x64.Pop('R12')
|
||||
code_64b += x64.Pop('R11')
|
||||
code_64b += x64.Pop('R10')
|
||||
code_64b += x64.Pop('R9')
|
||||
code_64b += x64.Pop('R8')
|
||||
code_64b += x64.Pop('RDI')
|
||||
code_64b += x64.Pop('RSI')
|
||||
code_64b += x64.Pop('RDX')
|
||||
code_64b += x64.Pop('RCX')
|
||||
code_64b += x64.Pop('RBX')
|
||||
code_64b += x64.Ret()
|
||||
return try_generate_stub_target(code_64b.get_code(), argument_buffer, target, errcheck=errcheck)
|
||||
|
||||
|
||||
def try_generate_stub_target(shellcode, argument_buffer, target, errcheck=None):
|
||||
if not windows.current_process.is_wow_64:
|
||||
raise ValueError("Calling execute_64bits_code_from_syswow from non-syswow process")
|
||||
native_caller = generate_64bits_execution_stub_from_syswow(shellcode)
|
||||
native_caller.errcheck = errcheck if errcheck is not None else target.errcheck
|
||||
# Generate the wrapper function that fill the argument_buffer
|
||||
expected_arguments_number = len(target.prototype._argtypes_)
|
||||
def wrapper(*args):
|
||||
if len(args) != expected_arguments_number:
|
||||
raise ValueError("{0} syswow accept {1} args ({2} given)".format(target.__name__, expected_arguments_number, len(args)))
|
||||
# Transform args (ctypes byref possibly) to int
|
||||
writable_args = []
|
||||
for i, value in enumerate(args):
|
||||
if not isinstance(value, int_types):
|
||||
try:
|
||||
value = ctypes.cast(value, ctypes.c_void_p).value
|
||||
except ctypes.ArgumentError as e:
|
||||
raise ctypes.ArgumentError("Argument {0}: wrong type <{1}>".format(i, type(value).__name__))
|
||||
writable_args.append(value)
|
||||
# Build buffer
|
||||
buffer = struct.pack("<" + "Q" * len(writable_args), *writable_args)
|
||||
ctypes.memmove(argument_buffer, buffer, len(buffer))
|
||||
# Copy origincal args in function, for errcheck if needed
|
||||
thread_state.current_original_args = args
|
||||
|
||||
return native_caller()
|
||||
wrapper.__name__ = "{0}<syswow64>".format(target.__name__,)
|
||||
wrapper.__doc__ = "This is a wrapper to {0} in 64b mode, it accept <{1}> args".format(target.__name__, expected_arguments_number)
|
||||
return wrapper
|
||||
|
||||
|
||||
def get_current_process_syswow_peb_addr():
|
||||
get_peb_64_code = x64.assemble("mov rax, gs:[0x60]; ret")
|
||||
return execute_64bits_code_from_syswow(get_peb_64_code)
|
||||
|
||||
def get_current_process_syswow_peb():
|
||||
current_process = windows.current_process
|
||||
|
||||
class CurrentProcessReadSyswow(process.Process):
|
||||
bitness = 64
|
||||
def _get_handle(self):
|
||||
return winproxy.OpenProcess(dwProcessId=current_process.pid)
|
||||
|
||||
def read_memory(self, addr, size):
|
||||
buffer_addr = ctypes.create_string_buffer(size)
|
||||
winproxy.NtWow64ReadVirtualMemory64(self.handle, addr, buffer_addr, size)
|
||||
return buffer_addr[:]
|
||||
peb_addr = get_current_process_syswow_peb_addr()
|
||||
return windows.winobject.process.RemotePEB64(peb_addr, CurrentProcessReadSyswow())
|
||||
|
||||
|
||||
class ReadSyswow64Process(process.Process):
|
||||
def __init__(self, target):
|
||||
self.target = target
|
||||
self._bitness = target.bitness
|
||||
|
||||
def _get_handle(self):
|
||||
return self.target.handle
|
||||
|
||||
def read_memory(self, addr, size):
|
||||
buffer_addr = ctypes.create_string_buffer(size)
|
||||
winproxy.NtWow64ReadVirtualMemory64(self.target.handle, addr, buffer_addr, size)
|
||||
return buffer_addr[:]
|
||||
|
||||
#read_string = process.Process.read_string
|
||||
|
||||
|
||||
def get_syswow_ntdll_exports():
|
||||
if get_syswow_ntdll_exports.value is not None:
|
||||
return get_syswow_ntdll_exports.value
|
||||
peb64 = get_current_process_syswow_peb()
|
||||
ntdll64 = [m for m in peb64.modules if m.name == "ntdll.dll"]
|
||||
if not ntdll64:
|
||||
raise ValueError("Could not find ntdll.dll in syswow peb")
|
||||
ntdll64 = ntdll64[0]
|
||||
exports = ntdll64.pe.exports
|
||||
get_syswow_ntdll_exports.value = exports
|
||||
return exports
|
||||
get_syswow_ntdll_exports.value = None
|
||||
|
||||
|
||||
class Syswow64ApiProxy(object):
|
||||
"""Create a python wrapper around a function"""
|
||||
def __init__(self, winproxy_function, errcheck=None):
|
||||
self.winproxy_function = winproxy_function
|
||||
self.errcheck = errcheck
|
||||
if winproxy_function is not None:
|
||||
self.params_name = [param[1] for param in winproxy_function.params]
|
||||
|
||||
def __call__(self, python_proxy):
|
||||
if not windows.winproxy.is_implemented(self.winproxy_function):
|
||||
return None
|
||||
|
||||
def force_resolution():
|
||||
if self.winproxy_function in thread_state.raw_call_per_function:
|
||||
return True
|
||||
try:
|
||||
stub = generate_syswow64_call(self.winproxy_function, errcheck=self.errcheck)
|
||||
thread_state.raw_call_per_function[self.winproxy_function] = stub
|
||||
except KeyError:
|
||||
raise windows.winproxy.ExportNotFound(self.winproxy_function.__name__, "SysWow[ntdll64]")
|
||||
|
||||
|
||||
def perform_call(*args):
|
||||
if len(self.params_name) != len(args):
|
||||
print("ERROR:")
|
||||
print("Expected params: {0}".format(self.params_name))
|
||||
print("Just Got params: {0}".format(args))
|
||||
raise ValueError("I do not have all parameters: how is that possible ?")
|
||||
for param_name, param_value in zip(self.params_name, args):
|
||||
if param_value is NeededParameter:
|
||||
raise TypeError("{0}: Missing Mandatory parameter <{1}>".format(self.winproxy_function.__name__, param_name))
|
||||
|
||||
if self.winproxy_function not in thread_state.raw_call_per_function:
|
||||
force_resolution()
|
||||
return thread_state.raw_call_per_function[self.winproxy_function](*args)
|
||||
|
||||
|
||||
setattr(python_proxy, "ctypes_function", perform_call)
|
||||
setattr(python_proxy, "force_resolution", force_resolution)
|
||||
return python_proxy
|
||||
|
||||
def ntquerysysteminformation_syswow64_error_check(result, func, args):
|
||||
args = thread_state.current_original_args
|
||||
if result == 0:
|
||||
return args
|
||||
# Ignore STATUS_INFO_LENGTH_MISMATCH if SystemInformation is None
|
||||
if result == STATUS_INFO_LENGTH_MISMATCH and not args[1]:
|
||||
return args
|
||||
raise winproxy.WinproxyError("NtQuerySystemInformation failed with NTStatus {0}".format(hex(result)))
|
||||
|
||||
@Syswow64ApiProxy(winproxy.NtQuerySystemInformation, errcheck=ntquerysysteminformation_syswow64_error_check)
|
||||
# @Syswow64ApiProxy(winproxy.NtQuerySystemInformation)
|
||||
def NtQuerySystemInformation_32_to_64(SystemInformationClass, SystemInformation=None, SystemInformationLength=0, ReturnLength=NeededParameter):
|
||||
if SystemInformation is not None and SystemInformationLength == 0:
|
||||
SystemInformationLength = ctypes.sizeof(SystemInformation)
|
||||
if SystemInformation is None:
|
||||
SystemInformation = 0
|
||||
return NtQuerySystemInformation_32_to_64.ctypes_function(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength)
|
||||
|
||||
|
||||
@Syswow64ApiProxy(winproxy.NtCreateThreadEx)
|
||||
def NtCreateThreadEx_32_to_64(ThreadHandle=None, DesiredAccess=0x1fffff, ObjectAttributes=0, ProcessHandle=NeededParameter, lpStartAddress=NeededParameter, lpParameter=NeededParameter, CreateSuspended=0, dwStackSize=0, Unknown1=0, Unknown2=0, Unknown3=0):
|
||||
if ThreadHandle is None:
|
||||
ThreadHandle = byref(HANDLE())
|
||||
return NtCreateThreadEx_32_to_64.ctypes_function(ThreadHandle, DesiredAccess, ObjectAttributes, ProcessHandle, lpStartAddress, lpParameter, CreateSuspended, dwStackSize, Unknown1, Unknown2, Unknown3)
|
||||
|
||||
|
||||
ProcessBasicInformation = 0
|
||||
@Syswow64ApiProxy(winproxy.NtQueryInformationProcess)
|
||||
def NtQueryInformationProcess_32_to_64(ProcessHandle, ProcessInformationClass=ProcessBasicInformation, ProcessInformation=NeededParameter, ProcessInformationLength=0, ReturnLength=None):
|
||||
if ProcessInformation is not None and ProcessInformationLength == 0:
|
||||
ProcessInformationLength = ctypes.sizeof(ProcessInformation)
|
||||
if type(ProcessInformation) == PROCESS_BASIC_INFORMATION:
|
||||
ProcessInformation = byref(ProcessInformation)
|
||||
if ReturnLength is None:
|
||||
ReturnLength = byref(ULONG())
|
||||
return NtQueryInformationProcess_32_to_64.ctypes_function(ProcessHandle, ProcessInformationClass, ProcessInformation, ProcessInformationLength, ReturnLength)
|
||||
|
||||
|
||||
@Syswow64ApiProxy(winproxy.NtQueryInformationThread)
|
||||
def NtQueryInformationThread_32_to_64(ThreadHandle, ThreadInformationClass, ThreadInformation, ThreadInformationLength=0, ReturnLength=None):
|
||||
if ReturnLength is None:
|
||||
ReturnLength = byref(ULONG())
|
||||
if ThreadInformation is not None and ThreadInformationLength == 0:
|
||||
ThreadInformationLength = ctypes.sizeof(ThreadInformation)
|
||||
return NtQueryInformationThread_32_to_64.ctypes_function(ThreadHandle, ThreadInformationClass, ThreadInformation, ThreadInformationLength, ReturnLength)
|
||||
|
||||
|
||||
|
||||
@Syswow64ApiProxy(winproxy.NtQueryVirtualMemory)
|
||||
def NtQueryVirtualMemory_32_to_64(ProcessHandle, BaseAddress, MemoryInformationClass, MemoryInformation=NeededParameter, MemoryInformationLength=0, ReturnLength=None):
|
||||
if ReturnLength is None:
|
||||
ReturnLength = byref(ULONG())
|
||||
if MemoryInformation is not None and MemoryInformationLength == 0:
|
||||
MemoryInformationLength = ctypes.sizeof(MemoryInformation)
|
||||
if isinstance(MemoryInformation, ctypes.Structure):
|
||||
MemoryInformation = byref(MemoryInformation)
|
||||
return NtQueryVirtualMemory_32_to_64.ctypes_function(ProcessHandle, BaseAddress, MemoryInformationClass, MemoryInformation, MemoryInformationLength, ReturnLength)
|
||||
|
||||
|
||||
@Syswow64ApiProxy(winproxy.NtProtectVirtualMemory)
|
||||
def NtProtectVirtualMemory_32_to_64(ProcessHandle, BaseAddress, NumberOfBytesToProtect, NewAccessProtection, OldAccessProtection=None):
|
||||
if OldAccessProtection is None:
|
||||
XOldAccessProtection = DWORD()
|
||||
OldAccessProtection = ctypes.addressof(XOldAccessProtection)
|
||||
return NtProtectVirtualMemory_32_to_64.ctypes_function(ProcessHandle, BaseAddress, NumberOfBytesToProtect, NewAccessProtection, OldAccessProtection)
|
||||
|
||||
|
||||
@Syswow64ApiProxy(winproxy.NtGetContextThread)
|
||||
def NtGetContextThread_32_to_64(hThread, lpContext):
|
||||
if type(lpContext) == windows.winobject.exception.ECONTEXT64:
|
||||
lpContext = byref(lpContext)
|
||||
return NtGetContextThread_32_to_64.ctypes_function(hThread, lpContext)
|
||||
|
||||
@Syswow64ApiProxy(winproxy.LdrLoadDll)
|
||||
def LdrLoadDll_32_to_64(PathToFile, Flags, ModuleFileName, ModuleHandle):
|
||||
return LdrLoadDll_32_to_64.ctypes_function(PathToFile, Flags, ModuleFileName, ModuleHandle)
|
||||
|
||||
@Syswow64ApiProxy(winproxy.NtSetContextThread)
|
||||
def NtSetContextThread_32_to_64(hThread, lpContext):
|
||||
return NtSetContextThread_32_to_64.ctypes_function(hThread, lpContext)
|
||||
@@ -0,0 +1,25 @@
|
||||
import windows
|
||||
import windows.generated_def as gdef
|
||||
from windows.utils import create_process, DisableWow64FsRedirection
|
||||
|
||||
|
||||
test_binary_name = "notepad.exe"
|
||||
DEFAULT_CREATION_FLAGS = gdef.CREATE_NEW_CONSOLE
|
||||
|
||||
if windows.system.bitness == 32:
|
||||
def pop_proc_32(dwCreationFlags=DEFAULT_CREATION_FLAGS):
|
||||
return create_process(r"C:\Windows\system32\{0}".format(test_binary_name).encode(), dwCreationFlags=dwCreationFlags, show_windows=True)
|
||||
|
||||
def pop_proc_64(dwCreationFlags=DEFAULT_CREATION_FLAGS):
|
||||
raise WindowsError("Cannot create calc64 in 32bits system")
|
||||
else:
|
||||
def pop_proc_32(dwCreationFlags=DEFAULT_CREATION_FLAGS):
|
||||
return create_process(r"C:\Windows\syswow64\{0}".format(test_binary_name).encode(), dwCreationFlags=dwCreationFlags, show_windows=True)
|
||||
|
||||
if windows.current_process.bitness == 32:
|
||||
def pop_proc_64(dwCreationFlags=DEFAULT_CREATION_FLAGS):
|
||||
with DisableWow64FsRedirection():
|
||||
return create_process(r"C:\Windows\system32\{0}".format(test_binary_name).encode(), dwCreationFlags=dwCreationFlags, show_windows=True)
|
||||
else:
|
||||
def pop_proc_64(dwCreationFlags=DEFAULT_CREATION_FLAGS):
|
||||
return create_process(r"C:\Windows\system32\{0}".format(test_binary_name).encode(), dwCreationFlags=dwCreationFlags, show_windows=True)
|
||||
@@ -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
|
||||
@@ -0,0 +1,181 @@
|
||||
import ctypes
|
||||
|
||||
import windows
|
||||
import windows.generated_def as gdef
|
||||
|
||||
from windows import utils
|
||||
|
||||
|
||||
def get_api_set_map_for_current_process(base):
|
||||
base = windows.current_process.peb.ApiSetMap
|
||||
version = windows.current_process.read_dword(base)
|
||||
if version not in API_SET_MAP_BY_VERSION:
|
||||
raise NotImplementedError("ApiSetMap version <{0}> not implemented, please contact me, I need a sample to implement it ;)")
|
||||
return API_SET_MAP_BY_VERSION[version](base)
|
||||
|
||||
|
||||
class ApiSetMap(object):
|
||||
"""The base class for the ApiSeMap
|
||||
(see `Runtime DLL name resolution: ApiSetSchema <https://blog.quarkslab.com/runtime-dll-name-resolution-apisetschema-part-ii.html>`_)
|
||||
"""
|
||||
version = None #: The version of the ApiSetMap
|
||||
|
||||
def __init__(self, base):
|
||||
self.base = base
|
||||
self.target = windows.current_process
|
||||
|
||||
# helpers
|
||||
def read_apiset_wstring(self, offset, length):
|
||||
return self.target.read_memory(self.base + offset, length).decode("utf-16")
|
||||
|
||||
# Low-level version-dependent parsing function
|
||||
def entries_array(self):
|
||||
raise NotImplementedError("Should be implemented by subclasses")
|
||||
|
||||
def get_entry_name(self, entry):
|
||||
raise NotImplementedError("Should be implemented by subclasses")
|
||||
|
||||
def get_entry_name_basicimpl(self, entry):
|
||||
return self.read_apiset_wstring(entry.NameOffset, entry.NameLength)
|
||||
|
||||
def values_for_entry(self, entry):
|
||||
raise NotImplementedError("Should be implemented by subclasses")
|
||||
|
||||
@utils.fixedpropety
|
||||
def apisetmap_dict(self):
|
||||
"""The apisetmap dll-mapping content extracted from memory as a :class:`dict`
|
||||
|
||||
``key -> value example``::
|
||||
|
||||
u'ext-ms-win-advapi32-encryptedfile-l1-1-1' -> u'advapi32.dll'
|
||||
"""
|
||||
res = {}
|
||||
for entry in self.entries_array():
|
||||
values = self.values_for_entry(entry)
|
||||
if not values:
|
||||
final_value = None
|
||||
else:
|
||||
final_value = values[-1]
|
||||
res[self.get_entry_name(entry)] = final_value
|
||||
return res
|
||||
|
||||
@utils.fixedpropety
|
||||
def resolution_dict(self):
|
||||
"""The :class:`dict` based on :obj:`apisetmap_dict` with only the part checked by ``Windows``.
|
||||
|
||||
``Windows`` does not care about what is after the last ``-``
|
||||
|
||||
``key -> value example``::
|
||||
|
||||
u'ext-ms-win-advapi32-encryptedfile-l1-1-' -> u'advapi32.dll'
|
||||
|
||||
"""
|
||||
res = {}
|
||||
for name, resolved_name in self.apisetmap_dict.items():
|
||||
# ApiSetResolveToHost does not care about last version + extension
|
||||
# It remove everything after the last '-'
|
||||
|
||||
# Possible to have no '-' ?
|
||||
try:
|
||||
cutname = name[:name.rindex("-") + 1]
|
||||
except ValueError as e:
|
||||
cutname = name
|
||||
res[cutname] = resolved_name
|
||||
return res
|
||||
|
||||
def resolve(self, dllname):
|
||||
"""The method used to resolve a DLL name using the ApiSetMap.
|
||||
The behavior should match the non-exported function ``ntdll!ApiSetResolveToHost``
|
||||
"""
|
||||
try:
|
||||
cutname = dllname[:dllname.rindex("-") + 1]
|
||||
except ValueError as e:
|
||||
return None
|
||||
return self.resolution_dict[cutname]
|
||||
|
||||
|
||||
|
||||
class ApiSetMapVersion2(ApiSetMap):
|
||||
"""Represent an ApiSetMap version-2"""
|
||||
version = 2 #: The version of the ApiSetMap
|
||||
|
||||
def namespace(self):
|
||||
return gdef.API_SET_NAMESPACE_ARRAY_V2.from_address(self.base)
|
||||
|
||||
def entries_array(self):
|
||||
namespace = self.namespace()
|
||||
array_addr = ctypes.addressof(namespace.Array)
|
||||
array_size = namespace.Count
|
||||
return (gdef.API_SET_NAMESPACE_ENTRY_V2 * array_size).from_address(array_addr)
|
||||
|
||||
get_entry_name = ApiSetMap.get_entry_name_basicimpl
|
||||
|
||||
def values_for_entry(self, entry):
|
||||
values_array_v2 = (gdef.API_SET_VALUE_ARRAY_V2).from_address(self.base + entry.DataOffset)
|
||||
array_size = values_array_v2.Count
|
||||
array_addr = ctypes.addressof(values_array_v2.Array)
|
||||
values_array = (gdef.API_SET_VALUE_ENTRY_V2 * array_size).from_address(array_addr)
|
||||
res = []
|
||||
for value in values_array:
|
||||
if value.ValueLength:
|
||||
v = self.read_apiset_wstring(value.ValueOffset, value.ValueLength)
|
||||
res.append(v)
|
||||
return res
|
||||
|
||||
|
||||
class ApiSetMapVersion4(ApiSetMap):
|
||||
"""Represent an ApiSetMap version-4"""
|
||||
version = 4 #: The version of the ApiSetMap
|
||||
|
||||
def namespace(self):
|
||||
return gdef.API_SET_NAMESPACE_ARRAY_V4.from_address(self.base)
|
||||
|
||||
def entries_array(self):
|
||||
namespace = self.namespace()
|
||||
array_addr = ctypes.addressof(namespace.Array)
|
||||
array_size = namespace.Count
|
||||
return (gdef.API_SET_NAMESPACE_ENTRY_V4 * array_size).from_address(array_addr)
|
||||
|
||||
get_entry_name = ApiSetMap.get_entry_name_basicimpl
|
||||
|
||||
def values_for_entry(self, entry):
|
||||
values_array_v2 = (gdef.API_SET_VALUE_ARRAY_V4).from_address(self.base + entry.DataOffset)
|
||||
array_size = values_array_v2.Count
|
||||
array_addr = ctypes.addressof(values_array_v2.Array)
|
||||
values_array = (gdef.API_SET_VALUE_ENTRY * array_size).from_address(array_addr)
|
||||
res = []
|
||||
for value in values_array:
|
||||
if value.ValueLength:
|
||||
v = self.read_apiset_wstring(value.ValueOffset, value.ValueLength)
|
||||
res.append(v)
|
||||
return res
|
||||
|
||||
class ApiSetMapVersion6(ApiSetMap):
|
||||
"""Represent an ApiSetMap version-6"""
|
||||
version = 6 #: The version of the ApiSetMap
|
||||
|
||||
def namespace(self):
|
||||
return gdef.API_SET_NAMESPACE_V6.from_address(self.base)
|
||||
|
||||
get_entry_name = ApiSetMap.get_entry_name_basicimpl
|
||||
|
||||
def entries_array(self):
|
||||
namespace = self.namespace()
|
||||
array_offset = namespace.EntryOffset
|
||||
array_size = namespace.Count
|
||||
return (gdef.API_SET_NAMESPACE_ENTRY_V6 * array_size).from_address(self.base + array_offset)
|
||||
|
||||
def values_for_entry(self, entry):
|
||||
values_array = (gdef.API_SET_VALUE_ENTRY * entry.ValueCount).from_address(self.base + entry.ValueOffset)
|
||||
res = []
|
||||
for value in values_array:
|
||||
if value.ValueLength:
|
||||
v = self.read_apiset_wstring(value.ValueOffset, value.ValueLength)
|
||||
res.append(v)
|
||||
return res
|
||||
|
||||
API_SET_MAP_BY_VERSION = {
|
||||
2: ApiSetMapVersion2,
|
||||
4: ApiSetMapVersion4,
|
||||
6: ApiSetMapVersion6,
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
import threading
|
||||
|
||||
import windows
|
||||
import windows.com
|
||||
from windows.com import COMImplementation
|
||||
from windows.generated_def.interfaces import (IBackgroundCopyManager, IEnumBackgroundCopyJobs, IBackgroundCopyJob,
|
||||
IBackgroundCopyCallback, IUnknown, IBackgroundCopyError, IEnumBackgroundCopyFiles,
|
||||
IBackgroundCopyFile)
|
||||
import windows.generated_def as gdef
|
||||
|
||||
BackgroundCopyManager = windows.com.IID.from_string("4991d34b-80a1-4291-83b6-3328366b9097")
|
||||
BackgroundCopyManager1_5 = windows.com.IID.from_string("f087771f-d74f-4c1a-bb8a-e16aca9124ea")
|
||||
BackgroundCopyManager2_0 = windows.com.IID.from_string("6d18ad12-bde3-4393-b311-099c346e6df9s")
|
||||
BackgroundCopyManager2_5 = windows.com.IID.from_string("03ca98d6-ff5d-49b8-abc6-03dd84127020")
|
||||
BackgroundCopyManager3_0 = windows.com.IID.from_string("659cdea7-489e-11d9-a9cd-000d56965251")
|
||||
|
||||
BITS_CLS_BY_VERSION = {
|
||||
(1,0): BackgroundCopyManager,
|
||||
(1,5): BackgroundCopyManager1_5,
|
||||
(2,0): BackgroundCopyManager2_0,
|
||||
(2,5): BackgroundCopyManager2_5,
|
||||
(3,0): BackgroundCopyManager3_0,
|
||||
}
|
||||
|
||||
|
||||
class BitsCopyCallback(COMImplementation):
|
||||
IMPLEMENT = IBackgroundCopyCallback
|
||||
|
||||
def JobError(self, this, job, error):
|
||||
return True
|
||||
|
||||
def JobTransferred(self, this, job):
|
||||
#copy_terminated.set()
|
||||
return True
|
||||
|
||||
def JobModification(self, job, reserved):
|
||||
return True
|
||||
|
||||
class BitsCopyCallbackSetEvent(BitsCopyCallback):
|
||||
def __init__(self, event):
|
||||
super(BitsCopyCallbackSetEvent, self).__init__()
|
||||
self.event = event
|
||||
|
||||
# With the current generated_def.interface design, the current
|
||||
# prototype is:
|
||||
# ctypes.WINFUNCTYPE(HRESULT, PVOID, PVOID)(4, "JobError")
|
||||
# How should I address that ?
|
||||
def JobError(self, this, job, error):
|
||||
job = BitsCopyJob(job)
|
||||
error = BitsCopyError(error)
|
||||
errcode, errctx = error.error
|
||||
print("Copy failed with error code <{0:#x}> (ctx={1})".format(errcode, errctx))
|
||||
print("see <https://msdn.microsoft.com/en-us/library/windows/desktop/aa362823(v=vs.85).aspx>")
|
||||
self.event.set()
|
||||
return True
|
||||
|
||||
def JobTransferred(self, this, job):
|
||||
self.event.set()
|
||||
return True
|
||||
|
||||
class BitsCopyManager(IBackgroundCopyManager):
|
||||
def get_jobs(self, flags=0):
|
||||
jobsenum = IEnumBackgroundCopyJobs()
|
||||
self.EnumJobs(flags, jobsenum)
|
||||
res = []
|
||||
nbretrieved = gdef.DWORD()
|
||||
while True:
|
||||
current = BitsCopyJob()
|
||||
jobsenum.Next(1, current, nbretrieved)
|
||||
if not nbretrieved.value:
|
||||
break
|
||||
res.append(current.promote())
|
||||
jobsenum.Release()
|
||||
return res
|
||||
|
||||
@property
|
||||
def jobs(self):
|
||||
return self.get_jobs()
|
||||
|
||||
def create(self, name, jobtype):
|
||||
myjob_uuid = windows.com.IID()
|
||||
newjob = BitsCopyJob()
|
||||
self.CreateJob(name, jobtype, myjob_uuid, newjob)
|
||||
return newjob.promote()
|
||||
|
||||
class BitsCopyJob(IBackgroundCopyJob):
|
||||
version = 1
|
||||
@property
|
||||
def owner(self):
|
||||
owner = gdef.LPWSTR()
|
||||
self.GetOwner(owner)
|
||||
data = owner.value
|
||||
windows.winproxy.CoTaskMemFree(owner)
|
||||
return data
|
||||
|
||||
|
||||
@property
|
||||
def iid(self):
|
||||
res = windows.com.IID()
|
||||
self.GetId(res)
|
||||
res.update_strid()
|
||||
return res
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
x = gdef.BG_JOB_STATE()
|
||||
self.GetState(x)
|
||||
return x.value
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
descr = gdef.LPWSTR()
|
||||
self.GetDisplayName(descr)
|
||||
data = descr.value
|
||||
windows.winproxy.CoTaskMemFree(descr)
|
||||
return data
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
descr = gdef.LPWSTR()
|
||||
self.GetDescription(descr)
|
||||
data = descr.value
|
||||
windows.winproxy.CoTaskMemFree(descr)
|
||||
return data
|
||||
|
||||
@property
|
||||
def files(self):
|
||||
enum = IEnumBackgroundCopyFiles()
|
||||
self.EnumFiles(enum)
|
||||
count = gdef.ULONG()
|
||||
enum.GetCount(count)
|
||||
if not count:
|
||||
return []
|
||||
res_size = gdef.ULONG()
|
||||
array = (BitsFile * count.value)()
|
||||
enum.Next(count.value, array, res_size)
|
||||
return array[:res_size.value]
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
res = gdef.BG_JOB_TYPE()
|
||||
self.GetType(res)
|
||||
return res.value
|
||||
|
||||
@property
|
||||
def priority(self):
|
||||
priority = gdef.BG_JOB_PRIORITY()
|
||||
self.GetPriority(priority)
|
||||
return priority.value
|
||||
|
||||
@property
|
||||
def minimum_retry_delay(self):
|
||||
retry_delay = gdef.ULONG()
|
||||
self.GetMinimumRetryDelay(retry_delay)
|
||||
return retry_delay.value
|
||||
|
||||
@property
|
||||
def proxy_settings(self):
|
||||
ProxyUsage = gdef.BG_JOB_PROXY_USAGE()
|
||||
ProxyList = gdef.LPWSTR()
|
||||
ProxyBypassList = gdef.LPWSTR()
|
||||
self.GetProxySettings(ProxyUsage, ProxyList, ProxyBypassList)
|
||||
result = ProxyUsage.value, ProxyList.value, ProxyBypassList.value
|
||||
windows.winproxy.CoTaskMemFree(ProxyList)
|
||||
windows.winproxy.CoTaskMemFree(ProxyBypassList)
|
||||
return result
|
||||
|
||||
@property
|
||||
def times(self):
|
||||
res = gdef.BG_JOB_TIMES()
|
||||
self.GetTimes(res)
|
||||
return res
|
||||
|
||||
|
||||
def wait(self):
|
||||
if self.state.value == gdef.BG_JOB_STATE_SUSPENDED:
|
||||
raise ValueError("Cannot wait a BG_JOB_STATE_SUSPENDED job")
|
||||
event = threading.Event()
|
||||
callback_event = BitsCopyCallbackSetEvent(event)
|
||||
self.SetNotifyInterface(callback_event)
|
||||
self.SetNotifyFlags(1 | 2) # BG_NOTIFY_JOB_TRANSFERRED | BG_NOTIFY_JOB_ERROR
|
||||
event.wait()
|
||||
return True
|
||||
|
||||
|
||||
def promote(self):
|
||||
try:
|
||||
return self.query(BitsCopyJob2)
|
||||
except WindowsError as e:
|
||||
return self
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<{0} iid="{1}" at {2:#08x}>'.format(type(self).__name__, self.iid, id(self))
|
||||
|
||||
|
||||
class BitsCopyJob2(gdef.IBackgroundCopyJob2, BitsCopyJob):
|
||||
version = 2
|
||||
@property
|
||||
def notify_cmdline(self):
|
||||
path = gdef.LPWSTR()
|
||||
params = gdef.LPWSTR()
|
||||
self.GetNotifyCmdLine(path, params)
|
||||
strpath, strparams = path.value, params.value
|
||||
windows.winproxy.CoTaskMemFree(path)
|
||||
windows.winproxy.CoTaskMemFree(params)
|
||||
return strpath, strparams
|
||||
|
||||
|
||||
class BitsFile(IBackgroundCopyFile):
|
||||
version = 1
|
||||
@property
|
||||
def local_name(self):
|
||||
name = gdef.LPWSTR()
|
||||
self.GetLocalName(name)
|
||||
data = name.value
|
||||
windows.winproxy.CoTaskMemFree(name)
|
||||
return data
|
||||
|
||||
@property
|
||||
def remote_name(self):
|
||||
name = gdef.LPWSTR()
|
||||
self.GetRemoteName(name)
|
||||
data = name.value
|
||||
windows.winproxy.CoTaskMemFree(name)
|
||||
return data
|
||||
|
||||
@property
|
||||
def progress(self):
|
||||
progress = gdef.BG_FILE_PROGRESS()
|
||||
self.GetProgress(progress)
|
||||
return progress
|
||||
|
||||
def promote(self):
|
||||
try:
|
||||
return self.query(BitsFile3)
|
||||
except WindowsError as e:
|
||||
return self
|
||||
|
||||
class BitsFile3(gdef.IBackgroundCopyFile3, BitsFile):
|
||||
version = 3
|
||||
@property
|
||||
def temporary_name(self):
|
||||
name = gdef.LPWSTR()
|
||||
self.GetTemporaryName(name)
|
||||
data = name.value
|
||||
windows.winproxy.CoTaskMemFree(name)
|
||||
return data
|
||||
|
||||
class BitsCopyError(IBackgroundCopyError):
|
||||
@property
|
||||
def error(self):
|
||||
err_ctx = gdef.BG_ERROR_CONTEXT()
|
||||
err = gdef.HRESULT()
|
||||
self.GetError(err_ctx, err)
|
||||
return (err.value & 0xffffffff, err_ctx)
|
||||
|
||||
|
||||
def create_manager(version=(3,0)):
|
||||
windows.com.init()
|
||||
clsid = BITS_CLS_BY_VERSION[version]
|
||||
manager = BitsCopyManager()
|
||||
windows.com.create_instance(clsid, manager)
|
||||
return manager
|
||||
@@ -0,0 +1,515 @@
|
||||
import ctypes
|
||||
import itertools
|
||||
|
||||
import windows
|
||||
from windows import winproxy
|
||||
import windows.generated_def as gdef
|
||||
from windows.security import SecurityDescriptor
|
||||
from windows.utils import fixedproperty
|
||||
|
||||
|
||||
class DeviceManager(object):
|
||||
"""Represent the device manager"""
|
||||
|
||||
|
||||
@property
|
||||
def classes(self):
|
||||
"""The list of installed device classes.
|
||||
|
||||
:return: [:class:`DeviceClass`] -- A list of :class:`DeviceClass`
|
||||
"""
|
||||
return list(self._classes_generator())
|
||||
|
||||
def _classes_generator(self):
|
||||
for index in itertools.count():
|
||||
try:
|
||||
yield self._enumerate_classes(index, 0)
|
||||
except WindowsError as e:
|
||||
if e.winerror == gdef.CR_NO_SUCH_VALUE:
|
||||
break
|
||||
# Some index values might represent list entries containing invalid class data,
|
||||
# in which case the function returns CR_INVALID_DATA.
|
||||
# This return value can be ignored.
|
||||
if e.winerror == gdef.CR_INVALID_DATA:
|
||||
continue
|
||||
raise
|
||||
|
||||
|
||||
def _enumerate_classes(self, index, flags=0):
|
||||
res = DeviceClass()
|
||||
x = winproxy.CM_Enumerate_Classes(index, res, flags)
|
||||
return res
|
||||
|
||||
|
||||
class DeviceClass(gdef.GUID):
|
||||
"""A Device class, which is mainly a :class:`GUID` with additional attributes"""
|
||||
def __init__(self):
|
||||
# Bypass GUID __init__ that is not revelant here
|
||||
pass
|
||||
|
||||
@fixedproperty
|
||||
def name(self):
|
||||
"""The name of the device class"""
|
||||
return self._get_device_class_name()
|
||||
|
||||
@property
|
||||
def devices(self):
|
||||
"""The set of devices of the current class.
|
||||
|
||||
:type: :class:`DeviceInformationSet`
|
||||
"""
|
||||
return self.enumerate_devices()
|
||||
|
||||
def enumerate_devices(self, flags=0):
|
||||
handle = winproxy.SetupDiGetClassDevsA(self, Flags=flags)
|
||||
return DeviceInformationSet(handle)
|
||||
|
||||
def _get_device_class_name(self):
|
||||
name = ctypes.create_string_buffer(gdef.MAX_CLASS_NAME_LEN)
|
||||
winproxy.SetupDiClassNameFromGuidA(self, name)
|
||||
return name.value
|
||||
|
||||
def __repr__(self):
|
||||
guid_cls = self.to_string()
|
||||
return """<{0} name="{1}" guid={2}>""".format(type(self).__name__, self.name, guid_cls)
|
||||
|
||||
__str__ = __repr__ # Overwrite default GUID str
|
||||
|
||||
class DeviceInformationSet(gdef.HDEVINFO):
|
||||
"""A device instances, can be itered to retrieve the underliyings :class:`DeviceInstance`"""
|
||||
|
||||
def all_device_infos(self):
|
||||
for index in itertools.count():
|
||||
try:
|
||||
yield self.enum_device_info(index)
|
||||
except WindowsError as e:
|
||||
if e.winerror == gdef.ERROR_NO_MORE_ITEMS:
|
||||
return
|
||||
raise
|
||||
|
||||
__iter__ = all_device_infos
|
||||
|
||||
def enum_device_info(self, index):
|
||||
res = DeviceInstance(self)
|
||||
res.cbSize = ctypes.sizeof(res)
|
||||
winproxy.SetupDiEnumDeviceInfo(self, index, res)
|
||||
return res
|
||||
|
||||
def enum_device_interface(self, index):
|
||||
"""Not Implemented Yet"""
|
||||
raise NotImplementedError("enum_device_interface")
|
||||
|
||||
def all(self):
|
||||
return list(self)
|
||||
|
||||
|
||||
class DeviceInstance(gdef.SP_DEVINFO_DATA):
|
||||
"""An instance of a Device.
|
||||
|
||||
The properties are from the page https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdigetdeviceregistrypropertya#spdrp_address
|
||||
"""
|
||||
def __init__(self, information_set=None):
|
||||
self.information_set = information_set
|
||||
|
||||
# make a .device_class ? that return the DeviceClass ased in ClassGuid ?
|
||||
def get_property(self, property):
|
||||
datatype = gdef.DWORD()
|
||||
buffer_size = 0x1000
|
||||
buffer = windows.utils.BUFFER(gdef.BYTE, nbelt=buffer_size)()
|
||||
required_size = gdef.DWORD()
|
||||
# Registry parsing code expect W stuff, so use W function
|
||||
try:
|
||||
winproxy.SetupDiGetDeviceRegistryPropertyW(self.information_set, self, property, datatype, buffer.cast(gdef.LPBYTE), buffer_size, required_size)
|
||||
except WindowsError as e:
|
||||
if e.winerror == gdef.ERROR_INVALID_DATA:
|
||||
return None
|
||||
raise
|
||||
# PropertyRegDataType
|
||||
# A pointer to a variable that receives the data type of the property
|
||||
# that is being retrieved.
|
||||
# This is one of the standard registry data types
|
||||
# Look like its registry based, so use the registry decoders :)
|
||||
return windows.winobject.registry.decode_registry_buffer(datatype.value, buffer, required_size.value)
|
||||
|
||||
|
||||
def _generate_property_getter(prop):
|
||||
def getter(self):
|
||||
return self.get_property(prop)
|
||||
return property(getter)
|
||||
|
||||
name = _generate_property_getter(gdef.SPDRP_FRIENDLYNAME)
|
||||
"""The name of the device"""
|
||||
description = _generate_property_getter(gdef.SPDRP_DEVICEDESC)
|
||||
"""The description of the device"""
|
||||
hardware_id = _generate_property_getter(gdef.SPDRP_HARDWAREID)
|
||||
"""The list of hardware IDs for the device.
|
||||
(https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdigetdeviceregistrypropertya#spdrp_hardwareid)
|
||||
"""
|
||||
enumerator_name = _generate_property_getter(gdef.SPDRP_ENUMERATOR_NAME)
|
||||
"""The enumerator name of the devices
|
||||
(https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdigetdeviceregistrypropertya#spdrp_enumerator_name)
|
||||
"""
|
||||
driver = _generate_property_getter(gdef.SPDRP_DRIVER)
|
||||
"""The driver of the device
|
||||
https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdigetdeviceregistrypropertya#spdrp_driver
|
||||
"""
|
||||
# Map on Device type ?
|
||||
# https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/specifying-device-types
|
||||
type = _generate_property_getter(gdef.SPDRP_DEVTYPE)
|
||||
"""The type of device
|
||||
(https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/specifying-device-types)
|
||||
"""
|
||||
upper_filters = _generate_property_getter(gdef.SPDRP_UPPERFILTERS)
|
||||
"""A list of string that contains the names of a device's upper filter drivers."""
|
||||
lower_filters = _generate_property_getter(gdef.SPDRP_LOWERFILTERS)
|
||||
"""A list of string that contains the names of a device's lower filter drivers."""
|
||||
raw_security_descriptor = _generate_property_getter(gdef.SPDRP_SECURITY)
|
||||
"""The raw (binary) security descriptor of the device"""
|
||||
# I would prefer to use the security_descriptor sddl
|
||||
# ssdl = _generate_property_getter(gdef.SPDRP_SECURITY_SDS)
|
||||
service_name = _generate_property_getter(gdef.SPDRP_SERVICE)
|
||||
"""The name of the service for the device
|
||||
(https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdigetdeviceregistrypropertya#spdrp_service)
|
||||
"""
|
||||
manufacturer = _generate_property_getter(gdef.SPDRP_MFG)
|
||||
"""The name of the device manufacturer."""
|
||||
location_information = _generate_property_getter(gdef.SPDRP_LOCATION_INFORMATION)
|
||||
"""The hardware location of a device."""
|
||||
location_paths = _generate_property_getter(gdef.SPDRP_LOCATION_PATHS)
|
||||
"""A list of strings that represents the location of the device in the device tree."""
|
||||
# Looks like it can raise ERROR_NO_SUCH_DEVINST
|
||||
# install_date = _generate_property_getter(gdef.SPDRP_INSTALL_STATE)
|
||||
capabilites = _generate_property_getter(gdef.SPDRP_CAPABILITIES)
|
||||
"""The device capabilites
|
||||
(https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdigetdeviceregistrypropertya#spdrp_capabilities)
|
||||
"""
|
||||
bus_type = _generate_property_getter(gdef.SPDRP_BUSTYPEGUID)
|
||||
"""The function retrieves the GUID for the device's bus type."""
|
||||
bus_number = _generate_property_getter(gdef.SPDRP_BUSNUMBER)
|
||||
"""The device's bus number."""
|
||||
address = _generate_property_getter(gdef.SPDRP_ADDRESS)
|
||||
"""The device's address."""
|
||||
ui_number = _generate_property_getter(gdef.SPDRP_UI_NUMBER)
|
||||
"""Retrieves a DWORD value set to the value of the UINumber member of the device's"""
|
||||
ui_number_desc_format = _generate_property_getter(gdef.SPDRP_UI_NUMBER_DESC_FORMAT)
|
||||
|
||||
# Getter with special error handling
|
||||
@property
|
||||
def device_object_name(self):
|
||||
"""The function retrieves a string that contains the name that is associated with the device's PDO."""
|
||||
try:
|
||||
return self.get_property(gdef.SPDRP_PHYSICAL_DEVICE_OBJECT_NAME)
|
||||
except WindowsError as e:
|
||||
if e.winerror not in (gdef.ERROR_INVALID_DATA, gdef.ERROR_NO_SUCH_DEVINST):
|
||||
raise
|
||||
|
||||
|
||||
# https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/hardware-resources
|
||||
# Explanation of types:
|
||||
# - https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/hardware-resources#logical-configuration-types-for-resource-requirements-lists
|
||||
def get_first_logical_configuration(self, type):
|
||||
res = LogicalConfiguration()
|
||||
try:
|
||||
winproxy.CM_Get_First_Log_Conf(res, self.DevInst, type)
|
||||
except WindowsError as e:
|
||||
if e.winerror == gdef.CR_CALL_NOT_IMPLEMENTED:
|
||||
e.strerror += " (Cannot be called from Wow64 process since Win8)"
|
||||
raise
|
||||
return res
|
||||
|
||||
def get_next_logical_configuration(self, logconf):
|
||||
res = gdef.HANDLE(0)
|
||||
winproxy.CM_Get_Next_Log_Conf(res, logconf)
|
||||
return res
|
||||
|
||||
def _logical_configuration_generator(self, type):
|
||||
x = self.get_first_logical_configuration(type)
|
||||
while x:
|
||||
yield x
|
||||
try:
|
||||
x = self.get_next_logical_configuration(x)
|
||||
except WindowsError as e:
|
||||
if e.winerror == gdef.CR_NO_MORE_LOG_CONF:
|
||||
return
|
||||
raise
|
||||
|
||||
def get_logical_configuration(self, type):
|
||||
return list(self._logical_configuration_generator(type))
|
||||
|
||||
|
||||
# Allocated Configuration
|
||||
# From https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/hardware-resources#logical-configuration-types-for-resource-lists
|
||||
# A resource list identifying resources currently in use by a device instance.
|
||||
# !!! Only one allocated configuration can exist for each device instance.
|
||||
@property
|
||||
def allocated_configuration(self):
|
||||
"""The allocated configuration of the device.
|
||||
(https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/hardware-resources#logical-configuration-types-for-resource-lists)
|
||||
|
||||
:type: :class:`LogicalConfiguration`
|
||||
"""
|
||||
|
||||
allocconfs = self.get_logical_configuration(gdef.ALLOC_LOG_CONF)
|
||||
if not allocconfs:
|
||||
return allocconfs
|
||||
assert len(allocconfs) == 1 # Only one allocated configuration can exist for each device instance.
|
||||
return allocconfs[0]
|
||||
|
||||
# Boot Configuration
|
||||
# From https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/hardware-resources#logical-configuration-types-for-resource-lists
|
||||
# A resource list identifying the resources assigned to a device instance when the system is booted
|
||||
# Only one boot configuration can exist for each device instance.
|
||||
|
||||
@property
|
||||
def boot_configuration(self):
|
||||
"""The boot configuration of the device.
|
||||
(https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/hardware-resources#logical-configuration-types-for-resource-lists)
|
||||
|
||||
:type: :class:`LogicalConfiguration`
|
||||
"""
|
||||
bootconfs = self.get_logical_configuration(gdef.BOOT_LOG_CONF)
|
||||
if not bootconfs:
|
||||
return bootconfs
|
||||
assert len(bootconfs) == 1 # Only one boot configuration can exist for each device instance.
|
||||
return bootconfs[0]
|
||||
|
||||
|
||||
|
||||
# Make properties for Each type of logical configuration ?
|
||||
|
||||
# 'advanced' attributes extrapolated from properties
|
||||
@property
|
||||
def security_descriptor(self):
|
||||
"""The security descriptor of the device.
|
||||
|
||||
:type: :class:`~windows.security.SecurityDescriptor`
|
||||
"""
|
||||
|
||||
return SecurityDescriptor.from_binary(self.raw_security_descriptor)
|
||||
|
||||
def __repr__(self):
|
||||
return """<{0} "{1}" (id={2})>""".format(type(self).__name__, self.description, self.DevInst)
|
||||
|
||||
|
||||
class LogicalConfiguration(gdef.HANDLE):
|
||||
"""Logical Configuration of a Device instance"""
|
||||
|
||||
def get_next_resource_descriptor(self, resource, resdes=None):
|
||||
if resdes is None:
|
||||
# Using logical-conf as resdes will retrieve the first one
|
||||
# https://docs.microsoft.com/en-us/windows/win32/api/cfgmgr32/nf-cfgmgr32-cm_get_next_res_des#remarks
|
||||
resdes = self
|
||||
resid = None
|
||||
if resource == gdef.ResType_All:
|
||||
resid = gdef.RESOURCEID()
|
||||
res = gdef.HANDLE()
|
||||
winproxy.CM_Get_Next_Res_Des(res, resdes, resource, resid, 0)
|
||||
resdes_type = resid.value if resid is not None else resource
|
||||
return ResourceDescriptor.from_handle_and_type(res.value, resdes_type)
|
||||
|
||||
def get_resources_for_type(self, type):
|
||||
try:
|
||||
current = self.get_next_resource_descriptor(type)
|
||||
yield current
|
||||
while True:
|
||||
current = self.get_next_resource_descriptor(type, current)
|
||||
yield current
|
||||
except WindowsError as e:
|
||||
if e.winerror == gdef.CR_NO_MORE_RES_DES:
|
||||
return
|
||||
raise
|
||||
|
||||
@property
|
||||
def resources(self):
|
||||
"""The list of resources in the current logical configuration
|
||||
|
||||
:type: [:class:`ResourceDescriptor`] -- A list of [:class:`ResourceDescriptor`]
|
||||
"""
|
||||
return list(self.get_resources_for_type(gdef.ResType_All))
|
||||
|
||||
def __repr__(self):
|
||||
return "<{0}>".format(type(self).__name__)
|
||||
|
||||
|
||||
ResType_Mapper = gdef.FlagMapper(
|
||||
gdef.ResType_None,
|
||||
gdef.ResType_Mem,
|
||||
gdef.ResType_IO,
|
||||
gdef.ResType_DMA,
|
||||
gdef.ResType_IRQ,
|
||||
gdef.ResType_BusNumber,
|
||||
gdef.ResType_MemLarge,
|
||||
gdef.ResType_ClassSpecific,
|
||||
gdef.ResType_DevicePrivate,
|
||||
gdef.ResType_MfCardConfig,
|
||||
gdef.ResType_PcCardConfig,
|
||||
|
||||
)
|
||||
|
||||
class ResourceDescriptor(gdef.HANDLE):
|
||||
"""Describe a resource allocated or reserved by a device instance.
|
||||
This class is a base class, all resources returned by :class:`LogicalConfiguration` should be one of the following:
|
||||
|
||||
* :class:`ResourceNoType`
|
||||
* :class:`MemoryResource`
|
||||
* :class:`IoResource`
|
||||
* :class:`DmaResource`
|
||||
* :class:`IrqResource`
|
||||
* :class:`BusNumberResource`
|
||||
* :class:`MemLargeResource`
|
||||
* :class:`ClassSpecificResource`
|
||||
* :class:`DevicePrivateResource`
|
||||
* :class:`MfCardConfigResource`
|
||||
* :class:`PcCardConfigResource`
|
||||
"""
|
||||
SUBCLASSES = {}
|
||||
|
||||
def __init__(self, handle, type):
|
||||
super(ResourceDescriptor, self).__init__(handle)
|
||||
self.type = ResType_Mapper[type]
|
||||
|
||||
@classmethod
|
||||
def from_handle_and_type(cls, handle, type):
|
||||
ecls = cls.SUBCLASSES[type]
|
||||
return ecls(handle, type)
|
||||
|
||||
@property
|
||||
def rawdata(self):
|
||||
"""The raw data describing the resource"""
|
||||
data_size = gdef.ULONG()
|
||||
winproxy.CM_Get_Res_Des_Data_Size(data_size, self)
|
||||
if not self:
|
||||
return None
|
||||
data_size = data_size.value
|
||||
buffer = ctypes.create_string_buffer(data_size)
|
||||
winproxy.CM_Get_Res_Des_Data(self, buffer, data_size)
|
||||
return bytearray(buffer[:data_size])
|
||||
|
||||
def __repr__(self):
|
||||
return "<{0} type={1!r}>".format(type(self).__name__, self.type)
|
||||
|
||||
|
||||
class ResourceDescriptorWithHeader(ResourceDescriptor):
|
||||
# Assert the header is the first field
|
||||
@property
|
||||
def header_type(self):
|
||||
# Type of first field
|
||||
return self.DATA_TYPE._fields_[0][1]
|
||||
|
||||
@property
|
||||
def header(self):
|
||||
return self.header_type.from_buffer(self.rawdata)
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return None
|
||||
|
||||
class ResourceDescriptorWithHeaderAndRanges(ResourceDescriptorWithHeader):
|
||||
def count_field_name(self):
|
||||
# Assert (manyally checked) that the first field of the
|
||||
# header is a field containing the size of the data array
|
||||
# Return name of the first field of the header
|
||||
return self.header_type._fields_[0][0]
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
count_field_name = self.count_field_name()
|
||||
count = getattr(self.header, count_field_name)
|
||||
# No entry:
|
||||
if not count:
|
||||
return []
|
||||
raise NotImplementedError("Resource descriptor with non-zero entry in range array")
|
||||
|
||||
|
||||
class ResourceNoType(ResourceDescriptor):
|
||||
@property
|
||||
def data(self):
|
||||
return self.rawdata
|
||||
|
||||
class MemoryResource(ResourceDescriptorWithHeaderAndRanges):
|
||||
"""A resource of type MEM_RESOURCE"""
|
||||
DATA_TYPE = gdef.MEM_RESOURCE
|
||||
|
||||
def __str__(self):
|
||||
return "<{0} : [{1:#016x}-{2:#016x}]>".format(type(self).__name__, self.header.MD_Alloc_Base, self.header.MD_Alloc_End)
|
||||
|
||||
class IoResource(ResourceDescriptorWithHeaderAndRanges):
|
||||
"""A resource of type IO_RESOURCE"""
|
||||
DATA_TYPE = gdef.IO_RESOURCE
|
||||
|
||||
def __str__(self):
|
||||
return "<{0} : [{1:#016x}-{2:#016x}]>".format(type(self).__name__, self.header.IOD_Alloc_Base, self.header.IOD_Alloc_End)
|
||||
|
||||
class DmaResource(ResourceDescriptorWithHeaderAndRanges):
|
||||
"""A resource of type DMA_RESOURCE"""
|
||||
DATA_TYPE = gdef.DMA_RESOURCE
|
||||
|
||||
def __str__(self):
|
||||
return "<{0} : [{1:#016x}]>".format(type(self).__name__, self.header.DD_Alloc_Chan)
|
||||
|
||||
|
||||
class IrqResource(ResourceDescriptorWithHeaderAndRanges):
|
||||
"""A resource of type IRQ_RESOURCE"""
|
||||
# 32/64 based on current process bitness
|
||||
# Cross bitness cannot be implemented as >=Win8 block it
|
||||
DATA_TYPE = gdef.IRQ_RESOURCE
|
||||
|
||||
def __str__(self):
|
||||
return "<{0} : [{1:#016x}]>".format(type(self).__name__, self.header.IRQD_Alloc_Num)
|
||||
|
||||
|
||||
class BusNumberResource(ResourceDescriptorWithHeaderAndRanges):
|
||||
"""A resource of type BUSNUMBER_RESOURCE"""
|
||||
DATA_TYPE = gdef.BUSNUMBER_RESOURCE
|
||||
|
||||
def __str__(self):
|
||||
return "<{0} : [{1:#016x}-{2:#016x}]>".format(type(self).__name__, self.header.BUSD_Alloc_Base, self.header.BUSD_Alloc_End)
|
||||
|
||||
|
||||
class MemLargeResource(ResourceDescriptor):
|
||||
"""A resource of type MEM_LARGE_RESOURCE"""
|
||||
DATA_TYPE = gdef.MEM_LARGE_RESOURCE
|
||||
|
||||
def __str__(self):
|
||||
return "<{0} : [{1:#016x}-{2:#016x}]>".format(type(self).__name__, self.header.MLD_Alloc_Base, self.header.MLD_Alloc_End)
|
||||
|
||||
class ClassSpecificResource(ResourceDescriptorWithHeader):
|
||||
"""A resource of type CS_RESOURCE"""
|
||||
DATA_TYPE = gdef.CS_RESOURCE
|
||||
# Any idea for __str__ ?
|
||||
|
||||
class DevicePrivateResource(ResourceDescriptor):
|
||||
"""A device private resource
|
||||
(https://docs.microsoft.com/en-us/windows-hardware/drivers/install/devprivate-resource)
|
||||
"""
|
||||
|
||||
@property
|
||||
def header(self):
|
||||
return None
|
||||
|
||||
# Any idea for __str__ ?
|
||||
|
||||
class MfCardConfigResource(ResourceDescriptorWithHeader):
|
||||
"""A resource of type MFCARD_RESOURCE"""
|
||||
DATA_TYPE = gdef.MFCARD_RESOURCE
|
||||
# Any idea for __str__ ?
|
||||
|
||||
class PcCardConfigResource(ResourceDescriptorWithHeader):
|
||||
"""A resource of type PCCARD_RESOURCE"""
|
||||
DATA_TYPE = gdef.PCCARD_RESOURCE
|
||||
# Any idea for __str__ ?
|
||||
|
||||
# Flemme de faire une meta-classe pour ca..
|
||||
ResourceDescriptor.SUBCLASSES.update({
|
||||
gdef.ResType_None: ResourceNoType,
|
||||
gdef.ResType_Mem: MemoryResource,
|
||||
gdef.ResType_IO: IoResource,
|
||||
gdef.ResType_DMA: DmaResource,
|
||||
gdef.ResType_IRQ: IrqResource,
|
||||
gdef.ResType_BusNumber: BusNumberResource,
|
||||
gdef.ResType_MemLarge: MemLargeResource,
|
||||
gdef.ResType_ClassSpecific: ClassSpecificResource,
|
||||
gdef.ResType_DevicePrivate: DevicePrivateResource,
|
||||
gdef.ResType_MfCardConfig: MfCardConfigResource,
|
||||
gdef.ResType_PcCardConfig: PcCardConfigResource,
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,449 @@
|
||||
import ctypes
|
||||
import windows
|
||||
import windows.generated_def as gdef
|
||||
from windows.pycompat import basestring
|
||||
|
||||
# Renommer le fichier etw ?
|
||||
|
||||
MAX_ETW_SESSIONS = 64
|
||||
MAX_SESSION_NAME_LEN = 1024
|
||||
MAX_LOGFILE_PATH_LEN = 1024
|
||||
|
||||
MAX_SESSION_NAME_LEN_W = MAX_SESSION_NAME_LEN * 2
|
||||
MAX_LOGFILE_PATH_LEN_W = MAX_LOGFILE_PATH_LEN * 2
|
||||
|
||||
|
||||
class EventRecord(gdef.EVENT_RECORD):
|
||||
@property
|
||||
def tid(self):
|
||||
"""Thread ID that provided the event"""
|
||||
return self.EventHeader.ThreadId
|
||||
|
||||
@property
|
||||
def pid(self):
|
||||
"""Process ID that provided the event"""
|
||||
return self.EventHeader.ProcessId
|
||||
|
||||
@property
|
||||
def guid(self):
|
||||
"""Guid of the Event"""
|
||||
# Well, this is called "ProviderId" but seems to be the Event GUID
|
||||
# As a provider can generated multiple event with differents GUID
|
||||
# And this value reflect EVENT_TRACE_HEADER.Guid passed to TraceEvent
|
||||
return self.EventHeader.ProviderId
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""ID of the Event"""
|
||||
return self.EventHeader.EventDescriptor.Id
|
||||
|
||||
@property
|
||||
def opcode(self):
|
||||
return self.EventHeader.EventDescriptor.Opcode
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self.EventHeader.EventDescriptor.Version
|
||||
|
||||
@property
|
||||
def level(self):
|
||||
return self.EventHeader.EventDescriptor.Level
|
||||
|
||||
@property
|
||||
def context(self):
|
||||
if self.UserContext is None:
|
||||
return None
|
||||
return ctypes.py_object.from_address(self.UserContext).value
|
||||
|
||||
@property
|
||||
def user_data(self):
|
||||
"""Event specific data
|
||||
|
||||
:type: :class:`str`
|
||||
"""
|
||||
if not (self.UserData and self.UserDataLength):
|
||||
return ""
|
||||
dbuf = (ctypes.c_char * self.UserDataLength).from_address(self.UserData)
|
||||
return dbuf[:]
|
||||
|
||||
# def match(self, provider=None, id=None, opcode=None):
|
||||
|
||||
def __repr__(self):
|
||||
guid = self.EventHeader.ProviderId
|
||||
return """<{0} provider="{1}" id={2}>""".format(type(self).__name__, guid, self.id)
|
||||
|
||||
|
||||
PEventRecord = ctypes.POINTER(EventRecord)
|
||||
|
||||
class EventTraceProperties(gdef.EVENT_TRACE_PROPERTIES):
|
||||
"""Represent an Event Trace session that may exist or now. (https://docs.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-event_trace_properties)
|
||||
|
||||
This class is widly used by :class:`EtwTrace`
|
||||
"""
|
||||
# Test: ascii / Use Wchar ?
|
||||
FULL_SIZE = ctypes.sizeof(gdef.EVENT_TRACE_PROPERTIES) + MAX_SESSION_NAME_LEN_W + MAX_LOGFILE_PATH_LEN_W
|
||||
|
||||
# def alloc(cls, size) ?
|
||||
@classmethod
|
||||
def create(cls):
|
||||
"""Initialize a new :class:`EventTraceProperties`"""
|
||||
buff = windows.utils.BUFFER(cls)(size=cls.FULL_SIZE)
|
||||
# ctypes.memset(buff, "\x00", cls.FULL_SIZE)
|
||||
self = buff[0]
|
||||
self.Wnode.BufferSize = cls.FULL_SIZE
|
||||
self.LoggerNameOffset = ctypes.sizeof(cls)
|
||||
self.LogFileNameOffset = ctypes.sizeof(cls) + MAX_SESSION_NAME_LEN
|
||||
return self
|
||||
|
||||
def get_logfilename(self):
|
||||
assert self.LogFileNameOffset
|
||||
return windows.current_process.read_string(ctypes.addressof(self) + self.LogFileNameOffset)
|
||||
|
||||
def set_logfilename(self, filename):
|
||||
assert self.LogFileNameOffset
|
||||
if not filename.endswith("\x00"):
|
||||
filename += "\x00"
|
||||
return windows.current_process.write_memory(ctypes.addressof(self) + self.LogFileNameOffset, filename)
|
||||
|
||||
logfile = property(get_logfilename, set_logfilename) #: The logfile associated with the session
|
||||
|
||||
def get_logger_name(self):
|
||||
assert self.LoggerNameOffset
|
||||
return windows.current_process.read_string(ctypes.addressof(self) + self.LoggerNameOffset)
|
||||
|
||||
|
||||
def set_logfilename(self, filename):
|
||||
assert self.LoggerNameOffset
|
||||
if not filename.endswith("\x00"):
|
||||
filename += "\x00"
|
||||
return windows.current_process.write_memory(ctypes.addressof(self) + self.LoggerNameOffset, filename)
|
||||
|
||||
name = property(get_logger_name, set_logfilename) #: The name of the session
|
||||
|
||||
@property
|
||||
def guid(self):
|
||||
"""The GUID of the Event Trace session (see ``Wnode.Guid``)"""
|
||||
return self.Wnode.Guid
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""The LoggerId if the session (see ``Wnode.HistoricalContext``)"""
|
||||
return self.Wnode.HistoricalContext
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return """<{0} name="{1}" guid={2}>""".format(type(self).__name__, self.name, self.guid)
|
||||
|
||||
|
||||
# GUID setter ?
|
||||
|
||||
class CtxProcess(object):
|
||||
def __init__(self, trace, func, stop=False):
|
||||
self.trace = trace
|
||||
self.func = func
|
||||
self.stop = stop
|
||||
self.timing = {}
|
||||
|
||||
def _get_time(self):
|
||||
now = gdef.FILETIME()
|
||||
windows.winproxy.GetSystemTimeAsFileTime(now)
|
||||
return now
|
||||
|
||||
def __enter__(self):
|
||||
self.timing["begin"] = self._get_time()
|
||||
return self.timing
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
# bad_end = self._get_time()
|
||||
self.trace.flush()
|
||||
if self.stop:
|
||||
self.trace.stop()
|
||||
# End time after the flush is effective.
|
||||
self.timing["end"] = self._get_time()
|
||||
# print("Trace ctx: fake-end: {0:#x}".format(int(fake_end)))
|
||||
print("Trace ctx: begin={0:#x} | end={1:#x}".format(int(self.timing["begin"]), int(self.timing["end"])))
|
||||
self.trace.process(self.func, **self.timing)
|
||||
|
||||
|
||||
class EtwTrace(object):
|
||||
"""Represent an ETW Trace for tracing/processing events"""
|
||||
def __init__(self, name, logfile=None, guid=None):
|
||||
self.name = windows.pycompat.raw_encode(name) #: The name of the trace
|
||||
self.logfile = logfile #: The logging file of the trace (``None`` means real time trace)
|
||||
if guid and isinstance(guid, basestring):
|
||||
guid = gdef.GUID.from_string(guid)
|
||||
self.guid = guid #: The guid of the trace
|
||||
self.handle = 0
|
||||
|
||||
def exists(self):
|
||||
"""Return ``True`` if the trace already exist (based on its name)"""
|
||||
prop = EventTraceProperties.create()
|
||||
try:
|
||||
windows.winproxy.ControlTraceA(self.handle, self.name, prop, gdef.EVENT_TRACE_CONTROL_QUERY)
|
||||
except WindowsError as e:
|
||||
if e.winerror == gdef.ERROR_WMI_INSTANCE_NOT_FOUND:
|
||||
return False # Not found -> does not exists
|
||||
raise # Other error -> reraise
|
||||
return True
|
||||
|
||||
def start(self, flags=0, mode=0):
|
||||
"""Start the tracing"""
|
||||
prop = EventTraceProperties.create()
|
||||
prop.NumberOfBuffers = 42
|
||||
prop.EnableFlags = flags
|
||||
prop.LogFileMode = mode
|
||||
if self.guid:
|
||||
prop.Wnode.Guid = self.guid
|
||||
if self.logfile:
|
||||
prop.logfile = self.logfile
|
||||
if self.name: # Base REAL_TIME on option ? name presence ? logfile presence ?
|
||||
prop.LogFileMode |= gdef.EVENT_TRACE_REAL_TIME_MODE
|
||||
handle = gdef.TRACEHANDLE()
|
||||
windows.winproxy.StartTraceA(handle, self.name, prop)
|
||||
if not self.guid:
|
||||
self.guid = prop.Wnode.Guid
|
||||
self.handle = handle
|
||||
|
||||
def stop(self, soft=False): # Change name
|
||||
"""stop the tracing.
|
||||
|
||||
``soft`` will allow to stop a non-existing trace that do not exists/run.
|
||||
This allow for simpler script that stop/start some EtwTrace.
|
||||
"""
|
||||
prop = EventTraceProperties.create()
|
||||
try:
|
||||
windows.winproxy.ControlTraceA(0, self.name, prop, gdef.EVENT_TRACE_CONTROL_STOP)
|
||||
except WindowsError as e:
|
||||
if soft and e.winerror == gdef.ERROR_WMI_INSTANCE_NOT_FOUND:
|
||||
return False
|
||||
raise
|
||||
return True
|
||||
|
||||
def flush(self):
|
||||
"""Flush the trace"""
|
||||
prop = EventTraceProperties.create()
|
||||
windows.winproxy.ControlTraceA(0, self.name, prop, gdef.EVENT_TRACE_CONTROL_FLUSH)
|
||||
|
||||
|
||||
def enable(self, guid, flags=0xff, level=0xff):
|
||||
"""Enable the specified event trace provider."""
|
||||
if isinstance(guid, basestring):
|
||||
guid = gdef.GUID.from_string(guid)
|
||||
return windows.winproxy.EnableTrace(1, flags, level, guid, self.handle) # EnableTraceEx ?
|
||||
|
||||
def enable_ex(self, guid, flags=0xff, level=0xff, any_keyword = 0xffffffff, all_keyword=0x00):
|
||||
"""Enable the specified event trace provider."""
|
||||
if isinstance(guid, basestring):
|
||||
guid = gdef.GUID.from_string(guid)
|
||||
|
||||
# TODO : implement EnableParameters
|
||||
EVENT_CONTROL_CODE_ENABLE_PROVIDER = 1
|
||||
|
||||
# EnableTraceEx only accept a UCHAR for the level param
|
||||
# TODO : maybe raise an Exception instead of silently masking the value ?
|
||||
level = gdef.UCHAR(chr(level & 0xff))
|
||||
|
||||
return windows.winproxy.EnableTraceEx2(self.handle, guid, EVENT_CONTROL_CODE_ENABLE_PROVIDER, level , any_keyword, all_keyword, 0, None)
|
||||
|
||||
|
||||
def process(self, callback, begin=None, end=None, context=None):
|
||||
"""Process the event retrieved by the trace.
|
||||
This function will call ``callback`` with any :class:`EventRecord` in the trace.
|
||||
``begin/end`` allow to filter and only process events in a given timeframe.
|
||||
|
||||
.. warning::
|
||||
|
||||
If the trace if ``REALTIME`` (no logfile) this function will hang/process new event until the trace is stopped.
|
||||
|
||||
Using ``logman -ets stop TRACE_NAME`` for exemple.
|
||||
|
||||
"""
|
||||
if end == "now":
|
||||
end = gdef.FILETIME()
|
||||
windows.winproxy.GetSystemTimeAsFileTime(end)
|
||||
windows.utils.sprint(end)
|
||||
|
||||
logfile = gdef.EVENT_TRACE_LOGFILEW()
|
||||
logfile.LoggerName = windows.pycompat.raw_decode(self.name)
|
||||
# logfile.ProcessTraceMode = gdef.PROCESS_TRACE_MODE_EVENT_RECORD | gdef.PROCESS_TRACE_MODE_RAW_TIMESTAMP
|
||||
logfile.ProcessTraceMode = gdef.PROCESS_TRACE_MODE_EVENT_RECORD
|
||||
if not self.logfile:
|
||||
logfile.ProcessTraceMode |= gdef.PROCESS_TRACE_MODE_REAL_TIME
|
||||
else:
|
||||
# logfile.ProcessTraceMode |= gdef.PROCESS_TRACE_MODE_REAL_TIME
|
||||
logfile.LogFileName = self.logfile
|
||||
|
||||
if context:
|
||||
context_ptr = ctypes.pointer(ctypes.py_object(context))
|
||||
logfile.Context = ctypes.cast(context_ptr, ctypes.c_void_p)
|
||||
|
||||
@ctypes.WINFUNCTYPE(gdef.PVOID, PEventRecord)
|
||||
def real_callback(record_ptr):
|
||||
try:
|
||||
x = callback(record_ptr[0])
|
||||
except Exception as e:
|
||||
print("CALLBACK ERROR: {0}".format(e))
|
||||
return 1
|
||||
if x is None:
|
||||
x = 1
|
||||
return x
|
||||
|
||||
@ctypes.WINFUNCTYPE(gdef.PVOID, gdef.PEVENT_TRACE_LOGFILEW)
|
||||
def buffer_callback(trace):
|
||||
print("Buffer-callback: event-lost={0}".format(trace[0].LogfileHeader.EventsLost))
|
||||
print("Buffer-callback: buffer-lost={0}".format(trace[0].LogfileHeader.BuffersLost))
|
||||
return True
|
||||
|
||||
logfile.EventRecordCallback = ctypes.cast(real_callback, gdef.PVOID)
|
||||
# logfile.BufferCallback = ctypes.cast(buffer_callback, gdef.PVOID)
|
||||
r = windows.winproxy.OpenTraceW(logfile)
|
||||
rh = gdef.TRACEHANDLE(r)
|
||||
return windows.winproxy.ProcessTrace(rh, 1, begin, end)
|
||||
|
||||
def CtxProcess(self, func, stop=False):
|
||||
return CtxProcess(self, func, stop=stop)
|
||||
|
||||
def __repr__(self):
|
||||
return """<{0} name={1!r} logfile={2!r}>""".format(type(self).__name__, self.name, self.logfile)
|
||||
|
||||
class TraceProvider(object):
|
||||
"""Represent a ETW provider, which is just a GUID.
|
||||
Corresponding name for a provider may be available trhought WMI.
|
||||
"""
|
||||
def __init__(self, guid):
|
||||
self.guid = guid
|
||||
|
||||
@property
|
||||
def infos(self):
|
||||
"""The :class:`TraceGuidInfo` associated with the provider.
|
||||
Main use is to retrieve the instances of the provider (directly available with ``instances``)
|
||||
|
||||
:type: :class:`TraceGuidInfo`
|
||||
"""
|
||||
size = gdef.DWORD()
|
||||
info_buffer = ctypes.c_buffer(0x1000)
|
||||
try:
|
||||
windows.winproxy.EnumerateTraceGuidsEx(gdef.TraceGuidQueryInfo, self.guid, ctypes.sizeof(self.guid), info_buffer, ctypes.sizeof(info_buffer), size)
|
||||
except WindowsError as e:
|
||||
if not e.winerror == gdef.ERROR_INSUFFICIENT_BUFFER:
|
||||
raise
|
||||
# Buffer to small
|
||||
info_buffer = ctypes.c_buffer(size.value)
|
||||
windows.winproxy.EnumerateTraceGuidsEx(gdef.TraceGuidQueryInfo, self.guid, ctypes.sizeof(self.guid), info_buffer, ctypes.sizeof(info_buffer), size)
|
||||
return TraceGuidInfo.from_raw_buffer(info_buffer)
|
||||
|
||||
# We dont really care about the C struct layout
|
||||
# Our trace providers should be able to directly returns its instances
|
||||
@property
|
||||
def instances(self):
|
||||
"""The instances of the provider.
|
||||
|
||||
:type: [:class:`TraceProviderInstanceInfo`] -- A list of :class:`TraceProviderInstanceInfo`
|
||||
"""
|
||||
return self.infos.instances
|
||||
|
||||
def __repr__(self):
|
||||
return """<{0} for "{1}">""".format(type(self).__name__, self.guid)
|
||||
|
||||
|
||||
class TraceGuidInfo(gdef.TRACE_GUID_INFO):
|
||||
"""Defines the header to the list of sessions that enabled the provider
|
||||
(see https://docs.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-trace_guid_info)
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def from_raw_buffer(cls, buffer):
|
||||
self = cls.from_buffer(buffer)
|
||||
self._raw_buffer_ = buffer
|
||||
return self
|
||||
|
||||
def _instance_generator(self):
|
||||
if not self.InstanceCount:
|
||||
return
|
||||
abs_offset = ctypes.sizeof(self)
|
||||
for i in range(self.InstanceCount):
|
||||
instance = TraceProviderInstanceInfo.from_raw_buffer(self._raw_buffer_, abs_offset)
|
||||
abs_offset += instance.NextOffset
|
||||
yield instance
|
||||
|
||||
@property
|
||||
def instances(self):
|
||||
"""The instances of the provider.
|
||||
|
||||
:type: [:class:`TraceProviderInstanceInfo`] -- A list of :class:`TraceProviderInstanceInfo`
|
||||
"""
|
||||
return [x for x in self._instance_generator()]
|
||||
|
||||
def __repr__(self):
|
||||
return "<{0} InstanceCount={1} Reserved={2}>".format(type(self).__name__, self.InstanceCount, self.Reserved)
|
||||
|
||||
|
||||
class TraceProviderInstanceInfo(gdef.TRACE_PROVIDER_INSTANCE_INFO):
|
||||
"""Defines an instance of the provider
|
||||
(see https://docs.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-trace_provider_instance_info)
|
||||
"""
|
||||
@classmethod
|
||||
def from_raw_buffer(cls, buffer, offset):
|
||||
self = cls.from_buffer(buffer, offset)
|
||||
self._offset = offset
|
||||
self._raw_buffer_ = buffer
|
||||
return self
|
||||
|
||||
def _instance_generator(self):
|
||||
offset = self._offset + ctypes.sizeof(self)
|
||||
entry_size = ctypes.sizeof(gdef.TRACE_ENABLE_INFO)
|
||||
for i in range(self.EnableCount):
|
||||
yield gdef.TRACE_ENABLE_INFO.from_buffer(self._raw_buffer_, offset)
|
||||
offset += entry_size
|
||||
|
||||
@property
|
||||
def sessions(self):
|
||||
"""The sessions for the instance
|
||||
|
||||
:type: [:class:`~windows.generated_def.winstructs.TRACE_ENABLE_INFO`] -- A list of session
|
||||
"""
|
||||
return [x for x in self._instance_generator()]
|
||||
|
||||
def __repr__(self):
|
||||
return "<{0} Pid={1} EnableCount={2}>".format(type(self).__name__, self.Pid, self.EnableCount)
|
||||
|
||||
|
||||
class EtwManager(object):
|
||||
"""An object to query ETW session/providers and open new trace"""
|
||||
|
||||
@property
|
||||
def sessions(self):
|
||||
"""The list of currently active ETW session.
|
||||
|
||||
:type: [:class:`EventTraceProperties`] -- A list of :class:`EventTraceProperties`
|
||||
"""
|
||||
# Create a tuple of MAX_ETW_SESSIONS EventTraceProperties ptr
|
||||
t = [EventTraceProperties.create() for _ in range(MAX_ETW_SESSIONS)]
|
||||
# Put this in a ctypes array
|
||||
array = (gdef.POINTER(EventTraceProperties) * MAX_ETW_SESSIONS)(*(ctypes.pointer(e) for e in t))
|
||||
# Cast as array/ptr does not handle subtypes very-well
|
||||
tarray = ctypes.cast(array, ctypes.POINTER(ctypes.POINTER(gdef.EVENT_TRACE_PROPERTIES)))
|
||||
count = gdef.DWORD()
|
||||
windows.winproxy.QueryAllTracesA(tarray, MAX_ETW_SESSIONS, count)
|
||||
return t[:count.value]
|
||||
|
||||
|
||||
@property
|
||||
def providers(self):
|
||||
"""The list of currently existing ETW providers.
|
||||
|
||||
:type: [:class:`TraceProvider`] -- A list of ETW providers
|
||||
"""
|
||||
buffer = windows.utils.BUFFER(gdef.GUID, 0x1000)()
|
||||
size = gdef.DWORD()
|
||||
windows.winproxy.EnumerateTraceGuidsEx(gdef.TraceGuidQueryList, None, 0, buffer, buffer.real_size, size)
|
||||
return [TraceProvider(g) for g in buffer[:size.value // ctypes.sizeof(gdef.GUID)]]
|
||||
|
||||
|
||||
# Temp name / API ?
|
||||
def open_trace(self, name=None, logfile=None, guid=None):
|
||||
"""Open a new ETW Trace
|
||||
|
||||
:return: :class:`EtwTrace`
|
||||
"""
|
||||
return EtwTrace(name, logfile, guid)
|
||||
@@ -0,0 +1,375 @@
|
||||
import ctypes
|
||||
import windows
|
||||
from windows.generated_def.winstructs import *
|
||||
import windows.generated_def.windef as windef
|
||||
|
||||
EXCEPTION_CONTINUE_SEARCH = (0x0)
|
||||
EXCEPTION_CONTINUE_EXECUTION = (0xffffffff)
|
||||
|
||||
exception_type = [
|
||||
"EXCEPTION_ACCESS_VIOLATION",
|
||||
"EXCEPTION_DATATYPE_MISALIGNMENT",
|
||||
"EXCEPTION_BREAKPOINT",
|
||||
"EXCEPTION_SINGLE_STEP",
|
||||
"EXCEPTION_ARRAY_BOUNDS_EXCEEDED",
|
||||
"EXCEPTION_FLT_DENORMAL_OPERAND",
|
||||
"EXCEPTION_FLT_DIVIDE_BY_ZERO",
|
||||
"EXCEPTION_FLT_INEXACT_RESULT",
|
||||
"EXCEPTION_FLT_INVALID_OPERATION",
|
||||
"EXCEPTION_FLT_OVERFLOW",
|
||||
"EXCEPTION_FLT_STACK_CHECK",
|
||||
"EXCEPTION_FLT_UNDERFLOW",
|
||||
"EXCEPTION_INT_DIVIDE_BY_ZERO",
|
||||
"EXCEPTION_INT_OVERFLOW",
|
||||
"EXCEPTION_PRIV_INSTRUCTION",
|
||||
"EXCEPTION_IN_PAGE_ERROR",
|
||||
"EXCEPTION_ILLEGAL_INSTRUCTION",
|
||||
"EXCEPTION_NONCONTINUABLE_EXCEPTION",
|
||||
"EXCEPTION_STACK_OVERFLOW",
|
||||
"EXCEPTION_INVALID_DISPOSITION",
|
||||
"EXCEPTION_GUARD_PAGE",
|
||||
"EXCEPTION_INVALID_HANDLE",
|
||||
"EXCEPTION_POSSIBLE_DEADLOCK",
|
||||
]
|
||||
|
||||
# x -> x dict may seems strange but useful to get the Flags (with name) from the int
|
||||
# exception_name_by_value[0x80000001] -> EXCEPTION_GUARD_PAGE(0x80000001L)
|
||||
exception_name_by_value = dict([(x, x) for x in [getattr(windows.generated_def.windef, name) for name in exception_type]])
|
||||
|
||||
class EEXCEPTION_RECORDBase(object):
|
||||
@property
|
||||
def ExceptionCode(self):
|
||||
"""The Exception code
|
||||
|
||||
:type: :class:`int`"""
|
||||
real_code = super(EEXCEPTION_RECORDBase, self).ExceptionCode
|
||||
return exception_name_by_value.get(real_code, windows.generated_def.windef.Flag("UNKNOW_EXCEPTION", real_code))
|
||||
|
||||
@property
|
||||
def ExceptionAddress(self):
|
||||
"""The Exception Address
|
||||
|
||||
:type: :class:`int`"""
|
||||
x = super(EEXCEPTION_RECORDBase, self).ExceptionAddress
|
||||
if x is None:
|
||||
return 0x0
|
||||
return x
|
||||
|
||||
class EEXCEPTION_RECORD(EEXCEPTION_RECORDBase, EXCEPTION_RECORD):
|
||||
"""Enhanced exception record"""
|
||||
|
||||
fields = [f[0] for f in EXCEPTION_RECORD._fields_]
|
||||
"""The fields of the structure"""
|
||||
|
||||
class EEXCEPTION_RECORD32(EEXCEPTION_RECORDBase, EXCEPTION_RECORD32):
|
||||
"""Enhanced exception record (32bits)"""
|
||||
|
||||
fields = [f[0] for f in EXCEPTION_RECORD32._fields_]
|
||||
"""The fields of the structure"""
|
||||
|
||||
class EEXCEPTION_RECORD64(EEXCEPTION_RECORDBase, EXCEPTION_RECORD64):
|
||||
"""Enhanced exception record (64bits)"""
|
||||
|
||||
fields = [f[0] for f in EXCEPTION_RECORD64._fields_]
|
||||
"""The fields of the structure"""
|
||||
|
||||
|
||||
class EEXCEPTION_DEBUG_INFO32(ctypes.Structure):
|
||||
"""Enhanced Debug info"""
|
||||
_fields_ = windows.utils.transform_ctypes_fields(EXCEPTION_DEBUG_INFO, {"ExceptionRecord": EEXCEPTION_RECORD32})
|
||||
|
||||
fields = [f[0] for f in _fields_]
|
||||
"""The fields of the structure"""
|
||||
|
||||
class EEXCEPTION_DEBUG_INFO64(ctypes.Structure):
|
||||
"""Enhanced Debug info"""
|
||||
_fields_ = windows.utils.transform_ctypes_fields(EXCEPTION_DEBUG_INFO, {"ExceptionRecord": EEXCEPTION_RECORD64})
|
||||
|
||||
fields = [f[0] for f in _fields_]
|
||||
"""The fields of the structure"""
|
||||
|
||||
|
||||
class EEflags(ctypes.Structure):
|
||||
"Flag view of the Eflags register"
|
||||
_fields_ = [("CF", DWORD, 1),
|
||||
("RES_1", DWORD, 1),
|
||||
("PF", DWORD, 1),
|
||||
("RES_3", DWORD, 1),
|
||||
("AF", DWORD, 1),
|
||||
("RES_5", DWORD, 1),
|
||||
("ZF", DWORD, 1),
|
||||
("SF", DWORD, 1),
|
||||
("TF", DWORD, 1),
|
||||
("IF", DWORD, 1),
|
||||
("DF", DWORD, 1),
|
||||
("OF", DWORD, 1),
|
||||
("IOPL_1", DWORD, 1),
|
||||
("IOPL_2", DWORD, 1),
|
||||
("NT", DWORD, 1),
|
||||
("RES_15", DWORD, 1),
|
||||
("RF", DWORD, 1),
|
||||
("VM", DWORD, 1),
|
||||
("AC", DWORD, 1),
|
||||
("VIF", DWORD, 1),
|
||||
("VIP", DWORD, 1),
|
||||
("ID", DWORD, 1),
|
||||
]
|
||||
|
||||
fields = [f[0] for f in _fields_]
|
||||
"""The fields of the structure"""
|
||||
|
||||
def get_raw(self):
|
||||
x = DWORD.from_address(ctypes.addressof(self))
|
||||
return x.value
|
||||
|
||||
def set_raw(self, value):
|
||||
x = DWORD.from_address(ctypes.addressof(self))
|
||||
x.value = value
|
||||
return None
|
||||
|
||||
def dump(self):
|
||||
res = []
|
||||
for name in [x[0] for x in self._fields_]:
|
||||
if name.startswith("RES_"):
|
||||
continue
|
||||
if getattr(self, name):
|
||||
res.append(name)
|
||||
return "|".join(res)
|
||||
|
||||
def __repr__(self):
|
||||
return hex(self)
|
||||
|
||||
def __hex__(self):
|
||||
if self.raw == 0:
|
||||
return "{0}({1})".format(type(self).__name__, hex(self.raw))
|
||||
return "{0}({1}:{2})".format(type(self).__name__, hex(self.raw), self.dump())
|
||||
|
||||
raw = property(get_raw, set_raw)
|
||||
"""Raw value of the eflags
|
||||
|
||||
:type: :class:`int`
|
||||
"""
|
||||
|
||||
|
||||
class EDr7(ctypes.Structure):
|
||||
"Flag view of the DR7 register"
|
||||
_fields_ = [("L0", DWORD, 1),
|
||||
("G0", DWORD, 1),
|
||||
("L1", DWORD, 1),
|
||||
("G1", DWORD, 1),
|
||||
("L2", DWORD, 1),
|
||||
("G2", DWORD, 1),
|
||||
("L3", DWORD, 1),
|
||||
("G3", DWORD, 1),
|
||||
("LE", DWORD, 1),
|
||||
("GE", DWORD, 1),
|
||||
("RES_1", DWORD, 3),
|
||||
("GD", DWORD, 1),
|
||||
("RES_1", DWORD, 2),
|
||||
("RW0", DWORD, 2),
|
||||
("LEN0", DWORD, 2),
|
||||
("RW1", DWORD, 2),
|
||||
("LEN1", DWORD, 2),
|
||||
("RW2", DWORD, 2),
|
||||
("LEN2", DWORD, 2),
|
||||
("RW3", DWORD, 2),
|
||||
("LEN3", DWORD, 2),
|
||||
]
|
||||
|
||||
fields = [f[0] for f in _fields_]
|
||||
"""The fields of the structure"""
|
||||
|
||||
class ECONTEXTBase(object):
|
||||
"""DAT CONTEXT"""
|
||||
default_dump = ()
|
||||
pc_reg = ''
|
||||
sp_reg = ''
|
||||
func_result_reg = ''
|
||||
special_reg_type = {}
|
||||
|
||||
|
||||
def regs(self, to_dump=None):
|
||||
"""Return the name and values of the registers
|
||||
|
||||
:returns: [(reg_name, value)] -- A :class:`list` of :class:`tuple`"""
|
||||
res = []
|
||||
if to_dump is None:
|
||||
to_dump = self.default_dump
|
||||
for name in to_dump:
|
||||
value = getattr(self, name)
|
||||
if name in self.special_reg_type:
|
||||
value = self.special_reg_type[name](value)
|
||||
res.append((name, value))
|
||||
return res
|
||||
|
||||
def dump(self, to_dump=None):
|
||||
"""Dump (print) the current context"""
|
||||
regs = self.regs()
|
||||
for name, value in regs:
|
||||
print("{0} -> {1}".format(name, hex(value)))
|
||||
return None
|
||||
|
||||
def get_pc(self):
|
||||
return getattr(self, self.pc_reg)
|
||||
|
||||
def set_pc(self, value):
|
||||
return setattr(self, self.pc_reg, value)
|
||||
|
||||
def get_sp(self):
|
||||
return getattr(self, self.sp_reg)
|
||||
|
||||
def set_sp(self, value):
|
||||
return setattr(self, self.sp_reg, value)
|
||||
|
||||
def get_func_result(self):
|
||||
return getattr(self, self.func_result_reg)
|
||||
|
||||
def set_func_result(self, value):
|
||||
return setattr(self, self.func_result_reg, value)
|
||||
|
||||
pc = property(get_pc, set_pc, None, "Program Counter register (EIP or RIP)")
|
||||
sp = property(get_sp, set_sp, None, "Stack Pointer register (ESP or RSP)")
|
||||
func_result = property(get_func_result, set_func_result, None, "Function Resultat register (EAX or RAX)")
|
||||
|
||||
@property
|
||||
def EEFlags(self):
|
||||
"""Enhanced view of the Eflags (you also have ``EFlags`` for the raw value)
|
||||
|
||||
:type: :class:`EEflags`
|
||||
"""
|
||||
off = type(self).EFlags.offset
|
||||
x = EEflags.from_address(ctypes.addressof(self) + off)
|
||||
x.self = self
|
||||
return x
|
||||
|
||||
@property
|
||||
def EDr7(self):
|
||||
"""Enhanced view of the DR7 register (you also have ``Dr7`` for the raw value)
|
||||
|
||||
:type: :class:`EDr7`
|
||||
"""
|
||||
off = type(self).Dr7.offset
|
||||
x = EDr7.from_address(ctypes.addressof(self) + off)
|
||||
x.self = self
|
||||
return x
|
||||
|
||||
class ECONTEXT32(ECONTEXTBase, CONTEXT32):
|
||||
default_dump = ('Eip', 'Esp', 'Eax', 'Ebx', 'Ecx', 'Edx', 'Ebp', 'Edi', 'Esi', 'EFlags')
|
||||
pc_reg = 'Eip'
|
||||
sp_reg = 'Esp'
|
||||
func_result_reg = 'Eax'
|
||||
fields = [f[0] for f in CONTEXT32._fields_]
|
||||
"""The fields of the structure"""
|
||||
|
||||
class ECONTEXTWOW64(ECONTEXTBase, WOW64_CONTEXT):
|
||||
default_dump = ('Eip', 'Esp', 'Eax', 'Ebx', 'Ecx', 'Edx', 'Ebp', 'Edi', 'Esi', 'EFlags')
|
||||
pc_reg = 'Eip'
|
||||
sp_reg = 'Esp'
|
||||
func_result_reg = 'Eax'
|
||||
fields = [f[0] for f in WOW64_CONTEXT._fields_]
|
||||
"""The fields of the structure"""
|
||||
|
||||
|
||||
class ECONTEXT64(ECONTEXTBase, CONTEXT64):
|
||||
default_dump = ('Rip', 'Rsp', 'Rax', 'Rbx', 'Rcx', 'Rdx', 'Rbp', 'Rdi', 'Rsi',
|
||||
'R8', 'R9', 'R10', 'R11', 'R12', 'R13', 'R14', 'R15', 'EFlags')
|
||||
pc_reg = 'Rip'
|
||||
sp_reg = 'Rsp'
|
||||
func_result_reg = 'Rax'
|
||||
fields = [f[0] for f in CONTEXT64._fields_]
|
||||
"""The fields of the structure"""
|
||||
|
||||
@classmethod
|
||||
def new_aligned(cls):
|
||||
"""Return a new :class:`ECONTEXT64` aligned on 16 bits
|
||||
|
||||
temporary workaround or horrible hack ? choose your side
|
||||
"""
|
||||
size = ctypes.sizeof(cls)
|
||||
nb_qword = int((size + 8) / ctypes.sizeof(ULONGLONG))
|
||||
buffer = (nb_qword * ULONGLONG)()
|
||||
struct_address = ctypes.addressof(buffer)
|
||||
if (struct_address & 0xf) not in [0, 8]:
|
||||
raise ValueError("ULONGLONG array not aligned on 8")
|
||||
if (struct_address & 0xf) == 8:
|
||||
struct_address += 8
|
||||
self = cls.from_address(struct_address)
|
||||
# Keep the raw buffer alive
|
||||
self._buffer = buffer
|
||||
return self
|
||||
|
||||
def bitness():
|
||||
"""Return 32 or 64"""
|
||||
import platform
|
||||
bits = platform.architecture()[0]
|
||||
return int(bits[:2])
|
||||
|
||||
if bitness() == 32:
|
||||
ECONTEXT = ECONTEXT32
|
||||
else:
|
||||
ECONTEXT = ECONTEXT64
|
||||
|
||||
|
||||
class EEXCEPTION_POINTERS(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("ExceptionRecord", ctypes.POINTER(EEXCEPTION_RECORD)),
|
||||
("ContextRecord", ctypes.POINTER(ECONTEXT)),
|
||||
]
|
||||
|
||||
def dump(self):
|
||||
"""Dump (print) the EEXCEPTION_POINTERS"""
|
||||
record = self.ExceptionRecord[0]
|
||||
print("Dumping Exception: ")
|
||||
print(" ExceptionCode = {0} at {1}".format(record.ExceptionCode, hex(record.ExceptionAddress)))
|
||||
regs = self.ContextRecord[0].regs()
|
||||
for name, value in regs:
|
||||
print(" {0} -> {1}".format(name, hex(value)))
|
||||
|
||||
|
||||
class VectoredException(object):
|
||||
"""A decorator that create a callable which can be passed to :func:`AddVectoredExceptionHandler`"""
|
||||
func_type = ctypes.WINFUNCTYPE(ctypes.c_uint, ctypes.POINTER(EEXCEPTION_POINTERS))
|
||||
|
||||
def __new__(cls, func):
|
||||
self = object.__new__(cls)
|
||||
self.func = func
|
||||
v = self.func_type(self.decorator)
|
||||
v.self = self
|
||||
return v
|
||||
|
||||
def decorator(self, exception_pointers):
|
||||
try:
|
||||
return self.func(exception_pointers)
|
||||
except BaseException as e:
|
||||
import traceback
|
||||
print("Ignored Python Exception in Vectored Exception: {0}".format(e))
|
||||
traceback.print_exc()
|
||||
return windef.EXCEPTION_CONTINUE_SEARCH
|
||||
|
||||
|
||||
class VectoredExceptionHandler(object):
|
||||
def __init__(self, pos, handler):
|
||||
self.handler = VectoredException(handler)
|
||||
self.pos = pos
|
||||
|
||||
def __enter__(self):
|
||||
self.value = windows.winproxy.AddVectoredExceptionHandler(self.pos, self.handler)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
windows.winproxy.RemoveVectoredExceptionHandler(self.value)
|
||||
return False
|
||||
|
||||
class DumpContextOnException(VectoredExceptionHandler):
|
||||
def __init__(self, exit=False):
|
||||
self.exit = exit
|
||||
super(DumpContextOnException, self).__init__(self.print_context_result)
|
||||
|
||||
def print_context_result(self, exception_pointers):
|
||||
except_record = exception_pointers[0].ExceptionRecord[0]
|
||||
exception_pointers[0].dump()
|
||||
sys.stdout.flush()
|
||||
if self.exit:
|
||||
windows.current_process.exit()
|
||||
return 0
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import os.path
|
||||
|
||||
import windows
|
||||
import windows.generated_def as gdef
|
||||
|
||||
from windows import security
|
||||
from windows import utils
|
||||
|
||||
|
||||
class WinFile(object):
|
||||
def __init__(self, filename=None, handle=None):
|
||||
if not filename and handle:
|
||||
raise ValueError("File constructor should be given a filename OR handle")
|
||||
self.filename = filename
|
||||
if handle:
|
||||
self._handle = handle
|
||||
self._file = utils.create_file_from_handle(self.handle)
|
||||
|
||||
@utils.fixedproperty
|
||||
def file(self):
|
||||
assert not getattr(self, "_handle", None)
|
||||
return open(self.filename, "r")
|
||||
|
||||
# We do not close the handle on __del__ -> the destructor of file will do it ?
|
||||
# BUt in this case handle without a file will NOT close it..
|
||||
@utils.fixedproperty
|
||||
def handle(self):
|
||||
if os.path.isdir(self.filename):
|
||||
return windows.utils.create_file(self.filename, share=gdef.FILE_SHARE_READ | gdef.FILE_SHARE_WRITE, flags=gdef.FILE_FLAG_BACKUP_SEMANTICS)
|
||||
else:
|
||||
file = self.file
|
||||
return utils.get_handle_from_file(file)
|
||||
|
||||
|
||||
def get_security_descriptor(self, query_sacl=False, flags=security.SecurityDescriptor.DEFAULT_SECURITY_INFORMATION):
|
||||
return security.SecurityDescriptor.from_handle(self.handle, query_sacl=query_sacl, flags=flags, obj_type="file")
|
||||
|
||||
def set_security_descriptor(self, sd):
|
||||
flags = 0
|
||||
if sd.owner:
|
||||
flags |= gdef.OWNER_SECURITY_INFORMATION
|
||||
if sd.group:
|
||||
flags |= gdef.GROUP_SECURITY_INFORMATION
|
||||
if sd.dacl:
|
||||
flags |= gdef.DACL_SECURITY_INFORMATION
|
||||
if sd.sacl:
|
||||
flags |= gdef.SACL_SECURITY_INFORMATION
|
||||
# Check Mandatory label ?
|
||||
|
||||
handle = windows.utils.create_file(self.filename, access=gdef.GENERIC_READ|gdef.WRITE_DAC, share=gdef.FILE_SHARE_READ | gdef.FILE_SHARE_WRITE, flags=gdef.FILE_FLAG_BACKUP_SEMANTICS)
|
||||
return windows.winproxy.SetSecurityInfo(handle, gdef.SE_KERNEL_OBJECT, flags, sd.owner, sd.group, sd.dacl, sd.sacl)
|
||||
|
||||
|
||||
|
||||
security_descriptor = property(get_security_descriptor, set_security_descriptor)
|
||||
|
||||
@classmethod
|
||||
def from_file(cls):
|
||||
handle = utils.get_handle_from_file(file)
|
||||
self = cls(filename=file.name, handle=handle)
|
||||
self._file = file
|
||||
return self
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
import os
|
||||
import ctypes
|
||||
|
||||
import windows
|
||||
from windows import winproxy
|
||||
from windows.generated_def import windef
|
||||
import windows.generated_def as gdef
|
||||
|
||||
current_process_pid = os.getpid()
|
||||
|
||||
class BaseSystemHandle(object):
|
||||
# Big bypass to prevent missing reference at programm exit..
|
||||
_close_function = ctypes.WinDLL("kernel32").CloseHandle
|
||||
|
||||
"""A handle of the system"""
|
||||
@windows.utils.fixedpropety
|
||||
def process(self):
|
||||
"""The process possessing the handle
|
||||
|
||||
:type: :class:`WinProcess <windows.winobject.process.WinProcess>`"""
|
||||
# "TODO: something smart ? :D"
|
||||
# return [p for p in windows.system.processes if p.pid == self.dwProcessId][0]
|
||||
return windows.WinProcess(pid=self.dwProcessId)
|
||||
|
||||
@property
|
||||
def pid(self):
|
||||
return self.dwProcessId
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self.wValue
|
||||
|
||||
|
||||
@windows.utils.fixedpropety
|
||||
def name(self):
|
||||
"""The name of the handle
|
||||
|
||||
:type: :class:`str`"""
|
||||
return self._get_object_name()
|
||||
|
||||
@windows.utils.fixedpropety
|
||||
def type(self):
|
||||
"""The type of the handle
|
||||
|
||||
:type: :class:`str`"""
|
||||
return self._get_object_type()
|
||||
|
||||
@property
|
||||
def infos(self):
|
||||
"""TODO: DOC"""
|
||||
return self._get_object_basic_infos()
|
||||
|
||||
def _get_object_name(self):
|
||||
lh = self.local_handle
|
||||
size_needed = gdef.DWORD()
|
||||
yyy = ctypes.c_buffer(0x1000)
|
||||
winproxy.NtQueryObject(lh, gdef.ObjectNameInformation, ctypes.byref(yyy), ctypes.sizeof(yyy), ctypes.byref(size_needed))
|
||||
return gdef.LSA_UNICODE_STRING.from_buffer_copy(yyy[:size_needed.value]).str
|
||||
|
||||
def _get_object_type(self):
|
||||
lh = self.local_handle
|
||||
xxx = gdef.PUBLIC_OBJECT_TYPE_INFORMATION()
|
||||
size_needed = gdef.DWORD()
|
||||
try:
|
||||
winproxy.NtQueryObject(lh, gdef.ObjectTypeInformation, ctypes.byref(xxx), ctypes.sizeof(xxx), ctypes.byref(size_needed))
|
||||
except WindowsError as e:
|
||||
if e.code != gdef.STATUS_INFO_LENGTH_MISMATCH:
|
||||
raise
|
||||
size = size_needed.value
|
||||
buffer = ctypes.c_buffer(size)
|
||||
winproxy.NtQueryObject(lh, gdef.ObjectTypeInformation, buffer, size, ctypes.byref(size_needed))
|
||||
xxx = gdef.PUBLIC_OBJECT_TYPE_INFORMATION.from_buffer_copy(buffer)
|
||||
return xxx.TypeName.str
|
||||
|
||||
def _get_object_basic_infos(self):
|
||||
pass
|
||||
lh = self.local_handle
|
||||
size_needed = gdef.DWORD()
|
||||
basic_infos = gdef.PUBLIC_OBJECT_BASIC_INFORMATION()
|
||||
winproxy.NtQueryObject(lh, gdef.ObjectBasicInformation, ctypes.byref(basic_infos), ctypes.sizeof(basic_infos), ctypes.byref(size_needed))
|
||||
return basic_infos
|
||||
|
||||
@windows.utils.fixedpropety
|
||||
def local_handle(self):
|
||||
"""A local copy of the handle, acquired with ``DuplicateHandle``
|
||||
|
||||
:type: :class:`int`"""
|
||||
if self.dwProcessId == windows.current_process.pid:
|
||||
return self.wValue
|
||||
res = gdef.HANDLE()
|
||||
winproxy.DuplicateHandle(self.process.handle, self.wValue, windows.current_process.handle, ctypes.byref(res), dwOptions=gdef.DUPLICATE_SAME_ACCESS)
|
||||
return res.value
|
||||
|
||||
def description(self):
|
||||
stype = self.type
|
||||
descr_func = getattr(self, "description_" + stype, None)
|
||||
if descr_func is None:
|
||||
return None
|
||||
return descr_func()
|
||||
|
||||
def description_Process(self):
|
||||
proc = windows.WinProcess(handle=self.wValue)
|
||||
res = str(proc)
|
||||
del proc._handle
|
||||
return res
|
||||
|
||||
def description_Thread(self):
|
||||
thread = windows.WinThread(handle=self.wValue)
|
||||
res = str(thread)
|
||||
del thread._handle
|
||||
return res
|
||||
|
||||
def __repr__(self):
|
||||
return "<{0} value=<0x{1:x}> in process pid={2}>".format(type(self).__name__, self.wValue, self.dwProcessId)
|
||||
|
||||
def __del__(self):
|
||||
if self.dwProcessId == current_process_pid:
|
||||
return
|
||||
if hasattr(self, "_local_handle"):
|
||||
return self._close_function(self._local_handle)
|
||||
|
||||
class Handle(gdef.SYSTEM_HANDLE, BaseSystemHandle):
|
||||
pass
|
||||
|
||||
class HandleWow64(gdef.SYSTEM_HANDLE64, BaseSystemHandle):
|
||||
pass # For wow64 process
|
||||
|
||||
def enumerate_handles():
|
||||
if windows.current_process.is_wow_64:
|
||||
return enumerate_handles_syswow64()
|
||||
size_needed = gdef.ULONG()
|
||||
# Should at least be sizeof(gdef.SYSTEM_HANDLE_INFORMATION)
|
||||
tmp_buffer = windows.utils.BUFFER(gdef.SYSTEM_HANDLE_INFORMATION)()
|
||||
try:
|
||||
winproxy.NtQuerySystemInformation(gdef.SystemHandleInformation, tmp_buffer, tmp_buffer.real_size, ReturnLength=ctypes.byref(size_needed))
|
||||
except WindowsError as e:
|
||||
pass
|
||||
size = size_needed.value + 0x1000 # In case we have some more handle created
|
||||
buf = windows.utils.BUFFER(gdef.SYSTEM_HANDLE_INFORMATION)(size=size)
|
||||
size_needed.value = 0
|
||||
winproxy.NtQuerySystemInformation(gdef.SystemHandleInformation, buf, buf.real_size, ReturnLength=ctypes.byref(size_needed))
|
||||
handle_array = windows.utils.resized_array(buf[0].Handles, buf[0].HandleCount, Handle)
|
||||
return list(handle_array)
|
||||
|
||||
|
||||
def enumerate_handles_syswow64():
|
||||
size_needed = gdef.ULONG()
|
||||
# Should at least be sizeof(gdef.SYSTEM_HANDLE_INFORMATION)
|
||||
tmp_buffer = windows.utils.BUFFER(gdef.SYSTEM_HANDLE_INFORMATION64)()
|
||||
try:
|
||||
windows.syswow64.NtQuerySystemInformation_32_to_64(gdef.SystemHandleInformation, tmp_buffer, tmp_buffer.real_size, ReturnLength=ctypes.byref(size_needed))
|
||||
except WindowsError as e:
|
||||
pass
|
||||
size = size_needed.value + 0x1000 # In case we have some more handle created
|
||||
buf = windows.utils.BUFFER(gdef.SYSTEM_HANDLE_INFORMATION64)(size=size)
|
||||
size_needed.value = 0
|
||||
windows.syswow64.NtQuerySystemInformation_32_to_64(gdef.SystemHandleInformation, buf, buf.real_size, ReturnLength=ctypes.byref(size_needed))
|
||||
handle_array = windows.utils.resized_array(buf[0].Handles, buf[0].HandleCount, HandleWow64)
|
||||
return list(handle_array)
|
||||
|
||||
|
||||
def enumerate_type():
|
||||
"WIP: DO NOT USE"
|
||||
size_needed = DWORD()
|
||||
fsize = 8
|
||||
fbuffer = ctypes.c_buffer(fsize)
|
||||
try:
|
||||
winproxy.NtQueryObject(None, gdef.ObjectTypesInformation, fbuffer, fsize, ctypes.byref(size_needed))
|
||||
except WindowsError as e:
|
||||
if e.code != STATUS_INFO_LENGTH_MISMATCH:
|
||||
raise
|
||||
else:
|
||||
# We had enought memory ?
|
||||
return
|
||||
|
||||
# Looks like the Wow64 syscall emulation is broken :D
|
||||
# It write AFTER the buffer if we are a wow64 process :D
|
||||
# So better allocate a standalone buffer (triggering a ACCESS_VIOLATION) that corrupting the heap
|
||||
# This is a worst case scenario, as we allocation more space it should not happen !
|
||||
size = size_needed.value + 0x200
|
||||
size_needed.value = 0
|
||||
|
||||
with windows.current_process.allocated_memory(size, gdef.PAGE_READWRITE) as buffer_base:
|
||||
winproxy.NtQueryObject(None, gdef.ObjectTypesInformation, buffer_base, size, ctypes.byref(size_needed))
|
||||
# Cache some exceptions ?
|
||||
# Parse the buffer data in-place as string are addr-dependant
|
||||
types_info = gdef.OBJECT_TYPES_INFORMATION.from_address(buffer_base)
|
||||
offset = ctypes.sizeof(gdef.PVOID) # Looks like the size of the struct is PTR aligned as the struct is follower by other stuff
|
||||
for i in range(types_info.NumberOfTypes):
|
||||
info = gdef.PUBLIC_OBJECT_TYPE_INFORMATION.from_address(buffer_base + offset)
|
||||
yield info
|
||||
offset += ctypes.sizeof(gdef.PUBLIC_OBJECT_TYPE_INFORMATION) + info.TypeName.MaximumLength
|
||||
if offset % ctypes.sizeof(gdef.PVOID):
|
||||
offset += ctypes.sizeof(gdef.PVOID) - (offset % ctypes.sizeof(gdef.PVOID))
|
||||
# End-of ctx-manager
|
||||
return
|
||||
@@ -0,0 +1,447 @@
|
||||
import windows
|
||||
import ctypes
|
||||
import socket
|
||||
import struct
|
||||
|
||||
from windows import winproxy
|
||||
import windows.generated_def as gdef
|
||||
from windows.com import interfaces as cominterfaces
|
||||
from windows.generated_def.winstructs import *
|
||||
from windows.generated_def.windef import *
|
||||
|
||||
|
||||
class TCP4Connection(MIB_TCPROW_OWNER_PID):
|
||||
"""A TCP4 socket (connected or listening)"""
|
||||
@property
|
||||
def established(self):
|
||||
"""``True`` if connection is established else it's a listening socket"""
|
||||
return self.dwState == MIB_TCP_STATE_ESTAB
|
||||
|
||||
@property
|
||||
def remote_port(self):
|
||||
""":type: :class:`int`"""
|
||||
if not self.established:
|
||||
return None
|
||||
return socket.ntohs(self.dwRemotePort)
|
||||
|
||||
@property
|
||||
def local_port(self):
|
||||
""":type: :class:`int`"""
|
||||
return socket.ntohs(self.dwLocalPort)
|
||||
|
||||
@property
|
||||
def local_addr(self):
|
||||
"""Local address IP (x.x.x.x)
|
||||
|
||||
:type: :class:`str`"""
|
||||
return socket.inet_ntoa(struct.pack("<I", self.dwLocalAddr))
|
||||
|
||||
@property
|
||||
def remote_addr(self):
|
||||
"""remote address IP (x.x.x.x)
|
||||
|
||||
:type: :class:`str`"""
|
||||
if not self.established:
|
||||
return None
|
||||
return socket.inet_ntoa(struct.pack("<I", self.dwRemoteAddr))
|
||||
|
||||
@property
|
||||
def remote_proto(self):
|
||||
"""Identification of the protocol associated with the remote port.
|
||||
Equals ``remote_port`` if no protocol is associated with it.
|
||||
|
||||
:type: :class:`str` or :class:`int`
|
||||
"""
|
||||
try:
|
||||
return socket.getservbyport(self.remote_port, 'tcp')
|
||||
except socket.error:
|
||||
return self.remote_port
|
||||
|
||||
@property
|
||||
def remote_host(self):
|
||||
"""Identification of the remote hostname.
|
||||
Equals ``remote_addr`` if the resolution fails
|
||||
|
||||
:type: :class:`str` or :class:`int`
|
||||
"""
|
||||
|
||||
try:
|
||||
return socket.gethostbyaddr(self.remote_addr)
|
||||
except socket.error:
|
||||
return self.remote_addr
|
||||
|
||||
def close(self):
|
||||
"""Close the connection <require elevated process>"""
|
||||
closing = MIB_TCPROW()
|
||||
closing.dwState = MIB_TCP_STATE_DELETE_TCB
|
||||
closing.dwLocalAddr = self.dwLocalAddr
|
||||
closing.dwLocalPort = self.dwLocalPort
|
||||
closing.dwRemoteAddr = self.dwRemoteAddr
|
||||
closing.dwRemotePort = self.dwRemotePort
|
||||
return winproxy.SetTcpEntry(ctypes.byref(closing))
|
||||
|
||||
def __repr__(self):
|
||||
if not self.established:
|
||||
return "<TCP IPV4 Listening socket on {0}:{1}>".format(self.local_addr, self.local_port)
|
||||
return "<TCP IPV4 Connection {s.local_addr}:{s.local_port} -> {s.remote_addr}:{s.remote_port}>".format(s=self)
|
||||
|
||||
|
||||
class TCP6Connection(MIB_TCP6ROW_OWNER_PID):
|
||||
"""A TCP6 socket (connected or listening)"""
|
||||
@staticmethod
|
||||
def _str_ipv6_addr(addr):
|
||||
return ":".join(c.encode('hex') for c in addr)
|
||||
|
||||
@property
|
||||
def established(self):
|
||||
"""``True`` if connection is established else it's a listening socket"""
|
||||
return self.dwState == MIB_TCP_STATE_ESTAB
|
||||
|
||||
@property
|
||||
def remote_port(self):
|
||||
""":type: :class:`int`"""
|
||||
if not self.established:
|
||||
return None
|
||||
return socket.ntohs(self.dwRemotePort)
|
||||
|
||||
@property
|
||||
def local_port(self):
|
||||
""":type: :class:`int`"""
|
||||
return socket.ntohs(self.dwLocalPort)
|
||||
|
||||
@property
|
||||
def local_addr(self):
|
||||
"""Local address IP
|
||||
|
||||
:type: :class:`str`"""
|
||||
return self._str_ipv6_addr(self.ucLocalAddr)
|
||||
|
||||
@property
|
||||
def remote_addr(self):
|
||||
"""remote address IP
|
||||
|
||||
:type: :class:`str`"""
|
||||
if not self.established:
|
||||
return None
|
||||
return self._str_ipv6_addr(self.ucRemoteAddr)
|
||||
|
||||
@property
|
||||
def remote_proto(self):
|
||||
"""Equals to ``self.remote_port`` for Ipv6"""
|
||||
return self.remote_port
|
||||
|
||||
@property
|
||||
def remote_host(self):
|
||||
"""Equals to ``self.remote_addr`` for Ipv6"""
|
||||
return self.remote_addr
|
||||
|
||||
def close(self):
|
||||
raise NotImplementedError("Closing IPV6 connection non implemented")
|
||||
|
||||
def __repr__(self):
|
||||
if not self.established:
|
||||
return "<TCP IPV6 Listening socket on {0}:{1}>".format(self.local_addr, self.local_port)
|
||||
return "<TCP IPV6 Connection {0}:{1} -> {2}:{3}>".format(self.local_addr, self.local_port, self.remote_addr, self.remote_port)
|
||||
|
||||
|
||||
def get_MIB_TCPTABLE_OWNER_PID_from_buffer(buffer):
|
||||
x = windows.generated_def.winstructs.MIB_TCPTABLE_OWNER_PID.from_buffer(buffer)
|
||||
nb_entry = x.dwNumEntries
|
||||
|
||||
class _GENERATED_MIB_TCPTABLE_OWNER_PID(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("dwNumEntries", DWORD),
|
||||
("table", TCP4Connection * nb_entry),
|
||||
]
|
||||
|
||||
return _GENERATED_MIB_TCPTABLE_OWNER_PID.from_buffer(buffer)
|
||||
|
||||
|
||||
def get_MIB_TCP6TABLE_OWNER_PID_from_buffer(buffer):
|
||||
x = windows.generated_def.winstructs.MIB_TCP6TABLE_OWNER_PID.from_buffer(buffer)
|
||||
nb_entry = x.dwNumEntries
|
||||
|
||||
# Struct _MIB_TCP6TABLE_OWNER_PID definitions
|
||||
class _GENERATED_MIB_TCP6TABLE_OWNER_PID(Structure):
|
||||
_fields_ = [
|
||||
("dwNumEntries", DWORD),
|
||||
("table", TCP6Connection * nb_entry),
|
||||
]
|
||||
|
||||
return _GENERATED_MIB_TCP6TABLE_OWNER_PID.from_buffer(buffer)
|
||||
|
||||
class Firewall(cominterfaces.INetFwPolicy2):
|
||||
"""The windows firewall"""
|
||||
@property
|
||||
def rules(self):
|
||||
"""The rules of the firewall
|
||||
|
||||
:type: [:class:`FirewallRule`] -- A list of rule
|
||||
"""
|
||||
ifw_rules = cominterfaces.INetFwRules()
|
||||
self.get_Rules(ifw_rules)
|
||||
|
||||
nb_rules = gdef.LONG()
|
||||
ifw_rules.get_Count(nb_rules)
|
||||
|
||||
unknw = cominterfaces.IUnknown()
|
||||
ifw_rules.get__NewEnum(unknw)
|
||||
|
||||
pVariant = cominterfaces.IEnumVARIANT()
|
||||
unknw.QueryInterface(pVariant.IID, pVariant)
|
||||
|
||||
count = gdef.ULONG()
|
||||
var = windows.com.Variant()
|
||||
|
||||
rules = []
|
||||
for i in range(nb_rules.value):
|
||||
pVariant.Next(1, var, count)
|
||||
if not count.value:
|
||||
break
|
||||
rule = FirewallRule()
|
||||
idisp = var.asdispatch
|
||||
idisp.QueryInterface(rule.IID, rule)
|
||||
rules.append(rule)
|
||||
return rules
|
||||
|
||||
@property
|
||||
def current_profile_types(self):
|
||||
"""Mask of the profiles currently enabled
|
||||
|
||||
:type: :class:`long`
|
||||
"""
|
||||
cpt = gdef.LONG()
|
||||
self.get_CurrentProfileTypes(cpt)
|
||||
return cpt.value
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
"""A maping of the active firewall profiles
|
||||
|
||||
{
|
||||
|
||||
``NET_FW_PROFILE_TYPE2_.NET_FW_PROFILE2_DOMAIN(0x1L)``: ``True`` or ``False``,
|
||||
|
||||
``NET_FW_PROFILE_TYPE2_.NET_FW_PROFILE2_PRIVATE(0x2L)``: ``True`` or ``False``,
|
||||
|
||||
``NET_FW_PROFILE_TYPE2_.NET_FW_PROFILE2_PUBLIC(0x4L)``: ``True`` or ``False``,
|
||||
|
||||
}
|
||||
|
||||
|
||||
:type: :class:`dict`
|
||||
"""
|
||||
profiles = [gdef.NET_FW_PROFILE2_DOMAIN, gdef.NET_FW_PROFILE2_PRIVATE, gdef.NET_FW_PROFILE2_PUBLIC]
|
||||
return {prof: self.enabled_for_profile_type(prof) for prof in profiles}
|
||||
|
||||
|
||||
def enabled_for_profile_type(self, profile_type):
|
||||
enabled = gdef.VARIANT_BOOL()
|
||||
self.get_FirewallEnabled(profile_type, enabled)
|
||||
return enabled.value
|
||||
|
||||
|
||||
|
||||
class FirewallRule(cominterfaces.INetFwRule):
|
||||
"""A rule of the firewall"""
|
||||
@property
|
||||
def name(self):
|
||||
"""Name of the rule
|
||||
|
||||
:type: :class:`unicode`
|
||||
"""
|
||||
name = gdef.BSTR()
|
||||
self.get_Name(name)
|
||||
return name.value
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
"""Description of the rule
|
||||
|
||||
:type: :class:`unicode`
|
||||
"""
|
||||
description = gdef.BSTR()
|
||||
self.get_Description(description)
|
||||
return description.value
|
||||
|
||||
@property
|
||||
def application_name(self):
|
||||
"""Name of the application to which apply the rule
|
||||
|
||||
:type: :class:`unicode`
|
||||
"""
|
||||
applicationname = gdef.BSTR()
|
||||
self.get_ApplicationName(applicationname)
|
||||
return applicationname.value
|
||||
|
||||
@property
|
||||
def service_name(self):
|
||||
"""Name of the service to which apply the rule
|
||||
|
||||
:type: :class:`unicode`
|
||||
"""
|
||||
servicename = gdef.BSTR()
|
||||
self.get_ServiceName(servicename)
|
||||
return servicename.value
|
||||
|
||||
@property
|
||||
def protocol(self):
|
||||
"""Protocol to which apply the rule
|
||||
|
||||
:type: :class:`long`
|
||||
"""
|
||||
protocol = gdef.LONG()
|
||||
self.get_Protocol(protocol)
|
||||
return protocol.value
|
||||
|
||||
@property
|
||||
def local_address(self):
|
||||
"""Local address of the rule
|
||||
|
||||
:type: :class:`unicode`
|
||||
"""
|
||||
local_address = gdef.BSTR()
|
||||
self.get_LocalAddresses(local_address)
|
||||
return local_address.value
|
||||
|
||||
@property
|
||||
def remote_address(self):
|
||||
"""Remote address of the rule
|
||||
|
||||
:type: :class:`unicode`
|
||||
"""
|
||||
remote_address = gdef.BSTR()
|
||||
self.get_RemoteAddresses(remote_address)
|
||||
return remote_address.value
|
||||
|
||||
@property
|
||||
def direction(self):
|
||||
"""Direction of the rule, values might be:
|
||||
|
||||
* ``NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN(0x1L)``
|
||||
* ``NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_OUT(0x2L)``
|
||||
|
||||
subclass of :class:`long`
|
||||
"""
|
||||
direction = gdef.NET_FW_RULE_DIRECTION()
|
||||
self.get_Direction(direction)
|
||||
return direction.value
|
||||
|
||||
@property
|
||||
def interface_types(self):
|
||||
"""Types of interface of the rule
|
||||
|
||||
:type: :class:`unicode`
|
||||
"""
|
||||
interface_type = gdef.BSTR()
|
||||
self.get_InterfaceTypes(interface_type)
|
||||
return interface_type.value
|
||||
|
||||
@property
|
||||
def local_port(self):
|
||||
"""Local port of the rule
|
||||
|
||||
:type: :class:`unicode`
|
||||
"""
|
||||
local_port = gdef.BSTR()
|
||||
self.get_LocalPorts(local_port)
|
||||
return local_port.value
|
||||
|
||||
@property
|
||||
def remote_port(self):
|
||||
"""Remote port of the rule
|
||||
|
||||
:type: :class:`unicode`
|
||||
"""
|
||||
remote_port = gdef.BSTR()
|
||||
self.get_RemotePorts(remote_port)
|
||||
return remote_port.value
|
||||
|
||||
@property
|
||||
def action(self):
|
||||
"""Action of the rule, values might be:
|
||||
|
||||
* ``NET_FW_ACTION_.NET_FW_ACTION_BLOCK(0x0L)``
|
||||
* ``NET_FW_ACTION_.NET_FW_ACTION_ALLOW(0x1L)``
|
||||
|
||||
subclass of :class:`long`
|
||||
"""
|
||||
action = gdef.NET_FW_ACTION()
|
||||
self.get_Action(action)
|
||||
return action.value
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
"""``True`` if rule is enabled"""
|
||||
enabled = gdef.VARIANT_BOOL()
|
||||
self.get_Enabled(enabled)
|
||||
return enabled.value
|
||||
|
||||
@property
|
||||
def grouping(self):
|
||||
"""Grouping of the rule
|
||||
|
||||
:type: :class:`unicode`
|
||||
"""
|
||||
grouping = gdef.BSTR()
|
||||
self.get_RemotePorts(grouping)
|
||||
return grouping.value
|
||||
|
||||
@property
|
||||
def icmp_type_and_code(self):
|
||||
icmp_type_and_code = gdef.BSTR()
|
||||
self.get_RemotePorts(icmp_type_and_code)
|
||||
return icmp_type_and_code.value
|
||||
|
||||
def __repr__(self):
|
||||
return u'<{0} "{1}">'.format(type(self).__name__, self.name).encode("ascii", errors='backslashreplace')
|
||||
|
||||
class Network(object):
|
||||
NetFwPolicy2 = windows.com.IID.from_string("E2B3C97F-6AE1-41AC-817A-F6F92166D7DD")
|
||||
|
||||
@property
|
||||
def firewall(self):
|
||||
"""The firewall of the system
|
||||
|
||||
:type: :class:`Firewall`
|
||||
"""
|
||||
windows.com.init()
|
||||
firewall = Firewall()
|
||||
windows.com.create_instance(self.NetFwPolicy2, firewall)
|
||||
return firewall
|
||||
|
||||
@staticmethod
|
||||
def _get_tcp_ipv4_sockets():
|
||||
size = ctypes.c_uint(0)
|
||||
try:
|
||||
winproxy.GetExtendedTcpTable(None, ctypes.byref(size), ulAf=AF_INET)
|
||||
except winproxy.WinproxyError:
|
||||
pass # Allow us to set size to the needed value
|
||||
buffer = (ctypes.c_char * size.value)()
|
||||
winproxy.GetExtendedTcpTable(buffer, ctypes.byref(size), ulAf=AF_INET)
|
||||
t = get_MIB_TCPTABLE_OWNER_PID_from_buffer(buffer)
|
||||
return list(t.table)
|
||||
|
||||
@staticmethod
|
||||
def _get_tcp_ipv6_sockets():
|
||||
size = ctypes.c_uint(0)
|
||||
try:
|
||||
winproxy.GetExtendedTcpTable(None, ctypes.byref(size), ulAf=AF_INET6)
|
||||
except winproxy.WinproxyError:
|
||||
pass # Allow us to set size to the needed value
|
||||
buffer = (ctypes.c_char * size.value)()
|
||||
winproxy.GetExtendedTcpTable(buffer, ctypes.byref(size), ulAf=AF_INET6)
|
||||
t = get_MIB_TCP6TABLE_OWNER_PID_from_buffer(buffer)
|
||||
return list(t.table)
|
||||
|
||||
|
||||
ipv4 = property(lambda self: self._get_tcp_ipv4_sockets())
|
||||
"""List of TCP IPv4 socket (connection and listening)
|
||||
|
||||
:type: [:class:`TCP4Connection`]"""
|
||||
|
||||
ipv6 = property(lambda self: self._get_tcp_ipv6_sockets())
|
||||
"""List of TCP IPv6 socket (connection and listening)
|
||||
|
||||
:type: [:class:`TCP6Connection`]
|
||||
"""
|
||||
@@ -0,0 +1,226 @@
|
||||
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]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,450 @@
|
||||
import sys
|
||||
import ctypes
|
||||
import itertools
|
||||
import struct
|
||||
from collections import namedtuple, defaultdict
|
||||
|
||||
import windows
|
||||
from windows.dbgprint import dbgprint
|
||||
import windows.generated_def as gdef
|
||||
from windows import winproxy
|
||||
from windows.pycompat import basestring, int_types, is_py3
|
||||
|
||||
WENCODING = "utf-16-le"
|
||||
|
||||
# So _winreg does not handle unicode stuff in Py2 :(
|
||||
# Need to rewrite everything to get it working with unicode
|
||||
|
||||
class WinRegistryKey(gdef.HKEY):
|
||||
_close_function = staticmethod(winproxy.RegCloseKey)
|
||||
|
||||
def __del__(self):
|
||||
if sys is None or sys.path is None: # Late shutdown (not sur winproxy is still up)
|
||||
return
|
||||
if self: # Not NULL handle ?
|
||||
dbgprint(u"Closing registry key handle {0:#x}".format(self.value), 'REGISTRY')
|
||||
self._close_function(self)
|
||||
|
||||
|
||||
|
||||
|
||||
class ExpectWindowsError(object):
|
||||
def __init__(self, errornumber):
|
||||
self.errornumber = errornumber
|
||||
|
||||
def __enter__(self):
|
||||
pass
|
||||
|
||||
def __exit__(self, etype, e, tb):
|
||||
return (etype in (winproxy.WinproxyError, WindowsError) and e.winerror == self.errornumber)
|
||||
|
||||
# Translation reg-buffer <-> python methodes
|
||||
def Reg2Py_QWORD(buffer, size):
|
||||
return buffer.cast(gdef.PULONG64)[0]
|
||||
|
||||
def Py2Reg_QWORD(obj):
|
||||
return struct.pack("<Q", obj)
|
||||
|
||||
def Reg2Py_DWORD(buffer, size):
|
||||
# Check size ?
|
||||
return buffer.cast(gdef.LPDWORD)[0]
|
||||
|
||||
def Py2Reg_DWORD(obj):
|
||||
return struct.pack("<I", obj)
|
||||
|
||||
|
||||
def Reg2Py_DWORD_BIG_ENDIAN(buffer, size):
|
||||
# Check size ?
|
||||
return (buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3]
|
||||
|
||||
def Py2Reg_DWORD_BIG_ENDIAN(obj):
|
||||
return struct.pack(">I", obj)
|
||||
|
||||
|
||||
def Reg2Py_BINARY(buffer, size):
|
||||
return bytes(bytearray(buffer[:size]))
|
||||
|
||||
def Py2Reg_BINARY(obj):
|
||||
# latin-1 encoding if py3 & type is str ?
|
||||
return obj
|
||||
|
||||
|
||||
def Reg2Py_SZ(buffer, size):
|
||||
# Buffer is UTF16. buffer is extended-buffer
|
||||
if size == 0:
|
||||
return u""
|
||||
if buffer[size - 1] == 0 and buffer[size - 2] == 0:
|
||||
# NULL TERMINATED: EASY
|
||||
return buffer.as_wstring()
|
||||
# Not null terminated: keep last byte
|
||||
assert not size % 2
|
||||
return (gdef.WCHAR * (size // 2)).from_buffer(buffer)[:]
|
||||
|
||||
def Py2Reg_SZ(obj):
|
||||
return obj.encode(WENCODING)
|
||||
|
||||
def Reg2Py_Multi_SZ(buffer, size):
|
||||
if not size:
|
||||
return []
|
||||
# Simple path
|
||||
if is_py3:
|
||||
rawstr = bytes(buffer)
|
||||
else:
|
||||
rawstr = "".join([chr(c) for c in buffer[:size]])
|
||||
try:
|
||||
unistr = rawstr.decode(WENCODING)
|
||||
return unistr.rstrip(u"\x00").split(u"\x00")
|
||||
except UnicodeDecodeError as e:
|
||||
pass
|
||||
# Complexe-path
|
||||
# This is not some valide UTF-16
|
||||
# Try our best to extract some stuff from raw
|
||||
return rawstr.rstrip(b"\x00").split(b"\x00")
|
||||
|
||||
|
||||
def Py2Reg_Multi_SZ(obj):
|
||||
# Work on encoded values (to prevent str/unicode errors)
|
||||
uni_list = [s.encode(WENCODING) for s in obj]
|
||||
# Separate by UTF-16 NULL BYTE (2 \x00)
|
||||
uni_str = b"\x00\x00".join(uni_list)
|
||||
# Add UTF-16 NULL byte for final string + final UTF-16 \x00 (4 \x00)
|
||||
return uni_str + b"\x00\x00\x00\x00"
|
||||
|
||||
DECODE_METHOD = 0
|
||||
ENCODE_METHOD = 1
|
||||
|
||||
|
||||
KNOWN_ENCODE_DECODE_METHODS = {
|
||||
gdef.REG_SZ: (Reg2Py_SZ, Py2Reg_SZ),
|
||||
gdef.REG_EXPAND_SZ: (Reg2Py_SZ, Py2Reg_SZ),
|
||||
gdef.REG_MULTI_SZ: (Reg2Py_Multi_SZ, Py2Reg_Multi_SZ),
|
||||
gdef.REG_DWORD: (Reg2Py_DWORD, Py2Reg_DWORD),
|
||||
gdef.REG_DWORD_BIG_ENDIAN: (Reg2Py_DWORD_BIG_ENDIAN, Py2Reg_DWORD_BIG_ENDIAN),
|
||||
gdef.REG_QWORD: (Reg2Py_QWORD, Py2Reg_QWORD),
|
||||
# Binary formats
|
||||
gdef.REG_LINK: (Reg2Py_BINARY, Py2Reg_BINARY), # TESTING
|
||||
gdef.REG_BINARY: (Reg2Py_BINARY, Py2Reg_BINARY),
|
||||
gdef.REG_NONE: (Reg2Py_BINARY, Py2Reg_BINARY),
|
||||
}
|
||||
|
||||
# All unknown format are seens as binary data
|
||||
UNKNOWM_FORMAT = (Reg2Py_BINARY, Py2Reg_BINARY)
|
||||
ENCODE_DECODE_METHODS = defaultdict(lambda: UNKNOWM_FORMAT, KNOWN_ENCODE_DECODE_METHODS)
|
||||
|
||||
def decode_registry_buffer(type, buffer, size):
|
||||
try:
|
||||
return ENCODE_DECODE_METHODS[type][DECODE_METHOD](buffer, size)
|
||||
except UnicodeDecodeError as e:
|
||||
# Best effort if any decoding error happen
|
||||
return "".join(chr(c) for c in buffer[:size])
|
||||
|
||||
|
||||
KeyValue = namedtuple("KeyValue", ["name", "value", "type"])
|
||||
"""A registry value (name, value, type)"""
|
||||
|
||||
|
||||
class PyHKey(object):
|
||||
"""A windows registry key"""
|
||||
def __init__(self, surkey, name, sam=gdef.KEY_READ):
|
||||
self.surkey = surkey
|
||||
self.name = name
|
||||
self.fullname = self.surkey.fullname + "\\" + self.name if self.name else self.surkey.name
|
||||
self.sam = sam
|
||||
self._phkey = None
|
||||
#self.phkey
|
||||
|
||||
def __repr__(self):
|
||||
return '<PyHKey "{0}">'.format(self.fullname)
|
||||
|
||||
def _open_key(self, handle, name, sam):
|
||||
result = WinRegistryKey()
|
||||
winproxy.RegOpenKeyExW(handle, name, 0, sam, result) # TODO: options REG_OPTION_OPEN_LINK
|
||||
dbgprint(u"Opening registry key <{0}> (handle={1:#x})".format(name, result.value), "REGISTRY")
|
||||
return result
|
||||
|
||||
def _create_key(self, parent, name, sam):
|
||||
result = WinRegistryKey()
|
||||
flags = 0
|
||||
winproxy.RegCreateKeyExW(parent, name, 0, None, flags, sam, None, result, None)
|
||||
dbgprint(u"Creating registry key <{0}> (handle={1:#x})".format(name, result.value), "REGISTRY")
|
||||
return result
|
||||
|
||||
@property
|
||||
def phkey(self):
|
||||
if self._phkey is not None:
|
||||
return self._phkey
|
||||
try:
|
||||
self._phkey = self._open_key(self.surkey.phkey, self.name, self.sam)
|
||||
except WindowsError as e:
|
||||
raise WindowsError(e.winerror, "Could not open registry key <{0}> ({1})".format(self.fullname, e.strerror))
|
||||
return self._phkey
|
||||
|
||||
@property
|
||||
def exists(self):
|
||||
# May have been deleted in between
|
||||
# So <self._phkey> tells use nothing
|
||||
if self._phkey: # Not None + pointer not NULL
|
||||
try:
|
||||
self.get_key_size_info()
|
||||
except WindowsError as e:
|
||||
if e.winerror == gdef.ERROR_KEY_DELETED:
|
||||
return False
|
||||
raise
|
||||
return True
|
||||
try:
|
||||
tmpphkey = self._open_key(self.surkey.phkey, self.name, gdef.KEY_READ)
|
||||
except WindowsError as e:
|
||||
return False
|
||||
# tmpphkey will be garbage collected and auto-closed
|
||||
return True
|
||||
|
||||
@property
|
||||
def subkeys(self):
|
||||
"""The subkeys of the registry key
|
||||
|
||||
:type: [:class:`PyHKey`] - A list of keys"""
|
||||
res = []
|
||||
with ExpectWindowsError(259):
|
||||
default_name_size = 256 + 1
|
||||
name_size = gdef.DWORD(default_name_size)
|
||||
name_buffer = ctypes.create_unicode_buffer(name_size.value)
|
||||
for i in itertools.count():
|
||||
name_size.value = default_name_size
|
||||
winproxy.RegEnumKeyExW(self.phkey, i, name_buffer, name_size, None, None, None, None)
|
||||
res.append(name_buffer[:name_size.value]) # Will allow key name with \x00 inside
|
||||
return [PyHKey(self, n) for n in res]
|
||||
|
||||
def get_key_size_info(self):
|
||||
max_name_len = gdef.DWORD()
|
||||
max_value_len = gdef.DWORD()
|
||||
winproxy.RegQueryInfoKeyW(self.phkey, None, None, None, None, None, None, None, max_name_len, max_value_len, None, None)
|
||||
return (max_name_len.value, max_value_len.value)
|
||||
|
||||
@property
|
||||
def values(self):
|
||||
"""The values of the registry key
|
||||
|
||||
:type: [:class:`KeyValue`] - A list of values"""
|
||||
res = []
|
||||
# Get max info keys
|
||||
|
||||
max_name_size, max_data_size = self.get_key_size_info()
|
||||
# Null terminators
|
||||
max_name_size += 1
|
||||
max_data_size += 2
|
||||
with ExpectWindowsError(259):
|
||||
for i in itertools.count():
|
||||
value_type = gdef.DWORD()
|
||||
namesize = gdef.DWORD(max_name_size)
|
||||
keyname = ctypes.create_unicode_buffer(namesize.value)
|
||||
datasize = gdef.DWORD(max_data_size)
|
||||
databuffer = windows.utils.BUFFER(gdef.BYTE, nbelt=datasize.value)()
|
||||
# A value can have been added in-between.
|
||||
# So recheck the size given by get_key_size_info :)
|
||||
# But check 10 times max as RegEnumValueW may bug (seen) and always return ERROR_MORE_DATA even with enought size
|
||||
for _ in range(10):
|
||||
try:
|
||||
winproxy.RegEnumValueW(self.phkey, i, keyname, namesize, None, value_type, databuffer, datasize)
|
||||
break
|
||||
except WindowsError as e:
|
||||
if e.winerror != gdef.ERROR_MORE_DATA:
|
||||
raise
|
||||
# I found some strange Windows where even with a big enought buffer:
|
||||
# - the data was filled
|
||||
# - ERROR_MORE_DATA was returned
|
||||
## To prevent such bug to trigger and infinite loop, two things
|
||||
# - If the retuned namesize <= the passed keysize and keyname is not empty -> return the data`
|
||||
# - Max 10 test to prevent Infinite loop
|
||||
if ((namesize.value <= max_name_size) and (datasize.value <= max_data_size) and
|
||||
(keyname[:namesize.value].count("\x00") < namesize.value)): # Not just 0 Zero ?
|
||||
break
|
||||
|
||||
# Update the sizes / buffers & try again :)
|
||||
max_name_size, max_data_size = self.get_key_size_info()
|
||||
max_name_size = max(max_name_size + 1, namesize.value + 1) # namesize.value may be > to max_name_size apparently (guessed)
|
||||
max_data_size = max(max_data_size + 2, datasize.value + 2) # datasize.value may be > to max_data_size apparently (seen)
|
||||
namesize = gdef.DWORD(max_name_size)
|
||||
keyname = ctypes.create_unicode_buffer(namesize.value)
|
||||
datasize = gdef.DWORD(max_data_size)
|
||||
databuffer = windows.utils.BUFFER(gdef.BYTE, nbelt=datasize.value)()
|
||||
else:
|
||||
# Probably a windows bug that prevent us from retrieving the data
|
||||
# Raise something (thus preventing getting the other values..) ? ignore it ?
|
||||
raise ValueError("Could not extract registry key values, problably a Windows/hook bug")
|
||||
vobj = decode_registry_buffer(value_type.value, databuffer, datasize.value)
|
||||
res.append(KeyValue(keyname.value, vobj, value_type.value))
|
||||
return res
|
||||
|
||||
|
||||
@property
|
||||
def info(self):
|
||||
# Need other stuff ?
|
||||
nb_key = gdef.DWORD()
|
||||
nb_values = gdef.DWORD()
|
||||
last_modif = gdef.FILETIME()
|
||||
winproxy.RegQueryInfoKeyW(self.phkey, None, None, None, nb_key, None, None, nb_values, None, None, None, last_modif)
|
||||
return nb_key.value, nb_values.value, int(last_modif)
|
||||
|
||||
@property
|
||||
def last_write(self):
|
||||
return self.info[2]
|
||||
|
||||
def get(self, value_name):
|
||||
"""Retrieves the value ``value_name``
|
||||
|
||||
:rtype: :class:`KeyValue`
|
||||
"""
|
||||
|
||||
type = gdef.DWORD(0)
|
||||
size = gdef.DWORD(0x100)
|
||||
while True:
|
||||
buffer = windows.utils.BUFFER(gdef.BYTE, nbelt=size.value)()
|
||||
try:
|
||||
winproxy.RegQueryValueExW(self.phkey, value_name, None, type, buffer, size)
|
||||
break
|
||||
except WindowsError as e:
|
||||
if e.winerror != gdef.ERROR_MORE_DATA:
|
||||
raise
|
||||
size.value *= 2
|
||||
buffer = windows.utils.BUFFER(gdef.BYTE, nbelt=size.value)()
|
||||
continue
|
||||
vobj = decode_registry_buffer(type.value, buffer, size.value)
|
||||
return KeyValue(value_name, vobj, type.value)
|
||||
|
||||
def _guess_value_type(self, value):
|
||||
if isinstance(value, basestring):
|
||||
return gdef.REG_SZ
|
||||
elif isinstance(value, int_types):
|
||||
return gdef.REG_DWORD
|
||||
# elif isinstance(value, (list, tuple)):
|
||||
# if all(isinstance(v, basestring) in value):
|
||||
# return _winreg.REG_MULTI_SZ
|
||||
raise ValueError("Cannot guest registry type of value to set <{0}>".format(value))
|
||||
|
||||
|
||||
def set(self, name, value, type=None):
|
||||
"""Set the value for ``name`` to ``value``. if ``type`` is None try to guess items"""
|
||||
if type is None:
|
||||
type = self._guess_value_type(value)
|
||||
|
||||
|
||||
buffer = ENCODE_DECODE_METHODS[type][ENCODE_METHOD](value)
|
||||
if isinstance(buffer, bytes): # Should not be unicode at this point
|
||||
buffer = windows.utils.BUFFER(gdef.BYTE).from_buffer_copy(buffer)
|
||||
return winproxy.RegSetValueExW(self.phkey, name, 0, type, buffer, len(buffer))
|
||||
|
||||
def delete_value(self, name):
|
||||
"""Delete the value with ``name``"""
|
||||
return winproxy.RegDeleteValueW(self.phkey, name)
|
||||
|
||||
|
||||
def open_subkey(self, name, sam=None):
|
||||
"""Open the subkey ``name``
|
||||
|
||||
:rtype: :class:`PyHKey`
|
||||
"""
|
||||
if sam is None:
|
||||
sam = self.sam
|
||||
return PyHKey(self, name, sam)
|
||||
|
||||
def reopen(self, sam):
|
||||
"""Reopen the registry key with a new ``sam``
|
||||
|
||||
:rtype: :class:`PyHKey`
|
||||
"""
|
||||
return PyHKey(self.surkey, self.name, sam)
|
||||
|
||||
def create(self):
|
||||
"""Create the registry key"""
|
||||
try:
|
||||
self._phkey = self._create_key(self.surkey.phkey, self.name, self.sam)
|
||||
except WindowsError as e:
|
||||
raise WindowsError(e.winerror, "Could not create registry key <{0}> ({1})".format(self.fullname, e.strerror))
|
||||
return self
|
||||
|
||||
def delete(self):
|
||||
"""Delete the registry key"""
|
||||
# Allow a 'recursive' param to empty before delete ?
|
||||
try:
|
||||
windows.winproxy.RegDeleteKeyExW(self.surkey.phkey, self.name, self.sam, 0)
|
||||
except WindowsError as e:
|
||||
raise WindowsError(e.winerror, "Could not delete registry key <{0}> ({1})".format(self.fullname, e.strerror))
|
||||
return None
|
||||
|
||||
def empty(self):
|
||||
windows.winproxy.RegDeleteTreeW(self.phkey, None)
|
||||
|
||||
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
rtype = None
|
||||
if not (isinstance(value, basestring) or isinstance(value, int_types)):
|
||||
value, rtype = value
|
||||
return self.set(name, value, rtype)
|
||||
|
||||
__getitem__ = get
|
||||
|
||||
__delitem__ = delete_value
|
||||
|
||||
__call__ = open_subkey
|
||||
|
||||
|
||||
class DummyPHKEY(object):
|
||||
def __init__(self, phkey, name):
|
||||
self.phkey = phkey
|
||||
self.name = name
|
||||
|
||||
|
||||
HKEY_LOCAL_MACHINE = PyHKey(DummyPHKEY(gdef.HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE"), "", gdef.KEY_READ)
|
||||
HKEY_CLASSES_ROOT = PyHKey(DummyPHKEY(gdef.HKEY_CLASSES_ROOT, "HKEY_CLASSES_ROOT"), "", gdef.KEY_READ )
|
||||
HKEY_CURRENT_USER = PyHKey(DummyPHKEY(gdef.HKEY_CURRENT_USER, "HKEY_CURRENT_USER"), "", gdef.KEY_READ)
|
||||
HKEY_DYN_DATA = PyHKey(DummyPHKEY(gdef.HKEY_DYN_DATA, "HKEY_DYN_DATA"), "", gdef.KEY_READ)
|
||||
HKEY_PERFORMANCE_DATA = PyHKey(DummyPHKEY(gdef.HKEY_PERFORMANCE_DATA, "HKEY_PERFORMANCE_DATA"), "", gdef.KEY_READ)
|
||||
HKEY_USERS = PyHKey(DummyPHKEY(gdef.HKEY_USERS, "HKEY_USERS"), "", gdef.KEY_READ )
|
||||
|
||||
|
||||
class Registry(object):
|
||||
"""The ``Windows`` registry"""
|
||||
|
||||
registry_base_keys = {
|
||||
"HKEY_LOCAL_MACHINE" : HKEY_LOCAL_MACHINE,
|
||||
"HKEY_CLASSES_ROOT" : HKEY_CLASSES_ROOT,
|
||||
"HKEY_CURRENT_USER" : HKEY_CURRENT_USER,
|
||||
"HKEY_DYN_DATA" : HKEY_DYN_DATA,
|
||||
"HKEY_PERFORMANCE_DATA": HKEY_PERFORMANCE_DATA,
|
||||
"HKEY_USERS" : HKEY_USERS
|
||||
}
|
||||
|
||||
def __init__(self, sam=gdef.KEY_READ):
|
||||
self.sam = sam
|
||||
|
||||
@classmethod
|
||||
def reopen(cls, sam):
|
||||
"""Return a new :class:`Registry` using ``sam`` as the new default
|
||||
|
||||
:rtype: :class:`Registry`
|
||||
"""
|
||||
return cls(sam)
|
||||
|
||||
def __call__(self, name, sam=None):
|
||||
"""Get a registry key::
|
||||
|
||||
registry(r"HKEY_LOCAL_MACHINE\\Software")
|
||||
registry("HKEY_LOCAL_MACHINE")("Software")
|
||||
|
||||
:rtype: :class:`PyHKey`
|
||||
"""
|
||||
if sam is None:
|
||||
sam = self.sam
|
||||
|
||||
if name in self.registry_base_keys:
|
||||
key = self.registry_base_keys[name]
|
||||
if sam != key.sam:
|
||||
key = key.reopen(sam)
|
||||
return key
|
||||
if "\\" not in name:
|
||||
raise ValueError("Unknow registry base key <{0}>".format(name))
|
||||
base_name, subkey = name.split("\\", 1)
|
||||
if base_name not in self.registry_base_keys:
|
||||
raise ValueError("Unknow registry base key <{0}>".format(base_name))
|
||||
return self.registry_base_keys[base_name](subkey, sam)
|
||||
@@ -0,0 +1,191 @@
|
||||
import ctypes
|
||||
import windows
|
||||
|
||||
from collections import namedtuple
|
||||
from contextlib import contextmanager
|
||||
|
||||
from windows import utils
|
||||
from windows.pycompat import int_types
|
||||
import windows.generated_def as gdef
|
||||
from windows.generated_def import *
|
||||
from windows import security
|
||||
from windows.pycompat import basestring
|
||||
|
||||
"""
|
||||
``type`` might be one of:
|
||||
|
||||
* ``SERVICE_KERNEL_DRIVER(0x1L)``
|
||||
* ``SERVICE_FILE_SYSTEM_DRIVER(0x2L)``
|
||||
* ``SERVICE_WIN32_OWN_PROCESS(0x10L)``
|
||||
* ``SERVICE_WIN32_SHARE_PROCESS(0x20L)``
|
||||
* ``SERVICE_INTERACTIVE_PROCESS(0x100L)``
|
||||
|
||||
``state`` might be one of:
|
||||
|
||||
* ``SERVICE_STOPPED(0x1L)``
|
||||
* ``SERVICE_START_PENDING(0x2L)``
|
||||
* ``SERVICE_STOP_PENDING(0x3L)``
|
||||
* ``SERVICE_RUNNING(0x4L)``
|
||||
* ``SERVICE_CONTINUE_PENDING(0x5L)``
|
||||
* ``SERVICE_PAUSE_PENDING(0x6L)``
|
||||
* ``SERVICE_PAUSED(0x7L)``
|
||||
|
||||
``flags`` might be one of:
|
||||
|
||||
* ``0``
|
||||
* ``SERVICE_RUNS_IN_SYSTEM_PROCESS(0x1L)``
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class ServiceManager(utils.AutoHandle):
|
||||
"""An object to query, list and explore services"""
|
||||
def _get_handle(self):
|
||||
return windows.winproxy.OpenSCManagerW(dwDesiredAccess=gdef.MAXIMUM_ALLOWED)
|
||||
|
||||
def open_service(self, name, access=gdef.MAXIMUM_ALLOWED):
|
||||
return windows.winproxy.OpenServiceW(self.handle, name, access) # Check service exists :)
|
||||
|
||||
def get_service(self, key, access=gdef.MAXIMUM_ALLOWED):
|
||||
"""Get a service by its name/index or a list of services via a slice
|
||||
|
||||
:return: :class:`Service` or [:class:`Service`] -- A :class:`Service` or list of :class:`Service`
|
||||
"""
|
||||
if isinstance(key, int_types):
|
||||
return self.enumerate_services()[key]
|
||||
if isinstance(key, slice):
|
||||
# Get service list
|
||||
servlist = self.enumerate_services()
|
||||
# Extract indexes matching the slice
|
||||
indexes = key.indices(len(servlist))
|
||||
return [servlist[idx] for idx in range(*indexes)]
|
||||
# Retrieve service by its name
|
||||
handle = self.open_service(key, access)
|
||||
return Service(name=key, handle=handle)
|
||||
|
||||
__getitem__ = get_service
|
||||
"""Get a service by its name/index or a list of services via a slice
|
||||
|
||||
:return: :class:`Service` or [:class:`Service`] -- A :class:`Service` or list of :class:`Service`
|
||||
"""
|
||||
|
||||
def get_service_display_name(self, name):
|
||||
# This API is strange..
|
||||
# Why can't we retrieve the display name for a service handle ?
|
||||
BUFFER_SIZE = 0x1000
|
||||
result = (WCHAR * BUFFER_SIZE)()
|
||||
size_needed = gdef.DWORD(BUFFER_SIZE)
|
||||
windows.winproxy.GetServiceDisplayNameW(self.handle, name, result, size_needed)
|
||||
return result.value
|
||||
|
||||
def _enumerate_services_generator(self):
|
||||
"""The generator code behind __iter__.
|
||||
Allow to iter over the services on the system
|
||||
"""
|
||||
size_needed = gdef.DWORD()
|
||||
nb_services = gdef.DWORD()
|
||||
counter = gdef.DWORD()
|
||||
try:
|
||||
windows.winproxy.EnumServicesStatusExW(self.handle, SC_ENUM_PROCESS_INFO, SERVICE_TYPE_ALL, SERVICE_STATE_ALL, None, 0, ctypes.byref(size_needed), ctypes.byref(nb_services), byref(counter), None)
|
||||
except WindowsError:
|
||||
pass
|
||||
|
||||
while True:
|
||||
size = size_needed.value
|
||||
buffer = (BYTE * size)()
|
||||
try:
|
||||
windows.winproxy.EnumServicesStatusExW(self.handle, SC_ENUM_PROCESS_INFO, SERVICE_TYPE_ALL, SERVICE_STATE_ALL, buffer, size, ctypes.byref(size_needed), ctypes.byref(nb_services), byref(counter), None)
|
||||
except WindowsError as e:
|
||||
continue
|
||||
break
|
||||
services_array = (gdef.ENUM_SERVICE_STATUS_PROCESSW * nb_services.value).from_buffer(buffer)
|
||||
for service_info in services_array:
|
||||
shandle = self.open_service(service_info.lpServiceName)
|
||||
yield Service(handle=shandle, name=service_info.lpServiceName, description=service_info.lpDisplayName)
|
||||
return
|
||||
|
||||
__iter__ = _enumerate_services_generator
|
||||
"""Iter over the services on the system
|
||||
|
||||
:yield: :class:`Service`
|
||||
"""
|
||||
|
||||
def enumerate_services(self):
|
||||
return list(self._enumerate_services_generator())
|
||||
|
||||
|
||||
class Service(gdef.SC_HANDLE):
|
||||
"""Represent a service on the system"""
|
||||
def __init__(self, handle, name, description=None):
|
||||
super(Service, self).__init__(handle)
|
||||
self.name = name
|
||||
"""The name of the service
|
||||
|
||||
:type: :class:`str`
|
||||
"""
|
||||
if description is not None:
|
||||
self._description = description # Setup fixedpropety
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
"""The description of the service
|
||||
|
||||
:type: :class:`str`
|
||||
"""
|
||||
return ServiceManager().get_service_display_name(self.name)
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""The status of the service
|
||||
|
||||
:type: :class:`~windows.generated_def.winstructs.SERVICE_STATUS_PROCESS`
|
||||
"""
|
||||
buffer = windows.utils.BUFFER(gdef.SERVICE_STATUS_PROCESS)()
|
||||
size_needed = gdef.DWORD()
|
||||
windows.winproxy.QueryServiceStatusEx(self, gdef.SC_STATUS_PROCESS_INFO, buffer.cast(gdef.LPBYTE), ctypes.sizeof(buffer), size_needed)
|
||||
return buffer[0]
|
||||
|
||||
@property # Can change if service is started/stopped when the object exist
|
||||
def process(self):
|
||||
"""The process running the service (if any)
|
||||
|
||||
:type: :class:`WinProcess <windows.winobject.process.WinProcess>` or ``None``
|
||||
"""
|
||||
pid = self.status.dwProcessId
|
||||
if not pid:
|
||||
return None
|
||||
l = windows.WinProcess(pid=pid)
|
||||
return l
|
||||
|
||||
@property
|
||||
def security_descriptor(self):
|
||||
"""The security descriptor of the service
|
||||
|
||||
:type: :class:`~windows.security.SecurityDescriptor`
|
||||
"""
|
||||
return security.SecurityDescriptor.from_service(self.name)
|
||||
|
||||
def start(self, args=None):
|
||||
"""Start the service
|
||||
|
||||
:param args: a list of :class:`str`
|
||||
"""
|
||||
nbelt = 0
|
||||
if args is not None:
|
||||
if isinstance(args, windows.pycompat.anybuff):
|
||||
args = [args]
|
||||
nbelt = len(args)
|
||||
args = (gdef.LPWSTR * (nbelt))(*args)
|
||||
return windows.winproxy.StartServiceW(self, nbelt, args)
|
||||
|
||||
def stop(self):
|
||||
"""Stop the service"""
|
||||
status = SERVICE_STATUS()
|
||||
windows.winproxy.ControlService(self, gdef.SERVICE_CONTROL_STOP, status)
|
||||
return status
|
||||
|
||||
def __repr__(self):
|
||||
return """<{0} "{1}" {2!r}>""".format(type(self).__name__, self.name, self.status.state)
|
||||
|
||||
def __del__(self):
|
||||
return windows.winproxy.CloseServiceHandle(self)
|
||||
@@ -0,0 +1,508 @@
|
||||
import os
|
||||
import ctypes
|
||||
import copy
|
||||
import struct
|
||||
|
||||
import windows
|
||||
from windows import winproxy
|
||||
from windows import utils
|
||||
|
||||
import windows.generated_def as gdef
|
||||
|
||||
|
||||
|
||||
from windows.winobject import process
|
||||
from windows.winobject import network
|
||||
from windows.winobject import registry
|
||||
from windows.winobject import exception
|
||||
from windows.winobject import service
|
||||
from windows.winobject import volume
|
||||
from windows.winobject import wmi
|
||||
from windows.winobject import object_manager
|
||||
from windows.winobject import device_manager
|
||||
from windows.winobject import handle
|
||||
from windows.winobject import event_log
|
||||
from windows.winobject import event_trace
|
||||
from windows.winobject import task_scheduler
|
||||
from windows.winobject import system_module
|
||||
from windows.winobject import bits
|
||||
|
||||
from windows.dbgprint import dbgprint
|
||||
|
||||
class System(object):
|
||||
"""The state of the current ``Windows`` system ``Python`` is running on"""
|
||||
|
||||
# Setup these in a fixedproperty ?
|
||||
network = network.Network()
|
||||
"""Object of class :class:`windows.winobject.network.Network`"""
|
||||
registry = registry.Registry()
|
||||
"""Object of class :class:`windows.winobject.registry.Registry`"""
|
||||
|
||||
@property
|
||||
def processes(self):
|
||||
"""The list of running processes
|
||||
|
||||
:type: [:class:`~windows.winobject.process.WinProcess`] -- A list of Process
|
||||
"""
|
||||
return self.enumerate_processes()
|
||||
|
||||
@property
|
||||
def threads(self):
|
||||
"""The list of running threads
|
||||
|
||||
:type: [:class:`~windows.winobject.process.WinThread`] -- A list of Thread
|
||||
"""
|
||||
return self.enumerate_threads_setup_owners()
|
||||
|
||||
@property
|
||||
def logicaldrives(self):
|
||||
"""List of logical drives [C:\, ...]
|
||||
|
||||
:type: [:class:`~windows.winobject.volume.LogicalDrive`] -- A list of LogicalDrive
|
||||
"""
|
||||
return volume.enum_logical_drive()
|
||||
|
||||
@utils.fixedpropety
|
||||
def services(self):
|
||||
"""An object to query, list and explore services
|
||||
|
||||
:type: :class:`~windows.winobject.service.ServiceManager`
|
||||
"""
|
||||
return service.ServiceManager()
|
||||
|
||||
@property
|
||||
def handles(self):
|
||||
"""The list of system handles
|
||||
|
||||
:type: [:class:`~windows.winobject.handle.Handle`] -- A list of Hanlde"""
|
||||
return handle.enumerate_handles()
|
||||
|
||||
@property
|
||||
def modules(self):
|
||||
"""The list of system modules
|
||||
|
||||
:type: [:class:`~windows.winobject.system_module.SystemModule`] -- A list of :class:`~windows.winobject.system_module.SystemModule` or :class:`~windows.winobject.system_module.SystemModuleWow64`
|
||||
"""
|
||||
return system_module.enumerate_kernel_modules()
|
||||
|
||||
@utils.fixedpropety
|
||||
def bitness(self):
|
||||
"""The bitness of the system
|
||||
|
||||
:type: :class:`int` -- 32 or 64
|
||||
"""
|
||||
if os.environ["PROCESSOR_ARCHITECTURE"].lower() != "x86":
|
||||
return 64
|
||||
if "PROCESSOR_ARCHITEW6432" in os.environ:
|
||||
return 64
|
||||
return 32
|
||||
|
||||
@utils.fixedpropety
|
||||
def wmi(self):
|
||||
r"""An object to perform wmi requests to various namespaces
|
||||
|
||||
:type: :class:`~windows.winobject.wmi.WmiManager`"""
|
||||
return wmi.WmiManager()
|
||||
|
||||
|
||||
@utils.fixedpropety
|
||||
def event_log(self):
|
||||
"""An object to open Event channel/publisher and evtx file
|
||||
|
||||
:type: :class:`~windows.winobject.event_log.EvtlogManager`
|
||||
"""
|
||||
return event_log.EvtlogManager()
|
||||
|
||||
@utils.fixedpropety
|
||||
def etw(self):
|
||||
"""An object to interact with ETW (Event Tracing for Windows)
|
||||
|
||||
:type: :class:`~windows.winobject.event_trace.EtwManager`
|
||||
"""
|
||||
return event_trace.EtwManager()
|
||||
|
||||
|
||||
@utils.fixedpropety
|
||||
def task_scheduler(self):
|
||||
"""An object able to manage scheduled tasks on the local system
|
||||
|
||||
:type: :class:`~windows.winobject.task_scheduler.TaskService`
|
||||
"""
|
||||
windows.com.init()
|
||||
clsid_task_scheduler = gdef.IID.from_string("0f87369f-a4e5-4cfc-bd3e-73e6154572dd")
|
||||
task_service = task_scheduler.TaskService()
|
||||
# What is non-implemented (WinXP)
|
||||
# Raise (NotImplementedError?) ? Return NotImplemented ?
|
||||
windows.com.create_instance(clsid_task_scheduler, task_service)
|
||||
task_service.connect()
|
||||
return task_service
|
||||
|
||||
@utils.fixedpropety
|
||||
def object_manager(self):
|
||||
"""An object to query the objects in the kernel object manager.
|
||||
|
||||
:type: :class:`~windows.winobject.object_manager.ObjectManager`
|
||||
"""
|
||||
return windows.winobject.object_manager.ObjectManager()
|
||||
|
||||
@utils.fixedpropety
|
||||
def device_manager(self):
|
||||
"""An object to query the device&driver configured on the computer.
|
||||
|
||||
:type: :class:`~windows.winobject.device_manager.DeviceManager`
|
||||
"""
|
||||
return windows.winobject.device_manager.DeviceManager()
|
||||
|
||||
@utils.fixedpropety
|
||||
def bits(self):
|
||||
return bits.create_manager()
|
||||
|
||||
#TODO: use GetComputerNameExA ? and recover other names ?
|
||||
@utils.fixedpropety
|
||||
def computer_name(self):
|
||||
"""The name of the computer
|
||||
|
||||
:type: :class:`str`
|
||||
"""
|
||||
size = gdef.DWORD(0x1000)
|
||||
# For now I don't know what is best as A vs W APIs...
|
||||
if windows.pycompat.is_py3:
|
||||
buf = ctypes.create_unicode_buffer(size.value)
|
||||
winproxy.GetComputerNameW(buf, ctypes.byref(size))
|
||||
else:
|
||||
buf = ctypes.create_string_buffer(size.value)
|
||||
winproxy.GetComputerNameA(buf, ctypes.byref(size))
|
||||
return buf[:size.value]
|
||||
|
||||
def _computer_name_ex(self, nametype):
|
||||
size = gdef.DWORD(0)
|
||||
try:
|
||||
winproxy.GetComputerNameExW(nametype, None, ctypes.byref(size))
|
||||
except WindowsError as e:
|
||||
if e.winerror != gdef.ERROR_MORE_DATA:
|
||||
raise
|
||||
|
||||
buf = ctypes.create_unicode_buffer(size.value)
|
||||
winproxy.GetComputerNameExW(nametype, buf, ctypes.byref(size))
|
||||
return buf[:size.value]
|
||||
|
||||
@utils.fixedproperty
|
||||
def domain(self):
|
||||
# [WIP] name of the domain joined by the computer, None is no domain joined
|
||||
return self._computer_name_ex(gdef.ComputerNameDnsDomain) or None
|
||||
|
||||
@utils.fixedpropety
|
||||
def version(self):
|
||||
"""The version of the system
|
||||
|
||||
:type: (:class:`int`, :class:`int`) -- (Major, Minor)
|
||||
"""
|
||||
data = self.get_version()
|
||||
result = data.dwMajorVersion, data.dwMinorVersion
|
||||
if result == (6,2):
|
||||
result_str = self.get_file_version("kernel32")
|
||||
result_tup = [int(x) for x in result_str.split(".")]
|
||||
result = tuple(result_tup[:2])
|
||||
return result
|
||||
|
||||
@utils.fixedpropety
|
||||
def version_name(self):
|
||||
"""The name of the system version, values are:
|
||||
|
||||
* Windows Server 2016
|
||||
* Windows 10
|
||||
* Windows Server 2012 R2
|
||||
* Windows 8.1
|
||||
* Windows Server 2012
|
||||
* Windows 8
|
||||
* Windows Server 2008
|
||||
* Windows 7
|
||||
* Windows Server 2008
|
||||
* Windows Vista
|
||||
* Windows XP Professional x64 Edition
|
||||
* TODO: version (5.2) + is_workstation + bitness == 32 (don't even know if possible..)
|
||||
* Windows Server 2003 R2
|
||||
* Windows Server 2003
|
||||
* Windows XP
|
||||
* Windows 2000
|
||||
* "Unknow Windows <version={0} | is_workstation={1}>".format(version, is_workstation)
|
||||
|
||||
:type: :class:`str`
|
||||
"""
|
||||
version = self.version
|
||||
is_workstation = self.product_type == gdef.VER_NT_WORKSTATION
|
||||
if version == (10, 0):
|
||||
return ["Windows Server 2016", "Windows 10"][is_workstation]
|
||||
elif version == (6, 3):
|
||||
return ["Windows Server 2012 R2", "Windows 8.1"][is_workstation]
|
||||
elif version == (6, 2):
|
||||
return ["Windows Server 2012", "Windows 8"][is_workstation]
|
||||
elif version == (6, 1):
|
||||
return ["Windows Server 2008 R2", "Windows 7"][is_workstation]
|
||||
elif version == (6, 0):
|
||||
return ["Windows Server 2008", "Windows Vista"][is_workstation]
|
||||
elif version == (5, 2):
|
||||
metric = winproxy.GetSystemMetrics(gdef.SM_SERVERR2)
|
||||
if is_workstation:
|
||||
if self.bitness == 64:
|
||||
return "Windows XP Professional x64 Edition"
|
||||
else:
|
||||
return "TODO: version (5.2) + is_workstation + bitness == 32"
|
||||
elif metric != 0:
|
||||
return "Windows Server 2003 R2"
|
||||
else:
|
||||
return "Windows Server 2003"
|
||||
elif version == (5, 1):
|
||||
return "Windows XP"
|
||||
elif version == (5, 0):
|
||||
return "Windows 2000"
|
||||
else:
|
||||
return "Unknow Windows <version={0} | is_workstation={1}>".format(version, is_workstation)
|
||||
|
||||
VERSION_MAPPER = gdef.FlagMapper(gdef.VER_NT_WORKSTATION, gdef.VER_NT_DOMAIN_CONTROLLER, gdef.VER_NT_SERVER)
|
||||
@utils.fixedpropety
|
||||
def product_type(self):
|
||||
"""The product type, value might be:
|
||||
|
||||
* VER_NT_WORKSTATION(0x1L)
|
||||
* VER_NT_DOMAIN_CONTROLLER(0x2L)
|
||||
* VER_NT_SERVER(0x3L)
|
||||
|
||||
:type: :class:`long` or :class:`int` (or subclass)
|
||||
"""
|
||||
version = self.get_version()
|
||||
return self.VERSION_MAPPER[version.wProductType]
|
||||
|
||||
|
||||
EDITION_MAPPER = gdef.FlagMapper(gdef.PRODUCT_UNDEFINED,
|
||||
gdef.PRODUCT_ULTIMATE,
|
||||
gdef.PRODUCT_HOME_BASIC,
|
||||
gdef.PRODUCT_HOME_PREMIUM,
|
||||
gdef.PRODUCT_ENTERPRISE,
|
||||
gdef.PRODUCT_HOME_BASIC_N,
|
||||
gdef.PRODUCT_BUSINESS,
|
||||
gdef.PRODUCT_STANDARD_SERVER,
|
||||
gdef.PRODUCT_DATACENTER_SERVER,
|
||||
gdef.PRODUCT_SMALLBUSINESS_SERVER,
|
||||
gdef.PRODUCT_ENTERPRISE_SERVER,
|
||||
gdef.PRODUCT_STARTER,
|
||||
gdef.PRODUCT_DATACENTER_SERVER_CORE,
|
||||
gdef.PRODUCT_STANDARD_SERVER_CORE,
|
||||
gdef.PRODUCT_ENTERPRISE_SERVER_CORE,
|
||||
gdef.PRODUCT_ENTERPRISE_SERVER_IA64,
|
||||
gdef.PRODUCT_BUSINESS_N,
|
||||
gdef.PRODUCT_WEB_SERVER,
|
||||
gdef.PRODUCT_CLUSTER_SERVER,
|
||||
gdef.PRODUCT_HOME_SERVER,
|
||||
gdef.PRODUCT_STORAGE_EXPRESS_SERVER,
|
||||
gdef.PRODUCT_STORAGE_STANDARD_SERVER,
|
||||
gdef.PRODUCT_STORAGE_WORKGROUP_SERVER,
|
||||
gdef.PRODUCT_STORAGE_ENTERPRISE_SERVER,
|
||||
gdef.PRODUCT_SERVER_FOR_SMALLBUSINESS,
|
||||
gdef.PRODUCT_SMALLBUSINESS_SERVER_PREMIUM,
|
||||
gdef.PRODUCT_HOME_PREMIUM_N,
|
||||
gdef.PRODUCT_ENTERPRISE_N,
|
||||
gdef.PRODUCT_ULTIMATE_N,
|
||||
gdef.PRODUCT_WEB_SERVER_CORE,
|
||||
gdef.PRODUCT_MEDIUMBUSINESS_SERVER_MANAGEMENT,
|
||||
gdef.PRODUCT_MEDIUMBUSINESS_SERVER_SECURITY,
|
||||
gdef.PRODUCT_MEDIUMBUSINESS_SERVER_MESSAGING,
|
||||
gdef.PRODUCT_SERVER_FOUNDATION,
|
||||
gdef.PRODUCT_HOME_PREMIUM_SERVER,
|
||||
gdef.PRODUCT_SERVER_FOR_SMALLBUSINESS_V,
|
||||
gdef.PRODUCT_STANDARD_SERVER_V,
|
||||
gdef.PRODUCT_DATACENTER_SERVER_V,
|
||||
gdef.PRODUCT_ENTERPRISE_SERVER_V,
|
||||
gdef.PRODUCT_DATACENTER_SERVER_CORE_V,
|
||||
gdef.PRODUCT_STANDARD_SERVER_CORE_V,
|
||||
gdef.PRODUCT_ENTERPRISE_SERVER_CORE_V,
|
||||
gdef.PRODUCT_HYPERV,
|
||||
gdef.PRODUCT_STORAGE_EXPRESS_SERVER_CORE,
|
||||
gdef.PRODUCT_STORAGE_STANDARD_SERVER_CORE,
|
||||
gdef.PRODUCT_STORAGE_WORKGROUP_SERVER_CORE,
|
||||
gdef.PRODUCT_STORAGE_ENTERPRISE_SERVER_CORE,
|
||||
gdef.PRODUCT_STARTER_N,
|
||||
gdef.PRODUCT_PROFESSIONAL,
|
||||
gdef.PRODUCT_PROFESSIONAL_N,
|
||||
gdef.PRODUCT_SB_SOLUTION_SERVER,
|
||||
gdef.PRODUCT_SERVER_FOR_SB_SOLUTIONS,
|
||||
gdef.PRODUCT_STANDARD_SERVER_SOLUTIONS,
|
||||
gdef.PRODUCT_STANDARD_SERVER_SOLUTIONS_CORE,
|
||||
gdef.PRODUCT_SB_SOLUTION_SERVER_EM,
|
||||
gdef.PRODUCT_SERVER_FOR_SB_SOLUTIONS_EM,
|
||||
gdef.PRODUCT_SOLUTION_EMBEDDEDSERVER,
|
||||
gdef.PRODUCT_SOLUTION_EMBEDDEDSERVER_CORE,
|
||||
gdef.PRODUCT_SMALLBUSINESS_SERVER_PREMIUM_CORE,
|
||||
gdef.PRODUCT_ESSENTIALBUSINESS_SERVER_MGMT,
|
||||
gdef.PRODUCT_ESSENTIALBUSINESS_SERVER_ADDL,
|
||||
gdef.PRODUCT_ESSENTIALBUSINESS_SERVER_MGMTSVC,
|
||||
gdef.PRODUCT_ESSENTIALBUSINESS_SERVER_ADDLSVC,
|
||||
gdef.PRODUCT_CLUSTER_SERVER_V,
|
||||
gdef.PRODUCT_EMBEDDED,
|
||||
gdef.PRODUCT_STARTER_E,
|
||||
gdef.PRODUCT_HOME_BASIC_E,
|
||||
gdef.PRODUCT_HOME_PREMIUM_E,
|
||||
gdef.PRODUCT_PROFESSIONAL_E,
|
||||
gdef.PRODUCT_ENTERPRISE_E,
|
||||
gdef.PRODUCT_ULTIMATE_E,
|
||||
gdef.PRODUCT_ENTERPRISE_EVALUATION,
|
||||
gdef.PRODUCT_MULTIPOINT_STANDARD_SERVER,
|
||||
gdef.PRODUCT_MULTIPOINT_PREMIUM_SERVER,
|
||||
gdef.PRODUCT_STANDARD_EVALUATION_SERVER,
|
||||
gdef.PRODUCT_DATACENTER_EVALUATION_SERVER,
|
||||
gdef.PRODUCT_ENTERPRISE_N_EVALUATION,
|
||||
gdef.PRODUCT_STORAGE_WORKGROUP_EVALUATION_SERVER,
|
||||
gdef.PRODUCT_STORAGE_STANDARD_EVALUATION_SERVER,
|
||||
gdef.PRODUCT_CORE_ARM,
|
||||
gdef.PRODUCT_CORE_N,
|
||||
gdef.PRODUCT_CORE_COUNTRYSPECIFIC,
|
||||
gdef.PRODUCT_CORE_LANGUAGESPECIFIC,
|
||||
gdef.PRODUCT_CORE,
|
||||
gdef.PRODUCT_PROFESSIONAL_WMC,
|
||||
gdef.PRODUCT_UNLICENSED)
|
||||
|
||||
@utils.fixedpropety
|
||||
def edition(self): # Find a better name ?
|
||||
version = self.get_version()
|
||||
edition = gdef.DWORD()
|
||||
try:
|
||||
winproxy.GetProductInfo(version.dwMajorVersion,
|
||||
version.dwMinorVersion,
|
||||
version.wServicePackMajor,
|
||||
version.wServicePackMinor,
|
||||
edition)
|
||||
except winproxy.ExportNotFound as e:
|
||||
# Windows XP does not implem GetProductInfo
|
||||
assert version.dwMajorVersion, version.dwMinorVersion == (5,1)
|
||||
return self._edition_windows_xp()
|
||||
return self.EDITION_MAPPER[edition.value]
|
||||
|
||||
def _edition_windows_xp(self):
|
||||
# Emulate standard response from IsOS(gdef.OS_PROFESSIONAL)
|
||||
if winproxy.IsOS(gdef.OS_PROFESSIONAL):
|
||||
return gdef.PRODUCT_PROFESSIONAL
|
||||
return gdef.PRODUCT_HOME_BASIC
|
||||
|
||||
@utils.fixedpropety
|
||||
def windir(self):
|
||||
buffer = ctypes.c_buffer(0x100)
|
||||
reslen = winproxy.GetWindowsDirectoryA(buffer)
|
||||
return buffer[:reslen]
|
||||
|
||||
def get_version(self):
|
||||
data = gdef.OSVERSIONINFOEXA()
|
||||
data.dwOSVersionInfoSize = ctypes.sizeof(data)
|
||||
winproxy.GetVersionExA(ctypes.cast(ctypes.pointer(data), ctypes.POINTER(gdef.OSVERSIONINFOA)))
|
||||
return data
|
||||
|
||||
def get_file_version(self, name):
|
||||
size = winproxy.GetFileVersionInfoSizeA(name)
|
||||
buf = ctypes.c_buffer(size)
|
||||
winproxy.GetFileVersionInfoA(name, 0, size, buf)
|
||||
|
||||
bufptr = gdef.PVOID()
|
||||
bufsize = gdef.UINT()
|
||||
winproxy.VerQueryValueA(buf, "\\VarFileInfo\\Translation", ctypes.byref(bufptr), ctypes.byref(bufsize))
|
||||
bufstr = ctypes.cast(bufptr, gdef.LPCSTR)
|
||||
tup = struct.unpack("<HH", bufstr.value[:4])
|
||||
req = "{0:04x}{1:04x}".format(*tup)
|
||||
winproxy.VerQueryValueA(buf, "\\StringFileInfo\\{0}\\ProductVersion".format(req), ctypes.byref(bufptr), ctypes.byref(bufsize))
|
||||
bufstr = ctypes.cast(bufptr, gdef.LPCSTR)
|
||||
return bufstr.value
|
||||
|
||||
@utils.fixedpropety
|
||||
def build_number(self):
|
||||
# Best effort. use get_file_version if registry code fails
|
||||
try:
|
||||
# Does not works on Win7..
|
||||
# Missing CurrentMajorVersionNumber/CurrentMinorVersionNumber/UBR
|
||||
# We have CurrentVersion instead
|
||||
# Use this code and get_file_version as a backup ?
|
||||
curver_key = windows.system.registry(r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion")
|
||||
try:
|
||||
major = curver_key["CurrentMajorVersionNumber"].value
|
||||
minor = curver_key["CurrentMinorVersionNumber"].value
|
||||
except WindowsError as e:
|
||||
version = curver_key["CurrentVersion"].value
|
||||
# May raise ValueError if no "."
|
||||
major, minor = version.split(".")
|
||||
build = curver_key["CurrentBuildNumber"].value
|
||||
# Update Build Revision
|
||||
try:
|
||||
ubr = curver_key["UBR"].value
|
||||
except WindowsError as e:
|
||||
ubr = 0 # Not present on Win7
|
||||
return "{0}.{1}.{2}.{3}".format(major, minor, build, ubr)
|
||||
except (WindowsError, ValueError):
|
||||
return self.get_file_version("ntdll")
|
||||
|
||||
|
||||
@staticmethod
|
||||
def enumerate_processes():
|
||||
dbgprint("Enumerating processes with CreateToolhelp32Snapshot", "SLOW")
|
||||
process_entry = gdef.PROCESSENTRY32W()
|
||||
process_entry.dwSize = ctypes.sizeof(process_entry)
|
||||
snap = winproxy.CreateToolhelp32Snapshot(gdef.TH32CS_SNAPPROCESS, 0)
|
||||
winproxy.Process32FirstW(snap, process_entry)
|
||||
res = []
|
||||
res.append(process.WinProcess._from_PROCESSENTRY32(process_entry))
|
||||
while winproxy.Process32NextW(snap, process_entry):
|
||||
res.append(process.WinProcess._from_PROCESSENTRY32(process_entry))
|
||||
winproxy.CloseHandle(snap)
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def enumerate_threads_generator():
|
||||
# Ptet dangereux, parce que on yield la meme THREADENTRY32 a chaque fois
|
||||
dbgprint("Enumerating threads with CreateToolhelp32Snapshot <generator>", "SLOW")
|
||||
thread_entry = gdef.THREADENTRY32()
|
||||
thread_entry.dwSize = ctypes.sizeof(thread_entry)
|
||||
snap = winproxy.CreateToolhelp32Snapshot(gdef.TH32CS_SNAPTHREAD, 0)
|
||||
dbgprint("New handle CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD) <generator> | {0:#x}".format(snap), "HANDLE")
|
||||
try:
|
||||
winproxy.Thread32First(snap, thread_entry)
|
||||
yield thread_entry
|
||||
while winproxy.Thread32Next(snap, thread_entry):
|
||||
yield thread_entry
|
||||
finally:
|
||||
winproxy.CloseHandle(snap)
|
||||
dbgprint("CLOSE CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD) <generator> | {0:#x}".format(snap), "HANDLE")
|
||||
|
||||
|
||||
@staticmethod
|
||||
def enumerate_threads():
|
||||
return [WinThread._from_THREADENTRY32(th) for th in System.enumerate_threads_generator()]
|
||||
|
||||
|
||||
def enumerate_threads_setup_owners(self):
|
||||
# Enumerating threads is a special operation concerning the owner process.
|
||||
# We may not be able to retrieve the name of the owning process by normal way
|
||||
# (as we need to get a handle on the process)
|
||||
# So, this implementation of enumerate_thread also setup the owner with the result of enumerate_processes
|
||||
dbgprint("Enumerating threads with CreateToolhelp32Snapshot and setup owner", "SLOW")
|
||||
|
||||
# One snap for both enum to be prevent race
|
||||
snap = winproxy.CreateToolhelp32Snapshot(gdef.TH32CS_SNAPTHREAD | gdef.TH32CS_SNAPPROCESS, 0)
|
||||
|
||||
process_entry = gdef.PROCESSENTRY32W()
|
||||
process_entry.dwSize = ctypes.sizeof(process_entry)
|
||||
winproxy.Process32FirstW(snap, process_entry)
|
||||
processes = []
|
||||
processes.append(process.WinProcess._from_PROCESSENTRY32(process_entry))
|
||||
while winproxy.Process32NextW(snap, process_entry):
|
||||
processes.append(process.WinProcess._from_PROCESSENTRY32(process_entry))
|
||||
|
||||
# Forge a dict pid -> process
|
||||
proc_dict = {proc.pid: proc for proc in processes}
|
||||
|
||||
thread_entry = gdef.THREADENTRY32()
|
||||
thread_entry.dwSize = ctypes.sizeof(thread_entry)
|
||||
threads = []
|
||||
winproxy.Thread32First(snap, thread_entry)
|
||||
parent = proc_dict[thread_entry.th32OwnerProcessID]
|
||||
threads.append(process.WinThread._from_THREADENTRY32(thread_entry, owner=parent))
|
||||
while winproxy.Thread32Next(snap, thread_entry):
|
||||
parent = proc_dict[thread_entry.th32OwnerProcessID]
|
||||
threads.append(process.WinThread._from_THREADENTRY32(thread_entry, owner=parent))
|
||||
winproxy.CloseHandle(snap)
|
||||
return threads
|
||||
@@ -0,0 +1,53 @@
|
||||
import ctypes
|
||||
|
||||
import windows
|
||||
import windows.winproxy as winproxy
|
||||
import windows.generated_def as gdef
|
||||
|
||||
class BaseSystemModule(object):
|
||||
"""[ABSTRACT] A common base class for all system modules"""
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name of the system module: alias for ``ImageName``"""
|
||||
return self.ImageName
|
||||
|
||||
def __repr__(self):
|
||||
return """<{0} name="{1}" base={2:#x}>""".format(type(self).__name__, self.ImageName, self.Base)
|
||||
|
||||
|
||||
|
||||
class SystemModule(BaseSystemModule, gdef.SYSTEM_MODULE):
|
||||
"""A system module.
|
||||
|
||||
.. note::
|
||||
inherit from SYSTEM_MODULE[32/64] based on the current process bitness
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
# Only useful / meaningful in Wow64 Process
|
||||
class SystemModuleWow64(BaseSystemModule, gdef.SYSTEM_MODULE64):
|
||||
"""An explicite 64b system module for SysWow64 processes"""
|
||||
pass
|
||||
|
||||
|
||||
def enumerate_kernel_modules():
|
||||
if windows.current_process.is_wow_64:
|
||||
return enumerate_kernel_modules_syswow64()
|
||||
cbsize = gdef.DWORD()
|
||||
winproxy.NtQuerySystemInformation(gdef.SystemModuleInformation, None, 0, ctypes.byref(cbsize))
|
||||
raw_buffer = (cbsize.value * gdef.BYTE)()
|
||||
buffer = gdef.SYSTEM_MODULE_INFORMATION.from_address(ctypes.addressof(raw_buffer))
|
||||
winproxy.NtQuerySystemInformation(gdef.SystemModuleInformation, ctypes.byref(raw_buffer), ctypes.sizeof(raw_buffer), ctypes.byref(cbsize))
|
||||
modules = (SystemModule * buffer.ModulesCount).from_buffer(raw_buffer, gdef.SYSTEM_MODULE_INFORMATION.Modules.offset)
|
||||
return list(modules)
|
||||
|
||||
def enumerate_kernel_modules_syswow64():
|
||||
cbsize = gdef.DWORD()
|
||||
windows.syswow64.NtQuerySystemInformation_32_to_64(gdef.SystemModuleInformation, None, 0, ctypes.addressof(cbsize))
|
||||
raw_buffer = (cbsize.value * gdef.BYTE)()
|
||||
buffer = gdef.SYSTEM_MODULE_INFORMATION64.from_address(ctypes.addressof(raw_buffer))
|
||||
windows.syswow64.NtQuerySystemInformation_32_to_64(gdef.SystemModuleInformation, ctypes.byref(raw_buffer), ctypes.sizeof(raw_buffer), ctypes.byref(cbsize))
|
||||
modules = (SystemModuleWow64 * buffer.ModulesCount).from_buffer(raw_buffer, gdef.SYSTEM_MODULE_INFORMATION64.Modules.offset)
|
||||
return list(modules)
|
||||
@@ -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)
|
||||
@@ -0,0 +1,626 @@
|
||||
import ctypes
|
||||
import functools
|
||||
|
||||
import windows
|
||||
from windows import utils
|
||||
from windows import winproxy
|
||||
import windows.generated_def as gdef
|
||||
# import windows.security # at the end of this file (loop import)
|
||||
|
||||
bltn_type = type
|
||||
|
||||
KNOW_INTEGRITY_LEVEL = gdef.FlagMapper(
|
||||
gdef.SECURITY_MANDATORY_UNTRUSTED_RID,
|
||||
gdef.SECURITY_MANDATORY_LOW_RID,
|
||||
gdef.SECURITY_MANDATORY_MEDIUM_RID,
|
||||
gdef.SECURITY_MANDATORY_MEDIUM_PLUS_RID,
|
||||
gdef.SECURITY_MANDATORY_HIGH_RID,
|
||||
gdef.SECURITY_MANDATORY_SYSTEM_RID,
|
||||
gdef.SECURITY_MANDATORY_PROTECTED_PROCESS_RID
|
||||
)
|
||||
|
||||
# Voodoo to fix lookup-strangeness in class declaration
|
||||
def meta_craft(x):
|
||||
def partial_applier(infos_class, rtype):
|
||||
return property(functools.partial(x, infos_class=infos_class, rtype=rtype))
|
||||
return partial_applier
|
||||
|
||||
|
||||
class TokenGroups(gdef.TOKEN_GROUPS):
|
||||
@property
|
||||
def _groups(self):
|
||||
return windows.utils.resized_array(self.Groups, self.GroupCount)
|
||||
|
||||
@property
|
||||
def sids_and_attributes(self):
|
||||
"""The sids and attributes of each group
|
||||
|
||||
:type: [:class:`~windows.generated_def.winstructs.SID_AND_ATTRIBUTES`] - A list of :class:`~windows.generated_def.winstructs.SID_AND_ATTRIBUTES`
|
||||
"""
|
||||
return self._groups # Something else ?
|
||||
|
||||
@property
|
||||
def sids(self):
|
||||
"""The sids of each group
|
||||
|
||||
:type: [:class:`~windows.generated_def.winstructs.PSID`] - A list of :class:`~windows.generated_def.winstructs.PSID`
|
||||
"""
|
||||
return [g.Sid for g in self._groups]
|
||||
|
||||
def __repr__(self):
|
||||
return "<{0} count={1}>".format(type(self).__name__, self.GroupCount)
|
||||
|
||||
TokenGroupsType = TokenGroups # Prevent confusion with token.TokenGroups
|
||||
|
||||
class TokenPrivileges(gdef.TOKEN_PRIVILEGES):
|
||||
"""Improved ``TOKEN_PRIVILEGES`` usable like a mapping"""
|
||||
@property
|
||||
def _privileges(self):
|
||||
return windows.utils.resized_array(self.Privileges, self.PrivilegeCount)
|
||||
|
||||
def all(self):
|
||||
"""The list of all privileges
|
||||
|
||||
:returns: [:class:`~windows.generated_def.winstructs.LUID_AND_ATTRIBUTES`] - A list of :class:`~windows.generated_def.winstructs.LUID_AND_ATTRIBUTES`
|
||||
"""
|
||||
return list(self._privileges)
|
||||
|
||||
def keys(self):
|
||||
"""The name of all privileges in the TokenPrivileges
|
||||
|
||||
:returns: [:class:`str`] - A list of name
|
||||
"""
|
||||
return [self._lookup_name(p.Luid) for p in self._privileges]
|
||||
|
||||
__iter__ = keys
|
||||
|
||||
def items(self):
|
||||
"""The (name, Attribute) of all privileges in the TokenPrivileges
|
||||
|
||||
:returns: [(:class:`str`, :class:`int`)] - A list of (name, Attribute) tuple
|
||||
"""
|
||||
return [(self._lookup_name(p.Luid), p.Attributes) for p in self._privileges]
|
||||
|
||||
def _get_priv_by_name(self, name):
|
||||
luid = self._lookup_value(name)
|
||||
x = [p for p in self._privileges if p.Luid == luid]
|
||||
if not x:
|
||||
return None
|
||||
assert len(x) == 1
|
||||
return x[0]
|
||||
|
||||
def __getitem__(self, name):
|
||||
"""Retrieve the attribute value for privilege ``name``
|
||||
|
||||
:raises: KeyError if privilege ``name`` not in the TokenPrivileges
|
||||
:returns: :class:`int`
|
||||
"""
|
||||
priv = self._get_priv_by_name(name)
|
||||
if not priv:
|
||||
raise KeyError(name)
|
||||
return priv.Attributes
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
"""Set the attribute value for privilege ``name``
|
||||
|
||||
:raises: KeyError if privilege ``name`` not in the TokenPrivileges
|
||||
"""
|
||||
priv = self._get_priv_by_name(name)
|
||||
if not priv:
|
||||
raise KeyError(name)
|
||||
priv.Attributes = value
|
||||
|
||||
# __delitem__ that set SE_PRIVILEGE_REMOVED ?
|
||||
|
||||
def _lookup_name(self, luid):
|
||||
size = gdef.DWORD(0x100)
|
||||
buff = ctypes.create_unicode_buffer(size.value)
|
||||
winproxy.LookupPrivilegeNameW(None, luid, buff, size)
|
||||
return buff[:size.value]
|
||||
|
||||
def _lookup_value(self, name):
|
||||
luid = gdef.LUID()
|
||||
winproxy.LookupPrivilegeValueW(None, name, ctypes.byref(luid))
|
||||
return luid
|
||||
|
||||
|
||||
|
||||
TokenPrivilegesType = TokenPrivileges
|
||||
|
||||
class TokenSecurityAttributesInformation(gdef.TOKEN_SECURITY_ATTRIBUTES_INFORMATION):
|
||||
@property
|
||||
def attributes(self):
|
||||
"""Return all the attributes as :class:`TokenSecurityAttributeV1`
|
||||
|
||||
:type: [:class:`TokenSecurityAttributeV1`] - A list of token security attributes
|
||||
"""
|
||||
tptr = ctypes.cast(self.Attribute.pAttributeV1, ctypes.POINTER(TokenSecurityAttributeV1))
|
||||
# Well look like this cast does NOT keep a ref to self.
|
||||
# Setup the base object ref ourself
|
||||
tptr._custom_base_ = self
|
||||
return tptr[:self.AttributeCount]
|
||||
|
||||
|
||||
class TokenSecurityAttributeV1(gdef.TOKEN_SECURITY_ATTRIBUTE_V1):
|
||||
VALUE_ARRAY_PTR_BY_TYPE = {
|
||||
gdef.TOKEN_SECURITY_ATTRIBUTE_TYPE_INT64: "pInt64",
|
||||
gdef.TOKEN_SECURITY_ATTRIBUTE_TYPE_UINT64: "pUint64",
|
||||
gdef.TOKEN_SECURITY_ATTRIBUTE_TYPE_STRING: "pString",
|
||||
gdef.TOKEN_SECURITY_ATTRIBUTE_TYPE_FQBN: "pFqbn",
|
||||
# TOKEN_SECURITY_ATTRIBUTE_TYPE_SID
|
||||
# TOKEN_SECURITY_ATTRIBUTE_TYPE_BOOLEAN
|
||||
gdef.TOKEN_SECURITY_ATTRIBUTE_TYPE_OCTET_STRING: "pOctetString",
|
||||
}
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name of the security attribute"""
|
||||
return self.Name.str
|
||||
|
||||
@property
|
||||
def values(self):
|
||||
"""The values of the security attribute"""
|
||||
array_name = self.VALUE_ARRAY_PTR_BY_TYPE[self.ValueType]
|
||||
return getattr(self.Values, array_name)[:self.ValueCount]
|
||||
|
||||
def __repr__(self):
|
||||
return """<{0} name="{1}">""".format(type(self).__name__, self.name)
|
||||
|
||||
|
||||
|
||||
# https://docs.microsoft.com/en-us/windows/desktop/SecAuthZ/access-tokens
|
||||
class Token(utils.AutoHandle):
|
||||
"""Represent a Windows Token.
|
||||
The attributes only documented by a type are from the :class:`~windows.generated_def.winstructs.TOKEN_INFORMATION_CLASS`, such return values may be improved version of the structure.
|
||||
|
||||
.. note::
|
||||
|
||||
see `[MSDN] TOKEN_INFORMATION_CLASS <https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ne-winnt-_token_information_class>`_
|
||||
"""
|
||||
def __init__(self, handle):
|
||||
self._handle = handle
|
||||
|
||||
def _get_required_token_information_size(self, infos_class):
|
||||
cbsize = gdef.DWORD()
|
||||
try:
|
||||
winproxy.GetTokenInformation(self.handle, infos_class, None, 0, ctypes.byref(cbsize))
|
||||
except winproxy.WinproxyError as e:
|
||||
if not e.winerror in (gdef.ERROR_INSUFFICIENT_BUFFER, gdef.ERROR_BAD_LENGTH):
|
||||
raise
|
||||
return cbsize.value
|
||||
|
||||
def get_token_infomations(self, infos_class, rtype):
|
||||
required_size = self._get_required_token_information_size(infos_class)
|
||||
requested_size = max(required_size, ctypes.sizeof(rtype))
|
||||
buffer = utils.BUFFER(rtype, 1)(size=requested_size)
|
||||
cbsize = gdef.DWORD()
|
||||
winproxy.GetTokenInformation(self.handle, infos_class, buffer, buffer.real_size, cbsize)
|
||||
return buffer[0]
|
||||
|
||||
|
||||
def set_informations(self, info_type, infos):
|
||||
return winproxy.SetTokenInformation(self.handle, info_type, ctypes.byref(infos), ctypes.sizeof(infos))
|
||||
|
||||
|
||||
craft = meta_craft(get_token_infomations)
|
||||
# https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ne-winnt-_token_information_class
|
||||
TokenUser = craft(gdef.TokenUser, gdef.TOKEN_USER) #: :class:`~windows.generated_def.winstructs.TOKEN_USER`
|
||||
TokenGroups = craft(gdef.TokenGroups , TokenGroupsType) #: :class:`TokenGroups`
|
||||
TokenPrivileges = craft(gdef.TokenPrivileges , TokenPrivilegesType) #: :class:`TokenPrivileges`
|
||||
TokenOwner = craft(gdef.TokenOwner, gdef.TOKEN_OWNER) #: :class:`~windows.generated_def.winstructs.TOKEN_OWNER`
|
||||
TokenPrimaryGroup = craft(gdef.TokenPrimaryGroup, gdef.TOKEN_PRIMARY_GROUP) #: :class:`~windows.generated_def.winstructs.TOKEN_PRIMARY_GROUP`
|
||||
TokenDefaultDacl = craft(gdef.TokenDefaultDacl, gdef.TOKEN_DEFAULT_DACL) #: :class:`~windows.generated_def.winstructs.TOKEN_DEFAULT_DACL`
|
||||
TokenSource = craft(gdef.TokenSource, gdef.TOKEN_SOURCE) #: :class:`~windows.generated_def.winstructs.TOKEN_SOURCE`
|
||||
TokenType = craft(gdef.TokenType, gdef.TOKEN_TYPE) #: :class:`~windows.generated_def.winstructs.TOKEN_TYPE`
|
||||
TokenImpersonationLevel = craft(gdef.TokenImpersonationLevel, gdef.SECURITY_IMPERSONATION_LEVEL) #: :class:`~windows.generated_def.winstructs.SECURITY_IMPERSONATION_LEVEL`
|
||||
TokenStatistics = craft(gdef.TokenStatistics, gdef.TOKEN_STATISTICS) #: :class:`~windows.generated_def.winstructs.TOKEN_STATISTICS`
|
||||
TokenRestrictedSids = craft(gdef.TokenRestrictedSids, TokenGroupsType) #: :class:`~windows.generated_def.winstructs.TokenGroups`
|
||||
TokenSessionId = craft(gdef.TokenSessionId, gdef.DWORD) #: :class:`~windows.generated_def.winstructs.DWORD`
|
||||
TokenGroupsAndPrivileges = craft(gdef.TokenGroupsAndPrivileges, gdef.TOKEN_GROUPS_AND_PRIVILEGES) #: :class:`~windows.generated_def.winstructs.TOKEN_GROUPS_AND_PRIVILEGES`
|
||||
# TokenSessionReference = craft(gdef.TokenSessionReference, ???) # Reserved.
|
||||
TokenSandBoxInert = craft(gdef.TokenSandBoxInert, gdef.DWORD) #: :class:`~windows.generated_def.winstructs.DWORD`
|
||||
# TokenAuditPolicy = craft(gdef.TokenAuditPolicy, ???) # Reserved.
|
||||
TokenOrigin = craft(gdef.TokenOrigin, gdef.TOKEN_ORIGIN) #: :class:`~windows.generated_def.winstructs.TOKEN_ORIGIN`
|
||||
TokenElevationType = craft(gdef.TokenElevationType, gdef.TOKEN_ELEVATION_TYPE) #: :class:`~windows.generated_def.winstructs.TOKEN_ELEVATION_TYPE`
|
||||
TokenLinkedToken = craft(gdef.TokenLinkedToken, gdef.TOKEN_LINKED_TOKEN) #: :class:`~windows.generated_def.winstructs.TOKEN_LINKED_TOKEN`
|
||||
TokenElevation = craft(gdef.TokenElevation, gdef.TOKEN_ELEVATION) #: :class:`~windows.generated_def.winstructs.TOKEN_ELEVATION`
|
||||
TokenHasRestrictions = craft(gdef.TokenHasRestrictions, gdef.DWORD) #: :class:`~windows.generated_def.winstructs.DWORD`
|
||||
TokenAccessInformation = craft(gdef.TokenAccessInformation, gdef.TOKEN_ACCESS_INFORMATION) #: :class:`~windows.generated_def.winstructs.TOKEN_ACCESS_INFORMATION`
|
||||
TokenVirtualizationAllowed = craft(gdef.TokenVirtualizationAllowed, gdef.DWORD) #: :class:`~windows.generated_def.winstructs.DWORD`
|
||||
TokenVirtualizationEnabled = craft(gdef.TokenVirtualizationEnabled, gdef.DWORD) #: :class:`~windows.generated_def.winstructs.DWORD`
|
||||
TokenIntegrityLevel = craft(gdef.TokenIntegrityLevel, gdef.TOKEN_MANDATORY_LABEL) #: :class:`~windows.generated_def.winstructs.TOKEN_MANDATORY_LABEL`
|
||||
TokenUIAccess = craft(gdef.TokenUIAccess, gdef.DWORD) #: :class:`~windows.generated_def.winstructs.DWORD`
|
||||
TokenMandatoryPolicy = craft(gdef.TokenMandatoryPolicy, gdef.TOKEN_MANDATORY_POLICY) #: :class:`~windows.generated_def.winstructs.TOKEN_MANDATORY_POLICY`
|
||||
TokenLogonSid = craft(gdef.TokenLogonSid, TokenGroupsType) #: :class:`TokenGroups`
|
||||
TokenIsAppContainer = craft(gdef.TokenIsAppContainer, gdef.DWORD) #: :class:`~windows.generated_def.winstructs.DWORD`
|
||||
TokenCapabilities = craft(gdef.TokenCapabilities, TokenGroupsType) #: :class:`TokenGroups`
|
||||
TokenAppContainerSid = craft(gdef.TokenAppContainerSid, gdef.TOKEN_APPCONTAINER_INFORMATION) #: :class:`~windows.generated_def.winstructs.TOKEN_APPCONTAINER_INFORMATION`
|
||||
TokenAppContainerNumber = craft(gdef.TokenAppContainerNumber, gdef.DWORD) #: :class:`~windows.generated_def.winstructs.DWORD`
|
||||
TokenUserClaimAttributes = craft(gdef.TokenUserClaimAttributes, gdef.CLAIM_SECURITY_ATTRIBUTES_INFORMATION) #: :class:`~windows.generated_def.winstructs.CLAIM_SECURITY_ATTRIBUTES_INFORMATION`
|
||||
TokenDeviceClaimAttributes = craft(gdef.TokenDeviceClaimAttributes, gdef.CLAIM_SECURITY_ATTRIBUTES_INFORMATION) #: :class:`~windows.generated_def.winstructs.CLAIM_SECURITY_ATTRIBUTES_INFORMATION`
|
||||
# TokenRestrictedUserClaimAttributes = craft(gdef.TokenRestrictedUserClaimAttributes, ???) # Reserved.
|
||||
# TokenRestrictedDeviceClaimAttributes = craft(gdef.TokenRestrictedDeviceClaimAttributes, ???) # Reserved.
|
||||
TokenDeviceGroups = craft(gdef.TokenDeviceGroups, TokenGroupsType) #: :class:`TokenGroups`
|
||||
TokenRestrictedDeviceGroups = craft(gdef.TokenRestrictedDeviceGroups, gdef.TOKEN_GROUPS) #: :class:`~windows.generated_def.winstructs.TOKEN_GROUPS`
|
||||
# Reserved.
|
||||
# Structure found in ntseapi.h (thx internet)
|
||||
TokenSecurityAttributes = craft(gdef.TokenSecurityAttributes, TokenSecurityAttributesInformation) #: :class:`TokenSecurityAttributesInformation`
|
||||
# Help would be appreciated for the structures of the following query type
|
||||
|
||||
# TokenIsRestricted = craft(gdef.TokenIsRestricted, ???) # Reserved.
|
||||
TokenProcessTrustLevel = craft(gdef.TokenProcessTrustLevel, gdef.PSID) #: :class:`~windows.generated_def.winstructs.PSID`
|
||||
# TokenPrivateNameSpace = craft(gdef.TokenPrivateNameSpace, gdef.ULONG) # Reserved.
|
||||
# TokenSingletonAttributes = craft(gdef.TokenSingletonAttributes, ???) # Reserved.
|
||||
# TokenBnoIsolation = craft(gdef.TokenBnoIsolation, ???) # Reserved.
|
||||
# TokenChildProcessFlags = craft(gdef.TokenChildProcessFlags, ???) # Reserved.
|
||||
# TokenIsLessPrivilegedAppContainer = craft(gdef.TokenIsLessPrivilegedAppContainer, ???) # Reserved.
|
||||
|
||||
# High level properties
|
||||
|
||||
@property
|
||||
def user(self):
|
||||
"""The user sid of the token
|
||||
|
||||
:type: :class:`~windows.generated_def.winstructs.PSID`
|
||||
"""
|
||||
return self.TokenUser.User.Sid
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
"""The username of the token
|
||||
|
||||
:type: :class:`str`
|
||||
"""
|
||||
return self._user_and_computer_name()[1]
|
||||
|
||||
@property
|
||||
def computername(self):
|
||||
"""The computername of the token
|
||||
|
||||
:type: :class:`str`
|
||||
"""
|
||||
return self._user_and_computer_name()[0]
|
||||
|
||||
def _user_and_computer_name(self):
|
||||
return windows.utils.lookup_sid(self.user)
|
||||
|
||||
|
||||
groups = TokenGroups #: Alias for TokenGroups (type may change in the future for improved struct)
|
||||
|
||||
@property
|
||||
def owner(self):
|
||||
"""The owner sid of the token
|
||||
|
||||
:type: :class:`~windows.generated_def.winstructs.PSID`
|
||||
"""
|
||||
return self.TokenOwner.Owner
|
||||
|
||||
@property
|
||||
def primary_group(self):
|
||||
"""The sid of the primary group of the token
|
||||
|
||||
:type: :class:`~windows.generated_def.winstructs.PSID`
|
||||
"""
|
||||
return self.TokenPrimaryGroup.PrimaryGroup
|
||||
|
||||
@property
|
||||
def default_dacl(self):
|
||||
"""The defaul DACL of the token
|
||||
|
||||
:type: :class:`windows.security.Acl`
|
||||
"""
|
||||
return self.get_token_infomations(gdef.TokenDefaultDacl, windows.security.PAcl)[0]
|
||||
|
||||
# def source(self): (tok.TokenSource) ??
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""The type (Primary / Impersonation) of the token
|
||||
|
||||
|
||||
"""
|
||||
return self.TokenType.value
|
||||
|
||||
@property
|
||||
def impersonation_level(self):
|
||||
"""The impersonation level of a ``TokenImpersonation`` token.
|
||||
|
||||
:raises: :class:`WindowsError` if token is not a ``TokenImpersonation``
|
||||
:type: :class:`int` -- Enum value from :class:`~windows.generated_def.winstructs.SECURITY_IMPERSONATION_LEVEL`
|
||||
"""
|
||||
try:
|
||||
return self.TokenImpersonationLevel.value
|
||||
except WindowsError as e:
|
||||
if (e.winerror == gdef.ERROR_INVALID_PARAMETER and
|
||||
self.type != gdef.TokenImpersonation):
|
||||
# raise ValueError ?
|
||||
e.strerror += " This Token is not an Impersonation token"
|
||||
raise
|
||||
|
||||
statistics = TokenStatistics #: Alias for TokenStatistics (type may change in the future for improved struct)
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""The TokenId Specifies an unique identifier that identifies this instance of the token object.
|
||||
|
||||
:type: :class:`int`
|
||||
"""
|
||||
return int(self.TokenStatistics.TokenId)
|
||||
|
||||
@property
|
||||
def authentication_id(self):
|
||||
"""The AuthenticationId Specifies an unique identifier assigned to the session this token represents.
|
||||
There can be many tokens representing a single logon session.
|
||||
|
||||
:type: :class:`int`
|
||||
"""
|
||||
return int(self.TokenStatistics.AuthenticationId)
|
||||
|
||||
@property
|
||||
def modified_id(self):
|
||||
"""The ModifiedId Specifies an unique identifier that changes each time the token is modified.
|
||||
|
||||
:type: :class:`int`
|
||||
"""
|
||||
return int(self.TokenStatistics.ModifiedId)
|
||||
|
||||
restricted_sids = TokenRestrictedSids #: Alias for TokenRestrictedSids (type may change in the future for improved struct)
|
||||
session_id = TokenSessionId #: Alias for TokenSessionId (type may change in the future for improved struct)
|
||||
|
||||
@property
|
||||
def groups_and_privileges(self):
|
||||
"""Alias for TokenGroupsAndPrivileges (type may change in the future for improved struct)"""
|
||||
# Return enhanced 'TOKEN_GROUPS_AND_PRIVILEGES' ?
|
||||
return self.TokenGroupsAndPrivileges
|
||||
|
||||
@property
|
||||
def privileges(self):
|
||||
"""Alias for ``TokenPrivileges``
|
||||
|
||||
:type: :class:`TokenPrivileges`
|
||||
"""
|
||||
return self.TokenPrivileges
|
||||
|
||||
sandbox_inert = TokenSandBoxInert #: Alias for TokenSandBoxInert (type may change in the future for improved struct)
|
||||
|
||||
# def audit_policy(self):
|
||||
# raise NotImplementedError("Need to find the type of TokenAuditPolicy")
|
||||
|
||||
@property
|
||||
def origin(self):
|
||||
"""The originating logon session of the token.
|
||||
|
||||
:type: :class:`int`
|
||||
"""
|
||||
origin_logon_session = self.TokenOrigin.OriginatingLogonSession
|
||||
return int(origin_logon_session) # improved LUID implem __int__ :)
|
||||
|
||||
@property
|
||||
def elevation_type(self):
|
||||
"""The elevation type of the token.
|
||||
|
||||
:type: :class:`int` -- Enum value from :class:`~windows.generated_def.winstructs.TOKEN_ELEVATION_TYPE`
|
||||
"""
|
||||
return self.TokenElevationType.value
|
||||
|
||||
@property
|
||||
def linked_token(self):
|
||||
"""The token linked to our token if present (may raise else)
|
||||
|
||||
:type: :class:`Token`
|
||||
"""
|
||||
# TODO: return None if not present ?
|
||||
return Token(self.TokenLinkedToken.LinkedToken)
|
||||
|
||||
@property
|
||||
def elevated(self):
|
||||
"""``True`` if token is an elevated token"""
|
||||
return bool(self.TokenElevation.TokenIsElevated)
|
||||
|
||||
is_elevated = elevated #: Alias for ``elevated`` deprecated and may disapear
|
||||
has_restriction = TokenHasRestrictions #: Alias for TokenHasRestrictions (type may change in the future for improved struct)
|
||||
|
||||
@property
|
||||
def access_information(self):
|
||||
"""Alias for TokenAccessInformation (type may change in the future for improved struct)"""
|
||||
# Return enhanced subclass ?
|
||||
return self.TokenAccessInformation
|
||||
|
||||
@property
|
||||
def trust_level(self):
|
||||
"""The trust level of the process if present else ``None``.
|
||||
|
||||
:type: :class:`~windows.generated_def.winstructs.PSID`
|
||||
"""
|
||||
tl = self.TokenProcessTrustLevel
|
||||
if not tl: # NULL:
|
||||
return None
|
||||
return tl
|
||||
|
||||
virtualization_allowed = TokenVirtualizationAllowed #: Alias for TokenVirtualizationAllowed (type may change in the future for improved struct)
|
||||
virtualization_enabled = TokenVirtualizationEnabled #: Alias for TokenVirtualizationEnabled (type may change in the future for improved struct)
|
||||
|
||||
@property
|
||||
def integrity_level(self):
|
||||
"""The integrity level and attributes of the token
|
||||
|
||||
:type: :class:`windows.generated_def.winstructs.SID_AND_ATTRIBUTES`
|
||||
"""
|
||||
return self.TokenIntegrityLevel.Label # SID_AND_ATTRIBUTES
|
||||
|
||||
def get_integrity(self):
|
||||
"""Return the integrity level of the token
|
||||
|
||||
:type: :class:`int`
|
||||
"""
|
||||
sid = self.integrity_level.Sid
|
||||
count = winproxy.GetSidSubAuthorityCount(sid)
|
||||
integrity = winproxy.GetSidSubAuthority(sid, count[0] - 1)[0]
|
||||
return KNOW_INTEGRITY_LEVEL[integrity]
|
||||
|
||||
def set_integrity(self, integrity):
|
||||
"""Set the integrity level of a token
|
||||
|
||||
:param type: :class:`int`
|
||||
"""
|
||||
mandatory_label = gdef.TOKEN_MANDATORY_LABEL()
|
||||
mandatory_label.Label.Attributes = 0x60
|
||||
# cast integrity to int to accept SECURITY_MANDATORY_LOW_RID & other Flags
|
||||
mandatory_label.Label.Sid = gdef.PSID.from_string("S-1-16-{0}".format(int(integrity)))
|
||||
return self.set_informations(gdef.TokenIntegrityLevel, mandatory_label)
|
||||
|
||||
_INTEGRITY_PROPERTY_DOC = """The integrity of the token as an int (extracted from integrity PSID)
|
||||
|
||||
:getter: :func:`get_integrity`
|
||||
:setter: :func:`set_integrity`
|
||||
"""
|
||||
|
||||
integrity = property(get_integrity, set_integrity, doc=_INTEGRITY_PROPERTY_DOC)
|
||||
|
||||
ui_access = TokenUIAccess #: Alias for TokenUIAccess (type may change in the future for improved struct)
|
||||
|
||||
VALID_TOKEN_POLICIES = gdef.FlagMapper(
|
||||
gdef.TOKEN_MANDATORY_POLICY_OFF,
|
||||
gdef.TOKEN_MANDATORY_POLICY_NO_WRITE_UP,
|
||||
gdef.TOKEN_MANDATORY_POLICY_NEW_PROCESS_MIN,
|
||||
gdef.TOKEN_MANDATORY_POLICY_VALID_MASK,
|
||||
)
|
||||
|
||||
@property
|
||||
def mandatory_policy(self):
|
||||
"""mandatory integrity access policy for the associated token
|
||||
|
||||
:type: :class:`int` -- see `[MSDN] mandatory policy <https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_token_mandatory_policy>`_
|
||||
"""
|
||||
return self.VALID_TOKEN_POLICIES[self.TokenMandatoryPolicy.Policy]
|
||||
|
||||
@property
|
||||
def logon_sid(self):
|
||||
"""The logon sid of the token. (Case of multiple logon sid not handled and will raise AssertionError)
|
||||
|
||||
:type: :class:`windows.generated_def.winstructs.SID_AND_ATTRIBUTES`
|
||||
"""
|
||||
rgroups = self.TokenLogonSid
|
||||
assert rgroups.GroupCount == 1, "More than 1 TokenLogonSid"
|
||||
return rgroups.Groups[0]
|
||||
|
||||
is_appcontainer = TokenIsAppContainer #: Alias for TokenIsAppContainer (type may change in the future for improved struct)
|
||||
capabilities = TokenCapabilities #: Alias for TokenCapabilities (type may change in the future for improved struct)
|
||||
|
||||
@property
|
||||
def appcontainer_sid(self):
|
||||
"""The sid of the TokenAppContainerSid if present else ``None``
|
||||
|
||||
:type: :class:`~windows.generated_def.winstructs.PSID`
|
||||
"""
|
||||
sid = self.TokenAppContainerSid.TokenAppContainer
|
||||
if not sid: # NULL
|
||||
return None
|
||||
return sid
|
||||
|
||||
appcontainer_number = TokenAppContainerNumber #: Alias for TokenAppContainerNumber (type may change in the future for improved struct)
|
||||
|
||||
@property
|
||||
def security_attributes(self):
|
||||
"""The security attributes of the token
|
||||
|
||||
:type: [:class:`TokenSecurityAttributeV1`] - A list of token security attributes
|
||||
"""
|
||||
return self.TokenSecurityAttributes.attributes
|
||||
|
||||
|
||||
## Token Methods
|
||||
def duplicate(self, access_rigth=gdef.MAXIMUM_ALLOWED, attributes=None, type=None, impersonation_level=None):
|
||||
"""Duplicate the token into a new :class:`Token`.
|
||||
|
||||
:param type: The type of token: ``TokenPrimary(0x1L)`` or ``TokenImpersonation(0x2L)``
|
||||
:param impersonation_level: The :class:`~windows.generated_def.winstructs.SECURITY_IMPERSONATION_LEVEL` for a ``TokenImpersonation(0x2L)``:
|
||||
|
||||
- If ``type`` is ``TokenPrimary(0x1L)`` this parameter is ignored if ``None`` or used as-is.
|
||||
- If ``type`` is ``TokenImpersonation(0x2L)`` and this parameter is None, ``self.impersonation_level`` is used.
|
||||
- If ``type`` is ``TokenImpersonation(0x2L)`` and our Token is a ``TokenPrimary(0x1L)`` this parameter MUST be provided
|
||||
|
||||
:returns: :class:`Token` - The duplicate token
|
||||
|
||||
Example:
|
||||
|
||||
>>> tok
|
||||
<Token TokenId=0x39d6dde5 Type=TokenPrimary(0x1L)>
|
||||
>>> tok.duplicate()
|
||||
<Token TokenId=0x39d7b206 Type=TokenPrimary(0x1L)>
|
||||
>>> tok.duplicate(type=gdef.TokenImpersonation)
|
||||
...
|
||||
ValueError: Duplicating a PrimaryToken as a TokenImpersonation require explicit <impersonation_level> parameter
|
||||
>>> tok.duplicate(type=gdef.TokenImpersonation, impersonation_level=gdef.SecurityImpersonation)
|
||||
<Token TokenId=0x39dadbf8 Type=TokenImpersonation(0x2L) ImpersonationLevel=SecurityImpersonation(0x2L)>
|
||||
"""
|
||||
newtoken = gdef.HANDLE()
|
||||
if type is None:
|
||||
type = self.type
|
||||
if impersonation_level is None:
|
||||
if self.type == gdef.TokenImpersonation:
|
||||
impersonation_level = self.impersonation_level
|
||||
elif type != gdef.TokenImpersonation:
|
||||
impersonation_level = 0 #: ignored
|
||||
else:
|
||||
raise ValueError("Duplicating a PrimaryToken as a TokenImpersonation require explicit <impersonation_level> parameter")
|
||||
winproxy.DuplicateTokenEx(self.handle, access_rigth, attributes, impersonation_level, type, newtoken)
|
||||
return bltn_type(self)(newtoken.value)
|
||||
|
||||
def adjust_privileges(self, privileges):
|
||||
"""Adjust the token privileges according to ``privileges``.
|
||||
This API is the `complex one` to adjust multiple privileges at once.
|
||||
|
||||
To simply enable one privilege see :func:`enable_privilege`.
|
||||
|
||||
:param privileges: :class:`~windows.generated_def.winstructs.TOKEN_PRIVILEGES` (or subclass as :class:`TokenPrivileges`). To easily update your token privileges use the result of :data:`privileges`.
|
||||
|
||||
Example:
|
||||
|
||||
>>> tok = windows.current_process.token
|
||||
>>> privs = tok.privileges
|
||||
>>> privs["SeShutdownPrivilege"] = gdef.SE_PRIVILEGE_ENABLED
|
||||
>>> privs["SeUndockPrivilege"] = gdef.SE_PRIVILEGE_ENABLED
|
||||
>>> tok.adjust_privileges(privs)
|
||||
|
||||
"""
|
||||
buffsize = None
|
||||
if isinstance(privileges, TokenPrivilegesType):
|
||||
# The TokenPrivilegesType should come from a PTR via Improved buffer
|
||||
try:
|
||||
buffsize = privileges._b_base_.real_size
|
||||
except AttributeError as e:
|
||||
pass
|
||||
if buffsize is None:
|
||||
buffsize = ctypes.sizeof(privileges)
|
||||
winproxy.AdjustTokenPrivileges(self.handle, False, privileges, buffsize, None, None)
|
||||
if winproxy.GetLastError() == gdef.ERROR_NOT_ALL_ASSIGNED:
|
||||
# Transform this in a real WindowsError
|
||||
raise WindowsError(gdef.ERROR_NOT_ALL_ASSIGNED, "Failed to adjust all privileges")
|
||||
|
||||
def enable_privilege(self, name):
|
||||
"""Enable privilege ``name`` in the token
|
||||
|
||||
:raises: :class:`ValueError` if :class:`Token` has no privilege ``name``
|
||||
"""
|
||||
privs = self.privileges
|
||||
try:
|
||||
privs[name] = gdef.SE_PRIVILEGE_ENABLED
|
||||
except KeyError as e:
|
||||
# Emulate the WindowsError that would be triggered in 'adjust_privileges' ?
|
||||
raise ValueError("{0} has no privilege <{1}>".format(self, name))
|
||||
return self.adjust_privileges(privs)
|
||||
|
||||
def __repr__(self):
|
||||
flag_repr = gdef.Flag.__repr__
|
||||
try:
|
||||
tid_int = int(self.TokenStatistics.TokenId) # May raise -> which is bad as __repr__ may be called on __del__...
|
||||
except WindowsError as e:
|
||||
return object.__repr__(self)
|
||||
toktype = self.type
|
||||
if toktype == gdef.TokenPrimary:
|
||||
return "<{0} TokenId={1:#x} Type={2}>".format(type(self).__name__, tid_int, flag_repr(toktype))
|
||||
return "<{0} TokenId={1:#x} Type={2} ImpersonationLevel={3}>".format(type(self).__name__, tid_int, flag_repr(toktype), flag_repr(self.impersonation_level))
|
||||
|
||||
|
||||
import windows.security
|
||||
@@ -0,0 +1,88 @@
|
||||
import ctypes
|
||||
|
||||
import windows
|
||||
from windows import winproxy
|
||||
import windows.generated_def as gdef
|
||||
from windows.generated_def.winstructs import *
|
||||
|
||||
from windows.utils import AutoHandle
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
class LogicalDrive(AutoHandle):
|
||||
DRIVE_TYPE = gdef.FlagMapper(DRIVE_UNKNOWN, DRIVE_NO_ROOT_DIR, DRIVE_REMOVABLE,
|
||||
DRIVE_FIXED, DRIVE_REMOTE, DRIVE_CDROM, DRIVE_RAMDISK)
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""The type of drive, values are:
|
||||
|
||||
* DRIVE_UNKNOWN(0x0L)
|
||||
* DRIVE_NO_ROOT_DIR(0x1L)
|
||||
* DRIVE_REMOVABLE(0x2L)
|
||||
* DRIVE_FIXED(0x3L)
|
||||
* DRIVE_REMOTE(0x4L)
|
||||
* DRIVE_CDROM(0x5L)
|
||||
* DRIVE_RAMDISK(0x6L)
|
||||
|
||||
:type: :class:`long` or :class:`int` (or subclass)
|
||||
"""
|
||||
t = winproxy.GetDriveTypeA(self.name)
|
||||
return self.DRIVE_TYPE.get(t,t)
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"""The target path of the device
|
||||
|
||||
:type: :class:`str`"""
|
||||
res = query_dos_device(self.name.strip("\\"))
|
||||
if len(res) != 1:
|
||||
raise ValueError("[Unexpected result] query_dos_device(logicaldrive) returned multiple path")
|
||||
return res[0]
|
||||
|
||||
def query_info(self, info):
|
||||
return windows.utils.query_volume_information(self.handle, info)
|
||||
|
||||
@property
|
||||
def volume_info(self):
|
||||
return self.query_info(gdef.FileFsVolumeInformation)
|
||||
|
||||
@property
|
||||
def serial(self):
|
||||
return self.volume_info.VolumeSerialNumber
|
||||
|
||||
def _get_handle(self):
|
||||
nt_name = windows.utils.dospath_to_ntpath(self.name)
|
||||
handle = windows.winproxy.CreateFileA(nt_name, gdef.GENERIC_READ,
|
||||
gdef.FILE_SHARE_READ, None, gdef.OPEN_EXISTING, gdef.FILE_FLAG_BACKUP_SEMANTICS , None)
|
||||
return handle
|
||||
|
||||
def __repr__(self):
|
||||
return """<{0} "{1}" ({2})>""".format(type(self).__name__, self.name, self.type.name)
|
||||
|
||||
def enum_logical_drive():
|
||||
return [LogicalDrive(name) for name in get_logical_drive_names()]
|
||||
|
||||
def get_logical_drive_names():
|
||||
size = 0x100
|
||||
buffer = ctypes.c_buffer(size)
|
||||
rsize = winproxy.GetLogicalDriveStringsA(0x1000, buffer)
|
||||
return buffer[:rsize].rstrip(b"\x00").split(b"\x00")
|
||||
|
||||
def get_info(drivename):
|
||||
size = 0x1000
|
||||
volume_name = ctypes.c_buffer(size)
|
||||
fs_name = ctypes.c_buffer(size)
|
||||
flags = DWORD()
|
||||
winproxy.GetVolumeInformationA(drivename, volume_name, size, None, None, ctypes.byref(flags), fs_name, size)
|
||||
return volume_name[:10], fs_name[:10]
|
||||
|
||||
def query_dos_device(name):
|
||||
size = 0x1000
|
||||
buffer = ctypes.c_buffer(size)
|
||||
rsize = winproxy.QueryDosDeviceA(name, buffer, size)
|
||||
return buffer[:rsize].rstrip(b"\x00").split(b"\x00")
|
||||
@@ -0,0 +1,473 @@
|
||||
import windows
|
||||
import ctypes
|
||||
import struct
|
||||
import functools
|
||||
from functools import partial
|
||||
from collections import namedtuple
|
||||
|
||||
from ctypes.wintypes import *
|
||||
|
||||
import windows.com
|
||||
import windows.generated_def as gdef
|
||||
from windows.generated_def.winstructs import *
|
||||
|
||||
from windows.pycompat import basestring
|
||||
|
||||
# Common error check for all WMI COM interfaces
|
||||
# This 'just' add the corresponding 'WBEMSTATUS' to the hresult error code
|
||||
class WmiComInterface(object):
|
||||
"""Base class used for COM call error checking for WMI interfaces"""
|
||||
def errcheck(self, result, func, args):
|
||||
if result < 0:
|
||||
wmitag = gdef.WBEMSTATUS.mapper[result & 0xffffffff]
|
||||
raise ctypes.WinError(result, wmitag)
|
||||
return args
|
||||
|
||||
sentinel = object()
|
||||
# POC
|
||||
class QualifierSet(gdef.IWbemQualifierSet):
|
||||
def get_variant(self, name):
|
||||
"""Retrieve the value of property ``name`` as a :class:`~windows.com.Variant`
|
||||
|
||||
:return: :class:`~windows.com.Variant`
|
||||
"""
|
||||
if not isinstance(name, basestring):
|
||||
nametype = type(name).__name__
|
||||
raise TypeError("WmiObject attributes name must be str, not <{0}>".format(nametype))
|
||||
variant_res = windows.com.Variant()
|
||||
self.Get(name, 0, variant_res, None)
|
||||
return variant_res
|
||||
|
||||
def get(self, name, default=sentinel):
|
||||
"""Return the value of the property ``name``. The return value depends of the type of the property and can vary"""
|
||||
try:
|
||||
return self.get_variant(name).value
|
||||
except WindowsError as e:
|
||||
if (e.winerror & 0xffffffff) != gdef.WBEM_E_NOT_FOUND:
|
||||
raise
|
||||
if default is sentinel:
|
||||
raise
|
||||
return default
|
||||
|
||||
def names(self):
|
||||
res = POINTER(windows.com.SafeArray)()
|
||||
x = ctypes.pointer(res)
|
||||
self.GetNames(0, cast(x, POINTER(POINTER(gdef.SAFEARRAY))))
|
||||
# need to free the safearray / unlock ?
|
||||
properties = [p for p in res[0].to_list(BSTR)]
|
||||
return properties
|
||||
|
||||
|
||||
# https://docs.microsoft.com/en-us/windows/desktop/api/wbemcli/nn-wbemcli-iwbemclassobject
|
||||
|
||||
WmiMethod = namedtuple("WmiMethod", ["inparam", "outparam"])
|
||||
|
||||
# https://docs.microsoft.com/en-us/windows/desktop/WmiSdk/calling-a-method
|
||||
class WmiObject(gdef.IWbemClassObject, WmiComInterface):
|
||||
"""The WmiObject (which wrap ``IWbemClassObject``) contains and manipulates both class definitions and class object instances.
|
||||
Can be used as a mapping to access properties.
|
||||
"""
|
||||
|
||||
def get_variant(self, name):
|
||||
"""Retrieve the value of property ``name`` as a :class:`~windows.com.Variant`
|
||||
|
||||
:return: :class:`~windows.com.Variant`
|
||||
"""
|
||||
if not isinstance(name, basestring):
|
||||
nametype = type(name).__name__
|
||||
raise TypeError("WmiObject attributes name must be str, not <{0}>".format(nametype))
|
||||
variant_res = windows.com.Variant()
|
||||
self.Get(name, 0, variant_res, None, None)
|
||||
return variant_res
|
||||
|
||||
def get(self, name):
|
||||
"""Return the value of the property ``name``. The return value depends of the type of the property and can vary"""
|
||||
return self.get_variant(name).value
|
||||
|
||||
def get_method(self, name):
|
||||
"""Return the information about the method ``name``
|
||||
|
||||
:returns: :class:`WmiMethod`
|
||||
"""
|
||||
inpararm = type(self)()
|
||||
outpararm = type(self)()
|
||||
variant_res = windows.com.Variant()
|
||||
self.GetMethod(name, 0, inpararm, outpararm)
|
||||
return WmiMethod(inpararm, outpararm)
|
||||
|
||||
|
||||
def put_variant(self, name, variant):
|
||||
if not isinstance(name, basestring):
|
||||
nametype = type(name).__name__
|
||||
raise TypeError("WmiObject attributes name must be str, not <{0}>".format(nametype))
|
||||
return self.Put(name, 0, variant, 0)
|
||||
|
||||
def put(self, name, value):
|
||||
"""Set the property ``name`` to ``value``"""
|
||||
variant_value = windows.com.Variant(value)
|
||||
return self.put_variant(name, variant_value)
|
||||
|
||||
def spawn_instance(self):
|
||||
"""Create a new object of the class represented by the current :class:`WmiObject`
|
||||
|
||||
:returns: :class:`WmiObject`
|
||||
"""
|
||||
instance = type(self)()
|
||||
self.SpawnInstance(0, instance)
|
||||
return instance
|
||||
|
||||
@property
|
||||
def genus(self):
|
||||
"""The genus of the object.
|
||||
|
||||
:returns: ``WBEM_GENUS_CLASS(0x1L)`` if the :class:`WmiObject` is a Class and ``WBEM_GENUS_INSTANCE(0x2L)`` for instances and events.
|
||||
"""
|
||||
return gdef.tag_WBEM_GENUS_TYPE.mapper[self.get("__GENUS")]
|
||||
|
||||
## Higher level API
|
||||
def get_properties(self, system_properties=False):
|
||||
"""Return the list of properties names available for the current object.
|
||||
If ``system_properties`` is ``False`` property names begining with ``_`` are ignored.
|
||||
|
||||
:returns: [:class:`str`] -- A list of string
|
||||
|
||||
.. note:
|
||||
|
||||
About system properties: https://docs.microsoft.com/en-us/windows/desktop/wmisdk/wmi-system-properties
|
||||
"""
|
||||
res = POINTER(windows.com.SafeArray)()
|
||||
x = ctypes.pointer(res)
|
||||
self.GetNames(None, 0, None, cast(x, POINTER(POINTER(gdef.SAFEARRAY))))
|
||||
# need to free the safearray / unlock ?
|
||||
properties = [p for p in res[0].to_list(BSTR) if system_properties or (not p.startswith("_"))]
|
||||
return properties
|
||||
|
||||
properties = property(get_properties) #: The properties of the object (exclude system properties)
|
||||
|
||||
@property
|
||||
def qualifier_set(self): # changer de nom ?
|
||||
res = QualifierSet()
|
||||
self.GetQualifierSet(res)
|
||||
return res
|
||||
|
||||
def get_p_set(self, name): # Changer de nom ?
|
||||
res = QualifierSet()
|
||||
self.GetPropertyQualifierSet(name, res)
|
||||
return res
|
||||
|
||||
# Make WmiObject a mapping object
|
||||
|
||||
def keys(self):
|
||||
"""The properties of the object (include system properties)"""
|
||||
return self.get_properties(system_properties=True)
|
||||
|
||||
__getitem__ = get
|
||||
__setitem__ = put
|
||||
|
||||
def items(self):
|
||||
return [(k, self.get(k)) for k in self.properties]
|
||||
|
||||
def values(self): # Not sur anyone will use this but keep the dict interface
|
||||
return [x[1] for x in self.items()]
|
||||
|
||||
## Make it callable like any class :D
|
||||
__call__ = spawn_instance
|
||||
|
||||
def __repr__(self):
|
||||
if not self:
|
||||
return """<{0} (NULL)>""".format(type(self).__name__,)
|
||||
if self.genus == gdef.WBEM_GENUS_CLASS:
|
||||
return """<{0} class "{1}">""".format(type(self).__name__, self.get("__Class"))
|
||||
return """<{0} instance of "{1}">""".format(type(self).__name__, self.get("__Class"))
|
||||
|
||||
def __sprint__(self):
|
||||
return """ {0}\n
|
||||
{1}
|
||||
""".format(repr(self), "\n".join(": ".join([x[0], str(x[1])]) for x in sorted(self.items())))
|
||||
|
||||
|
||||
class WmiEnumeration(gdef.IEnumWbemClassObject, WmiComInterface):
|
||||
"""Represent an enumeration of object that can be itered"""
|
||||
DEFAULT_TIMEOUT = gdef.WBEM_INFINITE #: The default timeout
|
||||
|
||||
def next(self, timeout=None):
|
||||
"""Return the next object in the enumeration with `timeout`.
|
||||
|
||||
:raises: ``WindowsError(WBEM_S_TIMEDOUT)`` if timeout expire
|
||||
:returns: :class:`WmiObject`
|
||||
"""
|
||||
timeout = self.DEFAULT_TIMEOUT if timeout is None else timeout
|
||||
# For now the count is hardcoded to 1
|
||||
obj = WmiObject()
|
||||
return_count = gdef.ULONG(0)
|
||||
error = self.Next(timeout, 1, obj, return_count)
|
||||
if error == gdef.WBEM_S_TIMEDOUT:
|
||||
raise ctypes.WinError(gdef.WBEM_S_TIMEDOUT, "Wmi timeout")
|
||||
elif error == WBEM_S_FALSE:
|
||||
return None
|
||||
else:
|
||||
return obj
|
||||
|
||||
def __iter__(self):
|
||||
"""Return an iterator with ``DEFAULT_TIMEOUT``"""
|
||||
return self.iter_timeout(self.DEFAULT_TIMEOUT)
|
||||
|
||||
def iter_timeout(self, timeout=None):
|
||||
"""Return an iterator with a custom ``timeout``"""
|
||||
while True:
|
||||
obj = self.next(timeout)
|
||||
if obj is None:
|
||||
return
|
||||
yield obj
|
||||
|
||||
def all(self):
|
||||
"""Return all elements in the enumeration as a list
|
||||
|
||||
:returns: [:class:`WmiObject`] - A list of :class:`WmiObject`
|
||||
"""
|
||||
return list(self) # SqlAlchemy like :)
|
||||
|
||||
|
||||
class WmiCallResult(gdef.IWbemCallResult, WmiComInterface):
|
||||
"""The result of a WMI call/query. Real result value type depends of the context"""
|
||||
def __init__(self, result_type=None, namespace_name=None):
|
||||
self.result_type = result_type
|
||||
self.namespace_name = namespace_name
|
||||
|
||||
def get_call_status(self, timeout=gdef.WBEM_INFINITE):
|
||||
"""The status of the call"""
|
||||
status = gdef.LONG()
|
||||
self.GetCallStatus(timeout, status)
|
||||
return WBEMSTATUS.mapper[status.value & 0xffffffff]
|
||||
|
||||
def get_result_object(self, timeout=gdef.WBEM_INFINITE):
|
||||
"""The result as a :class:`WmiObject` (returned by :func:`WmiNamespace.exec_method`)"""
|
||||
result = WmiObject()
|
||||
self.GetResultObject(timeout, result)
|
||||
return result
|
||||
|
||||
def get_result_string(self, timeout=gdef.WBEM_INFINITE):
|
||||
"""The result as a :class:`WmiObject` (returned by :func:`WmiNamespace.put_instance`)"""
|
||||
result = gdef.BSTR()
|
||||
self.GetResultString(timeout, result)
|
||||
return result
|
||||
|
||||
def get_result_service(self, timeout=gdef.WBEM_INFINITE):
|
||||
"""The result as a :class:`WmiNamespace` (not used yet)"""
|
||||
result = WmiNamespace()
|
||||
self.GetResultServices(timeout, result)
|
||||
return result
|
||||
|
||||
@property
|
||||
def result(self):
|
||||
"""The result of the correct type based on ``self.result_type``"""
|
||||
if self.result_type is None:
|
||||
raise ValueError("Cannot call <result> with no result_type")
|
||||
return getattr(self, "get_result_" + self.result_type)()
|
||||
|
||||
|
||||
class WmiLocator(gdef.IWbemLocator, WmiComInterface):
|
||||
pass # Just for the WMI errcheck callback
|
||||
|
||||
|
||||
# !TEST CODE
|
||||
class WmiNamespace(gdef.IWbemServices, WmiComInterface):
|
||||
r"""An object to perform wmi request to a given ``namespace``"""
|
||||
|
||||
#CLSID_WbemAdministrativeLocator_IID = windows.com.IID.from_string('CB8555CC-9128-11D1-AD9B-00C04FD8FDFF')
|
||||
WbemLocator_CLSID = windows.com.IID.from_string('4590F811-1D3A-11D0-891F-00AA004B2E24')
|
||||
|
||||
DEFAULT_ENUM_FLAGS = (gdef.WBEM_FLAG_RETURN_IMMEDIATELY |
|
||||
WBEM_FLAG_FORWARD_ONLY) #: The defauls flags used for enumeration. ``(WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY)``
|
||||
|
||||
def __init__(self, namespace):
|
||||
self.name = namespace
|
||||
|
||||
@classmethod
|
||||
def connect(cls, namespace, user=None, password=None):
|
||||
"""Connect to ``namespace`` using ``user`` and ``password`` for authentification if given
|
||||
|
||||
:return: :class:`WmiNamespace` - The connected :class:`WmiNamespace`"""
|
||||
# this method assert com is initialised
|
||||
self = cls(namespace) # IWbemServices subclass
|
||||
locator = WmiLocator()
|
||||
windows.com.create_instance(cls.WbemLocator_CLSID, locator)
|
||||
locator.ConnectServer(namespace, user, password , None, gdef.WBEM_FLAG_CONNECT_USE_MAX_WAIT, None, None, self)
|
||||
locator.Release()
|
||||
return self
|
||||
|
||||
def query(self, query):
|
||||
"""Return the list of :class:`WmiObject` matching ``query``.
|
||||
|
||||
This API is the `simple one`, if you need timeout or complexe feature see :func:`exec_query`
|
||||
|
||||
:return: [:class:`WmiObject`] - A list of :class:`WmiObject`
|
||||
"""
|
||||
return list(self.exec_query(query))
|
||||
|
||||
def select(self, clsname, deep=True):
|
||||
"""Return the list of :class:`WmiObject` that are instance of ``clsname``. Deep has the same meaning as in :func:`create_instance_enum`.
|
||||
|
||||
This API is the `simple one`, if you need timeout or complexe feature see :func:`create_instance_enum`
|
||||
|
||||
:return: [:class:`WmiObject`] - A list of :class:`WmiObject`
|
||||
"""
|
||||
return list(self.create_instance_enum(clsname, deep=deep))
|
||||
|
||||
|
||||
def exec_query(self, query, flags=DEFAULT_ENUM_FLAGS, ctx=None):
|
||||
"""Execute a WQL query with custom flags and returns a ::class:`WmiEnumeration` that can be used to
|
||||
iter the result with timeouts
|
||||
|
||||
:returns: :class:`WmiEnumeration`
|
||||
"""
|
||||
enumerator = WmiEnumeration()
|
||||
self.ExecQuery("WQL", query, flags, ctx, enumerator)
|
||||
return enumerator
|
||||
|
||||
# Create friendly name for create_class_enum & create_instance_enum ?
|
||||
|
||||
def create_class_enum(self, superclass, flags=DEFAULT_ENUM_FLAGS, deep=True):
|
||||
"""Enumerate the classes in the ``namespace`` that match ``superclass``.
|
||||
if ``superclass`` is None will enumerate all top-level class. ``deep`` allow to returns all subclasses
|
||||
|
||||
:returns: :class:`WmiEnumeration`
|
||||
|
||||
.. note::
|
||||
|
||||
See https://docs.microsoft.com/en-us/windows/desktop/api/wbemcli/nf-wbemcli-iwbemservices-createclassenum
|
||||
"""
|
||||
|
||||
flags |= gdef.WBEM_FLAG_DEEP if deep else gdef.WBEM_FLAG_SHALLOW
|
||||
enumerator = WmiEnumeration()
|
||||
self.CreateClassEnum(superclass, flags, None, enumerator)
|
||||
return enumerator
|
||||
|
||||
@property
|
||||
def classes(self):
|
||||
"""The list of classes in the namespace. This a a wrapper arround :func:`create_class_enum`.
|
||||
|
||||
:return: [:class:`WmiObject`] - A list of :class:`WmiObject`
|
||||
"""
|
||||
return self.create_class_enum(None, deep=True)
|
||||
|
||||
def create_instance_enum(self, clsname, flags=DEFAULT_ENUM_FLAGS, deep=True):
|
||||
"""Enumerate the instances of ``clsname``. Deep allows to enumerate the instance of subclasses as well
|
||||
|
||||
:returns: :class:`WmiEnumeration`
|
||||
|
||||
Example:
|
||||
>>> windows.system.wmi["root\\subscription"].create_instance_enum("__EventConsumer", deep=False).all()
|
||||
[]
|
||||
>>> windows.system.wmi["root\\subscription"].create_instance_enum("__EventConsumer", deep=True).all()
|
||||
[<WmiObject instance of "NTEventLogEventConsumer">]
|
||||
|
||||
.. note::
|
||||
|
||||
See https://docs.microsoft.com/en-us/windows/desktop/api/wbemcli/nf-wbemcli-iwbemservices-createinstanceenum
|
||||
"""
|
||||
flags |= gdef.WBEM_FLAG_DEEP if deep else gdef.WBEM_FLAG_SHALLOW
|
||||
enumerator = WmiEnumeration()
|
||||
self.CreateInstanceEnum(clsname, flags, None, enumerator)
|
||||
return enumerator
|
||||
|
||||
def get_object(self, path):
|
||||
"""Return the object matching ``path``. If ``path`` is a class name return the class object``
|
||||
|
||||
:return: :class:`WmiObject`
|
||||
"""
|
||||
result = WmiObject()
|
||||
self.GetObject(path, gdef.WBEM_FLAG_RETURN_WBEM_COMPLETE, None, result, None)
|
||||
return result
|
||||
|
||||
def put_instance(self, instance, flags=gdef.WBEM_FLAG_CREATE_ONLY):
|
||||
"""Creates or updates an instance of an existing class in the namespace
|
||||
|
||||
:return: :class:`WmiCallResult` ``(string)`` - Used to retrieve the string representing the path of the object created/updated
|
||||
"""
|
||||
res = WmiCallResult(result_type="string")
|
||||
self.PutInstance(instance, flags, None, res)
|
||||
return res
|
||||
|
||||
def delete_instance(self, instance, flags=0):
|
||||
"""TODO: Document"""
|
||||
if isinstance(instance, gdef.IWbemClassObject):
|
||||
instance = instance["__Path"]
|
||||
return self.DeleteInstance(instance, flags, None, None)
|
||||
|
||||
def exec_method(self, obj, method, inparam, flags=0):
|
||||
"""Exec method named on ``object`` with ``inparam``.
|
||||
|
||||
:params obj: The :class:`WmiObject` or path of the object the call apply to
|
||||
:params method: The name of the method to call on the object
|
||||
:params inparam: The :class:`WmiObject` representing the input parameters and retrieve using :func:`WmiObject.get_method`
|
||||
|
||||
:returns: :class:`WmiCallResult` ``(object)`` if flag `WBEM_FLAG_RETURN_IMMEDIATELY` was passed
|
||||
:returns: :class:`WmiObject` the outparam object if flag `WBEM_FLAG_RETURN_IMMEDIATELY` was NOT passed
|
||||
|
||||
.. note::
|
||||
|
||||
This API will lakely change to better wrap with WmiObject/inparam/Dict & co
|
||||
"""
|
||||
if flags & gdef.WBEM_FLAG_RETURN_IMMEDIATELY:
|
||||
# semisynchronous call -> WmiCallResult
|
||||
result = WmiCallResult(result_type="object")
|
||||
outparam = None
|
||||
else:
|
||||
# Synchronous call -> WmiObject (outparam)
|
||||
result = None
|
||||
outparam = WmiObject()
|
||||
if isinstance(obj, gdef.IWbemClassObject):
|
||||
obj = obj.get("__Path")
|
||||
# Flags 0 -> synchronous call
|
||||
# No WmiCallResult result is directly in outparam
|
||||
self.ExecMethod(obj, method, 0, None, inparam, outparam, result)
|
||||
return outparam or result
|
||||
|
||||
def __repr__(self):
|
||||
null = "" if self else " (NULL)"
|
||||
return """<{0} "{1}"{2}>""".format(type(self).__name__, self.name, null)
|
||||
|
||||
class WmiManager(dict):
|
||||
"""The main WMI class exposed, used to list and access differents WMI namespace, can be used as a dict to access
|
||||
:class:`WmiNamespace` by name
|
||||
|
||||
Example:
|
||||
>>> windows.system.wmi["root\\SecurityCenter2"]
|
||||
<WmiNamespace "root\SecurityCenter2">
|
||||
"""
|
||||
DEFAULT_NAMESPACE = "root\\cimv2" #: The default namespace for :func:`select` & :func:`query`
|
||||
def __init__(self):
|
||||
# Someone is going to use wmi: let's init com !
|
||||
windows.com.init()
|
||||
self.wmi_requester_by_namespace = {}
|
||||
|
||||
@property
|
||||
def default_namespace(self):
|
||||
return self[self.DEFAULT_NAMESPACE]
|
||||
|
||||
@property
|
||||
def select(self):
|
||||
r""":func:`WmiRequester.select` for default WMI namespace 'root\\cimv2'"""
|
||||
return self.default_namespace.select
|
||||
|
||||
@property
|
||||
def query(self):
|
||||
r""":func:`WmiRequester.query` for default WMI namespace 'root\\cimv2'"""
|
||||
return self.default_namespace.query
|
||||
|
||||
def get_subnamespaces(self, root="root"):
|
||||
return [x["Name"] for x in self[root].select("__NameSpace")]
|
||||
|
||||
namespaces = property(get_subnamespaces)
|
||||
"""The list of available WMI namespaces"""
|
||||
|
||||
def _open_wmi_requester(self, namespace):
|
||||
return WmiNamespace.connect(namespace)
|
||||
|
||||
def __missing__(self, key):
|
||||
self[key] = self._open_wmi_requester(key)
|
||||
return self[key]
|
||||
|
||||
def __repr__(self):
|
||||
return object.__repr__(self)
|
||||
@@ -0,0 +1,3 @@
|
||||
from .apiproxy import is_implemented, get_target, resolve
|
||||
from .error import WinproxyError, ExportNotFound
|
||||
from .apis import * # Import all functions
|
||||
@@ -0,0 +1,122 @@
|
||||
import ctypes
|
||||
import functools
|
||||
|
||||
import windows.generated_def as gdef
|
||||
from .error import ExportNotFound
|
||||
from windows.pycompat import is_py3
|
||||
|
||||
# Utils
|
||||
def is_implemented(apiproxy):
|
||||
"""Return :obj:`True` if DLL/Api can be found"""
|
||||
try:
|
||||
apiproxy.force_resolution()
|
||||
except ExportNotFound:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def get_target(apiproxy):
|
||||
"""POC for newshook"""
|
||||
return apiproxy.target_dll, apiproxy.target_func
|
||||
|
||||
|
||||
def resolve(apiproxy):
|
||||
"""Resolve the address of ``apiproxy``. Might raise if ``apiproxy`` is not implemented"""
|
||||
apiproxy.force_resolution()
|
||||
func = ctypes.WinDLL(apiproxy.target_dll)[apiproxy.target_func]
|
||||
return ctypes.cast(func, gdef.PVOID).value
|
||||
|
||||
|
||||
class NeededParameterType(object):
|
||||
_inst = None
|
||||
|
||||
def __new__(cls):
|
||||
if cls._inst is None:
|
||||
cls._inst = super(NeededParameterType, cls).__new__(cls)
|
||||
return cls._inst
|
||||
|
||||
def __repr__(self):
|
||||
return "NeededParameter"
|
||||
|
||||
NeededParameter = NeededParameterType()
|
||||
sentinel = object()
|
||||
|
||||
class ApiProxy(object):
|
||||
APIDLL = None
|
||||
"""Create a python wrapper around a kernel32 function"""
|
||||
def __init__(self, func_name=None, error_check=sentinel, deffunc_module=None):
|
||||
self.deffunc_module = deffunc_module if deffunc_module is not None else gdef.winfuncs
|
||||
self.func_name = func_name
|
||||
if error_check is sentinel:
|
||||
error_check = self.default_error_check
|
||||
|
||||
self.error_check = error_check
|
||||
self._cprototyped = None
|
||||
|
||||
def __call__(self, python_proxy):
|
||||
# Use the name of the sub-function if None was given
|
||||
if self.func_name is None:
|
||||
self.func_name = python_proxy.__name__
|
||||
|
||||
errchk = None
|
||||
if self.error_check is not None:
|
||||
errchk = functools.wraps(self.error_check)(functools.partial(self.error_check, self.func_name))
|
||||
|
||||
prototype = getattr(self.deffunc_module, self.func_name + "Prototype")
|
||||
params = getattr(self.deffunc_module, self.func_name + "Params")
|
||||
python_proxy.prototype = prototype
|
||||
python_proxy.params = params
|
||||
python_proxy.errcheck = errchk
|
||||
python_proxy.target_dll = self.APIDLL
|
||||
python_proxy.target_func = self.func_name
|
||||
# Give access to the 'ApiProxy' object from the function
|
||||
python_proxy.proxy = self
|
||||
params_name = [param[1] for param in params]
|
||||
if (self.error_check.__doc__):
|
||||
doc = python_proxy.__doc__
|
||||
doc = doc if doc else ""
|
||||
python_proxy.__doc__ = doc + "\nErrcheck:\n " + self.error_check.__doc__
|
||||
|
||||
def generate_ctypes_function():
|
||||
try:
|
||||
api_dll = ctypes.windll[self.APIDLL]
|
||||
except WindowsError as e:
|
||||
if e.winerror == gdef.ERROR_BAD_EXE_FORMAT:
|
||||
e.strerror = e.strerror.replace("%1", "<{0}>".format(self.APIDLL))
|
||||
raise
|
||||
try:
|
||||
c_prototyped = prototype((self.func_name, api_dll), params)
|
||||
except (AttributeError, WindowsError):
|
||||
raise ExportNotFound(self.func_name, self.APIDLL)
|
||||
if errchk is not None:
|
||||
c_prototyped.errcheck = errchk
|
||||
self._cprototyped = c_prototyped
|
||||
|
||||
def perform_call(*args):
|
||||
if self._cprototyped is None:
|
||||
generate_ctypes_function()
|
||||
try:
|
||||
return self._cprototyped(*args)
|
||||
except ctypes.ArgumentError as e:
|
||||
# We just add a conversion ctypes argument fail
|
||||
# We can do some heavy computation if needed
|
||||
# Not a case that normally happen
|
||||
|
||||
# "argument 2: <type 'exceptions.TypeError'>: wrong type"
|
||||
# Thx ctypes..
|
||||
argnbstr, ecx, reason = e.args[0].split(":") # py2 / py3 compat :)
|
||||
if not argnbstr.startswith("argument "):
|
||||
raise # Don't knnow if it can happen
|
||||
argnb = int(argnbstr[len("argument "):])
|
||||
badarg = args[argnb - 1]
|
||||
if badarg is NeededParameter:
|
||||
badargname = params_name[argnb - 1]
|
||||
raise TypeError("{0}: Missing Mandatory parameter <{1}>".format(self.func_name, badargname))
|
||||
# Not NeededParameter: the caller need to fix the used param :)
|
||||
# raise the real ctypes error
|
||||
raise
|
||||
|
||||
|
||||
setattr(python_proxy, "ctypes_function", perform_call)
|
||||
setattr(python_proxy, "force_resolution", generate_ctypes_function)
|
||||
return python_proxy
|
||||
@@ -0,0 +1,27 @@
|
||||
from .advapi32 import *
|
||||
from .cfgmgr32 import *
|
||||
from .crypt32 import *
|
||||
from .cryptui import *
|
||||
from .dbghelp import *
|
||||
from .dnsapi import *
|
||||
from .iphlpapi import *
|
||||
from .kernel32 import *
|
||||
from .ktmw32 import *
|
||||
from .ntdll import *
|
||||
from .netapi32 import *
|
||||
from .ole32 import *
|
||||
from .oleaut32 import *
|
||||
from .oleacc import *
|
||||
from .psapi import *
|
||||
from .setupapi import *
|
||||
from .shell32 import *
|
||||
from .shlwapi import *
|
||||
from .tdh import *
|
||||
from .user32 import *
|
||||
from .version import *
|
||||
from .virtdisk import *
|
||||
from .wevtapi import *
|
||||
from .winhttp import *
|
||||
from .wininet import *
|
||||
from .wintrust import *
|
||||
from .ws2_32 import *
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user