提交 3bbb6b10 编写于 作者: S Skylot

fix: rename all related overridden methods in deobf map file (#1058)

上级 3a4895b2
package jadx.core.deobf;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.files.FileUtils;
import static java.nio.charset.StandardCharsets.UTF_8;
class DeobfPresets {
public class DeobfPresets {
private static final Logger LOG = LoggerFactory.getLogger(DeobfPresets.class);
private static final Charset MAP_FILE_CHARSET = UTF_8;
private final Deobfuscator deobfuscator;
private final Path deobfMapFile;
private final Map<String, String> pkgPresetMap = new HashMap<>();
private final Map<String, String> clsPresetMap = new HashMap<>();
private final Map<String, String> fldPresetMap = new HashMap<>();
private final Map<String, String> mthPresetMap = new HashMap<>();
public DeobfPresets(Deobfuscator deobfuscator, Path deobfMapFile) {
this.deobfuscator = deobfuscator;
@Nullable
public static DeobfPresets build(RootNode root) {
Path deobfMapPath = getPathDeobfMapPath(root);
if (deobfMapPath == null) {
return null;
}
return new DeobfPresets(deobfMapPath);
}
@Nullable
private static Path getPathDeobfMapPath(RootNode root) {
List<File> inputFiles = root.getArgs().getInputFiles();
if (inputFiles.isEmpty()) {
return null;
}
Path inputFilePath = inputFiles.get(0).getAbsoluteFile().toPath();
String baseName = FileUtils.getPathBaseName(inputFilePath);
return inputFilePath.getParent().resolve(baseName + ".jobf");
}
private DeobfPresets(Path deobfMapFile) {
this.deobfMapFile = deobfMapFile;
}
......@@ -57,17 +81,22 @@ class DeobfPresets {
}
String origName = va[0];
String alias = va[1];
if (l.startsWith("p ")) {
deobfuscator.addPackagePreset(origName, alias);
} else if (l.startsWith("c ")) {
clsPresetMap.put(origName, alias);
} else if (l.startsWith("f ")) {
fldPresetMap.put(origName, alias);
} else if (l.startsWith("m ")) {
mthPresetMap.put(origName, alias);
switch (l.charAt(0)) {
case 'p':
pkgPresetMap.put(origName, alias);
break;
case 'c':
clsPresetMap.put(origName, alias);
break;
case 'f':
fldPresetMap.put(origName, alias);
break;
case 'm':
mthPresetMap.put(origName, alias);
break;
}
}
} catch (IOException e) {
} catch (Exception e) {
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
}
}
......@@ -80,66 +109,28 @@ class DeobfPresets {
return v;
}
public void save(boolean forceSave) {
try {
if (Files.exists(deobfMapFile)) {
if (forceSave) {
dumpMapping();
} else {
LOG.warn("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
deobfMapFile.toAbsolutePath());
}
} else {
dumpMapping();
}
} catch (IOException e) {
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
}
}
/**
* Saves DefaultDeobfuscator presets
*/
private void dumpMapping() throws IOException {
public void save() throws IOException {
List<String> list = new ArrayList<>();
// packages
for (PackageNode p : deobfuscator.getRootPackage().getInnerPackages()) {
for (PackageNode pp : p.getInnerPackages()) {
dfsPackageName(list, p.getName(), pp);
}
if (p.hasAlias()) {
list.add(String.format("p %s = %s", p.getName(), p.getAlias()));
}
for (Map.Entry<String, String> pkgEntry : pkgPresetMap.entrySet()) {
list.add(String.format("p %s = %s", pkgEntry.getKey(), pkgEntry.getValue()));
}
// classes
for (DeobfClsInfo deobfClsInfo : deobfuscator.getClsMap().values()) {
if (deobfClsInfo.getAlias() != null) {
list.add(String.format("c %s = %s",
deobfClsInfo.getCls().getClassInfo().makeRawFullName(), deobfClsInfo.getAlias()));
}
for (Map.Entry<String, String> clsEntry : clsPresetMap.entrySet()) {
list.add(String.format("c %s = %s", clsEntry.getKey(), clsEntry.getValue()));
}
for (FieldInfo fld : deobfuscator.getFldMap().keySet()) {
list.add(String.format("f %s = %s", fld.getRawFullId(), fld.getAlias()));
for (Map.Entry<String, String> fldEntry : fldPresetMap.entrySet()) {
list.add(String.format("f %s = %s", fldEntry.getKey(), fldEntry.getValue()));
}
for (MethodInfo mth : deobfuscator.getMthMap().keySet()) {
list.add(String.format("m %s = %s", mth.getRawFullId(), mth.getAlias()));
for (Map.Entry<String, String> mthEntry : mthPresetMap.entrySet()) {
list.add(String.format("m %s = %s", mthEntry.getKey(), mthEntry.getValue()));
}
Collections.sort(list);
Files.write(deobfMapFile, list, MAP_FILE_CHARSET);
Files.write(deobfMapFile, list, MAP_FILE_CHARSET,
StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
if (LOG.isDebugEnabled()) {
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile);
}
}
private static void dfsPackageName(List<String> list, String prefix, PackageNode node) {
for (PackageNode pp : node.getInnerPackages()) {
dfsPackageName(list, prefix + '.' + node.getName(), pp);
}
if (node.hasAlias()) {
list.add(String.format("p %s.%s = %s", prefix, node.getName(), node.getAlias()));
}
}
public String getForCls(ClassInfo cls) {
return clsPresetMap.get(cls.makeRawFullName());
}
......@@ -158,6 +149,14 @@ class DeobfPresets {
mthPresetMap.clear();
}
public Path getDeobfMapFile() {
return deobfMapFile;
}
public Map<String, String> getPkgPresetMap() {
return pkgPresetMap;
}
public Map<String, String> getClsPresetMap() {
return clsPresetMap;
}
......
package jadx.core.deobf;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
......@@ -58,28 +59,75 @@ public class Deobfuscator {
private int fldIndex = 0;
private int mthIndex = 0;
public Deobfuscator(JadxArgs args, RootNode root, Path deobfMapFile) {
this.args = args;
public Deobfuscator(RootNode root) {
this.root = root;
this.args = root.getArgs();
this.minLength = args.getDeobfuscationMinLength();
this.maxLength = args.getDeobfuscationMaxLength();
this.useSourceNameAsAlias = args.isUseSourceNameAsClassAlias();
this.parseKotlinMetadata = args.isParseKotlinMetadata();
this.deobfPresets = new DeobfPresets(this, deobfMapFile);
this.deobfPresets = DeobfPresets.build(root);
}
public void execute() {
if (!args.isDeobfuscationForceSave()) {
deobfPresets.load();
for (Map.Entry<String, String> pkgEntry : deobfPresets.getPkgPresetMap().entrySet()) {
addPackagePreset(pkgEntry.getKey(), pkgEntry.getValue());
}
deobfPresets.getPkgPresetMap().clear(); // not needed anymore
initIndexes();
}
process();
}
public void savePresets() {
deobfPresets.save(args.isDeobfuscationForceSave());
Path deobfMapFile = deobfPresets.getDeobfMapFile();
if (Files.exists(deobfMapFile) && !args.isDeobfuscationForceSave()) {
LOG.warn("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
deobfMapFile.toAbsolutePath());
return;
}
try {
deobfPresets.clear();
fillDeobfPresets();
deobfPresets.save();
} catch (Exception e) {
LOG.error("Failed to save deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
}
}
private void fillDeobfPresets() {
for (PackageNode p : getRootPackage().getInnerPackages()) {
for (PackageNode pp : p.getInnerPackages()) {
dfsPackageName(p.getName(), pp);
}
if (p.hasAlias()) {
deobfPresets.getPkgPresetMap().put(p.getName(), p.getAlias());
}
}
for (DeobfClsInfo deobfClsInfo : clsMap.values()) {
if (deobfClsInfo.getAlias() != null) {
deobfPresets.getClsPresetMap().put(deobfClsInfo.getCls().getClassInfo().makeRawFullName(), deobfClsInfo.getAlias());
}
}
for (FieldInfo fld : fldMap.keySet()) {
deobfPresets.getFldPresetMap().put(fld.getRawFullId(), fld.getAlias());
}
for (MethodInfo mth : mthMap.keySet()) {
deobfPresets.getMthPresetMap().put(mth.getRawFullId(), mth.getAlias());
}
}
private void dfsPackageName(String prefix, PackageNode node) {
for (PackageNode pp : node.getInnerPackages()) {
dfsPackageName(prefix + '.' + node.getName(), pp);
}
if (node.hasAlias()) {
deobfPresets.getPkgPresetMap().put(node.getName(), node.getAlias());
}
}
public void clear() {
......
......@@ -2,10 +2,12 @@ package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull;
......@@ -74,16 +76,12 @@ public class OverrideMethodVisitor extends AbstractVisitor {
for (ArgType superType : superTypes) {
ClassNode classNode = cls.root().resolveClass(superType);
if (classNode != null) {
for (MethodNode supMth : classNode.getMethods()) {
String mthShortId = supMth.getMethodInfo().getShortId();
if (!supMth.getAccessFlags().isStatic()
&& mthShortId.startsWith(signature)
&& isMethodVisibleInCls(supMth, cls)) {
overrideList.add(supMth);
MethodOverrideAttr attr = supMth.get(AType.METHOD_OVERRIDE);
if (attr != null) {
return buildOverrideAttr(mth, overrideList, attr);
}
MethodNode ovrdMth = searchOverriddenMethod(classNode, signature);
if (ovrdMth != null && isMethodVisibleInCls(ovrdMth, cls)) {
overrideList.add(ovrdMth);
MethodOverrideAttr attr = ovrdMth.get(AType.METHOD_OVERRIDE);
if (attr != null) {
return buildOverrideAttr(mth, overrideList, attr);
}
}
} else {
......@@ -102,25 +100,50 @@ public class OverrideMethodVisitor extends AbstractVisitor {
return buildOverrideAttr(mth, overrideList, null);
}
@Nullable
private MethodNode searchOverriddenMethod(ClassNode cls, String signature) {
for (MethodNode supMth : cls.getMethods()) {
if (!supMth.getAccessFlags().isStatic() && supMth.getMethodInfo().getShortId().startsWith(signature)) {
return supMth;
}
}
return null;
}
@Nullable
private MethodOverrideAttr buildOverrideAttr(MethodNode mth, List<IMethodDetails> overrideList, @Nullable MethodOverrideAttr attr) {
if (overrideList.isEmpty() && attr == null) {
return null;
}
List<IMethodDetails> cleanOverrideList = overrideList.stream().distinct().collect(Collectors.toList());
if (attr == null) {
// traced to base method
List<IMethodDetails> cleanOverrideList = overrideList.stream().distinct().collect(Collectors.toList());
return applyOverrideAttr(mth, cleanOverrideList, false);
}
// trace stopped at already processed method -> start merging
List<IMethodDetails> mergedOverrideList = Utils.mergeLists(cleanOverrideList, attr.getOverrideList());
return applyOverrideAttr(mth, mergedOverrideList, true);
List<IMethodDetails> mergedOverrideList = Utils.mergeLists(overrideList, attr.getOverrideList());
List<IMethodDetails> cleanOverrideList = mergedOverrideList.stream().distinct().collect(Collectors.toList());
return applyOverrideAttr(mth, cleanOverrideList, true);
}
private MethodOverrideAttr applyOverrideAttr(MethodNode mth, List<IMethodDetails> overrideList, boolean update) {
// don't rename method if override list contains not resolved method
boolean dontRename = overrideList.stream().anyMatch(m -> !(m instanceof MethodNode));
List<MethodNode> mthNodes = getMethodNodes(mth, overrideList);
if (update) {
// merge related methods from all override attributes
Set<MethodNode> relatedMthSet = new HashSet<>(mthNodes);
for (MethodNode mthNode : mthNodes) {
MethodOverrideAttr ovrdAttr = mthNode.get(AType.METHOD_OVERRIDE);
if (ovrdAttr != null) {
relatedMthSet.addAll(ovrdAttr.getRelatedMthNodes());
}
}
if (relatedMthSet.size() != mthNodes.size()) {
mthNodes = new ArrayList<>(relatedMthSet);
Collections.sort(mthNodes);
}
}
int depth = 0;
for (MethodNode mthNode : mthNodes) {
if (dontRename) {
......
package jadx.core.dex.visitors;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
......@@ -23,7 +22,6 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.files.FileUtils;
public class RenameVisitor extends AbstractVisitor {
......@@ -33,12 +31,8 @@ public class RenameVisitor extends AbstractVisitor {
if (inputFiles.isEmpty()) {
return;
}
Path inputFilePath = inputFiles.get(0).getAbsoluteFile().toPath();
String baseName = FileUtils.getPathBaseName(inputFilePath);
Path deobfMapPath = inputFilePath.getParent().resolve(baseName + ".jobf");
Deobfuscator deobfuscator = new Deobfuscator(root);
JadxArgs args = root.getArgs();
Deobfuscator deobfuscator = new Deobfuscator(args, root, deobfMapPath);
if (args.isDeobfuscationOn()) {
deobfuscator.execute();
}
......
package jadx.tests.integration.deobf;
import org.junit.jupiter.api.Test;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestRenameOverriddenMethod2 extends IntegrationTest {
public static class TestCls {
public interface I {
int call();
}
public static class A implements I {
@Override
public int call() {
return 1;
}
}
public static class B implements I {
@Override
public int call() {
return 2;
}
}
}
@Test
public void test() {
enableDeobfuscation();
args.setDeobfuscationMinLength(100); // rename everything
ClassNode cls = getClassNode(TestCls.class);
assertThat(cls)
.code()
.countString(2, "@Override")
.countString(3, "int mo0call()");
assertThat(searchCls(cls.getInnerClasses(), "I")).isNotNull()
.extracting(c -> c.searchMethodByShortName("call")).isNotNull()
.extracting(m -> m.get(AType.METHOD_OVERRIDE)).isNotNull()
.satisfies(ovrdAttr -> {
assertThat(ovrdAttr.getRelatedMthNodes()).hasSize(3);
assertThat(ovrdAttr.getOverrideList()).isEmpty();
});
}
}
......@@ -4,13 +4,6 @@ import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
......@@ -39,6 +32,7 @@ import jadx.api.JavaField;
import jadx.api.JavaMethod;
import jadx.api.JavaNode;
import jadx.core.codegen.CodeWriter;
import jadx.core.deobf.DeobfPresets;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.nodes.MethodNode;
......@@ -117,85 +111,25 @@ public class RenameDialog extends JDialog {
return false; // TODO: can't open dialog, 'node' is replaced with new one after reopen
}
private Path getDeobfMapPath(RootNode root) {
List<File> inputFiles = root.getArgs().getInputFiles();
if (inputFiles.isEmpty()) {
return null;
}
File firstInputFile = inputFiles.get(0);
Path inputFilePath = firstInputFile.getAbsoluteFile().toPath();
String inputName = inputFilePath.getFileName().toString();
String baseName = inputName.substring(0, inputName.lastIndexOf('.'));
return inputFilePath.getParent().resolve(baseName + ".jobf");
}
private String getNodeAlias(String renameText) {
String type = "";
String id = "";
private void updateDeobfMap(DeobfPresets deobfPresets, String renameText) {
if (node instanceof JMethod) {
JavaMethod javaMethod = (JavaMethod) node.getJavaNode();
type = "m";
MethodNode mthNode = javaMethod.getMethodNode();
MethodNode mthNode = ((JavaMethod) node.getJavaNode()).getMethodNode();
MethodOverrideAttr overrideAttr = mthNode.get(AType.METHOD_OVERRIDE);
if (overrideAttr != null) {
// use method closest to base method
mthNode = Objects.requireNonNull(Utils.last(overrideAttr.getRelatedMthNodes()));
for (MethodNode relatedMth : overrideAttr.getRelatedMthNodes()) {
deobfPresets.getMthPresetMap().put(relatedMth.getMethodInfo().getRawFullId(), renameText);
}
}
id = mthNode.getMethodInfo().getRawFullId();
deobfPresets.getMthPresetMap().put(mthNode.getMethodInfo().getRawFullId(), renameText);
} else if (node instanceof JField) {
JavaField javaField = (JavaField) node.getJavaNode();
type = "f";
id = javaField.getFieldNode().getFieldInfo().getRawFullId();
deobfPresets.getFldPresetMap().put(javaField.getFieldNode().getFieldInfo().getRawFullId(), renameText);
} else if (node instanceof JClass) {
JavaClass javaClass = (JavaClass) node.getJavaNode();
type = "c";
id = javaClass.getRawName();
deobfPresets.getClsPresetMap().put(javaClass.getRawName(), renameText);
} else if (node instanceof JPackage) {
type = "p";
id = ((JPackage) node).getFullName();
}
return String.format("%s %s = %s", type, id, renameText);
}
private void writeDeobfMapFile(Path deobfMapPath, List<String> deobfMap) throws IOException {
if (deobfMapPath == null) {
LOG.error("updateDeobfMapFile(): deobfMapPath is null!");
return;
}
Path deobfMapDir = deobfMapPath.getParent();
Path tmpFile = Files.createTempFile(deobfMapDir, "deobf_tmp_", ".txt");
try (Writer writer = Files.newBufferedWriter(tmpFile, StandardCharsets.UTF_8)) {
for (String entry : deobfMap) {
writer.write(entry);
writer.write(System.lineSeparator());
}
deobfPresets.getPkgPresetMap().put(((JPackage) node).getFullName(), renameText);
}
Files.move(tmpFile, deobfMapPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
LOG.info("Updated deobf file {}", deobfMapPath);
}
@NotNull
private List<String> readDeobfMap(Path deobfMapPath) throws IOException {
return Files.readAllLines(deobfMapPath, StandardCharsets.UTF_8);
}
private List<String> updateDeobfMap(List<String> deobfMap, String alias) {
String id = alias.substring(0, alias.indexOf('=') + 1);
int i = 0;
while (i < deobfMap.size()) {
String entry = deobfMap.get(i);
if (entry.startsWith(id)) {
LOG.debug("updateDeobfMap(): Removing entry {}", entry);
deobfMap.remove(i);
} else {
i++;
}
}
LOG.debug("updateDeobfMap(): placing alias = {}", alias);
deobfMap.add(alias);
return deobfMap;
}
private void rename() {
......@@ -223,18 +157,20 @@ public class RenameDialog extends JDialog {
}
private boolean refreshDeobfMapFile(String renameText, RootNode root) {
List<String> deobfMap;
Path deobfMapPath = getDeobfMapPath(root);
DeobfPresets deobfPresets = DeobfPresets.build(root);
if (deobfPresets == null) {
return false;
}
try {
deobfMap = readDeobfMap(deobfMapPath);
} catch (IOException e) {
deobfPresets.load();
} catch (Exception e) {
LOG.error("rename(): readDeobfMap() failed");
return false;
}
updateDeobfMap(deobfMap, getNodeAlias(renameText));
updateDeobfMap(deobfPresets, renameText);
try {
writeDeobfMapFile(deobfMapPath, deobfMap);
} catch (IOException e) {
deobfPresets.save();
} catch (Exception e) {
LOG.error("rename(): writeDeobfMap() failed");
return false;
}
......@@ -301,7 +237,7 @@ public class RenameDialog extends JDialog {
cls.reload();
IndexJob.refreshIndex(cache, cls.getCls());
} catch (Exception e) {
LOG.error("Failed to reload class: {}", cls, e);
LOG.error("Failed to reload class: {}", cls.getFullName(), e);
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册