Repository: contra/JMD Branch: master Commit: 0f3051ca70fd Files: 46 Total size: 162.5 KB Directory structure: gitextract_nu5m95t5/ ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jmd-cli/ │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── src/ │ └── main/ │ └── java/ │ └── net/ │ └── contra/ │ └── jmd/ │ ├── Deobfuscator.java │ └── Version.java ├── jmd-core/ │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── net/ │ │ └── contra/ │ │ └── jmd/ │ │ ├── transformers/ │ │ │ ├── Transformer.java │ │ │ ├── allatori/ │ │ │ │ └── AllatoriTransformer.java │ │ │ ├── dasho/ │ │ │ │ └── DashOTransformer.java │ │ │ ├── generic/ │ │ │ │ ├── ForeignCallRemover.java │ │ │ │ ├── GenericStringDeobfuscator.java │ │ │ │ ├── Renamer.java │ │ │ │ ├── StackFixer.java │ │ │ │ ├── StringFixer.java │ │ │ │ ├── StringScanner.java │ │ │ │ └── TransformerTemplate.java │ │ │ ├── jshrink/ │ │ │ │ ├── JShrinkTransformer.java │ │ │ │ └── StoreHandler.java │ │ │ ├── smokescreen/ │ │ │ │ └── SmokeScreenTransformer.java │ │ │ └── zkm/ │ │ │ └── ZKMTransformer.java │ │ └── util/ │ │ ├── GenericClassLoader.java │ │ ├── GenericMethods.java │ │ ├── HandleSearcher.java │ │ ├── LogHandler.java │ │ └── NonClassEntries.java │ └── test/ │ └── java/ │ └── net/ │ └── contra/ │ └── jmd/ │ └── transformers/ │ └── dasho/ │ └── DashOTransformerTest.java ├── jmd-gui/ │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── src/ │ └── main/ │ ├── java/ │ │ └── net/ │ │ └── contra/ │ │ └── jmd/ │ │ └── ConfigureApp.java │ └── resources/ │ └── view/ │ └── configure.fxml └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .gradle .idea *.iml build ================================================ FILE: .travis.yml ================================================ language: java jdk: - oraclejdk8 after_success: - ./gradlew jacocoTestReport coveralls ================================================ FILE: LICENSE ================================================ Copyright (c) 2011 Contra Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ **JMD is a general purpose Java bytecode deobfuscation tool** [![Build Status][travis-image]][travis-url] [![Coverage Status][coveralls-image]][coveralls-url] ## Usage java -jar JMD.jar JMDGUI.exe provides an optional frontend to the command line interface. ## Road map * Actualization transformers * Migrate from BCEL to ASM * Add tests ## Examples Remove ZKM obfuscation: java -jar JMD.jar "C:/Files/Magic.jar" zkm true Remove Allatori obfuscation: java -jar JMD.jar "C:/Files/Magic.jar" allatori true Remove Allatori-Strong obfuscation: java -jar JMD.jar "C:/Files/Magic.jar" allatori-strong true Remove JShrink obfuscation: java -jar JMD.jar "C:/Files/Magic.jar" jshrink true Remove DashO obfuscation: java -jar JMD.jar "C:/Files/Magic.jar" dasho true Remove SmokeScreen obfuscation: java -jar JMD.jar "C:/Files/Magic.jar" smokescreen true Remove Generic String obfuscation: java -jar JMD.jar "C:/Files/Magic.jar" genericstringdeobfuscator true Find all instances of example.com: java -jar JMD.jar "C:/Files/Magic.jar" stringscanner "example.com" true Replace all instances of example.com: java -jar JMD.jar "C:/Files/Magic.jar" stringreplacer "example.com" true "rscbunlocked.net" Renamer (currently breaks code): java -jar JMD.jar "C:/Files/Magic.jar" renamer true Correct stack issues: java -jar JMD.jar "C:/Files/Magic.jar" stackfixer true ## Download Stable releases: https://github.com/Contra/JMD/downloads Latest release (most likely stable): Get the files from the repo ## Requirements JMD - Java 6 (hasn't been tested on anything else but should work) JMDGUI - .NET 4 or higher ## License Copyright (c) 2011 Contra Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. [travis-image]: https://travis-ci.org/contra/JMD.svg?branch=master [travis-url]: https://travis-ci.org/contra/JMD [coveralls-image]: https://coveralls.io/repos/github/contra/JMD/badge.svg?branch=master [coveralls-url]: https://coveralls.io/github/contra/JMD?branch=master ================================================ FILE: build.gradle ================================================ buildscript { ext { projectVersion = '1.6' gradleWrapperVersion = '3.1' daggerVersion = '2.6' bcelVersion = '6.0' commonsIoVersion = '2.5' slf4jVersion = '1.7.21' logbackVersion = '1.1.7' testngVersion = '6.9.13.6' } repositories { mavenCentral() } } plugins { id "com.github.kt3k.coveralls" version '2.6.3' } description = 'JMD is a general purpose Java bytecode deobfuscation tool' subprojects { apply plugin: "java" apply plugin: "findbugs" apply plugin: "pmd" apply plugin: "jacoco" apply plugin: "com.github.kt3k.coveralls" sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 version = projectVersion repositories { mavenCentral() } task wrapper(type: Wrapper) { gradleVersion = gradleWrapperVersion } findbugs { ignoreFailures = true effort = 'max' } pmd { ignoreFailures = true ruleSets = [ 'java-basic', 'java-braces', 'java-clone', 'java-codesize', 'java-comments', 'java-controversial', 'java-design', 'java-empty', 'java-finalizers', 'java-imports', 'java-j2ee', 'java-javabeans', 'java-junit', 'java-logging-jakarta-commons', 'java-logging-java', 'java-migrating', 'java-naming', 'java-optimizations', 'java-strictexception', 'java-strings', 'java-sunsecure', 'java-typeresolution', 'java-unnecessary', 'java-unusedcode' ] } tasks.withType(Pmd) { reports { xml.enabled = false html.enabled = true } } tasks.withType(FindBugs) { reports { xml.enabled = false html.enabled = true } } tasks.coveralls { dependsOn 'check' } jacocoTestReport { reports { xml.enabled = true html.enabled = false } } } project(':jmd-cli') { dependencies { compile project(':jmd-core') } jar { manifest { attributes 'Implementation-Title': 'net.contra.jmd', 'Implementation-Version': version, 'Main-Class': 'net.contra.jmd.Deobfuscator' } from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } } } project(':jmd-core') { dependencies { compile group: 'com.google.dagger', name: 'dagger', version: daggerVersion compile group: 'org.apache.bcel', name: 'bcel', version: bcelVersion compile group: 'commons-io', name: 'commons-io', version: commonsIoVersion compile group: 'org.slf4j', name: 'slf4j-api', version: slf4jVersion compile group: 'ch.qos.logback', name: 'logback-classic', version: logbackVersion compile group: 'org.testng', name: 'testng', version: testngVersion } } project(':jmd-gui') { } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ #Tue Nov 08 23:54:47 MSK 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-bin.zip ================================================ FILE: gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn ( ) { echo "$*" } die ( ) { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: jmd-cli/gradle/wrapper/gradle-wrapper.properties ================================================ #Tue Nov 08 23:54:48 MSK 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-bin.zip ================================================ FILE: jmd-cli/gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn ( ) { echo "$*" } die ( ) { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: jmd-cli/gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: jmd-cli/src/main/java/net/contra/jmd/Deobfuscator.java ================================================ package net.contra.jmd; import net.contra.jmd.transformers.allatori.AllatoriTransformer; import net.contra.jmd.transformers.dasho.DashOTransformer; import net.contra.jmd.transformers.generic.*; import net.contra.jmd.transformers.jshrink.JShrinkTransformer; import net.contra.jmd.transformers.smokescreen.SmokeScreenTransformer; import net.contra.jmd.transformers.zkm.ZKMTransformer; import net.contra.jmd.util.LogHandler; import java.util.Scanner; /** * Created by IntelliJ IDEA. * User: Eric * Date: Nov 24, 2010 * Time: 9:21:10 PM */ public class Deobfuscator { private static String version = Version.getVersion(); private static String credits = "skoalman, super_, ollie, popcorn89, the prophecy, and saevion"; private static LogHandler logger = new LogHandler("Deobfuscator"); //TODO: Load single class files... herpa derp public static void main(String[] argv) throws Exception { logger.message("Java Multi-Purpose Deobfuscator"); logger.message("Version " + version); logger.message("Created by Contra. Please read LICENSE.txt"); logger.message("Tons of code from " + credits); if (!(argv.length >= 3)) { logger.error("java -jar JMD.jar "); logger.error("Example ZKM: java -jar JMD.jar \"C:/Files/Magic.jar\" zkm true"); logger.error("Example StringScan: java -jar JMD.jar \"C:/Files/Magic.jar\" stringscanner \"rscheata.net\" false"); logger.error("Example StringReplace: java -jar JMD.jar \"C:/Files/Magic.jar\" stringreplacer \"rscheata.net\" true \"rscbunlocked.net\""); return; } if (argv[2].equals("true")) { } if (argv[1].toLowerCase().equals("zkm")) { ZKMTransformer zt = new ZKMTransformer(argv[0]); zt.transform(); } else if (argv[1].toLowerCase().equals("allatori")) { AllatoriTransformer at = new AllatoriTransformer(argv[0], false); at.transform(); } else if (argv[1].toLowerCase().equals("allatori-strong")) { AllatoriTransformer at = new AllatoriTransformer(argv[0], true); at.transform(); } else if (argv[1].toLowerCase().equals("jshrink")) { JShrinkTransformer jt = new JShrinkTransformer(argv[0]); jt.transform(); } else if (argv[1].toLowerCase().equals("dasho")) { DashOTransformer dt = new DashOTransformer(argv[0]); dt.transform(); } else if (argv[1].toLowerCase().equals("smokescreen")) { SmokeScreenTransformer st = new SmokeScreenTransformer(argv[0]); st.transform(); } else if (argv[1].toLowerCase().equals("renamer")) { Renamer re = new Renamer(argv[0]); re.transform(); } else if (argv[1].toLowerCase().equals("genericstringdeobfuscator")) { GenericStringDeobfuscator gsd = new GenericStringDeobfuscator(argv[0]); gsd.transform(); } else if (argv[1].toLowerCase().equals("stringfixer")) { StringFixer sf = new StringFixer(argv[0]); sf.transform(); } else if (argv[1].toLowerCase().equals("stackfixer")) { StackFixer sf = new StackFixer(argv[0]); sf.transform(); } else if (argv[1].toLowerCase().equals("foreigncallremover")) { ForeignCallRemover fc = new ForeignCallRemover(argv[0]); fc.transform(); } else if (argv[1].toLowerCase().equals("stringscanner")) { StringScanner us = new StringScanner(argv[0], argv[3], false, ""); us.scan(); } else if (argv[1].toLowerCase().equals("stringreplacer")) { StringScanner us = new StringScanner(argv[0], argv[3], true, argv[4]); us.scan(); } else { logger.error("Types are: ZKM, Allatori, JShrink, DashO, SmokeScreen, StringFixer, StringScanner, " + "\n ForeignCallRemover, GenericStringDeobfuscator, StackFixer, StringFixer, Renamer, and StringReplacer (not case sensitive)"); } Scanner in = new Scanner(System.in); logger.message("Press key to exit..."); in.nextLine(); in.close(); } } ================================================ FILE: jmd-cli/src/main/java/net/contra/jmd/Version.java ================================================ package net.contra.jmd; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; public final class Version { private static final String UNKNOWN = "UNKNOWN"; private Version() { } public static String getVersion() { final URL url = Version.class.getResource(JarFile.MANIFEST_NAME); if (url == null) { return UNKNOWN; } try { final InputStream inputStream = url.openStream(); final Manifest manifest = new Manifest(inputStream); return manifest.getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION); } catch (IOException e) { // empty } return UNKNOWN; } } ================================================ FILE: jmd-core/gradle/wrapper/gradle-wrapper.properties ================================================ #Tue Nov 08 23:54:48 MSK 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-bin.zip ================================================ FILE: jmd-core/gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn ( ) { echo "$*" } die ( ) { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: jmd-core/gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: jmd-core/src/main/java/net/contra/jmd/transformers/Transformer.java ================================================ package net.contra.jmd.transformers; import org.apache.bcel.generic.TargetLostException; public interface Transformer { } ================================================ FILE: jmd-core/src/main/java/net/contra/jmd/transformers/allatori/AllatoriTransformer.java ================================================ package net.contra.jmd.transformers.allatori; import net.contra.jmd.transformers.Transformer; import net.contra.jmd.util.GenericMethods; import net.contra.jmd.util.LogHandler; import net.contra.jmd.util.NonClassEntries; import org.apache.bcel.classfile.ClassParser; import org.apache.bcel.classfile.Method; import org.apache.bcel.generic.*; import java.io.File; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; public class AllatoriTransformer implements Transformer { LogHandler logger = new LogHandler("AllatoriTransformer"); Map cgs = new HashMap(); ClassGen ALLATORI_CLASS; String JAR_NAME; boolean isStrong = false; public AllatoriTransformer(String jarfile, boolean strong) throws Exception { logger.log("Allatori Deobfuscator"); isStrong = strong; File jar = new File(jarfile); JAR_NAME = jarfile; JarFile jf = new JarFile(jar); Enumeration entries = jf.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry == null) { break; } if (entry.getName().endsWith(".class")) { ClassGen cg = new ClassGen(new ClassParser(jf .getInputStream(entry), entry.getName()).parse()); if (isStringClass(cg) || isStringClassB(cg)) { ALLATORI_CLASS = cg; logger.debug("Allatori Class: " + ALLATORI_CLASS.getClassName()); } cgs.put(cg.getClassName(), cg); } else { NonClassEntries.add(entry, jf.getInputStream(entry)); } } jf.close(); } private boolean isStringClass(ClassGen cg) { if (cg.getMethods().length == 2 && cg.getMethods()[0].isStatic() && cg.getMethods()[1].isStatic()) { if (cg.getMethods()[0].getReturnType().toString().equals( "java.lang.String") && cg.getMethods()[1].getReturnType().toString().equals( "java.lang.String")) { return true; } } return false; } private boolean isStringClassB(ClassGen cg) { if (cg.getMethods().length == 1 && cg.getMethods()[0].isStatic()) { if (cg.getMethods()[0].getReturnType().toString().equals("java.lang.String")) { return true; } } return false; } public static String decode(String string) { int i = 85; char[] cs = new char[string.length()]; int pos = cs.length - 1; int index = pos; int xor = i; while (pos >= 0) { char c = (char) (string.charAt(index) ^ xor); int c1_index = index; xor = (char) ((char) (c1_index ^ xor) & '?'); cs[c1_index] = c; if (--index < 0) { break; } char c2 = (char) (string.charAt(index) ^ xor); int c2_index = index; xor = (char) ((char) (c2_index ^ xor) & '?'); cs[c2_index] = c2; pos = --index; } return new String(cs); } public static String decodeContext(String encrypted, String callingClass, String callingMethod) { String keyString = callingClass + callingMethod; int lastKeyIndex = keyString.length() - 1; int xor = 85; int keyIndex = lastKeyIndex; int length = encrypted.length(); char[] cs = new char[length]; for (int i = length - 1; i >= 0; i--) { if (keyIndex < 0) { keyIndex = lastKeyIndex; } char keyChar = keyString.charAt(keyIndex--); cs[i] = (char) (keyChar ^ (encrypted.charAt(i) ^ xor)); xor = (char) (63 & (xor ^ (i ^ keyChar))); } return new String(cs); } public void transform() throws TargetLostException { logger.log("Starting Encrypted String Removal..."); replaceStrings(); logger.log("Deobfuscation Finished! Dumping jar..."); GenericMethods.dumpJar(JAR_NAME, cgs.values()); logger.log("Operation Completed."); } public void replaceStrings() throws TargetLostException { for (ClassGen cg : cgs.values()) { int replaced = 0; for (Method method : cg.getMethods()) { MethodGen mg = new MethodGen(method, cg.getClassName(), cg.getConstantPool()); InstructionList list = mg.getInstructionList(); if (list == null) { continue; } InstructionHandle[] handles = list.getInstructionHandles(); for (int i = 1; i < handles.length; i++) { if (handles[i].getInstruction() instanceof INVOKESTATIC && handles[i - 1].getInstruction() instanceof LDC) { INVOKESTATIC methodCall = (INVOKESTATIC) handles[i].getInstruction(); if (methodCall.getClassName(cg.getConstantPool()).contains(ALLATORI_CLASS.getClassName())) { LDC encryptedLDC = (LDC) handles[i - 1].getInstruction(); String encryptedString = encryptedLDC.getValue(cg.getConstantPool()).toString(); String decryptedString; if(isStrong){ decryptedString = decodeContext(encryptedString, cg.getClassName(), method.getName()); } else { decryptedString = decode(encryptedString); } logger.debug(encryptedString + " -> " + decryptedString + " in " + cg.getClassName() + "." + method.getName()); int stringRef = cg.getConstantPool().addString(decryptedString); LDC lc = new LDC(stringRef); NOP nop = new NOP(); handles[i].setInstruction(lc); handles[i - 1].setInstruction(nop); replaced++; } } } mg.setInstructionList(list); mg.setMaxLocals(); mg.setMaxStack(); cg.replaceMethod(method, mg.getMethod()); } if (replaced > 0) { logger.debug("decrypted " + replaced + " strings in class " + cg.getClassName()); } } } } ================================================ FILE: jmd-core/src/main/java/net/contra/jmd/transformers/dasho/DashOTransformer.java ================================================ package net.contra.jmd.transformers.dasho; import net.contra.jmd.transformers.Transformer; import net.contra.jmd.util.GenericMethods; import net.contra.jmd.util.LogHandler; import net.contra.jmd.util.NonClassEntries; import org.apache.bcel.classfile.ClassParser; import org.apache.bcel.classfile.Method; import org.apache.bcel.generic.*; import org.apache.commons.io.IOUtils; import java.io.*; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarInputStream; import java.util.jar.JarOutputStream; public class DashOTransformer implements Transformer { private static LogHandler logger = new LogHandler("DashOTransformer"); private Map cgs = new HashMap(); String JAR_NAME; String decryptor = "NOTFOUND"; String decryptorclass = "NOTFOUND"; public DashOTransformer(String jarfile) throws Exception { File jar = new File(jarfile); JAR_NAME = jarfile; JarFile jf = new JarFile(jar); Enumeration entries = jf.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry == null) { break; } if (entry.getName().endsWith(".class")) { ClassGen cg = new ClassGen(new ClassParser(jf.getInputStream(entry), entry.getName()).parse()); cgs.put(cg.getClassName(), cg); } else { NonClassEntries.add(entry, jf.getInputStream(entry)); } } jf.close(); } public static String decrypt(String input) { if (isEmpty(input)) { return input; } char[] inputChars = input.toCharArray(); int length = inputChars.length; char[] inputCharsCopy = new char[length]; int j = 0; int i = 0; while (j < length) { inputCharsCopy[j] = ((char) (inputChars[j] - '\1' ^ i)); i = (char) (i + 1); j++; } return new String(inputCharsCopy); } private static boolean isEmpty(final CharSequence cs) { return cs == null || cs.length() == 0; } public void setDecryptor() { for (ClassGen cg : cgs.values()) { for (Method m : cg.getMethods()) { try { if (m.isPublic() && m.isStatic() && m.getArgumentTypes()[0].toString().equals("java.lang.String") && m.getReturnType().toString().equals("java.lang.String")) { String dc = cg.getClassName() + "." + m.getName(); decryptor = m.getName(); decryptorclass = cg.getClassName(); logger.debug("Found String Decryptor! " + dc); return; } } catch (Exception e) { continue; } } } logger.error("String decrypt not found!"); } public void removeStringEncryption() { for (ClassGen cg : cgs.values()) { for (Method m : cg.getMethods()) { MethodGen mg = new MethodGen(m, cg.getClassName(), cg.getConstantPool()); InstructionList il = mg.getInstructionList(); InstructionHandle[] handles = il.getInstructionHandles(); for (int i = 1; i < handles.length; i++) { if ((handles[i].getInstruction() instanceof LDC) && (handles[i + 1].getInstruction() instanceof INVOKESTATIC)) { INVOKESTATIC invoke = (INVOKESTATIC) handles[i + 1].getInstruction(); if (decryptor.equals("NOTFOUND")) { logger.error("String Decryption Method not Set!"); return; } String call = invoke.getClassName(cg.getConstantPool()); String mcall = invoke.getMethodName(cg.getConstantPool()); if (call.equals(decryptorclass) && mcall.equals(decryptor)) { LDC orig = ((LDC) handles[i].getInstruction()); String enc = (String) orig.getValue(cg.getConstantPool()); int index = cg.getConstantPool().addString(decrypt(enc)); LDC lc = new LDC(index); handles[i].setInstruction(lc); handles[i + 1].setInstruction(new NOP()); logger.debug(enc + " -> " + decrypt(enc)); } } } mg.setInstructionList(il); mg.setMaxLocals(); mg.setMaxStack(); cg.replaceMethod(m, mg.getMethod()); } } } public void dumpJar(String path) { FileOutputStream os; try { os = new FileOutputStream(new File(path)); } catch (FileNotFoundException fnfe) { throw new RuntimeException("could not create file \"" + path + "\": " + fnfe); } JarOutputStream jos; try { jos = new JarOutputStream(os); for (JarEntry jbe : NonClassEntries.entries) { JarEntry destEntry = new JarEntry(jbe.getName()); byte[] bite = IOUtils.toByteArray(NonClassEntries.ins.get(jbe)); jos.putNextEntry(destEntry); jos.write(bite); jos.closeEntry(); } for (ClassGen classIt : cgs.values()) { jos.putNextEntry(new JarEntry(classIt.getClassName().replace('.', '/') + ".class")); jos.write(classIt.getJavaClass().getBytes()); jos.closeEntry(); jos.flush(); } jos.closeEntry(); jos.close(); } catch (IOException ioe) { } } public void transform() { logger.log("DashO Deobfuscator"); logger.log("Finding String Decryption Method..."); setDecryptor(); logger.log("Starting String Encryption Removal..."); removeStringEncryption(); //logger.log("Starting Unconditional Branch Remover..."); //removeControlFlow(); //unconditionalBranchTransformer(); //logger.log("Starting Exit Flow Corrector..."); //exitFlowTransformer(); logger.log("Deobfuscation finished! Dumping jar..."); GenericMethods.dumpJar(JAR_NAME, cgs.values()); logger.log("Operation Completed."); } } ================================================ FILE: jmd-core/src/main/java/net/contra/jmd/transformers/generic/ForeignCallRemover.java ================================================ package net.contra.jmd.transformers.generic; import net.contra.jmd.util.GenericMethods; import net.contra.jmd.util.LogHandler; import net.contra.jmd.util.NonClassEntries; import org.apache.bcel.classfile.ClassParser; import org.apache.bcel.classfile.Method; import org.apache.bcel.generic.*; import org.apache.commons.io.IOUtils; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; public class ForeignCallRemover { private static LogHandler logger = new LogHandler("ForeignCallRemover"); private Map cgs = new HashMap(); String JAR_NAME; String AuthClass; public ForeignCallRemover(String jarfile) throws Exception { File jar = new File(jarfile); JAR_NAME = jarfile; JarFile jf = new JarFile(jar); Enumeration entries = jf.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry == null) { break; } if (entry.getName().endsWith(".class")) { ClassGen cg = new ClassGen(new ClassParser(jf.getInputStream(entry), entry.getName()).parse()); if (isAuthClass(cg)) { logger.debug("Found auth class! " + cg.getClassName()); AuthClass = cg.getClassName(); } cgs.put(cg.getClassName(), cg); } else { NonClassEntries.add(entry, jf.getInputStream(entry)); } } jf.close(); } public boolean isAuthClass(ClassGen cg) { if (cg.getMethods().length == 2 && cg.getMethods()[0].getArgumentTypes().length == 1) { if (cg.getMethods()[0].getArgumentTypes()[0].getSignature().contains("String")) { if (cg.getMethods()[0].getReturnType().toString().contains("boolean")) { return true; } } } return false; } public void dumpJar(String path) { FileOutputStream os; try { os = new FileOutputStream(new File(path)); } catch (FileNotFoundException fnfe) { throw new RuntimeException("could not create file \"" + path + "\": " + fnfe); } JarOutputStream jos; try { jos = new JarOutputStream(os); for (ClassGen classIt : cgs.values()) { jos.putNextEntry(new JarEntry(classIt.getClassName().replace('.', '/') + ".class")); jos.write(classIt.getJavaClass().getBytes()); jos.closeEntry(); jos.flush(); } for (JarEntry jbe : NonClassEntries.entries) { if (!jbe.isDirectory()) { JarEntry destEntry = new JarEntry(jbe.getName()); byte[] bite = IOUtils.toByteArray(NonClassEntries.ins.get(jbe)); jos.putNextEntry(destEntry); jos.write(bite); jos.closeEntry(); } } jos.closeEntry(); jos.close(); } catch (IOException ioe) { } } public void RemoveCalls() { for (ClassGen cg : cgs.values()) { int replaced = 0; for (Method method : cg.getMethods()) { //logger.debug("in method " + method.getName()); MethodGen mg = new MethodGen(method, cg.getClassName(), cg.getConstantPool()); InstructionList list = mg.getInstructionList(); if (list == null) { continue; } InstructionHandle[] handles = list.getInstructionHandles(); for (int i = 0; i < handles.length; i++) { if (GenericMethods.isCall(handles[i].getInstruction())) { String callClass = GenericMethods.getCallClassName(handles[i].getInstruction(), cg.getConstantPool()); String callMethod = GenericMethods.getCallMethodName(handles[i].getInstruction(), cg.getConstantPool()); if (!callClass.startsWith("java") && (!callClass.startsWith("org") || callClass.contains("PingBack")) && !cgs.containsKey(callClass)) { if (GenericMethods.getCallArgTypes(handles[i].getInstruction(), cg.getConstantPool()).length == 0) { handles[i].setInstruction(new NOP()); if (handles[i + 1].getInstruction() instanceof ASTORE) { handles[i + 1].setInstruction(new NOP()); } logger.debug(callClass + "." + callMethod + " invoke had no arguments, so we NOP"); replaced++; } else { //TODO: WRITE SOMETHING TO DETECT MULTIPLE ARGUMENTS handles[i - 1].setInstruction(new NOP()); handles[i].setInstruction(new NOP()); if (handles[i + 1].getInstruction() instanceof ASTORE) { handles[i + 1].setInstruction(new NOP()); } logger.debug(callClass + "." + callMethod + " invoke had arguments, so we NOP it and the line before"); replaced++; } mg.setInstructionList(list); mg.removeNOPs(); mg.setMaxLocals(); mg.setMaxStack(); cg.replaceMethod(method, mg.getMethod()); } } if (handles[i].getInstruction() instanceof NEW) { String callClass = ((NEW) handles[i].getInstruction()).getLoadClassType(cg.getConstantPool()).getClassName(); //logger.debug(callClass); if (!callClass.startsWith("java") && (!callClass.startsWith("org") || callClass.contains("PingBack")) && !cgs.containsKey(callClass)) { handles[i].setInstruction(new NOP()); logger.debug("NOPed out NEW " + callClass); replaced++; } mg.setInstructionList(list); mg.removeNOPs(); mg.setMaxLocals(); mg.setMaxStack(); cg.replaceMethod(method, mg.getMethod()); } } } if (replaced > 0) { logger.debug("Removed " + replaced + " foreign calls in " + cg.getClassName()); } } } public void replaceCheckMethod() { ClassGen cg = cgs.get(AuthClass); Method method = cg.getMethods()[0]; MethodGen mg = new MethodGen(method, cg.getClassName(), cg.getConstantPool()); InstructionList list = new InstructionList(); list.append(new ICONST(1)); list.append(new IRETURN()); mg.removeExceptionHandlers(); mg.removeLineNumbers(); mg.removeLocalVariables(); mg.removeExceptions(); mg.setInstructionList(list); mg.setMaxLocals(); mg.setMaxStack(); cg.replaceMethod(method, mg.getMethod()); } public void fixPOPs() { for (ClassGen cg : cgs.values()) { int replaced = 0; for (Method method : cg.getMethods()) { //logger.debug("in method " + method.getName()); MethodGen mg = new MethodGen(method, cg.getClassName(), cg.getConstantPool()); InstructionList list = mg.getInstructionList(); if (list == null) { continue; } InstructionHandle[] handles = list.getInstructionHandles(); if (handles[0].getInstruction() instanceof DUP || handles[0].getInstruction() instanceof ASTORE || handles[0].getInstruction() instanceof POP) { handles[0].setInstruction(new NOP()); replaced++; } mg.setInstructionList(list); mg.removeNOPs(); mg.setMaxLocals(); mg.setMaxStack(); cg.replaceMethod(method, mg.getMethod()); } if (replaced > 0) { logger.debug("Removed " + replaced + " invalid POPs in " + cg.getClassName()); } } } public void transform() { //logger.log("Removing Exception Handlers..."); //removeExceptions(); logger.log("Removing Foreign Calls..."); RemoveCalls(); logger.log("Fixing DUPs..."); fixPOPs(); if (AuthClass != null) { logger.log("Replacing Authentication System..."); replaceCheckMethod(); } else { logger.error("Auth class wasn't found so we couldn't remove it!"); } logger.log("Deobfuscation finished! Dumping jar..."); dumpJar(JAR_NAME.replace(".jar", "") + "-deob.jar"); logger.log("Operation Completed."); } } ================================================ FILE: jmd-core/src/main/java/net/contra/jmd/transformers/generic/GenericStringDeobfuscator.java ================================================ /* TODO: Write a dynamic string decryptor like the one in SAE; Pattern is LDC INVOKESTATIC, grab the IL from the invokestatic method and put it in a new methodgen and run the LDC through that and replace it with the result Please see http://www.java-tips.org/java-se-tips/java.lang.reflect/invoke-method-using-reflection.html Also ask somebody how you can trace the stack for stringfixer, foreigncallremover AND to get the key, check the method arguments. If it is just string pass the string, otherwise grab the integer above it. If there is more leave it be or attempt to grab the values */ package net.contra.jmd.transformers.generic; import net.contra.jmd.util.GenericClassLoader; import net.contra.jmd.util.GenericMethods; import net.contra.jmd.util.LogHandler; import net.contra.jmd.util.NonClassEntries; import org.apache.bcel.classfile.ClassParser; import org.apache.bcel.classfile.Method; import org.apache.bcel.generic.*; import java.io.File; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; public class GenericStringDeobfuscator { private static LogHandler logger = new LogHandler("GenericStringDeobfuscator"); private Map cgs = new HashMap(); String JAR_NAME; public GenericStringDeobfuscator(String jarfile) throws Exception { File jar = new File(jarfile); JAR_NAME = jarfile; JarFile jf = new JarFile(jar); Enumeration entries = jf.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry == null) { break; } if (entry.getName().endsWith(".class")) { ClassGen cg = new ClassGen(new ClassParser(jf.getInputStream(entry), entry.getName()).parse()); cgs.put(cg.getClassName(), cg); } else { NonClassEntries.add(entry, jf.getInputStream(entry)); } } jf.close(); } public void replaceStrings() { for (ClassGen cg : cgs.values()) { int replaced = 0; for (Method method : cg.getMethods()) { MethodGen mg = new MethodGen(method, cg.getClassName(), cg.getConstantPool()); InstructionList list = mg.getInstructionList(); if (list == null) { continue; } InstructionHandle[] handles = list.getInstructionHandles(); for (int i = 1; i < handles.length; i++) { //java.lang.reflect.Method if (GenericMethods.isCall(handles[i].getInstruction()) && handles[i - 1].getInstruction() instanceof LDC) { String methodCallClass = GenericMethods.getCallClassName(handles[i].getInstruction(), cg.getConstantPool()); String methodCallMethod = GenericMethods.getCallMethodName(handles[i].getInstruction(), cg.getConstantPool()); String methodCallSig = GenericMethods.getCallSignature(handles[i].getInstruction(), cg.getConstantPool()); String methodRetType = GenericMethods.getCallReturnType(handles[i].getInstruction(), cg.getConstantPool()); if (GenericMethods.getCallArgTypes(handles[i].getInstruction(), cg.getConstantPool()).length == 1 && methodCallClass != null && methodCallMethod != null && methodRetType.contains("String")) { //Begin classloader bullshit GenericClassLoader loader = new GenericClassLoader(GenericStringDeobfuscator.class.getClassLoader()); Class cl; ClassGen ourCz = cgs.get(methodCallClass); if (ourCz != null && ourCz.containsMethod(methodCallMethod, methodCallSig) != null) { byte[] bit = ourCz.getJavaClass().getBytes(); logger.debug(methodCallClass + " " + methodCallSig); logger.debug(ourCz.getClassName()); cl = loader.loadClass(ourCz.getClassName(), bit); } else { continue; } if (cl == null) { continue; } java.lang.reflect.Method mthd; try { mthd = cl.getMethod(methodCallMethod, String.class); mthd.setAccessible(true); } catch (NoSuchMethodException e) { continue; } LDC encryptedLDC = (LDC) handles[i - 1].getInstruction(); String encryptedString = encryptedLDC.getValue(cg.getConstantPool()).toString(); String decryptedString; try { decryptedString = mthd.invoke(null, encryptedString).toString(); } catch (Exception e) { continue; } logger.debug(encryptedString + " -> " + decryptedString + " in " + cg.getClassName() + "." + method.getName()); int stringRef = cg.getConstantPool().addString(decryptedString); LDC lc = new LDC(stringRef); NOP nop = new NOP(); handles[i].setInstruction(lc); handles[i - 1].setInstruction(nop); replaced++; } else { continue; } } } mg.setInstructionList(list); mg.setMaxLocals(); mg.setMaxStack(); cg.replaceMethod(method, mg.getMethod()); } if (replaced > 0) { logger.debug("decrypted " + replaced + " strings in class " + cg.getClassName()); } } } public void transform() { logger.log("Generic String Deobfuscator"); logger.log("Still basic (and very buggy)"); replaceStrings(); logger.log("Deobfuscation finished! Dumping jar..."); GenericMethods.dumpJar(JAR_NAME, cgs.values()); logger.log("Operation Completed."); } } ================================================ FILE: jmd-core/src/main/java/net/contra/jmd/transformers/generic/Renamer.java ================================================ package net.contra.jmd.transformers.generic; import net.contra.jmd.util.GenericMethods; import net.contra.jmd.util.LogHandler; import net.contra.jmd.util.NonClassEntries; import org.apache.bcel.classfile.ClassParser; import org.apache.bcel.classfile.Method; import org.apache.bcel.generic.*; import java.io.File; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; public class Renamer { private static LogHandler logger = new LogHandler("Renamer"); private Map cgs = new HashMap(); private Map tempcgs = new HashMap(); private Map methodNames = new HashMap(); //OldName, NewName String JAR_NAME; public Renamer(String jarfile) throws Exception { File jar = new File(jarfile); JAR_NAME = jarfile; JarFile jf = new JarFile(jar); Enumeration entries = jf.entries(); //TODO: Make it not rename the main class //TODO: Keep it from renaming like, methods that shouldn't be renamed and shit?? //TODO: Rename FIELDS MOTHERFUCKER //Manifest jm = jf.getManifest(); //if(jm.getAttributes("Main-class") != null && //logger.debug("Found main class for jar: " + ); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry == null) { break; } if (entry.getName().endsWith(".class")) { ClassGen cg = new ClassGen(new ClassParser(jf.getInputStream(entry), entry.getName()).parse()); cgs.put(cg.getClassName(), cg); } else { NonClassEntries.add(entry, jf.getInputStream(entry)); } } jf.close(); } public void renameClasses() { int classCount = 1; for (ClassGen cg : cgs.values()) { String className = cg.getClassName(); String shortClassName = className.substring(className.lastIndexOf(".") + 1, className.length()); String newClassName = className.replace(shortClassName, "Class" + classCount); cg.setClassName(newClassName); tempcgs.put(newClassName, cg); classCount++; } if (classCount > 1) { logger.debug("Renamed " + classCount + " classes."); cgs = tempcgs; } } public void replaceMethodRefs() { for (ClassGen cg : cgs.values()) { for (Method m : cg.getMethods()) { int replaced = 0; MethodGen mg = new MethodGen(m, cg.getClassName(), cg.getConstantPool()); InstructionList list = mg.getInstructionList(); if (list == null) { continue; } InstructionHandle[] handles = list.getInstructionHandles(); for (InstructionHandle handle : handles) { if (GenericMethods.isCall(handle.getInstruction())) { String oldClassName = GenericMethods.getCallClassName(handle.getInstruction(), cg.getConstantPool()); String oldMethodName = GenericMethods.getCallMethodName(handle.getInstruction(), cg.getConstantPool()); String oldSignature = GenericMethods.getCallSignature(handle.getInstruction(), cg.getConstantPool()); String mod = oldClassName + "-" + oldMethodName + "-" + oldSignature; if (methodNames.containsKey(mod)) { //logger.debug("Accessing " + methodNames.get(mod)); String[] args = methodNames.get(mod).split("-"); String newClassName = args[0]; String newMethodName = args[1]; String newSignature = args[2]; int newindex = cg.getConstantPool().addMethodref(newClassName, newMethodName, newSignature); Instruction newInvoke = GenericMethods.getNewInvoke(handle.getInstruction(), newindex); handle.setInstruction(newInvoke); replaced++; } } } mg.setInstructionList(list); mg.setMaxLocals(); mg.setMaxStack(); if (replaced > 0) { logger.debug("replaced " + replaced + " methodrefs in " + m.getName()); cg.replaceMethod(m, mg.getMethod()); } } } } public void renameMethods() { for (ClassGen cg : cgs.values()) { if (cg.isAbstract() || cg.isInterface()) { continue; } int count = 1; for (Method m : cg.getMethods()) { if (m.getName().equalsIgnoreCase("") || m.getName().equalsIgnoreCase("") || m.getName().equalsIgnoreCase("main")) { continue; } ConstantPoolGen cpg = cg.getConstantPool(); String name = ""; if (m.isStatic()) { name += "static"; } String type = m.getReturnType().toString(); name += type.substring(type.lastIndexOf(".") + 1, type.length()); name = name.replace("void", ""); if (name.contains("[]")) { name = name.replace("[]", "Array"); } name += "Method" + count; //TODO: Get it to fully change the name (updated methodref name index) and not corrupt the constant pool lol //TODO: Rename classes first, then methods, then fields. MethodGen mg = new MethodGen(m, cg.getClassName(), cpg); mg.setName(name); cg.replaceMethod(m, mg.getMethod()); cg.setConstantPool(cpg); //y.pb.methodsig - Class.Method04.MethodSig methodNames.put(cg.getClassName() + "-" + m.getName() + "-" + m.getSignature(), cg.getClassName() + "-" + name + "-" + m.getSignature()); count++; logger.debug(cg.getClassName() + "." + m.getName() + " -> " + cg.getClassName() + "." + name); } } } public void transform() { logger.log("Generic Renamer"); renameClasses(); renameMethods(); replaceMethodRefs(); logger.log("Deobfuscation finished! Dumping jar..."); GenericMethods.dumpJar(JAR_NAME, cgs.values()); logger.log("Operation Completed."); } } ================================================ FILE: jmd-core/src/main/java/net/contra/jmd/transformers/generic/StackFixer.java ================================================ package net.contra.jmd.transformers.generic; import net.contra.jmd.util.GenericMethods; import net.contra.jmd.util.LogHandler; import net.contra.jmd.util.NonClassEntries; import org.apache.bcel.classfile.ClassParser; import org.apache.bcel.classfile.Method; import org.apache.bcel.generic.ClassGen; import org.apache.bcel.generic.MethodGen; import java.io.File; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * Created by IntelliJ IDEA. * User: Eric * Date: Nov 30, 2010 * Time: 4:52:48 AM */ public class StackFixer { private static LogHandler logger = new LogHandler("StackFixer"); private Map cgs = new HashMap(); String JAR_NAME; public StackFixer(String jarfile) throws Exception { File jar = new File(jarfile); JAR_NAME = jarfile; JarFile jf = new JarFile(jar); Enumeration entries = jf.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry == null) { break; } if (entry.getName().endsWith(".class")) { ClassGen cg = new ClassGen(new ClassParser(jf.getInputStream(entry), entry.getName()).parse()); cgs.put(cg.getClassName(), cg); } else { NonClassEntries.add(entry, jf.getInputStream(entry)); } } jf.close(); } public void fixStack() { for (ClassGen cg : cgs.values()) { for (Method method : cg.getMethods()) { MethodGen mg = new MethodGen(method, cg.getClassName(), cg.getConstantPool()); mg.removeNOPs(); mg.setMaxLocals(); mg.setMaxStack(); cg.replaceMethod(method, mg.getMethod()); logger.debug(String.format("Reset MaxStack and MaxLocals in %s.%s", cg.getClassName(), mg.getName())); } } } public void transform() { logger.log("Starting StackFixer"); fixStack(); logger.log("Deobfuscation finished! Dumping jar..."); GenericMethods.dumpJar(JAR_NAME, cgs.values()); logger.log("Operation Completed."); } } ================================================ FILE: jmd-core/src/main/java/net/contra/jmd/transformers/generic/StringFixer.java ================================================ package net.contra.jmd.transformers.generic; import net.contra.jmd.util.GenericMethods; import net.contra.jmd.util.LogHandler; import net.contra.jmd.util.NonClassEntries; import org.apache.bcel.classfile.ClassParser; import org.apache.bcel.classfile.Method; import org.apache.bcel.generic.*; import org.apache.commons.io.IOUtils; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; public class StringFixer { private static LogHandler logger = new LogHandler("StringFixer"); private Map cgs = new HashMap(); String JAR_NAME; boolean replacing = false; public StringFixer(String jarfile) throws Exception { File jar = new File(jarfile); JAR_NAME = jarfile; JarFile jf = new JarFile(jar); Enumeration entries = jf.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry == null) { break; } if (entry.getName().endsWith(".class")) { ClassGen cg = new ClassGen(new ClassParser(jf.getInputStream(entry), entry.getName()).parse()); cgs.put(cg.getClassName(), cg); } else { NonClassEntries.add(entry, jf.getInputStream(entry)); } } jf.close(); } public void removeBASA() { for (ClassGen cg : cgs.values()) { for (Method method : cg.getMethods()) { String type = ""; MethodGen mg = new MethodGen(method, cg.getClassName(), cg.getConstantPool()); InstructionList list = mg.getInstructionList(); if (list == null) { continue; } InstructionHandle[] handles = list.getInstructionHandles(); int startLoc = -1; int endLoc = -1; int arrayLength = -1; for (int i = 0; i < handles.length; i++) { if ((handles[i].getInstruction() instanceof NEW) && (handles[i + 1].getInstruction() instanceof DUP) && GenericMethods.isNumber(handles[i + 2].getInstruction()) && (handles[i + 3].getInstruction() instanceof NEWARRAY)) { String newType = ((NEW) handles[i].getInstruction()).getLoadClassType(cg.getConstantPool()).toString(); type = ((NEWARRAY) handles[i + 3].getInstruction()).getType().toString(); logger.debug("Found new array conversion pattern: " + type + "->" + newType + " in " + cg.getClassName() + "." + method.getName()); //if(newType.equals("java.lang.String") && (type.equals("byte[]") || type.equals("char[]"))) { if (newType.equals("java.lang.String") && type.contains("[]")) { startLoc = i; arrayLength = GenericMethods.getValueOfNumber(handles[i + 2].getInstruction(), cg.getConstantPool()); //logger.debug("Start Location for BASA replacement: " + startLoc); //logger.debug("Length of Array: " + arrayLength); } } if (startLoc >= 0 && arrayLength >= 1) { if (handles.length > (i + 1)) { if ((handles[i].getInstruction() instanceof BASTORE) && GenericMethods.isNumber(handles[i - 1].getInstruction()) && GenericMethods.isNumber(handles[i - 2].getInstruction()) && (handles[i - 3].getInstruction() instanceof DUP)) { /* TODO: Also you need to make the endLoc INVOKESTATIC STRING or something because it is leaving a trailing command and fucking up. */ if (GenericMethods.isCall(handles[i + 1].getInstruction())) { if (GenericMethods.getCallArgTypes(handles[i + 1].getInstruction(), cg.getConstantPool()) != null) { Type[] tp = GenericMethods.getCallArgTypes(handles[i + 1].getInstruction(), cg.getConstantPool()); String lasttp = tp[tp.length - 1].toString(); if (!lasttp.contains(type)) { int tendLoc = GenericMethods.getValueOfNumber(handles[i - 2].getInstruction(), cg.getConstantPool()); if (tendLoc == (arrayLength - 1)) { endLoc = i; //logger.debug("End Location for BASA replacement: " + endLoc); } } else { logger.debug("Can't replace String(" + type + "), following method call has argument of same type"); startLoc = -1; endLoc = -1; arrayLength = -1; continue; } } } } } } if ((startLoc >= 0) && (endLoc >= 0) && (arrayLength > 1) && (endLoc < handles.length)) { byte[] stringBytes = new byte[arrayLength]; int loc = 0; for (int x = startLoc; x <= endLoc; x++) { if (GenericMethods.isNumber(handles[x].getInstruction()) && GenericMethods.isNumber(handles[x + 1].getInstruction()) && (handles[x + 2].getInstruction() instanceof BASTORE) && (GenericMethods.getValueOfNumber(handles[x].getInstruction(), cg.getConstantPool()) == loc)) { stringBytes[loc] = (byte) GenericMethods.getValueOfNumber(handles[x + 1].getInstruction(), cg.getConstantPool()); loc++; if (loc == stringBytes.length) { if (replacing) { int stringRef = cg.getConstantPool().addString(new String(stringBytes)); LDC lc = new LDC(stringRef); handles[startLoc].setInstruction(lc); for (int z = startLoc + 1; z <= endLoc; z++) { NOP nop = new NOP(); handles[z].setInstruction(nop); } logger.debug("Replaced conversion with string " + new String(stringBytes) + " in " + cg.getClassName() + "." + method.getName()); } else { logger.debug("Found conversion, string:" + new String(stringBytes)); } startLoc = -1; endLoc = -1; arrayLength = -1; type = ""; mg.setInstructionList(list); mg.removeNOPs(); mg.setMaxLocals(); mg.setMaxStack(); cg.replaceMethod(method, mg.getMethod()); continue; } } } } } } } } public void dumpJar(String path) { FileOutputStream os; try { os = new FileOutputStream(new File(path)); } catch (FileNotFoundException fnfe) { throw new RuntimeException("could not create file \"" + path + "\": " + fnfe); } JarOutputStream jos; try { jos = new JarOutputStream(os); for (ClassGen classIt : cgs.values()) { jos.putNextEntry(new JarEntry(classIt.getClassName().replace('.', '/') + ".class")); jos.write(classIt.getJavaClass().getBytes()); jos.closeEntry(); jos.flush(); } for (JarEntry jbe : NonClassEntries.entries) { JarEntry destEntry = new JarEntry(jbe.getName()); byte[] bite = IOUtils.toByteArray(NonClassEntries.ins.get(jbe)); jos.putNextEntry(destEntry); jos.write(bite); jos.closeEntry(); } jos.closeEntry(); jos.close(); } catch (IOException ioe) { } } public void transform() { logger.log("Generic String Fixer"); logger.log("This hasn't been finished yet."); logger.log("Scanning/Replacing Strings hidden as byte arrays..."); removeBASA(); logger.log("Deobfuscation finished! Dumping jar..."); dumpJar(JAR_NAME.replace(".jar", "") + "-deob.jar"); logger.log("Operation Completed."); } } ================================================ FILE: jmd-core/src/main/java/net/contra/jmd/transformers/generic/StringScanner.java ================================================ package net.contra.jmd.transformers.generic; import net.contra.jmd.util.LogHandler; import net.contra.jmd.util.NonClassEntries; import org.apache.bcel.classfile.ClassParser; import org.apache.bcel.classfile.Method; import org.apache.bcel.generic.*; import java.io.File; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; public class StringScanner { private static LogHandler logger = new LogHandler("StringScanner"); private Map cgs = new HashMap(); boolean replaceMode = false; String substitute = ""; String inputScan = ""; public StringScanner(String jarfile, String scanstring, boolean replace, String replacestring) throws Exception { inputScan = scanstring; replaceMode = replace; substitute = replacestring; File jar = new File(jarfile); JarFile jf = new JarFile(jar); Enumeration entries = jf.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry == null) { break; } if (entry.getName().endsWith(".class")) { ClassGen cg = new ClassGen(new ClassParser(jf.getInputStream(entry), entry.getName()).parse()); cgs.put(cg.getClassName(), cg); } else { NonClassEntries.add(entry, jf.getInputStream(entry)); } } jf.close(); } public void searchConstantPool() { for (ClassGen cg : cgs.values()) { for (Method m : cg.getMethods()) { MethodGen mg = new MethodGen(m, cg.getClassName(), cg.getConstantPool()); InstructionList list = mg.getInstructionList(); InstructionHandle[] handles; if (list != null && list.size() > 0) { handles = list.getInstructionHandles(); } else { break; } for (InstructionHandle handle : handles) { if (handle.getInstruction() instanceof LDC) { LDC newldc = (LDC) handle.getInstruction(); String val = newldc.getValue(cg.getConstantPool()).toString(); if (val.contains(inputScan)) { if (!replaceMode) { logger.log(val + " in " + cg.getClassName() + "." + m.getName()); } else { String newz = val.replace(inputScan, substitute); int stringRef = cg.getConstantPool().addString(newz); handle.setInstruction(new LDC(stringRef)); logger.log(val + "->" + newz + " in " + cg.getClassName() + "." + m.getName()); } } } } if (replaceMode) { list.setPositions(); mg.setMaxLocals(); mg.setMaxStack(); cg.replaceMethod(m, mg.getMethod()); } } } } public void scan() { logger.log("Generic URL Scanner/Replacer"); if (!replaceMode) { logger.log("Scanning for Strings containing " + inputScan); } else { logger.log("Replacing " + inputScan + " with " + substitute); } searchConstantPool(); logger.log("Operation Completed."); } } ================================================ FILE: jmd-core/src/main/java/net/contra/jmd/transformers/generic/TransformerTemplate.java ================================================ package net.contra.jmd.transformers.generic; import net.contra.jmd.util.GenericMethods; import net.contra.jmd.util.LogHandler; import org.apache.bcel.generic.ClassGen; import java.util.HashMap; import java.util.Map; public class TransformerTemplate { private static LogHandler logger = new LogHandler("StringFixer"); private Map cgs = new HashMap(); String JAR_NAME; public void transform() { logger.log("Generic Transformer"); logger.log("Deobfuscation finished! Dumping jar..."); GenericMethods.dumpJar(JAR_NAME, cgs.values()); logger.log("Operation Completed."); } } ================================================ FILE: jmd-core/src/main/java/net/contra/jmd/transformers/jshrink/JShrinkTransformer.java ================================================ package net.contra.jmd.transformers.jshrink; import net.contra.jmd.transformers.Transformer; import net.contra.jmd.util.GenericMethods; import net.contra.jmd.util.LogHandler; import net.contra.jmd.util.NonClassEntries; import org.apache.bcel.classfile.ClassParser; import org.apache.bcel.classfile.Method; import org.apache.bcel.generic.*; import java.io.File; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; public class JShrinkTransformer implements Transformer { private static LogHandler logger = new LogHandler("JShrinkTransformer"); private Map cgs = new HashMap(); String JAR_NAME; ClassGen LoaderClass = null; public JShrinkTransformer(String jarfile) throws Exception { File jar = new File(jarfile); JAR_NAME = jarfile; JarFile jf = new JarFile(jar); Enumeration entries = jf.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry == null) { break; } if (entry.getName().endsWith(".class")) { ClassGen cg = new ClassGen(new ClassParser(jf.getInputStream(entry), entry.getName()).parse()); String className = entry.getName().replace(".class", "").replace("\\", "/").replace("/", "."); if (isLoader(cg) && LoaderClass == null) { logger.debug("Found JShrink Loader! " + cg.getClassName()); LoaderClass = cg; } else { cgs.put(className, cg); } } else { if (!entry.isDirectory()) { NonClassEntries.add(entry, jf.getInputStream(entry)); } } } logger.debug("Classes loaded from JAR"); if (LoaderClass == null) { logger.error("Loader class not found! Class not using JShrink"); } jf.close(); } public boolean isLoader(ClassGen cg) { return cg.getMethods().length == 3 && cg.getMethods()[1].isStatic() && cg.getMethods()[1].isFinal() && cg.getMethods()[1].isPublic() && cg.getMethods()[1].isSynchronized() && cg.getMethods()[1].getReturnType().toString().equals("java.lang.String"); } public void replaceStrings() throws TargetLostException { for (ClassGen cg : cgs.values()) { int replaced = 0; for (Method method : cg.getMethods()) { MethodGen mg = new MethodGen(method, cg.getClassName(), cg.getConstantPool()); InstructionList list = mg.getInstructionList(); if (list == null) { continue; } InstructionHandle[] handles = list.getInstructionHandles(); for (int i = 1; i < handles.length; i++) { if (handles[i].getInstruction() instanceof INVOKESTATIC && GenericMethods.isNumber(handles[i - 1].getInstruction())) { INVOKESTATIC methodCall = (INVOKESTATIC) handles[i].getInstruction(); if (methodCall.getClassName(cg.getConstantPool()).contains(LoaderClass.getClassName())) { int push = GenericMethods.getValueOfNumber(handles[i - 1].getInstruction(), cg.getConstantPool()); String decryptedString = StoreHandler.I(push); if (decryptedString != null) { int stringRef = cg.getConstantPool().addString(decryptedString); LDC lc = new LDC(stringRef); NOP nop = new NOP(); handles[i].setInstruction(lc); handles[i - 1].setInstruction(nop); replaced++; } } } } mg.setInstructionList(list); mg.removeNOPs(); mg.setMaxLocals(); mg.setMaxStack(); cg.replaceMethod(method, mg.getMethod()); } if (replaced > 0) { logger.debug("replaced " + replaced + " calls in class " + cg.getClassName()); } } } public void transform() { try { logger.log("Starting String Replacer..."); replaceStrings(); } catch (TargetLostException e) { e.printStackTrace(); } logger.log("Deobfuscation Finished! Dumping jar..."); GenericMethods.dumpJar(JAR_NAME, cgs.values()); logger.log("Operation Completed."); } } ================================================ FILE: jmd-core/src/main/java/net/contra/jmd/transformers/jshrink/StoreHandler.java ================================================ package net.contra.jmd.transformers.jshrink; import net.contra.jmd.util.NonClassEntries; import java.io.InputStream; /** * Created by IntelliJ IDEA. * User: Eric * Date: Nov 29, 2010 * Time: 10:25:20 PM */ public class StoreHandler { static byte[] WSVZ; static String[] append = new String[256]; static int[] close = new int[256]; public static synchronized String I(int paramInt) { int i = paramInt & 0xFF; if (close[i] != paramInt) { close[i] = paramInt; if (paramInt < 0) { paramInt &= 65535; } String str = new String(WSVZ, paramInt, WSVZ[(paramInt - 1)] & 0xFF).intern(); append[i] = str; } return append[i]; } static { try { InputStream localInputStream = NonClassEntries.ins.get(NonClassEntries.getByName("I/I.gif")); if (localInputStream != null) { int i = localInputStream.read() << 16 | localInputStream.read() << 8 | localInputStream.read(); WSVZ = new byte[i]; int j = 0; int k = (byte) i; byte[] arrayOfByte = WSVZ; while (i != 0) { int m = localInputStream.read(arrayOfByte, j, i); if (m == -1) { break; } i -= m; m += j; while (j < m) { int int2 = j; arrayOfByte[int2] = (byte) (arrayOfByte[int2] ^ k); j++; } } localInputStream.reset(); localInputStream.close(); } } catch (Exception localException) { } } } ================================================ FILE: jmd-core/src/main/java/net/contra/jmd/transformers/smokescreen/SmokeScreenTransformer.java ================================================ package net.contra.jmd.transformers.smokescreen; import net.contra.jmd.transformers.Transformer; import net.contra.jmd.util.GenericMethods; import net.contra.jmd.util.LogHandler; import net.contra.jmd.util.NonClassEntries; import org.apache.bcel.classfile.ClassParser; import org.apache.bcel.classfile.Method; import org.apache.bcel.generic.*; import org.apache.bcel.util.InstructionFinder; import java.io.File; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; public class SmokeScreenTransformer implements Transformer { private static LogHandler logger = new LogHandler("SmokeScreenTransformer"); private Map cgs; private Map ssStrings = new HashMap(); String JAR_NAME; public String getActualString(String className, int i1, int i2) { return ssStrings.get(className).substring(i1, i2); } public void replaceStrings() { for (ClassGen cg : cgs.values()) { int replaced = 0; for (Method m : cg.getMethods()) { MethodGen mg = new MethodGen(m, cg.getClassName(), cg.getConstantPool()); InstructionList list = mg.getInstructionList(); if (list == null) { return; } InstructionHandle[] handles = list.getInstructionHandles(); for (int x = 0; x < handles.length; x++) { if (x + 3 < handles.length) { if (handles[x].getInstruction() instanceof GETSTATIC && GenericMethods.isNumber(handles[x + 1].getInstruction()) && GenericMethods.isNumber(handles[x + 2].getInstruction()) && GenericMethods.isCall(handles[x + 3].getInstruction())) { if (GenericMethods.getCallMethodName(handles[x + 3].getInstruction(), cg.getConstantPool()).contains("substring")) { int con1 = GenericMethods.getValueOfNumber(handles[x + 1].getInstruction(), cg.getConstantPool()); int con2 = GenericMethods.getValueOfNumber(handles[x + 2].getInstruction(), cg.getConstantPool()); NOP nop = new NOP(); int stringRef = cg.getConstantPool().addString(getActualString(cg.getClassName(), con1, con2)); LDC data = new LDC(stringRef); handles[x].setInstruction(data); handles[x + 1].setInstruction(nop); handles[x + 2].setInstruction(nop); handles[x + 3].setInstruction(nop); replaced++; mg.setInstructionList(list); mg.removeNOPs(); mg.setMaxLocals(); mg.setMaxStack(); cg.replaceMethod(m, mg.getMethod()); continue; } } } } } if (replaced > 0) { logger.debug("Replaced " + replaced + " calls in class " + cg.getClassName()); } } } public void grabStrings() { for (ClassGen cg : cgs.values()) { for (Method m : cg.getMethods()) { int key = 0; MethodGen mg = new MethodGen(m, cg.getClassName(), cg.getConstantPool()); InstructionList list = mg.getInstructionList(); if (list == null) { return; } InstructionHandle[] handles = list.getInstructionHandles(); for (int x = 0; x < handles.length; x++) { if (x + 3 < handles.length) { if (handles[x].getInstruction() instanceof LDC && handles[x + 1].getInstruction() instanceof ASTORE && GenericMethods.isNumber(handles[x + 2].getInstruction()) && handles[x + 3].getInstruction() instanceof ISTORE) { key = GenericMethods.getValueOfNumber(handles[x + 2].getInstruction(), cg.getConstantPool()); LDC tx = (LDC) handles[x].getInstruction(); String encryptedContent = tx.getValue(cg.getConstantPool()).toString(); String decryptedContent = decrypt(encryptedContent, key); logger.debug("Found key for " + cg.getClassName() + ": " + key); logger.debug("Strings for class: " + decryptedContent); ssStrings.put(cg.getClassName(), decryptedContent); continue; } } } } } } public SmokeScreenTransformer(String jarfile) throws Exception { cgs = new HashMap(); File jar = new File(jarfile); JAR_NAME = jarfile; JarFile jf = new JarFile(jar); Enumeration entries = jf.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry == null) { break; } if (entry.getName().endsWith(".class")) { ClassGen cg = new ClassGen(new ClassParser(jf.getInputStream(entry), entry.getName()).parse()); String className = entry.getName().replace(".class", "").replace("\\", "/").replace("/", "."); cgs.put(className, cg); } else { NonClassEntries.add(entry, jf.getInputStream(entry)); } } logger.debug("Classes loaded from JAR"); jf.close(); } public static String decrypt(String encrypted, int myKey) { int key = myKey; char[] encChars = encrypted.toCharArray(); char[] tmpChars = new char[encChars.length]; for (int j = 0; j < encChars.length; j++) { tmpChars = encChars; tmpChars[j] = (char) (tmpChars[j] ^ key); key = (char) (key + encChars[j] & 0x3E); } return new String(tmpChars); } public void transform() { logger.log("SmokeScreen Deobfuscator"); logger.log("Decrypting Strings..."); grabStrings(); logger.log("Replacing string calls with LDC"); replaceStrings(); logger.log("Starting Exit Flow Corrector..."); exitFlowTransformer(); logger.log("Removing Unconditional Branches..."); unconditionalBranchTransformer(); logger.log("Deobfuscation finished! Dumping jar..."); GenericMethods.dumpJar(JAR_NAME, cgs.values()); logger.log("Operation Completed."); } public void unconditionalBranchTransformer() { for (ClassGen cg : cgs.values()) { for (Method method : cg.getMethods()) { final MethodGen mg = new MethodGen(method, cg.getClassName(), cg.getConstantPool()); if (method.isAbstract() || method.isNative()) { return; } final InstructionList list = mg.getInstructionList(); InstructionFinder finder = new InstructionFinder(list); final ConstantPoolGen cpg = cg.getConstantPool(); int branchesSimplified = 0; Iterator matches = finder.search("IfInstruction"); while (matches.hasNext()) { InstructionHandle ifHandle = matches.next()[0]; InstructionHandle target = ((BranchHandle) ifHandle).getTarget(); if (target.getInstruction() instanceof GOTO) { branchesSimplified++; ((BranchHandle) ifHandle).setTarget(((BranchHandle) target).getTarget()); } } matches = finder.search("GOTO GOTO"); while (matches.hasNext()) { InstructionHandle[] match = matches.next(); try { list.delete(match[0]); } catch (TargetLostException tlex) { for (InstructionHandle target : tlex.getTargets()) { for (InstructionTargeter targeter : target.getTargeters()) { targeter.updateTarget(target, match[1]); } } } } mg.setInstructionList(list); mg.setMaxLocals(); mg.setMaxStack(); if (branchesSimplified > 0) { logger.debug("simplified " + branchesSimplified + " unconditional branches"); cg.replaceMethod(method, mg.getMethod()); } } } } public void exitFlowTransformer() { for (ClassGen cg : cgs.values()) { int correct = 0; for (Method method : cg.getMethods()) { final MethodGen mgen = new MethodGen(method, cg.getClassName(), cg.getConstantPool()); if (!method.isAbstract() && !method.isNative()) { InstructionList list = mgen.getInstructionList(); InstructionFinder finder = new InstructionFinder(list); CodeExceptionGen[] exceptionGens = mgen.getExceptionHandlers(); for (Iterator matches = finder.search( "ASTORE ALOAD (NEW DUP (PushInstruction InvokeInstruction)+ (ALOAD IfInstruction LDC GOTO LDC " + "INVOKEVIRTUAL)? (PushInstruction InvokeInstruction)* InvokeInstruction+)?"); matches.hasNext();) { /* thanks to popcorn89 */ InstructionHandle[] match = matches.next(); if (!(match[match.length - 1].getInstruction() instanceof ATHROW)) { continue; } InstructionHandle astoreInstr = match[0]; InstructionHandle athrowInstr = match[match.length - 1]; InstructionHandle toRedirect = athrowInstr.getNext(); for (CodeExceptionGen exgen : exceptionGens) { if (exgen.getHandlerPC().equals(astoreInstr)) { mgen.removeExceptionHandler(exgen); } } try { list.delete(astoreInstr, athrowInstr); } catch (TargetLostException tlex) { if (athrowInstr == list.getEnd()) { toRedirect = astoreInstr.getPrev(); } for (InstructionHandle target : tlex.getTargets()) { for (InstructionTargeter targeter : target.getTargeters()) { targeter.updateTarget(target, toRedirect); } } } } list.setPositions(true); InstructionHandle lastInstr = list.getEnd(); InstructionHandle secondToLastInstr = lastInstr.getPrev(); if (secondToLastInstr != null && (lastInstr.getInstruction() instanceof RETURN) && (secondToLastInstr.getInstruction() instanceof RETURN)) { try { list.delete(secondToLastInstr); } catch (TargetLostException tlex) { for (InstructionHandle target : tlex.getTargets()) { for (InstructionTargeter targeter : target.getTargeters()) { targeter.updateTarget(target, lastInstr); } } } } if (mgen.getMethod() != method) { correct++; //logger.debug("corrected exit flow in " + cg.getClassName() + "." + mgen.getName() + mgen.getSignature()); mgen.setInstructionList(list); mgen.setMaxLocals(); mgen.setMaxStack(); cg.replaceMethod(method, mgen.getMethod()); } } } logger.debug("Corrected exit flow " + correct + " times in " + cg.getClassName()); } } } ================================================ FILE: jmd-core/src/main/java/net/contra/jmd/transformers/zkm/ZKMTransformer.java ================================================ package net.contra.jmd.transformers.zkm; import net.contra.jmd.transformers.Transformer; import net.contra.jmd.util.GenericMethods; import net.contra.jmd.util.LogHandler; import net.contra.jmd.util.NonClassEntries; import org.apache.bcel.classfile.*; import org.apache.bcel.generic.*; import org.apache.bcel.util.*; import java.io.File; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; public class ZKMTransformer implements Transformer { private static LogHandler logger = new LogHandler("ZKMTransformer"); private Map cgs = new HashMap(); private Map> zkStrings = new HashMap>(); String JAR_NAME; private List flowObstructors = new LinkedList(); private Field controlField = null; private String controlClass = ""; public String getZKMString(String className, int index) { return zkStrings.get(className).get(index); } public ZKMTransformer(String jarfile) throws Exception { File jar = new File(jarfile); JAR_NAME = jarfile; JarFile jf = new JarFile(jar); Enumeration entries = jf.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry == null) { break; } if (entry.getName().endsWith(".class")) { ClassGen cg = new ClassGen(new ClassParser(jf.getInputStream(entry), entry.getName()).parse()); String className = entry.getName().replace(".class", "").replace("\\", "/").replace("/", "."); cgs.put(className, cg); } else { NonClassEntries.add(entry, jf.getInputStream(entry)); } } logger.debug("Classes loaded from JAR"); jf.close(); } public static boolean typeA(ClassGen cg) { for (Method m : cg.getMethods()) { if (m.getArgumentTypes().length == 1 && m.getArgumentTypes()[0].equals(Type.getType(char[].class))) { return true; } } return false; } public static char[] findKeyC(ClassGen cg) { for (Method m : cg.getMethods()) { if (m.getName().contains("clinit")) { MethodGen mg = new MethodGen(m, cg.getClassName(), cg.getConstantPool()); InstructionHandle[] handles = mg.getInstructionList().getInstructionHandles(); char[] keyAsChars = new char[5]; int found = 0; for (int i = handles.length - 1; i > 0; i--) { if (found < 5) { //TODO: FIX THIS, THE LAST TWO CHARS FOR THE KEY ARE BEING FOUND PROPERLY BUT SAVED TO THE ARRAY WRONG if ((handles[i - 1].getInstruction() instanceof BIPUSH) && ((handles[i].getInstruction() instanceof GOTO && ((GOTO) handles[i].getInstruction()).getTarget().getInstruction() instanceof IXOR) || handles[i].getInstruction() instanceof IXOR)) { keyAsChars[found] = (char) ((BIPUSH) handles[i - 1].getInstruction()).getValue().intValue(); logger.debug(found + " found key char: " + (int) keyAsChars[found] + " line: " + i); found++; } else if ((handles[i - 1].getInstruction() instanceof ICONST) && ((handles[i].getInstruction() instanceof GOTO && ((GOTO) handles[i].getInstruction()).getTarget().getInstruction() instanceof IXOR) || handles[i].getInstruction() instanceof IXOR)) { keyAsChars[found] = (char) ((ICONST) handles[i - 1].getInstruction()).getValue().intValue(); logger.debug(found + " found key char: " + (int) keyAsChars[found] + " line: " + i); found++; } } else { break; } } char[] right = new char[5]; right[0] = keyAsChars[4]; right[1] = keyAsChars[3]; right[2] = keyAsChars[2]; right[3] = keyAsChars[1]; right[4] = keyAsChars[0]; for (char c : right) { logger.debug("KeyChar: " + (int) c); } if (keyAsChars == new char[5]) { logger.error("ZKM Key Method C Failed, please send in this file to be analyzed."); return null; } else { logger.debug("ZKM Key = " + String.valueOf(right)); return right; } } } return null; } public static char[] findKeyB(ClassGen cg) { return findKeyC(cg); /* for(Method m : cg.getMethods()) { if(m.getName().contains("clinit")) { MethodGen mg = new MethodGen(m, cg.getClassName(), cg.getConstantPool()); InstructionHandle[] handles = mg.getInstructionList().getInstructionHandles(); char[] keyAsChars = new char[5]; int found = 0; int ixor = -1; for(int i = handles.length - 1; i < handles.length; i--){ if(handles[i].getInstruction() instanceof IXOR){ ixor = i; break; } } if(ixor == -1){ logger.error("ZKM Key Method B Failed, attempting method C"); return findKeyC(cg); } for(int i = ixor; i > 0; i--) { if(found < 5) { if((handles[i - 1].getInstruction() instanceof BIPUSH) && (handles[i].getInstruction() instanceof GOTO || handles[i].getInstruction() instanceof IXOR)) { keyAsChars[found] = (char) ((BIPUSH) handles[i - 1].getInstruction()).getValue().intValue(); logger.debug("found key char: " + (char) found); found++; } else if((handles[i - 1].getInstruction() instanceof ICONST) && (handles[i].getInstruction() instanceof GOTO || handles[i].getInstruction() instanceof IXOR)) { keyAsChars[found] = (char) ((ICONST) handles[i - 1].getInstruction()).getValue().intValue(); found++; logger.debug("found key char: " + (char) found); } } else { break; } } char[] right = keyAsChars; right[0] = keyAsChars[4]; right[1] = keyAsChars[3]; right[2] = keyAsChars[2]; right[3] = keyAsChars[1]; right[4] = keyAsChars[0]; if(keyAsChars == new char[5]) { logger.error("ZKM Key Method B Failed, please send in this file to be analyzed."); return null; } else { logger.debug("ZKM Key = " + String.valueOf(right)); return right; } } } return null; */ } public static char[] findKey(ClassGen cg) { if (typeA(cg)) { System.out.println("Type A Found!"); for (Method m : cg.getMethods()) { if (m.getArgumentTypes().length == 1 && m.getArgumentTypes()[0].equals(Type.getType(char[].class))) { MethodGen mg = new MethodGen(m, cg.getClassName(), cg.getConstantPool()); InstructionHandle[] handles = mg.getInstructionList().getInstructionHandles(); char[] keyAsChars = new char[5]; for (InstructionHandle handle : handles) { if (handle.getInstruction() instanceof TABLESWITCH) { TABLESWITCH xor = (TABLESWITCH) handle.getInstruction(); for (int a = 0; a < xor.getTargets().length; a++) { Instruction target = xor.getTargets()[a].getInstruction(); if (target instanceof BIPUSH) { keyAsChars[a] = (char) ((BIPUSH) target).getValue().intValue(); } else { keyAsChars[a] = (char) ((ICONST) target).getValue().intValue(); } } Instruction target = xor.getTarget().getInstruction(); if (target instanceof BIPUSH) { keyAsChars[4] = (char) ((BIPUSH) target).getValue().intValue(); } else { keyAsChars[4] = (char) ((ICONST) target).getValue().intValue(); } } } return keyAsChars; } } } else { for (Method m : cg.getMethods()) { if (m.getName().contains("clinit")) { MethodGen mg = new MethodGen(m, cg.getClassName(), cg.getConstantPool()); InstructionHandle[] handles = mg.getInstructionList().getInstructionHandles(); char[] keyAsChars; for (InstructionHandle handle : handles) { if (handle.getInstruction() instanceof TABLESWITCH) { TABLESWITCH xor = (TABLESWITCH) handle.getInstruction(); keyAsChars = getKeyFromSwitch(xor, cg); if (keyAsChars != null) { return keyAsChars; } } } return findKeyB(cg); } } } return null; } private static char[] getKeyFromSwitch(TABLESWITCH xor, ClassGen cg) { char[] keyAsChars = new char[5]; for (int a = 0; a < xor.getTargets().length; a++) { Instruction target = xor.getTargets()[a].getInstruction(); if (GenericMethods.isNumber(target)) { keyAsChars[a] = (char) GenericMethods.getValueOfNumber(target, cg.getConstantPool()); } else { return null; } } Instruction target = xor.getTarget().getInstruction(); if (target instanceof BIPUSH) { keyAsChars[4] = (char) ((BIPUSH) target).getValue().intValue(); } else if (target instanceof ICONST) { keyAsChars[4] = (char) ((ICONST) target).getValue().intValue(); } else { return null; } return keyAsChars; } public static String decrypt(String encrypted, char[] key) { char[] plainText = encrypted.toCharArray(); int plainTextLength = plainText.length; int keyLength = key.length; // // encryption char[] cryptoText = new char[plainTextLength]; for (int i = 0; i < plainTextLength; i++) { cryptoText[i] = (char) (plainText[i] ^ key[i % keyLength]); } // finishing return new String(cryptoText); } public void replaceStrings() throws TargetLostException { for (ClassGen cg : cgs.values()) { int replaced = 0; for (Method method : cg.getMethods()) { MethodGen mg = new MethodGen(method, cg.getClassName(), cg.getConstantPool()); InstructionList list = mg.getInstructionList(); if (list == null) continue; InstructionHandle[] handles = list.getInstructionHandles(); //if (!typeA(cg)) { for (int i = 0; i < handles.length; i++) { if ((handles[i].getInstruction() instanceof GETSTATIC) && ((handles[i + 1].getInstruction() instanceof BIPUSH) || (handles[i + 1].getInstruction() instanceof SIPUSH) || (handles[i + 1].getInstruction() instanceof ICONST)) && (handles[i + 2].getInstruction() instanceof AALOAD)) { int push; if (handles[i + 1].getInstruction() instanceof BIPUSH) { push = ((BIPUSH) handles[i + 1].getInstruction()).getValue().intValue(); } else if (handles[i + 1].getInstruction() instanceof SIPUSH) { push = ((SIPUSH) handles[i + 1].getInstruction()).getValue().intValue(); } else { push = ((ICONST) handles[i + 1].getInstruction()).getValue().intValue(); } String decryptedString = getZKMString(cg.getClassName(), push); int stringRef = cg.getConstantPool().addString(decryptedString); LDC lc = new LDC(stringRef); NOP nop = new NOP(); handles[i].setInstruction(lc); handles[i + 1].setInstruction(nop); handles[i + 2].setInstruction(nop); replaced++; } } mg.setInstructionList(list); mg.setMaxLocals(); mg.setMaxStack(); cg.replaceMethod(method, mg.getMethod()); /* } else { logger.error("This type of ZKM is not currently supported for string deob!"); } */ } if (replaced > 0) { logger.debug("replaced " + replaced + " calls in class " + cg.getClassName()); } } } public void removeOriginStrings() { for (ClassGen cg : cgs.values()) { for (Method method : cg.getMethods()) { if (method.getName().contains("clinit")) { MethodGen mg = new MethodGen(method, cg.getClassName(), cg.getConstantPool()); InstructionList list = mg.getInstructionList(); InstructionHandle[] handles = list.getInstructionHandles(); int startLoc = -1; int endLoc = -1; for (int i = 0; i < handles.length; i++) { if (((handles[i].getInstruction() instanceof BIPUSH) || (handles[i].getInstruction() instanceof SIPUSH) || (handles[i].getInstruction() instanceof ICONST)) && (handles[i + 1].getInstruction() instanceof ANEWARRAY)) { ANEWARRAY an = (ANEWARRAY) handles[i + 1].getInstruction(); ObjectType ty = an.getLoadClassType(cg.getConstantPool()); if (ty == null) continue; String type = ty.toString(); if (type.equals("java.lang.String")) { startLoc = i; logger.debug("Start Location for removal: " + startLoc); } } if (startLoc >= 0) { if (handles.length > (i + 2)) { if (handles[i].getInstruction() instanceof POP && handles[i + 1].getInstruction() instanceof SWAP && handles[i + 2].getInstruction() instanceof TABLESWITCH) { endLoc = i + 2; logger.debug("End Location for removal: " + endLoc); break; } } } } if ((startLoc >= 0) && (endLoc >= 0)) { try { list.delete(handles[startLoc], handles[endLoc]); } catch (TargetLostException e) { logger.error("Control flow obfuscation evident. Couldn't clear clinit"); //e.printStackTrace(); return; } logger.debug("NOPed " + (endLoc - startLoc) + " instructions from in " + cg.getClassName()); list.setPositions(); mg.setInstructionList(list); mg.setMaxLocals(); mg.setMaxStack(); cg.replaceMethod(method, mg.getMethod()); } } } } } //TODO: It isn't finding the last string sometimes, so shit gets all fucked up and it ends up leaving a call to static{} and extra instructions public void getStringsFromZKM() { for (ClassGen cg : cgs.values()) { char[] key = findKey(cg); for (Method method : cg.getMethods()) { MethodGen mg = new MethodGen(method, cg.getClassName(), cg.getConstantPool()); InstructionList list = mg.getInstructionList(); if (list == null) { continue; } InstructionHandle[] handles = list.getInstructionHandles(); ArrayList all = new ArrayList(); if (method.getName().contains("clinit")) { for (int i = 0; i < handles.length; i++) { if (handles[i].getInstruction() instanceof LDC) { LDC orig = ((LDC) handles[i].getInstruction()); if (!orig.getType(cg.getConstantPool()).getSignature().contains("String")) continue; String enc = orig.getValue(cg.getConstantPool()).toString(); String dec = decrypt(enc, key); all.add(dec); logger.debug(cg.getClassName() + " -> " + dec); } } zkStrings.put(cg.getClassName(), all); logger.debug("Decrypted and stored " + all.size() + " strings from " + cg.getClassName()); } } } } private void locateObstructors() { for (ClassGen cg : cgs.values()) { for (Method method : cg.getMethods()) { if (method.isAbstract() || method.isNative()) { continue; } MethodGen mGen = new MethodGen(method, cg.getClassName(), cg.getConstantPool()); InstructionFinder finder = new InstructionFinder(mGen.getInstructionList()); Iterator matches = finder.search( "GETSTATIC IFEQ ((ILOAD IFEQ ICONST GOTO ICONST)|(IINC ILOAD)) PUTSTATIC" ); if (matches.hasNext()) { InstructionHandle[] match = matches.next(); GETSTATIC gstatCtrlField = (GETSTATIC) match[0].getInstruction(); controlClass = gstatCtrlField.getName(cg.getConstantPool()); String fieldName = gstatCtrlField.getFieldName(cg.getConstantPool()); ClassGen ctrlClazz = cgs.get(controlClass); controlField = ctrlClazz.containsField(fieldName); } } } if (controlField == null) { logger.error("Couldn't Locate Control Field!"); return; } flowObstructors.add(controlField); logger.debug("control field: " + controlClass + "." + controlField.getName() + " " + controlField.getSignature()); for (ClassGen cg : cgs.values()) { final ConstantPoolGen cpg = cg.getConstantPool(); for (Method method : cg.getMethods()) { if (method.isAbstract() || method.isNative()) { continue; } MethodGen mGen = new MethodGen(method, cg.getClassName(), cpg); InstructionFinder finder = new InstructionFinder(mGen.getInstructionList()); Iterator matches = finder.search( "(GETSTATIC|ILOAD) IFEQ (((ILOAD|GETSTATIC) IFEQ ICONST GOTO ICONST)|(IINC ILOAD)) PUTSTATIC", new InstructionFinder.CodeConstraint() { public boolean checkCode(InstructionHandle[] code) { FieldInstruction ctrlFieldInstr = null; if (code[0].getInstruction() instanceof GETSTATIC) { ctrlFieldInstr = (FieldInstruction) code[0].getInstruction(); } else { ctrlFieldInstr = (FieldInstruction) code[code.length - 2].getInstruction(); } String className = ctrlFieldInstr.getName(cpg); String fieldName = ctrlFieldInstr.getFieldName(cpg); return className.equals(controlClass) && fieldName.equals(controlField.getName()); } }); while (matches.hasNext()) { InstructionHandle[] match = matches.next(); Instruction first = match[0].getInstruction(); ClassGen ctrlClazz; Field flowObstructor = null; if (first instanceof GETSTATIC) { PUTSTATIC pstatCtrlField = (PUTSTATIC) match[match.length - 2].getInstruction(); String className = pstatCtrlField.getName(cg.getConstantPool()); String fieldName = pstatCtrlField.getFieldName(cg.getConstantPool()); ctrlClazz = cgs.get(className); flowObstructor = ctrlClazz.containsField(fieldName); } else { ILOAD iLoad = (ILOAD) first; int idx = iLoad.getIndex(); InstructionHandle iStoreHandle = finder.getInstructionList().getInstructionHandles()[1]; ISTORE iStore = (ISTORE) iStoreHandle.getInstruction(); assert idx == iStore.getIndex() && idx == mGen.getMaxLocals() - 1 : "expected " + idx + " found " + iStore.getIndex(); GETSTATIC gstatCtrlField = (GETSTATIC) iStoreHandle.getPrev().getInstruction(); String className = gstatCtrlField.getName(cg.getConstantPool()); String fieldName = gstatCtrlField.getFieldName(cg.getConstantPool()); ctrlClazz = cgs.get(className); flowObstructor = ctrlClazz.containsField(fieldName); } if (!flowObstructors.contains(flowObstructor)) { logger.debug("flow obstructor: " + ctrlClazz.getClassName() + "." + flowObstructor.getName() + " " + flowObstructor.getSignature()); flowObstructors.add(flowObstructor); } } } } } public void transform() { logger.log("ZKM Deobfuscator"); logger.log("Starting Opaque Predicate Remover..."); locateObstructors(); opaqueTransformer(); logger.log("Starting String Encryption Removal..."); try { logger.log("Starting ZKM String Grabber..."); getStringsFromZKM(); logger.log("Starting ZKM String Replacer..."); replaceStrings(); } catch (TargetLostException e) { e.printStackTrace(); } logger.log("Starting String Origin Removal..."); removeOriginStrings(); logger.log("Starting Unconditional Branch Remover..."); unconditionalBranchTransformer(); logger.log("Starting Exit Flow Corrector..."); exitFlowTransformer(); logger.log("Deobfuscation finished! Dumping jar..."); GenericMethods.dumpJar(JAR_NAME, cgs.values()); logger.log("Operation Completed."); } private InstructionHandle findIStore(InstructionHandle start, int idx) { InstructionHandle ih = start; while (ih != null) { if (ih.getInstruction() instanceof ISTORE) { if (((ISTORE) ih.getInstruction()).getIndex() == idx) { return ih; } } ih = ih.getNext(); } return null; } public void unconditionalBranchTransformer() { for (ClassGen cg : cgs.values()) { for (Method method : cg.getMethods()) { final MethodGen mg = new MethodGen(method, cg.getClassName(), cg.getConstantPool()); if (method.isAbstract() || method.isNative()) { return; } final InstructionList list = mg.getInstructionList(); InstructionFinder finder = new InstructionFinder(list); final ConstantPoolGen cpg = cg.getConstantPool(); int branchesSimplified = 0; Iterator matches = finder.search("IfInstruction"); while (matches.hasNext()) { InstructionHandle ifHandle = matches.next()[0]; InstructionHandle target = ((BranchHandle) ifHandle).getTarget(); if (target.getInstruction() instanceof GOTO) { branchesSimplified++; ((BranchHandle) ifHandle).setTarget(((BranchHandle) target).getTarget()); } } matches = finder.search("GOTO GOTO"); while (matches.hasNext()) { InstructionHandle[] match = matches.next(); try { list.delete(match[0]); } catch (TargetLostException tlex) { for (InstructionHandle target : tlex.getTargets()) { for (InstructionTargeter targeter : target.getTargeters()) { targeter.updateTarget(target, match[1]); } } } } mg.setInstructionList(list); mg.setMaxLocals(); mg.setMaxStack(); if (branchesSimplified > 0) { logger.debug("simplified " + branchesSimplified + " unconditional branches"); cg.replaceMethod(method, mg.getMethod()); } } } } public void exitFlowTransformer() { for (ClassGen cg : cgs.values()) { int correct = 0; for (Method method : cg.getMethods()) { final MethodGen mgen = new MethodGen(method, cg.getClassName(), cg.getConstantPool()); if (!method.isAbstract() && !method.isNative()) { InstructionList list = mgen.getInstructionList(); InstructionFinder finder = new InstructionFinder(list); CodeExceptionGen[] exceptionGens = mgen.getExceptionHandlers(); for (Iterator matches = finder.search( "ASTORE ALOAD (NEW DUP (PushInstruction InvokeInstruction)+ (ALOAD IfInstruction LDC GOTO LDC " + "INVOKEVIRTUAL)? (PushInstruction InvokeInstruction)* InvokeInstruction+)?"); matches.hasNext();) { /* thanks to popcorn89 */ InstructionHandle[] match = matches.next(); if (!(match[match.length - 1].getInstruction() instanceof ATHROW)) { continue; } InstructionHandle astoreInstr = match[0]; InstructionHandle athrowInstr = match[match.length - 1]; InstructionHandle toRedirect = athrowInstr.getNext(); for (CodeExceptionGen exgen : exceptionGens) { if (exgen.getHandlerPC().equals(astoreInstr)) { mgen.removeExceptionHandler(exgen); } } try { list.delete(astoreInstr, athrowInstr); } catch (TargetLostException tlex) { if (athrowInstr == list.getEnd()) { toRedirect = astoreInstr.getPrev(); } if (toRedirect != null) { for (InstructionHandle target : tlex.getTargets()) { for (InstructionTargeter targeter : target.getTargeters()) { targeter.updateTarget(target, toRedirect); } } } } } list.setPositions(true); InstructionHandle lastInstr = list.getEnd(); InstructionHandle secondToLastInstr = lastInstr.getPrev(); if (secondToLastInstr != null && (lastInstr.getInstruction() instanceof RETURN) && (secondToLastInstr.getInstruction() instanceof RETURN)) { try { list.delete(secondToLastInstr); } catch (TargetLostException tlex) { for (InstructionHandle target : tlex.getTargets()) { for (InstructionTargeter targeter : target.getTargeters()) { targeter.updateTarget(target, lastInstr); } } } } if (mgen.getMethod() != method) { correct++; logger.debug("corrected exit flow in " + cg.getClassName() + "." + mgen.getName() + mgen.getSignature()); mgen.setInstructionList(list); mgen.setMaxLocals(); mgen.setMaxStack(); cg.replaceMethod(method, mgen.getMethod()); } } } logger.debug("Corrected exit flow " + correct + " times in " + cg.getClassName()); } } public void opaqueTransformer() { for (ClassGen cg : cgs.values()) { for (Method method : cg.getMethods()) { final MethodGen mg = new MethodGen(method, cg.getClassName(), cg.getConstantPool()); if (method.isAbstract() || method.isNative()) { return; } final InstructionList list = mg.getInstructionList(); InstructionFinder finder = new InstructionFinder(list); final ConstantPoolGen cpg = cg.getConstantPool(); int stripped = 0; Iterator matches = finder.search( "(ILOAD|GETSTATIC) ((ISTORE)|(IFNE|IFEQ)) (((IINC ILOAD)|((ILOAD IFEQ)? ICONST GOTO ICONST)) PUTSTATIC)?", code -> { InstructionHandle ih = code[0]; if (code.length <= 3) { if (ih.getInstruction() instanceof GETSTATIC) { GETSTATIC gstat = (GETSTATIC) ih.getInstruction(); return flowObstructors.contains(cgs.get(gstat.getName(cpg)) .containsField(gstat.getFieldName(cpg))); } else { ILOAD iLoad = (ILOAD) ih.getInstruction(); int idx = iLoad.getIndex(); if (idx < mg.getArgumentTypes().length + (mg.isStatic() ? 0 : 1)) { return false; } InstructionHandle storeHandle = findIStore(list.getStart(), idx); if (storeHandle == null || !(storeHandle.getPrev().getInstruction() instanceof GETSTATIC)) { return false; } GETSTATIC gstat = (GETSTATIC) storeHandle.getPrev().getInstruction(); return flowObstructors.contains(cgs.get(gstat.getName(cpg)) .containsField(gstat.getFieldName(cpg))); } } else { GETSTATIC gstat = (GETSTATIC) ih.getInstruction(); ClassGen cp = cgs.get(gstat.getName(cpg)); Field fz = cp.containsField(gstat.getFieldName(cpg)); return cp != null && fz != null && controlField != null && controlField.equals(fz); } }); while (matches.hasNext()) { List toDelete = new LinkedList(); InstructionHandle[] match = matches.next(); InstructionHandle ih = match[0]; InstructionHandle theBranch = match[1]; InstructionHandle lastInstr = match[match.length - 2]; InstructionHandle toRedirect = lastInstr.getNext(); if (toRedirect == null) { break; } if (theBranch.getInstruction() instanceof IFEQ && match.length <= 3) { toDelete.add(ih); theBranch.setInstruction(new GOTO(((BranchHandle) theBranch).getTarget())); } else { try { list.delete(ih, lastInstr); } catch (TargetLostException tlex) { for (InstructionHandle target : tlex.getTargets()) { for (InstructionTargeter targeter : target.getTargeters()) { logger.debug("redirected " + target + " to " + toRedirect + " in " + cg.getClassName() + "." + mg.getName() + mg.getSignature()); targeter.updateTarget(target, toRedirect); } } } } stripped++; for (InstructionHandle del : toDelete) { try { list.delete(del); } catch (TargetLostException tlex) { for (InstructionHandle target : tlex.getTargets()) { for (InstructionTargeter targeter : target.getTargeters()) { logger.debug("redirected " + target + " to " + (theBranch.getInstruction() instanceof IFEQ ? lastInstr : toRedirect) + " in " + cg.getClassName() + "." + mg.getName() + mg.getSignature()); targeter.updateTarget(target, theBranch.getInstruction() instanceof IFEQ ? lastInstr : toRedirect); } } } } } if (stripped > 0) { logger.debug("stripped " + stripped + " opaque predicates from " + cg.getClassName() + "." + mg.getName() + mg.getSignature()); mg.setInstructionList(list); mg.setMaxStack(); mg.setMaxLocals(); cg.replaceMethod(method, mg.getMethod()); } } } } } ================================================ FILE: jmd-core/src/main/java/net/contra/jmd/util/GenericClassLoader.java ================================================ package net.contra.jmd.util; /** * Created by IntelliJ IDEA. * User: Eric * Date: Dec 9, 2010 * Time: 4:23:08 AM */ public class GenericClassLoader extends ClassLoader { public GenericClassLoader(ClassLoader parent) { super(parent); } public Class loadClass(String name, byte[] crap) { //name = name.substring(0, name.lastIndexOf('.')); Class c = null; try { //c = super.defineClass(crap, 0, crap.length); c = super.defineClass(name, crap, 0, crap.length); } catch (Exception e) { return c; } super.resolveClass(c); return c; } } ================================================ FILE: jmd-core/src/main/java/net/contra/jmd/util/GenericMethods.java ================================================ package net.contra.jmd.util; import org.apache.bcel.generic.*; import org.apache.commons.io.IOUtils; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.Collection; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; public class GenericMethods { //TODO: Get LDC_W into isInt and getValueOfInt public static boolean isNumber(Instruction ins) { return ins instanceof BIPUSH || ins instanceof SIPUSH || ins instanceof ICONST || ins instanceof LDC_W; } public static int getValueOfNumber(Instruction ins, ConstantPoolGen cpg) { if (ins instanceof BIPUSH) { return ((BIPUSH) ins).getValue().intValue(); } else if (ins instanceof SIPUSH) { return ((SIPUSH) ins).getValue().intValue(); } else if (ins instanceof ICONST) { return ((ICONST) ins).getValue().intValue(); } else if (ins instanceof LDC_W) { LDC_W ldcw = (LDC_W) ins; return Integer.valueOf(ldcw.getValue(cpg).toString()); } else { return -1; } } public static String getCallSignature(Instruction ins, ConstantPoolGen cp) { if (ins instanceof INVOKESTATIC) { INVOKESTATIC invst = (INVOKESTATIC) ins; return invst.getSignature(cp); } else if (ins instanceof INVOKEVIRTUAL) { INVOKEVIRTUAL invst = (INVOKEVIRTUAL) ins; return invst.getSignature(cp); } else if (ins instanceof INVOKEINTERFACE) { INVOKEINTERFACE invst = (INVOKEINTERFACE) ins; return invst.getSignature(cp); } else if (ins instanceof INVOKESPECIAL) { INVOKESPECIAL invst = (INVOKESPECIAL) ins; return invst.getSignature(cp); } else { return null; } } public static Instruction getNewInvoke(Instruction ins, int index) { if (ins instanceof INVOKESTATIC) { INVOKESTATIC invst = (INVOKESTATIC) ins; invst.setIndex(index); return invst; } else if (ins instanceof INVOKEVIRTUAL) { INVOKEVIRTUAL invst = (INVOKEVIRTUAL) ins; invst.setIndex(index); return invst; } else if (ins instanceof INVOKEINTERFACE) { INVOKEINTERFACE invst = (INVOKEINTERFACE) ins; invst.setIndex(index); return invst; } else if (ins instanceof INVOKESPECIAL) { INVOKESPECIAL invst = (INVOKESPECIAL) ins; invst.setIndex(index); return invst; } else { return null; } } public static String getCallReturnType(Instruction ins, ConstantPoolGen cp) { if (ins instanceof INVOKESTATIC) { INVOKESTATIC invst = (INVOKESTATIC) ins; return invst.getReturnType(cp).toString(); } else if (ins instanceof INVOKEVIRTUAL) { INVOKEVIRTUAL invst = (INVOKEVIRTUAL) ins; return invst.getReturnType(cp).toString(); } else if (ins instanceof INVOKEINTERFACE) { INVOKEINTERFACE invst = (INVOKEINTERFACE) ins; return invst.getReturnType(cp).toString(); } else if (ins instanceof INVOKESPECIAL) { INVOKESPECIAL invst = (INVOKESPECIAL) ins; return invst.getReturnType(cp).toString(); } else { return null; } } public static String getCallClassName(Instruction ins, ConstantPoolGen cp) { if (ins instanceof INVOKESTATIC) { INVOKESTATIC invst = (INVOKESTATIC) ins; return invst.getClassName(cp); } else if (ins instanceof INVOKEVIRTUAL) { INVOKEVIRTUAL invst = (INVOKEVIRTUAL) ins; return invst.getClassName(cp); } else if (ins instanceof INVOKEINTERFACE) { INVOKEINTERFACE invst = (INVOKEINTERFACE) ins; return invst.getClassName(cp); } else if (ins instanceof INVOKESPECIAL) { INVOKESPECIAL invst = (INVOKESPECIAL) ins; return invst.getClassName(cp); } else { return null; } } public static void dumpJar(String path, Collection cgs) { FileOutputStream os; path = path.replace(".jar", "") + "-deob.jar"; try { os = new FileOutputStream(new File(path)); } catch (FileNotFoundException fnfe) { throw new RuntimeException("could not create file \"" + path + "\": " + fnfe); } JarOutputStream jos; try { jos = new JarOutputStream(os); for (JarEntry jbe : NonClassEntries.entries) { JarEntry destEntry = new JarEntry(jbe.getName()); byte[] bite = IOUtils.toByteArray(NonClassEntries.ins.get(jbe)); jos.putNextEntry(destEntry); jos.write(bite); jos.closeEntry(); } for (ClassGen classIt : cgs) { JarEntry classEntry = new JarEntry(classIt.getClassName().replace('.', '/') + ".class"); jos.putNextEntry(classEntry); jos.write(classIt.getJavaClass().getBytes()); jos.closeEntry(); } jos.closeEntry(); jos.close(); } catch (IOException ignored) { } } public static String getCallMethodName(Instruction ins, ConstantPoolGen cp) { if (ins instanceof INVOKESTATIC) { INVOKESTATIC invst = (INVOKESTATIC) ins; return invst.getMethodName(cp); } else if (ins instanceof INVOKEVIRTUAL) { INVOKEVIRTUAL invst = (INVOKEVIRTUAL) ins; return invst.getMethodName(cp); } else if (ins instanceof INVOKEINTERFACE) { INVOKEINTERFACE invst = (INVOKEINTERFACE) ins; return invst.getMethodName(cp); } else if (ins instanceof INVOKESPECIAL) { INVOKESPECIAL invst = (INVOKESPECIAL) ins; return invst.getMethodName(cp); } else { return null; } } public static Type[] getCallArgTypes(Instruction ins, ConstantPoolGen cp) { if (ins instanceof INVOKESTATIC) { INVOKESTATIC invst = (INVOKESTATIC) ins; return invst.getArgumentTypes(cp); } else if (ins instanceof INVOKEVIRTUAL) { INVOKEVIRTUAL invst = (INVOKEVIRTUAL) ins; return invst.getArgumentTypes(cp); } else if (ins instanceof INVOKEINTERFACE) { INVOKEINTERFACE invst = (INVOKEINTERFACE) ins; return invst.getArgumentTypes(cp); } else if (ins instanceof INVOKESPECIAL) { INVOKESPECIAL invst = (INVOKESPECIAL) ins; return invst.getArgumentTypes(cp); } else { return null; } } public static boolean isCall(Instruction ins) { return ins instanceof INVOKESTATIC || ins instanceof INVOKEVIRTUAL || ins instanceof INVOKEINTERFACE || ins instanceof INVOKESPECIAL; } } ================================================ FILE: jmd-core/src/main/java/net/contra/jmd/util/HandleSearcher.java ================================================ package net.contra.jmd.util; import org.apache.bcel.generic.ConstantPoolGen; import org.apache.bcel.generic.INVOKESTATIC; import org.apache.bcel.generic.InstructionHandle; import org.apache.bcel.generic.LDC; /** * Created by IntelliJ IDEA. * User: Eric * Date: Nov 25, 2010 * Time: 10:16:26 PM */ public class HandleSearcher { InstructionHandle[] handles; ConstantPoolGen cpg; public int index; public void setPosition(int index) { this.index = index; } public LDC previousLDC() { for (; index >= 0; index--) { if (handles[index].getInstruction() instanceof LDC) { return (LDC) handles[index].getInstruction(); } } return null; } public INVOKESTATIC nextInvokeStatic(String className) { for (; index < handles.length; index++) { if (index > -1) { if (handles[index].getInstruction() instanceof INVOKESTATIC) { INVOKESTATIC methodCall = (INVOKESTATIC) handles[index].getInstruction(); if (methodCall.getClassName(cpg).equals(className)) { return methodCall; } } } } return null; } } ================================================ FILE: jmd-core/src/main/java/net/contra/jmd/util/LogHandler.java ================================================ package net.contra.jmd.util; public class LogHandler { private String _className = "NoClass"; public LogHandler(String className) { _className = className; } public void message(String msg) { System.out.println(msg); } public void log(String msg) { System.out.println("[" + _className + "]" + msg); } public void debug(String msg) { System.out.println("[" + _className + "]" + "[DEBUG]" + msg); } public void error(String msg) { System.out.println("[" + _className + "]" + "[ERROR]" + msg); } } ================================================ FILE: jmd-core/src/main/java/net/contra/jmd/util/NonClassEntries.java ================================================ package net.contra.jmd.util; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; public final class NonClassEntries { public static ArrayList entries = new ArrayList(); public static Map ins = new HashMap(); private NonClassEntries() { } public static JarEntry getByName(String name) { for (JarEntry e : entries) { if (e.getName().equals(name)) { return e; } } return null; } public static void add(JarEntry entry, InputStream inputStream) { entries.add(entry); ins.put(entry, inputStream); } } ================================================ FILE: jmd-core/src/test/java/net/contra/jmd/transformers/dasho/DashOTransformerTest.java ================================================ package net.contra.jmd.transformers.dasho; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static org.testng.Assert.*; public class DashOTransformerTest { @Test(dataProvider = "inputData") public void testDecrypt(String input, String outputExpected) throws Exception { final String outputActual = DashOTransformer.decrypt(input); assertEquals(outputActual, outputExpected); } @DataProvider public Object[][] inputData() { return new Object[][]{ {null, null}, {"<", ";"}, {"", ""} }; } } ================================================ FILE: jmd-gui/gradle/wrapper/gradle-wrapper.properties ================================================ #Tue Nov 08 23:54:48 MSK 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-bin.zip ================================================ FILE: jmd-gui/gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn ( ) { echo "$*" } die ( ) { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: jmd-gui/gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: jmd-gui/src/main/java/net/contra/jmd/ConfigureApp.java ================================================ package net.contra.jmd; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.scene.layout.Pane; import javafx.stage.Stage; import java.io.IOException; public class ConfigureApp extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { primaryStage.setTitle("JMD-GUI"); try { FXMLLoader loader = new FXMLLoader(); loader.setLocation(ConfigureApp.class.getResource("/view/configure.fxml")); Pane rootLayout = loader.load(); Scene scene = new Scene(rootLayout); primaryStage.setScene(scene); primaryStage.show(); } catch (IOException e) { // empty } } } ================================================ FILE: jmd-gui/src/main/resources/view/configure.fxml ================================================