CalebFenton / simplify

Android virtual machine and deobfuscator
Other
4.45k stars 438 forks source link

[Bug] [SmaliVM] visitClassAnnotations() fail on parsing obfuscated member class names #161

Open fipso opened 3 years ago

fipso commented 3 years ago

Some obfuscators tend to rename member classes to short random strings like "Ab" or "Bc".

.class public final Lde/fipso/test/container/ui/ContainerActivity;
.super Landroidx/appcompat/app/AppCompatActivity;
.source "ContainerActivity.kt"

# annotations
.annotation system Ldalvik/annotation/MemberClasses;
    value = {
        Lde/fipso/test/rL;
    }
.end annotation

visitInnerClasses() then fails to parse the inner and outer class name, because we are not using the default class name format with the $ as the separator. (java.lang.ArrayIndexOutOfBoundsException: 1 -> ClassBuilder.java:150);

EDIT: This could be a baksmali issue.

CalebFenton commented 3 years ago

Thanks for the report. I'm also leaning towards this being a dexlib issue. If you can't reproduce with just baksmali, can you share a Dex file which causes the issue?

fipso commented 3 years ago

classes3.zip

Seems like this is a feature of the Axan Obfuscator, but again this could be a misbehaviour in the smali class name generation by baksmali or dexlib.

I was able to come up with a dirty fix for this problem. Basically, I am just passing the parents Class name as the outer class name and using the full member class name as the inner class name.

private void visitInnerClasses(String parentClassName, BuilderEncodedValues.BuilderTypeEncodedValue value,
                                   ClassWriter classWriter) {
        // String name, String outerName, String innerName, int access
        String internalName = value.getValue();
        String fullName = stripName(value.getValue());
        String[] parts = fullName.split("\\$", 2);
        String outerName;
        String innerName;
        if (parts.length == 2){
            outerName = parts[0];
            innerName = parts[1];
        }else{
            outerName = parentClassName;
            innerName = fullName;
        }

        System.out.println("visitInnerClasses(): " + outerName + "$" + innerName);

        boolean isAnonymous = innerName.equals("1") || Ints.tryParse(innerName) != null;
        if (isAnonymous) {
            innerName = null;
        }

        int innerAccess = classManager.getVirtualClass(internalName).getClassDef().getAccessFlags();
        classWriter.visitInnerClass(fullName, outerName, innerName, innerAccess);
    }
CalebFenton commented 3 years ago

Thanks for the extra detail and the fix. I'll have to read the java specs to see what's technically allowed and see if I can improve what you have here.

Also: I added smali syntax highlighting to github a while back so I changed your original comment to use it. :D