Thursday, June 9, 2011

Solving Binary 300 From the Defcon 2011 Quals Using AndBug

Binary 300 was provided in the form of a memory dump, a few encrypted files, and a classes.dex. For this walkthrough, we will use AndBug and ApkTool for analysis, and python for implementing a cipher I wouldn't use to protect HBGary's address book.. Let alone my phone pr0n.

The classes.dex had an invalid checksum and version (666); updating the version to 035 and regenerating the checksum let us use dexdump to dump Dalvik pseudocode from classes.dex.

Immediately, it became obvious that this was "LokPixLite," an image-encryption app in the Android Market; we downloaded LPL from the market, compared the dex files to verify our assumption using AndroGuard, and rebuilt the dex file using "apktool d -d" and "apktool b -d" before installing into an emulator.

We then used the emulator to encrypt a few reference images with a known password, then went to the decompiled source, we see there's a rat's nest of obfuscated methods in class "g" that are packed full of references to "XOR" and "SHA1". What we need to sort this out is some context.. A trace of call flow with arguments will do nicely:
$ ./andbug trace -p com.closecrowd.lokpixlite com.closecrowd.lokpixlite.g

This command instructs AndBug to connect to LokPixLite, and use JDWP to produce METHOD_ENTRY events for every method of "g"; we then decrypt one of our images by submitting the password:
[::] setting hooks
[::] hooked com.closecrowd.lokpixlite.g
[::] hooks set
[::] thread <1> main com.closecrowd.lokpixlite.g.d()Ljava/lang/Boolean;:0
this = <obj Lcom/closecrowd/lokpixlite/g; #c1406db458>
[::] thread <1> main com.closecrowd.lokpixlite.g.b()V:0
this = <obj Lcom/closecrowd/lokpixlite/g; #c1406db458>
[::] thread <1> main com.closecrowd.lokpixlite.g.d()Ljava/lang/Boolean;:0
this = <obj Lcom/closecrowd/lokpixlite/g; #c1406db458>
[::] thread <10> password com.closecrowd.lokpixlite.g.a(Ljava/lang/String;)V:0
this = <obj Lcom/closecrowd/lokpixlite/g; #c1406db458>
p1 = test
[::] thread <10> password com.closecrowd.lokpixlite.g.e()V:0
this = <obj Lcom/closecrowd/lokpixlite/g; #c1406db458>
[::] thread <10> password com.closecrowd.lokpixlite.g.b(Ljava/lang/String;)[B:0
p0 = test
[::] thread <1> main com.closecrowd.lokpixlite.g.d()Ljava/lang/Boolean;:0
this = <obj Lcom/closecrowd/lokpixlite/g; #c1406db458>
[::] thread <10> loading com.closecrowd.lokpixlite.g.a()V:0
this = <obj Lcom/closecrowd/lokpixlite/g; #c1406db458>
[::] thread <10> loading com.closecrowd.lokpixlite.g.a()V:0
this = <obj Lcom/closecrowd/lokpixlite/g; #c1406db458>
[::] thread <10> loading com.closecrowd.lokpixlite.g.a()V:0
this = <obj Lcom/closecrowd/lokpixlite/g; #c1406db458>
[::] thread <10> loading com.closecrowd.lokpixlite.g.a([BIZ)[B:0
this = <obj Lcom/closecrowd/lokpixlite/g; #c1406db458>
p2 = 16
p1 = 830009846960
p3 = False
[::] thread <10> loading com.closecrowd.lokpixlite.g.b([BIZ)[B:0
this = <obj Lcom/closecrowd/lokpixlite/g; #c1406db458>
p2 = 16
p1 = 830009846960
p3 = False

We can see our password, "test" is submitted to b(Ljava.lang.String;) to get a [B (byte array) back, which is then stashed in a global variable. Later, we see a byte array with the encrypted data passed to b([BIZ]), which returns our plaintext image.

At this point, we have found the two key functions in the cipher -- the conversion of a password to a key, and applying the key to the ciphertext. We flip back to static analysis, at this point with our context data to analyze the code. The method teases apart pretty easily with context, resulting in the following python implementation:
def decrypt(data, key):
mask = sha1sum(key)[:8]
return strxor(data[16:], cycle(mask))

After that, it is simply a round of using strings to find all the passwords in the heap dump, and applying them in turn until we get a valid JPEG decode. From there, the password's easy to read if you squint, and it's on to the next level.

3 comments:

  1. Nice! One question, I've been unable to test AndBug on a real device... if I try to execute any of the commands I always get:
    File "/usr/local/lib/python2.6/dist-packages/andbug/proto.py", line 67, in read
    pkt = conn.recv(req)
    error: [Errno 104] Connection reset by peer

    Is there any step by step guide where I can check that everything is setup properly?

    ReplyDelete
    Replies
    1. Have you solved this question? If you have solved, would you tell me ? Thanks!

      Delete
  2. Woo hoo - my app was used as an example at Defcon :-) And I didn't even get a T-shirt...

    A couple of quick notes:

    Before the crypto-weenies complain about the simple encryption, I *know* it's weak. It's strong enough for this particular niche, and the documentation repeatedly *says* that it's only for casual use. The real reason is that I can't export strong crypto from the U.S. without an expensive export license. So the code isn't hardened against reverse-engineering simply because that would be a waste of time. Any attack is going to focus on the encrypted files, not the code. And since it's a *free* app, it's not worth the time.

    My *paid* version uses AES-CBC with a random IV, which, if the NSA hasn't been lying to us these past years, isn't vulnerable to this known-plaintext attack. Even if you have the source code, that wouldn't help decrypt an unknown file. So it's not especially hardened either, although I may add some tamper-detection in the future.

    ReplyDelete