ihatefeds.com · 2020-04-21 · 005.8--dc23 2015036294 no starch press and the no starch press logo...
TRANSCRIPT
GAMEHACKINGDevelopingAutonomousBotsforOnlineGames
NickCano
SanFrancisco
GAMEHACKING.Copyright©2016byNickCano.
Allrightsreserved.Nopartofthisworkmaybereproducedortransmittedinanyformorbyanymeans,electronicormechanical,includingphotocopying,recording,orbyanyinformationstorageorretrievalsystem,withoutthepriorwrittenpermissionofthecopyrightownerandthepublisher.
PrintedinUSA
Firstprinting
2019181716123456789
ISBN-10:1-59327-669-9ISBN-13:978-1-59327-669-0
Publisher:WilliamPollockProductionEditor:LaurelChunCoverIllustration:RyanMilnerInteriorDesign:OctopodStudiosDevelopmentalEditor:JenniferGriffith-DelgadoTechnicalReviewer:StephenLawlerCopyeditor:RachelMonaghanCompositor:LaurelChunProofreader:PaulaL.FlemingIndexer:BIMCreatives,LLC
Forinformationondistribution,translations,orbulksales,pleasecontactNoStarchPress,Inc.directly:NoStarchPress,Inc.2458thStreet,SanFrancisco,CA94103phone:415.863.9900;[email protected]
LibraryofCongressCataloging-in-PublicationData
Cano,Nick,author.Gamehacking:developingautonomousbotsforonlinegames/byNickCano.pagescmIncludesindex.Summary:"Ahands-onguidetohackingcomputergames.Showsprogrammershowtodissectcomputergamesandcreatebotstoaltertheirgamingenvironment.Coversthebasicsofgamehacking,includingreverseengineering,assemblycodeanalysis,programmaticmemorymanipulation,persistenthacks,responsivehacks,andcodeinjection."--Providedbypublisher.ISBN978-1-59327-669-0--ISBN1-59327-669-91.Intelligentagents(Computersoftware)2.Internetprogramming.3.Internetgames--Programming.4.Hacking.I.Title.QA76.76.I58C362016
005.8--dc23
2015036294
NoStarchPressandtheNoStarchPresslogoareregisteredtrademarksofNoStarchPress,Inc.Otherproductandcompanynamesmentionedhereinmaybethetrademarksoftheirrespectiveowners.Ratherthanuseatrademarksymbolwitheveryoccurrenceofatrademarkedname,weareusingthenamesonlyinaneditorialfashionandtothebenefitofthetrademarkowner,withnointentionofinfringementofthetrademark.
Theinformationinthisbookisdistributedonan“AsIs”basis,withoutwarranty.Whileeveryprecautionhasbeentakeninthepreparationofthiswork,neithertheauthornorNoStarchPress,Inc.shallhaveanyliabilitytoanypersonorentitywithrespecttoanylossordamagecausedorallegedtobecauseddirectlyorindirectlybytheinformationcontainedinit.
AbouttheAuthorNickCanowrotehisfirstscriptsforopensourcegameserverswhenhewas12andstartedabusinesssellinghisbotswhenhewas16.Hehasbeenapartofthegame-hackingcommunityeversinceandadvisesgamedevelopersanddesignersonbestpracticestoprotecttheirgamesagainstbots.Nickalsohasyearsofexperienceindetectinganddefendingagainstmalware,andhehasspokenatmanyconferencesabouthisresearchandtools.
AbouttheTechnicalReviewerStephenLawleristhefounderandpresidentofasmallcomputersoftwareandsecurityconsultingfirm.Hehasbeenactivelyworkingininformationsecurityforover10years,primarilyinreverseengineering,malwareanalysis,andvulnerabilityresearch.HewasamemberoftheMandiantmalwareanalysisteamandassistedwithhigh-profilecomputerintrusionsaffectingseveralFortune100companies.StephenalsodevelopedandteachesthePracticalARMExploitationclass,whichhasbeenofferedatBlackHatandseveralothersecurityconferencesforthepastfiveyears.
BRIEFCONTENTS
ForewordbyDr.JaredDeMott
Acknowledgments
Introduction
PART1:TOOLSOFTHETRADEChapter1:ScanningMemoryUsingCheatEngine
Chapter2:DebuggingGameswithOllyDbg
Chapter3:ReconnaissancewithProcessMonitorandProcessExplorer
PART2:GAMEDISSECTIONChapter4:FromCodetoMemory:AGeneralPrimer
Chapter5:AdvancedMemoryForensics
Chapter6:ReadingfromandWritingtoGameMemory
PART3:PROCESSPUPPETEERINGChapter7:CodeInjection
Chapter8:ManipulatingControlFlowinaGame
PART4:CREATINGBOTSChapter9:UsingExtrasensoryPerceptiontoWardOffFogofWar
Chapter10:ResponsiveHacks
Chapter11:PuttingItAllTogether:WritingAutonomousBots
Chapter12:StayingHidden
Index
CONTENTSINDETAIL
FOREWORDbyDr.JaredDeMott
ACKNOWLEDGMENTS
INTRODUCTIONPrerequisitesfortheReaderABriefGameHackingHistoryWhyHackGames?HowThisBookIsOrganizedAbouttheOnlineResourcesHowtoUseThisBook
PART1TOOLSOFTHETRADE
1SCANNINGMEMORYUSINGCHEATENGINEWhyMemoryScannersAreImportantBasicMemoryScanningCheatEngine’sMemoryScanner
ScanTypesRunningYourFirstScanNextScansWhenYouCan’tGetaSingleResultCheatTables
MemoryModificationinGamesManualModificationwithCheatEngine
TrainerGeneratorPointerScanning
PointerChainsPointerScanningBasicsPointerScanningwithCheatEnginePointerRescanning
LuaScriptingEnvironmentSearchingforAssemblyPatternsSearchingforStrings
ClosingThoughts
2DEBUGGINGGAMESWITHOLLYDBGABriefLookatOllyDbg’sUserInterfaceOllyDbg’sCPUWindow
ViewingandNavigatingaGame’sAssemblyCodeViewingandEditingRegisterContentsViewingandSearchingaGame’sMemoryViewingaGame’sCallStack
CreatingCodePatchesTracingThroughAssemblyCodeOllyDbg’sExpressionEngine
UsingExpressionsinBreakpointsUsingOperatorsintheExpressionEngineWorkingwithBasicExpressionElementsAccessingMemoryContentswithExpressions
OllyDbgExpressionsinActionPausingExecutionWhenaSpecificPlayer’sNameIsPrintedPausingExecutionWhenYourCharacter’sHealthDrops
OllyDbgPlug-insforGameHackersCopyingAssemblyCodewithAsm2ClipboardAddingCheatEnginetoOllyDbgwithCheatUtilityControllingOllyDbgThroughtheCommandLineVisualizingControlFlowwithOllyFlow
ClosingThoughts
3RECONNAISSANCEWITHPROCESSMONITORANDPROCESSEXPLORERProcessMonitor
LoggingIn-GameEventsInspectingEventsintheProcessMonitorLogDebuggingaGametoCollectMoreData
ProcessExplorerProcessExplorer’sUserInterfaceandControlsExaminingProcessPropertiesHandleManipulationOptions
ClosingThoughts
PART2GAMEDISSECTION
4FROMCODETOMEMORY:AGENERALPRIMERHowVariablesandOtherDataManifestinMemory
NumericDataStringDataDataStructuresUnionsClassesandVFTables
x86AssemblyCrashCourseCommandSyntaxProcessorRegistersTheCallStackImportantx86InstructionsforGameHacking
ClosingThoughts
5ADVANCEDMEMORYFORENSICSAdvancedMemoryScanning
DeducingPurposeFindingthePlayer’sHealthwithOllyDbgDeterminingNewAddressesAfterGameUpdates
IdentifyingComplexStructuresinGameDataThestd::stringClassThestd::vectorClassThestd::listClassThestd::mapClass
ClosingThoughts
6READINGFROMANDWRITINGTOGAMEMEMORYObtainingtheGame’sProcessIdentifier
ObtainingProcessHandlesWorkingwithOpenProcess()
AccessingMemoryWorkingwithReadProcessMemory()andWriteProcessMemory()AccessingaValueinMemorywithReadProcessMemory()andWriteProcessMemory()WritingTemplatedMemoryAccessFunctions
MemoryProtectionDifferentiatingx86WindowsMemoryProtectionAttributesChangingMemoryProtection
AddressSpaceLayoutRandomizationDisablingASLRtoSimplifyBotDevelopmentBypassingASLRinProduction
ClosingThoughts
PART3PROCESSPUPPETEERING
7CODEINJECTIONInjectingCodeCaveswithThreadInjection
CreatinganAssemblyCodeCaveTranslatingtheAssemblytoShellcodeWritingtheCodeCavetoMemoryUsingThreadInjectiontoExecutetheCodeCave
HijackingaGame’sMainThreadtoExecuteCodeCavesBuildingtheAssemblyCodeCaveGeneratingSkeletonShellcodeandAllocatingMemoryFindingandFreezingtheMainThread
InjectingDLLsforFullControlTrickingaProcessintoLoadingYourDLLAccessingMemoryinanInjectedDLLBypassingASLRinanInjectedDLL
ClosingThoughts
8MANIPULATINGCONTROLFLOWINAGAMENOPingtoRemoveUnwantedCode
WhentoNOPHowtoNOP
HookingtoRedirectGameExecutionCallHookingVFTableHookingIATHookingJumpHooking
ApplyingCallHookstoAdobeAIR
AccessingtheRTMPGoldmineHookingtheRTMPSencode()FunctionHookingtheRTMPSdecode()FunctionPlacingtheHooks
ApplyingJumpHooksandVFHookstoDirect3DTheDrawingLoopFindingtheDirect3DDeviceWritingaHookforEndScene()WritingaHookforReset()What’sNext?
ClosingThoughts
PART4CREATINGBOTS
9USINGEXTRASENSORYPERCEPTIONTOWARDOFFFOGOFWARBackgroundKnowledgeRevealingHiddenDetailswithLighthacks
AddingaCentralAmbientLightSourceIncreasingtheAbsoluteAmbientLightCreatingOtherTypesofLighthacks
RevealingSneakyEnemieswithWallhacksRenderingwithZ-BufferingCreatingaDirect3DWallhackFingerprintingtheModelYouWanttoReveal
GettingaWiderFieldofVisionwithZoomhacksUsingNOPingZoomhacksScratchingtheSurfaceofHookingZoomhacks
DisplayingHiddenDatawithHUDsCreatinganExperienceHUD
UsingHookstoLocateDataAnOverviewofOtherESPHacksClosingThoughts
10RESPONSIVEHACKSObservingGameEvents
MonitoringMemoryDetectingVisualCuesInterceptingNetworkTraffic
PerformingIn-GameActionsEmulatingtheKeyboardSendingPackets
TyingthePiecesTogetherMakingthePerfectHealerResistingEnemyCrowd-ControlAttacksAvoidingWastedMana
ClosingThoughts
11PUTTINGITALLTOGETHER:WRITINGAUTONOMOUSBOTSControlTheoryandGameHackingStateMachinesCombiningControlTheoryandStateMachines
ABasicHealerStateMachineAComplexHypotheticalStateMachineErrorCorrection
PathfindingwithSearchAlgorithmsTwoCommonSearchTechniquesHowObstaclesDisruptSearchesAnA*SearchAlgorithmWhenA*SearchesAreParticularlyUseful
CommonandCoolAutomatedHacksLootingwithCavebotsAutomatingCombatwithWarbots
ClosingThoughts
12STAYINGHIDDENProminentAnti-CheatSoftwareThePunkBusterToolkit
Signature-BasedDetectionScreenshotsHashValidation
TheESEAAnti-CheatToolkitTheVACToolkit
DNSCacheScansBinaryValidationFalsePositives
TheGameGuardToolkitUser-ModeRootkitKernel-ModeRootkit
TheWardenToolkitCarefullyManagingaBot’sFootprint
MinimizingaBot’sFootprintMaskingYourFootprintTeachingaBottoDetectDebuggersAnti-DebuggingTechniques
DefeatingSignature-BasedDetectionDefeatingScreenshotsDefeatingBinaryValidationDefeatinganAnti-CheatRootkitDefeatingHeuristicsClosingThoughts
INDEX
FOREWORD
Nickisgreat.Wefirsthititoffinalltherightandwrongways,asyoucanimagine.I’vebeeninthesecurityfieldawhile;he’salittleyounger.I’vehadtheschooling,whereashe’snotmuchforcollege.I’mafaithguy,andhe’snot.Theinterestingthingisthatnoneofthatmatters;we’vehadablastanyway.Age,race,gender,degrees—whenitcomestogaming,hacking,andcoding,noonecares!
Nickgetsitdone.He’sfun.He’sbrilliant.He’shardworking.Andprobablymostpertinent:he’soneoftherarefewwhounderstandtheintersectionofgaming,hacking,andcoding.He’sworkedinthisnicheandcreatedprofitablebots.
Inthisfirst-of-its-kindbook,Nickwalksyouthroughwhatitmeanstopullapartgames.Heteachesyouthesoftwareinvestigationtoolsandtricksofthetrade.You’lllearnaboutgameinternals,howtopullthemapart,andhowtomodifyplay.Forexample,Nickteacheshowtoavoidanti-cheatsothatyoucanautomateplay.Wouldn’titbecooltohaveyourownbotthatcollectsexperience,gold,items,andmore—allwhileyou’reaway?
Everwonderhowthecheaterscheat?Everwantedtopatchorprotectyourgame?Grabacoffee,crackopenyourlaptop,andenjoy.
Blessingstoyouandyours,
Dr.JaredDeMottSecurityExpert&SoftwareBuilder
ACKNOWLEDGMENTS
Writingthisbookwasanamazingjourney,andIcouldn’thavedoneitalone.NoStarchPresshasbeenextremelysupportiveandworkedcloselywithmetotakethisbookfromconcepttoreality.Inparticular,I’dliketothankmydevelopmentaleditor,JenniferGriffith-Delgado,andmyproductioneditor,LaurelChun.BillPollock,TylerOrtman,AlisonLaw,andtherestoftheteamatNoStarcharewonderfulpeople,andI’mpleasedtohaveworkedwiththem.
ThankstocopyeditorRachelMonaghan,proofreaderPaulaL.Fleming,andtechnicalreviewerStephenLawler.ThanksalsotomyfriendsCavitt“synt4x”GloverandVadimKotov,whotookthetimetoskimsomechaptersbeforesubmission,andtoJaredDeMottforwritingthebook’sforeword.
I’dliketothankallofthepeopleonTPForumswhotookmeinwhenIwasjustanaivekidandhelpedmelearnhowtohackgames.Inparticular,IowemythankstoJoseph“jo3bingham”Bingham,IanObermiller,andjeremic,whoallhadasignificantinfluenceonmyprogressionasahacker,andtoTPForumsfounderJosh“Zyphrus”Hartzell,whohelpedmefindmyconfidenceandskillswhenmyfuturelookeditsbleakest.
Thanksalsotomyentireforumstaffandeverycustomerwhohaseverusedmybots.Andfinally,thankstomyfamily,friends,andcolleagues,whohavebeenfunandsupportiveandhelpedshapemeintothemanIamtoday.
INTRODUCTION
Acommonmisconceptionintheworldofonlinegamingistheideathattheonlygameyoucanplayistheoneinthetitle.Infact,gamehackersenjoyplayingthegamethathidesbehindthecurtain:acat-and-mousegameofwitsbetweenthemandthegamedevelopers.Whilegamehackersworktoreverseengineergamebinaries,automateaspectsofgameplay,andmodifygamingenvironments,gamedeveloperscombatthehacker-designedtools(normallyreferredtoasbots)usinganti-reversingtechniques,botdetectionalgorithms,andheuristicdatamining.
Asthebattlebetweengamehackersanddevelopershasprogressed,thetechnicalmethodsimplementedbybothparties—manyofwhichresembletechniquesutilizedbymalwaredevelopersandantivirusvendors—haveevolved,becomingmorecomplex.Thisbookhighlightsthefightputupbygamehackers,andtheadvancedmethodstheyhaveengineeredtomanipulategameswhilesimultaneouslyeludinggamedevelopersinthedarkcornersoftheirownsoftware.
Althoughthebookfocusesonteachingyoutodeveloptoolsthatwouldlikelybeconsideredanuisanceorevenmaliciousbygamingcompanies,you’llfindthatmanyofthetechniquesareusefulfordevelopmentoftoolsthatareperfectlybenignandneutral.Furthermore,theknowledgeofhowthesetechniquesareimplementediskeyforthegamedevelopersworkingtopreventtheiruse.
PrerequisitesfortheReaderThisbookdoesnotaimtoteachyousoftwaredevelopment,andthereforeassumesthatyouhave,atminimum,asolidsoftwaredevelopmentbackground.ThisbackgroundshouldincludefamiliaritywithnativeWindows-baseddevelopment,aswellaslightexperiencewithgamedevelopmentandmemorymanagement.Whiletheseskillswillbeenoughforyoutofollowthisbook,experiencewithx86assemblyandWindowsinternalswillensurethatdetailsofmoreadvancedimplementationsarenotlostonyou.
Furthermore,sincealltheadvancedhacksdiscussedinthisbookrelyoncodeinjection,anabilitytowritecodeinanativelanguagelikeCorC++isamust.AlloftheexamplecodeinthisbookiswritteninC++andcanbecompiledwithMicrosoftVisualC++ExpressEdition.(YoucandownloadMSVC++ExpressEditionfromhttp://www.visualstudio.com/en-US/products/visual-studio-express-vs.)
NOTE
Otherlanguagesthatcompiletonativecode,suchasDelphi,arealsocapableofinjection,butIwillnotdiscusstheminthisbook.
ABriefGameHackingHistorySincethedawnofonlinePCgamingintheearly1980s,anongoingwarofwitsbetweengamehackersandgamedevelopershasbeentakingplace.Thisseeminglyendlessstrugglehaspromptedgamedeveloperstodevotecountlesshourstowardpreventinghackersfromtakingtheirgamesapartandgreasingbetweenthegears.Thesehackers,whofightbackwiththeirsophisticatedstealthimplementations,havemanymotivations:customizedgraphics,betterperformance,easeofuse,autonomousplay,in-gameassetacquisition,and,ofcourse,real-lifeprofit.
Thelate1990sandearly2000swerethegoldenageofgamehacking,whenonlinePCgamesbecameadvancedenoughtodrawlargecrowdsbutwerestillsimpleenoughtoeasilyreverseengineerandmanipulate.Online
gamesthatcameoutduringthistime,suchasTibia(January1997),Runescape(January2001),andUltimaOnline(September1997),wereheavilytargetedbybotdevelopers.Thedevelopersofthesegamesandotherslikethemstillstruggletodaytocontrolthemassivecommunitiesofbotdevelopersandbotusers.Thegamedevelopers’lackofactionandthehackers’tenacityhavenotonlycompletelyshatteredtheeconomieswithinthegames,buthavealsoproducedathrivingfor-profitindustryfocusedaroundbotdevelopmentandbotdefense.
Intheyearssincethegoldenage,morematuregamecompaniesstartedtakingbotdefenseveryseriously.Thesecompaniesnowhavededicatedteamsfocusedondevelopingbotpreventionsystems,andmanyalsoviewbotsasalegalmatterandwillnothesitatetobanishplayerswhousebotsandsuethebotdeveloperswhoprovidedthem.Asaresult,manygamehackershavebeenforcedtodevelopadvancedstealthtechniquestokeeptheiruserssafe.
Thiswarwageson,andthenumbersonbothsidesofthefightwillcontinuetogrowasonlinegamingbecomesmoreprevalentoverthecomingyears.Majorgamedevelopersarepursuinghackerswithendlessdetermination,evenslammingsomegamehackinggiantswithmultimillion-dollarlawsuits.Thismeansthatgamehackerswhoareseriousabouttheirbusinessmusteithertargetsmallergamingcompanies,oranonymouslymarkettheirproductsfromtheshadowsinordertoescapeprosecution.Fortheforeseeablefuture,gamehackingandbotdevelopmentwillcontinuetogrowintoalargerandmorelucrativeindustryforthosegamehackersboldenoughtotaketherisks.
WhyHackGames?Asidefromitsobviousallureandchallengingnature,gamehackinghassomepracticalandprofitablepurposes.Everyday,thousandsofnoviceprogrammersexperimentwithsmall-scalegamehackingasawaytoautomatemonotonoustasksorperformmenialactions.ThesescriptkiddieswilluseautomationtoolslikeAutoItfortheirsmall,relativelyharmlesshacks.Ontheotherhand,professionalgamehackers,backedbytheirlargetoolkitsandyearsofprogrammingexperience,willdevotehundredsofhourstothedevelopmentofadvancedgamehacks.Thesetypesofgamehacks,whicharethefocusofthisbook,areoftencreatedwiththeintentofmaking
whicharethefocusofthisbook,areoftencreatedwiththeintentofmakinglargeamountsofmoney.
Gamingisahugeindustrythatgenerated$22.4billioninsalesin2014,accordingtotheEntertainmentSoftwareAssociation.Ofthetensofmillionsofplayerswhoplaygamesdaily,20percentplaymassivelymultiplayeronlinerole-playinggames(MMORPGs).TheseMMORPGsoftenhavethousandsofplayerswhotradevirtualgoodswithinthrivingin-gameeconomies.Playersoftenhaveaneedforin-gameassetsandarewillingtobuytheseassetswithreal-worldmoney.Consequently,MMORPGplayersendupdevelopinglargecommunitiesthatprovidegold-for-cashservices.Theseservicesoftengoasfarasenforcingexchangeratesfromin-gamegoldtoreal-worldcurrencies.
Totakeadvantageofthis,gamehackerswillcreatebotsthatarecapableofautomaticallyfarminggoldandlevelingcharacters.Then,dependingontheirgoal,hackerswilleithersetupmassivegoldfarmsandselltheirin-gameprofits,orperfectandselltheirsoftwaretoplayerswhowishtoseamlesslyobtainlevelsandgoldwithminimalinterference.DuetothemassivecommunitiessurroundingpopularMMORPGs,thesegamehackerscanmakebetweensixandsevenfiguresannually.
WhileMMORPGsprovidethelargestattacksurfaceforhackers,theyhavearelativelysmallaudienceoverall.About38percentofgamersfavorreal-timestrategy(RTS)andmassiveonlinebattlearena(MOBA)games,andanother6percentplayprimarilyfirst-personshooter(FPS)games.Thesecompetitiveplayerversusplayer(PvP)gamescollectivelyrepresent44percentofthegamingmarketandprovidegreatrewardstodeterminedgamehackers.
PvPgamesareoftenepisodicinnature;eachmatchisanisolatedgame,andthere’stypicallynotmuchprofitableprogressionforbottingawayfromkeyboard(AFK).Thismeansthat,insteadofrunninggoldfarmsorcreatingautonomousbotstolevelupcharacters,hackerswillcreatereactivebotsthatassistplayersincombat.
Thesehighlycompetitivegamesareaboutskillandtactics,andmostplayersparticipatetoprovetheirabilitytothemselvesandothers.Asaconsequence,thenumberofpeopleseekingbotsforPvP-typegamesissubstantiallylowerthanyou’dfindinthegrind-heavyworldofMMORPGs.Nevertheless,hackerscanstillmakeaprettypennysellingtheirPvPbots,whichareoftenmucheasiertodevelopthanfull-fledgedautonomousbots.
HowThisBookIsOrganizedThisbookissplitintofourparts,eachofwhichfocusesonadifferentcoreaspectofgamehacking.InPart1:ToolsoftheTrade,you’llgetaboxfulloftoolstohelpyouhackgames.
•Chapter1:ScanningMemoryUsingCheatEnginewillteachyouhowtoscanagame’smemoryforimportantvaluesusingCheatEngine.
•InChapter2:DebuggingGameswithOllyDbg,you’llgetacrashcourseindebuggingandreverseengineeringwithOllyDbg.Theskillsyoulearnherewillbeextremelyusefulwhenyoustartmakingadvancedbotsandinjectingcode.
•Towrapup,Chapter3:ReconnaissancewithProcessMonitorandProcessExplorer,willteachyouhowtousetworeconnaissancetoolstoinspecthowgamesinteractwithfiles,otherprocesses,thenetwork,andtheoperatingsystem.
TheonlineresourcesforeachchapterinPart1includecustombinariesIcreatedtogiveyouasafeplacetotestandhoneyournewlydiscoveredskills.
Onceyou’recomfortablewitheverywrenchandhammer,Part2:GameDissection,willteachyouhowtogetunderthehoodandfigureouthowgameswork.
•InChapter4:FromCodetoMemory:AGeneralPrimer,you’lllearnwhatagame’ssourcecodeanddatalooklikeoncecompiledintoagamebinary.
•Chapter5:AdvancedMemoryForensicsbuildsontheknowledgeyou’llgainfromChapter4.You’lllearnhowtoscanmemoryandusedebuggingtoseamlesslylocatetrickymemoryvaluesanddissectcomplexclassesandstructures.
•Finally,Chapter6:ReadingfromandWritingtoGameMemoryshowsyouhowtoreadandmodifydatawithinarunninggame.
Thesechaptersprovidelotsofin-depthproof-of-conceptexamplecodethatyoucanusetoverifyeverythingyouread.
InPart3:ProcessPuppeteering,you’llbecomeapuppeteerasyoulearnhowtoturnanygameintoamarionette.
•BuildingontheskillsfromParts1and2,Chapter7:CodeInjectiondescribeshowtoinjectandexecuteyourowncodeintheaddressspaceofagame.
•Onceyou’vemasteredinjection,Chapter8:ManipulatingControlFlowinaGamewillteachyouhowtouseinjectiontointercept,modify,ordisableanyfunctioncallmadebyagame,andwillwrapupwithsomeusefulreal-worldexamplesforthecommonlibrariesAdobeAIRandDirect3D.
Tocomplementyourpuppeteeringclasses,thesechaptersareaccompaniedbythousandsoflinesofproduction-readycodethatyoucanuseasaboilerplatelibraryforafuturebot.
InPart4:CreatingBots,you’llseehowtocombineyourtoolbox,dissectionabilities,puppeteeringskills,andsoftwareengineeringbackgroundtocreatepowerfulbots.
•Chapter9:UsingExtrasensoryPerceptiontoWardOffFogofWarexploreswaystomakeagamedisplayusefulinformationthatisn’texposedbydefault,suchasthelocationsofhiddenenemiesandtheamountofexperienceyouearnperhour.
•Chapter10:ResponsiveHacksshowscodepatternsyoucanusetodetectin-gameevents,likedecreasesinhealth,andtomakebotsthatreacttothoseeventsfasterthanhumanplayers.
•Chapter11:PuttingItAllTogether:WritingAutonomousBotsrevealshowbotsthatplaygameswithouthumaninteractionwork.Automatedbotscombinecontroltheory,statemachines,searchalgorithms,andmathematicalmodels,andthischapterisacrashcourseinthosetopics.
•InChapter12:StayingHidden,you’lllearnaboutsomeofthehigh-leveltechniquesyoucanusetoescapeandevadeanysystemthatwouldinterferewithyourbots.
Asyou’veprobablycometoexpect,thesechaptershavelotsofexamplecode.Someofthehacksshowninthispartarebuiltonexamplecodefrom
previouschapters.Othersexploresuccinct,straightforwarddesignpatternsyoucanusetocreateyourownbots.Onceyou’vefinishedallfourpartsofthisbook,you’llbesentoffintothevirtualworldwithyournewsuperpower.
AbouttheOnlineResourcesYou’llfindmanyadditionalresourcesforthisbookathttps://www.nostarch.com/gamehacking/.Theseresourcesincludecompiledbinariestotestyourskills,aconsiderableamountofexamplecode,andquiteafewsnippetsofproduction-readygamehackingcode.Theseresourcesgohand-in-handwiththebook,anditreallyisn’tcompletewithoutthem,somakesuretodownloadthembeforeyoucontinue.
HowtoUseThisBookThisbookshouldbeusedfirstandforemostasaguidetogetyoustartedingamehacking.Theprogressionissuchthatthecontentofeachchapterintroducesnewskillsandabilitiesthatbuildonallpreviouschapters.Asyoucompletechapters,Iencourageyoutoplaywiththeexamplecodeandtestyourskillsonarealgamebeforecontinuingyourreading.Thisisimportant,assomecoveredtopicswillhaveusecasesthatdon’tbecomeevidentuntilyou’re10feetdeepinthemud.
Onceyou’vefinishedthebook,Ihopeitcanstillbeusefultoyouasafieldmanual.Ifyoucomeacrosssomedatastructureyou’reunsureof,maybethedetailsinChapter5canhelp.Ifyoureverseengineeragame’smapformatandarereadytocreateapathfinder,youcanalwaysfliptoChapter11,studythecontent,andusesomeoftheexamplecodeasastartingpoint.Althoughit’simpossibletoanticipatealltheproblemsyoumightfacewhenyou’rehackingaway,I’vetriedtoensureyou’llfindsomeanswerswithinthesepages.
ANOTEFROMTHEPUBLISHERThisbookdoesnotcondonepiracy,violatingtheDMCA,infringingcopyright,orbreakingin-gameTermsofService.Gamehackershave
copyright,orbreakingin-gameTermsofService.Gamehackershavebeenbannedfromgamesforlife,suedformillionsofdollars,andevenjailedfortheirwork.
PART1TOOLSOFTHETRADE
1SCANNINGMEMORYUSINGCHEATENGINE
Thebestgamehackersintheworldspendyearspersonalizingexpansivearsenalswithcustom-builttools.Suchpotenttoolkitsenablethesehackerstoseamlesslyanalyzegames,effortlesslyprototypehacks,andeffectivelydevelopbots.Atthecore,however,eachuniquekitisbuiltfromthesamefour-piecepowerhouse:amemoryscanner,anassembler-leveldebugger,aprocessmonitor,andahexeditor.
Memoryscanningisthegatewaytogamehacking,andthischapterwillteachyouaboutCheatEngine,apowerfulmemoryscannerthatsearchesagame’soperatingmemory(whichlivesinRAM)forvaluesliketheplayer’slevel,health,orin-gamemoney.First,I’llfocusonbasicmemoryscanning,memorymodification,andpointerscanning.Followingthat,we’lldiveintoCheatEngine’spowerfulembeddedLuascriptingengine.
NOTE
YoucangrabCheatEnginefromhttp://www.cheatengine.org/.Payattentionwhenrunningtheinstallerbecauseitwilltrytoinstallsometoolbarsandotherbloatware.Youcandisablethoseoptionsifyouwish.
WhyMemoryScannersAreImportantKnowingagame’sstateisparamounttointeractingwiththegame
Knowingagame’sstateisparamounttointeractingwiththegameintelligently,butunlikehumans,softwarecan’tdeterminethestateofagamesimplybylookingatwhat’sonthescreen.Fortunately,underneathallofthestimuliproducedbyagame,acomputer’smemorycontainsapurelynumericrepresentationofthatgame’sstate—andprogramscanunderstandnumberseasily.Hackersusememoryscannerstofindthosevaluesinmemory,andthenintheirprograms,theyreadthememoryintheselocationstounderstandthegame’sstate.
Forexample,aprogramthathealsplayerswhentheyfallbelow500healthneedstoknowhowtodotwothings:trackaplayer’scurrenthealthandcastahealingspell.Theformerrequiresaccesstothegame’sstate,whilethelattermightonlyrequireabuttontobepressed.Giventhelocationwhereaplayer’shealthisstoredandthewaytoreadagame’smemory,theprogramwouldlooksomethinglikethispseudocode:
//dothisinsomeloophealth=readMemory(game,HEALTH_LOCATION)if(health<500)pressButton(HEAL_BUTTON)
AmemoryscannerallowsyoutofindHEALTH_LOCATIONsothatyoursoftwarecanqueryitforyoulater.
BasicMemoryScanningThememoryscanneristhemostbasic,yetmostimportant,toolfortheaspiringgamehacker.Asinanyprogram,alldatainthememoryofagameresidesatanabsolutelocationcalledamemoryaddress.Ifyouthinkofthememoryasaverylargebytearray,amemoryaddressisanindexpointingtoavalueinthatarray.Whenamemoryscanneristoldtofindsomevaluex(calledascanvalue,becauseit’sthevalueyou’rescanningfor)inagame’smemory,thescannerloopsthroughthebytearraylookingforanyvalueequaltox.Everytimeitfindsamatchingvalue,itaddstheindexofthematchtoaresultlist.
Duetothesheersizeofagame’smemory,however,thevalueofxcanappearinhundredsoflocations.Imaginethatxistheplayer’shealth,whichiscurrently500.Ourxuniquelyholds500,but500isnotuniquelyheldbyx,
soascanforxreturnsallvariableswithavalueof500.Anyaddressesnotrelatedtoxareultimatelyclutter;theyshareavalueof500withxonlybychance.Tofilterouttheseunwantedvalues,thememoryscannerallowsyoutorescantheresultlist,removingaddressesthatnolongerholdthesamevalueasx,whetherxisstill500orhaschanged.
Fortheserescanstobeeffective,theoverallstateofthegamemusthavesignificantentropy—ameasureofdisorder.Youincreaseentropybychangingthein-gameenvironment,oftenbymovingaround,killingcreatures,orswitchingcharacters.Asentropyincreases,unrelatedaddressesarelesslikelytocontinuetoarbitrarilyholdthesamevalue,andgivenenoughentropy,afewrescansshouldfilteroutallfalsepositivesandleaveyouwiththetrueaddressofx.
CheatEngine’sMemoryScannerThissectiongivesyouatourofCheatEngine’smemory-scanningoptions,whichwillhelpyoutrackdowntheaddressesofgamestatevaluesinmemory.I’llgiveyouachancetotrythescanneroutin“BasicMemoryEditing”onpage11;fornow,openCheatEngineandhavealookaround.Thememoryscanneristightlyencapsulatedinitsmainwindow,asshowninFigure1-1.
Figure1-1:CheatEnginemainscreen
Tobeginscanningagame’smemory,clicktheAttachicon➊toattachtoaprocessandthenenterthescanvalue(referredtoasxinourconceptualscanner)youwanttolocate➌.Byattachingtoaprocess,we’retellingCheatEnginetopreparetooperateonit;inthiscase,thatoperationisascan.IthelpstoalsotellCheatEnginewhatkindofscantorun,asI’lldiscussnext.
ScanTypesCheatEngineallowsyoutoselecttwodifferentscandirectives,calledScanTypeandValueType➍.ScanTypetellsthescannerhowtocompareyourscanvaluewiththememorybeingscannedusingoneofthefollowingscantypes:
ExactValueReturnsaddressespointingtovaluesequaltothescanvalue.Choosethisoptionifthevalueyouarelookingforwon’tchangeduringthescan;health,mana,andleveltypicallyfallintothiscategory.
BiggerThanReturnsaddressespointingtovaluesgreaterthanthescanvalue.Thisoptionisusefulwhenthevalueyou’researchingforissteadilyincreasing,whichoftenhappenswithtimers.
SmallerThanReturnsaddressespointingtovaluessmallerthanthescanvalue.LikeBiggerThan,thisoptionisusefulforfindingtimers(inthiscase,onesthatcountdownratherthanup).
ValueBetweenReturnsaddressespointingtovalueswithinascanvaluerange.ThisoptioncombinesBiggerThanandSmallerThan,displayingasecondaryscanvalueboxthatallowsyoutoinputamuchsmallerrangeofvalues.
UnknownInitialValueReturnsalladdressesinaprogram’smemory,allowingrescanstoexaminetheentireaddressrangerelativetotheirinitialvalues.Thisoptionisusefulforfindingitemorcreaturetypes,sinceyouwon’talwaysknowtheinternalvaluesthegamedevelopersusedtorepresenttheseobjects.
TheValueTypedirectivetellstheCheatEnginescannerwhattypeofvariableit’ssearchingfor.
RunningYourFirstScanOncethetwoscandirectivesareset,clickFirstScan➋torunaninitialscanforvalues,andthescannerwillpopulatetheresultslist➎.Anygreenaddressesinthislistarestatic,meaningthattheyshouldremainpersistentacrossprogramrestarts.Addresseslistedinblackresideindynamicallyallocatedmemory,memorythatisallocatedatruntime.
Whentheresultslistisfirstpopulated,itshowstheaddressandreal-timevalueofeachresult.Eachrescanwillalsoshowthevalueofeachresultduringthepreviousscan.(Anyreal-timevaluesdisplayedareupdatedatanintervalthatyoucansetinEdit▸Settings▸GeneralSettings▸Updateinterval.)
NextScansOncetheresultslistispopulated,thescannerenablestheNextScan➋button,whichofferssixnewscantypes.Theseadditionalscantypesallowyoutocomparetheaddressesintheresultslisttotheirvaluesinthepreviousscan,whichwillhelpyounarrowdownwhichaddressholdsthegamestatevalueyou’rescanningfor.Theyareasfollows:
IncreasedValueReturnsaddressespointingtovaluesthathaveincreased.ThiscomplementstheBiggerThanscantypebykeepingthesameminimumvalueandremovinganyaddresswhosevaluehasdecreased.
IncreasedValueByReturnsaddressespointingtovaluesthathaveincreasedbyadefinedamount.Thisscantypeusuallyreturnsfarfewerfalsepositives,butyoucanuseitonlywhenyouknowexactlyhowmuchavaluehasincreased.
DecreasedValueThisoptionistheoppositeofIncreasedValue.
DecreasedValueByThisoptionistheoppositeofIncreasedValueBy.
ChangedValueReturnsaddressespointingtovaluesthathavechanged.Thistypeisusefulwhenyouknowavaluewillmutate,butyou’reunsurehow.
UnchangedValueReturnsaddressespointingtovaluesthathaven’tchanged.Thiscanhelpyoueliminatefalsepositives,sinceyoucaneasilycreatealargeamountofentropywhileensuringthedesiredvaluestaysthesame.
You’llusuallyneedtousemultiplescantypesinordertonarrowdownalargeresultlistandfindthecorrectaddress.Eliminatingfalsepositivesisoftenamatterofproperlycreatingentropy(asdescribedin“BasicMemoryScanning”onpage4),tacticallychangingyourscandirectives,bravelypressingNextScan,andthenrepeatingtheprocessuntilyouhaveasingleremainingaddress.
WhenYouCan’tGetaSingleResultSometimesitisimpossibletopinpointasingleresultinCheatEngine,in
whichcaseyoumustdeterminethecorrectaddressthroughexperimentation.Forexample,ifyou’relookingforyourcharacter’shealthandcan’tnarrowitdowntofewerthanfiveaddresses,youcouldtrymodifyingthevalueofeachaddress(asdiscussedin“ManualModificationwithCheatEngine”onpage8)untilyouseethehealthdisplaychangeortheothervaluesautomaticallychangetotheoneyouset.
CheatTablesOnceyou’vefoundthecorrectaddress,youcandouble-clickittoaddittothecheattablepane➏;addressesinthecheattablepanecanbemodified,watched,andsavedtocheattablefilesforfutureuse.
Foreachaddressinthecheattablepane,youcanaddadescriptionbydouble-clickingtheDescriptioncolumn,andyoucanaddacolorbyright-clickingandselectingChangeColor.Youcanalsodisplaythevaluesofeachaddressinhexadecimalordecimalformatbyright-clickingandselectingShowashexadecimalorShowasdecimal,respectively.Lastly,youcanchangethedatatypeofeachvaluebydouble-clickingtheTypecolumn,oryoucanchangethevalueitselfbydouble-clickingtheValuecolumn.
Sincethemainpurposeofthecheattablepaneistoallowagamehackertoneatlytrackaddresses,itcanbedynamicallysavedandloaded.GotoFile▸SaveorFile▸SaveAstosavethecurrentcheattablepanetoa.ctdocumentfilecontainingeachaddresswithitsvaluetype,description,displaycolor,anddisplayformat.Toloadthesaved.ctdocuments,gotoFile▸Load.(You’llfindmanyready-madecheattablesforpopulargamesathttp://cheatengine.org/tables.php.)
NowthatI’vedescribedhowtoscanforagamestatevalue,I’lldiscusshowyoucanchangethatvaluewhenyouknowwhereitlivesinmemory.
MemoryModificationinGamesBotscheatagamesystembymodifyingmemoryvaluesinthegame’sstateinordertogiveyoulotsofin-gamemoney,modifyyourcharacter’shealth,changeyourcharacter’sposition,andsoon.Inmostonlinegames,acharacter’svitals(suchashealth,mana,skills,andposition)areheldinmemorybutarecontrolledbythegameserverandrelayedtoyourlocal
memorybutarecontrolledbythegameserverandrelayedtoyourlocalgameclientovertheInternet,somodifyingsuchvaluesduringonlineplayismerelycosmeticanddoesn’taffecttheactualvalues.(Anyusefulmemorymodificationtoanonlinegamerequiresamuchmoreadvancedhackthat’sbeyondCheatEngine’scapabilities.)Inlocalgameswithnoremoteserver,however,youcanmanipulateallofthesevaluesatwill.
ManualModificationwithCheatEngineWe’lluseCheatEnginetounderstandhowthememorymodificationmagicworks.
Tomodifymemorymanually,dothefollowing:
1. AttachCheatEnginetoagame.2. Eitherscanfortheaddressyouwishtomodifyorloadacheattablethat
containsit.3. Double-clickontheValuecolumnfortheaddresstoopenaninput
promptwhereyoucanenteranewvalue.4. Ifyouwanttomakesurethenewvaluecan’tbeoverwritten,selectthe
boxundertheActivecolumntofreezetheaddress,whichwillmakeCheatEnginekeepwritingthesamevaluebacktoiteverytimeitchanges.
Thismethodworkswondersforquick-and-dirtyhacks,butconstantlychangingvaluesbyhandiscumbersome;anautomatedsolutionwouldbemuchmoreappealing.
TrainerGeneratorCheatEngine’strainergeneratorallowsyoutoautomatethewholememorymodificationprocesswithoutwritinganycode.
Tocreateatrainer(asimplebotthatbindsmemorymodificationactionstokeyboardhotkeys),gotoFile▸CreategenerictrainerLuascriptfromtable.ThisopensaTrainergeneratordialogsimilartotheoneshowninFigure1-2.
Figure1-2:CheatEngineTrainergeneratordialog
Thereareanumberoffieldstomodifyhere:
ProcessnameThenameoftheexecutablethetrainershouldattachto.ThisisthenameshownintheprocesslistwhenyouattachwithCheatEngine,anditshouldbeautofilledwiththenameoftheprocessCheatEngineisattachedto.
PopuptraineronkeypressOptionallyenablesahotkey—whichyousetbyenteringakeycombinationintheboxbelowthecheckbox—todisplaythetrainer’smainwindow.
TitleThenameofyourtrainer,whichwillbedisplayedonitsinterface.Thisisoptional.
AbouttextThedescriptionofyourtrainer,tobedisplayedontheinterface;thisisalsooptional.
Freezeinterval(inmilliseconds)Theintervalduringwhichafreezeoperationoverwritesthevalue.Youshouldgenerallyleavethisat250,aslowerintervalscansapresourcesandhighervaluesmaybetooslow.
Oncethesevaluesareconfigured,clickAddHotkeytosetupakeysequencetoactivateyourtrainer.Youwillbepromptedtoselectavalue
fromyourcheattable.Enteravalue,andyouwillbetakentoaSet/ChangehotkeyscreensimilartoFigure1-3.
Figure1-3:CheatEngineSet/Changehotkeyscreen
Onthisscreen,placeyourcursorintheboxlabeledTypethekeysyouwanttosetthehotkeyto➊andenterthedesiredkeycombination.Next,choosethedesiredactionfromthedrop-downmenu➋;youroptionsshouldappearinthefollowingorder:
TogglefreezeTogglesthefreezestateoftheaddress.
TogglefreezeandallowincreaseTogglesthefreezestateoftheaddressbutallowsthevaluetoincrease.Anytimethevaluedecreases,thetraineroverwritesitwithitspreviousvalue.Increasedvalueswillnotbeoverwritten.
TogglefreezeandallowdecreaseDoestheoppositeofTogglefreezeandallowincrease.
FreezeSetstheaddresstofrozenifit’snotfrozenalready.
UnfreezeUnfreezestheaddressifit’sfrozen.
SetvaluetoSetsthevaluetowhateveryouspecifyinthevaluebox➌.
DecreasevaluewithDecreasesthevaluebytheamountyouspecifyinthevaluebox➌.
IncreasevaluewithDoestheoppositeofDecreasevaluewith.
Finally,youcansetadescriptionfortheaction➍.ClickApply,thenOK,andyouractionwillappearinthelistontheTrainergeneratorscreen.Atthispoint,CheatEnginerunsthetrainerinthebackground,andyoucansimplypressthehotkeysyouconfiguredtoexecutethememoryactions.
Tosaveyourtrainertoaportableexecutable,clickGeneratetrainer.RunningthisexecutableafterthegameislaunchedwillattachyourtrainertothegamesoyoucanuseitwithoutstartingCheatEngine.
NowthatyouknowyourwayaroundCheatEngine’smemoryscannerandtrainergenerator,trymodifyingsomememoryyourself.
BASICMEMORYEDITINGDownloadthefilesforthisbookfromhttps://www.nostarch.com/gamehacking/,andrunthefileBasicMemory.exe.Next,startupCheatEngineandattachtothebinary.Then,usingonlyCheatEngine,findtheaddressesforthex-andy-coordinatesofthegrayball.(Hint:Usethe4Bytesvaluetype.)
Onceyou’vefoundthevalues,modifythemtoplacetheballontopoftheblacksquare.Thegamewillletyouknowonceyou’vesucceededbydisplayingthetext“Goodjob!”(Hint:Eachtimetheballismoved,itsposition—storedasa4-byteinteger—inthatplaneischangedby1.Also,trytolookonlyforstatic[green]results.)
PointerScanningAsI’vementioned,onlinegamesoftenstorevaluesindynamicallyallocatedmemory.Whileaddressesthatreferencedynamicmemoryareuselesstous
inandofthemselves,somestaticaddresswillalwayspointtoanotheraddress,whichinturnpointstoanother,andsoon,untilthetailofthechainpointstothedynamicmemorywe’reinterestedin.CheatEnginecanlocatethesechainsusingamethodcalledpointerscanning.
Inthissection,I’llintroduceyoutopointerchainsandthendescribehowpointerscanningworksinCheatEngine.Whenyouhaveagoodgraspoftheuserinterface,youcangetsomehands-onexperiencein“PointerScanning”onpage18.
PointerChainsThechainofoffsetsI’vejustdescribediscalledapointerchainandlookslikethis:
list<int>chain={start,offset1,offset2[,...]}
Thefirstvalueinthispointerchain(start)iscalledamemorypointer.It’sanaddressthatstartsthechain.Theremainingvalues(offset1,offset2,andsoon)makeuptheroutetothedesiredvalue,calledapointerpath.
Thispseudocodeshowshowapointerchainmightberead:
intreadPointerChain(chain){➊ret=read(chain[0])fori=1,chain.len-1,1{offset=chain[i]ret=read(ret+offset)}returnret}
ThiscodecreatesthefunctionreadPointerPath(),whichtakesapointerchaincalledchainasaparameter.ThefunctionreadPointerPath()treatsthepointerpathinchainasalistofmemoryoffsetsfromtheaddressret,whichisinitiallysettothememorypointerat➊.Itthenloopsthroughtheseoffsets,updatingthevalueofrettotheresultofread(ret+offset)oneachiterationandreturningretonceit’sfinished.ThispseudocodeshowswhatreadPointerPath()lookslikewhentheloopisunrolled:
list<int>chain={0xDEADBEEF,0xAB,0x10,0xCC}value=readPointerPath(chain)//thefunctioncallunrollstothisret=read(0xDEADBEEF)//chain[0]
ret=read(0xDEADBEEF)//chain[0]ret=read(ret+0xAB)ret=read(ret+0x10)ret=read(ret+0xCC)intvalue=ret
Thefunctionultimatelycallsreadfourtimes,onfourdifferentaddresses—oneforeachelementinchain.
NOTE
Manygamehackersprefertocodetheirchainreadsinplace,insteadofencapsulatingtheminfunctionslikereadPointerPath().
PointerScanningBasicsPointerchainsexistbecauseeverychunkofdynamicallyallocatedmemorymusthaveacorrespondingstaticaddressthatthegame’scodecanusetoreferenceit.Gamehackerscanaccessthesechunksbylocatingthepointerchainsthatreferencethem.Becauseoftheirmultitierstructure,however,pointerchainscannotbelocatedthroughthelinearapproachthatmemoryscannersuse,sogamehackershavedevisednewwaystofindthem.
Fromareverseengineeringperspective,youcouldlocateandanalyzetheassemblycodeinordertodeducewhatpointerpathitusedtoaccessthevalue,butdoingsoisverytime-consumingandrequiresadvancedtools.Pointerscannerssolvethisproblembyusingbrute-forcetorecursivelyiterateovereverypossiblepointerchainuntiltheyfindonethatresolvestothetargetmemoryaddress.
TheListing1-1pseudocodeshouldgiveyouageneralideaofhowapointerscannerworks.
list<int>pointerScan(target,maxAdd,maxDepth){➊foraddress=BASE,0x7FFFFFF,4{ret=rScan(address,target,maxAdd,maxDepth,1)if(ret.len>0){ret.pushFront(address)returnret}}return{}}
list<int>rScan(address,target,maxAdd,maxDepth,curDepth){➋foroffset=0,maxAdd,4{value=read(address+offset)➌if(value==target)returnlist<int>(offset)}➍if(curDepth<maxDepth){curDepth++➎foroffset=0,maxAdd,4{ret=rScan(address+offset,target,maxAdd,maxDepth,curDepth)➏if(ret.len>0){ret.pushFront(offset)➐returnret}}}return{}}
Listing1-1:Pseudocodeforapointerscanner
ThiscodecreatesthefunctionspointerScan()andrScan().
pointerScan()ThepointerScan()functionistheentrypointtothescan.Ittakestheparameterstarget(thedynamicmemoryaddresstofind),maxAdd(themaximumvalueofanyoffset),andmaxDepth(themaximumlengthofthepointerpath).Itthenloopsthroughevery4-bytealignedaddress➊inthegame,callingrScan()withtheparametersaddress(theaddressinthecurrentiteration),target,maxAdd,maxDepth,andcurDepth(thedepthofthepath,whichisalways1inthiscase).
rScan()TherScan()functionreadsmemoryfromevery4-bytealignedoffsetbetween0andmaxAdd➋,andreturnsifaresultisequaltotarget➌.IfrScan()doesn’treturninthefirstloopandtherecursionisnottoodeep➍,itincrementscurDepthandagainloopsovereachoffset➎,callingitselfforeachiteration.
Ifaselfcallreturnsapartialpointerpath➏,rScan()willprependthe
currentoffsettothepathandreturnuptherecursionchain➐untilitreachespointerScan().WhenacalltorScan()frompointerScan()returnsapointerpath,pointerScan()pushesthecurrentaddresstothefrontofthepathandreturnsitasacompletechain.
PointerScanningwithCheatEngineThepreviousexampleshowedthebasicprocessofpointerscanning,buttheimplementationI’veshownisprimitive.Asidefrombeinginsanelyslowtoexecute,itwouldgeneratecountlessfalsepositives.CheatEngine’spointerscannerusesanumberofadvancedinterpolationstospeedupthescanandmakeitmoreaccurate,andinthissection,I’llintroduceyoutothesmorgasbordofavailablescanningoptions.
ToinitiateapointerscaninCheatEngine,right-clickonadynamicmemoryaddressinyourcheattableandclickPointerscanforthisaddress.Whenyouinitiateapointerscan,CheatEnginewillaskyouwheretostorethescanresultsasa.ptrfile.Onceyouenteralocation,aPointerscannerscanoptionsdialogsimilartotheoneshowninFigure1-4willappear.
Figure1-4:CheatEnginePointerscannerscanoptionsdialog
TheAddresstofindinputfieldatthetopdisplaysyourdynamicmemoryaddress.NowcarefullyselectfromamongCheatEngine’smanyscanoptions.
KeyOptionsSeveralofCheatEngine’sscanoptionstypicallyretaintheirdefaultvalues.Thoseoptionsareasfollows:
Addressesmustbe32-bitsalignedTellsCheatEnginetoscanonlyaddressesthataremultiplesof4,whichgreatlyincreasesthescanspeed.
Asyou’lllearninChapter4,compilersaligndatasothatmostaddresseswillbemultiplesof4anywaybydefault.You’llrarelyneedtodisablethisoption.
OnlyfindpathswithastaticaddressSpeedsupthescanbypreventingCheatEnginefromsearchingpathswithadynamicstartpointer.Thisoptionshouldalwaysbeenabledbecausescanningforapathstartingatanotherdynamicaddresscanbecounterproductive.
Don’tincludepointerswithread-onlynodesShouldalsoalwaysbeenabled.Dynamicallyallocatedmemorythatstoresvolatiledatashouldneverberead-only.
StoptraversingapathwhenastatichasbeenfoundTerminatesthescanwhenitfindsapointerpathwithastaticstartaddress.Thisshouldbeenabledtoreducefalsepositivesandspeedupthescan.
PointerpathmayonlybeinsidethisregionCantypicallybeleftasis.Theotheroptionsavailabletoyoucompensateforthislargerangebyintelligentlynarrowingthescopeofthescan.
FirstelementofpointerstructmustpointtomoduleTellsCheatEnginenottosearchheapchunksinwhichvirtualfunctiontablesarenotfound,undertheassumptionthatthegamewascodedusingobjectorientation.Whilethissettingcanimmenselyspeedupscans,it’shighlyunreliableandyoushouldalmostalwaysleaveitdisabled.
NoloopingpointersInvalidatesanypathsthatpointtothemselves,weedingoutinefficientpathsbutslightlyslowingdownthescan.Thisshouldusuallybeenabled.
MaxlevelDeterminesthemaximumlengthofthepointerpath.(RememberthemaxDepthvariableintheexamplecodeinListing1-1?)Thisshouldbekeptaround6or7.Ofcourse,therewillbetimeswhenyou’llneedtochangetheseoptions
fromthesettingsdescribed.Forexample,failingtoobtainreliableresultswiththeNoloopingpointersorMaxlevelsettingstypicallymeansthatthevalueyou’relookingforexistsinadynamicdatastructure,likealinkedlist,binarytree,orvector.AnotherexampleistheStoptraversingapathwhenastatichasbeenfoundoption,whichinrarecasescanpreventyoufromgettingreliableresults.
SituationalOptionsUnlikethepreviousoptions,yoursettingsfortheremainingoneswilldependonyoursituation.Here’showtodeterminethebestconfigurationforeach:
ImprovepointerscanwithgatheredheapdataAllowsCheatEnginetousetheheapallocationrecordtodetermineoffsetlimits,effectivelyspeedingupthescanbyweedingoutmanyfalsepositives.Ifyourunintoagameusingacustommemoryallocator(whichisbecomingincreasinglycommon),thisoptioncanactuallydotheexactoppositeofwhatit’smeanttodo.Youcanleavethissettingenabledininitialscans,butitshouldbethefirsttogowhenyou’reunabletofindreliablepaths.
OnlyallowstaticandheapaddressesinthepathInvalidatesallpathsthatcan’tbeoptimizedwithheapdata,makingthisapproachevenmoreaggressive.
MaxdifferentoffsetspernodeLimitsthenumberofsame-valuepointersthescannerchecks.Thatis,ifndifferentaddressespointto0x0BADF00D,thisoptiontellsCheatEnginetoconsideronlythefirstmaddresses.Thiscanbeextremelyhelpfulwhenyou’reunabletonarrowdownyourresultset.Inothercases,youmaywanttodisableit,asitwillmissmanyvalidpaths.
Allowstackaddressesofthefirstthread(s)tobehandledasstaticScansthecallstacksofoldestmthreadsinthegame,consideringthefirstnbytesineachone.ThisallowsCheatEnginetoscantheparametersandlocalvariablesoffunctionsinthegame’scallchain(thegoalbeingtofindvariablesusedbythegame’smainloop).Thepathsfoundwiththisoptioncanbebothhighlyvolatileandextremelyuseful;IuseitonlywhenIfailtofindheapaddresses.
StackaddressesasonlystaticaddressTakesthepreviousoptionevenfurtherbyallowingonlystackaddressesinpointerpaths.
PointersmustendwithspecificoffsetsCanbeusefulifyouknowtheoffset(s)attheendofavalidpath.Thisoptionwillallowyoutospecifythoseoffsets(startingwiththelastoffsetatthetop),greatlyreducingthescopeofthescan.
NrofthreadsscanningDetermineshowmanythreadsthescannerwill
use.Anumberequaltothenumberofcoresinyourprocessoroftenworksbest.Adrop-downmenuwithoptionsallowsyoutospecifythepriorityforeachthread.Idleisbestifyouwantyourscantogoveryslowly,Normaliswhatyoushoulduseformostscans,andTimecriticalisusefulforlengthyscansbutwillrenderyourcomputeruselessforthescanduration.
MaximumoffsetvalueDeterminesthemaximumvalueofeachoffsetinthepath.(RememberthemaxAddvariableinListing1-1?)Itypicallystartwithalowvalue,increasingitonlyifmyscanfails;128isagoodstartingvalue.Keepinmindthatthisvalueismostlyignoredifyou’reusingtheheapoptimizationoptions.
NOTE
WhatifbothOnlyallowstaticandheapaddressesinthepathandStackaddressesasonlystaticaddressareenabled?Willthescancomeupempty?Seemslikeafun,albeituseless,experiment.
Onceyouhavedefinedyourscanoptions,clickOKtostartapointerscan.Whenthescancompletes,aresultswindowwillappearwiththelistofpointerchainsfound.Thislistoftenhasthousandsofresults,containingbothrealchainsandfalsepositives.
PointerRescanningThepointerscannerhasarescanfeaturethatcanhelpyoueliminatefalsepositives.Tobegin,pressCTRL-RfromtheresultswindowtoopentheRescanpointerlistdialog,asshowninFigure1-5.
Figure1-5:CheatEngineRescanpointerlistdialog
TherearetwomainoptionstoconsiderwhenyoutellCheatEnginetorescan:
OnlyfilteroutinvalidpointersIfyoucheckthisbox➊,therescanwilldiscardonlypointerchainsthatpointtoinvalidmemory,whichhelpsifyourinitialresultsetisverylarge.Disablethistofilteroutpathsthatdon’tresolvetoaspecificaddressorvalue(asshowninthefigure).
RepeatrescanuntilstoppedIfyoucheckthisbox➋,therescanwillexecuteinacontinuousloop.Ideally,youshouldenablethissettingandletrescanrunwhileyoucreatealargeamountofmemoryentropy.
Fortheinitialrescan,enablebothOnlyfilteroutinvalidpointersandRepeatrescanuntilstopped,andthenpressOKtoinitiatetherescan.Therescanwindowwillgoaway,andaStoprescanloopbuttonwillappearintheresultswindow.TheresultlistwillbeconstantlyrescanneduntilyouclickStoprescanloop,butspendafewminutescreatingmemoryentropybeforedoingso.
Inrarecases,rescanningusingarescanloopmaystillleaveyouwithalargelistofpossiblepaths.Whenthishappens,youmayneedtorestartthegame,findtheaddressthatholdsyourvalue(itmayhavechanged!),andusetherescanfeatureonthisaddresstofurthernarrowresults.Inthisscan,leaveOnlyfilteroutinvalidpointersuncheckedandenterthenewaddress
intheAddresstofindfield.
NOTE
Ifyouhadtoclosetheresultswindow,youcanreopenitandloadtheresultlistbygoingtothemainCheatEnginewindowandpressingtheMemoryViewbuttonbelowtheresultspane.Thisshouldbringupamemorydumpwindow.Whenthewindowappears,pressCTRL-Ptoopenthepointerscanresultslist.ThenpressCTRL-Otoopenthe.ptrfilewhereyousavedthepointerscan.
Ifyourresultsstillaren’tnarrowenough,tryrunningthesamescanacrosssystemrestartsorevenondifferentsystems.Ifthisstillyieldsalargeresultset,eachresultcansafelybeconsideredstaticbecausemorethanonepointerchaincanresolvetothesameaddress.
Onceyou’venarroweddownyourresultset,double-clickonausablepointerchaintoaddittoyourcheattable.Ifyouhaveahandfulofseeminglyusablechains,grabtheonewiththefewestoffsets.Ifyoufindmultiplechainswithidenticaloffsetsthatstartwiththesamepointerbutdivergeafteracertainpoint,yourdatamaybestoredinadynamicdatastructure.
That’sallthereistopointerscanninginCheatEngine.Tryityourself!
POINTERSCANNINGGotohttps://www.nostarch.com/gamehacking/anddownloadMemoryPointers.exe.Unlikethelasttask,whichrequiredyoutowinonlyonce,thisonerequiresthatyouwin50timesin10seconds.Uponeachwin,thememoryaddressesforthex-andy-coordinateswillchange,meaningyouwillbeabletofreezethevalueonlyifyouhavefoundaproperpointerpath.Startthisexercisethesamewayasthepreviousone,butonceyou’vefoundtheaddresses,usethePointerscanfeaturetolocatepointerpathstothem.Then,placetheballontopoftheblacksquare,freezethevalueinplace,andpressTABtobeginthetest.Justasbefore,thegamewillletyouknowonceyou’vewon.(Hint:Trysettingthemaximumlevelto5andthe
maximumoffsetvalueto512.Also,playwiththeoptionstoallowstackaddresses,terminatethescanwhenastaticisfound,andimprovethepointerscanwithheapdata.Seewhichcombinationofoptionsgivesthebestresults.)
LuaScriptingEnvironmentHistorically,botdevelopersrarelyusedCheatEnginetoupdatetheiraddresseswhenagamereleasedapatchbecauseitwasmucheasiertodosoinOllyDbg.ThismadeCheatEngineuselesstogamehackersotherthanforinitialresearchanddevelopment—thatis,untilapowerfulLua-basedembeddedscriptingenginewasimplementedaroundCheatEngine’srobustscanningenvironment.WhilethisenginewascreatedtoenablethedevelopmentofsimplebotswithinCheatEngine,professionalgamehackersfoundtheycouldalsouseittoeasilywritecomplexscriptstoautomaticallylocateaddressesacrossdifferentversionsofagame’sbinary—ataskthatmightotherwisetakehours.
NOTE
You’llfindmoredetailabouttheCheatEngineLuascriptingengineonthewikiathttp://wiki.cheatengine.org/.
TostartusingtheLuaengine,pressCTRL-ALT-LfromthemainCheatEnginewindow.Oncethewindowopens,writeyourscriptinthetextareaandclickExecutescripttorunit.SaveascriptwithCTRL-SandopenasavedscriptwithCTRL-O.
Thescriptingenginehashundredsoffunctionsandinfiniteusecases,soI’llgiveyoujustaglimpseofitsabilitiesbybreakingdowntwoscripts.Everygameisdifferentandeverygamehackerwritesscriptstoaccomplishuniquegoals,sothesescriptsareonlyusefulfordemonstratingconcepts.
SearchingforAssemblyPatternsThisfirstscriptlocatesfunctionsthatcomposeoutgoingpacketsandsendsthemtothegameserver.Itworksbysearchingagame’sassemblycodefor
themtothegameserver.Itworksbysearchingagame’sassemblycodeforfunctionsthatcontainacertaincodesequence.
➊BASEADDRESS=getAddress("Game.exe")➋functionLocatePacketCreation(packetType)➌foraddress=BASEADDRESS,(BASEADDRESS+0x2ffffff)dolocalpush=readBytes(address,1,false)localtype=readInteger(address+1)localcall=readInteger(address+5)➍if(push==0x68andtype==packetTypeandcall==0xE8)thenreturnaddressendendreturn0endFUNCTIONHEADER={0xCC,0x55,0x8B,0xEC,0x6A}➎functionLocateFunctionHead(checkAddress)if(checkAddress==0)thenreturn0end➏foraddress=checkAddress,(checkAddress-0x1fff),-1dolocalmatch=truelocalcheckheader=readBytes(address,#FUNCTIONHEADER,true)➐fori,vinipairs(FUNCTIONHEADER)doif(v~=checkheader[i])thenmatch=falsebreakendend➑if(match)thenreturnaddress+1endendreturn0end
➒localfuncAddress=LocateFunctionHead(LocatePacketCreation(0x64))if(funcAddress~=0)thenprint(string.format("0x%x",funcAddress))elseprint("Notfound!")end
ThecodebeginsbygettingthebaseaddressofthemodulethatCheatEngineisattachedto➊.Onceithasthebaseaddress,thefunctionLocatePacketCreation()isdefined➋.Thisfunctionloopsthroughthefirst0x2FFFFFFbytesofmemoryinthegame➌,searchingforasequencethatrepresentsthisx86assemblercode:
PUSHtype;Datais:0x68[4bytetype]CALLoffset;Datais:0xE8[4byteoffset]
ThefunctionchecksthatthetypeisequaltopacketType,butitdoesn’tcarewhatthefunctionoffsetis➍.Oncethissequenceisfound,thefunctionreturns.
Next,theLocateFunctionHead()functionisdefined➎.Thefunctionbacktracksupto0x1FFFbytesfromagivenaddress➏,andateachaddress,itchecksforastubofassemblercode➐thatlookssomethinglikethis:
INT3;0xCCPUSHEBP;0x55MOVEBP,ESP;0x8B0xECPUSH[-1];0x6A0xFF
Thisstubwillbepresentatthebeginningofeveryfunction,becauseit’spartofthefunctionprologuethatsetsupthefunction’sstackframe.Onceitfindsthecode,thefunctionwillreturntheaddressofthestubplus1➑(thefirstbyte,0xCC,ispadding).
Totiethesestepstogether,theLocatePacketCreation()functioniscalledwiththepacketTypethatI’mlookingfor(arbitrarily0x64)andtheresultingaddressispassedintotheLocateFunctionHead()function➒.ThiseffectivelylocatesthefirstfunctionthatpushespacketTypeintoafunctioncallandstoresitsaddressinfuncAddress.Thisstubshowstheresult:
INT3;LocateFunctionHeadback-trackedtoherePUSHEBP;andreturnedthisaddressMOVEBP,ESPPUSH[-1]--snip--PUSH[0x64];LocatePacketCreationreturnedthisaddressCALL[something]
This35-linescriptcanautomaticallylocate15differentfunctionsinunderaminute.
SearchingforStringsThisnextLuascriptscansagame’smemoryfortextstrings.ItworksmuchastheCheatEngine’smemoryscannerdoeswhenyouusethestringvaluetype.
type.
BASEADDRESS=getAddress("Game.exe")➊functionfindString(str)locallen=string.len(str)➋localchunkSize=4096➌localchunkStep=chunkSize-lenprint("Found'"..str.."'at:")➍foraddress=BASEADDRESS,(BASEADDRESS+0x2ffffff),chunkStepdolocalchunk=readBytes(address,chunkSize,true)if(notchunk)thenbreakend➎forc=0,chunkSize-lendo➏checkForString(address,chunk,c,str,len)endendendfunctioncheckForString(address,chunk,start,str,len)fori=1,lendoif(chunk[start+i]~=string.byte(str,i))thenreturnfalseendend➐print(string.format("\t0x%x",address+start))end
➑findString("hello")➒findString("world")
Aftergettingthebaseaddress,thefindString()functionisdefined➊,whichtakesastring,str,asaparameter.Thisfunctionloopsthroughthegame’smemory➍in4,096-byte-longchunks➋.Thechunksarescannedsequentially,eachonestartinglen(thelengthofstr)bytesbeforetheendofthepreviousone➌topreventmissingastringthatbeginsononechunkandendsonanother.
AsfindString()readseachchunk,ititeratesovereverybyteuntiltheoverlappointinthechunk➎,passingeachsubchunkintothecheckForString()function➏.IfcheckForString()matchesthesubchunktostr,itprintstheaddressofthatsubchunktotheconsole➐.
Lastly,tofindalladdressesthatreferencethestrings"hello"and"world",thefunctionsfindString("hello")➑andfindString("world")➒arecalled.
Byusingthiscodetosearchforembeddeddebugstringsandpairingitwiththepreviouscodetolocatefunctionheaders,I’mabletofindalargenumberofinternalfunctionswithinagameinmereseconds.
OPTIMIZINGMEMORYCODEDuetothehighoverheadofmemoryreading,optimizationisextremelyimportantwhenyou’rewritingcodethatperformsmemoryreads.Inthepreviouscodesnippet,noticethatthefunctionfindString()doesnotusetheLuaengine’sbuiltinreadString()function.Instead,itreadsbigchunksofmemoryandsearchesthemforthedesiredstring.Let’sbreakdownthenumbers.
AscanusingreadString()wouldtrytoreadastringoflenbytesateverypossiblememoryaddress.Thismeansitwouldread,atmost,(0x2FFFFFF*len+len)bytes.However,findString()readschunksof4,096bytesandscansthemlocallyformatchingstrings.Thismeansitwouldread,atmost,(0x2FFFFFF+4096+(0x2FFFFFF/(4096-10))*len)bytes.Whensearchingforastringwithalengthof10,thenumberofbytesthateachmethodwouldreadis503,316,480and50,458,923,respectively.
NotonlydoesfindString()readanorderofmagnitudelessdata,italsoinvokesfarfewermemoryreads.Readinginchunksof4,096byteswouldrequireatotalof(0x2FFFFFF/(4096-len))reads.ComparethattoascanusingreadString(),whichwouldneed0x2FFFFFFreads.ThescanthatusesfindString()isahugeimprovementbecauseinvokingareadismuchmoreexpensivethanincreasingthesizeofdatabeingread.(NotethatIchose4,096arbitrarily.Ikeepthechunkrelativelysmallbecausereadingmemorycanbetime-consuming,anditmightbewastefultoreadfourpagesatatimejusttofindthestringinthefirst.)
ClosingThoughts
Bythispoint,youshouldhaveabasicunderstandingofCheatEngineandhowitworks.CheatEngineisaveryimportanttoolinyourkit,andIencourageyoutogetsomehands-onexperiencewithitbyfollowing“BasicMemoryEditing”onpage11and“PointerScanning”onpage18andplayingaroundwithitonyourown.
2DEBUGGINGGAMESWITHOLLYDBG
YoucanscratchthesurfaceofwhathappensasagamerunswithCheatEngine,butwithagooddebugger,youcandigdeeperuntilyouunderstandthegame’sstructureandexecutionflow.ThatmakesOllyDbgessentialtoyourgame-hackingarsenal.It’spackedwithamyriadofpowerfultoolslikeconditionalbreakpoints,referencedstringsearch,assemblypatternsearch,andexecutiontracing,makingitarobustassembler-leveldebuggerfor32-bitWindowsapplications.
I’llcoverlow-levelcodestructureindetailinChapter4,butforthischapter,Iassumeyou’reatleastfamiliarwithmoderncode-leveldebuggers,suchastheonepackagedwithMicrosoftVisualStudio.OllyDbgisfunctionallysimilartothose,withonemajordifference:itinterfaceswiththeassemblycodeofanapplication,workingevenintheabsenceofsourcecodeand/ordebugsymbols,makingitidealwhenyouneedtodigintotheinternalsofagame.Afterall,gamecompaniesarerarelynice(ordumb)enoughtoshiptheirgameswithdebugsymbols!
Inthischapter,I’llgooverOllyDbg’suserinterface,showyouhowtouseitsmostcommondebuggingfeatures,breakdownitsexpressionengine,andprovidesomereal-worldexamplesofhowyoucantieitintoyourgamehackingendeavors.Asawrap-up,I’llteachyouaboutsomeusefulplug-insandsendyouoffwithatestgamedesignedtogetyoustartedinOllyDbg.
NOTE
ThischapterfocusesonOllyDbg1.10andmaynotbeentirelyaccurateforlaterversions.Iusethisversionbecause,atthetimeofwriting,theplug-ininterfaceforOllyDbg2isstillfarlessrobustthantheoneforOllyDbg1.
WhenyoufeellikeyouhaveahandleonOllyDbg’sinterfaceandfeatures,youcantryitonagameyourselfwith“Patchinganif()Statement”onpage46.
ABriefLookatOllyDbg’sUserInterfaceGototheOllyDbgwebsite(http://www.ollydbg.de/),downloadandinstallOllyDbg,andopentheprogram.YoushouldseethetoolbarshowninFigure2-1aboveamultiplewindowinterfacearea.
Figure2-1:OllyDbgmainwindow
Thistoolbarcontainstheprogramcontrols➊,thedebugbuttons➋,theGotobutton➌,thecontrolwindowbuttons➍,andtheSettingsbutton➎.
Thethreeprogramcontrolsallowyoutoopenanexecutableandattachtotheprocessitcreates,restartthecurrentprocess,orterminateexecutionofthecurrentprocess,respectively.YoucanalsocompletethesefunctionswiththehotkeysF3,CTRL-F2,andALT-F2,respectively.Toattachtoaprocessthatisalreadyrunning,clickFile▸Attach.
Thedebugbuttonscontrolthedebuggeractions.Table2-1describeswhatthesebuttonsdo,alongwiththeirhotkeysandfunctions.Thistablealsoliststhreeusefuldebuggeractionsthatdon’thavebuttonsonthedebug
toolbar.
Table2-1:DebugButtonsandOtherDebuggerFunctions
Button Hotkey Function
Play F9 Resumesnormalexecutionoftheprocess.
Pause F12 PausesexecutionofallthreadswithintheprocessandbringsuptheCPUwindowattheinstructioncurrentlybeingexecuted.
Stepinto
F7 Single-stepstothenextoperationtobeexecuted(willdivedownintofunctioncalls).
Stepover
F8 Stepstothenextoperationtobeexecutedwithincurrentscope(willskipoverfunctioncalls).
Traceinto
CTRL-F11
Runsadeeptrace,tracingeveryoperationthatisexecuted.
Traceover
CTRL-F12
Runsapassivetracethattracesonlyoperationswithinthecurrentscope.
Executeuntilreturn
CTRL-F9
Executesuntilareturnoperationishitwithinthecurrentscope.
CTRL-F7
Automaticallysingle-stepsoneveryoperation,followingexecutioninthedisassemblywindow.Thismakesexecutionappeartobeanimated.
CTRL-F8
Alsoanimatesexecution,butstepsoverfunctionsinsteadofsteppingintothem.
ESC Stopsanimation,pausingexecutiononthecurrentoperation.
TheGotobuttonopensadialogaskingforahexadecimaladdress.Onceyouentertheaddress,OllyDbgopenstheCPUwindowandshowsthedisassemblyatthespecifiedaddress.WhentheCPUwindowisinfocus,youcanalsoshowthatinformationwiththehotkeyCTRL-G.
Thecontrolwindowbuttonsopendifferentcontrolwindows,whichdisplay
usefulinformationabouttheprocessyou’redebuggingandexposemoredebuggingfunctions,liketheabilitytosetbreakpoints.OllyDbghasatotalof13controlwindows,whichcanallbeopensimultaneouslywithinthemultiplewindowinterface.Table2-2describesthesewindows,listedintheorderinwhichtheyappearonthewindowbuttonstoolbar.
Table2-2:OllyDbg’sControlWindows
Window HotkeyFunction
Log ALT-L
Displaysalistoflogmessages,includingdebugprints,threadevents,debuggerevents,moduleloads,andmuchmore.
Modules ALT-E Displaysalistofallexecutablemodulesloadedintotheprocess.Double-clickamoduletoopenitintheCPUwindow.
Memorymap
ALT-M
Displaysalistofallblocksofmemoryallocatedbytheprocess.Double-clickablockinthelisttobringupadumpwindowofthatmemoryblock.
Threads Displaysalistofthreadsrunningintheprocess.Foreachthreadinthislist,theprocesshasastructurecalledaThreadInformationBlock(TIB).OllyDbgallowsyoutovieweachthread’sTIB;simplyright-clickathreadandselectDumpthreaddatablock.
Windows Displaysalistofwindowhandlesheldbytheprocess.Right-clickawindowinthislisttojumptoorsetabreakpointonitsclassprocedure(thefunctionthatgetscalledwhenamessageissenttothewindow).
Handles Displaysalistofhandlesheldbytheprocess.(NotethatProcessExplorerhasamuchbetterhandlelistthanOllyDbg,asIwilldiscussinChapter3.)
CPU ALT-C
Displaysthemaindisassemblerinterfaceandcontrolsamajorityofthedebuggerfunctionality.
Patches CTRL-P
Displaysalistofanyassemblycodemodificationsyouhavemadetomoduleswithintheprocess.
Callstack ALT-K
Displaysthecallstackfortheactivethread.Thewindowupdateswhentheprocesshalts.
BreakpointsALT-B Displaysalistofactivedebuggerbreakpointsandallowsyoutotogglethemonandoff.
References Displaysthereferencelist,whichtypicallyholdsthesearchresultsformanydifferenttypesofsearches.Itpopsuponitsownwhenyourunasearch.
Runtrace Displaysalistofoperationsloggedbyadebuggertrace.
Source Displaysthesourcecodeofthedisassembledmoduleifaprogramdebugdatabaseispresent.
Finally,theSettingsbuttonopenstheOllyDbgsettingswindow.Keepthedefaultsettingsfornow.
Nowthatyou’vehadatourofthemainOllyDbgwindow,let’sexploretheCPU,Patches,andRuntracewindowsmoreclosely.You’llusethosewindowsextensivelyasagamehacker,andknowingyourwayaroundthemiskey.
OllyDbg’sCPUWindowTheCPUwindowinFigure2-2iswheregamehackersspendmostoftheirtimeinOllyDbgbecauseitisthemaincontrolwindowforthedebuggingfeatures.
Figure2-2:OllyDbgCPUwindow
Thiswindowhousesfourdistinctcontrolpanes:thedisassemblerpane➊,theregisterspane➋,thedumppane➌,andthestackpane➍.ThesefourpanesencapsulateOllyDbg’smaindebuggerfunctions,soit’simportanttoknowtheminsideandout.
ViewingandNavigatingaGame’sAssemblyCodeYou’llnavigategamecodeandcontrolmostaspectsofdebuggingfromOllyDbg’sdisassemblerpane.Thispanedisplaystheassemblycodeforthecurrentmodule,anditsdataisneatlydisplayedinatablecomposedoffourdistinctcolumns:Address,Hexdump,Disassembly,andComment.
TheAddresscolumndisplaysthememoryaddressesofeachoperationinthegameprocessyou’reattachedto.Youcandouble-clickanaddressinthis
columntotogglewhetherit’sthedisplaybase.Whenanaddressissetasthedisplaybase,theAddresscolumndisplaysallotheraddressesasoffsetsrelativetoit.
TheHexdumpcolumndisplaysthebytecodeforeachoperation,groupingoperationcodesandparametersaccordingly.Blackbracesspanningmultiplelinesontheleftsideofthiscolumnmarkknownfunctionboundaries.Operationsthathavejumpsgoingtothemareshownwitharight-facingarrowontheinsideofthesebraces.Operationsthatperformjumpsareshownwitheitherup-facingordown-facingarrows,dependingonthedirectioninwhichtheyjump,ontheinsideofthesebraces.Forexample,inFigure2-2,theinstructionataddress0x779916B1(highlightedingray)hasanup-facingarrow,indicatingit’sanupwardjump.Youcanthinkofajumpasagotooperator.
TheDisassemblycolumndisplaystheassemblycodeofeachoperationthegameperforms.So,forexample,youcanconfirmthattheinstructionat0x779916B1inFigure2-2isajumpbylookingattheassembly,whichshowsaJNZ(jumpifnonzero)instruction.Blackbracesinthiscolumnmarktheboundariesofloops.Right-facingarrowsattachedtothesebracespointtotheconditionalstatementsthatcontrolwhethertheloopscontinueorexit.Thethreeright-facingarrowsinthiscolumninFigure2-2pointtoCMP(compare)andTESTinstructions,whichareusedbyassemblycodetocomparevalues.
TheCommentcolumndisplayshuman-readablecommentsabouteachoperationthegameperforms.IfOllyDbgencountersknownAPIfunctionnames,itwillautomaticallyinsertacommentwiththenameofthefunction.Similarly,ifitsuccessfullydetectsargumentsbeingpassedtoafunction,itwilllabelthem(forexample,Arg1,Arg2,...,ArgN).Youcandouble-clickinthiscolumntoaddacustomizedcomment.Blackbracesinthiscolumnmarktheassumedboundariesoffunctioncallparameters.
NOTE
OllyDbginfersfunctionboundaries,jumpdirections,loopstructures,andfunctionparametersduringcodeanalysis,soifthesecolumnslackboundarylinesorjumparrows,justpressCTRL-Atorunacodeanalysisonthebinary.
Whenthedisassemblerpaneisinfocus,thereareafewhotkeysyoucanusetoquicklynavigatecodeandcontrolthedebugger.UseF2forTogglebreakpoint,SHIFT-F12forPlaceconditionalbreakpoint,-(hyphen)forGobackand+(plus)forGoforward(thesetwoworkasyou’dexpectinawebbrowser),*(asterisk)forGotoEIP(whichistheexecutionpointerinthex86architecture),CTRL--(hyphen)forGotopreviousfunction,andCTRL-+
forGotonextfunction.ThedisassemblercanalsopopulatetheReferenceswindowwithdifferent
typesofsearchresults.WhenyouwanttochangetheReferenceswindow’scontents,right-clickinthedisassemblerpane,mouseovertheSearchformenutoexpandit,andselectoneofthefollowingoptions:
AllintermodularcallsSearchesforallcallstofunctionsinremotemodules.Thiscan,forexample,allowyoutoseeeverywherethatagamecallsSleep(),PeekMessage(),oranyotherWindowsAPIfunction,enablingyoutoinspectorsetbreakpointsonthecalls.
AllcommandsSearchesforalloccurrencesofagivenoperationwritteninassembly,wheretheaddedoperatorsCONSTandR32willmatchaconstantvalueoraregistervalue,respectively.OneuseforthisoptionmightbesearchingforcommandslikeMOV[0xDEADBEEF],CONST;MOV[0xDEADBEEF],R32;andMOV[0xDEADBEEF],[R32+CONST]tolistalloperationsthatmodifymemoryattheaddress0xDEADBEEF,whichcouldbeanything,includingtheaddressofyourplayer’shealth.
AllsequencesSearchesforalloccurrencesofagivensequenceofoperations.Thisissimilartothepreviousoptions,butitallowsyoutospecifymultiplecommands.
AllconstantsSearchesforallinstancesofagivenhexadecimalconstant.Forinstance,ifyouentertheaddressofyourcharacter’shealth,thiswilllistallofthecommandsthatdirectlyaccessit.
AllswitchesSearchesforallswitch-caseblocks.
AllreferencedtextstringsSearchesforallstringsreferencedincode.Youcanusethisoptiontosearchthroughallreferencedstringsandseewhatcodeaccessesthem,whichcanbeusefulforcorrelatingin-gametextdisplayswiththecodethatdisplaysthem.Thisoptionisalsoveryusefulforlocatinganydebugassertionorloggingstrings,whichcanbea
tremendoushelpindeterminingthepurposeofcodeparts.
ThedisassemblercanalsopopulatetheNameswindowwithalllabelsinthecurrentmodule(CTRL-N)orallknownlabelsinallmodules(Searchfor▸Nameinallmodules).KnownAPIfunctionswillbeautomaticallylabeledwiththeirnames,andyoucanaddalabeltoacommandbyhighlightingit,pressingSHIFT-;andenteringthelabelwhenprompted.Whenalabeledcommandisreferencedincode,thelabelwillbeshowninplaceoftheaddress.Onewaytousethisfeatureistonamefunctionsthatyou’veanalyzed(justsetalabelonthefirstcommandinafunction)soyoucanseetheirnameswhenotherfunctionscallthem.
ViewingandEditingRegisterContentsTheregisterspanedisplaysthecontentsoftheeightprocessorregisters,alleightflagbits,thesixsegmentregisters,thelastWindowserrorcode,andEIP.Underneaththesevalues,thispanecandisplayeitherFloating-PointUnit(FPU)registersordebugregisters;clickonthepane’sheadertochangewhichregistersaredisplayed.Thevaluesinthispanearepopulatedonlyifyoufreezeyourprocess.Valuesthataredisplayedinredhavebeenchangedsincethepreviouspause.Double-clickonvaluesinthispanetoeditthem.
ViewingandSearchingaGame’sMemoryThedumppanedisplaysadumpofthememoryataspecificaddress.Tojumptoanaddressanddisplaythememorycontents,pressCTRL-Gandentertheaddressintheboxthatappears.YoucanalsojumptotheaddressofanentryintheotherCPUwindowpanesbyright-clickingontheAddresscolumnandselectingFollowindump.
Whiletherearealwaysthreecolumnsinthedumppane,theonlyoneyoushouldalwaysseeistheAddresscolumn,whichbehavesmuchlikeitscousinwithinthedisassemblerpane.Thedatadisplaytypeyouchoosedeterminestheothertwocolumnsshown.Right-clickthedumppanetochangethedisplaytype;fortheoneshowninFigure2-2,you’dright-clickandselectHex▸Hex/ASCII(8bytes).
Youcansetamemorybreakpointonanaddressshowninthedumppanebyright-clickingthataddressandexpandingtheBreakpointsubmenu.Select
Memory▸Onaccessfromthismenutobreakonanycodethatusestheaddressatall,orselectMemory▸Onwritetobreakonlyoncodethatwritestothatspaceinmemory.Toremoveamemorybreakpoint,selectRemovememorybreakpointinthesamemenu;thisoptionappearsonlywhentheaddressyouright-clickhasabreakpoint.
Withoneormorevaluesselectedinthedump,youcanpressCTRL-Rtosearchthecurrentmodule’scodeforreferencestoaddressesoftheselectedvalues;resultsofthissearchappearintheReferenceswindow.YoucanalsosearchforvaluesinthispaneusingCTRL-BforbinarystringsandCTRL-Nforlabels.Afteryouinitiateasearch,pressCTRL-Ltojumptothenextmatch.CTRL-Eallowsyoutoeditanyvaluesyouhaveselected.
NOTE
ThedumpwindowsthatyoucanopenfromtheMemorywindowworkinthesamewayasthedumppane.
ViewingaGame’sCallStackThefinalCPUpaneisthestackpane,andasthenamesuggests,itshowsthecallstack.Likethedumpanddisassemblerpanes,thestackpanehasanAddresscolumn.ThestackpanealsohasaValuecolumn,whichshowsthestackasanarrayof32-bitintegers,andaCommentcolumn,whichshowsreturnaddresses,knownfunctionnames,andotherinformativelabels.Thestackpanesupportsallthesamehotkeysasthedumppane,withtheexceptionofCTRL-N.
MULTICLIENTPATCHINGOnetypeofhack,calledamulticlientpatch,overwritesthesingle-instancelimitationcodewithinagame’sbinarywithno-operationcode,allowingtheusertorunmultiplegameclients,evenwhendoingsoisnormallyforbidden.Becausethecodethatperformsinstancelimitationmustbeexecutedveryearlyafteragameclientislaunched,itcanbenearlyimpossibleforabottoinjectitspatchontime.The
easiestworkaroundforthisistomakemulticlientpatchespersistbyapplyingthemwithinOllyDbgandsavingthemdirectlytothegamebinary.
CreatingCodePatchesOllyDbg’scodepatchesletyoumakeassemblycodemodificationsforagameyouwanttohack,removingtheneedtoengineeratooltailoredtothatspecificgame.Thismakesprototypingcontrolflowhacks—whichmanipulategamebehaviorthroughamixofgamedesignflaws,x86assemblyprotocols,andcommonbinaryconstructs—mucheasier.
Gamehackerstypicallyincludeperfectedpatchesasoptionalfeaturesinabot’stoolsuite,butinsomecases,makingthosefeaturespersistentisactuallymoreconvenientforyourenduser.Luckily,OllyDbgpatchesprovidethecompletefunctionalityyouneedtodesign,test,andpermanentlysavecodemodificationstoanexecutablebinaryusingonlyOllyDbg.
Toplaceapatch,navigatetothelineofassemblycodeyouwanttopatchintheCPUwindow,double-clicktheinstructionyouwishtomodify,placeanewassemblyinstructioninthepop-upprompt,andclickAssemble,asshowninFigure2-3.
Figure2-3:PlacingapatchwithOllyDbg
Alwayspayattentiontothesizeofyourpatch—youcan’tjustresizeandmovearoundassembledcodehoweveryou’dlike.Patcheslargerthanthecodeyouintendtoreplacewilloverflowintosubsequentoperations,potentiallyremovingcriticalfunctionality.Patchessmallerthantheoperationsyouintendtoreplacearesafe,aslongasFillwithNOPsischecked.Thisoptionfillsanyabandonedbyteswithno-operation(NOP)commands,whicharesingle-byteoperationsthatdonothingwhenexecuted.
Allpatchesyouplacearelisted,alongwiththeaddress,size,state,oldcode,newcode,andcomment,inthePatcheswindow.Selectapatchinthislisttoaccessasmallbutpowerfulsetofhotkeys,showninTable2-3.
Table2-3:PatchesWindowHotkeys
OperatorFunction
ENTER Jumpstothepatchinthedisassembler.
spacebar Togglesthepatchonoroff.
F2 Placesabreakpointonthepatch.
SHIFT-F2 Placesaconditionalbreakpointonthepatch.
SHIFT-F4 Placesaconditionallogbreakpointonthepatch.DEL Removesthepatchentryfromthelistonly.
InOllyDbg,youcanalsosaveyourpatchesdirectlytothebinary.First,right-clickinthedisassemblerandclickCopytoexecutable▸Allmodifications.Ifyouwanttocopyonlycertainpatches,highlighttheminthedisassemblypaneandpressCopytoexecutable▸Selectioninstead.
DETERMININGPATCHSIZEThereareafewwaystodeterminewhetheryourpatchwillbeadifferentsizethantheoriginalcode.Forexample,inFigure2-3,youcanseethecommandat0x7790ED2EbeingchangedfromSHRAL,6toSHRAL,7.Ifyoulookatthebytestotheleftofthecommand,yousee3bytesthatrepresentthememoryofthecommand.Thismeansour
newcommandmusteitherbe3bytesorpaddedwithNOPsifit’slessthan3bytes.Furthermore,thesebytesarearrangedintwocolumns.Thefirstcolumncontains0xC0and0x08,whichrepresentthecommandSHRandthefirstoperand,AL.Thesecondcolumncontains0x06,whichrepresentstheoriginaloperand.Becausethesecondcolumnshowsasinglebyte,anyreplacementoperandmustalsobe1byte(between0x00and0xFF).Ifthiscolumnhadshown0x00000006instead,areplacementoperandcouldbeupto4bytesinlength.
TypicalcodepatcheswilleitheruseallNOPstocompletelyremoveacommand(byleavingtheboxemptyandlettingitfilltheentirecommandwithNOPs)orjustreplaceasingleoperand,sothismethodofcheckingpatchsizeisalmostalwayseffective.
TracingThroughAssemblyCodeWhenyourunatraceonanyprogram,OllyDbgsingle-stepsovereveryexecutedoperationandstoresdataabouteachone.Whenthetraceiscomplete,theloggeddataisdisplayedintheRuntracewindow,showninFigure2-4.
Figure2-4:TheRuntracewindow
TheRuntracewindowisorganizedintothefollowingsixcolumns:
BackThenumberofoperationsloggedbetweenanoperationandthecurrentexecutionstate
ThreadThethreadthatexecutedtheoperation
ModuleThemodulewheretheoperationresides
AddressTheaddressoftheoperation
CommandTheoperationthatwasexecuted
ModifiedregistersTheregisterschangedbytheoperationandtheirnewvalues
Whenhackinggames,IfindOllyDbg’stracefeatureveryeffectiveathelpingmefindpointerpathstodynamicmemorywhenCheatEnginescansproveinconclusive.ThisworksbecauseyoucanfollowthelogintheRuntracewindowbackwardfromthepointwhenthememoryisusedtothepointwhereitisresolvedfromastaticaddress.
Thispotentfeature’susefulnessislimitedonlybythecreativityofthehackerusingit.ThoughItypicallyuseitonlytofindpointerpaths,I’vecomeacrossafewothersituationswhereithasproveninvaluable.Theanecdotesin“OllyDbgExpressionsinAction”onpage36willhelptoilluminatethefunctionalityandpoweroftracing.
OllyDbg’sExpressionEngineOllyDbgishometoacustomexpressionenginethatcancompileandevaluateadvancedexpressionswithasimplesyntax.Theexpressionengineissurprisinglypowerfuland,whenutilizedproperly,canbethedifferencebetweenanaverageOllyDbguserandanOllyDbgwizard.Youcanusethisenginetospecifyexpressionsformanyfeatures,suchasconditionalbreakpoints,conditionaltraces,andthecommandlineplug-in.Thissectionintroducestheexpressionengineandtheoptionsitprovides.
NOTE
Partsofthissectionarebasedontheofficialexpressionsdocumentation(http://www.ollydbg.de/Help/i_Expressions.htm).Ihavefound,however,thatafewofthecomponentsdefinedinthedocumentationdon’tseemtowork,
atleastnotinOllyDbgv1.10.TwoexamplesaretheINTandASCIIdatatypes,whichmustbesubstitutedwiththealiasesLONGandSTRING.Forthisreason,hereIincludeonlycomponentsthatI’vepersonallytestedandfullyunderstand.
UsingExpressionsinBreakpointsWhenaconditionalbreakpointistoggledon,OllyDbgpromptsyoutoenteranexpressionforthecondition;thisiswheremostexpressionsareused.Whenthatbreakpointisexecuted,OllyDbgsilentlypausesexecutionandevaluatestheexpression.Iftheresultoftheevaluationisnonzero,executionremainspausedandyouwillseethebreakpointgettriggered.Butiftheresultoftheevaluationis0,OllyDbgsilentlyresumesexecutionasifnothinghappened.
Withthehugenumberofexecutionsthathappenwithinagameeverysecond,you’lloftenfindthatapieceofcodeisexecutedinfartoomanycontextsforabreakpointtobeaneffectivewayofgettingthedatayouarelookingfor.Aconditionalbreakpointpairedwithagoodunderstandingofthecodesurroundingitisafoolproofwaytoavoidthesesituations.
UsingOperatorsintheExpressionEngineFornumericdatatypes,OllyDbgexpressionssupportgeneralC-styleoperators,asseeninTable2-4.Whilethereisnocleardocumentationontheoperatorprecedence,OllyDbgseemstofollowC-styleprecedenceandcanuseparenthesizedscoping.
Table2-4:OllyDbgNumericOperators
OperatorFunction
a==b Returns1ifaisequaltob,elsereturns0.a!=b Returns1ifaisnotequaltob,elsereturns0.a>b Returns1ifaisgreaterthanb,elsereturns0.a<b Returns1ifaislessthanb,elsereturns0.a>=b Returns1ifaisgreaterthanorequaltob,elsereturns0.a<=b Returns1ifaislessthanorequaltob,elsereturns0.
a<=b Returns1ifaislessthanorequaltob,elsereturns0.a&&b
Returns1ifaandbarebothnonzero,elsereturns0.a||b Returns1ifeitheraorbarenonzero,elsereturns0.a^b ReturnstheresultofXOR(a,b).a%b ReturnstheresultofMODULUS(a,b).a&b ReturntheresultofAND(a,b).a|b ReturntheresultofOR(a,b).a<<b Returnstheresultofashiftedbbitstotheleft.a>>b Returnstheresultofashiftedbbitstotheright.a+b Returnsthesumofaplusb.a-b Returnsthedifferenceofaminusb.a/b Returnsthequotientofadividedbyb.a*b Returnstheproductofatimesb.+a Returnsthesignedrepresentationofa.-a Returnsa*-1.!a Returns1ifais0,elsereturns0.
Forstrings,ontheotherhand,theonlyavailableoperatorsare==and!=,whichbothadheretothefollowingsetofrules:
•Stringcomparisonsarecaseinsensitive.
•Ifonlyoneoftheoperandsisastringliteral,thecomparisonwillterminateafteritreachesthelengthoftheliteral.Asaresult,theexpression[STRINGEAX]=="ABC123",whereEAXisapointertothestringABC123XYZ,willevaluateto1insteadof0.
•Ifnotypeisspecifiedforanoperandinastringcomparisonandtheotheroperandisastringliteral(forexample,"MyString"!=EAX),thecomparisonwillfirstassumethenonliteraloperandisanASCIIstring,and,ifthatcomparewouldreturn0,itwilltryasecondcompareassumingthe
operandisaUnicodestring.
Ofcourse,operatorsaren’tmuchusewithoutoperands.Let’slookatsomeofthedatayoucanevaluateinexpressions.
WorkingwithBasicExpressionElementsExpressionsareabletoevaluatemanydifferentelements,including:
CPUregistersEAX,EBX,ECX,EDX,ESP,EBP,ESI,andEDI.Youcanalsousethe1-byteand2-byteregisters(forexample,ALforthelowbyteandAXforthelowwordofEAX).EIPcanalsobeused.
SegmentregistersCS,DS,ES,SS,FS,andGS.
FPUregistersST0,ST1,ST2,ST3,ST4,ST5,ST6,andST7.
SimplelabelsCanbeAPIfunctionnames,suchasGetModuleHandle,oruser-definedlabels.
WindowsconstantsSuchasERROR_SUCCESS.
IntegersArewritteninhexadecimalformatordecimalformatiffollowedbyatrailingdecimalpoint(forexample,FFFFor65535.).
Floating-pointnumbersAllowexponentsindecimalformat(forexample,654.123e-5).
StringliteralsArewrappedinquotationmarks(forexample,"mystring").
Theexpressionsenginelooksfortheseelementsintheorderthey’relistedhere.Forexample,ifyouhavealabelthatmatchesthenameofaWindowsconstant,theengineusestheaddressofthelabelinsteadoftheconstant’svalue.Butifyouhavealabelnamedafteraregister,suchasEAX,theengineusestheregistervalue,notthelabelvalue.
AccessingMemoryContentswithExpressionsOllyDbgexpressionsarealsopowerfulenoughtoincorporatememoryreading,whichyoucandobywrappingamemoryaddress,oranexpressionthatevaluatestoone,insquarebrackets.Forexample,[EAX+C]and[401000]
representthecontentsattheaddressesEAX+Cand401000.ToreadthememoryasatypeotherthanDWORD,youcanspecifythedesiredtypeeitherbeforethebrackets,asinBYTE[EAX],orasthefirsttokenwithinthem,asin[STRINGESP+C].SupportedtypesarelistedinTable2-5.
Table2-5:OllyDbgDataTypes
DatatypeInterpretationBYTE 8-bitinteger(unsigned)CHAR 8-bitinteger(signed)WORD 16-bitinteger(unsigned)SHORT 16-bitinteger(signed)DWORD 32-bitinteger(unsigned)LONG 32-bitinteger(signed)FLOAT 32-bitfloating-pointnumberDOUBLE 64-bitfloating-pointnumberSTRING PointertoanASCIIstring(null-terminated)UNICODE PointertoaUnicodestring(null-terminated)
PluggingmemorycontentsdirectlyintoyourOllyDbgexpressionsisincrediblyusefulingamehacking,inpartbecauseyoucantellthedebuggertocheckacharacter’shealth,name,gold,andsooninmemorybeforebreaking.You’llseeanexampleofthisin“PausingExecutionWhenaSpecificPlayer’sNameIsPrinted”onpage37.
OllyDbgExpressionsinActionExpressionsinOllyDbguseasyntaxsimilartothatofmostprogramminglanguages;youcanevencombinemultipleexpressionsandnestoneexpressionwithinanother.Gamehackers(really,allhackers)commonlyusethemtocreateconditionalbreakpoints,asIdescribedin“UsingExpressionsinBreakpoints”onpage34,butyoucanusetheminmanydifferentplacesinOllyDbg.Forinstance,OllyDbg’scommandlineplug-incanevaluate
expressionsinplaceanddisplaytheirresults,allowingyoutoeasilyreadarbitrarymemory,inspectvaluesthatarebeingcalculatedbyassemblycode,orquicklygettheresultsofmathematicalequations.Furthermore,hackerscanevencreateintelligent,position-agnosticbreakpointsbycouplingexpressionswiththetracefeature.
Inthissection,I’llsharesomeanecdoteswheretheexpressionenginehascomeinhandyduringmywork.Iwillexplainmythoughtprocess,walkthroughmyentiredebuggingsession,andbreakeachexpressiondownintoitscomponentpartssoyoucanseesomewaystouseOllyDbgexpressionsingamehacking.
NOTE
Theseexamplescontainsomeassemblycode,butifyoudon’thavemuchexperiencewithassembly,don’tworry.JustignorethefinedetailsandknowthatvalueslikeECX,EAX,andESPareprocessregistersliketheonesdiscussedin“ViewingandEditingRegisterContents”onpage29.Fromthere,I’llexplaineverythingelse.
Ifyougetconfusedaboutanoperator,element,ordatatypeinanexpressionasIwalkthroughtheseanecdotes,justreferto“OllyDbg’sExpressionEngine”onpage33.
PausingExecutionWhenaSpecificPlayer’sNameIsPrintedDuringoneparticulardebuggingsession,Ineededtofigureoutexactlywhatwashappeningwhenagamewasdrawingthenamesofplayersonscreen.Specifically,Ineededtoinvokeabreakpointbeforethegamedrewthename“Player1,”ignoringallothernamesthatweredrawn.
FiguringOutWheretoPauseAsastartingpoint,IusedCheatEnginetofindtheaddressofPlayer1’snameinmemory.OnceIhadtheaddress,IusedOllyDbgtosetamemorybreakpointonthefirstbyteofthestring.Everytimethisbreakpointgothit,IquicklyinspectedtheassemblycodetodeterminehowitwasusingPlayer
1’sname.Eventually,IfoundthenamebeingaccesseddirectlyaboveacalltoafunctionthatIhadpreviouslygiventhenameprintText().Ihadfoundthecodethatwasdrawingthename.
IremovedmymemorybreakpointandreplaceditwithacodebreakpointonthecalltoprintText().Therewasaproblem,however:becausethecalltoprintText()wasinsidealoopthatiteratedovereveryplayerinthegame,mynewbreakpointwasgettinghiteverytimeanamewasdrawn—andthatwasmuchtoooften.Ineededtofixittohitonlyonaspecificplayer.
Inspectingtheassemblycodeatmypreviousmemorybreakpointtoldmethateachplayer’snamewasaccessedusingthefollowingassemblycode:
PUSHDWORDPTRDS:[EAX+ECX*90+50]
TheEAXregistercontainedtheaddressofanarrayofplayerdata;I’llcallitplayerStruct.ThesizeofplayerStructwas0x90bytes,theECXregistercontainedtheiterationindex(thefamousvariablei),andeachplayer’snamewasstored0x50bytesafterthestartofitsrespectiveplayerStruct.ThismeantthatthisPUSHinstructionessentiallyputEAX[ECX].name(thenameoftheplayeratindexi)onthestacktobepassedasanargumenttotheprintText()functioncall.Theloop,then,brokedowntosomethinglikethefollowingpsuedocode:
playerStructEAX[MAX_PLAYERS];//thisisfilledelsewherefor(int➊ECX=0;ECX<MAX_PLAYERS;ECX++){char*name=➋EAX[ECX].name;breakpoint();//mycodebreakpointwasbasicallyrighthereprintText(name);}
Purelythroughanalysis,IdeterminedthattheplayerStruct()functioncontaineddataforallplayers,andtheloopiteratedoverthetotalnumberofplayers(countingupwithECX➊),fetchedthecharactername➋foreachindex,andprintedthename.
CraftingtheConditionalBreakpointKnowingthat,topauseexecutiononlywhenprinting“Player1”allIhadtodowascheckthecurrentplayernamebeforeexecutingmybreakpoint.Inpseudocode,thenewbreakpointwouldlooklikethis:
if(EAX[ECX].name=="Player1")breakpoint();
OnceIfiguredouttheformofmynewbreakpoint,IneededtoaccessEAX[ECX].namefromwithintheloop.That’swhereOllyDbg’sexpressionenginecamein:Icouldachievemygoalbymakingslightmodificationstotheexpressionthattheassemblycodeused,leavingmewiththisexpression:
[STRINGEAX+ECX*0x90+0x50]=="Player1"
IremovedthecodebreakpointonprintText()andreplaceditwithaconditionalbreakpointthatusedthisexpression,whichtoldOllyDbgtobreakonlyifthestringvaluestoredatEAX+ECX*0x90+0x50matchedPlayer1’sname.Thisbreakpointhitonlywhen"Player1"wasbeingdrawn,allowingmetocontinuemyanalysis.
Theamountofworkittooktoengineerthisbreakpointmightseemextensive,butwithpractice,theentireprocessbecomesasintuitiveaswritingcode.Experiencedhackerscandothisinamatterofseconds.
Inpractice,thisbreakpointenabledmetoinspectcertainvaluesintheplayerStruct()functionfor"Player1"assoonasheappearedonscreen.Doingitthiswaywasimportant,asthestatesofthesevalueswererelevanttomyanalysisonlyinthefirstfewframesaftertheplayerenteredthescreen.Creativelyusingbreakpointslikethiscanenableyoutoanalyzeallsortsofcomplexgamebehavior.
PausingExecutionWhenYourCharacter’sHealthDropsDuringanotherdebuggingsession,Ineededtofindthefirstfunctioncalledaftermycharacter’shealthdroppedbelowthemaximum.Iknewtwowaystoapproachthisproblem:
•Findeverypieceofcodethataccessesthehealthvalueandplaceaconditionalbreakpointthatchecksthehealthoneachone.Then,onceoneofthesebreakpointsishit,single-stepthroughthecodeuntilthenextfunctioncall.
•UseOllyDbg’stracefunctiontocreateadynamicbreakpointthatcanstopexactlywhereIneed.
Thefirstmethodrequiredmoresetupandwasnoteasilyrepeatable,mostlyduetothesheernumberofbreakpointsneededandthefactthatI’d
mostlyduetothesheernumberofbreakpointsneededandthefactthatI’dhavetosingle-stepbyhand.Incontrast,thelattermethodhadaquicksetup,andsinceitdideverythingautomatically,itwaseasilyrepeatable.Thoughusingthetracefunctionwouldslowthegamedownconsiderably(everysingleoperationwascapturedbythetrace),Ichosethelattermethod.
WritinganExpressiontoCheckHealthOnceagain,IstartedbyusingCheatEnginetofindtheaddressthatstoredmyhealth.Usingthemethoddescribedin“CheatEngine’sMemoryScanner”onpage5,Ideterminedtheaddresstobe0x40A000.
Next,IneededanexpressionthattoldOllyDbgtoreturn1whenmyhealthwasbelowmaximumandreturn0otherwise.Knowingthatmyhealthwasstoredat0x40A000andthatthemaximumvaluewas500,Iinitiallydevisedthisexpression:
[0x40A000]<500.
Thisexpressionwouldinvokeabreakwhenmyhealthwasbelow500(remember,decimalnumbersmustbesuffixedwithaperiodintheexpressionengine),butinsteadofwaitingforafunctiontobecalled,thebreakwouldhappenimmediately.Toensurethatitwaiteduntilafunctionwascalled,Iappendedanotherexpressionwiththe&&operator:
[0x40A000]<500.&&[➊BYTEEIP]==0xE8
Onx86processors,theEIPregisterstorestheaddressoftheoperationbeingexecuted,soIdecidedtocheckthefirstbyteatEIP➊toseeifitwasequalto0xE8.Thisvaluetellstheprocessortoexecuteanearfunctioncall,whichisthetypeofcallIwaslookingfor.
Beforestartingmytrace,Ihadtodoonelastthing.Becausethetracefeaturerepeatedlysingle-steps(TraceintousesstepintoandTraceoverusesstepover,asdescribedin“ABriefLookatOllyDbg’sUserInterface”onpage24),Ineededtostartthetraceatalocationscopedatorabovethelevelofanycodethatcouldpossiblyupdatethehealthvalue.
FiguringOutWheretoStarttheTraceTofindagoodlocation,Iopenedthegame’smainmoduleinOllyDbg’s
CPUwindow,right-clickedinthedisassemblerpane,andselectedSearchfor▸Allintermodularcalls.TheReferenceswindowpoppedupanddisplayedalistofexternalAPIfunctionsthatwerecalledbythegame.NearlyallgamingsoftwarepollsfornewmessagesusingtheWindowsUSER32.PeekMessage()function,soIsortedthelistusingtheDestinationcolumnandtypedPEEK(youcansearchthelistbysimplytypinganamewiththewindowinfocus)tolocatethefirstcalltoUSER32.PeekMessage().
ThankstotheDestinationsorting,everycalltothisfunctionwaslistedinacontiguouschunkfollowingthefirst,asshowninFigure2-5.IsetabreakpointoneachbyselectingitandpressingF2.
Figure2-5:OllyDbg’sFoundintermodularcallswindow
ThoughtherewerearoundadozencallstoUSER32.PeekMessage(),onlytwoofthemweresettingoffmybreakpoints.Evenbetter,theactivecallswerebesideoneanotherinanunconditionalloop.Atthebottomofthisloopwereanumberofinternalfunctioncalls.Thislookedexactlylikeamaingameloop.
ActivatingtheTrace
Tofinallysetmytrace,Iremovedallofmypreviousbreakpointsandplacedoneatthetopofthesuspectedmainloop.Iremovedthebreakpointassoonasitwashit.IthenpressedCTRL-TfromtheCPUwindow,whichbroughtupadialogcalledConditiontopauseruntrace,showninFigure2-6.Withinthisnewdialog,IenabledtheConditionisTRUEoption,placedmyexpressionintheboxbesideit,andpressedOK.Then,IwentbacktotheCPUwindowandpressedCTRL-F11tobeginaTraceIntosession.
Figure2-6:Conditiontopauseruntracedialog
Oncethetracebegan,thegameransoslowlyitwasnearlyunplayable.Todecreasemytestcharacter’shealth,Iopenedasecondinstanceofthegame,loggedintoadifferentcharacter,andattackedmytestcharacter.Whentheexecutionofthetracecaughtuptorealtime,OllyDbgsawmyhealthchangeandtriggeredthebreakpointonthefollowingfunctioncall—justasexpected.
Inthisgame,themainpiecesofcodethatwouldmodifythehealthvalueweredirectlyinvokedfromthenetworkcode.Usingthistrace,Iwasabletofindthefunctionthatthenetworkmodulecalleddirectlyafteranetworkpackettoldthegametochangetheplayer’shealth.Here’sthepsuedocodeofwhatthegamewasdoing:
voidnetwork::check(){while(this->hasPacket()){packet=this->getPacket();if(packet.type==UPDATE_HEALTH){oldHealth=player->health;player->health=packet.getInteger();➊observe(HEALTH_CHANGE,oldHealth,player->health);}}}
Iknewthegamehadcodethatneededtoexecuteonlywhentheplayer’shealthwaschanged,andIneededtoaddcodethatcouldalsorespondtosuchchanges.Withoutknowingtheoverallcodestructure,Iguessedthatthehealth-dependentcodewouldbeexecutedfromsomefunctioncalldirectlyafterhealthwasupdated.Mytraceconditionalbreakpointconfirmedthishunch,asitbrokedirectlyontheobserve()function➊.Fromthere,Iwasabletoplaceahookonthefunction(hooking,awaytointerceptfunctioncalls,isdescribedin“HookingtoRedirectGameExecution”onpage153)andexecutemyowncodewhentheplayer’shealthchanged.
OllyDbgPlug-insforGameHackersOllyDbg’shighlyversatileplug-insystemisperhapsoneofitsmostpowerfulfeatures.ExperiencedgamehackersoftenconfiguretheirOllyDbgenvironmentswithdozensofusefulplug-ins,bothpubliclyavailableandcustom-made.
Youcandownloadpopularplug-insfromtheOpenRCE(http://www.openrce.org/downloads/browse/OllyDbg_Plugins)andtuts4you(http://www.tuts4you.com/download.php?list.9/)plug-inrepositories.Installingthemiseasy:justunziptheplug-infilesandplacetheminsideOllyDbg’sinstallationfolder.
Onceinstalled,someplug-inscanbeaccessedfromtheOllyDbg’sPluginmenuitem.Otherplug-ins,however,mightbefoundonlyinspecificplacesthroughouttheOllyDbginterface.
Youcanfindhundredsofpotentplug-insusingtheseonlinerepositories,butyoushouldbecarefulwhenconstructingyourarsenal.Workinginanenvironmentbloatedbyunusedplug-inscanactuallyimpedeproductivity.Inthissection,I’vecarefullyselectedfourplug-insthatIbelievearenotonly
thissection,I’vecarefullyselectedfourplug-insthatIbelievearenotonlyintegraltoagamehacker’stoolkitbutalsononinvasivetotheenvironment.
CopyingAssemblyCodewithAsm2ClipboardAsm2Clipboardisaminimalisticplug-infromtheOpenRCErepositorythatallowsyoutocopychunksofassemblycodefromthedisassemblerpanetotheclipboard.Thiscanbeusefulforupdatingaddressoffsetsanddevisingcodecaves,twogame-hackingessentialsIcoverdeeplyinChapters5and7.
WithAsm2Clipboardinstalled,youcanhighlightablockofassemblycodeinthedisassembler,right-clickthehighlightedcode,expandtheAsm2Clipboardsubmenu,andselecteitherCopyfixedAsmcodetoclipboardorCopyAsmcodetoclipboard.Thelatterprependsthecodeaddressofeachinstructionasacomment,whiletheformercopiesonlythepurecode.
AddingCheatEnginetoOllyDbgwithCheatUtilityTheCheatUtilityplug-infromtuts4youprovidesahighlyslimmed-downversionofCheatEnginewithinOllyDbg.WhileCheatUtilityonlyallowsyoutodoexact-valuescanswithaverylimitednumberofdatatypes,itcanmakesimplescansmucheasierwhenyoudon’tneedthefullfunctionalityofCheatEnginetofindwhatyou’relookingfor.AfterinstallingCheatUtility,toopenitsinterface(showninFigure2-7),selectPlugins▸Cheatutility▸Start.
Figure2-7:CheatUtilityinterface
CheatUtility’suserinterfaceandoperationmimicCheatEngineclosely,soreviewChapter1ifyouneedarefresher.
NOTE
GamesInvader,anupdatedversionofCheatUtilityalsofromtuts4you,wascreatedtoprovidemorefunctionality.I’vefounditbuggy,however,andIpreferCheatUtilitysinceIcanalwaysuseCheatEngineforadvancedscans.
ControllingOllyDbgThroughtheCommandLineThecommandlineplug-inenablesyoutocontrolOllyDbgthroughasmallcommandlineinterface.Toaccesstheplug-in,eitherpressALT-F1orselectPlugins▸Commandline▸Commandline.Youshouldthenseeawindow,showninFigure2-8,whichactsasthecommandlineinterface.
Figure2-8:Commandlineinterface
Toexecuteacommand,typeitintotheinputbox➊andpressENTER.Youwillseeasession-levelcommandhistoryinthecenterlist➋,andthebottomlabeldisplaysthecommand’sreturnvalue➌(ifany).
Thoughtherearemanycommandsavailable,Ifindamajorityofthemuseless.Iprimarilyusethistoolasawaytotestthatexpressionsareparsingasexpectedandasahandycalculator,butthereareafewadditionalusecasesthatarealsoworthmentioning.I’vedescribedtheseinTable2-6.
Table2-6:CommandLinePlug-inCommands
Command Function
BCidentifier
Removesanybreakpointspresentonidentifier,whichcanbeacodeaddressorAPIfunctionname.
BPidentifier[,condition]
Placesadebuggerbreakpointonidentifier,whichcanbeacodeaddressorAPIfunctionname.WhenidentifierisanAPIfunctionname,thebreakpointwillbeplacedonthefunctionentrypoint.Theconditionparameterisanoptionalexpressionthat,ifpresent,willbesetasthebreakpointcondition.
BPXlabel Placesadebuggerbreakpointoneveryinstanceoflabelwithinthemodulecurrentlybeingdisassembled.ThislabelwilltypicallybeanAPIfunctionname.
CALCexpression?expression
Evaluatesexpressionanddisplaystheresult.
HDaddress Removesanyhardwarebreakpointspresentonaddress.HEaddress Placesahardwareon-executebreakpointonaddress.HRaddress Placesahardwareon-accessbreakpointonaddress.Onlyfour
hardwarebreakpointscanexistatatime.HWaddress Placesahardwareon-writebreakpointonaddress.MD Removesanyexistingmemorybreakpoint,ifpresent.MRaddress1,address2
Placesamemoryon-accessbreakpointstartingataddress1andspanninguntiladdress2.Willreplaceanyexistingmemorybreakpoint.
MWaddress1,address2
Placesamemoryon-writebreakpointstartingataddress1and
spanninguntiladdress2.Willreplaceanyexistingmemorybreakpoint.
WATCHexpressionWexpression
OpenstheWatcheswindowandaddsexpressiontothewatchlist.Expressionsinthislistwillbereevaluatedeverytimetheprocessreceivesamessageandtheevaluationresultswillbedisplayedbesidethem.
Thecommandlineplug-inwasmadebytheOllyDbgdeveloperandshouldcomepreinstalledwithOllyDbg.
VisualizingControlFlowwithOllyFlowOllyFlow,whichcanbefoundintheOpenRCEplug-indirectory,isapurelyvisualplug-inthatcangeneratecodegraphsliketheoneinFigure2-9anddisplaythemusingWingraph32.
Figure2-9:AnOllyFlowfunctionflowchart
NOTE
Wingraph32isnotprovidedwithOllyFlow,butitisavailablewiththefreeversionofIDAhere:https://www.hex-rays.com/products/ida/.Downloaditanddropthe.exeinyourOllyDbginstallationfolder.
Thoughnotinteractive,thesegraphsallowyoutoeasilyidentifyconstructssuchasloopsandnestedif()statementsingamecode,whichcanbeparamountincontrolflowanalysis.WithOllyFlowinstalled,youcangenerateagraphbygoingtoPlugins▸OllyFlow(alternatively,right-clickinthedisassemblerpaneandexpandtheOllyFlowgraphsubmenu)andselectingoneofthefollowingoptions:
GeneratefunctionflowchartGeneratesagraphofthefunctioncurrentlyinscope,breakingapartdifferentcodeblocksandshowingjumppaths.Figure2-9showsafunctionflowchart.Withoutadoubt,thisisOllyFlow’smostusefulfeature.
GeneratexrefsfromgraphGeneratesagraphofallfunctionscalledbythefunctionthatiscurrentlyinscope.
GeneratexrefstographGeneratesagraphofallfunctionsthatcallthefunctioncurrentlyinscope.
GeneratecallstackgraphGeneratesagraphoftheassumedcallpathfromtheprocessentrypointtothefunctioncurrentlyinscope.
GeneratemodulegraphTheoreticallygeneratesacompletegraphofallfunctioncallsintheentiremodule,butrarelyactuallyworks.
TogetanideaoftheusefulnessofOllyFlow,takealookatthegraphinFigure2-9andcompareittotherelativelysimpleassemblyfunctionthatgeneratedit:
76f86878:➊MOVEAX,DWORDPTRDS:[76FE7E54]TESTAL,1JEntdll.76F8689B76f86881:➋MOVEAX,DWORDPTRFS:[18]MOVEAX,DWORDPTRDS:[EAX+30]ORDWORDPTRDS:[EAX+68],2000000
MOVEAX,DWORDPTRDS:[76FE66E0]ORDWORDPTRDS:[EAX],1JMPntdll.76F868B276f8689b:➌TESTEAX,8000JEntdll.76F868B276f868a2:➍MOVEAX,DWORDPTRFS:[18]MOVEAX,DWORDPTRDS:[EAX+30]ORDWORDPTRDS:[EAX+68],200000076f868b2:➎MOVAL,1RETN
TherearefiveboxesinFigure2-9,andtheymaptothefivepiecesofthisfunction.Thefunctionstartswith➊,anditfallsthroughto➋ifthebranchfailsorjumpsto➌ifitsucceeds.After➋executes,itjumpsdirectlytopiece➎,whichthenreturnsoutofthefunction.After➌executes,iteitherfallsthroughto➍orbranchesto➎toreturndirectly.After➍executes,itunconditionallyfallsthroughto➎.WhatthisfunctiondoesisirrelevanttounderstandingOllyFlow;fornow,justfocusonseeinghowthecodemapstothegraph.
PATCHINGANIF()STATEMENTIfyouthinkyou’rereadytogetyourhandsdirtywithOllyDbg,keepreading.Gotohttps://www.nostarch.com/gamehacking/,downloadthebook’sresourcefiles,grabBasicDebugging.exe,andexecuteit.Atfirstglance,you’llseethatitlooksliketheclassicgamePong.InthisversionofPong,theballisinvisibletoyouwhenitisonyouropponent’sscreen.Yourtaskistodisablethisfeaturesothatyoucanalwaysseetheball.Tomakeiteasierforyou,I’vemadethegameautonomous.Youdon’thavetoplay,onlyhack.
Tostart,attachOllyDbgtothegame.ThenfocustheCPUwindowonthemainmodule(findthe.exeinthemodulelistanddouble-clickit)andusetheReferencedtextstringsfeaturetolocatethestringthatisdisplayedwhentheballishidden.Next,double-clickthestringtobringitupinthecodeandanalyzethesurroundingcode
untilyoufindtheif()statementthatdetermineswhethertohidetheball.Lastly,usingthecode-patchingfeature,patchtheif()statementsotheballisalwaysdrawn.Asanaddedbonus,youmighttryusingOllyFlowtographthisfunctionsoyoucangetabetterunderstandingofwhatexactlyitisdoing.(Hint:Theif()statementcheckswhethertheball’sx-coordinateislessthan0x140.Ifso,itjumpstocodethatdrawstheball.Ifnot,itdrawsthescenewithouttheball.Ifyoucanchange0x140to,say,0xFFFF,theballwillnevergethidden.)
ClosingThoughtsOllyDbgisamuchmorecomplexbeastthanCheatEngine,butyou’lllearnbestbyusingit,sodiveinandgetyourhandsdirty!Youcanstartbypairingthecontrolstaughtinthischapterwithyourdebuggingskillsandgoingtoworkonsomerealgames.Ifyouarenotyetreadytotamperwithyourvirtualfate,however,trytacklingtheexamplein“Patchinganif()Statement”forapracticeenvironment.Whenyou’redone,readontoChapter3,whereI’llintroduceyoutoProcessMonitorandProcessExplorer,twotoolsyou’llfindinvaluableingame-hackingreconnaissance.
3RECONNAISSANCEWITHPROCESSMONITORANDPROCESSEXPLORER
CheatEngineandOllyDbgcanhelpyoutearapartagame’smemoryandcode,butyoualsoneedtounderstandhowthegameinteractswithfiles,registryvalues,networkconnections,andotherprocesses.Tolearnhowthoseinteractionswork,youmustusetwotoolsthatexcelatmonitoringtheexternalactionsofprocesses:ProcessMonitorandProcessExplorer.Withthesetools,youcantrackdownthecompletegamemap,locatesavefiles,identifyregistrykeysusedtostoresettings,andenumeratetheInternetProtocol(IP)addressesofremotegameservers.
Inthischapter,I’llteachyouhowtousebothProcessMonitorandProcessExplorertologsystemeventsandinspectthemtoseehowagamewasinvolved.Usefulmainlyforinitialreconnaissance,thesetoolsareamazingatgivingaclear,verbosepictureofexactlyhowagameinteractswithyoursystem.YoucandownloadbothprogramsfromtheWindowsSysinternalswebsite(https://technet.microsoft.com/en-us/sysinternals/).
ProcessMonitorYoucanlearnalotaboutagamesimplybyexploringhowitinteractswiththeregistry,filesystem,andnetwork.ProcessMonitorisapowerfulsystem-monitoringtoolthatlogssucheventsinrealtimeandletsyouseamlessly
monitoringtoolthatlogssucheventsinrealtimeandletsyouseamlesslyintegratethedataintoadebuggingsession.Thistoolprovidesextensiveamountsofusefuldataregardingagame’sinteractionwiththeexternalenvironment.Withcalculatedreview(andsometimes,spontaneousintuition)onyourpart,thisdatacanrevealdetailsaboutdatafiles,networkconnections,andregistryeventsthatarehelpfultoyourabilitytoseeandmanipulatehowthegamefunctions.
Inthissection,I’llshowyouhowtouseProcessMonitortologdata,navigateit,andmakeeducatedguessesaboutthefilesagameinteractswith.Afterthisinterfacetour,you’llhaveachancetotryoutProcessMonitorforyourselfin“FindingaHighScoreFile”onpage55.
LoggingIn-GameEventsProcessMonitor’slogscanholdallsortsofpotentiallyusefulinformation,buttheirmostpracticaluseistohelpyoufigureoutwheredatafiles,suchasin-gameitemdefinitions,mightbestored.WhenyoustartProcessMonitor,thefirstdialogyouseeistheProcessMonitorFilter,showninFigure3-1.
Figure3-1:ProcessMonitorFilterdialog
Thisdialogallowsyoutoshoworsuppresseventsbasedonanumberofdynamicpropertiestheypossess.Tostartmonitoringprocesses,selectProcessName▸Is▸YourGameFilename.exe▸Includeandthenpress
Add,Apply,andOK.ThistellsProcessMonitortoshoweventsinvokedbyYourGameFilename.exe.Withtheproperfiltersset,youwillbetakentothemainwindowshowninFigure3-2.
Figure3-2:ProcessMonitormainwindow
ToconfigurethecolumnsdisplayedinProcessMonitor’slogarea,right-clickontheheaderandchooseSelectColumns.There’sanimpressivenumberofoptions,butIrecommendseven.
TimeofDayLetsyouseewhenactionsarehappening.
ProcessNameIsusefulifyou’remonitoringmultipleprocesses,butwiththesingle-processfilterthatistypicallyusedforgames;disablingthisoptioncansavepreciousspace.
ProcessIDIslikeProcessName,butitshowstheIDratherthanthename.
OperationShowswhatactionwasperformed;thus,thisoptioniscompulsory.
PathShowsthepathoftheaction’starget;alsocompulsory.
DetailIsusefulonlyinsomecases,butenablingitwon’thurt.
ResultShowswhenactions,suchasloadingfiles,fail.
Asyoushowmorecolumns,thelogcangetverycrowded,butstickingwiththeseoptionsshouldhelpkeeptheoutputsuccinct.
withtheseoptionsshouldhelpkeeptheoutputsuccinct.Oncethemonitorisrunningandyou’vedefinedthecolumnsyouwishto
see,therearefiveeventclassfilters,outlinedinblackinFigure3-2,thatyoucantoggletocleanupyourlogsevenfurther.Eventclassfiltersletyouchoosewhicheventstoshowinthelog,basedontype.Fromlefttoright,thesefiltersareasfollows:
RegistryShowsallregistryactivity.Therewillbealotofwhitenoiseintheregistryuponprocesscreation,asgamesrarelyusetheregistryandWindowslibrariesalwaysuseit.Leavingthisfilterdisabledcansavealotofspaceinthelog.
FilesystemShowsallfilesystemactivity.Thisisthemostimportanteventclassfilter,sinceknowingwheredatafilesarestoredandhowtheyareaccessedisintegraltowritinganeffectivebot.
NetworkShowsallnetworkactivity.Thecallstackonnetworkeventscanbeusefulinfindingnetwork-relatedcodewithinagame.
ProcessandthreadactivityShowsallprocessandthreadactions.Thecallstackontheseeventscangiveyouinsightintohowagame’scodehandlesthreads.
ProcessprofilingPeriodicallyshowsinformationaboutthememoryandCPUusageofeachrunningprocess;agamehackerwillrarelyuseit.
Ifclass-leveleventfilteringisstillnotpreciseenoughtofilteroutunwantedpollutioninyourlogs,right-clickonspecificeventsforevent-levelfilteringoptions.Onceyouhaveyoureventfilteringconfiguredtologonlywhatyouneed,youcanbeginnavigatingthelog.Table3-1listssomeusefulhotkeysforcontrollingthelog’sbehavior.
Table3-1:ProcessMonitorHotkeys
HotkeyAction
CTRL-E Toggleslogging.
CTRL-A Togglesautomaticscrollingofthelog.
CTRL-X Clearsthelog.
CTRL-L DisplaystheFilterdialog.
CTRL- DisplaystheHighlightdialog.Thisdialoglooksverysimilartothe
CTRL-H
DisplaystheHighlightdialog.ThisdialoglooksverysimilartotheFilterdialog,butitisusedtoindicatewhicheventsshouldbehighlighted.
CTRL-F DisplaystheSearchdialog.
CTRL-P DisplaystheEventPropertiesdialogfortheselectedevent.
Asyounavigatethelog,youcanexaminetheoperationsrecordedtoseethefine-graineddetailsofanevent.
InspectingEventsintheProcessMonitorLogProcessMonitorlogseverydatapointitpossiblycanaboutanevent,enablingyoutolearnmoreabouttheseeventsthanjustthefilestheyactupon.Carefullyinspectingdata-richcolumns,suchasResultandDetail,canyieldsomeveryinterestinginformation.
Forexample,I’vefoundthatgamessometimesreaddatastructures,elementbyelement,directlyfromfiles.Thisbehaviorisapparentwhenalogcontainsalargenumberofreadstothesamefile,whereeachreadhassequentialoffsetsbutdifferinglengths.ConsiderthehypotheticaleventlogshowninTable3-2.
Table3-2:ExampleEventLog
OperationPath Detail
CreateFileC:\file.datDesiredAccess:Read
ReadFile C:\file.datOffset:0Size:4
ReadFile C:\file.datOffset:4Size:2
ReadFile C:\file.datOffset:6Size:2
ReadFile C:\file.datOffset:8Size:4
ReadFile C:\file.datOffset:12Size:4
... ... ...Continuestoreadchunksof4bytesforawhile
Thislogrevealsthatthegameisreadingastructurefromthefilepiecebypiece,disclosingsomehintsaboutwhatthestructurelookslike.Forexample,let’ssaythatthesereadsreflectthefollowingdatafile:
structmyDataFile{intheader;//4bytes(offset0)shorteffectCount;//2bytes(offset4)shortitemCount;//2bytes(offset6)int*effects;int*items;};
ComparetheloginTable3-2withthisstructure.First,thegamereadsthe4headerbytes.Then,itreadstwo2-bytevalues:effectCountanditemCount.Itthencreatestwointegerarrays,effectsanditems,ofrespectivelengthseffectCountanditemCount.Thegamethenfillsthesearrayswithdatafromthefile,reading4byteseffectCount+itemCounttimes.
NOTE
Developersdefinitelyshouldn’tuseaprocesslikethistoreaddatafromafile,butyou’dbeamazedathowoftenithappens.Fortunatelyforyou,naïvetélikethisjustmakesyouranalysiseasier.
Inthiscase,theeventlogcanidentifysmallpiecesofinformationwithinafile.Butkeepinmindthat,whilecorrelatingthereadswiththeknownstructureiseasy,it’smuchhardertoreverseengineeranunknownstructurefromnothingbutaneventlog.Typically,gamehackerswilluseadebuggertogetmorecontextabouteachinterestingevent,andthedatafromProcessMonitorcanbeseamlesslyintegratedintoadebuggingsession,effectivelytyingtogetherthetwopowerfulreverseengineeringparadigms.
DebuggingaGametoCollectMoreDataLet’sstepawayfromthishypotheticalfilereadandlookathowProcessMonitorletsyoutransitionfromeventloggingtodebugging.ProcessMonitorstoresacompletestacktraceforeachevent,showingthefullexecutionchainthatledtotheeventbeingtriggered.YoucanviewthesestacktracesintheStacktaboftheEventPropertieswindow(double-clicktheeventorpressCTRL-P),asshowninFigure3-3.
Figure3-3:ProcessMonitoreventcallstack
ThestacktraceisdisplayedinatablestartingwithaFramecolumn➊,whichshowstheexecutionmodeandstackframeindex.ApinkKinthiscolumnmeansthecallhappenedinkernelmode,whileablueUmeansithappenedinusermode.Sincegamehackerstypicallyworkinusermode,kernelmodeoperationsareusuallymeaningless.
TheModulecolumn➋showstheexecutablemodulewherethecallingcodewaslocated.Eachmoduleisjustthenameofthebinarythatmadethecall;thismakesiteasytoidentifywhichcallswereactuallymadefromwithinagamebinary.
TheLocationcolumn➌showsthenameofthefunctionthatmadeeachcall,aswellasthecalloffset.Thesefunctionnamesarededucedfromthe
exporttableofthemoduleandwillgenerallynotbepresentforthefunctionswithinagamebinary.Whennofunctionnamesarepresent,theLocationcolumninsteadshowsthemodulenameandthecall’soffset(howmanybytespasttheoriginaddressthecallisinmemory)fromthemodule’sbaseaddress.
NOTE
Inthecontextofcode,theoffsetishowmanybytesofassemblycodearebetweenanitemanditsorigin.
TheAddresscolumn➍showsthecodeaddressofthecall,whichisveryusefulbecauseyoucanjumptotheaddressintheOllyDbgdisassembler.Finally,thePathcolumn➎showsthepathtothemodulethatmadethecall.
Inmyopinion,thestacktraceis,byfar,themostpowerfulfeatureinProcessMonitor.Itrevealstheentirecontextthatledtoanevent,whichcanbeimmenselyusefulwhenyouaredebuggingagame.Youcanuseittofindtheexactcodethattriggeredanevent,crawlupthecallchaintoseehowitgotthere,andevendetermineexactlywhatlibrarieswereusedtocompleteeachaction.
ProcessMonitor’ssisterapplication,ProcessExplorer,doesn’thavemanycapabilitiesbeyondthoseinProcessMonitororOllyDbg.Butitdoesexposesomeofthosecapabilitiesmuchmoreeffectively,makingitanidealpickincertainsituations.
FINDINGAHIGHSCOREFILEIfyou’rereadytotestyourProcessMonitorskills,you’vecometotherightplace.OpentheGameHackingExamples/Chapter3_FindingFilesdirectoryandexecuteFindingFiles.exe.You’llseethatitisagameofPong,liketheonein“Patchinganif()Statement”onpage46.UnlikeinChapter2,though,nowthegameisactuallyplayable.Italsodisplaysyourcurrentscoreandyourall-time-highscore.
Nowrestartthegame,firingupProcessMonitorbeforeexecutingitforthesecondtime.Filteringforfilesystemactivityandcreatinganyotherfiltersyouseefit,trytolocatewherethegamestoresthe
anyotherfiltersyouseefit,trytolocatewherethegamestoresthehigh-scorefile.Forbonuspoints,trytomodifythisfiletomakethegameshowthehighestpossiblescore.
ProcessExplorerProcessExplorerisanadvancedtaskmanager(itevenhasabuttonyoucanpresstomakeityourdefaulttaskmanager),andit’sveryhandywhenyou’restartingtounderstandhowagameoperates.Itprovidescomplexdataaboutrunningprocesses,suchasparentandchildprocesses,CPUusage,memoryusage,loadedmodules,openhandles,andcommandlinearguments,anditcanmanipulatethoseprocesses.Itexceedsatshowingyouhigh-levelinformation,suchasprocesstrees,memoryconsumption,fileaccess,andprocessIDs,allofwhichcanbeveryuseful.
Ofcourse,noneofthisdataisspecificallyusefulinisolation.Butwithakeeneye,youcanmakecorrelationsanddrawsomeusefulconclusionsaboutwhatglobalobjects—includingfiles,mutexes,andsharedmemorysegments—agamehasaccessto.Additionally,thedatashowninProcessExplorercanbeevenmorevaluablewhencross-referencedwithdatagatheredinadebuggingsession.
ThissectionintroducestheProcessExplorerinterface,discussesthepropertiesitshows,anddescribeshowyoucanusethistooltomanipulatehandles(referencestosystemresources).Afterthisintroduction,use“FindingandClosingaMutex”onpage60tohoneyourskills.
ProcessExplorer’sUserInterfaceandControlsWhenyouopenProcessExplorer,youseeawindowthatissplitintothreedistinctsections,asinFigure3-4.
Figure3-4:ProcessExplorermainwindow
Thosethreesectionsarethetoolbar➊,anupperpane➋,andalowerpane➌.Theupperpaneshowsalistofprocesses,utilizingatreestructuretodisplaytheirparent/childrelationships.Differentprocessesarehighlightedwithdifferentcolors;ifyoudon’tlikethecurrentcolors,clickOptions▸ConfigureColorstodisplayadialogthatallowsyoutoviewandchangethem.
JustasinProcessMonitor,thedisplayforthistableishighlyversatile,andyoucancustomizeitbyright-clickingonthetableheaderandchoosingSelectColumns.Thereareprobablymorethan100customizationoptions,butIfindthatthedefaultswiththeadditionoftheASLREnabledcolumnworkjustfine.
NOTE
AddressSpaceLayoutRandomization(ASLR)isaWindowssecurity
featurethatallocatesexecutableimagesatunpredictablelocations,andknowingwhetherit’sonisinvaluablewhenyou’retryingtoaltergamestatevaluesinmemory.
Thelowerpanehasthreepossiblestates:Hidden,DLLs,andHandles.TheHiddenoptionhidesthepanefromview,DLLsdisplaysalistofDynamicLinkLibrariesloadedwithinthecurrentprocess,andHandlesshowsalistofhandlesheldbytheprocess(visibleinFigure3-4).YoucanhideorunhidetheentirelowerpanebytogglingView▸ShowLowerPane.Whenitisvisible,youcanchangetheinformationdisplaybyselectingeitherView▸LowerPaneView▸DLLsorView▸LowerPaneView▸Handles.
Youcanalsousehotkeystoquicklychangebetweenlowerpanemodeswithoutaffectingprocessesintheupperpane.ThesehotkeysarelistedinTable3-3.
Table3-3:ProcessExplorerHotkeys
Hotkey Action
CTRL-F Searchthroughlowerpanedatasetsforavalue.
CTRL-L Togglethelowerpanebetweenhiddenandvisible.
CTRL-D TogglethelowerpanetodisplayDLLs.
CTRL-H Togglethelowerpanetodisplayhandles.
spacebar Toggleprocesslistautorefresh.ENTER DisplaythePropertiesdialogfortheselectedprocess.DEL Killtheselectedprocess.
SHIFT-DELKilltheselectedprocessandallchildprocesses.
UsetheGUIorhotkeystopracticechangingmodes.Whenyou’reacquaintedwiththemainwindow,we’lllookatanotherimportantProcessExplorerdialog,calledProperties.
ExaminingProcessPropertiesMuchlikeProcessMonitor,ProcessExplorerhasaverykineticapproachto
datagathering;theendresultisabroadandverbosespectrumofinformation.Infact,ifyouopenthePropertiesdialog(showninFigure3-5)foraprocess,you’llseeamassivetabbarcontaining10tabs.
TheImagetab,selectedbydefaultandshowninFigure3-5,displaystheexecutablename,version,builddate,andcompletepath.ItalsodisplaysthecurrentworkingdirectoryandtheAddressSpaceLayoutRandomizationstatusoftheexecutable.ASLRstatusisthemostimportantpieceofinformationhere,becauseithasadirecteffectonhowabotcanreadthememoryfromagame.I’lltalkaboutthismoreinChapter6.
Figure3-5:ProcessExplorerPropertiesdialog
ThePerformance,PerformanceGraph,DiskandNetwork,andGPUGraphtabsdisplayamyriadofmetricsabouttheCPU,memory,disk,network,andGPUusageoftheprocess.Ifyoucreateabotthatinjectsintoa
network,andGPUusageoftheprocess.Ifyoucreateabotthatinjectsintoagame,thisinformationcanbeveryusefultodeterminehowmuchofaperformanceimpactyourbothasonthegame.
TheTCP/IPtabdisplaysalistofactiveTCPconnections,whichyoucanusetofindanygameserverIPaddressesthatagameconnectsto.Ifyou’retryingtotestconnectionspeed,terminateconnections,orresearchagame’snetworkprotocol,thisinformationiscritical.
TheStringstabdisplaysalistofstringsfoundineitherthebinaryorthememoryoftheprocess.UnlikethestringlistinOllyDbg,whichshowsonlystringsreferencedbyassemblycode,thelistincludesanyoccurrencesofthreeormoreconsecutivereadablecharacters,followedbyanullterminator.Whenagamebinaryisupdated,youcanuseadiffingtoolonthislistfromeachgameversiontodeterminewhetherthereareanynewstringsthatyouwanttoinvestigate.
TheThreadstabshowsyoualistofthreadsrunningwithintheprocessandallowsyoutopause,resume,orkilleachthread;theSecuritytabdisplaysthesecurityprivilegesoftheprocess;andtheEnvironmenttabdisplaysanyenvironmentvariablesknowntoorsetbytheprocess.
NOTE
IfyouopenthePropertiesdialogfora.NETprocess,you’llnoticetwoadditionaltabs:.NETAssembliesand.NETPerformance.Thedatainthesetabsisprettyself-explanatory.Pleasekeepinmindthatamajorityofthetechniquesinthisbookwon’tworkwithgameswrittenin.NET.
HandleManipulationOptionsAsyou’veseen,ProcessExplorercanprovideyouwithawealthofinformationaboutaprocess.That’snotallit’sgoodfor,though:itcanalsomanipulatecertainpartsofaprocess.Forexample,youcanviewandmanipulateopenhandlesfromthecomfortofProcessExplorer’slowerpane(seeFigure3-4).ThisalonemakesastrongargumentforaddingProcessExplorertoyourtoolbox.Closingahandleisassimpleasright-clickingonitandselectingCloseHandle.Thiscancomeinhandywhenyouwant,forinstance,toclosemutexes,whichisessentialtocertaintypesofhacks.
NOTE
Youcanright-clickonthelowerpaneheaderandclickSelectColumnstocustomizethedisplay.OnecolumnyoumightfindparticularlyusefulisHandleValue,whichcanhelpwhenyouseeahandlebeingpassedaroundinOllyDbgandwanttoknowwhatitdoes.
ClosingMutexesGamesoftenallowonlyoneclienttorunatatime;thisiscalledsingle-instancelimitation.Youcanimplementsingle-instancelimitationinanumberofways,butusingasystemmutexiscommonbecausemutexesaresessionwideandcanbeaccessedbyasimplename.It’strivialtolimitinstanceswithmutexes,andthankstoProcessExplorer,it’sjustastrivialtoremovethatlimit,allowingyoutorunmultipleinstancesofagameatthesametime.
First,here’showagamemighttacklesingle-instancelimitationwithamutex:
intmain(intargc,char*argv[]){//createthemutexHANDLEmutex=CreateMutex(NULL,FALSE,"onlyoneplease");if(GetLastError()==ERROR_ALREADY_EXISTS){//themutexalreadyexists,soexitErrorBox("Aninstanceisalreadyrunning.");return0;}//themutexdidn'texist;itwasjustcreated,so//letthegamerunRunGame();//thegameisover;closethemutextofreeitup//forfutureinstancesif(mutex)CloseHandle(mutex);return0;}
Thisexamplecodecreatesamutexnamedonlyoneplease.Next,thefunctionchecksGetLastError()toseewhetherthemutexwasalreadycreated,andifso,itclosesthegame.Ifthemutexdoesn’talreadyexist,thegamecreatesthefirstinstance,therebyblockinganyfuturegameclientsfromrunning.Inthisexample,thegamerunsnormally,andonceitfinishes,
CloseHandle()iscalledtoclosethemutexandallowfuturegameinstancestorun.
YoucanuseProcessExplorertocloseinstance-limitingmutexesandrunmanygameinstancessimultaneously.Todoso,choosetheHandlesviewofthelowerpane,lookforallhandleswithatypeofMutant,determinewhichoneislimitinginstancesofthegame,andclosethatmutex.
WARNING
Mutexesarealsousedtosynchronizedataacrossthreadsandprocesses.Closeoneonlyifyou’resurethatitssolepurposeistheoneyou’retryingtosubvert!
Multiclienthacksaregenerallyinhighdemand,sobeingabletoquicklydevelopthemforemerginggamesiscrucialtoyouroverallsuccessasabotdeveloperwithinthatmarket.Sincemutexesareoneofthemostcommonwaystoachievesingle-instancelimitation,ProcessExplorerisanintegraltoolforprototypingthesekindsofhacks.
InspectingFileAccessesUnlikeProcessMonitor,ProcessExplorercan’tshowalistoffilesystemcalls.Ontheotherhand,theHandlesviewofProcessExplorer’slowerpanecanshowallfilehandlesthatagamecurrentlyhasopen,revealingexactlywhatfilesareincontinuoususewithouttheneedtosetupadvancedfilteringcriteriainProcessMonitor.JustlookforhandleswithatypeofFiletoseeallfilesthegameiscurrentlyusing.
Thisfunctionalitycancomeinhandyifyou’retryingtolocatelogfilesorsavefiles.Moreover,youcanlocatenamedpipesthatareusedforinterprocesscommunication(IPC);thesearefilesprefixedwith\Device\NamedPipe\.Seeingoneofthesepipesisoftenahintthatthegameistalkingtoanotherprocess.
FINDINGANDCLOSINGAMUTEXToputyourProcessExplorerskillstouse,gototheGameHackingExamples/Chapter3_CloseMutexdirectoryandexecute
CloseMutex.exe.Thisgameplaysexactlyliketheonein“FindingaHighScoreFile”onpage55,butitpreventsyoufromsimultaneouslyrunningmultipleinstances.Asyoumighthaveguessed,itdoesthisusingasingle-instance-limitationmutex.UsingProcessExplorer’sHandlesviewinthelowerpane,findthemutexresponsibleforthislimitationandcloseit.Ifyousucceed,you’llbeabletoopenasecondinstanceofthegame.
ClosingThoughtsTobeeffectivewhenusingProcessMonitorandProcessExplorer,youneed,aboveallelse,adeepfamiliaritywiththedatathattheseapplicationsdisplayaswellastheinterfacestheyusetodisplayit.Whilethischapter’soverviewisagoodbaseline,theintricaciesoftheseapplicationscanbelearnedonlythroughexperience,soIencourageyoutoplayaroundwiththemonyoursystem.
Youwon’tusethesetoolsonaregularbasis,butatsomepoint,they’llsavetheday:asyoustruggletofigureouthowsomecodeworks,you’llrecallanobscurepieceofinformationthatcaughtyoureyeduringapreviousProcessExplorerorProcessMonitorsession.That’swhyIconsiderthemusefulreconnaissancetools.
PART2GAMEDISSECTION
4FROMCODETOMEMORY:AGENERAL
PRIMER
Atthelowestlevel,agame’scode,data,input,andoutputarecomplexabstractionsoferraticallychangingbytes.Manyofthesebytesrepresentvariablesormachinecodegeneratedbyacompilerthatwasfedthegame’ssourcecode.Somerepresentimages,models,andsounds.Othersexistonlyforaninstant,postedbythecomputer’shardwareasinputanddestroyedwhenthegamefinishesprocessingthem.Thebytesthatremaininformtheplayerofthegame’sinternalstate.Buthumanscan’tthinkinbytes,sothecomputermusttranslatetheminawaywecanunderstand.
There’sahugedisconnectintheoppositedirectionaswell.Acomputerdoesn’tactuallyunderstandhigh-levelcodeandvisceralgamecontent,sothesemustbetranslatedfromtheabstractintobytes.Somecontent—suchasimages,sounds,andtext—isstoredlosslessly,readytobepresentedtotheplayeratamicrosecond’snotice.Agame’scode,logic,andvariables,ontheotherhand,arestrippedofallhumanreadabilityandcompileddowntomachinedata.
Bymanipulatingagame’sdata,gamehackersobtainhumanlyimprobableadvantageswithinthegame.Todothis,however,theymustunderstandhowadeveloper’scodemanifestsonceithasbeencompiledandexecuted.Essentially,theymustthinklikecomputers.
Togetyouthinkinglikeacomputer,thischapterwillbeginbyteachingyouhownumbers,text,simplestructures,andunionsarerepresentedin
youhownumbers,text,simplestructures,andunionsarerepresentedinmemoryatthebytelevel.Thenyou’lldivedeepertoexplorehowclassinstancesarestoredinmemoryandhowabstractinstancesknowwhichvirtualfunctionstocallatruntime.Inthelasthalfofthechapter,you’lltakeanx86assemblylanguagecrashcoursethatcoverssyntax,registers,operands,thecallstack,arithmeticoperations,branchingoperations,functioncalls,andcallingconventions.
Thischapterfocusesveryheavilyongeneraltechnicaldetails.Thereisn’talotofjuicyinformationthatimmediatelyrelatestohackinggames,buttheknowledgeyougainherewillbecentralinthecomingchapters,whenwetalkabouttopicslikeprogrammaticallyreadingandwritingmemory,injectingcode,andmanipulatingcontrolflow.
SinceC++isthedefactostandardforbothgameandbotdevelopment,thischapterexplainstherelationshipsbetweenC++codeandthememorythatrepresentsit.Mostnativelanguageshaveverysimilar(sometimesidentical)low-levelstructureandbehavior,however,soyoushouldbeabletoapplywhatyoulearnheretojustaboutanypieceofsoftware.
AlloftheexamplecodeinthischapterisintheGameHackingExamples/Chapter4_CodeToMemorydirectoryofthisbook’ssourcefiles.TheincludedprojectscanbecompiledwithVisualStudio2010butshouldalsoworkwithanyotherC++compiler.Downloadthemathttps://www.nostarch.com/gamehacking/andcompilethemifyouwanttofollowalong.
HowVariablesandOtherDataManifestinMemoryProperlymanipulatingagame’sstatecanbeveryhard,andfindingthedatathatcontrolsitisnotalwaysaseasyasclickingNextScanandhopingCheatEnginewon’tfailyou.Infact,manyhacksmustmanipulatedozensofrelatedvaluesatonce.Findingthesevaluesandtheirrelationshipsoftenrequiresyoutoanalyticallyidentifystructuresandpatterns.Moreover,developinggamehackstypicallymeansre-creatingtheoriginalstructureswithinyourbot’scode.
Todothesethings,youneedanin-depthunderstandingofexactlyhowvariablesanddataarelaidoutinthegame’smemory.Throughexample
variablesanddataarelaidoutinthegame’smemory.Throughexamplecode,OllyDbgmemorydumps,andsometablestotieeverythingtogether,thissectionwillteachyoueverythingthereistoknowabouthowdifferenttypesofdatamanifestinmemory.
NumericDataMostofthevaluesgamehackersneed(liketheplayer’shealth,mana,location,andlevel)arerepresentedbynumericdatatypes.Becausenumericdatatypesarealsoabuildingblockforallotherdatatypes,understandingthemisextremelyimportant.Luckily,theyhaverelativelystraightforwardrepresentationsinmemory:theyarepredictablyalignedandhaveafixedbitwidth.Table4-1showsthefivemainnumericdatatypesyou’llfindinWindowsgames,alongwiththeirsizesandranges.
Table4-1:NumericDataTypes
Typename(s)
SizeSignedrange Unsignedrange
char,BYTE 8bits
-128to127 0to255
short,WORD,wchar_t
16bits
-32,768to-32,767 0to65535
int,long,DWORD
32bits
-2,147,483,648to2,147,483,647
0to4,294,967,295
longlong 64bits
-9,223,372,036,854,775,808to9,223,372,036,854,775,807
0to18,446,744,073,709,551,615
float 32bits
+/-1.17549*10-38to+/-3.40282*1038
N/A
Thesizesofnumericdatatypescandifferbetweenarchitecturesandevencompilers.Sincethisbookfocusesonhackingx86gamesonWindows,I’musingtypenamesandsizesmadestandardbyMicrosoft.Withtheexceptionoffloat,thedatatypesinTable4-1arestoredwithlittle-endianordering,
meaningtheleastsignificantbytesofanintegerarestoredinthelowestaddressesoccupiedbythatinteger.Forexample,Figure4-1showsthatDWORD0x0A0B0C0Disrepresentedbythebytes0x0D0x0C0x0B0x0A.
Figure4-1:Little-endianorderingdiagram
Thefloatdatatypecanholdmixednumbers,soitsrepresentationinmemoryisn’tassimpleasthatofotherdatatypes.Forexample,ifyousee0x0D0x0C0x0B0x0Ainmemoryandthatvalueisafloat,youcan’tsimplyconvertitto0x0A0B0C0D.Instead,floatvalueshavethreecomponents:thesign(bit0),exponent(bits1–8),andmantissa(bits9–31).
Thesigndetermineswhetherthenumberisnegativeorpositive,theexponentdetermineshowmanyplacestomovethedecimalpoint(startingbeforethemantissa),andthemantissaholdsanapproximationofthevalue.Youcanretrievethestoredvaluebyevaluatingtheexpressionmantissa×10n
(wherenistheexponent)andmultiplyingtheresultby–1ifthesignisset.Nowlet’slookatsomenumericdatatypesinmemory.Listing4-1
initializesninevariables.
unsignedcharubyteValue=0xFF;charbyteValue=0xFE;unsignedshortuwordValue=0x4142;shortwordValue=0x4344;unsignedintudwordValue=0xDEADBEEF;intdwordValue=0xDEADBEEF;unsignedlonglongulongLongValue=0xEFCDAB8967452301;longlonglongLongValue=0xEFCDAB8967452301;floatfloatValue=1337.7331;
Listing4-1:CreatingvariablesofnumericdatatypesinC++
Startingfromthetop,thisexampleincludesvariablesoftypeschar,short,int,longlong,andfloat.Fouroftheseareunsigned,andfivearesigned.(InC++,afloatcan’tbeunsigned.)Takingintoaccountwhatyou’velearnedsofar,carefullystudytherelationshipbetweenthecodeinListing4-1andthememorydumpinFigure4-2.Assumethatthevariablesaredeclaredinglobalscope.
Figure4-2:OllyDbgmemorydumpofournumericdata
Youmightnoticethatsomevaluesseemarbitrarilyspacedout.Sinceit’smuchfasterforprocessorstoaccessvaluesresidingataddressesthataremultiplesoftheaddresssize(whichis32bitsinx86),compilerspadvalueswithzerosinordertoalignthemonsuchaddresses—hence,paddingisalsocalledalignment.Single-bytevaluesarenotpadded,sinceoperationsthataccessthemperformthesameregardlessofalignment.
Keepingthisinmind,takealookatTable4-2,whichprovidesasortofmemory-to-codecrosswalkbetweenthememorydumpinFigure4-2andthevariablesdeclaredinListing4-1.
Table4-2:Memory-to-CodeCrosswalkforListing4-1andFigure4-2
Address Size Data Object
0x00BB3018 1byte
0xFF ubyteValue
0x00BB3019 1byte
0xFE byteValue
0x00BB301A 2bytes
0x000x00 PaddingbeforeuwordValue
0x00BB301C 2bytes
0x420x41 uwordValue
0x00BB301E 2bytes
0x000x00 PaddingbeforewordValue
bytes wordValue
0x00BB3020 2bytes
0x440x43 wordValue
0x00BB3022 2bytes
0x000x00 PaddingbeforeudwordValue
0x00BB3024 4bytes
0xEF0xBE0xAD0xDE udwordValue
0x00BB3028 4bytes
0xEF0xBE0xAD0xDE dwordValue
0x00BB302C 4bytes
0x760x370xA70x44 floatValue
0x00BB3030 8bytes
0x010x230x450x670x890xAB0xCD0xEF
ulongLongValue
0x00BB3038 8bytes
0x010x230x450x670x890xAB0xCD0xEF
LongLongValue
TheAddresscolumnlistslocationsinmemory,andtheDatacolumntellsyouexactlywhat’sstoredthere.TheObjectcolumntellsyouwhichvariablefromListing4-1eachpieceofdatarelatesto.NoticethatfloatValueisplacedbeforeulongLongValueinmemory,eventhoughit’sthelastvariabledeclaredinListing4-1.Becausethesevariablesaredeclaredinglobalscope,thecompilercanplacethemwhereveritwants.Thisparticularmoveislikelyaresultofeitheralignmentoroptimization.
StringDataMostdevelopersusethetermstringasifit’ssynonymouswithtext,buttextisonlythemostcommonuseforstrings.Atalowlevel,stringsarejustarraysofarbitrarynumericobjectsthatappearlinearandunalignedinmemory.Listing4-2showsfourtextstringdeclarations.
//charwillbe1bytepercharacterchar*thinStringP="my_thin_terminated_value_pointer";charthinStringA[40]="my_thin_terminated_value_array";
//wchar_twillbe2bytespercharacter
//wchar_twillbe2bytespercharacterwchar_t*wideStringP=L"my_wide_terminated_value_pointer";wchar_twideStringA[40]=L"my_wide_terminated_value_array";
Listing4-2:DeclaringseveralstringsinC++
Inthecontextoftext,stringsholdcharacterobjects(charfor8-bitencodingorwchar_tfor16-bitencoding),andtheendofeachstringisspecifiedbyanullterminator,acharacterequalto0x0.Let’slookatthememorywherethesevariablesarestored,asshowninthetwomemorydumpsinFigure4-3.
Figure4-3:InthisOllyDbgmemorydumpofstringdata,thehuman-readabletextintheASCIIcolumnisthetextwestoredinListing4-2.
Ifyou’renotusedtoreadingmemory,theOllyDbgdumpmightbeabitdifficulttofollowatthispoint.Table4-3showsadeeperlookatthecorrelationbetweenthecodeinListing4-2andthememoryinFigure4-3.
Table4-3:Memory-to-CodeCrosswalkforListing4-2andFigure4-3
Address Size Data Object
Pane1
0x012420F8 32bytes
0x6D0x790x5F{...}0x740x650x72
thinStringPcharacters
0x01242118 4bytes
0x000x000x000x00 thinStringPterminatorandpadding
0x0124211C 4bytes
0x000x000x000x00 Unrelateddata
0x01242120 64bytes
0x6D0x000x79{...}0x000x720x00
wideStringPcharacters
0x01242160 4bytes
0x000x000x000x00 wideStringPterminatorandpadding
{...} Unrelateddata
Pane2
0x01243040 4bytes
0xF80x200x240x01 PointertothinStringPat0x012420F8
0x01243044 30bytes
0x6D0x790x5F{...}0x720x610x79
thinStringAcharacters
0x01243062 10bytes
0x00repeated10times thinStringAterminatorandarrayfill
0x0124306C 4bytes
0x200x210x240x01 PointertowideStringPat0x01242120
0x01243070 60bytes
0x6D0x000x79{...}0x000x790x00
wideStringAcharacters
0x012430AC 20bytes
0x00repeated10times wideStringAterminatorandarrayfill
InFigure4-3,pane1showsthatthevaluesstoredwherethinStringP(address0x01243040)andwideStringP(address0x0124306C)belonginmemoryareonly4byteslongandcontainnostringdata.That’sbecausethesevariablesareactuallypointerstothefirstcharactersoftheirrespectivearrays.Forexample,thinStringPcontains0x012420F8,andinpane2inFigure4-3,youcansee"my_thin_terminated_value_pointer"locatedataddress0x012420F8.
Lookatthedatabetweenthesepointersinpane1,andyoucanseethetextbeingstoredbythinStringAandwideStringA.Furthermore,noticethatthinStringAandwideStringAarepaddedbeyondtheirnullterminators;thisisbecausethesevariablesweredeclaredasarrayswithlength40,sotheyarefilledupto40characters.
DataStructuresUnlikethedatatypeswehavepreviouslydiscussed,structuresarecontainersthatholdmultiplepiecesofsimple,relateddata.Gamehackerswhoknowhowtoidentifystructuresinmemorycanmimicthosestructuresintheirowncode.Thiscangreatlyreducethenumberofaddressestheymustfind,astheyneedtofindonlytheaddresstothestartofthestructure,nottheaddressofeveryindividualitem.
NOTE
Thissectiontalksaboutstructuresassimplecontainersthatlackmemberfunctionsandcontainonlysimpledata.Objectsthatexceedtheselimitationswillbediscussedin“ClassesandVFTables”onpage74.
StructureElementOrderandAlignmentSincestructuressimplyrepresentanassortmentofobjects,theydon’tvisiblymanifestinmemorydumps.Instead,amemorydumpofastructureshowstheobjectsthatarecontainedwithinthatstructure.ThedumpwouldlookmuchliketheothersI’veshowninthischapter,butwithimportantdifferencesinbothorderandalignment.
Toseethesedifferences,startbytakingalookatListing4-3.
structMyStruct{unsignedcharubyteValue;charbyteValue;unsignedshortuwordValue;shortwordValue;unsignedintudwordValue;intdwordValue;unsignedlonglongulongLongValue;longlonglongLongValue;floatfloatValue;};MyStruct&m=0;printf("Offsets:%d,%d,%d,%d,%d,%d,%d,%d,%d\n",&m->ubyteValue,&m->byteValue,&m->uwordValue,&m->wordValue,&m->udwordValue,&m->dwordValue,&m->ulongLongValue,&m->longLongValue,&m->floatValue);
Listing4-3:AC++structureandsomecodethatusesit
ThiscodedeclaresastructurenamedMyStructandcreatesavariablenamedmthatsupposedlypointstoaninstanceofthestructureataddress0.There’snotactuallyaninstanceofthestructureataddress0,butthistrickletsmeusetheampersandoperator(&)intheprintf()calltogettheaddressofeachmemberofthestructure.Sincethestructureislocatedataddress0,theaddressprintedforeachmemberisequivalenttoitsoffsetfromthestartofthestructure.
Theultimatepurposeofthisexampleistoseeexactlyhoweachmemberislaidoutinmemory,relativetothestartofthestructure.Ifyouweretorunthecode,you’dseethefollowingoutput:
Offsets:0,1,2,4,8,12,16,24,32
Asyoucansee,thevariablesinMyStructareorderedexactlyastheyweredefinedincode.Thissequentialmemberlayoutisamandatorypropertyofstructures.ComparethistotheexamplefromListing4-1,whenwedeclaredanidenticalsetofvariables;inthememorydumpfromFigure4-2,thecompilerclearlyplacedsomevaluesoutoforderinmemory.
Furthermore,youmayhavenoticedthatthemembersarenotalignedlikethegloballyscopedvariablesinListing4-1;iftheywere,forexample,therewouldbe2paddingbytesbeforeuwordValue.Thisisbecausestructuremembersarealignedonaddressesdivisiblebyeitherthestructmemberalignment(acompileroptionthataccepts1,2,4,8,or16bytes;inthisexample,it’ssetto4)orthesizeofthemember—whicheverissmaller.IarrangedthemembersofMyStructsothatthecompilerdidn’tneedtopadthevalues.
If,however,weputacharimmediatelyafterulongLongValue,theprintf()callwouldgivethefollowingoutput:
Offsets:0,1,2,4,8,12,16,28,36
Now,takealookattheoriginalandthemodifiedoutputstogether:
Original:Offsets:0,1,2,4,8,12,16,24,32Modified:Offsets:0,1,2,4,8,12,16,28,36
Inthemodifiedversion,thelasttwovalues,whicharetheoffsetsfor
longLongValueandfloatValuefromthestartofthestructure,havechanged.Thankstothestructmemberalignment,thevariablelongLongValuemovesby4bytes(1forthecharvalueand3followingit)toensureitgetsplacedonanaddressdivisibleby4.
HowStructuresWorkUnderstandingstructures—howtheyarealignedandhowtomimicthem—canbeveryuseful.Forinstance,ifyoureplicateagame’sstructuresinyourowncode,youcanreadorwritethoseentirestructuresfrommemoryinasingleoperation.Consideragamethatdeclarestheplayer’scurrentandmaxhealthlikeso:
struct{intcurrent;intmax;}vital;vitalhealth;
Ifaninexperiencedgamehackerwantstoreadthisinformationfrommemory,theymightwritesomethinglikethistofetchthehealthvalues:
intcurrentHealth=readIntegerFromMemory(currentHealthAddress);intmaxHealth=readIntegerFromMemory(maxHealthAddress);
Thisgamehackerdoesn’trealizethatseeingthesevaluesrightnexttoeachotherinmemorycouldbemorethanaluckyhappenstance,sothey’veusedtwoseparatevariables.Butifyoucamealongwithyourknowledgeofstructures,youmightconcludethat,sincethesevaluesarecloselyrelatedandareadjacentinmemory,ourhackercouldhaveusedastructureinstead:
struct{intcurrent;intmax;}_vital;➊_vitalhealth=readTypeFromMemory<_vital>(healthStructureAddress);
Sincethiscodeassumesastructureisbeingusedandcorrectlymimicsit,itcanfetchbothhealthandmaxhealthinjustoneline➊.We’lldivedeeperintohowtowriteyourowncodetoreadmemoryfrominChapter6.
UnionsUnlikestructures,whichencapsulatemultiplepiecesofrelateddata,unionscontainasinglepieceofdatathatisexposedthroughmultiplevariables.Unionsfollowthreerules:
•Thesizeofaunioninmemoryisequaltothatofitslargestmember.
•Membersofaunionallreferencethesamememory.
•Aunioninheritsthealignmentofitslargestmember.
Theprintf()callinthefollowingcodehelpsillustratethefirsttworules:
union{BYTEbyteValue;struct{WORDfirst;WORDsecond;}words;DWORDvalue;}dwValue;dwValue.value=0xDEADBEEF;printf("Size%d\nAddresses0x%x,0x%x\nValues0x%x,0x%x\n",sizeof(dwValue),&dwValue.value,&dwValue.words,dwValue.words.first,dwValue.words.second);
Thiscalltoprintf()outputsthefollowing:
Size4Addresses0x2efda8,0x2efda8Values0xbeef,0xdead
ThefirstruleisillustratedbytheSizevalue,whichisprintedfirst.EventhoughdwValuehasthreemembersthatoccupyatotalof9bytes,ithasasizeofonly4bytes.Thesizeresultvalidatesthesecondruleaswell,becausedwValue.valueanddwValue.wordsbothpointtoaddress0x2efda8,asshownbythevaluesprintedafterthewordAddresses.ThesecondruleisalsovalidatedbythefactthatdwValue.words.firstanddwValue.words.secondcontain0xbeefand0xdead,printedafterValues,whichmakessenseconsideringthatdwValue.valueis0xdeadbeef.Thethirdruleisn’tdemonstratedinthisexamplebecausewedon’thaveenoughmemorycontext,butifyouweretoputthisunioninsideastructureandsurrounditwithwhatevertypesyoulike,itwouldinfactalwaysalignlikeaDWORD.
ClassesandVFTablesMuchlikestructures,classesarecontainersthatholdandisolatemultiplepiecesofdata,butclassescanalsocontainfunctiondefinitions.
ASimpleClassClasseswithnormalfunctions,suchasbarinListing4-4,conformtothesamememorylayoutsasstructures.
classbar{public:bar():bar1(0x898989),bar2(0x10203040){}voidmyfunction(){bar1++;}intbar1,bar2;};
bar_bar=bar();printf("Size%d;Address0x%x:_bar\n",sizeof(_bar),&_bar);
Listing4-4:AC++class
Theprintf()callinListing4-4wouldoutputthefollowing:
Size8;Address0x2efd80:_bar
Eventhoughbarhastwomemberfunctions,thisoutputshowsthatitspansonlythe8bytesneededtoholdbar1andbar2.Thisisbecausethebarclassdoesn’tincludeabstractionsofthosememberfunctions,sotheprogramcancallthemdirectly.
NOTE
Accesslevelssuchaspublic,private,andprotecteddonotmanifestinmemory.Regardlessofthesemodifiers,membersofclassesarestillorderedastheyaredefined.
AClasswithVirtualFunctionsInclassesthatdoincludeabstractfunctions(oftencalledvirtualfunctions),theprogrammustknowwhichfunctiontocall.ConsidertheclassdefinitionsinListing4-5:
classfoo{public:foo():myValue1(0xDEADBEEF),myValue2(0xBABABABA){}intmyValue1;staticintmyStaticValue;virtualvoidbar(){printf("callfoo::bar()\n");}virtualvoidbaz(){printf("callfoo::baz()\n");}virtualvoidbarbaz(){}intmyValue2;};
intfoo::myStaticValue=0x12121212;
classfooa:publicfoo{public:fooa():foo(){}virtualvoidbar(){printf("callfooa::bar()\n");}virtualvoidbaz(){printf("callfooa::baz()\n");}};
classfoob:publicfoo{public:foob():foo(){}virtualvoidbar(){printf("callfoob::bar()\n");}virtualvoidbaz(){printf("callfoob::baz()\n");}};
Listing4-5:Thefoo,fooa,andfoobclasses
Theclassfoohasthreevirtualfunctions:bar,baz,andbarbaz.Classesfooaandfoobinheritfromclassfooandoverloadbothbarandbaz.Sincefooaandfoobhaveapublicbaseclassoffoo,afoopointercanpointtothem,buttheprogrammuststillcallthecorrectversionsofbarandbaz.Youcanseethisbyexecutingthefollowingcode:
foo*_testfoo=(foo*)newfooa();_testfoo->bar();//callsfooa::bar()
Andhereistheoutput:
callfooa::bar()
Theoutputshowsthat_testfoo->bar()invokedfooa::bar()eventhough_testfooisafoopointer.Theprogramknewwhichversionofthefunctiontocall,becausethecompilerincludedaVF(virtualfunction)tableinthememoryof_testfoo.VFtablesarearraysoffunctionaddressesthatabstractclassinstancesusetotellaprogramwheretheiroverloadedfunctionsare
located.
ClassInstancesandVirtualFunctionTablesTounderstandtherelationshipbetweenclassinstancesandVFtables,let’sinspectamemorydumpofthethreeobjectsdeclaredinthislisting:
foo_foo=foo();fooa_fooa=fooa();foob_foob=foob();
TheseobjectsareofthetypesdefinedinListing4-5.YoucanseetheminmemoryinFigure4-4.
Figure4-4:OllyDbgmemorydumpofclassdata
Pane1showsthateachclassinstancestoresitsmembersjustlikeastructure,butitprecedesthemwithaDWORDvaluethatpointstotheclassinstance’sVFtable.Pane2showstheVFtablesforeachofourthreeclassinstances.Thememory-to-codecrosswalkinTable4-4showshowthesepanesandthecodetietogether.
Table4-4:Memory-to-CodeCrosswalkforListing4-5andFigure4-4
Address Size Data Object
Pane1
0x0018FF20 4bytes
0x004022B0 Startof_fooandpointertofooVFtable
0x0018FF24 8bytes
0xDEADBEEF0xBABABABA
_foo.myValue1and_foo.myValue2
0x0018FF2C 4bytes
0x004022C0 Startof_fooaandpointertofooaVFtable
0x0018FF30 8bytes
0xDEADBEEF0xBABABABA
_fooa.myValue1and_fooa.myValue2
0x0018FF38 4bytes
0x004022D0 Startof_foobandpointertofoobVFtable
0x0018FF3C 8bytes
0xDEADBEEF0xBABABABA
_foob.myValue1and_foob.myValue2
{...} Unrelateddata
Pane2
0x004022B0 4bytes
0x00401060 StartoffooVFtable;addressoffoo::bar
0x004022B4 4bytes
0x00401080 Addressoffoo::baz
0x004022B8 4bytes
0x004010A0 Addressoffoo::barbaz
0x004022BC 4bytes
0x0040243C Unrelateddata
0x004022C0 4bytes
0x004010D0 StartoffooaVFtable;addressoffooa::bar
0x004022C4 4bytes
0x004010F0 Addressoffooa::baz
0x004022C8 4bytes
0x004010A0 Addressoffoo::barbaz
0x004022CC 4bytes
0x004023F0 Unrelateddata
0x004022D0 4bytes
0x00401130 StartoffoobVFtable;addressoffoob::bar
0x004022D4 4bytes
0x00401150 Addressoffoob::baz
bytes
0x004022D8 4bytes
0x004010A0Addressoffoo::barbaz
ThiscrosswalkshowshowtheVFtablesforthecodeinListing4-5arelaidoutinmemory.EachVFtableisgeneratedbythecompilerwhenthebinaryismade,andthetablesremainconstant.Tosavespace,instancesofthesameclassallpointtothesameVFtable,whichiswhytheVFtablesaren’tplacedinlinewiththeclass.
SincewehavethreeVFtables,youmightwonderhowaclassinstanceknowswhichVFtabletouse.Thecompilerplacescodesimilartothefollowingbitofassemblyineachvirtualclassconstructor:
MOVDWORDPTRDS:[EAX],VFADDR
ThisexampletakesthestaticaddressofaVFtable(VFADDR)andplacesitinmemoryasthefirstmemberoftheclass.
Nowlookataddresses0x004022B0,0x004022C0,and0x004022D0inTable4-4.Theseaddressescontainthebeginningofthefoo,fooa,andfoobVFtables.Noticethatfoo::barbazexistsinallthreeVFtables;thisisbecausethefunctionisnotoverloadedbyeithersubclass,meaninginstancesofeachsubclasswillcalltheoriginalimplementationdirectly.
Notice,too,thatfoo::myStaticValuedoesnotappearinthiscrosswalk.Sincethevalueisstatic,itdoesn’tactuallyneedtoexistasapartofthefooclass;it’splacedinsidethisclassonlyforbettercodeorganization.Inreality,itgetstreatedlikeaglobalvariableandisplacedelsewhere.
VFTABLESANDCHEATENGINERememberCheatEngine’sFirstelementofpointerstructmustpointtomoduleoptionforpointerscansfromFigure1-4onpage14?Nowthatyou’vereadabitaboutVFtables,thatknowledgeshouldhelpyouunderstandhowthisoptionworks:itmakesCheatEngineignoreallheapchunkswherethefirstmemberisnotapointertoavalidVFtable.Itspeedsupscans,butitworksonlyifeverystepinapointerpathispartofanabstractclassinstance.
Thememorytourendshere,butifyouhavetroubleidentifyingachunkofdatainthefuture,comebacktothissectionforreference.Next,we’lllookathowacomputercanunderstandagame’shigh-levelsourcecodeinthefirstplace.
x86AssemblyCrashCourseWhenaprogram’ssourcecodeiscompiledintoabinary,itisstrippedofallunnecessaryartifactsandtranslatedintomachinecode.Thismachinecode,madeupofonlybytes(commandbytesarecalledopcodes,buttherearealsobytesrepresentingoperands),getsfeddirectlytotheprocessorandtellsitexactlyhowtobehave.Those1sand0sfliptransistorstocontrolcomputation,andtheycanbeextremelydifficulttounderstand.Tomakecomputersalittleeasiertotalkto,engineersworkingwithsuchcodeuseassemblylanguage,ashorthandthatrepresentsrawmachineopcodeswithabbreviatednames(calledmnemonics)andasimplisticsyntax.
Assemblylanguageisimportantforgamehackerstoknowbecausemanypowerfulhackscanbeachievedonlythroughdirectmanipulationofagame’sassemblycode,viamethodssuchasNOPingorhooking.Inthissection,you’lllearnthebasicsofx86assemblylanguage,aspecificflavorofassemblymadeforspeakingto32-bitprocessors.Assemblylanguageisveryextensive,soforthesakeofbrevitythissectiontalksonlyaboutthesmallsubsetofassemblyconceptsthataremostusefultogamehackers.1
NOTE
Throughoutthissection,manysmallsnippetsofassemblycodeincludecommentssetoffbyasemicolon(;)todescribeeachinstructioningreaterdetail.
CommandSyntaxAssemblylanguageisusedtodescribemachinecode,soitssyntaxisprettysimplistic.Whilethissyntaxmakesitveryeasyforsomeonetounderstandindividualcommands(alsocalledoperations),italsomakesunderstanding
complexblocksofcodeveryhard.Evenalgorithmsthatareeasilyreadableinhigh-levelcodeseemobfuscatedwhenwritteninassembly.Forexample,thefollowingsnippetofpseudocode:
if(EBX>EAX)ECX=EDXelseECX=0
wouldlooklikeListing4-6inx86assembly.
CMPEBX,EAXJGlabel1MOVECX,0JMPlabel2label1:MOVECX,EDXlabel2:
Listing4-6:Somex86assemblycommands
Therefore,ittakesextensivepracticetounderstandeventhemosttrivialfunctionsinassembly.Understandingindividualcommands,however,isverysimple,andbytheendofthissection,you’llknowhowtoparsethecommandsIjustshowedyou.
InstructionsThefirstpartofanassemblycommandiscalledaninstruction.Ifyouequateanassemblycommandtoaterminalcommand,theinstructionistheprogramtorun.Atthemachinecodelevel,instructionsaretypicallythefirstbyteofacommand;2therearealsosome2-byteinstructions,wherethefirstbyteis0x0F.Regardless,aninstructiontellstheprocessorexactlywhattodo.InListing4-6,CMP,JG,MOV,andJMPareallinstructions.
OperandSyntaxWhilesomeinstructionsarecompletecommands,thevastmajorityareincompleteunlessfollowedbyoperands,orparameters.EverycommandinListing4-6hasatleastoneoperand,likeEBX,EAX,andlabel1.
Assemblyoperandscomeinthreeforms:
ImmediatevalueAnintegervaluethatisdeclaredinline(hexadecimalvalueshaveatrailingh).
RegisterAnamethatreferstoaprocessorregister.
MemoryoffsetAnexpression,placedinbrackets,thatrepresentsthememorylocationofavalue.Theexpressioncanbeanimmediatevalueoraregister.Alternatively,itcanbeeitherthesumordifferenceofaregisterandimmediatevalue(somethinglike[REG+Ah]or[REG-10h]).
Eachinstructioninx86assemblycanhavebetweenzeroandthreeoperands,andcommasareusedtoseparatemultipleoperands.Inmostcases,instructionsthatrequiretwooperandshaveasourceoperandandadestinationoperand.Theorderingoftheseoperandsisdependentontheassemblysyntax.Forexample,Listing4-7showsagroupofpseudocommandswrittenintheIntelsyntax,whichisusedbyWindows(and,thus,byWindowsgamehackers):
MOVR1,1;setR1(register)to1(immediate)➊MOVR1,[BADF00Dh];setR1tovalueat[BADFOODh](memoryoffset)MOVR1,[R2+10h];setR1tovalueat[R2+10h](memoryoffset)MOVR1,[R2-20h];setR1tovalueat[R2+20h](memoryoffset)
Listing4-7:DemonstratingIntelsyntax
IntheIntelsyntax,thedestinationoperandcomesfirst,followedbythesource,soat➊,R1isthedestinationand[BADFOODh]isthesource.Ontheotherhand,compilerslikeGCC(whichcanbeusedtowritebotsonWindows)useasyntaxknownasAT&T,orUNIX,syntax.Thissyntaxdoesthingsalittledifferently,asyoucanseeinthefollowingexample:
MOV$1,%R1;setR1(register)to1(immediate)MOV0xBADF00D,%R1;setR1tovalueat0xBADFOOD(memoryoffset)MOV0x10(%R2),%R1;setR1tovalueat0x10(%R2)(memoryoffset)MOV-0x20(%R2),%R1;setR1tovalueat-0x20(%R2)(memoryoffset)
ThiscodeistheAT&TversionofListing4-7.AT&Tsyntaxnotonlyreversestheoperandorderbutalsorequiresoperandprefixingandhasadifferentformatformemoryoffsetoperands.
AssemblyCommandsOnceyouunderstandassemblyinstructionsandhowtoformattheir
Onceyouunderstandassemblyinstructionsandhowtoformattheiroperands,youcanstartwritingcommands.Thefollowingcodeshowsanassemblyfunction,consistingofsomeverybasiccommands,thatessentiallydoesnothing.
PUSHEBP;putEBP(register)onthestackMOVEBP,ESP;setEBPtovalueofESP(register,topofstack)PUSH-1;put-1(immediate)onthestackADDESP,4;negatethe'PUSH-1'toputESPbackwhereitwas(aPUSH;subtracts4fromESP,sinceitgrowsthestack)MOVESP,EBP;setESPtothevalueofEBP(theywillbethesameanyway,;sincewehavekeptESPinthesameplace)POPEBP;setEBPtothevalueontopofthestack(itwillbewhat;EBPstartedwith,putonthestackbyPUSHEBP)XOREAX,EAX;exclusive-orEAX(register)withitself(sameeffectas;'MOVEAX,0'butmuchfaster)RETN;returnfromthefunctionwithavalueof0(EAXtypically;holdsthereturnvalue)
Thefirsttwolines,aPUSHcommandandaMOVcommand,setupastackframe.Thenextlinepushes–1tothestack,whichisundonewhenthestackissetbacktoitsoriginalpositionbytheADDESP,4command.Followingthat,thestackframeisremoved,thereturnvalue(storedinEAX)issetto0withanXORinstruction,andthefunctionreturns.
You’lllearnmoreaboutstackframesandfunctionsin“TheCallStack”onpage86and“FunctionCalls”onpage94.Fornow,turnyourattentiontotheconstantsinthecode—namelyEBP,ESP,andEAX,whichareusedfrequentlyinthecodeasoperands.Thesevalues,amongothers,arecalledprocessorregisters,andunderstandingthemisessentialtounderstandingthestack,functioncalls,andotherlow-levelaspectsofassemblycode.
ProcessorRegistersUnlikehigh-levelprogramminglanguages,assemblylanguagedoesnothaveuser-definedvariablenames.Instead,itaccessesdatabyreferencingitsmemoryaddress.Duringintensivecomputation,however,itcanbeextremelycostlyfortheprocessortoconstantlydealwiththeoverheadofreadingandwritingdatatoRAM.Tomitigatethishighcost,x86processorsprovideasmallsetoftemporaryvariables,calledprocessorregisters,which
provideasmallsetoftemporaryvariables,calledprocessorregisters,whicharesmallstoragespaceswithintheprocessoritself.SinceaccessingtheseregistersrequiresfarlessoverheadthanaccessingRAM,assemblyusesthemtodescribeitsinternalstate,passvolatiledataaround,andstorecontext-sensitivevariables.
GeneralRegistersWhenassemblycodeneedstostoreoroperateonarbitrarydata,itusesasubsetofprocessregisterscalledgeneralregisters.Theseregistersareusedexclusivelytostoreprocess-specificdata,suchasafunction’slocalvariables.Eachgeneralregisteris32bitsandthuscanbethoughtofasaDWORDvariable.Generalregistersarealsooptimizedforspecificpurposes:
EAX,theaccumulatorThisregisterisoptimizedformathematicalcomputations.Someoperations,suchasmultiplicationanddivision,canonlyoccurinEAX.
EBX,thebaseregisterThisregisterisusedarbitrarilyforextrastorage.Sinceits16-bitpredecessor,BX,wastheonlyregisterthatoperationscouldusetoreferencememoryaddresses,EBXwasusedasareferencetoRAM.Inx86assembly,however,allregisterscanbeaddressreferences,leavingEBXwithoutatruepurpose.
ECX,thecounterThisregisterisoptimizedtoactasthecountervariable(oftencallediinhigh-levelcode)inaloop.
EDX,thedataregisterThisregisterisoptimizedtoactasahelpertoEAX.In64-bitcomputations,forinstance,EAXactsasbits0–31andEDXactsasbits32–63.
Theseregistersalsohaveasetof8-and16-bitsubregistersthatyoucanusetoaccesspartialdata.Thinkofeverygeneralregisterasaunion,wherearegisternamedescribesthe32-bitmemberandthesubregistersarealternatemembersthatallowaccesstosmallerpiecesoftheregister.ThefollowingcodeshowswhatthisunionmightlooklikeforEAX:
union{DWORDEAX;WORDAX;struct{BYTEL;
BYTEH;}A;}EAX;
Inthisexample,AXallowsaccesstothelowerWORDofEAX,whileALallowsaccesstothelowerBYTEofAXandAHtoitshigherBYTE.Everygeneralregisterhasthisstructure,andIoutlinetheotherregisters’subregistersinFigure4-5.
Figure4-5:x86registersandsubregisters
EAX,EBC,ECX,andEDXhavehigherwords,too,butthecompilerwillalmostneveraccessthemonitsown,asitcanjustusethelowerwordwhenitneedsword-onlystorage.
IndexRegistersx86assemblyalsohasfourindexregisters,whichareusedtoaccessdatastreams,referencethecallstack,andkeeptrackoflocalinformation.Likethegeneralregisters,indexregistersare32bits,butindexregistershavemorestrictlydefinedpurposes:
EDI,thedestinationindexThisregisterisusedtoindexmemorytargetedbywriteoperations.Iftherearenowriteoperationsinapieceofcode,thecompilercanuseEDIforarbitrarystorageifneeded.
ESI,thesourceindexThisregisterisusedtoindexmemorytargetedby
readoperations.Itcanalsobeusedarbitrarily.
ESP,thestackpointerThisregisterisusedtoreferencethetopofthecallstack.Allstackoperationsdirectlyaccessthisregister.YoumustuseESPonlywhenworkingwiththestack,anditmustalwayspointtothetopofthestack.
EBP,thestackbasepointerThisregistermarksthebottomofthestackframe.Functionsuseitasareferencetotheirparametersandlocalvariables.Somecodemaybecompiledwithanoptiontoomitthisbehavior,inwhichcaseEBPcanbeusedarbitrarily.
Likethegeneralregisters,eachindexregisterhasa16-bitcounterpart:DI,SI,SP,andBP,respectively.However,theindexregistershaveno8-bitsubregisters.
WHYDOSOMEX86REGISTERSHAVESUBREGISTERS?
Thereisahistoricalreasonwhybothgeneralandindexregistershave16-bitcounterparts.Thex86architecturewasbasedona16-bitarchitecture,fromwhichitextendedtheregistersAX,BX,CX,DX,DI,SI,SP,andBP.Appropriately,theextensionsretainthesamenamesbutareprefixedwithanE,for“extended.”The16-bitversionsremainforbackwardcompatibility.Thisalsoexplainswhyindexregistershaveno8-bitabstractions:theyareintendedtobeusedasmemory-addressoffsets,andthereisnopracticalneedtoknowpartialbytesofsuchvalues.
TheExecutionIndexRegisterTheExecutionIndexregister,referredtoasEIP,hasaveryconcretepurpose:itpointstotheaddressofthecodecurrentlybeingexecutedbytheprocessor.Becauseitcontrolstheflowofexecution,itisdirectlyincrementedbytheprocessorandisoff-limitstoassemblycode.TomodifyEIP,assemblycodemustindirectlyaccessitusingoperationssuchasCALL,
JMP,andRETN.
TheEFLAGSRegisterUnlikehigh-levelcode,assemblylanguagedoesn’thavebinarycomparisonoperatorslike==,>,and<.Instead,itusestheCMPcommandtocomparetwovalues,storingtheresultinginformationintheEFLAGSregister.Then,thecodechangesitscontrolflowusingspecialoperationsthatdependonthevaluestoredinELFAGS.
Whilecomparisoncommandsaretheonlyuser-modeoperationsthatcanaccessEFLAGS,theyuseonlythisregister’sstatusbits:0,2,4,6,7,and11.Bits8–10actascontrolflags,bits12–14and16–21actassystemflags,andtheremainingbitsarereservedfortheprocessor.Table4-5showsthetype,name,anddescriptionofeachEFLAGSbit.
Table4-5:EFLAGSbits
Bit(s)Type Name Description
0 Status Carry Setifacarryorborrowwasgeneratedfromthemostsignificantbitduringthepreviousinstruction.
2 Status Parity Setiftheleastsignificantbyteresultingfromthepreviousinstructionhasanevennumberofbitsset.
4 Status Adjust Sameasthecarryflag,butconsidersthe4leastsignificantbits.
6 Status Zero Setiftheresultingvaluefromthepreviousinstructionisequalto0.
7 Status Sign Setiftheresultingvaluefromthepreviousinstructionhasitssignbit(mostsignificantbit)set.
8 Control Trap Whenset,theprocessorsendsaninterrupttotheoperatingsystemkernelafterexecutingthenextoperation.
9 Control Interrupt Whennotset,thesystemignoresmaskable
interrupts.
10 Control Direction Whenset,ESIandEDIaredecrementedbyoperationsthatautomaticallymodifythem.Whennotset,theyareincremented.
11 Status Overflow Setwhenavalueisoverflowedbythepreviousinstruction,suchaswhenADDisperformedonapositivevalueandtheresultisanegativevalue.
TheEFLAGSregisteralsocontainsasystembitandareservedbit,butthoseareirrelevantinuser-modeassemblyandgamehacking,soI’veomittedthemfromthistable.KeepEFLAGSinmindwhenyou’redebugginggamecodetofigureouthowitworks.Forexample,ifyousetabreakpointonaJE(jumpifequal)instruction,youcanlookattheEFLAGS0bittoseewhetherthejumpwillbetaken.
SegmentRegistersFinally,assemblylanguagehasasetof16-bitregisterscalledsegmentregisters.Unlikeotherregisters,segmentregistersarenotusedtostoredata;theyareusedtolocateit.Intheory,theypointtoisolatedsegmentsofmemory,allowingdifferenttypesofdatatobestoredincompletelyseparatememorysegments.Theimplementationofsuchsegmentationisleftuptotheoperatingsystem.Thesearethex86segmentregistersandtheirintendedpurposes:
CS,thecodesegmentThisregisterpointstothememorythatholdsanapplication’scode.
DS,thedatasegmentThisregisterpointstothememorythatholdsanapplication’sdata.
ES,FS,andGS,theextrasegmentsTheseregisterspointtoanyproprietarymemorysegmentsusedbytheoperatingsystem.
SS,thestacksegmentThisregisterpointstomemorythatactsasadedicatedcallstack.
Inassemblycode,segmentregistersareusedasprefixestomemoryoffsetoperands.Whenasegmentregisterisn’tspecified,DSisusedbydefault.
ThismeansthatthecommandPUSH[EBP]iseffectivelythesameasPUSHDS:[EBP].ButthecommandPUSHFS:[EBP]isdifferent:itreadsmemoryfromtheFSsegment,nottheDSsegment.
IfyoulookcloselyattheWindowsx86implementationofmemorysegmentation,youmightnoticethatthesesegmentregisterswerenotexactlyusedasintended.Toseethisinaction,youcanrunthefollowingcommandswiththeOllyDbgcommandlineplug-inwhileOllyDbgisattachedtoapausedprocess:
?CALC(DS==SS&&SS==GS&&GS==ES)?1?CALCDS-CS?8?CALCFS-DS;returnsnonzero(andchangesbetweenthreads)
Thisoutputtellsusthreedistinctthings.First,itshowsthatthereareonlythreesegmentsbeingusedbyWindows:FS,CS,andeverythingelse.ThisisdemonstratedbyDS,SS,GS,andESbeingequal.Forthesamereason,thisoutputshowsthatDS,SS,GS,andEScanallbeusedinterchangeably,astheyallpointtothesamememorysegments.Lastly,sinceFSchangesdependingonthethread,thisoutputshowsthatitisthreaddependent.FSisaninterestingsegmentregister,anditpointstocertainthread-specificdata.In“BypassingASLRinProduction”onpage128,we’llexplorehowthedatainFScanbeusedtobypassASLR—somethingmostbotswillneedtodo.
Infact,inassemblycodegeneratedforWindowsbyacompiler,you’donlyeverseethreesegmentsused:DS,FS,andSS.Interestinglyenough,eventhoughCSseemstoshowaconstantoffsetfromDS,ithasnorealpurposeinuser-modecode.Knowingallofthesethings,youcanfurtherconcludethatthereareonlytwosegmentsbeingusedbyWindows:FSandeverythingelse.
Thesetwosegmentsactuallypointtodifferentlocationsinthesamememory(there’snosimplewaytoverifythis,butitistrue),whichshowsthatWindowsactuallydoesn’tusememorysegmentsatall.Instead,itusesaflatmemorymodelinwhichsegmentregistersarenearlyirrelevant.Whileallsegmentregisterspointtothesamememory,onlyFSandCSpointtodifferentlocations,andCSisnotused.
Inconclusion,thereareonlythreethingsyouneedtoknowabout
Inconclusion,thereareonlythreethingsyouneedtoknowaboutsegmentregisterswhenworkingwithx86assemblyinWindows.First,DS,SS,GS,andESareinterchangeable,butforclarityDSshouldbeusedtoaccessdataandSSshouldbeusedtoaccessthecallstack.Second,CScanbesafelyforgotten.Third,FSistheonlysegmentregisterwithaspecialpurpose;itshouldbeleftalonefornow.
TheCallStackRegistersarepowerful,butunfortunatelytheycomeinverylimitedsupply.Inorderforassemblycodetoeffectivelystoreallofitslocaldata,itmustalsousethecallstack.Thestackisusedtostoremanydifferentvalues,includingfunctionparameters,returnaddresses,andsomelocalvariables.
Understandingtheinsandoutsofthecallstackwillcomeinhandywhenyou’rereverseengineeringagame.Moreover,you’llrelyonthisknowledgeheavilywhenwejumpintocontrolflowmanipulationinChapter8.
StructureYoucanthinkofthecallstackasaFILO(first-in-last-out)listofDWORDvaluesthatcanbedirectlyaccessedandmanipulatedbyassemblycode.Thetermstackisusedbecausethestructureresemblesastackofpaper:objectsarebothaddedtoandremovedfromthetop.DataisaddedtothestackthroughthePUSHoperandcommand,anditisremoved(andplacedinaregister)throughthePOPregistercommand.Figure4-6showshowthisprocessmightlook.
Figure4-6:Thestructureofastack
InWindows,thestackgrowsfromhighermemoryaddressestolowerones.Itoccupiesafiniteblockofmemory,pilinguptoaddress0x00000000(theabsolutetop)fromaddressn(theabsolutebottom).ThismeansthatESP(thepointertothetopofthestack)decreasesasitemsareaddedandincreasesasitemsareremoved.
TheStackFrameWhenanassemblyfunctionusesthestacktostoredata,itreferencesthedatabycreatingastackframe.ItdoessobystoringESPinEBPandthensubtractingnbytesfromESP,effectivelyopeningann-bytegapthatisframedbetweentheregistersEBPandESP.Tobetterunderstandthis,firstimaginethatthestackinFigure4-7ispassedtoafunctionthatrequires0x0Cbytesoflocalstoragespace.
Figure4-7:Initialexamplestack(readfrombottomtotop)
Inthisexample,address0x0000istheabsolutetopofthestack.Wehaveunusedmemoryfromaddresses0x0000to0xFF00–4,andatthetimeofthefunctioncall,0xFF00isthetopofthestack.ESPpointstothisaddress.Thestackmemoryafter0xFF00isusedbyprecedingfunctionsinthecallchain(from0xFF04to0xFFFF).Whenthefunctioniscalled,thefirstthingitdoesisexecutethefollowingassemblycode,whichcreatesastackframeof0x0C(12indecimal)bytes:
PUSHEBP;savesthebottomofthelowerstackframeMOVEBP,ESP;storesthebottomofthecurrentstackframe,inEBP;(also4bytesabovethelowerstackframe)SUBESP,0x0C;subtracts0x0CbytesfromESP,movingitupthestack;tomarkthetopofthestackframe
Afterthiscodeexecutes,thestacklooksmoreliketheoneshowninFigure4-8.Aftercreatingthisstack,thefunctioncanworkwiththe0x0Cbytesitallocatedonthestack.
0x0000isstilltheabsolutetopofthestack.Wehaveunusedstackmemoryfromaddresses0x0000to0xFF00–20,andthememoryataddress0xFF00–16containsthefinal4bytesoflocalstorage(referencedby[EBP-Ch]).Thisisalsothetopofthecurrentstackframe,soESPpointshere.0xFF00–12containsthemiddle4bytesoflocalstorage(referencedby[EBP-8h]),and0xFF00–8containsthefirst4bytesoflocalstorage(referencedby[EBP-4h]).EBPpointsto0xFF00–4,whichisthebottomofthecurrentstackframe;thisaddressholdstheoriginalvalueofEBP.0xFF00isthetopofthelowerstackframe,andtheoriginalESPinFigure4-7pointedhere.Finally,youcanstillseethestackmemoryfromprecedingfunctionsinthecallchainfrom0xFF04to0xFFFF.
Figure4-8:Examplestackwithstackframesetup(readfrombottomtotop)
Withthestackinthisstate,thefunctionisfreetouseitslocaldataasitpleases.Ifthisfunctioncalledanotherfunction,thenewfunctionwouldbuilditsownstackframeusingthesametechnique(thestackframesreallystackup).Onceafunctionfinishesusingastackframe,however,itmustrestorethestacktoitspreviousstate.Inourcase,thatmeansmakingthestacklooklikeitdidinFigure4-7.Whenthesecondfunctionfinishes,ourfirstfunctioncleansthestackusingthefollowingtwocommands:
MOVESP,EBP;demolishesthestackframe,bringingESPto4bytesabove;itsoriginalvalue(0xFF00-4)POPEBP;restoresthebottomoftheoldstackframethatwassavedby;'PUSHEBP'.Alsoadds4bytestoESP,puttingitbackat;itsoriginalvalue
Butifyouwanttochangetheparameterspassedtoafunctioninagame,don’tlookfortheminthatfunction’sstackframe.Afunction’sparametersarestoredinthestackframeofthefunctionthatcalledit,andthey’re
referencedthrough[EBP+8h],[EBP+Ch],andsoon.Theystartat[EBP+8h]because[EBP+4h]storesthefunction’sreturnaddress.(“FunctionCalls”onpage94explainsthistopicfurther.)
NOTE
Codecanbecompiledwithstackframesdisabled.Whenthisisthecase,you’llnoticethatfunctionsdon’topenwithPUSHEBPandinsteadreferenceeverythingrelativetoESP.Moreoftenthannot,though,stackframesareenabledincompiledgamecode.
Nowthatyouhaveagrasponthefundamentalsofassemblycode,let’sexploresomespecificsthatwillcomeinhandywhenhackinggames.
Importantx86InstructionsforGameHackingWhileassemblylanguagehashundredsofinstructions,manywell-equippedgamehackersunderstandonlyasmallsubsetofthem,whichIcoverindetailhere.Thissubsettypicallyencapsulatesallinstructionsthatareusedtomodifydata,callfunctions,comparevalues,orjumparoundwithincode.
DataModificationDatamodificationoftenhappensoverseveralassemblyoperations,buttheendresulthastobestoredeitherinmemoryorinaregister,typicallywiththeMOVinstruction.TheMOVoperationtakestwooperands:adestinationandasource.Table4-6showsallpossiblesetsofMOVoperandsandtheresultsyoucanexpectfromthosecalls.
Table4-6:OperandstotheMOVInstruction
Instructionsyntax Result
MOVR1,R2 CopiesR2’svaluetoR1.MOVR1,[R2] CopiesthevaluefromthememoryreferencedbyR2to
R1.MOVR1,[R2+Ah] Copiesthevaluefromthememoryreferencedby
R2+0xAtoR1.
MOVR1,[DEADBEEFh] Copiesthevaluefromthememoryat0xDEADBEEFtoR1.
MOVR1,BADF00Dh Copiesthevalue0xBADF00DtoR1.MOV[R1],R2 CopiesR2’svaluetothememoryreferencedbyR1.MOV[R1],BADF00Dh Copiesthevalue0xBADF00Dtothememory
referencedbyR1.MOV[R1+4h],R2 CopiesR2’svaluetothememoryreferencedbyR1+0x4.MOV[R1+4h],BADF00Dh
Copiesthevalue0xBADF00DtothememoryreferencedbyR1+0x4.
MOV[DEADBEEFh],R1 CopiesR1’svaluetothememoryat0xDEADBEEF.MOV[DEADBEEFh],BADF00Dh
Copiesthevalue0xBADF00Dtothememoryat0xDEADBEEF.
TheMOVinstructioncantakealotofoperandcombinations,butsomearen’tallowed.First,thedestinationoperandcan’tbeanimmediatevalue;itmustbearegisterormemoryaddress,becauseimmediatevaluescan’tbemodified.Second,valuescan’tbedirectlycopiedfromonememoryaddresstoanother.Copyingavaluerequirestwoseparateoperations,likeso:
MOVEAX,[EBP+10h];copymemoryfromEBP+0x10toEAXMOV[DEADBEEFh],EAX;MOVthecopiedmemorytomemoryat0xDEADBEEF
TheseinstructionscopywhateverisstoredatEBP+0x10tothememoryat0xDEADBEEF.
ArithmeticLikemanyhigh-levellanguages,assemblylanguagehastwotypesofarithmetic:unaryandbinary.Unaryinstructionstakeasingleoperandthatactsasbothadestinationandasource.Thisoperandcanbearegisteroramemoryaddress.Table4-7showsthecommonunaryarithmeticinstructionsinx86.
Table4-7:UnaryArithmeticInstructions
Instructionsyntax
Result
INCoperand Adds1totheoperandvalue.
DECoperand Subtracts1fromtheoperandvalue.NOToperand Logicallynegatestheoperandvalue(flipsallbits).NEGoperand Performstwo’s-complementnegation(flipsallbitsandadds1;
essentiallymultipliesby-1).
Binaryinstructions(whichmakeupthemajorityofx86arithmetic),ontheotherhand,aresyntacticallysimilartotheMOVinstruction.Theyrequiretwooperandsandhavesimilaroperandlimitations.UnlikeMOV,however,theirdestinationoperandservesasecondpurpose:itisalsotheleft-handvalueinthecalculation.Forexample,theassemblyoperationADDEAX,EBXequatestoEAX=EAX+EBXorEAX+=EBXinC++.Table4-8showsthecommonx86binaryarithmeticinstructions.
Table4-8:BinaryArithmeticInstructions
Instructionsyntax
Function Operandnotes
ADDdestination,source
destination+=source
SUBdestination,source
destination-=source
ANDdestination,source
destination&=source
ORdestination,source
destination|=source
XORdestination,source
destination^=source
SHLdestination,source
destination=destination<<source
sourcemustbeCLoran8-bitimmediatevalue.
source source
SHRdestination,
source
destination=destination>>
source
sourcemustbeCLoran8-bitimmediatevalue.
IMULdestination,source
destination*=source
destinationmustbearegister;sourcecannotbeanimmediatevalue.
Ofthesearithmeticinstructions,IMULisspecialbecauseyoucanpassitathirdoperand,intheformofanimmediatevalue.Withthisprototype,thedestinationoperandisnolongerinvolvedinthecalculation,whichinsteadtakesplacebetweentheremainingoperands.Forexample,theassemblycommandIMULEAX,EBX,4hequatestoEAX=EBX*0x4inC++.
YoucanalsopassasingleoperandtoIMUL.3Inthiscase,theoperandactsasthesourceandcanbeeitheramemoryaddressoraregister.Dependingonthesizeofthesourceoperand,theinstructionwillusedifferentpartsoftheEAXregisterforinputsandoutput,asshowninTable4-9.
Table4-9:PossibleIMULRegisterOperands
Sourcesize
InputOutput
8bits AL 16bit,storedinAH:AL(whichisAX)
16bits AX 32bit,storedinDX:AX(bits0–15inAXandbits16–31inDX)
32bits EAX 64bit,storedinEDX:EAX(bits0–31inEAXandbits32–64inEDX)
Noticethateventhoughtheinputisonlyoneregister,eachoutputusestworegisters.That’sbecauseinmultiplication,theresultgenerallyislargerthantheinputs.
Let’slookatanexamplecalculationusingIMULwithasingle32-bitoperand:
IMUL[BADFOODh];32-bitoperandisataddress0xBADFOOD
Thiscommandbehaveslikethefollowingpseudocode:
EDX:EAX=EAX*[BADFOODh]
Similarly,here’sanoperationthatusesIMULwithasingle16-bitoperand:
IMULCX;16-bitoperandisstoredinCX
Anditscorrespondingpseudocode:
DX:AX=AX*CX
Finally,thisisanIMULcommandwithasingle8-bitoperand:
IMULCL;8-bitoperandisstoredinCL
Anditscorrespondingpseudocode:
AX=AL*CL
x86assemblylanguagehasdivisionaswell,throughtheIDIVinstruction.4TheIDIVinstructionacceptsasinglesourceoperandandfollowsregisterrulessimilartothoseforIMUL.AsTable4-10shows,IDIVoperationsrequiretwoinputsandtwooutputs.
Table4-10:PossibleIDIVRegisterOperands
Sourcesize
Input Output
8bit 16bit,storedinAH:AL(whichisAX)
RemainderinAH;quotientinAL
16bit 32bit,storedinDX:AX RemainderinDX;quotientinAX
32bit 64bit,storedinEDX:EAX RemainderinEDX;quotientinEAX
Indivision,theinputsaregenerallylargerthantheoutput,soheretheinputstaketworegisters.Moreover,divisionoperationsmuststorearemainder,whichgetsstoredinthefirstinputregister.Forexample,here’showa32-bitIDIVcalculationwouldlook:
MOVEDX,0;there'snohigh-orderDWORDintheinput,soEDXis0MOVEAX,inputValue;32-bitinputvalueIDIVECX;divideEDX:EAXbyECX
Andhere’ssomepseudocodethatexpresseswhathappensunderthehood:
EAX=EDX:EAX/ECX;quotientEDX=EDX:EAX%ECX;remainder
ThesedetailsofIDIVandIMULareimportanttoremember,asthebehaviorcanotherwisebequiteobfuscatedwhenyou’resimplylookingatthecommands.
BranchingAfterevaluatinganexpression,programscandecidewhattoexecutenextbasedontheresult,typicallyusingconstructssuchasif()statementsorswitch()statements.Thesecontrolflowstatementsdon’texistattheassemblylevel,however.Instead,assemblycodeusestheEFLAGSregistertomakedecisionsandjumpoperationstoexecutedifferentblocks;thisprocessiscalledbranching.
TogetthepropervalueinEFLAGS,assemblycodeusesoneoftwoinstructions:TESTorCMP.Bothcomparetwooperands,setthestatusbitsofEFLAGS,andthendiscardanyresults.TESTcomparestheoperandsusingalogicalAND,whileCMPusessignedsubtractiontosubtractthelatteroperandfromtheformer.
Inordertobranchproperly,thecodehasajumpcommandimmediatelyfollowingthecomparison.Eachtypeofjumpinstructionacceptsasingleoperandthatspecifiestheaddressofthecodetojumpto.HowaparticularjumpinstructionbehavesdependsonthestatusbitsofEFLAGS.Table4-11describessomex86jumpinstructions.
Table4-11:Commonx86JumpInstructions
InstructionName Behavior
JMPdest Unconditionaljump
Jumpstodest(setsEIPtodest).
JEdest Jumpifequal JumpsifZF(zeroflag)is1.
JNEdest Jumpifnotequal JumpsifZFis0.JGdest Jumpifgreater JumpsifZFis0andSF(signflag)isequalto
OF(overflowflag).JGEdest Jumpifgreater
orequalJumpsifSFisequaltoOF.
JAdest UnsignedJG JumpsifCF(carryflag)is0andZFis0.JAEdest UnsignedJGE JumpsifCFis0.JLdest Jumpifless JumpsifSFisnotequaltoOF.JLEdest Jumpiflessor
equalJumpsifZFis1orSFisnotequaltoOF.
JBdest UnsignedJL JumpsifCFis1.JBEdest UnsignedJLE JumpsifCFis1orZFis1.JOdest Jumpifoverflow JumpsifOFis1.JNOdest Jumpifnot
overflowJumpsifOFis0.
JZdest Jumpifzero JumpsifZFis1(identicaltoJE).JNZdest Jumpifnotzero JumpsifZFis0(identicaltoJNE).
Rememberingwhichflagscontrolwhichjumpinstructionscanbeapain,buttheirpurposeisclearlyexpressedintheirname.AgoodruleofthumbisthatajumpprecededbyaCMPisthesameasitscorrespondingoperator.Forexample,Table4-11listsJEas“jumpifequal,”sowhenJEfollowsaCMPoperation,it’sthesameasthe==operator.Similarly,JGEwouldbe>=,JLEwouldbe>=,andsoon.
Asanexample,considerthehigh-levelcodeshowninListing4-8.
--snip--if(EBX>EAX)ECX=EDX;elseECX=0;
--snip--
Listing4-8:Asimpleconditionalstatement
Thisif()statementjustcheckswhetherEBXisgreaterthanEAXandsetsECXbasedontheresult.Inassembly,thesamestatementmaylooksomethinglikethis:
--snip--CMPEBX,EAX;if(EBX>EAX)JGlabel1;jumptolabel1ifEBX>EAXMOVECX,0;ECX=0(elseblock)JMPlabel2;jumpovertheifblocklabel1:➊MOVECX,EDX;ECX=EDX(ifblock)label2:--snip--
Theassemblyfortheif()statementinListing4-8beginswithaCMPinstructionandbranchesifEBXisgreaterthanEAX.Ifthebranchistaken,EIPissettotheifblockat➊courtesyoftheJGinstruction.Ifthebranchisnottaken,thecodecontinuesexecutinglinearlyandhitstheelseblockimmediatelyaftertheJGinstruction.Whentheelseblockfinishesexecuting,anunconditionalJMPsetsEIPto0x7,skippingovertheifblock.
FunctionCallsInassemblycode,functionsareisolatedblocksofcommandsexecutedthroughtheCALLinstruction.TheCALLinstruction,whichtakesafunctionaddressastheonlyoperand,pushesareturnaddressontothestackandsetsEIPtoitsoperandvalue.ThefollowingpseudocodeshowsaCALLinaction,withmemoryaddressesontheleftinhex:
0x1:CALLEAX0x2:...
WhenCALLEAXisexecuted,thenextaddressispushedtothestackandEIPissettoEAX,showingthatCALLisessentiallyaPUSHandJMP.Thefollowingpseudocodeunderscoresthispoint:
0x1:PUSH3h0x2:JMPEAX0x3:...
Whilethere’sanextraaddressbetweenthePUSHinstructionandthecodetoexecute,theresultisthesame:beforetheblockofcodeatEAXisexecuted,theaddressofthecodethatfollowsthebranchispushedtothestack.Thishappenssothecallee(thefunctionbeingcalled)knowswheretojumptointhecaller(thefunctiondoingthecall)whenitreturns.
Ifafunctionwithoutparametersiscalled,aCALLcommandisallthat’snecessary.Ifthecalleetakesparameters,however,theparametersmustfirstbepushedontothestackinreverseorder.Thefollowingpseudocodeshowshowafunctioncallwiththreeparametersmightlook:
PUSH300h;arg3PUSH200h;arg2PUSH100h;arg1CALLECX;call
Whenthecalleeisexecuted,thetopofthestackcontainsareturnaddressthatpointstothecodeafterthecall.Thefirstparameter,0x100,isbelowthereturnaddressonthestack.Thesecondparameter,0x200,isbelowthat,followedbythethirdparameter,0x300.Thecalleesetsupitsstackframe,usingmemoryoffsetsfromEBPtoreferenceeachparameter.Oncethecalleehasfinishedexecuting,itrestoresthecaller’sstackframeandexecutestheRETinstruction,whichpopsthereturnaddressoffthestackandjumpstoit.
Sincetheparametersarenotapartofthecallee’sstackframe,theyremainonthestackafterRETisexecuted.Ifthecallerisresponsibleforcleaningthestack,itadds12(3parameters,at4byteseach)toESPimmediatelyafterCALLECXcompletes.Ifthecalleeisresponsible,itcleansupbyexecutingRET12insteadofRET.Thisresponsibilityisdeterminedbythecallee’scallingconvention.
Afunction’scallingconventiontellsthecompilerhowtheassemblycodeshouldpassparameters,storeinstancepointers,communicatethereturnvalue,andcleanthestack.Differentcompilershavedifferentcallingconventions,buttheoneslistedinTable4-12aretheonlyfourthatagamehackerislikelytoencounter.
Table4-12:CallingConventionstoKnowforGameHacking
Directive CleanerNotes__cdecl caller DefaultconventioninVisualStudio.
__stdcall callee ConventionusedbyWin32APIfunctions.__fastcallcallee FirsttwoDWORD(orsmaller)parametersarepassedin
ECXandEDX.__thiscallcallee Usedformemberfunctions.Thepointertotheclass
instanceispassedinECX.
TheDirectivecolumninTable4-12givesthenameofthecallingconvention,andtheCleanercolumntellsyouwhetherthecallerorcalleeisresponsibleforcleaningthestackgiventhatdirective.Inthecaseofthesefourcallingconventions,parametersarealwayspushedrighttoleft,andreturnvaluesarealwaysstoredinEAX.Thisisastandard,butnotarule;itcandifferacrossothercallingconventions.
ClosingThoughtsMygoalinwritingthischapterwastohelpyouunderstandmemoryandassemblyinageneralsense,beforewedigintogame-hackingspecifics.Withyournewfoundabilitytothinklikeacomputer,youshouldbeadequatelyarmedtostarttacklingmoreadvancedmemoryforensicstasks.Ifyou’reitchingforapeekathowyou’llapplyallofthistosomethingreal,flipto“ApplyingCallHookstoAdobeAIR”onpage169or“ApplyingJumpHooksandVFHookstoDirect3D”onpage175.
Ifyouwantsomehands-ontimewithmemory,compilethischapter’sexamplecodeanduseCheatEngineorOllyDbgtoinspect,tweak,andpokeatthememoryuntilyou’vegotthehangofit.Thisisimportant,asthenextchapterwillbuildontheseskillsbyteachingyouadvancedmemoryforensictechniques.
5ADVANCEDMEMORYFORENSICS
Whetheryouhackgamesasahobbyorabusiness,you’lleventuallyfindyourselfbetweenarockand...anunintelligiblememorydump.Beitaracewitharivalbotdevelopertoreleaseahighlyrequestedfeature,abattleagainstagamecompany’sconstantbarrageofupdates,orastruggletolocatesomecomplexdatastructureinmemory,you’llneedtop-notchmemoryforensicsskillstoprevail.
Successfulbotdevelopmentisprecariouslybalancedatopspeedandskill,andtenacioushackersmustrisetothechallengebyswiftlyreleasingingeniousfeatures,promptlyrespondingtogameupdates,andreadilysearchingforeventhemostelusivepiecesofdata.Doingthis,however,requiresacomprehensiveunderstandingofcommonmemorypatterns,advanceddatastructures,andthepurposeofdifferentpiecesofdata.
Thosethreeaspectsofmemoryforensicsareperhapsthemosteffectiveweaponsinyourarsenal,andthischapterwillteachyouhowtousethem.First,I’lldiscussadvancedmemory-scanningtechniquesthatfocusonsearchingfordatabyunderstandingitspurposeandusage.Next,I’llteachyouhowtousememorypatternstotacklegameupdatesandtweakyourbotswithouthavingtorelocateallofyouraddressesfromscratch.Towrapup,I’lldissectthefourmostcommoncomplexdatastructuresintheC++standardlibrary(std::string,std::vector,std::list,andstd::map)soyoucanrecognizetheminmemoryandenumeratetheircontents.Bytheendofthechapter,myhopeisthatyou’llhaveadeepunderstandingofmemory
forensicsandbeabletotakeonanychallengerelatedtomemoryscanning.
AdvancedMemoryScanningWithinagame’ssourcecode,eachpieceofdatahasacold,calculateddefinition.Whenthegameisbeingplayed,however,allofthatdatacomestogethertocreatesomethingnew.Playersonlyexperiencethebeautifulscenery,visceralsounds,andintenseadventures;thedatathatdrivestheseexperiencesisirrelevant.
Withthatinmind,imagineHackerAhasjuststartedtearingintohisfavoritegame,wantingtoautomatesomeoftheboringbitswithabot.Hedoesn’thaveacompleteunderstandingofmemoryyet,andtohim,thedataisnothingbutassumptions.Hethinks,“Ihave500health,soIcanfindthehealthaddressbytellingCheatEnginetolookfora4-byteintegerwithavalueof500.”HackerAhasanaccurateunderstandingofdata:it’sjustinformation(values)storedatparticularlocations(addresses)usingdefinedstructures(types).
NowimagineHackerB,whoalreadyunderstandsthegamebothinsideandout;sheknowshowplayingthegamealtersitsstateinmemory,andthedatanolongerhasanysecrets.Sheknowsthateverydefinedpropertyofthedatacanbedeterminedgivenitspurpose.UnlikeHackerA,HackerBhasanunderstandingofdatathattranscendstheconfinesofasinglevariabledeclaration:sheconsidersthedata’spurposeandusage.Inthissection,we’lldiscussboth.
Eachpieceofdatainagamehasapurpose,andtheassemblycodeofthegamemust,atsomepoint,referencethedatatofulfillthatpurpose.Findingtheuniquecodethatusesapieceofdatameansfindingaversion-agnosticmarkerthatpersistsacrossgameupdatesuntilthedataiseitherremovedoritspurposeischanged.Letmeshowyouwhythisisimportant.
DeducingPurposeSofar,I’veonlyshownyouhowtoblindlysearchmemoryforagivenpieceofdatawithoutconsideringhowit’sbeingused.Thismethodcanbeeffective,butitisnotalwaysefficient.Inmanycases,it’smuchquickertodeducethepurposeofdata,determinewhatcodemightusethatdata,andthenlocatethatcodetoultimatelyfindtheaddressofthedata.
thenlocatethatcodetoultimatelyfindtheaddressofthedata.Thismightnotsoundeasy,butneitherdoes“scanthegame’smemoryfor
aspecificvalueofaspecificdatatype,andthencontinuouslyfiltertheresultlistbasedonchangingcriteria,”whichiswhatyou’velearnedtodothusfar.Solet’slookathowwemightlocatetheaddressforhealthgivenitspurpose.ConsiderthecodeinListing5-1.
structPlayerVital{intcurrent,maximum;};PlayerVitalhealth;--snip--printString("Health:%dof%d\n",health.current,health.maximum);
Listing5-1:Astructurecontainingtheplayer’svitals,andafunctionthatdisplaysthem
IfyoupretendthatprintString()isafancyfunctiontodrawtextonanin-gameinterface,thenthiscodeisprettyclosetowhatyoumightfindinagame.ThePlayerVitalstructurehastwoproperties:thecurrentvalueandamaximumvalue.ThevaluehealthisaPlayerVitalstructure,soithastheseproperties,too.Basedonthenamealone,youcandeducethathealthexiststodisplayinformationabouttheplayer’shealth,andyoucanseethispurposefulfilledwhenprintString()usesthedata.
Evenwithoutthecode,youcanintuitivelydrawsimilarconclusionsbyjustlookingatthehealthtextdisplayedinthegame’sinterface;acomputercan’tdoanythingwithoutcode,afterall.Asidefromtheactualhealthvariable,thereareafewcodeelementsthatneedtoexisttoshowaplayerthistext.First,thereneedstobesomefunctiontodisplaytext.Second,thestringsHealthandofmustbenearby.
NOTE
WhydoIassumethetextissplitintotwoseparatestringsinsteadofone?Thegameinterfaceshowsthatthecurrenthealthvalueisbetweenthesetwostrings,buttherearemanywaysthatcouldhappen,includingformatstrings,strcat(),ortextalignedwithmultipledisplaytextcalls.Whenyou’reanalyzingdata,it’sbesttokeepyourassumptionsbroadtoaccountforallpossibilities.
Tofindhealthwithoutusingamemoryscanner,wecouldutilizethesetwodistinctstrings.Weprobablywouldn’thaveacluewhatthefunctionto
displaytextlookslike,whereitis,orhowmanytimesit’scalled,though.Realistically,thestringsareallwewouldknowtolookfor,andthat’senough.Let’swalkthroughit.
FindingthePlayer’sHealthwithOllyDbgI’llwalkyouthroughhowtotrackdownthehealthstructureinthissection,butI’vealsoincludedthebinaryIanalyzeinthebook’sresourcefiles.Tofollowalongandgetsomehands-onpractice,usethefileChapter5_AdvancedMemoryForensics_Scanning.exe.
First,openOllyDbgandattachittotheexecutable.Then,openOllyDbg’sExecutablemoduleswindowanddouble-clickthemainmodule;inmyexample,themainmoduleistheonly.exeinthemodule’swindow.TheCPUwindowshouldpopup.Now,right-clickintheDisassemblerpaneandselectSearchfor▸Allreferencedtextstrings.ThisshouldopentheReferenceswindow,showninFigure5-1.
Figure5-1:OllyDbg’sReferenceswindow,showingonlyalistofstrings.Therewouldbealotmorethanfourinarealgame.
Fromthiswindow,right-clickandselectSearchfortext.Asearchdialogappears.Enterthestringyou’relookingfor,asshowninFigure5-2,andmakethesearchasbroadaspossiblebydisablingCasesensitiveandenablingEntirescope.
Figure5-2:SearchingforstringsinOllyDbg
ClickOKtoexecutethesearch.TheReferenceswindowcomesbackintofocuswiththefirstmatchhighlighted.Double-clickthematchtoseetheassemblycodethatusesthestringinsidetheCPUwindow.TheDisassemblerpanefocusesonthelineofcodeat0x401030,whichpushestheformatstringparametertoprintString().YoucanseethislineinFigure5-3,whereI’vehighlightedtheentirefunctioncallblock.
Figure5-3:ViewingtheprintString()callintheCPUwindow’sDisassemblerpane
Byreadingtheassemblycode,youcangetaveryaccurateunderstandingofexactlywhatthegameisdoing.TheblackbracketontheleftshowsthatthestringHealthisinsideafunctioncall.Noticetheargumentstothatfunction.Inorder,theseareEAX➊,ECX➋,andtheformatstringat0x4020D0➌.EAXisthevalueat0x40301C,ECXisthevalueat0x403018,andtheformatstringcontainsHealth.Sincethestringcontainstwoformatplaceholders,youcanassumethattheremainingtwoparametersaretheargumentsforthoseplaceholders.
Knowingwhattheargumentsareandthattheyarepushedinreverseorder,youcanworkbackwardandconcludethattheoriginalcodelookedsomethinglikeListing5-2.
intcurrentHealth;//valueat0x403018
intmaxHealth;//valueat0x40301C--snip--someFunction("Health:%dof%d\n",currentHealth,maxHealth);
Listing5-2:HowagamehackermightinterprettheassemblythatFigure5-3compilesto
ThevaluesstoredinEAXandECXareadjacentinmemory,whichmeanstheymaybepartofastructure.Tokeepitsimple,though,thisexamplejustshowsthemasvariabledefinitions.Eitherway,thesearethetwonumbersusedtodisplaytheplayer’shealth.Becausebothoftheseimportantvaluesweredisplayedinthegame’sUI,itwaseasytomakeassumptionsabouttheunderlyingcodethatdisplaysthem.Whenyouknowthepurposeofapieceofdata,youcanquicklyfindthecoderesponsibleforfulfillingit;inthiscase,thatknowledgehelpedusquicklyfindbothaddresses.
Inmanycases,findingaddressescanbethiseasy,butsomepiecesofdatahavesuchcomplexpurposesthatit’shardertoguesswhattolookfor.FiguringouthowtosearchformapdataorcharacterlocationsinOllyDbg,forinstance,canbeprettytricky.
Stringsarefarfromtheonlymarkersthatyoucanusetofindthedatayouwanttochangeinagame,buttheyaredefinitelytheeasiesttoteachwithoutgivingcontrivedexamples.Moreover,somegameshaveloggingorerrorstringsembeddedintheircode,andpokingaroundintheReferencedtextstringswindowofOllyDbgcanbeaquickwaytodeterminewhetherthesestringsarepresent.Ifyoubecomefamiliarwithagame’sloggingpractices,you’llbeabletofindvaluesevenmoreeasily.
DeterminingNewAddressesAfterGameUpdatesWhenapplicationcodeismodifiedandrecompiled,abrand-newbinarythatreflectsthechangesisproduced.Thisbinarymightbeverysimilartothepreviousone,orthebinariesmightbenothingalike;thedifferencebetweenthetwoversionshasadirectcorrelationtothecomplexityofthehigh-levelchanges.Smallchanges,likemodifiedstringsorupdatedconstants,canleavebinariesnearlyidenticalandoftenhavenoeffectontheaddressesofcodeordata.Butmorecomplexchanges—likeaddedfeatures,anewuserinterface,refactoredinternals,ornewin-gamecontent—oftencauseshiftsinthelocationofcrucialmemory.
AUTOMATICALLYFINDCURRENTHEALTHANDMAXHEALTH
In“SearchingforAssemblyPatterns”onpage19and“SearchingforStrings”onpage21,IshowedafewCheatEngineLuascriptsandexplainedhowtheyworked.UsingthefindString()functionintheseexamples,youcanmakeCheatEngineautomaticallylocatetheaddressoftheformatstringthatwejustfoundmanuallyinOllyDbg.Next,youcanwriteasmallfunctiontoscanforthisaddressfollowingbyte0x68(thebyteforthePUSHcommand,asyoucanseebesideitat0x401030inFigure5-3)tolocatetheaddressofthecodethatpushesittothestack.Then,youcanread4bytesfrompushAddress-5andpushAddress-12tolocatecurrentHealthandmaxHealth,respectively.
Thismaynotseemusefulsincewe’vealreadyfoundtheaddresses,butifthiswerearealgame,theseaddresseswouldchangewhenanupdateisreleased.Usingthisknowledgetoautomatefindingthemcanbeveryhelpful.Ifyou’reuptothechallenge,giveitawhirl!
Duetoconstantbugfixes,contentimprovements,andfeatureadditions,onlinegamesareamongthemostrapidlyevolvingtypesofsoftware.Somegamesreleaseupdatesasoftenasonceaweek,andgamehackersoftenspendamajorityoftheirtimereverseengineeringthenewbinariesinordertoaccordinglyupdatetheirbots.
Ifyoucreateadvancedbots,theywillbecomeincreasinglysupportedbyafoundationofmemoryaddresses.Whenanupdatecomes,determiningthenewaddressesforalargenumberofvaluesandfunctionsisthemosttime-consuminginevitabilityyouwillface.Relyingonthe“TipsforWinningtheUpdateRace”canbeverybeneficial,butthetipswon’thelpyoulocatetheupdatedaddresses.YoucanautomaticallylocatesomeaddressesusingCheatEnginescripts,butthatwon’talwaysworkeither.Sometimesyou’llhavetodothedirtyworkbyhand.
Ifyoutrytoreinventthewheelandfindtheseaddressesthesamewayyoudidinitially,you’llbewastingyourtime.Youactuallyhaveabigadvantage,though:theoldbinaryandtheaddressesthemselves.Usingthesetwothings,itispossibletofindeverysingleaddressyouneedtoupdateina
twothings,itispossibletofindeverysingleaddressyouneedtoupdateinafractionofthetime.
Figure5-4showstwodifferentdisassemblies:anewgamebinaryontheleftandthepreviousversionontheright.Ihavetakenthisimagefromanactualgame(whichwillremainnameless)inordertogiveyouarealisticexample.
Figure5-4:Side-by-sidedisassembliesoftwoversionsofonegame
Mybotmodifiedthecodeat0x047B542(right),andIneededtofindthecorrespondingcodeinthenewversion,whichIdiscoveredat0x047B672(left).Thisfunctioncallinvokesapacket-parsingfunctionwhenapackethasbeenreceived.Inordertofindthisaddressoriginally(andby“originally,”Imeanabout100updatesprevious),Ifiguredouthowthegame’snetworkprotocolworked,setbreakpointsonmanynetwork-relatedAPIcalls,steppedthroughexecution,andinspecteddataonthestackuntilIfoundsomethingthatlookedsimilartowhatIexpectedgivenmyknowledgeoftheprotocol.
TIPSFORWINNINGTHEUPDATERACEInsaturatedmarkets,beingthefirstbotdevelopertoreleaseastableupdateiscriticaltosuccess.Theracestartsthesecondthegameupdates,andhackersdeterminedtobethefastestwillspendhundredsofhourspreparing.Thesearethemostcommonwaystostayontop:
CreateupdatealarmsBywritingsoftwarethatalertsyouassoonasthegamepatches,youcanbeginworkingonyourupdatesassoonaspossible.
AutomatebotinstallsGamesoftenscheduleexpectedupdatesattimeswhenthefewestplayersareonline.Bottershatewakingupand
downloadingnewsoftwarebeforetheybot,buttheylovewakinguptofinditsilentlyinstalledwhilethegameispatching.
UsefeweraddressesThelessthereistoupdate,thebetter.Consolidatingrelateddataintostructuresandeliminatingunnecessarymemoryaddressusagecansaveabunchoftime.
HavegreattestcasesDatachanges,andhackersmakemistakes.Havingwaystoquicklytesteveryfeaturecanbethedifferencebetweenastablebotandonethatrandomlycrashes,getsuserskilled,orevenleadstotheircharactersbeingbannedfromthegame.
Attackingupdateswiththesepracticeswillgiveyouasizableheadstart,buttheymightnotalwaysbeenoughtoleadyoutovictory.Aboveallelse,strivetounderstandreverseengineeringasmuchaspossibleandusethatunderstandingtoyouradvantage.
Icouldhavefollowedthesamestepsforeachofthe100+updatessincethen,butthatwouldhavebeenunnecessary.Thecodestayedrelativelythesamethroughouttheyears,whichletmeusepatternsfromtheoldcodetofindthatfunctioncall’saddressinthenewcode.
Now,considerthischunkofassemblycode:
PUSHEDIPUSHEAXLEAEAX,DWORDPTRSS:[EBP-C]MOVDWORDPTRFS:[0],EAXMOVDWORDPTRSS:[EBP-10],ESPMOVDWORDPTRSS:[EBP-220],-1MOVDWORDPTRSS:[EBP-4],0
Doesitlookfamiliar?CompareittoFigure5-4,andyou’llseethatthisexactcodeexistsrightabovethehighlightedfunctioncallinbothversionsofthegame.Regardlessofwhatitdoes,thecombinationofoperationslooksprettydistinctive;becauseofthenumberofdifferentoffsetsthecodeisusingrelativetoEBP,it’sunlikelythatanidenticalchunkofcodeexistsinanyotherpartofthebinary.
EverytimeIhavetoupdatethisaddress,IopentheoldbinaryinOllyDbg,highlightthischunkofoperations,right-click,andselect
Asm2Clipboard▸Copyfixedasmtoclipboard.Then,IopenthenewbinaryinOllyDbg,navigatetotheCPUWindow,pressCTRL-S,pastetheassemblycode,andhitFind.In9.5casesoutof10,thisplacesmedirectlyabovethefunctioncallIneedtofindinthenewversion.
Whenanupdatecomes,youcanusethesamemethodtofindnearlyallofyourknownaddresses.Itshouldworkforeveryaddressyoucanfindeasilyinassemblycode.Thereareafewcaveats,though:
•OllyDbglimitssearchtoeightoperations,soyoumustfindcodemarkersofthatsizeorsmaller.
•Theoperationsyouusecannotcontainanyotheraddresses,asthoseaddresseshavelikelychanged.
•Ifpartsofthegamehavechangedthatusetheaddressyou’relookingfor,thecodemightbedifferent.
•Ifthegamechangescompilersorswitchesoptimizationsettings,almostallcodewillbeentirelydifferent.
Asdiscussedin“AutomaticallyFindcurrentHealthandmaxHealth”onpage102,youcanbenefitfromwritingscriptsthatcarryoutthesetasksforyou.Seriousgamehackersworkveryhardtoautomaticallylocateasmanyaddressesaspossible,andsomeofthebestbotsareengineeredtoautomaticallydetecttheiraddressesatruntime,everytime.Itcanbealotofworkinitially,buttheinvestmentcandefinitelypayoff.
IdentifyingComplexStructuresinGameDataChapter4describedhowagamemightstoredatainstaticstructures.Thisknowledgewillsufficewhenyou’retryingtofindsimpledata,butitfallsshortfordatathatisstoredthroughdynamicstructures.Thisisbecausedynamicstructuresmightbescatteredacrossdifferentmemorylocations,followlongpointerchains,orrequirecomplexalgorithmstoactuallyextractthedatafromthem.
Thissectionexplorescommondynamicstructuresyou’llfindinvideogamecode,andhowtoreaddatafromthemoncethey’refound.Tobegin,I’lltalkabouttheunderlyingcompositionofeachdynamicstructure.Next,I’lloutlinethealgorithmsneededtoreadthedatafromthesestructures.(For
simplicity,eachalgorithmdiscussionassumesyouhaveapointertoaninstanceofthestructureaswellassomewaytoreadfrommemory.)Lastly,I’llcovertipsandtricksthatcanhelpyoudeterminewhenavalueyou’researchingforinmemoryisactuallyencapsulatedinoneofthesestructures,soyou’llknowwhentoapplythisknowledge.I’llfocusonC++,asitsobject-orientednatureandheavilyusedstandardlibraryaretypicallyresponsibleforsuchstructures.
NOTE
Someofthesestructuresmightdifferslightlyfrommachinetomachinebasedoncompilers,optimizationsettings,orstandardlibraryimplementations,butthebasicconceptswillremainthesame.Also,intheinterestofbrevity,Iwillbeomittingirrelevantpartsofthesestructures,suchascustomallocatorsorcomparisonfunctions.Workingexamplecodecanbefoundathttps://www.nostarch.com/gamehacking/intheresourcefilesforChapter5.
Thestd::stringClassInstancesofstd::stringareamongthemostcommonculpritsofdynamicstorage.ThisclassfromtheC++StandardTemplateLibrary(STL)abstractsstringoperationsawayfromthedeveloperwhilepreservingefficiency,makingitwidelyusedinalltypesofsoftware.Avideogamemightusestd::stringstructureforanystringdata,suchascreaturenames.
ExaminingtheStructureofastd::stringWhenyoustripawaythememberfunctionsandothernondatacomponentsofthestd::stringclass,thisisthestructurethatremains:
classstring{union{char*dataP;chardataA[16];};intlength;};
//pointtoastringinmemory
string*_str=(string*)stringAddress;
Theclassreserves16charactersthatarepresumablyusedtostorethestringinplace.Italso,however,declaresthatthefirst4bytescanbeapointertoacharacter.Thismightseemodd,butit’saresultofoptimization.Atsomepoint,thedevelopersofthisclassdecidedthat15characters(plusanullterminator)wasasuitablelengthformanystrings,andtheychosetosaveonmemoryallocationsandde-allocationsbyreserving16bytesofmemoryinadvance.Toaccommodatelongerstrings,theyallowedthefirst4bytesofthisreservedmemorytobeusedasapointertothecharactersoftheselongerstrings.
NOTE
Ifthecodewerecompiledto64bits,thenitwouldactuallybethefirst8(not4)bytesthatpointtoacharacter.Throughoutthisexample,however,youcanassume32-bitaddressesandthatintisthesizeofanaddress.
Accessingstringdatathiswaytakessomeoverhead.Thefunctiontolocatetherightbufferlookssomethinglikethis:
constchar*c_str(){if(_str->length<=15)return(constchar*)&_str->dataA[0];elsereturn(constchar*)_str->dataP;}
Thefactthatastd::stringcanbeeitheracompletestringorapointertoalongerstringmakesthisparticularstructurequitetrickyfromagame-hackingperspective.Somegamesmayusestd::stringtostorestringsthatonlyrarelyexceed15characters.Whenthisisthecase,youmightimplementbotsthatrelyonthesestrings,neverknowingthattheunderlyingstructureisinfactmorecomplicatedthanasimplestring.
Overlookingastd::stringCanRuinYourFunNotknowingthetruenatureofthestructurecontainingthedatayouneedcanleadyoutowriteabotthatworksonlysomeofthetimeandfailswhenitcounts.Imagine,forexample,thatyou’retryingtofigureouthowagame
storescreaturedata.Inyourhypotheticalsearch,youfindthatallthecreaturesinthegamearestoredinanarrayofstructuresthatlooksomethinglikeListing5-3.
structcreatureInfo{intuniqueID;charname[16];intnameLength;inthealthPercent;intxPosition;intyPosition;intmodelID;
intcreatureType;};
Listing5-3:Howyoumightinterpretcreaturedatafoundinmemory
Afterscanningthecreaturedatainmemory,sayyounoticethatthefirst4bytesofeachstructureareuniqueforeachcreature,soyoucallthosebytestheuniqueIDandassumetheyformasingleintproperty.Lookingfurtherinthememory,youfindthatthecreature’snameisstoredrightafteruniqueID,andaftersomededuction,youfigureoutthenameis16byteslong.ThenextvalueyouseeinmemoryturnsouttobethenameLength;it’sabitstrangethatanull-terminatedstringhasanassociatedlength,butyouignorethatoddityandcontinueanalyzingthedatainmemory.Afterfurtheranalysis,youdeterminewhattheremainingvaluesarefor,definethestructureshowninListing5-3,andwriteabotthatautomaticallyattackscreatureswithcertainnames.
AfterweeksoftestingyourbotwhilehuntingcreatureswithnameslikeDragon,Cyclops,Giant,andHound,youdecideit’stimetogiveyourbottoyourfriends.Fortheinauguraluse,yougathereveryonetogethertokillabossnamedSuperBossmanSupreme.TheentireteamsetsthebottoattackthebossfirstandtargetlessercreatureslikeaDemonorGrimReaperwhenthebossgoesoutofrange.
Onceyourteamarrivesattheboss’sdungeon...you’reallslowlyobliterated.
Whatwentwronginthisscenario?Yourgamemustbestoringcreaturenameswithstd::string,notjustasimplecharacterarray.ThenameandnameLengthfieldsincreatureInfoare,infact,partofastd::stringfield,andthenamecharacterarrayisaunionofdataAanddataPmembers.Super
BossmanSupremeislongerthan15characters,andbecausethebotwasnotawareofthestd::stringimplementation,itdidn’trecognizetheboss.Instead,itconstantlyretargetedsummonedDemoncreatures,effectivelykeepingyoufromtargetingthebosswhileheslowlydrainedyourhealthandsupplies.
DeterminingWhetherDataIsStoredinastd::stringWithoutknowinghowthestd::stringclassisstructured,you’dhavetroubletrackingdownbugslikethehypotheticaloneIjustdescribed.Butpairwhatyou’velearnedherewithexperience,andyoucanavoidthesekindsofbugsentirely.Whenyoufindastringlikenameinmemory,don’tjustassumeit’sstoredinasimplearray.Tofigureoutwhetherastringisinfactastd::string,askyourselfthesequestions:
•Whyisthestringlengthpresentforanull-terminatedstring?Ifyoucan’tthinkofagoodreason,thenyoumayhaveastd::stringonyourhands.
•Dosomecreatures(orothergameelements,dependingonwhatyou’relookingfor)havenameslongerthan16letters,butyoufindroomforonly16charactersinmemory?Ifso,thedataisalmostdefinitelystoredinastd::string.
•Isthenamestoredinplace,requiringthedevelopertousestrcpy()tomodifyit?It’sprobablyastd::string,becauseworkingwithrawCstringsinthiswayisconsideredbadpractice.
Finally,keepinmindthatthereisalsoaclasscalledstd::wstringthatisusedtostorewidestrings.Theimplementationisverysimilar,butwchar_tisusedinplaceofeverychar.
Thestd::vectorClassGamesmustkeeptrackofmanydynamicarraysofdata,butmanagingdynamicallysizedarrayscanbeverytricky.Forspeedandflexibility,gamedevelopersoftenstoresuchdatausingatemplatedSTLclasscalledstd::vectorinsteadofasimplearray.
ExaminingtheStructureofastd::vector
AdeclarationofthisclasslookssomethinglikeListing5-4.
template<typenameT>classvector{T*begin;T*end;T*reservationEnd;};
Listing5-4:Anabstractedstd::vectorobject
Thistemplateaddsanextralayerofabstraction,soI’llcontinuethisdescriptionusingastd::vectordeclaredwiththeDWORDtype.Here’showagamemightdeclarethatvector:
std::vector<DWORD>_vec;
Now,let’sdissectwhatastd::vectorofDWORDobjectswouldlooklikeinmemory.Ifyouhadtheaddressof_vecandsharedthesamememoryspace,youcouldre-createtheunderlyingstructureoftheclassandaccess_vecasshowninListing5-5.
classvector{DWORD*begin;DWORD*end;DWORD*tail;};//pointtoavectorinmemoryvector*_vec=(vector*)vectorAddress;
Listing5-5:ADWORDstd::vectorobject
Youcantreatthememberbeginlikearawarray,asitpointstothefirstelementinthestd::vectorobject.Thereisnoarraylengthmember,though,soyoumustcalculatethevector’slengthbasedonbeginandend,whichisanemptyobjectfollowingthefinalobjectinthearray.Thelengthcalculationcodelookslikethis:
intlength(){return((DWORD)_vec->end-(DWORD)_vec->begin)/sizeof(DWORD);}
Thisfunctionsimplysubtractstheaddressstoredinbeginfromtheaddressstoredinendtofindthenumberofbytesbetweenthem.Then,to
calculatethenumberofobjects,itdividesthenumberofbytesbythenumberofbytesperobject.
Usingbeginandthislength()function,youcansafelyaccesselementsin_vec.Thatcodewouldlooksomethinglikethis:
DWORDat(intindex){if(index>=_vec->length())thrownewstd::out_of_range();return_vec->begin[index];}
Givenanindex,thiscodewillfetchanitemfromthevector.Butiftheindexisgreaterthanthevector’slength,astd::out_of_rangeexceptionwillbethrown.Addingvaluestoastd::vectorwouldbeveryexpensiveiftheclasscouldn’treserveorreusememory,though.Toremedythis,theclassimplementsafunctioncalledreserve()thattellsthevectorhowmanyobjectstoleaveroomfor.
Theabsolutesizeofastd::vector(itscapacity)isdeterminedthroughanadditionalpointer,whichiscalledtailinthevectorclasswe’vere-created.Thecalculationforthecapacityresemblesthelengthcalculation:
intcapacity(){return((DWORD)_vec->tail-(DWORD)_vec->begin)/sizeof(DWORD);}
Tofindthecapacityofastd::vector,insteadofsubtractingthebeginaddressfromtheendaddress,asyouwouldtocalculatelength,thisfunctionsubtractsthebeginaddressfromtail.Additionally,youcanusethiscalculationathirdtimetodeterminethenumberoffreeelementsinthevectorbyusingtailandendinstead:
intfreeSpace(){return((DWORD)_vec->tail-(DWORD)_vec->end)/sizeof(DWORD);}
Givenpropermemoryreadingandwritingfunctions,youcanusethedeclarationinListing5-4andthecalculationsthatfollowtoaccessandmanipulatevectorsinthememoryofagame.Chapter6discussesreadingmemoryindetail,butfornow,let’slookatwaysyoucandeterminewhetherdatayou’reinterestedinisstoredinastd::vector.
DeterminingWhetherDataIsStoredinastd::vectorOnceyou’vefoundanarrayofdatainagame’smemory,thereareafewstepsyoucanfollowtodeterminewhetheritisstoredinastd::vector.First,youcanbesurethatthearrayisnotstoredinastd::vectorifithasastaticaddress,becausestd::vectorobjectsrequirepointerpathstoaccesstheunderlyingarray.Ifthearraydoesrequireapointerpath,havingafinaloffsetof0wouldindicateastd::vector.Toconfirm,youcanchangethefinaloffsetto4andcheckifitpointstothefinalobjectinthearrayinsteadofthefirstone.Ifso,you’realmostdefinitelylookingatavector,asyou’vejustconfirmedthebeginandendpointers.
Thestd::listClassSimilartostd::vector,std::listisaclassthatyoucanusetostoreacollectionofitemsinalinkedlist.Themaindifferencesarethatstd::listdoesn’trequireacontiguousstoragespaceforelements,cannotdirectlyaccesselementsbytheirindex,andcangrowinsizewithoutaffectinganypreviouselements.Duetotheoverheadrequiredtoaccessitems,itisraretoseethisclassusedingames,butitshowsupinsomespecialcases,whichI’lldiscussinthissection.
ExaminingtheStructureofastd::listThestd::listclasslookssomethinglikeListing5-6.
template<typenameT>classlistItem{listItem<T>*next;listItem<T>*prev;Tvalue;};
template<typenameT>classlist{listItem<T>*root;intsize;};
Listing5-6:Anabstractedstd::listobject
Therearetwoclasseshere:listItemandlist.Toavoidextraabstractionwhileexplaininghowstd::listworks,I’lldescribethisobjectasitwould
lookwhenthetypeisDWORD.Here’showagamewoulddeclareastd::listoftheDWORDtype:
std::list<DWORD>_lst;
Giventhatdeclaration,thestd::listisstructuredlikethecodeinListing5-7.
classlistItem{listItem*next;listItem*prev;DWORDvalue;};classlist{listItem*root;intsize;};//pointtoalistlist*_lst=(list*)listAddress;
Listing5-7:ADWORDstd::listobject
Theclasslistrepresentsthelistheader,whilelistItemrepresentsavaluestoredinthelist.Insteadofbeingstoredcontiguously,theitemsinthelistarestoredindependently.Eachitemcontainsapointertotheitemthatcomesafterit(next)andtheonethatcomesbeforeit(prev),andthesepointersareusedtolocateitemsinthelist.Therootitemactsasamarkerfortheendofthelist;thenextpointerofthelastitempointstoroot,asdoestheprevpointerofthefirstitem.Therootitem’snextandprevpointersalsopointtothefirstitemandthelastitem,respectively.Figure5-5showswhatthislookslike.
Giventhisstructure,youcanusethefollowingcodetoiterateoverastd::listobject:
Figure5-5:Astd::listflowchart
//iterateforwardlistItem*it=_lst->root->next;for(;it!=_lst->root;it=it->next)printf("Valueis%d\n",it->value);
//iteratebackwardlistItem*it=_lst->root->prev;for(;it!=_lst->root;it=it->prev)printf("Valueis%d\n",it->value);
Thefirstloopstartsatthefirstitem(root->next)anditeratesforward(it=it->next)untilithitstheendmarker(root).Thesecondloopstartsatthelastitem(root->pres)anditeratesbackward(it=it->prev)untilithitstheendmarker(root).Thisiterationreliesonnextandprevbecauseunlikeobjectsinanarray,objectsinastd::listarenotcontiguous.Sincethememoryofeachobjectinastd::listisnotcontiguous,there’snoquick-and-dirtywaytocalculatethesize.Instead,theclassjustdefinesasizemember.Additionally,theconceptofreservingspacefornewobjectsisirrelevantforlists,sothere’snovariableorcalculationtodeterminealist’scapacity.
DeterminingWhetherGameDataIsStoredinastd::listIdentifyingobjectsstoredinthestd::listclasscanbetricky,butthereareafewhintsyoucanwatchfor.First,itemsinastd::listcannothavestaticaddresses,soifthedatayouseekhasastaticaddress,thenyou’reintheclear.Itemsthatareobviouslypartofacollectionmay,however,bepartofastd::listifthey’renotcontiguousinmemory.
Alsoconsiderthatobjectsinastd::listcanhaveinfinitelylongpointerchains(thinkit->prev->next->prev->next->prev...),andpointerscanningfortheminCheatEnginewillshowmanymoreresultswhenNoLoopingPointersisturnedoff.
Youcanalsouseascripttodetectwhenavalueisstoredinalinkedlist.Listing5-8showsaCheatEnginescriptthatdoesjustthis.
function_verifyLinkedList(address)localnextItem=readInteger(address)or0localpreviousItem=readInteger(address+4)or0localnextItemBack=readInteger(nextItem+4)localpreviousItemForward=readInteger(previousItem)
return(address==nextItemBackandaddress==previousItemForward)end
functionisValueInLinkedList(valueAddress)foraddress=valueAddress-8,valueAddress-48,-4doif(_verifyLinkedList(address))thenreturnaddressendendreturn0end
localnode=isValueInLinkedList(addressOfSomeValue)if(node>0)thenprint(string.format("ValueinLL,topofnodeat0x0%x",node))end
Listing5-8:Determiningwhetherdataisinastd::listusingaCheatEngineLuascript
There’squiteabitofcodehere,butwhatit’sdoingisactuallyprettysimple.TheisValueInLinkedList()functiontakesanaddressofsomevalueandthenlooksbackwardforupto40bytes(10integerobjects,incasethevalueisinsomelargerstructure),starting8bytesabovetheaddress(twopointersmustbepresent,andtheyare4byteseach).Becauseofmemoryalignment,thisloopiteratesinstepsof4bytes.
Oneachiteration,theaddressispassedtothe_verifyLinkedList()function,whichiswherethemagichappens.Ifwelookatitintermsoflinkedliststructureasdefinedinthischapter,thefunctionsimplydoesthis:
return(node->next->prev==node&&node->prev->next==node)
Thatis,thefunctionbasicallyassumesthememoryaddressit’sgivenpointstoalinkedlist,anditmakessurethesupposednodehasvalidnextandpreviousnodes.Ifthenodesarevalid,theassumptionwascorrectandtheaddressisthatofalinkedlistnode.Ifthenodesdon’texistordon’tpointtotherightlocations,theassumptionwaswrongandtheaddressisnotpartofalinkedlist.
Keepinmindthatthisscriptwon’tgiveyoutheaddressofthelist’srootnodebutsimplytheaddressofthenodecontainingthevalueyou’vegivenit.Toproperlytraversealinkedlist,you’llneedtoscanforavalidpointerpathtotherootnode,soyou’llneeditsaddress.
Findingthataddresscanrequiresomesearchingofmemorydumps,alotoftrialanderror,andatonofheadscratching,butit’sdefinitelypossible.Thebestwaytostartistofollowthechainofprevandnextnodesuntilyoufindanodewithdatathatiseitherblank,nonsensical,orfilledwiththevalue0xBAADF00D(some,butnotall,standardlibraryimplementationsusethisvaluetomarkrootnodes).
Thisinvestigationcanalsobemadeeasierifyouknowexactlyhowmanynodesareinthelist.Evenwithoutthelistheader,youcandeterminetheamountofnodesbycontinuouslyfollowingthenextpointeruntilyouendupbackatyourstartingnode,asinListing5-9.
functioncountLinkedListNodes(nodeAddress)localcounter=0localnext=readInteger(nodeAddress)while(next~=nodeAddress)docounter=counter+1next=readInteger(next)endreturncounterend
Listing5-9:Determiningthesizeofanarbitrarystd::listusingaCheatEngineLuascript
First,thisfunctioncreatesacountertostorethenumberofnodesandavariabletostorethenextnode’saddress.Thewhilelooptheniteratesoverthenodesuntilitendsupbackattheinitialnode.Finally,itreturnsthecountervariable,whichwasincrementedoneveryiterationoftheloop.
FINDTHEROOTNODEWITHASCRIPTIt’sactuallypossibletowriteascriptthatcanfindtherootnode,butI’llleaveitasanoptionalexerciseforyou.Howdoesitwork?Well,therootnodemustbeinthechainofnodes,thelistheaderpointstotheroot,andthesizeofthelistwillimmediatelyfollowtherootinmemory.Giventhisinformation,youcanwriteascriptthatwillsearchforanymemorycontainingapointertooneofthelist’snodes,followedbythesizeofthelist.Moreoftenthannot,thispieceofmemoryisthelistheader,andthenodeitpointstoistherootnode.
Thestd::mapClassLikeastd::list,astd::mapuseslinksbetweenelementstoformitsstructure.Uniquetostd::map,however,isthefactthateachelementstorestwopiecesofdata(akeyandavalue),andsortingtheelementsisaninherentpropertyoftheunderlyingdatastructure:ared-blacktree.Thefollowingcodeshowsthestructuresthatcomposeastd::map.
template<typenamekeyT,typenamevalT>structmapItem{mapItem<keyT,valT>*left;mapItem<keyT,valT>*parent;mapItem<keyT,valT>*right;keyTkey;valTvalue;};
template<typenamekeyT,typenamevalT>structmap{DWORDirrelevant;mapItem<keyT,valT>*rootNode;intsize;}
Ared-blacktreeisaself-balancingbinarysearchtree,soastd::mapis,too.IntheSTL’sstd::mapimplementation,eachelement(ornode)inthetreehasthreepointers:left,parent,andright.Inadditiontothepointers,eachnodealsohasakeyandavalue.Thenodesarearrangedinthetreebasedonacomparisonbetweentheirkeys.Theleftpointerofanodepointstoanodewithasmallerkey,andtherightpointerpointstoanodewithalargerkey.Theparentpointstotheuppernode.ThefirstnodeinthetreeiscalledtherootNode,andnodesthatlackchildrenpointtoit.
Visualizingastd::mapFigure5-6showsastd::mapthathasthekeys1,6,8,11,13,15,17,22,25,and27.
Figure5-6:Ared-blacktree
Thetopnode(holdingthevalue13)ispointedtobytheparentofrootNode.Everythingtotheleftofithasasmallerkey,andeverythingtotherighthasagreaterkey.Thisistrueforanynodeinthetree,andthistruthenablesefficientkey-basedsearch.Whilenotrepresentedintheimage,theleftpointeroftherootnodewillpointtotheleftmostnode(1),andtherightpointerwillpointtotherightmostnode(27).
AccessingDatainastd::mapOnceagain,I’lluseastaticstd::mapdefinitionwhendiscussinghowtoextractdatafromthestructure.Sincethetemplatetakestwotypes,I’llalsousesomepseudotypestokeepthingsobvious.Here’sthedeclarationforthestd::mapobjectI’llreferencefortherestofthesection:
typedefintkeyInt;typedefintvalInt;std::map<keyInt,valInt>myMap;
Withthisdeclaration,thestructureofmyMapbecomes:
structmapItem{mapItem*left;mapItem*parent;mapItem*right;keyIntkey;valIntvalue;};structmap{DWORDirrelevant;mapItem*rootNode;intsize;}map*_map=(map*)mapAddress;
Therearesomeimportantalgorithmsthatyoumightneedtoaccessthedatainastd::mapstructureinagame.First,blindlyiteratingovereveryiteminthemapcanbeusefulifyoujustwanttoseeallofthedata.Todothissequentially,youcouldwriteaniterationfunctionlikethis:
voiditerateMap(mapItem*node){if(node==_map->rootNode)return;iterateMap(node->left);printNode(node);iterateMap(node->right);}
Afunctiontoiterateoveranentiremapwouldfirstreadthecurrentnodeandcheckwhetherit’stherootNode.Ifnot,itwouldrecurseleft,printthenode,andrecurseright.
Tocallthisfunction,you’dhavetopassapointertotherootNodeasfollows:
iterateMap(_map->rootNode->parent);
Thepurposeofastd::map,however,istostorekeyeddatainaquicklysearchableway.Whenyouneedtolocateanodegivenaspecifickey,mimickingtheinternalsearchalgorithmispreferabletoscanningtheentiretree.Thecodeforsearchingastd::maplookssomethinglikethis:
mapItem*findItem(keyIntkey,mapItem*node){if(node!=_map->rootNode){if(key==node->key)returnnode;elseif(key<node->key)returnfindItem(key,node->left);
elsereturnfindItem(key,node->right);}elsereturnNULL;}
Startingatthetopofthetree,yousimplyrecurseleftifthecurrentkeyisgreaterthanthesearchkeyandrecurserightifitissmaller.Ifthekeysareequal,youreturnthecurrentnode.Ifyoureachthebottomofthetreeanddon’tfindthekey,youreturnNULLbecausethekeyisn’tstoredinthemap.
Here’sonewayyoumightusethisfindItem()function:
mapItem*ret=findItem(someKey,_map->rootNode->parent);if(ret)printNode(ret);
AslongasfindItem()doesn’treturnNULL,thiscodeshouldprintanodefrom_map.
DeterminingWhetherGameDataIsStoredinastd::mapTypically,Idon’tevenconsiderwhetherdatacouldbeinastd::mapuntilIknowthecollectionisnotanarray,astd::vector,orastd::list.Ifyouruleoutallthreeoptions,thenaswithastd::list,youcanlookatthethreeintegervaluesbeforethevalueandcheckiftheypointtomemorythatcouldpossiblybeothermapnodes.
Onceagain,thiscanbedonewithaLuascriptinCheatEngine.ThescriptissimilartotheoneIshowedforlists,loopingbackwardovermemorytoseeifavalidnodestructureisfoundbeforethevalue.Unlikethelistcode,though,thefunctionthatverifiesanodeismuchtrickier.TakealookatthecodeinListing5-10,andthenI’lldissectit.
function_verifyMap(address)localparentItem=readInteger(address+4)or0
localparentLeftItem=readInteger(parentItem+0)or0localparentRightItem=readInteger(parentItem+8)or0
➊localvalidParent=parentLeftItem==addressorparentRightItem==addressif(notvalidParent)thenreturnfalseend
localtries=0
locallastChecked=parentItemlocalparentsParent=readInteger(parentItem+4)or0➋while(readInteger(parentsParent+4)~=lastCheckedandtries<200)dotries=tries+1lastChecked=parentsParentparentsParent=readInteger(parentsParent+4)or0end
returnreadInteger(parentsParent+4)==lastCheckedend
Listing5-10:Determiningwhetherdataisinastd::mapusingaCheatEngineLuascript
Givenaddress,thisfunctionchecksifaddressisinamapstructure.Itfirstchecksifthere’savalidparentnodeand,ifso,checkswhetherthatparentnodepointstoaddressoneitherside➊.Butthischeckisn’tenough.Ifthecheckpasses,thefunctionwillalsoclimbupthelineofparentnodesuntilitreachesanodethatistheparentofitsownparent➋,trying200timesbeforecallingitquits.Iftheclimbsucceedsinfindinganodethatisitsowngrandparent,thenaddressdefinitelypointstoamapnode.Thisworksbecause,asIoutlinedin“Visualizingastd::map”onpage114,atthetopofeverymapisarootnodewhoseparentpointstothefirstnodeinthetree,andthatnode’sparentpointsbacktotherootnode.
NOTE
Ibetyoudidn’texpecttorunintothegrandfatherparadoxfromtimetravelwhenreadingagame-hackingbook!
UsingthisfunctionandaslightlymodifiedbacktrackingloopfromListing5-8,youcanautomaticallydetectwhenavalueisinsideamap:
functionisValueInMap(valueAddress)foraddress=valueAddress-12,valueAddress-52,-4doif(_verifyMap(address))thenreturnaddressendendreturn0end
localnode=isValueInMap(addressOfSomeValue)if(node>0)then
print(string.format("Valueinmap,topofnodeat0x0%x",node))end
Asidefromfunctionnames,theonlychangeinthiscodefromListing5-8isthatitstartslooping12bytesbeforethevalueinsteadof8,becauseamaphasthreepointersinsteadofthetwoinalist.Onegoodconsequenceofamap’sstructureisthatit’seasytoobtaintherootnode.Whenthe_verifyMapfunctionreturnstrue,theparentsParentvariablewillcontaintheaddressoftherootnode.Withsomesimplemodifications,youcouldreturnthistothemaincallandhaveeverythingyouneedtoreadthedatafromastd::mapinoneplace.
ClosingThoughtsMemoryforensicsisthemosttime-consumingpartofhackinggames,anditsobstaclescanappearinallshapesandsizes.Usingpurpose,patterns,andadeepunderstandingofcomplexdatastructures,however,youcanquicklyovercometheseobstacles.Ifyou’restillabitconfusedaboutwhat’sgoingon,makesuretodownloadandplaywiththeexamplecodeprovided,asitcontainsproofsofconceptforallofthealgorithmscoveredinthischapter.
InChapter6,we’llstartdivingintothecodeyouneedtoreadfromandwritetoagame’smemoryfromyourownprogramssoyoucantakethefirststepinputtingtoworkallofthisinformationaboutmemorystructures,addresses,anddata.
6READINGFROMANDWRITINGTOGAME
MEMORY
EarlierchaptersdiscussedhowmemoryisstructuredaswellashowtoscanandmodifymemoryusingCheatEngineandOllyDbg.Workingwithmemorywillbeessentialwhenyoubegintowritebots,andyourcodewillneedtoknowhowtodoso.
Thischapterdigsintothecode-leveldetailsofmemorymanipulation.First,you’lllearnhowtousecodetolocateandobtainhandlestogameprocesses.Next,you’lllearnhowtousethosehandlestoreadfromandwritetomemorybothfromremoteprocessesandfrominjectedcode.Towrapup,you’lllearnbypassesforacertainmemoryprotectiontechnique,completewithasmallexampleofcodeinjection.You’llfindtheexamplecodeforthischapterintheGameHackingExamples/Chapter6_AccessingMemorydirectoryinthisbook’ssourcefiles.
NOTE
WhenItalkaboutAPIfunctionsinthischapter(andinlaterones),I’mreferringtotheWindowsAPIunlessotherwisespecified.IfIdon’tmentionaheaderfileforthelibrary,youcanassumeitisWindows.h.
ObtainingtheGame’sProcessIdentifierToreadfromorwritetoagame’smemory,youneeditsprocessidentifier(PID),anumberthatuniquelyidentifiesanactiveprocess.Ifthegamehasavisiblewindow,youcanobtainthePIDoftheprocessthatcreatedthatwindowbycallingGetWindowThreadProcessId().Thisfunctiontakesthewindow’shandleasthefirstparameterandoutputsthePIDtothesecondparameter.Youcanfindthewindow’shandlebypassingitstitle(thetextonthetaskbar)asthesecondparametertoFindWindow(),asshowninListing6-1.
HWNDmyWindow=FindWindow(NULL,"Titleofthegamewindowhere");DWORDPID;GetWindowThreadProcessId(myWindow,&PID);
Listing6-1:Fetchingawindow’shandletoobtainaPID
Withthewindowhandlesecured,allyouhavetodoiscreateaplacetostorethePIDandcallGetWindowThreadProcessId(),asshowninthisexample.
Ifagameisn’twindowedorthewindownameisn’tpredictable,youcanfindthegame’sPIDbyenumeratingallprocessesandlookingforthenameofthegamebinary.Listing6-2doesthisusingtheAPIfunctionsCreateToolhelp32Snapshot(),Process32First(),andProcess32Next()fromtlhelp32.h.
#include<tlhelp32.h>
PROCESSENTRY32entry;entry.dwSize=sizeof(PROCESSENTRY32);HANDLEsnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,NULL);if(Process32First(snapshot,&entry)==TRUE){while(Process32Next(snapshot,&entry)==TRUE){wstringbinPath=entry.szExeFile;if(binPath.find(L"game.exe")!=wstring::npos){printf("gamepidis%d\n",entry.th32ProcessID);break;}}}CloseHandle(snapshot);
Listing6-2:Fetchingagame’sPIDwithoutthewindowname
Listing6-2mightlookabitmorecomplexthanListing6-1,butunderneathallthatcode,thefunctionisactuallylikeacanonicalfor(iterator;comparator;increment)loop.TheCreateToolhelp32Snapshot()functionobtainsalistofprocessesnamedsnapshot,andentryisaniteratoroverthatlist.ThevaluereturnedbyProcess32First()initializestheiterator,whileProcess32Next()incrementsit.Finally,theBooleanreturnvalueofProcess32Next()isthecomparator.Thiscodejustiteratesoverasnapshotofeveryrunningprocess,looksforonewhosebinarypathcontainsthetextgame.exe,andprintsitsPID.
ObtainingProcessHandlesOnceyouknowagame’sPID,youcanobtainahandletotheprocessitselfusinganAPIfunctioncalledOpenProcess().Thisfunctionallowsyoutofetchhandleswiththeaccesslevelsyouneedtoreadfromandwritetomemory.Thisiscrucialtogamehacking,asanyfunctionthatoperatesonaprocesswillrequireahandlewithproperaccess.
Let’stakealookattheprototypeofOpenProcess():
HANDLEOpenProcess(DWORDDesiredAccess,BOOLInheritHandle,DWORDProcessId);
Thefirstparameter,DesiredAccess,expectsoneoramixtureofprocessaccessflagstosetonthehandlethatOpenProcess()returns.Therearemanyflagsyoucanuse,butthesearethemostcommoningamehacking:
PROCESS_VM_OPERATIONThereturnedhandlecanbeusedwithVirtualAllocEx(),VirtualFreeEx(),andVirtualProtectEx()toallocate,free,andprotectchunksofmemory,respectively.
PROCESS_VM_READThereturnedhandlecanbeusedwithReadProcessMemory().
PROCESS_VM_WRITEThereturnedhandlecanbeusedwithWriteProcessMemory(),butitmustalsohavePROCESS_VM_OPERATIONrights.YoucansetbothflagsbypassingPROCESS_VM_OPERATION|PROCESS_VM_WRITEastheDesiredAccessparameter.
PROCESS_CREATE_THREADThereturnedhandlecanbeusedwithCreateRemoteThread().
PROCESS_ALL_ACCESSThereturnedhandlecanbeusedtodoanything.Avoidusingthisflag,asitcanonlybeusedbyprocesseswithdebugprivilegesenabledandhascompatibilityissueswitholderversionsofWindows.
Whenfetchingahandletoagame,youcantypicallyjustsettheOpenProcess()function’ssecondparameter,InheritHandle,tofalse.Thethirdparameter,ProcessId,expectsthePIDoftheprocesstobeopened.
WorkingwithOpenProcess()Nowlet’swalkthroughanexamplecalltoOpenProcess()thatusesahandlewithaccesspermissionsallowingittoreadfromandwritetomemory:
DWORDPID=getGamePID();HANDLEprocess=OpenProcess(PROCESS_VM_OPERATION|PROCESS_VM_READ|PROCESS_VM_WRITE,
FALSE,PID);➊if(process==INVALID_HANDLE_VALUE){printf("FailedtoopenPID%d,errorcode%d",PID,GetLastError());}
First,thecalltogetGamePID()fetchesthePIDyou’relookingfor.(Thefunctionissomethingyou’llhavetowriteyourself,thoughitcouldjustbeoneofthesnippetsIshowedinListings6-1and6-2,fleshedoutintoafullblownfunction.)Then,thecodecallsOpenProcess()withthreeflags:thePROCESS_VM_OPERATIONflaggivesthishandlememoryaccesspermissions,andtheothertwocombinedgiveitreadandwritepermissions.Thisexamplealsocontainsanerror-handlingcase➊,butaslongasyouhavethecorrectPID,youhavevalidaccessflags,andyourcodeisrunningunderthesameorhigherpermissionsasthegame(forexample,ifyoustartyourbotusingRunAsAdmin),thecallshouldneverfail.
Onceyou’redoneusingahandle,cleanitupusingCloseHandle()asfollows:
CloseHandle(process);
CloseHandle(process);
Youcanreusehandlesasmuchasyouwant,soyoucanleaveoneopenuntilyou’recompletelydoneusingitoruntilyourbotisexited.
Nowthatyou’veseenhowtoopenaprocesshandleinpreparationformanipulatinggamememory,let’sdigintohowtoactuallyaccessthememoryofthatprocess.
AccessingMemoryTheWindowsAPIexposestwofunctionsthatarecrucialtomemoryaccess:ReadProcessMemory()andWriteProcessMemory().Youcanusethesefunctionstoexternallymanipulateagame’smemory.
WorkingwithReadProcessMemory()andWriteProcessMemory()Theprototypesforthesetwofunctions(showninListing6-3)resembleeachotherclosely,andyou’llfollowalmostexactlythesamestepstousethem.
BOOLReadProcessMemory(HANDLEProcess,LPVOIDAddress,LPVOIDBuffer,DWORDSize,DWORD*NumberOfBytesRead);BOOLWriteProcessMemory(HANDLEProcess,LPVOIDAddress,LPCVOIDBuffer,DWORDSize,
DWORD*NumberOfBytesWritten);
Listing6-3:ReadProcessMemory()andWriteProcessMemory()prototypes
BothfunctionsexpectProcesstobeaprocesshandleandAddresstobethetargetmemoryaddress.Whenthefunctionisreadingfrommemory,Bufferisexpectedtopointtoanobjectthatwillholdthereaddata.Whenthefunctioniswritingtomemory,Bufferisexpectedtopointtothedatatowrite.Inbothcases,SizedefinesthesizeofBuffer,inbytes.Thefinalparametertobothfunctionsisusedtooptionallyreturnthenumberofbytesthatwereaccessed;youcansafelysetittoNULL.Unlessthefunctionfails,the
valuereturnedinthefinalparametershouldbeequaltoSize.
AccessingaValueinMemorywithReadProcessMemory()andWriteProcessMemory()ThecodeinListing6-4showshowyoumightusethesefunctionstoaccessavalueinmemory.
DWORDval;ReadProcessMemory(proc,adr,&val,sizeof(DWORD),0);printf("Currentmemvalueis%d\n",val);
val++;
WriteProcessMemory(proc,adr,&val,sizeof(DWORD),0);ReadProcessMemory(proc,adr,&val,sizeof(DWORD),0);printf("Newmemvalueisconfirmedas%d\n",val);
Listing6-4:ReadingfromandwritingtoprocessmemoryusingtheWindowsAPI
Beforecodelikethisappearsinaprogram,youneedtofindthePID(proc)asdescribedin“ObtainingtheGame’sProcessIdentifier”onpage120,aswellasthememoryaddress(adr)youwanttoreadfromorwriteto.Withthosevaluesinplace,theReadProcessMemory()functionstoresafetchedvaluefrommemoryinval.Then,thecodeincrementsvalandreplacestheoriginalvaluebycallingWriteProcessMemory().Afterthewritetakesplace,ReadProcessMemory()iscalledonthesameaddresstoconfirmthenewmemoryvalue.Noticethatvalisn’tactuallyabuffer.Passing&valastheBufferparameterworksbecauseitcanbeapointertoanystaticmemorystructure,aslongasSizematches.
WritingTemplatedMemoryAccessFunctionsOfcourse,theexampleinListing6-4assumesyoualreadyknowwhattypeofmemoryyou’redealingwith,andithardcodesthetypeasDWORD.Tobeaversatilegamehacker,it’sbettertohavesomegenericcodeinyourtoolboxtoavoidduplicatingcodefordifferenttypes.GenericmemoryreadingandwritingfunctionsthatsupportdifferenttypesmightlooklikeListing6-5.
template<typenameT>
TreadMemory(HANDLEproc,LPVOIDadr){Tval;ReadProcessMemory(proc,adr,&val,sizeof(T),NULL);returnval;}
template<typenameT>voidwriteMemory(HANDLEproc,LPVOIDadr,Tval){WriteProcessMemory(proc,adr,&val,sizeof(T),NULL);}
Listing6-5:Genericmemoryfunctions
ThesefunctionsuseC++templatestoacceptarbitrarytypesasarguments.Theyallowyoutoaccessmemorywithwhatevertypesyoulikeinaverycleanway.Forexample,giventhesereadMemory()andwriteMemory()templatesIjustshowed,youcouldmakethecallsinListing6-6.
DWORDvalue=readMemory<DWORD>(proc,adr);//readwriteMemory<DWORD>(proc,adr,value++);//incrementandwrite
Listing6-6:Callingtemplatedmemoryaccessfunctions
ComparethistothecallstoWriteProcessMemory()andReadProcessMemory()inListing6-4.Thiscodestillreadsavalue,incrementsit,andwritesthenewvaluetomemory.Butsincethetemplatedfunctionsletyouspecifythetypewhenyoucallthem,youdon’tneedanewreadMemory()andwriteMemory()functionforeverydatatypeyoumightneedtoworkwith.That’smuchcleaner,sinceyou’lloftenwanttoworkwithallkindsofdata.
MemoryProtectionWhenmemoryisallocatedbyagame(oranyprogram),itisplacedinapage.Inx86Windows,pagesarechunksof4,096bytesthatstoredata.Becauseallmemorymustbewithinapage,theminimalallocationunitis4,096bytes.Theoperatingsystemcanplacememorychunkssmallerthan4,096bytesasasubsetofanexistingpagethathasenoughuncommittedspace,inanewlyallocatedpage,oracrosstwocontiguouspagesthathavethesameattributes.
Memorychunks4,096bytesorlargerspannpages,wherenis
Theoperatingsystemtypicallylooksforroominexistingpageswhenallocatingmemory,butitallocatesnewpagesondemandifnecessary.
NOTE
It’salsopossibleforlargechunkstospann+1pages,asthere’snoguaranteethatachunkbeginsatthestartofapage.
Theimportantthingtounderstandaboutmemorypagesisthateachpagehasasetofspecificattributes.Mostoftheseattributesaretransparentinusermode,butthere’soneyoushouldbeextraconsciousofwhenworkingwithmemory:protection.
Differentiatingx86WindowsMemoryProtectionAttributesThememory-readingtechniquesyou’velearnedsofarareverybasic.Theyassumethatthememoryyou’reaccessingisprotectedwiththePAGE_READWRITEattribute.Whilethisassumptioniscorrectforvariabledata,othertypesofdataexistonpageswithdifferenttypesofprotection.Table6-1describesthedifferenttypesofmemoryprotectioninx86Windows.
Table6-1:MemoryProtectionTypes
Protectiontype Value Readpermission?
Writepermission?
Executepermission?
Specialpermissions?
PAGE_NOACCESS 0x01 No No No PAGE_READONLY 0x02 Yes No No PAGE_READWRITE 0x04 Yes No No PAGE_WRITECOPY 0x08 Yes Yes No Yes,copyon
writePAGE_EXECUTE 0x10 No No Yes PAGE_EXECUTE_READ 0x20 Yes No Yes
PAGE_EXECUTE_READ 0x20 Yes No Yes PAGE_EXECUTE_READWRITE 0x40 Yes Yes Yes
PAGE_EXECUTE_WRITECOPY 0x80 Yes Yes Yes Yes,copyonwrite
PAGE_GUARD 0x100 No No No Yes,guardpage
IfaprotectiontypeinTable6-1hasaYesinanypermissioncolumn,itmeanstheactioninquestioncanbeperformedonthatpageofmemory.Forexample,ifapageisPAGE_READONLY,thenaprogramcanreadthememoryonthatpage,buttheprogramcannotwritetothatmemory.
Constantstrings,forexample,areusuallystoredwithPAGE_READONLYprotection.Otherconstantdata,suchasvirtualfunctiontablesandamodule’sentirePortableExecutable(PE)header(whichcontainsinformationaboutaprogram,suchasthekindofapplicationitis,libraryfunctionsituses,itssize,andsoon),arealsostoredonread-onlypages.Assemblycode,ontheotherhand,isstoredonpagesprotectedwithPAGE_EXECUTE_READ.
Mostprotectiontypesinvolveonlysomecombinationofread,write,andexecuteprotection.Fornow,youcansafelyignorespecialprotectiontypes;Icoverthemin“SpecialProtectionTypes”onpage126ifyou’recurious,butonlyveryadvancedhackswilleverrequireknowledgeofthem.Thebasicprotectiontypes,though,willbeprevalentinyourgame-hackingadventures.
SPECIALPROTECTIONTYPESTwoprotectiontypesinTable6-1includecopy-on-writeprotection.Whenmultipleprocesseshavepagesofmemorythatareidentical(suchaspageswithmappedsystemDLLs),copy-on-writeprotectionisusedtoconservememory.Theactualdataisstoredinonlyonephysicalplace,andtheoperatingsystemvirtuallymapsallmemorypagescontainingthatdatatothephysicallocation.Ifaprocesssharingthememorymakesachangetoit,acopyofthedatawillbemadeinphysicalmemory,thechangewillbeapplied,andthememorypage(s)forthatprocesswillberemappedtothenewphysical
memory.Whenacopyonwritehappens,theprotectionforallaffectedpageschangesaccordingly;PAGE_WRITECOPYwillbecomePAGE_READWRITE,andPAGE_EXECUTE_WRITECOPYwillbecomePAGE_EXECUTE_READWRITE.I’vefoundnogamehacking–specificusesforcopy-on-writepages,butit’susefultounderstandthem.
Pagescanalsobecreatedwithguardprotection.Guardedpagesmusthaveasecondaryprotection,definedlikePAGE_GUARD|PAGE_READONLY.Whentheprogramtriestoaccessaguardedpage,theoperatingsystemwillthrowaSTATUS_GUARD_PAGE_VIOLATIONexception.Oncetheexceptionishandled,theguardprotectionisremovedfromthepage,leavingonlythesecondaryprotection.Onewayinwhichtheoperatingsystemusesthistypeofprotectionistodynamicallyexpandthecallstackbyplacingaguardedpageatthetopandallocatingmorememorywhenthatguardedpageishit.Somememoryanalysistoolsplaceguardedpagesafterheapmemorytodetectheapcorruptionbugs.Inthecontextofgamehacking,aguardedpagecanbeusedasatripwirethattellsyouwhenagamemightbeattemptingtodetectyourcodewithinitsmemory.
ChangingMemoryProtectionWhenyouwanttohackagame,you’llsometimesneedtoaccessmemoryinawaythatisforbiddenbythememorypage’sprotection,makingitimportanttobeabletochangememoryprotectionatwill.Luckily,theWindowsAPIprovidestheVirtualProtectEx()functionforthispurpose.Thisisthefunction’sprototype:
BOOLVirtualProtectEx(HANDLEProcess,LPVOIDAddress,DWORDSize,DWORDNewProtect,PDWORDOldProtect);
TheparametersProcess,Address,andSizetakethesameinputastheydointheReadProcessMemory()andWriteProcessMemory()functions.NewProtectshouldspecifythenewprotectionflagsforthememory,andOldProtectcanoptionallypointtoaDWORDwheretheoldprotectionflagswillbestored.
Themostgranularscaleformemoryprotectionisperpage,whichmeansVirtualProtectEx()willsetthenewprotectiontoeverypagethatisonorbetweenAddressandAddress+Size-1.
NOTE
TheVirtualProtectEx()functionhasasistercalledVirtualProtect().Theyworkthesameway,butVirtualProtect()operatesonlyontheprocesscallingitand,thus,doesnothaveaprocesshandleparameter.
Whenyou’rewritingyourowncodetochangememoryprotections,Isuggestmakingitflexiblebycreatingatemplate.AgenericwrappedfunctionforVirtualProtectEx()shouldlooksomethinglikeListing6-7.
template<typenameT>DWORDprotectMemory(HANDLEproc,LPVOIDadr,DWORDprot){DWORDoldProt;VirtualProtectEx(proc,adr,sizeof(T),prot,&oldProt);returnoldProt;}
Listing6-7:Agenericfunctiontochangememoryprotection
Withthistemplateinplace,ifyouwantedto,say,writeaDWORDtoamemorypagewithoutwritepermission,youmightdosomethinglikethis:
protectMemory<DWORD>(process,address,PAGE_READWRITE)writeMemory<DWORD>(process,address,newValue)
First,thissetstheprotectiononthememorytochangetoPAGE_READWRITE.Withwritepermissiongranted,thedoorisopentocallwriteMemory()andchangethedataataddress.
Whenyou’rechangingmemoryprotection,it’sbestpracticetoletthechangepersistonlyaslongasneededandrestoretheoriginalprotectionassoonaspossible.Thisislessefficient,butitensuresthatagamedoesn’tdetectyourbot(forexample,bynoticingthatsomeofitsassemblycodepageshavebecomewritable).
Atypicalwriteoperationonread-onlymemoryshouldlooklikethis:
DWORDoldProt=protectMemory<DWORD>(process,address,PAGE_READWRITE);
writeMemory<DWORD>(process,address,newValue);protectMemory<DWORD>(process,address,oldProt);
ThiscodecallstheprotectMemory()functionfromListing6-7tochangetheprotectiontoPAGE_READWRITE.ItthenwritesnewValuetothememorybeforechangingtheprotectionbacktooldProt,whichwassettothepage’soriginalprotectionbytheinitialcalltoprotectMemory().ThewriteMemory()functionusedhereisthesameonedefinedinListing6-5.
Afinalimportantpointisthatwhenyou’remanipulatingagame’smemory,it’sentirelypossiblethatthegamewillaccessthememoryatthesametimeasyou.Ifthenewprotectionthatyousetisnotcompatiblewiththeoriginalprotection,thegameprocesswillgetanACCESS_VIOLATIONexceptionandcrash.Forinstance,ifyouchangememoryprotectionfromPAGE_EXECUTEtoPAGE_READWRITE,thegamemighttrytoexecutethecodeonthepage(s)whenthememoryisnotmarkedasexecutable.Inthiscase,you’dwanttoinsteadsetthememoryprotectiontoPAGE_EXECUTE_READWRITEtoensurethatyoucanoperateonthememorywhilestillallowingthegametoexecuteit.
AddressSpaceLayoutRandomizationSofar,I’vedescribedmemoryaddressesasstaticintegersthatchangeonlyasthebinarychanges.ThismodeliscorrectonWindowsXPandearlier.OnlaterWindowssystems,however,memoryaddressesareonlystaticrelativetothebaseaddressofthegamebinary,becausethesesystemsenableafeaturecalledaddressspacelayoutrandomization(ASLR)forsupportedbinaries.WhenabinaryiscompiledwithASLRsupport(enabledbydefaultonMSVC++2010andmanyothercompilers),itsbaseaddresscanbedifferenteverytimeitisrun.Conversely,non-ASLRbinarieswillalwayshaveabaseaddressof0x400000.
NOTE
SinceASLRdoesn’tworkonXP,I’llcall0x400000theXP-base.
DisablingASLRtoSimplifyBotDevelopmentTokeepdevelopmentsimple,youcandisableASLRanduseaddresseswith
Tokeepdevelopmentsimple,youcandisableASLRanduseaddresseswiththetransparentXP-base.Todoso,enterasinglecommandintheVisualStudioCommandPrompt:
>editbin/DYNAMICBASE:NO"C:\path\to\game.exe"
Tore-enableit,enter:
>editbin/DYNAMICBASE"C:\path\to\game.exe"
BypassingASLRinProductionDisablingASLRissuitableforbotdevelopment,butitisano-noforproduction;enduserscannotbeexpectedtoturnoffALSR.Instead,youcanwriteafunctiontodynamicallyrebaseaddressesatruntime.IfyouuseaddresseswiththeXP-base,thecodetodoarebasewouldlooklikethis:
DWORDrebase(DWORDaddress,DWORDnewBase){DWORDdiff=address-0x400000;returndiff+newBase;}
Whenyouknowthebaseaddressofthegame(newBase),thisfunctionallowsyoutoessentiallyignoreASLRbyrebasingaddress.
TofindnewBase,however,youneedtousetheGetModuleHandle()function.WhentheparametertoGetModuleHandle()isNULL,italwaysreturnsahandletothemainbinaryinaprocess.Thefunction’sreturnedtypeisHMODULE,butthevalueisactuallyjusttheaddresswherethebinaryismapped.Thisisthebaseaddress,soyoucandirectlycastittoaDWORDtogetnewBase.Sinceyou’relookingforthebaseaddressinanotherprocess,though,youneedawaytoexecutethefunctioninthecontextofthatprocess.
Todothis,callGetModuleHandle()usingtheCreateRemoteThread()APIfunction,whichcanbeusedtospawnthreadsandexecutecodeinaremoteprocess.IthastheprototypeshowninListing6-8.
HANDLECreateRemoteThread(HANDLEProcess,LPSECURITY_ATTRIBUTESThreadAttributes,DWORDStackSize,LPTHREAD_START_ROUTINEStartAddress,LPVOIDParam,
DWORDCreationFlags,LPDWORDThreadId);
Listing6-8:Afunctionthatspawnsathread
ThespawnedthreadwillstartexecutiononStartAddress,treatingitasasingle-parameterfunctionwithParamasinputandsettingthevaluereturnedasthethreadexitcode.Thisisideal,asthethreadcanbestartedwithStartAddresspointingtotheaddressofGetModuleHandle()andParamsettoNULL.YoucanthenusetheAPIfunctionWaitForSingleObject()towaituntilthethreadisdoneexecutingandgetthereturnedbaseaddressusingtheAPIfunctionGetExitCodeThread().
Onceallofthesethingsaretiedtogether,thecodetogetnewBasefromanexternalbotshouldlooklikeListing6-9.
DWORDnewBase;
//gettheaddressofkernel32.dllHMODULEk32=GetModuleHandle("kernel32.dll");
//gettheaddressofGetModuleHandle()LPVOIDfuncAdr=GetProcAddress(k32,"GetModuleHandleA");if(!funcAdr)funcAdr=GetProcAddress(k32,"GetModuleHandleW");
//createthethreadHANDLEthread=CreateRemoteThread(process,NULL,NULL,(LPTHREAD_START_ROUTINE)funcAdr,NULL,NULL,NULL);
//letthethreadfinishWaitForSingleObject(thread,INFINITE);
//gettheexitcodeGetExitCodeThread(thread,&newBase);
//cleanupthethreadhandleCloseHandle(thread);
Listing6-9:FindingthebaseaddressofagamewithAPIfunctions
TheGetModuleHandle()functionispartofkernel32.dll,whichhasthesamebaseaddressineveryprocess,sofirstthiscodegetstheaddressforkernel32.dll.Sincethebaseaddressofkernel32.dllisthesameinevery
process,theaddressofGetModuleHandle()willbethesameinthegameasitisintheexternalbot.Giventhebaseaddressofkernel32.dll,thiscodefindstheaddressofGetModuleHandle()easilywiththeAPIfunctionGetProcAddress().Fromthere,itcallstheCreateRemoteThread()functionfromListing6-8,letsthethreaddoitsjob,andfetchestheexitcodetoobtainnewBase.
ClosingThoughtsNowthatyou’veseenhowtomanipulatememoryfromyourowncode,I’llshowyouhowtoapplytheskillsfromPartsIandIItogames.Theseskillsareparamounttotheconceptsyou’llexploreinthecomingchapters,somakesureyouhaveafirmgrasponwhat’shappening.Ifyou’rehavingtrouble,playwiththeexamplecodeasyoureviewconcepts,asitprovidesasafesandboxfortestingandtweakinghowthemethodsinthisandearlierchaptersbehave.
ThewayListing6-9tricksthegameintoexecutingGetModuleHandle()isaformofcodeinjection.Butthat’sjustaglimpseintowhatinjectioncando.Ifyou’reexcitedtolearnmoreaboutit,diveintoChapter7,whichexploresthistopicindetail.
PART3PROCESSPUPPETEERING
7CODEINJECTION
Imaginebeingabletowalkintoagamecompany’soffice,sitdown,andstartaddingcodetotheirgameclient.Imaginethatyoucandothisforanygameyouwant,wheneveryouwant,andforanyfunctionalityyouwant.Almostanygameryoutalktowillhaveideasonhowtoimproveagame,but,asfarastheyknow,it’sjustapipedream.Butyouknowthatdreamsaremeanttobefulfilled,andnowthatyou’velearnedabitabouthowmemoryworks,you’rereadytostartthrowingtherulesoutthewindow.Usingcodeinjection,youcan,forallintentsandpurposes,becomeaspowerfulasanygame’sdevelopers.
Codeinjectionisameansofforcinganyprocesstoexecuteforeigncodewithinitsownmemoryspaceandexecutioncontext.Itouchedonthistopicpreviouslyin“BypassingASLRinProduction”onpage128,whereIshowedyouhowtoremotelysubvertASLRusingCreateRemoteThread(),butthatexampleonlyscratchedthesurface.Inthefirstpartofthischapter,you’lllearnhowtocreatecodecaves,injectnewthreads,andhijackthreadexecutiontoforcegamestoexecutesmallsnippetsofassemblycode.Inthesecondpart,you’lllearnhowtoinjectforeignbinariesdirectlyintogames,forcingthosegamestoexecuteentireprogramsthatyou’vecreated.
InjectingCodeCaveswithThreadInjection
Thefirststeptoinjectingcodeintoanotherprocessiswritingposition-agnosticassemblycode,knownasshellcode,intheformofabytearray.Youcanwriteshellcodetoremoteprocessestoformcodecaves,whichactastheentrypointforanewthreadthatyouwantagametoexecute.Onceacodecaveiscreated,youcanexecuteitusingeitherthreadinjectionorthreadhijacking.I’llshowyouanexampleofthreadinjectioninthissection,andI’llillustratethreadhijackingin“HijackingaGame’sMainThreadtoExecuteCodeCaves”onpage138.
You’llfindexamplecodeforthischapterinthisbook’sresourcefilesinthedirectoryGameHackingExamples/Chapter7_CodeInjection.Openmain-codeInjection.cpptofollowalongasIexplainhowtobuildasimplifiedversionofthefunctioninjectCodeUsingThreadInjection()fromthatfile.
CreatinganAssemblyCodeCaveIn“BypassingASLRinProduction”onpage128,IusedthreadinjectiontocallthefunctionGetModuleHandle()bywayofCreateRemoteThread()andobtainaprocesshandle.Inthatcase,GetModuleHandle()actedasthecodecave;ithadthepropercodestructuretoactastheentrypointforanewthread.Threadinjectionisn’talwaysthateasy,though.
Forexample,sayyouwantyourexternalbottoremotelycallafunctionwithinagame,andthefunctionhasthisprototype:
DWORD__cdeclsomeFunction(inttimes,constchar*string);
Afewthingsmakeremotelycallingthisfunctiontricky.First,ithastwoparameters,meaningyouneedtocreateacodecavethatwillbothsetupthestackandproperlymakethecall.CreateRemoteThread()allowsyoutopassoneargumenttothecodecave,andyoucanaccessthatargumentrelativetoESP,buttheotheronewouldstillneedtobehardcodedintothecodecave.Hardcodingthefirstargument,times,iseasiest.Additionally,you’dneedtomakesurethatthecaveproperlycleansthestack.
NOTE
RecallthatwhenbypassingASLRinChapter6,IusedCreateRemoteThread()tostartnewthreadsbyexecutinganyarbitrarycodeatagivenaddressandpassingthatcodeasingleparameter.That’swhytheseexamplescanpassone
parameterusingthestack.
Ultimately,thecodecavetoinjectthatcalltosomeFunctionintoarunninggameprocesswouldlooksomethinglikethispseudocode:
PUSHDWORDPTR:[ESP+0x4]//getsecondargfromstackPUSHtimesCALLsomeFunctionADDESP,0x8RETN
Thiscodecaveisalmostperfect,butitcouldbelesscomplex.TheCALLoperationexpectsoneoftwooperands:eitheraregisterwithanabsolutefunctionaddressoranimmediateintegerthatholdsanoffsettoafunction,relativetothereturnaddress.Thismeansyou’dhavetodoabunchofoffsetcalculations,whichcanbetedious.
Tokeepthecavepositionagnostic,modifyittousearegisterinstead,asinListing7-1.
PUSHDWORDPTR:[ESP+0x4]//getsecondargfromstackPUSHtimesMOVEAX,someFunctionCALLEAXADDESP,0x8RETN
Listing7-1:AcodecavetocallsomeFunction
SinceacallerknowsthatafunctionitcallswilloverwriteEAXwithitsreturnvalue,thecallershouldensurethatEAXdoesn’tholdanycriticaldata.Knowingthis,youcanuseEAXtoholdtheabsoluteaddressofsomeFunction.
TranslatingtheAssemblytoShellcodeBecausecodecavesneedtobewrittentoanotherprocess’smemory,theycannotbewrittendirectlyinassembly.Instead,you’llneedtowritethembytebybyte.There’snostandardwaytodeterminewhichbytesrepresentwhichassemblycode,butthereareafewhackyapproaches.MypersonalfavoriteistocompileanemptyC++applicationwiththeassemblycodeinafunctionanduseOllyDbgtoinspectthatfunction.Alternatively,youcouldopenOllyDbgonanyarbitraryprocessandscanthroughthedisassemblyuntilyoufindthebytesforalloftheoperationsyouneed.Thismethodis
untilyoufindthebytesforalloftheoperationsyouneed.Thismethodisactuallyreallygood,asyourcodecavesshouldbewrittenassimplyaspossible,meaningalloftheoperationsshouldbeverycommon.Youcanalsofindchartsofassemblyopcodesonline,butIfindthatthey’reallprettyhardtoread;themethodsIjustdescribedareeasieroverall.
Whenyouknowwhatyourbytesshouldbe,youcanuseC++toeasilygeneratethepropershellcode.Listing7-2showsthefinishedshellcodeskeletonfortheassemblyinListing7-1.
BYTEcodeCave[20]={0xFF,0x74,0x24,0x04,//PUSHDWORDPTR:[ESP+0x4]0x68,0x00,0x00,0x00,0x00,//PUSH00xB8,0x00,0x00,0x00,0x00,//MOVEAX,0x00xFF,0xD0,//CALLEAX0x83,0xC4,0x08,//ADDESP,0x080xC3//RETN};
Listing7-2:Shellcodeskeleton
ThisexamplecreatesaBYTEarraycontainingtheneededbytesofshellcode.Butthetimesargumentneedstobedynamic,andit’simpossibletoknowtheaddressofsomeFunctionatcompiletime,whichiswhythisshellcodeiswrittenasaskeleton.Thetwogroupsoffoursequential0x00bytesareplaceholdersfortimesandtheaddressofsomeFunction,andyoucaninserttherealvaluesintoyourcodecaveatruntimebycallingmemcpy(),asinthesnippetinListing7-3.
memcpy(&codeCave[5],×,4);memcpy(&codeCave[10],&addressOfSomeFunc,4);
Listing7-3:InsertingtimesandthelocationofsomeFunctionintothecodecave
BothtimesandtheaddressofsomeFunctionare4byteseach(recallthattimesisanintandaddressesare32-bitvalues),andtheybelongatcodeCave[5-8]andcodeCave[10-13],respectively.Thetwocallstomemcpy()passthisinformationasparameterstofilltheblanksinthecodeCavearray.
WritingtheCodeCavetoMemoryWiththepropershellcodecreated,youcanplaceitinsidethetargetprocess
usingVirtualAllocEx()andWriteProcessMemory().Listing7-4showsonewaytodothis.
intstringlen=strlen(string)+1;//+1toincludenullterminatorintcavelen=sizeof(codeCave);➊intfulllen=stringlen+cavelen;autoremoteString=//allocatethememorywithEXECUTErights➋VirtualAllocEx(process,0,fulllen,MEM_COMMIT,PAGE_EXECUTE);
autoremoteCave=//keepanoteofwherethecodecavewillgo➌(LPVOID)((DWORD)remoteString+stringlen);
//writethestringfirst➍WriteProcessMemory(process,remoteString,string,stringlen,NULL);
//writethecodecavenext➎WriteProcessMemory(process,remoteCave,codeCave,cavelen,NULL);
Listing7-4:Writingthefinalshellcodetoacodecavememory
First,thiscodedeterminesexactlyhowmanybytesofmemoryitwillneedtowritethestringargumentandthecodecaveintothegame’smemory,anditstoresthatvalueinfulllen➊.Then,itcallstheAPIfunctionVirtualAllocEx()toallocatefulllenbytesinsideofprocesswithPAGE_EXECUTEprotection(youcanalwaysuse0andMEM_COMMIT,respectively,forthesecondandfourthparameters),anditstorestheaddressofthememoryinremoteString➋.ItalsoincrementsremoteStringbystringlenbytesandstorestheresultinremoteCave➌,astheshellcodeshouldbewrittendirectlytothememoryfollowingthestringargument.Finally,itusesWriteProcessMemory()tofilltheallocatedbufferwithstring➍andtheassemblybytes➎storedincodeCave.
Table7-1showshowamemorydumpofthecodecavemightlook,assumingthatitisallocatedat0x030000,someFunctionisat0xDEADBEEF,timesissetto5,andstringispointingtotheinjected!text.
Table7-1:CodeCaveMemoryDump
Address Coderepresentation
Rawdata Datameaning
0x030000 remoteString[0-4]
0x690x6E0x6A0x650x63
injec
4] 0x63
0x030005 remoteString[5-9] 0x740x650x640x0A0x00
ted!\0
0x03000A remoteCave[0-3] 0xFF0x740x240x04 PUSHDWORDPTR[ESP+0x4]
0x03000E remoteCave[4-8] 0x680x050x000x000x00
PUSH0x05
0x030013 remoteCave[9-13] 0xB80xEF0xBE0xAD0xDE
MOVEAX,0xDEADBEEF
0x030018 remoteCave[14-15] 0xFF0xD0 CALLEAX
0x03001A remoteCave[16-18]
0x830xC40x08 ADDESP,0x08
0x03001D remoteCave[19] 0xC3 RETN
TheAddresscolumnshowswhereeachpieceofthecaveislocatedinmemory;theCoderepresentationcolumntellsyouwhichindexesofremoteStringandremoteCavecorrespondtothebytesintheRawdatacolumn;andtheDatameaningcolumnshowswhatthebytesrepresent,inhuman-readableformat.Youcanseetheinjected!stringat0x030000,thevalueoftimesat0x03000E,andtheaddressofsomeFunctionat0x030014.
UsingThreadInjectiontoExecutetheCodeCaveWithacompletecodecavewrittentomemory,theonlythinglefttodoisexecuteit.Inthisexample,youcouldexecutethecaveusingthefollowingcode:
HANDLEthread=CreateRemoteThread(process,NULL,NULL,(LPTHREAD_START_ROUTINE)remoteCave,remoteString,NULL,NULL);
WaitForSingleObject(thread,INFINITE);CloseHandle(thread);VirtualFreeEx(process,remoteString,fulllen,MEM_RELEASE)
ThecallstoCreateRemoteThread(),WaitForSingleObject(),andCloseHandle()worktoinjectandexecutethecodecave,andVirtalFreeEx()
coversthebot’stracksbyfreeingthememoryallocatedincodelikeListing7-4.Inthesimplestform,that’sallthereistoexecutingacodecaveinjectedintoagame.Inpractice,youshouldalsocheckreturnvaluesaftercallingVirtualAllocEx(),WriteProcessMemory(),andCreateRemoteThread()tomakesurethateverythingwassuccessful.
Forinstance,ifVirtualAllocEx()returns0x00000000,itmeansthatthememoryallocationfailed.Ifyoudon’thandlethefailure,WriteProcessMemory()willalsofailandCreateRemoteThread()willbeginexecutingwithanentrypointof0x00000000,ultimatelycrashingthegame.ThesameistrueforthereturnvaluesofWriteProcessMemory()andCreateRemoteThread().Typically,thesefunctionswillonlyfailwhentheprocesshandleisopenedwithouttherequiredaccessflags.
HijackingaGame’sMainThreadtoExecuteCodeCavesInsomecases,injectedcodecavesneedtobeinsyncwiththemainthreadofthegameprocess.Solvingthisproblemcanbeverytrickybecauseitmeansthatyoumustcontroltheexistingthreadsinanexternalprocess.
Youcouldsimplysuspendthemainthreaduntilthecodecavefinishesexecuting,whichmightwork,butthatwouldproveveryslow.Theoverheadrequiredtowaitforacodecaveandthenresumeathreadisprettyheavy.Afasteralternativeistoforcethethreadtoexecutethecodeforyou,aprocesscalledthreadhijacking.
NOTE
Openthemain-codeInjection.cppfileinthisbook’ssourcecodefilestofollowalongwithbuildingthisthread-hijackingexample,whichisasimplifiedversionofinjectCodeUsingThreadHijacking().
BuildingtheAssemblyCodeCaveAswiththreadinjection,thefirststeptothreadhijackingisknowingwhatyouwanttohappeninyourcodecave.Thistime,however,youdon’tknow
whatthethreadwillbeexecutingwhenyouhijackit,soyou’llneedtomakesuretosavethethread’sstatewhenthecodecavestartsandrestorethestatewhenyou’redonehijackingit.Thismeansyourshellcodeneedstobewrappedinsomeassembly,asinListing7-5.
PUSHAD//pushgeneralregisterstothestackPUSHFD//pushEFLAGStothestack
//shellcodeshouldbehere
POPFD//popEFLAGSfromthestackPOPAD//popgeneralregisterstothestack
//resumethethreadwithoutusingregistershere
Listing7-5:Aframeworkforthethread-hijackingcodecave
IfyouweretocallthesamesomeFunctionthatyoudidwiththreadinjection,youcoulduseshellcodesimilartothatinListing7-2.Theonlydifferenceisthatyoucouldn’tpassthesecondparametertoyourbotusingthestackbecauseyouwouldn’tbeusingCreateRemoteThread().Butthat’snoproblem;youcouldjustpushitthesamewayyou’dpushthefirstparameter.ThepartofthecodecavethatexecutesthefunctionyouwanttocallwouldneedtolooklikeListing7-6.
PUSHstringPUSHtimesMOVEAX,someFunctionCALLEAXADDESP,0x8
Listing7-6:AssemblyskeletonforcallingsomeFunction
Allthat’schangedherefromListing7-1isthatthisexamplepushesstringexplicitlyandthere’snoRETN.Youdon’tcallRETNinthiscasebecauseyouwantthegamethreadtogobacktowhateveritwasdoingbeforeyouhijackedit.
Toresumetheexecutionofthethreadnormally,thecaveneedstojumpbacktothethread’soriginalEIPwithoutusingregisters.Fortunately,youcanusetheGetThreadContext()functiontofetchEIP,fillingtheshellcodeskeletoninC++.Thenyoucanpushittothestackinsideyourcodecaveanddoareturn.Listing7-7showshowyourcodecavewouldneedtoend.
PUSHoriginalEIP
PUSHoriginalEIPRETN
Listing7-7:JumpingtoEIPindirectly
Areturnjumpstothevalueonthetopofthestack,sodoingthisimmediatelyafterpushingEIPwilldothetrick.Youshouldusethismethodinsteadofajump,becausejumpsrequireoffsetcalculationandmaketheshellcodeabitmorecomplextogenerate.IfyoutieListings7-5through7-7together,youcomeupwiththefollowingcodecave:
//savestatePUSHAD//pushgeneralregisterstothestackPUSHFD//pushEFLAGStothestack
//doworkwithshellcodePUSHstringPUSHtimesMOVEAX,someFunctionCALLEAXADDESP,0x8
//restorestatePOPFD//popEFLAGSfromthestackPOPAD//popgeneralregisterstothestack
//un-hijack:resumethethreadwithoutusingregistersPUSHoriginalEIPRETN
Next,followtheinstructionsin“TranslatingtheAssemblytoShellcode”onpage135andplugthosebytesintoanarrayrepresentingyourcodecave.
GeneratingSkeletonShellcodeandAllocatingMemoryUsingthesamemethodshowninListing7-2,youcouldgeneratetheshellcodeforthiscave,asshowninListing7-8.
BYTEcodeCave[31]={0x60,//PUSHAD0x9C,//PUSHFD0x68,0x00,0x00,0x00,0x00,//PUSH00x68,0x00,0x00,0x00,0x00,//PUSH00xB8,0x00,0x00,0x00,0x00,//MOVEAX,0x00xFF,0xD0,//CALLEAX0x83,0xC4,0x08,//ADDESP,0x080x9D,//POPFD
0x61,//POPAD0x68,0x00,0x00,0x00,0x00,//PUSH00xC3//RETN};
//we'llneedtoaddsomecodeheretoplace//thethread'sEIPintothreadContext.Eip
memcpy(&codeCave[3],&remoteString,4);memcpy(&codeCave[8],×,4);memcpy(&codeCave[13],&func,4);memcpy(&codeCave[25],&threadContext.Eip,4);
Listing7-8:Creatingthethread-hijackingshellcodearray
AsinListing7-3,memcpy()isusedtoputthevariablesintotheskeleton.Unlikeinthatlisting,though,therearetwovariablesthatcannotbecopiedrightaway;timesandfuncareknownimmediately,butremoteStringisaresultofallocationandthreadContext.Eipwillbeknownonlyoncethethreadisfrozen.Italsomakessensetoallocatememorybeforefreezingthethread,becauseyoudon’twantthethreadtobefrozenanylongerthanithastobe.Here’showthismightlook:
intstringlen=strlen(string)+1;intcavelen=sizeof(codeCave);intfulllen=stringlen+cavelen;
autoremoteString=VirtualAllocEx(process,0,fulllen,MEM_COMMIT,PAGE_EXECUTE);autoremoteCave=(LPVOID)((DWORD)remoteString+stringlen);
Theallocationcodeisthesameasitwasforthreadinjection,soyoucanreusethesamesnippet.
FindingandFreezingtheMainThreadThecodetofreezethemainthreadisabittrickier.First,yougetthethread’suniqueidentifier.ThisworksmuchlikegettingaPID,andyoucandoitusingCreateToolhelp32Snapshot(),Thread32First(),andThread32Next()fromTlHelp32.h.Asdiscussedin“ObtainingtheGame’sProcessIdentifier”onpage120,thesefunctionsareusedtoessentiallyiterateoveralist.Aprocesscanhavemanythreads,butthefollowingexampleassumesthatthefirstthreadthegameprocesscreatedistheonethatneedstobehijacked:
DWORDGetProcessThreadID(HANDLEProcess){THREADENTRY32entry;entry.dwSize=sizeof(THREADENTRY32);HANDLEsnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,0);
if(Thread32First(snapshot,&entry)==TRUE){DWORDPID=GetProcessId(Process);while(Thread32Next(snapshot,&entry)==TRUE){if(entry.th32OwnerProcessID==PID){CloseHandle(snapshot);returnentry.th32ThreadID;}}}CloseHandle(snapshot);returnNULL;}
Thiscodesimplyiteratesoverthelistofallthreadsonthesystemandfindsthefirstonethatmatchesthegame’sPID.Thenitgetsthethreadidentifierfromthesnapshotentry.Onceyouknowthethreadidentifier,fetchthethread’scurrentregisterstatelikethis:
HANDLEthread=OpenThread((THREAD_GET_CONTEXT|THREAD_SUSPEND_RESUME|THREAD_SET_CONTEXT),false,threadID);SuspendThread(thread);
CONTEXTthreadContext;threadContext.ContextFlags=CONTEXT_CONTROL;GetThreadContext(thread,&threadContext);
ThiscodeusesOpenThread()togetathreadhandle.ItthensuspendsthethreadusingSuspendThread()andobtainsthevaluesofitsregistersusingGetThreadContext().Afterthis,thememcpy()codeinListing7-8shouldhaveallofthevariablesitneedstofinishgeneratingtheshellcode.
Withtheshellcodegenerated,thecodecavecanbewrittentotheallocatedmemoryinthesamefashionasinListing7-4:
WriteProcessMemory(process,remoteString,string,stringlen,NULL);WriteProcessMemory(process,remoteCave,codeCave,cavelen,NULL);
Oncethecaveisreadyandwaitinginmemory,allyouneedtodoissetthethread’sEIPtotheaddressofthecodecaveandletthethreadresumeexecution,asfollows:
threadContext.Eip=(DWORD)remoteCave;threadContext.ContextFlags=CONTEXT_CONTROL;SetThreadContext(thread,&threadContext);ResumeThread(thread);
Thiscodecausesthethreadtoresumeexecutionattheaddressofthecodecave.Becauseofthewaythecodecaveiswritten,thethreadhasnocluethatanythinghaschanged.Thecavestoresthethread’soriginalstate,executesthepayload,restoresthethread’soriginalstate,andthenreturnstotheoriginalcodewitheverythingintact.
Whenyou’reusinganyformofcodeinjection,itisalsoimportanttounderstandwhatdatayourcodecavestouch.Forexample,ifyouweretocreateacodecavethatcallsagame’sinternalfunctionstocreateandsendanetworkpacket,you’dneedtomakesurethatanyglobalvariablesthatthefunctionstouch(likeapacketbuffer,packetpositionmarker,andsoon)aresafelyrestoredonceyou’redone.Youneverknowwhatthegameisdoingwhenyourcodecaveisexecuted—itcouldbecallingthesamefunctionasyou!
InjectingDLLsforFullControlCodecavesareverypowerful(youcanmakeagamedoanythingusingassemblyshellcode),buthandcraftingshellcodeisn’tpractical.ItwouldbemuchmoreconvenienttoinjectC++code,wouldn’tit?That’spossible,buttheprocessisfarmorecomplex:thecodemustbecompiledtoassembly,packagedinaposition-agnosticformat,madeawareofanyexternaldependencies,entirelymappedintomemory,andthenexecutedonsomeentrypoint.
Luckily,allofthesethingsarealreadytakencareofinWindows.BychangingaC++projecttocompileasadynamiclibrary,youcancreateaself-contained,position-agnosticbinarycalledadynamiclinklibrary(DLL).ThenyoucanuseamixofthreadinjectionorhijackingandtheLoadLibrary()APIfunctiontomapyourDLLfileintoagame’smemory.
Openmain-codeInjection.cppintheGameHackingExamples/Chapter7_CodeInjectiondirectoryanddllmain.cppunderGameHackingExamples/Chapter7_CodeInjection_DLLtofollowalongwithsomeexamplecodeasyoureadthissection.Inmain-codeInjection.cpp,lookat
theLoadDLL()functionspecifically.
TrickingaProcessintoLoadingYourDLLUsingacodecave,youcantrickaremoteprocessintoinvokingLoadLibrary()onaDLL,effectivelyloadingforeigncodeintoitsmemoryspace.BecauseLoadLibrary()takesonlyasingleparameter,youcouldcreateacodecavetocallitasfollows:
//writethedllnametomemorywchar_t*dllName="c:\\something.dll";intnamelen=wcslen(dllName)+1;LPVOIDremoteString=VirtualAllocEx(process,NULL,namelen*2,MEM_COMMIT,PAGE_EXECUTE);WriteProcessMemory(process,remoteString,dllName,namelen*2,NULL);
//gettheaddressofLoadLibraryW()HMODULEk32=GetModuleHandleA("kernel32.dll");LPVOIDfuncAdr=GetProcAddress(k32,"LoadLibraryW");
//createathreadtocallLoadLibraryW(dllName)HANDLEthread=CreateRemoteThread(process,NULL,NULL,(LPTHREAD_START_ROUTINE)funcAdr,remoteString,NULL,NULL);
//letthethreadfinishandcleanupWaitForSingleObject(thread,INFINITE);CloseHandle(thread);
Thiscodeissomewhatamixofthethreadinjectioncodefrom“BypassingASLRinProduction”onpage128andthecodecavecreatedtocallsomeFunctioninListings7-2and7-3.Liketheformer,thisexampleusesthebodyofasingle-parameterAPIfunction,namelyLoadLibrary,asthebodyofthecodecave.Likethelatter,though,ithastoinjectastringintomemory,sinceLoadLibraryexpectsastringpointerasitsfirstargument.Oncethethreadisinjected,itforcesLoadLibrarytoloadtheDLLwhosenamewasinjectedintomemory,effectivelyputtingforeigncodeintoagame.
NOTE
GiveanyDLLyouplantoinjectauniquename,like
MySuperBotV2Hook.dll.Simplernames,suchasHook.dllorInjected.dll,aredangerouslygeneric.IfthenameconflictswithaDLLthatisalreadyloaded,LoadLibrary()willassumethatitisthesameDLLandnotloadit!
OncetheLoadLibrary()codecaveloadsyourDLLintoagame,theDLL’sentrypoint—knownasDllMain()—willbeexecutedwithDLL_PROCESS_ATTACHasthereason.WhentheprocessiskilledorFreeLibrary()iscalledontheDLL,itsentrypointwillbecalledwiththeDLL_PROCESS_DETACHreason.Handlingtheseeventsfromtheentrypointmightlooklikethis:
BOOLAPIENTRYDllMain(HMODULEhModule,DWORDul_reason_for_call,LPVOIDlpReserved){switch(ul_reason_for_call){caseDLL_PROCESS_ATTACH:printf("DLLattached!\n");break;caseDLL_PROCESS_DETACH:printf("DLLdetached!\n");break;}returnTRUE;}
ThisexamplefunctionstartsbycheckingwhyDllMain()wascalled.ItthenoutputstextindicatingwhetheritwascalledbecausetheDLLwasattachedordetached,returningTRUEeitherway.
KeepinmindthattheentrypointofaDLLisexecutedinsidealoaderlock,whichisaglobalsynchronizationlockusedbyallfunctionsthatreadormodifythelistofmodulesloadedinaprocess.ThisloaderlockgetsusedbyfunctionslikeGetModuleHandle(),GetModuleFileName(),Module32First(),andModule32Next(),whichmeansthatrunningnontrivialcodefromaDLLentrypointcanleadtodeadlocksandshouldbeavoided.
IfyouneedtoruncodefromaDLLentrypoint,dosofromanewthread,asfollows:
DWORDWINAPIrunBot(LPVOIDlpParam){//runyourbotreturn1;}
//dothisfromDllMain()forcaseDLL_PROCESS_ATTACHautothread=CreateThread(NULL,0,&runBot,NULL,0,NULL);CloseHandle(thread);
FromDllMain(),thiscodecreatesanewthreadstartingonthefunctionrunBot().Itthenimmediatelyclosesitshandletothethread,asdoinganyfurtheroperationsfromDllMain()canleadtoseriousproblems.FrominsidethisrunBot(),youcanbeginexecutingyourbot’scode.Thecoderunsinsidethegame,meaningyoucandirectlymanipulatememoryusingthetype-castingmethods.Youcanalsodoalotmore,asyou’llseeinChapter8.
WheninjectingDLLs,makesureyouhavenodependencyissues.IfyourDLLreliesonsomenonstandardDLLs,forexample,youhavetoeitherinjectthoseDLLsintothegamefirstorputtheminafolderthatLoadLibrary()willsearch,suchasanyfolderinthePATHenvironmentvariable.TheformerwillworkonlyiftheDLLshavenodependenciesoftheirown,whereasthelatterisabittrickytoimplementandsubjecttonamecollisions.ThebestoptionistolinkallexternallibrariesstaticallysothattheyarecompileddirectlyintoyourDLL.
AccessingMemoryinanInjectedDLLWhenyou’retryingtoaccessagame’smemoryfromaninjectedDLL,processhandlesandAPIfunctionsareahindrance.Becauseagamesharesthesamememoryspaceasallcodeinjectedintoit,youcanaccessagame’smemorydirectlyfrominjectedcode.Forexample,toaccessaDWORDvaluefrominjectedcode,youcouldwritethefollowing:
DWORDvalue=*((DWORD*)adr);//readaDWORDfromadr*((DWORD*)adr)=1234;//write1234toDWORDadr
ThissimplytypecaststhememoryaddressadrtoaDWORD*anddereferencesthatpointertoaDWORD.Doingtypecastsinplacelikethatisfine,butyourmemoryaccesscodewilllookcleanerifthefunctionsareabstractedandmadegeneric,justliketheWindowsAPIwrappers.
Thegenericfunctionsforaccessingmemoryfrominsideinjectedcodelooksomethinglikethis:
template<typenameT>TreadMemory(LPVOIDadr){return*((T*)adr);
}
template<typenameT>voidwriteMemory(LPVOIDadr,Tval){*((T*)adr)=val;}
Usingthesetemplatesisjustlikeusingthefunctionsunder“WritingTemplatedMemoryAccessFunctions”onpage123.Here’sanexample:
DWORDvalue=readMemory<DWORD>(adr);//readwriteMemory<DWORD>(adr,value++);//incrementandwrite
ThesecallsarenearlyidenticaltothecallsinListing6-6onpage124;theyjustdon’tneedtotaketheprocesshandleasanargumentbecausethey’rebeingcalledfrominsidetheprocessitself.YoucanmakethismethodevenmoreflexiblebycreatingathirdtemplatedfunctioncalledpointMemory(),asfollows:
template<typenameT>T*pointMemory(LPVOIDadr){return((T*)adr);}
Thisfunctionskipsthedereferencingstepofamemoryreadandsimplygivesyouthepointertothedata.Fromthere,you’refreetobothreadfromandwritetothememorybydereferencingthatpointeryourself,likethis:
DWORD*pValue=pointMemory<DWORD>(adr);//pointDWORDvalue=*pValue;//'read'(*pValue)++;//incrementand'write'
WithafunctionlikepointMemory()inplace,youcouldeliminatethecallstoreadMemory()andwriteMemory().You’dstillneedtofindadraheadoftime,butfromthere,thecodetoreadavalue,changeit,andwriteitbackwouldbemuchsimplertofollow.
BypassingASLRinanInjectedDLLSimilarly,sincethecodeisinjected,there’snoneedtoinjectathreadintothegametogetthebaseaddress.Instead,youcanjustcallGetModuleHandle()directly,likeso:
DWORDnewBase=(DWORD)GetModuleHandle(NULL);
DWORDnewBase=(DWORD)GetModuleHandle(NULL);
Afasterwaytogetthebaseaddressistoutilizethegame’sFSmemorysegment,whichisanothersuperpoweryougetfrominjectedcode.Thismemorysegmentpointstoastructurecalledthethreadenvironmentblock(TEB),and0x30bytesintotheTEBisapointertotheprocessenvironmentblock(PEB)structure.Thesestructuresareusedbytheoperatingsystemandcontainatonofdataaboutthecurrentthreadandthecurrentprocess,butwe’reinterestedonlyinthebaseaddressofthemainmodule,whichisstored0x8bytesintothePEB.Usinginlineassembly,youcantraversethesestructurestogetnewBase,likethis:
DWORDnewBase;__asm{MOVEAX,DWORDPTRFS:[0x30]MOVEAX,DWORDPTRDS:[EAX+0x8]MOVnewBase,EAX}
ThefirstcommandstoresthePEBaddressinEAX,andthesecondcommandreadsthemainmodule’sbaseaddressandstoresitinEAX.ThefinalcommandthencopiesEAXtonewBase.
ClosingThoughtsInChapter6,IshowedyouhowtoreadfrommemoryremotelyandhowaninjectedDLLcandirectlyaccessagame’smemoryusingpointers.Thischapterdemonstratedhowtoinjectalltypesofcode,frompureassemblybytecodetoentireC++binaries.Inthenextchapter,you’lllearnjusthowmuchpowerbeinginagame’smemoryspaceactuallygivesyou.Ifyouthoughtassemblycodeinjectionwascool,you’lllovewhatyoucandowhenyoumixinjectedC++withcontrolflowmanipulation.
Theexamplecodeforthischaptercontainsproofsofconceptforeverythingwe’vediscussed.Ifyou’restillunclearaboutanyofthetopics,youcanpokeatthecodetolearnexactlywhat’sgoingonandseeallofthetricksinaction.
8MANIPULATINGCONTROLFLOWINA
GAME
Forcingagametoexecuteforeigncodeisdefinitelypowerful,butwhatifyoucouldalterthewayagameexecutesitsowncode?Whatifyoucouldforcethegametobypassthecodethatdrawsthefogofwar,trickitintomakingenemiesvisiblethroughwalls,ormanipulatetheargumentsitpassestofunctions?Controlflowmanipulationletsyoudoexactlythat,allowingyoutochangewhataprocessdoesbyinterceptingcodeexecutionandmonitoring,modifying,orpreventingit.
Therearemanywaystomanipulatethecontrolflowofaprocess,butalmostallrequireyoutomodifytheprocess’sassemblycode.Dependingonyourgoals,you’llneedtoeithercompletelyremovecodefromtheprocess(calledNOPing)orforcetheprocesstoredirectexecutiontoinjectedfunctions(calledhooking).Inthebeginningofthischapter,you’lllearnaboutNOPing,severaltypesofhooking,andothercontrolflowmanipulationtechniques.OnceI’veexplainedthebasics,I’llshowyouhowI’veappliedtheseprinciplestocommongamelibrarieslikeAdobeAIRandDirect3D.
OpenthedirectoryGameHackingExamples/Chapter8_ControlFlowinthisbook’sresourcefilestoseethecompletesamplecodeforthenextsectionand“HookingtoRedirectGameExecution”onpage153.
NOPingtoRemoveUnwantedCodeChapter7describedhowtoinjectnewcodeintoagame,buttheopposite—removingcodefromagame—canalsobeuseful.Somehacksrequireyoutostopsomeofagame’soriginalcodefrombeingexecuted,andtodothat,you’llhavetogetridofit.OnewaytoeliminatecodefromagameprocessisNOPing,whichinvolvesoverwritingtheoriginalx86assemblycodewithNOPinstructions.
WhentoNOPConsideragamethatwon’tshowthehealthbarsofcloakedenemies.It’sprettyhardtoseecloakedenemiescoming,andyou’dhaveahugeadvantageincombatifyoucouldatleastseetheirhealthbars.ThecodetodrawhealthbarsoftenlookslikeListing8-1.
for(inti=0;i<creatures.size();i++){autoc=creatures[i];if(c.isEnemy&&c.isCloaked)continue;drawHealthBar(c.healthBar);}
Listing8-1:TheloopfromthedrawCreatureHealthBarExample()function
Whendrawinghealthbars,agamewithcloakedcreaturesmightuseaforlooptocheckwhetherthecreatureswithinthescreen’sboundsarecloaked.Ifanenemyisn’tcloaked,theloopcallssomefunction(drawHealthBar()inthisexample)todisplaytheenemy’shealthbar.
Giventhesourcecode,youcouldforcethegametodrawevencloakedenemies’healthbarsbysimplyremovingif(c.isEnemy&&c.isCloaked)continue;fromthecode.Butasagamehacker,youhaveonlytheassemblycode,notthesourcecode.Whensimplified,theassemblythatListing8-1translatesintolookssomethinglikethispseudocode:
startOfLoop:;forMOVi,0;inti=0JMPcondition;firstloop,skipincrementincrement:ADDi,1;i++condition:CMPi,creatures.Size();i<creatures.size()JNBendOfLoop;exitloopifi>=
creatures.size()body:MOVc,creatures[i];autoc=creatures[i]TESTc.isEnemy,c.isEnemy;ifc.isEnemyJZdrawHealthBar;drawbarifc.isEnemy==falseTESTc.isCloaked,c.isCloaked;&&c.isCloakedJZdrawHealthBar;drawbarifc.isCloaked==false➊JMPincrement;continuedrawHealthBar:CALLdrawHealthBar(c.healthBar);drawHealthBar(c.healthBar)JMPincrement;continueendOfLoop:
Totrickthegameintodrawingallenemyhealthbars,regardlessofcloaking,you’dneedtoremovetheJMPincrementcommand➊thatexecuteswhenc.isEnemy&&c.isCloakedevaluatestotrue.Inassembly,though,replacingunwantedcodewithinstructionsthatdonothingiseasierthandeletingcode.That’swheretheNOPcommandcomesin.SinceNOPisasinglebyte(0x90),youcanoverwritethe2-byteJMPincrementcommandwithtwoNOPcommands.WhentheprocessorreachesthoseNOPcommands,itrollsoverthemandfallsintodrawHealthBar()evenwhenc.isEnemy&&c.isCloakedevaluatestotrue.
HowtoNOPThefirststeptoNOPingachunkofassemblycodeismakingthememorychunkwherethecodeliveswritable.It’spossibleforthecodeonthesamememorypagetobeexecutedwhileyou’rewritingtheNOPcommands,though,soyoualsowanttomakesurethememoryisstillexecutable.Youcanaccomplishbothofthesetasksbysettingthememory’sprotectiontoPAGE_EXECUTE_READWRITE.Oncethememoryisproperlyprotected,youcanwritetheNOPcommandsandbedone.Ittechnicallydoesn’thurttoleavethememorywritable,butit’sgoodpracticetoalsorestoretheoriginalprotectionwhenyou’refinished.
Providedyouhavefacilitiesinplaceforwritingandprotectingmemory(asdescribedinChapter6),youcanwriteafunctionliketheoneshowninListing8-2towriteNOPcommandstogamememory.(Followalongbyopeningtheproject’sNOPExample.cppfile.)
template<intSIZE>
voidwriteNop(DWORDaddress){autooldProtection=protectMemory<BYTE[SIZE]>(address,PAGE_EXECUTE_READWRITE);
for(inti=0;i<SIZE;i++)writeMemory<BYTE>(address+i,0x90);
protectMemory<BYTE[SIZE]>(address,oldProtection);}
Listing8-2:ProperNOPing,completewithmemoryprotection
Inthisexample,thewriteNop()functionsetstheappropriatememoryprotection,writesanumberofNOPcommandsequaltoSIZE,andreappliestheoriginalmemoryprotectionlevel.
ThewriteNop()functiontakesthenumberofNOPinstructionstoplaceasatemplateparameter,sincethememoryfunctionsrequireacorrectlysizedtypeatcompiletime.PassinganintegerSIZEtellsthememoryfunctionstooperateonatypeofBYTE[SIZE]atcompiletime.Tospecifyadynamicsizeatruntime,simplydroptheloopandinsteadcallprotectMemory<BYTE>andpassaddressandaddress+SIZEasarguments.Aslongasthesizeisn’tlargerthanapage(andreally,youshouldn’tbeNOPingafullpage),thiswillensurethatthememorygetsproperlyprotectedevenifit’sonapageboundary.
CallthisfunctionwiththeaddresswhereyouwanttoplaceyourNOPsandthenumberofNOPcommandstoplace:
writeNop<2>(0xDEADBEEF);
KeepinmindthatthenumberofNOPcommandsshouldmatchthesizeinbytesofthecommandbeingremoved.ThiscalltowriteNop()writestwoNOPcommandstotheaddress0xDEADBEEF.
PRACTICENOPINGIfyouhaven’talready,openNOPExample.cppinthischapter’sexamplecodenowandplayaroundwithitforabit.You’llfindaworkingimplementationofthewriteNop()functionandaninterestingfunctioncalledgetAddressforNOP()thatscanstheexampleprogram’s
memorytofindwheretheNOPcommandshouldbeplaced.ToseetheNOPcommandinaction,runthecompiled
NOPapplicationinVisualStudio’sdebuggerwithbreakpointsatthestartandendofthewriteNop()function.Whenthefirstbreakpointishit,pressALT-8toopenthedisassemblywindow,enteraddressintheinputbox,andpressENTER.ThisbringsyoutotheNOP’stargetaddress,whereyou’llseetheassemblycodefullyintact.PressF5tocontinueexecution,whichtriggersthesecondbreakpointafterallowingtheapplicationtoplacetheNOPs.Finally,jumpbacktoaddressinthedisassemblytabtoseethatthecodewasreplacedbyNOPs.
Youcanreworkthiscodetodoothercoolstuff.Forexample,youmighttryplacingNOPsonthecomparisonsinsteadofthejumporevenmodifyingthejump’stypeordestination.
Theseandotheralternativeapproachesmaywork,butnotethattheyintroducemoreroomforerrorthanoverwritingthesingleJMPwithNOPcommands.Whenmodifyingforeigncode,makeasfewchangesaspossibletominimizethepotentialforerrors.
HookingtoRedirectGameExecutionSofar,I’veshownyouhowtomanipulategamesbyaddingcodetothem,hijackingtheirthreads,creatingnewthreads,andevenremovingexistingcodefromtheirexecutionflow.Thesemethodsareverypowerfulontheirown,butwhencombined,theyformanevenmorepotentmethodofmanipulationcalledhooking.Hookingallowsyoutointerceptprecisebranchesofexecutionandredirectthemtoinjectedcodethatyou’vewrittentodictatewhatthegameshoulddonext,anditcomesinavarietyofflavors.Inthissection,I’llteachyouaboutfourofthemostpowerfulhookingmethodsforgamehacking:callhooking,virtualfunctiontablehooking,importaddresstablehooking,andjumphooking.
CallHookingAcallhookdirectlymodifiesthetargetofaCALLoperationtopointtoanew
pieceofcode.ThereareafewvariationsoftheCALLoperationinx86assembly,butcallhooksaregenerallyusedononlyone:thenearcall,whichtakesanimmediateaddressasanoperand.
WorkingwithNearCallsinMemoryInanassemblyprogram,anearcalllookslikethis:
CALL0x0BADF00D
Thisnearcallisrepresentedbythebyte0xE8,soyoumightassumeitisstoredinmemorylikethis:
0xE80x0BADF00D
Or,whensplitintosinglebytesandswappedforendianness,likethis:
0xE80x0D0xF00xAD0x0B
Buttheanatomyofanearcallinmemoryisnotthatsimple.Insteadofstoringthecallee’sabsoluteaddress,anearcallstoresanoffsettothecalleerelativetotheaddressimmediatelyafterthecall.Sinceanearcallis5bytes,theaddressimmediatelyafterthecallis5byteslaterinmemory.Giventhat,theaddressstoredcanbecomputedasfollows:
calleeAddress–(callAddress+5)
IfCALL0x0BADF00Dlivesat0xDEADBEEFinmemory,thenthevalueafter0xE8isthis:
0x0BADF00D–(0xDEADBEEF+5)=0x2D003119
Inmemory,then,thatCALLinstructionlookslikethis:
0xE80x190x310x000x2D
Tohookanearcall,youfirstneedtochangetheoffsetfollowing0xE8(thatis,thelittle-endian0x190x310x000x2D)topointtoyournewcallee.
HookingaNearCall
FollowingthesamememoryprotectionrulesshowninListing8-2,youhookanearcalllikeso(followalongbyopeningCallHookExample.cpp):
DWORDcallHook(DWORDhookAt,DWORDnewFunc){DWORDnewOffset=newFunc-hookAt-5;
autooldProtection=protectMemory<DWORD>(hookAt+1,PAGE_EXECUTE_READWRITE);
DWORDoriginalOffset=readMemory<DWORD>(➊hookAt+1);writeMemory<DWORD>(hookAt+1,newOffset);protectMemory<DWORD>(hookAt+1,oldProtection);
➋returnoriginalOffset+hookAt+5;}
ThisfunctiontakesasargumentstheaddressoftheCALLtohook(hookAt)andtheaddresstoredirectexecutionto(newFunc),anditusesthemtocalculatetheoffsetrequiredtocallthecodeattheaddressnewFunccontains.Afteryouapplythecorrectmemoryprotections,thecallHook()functionwritesthenewoffsettothememoryathookAt+1➊,appliestheoldmemoryprotections,calculatestheaddressoftheoriginalcall➋,andreturnsthatvaluetothecaller.
Here’showyoumightactuallyuseafunctionlikethisinagamehack:
DWORDorigFunc=callHook(0xDEADBEEF,(DWORD)&someNewFunction);
Thishooksthenearcallto0x0BADF00Dat0xDEADBEEFandredirectsittotheaddressofsomeNewFunction,whichisthecodeyourhackwillexecute.Afterthisiscalled,theorigFuncvaluewillhold0x0BADF00D.
CleaningUptheStackThenewcalleemustalsoproperlyhandlethestack,preserveregisters,andpassproperreturnvalues.Attheleast,thismeansyourreplacementfunctionmustmatchthegame’soriginalfunctioninbothcallingconventionandargumentcount.
Let’ssaythisistheoriginalfullfunctioncall,inassembly:
PUSH1PUSH456PUSH321
PUSH321CALL0x0BADF00DADDESP,0x0C
YoucantellthefunctionhastheC++__cdeclconventionbecausethestackisbeingresetbythecaller.Additionally,the0x0Cbytesbeingcleanedfromthestackshowthattherearethreearguments,whichyoucancalculateasfollows:
Ofcourse,youcanalsoobtainthenumberofargumentsbycheckinghowmanythingsarepushedtothestack:therearethreePUSHcommands,oneforeachargument.
WritingaCallHookInanycase,thenewcallee,someNewFunction,mustfollowthe__cdeclconventionandhavethreearguments.Here’sanexampleskeletonforthenewcallee:
DWORD__cdeclsomeNewFunction(DWORDarg1,DWORDarg2,DWORDarg3){
}
InVisualStudio,C++programsusethe__cdeclconventionbydefault,sotechnicallyyoucouldomititfromyourfunctiondefinition;however,I’vefoundit’sbettertobeverbosesoyougetintothehabitofbeingspecific.Alsokeepinmindthatifthecallerexpectsavaluetobereturned,thereturntypeofyourfunctionshouldmatchaswell.ThisexampleassumesthereturntypeisalwaysaDWORDorsmaller.SincereturntypesinthissizerangewillallbepassedbackonEAX,furtherexampleswillalsouseareturntypeofDWORD.
Inmostcases,ahookfinishesbycallingtheoriginalfunctionandpassingitsreturnvaluebacktothecaller.Here’showallofthatmightfittogether:
typedefDWORD(__cdecl_origFunc)(DWORDarg1,DWORDarg2,DWORDarg3);
_origFunc*originalFunction=(_origFunc*)hookCall(0xDEADBEEF,(DWORD)&someNewFunction);
DWORD__cdeclsomeNewFunction(DWORDarg1,DWORDarg2,DWORDarg3){
returnoriginalFunction(arg1,arg2,arg3);}
Thisexampleusestypedeftodeclareatyperepresentingtheoriginalfunction’sprototypeandcreatesapointerwiththistypetotheoriginalfunction.ThensomeNewFunction()usesthispointertocalltheoriginalfunctionwiththeoriginalargumentsandpassthereturnedvaluebacktothecaller.
Rightnow,allsomeNewFunction()doesisreturntotheoriginalfunction.ButyoucandowhateveryouwantfrominsidethesomeNewFunction()callfromhere.Youcanmodifytheparametersbeingpassedtotheoriginalfunctionorinterceptandstoreinterestingparametersforlateruse.Ifyouknowthecallerisn’texpectingareturnvalue(orifyouknowhowtospoofthereturnvalue),youcanevenforgetabouttheoriginalfunctionandcompletelyreplace,replicate,orimproveitsfunctionalityinsidethenewcallee.Onceyou’veperfectedthisskill,youcanaddyourownnativeCorC++codetoanypartofagamethatyouwish.
VFTableHookingUnlikecallhooks,virtualfunction(VF)tablehooksdon’tmodifyassemblycode.Instead,theymodifythefunctionaddressesstoredintheVFtablesofclasses.(IfyouneedarefresheronVFtables,see“AClasswithVirtualFunctions”onpage75.)AllinstancesofthesameclasstypeshareastaticVFtable,soVFtablehookswillinterceptallcallsmadetoamemberfunction,regardlessofwhichclassinstancethegameiscallingthefunctionfrom.Thiscanbebothpowerfulandtricky.
THETRUTHABOUTVFTABLESTosimplifytheexplanation,IliedalittlewhenIsaidthatVFtablehookscouldinterceptallcallsmadetoafunction.Inreality,theVFtableistraversedonlywhenavirtualfunctioniscalledinawaythatleavesthecompilerwithsomeplausibletypeambiguity.Forexample,aVFtablewillbetraversedwhenafunctioniscalledthroughtheinst->function()callformat.AVFtablewon’tbetraversedwhena
virtualfunctionisinvokedinsuchawaythatthecompilerissureaboutthetype,asininst.function()orsimilarcalls,sincethecompilerwillknowthefunction’saddress.Conversely,callinginst.function()fromascopewhereinstispassedinasareferencewouldtriggeraVFtabletraversal.BeforeyoutrytodeployVFtablehooking,makesurethefunctioncallsyouwanttohookhavetypeambiguity.
WritingaVFTableHookBeforewegoanydeeperintohowtoplaceaVFtablehook,weneedtotalkaboutthosepeskycallingconventionsagain.VFtablesareusedbyclassinstancestocallvirtualmemberfunctions,andallmemberfunctionswillhavethe__thiscallconvention.Thename__thiscallisderivedfromthethispointerthatmemberfunctionsusetoreferencetheactiveclassinstance.Thus,memberfunctionsaregiventhisasapseudoparameteronECX.
It’spossibletomatchtheprototypeofa__thiscallbydeclaringaclassthatactsasacontainerforall__thiscallhookcallbacks,butIdon’tpreferthismethod.Instead,Ifinditeasiertocontrolthedatausinginlineassembly.Let’sexplorehowyoucontrolthedatawhenplacingaVFhookonaclassthatlookslikethis:
classsomeBaseClass{public:virtualDWORDsomeFunction(DWORDarg1){}};classsomeClass:publicsomeBaseClass{public:virtualDWORDsomeFunction(DWORDarg1){}};
ThesomeBaseClassclassjusthasonemember(apublicvirtualfunction),andthesomeClassclassinheritsfromsomeBaseClassandoverridesthesomeBaseClass::someFunctionmember.TohooksomeClass::someFunction,youreplicatetheprototypeinyourVFtablehook,asshowninListing8-3(followalongintheVFHookExample.cppfileoftheproject).
DWORD__stdcallsomeNewVFFunction(DWORDarg1){➊staticDWORD_this;
__asmMOV_this,ECX}
Listing8-3:ThestartofaVFtablehook
Thisfunctionworksasahookbecause__thiscallonlydiffersfrom__stdcallinthattheformerisgiventhisonECX.Toreconcilethissmalldifference,thecallbackfunctionusesinlineassembly(denotedby__asm)tocopythisfromECXtoastaticvariable➊.Sincethestaticvariableisactuallyinitializedasaglobal,theonlycodethatexecutesbeforeMOV_this,ECXisthecodethatsetsupthestackframe—andthatcodenevertouchesECX.ThatensuresthatthepropervalueisinECXwhentheassemblyisexecuted.
NOTE
IfmultiplethreadsstartcallingthesameVFfunction,thesomeNewVFFunction()hookwillbreakbecause_thismightbemodifiedbyonecallwhilestillbeingusedbyanothercall.I’veneverpersonallyrunintothisproblem,asgamesdon’ttypicallythrowaroundmultipleinstancesofcriticalclassesbetweenthreads,butanefficientremedywouldbetostore_thisinthreadlocalstorage,ensuringeachthreadwouldhaveitsowncopy.
Beforereturning,aVFtablecallbackmustalsorestoreECX,tokeepwiththe__thiscallconvention.Here’showthatprocesslooks:
DWORD__stdcallsomeNewVFFunction(DWORDarg1){staticDWORD_this;__asmMOV_this,ECX
//dogamemodifyingstuffhere
__asm➊MOVECX,_this}
Afterexecutingsomegame-hackingcode,thisversionofthefunctionsomeNewVFFunction()restoresECX➊withareversedversionofthefirstMOVinstructionfromListing8-3.
Unlikewith__cdeclfunctions,however,youshouldn’tcallfunctionsthat
usethe__thiscallconventionfrompureC++usingonlyafunctionpointerandtypedef(asyouwouldforacallhook).WhencallingtheoriginalfunctionfromaVFtablehook,youmustuseinlineassembly—that’stheonlywaytobesureyou’repassingdata(specifically_this)aroundproperly.Forexample,thisishowyoucontinuetobuildthesomeNewVFFunction()hook:
DWORD__stdcallsomeNewVFFunction(DWORDarg1){staticDWORD_this,_ret;__asmMOV_this,ECX
//dopre-callstuffhere
__asm{PUSHarg1MOVECX,_this➊CALL[originalVFFunction]➋MOV_ret,EAX}
//dopost-callstuffhere
➌__asmMOVECX,_thisreturn_ret;}
Now,someNewVFFunction()storesthisinthe_thisvariable,allowssomecodetoexecute,callstheoriginalgamefunction➊that’sbeinghooked,storesthatfunction’sreturnvaluein_ret➋,allowssomemorecodetoexecute,restoresthistoECX➌,andreturnsthevaluestoredin_ret.Thecalleecleansthestackfor__thiscallcalls,sounlikeacallhook,thepushedargumentdoesn’tneedtoberemoved.
NOTE
Ifyouwanttoremoveasinglepushedargumentatanypoint,usetheassemblyinstructionADDESP,0x4becauseasingleargumentis4bytes.
UsingaVFTableHookWiththecallingconventionestablishedandaskeletoncallbackinplace,it’stimetomoveontothefunpart:actuallyusingaVFtablehook.Apointerto
timetomoveontothefunpart:actuallyusingaVFtablehook.Apointertoaclass’sVFtableisthefirstmemberofeveryclassinstance,soplacingaVFtablehookrequiresonlyaclassinstanceaddressandtheindexofthefunctiontobehooked.Usingthesetwopiecesofinformation,youneedonlyamodestamountofcodetoplaceahook.Here’sanexample:
DWORDhookVF(DWORDclassInst,DWORDfuncIndex,DWORDnewFunc){DWORDVFTable=➊readMemory<DWORD>(classInst);DWORDhookAt=VFTable+funcIndex*sizeof(DWORD);
autooldProtection=protectMemory<DWORD>(hookAt,PAGE_READWRITE);DWORDoriginalFunc=readMemory<DWORD>(hookAt);writeMemory<DWORD>(hookAt,newFunc);protectMemory<DWORD>(hookAt,oldProtection);
returnoriginalFunc;}
ThehookVF()functionfindstheVFtablebyreadingthefirstmemberoftheclassinstance➊andstoringitinVFTable.SincetheVFtableisjustanarrayofDWORD-sizedaddresses,thiscodefindsthefunctionaddressbymultiplyingthefunction’sindexintheVFtable(funcIndexinthisexample)bythesizeofaDWORD,whichis4,andaddingtheresulttotheVFtable’saddress.Fromthere,hookVF()actssimilartoacallhook:itmakessurethememoryisproperlyaccessiblebysettingappropriateprotections,storestheoriginalfunctionaddressforlater,writesthenewfunctionaddress,andfinally,restorestheoriginalmemoryprotection.
You’lltypicallyhooktheVFtableofaclassinstantiatedbythegame,andcallingafunctionlikehookVF()foraVFtablehooklookslikethis:
DWORDorigVFFunction=hookVF(classInstAddr,0,(DWORD)&someNewVFFunction);
Asusual,youneedtofindclassInstAddrandthefuncIndexargumentaheadoftime.
TherearesomeverynichecasesinwhichVFtablehooksareuseful,anditcanbereallyhardtofindtherightclasspointersandfunctions.Giventhat,insteadofshowingcontrivedusecases,I’llcomebacktoVFtablehooksin“ApplyingJumpHooksandVFHookstoDirect3D”onpage175,onceI’vediscussedothertypesofhooking.
IfyouwanttoplaywithVFhooksbeforereadingmore,addnewvirtualfunctionstotheexampleclassesinthisbook’sresourcefilesandpracticehookingthem.YoumightevencreateasecondclassthatderivesfromsomeBaseClassandplaceahookonitsvirtualtabletodemonstratehowyoucanhavetwocompletelyseparateVFhooksontwoclassesthatinheritthesamebaseclass.
IATHookingIAThooksactuallyreplacefunctionaddressesinaspecifictypeofVFtable,calledtheimportaddresstable(IAT).EachloadedmoduleinaprocesscontainsanIATinitsPEheader.Amodule’sIATholdsalistofalltheothermodulesonwhichthemoduledepends,aswellasalistoffunctionsthatthemoduleusesfromeachdependency.ThinkofanIATasalookuptableforAPIstocalloneanother.
Whenamoduleisloaded,itsdependenciesarealsoloaded.Dependencyloadingisarecursiveprocessthatcontinuesuntilalldependenciesforallmodulesareloaded.Aseachdependencyisloaded,theoperatingsystemfindsallfunctionsusedbythedependentmoduleandfillsanyblankspacesinitsIATwiththefunctionaddresses.Then,whenamodulecallsafunctionfromadependency,itmakesthatcallbyresolvingthefunction’saddressfromtheIAT.
PayingforPortabilityFunctionaddressesarealwaysresolvedfromtheIATinrealtime,sohookingtheIATissimilartohookingVFtables.SincefunctionpointersarestoredintheIATbesidetheiractualnames,there’snoneedtodoanyreverseengineeringormemoryscanning;aslongasyouknowthenameoftheAPIyouwanttohook,youcanhookit!Moreover,IAThookingletsyoueasilyhookWindowsAPIcallsonamodule-specificbasis,allowingyourhookstointerceptonlyAPIcallsfromagame’smainmodule.
Thisportabilityhasacost,though;thecodetoplaceanIAThookismuchmorecomplexthanwhatyou’veseensofar.First,youneedtolocatethePEheaderofthegame’smainmodule.SincethePEheaderisthefirststructureinanybinary,youcanfinditatthebaseaddressofeachmodule,asshowninListing8-4(followalongintheIATHookExample.cppfileofthe
project).
DWORDbaseAddr=(DWORD)GetModuleHandle(NULL);
Listing8-4:Fetchingthemodule’sbaseaddress
Onceyou’vefoundthebaseaddress,youmustverifythatthePEheaderisvalid.Thisvalidationcanbeveryimportant,assomegamestrytopreventthesetypesofhooksbyscramblingnonessentialpartsoftheirPEheaderaftertheyload.AvalidPEheaderisprefixedbyaDOSheader,whichindicatesthefileisaDOSMZexecutable;theDOSheaderisidentifiedbythemagicvalue0x5A4D.AmemberoftheDOSheadercallede_lfanewthenpointstotheoptionalheader,whichcontainsvalueslikethesizeofthecode,aversionnumber,andsoonandisidentifiedbythemagicvalue0x10B.
TheWindowsAPIhasPEstructurescalledIMAGE_DOS_HEADERandIMAGE_OPTIONAL_HEADERthatcorrespondtotheDOSheaderandoptionalheader,respectively.YoucanusethemtovalidatethePEheaderwithcodelikeListing8-5.
autodosHeader=pointMemory<IMAGE_DOS_HEADER>(baseAddr);if(dosHeader->e_magic!=0x5A4D)return0;
autooptHeader=pointMemory<IMAGE_OPTIONAL_HEADER>(baseAddr+dosHeader->e_lfanew+24);if(optHeader->Magic!=0x10B)return0;
Listing8-5:ConfirmingtheDOSandoptionalheadersarevalid
ThecallstopointMemory()createpointerstothetwoheadersthatneedtobechecked.Ifeitherif()statementreturns0,thenthecorrespondingheaderhasthewrongmagicnumber,meaningthePEheaderisn’tvalid.
ReferencestotheIATfromassemblyarehardcoded,meaningassemblyreferencesdon’ttraversethePEheadertolocatetheIAT.Instead,eachfunctioncallhasastaticlocationindicatingwheretofindthefunctionaddress.ThatmeansoverwritingthePEheadertosaythattherearenoimportsisaviablewaytoprotectagainstIAThooks,andsomegameshavethisprotection.
Toaccountforthat,youalsoneedtomakesurethegame’sIATstill
exists.Listing8-6showshowtoaddsuchachecktothecodeinListing8-5.
autoIAT=optHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];if(IAT.Size==0||IAT.VirtualAddress==0)return0;
Listing8-6:CheckingthattheIATactuallyexists
ThePEheadercontainsmanysectionsthatstoreinformationabouttheapplication’scode,embeddedresources,relocations,andsoon.ThepieceofcodeinListing8-6isparticularlyinterestedinthedatasection,which—asyoumightguess—storesmanydifferenttypesofdata.Eachtypeofdataisstoredinitsowndirectory,andtheDataDirectorymemberofIMAGE_OPTIONAL_HEADERisanarrayofdirectoryheadersthatdescribesthesizeandvirtualaddressofeachdirectoryinthedatasection.TheWindowsAPIdefinesaconstantcalledIMAGE_DIRECTORY_ENTRY_IMPORT,whichhappenstobetheindexoftheIATheaderwithintheDataDirectoryarray.
Thus,thiscodeusesoptHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]toresolvetheheaderoftheIATandcheckthattheheader’sSizeandVirtualAddressarenonzero,essentiallyconfirmingitsexistence.
TraversingtheIATOnceyouknowtheIATisstillintact,youcanstarttraversingit,andthisiswhereIAThookingstartstogetugly.TheIATisanarrayofstructurescalledimportdescriptors.Thereisoneimportdescriptorforeachdependency,eachimportdescriptorpointstoanarrayofstructurescalledthunks,andeachthunkrepresentsafunctionimportedfromthedependency.
Luckily,theWindowsAPIexposesboththeimportdescriptorsandthunksthroughtheIMAGE_IMPORT_DESCRIPTORandIMAGE_THUNK_DATAstructures,respectively.Havingthestructurespredefinedsavesyoufromcreatingyourown,butitdoesn’tmakethecodetotraversetheIATanyprettier.ToseewhatImean,lookatListing8-7,whichbuildsonListings8-4through8-6.
autoimpDesc=pointMemory<IMAGE_IMPORT_DESCRIPTOR>(➊baseAddr+IAT.VirtualAddress);
➋while(impDesc->FirstThunk){➌autothunkData=pointMemory<IMAGE_THUNK_DATA>(baseAddr+impDesc->OriginalFirstThunk);intn=0;➍while(thunkData->u1.Function){//thehookhappensinheren++;thunkData++;}impDesc++;}
Listing8-7:IteratingovertheIATtofindafunction
KeepinginmindthattheimportdescriptorsarestoredrelativetothestartofthePEheader,thiscodeaddsthemodule’sbaseaddresstothevirtualaddressfoundintheIAT’sdirectoryheader➊,creatingapointer,impDesc,thatpointstothemodule’sfirstimportdescriptor.
Importdescriptorsarestoredinasequentialarray,andadescriptorwithaFirstThunkmembersettoNULLsignifiestheendofthearray.Knowingthis,thecodeusesawhileloop➋thatcontinuesuntilimpDesc->FirstThunkisNULL,incrementingthedescriptorbyexecutingimpDesc++eachiteration.
Foreachimportdescriptor,thecodecreatesapointercalledthunkData➌thatpointstothefirstthunkinsidethedescriptor.Usingafamiliarloop,thecodeiteratesoverthunks➍untiloneisfoundwithaFunctionmembersettoNULL.Theloopalsousesaninteger,n,tokeeptrackofthecurrentthunkindex,astheindexisimportantwhenplacingthehook.
PlacingtheIATHookFromhere,placingthehookisjustamatteroffindingtheproperfunctionnameandreplacingthefunctionaddress.Youcanfindthenameinsidethenestedwhileloop,asshowninListing8-8.
char*importFunctionName=pointMemory<char>(baseAddr+(DWORD)thunkData->u1.AddressOfData+2);
Listing8-8:Findingthefunctionname
ThefunctionnameforeachthunkisstoredatthunkData-
>u1.AddressOfData+2bytesintothemodule,soyoucanaddthatvaluetothemodule’sbaseaddresstolocatethefunctionnameinmemory.
Afterobtainingapointertothefunctionname,usestrcmp()tocheckwhetherit’sthetargetfunction,likeso:
if(strcmp(importFuncName,funcName)==0){//thefinalstephappensinhere}
Onceyou’velocatedthetargetfunctionusingitsname,yousimplyneedtooverwritethefunctionaddresswiththeaddressofyourownfunction.Unlikefunctionnames,functionaddressesarestoredinanarrayatthestartofeachimportdescriptor.Usingnfromthethunkloop,youcanfinallysetthehook,asshowninListing8-9.
autovfTable=pointMemory<DWORD>(baseAddr+impDesc->FirstThunk);DWORDoriginal=vfTable[n];
➊autooldProtection=protectMemory<DWORD>((DWORD)&vfTable[n],PAGE_READWRITE);➋vfTable[n]=newFunc;protectMemory<DWORD>((DWORD)&vfTable[n],oldProtection);
Listing8-9:Findingthefunctionaddress
ThiscodelocatestheVFtableforthecurrentdescriptorbyaddingtheaddressofthefirstthunktothemodulebaseaddress.TheVFtableisanarrayoffunctionaddresses,sothecodeusesthenvariableasanindextolocatethetargetfunctionaddress.
Oncetheaddressisfound,thecodeinListing8-9worksjustlikeatypicalVFhook:itstorestheoriginalfunctionaddress,setstheprotectionofindexnintheVFtabletoPAGE_READWRITE➊,insertsthenewfunctionaddressintotheVFtable➋,andfinallyrestorestheoldprotection.
IfyoustitchtogetherthecodefromListings8-4through8-9,thefinalIAThookingfunctionlookslikeListing8-10.
DWORDhookIAT(constchar*funcName,DWORDnewFunc){DWORDbaseAddr=(DWORD)GetModuleHandle(NULL);autodosHeader=pointMemory<IMAGE_DOS_HEADER>(baseAddr);if(dosHeader->e_magic!=0x5A4D)return0;
autooptHeader=pointMemory<IMAGE_OPTIONAL_HEADER>(baseAddr+dosHeader->e_lfanew+24);if(optHeader->Magic!=0x10B)return0;
autoIAT=optHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];if(IAT.Size==0||IAT.VirtualAddress==0)return0;
autoimpDesc=pointMemory<IMAGE_IMPORT_DESCRIPTOR>(baseAddr+IAT.VirtualAddress);
while(impDesc->FirstThunk){autothunkData=pointMemory<IMAGE_THUNK_DATA>(baseAddr+impDesc->OriginalFirstThunk);intn=0;while(thunkData->u1.Function){char*importFuncName=pointMemory<char>(baseAddr+(DWORD)thunkData->u1.AddressOfData+2);if(strcmp(importFuncName,funcName)==0){autovfTable=pointMemory<DWORD>(baseAddr+impDesc->FirstThunk);DWORDoriginal=vfTable[n];autooldProtection=protectMemory<DWORD>((DWORD)&vfTable[n],PAGE_READWRITE);vfTable[n]=newFunc;protectMemory<DWORD>((DWORD)&vfTable[n],oldProtection);returnoriginal;}n++;thunkData++;}impDesc++;}}
Listing8-10:ThecompleteIAThookingfunction
Thisisthemostcomplexcodethatwe’veputtogethersofar,andit’sprettyhardtoreadwhensquishedtofitonapage.Ifyouhaven’tyetwrappedyourheadaroundwhatit’sdoing,youmightwanttostudytheexamplecodefromthisbook’sresourcefilesbeforecontinuing.
UsinganIATHooktoSyncwithaGameThreadWiththecodeinListing8-10,hookinganyWindowsAPIfunctionisassimpleasknowingthefunctionnameandtheproperprototype.TheSleep()APIisacommonAPItohookwhengamehacking,asbotscanuseaSleep()hooktothread-syncwithagame’smainloop.
GETTINGINSYNCWITHTHREADSYNCYourinjectedcodewillinevitablyhavetosyncwithagame’smainloop,oritwon’twork.Whenyou’rereadingorwritingdatalargerthan4bytes,forexample,beingoutofsyncallowsthegametoreadorwritethatdataatthesametimeasyou.You’llbesteppingonthegame’stoes,andviceversa,leadingtoallsortsofraceconditionsanddatacorruptionissues.Similarly,ifyoutrytocallagame’sfunctionfromyourownthread,youruntheriskofcrashingthegameifthefunctionisnotthreadsafe.
SinceIAThooksarethread-safemodificationstothePEheader,theycanbeplacedfromanythread.Byplacingoneonafunctionthat’scalledbeforeorafterthegame’smainloop,youcaneffectivelysyncwiththegame’smainthread.Allyouneedtodoisplacethehookandexecuteanythread-sensitivecodefromyourhookcallback.
Here’sonewaytousehookIAT()tohooktheSleep()API:
VOIDWINAPInewSleepFunction(DWORDms){//dothread-sensitivethingsoriginalSleep(ms);}
typedefVOID(WINAPI_origSleep)(DWORDms);_origSleep*originalSleep=(_origSleep*)hookIAT("Sleep",(DWORD)&newSleepFunction);
Here’swhythisworks.Attheendofagame’smainloop,itmightcallSleep()torestuntilit’sreadytodrawthenextframe.Sinceit’ssleeping,it’ssafeforyoutodoanythingyouwantwithoutworryingabout
synchronizationissues.Somegamesmightnotdothis,ortheymightcallSleep()frommultiplethreads,andthosegameswillrequireadifferentmethod.
AmoreportablealternativeistohookthePeekMessageA()APIfunction,becausegamesoftencallthatfunctionfromthemainloopwhilewaitingforinput.Then,yourbotcandothread-sensitiveoperationsfromwithinthePeekMessageA()hook,ensuringthatthey’redonefromthegame’smainthread.Youmayalsowantyourbottousethismethodtohookthesend()andrecv()APIfunctions,asinterceptingtheseallowsyoutocreateapacketsnifferrelativelysimply.
JumpHookingJumphookingallowsyoutohookcodeinplaceswherethereisnobranchingcodetomanipulate.Ajumphookreplacesthecodebeinghookedwithanunconditionaljumptoatrampolinefunction.Whenthejumpishit,thetrampolinefunctionstoresallcurrentregisterandflagvalues,callsacallbackfunctionofyourchoice,restorestheregisters,restorestheflags,executesthecodethatwasreplacedbythehook,andfinallyjumpsbacktothecodejustbelowthehook.ThisprocessisshowninFigure8-1.
Figure8-1:Ajumphook
Theoriginalcodeshowsanexampleofsomeunmodifiedassemblyyoumightfindinagame,andthehookedcodeshowshowthatassemblymightlookafterbeinghookedbyajumphook.Thetrampolineboxshowsanexampletrampolinefunction,inassembly,andthecallbackrepresentsthe
codeyou’retryingtoexecutethroughhooking.Intheoriginalcode,theassemblyexecutesfromtoptobottom.Inthehookedcode,togetfromtheSUBEAX,1instructiontotheRETNinstruction,executionmustfollowthepathshownbythedashedarrows.
NOTE
Ifyourcallbackcodeissimple,itcanbeintegratedintothetrampolineinstead.It’salsonotalwaysnecessarytostoreandrestoretheregistersandflags,butdoingsoisgoodpractice.
PlacingaJumpThebytecodeofanunconditionaljumpresemblesthatofanearcall,butthefirstbyteis0xE9insteadof0xE8.(See“WorkingwithNearCallsinMemory”onpage153forarefresher.)InFigure8-1,theunconditionaljumpJMPtrampolinereplacesthefollowingfouroperations:
POPEAXMOVAL,1POPEDIPOPESI
Inthiscase,youneedtoreplacemultiplesequentialoperationstoaccommodatethe5-bytesizeoftheunconditionaljump.Youmaycomeacrosscaseswherethesizeoftheoperation(oroperations)beingreplacedislargerthan5bytes.Whenthishappens,replacetheremainingbyteswithNOPinstructions.
Now,let’slookathowtoreplacethoseoperations.Listing8-11showsthecodetoplaceajumphook.
DWORDhookWithJump(DWORDhookAt,DWORDnewFunc,intsize){if(size>12)//shouldn'teverhavetoreplace12+bytesreturn0;➊DWORDnewOffset=newFunc-hookAt-5;
autooldProtection=protectMemory<DWORD[3]>(hookAt+1,PAGE_EXECUTE_READWRITE);➋writeMemory<BYTE>(hookAt,0xE9);➌writeMemory<DWORD>(hookAt+1,newOffset);
for(unsignedinti=5;i<size;i++)writeMemory<BYTE>(hookAt+i,0x90);protectMemory<DWORD[3]>(hookAt+1,oldProtection);
returnhookAt+5;}
Listing8-11:Howtoplaceajumphook
Thisfunctiontakestheaddresstohookat,theaddressofthecallbackfunction,andthesizeofthememorytooverwrite(inbytes)asarguments.First,itcalculatestheoffsetbetweenthehooksiteandthetrampolineandstorestheresultinnewOffset➊.Next,PAGE_EXECUTE_READWRITEpermissionsareappliedtothememorytobechanged.Theunconditionaljump(0xE9)➋andtheaddressofthecallbackfunction➌arethenwrittentomemory,andaforloopwritesNOPinstructions(0x90)toanyabandonedbytes.Aftertheoldprotectionsarereapplied,hookWithJump()returnstotheoriginaladdress.
NoticethatthehookWithJump()functionensuresthatsizeisnotabove12beforeplacingthejump.Thischeckisimportantbecauseajumptakesup5bytes,meaningitcanreplaceuptofivecommandsifthefirstfourareeachasinglebyte.Ifthefirstfourcommandsareeachasinglebyte,thefifthcommandwouldneedtobemorethan8bytestotriggertheif(size>12)clause.Because9-byteoperationsarevery,veryrare,12isasafebutflexiblelimit.Havingthislimitcanstopallsortsofbugsfromhappening,especiallyifyourbotisdynamicallydetectingthesizeparameter.Ifthebotmessesupandpassesasizeof500,000,000,forinstance,thecheckwillstopyoufromNOPingthewholeuniverse.
WritingtheTrampolineFunctionUsingthefunctioninListing8-11,youcanreplicatethehookshowninFigure8-1,butfirstyou’llhavetocreatethetrampolinefunctionasfollows:
DWORDrestoreJumpHook=0;void__declspec(naked)myTrampoline(){__asm{➊PUSHFD➋PUSHAD➌CALLjumpHookCallback➍POPAD
➎POPFD➏POPEAXMOVAL,1POPEDI➐POPESI➑JMP[restoreJumpHook]}}
JustlikethetrampolinedescribedalongsideFigure8-1,thistrampolinestoresallcurrentflag➊andregistervalues➋,callsacallbackfunction➌,restorestheregisters➍,restorestheflags➎,executesthecodethatwasreplacedbythehookat➏and➐,andfinallyjumpsbacktotheoriginalcodejustbelowthejumpandNOPs➑.
NOTE
Toensurethatthecompilerdoesn’tautogenerateanyextracodewithinthetrampoline,alwaysdeclarethetrampolineusingthe__declspec(naked)convention.
FinishingtheJumpHookOnceyoucreatethetrampoline,definethecallbackandsetthehooklikeso:
voidjumpHookCallback(){//dostuff}restoreJumpHook=hookWithJump(0xDEADBEEF,&myTrampoline,5);
Finally,insidethejumpHookCallback()function,executethecodethatreliesonthehook.Ifyourcodeneedstoreadorwritethevaluesoftheregistersastheywerewhenthehookexecuted,you’reinluck.ThePUSHADcommandpushesthemtothestackintheorderEAX,ECX,EDX,EBX,originalESP,EBP,ESI,andEDI.ThetrampolinecallsPUSHADdirectlybeforethejumpHookCallback()call,soyoucanreferencetheregistervaluesasarguments,likethis:
voidjumpHookCallback(DWORDEDI,DWORDESI,DWORDEBP,DWORDESP,DWORDEBX,DWORDEDX,DWORDECX,DWORDEAX){//dostuff
}restoreJumpHook=hookWithJump(0xDEADBEEF,&myTrampoline,5);
SincethetrampolineusesPOPADtodirectlyrestoretheregistersfromthesevaluesonthestack,anymodificationsyoumaketotheparameterswillbeappliedtotheactualregisterswhentheyarerestoredfromthestack.
LikeVFtablehooks,jumphooksarerarelyneeded,andtheycanbetrickytosimulatewithasimpleexample.Tohelpyouwrapyourheadaroundthem,I’llexploreareal-world,practicalusecasein“ApplyingJumpHooksandVFHookstoDirect3D”onpage175.
PROFESSIONALAPIHOOKINGLIBRARIESThereareprewrittenhookinglibraries,likeMicrosoft’sDetoursandMadCHook,thatuseonlyjumphooks.Theselibrariescanautomaticallydetectandfollowotherhooks,theyknowhowmanyinstructionstoreplace,andtheygeneratetrampolinefunctionsforyou.Thelibrariesareabletodothisbecausetheyunderstandhowtodisassembleandwalkthroughassemblyinstructionstodeterminelengths,jumpdestinations,andsoon.Ifyouneedtousehookswiththatmuchpower,itisarguablybettertouseoneofthoselibrariesthantocreateyourown.
ApplyingCallHookstoAdobeAIRAdobeAIRisadevelopmentframeworkthatcanbeusedtomakecross-platformgamesinanenvironmentsimilartoAbodeFlash.AIRisacommonframeworkforonlinegames,asitallowsdeveloperstowritecross-platformcodeinaversatile,high-levellanguagecalledActionScript.ActionScriptisaninterpretedlanguage,andAIRrunsthecodeinsideavirtualmachine,whichmakesitinfeasibletohookgame-specificcodewithAIR.Instead,itiseasiertohookAIRitself.
TheexamplecodeforthissectioncanbefoundinGameHackingExamples/Chapter8_AdobeAirHookinthisbook’ssourcefiles.
Thecodecomesfromanoldprojectofmine,anditworksonanygamerunningAdobeAIR.dllversion3.7.0.1530.I’vegottenitworkingonotherversionsaswell,butIcan’tguaranteeitwillworkwithmuchnewerormucholderversions,sotreatthisasacasestudy.
AccessingtheRTMPGoldmineTheRealTimeMessagingProtocol(RTMP)isatext-basednetworkprotocolthatActionScriptusestoserializeandsendentireobjectsoverthenetwork.RTMPsitsontopoftheHyperTextTransferProtocol(HTTP),andasecureversion,RTMPS,sitsontopofHTTPSecure(HTTPS).RTMPSallowsgamedeveloperstoeasilysendandreceiveentireobjectinstancesoverasecureconnectionwithlittlecomplication,makingitthenetworkprotocolofchoiceforanygamesrunningonAIR.
NOTE
DatasentoverRTMP/RTMPSisserializedthroughActionMessageFormat(AMF),andparsingAMFpacketsisbeyondthescopeofthisbook.Searchonlinefor“AMF3Parser,”andyou’llfindalotofcodethatdoesit.
DatasentoverRTMPandRTMPSisveryrich.Thepacketscontaininformationaboutobjecttypes,names,andvalues.Thisisagoldmine.Ifyoucaninterceptthisdatainrealtime,youcaninstantaneouslyrespondtochangesingamestate,seeawealthofcriticalinformationwithouteverreadingitfrommemory,andfindusefulpiecesofdatathatyoumightnotevenknowexist.
Awhileback,Iwasworkingonatoolthatrequiredatonofinsightintothestateofagame.Obtainingsuchalargeamountofdatadirectlyfrommemorywouldhavebeenextremelyhard,ifnotimpossible.Aftersomeresearch,IrealizedthatthegamewasusingRTMPStocommunicatewiththeserver,andthatpromptedmetostartdiggingintothisgoldmine.
SinceRTMPSisencrypted,IknewIhadtosomehowhookthecryptographicfunctionsusedbyAIRbeforeIcouldgetanyusabledata.Aftersearchingonline,Ifoundsourcecodeforasmalltoolcalledairlog,createdbyanothergamehackerwho,likeme,wastryingtologpacketssentoverRTMPS.AlthoughthetoolhookedtheexactfunctionsIneeded,thecodewasoutdated,messy,and,worstofall,didn’tworkontheversionof
codewasoutdated,messy,and,worstofall,didn’tworkontheversionofAIRIwastryingtohook.
Butthatdidn’tmeanitwasuseless.NotonlydidairloghookthetwofunctionsIneeded,butitalsolocatedthembyscanningforcertainbytepatternswithintheAdobeAIRlibrary.Thesebytepatternswerethreeyearsold,though,sotheyweren’tworkinganymore.ThenewerversionsofAdobeAIRhadchangedenoughthattheassemblybyteswerenolongerthesame.Thedifferenceinbyteswasaproblemforthecodeinairlog,butnotforme.
Insideaninlineassemblyblock,youcanspecifyrawbyteswiththefollowingfunctioncall:
_emitBYTE
IfyoureplaceBYTEwith,say,0x03,thecodewillbecompiledinawaythattreats0x03asabyteintheassemblycode,regardlessofwhetherthatmakessense.Usingthistrick,Icompiledthebytearraysbacktoassemblycode.Thecodedidn’tdoanything,anditwasn’tmeantto;usingthistricksimplyallowedmetoattachtomydummyapplicationwithOllyDBGandinspectbytes,whichwereconvenientlypresentedasacleandisassembly.
SincethesebytesrepresentedthecodesurroundingthefunctionsIneeded,so,too,didtheirdisassembly.Thecodewasprettystandardanddidn’tseemlikelytochange,soIturnedmyattentiontotheconstants.Thecodehadafewimmediatevaluespassedasoffsetsincommands.Knowinghowcommonlythesecanchange,Irewiredairlog’spattern-matchingalgorithmtosupportwildcards,updatedthepatternstotreatanyconstantsaswildcards,andthenranthematch.Aftersometweakstothepatternsandabitofdiggingthroughduplicatesearchresults,ItrackeddownthefunctionsIwantedtohook.Iappropriatelynamedthemencode()anddecode()andbeganworkingonatoolsimilartoairlog—butbetter.
HookingtheRTMPSencode()FunctionIdiscoveredthattheencode()function,whichisusedtoencryptthedataforoutgoingpackets,isanonvirtual__thiscall,meaningit’scalledbyanearcall.Moreover,thecallhappensinsidealoop.TheentirelooplookslikeListing8-12,takendirectlyfromtheOllyDBGdisassemblypane.
loop:
MOVEAX,[ESI+3C58]SUBEAX,EDIPUSHEAX➊LEAEAX,[ESI+EDI+1C58]PUSHEAXMOVECX,ESI➋CALLencodeCMPEAX,-1➌JESHORTendLoopADDEDI,EAX➍CMPEDI,[ESI+3C58]JLloopendLoop:
Listing8-12:Theencode()loop
Withabitofanalysisandsomeguidancefromairlog,Ideterminedthattheencode()functioncalledat➊takesabytearrayandbufferlength(let’scallthembufferandsize,respectively)asparameters.Thefunctionreturns-1whenitfailsandreturnssizeotherwise.Thefunctionoperatesonchunksof4,096bytes,whichiswhythishappensinaloop.
Turnedintomorereadablepseudocode,theloopcallingencode()lookslikethis(thenumbersrefertotherelevantassemblyinstructionsinListing8-12):
for(EDI=0;EDI<➍[ESI+3C58];){EAX=➋encode(➊&[ESI+EDI+1C58],[ESI+3C58]-EDI);if(EAX==-1)➌break;EDI+=EAX;}
Iwasn’tinterestedinwhatencode()did,butIneededtheentirebufferitwasloopingover,andhookingencode()wasmymeansofgettingthatbuffer.LookingattherealloopinListing8-12,Iknewthatthecallingobjectinstance’sfullbufferwasstoredatESI+0x1C58,thatthefullsizewasstoredatESI+0x3C58,andthatEDIcontainedtheloopcounter.Idevisedthehookwiththesethingsinmind,ultimatelycreatingatwo-parthook.
ThefirstpartofmyhookwasareportEncode()functionthatlogstheentirebufferonthefirstloopiteration.Here’sthereportEncode()functioninfull:
DWORD__stdcallreportEncode(
constunsignedchar*buffer,unsignedintsize,unsignedintloopCounter){if(loopCounter==0)printBuffer(buffer,size);returnorigEncodeFunc;}
Thisfunctiontakesbuffer,size,andloopCounterasparametersandreturnstheaddressofthefunctionIdubbedencode().Beforefetchingthataddress,however,thesecondpartofmyhook,amyEncode()function,doesallofthedirtyworktoobtainbuffer,size,andloopCounter,asfollows:
void__declspec(naked)myEncode(){__asm{MOVEAX,DWORDPTRSS:[ESP+0x4]//getbufferMOVEDX,DWORDPTRDS:[ESI+0x3C58]//getfullsizePUSHECX//storeecxPUSHEDI//pushcurrentpos(loopcounter)PUSHEDX//pushsizePUSHEAX//pushbufferCALLreportEncode//reporttheencodecallPOPECX//restoreecxJMPEAX//jumptoencode}}
ThemyEncode()functionisapureassemblyfunctionthatreplacestheoriginalencode()functioncallusinganearcallhook.AfterstoringECXonthestack,myEncode()obtainsbuffer,size,andloopCounterandpassesthemtothereportEncode()function.AftercallingthereportEncode()function,themyEncode()functionrestoresECXandjumpsdirectlyintoencode(),causingtheoriginalfunctiontoexecuteandreturngracefullytotheloop.
SincemyEncode()cleanseverythingitusesfromthestack,thestackstillcontainstheoriginalparametersandreturnaddressinthecorrectspotaftermyEncode()runs.That’swhymyEncode()jumpsdirectlyintoencode()insteadofusingafunctioncall:thatstackisalreadysetupwiththeproperreturnaddressandparameters,sotheencode()functionwillthinkeverythinghappenedasnormal.
HookingtheRTMPSdecode()Function
ThefunctionInameddecode(),whichisusedtodecryptincomingdata,wasalsoa__thiscallthatwascalledinaloop.Itworkedonchunksof4,096bytesandtookabufferandsizeasparameters.Theloopwasquiteabitmorecomplex,containingmultiplefunctioncalls,nestedloops,andloopescapes,buthookingworkedmuchthesameashookingtheso-calledencode()function.Thereasonfortheaddedcomplexityisnotrelevanttohookingthefunction,butitmakesthecodedifficulttosummarize,soIwon’tshowtheoriginalfunctionhere.Thebottomlineisthis:onceallthecomplexitywasrubbedaway,thedecode()loopwastheencode()loopinreverse.
Onceagain,Idevisedatwo-partnearcallhook.Thefirstpart,reportDecode(),isshownhere:
void__stdcallreportDecode(constunsignedchar*buffer,unsignedintsize){printBuffer(buffer,size);}
Thefunctionlogseachpacketthatcomesthrough.Ididn’thavealoopindexatthetime,soIdecidedthatitwasokaytologeverysinglepartialpacket.
Thesecondpartofthehook,themyDecode()function,actsasthenewcalleeanddoesallofthedirtywork,asfollows:
void__declspec(naked)myDecode(){__asm{MOVEAX,DWORDPTRSS:[ESP+0x4]//getbufferMOVEDX,DWORDPTRSS:[ESP+0x8]//getsizePUSHEDX//pushsizePUSHEAX//pushbuffer➊CALL[origDecodeFunc]
MOVEDX,DWORDPTRSS:[ESP+0x4]//getthebuffer
PUSHEAX//storeeax(returnvalue)PUSHECX//storeecxPUSHEAX//pushsizePUSHEDX//pushbufferCALLreportDecode//reporttheresultsnowPOPECX//restoreecx➋POPEAX//restoreeax(returnvalue)
➌RETN8//returnandcleanstack}}
Iknewthebufferwasdecryptedinplace,meaningtheencryptedchunkwouldbeoverwrittenwiththedecryptedoneoncethecalltodecode()wascomplete.ThismeantthatmyDecode()hadtocalltheoriginaldecode()function➊beforecallingthereportDecode()function,whichwouldgivetheresultsofthedecoding.Ultimately,myDecode()alsoneededtoreturnwiththesamevaluethattheoriginaldecode()functionwouldandcleanupthestack,andthefinalPOP➋andRETN➌instructionstookcareofthat.
PlacingtheHooksThenextproblemIranintowasthatthehookswereforcodeinsidethemoduleAdobeAIR.dll,whichwasnotthemainmoduleofthegame.Becauseofthecode’slocation,Ineededtofindthebaseaddressesforthehooksabitdifferently.Additionally,sinceIneededthesehookstoworkacrossafewdifferentversionsofAdobeAIR,Ialsohadtofindtherightaddressesforeachversion.InsteadoftryingtogetmyhandsonallofthedifferentversionsofAdobeAIR,Itookanotherpageoutofairlog’splaybookanddecidedtoprogrammaticallylocatetheaddressesbywritingasmallmemoryscanner.BeforeIcouldwritethememoryscanner,IneededboththebaseaddressandsizeofAdobeAIR.dllsoIcouldlimitmymemorysearchtoonlythatarea.
IfoundthesevaluesusingModule32First()andModule32Next()asfollows:
MODULEENTRY32entry;entry.dwSize=sizeof(MODULEENTRY32);HANDLEsnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,NULL);
DWORDbase,size;if(Module32First(snapshot,&entry)==TRUE){➊while(Module32Next(snapshot,&entry)==TRUE){std::wstringbinaryPath=entry.szModule;➋if(binaryPath.find("AdobeAIR.dll")!=std::wstring::npos){size=(DWORD)entry.modBaseSize;base=(DWORD)entry.modBaseAddr;break;}
}}
CloseHandle(snapshot);
ThiscodeloopsthroughallmodulesintheprocessuntilitfindsAdobeAIR.dll➊.Whenitfindsthecorrectmoduleentry➋,itfetchesthemodBaseSizeandmodBaseAddrpropertiesfromitandbreaksoutimmediately.
ThenextstepwasfindingasequenceofbytesIcouldusetoidentifythefunctions.Idecidedtousethebytecodesurroundingeachcall.Ialsohadtomakesurethateachsequencewasuniquewhileavoidingtheuseofanyconstantsinthepatternstoensurethecode’sportability.Listing8-13showsthebytesequencesIendedupwith.
constcharencodeSeq[16]={0x8B,0xCE,//MOVECX,ESI0xE8,0xA6,0xFF,0xFF,0xFF,//CALLencode0x83,0xF8,0xFF,//CMPEAX,-10x74,0x16,//JESHORTendLoop0x03,0xF8,//ADDEDI,EAX0x3B,0xBE};//partofCMPEDI,[ESI+0x3C58]constchardecodeSeq[12]={0x8B,0xCE,//MOVECX,ESI0xE8,0x7F,0xF7,0xFF,0xFF,//CALLdecode0x83,0xF8,0xFF,//CMPEAX,-10x89,0x86};//partofMOV[ESI+0x1C54],EAX
Listing8-13:Theencode()anddecode()bytesequences
NoticetheCALLinstructionineachpattern;thesearethecallstotheAdobeAIRfunctionsInamedencode()anddecode().Iscannedforthesesequenceswiththefollowingfunction:
DWORDfindSequence(DWORDbase,DWORDsize,constchar*sequence,unsignedintseqLen){for(DWORDadr=base;adr<=base+size–seqLen;adr++){if(memcmp((LPVOID)sequence,(LPVOID)adr,seqLen)==0)returnadr;}return0;}
TreatingthememoryofAdobeAIR.dllasabytearray,thefindSequence()functionlooksforasequenceofbytesasasubsetofthatbytearrayand
returnstheaddressofthefirstmatchitfinds.WiththefindSequence()functionwritten,findingtheaddressesIneededtohookencode()anddecode()wassimple.Here’showthosecallslooked:
DWORDencodeHookAt=findSequence(base,size,encodeSeq,16)+2;DWORDdecodeHookAt=findSequence(base,size,decodeSeq,12)+2;
Sinceeachtargetcallwas2bytesintoitsreceptivesearchsequence,allIhadtodowaslocateeachsequenceandadd2.Afterthat,thefinalstepwastoplacethehooksusingthemethoddescribedin“CallHooking”onpage153.
Withmyhookfinished,Icouldseeeverysinglepieceofdatathatwentbetweenthegame’sclientandserver.Moreover,sincetheRTMPSprotocolsendsserializedActionScriptobjects,thedatawasbasicallyself-documenting.Everysinglepieceofinformationwasaccompaniedbyavariablename.Everyvariableexistedasamemberofawell-describedobject.Everyobjecthadaconsistentname.LikeIsaid—itwasagoldmine.
ApplyingJumpHooksandVFHookstoDirect3DUnliketheAdobeAIRhookIjustdescribed,hooksforDirect3D(the3DgraphicscomponentofMicrosoft’sDirectXAPI)areverycommonandhighlydocumented.Direct3Disubiquitousintheworldofgaming:amajorityofPCgamesusethelibrary,whichmeansthathookingitgivesyouaverypowerfulmethodforinterceptingdataandmanipulatingthegraphicslayersofmanydifferentgames.YoucanuseaDirect3Dhookforanumberoftasks,suchasdetectinglocationsofhiddenenemyplayers,increasingthelightingofdarkin-gameenvironments,andseamlesslydisplayingadditionalgraphicalinformation.MakingeffectiveuseofaDirect3DhookrequiresyoutolearnabouttheAPI,butthere’smorethanenoughinformationinthisbooktogetyoustarted.
Inthissection,I’llgiveyouahigh-levelintroductiontoagameloopthatusesDirect3DbeforedivingrightintotheimplementationofaDirect3Dhook.RatherthandetailingtheinternalsandgivingyoutheanalyticalbackstoryasIdidwiththeAdobeAIRhook,I’llgooverthemostpopularDirect3Dhookmethod,asitiswelldocumentedandusedbythemajorityof
Direct3Dhookmethod,asitiswelldocumentedandusedbythemajorityofgamehackers.
Theonlineresourcesforthisbookincludetwopiecesofexamplecodeforthissection;findthosefilesnowifyouwanttofollowalong.Thefirstpart,anexampleDirect3D9applicationforyoutohackon,canbefoundunderGameHackingExamples/Chapter8_Direct3DApplication.Thesecondpart,theactualhook,isunderChapter8_Direct3DHook.
TherearemultipleversionsofDirect3Dinuseatanygiventime,andtherearewaystohookeachone.Forthisbook,I’llfocusonhookingDirect3D9,becauseitistheonlycommonlyusedversionthatissupportedbyWindowsXP.
NOTE
EventhoughXPhasreachedendoflife,manypeopleinlessdevelopedcountriesstilluseitasaprimarygamingsystem.Direct3D9worksonallversionsofWindowsandisnearlyaspowerfulasitssuccessors,somanygamecompaniesstillprefertouseitovernewerversionsthatdon’thaveasmuchbackwardcompatibility.
TheDrawingLoopLet’sjumprightinwithacrashcourseonhowDirect3Dworks.InsideaDirect3Dgame’ssourcecode,you’llfindaninfiniteloopthatprocessesinputandrendersgraphics.Eachiterationinthisdrawingloopiscalledaframe.Ifwecutoutalltheextraneouscodeandfocussimplyonabareskeleton,wecanvisualizeagame’smainloopwiththefollowingcode:
intWINAPIWinMain(args){/*SomecodeherewouldbecalledtosetupDirect3Dandinitializethegame.Leavingitoutforbrevity.*/MSGmsg;while(TRUE){/*Somecodewouldbeheretohandleincomingmouseandkeyboardmessages.*/drawFrame();//thisisthefunctionwecareabout}/*Somecodeherewouldbecalledtocleanupeverythingbeforeexiting.*/}
Thisfunctionistheentrypointofthegame.Simplyput,itinitializesthegameandthenentersthegame’smainloop.Insidethemainloop,itexecutescoderesponsibleforprocessinguserinputbeforecallingdrawFrame()toredrawthescreenusingDirect3D.(CheckoutthecodeinGameHackingExamples/Chapter8_Direct3DApplicationtoseeafullyfunctionalgameloop.)
Eachtimeitiscalled,thedrawFrame()functionredrawstheentirescreen.Thecodelookssomethinglikethis:
voiddrawFrame(){➊device->Clear(0,NULL,D3DCLEAR_TARGET,D3DCOLOR_XRGB(0,0,0),1.0f,0);device->BeginScene();//drawingwillhappenheredevice->EndScene();device->Present(NULL,NULL,NULL,NULL);}
Afterclearingthescreenwithdevice->Clear➊,thedrawFrame()functioncallsdevice->BeginScene()tounlockthescenefordrawing.Itthenexecutessomedrawingcode(whatthatdrawingcodeactuallydoesisn’timportantrightnow)andlocksthescenewithadevice->EndScene()call.Tofinishup,itrendersthescenetothescreenbycallingthedevice->Present()function.
Noticethatallofthesefunctionsarecalledasmembersofsomeinstancecalleddevice.ThisissimplyanobjectinstancerepresentingtheDirect3Ddevice,whichisusedtoinvokeallsortsofdrawingcalls.Also,noticethatthisfunctionisdevoidofanyactualdrawingcode,butthat’sokay.It’sreallyonlyimportantforyoutounderstandthehigh-levelconceptsofdrawingloops,frames,andtheDirect3Ddevice.Torecap,gameshaveamainloopwithtworesponsibilities:
•Handlingincomingmessages
•Drawingthegametothescreen
Eachiterationinthisloopiscalledaframe,andeachframeisdrawnbyadevice.Takingcontrolofthedevicegivesyouaccesstothemostsensitiveanddescriptivedetailsofthegame’sstate;thatis,you’llbeabletopeekintothegame’sstateafterthedatahasbeenparsed,processed,andrenderedtothescreen.Moreover,you’llbeabletomodifytheoutputofthisstate.These
thescreen.Moreover,you’llbeabletomodifytheoutputofthisstate.Thesetwosuperpowersenableyoutopulloffallkindsofawesomehacks.
FindingtheDirect3DDeviceTotakecontrolofaDirect3Ddevice,youhookthememberfunctionsinthedevice’sVFtable.Unfortunately,however,usingtheDirect3DAPItoinstantiateyourowninstanceofthesamedeviceclassfrominjectedcodedoesn’tmeanyou’llshareaVFtablewiththegame’sinstance.Direct3DdevicesuseacustomizedruntimeimplementationofVFtables,andeachdevicegetsitsownuniqueVFtable.Additionally,devicessometimesrewritetheirownVFtables,removinganyhooksandrestoringtheoriginalfunctionaddresses.
BothoftheseDirect3Dquirksleaveyouwithoneinevitableoption:youmustfindtheaddressofthegame’sdeviceandmodifyitsVFtabledirectly.Here’show:
1. CreateaDirect3DdeviceandtraverseitsVFtabletolocatethetrueaddressofEndScene().
2. PlaceatemporaryjumphookonEndScene().
3. Whenthejumphookcallbackisexecuted,storetheaddressofthedevicethatwasusedtocallthefunction,removethehook,andrestoreexecutionnormally.
4. Fromthere,useVFhookstohookanymemberfunctionoftheDirect3Ddevice.
JumpHookingEndScene()SinceeverydevicewillcallEndScene()attheendofeachframe,youcanhookEndScene()usingajumphookandinterceptthegame’sdevicefromyourhookcallback.UniquedevicesmayhavetheirownuniqueVFtables,butthedifferenttablesstillpointtothesamefunctions,soyoucanfindtheaddressofEndScene()intheVFtableofanyarbitrarydevice.UsingstandardDirect3DAPIcalls,youcancreateyourowndevicelikethis:
LPDIRECT3D9pD3D=Direct3DCreate9(D3D_SDK_VERSION);if(!pD3D)return0;
D3DPRESENT_PARAMETERSd3dpp;ZeroMemory(&d3dpp,sizeof(d3dpp));d3dpp.Windowed=TRUE;d3dpp.SwapEffect=D3DSWAPEFFECT_DISCARD;d3dpp.hDeviceWindow=hWnd;
LPDIRECT3DDEVICE9device;HRESULTres=pD3D->CreateDevice(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,hWnd,D3DCREATE_SOFTWARE_VERTEXPROCESSING,&d3dpp,&device);if(FAILED(res))return0;
ExplaininghoweverythinginDirect3Dworksisoutsidethescopeofthisbook,sojustknowthatyoucancopythiscodetocreateaDirect3DdevicethatcontainstheEndScene()functionasamember.TheEndScene()addressisatindex42intheVFtableofdevice(see“TheMeaningofDevice,Direct3D,andVFHooks”boxtolearnhowtofindthatindex),andyoucanreaditusingasubsetoftheVFtablehookingcodefrom“UsingaVFTableHook”onpage159,asfollows:
DWORDgetVF(DWORDclassInst,DWORDfuncIndex){DWORDVFTable=readMemory<DWORD>(classInst);DWORDhookAddress=VFTable+funcIndex*sizeof(DWORD);returnreadMemory<DWORD>(hookAddress);}DWORDEndSceneAddress=getVF((DWORD)device,42);
Onceyou’veobtainedtheaddress,yourdiscoverydevicehasserveditspurpose,anditcanbedestroyedwithacalltotheRelease()function:
pD3D->Release();device->Release();
WiththeaddressofEndScene()inhand,you’dbereadytostartthinkingabouthowtoplaceyourhookinmemory.Butsinceyoujusthaveafunctionaddress,youronlyoptionistoplaceajumphookatthetopofthefunction.
THEMEANINGOFDEVICE,DIRECT3D,ANDVF
HOOKSIfyou’rewonderinghowIknowthattheindexoftheEndScene()functionis42,you’vecometotherightbox.SinceDirect3D9isafreelyavailablelibrary,youcanactuallyseequiteabitofwhatgoesonunderthehood.Themainheaderfileforthelibraryisd3d9.h.Ifyouopenthisfileinyoureditorandsearchfor“EndScene,”you’llendupinthemiddleofalargeclassdefinitionthatspecifiesseveralfunctionsusingCmacros.ThisisthebaseclassforallDirect3D9deviceimplementations,anditdefinesthevirtualfunctionsusedbytheclass.
TheVFtableisconstructedinthesameorderasthefunctionsaredefinedincode,soyoucandeterminetheindexofanymemberfunctionbysimplycountingthelines.Youcanscrolltothetopoftheclassdefinition(atline426inmyversionofthelibrary,andprobablyyourstoo),notethelinewherethefirstfunctionisdeclared(line429),andthenscrolltotheEndScene()definitionandnotethatline(line473).Finally,countthenumberofblankorcommentedlines(twoforme)anddosomemath:473–429–2=42.
Presto!TheEndScene()functionisthe43rdfunctiondeclared,soitsitsatthe42ndspotintheVFtable.Anotheradvantagetohavingthisheaderisthatyoucanseethename,argumenttypes,argumentnames,andreturntypeofeverysinglefunctioninthedeviceclass.Sowhenyou’rewritingyourownhooksinthefuture,you’llknowexactlywheretolook.
PlacingandRemovingtheJumpHookSinceyou’rejustusingthehooktofindthedevice,youneedtocallitonlyonce.Afterobtainingthedevice,you’llremovethejumphookandrestoreexecutionbacktothestartofEndScene()sothatthedrawingloopcancarryonitswork.Believeitornot,thismakesyourlifemucheasier.Sincethecodewillberestoredimmediately,there’snoneedforyourtrampolinetoexecutethecommandsthatarereplacedbythejump,andthere’snoneedtopadthejumpwithNOPs.Allyouneedtodoisstoretheoriginalbytesandplacethehook.Todoso,youuseaslightlytweakedversionofthejump-hookingcodefromListing8-11:
unsignedchar*hookWithJump(DWORDhookAt,DWORDnewFunc){DWORDnewOffset=newFunc-hookAt-5;➊autooldProtection=protectMemory<BYTE[5]>(hookAt,PAGE_EXECUTE_READWRITE);unsignedchar*originals=newunsignedchar[5];for(inti=0;i<5;i++)➋originals[i]=readMemory<unsignedchar>(hookAt+i);➌writeMemory<BYTE>(hookAt,0xE9);writeMemory<DWORD>(hookAt+1,newOffset);protectMemory<BYTE[5]>(hookAt,oldProtection);returnoriginals;}
LikethefunctioninListing8-11,thisfunctionmakesthememorywritable➊,placesthehook➌,andrestoresthememoryprotection.Beforeplacingthehook,itallocatesa5-bytebuffercalledoriginals➋andfillsitwiththeoriginalbytes.Afterthehookisplaced,itreturnsoriginalstothecallingfunction.
Whenit’stimetoremovethehook,passoriginalstothefollowingfunction:
voidunhookWithJump(DWORDhookAt,unsignedchar*originals){autooldProtection=protectMemory<BYTE[5]>(hookAt,PAGE_EXECUTE_READWRITE);for(inti=0;i<5;i++)writeMemory<BYTE>(hookAt+i,originals[i]);protectMemory<BYTE[5]>(hookAt,oldProtection);delete[]originals;}
Thiscodesimplyiteratesoveroriginalsandquietlyplacesthose5bytesbackwheretheywerefoundsothateverythingisasexpectedwhenexecutionreturnstotheEndScene()function.Whenthetimecomes,youcanplaceandremoveyouractualhookusingtwolinesofcode,likethis:
autooriginals=hookWithJump(EndSceneAddress,(DWORD)&endSceneTrampoline);unhookWithJump(EndSceneAddress,originals);
OnceyouhavethehookWithJump()andunhookWithJump()functions,it’stimetopreparethecallbackandfindthedevice.
WritingtheCallbackandTrampolineEventhoughyoucanobtaintheEndScene()addressfromaVFtable,theEndScene()functiondoesn’tactuallyfollowthe__thiscallconvention.Direct3DclassesaresimplewrappersaroundaCAPI,andallofthememberfunctioncallsareforwardedto__stdcallfunctionsthattakeaclassinstanceasafirstparameter.Thismeansthatyourtrampolineonlyneedstograbthedevicefromthestack,passittothecallback,andthenjumpbacktoEndScene().Thecallbackonlyhastoremovethejumphookbeforereturningtothetrampoline.
Thefinalcodeforthecallbackandtrampolinetothisjumphooklookssomethinglikethis:
LPDIRECT3DDEVICE9discoveredDevice;DWORD__stdcallreportInitEndScene(LPDIRECT3DDEVICE9device){discoveredDevice=device;unhookWithJump(EndSceneAddress,originals);returnEndSceneAddress;}__declspec(naked)voidendSceneTrampoline(){__asm{MOVEAX,DWORDPTRSS:[ESP+0x4]PUSHEAX//givethedevicetothecallback➊CALLreportInitEndSceneJMPEAX//jumptothestartofEndScene}}
UsingthehookWithJump()function,youcanplaceajumphookonEndScene()thatcallstheendSceneTrampoline()function.Whenthegame’sdevicecallstheEndScene()function,thetrampolinefunctioncallsthereportInitEndScene()function➊.ThereportInitEndScene()functionstoresthecaptureddevicepointertoaglobalvariablecalleddiscoveredDevice,removesthehookbycallingunhookWithJump(),andreturnstheaddressofEndScene()tothetrampoline.Tofinishup,thetrampolinejumpsdirectlytoEAX,whichwillbeholdingtheaddressthatwasreturnedfromthereportingfunction.
NOTE
YoucanusejumphookstocompletelyskiptheVFtablehookingthatI’llshowyou,butit’sveryunreliabletouse“dumb”jumphooksoncommonlyhookedAPIfunctions.Consistentlyobtaininggoodresultswithonlyjumphooksrequiresprofessionalhookinglibraries,andI’dratherteachyouhowtodoitcompletelyonyourown.
Atthispoint,allthat’slefttodoishooktheVFtableofdiscoveredDevicetohackthegame.ThenexttwosectionswillwalkyouthroughhooksontheEndScene()andReset()functions,whicharerequiredifyouwantastablehook.
WritingaHookforEndScene()AhookonEndScene()isusefulbecauseitallowsyoutointerceptacompletedframejustbeforeitisrendered;youcaneffectivelyexecuteyourownrenderingcodeinsidethegameloop.Asyousawwhenlocatingthisfunction’saddressin“JumpHookingEndScene()”onpage178,thisfunctionisatindex42intheVFtable.YoucanhookEndScene()usingaVFhookasfollows:
typedefHRESULT(WINAPI*_endScene)(LPDIRECT3DDEVICE9pDevice);_endSceneorigEndScene=(_endScene)hookVF((DWORD)discoveredDevice,42,(DWORD)&myEndScene);HRESULTWINAPImyEndScene(LPDIRECT3DDEVICE9pDevice){//drawyourownstuffherereturnorigEndScene(pDevice);}
ThiscodeusesthehookVF()functionfrom“UsingaVFTableHook”onpage159tohookEndScene()atindex42ofdiscoveredDevice,usingmyEndScene()asthecallbackfunction.AdirectDirect3DdevicewilloccasionallyrepatchitsownVFtableandrestoretheoriginalfunctionaddresses.ThistypicallyhappensfromwithintheEndScene()function,meaningyoualsohavetorepatchtheVFtableaftercallingtheoriginalEndScene()function.Thereareafewchangesyoucanmaketothishooktohandlethat,asshowninListing8-14.
_endSceneorigEndScene=NULL;voidplaceHooks()
{autoret=hookVF((DWORD)discoveredDevice,42,(DWORD)&myEndScene);if(ret!=(DWORD)&myEndScene)//don'tpointtoyourhookorigEndScene=(_endScene)ret;}placeHooks();
HRESULTWINAPImyEndScene(LPDIRECT3DDEVICE9pDevice){//drawyourownstuffhereautoret=origEndScene(pDevice);placeHooks();//updatehooksreturnret;}
Listing8-14:FinalcodetohookEndScene()
ThecodetoplacethehookhasbeenmovedintoafunctioncalledplaceHooks()soitcanbecalledmultipletimeswithease.Thecallbackfunctionstillforwardsthecalltotheoriginalfunction,butitmakessuretocallplaceHooks()beforereturning.Thisensuresthatthehookisalwaysactive,eveniftheoriginalEndScene()functionremovesit.
AnotherpointtonoticeisthatplaceHooks()updatestheaddressoforigEndScene()everytimethehookisreplaced,aslongastheaddressreturnedfromhookVF()isn’ttheaddressofthemyEndScene()function.Thisdoestwodistinctthings.First,itallowsotherapplicationstohookEndScene()withoutsteppingontheirtoes,sinceitwillupdateorigEndScene()towhateverisseenintheVFtable.Second,itmakessurethatthevalueoforigEndScene()canneverbetheaddressofourcallback,preventingapotentialinfiniteloop.Aninfiniteloopispossibleotherwise,becauseorigEndScene()doesn’talwaysfixthedevice’sVFtable,meaningplaceHooks()canbecalledwhentheVFtablestillcontainsthemyEndScene()function.
WritingaHookforReset()Whenyou’reusingaDirect3Dhookinproduction,you’llbedoingallkindsoftaskslikedrawingcustomtext,displayingimagesrelatedtoyourbot,andinteractingwithfunctioncallsfromthegame.ThesetaskswillrequireyoutocreateyourownDirect3Dobjectsthataretiedtothegame’sdevice,andthatcanbeaproblem.Fromtimetotime,thegamemaycompletelyresetitsdevicethroughaReset()function.Whenadeviceisreset,you’llneedto
updateanyobjects(mostcommonlyfontsandsprites)thatyou’vecreatedforthedevice,usingtheirOnLostDevice()memberfunctions.
SinceReset()iscalledfromtheVFtableofthedevice,youcanuseahookonittotellyouwhenthedevicehasbeenreset.Reset()takestwoparametersandisatindex16intheVFtable.YoucanaddthiscodetoplaceHooks()inListing8-14tohooktheReset()function:
autoret=hookVF((DWORD)discoveredDevice,16,(DWORD)&myReset);if(ret!=(DWORD)&myReset)origReset=(_reset)ret;
AndthisisthedeclarationtousefororigReset:
typedefHRESULT(WINAPI*_reset)(LPDIRECT3DDEVICE9pDevice,D3DPRESENT_PARAMETERS*pPresentationParameters);_resetorigReset=NULL;
Whenaresetissuccessful,theoriginalfunctionreturnsD3D_OK.YourhookfunctionrecognizesthisandcallsOnLostDevice()accordingly:
HRESULTWINAPImyReset(LPDIRECT3DDEVICE9pDevice,D3DPRESENT_PARAMETERS*pPresentationParameters){autoresult=origReset(pDevice,pPresentationParameters);if(result==D3D_OK){//callonLostDevice()forallofyourobjects}returnresult;}
Onceyoufillinthecontentsoftheif()statement,allofyourobjectsarereadytouseagain.
What’sNext?NowthatI’veshownyouhowtotakecontrolofagame’sDirect3Ddevice,you’reprobablywonderingwhatyoucandowithit.Unliketheotherexamplesinthebook,thecodeinthissectionandtheexamplecodedon’thaveaone-to-onecorrelation,butthefunctionalityisstillthesame.Here’sahigh-levelviewofthecorrelationbetweenthischapterandthecodeintheChapter8_Direct3DHookexampleproject.
ThefileDirectXHookCallbacks.hcontainsthecallbacksfortheEndScene()andReset()functions,twocallbacksforothercommonfunctions,andthetrampolineandreporterfunctionsforthetemporaryjumphook.Thesefunctionsareallprettymuchasdescribedinthischapter,excepttheycallintoasingletonclassdefinedinDirectXHook.handDirectXHook.cpp.Thissingletonclassisresponsibleforforwardingthecallstotheoriginalfunctions.
Theclassisalsoresponsibleforalloftheheavylifting,anditcontainsthecodetocreatethediscoverydevice,placethehooks,drawtext,handledeviceresets,anddisplayimages.Furthermore,itallowsexternalcodetoaddcustomcallbacksforeachhook,asyoucanseeinmain.cpp.Here,you’llseeanumberofdifferentcallbacksthataredrawingcustomtext,addingnewimagestothescreen,andchangingthetexturesofmodelsthataredrawnbythegame.Irecommendpokingaroundinthecodetogetabetterunderstandingofwhat’sgoingon,butdon’tgettoocarriedaway.We’lldiveintothiscodeinChapter9totalkaboutallthecoolhacksitcando.
OPTIONALFIXESFORSTABILITYTheReset()andEndScene()hooksdescribedinthischaptershouldworkwellforanygamerunningDirect3D9,butitisslightlyunstable.IfthegametriestoexecuteEndScene()whenthejumphookisplaced,itwillcrashbecausethebytesarebeingmodified.Therearetwowaystofixthis.First,youcanplacethejumphookfromwithinanIAThookonPeekMessage().ThiswillworkbecauseplacinganIAThookisathread-safeoperation,butitassumesthatPeekMessage()iscalledonlyfromthesamethreadthatdoestheDirect3Ddrawing.
Asafer,butmorecomplex,alternativeistoiterateovereverythreadinthegame(similartohowitworkedforthreadhijacking)anduseSuspendThread()topauseallthreadsinthegame(exceptfortheoneplacingthehook,ofcourse).Beforepausingathread,youmustmakesureitsEIPisnotexecutingthefirst5bytesofEndScene().Afterthehookisplaced,youmustuseResumeThread()torestoreexecutionwithyourhookinplace.
ClosingThoughtsControlflowmanipulationisaveryimportantskillingamehacking,andalotofthehacksinthisbookrelyonit.Throughoutthenexttwochaptersyou’lllearnhowtocreatecommonhacksusingtheDirect3Dhook,andyou’llgetabetterideaofthegeneralusecasesofhooking.Evenifyoufeelalittleshaky,continuetoChapter9.ThecodeexamplestherecenterontheDirect3Dhookandwillgetyouevenmorefamiliarwithhookingtechniques.
PART4CREATINGBOTS
9USINGEXTRASENSORYPERCEPTIONTO
WARDOFFFOGOFWAR
Fogofwar(oftenshortenedtojustfog)isamechanismthatgamedeveloperscommonlyusetolimitaplayer’ssituationalawarenessandhideinformationaboutthegameenvironment.Fogisoftenaliterallackofsightinmassiveonlinebattlearena(MOBA)games,buttheconceptalsoincludesanylackorobscurityofpertinentgameplayinformation.Cloakedfigures,darkrooms,andenemieshidingbehindwallsareallformsoffog.
Gamehackerscanreduceorevencompletelyremovefogusinganextrasensoryperception(ESP)hack.AnESPhackuseshooking,memorymanipulation,orbothtoforceagametodisplayhiddeninformation.Thesehackstakeadvantageofthefactthatsometypesoffogareoftenimplementedontheclientside,asopposedtotheserverside,meaningthatthegameclientsstillcontaininformation(partialorcomplete)aboutwhatisbeinghidden.
Inthischapter,youwilllearnhowtoimplementdifferenttypesofESPhacks.First,you’lllearntolightupdarkenvironments.Next,you’llusex-rayvisiontoseethroughwalls.Finally,you’lllearnaboutzoomhacking,tweakingheads-updisplays,andothersimpleESPhacksthatcanrevealallsortsofuseful(butotherwisehidden)informationaboutthegameyou’replaying.
BackgroundKnowledgeThischapterstartsthetransitionfromhacking,puppeteering,andreverseengineeringtocoding.Fromhereonout,you’llbelearninghowtoactuallycodeyourownhacks.Tokeepontopic,everythingI’vetalkedaboutthusfarwillbetreatedasbackgroundknowledge.Ifyouseeatechniqueusedthatyoudon’tquiteremember,suchasmemoryscanning,settingmemorybreakpoints,hooking,orwritingmemory,flipbacktotherelevantchaptersandstudythemabitmorebeforecontinuing.Throughoutthetext,you’llfindnotestoremindyouwhereyoucanbrushuponcertaintopics.
Specifically,thischapterwilltalkalotaboutDirect3D.In“ApplyingJumpHooksandVFHookstoDirect3D”onpage175,Iexplainedhowtohookintoagame’sDirect3Ddrawingloop.TheexamplecodeforthatchapterincludesafullyfeaturedDirect3DhookingengineinGameHackingExamples/Chapter8_Direct3DHook.Alotofthehacksinthischapterbuildonthathook,andtheirexamplecodecanbefoundinthemain.cppfileoftheDirect3Dhookcode.YoucanrunthecompiledapplicationfromGameHackingExamples/Chapter8_Direct3DApplicationtoseethehacksinactiononatestapplication.
RevealingHiddenDetailswithLighthacksLighthacksincreaselightingindarkenvironments,allowingyoutoclearlyseeenemies,treasurechests,pathways,andanythingelsethatisnormallyobscuredbydarkness.Lightingisoftenacosmeticchangethat’saddedatagame’sgraphicallayer,anditcanusuallybedirectlymodifiedwithahookonthegraphicslayer.
Optimallightingdependsoncameraorientation,environmentlayout,andevenspecifictraitsofagame’sengine,andyoucanmanipulateanyofthesefactorstocreatelighthacks.Buttheeasiestwayissimplytoaddmorelighttoaroom.
AddingaCentralAmbientLightSourceTheonlineresourcesforthisbookincludetwosmalllighthackexamples.ThefirstistheenableLightHackDirectional()functioninmain.cpp,whichis
showninListing9-1.
voidenableLightHackDirectional(LPDIRECT3DDEVICE9pDevice){D3DLIGHT9light;ZeroMemory(&light,sizeof(light));light.Type=D3DLIGHT_DIRECTIONAL;light.Diffuse=D3DXCOLOR(0.5f,0.5f,0.5f,1.0f);light.Direction=D3DXVECTOR3(-1.0f,-0.5f,-1.0f);
pDevice->SetLight(0,&light);pDevice->LightEnable(0,TRUE);}Whenyouknowhowmuchexperienceyou
Listing9-1:Adirectionallighthack
ThiscodeiscalledfromtheEndScene()hook,anditaddslighttothescenebycreatingalightsourcecalledlight.Thecodesetslight.Typetodirectional,whichmeansthelightsourcewillactlikeaspotlightandprojectlightinaspecificdirection.Thecodethensetsthered,green,andbluevaluesoflight.Diffuseto0.5,0.5,and0.5,givingthelightanoff-whiteshinewhenreflectedfromasurface.Next,itsetslight.Directiontoanarbitrarypointinthethree-dimensionalspace.Finally,thecodeusesthegame’sDirect3Ddevicetosetupthelightatindex0andenablelightingeffects.
NOTE
Intheexampleapplication,thelightshinesupandtotherightfromthebottomleftofthescene.Youmayneedtochangethislocationdependingonhowyourtargetgameisrendered.
Notethatinsertingthelightatindex0worksforthisproofofconcept,butitwon’talwayswork.Gamestypicallyhavemultiplelightsourcesdefined,andsettingyourlightatanindexthegameusesmightoverridecriticallightingeffects.Inpractice,youmighttrysettingtheindextoanarbitrarilyhighnumber.There’sanissuewiththistypeoflighthack,though:directionallightswillbeblockedbyobjectssuchaswalls,creatures,andterrain,meaningshadowscanstillbecast.Directionallightsworkgreatforwide-openspaces,butnotsowellfortightlywoundcorridorsorundergroundcaves.
IncreasingtheAbsoluteAmbientLightTheotherlighthackmethod,seenintheenableLightHackAmbient()function,isfarmoreaggressivethantheoneinListing9-1.Itaffectsthelightlevelglobally,ratherthanaddinganextralightsource.Here’swhatthecodelookslike:
voidenableLightHackAmbient(LPDIRECT3DDEVICE9pDevice){pDevice->SetRenderState(D3DRS_AMBIENT,D3DCOLOR_XRGB(100,100,100));}
Thislighthacksetstheabsoluteambientlight(whichyouindicatebypassingD3DRS_AMBIENTtotheSetRenderState()function)toamedium-strengthwhite.TheD3DCOLOR_XRGBmacrosetsthatstrength,taking100asitsparametersforthered,green,andbluelevels.Thislightsupobjectsusinganomnidirectionalwhitelight,effectivelyrevealingeverythingatthecostofshadowsandotherlighting-baseddetails.
CreatingOtherTypesofLighthacksTherearemanyotherwaystocreatelighthacks,buttheydifferfromgametogame.OnecreativewaytoaffectthelightinagameistoNOPthecodethatthegameusestocallthedevice->SetRenderState()function.Sincethisfunctionisusedtosetuptheglobalambientlightstrength,disablingcallstoitleavesDirect3Datthedefaultlightsettingsandmakeseverythingvisible.Thisisperhapsthemostpowerfultypeoflighthack,butitrequiresyourbottoknowtheaddressofthelightingcodetoNOP.
Therearealsomemory-basedlighthacks.Insomegames,playersandcreaturesemitlightofdifferentcolorsandstrengths,oftendependingonattributesliketheirequipment,mount,oractivespells.Ifyouunderstandthestructureofthegame’screaturelist,youcandirectlymodifythevaluesthatdetermineacreature’slightlevel.
Forinstance,imagineagameinwhichcharactersemitabluishballoflightwhenunderahealingorstrengtheningspell.Somewhereinthegame’smemoryarevaluesassociatedwitheachcreaturethattellthegamethecolorandintensityoflightthecreatureshouldemit.Ifyoucanlocatethesevaluesinmemory,youcanchangethemsothatthecreatureseffectivelyemitorbsoflight.Thistypeoflighthackiscommonlyusedingameswitha2Dtop-
oflight.Thistypeoflighthackiscommonlyusedingameswitha2Dtop-downstyle,sincetheorbsaroundindividualcreaturesproduceacoolartisticeffectwhilesheddinglightonimportantpartsofthescreen.In3Dgames,however,thissortofhackjustturnscreaturesintoblobsoflightthatrunaround.
YoucanalsohooktheSetLight()memberfunctionatindex51intheVFtableofthegame’sDirect3Ddevice.Then,wheneveryourhookcallbackisinvoked,youcanmodifythepropertiesoftheinterceptedD3DLIGHT9lightstructurebeforepassingittotheoriginalfunction.Youmight,forinstance,changealllightstotheD3DLIGHT_POINTtype,causinganyexistinglightsourcesinthegametoradiatelightineverydirectionlikealightbulb.Thistypeoflighthackisverypowerfulandaccurate,butitcanproducesomedisturbingvisuals.Italsotendstobreakinanyenvironmentthathasnolighting,andopaqueobstaclesstillblockpointlightsources.
Lighthacksareverypowerful,buttheydon’trevealanything.Ifinformationishiddenbehindanobstacle,ratherthanbydarkness,you’llneedawallhacktorevealit.
RevealingSneakyEnemieswithWallhacksYoucanusewallhackstoshowenemiesthatarehiddenbywalls,floors,andotherobstacles.Thereareafewwaystocreatethesehacks,butthemostcommonmethodtakesadvantageofatypeofrenderingknownasz-buffering.
RenderingwithZ-BufferingMostgraphicsengines,includingDirect3D,supportz-buffering,whichisawaytomakesurethatwhenthereareoverlappingobjectsinascene,onlythetopobjectisdrawn.Z-bufferingworksby“drawing”thescenetoatwo-dimensionalarraythatdescribeshowclosetheobjectateachpixelonthescreenistotheviewer.Thinkofthearray’sindicesasaxes:theycorrespondtothex-axis(rightandleft)andy-axis(upanddown)foreachpixelonthescreen.Eachvaluestoredinthearrayisthez-axisvalueforapixel.
Whenanewobjectappears,whetheritisactuallydrawnonthescreenisdecidedbythez-bufferarray.Ifthespotattheobject’sx-andy-positionisalreadyfilledinthearray,thatmeansthere’sanotherobjectatthatpixelon
alreadyfilledinthearray,thatmeansthere’sanotherobjectatthatpixelonthescreen.Thenewobjectwillappearonlyifithasalowerz-axisvalue(thatis,ifit’sclosertotheviewer)thanthepixelalreadythere.Whenthesceneisfinishedbeingdrawntothearray,itisflushedtothescreen.
Toillustratethis,imagineathree-dimensionalspacethatneedstobedrawntoatwo-dimensionalcanvasbysomegamewith4×4-pixelviewport.Thez-bufferforthisscenariowouldlooklikeFigure9-1.
Figure9-1:Anemptyz-buffer
Tostart,thegamedrawsabluebackgroundthatcompletelyfillstheviewportandislocatedasfarawayonthez-axisaspossible;let’ssaythehighestz-valueis100.Next,thegamedrawsa2×2-pixelredrectangleatposition(0,0)withaz-positionof5.Finally,thegamedrawsa2×2-pixelgreenrectangleatposition(1,1)withaz-positionof3.Thez-bufferwouldnowlooklikeFigure9-2.
Figure9-2:Afilledz-buffer
Thez-bufferneatlyhandledoverlappingobjectsbasedontheirz-positions.Thegreensquarethat’sclosestonthez-axisoverlapstheredsquarethat’sabitfartheraway,andbothsquaresoverlapthebluebackground,whichisveryfaraway.
Thisbehaviorallowsagametodrawitsmap,players,creatures,details,andparticleswithoutworryingaboutwhatisactuallyvisibletotheplayer.Thisisahugeoptimizationforgamedevelopers,butitexposesalargeareaofattack.Sinceallgamemodelsarealwaysgiventothegraphicsengine,youcanusehookstodetectobjectsthattheplayercan’tactuallysee.
CreatingaDirect3DWallhackYoucancreatewallhacksthatmanipulatez-bufferinginDirect3DusingahookontheDrawIndexedPrimitive()function,whichiscalledwhenagamedrawsa3Dmodeltothescreen.Whenanenemyplayermodelisdrawn,a
wallhackofthistypedisablesz-buffering,callstheoriginalfunctiontodrawthemodel,andthenreenablesz-buffering.Thiscausestheenemymodeltobedrawnontopofeverythingelseinthescene,regardlessofwhat’sinfrontofit.Somewallhackscanalsorenderspecificmodelsinasolidcolor,suchasredforenemiesandgreenforallies.
TogglingZ-BufferingTheDirect3Dhookinmain.cppfromGameHackingExamples/Chapter8_Direct3DHookhasthisexamplewallhackintheonDrawIndexedPrimitive()function:
voidonDrawIndexedPrimitive(DirectXHook*hook,LPDIRECT3DDEVICE9device,D3DPRIMITIVETYPEprimType,INTbaseVertexIndex,UINTminVertexIndex,UINTnumVertices,UINTstartIndex,UINTprimCount){if(numVertices==24&&primCount==12){//it'sanenemy,dothewallhack}}
ThisfunctionisusedasacallbackforahookonDrawIndexedPrimitive()atVFindex82ofthegame’sDirect3Ddevice.Everymodelthegamedrawspassesthroughthisfunction,accompaniedbysomemodel-specificproperties.Byinspectingasubsetoftheproperties,namelythenumVerticesandprimCountvalues,thehookdetectswhenanenemymodelisdrawnandcommencesthewallhack.Inthisexample,thevaluesrepresentinganenemymodelare24and12.
Themagichappensinsidetheif()statement.Usingjustafewlinesofcode,thewallhackdrawsthemodelinawaythatignoresz-buffering,likeso:
device->SetRenderState(D3DRS_ZENABLE,false);//disablez-bufferingDirectXHook::origDrawIndexedPrimitive(//drawmodeldevice,primType,baseVertexIndex,minVertexIndex,numVertices,startIndex,primCount);device->SetRenderState(D3DRS_ZENABLE,true);//enablez-buffering
Simplyput,thiscodedisablesz-bufferingwhendrawingtheenemymodelandreenablesitafterward.Withz-bufferingoff,theenemyisdrawninfrontofeverything.
ChanginganEnemyTextureWhenamodelisrenderedonscreen,atextureisusedtoskinthemodel.Texturesare2Dimagesthatarestretchedaround3Dmodelstoapplythecolorsandpatternsthatmakeupthemodel’s3Dartwork.Tochangethewayanenemylookswhenit’sdrawninyourwallhack,youcansetittobedrawnwithadifferenttexture,asinthisexample:
//whenhookinitializesLPDIRECT3DTEXTURE9red;D3DXCreateTextureFromFile(device,"red.png",&red);//justbeforedrawingtheprimitivedevice->SetTexture(0,red);
Thefirstblockofthiscodeloadsthetexturefromafileandisexecutedonlyonce—whenthehookisinitialized.Thefullexamplecodedoesthisinaninitialize()function,whichgetscalledthefirsttimetheEndScene()hookcallbackisinvoked.ThesecondblockofthiscodehappensrightbeforethecalltotheoriginalDrawIndexedPrimitive()functioninthewallhack,anditcausesthemodeltobedrawnwiththecustomtexture.
FingerprintingtheModelYouWanttoRevealThetrickiestparttocreatingagoodwallhackisfindingtherightvaluesfornumVerticesandprimCount.Todothis,youcancreateatoolthatlogseveryuniquecombinationofthetwovariablesandallowsyoutoiterateoverthelistusingyourkeyboard.Workingexamplecodeforthistoolwon’tbeusefulintheexampleapplicationprovidedwiththischapter,butI’llgiveyousomehigh-levelimplementationdetails.
First,intheglobalscope,you’ddeclareastructurethathasmemberstostorethefollowing:
•numVerticesandprimCount
•Astd::setofthisstructure(let’scallitseenParams)
•Aninstanceofthatstructure(let’scallitcurrentParams)
Thestd::setrequiresacomparatorforthisstructure,soyou’dalsodeclareacomparisonfunctorthatcallsmemcmp()tocomparetwoofthestructuresusingmemcmp().EachtimetheDrawIndexedPrimitive()callbackis
invoked,yourhackcouldcreateastructureinstancewiththeinterceptedvaluesandpassittoaseenParams.insert()function,whichshouldinserttheparameterpairintothelistonlyifthepairisn’talreadythere.
UsingtheGetAsyncKeyState()WindowsAPIfunction,youcouldthendetectwhenthespacebarispressedandexecutesomethingsimilartothispseudocode:
autocurrent=seenParams.find(currentParam);if(current==seenParams.end())current=seenParams.begin();elsecurrent++;currentParams=*current;
ThiswouldsetcurrentParamstothenextpairinseenParamswhenthespacebarispressed.Withthiscodeinplace,youcouldusecodesimilartoawallhacktochangethetextureofmodelsmatchingcurrentParams.numVerticesandcurrentParams.primCount.Thetoolcouldalsodrawthosevaluesonthescreensoyoucouldseethemandwritethemdown.
Withatoollikethis,findingthepropermodelsisaseasyasstartingupagameinamodewhereyourcharacterwon’tdie(againstafriend,inacustomizationmode,andsoon),runningthebot,andpressingthespacebaruntileachmodelyouneedishighlighted.Onceyouhavethevaluesforyourtargetmodels,you’llmodifythenumVerticesandprimCountcheckinyourwallhacksoitknowswhichmodelstohighlight.
NOTE
Charactermodelsarecommonlymadeupofsmallermodelsforindividualbodysegments,andgamesoftenshowdifferentmodelsofacharacteratdifferentdistances.Thatmeansagamemayhave20ormoremodelsforonetypeofcharacter.Eveninthatcase,selectingonlyonemodel(say,theenemy’storso)toshowinyourwallhackmaybeenough.
GettingaWiderFieldofVisionwithZoomhacksManygamesintheMOBAandreal-timestrategy(RTS)genresusea3Dtop-downstylethatmakesthemimmunetowallhacks.Theyalsousedarknessonthemapasatypeoffog,butshowingthedarkareasusinga
darknessonthemapasatypeoffog,butshowingthedarkareasusingalighthackdoesn’tgiveanyextrainformation;modelshiddeninsidethefogareknownonlytothegameserver,nottotheclient.
ThisstylemakesmosttypesofESPhacksuseless:there’slittleunknowninformationtoreveal,sothesehacksonlyaugmentyourviewoftheinformationyoucanalreadysee.OnetypeofESPhack,however,canstillbehelpful.Zoomhacksletyouzoomoutmuchfartherthanagamenormallyallows,effectivelyrevealinglargeportionsofthemapthatyoucouldn’tseeotherwise—andthusgettingaroundthegame’swallhackandlighthackimmunity.
UsingNOPingZoomhacksMOBAandRTSgamestypicallyallowplayersavariablebutlimitedamountofzoom.Thesimplesttypeofzoomhackfindsthevalueofthezoomfactor(amultiplierthatchangesasthezoomlevelchanges,typicallyafloatordouble)andoverwritesitwithalargervalue.
Tofindthezoomfactor,fireupCheatEngineandsearchforafloatwithanunknowninitialvalue.(TobrushuponCheatEngine,headoverto“CheatEngine’sMemoryScanner”onpage5.)Forrescans,repeatthefollowingprocessuntilthereareonlyafewvalueslefttofindthezoomfactor:
1. Gotothegamewindowandzoomin.
2. SearchforanincreasedvalueinCheatEngine.
3. Gotothegamewindowandzoomout.
4. SearchforadecreasedvalueinCheatEngine.
Trytogetthevaluelistdowntooneoption.Toconfirmthattheremainingvalueisthezoomfactor,freezeitinCheatEngineandseehowzoombehavesin-game;freezingthepropervaluewilldisablezooming.Ifyoufailtofindthezoomfactorusingafloatsearch,retrythesearchusingadouble.Ifbothsearchesfail,trythemagainbutcorrespondzoominginwithdecreasedvaluesandzoomingoutwithincreasedvaluesinstead.Onceyou’vefoundthezoomfactorinmemory,youcanwriteasmallbottooverwriteittothezoomfactorthatbestsuitsyou.
MoreadvancedzoomhacksNOPthegamecoderesponsibleformakingsurethezoomfactoriswithinasetrange.YoushouldbeabletofindthiscodewithOllyDbg.Setamemoryon-writebreakpointonthezoomfactor,zoomin-gametotriggerthebreakpoint,andinspectthecodeatthebreakpoint.(TohoneyourOllyDbgmemorybreakpointskills,flipto“ControllingOllyDbgThroughtheCommandLine”onpage43.)Youshouldseethecodethatmodifiedthezoomfactor.Zoomlimitationcodeistypicallyeasytospot:constantsthatmatchtheminimumandmaximumzoomvaluesareadeadgiveaway.
Ifyoucan’tfindthelimitationcodeusingthismethod,thenthelimitationmaybeappliedwhenthegraphicsareredrawnatanewzoomlevel,ratherthanwhenthezoomfactorchanges.Inthiscase,switchyourbreakpointtomemoryon-readandlookforthesameclues.
ScratchingtheSurfaceofHookingZoomhacksYoucanalsocreatezoomhacksbyusingaDirect3Dhookonthefunctiondevice->SetTransform(type,matrix),butthisrequiresadeepunderstandingofhowagamesetsuptheplayer’sperspective.Thereareafewdifferentwaystomanageperspective,butyoucontrolzoomlevelusingeitherview(transformtypeD3DTS_VIEW)orprojection(transformtypeD3DTS_PROJECTION).
Properlymanipulatingtransformmatricesthatcontrolviewandprojectionrequiressomeprettyextensiveknowledgeofthemathematicsbehind3Dgraphics,though,soIstayawayfromthismethodatallcosts—andI’veneverhadtroublesimplymanipulatingthezoomfactor.Ifyou’reinterestedinthiskindofhack,though,Irecommendreadinga3Dgameprogrammingbooktolearnmoreabout3Dmathematicsfirst.
Butsometimes,evenazoomhackisn’tenough.Someusefulinformationmayremainhiddenasapartofagame’sinternalstateormaysimplybehardforaplayertodetermineatamoment’sglance.Forthesesituations,aheads-updisplayisthetoolforthejob.
DisplayingHiddenDatawithHUDsAheads-updisplay(HUD)isatypeofESPhackthatdisplayscriticalgameinformationinanoverlay.HUDsoftenresembleagame’sexistinginterface
fordisplayinginformationlikeyourremainingammunition,amini-map,yourcurrenthealthlevel,anyactiveabilitycooldowns,andsoon.HUDstypicallydisplayeitherhistoricaloraggregatedinformation,andthey’remostlyusedonMMORPGs.Theyareoftentextbased,butsomealsocontainsprites,shapes,andothersmallvisualeffects.
TheHUDsyoucancreatedependonwhatdataisavailableinthegame.Commondatapointsarethese:
•Experiencegainperhour(exp/h)
•Creaturekillsperhour(KPH)
•Damagepersecond(DPS)
•Goldlootedperhour(GPH)
•Healingperminute
•Estimatedtimeuntilnextlevel
•Amountofgoldspentonsupplies
•Overallgoldvalueofitemslooted
MoreadvancedcustomHUDsmaydisplaylargetablescontainingitemslooted,suppliesused,thenumberofkillsforeachtypeofcreature,andthenamesofplayersthathaverecentlybeenseen.
Beyondwhatyou’vealreadylearnedaboutreadingmemory,hookinggraphicsengines,anddisplayingcustomizeddata,there’snotmuchelseIcanteachyouabouthowtocreateaHUD.Mostgameshaveasimpleenougharchitecturethatyoucaneasilyobtainmostoftheinformationyouneedfrommemory.Then,youcanrunsomebasichourly,percentage,orsummationcalculationstogetthedataintoausableformat.
CreatinganExperienceHUDImagineyouwantaHUDthatdisplaysyourcurrentlevel,hourlyexperience,andhowlongyou’llhavetoplaybeforeyourcharacterlevelsup.First,youcoulduseCheatEnginetofindthevariablesthatcontainyourlevelandexperience.Whenyouknowthosevalues,youcanuseeitheragame-specificalgorithmorahardcodedexperiencetabletocalculatetheexperiencerequiredtoreachthenextlevel.
Whenyouknowhowmuchexperienceyouneedtolevelup,youcancalculateyourhourlyexperience.Putintopseudocode,thatprocessmightlooklikethis:
//thisexampleassumesthetimeisstoredinmilliseconds//forseconds,removethe"1000*"timeUnitsPerHour=1000*60*60timePassed=(currentTime-startTime)➊timePassedToHourRatio=timeUnitsPerHour/timePassed➋expGained=(currentExp-startExp)hourlyExp=expGained*timePassedToHourRatio
➌remainingExp=nextExp-currentExp➍hoursToGo=remainingExp/hourlyExp
Tofindyourhourlyexperience,hourlyExp,you’dstoreyourexperienceandthetimewhenyourHUDfirststarts;thesearestartExpandstartTime,respectively.ThisexamplealsoassumescurrentLevelandcurrentExparepreviouslydefined,wherecurrentLevelisthecharacter’slevelandcurrentExpisthecurrentamountofexperience.
Withthesevalues,hourlyExpcanbecalculatedbymultiplyingaratio➊ofthetimeunitsinanhourtothetimethathaspassedbytheexperiencegainedsincestartTime➋.Inthiscase,thetimeunitisamillisecond,sothetimeunitsgetmultipliedby1,000.
Next,currentExpissubtractedfromnextExptodeterminetheremainingexperience➌tolevelup.Tocalculatehowmanyhoursarelefttolevelup,yourremainingexperienceisdividedbyyourhourlyexperience➍.
Whenyouhaveallthisinformation,youcanfinallydisplayitonscreen.UsingtheDirect3Dhookingengineprovidedinthisbook’sexamplecode,you’ddrawthetextusingthiscallinsidetheEndScene()hookcallback:
hook->drawText(10,10,D3DCOLOR_ARGB(255,255,0,0),"Willreachlevel%din%0.20fhours(%dexpperhour)",currentLevel,hoursToGo,hourlyExp);
That’sallyouneedforaworking,experience-trackingHUD.VariationsofthesesameequationscanbeusedtocalculateKPH,DPS,GPH,andotherusefultime-basedmeasures.Furthermore,youcanusethedrawText()
functionoftheDirect3Dhooktodisplayanyinformationyoucanlocateandnormalize.ThehookalsocontainsaddSpriteImage()anddrawSpriteImage()functionsthatyoucanusetodrawyourowncustomimages,allowingyoutomakeyourHUDsasfancyasyouwant.
UsingHookstoLocateDataMemoryreadingisn’ttheonlywaytogetdataforacustomHUD.YoucanalsogatherinformationbycountingthenumberoftimesaspecificmodelisdrawnbytheDrawIndexedPrimitive()function,hookingthegame’sinternalfunctionsresponsiblefordrawingcertaintypesoftext,oreveninterceptingfunctioncallsresponsibleforprocessingdatapacketsfromthegameserver.Themethodsyouusetodothiswillbedrasticallydifferentforeverygame,andfindingthosemethodswillrequireyoutopaireverythingyou’velearnedfromthisbookwithyourowningenuityandprogramminginstincts.
Forinstance,tocreateaHUDthatdisplayshowmanyenemiesareonthemap,youcouldusethemodel-fingerprintingmethodsusedbywallhackstocountthenumberofenemiesandoutputthatnumbertothescreen.Thismethodisbetterthancreatingawaytoreadthelistofenemiesfrommemory,sinceitdoesn’trequirenewmemoryaddresseseverytimethegamepatches.
Anotherexampleisdisplayingalistofenemycooldowns,whichwouldrequireyoutointerceptincomingpacketsthattelltheclientwhichspelleffectstodisplay.Youcouldthencorrelatecertainspellswithcertainenemiesbasedonspellandenemylocation,spelltype,andsoon,andusethatinformationtotrackspellseachenemyhasused.Ifyoucorrelatethedatawithadatabaseofcooldowntimes,youcandisplayexactlywheneachenemyspellcanbeusedagain.Thisisespeciallypowerfulbecausemostgamesdon’tstoreenemycooldownsinmemory.
AnOverviewofOtherESPHacksInadditiontothehacksdiscussedinthischapter,thereareanumberofESPhacksthatdon’thavecommonnamesandarespecifictocertaingenresorevencertaingames.I’llquicklytakeyouthroughthetheory,background,andarchitectureofsomeofthesehacks.
RangeHacksRange hacks use a method similar to wallhacks to detect when themodelsfordifferenttypesofchampionsorheroesaredrawn.Thentheydrawcirclesonthegroundaroundeachheromodel.Theradiusofeachcirclecorrespondstothemaximumattackrangeofthechampionorheroitsurrounds,effectivelyshowingyouareaswhereyoucanbedamagedbyeachenemy.
Loading-ScreenHUDsLoading-screen HUDs are common in MOBA and RTS games thatrequireallplayerstositthroughaloadingscreenwhileeveryone’sgameis starting up.These hacks take advantage of the fact that such gamesoftenhavewebsiteswherehistoricalplayerstatisticscanbequeried.Youcanwriteabotthatautomaticallyqueriesthestatisticsofeachplayerinthegameandseamlesslydisplaysthe informationasanoverlayonyourloadingscreen,allowingyoutostudyyourenemiesbeforelaunchingintobattle.
Pick-PhaseHUDsPick-phaseHUDs are similar to their loading-screen cousins, but theyare displayed during the pregame phase when each player is picking achampion or hero to play. Instead of showing enemy statistics, pick-phase HUDs show statistics about allies. This allows you to quicklyassessthestrengthsandweaknessesofyouralliessoyoucanmakebetterdecisionsaboutwhichcharactertoplay.
FloorSpyHacksFloor spy hacks are common in older 2D top-down games that havedifferent distinct floors or platforms. If you’re on the top floor, youmightwanttoknowwhat’sgoingondownstairsbeforeyougochargingin. You can write floor spy hacks that modify the current floor value(typically an unsigned int) to a different floor above or below you,allowingyoutospyonotherfloors.
Gamesoftenrecalculatethecurrentfloorvalueeveryframebasedonplayerposition,soNOPsaresometimesrequiredtokeepthevaluefrombeingreseteverytimeaframeisredrawn.FindingthecurrentfloorvalueandthecodetoNOPwouldbesimilartofindingthezoomfactor,as
discussedin“UsingNOPingZoomhacks”onpage197.
ClosingThoughtsESPhacksarepowerfulwaystoobtainextrainformationaboutagame.SomeofthemcanbedoneprettyeasilythroughDirect3Dhooksorsimplememoryediting.Othersrequireyoutolearnaboutagame’sinternaldatastructuresandhookproprietaryfunctions,givingyouareasontoemployyourreverseengineeringskills.
IfyouwanttoexperimentwithESPhacks,studyandtweaktheexamplecodeforthischapter.ForpracticewithmorespecificESPhacks,Iencourageyoutogooutandfindsomegamestoplayaroundwith.
10RESPONSIVEHACKS
Theaveragegamerhasareactiontimeof250milliseconds,oraquarterofasecond.Professionalgamersaverageafifthofasecond,butsomecanreactinasixthofasecond.Thesefiguresarebasedononlineteststhatmeasureplayers’reactiontimestosingular,predictableevents.Inactualgames,though,playersmustreacttodozensofdifferentevents,likehealthloss,incomingskillshots,abilitiescomingoffofcooldown,enemyattacks,andmanyothers.Onlyveryskilledgamerscanmaintainafourth-orfifth-of-a-secondreactiontimeinsuchdynamicenvironments;theonlywaytobefasteristobeacomputer.
Inthischapter,you’lllearnhowtomakebotsthatreactfasterthananyplayer.First,I’llshowyousomecodepatternsyoucanincorporateintoabottodetectwhencertaineventshappenwithinagame.Next,you’lllearnhowtomakeabotthatmovesyourcharacter,heals,orcastsspellsallonitsown.Onceyou’veexploredthosefundamentaltechniques,I’llhelpyoutiethemtogethertoimplementsomeofthemostcommon,andmostpowerful,responsivehacks.
ObservingGameEventsWithinjustafewsecondsofplayingagame,mostpeoplecanmakeessentialobservationsaboutthegameenvironment.Youcanclearlyseewhenmissilesareflyingtowardyourcharacter,whenyourhealthistoolow,andwhen
areflyingtowardyourcharacter,whenyourhealthistoolow,andwhenabilitiescomeoffofcooldown.Forabot,though,theseseeminglyintuitiveobservationsarenotaseasytomake.Thebotmustdetecteacheventbylookingforchangesinmemory,detectingvisualcues,orinterceptingnetworktraffic.
MonitoringMemoryTodetectsimpleevents,suchasyourhealthbardroppinglow,youcanprogramabottoperiodicallyreadyourhealthfrommemoryandcompareittosomeminimumacceptablevalue,asinListing10-1.
//dothisevery10milliseconds(100timesasecond)autohealth=readMemory<int>(HEALTH_ADDRESS);if(health<=500){//somecodetotellthebothowtoreact}
Listing10-1:Anifstatementthatcheckshealth
Giventheaddressofyourcharacter’shealth,youcancheckthevaluethereasoftenasyouneed;every10millisecondsistypicallyagoodrate.(FlipbacktoChapter1ifyouneedarefresheronlocatingvaluesinmemory.)Oncehealthdropsbelowacertainvalue,you’llwanttorunsomereactioncodetocastahealingspellordrinkapotion.I’lltalkabouthowyoucandothislaterinthechapter.
Ifyouwantyourbottohavemoregranularinformationandthechanceforagreatervarietyofresponses,youcanprogramittoreacttoanychangeinhealth,insteadofonlyafterasetthreshold.Todoso,changethecodeinListing10-1tocompareyourcurrenthealthtotheamountyouhadduringthepreviousexecution,asfollows:
//stilldothisevery10millisecondsstaticintpreviousHealth=0;autohealth=readMemory<int>(HEALTH_ADDRESS);if(health!=previousHealth){if(health>previousHealth){//reacttoincrease}else{//reacttodecrease}previousHealth=health;}
Now,thiscodeusesastaticvariablecalledpreviousHealthtotrackthevalueofhealthonthepreviousiteration.IfpreviousHealthandhealthdiffer,thebotnotonlyreactstothechangeinhealthbutalsoreactsdifferentlytohealthincreasesanddecreases.Thistechniqueisthesimplest,andmostcommon,waytoreacttochangesinagamestate.Withthepropermemoryaddresses,youcanusethiscodepatterntoobservechangesinhealth,mana,abilitycooldowns,andothercriticalinformation.
DetectingVisualCuesHealthisrelativelysimpleforabottocheckbecauseit’sjustanumber,butsomegameelementshavetoberelayedtothebotdifferently.Forexample,whenstatusailmentsorbuffsareaffectingacharacter,theeasiestwayforyoutotellistosimplylookforanonscreenstatusindicator,andthesameistrueforbots.
Whenreadingmemoryisn’tenough,youcandetectcertaineventsbyhookingagame’sgraphicsengineandwaitingforthegametorenderaspecificmodel.(Referbackto“ApplyingJumpHooksandVFHookstoDirect3D”onpage175and“CreatingaDirect3DWallhack”onpage194togetrefreshedonDirect3Dhooks.)Whenthemodelisdrawn,youcanqueueupareactiontobeexecutedaftertheframeisdrawn,likethis:
//belowisthedrawIndexedPrimitivehookvoidonDrawIndexedPrimitive(...){if(numVertices==EVENT_VERT&&primCount==EVENT_PRIM){//react,preferablyafterdrawingisdone}}
Usingthesamemodel-fingerprintingtrickasthewallhackcodeinChapter9,thiscodedetectswhenaspecificmodelisdrawntothescreenandreactsaccordingly.Thiscodereactstotheeventeverysingleframe,though,andthatcanmakeyourgameunplayable.You’llprobablywantsomeinternalcooldowntoavoidspammingareaction.Incaseswheretheindicatormodelispersistentlydrawn(thatis,notblinking),youcanactuallytrackitacrossframestodeterminewhenitappearsanddisappears.
Here’sacodesnippetthatalsohandlestracking:
booleventActive=false;booleventActiveLastFrame=false;
//belowisthedrawIndexedPrimitivehookvoidonDrawIndexedPrimitive(...){if(numVertices==EVENT_VERT&&primCount==EVENT_PRIM)eventActive=true;}
//belowistheendScenehookvoidonDrawFrame(...){if(eventActive){if(!eventActiveLastFrame){//reacttoeventmodelappear}eventActiveLastFrame=true;}else{if(eventActiveLastFrame){//reacttoeventmodeldisappear}eventActiveLastFrame=false;}eventActive=false;}
TheonDrawIndexedPrimitive()functionstillcheckswhetheracertainmodelwasdrawn,butnow,twoBooleanstrackwhetherthemodelwasdrawnthisframeorthepreviousframe.Then,whentheframeiscompletelydrawn,thebotcancheckthesevariablesandreacttothemodeleitherappearingordisappearing.
Thismethodworksgreatfordetectingvisualstatusindicatorsthatappearonlywhenyourcharacterisaffectedbystuns,movementslows,snares,poisons,andsoon.YoucanalsouseittodetectwhenenemiesappearanddisappearinMOBAandRTSgames,asthesegamesdrawonlyenemiesthatareexplicitlyinthesightrangeofanalliedunitorplayer.
InterceptingNetworkTrafficOneofthemostreliablewaystoobserveeventsisthesamewaythegameclientdoes:bywaitingforthegameservertotellyouthattheyoccurred.Inthistypeofcommunication,thegameserversendsbytearrayscalledpacketsoverthenetworktotheclient,usingsockets.Thepacketsaretypicallyencryptedandcontainblobsofdataserializedthroughaproprietaryformat.
ATypicalPacket-ParsingFunctionToreceiveandprocesspackets,agameclientdoessomethinglikeListing
10-2beforeitdrawsaframe.
voidparseNextPacket(){if(!network->packetReady())return;
autopacket=network->getPacket();autodata=packet->decrypt();switch(data->getType()){casePACKET_HEALTH_CHANGE:onHealthChange(data->getMessage());break;casePACKET_MANA_CHANGE:onManaChange(data->getMessage());break;//morecasesformorepackettypes}}
Listing10-2:Asimplifiedlookathowagameparsespackets
Theexactcodeforanyparticulargamemightlookdifferent,butthecontrolflowisalwaysthesame:receiveapacket,decryptit,decidewhatkindofmessageitcontains,andcallafunctionthatknowswhattodowithit.Somegamehackersinterceptrawnetworkpacketsandreplicatethisfunctionalityintheirbots.Thistechniqueworks,butitrequiresextensiveknowledgeofencryption,acompleteunderstandingofhowthegamestoresdatainsideapacket,theabilitytoman-in-the-middlethenetworkconnection,andawaytolocatethedecryptionkeysbeingusedbythegameclient.
Hookingthefunctionsresponsibleforhandlingthepacketsaftertheyaredecryptedandparsedisamuchbetterapproach;inListing10-2,thosefunctionsaretheonHealthChange()andonManaChange()functions.Thismethodleveragesthegame’sinherentabilitytoprocesspackets,allowingabottoremainignorantofthevariousnetworkfacilitiesthegameuses.Italsogivesyoudiscretionoverwhichnetworkdatayouintercept,asyouneedtohookonlythehandlersthatmeetyourneeds.
NOTE
Interceptingentirepacketscansometimesbeadvantageous—forexample,inanygamethatusesAdobeAIRandcommunicatesusingRTMPS.SinceRTMPSissoheavilydocumented,there’snoneedtoreverseengineertheformatorencryption.Chapter8explainshowtohookRTMPSindetail.
Thereareafewtricksyoucanusetoeasilyfindtheparserfunctionand,ultimately,theswitch()statementthatdispatchespacketstotheirhandlers.ThemostusefulmethodI’vefoundistoplaceabreakpointonthefunctionthegameusestoreceivedatafromthenetwork,andthenanalyzetheflowoftheapplicationwhenthebreakpointishit.
Let’swalkthroughhowyoumightdothiswithOllyDbgattachedtoyourtargetgame.InWindows,recv()istheAPIfunctiontoreceivedatafromasocket.FromtheOllyDbgcommandline,youcansetabreakpointonrecv()byenteringthebprecvcommand.Whenthebreakpointishit,youcanclimbthecallstackusingCTRL-F9,theshortcutforexecuteuntilreturn,andF8,theshortcutforstepover.Thiscombinationessentiallyletstheprogramexecuteuntilthecalleehasreturnedtothecaller,allowingyoutoclimbthecallstackintandemwiththegame.Ateachstacklevel,youcaninspectthecodeofeachcalleruntilyoufindonethathasabigswitch()statement;thisshouldbethepacketparser.
ATrickierParserDependingonthegame’sarchitecture,though,findingtheparserfunctionmaynotbethatsimple.Consideragamewithaparserfunctionthatlookslikethis:
packetHandlers[PACKET_HEALTH_CHANGE]=onHealthChange;packetHandlers[PACKET_MANA_CHANGE]=onManaChange;
voidparseNextPacket(){if(!network->packetReady())return;autopacket=network->getPacket();autodata=packet->decrypt();autohandler=packetHandlers[data->getType()];handler->invoke(data->getMessage());}
SincetheparseNextPacket()functiondoesn’thaveaswitch()statement,there’snoobviouswaytoidentifyitinmemory.Unlessyoupayverycloseattention,you’lllikelyclimbrightpastitonthecallstack.Whenagamehasaparserfunctionlikethis,tryingtofigureoutwhattheparserfunctionlookslikemightbepointless.Ifyoudon’tseeaswitch()statementwhenclimbing
therecv()callstack,you’llhavetonoteeverycalleeonthecallstackinstead.Insteadofclimbingupthecallstackfromthebreakpoint,you’dgoto
everyaddressmarkedasaRETURNbelowESPintheOllyDbgstackpane.Thesearethereturnaddressesintoeachcallerforeachcallee.Ateachreturnaddress,you’dneedtofindthetopofthecallerinOllyDbg’sdisassemblypaneandnotetheaddress.Asaresult,you’dhavealistofeveryfunctioncallleadinguptotherecv()call.
Next,you’drepeatthesamelist-makingprocessfrombreakpointsplacedonafewofthegame’shandlerfunctions.Youcanfindahandlerfunctionbymonitoringmemorythatitwillinevitablyuse.Thehandlerforahealthchangepacket,forinstance,willupdateyourhealthinmemory.UsingOllyDbg,youcansetamemoryonwritebreakpointtothehealthaddress.Whenthebreakpointgetstriggered,itmeansthegameupdatedthehealthvaluefromahandlerfunction.Thisshouldworkthesamewayformostvaluesthatarecontrolledbytheserver.Theserverwillcontrolanygame-criticalvalues,suchashealth,mana,level,items,andsoon.
Onceyou’verecordedthecallstackfromrecv()andafewhandlerfunctions,youcancorrelatethemtolocatetheparserfunction.Forexample,considerthethreepseudo–callstacksinTable10-1.
Table10-1:Pseudo–CallStacksforThreePacket-RelatedFunctions
recv()stack onHealthChange()stackonManaChange()stack
0x0BADF00D 0x101E1337 0x14141414
0x40404040 0x50505050 0x60606060
0xDEADBEEF0xDEADBEEF 0xDEADBEEF
0x30303030 0x30303030 0x30303030
0x20202020 0x20202020 0x20202020
0x10101010 0x10101010 0x10101010
Thesestacksshowwhatmemorymightlooklikeduringacalltorecv()andtoagame’shypotheticalonHealthChange()andonManaChange()functions.Noticethateachfunctionoriginatesfromachainoffourcommonfunctioncalls(showninboldface).Thedeepestcommonaddress,0xDEADBEEF,istheaddressoftheparser.Forabetterunderstandingofthisstructure,lookat
thecallstackslaidoutinatreeview,asinFigure10-1.
Figure10-1:Treeviewofourthreecallstacks
Eachfunction’scallstackbranchesoutfromthefunctionat0xDEADBEEF,meaningthatfunctionisacommonpointoforiginforallthreecalls.TheexampleparseNextPacket()functionisresponsibleforcallingthesefunctions,soitmustbethemostrecentcommonancestorat0xDEADBEEF.
NOTE
Thesecallstacksarehypothetical,andthey’resimplifiedbeyondwhatyou’lltypicallyencounter.Realcallstackswillprobablyhavequiteafewmorefunctioncalls,andcomparingthemwon’tbeaseasy.
AHybridParsingSystemAthirdvariationoftheparsingloopmightbeahybridoftheprevioustwothatusesaswitch()statementafterafunctioncall.Here’sanotherhypotheticalfunction:
voidprocessNextPacket(){if(!network->packetReady())return;autopacket=network->getPacket();
autodata=packet->decrypt();dispatchPacket(data);}
voiddispatchPacket(data){switch(data->getType()){casePACKET_HEALTH_CHANGE:processHealthChangePacket(data->getMessage());break;casePACKET_MANA_CHANGE:processManaChangePacket(data->getMessage());break;//morecasesformoredatatypes}}
TheprocessNextPacket()functionfetchesanewpacketandcallsdispatchPacket()tohandlethedata.Inthiscase,thedispatchPacket()functionexistsinthecallstackofeachhandler,butnotintheonefortherecv()function.LookatthehypotheticalstacksinTable10-2,forexample.
Table10-2:Pseudo–CallStacksforThreePacket-RelatedFunctions
recv()stack onHealthChange()stackonManaChange()stack
0x0BADF00D 0x101E1337 0x14141414
0x40404040 0x00ABCDEF 0x00ABCDEF
0xDEADBEEF0xDEADBEEF 0xDEADBEEF
0x30303030 0x30303030 0x30303030
0x20202020 0x20202020 0x20202020
0x10101010 0x10101010 0x10101010
Althoughthesethreefunctionshavethesamefirstfouraddressesintheircallstacks,onlythetwohandlershaveonemoreaddressincommon(againshowninboldface).That’s0x00ABCDEF,andit’stheaddressofthedispatchPacket()function.Onceagain,youcanimaginetheselaidoutinatreeview,asinFigure10-2.
Figure10-2:Treeviewofourthreecallstacks
AParserHackOnceyou’velocatedthefunctionresponsiblefordispatchingpacketstotheirhandlers,you’llbeabletospoteveryhandlerthatcanbecalled.Youcandeduceahandler’spurposebyplacingabreakpointonitandwatchingwhatvalueschangeinmemorywhenitexecutes.Then,youcanhookanyhandlersthatyourbotneedstoreactto.(FlipbacktoChapter8ifyouneedarefresheronhowyoumighthookthesefunctions.)
Ofcourse,thereareendlesswaystoimplementnetworkbehavior.Ican’tcoverthemall,butseeingthesethreecommontechniquesshouldhelpyouunderstandthemethodology.Nomatterwhatgameyou’redealingwith,abreakpointonrecv()shouldbeastepintherightdirection.
PerformingIn-GameActionsBeforeabotcanreacttoevents,youhavetoteachittoplaythegame.Itneedstobeabletocastspells,movearound,andactivateitems.Onthisfront,botsaren’tmuchdifferentfrompeople:theycanjustbetoldwhichbuttonstopress.Pressingbuttonsissimpleandsufficesinmanycases,butinmoreintricatesituations,abotmayhavetocommunicateonthenetworkandtelltheserverwhatit’stryingtodo.
Tofollowalongwiththeexamplesinthissectionandexploreonyourownafterward,openthefilesintheGameHackingExamples/Chapter10_
ResponsiveHacks/folderinthisbook’sresourcefiles.
EmulatingtheKeyboardThemostcommonbuttonsyou’llpressinagamearekeyboardkeys,andthereareacoupleofwaysyoucanteachyourbottotype.
TheSendInput()FunctionOnecommonwaytoemulatethekeyboardiswiththeSendInput()WindowsAPIfunction.Thisfunction,whichsendskeyboardandmouseinputtothetopmostwindow,hasthefollowingprototype:
UINTSendInput(UINTinputCount,LPINPUTinputs,intsize);
Thefirstparameter,inputCount,isthenumberofinputsbeingsent.Fortheexamplesinthisbook,I’llalwaysuseavalueof1.Thesecondparameter,inputs,isapointertoastructure(oranarrayofstructureswhoselengthmatchestheinputCountvalue)withthepredefinedtypeINPUT.Thefinalparameteristhesizeofinputsinmemory,ascalculatedwiththeformulasize=inputCount×sizeof(INPUT).
TheINPUTstructuretellstheSendInput()functionwhattypeofinputtosend,andthefollowingcodeshowshowyoumightinitializeaninstanceofINPUTtopresstheF1key:
INPUTinput={0};input.type=INPUT_KEYBOARD;input.ki.wVk=VK_F1;
TohaveyourbotactuallypressF1,you’dneedtosendthisinputtwice,likeso:
SendInput(1,&input,sizeof(input));//changeinputtokeyupinput.ki.dwFlags|=KEYEVENTF_KEYUP;SendInput(1,&input,sizeof(input));
ThefirstcalltoSendInput()pressesF1,andthesecondreleasesit.Thereleasehappensnotbecausetheinputwassenttwice,butbecausethesecondcallwasmadewiththeKEYEVENTF_KEYUPflagenabledintheinputparameter’skeyboardflagsfield.Sincesettingupinputforevenasinglekeyisabit
messy,it’sbesttowrapeverythinginsideafunction.TheresultlookssomethinglikeListing10-3.
voidsendKeyWithSendInput(WORDkey,boolup){INPUTinput={0};input.type=INPUT_KEYBOARD;input.ki.wVk=key;input.ki.dwFlags=0;
if(up)input.ki.dwFlags|=KEYEVENTF_KEYUP;SendInput(1,&input,sizeof(input));}sendKeyWithSendInput(VK_F1,false);//presssendKeyWithSendInput(VK_F1,true);//release
Listing10-3:AwrapperforemulatingkeystrokeswithSendInput()
Thisfunctioninitializesinputwiththegivenkey,enablestheflagKEYEVENTF_KEYUPifupisset,andcallstheSendInput()function.ThismeanssendKeyWithSendInput()mustbecalledasecondtimetosendthekeyrelease,eventhoughthereleaseisalwaysrequired.ThefunctioniswrittenthiswaybecausekeycombinationsthatinvolvemodifierslikeSHIFT,ALT,orCTRL
mustbesentabitdifferently;themodifier’spressmustcomebeforethekey’spress,butitsreleasemustcomeafterthekey’srelease.
Thefollowingcodeshowshowyou’dusethesendKeyWithSendInput()functiontotellabottopressSHIFT-F1:
sendKeyWithSendInput(VK_LSHIFT,false);//pressshiftsendKeyWithSendInput(VK_F1,false);//pressF1sendKeyWithSendInput(VK_F1,true);//releaseF1sendKeyWithSendInput(VK_LSHIFT,true);//releaseshift
You’dhavetocallsendKeyWithSendInput()fourtimes,butthat’sstilleasierthanusingthecodewithoutawrapperfunction.
TheSendMessage()FunctionAnalternativemethodforsendingkeystrokesreliesontheSendMessage()WindowsAPIfunction.Thisfunctionallowsyoutosendinputtoanywindow,evenifit’sminimizedorhidden,bypostingdatadirectlytothetargetwindow’smessagequeue.Thisadvantagemakesitthemethodof
choiceforgamehackers,becauseitenablesuserstodootherthingswhiletheirbotplaysthegameinthebackground.SendMessage()hasthefollowingprototype:
LRESULTSendMessage(HWNDwindow,UINTmessage,WPARAMwparam,LPARAMlparam);
Thefirstparameter,window,isahandletothewindowthattheinputisbeingsentto.Thesecondparameter,message,isthetypeofinputbeingsent;forkeyboardinput,thisparameterisWM_KEYUP,WM_KEYDOWN,orWM_CHAR.Thethirdparameter,wparam,shouldbethekeycode.Thefinalparameter,lparam,shouldbe0whenthemessageisWM_KEYDOWNand1otherwise.
BeforeyoucanusetheSendMessage()function,youmustobtainahandletothetargetprocess’smainwindow.Giventhetitleofthewindow,youcanobtainahandleusingtheFindWindow()WindowsAPIfunction,asfollows:
autowindow=FindWindowA(NULL,"TitleOfGameWindow");
Withavalidwindowhandle,makingacalltoSendMessage()lookssomethinglikethis:
SendMessageA(window,WM_KEYDOWN,VK_F1,0);SendMessageA(window,WM_KEYUP,VK_F1,0);
ThefirstcallpressestheF1key,andthesecondcallreleasesit.Keepinmind,however,thatthisseriesofcallsworksonlyforkeysthatdon’tinputtext,likeF1,INSERT,orTAB.Tohaveyourbotpresskeysthatinputtext,youmustalsosendaWM_CHARmessagebetweenthedownandupmessages.TotypeW,forinstance,you’ddosomethinglikethis:
DWORDkey=(DWORD)'W';SendMessageA(window,WM_KEYDOWN,key,0);SendMessageA(window,WM_CHAR,key,1);SendMessageA(window,WM_KEYUP,key,1);
Thiscreatesakeyvariablesotheletterkeytopresscanbechangedeasily.ThenitfollowsthesamestepstheF1exampleused,justwithaWM_CHARmessageinbetween.
NOTE
YoucanactuallysendnothingbuttheWM_CHARmessageandgetthesameresult,butit’sbestpracticetosendallthreemessages.GamedeveloperscaneasilyshutdownbotsbypatchingthegametoignoreWM_CHARmessagesthatdon’tfollowWM_KEYDOWN,andtheycanevenuseitasawaytodetectyourbotandbanyou.
AsIshowedwiththeSendInput()technique,youcancreateawrapperaroundthisfunctionalitytomakeyourbotcodeeasiertoworkwith.Thewrapperlookssomethinglikethis:
voidsendKeyWithSendMessage(HWNDwindow,WORDkey,charletter){SendMessageA(window,WM_KEYDOWN,key,0);if(letter!=0)SendMessageA(window,WM_CHAR,letter,1);SendMessageA(window,WM_KEYUP,key,1);}
UnlikeListing10-3,thiswrapperactuallysendsboththepressandrelease.ThisisbecauseSendMessage()can’tbeusedtosendkeystrokeswithmodifiers,sothere’sneveranyneedtoinsertcodebetweenthetwocalls.
NOTE
Therearemultiplewaysagamemightcheckwhetheramodifierkeyispressed,though.YoumightbeabletosendmodifierkeystocertaingamesbycallingtheSendMessage()function,butitdependsonhowthosegamesdetectmodifiers.
YoucanusethiswrapperinasimilarwayastheoneinListing10-3.Forexample,thiscodesendsF1followedbyW:
sendKeyWithSendMessage(window,VK_F1,0);sendKeyWithSendMessage(window,'W','W');
Thisexample,likealloftheSendMessage()codeI’veshownsofar,simplygetsthejobdone.Itcaninputtext,butitdoesn’texactlysendpropermessages.
Therearealotofsmalldetailsyouhavetogetrightifyouwanttosend100percentvalidmessageswiththeSendMessage()function.Forinstance,
thefirst16bitsoflparamshouldstorethenumberoftimesthekeyhasbeenautomaticallyrepeatedasaresultofbeinghelddown.Thenext8bitsshouldstorethescancode,akeyidentifierthatisspecifictoeachkeyboardmanufacturer.Thenextbit,number24,shouldbesetonlyifthebuttonisonanextendedpartofthekeyboard,suchasthenumberpad.Thefollowing4bitsareundocumented,andthenextbitshouldbesetonlyiftheALTkeywasdownwhenthemessageoriginated.Thelast2bitsarethepreviousstateflagandthetransitionstateflag.Thepreviousstateflagissetonlyifthekeywaspreviouslydown,andthetransitionstateissetonlyifthekeywaspreviouslyinthestateoppositeitscurrentposition(thatis,ifthekeyisnowupandwaspreviouslydown,orviceversa).
Thankfully,theaveragegamedoesn’tconsidermostofthesevalues.Forthatmatter,theaveragepieceofsoftwaredoesn’tcareaboutthemeither.Ifyouhavetofillallofthesevalueswithproperdatatomakeyourbotwork,you’removinginthewrongdirection.Therearemanyotherwaystoperformactions,themajorityofwhicharesimplerthantryingtoemulatetheexactbehavioroftheoperatingsystem’skernel-levelkeyboardinputhandler/dispatcher.Infact,there’salreadyafunctionthatdoesthat,andI’vealreadytalkedaboutit:theSendInput()function.
YoucanalsocontrolthemousewiththeSendInput()andSendMessage()functions,butIhighlyrecommendavoidingit.Anymousecommandsyousendwillaffect,andbeaffectedby,anylegitimatemousemovements,mouseclicks,orkeystrokessentbytheplayer.Thesameistrueforkeyboardinput,butthecomplicationsaremuchrarer.
SendingPacketsBeforeagamedrawsaframe,itchecksforkeyboardandmouseinput.Whenitreceivesinputthatresultsinanaction,suchasmovingaroundorcastingaspell,itcheckstomakesuretheactionispossibleand,ifso,tellsthegameserverthattheactionhasbeenperformed.Thegamecodetocheckforeventsandalerttheserveroftenlookssomethinglikethis:
voidprocessInput(){do{autoinput=getNextInput();if(input.isKeyboard())processKeyboardInput(input);//handleotherinputtypes(e.g.,mouse)
}while(!input.isEmpty());}voidprocessKeyboardInput(input){if(input.isKeyPress()){if(input.getKey()=='W')step(FORWARD);elseif(input.getKey()=='A')step(BACKWARD);//handleotherkeystrokes(e.g.,'S'and'D')}}voidstep(intdirection){if(!map->canWalkOn(player->position))return;playerMovePacketpacket(direction);network->send(packet);}
TheprocessInput()functioniscalledeveryframe.Thefunctioniteratesoverallpendinginputsanddispatchesdifferenttypesofinputstotheirrelevanthandlers.Inthiscase,whenkeyboardinputisreceived,it’sdispatchedtotheprocessKeyboardInput()function.ThishandlerthencheckswhetherthekeyiseitherWorS,and,ifso,callsstep()tomovetheplayerinthecorrespondingdirection.
Sincestep()isusedtoperformanaction,itiscalledanactorfunction.Theinvocationofanactorfunctioniscalledactuation.Youcandirectlycallagame’sactorfunctionsfromyourbottoperformanactionwhilecompletelybypassingtheinputlayer.
Beforeyoucancallanactor,though,youmustfinditsaddress.Todothis,youcanattachOllyDbgtothegame,openthecommandline,andenterbpsend.Thiswillplaceabreakpointonthesend()function,whichisusedtosenddataoverthenetwork.Whenyouplaythegame,everytimeyoutakeastep,castaspell,pickuploot,ordoanythingelse,yourbreakpointshouldtrigger,andyoucannoteeachfunctioninthecallstack.
NOTE
Thegameshouldcallsend()everytimeyoudoanythingwhileplaying.Payattentiontowhatyoudidbeforeeachsend()breakpointishit,asthatwillgiveyouaroughideaofwhatactioneachcalliscommunicatingtotheserver,and,ultimately,whattheactoryoufindisresponsiblefor.
Onceyouhaveafewdifferentcallstacks,youcancomparethemtolocatetheactorfunctions.Toseehowtospottheactorfunctions,let’scomparethetwoannotatedcallstacksinFigure10-3.
Figure10-3:Treeviewofcallstackstotwoactorfunctions
Likethesetwostacks,thecallstacksyoufindshouldbeidenticalatthetop,sharingacoupleofcommonfunctionsresponsibleforgenericnetworktransmission.Theyshouldalsobeidenticalonthebottom,sinceeachcalltosend()shouldhaveoriginatedfromtheprocessInput()function.Eachstackshouldhavesomeuniquefunctionsbetweentheseidenticalregions,though,andthosearetheactorfunctionsyou’relookingfor.Typically,thefunctionofinterestisimmediatelybeneaththecommonnetworkcalls.Inthiscase,thetwoactorsarethestep()andcastSpell()functions.
Afterhackingthesamegameforawhile,you’lllearnhowfarupthestacktheactorfunctionsarefromthesend()call.InFigure10-3,forexample,theactorshappenthreecallsbeforethesend()call.Knowingthis,youcouldjustclimbthestackinOllyDbg(CTRL-F9followedbyF8)threetimeswhenyoursend()breakpointishitandbeinsidetheactorfunctionthatsentthedata.
Onceyou’vefoundanactorfunction,youcancallitfromaninjectedDLL.Here’showyoumightcallstep()ifyoufounditat0xDEADBEEF:
typedefvoid_step(intdirection);autostepActor=(_step*)0xDEADBEEF;
autostepActor=(_step*)0xDEADBEEF;
stepActor(FORWARD);
Sincethebotwon’tknowtheactualnameforthisgamefunction,thecodeassignsthecontentsofmemoryat0xDEADBEEFtoaconvenientlynamedvariable:stepActor.Then,thecodejustcallsstepActor()likeanyotherfunction.
Ifyou’vegottherightaddress,functionprototype,andparameters,thisshouldworkbeautifully;you’llbeabletoautomateactionsasifyouhaveaccesstothegame’ssourcecode.Justmakesuretocalltheactorfunctionsfrominsidethesamethreadasthegame,oryoucanrunintothreadingissues.ThebestwaytodothisistocalltheactorsfromahookonamajorfunctionlikeDirect3D’sEndScene()ortheWindowsAPI’sPeekMessage()function,asthesefunctionswillusuallybecalledonlyfromthegame’smainthread.
USINGTHISTOCALL__THISCALLIfyoutrytocallanactorfunctionthat’sanonstaticmemberofaclass,thefunctionwillhavea_thiscallcallingconvention,whichmeansyou’llneedtopasstheinstanceoftheclassontheECXregister.(Youcanbrushuponcallingconventionsin“FunctionCalls”onpage94.)Passingtheinstanceisstraightforward,butyou’llhavetolocateapointerchaintotheclassinstancefirst.
Tofindthepointerchain,youcandropabreakpointontheactorfunction,grabtheclassinstancevaluefromECXwhenthebreakpointkicks,andthrowthatvalueintoaCheatEnginepointerscan.Then,tocallthefunction,you’dwalkthepointerchain,obtainthecurrentinstanceaddress,anduseinlineassemblytosetupECXandmaketheactualfunctioncall.ThisprocessworkssimilarlytothewayVFhookcallbackscalltheiroriginalcounterparts,asshownin“WritingaVFTableHook”onpage156.
TyingthePiecesTogetherAfteryou’vecreatedframeworksforobservingeventsandperformingactions,youcantiethemtogethertocreateresponsivehacks.Responsivehackscomeinmanyflavors,butthereareafewcommonones.
MakingthePerfectHealerAfavoritebotamonggamersisautohealing,ahackthatautomaticallyusesahealingspellwhentheplayer’shealthdecreasesdrasticallyordropsbelowacertainthreshold.Givenawaytodetectchangesinhealthandanactorfunctiontocastspells,anautohealermightlooksomethinglikethis:
voidonHealthDecrease(inthealth,intdelta){if(health<=500)//healthbelow500castHealing();elseif(delta>=400)//largedropinhealthcastHealing();}
Thisautohealingfunctionisprettysimple,butitworkswell.Moreadvancedautohealersmighthavemanymorelevelsofhealingandbeabletolearnastheygo.You’llgetworkingexamplecodeandanin-depthexplanationofadvancedautohealersin“ControlTheoryandGameHacking”onpage222.
ResistingEnemyCrowd-ControlAttacksAnti-crowd-controlhacksdetectincomingcrowd-controlattacksandautomaticallycastspellsthatreducetheireffectsorcompletelynegatethem.Crowd-controlattacksdisableplayersinsomeway,sohavingenemiescastthemonyoucanbeapain.
Givenawaytodetectincomingoractivecrowd-controleffects,suchasbydetectingaDirect3Dmodelorbyinterceptinganincomingpacket,andanactorfunctiontocastspells,youcouldhaveabotreactinstantlytosuchattackslikeso:
voidonIncomingCrowdControl(){//castashieldtoblockthecrowdcontrolcastSpellShield();
}voidonReceiveCrowdControl(){//cleansecrowdcontrolthathasalreadytakeneffectcastCleanse();}
AnonIncomingCrowdControl()functionmighttrytostopthecrowd-controlspellfromeverhittingyou.Failingthat,thebotcouldcallanonReceiveCrowdControl()spelltoremovetheeffects.
AvoidingWastedManaSpelltrainersarealsoquitecommonamongbotters.Spelltrainerswaituntiltheplayerhasfullmanaandthencastspellstoincreasetheplayer’smagiclevelorstats.Thisallowsplayerstoquicklyincreasetheirmagicskills,astheywillneverwastemanaregenerationjustbecausetheyhavefullmana.
Givenawaytodetectchangesinmanaandanactorfunctiontocastspells,abotmightincludethefollowingpseudocodeforaspelltrainer:
voidonManaIncrease(intmana,intdelta){if(delta>=100)//playerisusingmanapotions,return;//theymustneedthemana,abortif(mana>=MAX_MANA-10)//manaisnearlyfull,wastesomecastManaWasteSpell();}
Thisfunctiontakestheplayer’smanaandtheincreaseinthatplayer’smana(delta)asparameters.Iftheincreaseinmanaisaboveacertainamount,itassumestheplayerisusingpotionsorotheritemstoreplenishmana,anditwon’tcastanyextraspells.Otherwise,iftheplayerhasplentyofmana,thefunctionfiresoffanyoldspelltogettheplayersomeexperiencepoints.
Othercommonresponsivehacksareautoreloadtoinstantlyreloadammo,autododgetoevadeincomingprojectiles,andautocombotoinstantlyattackthesametargetasanearbyally.Really,theonlylimittothenumberofresponsivehacksyoucanaddtoabotisthenumberofeventsyourbotcanobserveinthegame,multipliedbythenumberofvalidandhelpfulresponsesitcansendforeachevent.
ClosingThoughtsUsinghooks,memorymanipulation,andkeyboardsimulation,youcanbegincreatingyourfirstresponsivehacks.Thesehacksareyourentrypointintogamingautonomy,butthey’reonlyaglimpseofwhat’spossible.Chapter11willbethepinnacleofyourgame-hackingadventure.Usingeverythingyou’velearnedsofar,andbuildingontheprinciplesofresponsivehacks,you’lllearnhowtoautomateadvancedactionsandcreateatrulyautonomousbot.
Ifyou’renotfeelingquitereadytogodeeper,Istronglyrecommendreviewingtheearliermaterialandthengettingsomepracticeinanisolatedenvironmentonyourownmachine.Implementingbotslikethisisaloteasierthanyoumightthink,andit’sanamazinglysatisfyingexperience.Onceyou’recomfortablemakingautohealersandotherbasicresponsivehacks,you’llbereadytostartcompletelyautomatinggameplay.
11PUTTINGITALLTOGETHER:WRITING
AUTONOMOUSBOTS
Theendgoalofgamehackingistomakeafull-fledgedautomatedbotcapableofplayingagameforhoursonend.Suchbotscanheal,drinkpotions,farmmonsters,lootcorpses,walkaround,sellloot,buysupplies,andmore.Makingbotsthispowerfulrequiresyoutocombineyourhooksandmemoryreadswithconceptslikecontroltheory,statemachines,andsearchalgorithms,whichareallcoveredinthischapter.
Throughoutthelessonshere,you’llalsolearnaboutcommonautomatedhacksandhowtheyshouldbehaveatahighlevel.Aftercoveringthetheoryandcodebehindautomatedhacks,I’llgiveyouahigh-levellookattwotypesofbotsthatrelyonsuchcode:cavebots,whichcanexplorecavesandbringhometheloot,andwarbots,whichcanfightenemiesforyou.Bytheendofthechapter,youshouldbereadytobustoutyourtools,fireupyourdevelopmentenvironment,andstartmakingsomereallyawesomebots.
ControlTheoryandGameHackingControltheoryisabranchofengineeringthatprovidesawaytocontrolthebehaviorofdynamicsystems.Controltheorydeterminesthestateofasystemusingsensors,afterwhichacontrollerdeterminesthesetofactionsneededto
bringthesystem’scurrentstatetosomeotherdesiredstate.Afterthecontrollerexecutesthefirstactionintheset,theentireprocess—knownasafeedbackloop—repeats(seeFigure11-1).
Figure11-1:Acontroltheoryfeedbackloop
Let’sapplythisfeedbacklooptogamehacking.Toautomateplaywithinagame(thesystem),abotimplementssomealgorithms(thecontroller)thatunderstandhowtoplaythegameinanystateobservedbythememoryreads,networkhooks,andsoon(thesensors).Thecontrollertypicallyhassomehumaninputs,likethepathtowalk,creaturestoattack,andloottopickup.Thus,toreachthedesiredstate,thecontrollermustperformsomesubsetoftheseinputsthatarepossiblegiventhecurrentstate.
Forinstance,iftherearenocreaturesonscreenandnocorpsestoloot,thedesiredstatemaybefortheplayertoreachthenextlocation(calledawaypoint)inthepredefinedpath.Inthiscase,thecontrollermovestheplayeronestepclosertothewaypointoneachiteration.Iftheplayerencountersacreature,thecontrollermightdecidetoattackthecreatureinthefirstframeand,inthefollowingframes,switchbetweenrunningfromthecreature(knownaskiting)andshootingspellsatit.Oncethecreaturedies,thecontrollerexecutesasetofactionstolootthebodyandcontinuetothenextwaypoint.
Giventhisexampleofhowafeedbackloopmightoperate,itmightseemoverwhelmingtocodesuchasystem.Luckily,thereareafewdesignpatternsthatmakethetaskmucheasierthanitsounds.
StateMachinesStatemachinesaremathematicalmodelsofcomputationthatdescribehowasystembehavesbasedoninput.Figure11-2showsasimplestatemachine
thatreadsalistofbinarydigits.ThemachinestartswithaninitialstateofS1.Asititeratesoverthedigitsintheinput,itchangesitsstateaccordingly.Inthiscase,statesS1andS2repeatthemselveswhenthemachineencountersa1andactivateoneanotherwhenitencountersa0.Forexample,forthebinarydigits11000111,thestatetransitionswouldbeS1,S1,S2,S1,S2,S2,S2,andfinallyS2.
Figure11-2:Asimplestatemachine
Withasmallspinontheclassicalstatemachinetheory,astatemachinecanbethecontrollerinacontroltheoryfeedbackloop.Thistweakedversionofastatemachinecomprisesalistofstates,theconditionssignifyingeachstate,andtheactionsthatmusthappentoreacheachstate.
STATEMACHINESANDGAMEHACKINGAgame-hackingstatemachinenotonlymustkeepaninternalstatebutalsomustrespondto(oractuate)thegameenvironmentbasedonthatstate.Theoverallgamestatecanchangebasedonyourbot’sactuation,thebehaviorofotherplayers,andotherunpredictableoccurrencesinthegameenvironment.Forthisreason,tryingtopersistentlywalkastatemachinebasedontheobservedgameenvironmentisfutile;it’snearlyimpossibletocreateasetoftransitionsforeachstatetoaccountforeverypossibleobservationthatcanbemadebetweeniterations.Itmakesmoresenseforthestatemachinetoreevaluatethegameenvironmentasafreshslateeachtimeitconsiderstheinput.Todothis,thestatemachinemustusethegameenvironmentitselfasthemechanismfortransitioningbetween
states—thatis,themachine’sactuationontheenvironmentshouldhaveenoughofaneffectonthenextiterationsthatitactivatesanewstate.Classicalstatemachinescanbedevisedthatarecapableofworkinglikethis,butwe’regoingtoflattenthemoutandusetheminamuchsimpler,yetstillverypowerful,way.
Ifyou’refamiliarwithclassicalstatemachines,thismaynotseemintuitive,butinthecomingsectionsyou’llseehowstatemachinescanbemutatedandpairedwithcontroltheorytoachievewhatwewant.
Themajordifferenceisthatinsteadofonestatemerelyactivatinganother,foreachstateinagameautomationstatemachine,abotwillperformin-gameactionsthatchangetheoverallstateofthegameand,thus,thestatethatisdetectedonthenextiterationofthefeedbackloop.Incode,anobjecttorepresentastateinthismachinemightlooklikethis:
classStateDefinition{public:StateDefinition(){}~StateDefinition(){}boolcondition();voidreach();};
YoucanassembleStateDefinitionobjectsintoastatemachinewithasimplestd::vectordefinition,likethis:
std::vector<StateDefinition>stateMachine;
Andpresto,youhavetheskeletonofastatemachine,readytoreceiveanyStateDefinitionobjectsyoucreate.Inconjunctionwithafeedbackloop,thisstatemachinecanbeusedtodefinetheflowofautomation.
First,youcancreatealistofdefinitionsthatmodelyourbot’sdesiredbehavior,orderedinthevectorbyimportance.EachStateDefinitionobjectcanuseinformationfromyoursensorsasinput,passingthatdatatothecondition()functiontodeterminewhetherornotthestateshouldbeactivated.Then,youcancreateacontrollerthatloopsoverthelistofstates,callingthereach()functionofthefirststatewhosecondition()functionreturnsfalse.Finally,youcanwrapthecontrollerinafeedbackloop.Ifyoudon’tseehowthisfeedbackloopwouldworkyet,don’tworry;I’llshowyou
howtocodeitnow.
NOTE
Youcanthinkofthestatementinyourcondition()functionasarequirementforthemachinetotransitiontothenextstate.Ifthestatementistrue,itmeansnoactuationmusthappenbeforethenextstateinthelistcanbeevaluatedandtheloopcancontinueiterating.Ifthestatementisfalse,itmeanssomeactuatormustoccurbeforethetransitioncanhappen.
You’llfindalloftheexamplecodeforthefollowingsectionand“ErrorCorrection”onpage230intheGameHackingExamples/Chapter11_StateMachinesdirectoryofthisbook’ssourcefiles.TheincludedprojectscanbecompiledwithVisualStudio2010,buttheyshouldalsoworkwithanyotherC++compiler.Downloadthemathttps://www.nostarch.com/gamehacking/andcompilethemifyouwanttofollowalong.
CombiningControlTheoryandStateMachinesTotiestatestogetherwithafeedbackloop,firstyouhavetoprovideeachStateDefinitionobjectwithagenericwaytoaccessthesensorsandactuatorsthatyou’veimplemented.TheStateDefinitionclassthenbecomesthefollowing:
classStateDefinition{public:StateDefinition(){}~StateDefinition(){}boolcondition(GameSensors*sensors);voidreach(GameSensors*sensors,GameActuators*actuators);};
Thischangesimplymodifiesthecondition()andreach()functionstoacceptinstancesoftheclassesGameSensorsandGameActuatorsasarguments.GameSensorsandGameActuatorsareclassesyouneedtodefine;GameSensorswillcontaintheresultsofmemoryreads,networkhooks,andotherdatasourcesyourbotinterceptsfromthegame,whileGameActuatorswillbea
collectionofactorfunctionscapableofperformingactionsinsidethegame.Next,youneedagenericwaytodefineeachindividualstate.Youcould
abstractthedefinitionofeachstatetoitsownclassthatinheritsStateDefinitionandimplementscondition()andreach()asvirtualfunctions.Alternatively,ifthesourcecodeneedstofitinasmallspace(likeabook,winkwink),youcouldkeepasingleclasstorepresenteachdefinitionandusestd::functiontoimplementthecondition()andreach()functionsoutsidetheclassdefinition.
Followingthatalternativemethod,thefinalversionofStateDefinitionwouldlooklikethis:
classStateDefinition{public:StateDefinition(){}~StateDefinition(){}std::function<bool(GameSensors*)>condition;std::function<void(GameSensors*,GameActuators*)>reach;};
WiththisversionoftheStateDefinitionclass,youcoulddefineanewstatebycreatinganinstanceoftheclassandassigningcondition()andreach()tofunctionsthatcorrespondwiththeintendedbehavior.
ABasicHealerStateMachineThenextstepisdefiningthebot’sactualbehavior.Tokeeptheexamplecodesimple,let’ssayyou’reimplementinganautomatichealer.Thishealerhastwohealingmethods:itusesstronghealingiftheplayerisatorbelow50percenthealthandweakhealingiftheplayerisbetween51and70percenthealth.
Astatemachinerepresentingthisbehaviorneedstwostates,oneforstronghealingandoneforweakhealing.Tostart,youneedtodefinethestatemachineasavectorwithtwoStateDefinitionobjects:
std::vector<StateDefinition>stateMachine(2);
ThiscodecreatesastatemachinecalledstateMachineandinitializesitwithtwoemptyStateDefinitionobjects.Next,youdefinethecondition()andreach()functionsforthesestatedefinitions.Thestronghealingstateisthemostimportantbecauseitkeepsthecharacterfromdying,soitshould
comefirstinthevector,asshowninListing11-1.
autocurDef=stateMachine.begin();curDef->condition=[](GameSensors*sensors){➊returnsensors->getHealthPercent()>50;};curDef->reach=[](GameSensors*sensors,GameActuators*actuators){➋actuators->strongHeal();};
Listing11-1:Codeforastronghealingstate
ThiscodefirstcreatesaniteratorcalledcurDefthatpointstothefirstStateDefinitionobjectinthestateMachinevector.Theobject’scondition()functionisthendefined➊;inEnglish,thisdefinitionsays,“Thestateismetiftheplayer’shealthpercentisgreaterthan50.”Ifthestateisn’tmet,thentheobject’sreach()functioncallsthestrongHeal()actorfunction➋sothatstronghealingcanbeperformed.
Withthestronghealingstatedefined,nextyoudefinetheweakhealingstate,asshowninListing11-2.
curDef++;curDef->condition=[](GameSensors*sensors){➊returnsensors->getHealthPercent()>70;};curDef->reach=[](GameSensors*sensors,GameActuators*actuators){➋actuators->weakHeal();};
Listing11-2:Codeforweakhealing
AfterincrementingcurDefsoitpointstothesecondStateDefinitionobjectinthestateMachinevector,thiscodedefinestheobject’scondition()function➊as,“Thestateismetiftheplayer’shealthpercentisgreaterthan70.”Italsodefinestheobject’sreach()functionasanactuators->weakHeal()call➋.
Onceyou’vefinisheddefiningthestatemachine,youmustimplementthecontroller.Sincetheactualbehaviorofthecontrolleriscontainedinthestatemachine,youonlyneedtoaddasimplelooptocompleteit:
for(autostate=stateMachine.begin();state!=stateMachine.end();state++){
if(➊!state->condition(&sensors)){state->reach(&sensors,&actuators);break;}}
Thiscontrollerloopiteratesoverthestatemachine,executesthereach()functionofthefirststatewhosecondition()functionreturnsfalse➊,andbreaksoutifanyreach()functioniscalled.Thefinalstepistoimplementthefeedbackloopandplopthecontrollerloopinsideit,asshowninListing11-3.
while(true){for(autostate=stateMachine.begin();state!=stateMachine.end();state++){if(!state->condition(&sensors)){state->reach(&sensors,&actuators);break;}Sleep(FEEDBACK_LOOP_TIMEOUT);}
Listing11-3:Finalhealingstatemachineandfeedbackloop
ThisloopcontinuouslyexecutesthecontrollerloopandsleepsforFEEDBACK_LOOP_TIMEOUTmillisecondsbetweeneachexecution.TheSleep()callallowsthegameservertoreceiveandprocessanyactuationfromthepreviousiterationandallowsthegameclienttoreceiveanyresultsoftheactuationfromtheserverbeforeexecutingthenextcontrollerloop.
Ifyou’restillabitconfusedaboutwhatIjustshowedyou,checkoutFigure11-3,whichshowshowtheinfinitelyloopingcodeinListing11-3works.First,itcheckswhetherthestronghealingconditionistrue,andifitis,theweakhealingconditionischecked.Ifthestronghealingconditionisfalse,thentheplayer’shealthmustbeatorbelow50percent,soastronghealingmethodgetscalled.Iftheweakhealingconditioncheckisfalse,thentheplayer’shealthmustbebetween51and70percent,sotheweakhealingmethodisexecuted.
Figure11-3:Flowchartofthehealingstatemachineandfeedbackloop
Aftereithermethod,themachinesleeps.Ifbothconditionchecksaretrue,thentheplayerneedsnohealing.Themachinedoesnothingtochangethestateandsleepsbeforestartingagainatthetopofthewhileloop.
AComplexHypotheticalStateMachineThebehaviorimplementedinthehealingstatemachineissimple,sorollingitintothiskindofcontrolstructuremayseemlikeoverkill,butit’susefulifyouwanttoexpandthecontroller.If,forexample,youwantedtocombinethehealingstatemachinewiththe“walk,attack,loot”behaviorthatI
discussedin“ControlTheoryandGameHacking”onpage222,thecontrolstructurewouldbemuchmorecomplex.Let’stakeahigh-levellookatthestatesyou’dneed:
StronghealingConditionmetifhealthisover50percent.Reachbycastingstronghealingspell.
WeakhealingConditionmetifhealthisover70percent.Reachbycastingweakhealingspell.
AttackspellConditionmetifnotargetisavailableorifattackspellisoncooldown.Reachbycastingattackspellontarget.
KitemonsterConditionmetifnotargetisavailableorifdistancefromtargetisadequate.(Thedefinitionof“adequate”dependsonhowfarawayyouwanttobefromenemieswhenkiting.)Reachbytakingastepawayfromtarget.
TargetmonsterConditionmetifthere’snocreaturetoattack.Reachbyattackingacreature.
LootitemConditionmetifthere’snocorpseopenorifopencorpsehasnothingtoloot.Reachbytakinganitemfromopencorpse.
ApproachcorpseConditionmetiftherearenocorpsestoopenorifadjacenttoacorpse.Reachbytakingasteptowardacorpsethatwillbeopened.
OpencorpseConditionmetifthecharacterisnotadjacenttoacorpsethatcanbeopened.Reachbyopeningadjacentcorpse.
FollowpathConditionmetifthecharacterisunabletomovetocurrentwaypointorifstandingoncurrentwaypoint.Reachbytakingasteptowardcurrentwaypoint.
AdvancewaypointConditionmetiftherearenowaypointslefttofollow.Reachbyupdatingthecurrentwaypointtothenextwaypointinthelist.Ifthecharactercan’treachthecurrentwaypointforsomereason(say,ifthecharacterisstuck),thentheAdvanceWaypointstatekeepsitfrombeingstuck.Ifthecharacterhasreachedthecurrentwaypoint,AdvanceWaypointselectsthenextwaypointtokeepthingsmovingalong.
Thisstatemachineisquiteabitmorecomplexthanthehealing-onlystatemachine.IfIdiagrammedthisstatemachine,therewouldbe23objectsinthediagram,witharrowsgoingover33controlpaths.ComparethattoFigure11-3,whichhasonly7objectsand9controlpaths.
Youcouldcodethehealerbehaviorwithoutusingastatemachineorfeedbackloop,butIcan’timaginehowtoeasilydothesameforthisfull-fledgedbot.Eachofthese10statesreliesonnotonlyitsownconditionbutalsotheconditionofeverystateprecedingit.Moreover,hardcodingthelogicwouldeitherrequireatonofnestedif()statementsorabunchofstackedif()/return()statements—and,eitherway,itwouldjustbehaveexactlylikethestatemachinebutwithnoruntimeflexibility.
Runtimeflexibilityreferstoastatemachine’sabilitytomutate.Unlikehardcodedconditionchecks,statedefinitionsinastatemachinecanbemoved,removed,andaddeddynamically.Thestatemachinemethodallowsyoutoplugandplaydifferentbehaviorsandfeaturesdependingonuserinput.
Totakethisconceptastepfurther,youcouldexposeyoursensorsandactuatorstoaLuaenvironment,createLuafunctionscapableofaddingandremovingstatesfromthestatemachine,andmodifytheStateDefinitionsothatitscondition()andreach()functionscancallLuafunctionsexposedbytheLuaenvironment.Writingacontrolsystemthiswaywouldallowyoutocodethecoreofyourbot(hooks,memoryreading,actuation)inC++whilemakingLua(ahigh-level,dynamiclanguage)availabletoyouforautomation.
NOTE
YoucanembedLuainyourownprogramsbyincludingafewheadersandlinkingagainsttheLualibrary.Thisprocessisnotdifficult,butit’soutsidethescopeofthisbook,soIencourageyoutocheckoutChapter24ofProgramminginLuabyRobertoIerusalimschy(http://www.lua.org/pil/24.html)formoreinformation.
ErrorCorrectionAnotherpieceofcontroltheorythat’susefulforgamehackingiserror
correction.Anerrorcorrectionmechanisminacontrollerobservestheoutcomeofactuation,comparestheoutcometoanexpectedresult,andadjustsfuturecalculationstobringlateroutcomesclosertotheexpectedone.Errorcorrectioncancomeinhandywhenyou’reworkingwithstochasticsystems,wheretheoutputgeneratedfromagiveninputisnotfullypredictable.
Gamesasawholearestochastic,but,luckilyforgamehackers,theresultsofactionsaremostlydeterministic.Takethehealingcontroller,forexample.Inmostgames,youcancalculateexactlyhowmuchhealthyoucanhealwithagivenspell,and,thus,youknowexactlywhentoheal.Butimagineyou’rewritingahealerforthesmallspectrumofsituationswhereyourhealingisimpossibletocalculate;forinstance,maybethebotissupposedtoworkonavarietyofcharactersspanningmanylevelswithoutuserinput.
Errorcorrectioncouldenableyourbottolearnhowtobesthealtheplayers.Inthisscenario,therearetwowaysyoucanimplementerrorcorrection,eachofwhichdependsonhowthehealingsystemworks.
AdjustingforaConstantRatioIfyouhealforaconstantratioofhealth,you’llonlyneedtoadjustyourcontrollerafterthefirstheal.Assumingthatyoursensorscandetecthowmuchyou’vehealed,thisaddsonlyafewlinesofcode.YoucouldeasilymodifytheweakhealingstateinListing11-2tosomethinglikethis:
curDef->condition=[](GameSensors*sensors)->bool{staticfloathealAt=70;staticboolhasLearned=false;if(!hasLearned&&sensors->detectedWeakHeal()){hasLearned=true;healAt=100-sensors->getWeakHealIncrease();}returnsensors->getHealthPercent()>healAt;};
Insteadofhardcoding70asthethresholdforweakhealing,thiscodemovesthethresholdtoastaticvariablecalledhealAt.ItalsoaddsanotherstaticvariablecalledhasLearnedsothatthecodeknowswhenlearningiscomplete.
Oneachinvocationofthiscondition()function,thecodecheckstwoconditions:whetherhasLearnedisfalseandwhetherthesensorsdetecteda
weakhealingevent.Whenthischeckpasses,thecodesetshasLearnedtotrueandupdateshealAttohealatorbelowtheperfectpercentage;thatis,ifyourweakhealingmusteredupa20percentincreaseinhealth,healAtwouldbesetto80percenthealthinsteadof70percent,soeachhealwouldbringtheplayerbackupto100percenthealth.
ImplementingAdaptableErrorCorrectionButwhatifyourhealingpowerincreases?Ifacharactercangainlevels,applyskillpoints,orincreasemaximumhealth,theamountofhealthitcanhealmaychangeaccordingly.Forexample,ifyoustartabotonalevel-10characterandletitrununtilthecharacterislevel40,yourhealingcodewillneedtoadapt.Alevel-40characterhealinglikeitdidatlevel10wouldeitherimmenselyoverhealordiequicklyagainston-levelgameenemies.
Tohandlethisscenario,abotneedstoconstantlyupdateitshealingthresholdtoreflecttheobservedhealingamount.Listing11-4showshowyoucanmodifythestronghealingconditionfunctioninListing11-1todothis.
curDef->condition=[](GameSensors*sensors)->bool{staticfloathealAt=50;➊if(sensors->detectedStrongHeal()){autonewHealAt=100-sensors->getStrongHealIncrease();➋healAt=(healAt+newHealAt)/2.00f;➌sensors->clearStrongHealInfo();}returnsensors->getHealthPercent()>healAt;};
Listing11-4:Tweakingthestronghealingconditioncode
Asinthemodifiedweakhealingfunction,thehealingthresholdhasbeenmovedtoastaticvariablecalledhealAt,butthistime,thelogicisabitdifferent.Sincelearningmusthappencontinually,there’snovariabletotrackwhetherthebothasalreadylearneditstruehealingcapacity.Instead,thecodejustcheckswhetherthesensorshaveseenastronghealingeventsinceitslastinvocation➊.Ifso,thecodereplaceshealAtwiththeaverageofhealAtandnewHealAtandcallsafunctiontoclearthesensorsofinformationrelatedtostronghealing➌.
Clearingthesensorsisactuallyveryimportant,becauseitkeepsthecode
fromconstantlyupdatinghealAtagainstfeedbackfromthesamestronghealingcast.Notice,too,thatthisfunctiondoesn’tupdatehealAttoaperfectvaluebutinsteadslidesittowardtheobservedoptimalvalue.Thisbehaviormakesthenewfunctionidealforsituationswherethereissomeamountofrandomnessinhowmuchyoucanactuallyheal.Ifyourbotneedstoslidetowardthenewvaluefaster,youmightchangethelineat➋tosomethinglikethis:
healAt=(healAt+newHealAt*2)/3.00f;
ThiscodetoupdatehealAtusesanaverageweightedtowardthenewHealAtvalue.Thereareafewpointstoconsiderwhenusingthisapproach,however.First,whathappenswhenyouoverheal?Insomegames,whenyouhealtofullhealth,yoursensorsmightbeabletodetectonlyhowmuchyouactuallyhealed.Inothergames,yoursensorsmaybeabletodetecttheactualamounthealed.Putanotherway,ifyoucasta30percentstronghealfrom85percenthealth,doyoursensorsseeahealof30percentor15percent?Iftheansweris30percent,you’reset.Iftheansweris15percent,yourcodeneedsawaytoadjustdownward.
OnewaytoadjustaccordinglyistodecrementhealAtwhenyoursensorsseeahealthatbringsyoutofullhealth,likethis:
curDef->condition=[](GameSensors*sensors)->bool{staticfloathealAt=50;if(sensors->detectedStrongHeal()){➊if(sensors->getStrongHealMaxed()){healAt--;}else{autonewHealAt=100-sensors->getStrongHealIncrease();healAt=(healAt+newHealAt)/2.00f;}sensors->clearStrongHealInfo();}returnsensors->getHealthPercent()>healAt;};
ThiscodeisalmostthesameasListing11-4,butitaddsanif()clausetodecrementhealAtifamaxhealisdetected➊.Otherwise,thefunctionshouldbehavelikeListing11-4.
Healingisasimplecase,butthiscodeshowsagreatexampleofhowyoucanuseerrorcorrectiontodynamicallyimproveyourbots’behavior.Onemoreadvancedusecaseisadjustingskillshotstoaccountforenemy
moreadvancedusecaseisadjustingskillshotstoaccountforenemymovementpatterns.Everyplayerhaspatternsinhowtheyavoidskillshots,soifyoursensorsareabletomeasurethedirectionanddistanceanenemymoveswhendodgingaskillshot,yourcontrollercodecanadjustthelocationwherethebotinitiallyshootstheskillshot.Inthissamescenario,learningwouldalsohelpthebotaccountfordifferencesingameserverlatency,charactermovementspeed,andsoon.
Whenusingerrorcorrection,notethatyourcodewillbecleanerandmoreportableifyourstatedefinitionshavesomeformofinternalbookkeepingotherthanstaticvariables.Moreover,toavoidclutteringyourstatedefinitions,Isuggestencapsulatingtheerrorcorrectionlogicinsomeexternalmodulesthatareeasilyinvokedwhenneeded.
PathfindingwithSearchAlgorithmsOnecommonchallengeyou’llfacewhenwritinganautonomousbotiscalculatingapathforacharactertofollowfromonelocationtoanother.Asidefromthesheerreverseengineeringchallengeofcreatingsensorstoreadwhichcoordinatesonthegamemapareblockingforwardmovementornot,there’salsothealgorithmicchallengeofcalculatingapathwithinthatmap.Calculatingapathiscalledpathfinding,andgamehackersoftenuseasearchalgorithmtotackleit.
TwoCommonSearchTechniquesGivenagridoftiles,astartinglocationa,andanendinglocationb,asearchalgorithmcalculatesapathfromatob.Thealgorithmdoesthisbycreatinganodeata,addingnodesadjacenttoatoalistoftilestobeexplored(calledthefrontier),updatingthenodetothebesttileinthefrontier,andrepeatingtheprocessuntilthenodereachesb.Differentsearchalgorithmsselectthebestnodedifferently,usingeitheracost,aheuristic,orboth.
Dijkstra’salgorithm,forexample,calculatesthecostofatilebasedonitsdistancefromtheanodeandselectsthetilewiththelowestcost.Imagineanemptytwo-dimensionalgridwithainthemiddle.InasearchfollowingDijkstra’salgorithm,thefrontierwillexpandinacircularpatternaroundauntilbliesontheedgeofthecircle,asseeninFigure11-4.
Thegreedybest-firstsearchalgorithm,insteadofprioritizingnodesbytheirdistancefromthestartingpoint,usesaheuristictoestimatethedistancefromanodeinthefrontiertob.Thealgorithmthenselectsthenodewiththeshortestestimateddistance.Imaginethisalgorithminthesamegridasbefore;thefrontierwouldbealinegoingalmostdirectlyfromatob,asseeninFigure11-5.
Figure11-4:ThefrontierofDijkstra’salgorithm.Lightertilesarehighercost.
Figure11-5:Thefrontierofthegreedybest-firstsearchalgorithm.Lightertilesarehighercost.
HowObstaclesDisruptSearchesThedifferenceinhowthesealgorithmsbehavebecomescleareronceobstaclesareaddedtothegrid.If,forinstance,awallseparatesaandb,Dijkstra’salgorithmwillalwaysfindthequickestpath,butwithahugeconsequence.Theradiusofthecircularfrontieraroundawillbeequaltothe
lengthofthefinalpath;let’scallthatradiusr.Ifnogridboundariesclipthefrontier,youcanroughlycalculatethenumberofnodesopenedbytakingtheareaofacirclewithradiusr.Ifthepatharoundthewallis50tiles,thealgorithmwillopenroughly7,854tiles,asshowninthisequation:
π×502=7,854
Inthesamescenario,greedybest-firstsearchwillcalculatealess-than-optimalpathbutopensubstantiallyfewertiles.It’snotaseasytovisualizehowthefrontierwillexpand,andit’snotimportantrightnow,soIwon’tgointoithere.Attheendoftheday,neitherofthesealgorithmsreallyfitsthepathfindingproblemwell.Theoptimalpathisslow,andthefastpathisn’toptimal.
Toquicklycalculateanoptimalpath,youneedtofuseDijkstra’salgorithmwithgreedybest-firstsearch.Luckily,someonehasalreadydonethis,andtheresultingalgorithmisamonsterknownasA-starsearch(oftenjustcalledA*).
A*usesthesumofacost,calledg,andaheuristic,calledh,toselectnodes.Theseresultingsumiscalledthescore.Putsimply,score=g+h.LikeDijkstra’salgorithm,A*cancalculatethemostoptimalpathfromatob,andlikegreedybest-firstsearch,itcandosorelativelyquickly.
AnA*SearchAlgorithmNowthatyouknowthefundamentals,let’swritecodetoimplementtheA*algorithm.Thisimplementationwillworkinatwo-dimensionalgrid.Itwon’tallowdiagonalmovementatfirst,butI’lldiscussinabithowyoucanmodifythecodetoworkwithdiagonalmovement,too.
AlloftheexamplecodeforthissectionisintheGameHackingExamples/Chapter11_SearchAlgorithmsdirectoryofthisbook’ssourcefiles.TheincludedprojectscanbecompiledwithVisualStudio2010,buttheyshouldalsoworkwithanyotherC++compiler.Downloadthemathttps://www.nostarch.com/gamehacking/andcompilethemtofollowalong.IfyouexecuteChapter11_SearchAlgorithms.exe,you’llbeabletodefineyourown20×20gridandwatchthealgorithmcalculateasearchpath.
CreatinganA*Node
Tostart,defineanemptyAStarNodeclassasfollows:
typedefstd::shared_ptr<classAStarNode>AStarNodePtr;classAStarNode{public:};
ThiscodedefinestheAStarNodeclassandastd::shared_ptrtypedefinitioncalledAStarNodePtrtomakeiteasiertocreatesafepointerstotheclass.Next,withinthepublicscopeofthisclass,declaremembervariablesforthenode’sx-position,y-position,cost,andnode’sscore:
intx,y;intg,score;
Additionally,youneedapublicmemberoftypeAStarNodePtrthatreferencestheparentnode:
AStarNodePtrparent;
Afterdeclaringallmembervariables,declareapublicconstructorthatinitializesthemuponinstancecreation,asfollows:
AStarNode(intx,inty,intcost,AStarNodePtrp,intscore=0):x(x),y(y),g(cost),score(score),parent(p){}
Now,tomakecreatingsafepointerseasier,addastatichelperfunctionlikethis:
staticAStarNodePtrmakePtr(intx,inty,intcost,AStarNodePtrp,intscore=0){returnAStarNodePtr(newAStarNode(x,y,cost,p,score));}
ThismakePtr()functioncreatesanewinstanceofAStarNodeandreturnstheinstancewrappedinsideofanAstarNodePtr.
Let’srecap.TheAStarNodeclasshasmembervariablesx,y,g,score,andparent.Whentheclassisconstructed,allofthesemembersareinitializedfromvaluespassedtotheconstructor,withtheexceptionofscore,whichis
optional(becauseyouuseitonlywhenmakingcopiesofanAStarNodeinstance)andsetto0ifnotprovided.
Next,defineapublicmemberfunctiontocalculatetheheuristicwhengiventhedestinationcoordinates:
intheuristic(constintdestx,intdesty)const{intxd=destx-x;intyd=desty-y;➊returnabs(xd)+abs(yd);}
ThisfunctionreturnstheManhattandistanceheuristic➊,adistancecalculationdesignedforgridswherediagonalmovementisnotpossible:
|Δx|+|Δy|Tocalculateapaththatallowsdiagonalmovement,you’dneedtomodify
thisfunctiontousetheEuclideandistanceheuristic,whichlookslikethis:
Theclassalsoneedsafunctiontoupdatescore.Youaddthatfunctiontothepublicscopeasfollows:
#defineTILE_COST1voidupdateScore(intendx,intendy){autoh=this->heuristic(endx,endy)*TILE_COST;this->score=g+h;}
Now,scoreshouldchangetog+hwhengivendestinationcoordinatestocalculateh.
Towrapup,thenodeclassalsoneedsafunctionthatcancalculateallofitschildnodes.Thefunctioncoulddothisbycreatingnewnodesforeachtileadjacenttothecurrentnode.Eachnewnodereferstothecurrentnodeasitsparent,sotheclassneedstobeabletocreateanAStarNodePtrtoacopyofthecurrentnodeaswell.Here’showallthatworks:
AStarNodePtrgetCopy(){returnAStarNode::makePtr(x,y,g,parent,score);
}std::vector<AStarNodePtr>getChildren(intwidth,intheight){std::vector<AStarNodePtr>ret;autocopy=getCopy();if(x>0)➊ret.push_back(AStarNode::makePtr(x-1,y,g+TILE_COST,copy));if(y>0)➋ret.push_back(AStarNode::makePtr(x,y-1,g+TILE_COST,copy));if(x<width-1)➌ret.push_back(AStarNode::makePtr(x+1,y,g+TILE_COST,copy));if(y<height-1)➍ret.push_back(AStarNode::makePtr(x,y+1,g+TILE_COST,copy));returnret;}
Thisfunctioncreateschildnodesat(x–1,y)➊,(x,y–1)➋,(x+1,y)➌,and(x,y+1)➍.TheirparentisthenodethatcalledgetChildren,andtheirgistheparent’sgplusTILE_COST.
Toallowfordiagonalmovement,thisfunctionneedstoaddchildrenat(x–1,y–1),(x+1,y–1),(x+1,y+1),and(x–1,y+1).Additionally,ifmovingdiagonallywouldcostmore—thatis,ifthecharacterrequiresmoretimetodoit—you’dalsoneedtodothefollowing:
1. ChangeTILE_COSTto10.
2. DefineaconstantDIAG_TILE_COSTasTILE_COSTmultipliedbythetimeincrease.Ifadiagonalsteptakes1.5timesaslong,DIAG_TILE_COSTwouldbe15.
3. Givediagonalchildrenagoftheparent’sgplusDIAG_TILE_COST.
TofinishoffAStarNode,declareoperatorsforcomparingthepriorityandequalityoftwonodes.Youcouldplacethesedeclarationsoutsidetheclassinglobalscopelikethis:
➊booloperator<(constAStarNodePtr&a,constAStarNodePtr&b){returna.score>b.score;}
➋booloperator==(constAStarNodePtr&a,constAStarNodePtr&b){returna.x==b.x&&a.y==b.y;}
Theseoperatorsallowstd::priority_queuetosortnodesbyscore➊andstd::findtodeterminenodeequalitybylocation➋.
WritingtheA*SearchFunctionNowthatyou’vecompletedtheAStarNodeclass,youcancodetheactualsearchfunction.Startbydefiningthefunctionprototype:
template<intWIDTH,intHEIGHT,intBLOCKING>booldoAStarSearch(intmap[WIDTH][HEIGHT],intstartx,intstarty,intendx,intendy,intpath[WIDTH][HEIGHT]){}
Theprototypeacceptsthegamemap’swidthandheight,aswellasthevaluethatsignifiesablockingtileonthemap,astemplateparameters.ThedoAStarSearch()functionalsotakesthemapitself(map),thestartingcoordinates(startxandstarty),thedestinationcoordinates(endxandendy),andablankmap(path)whereitcanfillthecalculatedpathwhenitfinishes.
NOTE
Thefirstthreeparametersaretemplateparameters,soyoucanpassthemascompiletimeconstants.I’vedonethisfortheexamplecodetoallowexplicitarraysizedeclarationsforthemapandpathparametersandtoallowadefinitevaluetosignifyblockingtilesonthemap.Inpractice,themapyoureadfromagamewillhaveadynamicsize,andyou’llprobablyneedamorerobustwaytopassthisdata.
Next,thedoAStarSearch()functionneedsasortedlisttoholdthefrontierandacontainertotrackallcreatednotessoyoucanupdatethescoreandparentofanexistingnodeifit’sopenedasachildofadifferentparent.Youcancreatetheseasfollows:
std::vector<AStarNodePtr>allNodes;
std::vector<AStarNodePtr>allNodes;std::priority_queue<AStarNodePtr>frontier;
Thefrontierisdefinedwithstd::priority_queuesinceitcanautomaticallysortthenodesbasedontheirscore.Thenodecontainer,allNodes,isdefinedasastd::vector.
Now,let’screatethefirstnode:
autonode=AStarNode::makePtr(startx,starty,0,nullptr);node->updateScore(endx,endy);allNodes.push_back(node);
Thefirstnodeisano-costorphannodeattheposition(startx,starty).ThenodeisgivenascorebasedonwhattheupdateScore()functionreturns,andthenit’saddedtotheallNodescontainer.
Withanodeinthecontainer,it’stimetowritethemeatoftheA*algorithm,startingwithasimpleloop:
while(true){}
Untilotherwisespecified,therestofthecodeinthissectionwillappearinsideofthisloop,intheordershown.
Fromhere,thefirststepistocheckthegoalstate.Inthiscase,thegoalistofindapathfortheplayertofollowtothenextwaypoint,whichhappenswhenthenodeobject’spositionis(endx,endy).Thus,tocheckthegoalstate,theprogramneedstocheckwhethernodehasreachedthosecoordinatesornot.Here’showthatcheckshouldlook:
if(node->x==endx&&node->y==endy){makeList<WIDTH,HEIGHT>(node,allNodes,path);returntrue;}
Whenthegoalstateismet,theprogramreportstruebacktothecallerandfillspathwiththefinalpath.Fornow,assumeafunctioncalledmakeList()canfillinpathforyou;I’llshowyouthisfunctionshortly.Ifthegoalstateisn’tmet,youneedtoexpandthechildrenofnode,whichisactuallyaprettycomplicatedprocess:
autochildren=node->getChildren(WIDTH,HEIGHT);for(autoc=children.begin();c!=children.end();c++){
➊if(map[(*c)->x][(*c)->y]==BLOCKING)continue;autofound=std::find(allNodes.rbegin(),allNodes.rend(),*c);➋if(found!=allNodes.rend()){➌if(*found>*c){(*found)->g=(*c)->g;(*found)->parent=(*c)->parent;(*found)->updateScore(endx,endy);}}else{(*c)->updateScore(endx,endy);➍frontier.push(*c);➎allNodes.push_back(*c);}}
Aftercallingnode->getChildrentogeneratealistofnodesthatcanbeaddedtothefrontier,thecodeiteratesovereachchildandignoresanythatareonblockingtiles➊.Next,foreachchild,thecodecheckswhetheranodehasalreadybeenopenedatthesamecoordinates➋.Ifso,andifthescoreoftheexistingnodeisgreaterthanthescoreofthenewchild,theexistingnodeisupdatedtotheparent,cost,andscoreofthenewchildbytheif()statementat➌.Ifthenewchilddoesn’thaveabrother-from-another-mother,itwillbeaddedasistothefrontier➍andthenodelist➎.
Alsonoticethatstd::findusesthereversebeginandreverseenditeratorsofallNodesinsteadoftheregulariterators➊.Theexampledoesthisbecausenewnodesareappendedtotheendofthevectorandduplicatenodeswillbeclosetogether,soduplicateswillusuallybeclosertotheendofthevector.(Thisstepcouldalsobedonedirectlyagainstthefrontier,butstd::priority_queuedoesn’tallowiterationovernodesandwritingthesortinplacewouldmakethecodetoolargeforprint.)
Eventually,thefunctionwillrunoutofnewchildrentoaddtothefrontier;thefollowingif()statementhandlesthatsituation:
if(frontier.size()==0)returnfalse;➊node=frontier.top();➋frontier.pop();
Thiscodepointsnodetothecheapestnodefromthefrontier➊,removesitfromthefrontier➋,andletsthelooprepeat.Ifthefrontierendsup
empty,thefunctionreportsfalsebacktothecaller,sincethere’snothinglefttosearch.
CreatingthePathListFinally,it’stimetoimplementthemakeList()function:
template<intWIDTH,intHEIGHT>voidmakeList(AStarNodePtrend,std::vector<AStarNodePtr>nodes,intpath[WIDTH][HEIGHT]){for(auton=nodes.begin();n!=nodes.end();n++)➊path[(*n)->x][(*n)->y]=2;autonode=end;while(node.get()!=nullptr){➋path[node->x][node->y]=1;node=node->parent;}}
Thisfunctionupdatespathwithbothalistofclosednodes➊andthecalculatedpath➋.Forthisexample,thevalue2representstheclosednodesand1representsthepathnodes.Theprogramcalculatesnodesinthepathbyfollowingparentnodesfromthegoalnodeuntilitreachesthestartingnode,whichisanorphanwithnullptrasaparent.
WhenA*SearchesAreParticularlyUsefulMakesuretoplaywiththeexamplecodeandexecutablefortheprevioussection,becausethat’stheonlywayyou’llreallygetacquaintedwiththebehaviorofA*searches.Inmostnewergames,youshouldbeabletojustsendapacketwiththedestinationorevenemulateaclickonthemapatthedesiredspot,butwhenyoucomeacrossasituationwhereyouneedtocalculateapath,you’llbegladyoulearnedA*.
Thereareactuallymanysituationswherecalculatingapathcanbeuseful:
SelectingtargetsWhen your bot is selecting targets to attack, you may want to checkwhetheryourcharactercanactuallyreachthem.Otherwise,ifanenemy
isisolatedinanunreachableroom,youmightgetstuckinplacetryingtotargetthemforever!
SelectingcorpsesAs your looting state(s) determine which corpses to open, you canoptimizebyalwaystryingtoloottheclosestcorpsefirst.
EmulatingmousemovementsVery rarely, some heavily protected games actually correlate in-gameactionswithmousemovementstoensurethatthere’snobotrunning.Inthis case, you might need to emulate the mouse. Using a modifiedversionofA*where the screen is themap, there are no blocking tiles,and node costs are slightly randomized, you can calculate human-likepathsforyourmousetofollowwhenyousimulatemovement.
KitingmonstersIfyoueverneed towritecode tokitemonsters,youcan implementA*withagoalstateofbeingNunitsawayfromallcreatures.Usingthesamecostmechanismshown in thischapter,playwith theheuristic togiveahigher cost to nodes that are closer to creatures.Kiting isn’t exactly aconventionalusecase,andtheheuristicwillrequireabunchoftweaking,butitworksamazinglyonceyou’vegotitgoing.Someimplementationscankiteanynumberofmonstersbetterthanahuman!
PredictingenemymovementsIfyou’rewritingabotthatfightsotherplayers,youcanuseA*topredicttheirmovementsandactaccordingly.Forinstance,ifyourenemystartsrunning away, your bot can assume they are running to their base,calculatetheirroute,anduseaspelltoblocktheirpathoreventeleporttoalocationwhereitexpectsthemtobe.
ThesearejustafewusecasesforA*searches,andyou’lldefinitelyfindmanymoreasyouimproveyourbots.Fortherestofthechapter,I’lldescribesomepopularautomatedhacksthatyoucanimplementusingthetechniquesdescribedinthisbook.
OTHERUSESFORA*SEARCHA*isn’tjustforcalculatingpaths.WithabstractionsontopoftheAStarNodeclass,youcanadaptthesamealgorithmtoanysearchproblem.Realistically,A*isjustaweightediterationoveramultidimensionaldatasetthatiteratesuntilsomegoalobjectisfound,and,thus,itcansolveanyproblemthatcanberepresentedasamultidimensionaldataset.MoreadvancedapplicationsforA*includeplayingchessandcheckers,and—whenit’spairedwithathree-dimensionalManhattandistanceheuristicandadepth-firstsearchimplementation—evensolvingaRubik’scube.Sadly,I’mnotgoingtogointotheseusecases;ifyouwanttogetreallygoodwithsearchalgorithms,Iencourageyoutoresearchmoreonline.
CommonandCoolAutomatedHacksNowthatyou’veseenthedesignpatternsandalgorithmsneededtocreateefficient,self-teachingbots,it’stimetolearnaboutsomepopularautomatedhacksthatgobeyondsimplehealingandpathfinding.Let’sflyupto10,000feettoexploretwotypesofbotsatahighlevel.
LootingwithCavebotsWhilediscussingcontroltheory,statemachines,andsearchalgorithms,Itouchedontheideaofacavebotthatkillscreatures,grabsloot,andwalksaroundcaves.Theabilitiesofcavebotscanvarygreatly.
DepositingGoldandRestockingSuppliesIfyouwanttoleaveacharacterbottingfordaysonend,you’llneedadepositorandarefiller.Adepositorcandepositlootinyourbankorvault,whilearefillerrefillsyourpotions,runes,andothersupplies.Thesefeaturescanbedescribedwithsixbasicstates:
LeavespawnConditionmetifthecharacterisinthespawnareaorcave,
ifithasnothingtodeposit,andifithasenoughsupplies.Reachthisstatebyexitingthespawnareaorcave.
WalktotownConditionmetifthecharacterisinthespawnareaorcave.Reachthisstatebywalkingfromthespawnorcavetotown.
DepositConditionmetifthecharacterisinthespawnareaorcave,orifthecharacterisintownandhasnothingtodeposit.Reachthisstatebyputtinglootinthebankorvault.
WithdrawcashConditionmetifthecharacterisinthespawnareaorcave,isintownwithnosuppliestopurchase,orhasenoughgoldtopurchasesupplies.Reachthisstatebywithdrawinggoldfromthebankorvault.
PurchasesuppliesConditionmetifthecharacterisinthespawnareaorcaveorifthecharacterhasenoughsuppliestostarthunting.Reachbybuyingsupplies.
EnterspawnConditionmetifthecharacterisinthespawnareaorcave.Reachthisstatebywalkingtothespawnareaorcave.
Thesestateswouldcomebeforethestatesrelatedtofollowingwaypoints(Idescribeacoupleofthosestatesin“AComplexHypotheticalStateMachine”onpage228)inthevectorofStateDefinitionobjects.Placingthemfirstgivesthempriorityoverremaininginthecave,whilestillallowingthecharactertotarget,kill,andlootmonstersonthewaybacktotown.Dependingonwhereyou’rehuntingandhowyouwantthebottobehave,youmayalsotellyourtargetingstatesnottoattackcreaturesifthecharacterisn’tinthespawnareaorcave,andyoumightaddanextrastatebeforewalktotownthatattacksonlycreaturesthatblockthecharacter’spathtotown.Specifyingthatextrastateincreasesthebot’sefficiency,sincetripstoandfromtownwillbemuchquickerifthemonstersonthewayaren’tworthkilling.
UsingtheCharacterasBaitTwoothercavebotfeaturesthatcanmakeyourbotawesomeareluremodeanddynamiclure.Youwouldn’timplementthesetwofeaturesasactualstatesinacomplexbot;rather,you’dhavetheminformthebot’stargetingandwalkingstatestohelpthebotmakedecisions.
Youcancontrolluremodewithspecialwaypointsinyourpath,anditscodewilltellyourtargetingstatestoattackcreaturesonlyifthebotisstuck,similartothemechanismdiscussedforwalkingtoorfromtown.Thedifferenceisthatluremodecanbeswitchedonandoffatdifferentareasinthecave,allowingyoutoluremultiplemobsofmonsterstocertainlocationsbeforeattackingthem.Thiscanmakeyourbotmuchmoreefficient,ascertaintypesofcharactersmayexcelatkillingmanymonstersatonce.
Dynamiclureissimilar,butinsteadofturningitonandoffatdefinitelocationsviawaypoints,youcanautomaticallyturnluremodeonwhentherearen’tenoughmonsters.Forexample,abotwiththedynamiclurefeaturemighttellthetargetingstatesnottoattackanycreatureuntilfivemonstersareonscreen.Thetargetingstateswouldresumeattackingandkitinguntilallfivemonstersaredead,andthebotwouldsnapbackintoluremodeuntilasuitablysizedmobappearsagain.
Ifyourcharacterisquickenoughtooutrunmonsters,though,you’llneedtomodifyyourbot’swalkingstatestowalkslowlywhenluremodeisonandcreaturesarepresent.Otherwise,yourcharacterwillleavemobsbehindwithoutkillingthem.Youcanslowdownacharacterbyaddingastatebeforethefollowpathstateinyourstatemachinedefinitionthatdelaysmovementslightlywhenluremodeisonandanycreaturesaretoofaraway.
AllowingPlayerstoScriptCustomBehaviorsNearlyeverycavebotincludesascriptinginterfacethatallowsplayerstoaddtheirownbehaviors.Youcouldimplementthisinterfaceasawaytospecifycustomwaypointstofollow,spellstouse,oritemstoloot.Inmoreadvancedbots,youmightmakeyourtargeting,looting,walking,andluringsystemsasdynamicaspossiblesoplayerscanadduniquefeatures.IfyouimplementyourautomationinLua,thirdpartiescouldeasilyimproveandexpandyourbot’sabilities.
Makingyourboteasytowritescriptsfortakesalotofworkoffyourshoulders,sinceotherprogrammerswhoplaythegamemightreleasescriptstoaddsupportfornewhuntingspotsandimproveyourautomation.Suchscriptingservicesarecommoninbottingcommunities,andplayersoftencreateandsellprofessional-gradescriptsthatintegratewithbots.
AutomatingCombatwithWarbotsAnotherclassofautomatedbotsisusedforplayerversusplayer(PvP)combat.Thesewarbots,orPvPbots,havemanyfeaturescategorizedasresponsiveorESPhacks,sincethebotsfocusonrespondingtoincomingdamageorspells,revealinghiddenenemies,andgivingtheplayeraninformationadvantage.
Fullyautomatedwarbotsarerare,butI’vealreadylightlydiscussedhowyoucanusesomeautomationtechniquestomakesmarterhealers,teachbotstolandmoreaccurateskillshots,andpredictplayers’pathstostopthemintheirtracks.Let’sexploreafewothercoolhacksthatfallonthefringeofresponsive,ESP,andautomated.
NOTE
IngamesthatarecompletelyPvPbased,suchasbattlegroundsorreal-timestrategygames,someplayersmightalsojustcallthesebots,sincewarorPvPisthebot’sonlypurpose.
AutowallBotsIfyourcharacterhasa spell tocreatea temporarywall,youcancodeabot that automatically blocks enemy players when they enter smallcorridors.Usingerrorcorrection, thebotcould learnhow faraheadoftheenemytoplacethewall.Withsomereallycreativeengineering,thebot could even learn which enemies can jump over walls by checkingwhethereachenemymanagestogetpastthewallbeforeitdisappears.
AutosnipeBotsForcharacterswithalong-rangeskillshotorglobalexecutionspell,youcan use automation to detect when an enemy across themap has lowhealthandcastyourspelltokillthem.Youcanalsouseerrorcorrectiontomoreaccuratelyguesswheretoshootalong-rangeskillshot.Ifyou’reunabletocalculateexactdamageamounts,errorcorrectioncanalsohelpa bot determine howmuch damage a spell does and tweak the castingthresholdaccordingly.
AutokiteBotsIf you’re playing a carry character that does most of its damage by
attackingatashortdistance,youmightimplementabottoautomaticallykiteenemies.Usingasetofstatessimilartotheonesacavebotmightuseto kite monsters, you can make a bot that automatically kites enemycharacterswhenyouattack them.Whenyou stop targeting theenemy,the bot can stop kiting. Using A* search, you can improve the kitingmechanism to avoidmultiple enemies, or, if you want to escape whileattacking,guidethekitingmechanismbacktoasafeplace,suchasyourteam’sbaseoraneutrallocation.
ClosingThoughtsBythispoint,youshouldbereadytogooutandmakesomeprettyawesomebots.Don’tworryifyou’restillnotcompletelycomfortablewiththetechniquesinthischapter;thebestwaytolearnistojustdiveinandstarthacking.Usethethousandsoflinesofexamplecodeprovidedforthisbooktogetstartedwithoutworkingfromscratch,andmostofall,havefun!
Inthenextchapter,I’lldiscusswaysthatbotscanhidefromanti-cheatmechanisms,whicharepiecesofsoftwarethatgamesusetodetectandstopbotters.
12STAYINGHIDDEN
Gamehackingisanever-evolvingpractice,agameofcatandmousebetweenhackersandgamedeveloperswhereeachpartyworkstosubverttheother.Aslongaspeoplemakebots,gamecompanieswillfindwaystohinderbotadvancesandbanplayerswhousebots.Ratherthanmakingtheirgamesinherentlyhardertohack,though,gamecompaniesfocusondetection.
Thelargestgamecompanieshaveverysophisticateddetectionsuitescalledanti-cheatsoftware.Inthebeginningofthischapter,I’lldiscussthecapabilitiesofthemostcommonanti-cheatsuites.Afterrevealinghowthesesuitesdetectbots,I’llteachyousomepowerfulwaystoevadethem.
ProminentAnti-CheatSoftwareThebest-knownanti-cheatsuitesusethesamemethodsasmostantivirussoftwaretoscanforbotsandflagthemasthreats.Someanti-cheatsuitesarealsodynamic,meaningtheirinnerworkingsandcapabilitiescanchangebasedonthegamethey’reprotecting.Anti-cheatsoftwaredevelopersalsotrackdownandpatchtheirsuitesagainstbypasssoftware,soalwaysdoyourownin-depthresearchofanyanti-cheatsoftwarethatyoumightface.
Whenthesesuitesdetectabotter,theyflagthebotter’saccountforbanishment.Everyfewweeks,gamecompanyadministratorsbantheflaggedplayersinabanwave.Gamecompaniesusebanwavesinsteadof
instantaneousbansbecausebanninginwavesismoreprofitable.Ifbottersarebannedafterafewweeksofplaying,theirfamiliaritywiththegamewillmakethemmorelikelytobuyanewaccountthaniftheywerebannedthemomenttheirbotstartedrunning.
Therearedozensofanti-cheatsuites,butI’llfocusonthefivepackagesthatarethemostcommonandthoroughlyunderstood:PunkBuster,ESEAAnti-Cheat,ValveAnti-Cheat(VAC),GameGuard,andWarden.
ThePunkBusterToolkitPunkBuster,madebyEvenBalanceInc.,istheoriginalanti-cheattoolkit.ManygamesusePunkBuster,butit’smostcommoninfirst-personshootergameslikeMedalofHonor,FarCry3,andseveralinstallmentsoftheBattlefieldseries.
Thetoolkitusesamyriadofdetectionmethods,themostformidableofwhicharesignature-baseddetection(SBD),screenshots,andhashvalidation.PunkBusterisalsoknownforimposinghardwarebansthatpermanentlybanacheater’scomputer,ratherthanjusttheirgameaccount,bysavingafingerprintofthehardware’sserialnumbersandblockingloginsfromamachinethatmatchesit.
Signature-BasedDetectionPunkBusterscansthememoryofallprocessesonasystemrunningagamethatemploysit,searchingforbytepatternsuniquetoknowncheatsoftware,calledsignatures.IfPunkBusterdetectsasignature,theplayerisflaggedforaban.PunkBustercarriesoutmemoryscansfromusermodeusingtheNtQueryVirtualMemory()WindowsAPIfunction,anditsometimesrunsscansfrommultiplehiddenprocesses.
Signature-baseddetectionisblindtocontextbydesign,anditultimatelysuffersfromafatalflaw:falsepositives.OnMarch23,2008,ateamofhackerssetouttoprovetheexistenceofthisflawbyspammingpublicchatroomswithatextstringthatPunkBusterwouldidentifyasabotsignature.SinceSBDblindlyscansprocessmemoryformatchingpatterns,anyandalllegitimateplayersinsidethesepublicchatroomswereflaggedasbotters.
Thiscausedthousandsoffairplayerstobebannedwithnojustification.AsimilarsituationhappenedagaininNovember2013:PunkBusterfalselybannedthousandsofplayersonBattlefield4.Thattime,noonewastryingtoproveapoint;thecompanyhadjustaddedabadsignaturetoitssoftware.
PunkBusterresolvedbothoftheseissuesbyrestoringtheplayers’accounts,buttheseincidentsshowjusthowaggressiveitsflavorofSBDis.Inthetimesincetheseattacks,though,PunkBuster’sSBDhasreducedthenumberoffalsepositivesbycheckingonlyforsignaturesatpredefinedbinaryoffsets.
ScreenshotsAsanothermethodofbotdetection,PunkBusteralsoperiodicallytakesscreenshotsofaplayer’sscreenandsendsthemtothecentralgameserver.Thisformofdetectionisanuisance,andit’sweakcomparedtoSDB.Game-hackingcommunitiesspeculatethatPunkBusterimplementedthisfeaturetogivegameadminsproofagainstbotterswhodisputebans.
HashValidationInadditiontoemployingSBDandscreenshots,PunkBusterdetectsbotsbycreatingcryptographichashesofagame’sexecutablebinariesonaplayer’ssystemandcomparingthemtohashesstoredonacentralserver.Ifthehashesdonotmatch,theplayerisflaggedforaban.Thischeckiscarriedoutonlyonthebinariesonthefilesystem,notonin-memorybinaries.
TheESEAAnti-CheatToolkitTheESEAAnti-CheattoolkitisusedbytheE-SportsEntertainmentAssociation(ESEA),primarilyforitsCounter-Strike:GlobalOffensiveleague.UnlikePunkBuster,thissuiteisknownforgeneratingveryfewfalsepositivesandbeinghighlyeffectiveatcatchingcheaters.
ESEAAnti-Cheat’sdetectioncapabilitiesresemblethoseofPunkBuster,withonenoteworthydifference.ESEAAnti-Cheat’sSBDalgorithmiscarriedoutfromakernel-modedriverusingthreedifferentWindowsKernelfunctions:theMmGetPhysicalMemoryRanges()function,theZwOpenSection()
function,andtheZwMapViewOfSection()function.Thisimplementationmakestheanti-cheatsystemnearlyimmunetomemoryspoofing(acommonwaytodefeatSBD),asthefunctionsusedbythescanaremuchhardertohookwhenthey’recalledfromadriver.
TheVACToolkitVACisthetoolkitValveCorporationappliestoitsowngamesandmanyofthethird-partygamesavailableonitsSteamgamingplatform.VACusesSDBandhashvalidationmethodsthatresemblePunkBuster’sdetectiontechniques,anditalsousesDomainNameSystem(DNS)cachescansandbinaryvalidation.
DNSCacheScansDNSisaprotocolthatconvertsbetweendomainnamesandIPaddressessmoothly,andtheDNScacheiswherethatinformationgetsstoredonacomputer.WhenVAC’sSBDalgorithmdetectscheatsoftware,VACscanstheplayer’sDNScacheforanydomainnamesassociatedwithcheatingwebsites.It’snotcertainwhetherapositiveDNScachescanisrequiredforVAC’sSBDalgorithmtoflagaplayerforbanishment,oriftheDNScachescansimplyactsasanothernailinthecoffinforplayerswhoarealreadyflaggedbySBD.
NOTE
ToseeyourDNScache,enteripconfig/displaydnsatacommandprompt.Yes,VAClooksatallofthat.
BinaryValidationVACalsousesbinaryvalidationtopreventin-memorytamperingofexecutablebinaries.ItscansformodificationslikeIAT,jump,andcodehookingbycomparinghashesofin-memorybinarycodetohashesofthesamecodeinthebinariesonthefilesystem.Ifitfindsamismatch,VACflagstheplayerforaban.
Thisdetectionmethodisformidable,butValve’sinitialimplementationofthealgorithmwasflawed.InJuly2010,VAC’sbinaryvalidationfalselybanned12,000CallofDutyplayers.ThebinaryvalidationmodulefailedtoaccountforaSteamupdate,anditbannedtheplayerswhentheirin-memorycodedidnotmatchtheupdatedbinariesonthefilesystem.
FalsePositivesVAChashadotherissueswithfalsepositives.Itsinitialreleaseroutinelybannedfairplayersfor“faultymemory.”ThissameearlyversionbannedplayersforusingCedega,aplatformthatranWindowsgamesonLinux.AndonApril1,2004,Valvefalselybannedacouplethousandplayersduetoaserver-sideglitch.Ontwoseparateoccasions,oneinJune2011andoneinFebruary2014,VACalsofalselybannedthousandsofTeamFortress2andCounter-Strikeplayersduetobugsthatthecompanyrefusestodisclose.AswithPunkBuster,theseincidentsshowthatVACisveryaggressive.
TheGameGuardToolkitGameGuardisananti-cheattoolkitmadebyINCAInternetCo.Ltd.andusedbymanyMMORPGs,includingLineageII,CabalOnline,andRagnarokOnline.InadditiontosomemildlyaggressiveSBD,GameGuardusesrootkitstoproactivelypreventcheatsoftwarefromrunning.
User-ModeRootkitGameGuardutilizesauser-moderootkittodenybotsaccesstotheWindowsAPIfunctionstheyusetooperate.Therootkithooksthefunctionsattheirlowest-levelentrypoint,ofteninsideundocumentedfunctionsinntdll.dll,user32.dll,andkernel32.dll.ThesearethemostnotableAPIfunctionsGameGuardhooks,andhere’swhatGameGuarddoesfrominsideeachhookedfunction:
NtOpenProcess()BlocksanyOpenProcess()attemptsonthegamebeingprotected.
NtProtectVirtualMemory()BlocksanyVirtualProtect()or
VirtualProtectEx()attemptsonthegame.
NtReadVirtualMemory()andNtWriteVirtualMemory()BlockanyReadProcessMemory()andWriteProcessMemory()attemptsonthegame.
NtSuspendProcess()andNtSuspendThread()BlockanyattemptstosuspendGameGuard.
NtTerminateProcess()andNtTerminateThread()BlockanyattemptstoterminateGameGuard.
PostMessage(),SendMessage(),andSendInput()Blockanyattemptstosendprogrammaticinputtothegame.
SetWindowsHookEx()Preventsbotsfromgloballyinterceptingmouseandkeyboardinput.
CreateProcessInternal()Automaticallydetectsandhooksintonewprocesses.
GetProcAddress(),LoadLibraryEx(),andMapViewOfFileEx()PreventanyattempttoinjectlibrariesintothegameorGameGuard.
Kernel-ModeRootkitGameGuardalsousesadriver-basedrootkittopreventbotsthatworkinthekernel.Thisrootkithasthesameabilitiesasitsuser-modecounterpart,anditworksbyhookingZwProtectVirtualMemory(),ZwReadVirtualMemory(),ZwWriteVirtualMemory(),SendInput(),andsimilarfunctions.
TheWardenToolkitWarden,madeexclusivelyforBlizzard’sgames,isbyfarthemostadvancedanti-bottoolkitI’veencountered.It’shardtosaywhatexactlyWardendoes,becauseitdownloadsdynamiccodeatruntime.Thiscode,deliveredascompiledshellcode,typicallyhastworesponsibilities:
•Detectbots.
•Periodicallysendaheartbeatsignaltothegameserver.Thevaluesentisnotpredefinedbutinsteadisgeneratedbysomesubsetofthedetectioncode.
code.
IfWardenfailstocompletethesecondtaskorsendsthewrongvalue,thegameserverwillknowthatit’sbeendisabledortamperedwith.Furthermore,abotcan’tdisablethedetectioncodeandleavetheheartbeatcoderunning.
THEHALTINGPROBLEM
AbotthatcoulddisableWarden’sdetectioncodeandstillsendtheheartbeatsignalwouldsolvethehaltingproblem,whichAlanTuringprovedtobeimpossiblein1936.Thehaltingproblemistheproblemofdetermining,withagenericalgorithm,whetheraprogramwillfinishrunningorcontinueforever.BecauseWardendoestwotasksusingthesameshellcode,writingagenericalgorithmthatcandisablejustonetaskisavariationofthehaltingproblem:thealgorithmcan’tbesurewhichpartsofthecodewilldefinitelyexecute,whichpartswon’t,andwhichpartsareresponsibleforeachtask.
Wardenisformidablebecauseyounotonlyhavenowaytoknowwhatyou’rehidingfrombutalsohavenowaytodisablethetoolkit.Evenifyoumanagetoavoiddetectiontoday,anewdetectionmethodmightbeusedtomorrow.
Ifyouplanonpubliclydistributingbots,youwilleventuallymeetoneoftheanti-cheatsolutionsdescribedintheprevioussections—andyou’llhavetobeatit.Dependingonyourbot’sfootprint,thetypeofdetectioninthegameyou’rebotting,andyourimplementation,thedifficultyofevadingoneofthesetoolkitscanrangefromtrivialtoextremelyhard.
CarefullyManagingaBot’sFootprintAbot’sfootprintishowmanyunique,detectablecharacteristicsithas.Forexample,abotthathooks100functionswilltypicallybeeasiertodetectthanabotthathooksonly10functionsbecausetheformermakesanorderofmagnitudemorechangestoagame’scodethanthelatter.Sinceatargeted
detectionsystemneedstodetectonlyonehook,thedeveloperoftheformerbotneedstospendmuchmoretimemakingsureallofthebot’shooksareasstealthyaspossible.
Anotherfootprintcharacteristicishowdetailedabot’suserinterfaceis.Ifaknownbothasmanydialogboxesthatallhavespecifictitles,agamecompanycanjusthaveitsanti-cheatsoftwaredetectthebotbysearchingforwindowsthathavethosetitles.Thissamebasicreasoningcanbeusedwithprocessnamesandfilenames.
MinimizingaBot’sFootprintDependingonhowyourbotworks,therearemanywaystominimizeitsfootprint.Ifyourbotreliesheavilyonhooks,forinstance,youcanavoiddirectlyhookingagame’scodeandinsteadfocusonhookingWindowsAPIfunctions.WindowsAPIhookingissurprisinglycommon,sodeveloperscan’tassumeaprogramthathookstheWindowsAPIisabot.
Ifyourbothasawell-defineduserinterface,youcanmasktheinterfacebyremovingallstringsfromwindowbars,buttons,andsoon.Instead,displayimagesthatshowtext.Ifyou’reworriedaboutspecificprocessnamesorfilenamesbeingdetectedbytheanti-cheatsoftware,usegenericfilenamesandmakeyourbotcopyitselftoanew,randomizeddirectoryeverytimeitlaunches.
MaskingYourFootprintMinimizingyourfootprintisapreferredwaytoavoiddetection,butit’snotnecessary.Youcanalsoobfuscateyourbot,makingitharderforanyonetofigureouthowitworks.Obfuscationcanpreventbothanti-botdevelopersfromtryingtodetectyourbotandotherbotdevelopersfromanalyzingyourbottostealproprietaryfunctionality.Ifyousellyourbot,obfuscationpreventspeoplefromcrackingittobypassyourpurchaseverification,too.
Onecommontypeofobfuscationiscalledpacking.Packinganexecutableencryptsitandhidesitinsideanotherexecutable.Whenthecontainerexecutableislaunched,thepackedexecutableisdecryptedandexecutedin-memory.Whenabotispacked,analyzingthebinarytolearnwhatthebotdoesisimpossible,anddebuggingthebotprocessismuchharder.SomecommonpackerprogramsareUPX,Armadillo,Themida,andASPack.
TeachingaBottoDetectDebuggersWhenanti-botdevelopers(orotherbotcreators)candebugabot,theycanfigureouthowitworksandthushowtostopit.Ifsomeoneisactivelytryingtopickapartabot,packingtheexecutablemaynotbeenoughtoevadethem.Toprotectagainstthis,botsoftenemployanti-debuggingtechniques,whichobfuscatecontrolflowbychangingthebot’sbehaviorwhenadebuggerisdetected.Inthissection,I’llquicklycoversomewell-knownmethodsfordetectingwhenadebuggerisattachedtoyourbot,andinthenext,I’llshowyousometricksforobfuscation.
CallingCheckRemoteDebuggerPresent()CheckRemoteDebuggerPresent()isaWindowsAPIfunctionthatcantellyouifadebuggerisattachedtothecurrentprocess.Codetocheckforadebuggermightlooklikethis:
boolIsRemoteDebuggerPresent(){BOOLdbg=false;CheckRemoteDebuggerPresent(GetCurrentProcess(),&dbg);returndbg;}
Thischeckisprettystraightforward—itcallsCheckRemoteDebuggerPresent()withthecurrentprocessandapointertothedbgBoolean.Callingthisfunctionistheeasiestwaytodetectadebugger,butit’salsoveryeasyforadebuggertoevade.
CheckingforInterruptHandlersInterruptsaresignalstheprocessorsendstotriggeracorrespondinghandlerintheWindowskernel.Interruptsaretypicallygeneratedbyhardwareevents,buttheycanalsobegeneratedinsoftwareusingtheINTassemblyinstruction.Thekernelallowssomeinterrupts—namely,interrupts0x2Dand0x03—totriggeruser-modeinterrupthandlersintheformofexceptionhandlers.Youcantakeadvantageoftheseinterruptstodetectdebuggers.
Whenadebuggersetsabreakpointonaninstruction,itreplacesthatinstructionwithabreakpointinstruction,suchasINT0x03.Whentheinterruptisexecuted,thedebuggerisnotifiedviaanexceptionhandler,whereithandlesthebreakpoint,replacestheoriginalcode,andallowstheapplicationtoresumeexecutionseamlessly.Whenfacedwithan
applicationtoresumeexecutionseamlessly.Whenfacedwithanunrecognizedinterrupt,somedebuggersevensilentlystepoverthatinterruptandallowexecutiontocontinuenormally,withouttriggeringanyotherexceptionhandlers.
Youcandetectthisbehaviorbypurposelygeneratinginterruptswithinexceptionhandlersinyourcode,asshowninListing12-1.
inlineboolHas2DBreakpointHandler(){__try{__asmINT0x2D}__except(EXCEPTION_EXECUTE_HANDLER){returnfalse;}returntrue;}
inlineboolHas03BreakpointHandler(){__try{__asmINT0x03}__except(EXCEPTION_EXECUTE_HANDLER){returnfalse;}returntrue;}
Listing12-1:Detectinginterrupthandlers
Duringnormalexecution,theseinterruptstriggertheexceptionhandlerssurroundingtheminthecode.Duringadebuggingsession,somedebuggersmightintercepttheexceptionsgeneratedbytheseinterruptsandsilentlyignorethem,preventingthesurroundingexceptionhandlersfromexecuting.Thus,iftheinterruptsdon’ttriggeryourexceptionhandler,thenadebuggerispresent.
CheckingforHardwareBreakpointsDebuggerscanalsosetbreakpointsusingtheprocessor’sdebugregisters;thesearecalledhardwarebreakpoints.Adebuggercansetahardwarebreakpointonaninstructionbywritingtheaddressoftheinstructiontooneofthefourdebugregisters.
Whenanaddresspresentonadebugregisterisexecuted,thedebuggerisnotified.Todetecthardwarebreakpoints(andthus,thepresenceofadebugger),youcancheckfornonzerovaluesonanyofthefourdebugregisterslikethis:
boolHasHardwareBreakpoints(){CONTEXTctx={0};ctx.ContextFlags=CONTEXT_DEBUG_REGISTERS;autohThread=GetCurrentThread();
if(GetThreadContext(hThread,&ctx)==0)returnfalse;return(ctx.Dr0!=0||ctx.Dr1!=0||ctx.Dr2!=0||ctx.Dr3!=0);}
PrintingDebugStringsOutputDebugString()isaWindowsAPIfunctionthatcanbeusedtoprintlogmessagestoadebuggerconsole.Ifnodebuggerispresent,thefunctionwillreturnwithanerrorcode.Ifadebuggerispresent,however,thefunctionwillreturnwithnoerrorcode.Here’showyoucanusethisfunctionasatrivialdebuggercheck:
inlineboolCanCallOutputDebugString(){SetLastError(0);OutputDebugStringA("test");return(GetLastError()==0);}
LiketheCheckRemoteDebuggerPresent()method,thismethodisverystraightforwardbutalsoveryeasyforadebuggertoevade.
CheckingforDBG_RIPEXCEPTIONHandlersDebuggerstypicallyhaveexceptionhandlersthatblindlycatchexceptionswithWindows’DBG_RIPEXCEPTIONexceptioncode,makingthatcodeaclearwaytospotadebugger.YoucandetecttheseexceptionhandlersinmuchthesamewayListing12-1detectsinterrupthandlers:
#defineDBG_RIPEXCEPTION0x40010007inlineboolhasRIPExceptionHandler(){__try{RaiseException(DBG_RIPEXCEPTION,0,0,0);}__except(EXCEPTION_EXECUTE_HANDLER){returnfalse;}returntrue;}
TimingControl-CriticalRoutinesIfananti-botdeveloperisdebuggingyourbot,thedeveloperwilllikelyplacebreakpointsonandsingle-stepthroughpartsofyourcodethatarecriticaltothebot’sbehavior.Youcandetectthisactivitybymeasuringcodeexecutiontimes;whensomeonestepsthroughcode,executiontakesalotlongerthanusual.
usual.Forexample,ifafunctiononlyplacessomehooks,youcanbesurethat
thecodeshouldn’ttakemorethanatenthofasecondtodothememoryprotection.YoucouldchecktheexecutiontimeformemoryprotectionwithhelpfromtheGetTickCount()WindowsAPIfunction,asfollows:
--snip--autostartTime=GetTickCount();protectMemory<>(...);if(GetTickCount()-startTime>=100)debuggerDetectedGoConfuseIt();--snip--
CheckingforDebugDriversSomedebuggersloadkernel-modedriverstoassisttheiroperation.Youcandetectthesedebuggersbyattemptingtogetahandletotheirkernel-modedrivers,likethis:
boolDebuggerDriversPresent(){//anarrayofcommondebuggerdriverdevicenamesconstchardrivers[9][20]={"\\\\.\\EXTREM","\\\\.\\ICEEXT","\\\\.\\NDBGMSG.VXD","\\\\.\\RING0","\\\\.\\SIWVID","\\\\.\\SYSER","\\\\.\\TRW","\\\\.\\SYSERBOOT","\0"};for(inti=0;drivers[i][0]!='\0';i++){autoh=CreateFileA(drivers[i],0,0,0,OPEN_EXISTING,0,0);if(h!=INVALID_HANDLE_VALUE){CloseHandle(h);returntrue;}}returnfalse;}
Thereareafewcommonkernel-modedriverdevicenamestocheckfor,like\\\\.\\EXTREMandtheothersshowninthedriversarray.Ifthishandle-fetchingcodesucceeds,thenthere’sadebuggerrunningonthesystem.Unlikewiththepreviousmethods,though,obtainingahandletooneofthosedriversdoesn’talwaysmeanthedebuggerisattachedtoyourbot.
Anti-DebuggingTechniquesOnceyoudetectadebugger,therearemultiplewaystoobfuscateyourcontrolflow.Forinstance,youmighttrytocrashthedebugger.ThefollowingcodecrashesOllyDbgv1.10:
OutputDebugString("%s%s%s%s");
Thestring"%s%s%s%s"containsformatspecifiers,andOllyDbgpassesittoprintf()withoutanyextraparameters,whichiswhythedebuggercrashes.Youcouldplacethiscodeinafunctionthatgetscalledinresponsetodetectingadebugger,butthisoptionworksonlyagainstOllyDbg.
CausinganUnavoidableInfiniteLoopAnotherobfuscationmethodtotryisoverloadingthesystemuntilthepersondebuggingyourbotisforcedtoclosethebotanddebugger.Thisfunctiondoesthetrick:
voidSelfDestruct(){std::vector<char*>explosion;while(true)explosion.push_back(newchar[10000]);}
Theinfinitewhileloopjustkeepsaddingelementstoexplosionuntiltheprocessrunsoutofmemoryorsomeonepullstheplug.
OverflowingtheStackIfyouwanttoreallyconfusetheanalyst,youcanmakeachainoffunctionsthateventuallycauseastackoverflow,butinanindirectway:
#include<random>typedefvoid(*_recurse)();voidrecurse1();voidrecurse2();voidrecurse3();voidrecurse4();voidrecurse5();_recurserecfuncs[5]={&recurse1,&recurse2,&recurse3,&recurse4,&recurse5};voidrecurse1(){recfuncs[rand()%5]();}voidrecurse2(){recfuncs[(rand()%3)+2]();}
voidrecurse3(){if(rand()%100<50)recurse1();elserecfuncs[(rand()%3)+1]();}voidrecurse4(){recfuncs[rand()%2]();}voidrecurse5(){for(inti=0;i<100;i++)if(rand()%50==1)recfuncs[i%5]();recurse5();}//callanyoftheabovefunctionstotriggerastackoverflow
Inanutshell,thesefunctionsrandomlyandinfinitelyrecurseuntilthere’snoroomleftonthecallstack.Causingtheoverflowindirectlymakesithardfortheanalysttopauseandexaminepreviouscallsbeforetheyrealizewhat’shappened.
CausingaBSODIfyou’reseriousaboutobfuscation,youcaneventriggeraBlueScreenofDeath(BSOD)whenyoudetectadebugger.Onewaytodothatistosetyourbot’sprocessascriticalusingtheSetProcessIsCritical()WindowsAPIfunctionandthencallexit(),sinceWindowswilltriggeraBSODwhenacriticalprocessiskilled.Here’showyoumightdothat:
voidBSODBaby(){typedeflong(WINAPI*RtlSetProcessIsCritical)(BOOLEANNew,BOOLEAN*Old,BOOLEANNeedScb);autontdll=LoadLibraryA("ntdll.dll");if(ntdll){autoSetProcessIsCritical=(RtlSetProcessIsCritical)GetProcAddress(ntdll,"RtlSetProcessIsCritical");if(SetProcessIsCritical)SetProcessIsCritical(1,0,0);}}
BSODBaby();exit(1);
Ormaybeyou’reevil,inwhichcaseyoucandothis:
BSODBaby();OutputDebugString("%s%s%s%s");recurse1();exit(1);
Assumingyou’veimplementedallofthetechniquesdescribedinthissection,thiscodewouldcauseaBSOD,crashthedebugger(ifit’sOllyDbgv1.10),overflowthestack,andexittherunningprogram.Ifanyoneofthemethodsfailsorgetspatched,theanalyststillhastodealwiththeremainingonesbeforetheycancontinuedebugging.
DefeatingSignature-BasedDetectionEvenwithamazingobfuscation,youwon’teasilybeatsignaturedetection.Engineerswhoanalyzebotsandwritesignaturesareveryskilled,andobfuscationis,atbest,anuisancethatmakestheirjobmarginallyharder.
TocompletelyevadeSBD,youneedtosubvertthedetectioncode.ThisrequiresknowingexactlyhowtheSBDworks.PunkBuster,forinstance,usesNtQueryVirtualMemory()toscanthememoryofallrunningprocessesforanysignatures.Ifyouwanttobypassthis,youcaninjectcodeintoallPunkBusterprocesseswithahookontheNtQueryVirtualMemory()function.
Whenthefunctiontriestoquerymemoryfromyourbotprocess,youcangiveitwhateverdatayouwant,likethis:
NTSTATUSonNtQueryVirtualMemory(HANDLEprocess,PVOIDbaseAddress,MEMORY_INFORMATION_CLASSmemoryInformationClass,PVOIDbuffer,ULONGnumberOfBytes,PULONGnumberOfBytesRead){
//ifthescanisonthisprocess,makesureitcan'tseethehookDLLif((process==INVALID_HANDLE_VALUE||process==GetCurrentProcess())&&baseAddress>=MY_HOOK_DLL_BASE&&baseAddress<=MY_HOOK_DLL_BASE_PLUS_SIZE)➊returnSTATUS_ACCESS_DENIED;
//ifthescanisonthebot,zerothereturnedmemoryautoret=origNtQueryVirtualMemory(process,baseAddress,memoryInformationClass,buffer,numberOfBytes,numberOfBytesRead);if(GetProcessId(process)==MY_BOT_PROCESS)➋ZeroMemory(buffer,numberOfBytesRead);returnret;}
ThisonNtQueryVirtualMemory()hookreturnsSTATUS_ACCESS_DENIED➊whenNtQueryVirtualMemory()triestoquerythehookDLL’smemory,butitgiveszeroedmemory➋whenNtQueryVirtualMemory()triestoquerythebot’smemory.Thedifferenceisn’tforanyspecificreason;I’mjustshowingtwowaysyoucanhidefromtheNtQueryVirtualMemory()functioncall.Ifyou’rereallyparanoid,youcanevenreplacetheentirebufferwitharandombytesequence.
Ofcourse,thismethodworksonlyforSBDthathappensfromusermode,liketheSBDinPunkBusterorVAC.SBDthathappensfromthedriver,likeESEA’s,orthatisn’tpredictable,likeWarden’s,isn’taseasytobypass.
Inthosecases,youcantakeprecautionstoeliminateuniquesignaturesinyourbot.Ifyou’redistributingthebottomorethanadozenorsopeople,however,removingalldistinguishingpropertiesistricky.Tothrowanalystsoffthescent,eachtimeyougivesomebodyacopyofthebot,youcouldtrysomecombinationofthefollowing:
•Compilingthebotusingadifferentcompiler
•Changingthecompileroptimizationsettings
•Togglingbetweenusing__fastcalland__cdecl
•Packingthebinariesusingadifferentpacker
•Switchingbetweenstaticanddynamiclinkingofruntimelibraries
Varyingtheseelementscreatesadifferentassemblyforeachuser,butthere’salimitonhowmanyuniqueversionsofthebotyoucanproducethatway.Pastsomepoint,thismethoddoesn’tscaletodemand,andeventually,gamecompanieswillhavesignaturesforeveryincarnationofyourbot.
Apartfromobfuscationandcodemutation,therearen’tmanywaystodefeatadvancedSBDmechanisms.Youcouldimplementyourbotinadriverorcreateakernel-moderootkittohideyourbot,buteventhosemethodsaren’tfoolproof.
NOTE
Thisbookdoesn’tcoverimplementingabotinadriverorcreatingarootkitto
hideabot,asbothtopicsareprettycomplex.Rootkitdevelopmentaloneisasubjectthatdozensofbookshavecoveredalready.I’drecommendBillBlunden’sTheRootkitArsenal:EscapeandEvasioninTheDarkCornersofTheSystem(Jones&BartlettLearning,2009).
Somegamehackerstrytocovereverysinglebase,hookingeverymemory-readingfunctionandtheentirefilesystemAPI,butstillgetcaughtbydeterminedsystemslikeWarden.Infact,IrecommendstayingawayfromWardenandBlizzardatallcosts.
DefeatingScreenshotsIfyouencounteradetectionmechanismthatusesscreenshotsasadditionalprooftonailbotters,you’reinluck.Bypassingscreenshotmechanismsiseasy:don’tletyourbotbeseen.
YoucansubvertthistypeofdetectionbykeepingaminimalUIandmakingnovisiblydistinguishablechangestothegameclient.IfyourbotrequiresaHUDorotherdistinctiveUIdisplays,though,don’tfret—youcanhaveyourcakeandeatit,too.Aslongasyoucaninterceptthescreenshotcode,youcanhideyourfingerprintswhileascreenshotistaken.
InsomeversionsofPunkBuster,forexample,theWindowsAPIfunctionGetSystemTimeAsFileTime()iscalledjustbeforeascreenshotistaken.YoucanuseahookonthisfunctiontoquicklyhideyourUIforafewsecondstoensureit’snotseen:
voidonGetSystemTimeAsFileTime(LPFILETIMEsystemTimeAsFileTime){myBot->hideUI(2000);//hideUIfor2secondsorigGetSystemTimeAsFileTime(systemTimeAsFileTime);}
JusthookGetSystemTimeAsFileTime()usingthetechniquesdescribedin“HookingtoRedirectGameExecution”onpage153,writeahideUI()function,andcallthehideUI()functionbeforeexecutionresumes.
DefeatingBinaryValidationDefeatingbinaryvalidationisassimpleasnotplacinghooksinsidegame-
Defeatingbinaryvalidationisassimpleasnotplacinghooksinsidegame-specificbinaries.JumphooksandIAThooksonWindowsAPIfunctionsareextremelycommon,sowhereveryoucan,trytogetawaywithusingthosemethodsinsteadofusingjumpornear-callhooksinagamebinary.Incaseswhereyoumustdirectlyhookagame’scode,youcantricktheanti-cheatsoftware’sbinaryvalidationroutinesbyinterceptingthebinaryscanandspoofingthedatatomatchwhatthesoftwareexpectstosee.
LikeSBD,binaryvalidationoftenusesNtQueryVirtualMemory()toscanmemory.Totrickthevalidationcode,startwithahookonthatfunction.Then,writeafunctionlikethisonetospoofthedatawhenNtQueryVirtualMemory()iscalled:
NTSTATUSonNtQueryVirtualMemory(HANDLEprocess,PVOIDbaseAddress,MEMORY_INFORMATION_CLASSmemoryInformationClass,PVOIDbuffer,ULONGnumberOfBytes,PULONGnumberOfBytesRead){
autoret=origNtQueryVirtualMemory(process,baseAddress,memoryInformationClass,buffer,numberOfBytes,numberOfBytesRead);//placetrickycodesomewhereinherereturnret;}
Insidethishook,you’llneedtowatchforanymemoryscansovermemorythathasbeenmodifiedbyoneofyourhooks.
NOTE
ThisexampleassumesthebothasonlyonehookandthatvariablesprefixedwithHOOK_alreadyexistanddescribethecodethehookreplaces.
Listing12-2showssomescan-monitoringcode.
//isthescanonthecurrentprocess?boolcurrentProcess=process==INVALID_HANDLE_VALUE||process==GetCurrentProcess();
//isthehookinthememoryrangebeingscanned?autoendAddress=baseAddress+numberOfBytesRead-1;boolcontainsHook=(HOOK_START_ADDRESS>=baseAddress&&
HOOK_START_ADDRESS<=endAddress)||(HOOK_END_ADDRESS>=baseAddress&&HOOK_END_ADDRESS<=endAddress);➊if(currentProcess&&containsHook){//hidethehook}
Listing12-2:Checkingwhetherhookedmemoryisbeingscanned
Whenamemoryscanoverthehookedcodehappens(whichmakescurrentProcessandcontainsHookbecometrueatthesametime),codeinsidetheif()statement➊updatestheoutputbuffertoreflecttheoriginalcode.Thismeansyoumustknowwherethehookedcodeiswithinthescannedblock,takingintoaccountthefactthattheblockmayspanonlyasubsetofthehookedcode.
SoifbaseAddressmarkstheaddresswherethescanstarts,HOOK_START_ADDRESSmarksthespotwherethemodifiedcodestarts,endAddressmarkstheaddresswherethescanends,andHOOK_END_ADDRESSmarkstheaddresswherethemodifiedcodeends,youcanusesomesimplemathtocalculatewhichpartsofthemodifiedcodearepresentinwhichpartsofthebuffer.Youdosoasfollows,usingwriteStarttostoretheoffsetofthemodifiedcodeinthescanbufferandreadStarttostoretheoffsetofthescanbufferrelativetothemodifiedcode,incasethescanbufferstartsinthemiddleofthemodifiedcode:
intreadStart,writeStart;if(HOOK_START_ADDRESS>=baseAddress){readStart=0;writeStart=HOOK_START_ADDRESS-baseAddress;}else{readStart=baseAddress-HOOK_START_ADDRESS;writeStart=baseAddress;}
intreadEnd;if(HOOK_END_ADDRESS<=endAddress)readEnd=HOOK_LENGTH-readStart-1;elsereadEnd=endAddress–HOOK_START_ADDRESS;
Onceyouknowhowmanybytesyouneedtoreplace,wheretoputthem,andwheretogetthem,youcandothespoofwiththreelinesofcode:
char*replaceBuffer=(char*)buffer;
for(;readStart<=readEnd;readStart++,writeStart++)replaceBuffer[writeStart]=HOOK_ORIG_DATA[readStart];
Completelyassembled,thecodelookslikethis:
NTSTATUSonNtQueryVirtualMemory(HANDLEprocess,PVOIDbaseAddress,MEMORY_INFORMATION_CLASSmemoryInformationClass,PVOIDbuffer,ULONGnumberOfBytes,PULONGnumberOfBytesRead){autoret=origNtQueryVirtualMemory(process,baseAddress,memoryInformationClass,buffer,numberOfBytes,numberOfBytesRead);boolcurrentProcess=process==INVALID_HANDLE_VALUE||process==GetCurrentProcess();autoendAddress=baseAddress+numberOfBytesRead-1;boolcontainsHook=(HOOK_START_ADDRESS>=baseAddress&&HOOK_START_ADDRESS<=endAddress)||(HOOK_END_ADDRESS>=baseAddress&&HOOK_END_ADDRESS<=endAddress);if(currentProcess&&containsHook){intreadStart,writeStart;if(HOOK_START_ADDRESS>=baseAddress){readStart=0;writeStart=HOOK_START_ADDRESS-baseAddress;}else{readStart=baseAddress-HOOK_START_ADDRESS;writeStart=baseAddress;}
intreadEnd;if(HOOK_END_ADDRESS<=endAddress)readEnd=HOOK_LENGTH-readStart-1;elsereadEnd=endAddress–HOOK_START_ADDRESS;
char*replaceBuffer=(char*)buffer;for(;readStart<=readEnd;readStart++,writeStart++)replaceBuffer[writeStart]=HOOK_ORIG_DATA[readStart];}returnret;}
Ofcourse,ifyouhadmultiplehooksthatyouneededtohidefrombinaryvalidationscans,youwouldneedtoimplementthisfunctionalityinamorerobustwaythatwouldallowittotrackmultiplemodifiedcoderegionsaccordingly.
DefeatinganAnti-CheatRootkitGameGuardandsomeotheranti-cheatsuitescomewithuser-moderootkitsthatnotonlydetectbotsbutalsoproactivelypreventthemfromrunning.Todefeatthistypeofprotection,ratherthanthinkoutsidethebox,youcancompletelycopytheboxandworkinsidethatcopy.
Forexample,ifyouwanttowritememorytoagame,youmustcalltheWriteProcessMemory()function,whichisexportedbykernel32.dll.Whenyoucallthisfunction,itdirectlycallsNtWriteVirtualMemory()fromntdll.dll.GameGuardhooksntdll.NtWriteVirtualMemory()topreventyoufromwritingmemory.ButifNtWriteVirtualMemory()isexportedfrom,say,ntdll_copy.dll,GameGuardwon’thookthatfunction.
Thatmeansyoucancopyntdll.dllanddynamicallyimportallofthefunctionsyouneed,asfollows:
//copyandloadntdllcopyFile("ntdll.dll","ntdll_copy.dll");automodule=LoadLibrary("ntdll_copy.dll");
//dynamicallyimportNtWriteVirtualMemorytypedefNTSTATUS(WINAPI*_NtWriteVirtualMemory)(HANDLE,PVOID,PVOID,ULONG,PULONG);automyWriteVirtualMemory=(_NtWriteVirtualMemory)GetProcAddress(module,"NtWriteVirtualMemory");
//callNtWriteVirtualMemorymyWriteVirtualMemory(process,address,data,length,&writtenlength);
Aftercopyingntdll.dll,thiscodeimportstheNtWriteVirtualMemory()fromthecopywiththenamemyWriteVirtualMemory().Fromthere,thebotcanusethisfunctioninplaceoftheNtWriteVirtualMemory()function.They’reeffectivelythesamecodeinthesamelibrary,justloadedunderdifferentnames.
Copyingafunctionthatanti-cheatsoftwarehooksworksonlyifyoucallthatfunctionatitslowest-levelentrypoint,though.Ifthiscodecopiedkernel32.dllanddynamicallyimportedtheWriteProcessMemory()function,ananti-cheatrootkitwouldstillstopthebot,becausekernel32_copy.dllwouldstillrelyonntdll.NtWriteVirtualMemory()whencallingtheWriteProcessMemory()function.
DefeatingHeuristicsInadditiontoalloftheadvancedclient-sidedetectionmechanismswe’vejustdiscussed,gamecompanieswillemployserver-sideheuristicsthatcandetectbotssimplybymonitoringaplayer’sbehavior.Thesesystemslearntodistinguishbetweenhumanandautonomousplayerbehaviorthroughmachine-learningalgorithms.Theirdecision-makingprocessisofteninternalandincomprehensibletohumans,soit’sdifficulttopinpointexactlywhatfeaturesofgameplayleadtodetection.
Youdon’tneedtoknowhowsuchalgorithmsworktotrickthem;yourbotjustneedstoacthuman.Herearesomecommonbehaviorsthataredistinguishablydifferentbetweenhumansandbots:
IntervalsbetweenactionsManybots perform actions unreasonably fast or at consistent intervals.Bots will seem more human-like if they have a reasonable cooldownperiod between actions. They should also have some form ofrandomization to prevent them from repeating an action at a constantrate.
PathrepetitionBots that farm enemies automatically visit a preprogrammed list oflocations to kill creatures. These waypoint lists are often extremelyaccurate,indicatingeachlocationasanexactpixel.Humans,conversely,moveinlesspredictablewaysandvisitmoreuniquelocationsalongthewaytoafamiliararea.Toreplicatethisbehavior,abotmightwalktoarandomlocationwithinacertainrangeofatargetlocation,ratherthantothetargetlocationitself.Also,ifthebotrandomizestheorderinwhichitvisitstargetlocations,thevarietyofpathsittakeswillincreasefurther.
UnrealisticplaySome botters run their bots in the same location for hundreds ofconsecutivehours, buthumans can’t play a game that long.Encourageyourusers to refrain frombotting formore than eighthours at a timeand warn them that doing the same thing for seven straight days willdefinitelytriggeralarmsinaheuristicsystem.
PerfectaccuracyBotscanhitathousandheadshotsinarowwithoutfiringasingleextrabullet,andtheycanhiteveryskillshotwithconsistentprecision.Butit’svirtuallyimpossibleforahumantodothesame,soasmartbotshouldbeintentionallyinaccurateattimes.
Thesearejustafewexamples,butingeneral,youcansneakpastheuristicchecksifyoujustusecommonsense.Don’ttrytohaveabotdosomethingahumancan’t,anddon’thavethebotdoanysinglethingfortoolong.
ClosingThoughtsGamehackersandgamedevelopersareengagedinaconstantbattleofwits.Hackerswillkeepfindingwaystosubvertdetection,anddeveloperswillkeepfindingbetterwaystodetectthem.Ifyou’redetermined,however,theknowledgeinthischaptershouldhelpyoudefeatanyanti-cheatsoftwareyouencounter.
INDEX
AAbouttextfield,Trainergeneratordialog,9accessingmemory
ininjectedDLL,145–146forwritingandreading,122–124
ActionMessageFormat(AMF),169actorfunctions,216actuation,216,223Addresscolumn
EventPropertiesdialog,55OllyDbgdisassemblerpane,27
addresses,memory.SeememoryaddressesAddressSpaceLayoutRandomization(ASLR),128
bypassingininjectedDLL,146–147bypassinginproduction,128–130disablingforbotdevelopment,128inProcessExplorer,56,57
AdobeAIRhooking,169decode()function,172–173,174–175encode()function,171–172,174–175placinghooks,173–175RTMP,assessing,169–170
AdobeAIR.dll,173–175airlogtool,170alignment
innumericdata,68ofvariables,indatastructures,70–71
ambientlight,adding,190–192
AMF(ActionMessageFormat),169anti-cheatsoftware,245–246
anti-cheatrootkit,defeating,261–262binaryvalidation,defeating,259–261botfootprints,managing,250–256ESEAAnti-Cheattoolkit,247GameGuardtoolkit,248–249heuristics,defeating,262–263PunkBustertoolkit,246–247screenshots,defeating,258signature-baseddetection,evading,256–257VACtoolkit,247–248Wardentoolkit,249–250
anti-crowd-controlhacks,218anti-debuggingtechniques,251,255–256arithmeticinstructions,90–92A*searchalgorithm,234
cost,233creatingnode,234–237creatingpathlist,239–240score,234usesfor,240–241writingsearchfunction,237–239
ASLR.SeeAddressSpaceLayoutRandomization(ASLR)Asm2Clipboardplug-in,42assemblycode
copying,42tracing,32–33viewingandnavigatinginOllyDbg,27–29
assemblylanguage,78.Seealsox86assemblylanguageassemblypatterns,searchingfor,19–21AStarNodeclass,234–236AT&Tsyntax,80
autocombo,219autododge,219autokitebots,244automatichealer,218,225–228,230–232autonomousbots,221–222.Seealsocontroltheory;statemachines
cavebots,241–243complexhypotheticalstatemachine,228–230errorcorrection,230–232healerstatemachine,225–228pathfindingwithsearchalgorithms,232–234warbots,243–244
autoreload,219autosnipebots,244autowallbots,244
Bbanwaves,246BiggerThanscantype,CheatEngine,6binaryarithmeticinstructions,90binaryvalidation,248,259–261bits,EFLAGSregister,84BlueScreenofDeath(BSOD),256bots.Seealsoautonomousbots;extrasensoryperception(ESP)hacks
anti-crowd-controlhacks,218anti-debuggingtechniques,251,255–256automatichealer,218,225–228,230–232detectingdebuggers,251–254detectingvisualcues,205–206disablingASLRfordevelopment,128emulatingkeyboard,211–215footprints,managing,250–256gameupdates,dealingwith,101–104
interceptingnetworktraffic,206–211monitoringmemory,204–205obfuscation,251,255–256sendingpackets,215–217spelltrainers,219
branching,92–94breakpoints,30,34,38Breakpointswindow,OllyDbg,26BSOD(BlueScreenofDeath),256BYTEdatatype,67bytes,machinecode,78
CC++,66callee,94–95caller,94–95callHook()function,154callhooking,153–156.SeealsoAdobeAIRhookingcallingconventions,95
forcallhooks,155__cdecl,95,155__fastcall,95__stdcall,95__thiscall,95,217fortrampolinefunctions,168forVFtablehooks,156–158
CALLinstruction,94–95callstack
overflow,255–256viewing,30x86assemblylanguage,86–88
Callstackwindow,OllyDbg,26
capacityofstd::vector,109castingspells.Seespellscavebots,241–243__cdeclconvention,95,155ChangedValuescantype,CheatEngine,7characters.Seealsoenemies
healthbars,monitoringwithbots,204–205pausingexecutionwhenhealthdrops,39–42playerhealth,findingwithOllyDbg,99–101
chardatatype,67CheatEngine,3,5–6
automaticallylocatingstringaddresseswith,102cheattables,7–8correctaddress,determining,7firstscan,running,6installing,4Luascriptingenvironment,18–22memorymodification,8–11nextscan,running,7pointerscanningwith,14–18scantypes,6std::list,determiningwhetherdataisstoredin,112–113std::map,determiningwhetherdataisstoredin,117trainergenerator,9–11VFtables,78zoomfactor,finding,197
cheattables,CheatEngine,7–8CheatUtilityplug-in,42–43CheckRemoteDebuggerPresent()function,251classes,74–78classinstances,76CloseHandle()function,122,138closingmutexes,59–60
CMPinstruction,92codecaves,134
loadingDLLs,143–146threadhijacking,138–142threadinjection,134–138
codeinjection,133–134bypassingASLRinproduction,128–130DLLs,142–146withthreadhijacking,138–142withthreadinjection,134–138
codepatches,creating,31–32columnconfigurations,ProcessMonitor,51combat,automating,243–244commandlineplug-in,OllyDbg,43–44commandsyntax,x86assemblylanguage,79–81Commentcolumn,OllyDbgdisassemblerpane,28complexhypotheticalstatemachine,228–230conditionalbreakpoints,34,38conditionalstatements,93constantratioofhealth,adjustingfor,230–231control-criticalroutines,timing,254controlflowhacks,31controlflowmanipulation,149–150.SeealsoAdobeAIRhooking;Direct3D
hookingcallhooking,153–156IAThooking,160–165jumphooking,165–169NOPing,150–152VFtablehooking,156–160
controltheory,222combiningwithstatemachines,225complexhypotheticalstatemachine,228–230errorcorrection,230–232
healerstatemachine,225–228controlwindows,OllyDbg,25–26cooldowns,displayingenemy,200–201copyingassemblycode,42copy-on-writeprotection,126corpses,botbehaviortoward,229,240correctaddress,determininginCheatEngine,7CPUwindow,OllyDbg,26–30,40crashingdebuggers,255CreateRemoteThread()function,129,130,134,138CreateToolhelp32Snapshot()function,120,141creaturedata,knowingstructurebehind,106–107criticalgameinformation,displaying,198–201crowd-controlattacks,218cryptographicfunctions,hooking,170CSregister,85C-styleoperators,OllyDbg,34–35custombehaviorsforcavebots,scripting,243
Ddarkenvironments,lightingup,190–192datamodificationinstructions,89datastructures,71–73datatypes,66
classesandVFtables,74–78numericdata,67–69OllyDbg,36stringdata,69–71unions,73–74
DBG_RIPEXCEPTIONhandlers,checkingfor,253debugging.SeealsoOllyDbg
anti-debuggingtechniques,255–256
debugdrivers,checkingfor,254debugstrings,printing,253detectingdebuggers,251–254ProcessMonitor,52–53
__declspec(naked)convention,168decode()function,hooking,172–173,174–175DecreasedValueByscantype,CheatEngine,7DecreasedValuescantype,CheatEngine,7dependencies,DLL,145dependencyloading,160depositor,242destinationoperand,80detection,avoiding.Seeanti-cheatsoftwaredevice->SetRenderState()function,192Dijkstra’salgorithm,233–234Direct3D9,176Direct3Dhooking,175–176.Seealsoextrasensoryperception(ESP)hacks
detectingvisualcuesingames,205–206drawingloop,176–177findingdevices,177–181optionalfixesforstability,184writinghookforEndScene(),182–183writinghookforReset(),183–184
directionallighthacks,190–191disablingASLR,128disassemblerpane,OllyDbg,27–29,42Disassemblycolumn,OllyDbgdisassemblerpane,28dispatchPacket()function,210displaybase,27DLL(dynamiclinklibrary),injecting,142–146DllMain()entrypoint,144–145DLLsoption,ProcessExplorerpane,57
DomainNameSystem(DNS)cachescans,248DOSheader,160–161DrawIndexedPrimitive()function,194,195,196,200drawingloop,Direct3D,176–177DSregister,85dumppane,OllyDbg,29–30DWORDdatatype,67,145–146dynamicallyallocatedmemory,6,11,12dynamiclinklibrary(DLL),injecting,142–146dynamiclure,242–243dynamicstructures,105
std::listclass,110–113std::mapclass,114–118std::stringclass,105–108std::vectorclass,108–110
EEAXregister,81EBPregister,83EBXregister,82ECXregister,82,157EDIregister,83EDXregister,82EFLAGSregister,84,92EIPregister,83,139emulatingkeyboard,211–215enableLightHackDirectional()function,190–191encode()function,hooking,171–172,174–175EndScene()function
jumphooking,178–181stabilityof,184writinghookfor,182–183
endSceneTrampoline()function,181enemies.Seealsoextrasensoryperception(ESP)hacks
cooldowns,displaying,200–201criticalgameinformation,displaying,198–201predictingmovementsof,241texture,changing,195–196
entropy,5,7Environmenttab,ProcessExplorerPropertiesdialog,58errorcorrection,230–232ESEA(E-SportsEntertainmentAssociation),247ESEAAnti-Cheattoolkit,247ESIregister,83ESPhacks.Seeextrasensoryperception(ESP)hacksESPregister,83ESregister,85Euclideandistanceheuristic,236eventclassfilters,ProcessMonitor,51–52eventlog,ProcessMonitor,52–53EventPropertiesdialog,54–55ExactValuescantype,CheatEngine,6exceptionhandlers,checkingfor,253executeprotection,125–128Executeuntilreturnbutton,OllyDbg,25experience-trackingHUD,200exponent,floatdatatype,68expressions,OllyDbg,36–37
accessingmemorycontentswith,36elementsevaluatedby,35–36expressionengine,33–36pausingexecutionwhenhealthofcharacterdrops,39–42pausingexecutionwhennameofplayerisprinted,37–38supporteddatatypes,36
extrasensoryperception(ESP)hacks,189–190
backgroundknowledge,190floorspyhacks,201–202HUDs,198–201lighthacks,190–192loading-screenHUDs,201pick-phaseHUDs,201rangehacks,201wallhacks,192–197zoomhacks,197–198
Ffalsepositives,VACtoolkit,248__fastcallconvention,95feedbackloop,222fileaccesses,inspectinginProcessExplorer,60Filesystemeventclassfilter,52FILO(first-in-last-out),86filters,eventclass,51–52findItem()function,116–117findSequence()function,175first-in-last-out(FILO),86first-personshooter(FPS),xxii,246firstscan,runninginCheatEngine,6flags,processaccess,121floatdatatype,67–68floorspyhacks,201–202fogofwar,189.Seealsoextrasensoryperception(ESP)hacksfootprints,managing,250–256Foundintermodularcallswindow,OllyDbg,40FPS(first-personshooter),xxii,246FPUregisters,29Framecolumn,EventPropertieswindow,54
frames,inDirect3Ddrawingloop,176Freezeinterval,Trainergeneratordialog,9freezing
addresses,8mainthread,141
frontier,233FSregister,85functioncalls,x86assemblylanguage,94–95functionflowchart,OllyFlow,45functionnames,findingforIAThooking,163
GGameActuatorsclass,225gameautomationstatemachine,223–224GameGuardtoolkit,248–249gameupdates,determiningnewaddressesafter,101–104generalregisters,81–82genericmemoryfunctions,123–124getAddressforNOP()function,152GetAsyncKeyState()function,196GetExitCodeThread()function,129GetModuleFileName()function,144GetModuleHandle()function,129–130,134,144,146–147GetSystemTimeAsFileTime()function,258GetThreadContext()function,139,142GetTickCount()function,254GetWindowThreadProcessId()function,120goalstate,238GoTobutton,OllyDbg,25greedybest-firstsearchalgorithm,233–234GSregister,85
guardprotection,126
Hhaltingproblem,250handlemanipulationoptions,ProcessExplorer,59–60handlerfunctions,208handles,56,121,210–211,252Handlesoption,ProcessExplorerpane,57Handleswindow,OllyDbg,26hardwarebreakpoints,checkingfor,252–253hashvalidation,247heads-updisplay(HUD),198–201healerstatemachine,225–228,230–232healthofcharacters
healthbars,monitoringwithbots,204–205healthbarsofenemies,displaying,150–152pausingexecutionupondropin,39–42
heapdata,16heuristics,233
defeating,262–263Euclideandistance,236Manhattandistance,235
Hexdumpcolumn,OllyDbgdisassemblerpane,27–28hiddendata,displaying,198–201Hiddenoption,ProcessExplorerpane,57hooking,42,149,153.SeealsoAdobeAIRhooking;Direct3Dhooking;
extrasensoryperception(ESP)hackscall,153–156detectingvisualcuesingames,205–206IAT,160–165interceptingnetworktraffic,206–211jump,165–169
prewrittenlibraries,169signature-baseddetection,evading,257VFtable,156–160zoomhacks,198
hotkeysPatcheswindow,OllyDbg,32ProcessExplorer,57ProcessMonitor,52fortrainer,settingup,10
hourlyexperience,finding,200HTTP(HyperTextTransferProtocol),169HTTPS(HTTPSecure),169HUD(heads-updisplay),198–201
IIAT(importaddresstable)hooking,160–165IDIVinstruction,92IMAGE_DOS_HEADERstructure,161IMAGE_IMPORT_DESCRIPTORstructure,162IMAGE_OPTIONAL_HEADERstructure,161Imagetab,ProcessExplorerPropertiesdialog,57–58IMAGE_THUNK_DATAstructure,162immediatevalue,80importaddresstable(IAT)hooking,160–165importdescriptors,162IMULarithmeticinstruction,90–91IncreasedValueByscantype,CheatEngine,7IncreasedValuescantype,CheatEngine,7indexregisters,83infiniteloops,causingunavoidable,255in-gameactions,botsfor
anti-crowd-controlhacks,218
automatichealer,218,225–228,230–232emulatingkeyboard,211–215sendingpackets,215–217spelltrainers,219
in-gameevents,logging,50–52instructions,79
arithmetic,90–92branching,92–94datamodification,89functioncalls,94–95jump,92–94
intdatatype,67Intelsyntax,80interrupthandlers,checkingfor,252iterator,120
JjumpHookCallback()function,168jumphooking,165–169,178–181jumpinstructions,x86assemblylanguage,92–94
Kkernel-moderootkit,GameGuardtoolkit,249keyboard,emulating,211–215KEYEVENTF_KEYUPflag,212kiting,222,240–241
Llibraries,hooking,169lighthacks,190–192
listclass,110–111listItemclass,110–111little-endianordering,67loaderlock,144loading-screenHUDs,201LoadLibrary()function,143–144Locationcolumn,EventPropertieswindow,54loggingevents,ProcessMonitor,50–52Logwindow,OllyDbg,25longdatatype,67longlongdatatype,67looting,229,241–243Luascriptingenvironment,CheatEngine,18–22luremode,242
Mmachinecode,78mainloop
Direct3Ddrawingloop,176–177syncingwith,164–165
mana,avoidingwasted,219Manhattandistanceheuristic,235mantissa,floatdatatype,68massivelymultiplayeronlinerole-playinggames(MMORPGs),xxi–xxii,198,
248massiveonlinebattlearena(MOBA),xxii,189,197,201,206memcpy()function,136memory,65–66
classesandVFtables,74–78datastructures,71–73numericdata,67–69stringdata,69–71
unions,73–74memoryaccess
ininjectedDLL,145–146forwritingandreading,122–124
memoryaddresses,4accessingwithOllyDbgexpressions,36correct,determininginCheatEngine,7freezing,8new,determiningaftergameupdates,101–104rebasingatruntime,128–129static,6
memory-basedlighthacks,192memorydump
ofclassdata,76ofcodecave,137ofdatastructures,inspecting,70–71ofnumericdata,inspecting,68–69ofstringdata,inspecting,70
memoryforensics,97–98newaddresses,determiningaftergameupdates,101–104playerhealth,findingwithOllyDbg,99–101purposeofdata,deducing,98–99std::listclass,110–113std::mapclass,114–118std::stringclass,105–108std::vectorclass,108–110
memorymanipulation,119accessingmemory,122–124addressspacelayoutrandomization,128–130memoryprotection,124–128processidentifier,obtaining,120–122
Memorymapwindow,OllyDbg,26memorymodification,8–11memorymonitoringwithbots,204–205
memoryoffset,80memoryonwritebreakpoint,208memorypointer,11memoryprotection,124–128,151memoryscanning,3,98.SeealsoCheatEngine;pointerscanning
basic,4–5importanceof,4memorymodification,8–11newaddresses,determiningaftergameupdates,101–104optimizationofcode,22playerhealth,findingwithOllyDbg,99–101purposeofdata,deducing,98–99
MMORPGs(massivelymultiplayeronlinerole-playinggames),xxi–xxii,198,248
mnemonics,78MOBA(massiveonlinebattlearena),xxii,189,197,201,206modifyingmemoryvalues,8–11Module32First()function,144,174Module32Next()function,144,174Modulecolumn,EventPropertieswindow,54Moduleswindow,OllyDbg,25monitoringmemorywithbots,204–205monsters,kiting,240–241mousemovements,emulating,215,240MOVinstruction,89multiclientpatching,30mutexes,closing,59–60
Nnamedpipes,locating,60nameofspecificplayer,pausingexecutionwhenprinted,37–38Nameswindow,OllyDbg,29
nearcalls,153–154nearfunctioncall,39.NETprocesses,59Networkeventclassfilter,52newaddresses,determiningaftergameupdates,101–104nextscan,runninginCheatEngine,7nodes,233,234–238no-operation(NOP)commands,31,32NOPing,150–152
lighthacks,192zoomhacks,197–198
NtQueryVirtualMemory()function,246,257,259NtWriteVirtualMemory()function,261–262nullterminator,70numericdatatypes,67–69numericoperators,OllyDbg,34–35
Oobfuscation,251,255–256observinggameevents
detectingvisualcues,205–206interceptingnetworktraffic,206–211monitoringmemory,204–205
obstacles,searchesdisruptedby,233–234offset,54OllyDbg,23–24
assemblycode,27–29,32–33callstack,viewing,30codepatches,creating,31–32commandlinefor,43–44controlwindows,25–26CPUwindow,26–30
crashingdebuggers,255dealingwithgameupdates,104debuggerbuttonsandfunctions,25expressionengine,33–37memory,viewingandsearching,29–30memorydumpofnumericdata,68–69memorydumpofstringdata,70packetparser,finding,207–208Patcheswindow,31–32patchingif()statements,46–47pausingexecutionwhenhealthofcharacterdrops,39–42pausingexecutionwhennameofplayerisprinted,37–38plug-ins,42–46registercontents,viewingandediting,29Runtracewindow,32–33supporteddatatypes,36translatingcodecaveassemblytoshellcode,135–136userinterface,24–26zoomlimitationcode,finding,198
OllyFlowplug-in,45–46opcodes,78OpenProcess()function,121–122OpenThread()function,142operands
binaryarithmeticinstructions,90IDIVinstruction,92MOVinstruction,89syntax,80–81unaryarithmeticinstructions,90
operations,79operators,usinginOllyDbgexpressionengine,34–35optimizingmemorycode,22ordering,little-endian,67
orderofvariables,indatastructures,70–71OutputDebugString()function,253
Ppackets
intercepting,206–211sending,215–217
packing,251padding,68pageprotection,125–126pages,124parsingpackets,206–211Patcheswindow,OllyDbg,26,31–32patching,multiclient,30patchingif()statements,46–47Pathcolumn,EventPropertiesdialog,55pathfindingwithsearchalgorithms,232–234.SeealsoA*searchalgorithmpathlist,A*searchalgorithm,239–240Pausebutton,OllyDbg,25pausingexecution,37–38,39–42pausingthreads,184PEB(processenvironmentblock)structure,146PeekMessage()function,184PEheader,160–161pick-phaseHUDs,201PID(processidentifier),120–122pipes,locatingnamed,60Playbutton,OllyDbg,25playerhealth,findingwithOllyDbg,99–101playerversusplayer(PvP)combat,243–244plug-ins,OllyDbg,42–46
pointerchains,11–12pointerpath,11PointerscannerScanoptionsdialog,CheatEngine,14–16pointerscanning,11
basicsof,12–14withCheatEngine,14–18pointerchains,11–12rescanning,17–18
Pong,46–47Popuptraineronkeypressfield,Trainergeneratordialog,9predictingenemymovements,241prewrittenhookinglibraries,169printf()call,72,73–74,75printingdebugstrings,253Process32First()function,120Process32Next()function,120–121processaccessflags,121PROCESS_ALL_ACCESSflag,121Processandthreadactivityeventclassfilter,52PROCESS_CREATE_THREADflag,121processenvironmentblock(PEB)structure,146ProcessExplorer,49–50,55–56
configuringcolors,56handlemanipulationoptions,59–60hotkeys,57Propertiesdialog,57–59userinterfaceandcontrols,56–57
processhandles,obtaining,121processidentifier(PID),120–122processInput()function,215–216processKeyboardInput()function,216ProcessMonitor,49–50
configuringcolumnsin,51
debugging,53–55eventclassfilters,51–52high-scorefile,finding,55hotkeys,52inspectingeventsineventlog,52–53loggingin-gameevents,50–52
ProcessMonitorFilterdialog,50Processnamefield,Trainergeneratordialog,9processNextPacket()function,210processorregisters,81–86Processprofilingeventclassfilter,52PROCESS_VM_OPERATIONflag,121,122PROCESS_VM_READflag,121PROCESS_VM_WRITEflag,121Propertiesdialog,ProcessExplorer,57–59protection,memory,124–128,151PunkBustertoolkit,246–247,257purposeofdata,deducing,98–99PvP(playerversusplayer)combat,243–244
Rrangehacks,201readingfromgamememory,119
accessingmemory,122–124addressspacelayoutrandomization,128–130memoryprotection,124–128processidentifier,obtaining,120–122
ReadProcessMemory()function,122–124readprotection,125–128RealTimeMessagingProtocol(RTMP)
assessing,169–170decode()function,hooking,172–173,174–175
encode()function,hooking,171–172,174–175interceptingpackets,207
real-timestrategy(RTS),xxii,197,201,206,243rebasingaddressesatruntime,128–129reconnaissance,49–50
ProcessExplorer,55–60ProcessMonitor,50–55
recv()function,207–208red-blacktree,114–115Referenceswindow,OllyDbg,26,28–29,40,100refiller,242registers,processor,81–86registerspane,OllyDbg,29Registryeventclassfilter,51Rescanpointerlistwindow,CheatEngine,17–18responsivehacks,203
anti-crowd-controlhacks,218automatichealer,218,225–228,230–232detectingvisualcues,205–206emulatingkeyboard,211–215interceptingnetworktraffic,206–211monitoringmemory,204–205sendingpackets,215–217spelltrainers,219
rootkitsdefeatinganti-cheat,261–262GameGuardtoolkit,248–249
rootnode,113–114RTMP.SeeRealTimeMessagingProtocolRTS(real-timestrategy),xxii,197,201,206,243runtimeflexibility,229Runtracewindow,OllyDbg,26,32–33
SSBD.Seesignature-baseddetection(SBD)scancode,214scantypes,CheatEngine,6scanvalue,4score,234screenshots,247,258scriptingcustombehaviorsforcavebots,243scriptingengine,CheatEngine,18–22searchalgorithms,232–234.SeealsoA*searchalgorithmSecuritytab,ProcessExplorerPropertiesdialog,58segmentregisters,84–86send()function,216–217sendingpackets,215–217SendInput()function,211–212,215SendMessage()function,213–215sensors,ofasystem,222Set/Changehotkeyscreen,CheatEngine,10SetLight()memberfunction,192SetProcessIsCritical()function,256shellcode,134,135–136,138–141shortdatatype,67sign,floatdatatype,68signature-baseddetection(SBD)
ESEAAnti-Cheattoolkit,247evading,256–257PunkBustertoolkit,246–247
signatures,246single-instancelimitation,59–60skillshots,232Sleep()function,164–165,227
SmallerThanscantype,CheatEngine,6sourceoperand,80Sourcewindow,OllyDbg,26spawningthreads,129spells
anti-crowd-controlhacks,218complexhypotheticalstatemachine,228–230spelltrainers,219
SSregister,85stackframe,87–89stackoverflow,255–256stackpane,OllyDbg,30stacktrace,ProcessMonitor,54–55statemachines,223–224
automatedhealer,225–228combiningwithcontroltheory,225complexhypothetical,228–230errorcorrection,230–232Luafunctions,adding,229–230runtimeflexibility,229
staticaddresses,6__stdcallconvention,95std::listclass,110–113std::mapclass,114–118std::stringclass,105–108std::vectorclass,108–110Stepintobutton,OllyDbg,25Stepoverbutton,OllyDbg,25stochasticsystems,230stringdata,21,69–71,100–101stringoperators,OllyDbg,35Stringstab,ProcessExplorerPropertiesdialog,58
structmemberalignment,71structures,data,71–73subregisters,83SuspendThread()function,142,184syncingwithgamethreads,164–165systems,controllingbehaviorof,222
Ttargets,selecting,240TCP/IPtab,ProcessExplorerPropertiesdialog,58TEB(threadenvironmentblock),146templates
forchangingmemoryprotection,127memoryaccessfunctions,123–124,145–146
TESTinstruction,92textstrings,21,69–71,100–101textureofenemies,changing,195–196__thiscallconvention,95,156–158,217Thread32First()function,141Thread32Next()function,141threadenvironmentblock(TEB),146threads
hijacking,138–142injection,134–138spawning,129
Threadstab,ProcessExplorerPropertiesdialog,58Threadswindow,OllyDbg,26thunks,162–163timingcontrol-criticalroutines,254Titlefield,Trainergeneratordialog,9togglingz-buffering,195
Traceintobutton,OllyDbg,25Traceoverbutton,OllyDbg,25tracingwithOllyDbg,32–33,39–42trainergenerator,CheatEngine,9–11trampolinefunctions,165–168,181traversals
IAThooking,162VFtables,156
Uunaryarithmeticinstructions,90unavoidableinfiniteloops,causing,255UnchangedValuescantype,CheatEngine,7unions,73–74Unixsyntax,80UnknownInitialValuescantype,CheatEngine,6updates,determiningnewaddressesafter,101–104userinterface,ProcessExplorer,56–57user-moderootkit,GameGuardtoolkit,248–249
VVACtoolkit,247–248ValueBetweenscantype,CheatEngine,6ValueTypedirective,CheatEngine,6VF(virtualfunction)tables
classinstancesand,76–78findingDirect3Ddevices,177–181hooking,156–160,182–183traversals,156
VirtualAllocEx()function,136–137,138virtualfunctions,classeswith,75–76
VirtualProtectEx()function,126–128VirtualProtect()function,127
WWaitForSingleObject()function,129,138wallhacks,192
creatingforDirect3D,194–197renderingwithz-buffering,193–194
warbots,243–244Wardentoolkit,249–250waypoints,222,229wchar_tdatatype,67windowhandle,fetching,120Windowswindow,OllyDbg,26WM_CHARmessages,213–214WORDdatatype,67WriteProcessMemory()function,122–124,136–137,138writeprotection,125–128writingtogamememory,119
accessingmemory,122–124addressspacelayoutrandomization,128–130codecaves,136–137memoryprotection,124–128processidentifier,obtaining,120–122
Xx86assemblylanguage,78–79
arithmeticinstructions,90–92branchinginstructions,92–94callstack,86–88commandsyntax,79–81
datamodificationinstructions,89functioncalls,94–95jumpinstructions,92–94NOPing,150–152processorregisters,81–86
x86Windowsmemoryprotectionattributes,125–126
Zz-buffering,192–195zoomfactor,197zoomhacks,197–198
Footnotes
Chapter4:FROMCODETOMEMORY:AGENERALPRIMER1.RandallHyde’sTheArtofAssemblyLanguage,2ndedition(NoStarchPress,2010)isawonderfulbookthatcanteachyoueverythingthereistoknowaboutassembly.2.Eachcommandmustfitwithin15bytes.Mostcommandsare6orfewer.3.Thereisalsoanunsignedmultiplicationinstruction,MUL,whichonlyworkswithasingleoperand.4.JustasMUListoIMUL,DIVistheunsignedcounterparttoIDIV.
RESOURCESVisithttps://www.nostarch.com/gamehacking/forresources,errata,andotherinformation.
Moreno-nonsensebooksfrom NOSTARCHPRESS
BLACKHATPYTHONPythonProgrammingforHackersandPentestersbyJUSTINSEITZ
DEC2014,192PP.,$34.95ISBN978-1-59327-590-7
THECARHACKER’SHANDBOOKAGuideforthePenetrationTesterbyCRAIGSMITH
MAR2016,304PP.,$49.95ISBN978-1-59327-703-1
THEIDAPROBOOK,2NDEDITIONTheUnofficialGuidetotheWorld’sMostPopularDisassemblerbyCHRISEAGLE
JUL2011,672PP.,$69.95ISBN978-1-59327-289-0
PRACTICALFORENSICIMAGINGSecuringDigitalEvidencewithLinuxToolsbyBRUCENIKKEL
FALL2016,256PP.,$49.95
ISBN978-1-59327-793-2
IOSAPPLICATIONSECURITYTheDefinitiveGuideforHackersandDevelopersbyDAVIDTHIEL
FEB2016,296PP.,$49.95ISBN978-1-59327-601-0
PRACTICALMALWAREANALYSISTheHands-OnGuidetoDissectingMaliciousSoftwarebyMICHAELSIKORSKIandANDREWHONIG
FEB2012,800PP.,$59.95ISBN978-1-59327-290-6
800.420.7240OR415.863.9900|[email protected]|WWW.NOSTARCH
GetInsidetheGameYoudon’tneedtobeawizardtotransformagameyoulikeintoagameyoulove.ImagineifyoucouldgiveyourfavoritePCgameamoreinformativeheads-updisplayorinstantlycollectallthatlootfromyourlatestepicbattle.
BringyourknowledgeofWindows-baseddevelopmentandmemorymanagement,andGameHackingwillteachyouwhatyouneedtobecomeatruegamehacker.Learnthebasics,likereverseengineering,assemblycodeanalysis,programmaticmemorymanipulation,andcodeinjection,andhoneyournewskillswithhands-onexamplecodeandpracticebinaries.
Levelupasyoulearnhowto:
ScanandmodifymemorywithCheatEngine
ExploreprogramstructureandexecutionflowwithOllyDbg
LogprocessesandpinpointusefuldatafileswithProcessMonitor
ManipulatecontrolflowthroughNOPing,hooking,andmore
Locateanddissectcommongamememorystructures
You’llevendiscoverthesecretsbehindcommongamebots,including:
Extrasensoryperceptionhacks,suchaswallhacksandheads-updisplays
Responsivehacks,suchasautohealersandcombobots
Botswithartificialintelligence,suchascavewalkersandautomaticlooters
Gamehackingmightseemlikeblackmagic,butitdoesn’thavetobe.Onceyouunderstandhowbotsaremade,you’llbebetterpositionedtodefendagainsttheminyourowngames.JourneythroughtheinnerworkingsofPCgameswithGameHacking,andleavewithadeeperunderstandingofbothgamedesignandcomputersecurity.
AbouttheAuthorNickCanowrotehisfirstscriptsforopensourcegameserverswhenhewas12andhasbeenapartofthegame-hackingcommunityeversince.Hehasyearsofexperienceindetectinganddefendingagainstmalware,andadvisesdevelopersanddesignersonbestpracticestoprotecttheirgamesagainstbots.Nickhasspokenabouthisresearchandtoolsatmanyconferences.
WARNING!Thisbookdoesnotcondonepiracy,violatingtheDMCA,infringingcopyright,orbreakingin-gameTermsofService.Gamehackershavebeenbannedfromgamesforlife,suedformillionsofdollars,andevenjailedfortheirwork.
THEFINESTINGEEKENTERTAINMENT™www.nostarch.com