121 lines
3.5 KiB
Kotlin
121 lines
3.5 KiB
Kotlin
@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.collectArgNodes().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'"
|
|
}
|