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