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:
YenKoc
2022-02-18 20:54:41 +08:00
committed by GitHub
parent 4d4d67f0b4
commit 3c2c198a0e
9 changed files with 159 additions and 30 deletions
@@ -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=排除