Search K
Appearance
Appearance
Other ways to support HackTricks:
This is a summary of the post https://blog.xpnsec.com/macos-injection-via-third-party-frameworks/. Check it for further details!
The handling of communication between debugger and debuggee in .NET is managed by dbgtransportsession.cpp. This component sets up two named pipes per .NET process as seen in dbgtransportsession.cpp#L127, which are initiated via twowaypipe.cpp#L27. These pipes are suffixed with -in
and -out
.
By visiting the user's $TMPDIR
, one can find debugging FIFOs available for debugging .Net applications.
DbgTransportSession::TransportWorker is responsible for managing communication from a debugger. To initiate a new debugging session, a debugger must send a message via the out
pipe starting with a MessageHeader
struct, detailed in the .NET source code:
struct MessageHeader {
MessageType m_eType; // Message type
DWORD m_cbDataBlock; // Size of following data block (can be zero)
DWORD m_dwId; // Message ID from sender
DWORD m_dwReplyId; // Reply-to Message ID
DWORD m_dwLastSeenId; // Last seen Message ID by sender
DWORD m_dwReserved; // Reserved for future (initialize to zero)
union {
struct {
DWORD m_dwMajorVersion; // Requested/accepted protocol version
DWORD m_dwMinorVersion;
} VersionInfo;
...
} TypeSpecificData;
BYTE m_sMustBeZero[8];
}
To request a new session, this struct is populated as follows, setting the message type to MT_SessionRequest
and the protocol version to the current version:
static const DWORD kCurrentMajorVersion = 2;
static const DWORD kCurrentMinorVersion = 0;
// Configure the message type and version
sSendHeader.m_eType = MT_SessionRequest;
sSendHeader.TypeSpecificData.VersionInfo.m_dwMajorVersion = kCurrentMajorVersion;
sSendHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion = kCurrentMinorVersion;
sSendHeader.m_cbDataBlock = sizeof(SessionRequestData);
This header is then sent over to the target using the write
syscall, followed by the sessionRequestData
struct containing a GUID for the session:
write(wr, &sSendHeader, sizeof(MessageHeader));
memset(&sDataBlock.m_sSessionID, 9, sizeof(SessionRequestData));
write(wr, &sDataBlock, sizeof(SessionRequestData));
A read operation on the out
pipe confirms the success or failure of the debugging session establishment:
read(rd, &sReceiveHeader, sizeof(MessageHeader));
Once a debugging session is established, memory can be read using the MT_ReadMemory
message type. The function readMemory is detailed, performing the necessary steps to send a read request and retrieve the response:
bool readMemory(void *addr, int len, unsigned char **output) {
// Allocation and initialization
...
// Write header and read response
...
// Read the memory from the debuggee
...
return true;
}
The complete proof of concept (POC) is available here.
Similarly, memory can be written using the writeMemory
function. The process involves setting the message type to MT_WriteMemory
, specifying the address and length of the data, and then sending the data:
bool writeMemory(void *addr, int len, unsigned char *input) {
// Increment IDs, set message type, and specify memory location
...
// Write header and data, then read the response
...
// Confirm memory write was successful
...
return true;
}
The associated POC is available here.
To execute code, one needs to identify a memory region with rwx permissions, which can be done using vmmap -pages:
vmmap -pages [pid]
vmmap -pages 35829 | grep "rwx/rwx"
Locating a place to overwrite a function pointer is necessary, and in .NET Core, this can be done by targeting the Dynamic Function Table (DFT). This table, detailed in jithelpers.h
, is used by the runtime for JIT compilation helper functions.
For x64 systems, signature hunting can be used to find a reference to the symbol _hlpDynamicFuncTable
in libcorclr.dll
.
The MT_GetDCB
debugger function provides useful information, including the address of a helper function, m_helperRemoteStartAddr
, indicating the location of libcorclr.dll
in the process memory. This address is then used to start a search for the DFT and overwrite a function pointer with the shellcode's address.
The full POC code for injection into PowerShell is accessible here.
Other ways to support HackTricks: