feat: add events support (#1832)
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
@file:DependsOn("org.apache.commons:commons-text:1.10.0")
|
||||
|
||||
import jadx.api.metadata.ICodeNodeRef
|
||||
import jadx.core.codegen.TypeGen
|
||||
import jadx.core.dex.instructions.args.ArgType
|
||||
import jadx.core.dex.nodes.ClassNode
|
||||
import jadx.core.dex.nodes.FieldNode
|
||||
import jadx.core.dex.nodes.MethodNode
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException
|
||||
import org.apache.commons.text.StringEscapeUtils
|
||||
|
||||
val jadx = getJadxInstance()
|
||||
|
||||
jadx.gui.ifAvailable {
|
||||
addPopupMenuAction(
|
||||
"Custom Frida snippet (g)",
|
||||
enabled = ::isActionEnabled,
|
||||
keyBinding = "G",
|
||||
action = ::runAction,
|
||||
)
|
||||
}
|
||||
|
||||
fun isActionEnabled(node: ICodeNodeRef): Boolean {
|
||||
return node is MethodNode || node is ClassNode || node is FieldNode
|
||||
}
|
||||
|
||||
fun runAction(node: ICodeNodeRef) {
|
||||
try {
|
||||
val fridaSnippet = generateFridaSnippet(node)
|
||||
log.info { "Custom frida snippet:\n$fridaSnippet" }
|
||||
jadx.gui.copyToClipboard(fridaSnippet)
|
||||
} catch (e: Exception) {
|
||||
log.error(e) { "Failed to generate Frida code snippet" }
|
||||
}
|
||||
}
|
||||
|
||||
fun generateFridaSnippet(node: ICodeNodeRef): String {
|
||||
return when (node) {
|
||||
is MethodNode -> generateMethodSnippet(node)
|
||||
is ClassNode -> generateClassSnippet(node)
|
||||
is FieldNode -> generateFieldSnippet(node)
|
||||
else -> throw JadxRuntimeException("Unsupported node type: " + node.javaClass)
|
||||
}
|
||||
}
|
||||
|
||||
fun generateClassSnippet(cls: ClassNode): String {
|
||||
return """let ${cls.name} = Java.use("${StringEscapeUtils.escapeEcmaScript(cls.rawName)}");"""
|
||||
}
|
||||
|
||||
fun generateMethodSnippet(mthNode: MethodNode): String {
|
||||
val methodInfo = mthNode.methodInfo
|
||||
val methodName = if (methodInfo.isConstructor) {
|
||||
"\$init"
|
||||
} else {
|
||||
StringEscapeUtils.escapeEcmaScript(methodInfo.name)
|
||||
}
|
||||
val overload = if (isOverloaded(mthNode)) {
|
||||
".overload(${methodInfo.argumentsTypes.joinToString(transform = this::parseArgType)})"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
val shortClassName = mthNode.parentClass.name
|
||||
val argNames = mthNode.collectArgsWithoutLoading().map { a -> a.name }
|
||||
val args = argNames.joinToString(separator = ", ")
|
||||
val logArgs = if (argNames.isNotEmpty()) {
|
||||
argNames.joinToString(separator = " + ', ' + ", prefix = " + ', ' + ") { p -> "'$p: ' + $p" }
|
||||
} else {
|
||||
""
|
||||
}
|
||||
val clsSnippet = generateClassSnippet(mthNode.parentClass)
|
||||
return if (methodInfo.isConstructor || methodInfo.returnType == ArgType.VOID) {
|
||||
// no return value
|
||||
"""
|
||||
$clsSnippet
|
||||
$shortClassName["$methodName"]$overload.implementation = function ($args) {
|
||||
console.log('$shortClassName.$methodName is called'$logArgs);
|
||||
this["$methodName"]($args);
|
||||
};
|
||||
""".trimIndent()
|
||||
} else {
|
||||
"""
|
||||
$clsSnippet
|
||||
$shortClassName["$methodName"]$overload.implementation = function ($args) {
|
||||
console.log('$shortClassName.$methodName is called'$logArgs);
|
||||
let ret = this["$methodName"]($args);
|
||||
console.log('$shortClassName.$methodName return: ' + ret);
|
||||
return ret;
|
||||
};
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
fun generateFieldSnippet(fld: FieldNode): String {
|
||||
var rawFieldName = StringEscapeUtils.escapeEcmaScript(fld.name)
|
||||
for (methodNode in fld.parentClass.methods) {
|
||||
if (methodNode.name == rawFieldName) {
|
||||
rawFieldName = "_$rawFieldName"
|
||||
break
|
||||
}
|
||||
}
|
||||
return """
|
||||
${generateClassSnippet(fld.parentClass)}
|
||||
${fld.name} = ${fld.parentClass.name}.$rawFieldName.value;
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
fun isOverloaded(methodNode: MethodNode): Boolean {
|
||||
return methodNode.parentClass.methods.stream().anyMatch { m: MethodNode ->
|
||||
m.name == methodNode.name && methodNode.methodInfo.shortId != m.methodInfo.shortId
|
||||
}
|
||||
}
|
||||
|
||||
fun parseArgType(x: ArgType): String {
|
||||
val typeStr = if (x.isArray) {
|
||||
TypeGen.signature(x).replace("/", ".")
|
||||
} else {
|
||||
x.toString()
|
||||
}
|
||||
return "'$typeStr'"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import jadx.api.plugins.events.JadxEvents
|
||||
|
||||
/**
|
||||
* Log events
|
||||
*/
|
||||
|
||||
val jadx = getJadxInstance()
|
||||
|
||||
jadx.events.addListener(JadxEvents.NODE_RENAMED_BY_USER) { rename ->
|
||||
log.info { "Rename from '${rename.oldName}' to '${rename.newName}' for node ${rename.node}" }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// customize jadx-gui
|
||||
|
||||
val jadx = getJadxInstance()
|
||||
|
||||
jadx.gui.ifAvailable {
|
||||
addMenuAction("Decompile All") {
|
||||
jadx.decompile.all()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user