diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java index 4bcec51de..5fcbf4e06 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java @@ -95,20 +95,25 @@ public final class CodeArea extends AbstractCodeArea { RenameAction rename = new RenameAction(this); CommentAction comment = new CommentAction(this); FridaAction frida = new FridaAction(this); + XposedAction xposed = new XposedAction(this); JPopupMenu popup = getPopupMenu(); popup.addSeparator(); popup.add(findUsage); - popup.add(frida); popup.add(goToDeclaration); popup.add(comment); popup.add(new CommentSearchAction(this)); popup.add(rename); + popup.addSeparator(); + popup.add(frida); + popup.add(xposed); + popup.addPopupMenuListener(findUsage); - popup.addPopupMenuListener(frida); popup.addPopupMenuListener(goToDeclaration); popup.addPopupMenuListener(comment); popup.addPopupMenuListener(rename); + popup.addPopupMenuListener(frida); + popup.addPopupMenuListener(xposed); // move caret on mouse right button click popup.addPopupMenuListener(new DefaultPopupMenuListener() { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/FridaAction.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/FridaAction.java index e0726f394..e318adca0 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/FridaAction.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/FridaAction.java @@ -18,6 +18,7 @@ import jadx.api.JavaClass; import jadx.api.JavaField; import jadx.api.JavaMethod; import jadx.api.data.annotations.VarDeclareRef; +import jadx.core.codegen.TypeGen; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.ClassNode; @@ -33,7 +34,7 @@ import static javax.swing.KeyStroke.getKeyStroke; public final class FridaAction extends JNodeMenuAction { private static final Logger LOG = LoggerFactory.getLogger(FridaAction.class); - private static final long serialVersionUID = 4692546569977976384L; + private static final long serialVersionUID = -3084073927621269039L; private final Map isInitial = new HashMap<>(); public FridaAction(CodeArea codeArea) { @@ -51,9 +52,16 @@ public final class FridaAction extends JNodeMenuAction { }); } + @Override + public void actionPerformed(ActionEvent e) { + node = codeArea.getNodeUnderCaret(); + copyFridaSnippet(); + } + private void copyFridaSnippet() { try { String fridaSnippet = generateFridaSnippet(); + LOG.info("Frida snippet:\n{}", fridaSnippet); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); StringSelection selection = new StringSelection(fridaSnippet); clipboard.setContents(selection, selection); @@ -66,23 +74,20 @@ public final class FridaAction extends JNodeMenuAction { private String generateFridaSnippet() { if (node instanceof JMethod) { - LOG.debug("node is jmethod"); return generateMethodSnippet((JMethod) node); } else if (node instanceof JClass) { - LOG.debug("node is jclass"); return generateClassSnippet((JClass) node); } else if (node instanceof JField) { - LOG.debug("node is jfield"); return generateFieldSnippet((JField) node); } - throw new JadxRuntimeException("Unsupported node type: " + node.getClass()); + throw new JadxRuntimeException("Unsupported node type: " + (node != null ? node.getClass() : "null")); } private String generateMethodSnippet(JMethod jMth) { JavaMethod javaMethod = jMth.getJavaMethod(); MethodInfo methodInfo = javaMethod.getMethodNode().getMethodInfo(); String methodName = methodInfo.getName(); - if (methodName.equals("") || methodName.equals("onCreate")) { + if (methodInfo.isConstructor()) { methodName = "$init"; } String rawClassName = javaMethod.getDeclaringClass().getRawName(); @@ -105,8 +110,12 @@ public final class FridaAction extends JNodeMenuAction { .collect(Collectors.joining(", ")); String functionParameterAndBody = String.format( - "%s = function(%s){\n\tconsole.log('%s is called');\n\tlet ret = this.%s(%s);\n" - + "\tconsole.log('%s ret value is ' + ret);\n\treturn ret;\n};", + "%s = function(%s){\n" + + " console.log('%s is called');\n" + + " let ret = this.%s(%s);\n" + + " console.log('%s ret value is ' + ret);\n" + + " return ret;\n" + + "};", functionUntilImplementation, functionParametersString, methodName, methodName, functionParametersString, methodName); String finalFridaCode; @@ -116,7 +125,6 @@ public final class FridaAction extends JNodeMenuAction { } else { finalFridaCode = functionParameterAndBody; } - LOG.debug("Frida code : {}", finalFridaCode); return finalFridaCode; } @@ -125,7 +133,6 @@ public final class FridaAction extends JNodeMenuAction { String rawClassName = javaClass.getRawName(); String shortClassName = javaClass.getName(); String finalFridaCode = String.format("let %s = Java.use(\"%s\");", shortClassName, rawClassName); - LOG.debug("Frida code : {}", finalFridaCode); isInitial.put(rawClassName, false); return finalFridaCode; } @@ -145,9 +152,7 @@ public final class FridaAction extends JNodeMenuAction { JClass jc = jf.getRootClass(); String classSnippet = generateClassSnippet(jc); - String finalFridaCode = String.format("%s\n%s = %s.%s.value;", classSnippet, fieldName, jc.getName(), rawFieldName); - LOG.debug("Frida code : {}", finalFridaCode); - return finalFridaCode; + return String.format("%s\n%s = %s.%s.value;", classSnippet, fieldName, jc.getName(), rawFieldName); } public Boolean isOverloaded(MethodNode methodNode) { @@ -161,24 +166,13 @@ public final class FridaAction extends JNodeMenuAction { private String parseArgType(ArgType x) { StringBuilder parsedArgType = new StringBuilder("'"); if (x.isArray()) { - parsedArgType.append(x.getPrimitiveType().getShortName()); - parsedArgType.append(x.getArrayElement().getPrimitiveType().getShortName()); - if (!x.getArrayElement().isPrimitive()) { - parsedArgType.append(x.getArrayElement().toString()).append(";"); - } - + parsedArgType.append(TypeGen.signature(x).replace("/", ".")); } else { parsedArgType.append(x); } return parsedArgType.append("'").toString(); } - @Override - public void actionPerformed(ActionEvent e) { - node = codeArea.getNodeUnderCaret(); - copyFridaSnippet(); - } - @Nullable @Override public JNode getNodeByOffset(int offset) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/XposedAction.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/XposedAction.java new file mode 100644 index 000000000..d102a1bcc --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/XposedAction.java @@ -0,0 +1,124 @@ +package jadx.gui.ui.codearea; + +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.util.List; +import java.util.stream.Collectors; + +import javax.swing.AbstractAction; +import javax.swing.JOptionPane; +import javax.swing.KeyStroke; + +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.JavaClass; +import jadx.api.JavaMethod; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.gui.treemodel.JClass; +import jadx.gui.treemodel.JMethod; +import jadx.gui.treemodel.JNode; +import jadx.gui.utils.NLS; + +import static javax.swing.KeyStroke.getKeyStroke; + +public class XposedAction extends JNodeMenuAction { + private static final Logger LOG = LoggerFactory.getLogger(XposedAction.class); + private static final long serialVersionUID = 2641585141624592578L; + + public XposedAction(CodeArea codeArea) { + super(NLS.str("popup.xposed") + " (y)", codeArea); + KeyStroke key = getKeyStroke(KeyEvent.VK_Y, 0); + codeArea.getInputMap().put(key, "trigger xposed"); + codeArea.getActionMap().put("trigger xposed", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + node = getNodeByOffset(codeArea.getWordStart(codeArea.getCaretPosition())); + copyXposedSnippet(); + } + }); + } + + @Override + public void actionPerformed(ActionEvent e) { + node = codeArea.getNodeUnderCaret(); + copyXposedSnippet(); + } + + private void copyXposedSnippet() { + try { + String xposedSnippet = generateXposedSnippet(); + LOG.info("Xposed snippet:\n{}", xposedSnippet); + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + StringSelection selection = new StringSelection(xposedSnippet); + clipboard.setContents(selection, selection); + } catch (Exception e) { + LOG.error("Failed to generate Xposed code snippet", e); + JOptionPane.showMessageDialog(codeArea.getMainWindow(), e.getLocalizedMessage(), NLS.str("error_dialog.title"), + JOptionPane.ERROR_MESSAGE); + } + } + + private String generateXposedSnippet() { + if (node instanceof JMethod) { + return generateMethodSnippet((JMethod) node); + } + if (node instanceof JClass) { + return generateClassSnippet((JClass) node); + } + throw new JadxRuntimeException("Unsupported node type: " + (node != null ? node.getClass() : "null")); + } + + private String generateMethodSnippet(JMethod jMth) { + JavaMethod javaMethod = jMth.getJavaMethod(); + MethodNode mth = javaMethod.getMethodNode(); + String methodName; + String xposedMethod; + if (mth.isConstructor()) { + xposedMethod = "findAndHookConstructor"; + methodName = ""; + } else { + xposedMethod = "findAndHookMethod"; + methodName = "\"" + mth.getMethodInfo().getName() + "\", "; + } + String rawClassName = javaMethod.getDeclaringClass().getRawName(); + String xposedFormatStr = "XposedHelpers.%s(\"%s\", classLoader, %snew XC_MethodHook() {\n" + + " @Override\n" + + " protected void beforeHookedMethod(MethodHookParam param) throws Throwable {\n" + + " super.beforeHookedMethod(param);\n" + + " }\n" + + " @Override\n" + + " protected void afterHookedMethod(MethodHookParam param) throws Throwable {\n" + + " super.afterHookedMethod(param);\n" + + " }\n" + + "});"; + + List mthArgs = mth.getArgTypes(); + if (mthArgs.isEmpty()) { + return String.format(xposedFormatStr, xposedMethod, rawClassName, methodName); + } + String params = mthArgs.stream().map(type -> type + ".class, ").collect(Collectors.joining()); + return String.format(xposedFormatStr, xposedMethod, rawClassName, methodName + params); + } + + private String generateClassSnippet(JClass jc) { + JavaClass javaClass = jc.getCls(); + String rawClassName = javaClass.getRawName(); + String shortClassName = javaClass.getName(); + return String.format("ClassLoader classLoader=lpparam.classLoader;\n" + + "Class %sClass=classLoader.loadClass(\"%s\");", + shortClassName, rawClassName); + } + + @Nullable + @Override + public JNode getNodeByOffset(int offset) { + return codeArea.getJNodeAtOffset(offset); + } +} diff --git a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties index 75945f0c4..1048b3039 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -195,6 +195,7 @@ popup.paste=Einfügen popup.delete=Löschen popup.select_all=Alle auswählen popup.frida=Als Frida-Snippet kopieren +popup.xposed=Als Xposed-Snippet kopieren popup.find_usage=Verwendung suchen popup.go_to_declaration=Zur Erklärung gehen popup.exclude=Ausschließen diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index 5b43e8050..65b7fcd46 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -195,6 +195,7 @@ popup.paste=Paste popup.delete=Delete popup.select_all=Select All popup.frida=Copy as frida snippet +popup.xposed=Copy as xposed snippet popup.find_usage=Find Usage popup.go_to_declaration=Go to declaration popup.exclude=Exclude diff --git a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties index aa9b88c42..7db104e70 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -194,7 +194,8 @@ popup.copy=Copiar popup.paste=Pegar popup.delete=Borrar popup.select_all=Seleccionar todo -popup.frida=Copiar como fragmento de frida +popup.frida=Copiar como fragmento de frida +popup.xposed=Copiar como fragmento de xposed #popup.find_usage= #popup.go_to_declaration= #popup.exclude= diff --git a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties index 9f6c6ed14..e13e832a6 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -195,6 +195,7 @@ popup.paste=복사 popup.delete=삭제 popup.select_all=모두 선택 popup.frida=frida 스니펫으로 복사 +popup.xposed=xposed 스니펫으로 복사 popup.find_usage=사용 찾기 popup.go_to_declaration=선언문으로 이동 popup.exclude=제외 diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties index 0da7ef721..b0172ac33 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -194,7 +194,8 @@ popup.copy=复制 popup.paste=粘贴 popup.delete=删除 popup.select_all=全选 -popup.frida=复制为 frida 片段 +popup.frida=复制为 frida 片段 +popup.xposed=复制为 xposed 片段 popup.find_usage=查找用例 popup.go_to_declaration=跳到声明 popup.exclude=排除此包 diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties index e0df06b51..39d0a4443 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -194,7 +194,8 @@ popup.copy=複製 popup.paste=貼上 popup.delete=刪除 popup.select_all=全選 -popup.frida=复制为 frida 片段 +popup.frida=复制为 frida 片段 +popup.xposed=复制为 xposed 片段 popup.find_usage=尋找使用情況 popup.go_to_declaration=前往宣告 popup.exclude=排除