quickvm is a little tool that interprets Smali disassembly,like a real Dalvik VM would.
The main motivation to write this was that some obfuscatorsencrypt string constants, putting code at the static constructorthat decrypts them and stores them into static fields. Here's anexample of a disassembled class with an encrypted string:
...# static fields.fieldprivate static finalz:Ljava/lang/String;# direct methods.methodstatic constructor<clinit>()V.locals5const-string/jumbov0,"\u0011\u00172x\u00031\u001c{f\u0001\u007f\u0008}{\u0012\u007f\u0019di\u000f3\u0019pd\u0003"invoke-virtual {v0},Ljava/lang/String;->toCharArray()[Cmove-result-objectv0array-lengthv1,v0const/4v2,0x0movev3,v2movev2,v1move-objectv1,v0:goto_0if-gtv2,v3,:cond_0new-instancev0,Ljava/lang/String;invoke-direct {v0,v1},Ljava/lang/String;-><init>([C)Vinvoke-virtual {v0},Ljava/lang/String;->intern()Ljava/lang/String;move-result-objectv0sput-objectv0,Lde/greenrobot/event/d;->z:Ljava/lang/String;return-void:cond_0aget-charv4,v1,v3rem-int/lit8v0,v3,0x5packed-switchv0,:pswitch_data_0const/16v0,0x66:goto_1xor-int/2addrv0,v4int-to-charv0,v0aput-charv0,v1,v3add-int/lit8v0,v3,0x1movev3,v0goto:goto_0:pswitch_0const/16v0,0x5fgoto:goto_1:pswitch_1const/16v0,0x78goto:goto_1:pswitch_2const/16v0,0x12goto:goto_1:pswitch_3const/16v0,0x8goto:goto_1nop:pswitch_data_0.packed-switch0x0:pswitch_0:pswitch_1:pswitch_2:pswitch_3 .end packed-switch.end method...
quickvm's will interpret statements in the smali file, simulating a VMexecuting the static constructor, and tell you the values of static fieldsas the code initializes them. For the above class, quickvm would report:
Trying file: de/greenrobot/event/d.smaliLde/greenrobot/event/d;->z:Ljava/lang/String; = "No pending post available"
A nice advantage of this method is that it doesn't depend on any obfuscatorin particular. It will decrypt them as long as the decryption takes placefrom the static constructor (but see limitations below).
quickvm can work with (and print) all primitive values (booleans, chars andintegers), plus strings, arrays and some common containers likeArrayList
.Booleans and chars are printed as integers. Example:
Lde/greenrobot/event/util/h;->b:Z = 1Lde/greenrobot/event/d;->z:Ljava/lang/String; = "No pending post available"Lde/greenrobot/event/m;->z:[Ljava/lang/String; = [ "PostThread", "BackgroundThread", "MainThread", "Async" ]Lde/greenrobot/event/util/h;->a:I = 0Lorg/whispersystems/O;->a:[B = [ 1, 59, 3 ]Lorg/whispersystems/bF;->a:I = 16777215
So, even when the constants are not encrypted, it's still handy to have themin readable form, instead of having to read the static constructor.
Build the code (runningant jar
should suffice) and then change to the rootof the smali disassembly (this root will usually containcom/
,org/
, et all).From there:
find | grep '\.smali$' | java -jar ~/path/to/quickvm/dist/quickvm.jar . > constants.txt
And watch quickvm try to load each of your classes, and probably fail onmost of them. Constants will be saved toconstants.txt
.
If quickvm fails on most classes, it's probably because of someinstruction or API call that the obfuscator likes to use and is not implemented.It should be implemented inOps.java
andMethods.java
respectively.
If most classes complete without errors (quickvm just moves to the next one)then good news! Even if some classes still fail, decryption is usually performedright at the beginning of the static constructor, so by the time those classesfail (see limitations below), their constants have probably been decryptedand printed already. However, reviewing those classes manually is still a good idea...
When running this at big codebases (500+ classes), it's better to skip loadingclasses you don't care about, such as support library, google, etc. It's also betterto log the error stream as well, so we can review the errors later.
find com/provider1 | grep '\.smali$' | java -jar ~/path/to/quickvm/dist/quickvm.jar . > constants.txt 2> status.txt
Although a useful tool, it's still very limited for a VM:
No notion of type inheritance, so type casts, method calls, etc.can not be implemented with the current design.
The VM does not have exceptions, and implementing them would requiresignificant refactoring and such.
No STL, only a few methods implemented inMethods.java
. If the Smali callsany other method of the STL, you'll have to implement it too.
Slow as fuck. Which is expected, knowing this "VM" runs off Smali codeinstead of actual bytecode, doesn't have a GC, isn't written with efficiencyin mind, and was coded in a few hours.
A lot of opcodes are not implemented yet. 64-bit operations, floatingarithmetic, branching... Again, I hacked this up for my needs rather thanas a serious project.
In a way, some of these limitations were actuallyintended, because the aimof this tool is to extract static constants. By not implementing I/O, threads,or other fancy features, we can be sure that constants extracted by quickvmdo not depend on the environment in any way. This is the fundamental differencebetween using this tool and making a memory dump of the Dalvik VM while the appis loaded.
Issues and improvements are welcome. Keep in mind this is just a tool, and it'sespecially crappy. It can (and will) miss interesting stuff, so take the resultswith a pinch of salt. It's a good idea to make sure to only run this at Smalicode coming straight from the disassembler, and make sure the assembled coderuns on a real Android VM without crashing.