githubEdit

macOS .Net Applications Injection

circle-check

This is a summary of the post https://blog.xpnsec.com/macos-injection-via-third-party-frameworks/arrow-up-right. Check it for further details!

.NET Core Debugging

Establishing a Debugging Session

The handling of communication between debugger and debuggee in .NET is managed by dbgtransportsession.cpparrow-up-right. This component sets up two named pipes per .NET process as seen in dbgtransportsession.cpp#L127arrow-up-right, which are initiated via twowaypipe.cpp#L27arrow-up-right. 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::TransportWorkerarrow-up-right 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:

This header is then sent over to the target using the write syscall, followed by the sessionRequestData struct containing a GUID for the session:

A read operation on the out pipe confirms the success or failure of the debugging session establishment:

Reading Memory

Once a debugging session is established, memory can be read using the MT_ReadMemoryarrow-up-right message type. The function readMemory is detailed, performing the necessary steps to send a read request and retrieve the response:

The complete proof of concept (POC) is available herearrow-up-right.

Writing Memory

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:

The associated POC is available herearrow-up-right.

.NET Core Code Execution

To execute code, one needs to identify a memory region with rwx permissions, which can be done using vmmap -pages:

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.harrow-up-right, 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 herearrow-up-right.

References

circle-check

Last updated