├── .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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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
--------------------------------------------------------------------------------