KISS 🇺🇦

Stop the war!

Stop the war in Ukraine! Fuck putin!

More information is at: https://war.ukraine.ua/.

There is a fund to support the Ukrainian Army: https://savelife.in.ua/en/donate/, and there is a special bank account that accepts funds in multiple currencies: https://bank.gov.ua/en/about/support-the-armed-forces. I donated to them. Please donate if you can!

Killer putin

Killer putin. Source: politico.eu.

Arrested putin

"It hasn't happened yet, but it will happen sooner or later. Beautiful photo, isn't it?" Source: twitter.

OSX says, "iCloud Drive may not work properly". I say, "Who cares?"

| comments

I had to upgrade recently from OSX 10.11.6 to OSX 10.13.3 to be able to run the latest Xcode. The upgrade has broken a few things and also brought a bunch of new system stuff — I found out about a dozen of new daemons because my Little Snitch was popping an alert quite often after the upgrade.

There were some questionable daemons trying to access the internets, for example keyboardservicesd — based on the name only, why would a keyboard-related daemon connect online? I didn’t want to get into the details so I just blocked almost all of them. But why stop at blocking the access? A better way is to actually stop these muddy services, that’s what I did and ended up seeing this “iCloud Drive may not work properly. Please check the iCloud preference pane.” alert every time I opened the Open/Save File dialog in any application — it showed up once per app launch.

"iCloud Drive may not work properly. Please check the iCloud preference pane."

That is very annoying, especially since I don’t care about this iCloud thing at all and I would gladly remove/disable it altogether in an official way, but guess what, Apple doesn’t provide that way (at least, I couldn’t find anything online). I was then searching for this message to figure out what is possible to do to get rid of it — nope, nothing. I had to brush up on my little reverse engineering skills to deal with it myself. The step-by-step story (and guide to repeat it) is below.

Disabling services

Here is the list of daemons that I’ve disabled before seeing this message:

1
2
3
4
$ launchctl unload -w /System/Library/LaunchAgents/com.apple.iCloudUserNotifications.plist
$ launchctl unload -w /System/Library/LaunchAgents/com.apple.icloud.fmfd.plist
$ launchctl unload -w /System/Library/LaunchAgents/com.apple.icloud.findmydeviced.findmydevice-user-agent.plist
$ launchctl unload -w /System/Library/LaunchAgents/com.apple.bird.plist

I strongly suspect that disabling the bird daemon caused all this mess. The man page says:

bird is one of the system daemons backing the Documents in the Cloud feature.

Looking for the string

The basic way to find the piece of code that you need is to search for a string related to that code, in our case it’s the string about the iCloud. It can be either in the binary or in external resources. I’m using the ag command here — “The Silver Searcher” — a better grep.

1
2
3
4
$ ag -Q --search-binary 'iCloud Drive may not work properly. Please check the iCloud preference pane.' /System/ 2>/dev/null
Binary file /System/Library/CoreServices/Finder.app/Contents/Resources/English.lproj/Localizable.strings matches.

Binary file /System/Library/PrivateFrameworks/FinderKit.framework/Versions/A/Resources/English.lproj/Localizable.strings matches.

Not bad, we have something to start with. If you less either file, you’ll see they are stored in a binary plist format. We can convert it to something more readable:

1
2
3
$ plutil -convert xml1 -o - /System/Library/PrivateFrameworks/FinderKit.framework/Versions/A/Resources/English.lproj/Localizable.strings | grep -B1 'iCloud Drive may not work properly.'
        <key>A42</key>
        <string>iCloud Drive may not work properly. Please check the iCloud preference pane.</string>

Whatever binary displaying the alert doesn’t contain the string itself, but it must have the localization key “A42” instead. My idea to find the library using it was to observe that TextEdit and Console applications display it, so there must be a common framework between them. The following command prints the frameworks that are used by the both apps:

1
2
3
4
5
6
$ comm -12 <(otool -L /Applications/TextEdit.app/Contents/MacOS/TextEdit | cut -d' ' -f1 | sort) <(otool -L /Applications/Utilities/Console.app/Contents/MacOS/Console | cut -d' ' -f1 | sort)
        /System/Library/Frameworks/AppKit.framework/Versions/C/AppKit
        /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
        /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation
        /usr/lib/libSystem.B.dylib
        /usr/lib/libobjc.A.dylib

The bottom two are two base system libraries not dealing with the UI; the two Foundations don’t deal with the UI either; so it must be the AppKit.

1
$ strings -3 /System/Library/Frameworks/AppKit.framework/Versions/C/AppKit | grep A42

Nothing. I wonder where this alert is coming from.

A primitive tester

To reproduce this behavior and be able to debug it, I created a new “Cocoa App” in Xcode, used Objective-C to have fewer levels of abstractions. I only added this function to ViewController.m to be able to open a file dialog using “Cmd+O”:

1
2
3
4
5
6
7
- (IBAction)openDocument:(id)sender {
    NSOpenPanel *panel = [NSOpenPanel openPanel];
    panel.canChooseFiles = YES;
    [panel beginWithCompletionHandler:^(NSModalResponse result) {
        NSLog(@"result = %@", @(result));
    }];
}

There are sandboxed applications on OSX and the system restricts their access to the file system, which means the file dialogs may behave differently. This documentation link confirms it:

The macOS security technology that interacts with the user to expand your sandbox is called Powerbox. Powerbox has no API. Your app uses Powerbox transparently when you use the NSOpenPanel and NSSavePanel classes.

So I disabled the “App Sandbox” checkbox on the app target’s Capabilities tab. Sure enough, I get the same error in my app.

Going back to the search results for the original string I tried:

1
2
$ strings -3 /System/Library/PrivateFrameworks/FinderKit.framework/FinderKit | grep A42
A42

Even though

1
$ otool -L /System/Library/Frameworks/AppKit.framework/AppKit | grep Finder

FinderKit is not linked to AppKit directly. Just to confirm, I ran my app, opened the file dialog, then paused the app in Xcode:

1
2
3
4
(lldb) target modules list
[  0] E9D7BEAC-E215-37A2-8BA1-3E8F8B76CC09 0x0000000100000000 …/Build/Products/Debug/testalert.app/Contents/MacOS/testalert
[329] 4DCCE9B6-2CE6-358F-BE78-C47A87BBD7D5 0x00007fff69189000 /System/Library/PrivateFrameworks/FinderKit.framework/Versions/A/FinderKit

I think we’ve found the binary!

Disassembling

I’ve never used the Hopper Disassembler, so it was a good time to brew cask install hopper-disassembler and run it. Open the FinderKit binary, select the 64-bit architecture and let it disassemble the code. I searched for “A42” in the left strings panel and found it here:

1
2
                 aA42:
00000000003833c2         db         "A42", 0                                    ; DATA XREF=cfstring_A42

Press “x” to jump to the single reference, press “x” again to jump to its single usage here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
                 __ZN4fstd23finder_callable_details15callable_holderIZL18ShowBRGenericErrorP8NSStringE3$_6vJEEclEv:
                 // fstd::finder_callable_details::callable_holder<ShowBRGenericError(NSString*)::$_6, void>::operator()()
000000000025252e         push       rbp                                         ; Begin of try block

000000000025256d         mov        rdi, rbx                                    ; argument "cf" for method imp___stubs__CFRetain
0000000000252570         call       imp___stubs__CFRetain                       ; CFRetain
0000000000252575         lea        rdx, qword [cfstring_A42]                   ; End of try block started at 0x25252e, Begin of try block (catch block at 0x25267b), @"A42"
000000000025257c         mov        rdi, r12                                    ; argument #1 for method __ZN7TString3SetEPK10__CFStringS2_
000000000025257f         mov        rsi, r15                                    ; argument #2 for method __ZN7TString3SetEPK10__CFStringS2_
0000000000252582         call       __ZN7TString3SetEPK10__CFStringS2_          ; TString::Set(__CFString const*, __CFString const*)
0000000000252587         mov        qword [rbp+var_D0], rbx                     ; End of try block started at 0x252575
000000000025258e         mov        rdi, rbx                                    ; Begin of try block (catch block at 0x252679), argument "cf" for method imp___stubs__CFRetain
0000000000252591         call       imp___stubs__CFRetain                       ; CFRetain
0000000000252596         lea        rdx, qword [cfstring_A41]                   ; End of try block started at 0x25258e, Begin of try block (catch block at 0x252674), @"A41"
000000000025259d         lea        rdi, qword [rbp+var_D0]                     ; argument #1 for method __ZN7TString3SetEPK10__CFStringS2_
00000000002525a4         mov        rsi, r15                                    ; argument #2 for method __ZN7TString3SetEPK10__CFStringS2_
00000000002525a7         call       __ZN7TString3SetEPK10__CFStringS2_          ; TString::Set(__CFString const*, __CFString const*)
00000000002525ac         mov        qword [rbp+var_C8], rbx                     ; End of try block started at 0x252596
00000000002525b3         mov        rdi, rbx                                    ; Begin of try block (catch block at 0x25266f), argument "cf" for method imp___stubs__CFRetain
00000000002525b6         call       imp___stubs__CFRetain                       ; CFRetain
00000000002525bb         lea        rdx, qword [cfstring_A38]                   ; End of try block started at 0x2525b3, Begin of try block (catch block at 0x25266d), @"A38"
00000000002525c2         lea        rdi, qword [rbp+var_C8]                     ; argument #1 for method __ZN7TString3SetEPK10__CFStringS2_
00000000002525c9         mov        rsi, r15                                    ; argument #2 for method __ZN7TString3SetEPK10__CFStringS2_
00000000002525cc         call       __ZN7TString3SetEPK10__CFStringS2_          ; TString::Set(__CFString const*, __CFString const*)
00000000002525d1         call       __ZN7TString12KEmptyStringEv                ; TString::KEmptyString(), End of try block started at 0x2525bb, Begin of try block (catch block at 0x252691)
00000000002525d6         add        r14, 0x8
00000000002525da         lea        rdi, qword [rbp+var_C0]                     ; argument #1 for method __ZN6TAlertC2ERK7TStringS2_S2_S2_S2_
00000000002525e1         lea        rsi, qword [rbp+var_D8]                     ; argument #2 for method __ZN6TAlertC2ERK7TStringS2_S2_S2_S2_
00000000002525e8         lea        rcx, qword [rbp+var_D0]                     ; argument #4 for method __ZN6TAlertC2ERK7TStringS2_S2_S2_S2_
00000000002525ef         lea        r8, qword [rbp+var_C8]                      ; argument #5 for method __ZN6TAlertC2ERK7TStringS2_S2_S2_S2_
00000000002525f6         mov        rdx, r14                                    ; argument #3 for method __ZN6TAlertC2ERK7TStringS2_S2_S2_S2_
00000000002525f9         mov        r9, rax
00000000002525fc         call       __ZN6TAlertC2ERK7TStringS2_S2_S2_S2_        ; TAlert::TAlert(TString const&, TString const&, TString const&, TString const&, TString const&)
0000000000252601         lea        rdi, qword [rbp+var_C8]                     ; End of try block started at 0x2525d1
0000000000252608         call       __ZN4TRefIPK10__CFString20TRetainReleasePolicyIS2_EED2Ev ; TRef<__CFString const*, TRetainReleasePolicy<__CFString const*> >::~TRef()
000000000025260d         lea        rdi, qword [rbp+var_D0]
0000000000252614         call       __ZN4TRefIPK10__CFString20TRetainReleasePolicyIS2_EED2Ev ; TRef<__CFString const*, TRetainReleasePolicy<__CFString const*> >::~TRef()
0000000000252619         lea        rdi, qword [rbp+var_D8]
0000000000252620         call       __ZN4TRefIPK10__CFString20TRetainReleasePolicyIS2_EED2Ev ; TRef<__CFString const*, TRetainReleasePolicy<__CFString const*> >::~TRef()
0000000000252625         lea        rdi, qword [rbp+var_C0]                     ; Begin of try block (catch block at 0x252680)
000000000025262c         call       __ZNK6TAlert15DisplayStdAlertEv             ; TAlert::DisplayStdAlert() const
0000000000252631         cmp        rax, 0x1
0000000000252635         jne        loc_25263c
0000000000252637         call       __ZN7TLaunch18OpenICloudPrefPaneEv          ; TLaunch::OpenICloudPrefPane()

This is clearly the right place! You can see the strings A41 and A38 nearby which are “Open iCloud Preferences…” and “OK” respectively, and the call to TAlert::DisplayStdAlert().

Figuring out the code path

I was interested in the conditions of when this function is called. Launch our test app, open the dialog, then pause the program.

1
2
3
4
5
6
(lldb) target modules dump sections FinderKit
Sections for '/System/Library/PrivateFrameworks/FinderKit.framework/Versions/A/FinderKit' (x86_64):
  SectID     Type             Load Address                             Perm File Off.  File Size  Flags      Section Name
  ---------- ---------------- ---------------------------------------  ---- ---------- ---------- ---------- ----------------------------
  0x00000100 container        [0x00007fff69189000-0x00007fff69579000)  r-x  0x00000000 0x003f0000 0x00000000 FinderKit.__TEXT
  0x00000001 code             [0x00007fff6918aeb0-0x00007fff69446503)  r-x  0x00001eb0 0x002bb653 0x80000400 FinderKit.__TEXT.__text

Our function is located at address 0x0025252e in the binary (more specifically, in the x86_64 section of the binary since the FinderKit and all other system frameworks are fat binaries containing i386 and x86_64 architectures; we’ll come back to this below). The dump shows that the section for this function is FinderKit.__TEXT.__text which has the base address of 0x00007fff6918aeb0. Now we need to calculate the memory address of the function: 0x6918aeb0 - 0x00001eb0 + 0x0025252e = 0x693db52e. Verifying this:

1
2
3
4
5
(lldb) di -s 0x00007fff693db52e -c 3
FinderKit`fstd::finder_callable_details::callable_holder<ShowBRGenericError(NSString*)::$_6, void>::operator():
    0x7fff693db52e <+0>: pushq  %rbp
    0x7fff693db52f <+1>: movq   %rsp, %rbp
    0x7fff693db532 <+4>: pushq  %r15

Looks familiar, doesn’t it? We can set a breakpoint there. Restarting the app:

1
2
3
4
5
6
(lldb) target modules dump sections FinderKit
Sections for '/System/Library/PrivateFrameworks/FinderKit.framework/Versions/A/FinderKit' (x86_64):
  SectID     Type             Load Address                             Perm File Off.  File Size  Flags      Section Name
  ---------- ---------------- ---------------------------------------  ---- ---------- ---------- ---------- ----------------------------
  0x00000100 container        [0x0000000000000000-0x00000000003f0000)* r-x  0x00000000 0x003f0000 0x00000000 FinderKit.__TEXT
  0x00000001 code             [0x0000000000001eb0-0x00000000002bd503)* r-x  0x00001eb0 0x002bb653 0x80000400 FinderKit.__TEXT.__text

Hmm, it’s not loaded yet. Apparently it’s loaded lazily when needed. We can put a breakpoint visually at the beginWithCompletionHandler: line, which is when the framework is already loaded (by the way. the memory address is the same between launches).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(lldb) breakpoint set -a 0x00007fff693db52e
Breakpoint 2: where = FinderKit`fstd::finder_callable_details::callable_holder<ShowBRGenericError(NSString*)::$_6, void>::operator()(), address = 0x00007fff693db52e

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
  * frame #0: 0x00007fff693db52e FinderKit`fstd::finder_callable_details::callable_holder<ShowBRGenericError(NSString*)::$_6, void>::operator()()
    frame #1: 0x00007fff55bc01c5 Foundation`__NSThreadPerformPerform + 334
    frame #2: 0x00007fff53ac5721 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    frame #3: 0x00007fff53b7f0ac CoreFoundation`__CFRunLoopDoSource0 + 108
    frame #4: 0x00007fff53aa8260 CoreFoundation`__CFRunLoopDoSources0 + 208
    frame #5: 0x00007fff53aa76dd CoreFoundation`__CFRunLoopRun + 1293
    frame #6: 0x00007fff53aa6f43 CoreFoundation`CFRunLoopRunSpecific + 483
    frame #7: 0x00007fff52dbee26 HIToolbox`RunCurrentEventLoopInMode + 286
    frame #8: 0x00007fff52dbea9f HIToolbox`ReceiveNextEventCommon + 366
    frame #9: 0x00007fff52dbe914 HIToolbox`_BlockUntilNextEventMatchingListInModeWithFilter + 64
    frame #10: 0x00007fff51089f5f AppKit`_DPSNextEvent + 2085
    frame #11: 0x00007fff5181fb4c AppKit`-[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 3044
    frame #12: 0x00007fff5107ed6d AppKit`-[NSApplication run] + 764
    frame #13: 0x00007fff5104df1a AppKit`NSApplicationMain + 804
    frame #14: 0x0000000100001342 testalert`main(argc=3, argv=0x00007ffeefbff5b0) at main.m:12
    frame #15: 0x00007fff7b3b8115 libdyld.dylib`start + 1

This is the code in the caller’s frame:

1
2
3
4
5
6
7
8
9
10
0x7fff55bc0193 <+284>: movq   %rax, %rbx
0x7fff55bc0196 <+287>: movq   0x586c70bb(%rip), %rax    ; _NSThreadPerformInfo.target
0x7fff55bc019d <+294>: movq   (%r14,%rax), %rdi
0x7fff55bc01a1 <+298>: movq   0x586c70c8(%rip), %rax    ; _NSThreadPerformInfo.selector
0x7fff55bc01a8 <+305>: movq   (%r14,%rax), %rdx
0x7fff55bc01ac <+309>: movq   0x586c70ad(%rip), %rax    ; _NSThreadPerformInfo.argument
0x7fff55bc01b3 <+316>: movq   (%r14,%rax), %rcx
0x7fff55bc01b7 <+320>: movq   -0x68(%rbp), %rsi
0x7fff55bc01bb <+324>: movq   %rbx, -0x70(%rbp)
0x7fff55bc01bf <+328>: callq  *0x585dc833(%rip)         ; (void *)0x00007fff7a7bae80: objc_msgSend

So it’s an objc_msgSend call, which also somehow overwrites the frame because it’s not in the stacktrace. I think it’s an optimization of the dynamic Objective-C runtime. Let’s restart the program, set the same breakpoint and also another one to figure out what message is sent:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(lldb) breakpoint set -a 0x7fff55bc01bf
Breakpoint 3: where = Foundation`__NSThreadPerformPerform + 328, address = 0x00007fff55bc01bf
(lldb) breakpoint command add 3
Enter your debugger command(s).  Type 'DONE' to end.
> p (void)printf("[%s, %s]\n", (char*)object_getClassName($arg1), $arg2)
> continue
> DONE

[FI_TRunSoonHelper, performSelector:withObject:]
 p (void)printf("[%s, %s]\n", (char*)object_getClassName($arg1), $arg2)
 continue
Process 69182 resuming

Command #2 'continue' continued the target.

Not very helpful, only one call. I’m not that familiar with the ObjC’s inner workings and didn’t have much time to extract more information from it. So let’s move on.

Binary patching in memory

This is the prologue and epilogue of our function, you can clearly see the epilogue undoing the changes that the prologue does:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
000000000025252e         push       rbp                                         ; Begin of try block
000000000025252f         mov        rbp, rsp
0000000000252532         push       r15
0000000000252534         push       r14
0000000000252536         push       r12
0000000000252538         push       rbx
0000000000252539         sub        rsp, 0xc0

0000000000252658         add        rsp, 0xc0
000000000025265f         pop        rbx
0000000000252660         pop        r12
0000000000252662         pop        r14
0000000000252664         pop        r15
0000000000252666         pop        rbp
0000000000252667         ret
                        ; endp

To disable the function, we can just replace the first instruction (push rbp) with ret. What’s annoying is that I couldn’t find an option to display the opcodes directly in the disassembly in Hopper, so we can see them in lldb again:

1
2
3
4
5
6
7
8
9
10
11
(lldb) di -s 0x7fff693db667 -c 1 -b
FinderKit`fstd::finder_callable_details::callable_holder<ShowBRGenericError(NSString*)::$_6, void>::operator():
    0x7fff693db667 <+313>: c3  retq

(lldb) di -s 0x7fff693db52e -c 2 -b
FinderKit`fstd::finder_callable_details::callable_holder<ShowBRGenericError(NSString*)::$_6, void>::operator():
->  0x7fff693db52e <+0>: 55        pushq  %rbp
    0x7fff693db52f <+1>: 48 89 e5  movq   %rsp, %rbp

(lldb) memory write 0x7fff693db52e c3
error: Memory write to 0x7fff693db52e failed: (null).

Okaaaay… I know, it must be read-only!

1
2
3
4
5
6
7
8
(lldb) memory region 0x7fff693db52e
[0x00007fff693db000-0x00007fff693dc000) r-x __TEXT
(lldb) p (int)mprotect(0x00007fff693db000, 1, 0x01|0x02|0x04)
(int) $4 = 0
(lldb) memory write 0x7fff693db52e c3
error: Memory write to 0x7fff693db52e failed: (null).
(lldb) memory region 0x7fff693db52e
[0x00007fff693db000-0x00007fff693dc000) rwx __TEXT

WTF?! It’s writable now, why can’t I change the byte? I don’t know the definite answer, but I suspect it’s because of the code signatures of the system frameworks or the fact that the files are not writable (however that shouldn’t matter). I failed to patch the binary in memory, let’s try a patched file.

Thanks to https://stackoverflow.com/questions/30587597/lldb-on-mac-os-x-set-breakpoint-on-memory-execution for the mprotect() idea.

Local binary patching

In order not to mess with the system, I wanted to patch the framework and inject my version into my program to verify the patch works. As mentioned above, the framework contains two architectures:

1
2
$ lipo -info /System/Library/PrivateFrameworks/FinderKit.framework/FinderKit
Architectures in the fat file: /System/Library/PrivateFrameworks/FinderKit.framework/FinderKit are: x86_64 i386

I copied into my home directory to inject the local copy (still unpatched) into my program. Briefly, I tried setting the DYLD_LIBRARY_PATH, DYLD_FRAMEWORK_PATH, DYLD_INSERT_LIBRARIES (with and without DYLD_FORCE_FLAT_NAMESPACE=1) environment variables — didn’t work, according to the output of target modules dump sections FinderKit.

I even tried linking to FinderKit explicitly in my program, but it still failed to load my version. I moved on to disable all standard paths to make sure /System/Library/PrivateFrameworks is not in the search path by using these linker flags: -framework FinderKit -F $HOME/bin/ -Z -F /System/Library/Frameworks -L /usr/lib. Nope, didn’t work a single bit, the binary was still linked against the system framework:

1
2
3
$ otool -L …/Build/Products/Debug/testalert.app/Contents/MacOS/testalert
…/Build/Products/Debug/testalert.app/Contents/MacOS/testalert:
        /System/Library/PrivateFrameworks/FinderKit.framework/Versions/A/FinderKit (compatibility version 1.0.0, current version 1054.2.4)

Why, oh why?! I failed miserably at this task.

Final binary patching for real

We need to extract the individual slices from the fat binary first:

1
2
$ cd ~/bin/FinderKit.framework/Versions/A
$ lipo -thin x86_64 -output FinderKit.x86_64 FinderKit

I used Hex Fiend to replace the byte 55 at offset 0x25252e with the byte c3. Repackaging and replacing the fat binary (NB: you need to disable SIP to overwrite the files in /System):

1
2
3
$ lipo -replace x86_64 FinderKit.x86_64 -o FinderKit FinderKit
$ rm FinderKit.x86_64
$ sudo cp FinderKit /System/Library/PrivateFrameworks/FinderKit.framework/Versions/A/FinderKit

Let the fun begin! I opened the Console.app, pressed “Cmd+O”… and got a crash with this:

1
2
3
4
5
Exception Type:        EXC_BAD_ACCESS (Code Signature Invalid)
Exception Codes:       0x0000000000000032, 0x0000000110423000
Exception Note:        EXC_CORPSE_NOTIFY

Termination Reason:    Namespace CODESIGNING, Code 0x2

Oops, how could I forget about code signing?! Interestingly, I tried the same with TextEdit.app, which didn’t crash, the file dialog didn’t open either, and I saw these lines in the system log:

1
CODE SIGNING: process 85221[com.apple.appkit]: rejecting invalid page at address 0x10609d000 from offset 0x253000 in file "/System/Library/PrivateFrameworks/FinderKit.framework/Versions/A/FinderKit" (cs_mtime:1524345553.61230579 == mtime:1524345553.61230579) (signed:1 validated:1 tainted:1 nx:0 wpmapped:0 slid:0 dirty:0 depth:0)

While still inside the ~/bin/FinderKit.framework/Versions/A directory, resign the binary:

1
2
3
4
5
6
7
8
9
10
11
$ codesign -vvv .
.: invalid signature (code or signature have been modified)
In architecture: x86_64
$ codesign -f -s - --timestamp=none .
.: replacing existing signature

$ sudo cp FinderKit /System/Library/PrivateFrameworks/FinderKit.framework/Versions/A/FinderKit
Password:
$ codesign -vv /System/Library/PrivateFrameworks/FinderKit.framework/
/System/Library/PrivateFrameworks/FinderKit.framework/: valid on disk
/System/Library/PrivateFrameworks/FinderKit.framework/: satisfies its Designated Requirement

Repeat the simple trick with Console.app and… crash! Let’s try again:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ cd ../../..
$ codesign -f -s - --timestamp=none FinderKit.framework
FinderKit.framework: replacing existing signature
$ codesign -vvvv FinderKit.framework
--prepared:~/bin/FinderKit.framework/Versions/Current/PlugIns/iCloud Sharing.appex--prepared:~/bin/FinderKit.framework/Versions/Current/PlugIns/AirDrop.appex

--validated:~/bin/FinderKit.framework/Versions/Current/PlugIns/iCloud Sharing.appex
--validated:~/bin/FinderKit.framework/Versions/Current/PlugIns/AirDrop.appex
--prepared:~/bin/FinderKit.framework/Versions/Current/XPCServices/CloudSharingInvitationViewService.xpc
--validated:~/bin/FinderKit.framework/Versions/Current/XPCServices/CloudSharingInvitationViewService.xpc
FinderKit.framework: valid on disk
FinderKit.framework: satisfies its Designated Requirement
$ sudo rsync -av FinderKit.framework/ /System/Library/PrivateFrameworks/FinderKit.framework/

The file dialog finally works (it seems you need to copy the whole framework directory even though the only change is in the FinderKit.framework/Versions/A/FinderKit file). And without that annoying alert! Epic Success!

Post scriptum

First, this works for all the apps I’ve tried so far — the alert is gone, I haven’t noticed any strange behavior. FinderKit is a dynamic framework only, so it’s impossible to compile it statically into an application, thus we don’t have to worry that the alert will appear in an app as long as the shared framework is patched.

Second, when I restart OSX or relaunch Finder.app, I still see this alert. That correlates with the search result of our target string in /System/Library/CoreServices/Finder.app as seen at the top of the post, and should be pretty easy to patch in the same way.

Third, the same as with using a custom sudo binary, an OS update is very likely to break the fix.

This process was definitely fun, I’ve learned some things and I’ve learned that some things don’t work as expected in OSX. And it was easier than I’d anticipated.

Comments