diff --git a/README.md b/README.md index a67701d..c6f90c6 100644 --- a/README.md +++ b/README.md @@ -37,14 +37,14 @@ Currently, the project has only one dynamic plugin: rename from constant arg. It 1. Use the shortcut "Ctrl+Alt+U" while selecting a method. Then, choose the plugin like you choose a one time plugin. 2. Use the shortcut "Ctrl+Alt+L" while selecting a method. -### Scripts +### Scripts The project comes with some handy scripts you can use. ### Naming convention -The naming convention of the plugin is `original__Reason_NewName`. -For example, it may convert `axu` to `axu__A_SettingsUtils`. +The naming convention of the plugin is `original__Reason_NewName`. For example, it may convert `axu` +to `axu__A_SettingsUtils`. ## Plugins @@ -129,10 +129,21 @@ Before: // PARTIAL FAILURE: ENUM SUGARING // The enumeration is rendered as-is instead of being sugared into a Java 5 enum. static final class b extends Enum { - public static final enum b U; - public static final enum b V; - public static final enum b W; - public static final enum b X; + public static final enum b U + + ; + + public static final enum b V + + ; + + public static final enum b W + + ; + + public static final enum b X + + ; private static final b[] Y; static { @@ -153,10 +164,21 @@ After: ```java static final class b__T_Enum extends Enum { - public static final enum b__T_Enum U__E_NONE; - public static final enum b__T_Enum V__E_START; - public static final enum b__T_Enum W__E_END; - public static final enum b__T_Enum X__E_CENTER; + public static final enum b__T_Enum U__E_NONE + + ; + + public static final enum b__T_Enum V__E_START + + ; + + public static final enum b__T_Enum W__E_END + + ; + + public static final enum b__T_Enum X__E_CENTER + + ; private static final b__T_Enum[] Y; } ``` @@ -298,7 +320,7 @@ Where: * `renamer_bundle_get.py` - supports the case of `var = get("LONG.DOT.SEPARATED.NAME.NOTIFICATION_TITLE")` * `renamer_bundle_set.py` - supports the case of `set("LONG.DOT.SEPARATED.NAME.NOTIFICATION_TITLE", var)` * `renamer_log_renamer.py` - supports the case of `log("CLASSNAME MAYBE_METHODNAME", ...)` - + The project comes with a builtin list, supporting: Intent, Bundle, ContentValues, org.json and shared preferences. It doesn't support external libraries because they would probably be obfuscated. @@ -322,27 +344,28 @@ Stats: Before: ```java -private static Float d(Intent arg3) { - int v0 = arg3.getIntExtra("level", -1); - int v3 = arg3.getIntExtra("scale", -1); - return v0 == -1 || v3 == -1 ? null : ((float)(((float)v0) / ((float)v3))); -} +private static Float d(Intent arg3){ + int v0=arg3.getIntExtra("level",-1); + int v3=arg3.getIntExtra("scale",-1); + return v0==-1||v3==-1?null:((float)(((float)v0)/((float)v3))); + } ``` After: ```java -private static Float d(Intent arg3) { - int __A_Level = arg3.getIntExtra("level", -1); - int __A_Scale = arg3.getIntExtra("scale", -1); - return __A_Level == -1 || __A_Scale == -1 ? null : ((float)(((float)__A_Level) / ((float)__A_Scale))); -} +private static Float d(Intent arg3){ + int __A_Level=arg3.getIntExtra("level",-1); + int __A_Scale=arg3.getIntExtra("scale",-1); + return __A_Level==-1||__A_Scale==-1?null:((float)(((float)__A_Level)/((float)__A_Scale))); + } ``` -#### Getters and setters renamers -A common case not covered by the previous method is getters & setters. We provide a plugin that uses same infrastructure as the previous plugins, -but searches for methods of the form getX() and setY(param). It will rename the asignee and argument as expected. +#### Getters and setters renamers +A common case not covered by the previous method is getters & setters. We provide a plugin that uses same infrastructure +as the previous plugins, but searches for methods of the form getX() and setY(param). It will rename the asignee and +argument as expected. ##### Example @@ -356,33 +379,115 @@ Stats: Before: ```java -ComponentName v0_1 = MediaButtonReceiver.getServiceComponentByAction(arg4, "android.media.browse.MediaBrowserService"); -if(v0_1 != null) { - BroadcastReceiver.PendingResult v1 = this.goAsync(); - Context v3 = arg4.getApplicationContext(); - MediaButtonConnectionCallback v2 = new MediaButtonConnectionCallback(v3, arg5, v1); - MediaBrowserCompat v4 = new MediaBrowserCompat(v3, v0_1, v2, null); - v2.setMediaBrowser(v4); - v4.connect(); - return; +ComponentName v0_1=MediaButtonReceiver.getServiceComponentByAction(arg4,"android.media.browse.MediaBrowserService"); + if(v0_1!=null){ + BroadcastReceiver.PendingResult v1=this.goAsync(); + Context v3=arg4.getApplicationContext(); + MediaButtonConnectionCallback v2=new MediaButtonConnectionCallback(v3,arg5,v1); + MediaBrowserCompat v4=new MediaBrowserCompat(v3,v0_1,v2,null); + v2.setMediaBrowser(v4); + v4.connect(); + return; + } +``` + +After: + +```java +ComponentName v0_1=MediaButtonReceiver.getServiceComponentByAction(arg4,"android.media.browse.MediaBrowserService"); + if(v0_1!=null){ + BroadcastReceiver.PendingResult v1=this.goAsync(); + Context __A_applicationContext=arg4.getApplicationContext(); + MediaButtonConnectionCallback v2=new MediaButtonConnectionCallback(__A_applicationContext,arg5,v1); + MediaBrowserCompat __A_mediaBrowser=new MediaBrowserCompat(__A_applicationContext,v0_1,v2,null); + v2.setMediaBrowser(__A_mediaBrowser); + __A_mediaBrowser.connect(); + return; + } +``` + +### Source file name + +Sometimes the dex contains as a metadata, the source file name for some classes. JEB by default does not show it for +smali view, and never shows it for decompiler view. The plugin adds comment for every class that has this debug +attribute, with the source name. If enabled, it would also add the source file name as part of the class name. + +**Warning:** Some obfuscator sets the source file to a specific string to ALL classes - Use with caution. You are +advised to enable debug directives, and search for ".source", to see if this is indeed the case. + +We could not run it on Twitter, since the source file name is always `"TWTR"` + +### Example + +Before: + +```java +class A {... +} + +class B {... } ``` After: ```java -ComponentName v0_1 = MediaButtonReceiver.getServiceComponentByAction(arg4, "android.media.browse.MediaBrowserService"); -if(v0_1 != null) { - BroadcastReceiver.PendingResult v1 = this.goAsync(); - Context __A_applicationContext = arg4.getApplicationContext(); - MediaButtonConnectionCallback v2 = new MediaButtonConnectionCallback(__A_applicationContext, arg5, v1); - MediaBrowserCompat __A_mediaBrowser = new MediaBrowserCompat(__A_applicationContext, v0_1, v2, null); - v2.setMediaBrowser(__A_mediaBrowser); - __A_mediaBrowser.connect(); - return; +/* if comment only is enabled */ +// Source name: RandomUtils.java +class A {... +} + +/* if class name renaming is enabled */ +class B__SF_UserInfo { ... +} +``` + +### Kotlin metadata + +If the apk does not obfuscate the kotlin metadata information, We could get a lot of information about the class for +free. The plugin also supports obfuscated name for the metadata annotation. + +#### Example + +Before: + +```java + +@Metadata(bv = {1, 0, 3}, d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0012\u0010\u0003\u001A\u00020\u00042\b\u0010\u0005\u001A\u0004\u0018\u00010\u0006H\u0014\u00A8\u0006\u0007"}, d2 = {"Lcom/yoavst/testing/project/MainActivity;", "Landroidx/appcompat/app/AppCompatActivity;", "()V", "onCreate", "", "savedInstanceState", "Landroid/os/Bundle;", "app_debug"}, k = 1, mv = {1, 1, 15}) +public final class A extends AppCompatActivity { + } ``` +After: + +```java +/* +Kotlin metadata: +Type: Class +Class Info: + Name: com/yoavst/testing/project/MainActivity + Supertypes: Class(name=androidx/appcompat/app/AppCompatActivity) + Module Name: app_debug + Type Aliases: + Companion Object: + Nested Classes: + Enum Entries: + +Constructors: + ()V, Arguments: + +Functions: + onCreate(Landroid/os/Bundle;)V, Arguments: savedInstanceState + +Properties: + +*/ +@Metadata(bv = {1, 0, 3}, d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0012\u0010\u0003\u001A\u00020\u00042\b\u0010\u0005\u001A\u0004\u0018\u00010\u0006H\u0014\u00A8\u0006\u0007"}, d2 = {"Lcom/yoavst/testing/project/MainActivity;", "Landroidx/appcompat/app/AppCompatActivity;", "()V", "onCreate", "", "savedInstanceState", "Landroid/os/Bundle;", "app_debug"}, k = 1, mv = {1, 1, 15}) +public final class A__KT_MainActivity extends AppCompatActivity { + +} +``` ## Development @@ -392,11 +497,12 @@ The plugin relays on JEB internal api for some purposes. It was tested on JEB 3. 1. `UIUtils` depends on `OptionsForEnginesPluginDialog` for showing options dialog from script. As such, it also depends on SWT. - + ### Utils -You can use the script `JarLoader.py` to run a plugin directly from a jar. -For that to work, you need to delete the current version from `coreplugins`, and have your code to have no reference after running. +You can use the script `JarLoader.py` to run a plugin directly from a jar. For that to work, you need to delete the +current version from `coreplugins`, and have your code to have no reference after running. ## Wishlist + 1. Create an informative name from short methods body \ No newline at end of file diff --git a/build.gradle b/build.gradle index c103dd9..1aae9ad 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,7 @@ jar { "com.yoavst.jeb.plugins.enumsupport.EnumRenamingPlugin", "com.yoavst.jeb.plugins.resourcesname.ResourcesNamePlugin", "com.yoavst.jeb.plugins.kotlin.KotlinPlugin", + "com.yoavst.jeb.plugins.sourcefile.SourceFilePlugin", "com.yoavst.jeb.plugins.tostring.ToStringRenamingPlugin"].join(" "), "jebPlugin-version": project.version ) diff --git a/src/main/kotlin/com/yoavst/jeb/plugins/kotlin/KotlinPlugin.kt b/src/main/kotlin/com/yoavst/jeb/plugins/kotlin/KotlinPlugin.kt index 7c4608e..51520ad 100644 --- a/src/main/kotlin/com/yoavst/jeb/plugins/kotlin/KotlinPlugin.kt +++ b/src/main/kotlin/com/yoavst/jeb/plugins/kotlin/KotlinPlugin.kt @@ -155,18 +155,16 @@ class KotlinPlugin : BasicEnginesPlugin(supportsClassFilter = true, defaultForSc seq.filter(classFilter::matches) } - val tmp = unit.classBySignature("Lcom/yoavst/testing/project/MainActivity;")!! - val kotlinAnnotation = tmp.annotationsDirectory.classAnnotations[0].annotation - processClass(unit, tmp, kotlinAnnotation, renameEngine) - - + var i = 0 seq.filter { it.annotationsDirectory?.classAnnotations?.isNotEmpty() ?: false }.mapToPairNotNull { cls -> cls.annotationsDirectory.classAnnotations.firstOrNull { it.annotation.typeIndex == annotationTypeIndex }?.annotation }.forEach { (cls, annotation) -> + i++ processClass(unit, cls, annotation, renameEngine) } + logger.info("There are $i classes with kotlin metadata annotation!") } private fun processClass(unit: IDexUnit, cls: IDexClass, annotation: IDexAnnotation, renameEngine: RenameEngine) { @@ -181,6 +179,16 @@ class KotlinPlugin : BasicEnginesPlugin(supportsClassFilter = true, defaultForSc val header = KotlinClassHeader(k, mv, d1, d2, xs, pn, xi) + val originalComment = unit.getComment(cls.currentSignature) ?: "" + if (KOTLIN_METADATA_COMMENT_PREFIX !in originalComment) { + val comment = header.toStringBlock() + if (originalComment.isBlank()) { + unit.setComment(cls.currentSignature, comment) + } else { + unit.setComment(cls.currentSignature, originalComment + "\n\n" + comment) + } + } + when (val metadata = KotlinClassMetadata.read(header)) { is KotlinClassMetadata.Class -> { val classInfo = metadata.toKmClass() diff --git a/src/main/kotlin/com/yoavst/jeb/plugins/sourcefile/SourceFilePlugin.kt b/src/main/kotlin/com/yoavst/jeb/plugins/sourcefile/SourceFilePlugin.kt new file mode 100644 index 0000000..cab35f7 --- /dev/null +++ b/src/main/kotlin/com/yoavst/jeb/plugins/sourcefile/SourceFilePlugin.kt @@ -0,0 +1,99 @@ +package com.yoavst.jeb.plugins.sourcefile + +import com.pnfsoftware.jeb.core.BooleanOptionDefinition +import com.pnfsoftware.jeb.core.IOptionDefinition +import com.pnfsoftware.jeb.core.IPluginInformation +import com.pnfsoftware.jeb.core.PluginInformation +import com.pnfsoftware.jeb.core.units.code.android.IDexUnit +import com.pnfsoftware.jeb.core.units.code.android.dex.IDexClass +import com.yoavst.jeb.bridge.UIBridge +import com.yoavst.jeb.plugins.JEB_VERSION +import com.yoavst.jeb.plugins.PLUGIN_VERSION +import com.yoavst.jeb.utils.BasicEnginesPlugin +import com.yoavst.jeb.utils.currentSignature +import com.yoavst.jeb.utils.matches +import com.yoavst.jeb.utils.renaming.RenameEngine +import com.yoavst.jeb.utils.renaming.RenameReason +import com.yoavst.jeb.utils.renaming.RenameRequest + +class SourceFilePlugin : BasicEnginesPlugin(supportsClassFilter = true, defaultForScopeOnThisClass = false) { + private var shouldAddComment: Boolean = false + private var shouldAddToTypeName: Boolean = false + override fun getPluginInformation(): IPluginInformation = PluginInformation( + "Source file information", + "Fire the plugin propagate source file info to decompilation view and to classes' name", + "Yoav Sternberg", + PLUGIN_VERSION, + JEB_VERSION, + null + ) + + override fun getExecutionOptionDefinitions(): List { + return super.getExecutionOptionDefinitions() + BooleanOptionDefinition( + ADD_COMMENT, + true, + """Add the original source file as comment to the class""" + ) + BooleanOptionDefinition( + ADD_TO_TYPE_NAME, + false, + """Add the original source file as part of the type""" + ) + } + + override fun processOptions(executionOptions: Map): Boolean { + super.processOptions(executionOptions) + shouldAddComment = executionOptions.getOrDefault(ADD_COMMENT, "true").toBoolean() + shouldAddToTypeName = executionOptions.getOrDefault(ADD_TO_TYPE_NAME, "false").toBoolean() + return true + } + + override fun processUnit(unit: IDexUnit, renameEngine: RenameEngine) { + if (!shouldAddComment && !shouldAddToTypeName) + return + + var seq = unit.classes.asSequence() + seq = if (isOperatingOnlyOnThisClass) { + seq.filter { it.classType == UIBridge.currentClass } + } else { + seq.filter(classFilter::matches) + } + + var i = 0 + seq.filter { it.sourceStringIndex != -1 }.forEach { + i++ + processClass(unit, it, renameEngine) + } + logger.info("There are $i classes with source info") + } + + private fun processClass(unit: IDexUnit, cls: IDexClass, renameEngine: RenameEngine) { + val sourceName = unit.getString(cls.sourceStringIndex).value + if (sourceName.isBlank()) return + val sourceWithExtension = sourceName.split('.', limit = 2)[0] + + if (shouldAddComment) { + if (sourceWithExtension !in cls.currentSignature && !cls.isMemberClass) { + val originalComment = unit.getComment(cls.currentSignature) ?: "" + if (COMMENT_PREFIX !in originalComment) { + val comment = "$COMMENT_PREFIX$sourceName" + if (originalComment.isBlank()) { + unit.setComment(cls.currentSignature, comment) + } else { + unit.setComment(cls.currentSignature, originalComment + "\n\n" + comment) + } + } + } + } + if (shouldAddToTypeName) { + if (sourceWithExtension !in cls.currentSignature && !cls.isMemberClass) { + renameEngine.renameClass(RenameRequest(sourceWithExtension, RenameReason.SourceFile), cls) + } + } + } + + companion object { + private const val ADD_COMMENT = "add comment" + private const val ADD_TO_TYPE_NAME = "add to type name" + private const val COMMENT_PREFIX = "source name: " + } +} diff --git a/src/main/kotlin/com/yoavst/jeb/utils/MetadataUtils.kt b/src/main/kotlin/com/yoavst/jeb/utils/MetadataUtils.kt new file mode 100644 index 0000000..2b5619b --- /dev/null +++ b/src/main/kotlin/com/yoavst/jeb/utils/MetadataUtils.kt @@ -0,0 +1,79 @@ +package com.yoavst.jeb.utils + +import kotlinx.metadata.jvm.* + +// taken from https://github.com/mforlini/KMparse/blob/master/src/main/kotlin/io/github/mforlini/kmparse/Extensions.kt +private const val INDENT = "\n| " +const val KOTLIN_METADATA_COMMENT_PREFIX = "Kotlin metadata:" +fun KotlinClassHeader.toStringBlock(): String { + return when (val metadata = KotlinClassMetadata.read(this)) { + is KotlinClassMetadata.Class -> { + val klass = metadata.toKmClass() + """$KOTLIN_METADATA_COMMENT_PREFIX + |Type: Class + |Class Info: + | Name: ${klass.name} + | Supertypes: ${klass.supertypes.joinToString(", ") { it.classifier.toString() }} + | Module Name: ${klass.moduleName} + | Type Aliases: ${klass.typeAliases.joinToString(", ")} + | Companion Object: ${klass.companionObject ?: ""} + | Nested Classes: ${klass.nestedClasses.joinToString(", ")} + | Enum Entries: ${klass.enumEntries.joinToString(", ")} + | + |Constructors:${klass.constructors.joinToString(separator = INDENT, prefix = INDENT) { "${it.signature}, Arguments: ${it.valueParameters.joinToString(", ") { arg -> arg.name }}" }} + | + |Functions:${klass.functions.joinToString(separator = INDENT, prefix = INDENT) { "${it.signature}, Arguments: ${it.valueParameters.joinToString(", ") { arg -> arg.name }}" }} + | + |Properties:${klass.properties.joinToString(separator = INDENT, prefix = INDENT) { "${it.fieldSignature}" }} + """.trimMargin() + } + is KotlinClassMetadata.FileFacade -> { + val klass = metadata.toKmPackage() + """$KOTLIN_METADATA_COMMENT_PREFIX + |Type: File Facade + | + |Functions:${klass.functions.joinToString(separator = INDENT, prefix = INDENT) { "${it.signature}, Arguments: ${it.valueParameters.joinToString(", ") { arg -> arg.name }}" }} + | + |Properties:${klass.properties.joinToString(separator = INDENT, prefix = INDENT) { "${it.fieldSignature}" }} + """.trimMargin() + } + is KotlinClassMetadata.SyntheticClass -> { + if (metadata.isLambda) { + val klass = metadata.toKmLambda() + """$KOTLIN_METADATA_COMMENT_PREFIX + |Type: Synthetic Class + |Is Kotlin Lambda: True + | + |Functions: + | ${klass?.function?.signature}, Arguments: ${klass?.function?.valueParameters?.joinToString(", ") { it.name }} + + """.trimMargin() + + } else { + """$KOTLIN_METADATA_COMMENT_PREFIX + |Type: Synthetic Class + |Is Kotlin Lambda: False + """.trimMargin() + + } + } + is KotlinClassMetadata.MultiFileClassFacade -> """$KOTLIN_METADATA_COMMENT_PREFIX + |Type: Multi-File Class Facade + |This multi-file class combines: + |${metadata.partClassNames.joinToString(separator = INDENT, prefix = INDENT) { "Class: $it" }} + """.trimMargin() + is KotlinClassMetadata.MultiFileClassPart -> { + val klass = metadata.toKmPackage() + """$KOTLIN_METADATA_COMMENT_PREFIX + |Type: Multi-File Class Part + |Name: ${metadata.facadeClassName} + | + |Functions:${klass.functions.joinToString(separator = INDENT, prefix = INDENT) { "${it.signature}, Arguments: ${it.valueParameters.joinToString(", ") { arg -> arg.name }}" }} + | + |Properties:${klass.properties.joinToString(separator = INDENT, prefix = INDENT) { "${it.fieldSignature}" }} + """.trimMargin() + } + is KotlinClassMetadata.Unknown -> """$KOTLIN_METADATA_COMMENT_PREFIX Type: Unknown""" + null -> "" + } +} diff --git a/src/main/kotlin/com/yoavst/jeb/utils/renaming/RenameReason.kt b/src/main/kotlin/com/yoavst/jeb/utils/renaming/RenameReason.kt index ed5a629..68917c9 100644 --- a/src/main/kotlin/com/yoavst/jeb/utils/renaming/RenameReason.kt +++ b/src/main/kotlin/com/yoavst/jeb/utils/renaming/RenameReason.kt @@ -6,6 +6,7 @@ package com.yoavst.jeb.utils.renaming */ enum class RenameReason(val prefix: String) : Comparable { Type("T"), + SourceFile("SF"), FieldName("F"), Log("L"), Resource("R"), diff --git a/src/main/resources/rename_signatures.md b/src/main/resources/rename_signatures.md index 16c099f..06fcf18 100644 --- a/src/main/resources/rename_signatures.md +++ b/src/main/resources/rename_signatures.md @@ -266,3 +266,7 @@ Argument Landroid/content/SharedPreferences$Editor;->putInt(Ljava/lang/String;I) Argument Landroid/content/SharedPreferences$Editor;->putLong(Ljava/lang/String;J)Landroid/content/SharedPreferences$Editor; 0 1 Argument Landroid/content/SharedPreferences$Editor;->putString(Ljava/lang/String;Ljava/lang/String;)Landroid/content/SharedPreferences$Editor; 0 1 Argument Landroid/content/SharedPreferences$Editor;->putStringSet(Ljava/lang/String;Ljava/util/Set;)Landroid/content/SharedPreferences$Editor; 0 1 + +# Kotlin +## Intrinsics +Argument Lkotlin/jvm/internal/Intrinsics;->checkParameterIsNotNull(Ljava/lang/Object;Ljava/lang/String;)V 1 0 \ No newline at end of file