Chapters

Hide chapters

Advanced Apple Debugging & Reverse Engineering

Third Edition · iOS 12 · Swift 4.2 · Xcode 10

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section III: Low Level

Section 3: 7 chapters
Show chapters Hide chapters

Section IV: Custom LLDB Commands

Section 4: 8 chapters
Show chapters Hide chapters

27. SB Examples, Resymbolicating a Stripped ObjC Binary
Written by Derek Selander

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... 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.

Unlock now

This will be a novel example of what you can do with some knowledge of the Objective-C runtime mixed in with knowledge of the lldb Python module.

When LLDB comes up against a stripped executable (an executable devoid of DWARF debugging information), LLDB won’t have the symbol information to give you the stack trace.

Instead, LLDB will generate a synthetic name for a method it recognizes as a method, but doesn’t know what to call it.

Here’s an example of a synthetic method created by LLDB on a fun-to-explore process…

___lldb_unnamed_symbol906$$SpringBoard

One strategy to reverse engineer the name of this method is to create a breakpoint on it and explore the registers right at the start of the method.

Using your assembly knowledge of the Objective-C runtime, you know the RSI register (x64) or the X1 register (ARM64) will contain the Objective-C Selector that holds the name of method. In addition, you also have the RDI (x64) or X0 (ARM64) register which holds the reference to the instance (or class).

However, as soon as you leave the function prologue, you have no guarantee that either of these registers will contain the values of interest, as they will likely be overwritten. What if a stripped method of interest calls another function? The registers you care about are now lost, as they’re set for the parameters for this new function. You need a way to resymbolicate a stack trace without having to rely upon these registers.

In this chapter, you’ll build an LLDB script that will resymbolicate stripped Objective-C functions in a stack trace.

When you called bt for this process, LLDB didn’t have the function names for the highlighted methods. You will build a new command named sbt that will look for stripped functions and try to resymbolicate them using the Objective-C runtime. By the end of the chapter, your sbt command will produce this:

Those once stripped-out Objective-C function calls are now resymbolicated. As with any of these scripts, you can run this new sbt script on any Objective-C executable provided LLDB can attach to it.

So how are you doing this, exactly?

Let’s first discuss how one can go about resymbolicating Objective-C code in a stripped binary with the Objective-C runtime.

The Objective-C runtime can list all classes from a particular image (an image being the main executable, a dynamic library, an NSBundle, etc.) provided you have the full path to the image. This can be accomplished through the objc_copyClassNamesForImage API.

From there, you can get a list of all classes returned by objc_copyClassNamesForImage where you can dump all class and instance methods for a particular class using the class_copyMethodList API.

Therefore, you can grab all the method addresses and compare them to the addresses of the stack trace. If the stack trace’s function can’t generate a default function name (such as if the SBSymbol is synthetically generated by LLDB), then you can assume LLDB has no debug info for this address.

Using the lldb Python module, you can get the starting address for a particular function — even when a function’s execution is partially complete. This is accomplished using SBValue’s reference to an SBAddress. From there, you can compare the addresses of all the Objective-C methods you’ve obtained to the starting address of the synthetic SBSymbol. If two addresses match, then you can swap out the stripped (synthetic) method name and replace it with the function name that was obtained with the Objective-C runtime.

Don’t worry: You’ll explore this systematically using LLDB’s script command before you go building this Python script.

50 Shades of Ray

Included in the starter directory is an application called 50 Shades of Ray. A well-chosen name (in my humble opinion) for a project that showcases the many faces of Ray Wenderlich. There’s gentle Ray, there’s superhero Ray, there’s confused Ray, there’s even goat BFF Ray!

(lldb) image lookup -a 4449531728
Address: 50 Shades of Ray[0x00000001000017e0] (50 Shades of Ray.__TEXT.__text + 624)
Summary: 50 Shades of Ray`-[ViewController dumpObjCMethodsTapped:] at ViewController.m:36

Using script to guide your way

Time to use the script LLDB command to explore the lldb module APIs and build a quick POC to see how you’re going to tackle finding the starting address of a function in memory.

(lldb) b NSLog
(lldb) script print lldb.frame
frame #0: 0x000000010b472390 Foundation`NSLog
(lldb) script print lldb.frame.symbol
(lldb) script print lldb.frame.symbol.addr.GetLoadAddress(lldb.target)
(lldb) p/x 4484178832

lldb.value with NSDictionary

Since you’re already here, you can explore one more thing. How are you going to parse this NSDictionary with all these addresses?

(lldb) f 1
(lldb) script print lldb.frame.FindVariable('retdict')
(__NSDictionaryM *) retdict = 0x000060800024ce10 10 key/value pairs
(lldb) script print lldb.frame.FindVariable('retdict').deref
(__NSDictionaryM) *retdict = {
  [0] = { 
    key = 0x000060800002bb80 @"4411948768"
    value = 0x000060800024c660 @"-[AppDelegate window]"
  }
  [1] = {
    key = 0x000060800002c1e0 @"4411948592"
    value = 0x000060800024dd10 @"-[ViewController toolBar]"
  }
  [2] = {
    key = 0x000060800002bc00 @"4411948800"
    value = 0x000060800024c7e0 @"-[AppDelegate setWindow:]"
  }
  [3] = {
    key = 0x000060800002bba0 @"4411948864"
    value = 0x000060800004afe0 @"-[AppDelegate .cxx_destruct]"
  }
(lldb) script a = lldb.value(lldb.frame.FindVariable('retdict').deref)
(lldb) script print a[0]
(lldb) script print a[0].key
(__NSCFString *) key = 0x000060800002bb80 @"4411948768"
(lldb) script print a[0].value
(__NSCFString *) value = 0x000060800024c660 @"-[AppDelegate window]"

(lldb) script print a[0].value.sbvalue.description
(lldb) script print '\n'.join([x.key.sbvalue.description for x in a])
4411948768
4411948592
4411948800
4411948864
4411948656
4411948720
4411949072
4411946944
4411946352
4411946976
(lldb) script print '\n'.join([x.value.sbvalue.description for x in a])

The “stripped” 50 Shades of Ray

Yeah, that title got your attention, didn’t it?

(lldb) f 0
(lldb) script lldb.frame.symbol.synthetic
(lldb) f 1
(lldb) script lldb.frame.symbol.synthetic

Building sbt.py

Included within the starter folder is a Python script named sbt.py.

(lldb) help sbt
def generateExecutableMethodsScript(frame_addresses):
    frame_addr_str = 'NSArray *ar = @['
    for f in frame_addresses:
        frame_addr_str += '@"' + str(f) + '",'

    frame_addr_str = frame_addr_str[:-1]
    frame_addr_str += '];'

    # #############################
    # Truncated content...
    # #############################

    command_script += frame_addr_str
    command_script += r'''
  NSMutableDictionary *stackDict = [NSMutableDictionary dictionary];
  [retdict keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) {
    if ([ar containsObject:key]) {
      [stackDict setObject:obj forKey:key];
      return YES;
    }
    return NO;
  }];
  stackDict;
  '''
    return command_script

Implementing the code

The JIT code is already set up. All you need to do is just call it, then compare the return NSDictionary against any synthetic SBValues.

    # New content start 1
    # New content end 1
    # New content start 1
    methods = target.EvaluateExpression(script, generateOptions())
    methodsVal = lldb.value(methods.deref)
    # New content end 1
    # New content start 2
    name = symbol.name
    # New content end 2
  # New content start 2
  if symbol.synthetic: # 1
      children = methodsVal.sbvalue.GetNumChildren() # 2
      name = symbol.name + r' ... unresolved womp womp' # 3

      loadAddr = symbol.addr.GetLoadAddress(target) # 4

      for i in range(children):
          key = long(methodsVal[i].key.sbvalue.description) # 5
          if key == loadAddr:
              name = methodsVal[i].value.sbvalue.description # 6
              break
  else:
      name = symbol.name # 7

  # New content end 2

  offset_str = ''
(lldb) sbt bt
frame #0: 0x1053fe694 UIKit`-[UIView initWithFrame:] 
frame #1: 0x103cf53ac ShadesOfRay`-[RayView initWithFrame:] + 924
frame #2: 0x1053fdda2 UIKit`-[UIView init] + 62
frame #3: 0x103cf45bf ShadesOfRay`-[ViewController generateRayViewTapped:] + 79

Where to go from here?

Congratulations! You’ve used the Objective-C runtime to successful resymbolicate a stripped binary! It’s crazy what you can do with the proper application of Objective-C.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

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.

Unlock now