Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] Help with Script to rename Variables based on Constructor #2422

Open
marcohald opened this issue Feb 26, 2025 · 4 comments
Open

[feature] Help with Script to rename Variables based on Constructor #2422

marcohald opened this issue Feb 26, 2025 · 4 comments

Comments

@marcohald
Copy link

Describe your idea

I try to rename Variables based on the type of the Constructor.
For example this:

/* renamed from: T9.c0 */
/* loaded from: classes6.dex */
public final class C5365c0 {

    /* renamed from: a */
    public final connectionBroadcaster f14883a;

    /* renamed from: b */
    public final queueManager f14884b;

    /* renamed from: c */
    public boolean f14885c;

    public C5365c0(connectionBroadcaster connectionbroadcaster, queueManager queuemanager) {
        AbstractC6645l.notNull(connectionbroadcaster, "connectionBroadcaster");
        AbstractC6645l.notNull(queuemanager, "queueManager");
        this.f14883a = connectionbroadcaster;
        this.f14884b = queuemanager;
    }

should become

public final class C5365c0 {

    /* renamed from: a */
    /* renamed from: f14883a */
    public final connectionBroadcaster connectionbroadcaster;

    /* renamed from: b */
    /* renamed from: f14884b */
    public final queueManager queuemanager;

    /* renamed from: c */
    public boolean f14885c;

