Hot on the heels of the so-called “master key” hole in Android comes what Chinese Android researchers are calling “a similar vulnerability.”
They’ve definitely found a bug, and an another embarrassing one for Google’s coders, too.
But is it really a similar vulnerability? How does this one work?
Understanding the bug
The bug is a signed-unsigned integer mismatch.
I’ll try to explain by using diagrams.
As we mentioned before, Android apps are installed using APK files (Android packages), which are just ZIP files with a different extension and some specially-named files inside.
ZIP archives consist of a header, a sequence of file objects, and a central directory, though the central directory is at the end of the file, not in the middle.
Each file object looks something like this:
→ ZIP files are little-endian, meaning that multibyte numbers are stored with the least-significant byte first. So the compressed size of 11,141 bytes above translates into a 32-bit (4-byte) hex number as 0x00002B85, which is stored in the file as 85 2B 00 00. The “magic number” isn’t really a number, it’s a text string: PK is the late Phil Katz, inventor of the file format.
The actual byte values in Fig 1 are taken from a sample app from the Android Game Frame project. I chose this app simply for the modest size of its APK. (Thanks, guys!)
The file in question is classes.dex, the Android bytecode compiled from the app’s Java source.
This is what actually executes on top of Android when the app is launched.
Between the filename and the raw file data (a compressed version of the DEX file) is what the ZIP format calls an “extra field”, used to store arbitrary metadata about the file.
Here, the extra field length (X) is zero, so the extra field itself is zero bytes long, i.e. absent.
The researchers noticed that while Android is verifying an APK, the package is traversed using Java-based utility code for managing ZIP archives.
This code treats the sizes stored in fields F and X as signed 16-bit integers.
But that code that actually loads the DEX file from the package treats these lengths correctly, as unsigned 16-bit integers.
In other words, patching in an extra field length of 0xFFFD, as shown in Fig 2, creates an ambiguous APK.
Treated as unsigned, 0xFFFD translates into 65533 in decimal; as a signed integer, it comes out as -3.
→ 16-bit integers “count” as follows:
0x0000 Unsigned = 0 / Signed = 0 0x0001 Unsigned = 1 / Signed = 1 0x0002 Unsigned = 2 / Signed = 2 ...... 0x7FFE Unsigned = 32,766 / Signed = 32,766 0x7FFF Unsigned = 32,767 / Signed = 32,767 0x8000 Unsigned = 32,768 / Signed = -32,768 0x8001 Unsigned = 32,769 / Signed = -32,767 ...... 0xFFFD Unsigned = 65,533 / Signed = -3 0xFFFE Unsigned = 65,534 / Signed = -2 0xFFFF Unsigned = 65,535 / Signed = -1
So, when the file is checked to validate the cryptographic checksum of the classes.dex file, the verifier treats X as -3.
Instead of jumping forwards in the file as you might expect, it “skips” the extra field by jumping backwards three bytes.
As a result, it reads in the last three bytes of the filename and what comes after it in the extra field, as shown in Fig 3, instead of skipping the extra field and reading the file data that follows.
But when the APK is loaded, 65533 bytes of extra field are skipped forwards, because X is correctly handled as an unsigned number, as shown in Fig 4.
That’s a bug in any language, and an discomfiting one for Google, whose security teams will surely consider this an elementary mistake that ought to have been caught in testing, if not during code review.
Exploiting the flaw
The researchers suggest that you can exploit the verifier’s signed/unsigned U-turn in the file because the characters dex at the end of the filename just happen to match the identification signature bytes found at the start of every DEX bytecode file.
So, they say, all you have to do is to decompress the original classes.dex file from the APK, pad it out to 65536 bytes in length (3 + 65533), and drop it into your hacked APK so it overlaps the end of the filename: the red bytes shown in Fig 3.
Change X to 0xFFFD, and set the compression type to Stored so the verifier knows to take the DEX file as it finds it, and, “Bingo.”
The verifier sees the original classes.dex but the loader skips over this part and read in the attacker’s tampered DEX file.
There’s your exploit.
The earlier “master key” hole involves stashing two different versions of a file of your choice in an APK, relying on a feature that is explicitly supported, albeit not intentionally, by the ZIP format.
The “extra field” flaw involves stashing two versions of the classes.dex file, using a fault in the ZIP handling code that was definitely not intended.
As a result, the “extra field” flaw is not as generic as the “master key” bug.
In particular, any APK hacked in this way must start with a classes.dex that fits into 65536 bytes when decompressed.
That narrows things down a bit, which is a small mercy: out of 96 APK files extracted from my personal Android device, for example, 75 are ineligible for attack by this flaw.
Comparing flaws
This flaw and the recently disclosed “master key” hole are indeed of a similar sort: instead of cracking the cryptographic verifier, they simply mislead it into seeing what it expects, not what will later actually be loaded.
Both attacks involve stashing both clean and hacked versions of the same file object inside a modified APK.
→ This is not a new trick: malware has used anti-anti-virus subterfuges of this sort, known as stealth, since the earliest days. In fact, the first recorded PC virus, Brain, replaced the boot sector of your startup disks, but kept the original copy to show you if ever you went looking for signs of infection. That was back in the mid-1980s.
What next?
Google has published patches already, under the laconic comment "Values in ZIP files are unsigned."
Of course, Google recently announced that seven days is an achievable timeframe for responding to vulnerability reports, down from the 60 days it accepted before.
Although Google has indeed responded quicklly by patching both holes, and should be commended for its efficiency, that doesn’t get the fixes out into the wider world.
It remains to be seen how hard Mountain View will lean on its many handset licensees to push out firmware updates for the “extra field” and “master key” flaws, since they go to the heart of application verification on the Android platform.
Ok Google released a patch but how can I apply this patch ?
It's hard for Android users to apply patches. Maybe we should wait for CyanogenMod ROM to pull this fixes from AOSP and release a new version of the ROM.
Maybe the end-users should wait for Android 5.0 to fix this vulnerability.
Is there a way to prevent this vuln.?
AFAIK (but don't quote me on this 🙂 the Cyanogen crew have already scooped up the patches for both these flaws("master key" and "extra field").
As for handset vendors who ship devices with their own builds of Android, including Google's own devices such as the Nexoi…err…I don't have an answer, I'm afraid.
Stick to the Play Store?
(An irony since that's probably kept secure by some of the same coders 🙂
Use an anti-malware tool to block dodgy files?
(I would say that, though, wouldn't I 🙂
Actually sticking with CyanogenMod ROM is the best solution and the best ROM and the best Android experience and much much secure than OEMs
Classic example of 'testing' that doesn't test properly! That's often what comes from letting the people who wrote to code also write the testing script, complete with misconceptions and bugs that they don't realise are such – as in this example!
Tests should be written by people other than those who wrote the code, but who understand what the code is meant to do. That assumes they are using scripted auto-testing, which most houses do. As we've seen so many times, that misses the independence required of testing and shows why so many bugs get through to end users.
In jy work at an EDA software house we always did scripted auto test followed by independently scripted tests followed by manual tests by support and training people (where I joined the fun!)