From bc7300bd01e88f5af31557ab720149101f019537 Mon Sep 17 00:00:00 2001 From: Skylot Date: Mon, 25 Jul 2022 12:49:46 +0100 Subject: [PATCH] feat: add package node, allow to rename packages --- .../main/java/jadx/core/codegen/ClassGen.java | 2 +- .../java/jadx/core/dex/info/FieldInfo.java | 4 - .../java/jadx/core/dex/info/InfoStorage.java | 12 ++ .../java/jadx/core/dex/info/PackageInfo.java | 90 +++++++++++ .../java/jadx/core/dex/nodes/ClassNode.java | 18 ++- .../jadx/core/dex/nodes/IPackageUpdate.java | 6 + .../java/jadx/core/dex/nodes/PackageNode.java | 150 ++++++++++++++++++ .../java/jadx/core/dex/nodes/RootNode.java | 22 +++ .../jadx-script/examples/build.gradle.kts | 3 + .../examples/scripts/deobf2.jadx.kts | 39 +++++ .../plugins/script/runtime/data/rename.kt | 11 +- .../jadx/plugins/script/runtime/script.kt | 3 + 12 files changed, 353 insertions(+), 7 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/core/dex/info/PackageInfo.java create mode 100644 jadx-core/src/main/java/jadx/core/dex/nodes/IPackageUpdate.java create mode 100644 jadx-core/src/main/java/jadx/core/dex/nodes/PackageNode.java create mode 100644 jadx-plugins/jadx-script/examples/scripts/deobf2.jadx.kts diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index b79be4a75..74350adf7 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -399,7 +399,7 @@ public class ClassGen { annotationGen.addForField(code, f); boolean addInfoComments = f.checkCommentsLevel(CommentsLevel.INFO); - if (f.getFieldInfo().isRenamed() && addInfoComments) { + if (f.getFieldInfo().hasAlias() && addInfoComments) { code.newLine(); CodeGenUtils.addRenamedComment(code, f, f.getName()); } diff --git a/jadx-core/src/main/java/jadx/core/dex/info/FieldInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/FieldInfo.java index 2e3b45e9e..9aca16df1 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/FieldInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/FieldInfo.java @@ -72,10 +72,6 @@ public final class FieldInfo { return declClass.makeRawFullName() + '.' + name + ':' + TypeGen.signature(type); } - public boolean isRenamed() { - return !name.equals(alias); - } - public boolean equalsNameAndType(FieldInfo other) { return name.equals(other.name) && type.equals(other.type); } diff --git a/jadx-core/src/main/java/jadx/core/dex/info/InfoStorage.java b/jadx-core/src/main/java/jadx/core/dex/info/InfoStorage.java index fdd7667d1..b0cb6ff0c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/InfoStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/InfoStorage.java @@ -3,6 +3,8 @@ package jadx.core.dex.info; import java.util.HashMap; import java.util.Map; +import org.jetbrains.annotations.Nullable; + import jadx.core.dex.instructions.args.ArgType; public class InfoStorage { @@ -14,6 +16,8 @@ public class InfoStorage { // can contain same method with different ids (from different files) private final Map methods = new HashMap<>(); + private final Map packages = new HashMap<>(); + public ClassInfo getCls(ArgType type) { return classes.get(type); } @@ -58,4 +62,12 @@ public class InfoStorage { return field; } } + + public @Nullable PackageInfo getPkg(String fullName) { + return packages.get(fullName); + } + + public void putPkg(PackageInfo pkg) { + packages.put(pkg.getFullName(), pkg); + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/info/PackageInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/PackageInfo.java new file mode 100644 index 000000000..96212d83b --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/info/PackageInfo.java @@ -0,0 +1,90 @@ +package jadx.core.dex.info; + +import java.util.Objects; + +import org.jetbrains.annotations.Nullable; + +import jadx.core.dex.nodes.RootNode; + +public class PackageInfo { + + private final @Nullable PackageInfo parentPkg; + private final String fullName; + private final String name; + + public static synchronized PackageInfo fromFullPkg(RootNode root, String fullPkg) { + PackageInfo existPkg = root.getInfoStorage().getPkg(fullPkg); + if (existPkg != null) { + return existPkg; + } + PackageInfo newPkg; + int lastDot = fullPkg.lastIndexOf('.'); + if (lastDot == -1) { + // unknown root pkg + newPkg = new PackageInfo(fullPkg, null, fullPkg); + } else { + PackageInfo parentPkg = fromFullPkg(root, fullPkg.substring(0, lastDot)); + newPkg = new PackageInfo(fullPkg, parentPkg, fullPkg.substring(lastDot + 1)); + } + root.getInfoStorage().putPkg(newPkg); + return newPkg; + } + + public static synchronized PackageInfo fromShortName(RootNode root, @Nullable PackageInfo parent, String shortName) { + String fullPkg = parent == null ? shortName : parent.getFullName() + '.' + shortName; + PackageInfo existPkg = root.getInfoStorage().getPkg(fullPkg); + if (existPkg != null) { + return existPkg; + } + PackageInfo newPkg = new PackageInfo(fullPkg, parent, shortName); + root.getInfoStorage().putPkg(newPkg); + return newPkg; + } + + private PackageInfo(String fullName, @Nullable PackageInfo parentPkg, String name) { + this.fullName = fullName; + this.parentPkg = parentPkg; + this.name = name; + } + + public boolean isRoot() { + return parentPkg == null; + } + + public boolean isDefaultPkg() { + return fullName.isEmpty(); + } + + public String getFullName() { + return fullName; + } + + public @Nullable PackageInfo getParentPkg() { + return parentPkg; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof PackageInfo)) { + return false; + } + return Objects.equals(fullName, ((PackageInfo) o).getFullName()); + } + + @Override + public int hashCode() { + return fullName.hashCode(); + } + + @Override + public String toString() { + return fullName; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index d3e33b7a7..78a02d43c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -54,11 +54,13 @@ import jadx.core.utils.exceptions.JadxRuntimeException; import static jadx.core.dex.nodes.ProcessState.LOADED; import static jadx.core.dex.nodes.ProcessState.NOT_LOADED; -public class ClassNode extends NotificationAttrNode implements IClassNode, ILoadable, ICodeNode, Comparable { +public class ClassNode extends NotificationAttrNode + implements IClassNode, ILoadable, ICodeNode, IPackageUpdate, Comparable { private final RootNode root; private final IClassData clsData; private final ClassInfo clsInfo; + private final PackageNode packageNode; private AccessInfo accessFlags; private ArgType superClass; private List interfaces; @@ -103,6 +105,7 @@ public class ClassNode extends NotificationAttrNode implements IClassNode, ILoad public ClassNode(RootNode root, IClassData cls) { this.root = root; this.clsInfo = ClassInfo.fromType(root, ArgType.object(cls.getType())); + this.packageNode = PackageNode.getForClass(root, clsInfo.getPackage(), this); this.clsData = cls.copy(); initialLoad(clsData); } @@ -236,6 +239,7 @@ public class ClassNode extends NotificationAttrNode implements IClassNode, ILoad this.fields = new ArrayList<>(); this.accessFlags = new AccessInfo(accessFlags, AFType.CLASS); this.parentClass = this; + this.packageNode = PackageNode.getForClass(root, clsInfo.getPackage(), this); } private void initStaticValues(List fields) { @@ -568,6 +572,18 @@ public class ClassNode extends NotificationAttrNode implements IClassNode, ILoad parentClass = this; } + @Override + public void onParentPackageUpdate(PackageNode updatedPkg) { + if (isInner()) { + return; + } + getClassInfo().changePkg(packageNode.getAliasPkgInfo().getFullName()); + } + + public PackageNode getPackageNode() { + return packageNode; + } + public ClassNode getTopParentClass() { ClassNode parent = getParentClass(); return parent == this ? this : parent.getTopParentClass(); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/IPackageUpdate.java b/jadx-core/src/main/java/jadx/core/dex/nodes/IPackageUpdate.java new file mode 100644 index 000000000..e7854b452 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/IPackageUpdate.java @@ -0,0 +1,6 @@ +package jadx.core.dex.nodes; + +public interface IPackageUpdate { + + void onParentPackageUpdate(PackageNode updatedPkg); +} diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/PackageNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/PackageNode.java new file mode 100644 index 000000000..ec7ab3a82 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/PackageNode.java @@ -0,0 +1,150 @@ +package jadx.core.dex.nodes; + +import java.util.ArrayList; +import java.util.List; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import jadx.core.dex.info.PackageInfo; + +public class PackageNode implements IPackageUpdate, IDexNode, Comparable { + + private final RootNode root; + private final PackageInfo pkgInfo; + private final @Nullable PackageNode parentPkg; + private final List subPackages = new ArrayList<>(); + private final List classes = new ArrayList<>(); + + private PackageInfo aliasPkgInfo; + + public static PackageNode getForClass(RootNode root, String fullPkg, ClassNode cls) { + PackageNode pkg = getOrBuild(root, fullPkg); + pkg.getClasses().add(cls); + return pkg; + } + + public static PackageNode getOrBuild(RootNode root, String fullPkg) { + PackageNode existPkg = root.resolvePackage(fullPkg); + if (existPkg != null) { + return existPkg; + } + PackageInfo pgkInfo = PackageInfo.fromFullPkg(root, fullPkg); + PackageNode parentPkg = getParentPkg(root, pgkInfo); + PackageNode pkgNode = new PackageNode(root, parentPkg, pgkInfo); + if (parentPkg != null) { + parentPkg.getSubPackages().add(pkgNode); + } + root.addPackage(pkgNode); + return pkgNode; + } + + private static @Nullable PackageNode getParentPkg(RootNode root, PackageInfo pgkInfo) { + return root.resolvePackage(pgkInfo.getParentPkg()); + } + + private PackageNode(RootNode root, @Nullable PackageNode parentPkg, PackageInfo pkgInfo) { + this.root = root; + this.parentPkg = parentPkg; + this.pkgInfo = pkgInfo; + } + + public void rename(String alias) { + rename(alias, true); + } + + public void rename(String alias, boolean runUpdates) { + if (pkgInfo.getName().equals(alias)) { + aliasPkgInfo = pkgInfo; + return; + } + aliasPkgInfo = PackageInfo.fromShortName(root, getParentAliasPkgInfo(), alias); + if (runUpdates) { + updatePackages(this); + } + } + + @Override + public void onParentPackageUpdate(PackageNode updatedPkg) { + aliasPkgInfo = PackageInfo.fromShortName(root, getParentAliasPkgInfo(), aliasPkgInfo.getName()); + updatePackages(updatedPkg); + } + + public void updatePackages() { + updatePackages(this); + } + + private void updatePackages(PackageNode updatedPkg) { + for (PackageNode subPackage : subPackages) { + subPackage.onParentPackageUpdate(updatedPkg); + } + for (ClassNode cls : classes) { + cls.onParentPackageUpdate(updatedPkg); + } + } + + public PackageInfo getPkgInfo() { + return pkgInfo; + } + + public PackageInfo getAliasPkgInfo() { + return aliasPkgInfo; + } + + public PackageNode getParentPkg() { + return parentPkg; + } + + public @Nullable PackageInfo getParentAliasPkgInfo() { + return parentPkg == null ? null : parentPkg.aliasPkgInfo; + } + + public List getSubPackages() { + return subPackages; + } + + public List getClasses() { + return classes; + } + + @Override + public String typeName() { + return "package"; + } + + @Override + public RootNode root() { + return root; + } + + @Override + public String getInputFileName() { + return ""; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof PackageNode)) { + return false; + } + return pkgInfo.equals(((PackageNode) o).pkgInfo); + } + + @Override + public int hashCode() { + return pkgInfo.hashCode(); + } + + @Override + public int compareTo(@NotNull PackageNode other) { + return getPkgInfo().getFullName().compareTo(other.getPkgInfo().getFullName()); + } + + @Override + public String toString() { + return getPkgInfo().getFullName(); + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java index d0519617f..f7ba4f35d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java @@ -1,6 +1,7 @@ package jadx.core.dex.nodes; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; @@ -37,6 +38,7 @@ import jadx.core.dex.info.ConstStorage; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.InfoStorage; import jadx.core.dex.info.MethodInfo; +import jadx.core.dex.info.PackageInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.utils.MethodUtils; import jadx.core.dex.nodes.utils.TypeUtils; @@ -78,6 +80,8 @@ public class RootNode implements IRootNode { private final Map clsMap = new HashMap<>(); private List classes = new ArrayList<>(); + private final Map pkgMap = new HashMap<>(); + private ClspGraph clsp; @Nullable private String appPackage; @@ -345,6 +349,24 @@ public class RootNode implements IRootNode { return notInnerClasses; } + public List getPackages() { + List list = new ArrayList<>(pkgMap.values()); + Collections.sort(list); + return list; + } + + public @Nullable PackageNode resolvePackage(String fullPkg) { + return pkgMap.get(fullPkg); + } + + public @Nullable PackageNode resolvePackage(@Nullable PackageInfo pkgInfo) { + return pkgInfo == null ? null : pkgMap.get(pkgInfo.getFullName()); + } + + public void addPackage(PackageNode pkg) { + pkgMap.put(pkg.getPkgInfo().getFullName(), pkg); + } + @Nullable public ClassNode resolveClass(ClassInfo clsInfo) { return clsMap.get(clsInfo); diff --git a/jadx-plugins/jadx-script/examples/build.gradle.kts b/jadx-plugins/jadx-script/examples/build.gradle.kts index 8219f693b..3a650d352 100644 --- a/jadx-plugins/jadx-script/examples/build.gradle.kts +++ b/jadx-plugins/jadx-script/examples/build.gradle.kts @@ -9,6 +9,9 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-script-runtime") implementation("io.github.microutils:kotlin-logging-jvm:3.0.2") + + // manual imports ( IDE can't import dependencies by scripts annotations) + implementation("com.github.javafaker:javafaker:1.0.2") } sourceSets { diff --git a/jadx-plugins/jadx-script/examples/scripts/deobf2.jadx.kts b/jadx-plugins/jadx-script/examples/scripts/deobf2.jadx.kts new file mode 100644 index 000000000..1bd626362 --- /dev/null +++ b/jadx-plugins/jadx-script/examples/scripts/deobf2.jadx.kts @@ -0,0 +1,39 @@ +// animal deobfuscator ^_^ +@file:DependsOn("com.github.javafaker:javafaker:1.0.2") + +import com.github.javafaker.Faker +import jadx.core.deobf.NameMapper +import java.util.Random + +val jadx = getJadxInstance() +jadx.args.isDeobfuscationOn = false +jadx.args.renameFlags = emptySet() + +val regex = """[Oo0]+""".toRegex() +val usedNames = mutableSetOf() +val faker = Faker(Random(1)) +var dups = 1 + +jadx.rename.all { name, node -> + when { + name matches regex -> { + val prefix = node.typeName().first() + val alias = faker.name().firstName().cap() + faker.animal().name().cap() + makeUnique(prefix, alias) + } + else -> null + } +} + +fun makeUnique(prefix: Char, name: String): String { + while (true) { + val resName = prefix + NameMapper.removeInvalidCharsMiddle(name) + return if (usedNames.add(resName)) resName else "$resName${dups++}" + } +} + +jadx.afterLoad { + println("Renames count: ${usedNames.size + dups}, names: ${usedNames.size}, dups: $dups") +} + +fun String.cap() = this.replaceFirstChar(Char::uppercaseChar) diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/rename.kt b/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/rename.kt index 5816e7ebf..bcfba99bf 100644 --- a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/rename.kt +++ b/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/rename.kt @@ -12,9 +12,18 @@ class RenamePass(private val jadx: JadxScriptInstance) { } fun all(makeNewName: (String, IDexNode) -> String?) { - jadx.addPass(object : ScriptPreparePass(jadx, "RenameAll") { + jadx.addPass(object : ScriptOrderedPreparePass( + jadx, + "RenameAll", + runBefore = listOf("RenameVisitor") + ) { override fun init(root: IRootNode) { val rootNode = root as RootNode + for (pkgNode in rootNode.packages) { + makeNewName.invoke(pkgNode.pkgInfo.name, pkgNode)?.let { + pkgNode.rename(it) + } + } for (cls in rootNode.classes) { makeNewName.invoke(cls.classInfo.shortName, cls)?.let { cls.classInfo.changeShortName(it) diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/script.kt b/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/script.kt index cdf68ce33..45cf60117 100644 --- a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/script.kt +++ b/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/script.kt @@ -23,6 +23,9 @@ object JadxScriptConfiguration : ScriptCompilationConfiguration({ wholeClasspath = true ) } + ide { + acceptedLocations(ScriptAcceptedLocation.Everywhere) + } baseClass(JadxScriptBaseClass::class)