Skip to content

Instantly share code, notes, and snippets.

@xpn

xpn/dotnetportal.c Secret

Created Nov 24, 2020
Embed
What would you like to do?
A POC to show how VTableFixup and VTables works to allow .NET methods to be called from unmanaged code.
#include <iostream>
#include <windows.h>
// Bytes needed for us to create a VTable and VTFixup
#define VTALLOC_SIZE 12
// V-table constants
#define COR_VTABLE_32BIT 0x01 // V-table slots are 32-bits in size.
#define COR_VTABLE_64BIT 0x02 // V-table slots are 64-bits in size.
#define COR_VTABLE_FROM_UNMANAGED 0x04 // If set, transition from unmanaged.
#define COR_VTABLE_FROM_UNMANAGED_RETAIN_APPDOMAIN 0x08 // NEW
#define COR_VTABLE_CALL_MOST_DERIVED 0x10 // Call most derived method described by
typedef BOOL STDMETHODCALLTYPE _CorDllMain(
HINSTANCE hInst,
DWORD dwReason,
LPVOID lpReserved
);
typedef struct IMAGE_COR_VTABLEFIXUP // From CoreCLR's corhdr.h
{
uint32_t RVA; // Offset of v-table array in image.
uint16_t Count; // How many entries at location.
uint16_t Type; // COR_VTABLE_xxx type of entries.
} IMAGE_COR_VTABLEFIXUP;
typedef void (*portal)(void);
// Jumps to the _CorDLLMain function
BOOL loadCOR(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {
_CorDllMain* corInit;
HMODULE cor = LoadLibraryA("mscoree.dll");
corInit = (_CorDllMain*)GetProcAddress(cor, "_CorDllMain");
return corInit(hinstDLL, fdwReason, lpReserved);
}
// Finds a section with enough space for us to inject our VTFixup and VTable entries
IMAGE_SECTION_HEADER* findSlackSpace(IMAGE_SECTION_HEADER* sectionHeaders, int sectionCount) {
for (int i = 0; i < sectionCount; i++) {
if (sectionHeaders[i].Misc.VirtualSize - sectionHeaders[i].SizeOfRawData > VTALLOC_SIZE) {
return &sectionHeaders[i];
}
}
return NULL;
}
int main(int argc, char **argv)
{
IMAGE_DOS_HEADER* dosHeader;
IMAGE_FILE_HEADER* fileHeader;
IMAGE_OPTIONAL_HEADER32* optionalHeader;
IMAGE_COR20_HEADER *corheader;
IMAGE_SECTION_HEADER* sectionHeader, *slackSectionHeader;
DWORD old, vtfixupAddr, vtableAddr;
IMAGE_COR_VTABLEFIXUP* vtFixupEntry;
printf(".NET Portal POC - by @_xpn_\n\n");
printf("[*] Loading our .NET assembly into memory\n");
char* assembly = (char*)LoadLibraryExA("C:\\L33tMalwarez.dll", 0, DONT_RESOLVE_DLL_REFERENCES);
if (assembly == NULL) {
printf("[x] Error loading assembly\n");
return 1;
}
dosHeader = (IMAGE_DOS_HEADER*)assembly;
fileHeader = (IMAGE_FILE_HEADER *)((char *)assembly + dosHeader->e_lfanew + 4);
optionalHeader = (IMAGE_OPTIONAL_HEADER32 *)((char*)fileHeader + sizeof(IMAGE_FILE_HEADER));
if (optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress == NULL) {
printf("[x] .NET header offset NULL (make sure this is a .NET assembly)\n");
return 2;
}
corheader = (IMAGE_COR20_HEADER *)((char *)assembly + optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress);
sectionHeader = (IMAGE_SECTION_HEADER*)((char*)fileHeader + fileHeader->SizeOfOptionalHeader + sizeof(IMAGE_FILE_HEADER));
if ((corheader->Flags & 1) == 1) {
printf("[x] Error: Assembly compiled with ILOnly flag, CorDllMain will fail, remove this flag with 'corflags.exe'\n");
return 4;
}
// Find a section for us to inject into
printf("[*] Finding a section for us to inject into\n");
slackSectionHeader = findSlackSpace(sectionHeader+1, fileHeader->NumberOfSections);
if (slackSectionHeader == NULL) {
printf("[x] Could not find an appropriate section to inject\n");
return 3;
}
printf("[*] Found section %s\n", slackSectionHeader->Name);
// Give ourselves some of that slack space
vtfixupAddr = slackSectionHeader->VirtualAddress + slackSectionHeader->Misc.VirtualSize;
vtableAddr = vtfixupAddr + sizeof(IMAGE_COR_VTABLEFIXUP);
// Update the IMAGE_SECTION_HEADER
VirtualProtect(slackSectionHeader, sizeof(IMAGE_SECTION_HEADER), PAGE_READWRITE, &old);
slackSectionHeader->Misc.VirtualSize += VTALLOC_SIZE;
// Create our VTFixup table
printf("[*] Creating our VTFixup\n");
VirtualProtect(assembly + vtfixupAddr, VTALLOC_SIZE, PAGE_READWRITE, &old);
vtFixupEntry = (IMAGE_COR_VTABLEFIXUP *)((char*)assembly + vtfixupAddr);
vtFixupEntry->Count = 1;
vtFixupEntry->RVA = vtableAddr;
vtFixupEntry->Type = COR_VTABLE_32BIT | COR_VTABLE_FROM_UNMANAGED;
// Add the method token we want converting
*(DWORD *)((char *)assembly + vtableAddr) = 0x06000002;
// Update the IMAGE_COR20_HEADER to point to our new VTableFixup
printf("[*] Pointing COR20 header to our VTFixup\n");
VirtualProtect(corheader, sizeof(IMAGE_COR20_HEADER), PAGE_READWRITE, &old);
corheader->VTableFixups.VirtualAddress = vtfixupAddr;
corheader->VTableFixups.Size = 8;
//corheader->Flags = 0x20000;
// Let the CLR create the portal
printf("[*] Calling CorDllMain\n");
BOOL r = loadCOR((HMODULE)assembly, DLL_PROCESS_ATTACH, 0);
if (r == FALSE) {
printf("[x] Error: COR init failed - Make sure ILOnly flag isn't set\n");
return 4;
}
printf("[*] Jumping through the portal...\n");
// If we here, we can jump through the portal into managed land
portal entry = (portal)*(DWORD*)((char*)assembly + vtableAddr);
entry();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment