diff --git a/jadx-plugins/jadx-script/examples/scripts/deobf_from_tostring.jadx.kts b/jadx-plugins/jadx-script/examples/scripts/deobf_from_tostring.jadx.kts new file mode 100644 index 00000000000..8725bd62d96 --- /dev/null +++ b/jadx-plugins/jadx-script/examples/scripts/deobf_from_tostring.jadx.kts @@ -0,0 +1,89 @@ +/* + Rename class and fields using strings from toString() method +*/ + +import jadx.core.deobf.NameMapper +import jadx.core.dex.attributes.AFlag +import jadx.core.dex.attributes.nodes.RenameReasonAttr +import jadx.core.dex.info.FieldInfo +import jadx.core.dex.instructions.ConstStringNode +import jadx.core.dex.instructions.IndexInsnNode +import jadx.core.dex.instructions.InsnType +import jadx.core.dex.instructions.args.InsnWrapArg +import jadx.core.dex.nodes.InsnNode +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) { + if (mth.methodInfo.shortId == "toString()Ljava/lang/String;") { + val returnBlock = mth.exitBlock.predecessors.firstOrNull { it.contains(AFlag.RETURN) } + val lastInsn = returnBlock?.instructions?.lastOrNull() + if (lastInsn != null && lastInsn.type == InsnType.RETURN) { + val arg = lastInsn.getArg(0) + if (arg.isInsnWrap) { + val wrapInsn = (arg as InsnWrapArg).wrapInsn + if (wrapInsn.type == InsnType.STR_CONCAT) { + log.info { "Renaming using 'toString' in class: ${mth.parentClass}" } + processArgs(mth, wrapInsn) + } + } + } + } + } + + val clsSepRgx = Regex("[ ({:]") + + private fun processArgs(mth: MethodNode, wrapInsn: InsnNode): Boolean { + try { + var fldName: String? = null + for ((i, arg) in wrapInsn.arguments.withIndex()) { + val insn = arg.unwrap() ?: return false + if (i % 2 == 0) { + if (insn !is ConstStringNode) { + return false + } + var str = insn.string + if (i == 0) { + // class and first field name + val parts = str.split(clsSepRgx) + val clsName = parts[0] + if (NameMapper.isValidIdentifier(clsName)) { + mth.parentClass.run { + log.info { "rename class '$name' to '$clsName'" } + rename(clsName) + RenameReasonAttr.forNode(this).append("from toString()") + } + } + str = parts[1] + } + fldName = str.trim('\'', '=', ',', ' ', ':') + } else { + if (insn.type != InsnType.IGET) { + return false + } + val iget = insn as IndexInsnNode + val fldInfo = iget.index as FieldInfo + val fld = mth.parentClass.searchField(fldInfo) + if (fld != null && NameMapper.isValidIdentifier(fldName)) { + log.info { "rename field '${fld.name}' to '$fldName'" } + fld.rename(fldName) + RenameReasonAttr.forNode(fld).append("from toString()") + } + } + } + return true + } catch (e: Exception) { + log.error(e) { "Args process failed" } + return false + } + } +})