Swizzling in iOS 11 with UIDebuggingInformationOverlay

Derek Selander

This is an abridged chapter from our book Advanced Apple Debugging & Reverse Engineering, which has been completely updated for Xcode 9.1 and iOS 11. Enjoy!

In this tutorial, 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 an 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.

You’ll explore UIDebuggingInformationOverlay and learn why this class fails to work in iOS 11, 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 an iOS 11 Simulator for this tutorial as Apple can impose new checks on these classes in the future where I have no intention to “up the ante” when they make this class harder to use or remove it from release UIKit builds altogether.

Between iOS 10 and 11

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 11. Executing the exact same LLDB commands in iOS 11 will produce nothing.

To understand what’s happening, you need to explore the overridden methods UIDebuggingInformationOverlay contains and wade into the assembly.

Use LLDB to attach to any iOS 11.x Simulator process, this can MobileSafari, SpringBoard, or your own work. It doesn’t matter if it’s your own app or not, as you will be exploring assembly in the UIKit 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 overridden methods by the UIDebuggingInformationOverlay class.

You can use the image lookup LLDB command:

(lldb) image lookup -rn UIDebuggingInformationOverlay

Or alternatively, you can use the methods command you create in Chapter 14 of the book, “Dynamic Frameworks”:

(lldb) methods UIDebuggingInformationOverlay

The following command would be equivalent to that:

(lldb) exp -lobjc -O -- [UIDebuggingInformationOverlay _shortMethodDescription]

Take note of the overridden init instance method found in the output of either command.

You’ll need to explore what this init is doing. You can follow along with LLDB’s disassemble command, but for visual clarity, I’ll use my own custom LLDB disassembler, dd, which outputs in color and is available here: https://github.com/DerekSelander/lldb.

Here’s the init method’s assembly in iOS 10. If you want to follow along in black & white in LLDB, type:

(lldb) disassemble -n "-[UIDebuggingInformationOverlay init]"

Again, this is showing the assembly of this method in iOS 10.

Colors (and dd‘s comments marked in green) make reading x64 assembly soooooooooooo much easier. In pseudo-Objective-C code, this translates to the following:

@implementation UIDebuggingInformationOverlay

- (instancetype)init {
  if (self = [super init]) {
    [self _setWindowControlsStatusBarOrientation:NO];
  }
  return self;
}

@end

Nice and simple for iOS 10. Let’s look at the same method for iOS 11:

This roughly translates to the following:


@implementation UIDebuggingInformationOverlay

- (instancetype)init {
  static BOOL overlayEnabled = NO;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    overlayEnabled = UIDebuggingOverlayIsEnabled();
  });
  if (!overlayEnabled) { 
    return nil;
  }

  if (self = [super init]) {
    [self _setWindowControlsStatusBarOrientation:NO];
  }
  return self;
}

@end

There are checks enforced in iOS 11 thanks to UIDebuggingOverlayIsEnabled() 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 11 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]:

(lldb) disassemble -n "-[UIDebuggingInformationOverlay init]" -c10

Your assembly won’t be color coded, but this is a small enough chunk to understand what’s going on.

Your output will look similar to:

UIKit`-[UIDebuggingInformationOverlay init]:
  0x10d80023e <+0>:  push   rbp
  0x10d80023f <+1>:  mov    rbp, rsp
  0x10d800242 <+4>:  push   r14
  0x10d800244 <+6>:  push   rbx
  0x10d800245 <+7>:  sub    rsp, 0x10
  0x10d800249 <+11>: mov    rbx, rdi
  0x10d80024c <+14>: cmp    qword ptr [rip + 0x9fae84], -0x1 
    ; UIDebuggingOverlayIsEnabled.__overlayIsEnabled + 7

  0x10d800254 <+22>: jne    0x10d8002c0               ; <+130>
  0x10d800256 <+24>: cmp    byte ptr [rip + 0x9fae73], 0x0 
    ; mainHandler.onceToken + 7

  0x10d80025d <+31>: je     0x10d8002a8               ; <+106>

Pay close attention to offset 14 and 22:

  0x10d80024c <+14>: cmp    qword ptr [rip + 0x9fae84], -0x1 
    ; UIDebuggingOverlayIsEnabled.__overlayIsEnabled + 7

  0x10d800254 <+22>: jne    0x10d8002c0               ; <+130>

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:

  1. 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.

  1. You can use LLDB’s image lookup command with the verbose and symbol option to find the load address for UIDebuggingOverlayIsEnabled.__overlayIsEnabled.
(lldb) image lookup -vs 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:

(lldb) image lookup -a 0x000000010e1fb0d8

Which will then output:

Address: UIKit[0x00000000015b00d8] (UIKit.__DATA.__bss + 24824)
Summary: UIKit`UIDebuggingOverlayIsEnabled.onceToken

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.

