windows.alpc + windows.rpc first version (manual_uac works with those)

This commit is contained in:
Clement Rouault
2017-05-19 17:14:10 +02:00
parent d24e62e319
commit e5e9ff7a47
5 changed files with 814 additions and 0 deletions
+260
View File
@@ -0,0 +1,260 @@
import ctypes
import windows
from windows import winproxy
from windows import generated_def as gn
ALPC_MSGFLG_REPLY_MESSAGE = 0x1
ALPC_MSGFLG_LPC_MODE = 0x2
ALPC_MSGFLG_RELEASE_MESSAGE = 0x10000
ALPC_MSGFLG_SYNC_REQUEST = 0x20000
ALPC_MSGFLG_WAIT_USER_MODE = 0x100000
ALPC_MSGFLG_WAIT_ALERTABLE = 0x200000
ALPC_MSGFLG_WOW64_CALL = 0x80000000
ALPC_MESSAGE_SECURITY_ATTRIBUTE = 0x80000000
ALPC_MESSAGE_VIEW_ATTRIBUTE = 0x40000000
ALPC_MESSAGE_CONTEXT_ATTRIBUTE = 0x20000000
ALPC_MESSAGE_HANDLE_ATTRIBUTE = 0x10000000
class AlpcMessage(gn.PORT_MESSAGE):
def __new__(cls, buffersize):
size = ctypes.sizeof(cls) + buffersize
buffer = ctypes.c_buffer(size)
self = cls.from_buffer(buffer)
self.raw_buffer = buffer
return self
def __init__(self, buffersize):
self.u1.s1.TotalLength = buffersize + ctypes.sizeof(self)
self.u1.s1.DataLength = buffersize
return super(AlpcMessage, self).__init__()
def read_data(self):
return self.raw_buffer[ctypes.sizeof(self):ctypes.sizeof(self) + self.u1.s1.DataLength]
def write_data(self, data):
self.raw_buffer[ctypes.sizeof(self): ctypes.sizeof(self) + len(data)] = data
data = property(read_data, write_data)
class MessageAttribute(gn.ALPC_MESSAGE_ATTRIBUTES):
def __new__(cls, flags):
size = cls._get_required_buffer_size(flags)
buffer = ctypes.c_buffer(size)
self = cls.from_buffer(buffer)
self.raw_buffer = buffer
return self
def __init__(self, flags):
res = gn.DWORD()
winproxy.AlpcInitializeMessageAttribute(flags, self, len(self.raw_buffer), res)
@staticmethod
def _get_required_buffer_size(flags):
res = gn.DWORD()
try:
windows.winproxy.AlpcInitializeMessageAttribute(flags, None, 0, res)
except windows.generated_def.ntstatus.NtStatusException:
return res.value
class AlpcPORT(object):
def __init__(self, port_name, msglen=0x1000):
self.port_name = port_name
self.handle = self._create_port(port_name, msglen)
def _create_port(self, port_name, msglen=0x1000):
handle = gn.HANDLE()
raw_name = port_name
if not raw_name.startswith("\\"):
raw_name = "\\" + port_name
utf16_len = len(raw_name) * 2
port_name = gn.UNICODE_STRING(utf16_len, utf16_len, raw_name)
obj_attr = gn.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
port_attr = gn.ALPC_PORT_ATTRIBUTES()
port_attr.Flags = 0
port_attr.MaxMessageLength = msglen
port_attr.MemoryBandwidth = 0
port_attr.MaxPoolUsage = 0
winproxy.NtAlpcCreatePort(handle, obj_attr, port_attr)
return handle.value
#class AlpcExchange(object):
# def send_receive_data(port_handle, data):
# raw_sendmsg = ctypes.c_buffer(0x1000)
# size = gn.SIZE_T(0x1000)
# sendmsg = ctypes.cast(raw_sendmsg, gn.PPORT_MESSAGE)
# buffer = ctypes.c_buffer(0x200)
# sendmsg_attr = ctypes.cast(buffer, gn.PALPC_MESSAGE_ATTRIBUTES)
# res = gn.DWORD()
# winproxy.AlpcInitializeMessageAttribute(ALPC_MESSAGE_CONTEXT_ATTRIBUTE + ALPC_MESSAGE_HANDLE_ATTRIBUTE + 1, sendmsg_attr , 0x200, res)
#
# sendmsg = AlpcMessage(len(data))
# sendmsg.data = data
#
# size = gn.SIZE_T(0x1000)
# receive = AlpcMessage(size.value)
# buffer = ctypes.c_buffer(0x200)
# receive_attr = ctypes.cast(buffer, gn.PALPC_MESSAGE_ATTRIBUTES)
# res = gn.DWORD()
#
# winproxy.NtAlpcSendWaitReceivePort(port_handle, ALPC_MSGFLG_SYNC_REQUEST, sendmsg, sendmsg_attr, receive, size, receive_attr, None)
# return receive, receive_attr
def send_receive_data(port_handle, data):
#sendmsg_attr = MessageAttribute(ALPC_MESSAGE_CONTEXT_ATTRIBUTE + ALPC_MESSAGE_HANDLE_ATTRIBUTE + 1)
sendmsg_attr = MessageAttribute(0)
sendmsg = AlpcMessage(len(data))
sendmsg.data = data
size = gn.SIZE_T(0x1000)
receive = AlpcMessage(size.value)
receive_attr = MessageAttribute(0)
winproxy.NtAlpcSendWaitReceivePort(port_handle, ALPC_MSGFLG_SYNC_REQUEST, sendmsg, sendmsg_attr, receive, size, receive_attr, None)
return receive_attr, receive
class AlpcClient(object):
def __init__(self):
self.portname = None
self.handle = None
def connect_to_port(self, port_name, connect_msg=None, maxmsglen=0x1000):
if self.handle is not None:
raise ValueError("Client already connected")
handle = gn.HANDLE()
#raw_name = "\\" + port_name
raw_name = port_name
utf16_len = len(raw_name) * 2
port_name = gn.UNICODE_STRING(utf16_len, utf16_len, raw_name)
obj_attr = gn.OBJECT_ATTRIBUTES()
obj_attr.Length = ctypes.sizeof(obj_attr)
obj_attr.RootDirectory = None
obj_attr.ObjectName = None
obj_attr.Attributes = 0
obj_attr.SecurityDescriptor = None
obj_attr.SecurityQualityOfService = None
port_attr = gn.ALPC_PORT_ATTRIBUTES()
port_attr.Flags = 0
port_attr.MaxMessageLength = maxmsglen
port_attr.MemoryBandwidth = 0
port_attr.MaxPoolUsage = 0
if True:
port_attr.SecurityQos.Length = 12
port_attr.SecurityQos.ImpersonationLevel = 2
port_attr.SecurityQos.ContextTrackingMode = 0
port_attr.SecurityQos.EffectiveOnly = 0
#define ALPC_PORFLG_ALLOW_LPC_REQUESTS 0x20000 // rev
#define ALPC_PORFLG_WAITABLE_PORT 0x40000 // dbg
#define ALPC_PORFLG_SYSTEM_PROCESS 0x100000 // dbg
#port_attr.MaxPoolUsage = 0
port_attr.Flags = 0x10000 # Flag qui fonctionne pour l'UAC
# 0x0010000 est le flag qui permet l'impersonation (en tout cas le pop UAC)
#port_attr.MaxPoolUsage = 4294967295
#port_attr.MaxSectionSize = 4294967295
##port_attr.MaxViewSize = 4294967295
#port_attr.MaxTotalSectionSize = 4294967295
#port_attr.DupObjectTypes = 4093
# tst.Flags -> 34144256
# tst.SecurityQos.Length -> 12
# tst.SecurityQos.ImpersonationLevel -> SecurityImpersonation(0x2L)
# tst.SecurityQos.ContextTrackingMode -> 0
# tst.SecurityQos.EffectiveOnly -> 0
# tst.MaxMessageLength -> 4096
# tst.MemoryBandwidth -> 0
# tst.MaxPoolUsage -> 4294967295
# tst.MaxSectionSize -> 4294967295
# tst.MaxViewSize -> 4294967295
# tst.MaxTotalSectionSize -> 4294967295
# tst.DupObjectTypes -> 4093
if connect_msg is not None:
size = len(connect_msg)
send_msg = AlpcMessage(size)
send_msg.data = connect_msg
sendmsg_attr = MessageAttribute(0)
receive_attr = MessageAttribute(0)
receive_attr = None
sendmsg_attr = None
buffersize = gn.DWORD(len(send_msg.raw_buffer))
else:
size = None
send_msg = None
sendmsg_attr = None
receive_attr = None
buffersize = None
#print(hex([0].AllocatedAttributes))
#import pdb;pdb.set_trace()
x = winproxy.NtAlpcConnectPort(handle, port_name,obj_attr, port_attr, ALPC_MSGFLG_SYNC_REQUEST, None, send_msg, buffersize, sendmsg_attr, receive_attr, None)
self.handle = handle.value
self.portname = port_name
if connect_msg is not None:
return send_msg
def send_receive(self, data):
return send_receive_data(self.handle, data)
class AlpcServer(object):
def __init__(self, port_name):
self.port = AlpcPORT(port_name)
def wait_data(self):
size = gn.SIZE_T(0x1000)
receive = AlpcMessage(size.value)
receive_attr = MessageAttribute(0)
winproxy.NtAlpcSendWaitReceivePort(self.port.handle, 0, None, None, receive, size, receive_attr, None)
return receive_attr, receive
def accept_connection(self, msg):
port_handle = self.port.handle
rhandle = gn.HANDLE()
ALPC_HANDLEFLG_DUPLICATE_INHERIT = 0x80000
port_attr = gn.ALPC_PORT_ATTRIBUTES()
port_attr.Flags = ALPC_HANDLEFLG_DUPLICATE_INHERIT
port_attr.DupObjectTypes = 4
port_attr.MaxMessageLength = 0x578
port_attr.MemoryBandwidth = 0
port_attr.MaxPoolUsage = 0x15E00
winproxy.NtAlpcAcceptConnectPort(rhandle, port_handle, 0, None, port_attr, None, msg, None, 1)
return rhandle.value, msg
def send_receive(self, data):
return send_receive_data(self.port.handle, data)
def reply(self, reply_to_msg, reply_msg):
port_handle = self.port.handle
sendmsg = AlpcMessage(len(reply_msg))
sendmsg.data = reply_msg
sendmsg_attr = MessageAttribute(0)
sendmsg.MessageId = reply_to_msg.MessageId
winproxy.NtAlpcSendWaitReceivePort(port_handle, ALPC_MSGFLG_RELEASE_MESSAGE, sendmsg, None, None, None, None, None)
return None, None
+3
View File
@@ -0,0 +1,3 @@
from client import RPC_SYNTAX_IDENTIFIER, RPCClient
import ndr
from epmapper import find_alpc_endpoint_and_connect, construct_alpc_tower
+110
View File
@@ -0,0 +1,110 @@
import windows.alpc as alpc
import windows.com
from windows.generated_def import USHORT
import ctypes
import struct
class _RPC_SYNTAX_IDENTIFIER(ctypes.Structure):
_fields_ = [
("SyntaxGUID", windows.com.IID),
("MajorVersion", USHORT),
("MinorVersion", USHORT),
]
def __repr__(self):
return '<RPC_SYNTAX_IDENTIFIER "{0}" ({1}, {2})>'.format(self.SyntaxGUID.to_string(), self.MajorVersion, self.MinorVersion)
RPC_SYNTAX_IDENTIFIER = _RPC_SYNTAX_IDENTIFIER
# DEFINES
REQUEST_TYPE_CALL = 0
REQUEST_TYPE_BIND = 1
KNOW_REQUEST_TYPE = {
REQUEST_TYPE_CALL : "REQUEST_CALL",
REQUEST_TYPE_BIND : "REQUEST_BIND",
}
RESPONSE_TYPE_BIND_OK = 1
RESPONSE_TYPE_FAIL = 2
RESPONSE_TYPE_SUCESS = 3
KNOW_RESPONSE_TYPE = {
RESPONSE_TYPE_FAIL : "RESPONSE_FAIL",
RESPONSE_TYPE_SUCESS : "RESPONSE_SUCESS",
RESPONSE_TYPE_BIND_OK: "RESPONSE_BIND_OK",
}
KNOWN_RPC_ERROR_CODE = {
1783 : "RPC_X_BAD_STUB_DATA",
1717 : "RPC_S_UNKNOWN_IF"
}
NOT_USED = 0xBAADF00D
# def dword_pack(*args):
# return "".join(struct.pack("<I", x) for x in args)
class RPCClient(object):
REQUEST_IDENTIFIER = 0x11223344
def __init__(self, port):
self.aplc_client = alpc.AlpcClient()
self.aplc_client.connect_to_port(port)
self.number_of_bind_if = 0 # if -> interface
self.if_bind_number = {}
def bind(self, IID_str, version=(1,0)):
IID = windows.com.IID.from_string(IID_str)
request = self._forge_bind_request(buffer(IID)[:], version, self.number_of_bind_if)
response = self._send_request(request)
# Parse reponse
request_type = self._get_request_type(response)
if request_type != RESPONSE_TYPE_BIND_OK:
raise ValueError("Unexpected reponse type. Expected RESPONSE_TYPE_BIND_OK got {0}".format(KNOW_RESPONSE_TYPE.get(request_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 call(self, IID, method_offset, params):
iid_hash = hash(buffer(IID)[:])
interface_nb = self.if_bind_number[iid_hash] # TODO: add __hash__ to IID
request = self._forge_call_request(interface_nb, method_offset, params)
response = self._send_request(request)
# Parse reponse
request_type = self._get_request_type(response)
if request_type != RESPONSE_TYPE_SUCESS:
raise ValueError("Unexpected reponse type. Expected RESPONSE_SUCESS got {0}".format(KNOW_RESPONSE_TYPE.get(request_type, request_type)))
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):
resp_attr, resp = self.aplc_client.send_receive(request)
return resp.data
def _forge_bind_request(self, rawuuid, syntaxversion, requested_if_nb):
version_major, version_minor = syntaxversion
# TODO: flags
data = struct.pack("III16sHHIIIIIIIIII", REQUEST_TYPE_BIND, NOT_USED, NOT_USED, rawuuid, version_major, version_minor, NOT_USED, requested_if_nb, NOT_USED, NOT_USED ,NOT_USED, NOT_USED, NOT_USED, NOT_USED, NOT_USED, NOT_USED) # Fonctionne pour le BIND :D
return data
def _forge_call_request(self, interface_nb, method_offset, params):
# TODO: differents REQUEST_IDENTIFIER for each req ?
request = struct.pack("<16I", REQUEST_TYPE_CALL, NOT_USED, 0, self.REQUEST_IDENTIFIER, interface_nb, method_offset, NOT_USED, NOT_USED, NOT_USED, NOT_USED, NOT_USED, NOT_USED, NOT_USED, NOT_USED, NOT_USED, NOT_USED)
request += params
return request
def _get_request_type(self, response):
"raise if request_type == RESPONSE_TYPE_FAIL"
request_type = struct.unpack("<I", response[:4])[0]
if request_type == 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
+172
View File
@@ -0,0 +1,172 @@
import struct
from collections import namedtuple
import windows
import windows.generated_def as gdef
from windows.rpc import ndr
from windows.dbgprint import dbgprint
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 EPMapperFunc8Parameters(ndr.NdrParameters):
MEMBERS = [NDRIID,
NdrTower,
ndr.NdrUniquePTR(ndr.NdrSID),
NdrContext,
ndr.NdrLong]
class Towers(ndr.NdrConformantVaryingArrays):
MEMBER_TYPE = ndr.NdrUniquePTR(NdrTower)
class EPMapperFunc8Results(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 = windows.com.IID.from_buffer_copy(lhs[1:17])
object = windows.rpc.RPC_SYNTAX_IDENTIFIER(iid, lhs[17], lhs[18])
# Floor 1
lhs, rhs = parse_floor(stream)
if not (lhs[0] == 0xd):
raise ValueError("Floor 0: IID expected")
iid = windows.com.IID.from_buffer_copy(lhs[1:17])
syntax = windows.rpc.RPC_SYNTAX_IDENTIFIER(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):
raise ValueError("ALPC Port name doest not end by \\x00")
return UnpackTower("ncalrpc", 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 = "\x0d"
TOWER_EMPTY_RHS = "\x00\x00"
TOWER_PROTOCOL_ID_ALPC = "\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.SyntaxGUID) + struct.pack("<BB", object.MajorVersion, object.MinorVersion)
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(object.SyntaxGUID) + struct.pack("<BB", object.MajorVersion, object.MinorVersion)
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
floor_3_lsh = "\xff"
floor_3_rsh = TOWER_EMPTY_RHS
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 endpoint_map_alpc(targetiid, version=(1,0), nb_response=1):
if isinstance(targetiid, basestring):
targetiid = windows.com.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 = windows.rpc.RPC_SYNTAX_IDENTIFIER(targetiid, *version)
## Syntax
syntax_iid = windows.com.IID.from_string("8a885d04-1ceb-11c9-9fe8-08002b104860")
rpc_syntax = windows.rpc.RPC_SYNTAX_IDENTIFIER(syntax_iid, 2, 0)
## Forge tower
tower_array_size, towerarray = construct_alpc_tower(rpc_object, rpc_syntax, "ncalrpc", "", None)
# parameters
local_system_psid = windows.utils.get_known_sid(gdef.WinLocalSystemSid)
context = (0, 0, 0, 0, 0)
# Pack request
fullreq = EPMapperFunc8Parameters.pack([bytearray(targetiid),
(tower_array_size, towerarray),
local_system_psid,
context,
nb_response])
# RPC Call
response = client.call(epmapperiid, 8, fullreq)
# Unpack response
stream = ndr.NdrStream(response)
unpacked = EPMapperFunc8Results.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)):
alpctowers = endpoint_map_alpc(targetiid, version, nb_response=50)
for tower in alpctowers:
dbgprint("Trying to connect to endpoint <{0}>".format(tower.endpoint), "RPC")
alpc_port = r"\RPC Control\{0}".format(tower.endpoint)
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
+269
View File
@@ -0,0 +1,269 @@
import windows
import windows.generated_def as gdef
import struct
# 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 + ("P" * (4 - len(s) % 4))
class NdrUniquePTR(object):
def __init__(self, subcls):
self.subcls = subcls
def pack(self, data):
subpack = self.subcls.pack(data)
if subpack is None:
return dword_pad(0)
return pack_dword(0x02020202) + subpack
def pack_in_struct(self, data, id):
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, None
return ptr, self.subcls
def parse(self, stream):
data = stream.partial_unpack("<I")
if data[0] == 0:
return None
return self.subcls.parse(stream)
class NdrSID(object):
@classmethod
def pack(cls, 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)
class NdrWString(object):
@classmethod
def pack(cls, data):
if data is None:
return None
data = data.encode("utf-16-le")
l = (len(data) / 2)
result = struct.pack("<3I", l, 0, l)
result += data
return dword_pad(result)
class NdrLong(object):
@classmethod
def pack(cls, data):
return struct.pack("<I", data)
@classmethod
def unpack(self, stream):
return stream.partial_unpack("<I")[0]
class NdrByte(object):
@classmethod
def pack(self, data):
return struct.pack("<B", data)
@classmethod
def unpack(self, stream):
return stream.partial_unpack("<B")[0]
class NdrStructure(object):
@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("SUCE")
conformant_size = []
res = []
pointed = []
for i, (member, memberdata) in enumerate(zip(cls.MEMBERS, data)):
if hasattr(member, "pack_in_struct"):
x, y = member.pack_in_struct(memberdata, i)
res.append(x)
if y is not None:
pointed.append(y)
elif hasattr(member, "pack_conformant"):
size, data = member.pack_conformant(memberdata)
conformant_size.append(size)
res.append(data)
else:
packed_member = member.pack(memberdata)
res.append(packed_member)
return dword_pad("".join(conformant_size)) + dword_pad("".join(res)) + dword_pad("".join(pointed))
@classmethod
def unpack(cls, 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)
for i, member in enumerate(cls.MEMBERS):
if conformant_members[i]:
data.append(member.unpack_conformant(stream, conformant_size))
else:
data.append(member.unpack(stream))
return cls.post_unpack(data)
@classmethod
def post_unpack(cls, data):
return data
class NdrParameters(object):
@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("SUCE")
res = []
for (member, memberdata) in zip(cls.MEMBERS, data):
packed_member = member.pack(memberdata)
res.append(packed_member)
return dword_pad("".join(res))
@classmethod
def unpack(cls, stream):
res = []
for member in cls.MEMBERS:
unpacked_member = member.unpack(stream)
res.append(unpacked_member)
return res
class NdrConformantArray(object):
MEMBER_TYPE = None
@classmethod
def pack(cls, data):
ndrsize = NdrLong.pack(len(data))
return dword_pad(ndrsize + "".join([cls.MEMBER_TYPE.pack(memberdata) for memberdata in data]))
@classmethod
def pack_conformant(cls, data):
ndrsize = NdrLong.pack(len(data))
ndrdata = dword_pad("".join([cls.MEMBER_TYPE.pack(memberdata) for memberdata in data]))
return ndrsize, ndrdata
@classmethod
def unpack_conformant(cls, stream, size):
res = [cls.MEMBER_TYPE.unpack(stream) for i in range(size)]
stream.align(4)
return res
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 + "".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 result
class NdrLongConformantArray(NdrConformantArray):
MEMBER_TYPE = NdrLong
class NdrByteConformantArray(NdrConformantArray):
MEMBER_TYPE = NdrByte
class NdrStream(object):
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):
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:]