mirror of
https://github.com/naksyn/PythonMemoryModule
synced 2026-06-06 16:24:25 +00:00
command line support (partial) via PEB stomping
This update include support to passing command line parameters to unmanaged exe via PEB stomping. This technique is not working with every executable since it depends on which functions are used to pass arguments. Generally, to get a universally working technique would be required to hook GetCommandlineA GetCommandlineW __getmainargs and __wgetmainargs since PEB stomping won't cover all cases, more details here: https://blog-30cm-tw.translate.goog/2020/08/windows-c-mainargc-argv.html?_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=it&_x_tr_pto=wapp However, during my testing I found that mimikatz and several go binaries are working just by doing PEB stomping. On the other hand, cmdline passing via PEB stomping alone to mingw and VS compiled binaries won't likely work.
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
from . 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
|
||||
Reference in New Issue
Block a user