Click here to Skip to main content
15,893,588 members
Articles / Programming Languages / MSIL

.NET Internals and Code Injection

Rate me:
Please Sign up or sign in to vote.
4.91/5 (53 votes)
14 May 2008CPOL24 min read 144.4K   5.2K   188  
An article about .NET internals and code injection
// rbcoree.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
#include <CommCtrl.h>
#include <CommDlg.h>
#include <tlhelp32.h> 
#include <tchar.h>
#include <CorHdr.h>
#include "corinfo.h"
#include "corjit.h"
#include "RebelDotNET.h"
#include "resource.h"

#ifndef PAGE_SIZE 
#define PAGE_SIZE 0x1000
#endif

#define IS_FLAG(Value, Flag) ((Value & Flag) == Flag)

HINSTANCE hInstance;

extern "C" __declspec(dllexport) void HookJIT();

VOID ListThread();


BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  dwReason,
                       LPVOID lpReserved
					 )
{
	hInstance = (HINSTANCE) hModule;

	HookJIT();

	if (dwReason == DLL_PROCESS_ATTACH)
	{
		CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) ListThread, 
			NULL, 0, NULL);
	}

	return TRUE;
}

// unimportant
extern "C" __declspec(dllexport) int __stdcall _CorExeMain(void)
{
	return 0;
}


//
// Hook JIT's compileMethod
//

BOOL bHooked = FALSE;

ULONG_PTR *(__stdcall *p_getJit)();
typedef int (__stdcall *compileMethod_def)(ULONG_PTR classthis, ICorJitInfo *comp, 
										   CORINFO_METHOD_INFO *info, unsigned flags,         
										   BYTE **nativeEntry, ULONG  *nativeSizeOfCode);
struct JIT
{
	compileMethod_def compileMethod;
};

compileMethod_def compileMethod;

int __stdcall my_compileMethod(ULONG_PTR classthis, ICorJitInfo *comp,
							   CORINFO_METHOD_INFO *info, unsigned flags,         
							   BYTE **nativeEntry, ULONG  *nativeSizeOfCode);

extern "C" __declspec(dllexport) void HookJIT()
{
	if (bHooked) return;

	LoadLibrary(_T("mscoree.dll"));

	HMODULE hJitMod = LoadLibrary(_T("mscorjit.dll"));

	if (!hJitMod)
		return;

	p_getJit = (ULONG_PTR *(__stdcall *)()) GetProcAddress(hJitMod, "getJit");

	if (p_getJit)
	{
		JIT *pJit = (JIT *) *((ULONG_PTR *) p_getJit());

		if (pJit)
		{
			DWORD OldProtect;
			VirtualProtect(pJit, sizeof (ULONG_PTR), PAGE_READWRITE, &OldProtect);
			compileMethod =  pJit->compileMethod;
			pJit->compileMethod = &my_compileMethod;
			VirtualProtect(pJit, sizeof (ULONG_PTR), OldProtect, &OldProtect);
			bHooked = TRUE;
		}
	}
}

//
// Logging
//

struct AssemblyInfo
{
	CORINFO_MODULE_HANDLE hCorModule;

	WCHAR AssemblyName[MAX_PATH];

	VOID *ImgBase;
	UINT ImgSize;

	BOOL bIdentified;

	HANDLE hRebReport;

	BOOL bDump;
	TCHAR DumpFileName[MAX_PATH];

} LoggedAssemblies[100];

UINT NumberOfLoggedAssemblies = 0;

VOID LogAssembly(ICorJitInfo *comp, CORINFO_METHOD_INFO *info);

DWORD GetTokenFromMethodHandle(ICorJitInfo *comp, CORINFO_METHOD_INFO *info);

VOID AddMethod(CORINFO_METHOD_INFO *mi);
BOOL CreateRebFile(AssemblyInfo *ai);
//
// hooked compileMethod
//
/*__declspec (naked) */
int __stdcall my_compileMethod(ULONG_PTR classthis, ICorJitInfo *comp,
							   CORINFO_METHOD_INFO *info, unsigned flags,         
							   BYTE **nativeEntry, ULONG  *nativeSizeOfCode)
{
	// in case somebody hooks us (x86 only)
#ifdef _M_IX86
	__asm 
	{
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
	}
#endif

	//
	// check if it's the dump process
	//

	if (comp == NULL)
	{
		AddMethod(info);
		return 0;
	}

	LogAssembly(comp, info);

	// call original method
	// I'm not using the naked + jmp approach to avoid x64 incompatibilities
	int nRet = compileMethod(classthis, comp, info, flags, nativeEntry, nativeSizeOfCode);
	
	return nRet;
}

//
// convert an address to its module ImgBase and Name (if possible)
//

VOID AddressToModuleInfo(VOID *pAddress, WCHAR *AssemblyName,
						 VOID **pImgBase, UINT *pImgSize,
						 BOOL *pbIdentified) 
{ 
	DWORD dwPID = GetCurrentProcessId();
	HANDLE hModuleSnap = INVALID_HANDLE_VALUE; 
	MODULEENTRY32 me32; 

	static BOOL bFirstUnkAsm = TRUE;

	hModuleSnap = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE, dwPID ); 

	if (hModuleSnap == INVALID_HANDLE_VALUE) 
		return;

	me32.dwSize = sizeof (MODULEENTRY32); 

	if (!Module32First(hModuleSnap, &me32 )) 
	{ 
		CloseHandle(hModuleSnap);    
		return; 
	} 

	do 
	{
		if (((ULONG_PTR) pAddress) > ((ULONG_PTR) me32.modBaseAddr) &&
			((ULONG_PTR) pAddress) < (((ULONG_PTR) me32.modBaseAddr) + 
			me32.modBaseSize))
		{
			if (pImgBase) *pImgBase = (VOID *) me32.modBaseAddr;
			if (pImgSize) *pImgSize = me32.modBaseSize;
			wcscpy_s(AssemblyName, MAX_PATH, me32.szExePath);
			if (pbIdentified) *pbIdentified = TRUE;
			return;
		}

	} while (Module32Next(hModuleSnap, &me32)); 

	CloseHandle(hModuleSnap); 

	if (pbIdentified) *pbIdentified = FALSE;

	MEMORY_BASIC_INFORMATION mbi = { 0 };
	VirtualQuery(pAddress, &mbi, sizeof (MEMORY_BASIC_INFORMATION));

	if (pImgBase) *pImgBase = mbi.AllocationBase;

	DWORD ImgSize = 0;

	__try
	{
		IMAGE_DOS_HEADER *pDosHeader = (IMAGE_DOS_HEADER *)
			mbi.AllocationBase;

		if (pDosHeader->e_magic == IMAGE_DOS_SIGNATURE)
		{
			IMAGE_NT_HEADERS *pNtHeaders = (IMAGE_NT_HEADERS *)
				(pDosHeader->e_lfanew + (ULONG_PTR) pDosHeader);

			if (pNtHeaders->Signature == IMAGE_NT_SIGNATURE)
			{
				ImgSize = pNtHeaders->OptionalHeader.SizeOfImage;
			}
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		goto endinfo;
	}

endinfo:

	if (pImgSize) *pImgSize = ImgSize;

	if (bFirstUnkAsm)
	{
		wsprintfW(AssemblyName, L"Base: %p - Size: %08X - Primary Assembly", 
			mbi.AllocationBase, ImgSize);
		bFirstUnkAsm = FALSE;
	}
	else
	{
		wsprintfW(AssemblyName, L"Base: %p - Size: %08X - unidentfied", 
			mbi.AllocationBase, ImgSize);
	}
	
} 

VOID LogAssembly(ICorJitInfo *comp, CORINFO_METHOD_INFO *info)
{
	// already in the list?
	for (UINT x = 0; x < NumberOfLoggedAssemblies; x++)
	{
		if (LoggedAssemblies[x].hCorModule == info->scope)
			return;
	}

	//
	// Add assembly to the logged list
	//

	AddressToModuleInfo(info->ILCode, 
		LoggedAssemblies[NumberOfLoggedAssemblies].AssemblyName,
		&LoggedAssemblies[NumberOfLoggedAssemblies].ImgBase,
		&LoggedAssemblies[NumberOfLoggedAssemblies].ImgSize,
		&LoggedAssemblies[NumberOfLoggedAssemblies].bIdentified);

	LoggedAssemblies[NumberOfLoggedAssemblies].hCorModule = info->scope;
	LoggedAssemblies[NumberOfLoggedAssemblies].bDump = FALSE;

	NumberOfLoggedAssemblies++;
}

//
// Listing
//

LRESULT CALLBACK ListDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);

VOID ListThread()
{
	Sleep(2000);

	InitCommonControls();

	DialogBox(hInstance, MAKEINTRESOURCE(IDD_ASMLIST), NULL, (DLGPROC) ListDlgProc);
}

LRESULT CALLBACK ListDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{

	case WM_INITDIALOG:
		{
			HWND hList = GetDlgItem(hDlg, LST_ASMS);

			LV_COLUMN lvc;

			ZeroMemory(&lvc, sizeof (LV_COLUMN));

			lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
			lvc.fmt = LVCFMT_LEFT;

			lvc.cx = 500;
			lvc.pszText = _T("Assembly Path");
			ListView_InsertColumn(hList, 0, &lvc);

			SendMessage(hList, LVM_SETEXTENDEDLISTVIEWSTYLE,
				LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP,
				LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);


			SendMessage(hDlg, WM_COMMAND, IDC_REFRESH, 0);

			break;
		}

	case WM_CLOSE:
		{
			EndDialog(hDlg, 0);
			break;
		}

	case WM_COMMAND:
		{
			switch (LOWORD(wParam))
			{

			case IDC_REFRESH:
				{
					HWND hList = GetDlgItem(hDlg, LST_ASMS);

					ListView_DeleteAllItems(hList);

					LV_ITEM lvi;

					ZeroMemory(&lvi, sizeof (LV_ITEM));

					lvi.mask = LVIF_TEXT | LVIF_STATE | LVIF_PARAM;

					for (UINT x = 0; x < NumberOfLoggedAssemblies; x++)
					{
						lvi.lParam = (LPARAM) LoggedAssemblies[x].hCorModule;
						lvi.pszText = LoggedAssemblies[x].AssemblyName;
						ListView_InsertItem(hList, &lvi);
					}

					break;
				}

			case IDC_DUMPASM:
				{
					HWND hList = GetDlgItem(hDlg, LST_ASMS);

					int nSel = ListView_GetNextItem(hList, -1, LVNI_SELECTED);

					if (nSel == -1) break;

					OPENFILENAME SaveFileName;

					TCHAR DumpFileName[MAX_PATH];

					ZeroMemory(DumpFileName, MAX_PATH * sizeof (TCHAR));

					ZeroMemory(&SaveFileName, sizeof (OPENFILENAME));

					SaveFileName.lStructSize = sizeof (OPENFILENAME);
					SaveFileName.hwndOwner = hDlg;
					SaveFileName.lpstrFilter = _T("All Files (*.*)\0*.*\0");
					SaveFileName.lpstrFile = DumpFileName;
					SaveFileName.nMaxFile = MAX_PATH;
					SaveFileName.lpstrTitle = _T("Save Assembly As...");

					if (!GetSaveFileName(&SaveFileName)) 
						break;

					LV_ITEM lvi;
					ZeroMemory(&lvi, sizeof (LV_ITEM));

					lvi.mask = LVIF_PARAM;
					lvi.iItem = nSel;

					ListView_GetItem(hList, &lvi);

					for (UINT x = 0; x < NumberOfLoggedAssemblies; x++)
					{
						if (LoggedAssemblies[x].hCorModule == 
							(CORINFO_MODULE_HANDLE) lvi.lParam)
						{
							HANDLE hFile = CreateFile(DumpFileName, GENERIC_WRITE,
								FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);

							if (hFile == INVALID_HANDLE_VALUE)
								break;

							DWORD dwOldProtect;

							VirtualProtect(LoggedAssemblies[x].ImgBase, 
								LoggedAssemblies[x].ImgSize, PAGE_EXECUTE_READ,
								&dwOldProtect);

							for (UINT nPage = 0; 
								nPage < (LoggedAssemblies[x].ImgSize / PAGE_SIZE);
								nPage++)
							{
								DWORD BW;

								__try
								{
									VOID *pPage = (VOID *) ((nPage * PAGE_SIZE) + 
										(ULONG_PTR) LoggedAssemblies[x].ImgBase);

									WriteFile(hFile, pPage, PAGE_SIZE, &BW, NULL);
								}

								__except (EXCEPTION_EXECUTE_HANDLER)
								{
									SetFilePointer(hFile, PAGE_SIZE, NULL, FILE_CURRENT);
									SetEndOfFile(hFile);
								}
							}
							
							CloseHandle(hFile);
							
							MessageBox(hDlg, _T("Assembly successfully dumped."), 
								_T("Dumped"), MB_ICONINFORMATION);
						}
					}

					break;
				}

			case IDC_REBFILE:
				{
					HWND hList = GetDlgItem(hDlg, LST_ASMS);

					int nSel = ListView_GetNextItem(hList, -1, LVNI_SELECTED);

					if (nSel == -1) break;

					LV_ITEM lvi;
					ZeroMemory(&lvi, sizeof (LV_ITEM));

					lvi.mask = LVIF_PARAM;
					lvi.iItem = nSel;

					ListView_GetItem(hList, &lvi);

					for (UINT x = 0; x < NumberOfLoggedAssemblies; x++)
					{
						if (LoggedAssemblies[x].hCorModule == 
							(CORINFO_MODULE_HANDLE) lvi.lParam)
						{
							OPENFILENAME OpenFileName;

							TCHAR ReportFileName[MAX_PATH];

							ZeroMemory(ReportFileName, MAX_PATH * sizeof (TCHAR));

							ZeroMemory(&OpenFileName, sizeof (OPENFILENAME));

							OpenFileName.lStructSize = sizeof (OPENFILENAME);
							OpenFileName.hwndOwner = hDlg;
							OpenFileName.lpstrFilter = _T("Report Rebel File (*.rebel)\0*.rebel\0");
							OpenFileName.lpstrFile = ReportFileName;
							OpenFileName.nMaxFile = MAX_PATH;
							OpenFileName.lpstrTitle = _T("Select a Report Rebel File...");

							if (!GetOpenFileName(&OpenFileName)) 
								break;

							LoggedAssemblies[x].hRebReport = CreateFile(ReportFileName,
								GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);

							if (LoggedAssemblies[x].hRebReport == INVALID_HANDLE_VALUE)
								break;

							OPENFILENAME SaveFileName;

							ZeroMemory(LoggedAssemblies[x].DumpFileName, MAX_PATH * sizeof (TCHAR));

							ZeroMemory(&SaveFileName, sizeof (OPENFILENAME));

							SaveFileName.lStructSize = sizeof (OPENFILENAME);
							SaveFileName.hwndOwner = hDlg;
							SaveFileName.lpstrFilter = _T("Rebel File (*.rebel)\0*.rebel\0");
							SaveFileName.lpstrFile = LoggedAssemblies[x].DumpFileName;
							SaveFileName.nMaxFile = MAX_PATH;
							SaveFileName.lpstrTitle = _T("Save Rebel File As...");
							SaveFileName.lpstrDefExt = _T("rebel");

							if (!GetSaveFileName(&SaveFileName)) 
							{
								CloseHandle(LoggedAssemblies[x].hRebReport);
								break;
							}

							// dump
							CreateRebFile(&LoggedAssemblies[x]);

							break;
						}
					}

					break;
				}
			}

			break;
		}
	}

	return FALSE;
}

//
// Dumping
//

DWORD RvaToOffset(VOID *pBase, DWORD Rva)
{
	__try
	{
		DWORD Offset = Rva, Limit;

		IMAGE_DOS_HEADER *pDosHeader = (IMAGE_DOS_HEADER *) pBase;

		if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
			return 0;

		IMAGE_NT_HEADERS *pNtHeaders = (IMAGE_NT_HEADERS *) (
			pDosHeader->e_lfanew + (ULONG_PTR) pDosHeader);

		if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE)
			return 0;

		IMAGE_SECTION_HEADER *Img = IMAGE_FIRST_SECTION(pNtHeaders);

		if (Rva < Img->PointerToRawData)
			return Rva;

		for (WORD i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++)
		{
			if (Img[i].SizeOfRawData)
				Limit = Img[i].SizeOfRawData;
			else
				Limit = Img[i].Misc.VirtualSize;

			if (Rva >= Img[i].VirtualAddress &&
				Rva < (Img[i].VirtualAddress + Limit))
			{
				if (Img[i].PointerToRawData != 0)
				{
					Offset -= Img[i].VirtualAddress;
					Offset += Img[i].PointerToRawData;
				}

				return Offset;
			}
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		return 0;
	}

	return 0;
}

UINT GetMethodSize(REBEL_METHOD *rbMethod)
{
	UINT nMethodSize = sizeof (REBEL_METHOD);

	if (!IS_FLAG(rbMethod->Mask, REBEL_METHOD_MASK_NAMEOFFSET))
		nMethodSize += rbMethod->NameOffsetOrSize;

	if (!IS_FLAG(rbMethod->Mask, REBEL_METHOD_MASK_SIGOFFSET))
		nMethodSize += rbMethod->SignatureOffsetOrSize;

	if (!IS_FLAG(rbMethod->Mask, REBEL_METHOD_MASK_LOCVARSIGOFFSET))
		nMethodSize += rbMethod->LocalVarSigOffsetOrSize;

	nMethodSize += rbMethod->CodeSize;
	nMethodSize += rbMethod->ExtraSectionsSize;

	return nMethodSize;
}

static HANDLE hRebuildDump = INVALID_HANDLE_VALUE;

VOID AddMethod(CORINFO_METHOD_INFO *mi)
{
	REBEL_METHOD rbMethod;

	ZeroMemory(&rbMethod, sizeof (REBEL_METHOD));

	rbMethod.Token = mi->locals.token;
	rbMethod.CodeSize = mi->ILCodeSize;
	
	DWORD BRW;

	SetFilePointer(hRebuildDump, 0, NULL, FILE_END);

	WriteFile(hRebuildDump, &rbMethod, sizeof (REBEL_METHOD), &BRW, NULL);
	WriteFile(hRebuildDump, mi->ILCode, mi->ILCodeSize, &BRW, NULL);

	//
	// Increase number of methods
	//

	SetFilePointer(hRebuildDump, 0, NULL, FILE_BEGIN);

	REBEL_NET_BASE rbBase;

	ReadFile(hRebuildDump, &rbBase, sizeof (REBEL_NET_BASE), &BRW, NULL);

	rbBase.NumberOfMethods++;

	SetFilePointer(hRebuildDump, 0, NULL, FILE_BEGIN);

	WriteFile(hRebuildDump, &rbBase, sizeof (REBEL_NET_BASE), &BRW, NULL);
}

BOOL CreateRebFile(AssemblyInfo *ai)
{
	DWORD BRW;

	hRebuildDump = CreateFile(ai->DumpFileName, GENERIC_READ | 
		GENERIC_WRITE, FILE_SHARE_READ, NULL,
		CREATE_ALWAYS, 0, NULL);

	if (hRebuildDump == INVALID_HANDLE_VALUE)
		return FALSE;

	REBEL_NET_BASE rbBase;

	ZeroMemory(&rbBase, sizeof (REBEL_NET_BASE));

	rbBase.Signature = REBEL_NET_SIGNATURE;
	rbBase.MethodsOffset = sizeof (REBEL_NET_BASE);

	WriteFile(hRebuildDump, &rbBase, sizeof (REBEL_NET_BASE), &BRW, NULL);

	//
	// Get the new JIT compileMethod which by now should be
	// hooked by the code protection
	//

	HMODULE hJitMod = LoadLibrary(_T("mscorjit.dll"));

	p_getJit = (ULONG_PTR *(__stdcall *)()) GetProcAddress(hJitMod, "getJit");

	if (!p_getJit)
	{
		CloseHandle(hRebuildDump);
		hRebuildDump = INVALID_HANDLE_VALUE;
		return FALSE;
	}

	JIT *pJit = (JIT *) *((ULONG_PTR *) p_getJit());

	//

	REBEL_NET_BASE repBase;

	if (!ReadFile(ai->hRebReport, &repBase, sizeof (REBEL_NET_BASE), &BRW, NULL))
	{
		CloseHandle(hRebuildDump);
		hRebuildDump = INVALID_HANDLE_VALUE;
		return FALSE;
	}

	UINT CurMethodOffset = repBase.MethodsOffset;

	for (UINT x = 0; x < repBase.NumberOfMethods; x++)
	{
		SetFilePointer(ai->hRebReport, CurMethodOffset, NULL, FILE_BEGIN);

		REBEL_METHOD rbRepMethod;

		ReadFile(ai->hRebReport, &rbRepMethod, sizeof (REBEL_METHOD), &BRW, NULL);

		//
		// Calculate current method's code location
		// Use RvaToOffset only when the module wasn't mapped
		//

		BYTE *pMethodCode;

		if (ai->bIdentified)
		{
			pMethodCode = (BYTE *) (rbRepMethod.RVA + (ULONG_PTR) ai->ImgBase);
		}
		else
		{
			DWORD Offset = RvaToOffset(ai->ImgBase, rbRepMethod.RVA);

			if (Offset == 0) continue;

			pMethodCode = (BYTE *) (Offset + (ULONG_PTR) ai->ImgBase);
		}

		//
		// we should check the validity of the memory
		// pointer by pMethodCode
		//

		// TODO

		//
		// skips the method header
		//

		BYTE HeaderFormat = *pMethodCode;

		HeaderFormat &= 3;

		if (HeaderFormat == 2)			// Tiny = 2 (CorILMethod_TinyFormat)
			pMethodCode++;
		else							// Fat = 3 (CorILMethod_FatFormat)
			pMethodCode += (sizeof (DWORD) * 3);

		//
		// Do the fake compileMethod request
		//

		CORINFO_METHOD_INFO mi = { 0 };

		mi.ILCode = pMethodCode;
		mi.ILCodeSize = rbRepMethod.CodeSize;
		mi.scope = ai->hCorModule;
		// use this to pass the token to our AddMethod
		mi.locals.token = rbRepMethod.Token;

		pJit->compileMethod((ULONG_PTR) pJit, NULL, &mi, 0, NULL, NULL);

		//
		// next method
		//

		CurMethodOffset += GetMethodSize(&rbRepMethod);
	}

	//
	// Close file and notify the user
	//

	CloseHandle(hRebuildDump);
	hRebuildDump = INVALID_HANDLE_VALUE;

	MessageBox(0, _T("Assembly code successfully dumped."), _T("JIT Dumper"),
		MB_ICONINFORMATION);

	return TRUE;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Germany Germany
The languages I know best are: C, C++, C#, Assembly (x86, x64, ARM), MSIL, Python, Lua. The environments I frequently use are: Qt, Win32, MFC, .NET, WDK. I'm a developer and a reverse engineer and I like playing around with internals.

You can find most of my work at http://ntcore.com.

Comments and Discussions