How To Create an Xcode Plugin: Part 3/3

Wrap up your Rayrolling Xcode plugin by getting your hands dirty with more assembly language and Cycript in this final instalment of the three-part tutorial series. By Derek Selander.

Leave a rating/review
Save for later
Share

Update note: This tutorial has only been tested with Xcode 6.3.2 — if you’re working with another version of Xcode, your experience might not match up perfectly with this tutorial.

Welcome to the third and final part in this tutorial series on creating an Xcode Plugin. In the second part of this tutorial, you explored Xcode with LLDB and Dtrace while showcasing Ray’s hit song, Never Gonna Live You Up. In this tutorial, you’ll finish up your Rayrolling pranks by swapping any documentation URL requests for a YouTube link which Rayrolls the victim:

Plugin_Swizzle_Documentation

You’ll make use of the Cycript tool to dig into the x86_64 assembly behind Xcode to determine how to bend Xcode to your will. While this tutorial will walk you through each piece of assembly code, reading Part I and Part II of Mike Ash’s series on x86_64 assembly will definitely help you get the most out of this tutorial.

You’ll continue with the same Rayrolling project from before; if you didn’t work through Part 2, or just want a fresh start, you can download the finished Rayrolling Project from Part 2 here.

Getting Started

Cycript is a tool is authored by @saurik of Cydia fame, which lets you explore and modify code on iOS or OS X using a hybrid of Javascript and Objective-C. You can explore Xcode’s contents in memory while making your plugin development life considerably easier.

Download Cycript here and extract it to a convenient location.

Navigate to the Cycript.lib subdirectory and copy all of the dylibs to /usr/lib/. Copy the cycript binary found in the same directory of the dylibs to /usr/local/bin.

Open the Rayrolling project and navigate to Rayrolling.m. Add the following import header to the top of the file:

#import <dlfcn.h>

Now, add the following code to the beginning of pluginDidLoad::

// 1
void *handle = dlopen("/usr/lib/libcycript.dylib", RTLD_NOW); 

// 2
void (*listenServer)(short) = dlsym(handle, "CYListenServer");

// 3
(*listenServer)(54321);

Taking each numbered comment in turn:

  1. Although the Cycript zip contains a Framework, you’re using the dynamic library directly instead. dlopen opens the dynamic library given by the char *. You specify to load the contents immediately with RTLD_NOW.
  2. Using dlsym, you create a function pointer to a method you’ll need to start the Cycript server.
  3. Finally, you call CYListenServer and specify that it broadcast itself on a specific port using C’s good ‘ol function pointers.

Build and run the Xcode plugin to generate and install it. Once the child Xcode appears, stop and quit all running Xcodes and relaunch. With a new instance of Xcode in memory, head to Terminal and type the following:

cycript -r 127.0.0.1:54321
cy#

This launches Cycript and listens to the IP address (localhost) for broadcasts on port 54321. To exit Cycript, type Ctrl-D.

If you attached Cycript without issue, you’ll see the cy# prompt. Cycript will launch and attach itself to Xcode. Cycript’s “Javascript-y” syntax makes debugging incredibly easy with its built-in tab autocompletion.

Want to see all the instance variables for a particular class? Just prepend * to your query and it will print out all instance variables for a particular instance:

cy# *NSApplication.sharedApplication.delegate
...

This is much like the malloc_info -t {heap address}you saw in the previous tutorial. The output is presented as instance variables instead of properties; you access these instance variables like so:

cy# NSApplication.sharedApplication.delegate->_sourceControlUIHandler 

You also dump the contents of the _sourceControlUIHandler by using the same technique.

Note: If you want to see what else you can do with Cycript, check out iPhoneDevWiki’s Cycript page for some useful tricks. Be sure to read up on printMethods(); it’s incredibly useful.

Much like the heap LLDB script you explored in the previous tutorial, Cycript also has a command called choose, which hunts down all the instances of a particular class in memory:

cy# choose(NSViewController)
...
cy# choose(NSViewController)[0]
#"<IDELicenseAgreementViewController: 0x7faa3d244410>"
cy# var ivc = _ 
#"<IDELicenseAgreementViewController: 0x7faa3d244410>"
cy# *ivc
{isa:IDELicenseAgreementViewController,_nextResponder:null,_nibName:@"IDELicenseAgreementViewController",_nibBundle:#"NSBundle </Applications/Xcode.app/Contents/Frameworks/IDEKit.framework> (loaded)",_representedObject:null,_title:null,view:null,_topLevelObjects:null,_editors:null,_autounbinder:null,_designNibBundleIdentifier:null,__privateData:#"<_NSViewControllerPrivateData: 0x7faa3fd17370>",_viewIsAppearing:0,_delayViewDidAppear:0,_isContentViewController:0,_reserved:0,_licenseAgreementHelper:#"<DVTLicenseAgreementHelper: 0x7faa3d249610>",_agreementStatus:0,_licenseAgreement:null,_licenseAgreementTextView:null,_foregroundContentView:null,_licenseOnly:1,_licenseSubtitle:null}

This sets the contents of the Javascript ivc variable to be the first NSViewController returned in the choose(NSViewController) array.

However, there’s something very special about Saurik’s implementation of choose. Unlike objc_ref, which you learned about in the previous tutorial, choose returns an actual NSArray (or Javascript array!) of all the objects that it found in the heap. This means that you can run complex filters on any array of objects to determine how you want to filter them!

For example, try the follwing in Cycript:

cy# choose(NSTextView)
...
cy# choose(NSTextView).filter(function(object) { return  [[object string] containsString:@"Copyright"]  })

Using a mashup of Objective-C and Javascript, you can find all the NSTextView instances on the heap and filter them for only the ones with the word “Copyright” in the string property.

This feature will prove invaluable in the section to come.

Hunting For Documentation

With Cycript as your brand new best friend, you’ll hunt down all the NSViewControllers that belong to a specific window.

In Xcode, select Window\Documentation and API Reference.

Try and figure out how to filter out all the NSViewControllers that belong to this particular window using Cycript. The solution is below if you need a bit of help.

[spoiler title=”NSViewController Search”]

There are many ways to do this, but one way is to filter all NSViewControllers whose NSWindow‘s title contains the text “Documentation”:

cy# choose(NSViewController).filter(function(object) { return [[[[object view] window] title] containsString:@"Documentation "] });

[/spoiler]

This filters the NSViewController array down considerably to a size that you can navigate on your own.

Use the familiar technique of hiding and showing NSViewController views to explore which NSViewControllers are where:

cy# var vcs = _ 
...
cy# vcs[0].view.hidden = true 
true 
cy# vcs[0].view.hidden = false 
false 

Views automatically update when you modify them in Cycript; you don’t have to resume or pause execution in the debugger. You’ll wonder how you went this long without this feature! :]

By going through the NSViewController array in a systematic fashion, you can build a mental picture of what each NSViewController is responsible for by toggling the NSView‘s hidden property. I’ve done this for you, and here’s a visualization of Xcode’s Documentation window:

Xcode_Documentation_Window

Looking at the above image, it looks like IDEDocWebViewContentViewController might be a good place to start snooping to see what you can modify.

In Cycript:

cy# choose(IDEDocWebViewContentViewController)
[#"<IDEDocWebViewContentViewController: 0x7faa42269aa0 representing: (null)>"]
cy# choose(IDEDocWebViewContentViewController)[0]
#"<IDEDocWebViewContentViewController: 0x7faa42269aa0 representing: (null)>"
cy# var wvc = _ 
#"<IDEDocWebViewContentViewController: 0x7faa42269aa0 representing: (null)>"
cy# *wvc 
...

Wading though the instance variables of IDEDocWebViewContentViewController, there’s an interesting one called _webView. You can try to find a property equivalent to access this instance variable:

cy# wvc.webView
#"<WebView: 0x7faa41b80e50>"

Now, where is it located on the screen?

cy# wvc.webView.hidden = true 
cy# wvc.webView.hidden = false  

IDE_Doc_Hidding_view

Aha — webView is responsible for the detailed documentation section in the lower right. You’ve found the NSViewController and the class of interest to start figuring out where to perform the Rayrolling!

Cycript Swizzling

You found the class that will perform your Rayroll, but you need to figure out how the class works. Cocoa’s WebView has many methods, so you need to find out which ones Xcode uses behind the scenes.

Perusing the the documentation for WebView indicates that WebFrame performs much of the network interaction with URLs. That would be a good place to start looking.

Perhaps a quick Dtrace script will do the trick. After all, you only care about what the WebView‘s WebFrame is doing, not what it’s interacting with.

Open a new session in Terminal that does not have Cycript or LLDB. From there, type the following:

sudo dtrace -n 'objc$target:WebFrame::entry { @[probefunc] = count() }' -p `pgrep -xo Xcode`

With this script running, click around the documentation, and search for something like NSView. Stop the Dtrace script and look through the results.

It should be pretty obvious that WebView‘s WebFrame performs loadRequest: when fetching new data.

You’re now one step closer to pulling this off. You’ve found the view controller, the class of interest, and the method of interest. Instead of the usual code, swizzle, build cycle, you can use Cycript to dynamically swizzle this method without having to restart Xcode at all. That way you can see if this trick works, without all the implementation overhead in case it fails.

Back in Cycript, hunt down loadRequest: and assign it to a global Javascript variable:

cy# original_WebFrame_method = WebFrame.messages['loadRequest:']
0x11f4141e0

Now paste the following contents in Cycript:

function swizzled_loadRequest(request) { 
 var swizzledURL = [new NSURL initWithString:@"https://www.youtube.com/watch?v=ce-_0opZzh0"];
 var swizzledRequest = [new NSURLRequest initWithURL: swizzledURL];
 original_WebFrame_method.call(this, swizzledRequest); 
}

This is the Javascript equivalent function for a swizzled loadRequest:. You throw out the original request and make a new request to the lovely Rayrolling Video.

Now swap the methods:

WebFrame.messages['loadRequest:'] = swizzled_loadRequest 

Be very careful that there are no syntax errors in your swizzled function. The smallest typo will cause Xcode to crash. Which, come to think of it, is pretty much par for the course for Xcode. :]

ragecomic2

Try searching for something like NSObject in the documentation and press Enter.

But wait… instead of opening up the YouTube page in the Xcode documentation’s WebView, Xcode decided to dump you to your default browser and open it from there. This is clearly unacceptable — Xcode must bend to your will, not the other way around! :]

You’ll need to figure out what class is preventing the WebView from doing this and correct its behavior.

Since you will be viewing assembly, it would be best to see the code execution path when actually testing against the failed YouTube URL. Now that you’ve done the initial testing with Cycript swizzling, it’s time to implement it “for real” in your plugin.

Create a new WebFrame Category and name it Rayrolling WebFrame.

In WebFrame+Rayrolling_WebFrame.h, add the following header to the top so you can access this class correctly:

#import <WebKit/WebKit.h>

Now open WebView+Rayrolling_WebFrame.m and replace its contents with the following:

#import "WebFrame+Rayrolling_WebFrame.h"
#import "NSObject+MethodSwizzler.h" 
#import "Rayrolling.h"

@implementation WebFrame (Rayrolling_WebFrame)

+ (void)load {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{ 
    [self swizzleWithOriginalSelector:@selector(loadRequest:) swizzledSelector:@selector(Rayrolling_loadRequest:) isClassMethod:NO];
  });
}

- (void)Rayrolling_loadRequest:(NSURLRequest *)request {
  if ([Rayrolling isEnabled]) {
    NSURL *url = [NSURL URLWithString:@"https://www.youtube.com/watch?v=ce-_0opZzh0"];
    NSURLRequest *rickrollingRequest = [NSURLRequest requestWithURL:url];
    [self Rayrolling_loadRequest:rickrollingRequest];
  } else {
    [self Rayrolling_loadRequest:request];
  }
}
@end 

Build and run this to update the plugin, then quit and relaunch Xcode so the updated contents are loaded into memory.

Wading Through Assembly

Typically, when either an iOS or OS X application launches another application in this sort of manner, there are a very small set of APIs that could be the culprit. One very common one is openURL:.

Fire up a new tab in the Terminal, launch LLDB, and attach it to Xcode. From there, set a breakpoint on any class that has this particular method:

lldb
(lldb) pro at -n Xcode 
...
(lldb) rb openURL: 
...
(lldb) c

Go back to the Documentation window and try searching for a new item, say, NSString. Immediately upon hitting Enter in the Documentation Window, LLDB breaks on -[IDEWorkspace openURL:]. Bingo!

Just for giggles, make sure this is the correct URL:

(lldb) po $rdx 
<iframe width="500" height="375" src="https://www.youtube.com/embed/ce-_0opZzh0?feature=oembed" frameborder="0" allowfullscreen></iframe>

Good. So now you need to see how it was called:

(lldb) bt 5
* thread #1: tid = 0x2ea1d, 0x00007fff88b529bf AppKit`-[NSWorkspace openURL:], queue = 'com.apple.main-thread', stop reason = breakpoint 1.6
  * frame #0: 0x00007fff88b529bf AppKit`-[NSWorkspace openURL:]
    frame #1: 0x0000000119f5ab47 IDEDocViewer`-[IDEDocWebViewContentViewController haveWorkspaceOpenOrRevealURL:] + 577
    frame #2: 0x0000000119f5a5e1 IDEDocViewer`-[IDEDocWebViewContentViewController webView:decidePolicyForNavigationAction:request:frame:decisionListener:] + 607
    frame #3: 0x000000011d19cbd2 Rickrolling`-[NSViewController(self=0x00007fa3d75b3f20, _cmd=0x00007fff89c18f65, webView=0x00007fa3d75c46c0, actionInformation=0x00007fa3dadf5f40, request=0x00007fa3d2dae450, frame=0x00007fa3d73cbee0, listener=0x00007fa3dceee600) Rr_swzl_webView:decidePolicyForNavigationAction:request:frame:decisionListener:] + 274 at NSViewController+IDEDocWebViewContentViewController_Swizzler.m:35
    frame #4: 0x00007fff86b49ebc CoreFoundation`__invoking___ + 140

Looking at frame #2, it seems that this would be the method which makes the decision to launch it internally in the Documentation window or to hand it off to the default browser.

Doing a search on this method indicates that webView:decidePolicyForNavigationAction:request:frame:decisionListener: is defined in the WebPolicyDelegate protocol.

According to the Apple documentation on this protocol, the decisionListener implements WebPolicyDecisionListener which means, based upon some internal logic, you will call [listener ignore] if you don’t want to not load the content at all, [listener download] if you want to download the content, or [listener use] if you want to open the URL right in the WebView.

There’s no other way around it. You need to see what’s happening inside this method. Back to the magical land of assembly you thought you escaped in the first tutorial! :]

Note: If you aren’t fully caffeinated yet and want to skip the disassembly analysis, skip ahead to the final reconstructed method implementation.

In LLDB:

(lldb) di -n '-[IDEDocWebViewContentViewController webView:decidePolicyForNavigationAction:request:frame:decisionListener:]'

This dumps out a fair bit of assembly. It’s OK — don’t assume the fetal position and huddle in the corner. You’ll go through this assembly section by section and you’ll see that it’s not that bad.

Note: You’ll notice that the addresses are different on your workstation — this is to be expected.
0x114da2382 <+0>:   pushq  %rbp
0x114da2383 <+1>:   movq   %rsp, %rbp
0x114da2386 <+4>:   pushq  %r15
0x114da2388 <+6>:   pushq  %r14
0x114da238a <+8>:   pushq  %r13
0x114da238c <+10>:  pushq  %r12
0x114da238e <+12>:  pushq  %rbx                     ; // 1
0x114da238f <+13>:  subq   $0x58, %rsp
0x114da2393 <+17>:  movq   %r8, %r12
0x114da2396 <+20>:  movq   %rcx, %r13
0x114da2399 <+23>:  movq   %rdi, -0x58(%rbp)
0x114da239d <+27>:  movq   0x10(%rbp), %r15
0x114da23a1 <+31>:  movq   %rdi, -0x30(%rbp)         ; // 2 
0x114da23a5 <+35>:  movq   %rsi, -0x38(%rbp)
0x114da23a9 <+39>:  movq   0x61f20(%rip), %r14       ; (void *)0x00007fff95678050: objc_retain
0x114da23b0 <+46>:  movq   %rdx, %rdi
0x114da23b3 <+49>:  callq  *%r14
0x114da23b6 <+52>:  movq   %rax, -0x40(%rbp)
0x114da23ba <+56>:  movq   %r13, %rdi
0x114da23bd <+59>:  callq  *%r14
0x114da23c0 <+62>:  movq   %rax, -0x48(%rbp)
0x114da23c4 <+66>:  movq   %r12, %rdi
0x114da23c7 <+69>:  callq  *%r14
0x114da23ca <+72>:  movq   %rax, %rbx               ; // 3 
0x114da23cd <+75>:  movq   %rbx, -0x50(%rbp)
0x114da23d1 <+79>:  movq   %r15, %rdi
0x114da23d4 <+82>:  callq  *%r14
0x114da23d7 <+85>:  movq   %rax, %r14                ; // 4

Take a deep breath; here’s what all that means:

  1. After this instruction, all the scratchspace registers are now pushed. The pushq operand saves the state of the register so it can be popq‘d at a later time when leaving the function.
  2. The $rdi register, which holds the WebPolicyDecisionListener (aka the IDEDocWebViewContentViewController instance) is set to an address that is -0x30 below the address of $rbp. You can access it in LLDB lke so: x/gx '$rbp - 0x30'. The address spat out will be the address which you can then po in LLDB.
  3. Here’s the fun part of assembly: navigating which register stores what, and where. The contents of the return register $rax are copied to $rbx. $rax was set by the retain call in $r14 with an object passed in by $r12. $r12 was set by $r8. As you learned earlier, $r8 is the 3rd parameter passed into a function (not including the “self” $rdi register parameter nor the $rsi Selector register). Looking at the documentation again for this method implies that as of right now, $rbx should contain the NSURLRequest instance.
  4. Yep, the assembly is still performing the setup. As you can see, $r14 is passed the retain Selector for memory management, so the assembly is simply going through and retaining the items so they don’t disappear and cause a crash.

Ok…that wasn’t so bad. Onto the next section:

0x114da23da <+88>:  movq   0x7fc17(%rip), %rsi     ; "URL"
0x114da23e1 <+95>:  movq   0x61ed8(%rip), %r12     ; (void *)0x00007fff956700c0: objc_msgSend
0x114da23e8 <+102>: movq   %rbx, %rdi              ; // 1 
0x114da23eb <+105>: callq  *%r12
0x114da23ee <+108>: movq   %rax, %rdi
0x114da23f1 <+111>: callq  0x114de6bee             ; symbol stub for: objc_retainAutoreleasedReturnValue
0x114da23f6 <+116>: movq   %rax, %r15
0x114da23f9 <+119>: movq   0x7fb28(%rip), %rsi     ; "absoluteString"
0x114da2400 <+126>: movq   %r15, %rdi
0x114da2403 <+129>: callq  *%r12
0x114da2406 <+132>: movq   %rax, %rdi
0x114da2409 <+135>: callq  0x114de6bee             ; symbol stub for: objc_retainAutoreleasedReturnValue
0x114da240e <+140>: movq   %rax, %rbx
0x114da2411 <+143>: movq   0x7fde8(%rip), %rsi     ; "isEqualToString:" // 2 
0x114da2418 <+150>: leaq   0x641a1(%rip), %rdx     ; @"about:blank"
0x114da241f <+157>: movq   %rbx, %rdi
0x114da2422 <+160>: callq  *%r12
0x114da2425 <+163>: movb   %al, %r12b              ; // 3 
0x114da2428 <+166>: movq   0x61e99(%rip), %r13     ; (void *)0x00007fff95678440: objc_release
0x114da242f <+173>: movq   %rbx, %rdi
0x114da2432 <+176>: callq  *%r13
0x114da2435 <+179>: movq   %r15, %rdi
0x114da2438 <+182>: callq  *%r13
0x114da243b <+185>: testb  %r12b, %r12b            ; // 4 
0x114da243e <+188>: je     0x114da246e             ; <+236> // 5
  1. Remember that $rbx contains the NUSRLRequest at this point.
  2. This section is easy; just by looking at the disassembly’s comments, you can tell that the URL is being compared to @"about:blank"
  3. The result of isEqualToString: is now passed from $al to $r12b. $al is a register only 8 bits long. It wouldn’t make sense to store a BOOL value in 64 bits.
  4. The testb instruction compares the destination with the source operand. If the value in $r12b is a 1, then the ZF register flag will be a 0.
  5. Based upon the ZF register this instruction jumps to 0x119f5a46e if the value is 1. Another way to summarize the last two operands: if the value is not equal to @"about:blank", then jump to 0x119f5a46e
  6. .

You’re starting to see the method come together! So far you have the following:

- (void)webView:(WebView *)webView
decidePolicyForNavigationAction:(NSDictionary *)actionInformation
        request:(NSURLRequest *)request
          frame:(WebFrame *)frame
decisionListener:(id<WebPolicyDecisionListener>)listener
{
  if ([[[request URL] absoluteString] isEqualToString:@"about:blank"]) {
    // TODO 
  } else {
    // jump to 0x119f5a46e
  }
}

Onto the next section:

0x114da2440 <+190>: movq   0x7fdc1(%rip), %rsi     ; "ignore"
0x114da2447 <+197>: movq   %r14, %rdi
0x114da244a <+200>: callq  *0x61e70(%rip)          ; (void *)0x00007fff956700c0: objc_msgSend // 1 
0x114da2450 <+206>: movq   -0x40(%rbp), %r13
0x114da2454 <+210>: movq   -0x48(%rbp), %rax
0x114da2458 <+214>: movq   -0x50(%rbp), %r15
0x114da245c <+218>: movq   %r14, %r12
0x114da245f <+221>: movq   %rax, %r14
0x114da2462 <+224>: movq   0x61e5f(%rip), %rbx     ; (void *)0x00007fff95678440: objc_release
0x114da2469 <+231>: jmp    0x114da2621             ; <+671> // 2 
0x114da246e <+236>: movq   %r14, -0x60(%rbp)
0x114da2472 <+240>: movq   0x7fd9f(%rip), %rsi     ; "_allowURLRequest:webView:" // 3 
0x114da2479 <+247>: movq   -0x58(%rbp), %r15
0x114da247d <+251>: movq   %r15, %rdi
0x114da2480 <+254>: movq   -0x50(%rbp), %r14
0x114da2484 <+258>: movq   %r14, %rdx
0x114da2487 <+261>: movq   -0x40(%rbp), %rbx
0x114da248b <+265>: movq   %rbx, %rcx
0x114da248e <+268>: callq  *0x61e2c(%rip)          ; (void *)0x00007fff956700c0: objc_msgSend // 4
0x114da2494 <+274>: testb  %al, %al  				       ; // 5
0x114da2496 <+276>: movq   %rbx, %r13
0x114da2499 <+279>: je     0x114da25a4             ; <+546> // 6 
  1. It looks like ignore is called on the listener. This happens if the URL absoluteString is equal to @"about:blank".
  2. If absoluteString is equal to @"about:blank", there’s some further stack movement followed by a jump to some address further down.
  3. This is a new interesting Selector of IDEDocWebViewContentViewController. Disassembling this class is an exercise left to the reader. :] To get started in LLDB: di -n '-[IDEDocWebViewContentViewController _allowURLRequest:webView:]'.
  4. _allowURLRequest:webView: is now being called.
  5. This tests whether _allowURLRequest:webView: returned 1 or 0
  6. The code jumps if ZF is set to 1. That is, if _allowURLRequest:webView: returns false, the jump instruction will be executed.

You can now build out your pseudo-code a little bit further.

- (void)webView:(WebView *)webView
decidePolicyForNavigationAction:(NSDictionary *)actionInformation
        request:(NSURLRequest *)request
          frame:(WebFrame *)frame
decisionListener:(id<WebPolicyDecisionListener>)listener
{
  if ([[[request URL] absoluteString] isEqualToString:@"about:blank"]) {
    [listener ignore];
    // Do some stack cleanup logic
    // 0x114da2469 <+231>: jmp    0x114da2621      

  } else if ([self _allowURLRequest:request webView:webView]) {

  } else {
  	// 0x114da2499 <+279>: je     0x114da25a4               ; <+546>
  }
}

On to the final section — you’re almost there!

ragecomic

