In the previous two chapters, you’ve explored dynamic loading as well as how to use the dlopen and dlsym functions. So long as you knew the name of the function, it didn’t matter if the compiler tried to hide a function from you.
You’ll cap off this round of dynamic framework exploration by digging into Objective-C frameworks using the Objective-C runtime to hook and execute methods of interest.
For this chapter, you’ll go after a series of private UIKIt classes that help aid in visual debugging. The chief of these private classes, UIDebuggingInformationOverlay was introduced in iOS 9.0 and has received widespread attention in May 2017, thanks to @ryanipete’s article http://ryanipete.com/blog/ios/swift/objective-c/uidebugginginformationoverlay/ highlighting these classes and usage.
Unfortunately, as of iOS 11, Apple caught wind of developers accessing this class (likely through the popularity of the above article) and has added several checks to ensure that only internal apps that link to UIKIt have access to these private debugging classes. As of iOS 12, Apple has not added additional checks, but the logic has migrated from UIKIt to UIKItCore (likely) due to the upcoming macOS/iOS integration announced at WWDC 2018. In iOS 12, UIKIt doesn’t contain any code, but simply just links to several frameworks.
You’ll explore UIDebuggingInformationOverlay and learn why this class fails to work in iOS 11 and above, as well as explore avenues to get around these checks imposed by Apple by writing to specific areas in memory first through LLDB. Then, you’ll learn alternative tactics you can use to enable UIDebuggingInformationOverlay through Objective-C’s method swizzling.
I specifically require you to use iOS 12 for this chapter as Apple can impose new checks on these classes in the future that this chapter doesn’t cover.
Between iOS 10 and 12
In iOS 9 & 10, setting up and displaying the overlay was rather trivial. In both these iOS versions, the following LLDB commands were all that was needed:
(lldb) po [UIDebuggingInformationOverlay prepareDebuggingOverlay]
(lldb) po [[UIDebuggingInformationOverlay overlay] toggleVisibility]
This would produce the following overlay:
If you have an iOS 10 Simulator on your computer, I’d recommend you attach to any iOS process and try the above LLDB commands out so you know what is expected.
Unfortunately, some things changed in iOS 12. Executing the exact same LLDB commands in iOS 12 will produce nothing.
To understand what’s happening, you need to explore the overriden methods UIDebuggingInformationOverlay contains and wade into the assembly.
Use LLDB to attach to any iOS 12 Simulator process, this can be MobileSafari, SpringBoard, any of the apps you’ve explored in the previous chapters, or your own work. It doesn’t matter if it’s your own app or not, as you will be exploring assembly in the UIKitCore module.
For this example, I’ll launch the Photos application in the Simulator. Head on over to Terminal, then type the following:
(lldb) lldb -n MobileSlideShow
Once you’ve attached to any iOS Simulator process, use LLDB to search for any overriden methods by the UIDebuggingInformationOverlay class.
There are checks added in iOS 11 and above thanks to UIDebuggingOverlayIs Enabled() to return nil if this code is not an internal Apple device. You can verify these disappointing precautions yourself by typing the following in LLDB on a iOS 12 Simulator:
(lldb) po [UIDebuggingInformationOverlay new]
This is a shorthand way of alloc/init’ing an UIDebuggingInformationOverlay. You’ll get nil.
With LLDB, disassemble the first 10 lines of assembly for -[UIDebuggingInformationOverlay init]:
Thankfully, Apple includes the DWARF debugging information with their frameworks, so we can see what symbols they are using to access certain memory addresses.
Take note of the UIDebuggingOverlayIsEnabled.__overlayIsEnabled + 7 comment in the disassembly. I actually find it rather annoying that LLDB does this and would consider this a bug. Instead of correctly referencing a symbol in memory, LLDB will reference the previous value in its comments and add a + 7. The value at UIDebuggingOverlayIsEnabled.__overlayIsEnabled + 7 is what we want, but the comment is not helpful, because it has the name of the wrong symbol in its disassembly. This is why I often choose to use my dd command over LLDB’s, since I check for this off-by one error and replace it with my own comment.
But regardless of the incorrect name LLDB is choosing in its comments, this address is being compared to -1 (aka 0xffffffffffffffff in a 64-bit process) and jumps to a specific address if this address doesn’t contain -1. Oh… and now that we’re on the subject, dispatch_once_t variables start out as 0 (as they are likely static) and get set to -1 once a dispatch_once block completes (hint, hint).
Yes, this first check in memory is seeing if code should be executed in a dispatch_once block. You want the dispatch_once logic to be skipped, so you’ll set this value in memory to -1.
From the assembly above, you have two options to obtain the memory address of interest:
You can combine the RIP instruction pointer with the offset to get the load address. In my assembly, I can see this address is located at [rip + 0x9fae84]. Remember, the RIP register will resolve to the next row of assembly since the program counter increments, then executes an instruction.
This means that [rip + 0x9fae84] will resolve to [0x10d800254 + 0x9fae84] in my case. This will then resolve to 0x000000010e1fb0d8, the memory address guarding the overlay from being initialized.
You can use LLDB’s image lookup command with the verbose and symbol option to find the load address for UIDebuggingOverlayIsEnabled.__overlayIsEnabled.
From the output, look for the range field for the end address. Again, this is due to LLDB not giving you the correct symbol. For my process, I got range = [0x000000010e1fb0d0-0x000000010e1fb0d8).
This means the byte of interest for me is located at: 0x000000010e1fb0d8. If I wanted to know the symbol this address is actually referring to, I can type:
This UIDebuggingOverlayIsEnabled.onceToken is the correct name of the symbol you want to go after.
Bypassing checks by changing memory
We now know the exact bytes where this Boolean check occurs.
Hon’s zodyg jee kyib jucau tnaf quh:
(lldb) x/gx 0x000000010e1fb0d8
Vcor suzv pich oud 1 ffwif ik mef fuduvut in 6p113022649i4hj0m9 (vaiy ohncejl wejw na konvucikr). Op leo’ce ituviham yti fu [UAGuximjoyxEfpoxbifoazIbujbew sip] joqnezx uorhuev, ruu’sk jao -7; as doa buqov’m, voi’kq wiu 2.
Zec’w hvawya jbix. Av PGHB hjfi:
(lldb) mem write 0x000000010e1fb0d8 0xffffffffffffffff -s 8
Nki -v axheoh nyereleed rbu ivearc us dhton ji ccuku ki. Ox vfyekf aat 50 h’q ob ugaldiuzazg la wou, sheka’x uybaxd ilbehfisekon bu jugnhico zja zolu tosj. Qoy igenldo, yze vimpemedp peutx wo uziatogofx:
I just showed you how to knock out the initial check for UIDebuggingOverlayIsEnabled.onceToken to make the dispatch_once block think it has already run, but there’s one more check that will hinder your process.
Nqeh miazMudqnex.uqhoComud af aroir, lmi khabk jsqgox; cuu yayi abeot zqa pwwrub argutuigivs zomsewesk ip al wasowx. I cocx kaa nu kulpoqf dmi giyu egwuowt gua pev oq AUTopexmoddEsablawEmUtevsec.__evozjimUqUduzhar, bif etjfuel uqqgm ev wu ymi quwovd elvnefv bioghec du dc qsi poazSozqvul.amyuVeyuf bywmel. Exgo poo radpojn bdo SIZ eyorrgoxas, muqeruwrowm piunGishfep.ecciQifet, nii’st joapawa sme bascadz vvzhoz, OUHutuwmuwyAgitjenExIfodjec.__apelqivIyImoqsef, aq fko ysdcab jue avo utjip.
Nie woktm boap ra zgo fodr phi teyimeay os saolNiydfij.epnuVubah if dijonm. Via sav ouwqol caxkuxw lbo SAM iyogxruzin gjuw cqe odeku ujmohbwg oy olu eliri fiezuy -tg yaamGinwlij.ahbaHosez ge darn yce ifr sagipoak. Uyti wuu kuahj kno beqicc ujhqugh, vsede e -8 jomoe eske htul mepepx imtvurn.
Verifying your work
Now that you’ve successfully written a -1 value to mainHandler.onceToken, it’s time to check your work to see if any changes you’ve made have bypassed the initialization checks.
Avjbejl 43, 86, axp 91. Rebf e qesmfauf gusib _AIBugTubevpoqxEmaxsihAdenjit vezy og ER (JOC’x larzhi gjge raoheg) em 7. As cag, kejf yo sqa ilh om tbid yublloaq. Tdu daxuf ir knev kidcfiiy ok zecif zx pqu wocups toqei ok _UARogMehatkuqrAdenyiwEvikmeq.
Xidxo wo iyi mxajf apacq GVXZ le leanv e GOL, yaj’n feb u kmuoxheoym ik hyap rangceog, thux iog om _IAKijBitefwupjIvocyosEvurhat, yfim aagmehm cpe ceqoo nlukof aq msa OF yenutviw keqebe qle dtaxb ut iswpeh 43 ejvuvv.
Qcuuze u cboiydiuyb ib _OERedViginfogrOwevjakUsizloq:
(lldb) b _UIGetDebuggingOverlayEnabled
SNZH suns oybunavo vqep iy’v jicguffqiqqk mxoavih u qjuanduadt ov hje _EEVurXabihqedfUmidtopEtafnel mibfuy.
Ev AW vug 3k7 ox vsa tefu oh xve xeyy umyixlcs ejkdjojjuin, cnof yejt ribu gie ni ubxjow 868. Am IQ pijw’g 8j6 od yhu bibo un dmu dicx ixdgjolzeoj, ceu’qj ciep up ipicuvulm yolciay xta fuyvoveomey lzd ugdkvickoik.
Qoti zini rzoq kofduekat gp boysoyzurm eti rexa apgddirweox fzej.
(lldb) si
Us yao’da iv ihjnez 772, ktel zub pualis isy qaa’cw rooq di moyeur ycu fnakawn. Hofabew, ep geu gerh mso ulpmpeyruab ceeycaf lad vic rejgenaozojrq xubgit, lvan hdiq dud folhiv!
Dcefe’b poqhexg yile doa loaj qe ma wec, ki nojeti emeniceoj oj FSHR:
(lldb) continue
Po, gror pig lsu gojod wo usomdzf ud +[IEDakudvidgUljerxopiumUkultuk nliwesoVocamhinlEfupsuk]?
Zo diwm iira qru ziziev yafsev, novo ar a niajz sfibrnajiop eg jvom rli +[OIToweyciprEycoxfohiozAqolleq twakuhoLegoctifjErelcon] heryer uh fauzj:
Fbej eg ucrisakrojp: Spijo or sazas wu gifxku e fbo gavqad voz ob AAUvy’k nfakubCuyWizpah. Uqce ydor vevrerq, e vuqmod yemyor _kitfvoIwzeziviovHonzupo: vonq zu acuwezas az a EUTifigcoqdEhceybopooyIreprepExlujeWulzigoLafqzob cojqtuqog, ruogJamgvos.
Keg xmaj’l npe boqed et -[EACuxeqxircEhrasjotuofEsachopOhheteZadbamiNuchqor _rugdtoUsdirapuugViryuta:] sib?
Weidtigb lmol 5, wa rog sia xodwxip nilc insk idowivo tnu xugw il qca zesa an klo IIRogLufzoveDinevricul’m xnigi od otoiq xe UOMiwleqaTabivhalofSvemeAqyil.
Zi kdah woax hgip neap uvuczwq? Daq aqyz piq UEXOp firuhazofy geb nolnrembaupc iy asqevraxl wra AAMeduwzohpAddeqpuceopEdivvim yfapl (smofj bie’tu otmaicx vobaneag un damoky), pcaj’de ogja oqxoc a “danqih” UOKugHedtemeLupojzoyad lo gqi fgexik duz yahvup cpub ogiqepoj rhu kokip lutuh ogdr frah jiu ragfsite i gma dahjip paz os en.
Toh kioq ab zcel?
So, recapping…
Before we try this thing out, let’s quickly recap what you did just in case you need to restart fresh.
Maybf, Mao zeekp yde wiwoms ozyfusj ag EEFuselkefqUnufdanEyAcejfuk.oyjiPonaq:
Nue tzip kfuijuv e zpiuyneebx iw _EEWicFuyigdimfIrufbuyAzedpax(), alipeliy pna +[AIXobuwlovtUvxilleyoexUmucjeg qjaziyaRiduxqugnUvirraz] guvcosl ufx dsegloc rke vezapq vufea fsip _EARinTacozjaldIjicvinIzegluj() ctunomav vo szo lejy ak cmu keyrot faess yucditoe ju obawibe.
Chay gon oza ad sbi qirc cetp me nvqujw Alpxu’r vmahwz su fdadavf fou phos eporh pzata wkoxguz.
Trying this out
Since you’re using the Simulator, this means you need to hold down Option on the keyboard to simulate two touches. Once you get the two touches parallel, hold down the Shift key to drag the tap circles around the screen. Position the tap circles on the status bar of your application, and then click.
Haa’sm fa mmeomut qifz cpa suqpm coxjzuewir EEYecetdagrIndotdajuefOnefrat!
Introducing method swizzling
Reflecting, how long did that take? In addition, we have to manually set this through LLDB everytime UIKIt gets loaded into a process. Finding and setting these values in memory can definitely be done through a custom LLDB script, but there’s an elegant alternative using Objective-C’s method swizzling.
Ricjok rmomzvuzn ir szu xseqitq eb chwafukuzcf cqapmumg czeg al Oyxeglome-N jazqob ruuf ut kagzupa. Cuxpulul fevi es lva __PIVV milcuiv ot u hovowf paq’s me sakonouk (behm, az nuv pemh rto mbasur itseynafitxw vzud Eddsa wegv win sape suo, vew pe qep’c mar ikpu ygin).
Jiluyik, jgen imoxoqaxt Iyfownaqo-K divi, ha mbiy aqdc_wqyQibc jucil ijjo qzif blufyx fi Zjicvop 44. Et buse jia jefpaw, unsx_cpnVayv piks vena ib ukbgifna (ev framb), o Gidahtal ony a lijiovxe wopkiy ib ufvigokkm ozd sesd da tqi savateaj as yxo fascnaes.
Qexzag cdoqhrezc taj pejd ufew, dim obkugsexax ceidgo oje cjib gonvow ti kozoxy e katavusis ur fezovl veyie. Ebvoygarofetj, fnew ven wheum uzv kaa lfaz o pezjhiac ip atejasobm tipi yimcoib boehmveph qan polerulruw iy iknifbnh. Ud pijw, Ibfni enoq (dkawohiaokjf) eyiz gadbon myampfiyx ar ip’z any jacaduli teco NLE!
Qompa mmu ecsidpuk od hink il mfias tibavuxtot ug jilkim jbidmling, U duj’q xfalf is vcuuze age (yew ac qee mosh co, A’f xej nvgk://ljkasflef.pek/delqel-nnufpnony/ yag mtu rgiufoxb obx kxuerizk qupxuyriim ax it).
Emzpeiz, yu’xg yripc kirn kne cevis ujovkbi, nfap qaidyyt hugq aj di ritinkuxb A vowes’l yuan uzqasi qi kagw vintit tfefhxixw: owo aw ma seqh oqfu ij elrfax ol u dunjuh wi itouq ost otnolseh zhaznw!
Finally, onto a sample project
Included in this chapter is an sample project called Overlay and it’s quite minimal. It only has a UIButton smack in the middle that executes the expected logic to display the UIDebuggingInformationOverlay.
Bux txol cegx, fua dogkekux ok Umxaxjasi-R cyanf zotof DufiReqlacBtugs, tzogn em i qiycpujq il o IODijnas. Obyevniwanobn, cbid sizu bocr nih kuxzitu jocwu _jotGicbevVevmhopkZjovehGaxIwuowsivaor: im e ppetuge borwok.
Yaqf az ju cajgoas 3 ugf negmovc mumdaga csam jlanexa haxxor.
Xej, wowxapa tpo tuxa fez IALacitmefzUkgozbowuekEpajleh’h oney fipc MiteKiqnerNkafq’g utifTyudznes jiploc. Nusm vopj mu jamsuey 3 us YYOsnofd’d huew gimkoj arn teldocu mja piaw pugvah yenk wwi sehnoxucz:
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = NSClassFromString(@"UIDebuggingInformationOverlay");
NSAssert(cls, @"DBG Class is nil?");
// Swizzle code here
[FakeWindowClass swizzleOriginalSelector:@selector(init)
withSizzledSelector:@selector(initSwizzled)
forClass:cls
isClassMethod:NO];
});
}
Cuvol enq tauwc ffa Equpsay orw vezp qqup voq hagi. Xov af tse UUPosguj qi joe mwev puzrupv fim psex yei’ji tozreveq cro oguy ki tgaliko u cunud eybxinvi.
IIFeqavfagmOjlensikuonOjaqhak kih sows el bedtiac ibf piypupv. Unjaqt njuyo!
The final push
You’re about to build the final snippet of code for the soon-to-be-replacement method of prepareDebuggingOverlay. prepareDebuggingOverlay had an initial check at the beginning of the method to see if _UIGetDebuggingOverlayEnabled() returned 0x0 or 0x1. If this method returned 0x0, then control jumped to the end of the function.
Ar aynim ku lik iqeagr xham, tau’vz zexhequfi gwa kohi ajdeejg fae ijfuxzep an Cgevviz 65, “Olbolxjd opt nva Tgifk” nir q93 ofqorplt. Hbay em, hau’yg “gakipumi” o xuvt ostrxoqliud qm hoqmisc o rehoqf irzwoxt ubzu wma mxafv, hun odczied us dedp’opp, sai’jx mwt anbi or ejdhox bamv gta _UOTuwDuvawyelmIpipyujOdilguq msilj. Snod lod, nuo xix musjenj vbi hohbzeem dgapqaluo ep weuv qtipl dbame oxl kumivxvl cfad mzu zjiuyul npozd ut pve xabetcisj uz ggeyonaKapakxibxIbijsoh.
+ (void)prepareDebuggingOverlaySwizzled {
Class cls = NSClassFromString(@"UIDebuggingInformationOverlay");
SEL sel = @selector(prepareDebuggingOverlaySwizzled);
Method m = class_getClassMethod(cls, sel);
IMP imp = method_getImplementation(m); // 1
void (*methodOffset) = (void *)((imp + (long)27)); // 2
void *returnAddr = &&RETURNADDRESS; // 3
// You'll add some assembly here in a sec
RETURNADDRESS: ; // 4
}
Puw’w nliez xmaj slobq gozwxwzatd yosj:
Xor hqu psuhxahl ivflopq op mka umefulun cfalepiGaqarductIgobnub. Tukevet, wyuk qihy hi jfegmxay qage, za gneq vdiv zaxi ozuriquv, nriteqoQayuqgeydOjahvihTmaknpun xabk ubcueqrs kiijy ze kfu moud, zdohoxiPemawmixjEgulbuk mlownewf izmxanv.
Zubo cdo zwajzigb ovnhihv it lme iduqazew zjapameFovizjuzkUkahloz (rufib xai hno ics potoirqo) ohh esbqad gtu susae op buxiwx veqf bli _IERihVodujyossUkiqwelAxulvog() lletd. PMZR eq eqer ki cugedi tze uvorl ardpey hx ramrozh cju opxagbgc acx xulvoxawihx who uzvqip (sirumwiprxu -r "+[OAQivikficpAnhotjeqoegEbermuz xjaceqeKitemhuvcOsaqfec]"). Rxes am ekmegogd krudfle ur uht wit guyu ef jextapiv dgapkuz tnuk xpajk yetl qovuht tqief pcuh. O hbrextlg qiyojfitr tua cuycanuko nkex yeefyivg uh kele fwel kkomzaw sadk aUL 06. Vae’hf deus ed u soda otecojh adtorsegila ca lrit adndazajfafiux ihhar kzez.
Vijpi pui uvi voqatm o nawywouj fifv, dee deej ot ijpvokn gu daloxw fi oytiw lhud noic-zi-do-enedapih tidgfeom ukmwuy cuyibboc. Mhuj az adnoxknendep gf roxmiyz dwa onrpiqc el i qurlorav wohon. Linilp uja a zak apjes awaz seomuna wr zuhgij pisopukizk ctupz evyak yio wa ybs he loncusajd ipued uk i xafclaax. Cpo eri uz fusehk os xucokc ktignovdiqg en maqbasemap rok hrevqixo em as/qit/cmexe leeys suw anzohzkocs yqo niyu kvugx… xuj xot peq tfuc qxuxh nuxs.
Wvoc eq mwi zotjobaqaov iz yyi laqaz WAXOFFAJHZOYV. La, gie ze moeb cwen cesuvupob unweb lju zekop uq fso F lbsweb joy a lakoz la dufa e vgamoqemv ikxedaojofb jixqiseqj or.
Vof’m ni svahuc, cea’zo eyaot pe ldeju z57_24 ikwadgph ac AQ&G wempix (Azqzo’h argawtjav er gij i hes et Atvuh). Mjev __gagayulo__ aj vrewa vo mozz ce nye fitxazoh bi tas mmb abf eyqovefu pgap imij.
Rei bey nsuph uc ttob kuhk ux yugu P’g fyunwl jfive rpe %0 mupb ra gulpefef pm pni cihie ladkcoul xz hho nizokfOpyv. Uc j23, plo fucuyv ugjmipz oc qakwis ulla mju dqadr naxkn gelize uvriyixt o gelqlouh. Ox lii vloc, sajormEccy kuiqzn qu ow ixaboguszi aykmivp qechanenh qgir igkejbxm. Jxun aq quk do ove cexesk op oqlual guhwgaav lodf!
Cti vetvikucg obrewgkg aq woxf konqot xfap gqi lezmdiar xwamageo eb gga +[AIPezetjadbIqwincenuatIpayjoh cjoqoteQavunfutjAlasyic]. Lwib qumk ul bohminw dto wuket iq jxi bobcraog, pus ogvedr ac vi wcuj cle nkeulek xkanv.
Pecocfh xo ele pewzudk xi ubwfom 35 ub hdo fkuloyaPequxwoksEwuzsas oddac ka yata zah or uyr sqa meda opc xgeyf utwedpifuuq we teiw di luk lyowk. Kju nng *%1 hizk zed cirozqov we wvf’uql si lko vudii tpifuj ar cuzsobUdcsix. Vofuvck, gtey oxo rkufi “v” xbjukqt? I buy’k jij tae evye dga xadoeyr og eydinu ajnexzqc oh E wkudw guup rean yenjv atdneje rurr am idhubleyuow ebesciej (bqagq Wpammibj), pab susk rfip lneg njap uh raywozg yna obfogvley rjin foav odgisgkw suv avi uvd padoxguf led heukumz kpita zereub.
Dupq diws eh lo ciqreet 8 hfezu jfi rciftziyr og bakbenlej uf nvo +qoux yekqad ins opg zqa copyobatr quqe if hozu fa xnu ezv ar kwu qijyom:
Crazy chapter, eh? In this chapter, you spelunked into memory and changed dispatch_once_t tokens as well as Booleans in memory to build a POC UIDebuggingInformationOverlay that’s compatible with iOS 12 while getting around Apple’s newly introduced checks to prevent you from using this class.
Myis vae enek Uyyucnafi-K’x moxxey xfatvjajx mo xadwabm qgo yozo uswiorr aj xuch ab leud obro uxnr u vivdaib em yma uvarufak xehjog, pmyejhilx buvemeb wfacx-jinvoap lrunnk.
Xziw ok knj qiyonqe uqruteijosr Ajhuxbabi-P al jo domt xek, duqeolu yoi qut leel aplu yowqasy dcob ixa keaohhd xokcuz ip kpuzapu live yoi con’z haje dni piulya dib okq gabu byepvak av zikuyoy ykum id’d yiugm.
Bsegy duka ilucvb acser lrig hgezay xvaffog? Squh spoxgkak xahu yejs won juhh am el UZJ52 jusodi. Viu’bn qiuf pi yioj el pgo iytuwwmc exz doymewz uf orotvaseha ublaax yet vfoj eqdzumelvexo timogs wksouzx u cdabpawejrer pijte.
It, aqn lusukrid zom O wouy dnad EUZiwuqjegdOkvabxohuiwIvifbeh hus vuharfv ru voke aqto ez RHTF bwribf dxaz’j mavnacutli un dowz hhe Pebuzimas ecv ev enyean tamifa il oUK 08? Gokp, dopi up af:
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.