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:

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 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 ( >= 0 ) ;
    chksum = chk.getValue();
  } catch (Exception e) {

  // 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) {
  byte[] bytes = new byte[8192];
  int byteCount;
  FileInputStream fis = null;
  try {
    fis = new FileInputStream(new File(apkPath));

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

  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:
  if ( Arrays.equals(Base64.decode(keyStrVal, Base64.DEFAULT), digest) )
    // Validated
    // 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("", Context.CONTEXT_IGNORE_SECURITY);
    result = c.getString(resId);
  } catch (Exception e) {
    Console.log("Error while getting key string:\n" + e);
    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

    .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;
    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
    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;
    .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;
    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

    .line 947
    .end local v4           #chksumCmp:Ljava/lang/Long;
    .end local v8           #keyStrVal:Ljava/lang/String;
    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

    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
    const-string v10, "MD5"

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

    move-result-object v9

    .line 990
    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;
    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
    .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;
    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
    .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;
    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

    .line 986
    .end local v2           #bytes:[B
    .end local v6           #fis:Ljava/io/FileInputStream;
    .end local v8           #keyStrVal:Ljava/lang/String;
    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;
    const/4 v10, 0x0

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

    goto :goto_1

    .line 1001
    .end local v1           #byteCount:I
    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;
    invoke-virtual {v4}, Ljava/lang/Exception;->printStackTrace()V

    goto :goto_2

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

    move-object v4, v10

    goto :goto_3
.end method

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

    .line 1025
    const-string v2, ""

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

    const-string v4, ""

    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;
    .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;
    return-object v2

    .line 1030
    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

Friday, June 10, 2011

cracking verizon's v cast apps drm

verizon has a new app store. it has an amazon-store like drm that's fairly simple to crack.

here's how it works. in the launching activity a thread is started that calls checkLicense(). this was in com/cp/app/MainActivity$2.smali:
.method public run()V
    .locals 2

    .line 100
    invoke-static {}, Landroid/os/Looper;->prepare()V

    .line 102
    iget-object v0, p0, Lcom/cp/app/MainActivity$2;->this$0:Lcom/cp/app/MainActivity;

    iget-object v0, v0, Lcom/cp/app/MainActivity;->licenseAuthenticator:Lcom/verizon/vcast/apps/LicenseAuthenticator;

    sget-object v1, Lcom/cp/app/MainActivity;->verizonAppKeyword:Ljava/lang/String;

    # call checkLicense and store result in v0
    invoke-virtual {v0, v1}, Lcom/verizon/vcast/apps/LicenseAuthenticator;->checkLicense(Ljava/lang/String;)I
    move-result v0

    .line 103
    iget-object v1, p0, Lcom/cp/app/MainActivity$2;->this$0:Lcom/cp/app/MainActivity;

    # send result of checkLicense (v0) to isDRMDone()
    invoke-virtual {v1, v0}, Lcom/cp/app/MainActivity;->isDRMDone(I)Z
    move-result v0

    # isDRMDone() handles error messages and returns true when all is good
    if-eqz v0, :cond_0

    # ... rest of code

if you look at isDRMDone() you'll see that it's basically a big switch. input of 0 or 1 counts as valid. everything else is some kind of error. so we just need to make sure checkLicense returns 1 and doesn't call anything else that may have side effects (timeouts, checking to see if verizon app store is installed, etc..).

checkLicense() is defined in com/verizon/vcast/apps/LicenseAuthenticator.smali. after modification it looks like:
.method public declared-synchronized checkLicense(Ljava/lang/String;)I
    .locals 10
    .parameter "keyword"

    # just set v0 to true and return
    # the rest of the code never runs
    const/4 v0, 0x1
    return v0

    const/16 v9, 0x64

    const-string v7, "checkLicense() finished.  Trying to shutDownLicenseService()"

    const-string v7, "LicenseAuthenticator"

    .line 256
    monitor-enter p0

    const-string v7, "LicenseAuthenticator"

    const-string v8, "begin checkLicense()"

    # ... rest of code

if you're a developer, depending on how they implement the insertion of their drm, it may still be possible to use classical protection / anti-tampering techniques. i'd like to know. but really though, don't waste your time on protection. i'm not blasting verizon or amazon or google for weak security. real effort should be spent improving the program, not slowing down (because you can't stop) crackers. if you want money, use ads.

Wednesday, June 8, 2011

anti-tampering with crc check

one way an app will try to detect if it has been tampered with is to look at classes.dex inside the apk. just so you know, java code is compiled to java .class files, which is then transformed by dx into classes.dex. this one file contains all the compiled code of an app. once the code is finished and the app is ready to be published, properties of the file such as the size or crc (cyclic redundancy check) can be determined and then stored inside the resources of the app.

when the app runs, it can compare the stored values with the actual values of the classes.dex file. if they do not match, then the code was likely tampered with.

note that we're using zipentry here, but we could also use jarentry and jarfile. you can't simply look for getCrc() and feel safe either, because the method could be called with reflection.

here's what a crc check may look like in java:
private void crcTest() throws IOException {
 boolean modified = false;

 // required dex crc value stored as a text string.
 // it could be any invisible layout element
 long dexCrc = Long.parseLong(Main.MyContext.getString(R.string.dex_crc));

 ZipFile zf = new ZipFile(Main.MyContext.getPackageCodePath());
 ZipEntry ze = zf.getEntry("classes.dex");

 if ( ze.getCrc() != dexCrc ) {
  // dex has been modified
  modified = true;
 else {
  // dex not tampered with
  modified = false;

and here's the above code translated into smali:
.method private crcTest()V
    .locals 7
    .annotation system Ldalvik/annotation/Throws;
        value = {
    .end annotation

    .line 599
    const/4 v2, 0x0

    .line 602
    # modified will be set to true if classes.dex crc is not what it should be
    .local v2, modified:Z
    sget-object v5, Lcom/lohan/testtarget/Main;->MyContext:Landroid/content/Context;

    # get the crc value from string resources
    const v6, 0x7f040002
    invoke-virtual {v5, v6}, Landroid/content/Context;->getString(I)Ljava/lang/String;
    move-result-object v5

    # convert it to a long since ZipEntry.getCrc gives us long
    invoke-static {v5}, Ljava/lang/Long;->parseLong(Ljava/lang/String;)J
    move-result-wide v0

    .line 604
    .local v0, dexCrc:J
    new-instance v4, Ljava/util/zip/ZipFile;

    sget-object v5, Lcom/lohan/testtarget/Main;->MyContext:Landroid/content/Context;

    # get the path to the apk on the system
    invoke-virtual {v5}, Landroid/content/Context;->getPackageCodePath()Ljava/lang/String;
    move-result-object v5

    invoke-direct {v4, v5}, Ljava/util/zip/ZipFile;->(Ljava/lang/String;)V

    .line 605
    .local v4, zf:Ljava/util/zip/ZipFile;
    # get classes.dex entry from our apk
    const-string v5, "classes.dex"
    invoke-virtual {v4, v5}, Ljava/util/zip/ZipFile;->getEntry(Ljava/lang/String;)Ljava/util/zip/ZipEntry;
    move-result-object v3

    .line 607
    .local v3, ze:Ljava/util/zip/ZipEntry;
    # you could crack here by providing v5 with the correct
    # long value. this may be easier if later logic is convoluted
    # or if the result is stored in a class variable and acted
    # on later. you can write your own java program to get the
    # correct value. 
    invoke-virtual {v3}, Ljava/util/zip/ZipEntry;->getCrc()J
    move-result-wide v5

    # compare v5 (actual crc) with v0 (stored crc)
    cmp-long v5, v5, v0

    # if v5 is 0, meaning cmp-long reports values are NOT the same
    # goto :cond_0. this is where this could be cracked.
    # could simply remove this line, in this case.
    if-eqz v5, :cond_0

    .line 609
    # otherwise store true in v2.
    # normally there will be code to act on the value of v2.
    const/4 v2, 0x1

    .line 615

    .line 613
    # store false in v2.
    const/4 v2, 0x0

    goto :goto_0
.end method

Monday, June 6, 2011

protection by checking for debuggers

one way to figure out what an app is doing is to use a debugger so you can step through line by line. apktool makes it possible to debug apps to which you do not have the source, and you also have to setup a few other things covered in lesson 1 of the way of the android crack tutorials.

some apps try to protect against this and there are two techniques of doing so. the first is to check the android manifest to see if the app is set to debuggable. the java code would look something like this:

boolean isDebuggable = (0 != (getApplcationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABLE));
if ( isDebuggable )
  invalidLicense(); // you get the idea 

in smali, specifically from the testtarget app distributed with antilvl, it looks like:
# get app info object
invoke-virtual {p0}, Lcom/lohan/testtarget/Main;->getApplicationInfo()Landroid/content/pm/ApplicationInfo;
move-result-object v1

# get flags mask
iget v2, v1, Landroid/content/pm/ApplicationInfo;->flags:I

# 0x2 is ApplicationInfo.FLAG_DEBUGGABLE
and-int/lit8 v2, v2, 0x2

iput v2, v1, Landroid/content/pm/ApplicationInfo;->flags:I

# if v2 is 0x0 (the debuggable flag is not set), goto :cond_0
if-eqz v2, :cond_0

the second method is to check if a debugger is currently connected. the java looks like:
boolean debugConn = Debug.isDebuggerConnected();

Wednesday, April 27, 2011

smali syntax resource

jesusfreke, the guy who wrote smali/baksmali, has some nice documentation on the smali syntax on the wiki for his project: i learned some stuff! thanks jesusfreke.

Monday, April 18, 2011

antilvl 1.1.5

thanks to Notion and SuRViVe pointing out some instances of the lvl not being properly identified. i removed some requirements to match some key files that weren't likely necessary and were not being found recently. two new anti-cracking methods have been added and testtarget was updated appropriately.

grab it here:

Friday, April 8, 2011

antilvl 1.1.4

antilvl 1.1.4 has been released. main new feature is support for cracking amazon appstore drm. it also includes the apk i use to test new releases called testtarget.apk.

let me know if the new release breaks something :D

grab it here:

Saturday, April 2, 2011

cracking amazon drm

update: antilvl 1.1.4 can handle amazon drm protection.

amazon has an app store now and they rolled their own drm. Anonymous was kind enough to post a link describing how to crack the protection:

there may be a cleaner solution, and if you find one you are encouraged to share it. here's the code from the above link but syntax highlighted:
# virtual methods
.method public final a()V
    .registers 6

    const-string v4, "LICENSE_FAILURE_CONTENT"

    iget-object v0, p0, Lcom/amazon/android/aa/d;->b:Lcom/amazon/android/o/d;

    const-string v1, "APPLICATION_LICENSE"

    invoke-virtual {v0, v1}, Lcom/amazon/android/o/d;->b(Ljava/lang/String;)Z

    move-result v0

    # Comment out first jump
    #if-eqz v0, :cond_14

    sget-object v0, Lcom/amazon/android/aa/d;->a:Lcom/amazon/android/u/a;

    const-string v1, "license verification succeeded"

    invoke-virtual {v0, v1}, Lcom/amazon/android/u/a;->a(Ljava/lang/String;)V


    invoke-virtual {p0}, Lcom/amazon/android/aa/d;->f()Z

    move-result v0

    # Comment out second jump
    #if-eqz v0, :cond_1d

    invoke-virtual {p0}, Lcom/amazon/android/aa/d;->g()V

    new-instance v1, Lcom/amazon/android/l/m;

    iget-object v0, p0, Lcom/amazon/android/aa/d;->b:Lcom/amazon/android/o/d;

    const-string v2, "LICENSE_FAILURE_CONTENT"

    invoke-virtual {v0, v4}, Lcom/amazon/android/o/d;->a(Ljava/lang/String;)Ljava/lang/Object;

    move-result-object v0

    check-cast v0, Lcom/amazon/android/l/d;

    # Comment out third jump
    #if-eqz v0, :cond_3d

    iget-object v2, p0, Lcom/amazon/android/aa/d;->b:Lcom/amazon/android/o/d;

    const-string v3, "LICENSE_FAILURE_CONTENT"

    iget-object v2, v2, Lcom/amazon/android/o/d;->a:Lcom/amazon/android/o/b;

    invoke-virtual {v2, v4}, Lcom/amazon/android/o/b;->c(Ljava/lang/String;)V

    invoke-direct {v1, v0}, Lcom/amazon/android/l/m;->(Lcom/amazon/android/l/d;)V

    iget-object v0, p0, Lcom/amazon/android/aa/d;->c:Lcom/amazon/android/l/f;

    invoke-interface {v0, v1}, Lcom/amazon/android/l/f;->a(Lcom/amazon/android/l/a;)V

    goto :goto_13

    sget-object v0, Lcom/amazon/android/aa/f;->e:Lcom/amazon/android/l/d;

    goto :goto_34
.end method

the file name will likely always be different with obfuscation. just search for strings like "LICENSE_FAILURE_CONTENT" or "APPLICATION_LICENSE" and perform the three modifications mentioned above.

i'll be adding this functionality to the next release of antilvl. it will also contain a few more bypasses for anti-cracking techniques i've seen, and some improvements in lvl fingerprinting.

Thursday, March 31, 2011

spoof getinstallerpackagename with adb

as i wrote back in another post about anti-cracking technique examples, one method that is often used is getinstallerpackagename(). if the apk is installed from adb, it will be null, but if it's installed from the market it will be antilvl is well aware of this already, but there is an easier solution for when you're in a hurry. i learned it reading this post at tim's fantastic blog on reversing. he's not affiliated with me and for all i know he's an upstanding white hat who just loves hacking android.

all you need is adb. just give it this command either in a shell or as:
adb install -i
 this will setup as the installer for the if you're not sure what the app name is for a given apk, just use aapt, from the android-sdk. ex: aapt d --values badging someapk.apk

Thursday, March 24, 2011

original smalihook java source

i've noticed some interest about a file that antilvl sometimes uses when cracking a program. it's called smalihook and it's purpose is to provide "hook" (actually replacement) methods for things like getting device id or signature. it's not really anything special, unless you actually modify the places in the app that make use of certain function calls. there is also a floating around that is actually a badly decompiled, broken version. i'd rather people have the real thing.

the variable strings that start with "%!" (ex: %!AppPackage%) are for antilvl to replace with the actual information when it copies it over.

if you want to use any of the functions here you can simply use antilvl.

if you just want to spoof your android_id or getdeviceid, try this:

package lohan;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Random;

import android.content.Context;
import android.content.SharedPreferences;
import android.telephony.TelephonyManager;
import android.util.Log;

 * TODO:
 * I wonder if it's possible to check getClasses or getMethods to detect this
 * hook
 * Hooks:
 * PackageManager
 * getInstallerPackageName
 * getPackageInfo
 * getApplicationEnabledSetting
 * checkSignatures
 * getDeviceID - requires context
 * File
 * length
 * lastModified

public class SmaliHook {

 // replace with random var per antilvl run
 private static String PrefsFile = "HookSettings";
 private static Context myAppContext = null;
 // random - always random, permute - unreversible permutation
 // session means until app is reinstalled
 private static enum DEVICE_ID_SPOOF {
 private static String LOG_TAG = "lohan";
 private static boolean DEBUG = true;
 private static boolean DUMP_STACK = false;

 public static Object invokeHook(Method method, Object receiver,
   Object[] args) throws IllegalArgumentException,
   IllegalAccessException, InvocationTargetException,
   NameNotFoundException {

  boolean HookEnabled = true;

  String methodClassName = "unknown-static";
  String methodName = method.getName();
  if ( receiver != null )
   methodClassName = receiver.getClass().getName();
  else methodClassName = method.getDeclaringClass().getName();

  if ( DEBUG ) {
   String logStr = "Invoke Hook: " + methodClassName + "."
     + methodName + "(";
   if ( args != null ) {
    String argStr = "";
    for ( Object arg : args )
     argStr += arg.getClass().getName() + ":" + arg + ", ";
    if ( argStr.length() > 2 )
     argStr = argStr.substring(0, argStr.length() - 2);
    logStr += argStr;

   Log(logStr + ")");


  if ( !HookEnabled ) return method.invoke(receiver, args);

  if ( methodClassName
    || methodClassName
    || methodClassName.equals("")
    || methodClassName.contains("ApplicationPackageManager") ) {
   if ( methodName.equals("getInstallerPackageName") ) {
    // Hook get installer package name
    return getInstallerPackageName((String) args[0]);
   else if ( methodName.equals("getPackageInfo") ) {
    // Hook get package info for signatures
    int flags = (Integer) args[1];

    if ( methodClassName
      .equals("") )
     return SmaliHook.getPackageInfo(
       ((PackageManager) receiver), (String) args[0],

    // Cannot simply recast receiver to
    // ContextImpl.ApplicationPackageManager or we get error
    Object result = null;
    try {
     result = method.invoke(receiver, args);
    catch (Exception e) {
     result = method.invoke(receiver, "%!AppPackage%");

    if ( (flags & PackageManager.GET_SIGNATURES) == PackageManager.GET_SIGNATURES ) {
     Signature[] spoofSigs = SmaliHook.spoofSignatures();
     // should only need to spoof the first one
     ((PackageInfo) result).signatures[0] = spoofSigs[0];

    return result;
   else if ( methodName.equals("getApplicationEnabledSetting") ) {
    int result = getApplicationEnabledSetting(
      (PackageManager) receiver, (String) args[0]);
    return (Object) Integer.valueOf(result);
   else if ( methodName.equals("checkSignatures") ) {
    // This could be detected by comparing a known installed package
    // that will not match signatures. Will deal with that if it
    // ever happens. :D
    return checkSignatures((String) args[0], (String) args[1]);

  else if ( methodClassName.equals("") ) {
   if ( shouldSpoofFileInfo((File) receiver) ) {
    if ( methodName.equals("length") ) { return length((File) receiver); }

    if ( methodName.equals("lastModified") ) { return lastModified((File) receiver); }

  // No hooks, work as normal
  return method.invoke(receiver, args);

 public static int checkSignatures(String p1, String p2) {
  Log("checkSignatures returning SIGNATURE_MATCH");

  return PackageManager.SIGNATURE_MATCH;

 public static int checkSignatures() {
  Log("checkSignatures returning SIGNATURE_MATCH");

  return PackageManager.SIGNATURE_MATCH;

 public static String getInstallerPackageName(String packageName) {
  // LIE and say installed from market :D
  String result = "";
  Log("getInstallerPackageName returning " + result);
  return result;

 public static int getApplicationEnabledSetting(PackageManager pm,
   String packageName) {

  int result;
  try {
   result = pm.getApplicationEnabledSetting(packageName);
  catch (IllegalArgumentException ex) {

  // Fake value if it's disabled
  if ( result == PackageManager.COMPONENT_ENABLED_STATE_DISABLED )

  Log("enabledSetting returning " + result);
  return result;

 public static PackageInfo getPackageInfo(PackageManager pm,
   String packageName, int flags) throws NameNotFoundException {

  // Get regular package info
  PackageInfo pi = null;
  try {
   pi = pm.getPackageInfo(packageName, flags);
  catch (NameNotFoundException e) {
   // Sometimes the app wants to know of other, helper apps are
   // installed or if trial / nonfull versions are installed
   // Fail normally if it's NOT checking for pro/full version stuff
   if ( !(packageName.toLowerCase().contains("pro")
     || packageName.toLowerCase().contains("full")
     || packageName.toLowerCase().contains("donate") || packageName
     .toLowerCase().endsWith("key")) )
    throw new NameNotFoundException();

   // Spoof with this package's info
   pi = pm.getPackageInfo("%!AppPackage%", flags);

  // Populate with fake signatures if flags ask for it
  if ( (flags & PackageManager.GET_SIGNATURES) == PackageManager.GET_SIGNATURES ) {
   Signature[] spoofSigs = SmaliHook.spoofSignatures();
   for ( int i = 0; i < pi.signatures.length; i++ )
    pi.signatures[i] = spoofSigs[i];
   Log("spoofing signatures for " + packageName);

  return pi;

 public static Signature[] spoofSignatures() {
  final int certCount = Integer.parseInt("%!CertCount%");
  Signature[] result = new Signature[certCount];

  // Usually check signature of package and not individual files
  // This will only fool checks of entire package
  // Individual files would require a lot of smali generation
  String replace = "%!SignatureChars%";

  for ( int i = 0; i < certCount; i++ )
   result[i] = new Signature(replace);

  return result;

 public static long length(File f) {
  long retVal = Long.parseLong("%!OrigFileSize%");

  if ( !shouldSpoofFileInfo(f) ) {
   retVal = f.length();
   Log("spoofing file length of " + f.getName() + " with " + retVal);

  return retVal;

 public static long lastModified(File f) {
  // long retVal = 1287850800968L;
  long retVal = Long.parseLong("%!OrigLastModified%");

  if ( DUMP_STACK ) Thread.dumpStack();

  if ( !shouldSpoofFileInfo(f) ) {
   retVal = f.lastModified();
   Log("spoofing file modified of " + f.getName() + " with " + retVal);

  return retVal;

 public static String getDeviceID() {
  if ( myAppContext == null ) {
   Log("getDeviceID has no context, can't spoof device id");
   return "";

  // final TelephonyManager tm = (TelephonyManager)
  // myAppContext.getSystemService(Context.TELEPHONY_SERVICE);
  // Log("this is my device id: " + tm.getDeviceId());

  // fallback id
  String spoofID = "359881030314356";
  if ( myIDSpoof == DEVICE_ID_SPOOF.RANDOM )
   spoofID = generateRandomDeviceID();
  else {
   SharedPreferences settings = myAppContext.getSharedPreferences(
     PrefsFile, Context.MODE_PRIVATE);
   spoofID = settings.getString("android_id", "");

   if ( spoofID.length() == 0 ) {
     spoofID = generateRandomDeviceID();
    else if ( myIDSpoof == DEVICE_ID_SPOOF.SESSION_PERMUTE )
     spoofID = getPermutedDeviceID();
    SharedPreferences.Editor editor = settings.edit();
    editor.putString("android_id", spoofID);
  Log("spoofing device id: " + spoofID);

  return spoofID;

 private static boolean shouldSpoofFileInfo(File f) {
  boolean result = false;

  if ( f.exists() ) result = false;

  if ( f.getName().contains("%!AppPackage%")
    && f.getName().endsWith(".apk") ) result = true;

  return result;

 public static void SetAppContext(Context c) {
  if ( myAppContext == null ) myAppContext = c;

 private static String getPermutedDeviceID() {
  // permute device id
  final TelephonyManager tm = (TelephonyManager) myAppContext
  // lazy lazy lazy
  // this is a permutation with a loss of information
  // prevent anyone from knowing the id even if they knew the mapping
  final int[] p = { 12, 2, 10, 2, 13, 8, 0, 3, 14, 3, 6, 9, 5, 1, 12 };

  String deviceId = tm.getDeviceId();
  String result = "";
  if ( deviceId != null ) {
   for ( int i : p )
    result += deviceId.charAt(i);

  return result;

 private static String generateRandomDeviceID() {
  // device id is 15 digit number with seemingly no pattern
  // only changed by factory reset or with root
  // ex: 359881030314356 (emulators is all 0s)
  return generateString("0123456789", 15);

 private static String generateString(String charSet, int length) {
  Random rng = new Random();
  char[] text = new char[length];
  for ( int i = 0; i < length; i++ )
   text[i] = charSet.charAt(rng.nextInt(charSet.length()));

  return new String(text);

 public static void Log(Object o) {
  if ( !DEBUG ) return;

  Log.d(LOG_TAG, String.valueOf(o));

 public static void DumpStackIfWeShould() {
  if ( !DUMP_STACK ) return;


 public static void DumpStack() {
  StackTraceElement[] ste = Thread.currentThread().getStackTrace();

  // skip the first 4, it's just local stuff
  String trace = "Stack trace:\n";
  for ( int i = 4; i < ste.length; i++ )
   trace += "  " + ste[i].toString() + "\n";

  Log.d(LOG_TAG, trace);

 public static void Toast(Object o) {
  // todo: implement

Thursday, March 3, 2011

smali syntax highlighting for vim

i've been doing a lot in linux lately and was working on a smali syntax highlighting file for vim, but vierito5 posted a comment with a link to Jon Larimer, who beat me to it. here's the link: (mirror)

it's vim so there are 20 different ways to get it working. google around if this doesn't work, i will be no help. here is how i did it:
mkdir ~/.vim
echo au BufRead,BufNewFile *.smali set filetype=smali >> ~/.vim/filetype.vim
mkdir ~/.vim/syntax
cp smali.vim ~/.vim/syntax

Sunday, February 20, 2011

smali syntax highlighting for notepad++

update 10/8/2013:
thanks to Ádám Tóth for creating a dark themed version. i've linked to it next to the main version.

update: 11/10/2011:
thanks to Jho for pointing out how to get code folding to work. i updated the syntax file and made a few other tweaks. the link and picture have been updated and here are the instructions for installing (tested with v5.9.6.1):
View -> User-Defined Dialogue...

Click Import

Select smali_npp.xml
       no picture here, use imagination

There will be a message box that says "Import successful."
Any new files you open should have syntax highlighting.

several people have asked for smali highlighting for notepad++. thanks to furrelkt for having already sent me an example. here's what i've come up with:
for dark backgrounds, try this:

there are many limitations for notepad++'s user defined language. i could not get many tokens to highlight correctly, or as well as ultraedit or the highlighter used on this blog. perhaps a full lexer plugin could handle it. if you write one or make improvements to this xml, let me know.

Saturday, February 19, 2011

more smali syntax and running smali files

there are some examples of smali syntax written by the author of smali/baksmali JesusFreke here: most are rather technical, but still illuminating.

here's one that i modified slightly that shows a basic hello world app with a standard main method. also, in the comments you will see a way to quickly compile and run a smali file. this is sometimes quite useful in testing your code:
.class public LHelloWorld;

# Ye olde hello world application
# To assemble and run this on a phone or emulator:
# java -jar smali.jar -o classes.dex HelloWorld.smali
# zip classes.dex
# adb push /data/local
# adb shell dalvikvm -cp /data/local/ HelloWorld
# if you get out of memory type errors when running smali.jar
# give java more memory with -Xmx512m, like this:
# java -Xmx512m -jar smali.jar HelloWorld.smali

.super Ljava/lang/Object;

.method public static main([Ljava/lang/String;)V
    .registers 2

    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;

    const-string v1, "hello,world!"

    # combine executing with adb shell commands and console output
    # and you have a very quick way to test code
    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

.end method

writing large amounts of smali

for the android reverser and more-so for one wishing to modify an apk, it is sometimes necessary to write large amounts of smali code. by large i mean over 10 lines, with a lot of control flow and api calls. keeping track of all those gotos, catches, switches, etc. is cumbersome unless you want to be some kind of smali wizard.

i wrote a class for antilvl to handle function hooks (really just replacements), and there is a lot of scary logic in there to have written manually. i've found it's best to create an android project in eclipse, write the code in java and decompile it into smali. the setup is easy. just download eclipse and install the ADT android plugin.

i recommend creating an android project just to prototype code. also, with the android plugin, when you run code it can either execute on your phone or start up an emulator. this has saved me tons of time while exploring various under-documented android api calls or digging around system settings or just trying to get a large bit of smali working.

writing code in java and then seeing it as smali will aid in understanding smali since you'll already be familiar with the functionality of the code. you can also automate the process of getting the smali file out by writing a shell script or batch file. here's an example batch file:
SET CLASSES_PATH=%USERPROFILE%\workspace\ProjectName\bin\classes.dex
SET SMALI_OUT_PATH=\where\you\want\it
SET SMALI_FILE=%DUMP_DIR%\Package\Name\Smali_File.smali
SET BAKSMALI_PATH=baksmali.jar
SET BAKSMALI_OPTS=--use-locals --sequential-labels

ECHO Decompiling ...
java -jar "%BAKSMALI_PATH%" "%CLASSES_PATH%" -output "%DUMP_DIR%"
ECHO Finishing up ...
RMDIR /s /q "%DUMP_DIR%"

Sunday, February 13, 2011

antilvl 1.1.3

just put up antilvl 1.1.3. a few small but annoying bugs fixed and some improvements with the hooks. pick it up from the usual spot:

also had to do some major refactoring so people could make use of the source once it's released, and i think i got most of the kinks out.

while working on this, i noticed a few more apps that were using string encryption. maybe it will start to get popular? i wrote a proof of concept decryptor just to see how feasible it would be to convert dex to java .class files and run the apk's own methods to decrypt the strings. it worked but i want to make something more general. here's my idea:

  • start with an apk and disassemble
  • chose to decode literal strings (ex: const-string "some-encoded-string") or assume strings are the result of a function call (ex: invoke-static LStringHolder;->getString(v0)).
  • show all lines that match the above selection and allow for regex filtering. this way, if you pick literal strings and not all strings are encoded, you can filter for just the ones that are
  • decode strings by one of several methods: run the function, in the case of function-call encryption, built-in stuff like base 64, etc. or by using reflection on the classes of the apk. this way if every literal string in the apk is decoded through some function, i could use dex2jar to get the java class, dynamically load that and run each string through it.
the goal is to make the tool generic enough so that it's useful in the most situations. shouldn't be too hard. half of the work will be making my patching and apk libraries more generic and useful, so it wont be a total waste of time.

Monday, February 7, 2011

antilvl's source

several have asked for antilvl's source to use it in ways i could not have imagined. it seems it's true purpose is more of a general semi-automated apk patcher, which is fine. i'm thinking i should rename it and make it more general purpose, while still including the lvl-patching and anticracking-patching information.

i'm also getting a lot of fun out of this little shell extension for windows to get explorer to replace an apk's icon with the actual icon for the app inside:

Sunday, February 6, 2011

antilvl 1.1.2

antilvl 1.1.2 is finished. thanks for the bug reports from anonymous, sage and others. changes and download links are here:

Friday, February 4, 2011

updated lesson 1

for lesson 2 i'm using html and javascript to make things more readable. to test the template i rewrote and updated lesson 1 of way of the android cracker to include stuff on threads, stack dumps and using jdb with android apps. it's also in html now and the code looks much prettier. i'll rewrite lesson 0 if i ever get around to it.

Sunday, January 30, 2011

quick antilvl fix

got overeager with antilvl's new found powers and released without doing some extra bug testing. just uploaded a less broken version just now. download links haven't changed.

antilvl 1.1

finished antilvl 1.1. it's kind of scary how well it's working. now that the anti-cracking methods are added in, it should be able to crack the android license verification library and any type of protection i have ever seen, including a few i haven't.

the number of modifications done to an app has increased a bit, so there may be some mix up. let me know how it works for you.

here's the main gist of the changes:
  • introduced many anti-cracking bypassing methods. it's better than me!
  • improved --sign-only behavior, though it still errors every other time
  • fixed issue with modification's sometimes being done improperly
  • several under the hood improvements for future features
 you can download it here:

next up i intend to tackle slidelock protection and api key requirements.

Wednesday, January 26, 2011

smali syntax highlighting for syntaxhighlighter

Update: this is now being maintained by someone else here:

to facilitate the eye-pleasing posting of smali on blogs and html and such, here is a brush for syntaxhighlighter. i'm in the process of converting my previous tutorials to html and they use this instead of screen shots of code or simple green coloring. it looks much nicer.

 * shBrushSmali.js
 * For SyntaxHighlighter
 * @version
 * 1.0.1 (November 10 2011)
 * By lohan+
 // CommonJS
 typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null;

 function Brush()
  // Must put these in reverse order or else 'const' will catch but 'const/4' will not
  var funcs = 
   'xor-long/2addr xor-long xor-int/lit8 xor-int/lit16 xor-int/2addr xor-int'
   + ' ushr-long/2addr ushr-long ushr-int/lit8 ushr-int/2addr ushr-int throw throw-verification-error'
   + ' sub-long/2addr sub-long sub-int/lit8 sub-int/lit16 sub-int/2addr sub-int'
   + ' sub-float/2addr sub-float sub-double/2addr sub-double sput-wide sput-short'
   + ' sput-object sput-char sput-byte sput-boolean sput sparse-switch shr-long/2addr'
   + ' shr-long shr-int/lit8 shr-int/2addr shr-int shl-long/2addr shl-long shl-int/lit8'
   + ' shl-int/2addr shl-int sget-wide sget-short sget-object sget-char sget-byte'
   + ' sget-boolean sget return-wide return-void return-object return rem-long/2addr'
   + ' rem-long rem-int/lit8 rem-int/lit16 rem-int/2addr rem-int rem-float/2addr'
   + ' rem-float rem-double/2addr rem-double rsub-int rsub-int/lit8'
   + ' or-long/2addr or-long or-int/lit8'
   + ' or-int/lit16 or-int/2addr or-int not-long not-int nop new-instance new-array'
   + ' neg-long neg-int neg-float neg-double mul-long/2addr mul-long mul-int/lit16'
   + ' mul-int/lit8 mul-int/2addr mul-int mul-float/2addr mul-float mul-double/2addr'
   + ' mul-double move/from16 move/16 move-wide/from16 move-wide/16 move-wide'
   + ' move-result-wide move-result-object move-result move-object/from16 move-object/16'
   + ' move-object move-exception move monitor-exit monitor-enter long-to-int'
   + ' long-to-float long-to-double iput-wide-quick iput-wide iput-short iput-quick'
   + ' iput-object-quick iput-object iput-char iput-byte iput-boolean iput'
   + ' invoke-virtual/range invoke-virtual-quick/range invoke-virtual-quick'
   + ' invoke-virtual invoke-super/range invoke-super-quick/range invoke-super-quick'
   + ' invoke-super invoke-static/range invoke-static invoke-interface/range'
   + ' invoke-interface invoke-direct/range invoke-direct-empty invoke-direct'
   + ' int-to-short int-to-long int-to-float int-to-double int-to-char int-to-byte'
   + ' instance-of iget-wide-quick iget-wide iget-short iget-quick'
   + ' iget-object-quick iget-object iget-char iget-byte iget-boolean iget if-nez'
   + ' if-ne if-ltz if-lt if-lez if-le if-gtz if-gt if-gez if-ge if-eqz if-eq goto/32'
   + ' goto/16 goto float-to-long float-to-int float-to-double filled-new-array/range'
   + ' filled-new-array fill-array-data execute-inline double-to-long double-to-int'
   + ' double-to-float div-long/2addr div-long div-int/lit8 div-int/lit16 div-int/2addr'
   + ' div-int div-float/2addr div-float div-double/2addr div-double const/high16 const/4'
   + ' const/16 const-wide/high16 const-wide/32 const-wide/16 const-wide'
   + ' const-string-jumbo const-string const-class const cmpl-float cmpl-double cmpg-float'
   + ' cmpg-double cmp-long check-cast array-length aput-wide aput-short aput-object'
   + ' aput-char aput-byte aput-boolean aput and-long/2addr and-long and-int/lit8'
   + ' and-int/lit16 and-int/2addr and-int aget-wide aget-short aget-object aget-char'
   + ' aget-byte aget-boolean aget add-long/2addr add-long add-int/lit8 add-int/lit16'
   + ' add-int/2addr add-int add-float/2addr add-float add-double/2addr add-double';

  var directives =
   'annotation array-data catch catchall class end enum epilogue field'
   + ' implements line local locals method .packed-switch parameter prologue'
   + ' registers restart source sparse-switch subannotation super';

  var keywords =
   'abstract annotation bridge constructor declared-synchronized enum final'
   + ' interface native private protected public static strictfp synchronized synthetic'
   + ' system transient varargs volatile';

  this.regexList = [
   { regex: new RegExp('#[^!].*$', 'gm'),          css: 'comments' }
   , { regex: SyntaxHighlighter.regexLib.doubleQuotedString,css: 'string' }
   , { regex: new RegExp('[vp]\\d{1,2}', 'g'),        css: 'variable' }
   , { regex: new RegExp(this.getKeywords(funcs), 'gmi'),  css: 'functions' }
   , { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }
   , { regex: new RegExp('0x[0-9a-f]+', 'gi'),        css: 'constants' }
   , { regex: new RegExp(this.getKeywords(directives), 'gm'),css: 'value' }
   , { regex: new RegExp('L[\\w|/]+;', 'gm'),        css: 'color2' }
   , { regex: new RegExp('>\\w+\\S*\\(\\S*\\)', 'gm'),  css: 'color3' }
   , { regex: new RegExp('\\w+:[IZJ]', 'gm'),        css: 'color1' }


 Brush.prototype = new SyntaxHighlighter.Highlighter();
 Brush.aliases  = ['smali', 'Smali'];

 SyntaxHighlighter.brushes.Smali = Brush;

 // CommonJS
 typeof(exports) != 'undefined' ? exports.Brush = Brush : null;

Tuesday, January 25, 2011

why wont apk tool recompile?

if you are getting strange errors related to resources. for example:
W: Could not find sources
I: Checking whether resources has changed...
I: Building resources...
C:\someapp\res\values\public.xml:2395: error: Public entry identifier 0x107001d entry index is larger than available symbols (index 29, total symbols 29).
C:\someapp\res\values\public.xml:2395: error: Public symbol array/carrier_properties declared here is not defined.
Exception in thread "main" brut.androlib.AndrolibException: brut.common.BrutException: could not exec command: [aapt, p, -F, C:\Users\Soontir\AppData\Local\Temp\APKTOOL5080969896487671571.tmp, -x, -S, C:\someapp\res, -M, C:\framework\framework-res1\AndroidManifest.xml]
        at brut.androlib.res.AndrolibResources.aaptPackage(Unknown Source)
        at brut.androlib.Androlib.buildResourcesFull(Unknown Source)
        at brut.androlib.Androlib.buildResources(Unknown Source)
        at Source)
        at Source)
        at brut.apktool.Main.cmdBuild(Unknown Source)
        at brut.apktool.Main.main(Unknown Source)
Caused by: brut.common.BrutException: could not exec command: [aapt, p, -F, C:\Users\Soontir\AppData\Local\Temp\APKTOOL5080969896487671571.tmp, -x, -S, C:\framework\framework-res1\res, -M, C:\someapp\AndroidManifest.xml]
        at brut.util.OS.exec(Unknown Source)
        ... 7 more
the problem could be non-standard resource qualifiers. it affects current versions of apktool (1.3.2), but may be fixed in a later version. the solution is to decomile using --keep-broken-res and fix the xml file manually. for an explanation of how to do this, read this issue by the developer himself:

why wont google maps work after resigning or recompiling?

this is because access to google's stuff requires an api key, stored in xml as a resource or perhaps in code, that is linked with the certificate of the app. if you modified and resigned it, the certificate is different and will not match the api key. you will have to get a valid key for the signature you are using.

you should already have your own certificate you sign everything with. this way if you release an updated version of the app, users of your previous modified version can update without needing to uninstall the old one first. if you don't already have your own certificate, follow the directions here: Signing Your Applications | Android Developers. you will need to use keytool and examples are given in the link.

once you're done or if you already have your own certificate, follow the instructions here: Obtaining a Maps API Key - Google Projects for Android.

when finished with that, and you have your own api key, decompile your apk with apktool so you can modify resources. you are looking for an xml file with a string like:

or if it is created dynamically in code, you will see the initialization of a mapview object. the constructor takes a string as the second parameter which is the api key. here's an example:
# replace p2 with our api key here:
const-string p2, "3BCknOWFD_vyechU238a1f9YOWyYP2Z91CEfQWw"

# create the mapview object with our modified api key
# this line should already be here and doesn't need to be changed
invoke-direct {p0, p1, p2}, Lcom/google/android/maps/MapView;-><init>(Landroid/content/Context;Ljava/lang/String;)V

Sunday, January 23, 2011

anti-cracking example

there are only a handful of anti-cracking techniques for android, at least that i know of. this seems to be because of how android apps must behave. they must operate inside their own virtual machine and are not allowed to store permenent data. when they are uninstalled, they must remove all traces of themselves. this makes things like timed trials difficult to implement because a simple reinstall will reset timers.

if an app wants to prevent cracking, it only has a few options. it can check file properties of the apk such as file size, last modified date and signature. it could also check the installer package name to test if it was installed via market. a cracker or pirate would install via adb, not market. antilvl can already hook and subvert signature checks. file size, last modified and installer package hooking are in the works. :D

this is what a installer package check may look like in java:
private boolean InstalledFromMarketEasy() {
  String pname = this.getPackageName();
  PackageManager pm = this.getPackageManager();
  String installPM = pm.getInstallerPackageName(pname);
  if ( installPM == null ) {
    // Definitely not installed from Android Market
    return false;
  else if ( installPM.equals("") ) {
    // Installed from the Android Market
    return true;

  return false;

but there's another way to perform any check in a more clever way that makes detection more difficult... by using java reflection. instead of calling the method directly, the app could use reflection to store the method in a variable and call it later indirectly. this makes searching for the direct method call difficult. it also allows the method name to be obfuscated since it is just a strong. let me show you in code what it may look like:
const-class v1, Landroid/content/pm/PackageManager;

# do not assume this will not always be a plain text
# it could be put together in a much more complicated way such as building character by character using ascii codes
# you'll have to hook reflection methods to test the name or figure it out manually
const-string v2, "getInstallerPackageName"

# make array of size 1
const/4 v3, 0x1
new-array v3, v3, [Ljava/lang/Class;

const/4 v4, 0x0
const-class v5, Ljava/lang/String;

# store String class in array v3, at index v4 (0)
aput-object v5, v3, v4

# get method of name v2 ("getInstallerPackageName") and move to v0
#, java.lang.Class...)
invoke-virtual {v1, v2, v3}, Ljava/lang/Class;->getMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
move-result-object v0

# do something with v0, like store in class variable
# ....
# and then sometime later, somewhere else in code

# get the package manager and store in v1
invoke-virtual {p0}, Lcom/clever/app/Main;->getPackageManager()Landroid/content/pm/PackageManager;
move-result-object v1

const/4 v2, 0x1
new-array v2, v2, [Ljava/lang/Object;

const/4 v3, 0x0
invoke-virtual {p0}, Lcom/clever/app/Main;->getPackageName()Ljava/lang/String;
move-result-object v4

# put package name in array v2 at index v3 (0)
aput-object v4, v2, v3

# actually call "getInstallerPackageName" with the array we built as parameters
# which contains this package name
invoke-virtual {v0, v1, v2}, Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
move-result-object v0
check-cast v0, Ljava/lang/String;

# clever to not use any type of full string, just "goog"
# remember the full string is expected to be ""
const-string v1, "goog"
invoke-virtual {v0, v1}, Ljava/lang/String;->indexOf(Ljava/lang/String;)I
move-result v0

# check to see if v0 has the correct value. if not... error :D

the next lesson in way of the android cracker will cover all anti-cracking techniques i know in detail, and antilvl will support these types of checks soon, but this is enough for anyone to figure it out. :D

for some more information on reflection:
Using Java Reflection
Java programming dynamics, Part 2: Introducing reflection

Saturday, January 22, 2011

antilvl 1.0

antilvl 1.0 is finished and ready for action. here are some changes:
  • complete rewrite of previous versions
  • acts more like an engine, with modification information stored in fingerprints.xml
  • many, many more apk files can be decompiled and recompiled
  • handles several new types of protection methods
  • signature checking, a common anti-tampering technique, is subverted
  • file size / last modification checks are more accurately detected
  • pro / full unlock app protection is correctly handled sometimes
  • much more compact / optimized java bytecode

you can pick it up here:

doing a total rewrite has opened up many more possibilities for new ideas. the next major thing i plan to implement is complete file size / last modified hooking.

Monday, January 10, 2011

upcoming antilvl 1.0

it's been a while since my last update, so i figure i should say something about progress on antilvl 1.0.

i wanted to learn all kinds of best practices in java, so i spent a lot of time designing classes while trying to avoid coupling and other oop pitfalls. even threw in some unit testing. i also want antilvl to be able to be able to learn how to subvert any implementation of the lvl, and do it without having pages and pages of complicated regex code.

to accomplish this, antilvl's code will be more like an engine, with all file identification patterns and code modification information written in xml with a specification of my own design. it will be mature enough to have in-place dynamic variables and definable search patterns. the result will be almost any apk modification could be cleverly constructed inside the xml without touching the code in about 10-20 lines.

i must stress i'm not doing this for piracy. i'm doing it because it's a challenge. no one else has done it. it's really fun for me. if i was trying to be a pirate, i would not explain how the cracks work. if you're a developer and you insist on protection, you will have to roll your own and heavily modify the official lvl so known methods will not work. use antilvl to help you figure out if it's well protected.

most people don't mind paying a few euros for a program on what amounts to their main computing device. the benefit from spending many hours on increased protection may not be worth it unless your App is very expensive. or perhaps your and android master and just really get off to having wonderfully insane levels of protection.

Monday, January 3, 2011


many people find this blog by searching for smali syntax examples and finding a previous post on example smali syntax. since i originally hastily wrote it, i have learned more so i updated it as best i could for now. more helpfully, i worked out a syntax highlighter for smali using syntaxhighlighter. i'll upload it somewhere official when i'm finished so others can use it. because i know there are throngs of people just itching to have highlighted smali code!

so for the googler's out there trying to make sense of smali, here's example-structures.smali from lesson 1 from way of the android cracker.

.class public Lcom/lohan/crackme1/example;
.super Ljava/lang/Object;
.source ""

# static fields
.field private static Counter:I

# direct methods
# all the constructor does is set Counter to 0x10 (or 16)
.method public constructor <init>()V
    .registers 2

    .line 11
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V

    const/16 v0, 0x10
    sput v0, Lcom/lohan/crackme1/example;->Counter:I

.end method

.method public static ArrayExample()V
    .registers 4

    const/4 v3, 0x1
    const/4 v2, 0x0

    .line 50
    const/4 v1, 0x5

    new-array v0, v1, [Ljava/lang/String;

    .line 52
    .local v0, someArray:[Ljava/lang/String;

    # put value v1 inside array v0 at index v2 (0x0)
    const-string v1, "set value at index 0"
    aput-object v1, v0, v2

    .line 53
    # put value v1 inside array v0 at index v3 (0x1)
    const-string v1, "index 1 has this value"
    aput-object v1, v0, v3

    .line 55
    # store in v1 the value from array v0 at index v2
    aget-object v1, v0, v2

    # store in v2 the value from array v0 at index v3
    aget-object v2, v0, v3

    # compare two strings
    invoke-virtual {v1, v2}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
    move-result v1

    # if equals() returns 0, it means they are not equal, so just return
    if-eqz v1, :cond_1e

    .line 57
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
    const-string v2, "array at index 0 = 1 (wont happen)"
    invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    .line 59
.end method

# loop from 0 to Counter
# just a few lines in java
.method public static LoopExample()V
    .registers 4

    .line 15
    const/4 v0, 0x0

    .local v0, i:I
    sget v1, Lcom/lohan/crackme1/example;->Counter:I

    if-lt v0, v1, :cond_6

    .line 17

    .line 16
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;

    new-instance v2, Ljava/lang/StringBuilder;
    const-string v3, "current val for loop: "
    invoke-direct {v2, v3}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
    invoke-virtual {v2, v0}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;
    move-result-object v2

    invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
    move-result-object v2

    invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    .line 15
    add-int/lit8 v0, v0, 0x1

    goto :goto_1
.end method

.method public static SwitchExample()V
    .registers 3

    .line 21
    const/16 v0, 0x2a

    .line 22
    .local v0, val:I
    # begin the switch
    # look down at .sparse_switch directive
    sparse-switch v0, :sswitch_data_2e

    # switch default just passes through to here
    .line 27
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
    const-string v2, "invalid value"
    invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    .line 29

    .line 23
    :sswitch_d # if v0 is 1, we'll be here
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
    const-string v2, "val 1"
    invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
    goto :goto_c

    .line 24
    :sswitch_15 # if v0 is 2
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
    const-string v2, "val 2"
    invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
    goto :goto_c

    .line 25
    :sswitch_1d # if v0 is 42
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
    const-string v2, "val 42"
    invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
    goto :goto_c

    .line 26
    :sswitch_25 # if v0 is 5
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
    const-string v2, "val 5"
    invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
    goto :goto_c

    .line 22

    # if v0 is 0x1 goto :sswitch_d
    # if 0x2 :sswitch_15
    # and so on...
        0x1 -> :sswitch_d   # 1
        0x2 -> :sswitch_15  # 2
        0x5 -> :sswitch_25  # 5
        0x2a -> :sswitch_1d # 42
    .end sparse-switch
.end method

.method public static TryCatchExample()V
    .registers 8

    const-string v7, ": "

    .line 33
    const-string v3, ""

    .line 36
    .local v3, urlStr:Ljava/lang/String;
    # begin try here
    new-instance v2, Ljava/net/URL;

    invoke-direct {v2, v3}, Ljava/net/URL;-><init>(Ljava/lang/String;)V

    .line 37
    .local v2, url:Ljava/net/URL;
    invoke-virtual {v2}, Ljava/net/URL;->openStream()Ljava/io/InputStream;

    move-result-object v1

    .line 38
    .local v1, is:Ljava/io/InputStream;
    invoke-virtual {v1}, Ljava/io/InputStream;->close()V
    # end of the try
     # if there was a MalformedURLException, goto :catch_11
    .catch Ljava/net/MalformedURLException; {:try_start_4 .. :try_end_10} :catch_11
    # IOException goes to :catch_36
    .catch Ljava/io/IOException; {:try_start_4 .. :try_end_10} :catch_36

    # if NO exception, just pass through:
    .line 46
    .end local v1           #is:Ljava/io/InputStream;
    .end local v2           #url:Ljava/net/URL;

    .line 39
    # move the exception to v4, then to v0
    # all of the code below is what happens with an exception
    move-exception v4
    move-object v0, v4

    .line 41
    .local v0, e:Ljava/net/MalformedURLException;
    sget-object v4, Ljava/lang/System;->out:Ljava/io/PrintStream;

    new-instance v5, Ljava/lang/StringBuilder;
    const-string v6, "Invalid URL "
    invoke-direct {v5, v6}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
    invoke-virtual {v5, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    move-result-object v5

    const-string v6, ": "
    invoke-virtual {v5, v7}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    move-result-object v5

    invoke-virtual {v0}, Ljava/net/MalformedURLException;->getMessage()Ljava/lang/String;
    move-result-object v6

    invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    move-result-object v5

    invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
    move-result-object v5

    invoke-virtual {v4, v5}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    goto :goto_10

    .line 42
    .end local v0           #e:Ljava/net/MalformedURLException;
    # end exception code
    # begin exception code    
    move-exception v4
    move-object v0, v4

    .line 44
    .local v0, e:Ljava/io/IOException;
    sget-object v4, Ljava/lang/System;->out:Ljava/io/PrintStream;

    new-instance v5, Ljava/lang/StringBuilder;
    const-string v6, "Unable to execute "
    invoke-direct {v5, v6}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
    invoke-virtual {v5, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    move-result-object v5

    const-string v6, ": "
    invoke-virtual {v5, v7}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    move-result-object v5

    invoke-virtual {v0}, Ljava/io/IOException;->getMessage()Ljava/lang/String;
    move-result-object v6

    invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    move-result-object v5

    invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
    move-result-object v5

    invoke-virtual {v4, v5}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    goto :goto_10
    # end exception code
.end method