Files
naksyn-PythonMemoryModule/pythonmemorymodule/windows/rpc/client.py
T
naksyn db1893910c 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.
2023-07-27 06:44:29 -07:00

180 lines
7.6 KiB
Python

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