Let’s first see what value this has:

(lldb) x/gx 0x000000010e1fb0d8

This will dump out 8 bytes in hex located at 0x000000010e1fb0d8 (your address will be different). If you’ve executed the po [UIDebuggingInformationOverlay new] command earlier, you’ll see -1; if you haven’t, you’ll see 0.

Let’s change this. In LLDB type:

(lldb) mem write 0x000000010e1fb0d8 0xffffffffffffffff -s 8

The -s option specifies the amount of bytes to write to. If typing out 16 f’s is unappealing to you, there’s always alternatives to complete the same task. For example, the following would be equivalent:

(lldb) po *(long *)0x000000010e1fb0d0 = -1

You can of course verify your work be just examining the memory again.

(lldb) x/gx 0x000000010e1fb0d8

The output should be 0xffffffffffffffff now.

Your Turn

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.

Re-run the disassemble command you typed earlier:

(lldb) disassemble -n "-[UIDebuggingInformationOverlay init]" -c10

At the very bottom of output are these two lines:

0x10d800256 <+24>: cmp    byte ptr [rip + 0x9fae73], 0x0 
    ; mainHandler.onceToken + 7
0x10d80025d <+31>: je     0x10d8002a8               ; <+106>

This mainHandler.onceToken is again, the wrong symbol; you care about the symbol immediately following it in memory. I want you to perform the same actions you did on UIDebuggingOverlayIsEnabled.__overlayIsEnabled, but instead apply it to the memory address pointed to by the mainHandler.onceToken symbol. Once you perform the RIP arithmetic, referencing mainHandler.onceToken, you’ll realize the correct symbol, UIDebuggingOverlayIsEnabled.__overlayIsEnabled, is the symbol you are after.

You first need to the find the location of mainHandler.onceToken in memory. You can either perform the RIP arithmetic from the above assembly or use image lookup -vs mainHandler.onceToken to find the end location. Once you found the memory address, write a -1 value into this memory address.

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.

In LLDB type:

(lldb) po [UIDebuggingInformationOverlay new]

Provided you correctly augmented the memory, you’ll be greeted with some more cheery output:

<UIDebuggingInformationOverlay: 0x7fb622107860; frame = (0 0; 768 1024); hidden = YES; gestureRecognizers = <NSArray: 0x60400005aac0>; layer = <UIWindowLayer: 0x6040000298a0>>

And while you’re at it, make sure the class method overlay returns a valid instance:

(lldb) po [UIDebuggingInformationOverlay overlay]

If you got nil for either of the above LLDB commands, make sure you have augmented the correct addresses in memory. If you’re absolutely sure you have augmented the correct addresses and you still get a nil return value, make sure you’re running either the iOS 11.0-11.1 Simulator as Apple could have added additional checks to prevent this from working in a version since this tutorial was written!

If all goes well, and you have a valid instance, let’s put this thing on the screen!

In LLDB, type:

(lldb) po [[UIDebuggingInformationOverlay overlay] toggleVisibility]

Then resume the process:

(lldb) continue

Alright… we got something on the screen, but it’s blank!?

Sidestepping Checks in prepareDebuggingOverlay

The UIDebuggingInformationOverlay is blank because we didn’t call the class method, +[UIDebuggingInformationOverlay prepareDebuggingOverlay]

Dumping the assembly for this method, we can see one concerning check immediately:

Offsets 14, 19, and 21. Call a function named _UIGetDebuggingOverlayEnabled test if AL (RAX‘s single byte cousin) is 0. If yes, jump to the end of this function. The logic in this function is gated by the return value of _UIGetDebuggingOverlayEnabled.

