0% found this document useful (0 votes)
165 views

NET Internals and Native Compiling

This document discusses .NET internals and native compiling. It describes what native compiling is, which is converting .NET MSIL code to native machine code and removing the MSIL, making decompilation more difficult. It discusses native images, which are the internal format used for native compiling. It also describes how to create a simple "Native Framework Deployment" tool to deploy a native image without MSIL by modifying the registry to point another assembly to an existing native image. Finally, it briefly mentions topics like native injection, native decompiling, and .NET virtual machine protections.

Uploaded by

Steve Schneider
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
165 views

NET Internals and Native Compiling

This document discusses .NET internals and native compiling. It describes what native compiling is, which is converting .NET MSIL code to native machine code and removing the MSIL, making decompilation more difficult. It discusses native images, which are the internal format used for native compiling. It also describes how to create a simple "Native Framework Deployment" tool to deploy a native image without MSIL by modifying the registry to point another assembly to an existing native image. Finally, it briefly mentions topics like native injection, native decompiling, and .NET virtual machine protections.

Uploaded by

Steve Schneider
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 56

12/10/2016

.NETInternalsandNativeCompiling

.NETInternalsandNativeCompiling
Introduction
WhatisNativeCompiling?
NativeImages
NativeFrameworkDeployment
TheNativeLoader
RegistryVirtualization
IssuesandConclusions
NativeInjection
NativeDecompiling
.NETVirtualMachines
Conclusions

Introduction
Thisarticleisthesecondofatwoseriesofarticlesaboutthe.NETFrameworkinternalsandtheprotectionsavailablefor.NETassemblies.Thisarticle
analyzesmoreindepththe.NETinternals.Thus,thereadershouldbefamiliarwiththepastarticle,otherwisecertainparagraphsofthisarticlemay
seemobscure.AstheJITinnerworkingshaven'tbeenanalyzedyet,.NETprotectionsarequitenavenowadays.Thissituationwillrapidlychangeas
soonasthereverseengineeringcommunitywillfocusitsattentiononthistechnology.Thesetwoarticlesareaimedtoraisetheconsciousnessaboutthe
currentstateof.NETprotectionsandwhatispossibletoachievebuthasn'tbeendoneyet.Inparticular,thepastarticleabout.NETcodeinjection
represents,let'ssay,thepresent,whereasthecurrentoneabout.NETnativecompilingrepresentsthefuture.WhatI'mpresentinginthesetwoarticles
isnewatthetimeI'mwritingit,butIexpectittobecomeobsoleteinlessthanayear.Ofcourse,thisisobviousasI'mmovingthefirststepsoutfrom
current.NETprotectionsinthedirectionofbetterones.Butthisarticleisn'treallyaboutprotections:exploringthe.NETFrameworkinternalscanbe
usefulformanypurposes.So,talkingaboutprotectionsisjustameanstoanend.

WhatisNativeCompiling?
StrictlyspeakingitmeansconvertingtheMSILcodeofa.NETassemblytonativemachinecodeandthenremovingtheMSILcodefromthatassembly,
makingitimpossibletodecompileitinastraightforwardway.Theonlyexistingtooltonativecompile.NETassembliesistheSalamander.NETlinker
whichreliesonnativeimagestodoitsjob.The"nativeimages"(whichinthisarticleIcalled"NativeFrameworkDeployment")techniqueisquite
distantfrom.NETinternals:onedoesn'tneedagoodknowledgeof.NETinternalstoimplementit.But,asthetopicis,Imightsay,quitepopular,I'm
goingtoshowtothereaderhowtowritehisNativeFrameworkDeploymenttoolifhewishesto.However,thearticlewillgofurtherthanthatby
introducingNativeInjection,whichmeansnothingelsethantakingtheJIT'splace.Eventhoughthisisnotusefulforcommercialprotections(or
whatever),it'sagoodwaytoplaywithJITinternals.I'malsogoingtointroduceNativeDecompiling,whichistheresultofanunderstandingof.NET
internals.I'malsotryingtoaddressanothertopic:.NETVirtualMachineProtections.

NativeImages
Theinternalformatofnativeimagesisyetundocumented.Italsowouldbequiteharddocumentingitasitconstantlychanges.Forinstance,it
completelychangedfromversion1toversion2ofthe.NETframework.And,asthenewframework3.5SP1hasbeenreleasedafewdaysago,it
changedanothertime.I'mnotsureonwhatextentitchangedinthelastversion,butonechangecanbenoticedimmediately.TheoriginalMetaDatais
http://www.ntcore.com/files/netint_native.htm

1/56

12/10/2016

.NETInternalsandNativeCompiling

nowdirectlyavailablewithoutchangingtheentryinthe.NETdirectorytotheMetaDataRVAfoundintheNativeHeader.Ifyoudothataction,you'llend
upwiththenativeimageMetaDatawhichisn'tmuchinteresting.Also,inearliernativeimages(previousto3.5SP1framework)toobtaintheoriginal
MSILcodeofamethod,onehadtoaddtheRVAfoundintheMethodDeftabletotheOriginalMSILCodeRVAentryinthenativeheader.Thisisnolonger
necessaryastheMethodDefRVAentrynowpointsdirectlytothemethod'sMSILcode.
Thisisimportant,sinceprotectionsliketheSalamanderLinkerneedtoremovetheoriginalMSILcodefromanativeimagebeforetheycandeployit.
Otherwisethewholeprotectionbecomeuseless,sinceMetaDataandMSILcodeareallwhatisnecessarytorebuildafullydecompilable.NETassembly.
ThestrippingofMSILcodewaseasierinthe"old"format,becauseoneonlyneededtheOriginalMSILCodeRVAandSizeentriestoknowwhichpartof
thenativeimagehadtobeerasedwithasimplememset.
Allweneedtoknowaboutthenativeimages'formatinordertowriteaNativeFrameworkDeploymenttoolishowtostriptheMSILcodefromit.Even
theSalamanderLinkerwillneedtimetoadapttothenewnativeimageformatinordertoworkwiththeframework3.5SP1.And,asthereisn't
currentlyanyprotectionwhichworkswith3.5SP1nativeimages,whatI'mwritinginthisarticlehasbeenonlytestedagainstearlierimages.
AnotherreasonwhyitisdifficulttodocumentnativeimagesisthelackofthecodewhichhandlesthemintheRotorproject.Itwasadeliberatechoice
madebyMicrosofttoexcludethispartoftheframeworkfromtheRotorproject.

NativeFrameworkDeployment
ThenameIgavetothissortofprotectionmayappearabitstrange,butitwillappearquiteobviousassoonasIhaveexplainedhowitactuallyworks.
Asalreadysaid,there'snoprotectionsystemotherthantheSalamanderLinkerwhichremovestheMSILandshipsonlynativemachinecode.And,in
ordertodothat,theSalamanderLinkerreliesonnativeimagesgeneratedbyngen.TheSalamanderLinkeroffersadownloadabledemonstrationonits
homepageandwewilltakealookatthatwithout,ofcourse,analyzingitscode,asIdon'tintendtoviolateanylicensingtermsitmayimply.Inthis
paragraphI'mgoingtoshowhowitistechnicallyquiteeasytowriteaNativeFrameworkDeploymenttool,butIdoubtthatthereaderwillwantto
writeoneafterreadingthis.Don'tgetmewrong,theSalamanderLinkerabsolutelyholdsitspromiseandactuallyremovestheMSILcodefromone's
application,butthemethodusedfacesmanyproblemsandinmyopinionisnotarealsolution.
TheSalamanderLinker'sdemonstrationiscalledscribbleandit'sasimpleMDIapplication.Let'slookattheapplication'smaindirectory:

http://www.ntcore.com/files/netint_native.htm

2/56

12/10/2016

.NETInternalsandNativeCompiling

Thev2.0.50727directorycorrespondstotheframeworkdirectorywhichcanbefoundinside"C:\Windows\Microsoft.NET\",althoughitcomeswithonlya
limitednumberoffilesinside:

http://www.ntcore.com/files/netint_native.htm

3/56

12/10/2016

.NETInternalsandNativeCompiling

I'llexplaininamomentwhysomeimportantassemblieslikeSystemorSystem.Windows.Formsaremissing.Meanwhile,the"C"directoryleadstoa
seriesofotherdirectories.Themainpathitproduceslookssomethinglikethis:"C\WINDOWS\assembly\".Inthelastdirectoryofthispathtwomore
directoriesarecontained.Onedirectoryiscalled"GAC_32"andcontainsthemscorlibassembly.Theotherdirectoryiscalled
"NativeImages_v2.0.50727_32"andisthedirectorywherenativeimagesarestored.Thisdirectorycontainsonlytwonativeimages:themscorlibone
andthescribbleone.Thescribblenativeimageisgigantic,that'sbecausebeforengeningscribblewasmergedwithitsdependencies:System,
System.Windows.Forms,etc.Theonlydependencywhichcan'tbemergedtoanotherassemblyismscorlib.Thereasonsforthataremany.Thereader
canimagineoneofthemifhehasreadthepastarticle:mscorlibisalowlevelassemblystrictlyconnectedtotheframework,amongthethingsitdoes
itprovidestheinternalcallsimplementation.Ifanonsystemassemblytriestocallaninternalfunction,itwillonlyresultintheframeworkdisplayinga
privilegeserror.
TheSalamanderLinkerdeploysasubsetoftheframework.Thus,thenameNativeFrameworkDeploymentIgavetothistechnique.Nativeimagesare
boundtoatheframeworkinarathercomplicateway.Infact,nativeimagesarehighlyframeworkdependent.Butlet'sforasecondfocusonlyonthe
relationshipbetweenanassemblyanditsnativeimageonthelocalsystem.Onecanmodifyanassemblyallhewants,butbyjustleavingits#GUID
streamandsomedataintheMetaDatatableunchangedthesamenativeimagewillbeloadedforthatassembly.Thismeansthatonecanevenbinda
totallydifferentassemblytoanativeimage.Thisisquiteeasytoachieve:first,let'sngenarandomassembly.Assembliesareboundtotheirnative
imagesthroughtheregistry.Theregistrykey"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Fusion\NativeImagesIndex\v2.0.50727_32"iswherethe
bindingbetweenassembliesandnativeimageshappens:

http://www.ntcore.com/files/netint_native.htm

4/56

12/10/2016

.NETInternalsandNativeCompiling

Thiskeyhastwosubkeys:"IL"and"NI".The"IL"keycontainsaseriesofsubkeyswhichrepresentthengenedassembliesandtheinformationneeded
tobindthemtotheirnativeimages:

KeepinmindtheDisplayNameasitTheSIGvaluecontainstheassembly'sGUIDanditsSHA1hash:

http://www.ntcore.com/files/netint_native.htm

5/56

12/10/2016

.NETInternalsandNativeCompiling

TheselectedbytesrepresenttheSHA1hash.Ironically,thishashisn'tusedtobindtheactualassemblytoitsnativeimage.Butthisbehaviourmight
changeinthefuture,soit'sworthmentioning.
The"NI"key'ssubkeystelltheframeworkwhereitcanfindthenativeimageforagivenassembly:

TheMVIDvaluespecifiesthepathofthenativeimage.Inthiscaseit'llbe:
"C:\Windows\assembly\NativeImages_v2.0.50727_32\rebtest\0f12d8560d3b72df51b3471002c911a0".Also,itshouldbenotedthatthe"511072a1"
subkeyreferencestheappropriate"IL"subkey.
So,inordertobindanotherassemblytothisassembly'snativeimage,itisnecessarytochangeitsGUIDandalsotheAssemblyMetaDatatable:

http://www.ntcore.com/files/netint_native.htm

6/56

12/10/2016

.NETInternalsandNativeCompiling

TheNameintheAssemblyMetaDatatableshouldbechangedtothedisplayname(inthiscase:"rebtest").Also,changetheMajorVersion,
MinorVersion,BuildNumberandRevisionNumberaccordingly.IshowedtheModuleTableintheimagejustbecauseitwouldbelogicaltochangethatas
well,buttheframeworkdoesn'tcareaboutit.Thus,neitherdowe.
Thisisallittakestobindalocalimageanditworkswiththeframework3.5SP1aswell.Ofcourse,bindinganativeimageonanothercomputerisn't
aseasy,sincenativeimagesareframework/systemdependent.Andalsoitisnotguarantedtowork,since,asmentionedearlier,nativeimagesmay
changealongwithnewerversionsoftheframework.Thisproblemcanbe"solved"byshippingthewholeframeworkalongwiththenativeimages.
Let'sgobacktotheSalamanderLinkerdemonstation'smaindirectory.The"Scribble.exe"isanativeexewhichloadsthe"Scribble.rsm"."Scribble.rsm"
isanemptyassemblyusedtoloadanativeimage.ThebindingbetweenthisemptyassemblyandanativeimageisdonehowIdescribedabove.By
shippingitsownframeworkversiontheSalamanderLinkerhasonlytoworryaboutlocalbinding.Ofcourse,itisnotsufficienttoputtheframework
filesinafolderinordertodeployit.Avirtualizationhastobeprovidedaswell.The"mdepoy.registry"isatextfilewhichcontainstheregistrykeysto
virtualize.Itlookslikethis:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Fusion\NativeImagesIndex\v2.0.50727_32\IL]
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Fusion\NativeImagesIndex\v2.0.50727_32\IL\23ca0da0]
http://www.ntcore.com/files/netint_native.htm

7/56

12/10/2016

.NETInternalsandNativeCompiling

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Fusion\NativeImagesIndex\v2.0.50727_32\IL\23ca0da0\2bbf7a73]

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Fusion\NativeImagesIndex\v2.0.50727_32\IL\23ca0da0\2bbf7a73\8]

"DisplayName"="Scribble,0.0.0.0,,"

"SIG"=hex:af,ab,74,2d,d3,3a,1c,43,be,55,fc,b4,11,39,af,45,b7,ce,d1,a1,22,41,42,\

18,11,62,fb,d2,01,d5,41,f6,24,46,e2,15

"Status"=dword:00000000
"LastModTime"=hex:00,00,00,00,00,00,00,00
Theactualfileismuchbigger(31kb)."rsdeploy.dll"isthepartoftheSalamanderLinkerwhichdoesmostofthework:ithooksalltheAPIsitneedsto
virtualizetheframework.Thiscanbeeasilyverifiedwithoutanalyzingitscode.AmongtheAPIsitneedstohookthere'sLoadLibrary,ofcourse,andall
registryfunctions.Italsoneedstohooksomeotherfunctions,whichI'mgoingtodiscussinthenextparagraph.
Whenvirtualizinganapplicationthere'snotonlythefilesystemandtheregistrytoconsider.Environmentvariableshavetobeconsideredaswell.Ifwe
lookattheenvironmentoftheScribbleprocesswithRussinovich'sProcessExplorerwewillnoticesomething:

TheSalamanderLinkersetstheCOMPLUS_InstallRootvariabletoitsownmaindirectory.Sincethisvariableisnotusedandtheframeworkisloaded
evenwithoutit,myguessisthatit'sadeprecatedvariableoftheframework1.0.
ThisisabouteverythingonehastoknowinordertodevelophisownNativeFrameworkDeploymenttool.Onemightbeaskingwherethemergingpart
comesin.Actually,themergingisnotreallynecessary.Itonlymakesthingseasierandalso,sincethewholeframeworkisshipped,itspeedsup
performances.IcouldeasilyadapttheRebel.NETcodetowriteanassemblymerger(itwouldbeatwoweeksjob),butI'mnotinterestedinanything
thatcanbeachievedthroughmergingassemblies:like,forinstance,writingaprotectionlikethisone.Asalternative,onemightconsiderusing
ILMerge,aMicrosoftutilitywhichcanalsobeusedincommercialapplications.Theonlydrawbackisthatitisextremelyslow(it'sa.NETassembly)and
Ihavealreadyexperiencedcaseswhereitdoesn'twork,butthismayimproveintime.InthenextsubparagraphsI'mgoingtoaddresssomeaspects
ofthepossibledevelopmentofaNativeFrameworkDeploymentservice.

TheNativeLoader
Let'sseehowapossibleloaderforaNativeFrameworkDeploymentservicemaylooklike.Whatfollowsisonlyafirstdraftoftheloader:I'mnot
introducingthecompleteloaderyet,becauseI'mproceedinggradually.
intAPIENTRY_tWinMain(HINSTANCE hInstance,
HINSTANCEhPrevInstance,
http://www.ntcore.com/files/netint_native.htm

8/56

12/10/2016

.NETInternalsandNativeCompiling

LPTSTRlpCmdLine,
intnCmdShow)
{
//
//setCOMPLUS_InstallRootenvironmentvariable
//(uselessonframework2.0andlater)
//
/*
TCHARCurPath[MAX_PATH]
GetModuleFileName(NULL,CurPath,MAX_PATH)
TCHAR*pSlash=_tcsrchr(CurPath,'\\')
if(pSlash)*pSlash=0
SetEnvironmentVariable(_T("COMPLUS_InstallRoot"),CurPath)
*/
//////////////////////////////////////////////////////////////////////////
//TODO:hookregistryAPIs,LoadLibraryand ...
//////////////////////////////////////////////////////////////////////////
HMODULEhMainAsm=LoadLibrary(ASSEMBLY_TO_LOAD)
if(hMainAsm==NULL)return0
IMAGE_DOS_HEADER*pDosHeader=(IMAGE_DOS_HEADER*)hMainAsm
IMAGE_NT_HEADERS*pNtHeaders=(IMAGE_NT_HEADERS*) (pDosHeader>e_lfanew+
(ULONG_PTR)pDosHeader)
if(pNtHeaders>OptionalHeader.ImageBase!=(ULONG_PTR)pDosHeader)
FixReloc(pDosHeader,pNtHeaders)
FixIAT(pDosHeader,pNtHeaders)
//retrieveentrypoint
VOID*pEntryPoint=(VOID*) (pNtHeaders>OptionalHeader.AddressOfEntryPoint+
(ULONG_PTR)pDosHeader)
__asmjmppEntryPoint

http://www.ntcore.com/files/netint_native.htm

9/56

12/10/2016

.NETInternalsandNativeCompiling

return0
}

Thereareafewthingstosayaboutthiscode.Foronce,itmaynotseemobvioustothereaderwhyI'mfixingIATandrelocations.Usually,LoadLibrary
(whichI'musingtoloadtheassembly)doesthistask,butonsystemswhichhavethe.NETframeworkinstalleditdoesn'tdothisfor.NETassemblies.
AfterfixingthePE,Ijumptotheassembly'sentrypoint(whichisjustajumpto_CorExeMaininmscoree).Actually,Icouldhavecalledthe
_CorExeMaindirectlywithoutjumpingtotheoriginalentrypoint.Thus,makingthecodetofixIATandrelocationsnotnecessary.Ijustdiditthiswayin
ordertoavoidanyincompatibilitiesinthefuture.Thekeypointtoloadanassemblyistounderstandhow_CorExeMainisgoingtoretrievethebase
addressofthemainassemblyinthecurrentaddressspace.Thecodeof_CorExeMain,afterdoingsomecheckstoloadthecorrect.NETruntime,calls
thesamefunctioninsidemscorwks.Here'stheidemscorwks.Here'sthecodeinsidemscorwks:
.text:79F05ECAint__stdcall_CorExeMain()
.text:79F05ECApublic__CorExeMain@0
.text:79F05ECA__CorExeMain@0procnear
.text:79F05ECA
.text:79F05ECAvar_2C=byteptr2Ch
.text:79F05ECAvar_28=dwordptr28h
.text:79F05ECAvar_1C=byteptr1Ch
.text:79F05ECAvar_18=dwordptr18h
.text:79F05ECAvar_14=dwordptr14h
.text:79F05ECAvar_4=dwordptr4
.text:79F05ECA
.text:79F05ECAFUNCTIONCHUNKAT.text:79FBF47DSIZE0000005ABYTES
.text:79F05ECAFUNCTIONCHUNKAT.text:79FBF4FCSIZE00000042BYTES
.text:79F05Epush20h
.text:79F05ECCmoveax,offsetloc_7A2EE124
.text:79F05ED1call__EH_prolog3_catch
.text:79F05ED6xoredi,edi
.text:79F05ED8pushedilpModuleName
.text:79F05ED9call?WszGetModuleHandle@@YGPAUHINSTANCE__@@PBG@ZWszGetModuleHandle(ushortconst*)
The_CorExeMainfunctioninmscorwksretrievesthemainassemblythroughacalltoGetModuleHandleA/W(NULL)calledinsideWszGetModuleHandle.
Notonlythat:beforeGetModuleHandle,GetModuleFileNamegetscalledinsidemscoree.ThisAPIacceptsthesameNULLsyntaxasGetModuleHandleto
obtaininformationaboutthemainmoduleinthecurrentaddressspace.So,theeasiestwaytotelltheframeworkwhichthemainassemblyis,isto
hookbothGetModuleHandleA/WandGetModuleFileNameA/W.IdecidedtouseMicrosoft'sDetourtoimplementthehooking,sinceitslicensingisfree
forresearchprojectsanditisguarantedtoworkoneveryWindowsplatform.Here'sthecodeoftheactualloader:
#include"stdafx.h"
#include"fxloader.h"
#include"detours.h"
#defineASSEMBLY_TO_LOAD_T("rebtest.exe")
#defineASSEMBLY_TO_LOAD_A"rebtest.exe"
http://www.ntcore.com/files/netint_native.htm

10/56

12/10/2016

.NETInternalsandNativeCompiling

#defineASSEMBLY_TO_LOAD_WL"rebtest.exe"
#defineIS_FLAG(Value,Flag)((Value&Flag)==Flag)
typedefULONG_PTRTHUNK
VOIDFixIAT(VOID*pBase,IMAGE_NT_HEADERS*pNtHeaders)
VOIDFixReloc(VOID*pBase,IMAGE_NT_HEADERS*pNtHeaders)
HMODULEpMainBaseAddr=NULL
CHARMainAsmNameA[MAX_PATH]
WCHARMainAsmNameW[MAX_PATH]
HMODULE(WINAPI*pGetModuleHandleA)(LPCSTRlpModuleName)=GetModuleHandleA
HMODULE(WINAPI*pGetModuleHandleW)(LPCWSTRlpModuleName)=GetModuleHandleW
DWORD(WINAPI*pGetModuleFileNameA)(HMODULEhModule,LPCHlpFilename,
DWORDnSize)=GetModuleFileNameA
DWORD(WINAPI*pGetModuleFileNameW)(HMODULEhModule,LPWCHlpFilename,
DWORDnSize)=GetModuleFileNameW
HMODULEWINAPIMyGetModuleHandleA(LPCSTRlpModuleName)
HMODULEWINAPIMyGetModuleHandleW(LPCWSTRlpModuleName)
DWORDWINAPIMyGetModuleFileNameA(HMODULEhModule,LPCHlpFilename,DWORDnSize)
DWORDWINAPIMyGetModuleFileNameW(HMODULEhModule,LPWCHlpFilename,DWORDnSize)

intAPIENTRY_tWinMain(HINSTANCEhInstance,
HINSTANCEhPrevInstance,
LPTSTRlpCmdLine,
intnCmdShow)
{
//////////////////////////////////////////////////////////////////////////
//TODO:hookregistryandloadlibrary
//////////////////////////////////////////////////////////////////////////
HMODULEhMainAsm=LoadLibrary(ASSEMBLY_TO_LOAD)
if(hMainAsm==NULL)return0
pMainBaseAddr=hMainAsm
http://www.ntcore.com/files/netint_native.htm

11/56

12/10/2016

.NETInternalsandNativeCompiling

GetModuleFileNameA(NULL,MainAsmNameA,MAX_PATH)
CHAR*cSlash=strrchr(MainAsmNameA,'\\')+1
strcpy(cSlash,ASSEMBLY_TO_LOAD_A)
GetModuleFileNameW(NULL,MainAsmNameW,MAX_PATH)
WCHAR*wSlash=wcsrchr(MainAsmNameW,'\\')+1
wcscpy(wSlash,ASSEMBLY_TO_LOAD_W)
//
//HookGetModuleXXXXAPIs
//
DetourRestoreAfterWith()
DetourTransactionBegin()
DetourUpdateThread(GetCurrentThread())
DetourAttach(&(PVOID&)pGetModuleFileNameA,MyGetModuleFileNameA)
DetourAttach(&(PVOID&)pGetModuleFileNameW,MyGetModuleFileNameW)
DetourAttach(&(PVOID&)pGetModuleHandleA,MyGetModuleHandleA)
DetourAttach(&(PVOID&)pGetModuleHandleW,MyGetModuleHandleW)
LONGerr=DetourTransactionCommit()
if(err!=NO_ERROR)return0
//
IMAGE_DOS_HEADER*pDosHeader=(IMAGE_DOS_HEADER*)hMainAsm
IMAGE_NT_HEADERS*pNtHeaders=(IMAGE_NT_HEADERS*)(pDosHeader>e_lfanew+
(ULONG_PTR)pDosHeader)
if(pNtHeaders>OptionalHeader.ImageBase!=(ULONG_PTR)pDosHeader)
FixReloc(pDosHeader,pNtHeaders)
FixIAT(pDosHeader,pNtHeaders)
//retrieveentrypoint
VOID*pEntryPoint=(VOID*)(pNtHeaders>OptionalHeader.AddressOfEntryPoint+
(ULONG_PTR)pDosHeader)
__asm
{
http://www.ntcore.com/files/netint_native.htm

12/56

12/10/2016

.NETInternalsandNativeCompiling

jmppEntryPoint
}
return0
}
HMODULEWINAPIMyGetModuleHandleW(LPCWSTRlpModuleName)
{
if(lpModuleName==NULL)
returnpMainBaseAddr
returnpGetModuleHandleW(lpModuleName)
}
HMODULEWINAPIMyGetModuleHandleA(LPCSTRlpModuleName)
{
if(lpModuleName==NULL)
returnpMainBaseAddr
returnpGetModuleHandleA(lpModuleName)
}
DWORDWINAPIMyGetModuleFileNameA(HMODULEhModule,LPCHlpFilename,DWORDnSize)
{
if(hModule==NULL)
{
strcpy_s(lpFilename,nSize,MainAsmNameA)
return(DWORD)strlen(lpFilename)
}
returnpGetModuleFileNameA(hModule,lpFilename,nSize)
}
DWORDWINAPIMyGetModuleFileNameW(HMODULEhModule,LPWCHlpFilename,DWORDnSize)
{
if(hModule==NULL)
{
wcscpy_s(lpFilename,nSize,MainAsmNameW)
return(DWORD)wcslen(lpFilename)
}
returnpGetModuleFileNameW(hModule,lpFilename,nSize)
}

http://www.ntcore.com/files/netint_native.htm

13/56

12/10/2016

.NETInternalsandNativeCompiling

//x64compatible
VOIDFixIAT(VOID*pBase,IMAGE_NT_HEADERS*pNtHeaders)
{
if(pNtHeaders>OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress==0)
return
IMAGE_IMPORT_DESCRIPTOR*pImpDescr=(IMAGE_IMPORT_DESCRIPTOR*)
(pNtHeaders>OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress+
(ULONG_PTR)pBase)
DWORDdwOldIATProtect
VOID*pIAT=NULL
if(pNtHeaders>OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress!=0)
{
VOID*pIAT=(VOID*)(pNtHeaders>OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress+
(ULONG_PTR)pBase)
VirtualProtect(pIAT,
pNtHeaders>OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_IAT].Size,
PAGE_EXECUTE_READWRITE,
&dwOldIATProtect)
}
while(pImpDescr>Name!=0)
{
char*DllName=(char*)(pImpDescr>Name+
(ULONG_PTR)pBase)
HMODULEhImpDll=LoadLibraryA(DllName)
if(hImpDll==NULL)continue
THUNK*pThunk
if(pImpDescr>OriginalFirstThunk)
pThunk=(THUNK*)(pImpDescr>OriginalFirstThunk+
(ULONG_PTR)pBase)
else
http://www.ntcore.com/files/netint_native.htm

14/56

12/10/2016

.NETInternalsandNativeCompiling

pThunk=(THUNK*)(pImpDescr>FirstThunk+
(ULONG_PTR)pBase)
THUNK*pIATThunk=(THUNK*)(pImpDescr>FirstThunk+
(ULONG_PTR)pBase)
while(*pThunk)
{
if(IS_FLAG(*pThunk,IMAGE_ORDINAL_FLAG))
{
*pIATThunk=(THUNK)GetProcAddress(hImpDll,
(LPCSTR)(*pThunk^IMAGE_ORDINAL_FLAG))
}
else
{
char*pImpFunc=(char*)(sizeof(WORD)+((ULONG_PTR)*pThunk)+
((ULONG_PTR)pBase))
*pIATThunk=(THUNK)GetProcAddress(hImpDll,pImpFunc)
}
pThunk++
pIATThunk++
}
pImpDescr++
}
if(pIAT)
{
VirtualProtect(pIAT,
pNtHeaders>OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_IAT].Size,
dwOldIATProtect,
&dwOldIATProtect)
}
}
//x86recycledcodefromanolderarticle
VOIDFixReloc(VOID*pBase,IMAGE_NT_HEADERS*pNtHeaders)
{
//
//Setfirstsectiontowriteableinordertofix
//therelocationsinthecode
http://www.ntcore.com/files/netint_native.htm

15/56

12/10/2016

.NETInternalsandNativeCompiling

//
IMAGE_SECTION_HEADER*pCodeSect=(IMAGE_SECTION_HEADER*)
IMAGE_FIRST_SECTION(pNtHeaders)
VOID*pCode=(VOID*)(pCodeSect>VirtualAddress+(ULONG_PTR)pBase)
DWORDdwOldCodeProtect
VirtualProtect(pCode,
pCodeSect>Misc.VirtualSize,
PAGE_READWRITE,
&dwOldCodeProtect)
//
//Relocate
//
DWORDDelta=(DWORD)(((ULONG_PTR)pBase)
pNtHeaders>OptionalHeader.ImageBase)
DWORDRelocRva
if(!(RelocRva=pNtHeaders>OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress))
return
IMAGE_BASE_RELOCATION*ImgBaseReloc=
(IMAGE_BASE_RELOCATION*)(RelocRva+(ULONG_PTR)pBase)
WORD*wData
do
{
if(!ImgBaseReloc>SizeOfBlock)
break
UINTnItems=(ImgBaseReloc>SizeOfBlock
IMAGE_SIZEOF_BASE_RELOCATION)/sizeof(WORD)
wData=(WORD*)(IMAGE_SIZEOF_BASE_RELOCATION+
(ULONG_PTR)ImgBaseReloc)
for(UINTi=0i<nItemsi++)
http://www.ntcore.com/files/netint_native.htm

16/56

12/10/2016

.NETInternalsandNativeCompiling

{
DWORDOffset=(*wData&0xFFF)+ImgBaseReloc>VirtualAddress
DWORDType=*wData>>12
if(Type!=IMAGE_REL_BASED_ABSOLUTE)
{
DWORD*pBlock=(DWORD*)(Offset+(ULONG_PTR)pBase)
*pBlock+=Delta
}
wData++
}
ImgBaseReloc=(PIMAGE_BASE_RELOCATION)wData
}while(*(DWORD*)wData)
//
//Restorememorysettings
//
VirtualProtect(pCode,
pCodeSect>Misc.VirtualSize,
dwOldCodeProtect,
&dwOldCodeProtect)
}

Thecompletesourcecodeandthebinaryfilescanbedownloadedfromhere:
DownloadtheNativeLoader
Thiscodejustloadsa.NETassembly.Inordertoachievethedeploymentofa.NETframework,itisnecessarytohookregistryAPIsandfilesystem
onessuchasLoadLibraryaswell.InthenextparagraphI'mgoingtoaddressregistryvirtualizationwhichbringsusonestepforward.

RegistryVirtualization
Iwouldn'thavewrittenthisparagraphifIhadn'talreadyhadthematerialwhichI'mgoingtopresent.Oneofmyunfinished(duetothelackoftime)
articlesisrelatedtovirtualization.ManymonthsagoIwrotearegistryvirtualizer.
Themainform(VirtualRegManager)ofthistoolprovidesthevisualinterfacetocreateavirtualregistry.Thiscanalsobeachievedthroughcommand
line,aswe'llseelater.Onecandecidewhethertovirtualizeakeyalongwithitssubkeysornot.

http://www.ntcore.com/files/netint_native.htm

17/56

12/10/2016

.NETInternalsandNativeCompiling

ThevirtualregistryisanXMLdatabase.TheformatofthisXMLfilelookslikethis:
<?xmlversion="1.0" encoding="utf8"?>
<VIRTUALREG>
<KEYName="HKEY_LOCAL_MACHINE">
<SUBKEYS>
<KEYName="SOFTWARE">
<SUBKEYS>
<KEYName="Microsoft">
<SUBKEYS>
<KEYName="Fusion">
<VALUES>
<VALUEName="ZapQuotaInKB"Type="REG_DWORD">F4240</VALUE>
<VALUEName="DisableCacheViewer" Type="REG_BINARY">AQAQAA==</VALUE>
<VALUEName="ForceLog"Type="REG_DWORD">1</VALUE>
<VALUEName="LogPath"Type="REG_SZ">YwA6AFwAAAA=</VALUE>
</VALUES>
<SUBKEYS>
<KEYName="GACChangeNotification">
<SUBKEYS>
<KEYName="Default">
<VALUES>
<VALUE Name="Accessibility,1.0.5000.0,,b03f5f7f11d50a3a" Type="REG_BINARY">yEWDMkwyxgE=</VALUE>
<VALUEName="cscompmgd,7.0.5000.0,,b03f5f7f11d50a3a" Type="REG_BINARY">ROfXLkwyxgE=</VALUE>
<VALUE Name="CustomMarshalers,1.0.5000.0,,b03f5f7f11d50a3a" Type="REG_BINARY">yEWDMkwyxgE=</VALUE>
http://www.ntcore.com/files/netint_native.htm

18/56

12/10/2016

.NETInternalsandNativeCompiling

Numbersarestoredinhexformat,whereasallotherdataisbase64encoded.ThevirtualregistryfilecanbeeditedwithVirtualRegEditor(vregedit),
whichisveryuserfriendlyasitsinterfaceisidenticaltoregedit'sone.

CreatingavirtualregistryfromtheGUIisokayformanualtask,buttoolscanusetheprogram'scommandlinetogenerateavirtualregistry.Inorder
todothat,a".tovreg"filehastobepassedascommandlinetotheprogram.Atovregfilehasthissyntax:
[OPTIONS]
output="c:\....\fusion.vreg"
[HKEY_CLASSES_ROOT\CLSID]
[HKEY_LOCAL_MACHINE\Software\Microsoft\Fusion]
subkeys=true
Asonecansee,it'sasimplyinifile.Ifthe"subkeys"parameterismissing,thensubkeysarenotvirtualized.
Asthisispartofanunfinishedarticle,Ihavenotwrittenthemonitortoretrievethekeystovirtualizeyet.However,it'squiteeasytowriteoneor,
beingverylazy,usingtheloggeneratedbyRussinovich'sProcessMonitorisalsoanoption.Thecatchedkeysshouldbevirtualizedwithouttheir
subkeys,asthismightinsomecasesresultinamuchtobigvirtualregistrywithunnecessarykeys.
http://www.ntcore.com/files/netint_native.htm

19/56

12/10/2016

.NETInternalsandNativeCompiling

Feelfreetoincludethistoolinyourfreeware.

IssuesandConclusions
Sincethecodegenerationfornativeimagesisplatformspecific,itmightaswellimplyoptimizationswhichcannotworkonotherCPUs.Anexampleof
thiscouldtheuseofaspecificversionofSSEinstructionswhicharenotavailableoneveryarchitecture.Thisproblemcouldbe"solved"bymakingngen
believethatitisrunningonanolder(ordifferent)CPU,butthisisjustamess.
I'mnotinfavorofpersonalopinionsinsidetechnicalarticles,butitisnecessarytosaysomethingaboutthis,sinceonemightaskmewhyI'mnot
writingaNativeFrameworkDeploymentservicemyself.Withtheinformationprovidedinthisarticleitwouldtakenolongerthanamonthtoprovidea
commercialproduct.ThereasonwhyIdon'tdoitissimplybecauseIbelieveitisunprofessionalandtechnicallyspeakingamess.Itmightaswell
alwayswork,butnooneinhisrightmindwoulddeployevery.NETassemblywithasubsetofthe.NETframework.Deploying40MBsormoreofdata
forasimpleassemblyisnotarealsolution.Infact,it'snotasolutionatall.
Iwastemptedtowriteacompletedemonstrationofsuchaprotection(withoutthemergingpart,ofcourse)forthisarticleanditwouldhavetakenme
nolongerthanafewdays,butithassomedrawbacks.SinceI'mnotinterestedindevelopingacommercialsolutionaroundthisconcept,someoneelse
mightsimplyreusethecode.Evennowthere'snotmuchtodo,butatleastone'sgottoworkonitabitbeforehavingsomethingtomakemoneyout
of.However,Iamallinfavourofreverserswritingademonstrationjustforfunandgivingitawayforfree.Yes,itoughttobefree.Itisnottechnically
complicateandshouldn'tbecommercializedatall.

NativeInjection
InthisparagraphI'mgoingtoshowhowitispossibletodotheworkwhichisbeingdonewhennativeimagesarebeingloadedbytakingtheJIT's
place.Thecodecontainedinnativeimagesneedstobefixed:manyreferenceshavetobesolvedatruntimelike,forinstance,externalcalls.I'mnot
showingamethodtoactuallynativecompile.NETassemblies,sincetakingtheplaceoftheJITisnotonlycomplicated,butalsounlikelytoworkin
futureversionsofthe.NETframework.Infact,whatI'mwritingworksonthe.NETframework2and3,butitseemsthatthenewframework3.5SP1
changedlotsofthingsandIalreadynoticedthatwhatI'mdoingdoesn'tworkonthatversioninstalledonVistax64.ThisisratherunimportantandI'm
notinterestedindiggingtosolvetheproblem,sincewhatI'mdoinghereisonlyahacktogiveabetterunderstandingofhowtheJITworks,whichwill
turnoutusefulinthenextparagraphs.Itwillalsoprovethepointofmyfinalconclusionsabout.NETnativecompiling.
Thetestasssemblyusedinthisparagraphisrebtest.exe:anassemblyIalreadyusedtotestRebel.NET.Theapplicationisverysimple,it'sjustaform
withatextboxandabutton.Whentheuserclicksthebutton,itcheckswhetherthepasswordinsertedinthetextboxisrightornot.Ifnot,itshows
themessagebox:"Wrongpassword!".Here'stheMSILcodeofthebuttonclickevent:
.methodprivatehidebysiginstancevoidbutton1_Click(objectsender,
class[mscorlib]System.EventArgse)cilmanaged
{
//Codesize 43(0x2b)
.maxstack8
IL_0000:ldarg.0
IL_0001:ldarg.0
IL_0002:ldfldclass[System.Windows.Forms]System.Windows.Forms.TextBoxrebtest.Form1::textBox1
IL_0007:callvirtinstancestring[System.Windows.Forms]System.Windows.Forms.Control::get_Text()
IL_000c:callinstanceboolrebtest.Form1::CheckPassword(string)
http://www.ntcore.com/files/netint_native.htm

20/56

12/10/2016

.NETInternalsandNativeCompiling

IL_0011:brfalse.sIL_001f
IL_0013:ldstr"Rightpassword!"
IL_0018:callvaluetypeSystem.Windows.Forms.DialogResultSystem.Windows.Forms.MessageBox::Show(string)
IL_001d:pop
IL_001e:ret
IL_001f:ldstr"Wrongpassword!"
IL_0024:callvaluetypeSystem.Windows.Forms.DialogResultSystem.Windows.Forms.MessageBox::Show(string)
IL_0029:pop
IL_002a:ret
}//endofmethodForm1::button1_Click

Let'slookatthedifferencesofthenativecodeproducedfromthisMSILcodeontwodifferentcomputers:
CodeA

CodeB

00000000pushesi
00000001movesi,ecx
00000003movecx,[esi+0x140]
00000009moveax,[ecx]
0000000Bcall[eax+0x164]
00000011movedx,[0x238b9bc]
00000017movecx,eax
00000019call0x7426edd0
0000001Eandeax,0xff
00000023jz0x2c
00000025moveax,0x1
0000002Ajmp0x2e
0000002Cxoreax,eax
0000002Etesteax,eax
00000030jz0x42
00000032movecx,[0x238b9c0]
00000038call[0x5102544]
0000003Epopesi
0000003Fret0x4
00000042movecx,[0x238b9c4]
00000048call[0x5102544]
0000004Epopesi
0000004Fret0x4

00000000pushesi
00000001movesi,ecx
00000003movecx,[esi+0x140]
00000009moveax,[ecx]
0000000Bcall[eax+0x164]
00000011movedx,[0x385b9bc]
00000017movecx,eax
00000019call0x742ff5b0
0000001Eandeax,0xff
00000023jz0x2c
00000025moveax,0x1
0000002Ajmp0x2e
0000002Cxoreax,eax
0000002Etesteax,eax
00000030jz0x42
00000032movecx,[0x385b9c0]
00000038call[0x5053524]
0000003Epopesi
0000003Fret0x4
00000042movecx,[0x385b9c4]
00000048call[0x5053524]
0000004Epopesi
0000004Fret0x4

Eveninthissmallmethodmanythingsaresolvedatruntime.Inthisparticularcasewehavealdfld,acallvirt,aldstrandacall.Onethingthatshould
benotedisthatthisassemblycodeisusingfastcallsstoringthefirstargumentinecxandthesecondoneinedx.
Inordertounderstandhowtosolvethesereferences,itisnecessarytounderstandhowtheJITworksinternally.Inthefirstarticle,Iintroducedthe
compileMethodfunction,butIonlyfocusedonitsfirsttwoarguments:ICorJitInfoandCORINFO_METHOD_INFO.WhatIhavenotdiscussedyetareits
lasttwo:nativeEntryandnativeSizeOfCode.Twopointersusedtoretrievethenativecode'saddressandsize.Onecould,ofcourse,hookthe
http://www.ntcore.com/files/netint_native.htm

21/56

12/10/2016

.NETInternalsandNativeCompiling

compileMethodtoretrievethenativecodeofamethodafterhavingcalledtheoriginalcompileMethodfunction(whichisn'tveryuseful)oronecould
actuallyusethesetwoargumentstoinjecthisownnativecode.Andthat'sexactlywhatI'mgoingtodo.ButI'mnotinjectinganykindofcode.No,I'm
goingtoinjectnative.NETcodebysolvinginternalreferences.
Let'sstartfromthecompileMethodfunction:
/*****************************************************************************
*ThemainJITfunction
*/
//Note:thisassumesthatthecodeproducedbyfjitisfullyrelocatable,i.e.requires
//nofixupsafteritisgeneratedwhenitismoved. Inparticularitplacesrestrictions
//onthecodesequencesusedforstaticandnonvirtualcallsandforhelpercallsamong
//otherthings,i.e.thatpcrelativeinstructionsarenotusedforreferencestothingsoutsideofthe
//jittedmethod,andthatpcrelativeinstructionsareusedforallreferencestothings
//withinthejittedmethod. Toaccomplishthis,thefjittedcodeisalwaysreachedviaalevel
//ofindirection.
CorJitResult__stdcallFJitCompiler::compileMethod(
ICorJitInfo*compHnd, /*IN*/
CORINFO_METHOD_INFO*info, /*IN*/
unsignedflags, /*IN*/
BYTE**entryAddress, /*OUT*/
ULONG*nativeSizeOfCode /*OUT*/
)
{
#ifdefined(_DEBUG)||defined(LOGGING)
//makeacopyoftheICorJitInfovtablesothatIcanlogmesageslater
//thiswasmadenonstaticduetoaVC7bug
staticvoid*ijitInfoVtable
ijitInfoVtable=*((void**)compHnd)
logCallback=(ICorJitInfo*)&ijitInfoVtable
#endif
if(!FJitCompiler::GetJitHelpers(compHnd))
returnCORJIT_INTERNALERROR
//NOTE:shouldthepropertiesoftheFJITchangesuchthatit
//wouldhavetopayattentiontospecificILsequencepointsor
//localvariablelivenessrangesfordebuggingpurposes,wewould
//querytheRuntimeandDebuggerforsuchinformationhere,

FJit*fjitData=NULL
CorJitResultret=CORJIT_INTERNALERROR
unsignedchar*savedCodeBuffer=NULL
unsignedsavedCodeBufferCommittedSize=0
http://www.ntcore.com/files/netint_native.htm

22/56

12/10/2016

.NETInternalsandNativeCompiling

unsignedintcodeSize=0
unsignedactualCodeSize
#ifdefined(_DEBUG)||defined(LOGGING)
constchar*szDebugMethodName=NULL
constchar*szDebugClassName=NULL
szDebugMethodName=compHnd>getMethodName(info>ftn,&szDebugClassName)
#endif
#ifdef_DEBUG
staticConfigMethodSetfJitBreak
fJitBreak.ensureInit(L"JitBreak")
if(fJitBreak.contains(szDebugMethodName,szDebugClassName,PCCOR_SIGNATURE(info>args.sig)))
_ASSERTE(!"JITBreak")
//Checkifneedtoprintthetrace
staticConfigDWORDfJitTrace
if(fJitTrace.val(L"JitTrace"))
printf("Method%sClass%s\n",szDebugMethodName,szDebugClassName)
#endif

PAL_TRY //forPAL_FINALLY
PAL_TRY //forPAL_EXCEPT
{
fjitData=FJit::GetContext(compHnd,info,flags)
_ASSERTE(fjitData) //ifGetContextfailsforanyreasonitthrowsanexception
_ASSERTE(fjitData>opStack_len==0) //stackmustbebalancedatbeginningofmethod
codeSize=ROUND_TO_PAGE(info>ILCodeSize*CODE_EXPANSION_RATIO)
#ifdefLOGGING
staticConfigMethodSetfJitCodeLog
fJitCodeLog.ensureInit(L"JitCodeLog")
fjitData>codeLog=fJitCodeLog.contains(szDebugMethodName,
szDebugClassName,PCCOR_SIGNATURE(info>args.sig))
if(fjitData>codeLog)
codeSize=ROUND_TO_PAGE(info>ILCodeSize*64)
#endif
BOOLjitRetry=FALSE //thisissettofalseunlesswegetanexceptionbecause
//ofunderestimationofcodebuffersize
do{ //thefollowingloopisexpectedtoexecuteonlyonce,
//exceptwhenweunderestimatethesizeofthecodebuffer,
//inwhichcase,wetryagainwithalargercodeSize
http://www.ntcore.com/files/netint_native.htm

23/56

12/10/2016

.NETInternalsandNativeCompiling

if(codeSize<MIN_CODE_BUFFER_RESERVED_SIZE)
{
if(codeSize>fjitData>codeBufferCommittedSize)
{
if(fjitData>codeBufferCommittedSize>0)
{
unsignedAdditionalMemorySize=codeSizefjitData>codeBufferCommittedSize
if(AdditionalMemorySize>PAGE_SIZE){
unsignedchar*additionalMemory=(unsignedchar*)
VirtualAlloc(fjitData>codeBuffer+fjitData>codeBufferCommittedSize+PAGE_SIZE,
AdditionalMemorySizePAGE_SIZE,
MEM_COMMIT,
PAGE_READWRITE)
if(additionalMemory==NULL)
{
ret=CORJIT_OUTOFMEM
gotoDone
}
_ASSERTE(additionalMemory==fjitData>codeBuffer+
fjitData>codeBufferCommittedSize+PAGE_SIZE)
}
//recommittheguardpage
VirtualAlloc(fjitData>codeBuffer+fjitData>codeBufferCommittedSize,
PAGE_SIZE,
MEM_COMMIT,
PAGE_READWRITE)

fjitData>codeBufferCommittedSize=codeSize
}
else{ /*firsttimecodeBufferbeinginitialized*/
savedCodeBuffer=fjitData>codeBuffer
fjitData>codeBuffer=(unsignedchar*)VirtualAlloc(fjitData>codeBuffer,
codeSize,
MEM_COMMIT,
PAGE_READWRITE)
if(fjitData>codeBuffer==NULL)
{
fjitData>codeBuffer=savedCodeBuffer
ret=CORJIT_OUTOFMEM
gotoDone
}
fjitData>codeBufferCommittedSize=codeSize
}
_ASSERTE(codeSize==fjitData>codeBufferCommittedSize)
http://www.ntcore.com/files/netint_native.htm

24/56

12/10/2016

.NETInternalsandNativeCompiling

unsignedchar*guardPage=(unsignedchar*)VirtualAlloc(fjitData>codeBuffer+codeSize,
PAGE_SIZE,
MEM_COMMIT,
PAGE_READONLY)
if(guardPage==NULL)
{
ret=CORJIT_OUTOFMEM
gotoDone
}
}
}
else
{ //handlelargerthanMIN_CODE_BUFFER_RESERVED_SIZEmethods
savedCodeBuffer=fjitData>codeBuffer
savedCodeBufferCommittedSize=fjitData>codeBufferCommittedSize
fjitData>codeBuffer=(unsignedchar*)VirtualAlloc(NULL,
codeSize,
MEM_RESERVE|MEM_COMMIT,
PAGE_READWRITE)
if(fjitData>codeBuffer==NULL)
{
//Makesurethatthesavedbufferisfreedinthedestructor
fjitData>codeBuffer=savedCodeBuffer
ret=CORJIT_OUTOFMEM
gotoDone
}
fjitData>codeBufferCommittedSize=codeSize
}

unsignedchar*entryPoint
actualCodeSize=codeSize
PAL_TRY
{
FJitResultFJitRet
jitRetry=false
FJitRet=fjitData>jitCompile(&entryPoint,&actualCodeSize)
if(FJitRet==FJIT_VERIFICATIONFAILED)
{
if(!(flags&CORJIT_FLG_IMPORT_ONLY))
//Ifwegetaverificationfailederror,justmapittoOKas
//it'salreadybeendealtwith.
http://www.ntcore.com/files/netint_native.htm

25/56

12/10/2016

.NETInternalsandNativeCompiling

ret=CORJIT_OK
else
//ifwearein"Importonly"mode,weareactuallyverifying
//genericcode. It'simportantthatwedon'treturnCORJIT_OK,
//becausewewanttoskipthecodegenerationphase.
ret=CORJIT_BADCODE
}
elseif(FJitRet==FJIT_JITAGAIN)
{
jitRetry=true
ret=CORJIT_INTERNALERROR
}
else //OtherwisecastittoaCorJitResult
ret=(CorJitResult)FJitRet
if(ret==CORJIT_OK)
ret=fjitData>fixupTable>resolve(fjitData>mapping,fjitData>codeBuffer,jitRetry)
if(jitRetry)
{
fjitData>ReleaseContext()
fjitData=FJit::GetContext(compHnd,info,flags)
fjitData>mapInfo.savedIP=true
}
}

Thefunctionisactuallymuchbigger,butIonlypastedtheinterestingpartforus.AmongthelastlinesofcodeIpastedyoucanseethatcompileMethod
iscallingthefunctionjitCompile.ThisisthemainfunctionoftheJIT.It'saveryhugefunctionsinceitcontainstheswitchtohandleeveryMSILopcode.
I'mgoingtopasta"small"partofthefunctionheretogiveyouanideaofthemagnitude.
/************************************************************************************/
/*jitthemethod.ifsuccessful,returnnumberofbytesjitted,elsereturn0*/
FJitResultFJit::jitCompile(
BYTE**ReturnAddress,
unsigned*ReturncodeSize
)
{
/*****************************************************************************
*ThefollowingmacroreadsavaluefromtheILstream.Itchecksthatthesize
*oftheobjectdoesn'texceedthelengthofthestream.Italsochecksthat
*thedatahasnotbeenpreviouslyreadandmarksitasread,unlessthe"reread"
*variableissettotrue.
*****************************************************************************/
#defineGET(val,type,reread)\
{\
http://www.ntcore.com/files/netint_native.htm

26/56

12/10/2016

.NETInternalsandNativeCompiling

unsignedintsize_operand\
VALIDITY_CHECK(inPtr+sizeof(type)<=inBuffEnd)\
for(size_operand=0size_operand<sizeof(type)&&!rereadsize_operand++)\
VALIDITY_CHECK(!state[inPtrinBuff+size_operand].isJitted)\
switch(sizeof(type)){\
case1:val=(type)*inPtrbreak\
case2:val=(type)GET_UNALIGNED_VAL16(inPtr)break\
case4:val=(type)GET_UNALIGNED_VAL32(inPtr)break\
case8:val=(type)GET_UNALIGNED_VAL64(inPtr)break\
default:val=(type)0_ASSERTE(!"Invalidsize")break\
}\
inPtr+=sizeof(type)\
for(size_operand=1size_operand<=sizeof(type)&&!rereadsize_operand++)\
state[inPtrinBuffsize_operand].isJitted=true\
}
#defineLEAVE_CRIT\
if(methodInfo>args.hasThis()){\
emit_WIN32(emit_LDVAR_I4(offsetOfRegister(0)))\
emit_WIN64(emit_LDVAR_I8(offsetOfRegister(0)))\
emit_EXIT_CRIT()\
}\
else{\
void*syncHandle\
syncHandle=jitInfo>getMethodSync(methodInfo>ftn)\
emit_EXIT_CRIT_STATIC(syncHandle)\
}
#defineENTER_CRIT\
if(methodInfo>args.hasThis()){\
emit_WIN32(emit_LDVAR_I4(offsetOfRegister(0)))\
emit_WIN64(emit_LDVAR_I8(offsetOfRegister(0)))\
emit_ENTER_CRIT()\
}\
else{\
void*syncHandle\
syncHandle=jitInfo>getMethodSync(methodInfo>ftn)\
emit_ENTER_CRIT_STATIC(syncHandle)\
}
#defineCURRENT_INDEX(inPtrinBuff)
TailCallForbidden=!!((methodInfo>args.callConv&CORINFO_CALLCONV_MASK)==CORINFO_CALLCONV_VARARG)
//ifset,notailcallsallowed.InitializedtoFALSE.Whenasecuritytest
//changesittoTRUE,itremainsTRUEforthedurationofthejittingofthefunction
http://www.ntcore.com/files/netint_native.htm

27/56

12/10/2016

.NETInternalsandNativeCompiling

outBuff=codeBuffer
CORINFO_METHOD_HANDLEmethodHandle=methodInfo>ftn
unsignedintlen=methodInfo>ILCodeSize //ILsize
inBuff=methodInfo>ILCode //ILbytes
inBuffEnd=&inBuff[len] //endofIL
entryAddress=ReturnAddress
codeSize=ReturncodeSize
//Informationaboutargumentsandlocals
offsetVarArgToken=sizeof(prolog_frame)
//Localvariablesdeclaredforconvenienceandflags
unsignedoffset
unsignedaddress
signedinti4
intmerge_state
FJitResultJitResult=FJIT_OK
unsignedcharopcode_val
InstStart=0
DelegateStart=0
DelegateMethodRef=0
UnalignedOffset=(unsigned)1
JitAgain:
MadeTailCall=false

inRegTOS=false
controlContinue=true

//ifatailcallhasbeenmadeandsubsequentlyTailCallForbiddenissettoTRUE,
//wewillrejitthecode,disallowingtailcalls.
//flagindicatingifthetopofthestackisinaregister
//doescontrolwefallthrutonextilinstr

inPtr=inBuff //SetthecurrentILoffsettothestartoftheILbuffer
outPtr=outBuff //Setthecurrentoutputbufferpositiontothestartofthebuffer
codeGenState=FJIT_OK //Resettheglobalerrorflag
JitResult=FJIT_OK //Resettheresultflagforsimpleoperationsthatdon'tsetit
UnalignedAccess=false //Resettheunalignedaccessflag
#ifdef_DEBUG
didLocalAlloc=false
#endif
//Cannotjitanativemethod
http://www.ntcore.com/files/netint_native.htm

28/56

12/10/2016

.NETInternalsandNativeCompiling

VALIDITY_CHECK(!(methodAttributes&(CORINFO_FLG_NATIVE)))
//Zerosizedmethodsarenotallowed
VALIDITY_CHECK(methodInfo>ILCodeSize>0)
//Cannotjitmethodswithsharedbodies
VALIDITY_CHECK(!(methodAttributes&CORINFO_FLG_SHAREDINST))
*(entryAddress)=outPtr
#ifdefined(_DEBUG)
staticConfigMethodSetfJitHalt
fJitHalt.ensureInit(L"JitHalt")
if(fJitHalt.contains(szDebugMethodName,szDebugClassName,PCCOR_SIGNATURE(methodInfo>args.sig))){
emit_break()
}
#endif
//Skipverificationifpossible
JitVerify=!(flags&CORJIT_FLG_SKIP_VERIFICATION)
IsVerifiableCode=true //assumethecodeisverifiableunlessprovenotherwise
//loadanyconstraintsforverification,detectingandrejectingcycles
if(JitVerify)
{
BOOLhasCircularClassConstraints=FALSE
BOOLhasCircularMethodConstraints=FALSE
jitInfo>initConstraintsForVerification(methodHandle,&hasCircularClassConstraints,
&hasCircularMethodConstraints)
VERIFICATION_CHECK(!hasCircularClassConstraints)
VERIFICATION_CHECK(!hasCircularMethodConstraints)
}
#ifdefined(_SPARC_)||defined(_PPC_)
//Checkiftheoffsetofthevarargtokenhasbeencomputedcorrectly
offsetVarArgToken+=(methodInfo>args.hasThis()?sizeof(void*):0)+
(methodInfo>args.hasRetBuffArg()&&EnregReturnBuffer?sizeof(void*):0)
#endif
//itmaybeworthoptimizingthefollowingtoonlyinitializelocalssoastocoverallrefs.
unsignedintlocalWords=(localsFrameSize+sizeof(void*)1)/sizeof(void*)
emit_prolog(localWords)
if(flags&CORJIT_FLG_PROF_ENTERLEAVE)
{
BOOLbHookFunction
http://www.ntcore.com/files/netint_native.htm

29/56

12/10/2016

.NETInternalsandNativeCompiling

void*eeHandle
void*profilerHandle
BOOLbIndirected
jitInfo>GetProfilingHandle(methodHandle,
&bHookFunction,
&eeHandle,
&profilerHandle,
&bIndirected)
if(bHookFunction)
{
_ASSERTE(!bIndirected) //FJITdoesnothandleNGENcase
_ASSERTE(!inRegTOS)
ULONGfunc=(ULONG)jitInfo>getHelperFtn(CORINFO_HELP_PROF_FCN_ENTER)
_ASSERTE(func!=NULL)
emit_callhelper_prof4(func,
(CorJitFlag)CORINFO_HELP_PROF_FCN_ENTER,
eeHandle,
profilerHandle,
NULL, //FRAME_INFO(seedefinitionofFunctionEnter2incorprof.idl)
NULL) //ARG_INFO(seedefinitionofFunctionEnter2incorprof.idl)
}
}
//Doweneedtoinserta"JustMyCode"callback?
if(flags&CORJIT_FLG_DEBUG_CODE)
{
CORINFO_JUST_MY_CODE_HANDLE*pDbgHandle
CORINFO_JUST_MY_CODE_HANDLEdbgHandle=jitInfo>getJustMyCodeHandle(methodHandle,&pDbgHandle)
_ASSERTE(!dbgHandle||!pDbgHandle)
if(dbgHandle||pDbgHandle)
emit_justmycode_callback(dbgHandle,pDbgHandle)
}
#ifdefLOGGING
if(codeLog){
emit_log_entry(szDebugClassName,szDebugMethodName)
}
#endif
//Getsequencepoints
unsignednextSequencePoint=0
if(flags&CORJIT_FLG_DEBUG_INFO){
http://www.ntcore.com/files/netint_native.htm

30/56

12/10/2016

.NETInternalsandNativeCompiling

getSequencePoints(jitInfo,methodHandle,&cSequencePoints,&sequencePointOffsets,&offsetsImplicit)
}
else{
cSequencePoints=0
offsetsImplicit=ICorDebugInfo::NO_BOUNDARIES
}
mapInfo.prologSize=outPtroutBuff
//note:enteringofthecriticalsectionisnotpartoftheprolog
mapping>add(CURRENT_INDEX,(unsigned)(outPtroutBuff))
if(methodAttributes&CORINFO_FLG_SYNCH){
ENTER_CRIT
}
//Verifytheexceptionhandlers'table
intver_exceptions=verifyHandlers()
VALIDITY_CHECK(ver_exceptions!=FAILED_VALIDATION)
VERIFICATION_CHECK(ver_exceptions!=FAILED_VERIFICATION)
//Initializethestatemapwiththeexceptionhandlinginformation
initializeExceptionHandling()
boolFirst=true
popSplitStack=false
UncondBranch=false
LeavingTryBlock=false
LeavingCatchBlock=false
FinishedJitting=false

//Startjittingatthenextoffsetonthesplitstack
//Executinganunconditionalbranch
//Executinga"leave"fromatryblock
//Executinga"leave"fromacatchblock
//FinishedjittingtheILstream

makeClauseEmpty(&currentClause)
_ASSERTE(!inRegTOS)
while(!FinishedJitting)
{
//INDEBUG(printf("ILoffset:%xPopStack:%dStackEmpty:%d\n",CURRENT_INDEX,
//popSplitStack,SplitOffsets.isEmpty()))
START_LOOP:
//Ifwejittedthelaststatementoranuncondtionalbranchwithjittedtarget
//weneedtorestartatthenextsplitoffset
if(inPtr>=inBuffEnd||popSplitStack)
{
//RemovetheILoffsetsthat'salreadybeenjitted
http://www.ntcore.com/files/netint_native.htm

31/56

12/10/2016

.NETInternalsandNativeCompiling

while(!SplitOffsets.isEmpty()&&state[SplitOffsets.top()].isJitted)
(void)SplitOffsets.popOffset()
//INDEBUG(SplitOffsets.dumpStack())
//WereachedtheendoftheILopcodestream,butnotallcodehasbeenjitted
//Poptheoffsetfromthesplitoffsetsstack
if(!SplitOffsets.isEmpty())
{
inPtr=(unsignedchar*)&inBuff[SplitOffsets.popOffset()]
//INDEBUG(printf("Startingjittingat%d\n",inPtrinBuff))
//Treatasplitasaforwardjump
controlContinue=false
//Resetflag
popSplitStack=false
}
else
{
//Checkforafallthroughattheendofthefunction
VALIDITY_CHECK(popSplitStack||inBuff[InstStart]==CEE_THROW)
gotoEND_JIT_LOOP
}
}
//Checkifmaxstackvaluehasbeenexceded
VERIFICATION_CHECK(methodInfo>maxStack>=opStack_len)
//INDEBUG(if(JitVerify)printf("ILoffsetis%x\n",CURRENT_INDEX))
//Guardagainstafallthroughinto/fromacatch/finally/filter
VALIDITY_CHECK(!(state[CURRENT_INDEX].isHandler)&&!(state[CURRENT_INDEX].isFilter)&&
!(state[CURRENT_INDEX].isEndBlock)||!controlContinue||UncondBranch)
UncondBranch=false //Thisflagisonlyusedtocheckforfallthrough
if(controlContinue){
if(state[CURRENT_INDEX].isJmpTarget&&inRegTOS!=state[CURRENT_INDEX].isTOSInReg){
if(inRegTOS){
deregisterTOS
}
else{
enregisterTOS
}
}
http://www.ntcore.com/files/netint_native.htm

32/56

12/10/2016

.NETInternalsandNativeCompiling

}
else{ //controlContinue==false
unsignedintlabel=ver_stacks.findLabel(CURRENT_INDEX)
if(label==LABEL_NOT_FOUND){
CHECK_POP_STACK(opStack_len)
inRegTOS=false
}
else{
opStack_len=ver_stacks.setStackFromLabel(label,opStack,opStack_size)
inRegTOS=state[CURRENT_INDEX].isTOSInReg
}
controlContinue=true
}
//CheckifthisILoffsethasalreadybeenjitted.Note,thattoseeif
//anoffsethasbeenjittedweneedtocheckthatitisnotinskippedcode
//intervalsandthatanoffsetequaltooraboveithasbeenjitted
if(state[inPtrinBuff].isJitted)
{
//INDEBUG(printf("Detectedjittedcode:ILoffsetis%x\n",CURRENT_INDEX ))
//Theskippedcodeintervalmustjusthaveended
//Ifverificationisenabledweneedtocomparethecurrentstateofthestackwiththesavedone
merge_state=verifyStacks(CURRENT_INDEX,0)
VERIFICATION_CHECK(merge_state)
if(JitVerify&&merge_state==MERGE_STATE_REJIT)
{resetState(false)gotoJitAgain}
//Emitajumptothejittedcode
ilrel=CURRENT_INDEX
if(state[inPtrinBuff].isTOSInReg)
{enregisterTOS}
else
{deregisterTOS}
address=mapping>pcFromIL(inPtrinBuff)
VALIDITY_CHECK(address>0)
emit_jmp_abs_address(CEE_CondAlways,address+(unsigned)outBuff,true)
//INDEBUG(printf("Emittedajumpto%d\n",outPtr+addressoutBuff))
//RemovetheILoffsetsthat'salreadybeenjitted
while(!SplitOffsets.isEmpty()&&state[SplitOffsets.top()].isJitted)
(void)SplitOffsets.popOffset()
//Poptheoffsetfromthesplitoffsetsstack
if(!SplitOffsets.isEmpty())
http://www.ntcore.com/files/netint_native.htm

33/56

12/10/2016

.NETInternalsandNativeCompiling

{
inPtr=(unsignedchar*)&inBuff[SplitOffsets.popOffset()]
//INDEBUG(printf("Startingjittingat%d\n",inPtrinBuff))
//Treatasplitasaforwardjump
controlContinue=false
//INDEBUG(SplitOffsets.dumpStack())
gotoSTART_LOOP
}
else
gotoEND_JIT_LOOP
}
//Ifthecurrentoffsetisabeginningofatryblock,itisnecessarytopushtheaddressesof
//associatedhandlersontothesplitoffsetsstackinthecorrectorder
if(state[CURRENT_INDEX].isTry)
{
//INDEBUG(printf("PushedHandlersat%x\n",CURRENT_INDEX))
//Thestackhastobeemptyonanentrytoatryblock
VALIDITY_CHECK(isOpStackEmpty())
//Pushthestartingoffsetofthetryblockontothesplitoffsetsstack
SplitOffsets.pushOffset(CURRENT_INDEX)
//Pushthestartingaddressesofallthehandlersontothesplitoffsetsstack
pushHandlerOffsets(CURRENT_INDEX)
//Emitajumptothestartofthetryblock
fixupTable>insert((void**)outPtr)
emit_jmp_abs_address(CEE_CondAlways,CURRENT_INDEX,false)
//INDEBUG(SplitOffsets.dumpStack())
state[CURRENT_INDEX].isTry=0 //Resettheflagoncethehandlershavebeenpushedontothestack
//Startjittingthefirsthandler
popSplitStack=true
controlContinue=false
First=false
continue
}
//ThisILopcodewillbejitted
if(!First)
mapping>add(CURRENT_INDEX,(unsigned)(outPtroutBuff))
First=false
if(state[CURRENT_INDEX].isHandler){
if((offsetsImplicit&ICorDebugInfo::CALL_SITE_BOUNDARIES)!=0)
emit_sequence_point_marker()
unsignedintnestingLevel=Compute_EH_NestingLevel(inPtrinBuff)
http://www.ntcore.com/files/netint_native.htm

34/56

12/10/2016

.NETInternalsandNativeCompiling

emit_storeTOS_in_JitGenerated_local(nestingLevel,state[CURRENT_INDEX].isFilter)
}

state[CURRENT_INDEX].isTOSInReg=inRegTOS
//Checkifwearecurrentlyatasequencepoint
emitSequencePointPre(CURRENT_INDEX,nextSequencePoint)
//Ifverificationisenabledweneedtostorethecurrentstateofthestack
merge_state=verifyStacks(CURRENT_INDEX,1)
VERIFICATION_CHECK(merge_state)
if(JitVerify&&merge_state==MERGE_STATE_REJIT)
{resetState(false)gotoJitAgain}
InstStart=CURRENT_INDEX
if(InstStart==UnalignedOffset)UnalignedAccess=true
#ifdefLOGGING
ilrel=inPtrinBuff
#endif
GET(opcode_val,unsignedchar,false)
OPCODEopcode=OPCODE(opcode_val)
DECODE_OPCODE:
#ifdefLOGGING
if(codeLog&&opcode!=CEE_PREFIXREF&&(opcode<CEE_PREFIX7||opcode>CEE_PREFIX1)){
boololdstate=inRegTOS
emit_log_opcode(ilrel,opcode,oldstate)
inRegTOS=oldstate
}
#endif
switch(opcode)
{
caseCEE_PREFIX1:
GET(opcode_val,unsignedchar,false)
opcode=OPCODE(opcode_val+256)
gotoDECODE_OPCODE
caseCEE_LDARG_0:
caseCEE_LDARG_1:
caseCEE_LDARG_2:
caseCEE_LDARG_3:
http://www.ntcore.com/files/netint_native.htm

35/56

12/10/2016

.NETInternalsandNativeCompiling

offset=(opcodeCEE_LDARG_0)
//Makesurethattheoffsetislegal(withrespecttotheILencoding)
VERIFICATION_CHECK(offset<4)
JitResult=compileDO_LDARG(opcode,offset)
break

OnlyinthelastlinesofcodeweencountertheswitchIwastalkingabout.Theswitchisinsidealoop(naturally)whichgoesonuntilthelastopcode
hasn'tbeenjitted.Asonecannotice,theswitchdoesn'tcomedirectlyafterthebeginningofthejittingloop.That'sbecausebeforeeveryinstructionto
handletheJITperformsmanychecks.Forinstance,itchecksthatthemaximumstacksizehasn'tbeenexceededorthatthecurrentoffsetisn'tthe
benningofatryblock.However,wedon'tcareaboutallthosethings,sincewedon'thavetoperformvaliditychecksnorimplementexceptionhandlers.
Note:theGETmacroshouldbebrieflydiscussedforbetterunderstanding.ThismacroreadsavaluetypefromthecurrentMSILopcodestreampointer
andputsitinavariable(firstargument),thenitincrementsthestreampointer.
WhatI'mgoingtodoistoinjectthe.NETmessageboxdisplaying"Rightpassword!".Thus,we'llhavetoanalyzehowtheJIThandlestheopcodesldstr
andcall.Thisisagoodwaytoproceed,astheldstropcodeisveryeasyandgivesthereaderthetimetoadapttotheJITlogic.So,let'slookatthe
ldstrcaseintheswitch:
caseCEE_LDSTR:
JitResult=compileCEE_LDSTR()
break

Thisistheusualsyntaxusedtohandleopcodes:acalltocompileCEE_OpcodeName.Let'slookatthisfunction:
FJitResultFJit::compileCEE_LDSTR()
{
unsignedinttoken
InfoAccessTypeiat
CORINFO_MODULE_HANDLEtokenScope=methodInfo>scope
GET(token,unsignedint,false)VERIFICATION_CHECK(jitInfo>isValidToken(tokenScope,token))
void*literalHnd=NULL
iat=jitInfo>constructStringLiteral(tokenScope,token,&literalHnd)
//thecodeonlyeversupportedtheequivalentofIAT_PVALUE,thisisnowasserted
VALIDITY_CHECK(iat==IAT_PVALUE)
//Checkifthestringwasconstructedsuccessfully
VALIDITY_CHECK(literalHnd!=0)
emit_WIN32(emit_LDC_I4(literalHnd))emit_WIN64(emit_LDC_I8(literalHnd))
emit_LDIND_PTR(false)
//Getthetypehandleforstrings
CORINFO_CLASS_HANDLEs_StringClass=jitInfo>getBuiltinClass(CLASSID_STRING)
http://www.ntcore.com/files/netint_native.htm

36/56

12/10/2016

.NETInternalsandNativeCompiling

VALIDITY_CHECK(s_StringClass!=NULL)
pushOp(OpType(typeRef,s_StringClass))
returnFJIT_OK
}

Whenlookingatthisfunctionitisnecessarytodefinewhatweneedinordertogetastringreference.We'realreadyfamiliarwiththeGETmacroand
itsuse.Wealreadyhaveastringtokenandalsoascope.Wedon'tneedtodoanysortofverification.So,itallcomesdowntothefunction
constructStringLiteralwhichisdeclaredindynamicmethod.cpp:
InfoAccessTypeCEEDynamicCodeInfo::constructStringLiteral(
CORINFO_MODULE_HANDLEmoduleHnd,
mdTokenmetaTok,
void**ppInfo)
{
CONTRACTL
{
THROWS
GC_TRIGGERS
MODE_COOPERATIVE
PRECONDITION(IsDynamicScope(moduleHnd))
}
CONTRACTL_END
_ASSERTE(ppInfo!=NULL)
*ppInfo=NULL
DynamicResolver*pResolver=GetDynamicResolver(moduleHnd)
OBJECTHANDLEstring=NULL
STRINGREFstrRef=ObjectToSTRINGREF(pResolver>GetStringLiteral(metaTok))
GCPROTECT_BEGIN(strRef)
if(strRef!=NULL)
{
MethodDesc*pMD=pResolver>GetDynamicMethod()
string=(OBJECTHANDLE)pMD>GetModule()>GetAssembly()>Parent()>GetOrInternString(&strRef)
}
GCPROTECT_END()
*ppInfo=(LPVOID)string
returnIAT_PVALUE
}
http://www.ntcore.com/files/netint_native.htm

37/56

12/10/2016

.NETInternalsandNativeCompiling

Ipastedthefunctiononlytoshowhowthereferencetothestringisretrievedinternally.Itwasn'tnecessaryforthedemonstration,butIthoughtit's
interestingsinceitinvolvesGetDynamicResolverandthemodulehandle.IhavealreadyintroducedCORINFOhandlesinthepastarticle,showinghow
theyarenothingelsethanclasspointers.Infact,GetDynamicResolverisbasicallyjustacast:
inlineDynamicResolver*GetDynamicResolver(CORINFO_MODULE_HANDLEmodule)
{
WRAPPER_CONTRACT
CONSISTENCY_CHECK(IsDynamicScope(module))
return(DynamicResolver*)(((size_t)module)&~((size_t)CORINFO_MODULE_HANDLE_TYPE_MASK))
}

ToconcludetheanalysisofcompileCEE_LDSTR,the"emit_"macrosareusedtogeneratetheplatformspecificnativecode,whereasthepushOpfunction
ispartofaseriesoffunctionstohandletheMSILstacknecessaryforjittingtonativecode.I'lldiscusslatertheMSILstack.
Thisisthecallopcodehandler:
caseCEE_CALL:
JitResult=compileCEE_CALL()
break

compileCEE_CALLcallsanotherfunctioninternally.SoI'mgoingtopasteboth:
FJitResultFJit::compileCEE_CALL()
{
unsignedinttoken
CORINFO_METHOD_HANDLEtargetMethod
CORINFO_MODULE_HANDLEtokenScope=methodInfo>scope
GET(token,unsignedint,false)
VERIFICATION_CHECK(jitInfo>isValidToken(tokenScope,token))
CORINFO_CALL_INFOcallInfo
//CallthisbecausetheCLR"misuses"thismethodtoactivate
//thetargetassembly(ifneeded).Soifwewouldnotcallit
//laterinthegamethecompiledcodecouldtrytocallinto
//theassemblywhichwasnotactivatedyet.
//Ontheotherhandwedon'tactuallyneedanyinformation
//providedbythiscall.
jitInfo>getCallInfo(methodInfo>ftn,
tokenScope,
token,
0, //constraintToken
methodInfo>ftn,
http://www.ntcore.com/files/netint_native.htm

38/56

12/10/2016

.NETInternalsandNativeCompiling

CORINFO_CALLINFO_KINDONLY,
&callInfo)
targetMethod=jitInfo>findMethod(tokenScope,token,methodInfo>ftn)
VALIDITY_CHECK(targetMethod)
returnthis>compileHelperCEE_CALL(token,targetMethod,false
}

/*readonly*/ )

FJitResultFJit::compileHelperCEE_CALL(unsignedinttoken,
CORINFO_METHOD_HANDLEtargetMethod,
boolisReadOnly /*=false*/ )
{
unsignedintargBytes,stackPadorRetBase=0
unsignedintparentToken
CORINFO_CLASS_HANDLEtargetClass,parentClass=NULL
CORINFO_SIG_INFOtargetSigInfo
CORINFO_METHOD_HANDLEtokenContext=methodInfo>ftn
CORINFO_MODULE_HANDLEtokenScope=methodInfo>scope
//Getattributesforthemethodbeingcalled
DWORDmethodAttribs
methodAttribs=jitInfo>getMethodAttribs(targetMethod,methodInfo>ftn)
//Gettheclassofthemethodbeingcalled
targetClass=jitInfo>getMethodClass(targetMethod)
//gettheexactparentofthemethod
parentToken=jitInfo>getMemberParent(tokenScope,token)
parentClass=jitInfo>findClass(tokenScope,
parentToken,
methodInfo>ftn)
//Gettheattributesoftheclassofthemethodbeingcalled
DWORDclassAttribs
classAttribs=jitInfo>getClassAttribs(targetClass,methodInfo>ftn)
//Verifythatthemethodhasanimplementationi.e.itisnotabstract
VERIFICATION_CHECK(!(methodAttribs&CORINFO_FLG_ABSTRACT))
if(methodAttribs&CORINFO_FLG_SECURITYCHECK)
{
TailCallForbidden=TRUE
if(MadeTailCall)
{ //wehavealreadymadeatailcall,socleanupandjitthismethodagain
if(cSequencePoints>0)
cleanupSequencePoints(jitInfo,sequencePointOffsets)
http://www.ntcore.com/files/netint_native.htm

39/56

12/10/2016

.NETInternalsandNativeCompiling

resetContextState()
returnFJIT_JITAGAIN
}
}
jitInfo>getMethodSig(targetMethod,&targetSigInfo)
if(targetSigInfo.isVarArg())
jitInfo>findCallSiteSig(tokenScope,token,tokenContext,&targetSigInfo)
//Verifythattheargumentsonthestackmatchthemethodsignature
intresult_arg_ver=(JitVerify?verifyArguments(targetSigInfo,0,false):
SUCCESS_VERIFICATION)
VALIDITY_CHECK(result_arg_ver!=FAILED_VALIDATION)
VERIFICATION_CHECK(result_arg_ver!=FAILED_VERIFICATION)
//Verifythethisargumentfornonstaticmethods(itisnotpartofthemethodsignature)
CORINFO_CLASS_HANDLEinstanceClassHnd=jitInfo>getMethodClass(methodInfo>ftn)
if(!(methodAttribs&CORINFO_FLG_STATIC))
{
//Forarrayswedon'thavethecorrectclasshandle
if(classAttribs&CORINFO_FLG_ARRAY)
targetClass=jitInfo>findMethodClass(tokenScope,token,tokenContext)
intresult_this_ver=(JitVerify
?verifyThisPtr(instanceClassHnd,targetClass,
targetSigInfo.numArgs,false)
:SUCCESS_VERIFICATION)
VERIFICATION_CHECK(result_this_ver!=FAILED_VERIFICATION)
}
//Verifytheconstraintsonthetargetmethod(includingitsparent)
VERIFICATION_CHECK(jitInfo>satisfiesClassConstraints(parentClass))
VERIFICATION_CHECK(jitInfo>satisfiesMethodConstraints(parentClass,targetMethod))
//Verifythatthemethodisaccessiblefromthecallsite
VERIFICATION_CHECK(jitInfo>canAccessMethod(methodInfo>ftn,parentClass,
targetMethod,instanceClassHnd))
if(targetSigInfo.hasTypeArg())
{
CORINFO_CLASS_HANDLEtokenType
//Instantiatedgenericmethod
if(isReadOnly)
{
//whenthecallisreadonlytheArrayStubexpectsthetypeargto
//bezero
http://www.ntcore.com/files/netint_native.htm

40/56

12/10/2016

.NETInternalsandNativeCompiling

emit_LDC_I(0)
}
else
{
TokenToHandle(parentToken,tokenType)
}
}
argBytes=buildCall(&targetSigInfo,CALL_NONE,stackPadorRetBase,false)
CORINFO_CONST_LOOKUPaddrInfo
jitInfo>getFunctionEntryPoint(targetMethod,IAT_VALUE,&addrInfo)
VALIDITY_CHECK(addrInfo.addr)
VALIDITY_CHECK(addrInfo.accessType==IAT_VALUE||addrInfo.accessType==IAT_PVALUE)
emit_callnonvirt((unsigned)addrInfo.addr,
(targetSigInfo.hasRetBuffArg()?typeSizeInBytes(jitInfo,
targetSigInfo.retTypeClass):0),
addrInfo.accessType==IAT_PVALUE)
returncompileDO_PUSH_CALL_RESULT(argBytes,stackPadorRetBase,token,targetSigInfo,targetClass)
}

AsIsaidearlier,ldstrwasaveryeasyopcodetohandle.Thecallinstructionisabitmorecomplex,butdon'tgetimpressed,it'ssimpletounderstand.
Thesizeofthecodeismainlytheresultofthemanyvaliditychecks.compileCEE_CALLcallsfirstgetCallInfowhichis,asitseems,misusedtoactivate
theassemblyinwhichthecodeiscontained.ThenfindMethodiscalledtoretrievethehandleofthemethodwhichisbeingcalled.Afterthat,the
compileHelperCEE_CALLfunctioniscalled.Thisfunctionperformslotsofchecks:wecanskipthoseandfocusonthelatterpart.Amongthelastcallsa
getFunctionEntryPointfunctioncanbespottedandthat'sexactlywhatwewerelookingfor.ThebuildCall,emit_callnonvirtand
compileDO_PUSH_CALL_RESULTdoonlybuildthenativecodecallingsyntaxandemitthenativeopcodes.
TheonlydescriptionofgetFunctionEntryPointcanbefoundincorinfo.h:
//returnacallableaddressofthefunction(nativecode).Thisfunction
//mayreturnadifferentvalue(dependingonwhetherthemethodhas
//beenJITedornot. pAccessTypeisaninoutparameter. TheJIT
//specifieswhatlevelofindirectionitdesires,andtheEEsetsit
//towhatitcanprovide(whichmaynotbethesame).
virtualvoid__stdcallgetFunctionEntryPoint(
CORINFO_METHOD_HANDLEftn, /*IN*/
InfoAccessTyperequestedAccessType, /*IN*/
CORINFO_CONST_LOOKUP*pResult, /*OUT*/
CORINFO_ACCESS_FLAGSaccessFlags=CORINFO_ACCESS_ANY)=0

http://www.ntcore.com/files/netint_native.htm

41/56

12/10/2016

.NETInternalsandNativeCompiling

Basically,thisfunctionretrievesthecallablenativecodeofthetargetfunction.BeforecallinggetFunctionEntryPointitisnecessarytoretrievethetarget
method'shandle.ThiscanbeachievedwithfindMethod.
It'snowpossibletowritealittledemonstration.Asinthepastarticle,I'musinga.NETloadertohooktheJITbeforeloadingthevictimassembly.The
nvcoree.dllhookscompileMethodandinjectsthenativecodewhichshowsa.NETmessageboxwiththetext"Rightpassword!".Here'sthecodeof
nvcoree.dll:
#include"stdafx.h"
#include<CorHdr.h>
#include"corinfo.h"
#include"corjit.h"
#include<tchar.h>
extern"C"__declspec(dllexport)voidHookJIT()
BOOLAPIENTRYDllMain(HMODULEhModule,
DWORDdwReason,
LPVOIDlpReserved
)
{
HookJIT()
returnTRUE
}
BOOLbHooked=FALSE
ULONG_PTR*(__stdcall*p_getJit)()
typedefint(__stdcall*compileMethod_def)(ULONG_PTRclassthis,ICorJitInfo*comp,
CORINFO_METHOD_INFO*info,unsignedflags,
BYTE**nativeEntry,ULONG*nativeSizeOfCode)
structJIT
{
compileMethod_defcompileMethod
}
compileMethod_defcompileMethod
//
//nativecodetoinject
//
#defineCODE_SIZE15
BYTECode[CODE_SIZE]=
http://www.ntcore.com/files/netint_native.htm

42/56

12/10/2016

.NETInternalsandNativeCompiling

{
0x8B,0x0D,0x00,0x00,0x00,0x00, //movecx,[addr]
0xFF,0x15,0x00,0x00,0x00,0x00, //call[msgbox]
0xC2,0x04,0x00 //ret4
}
int__stdcallmy_compileMethod(ULONG_PTRclassthis,ICorJitInfo*comp,CORINFO_METHOD_INFO*info,
unsignedflags,BYTE**nativeEntry,ULONG*nativeSizeOfCode)
{
//
//Verylazywaytoidentifythemethodtoinject
//
constchar*szMethodName=NULL
constchar*szClassName=NULL
szMethodName=comp>getMethodName(info>ftn,&szClassName)
if(strcmp(szMethodName,"button1_Click")==0)
{
//
//Retrievestring
//
unsignedintstrToken=0x70000063 //"Rightpassword!"
void*literalHnd=NULL
comp>constructStringLiteral(info>scope,strToken,&literalHnd)
//
//Retrievemethod
//
/*
*misusedtoactivatethemethod'sassembly
*(wedon'tcareaboutthat)
*
CORINFO_CALL_INFOcallInfo
comp>getCallInfo(info>ftn,
info>scope,
0x0A00001E,
0,//constraintToken
info>ftn,
http://www.ntcore.com/files/netint_native.htm

43/56

12/10/2016

.NETInternalsandNativeCompiling

CORINFO_CALLINFO_KINDONLY,
&callInfo)
*/
CORINFO_METHOD_HANDLEtargetMethod=comp>findMethod(info>scope,
0x0A00001E,info>ftn)
CORINFO_CONST_LOOKUPaddrInfo
comp>getFunctionEntryPoint(targetMethod,IAT_VALUE,&addrInfo)
//
//Setupnativecode
//
/**Thisisbasicallywhatwe'redoing *__asm{movecx,[literalHnd] call[addrInfo.addr] }*/
BYTE*pCode=Code
pCode+=2
*((ULONG_PTR*)pCode)=(ULONG_PTR)literalHnd
pCode+=6
*((ULONG_PTR*)pCode)=(ULONG_PTR)addrInfo.addr
DWORDdwOldProtect
VirtualProtect(Code,CODE_SIZE,PAGE_EXECUTE_READWRITE,&dwOldProtect)
*nativeEntry=Code
*nativeSizeOfCode=CODE_SIZE
returnCORJIT_OK //it's0asusual
}
intnRet=compileMethod(classthis,comp,info,flags,nativeEntry,nativeSizeOfCode)
returnnRet
}
//
//HookscompileMethod
//
extern"C"__declspec(dllexport)
voidHookJIT()
http://www.ntcore.com/files/netint_native.htm

44/56

12/10/2016

.NETInternalsandNativeCompiling

{
if(bHooked)return
LoadLibrary(_T("mscoree.dll"))
HMODULEhJitMod=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)
{
DWORDOldProtect
VirtualProtect(pJit,sizeof(ULONG_PTR),PAGE_READWRITE,&OldProtect)
compileMethod=pJit>compileMethod
pJit>compileMethod=&my_compileMethod
VirtualProtect(pJit,sizeof(ULONG_PTR),OldProtect,&OldProtect)
bHooked=TRUE
}
}
}

Everytimetheuserclicksonthebutton,theinjectedcodewillalwaysbecalledinsteadoftheactualpasswordcheck.
DownloadtheNativeInjectionDemo
ThetwoinstructionIhandledwererathersimple.Otheropcodeslikeldfldandcallvirtareabitmorecomplicated,sincetheyalsomakeuseoftheMSIL
stack,whichImentionedearlier.ldfldpopsoutavaluefromthestackwhichistheobjectwhosefielditisgoingtoreference.Here'sabitofthecode
whichjitsldfld:
FJitResultFJit::compileCEE_LDFLD(OPCODEopcode)
{
unsignedaddress=0
unsignedinttoken,parentToken
DWORDfieldAttributes
CorInfoTypejitType
CORINFO_CLASS_HANDLEtargetClass=NULL,parentClass=NULL
http://www.ntcore.com/files/netint_native.htm

45/56

12/10/2016

.NETInternalsandNativeCompiling

boolfieldIsStatic
CORINFO_MODULE_HANDLEtokenScope=methodInfo>scope
CORINFO_METHOD_HANDLEtokenContext=methodInfo>ftn
CORINFO_FIELD_HANDLEtargetField
//GetMemberReftokenforobjectfield
GET(token,unsignedint,false)
VERIFICATION_CHECK(jitInfo>isValidToken(tokenScope,token))
targetField=jitInfo>findField(tokenScope,token,tokenContext)
VALIDITY_CHECK(targetField)
fieldAttributes=jitInfo>getFieldAttribs(targetField,methodInfo>ftn)
fieldIsStatic=(fieldAttributes&CORINFO_FLG_STATIC)?true:false
targetClass=jitInfo>findClass(tokenScope,jitInfo>getMemberParent(tokenScope,token),tokenContext)
VALIDITY_CHECK(targetClass)
//targetClassistheenclosingclass
CORINFO_CLASS_HANDLEvalClass
jitType=jitInfo>getFieldType(targetField,&valClass,targetClass)
if(fieldIsStatic)
{
emit_initclass(targetClass)
}
OpTypefieldType=createOpType(jitType,valClass)
OpTypetype
#if!defined(FJIT_NO_VALIDATION)

//Initializethetypecorrectlygettingadditionalinformationformanagedpointersandobjects
if(fieldType.enum_()==typeByRef)
{
_ASSERTE(valClass!=NULL)
CORINFO_CLASS_HANDLEchildClassHandle
CorInfoTypechildType=jitInfo>getChildType(valClass,&childClassHandle)
fieldType.setTarget(OpType(childType).enum_(),childClassHandle)
}
elseif(fieldType.enum_()==typeRef)
VALIDITY_CHECK(valClass!=NULL)
//Verifythatthecorrecttypeoftheinstructionisused
VALIDITY_CHECK(fieldIsStatic||(opcode==CEE_LDFLD))
http://www.ntcore.com/files/netint_native.htm

46/56

12/10/2016

.NETInternalsandNativeCompiling

CORINFO_CLASS_HANDLEinstanceClassHnd=jitInfo>getMethodClass(methodInfo>ftn)
//INDEBUG(printf("FieldType[%d,%d]%d\n",fieldType.enum_(),fieldType.cls(),valClass))
#endif
if(opcode==CEE_LDFLD)
{
//Theremustbeanobjectonthestack
CHECK_STACK(1)
type=topOp()
if(type.type_enum==typeR4||type.type_enum==typeR8){
returnFJIT_OK
}
//Theobjectonthestackcanbemanagedpointer,object,nativeint,instanceofobject
VALIDITY_CHECK(type.isPtr()||type.enum_()==typeValClass)
//Verificationdoesn'tallownativeinttobeused
VERIFICATION_CHECK(type.enum_()!=typeI||(type.cls()&&isPrimitiveValueType(type.cls())))
//Storetheobjectreferencefortheaccesscheck
instanceClassHnd=type.cls()
OpTypetargetType=createOpType(type.enum_(),targetClass)
//Checkthattheobjectonthestackenclosesthefield
VERIFICATION_CHECK(canAssign(jitInfo,methodInfo>ftn,type,targetType))
//RemovetheinstanceobjectoftheILstack
POP_STACK(1)
if(fieldIsStatic){
//wedon'tneedthispointer
if(type.isValClass())
{
unsignedsizeValClass=typeSizeInSlots(jitInfo,type.cls())*sizeof(void*)
emit_drop(BYTE_ALIGNED(sizeValClass))
}
else
{
emit_POP_PTR()
}
}
else
{
//INDEBUG(printf("ObjectType[%d,%d]\n",type.enum_(),type.cls()))
if(type.isValClass()||(type.enum_()==typeI&&type.cls()&&isPrimitiveValueType(type.cls())))
{ //theobjectitselfisavalueclass
pushOp(type) //wearegoingtoleaveitonthestack
http://www.ntcore.com/files/netint_native.htm

47/56

12/10/2016

.NETInternalsandNativeCompiling

emit_getSP(STACK_BUFFER) //pushpointertoobject
}
}

Asonecansee,thefunctionisusingmanyOpmethodswhichhandletheMSILstack(internallycalledoperandstack).Herearesomeoftheseinline
methods:
inlineOpType&FJit::topOp(unsignedback){
_ASSERTE(opStack_len>back)
if(opStack_len<=back)
RaiseException(SEH_JIT_REFUSED,EXCEPTION_NONCONTINUABLE,0,NULL)
return(opStack[opStack_lenback1])
}
inlinevoidFJit::popOp(unsignedcnt){
_ASSERTE(opStack_len>=cnt)
opStack_len=cnt
#ifdef_DEBUG
opStack[opStack_len]=OpType(typeError)
#endif
}
inlinevoidFJit::pushOp(OpTypetype){
_ASSERTE(opStack_len<opStack_size)
_ASSERTE(type.isValClass()||(type.enum_()>=typeI4||type.enum_()<typeU1))
_ASSERTE(type.enum_()!=0)
opStack[opStack_len++]=type
#ifdef_DEBUG
opStack[opStack_len]=OpType(typeError)
#endif
}
inlinevoidFJit::resetOpStack(){
opStack_len=0
#ifdef_DEBUG
opStack[opStack_len]=OpType(typeError)
#endif
}
inlineboolFJit::isOpStackEmpty(){
return(opStack_len==0)
}
http://www.ntcore.com/files/netint_native.htm

48/56

12/10/2016

.NETInternalsandNativeCompiling

TheopStackisnothingelsethanapointerthatpointstoanarrayofOpTypeclasses.WhatfollowsisthedeclarationoftheOpTypeclassalongwiththe
typesitcanrepresent:
enumOpTypeEnum{
typeError=0,
typeByRef=1,
typeRef=2,
typeU1=3,
typeU2=4,
typeI1=5,
typeI2=6,
typeI4=7,
typeI8=8,
typeR4=9,
typeR8=10,
typeRefAny=11,
typeValClass=12,
typeMethod=13,
typeCount=14,
typeI=typeI4,
}

structOpType{
OpType()
OpType(OpTypeEnumopEnum)
explicitOpType(CORINFO_CLASS_HANDLEvalClassHandle)
explicitOpType(CORINFO_METHOD_HANDLEmHandle)
explicitOpType(OpTypeEnumopEnum,
CORINFO_CLASS_HANDLEvalClassHandle,
boolsetClassHandle=false,
boolisReadOnly=false)
explicitOpType(OpTypeEnumopEnum,OpTypeEnumchildEnum)
explicitOpType(CorInfoTypejitType,CORINFO_CLASS_HANDLEvalClassHandle,
boolsetClassHandle=false)
explicitOpType(CorInfoTypejitType)
staticconstchartoOpStackType[]
/*OPERATORS*/
intoperator==(constOpType&opType){
return(type_handle==opType.type_handle&&
type_enum==opType.type_enum&&
readonly==opType.readonly)}
http://www.ntcore.com/files/netint_native.htm

49/56

12/10/2016

.NETInternalsandNativeCompiling

intoperator!=(constOpType&opType){return(!(*this==opType))}
/*ACCESSORS*/
boolisPtr(){return(type_enum==typeRef||type_enum==typeByRef||
type_enum==typeI)}
boolisPrimitive()
{return((unsigned)type_enum<=(unsigned)typeRefAny)} //refanyisaprimitive
boolisValClass()
{return((unsigned)type_enum>=(unsigned)typeRefAny)} //refanyisavalclasstoo
boolisTargetPrimitive(){return((unsigned)child_type<=(unsigned)typeRefAny)}
inlineboolisNull(){return(child_type==typeRef&&type_enum==typeRef)}
inlineboolisRef(){return(type_enum==typeRef)}
inlineboolisRefAny(){return(type_enum==typeRefAny)}
inlineboolisByRef(){return(type_enum==typeByRef)}
inlineboolisReadOnly(){return(readonly==1)}
inlineboolisMethod(){return(type_enum==typeMethod)}
inlineOpTypeEnumenum_(){return(type_enum)}
inlineCORINFO_CLASS_HANDLEcls(){return(type_handle)}
inlineCORINFO_METHOD_HANDLEgetMethod(){return(method_handle)}
inlineOpTypeEnumtargetAsEnum(){returnchild_type}
OpTypegetTarget()
{return(isTargetPrimitive()?OpType(child_type):OpType(type_handle))}
boolmatchTarget(OpTypeother)
{_ASSERTE(type_enum==typeByRef)returnisTargetPrimitive()?
other.enum_()==targetAsEnum():other.cls()==cls()}
/*MUTATORS*/
//unsafe,pleaselimituse
voidfromInt(unsignedi){type_handle=(CORINFO_CLASS_HANDLE)(size_t)i}
voidsetHandle(CORINFO_CLASS_HANDLEh){type_handle=h}
voidsetTarget(OpTypeEnumopEnum,CORINFO_CLASS_HANDLEh)
{if(h==NULL)child_type=opEnumelsetype_handle=h
_ASSERTE((child_type!=typeByRef&&child_type!=typeRef)||isNull())}
voidsetTarget(CorInfoTypejitType,CORINFO_CLASS_HANDLEh)
{if(h==NULL)child_type=OpType(jitType).enum_()elsetype_handle=h
_ASSERTE((child_type!=typeByRef&&child_type!=typeRef)||isNull())}
voidsetReadOnly(boolisReadOnly){readonly=(unsigned)isReadOnly}
voidinit(OpTypeEnumopEnum,CORINFO_CLASS_HANDLEvalClassHandle,
boolisReadOnly=false)
{type_enum=opEnumtype_handle=valClassHandlereadonly=
(unsigned)isReadOnly}
voidinit(CorInfoTypejitType,CORINFO_CLASS_HANDLEvalClassHandle)
{type_enum=OpType(jitType).enum_()type_handle=valClassHandle}

staticconstOpTypeEnumSigned[]
voidtoSigned(){
http://www.ntcore.com/files/netint_native.htm

50/56

12/10/2016

.NETInternalsandNativeCompiling

if(type_enum<typeI1)
type_enum=Signed[type_enum]
}
staticconstOpTypeEnumNormalize[]
voidtoNormalizedType(){
if(type_enum<typeI4)
type_enum=Normalize[type_enum]
}
staticconstOpTypeEnumFPNormalize[]
voidtoFPNormalizedType(){
if(type_enum<typeR8)
type_enum=FPNormalize[type_enum]
}

//Datastructure
unsignedreadonly:1
OpTypeEnumtype_enum:31
union{
//ValidonlyforSTRUCTorREForBYREF
CORINFO_CLASS_HANDLEtype_handle
//ValidonlyfortypeMETHOD
CORINFO_METHOD_HANDLEmethod_handle
//ValidforBYREFtoprimitivesonly
OpTypeEnumchild_type
}
}

Theactualdatacontainedinthisclassfitsintoaqword.Themainvalueofthisclassisthetypemember.Insomecases(dependingonthetype),
additionalinformation,suchasahandle,isneeded.Forinstance,ifthetypeistypeMethod,aCORINFO_METHOD_HANDLEisalsoneeded.Thereason
whyIpastedthiscodeisthatunderstandingtheMSILstackmightturnusefulforthenexttwoparagraphs.

NativeDecompiling
Thistopichasneverbeendiscussedyetregardingthe.NETcontext.WhatImeanbynativedecompilingisnotgoingfrommachinecodetoC#(toname
one),butgoingfrommachinecodetoMSIL.TheMSILcanthenbedecompiledintoC#.ConvertingmachinecodetoMSILisnotonlyeasier,buttheonly
logicaldecompilingmethod.Thisprocedureisdifficult:I'monlydiscussingthepossibility.Themostimportantthingisstackinterpretation.Let'stake
forinstancepartofthecodeseenintheNativeInjectionparagraph:
00000011movedx,[0x238b9bc]
00000017movecx,eax
00000019call0x7426edd0
0000001Eandeax,0xff
http://www.ntcore.com/files/netint_native.htm

51/56

12/10/2016

.NETInternalsandNativeCompiling

00000023jz0x2c
00000025moveax,0x1
0000002Ajmp0x2e
0000002Cxoreax,eax
0000002Etesteax,eax
00000030jz0x42
00000032movecx,[0x238b9c0]
00000038call[0x5102544]
0000003Epopesi
0000003Fret0x4
00000042movecx,[0x238b9c4]
00000048call[0x5102544]
0000004Epopesi
0000004Fret0x4
SinceIknowthatthecallatoffset38hcallsasMessageBox.Show(String),Ialsoknowthatthefirstargumentonthestackorinthiscase,sinceit'sa
fastcall,thedatainecxrepresentsaStringclass.However,thisisrathernormal,becauseMessageBoxisapublicAPI.PublicAPIscouldbesolvedin
thesamewayinnativeC++applications.ThedifferencecanbenotedwhenconsideringtheCheckPassword(String)methodcalledinthiscode.
CheckPasswordisaprivatemethod,nonethelessIcanretrieveitsarguments,itsreturntypeand,ifithasn'tbeenobfuscated,evenitsname.Thus,I
perfectlyknowthatthedatamovedinecxrepresentsaninstance,sinceCheckPasswordisanonstaticclassmember,andthatthedatamovedinedx
representsaStringclass.Ialsoknowthatthiscallreturnsabooleanvalueandcaninterprettheinstructionsbelowaccordingly.
IhavetodoasmallcomparisionwithnativeC++applications,becausemanypeopleminimizethefactthatMSILcodecanbedecompiledbysaying
thatevenC/C++codecanbedecompiled.Thisisacompletelyincorrectstatementasitcomparesapplestooranges.SpeakingaboutC/C++
applications,aroughdecompiledCcodecanbeobtainedsometimes.Insomecases,thedecompilerisnotevenabletogenerateanyCcodeatall.And
evenifheisableto,inmanycasesthedecompiledcodeiswrong.AndeveninthosecaseswherethedecompiledCcodeisactuallyright(meaningit
correctlyrepresentswhatthemachinecodeisdoing),itisnotguaranteedtobeeasiertounderstandforthereaderthanthemachinecode,sincethe
decompiledCcodeismostlyamess.Andlastbutnotleast,theCdecompilerhasnoclueofhowtointerpretdata.Forexample,whenI'mreferencinga
memberinastructure,theresultingdecompiledCcodewillonlyproduceareferencetopointer+N,whereNistheoffsettothereferencedmember.
Thismeansthat"info.bValue=TRUE"generatessomethinglike"*((int*)(ptr+N))=1"inCcode.Thesameappliestothemethod'sarguments,
returnvalue,calls,etc.AlthoughthedecompiledCcodemaysometimesberecompilable,itisabsolutelynothreattointellectualproperty.Atleast,no
morethananalyzingthemachinecodeis.
Whentalkingaboutprotecting.NETapplications,therootoftheproblemistheMetaData.TheMetaDataisusefulformanypurposes,butI'manalyzing
itfromthepointofviewofareverser.TheMetaDataleavesnothinguncovered,makingitimpossibletohidesomething.
Although.NETnativedecompilinghasn'ttobethoughtasanimportantissuerightnow,it'sinterestingtoevaluatethepossibility,sinceitwouldmakean
attemptsuchasaNativeFrameworkDeploymentserviceuseless.Nativeimagesthemselveshavetoholdenoughinformationinorderfortheexecution
enginetosolvethereferenceswithinthenativecode.Thisinformationcouldbeexploitedbyareverserfordecompiling.Eveniftheinformationwas
missing,likeinthecasewhenonemanuallyinjectsnativecode,itwouldbestillpossible(althoughnoteasy)tocommunicatewiththeJITtosolvethe
references.
Themachinecodecould,intheory,alsobeobfuscatedinordertofurthercomplicatedecompiling,butitwouldbestillpossibletosolvethereferencesin
thecode,makingitmucheasiertounderstanditthanitsC/C++equivalent.
http://www.ntcore.com/files/netint_native.htm

52/56

12/10/2016

.NETInternalsandNativeCompiling

.NETVirtualMachines
Virtualmachineshavebeenabighitintheareaofnativecode.Itwasonlyamatteroftime,beforesomeonetriedtobringtheconceptto.NETcode.I
don'tknowhowmanyprotectionsrelyonthistechnology,butIcansaythatMicrosoftitselfinvestedinitwithitsSLP(SoftwareLicensing&Protection)
services.Ican'tanalyzethecodeoftheirproductasitwouldinsomewayviolatetheirlicensingterms,butIcandiscussit.
SLPprovidesapermethodprotection.Thismeanstheusercanchoosewhichmethodstoprotect.Aprotectedmethodwhendisassembledlookslike
this:
privateboolCheckPassword(stringstrPass)
{
object[]args=newobject[]{strPass}
return(bool)SLMRuntime.SVMExecMethod(this,"28d981d5a74646a9bed4c66fdcbd82d8",args)
}

Themethoddoesnothingelsethaninvokingthevirtualmachinebypassingtheclassinstance,themethod'sargumentsandastringthatrepresentsthe
methodbeingcalled.
Theprotection'sruntimeismadeofthree.NETassemblies.Theruntimecreatesitsownvirtualmachineontopofthe.NETframework..NETvirtual
machinesusethereflectiontosolveexternalreferences.IfIreferenceaprivatevariableinside,let'ssay,thecurrentclass,thevirtualmachinewilldo
thefollowing:
usingSystem
usingSystem.Collections.Generic
usingSystem.ComponentModel
usingSystem.Data
usingSystem.Drawing
usingSystem.Text
usingSystem.Windows.Forms
usingSystem.Reflection
namespacereflection
{
publicpartialclassForm1:Form
{
publicForm1()
{
InitializeComponent()
}
privateintMyPrivateVariable=0
privatevoidChangePrivateVar(objectobj)
{
http://www.ntcore.com/files/netint_native.htm

53/56

12/10/2016

.NETInternalsandNativeCompiling

Typet=obj.GetType()
//getthefield,nomatterhowthefieldisdeclared
FieldInfof=t.GetField("MyPrivateVariable",BindingFlags.Public|
BindingFlags.Static|BindingFlags.NonPublic|BindingFlags.Instance)
f.SetValue(obj,(int)1)
}
privatevoidbutton1_Click(objectsender,EventArgse)
{
//displays0
MessageBox.Show(MyPrivateVariable.ToString())
//changesthevaluegiventhecurrentobject
ChangePrivateVar(this)
//displays1
MessageBox.Show(MyPrivateVariable.ToString())
}
}
}

Asonecansee,MetaDataturnsouttobequiteusefulwhencombinedwithreflection.However,Ileavethereaderimaginehowslowa.NETvirtual
machinebuiltontopofthereflectiontechnologywillresultinexecutiontime.That'swhyeventheSLPguidewarnsitsusers:
Intheearlieranalogyaboutbakingacakefromarecipe,itwasassumedthatyouhadtoprotecttheentirerecipe.Ofcourse,thereisalotofsimilarity
betweencakerecipes,anditisunnecessarytoprotecttheentirerecipe,justthosepartsofitthatmakeitunique.Thiswoulddolittletoreducethe
securityoftherecipe,butmakesitmuchfastertoreadonlythosesecretingredientsneedtobedecrypted.
Similarly,becausetheSVMneedstointerprettheSVMLcode,andrunsontopoftheCLR,thereisaperformanceelementtotheequationthatneedsto
beaddressed.Youdonotwanttoprotecttheentirecodebase,becauseitwouldslowthewholeapplicationdownandaddlittletooverallsecurity.
Instead,youwanttoprotectonlywhatisnecessary:thesecretingredient.
Inthistext,theymakeitsoundlikeitissomethinggoodthatonlyfewmethodsarebeingprotected,thoughthisisn'trealistic.Giventhatthe.NET
virtualmachineapproachisquitegoodandthatitismuchmoreprofessionalthanNativeFrameworkDeploymentservices,ithassomesignifcantflaws.
Thisapproachmightbethebestoneregardingthelicensingofa.NETapplication,butitreallycan'thelpmuchtoprotectintellectualproperty.Ifone's
entireapplicationreliesonabunchofnonexecutiontimecriticalmethods,thenwhatitishidingreallyisn'tagreatsecretanyway.Therearealsosome
restrictionsregardingthevirtualizationofmethods:
MethodswiththefollowingconstructscannotbetransformedinCodeProtector.
Methodswithingenericclasses.
Methodscontainingexplicitinstantiationsofgenerictypes.
Methodswithgenericparameters.
http://www.ntcore.com/files/netint_native.htm

54/56

12/10/2016

.NETInternalsandNativeCompiling

Nonstaticmethodsofastructure.
Methodswithoutorrefparameters.
Methodsthatinvokeothermethodswithoutorrefparameters.
Methodsthatmodifyanymethodparameter,eveniftheparameterisdefinedasabyvalue.
Methodswithavariablenumberofparameters(e.g.,usingtheparamskeywordinC#).
Methodswithtoomanylocalvariablesorparameters(>254).
MethodsthatcontaincallstoReflection.Assembly.GetExecutingAssembly(),Reflection.MethodInfo.GetCurrentMethod(),or
Reflection.Assembly.GetCallingAssembly().
CLR1.1Frameworkonly:Methodsthatcreateobjectsusingconstructorsthathaveavariablenumberofparameters.Thisrestrictiondoesnotexist
whenanonconstructormethodisinvoked.
ImplicitandexplicitcastoperatorscannotbetransformedtotheSecureVirtualMachine(SVM).
UnsafecodeForexample,inC#,methodsthatcontainthekeywordunsafetypicallycannotbetransformed.
Thislistisalsointerestingforthosewhomightconsiderwritinga.NETvirtualmachinethemselves.Ihavegiventhereadermyopinionaboutthis
protectiontechnique,butlet'sexaminehowonecouldovercomeit.
Ifoneisreallyinterestedinwhataprotectedmethoddoes,itisnecessarytoanalyzethevirtualmachine'scode.Thefirstapproachwhichcomestomy
mindisusingthe.NETprofilingAPItoinjectloggingcodeinordertoretrievethemethodscalledinsidethevirtualmachine.Thiswouldprovidean
executionflowlogwhichcanbeusedtoanalyzethevirtualmachine'scodeexecutedforaparticularmethod.
Thesecondtecniquetoovercomethiskindofprotectionisbasedonsubstitution.Ifoneisn'tinterestedinwhatthecodedoes,sinceheknowsitor
knowswhatthecodeshoulddo,thenhecanreplacethecodewithhisown.ThiscanbeeasilyaccomplishedthroughSebastienLebreton'sReflexil.This
approachaddressescracking,notreversing.ButsinceSLPisalsoalicensingsystem,thismustbetakenintoaccount.Let'ssaythatthemethodFsets
uptheinizializationssettingsforanapplication.ThismethodisprotectedthroughSLP,whichwon'texecuteitunlessonehasavalidlicenseforthe
program.OnecouldreimplementtheFmethodandcompletelydetachtheSLPruntimefromtheprotectedassembly.Thismightbedifficultinsome
cases,butthat'swhatreversingisallabout.However,SLPisterriblyslowandprotectingmanymethodsreflectsinanunacceptableperformanceloss.
Theperformanceproblemcouldbesignifcantlyimprovedbyautomaticallygeneratingnativeimagesduringthesetupprocess.
Sometimes,thevirtualmachineprotectioniscombinedwithcodeobfuscationtoprovidesecurityforallthemethodswhichhavenotbeingvirtualized.
Inthiscase,ifoneisinterestedindecompilingtheMSILcode,thefirststepisremovingthecodeobfuscation.Thiscanonlybedonebyanalyzingthe
obfuscationalgorithmandunderstandinghowtoreverseit.TherebuildingofthedeobfuscatedassemblycanbeeasilyachievedthroughRebel.NET.

Conclusions
AsI'veneverreadabooknoranarticleabouttheCLRinfrastructure,whathasbeenpresentedinthisarticlearethe.NETinternalsfromthe
perspectiveofareverser.Thiswasthesecondpartofthetwoseriesofarticlesabout.NETinternalsandprotections.IhopeIhavegiventhereaderan
ideaoftheproblemssurrounding.NETprotectionsystems.Asthe.NETtechnologyisstillveryyoung,itmightchangesignificantly.Idon'tknowif
intellectualpropertywillbetakenintoaccountinnextversionsoftheframework.Ialsohopethattheseproblemswillbetakenintoaccountwhennew
frameworksaregoingtobedevelopedinthefuture.Asthe.NETframeworkhasbeenanewplaygroundforreversing,Icanonlyguessthatmany
problemswerenottooobviousatbeginningofitsdevelopment(althoughtheJavaexperienceshould'vebeenalesson).Apossibleevolutionofthe.NET
frameworkcouldrelyonofferingnativecompilingasalternativetoMSILanddrasticallyreducingtheMetaDatainformationbypreservingitonlyfor
publictypes/members.
Maybe,I'mtotallywrongandwewillsoonseemostmajorapplicationsbeingdeployedasMSILassemblies.Istronglydoubtit.
http://www.ntcore.com/files/netint_native.htm

55/56

12/10/2016

.NETInternalsandNativeCompiling

DanielPistelli

http://www.ntcore.com/files/netint_native.htm

56/56

You might also like