okay, I'm going to do a livetweet on

🎉 How to mount the disk of an Android Emulator image 🎉

because apparently NO ONE AT GOOGLE thought this would be something that people would need to do at some point
join me in this journey through cryptography, interfaces, Android, custom kernels, and lots of undocumented things
so imagine you've started up an AVD as usual, and you want to mount the data of that emulator, particularly the partition that holds the user data.

you open the AVD's folder and locate:
📎 userdata.img
📎 userdata.img.qcow2
so far so good, it's the partition you're looking for. Android is emulated in QEMU, and QEMU uses the .qcow2 format for snapshots:

userdata.img holds the base for the partition (in the initial state of the VM) and the .qcow2 has the *changes* to it
so the first thing we want to do is merge both images to see how the volume is in its current state:

$ qemu-img convert userdata.img.qcow2 userdata-merged.img
however if we try to mount the resulting image...

# mkdir data
# mount userdata-merged.img data

it fails to detect the filesystem type: # ls userdata-qemu.img  userdata-qemu.img.qcow2  # qemu-img
we can mount the base image (userdata.img) just fine, and file tells us it's an ext4.

but for the merged image, we get 'data'. and if we look at the first blocks, the two images are completely different... # mount userdata-qemu.img data # ls data local.prop  lost+fohexdump of (the start of) both images. the base image is all
(btw sorry, I fucked up, userdata.img is actually called userdata-qemu.img, and the same with the .qcow2... replace the names mentally in the previous tweets)
anyway, you can see the logical conclusion... the partition is encrypted! Android comes with encryption turned on by default, and apparently it isn't disabled in the emulator images either 🙃
so before we continue, quick crash course on

✨ Encryption in Android ✨

(and how it's changed throughout time, which means most info you find will be outdated if you're running a recent Android version)
there are three main evolutions of encryption in Android:

1️⃣ Full Disk Encryption (FDE)
2️⃣ File Based Encryption (FBE)
3️⃣ File Based Encryption + Metadata Encryption

we're gonna go through them quickly
1️⃣ Full-Disk Encryption (present since Android 4.4) is the most basic one. The entire disk is encrypted with a symmetric key, usually AES, through the dm-crypt module.
This is the same kind of encryption you probably have on your laptops. It sits between the FS and the block device, and en/decrypts blocks as they are written/read. Pretty transparent.

The AES key used to encrypt blocks is tied *somehow* to your password (or pattern / PIN)...
Essentially, the disk has a "crypto footer" in the end where Android saves the AES key (which we'll call Device Encryption Key), but encrypted with your password. Then an elaborate process follows to decrypt the DEK using your password, which grants access to the disk:
But this has a known flaw: offline attacks. The attacker can extract the disk from the phone, and bruteforce all combinations of the pattern / PIN (there aren't that many) until they get the DEK.

To fix this, Android 5.0 introduces ✨ the keymaster ✨ a character from the Matrix movie with the same name. I don'
The keymaster is a component *outside* of Android (implemented by the OEM). It's a blackbox that lets you save secrets in it!

This box will ideally save these secrets in hardware, in a way that you can't easily extract them from the board (like you can with e.g. the disk)
I oversimplified a bit; the Keymaster doesn't actually save your secrets, it *wraps* them in an opaque blob that only *it* can decipher to get the secret again.

You save the (wrapped) secret yourself, but each time you need to use it, you'll need to ask the keymaster
Remember the process to get access to the DEK that I showed before? Android 5 complicates it further, and makes it dependent on the KeyMaster at some point. Without the KeyMaster's cooperation, you can't decrypt the disk (even if you know the password): a flow chart explaining the flow to decrypt the DEK on Andro
🔗 If you want to know more, here's a great in-depth explanation of FDE (and the source of these flowcharts): bits-please.blogspot.com/2016/06/extrac…

But all of that doesn't really matter because FDE is no longer used these days. Instead we use

2️⃣ File Based Encryption (FBE)
FBE (introduced in Android 7, and required in Android 10+) fixes many issues with FDE.

By definition, FDE prevents you from even *mounting* the disk til you enter the password. It's simple but very limiting, bc you can't even show the user's actual lock screen (wallpaper, etc.)
So in FBE you have a regular unencrypted disk; the contents of the files is what gets encrypted. This lets Android (and individual applications) decide what to encrypt on a file-by-file basis.

source.android.com/security/encry…
Example: the clock app decides to leave the alarms unencrypted, so that they can work before you enter the password. The times of your alarms isn't very sensitive data, so it makes sense. Your wallpaper is also left unencrypted, and so on.
Another good thing about FBE is that you can have multiple users in the system, and the files of each user are encrypted with their specific password (instead of being one single password for the disk / machine).

And the filenames can be optionally encrypted, too.
In essence, you get a much more polished experience because Android doesn't need to ask your password as part of the early boot process.

But that, too, has flaws. Enter

3️⃣ FBE + Metadata Encryption
Where FDE encrypted too many info, FBE encrypts too little: only the contents of the files. All the filesystem metadata, such as
➡️ directory structure
➡️ block layout
➡️ file access / modification times!
is left completely in the clear, anyone can take the disk out & mount it
Patterns in file timestamps, sizes, blocks can reveal some amount of sensitive info. It's not the end of the world, but we could at least make it harder to get this info.
This is what metadata encryption does! It's an improvement to FBE which encrypts everything that FBE doesn't (which we call metadata).

In particular, all blocks of the disk ⚠️ except the ones that store the contents of an FBE-covered file ⚠️ get encrypted with a 🔑 metadata key.
This metadata key gets stored alongside the encrypted disk, in the "metadata partition" (a modern alternative to the "crypto footer" from before).

The catch is that it is wrapped by the KeyMaster, so... as with FDE, you need it to cooperate before you can mount the disk.
So, like in FDE, we have a fully encrypted disk that we can't even mount. But the crucial difference is, the metadata key does NOT depend on the password, only the KeyMaster.
Oh, and we ended up with a system that has 10x the complexity of FDE, which will make us suffer.

And this concludes the overview of encryption in Android, now let's get back to work.

[theory end]
Our emulator image is Android 12, which has FBE + metadata encryption enabled by default, so no wonder we couldn't mount the data partition.

We need to decrypt the blocks, so our first goal is to obtain the metadata key.
And that key is located in the "metadata partition", which in the emulator is stored in:

📎 encryptionkey.img
📎 encryptionkey.img.qcow2

So the first thing we'll do is merge these into encryptionkey-merged.img, just like we did with userdata.
Now, if we look at the image, file(1) tells us that it's a partitioned volume. So see what partitions it has... there's a GPT table, with a single partition!
we can set this file as a loop device (with losetup):

# losetup /dev/loop0 encryptionkey-merged.img

and then tell Linux to create the partitions on it:

# kpartx -a /dev/loop0

this creates /dev/mapper/loop0p1, which seems to contain an ext4 filesystem # losetup /dev/loop0 encryptionkey-merged.img # kpartx -a /d
we mount it (⚠️ ALWAYS read-only, I forgot in the initial tweets. simply mounting in read-write tampers with the image, so don't forget the -o ro):

# mkdir metadata
# mount -o ro /dev/mapper/loop0p1 metadata

It mounts! We can see a bunch of files inside: the commands in the tweet, plus "tree -Cash metadata&qu
and there it is! in vold/metadata_encryption/key, we have a bunch of files. the key we're after is in "encrypted_key" but it's encrypted with the KeyMaster secret inside "keymaster_key_blob".

oh, and Vold is Android's storage daemon, which is what decrypts and mounts stuff
so while booting, Vold would do the following: it will give "keymaster_key_blob" to the KeyMaster and ask "hey can you use this to decrypt this message?" and then hand out the encrypted key.

KeyMaster returns the decrypted message (the metadata key), and Vold mounts the disk
so! we have to replicate this process, emulating what Vold and the KeyMaster would do, and obtain the decrypted metadata key,

now you may be asking "isn't the KeyMaster supposed to be a hardware black box so you can't replicate it yourself? that can't be done in an emulator"
and yes, you're correct! for devices that don't have a real KeyMaster, Android uses a dummy software-based KeyMaster that doesn't provide any security. This includes the emulator.

So our task is "easy": find the source code of that software KeyMaster, and replicate its behavior
✨ Crash course on Android development ✨

Android is an OS with lots of components. Instead of a terribly huge monorepo, they have many small repos. So many, that the *names of the repos* are hierarchical.

Here you can see them all: android.googlesource.com/platform
The "hardware/interfaces" repo (android.googlesource.com/platform/hardw…) holds definitions for the interfaces between components. The interface exposed by KeyMaster can be found in the "keymaster/4.0/IKeymasterDevice.hal" file: android.googlesource.com/platform/hardw…
as you can infer, there have been many versions of the KeyMaster interface, from v0 to v4, with varying (mostly growing) functionality. I recommend you take a quick look at the interface file and the methods it exposes.
And then, the implementation for the KeyMaster lives in the system/keymaster repo: android.googlesource.com/platform/syste…

🔗 Here's a good tour of the KeyMaster: the interface(s), and the various implementations (and relevant files / code) for each of them fatalerrors.org/a/android-q-10…
We only care about section 4.2, which talks about the default software-based implementation. The code creates a PureSoftKeymasterContext, so let's clone the repo and search for it.

And look! There's a ParseKeyBlob method with helpful comments 👀 screenshot of the code, which starts with a big comment that
This is the first step, we want to understand what's on the keymaster blobs, i.e. the "keymaster_key_blob" file. As the comment tells us, in the new versions, the blob is just the key + an HMAC at the end to check integrity.

For reference, this what "keymaster_key_blob" has: a hexdump of said file. it shows mostly zeroes, which indica
To save you the trouble of understanding KeyMaster's source code and decoding the blob by hand, I've created a 'little' tool here: gist.github.com/mildsunrise/58…

We can use this tool to inspect our blob, and we get: the tool inspected our "keymaster_key_blob" file a
So! We can see that this KeyMaster blob contains an AES-256 key, to be used with the GCM mode and no padding.

Now we have to use it to decrypt the encrypted_key! But before we can do that, we have to know where the IV is.
Vold is in the system/vold repo... if we clone them and search for "encrypted_key" or "keymaster_key_blob" we quickly end up in the KeyStorage.cpp file: android.googlesource.com/platform/syste…
Here's the relevant bit of code! It tells us that encrypted_key holds the concatenation of IV + ciphertext + authentication tag, and that the IV is 12 bytes, and the tag is 16 bytes

So with all that, we can go ahead and decrypt the key! This gets exciting 😁 code of the encryptWithKeystoreKey function. it asks the Key
WE DECRYPTED IT. WE HAVE THE METADATA KEY python session where we do:  from binascii import hexlify, u
note that we're using the GCM mode, which provides integrity checking. if we had messed up anything in the key, IV, ciphertext or tag, it would have given an error like in the photo.

so we can be sure this is the real metadata key 😊 we changed one bit in the key, and attempted decryption agai
now we can use this key to decrypt the userdata partition. first we'll set it up as a loop device:

# losetup /dev/loop1 userdata-merged.img

now we'll use dm-crypt to create a device that exposes the contents of /dev/loop1, decrypted with the key: kernel.org/doc/html/lates…
dm-crypt has many parameters; at the end there are some examples. we'll leave both offsets as 0. the cipher algorithm is the usual AES-256-XTS, so we pass "aes-xts-plain64". our command is:

# dmsetup create userdata --table "0 $SIZE crypt aes-xts-plain64 $KEY 0 $SOURCE 0 0" # losetup /dev/loop1 userdata-merged.img # set KEY <the key
this creates a /dev/mapper/userdata device that shows the decryped content. let's see how it looks... all zeroes! this is what we expected, since it matches what we saw on the (unencrypted) userdata-qemu.img.

however only the first 512 bytes are zeroes, then garbage 😕
this indicates that the key is indeed the right one, but some parameters are wrong. let's try increasing the sector size from the default 512 to 4096 (the maximum)

# dmsetup create userdata --table "0 $SIZE crypt aes-xts-plain64 $KEY 0 $SOURCE 0 1 sector_size:4096" dm-crypt documentation of the sector_size parameter:  sectorsame terminal as before, but with  # dmsetup remove userdata
now more data is decrypted correctly and matches what we expect! we can even see *text* at some point ^^ side by side comparison of the hexdumps of our decrypted ima
and it all goes well until we reach 4096 bytes. the next sector isn't decrypted correctly and has garbage.

we can't increase the sector size further, so that parameter is right. some other parameter must be the culprit...
at this point I was considering searching Vold's source code, but I got to the conclusion that it had to be the "iv_large_sectors" parameter. I tried adding it:

# dmsetup create userdata --table "0 $SIZE crypt aes-xts-plain64 $KEY 0 $SOURCE 0 2 sector_size:4096 iv_large_sectors"
and it worked! the next sector is now decrypted correctly, and the sector after that as well. it seems we decrypted the disk successfully 😊🎉 side-by-side hexdumps again. the contents now match perfectl
file(1) confirms that our decrypted disk is indeed an ext4 filesystem. we mount it and... finally, data! same terminal as before, with:  # file -s --dereference /dev
this is nice BUT we are only halfway there: we undid metadata encryption, but the files (contents and filenames) are still encrypted by FBE 👇 # tree -Cash data/app  we can see that the filenames are enc
whew, this is probably the longest Twitter thread I've written in my life. I'll take a break now
OKAY LET'S GO
remember this is an actual live tweet, I'm going in a bit blind, hopefully I won't mess up too much
so, the first priority is to decrypt the filenames. then we'll get to the file contents.

if we look at the disk we've mounted, there are some folders scattered around which have the same files that vold/metadata_encryption/key had: # tree -Cash data | grep -C10 encrypted_key  we see three fo
these folders are:

📂 unencrypted/key
📂 misc/vold/user_keys/de/0
📂 misc/vold/user_keys/ce/0/current

logically we want to start with unencrypted, which will cover all files not belonging to a user.

I've checked and 'version' & 'stretching' match the metadata encryption key
sorry, I spent these two hours improving the python scripts I posted earlier (gist.github.com/mildsunrise/58…) so that it automatically decrypts a Vold key given its folder.

look how easy it is to get the metadata key! my future me will be grateful 😊 # ./utils/tools/vold.py metadata/vold/metadata_encryption/ke
using this script, I decrypted all 3 keys I posted above. now that we have them, let's try decrypting some filenames, shall we?

the filenames seem to be a variant of Base64 that uses no padding and replaces "," with "/". it was originally made for filenames in IMAP mailboxes!
and according to the documentation of FBE that I mentioned at the start (source.android.com/security/encry…), the defaults are:

- aes-256-xts (for file contents, same as metadata encryption)
- aes-256-cts (for filenames)
there's also the encryption policies, which determine

1. the function used to derive the encryption key (from, among other things, the Vold key and the user's password AFAIU)

2. which folders get encrypted

there's v1 and v2. v2 is the default on Android 11+, so that's the one
also according to the docs, by default each file gets its *own* derivated key.

and the encryption of both filenames and file contents is performed by the kernel, in a module called fscrypt: kernel.org/doc/html/lates…
my kernel has fscrypt enabled, so we're good to go! I just have to find how the per-file key derivation works, and what the encryption policy is, and then I can use the fscrypt(1) command to feed the keys to the kernel! I ran fscrypt and it showed me the help string. it has "
so... it seems the kernel also performs per-file key derivation, and handles v1/v2 encryption policies. the only thing done in userspace is feeding a master key for each "encrypted directory tree" through an FS_IOC_ADD_ENCRYPTION_KEY ioctl
also fscrypt is a high level tool, the low-level tool is fscryptctl, which issues these ioctls for us.

and the encryption policies are persisted in the inodes of the FS, so we can query them with fscryptctl, and we see they reference a master key through a long hex ID: # fscryptctl get_policy data/app/ Encryption policy for data
so really, all that's left to do is deriving the master keys from the keys stored by Vold (which we already have) and the user's password, then, we add them with the right IDs and the files should magically appear decrypted
it gets even more simple. the Vold keys *are* the master keys, there's no derivation going on. I literally just have to add them... # ./utils/tools/vold.py --raw ./data/misc/vold/user_keys/de/
and the data becomes accessible 🎉 # tree -Cash data  which now shows decrypted filenames. fina
I thought FBE would be complicated but no, it really is just that easy. gotta be thankful to Google for putting so much effort into upstreaming everything, I guess
however, no, the thread doesn't end here. there's one last missing thing I've been hiding this whole time, sorry. 😔

if I try to read any encrypted file, I read garbage. why? hexdump of the file data/system/appops.xml showing garbage
remember this tweet? it says "except the blocks belonging to contents of files covered by FBE".

Android uses a module called dm-default-key that does exactly that; it only acts on blocks where the lower layer didn't specify a key.

However dm-default-key is precisely one of the things in Android kernels that have not been upstreamed to Linux. Maybe because metadata encryption is recent. So, I used dm-crypt instead.
dm-crypt is equivalent to dm-default-key, except for the blocks belonging to encrypted files, where dm-crypt will encrypt with the metadata key nevertheless.

So what do we do? We could build the Android common kernel and run it in a VM, or use UML android.googlesource.com/kernel/common
But that's clunky, so I think I'll attempt to build dm-default-key for my kernel. AFAIK, all the subsystems it depends on (blk_crypto, device mapper) are already upstreamed, so I should be able to just copy the file over and little else android.googlesource.com/kernel/common/…
sadly no, it needs to hook up some other places. so I can't build a module, I'll have to build a whole patched kernel and boot into it.

this is the patch I need to apply: android.googlesource.com/kernel/common-…

• • •

Missing some Tweet in this thread? You can try to force a refresh
 

Keep Current with Alba 🌸

Alba 🌸 Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

PDF

Twitter may remove this content at anytime! Save it as PDF for later use!

Try unrolling a thread yourself!

how to unroll video
  1. Follow @ThreadReaderApp to mention us!

  2. From a Twitter thread mention us with a keyword "unroll"
@threadreaderapp unroll

Practice here first or read more on our help page!

More from @mild_sunrise

15 Oct
why is this so bad? that .sys file would come with the game, so the only consequence is that cheaters have extra time to reverse engineer it. right? am I missing anything?
my understanding was that DRM or anticheats were supposed to be updated frequently enough that reverse engineering them isn't worth it. isn't that the case?
exactly, it's just a head start. weak obfuscation is an independent problem
Read 5 tweets
5 Aug
✨ Thread about UNIX, open() & the filesystem ✨

I think everyone can benefit from a more thorough understanding of FS, even if not applicable to Windows because Windows is shit

you can avoid races, bugs & edge cases
so. you might have heard how in UNIX "everything is a file". you have many kind of files. what we usually understand by 'file' is called 'regular file', but directories are another kind of file
files do NOT have names. directories give names to files.

just like a regular file is a "tape" of bytes that allows random access, a directory is a table of *pointers* to other files, where each pointer has a name
Read 19 tweets
5 Aug
github pages caido en @movistar_es otra vez, alguien puede confirmar?
@movistar_es las dos CDNs de fastly más importantes (github pages y githubusercontent.com) caídas. las IPs no son accesibles...
@movistar_es please @github @movistar_es these downtimes happen constantly, please fix them

in the meanwhile you can point to origin directly
140.82.121.13 *.githubusercontent.com
140.82.121.11 *.github.io
Read 4 tweets
4 Aug
los dramas me recuerdan lo fácil que es que 2 o más personas se hieran y luego no tengan la empatía o habilidad suficiente como para arreglarlo. es deprimente pensar en lo altas que son las posibilidades de que las cosas se tuerzan cuando se crean lazos
se me quitan todas las (pocas) fuerzas que tengo para socializar, de verdad
también tenemos muy idealizada la mediación, hay muchos casos en que simplemente *no se puede* arreglar algo por mucho que se intente, solo acabas más quemada
Read 4 tweets

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3/month or $30/year) and get exclusive features!

Become Premium

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!

Follow Us on Twitter!

:(