Having fun with AndroidManifest.xml
Before w will get back to Sandrorat let's have some fun. It's weekend after all, or rather it was weekend when I wrote this post. Everyone that ever tried to see the insides of the APK file knows what's an AndroidManifest.xml file is. Some even know that, inside the APK, it's not a real XML, but rather a compressed, binary form of an original XML.
This compressed form was first abused in the Android.OBAD malware. Compressed form of an XML differs with the real XML in at least one key aspect, hidden in the String Pool. Let's have a look at a compressed AndroidManifest.xml file of the previously mentioned Sandrorat sample and see if we can do something interesting with it. If you want to skip directly to the fun part, just ignore the next section.
Binary XML layout
Binary XML file starts with an XML header which is two words long (when I write "word" I mean 4 bytes, because I still live in early 2000s). Below is a hexdump of the Sandrorat AndroidManifest.xml, provided for your convenience. First two bytes are kind of a "magic number" for XML (which is 03 00 and is marked in red). Next two bytes are a header size (which is always 8, marked in green) and next word is the XML size, both in little endian convention (marked in blue). In fact every number in this format sticks to the little endian convention. After that header the actual data starts.
00000000: 03 00 08 00 c8 1c 00 00 01 00 1c 00 ec 0d 00 00
00000010: 41 00 00 00 00 00 00 00 00 00 00 00 20 01 00 00
00000020: 00 00 00 00 00 00 00 00 1a 00 00 00 34 00 00 00
00000030: 52 00 00 00 76 00 00 00 82 00 00 00 9c 00 00 00
00000040: a8 00 00 00 b6 00 00 00 c4 00 00 00 d6 00 00 00
00000050: e8 00 00 00 40 01 00 00 44 01 00 00 56 01 00 00
00000060: 6a 01 00 00 94 01 00 00 9e 01 00 00 b2 01 00 00
Next chunk of the binary XML is the String Pool (magic number is 01 and header size is always 1c). Then file contains the number of strings (marked in orange) and, after some additional data, the table of string offsets starts (purple). Each word is a relative offset to the string data (relative to the end of String Index Table, so first offset will usually be 0). String data uses a simple encoding: first two bytes are the string length and then there is a null-terminated, UTF-16 encoded string. String length is the actual length of the string, not the number of bytes. Just like shown below (in red).
00000120: 34 0c 00 00 80 0c 00 00 0b 00 76 00 65 00 72 00 4.........v.e.r.
00000130: 73 00 69 00 6f 00 6e 00 43 00 6f 00 64 00 65 00 s.i.o.n.C.o.d.e.
00000140: 00 00 0b 00 76 00 65 00 72 00 73 00 69 00 6f 00 ....v.e.r.s.i.o.
00000150: 6e 00 4e 00 61 00 6d 00 65 00 00 00 0d 00 6d 00 n.N.a.m.e.....m.
OK, this was boring and I promised you fun. What comes next is really interesting. It's called Resource Map (magic number 80 01) and contains a table of resource identifiers for some of the strings. Every word is a resource ID for the string. First resource ID is for the first string in the String Pool and so on. So now we have both an actual string data and this string ID. Mapping between resource ID and an actual string is also kept in the public.xml file of the AOSP.
Now, do you think that Android checks the string data if it has a resource ID? Or just simply ignores whatever string data says and just assumes it knows the string content by looking at its ID? If you think it's the latter, you're right.
Trolling the researchers
A quick recap for those of you who skipped the last section. Binary XML sometimes contains both the actual string (e.g. versionName) and its identifier (e.g. 0x0101021c). Android usually does not care about the actual string, if the identifier is present. This was used by the somewhat famous OBAD malware, which made it to extreme and just removed the unnecessary strings. This action lead to this beautiful part of the AndroidManifest file (decoded with an apktool 1.5.2):
<receiver ="System" =".Illlljlj" ="android.permission.BIND_DEVICE_ADMIN">
<meta-data ="android.app.device_admin" ="@xml/jijiiijj" />
As you can see some of the attributes are simply missing. When you run aapt on it, it becomes more clear what happened:
E: receiver (line=47)
A: :(0x01010001)="System" (Raw: "System")
A: :(0x01010003)=".Illlljlj" (Raw: ".Illlljlj")
A: :(0x01010006)="android.permission.BIND_DEVICE_ADMIN" (Raw: "android.permission.BIND_DEVICE_ADMIN")
When you look up the first ID (0x01010001) you find that it means label. We can do so with other resource IDs.
OK, now let's have some fun with it. Removing strings is confusing for researchers, but it is more or less a known technique. Let's move to something more confusing. Just by a simple variable substitution we can get this amazing AndroidManifest snippet, decoded by the apktool 1.5.2:
So, what happened here and why are there two different package names? When we have a look at aapt output we can deduce what happened:
A: android:versionCode(0x0101021b)=(type 0x10)0x1
A: android:versionName="1.0" package(0x0101021c)="com.acme.app" (Raw: "com.acme.app")
A: package="com.zero1.sandrorat" (Raw: "com.zero1.sandrorat")
I just changed the versionName to versionName="1.0" package and 1.0 to com.acme.app (nobody looks at the version name anyhow). While this can be confusing a lot, it still installs without any problems. If your workflow includes apktool + some XML parsing, you may now need to rethink that approach :)
There are endless opportunities for confusion! Change enabled to disabled or readPermission to writePermission!
So now you think: well, this is all fun and games, but I can still use aapt and see what's up. What would happen then when I change versionName to versionName\r? Since you're most probably not redirecting output to file aapt will "perform" a carriage return and you will see an incomplete log:
A: android:versionCode(0x0101021b)=(type 0x10)0x1
(0x0101021c)="com.acme.app" (Raw: "com.acme.app")
It gets even more interesting when you change versionName to versionName\x1b. At least on my terminal this resets the buffer, which makes the begging of the aapt output unreadable.
Breaking things
Trolling can be fun, but let's start breaking things. We won't be breaking anything major. Do you remember when I mentioned that the string should be null-terminated and preceded by it's length? What if we were to manipulate this size?
First, let's try to set size to some very big number. A number bigger than the size of the whole AndroidManifest. This leads to the errors in apktool and aapt. When I tried to install this app, the emulator restarted. So I tried it again, but it restarted again. So it seems that Android tries to read strings, but just doesn't care about it's value.
DO try this at home
Do you have some ideas that you want to try out? Like using the Unicode control characters (e.g. right-to-left mark). Or maybe you just want to make sure that what I'm writing here is true. Either way, you can try it for yourself. I wrote a small PoC tool (available in my github repo). Options are pretty self-explanatory (just run the tool with -h). However, the config file can be a little tricky. Configuration is stored in the config.py and is in the form of a dictionary, like the one outlined below (which produces the two different package entries):
config = {
'Manifest':
{
u'versionName': {'value':
'v\x00e\x00r\x00s\x00i\x00o\x00n\x00N\x00a\x00m\x00e\x00=\x00"\x001\x00.\x000\x00"\x00 \x00p\x00a\x00c\x00k\x00a\x00g\x00e\x00'},
u'1.0': {'value':
'c\x00o\x00m\x00.\x00a\x00c\x00m\x00e\x00.\x00a\x00p\x00p\x00\x00\x00',
'check_resource_id': False}
}
Details of the configuration are in the tool's readme file. Have fun!
UPDATE: Samples for your pleasure
I've started another repo on GitHub, which will be filling with obfuscation samples including the ones mentioned in this and previous post. Enjoy!
Comments
Post a Comment