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