From the Top to the Bottom
› tracking down the cause of CVE-2017-7149, from the UI level
11/25/2017
love these blog posts? support my tools & writing on patreon! Mahalo :)
In this blog, we'll take a detailed look a nasty bug (CVE-2017-7149) at affected High Sierra (macOS 10.13). Discovered by Matheus Mariano, this vulnerability could afford local attackers access to the contents of encrypted APFS volumes!
While Apple has patched this bug, and diff'ing the patch revealed the exact nature of the flaw (see Daniel MartÃn's
tweet and great
writeup), here, we'll take a different route to (re)illustrate the underlying issue.
Though our findings will mirror Daniel's, this blog post will instead start at the user interface (UI) level of the vulnerable app, then dig down, reversing various components and frameworks until we finally uncover the bug.
This method doesn't require a patch to diff and is a good practical reversing walk-thru!
Background
A few weeks ago Matheus Mariano tweeted: "If you create an Encrypted APFS container and install the new macOS, your password will be stored as plain text in your password hint."
Though he did not discuss the underlying reason of the vulnerability, he followed up the tweet with an illustrative blog post illustrating how to recreate the vulnerability: "New macOS High Sierra vulnerability exposes the password of an encrypted APFS container"
Here we'll follow the steps outlined in his blog to initially trigger the bug...then dive into reversing the UI to uncover the flawed code.
First, on a base High Sierra (macOS 10.13) system, create an encrypted Apple FileSystem (APFS) volume using the Disk Utility.app:
- In the app, click on the 'Add Volume' icon (or select 'Edit' -> 'Add APFS Volume'):
- In the 'Add APFS volume to container' pane, select 'APFS (Encrypted)' from the 'Format' drop-down:
- In the password dialog enter a password (such as hunter2), and a password hint. Then click 'Choose' to set the password and hint:
- Click 'Add' to create (and mount) the encrypted APFS volume.
Now, if we unmount, then attempt to (re)mount the encrypted APFS volume - it will (as expected), first prompt us for the password:
Click 'Show Hint' and wtf!?! ...the secret password, 'hunter2', not the hint, is shown in the UI:
If the password protecting the encrypted volume is displayed as the hint, this obviously undermines the entire security of the protection scheme, as it could allow a local attacker to gain access to the encrypted volume! #fail
Digging Deeper
As mentioned, we'll now illustrate how to track down this bug, starting from the user interface (UI). Our goal is to determine why the password value is displayed when the user clicks 'Show Hint.' It seems reasonable to assume that at some point the password was being saved into to the password hint...so let's dive in to determine the 'truthiness' of this assumption!
As user interactions with the UI are event driven (that is to say, the runtime will process and deliver events such as mouse and keyboard to the app), reversing UI components may be somewhat of a pain in the butt. Though one could use a tool such as cycript to enumerate and poke on the UI, whenever reversing apps I generally start with a simpler approach. In short, using using classdump or an 'objective-C aware' disassembler, I simply look for classes or method names of interest. Then in a debugging session I set breakpoints on these, interact with the app, and hope the breakpoints trigger :)
Since we're trying to figure out the cause of an apparent password/password hint mixup when adding a APFS volume the following methods stood out as likely candidates of interest:
$ class-dump 'Disk Utility'
//
// Generated by class-dump 3.5 (64 bit).
//
// class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard.
//
-[SUSharedActionController addDeleteVolumeButtonClicked:]
-[SUSharedActionController performAddAPFSVolume:]
-[SUAddAPFSVolumeSheetController addClicked:]:
-[SUAddAPFSVolumeSheetController setPassword:]:
-[SUAddAPFSVolumeSheetController setPasswordHint:]:
Let's fire up the debugger (lldb) and after disabling SIP, start debugging Disk Utility.app:
$ lldb "/Applications/Utilities/Disk Utility.app"
(lldb) target create "/Applications/Utilities/Disk Utility.app"
Current executable set to '/Applications/Utilities/Disk Utility.app' (x86_64).
As our goal is uncover the buggy code responsible for the apparent password/password hint mixup, it seemed reasonable to trace (debug) the process of creating an encrypted APFS volume, starting with the UI methods that kick off this process. Thus, we set a breakpoint on the '[SUSharedActionController addDeleteVolumeButtonClicked:]' method, which is at address 0x0000000109ded59e:
(lldb) b 0x0000000109ded59e
Breakpoint 1: where = Disk Utility`___lldb_unnamed_symbol998$$Disk Utility, address = 0x0000000109ded59e
Once this breakpoint has been set, clicking on the 'Add Volume' icon, causes this breakpoint to be triggered:
Process 395 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000109ded59e Disk Utility`___lldb_unnamed_symbol998$$Disk Utility
Disk Utility`___lldb_unnamed_symbol998$$Disk Utility:
-> 0x109ded59e <+0>: pushq %rbp
0x109ded59f <+1>: movq %rsp, %rbp
0x109ded5a2 <+4>: pushq %r15
0x109ded5a4 <+6>: pushq %r14
Examining the backtrace via the 'bt' command, we can see the 'addDeleteVolumeButtonClicked:' method was invoked directly via AppKit in response to (our) user interactions with the UI:
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x0000000109ded59e Disk Utility`___lldb_unnamed_symbol998$$Disk Utility
frame #1: 0x00007fff438e1116 AppKit`-[NSApplication(NSResponder) sendAction:to:from:]
frame #2: 0x00007fff433880ef AppKit`-[NSControl sendAction:to:]
frame #3: 0x00007fff43388017 AppKit`__26-[NSCell _sendActionFrom:]_block_invoke
Looking at the arguments passed in to the method, we can see the target object (an instance of a SUSharedActionController), the method name, and its first argument (an instance of an NSSegmentedControl):
(lldb) po $rdi
<SUSharedActionController: 0x600000140bb0>
(lldb) x/s $rsi
0x109e4b53a: "addDeleteVolumeButtonClicked:"
(lldb) po $rdx
<NSSegmentedControl: 0x604000183670>
Speaking of objective-C arguments and method parameters, the following briefly summarizes the relevant registers used in a method invocation on 64-bit systems:
When an objective-C method is invoked (in a 64-bit environment) the following registers are utilized (for more details, see
System V AMD64 ABI):
- The 1st argument is stored in RDI. It holds an object (a instance of a class) that implements the method that is being invoked.
- The 2nd argument is stored in RSI. It holds a pointer to the name of the method being invoked.
- If there is a 3rd argument it is stored in RDX. This is the 1st argument of the method.
- If there is a 4th argument it is stored in RCX. This is the 2st argument of the method.
- If there are 5th & 6th arguments, they are stored in R8 and R9. These are the 3rd and 4th arguments of the method.
- Any other arguments are passed on the stack and are referenced via RBP.
Let's take a closer look at the 'addDeleteVolumeButtonClicked: method. It's decompilation is shown below:
void -[SUSharedActionController addDeleteVolumeButtonClicked:](void * self, void * _cmd, void * arg2) {
r14 = self;
r15 = [arg2 retain];
rdx = [NSSegmentedControl class];
if ([r15 isKindOfClass:rdx] != 0x0) {
r12 = [r15 selectedSegment];
rbx = [[r14 representedDisk] retain];
if (r12 != 0x0) {
rsi = @selector(performDeleteAPFSVolume:);
}
else {
rsi = @selector(performAddAPFSVolume:);
}
_objc_msgSend(r14, rsi);
[rbx release];
}
else {
NSBeep();
}
[r15 release];
return;
}
After making sure the passed in argument is an instance of an NSSegmentedControl, the code gets the segment of the control that is currently selected to determine what item in the control was clicked by the user. If the 'Add Volume' segment of the control was clicked, the selected segment will be the first one (index: 0), and the code will invoke the 'performAddAPFSVolume:' method.
Since we have a reference to this control object (it's passed in as an argument, in RDX), we can query it our debugger session to confirm the selected segment is in fact index zero:
(lldb) po $rdx
<NSSegmentedControl: 0x604000183670>
(lldb) p (int) [0x604000183670 selectedSegment]
(int) $1 = 0
As the first segment contains the 'Add Volume' icon, the fact that a method named 'performAddAPFSVolume: handles this case, makes total sense :)
Now we set a breakpoint on the 'performAddAPFSVolume:' method, and hit continue ('c') in the debugger.
Once this second breakpoint is hit, we can dump its arguments:
Process 395 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
Disk Utility`___lldb_unnamed_symbol982$$Disk Utility:
-> 0x109dece42 <+0>: pushq %rbp
...
(lldb) po $rdi
<SUSharedActionController: 0x600000140bb0>
(lldb) x/s $rsi
0x109e4d46e: "performAddAPFSVolume:"
(lldb) po $rdx
SKAPFSDisk { APFS UUID: 0A81F3B1-51D9-3335-B3E3-169C3640360D, CONTAINER UUID: FCD6CA50-7007-4ADD-8243-07A01EC77893, DISK ID: disk1s1 ENCRYTPED: NO }
Most interesting is the argument passed to the method, a dictionary (in RDX) that contains information about the container (disk) to which the child volume is being added. Executing the diskutil command from the terminal illustrates that this container, unsurprisingly matches the 'main' disk ('Macintosh HD'):
$ diskutil info disk1s1
Device Identifier: disk1s1
Device Node: /dev/disk1s1
Volume Name: Macintosh HD
Mounted: Yes
Mount Point: /
Volume UUID: 0A81F3B1-51D9-3335-B3E3-169C3640360D
Disk / Partition UUID: 0A81F3B1-51D9-3335-B3E3-169C3640360D
Looking at the disassembly of the 'performAddAPFSVolume:' one can see that the method first compares the type of the disk with values such as kSKDiskTypeAPFSContainer, kSKDiskTypeAPFSLV, etc:
//[SUSharedActionController performAddAPFSVolume:]
r13 = [arg2 retain];
rbx = [[r13 type] retain];
rdx = *_kSKDiskTypeAPFSContainer;
if ([rbx isEqualToString:rdx] == 0x0) goto loc_100038ead;
...
var_38 = r14;
r14 = [[r13 type] retain];
rdx = *_kSKDiskTypeAPFSLV;
if ([r14 isEqualToString:rdx] == 0x0) goto loc_100038ef9;
Since the disk we're adding the encrypted volume to, is of type kSKDiskTypeAPFSLV (Logical Volume?), the code executes the logic for this case (address 0x100038f50):
//case: 'kSKDiskTypeAPFSLV'
rbx = [[SUAddAPFSVolumeSheetController alloc] initWithTargetDisk:r13, _kSKDiskTypeAPFSPS];
[r14 setAddAPFSVolumeSheetController:rbx, _kSKDiskTypeAPFSPS];
r14 = [[r14 addAPFSVolumeSheetController] retain];
[r14 showWindowWithParentWindow:r12 completionHandler:&var_90];
As shown in the decompilation, this code initializes an instance of an SUAddAPFSVolumeSheetController with the target disk. Shortly thereafter, it invokes the SUAddAPFSVolumeSheetController's 'showWindowWithParentWindow: completionHandler:' method.
The '[SUAddAPFSVolumeSheetController showWindowWithParentWindow:completionHandler:]' method invokes the NSWindow's 'beginSheet:completionHandler:' method to actually show the 'Add APFS volume to container?' sheet to the user:
The 'completionHandler', is of particular interest to us. Looking at the decompilation, we can see it's a block with a callback subroutine (sub_100079885):
//[SUAddAPFSVolumeSheetController showWindowWithParentWindow:completionHandler:]
var_58 = __NSConcreteStackBlock;
*(int32_t *)(&var_58 + 0x8) = 0xc2000000;
*(int32_t *)(&var_58 + 0xc) = 0x0;
*(&var_58 + 0x10) = sub_100079885;
...
This callback (sub_100079885), will be automatically invoked when the sheet is closed. Generally, such a completion handler will process the user's inputs. In this case, it's reasonable to assume that it will create the actual encrypted APFS volume with the password and hint provided by the user. As such, we set a breakpoint on this subroutine.
In the displayed sheet, if we change the format of the volume to 'APFS (Encrypted)' this causes another sheet to be displayed:
Before we continue, we set breakpoints on the following methods:
- -[SUAddAPFSVolumeSheetController setPassword:]:
- -[SUAddAPFSVolumeSheetController setPasswordHint:]:
In the UI if we enter a password and a hint and select 'Choose' the 'setPassword:' method gets invoked:
Process 395 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
Disk Utility`___lldb_unnamed_symbol2265$$Disk Utility:
-> 0x10007b4b4 <+0>: pushq %rbp
0x10007b4b5 <+1>: movq %rsp, %rbp
0x10007b4b8 <+4>: movq 0x6dcb9(%rip), %rcx
0x10007b4bf <+11>: popq %rbp
If we dump the arguments, we can see the see that password we entered, 'hunter2' which will saved into the 'password' instance variable. No surprises here.
(lldb) po $rdi
<SUAddAPFSVolumeSheetController: 0x100334f00>
(lldb) x/s $rsi
0x7fff655a205e: "setPassword:"
(lldb) po $rdx
hunter2
Continuing, the 'setPasswordHint' method is then called. In the argument passed to the method (RDX), is the password hint we entered. As expected, it is saved into the 'passwordHint' instance variable.
Process 395 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 5.1
Disk Utility`___lldb_unnamed_symbol2267$$Disk Utility:
-> 0x10007b4db <+0>: pushq %rbp
0x10007b4dc <+1>: movq %rsp, %rbp
0x10007b4df <+4>: movq 0x6dc9a(%rip), %rcx
0x10007b4e6 <+11>: popq %rbp
(lldb) po $rdi
<SUAddAPFSVolumeSheetController: 0x100334f00>
(lldb) x/s $rsi
0x7fff61870185: "setPasswordHint:"
(lldb) po $rdx
some password hint
With access to the 'SUAddAPFSVolumeSheetController' object (address: 0x100334f00), we can query it's instance variables to confirm that the correct values have been saved in both the 'password' and 'passwordHint' variables:
(lldb) po [0x100334f00 password]
hunter2
(lldb) po [0x100334f00 passwordHint]
some password hint
At this point we can rule out the scenario that the password was being incorrectly saved from the UI into the 'passwordHint' instance variable of the SUAddAPFSVolumeSheetController class. So, let's keep digging!
Clicking the 'Add' button causes the '[SUAddAPFSVolumeSheetController addClicked:]' method to be invoked.
Looking at the decompilation for this method, it's easy to see it simply closes the sheet via a call to 'endSheet: returnCode:]:
void -[SUAddAPFSVolumeSheetController addClicked:](void * self, void * _cmd, void * arg2) {
r14 = [[self parentWindow] retain];
rbx = [[self window] retain];
[r14 endSheet:rbx returnCode:0xfffffffffffffc16];
...
return;
}
Continuing execution, breaks on the completion handler for the sheet (that we previously set a breakpoint on, address 0x100079885):
Process 395 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
Disk Utility`___lldb_unnamed_symbol2211$$Disk Utility:
-> 0x100079885 <+0>: pushq %rbp
0x100079886 <+1>: movq %rsp, %rbp
0x100079889 <+4>: pushq %r15
0x10007988b <+6>: pushq %r14
The decompilation for this subroutine, shows it simply creates a block and invokes it asynchronously on the main thread. The block will execute the code at sub_10007991e, on the main thread:
int sub_100079885(int arg0, int arg1) {
var_58 = __NSConcreteStackBlock;
...
*(&var_58 + 0x10) = sub_10007991e;
dispatch_async(__dispatch_main_q, &var_58);
...
}
First, sub_10007991e configures and displays an 'Add APFS Volume' progress sheet (via a call to 'showWindowWithParentWindow:'):
rbx = [[SUBaseProgressSheet alloc] init];
rbx = [[rax localizedStringForKey:@"Add APFS Volume Progress Title" value:@"" table:0x0] retain];
[r14 showWindowWithParentWindow:rbx, rcx, 0x0];
It then invokes the 'SKAPFSContainerDisk' class's 'addVolumeWithName:caseSensitive:minSize:maxSize:password:passwordHint:
progressBlock:completetionBlock:]' method.
Once this method has been invoked, we can dump the arguments to validate that the correct values for the password and password hint are passed to this function:
(lldb) po $rdi
SKAPFSContainerDisk { APFS UUID: FCD6CA50-7007-4ADD-8243-07A01EC77893, DISK ID: disk1 }
(lldb) po [$rdi class]
SKAPFSContainerDisk
(lldb) x/s $rsi
0x7fff67eb0ea7: "addVolumeWithName:caseSensitive:minSize:maxSize:password:passwordHint:
progressBlock:completetionBlock:"
(lldb) po $rdx
test
(lldb) po $rcx
<nil>
(lldb) po $r8
<nil>
(lldb) po $r9
<nil>
(lldb) x/2gx $rsp
0x7ffeefbfdf60: 0x000000010921c470 0x000000010446a2e0
(lldb) po 0x000000010921c470
hunter2
(lldb) po 0x000000010446a2e0
some password hint
Alright; looks like that value for the 'password' parameter (address: 0x000000010921c470) is the password, while the value for the password hint (address: 0x000000010446a2e0) is indeed the password hint. Nothing amiss here! As such, we must keep heading down the rabbit hole.
The SKAPFSContainerDisk class is implemented into a private Apple framework; 'StorageKit' (/System/Library/PrivateFrameworks/StorageKit.framework).
Using 'otool' to dump the dependencies for the Disk Utility application, we can see that StorageKit is dynamically linked in:
$ otool -L /Applications/Utilities/Disk\ Utility.app/Contents/MacOS/Disk\ Utility
/Applications/Utilities/Disk Utility.app/Contents/MacOS/Disk Utility:
/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices
/usr/lib/libSystem.B.dylib
/System/Library/PrivateFrameworks/Restore.framework/Versions/A/Restore
/System/Library/PrivateFrameworks/DiskManagement.framework/Versions/A/DiskManagement
/System/Library/PrivateFrameworks/StorageKit.framework/Versions/A/StorageKit
...
Looking at the decompilation of the SKAPFSContainerDisk's 'addVolumeWithName:caseSensitive:minSize:maxSize:password:passwordHint:
progressBlock:completetionBlock:' method (implemented in the StorageKit framework), we can see it simply:
-
gets access to an instance of a 'SKHelperClient' object via a call to the SKHelperClient class method, 'sharedClient':
rax = [SKHelperClient sharedClient];
-
invokes the SKHelperClient's 'addChildVolumeToAPFSContainer:name:caseSensitive:minSize:maxSize:password:
passwordHint:progressBlock:completetionBlock:' method:
[rax addChildVolumeToAPFSContainer:self name:var_48 caseSensitive:arg3 minSize:arg4 maxSize:arg5 password:var_50 passwordHint:r14 progressBlock:r13 completetionBlock:r15];
Digging into the 'addChildVolumeToAPFSContainer:name:caseSensitive:minSize:maxSize:password:
passwordHint:progressBlock:completetionBlock:' method, we can see it creates a mutable dictionary and begins populating it with values pertaining to the child volume that is to be added to the APFS container. These of course are the values that were passed into the method as arguments.
000000000002db8d mov rdi, qword [objc_cls_ref_NSMutableDictionary]
000000000002db94 mov rsi, qword [0x4c928]
000000000002db9b mov edx, 0x5
000000000002dba0 call r15
000000000002dba3 mov rdi, rax
000000000002dba6 call imp___stubs__objc_retainAutoreleasedReturnValue
000000000002dbab mov r14, rax
000000000002dbae mov r12, qword [0x4c930]
000000000002dbb5 lea rcx, qword [cfstring_kSKAPFSVolumeNameOption]
000000000002dbbc mov rdi, r14
000000000002dbbf mov rsi, r12
000000000002dbc2 mov rdx, qword [rbp+var_48]
000000000002dbc6 call r15
...
For example, the name of the child volume ('test') is inserted into the dictionary with a key value of 'kSKAPFSVolumeNameOption'
Target 0: (Disk Utility) stopped.
(lldb) po $rdi
{
}
(lldb) po [$rdi class]
__NSDictionaryM
(lldb) x/s $rsi
0x7fff43dd61e8: "setObject:forKey:"
(lldb) po $rdx
test
(lldb) po $rcx
kSKAPFSVolumeNameOption
Following the name, (key: 'kSKAPFSVolumeNameOption'), the case sensitivity flag (key: 'kSKAPFSCaseSensitiveOption'), minimum size (key: 'kSKAPFSMinSizeOption'), and maximum size (key: 'kSKAPFSMaxSizeOption') are saved into the dictionary. Next up, the values for the password key ('kSKAPFSDiskPasswordOption') and password hint key ('kSKAPFSDiskPasswordHintOption'):
//save password
var_38 = [arg7 retain];
if (var_38 != 0x0) {
[r14 setObject:var_38 forKey:@"kSKAPFSDiskPasswordOption"];
}
Target 0: (Disk Utility) stopped.
(lldb) po $rdi
{
kSKAPFSCaseSensitiveOption = 0;
kSKAPFSMaxSizeOption = 0;
kSKAPFSMinSizeOption = 0;
kSKAPFSVolumeNameOption = test;
}
(lldb) x/s $rsi
0x7fff43dd61e8: "setObject:forKey:"
(lldb) po $rdx
hunter2
(lldb) po $rcx
kSKAPFSDiskPasswordOption
Looking good...now for the kSKAPFSDiskPasswordHintOption:
//save password hint?
if (arg8 != 0x0) {
[r14 setObject:var_38 forKey:@"kSKAPFSDiskPasswordHintOption"];
}
Target 0: (Disk Utility) stopped.
(lldb) po $rdi
{
kSKAPFSCaseSensitiveOption = 0;
kSKAPFSDiskPasswordOption = hunter2;
kSKAPFSMaxSizeOption = 0;
kSKAPFSMinSizeOption = 0;
kSKAPFSVolumeNameOption = test;
}
(lldb) x/s $rsi
0x7fff43dd61e8: "setObject:forKey:"
(lldb) po $rdx
hunter2
(lldb) po $rcx
kSKAPFSDiskPasswordHintOption
....wait what!? It appears as if the password ('hunter2'), instead of the password hint, is about to be incorrectly saved as the value for the kSKAPFSDiskPasswordHintOption key in the dictionary!
Stepping over the call to setObject:forKey: confirms this unfortunate fact:
(lldb) po 0x0000604000428180
{
kSKAPFSCaseSensitiveOption = 0;
kSKAPFSDiskPasswordHintOption = hunter2;
kSKAPFSDiskPasswordOption = hunter2;
kSKAPFSMaxSizeOption = 0;
kSKAPFSMinSizeOption = 0;
kSKAPFSVolumeNameOption = test;
}
Opps!
Taking a closer look at the (pseudo)code we can see the value in 'var_38' is being passed as the first argument to the setObject:forKey: method. Previously var_38 was assigned the (retained) value of 'arg7':
var_38 = [arg7 retain];
Recall the method's signature: addChildVolumeToAPFSContainer:name:caseSensitive:minSize:maxSize:password:
passwordHint:progressBlock:completetionBlock:. Since objective-C methods are invoked with an instance of target object (i.e. SKHelperClient) as the first argument ('arg0') and the name of the method as the second argument ('arg1'), this means 'arg7' is actually the password parameter.
This can also be confirmed by noting that the arg7, after being assigned to var_38 was saved as the value for the 'kSKAPFSDiskPasswordOption' key in the dictionary.
So finally we've identified the buggy line of code! Instead of saving the password hint into the dictionary (for the kSKAPFSDiskPasswordHintOption key), it uses the password.
We can confidently confirm this, by noting that if we change the value in the dictionary for the 'kSKAPFSDiskPasswordHintOption' key, to the password hint (as it should be), this will propagated thru the creation of the child volume to the specified APFS container. We can enact this change at the call to the 'setObject:forKey:' ('kSKAPFSDiskPasswordHintOption') by modifying the value in the RDXregister from the password ('hunter2') to the password hint (found at RBP+0x20):
(lldb) po $rdx
hunter2
(lldb) x/5gx $rbp
0x7ffee5e4a300: 0x00007ffee5e4a3b0 0x00007fff67e97236
0x7ffee5e4a310: 0x0000000000000000 0x0000600000431800
0x7ffee5e4a320: 0x0000604000647410
(lldb) po 0x0000604000647410
some password hint
(lldb) register write $rdx 0x0000604000647410
(lldb) po $rdx
some password hint
With this manual 'correction' in place, now, any time when the user selects "Show Hint" the correct value - the password hint (and not the password!), is displayed:
As noted in Daniel's 'diff-based' writeup, the error was likely the result of a simple copy & paste error. It seems the Apple software developer simply copied and pasted the chunk of code responsible for saving the password, and forgot to change it to save the password hint instead. We might imagine the erroneous code looks like:
//dictionary
NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithCapacity:0x5];
//save password
if (password != nil)
{
[infoDictionary setObject:password forKey:@"kSKAPFSDiskPasswordOption"];
}
//save password hint
if (passwordHint != nil)
{
[infoDictionary setObject:password forKey:@"kSKAPFSDiskPasswordHintOption"];
}
Though a trivial bug, it's ramifications from a security point of view are rather dire. As the password protecting the encrypted volume is displayed (instead of the hint), this undermines the entire security of the protection scheme, as it could allow a local attacker to gain access to the encrypted volume.
Though we now fully understand the root cause of vulnerability, let's briefly look at how the dictionary (containing the password hint set to the password) is used.
A few lines of code later (within the the same method), it is passed to an XPC method (__NSXPCInterfaceProxy_SKDaemonConnectionProtocol 'addChildVolumeToAPFSContainer:optionsDictionary:handlingProgressForOperationUUID:
completionBlock:').
(lldb) po [$rdi class]
__NSXPCInterfaceProxy_SKDaemonConnectionProtocol
(lldb) po $rdi
<__NSXPCInterfaceProxy_SKDaemonConnectionProtocol: 0x6040002993c0>
(lldb) x/s $rsi
0x7fff67eae707: "addChildVolumeToAPFSContainer:optionsDictionary:
handlingProgressForOperationUUID:completionBlock:"
(lldb) po $rdx
{
apfsUUID = "FCD6CA50-7007-4ADD-8243-07A01EC77893";
designatedPSUUID = "25B8F381-DC5C-40C4-BCF2-9B22412964BE";
diskIdentifier = disk1;
filesystemType = kSKDiskFileSystemUndefined;
isFusion = 0;
physicalStoreUUIDs = (
"25B8F381-DC5C-40C4-BCF2-9B22412964BE"
);
role = kSKDiskRoleStorageImplementation;
type = kSKDiskTypeAPFSContainer;
volumeUUIDs = (
"0A81F3B1-51D9-3335-B3E3-169C3640360D",
"F3CF4B13-2927-421A-8CF5-8F2E95348AC4",
"CF7A0ECF-6941-4625-8092-7E84F1D2F46D",
"B3ABA184-AB5F-4250-BE66-43B9DFA841B1"
);
}
(lldb) po $rcx
{
kSKAPFSCaseSensitiveOption = 0;
kSKAPFSDiskPasswordHintOption = hunter2;
kSKAPFSDiskPasswordOption = hunter2;
kSKAPFSMaxSizeOption = 0;
kSKAPFSMinSizeOption = 0;
kSKAPFSVolumeNameOption = test;
}
This appears to generate an XPC message from the Disk Utility application to the storage kit daemon process: storagekitd. We can use the lsmp tool to dump statistics about mach XPC messages, for example to those between the Disk Utility app and the storage kit daemon:
$ lsmp -a
...
Process (395) : Disk Utility
0x00005e03 0xd2f22311 send 1 -> 16 (396) storagekitd
0x00005f03 0xd82d9421 send 1 -> 6 (396) storagekitd
Also, by monitoring the file system, we can see that upon receiving this XPC message, storagekitd appears to create and then add the encrypted child volume to the specified APFS container:
# fs_usage -w -f filesystem
0.004001 W storagekitd.23367 RdMeta[A] D=0x00389b9e B=0x7000 /dev/disk1s1
0.003542 W storagekitd.23367 08:12:25.768059 PAGE_IN_FILE A=0x0101ea6000
....
Conclusion
Apple fixed this critical security vulnerability in a supplemental security update for High Sierra (macOS 10.13):
Reversing their patch (as discussed in Daniel's writeup), shows Apple - as expected - fixed the issue by ensuring the kSKAPFSDiskPasswordHintOption value contains the the password hint (as opposed to the password). Recall this value is passed into the method in arg8:
;arg8 contains password hint
var_48 = [arg8 retain];
rbx = var_48;
if (rbx != 0x0) {
[r14 setObject:rbx forKey:@"kSKAPFSDiskPasswordHintOption"];
}
On one hand I'm very surprised that this critical security vulnerability made it into the release of High Sierra. Then, again looking at Apple's security track record - I'm really not surprised at all :(
Makes one wonder...if not for an independent security researcher uncovering this bug - would it have ever been discovered and fixed by Apple? Or would blackhat hackers have found it first and utilized it for their own nefarious purposes?
Well, that's a wrap. Mahalo for reading along! I hope you enjoyed our spelunking session, which started at the UI level and took us into the heart of StorageKit framework where the bug ultimately was uncovered. And while once a patch is released, diff'ing is generally a more efficient route to uncover such flaws, our approach method didn't require such a patch to diff...and hopefully was a good practical reversing walk thru :)
love these blog posts & tools? you can support them via patreon! Mahalo :)