Since we are still using LLDB to build a POC, let’s set a breakpoint on this function, step out of _UIGetDebuggingOverlayEnabled, then augment the value stored in the AL register before the check in offset 19 occurs.

Create a breakpoint on _UIGetDebuggingOverlayEnabled:

(lldb) b _UIGetDebuggingOverlayEnabled

LLDB will indicate that it’s successfully created a breakpoint on the _UIGetDebuggingOverlayEnabled method.

Now, let’s execute the [UIDebuggingInformationOverlay prepareDebuggingOverlay] method, but have LLDB honor breakpoints. Type the following:

(lldb) exp -i0 -O -- [UIDebuggingInformationOverlay prepareDebuggingOverlay]

This uses the -i option that determines if LLDB should ignore breakpoints. You’re specifying 0 to say that LLDB shouldn’t ignore any breakpoints.

Provided all went well, execution will start in the prepareDebuggingOverlay method and call out to the _UIGetDebuggingOverlayEnabled where execution will stop.

Let’s just tell LLDB to resume execution until it steps out of this _UIGetDebuggingOverlayEnabled function:

(lldb) finish

Control flow will finish up in _UIGetDebuggingOverlayEnabled and we’ll be back in the prepareDebuggingOverlay method, right before the test of the AL register on offset 19:

UIKit`+[UIDebuggingInformationOverlay prepareDebuggingOverlay]:
    0x11191a312 <+0>:   push   rbp
    0x11191a313 <+1>:   mov    rbp, rsp
    0x11191a316 <+4>:   push   r15
    0x11191a318 <+6>:   push   r14
    0x11191a31a <+8>:   push   r13
    0x11191a31c <+10>:  push   r12
    0x11191a31e <+12>:  push   rbx
    0x11191a31f <+13>:  push   rax
    0x11191a320 <+14>:  call   0x11191b2bf               
          ; _UIGetDebuggingOverlayEnabled

->  0x11191a325 <+19>:  test   al, al
    0x11191a327 <+21>:  je     0x11191a430               ; <+286>
    0x11191a32d <+27>:  lea    rax, [rip + 0x9fc19c]     ; UIApp

Through LLDB, print out the value in the AL register:

(lldb) p/x $al

Unless you work at a specific fruit company inside a fancy new “spaceship” campus, you’ll likely get 0x00.

Change this around to 0xff:

(lldb) po $al = 0xff

Let’s verify this worked by single instruction stepping:

(lldb) si

This will get you onto the following line:

 je     0x11191a430               ; <+286>

If AL was 0x0 at the time of the test assembly instruction, this will move you to offset 286. If AL wasn’t 0x0 at the time of the test instruction, you’ll keep on executing without the conditional jmp instruction.

Make sure this succeeded by performing one more instruction step.

(lldb) si

If you’re on offset 286, this has failed and you’ll need to repeat the process. However, if you find the instruction pointer has not conditionally jumped, then this has worked!

There’s nothing more you need to do now, so resume execution in LLDB:

(lldb) continue

So, what did the logic do exactly in +[UIDebuggingInformationOverlay prepareDebuggingOverlay]?

To help ease the visual burden, here is a rough translation of what the +[UIDebuggingInformationOverlay prepareDebuggingOverlay] method is doing:

+ (void)prepareDebuggingOverlay {
  if (_UIGetDebuggingOverlayEnabled()) {
    id handler = [UIDebuggingInformationOverlayInvokeGestureHandler mainHandler];
    UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:handler action:@selector(_handleActivationGesture:)];
    [tapGesture setNumberOfTouchesRequired:2];
    [tapGesture setNumberOfTapsRequired:1];
    [tapGesture setDelegate:handler];
    
    UIView *statusBarWindow = [UIApp statusBarWindow];
    [statusBarWindow addGestureRecognizer:tapGesture];
  }
}

This is interesting: There is logic to handle a two finger tap on UIApp’s statusBarWindow. Once that happens, a method called _handleActivationGesture: will be executed on a UIDebuggingInformationOverlayInvokeGestureHandler singleton, mainHandler.

That makes you wonder what’s the logic in -[UIDebuggingInformationOverlayInvokeGestureHandler _handleActivationGesture:] is for?

A quick assembly dump using dd brings up an interesting area:

The UITapGestureRecognizer instance passed in by the RDI register, is getting the state compared to the value 0x3 (see offset 30). If it is 3, then control continues, while if it’s not 3, control jumps towards the end of the function.

A quick lookup in the header file for UIGestureRecognizer, tells us the state has the following enum values:

typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
    UIGestureRecognizerStatePossible,
    UIGestureRecognizerStateBegan,    
    UIGestureRecognizerStateChanged,   
    UIGestureRecognizerStateEnded,      
    UIGestureRecognizerStateCancelled,  
    UIGestureRecognizerStateFailed,     
    UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded 
};

Counting from 0, we can see control will only execute the bulk of the code if the UITapGestureRecognizer‘s state is equal to UIGestureRecognizerStateEnded.

So what does this mean exactly? Not only did UIKit developers put restrictions on accessing the UIDebuggingInformationOverlay class (which you’ve already modified in memory), they’ve also added a “secret” UITapGestureRecognizer to the status bar window that executes the setup logic only when you complete a two finger tap on it.

How cool is that?

So, Recapping…

Before we try this thing out, let’s quickly recap what you did just in case you need to restart fresh:

You found the memory address of UIDebuggingOverlayIsEnabled.onceToken:

(lldb) image lookup -vs UIDebuggingOverlayIsEnabled.onceToken

And then set it to -1 via LLDB’s memory write or just casting the address to a long pointer and setting the value to -1 like so:

(lldb) po *(long *)0x000000010e1fb0d0 = -1

You also performed the same action for UIDebuggingOverlayIsEnabled.__overlayIsEnabled.

You then created a breakpoint on _UIGetDebuggingOverlayEnabled(), executed the +[UIDebuggingInformationOverlay prepareDebuggingOverlay] command and changed the return value that _UIGetDebuggingOverlayEnabled() produced so the rest of the method could continue to execute.

This was one of the many ways to bypass Apple’s new iOS 11 checks to prevent you from using these classes.

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.

You’ll be greeted with the fully functional UIDebuggingInformationOverlay!

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.

But before diving into how, let’s talk about the what.

Method swizzling is the process of dynamically changing what an Objective-C method does at runtime. Compiled code in the __TEXT section of a binary can’t be modified (well, it can with the proper entitlements that Apple will not give you, but we won’t get into that). However, when executing Objective-C code, objc_msgSend comes into play. In case you forgot, objc_msgSend will take an instance (or class), a Selector and a variable number of arguments and jump to the location of the function.

Method swizzling has many uses, but oftentimes people use this tactic to modify a parameter or return value. Alternatively, they can snoop and see when a function is executing code without searching for references in assembly. In fact, Apple even (precariously) uses method swizzling in it’s own codebase like KVO!

Since the internet is full of great references on method swizzling, I won’t start at square one (but if you want to, I’d say http://nshipster.com/method-swizzling/ has the clearest and cleanest discussion of it). Instead, we’ll start with the basic example, then quickly ramp up to something I haven’t seen anyone do with method swizzling: use it to jump into an offset of a method to avoid any unwanted checks!

Finally — Onto A Sample Project

Included in this tutorial is a sample project named Overlay, which you can download here. It’s quite minimal; it only has a UIButton smack in the middle that executes the expected logic to display the UIDebuggingInformationOverlay.

You’ll build an Objective-C NSObject category to perform the Objective-C swizzling on the code of interest as soon as the module loads, using the Objective-C-only load class method.

Build and run the project. Tap on the lovely UIButton. You’ll only get some angry output from stderr saying:

UIDebuggingInformationOverlay 'overlay' method returned nil

As you already know, this is because of the short-circuited overriden init method for UIDebuggingInformationOverlay.

Let’s knock out this easy swizzle first; open NSObject+UIDebuggingInformationOverlayInjector.m. Jump to Section 1, marked by a pragma. In this section, add the following Objective-C class:

//****************************************************/
#pragma mark - Section 1 - FakeWindowClass
//****************************************************/

@interface FakeWindowClass : UIWindow
@end

@implementation FakeWindowClass

- (instancetype)initSwizzled
{
  if (self= [super init]) {
    [self _setWindowControlsStatusBarOrientation:NO];
  }
  return self;
}

@end

For this part, you declared an Objective-C class named FakeWindowClass, which is a subclass of a UIWindow. Unfortunately, this code will not compile since _setWindowControlsStatusBarOrientation: is a private method.

Jump up to section 0 and forward declare this private method.

//****************************************************/
#pragma mark - Section 0 - Private Declarations
//****************************************************/

@interface NSObject()
- (void)_setWindowControlsStatusBarOrientation:(BOOL)orientation;
@end

This will quiet the compiler and let the code build. The UIDebuggingInformationOverlay‘s init method has checks to return nil. Since the init method was rather simple, you just completely sidestepped this logic and reimplemented it yourself and removed all the “bad stuff”!

Now, replace the code for UIDebuggingInformationOverlay‘s init with FakeWindowClass‘s initSwizzled method. Jump down to section 2 in NSObject‘s load method and replace the load method with the following:

+ (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];
  });
}

Rerun and build the Overlay app with this new code. Tap on the UIButton to see what happens now that you’ve replaced the init to produce a valid instance.

UIDebuggingInformationOverlay now pops up without any content. Almost there!

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.

In order to get around this, you’ll you’ll “simulate” a call instruction by pushing a return address onto the stack, but instead of call‘ing, you’ll jmp into an offset past the _UIGetDebuggingOverlayEnabled check. That way, you can perform the function proglogue in your stack frame and directly skip the dreaded check in the beginning of prepareDebuggingOverlay.

In NSObject+UIDebuggingInformationOverlayInjector.m, Navigate down to Section 3 – prepareDebuggingOverlay, and add the following snippet of code:

+ (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
}

Let’s break this crazy witchcraft down:

  1. I want to get the starting address of the original prepareDebuggingOverlay. However, I know this will be swizzled code, so when this code executes, prepareDebuggingOverlaySwizzled will actually point to the real, prepareDebuggingOverlay starting address.
  2. I take the starting address of the original prepareDebuggingOverlay (given to me through the imp variable) and I offset the value in memory past the _UIGetDebuggingOverlayEnabled() check. I used LLDB to figure the exact offset by dumping the assembly and calculating the offset (disassemble -n "+[UIDebuggingInformationOverlay prepareDebuggingOverlay]"). This is insanely brittle as any new code or compiler changes from clang will likely break this. I strongly recommend you calculate this yourself in case this changes past iOS 11.1.1.
  3. Since you are faking a function call, you need an address to return to after this soon-to-be-executed function offset finishes. This is accomplished by getting the address of a declared label. Labels are a not often used feature by normal developers which allow you to jmp to different areas of a function. The use of labels in modern programming is considered bad practice as if/for/while loops can accomplish the same thing… but not for this crazy hack.
  4. This is the declaration of the label RETURNADDRESS. No, you do need that semicolon after the label as the C syntax for a label to have a statement immediately following it.

Time to cap this bad boy off with some sweet inline assembly! Right above the label RETURNADDRESS declaration, add the following inline assembly:

+ (void)prepareDebuggingOverlaySwizzled {
  Class cls = NSClassFromString(@"UIDebuggingInformationOverlay");
  SEL sel = @selector(prepareDebuggingOverlaySwizzled);
  Method m = class_getClassMethod(cls, sel); 

  IMP imp =  method_getImplementation(m); 
  void (*methodOffset) = (void *)((imp + (long)27)); 
  void *returnAddr = &&RETURNADDRESS; 
  
  __asm__ __volatile__(     // 1
      "pushq  %0\n\t"       // 2
      "pushq  %%rbp\n\t"    // 3
      "movq   %%rsp, %%rbp\n\t"
      "pushq  %%r15\n\t"
      "pushq  %%r14\n\t"
      "pushq  %%r13\n\t"
      "pushq  %%r12\n\t"
      "pushq  %%rbx\n\t"
      "pushq  %%rax\n\t"
      "jmp  *%1\n\t"        // 4
      :
      : "r" (returnAddr), "r" (methodOffset)); // 5
  
  RETURNADDRESS: ;  // 5
}
  1. Don’t be scared, you’re about to write x86_64 assembly in AT&T format (Apple’s assembler is not a fan of Intel). That __volatile__ is there to hint to the compiler to not try and optimize this away.
  2. You can think of this sort of like C’s printf where the %0 will be replaced by the value supplied by the returnAddr. In x86, the return address is pushed onto the stack right before entering a function. As you know, returnAddr points to an executable address following this assembly. This is how we are faking an actual function call!
  3. The following assembly is copy pasted from the function prologue in the +[UIDebuggingInformationOverlay prepareDebuggingOverlay]. This lets us perform the setup of the function, but allows us to skip the dreaded check.
  4. Finally we are jumping to offset 27 of the prepareDebuggingOverlay after we have set up all the data and stack information we need to not crash. The jmp *%1 will get resolved to jmp‘ing to the value stored at methodOffset. Finally, what are those “r” strings? I won’t get too into the details of inline assembly as I think your head might explode with an information overload (think Scanners), but just know that this is telling the assembler that your assembly can use any register for reading these values.

Jump back up to section 2 where the swizzling is performed in the +load method and add the following line of code to the end of the method:

[self swizzleOriginalSelector:@selector(prepareDebuggingOverlay) 
          withSizzledSelector:@selector(prepareDebuggingOverlaySwizzled) 
                     forClass:cls
                isClassMethod:YES];

Build and run. Tap on the UIButton to execute the required code to setup the UIDebuggingInformationOverlay class, then perform the two-finger tap on the status bar.

Omagerd, can you believe that worked?

I am definitely a fan of the hidden status bar dual tap thing, but let’s say you wanted to bring this up solely from code. Here’s what you can do:

Open ViewController.swift. At the top of the file add:

import UIKit.UIGestureRecognizerSubclass

This will let you set the state of a UIGestureRecognizer (default headers allow only read-only access to the state variable).

Once that’s done, augment the code in overlayButtonTapped(_ sender: Any) to be the following:

@IBAction func overlayButtonTapped(_ sender: Any) {
  guard 
    let cls = NSClassFromString("UIDebuggingInformationOverlay") as? UIWindow.Type else {
      print("UIDebuggingInformationOverlay class doesn't exist!")
      return
  }    
  cls.perform(NSSelectorFromString("prepareDebuggingOverlay"))

  let tapGesture = UITapGestureRecognizer()
  tapGesture.state = .ended

  let handlerCls = NSClassFromString("UIDebuggingInformationOverlayInvokeGestureHandler") as! NSObject.Type
  let handler = handlerCls
    .perform(NSSelectorFromString("mainHandler"))
    .takeUnretainedValue()
  let _ = handler
    .perform(NSSelectorFromString("_handleActivationGesture:"),
             with: tapGesture)
}

Final build and run. Tap on the button and see what happens.

Boom.

Where to Go From Here?

You can download the final project from this tutorial here.

Crazy tutorial, 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 11 while getting around Apple’s newly introduced checks to prevent you from using this class.

Then you used Objective-C’s method swizzling to perform the same actions as well as hook into only a portion of the original method, bypassing several short-circuit checks.

This is why reverse engineering Objective-C is so much fun, because you can hook into methods that are quietly called in private code you don’t have the source for and make changes or monitor what it’s doing.

Still have energy after that brutal chapter? This swizzled code will not work on an ARM64 device. You’ll need to look at the assembly and perform an alternative action for that architecture likely through a preprocessor macro.

If you enjoyed what you learned in the tutorial, why not check out the complete Advanced Apple Debugging & Reverse Engineering book, available in our store?

One thing you can be sure of: after reading this book, you’ll have the tools and knowledge to answer even the most obscure question about your code — or even someone else’s.

Questions? Comments? Come join the forum discussion below!

Other Items of Interest

Save time.
Learn more with our video courses.

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 20 total!

iOS Team

... 74 total!

Android Team

... 30 total!

Unity Team

... 12 total!

Articles Team

... 14 total!

Resident Authors Team

... 25 total!

Podcast Team

... 7 total!

Recruitment Team

... 9 total!