    public C5365c0(connectionBroadcaster connectionbroadcaster, queueManager queuemanager) {
        AbstractC6645l.notNull(connectionbroadcaster, "connectionBroadcaster");
        AbstractC6645l.notNull(queuemanager, "queueManager");
        this.connectionbroadcaster = connectionbroadcaster;
        this.queuemanager = queuemanager;
    }

What I tried so far is:

val jadx = getJadxInstance()

val targetClass = jadx.classes.find { it.fullName.contains("C5365c0") }


log.warn { "$targetClass" }
if (targetClass != null) {
	for (method in targetClass.methods) {
		if (method.name == "<init>") { // Find the constructor
			log.warn("Processing constructor: ${method.fullName}")
			log.warn(method.arguments.toString())
			val fields = targetClass.fields 
			for (field in targetClass.fields) {
				if (field.name.length <= 8 && field.name.startsWith("f")) {
					println("${field.type.toString().substringAfterLast('.')} ${field.name} matches conditions")
					//val fld = targetClass.searchField(field.name)
					//println("$fld test")
				} else {
					println("${field.type} ${field.name} doesnt matches conditions")
				}
			}
			log.warn(fields.toString())
		}
	}
}

Output:

WARN : p368T9.C5365c0
WARN : Processing constructor: T9.c0.<init>
WARN : [p368T9.connectionBroadcaster, p368T9.queueManager]
INFO : connectionBroadcaster f14883a matches conditions
INFO : queueManager f14884b matches conditions
INFO : boolean f14885c matches conditions
WARN : [T9.c0.a :T9.p, T9.c0.b :T9.V, T9.c0.c :boolean]

But here i'm stuck with unable to rename the field variable, as there seems no rename function.

I tried another approach with this code:

import jadx.core.dex.nodes.MethodNode
import jadx.plugins.script.runtime.data.ScriptOrderedDecompilePass

val jadx = getJadxInstance()

// StringBuilder chain replaced by STR_CONCAT instruction in SimplifyVisitor
// Search for return with STR_CONCAT and process args
jadx.addPass(object : ScriptOrderedDecompilePass(
	jadx,
	"DeobfFromToString",
	runAfter = listOf("SimplifyVisitor"),
) {

	override fun visit(mth: MethodNode) {
		val targetClassFound = mth.parentClass.fullName.contains("C5365c0")
		if (targetClassFound && mth.name == "<init>") {
			val targetClass = mth.parentClass
			log.warn { "$targetClass" }
			log.warn("Processing constructor: $mth")
			val fields = targetClass.fields // Get all fields in the class
			for (field in targetClass.fields) {
				if (field.name.length <= 8 && field.name.startsWith("f")) {
					println("${field.type.toString().substringAfterLast('.')} ${field.name} matches conditions")
				} else {
					println("${field.type} ${field.name} doesnt matches conditions")
				}
			}
			log.warn(fields.toString())
		}
	}
})

But at this Point the Constructor Types seems not to be replaced yet:

WARN : T9.c0
WARN : Processing constructor: T9.c0.<init>(T9.p, T9.V):void
INFO : T9.p a doesnt matches conditions
INFO : T9.V b doesnt matches conditions
INFO : boolean c doesnt matches conditions
WARN : [T9.c0.a :T9.p, T9.c0.b :T9.V, T9.c0.c :boolean]
WARN : T9.c0
WARN : Processing constructor: T9.c0.<init>(T9.p, T9.V):void
INFO : T9.p a doesnt matches conditions
INFO : T9.V b doesnt matches conditions
INFO : boolean c doesnt matches conditions
WARN : [T9.c0.a :T9.p, T9.c0.b :T9.V, T9.c0.c :boolean]
@skylot
Copy link
Owner

skylot commented Feb 26, 2025

@marcohald as I understand, you want to rename fields to variable name used in constructor on field init.
Simple script to do this:

import jadx.core.deobf.NameMapper
import jadx.core.dex.attributes.nodes.RenameReasonAttr
import jadx.core.dex.info.FieldInfo
import jadx.core.dex.instructions.IndexInsnNode
import jadx.core.dex.instructions.InsnType
import jadx.core.dex.instructions.args.RegisterArg
import jadx.core.dex.nodes.ClassNode
import jadx.core.dex.nodes.MethodNode
import jadx.plugins.script.runtime.data.ScriptOrderedDecompilePass

val jadx = getJadxInstance()

jadx.addPass(object : ScriptOrderedDecompilePass(
	jadx,
	"DeobfFieldFromVariableName",
	runAfter = listOf("SimplifyVisitor"),
) {
	override fun visit(mth: MethodNode) {
		val parentClass = mth.parentClass
		if (parentClass.name == "CLASS_NAME_CHANGE_ME!" && mth.isConstructor) {
			for (block in mth.basicBlocks) {
				for (insn in block.instructions) {
					if (insn.type == InsnType.IPUT) {
						renameByVarName(insn as IndexInsnNode, parentClass)
					}
				}
			}
		}
	}

	private fun renameByVarName(fldPut: IndexInsnNode, parentClass: ClassNode) {
		val fld = fldPut.index as FieldInfo
		if (fld.declClass == parentClass.classInfo) {
			val arg = fldPut.getArg(0)
			if (arg is RegisterArg) {
				val varName = arg.name
				if (NameMapper.isValidIdentifier(varName)) {
					val fldNode = parentClass.searchField(fld)
					fldNode.rename(varName)
					RenameReasonAttr.forNode(fldNode).append("by deobf script")
					log.info { "Rename field $fldNode to $varName" }
				}
			}
		}
	}
})

Also, such rename looks very useful and it will be nice to implement such rename in jadx, I will do this 👍

@jackwpa
Copy link

jackwpa commented Feb 26, 2025

Also, such rename looks very useful and it will be nice to implement such rename in jadx, I will do this

Please make this feature optional. This string metadata should not be trusted: code protectors can change those strings to anything they'd like, including bad or irrelevant names that could bring even more confusion.

@marcohald
Copy link
Author

@skylot I would definitely vote for an optional feature.
I have some struggles to get the Script working.
It seems the varName could not be obtained.
I run this slightly modified Script

import jadx.core.deobf.NameMapper
import jadx.core.dex.attributes.nodes.RenameReasonAttr
import jadx.core.dex.info.FieldInfo
import jadx.core.dex.instructions.IndexInsnNode
import jadx.core.dex.instructions.InsnType
import jadx.core.dex.instructions.args.RegisterArg
import jadx.core.dex.nodes.ClassNode
import jadx.core.dex.nodes.MethodNode
import jadx.plugins.script.runtime.data.ScriptOrderedDecompilePass

val jadx = getJadxInstance()


jadx.addPass(object : ScriptOrderedDecompilePass(
	jadx,
	"DeobfFieldFromVariableName",
	runAfter = listOf("SimplifyVisitor"),
) {


	override fun visit(mth: MethodNode) {
		val parentClass = mth.parentClass
		if (parentClass.fullName.contains("C5365c0") && mth.isConstructor) {
			log.warn { "$parentClass" }
			log.warn("Processing constructor: $mth")
			for (block in mth.basicBlocks) {
				for (insn in block.instructions) {
					if (insn.type == InsnType.IPUT) {
						renameByVarName(insn as IndexInsnNode, parentClass)
					}
				}
			}
		}
	}

	private fun renameByVarName(fldPut: IndexInsnNode, parentClass: ClassNode) {
		val fld = fldPut.index as FieldInfo
		
		log.warn("Processing fld: $fld")
		if (fld.declClass == parentClass.classInfo) {
			log.warn("Processing fldPut: $fldPut")
			val arg = fldPut.getArg(0)
			log.warn("Processing arg: $arg")
			if (arg is RegisterArg) {
				val varName = arg.name
				log.warn("Processing varName: $varName")
				if (NameMapper.isValidIdentifier(varName)) {
					val fldNode = parentClass.searchField(fld)
					fldNode.rename(varName)
					RenameReasonAttr.forNode(fldNode).append("by deobf script")
					log.warn { "Rename field $fldNode to $varName" }
				}
			}
		}
	}
})

With this Output

WARN : T9.c0
WARN : Processing constructor: T9.c0.<init>(T9.p, T9.V):void
WARN : Processing fld: T9.c0.a T9.p
WARN : Processing fldPut: 0x000d: IPUT (r2v0 T9.p), (r1v0 'this' T9.c0 A[IMMUTABLE_TYPE, THIS]) T9.c0.a T9.p
WARN : Processing arg: (r2v0 T9.p)
WARN : Processing varName: null
WARN : Processing fld: T9.c0.b T9.V
WARN : Processing fldPut: 0x000f: IPUT (r3v0 T9.V), (r1v0 'this' T9.c0 A[IMMUTABLE_TYPE, THIS]) T9.c0.b T9.V
WARN : Processing arg: (r3v0 T9.V)
WARN : Processing varName: null
WARN : T9.c0
WARN : Processing constructor: T9.c0.<init>(T9.p, T9.V):void
WARN : Processing fld: T9.c0.a T9.p
WARN : Processing fldPut: 0x000d: IPUT (r2v0 T9.p), (r1v0 'this' T9.c0 A[IMMUTABLE_TYPE, THIS]) T9.c0.a T9.p
WARN : Processing arg: (r2v0 T9.p)
WARN : Processing varName: null
WARN : Processing fld: T9.c0.b T9.V
WARN : Processing fldPut: 0x000f: IPUT (r3v0 T9.V), (r1v0 'this' T9.c0 A[IMMUTABLE_TYPE, THIS]) T9.c0.b T9.V
WARN : Processing arg: (r3v0 T9.V)
WARN : Processing varName: null

I'm running this nightly:

Image

@skylot
Copy link
Owner

skylot commented Feb 27, 2025

It seems the varName could not be obtained.

Well, now variable names is available only if set in debug info or in ProcessKotlinInternals pass from kotlin intrinsics method (I thought that was a case in your sample). Other names set during code generation phase and can't be accessed in passes (i.e both from scripts or plugins). I plan to move code for variable names apply into separate pass, so it will be possible to access them before codegen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants