Skip to content

Instantly share code, notes, and snippets.

@xpn
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();
}
@GetRektBoy724
Copy link

GetRektBoy724 commented May 16, 2022

Hi, it looks like that i've some problem with the "portal". Here's the code that i used, i've changed some stuff cause the DLL that i want to load is 64-bit. There is no problem at the process, it all went well, but when it comes to the execution of the portal, the process craches with an AccessViolation error, do you know what caused the problem?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment