Saturday, January 26, 2013

string decryption with dex2jar

i have been getting a lot of questions about string decryption lately, so let's talk.

let's say you have an app and notice encrypted strings. strings are an easy way to get a basic idea of what code is doing so naturally you want to decrypt them. but how? there are many different ways to encrypt strings and then decrypt at runtime but in practices there are some assumptions we can make in decreasing order of likelihood.

1. the encryption must be reversible. the strings must be decrypted at run time somehow. this is good but we can assume even more.

2. the process is automated. when Alice wants to release her app she puts the source code through an automated modification process which iterates over every string literal, encrypts it and replaces it with a call to a decryption method with the encrypted string as a parameter.

3. decryption is the same or nearly the same for each string. there is only one decryption method.

4. the type signature of the method is:
static String decryptMethod(String)

while these assumptions hold, it is not very difficult to create a general technique by which we can decrypt all of the strings of an app in place. the real question is do you want to do it at the java or smali level? if you primarily look at decompiled code you can work at the java level. and you're in luck, such a tool already exists in dex2jar: http://code.google.com/p/dex2jar/

there is a wiki article about it here: http://code.google.com/p/dex2jar/wiki/DecryptStrings but it is unfinished. you can at least get a visual for what the decompiled code will look like before and after. if you're a good person, you will update the wiki. i leave that as a task for some good reader.

the tool is currently incorrectly spelled as d2j-decrpyt-string.(sh|bat). it takes at least two parameters and sometimes needs three. they are:
  1. method name, -mn : in our case, decryptMethod
  2. method owner, -mo : let's say com.alice.utils
  3. class path, -cp : if decryptMethod makes use of any framework api, you will need to give the path to a framework.jar from the android.sdk
d2j-decrpyt-string -mn decryptMethod -mo com.alice.utils -cp ~/android/sdk/platforms/android-4/framework.jar

doing this at the smali level requires access to a dalvik vm, so in that regard it is trickier, but there are many emulators and you can even use your phone. here's how the process can work:
  • pull out all of the strings and put into a file
  • write some java code, unless you're comfortable with smali, to open the file and iterate over each line and call the decryption method on each string.
  • compile java bytecode and convert to dalvik executable with dx from the android sdk
  • run the code on a dalvik vm

Tuesday, April 10, 2012

android reverse tools - ART

here's a cool tool i was shown the other day. it's an easy-mode gui for all your decompiling and recompiling needs. ordinarily this would be rather unimpressive. it's not too hard to write a little wrapper for some java commands, but he/she really put some polish on this.

not only does it come with everything you need, including java and bits of the android sdk, but it even has a slick manual and a complete walk-through for my lesson0.crackme0.

here's a screen shot of the app:
 

here's the link to download (24mb): http://ul.to/or3kme6t
virus scan: https://www.virustotal.com/file/f6ac4279161b666811d80736a7a23790709c5b3ccb36a8f83dd138d9601eb480/analysis/1334082130/

as a first exercise, i recommend that you update the apktool included with the pack. it may have gone out of date. you can update the other components if you're so inclined but it might not help much.

if you have any trouble decompiling or compiling, remember it's using apktool under the hood so trouble shoot apktool first.

and if you want some more crackmes to try, here's deurus' profile on crackmes.de: http://crackmes.de/users/deurus

Wednesday, January 18, 2012

self-keygen tutorial by synack

previously, i posted a keygen tutorial and challenge, and synack has put his solution forward along with an interesting tutorial on creating a self-keygen, or a keygen that makes use of the original code to do the heavy lifting. it's very clearly written and organized, with plenty of good insight into the thought process as opposed to low-level "click here change this with no idea why" style tutorials.

download the solution and tutorial here: http://www.mediafire.com/?wzc45p269bcpbr5

Saturday, January 14, 2012

keygen tutorial and challenge by zAWS!

keygen'ing as a style of cracking requires you to really know and understand how the protection works. it also requires no modification of the original application, so is a very pure way of cracking.

zAWS!, who posted a keygen for lesson 0's crackme0b, sent me this challenge to share:
http://www.mediafire.com/?5ik1659n1k514f9


but before you start, you may want to see his/her keygen tutorial. it comes with the original apk and has videos showing the process:
http://www.mediafire.com/?ckvuiadh5cegl9l

Friday, January 6, 2012

way of the android cracker 0 rewrite

i have learned a lot since first writing way of the android cracker so i rewrote it. actually i rewrote it about 17 times. this one was the least annoyingly pedantic.

here's a direct link: http://www.mediafire.com/download.php?5ybhkqbzwguubf3

lesson 1 rewrite is next.

update: crackme0b has been updated. thanks to zAWS! for writing a keygen for crackme0b so quickly (even copied the icon) and helping me see it. unfortunately the update breaks the keygen.

Thursday, October 27, 2011

antilvl 1.4.0

it's been a while, but antilvl 1.4.0 is finally released. i did not plan to make another release, but there were some show-stopping bugs in the linux version and some other things that were just embarrassing. :D

major changes include:
  • option to use your own signatures
  • more control over which fingerprints are used
  • support for verizon drm
  • a few new anti-tampering checks are known
  • some fixes in how fingerprints were applied

you can read more / download here: http://androidcracking.blogspot.com/p/antilvl_01.html

Monday, October 17, 2011

protection using checksums and key / unlocker apps

if an app requires an unlocker key app, it's likely there will be protection hidden in the key. perhaps the key performs a checksum on the main app, or the key stores pre-computed checksum values for the main app. this is easy to add if you're a developer and is somewhat tricky to handle as a cracker. the reason is the cracker must know how to calculate the checksums himself and inject those values into the app.

calculating checksums is a way to determine if your apk has been modified. there are at least four easy methods to do this. they are: md5, sha1, crc32 and adler32. once you run these guys on your apk you will have either a long number or a byte array. of course, you can't store the checksum in your main app since you wont know the checksum until the app is finished. for this reason, either the calculations or just the checksum values must be stored in another app signed by the same certificate, such as a key / unlocker app.

here's some example code of what the protection may look like in java:
// Copied directly and made private from R.java of Key App
// After creating strings..
private static final int tt_crc32=0x7f040002;
private static final int tt_md5=0x7f040004;

public static TestResult checkCRC32ChkSum() {
  // Get path to our apk on the system.
  String apkPath = Main.MyContext.getPackageCodePath();
  Long chksum = null;
  try {
    // Open the file and build a CRC32 checksum.
    // You could also use Adler32 in place of CRC32.
    FileInputStream fis = new FileInputStream(new File(apkPath));
    CRC32 chk = new CRC32();
    
    CheckedInputStream cis = new CheckedInputStream(fis, chk);
    byte[] buff = new byte[80];
    while ( cis.read(buff) >= 0 ) ;
    
    chksum = chk.getValue();
  } catch (Exception e) {
    e.printStackTrace();
  }

  // After creating your apk, calculate the crc32 checksum
  // and store it as a string value in the KEY / unlock App.
  String keyStrVal = getKeyString(tt_crc32);
  
  // Key is not installed.
  if ( keyStrVal.equals("") ) {
    // Key not installed. Validation failure.
  }
  
  Long chksumCmp = Long.parseLong(keyStrVal);
  
  if ( chksum.equals(chksumCmp) ) {
    // Success. Checksum stored in key matches the
    // checksum we just created. We can assume APK
    // is not modified. Kinda.
  }
  else {
    // Validation failure! Apk has been modified.
  }
}

public static TestResult checkMD5ChkSum() {
  // Do pretty much the exact same thing here,
  // except instead of a CRC32 checksum, we'll be
  // using an MD5 digest. You could also use SHA1.
  // Any cracker worth his salt will immediately recognize
  // CRC32 and MD5 keywords and know them to be checksum
  // attempts. Using Adler32 or SHA1 may put them off.
  String apkPath = Main.MyContext.getPackageCodePath();
  MessageDigest msgDigest = null;
  byte[] digest = null;
  try {
    msgDigest = MessageDigest.getInstance("MD5");
  } catch (NoSuchAlgorithmException e1) {
    e1.printStackTrace();
  }
  
  byte[] bytes = new byte[8192];
  int byteCount;
  FileInputStream fis = null;
  
  try {
    fis = new FileInputStream(new File(apkPath));

    while ((byteCount = fis.read(bytes)) > 0)
      msgDigest.update(bytes, 0, byteCount);
    
    digest = msgDigest.digest();
  } catch (Exception e) {
    e.printStackTrace();
  }

  String keyStrVal = getKeyString(tt_md5);
  // Key is not installed.
  if ( keyStrVal.equals("") ) {
    // Key not installed. Validation failure.
  }

  // Using Base64 encoding is just a lazy way to store byte arrays.
  // You -could- also embed the values in the code of the Apk
  // Read more here:
  // http://stackoverflow.com/questions/2721386/android-how-to-share-code-between-projects-signed-with-the-same-certificate
  if ( Arrays.equals(Base64.decode(keyStrVal, Base64.DEFAULT), digest) )
    // Validated
  else
    // Apk has been modified
}


private static String getKeyString(int resId) {
  // You will need this to retrieve the stored checksums from the KEY App.
  String result = "";
  
  try {
    Context c = Main.MyContext.createPackageContext("your.package.name.key", Context.CONTEXT_IGNORE_SECURITY);
    result = c.getString(resId);
  } catch (Exception e) {
    Console.log("Error while getting key string:\n" + e);
    e.printStackTrace();
    result = "";
  }
  
  return result;
}


and here's what the above java looks like in smali, so you have some idea what to look for. cracking this will require you to write an app that simulates how the app calculates the checksum. then you'll have to come up with a clever way to get the value into the smali. if it's a long number, this is fairly easy, but if it's a byte array, you may need to use base64 encoding or some other method to make a byte array safe for literal strings. the keywords to look for, of course, are md5, sha1, crc32 and adler32. they may not appear at all if the developer is using reflection to make the method calls.
.method public static checkCRC32ChkSum()V
    .locals 11

    .prologue
    .line 934
    sget-object v9, Lcom/lohan/testtarget/Main;->MyContext:Landroid/content/Context;

    invoke-virtual {v9}, Landroid/content/Context;->getPackageCodePath()Ljava/lang/String;

    move-result-object v0

    .line 935
    .local v0, apkPath:Ljava/lang/String;
    const/4 v3, 0x0

    .line 939
    .local v3, chksum:Ljava/lang/Long;
    :try_start_0
    new-instance v7, Ljava/io/FileInputStream;

    new-instance v9, Ljava/io/File;

    invoke-direct {v9, v0}, Ljava/io/File;->(Ljava/lang/String;)V

    invoke-direct {v7, v9}, Ljava/io/FileInputStream;->(Ljava/io/File;)V

    .line 940
    .local v7, fis:Ljava/io/FileInputStream;
    new-instance v2, Ljava/util/zip/CRC32;

    invoke-direct {v2}, Ljava/util/zip/CRC32;->()V

    .line 942
    .local v2, chk:Ljava/util/zip/CRC32;
    new-instance v5, Ljava/util/zip/CheckedInputStream;

    invoke-direct {v5, v7, v2}, Ljava/util/zip/CheckedInputStream;->(Ljava/io/InputStream;Ljava/util/zip/Checksum;)V

    .line 943
    .local v5, cis:Ljava/util/zip/CheckedInputStream;
    const/16 v9, 0x50

    new-array v1, v9, [B

    .line 944
    .local v1, buff:[B
    :cond_0
    invoke-virtual {v5, v1}, Ljava/util/zip/CheckedInputStream;->read([B)I

    move-result v9

    if-gez v9, :cond_0

    .line 946
    invoke-virtual {v2}, Ljava/util/zip/CRC32;->getValue()J

    move-result-wide v9

    invoke-static {v9, v10}, Ljava/lang/Long;->valueOf(J)Ljava/lang/Long;
    :try_end_0
    .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0

    move-result-object v3

    .line 953
    .end local v1           #buff:[B
    .end local v2           #chk:Ljava/util/zip/CRC32;
    .end local v5           #cis:Ljava/util/zip/CheckedInputStream;
    .end local v7           #fis:Ljava/io/FileInputStream;
    :goto_0
    const v9, 0x7f040002

    invoke-static {v9}, Lcom/lohan/testtarget/PerformTestsTask;->getKeyString(I)Ljava/lang/String;

    move-result-object v8

    .line 956
    .local v8, keyStrVal:Ljava/lang/String;
    const-string v9, ""

    invoke-virtual {v8, v9}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

    .line 960
    invoke-static {v8}, Ljava/lang/Long;->parseLong(Ljava/lang/String;)J

    move-result-wide v9

    invoke-static {v9, v10}, Ljava/lang/Long;->valueOf(J)Ljava/lang/Long;

    move-result-object v4

    .line 962
    .local v4, chksumCmp:Ljava/lang/Long;
    invoke-virtual {v3, v4}, Ljava/lang/Long;->equals(Ljava/lang/Object;)Z

    .line 970
    return-void

    .line 947
    .end local v4           #chksumCmp:Ljava/lang/Long;
    .end local v8           #keyStrVal:Ljava/lang/String;
    :catch_0
    move-exception v9

    move-object v6, v9

    .line 948
    .local v6, e:Ljava/lang/Exception;
    invoke-virtual {v6}, Ljava/lang/Exception;->printStackTrace()V

    goto :goto_0
.end method

.method public static checkMD5ChkSum()V
    .locals 12

    .prologue
    const/4 v11, 0x0

    .line 981
    sget-object v10, Lcom/lohan/testtarget/Main;->MyContext:Landroid/content/Context;

    invoke-virtual {v10}, Landroid/content/Context;->getPackageCodePath()Ljava/lang/String;

    move-result-object v0

    .line 982
    .local v0, apkPath:Ljava/lang/String;
    const/4 v9, 0x0

    .line 983
    .local v9, msgDigest:Ljava/security/MessageDigest;
    const/4 v3, 0x0

    check-cast v3, [B

    .line 985
    .local v3, digest:[B
    :try_start_0
    const-string v10, "MD5"

    invoke-static {v10}, Ljava/security/MessageDigest;->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;
    :try_end_0
    .catch Ljava/security/NoSuchAlgorithmException; {:try_start_0 .. :try_end_0} :catch_0

    move-result-object v9

    .line 990
    :goto_0
    const/16 v10, 0x2000

    new-array v2, v10, [B

    .line 992
    .local v2, bytes:[B
    const/4 v6, 0x0

    .line 995
    .local v6, fis:Ljava/io/FileInputStream;
    :try_start_1
    new-instance v7, Ljava/io/FileInputStream;

    new-instance v10, Ljava/io/File;

    invoke-direct {v10, v0}, Ljava/io/File;->(Ljava/lang/String;)V

    invoke-direct {v7, v10}, Ljava/io/FileInputStream;->(Ljava/io/File;)V
    :try_end_1
    .catch Ljava/lang/Exception; {:try_start_1 .. :try_end_1} :catch_2

    .line 997
    .end local v6           #fis:Ljava/io/FileInputStream;
    .local v7, fis:Ljava/io/FileInputStream;
    :goto_1
    :try_start_2
    invoke-virtual {v7, v2}, Ljava/io/FileInputStream;->read([B)I

    move-result v1

    .local v1, byteCount:I
    if-gtz v1, :cond_0

    .line 1000
    invoke-virtual {v9}, Ljava/security/MessageDigest;->digest()[B
    :try_end_2
    .catch Ljava/lang/Exception; {:try_start_2 .. :try_end_2} :catch_1

    move-result-object v3

    move-object v6, v7

    .line 1005
    .end local v1           #byteCount:I
    .end local v7           #fis:Ljava/io/FileInputStream;
    .restart local v6       #fis:Ljava/io/FileInputStream;
    :goto_2
    const v10, 0x7f040004

    invoke-static {v10}, Lcom/lohan/testtarget/PerformTestsTask;->getKeyString(I)Ljava/lang/String;

    move-result-object v8

    .line 1007
    .local v8, keyStrVal:Ljava/lang/String;
    const-string v10, ""

    invoke-virtual {v8, v10}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

    .line 1015
    invoke-static {v8, v11}, Landroid/util/Base64;->decode(Ljava/lang/String;I)[B

    move-result-object v10

    invoke-static {v10, v3}, Ljava/util/Arrays;->equals([B[B)Z

    .line 1020
    return-void

    .line 986
    .end local v2           #bytes:[B
    .end local v6           #fis:Ljava/io/FileInputStream;
    .end local v8           #keyStrVal:Ljava/lang/String;
    :catch_0
    move-exception v10

    move-object v5, v10

    .line 987
    .local v5, e1:Ljava/security/NoSuchAlgorithmException;
    invoke-virtual {v5}, Ljava/security/NoSuchAlgorithmException;->printStackTrace()V

    goto :goto_0

    .line 998
    .end local v5           #e1:Ljava/security/NoSuchAlgorithmException;
    .restart local v1       #byteCount:I
    .restart local v2       #bytes:[B
    .restart local v7       #fis:Ljava/io/FileInputStream;
    :cond_0
    const/4 v10, 0x0

    :try_start_3
    invoke-virtual {v9, v2, v10, v1}, Ljava/security/MessageDigest;->update([BII)V
    :try_end_3
    .catch Ljava/lang/Exception; {:try_start_3 .. :try_end_3} :catch_1

    goto :goto_1

    .line 1001
    .end local v1           #byteCount:I
    :catch_1
    move-exception v10

    move-object v4, v10

    move-object v6, v7

    .line 1002
    .end local v7           #fis:Ljava/io/FileInputStream;
    .local v4, e:Ljava/lang/Exception;
    .restart local v6       #fis:Ljava/io/FileInputStream;
    :goto_3
    invoke-virtual {v4}, Ljava/lang/Exception;->printStackTrace()V

    goto :goto_2

    .line 1001
    .end local v4           #e:Ljava/lang/Exception;
    :catch_2
    move-exception v10

    move-object v4, v10

    goto :goto_3
.end method

.method private static asdfgetKeyString(I)Ljava/lang/String;
    .locals 6
    .parameter "resId"

    .prologue
    .line 1025
    const-string v2, ""

    .line 1028
    .local v2, result:Ljava/lang/String;
    :try_start_0
    sget-object v3, Lcom/lohan/testtarget/Main;->MyContext:Landroid/content/Context;

    const-string v4, "your.package.name.key"

    const/4 v5, 0x2

    invoke-virtual {v3, v4, v5}, Landroid/content/Context;->createPackageContext(Ljava/lang/String;I)Landroid/content/Context;

    move-result-object v0

    .line 1029
    .local v0, c:Landroid/content/Context;
    invoke-virtual {v0, p0}, Landroid/content/Context;->getString(I)Ljava/lang/String;
    :try_end_0
    .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0

    move-result-object v2

    .line 1036
    .end local v0           #c:Landroid/content/Context;
    :goto_0
    return-object v2

    .line 1030
    :catch_0
    move-exception v3

    move-object v1, v3

    .line 1031
    .local v1, e:Ljava/lang/Exception;
    new-instance v3, Ljava/lang/StringBuilder;

    const-string v4, "Error while getting key string:\n"

    invoke-direct {v3, v4}, Ljava/lang/StringBuilder;->(Ljava/lang/String;)V

    invoke-virtual {v3, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/Object;)Ljava/lang/StringBuilder;

    move-result-object v3

    invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v3

    invoke-static {v3}, Lcom/lohan/testtarget/Console;->log(Ljava/lang/String;)V

    .line 1032
    invoke-virtual {v1}, Ljava/lang/Exception;->printStackTrace()V

    .line 1033
    const-string v2, ""

    goto :goto_0
.end method