feat(gui): add Xposed snippet copy action (PR #1383)
* add xposedscript * fix code style and minor issues * some code style changes for Xposed snippets * some code style changes for Frida snippets + a fix for multidimensional arrays in overload params * hide frida and xposed when right-clicking on a null node * small style fix * fixed formatting violations * fix minor issues Co-authored-by: Skylot <skylot@gmail.com> Co-authored-by: Orip <oriori1703@gmail.com>
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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<JNode> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FridaAction.class);
|
||||
private static final long serialVersionUID = 4692546569977976384L;
|
||||
private static final long serialVersionUID = -3084073927621269039L;
|
||||
private final Map<String, Boolean> isInitial = new HashMap<>();
|
||||
|
||||
public FridaAction(CodeArea codeArea) {
|
||||
@@ -51,9 +52,16 @@ public final class FridaAction extends JNodeMenuAction<JNode> {
|
||||
});
|
||||
}
|
||||
|
||||
@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<JNode> {
|
||||
|
||||
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("<init>") || methodName.equals("onCreate")) {
|
||||
if (methodInfo.isConstructor()) {
|
||||
methodName = "$init";
|
||||
}
|
||||
String rawClassName = javaMethod.getDeclaringClass().getRawName();
|
||||
@@ -105,8 +110,12 @@ public final class FridaAction extends JNodeMenuAction<JNode> {
|
||||
.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<JNode> {
|
||||
} else {
|
||||
finalFridaCode = functionParameterAndBody;
|
||||
}
|
||||
LOG.debug("Frida code : {}", finalFridaCode);
|
||||
return finalFridaCode;
|
||||
}
|
||||
|
||||
@@ -125,7 +133,6 @@ public final class FridaAction extends JNodeMenuAction<JNode> {
|
||||
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<JNode> {
|
||||
|
||||
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<JNode> {
|
||||
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) {
|
||||
|
||||
@@ -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<JNode> {
|
||||
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<ArgType> 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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=제외
|
||||
|
||||
@@ -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=排除此包
|
||||
|
||||
@@ -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=排除
|
||||
|
||||
Reference in New Issue
Block a user