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 §ionHeaders[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