feat(gui): added dialog for select methods for generate frida class snippet (PR #2487)

feat(gui): add dialog for select methods for generate frida snippet
This commit is contained in:
Yaroslav
2025-05-16 20:14:33 +03:00
committed by GitHub
parent aee1e86398
commit be1c02455f
14 changed files with 285 additions and 75 deletions
@@ -1,12 +1,10 @@
package jadx.gui.treemodel;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JPopupMenu;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
@@ -20,22 +18,14 @@ import jadx.api.data.impl.JadxNodeRef;
import jadx.api.metadata.ICodeNodeRef;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.cellrenders.MethodRenderHelper;
import jadx.gui.ui.dialog.RenameDialog;
import jadx.gui.utils.Icons;
import jadx.gui.utils.OverlayIcon;
import jadx.gui.utils.UiUtils;
public class JMethod extends JNode implements JRenameNode {
private static final long serialVersionUID = 3834526867464663751L;
private static final ImageIcon ICON_METHOD_ABSTRACT = UiUtils.openSvgIcon("nodes/abstractMethod");
private static final ImageIcon ICON_METHOD_PRIVATE = UiUtils.openSvgIcon("nodes/privateMethod");
private static final ImageIcon ICON_METHOD_PROTECTED = UiUtils.openSvgIcon("nodes/protectedMethod");
private static final ImageIcon ICON_METHOD_PUBLIC = UiUtils.openSvgIcon("nodes/publicMethod");
private static final ImageIcon ICON_METHOD_CONSTRUCTOR = UiUtils.openSvgIcon("nodes/constructorMethod");
private static final ImageIcon ICON_METHOD_SYNC = UiUtils.openSvgIcon("nodes/methodReference");
private final transient JavaMethod mth;
private final transient JClass jParent;
@@ -78,36 +68,7 @@ public class JMethod extends JNode implements JRenameNode {
@Override
public Icon getIcon() {
AccessInfo accessFlags = mth.getAccessFlags();
Icon icon = Icons.METHOD;
if (accessFlags.isAbstract()) {
icon = ICON_METHOD_ABSTRACT;
}
if (accessFlags.isConstructor()) {
icon = ICON_METHOD_CONSTRUCTOR;
}
if (accessFlags.isPublic()) {
icon = ICON_METHOD_PUBLIC;
}
if (accessFlags.isPrivate()) {
icon = ICON_METHOD_PRIVATE;
}
if (accessFlags.isProtected()) {
icon = ICON_METHOD_PROTECTED;
}
if (accessFlags.isSynchronized()) {
icon = ICON_METHOD_SYNC;
}
OverlayIcon overIcon = new OverlayIcon(icon);
if (accessFlags.isFinal()) {
overIcon.add(Icons.FINAL);
}
if (accessFlags.isStatic()) {
overIcon.add(Icons.STATIC);
}
return overIcon;
return MethodRenderHelper.getIcon(mth);
}
@Override
@@ -121,24 +82,7 @@ public class JMethod extends JNode implements JRenameNode {
}
String makeBaseString() {
if (mth.isClassInit()) {
return "{...}";
}
StringBuilder base = new StringBuilder();
if (mth.isConstructor()) {
base.append(mth.getDeclaringClass().getName());
} else {
base.append(mth.getName());
}
base.append('(');
for (Iterator<ArgType> it = mth.getArguments().iterator(); it.hasNext();) {
base.append(UiUtils.typeStr(it.next()));
if (it.hasNext()) {
base.append(", ");
}
}
base.append(')');
return base.toString();
return MethodRenderHelper.makeBaseString(mth);
}
@Override
@@ -5,6 +5,7 @@ import java.util.Objects;
import java.util.stream.Collectors;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import org.apache.commons.text.StringEscapeUtils;
import org.slf4j.Logger;
@@ -18,12 +19,14 @@ import jadx.core.codegen.TypeGen;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JField;
import jadx.gui.treemodel.JMethod;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.codearea.CodeArea;
import jadx.gui.ui.dialog.MethodsDialog;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
@@ -38,9 +41,7 @@ public final class FridaAction extends JNodeAction {
@Override
public void runAction(JNode node) {
try {
String fridaSnippet = generateFridaSnippet(node);
LOG.info("Frida snippet:\n{}", fridaSnippet);
UiUtils.copyToClipboard(fridaSnippet);
generateFridaSnippet(node);
} catch (Exception e) {
LOG.error("Failed to generate Frida code snippet", e);
JOptionPane.showMessageDialog(getCodeArea().getMainWindow(), e.getLocalizedMessage(), NLS.str("error_dialog.title"),
@@ -53,17 +54,27 @@ public final class FridaAction extends JNodeAction {
return node instanceof JMethod || node instanceof JClass || node instanceof JField;
}
private String generateFridaSnippet(JNode node) {
private void generateFridaSnippet(JNode node) {
String fridaSnippet;
if (node instanceof JMethod) {
return generateMethodSnippet((JMethod) node);
fridaSnippet = generateMethodSnippet((JMethod) node);
copySnipped(fridaSnippet);
} else if (node instanceof JField) {
fridaSnippet = generateFieldSnippet((JField) node);
copySnipped(fridaSnippet);
} else if (node instanceof JClass) {
SwingUtilities.invokeLater(() -> showMethodSelectionDialog((JClass) node));
} else {
throw new JadxRuntimeException("Unsupported node type: " + (node != null ? node.getClass() : "null"));
}
if (node instanceof JClass) {
return generateClassAllMethodSnippet((JClass) node);
}
private void copySnipped(String fridaSnippet) {
if (!StringUtils.isEmpty(fridaSnippet)) {
LOG.info("Frida snippet:\n{}", fridaSnippet);
UiUtils.copyToClipboard(fridaSnippet);
}
if (node instanceof JField) {
return generateFieldSnippet((JField) node);
}
throw new JadxRuntimeException("Unsupported node type: " + (node != null ? node.getClass() : "null"));
}
private String generateMethodSnippet(JMethod jMth) {
@@ -129,13 +140,20 @@ public final class FridaAction extends JNodeAction {
return String.format("let %s = Java.use(\"%s\");", shortClassName, rawClassName);
}
private String generateClassAllMethodSnippet(JClass jc) {
private void showMethodSelectionDialog(JClass jc) {
JavaClass javaClass = jc.getCls();
String result = "";
for (JavaMethod javaMethod : javaClass.getMethods()) {
result = result + generateMethodSnippet(javaMethod, jc) + "\n";
new MethodsDialog(getCodeArea().getMainWindow(), javaClass.getMethods(), (result) -> {
String fridaSnippet = generateClassAllMethodSnippet(jc, result);
copySnipped(fridaSnippet);
});
}
private String generateClassAllMethodSnippet(JClass jc, List<JavaMethod> methodList) {
StringBuilder result = new StringBuilder();
for (JavaMethod javaMethod : methodList) {
result.append(generateMethodSnippet(javaMethod, jc)).append("\n");
}
return result;
return result.toString();
}
private String generateFieldSnippet(JField jf) {
@@ -0,0 +1,77 @@
package jadx.gui.ui.cellrenders;
import java.util.Iterator;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import jadx.api.JavaMethod;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.gui.utils.Icons;
import jadx.gui.utils.OverlayIcon;
import jadx.gui.utils.UiUtils;
public class MethodRenderHelper {
private static final ImageIcon ICON_METHOD_ABSTRACT = UiUtils.openSvgIcon("nodes/abstractMethod");
private static final ImageIcon ICON_METHOD_PRIVATE = UiUtils.openSvgIcon("nodes/privateMethod");
private static final ImageIcon ICON_METHOD_PROTECTED = UiUtils.openSvgIcon("nodes/protectedMethod");
private static final ImageIcon ICON_METHOD_PUBLIC = UiUtils.openSvgIcon("nodes/publicMethod");
private static final ImageIcon ICON_METHOD_CONSTRUCTOR = UiUtils.openSvgIcon("nodes/constructorMethod");
private static final ImageIcon ICON_METHOD_SYNC = UiUtils.openSvgIcon("nodes/methodReference");
public static Icon getIcon(JavaMethod mth) {
AccessInfo accessFlags = mth.getAccessFlags();
Icon icon = Icons.METHOD;
if (accessFlags.isAbstract()) {
icon = ICON_METHOD_ABSTRACT;
}
if (accessFlags.isConstructor()) {
icon = ICON_METHOD_CONSTRUCTOR;
}
if (accessFlags.isPublic()) {
icon = ICON_METHOD_PUBLIC;
}
if (accessFlags.isPrivate()) {
icon = ICON_METHOD_PRIVATE;
}
if (accessFlags.isProtected()) {
icon = ICON_METHOD_PROTECTED;
}
if (accessFlags.isSynchronized()) {
icon = ICON_METHOD_SYNC;
}
OverlayIcon overIcon = new OverlayIcon(icon);
if (accessFlags.isFinal()) {
overIcon.add(Icons.FINAL);
}
if (accessFlags.isStatic()) {
overIcon.add(Icons.STATIC);
}
return overIcon;
}
public static String makeBaseString(JavaMethod mth) {
if (mth.isClassInit()) {
return "{...}";
}
StringBuilder base = new StringBuilder();
if (mth.isConstructor()) {
base.append(mth.getDeclaringClass().getName());
} else {
base.append(mth.getName());
}
base.append('(');
for (Iterator<ArgType> it = mth.getArguments().iterator(); it.hasNext();) {
base.append(UiUtils.typeStr(it.next()));
if (it.hasNext()) {
base.append(", ");
}
}
base.append(')');
return base.toString();
}
}
@@ -0,0 +1,53 @@
package jadx.gui.ui.cellrenders;
import java.awt.BorderLayout;
import java.awt.Component;
import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.ListCellRenderer;
import jadx.api.JavaMethod;
import jadx.gui.utils.UiUtils;
public class MethodsListRenderer extends JPanel implements ListCellRenderer<JavaMethod> {
private final JCheckBox checkBox;
private final JLabel label;
public MethodsListRenderer() {
setLayout(new BorderLayout(5, 0));
checkBox = new JCheckBox();
label = new JLabel();
setBorder(BorderFactory.createEmptyBorder(1, 5, 1, 5));
add(checkBox, BorderLayout.WEST);
add(label, BorderLayout.CENTER);
setOpaque(true);
checkBox.setOpaque(false);
label.setOpaque(false);
}
@Override
public Component getListCellRendererComponent(JList<? extends JavaMethod> list,
JavaMethod value,
int index,
boolean isSelected,
boolean cellHasFocus) {
label.setText(UiUtils.typeFormatHtml(MethodRenderHelper.makeBaseString(value), value.getReturnType()));
label.setIcon(MethodRenderHelper.getIcon(value));
checkBox.setSelected(isSelected);
setBackground(isSelected ? list.getSelectionBackground() : list.getBackground());
setForeground(isSelected ? list.getSelectionForeground() : list.getForeground());
label.setForeground(isSelected ? list.getSelectionForeground() : list.getForeground());
return this;
}
}
@@ -0,0 +1,100 @@
package jadx.gui.ui.dialog;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.List;
import java.util.function.Consumer;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultListModel;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JButton;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;
import javax.swing.WindowConstants;
import jadx.api.JavaMethod;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.cellrenders.MethodsListRenderer;
import jadx.gui.utils.NLS;
public class MethodsDialog extends CommonDialog {
private JList<JavaMethod> methodList;
private final Consumer<List<JavaMethod>> listConsumer;
public MethodsDialog(MainWindow mainWindow, List<JavaMethod> methods, Consumer<List<JavaMethod>> listConsumer) {
super(mainWindow);
this.listConsumer = listConsumer;
initUI(methods);
setVisible(true);
}
private void initUI(List<JavaMethod> methods) {
setTitle(NLS.str("methods_dialog.title"));
DefaultListModel<JavaMethod> defaultListModel = new DefaultListModel<>();
defaultListModel.addAll(methods);
methodList = new JList<>();
methodList.setModel(defaultListModel);
methodList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
methodList.setCellRenderer(new MethodsListRenderer());
methodList.setSelectionModel(new DefaultListSelectionModel() {
@Override
public void setSelectionInterval(int index0, int index1) {
if (super.isSelectedIndex(index0)) {
super.removeSelectionInterval(index0, index1);
} else {
super.addSelectionInterval(index0, index1);
}
}
});
JScrollPane scrollPane = new JScrollPane(methodList);
JPanel buttonPane = initButtonsPanel();
JPanel contentPanel = new JPanel();
contentPanel.setLayout(new BorderLayout(5, 5));
contentPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
contentPanel.add(scrollPane, BorderLayout.CENTER);
contentPanel.add(buttonPane, BorderLayout.PAGE_END);
getContentPane().add(contentPanel);
pack();
setSize(500, 300);
setLocationRelativeTo(null);
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
}
protected JPanel initButtonsPanel() {
JButton cancelButton = new JButton(NLS.str("common_dialog.cancel"));
cancelButton.addActionListener(event -> dispose());
JButton okBtn = new JButton(NLS.str("common_dialog.ok"));
okBtn.addActionListener(event -> generateForSelected());
getRootPane().setDefaultButton(okBtn);
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
buttonPane.add(Box.createHorizontalGlue());
buttonPane.add(okBtn);
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
buttonPane.add(cancelButton);
return buttonPane;
}
private void generateForSelected() {
List<JavaMethod> selectedMethods = methodList.getSelectedValuesList();
if (!selectedMethods.isEmpty()) {
this.listConsumer.accept(selectedMethods);
}
dispose();
}
}
@@ -334,6 +334,8 @@ msg.project_error=Projekt konnte nicht geladen werden
msg.cmd_select_class_error=Klasse\n%s auswählen nicht möglich\nSie existiert nicht.
msg.cant_add_comment=Kann hier keinen Kommentar hinzufügen
#methods_dialog.title=Select methods
popup.bytecode_col=Dalvik-Bytecode anzeigen
popup.line_wrap=Zeilenumbruch
popup.undo=Rückgängig
@@ -334,6 +334,8 @@ msg.project_error=Project could not be loaded
msg.cmd_select_class_error=Failed to select the class\n%s\nThe class does not exist.
msg.cant_add_comment=Can't add comment here
methods_dialog.title=Select methods
popup.bytecode_col=Show Dalvik Bytecode
popup.line_wrap=Line Wrap
popup.undo=Undo
@@ -334,6 +334,8 @@ msg.language_changed=El nuevo idioma se mostrará la próxima vez que la aplicac
#msg.cmd_select_class_error=Failed to select the class\n%s\nThe class does not exist.
#msg.cant_add_comment=Can't add comment here
#methods_dialog.title=Select methods
#popup.bytecode_col=Show Dalvik Bytecode
#popup.line_wrap=Line Wrap
popup.undo=Deshacer
@@ -334,6 +334,8 @@ msg.project_error=Proyek tidak dapat dimuat
msg.cmd_select_class_error=Gagal memilih kelas\n%s\nKelas tidak ada.
msg.cant_add_comment=Tidak dapat menambahkan komentar di sini
#methods_dialog.title=Select methods
popup.bytecode_col=Tampilkan Bytecode Dalvik
popup.line_wrap=Baris Wrap
popup.undo=Kembalikan
@@ -334,6 +334,8 @@ msg.project_error=프로젝트를 로드 할 수 없습니다.
msg.cmd_select_class_error=클래스를 선택하지 못했습니다.\n%s\n클래스가 없습니다.
msg.cant_add_comment=여기에 주석을 추가할수 없음
#methods_dialog.title=Select methods
popup.bytecode_col=Dalvik Bytecode 보이기
popup.line_wrap=줄 바꿈
popup.undo=실행 취소
@@ -334,6 +334,8 @@ msg.project_error=Projeto não pôde ser carregado
msg.cmd_select_class_error=Falha ao selecionar classe\n%s\nA classe não existe.
msg.cant_add_comment=Não é possível adicionar comentários aqui
#methods_dialog.title=Select methods
popup.bytecode_col=Mostrar Dalvik Bytecode
popup.line_wrap=Quebra de linha
popup.undo=Desfazer
@@ -334,6 +334,8 @@ msg.project_error=Проект не может быть загружен
msg.cmd_select_class_error=Ошибка выбора класса\n%s\nЭтот класс не существует.
msg.cant_add_comment=Невозможно добавить комментарий сюда
#methods_dialog.title=Select methods
popup.bytecode_col=Показать Dalvik байткод
popup.line_wrap=Перенос строк
popup.undo=Отменить
@@ -334,6 +334,8 @@ msg.project_error=项目无法加载
msg.cmd_select_class_error=无法选择类\n%s\n该类不存在。
msg.cant_add_comment=无法在此添加注释
#methods_dialog.title=Select methods
popup.bytecode_col=显示Dalvik字节码
popup.line_wrap=自动换行
popup.undo=撤销
@@ -334,6 +334,8 @@ msg.project_error=無法載入專案
msg.cmd_select_class_error=無法選擇類別\n%s\n類別不存在。
msg.cant_add_comment=無法在此新增註解
#methods_dialog.title=Select methods
popup.bytecode_col=顯示 Dalvik 位元組碼
popup.line_wrap=自動換行
popup.undo=復原