mirror of
https://github.com/naksyn/PythonMemoryModule
synced 2026-06-06 16:24:25 +00:00
db1893910c
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.
200 lines
7.6 KiB
Python
200 lines
7.6 KiB
Python
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
|
|
|