mirror of
https://github.com/hakril/PythonForWindows
synced 2026-06-08 14:31:45 +00:00
windows.alpc + windows.rpc first version (manual_uac works with those)
This commit is contained in:
+260
@@ -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
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
from client import RPC_SYNTAX_IDENTIFIER, RPCClient
|
||||
import ndr
|
||||
from epmapper import find_alpc_endpoint_and_connect, construct_alpc_tower
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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:]
|
||||
Reference in New Issue
Block a user