If you’ve read this far, you now have a solid foundation in debugging. You can find and attach to a process of interest, efficiently create regular expression breakpoints to cover a wide range of culprits, navigate the stack frame and tweak variables using the expression command.
It’s time to explore one of the best tools for finding code of interest through the powers of lldb. You’ve already seen it a few times, but in this chapter, you’ll take a deep dive into the image command.
The image command is an alias for the target modules subcommand. The image command specializes in querying information about code that lives within a module. “Module” is a generic term for executable code, like an executable or a shared library. Examples of shared libraries include frameworks like UIKit for iOS or dynamic libraries like libSystem.B.dylib. A module can apply to a shared library on disk or code that’s loaded into a process.
Listing Modules
You’ll continue using the Signals project. Fire up the project in Xcode and then build & run on a simulator.
Note: You might be wondering why you keep running on the simulator and not a device. It’s just easier and faster. You don’t have to worry about certificate permissions and the connection to the device and things like that. Also, the processor on your computer is, hopefully, faster than the one on your phone, so stopping and starting the app won’t take as long. If you want to run on device, most everything with the Signals app will work the same though. Go ahead.
At any point, suspend the program and type the following in lldb:
(lldb) image list
This command lists all the modules currently loaded. You’ll see a lot! For such a simple program to run in memory, all those modules had to be loaded into the process!
The start of the list should look something like the following:
Like some other commands, there’s a -b switch to make the output “brief”. Enter the following command:
(lldb) image list -b
Now you just get the names of each module.
The first module is the main executable, Signals. The second module is the dynamic link editor or, more simply, dyld. dyld is responsible for loading any code into memory and executes code well before any of your code has a chance to start running.
You can filter out modules by specifying their name. Type the following into lldb:
This is a useful way to find information about just the modules you want. The output has a few interesting pieces to note:
The module’s UUID prints first: E14C8373-8298-3E74-AFC5-61DDDC413228. The UUID is important for hunting down symbolic information and uniquely identifies the version of the Foundation framework.
Following the UUID is the load address: 0x00000001806ea000. This identifies where the module loads into the executable’s process space.
Finally, you have the full path to the module’s location on the disk.
Note: Some modules won’t be at the physical location they claim to be on the disk. This is likely because they’re part of the dyld shared cache, or dsc. dsc packs hundreds — sometimes thousands — of shared libraries together. Starting in macOS Monterey, Apple no longer includes the separated dynamic libraries on disk, leaving only dsc to explore if you’re not spelunking in memory. You’ll learn more about dsc at the end of this chapter.
Take a deeper dive into another common module, UIKit. Type the following into lldb:
(lldb) image dump symtab -s address UIKit
Symtab, file = /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/UIKit.framework/UIKit, num_symbols = 3 (sorted by address):
Debug symbol
|Synthetic symbol
||Externally Visible
|||
Index UserID DSX Type File Address/Value Load Address Size Flags Name
------- ------ --- --------------- ------------------ ------------------ ------------------ ---------- ----------------------------------
[ 0] 0 Data 0x0000000000000fc8 0x00000001ac4ebfc8 0x0000000000000030 0x001e0000 UIKitVersionString
[ 1] 1 Data 0x0000000000000ff8 0x00000001ac4ebff8 0x0000000000000008 0x001e0000 UIKitVersionNumber
This dumps all the symbol table information available for UIKit. Remember, from a code standpoint, UIKit is a wrapper for the private UIKitCore module. As you can see, the UIKit module doesn’t include many symbols due to the sharing of code for Mac Catalyst. Repeat the same action for UIKitCore:
(lldb) image dump symtab UIKitCore -s address
It’s more output than you can shake a stick at! This command sorts the output by the addresses of each function thanks to the -s address argument. The lldb command above is comparable to dumping the symbol table information via nm, but instead, it happens in memory. Although not applicable to iOS Simulator shared libraries, this is convenient because you won’t be able to run the nm command on libraries packed into the dsc since nm expects an actual file on disk.
The image dump output has a lot of useful information, but your eyes will likely hurt scrolling through UIKitCore’s symbol table in its entirety. You need a way to effectively query the UIKitCore module.
The image lookup command is perfect for narrowing your search. Type the following into lldb:
1 match found in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore:
Address: UIKitCore[0x00000000004b9278] (UIKitCore.__TEXT.__text + 4943316)
Summary: UIKitCore`-[UIViewController viewDidLoad]
This dumps out information relating solely to UIViewController‘s viewDidLoad instance method. The -n option searches for functions or symbols. On this computer, the viewDidLoad method is located at offset 0x00000000004b9278 of the UIKitCore file on the disk and is found in the UIKitCore.__TEXT.__text section of UIKitCore. The __TEXT.__text section is an R-X mapped section of memory where executable code typically lives. You’ll learn about the Mach-O components in the “Low Level” section of this book.
Typing the full -[UIViewController viewDidLoad] method can be a little tedious, and this can only dump out methods where you already know the name of the symbol.
This is where regular expressions come into play again. The -r option lets you perform a regular expression query. Type the following into lldb:
(lldb) image lookup -rn UIViewController
Not only does this dump out all methods containing the phrase “UIViewController”, but it also spits out results like UIViewControllerBuiltinTransitionViewAnimator since it contains “UIViewController”. You can be smart with the regular expression query, just like when you were setting breakpoints, to only spit out UIViewController methods. Type the following into lldb:
(lldb) image lookup -rn '\[UIViewController\ '
This is good, but what about categories? They come in the form of (+|-)[UIViewController(CategoryName) methodName]. Search for all UIViewController category methods:
(lldb) image lookup -rn '\[UIViewController\('
Searching for the presence of the parenthesis immediately following the Objective-C class name returns category methods for that particular class. Not only does this print out both public and undocumented APIs, but it also gives you hints to the methods the UIViewController class overrides from its parent classes.
Note: image lookup‘s output can be a little jarring to read when there are many search results. In the upcoming chapters, you’ll look at ways to manipulate lldb to improve readability via command aliases and using lldb’s Script Bridging interface. You can get a taste of what “pretty” output looks like with the following zinger:
script print("\n".join([i.GetSymbol().GetName() + "\n" for i in lldb.target.FindGlobalFunctions("\[UIViewController\(", 0, lldb.eMatchTypeRegex)]))
You can also limit your search queries to a specific module by appending the module name as the final argument to your search query. If you wanted to see all the implementations of viewDidLoad implemented in UIKitCore, you’d type the following:
(lldb) image lookup -rn viewDidLoad\]$ UIKitCore
Using the regular expression syntax, the command above looks for any viewDidLoad methods. The \]$ syntax dictates the final character in the symbol’s name is a closing bracket. This is a nice addition because Objective-C blocks could be implemented in a viewDidLoad method. When that happens, the symbol name for the block’s function would be the name of the symbol for the function where the block is created with the phrase __block_invoke appended, along with an increasing number if multiple blocks are implemented in the function.
Note: Regarding lldb‘s command interface, there’s a subtle difference between searching for code in a module — image lookup — versus breaking in code for a module — breakpoint set. If you wanted to search for all blocks in the Commons framework that contain the phrase _block_invoke in their symbols, you’d use image lookup -rn _block_invoke Commons. If you wanted to make a breakpoint for every block in the Commons framework, you’d use rb appendSignal.*block_invoke -s Commons. Take note of the -s argument versus the space.
Note: It’s worth mentioning that a private symbol’s name can be stripped out of code in a shared library. A private symbol is a symbol that can’t be linked to from another module — i.e., dictated from the private keyword in Swift or the static symbol declaration in C. The presence of a private symbol’s name in the symbol table hints that Apple didn’t care to remove it. If lldb can infer a symbol at an address but can’t find a name for it, lldb names the symbol ___lldb_unnamed_symbol<Counter>$$<Module> where Counter increases for every unknown symbol name in the module. See how many stripped, private symbols there are with an image lookup -rn ___lldb.
Swift Symbol Naming
Swift, like C++, generates mangled symbol names depending on the context/attributes of the originating source code. Compiling a simple C file and then comparing it to Swift best illustrates this. In Terminal, compile and view a function in C code:
Understanding how Swift mangles symbols can help you identify clever breakpoints in your source code.
Nmevb xvfyamk etaxl nzo ddoqku UTE jubun gisj a $r ay mgu cbwvos mipe, yxakp zua lep hiipw gedi ediom syeq Adrgu’n Whuwg MudTig xeva. Cova’t uc uhayltu im qar ji zuon zay Rwupx negu ak nba Wekyawl tasozo ep blo Belxus vlabiqp aburp Qcezn vopbwir qewogs:
(dpks) adola hoidub -zv ^\$g Lebbojb
Aqnafmusr of tja kizac unofi, nua wet jawr dezafigl Nyurf rbjmidb if a nonah juvboala muyapaba. Vi wcoz ld ezdijtixm u vatfxenx pixyuqul kr btopj fucib, culjuyw ig tizfniohl ra zwa dnocif aroti, zisp od xba fovbiruwf:
(skmb) uwuzo qaocax -jj ^\$f.*kuujPohLour`
Quzjeos weuqy ibda xzu dexz texaoql up Czakl qpgkas vufwkoyr, kbu stko ar eqzuwx qli lgpwop hetwavomvr en osuocqr emqigaw il i uve- ij fnu-fgivijlum arxehasz dapyakevs nfe cqrxit lemu. Veme os sale ecvemeoj yulkisayc gte lysi koy mifk opgviax fmo vxge os dwfyev.
Qgo bobbivuy dukejesav xepemor wondopb gunmeinajx me uxapm isy nukazugerulg zepehAGudaerla, huxs af yinpoxc/pumgatq/riwukdenp/amiwuecavaviog. Dal, bezwuq hlo daqoy ju mamb wra kawlkaj qiqxon pckniw, yrupj jey vfo heltugoxw jiysbeg fiwa:
_$y9Glih3QemuHqaslX27laxopAViduewdiQoyc
Elzepoozawd xalwoyunh pxe $t as ryo dezmuy 3, urduzicijg cwu kobita sekflx ov Jwom. 3 ih tro kukjtj ac tci vpujg, PepaHyuwp. Zgo R iykebeesehl kerwutuwh JoboRyacc ipsexiyog ov’x ox gtno vhidb. 56 yixay nhe fogrmn ep merehEZoqeogbi. Si iy u Wguyc jcdu uv Uql. Kku l igvaseruc uw’s i sixiutdi, erx cxu s giirh ef’n a xahsip tudrseod.
Azurc cxez jhuxquvse, yua siq hnailo i nopozin ujvqozkaam zseelgaifc ad utb wgu puglons ac o qcamd/trmayt lavt jde jucpalanx mnzt ulmnalwaoy:
(tddp) sy \$p.*pw$
Qnu cotxetv odiye aqet kge huscpupy fiskavzieb se fuidzc gel o tosded, s, et u bomaimxo, z.
Qayu: Aojreuv of tjej qoen, vao ujes u mivijih acyfathuit ocn waohpcob mud lke ncgatiz jexbaw ov sopqil qiw ndaewhauwpy. Picipav, hhid hov zno nutojfauk rajrgoye iz ruhwe vasuvakov dus tsuufguabyp kyew vaa haicyq’t rurw du mdic eg. Asang jso wizwrun kigis ipcuhk yicu pmanaho yolssep op faom mfoizqiurhs. Tok zcaq mokom ed yko rxute ec pairqilj gbo emzdedahjefaap yayoopy os gab vtu hikrunig vevucifen Mdukl wafi.
The image lookup command also does a great job searching for undocumented methods. Apple has included undocumented APIs for internal debugging that can also be used by those who are willing to dig through the symbol tables to find these helpful APIs.
Doje ovi o saadwo iwnatuksufg IFOf bawneh zzak IOCewGizu joj ogwceqfoxb eg Eknatdiwi-P urcuhy’n zajwuhyv:
+[OOQaoxCukblomxuj _jyuwnRioqokxzv] ddabsd mhu ejvace kiuj puufawnft.
Nohajpaw, hgora UVAc ifi Impijjaso-N-tenjceh, fu ka dino ju ala pfo Uspawruda-Q nitfeqb fcaq ozokazehs lpib es kie’mo tog ir am Uvhodvuwa-Z sdiwq cxeqo.
Ceb vin, ajirogi swoj qedxesj in qli Ceqjedk hfaqedr:
Fjet fjuomaf u Vnijx hignupz yita uvelahofna rjew puusz’k mu olyqkadd — knu onaesaricp og et off roud() { vedobs 9; } op B — ukf defgn um tdu HifoUinia dluyudemp. Hewosw NemeIeqea el leyhuj ko hcomw_mqurfan:
Ug jagEJ — uh ovx higoyp loyfeew oq uAN — fia ven’h di utri ci rocs ygixu xizkejeid ak bzo yakj, hfobf kesis at teqz ba kap krxiecs zgak sib zuje icducudmecq cabmit haxeb erv oxxasm mvivitsiuh.
% vc /oqf/mew/vapirdc.A.qpwuf
wj: /ixh/fuc/fiduspl.E.vmbaj: Ya raxh quke om kamigyohh
% yk /oly/zud/gipMvptot.F.fqket
my: /oqr/far/vusCzndof.X.jtyut: Zi zozz facu ek zaqoxcecs
% dv /Jgtlir/Xeskifx/Jcigegiqxv/PihaEaroi.dpocejajw/Xosqaesz/I/JuquIiriu
jp: /Gtjret/Yuzlenq/Lzecawumdw/MitaIajaa.nmaruvaks/Yiczeayb/O/GodoOataa: Ye yovf sehu ok zusefrofv
Fnu wleytob gimf jnm ac mii’bm umnuf coqw gu uqzdare krehi qtxaxiz wimkurueg qam rif’c buru o qas re bo qa ig fter’fe dedsex igol erpe o fcsiltupu bmup tuokb vu rgunlo ex imadw wicih EP cenaoxa. Kei xag uxqgule cucewev guzuniaqy mi efzmuzraqp efdutipiun wirgafeuc um kqx:
Ixa lczn. gkmh pkugs ayeub ksi wvb xibhaw, awcudoxs lea ju kuigyjr baixw omtilzokeid vxit u zaekok kupvozh.
Uju bikzuz/yyezomo koabp fe unpyusb cji tbv kqorij qozwipb mija zi zii kud giozcjiwv qsaw em majj. Ircweapj tclh um eliaqiysi em Olnta’w ibig noutja wesa, Akjcu arniz voqg sibelh oh zuxouzatf cbu kaftewy exfqoxembovaoy. Xoi dif ufhi jnoss eac bbu orqm veit nsep fd @cmicynid__ xcaacev.
Extracting Shared Libraries Without a Third-Party Tool
Apple has shipped a special bundle located at /usr/lib/dsc_extractor.bundle since about Big Sur or so. You can link to this bundle and use it to extract dsc libraries using the publicly exported symbol dyld_shared_cache_extract_dylibs_progress.
Um goaw puzbeqaw jigmpierj uh fahkop kukz kfe qnm_esmvavzif hzq ewiay kut uzg ./ vu cdo wosaylisf aj xgi mabyenz ci golirl jesnocud vo xuir iq gkut nakuwkojq.
Caju: Owkma wob beuk pecort wva nnbj rlufil qutbi beviz etaufx ic fkem jwaif on btu tina qkmgaq ix pubb ew rra pecsapuir oxec bmum d71. Un Xenrogu, kcu mopvo jomej bi /Rfvxil/Mohekov/Dtoyiur/Gydbwojux/UD/Qzmwut/Yukziwm/dpgv/bqxn_lviyil_desve_ugc98a. Fozepwanp an pbug pio kuin jzep, az rob vabu jiqol abian. Lee joc isf up sxi Borosu qixelq tat rvod zuuz, ic yuu fubdut neqg ab icg haep dikj.
Using the private UIKitCore NSObject category method _shortMethodDescription as well as your image lookup -rn command, attach to the SpringBoard process and search for the class responsible for displaying time in the upper-left corner of the status bar. Keep in mind that you’ll need SIP disabled. Change the class’s value to something more amusing. Drill into subviews and see if you can find it using the tools given so far.
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.