Vous êtes sur la page 1sur 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

Vous aimerez peut-être aussi