0x114da249f <+285>: movq   %r14, %r13
0x114da24a2 <+288>: movq   0x61d87(%rip), %rax     ; (void *)0x00007fff788d7800: WebActionModifierFlagsKey  						    ; 
0x114da24a9 <+295>: movq   (%rax), %rdx				     ; // 1 
0x114da24ac <+298>: movq   0x7f5dd(%rip), %rsi     ; "objectForKey:"
0x114da24b3 <+305>: movq   -0x48(%rbp), %rdi       ; // 2 
0x114da24b7 <+309>: movq   0x61e02(%rip), %r12     ; (void *)0x00007fff956700c0: objc_msgSend
0x114da24be <+316>: callq  *%r12
0x114da24c1 <+319>: movq   %rax, %rdi
0x114da24c4 <+322>: callq  0x114de6bee             ; symbol stub for: objc_retainAutoreleasedReturnValue
0x114da24c9 <+327>: movq   %rax, %rbx
0x114da24cc <+330>: movq   0x7fd4d(%rip), %rsi     ; "unsignedIntegerValue" // 3
0x114da24d3 <+337>: movq   %rbx, %rdi
0x114da24d6 <+340>: callq  *%r12
0x114da24d9 <+343>: movq   %rax, %r14
0x114da24dc <+346>: movq   %rbx, %rdi
0x114da24df <+349>: callq  *0x61de3(%rip)          ; (void *)0x00007fff95678440: objc_release
0x114da24e5 <+355>: testl  $0x100000, %r14d        ; // 4
0x114da24ec <+362>: je     0x114da25fb             ; <+633> // 5

And here’s the location where the above instruction was jumping to…

0x114da25fb <+633>: movq   0x7fc26(%rip), %rsi     ; "use" // 6 
0x114da2602 <+640>: movq   -0x60(%rbp), %r12
0x114da2606 <+644>: movq   %r12, %rdi
0x114da2609 <+647>: callq  *0x61cb1(%rip)          ; (void *)0x00007fff956700c0: objc_msgSend
0x114da260f <+653>: movq   %r13, %r15
0x114da2612 <+656>: movq   0x61caf(%rip), %rbx     ; (void *)0x00007fff95678440: objc_release
0x114da2619 <+663>: movq   -0x40(%rbp), %r13
0x114da261d <+667>: movq   -0x48(%rbp), %r14
0x114da2621 <+671>: movq   %r12, %rdi

  1. The WebActionModifierFlagsKey is loaded into $rdx.
  2. The ‘$rbp – 0x40’ address contained the navigationAction NSDictionary parameter. It’s loaded into the “self” register for objc_msgSend.
  3. The result seems to be sending unsignedIntegerValue to another object, probably an NSNumber.
  4. This tests unsignedIntegerValue against 0x100000. This is likely some internal int or enum hidden by the compiler.
  5. If the value is not equal to 0x100000, then jump to the specified address.
  6. Following the address jump, if the value is not equal to 0x100000, you will finally see logic that uses the use command.

Here’s what the final reconstructed method looks like now:

- (void)webView:(WebView *)webView
decidePolicyForNavigationAction:(NSDictionary *)actionInformation
        request:(NSURLRequest *)request
          frame:(WebFrame *)frame
decisionListener:(id<WebPolicyDecisionListener>)listener
{
  if ([[[request URL] absoluteString] isEqualToString:@"about:blank"]) {
    [listener ignore];
    // Do some stack cleanup logic
    // 0x114da2469 <+231>: jmp    0x114da2621      
  } else if ([self _allowURLRequest:request webView:webView]) {
    if ([[actionInformation[WebActionModifierFlagsKey] unsignedIntegerValue] != 0x100000) {
      [listener use];
    } else {
      // Unexplored
    }
  } else {
    // Unexplored
  }
}

As long as _allowsURLRequest:webView returns YES and the WebActionModifierFlags doesn’t equal 0x100000, then this video should load!

It appears you need to force _allowsURLRequest:webView to always return YES. The $rax register is responsible for this.

Note: Advanced users of LLDB would suggest using thread return 1 in LLDB; however, using this trick with breakpoint commands will cause Xcode to crash. As a result, you’ll instead break on the return of this function call and modify the $al register to return 1 instead of 0

You can search for the code yourself, but since you’re a champ for working through all that assembly inspection, I’ll just give it to you: it’s found in the 274th offset in the disassembly dump. Search for it using ⌘ + F. Dump the assembly again and take note of the address:

(lldb) b 0x1173a1494
Breakpoint 11: where = IDEDocViewer`-[IDEDocWebViewContentViewController webView:decidePolicyForNavigationAction:request:frame:decisionListener:] + 274, address = 0x00000001173a1494
(lldb) br command add
Enter your debugger command(s).  Type 'DONE' to end.
> reg write $al 1 
> c 
> DONE
(lldb) c
error: Process is running.  Use 'process interrupt' to pause execution.

Now open the Documentation window and force a new loadRequest: call by searching for a new item in the title search bar:

LLDB_Doc_Command

Wahoooo! You’ve found the secret!

So what did you learn, other than assembly looks nothing like Objective-C? :] By wading through the assembly, you’ve figured out a couple of ways to augment this particular code:

  1. You could augment _allURLRequest:webView: to execute and then return YES.
  2. You could augment webView:decidePolicyForNavigationAction:request:frame:decisionListener: to just call decision [listener use]; and return.

The best way to guide your decision is to ask: How do you think the Xcode engineers will augment these APIs in future? _allURLRequest:webView: is private and could change. On the other hand, webView:decidePolicyForNavigationAction:request:frame:decisionListener: comes from the WebPolicyDecisionListener, which is a public API so it’s unlikely to change.

The choice should be obvious: you’ll augment webView:decidePolicyForNavigationAction:request:frame:decisionListener: to always execute [listener use] then return when Rayrolling is enabled.

Implementing the Helpful Payroll

Create a new NSObject Category and name it Rayrolling IDEDocWebViewContentViewController. By now, this process shouldn’t be surprising.

Open NSObject+Rayrolling_IDEDocWebViewContentViewController.m and replace the file’s contents with the following:

#import "NSObject+Rayrolling_IDEDocWebViewContentViewController.h"
#import "NSObject+MethodSwizzler.h"
#import "Rayrolling.h"
#import <WebKit/WebKit.h>

@implementation NSObject (Rayrolling_IDEDocWebViewContentViewController)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setup) name:NSApplicationDidFinishLaunchingNotification object:nil];
    });
}

+ (void)setup {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [NSClassFromString(@"IDEDocWebViewContentViewController") swizzleWithOriginalSelector:@selector(webView:decidePolicyForNavigationAction:request:frame:decisionListener:) swizzledSelector:@selector(Rayrolling_webView:decidePolicyForNavigationAction:request:frame:decisionListener:) isClassMethod:NO];
}

- (void)Rayrolling_webView:(WebView *)webView
decidePolicyForNavigationAction:(NSDictionary *)actionInformation
                   request:(NSURLRequest *)request
                     frame:(WebFrame *)frame
          decisionListener:(id<WebPolicyDecisionListener>)listener
{
    if ([Rayrolling isEnabled]) {
        [listener use];
    } else {
        [self Rayrolling_webView:webView decidePolicyForNavigationAction:actionInformation request:request frame:frame decisionListener:listener];
    }
}

@end

Note that this version of load doesn’t swizzle as usual, but instead listens for NSApplicationDidFinishLaunchingNotification. Xcode 6.3 loads plugins during applicationWillFinishLaunching:, which means that IDEDocWebViewContentViewController is not loaded by the time this code is executed.

IDEDocWebViewContentViewController is unique in that you are modifying a component from a plugin as illustrated by the location of where the binary resides on your computer (/Applications/Xcode.app/Contents/PlugIns/IDEDocViewer.ideplugin/Contents/MacOS/IDEDocViewer:). This is different compared to other private classes you augmented as they are loaded in as frameworks.

Typically plugins are used to modify or complement existing code so it makes sense for them to be loaded at a later time. As a result, you are listening for when the application did become active and then modifying the class after it has loaded.

Now run this program to create a fresh plugin, then quit and relaunch Xcode. Navigate to the documentation and look up some class documentation; you’ll be greeted by a chorus of beautiful voices singing about WWDC.

Achievement unlocked: Rayrolling like a boss! :]

Where to Go From Here?

You can download the final project from this tutorial series here.

You’ve completed your Rayrolling plugin! On the journey, you’ve learned some pretty cool things about LLDB tricks, scripts, Dtrace, Cycript, and assembly.

But the journey isn’t over. The Xcode Plugin community is a very welcoming one; feel free to create a new plugin and add it to Alcatraz. Someone else out there will definitely appreciate your work, even if it’s the smallest of modifications.

The workflow and tricks you’ve learned in this series will be completely different compared to the workflow of someone else who is attempting to solve the same problem. So go forth, explore and have fun!

Do you have any cool tips or tricks for exploring Xcode, or do you have any questions or comments on this tutorial series? Feel free to join the discussion below!