leibnitz27 / cfr

This is the public repository for the CFR Java decompiler
https://www.benf.org/other/cfr
MIT License
1.94k stars 249 forks source link

$$Lambda$ class files #235

Closed ghost closed 3 years ago

ghost commented 3 years ago

CFR version

CFR 0.151

Compiler

unknown, java 8?

Description

When decompiling .jar file (decompiled with dex2jar, also tried enjarify, same output), i get multiple $$Lambda$ .class files along with some .class files, but in code clogged with unreadable code. But when i ran in procyon, it decompiled without those $$Lambda$ .class files, and at least got all in one place, in java, even if not perfect. So it must have to do with Java 8 lambda expressions, or whatever it is.

Example

this is file content

ls
 InstanceSettings.java      
Service.java                      
 'SettingsActivity$$Lambda$14.java'  
'SettingsActivity$$Lambda$5.java'  
 SettingsActivity.java
'Provider$$Lambda$1.java'  
'SettingsActivity$$Lambda$10.java'  
'SettingsActivity$$Lambda$1.java'  
 'SettingsActivity$$Lambda$6.java'   
Settings.java
'Provider$$Lambda$2.java' 
 'SettingsActivity$$Lambda$11.java'  
'SettingsActivity$$Lambda$2.java'   
'SettingsActivity$$Lambda$7.java'
 Provider.java            
 'SettingsActivity$$Lambda$12.java' 
 'SettingsActivity$$Lambda$3.java'  
 'SettingsActivity$$Lambda$8.java'
 RemoteViewsFactory.java   
'SettingsActivity$$Lambda$13.java'  
'SettingsActivity$$Lambda$4.java'   
'SettingsActivity$$Lambda$9.java'

and just a small fraction of code, to see how it looks in code

