DexPatcher / dexpatcher-tool

Android Dalvik bytecode patcher.

Home Page:https://dexpatcher.github.io/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Another request to complete the 'class rewrite' feature

gurt-il opened this issue · comments

commented

Hi Lanchon,

First, I very appreciate your great job. You've saved me tons of time until now and for this, I'm thanking you.

Now to my suggestion. I've started to use the new 'class rewrite' feature and it's was really helpful. The only problem that left is when I'm trying to wrap some function, that in his signature I have the renamed class.
For example, I've renamed the class aa to class1.
Then I'm trying to wrap the function void function1(class1 input) but now I actually need to wrap the function void function1(aa input) because otherwise, I'm getting the error

Error:type 'com.testapp.class2': target 'com.testapp.bb': virtual method 'function1(com.testapp.class1):void': target 'a': (class2.java:27): target not found

So my request is when you checking for signatures for wrapping functions and etc., test the functions against the renamed names instead of the original names.

Thanks

hi,

thanks! sorry i can't really follow this up based on an imprecise english language description.

could you please post the exact, complete, tagged class and method definitions in the patch, and the error generated? their body is not needed. thanks again!

commented

OK

So this is the smali code of class aa:

.class public final synthetic Lcom/testapp/aa;
.super Ljava/lang/Object;
.source ""

# interfaces
.implements Landroid/content/DialogInterface$OnClickListener;

# direct methods
.method public constructor <init>()V
    .locals 0

    invoke-direct {p0}, Ljava/lang/Object;-><init>()V

    return-void
.end method


# virtual methods
.method public final onClick(Landroid/content/DialogInterface;I)V
    .locals 0
    .annotation runtime Ljava/lang/invoke/LambdaForm$Hidden;
    .end annotation

    invoke-virtual {p0}, Lcom/testapp/ax;->j()V

    return-void
.end method

And this is the smali code of class bb:

.class public final synthetic Lcom/testapp/bb;
.super Ljava/lang/Object;
.source ""


# instance fields
.field private final a:Lcom/testapp/aa;


# direct methods
.method public constructor <init>(Lcom/testapp/aa;)V
    .locals 0

    invoke-direct {p0}, Ljava/lang/Object;-><init>()V

    iput-object p1, p0, Lcom/testapp/bb;->a:Lcom/testapp/aa;

    return-void
.end method


# virtual methods
.method public final a(Lcom/testapp/aa;)V
    .locals 0
    .annotation runtime Ljava/lang/invoke/LambdaForm$Hidden;
    .end annotation

    iput-object p1, p0, Lcom/testapp/bb;->a:Lcom/testapp/aa;

    return-void
.end method

And this is the java code for class1

package com.testapp;

import lanchon.dexpatcher.annotation.DexAdd;
import lanchon.dexpatcher.annotation.DexEdit;
import lanchon.dexpatcher.annotation.DexIgnore;
import lanchon.dexpatcher.annotation.DexReplace;

@DexEdit(target = "aa", contentOnly = true)
public class class1 {
    @DexIgnore
    public class1() {
    }

    @DexReplace
    public InputStream b(boolean paramBoolean) {
        return null;
    }
}

And this is the java code for class2

package com.testapp;

import lanchon.dexpatcher.annotation.DexAdd;
import lanchon.dexpatcher.annotation.DexEdit;
import lanchon.dexpatcher.annotation.DexIgnore;
import lanchon.dexpatcher.annotation.DexReplace;

@DexEdit(target = "bb", contentOnly = true)
public class class2 {
    @DexIgnore
    public class2() {
    }

    @DexWrap(target = "a")
    public final void function1(class1 input) {
        function1(input);
        Log.d("debug", "on function1");
    }
}

And the error is

Error:type 'com.testapp.class2': target 'com.testapp.bb': virtual method 'function1(com.testapp.class1):void': target 'a': (class2.java:27): target not found

Hope this will be enough
Thanks

oh i see now, you had 2 classes! i thought there was a bug in dxp!! :)

if you did this it would work as you want:

@DexEdit(target = "bb", contentOnly = true)
public class class2 {
/*
    // THIS IS NOT NEEDED ANYMORE
    @DexIgnore
    public class2() {
    }
*/
    @DexWrap(target = "a")
    public final void function1(class2 input) {
        function1(input);
        Log.d("debug", "on function1");
    }
}

but no, class rewrite only rewrites the class being operated upon, and not the whole dex. there are design and implementation issues of why this is this way. imagine rewriting a 'source' class to rename it: if i rewrote the whole source dex, what is there to gain by the rename, if the clients in the source are going to look for the new name?

and the other way around, your case: what if the class has more than one target? i know this can't happen now, but it could in the future. until recently you couldn't target anonymous inner classes. now you can, but using an ugly name such as target="SomeClass$0", which is horrible because the 0 part might change with any recompile of the source.

imagine instead using target="SomeClass/java.lang.Runnable", which would be a query to find the target class that is nested in SomeClass and implements the Runnable interface. or targetQuery=@DexQuery(outer="SomeClass", type="java.lang.Runnable", referencedFrom="someMethod", minMatches=1, maxMatches=1).

the point is that query targets can generate more than one result on contentOnly=true items and there is no reason not to allow this. if there are multiple targets, which of those should be used to rewrite the complete patch dex? (multiple method targets also have their uses, if/when contentOnly=true is implemented for methods.)

the contract on rewrite is: only the item is rewritten, not the whole dex; no matter where the item comes from (patch or source). this is the absolute minimum change needed to keep the bytecode valid across a class rename.

in your case, you need to reference aa directly in class2:

@DexIgnore
public class NullInputStreamPatch {

  @DexEdit(target = "aa", contentOnly = true)
  public static class class1 {
      @DexReplace
      public InputStream b(boolean paramBoolean) {
          return null;
      }
  }

  @DexEdit(target = "bb", contentOnly = true)
  public static class class2 {
      @DexWrap(target = "a")
      public final void function1(aa input) {
          function1(input);
          Log.d("debug", "on function1");
      }
  }

}

if you can't access the aa name due to obfuscation issues, you can try using importSymbols=false and define class aa yourself with @DexIgnore.

the issue here is that you are abusing cross-class edits to handle obfuscation. i know that obfuscation is not really supported at this time in dexpatcher so there's frustration, but this request is IMHO the wrong way around it.

a well designed and complete implementation to work with obfuscation is needed for sure, and i have it planned. it's not implemented because it depends on dex2jar functionality that was broken when dex2jar moved from the 0.99 branch to the 2.0 branch. i'm sort of maintaining dex2jar lately already (the version you are using has fixes of mine), so i don't discard the possibility of fixing this myself. but it won't happen immediately: @DexCheck is higher priority for me, it's sort of a shame that it's not implemented yet, it's the last piece missing from a complete basic dxp-tool IMHO. a lot of important stuff could be added later to dxp-tool, but @DexCheck is urgent.

i'm closing, but you can continue the chat here anyway.

thanks for using dxp and for taking the time to report!

commented

Thank you