Professional Documents
Culture Documents
How To Write Your Own Packer by Bigboote: Vol. 1, No. 2, 2006
How To Write Your Own Packer by Bigboote: Vol. 1, No. 2, 2006
2,2006
HowtoWriteYourOwnPacker
byBigBoote
Vol.1,No.2,2006
Abstract:
Whywriteyourownpackerwhentherearesomanyexistingonestochoosefrom?Well,asidefrommakingyourexecutablessmaller,
packingisagoodwaytoquicklyandeasilyobfuscateyourwork.
CodeBreakersJournal,http://www.CodeBreakersJournal.com
CodeBreakersMagazineVol.1,No.2,2006
How to Write Your Own Packer produced took about 1.5 weeks to produce including
researchand bugfixing. Subsequentones tookfarless
ByBigBoote sinceIhadalreadydonethehardpart,whichisfiguring
outhow.Hopefullythisdocumentwillsaveyouthattime
aswell!
Youdonothavetouseassemblerforthemostpart.Ifyou
Why write your own packer when there are so many
can part with supporting some esoteric features, you
existing onesto choose from? Well, aside from making
won'thavetouseitatall.Allofthatisrelevantforthe
your executables smaller, packing is a good way to
decompression stub only anyway. The packer can be in
quickly and easily obfuscate your work. Existing well
LogoorObjectOrientedCOBOLifyoulike.
knowpackerseitherhaveanexplicit'unpack'function,
or there are readily available procdump scripts for OK,enoughoftheblahblahblah,ontotechnicalstuff....
generatinganunpackedversion.
3 Big Picture
1 Intro Simple. Executable is analyzed, transformed, and an
extrapieceofcodeisattachedwhichgetsinvokedinstead
Why write your own packer when there are so many oftheoriginalprogram.Thispieceiscalleda'stub'and
existing ones to choose from? Well, aside from making decompressestheimagetoitsoriginallocation.Thenit
your executables smaller, packing is a good way to jumps to that original location. But you know this
quickly and easily obfuscate your work. Existing well already.
knowpackerseitherhaveanexplicit'unpack'function,or
there are readily available procdump scripts for Sounds simple, but there are pitfalls that await you.
generatinganunpackedversion. Someoftheseinclude:
SincethisdocumenthasquicklyexplodedinlengthI'm
SupportforsimplifiedThreadLocalStorage,whichis
goingtobreakitupintoseparateinstallments.Inthis
keyinsupportingDelphiapplications
installment I will cover the qualitative aspects of
Supportforcoderelocationfixupsindllsifyoucare
producingapacker.I'lldiscusswhatyou'regettinginto
aboutpackingdlls.RecallActiveXcontrolsaredlls
andhowthepackerisstructuredingeneral.I'llbriefly
too,asareothercommonthingsyoumightbe
discusssomepitfalls,andI'llgivesomelinkstotechnical
interestedinpacking
informationyouwillneedtobefamiliarwithbeforegoing
intothenextinstallments.
Supportforsomestuffthatmustbeavailableevenin
InthenexttwoinstallmentsI'llgointodetailsofhowto thecompressedform.Thisincludessomeofyour
implement the components of the packer and how I resourcesandexportnamesindlls
usuallygoaboutproducingthem. Dealingwithboundimports
CodeBreakersJournal,http://www.CodeBreakersJournal.com
CodeBreakersMagazineVol.1,No.2,2006
WorkingaroundbugsinMicrosoftcode.Thereisan MakingthePackerApplication
infamousonerelatingtoOLEandtheresource Thepackerapplicationdoesallthehardwork.This
section.Manypackersdonotaccommodatethisand makessincewhenyourealizethestubissupposedto
thisisimportantforActiveXsupport. doaslittleaspossibletohaveaminimumimpacton
runtime.
horse'smouth.Dry.Accurate.
7 A Stub That Runs
It'susefultorememberthatyourdecompressionstubis
5 Next Step actually a parasite onto a program that was never
OK,afteryou'vegottenfamiliarwiththose,wecanstart expectingforittobethere.Assuch,youshouldtryto
towritesomecode.I'mgoingtosavethatforthenext minimize your impact on the runtime environment in
installments(probablytwo).Theywilldetail: yourpacker.Ihadmentionedbeforethatyoucouldmake
a packer in Logo or ObjectOriented COBOL, and that
MakingtheUnpackerStub reallywasonlypartiallytrue.Youcanmakethepacker
Thestubhasseveralresponsibilitiesasidefromthe applicationthatwayfersureandyoumightevenbe
obviousdecompression.Italsohastoperformduties abletomaketheunpackerthatwaysometimesbutyou
normallydonebytheWindowsloader. willreallybemuchhappierwithC/C++/ASMforthestub
part.IpersonallylikeC++.Anyway,itwillbesmaller.If
youdon'tcareaboutthesize,stillusingstufflikeDelphi
orVBforthestubwouldbeproblematicbecauseithoists
CodeBreakersJournal,http://www.CodeBreakersJournal.com
CodeBreakersMagazineVol.1,No.2,2006
in subtle stuff like TLS and runtimes, and they don't assumptions.Inreallifeyoudon'thavetodoitthisway,
have primitives needed to thunk over to the original butlet'stemporarily pretend we are and at the end of
program. Plusit can hose COM stuff that the original this series you'll know how you might like to do it
appisn'texpecting.Solet'sassumetheunpackerwillbe different.
inthe lowerlevel languagesIspoke ofandtakesolace
that this is pretty straightforward code, and that the
Bigpictureisthattherewillbetwoprojects,producing
packerstillcanbeinwhatever. twodistinctexecutablesthepackerstubandthepacker
application. Their configuration will be significantly
Sincethestubisaparasite,andsinceitwillhavetobe
different.
locatedinaspotattheoriginalapplication'sconvenience,
wewillhavetoberelocatingitdynamicallyinthepacker
Wearegoingtodoabitofledgerdemainwiththestub
application.Tohelpwiththiswewillmakethestubwith
project which will be explained later, but for now,
relocationrecords.Theseareusuallyusedfordllswhen
configureaboilerplateprojectforyourstubthusly:
theycan'tbeloadedattheirpreferredaddress.Wewill
makeuseofthemwhenbindingthestubtotheoriginal ProduceaDLL
application.
Usestaticmultithreadedruntimelibraries
SettingUpProjectsandnowforsomethingcompletely addanydefinesorheaderpathsyourcompressorlib
different willbeneeding
/FAcsgeneratelistingfileswithsource,assembly,and
machinecode
OK, I am going to take a brief break from code and
/Fa(filename)specifywheretheselistingsgo
technologicalstuffandtalkaboutprojectconfiguration. remove/GZcompilergeneratedstackchecks
Normally I wouldn't since that's a personal choice, removeanydebugoptions,itwon'thelpuswherewe're
howeverthistimeIwillbecausethingsItalkaboutlater going
will be dependent upon some of the configuration
CodeBreakersJournal,http://www.CodeBreakersJournal.com
CodeBreakersMagazineVol.1,No.2,2006
BOOLWINAPI_DllMainCRTStartup(HANDLE,
theseoptionsareprobablyavailableascheckboxes,so
DWORD,LPVOID)
youwon'thavetomanuallyaddthem.
{
//(programwillgohere)
returnTRUE;
}
Thegististhatwearenotgoingtohavenormaldebug
capabilitessoweturnoffthatstuff.Instead,wewillbe
relyingonthelistingofthecompilergeneratedassembly Thisshouldresolvethelinkererrorandwillbeourentry
toseecodeandthelinkergeneratedmapfiletoseeactual point. The place our program will ultimately go is
addresses. All this is interesting stuff in any project indicatedbythecomment.Ultimatelywe'llneverhitthe
'return TRUE' statement; it's just there to make the
really, but it is all we have for debugging in this one.
compilerhappy,andthefunctionsignatureiswhatitis
If you build now you will should get a linker error tomakethelinkerhappy.
complaining about an unresolved external symbol
Ifyouwanttobemorearty,youcandothefollowing:
DllMainCRTStartup@12. This is good! If you don't get
thatthenthedefaultlibsarecomingin.Thesymbolis #pragmacomment(linker,"/entry:\"StubEntryPoint\"")
possibledifferentforBorlandstuff.Othererrorsprobably voiddeclspec(naked)StubEntryPoint(){
meansomethingelseneedstobefixed;thisistheonly //(programwillgohere)
oneyoushouldgetforMicrosoft'scompiler. }
whichissyntacticallyclearer.
9 Runtime dependencies
This is cosmetic so don't feel bad if you find the
You cannot assume what runtime dependencies the
equivalent pragmas for your compiler/linker. Also, this
originalapphas.Thus,youcannotmakecallstofunky
pervertswhatthecompilernormallythinksaboutandI
dlls(vbrunX.dll,etc).Youhavenoideaiftheyarethere.
have seen it crash randomly. I have found when the
Youwilldowelltostaticallylinkyourruntimelibrary.
compiler gets in a crashing mood, that putting in:
Youwilldomuch(much)better,however,tonotlinkany
runtimelibrariesatall!ASMcoderswilltakedelightin
this fact already, because they are hardcore, but this asmnop
neednotdissuadetheC/C++coderswhoareaccustomed
tomalloc()strcmp()newstd::vector<>orsuch.Allthisis
inacoupleplacesseemstogetitbackontrack.Ain'tthat
doable. You will just have to provide your own
alaugh?!Whatever...
implementation of these functions. Fortunately, this is
prettyeasysinceyoucancallnativefunctionsexported As code is added, you should periodically build. The
by Kernel32.dll. /That/ dll is certainly present, and linkerwilladdmoreandmorecomplaintslikeaboveand
certainlyonethatisalreadyusedbytheoriginalappyou wewillhavetoimplementtheunderlyingmethodsthe
arepackingsofeelfreetouseitwhenyoulike. compilerisemittingreferencesto.Here'satip:whenyou
installedyourdevtools,youmayhavehadtheoptionto
installthesourcetotheCRuntime.Itwillbehelpfulin
10 Making a Trivial C somecasessinceyoucancutandpastesomeparts.In
Runtime to Link Instead of the particular,afunction:
Proper One extern"C"declspec(naked)void_chkstk(void)
Replacing the C Runtime might sound scary but
rememberweonlywanttoimplementwhatisnecessary; issometimesemittedbythecompilerquietly(ifyouhave
thiswillturnouttobeasmallsetofthings.Thelinker alargearrayonthestack,likeforabuffer).Justcutand
willhelpyoufigureoutwhattheseare.Recallthatwe pastethatone;it'sfunky.
turnedoffdefaultlibrarysearchingwiththe/nodefault
switch (or equivalent for your linker, that's for FYI,Itypicallyhavetoimplement:
Microsoft's).IfyouconfiguredasIsuggestedabove,we've
memcpy
gotalinkererroralready:DllMainCRTStartup@12We'll memset
fixthatonefirst. memcmp
malloc
Discard your boilerplate DllMain. Replace it with:
free
realloc
CodeBreakersJournal,http://www.CodeBreakersJournal.com
CodeBreakersMagazineVol.1,No.2,2006
organizedintosections,whichhavealocationandsize.
Thisinformationisstoredinthesectionheaders,which What the Hell is that? Well, it creates a special data
describewherethesectionsgoinmemory(relativetothe sectionforourglobalvariables.Dirtylittlesecretabout
loadaddress). thelinkeristhatitsortsthesectionnameslexically,and
Todothisproperly,wewillbeneedingtoknowourload discardstheportionatthe'$'andafter.Bynamingthe
address.Ifweareastubforanexewecansimplydoa section'.A$A'wewillbeforcingtheglobalvarsstructure
GetModuleHandle(NULL) and the returned module tobeattheverybeginningofthedatasection,whichwill
handleisthebaseloadaddress.Thiswon'tworkforadll be easy for the packing application to locate. Next, we
however.Themodulehandleforthedllisonthestack. will merge some sections with the following linker
Wecanwritesomecodetogetit,orwecanchoosenotto options.Youcanputtheseonthelinkline,oryoucanbe
do the 'arty entry point' and it is referenceble as a fancierandplacetheminthecodewithapragma(ifyour
parameter(donotattempttoreferencethoseparameters toolssupportsuch).Ithinkputtingtheminthepragma
ifitisthestubforanexeunlessyouarefondofcrashes). makesitmoreobviousfromthecodestandpointthatthe
stuffisfragileandshouldbehandledcarefullyifchanges
CodeBreakersJournal,http://www.CodeBreakersJournal.com
CodeBreakersMagazineVol.1,No.2,2006
CodeBreakersJournal,http://www.CodeBreakersJournal.com
CodeBreakersMagazineVol.1,No.2,2006
whizthroughtherecordsgettingtheDWORDatthe
addresstheyindicateandaddtheoffset
voiddecompress_original_data(){
void*pvCompData=(void*)
Pretty straightforward. The format of the relocation
(gev.RVA_compressed_data_start+load_address);
initialize_compressor(pvCompData,
recordsisalittlebitoddandisstructuredthewayitis
gev.compressed_data_size;); presumably for size considerations. The records are
organizedasaseriesofchunksofrecords,onechunkper
decompress_data(&origdirinfo,sizeof(origdirinfo)); page.Therecordsinthechunkreferenceanoffsetinto
the page. Additionally, for padding consideration there
intsection_count;
are records that are essentially noops and should be
decompress_data(§ion_count,sizeof(section_count))
;
ignored.Pseudocodefollows:
for(inti=0;i<section_count;++i){ voidperform_relocations(){
section_headerhdr; //seeifnorelocationrecords
decompress_data(&hdr,sizeof(hdr)); if
void*pvOrigLoc=(void*)(hdr.RVA_location+ (origdirinfoIMAGE_DIRECTORY_ENTRY_BASERELO
load_address); C.VirtualAddress==0)
decompress_data(pvOrigLoc,hdr.size); return;
}
//computeoffset
cleanup_compressor(); IMAGE_DOS_HEADER*dos_header=
} (IMAGE_DOS_HEADER*)load_address;
IMAGE_NT_HEADERS32*nt_hdr=
Thiswillbecalledinthemainentrypointofthestub (IMAGE_NT_HEADERS32*)
rightaftercomputingtheactualloadaddress. &((unsignedchar*)load_address)dos_header>e_lfanew;
DWORDreloc_offset=load_addressnt_hdr
That'sit!Whatcouldbeeasier?Well,noticethatwe're >OptionalHeader.ImageBase;
using a stream model for our compressor. Most
compressionlibrariescomeprettyclosetoimplementing //ifwe'rewherewewanttobe,nothingfurthertodo
thatbutyouhavetodoeversoslightlymoretomakeit if(reloc_offset==0)
return;
thatsimple.Iwrapupmycompressorsinaclasssothat
they all implement the above interface to make things //gottadoit,computethestart
simple like above. Swaping out compressors then just IMAGE_BASE_RELOCATION*ibr_current=
meansmakinganewadaptorclass.Therestofthestub (IMAGE_BASE_RELOCATION*)
need not be touched to put in different (origdirinfoIMAGE_DIRECTORY_ENTRY_BASERELOC
compressors/encryptors. .VirtualAddress+load_address);
//computetheend
Nowthatalltheoriginaldataisdecompressedintoit's IMAGE_BASE_RELOCATION*ibr_end=
originallocation,wehavetodostuffthattheWindows (IMAGE_BASE_RELOCATION*)
loader normally does. This includes relocation fixups, &((unsigned
importslookup,andTLSinitialization/thunking. char*)ibr_current)origdirinfo[IMAGE_DIRECTORY_EN
TRY_BASERELOC.Size];
CodeBreakersJournal,http://www.CodeBreakersJournal.com
CodeBreakersMagazineVol.1,No.2,2006
ibr_current=(IMAGE_BASE_RELOCATION*) IMAGE_IMPORT_MODULE_DIRECTORY.dwModuleNa
&((unsignedchar*)ibr_current)ibr_current>SizeOfBlock; meRVA has the name of the dll you will need to
} LoadLibrary()on).Onceyougettheaddress,youstickit
} intheparallellocationintheImportAddressTablearray.
Youdothisforeachmember.
ThisisthemajorityofwhatisneededtosupportDLLs. InthecasewhentheImportNameTableisnotpresent,
Thereisalittlebitmorediscussedlater.Giventhatthis however, as with Borland's linker, you must get the
issostraightforward,I'malittlesurprisedatthenumber address of the function name from the
ofpackersouttherethatdonotsupportDLLs. ImportAddressTable itself. Then you overwrite it with
Thenextmajorthingwehavetodoistoresolveallthe thefunctionaddress.
imports. This is only a little more involved that the It is important to use the ImportNameTable in
relocationrecords. preferencetotheImportAddressTablebecauseofathing
called'boundexecutables'.Ifyouwanttotestyourwork
on a bound executable, consider that notepad.exe is
16 Resolving Imports bound.
Resolving the imports consists of walking through the
Import Address Table of the original application and AfterprocessingeachDLLyoumayormaynotwishto
doingGetProcAddresstoresolvetheimports.Thisisvery do a FreeLibrary. It's going to depend on how you
similar to the relocation record logic that I won't do a implementyourpackerapplication.We'lldiscussthatin
pseudocode example. Details of these structures are thenextinstallment,anditrelatesto'mergedimports'.
giveninthelinksprovidedinthefirstinstallment.The For now, suffice it to say that if you perform merged
structuresallstartat: imports,youcancallFreeLibrary,butifyoudonot,you
mustnotcallit.Youmightwanttoputthecallinand
origdirinfoIMAGE_DIRECTORY_ENTRY_IMPORT.Virtual commentitoutwhiledevelopinguntilyouhavemerged
Address
imports implemented. Merged imports is important for
properly supporting TLS that potentially exist in
ThereareacouplecaveatsIshouldmentionhowever: implicitly loaded DLLs. This leads into the final
responsibility for the stub, which is handling TLS
ThestructuresarewiredtogetherviaRVApointers. support.
Theseneedtohavetheload_addressaddedtomakea
realpointer
Thepointersinthestructuretostringsarereal 17 Supporting TLS
pointers.These_do_not_needtheload_address ThreadLocalStorage,orTLS,isahandyprogramming
added.Relocationprocessingwillhavealreadyfixed mechanism.Wedon'tcaremostly,sincewe'renotusing
theseup. it, but the original application to be packed might be
usingitindeed.Infact,Delphialwaysusesit,andsoif
Don'tforgetaboutimportingbyordinal.Youwillknow we're going to support packing Delphi apps, we better
thisishappeningbecausethepointertothestringwill accomodateit.
have the high bit set ( (ptr & 0x8000000) != 0 ).
BorlandandMicrosoftlinkersdodifferentthings,soyou TLSfundamentallyisdoneviaAPIcalls.Ingeneral,you
have to be prepared to get the string from either of allocatean'index'whichyoustoreinaglobalvariable.
differentspots.Basically,therearetwoparallelarrays, WiththisindexyoucangetaDWORDvaluespecificto
theImportNameTablewhichyougetfrom: each thread. Normally you use this value to store a
pointer to a hunk of memory you allocate once per
IMAGE_IMPORT_MODULE_DIRECTORY.dwImportNa thread. Because people thought this was tedious, a
meListRVA special mechanism was created to make it easier.
Consequently,youcanwritecodelikethis:
andtheImportAddressTablewhichyougetfrom:
declspec(thread)inttls_int_value=0;
IMAGE_IMPORT_MODULE_DIRECTORY.dwIATPortio andeachthreadcanaccessit'sdistinctinstancebyname
nRVA likeanyothervariable.Idon'tknowifthereisanofficial
nameforthisformofTLS,soI'llcallit'simplifiedTLS'.
TheImportNameTableisoptional.Borlanddoesn'tuseit. Thisisdoneincooperationoftheoperatingsystem,and
Ifitispresent,youshoulduseittogetthenameofthe there are structures within the PE file that makes it
function and GetProcAddress() it's pointer (the happen.Thosestructuresarecontainedinachunkthat
CodeBreakersJournal,http://www.CodeBreakersJournal.com
CodeBreakersMagazineVol.1,No.2,2006
Forthis,Iaddtwoitemstotheexternalglobalpacker extern"C"voidNTAPITLS_callback(PVOIDDllHandle,
datastructure: DWORDReason,PVOIDReserved){
if(safe_to_callback_tls){
GlobalExternVars PIMAGE_TLS_CALLBACK*ppfn=
{ g_pkrdat.m_tlsdirOrig.AddressOfCallBacks;
//(otherstuffwepreviouslydescribed) if(ppfn){
IMAGE_TLS_DIRECTORYtls_original; while(*ppfn){
IMAGE_TLS_DIRECTORYtls_proxy; (*ppfn)(DllHandle,Reason,Reserved);
}; ++ppfn;
}
}
The packer application will copy the original data to }else{
tls_original for our use at runtime. tls_proxy will be delayed_tls_callback=true;
almost an exact copy, except two items will not be TLS_dll_handle=DllHandle;
modifiedfromthestub: TLS_reason=Reason;
TLS_reserved=Reserved;
tls_proxy.AddressOfIndex }
tls_proxy.AddressOfCallBacks }
InthestubwewillinializetheAddressOfIndextopoint ThiswillprovideaplacefortheOStostoretheslotinfo,
to a normal global DWORD variable, and we will which we will later restore, and if it does call thunks
initialize AddressOfCallBacks to point to an array of thenwewillcapturetheparametersforlaterwhenwe
functionpointersinthestub.Thefunctionpointersarray will invoke the original thunks after decompression.
isalistofthingsthatiscalledwheneveranewthreadis Again,thisisalldonebecausetheOSwillbedoingthis
created. It is intended to be used for user defined stuff before we have a chance to decompress. After we
initializationoftheTLSobjects.Alas,nocompilerIhave decompress, we pass the call straight to the original
seenhaseverusedthem.Moreover,ontheWindows9x application.
line,thesefunctionsarenotevencalled.Still,wesupport
it in case one day they are used. We point the Wehandlethislaststeplikeso:
AddressOfCallbacks to an array of two items, one
voidFinalizeTLSStuff(){
pointing to a function of our implementation, and the if
secondbeingNULLtoindicatetheendofthelist. (origdirinfoIMAGE_DIRECTORY_ENTRY_TLS.Virtual
Address!=0){
TherewillbeaglobalDWORDfortheTLSslot:
*gev.tls_original.AddressOfIndex=TLS_slot_index;
void*TLS_data;
DWORDTLS_slot_index;
asm
{
The TLS callback function must be of the form: movecx,DWORDPTRTLS_slot_index;
movedx,DWORDPTRfs:02ch
movecx,DWORDPTRedx+ecx*4
movpvTLSData,ecx
extern"C"voidNTAPITLS_callback(PVOIDDllHandle,
}
DWORDReason,PVOIDReserved);
intsize=gev.tls_original.EndAddressOfRawData
gev.tls_original.StartAddressOfRawData;
alsoyouaddtwoglobalbooleansindicatingthatitissafe memcpy(pvTLSData,(void*)
CodeBreakersJournal,http://www.CodeBreakersJournal.com
CodeBreakersMagazineVol.1,No.2,2006
CodeBreakersJournal,http://www.CodeBreakersJournal.com
CodeBreakersMagazineVol.1,No.2,2006
assuchsincewearegoingtosnipoutinterestingpieces. memorysinceithasmappedthesectionsappropriately.
You could just as easily use a tool to spew just the
interestingpiecestobinaryresources,orencodethemas So,Iwouldfurthersuggestcreatingautilityclassthat
staticdatainaCsourcefile.Thischoiceispertasteand incorporates the address translator mentioned earlier
we are going to choose the resource approach. We are (along with logic to initialize it) that can provide
also going to be a commandline app. So... translated access from RVA to physical pointer for
regions within a PE file. Stick in an RVA, get out a
Configure your project as a commandline (console) physicalmemorypointer.Wecanusethisdeviceforboth
application.CreateaRCfileandincludearesourcethat thememorymappedoriginal,andalsofortheresource
isthestub'dll'producedbyyourpreviousproject.That's loadedstub.Youdon'thavetodothisbutitwillmake
reallyitforconfiguration.I'msurethatwillbeawelcome yourlifeeasier.Thisisaplusbecauseit'salreadygoing
simplificationafterhavingsetthestubproject! togetalittleharderasitiswink.gif.Youmaywishto
throwinacoupleotherconvenientPEspecificitems,like
pointers to the image headers. We'll be using various
22.2 Utility Code fieldsintheseheadersatseveralpointsthroughoutthe
packingprocess.
Therearegoingtobesomethingsthataresimple,but Oneotherthingthatwillmakeyouhappierinthelong
verytedious,andyouwillprobablyliketoproducesome run is to produce some sort of wrapper for your
machinerytotendtothesetasks. compressionlibraryofchoice.Indoingsoyoucanboth
Onesuchtaskrelatestotranslatingaddresses.Wehave simplifyuseofthelibraryandalsobeabletoswapouta
todothisinacoupleplacesfordifferentreasons,soyou differentcompressorshouldyouchoose.Forexample:
might consider making some sort of general purpose
classCompressor{
addresstranslator.Itwillneedtohandleseveraldistinct
public:
rangesofaddressesbeingmappedindependentlytoother Compressor(HANDLEhFile);//create;writetogiven
ranges.Inpracticalterms,therewon'tbeahugenumber fileatcurrentfilepos
ofrangemappings(likeabout5),soifyouwanttojust voidInsertData(constvoid*pv,intsize);//sticksome
keepalistofrangemappingsanddoalinearsearchno uncompresseddatain
onewillchastiseyou. voidFinish();//finishanypendingwrites
DWORDCompressedCount();//countofoutput
Anothertediousthing(Ifind)isreadinglittlebitsand (compressed)data
pieces from the original executable file. This is };
particularly true when navigating a network of objects
sinceyouhavetorunalongpointerpaths.Tomakethis ThissortofinterfaceIhavefoundtobesuitableforall
muchmorebearableIuseamemorymappedfileforthe compressionlibrariesIhaveconsidered,thoughofcourse
original executable. Readonly access is fine since we Iwouldn'tuseitforthings otherthanthisexepacker.
won'tbealteringtheoriginal(BTW,ifforsomereason
you do want to write to the mapped image, but not Other than that you might like to make a general
disruptyouroriginalfile,rememberyoucanmapitcopy purposeresourcetreewalker,butwe'lldiscussthatlater
onwrite.I'vedonethisforsomeprotectors.)Idon'tuse in the implementation. Making this part generic is
thisapproachfortheoutputfile,however,becausemost mostly useful if you wish to reuse it in other projects.
ofthatwillbesequentialwrite.
Lastly,Iwouldliketoreiteratethatthepointersinthe Withthatbeingsaid,wearereadytomoveontothe...
executable are RVA's. This means you will need to do
_two_thingstotransformthemtorealpointers.First,if
you'vemappedtheimagetoanaddress,youwillneedto
23 Basic Tasks
add that base address. The stub 'dll' compiled in as a Herearethefundamentalthingsthepackerwillneedto
resourcewillbeaccessedthroughamemoryaddressonce do.
we LockResource() on it. That address is the base
DetermineSizeoforiginal
address. Now, that's all you have to do on a running
module(i.e.onetheOSloadermappedin),butthat'snot Setupnewsection(s);modifyoriginals
all we have to do. The second thing we have to do is Createandaddstuboutsidethisregion
considerthefileandsectionalignmentoftheexecutable Preserveexportinfo
(do_not_assumetheyaretheysame).Thenetresultof
FixupTLSstuff
thisisthattherewillneedtobeanadjustmentonaper
sectionbasistotheresultantpointer.Again,thisisnot RelocatetheRelocations
necessary for a module loaded by the OS loader into
CodeBreakersJournal,http://www.CodeBreakersJournal.com
CodeBreakersMagazineVol.1,No.2,2006
CodeBreakersJournal,http://www.CodeBreakersJournal.com
CodeBreakersMagazineVol.1,No.2,2006
CodeBreakersJournal,http://www.CodeBreakersJournal.com
CodeBreakersMagazineVol.1,No.2,2006
CodeBreakersJournal,http://www.CodeBreakersJournal.com
CodeBreakersMagazineVol.1,No.2,2006
CodeBreakersJournal,http://www.CodeBreakersJournal.com
CodeBreakersMagazineVol.1,No.2,2006
IMAGE_NT_HEADERS::OptionalHeader.DataDirectoryI Recallfrominstallment2thattherelocationrecordsare
MAGE_DIRECTORY_ENTRY_EXPORT.VirtualAddress stored in chunks, one chunk per page, and as 16bit
IMAGE_NT_HEADERS::OptionalHeader.DataDirectoryI recordsthatareessentiallyoffsetsintothepage.Irefer
MAGE_DIRECTORY_ENTRY_EXPORT.Size youtoinstallment2fordetails,andtothereferencesin
installment1.Sufficeittosay,wetravelalongournow
towherewestuckitandhowbigitbecame.Finallyalign sorted array, emitting chunk headers whenever a page
upthebuffertoaDWORDboundaryforfurtherappends, changeisdetectedandemitting16bitrecordsotherwise.
andyou'redonewiththispart. Apageisona4096boundaryfor32bitPEfilessoyou
can AND the address with 0xfffff000 to find it's page
FYI,weare50%throughourtodolist.Andwehaven't
value,andyoucanANDtheaddresswith0x00000fffto
compressedanydatayet!It'salldownhillfromhere...
find it's offset for the relocation record. Also take care
that when you detect a page change, you will possibly
24.7 Do Stub Fixups and need to pad to a 32bit boundary by adding a noop
Relocating the Relocations relocation record (IMAGE_REL_BASED_ABSOLUTE).
Atthispointmostofthestub'sstuffhasbeenbuiltup Afterprocessingallrecordssetthe
andwecanfixupit'spointerstoreflectthefactthatwe
haveextractedandmovedit'soriginalcomponents.This IMAGE_NT_HEADERS::OptionalHeader.DataDirectoryI
taskisverysimilartotherelocationfixupsperformedin MAGE_DIRECTORY_ENTRY_BASERELOC.VirtualAdd
the stub. The difference is in computing the delta to ress
IMAGE_NT_HEADERS::OptionalHeader.DataDirectoryI
apply.
MAGE_DIRECTORY_ENTRY_BASERELOC.Size
Innormalrelocation,likewhatthestubperforms,there
isonlyonedelta.Thisisbecausetheimageasawhole to reflect this new chunk that we added. We should
moves,andallitemsarerelocatedbythesameamount. alreadybealignedtoa32bitboundary.
Inourcase,differentsectionshavemoveddifferently,and
thuseachitemmustbetreatedashavingit'sowndelta. 24.8 Setup for TLS Stuff
CodeBreakersJournal,http://www.CodeBreakersJournal.com
CodeBreakersMagazineVol.1,No.2,2006
wheneverweappend to the buffer we risk reallocating development in order to isolate problems. Not useful
memory, but we can recompute the pointer as needed otherwise.
fromtheindexbetweenappends.
OK, assuming you have implemented the wrapper
Anyway, if there is no TLS, as evidenced by:
interfaceIsuggestedinUtilityCode,above,weareready
todosomecompressing!Wellalmost.Thecompressionof
IMAGE_NT_HEADERS::OptionalHeader.DataDirectoryI theoriginaldatacouldbelarge,soIprefernottodoitto
MAGE_DIRECTORY_ENTRY_TLS.VirtualAddress memoryandratherdirectlycompresstotheoutputfile
(ergo the HANDLE constructor argument in the
Compressorclass).Sowemustcomputethefileposition
being0,thenwecansimplyclearoutthecopyofthetls
ofwherethisdatagoes.
directoryinthepublicdata(wecalledittls_originalin
installment2). WezeroedthesizeoftheoriginalPEsections,sothefirst
realoneisournewstubsection.Weneedtocomputethe
If there _is_ TLS, then we copy the original TLS
file offset to this new section (PointerToRawData).
directorystructuretothetls_originalinthepublicdata,
and copy over a few items to the tls_proxy:
You should make a copy of the original
IMAGE_NT_HEADERS if you haven't already. We will
SizeOfZeroFill manipulate it to reflect our output. Let's call it
Characteristics nthdrDest and initialize it to the original exe's values.
StartAddressOfRawData Thencalculate:
EndAddressOfRawData
nthdrDest.FileHeader.NumberOfSections=(newsection
count)
Note,theaddressesdonotneedtobetranslated(shock intnSectionHeadersPos=
ofshocks) because they reference data in the original IMAGE_DOS_HEADER::e_lfanew+
application, which we have not moved. The stub only sizeof(IMAGE_NT_HEADERS);
accesses that data _after_ it decompresses it. intnFirstSectionPos=nSectionHeadersPos+
(newsectioncount)*
sizeof(IMAGE_SECTION_HEADER);
Setup:
CodeBreakersJournal,http://www.CodeBreakersJournal.com
CodeBreakersMagazineVol.1,No.2,2006
InsertData The'fun'thatawaitsissimilartowhatwedidforexports
(IMAGE_NT_HEADERS::OptionalHeader.DataDirectory earlierinthatwewalkastructureandoptionallycopy
, stuffover,adjustingthepointerwhenwedoandleaving
sizeof(IMAGE_NT_HEADERS::OptionalHeader.DataDire itpointingtotheoriginaldatainthecompressedsection
ctory)); otherwise.
DWORDdwSectionCount=
IMAGE_NT_HEADERS::FileHeader.NumberOfSections;
InsertData(&dwSectionCount,sizeof(dwSectionCount)) The difference is that this structure is more complex,
; withmoreobjectsandamorecomplexdecisiononwhat
foreachsectionIMAGE_SECTION_HEADER tokeep.Firstletmebrieflytellyouwhatyouwantto
InsertData(& keepuncompressedbecausethat'stheeasyparttoknow
IMAGE_SECTION_HEADER::VirtualAddress, andtediouspartto figureoutexperimentally.Youwill
sizeof(IMAGE_SECTION_HEADER::VirtualAddress));
want to keep uncompressed the following resources:
InsertData(&
IMAGE_SECTION_HEADER::SizeOfRawData,
sizeof(IMAGE_SECTION_HEADER::SizeOfRawData));
firstRT_ICONshouldbekept
InsertData((actualpointertooriginaldata),
IMAGE_SECTION_HEADER::SizeOfRawData); firstRT_GROUP_ICONshouldbekept
firstRT_VERSIONshouldbekept
In other words, we are pushing the RVA of where the
datagoes,thephysical(uncompressed)size,andthenthe first"TYPELIB"shouldbekept
physicaldata.Wedothisforeachsectionoftheoriginal.
all"REGISTRY"shouldbekept
WhenwearedoneweinvokeFinish()onthecompressor
to flush any remaining data not written. OK,thatbeingsaid,keepinmindthatresourcesarea
multileveltreeofdirectories.Youneedtokeeptrackof
We get the number of actual compressed bytes with atwhatlevelyouaretomakeyourcomparisonsinorder
CompressedCount(). This we add to the size of the todeterminewhethertokeeparesourceornot.Also,asa
buffer we were building and store it in the perceivedconvenience,allthefixedsizedstructuresare
SizeOfRawDatafieldofthesectionheaderforthestub. coalesced at the beginning with variable length ones
afterwards.Thismeansallthedirectorystructuresare
Finally, get a pointer to the structure containing the atthebeginning,withthingslikestringidentifiersand
public data (this is why we didn't write out this until resourcedataafterwards.
now). Set the value of the stub entry point (after I do a similar thing as with the stub and build this
translating, of course), the RVA of the start of the sectioninmemorywithamanagedarrayofbytes.Once
compresseddata(whichistheRVAofthestub+thesize itisconstructedIwriteitoutlater.
ofthestubbuffer)andthesizeofthecompresseddata
(which we got from the compressor when done). Youcanwalkthetreeoncetofindwherethisboundary
betweenfixedandvariablesizeddatalays,thencopythe
Then seek back to the position PointerToRawData we fixeddataverbatim.It'sinterestingtonotthatmostof
justcomputedandwriteoutthestubbuffer.Basicallywe the pointers in this section are relative to the section
just concatenated the two in reverse order. itself,andthusdonotrequiretranslation.Theexception
tothisisthepointerstotheactualresourcedata,which
Finishedwithgeneratingandwritingoutthestub! isanRVA.
Walkthetreeasecondtimeandappendallthestring
24.10 Processing the Resource identifiers.Adjustthepointerstothesestringskeeping
inmindthattheyare_not_RVAs,butareratherrelative
Directory offsetsintotheresourcesection.
Processing the resource directory is a strictly optional
Walk the tree a third time and copy over the resource
task. It is a bit tedious. Benefits of processing include
chunks for the resources types of interest described
preserving the everimportant application icon and
above.Keepinmindthattheseactually_are_RVAs,so
CodeBreakersJournal,http://www.CodeBreakersJournal.com
CodeBreakersMagazineVol.1,No.2,2006
you will need to add the RVA of the beginning this IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT
section. What is that? Well, it is the RVA of the last kissitgoodbye
section, plus its size, aligned up to the
IMAGE_DIRECTORY_ENTRY_IATexpungeit
NT_HEADERS::OptionalHeader.SectionAlignment. The
resource chunks should be aligned between appends. IMAGE_DIRECTORY_ENTRY_DEBUG(wedon't
reallyhavebugs,anyway)
Setup the section header for this additional section. It
_must_havethename.rsrc.SetuptheVirtualAddressof Seriously, though, the first two are used by the loader
this section to the RVA we just computed. Setup the and will cause crashing behaviour. Removing them
PointerToRawDatainasimilarmanner,exceptusethe harms nothing. The last one might be nice, but the
last sections PointerToRawData + SizeOfRawData and debuggercan'tgettothedatauntilaftertheapplication
align the result up by the value of isrunning,whichistoolate.
IMAGE_NT_HEADERS::OptionalHeader.FileAlignment WritingouttheRemainder
instead. Set the SizeOfRawData to the size of the
CopyovertheoriginalDOSstub.
resulting chunk, and the VirtualSize to the same. You
can align these values up if you like. WriteoutthemodifiedPEheader.
Similar to what we did with the stub, seek to the Position to the section header offset we computed
PointerToRawDataandwriteoutthedatainthebuffer (nSectionHeadersPos)Loopthroughthesectionheaders
we'vebeenbuilding. wehavebeenkeepingonhandandwritethemout.Ifyou
haveamodifiedresourcesection,takecaretorenamethe
Finally,set:
originalandmakethenewonebenamed.rsrctowork
IMAGE_NT_HEADERS::OptionalHeader.DataDirectoryI
aroundtheMicrosoftOLEautomationbug.
MAGE_DIRECTORY_ENTRY_RESOURCE.VirtualAddr Closeyourfile.
ess
IMAGE_NT_HEADERS::OptionalHeader.DataDirectoryI
MAGE_DIRECTORY_ENTRY_RESOURCE.Size 25 Beyond Packers
Ithinkit'susefultoconsiderfromabigpictureofwhata
andwearedonewiththat.
packeris,becausesubsetsofthetechnologycanbeused
for different applications. For instance, we bound new
24.11 Dotting I's and Crossing code and data to an arbitrary executable that was not
T's designed to host it, without damaging the original
program. This is like an exe binder. Discard the
There are some details that will need to be fixed up compressionandalotofthemanipulationofdirectories
beforewritingtherestofthestuffout.Mostlythishasto and you can produce one. Similarly, one could retain
dowiththevariousdirectoryentries,butlet'snotforget some of the directory manipulations, like with the
theentrypointaddress! imports,andfashionaprotectorofsortstoresistreverse
Theentrypointiscomputedasthestub'dll'sentrypoint engineering. Other extended applications may come to
afterbeingtranslatedwiththetranslationdeviceIhope mindaswell.
youcreated.
The image size needs to be recomputed as the last 26 conclusions
section's VirtualAddress plus its VirtualSize.
Ihopedyoufoundsomeusefulinformationinthisarticle.
Ienjoyedhavingtheopportunitytowriteit.
Mostofthedirectoryentriesneedtobecopiedoverfrom
thestub'dll'afterbeingpassedthroughthetranslator.
Exceptions include the Resource directory. If you
processed resources you should point it to the new
sectionyoucreated.Ifyoudidnotleaveitasitwasinthe
original.Resourceswillbeavailableatruntime,butnot
toexplorerorOLE(orResHacker).
Ifyoumadeexports/relocations,setupthoseentries(that
wasdiscussedearlier).
Some directory entries should definitely be zeroed out:
CodeBreakersJournal,http://www.CodeBreakersJournal.com