Mac: It is possible to tamper with a signed disk image without invalidating its signature |
||
Issue description(This is an Apple bug, and is being reported to product-security@apple.com contemporaneously with being filed here for our own records.) This bug is subject to a 90 day disclosure deadline. If 90 days elapse without a broadly available patch, then the bug report will automatically become visible to the public. Background: UDIF disk image (dmg) signing is available in 10.11.4 and later. https://developer.apple.com/library/prerelease/content/technotes/tn2206/_index.html#//apple_ref/doc/uid/DTS40007919-CH1-TNTAG18. Items launched from quarantined unsigned disk images are subject to translocation in 10.12. https://developer.apple.com/library/prerelease/content/releasenotes/MacOSX/WhatsNewInOSX/Articles/OSXv10.html#//apple_ref/doc/uid/TP40017145-DontLinkElementID_68. It is possible to tamper with a signed disk image after signing without invalidating its signature. Specially-modified dmg files will appear as genuine during signature validation. On 10.12, items launched from these modified disk images will not be subject to translocation even when the dmg is quarantined, and apps will run directly from the disk image’s mounted filesystem. An ordinary unsigned UDIF file consists of a 512-byte UDIF header-trailer (identified by its magic number, “koly”) at the end of the file. The UDIF header contains some disk image metadata and pointers to other data (such as the “blkx”-carrying plist and the area of the file that contains representations of disk blocks) in the form of file offsets and lengths. An ASCII art representation of an unsigned dmg: +-------------+-------------+ + Other stuff | UDIF header | +-------------+-------------+ where the UDIF Header contains (offset, length) pointers to things within the “other stuff” region. A signed UDIF file retains this same structure, but adds pointers to code signature data in the UDIF header. When codesign --sign signs a disk image, it forms a hash of the entire unsigned dmg file’s contents with the exception of the UDIF header, and a separate hash of the UDIF header. Notably, the UDIF header hash covers the offset of the code signature data, but not its length. codesign --sign writes the code signature data (which includes these hashes) where the UDIF header had been in the unsigned image, and places the UDIF header immediately after the code signature data so that it remains at the end of the dmg file. An ASCII art representation of a signed dmg: +-------------+----------------+-------------+ + Other stuff | Code signature | UDIF header | +-------------+----------------+-------------+ In this representation, the UDIF header contains identical (offset, length) pointers to things within the “other stuff” region as in an unsigned dmg, plus an (offset, length) pointer to the code signature. The code signature contains a hash of “other stuff” and a hash of the UDIF header, with the exception of the UDIF header’s code signature length field. It is possible to take this signed dmg and modify it by inserting hidden data between the code signature and the UDIF header: +-------------+----------------+-------------+-------------+ + Other stuff | Code signature | Hidden data | UDIF header | +-------------+----------------+-------------+-------------+ Signature verification does check that the length of the code signature specified in the UDIF header matches the length stated in the code signature itself. It also checks that no data exists between the code signature and the UDIF header. Seemingly, these checks forestall the modification described here. However, it’s possible to modify the length of the code signature as specified in both the UDIF header and the code signature itself so that it encompasses the size of the hidden data. This effectively embeds the hidden data within the region assigned for use as the code signature. The file’s structure is interpreted in this way: +-------------+--------------------------------+-------------+ + Other stuff | Code signature and hidden data | UDIF header | +-------------+--------------------------------+-------------+ Although within the range of the code signature, the hidden data will “safely” be ignored by the signature verification mechanism. This is because the signature is structured as an embedded-signature “SuperBlob” which has this structure: struct SuperBlob { uint32_t magic; // 0xfade0cc0 uint32_t length; // modified to include hidden data uint32_t count; // size of the |index| array that follows struct { uint32_t type; // type of (Sub)Blob uint32_t offset; // relative to start of SuperBlob } index[]; // (Sub)Blob data follows, indexed by SuperBlob::index::offset }; While the signature reader may ensure that no part of the signature region exceeds the limit specified by SuperBlob::length, in fact nothing ensures that useful components of the code signature contribute to its length. Therefore, it’s possible to increase SuperBlob::length arbitrarily and hide data within this expanded region. No part of signature validation will be affected because no part of the signature references the portion of the signature region co-opted to host arbitrary data. The hidden data is unsealed content within a signature-protected file. The hidden data can be recovered by locating the code signature region, determining the actual extent of signature “blob” data, and extracting what follows it, up to the end of the code signature region (and beginning of the UDIF header). Were the length of the code signature region included in the hash of the UDIF header, any compromise reliant on altering the length of the code signature (as is done here) would be prevented. The portion of a signed dmg file that can be modified does not provide any filesystem data or disk image metadata, and is not normally referenced by dmg parsers, and is not visible to the user. Nevertheless, it is possible for an app running from a disk image’s mounted filesystem to determine that it’s running from a disk image, locate the dmg file backing the disk image, and access that file directly. This might be done intentionally: unsealed bytes in the dmg file can be used to “tag” a particular instance of an already-signed dmg without having to re-sign each tagged variant. This approach can also be used to steganographically hide information in signed disk images that purport to be from other sources. Tested on 10.11.6 15G31 and 10.12db3 16A254g. The attached test program, dmg_steg.py, exploits these findings. It can insert hidden data into a signed dmg (with the --insert option), recover data hidden in a signed dmg (with the --extract option), and remove hidden data from a signed dmg (with the --remove option). Example usage: # # Create a signed disk image. # $ mkdir empty $ hdiutil create -srcdir empty -fs HFS+ -format UDZO -o empty.dmg . created: …/empty.dmg $ codesign --sign='Developer ID Application: Me' empty.dmg $ stat -f %z empty.dmg; shasum empty.dmg 23590 12bb4d1d94593ed13a98686b358663c050df510f empty.dmg $ codesign --verify --verbose empty.dmg empty.dmg: valid on disk empty.dmg: satisfies its Designated Requirement $ hdiutil verify -quiet empty.dmg || echo INVALID # # Hide some data in the signed disk image. Verify that its signature still # validates and it still functions as a proper disk image. # $ stat -f %z /etc/passwd; shasum /etc/passwd 5253 c0c71bac843a3ec7233e99e123888beb6da8fbcf /etc/passwd $ python dmg_steg.py empty.dmg --insert /etc/passwd $ stat -f %z empty.dmg; shasum empty.dmg 28843 37649955f9ffbe3948785f7eecb0113a400a4916 empty.dmg $ codesign --verify --verbose empty.dmg empty.dmg: valid on disk empty.dmg: satisfies its Designated Requirement $ hdiutil verify -quiet empty.dmg || echo INVALID $ hdiutil attach empty.dmg expected CRC32 $A99E9A51 /dev/disk2 GUID_partition_scheme /dev/disk2s1 Apple_HFS /Volumes/empty $ hdiutil eject disk2 -quiet # # Extract hidden data from the disk image. Verify that it matches what was # inserted. # $ python dmg_steg.py empty.dmg --extract /tmp/passwd $ stat -f %z /tmp/passwd; shasum /tmp/passwd 5253 c0c71bac843a3ec7233e99e123888beb6da8fbcf /tmp/passwd # # Remove hidden data from the disk image. Verify that the disk image file has # been restored to its initial state. # $ python dmg_steg.py empty.dmg --remove $ stat -f %z empty.dmg; shasum empty.dmg 23590 12bb4d1d94593ed13a98686b358663c050df510f empty.dmg $ codesign --verify --verbose empty.dmg empty.dmg: valid on disk empty.dmg: satisfies its Designated Requirement $ hdiutil verify -quiet empty.dmg || echo INVALID
,
Sep 1 2016
2016-09-01 18:04 UTC from Apple: Hello, When we address an issue in a Security Update, we like to give credit to the person who reported the issue to us. The information is typically in the following format: [Person] of [Company or School or Agency] You can see examples of this at https://support.apple.com/kb/HT201222 Would you please let us know if you would like to be credited, and the information you would like us to use?
,
Sep 1 2016
2016-09-01 18:09 UTC to Apple: Please credit me as: Mark Mentovai of Google Inc.
,
Sep 20 2016
macOS Sierra (10.12) was released today, 2016-09-20. It contains a fix for this vulnerability, which was assigned CVE ID CVE-2016-4753. “About the security content of macOS Sierra 10.12”, https://support.apple.com/en-us/HT207170: > Security > Available for: OS X El Capitan v10.11.6 > Impact: A malicious application may be able to execute arbitrary code > with system privileges > Description: A validation issue existed in signed disk images. This > issue was addressed through improved size validation. > CVE-2016-4753: Mark Mentovai of Google Inc. De-restricting because a fix has been made broadly available. |
||
►
Sign in to add a comment |
||
Comment 1 by mark@chromium.org
, Jul 21 2016