├── .gitignore ├── .idea ├── .gitignore ├── .name ├── compiler.xml ├── dictionaries ├── jarRepositories.xml ├── kotlinc.xml ├── libraries-with-intellij-classes.xml ├── misc.xml └── vcs.xml ├── LICENSE.txt ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── kotlin └── com │ └── yoavst │ └── jeb │ ├── bridge │ └── UIBridge.kt │ ├── plugins │ ├── constarg │ │ ├── ConstArgMassRenaming.kt │ │ ├── ConstArgMassRenamingPlugin.kt │ │ ├── ConstArgRenamingPlugin.kt │ │ ├── ExtendedRenamer.kt │ │ ├── GetXPlugin.kt │ │ ├── RenameResult.kt │ │ ├── RenameSignaturesFileParser.kt │ │ ├── RenameTarget.kt │ │ └── Renamers.kt │ ├── consts.kt │ ├── enumsupport │ │ ├── BaseDalvikSimulator.kt │ │ ├── EnumRenamingPlugin.kt │ │ ├── MultiEnumStaticConstructorSimulator.kt │ │ └── SingleEnumConstructorSimulator.kt │ ├── kotlin │ │ ├── IntrinsicDetectionResult.kt │ │ ├── IntrinsicsMethodDetector.kt │ │ ├── KotlinAnnotationPlugin.kt │ │ └── KotlinIntrinsicsPlugin.kt │ ├── resourcesname │ │ ├── ResourceId.kt │ │ └── ResourcesNamePlugin.kt │ ├── sourcefile │ │ └── SourceFilePlugin.kt │ ├── tostring │ │ └── ToStringRenamingPlugin.kt │ └── xrefsquery │ │ ├── JythonHelper.kt │ │ └── XrefsQueryPlugin.kt │ └── utils │ ├── AstUtils.kt │ ├── BasicAstTraversal.kt │ ├── BasicEnginesPlugin.kt │ ├── ClassUtils.kt │ ├── ContextUtils.kt │ ├── DecompilerUtils.kt │ ├── FocusUtils.kt │ ├── GetterSetterUtils.kt │ ├── LogUtils.kt │ ├── MetadataUtils.kt │ ├── MethodUtils.kt │ ├── NameUtils.kt │ ├── OptionsUtils.kt │ ├── UIUtils.kt │ ├── UnitUtils.kt │ ├── renaming │ ├── InternalRenameRequest.kt │ ├── RenameBackendEngine.kt │ ├── RenameBackendEngineImpl.kt │ ├── RenameEngine.kt │ ├── RenameEngineImpl.kt │ ├── RenameFrontendEngine.kt │ ├── RenameFrontendEngineImpl.kt │ ├── RenameObjectType.kt │ ├── RenameReason.kt │ ├── RenameRequest.kt │ └── RenameStats.kt │ ├── script │ └── UIUtils.kt │ └── utils.kt ├── python ├── ClassSearch.py ├── FridaHook.py ├── JarLoader.py ├── MethodsOfClass.py ├── Phenotype.py ├── RenameFromConstArg.py ├── UIBridge.py ├── coreplugins │ ├── DNoSync.py │ └── DPropagation.py └── utils.py └── resources ├── aosp_public.xml ├── rename_signatures.md ├── renamer_bundle_get.py ├── renamer_bundle_set.py └── renamer_log_renamer.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/intellij,java,kotlin,gradle 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=intellij,java,kotlin,gradle 4 | 5 | ### Intellij ### 6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 8 | 9 | # User-specific stuff 10 | .idea/**/workspace.xml 11 | .idea/**/tasks.xml 12 | .idea/**/usage.statistics.xml 13 | .idea/**/dictionaries 14 | .idea/**/shelf 15 | 16 | # Generated files 17 | .idea/**/contentModel.xml 18 | 19 | # Sensitive or high-churn files 20 | .idea/**/dataSources/ 21 | .idea/**/dataSources.ids 22 | .idea/**/dataSources.local.xml 23 | .idea/**/sqlDataSources.xml 24 | .idea/**/dynamic.xml 25 | .idea/**/uiDesigner.xml 26 | .idea/**/dbnavigator.xml 27 | 28 | # Gradle 29 | .idea/**/gradle.xml 30 | .idea/**/libraries 31 | 32 | # Gradle and Maven with auto-import 33 | # When using Gradle or Maven with auto-import, you should exclude module files, 34 | # since they will be recreated, and may cause churn. Uncomment if using 35 | # auto-import. 36 | # .idea/artifacts 37 | # .idea/compiler.xml 38 | # .idea/jarRepositories.xml 39 | # .idea/modules.xml 40 | # .idea/*.iml 41 | # .idea/modules 42 | # *.iml 43 | # *.ipr 44 | 45 | # CMake 46 | cmake-build-*/ 47 | 48 | # Mongo Explorer plugin 49 | .idea/**/mongoSettings.xml 50 | 51 | # File-based project format 52 | *.iws 53 | 54 | # IntelliJ 55 | out/ 56 | 57 | # mpeltonen/sbt-idea plugin 58 | .idea_modules/ 59 | 60 | # JIRA plugin 61 | atlassian-ide-plugin.xml 62 | 63 | # Cursive Clojure plugin 64 | .idea/replstate.xml 65 | 66 | # Crashlytics plugin (for Android Studio and IntelliJ) 67 | com_crashlytics_export_strings.xml 68 | crashlytics.properties 69 | crashlytics-build.properties 70 | fabric.properties 71 | 72 | # Editor-based Rest Client 73 | .idea/httpRequests 74 | 75 | # Android studio 3.1+ serialized cache file 76 | .idea/caches/build_file_checksums.ser 77 | 78 | ### Intellij Patch ### 79 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 80 | 81 | # *.iml 82 | # modules.xml 83 | # .idea/misc.xml 84 | # *.ipr 85 | 86 | # Sonarlint plugin 87 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 88 | .idea/**/sonarlint/ 89 | 90 | # SonarQube Plugin 91 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 92 | .idea/**/sonarIssues.xml 93 | 94 | # Markdown Navigator plugin 95 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 96 | .idea/**/markdown-navigator.xml 97 | .idea/**/markdown-navigator-enh.xml 98 | .idea/**/markdown-navigator/ 99 | 100 | # Cache file creation bug 101 | # See https://youtrack.jetbrains.com/issue/JBR-2257 102 | .idea/$CACHE_FILE$ 103 | 104 | # CodeStream plugin 105 | # https://plugins.jetbrains.com/plugin/12206-codestream 106 | .idea/codestream.xml 107 | 108 | ### Java ### 109 | # Compiled class file 110 | *.class 111 | 112 | # Log file 113 | *.log 114 | 115 | # BlueJ files 116 | *.ctxt 117 | 118 | # Mobile Tools for Java (J2ME) 119 | .mtj.tmp/ 120 | 121 | # Package Files # 122 | *.jar 123 | *.war 124 | *.nar 125 | *.ear 126 | *.zip 127 | *.tar.gz 128 | *.rar 129 | 130 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 131 | hs_err_pid* 132 | 133 | ### Kotlin ### 134 | # Compiled class file 135 | 136 | # Log file 137 | 138 | # BlueJ files 139 | 140 | # Mobile Tools for Java (J2ME) 141 | 142 | # Package Files # 143 | 144 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 145 | 146 | ### Gradle ### 147 | .gradle 148 | build/ 149 | offline/ 150 | 151 | # Ignore Gradle GUI config 152 | gradle-app.setting 153 | 154 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 155 | !gradle-wrapper.jar 156 | 157 | # Cache of project 158 | .gradletasknamecache 159 | 160 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 161 | # gradle/wrapper/gradle-wrapper.properties 162 | 163 | ### Gradle Patch ### 164 | **/build/ 165 | 166 | # End of https://www.toptal.com/developers/gitignore/api/intellij,java,kotlin,gradle -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | JebOps -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/dictionaries: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/libraries-with-intellij-classes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 64 | 65 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Yoav Sternberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | dependencies { 6 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10" 7 | } 8 | } 9 | 10 | apply plugin: 'kotlin' 11 | 12 | 13 | group 'org.yoavst.jeb' 14 | version '0.4.5' 15 | 16 | compileKotlin { 17 | kotlinOptions { 18 | jvmTarget = "1.8" 19 | } 20 | } 21 | 22 | sourceCompatibility = JavaVersion.VERSION_1_8 23 | targetCompatibility = JavaVersion.VERSION_1_8 24 | 25 | jar { 26 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 27 | 28 | manifest { 29 | attributes( 30 | "JebPlugin-entryclass": ["com.yoavst.jeb.plugins.constarg.ConstArgRenamingPlugin", 31 | "com.yoavst.jeb.plugins.constarg.ConstArgMassRenamingPlugin", 32 | "com.yoavst.jeb.plugins.constarg.GetXPlugin", 33 | "com.yoavst.jeb.plugins.enumsupport.EnumRenamingPlugin", 34 | "com.yoavst.jeb.plugins.resourcesname.ResourcesNamePlugin", 35 | "com.yoavst.jeb.plugins.kotlin.KotlinAnnotationPlugin", 36 | "com.yoavst.jeb.plugins.kotlin.KotlinIntrinsicsPlugin", 37 | "com.yoavst.jeb.plugins.sourcefile.SourceFilePlugin", 38 | "com.yoavst.jeb.plugins.xrefsquery.XrefsQueryPlugin", 39 | "com.yoavst.jeb.plugins.tostring.ToStringRenamingPlugin"].join(" "), 40 | "jebPlugin-version": project.version 41 | ) 42 | } 43 | from { 44 | configurations.compileResolvable.collect { it.isDirectory() ? it : zipTree(it) } 45 | } 46 | } 47 | 48 | repositories { 49 | mavenCentral() 50 | } 51 | 52 | configurations { 53 | compileResolvable.extendsFrom implementation 54 | } 55 | configurations.compileResolvable.setCanBeResolved(true) 56 | 57 | dependencies { 58 | api 'org.apache.commons:commons-lang3:3.12.0' 59 | compileOnly fileTree(dir: 'libs/runtime', include: ['*.jar']) 60 | compileOnly 'org.python:jython:2.7.3b1' 61 | 62 | compileResolvable platform('org.jetbrains.kotlin:kotlin-bom:1.8.0-Beta') 63 | compileResolvable 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0-Beta' 64 | implementation 'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.5.0' 65 | api 'org.apache.commons:commons-text:1.10.0' 66 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | OFFLINE_BASE_PATH=offline 2 | OFFLINE_BUILD_PATH=offline/build 3 | OFFLINE_COMPILE_PATH=offline/compile 4 | OFFLINE_COMPILE_ONLY_PATH=offline/compileOnly -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoavst/JebOps/69ec2fc941fa9637bcd353920554816862ee3624/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'JebOps' 2 | -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/bridge/UIBridge.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.bridge 2 | 3 | import com.pnfsoftware.jeb.client.api.IGraphicalClientContext 4 | import com.pnfsoftware.jeb.core.output.IItem 5 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexItem 6 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexMethod 7 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexType 8 | import com.yoavst.jeb.utils.currentFocusedMethod 9 | import com.yoavst.jeb.utils.currentFocusedType 10 | 11 | @Suppress("unused") 12 | /** Used for scripts to update ui elements, so plugins could access it **/ 13 | object UIBridge { 14 | var focusedMethod: IDexMethod? = null 15 | private set 16 | var focusedClass: IDexType? = null 17 | private set 18 | var focusedAddr: String? = null 19 | private set 20 | var focusedItem: IItem? = null 21 | private set 22 | 23 | var currentMethod: IDexMethod? = null 24 | private set 25 | 26 | var currentClass: IDexType? = null 27 | private set 28 | 29 | @JvmStatic 30 | fun update(context: IGraphicalClientContext) { 31 | focusedMethod = context.currentFocusedMethod() 32 | focusedClass = context.currentFocusedType() 33 | focusedAddr = context.focusedFragment?.activeAddress 34 | focusedItem = context.focusedFragment?.activeItem 35 | currentMethod = context.currentFocusedMethod(supportFocus = false, verbose = false) 36 | currentClass = context.currentFocusedType(supportFocus = false, verbose = false) 37 | } 38 | 39 | override fun toString(): String = """ 40 | FocusedMethod: $focusedMethod 41 | CurrentMethod: $currentMethod 42 | FocusedClass: $focusedClass 43 | CurrentClass: $currentClass 44 | FocusedItem: $focusedItem 45 | FocusedAddr: $focusedAddr 46 | """.trimIndent() 47 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/constarg/ConstArgMassRenaming.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.constarg 2 | 3 | import com.pnfsoftware.jeb.core.units.code.android.IDexDecompilerUnit 4 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 5 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexClass 6 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexMethod 7 | import com.pnfsoftware.jeb.core.units.code.java.* 8 | import com.pnfsoftware.jeb.util.logging.GlobalLog 9 | import com.pnfsoftware.jeb.util.logging.ILogger 10 | import com.yoavst.jeb.bridge.UIBridge 11 | import com.yoavst.jeb.utils.* 12 | import com.yoavst.jeb.utils.renaming.RenameEngine 13 | import com.yoavst.jeb.utils.renaming.RenameReason 14 | import com.yoavst.jeb.utils.renaming.RenameRequest 15 | 16 | class ConstArgMassRenaming( 17 | private val renamers: Map, 18 | private val isOperatingOnlyOnThisClass: Boolean, 19 | private var classFilter: Regex, 20 | private var recursive: Boolean = true 21 | ) { 22 | private val logger: ILogger = GlobalLog.getLogger(javaClass) 23 | private var effectedMethods: MutableMap = mutableMapOf() 24 | 25 | fun processUnit(unit: IDexUnit, renameEngine: RenameEngine) { 26 | val decompiler = unit.decompilerRef 27 | var seq = getXrefs(unit).asSequence() 28 | seq = if (isOperatingOnlyOnThisClass) { 29 | seq.filter { it.classType == UIBridge.currentClass } 30 | } else { 31 | seq.filter { classFilter.matches(it.classType.implementingClass) } 32 | } 33 | seq.forEach { processMethod(it, unit, decompiler, renameEngine) } 34 | } 35 | 36 | fun propagate(unit: IDexUnit, renameEngine: RenameEngine) { 37 | effectedMethods.forEach { (method, cls) -> 38 | SimpleIdentifierPropagationTraversal(cls, renameEngine).traverse(method.body) 39 | } 40 | 41 | propagateRenameToGetterAndSetters(unit, renameEngine.stats.effectedClasses, renameEngine) 42 | unit.refresh() 43 | } 44 | 45 | fun processMethod(method: IDexMethod, unit: IDexUnit, decompiler: IDexDecompilerUnit, renameEngine: RenameEngine) { 46 | logger.trace("Processing: ${method.currentSignature}") 47 | val decompiledMethod = decompiler.decompileDexMethod(method) ?: run { 48 | logger.warning("Failed to decompile method: ${method.currentSignature}") 49 | return 50 | } 51 | ConstArgRenamingTraversal( 52 | method, 53 | decompiledMethod, 54 | method.classType.implementingClass!!, 55 | unit, 56 | renameEngine 57 | ).traverse(decompiledMethod.body) 58 | } 59 | 60 | private inner class ConstArgRenamingTraversal( 61 | private val method: IDexMethod, 62 | private val javaMethod: IJavaMethod, 63 | private val cls: IDexClass, 64 | private val unit: IDexUnit, 65 | renameEngine: RenameEngine 66 | ) : 67 | BasicAstTraversal(renameEngine) { 68 | override fun traverseNonCompound(statement: IJavaStatement) { 69 | if (statement is IJavaAssignment) { 70 | // Don't crash on: "Object x;" 71 | statement.right?.let { right -> 72 | traverseElement(right, statement.left) 73 | } 74 | } else { 75 | traverseElement(statement, null) 76 | } 77 | } 78 | 79 | private fun traverseElement(element: IJavaElement, assignee: IJavaLeftExpression? = null): Unit = when { 80 | element is IJavaCall && element.methodSignature in renamers -> { 81 | // we found the method we were looking for! 82 | try { 83 | processMatchedMethod(assignee, renamers[element.methodSignature]!!, element::getRealArgument) 84 | } catch (e: Exception) { 85 | logger.error("Failed for ${element.methodSignature}") 86 | throw e 87 | } 88 | } 89 | element is IJavaNew && element.constructorSignature in renamers -> { 90 | // the method we were looking for was a constructor 91 | processMatchedMethod(assignee, renamers[element.constructorSignature]!!) { element.arguments[it] } 92 | } 93 | recursive -> { 94 | // Try sub elements 95 | element.subElements.forEach { traverseElement(it, assignee) } 96 | } 97 | else -> { 98 | } 99 | } 100 | 101 | private inline fun processMatchedMethod(assignee: IJavaLeftExpression?, match: ExtendedRenamer, getArg: (Int) -> IJavaElement) { 102 | var result: RenameResult? = null 103 | if (match.constArgIndex < 0) { 104 | // case of method match unrelated to args 105 | result = match("") 106 | } else { 107 | val nameArg = getArg(match.constArgIndex) 108 | if (nameArg is IJavaConstant && nameArg.isString) { 109 | result = match(nameArg.string) 110 | } 111 | } 112 | 113 | result?.let { res -> 114 | if (!res.className.isNullOrEmpty()) { 115 | renameEngine.renameClass( 116 | RenameRequest( 117 | res.className, 118 | RenameReason.MethodStringArgument 119 | ), cls 120 | ) 121 | } 122 | if (!res.methodName.isNullOrEmpty()) { 123 | renameEngine.renameMethod( 124 | RenameRequest( 125 | res.methodName, 126 | RenameReason.MethodStringArgument 127 | ), method, cls 128 | ) 129 | } 130 | if (!res.argumentName.isNullOrEmpty()) { 131 | if (match.renamedArgumentIndex == null) { 132 | throw IllegalArgumentException("Forget to put renamed argument index") 133 | } 134 | renameElement(getArg(match.renamedArgumentIndex), res.argumentName) 135 | } 136 | if (!res.assigneeName.isNullOrEmpty() && assignee != null) { 137 | renameElement(assignee, res.assigneeName) 138 | } 139 | } 140 | } 141 | 142 | private fun renameElement(element: IJavaElement, name: String) { 143 | val request = RenameRequest(name, RenameReason.MethodStringArgument) 144 | when (element) { 145 | is IJavaDefinition -> renameElement(element.identifier, name) 146 | is IJavaStaticField -> { 147 | val field = element.field ?: run { 148 | logger.warning("Failed to get field: $element") 149 | return 150 | } 151 | renameEngine.renameField(request, field, cls) 152 | } 153 | is IJavaInstanceField -> { 154 | val field = element.field ?: run { 155 | logger.warning("Failed to get field: $element") 156 | return 157 | } 158 | renameEngine.renameField(request, field, cls) 159 | } 160 | is IJavaIdentifier -> { 161 | renameEngine.renameIdentifier(request, element, unit) 162 | } 163 | is IJavaCall -> { 164 | // maybe toString or valueOf on argument 165 | if (element.methodName == "toString" || element.methodName == "valueOf") { 166 | renameElement(element.getArgument(0), name) 167 | } else { 168 | logger.debug("Unsupported call - not toString or valueOf: $element") 169 | return 170 | } 171 | } 172 | else -> { 173 | logger.debug("Unsupported argument type: ${element.elementType}") 174 | return 175 | } 176 | } 177 | effectedMethods[javaMethod] = cls 178 | } 179 | } 180 | 181 | /** 182 | * We are going to do very simple "Identifier propagation", to support the case of: 183 | this.a = anIdentifierIRecovered 184 | */ 185 | private inner class SimpleIdentifierPropagationTraversal(private val cls: IDexClass, renameEngine: RenameEngine) : 186 | BasicAstTraversal(renameEngine) { 187 | override fun traverseNonCompound(statement: IJavaStatement) { 188 | if (statement is IJavaAssignment) { 189 | val left = statement.left 190 | val right = statement.right 191 | 192 | if (right is IJavaIdentifier) { 193 | val renameRequest = renameEngine.stats.renamedIdentifiers[right] ?: return 194 | if (left is IJavaInstanceField) { 195 | val field = left.field ?: run { 196 | logger.warning("Failed to get field: $left") 197 | return 198 | } 199 | renameEngine.renameField( 200 | RenameRequest( 201 | renameRequest.newName, 202 | RenameReason.MethodStringArgument 203 | ), field, cls 204 | ) 205 | } else if (left is IJavaStaticField) { 206 | val field = left.field ?: run { 207 | logger.warning("Failed to get field: $left") 208 | return 209 | } 210 | renameEngine.renameField( 211 | RenameRequest( 212 | renameRequest.newName, 213 | RenameReason.MethodStringArgument 214 | ), field, cls 215 | ) 216 | } 217 | } 218 | } 219 | } 220 | 221 | } 222 | 223 | private fun getXrefs(unit: IDexUnit): Set = renamers.keys.asSequence().mapNotNull(unit::getMethod).onEach { 224 | logger.info("Found method: ${it.currentSignature}") 225 | }.mapToPair(unit::xrefsFor).onEach { (method, xrefs) -> 226 | if (xrefs.isNotEmpty()) 227 | logger.info("Found ${xrefs.size} xrefs for method: ${method.currentSignature}") 228 | }.flatMap { it.second }.mapTo(mutableSetOf(), unit::getMethod) 229 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/constarg/ConstArgMassRenamingPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.constarg 2 | 3 | import com.pnfsoftware.jeb.core.* 4 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 5 | import com.yoavst.jeb.bridge.UIBridge 6 | import com.yoavst.jeb.plugins.JEB_VERSION 7 | import com.yoavst.jeb.plugins.PLUGIN_VERSION 8 | import com.yoavst.jeb.utils.BasicEnginesPlugin 9 | import com.yoavst.jeb.utils.decompilerRef 10 | import com.yoavst.jeb.utils.displayFileOpenSelector 11 | import com.yoavst.jeb.utils.renaming.RenameEngine 12 | import java.io.File 13 | 14 | class ConstArgMassRenamingPlugin : 15 | BasicEnginesPlugin( 16 | supportsClassFilter = true, 17 | defaultForScopeOnThisClass = false, 18 | defaultForScopeOnThisFunction = false, 19 | ) { 20 | private lateinit var signatures: Map 21 | override fun getPluginInformation(): IPluginInformation = PluginInformation( 22 | "Const arg mass renaming plugin", 23 | "Fire the plugin to change names using information from a constant string argument to function", 24 | "Yoav Sternberg", 25 | PLUGIN_VERSION, 26 | JEB_VERSION, 27 | null 28 | ) 29 | 30 | override fun getExecutionOptionDefinitions(): List { 31 | return super.getExecutionOptionDefinitions() + BooleanOptionDefinition( 32 | USE_BUILTIN, 33 | true, 34 | """Use the builtin method signature list. It supports Bundle, Intent, ContentValues and shared preferences. 35 | If you have a suggestion to add to the global list, Please contact Yoav Sternberg.""" 36 | ) 37 | } 38 | 39 | override fun processOptions(executionOptions: Map): Boolean { 40 | super.processOptions(executionOptions) 41 | 42 | if (!executionOptions.getOrDefault(USE_BUILTIN, "true").toBoolean()) { 43 | val scriptPath = File(displayFileOpenSelector("Signatures file") ?: run { 44 | logger.error("No script selected") 45 | return false 46 | }) 47 | 48 | try { 49 | signatures = RenameSignaturesFileParser.parseSignatures(scriptPath.readText(), scriptPath.absoluteFile.parent) 50 | } catch (e: RuntimeException) { 51 | logger.catching(e, "Failed to parse signature file") 52 | return false 53 | } 54 | } else { 55 | signatures = 56 | RenameSignaturesFileParser.parseSignatures(javaClass.classLoader.getResource("rename_signatures.md")!!.readText(), ".") 57 | } 58 | return true 59 | } 60 | 61 | override fun processUnit(unit: IDexUnit, renameEngine: RenameEngine) { 62 | val massRenamer = ConstArgMassRenaming( 63 | signatures, isOperatingOnlyOnThisClass, classFilter 64 | ) 65 | 66 | if (isOperatingOnlyOnThisMethod) { 67 | if (UIBridge.currentMethod != null && UIBridge.currentClass != null) { 68 | // you cannot see the sources of a type without implementing class 69 | massRenamer.processMethod(UIBridge.currentMethod!!, unit, unit.decompilerRef, renameEngine) 70 | } 71 | } else { 72 | massRenamer.processUnit(unit, renameEngine) 73 | } 74 | 75 | massRenamer.propagate(unit, renameEngine) 76 | } 77 | 78 | companion object { 79 | private const val USE_BUILTIN = "use builtin" 80 | } 81 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/constarg/ConstArgRenamingPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.constarg 2 | 3 | import com.pnfsoftware.jeb.core.* 4 | import com.pnfsoftware.jeb.core.units.NotificationType 5 | import com.pnfsoftware.jeb.core.units.UnitNotification 6 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 7 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexMethod 8 | import com.yoavst.jeb.bridge.UIBridge 9 | import com.yoavst.jeb.plugins.JEB_VERSION 10 | import com.yoavst.jeb.plugins.PLUGIN_VERSION 11 | import com.yoavst.jeb.utils.BasicEnginesPlugin 12 | import com.yoavst.jeb.utils.decompilerRef 13 | import com.yoavst.jeb.utils.displayFileOpenSelector 14 | import com.yoavst.jeb.utils.originalSignature 15 | import com.yoavst.jeb.utils.renaming.RenameEngine 16 | import java.io.File 17 | import kotlin.properties.Delegates 18 | 19 | class ConstArgRenamingPlugin : 20 | BasicEnginesPlugin( 21 | supportsClassFilter = true, 22 | defaultForScopeOnThisClass = false, 23 | defaultForScopeOnThisFunction = false, 24 | usingSelectedMethod = true 25 | ) { 26 | 27 | private var constArgumentIndex by Delegates.notNull() 28 | private var renamedArgumentIndex = -1 29 | private lateinit var renameMethod: IDexMethod 30 | private lateinit var renamer: (String) -> RenameResult 31 | 32 | override fun getPluginInformation(): IPluginInformation = PluginInformation( 33 | "Const arg renaming plugin", 34 | "Fire the plugin to change names using information from a constant string argument to function", 35 | "Yoav Sternberg", 36 | PLUGIN_VERSION, 37 | JEB_VERSION, 38 | null 39 | ) 40 | 41 | override fun getExecutionOptionDefinitions(): List { 42 | val options = super.getExecutionOptionDefinitions().toMutableList() 43 | options += ListOptionDefinition( 44 | TargetRenameTag, 45 | RenameTarget.Class.name, 46 | "What do we want to renamed based on the argument", 47 | *RenameTarget.values().map(RenameTarget::name).toTypedArray() 48 | ) 49 | options += OptionDefinition( 50 | TargetConstArgIndex, 51 | "0", 52 | "What is the index of the const argument that will be used as name?" 53 | ) 54 | options += OptionDefinition( 55 | TargetArgumentToBeRenamedPosition, 56 | "Optional: If renaming an argument, what is its index" 57 | ) 58 | return options 59 | } 60 | 61 | override fun processOptions(executionOptions: Map): Boolean { 62 | super.processOptions(executionOptions) 63 | val renamedMethodTemp = UIBridge.focusedMethod 64 | if (renamedMethodTemp == null) { 65 | logger.error("A method must be focus for this plugin to work") 66 | return false 67 | } 68 | renameMethod = renamedMethodTemp 69 | 70 | constArgumentIndex = executionOptions[TargetConstArgIndex]?.toIntOrNull() ?: run { 71 | logger.error("Index of the const argument must be a number") 72 | return false 73 | } 74 | if (constArgumentIndex < 0 || renameMethod.parameterTypes.size <= constArgumentIndex) { 75 | logger.error("The const argument index is out of range: [0, ${renameMethod.parameterTypes.size})") 76 | return false 77 | } 78 | val paramType = renameMethod.parameterTypes[constArgumentIndex].originalSignature 79 | if (paramType != "Ljava/lang/CharSequence;" && paramType != "Ljava/lang/String;") { 80 | logger.error("The argument type is not a string type. Received: $paramType") 81 | return false 82 | } 83 | 84 | val targetRenameStr = executionOptions[TargetRenameTag] ?: "" 85 | if (targetRenameStr.isBlank()) { 86 | logger.error("Target for renaming is not provided") 87 | return false 88 | } 89 | // safe because it comes from list 90 | renamer = when (RenameTarget.valueOf(targetRenameStr)) { 91 | RenameTarget.Custom -> { 92 | val tmp = executionOptions[TargetArgumentToBeRenamedPosition]?.toIntOrNull() 93 | if (tmp != null) 94 | renamedArgumentIndex = tmp 95 | 96 | val scriptPath = displayFileOpenSelector("Renaming script") ?: run { 97 | logger.error("No script selected") 98 | return false 99 | } 100 | scriptRenamer(File(scriptPath).readText()) 101 | } 102 | RenameTarget.Class -> classRenamer 103 | RenameTarget.Method -> methodRenamer 104 | RenameTarget.Argument -> { 105 | renamedArgumentIndex = executionOptions[TargetArgumentToBeRenamedPosition]?.toIntOrNull() ?: run { 106 | logger.error("Index of the renamed argument be a number") 107 | return false 108 | } 109 | if (constArgumentIndex < 0 || renameMethod.parameterTypes.size <= constArgumentIndex) { 110 | logger.error("The renamed argument index is out of range: [0, ${renameMethod.parameterTypes.size})") 111 | return false 112 | } 113 | argumentRenamer 114 | } 115 | RenameTarget.Assignee -> assigneeRenamer 116 | } 117 | return true 118 | } 119 | 120 | override fun processUnit(unit: IDexUnit, renameEngine: RenameEngine) { 121 | val massRenamer = ConstArgMassRenaming( 122 | mapOf( 123 | renameMethod.getSignature(false) to ExtendedRenamer(constArgumentIndex, renamer, renamedArgumentIndex) 124 | ), isOperatingOnlyOnThisClass, classFilter 125 | ) 126 | 127 | if (isOperatingOnlyOnThisMethod) { 128 | if (UIBridge.currentMethod != null && UIBridge.currentClass != null) { 129 | // you cannot see the sources of a type without implementing class 130 | massRenamer.processMethod(UIBridge.currentMethod!!, unit, unit.decompilerRef, renameEngine) 131 | } 132 | } else { 133 | massRenamer.processUnit(unit, renameEngine) 134 | } 135 | 136 | massRenamer.propagate(unit, renameEngine) 137 | } 138 | 139 | companion object { 140 | private const val TargetRenameTag = "Target rename" 141 | private const val TargetConstArgIndex = "Const arg index" 142 | private const val TargetArgumentToBeRenamedPosition = "renamed arg index" 143 | } 144 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/constarg/ExtendedRenamer.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.constarg 2 | 3 | class ExtendedRenamer(val constArgIndex: Int, func: (String) -> RenameResult, val renamedArgumentIndex: Int? = null) : 4 | (String) -> RenameResult by func -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/constarg/GetXPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.constarg 2 | 3 | import com.pnfsoftware.jeb.core.IPluginInformation 4 | import com.pnfsoftware.jeb.core.PluginInformation 5 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 6 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexMethod 7 | import com.yoavst.jeb.plugins.JEB_VERSION 8 | import com.yoavst.jeb.plugins.PLUGIN_VERSION 9 | import com.yoavst.jeb.utils.* 10 | import com.yoavst.jeb.utils.renaming.RenameEngine 11 | import java.util.* 12 | 13 | class GetXPlugin : BasicEnginesPlugin(supportsClassFilter = true, defaultForScopeOnThisClass = false) { 14 | override fun getPluginInformation(): IPluginInformation = PluginInformation( 15 | "GetX plugin", 16 | "Fire the plugin to scan the apk for getX methods and use that for naming", 17 | "Yoav Sternberg", 18 | PLUGIN_VERSION, 19 | JEB_VERSION, 20 | null 21 | ) 22 | 23 | override fun processUnit(unit: IDexUnit, renameEngine: RenameEngine) { 24 | val visitor = simpleNameMethodVisitor(renameEngine) 25 | val renamers = unit.methods.asSequence().mapToPairNotNull(visitor).associate { (method, result) -> 26 | method.currentSignature to result 27 | } 28 | ConstArgMassRenaming(renamers, isOperatingOnlyOnThisClass, classFilter, recursive = false).processUnit(unit, renameEngine) 29 | } 30 | 31 | 32 | private fun simpleNameMethodVisitor(renameEngine: RenameEngine): (IDexMethod) -> ExtendedRenamer? = { method -> 33 | val currentName = method.currentName 34 | val name = renameEngine.getModifiedInfo(currentName)?.let { (realName, _) -> realName } ?: currentName 35 | if (name.startsWith("get") && name.length >= 4) { 36 | val fieldName = name.substring(3).replaceFirstChar { it.lowercase(Locale.getDefault()) } 37 | when { 38 | method.parameterTypes.size == 0 -> { 39 | ExtendedRenamer(-1, { RenameResult(assigneeName = fieldName) }, -1) 40 | } 41 | method.isStatic && method.parameterTypes.size == 1 -> { 42 | ExtendedRenamer(-1, { RenameResult(argumentName = fieldName) }, 0) 43 | } 44 | else -> null 45 | } 46 | } else if (name.startsWith("set") && name.length >= 4) { 47 | val fieldName = name.substring(3).replaceFirstChar { it.lowercase(Locale.getDefault()) } 48 | when { 49 | method.parameterTypes.size == 1 -> { 50 | ExtendedRenamer(-1, { RenameResult(argumentName = fieldName) }, 0) 51 | } 52 | method.isStatic && method.parameterTypes.size == 2 -> { 53 | ExtendedRenamer(-1, { RenameResult(argumentName = fieldName) }, 1) 54 | } 55 | else -> null 56 | } 57 | } else null 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/constarg/RenameResult.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.constarg 2 | 3 | data class RenameResult( 4 | val className: String? = null, 5 | val methodName: String? = null, 6 | val argumentName: String? = null, 7 | val assigneeName: String? = null 8 | ) 9 | -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/constarg/RenameSignaturesFileParser.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.constarg 2 | 3 | import java.io.File 4 | import java.util.* 5 | 6 | /** 7 | * File format is very simple 8 | * every line is of the form: 9 | * TARGET SIGNATURE CONST_ARG_INDEX [custom script path] [renamed argument index] 10 | * where target in {Class, Method, Argument, Assignee, Custom} 11 | * signature is a dex method signature. for example: "Lcom/yoavst/test/TestClass;->doStuff(Ljava/lang/String;)Ljava/lang/String;" 12 | * 13 | * you can use # for comments 14 | */ 15 | object RenameSignaturesFileParser { 16 | fun parseSignatures(signatures: String, basePath: String): Map = signatures.lineSequence().map { 17 | it.substringBefore("#").trim() 18 | }.filter(String::isNotEmpty).associate { 19 | val split = it.split(" ", limit = 5) 20 | if (split.size == 1) { 21 | throw IllegalArgumentException("Invalid line: '$it'") 22 | } 23 | val constArgIndex = split[2].toIntOrNull() ?: throw IllegalArgumentException("Invalid line: '$it'") 24 | val target = RenameTarget.valueOf( 25 | split[0].lowercase(Locale.getDefault()) 26 | .replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }) 27 | split[1] to when (target) { 28 | RenameTarget.Class -> ExtendedRenamer(constArgIndex, classRenamer) 29 | RenameTarget.Method -> ExtendedRenamer(constArgIndex, methodRenamer) 30 | RenameTarget.Argument -> { 31 | if (split.size != 4) { 32 | throw IllegalArgumentException("Invalid line, no renamed argument index: '$it'") 33 | } 34 | val renamedArgIndex = 35 | split[3].toIntOrNull() ?: throw IllegalArgumentException("Invalid line, renamed argument is not int: '$it'") 36 | ExtendedRenamer(constArgIndex, argumentRenamer, renamedArgIndex) 37 | } 38 | RenameTarget.Assignee -> ExtendedRenamer(constArgIndex, assigneeRenamer) 39 | RenameTarget.Custom -> { 40 | if (split.size < 4) { 41 | throw IllegalArgumentException("Invalid line, no custom path: '$it'") 42 | } 43 | val filename = split[3] 44 | val script = if (filename.startsWith("jar:")) { 45 | javaClass.classLoader.getResourceAsStream(filename.substringAfter("jar:"))?.bufferedReader()?.readText() ?: run { 46 | throw IllegalArgumentException("Invalid line, no such file in jar: '$it'") 47 | } 48 | } else { 49 | File(basePath, split[3]).readText() 50 | } 51 | if (split.size == 5) { 52 | val renamedArgIndex = 53 | split[4].toIntOrNull() ?: throw IllegalArgumentException("Invalid line, renamed argument is not int: '$it'") 54 | ExtendedRenamer(constArgIndex, scriptRenamer(script), renamedArgIndex) 55 | } else { 56 | ExtendedRenamer(constArgIndex, scriptRenamer(script)) 57 | } 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/constarg/RenameTarget.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.constarg 2 | 3 | enum class RenameTarget { 4 | Class, Method, Argument, Assignee, Custom 5 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/constarg/Renamers.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.constarg 2 | 3 | import org.python.util.PythonInterpreter 4 | 5 | private const val CLASS = "cls" 6 | private const val METHOD = "method" 7 | private const val ARGUMENT = "argument" 8 | private const val ASSIGNEE = "assignee" 9 | private const val INPUT = "tag" 10 | 11 | val classRenamer: (String) -> RenameResult = { RenameResult(className = it) } 12 | val methodRenamer: (String) -> RenameResult = { RenameResult(methodName = it) } 13 | val argumentRenamer: (String) -> RenameResult = { RenameResult(argumentName = it) } 14 | val assigneeRenamer: (String) -> RenameResult = { RenameResult(assigneeName = it) } 15 | fun scriptRenamer(script: String): (String) -> RenameResult { 16 | val baseInterpreter = PythonInterpreter().apply { 17 | exec( 18 | """ 19 | def split2(s, sep, at_least): 20 | '''Split the string using separator. Result array will be at least the given length.''' 21 | arr = s.split(sep) 22 | return arr + [""]*(at_least-len(arr)) 23 | 24 | def underscore_to_camelcase(word): 25 | return ''.join(x.capitalize() or '_' for x in word.split('_')) 26 | 27 | """.trimIndent() 28 | ) 29 | } 30 | return { tag -> 31 | baseInterpreter[CLASS] = null 32 | baseInterpreter[METHOD] = null 33 | baseInterpreter[ARGUMENT] = null 34 | baseInterpreter[ASSIGNEE] = null 35 | baseInterpreter[INPUT] = tag 36 | baseInterpreter.exec(script) 37 | RenameResult( 38 | baseInterpreter[CLASS]?.asStringOrNull(), 39 | baseInterpreter[METHOD]?.asStringOrNull(), 40 | baseInterpreter[ARGUMENT]?.asStringOrNull(), 41 | baseInterpreter[ASSIGNEE]?.asStringOrNull() 42 | ) 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/consts.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins 2 | 3 | import com.pnfsoftware.jeb.core.Version 4 | 5 | val PLUGIN_VERSION: Version = Version.create(0, 4, 3) 6 | val JEB_VERSION: Version = Version.create(3, 0, 16) -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/enumsupport/BaseDalvikSimulator.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.enumsupport 2 | 3 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 4 | import com.pnfsoftware.jeb.core.units.code.android.dex.* 5 | import com.pnfsoftware.jeb.util.logging.GlobalLog 6 | import com.pnfsoftware.jeb.util.logging.ILogger 7 | import com.yoavst.jeb.utils.renaming.RenameEngine 8 | 9 | abstract class BaseDalvikSimulator(protected val clazz: IDexClass, protected val renameEngine: RenameEngine) { 10 | protected val logger: ILogger = GlobalLog.getLogger(javaClass) 11 | 12 | protected val mapping: MutableMap = mutableMapOf() 13 | protected val unit: IDexUnit = clazz.dex 14 | protected val classIndex: Int = clazz.classTypeIndex 15 | protected val className: String = clazz.name 16 | 17 | open fun run(method: IDexMethod) { 18 | @Suppress("UNCHECKED_CAST") 19 | (method.instructions as List).forEach(::onInstruction) 20 | } 21 | 22 | protected open fun onInstruction(instruction: IDalvikInstruction) { 23 | when (instruction.opcode) { 24 | DalvikInstructionOpcodes.OP_CONST_STRING, DalvikInstructionOpcodes.OP_CONST_STRING_JUMBO -> 25 | onConstString(instruction) 26 | 27 | DalvikInstructionOpcodes.OP_INVOKE_DIRECT, DalvikInstructionOpcodes.OP_INVOKE_DIRECT_JUMBO -> 28 | onInvokeDirect(instruction) 29 | 30 | DalvikInstructionOpcodes.OP_INVOKE_DIRECT_RANGE -> 31 | onInvokeDirectRange(instruction) 32 | 33 | DalvikInstructionOpcodes.OP_SPUT_OBJECT, DalvikInstructionOpcodes.OP_SPUT_OBJECT_JUMBO -> 34 | onStaticPutObject(instruction) 35 | 36 | DalvikInstructionOpcodes.OP_MOVE_OBJECT, DalvikInstructionOpcodes.OP_MOVE_OBJECT_16, DalvikInstructionOpcodes.OP_MOVE_OBJECT_FROM_16 -> 37 | onMoveObject(instruction) 38 | 39 | DalvikInstructionOpcodes.OP_NEW_INSTANCE, DalvikInstructionOpcodes.OP_NEW_INSTANCE_JUMBO -> 40 | onNewInstance(instruction) 41 | } 42 | } 43 | 44 | //region Handlers 45 | protected open fun onConstString(instruction: IDalvikInstruction) { 46 | val register = instruction[0] 47 | val str = getString(instruction[1]) 48 | 49 | mapping[register] = RegisterValue.StringValue(str) 50 | 51 | trace { "Mapping $register to $str, $instruction" } 52 | } 53 | 54 | protected open fun onMoveObject(instruction: IDalvikInstruction) { 55 | val (to, from) = instruction 56 | if (from in mapping) { 57 | mapping[to] = mapping[from]!! 58 | 59 | trace { "Moving from $from to $to" } 60 | 61 | } else { 62 | // Clear to in this case, as we don't want it to cache old value 63 | mapping.remove(to) 64 | 65 | trace { "Moving from $from to $to: invalidating" } 66 | } 67 | } 68 | 69 | protected abstract fun onInvokeDirect(instruction: IDalvikInstruction) 70 | protected abstract fun onInvokeDirectRange(instruction: IDalvikInstruction) 71 | protected abstract fun onStaticPutObject(instruction: IDalvikInstruction) 72 | protected abstract fun onNewInstance(instruction: IDalvikInstruction) 73 | //endregion 74 | 75 | //region Utils 76 | protected operator fun IDalvikInstruction.get(index: Int): Long = getParameter(index).value 77 | protected operator fun IDalvikInstruction.component1(): Long = this[0] 78 | protected operator fun IDalvikInstruction.component2(): Long = this[1] 79 | protected operator fun IDalvikInstruction.component3(): Long = this[2] 80 | 81 | protected fun getType(index: Long): IDexType = unit.getType(index.toInt()) 82 | protected fun getString(index: Long): String = unit.getString(index.toInt()).value 83 | protected fun getMethod(index: Long): IDexMethod = unit.getMethod(index.toInt()) 84 | 85 | 86 | protected inline fun trace(@Suppress("UNUSED_PARAMETER") state: () -> String) { 87 | // logger.trace("$className ${state()}") 88 | } 89 | 90 | protected inline fun warn(state: () -> String) { 91 | logger.warning("$className ${state()}") 92 | } 93 | 94 | protected fun IDexType.isSubClass(): Boolean = 95 | implementingClass?.supertypes?.any { it.index == classIndex } == true 96 | //endregion 97 | } 98 | 99 | sealed interface RegisterValue { 100 | data class StringValue(val value: String) : RegisterValue 101 | data class EnumInstanceValue(var value: String? = null) : RegisterValue 102 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/enumsupport/EnumRenamingPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.enumsupport 2 | 3 | import com.pnfsoftware.jeb.core.IPluginInformation 4 | import com.pnfsoftware.jeb.core.PluginInformation 5 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 6 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexClass 7 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexMethod 8 | import com.yoavst.jeb.bridge.UIBridge 9 | import com.yoavst.jeb.plugins.JEB_VERSION 10 | import com.yoavst.jeb.plugins.PLUGIN_VERSION 11 | import com.yoavst.jeb.utils.* 12 | import com.yoavst.jeb.utils.renaming.RenameEngine 13 | import com.yoavst.jeb.utils.renaming.RenameReason 14 | import com.yoavst.jeb.utils.renaming.RenameRequest 15 | 16 | /** 17 | * Refactor assignments of the given forms: 18 | ``` 19 | // 1. 20 | cls.a = new EnumCls("LIST_TYPE", ...) 21 | // 2. 22 | var temp = new EnumCls("LIST_TYPE", ...) 23 | cls.a = temp 24 | ``` 25 | */ 26 | class EnumRenamingPlugin : BasicEnginesPlugin(supportsClassFilter = true, defaultForScopeOnThisClass = false) { 27 | override fun getPluginInformation(): IPluginInformation = PluginInformation( 28 | "Enum fields renaming", 29 | "Fire the plugin to change obfuscated enum field names to their real name if available", 30 | "Yoav Sternberg", 31 | PLUGIN_VERSION, 32 | JEB_VERSION, 33 | null 34 | ) 35 | 36 | override fun processUnit(unit: IDexUnit, renameEngine: RenameEngine) { 37 | if (isOperatingOnlyOnThisClass) { 38 | val cls = UIBridge.focusedClass?.implementingClass ?: return 39 | processClass(cls, renameEngine) 40 | } else { 41 | unit.subclassesOf("Ljava/lang/Enum;").filter(classFilter::matches) 42 | .forEach { processClass(it, renameEngine) } 43 | } 44 | unit.refresh() 45 | } 46 | 47 | 48 | private fun processClass(cls: IDexClass, renameEngine: RenameEngine) { 49 | logger.trace("Processing enum: $cls") 50 | val staticConstructor = cls.methods.firstOrNull { it.originalName == "" } ?: run { 51 | logger.info("Enum without static initializer: ${cls.name}") 52 | return 53 | } 54 | val constructors = cls.methods.filter { it.originalName == "" } 55 | 56 | if (constructors.isEmpty()) { 57 | logger.info("Enum without constructor: ${cls.name}") 58 | return 59 | } 60 | 61 | 62 | if (constructors.size == 1 && constructors[0].parameterTypes.size == 0) { 63 | // Enum with an empty constructor, Therefore it has at most one instance 64 | processSingletonEnum(cls, constructors[0], renameEngine) 65 | } else { 66 | if (constructors.any { it.parameterTypes.size == 0 || it.parameterTypes[0].signature != "Ljava/lang/String;" }) { 67 | if (constructors.size == 1) { 68 | // Assume it is a singleton enum 69 | processSingletonEnum(cls, constructors[0], renameEngine) 70 | } else { 71 | logger.warning("Normal Enum with constructor receiving non string type at first index: ${cls.name}") 72 | } 73 | } else { 74 | // Normal enum 75 | MultiEnumStaticConstructorSimulator(cls, constructors, renameEngine).run(staticConstructor) 76 | } 77 | } 78 | 79 | renameEngine.renameClass(RenameRequest("Enum", RenameReason.Type, informationalRename = true), cls) 80 | } 81 | 82 | fun processSingletonEnum(clazz: IDexClass, constructor: IDexMethod, renameEngine: RenameEngine) { 83 | val superMethod = constructor.dex.getMethod("Ljava/lang/Enum;->(Ljava/lang/String;I)V") 84 | 85 | val simulator = SingleEnumConstructorSimulator(clazz, setOf(superMethod.prototypeIndex), renameEngine) 86 | simulator.run(constructor) 87 | 88 | if (simulator.name != null) { 89 | // Find enum field 90 | val matchingFields = clazz.fields.filter { it.fieldTypeIndex == clazz.classTypeIndex } 91 | if (matchingFields.size != 1) { 92 | logger.warning("Found multiple matching field in ${clazz.name}. Cannot rename to ${simulator.name}") 93 | } else { 94 | renameEngine.renameField(RenameRequest(simulator.name!!, RenameReason.EnumName), matchingFields[0], clazz) 95 | } 96 | } else { 97 | logger.info("Could not rename singleton enum: ${clazz.name}") 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/enumsupport/MultiEnumStaticConstructorSimulator.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.enumsupport 2 | 3 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDalvikInstruction 4 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexClass 5 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexField 6 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexMethod 7 | import com.yoavst.jeb.utils.renaming.RenameEngine 8 | import com.yoavst.jeb.utils.renaming.RenameReason 9 | import com.yoavst.jeb.utils.renaming.RenameRequest 10 | 11 | class MultiEnumStaticConstructorSimulator( 12 | clazz: IDexClass, 13 | constructors: List, 14 | renameEngine: RenameEngine 15 | ): BaseDalvikSimulator(clazz, renameEngine) { 16 | private val constructorIndices = constructors.mapTo(mutableSetOf()) { it.prototypeIndex } 17 | 18 | override fun onInvokeDirect(instruction: IDalvikInstruction) { 19 | val invokedMethod = getMethod(instruction[0]) 20 | if (invokedMethod.prototypeIndex in constructorIndices) { 21 | onInvokeGeneralized(register = instruction[1], strRegister = instruction[2], instruction) 22 | } else if (invokedMethod.name == "" && invokedMethod.classType.isSubClass()) { 23 | val parameterTypes = invokedMethod.parameterTypes 24 | if (parameterTypes.size != 0 && parameterTypes[0].signature == "Ljava/lang/String;") { 25 | // Assume it receives the enum name as first parameter 26 | onInvokeGeneralized(register = instruction[1], strRegister = instruction[2], instruction) 27 | } else { 28 | // the enum constructor was moved to a subclass 29 | val singleEnumConstructorSimulator = SingleEnumConstructorSimulator( 30 | invokedMethod.classType.implementingClass!!, 31 | constructorIndices, 32 | renameEngine 33 | ) 34 | singleEnumConstructorSimulator.run(invokedMethod) 35 | if (singleEnumConstructorSimulator.name != null) { 36 | onInvokeGeneralized(instruction[1], singleEnumConstructorSimulator.name!!, instruction) 37 | } 38 | } 39 | } 40 | } 41 | 42 | override fun onInvokeDirectRange(instruction: IDalvikInstruction) { 43 | val invokedMethod = getMethod(instruction[0]) 44 | if (invokedMethod.prototypeIndex in constructorIndices || (invokedMethod.name == "" && invokedMethod.classType.isSubClass() && invokedMethod.parameterTypes.size >= 2)) { 45 | val register = instruction[1] and 0xffffffff 46 | val strRegister = register + 1 47 | onInvokeGeneralized(register, strRegister, instruction) 48 | } 49 | } 50 | 51 | private fun onInvokeGeneralized(register: Long, strRegister: Long, instruction: IDalvikInstruction) { 52 | val matchingStr = (mapping[strRegister] as? RegisterValue.StringValue)?.value 53 | if (matchingStr == null) { 54 | warn { "Found init method, but the string wasn't in the mapping" } 55 | return 56 | } 57 | 58 | onInvokeGeneralized(register, matchingStr, instruction) 59 | } 60 | 61 | private fun onInvokeGeneralized(register: Long, matchingStr: String, instruction: IDalvikInstruction) { 62 | val instance = (mapping[register] as? RegisterValue.EnumInstanceValue) ?: run { 63 | warn { "Found init method on non enum instance" } 64 | return 65 | } 66 | 67 | if (instance.value != null) { 68 | warn { "Trying to init already initialized value" } 69 | return 70 | } 71 | instance.value = matchingStr 72 | 73 | trace { "Mapping $register to Enum($matchingStr), $instruction" } 74 | } 75 | 76 | 77 | override fun onStaticPutObject(instruction: IDalvikInstruction) { 78 | val (lhs, rhs) = instruction.parameters 79 | if (lhs.type == IDalvikInstruction.TYPE_REG && rhs.type == IDalvikInstruction.TYPE_IDX) { 80 | // Assign from reg to field 81 | val register = lhs.value 82 | val field = unit.getField(rhs.value.toInt()) 83 | 84 | if ((field.genericFlags and IDexField.FLAG_STATIC) != 0 && field.classTypeIndex == classIndex && field.fieldTypeIndex == classIndex) { 85 | // Static field of the class, we want to rename 86 | when (val value = mapping[register]) { 87 | is RegisterValue.EnumInstanceValue -> { 88 | if (value.value == null) { 89 | warn { "Assigning uninit instance to static field" } 90 | return 91 | } 92 | trace { "Renaming $field to ${value.value}" } 93 | renameEngine.renameField(RenameRequest(value.value!!, RenameReason.EnumName), field, clazz) 94 | } 95 | 96 | is RegisterValue.StringValue, null -> { 97 | warn { "Found a static assignment but without matching enum assignment" } 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | override fun onNewInstance(instruction: IDalvikInstruction) { 105 | val (reg, typeIndex) = instruction 106 | if (typeIndex.toInt() == classIndex) { 107 | mapping[reg] = RegisterValue.EnumInstanceValue(null) 108 | 109 | trace { "Allocating new instance on $reg" } 110 | } else { 111 | // Maybe it is a subclass 112 | val clazz = getType(typeIndex) 113 | if (clazz.isSubClass()) { 114 | mapping[reg] = RegisterValue.EnumInstanceValue(null) 115 | 116 | trace { "Allocating new subclass instance on $reg" } 117 | } 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/enumsupport/SingleEnumConstructorSimulator.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.enumsupport 2 | 3 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDalvikInstruction 4 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexClass 5 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexMethod 6 | import com.yoavst.jeb.utils.renaming.RenameEngine 7 | 8 | class SingleEnumConstructorSimulator(clazz: IDexClass, private val superInits: Set, renameEngine: RenameEngine) : 9 | BaseDalvikSimulator(clazz, renameEngine) { 10 | var name: String? = null 11 | private var thisInstance = RegisterValue.EnumInstanceValue(null) 12 | 13 | override fun run(method: IDexMethod) { 14 | // Add p0 to mapping 15 | val thisIndex = (method.data.codeItem.registerCount - method.parameterTypes.size - 1).toLong() 16 | mapping[thisIndex] = thisInstance 17 | 18 | super.run(method) 19 | } 20 | 21 | override fun onInvokeDirect(instruction: IDalvikInstruction) { 22 | val invokedMethod = getMethod(instruction[0]) 23 | if (invokedMethod.prototypeIndex in superInits) { 24 | onInvokeGeneralized(register = instruction[1], strRegister = instruction[2], instruction) 25 | } 26 | } 27 | 28 | override fun onInvokeDirectRange(instruction: IDalvikInstruction) { 29 | val invokedMethod = getMethod(instruction[0]) 30 | if (invokedMethod.prototypeIndex in superInits) { 31 | val register = instruction[1] and 0xffffffff 32 | val strRegister = register + 1 33 | onInvokeGeneralized(register, strRegister, instruction) 34 | } 35 | } 36 | 37 | private fun onInvokeGeneralized(register: Long, strRegister: Long, instruction: IDalvikInstruction) { 38 | val matchingStr = (mapping[strRegister] as? RegisterValue.StringValue)?.value 39 | if (matchingStr == null) { 40 | warn { "Found init method, but the string wasn't in the mapping" } 41 | return 42 | } 43 | 44 | val instance = (mapping[register] as? RegisterValue.EnumInstanceValue) ?: run { 45 | warn { "Found init method on non enum instance" } 46 | return 47 | } 48 | 49 | if (instance.value != null) { 50 | warn { "Trying to init already initialized value" } 51 | return 52 | } 53 | instance.value = matchingStr 54 | 55 | trace { "Mapping $register to Enum($matchingStr), $instruction" } 56 | 57 | if (thisInstance.value != null) { 58 | if (name != null) { 59 | warn { "Initialize this class twice, was: '$name' now '${thisInstance.value}'" } 60 | } 61 | name = thisInstance.value 62 | } 63 | } 64 | 65 | override fun onStaticPutObject(instruction: IDalvikInstruction) = Unit 66 | override fun onNewInstance(instruction: IDalvikInstruction) = Unit 67 | 68 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/kotlin/IntrinsicDetectionResult.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.kotlin 2 | 3 | import com.yoavst.jeb.plugins.constarg.ExtendedRenamer 4 | 5 | data class IntrinsicDetectionResult(val name: String, val renamer: ExtendedRenamer? = null) 6 | -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/kotlin/KotlinAnnotationPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.kotlin 2 | 3 | import com.pnfsoftware.jeb.core.IOptionDefinition 4 | import com.pnfsoftware.jeb.core.IPluginInformation 5 | import com.pnfsoftware.jeb.core.OptionDefinition 6 | import com.pnfsoftware.jeb.core.PluginInformation 7 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 8 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexAnnotation 9 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexClass 10 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexValue 11 | import com.yoavst.jeb.bridge.UIBridge 12 | import com.yoavst.jeb.plugins.JEB_VERSION 13 | import com.yoavst.jeb.plugins.PLUGIN_VERSION 14 | import com.yoavst.jeb.utils.* 15 | import com.yoavst.jeb.utils.renaming.RenameEngine 16 | import com.yoavst.jeb.utils.renaming.RenameReason 17 | import com.yoavst.jeb.utils.renaming.RenameRequest 18 | import kotlinx.metadata.jvm.KotlinClassHeader 19 | import kotlinx.metadata.jvm.KotlinClassMetadata 20 | 21 | class KotlinAnnotationPlugin : BasicEnginesPlugin(supportsClassFilter = true, defaultForScopeOnThisClass = false) { 22 | private lateinit var annotationSignature: String 23 | private lateinit var metadataKName: String 24 | private lateinit var metadataBVName: String 25 | private lateinit var metadataMVName: String 26 | private lateinit var metadataD1Name: String 27 | private lateinit var metadataD2Name: String 28 | private lateinit var metadataXSName: String 29 | private lateinit var metadataPNName: String 30 | private lateinit var metadataXIName: String 31 | 32 | private var metadataKIndex: Int = -1 33 | private var metadataBVIndex: Int = -1 34 | private var metadataMVIndex: Int = -1 35 | private var metadataD1Index: Int = -1 36 | private var metadataD2Index: Int = -1 37 | private var metadataXSIndex: Int = -1 38 | private var metadataPNIndex: Int = -1 39 | private var metadataXIIndex: Int = -1 40 | 41 | override fun getPluginInformation(): IPluginInformation = PluginInformation( 42 | "Kotlin metadata processor", 43 | "Fire the plugin to process kotlin metadata to get class names", 44 | "Yoav Sternberg", 45 | PLUGIN_VERSION, 46 | JEB_VERSION, 47 | null 48 | ) 49 | 50 | override fun getExecutionOptionDefinitions(): List { 51 | return super.getExecutionOptionDefinitions() + OptionDefinition( 52 | KOTLIN_METADATA_SIGNATURE, 53 | "Lkotlin/Metadata;", 54 | """The signature of the kotlin metadata annotation. Default is to use the unobfuscated name.""" 55 | ) + OptionDefinition( 56 | KOTLIN_METADATA_KIND, 57 | KOTLIN_METADATA_KIND, 58 | """The name of the kind parameter. Original name is: k. An integer - value is between 1 to 5. 1 for normal classes. """ 59 | ) + OptionDefinition( 60 | KOTLIN_METADATA_BYTECODE_VERSION, 61 | KOTLIN_METADATA_BYTECODE_VERSION, 62 | """The name of the bytecode version parameter. Original name is: bv. An int array - usually {1,0,3}.""" 63 | ) + OptionDefinition( 64 | KOTLIN_METADATA_METADATA_VERSION, 65 | KOTLIN_METADATA_METADATA_VERSION, 66 | """The name of the metadata version parameter. Original name is: mv. An int array - The one that is not the bytecode version.""" 67 | ) + OptionDefinition( 68 | KOTLIN_METADATA_DATA1, 69 | KOTLIN_METADATA_DATA1, 70 | """The name of the data1 parameter. Original name is: d1. A string array - Usually contains one long protobuf binary string.""" 71 | ) + OptionDefinition( 72 | KOTLIN_METADATA_DATA2, 73 | KOTLIN_METADATA_DATA2, 74 | """The name of the data2 parameter. Original name is: d2. A string array - Usually consists of many strings.""" 75 | ) + OptionDefinition( 76 | KOTLIN_METADATA_PACKAGE_NAME, 77 | KOTLIN_METADATA_PACKAGE_NAME, 78 | """The name of the package name parameter (since Kotlin 1.2). Original name is: pn. """ 79 | ) + OptionDefinition( 80 | KOTLIN_METADATA_EXTRA_STRING, 81 | KOTLIN_METADATA_EXTRA_STRING, 82 | """The name of the extra string parameter. Original name is: xs. A String - Usually doesn't have a value, it's the name of the facade class.""" 83 | ) + OptionDefinition( 84 | KOTLIN_METADATA_EXTRA_INT, 85 | KOTLIN_METADATA_EXTRA_INT, 86 | """The name of the extra int parameter (since Kotlin 1.1). Original name is: xi. An int - Value is between 0 to 6, usually not available""" 87 | ) 88 | } 89 | 90 | override fun processOptions(executionOptions: Map): Boolean { 91 | super.processOptions(executionOptions) 92 | annotationSignature = executionOptions.getOrElse(KOTLIN_METADATA_SIGNATURE) { "Lkotlin/Metadata;" } 93 | 94 | metadataKName = executionOptions.getOrElse(KOTLIN_METADATA_KIND) { KOTLIN_METADATA_KIND } 95 | metadataBVName = executionOptions.getOrElse(KOTLIN_METADATA_BYTECODE_VERSION) { KOTLIN_METADATA_BYTECODE_VERSION } 96 | metadataMVName = executionOptions.getOrElse(KOTLIN_METADATA_METADATA_VERSION) { KOTLIN_METADATA_METADATA_VERSION } 97 | metadataD1Name = executionOptions.getOrElse(KOTLIN_METADATA_DATA1) { KOTLIN_METADATA_DATA1 } 98 | metadataD2Name = executionOptions.getOrElse(KOTLIN_METADATA_DATA2) { KOTLIN_METADATA_DATA2 } 99 | metadataXSName = executionOptions.getOrElse(KOTLIN_METADATA_EXTRA_STRING) { KOTLIN_METADATA_EXTRA_STRING } 100 | metadataPNName = executionOptions.getOrElse(KOTLIN_METADATA_PACKAGE_NAME) { KOTLIN_METADATA_PACKAGE_NAME } 101 | metadataXIName = executionOptions.getOrElse(KOTLIN_METADATA_EXTRA_INT) { KOTLIN_METADATA_EXTRA_INT } 102 | 103 | return true 104 | } 105 | 106 | override fun processUnit(unit: IDexUnit, renameEngine: RenameEngine) { 107 | val annotationClass = unit.classBySignature(annotationSignature) 108 | if (annotationClass == null) { 109 | logger.error("No such class '$annotationSignature' in this dex unit") 110 | return 111 | } else if (annotationClass.accessFlags and IDexUnit.ACC_ANNOTATION == 0) { 112 | logger.error("Class '$annotationClass' is not an annotation class.") 113 | return 114 | } 115 | 116 | metadataKIndex = annotationClass.methods.firstOrNull { it.originalName == metadataKName }?.nameIndex ?: run { 117 | logger.error("The kind parameter does not exist for the annotation class") 118 | return 119 | } 120 | metadataBVIndex = annotationClass.methods.firstOrNull { it.originalName == metadataBVName }?.nameIndex ?: run { 121 | logger.error("The bytecode version parameter does not exist for the annotation class") 122 | return 123 | } 124 | metadataMVIndex = annotationClass.methods.firstOrNull { it.originalName == metadataMVName }?.nameIndex ?: run { 125 | logger.error("The metadata version parameter does not exist for the annotation class") 126 | return 127 | } 128 | metadataD2Index = annotationClass.methods.firstOrNull { it.originalName == metadataD2Name }?.nameIndex ?: run { 129 | logger.error("The data2 parameter does not exist for the annotation class") 130 | return 131 | } 132 | metadataXSIndex = annotationClass.methods.firstOrNull { it.originalName == metadataXSName }?.nameIndex ?: run { 133 | logger.error("The extra string parameter does not exist for the annotation class") 134 | return 135 | } 136 | metadataD1Index = annotationClass.methods.firstOrNull { it.originalName == metadataD1Name }?.nameIndex ?: run { 137 | logger.error("The data1 parameter does not exist for the annotation class") 138 | return 139 | } 140 | metadataPNIndex = annotationClass.methods.firstOrNull { it.originalName == metadataPNName }?.nameIndex ?: run { 141 | logger.error("The package name parameter does not exist for the annotation class. Ignoring the parameter") 142 | -1 143 | } 144 | metadataXIIndex = annotationClass.methods.firstOrNull { it.originalName == metadataXIName }?.nameIndex ?: run { 145 | logger.error("The extra int parameter does not exist for the annotation class. Ignoring the parameter") 146 | -1 147 | } 148 | 149 | val annotationTypeIndex = annotationClass.classTypeIndex 150 | 151 | var seq = unit.classes.asSequence() 152 | seq = if (isOperatingOnlyOnThisClass) { 153 | seq.filter { it.classType == UIBridge.currentClass } 154 | } else { 155 | seq.filter(classFilter::matches) 156 | } 157 | 158 | var i = 0 159 | seq.filter { it.annotationsDirectory?.classAnnotations?.isNotEmpty() ?: false }.mapToPairNotNull { cls -> 160 | cls.annotationsDirectory.classAnnotations.firstOrNull { 161 | it.annotation.typeIndex == annotationTypeIndex 162 | }?.annotation 163 | }.forEach { (cls, annotation) -> 164 | i++ 165 | processClass(unit, cls, annotation, renameEngine) 166 | } 167 | logger.info("There are $i classes with kotlin metadata annotation!") 168 | } 169 | 170 | private fun processClass(unit: IDexUnit, cls: IDexClass, annotation: IDexAnnotation, renameEngine: RenameEngine) { 171 | val elements = annotation.elements.sortedBy { it.nameIndex } 172 | val k = elements.firstOrNull { it.nameIndex == metadataKIndex }?.value?.int 173 | val mv = elements.firstOrNull { it.nameIndex == metadataMVIndex }?.value?.parseIntArray() 174 | val d1 = elements.firstOrNull { it.nameIndex == metadataD1Index }?.value?.parseStringArray(unit)?.map { it.unescape() }?.toTypedArray() 175 | val d2 = elements.firstOrNull { it.nameIndex == metadataD2Index }?.value?.parseStringArray(unit) 176 | val pn = elements.firstOrNull { it.nameIndex == metadataPNIndex }?.value?.stringIndex?.let(unit::getString)?.toString() 177 | val xs = elements.firstOrNull { it.nameIndex == metadataXSIndex }?.value?.stringIndex?.let(unit::getString)?.toString() 178 | val xi = elements.firstOrNull { it.nameIndex == metadataXIIndex }?.value?.int 179 | 180 | 181 | val header = KotlinClassHeader(k, mv, d1, d2, xs, pn, xi) 182 | val originalComment = unit.getCommentBackport(cls.currentSignature) ?: "" 183 | if (KOTLIN_METADATA_COMMENT_PREFIX !in originalComment) { 184 | val comment = header.toStringBlock() 185 | if (originalComment.isBlank()) { 186 | unit.setCommentBackport(cls.currentSignature, comment) 187 | } else { 188 | unit.setCommentBackport(cls.currentSignature, originalComment + "\n\n" + comment) 189 | } 190 | } 191 | 192 | when (val metadata = KotlinClassMetadata.read(header)) { 193 | is KotlinClassMetadata.Class -> { 194 | val classInfo = metadata.toKmClass() 195 | val name = classInfo.name.split("/").last().replace(".", "$") 196 | renameEngine.renameClass(RenameRequest(name, RenameReason.KotlinName), cls) 197 | } 198 | is KotlinClassMetadata.FileFacade -> Unit 199 | is KotlinClassMetadata.SyntheticClass -> Unit 200 | is KotlinClassMetadata.MultiFileClassFacade -> Unit 201 | is KotlinClassMetadata.MultiFileClassPart -> Unit 202 | is KotlinClassMetadata.Unknown -> { 203 | logger.warning("Encountered unknown class: ${cls.currentName}. It probably means the metadata lib version is old.") 204 | } 205 | null -> Unit 206 | } 207 | } 208 | 209 | private fun IDexValue.parseStringArray(dexUnit: IDexUnit): Array { 210 | assert(type == IDexValue.VALUE_ARRAY) { "The value of the given dex value must be array" } 211 | return array.map { dexUnit.getString(it.stringIndex).toString() }.toTypedArray() 212 | } 213 | 214 | private fun IDexValue.parseIntArray(): IntArray { 215 | assert(type == IDexValue.VALUE_ARRAY) { "The value of the given dex value must be array" } 216 | return array.map { it.int }.toIntArray() 217 | } 218 | 219 | companion object { 220 | private const val KOTLIN_METADATA_SIGNATURE = "Kotlin Metadata Signature" 221 | private const val KOTLIN_METADATA_KIND = "k" 222 | private const val KOTLIN_METADATA_METADATA_VERSION = "mv" 223 | private const val KOTLIN_METADATA_BYTECODE_VERSION = "bv" 224 | private const val KOTLIN_METADATA_DATA1 = "d1" 225 | private const val KOTLIN_METADATA_DATA2 = "d2" 226 | private const val KOTLIN_METADATA_EXTRA_STRING = "xs" 227 | private const val KOTLIN_METADATA_PACKAGE_NAME = "pn" 228 | private const val KOTLIN_METADATA_EXTRA_INT = "xi" 229 | } 230 | } 231 | 232 | -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/kotlin/KotlinIntrinsicsPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.kotlin 2 | 3 | import com.pnfsoftware.jeb.core.IPluginInformation 4 | import com.pnfsoftware.jeb.core.PluginInformation 5 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 6 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexClass 7 | import com.yoavst.jeb.plugins.JEB_VERSION 8 | import com.yoavst.jeb.plugins.PLUGIN_VERSION 9 | import com.yoavst.jeb.plugins.constarg.ConstArgMassRenaming 10 | import com.yoavst.jeb.utils.BasicEnginesPlugin 11 | import com.yoavst.jeb.utils.renaming.RenameEngine 12 | import com.yoavst.jeb.utils.renaming.RenameReason 13 | import com.yoavst.jeb.utils.renaming.RenameRequest 14 | import com.yoavst.jeb.utils.xrefsForString 15 | 16 | class KotlinIntrinsicsPlugin : BasicEnginesPlugin(supportsClassFilter = true) { 17 | override fun getPluginInformation(): IPluginInformation = PluginInformation( 18 | "Kotlin Intrinsics processor", 19 | "Fire the plugin to process kotlin intrinsics", 20 | "Yoav Sternberg", 21 | PLUGIN_VERSION, 22 | JEB_VERSION, 23 | null 24 | ) 25 | 26 | override fun processUnit(unit: IDexUnit, renameEngine: RenameEngine) { 27 | val foundStrings = unit.strings?.filter { it.toString() in EXPECTED_STRINGS } ?: emptyList() 28 | if (foundStrings.isEmpty()) { 29 | logger.error("No Strings from Kotlin.Intrinsics was found in the unit: $unit") 30 | return 31 | } 32 | val results = foundStrings.fold(mutableMapOf()) { cache, str -> 33 | unit.xrefsForString(str).forEach { xref -> 34 | val cls = unit.getMethod(xref)?.classType?.implementingClass ?: return@forEach 35 | cache[cls] = (cache[cls] ?: 0) + 1 36 | } 37 | cache 38 | } 39 | val result = results.maxByOrNull { it.value } 40 | if (result == null) { 41 | logger.error("Could not found Kotlin.Intrinsics!") 42 | return 43 | } 44 | 45 | val intrinsicsClass = result.key 46 | 47 | renameEngine.renameClass(RenameRequest("Intrinsics", RenameReason.KotlinName), intrinsicsClass) 48 | 49 | val renamers = intrinsicsClass.methods.mapNotNull { method -> 50 | val methodResult = IntrinsicsMethodDetector.detect(method, unit) ?: return@mapNotNull null 51 | renameEngine.renameMethod(RenameRequest(methodResult.name, RenameReason.KotlinName), method, method.classType.implementingClass) 52 | if (methodResult.renamer != null) { 53 | method.getSignature(false) to methodResult.renamer 54 | } else null 55 | } 56 | 57 | val massRenamer = ConstArgMassRenaming(renamers.toMap(), false, classFilter) 58 | 59 | massRenamer.processUnit(unit, renameEngine) 60 | massRenamer.propagate(unit, renameEngine) 61 | } 62 | 63 | companion object { 64 | val EXPECTED_STRINGS = setOf( 65 | "lateinit property ", 66 | " has not been initialized", 67 | " must not be null", 68 | "Method specified as non-null returned null: ", 69 | "Field specified as non-null is null: ", 70 | "Parameter specified as non-null is null: method ", 71 | "This function has a reified type parameter and thus can only be inlined at compilation time, not called directly.", 72 | " is not found. Please update the Kotlin runtime to the latest version", 73 | " is not found: this code requires the Kotlin runtime of version at least " 74 | ) 75 | } 76 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/resourcesname/ResourceId.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.resourcesname 2 | 3 | data class ResourceId(val name: String, val value: Int, val type: String, val isSystem: Boolean = false) { 4 | fun toField(packagePrefix: String) = "${if (isSystem) ANDROID_PREFIX else packagePrefix}R\$$type;->$name:I" 5 | fun toClass(packagePrefix: String) = "${if (isSystem) ANDROID_PREFIX else packagePrefix}R\$$type;" 6 | 7 | companion object { 8 | private const val ANDROID_PREFIX = "Landroid/" 9 | } 10 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/resourcesname/ResourcesNamePlugin.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.resourcesname 2 | 3 | import com.pnfsoftware.jeb.core.IPluginInformation 4 | import com.pnfsoftware.jeb.core.PluginInformation 5 | import com.pnfsoftware.jeb.core.units.code.android.DexParsingException 6 | import com.pnfsoftware.jeb.core.units.code.android.DexUtil.bytearrayULEInt32ToInt 7 | import com.pnfsoftware.jeb.core.units.code.android.IDexDecompilerUnit 8 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 9 | import com.pnfsoftware.jeb.core.units.code.android.dex.* 10 | import com.pnfsoftware.jeb.core.units.code.java.IJavaConstant 11 | import com.pnfsoftware.jeb.util.collect.MultiMap 12 | import com.yoavst.jeb.bridge.UIBridge 13 | import com.yoavst.jeb.plugins.JEB_VERSION 14 | import com.yoavst.jeb.plugins.PLUGIN_VERSION 15 | import com.yoavst.jeb.utils.* 16 | import com.yoavst.jeb.utils.renaming.RenameEngine 17 | import com.yoavst.jeb.utils.renaming.RenameReason 18 | import com.yoavst.jeb.utils.renaming.RenameRequest 19 | import org.w3c.dom.Document 20 | import java.io.InputStream 21 | import javax.xml.parsers.DocumentBuilderFactory 22 | 23 | 24 | /** 25 | * Replace const resource id with R.type.resourceName. 26 | * Doesn't work with: 27 | * - switch case since it is not supported by jeb. 28 | * xrefs are enabled though 29 | * - static initializers for field (works for array) 30 | * should we add xrefs support for this case? 31 | */ 32 | class ResourcesNamePlugin : BasicEnginesPlugin( 33 | supportsClassFilter = true, defaultForScopeOnThisClass = false, 34 | defaultForScopeOnThisFunction = false 35 | ) { 36 | private lateinit var resources: MultiMap 37 | private lateinit var intToResourceId: Map 38 | 39 | override fun getPluginInformation(): IPluginInformation = PluginInformation( 40 | "Resources name restore", 41 | "Fire the plugin to recreate R class and replace constants in code", 42 | "Yoav Sternberg", 43 | PLUGIN_VERSION, 44 | JEB_VERSION, 45 | null 46 | ) 47 | 48 | override fun preProcess(): Boolean { 49 | val (resources, intToResourceId) = context.getXmlResource("public.xml")?.parsePublicXml() ?: run { 50 | logger.error("No such resource public.xml! The resource is needed to reconstruct the names from ids.") 51 | return false 52 | } 53 | val (androidResources, androidIntToResourceId) = parseAndroidPublicXml() 54 | this.resources = resources 55 | androidResources.keySet().forEach { 56 | resources.putMulti(it, androidResources[it]) 57 | } 58 | this.intToResourceId = intToResourceId + androidIntToResourceId 59 | return true 60 | } 61 | 62 | override fun processUnit(unit: IDexUnit, renameEngine: RenameEngine) { 63 | val decompiler = unit.decompilerRef 64 | createRForUnit(unit) 65 | 66 | if (isOperatingOnlyOnThisMethod) { 67 | if (UIBridge.currentMethod != null && UIBridge.currentClass != null) { 68 | // you cannot see the sources of a type without implementing class 69 | processMethod(UIBridge.currentMethod!!, unit, decompiler, renameEngine) 70 | } 71 | } else if (isOperatingOnlyOnThisClass) { 72 | if (UIBridge.currentClass != null) { 73 | UIBridge.currentClass!!.implementingClass!!.methods.forEach { method -> 74 | preProcessMethod(method, unit, decompiler, renameEngine) 75 | } 76 | processClass(UIBridge.currentClass!!.implementingClass!!, decompiler, renameEngine) 77 | } 78 | } else { 79 | unit.classes.asSequence().filter(classFilter::matches).onEach { 80 | processClass(it, decompiler, renameEngine) 81 | }.flatMap(IDexClass::getMethods) 82 | .forEach { preProcessMethod(it, unit, decompiler, renameEngine) } 83 | } 84 | } 85 | 86 | private fun processClass(cls: IDexClass, decompiler: IDexDecompilerUnit, renameEngine: RenameEngine) { 87 | // check if we have a static field which is a resource 88 | cls.fields.asSequence().filter { it.staticInitializer?.type == IDexValue.VALUE_INT }.forEach { field -> 89 | val intValue = field.staticInitializer!!.int 90 | val res = intToResourceId[intValue] ?: return@forEach 91 | val decompiledField = decompiler.decompileDexField(field) ?: return@forEach 92 | renameEngine.renameField(RenameRequest(res.name, RenameReason.Resource), decompiledField, cls) 93 | } 94 | } 95 | 96 | private fun preProcessMethod(method: IDexMethod, unit: IDexUnit, decompiler: IDexDecompilerUnit, engine: RenameEngine) { 97 | if (method.isInternal && method.instructions != null) { 98 | @Suppress("UNCHECKED_CAST") 99 | val instructions = method.instructions as List 100 | for (instruction in instructions) { 101 | if (instruction.opcode == DalvikInstructionOpcodes.OP_CONST) { 102 | val value = instruction.parameters[1].value.toInt() 103 | if (value in intToResourceId) { 104 | // method uses resource id, it's ok to decompile it. 105 | processMethod(method, unit, decompiler, engine) 106 | break 107 | } 108 | } 109 | } 110 | } 111 | } 112 | 113 | private fun processMethod(method: IDexMethod, unit: IDexUnit, decompiler: IDexDecompilerUnit, engine: RenameEngine) { 114 | logger.trace("Processing: ${method.currentSignature}") 115 | val decompiledMethod = decompiler.decompileDexMethod(method) ?: run { 116 | logger.warning("Failed to decompile method: ${method.currentSignature}") 117 | return 118 | } 119 | 120 | val packageSignature = "L${context.apkPackage}.".replace(".", "/") 121 | val highLevelContext = decompiler.highLevelContext 122 | 123 | // process decompile method 124 | decompiledMethod.body.visitSubElementsRecursive { element, parent -> 125 | if (element is IJavaConstant && element.type?.isInt == true) { 126 | val resource = intToResourceId[element.int] ?: return@visitSubElementsRecursive 127 | 128 | val rType = decompiler.highLevelContext.typeFactory.createType(resource.toClass(packageSignature)) 129 | val resField = highLevelContext.createFieldReference(resource.toField(packageSignature)) 130 | val resStaticField = highLevelContext.createStaticField(rType, resField) 131 | parent.replaceSubElement(element, resStaticField) 132 | method.classType.implementingClass?.let(engine.stats.effectedClasses::add) 133 | } 134 | } 135 | 136 | // add xrefs 137 | @Suppress("UNCHECKED_CAST") 138 | val instructions = method.instructions as List 139 | for (instruction in instructions) { 140 | when { 141 | instruction.opcode == DalvikInstructionOpcodes.OP_CONST -> { 142 | // const REGISTER, VALUE 143 | val value = instruction.parameters[1].value.toInt() 144 | val resource = intToResourceId[value] ?: continue 145 | resource.addXref(method, unit, packageSignature, instruction) 146 | } 147 | instruction.opcode == DalvikInstructionOpcodes.OP_FILL_ARRAY_DATA -> { 148 | // fill-array-data REGISTER, ARRAY_DATA 149 | for (element in instruction.arrayData.elements) { 150 | if (element.size == 4) { 151 | try { 152 | // 32-bit number 153 | val value = bytearrayULEInt32ToInt(element, 0) 154 | val resource = intToResourceId[value] ?: continue 155 | resource.addXref(method, unit, packageSignature, instruction) 156 | } catch (e: DexParsingException) { 157 | logger.error("Failed to decompile OP_FILL_ARRAY_DATA: ${method.address}") 158 | } 159 | } 160 | } 161 | } 162 | instruction.isSwitch -> { 163 | // switch data consists of pairs (value, address) 164 | for ((value, _) in instruction.switchData.elements) { 165 | val resource = intToResourceId[value] ?: continue 166 | resource.addXref(method, unit, packageSignature, instruction) 167 | } 168 | } 169 | } 170 | } 171 | } 172 | 173 | private fun ResourceId.addXref(method: IDexMethod, unit: IDexUnit, packageSignature: String, instruction: IDalvikInstruction) { 174 | unit.referenceManager.addFieldReference( 175 | toField(packageSignature), 176 | "${method.signature}+${instruction.offset}", 177 | DexReferenceType.GET 178 | ) 179 | } 180 | 181 | private fun createRForUnit(unit: IDexUnit) { 182 | val packageName = context.apkPackage 183 | val packageSignature = "L$packageName.".replace(".", "/") 184 | resources.keySet().forEach { resourceType -> 185 | val typeSignature = ResourceId("", 0, resourceType, isSystem = false).toClass(packageSignature) 186 | val androidTypeSignature = ResourceId("", 0, resourceType, isSystem = true).toClass(packageSignature) 187 | unit.addType(typeSignature) 188 | unit.addType(androidTypeSignature) 189 | resources[resourceType].forEach { res -> 190 | unit.addField(if (res.isSystem) androidTypeSignature else typeSignature, res.name, "I") 191 | } 192 | } 193 | } 194 | 195 | private fun Document.parsePublicXml(isSystem: Boolean = false): Pair, Map> { 196 | val publicNodeList = getElementsByTagName("public") 197 | val resources = MultiMap() 198 | val idsToResources = mutableMapOf() 199 | for (i in 0 until publicNodeList.length) { 200 | val attrs = publicNodeList.item(i).attributes 201 | val type = attrs.getNamedItem("type").textContent 202 | val id = attrs.getNamedItem("id").textContent.substring(2).toInt(16) 203 | val res = ResourceId(attrs.getNamedItem("name").textContent, id, type, isSystem) 204 | resources.put(type, res) 205 | idsToResources[id] = res 206 | } 207 | return resources to idsToResources 208 | } 209 | 210 | private fun parseAndroidPublicXml(): Pair, Map> = 211 | javaClass.classLoader.getResourceAsStream("aosp_public.xml")!!.toXmlDocument().parsePublicXml(isSystem = true) 212 | 213 | private fun InputStream.toXmlDocument(): Document { 214 | val factory = DocumentBuilderFactory.newInstance() 215 | factory.isNamespaceAware = true 216 | val builder = factory.newDocumentBuilder() 217 | return builder.parse(this) 218 | } 219 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/sourcefile/SourceFilePlugin.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.sourcefile 2 | 3 | import com.pnfsoftware.jeb.core.BooleanOptionDefinition 4 | import com.pnfsoftware.jeb.core.IOptionDefinition 5 | import com.pnfsoftware.jeb.core.IPluginInformation 6 | import com.pnfsoftware.jeb.core.PluginInformation 7 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 8 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexClass 9 | import com.yoavst.jeb.bridge.UIBridge 10 | import com.yoavst.jeb.plugins.JEB_VERSION 11 | import com.yoavst.jeb.plugins.PLUGIN_VERSION 12 | import com.yoavst.jeb.utils.* 13 | import com.yoavst.jeb.utils.renaming.RenameEngine 14 | import com.yoavst.jeb.utils.renaming.RenameReason 15 | import com.yoavst.jeb.utils.renaming.RenameRequest 16 | 17 | class SourceFilePlugin : BasicEnginesPlugin(supportsClassFilter = true, defaultForScopeOnThisClass = false) { 18 | private var shouldAddComment: Boolean = false 19 | private var shouldAddToTypeName: Boolean = false 20 | override fun getPluginInformation(): IPluginInformation = PluginInformation( 21 | "Source file information", 22 | "Fire the plugin propagate source file info to decompilation view and to classes' name", 23 | "Yoav Sternberg", 24 | PLUGIN_VERSION, 25 | JEB_VERSION, 26 | null 27 | ) 28 | 29 | override fun getExecutionOptionDefinitions(): List { 30 | return super.getExecutionOptionDefinitions() + BooleanOptionDefinition( 31 | ADD_COMMENT, 32 | true, 33 | """Add the original source file as comment to the class""" 34 | ) + BooleanOptionDefinition( 35 | ADD_TO_TYPE_NAME, 36 | false, 37 | """Add the original source file as part of the type""" 38 | ) 39 | } 40 | 41 | override fun processOptions(executionOptions: Map): Boolean { 42 | super.processOptions(executionOptions) 43 | shouldAddComment = executionOptions.getOrDefault(ADD_COMMENT, "true").toBoolean() 44 | shouldAddToTypeName = executionOptions.getOrDefault(ADD_TO_TYPE_NAME, "false").toBoolean() 45 | return true 46 | } 47 | 48 | override fun processUnit(unit: IDexUnit, renameEngine: RenameEngine) { 49 | if (!shouldAddComment && !shouldAddToTypeName) 50 | return 51 | 52 | var seq = unit.classes.asSequence() 53 | seq = if (isOperatingOnlyOnThisClass) { 54 | seq.filter { it.classType == UIBridge.currentClass } 55 | } else { 56 | seq.filter(classFilter::matches) 57 | } 58 | 59 | var i = 0 60 | seq.filter { it.sourceStringIndex != -1 }.forEach { 61 | i++ 62 | processClass(unit, it, renameEngine) 63 | } 64 | logger.info("There are $i classes with source info") 65 | } 66 | 67 | private fun processClass(unit: IDexUnit, cls: IDexClass, renameEngine: RenameEngine) { 68 | val sourceName = unit.getString(cls.sourceStringIndex).value 69 | if (sourceName.isBlank()) return 70 | val sourceWithExtension = sourceName.split('.', limit = 2)[0] 71 | 72 | if (shouldAddComment) { 73 | if (sourceWithExtension !in cls.currentSignature && !cls.isMemberClass) { 74 | val originalComment = unit.getCommentBackport(cls.currentSignature) ?: "" 75 | if (COMMENT_PREFIX !in originalComment) { 76 | val comment = "$COMMENT_PREFIX$sourceName" 77 | if (originalComment.isBlank()) { 78 | unit.setCommentBackport(cls.currentSignature, comment) 79 | } else { 80 | unit.setCommentBackport(cls.currentSignature, originalComment + "\n\n" + comment) 81 | } 82 | } 83 | } 84 | } 85 | if (shouldAddToTypeName) { 86 | if (sourceWithExtension !in cls.currentSignature && !cls.isMemberClass) { 87 | renameEngine.renameClass(RenameRequest(sourceWithExtension, RenameReason.SourceFile), cls) 88 | } 89 | } 90 | } 91 | 92 | companion object { 93 | private const val ADD_COMMENT = "add comment" 94 | private const val ADD_TO_TYPE_NAME = "add to type name" 95 | private const val COMMENT_PREFIX = "source name: " 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/tostring/ToStringRenamingPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.tostring 2 | 3 | import com.pnfsoftware.jeb.core.IPluginInformation 4 | import com.pnfsoftware.jeb.core.PluginInformation 5 | import com.pnfsoftware.jeb.core.units.code.android.IDexDecompilerUnit 6 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 7 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexClass 8 | import com.pnfsoftware.jeb.core.units.code.java.* 9 | import com.yoavst.jeb.bridge.UIBridge 10 | import com.yoavst.jeb.plugins.JEB_VERSION 11 | import com.yoavst.jeb.plugins.PLUGIN_VERSION 12 | import com.yoavst.jeb.utils.* 13 | import com.yoavst.jeb.utils.renaming.RenameEngine 14 | import com.yoavst.jeb.utils.renaming.RenameReason 15 | import com.yoavst.jeb.utils.renaming.RenameRequest 16 | 17 | class ToStringRenamingPlugin : BasicEnginesPlugin(supportsClassFilter = true, defaultForScopeOnThisClass = false) { 18 | override fun getPluginInformation(): IPluginInformation = PluginInformation( 19 | "ToString renaming", 20 | "Fire the plugin to change obfuscated fields' name given a verbose toString implementation", 21 | "Yoav Sternberg", 22 | PLUGIN_VERSION, 23 | JEB_VERSION, 24 | null 25 | ) 26 | 27 | override fun processUnit(unit: IDexUnit, renameEngine: RenameEngine) { 28 | val decompiler = unit.decompilerRef 29 | if (isOperatingOnlyOnThisClass) { 30 | val cls = UIBridge.focusedClass?.implementingClass ?: return 31 | processClass(cls, decompiler, renameEngine) 32 | } else { 33 | unit.classes.asSequence().filter(classFilter::matches) 34 | .forEach { processClass(it, decompiler, renameEngine) } 35 | } 36 | 37 | propagateRenameToGetterAndSetters(unit, renameEngine.stats.effectedClasses, renameEngine) 38 | unit.refresh() 39 | } 40 | 41 | private fun processClass(cls: IDexClass, decompiler: IDexDecompilerUnit, renameEngine: RenameEngine) { 42 | val toStringMethod = cls.getMethod(false, "toString") ?: return 43 | logger.trace("Processing class: ${cls.currentName}") 44 | val decompiledToStringMethod = decompiler.decompileDexMethod(toStringMethod) ?: return 45 | processToStringMethod(decompiledToStringMethod, cls, renameEngine) 46 | } 47 | 48 | /** Process a toString() method from the given class **/ 49 | private fun processToStringMethod(method: IJavaMethod, cls: IDexClass, renameEngine: RenameEngine) { 50 | // currently, supports only one style of toString 51 | if (method.body.size() == 0 || method.body[0] !is IJavaReturn) 52 | return 53 | 54 | var expression = (method.body[0] as IJavaReturn).expression 55 | while (true) { 56 | if (expression !is IJavaOperation) { 57 | logger.warn("Warning: The toString method for ${cls.name} is not just an arithmetic expression") 58 | return 59 | } 60 | 61 | var left = expression.left 62 | var right = expression.right 63 | 64 | /* 65 | 2. Remove trailing strings: 66 | Examples: 67 | return "test: " + this.d + " ]" 68 | return "Entity [info=" + this.a + "aaa" + "bbb" 69 | notice exp.getRight() here returns "bbb", 70 | exp.getLeft() returns "Entity [info=" + this.a + "aaa" 71 | */ 72 | while (right is IJavaConstant) { 73 | // safe because we check it before, and inside the loop 74 | expression = (expression as IJavaOperation).left 75 | 76 | if (expression !is IJavaOperation) { 77 | logger.debug("Warning: The toString method for ${cls.name} folds to a const toString") 78 | return 79 | } 80 | 81 | left = expression.left 82 | right = expression.right 83 | } 84 | 85 | when (left) { 86 | is IJavaConstant -> { 87 | // we have reached the left end of whole return expression 88 | if (!left.isString) { 89 | logger.warn("Warning: The toString method for ${cls.name} is not in a valid format, left is not const string") 90 | return 91 | } 92 | 93 | val leftRaw = left.string.replaceNonApplicableChars().trim() 94 | if (leftRaw.isNotBlank()) { 95 | val lefts = leftRaw.split(' ') 96 | val fieldName = lefts.last().trim() 97 | val firstField = right 98 | val possibleClassName = if (lefts.size < 2) null else lefts[0] 99 | renamePossibleExpressionWithStr(firstField, fieldName, cls, renameEngine) 100 | if (possibleClassName != null) { 101 | renameEngine.renameClass(RenameRequest(possibleClassName, RenameReason.ToString), cls) 102 | } 103 | } 104 | break 105 | 106 | } 107 | is IJavaOperation -> { 108 | /* 109 | 2. Left traversal to restore names 110 | Example: 111 | return "Entity [info=" + this.a + ", value=" + this.b 112 | return {"Entity [info="+this.a+}(left.left)"value="(left.right)+this.b 113 | In this example: 114 | right = this.b 115 | left = "Entity [info=" + this.a + ", value=" 116 | left.right = ", value=" 117 | So the field name is `sanitize(left.right)`, and the field itself is `right` 118 | */ 119 | val fieldNameAst = left.right 120 | if (fieldNameAst !is IJavaConstant || !fieldNameAst.isString) { 121 | logger.debug("Warning: The toString method for ${cls.name} is not in a valid format") 122 | return 123 | } 124 | val fieldName = fieldNameAst.string.sanitizeClassname() 125 | renamePossibleExpressionWithStr(right, fieldName, cls, renameEngine) 126 | // the left traversal in action 127 | expression = left.left 128 | 129 | } 130 | else -> { 131 | logger.debug("Warning: The toString method for ${cls.name} has a complex expression ${left?.javaClass?.canonicalName}") 132 | return 133 | } 134 | } 135 | } 136 | } 137 | 138 | private fun renamePossibleExpressionWithStr( 139 | exp: IJavaExpression, newName: String, 140 | cls: IDexClass, renameEngine: RenameEngine 141 | ) { 142 | if (newName.isBlank()) { 143 | logger.debug("Warning: The toString method for ${cls.name} yielded a blank field name") 144 | return 145 | } else if (exp is IJavaInstanceField || exp is IJavaStaticField) { 146 | // why don't they have a common interface god damn it 147 | val field = (exp as? IJavaInstanceField)?.field ?: (exp as? IJavaStaticField)?.field ?: run { 148 | logger.info("failed to get field: $exp") 149 | return 150 | } 151 | renameEngine.renameField(RenameRequest(newName, RenameReason.ToString), field, cls) 152 | return 153 | } else if (exp is IJavaCall || exp is IJavaConditionalExpression || exp is IJavaPredicate) { 154 | // support the this.a.ToString() or String.valueOf(this.a) case 155 | val targetFields = exp.subElements.filterIsInstance(IJavaInstanceField::class.java) 156 | if (targetFields.size == 1) { 157 | renamePossibleExpressionWithStr(targetFields[0], newName, cls, renameEngine) 158 | } else { 159 | logger.warn("Cannot handle expression: $exp") 160 | } 161 | return 162 | } 163 | logger.warn("Warning: The toString method for ${cls.name} has a complex expression as a field for $newName: ${exp.javaClass.canonicalName}") 164 | } 165 | 166 | 167 | companion object { 168 | private fun String.sanitizeClassname() = filter(Char::isJavaIdentifierPart).trim() 169 | private fun String.replaceNonApplicableChars() = replace(".", "$$").replace("[^0-9a-zA-Z_\$]+".toRegex(), " ") 170 | } 171 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/xrefsquery/JythonHelper.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.xrefsquery 2 | 3 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexMethod 4 | import com.pnfsoftware.jeb.core.units.code.java.IJavaConstant 5 | import com.pnfsoftware.jeb.core.units.code.java.IJavaElement 6 | import com.pnfsoftware.jeb.core.units.code.java.IJavaMethod 7 | import com.pnfsoftware.jeb.core.units.code.java.IJavaStaticField 8 | import com.pnfsoftware.jeb.core.units.code.java.JavaFlags 9 | import org.python.util.PythonInterpreter 10 | 11 | object JythonHelper { 12 | private const val JAVA_METHOD = "jmethod" 13 | private const val JAVA_TYPE = "jtype" 14 | private const val DEX_METHOD = "dmethod" 15 | private const val DEX_CLASS = "dclass" 16 | private const val DEX_UNIT = "dunit" 17 | private const val XREF_METHOD = "xref_method" 18 | private const val ARGS = "args" 19 | private const val CALL_ELEMENT = "call_element" 20 | 21 | private const val UTILS = "utils" 22 | private const val RESULT = "result" 23 | 24 | fun execute(script: String, executeParams: ExecuteParams): Boolean { 25 | val baseInterpreter = PythonInterpreter() 26 | 27 | baseInterpreter[JAVA_METHOD] = executeParams.javaMethod 28 | baseInterpreter[JAVA_TYPE] = executeParams.javaMethod.classType 29 | baseInterpreter[DEX_METHOD] = executeParams.dexMethod 30 | baseInterpreter[DEX_CLASS] = executeParams.dexMethod.classType.implementingClass 31 | baseInterpreter[DEX_UNIT] = executeParams.dexMethod.dex 32 | baseInterpreter[XREF_METHOD] = executeParams.xrefMethod 33 | baseInterpreter[ARGS] = executeParams.args 34 | baseInterpreter[CALL_ELEMENT] = executeParams.callElement 35 | baseInterpreter[UTILS] = Utils 36 | 37 | baseInterpreter.exec(script) 38 | 39 | return baseInterpreter[RESULT]?.__nonzero__() ?: false 40 | } 41 | 42 | } 43 | 44 | object Utils { 45 | fun isConst(element: IJavaElement): Boolean = element is IJavaConstant 46 | 47 | @JvmOverloads 48 | fun isConstInt(element: IJavaElement, value: Int? = null): Boolean { 49 | if (element !is IJavaConstant || !element.type.isInt) { 50 | return false 51 | } 52 | 53 | if (value != null && value != element.int) 54 | return false 55 | 56 | return true 57 | } 58 | 59 | @JvmOverloads 60 | fun isConstLong(element: IJavaElement, value: Long? = null): Boolean { 61 | if (element !is IJavaConstant || !element.type.isLong) { 62 | return false 63 | } 64 | 65 | if (value != null && value != element.long) 66 | return false 67 | 68 | return true 69 | } 70 | 71 | @JvmOverloads 72 | fun isConstString(element: IJavaElement, value: String? = null): Boolean { 73 | if (element !is IJavaConstant || !element.isString) { 74 | return false 75 | } 76 | 77 | if (value != null && value != element.string) 78 | return false 79 | 80 | return true 81 | } 82 | 83 | @JvmOverloads 84 | fun isConstBoolean(element: IJavaElement, value: Boolean? = null): Boolean { 85 | if (element !is IJavaConstant || !element.type.isBoolean) { 86 | return false 87 | } 88 | 89 | if (value != null && value != element.boolean) 90 | return false 91 | 92 | return true 93 | } 94 | 95 | @JvmOverloads 96 | fun isConstClass(element: IJavaElement, classSignature: String? = null): Boolean { 97 | if (element !is IJavaStaticField || element.fieldName != "class") { 98 | return false 99 | } 100 | 101 | if (classSignature != null && classSignature != element.classType.signature) { 102 | return false 103 | } 104 | 105 | return true 106 | } 107 | 108 | 109 | fun isStaticFinalField(element: IJavaElement): Boolean { 110 | return element is IJavaStaticField && (element.field.accessFlags and JavaFlags.FINAL != 0) 111 | } 112 | } 113 | 114 | data class ExecuteParams( 115 | val xrefMethod: IDexMethod, 116 | val javaMethod: IJavaMethod, 117 | val dexMethod: IDexMethod, 118 | val args: List, 119 | val callElement: IJavaElement 120 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/plugins/xrefsquery/XrefsQueryPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.plugins.xrefsquery 2 | 3 | import com.pnfsoftware.jeb.core.IPluginInformation 4 | import com.pnfsoftware.jeb.core.PluginInformation 5 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 6 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexMethod 7 | import com.pnfsoftware.jeb.core.units.code.java.* 8 | import com.yoavst.jeb.bridge.UIBridge 9 | import com.yoavst.jeb.plugins.JEB_VERSION 10 | import com.yoavst.jeb.plugins.PLUGIN_VERSION 11 | import com.yoavst.jeb.plugins.constarg.scriptRenamer 12 | import com.yoavst.jeb.utils.* 13 | import com.yoavst.jeb.utils.renaming.RenameEngine 14 | import java.io.File 15 | 16 | class XrefsQueryPlugin : 17 | BasicEnginesPlugin(supportsClassFilter = false, usingSelectedMethod = true) { 18 | private lateinit var script: String 19 | override fun getPluginInformation(): IPluginInformation = PluginInformation( 20 | "Xrefs Query", 21 | "Used this plugin to filter xrefs", 22 | "Yoav Sternberg", 23 | PLUGIN_VERSION, 24 | JEB_VERSION, 25 | null 26 | ) 27 | 28 | override fun processOptions(executionOptions: Map): Boolean { 29 | super.processOptions(executionOptions) 30 | val scriptPath = displayFileOpenSelector("xrefs script") ?: run { 31 | logger.error("No script selected") 32 | return false 33 | } 34 | script = File(scriptPath).readText() 35 | return true 36 | } 37 | 38 | override fun processUnit(unit: IDexUnit, renameEngine: RenameEngine) { 39 | val expectedMethod = UIBridge.focusedMethod 40 | if (expectedMethod == null) { 41 | logger.warning("Selected method is null") 42 | return 43 | } 44 | 45 | logger.info("Using method: ${expectedMethod.signature}") 46 | 47 | val decompiler = unit.decompilerRef 48 | val xrefMethods = unit.xrefsFor(expectedMethod) 49 | .mapTo(mutableSetOf(), unit::getMethod) 50 | 51 | logger.info("Xref methods: ${xrefMethods.size}") 52 | 53 | xrefMethods.parallelStream() 54 | .map { decompiler.decompileDexMethod(it)?.let { javaMethod -> it to javaMethod } } 55 | .filter { it != null } 56 | .sequential() 57 | .forEach { (dexMethod, javaMethod) -> 58 | processMethod( 59 | expectedMethod, 60 | dexMethod, 61 | javaMethod, 62 | renameEngine, 63 | script 64 | ) 65 | } 66 | } 67 | 68 | private fun processMethod( 69 | expectedMethod: IDexMethod, 70 | dexMethod: IDexMethod, 71 | javaMethod: IJavaMethod, 72 | renameEngine: RenameEngine, 73 | script: String 74 | ) { 75 | ArgsTraversal(expectedMethod, dexMethod, javaMethod, renameEngine, script).traverse(javaMethod) 76 | } 77 | 78 | private inner class ArgsTraversal( 79 | private val expectedMethod: IDexMethod, 80 | private val dexMethod: IDexMethod, 81 | private val javaMethod: IJavaMethod, 82 | renameEngine: RenameEngine, 83 | private val script: String 84 | ) : 85 | BasicAstTraversal(renameEngine) { 86 | 87 | private val expectedSig = expectedMethod.getSignature(false) 88 | private val argsCount = expectedMethod.parameterTypes.size 89 | 90 | 91 | override fun traverseNonCompound(statement: IJavaStatement) { 92 | if (statement is IJavaAssignment) { 93 | // Don't crash on: "Object x;" 94 | statement.right?.let(::traverseElement) 95 | } else { 96 | traverseElement(statement) 97 | } 98 | } 99 | 100 | private fun traverseElement(element: IJavaElement): Unit = when { 101 | element is IJavaCall && element.methodSignature == expectedSig -> { 102 | // we found the method we were looking for! 103 | try { 104 | processMatchedMethod(element, element::getRealArgument) 105 | } catch (e: Exception) { 106 | logger.error("Failed for ${element.methodSignature}") 107 | throw e 108 | } 109 | } 110 | 111 | element is IJavaNew && element.constructorSignature == expectedSig -> { 112 | // the method we were looking for was a constructor 113 | processMatchedMethod(element) { element.arguments[it] } 114 | } 115 | 116 | else -> { 117 | // Try sub elements 118 | element.subElements.forEach(::traverseElement) 119 | } 120 | } 121 | 122 | private inline fun processMatchedMethod( 123 | element: IJavaElement, 124 | getArg: (Int) -> IJavaElement 125 | ) { 126 | val args = List(argsCount, getArg) 127 | val isExpected = JythonHelper.execute( 128 | script, ExecuteParams( 129 | expectedMethod, 130 | javaMethod, 131 | dexMethod, 132 | args, 133 | element 134 | ) 135 | ) 136 | 137 | if (isExpected) { 138 | logger.info("$dexMethod+${element.physicalOffset.toString(16)}h") 139 | } 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/AstUtils.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils 2 | 3 | import com.pnfsoftware.jeb.core.units.code.java.IJavaElement 4 | 5 | fun IJavaElement.visitSubElementsRecursive(visitor: (IJavaElement, IJavaElement) -> Unit) { 6 | subElements.forEach { subElement -> 7 | visitor(subElement, this) 8 | subElement.visitSubElementsRecursive(visitor) 9 | } 10 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/BasicAstTraversal.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils 2 | 3 | import com.pnfsoftware.jeb.core.units.code.java.IJavaCompound 4 | import com.pnfsoftware.jeb.core.units.code.java.IJavaBlock 5 | import com.pnfsoftware.jeb.core.units.code.java.IJavaMethod 6 | import com.pnfsoftware.jeb.core.units.code.java.IJavaStatement 7 | import com.yoavst.jeb.utils.renaming.RenameEngine 8 | 9 | abstract class BasicAstTraversal(protected val renameEngine: RenameEngine) { 10 | /** Traverse block is to traverse its children **/ 11 | fun traverse(block: IJavaBlock) { 12 | for (i in 0 until block.size()) { 13 | traverse(block[i]) 14 | } 15 | } 16 | 17 | /** Traverse a statement by delegating to `traverseNonCompound` **/ 18 | private fun traverse(statement: IJavaStatement) { 19 | // Handle recursive case 20 | if (statement is IJavaCompound) { 21 | statement.blocks.forEach(this::traverse) 22 | } else { 23 | traverseNonCompound(statement) 24 | } 25 | } 26 | 27 | protected abstract fun traverseNonCompound(statement: IJavaStatement) 28 | 29 | 30 | } 31 | 32 | fun BasicAstTraversal.traverse(method: IJavaMethod) = traverse(method.body) -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/BasicEnginesPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils 2 | 3 | import com.pnfsoftware.jeb.core.AbstractEnginesPlugin 4 | import com.pnfsoftware.jeb.core.IEnginesContext 5 | import com.pnfsoftware.jeb.core.IOptionDefinition 6 | import com.pnfsoftware.jeb.core.OptionDefinition 7 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 8 | import com.pnfsoftware.jeb.util.logging.GlobalLog 9 | import com.pnfsoftware.jeb.util.logging.ILogger 10 | import com.yoavst.jeb.bridge.UIBridge 11 | import com.yoavst.jeb.utils.renaming.RenameEngine 12 | import kotlin.properties.Delegates 13 | 14 | abstract class BasicEnginesPlugin( 15 | private val supportsClassFilter: Boolean = false, 16 | private val defaultForScopeOnThisClass: Boolean? = null, 17 | private val defaultForScopeOnThisFunction: Boolean? = null, 18 | private val usingSelectedMethod: Boolean = false, 19 | private val usingSelectedClass: Boolean = false 20 | ) : AbstractEnginesPlugin() { 21 | protected lateinit var context: IEnginesContext 22 | protected val logger: ILogger = GlobalLog.getLogger(javaClass) 23 | 24 | protected lateinit var classFilter: Regex 25 | protected var isOperatingOnlyOnThisClass: Boolean by Delegates.notNull() 26 | protected var isOperatingOnlyOnThisMethod: Boolean by Delegates.notNull() 27 | 28 | override fun execute(context: IEnginesContext, executionOptions: MutableMap?) { 29 | try { 30 | this.context = context 31 | if (context.projects.isEmpty()) { 32 | logger.error("Error: Please open a project!") 33 | return 34 | } 35 | 36 | if (!processOptions(executionOptions ?: mapOf())) return 37 | 38 | if (!preProcess()) return 39 | 40 | context.getDexUnits().forEach { 41 | val renameEngine = RenameEngine.create() 42 | processUnit(it, renameEngine) 43 | it.refresh() 44 | logger.error("Finished executing plugin ${this::class.simpleName} on unit: $it") 45 | logger.error(renameEngine.toString()) 46 | } 47 | } catch (e: Exception) { 48 | logger.catching(e) 49 | } 50 | } 51 | 52 | override fun getExecutionOptionDefinitions(): List { 53 | val out = mutableListOf() 54 | if (usingSelectedClass) 55 | out += usingThisClass(UIBridge.focusedClass?.currentSignature) 56 | if (usingSelectedMethod) 57 | out += usingThisMethod(UIBridge.focusedMethod?.currentSignature) 58 | if (usingSelectedClass || usingSelectedMethod) { 59 | // put an empty row 60 | out += OptionDefinition("") 61 | } 62 | if (supportsClassFilter) 63 | out += ClassFilterOption 64 | if (defaultForScopeOnThisClass != null) 65 | out += scopeThisClass( 66 | defaultForScopeOnThisClass, 67 | UIBridge.currentClass?.currentSignature ?: "no class selected" 68 | ) 69 | if (defaultForScopeOnThisFunction != null) 70 | out += scopeThisMethod( 71 | defaultForScopeOnThisFunction, 72 | UIBridge.currentMethod?.currentSignature ?: "no method selected" 73 | ) 74 | return out 75 | } 76 | 77 | protected open fun preProcess(): Boolean = true 78 | 79 | protected abstract fun processUnit(unit: IDexUnit, renameEngine: RenameEngine) 80 | 81 | protected open fun processOptions(executionOptions: Map): Boolean { 82 | if (supportsClassFilter) 83 | classFilter = Regex(executionOptions[ClassFilterOptionTag].orIfBlank(ClassFilterDefault)) 84 | if (defaultForScopeOnThisClass != null) 85 | isOperatingOnlyOnThisClass = 86 | executionOptions[ScopeThisClassTag].orIfBlank(defaultForScopeOnThisClass.toString()).toBoolean() 87 | if (defaultForScopeOnThisFunction != null) 88 | isOperatingOnlyOnThisMethod = 89 | executionOptions[ScopeThisMethodTag].orIfBlank(defaultForScopeOnThisFunction.toString()).toBoolean() 90 | return true 91 | } 92 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/ClassUtils.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils 2 | 3 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 4 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexClass 5 | import com.pnfsoftware.jeb.util.logging.GlobalLog 6 | 7 | private object ClassUtils 8 | 9 | private val logger = GlobalLog.getLogger(ClassUtils::class.java) 10 | 11 | fun IDexUnit.classBySignature(classSignature: String) = classes.firstOrNull { it.currentSignature == classSignature } 12 | 13 | fun IDexUnit.subclassesOf(classSignature: String): Sequence { 14 | val classesWithGivenName = types.filter { it.signature == classSignature } 15 | if (classesWithGivenName.isEmpty()) { 16 | logger.error("Failed to find class with the given signature: $classSignature") 17 | return emptySequence() 18 | } else if (classesWithGivenName.size != 1) { 19 | logger.error("Multiple class with the given signature: ${classesWithGivenName.joinToString()}") 20 | return emptySequence() 21 | } 22 | 23 | return classes.asSequence().filter { classesWithGivenName == it.supertypes } 24 | } 25 | 26 | fun IDexClass.isSubclassOf(unit: IDexUnit, classSignature: String): Boolean { 27 | val classesWithGivenName = unit.types.filter { it.signature == classSignature } 28 | if (classesWithGivenName.isEmpty()) { 29 | logger.error("Failed to find class with the given signature: $classSignature") 30 | return false 31 | } 32 | return supertypes == classesWithGivenName 33 | } 34 | 35 | fun Regex.matches(cls: IDexClass): Boolean = matches(cls.signature) -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/ContextUtils.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils 2 | 3 | import com.pnfsoftware.jeb.core.IEnginesContext 4 | import com.pnfsoftware.jeb.core.RuntimeProjectUtil 5 | import com.pnfsoftware.jeb.core.units.IUnit 6 | import com.pnfsoftware.jeb.core.units.IXmlUnit 7 | import com.pnfsoftware.jeb.core.units.UnitUtil 8 | import com.pnfsoftware.jeb.core.units.code.android.IApkUnit 9 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 10 | import com.pnfsoftware.jeb.core.units.code.android.IXApkUnit 11 | import org.w3c.dom.Document 12 | 13 | fun IEnginesContext.getDexUnits(): MutableList = 14 | RuntimeProjectUtil.findUnitsByType(projects[0], IDexUnit::class.java, false) 15 | 16 | fun IEnginesContext.getXmlResource(name: String): Document? = 17 | RuntimeProjectUtil.findUnitsByType(projects[0], IXmlUnit::class.java, false).firstOrNull { it.name == name }?.let { 18 | if (!it.isProcessed) 19 | it.process() 20 | it.document 21 | } 22 | 23 | 24 | val IEnginesContext.apkPackage: String 25 | get() = RuntimeProjectUtil.findUnitsByType(projects[0], IApkUnit::class.java, false).firstOrNull()?.packageName 26 | ?: RuntimeProjectUtil.findUnitsByType(projects[0], IXApkUnit::class.java, false).first().packageName 27 | 28 | fun IUnit.refresh() = UnitUtil.notifyGenericChange(this) -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/DecompilerUtils.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils 2 | 3 | import com.pnfsoftware.jeb.core.units.code.android.IDexDecompilerUnit 4 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 5 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexField 6 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexMethod 7 | import com.pnfsoftware.jeb.core.units.code.java.IJavaField 8 | import com.pnfsoftware.jeb.core.units.code.java.IJavaMethod 9 | import com.pnfsoftware.jeb.core.util.DecompilerHelper 10 | import com.pnfsoftware.jeb.util.logging.GlobalLog 11 | 12 | private object DecompilerUtils 13 | 14 | private val logger = GlobalLog.getLogger(DecompilerUtils::class.java) 15 | 16 | val IDexUnit.decompilerRef: IDexDecompilerUnit get() = DecompilerHelper.getDecompiler(this) as IDexDecompilerUnit 17 | 18 | fun IDexDecompilerUnit.decompileDexMethod(method: IDexMethod): IJavaMethod? { 19 | try { 20 | if (!decompileMethod(method.signature)) { 21 | logger.warning("Failed to decompile ${method.currentSignature}") 22 | return null 23 | } 24 | 25 | return getMethod(method.signature, false) 26 | } catch (e: StackOverflowError) { 27 | // Fix bug where JEB crashes with stackoverflow when trying to decompile 28 | logger.error("Encountered not decompileable method: ${method.currentSignature}") 29 | return null 30 | } 31 | } 32 | 33 | fun IDexDecompilerUnit.decompileDexField(field: IDexField): IJavaField? { 34 | if (!decompileField(field.signature)) { 35 | logger.warning("Failed to decompile ${field.currentSignature}") 36 | return null 37 | } 38 | 39 | return getField(field.signature, false) 40 | } 41 | 42 | fun IJavaMethod.toDexMethod(unit: IDexUnit): IDexMethod? { 43 | val result = unit.getMethod(signature) 44 | if (result == null) 45 | logger.error("Could not convert java method to dex method: $this") 46 | return result 47 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/FocusUtils.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils 2 | 3 | import com.pnfsoftware.jeb.client.api.IGraphicalClientContext 4 | import com.pnfsoftware.jeb.core.output.ItemClassIdentifiers 5 | import com.pnfsoftware.jeb.core.output.code.AssemblyItem 6 | import com.pnfsoftware.jeb.core.units.IAddressableUnit 7 | import com.pnfsoftware.jeb.core.units.code.ICodeUnit 8 | import com.pnfsoftware.jeb.core.units.code.android.IDexDecompilerUnit 9 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexClass 10 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexMethod 11 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexType 12 | import com.pnfsoftware.jeb.core.units.code.java.IJavaSourceUnit 13 | import com.pnfsoftware.jeb.util.logging.GlobalLog 14 | import com.pnfsoftware.jeb.util.logging.ILogger 15 | 16 | private class FocusUtils 17 | 18 | private val logger: ILogger = GlobalLog.getLogger(FocusUtils::class.java) 19 | 20 | fun IGraphicalClientContext.currentFocusedMethod(supportFocus: Boolean = true, verbose: Boolean = true): IDexMethod? { 21 | val fragment = focusedFragment 22 | var address = fragment?.activeAddress 23 | if (fragment == null || address == null) { 24 | if (verbose) { 25 | logger.error( 26 | "Set the focus on a UI fragment, and position the caret somewhere in the method you would like to work on\n" + 27 | "Note: If you focus it on method invocation, it is going to be that method and not the container method." 28 | ) 29 | } 30 | return null 31 | } 32 | 33 | var unit = fragment.unit 34 | if (unit is IJavaSourceUnit) { 35 | val parent = unit.parent 36 | if (parent !is IDexDecompilerUnit) { 37 | logger.error("Parent for java source is not dex decompiler unit: $parent") 38 | return null 39 | } 40 | unit = parent 41 | } else if (unit !is ICodeUnit) { 42 | logger.error("Unit is not supported: $unit") 43 | return null 44 | } 45 | // get compiler to know that unit is addressable 46 | if (unit !is IAddressableUnit) { 47 | logger.error("Unit is not addressable: $unit") 48 | return null 49 | } 50 | 51 | // try selected item first 52 | if (supportFocus) { 53 | val item = fragment.activeItem 54 | if (item is AssemblyItem) { 55 | when (item.classId) { 56 | ItemClassIdentifiers.METHOD_NAME -> { 57 | val selectedMethod = unit.getItemObject(item.itemId) 58 | if (selectedMethod == null) { 59 | logger.error("Cannot get selected method: $fragment") 60 | return null 61 | } else if (selectedMethod !is IDexMethod) { 62 | logger.error("Selected method is not dex: $selectedMethod") 63 | return null 64 | } 65 | return selectedMethod 66 | } 67 | ItemClassIdentifiers.CLASS_NAME, ItemClassIdentifiers.EXTERNAL_CLASS_NAME -> { 68 | logger.error("Note: You cannot select a constructor from Java decompilation view. Switch to bytecode view and select the init function.") 69 | return null 70 | } 71 | else -> {} 72 | } 73 | } 74 | } 75 | 76 | // fallback to select current method 77 | 78 | if (unit is IDexDecompilerUnit) { 79 | val pos = address.indexOf('+') 80 | if (pos >= 0) { 81 | address = address.substring(0, pos) 82 | } 83 | val javaMethod = unit.getMethod(address, false) 84 | if (javaMethod == null) { 85 | logger.error("Not inside a method: $address") 86 | return null 87 | } 88 | val options = enginesContext.getDexUnits().mapNotNull(javaMethod::toDexMethod) 89 | if (options.isEmpty()) { 90 | return null 91 | } 92 | return options[0] 93 | } else { 94 | val method = (unit as ICodeUnit).getMethod(address) 95 | if (method == null) { 96 | logger.error("Not a method at the given address: $address") 97 | return null 98 | } 99 | 100 | if (method is IDexMethod) 101 | return method 102 | 103 | logger.error("Method is of invalid class: $method") 104 | return null 105 | } 106 | } 107 | 108 | fun IGraphicalClientContext.currentFocusedType(supportFocus: Boolean = true, verbose: Boolean = true): IDexType? { 109 | val fragment = focusedFragment 110 | var address = fragment?.activeAddress 111 | if (fragment == null || address == null) { 112 | if (verbose) { 113 | logger.error( 114 | "Set the focus on a UI fragment, and position the caret somewhere in the class (inside method) you would like to work on\n" + 115 | "Note: If you focus it on a class name, it is going to be that class and not the container class." 116 | ) 117 | } 118 | return null 119 | } 120 | 121 | var unit = fragment.unit 122 | if (unit is IJavaSourceUnit) { 123 | val parent = unit.parent 124 | if (parent !is IDexDecompilerUnit) { 125 | logger.error("Parent for java source is not dex decompiler unit: $parent") 126 | return null 127 | } 128 | unit = parent 129 | } else if (unit !is ICodeUnit) { 130 | logger.error("Unit is not supported: $unit") 131 | return null 132 | } 133 | // get compiler to know that unit is addressable 134 | if (unit !is IAddressableUnit) { 135 | logger.error("Unit is not addressable: $unit") 136 | return null 137 | } 138 | 139 | // try selected item first 140 | if (supportFocus) { 141 | val item = fragment.activeItem 142 | if (item is AssemblyItem) { 143 | if (item.classId == ItemClassIdentifiers.CLASS_NAME) { 144 | return when (val selectedClass = unit.getItemObject(item.itemId)) { 145 | null -> { 146 | logger.error("Cannot get selected class: $fragment") 147 | null 148 | } 149 | is IDexClass -> selectedClass.classType as? IDexType 150 | else -> { 151 | logger.error("Selected class is not dex: $selectedClass") 152 | null 153 | } 154 | } 155 | } else if (item.classId == ItemClassIdentifiers.EXTERNAL_CLASS_NAME) { 156 | return when (val selectedType = unit.getItemObject(item.itemId)) { 157 | null -> { 158 | logger.error("Cannot get selected type: $fragment") 159 | null 160 | } 161 | is IDexType -> selectedType 162 | else -> { 163 | logger.error("Selected type is not dex: $selectedType") 164 | null 165 | } 166 | } 167 | } 168 | } 169 | } 170 | 171 | // fallback to select current class 172 | 173 | if (unit is IDexDecompilerUnit) { 174 | val pos = address.indexOf("->") 175 | if (pos >= 0) { 176 | address = address.substring(0, pos) 177 | } 178 | val javaClass = unit.getClass(address, false) 179 | if (javaClass == null) { 180 | logger.error("Not inside class: $address") 181 | return null 182 | } 183 | val options = 184 | enginesContext.getDexUnits() 185 | .mapNotNull { dexUnit -> dexUnit.types.firstOrNull { it.originalSignature == javaClass.signature } } 186 | if (options.isEmpty()) { 187 | return null 188 | } 189 | return options[0] 190 | } else { 191 | val pos = address.indexOf("->") 192 | if (pos >= 0) { 193 | address = address.substring(0, pos) 194 | } 195 | val dexClass = (unit as ICodeUnit).getClass(address) 196 | if (dexClass == null) { 197 | logger.error("Not inside class: $address") 198 | return null 199 | } 200 | return dexClass.classType as IDexType 201 | } 202 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/GetterSetterUtils.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils 2 | 3 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 4 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexClass 5 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexMethod 6 | import com.pnfsoftware.jeb.core.units.code.java.IJavaAssignment 7 | import com.pnfsoftware.jeb.core.units.code.java.IJavaInstanceField 8 | import com.pnfsoftware.jeb.core.units.code.java.IJavaMethod 9 | import com.pnfsoftware.jeb.core.units.code.java.IJavaReturn 10 | import com.pnfsoftware.jeb.util.logging.GlobalLog 11 | import com.yoavst.jeb.utils.renaming.RenameEngine 12 | import com.yoavst.jeb.utils.renaming.RenameReason 13 | import com.yoavst.jeb.utils.renaming.RenameRequest 14 | 15 | private object GetterSetterUtils 16 | 17 | private val logger = GlobalLog.getLogger(GetterSetterUtils::class.java) 18 | 19 | fun propagateRenameToGetterAndSetters( 20 | unit: IDexUnit, 21 | classes: Iterable, 22 | renameEngine: RenameEngine, 23 | useOnlyModified: Boolean = true 24 | ) { 25 | val decompiler = unit.decompilerRef 26 | 27 | for (cls in classes) { 28 | logger.trace("Processing for getters/setters: ${cls.currentName}") 29 | for (method in cls.methods) { 30 | // Getter / setter should have a very minimal bytecode, with less than 10 instructions. 31 | if ((method?.data?.codeItem?.instructions?.size ?: Int.MAX_VALUE) > 10) 32 | continue 33 | val decompiledMethod = decompiler.decompileDexMethod(method) ?: continue 34 | processPossibleGetterSetter(method, decompiledMethod, cls, renameEngine, useOnlyModified) 35 | } 36 | } 37 | } 38 | 39 | /** rebuild getter and setters e.g void a(object o){this.a = o;} to void setA(object o); **/ 40 | private fun processPossibleGetterSetter( 41 | method: IDexMethod, 42 | decompiledMethod: IJavaMethod, 43 | cls: IDexClass, 44 | renameEngine: RenameEngine, 45 | useOnlyModified: Boolean 46 | ) { 47 | if (decompiledMethod.body.size() != 1) 48 | return 49 | else if (method.currentName.startsWith("get") || method.currentName.startsWith("set")) 50 | return 51 | 52 | when (val statement = decompiledMethod.body[0]) { 53 | is IJavaReturn -> { 54 | val right = statement.expression 55 | if (right is IJavaInstanceField) { 56 | if (right.field == null) { 57 | logger.warning("Field is null for ${method.currentSignature}") 58 | return 59 | } 60 | 61 | val name = right.field.originalName 62 | val currentName = right.field.currentName(cls) 63 | val (actualCurrentName, renameReason) = when { 64 | currentName != null && useOnlyModified -> renameEngine.getModifiedInfo(currentName) ?: return 65 | currentName != null && !useOnlyModified -> renameEngine.getModifiedInfo(currentName) ?: (name to RenameReason.FieldName) 66 | currentName == null && useOnlyModified -> return 67 | else -> name to RenameReason.FieldName 68 | } 69 | 70 | renameEngine.renameGetter( 71 | RenameRequest(actualCurrentName, renameReason ?: RenameReason.FieldName), 72 | method, 73 | cls 74 | ) 75 | } 76 | } 77 | is IJavaAssignment -> { 78 | val left = statement.left 79 | if (left is IJavaInstanceField) { 80 | if (left.field == null) { 81 | logger.warning("Field is null for ${method.currentSignature}") 82 | return 83 | } 84 | 85 | val name = left.field.originalName 86 | val currentName = left.field.currentName(cls) 87 | val (actualCurrentName, renameReason) = when { 88 | currentName != null && useOnlyModified -> renameEngine.getModifiedInfo(currentName) ?: return 89 | currentName != null && !useOnlyModified -> renameEngine.getModifiedInfo(currentName) ?: (name to RenameReason.FieldName) 90 | currentName == null && useOnlyModified -> return 91 | else -> name to RenameReason.FieldName 92 | } 93 | 94 | renameEngine.renameSetter( 95 | RenameRequest(actualCurrentName, renameReason ?: RenameReason.FieldName), 96 | method, 97 | cls 98 | ) 99 | } 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/LogUtils.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils 2 | 3 | import com.pnfsoftware.jeb.util.logging.GlobalLog 4 | import com.pnfsoftware.jeb.util.logging.ILogger 5 | import java.io.File 6 | import java.io.PrintStream 7 | 8 | fun ILogger.enableAllLogs(): ILogger = apply { 9 | enabledLevel = GlobalLog.LEVEL_ALL 10 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/MetadataUtils.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils 2 | 3 | import kotlinx.metadata.jvm.* 4 | 5 | // taken from https://github.com/mforlini/KMparse/blob/master/src/main/kotlin/io/github/mforlini/kmparse/Extensions.kt 6 | private const val INDENT = "\n| " 7 | const val KOTLIN_METADATA_COMMENT_PREFIX = "Kotlin metadata:" 8 | fun KotlinClassHeader.toStringBlock(): String { 9 | return when (val metadata = KotlinClassMetadata.read(this)) { 10 | is KotlinClassMetadata.Class -> { 11 | val klass = metadata.toKmClass() 12 | """$KOTLIN_METADATA_COMMENT_PREFIX 13 | |Type: Class 14 | |Class Info: 15 | | Name: ${klass.name} 16 | | Supertypes: ${klass.supertypes.joinToString(", ") { it.classifier.toString() }} 17 | | Module Name: ${klass.moduleName} 18 | | Type Aliases: ${klass.typeAliases.joinToString(", ")} 19 | | Companion Object: ${klass.companionObject ?: ""} 20 | | Nested Classes: ${klass.nestedClasses.joinToString(", ")} 21 | | Enum Entries: ${klass.enumEntries.joinToString(", ")} 22 | | 23 | |Constructors:${klass.constructors.joinToString(separator = INDENT, prefix = INDENT) { "${it.signature}, Arguments: ${it.valueParameters.joinToString(", ") { arg -> arg.name }}" }} 24 | | 25 | |Functions:${klass.functions.joinToString(separator = INDENT, prefix = INDENT) { "${it.signature}, Arguments: ${it.valueParameters.joinToString(", ") { arg -> arg.name }}" }} 26 | | 27 | |Properties:${klass.properties.joinToString(separator = INDENT, prefix = INDENT) { "${it.fieldSignature}" }} 28 | """.trimMargin() 29 | } 30 | is KotlinClassMetadata.FileFacade -> { 31 | val klass = metadata.toKmPackage() 32 | """$KOTLIN_METADATA_COMMENT_PREFIX 33 | |Type: File Facade 34 | | 35 | |Functions:${klass.functions.joinToString(separator = INDENT, prefix = INDENT) { "${it.signature}, Arguments: ${it.valueParameters.joinToString(", ") { arg -> arg.name }}" }} 36 | | 37 | |Properties:${klass.properties.joinToString(separator = INDENT, prefix = INDENT) { "${it.fieldSignature}" }} 38 | """.trimMargin() 39 | } 40 | is KotlinClassMetadata.SyntheticClass -> { 41 | if (metadata.isLambda) { 42 | val klass = metadata.toKmLambda() 43 | """$KOTLIN_METADATA_COMMENT_PREFIX 44 | |Type: Synthetic Class 45 | |Is Kotlin Lambda: True 46 | | 47 | |Functions: 48 | | ${klass?.function?.signature}, Arguments: ${klass?.function?.valueParameters?.joinToString(", ") { it.name }} 49 | 50 | """.trimMargin() 51 | 52 | } else { 53 | """$KOTLIN_METADATA_COMMENT_PREFIX 54 | |Type: Synthetic Class 55 | |Is Kotlin Lambda: False 56 | """.trimMargin() 57 | 58 | } 59 | } 60 | is KotlinClassMetadata.MultiFileClassFacade -> """$KOTLIN_METADATA_COMMENT_PREFIX 61 | |Type: Multi-File Class Facade 62 | |This multi-file class combines: 63 | |${metadata.partClassNames.joinToString(separator = INDENT, prefix = INDENT) { "Class: $it" }} 64 | """.trimMargin() 65 | is KotlinClassMetadata.MultiFileClassPart -> { 66 | val klass = metadata.toKmPackage() 67 | """$KOTLIN_METADATA_COMMENT_PREFIX 68 | |Type: Multi-File Class Part 69 | |Name: ${metadata.facadeClassName} 70 | | 71 | |Functions:${klass.functions.joinToString(separator = INDENT, prefix = INDENT) { "${it.signature}, Arguments: ${it.valueParameters.joinToString(", ") { arg -> arg.name }}" }} 72 | | 73 | |Properties:${klass.properties.joinToString(separator = INDENT, prefix = INDENT) { "${it.fieldSignature}" }} 74 | """.trimMargin() 75 | } 76 | is KotlinClassMetadata.Unknown -> """$KOTLIN_METADATA_COMMENT_PREFIX Type: Unknown""" 77 | null -> "" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/MethodUtils.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils 2 | 3 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 4 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexMethod 5 | import com.pnfsoftware.jeb.core.units.code.java.IJavaCall 6 | import com.pnfsoftware.jeb.core.units.code.java.IJavaExpression 7 | 8 | fun IJavaCall.getRealArgument(pos: Int): IJavaExpression = if (isStaticCall) getArgument(pos) else getArgument(pos + 1) 9 | 10 | val IDexMethod.isStatic: Boolean 11 | get() = data?.let { (data.accessFlags and IDexUnit.ACC_STATIC) != 0 } ?: false -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/NameUtils.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils 2 | 3 | import com.pnfsoftware.jeb.core.units.code.ICodeItem 4 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 5 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexClass 6 | import com.pnfsoftware.jeb.core.units.code.java.IJavaField 7 | import com.pnfsoftware.jeb.core.units.code.java.IJavaIdentifier 8 | 9 | val ICodeItem.originalName: String get() = getName(false) 10 | val ICodeItem.currentName: String get() = getName(true) 11 | val ICodeItem.originalSignature: String get() = getSignature(false) 12 | val ICodeItem.currentSignature: String get() = getSignature(true) 13 | 14 | 15 | val IJavaField.originalName: String get() = name 16 | fun IJavaField.currentName(cls: IDexClass): String? = cls.getField(false, originalName, type.signature)?.currentName 17 | fun IJavaField.currentName(unit: IDexUnit): String? = unit.getField(signature)?.currentName 18 | 19 | val IJavaIdentifier.originalName: String get() = name 20 | fun IJavaIdentifier.currentName(unit: IDexUnit) = unit.decompilerRef.getIdentifierName(this) ?: originalName -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/OptionsUtils.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils 2 | 3 | import com.pnfsoftware.jeb.core.BooleanOptionDefinition 4 | import com.pnfsoftware.jeb.core.IOptionDefinition 5 | import com.pnfsoftware.jeb.core.OptionDefinition 6 | 7 | const val ClassFilterDefault = ".*" 8 | 9 | const val ClassFilterOptionTag = "Class filter" 10 | val ClassFilterOption: IOptionDefinition = OptionDefinition( 11 | ClassFilterOptionTag, 12 | ClassFilterDefault, 13 | """The operation you are about to perform may be costly, and cannot be interrupted. 14 | If you only want to run it only on specific classes, you can specify a regex. 15 | For example, if you only want to run it on default package, use "^L[^\/]*;$" 16 | Or if you want to run it on package com.test use "^Lcom\/test\/.*;$" 17 | Default is to run it on all packages: "$ClassFilterDefault"""" 18 | ) 19 | 20 | const val ScopeThisClassTag = "Scope this class" 21 | fun scopeThisClass(default: Boolean, classSignature: String? = null): IOptionDefinition { 22 | return if (classSignature == null) { 23 | BooleanOptionDefinition( 24 | ScopeThisClassTag, default, "Run the given operation only on the selected class. Default: $default" 25 | ) 26 | } else { 27 | BooleanOptionDefinition( 28 | ScopeThisClassTag, 29 | default, 30 | "Run the given operation only on the selected class. Default: $default\nCurrent class: $classSignature" 31 | ) 32 | } 33 | } 34 | 35 | const val ScopeThisMethodTag = "Scope this method" 36 | fun scopeThisMethod(default: Boolean = true, methodSignature: String? = null): IOptionDefinition { 37 | return if (methodSignature == null) { 38 | BooleanOptionDefinition( 39 | ScopeThisMethodTag, default, "Run the given operation only on the selected method. Default: $default" 40 | ) 41 | } else { 42 | BooleanOptionDefinition( 43 | ScopeThisMethodTag, 44 | default, 45 | "Run the given operation only on the selected method. Default: $default\nCurrent method: $methodSignature" 46 | ) 47 | } 48 | } 49 | 50 | fun usingThisMethod(methodSignature: String? = null) = OptionDefinition("Selected method: $methodSignature") 51 | fun usingThisClass(methodSignature: String? = null) = OptionDefinition("Selected class: $methodSignature") -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/UIUtils.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils 2 | 3 | import com.pnfsoftware.jeb.util.logging.GlobalLog 4 | import org.eclipse.swt.widgets.Display 5 | import org.eclipse.swt.widgets.FileDialog 6 | 7 | private class UIUtils 8 | 9 | private val logger = GlobalLog.getLogger(UIUtils::class.java) 10 | 11 | fun displayFileOpenSelector(caption: String): String? { 12 | var result: String? = null 13 | Display.getDefault().syncExec { 14 | val shell = Display.getDefault()?.activeShell 15 | if (shell == null) { 16 | logger.error("No available SWT shells, cannot open file dialog.") 17 | return@syncExec 18 | } 19 | 20 | val dlg = FileDialog(shell, 4096) 21 | dlg.text = caption.orIfBlank("Open a file...") 22 | result = dlg.open() 23 | } 24 | return result 25 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/UnitUtils.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils 2 | 3 | import com.pnfsoftware.jeb.core.actions.ActionContext 4 | import com.pnfsoftware.jeb.core.actions.ActionXrefsData 5 | import com.pnfsoftware.jeb.core.actions.Actions 6 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 7 | import com.pnfsoftware.jeb.core.units.code.android.dex.DexPoolType 8 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexItem 9 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexString 10 | 11 | fun IDexUnit.xrefsFor(item: IDexItem): List { 12 | val data = ActionXrefsData() 13 | if (prepareExecution(ActionContext(this, Actions.QUERY_XREFS, item.itemId, item.address), data)) 14 | return data.addresses 15 | 16 | return emptyList() 17 | } 18 | 19 | fun IDexUnit.xrefsForString(string: IDexString): List { 20 | return referenceManager.getReferences(DexPoolType.STRING, string.index).map { it.internalAddress } 21 | } 22 | 23 | private val getComment = try { 24 | IDexUnit::class.java.getMethod("getComment", String::class.java) 25 | } catch (e: NoSuchMethodException) { 26 | IDexUnit::class.java.getMethod("getInlineComment", String::class.java) 27 | } 28 | 29 | private val setComment = try { 30 | IDexUnit::class.java.getMethod("setComment", String::class.java, String::class.java) 31 | } catch (e: NoSuchMethodException) { 32 | IDexUnit::class.java.getMethod("setInlineComment", String::class.java, String::class.java) 33 | } 34 | 35 | fun IDexUnit.getCommentBackport(address: String): String? = getComment(this, address) as? String 36 | fun IDexUnit.setCommentBackport(address: String, value: String) { 37 | setComment(this, address, value) 38 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/renaming/InternalRenameRequest.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils.renaming 2 | 3 | data class InternalRenameRequest( 4 | val type: RenameObjectType, 5 | val currentName: String, 6 | val newName: String, 7 | val reason: RenameReason, 8 | /** 9 | * Do we try to rename just to provide the user extra info about the class, 10 | * or we actually try to get the real name of the class **/ 11 | val informationalRename: Boolean = false 12 | ) { 13 | companion object { 14 | fun ofClass(currentName: String, newName: String, reason: RenameReason, informationalRename: Boolean = false) = 15 | InternalRenameRequest(RenameObjectType.Class, currentName, newName, reason, informationalRename) 16 | 17 | fun ofMethod(currentName: String, newName: String, reason: RenameReason, informationalRename: Boolean = false) = 18 | InternalRenameRequest(RenameObjectType.Method, currentName, newName, reason, informationalRename) 19 | 20 | fun ofField(currentName: String, newName: String, reason: RenameReason, informationalRename: Boolean = false) = 21 | InternalRenameRequest(RenameObjectType.Field, currentName, newName, reason, informationalRename) 22 | 23 | fun ofIdentifier(currentName: String, newName: String, reason: RenameReason, informationalRename: Boolean = false) = 24 | InternalRenameRequest(RenameObjectType.Identifier, currentName, newName, reason, informationalRename) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/renaming/RenameBackendEngine.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils.renaming 2 | 3 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 4 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexClass 5 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexField 6 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexMethod 7 | import com.pnfsoftware.jeb.core.units.code.java.IJavaField 8 | import com.pnfsoftware.jeb.core.units.code.java.IJavaIdentifier 9 | 10 | interface RenameBackendEngine { 11 | fun renameClass(renameRequest: InternalRenameRequest, cls: IDexClass): Boolean 12 | 13 | fun renameField(renameRequest: InternalRenameRequest, field: IJavaField, cls: IDexClass): Boolean 14 | fun renameField(renameRequest: InternalRenameRequest, field: IJavaField, unit: IDexUnit): Boolean 15 | fun renameField(renameRequest: InternalRenameRequest, field: IDexField): Boolean 16 | fun renameMethod(renameRequest: InternalRenameRequest, method: IDexMethod, cls: IDexClass): Boolean 17 | fun renameIdentifier(renameRequest: InternalRenameRequest, identifier: IJavaIdentifier, unit: IDexUnit): Boolean 18 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/renaming/RenameBackendEngineImpl.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils.renaming 2 | 3 | import com.pnfsoftware.jeb.core.units.code.android.IDexDecompilerUnit 4 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 5 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexClass 6 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexField 7 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexMethod 8 | import com.pnfsoftware.jeb.core.units.code.java.IJavaField 9 | import com.pnfsoftware.jeb.core.units.code.java.IJavaIdentifier 10 | import com.pnfsoftware.jeb.util.logging.GlobalLog 11 | import com.yoavst.jeb.utils.currentName 12 | import com.yoavst.jeb.utils.decompilerRef 13 | import com.yoavst.jeb.utils.originalName 14 | import com.yoavst.jeb.utils.originalSignature 15 | 16 | object RenameBackendEngineImpl : RenameBackendEngine { 17 | private val logger = GlobalLog.getLogger(javaClass) 18 | private val decompilerCache: MutableMap = mutableMapOf() 19 | 20 | override fun renameClass(renameRequest: InternalRenameRequest, cls: IDexClass): Boolean { 21 | if (cls.setName(renameRequest.newName)) { 22 | logger.debug("${cls.currentName}: Renamed to ${renameRequest.newName}") 23 | return true 24 | } 25 | return false 26 | } 27 | 28 | 29 | override fun renameField(renameRequest: InternalRenameRequest, field: IJavaField, cls: IDexClass): Boolean { 30 | val dexField = cls.getField(false, field.originalName, field.type.signature) 31 | if (dexField == null) { 32 | logger.warning("Error: Failed to get dex field. ${cls.currentName}::${field.originalName} -> ${renameRequest.newName}") 33 | return false 34 | } 35 | if (renameField(renameRequest, dexField)) { 36 | logger.debug("${cls.currentName}: Field ${dexField.originalName} Renamed to ${renameRequest.newName}") 37 | return true 38 | } 39 | return false 40 | } 41 | 42 | override fun renameField(renameRequest: InternalRenameRequest, field: IJavaField, unit: IDexUnit): Boolean { 43 | val dexField = unit.getField(field.signature) 44 | if (dexField == null) { 45 | logger.warning("Error: Failed to get dex field from unit. ::${field.originalName} -> ${renameRequest.newName}") 46 | return false 47 | } 48 | if (renameField(renameRequest, dexField)) { 49 | val cls = dexField.classType?.implementingClass 50 | if (cls == null) { 51 | logger.debug("Field ${dexField.originalSignature} Renamed to ${renameRequest.newName}") 52 | } else { 53 | logger.debug("${cls.currentName}: Field ${dexField.originalName} Renamed to ${renameRequest.newName}") 54 | } 55 | return true 56 | } 57 | return false 58 | } 59 | 60 | override fun renameField(renameRequest: InternalRenameRequest, field: IDexField): Boolean = 61 | field.setName(renameRequest.newName) 62 | 63 | override fun renameMethod(renameRequest: InternalRenameRequest, method: IDexMethod, cls: IDexClass): Boolean { 64 | if (method.setName(renameRequest.newName)) { 65 | logger.debug("${cls.currentName}: Renamed method ${method.originalName} to ${renameRequest.newName}") 66 | return true 67 | } 68 | return false 69 | } 70 | 71 | override fun renameIdentifier( 72 | renameRequest: InternalRenameRequest, 73 | identifier: IJavaIdentifier, 74 | unit: IDexUnit 75 | ): Boolean { 76 | val decompiler = decompilerCache.getOrPut(unit, unit::decompilerRef) 77 | if (decompiler.setIdentifierName(identifier, renameRequest.newName)) { 78 | logger.debug("Renamed identifier $identifier to ${renameRequest.newName}") 79 | return true 80 | } 81 | return false 82 | } 83 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/renaming/RenameEngine.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils.renaming 2 | 3 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 4 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexClass 5 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexField 6 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexMethod 7 | import com.pnfsoftware.jeb.core.units.code.java.IJavaField 8 | import com.pnfsoftware.jeb.core.units.code.java.IJavaIdentifier 9 | 10 | interface RenameEngine { 11 | val stats: RenameStats 12 | 13 | fun renameClass(renameRequest: RenameRequest, cls: IDexClass) 14 | fun renameField(renameRequest: RenameRequest, field: IJavaField, cls: IDexClass) 15 | fun renameField(renameRequest: RenameRequest, field: IDexField, cls: IDexClass) 16 | fun renameMethod(renameRequest: RenameRequest, method: IDexMethod, cls: IDexClass) 17 | fun renameGetter(renameRequest: RenameRequest, method: IDexMethod, cls: IDexClass) 18 | fun renameSetter(renameRequest: RenameRequest, method: IDexMethod, cls: IDexClass) 19 | fun renameIdentifier(renameRequest: RenameRequest, identifier: IJavaIdentifier, unit: IDexUnit) 20 | 21 | fun getModifiedInfo(name: String): Pair? 22 | 23 | companion object { 24 | fun create() = RenameEngineImpl(RenameFrontendEngineImpl, RenameBackendEngineImpl) 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/renaming/RenameEngineImpl.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils.renaming 2 | 3 | import com.pnfsoftware.jeb.core.units.code.android.IDexUnit 4 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexClass 5 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexField 6 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexMethod 7 | import com.pnfsoftware.jeb.core.units.code.java.IJavaField 8 | import com.pnfsoftware.jeb.core.units.code.java.IJavaIdentifier 9 | import com.yoavst.jeb.utils.currentName 10 | import java.util.* 11 | 12 | class RenameEngineImpl( 13 | private val frontendEngine: RenameFrontendEngine, 14 | private val backendEngine: RenameBackendEngine 15 | ) : RenameEngine { 16 | override val stats: RenameStats = RenameStats() 17 | override fun renameClass(renameRequest: RenameRequest, cls: IDexClass) { 18 | val internalRenameRequest = InternalRenameRequest.ofClass( 19 | cls.currentName, 20 | renameRequest.newName, 21 | renameRequest.reason, 22 | renameRequest.informationalRename 23 | ) 24 | val finalRenameRequest = frontendEngine.applyRules(internalRenameRequest) ?: return 25 | if (backendEngine.renameClass(finalRenameRequest, cls)) { 26 | stats.renamedClasses[cls] = renameRequest 27 | stats.effectedClasses.add(cls) 28 | } 29 | } 30 | 31 | override fun renameField(renameRequest: RenameRequest, field: IJavaField, cls: IDexClass) { 32 | val name = field.currentName(cls) ?: return 33 | val internalRenameRequest = InternalRenameRequest.ofField( 34 | name, 35 | renameRequest.newName, 36 | renameRequest.reason, 37 | renameRequest.informationalRename 38 | ) 39 | val finalRenameRequest = frontendEngine.applyRules(internalRenameRequest) ?: return 40 | if (backendEngine.renameField(finalRenameRequest, field, cls)) { 41 | stats.renamedFields[cls.dex.getField(field.signature)] = renameRequest 42 | stats.effectedClasses.add(cls) 43 | } 44 | } 45 | 46 | override fun renameField(renameRequest: RenameRequest, field: IDexField, cls: IDexClass) { 47 | val name = field.currentName 48 | val internalRenameRequest = InternalRenameRequest.ofField( 49 | name, 50 | renameRequest.newName, 51 | renameRequest.reason, 52 | renameRequest.informationalRename 53 | ) 54 | val finalRenameRequest = frontendEngine.applyRules(internalRenameRequest) ?: return 55 | if (backendEngine.renameField(finalRenameRequest, field)) { 56 | stats.renamedFields[field] = renameRequest 57 | stats.effectedClasses.add(cls) 58 | } 59 | } 60 | 61 | override fun renameMethod(renameRequest: RenameRequest, method: IDexMethod, cls: IDexClass) { 62 | val internalRenameRequest = InternalRenameRequest.ofMethod( 63 | method.currentName, 64 | renameRequest.newName, 65 | renameRequest.reason, 66 | renameRequest.informationalRename 67 | ) 68 | val finalRenameRequest = frontendEngine.applyRules(internalRenameRequest) ?: return 69 | if (backendEngine.renameMethod(finalRenameRequest, method, cls)) { 70 | stats.renamedMethods[method] = renameRequest 71 | stats.effectedClasses.add(cls) 72 | } 73 | } 74 | 75 | override fun renameIdentifier(renameRequest: RenameRequest, identifier: IJavaIdentifier, unit: IDexUnit) { 76 | val internalRenameRequest = InternalRenameRequest.ofIdentifier( 77 | identifier.currentName(unit), 78 | renameRequest.newName, 79 | renameRequest.reason, 80 | renameRequest.informationalRename 81 | ) 82 | val finalRenameRequest = frontendEngine.applyRules(internalRenameRequest) ?: return 83 | if (backendEngine.renameIdentifier(finalRenameRequest, identifier, unit)) { 84 | stats.renamedIdentifiers[identifier] = renameRequest 85 | // no class is effected since it is identifier 86 | } 87 | } 88 | 89 | override fun getModifiedInfo(name: String) = frontendEngine.getModifiedInfo(name) 90 | 91 | override fun renameGetter(renameRequest: RenameRequest, method: IDexMethod, cls: IDexClass) { 92 | val preInternalNameRequest = InternalRenameRequest.ofIdentifier( 93 | method.currentName, 94 | renameRequest.newName, 95 | renameRequest.reason, 96 | renameRequest.informationalRename 97 | ) 98 | frontendEngine.applyRules(preInternalNameRequest) ?: return 99 | // now for the real request: 100 | val internalNameRequest = InternalRenameRequest.ofIdentifier( 101 | method.currentName, 102 | "get" + renameRequest.newName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }, 103 | renameRequest.reason, 104 | renameRequest.informationalRename 105 | ) 106 | val finalRequest = frontendEngine.applyRules(internalNameRequest) ?: return 107 | if (backendEngine.renameMethod(finalRequest, method, cls)) { 108 | stats.renamedMethods[method] = renameRequest 109 | stats.effectedClasses.add(cls) 110 | } 111 | } 112 | 113 | override fun renameSetter(renameRequest: RenameRequest, method: IDexMethod, cls: IDexClass) { 114 | val preInternalNameRequest = InternalRenameRequest.ofIdentifier( 115 | method.currentName, 116 | renameRequest.newName, 117 | renameRequest.reason, 118 | renameRequest.informationalRename 119 | ) 120 | frontendEngine.applyRules(preInternalNameRequest) ?: return 121 | // now for the real request: 122 | val internalNameRequest = InternalRenameRequest.ofIdentifier( 123 | method.currentName, 124 | "set" + renameRequest.newName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }, 125 | renameRequest.reason, 126 | renameRequest.informationalRename 127 | ) 128 | val finalRequest = frontendEngine.applyRules(internalNameRequest) ?: return 129 | if (backendEngine.renameMethod(finalRequest, method, cls)) { 130 | stats.renamedMethods[method] = renameRequest 131 | stats.effectedClasses.add(cls) 132 | } 133 | } 134 | 135 | override fun toString(): String = stats.toString() 136 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/renaming/RenameFrontendEngine.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils.renaming 2 | 3 | interface RenameFrontendEngine { 4 | /** 5 | * Process the renaming request and return a rename request with the final name to be applied. 6 | * Returns null if the request is denied 7 | */ 8 | fun applyRules(renameRequest: InternalRenameRequest): InternalRenameRequest? 9 | 10 | /** 11 | * Given a modified name, return the pair (modifiedName, renameReason?) 12 | */ 13 | fun getModifiedInfo(name: String): Pair? 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/renaming/RenameFrontendEngineImpl.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils.renaming 2 | 3 | object RenameFrontendEngineImpl : RenameFrontendEngine { 4 | private val PreservedShortNames = setOf("run", "act", "get", "set", "let") 5 | 6 | override fun applyRules(renameRequest: InternalRenameRequest): InternalRenameRequest? { 7 | val (type, currentName, newName, reason, informationalRename) = renameRequest 8 | val newNameSanitized = newName.sanitize() 9 | if (currentName.isBlank()) { 10 | return renameRequest 11 | } else if (!generalShouldRename(currentName, newNameSanitized, reason)) { 12 | return null 13 | } else if (!informationalRename && currentName.length > 3) { 14 | when (type) { 15 | RenameObjectType.Class -> { 16 | // check for CamelCase 17 | if (currentName[0].isUpperCase() && currentName.any(Char::isLowerCase)) 18 | return null 19 | } 20 | RenameObjectType.Method -> { 21 | // is it getter or setter or constructor 22 | if (currentName.startsWith("get") || currentName.startsWith("set") || 23 | currentName == "" || currentName == "" 24 | ) 25 | return null 26 | // is it a camelCase? 27 | if (currentName[0].isLowerCase() && currentName.any(Char::isUpperCase)) 28 | return null 29 | } 30 | RenameObjectType.Field -> { 31 | // check for mVariable or sVariable 32 | if ((currentName[0] == 'm' || currentName[0] == 's') && currentName[1].isUpperCase()) 33 | return null 34 | } 35 | RenameObjectType.Identifier -> { 36 | if (currentName[0].isLowerCase() && currentName.any(Char::isUpperCase)) 37 | return null 38 | } 39 | } 40 | } 41 | 42 | // check if currentName is a valid java name 43 | val currentModifiedName = if (currentName.isValidJavaIdentifier()) currentName else "$$currentName" 44 | 45 | if (informationalRename) { 46 | // we want to keep the current name 47 | return InternalRenameRequest( 48 | type, 49 | currentName, 50 | "${currentModifiedName}__${reason.prefix}_${newNameSanitized}", 51 | reason, 52 | informationalRename 53 | ) 54 | } else if (type == RenameObjectType.Identifier) { 55 | // We want to erase the previous name since it isn't a real name 56 | return InternalRenameRequest( 57 | type, 58 | currentName, 59 | "__${reason.prefix}_${newNameSanitized}", 60 | reason, 61 | informationalRename 62 | ) 63 | } else { 64 | return InternalRenameRequest( 65 | type, 66 | currentName, 67 | "${currentModifiedName.extractOriginalName()}__${reason.prefix}_${newNameSanitized}", 68 | reason, 69 | informationalRename 70 | ) 71 | } 72 | 73 | } 74 | 75 | /** Common rules for whether should rename from `name` to `newName` */ 76 | private fun generalShouldRename(name: String, newName: String, reason: RenameReason): Boolean { 77 | if (newName.isBlank()) 78 | return false 79 | 80 | if (name.contains(newName, ignoreCase = true)) 81 | return false 82 | 83 | if (name.length <= 3) { 84 | return name !in PreservedShortNames 85 | } 86 | 87 | if (newName.contains(name, ignoreCase = true)) 88 | return false 89 | 90 | if ('$' in name) 91 | return false 92 | 93 | val oldReason = getModifiedInfo(name)?.second ?: return true 94 | return reason >= oldReason 95 | } 96 | 97 | /** 98 | * Tries to extract the rename reason encoded in the given `name`. 99 | * It assumes normal name does not have an underscore in its name. 100 | * This is probably justified since this is against Java naming convention. 101 | * Some generated classes may contain __ in name, but that mean 102 | * they have the original name, so it's ok. 103 | **/ 104 | override fun getModifiedInfo(name: String): Pair? { 105 | var nameParts = name.split("__", limit = 2) 106 | if (nameParts.size == 1) { 107 | nameParts = name.split("_", limit = 2) 108 | if (nameParts.size == 1) { 109 | return null 110 | } 111 | // assume there is no reason for _ to be in name we want to rename. 112 | // The only reason it actually make sense is if we try to rename constant. 113 | // And in this case, it's ok to fail since we probably want to retain the original name. 114 | return nameParts[1] to null 115 | } 116 | val generatedNameParts = nameParts[1].split("_", limit = 2) 117 | if (generatedNameParts.size == 1) { 118 | // The user was too lazy to remove the extra underscore, ok... 119 | return generatedNameParts[0] to null 120 | } 121 | val prefix = generatedNameParts[0] 122 | if (RenameReason.MAX_PREFIX_LENGTH < prefix.length) return null 123 | return generatedNameParts[1] to RenameReason.fromPrefix(prefix) 124 | } 125 | 126 | private fun String.extractOriginalName(): String { 127 | var nameParts = split("__", limit = 2) 128 | if (nameParts.size == 1) { 129 | nameParts = split("_", limit = 2) 130 | } 131 | return nameParts[0] 132 | } 133 | 134 | private fun String.isValidJavaIdentifier(): Boolean { 135 | if (isEmpty()) { 136 | return false 137 | } 138 | if (!this[0].isJavaIdentifierStart()) { 139 | return false 140 | } 141 | for (i in 1 until length) { 142 | if (!this[i].isJavaIdentifierPart()) { 143 | return false 144 | } 145 | } 146 | return true 147 | } 148 | 149 | private fun String.sanitize(): String = trim().map { if (it.isJavaIdentifierPart()) it else "_" }.joinToString("") 150 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/renaming/RenameObjectType.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils.renaming 2 | 3 | /** Represents the type of object we want to rename **/ 4 | enum class RenameObjectType { 5 | Class, 6 | Method, 7 | Field, 8 | Identifier, 9 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/renaming/RenameReason.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils.renaming 2 | 3 | /** 4 | * Represents a reason for a renaming request. 5 | * The higher the ordinal, the bigger priority it has. 6 | */ 7 | enum class RenameReason(val prefix: String) : Comparable { 8 | Type("T"), 9 | SourceFile("SF"), 10 | FieldName("F"), 11 | Log("L"), 12 | Resource("R"), 13 | MethodStringArgument("A"), 14 | ToString("TS"), 15 | EnumName("E"), 16 | KotlinName("KT"), 17 | /** The user renamed the class. Should not be used as a reason */ 18 | User(""); 19 | 20 | companion object { 21 | const val MAX_PREFIX_LENGTH = 2 22 | /** Allocate it once for performance reason */ 23 | private val VALUES = values() 24 | 25 | fun fromPrefix(prefix: String) = VALUES.firstOrNull { it.prefix == prefix } 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/renaming/RenameRequest.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils.renaming 2 | 3 | data class RenameRequest( 4 | val newName: String, 5 | val reason: RenameReason, 6 | /** 7 | * Do we try to rename just to provide the user extra info about the class, 8 | * or we actually try to get the real name of the class **/ 9 | val informationalRename: Boolean = false 10 | ) 11 | -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/renaming/RenameStats.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils.renaming 2 | 3 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexClass 4 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexField 5 | import com.pnfsoftware.jeb.core.units.code.android.dex.IDexMethod 6 | import com.pnfsoftware.jeb.core.units.code.java.IJavaIdentifier 7 | 8 | class RenameStats { 9 | val renamedClasses: MutableMap = mutableMapOf() 10 | val renamedMethods: MutableMap = mutableMapOf() 11 | val renamedFields: MutableMap = mutableMapOf() 12 | val renamedIdentifiers: MutableMap = mutableMapOf() 13 | val effectedClasses: MutableSet = mutableSetOf() 14 | 15 | fun clear() { 16 | renamedClasses.clear() 17 | renamedMethods.clear() 18 | renamedFields.clear() 19 | renamedIdentifiers.clear() 20 | effectedClasses.clear() 21 | } 22 | 23 | override fun toString(): String = buildString { 24 | appendLine("Stats:") 25 | if (renamedClasses.isNotEmpty()) { 26 | append(" Classes: ${renamedClasses.size}") 27 | } 28 | if (renamedMethods.isNotEmpty()) { 29 | appendLine(" Methods: ${renamedMethods.size}") 30 | } 31 | if (renamedFields.isNotEmpty()) { 32 | append(" Fields: ${renamedFields.size}") 33 | } 34 | if (renamedIdentifiers.isNotEmpty()) { 35 | append(" Identifiers: ${renamedIdentifiers.size}") 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/script/UIUtils.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils.script 2 | 3 | import com.pnfsoftware.jeb.core.IEnginesContext 4 | import com.pnfsoftware.jeb.util.logging.GlobalLog 5 | import com.yoavst.jeb.utils.BasicEnginesPlugin 6 | import org.eclipse.swt.widgets.Display 7 | 8 | object UIUtils { 9 | private val logger = GlobalLog.getLogger(UIUtils::class.java) 10 | 11 | private const val OptionsDialogClass = "com.pnfsoftware.jeb.rcpclient.dialogs.OptionsForEnginesPluginDialog" 12 | 13 | fun openOptionsForPlugin(plugin: BasicEnginesPlugin, ctx: IEnginesContext): Map? { 14 | val shell = Display.getDefault()?.activeShell 15 | if (shell == null) { 16 | logger.error("No available SWT shells, cannot open options dialog.") 17 | return null 18 | } 19 | try { 20 | val dialogCls = Class.forName(OptionsDialogClass) 21 | val dialog = dialogCls.constructors[0].newInstance(shell, ctx, plugin) 22 | @Suppress("UNCHECKED_CAST") 23 | return (dialogCls.getMethod("open").invoke(dialog) as? Map) ?: emptyMap() 24 | } catch (e: ClassNotFoundException) { 25 | logger.catching(e, "Dialog class is not available in current version of JEB") 26 | } catch (e: ReflectiveOperationException) { 27 | logger.catching(e, "Could not initiate the options dialog. Probably unsupported jeb version") 28 | } catch (e: IllegalArgumentException) { 29 | try { 30 | val dialogCls = Class.forName(OptionsDialogClass) 31 | val dialog = dialogCls.constructors[0].newInstance(shell, plugin) 32 | @Suppress("UNCHECKED_CAST") 33 | return (dialogCls.getMethod("open").invoke(dialog) as? Map) ?: emptyMap() 34 | } catch (e: ClassNotFoundException) { 35 | logger.catching(e, "Dialog class is not available in current version of JEB") 36 | } catch (e: ReflectiveOperationException) { 37 | logger.catching(e, "Could not initiate the options dialog. Probably unsupported jeb version") 38 | } 39 | } 40 | return null 41 | } 42 | 43 | fun runOnMainThread(runnable: Runnable) = Display.getDefault().asyncExec(runnable) 44 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/yoavst/jeb/utils/utils.kt: -------------------------------------------------------------------------------- 1 | package com.yoavst.jeb.utils 2 | 3 | import org.apache.commons.text.StringEscapeUtils 4 | 5 | 6 | fun String?.orIfBlank(other: String) = if (isNullOrBlank()) other else this 7 | inline fun Sequence.mapToPair(crossinline f: (T) -> U): Sequence> = map { it to f(it) } 8 | inline fun Sequence.mapToPairNotNull(crossinline f: (T) -> U?): Sequence> = mapNotNull { 9 | val res = f(it) 10 | if (res == null) null else it to res 11 | } 12 | 13 | fun String.unescape(): String = StringEscapeUtils.unescapeJava(this) -------------------------------------------------------------------------------- /src/main/python/ClassSearch.py: -------------------------------------------------------------------------------- 1 | from com.pnfsoftware.jeb.client.api import IScript, IGraphicalClientContext 2 | from com.pnfsoftware.jeb.core.units.code.android import IDexUnit 3 | from com.pnfsoftware.jeb.core import RuntimeProjectUtil 4 | from com.pnfsoftware.jeb.core.units.code.java import IJavaSourceUnit 5 | 6 | classes = None 7 | project = None 8 | 9 | class ClassSearch(IScript): 10 | def run(self, ctx): 11 | global classes, project 12 | 13 | assert isinstance(ctx, IGraphicalClientContext), 'This script must be run within a graphical client' 14 | 15 | project = ctx.getEnginesContext().getProjects()[0] 16 | unit = RuntimeProjectUtil.findUnitsByType(project, IDexUnit, False)[0] 17 | assert isinstance(unit, IDexUnit), 'Unit must be IDexUnit' 18 | 19 | headers = ['Name', 'Original name', 'Package'] 20 | rows = [] 21 | 22 | classes = unit.classes 23 | for clazz in classes: 24 | if clazz.package is None or clazz.package.name is None: 25 | pkg = '' 26 | else: 27 | pkg = clazz.package.getSignature(True) 28 | 29 | rows.append([clazz.getName(True), 'L' + clazz.getName(False) + ';', pkg]) 30 | 31 | index = displayListModeless(ctx, 'Classes', 'go to class', headers, rows) 32 | if index < 0: 33 | return 34 | 35 | selected_class = classes[index] 36 | 37 | # Help GC 38 | classes = None 39 | 40 | # Try navigate to java, if fail try disassembly 41 | java_unit = RuntimeProjectUtil.findUnitsByType(project, IJavaSourceUnit, False).get(0) 42 | if java_unit is None: 43 | ctx.navigate(unit, selected_class.getAddress(True)) 44 | else: 45 | ctx.navigate(java_unit, selected_class.getAddress(True)) 46 | 47 | 48 | 49 | # ---------------------------------------------------------------------- 50 | from com.pnfsoftware.jeb.rcpclient.dialogs import DataFrameDialog 51 | from com.pnfsoftware.jeb.rcpclient.util import DataFrame 52 | 53 | class GotoClassWindow(DataFrameDialog): 54 | def __init__(self, ctx, title, subtitle): 55 | DataFrameDialog.__init__(self, None, title, False, None) 56 | self.ctx = ctx 57 | self.displayIndex = True 58 | self.message = subtitle 59 | if self.hasInstance(): 60 | raise ValueError("Can only have single instance") 61 | 62 | def createButtons(self, composite): 63 | self.super__createButtons(composite, [0x20, 0x100, 0x30000000], 0x20) 64 | self.hideButton(0x20) 65 | 66 | def getButtonText(self, v, s): 67 | if v == 0x100: 68 | return "Close" 69 | elif v == 0x30000000: 70 | return "Navigate" 71 | else: 72 | return self.super__getButtonText(v, s) 73 | 74 | def onButtonClick(self, v): 75 | if v == 0x20: 76 | self.nav() 77 | self.super__onButtonClick(0x20) 78 | elif v == 0x30000000: 79 | self.nav() 80 | else: 81 | self.super__onButtonClick(v) 82 | 83 | def onConfirm(self): 84 | self.nav() 85 | 86 | def nav(self): 87 | global classes 88 | 89 | v = self.selectedRow 90 | if v < 0 or v >= len(classes): 91 | return 92 | 93 | selected_class = classes[v] 94 | 95 | java_units = RuntimeProjectUtil.findUnitsByType(project, IJavaSourceUnit, False) 96 | java_unit = java_units[0] if java_units else None 97 | if java_unit is None: 98 | unit = RuntimeProjectUtil.findUnitsByType(project, IDexUnit, False).get(0) 99 | self.ctx.navigate(unit, selected_class.getAddress(True)) 100 | else: 101 | self.ctx.navigate(java_unit, selected_class.getAddress(True)) 102 | 103 | 104 | def displayListModeless(ctx, title, subtitle, header, values): 105 | dialog = GotoClassWindow(ctx, title, subtitle) 106 | 107 | frame = DataFrame(header) 108 | for value in values: 109 | frame.addRow(value) 110 | 111 | dialog.dataFrame = frame 112 | return dialog.open() 113 | 114 | 115 | -------------------------------------------------------------------------------- /src/main/python/FridaHook.py: -------------------------------------------------------------------------------- 1 | #?description=update the UIBridge class of JebPlugin 2 | #?shortcut=Mod1+Mod3+F 3 | #?author=LeadroyaL With modification by Yoav Sternberg 4 | 5 | from com.pnfsoftware.jeb.client.api import IScript, IGraphicalClientContext 6 | from com.pnfsoftware.jeb.core import IEnginesPlugin 7 | from com.pnfsoftware.jeb.core.units.code.android.dex import IDexMethod, IDexType 8 | from org.eclipse.swt.dnd import Clipboard, TextTransfer, Transfer 9 | from org.eclipse.swt.widgets import Display 10 | 11 | from java.lang import Object 12 | from jarray import array 13 | 14 | import utils 15 | 16 | 17 | class FridaHook(IScript): 18 | def run(self, ctx): 19 | if not isinstance(ctx, IGraphicalClientContext): 20 | print ('This script must be run within a graphical client') 21 | return 22 | 23 | plugins = ctx.getEnginesContext().getEnginesPlugins() 24 | for plugin in plugins: 25 | assert isinstance(plugin, IEnginesPlugin) 26 | if 'yoav' in plugin.getClass().getCanonicalName(): 27 | classloader = plugin.getClass().getClassLoader() 28 | 29 | # Update focused method 30 | UIBridge = utils.get_object("com.yoavst.jeb.bridge.UIBridge", classloader) 31 | UIBridge.update(ctx) 32 | 33 | # Get focused method 34 | dex_method = UIBridge.getFocusedMethod() 35 | if not dex_method: 36 | print "No selected method" 37 | return 38 | break 39 | else: 40 | print "JebOps is not installed" 41 | return 42 | 43 | assert isinstance(dex_method, IDexMethod) 44 | clz = dex_method.getClassType().getSignature(False)[1:-1].replace('/', '.') 45 | method = dex_method.getName(False) 46 | method = BasicMethodMap.get(method, method) 47 | sig = dex_method.getSignature(True) 48 | 49 | params_list = ', '.join('"{}"'.format(self.signature_to_frida_signature(t.getSignature(False))) for t in 50 | dex_method.getParameterTypes()) 51 | args = [] 52 | for t in dex_method.getParameterTypes(): 53 | args.append(self.type_to_pretty_name(t, args)) 54 | args_list = ', '.join(args) 55 | 56 | fmt = FMT_VOID if dex_method.getReturnType().getSignature() == "V" else FMT_RET 57 | result = fmt.format( 58 | class_name=clz, 59 | method_name=method, 60 | method_sig=sig, 61 | param_list=params_list, 62 | args_list=args_list) 63 | print result 64 | 65 | Clipboard(Display.getDefault()).setContents(array([result], Object), array([TextTransfer.getInstance()], Transfer)) 66 | 67 | def signature_to_frida_signature(self, sig): 68 | if not sig: 69 | print "Error: received empty type" 70 | return "" 71 | 72 | if sig[0] == '[': 73 | # Dealing with array. In this case, we only need to replace "/" with "." 74 | # input: [I, return: "[I" 75 | # input: [Ljava/lang/String; return: "[Ljava.lang.String;" 76 | return sig.replace('/', '.') 77 | elif sig[0] == "L": 78 | # Non primitive type, should just fix "/" and "." 79 | # input: Ljava/lang/String; return: "java.lang.String" 80 | return sig[1:-1].replace('/', '.') 81 | else: 82 | # Primitive type, map it to frida name 83 | # input: I, return: "int" 84 | return BasicTypeMap[sig] 85 | 86 | def type_to_pretty_name(self, t, history=None): 87 | assert isinstance(t, IDexType) 88 | name = t.getName(True).split("__")[-1].split("_", 1)[-1] 89 | 90 | if name == "Object": 91 | wanted_name = "arg" 92 | elif len(name) >= 4: 93 | wanted_name = decaptialize(name) 94 | elif t.getImplementingClass() is None: 95 | wanted_name = BasicTypeMap.get(t.getName(), "arg") 96 | else: 97 | # Try parent class 98 | clz = t.getImplementingClass() 99 | if clz.getSupertypes(): 100 | wanted_name = self.type_to_pretty_name(clz.getSupertypes()[0], history) 101 | if wanted_name.startswith("arg"): 102 | # Try interfaces 103 | for interface in clz.getImplementedInterfaces(): 104 | wanted_name = self.type_to_pretty_name(interface, history) 105 | if not wanted_name.startswith("arg"): 106 | return wanted_name 107 | else: 108 | wanted_name = "arg" 109 | 110 | if history is None: 111 | history = [] 112 | if wanted_name not in history: 113 | return wanted_name 114 | i = 1 115 | while True: 116 | new_name = wanted_name + str(i) 117 | if new_name not in history: 118 | return new_name 119 | i += 1 120 | 121 | 122 | def decaptialize(name): 123 | if not name: 124 | return "" 125 | return name[0].lower() + name[1:] 126 | 127 | 128 | FMT_RET = """Java.use("{class_name}") 129 | .{method_name} 130 | .overload({param_list}) 131 | .implementation = function ({args_list}) {{ 132 | console.log("before hooked {method_sig}") 133 | let ret = this.{method_name}({args_list}) 134 | console.log("after hooked {method_sig}") 135 | return ret; 136 | }};""" 137 | FMT_VOID = """Java.use("{class_name}") 138 | .{method_name} 139 | .overload({param_list}) 140 | .implementation = function ({args_list}) {{ 141 | console.log("before hooked {method_sig}") 142 | this.{method_name}({args_list}) 143 | console.log("after hooked {method_sig}") 144 | }};""" 145 | 146 | BasicTypeMap = { 147 | 'C': u'char', 148 | 'B': u'byte', 149 | 'D': u'double', 150 | 'F': u'float', 151 | 'I': u'int', 152 | 'J': u'long', 153 | 'L': u'ClassName', 154 | 'S': u'short', 155 | 'Z': u'boolean', 156 | 'V': u'void', 157 | } 158 | 159 | BasicMethodMap = { 160 | '': u'$init', 161 | } 162 | -------------------------------------------------------------------------------- /src/main/python/JarLoader.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import traceback 3 | from com.pnfsoftware.jeb.client.api import IScript, IClientContext 4 | from com.pnfsoftware.jeb.core import AbstractEnginesPlugin 5 | from java.io import File 6 | from java.lang import Class 7 | from java.net import URLClassLoader 8 | 9 | import utils 10 | from debug import CLASS, JAR_PATH 11 | 12 | 13 | class JarLoader(IScript): 14 | def run(self, ctx): 15 | try: 16 | assert isinstance(ctx, IClientContext) 17 | loader = URLClassLoader([File(JAR_PATH).toURI().toURL()], self.getClass().getClassLoader()) 18 | instance = Class.forName(CLASS, True, loader).newInstance() 19 | assert isinstance(instance, AbstractEnginesPlugin) 20 | 21 | # 1. update ui bridge 22 | UIBridge = utils.get_object("com.yoavst.jeb.bridge.UIBridge", loader) 23 | UIBridge.update(ctx) 24 | print UIBridge 25 | 26 | # 2. Launch plugin 27 | utils.launch_plugin(instance, ctx, loader) 28 | return 29 | except: 30 | traceback.print_exc(file=sys.stdout) 31 | -------------------------------------------------------------------------------- /src/main/python/MethodsOfClass.py: -------------------------------------------------------------------------------- 1 | #?description=get all methods of specific class 2 | import sys 3 | import traceback 4 | from com.pnfsoftware.jeb.client.api import IScript, IGraphicalClientContext 5 | from com.pnfsoftware.jeb.core import RuntimeProjectUtil 6 | from com.pnfsoftware.jeb.core.units.code.android import IDexUnit 7 | from com.pnfsoftware.jeb.core.units.code.android.dex import IDexMethod 8 | 9 | 10 | class MethodsOfClass(IScript): 11 | def run(self, ctx): 12 | assert isinstance(ctx, IGraphicalClientContext) 13 | class_name = ctx.displayQuestionBox("Get class methods", "What is the class?", "") 14 | if not class_name or not class_name.strip(): 15 | return 16 | filter_text = ctx.displayQuestionBox("Get class methods", "The method Should contain", "").strip() or "" 17 | 18 | try: 19 | unit = RuntimeProjectUtil.findUnitsByType(ctx.getEnginesContext().getProjects()[0], IDexUnit, False)[0] 20 | assert isinstance(unit, IDexUnit) 21 | for method in unit.getMethods(): 22 | assert isinstance(method, IDexMethod) 23 | if method.getSignature().startswith(class_name) and filter_text in method.getSignature(): 24 | print method.getSignature() 25 | except: 26 | traceback.print_exc(file=sys.stdout) 27 | -------------------------------------------------------------------------------- /src/main/python/Phenotype.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import traceback 3 | from com.pnfsoftware.jeb.client.api import IScript, IGraphicalClientContext 4 | from com.pnfsoftware.jeb.core import RuntimeProjectUtil 5 | from com.pnfsoftware.jeb.core.actions import ActionXrefsData, ActionContext, Actions 6 | from com.pnfsoftware.jeb.core.units.code.android import IDexUnit 7 | from com.pnfsoftware.jeb.core.units.code.java import IJavaAssignment, ICompound, IJavaCall, IJavaConstant, \ 8 | IJavaStaticField, IJavaReturn, IJavaArithmeticExpression 9 | from com.pnfsoftware.jeb.core.util import DecompilerHelper 10 | 11 | 12 | class Phenotype(IScript): 13 | def run(self, ctx): 14 | assert isinstance(ctx, IGraphicalClientContext) 15 | class_name = ctx.displayQuestionBox("Phenotype helper", "What is the class name for the phenotype flag builder?", "") 16 | if not class_name or not class_name.strip(): 17 | return 18 | elif not class_name.startswith("L"): 19 | class_name = "L" + class_name.replace(".", "/") + ";" 20 | print "Class name not in dex format, trying:", class_name 21 | 22 | try: 23 | unit = RuntimeProjectUtil.findUnitsByType(ctx.enginesContext.projects[0], IDexUnit, False)[0] 24 | assert isinstance(unit, IDexUnit) 25 | 26 | init_methods = Phenotype.detect_init_methods(unit, class_name) 27 | if not init_methods: 28 | return 29 | print "Found", len(init_methods), "flag initialization methods" 30 | 31 | fields = Phenotype.detect_fields(unit, init_methods) 32 | print "Found", len(fields), "flag fields" 33 | 34 | first_getters = Phenotype.detect_first_getters(unit, fields) 35 | print "Found", len(first_getters), "getters" 36 | 37 | secondary_getters = Phenotype.detect_second_getters(unit, first_getters) 38 | print "Found", len(secondary_getters), "secondary getters" 39 | except: 40 | traceback.print_exc(file=sys.stdout) 41 | 42 | @staticmethod 43 | def detect_init_methods(unit, class_name): 44 | phenotype_builder_cls = unit.getClass(class_name) 45 | 46 | if not phenotype_builder_cls: 47 | print "Class not found!" 48 | return 49 | 50 | if '_' not in phenotype_builder_cls.getName(True): 51 | phenotype_builder_cls.name = phenotype_builder_cls.getName(False) + "_" + "PhenotypePackage" 52 | 53 | init_methods = [] 54 | for method in phenotype_builder_cls.methods: 55 | types_of_params = method.parameterTypes 56 | if "<" not in method.name and len(types_of_params) >= 2 and types_of_params[0].name == "String": 57 | new_name = method.getName(False) + "_" + \ 58 | str(BasicTypeMap.get(types_of_params[1].name, types_of_params[1].name)).lower() + "Flag" 59 | if new_name != method.getName(True): 60 | method.name = new_name 61 | init_methods.append(method) 62 | 63 | return init_methods 64 | 65 | @staticmethod 66 | def detect_fields(unit, init_methods): 67 | using_methods = set() 68 | for method in init_methods: 69 | for xref in xrefs_for(unit, method): 70 | using_methods.add(unit.getMethod(xref)) 71 | 72 | signatures = {method.getSignature(False) for method in init_methods} 73 | 74 | decompiler = DecompilerHelper.getDecompiler(unit) 75 | fields = {} 76 | for method in using_methods: 77 | cls = method.classType.implementingClass 78 | decompiled_method = decompile_dex_method(decompiler, method) 79 | if not decompiled_method: 80 | continue 81 | 82 | def handle_non_compound(statement): 83 | if isinstance(statement, IJavaAssignment): 84 | left, right = statement.left, statement.right 85 | if isinstance(right, IJavaCall): 86 | if right.method.signature in signatures: 87 | name_element = right.getArgument(1) 88 | if isinstance(name_element, IJavaConstant) and name_element.isString(): 89 | name = name_element.getString() 90 | str_name = name.replace(":", "_") 91 | if isinstance(left, IJavaStaticField): 92 | field = left.field 93 | dex_field = cls.getField(False, field.name, field.type.signature) 94 | new_name = dex_field.getName(False) + "_" + str_name 95 | if new_name != dex_field.getName(True): 96 | dex_field.name = new_name 97 | 98 | fields[str_name] = dex_field 99 | 100 | AstTraversal(handle_non_compound).traverse_block(decompiled_method.body) 101 | return fields 102 | 103 | @staticmethod 104 | def detect_first_getters(unit, fields): 105 | getters = {} 106 | decompiler = DecompilerHelper.getDecompiler(unit) 107 | for flag_name, field in fields.iteritems(): 108 | sig = field.getSignature(False) 109 | for xref in xrefs_for(unit, field): 110 | method = unit.getMethod(xref) 111 | if method.data and method.data.codeItem and \ 112 | method.data.codeItem.instructions and method.data.codeItem.instructions.size() <= 20: 113 | decompiled_method = decompile_dex_method(decompiler, method) 114 | if not decompiled_method or decompiled_method.body.size() != 1: 115 | continue 116 | 117 | statement = decompiled_method.body.get(0) 118 | if isinstance(statement, IJavaReturn): 119 | last_call = extract_last_function_call(statement.expression) 120 | if last_call and len(last_call.arguments) == 1: 121 | src = last_call.getArgument(0) 122 | if isinstance(src, IJavaStaticField) and src.field.signature == sig: 123 | new_name = method.getName(False) + "_" + flag_name 124 | if new_name != method.getName(True): 125 | method.name = new_name 126 | getters[flag_name] = method 127 | return getters 128 | 129 | @staticmethod 130 | def detect_second_getters(unit, getters): 131 | secondary_getters = {} 132 | decompiler = DecompilerHelper.getDecompiler(unit) 133 | for flag_name, getter_method in getters.iteritems(): 134 | sig = getter_method.getSignature(False).split(';->')[1] 135 | 136 | for xref in xrefs_for(unit, getter_method): 137 | method = unit.getMethod(xref) 138 | if method.getSignature(False) == sig: 139 | continue 140 | 141 | if method.data and method.data.codeItem and \ 142 | method.data.codeItem.instructions and method.data.codeItem.instructions.size() <= 20: 143 | decompiled_method = decompile_dex_method(decompiler, method) 144 | if not decompiled_method or decompiled_method.body.size() != 1: 145 | continue 146 | 147 | statement = decompiled_method.body.get(0) 148 | if isinstance(statement, IJavaReturn): 149 | last_call = extract_last_function_call(statement.expression) 150 | if last_call and len(last_call.arguments) <= 1: 151 | if last_call.methodSignature.split(';->')[1] == sig: 152 | new_name = method.getName(False) + "_" + flag_name 153 | if new_name != method.getName(True): 154 | method.name = new_name 155 | secondary_getters[flag_name] = method 156 | return secondary_getters 157 | 158 | # region Utils 159 | class AstTraversal(object): 160 | def __init__(self, traverse_non_compound): 161 | self.traverse_non_compound = traverse_non_compound 162 | 163 | def traverse_block(self, block): 164 | for i in xrange(0, block.size()): 165 | self.traverse_statement(block.get(i)) 166 | 167 | def traverse_statement(self, statement): 168 | if isinstance(statement, ICompound): 169 | for block in statement.blocks: 170 | self.traverse_block(block) 171 | else: 172 | self.traverse_non_compound(statement) 173 | 174 | 175 | def extract_last_function_call(exp): 176 | if isinstance(exp, IJavaCall): 177 | name = exp.methodName 178 | if name == "booleanValue": 179 | return extract_last_function_call(exp.getArgument(0)) 180 | return exp 181 | elif isinstance(exp, IJavaArithmeticExpression): 182 | if exp.operator.isCast(): 183 | return extract_last_function_call(exp.right) 184 | 185 | 186 | def xrefs_for(unit, item): 187 | data = ActionXrefsData() 188 | if unit.prepareExecution(ActionContext(unit, Actions.QUERY_XREFS, item.itemId, item.address), data): 189 | return data.addresses 190 | 191 | return [] 192 | 193 | 194 | def decompile_dex_method(decompiler, method): 195 | if not decompiler.decompileMethod(method.signature): 196 | print "Failed to decompile", method.currentSignature 197 | return 198 | 199 | return decompiler.getMethod(method.signature, False) 200 | 201 | 202 | BasicTypeMap = { 203 | 'C': 'char', 204 | 'B': 'byte', 205 | 'D': 'double', 206 | 'F': 'float', 207 | 'I': 'int', 208 | 'J': 'long', 209 | 'L': 'ClassName', 210 | 'S': 'short', 211 | 'Z': 'boolean', 212 | 'V': 'void', 213 | } 214 | # endregion 215 | -------------------------------------------------------------------------------- /src/main/python/RenameFromConstArg.py: -------------------------------------------------------------------------------- 1 | #?description=Launch the already loaded const arg rename plugin 2 | #?shortcut=Mod1+Mod3+L 3 | import sys 4 | import traceback 5 | from com.pnfsoftware.jeb.client.api import IScript, IGraphicalClientContext 6 | from com.pnfsoftware.jeb.core import IEnginesPlugin 7 | 8 | import utils 9 | 10 | 11 | class RenameFromConstArg(IScript): 12 | def run(self, ctx): 13 | assert isinstance(ctx, IGraphicalClientContext) 14 | try: 15 | plugins = ctx.getEnginesContext().getEnginesPlugins() 16 | for plugin in plugins: 17 | assert isinstance(plugin, IEnginesPlugin) 18 | if 'ConstArgRenamingPlugin' == plugin.getClass().getSimpleName(): 19 | classloader = plugin.getClass().getClassLoader() 20 | 21 | # 1. update ui bridge 22 | UIBridge = utils.get_object("com.yoavst.jeb.bridge.UIBridge", classloader) 23 | UIBridge.update(ctx) 24 | 25 | # 2. Launch plugin 26 | utils.launch_plugin(plugin, ctx, classloader, close_loader=False) 27 | break 28 | except: 29 | traceback.print_exc(file=sys.stdout) 30 | -------------------------------------------------------------------------------- /src/main/python/UIBridge.py: -------------------------------------------------------------------------------- 1 | #?description=update the UIBridge class of JebPlugin 2 | #?shortcut=Mod1+Mod3+U 3 | import sys 4 | import traceback 5 | from com.pnfsoftware.jeb.client.api import IScript, IGraphicalClientContext 6 | from com.pnfsoftware.jeb.core import IEnginesPlugin 7 | 8 | import utils 9 | 10 | 11 | class UIBridge(IScript): 12 | def run(self, ctx): 13 | assert isinstance(ctx, IGraphicalClientContext) 14 | try: 15 | plugins = ctx.getEnginesContext().getEnginesPlugins() 16 | for plugin in plugins: 17 | assert isinstance(plugin, IEnginesPlugin) 18 | if 'yoav' in plugin.getClass().getCanonicalName(): 19 | classloader = plugin.getClass().getClassLoader() 20 | 21 | print "Updating UI bridge" 22 | UIBridge = utils.get_object("com.yoavst.jeb.bridge.UIBridge", classloader) 23 | UIBridge.update(ctx) 24 | break 25 | except: 26 | traceback.print_exc(file=sys.stdout) 27 | -------------------------------------------------------------------------------- /src/main/python/coreplugins/DNoSync.py: -------------------------------------------------------------------------------- 1 | #?type=dexdec-ir 2 | from com.pnfsoftware.jeb.core.units.code.android.ir import AbstractDOptimizer 3 | 4 | class DNoSync(AbstractDOptimizer): 5 | def perform(self): 6 | cnt = 0 7 | for insn in self.cfg.instructions(): 8 | if insn.isMonitorEnter() or insn.isMonitorExit(): 9 | insn.transformToNop() 10 | cnt += 1 11 | if cnt: 12 | self.cfg.invalidateDataFlowAnalysis() 13 | return cnt 14 | -------------------------------------------------------------------------------- /src/main/python/coreplugins/DPropagation.py: -------------------------------------------------------------------------------- 1 | #?type=dexdec-ir 2 | from com.pnfsoftware.jeb.core.units.code.android.ir import AbstractDOptimizer, IDVisitor, DUtil, DOpcodeType 3 | from java.lang import Integer 4 | 5 | class DPropagation(AbstractDOptimizer): 6 | def perform(self): 7 | cnt = 0 8 | self.insns_to_delete = [] 9 | 10 | scan_result = list(self.scan()) 11 | if scan_result: 12 | cnt += self.replace(scan_result) 13 | 14 | # Delete broken stuff 15 | for insn in self.cfg.instructions(): 16 | if insn.isAssign(): 17 | if insn.assignDestination == insn.assignSource: 18 | self.insns_to_delete.append(insn) 19 | 20 | if self.insns_to_delete: 21 | for insn in self.insns_to_delete: 22 | insn.transformToNop() 23 | cnt += len(self.insns_to_delete) 24 | return cnt 25 | 26 | 27 | def scan(self): 28 | variables_union_find = UnionFind() 29 | variable_assignments = dict() 30 | 31 | # Step 0: Collect function args 32 | for reg, reg_type in dict(self.ctx.parametersTypeMap).items(): 33 | if reg != -1: 34 | variable_assignments.setdefault(self.ctx.createRegisterVar(reg, reg_type), []).append(None) 35 | 36 | # Step 1: Scan all the instructions for identifier assignments 37 | for insn in self.cfg.instructions(): 38 | if insn.isAssign(): 39 | dest, src = insn.assignDestination, insn.assignSource 40 | if dest.isVar(): 41 | if src.isVar(): 42 | variables_union_find.union(dest, src) 43 | else: 44 | variable_assignments.setdefault(dest, []).append(src) 45 | 46 | elif insn.isStoreException(): 47 | identifier = insn.definedIdentifier 48 | variable_assignments.setdefault(identifier, []).append(insn) 49 | 50 | # Step 2: Collect them to groups 51 | groups = dict() # Root -> ([elements], [assignments]) 52 | for identifier in variables_union_find: 53 | root = variables_union_find[identifier] 54 | elements, assignments = groups.setdefault(root, ([], [])) 55 | elements.append(identifier) 56 | assignments.extend(variable_assignments.setdefault(identifier, [])) 57 | 58 | # Step 3: Check whether they are equivalents 59 | for _, (elements, assignments) in groups.iteritems(): 60 | # as long as there is at most a single assignment, we can make all those variables the same variable 61 | # and maybe even constant 62 | if len(elements) > 1 and len(assignments) <= 1: 63 | if not assignments: 64 | print "Should not be here", elements 65 | # merge the variables 66 | yield elements, None 67 | else: 68 | assignment = assignments[0] 69 | if assignment is None: 70 | yield elements, None 71 | elif assignment.isImm(): 72 | # Can replace with constant 73 | yield elements, assignment 74 | else: 75 | # Just merge the variables 76 | # **Note:**, this is unsafe as there are side effects, so here is a heroistic that should remove the most common case 77 | # TODO talk about it with someone, to see if we can define it better 78 | visitor = VariablesCollectorVisitor() 79 | assignment.visitDepthPost(visitor) 80 | if not (set(elements) & visitor.variables): 81 | yield elements, None 82 | 83 | def replace(self, scan_result): 84 | # create our instruction visitor 85 | vis = ReplacementVisitor(scan_result) 86 | # visit all the instructions of the IR CFG 87 | for insn in self.cfg.instructions(): 88 | insn.visitInstructionPreOrder(vis, False) 89 | # return the count of replacements 90 | return vis.cnt 91 | 92 | class ReplacementVisitor(IDVisitor): 93 | def __init__(self, groups): 94 | self.groups = groups 95 | self.cnt = 0 96 | self.replacements_cache = dict() 97 | self.assignments_to_delete = [] 98 | 99 | def process(self, e, parent, results): 100 | if e.isVar(): 101 | replacement = self.get_replacement(e) 102 | if replacement and parent.replaceSubExpression(e, replacement): 103 | # success (this visitor is pre-order, we need to report the replaced node) 104 | results.setReplacedNode(replacement) 105 | self.cnt += 1 106 | 107 | 108 | 109 | def get_replacement(self, var): 110 | if var in self.replacements_cache: 111 | return self.replacements_cache[var] 112 | 113 | for elements, const in self.groups: 114 | if var in elements: 115 | # TODO improve this 116 | # choose the identifier with the definition with the lower address 117 | if const: 118 | self.replacements_cache[var] = const 119 | return const 120 | 121 | replacement = min(elements, key=lambda v: v.id) 122 | result = None if replacement == var else replacement 123 | self.replacements_cache[var] = result 124 | return result 125 | return None 126 | 127 | class VariablesCollectorVisitor(IDVisitor): 128 | def __init__(self): 129 | self.variables = set() 130 | 131 | def process(self, e, parent, results): 132 | if e.isVar(): 133 | self.variables.add(e.id) 134 | 135 | 136 | # https://gist.github.com/AntiGameZ/67124a149d4c1d41e20ee82ba2cfdbe7 137 | class UnionFind(object): 138 | """Union-find data structure. 139 | Each unionFind instance X maintains a family of disjoint sets of 140 | hashable objects, supporting the following two methods: 141 | - X[item] returns a name for the set containing the given item. 142 | Each set is named by an arbitrarily-chosen one of its members; as 143 | long as the set remains unchanged it will keep the same name. If 144 | the item is not yet part of a set in X, a new singleton set is 145 | created for it. 146 | - X.union(item1, item2, ...) merges the sets containing each item 147 | into a single larger set. If any item is not yet part of a set 148 | in X, it is added to X as one of the members of the merged set. 149 | """ 150 | 151 | def __init__(self): 152 | """Create a new empty union-find structure.""" 153 | self.weights = {} 154 | self.parents = {} 155 | 156 | def __getitem__(self, object): 157 | """Find and return the name of the set containing the object.""" 158 | 159 | # check for previously unknown object 160 | if object not in self.parents: 161 | self.parents[object] = object 162 | self.weights[object] = 1 163 | return object 164 | 165 | # find path of objects leading to the root 166 | path = [object] 167 | root = self.parents[object] 168 | while root != path[-1]: 169 | path.append(root) 170 | root = self.parents[root] 171 | 172 | # compress the path and return 173 | for ancestor in path: 174 | self.parents[ancestor] = root 175 | return root 176 | 177 | def __iter__(self): 178 | """Iterate through all items ever found or unioned by this structure.""" 179 | return iter(self.parents) 180 | 181 | def union(self, *objects): 182 | """Find the sets containing the objects and merge them all.""" 183 | roots = [self[x] for x in objects] 184 | heaviest = max([(self.weights[r],r) for r in roots])[1] 185 | for r in roots: 186 | if r != heaviest: 187 | self.weights[heaviest] += self.weights[r] 188 | self.parents[r] = heaviest -------------------------------------------------------------------------------- /src/main/python/utils.py: -------------------------------------------------------------------------------- 1 | from java.lang import Class, Runnable 2 | 3 | 4 | def get_object(name, loader): 5 | """Get kotlin object for the given using the given loader""" 6 | return Class.forName(name, True, loader).getField("INSTANCE").get(None) 7 | 8 | 9 | def launch_plugin(plugin, ctx, loader, close_loader=True): 10 | UIUtils = get_object("com.yoavst.jeb.utils.script.UIUtils", loader) 11 | 12 | class RunnableClass(Runnable): 13 | def run(self): 14 | opts = UIUtils.openOptionsForPlugin(plugin, ctx.getEnginesContext()) 15 | if opts is None: 16 | print "Plugin opening aborted" 17 | return 18 | 19 | plugin.execute(ctx.getEnginesContext(), opts) 20 | if close_loader: 21 | loader.close() 22 | 23 | UIUtils.runOnMainThread(RunnableClass()) 24 | -------------------------------------------------------------------------------- /src/main/resources/renamer_bundle_get.py: -------------------------------------------------------------------------------- 1 | assignee = underscore_to_camelcase(tag.split(".")[-1]) 2 | -------------------------------------------------------------------------------- /src/main/resources/renamer_bundle_set.py: -------------------------------------------------------------------------------- 1 | argument = underscore_to_camelcase(tag.split(".")[-1]) 2 | -------------------------------------------------------------------------------- /src/main/resources/renamer_log_renamer.py: -------------------------------------------------------------------------------- 1 | # Will assume it is has one the following forms: 2 | # 1. Classname 3 | # 2. ClassnameMethod 4 | split_index = -1 5 | for i, c in enumerate(tag): 6 | if not c.isalnum(): 7 | split_index = i 8 | break 9 | 10 | if split_index == -1: 11 | cls = tag 12 | else: 13 | cls = tag[:split_index] 14 | method = "" 15 | for c in tag[split_index + 1:]: 16 | if not c.isalnum(): 17 | break 18 | method += c 19 | if not method: 20 | del method --------------------------------------------------------------------------------