CalebFenton/simplify

Error from potential anti-debug smali?

Closed this issue · 7 comments

I was digging into yidun protector from one of the pull requests from APKiD -- I'm not positive that this is specific to yidun or not, however it was contained in the example application (d03545df8d7a82e5dfd6c9baf4a8a22e437e74bc).

The smali code which hits an error is as follows;

.class public Lcom/_;
.super Ljava/lang/Object;
.source "a.java"


# virtual methods
.method public a()V
    .registers 2

    .prologue
    .line 5
    double-to-int p3, v0

    move-wide/16 p54870, v0

    .line 6
    .local v0, "i":I
    nop

    .line 7
    return-void
.end method

The error presented is;

rigby:com diff$ java -jar ~/repo/simplify/simplify/build/libs/simplify-1.1.0.jar test/
Executing: Lcom/_;->a()V
Exception in thread "main" java.lang.RuntimeException: Real exception was thrown executing ExecutionNode{signature=Lcom/_;->a()V, op=double-to-int r4, r0, @=0} and was not handled. This could be a bug in smalivm.
Exception: java.lang.NullPointerException
	at org.cf.smalivm.NodeExecutor.execute(NodeExecutor.java:88)
	at org.cf.smalivm.MethodExecutor.execute(MethodExecutor.java:62)
	at org.cf.smalivm.VirtualMachine.execute(VirtualMachine.java:103)
	at org.cf.smalivm.VirtualMachine.execute(VirtualMachine.java:64)
	at org.cf.simplify.Launcher.executeClass(Launcher.java:191)
	at org.cf.simplify.Launcher.run(Launcher.java:153)
	at org.cf.simplify.Main.main(Main.java:14)

I'm guessing this is some type of anti-debug, anti-decompile, anti-something trick as it isn't used anywhere in the application. Seems odd and while it is valid smali - it is essentially a junk operation that would not be used by the application. I'd expect this to be just simplified out - however, simplify is choking on this.

Feel free to assign this to me, I can dig into it later, just creating this issue so I won't forget and can dig into it later.

This is weird. Neither the double-to-int p3, v0 nor the move-wide/16 p54870, v0 are valid.

Does this come up in multiple apps obfuscated with the same protector?

Does IDA / dexlib2 / dexdump fail?

Is that method ever called? Honestly, I'm surprised it doesn't fail validation because that p54870 register index is so high and both it and p3 are outside of the registers 2 range.

Yea agreed - nothing should be valid when executing which is why I assume it's tryin to kill some tools.

Specifically, this code is never referenced (so likely not used? I didn't fully unpack the protector) - so I'm assuming it's not used...

dexlib2 handles this fine since baksmali worked on it.

No access to IDA right now or dexdump.

This actually would be a cool detection use case for a protector or anti trick if APKiD or something detected register ranges outside the context of the function.

Definitely anti-analysis techniques, looking at more files, it would appear dexlib2 is stripping out bad opcodes and replacing them with nop;

.class public La/_;
.super Ljava/lang/Object;
.source "a.java"


# virtual methods
.method public a()V
    .registers 2

    .prologue
    .line 5
    #Method index out of bounds: 50062
    #invoke-direct {}, method@50062
    nop

    .line 6
    .local v0, "i":I
    nop

    nop

    .line 7
    return-void
.end method
.class public La/_;
.super Ljava/lang/Object;
.source "a.java"


# virtual methods
.method public a()V
    .registers 2

    .prologue
    .line 5
    or-int/2addr v0, p11

    if-gt p4, p14, :cond_6c34

    .line 6
    .local v0, "i":I
    nop

    nop

    .line 7
    return-void
    :cond_6c34
.end method
.class public Lcom/_;
.super Ljava/lang/Object;
.source "a.java"


# virtual methods
.method public a()V
    .registers 2

    .prologue
    .line 5
    double-to-int p3, v0

    move-wide/16 p54870, v0

    .line 6
    .local v0, "i":I
    nop

    .line 7
    return-void
.end method
.class public La/_;
.super Ljava/lang/Object;
.source "a.java"


# virtual methods
.method public a()V
    .registers 2

    .prologue
    .line 5
    nop

    .line 6
    .local v0, "i":I
    nop

    nop

    .line 7
    return-void
.end method
.class public Lcom/_;
.super Ljava/lang/Object;
.source "a.java"


# virtual methods
.method public a()V
    .registers 2

    .prologue
    .line 5
    nop

    .line 6
    .local v0, "i":I
    nop

    nop

    .line 7
    return-void
.end method

Thanks for all the extra info. This would probably be hard to fix perfectly in dexlib. Simplify could probably check for invalid register references and skip the method.

I think I made a mistake using validation, I think the better word is verification, as in android wouldn't even load this dex file. Does this sample actually run?

As I dug into this more, the tactic appears to be the same I outlined in https://www.strazzere.com/papers/DexEducation-PracticingSafeDex.pdf way back when. Essentially this is code that is in the dex file, however it is always placed in an unexecuted class. Since this method/class is never actually referenced the Dalvik verifier never checks the validity of the code inside it. However this would kill jad/baksmali/jeb/etc at the time. The fixes for all of these were to just skip over invalidate op codes.

Edit:

So, TLDR, the sample does run however, this code is never invoked or accessed.

Thanks for digging into this. Makes sense now. Would be a cool rule for apkid.

I'm closing this because there's a work-around. Simplify could be improved to give better error messages, but it looks like there are many, many different ways the bytecode could be invalid, and adding checks for all of them would increase complexity a lot. I'd rather put the burden on the expert using this to exclude invalid classes.