mirror of
https://github.com/hakril/PythonForWindows
synced 2026-06-08 14:31:45 +00:00
466 lines
15 KiB
Python
466 lines
15 KiB
Python
"""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
|
|
|
|
|
|
# ## 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
|
|
res = []
|
|
for i in itertools.count():
|
|
x = self.target.read_memory(base + (i * 0x100), 0x100)
|
|
if "\x00" in x:
|
|
res.append(x.split("\x00", 1)[0])
|
|
break
|
|
res.append(x)
|
|
return "".join(res)
|
|
|
|
|
|
class RemoteWCharP(RemotePtr, ctypes.c_char_p):
|
|
@property
|
|
def value(self):
|
|
base = self.raw_value
|
|
res = []
|
|
for i in itertools.count():
|
|
x = self.target.read_memory(base + (i * 0x100), 0x100)
|
|
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):
|
|
if not isinstance(slice, (int, long)):
|
|
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
|
|
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):
|
|
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
|
|
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
|