Hi, I'm Tim

I like to dabble in all things related to programming, and occasionally I write about what I’m up to.

Byte patching Xcode 9.3 to get back the dark appearance of the Open Quickly menu

If you’re a frequent user of the Open Quickly feature in Xcode, and you use a dark menu bar and Dock, then you might have noticed that the appearance of the Open Quickly window changed in Xcode 9.3. In versions prior to Xcode 9.3 the Open Quickly window would use a dark theme if you used a dark menu bar and Dock, but the Open Quickly window in Xcode 9.3 always uses a light theme.

Screenshot of Open Quickly in Xcode

Open Quickly in Xcode 9.3 (left) and Xcode 9.2 (right)

Turns out that the dark theme is still there, and it’s possible to re-enable it! Re-enabling it involves unsigning Xcode and byte patching a binary, but it’s possible! :-)

In the rest of this post I’ll explain how we can reverse engineer Xcode to find the code that’s responsible for the styling of the Open Quickly feature, and then how we can byte patch the relevant binary.

Note: Everything in this post has only been tested on Xcode 9.3 (9E145) downloaded from the Apple Developer Portal. It will probably not work with any other version.

Finding the code responsible for the dark theme

To understand if the dark theme for the Open Quickly feature is still available in Xcode 9.3 or not we need to disassemble the Xcode binary. However, seeing that the Xcode binary is only 35 KB there’s probably not a lot going on in there.

I remember reading somewhere (probably in a tweet by @stroughtonsmith) that basically all of the Xcode functionality is implemented in a bunch of frameworks that can be found in the Xcode.app bundle. If we take a look around in Xcode.app/Contents/Frameworks we’ll see a framework called IDEKit. This jumped out at me (probably also from seeing it in a tweet sometime) and seems like a good a place as any to start, so let’s disassemble the IDEKit binary using Hopper.

Using Hopper to inspect IDEKit

If we use Hopper to search for labels matching openquickly in the IDEKit binary we can see that there’s a bunch of classes that seem to implement the Open Quickly functionality. This means that we probably picked the right framework! Great!

Let’s see if there’s any references to a dark theme in any of the openquickly classes by searching for openquickly dark. Well well well, the top result of -[IDEAbstractOpenQuicklyWindowController inDarkMode] certainly seems like a great indication that there’s still code in here for a dark theme (or mode). We can find even more evidence by looking at what code references the -inDarkMode method and using Hopper’s pseudo-code feature to inspect the -[IDEAbstractOpenQuicklyWindowController prepareToShowWindow] method, which contains this snippet:

// ...
COND = [r15 inDarkMode] == 0x0;
rax = *_NSAppearanceNameVibrantDark;
if (COND) {
    rax = *_NSAppearanceNameVibrantLight;
}
// ...

The return value of -inDarkMode seems to decide which visual effect the Open Quickly window uses. Similar conditionals can be found in the other methods that reference -inDarkMode.

Let’s find out how -inDarkMode is implemented using the handy pseudo-code feature again:

char -[IDEAbstractOpenQuicklyWindowController inDarkMode](void * self, void * _cmd) {
    r14 = [[self window] retain];
    rbx = [[r14 dvt_theme] retain];
    r15 = [rbx isDark];
    [rbx release];
    [r14 release];
    rax = sign_extend_64(r15);
    return rax;
}

This is basically just doing return [[[self window] dvt_theme] isDark]; which doesn’t really tell us a whole lot. Our next step is to figure out what type of object -dvt_theme returns and how its -isDark method is implemented.

Unfortunately, the -dvt_theme method and its related return type aren’t declared in IDEKit. They must be implemented in one of the other frameworks in the Xcode bundle, so let’s have another look in there.

There’s nothing in the Frameworks folder that seems related to the dvt prefix, but in the SharedFrameworks folder there’s a bunch of frameworks that are named using a DVT prefix. One of them is called DVTKit and since we had success with the similarly named IDEKit let’s open DVTKit in Hopper!

Using Hopper to inspect DVTKit

This time we know a bit more what we’re looking for, so let’s search for labels matching dvt_theme. It turns out we’re pretty good at guessing which frameworks are relevant, because right there at the top of the search results is -[NSWindow dvt_theme]!

void * -[NSWindow dvt_theme](void * self, void * _cmd) {
    r14 = [objc_getAssociatedObject(self, @selector(dvt_theme)) retain];
    if (r14 != 0x0) {
        rbx = [r14 retain];
    }
    else {
        r15 = [[DVTThemeManager shared] retain];
        rbx = [[r15 defaultTheme] retain];
        [r15 release];
    }
    [r14 release];
    [rbx autorelease];
    rax = rbx;
    return rax;
}

Unfortunately the pseudo-code doesn’t tell us what the return type of the method is (it just says void *), but based on the name of the method and the fact that DVTThemeManager is mentioned in the implementation I think a good bet is DVTTheme.

Searching for DVTTheme lets us know that we’ve guessed correctly, and looking through the search results there’s a -[DVTTheme isDark] method. That’s very likely the method that the Open Quickly code in IDEKit is invoking to see if it should use a light or dark theme for its window. What does the pseudo-code for the -isDark method look like?

char -[DVTTheme isDark](void * self, void * _cmd) {
    return 0x0;
}

Well, that definitely seems like it could be a reason for why we’re not seeing the dark Open Quickly window in Xcode 9.3 anymore :-)

We can compare the above implementation to the implementation in the version of DVTKit that ships with an older version of Xcode (9.2) to see if something changed:

char -[DVTTheme isDark](void * self, void * _cmd) {
    var_30 = self;
    var_28 = _cmd;
    r15 = [[self->_contents objectForKey:@"isDark"] retain];
    if (r15 != 0x0) {
            rdx = [NSString class];
            if ([r15 isKindOfClass:rdx] == 0x0) {
                    // ... stripped for brevity
            }
            rbx = [r15 boolValue];
    }
    else {
            rbx = 0x0;
    }
    [r15 release];
    rax = sign_extend_64(rbx);
    return rax;
}

Hey, it definitely changed! We could dig deeper and try to understand how the _contents instance variable is populated and why this method would return true in previous versions of Xcode, but that might not lead anywhere. Besides, it’s more fun to just change the current implementation of -[DVTTheme isDark] to see if something happens to the Open Quickly window!

Byte patching Xcode/DVTKit for fun and profit

By now we know that the -[DVTTheme isDark] method was modified in the latest version of Xcode (9.3), and we know that the Open Quickly-related class in IDEKit indirectly references that method when deciding which window background to use. All that’s left to do is to figure out how we can modify the DVTKit binary so that -[DVTTheme isDark] returns true instead of false.

I’m absolutely no expert at reverse engineering or assembly, but I’m pretty sure that by replacing one or more bytes in the DVTKit binary using a hex editor we should be able to change the behaviour of the aforementioned method.

The first step in our journey towards byte patching DVTKit is to leave the comfortable world of Hopper’s pseudo-code mode and start looking at the implementation of -[DVTTheme isDark] in Hopper’s assembly mode:

                     -[DVTTheme isDark]:
00000000000163c9         push       rbp
00000000000163ca         mov        rbp, rsp
00000000000163cd         xor        eax, eax
00000000000163cf         pop        rbp
00000000000163d0         ret

After looking at this method for some time and Googling the names of x86 registers we come to the conclusion that the xor eax, eax operation is probably what’s making the method return 0 (or false). The eax register seems to be what holds the return value of the method, and xor eax, eax is equivalent to eax = eax ^ eax; in C. XORing a value with itself always gives a result of 0, which explains the pseudo-code we saw earlier in Hopper.

If we want to change the return value of -[DVTTheme isDark] it would seem that modifying the xor eax, eax operation is the way forward. But what should we change it to? Since we can only replace bytes in the binary (changing the length of the binary would break other offsets and addresses), and the length in bytes of the xor eax, eax operation is 2 bytes (which Scripts > Disassemble instruction in Hopper helpfully tells us) we’re pretty limited in our options.

Being pretty inexperienced with assembly, I couldn’t think of a 1- or 2-byte change that would guarantee that eax would be populated with a non-zero value. However, by changing the XOR instruction to an OR instruction we can at least avoid modifying the eax register (since eax = eax | eax is a no-op). If we’re lucky the eax register might have a non-zero value in it when the -[DVTTheme isDark] method is called, which means it will return a non-zero value just like we wanted!

Using Hopper’s hex editor to replace a byte

Hopper doesn’t just have a pseudo-code mode and an assembly mode, it also has a hexadecimal mode that can be used as a hex editor. Just what we need! If we switch to the hexadecimal mode with the xor eax, eax instruction selected then Hopper will helpfully highlight the corresponding bytes (31 C0).

The relevant part of the -[DVTTheme isDark] method selected in Hopper's hexadecimal mode

We can verify that these are the bytes that encode the xor eax, eax instruction by referring to an x86 instruction set reference and looking up the XOR instruction. There’s a bunch of different opcodes for the different combinations of operands, and one of them is indeed encoded as 31. Looks like that’s the byte we’re replacing!

The variant of the XOR instruction that's used in -[DVTTheme isDark]

To find the correct hexadecimal value to replace 31 with we’ll refer to the OR instruction reference on the same website and look for an opcode that takes the same combination of operands (two registers):

The variant of the OR instruction that takes the correct combination of operands

That looks right! The leftmost column tells us that the first byte of this OR instruction is encoded as 09, so that’s what we’ll replace 31 with. We can do that by double-clicking 31 in Hopper’s hexadecimal mode and typing 09. Hopper will highlight the new byte value in red to indicate that the byte has been changed.

The red color of the byte value tells us that it differs from its original value

With the editing done all that’s left is getting the modified DVTKit binary into the Xcode 9.3 bundle.

Exporting the modified binary

When we edit the disassembled binary in Hopper we aren’t actually modifying the original binary. Hopper keeps its own internal representation of the binary, and that’s what we’re editing. To get a binary with our changes applied we need to use Hopper’s Produce New Executable… option from the File menu. Since DVTKit is a signed binary Hopper will ask us if we want the new binary to have the old, invalid signature or if we want to remove it.

I believe both options will give us the same result in our case, but let’s go ahead and remove the signature. We probably want to keep the original DVTKit binary around, so we should also make sure to save the modified binary somewhere where we don’t overwrite the original binary.

Getting the modified binary into Xcode

Before we move our new and improved DVTKit binary into Xcode I would recommend that we make a copy of Xcode 9.3 for reasons that will soon become clear.

Then, when we have a copy of Xcode 9.3, we can go ahead and overwrite the existing DVTKit binary in Xcode 9.3 with our modified binary by moving or copying the modified binary into the DVTKit framework bundle. The full path to the DVTKit binary is Xcode.app/Contents/SharedFrameworks/DVTKit.framework /Versions/A/DVTKit.

Now all we have to do is launch the copy of Xcode 9.3 that contains our modified binary and savour the fruits of our labour!

What we're greeted with when launching Xcode

Oh. That’s not what we were expecting. Did we make a mistake when editing the binary? Nope! If we look closely at the crash report it says code signing blocked mmap() of 'Xcode.app/…/DVTKit'. Remember when Hopper told us that we had modified a signed application and the new binary would have an invalid signature? Well this is the result of the modified DVTKit binary having an invalid or missing signature :-)

To get around this we’ll need to unsign Xcode. Fortunately there are tools that will do this for us! This is also why I recommended we make a copy of Xcode before replacing the DVTKit binary; I wouldn’t feel safe using an unsigned version of Xcode in my day-to-day work.

Unsigning Xcode

To unsign Xcode we’ll use the free unsign utility. When we have downloaded the repository and have run make we’ll have an unsign binary that’s ready to use. Using it is really simple:

# From the "unsign" directory
$ ./unsign /Applications/Xcode\ copy.app/Contents/MacOS/Xcode
$ cd /Applications/Xcode\ copy.app/Contents/MacOS
$ mv Xcode.unsigned Xcode # unsign outputs a copy of the input binary

We should now, finally, be able to launch our unsigned and modified version of Xcode 9.3 and rejoice in the return of the dark Open Quickly menu!

Success at last!

Conclusions

Phew. What a ride! At the start of this adventure I wasn’t sure we would be able to find references to a dark theme in Xcode 9.3, let alone be able to byte patch a binary to get it back, but we did! That’s pretty cool.

I hope that I’ve been able to show that reverse engineering and byte patching a binary is something that’s been made relatively accessible through tools like Hopper. Even though there’s a ton more features in Hopper that I don’t understand, we can still do cool things like modifying Xcode by just being curious, being patient, and being good at Google :-)

Have fun exploring!