sgan81 / apfs-fuse

FUSE driver for APFS (Apple File System)
GNU General Public License v2.0
1.78k stars 164 forks source link

Cannot extract dyld shared caches from iOS 16 System Cryptex DMGs due to new compression method #168

Open pwmoore opened 2 years ago

pwmoore commented 2 years ago

Hi,

I am trying to use apfs-fuse to mount the Cryptex1,SystemOS DMG from the latest iOS 16 IPSWs on Linux. It appears that Apple has started compressing some files with a new type of compression that apfs-fuse does not support, called LZBITMAP.

To reproduce this issue, download the iPhone 14 Plus 16.0 IPSW from here and extract the file 098-09358-001.dmg and mount it with apfs-fuse., then browse to $MOUNT_POINT/root/System/Library/Caches/com.apple.dyld. If you do an ls -l, you will see three files with a weird size and timestamp:

# In one terminal
$ sudo apfs-fuse -d 11 098-09358-001.dmg dmgmnt/

# Then in another
# ls -lh
-rwxr-xr-x 1 root 80   16 Jan  1  1970 dyld_shared_cache_arm64e.28.dyldlinkedit
-rwxr-xr-x 1 root 80   16 Jan  1  1970 dyld_shared_cache_arm64e.44.dyldlinkedit
-rwxr-xr-x 1 root 80   16 Jan  1  1970 dyld_shared_cache_arm64e.symbols

Further, if you try to read one of these files, you will see the following:

# hexdump -C dyld_shared_cache_arm64e.symbols
hexdump: dyld_shared_cache_arm64e.symbols: Input/output error

The apfs-fuse logs will show something like this.

DecompressFile 10 => 21cd8000, algo = e (Unknown)
Unsupported decompression algorithm.

This compression type 14 turns out to be the LZBITMAP type, which is part of libcompression on macOS. I added a switch case for 14 to the IsDecompAlgoSupported() and IsDecompAlgoInRsrc() functions in Decmpfs.cpp, and apfs-fuse reports the correct size, but obviously it still needs the algorithm.

I am not sure if apfs-fuse can support this algorithm, because it is closed source. I did take a look at the lzbitmap_decode_buffer() function in libcompression on macOS and it's a lot of architecture specific vectorized instructions. I certainly understand if this is not something that can be added, either because of legal concerns with proprietary algorithms, or just lack of time to re-implement something like this. Either way, I just wanted to bring it to your attention.

Thanks!

IceCooler148 commented 2 years ago

DecompressFile 10 => 21cd8000, algo = e (Unknown) Unsupported decompression algorithm.



This compression type 14 turns out to be the LZBITMAP type, which is part of `libcompression` on macOS. I added a switch case for 14 to the `IsDecompAlgoSupported()` and `IsDecompAlgoInRsrc()` functions in `Decmpfs.cpp`, and `apfs-fuse` reports the correct size, but obviously it still needs the algorithm.

I am not sure if `apfs-fuse` can support this algorithm, because it is closed source. I did take a look at the `lzbitmap_decode_buffer()` function in `libcompression` on macOS and it's a lot of architecture specific vectorized instructions. I certainly understand if this is not something that can be added, either because of legal concerns with proprietary algorithms, or just lack of time to re-implement something like this. Either way, I just wanted to bring it to your attention.

I'm facing the same issue.. How do you know that the missing algorithm is LZBITMAP ?

pwmoore commented 2 years ago

So when I tried to mount with debug messages turned on I got an error stating "unknown compression type 14". I found an earlier issue #145 talking about compression type 9. They found some information in the Apple opensource copyfile, so I went and looked at the most recent version, and in copyfile.c we see:

    switch (OSSwapLittleToHostInt32(hdr->compression_type)) {
            // ... SNIP ...
        case 13: /* LZBITMAP-compressed data in xattr */
        case 14: /* 64k chunked LZBITMAP-compressed data in resource fork */

                /* valid compression type, we want to copy. */
                break;

        case 5: /* specifies de-dup within the generation store. Don't copy decmpfs xattr. */
            copyfile_debug(3, "compression_type <5> on attribute com.apple.decmpfs for src file %s is not copied.",
            s->src ? s->src : "(null string)");
            continue;

        case 6: /* unused */
        case 0x80000001: /* faulting files are deprecated, don't copy decmpfs xattr */
        default:
            copyfile_warn("Invalid compression_type <%d> on attribute %s for src file %s",
                OSSwapLittleToHostInt32(hdr->compression_type), name, s->src ? s->src : "(null string)");
            continue;
    }

So that gave me enough hints to get started.

IceCooler148 commented 2 years ago

Did you started the RE ? are you working on it ? Or you are waiting for apfs-fuse to develop it ? @pwmoore

IceCooler148 commented 2 years ago

I'm working on it myself. I did most of the work already, the problem is that I'm not sure what _lzbitmap_decode should get as parameters ? Where I get the 64k chunks to decompress ? @pwmoore

pwmoore commented 2 years ago

I'm working on it myself. I did most of the work already, the problem is that I'm not sure what _lzbitmap_decode should get as parameters ? Where I get the 64k chunks to decompress ? @pwmoore

I haven't had an opportunity to work on it much further. If you look at the function DecompressFile() in ApfsLib/Decmpfs.cpp you'll see that algorithm type 4 is a resource fork algorithm that decompresses 64K chunks, calling DecompressLZVN() on each chunk. So I imagine you'd want to start with a DecompressLZBITMAP() function with the same parameters. The chunking probably wouldn't be exactly the same... you might have to fiddle with it some, but it's probably a good example on how to get started.

However LZVN decompression works a little differently under the hood than LZBITMAP. On macOS, you get to lzbitmap_decode() through the following callchain:

compression_decode_buffer()
    lzbitmap_decode_buffer()
        lzbitmap_decode()

And lzbitmap_decode_buffer() just passes its arguments to lzbitmap_decode(). You can take a look at DecompressLZFSE() in ApfsLib/Util.cpp for a good example of how this works... LZFSE is an open source compression library developed by Apple that's used in libcompression. They have a similar lzfse_decode_buffer() function that compression_decode_buffer() calls for LZFSE compression.

That's basically about as far as I was able to get... I work full time plus I'm in grad school with a family, so taking on another project just isn't on my plate right now :(

IceCooler148 commented 2 years ago

I ran ipsw extractor on my mac, trying to listen to libcompression calls, didn't found any call to compression_decode_buffer. Found only calls to compression_stream_process.

Maybe the algo isn't LZBITMAP ? @pwmoore

pwmoore commented 2 years ago

So the apfs decompression is done in the kernel on the Mac. If you look at the copyfile.c link I gave above, there's a comment that reads:

/*
 * From AppleFSCompression documentation:
 * "It is incumbent on the aware copy engine to identify
 *  the type of compression being used, and to perform an
 *  unaware copy of any file it does not recognize."
 *
 * Compression Types are defined in:
 * "AppleFSCompression/Common/compressorCommon.h"
 *
 * Unfortunately, they don't provide a way to dynamically
 * determine what possible compression_type values exist,
 * so we have to update this every time a new compression_type
 * is added. Types 7->10 were added in 10.10, Types 11 & 12
 * were added in 10.11.
 *
 * Ubiquity faulting file compression type 0x80000001 are
 * deprecated as of Yosemite, per rdar://17714998 don't copy the
 * decmpfs xattr on these files, zero byte files are safer
 * than a fault nobody knows how to handle.
 */

Further, the "Decmpfs" refers to the XNU kernel's decmpfs subsystem for file system compression.

If you do an nm on /System/Library/Extensions/AppleFSCompressionTypeZLib.kext/MacOS/AppleFSCompressionTypeZlib, you can see that it has libcompression built in, and although the symbol isn't there, it also has LZBITMAP. I forget the order of how it works, but basically the APFS kext and this kext provide transparent filesystem compression via registering decmpfs handlers.

I didn't do any kernel debugging to see if I hit any of this code, but I'm pretty sure that it is LZBITMAP.

eafer commented 1 year ago

I just finished a library for LZBITMAP decompression if you want to use it: https://github.com/eafer/libzbitmap. I'll be adding support for cryptex to my driver in a few days.

pwmoore commented 1 year ago

@eafer You are a hero to us all!

kleuter commented 1 year ago

https://github.com/sgan81/apfs-fuse/pull/169

sgan81 commented 1 year ago

I just pushed a version with experimental support for lzbitmap. Sorry didn't notice others have already done the work ...