final class RandomVersePopup$$Lambda$1
implements CompoundButton.OnCheckedChangeListener {
    private final RadioButton arg$1;

    private RandomVersePopup$$Lambda$1(RadioButton radioButton) {
        this.arg$1 = radioButton;
    }

    private static CompoundButton.OnCheckedChangeListener get$Lambda(RadioButton radioButton) {
        return new RandomVersePopup$$Lambda$1(radioButton);
    }

    public static CompoundButton.OnCheckedChangeListener lambdaFactory$(RadioButton radioButton) {
        return new RandomVersePopup$$Lambda$1(radioButton);
    }

    @LambdaForm.Hidden
    public void onCheckedChanged(CompoundButton compoundButton, boolean bl) {
        RandomVersePopup.access$lambda$0(this.arg$1, compoundButton, bl);
    }
leibnitz27 commented 3 years ago

Doesn't look much like regular lambdas. If I had to guess (and it really is a guess), I'd say lambdas back ported to jre7..... (you may find there are no invokedynamic instructions in the bytecode).

I'm afraid there's not much to go on there, I'd need a sample Jar (one that can be legally shared only please!!)

On Tue, 16 Mar 2021, 13:47 Igor Lerinc, @.***> wrote:

CFR version

CFR 0.151 Compiler

unknown, java 8? Description

When decompiling .jar file (decompiled with dex2jar, also tried enjarify, same output), i get multiple $$Lambda$ .class files along with some .class files, but in code clogged with unreadable code. But when i ran in procyon, it decompiled without those $$Lambda$ .class files, and at least got all in one place, in java, even if not perfect. So it must have to do with Java 8 lambda expressions, or whatever it is. Example

this is file content

ls InstanceSettings.java Service.java 'SettingsActivity$$Lambda$14.java' 'SettingsActivity$$Lambda$5.java' SettingsActivity.java 'Provider$$Lambda$1.java' 'SettingsActivity$$Lambda$10.java' 'SettingsActivity$$Lambda$1.java' 'SettingsActivity$$Lambda$6.java' Settings.java 'Provider$$Lambda$2.java' 'SettingsActivity$$Lambda$11.java' 'SettingsActivity$$Lambda$2.java' 'SettingsActivity$$Lambda$7.java' Provider.java 'SettingsActivity$$Lambda$12.java' 'SettingsActivity$$Lambda$3.java' 'SettingsActivity$$Lambda$8.java' RemoteViewsFactory.java 'SettingsActivity$$Lambda$13.java' 'SettingsActivity$$Lambda$4.java' 'SettingsActivity$$Lambda$9.java'

and just a small fraction of code, to see how it looks in code

final class RandomVersePopup$$Lambda$1 implements CompoundButton.OnCheckedChangeListener { private final RadioButton arg$1;

private RandomVersePopup$$Lambda$1(RadioButton radioButton) {
    this.arg$1 = radioButton;
}

private static CompoundButton.OnCheckedChangeListener get$Lambda(RadioButton radioButton) {
    return new RandomVersePopup$$Lambda$1(radioButton);
}

public static CompoundButton.OnCheckedChangeListener lambdaFactory$(RadioButton radioButton) {
    return new RandomVersePopup$$Lambda$1(radioButton);
}

@LambdaForm.Hidden
public void onCheckedChanged(CompoundButton compoundButton, boolean bl) {
    RandomVersePopup.access$lambda$0(this.arg$1, compoundButton, bl);
}

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/leibnitz27/cfr/issues/235, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABFXCEF2ASTX2KJ2OK774LDTD5OQTANCNFSM4ZISZQ6A .

ghost commented 3 years ago

bible pentest, example jar.zip

here, is example .jar file, i work closely with developer, denys dolganenko, he asked me to do pentesting on app security (including reverse engeenering), and this is just part, where $$Lambda$ is most commonly happening.

Lanchon commented 3 years ago

lee, maybe u never heard of retrolambda?

ghost commented 3 years ago

does cfr support retrolambda? or i'm missing some options while decompiling it?

leibnitz27 commented 3 years ago

lee, maybe u never heard of retrolambda?

Guess not ;)

I'll have a look.

leibnitz27 commented 3 years ago

Oh yeah, that's fairly obvious - so - yes, it does look like a syntactic sugar transpiling (I.e. retrolambda as @Lanchon says)

The reason you see these classes (and the synthetic methods) is that CFR will only remove them if I'm convinced they've been re-sugared; slightly less paranoid folk might ignore classes with $ in them, as they look like synthetic/inner classes. (and if you remove the synthetics without undoing the transform, you're losing a lot of information, which unfortunately I think is what Procyon for example is doing).

I.e. if you look at Profile.java

    private void confirmAndDeleteSelectedItems() {
        AlertDialog.Builder builder = new AlertDialog.Builder((Context)this);
        builder.setTitle(2131297197);
        int n = this.getSelectedItemsCount();
        if (n == 1) {
            builder.setMessage((CharSequence)this.getString(2131296860, new Object[]{((Map)this.itemsData.get(this.getSelectedItemIndex())).get("name")}));
        } else {
            builder.setMessage((CharSequence)this.getString(2131296861, new Object[]{n}));
        }
        builder.setNegativeButton(2131296302, null);
        builder.setPositiveButton(2131296306, Profiles$$Lambda$6.lambdaFactory$((Profiles)this));
        builder.show();
    }

Lambda$6's lambdaFactory returns something which eventually generates an anonymous class that calls Profiles.access$lambda$3 -> lambda$confirmAndDeleteSelectedItems$2 -> deleteSelectedItems

builder.setPositiveButton(2131296306, Profiles$$Lambda$6.lambdaFactory$((Profiles)this));

--> is really -->

builder.setPositiveButton(2131296306, () -> deleteSelectedItems());

I'm a bit torn about this one; the problem with bolt-on/transpiling like this is that the source language isn't java (You know what I mean - it's something that's turned into Java, then compiled ;) ), so undoing this actually isn't decompiling ;).

(And it's a dangerous road to start down - for example, I do NOT want to be undoing all of the things that (picking a random one) Project Lombok could do).

Having said that, it's wacky and interesting, so I'll see if there's a nice way of doing it, maybe optionally.

x4e commented 3 years ago

Why is retrolambda being used here? The purpose of it is to make decompilers that don't support indy (and similar) support it - CFR already does so there does not seem to be any need.

leibnitz27 commented 3 years ago

The purpose of it is to make decompilers that don't support indy (and similar) support it - CFR already does so there does not seem to be any need.

Compilers, shurely? It exists (IIUC) to allow lambda-containing code to be targetted nicely at pre-invokedynamic jvms.

The interesting thing is that the OUTPUT of retrolambda is a mess of temporary classes, etc. But (as I say above), that's the java that's being compiled, so CFR is faithfully decompiling it. It's just not super useful. ;)

Lanchon commented 3 years ago

the problem with bolt-on/transpiling like this is that the source language isn't java

project lombok (it still exists?) definitely is a new language. but AFAIR retrolambda takes Java 8 bytecode as input? then reworks the bytecode to be java 7. if this is true, javac + retrol could be simply considered a different java 8 compiler, and then there is a case for better recovery of source.

resugaring is always optional for correctness (and its great to be able to turn it off for lay people -me- to see first hand how desugaring works) , but u seem to always prefer resugaring when possible.

x4e commented 3 years ago

Compilers, shurely? It exists (IIUC) to allow lambda-containing code to be targetted nicely at pre-invokedynamic jvms.

Sorry! I got confused between retrolambda and retroindy.

The interesting thing is that the OUTPUT of retrolambda is a mess of temporary classes, etc. But (as I say above), that's the java that's being compiled, so CFR is faithfully decompiling it. It's just not super useful. ;)

I guess this is a bigger problem. Should CFR be a simple JVM decompiler, or should it be targetted to allow decompilation of specific compilers output (e.g. Java, RetroLambda, Kotlin, Lombok...). At the moment it is targetted for Java and some Kotlin.

The problem is whether it is practical to continue going down this route and add support for every possible compiler, which would have problems with distinguishing between the bytecode of different compilers, maintenance, exploitation (malicious code matching a compiler heuristic to create invalid output for example).

If it were me creating the decompiler it would just plain and simply represent the bytecode. But I think CFR has a different purpose. It is just managing the scope of that purpose.

Lanchon commented 3 years ago

in my view you are confounding things. you mix alt language compilers with std java compilers. cfr always tried to deco all java compilers (javac and eclipse) while otherwise deco other languages to java. the point here is that javac+retrolambda is a (restricted) std java compiler.

leibnitz27 commented 3 years ago

I don't like the idea of undoing this sort of thing automatically (especially not something as complex as this), because I want to actually see the java represented by the bytecode.

That being said, I can imagine given code like the above, it's really useful to see what's really going on - so - there's an EXPLICIT argument sugarretrolambda, (hey, it'll probably never get used, but it's a nice toy to have).

I'm not going to enable this automatically, so I may add something in the future to warn it might be appropriate.

(also the code's a bit of a mess ;) )

Normal

    private void confirmAndDeleteSelectedItems() {
        AlertDialog.Builder builder = new AlertDialog.Builder((Context)this);
        builder.setTitle(2131297197);
        int n = this.getSelectedItemsCount();
        if (n == 1) {
            builder.setMessage((CharSequence)this.getString(2131296860, new Object[]{((Map)this.itemsData.get(this.getSelectedItemIndex())).get("name")}));
        } else {
            builder.setMessage((CharSequence)this.getString(2131296861, new Object[]{n}));
        }
        builder.setNegativeButton(2131296302, null);
        builder.setPositiveButton(2131296306, Profiles$$Lambda$6.lambdaFactory$((Profiles)this));
        builder.show();
    }

With --sugarretrolambda true

    private void confirmAndDeleteSelectedItems() {
        AlertDialog.Builder builder = new AlertDialog.Builder((Context)this);
        builder.setTitle(2131297197);
        int n = this.getSelectedItemsCount();
        if (n == 1) {
            builder.setMessage((CharSequence)this.getString(2131296860, new Object[]{this.itemsData.get(this.getSelectedItemIndex()).get(KEY_NAME)}));
        } else {
            builder.setMessage((CharSequence)this.getString(2131296861, new Object[]{n}));
        }
        builder.setNegativeButton(2131296302, null);
        builder.setPositiveButton(2131296306, (DialogInterface dialogInterface, int n) -> {
            this.deleteSelectedItems();
        });
        builder.show();
    }
Lanchon commented 3 years ago

it's perfect. yeah good call, enabled by default would convert java 7 code to java 8. great!

Lanchon commented 3 years ago

then again, decompilation is mostly done to understand the code, not to recompile it. and enabled helps understanding...

maybe youll find that sensible defaults are different for analyzing than for roundtripping. a general "roundtrip" option could disable all stuff like this while otherwise defaulting to analysis mode.