commit a45ffb98c80b1ebea24af204e47433cdc8bef2e0 Author: Potato Sus <82276088+PotatoSUS@users.noreply.github.com> Date: Sun Jan 29 09:06:35 2023 -0300 Initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..90253ee --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,34 @@ +name: Build + +on: [ pull_request, push ] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + java: [ '8', '17' ] + name: Java ${{ matrix.Java }} build + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + - name: Ensure genuine gradle + uses: gradle/wrapper-validation-action@v1 + - name: Setup Java + uses: actions/setup-java@v2 + with: + distribution: 'zulu' + java-version: ${{ matrix.java }} + name: Gradle Build + check-latest: true + - name: Make gradle wrapper executable + run: chmod +x ./gradlew + - name: Run "clean" task + run: ./gradlew clean + - name: Run "build" task + run: ./gradlew build --no-daemon + - name: Capture artifacts + uses: actions/upload-artifact@v2 + with: + name: zProtect Internal Preview (Java ${{ matrix.java }}) + path: build/libs/* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0145d05 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Eclipse +bin +*.launch +.settings +.metadata +.classpath +.project + +# IDEA +out +*.ipr +*.iws +*.iml +.idea + +# Gradle +build +.gradle + +# Other +eclipse +run diff --git a/.onedev-buildspec.yml b/.onedev-buildspec.yml new file mode 100644 index 0000000..2ebae92 --- /dev/null +++ b/.onedev-buildspec.yml @@ -0,0 +1,69 @@ +version: 15 +jobs: +- name: Build + steps: + - !CheckoutStep + name: Checkout + cloneCredential: !DefaultCredential {} + withLfs: false + withSubmodules: false + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + - !CommandStep + name: Detect Version + runInContainer: true + image: '@script:builtin:gradle:determine-docker-image@' + interpreter: !DefaultInterpreter + commands: + - echo "Detecting project version (may require some time while downloading gradle + dependencies)..." + - 'echo $(gradle properties | grep ^version: | grep -v unspecified | cut -c10-) + > buildVersion' + useTTY: false + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + - !SetBuildVersionStep + name: Set Version + buildVersion: '@file:buildVersion@' + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + - !CommandStep + name: Post Webhook + runInContainer: true + image: prontotools/alpine-git-curl + interpreter: !DefaultInterpreter + commands: + - user=$(git log --format=%an -n 1 @commit_hash@) + - commit_hash=@commit_hash@ + - commit_message=$(git log --format=%B -n 1 @commit_hash@) + - 'curl -H ''Content-Type: application/json'' \' + - "\t-d \"{\\\"avatar_url\\\": \\\"https://test.it-snek.com/static/media/logo.c0ab911818705a32c78c.png\\\ + \", \\\"username\\\": \\\"zGitBot\\\", \\\"embeds\\\": [{\\\"author\\\": {\\\ + \"name\\\": \\\"${user}\\\"},\\\"title\\\": \\\"@project_name@/@branch@\\\"\ + , \\\"description\\\": \\\"${commit_hash:0:7}: ${commit_message}\\\"}]}\"\ + \ \\" + - "\t'https://discord.com/api/webhooks/904058376504348702/v7Otrf48So0S8g67YkH8Xnh6g7EN9T8grSuvY3IHGksjhP6V7AYb5FG0zKr33mtZSFmq'" + useTTY: false + condition: ALWAYS + - !CommandStep + name: Gradle Build + runInContainer: true + image: '@script:builtin:gradle:determine-docker-image@' + interpreter: !DefaultInterpreter + commands: + - gradle build --no-daemon + useTTY: false + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + - !PublishArtifactStep + name: Publish Artifact + artifacts: build/libs/* + condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL + triggers: + - !BranchUpdateTrigger {} + - !PullRequestUpdateTrigger {} + retryCondition: never + maxRetries: 3 + retryDelay: 30 + cpuRequirement: 500 + memoryRequirement: 256 + caches: + - key: gradle-cache + path: /home/gradle/.gradle + timeout: 3600 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e0f15db --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic" +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bff86c0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Potato Sus + +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. diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..52fe04a --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,61 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + java + kotlin("jvm") version "1.7.20" + kotlin("plugin.serialization") version "1.6.10" + id("com.github.johnrengelman.shadow") version "7.1.2" +} + +group = "dev.zprotect" +version = "0.2" + +repositories { + mavenCentral() + maven { + url = uri("https://oss.sonatype.org/content/groups/public/") + } +} + +dependencies { + implementation("org.json:json:20211205") + + implementation("org.ow2.asm:asm:9.2") + implementation("org.ow2.asm:asm-tree:9.2") + implementation("org.ow2.asm:asm-commons:9.2") + implementation("org.ow2.asm:asm-util:9.2") + + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") +} + +tasks { + build { + dependsOn(shadowJar) + } + + register("standaloneJar") { + manifest { + attributes["Main-Class"] = "dev.zprotect.obfuscator.StandaloneKt" + } + } + + named("shadowJar") { + manifest { + attributes["Main-Class"] = "dev.zprotect.obfuscator.MainKt" + } + minimize() + } + + withType { + manifest { + attributes["Main-Class"] = "dev.zprotect.obfuscator.MainKt" + } + } + + withType { + kotlinOptions { + jvmTarget = "1.8" + } + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..29e08e8 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..62d4c05 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..8049c68 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..fbd7c51 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## 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='"-Xmx64m" "-Xms64m"' + +# 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 or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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=`expr $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 + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..5093609 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,104 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@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 Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@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="-Xmx64m" "-Xms64m" + +@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 diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..e0dab88 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "zProtect" diff --git a/src/main/java/dev/zprotect/obfuscator/Extensions.kt b/src/main/java/dev/zprotect/obfuscator/Extensions.kt new file mode 100644 index 0000000..05b45a4 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/Extensions.kt @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator + +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.FieldNode +import org.objectweb.asm.tree.MethodNode + +fun ClassNode.hasAccess(access: Int) = this.access and access == 0 +fun FieldNode.hasAccess(access: Int) = this.access and access == 0 +fun MethodNode.hasAccess(access: Int) = this.access and access == 0 + diff --git a/src/main/java/dev/zprotect/obfuscator/Main.kt b/src/main/java/dev/zprotect/obfuscator/Main.kt new file mode 100644 index 0000000..6d78d36 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/Main.kt @@ -0,0 +1,31 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator + +import dev.zprotect.obfuscator.Obfuscator.info +import java.io.File +import kotlin.system.exitProcess +import java.util.Collections +import java.util.UUID + +fun main(args: Array) { + if (args.size != 1) { + Obfuscator.info("Missing argument: Config file") + exitProcess(-1) + } + + Obfuscator.info("Visit https://zprotect.dev for more information.\n") + + Obfuscator.run(File(args[0])) +} diff --git a/src/main/java/dev/zprotect/obfuscator/Obfuscator.kt b/src/main/java/dev/zprotect/obfuscator/Obfuscator.kt new file mode 100644 index 0000000..8f45b59 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/Obfuscator.kt @@ -0,0 +1,306 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator + +import dev.zprotect.obfuscator.api.ClassPath +import dev.zprotect.obfuscator.api.Transformer +import dev.zprotect.obfuscator.api.interfaces.IBytecode +import dev.zprotect.obfuscator.api.settings.ArrayConfig +import dev.zprotect.obfuscator.api.settings.BooleanConfig +import dev.zprotect.obfuscator.api.settings.Config +import dev.zprotect.obfuscator.api.settings.StringConfig +import dev.zprotect.obfuscator.transformers.antitamper.AntiDebug +import dev.zprotect.obfuscator.transformers.decompiler.BadAnnotationCrasher +import dev.zprotect.obfuscator.transformers.decompiler.DecompilerCrasher +import dev.zprotect.obfuscator.transformers.flow.Flow +import dev.zprotect.obfuscator.transformers.naming.ClassRenamer +import dev.zprotect.obfuscator.transformers.naming.FieldRenamer +import dev.zprotect.obfuscator.transformers.naming.LocalVariableRenamer +import dev.zprotect.obfuscator.transformers.naming.MethodRenamer +import dev.zprotect.obfuscator.transformers.optimization.* +import dev.zprotect.obfuscator.transformers.poolers.NumberPooler +import dev.zprotect.obfuscator.transformers.shrinking.* +import dev.zprotect.obfuscator.transformers.shufflers.ShuffleFields +import dev.zprotect.obfuscator.transformers.shufflers.ShuffleMethods +import org.objectweb.asm.ClassReader +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.ClassNode +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.StandardOpenOption +import java.time.Instant +import java.util.jar.JarEntry +import java.util.jar.JarFile +import java.util.jar.JarOutputStream +import java.util.jar.Manifest +import kotlin.random.Random + + +object Obfuscator { + private val inputJar = StringConfig("inputJar") + private val outputJar = StringConfig("outputJar") + private val exclusions = ArrayConfig("exclusions") + private val libraries = ArrayConfig("libraries") + + private val watermark = BooleanConfig("watermark") + + private lateinit var ioMode: IOMode + private val files: MutableMap = HashMap() + private val classNodes: MutableMap = HashMap() + private lateinit var manifest: Manifest + + @JvmStatic + fun info(msg: String) { + println("[${Instant.now()}] $msg") + } + + fun run(config: File) { + val startTime = System.currentTimeMillis() + + Config.read(config) + + if (inputJar.value == null || outputJar.value == null) { + info("Error: Input file or output file is not specified!") + return + } + + val inputFile = File(inputJar.value!!) + if (!inputFile.exists()) { + info("Input file not found!") + return + } + openFile(inputFile) + + ClassPath.load(getLibraries()) + + arrayListOf( + // Flow + Flow, + + // Shrinking + RemoveInnerClasses, + LocalVariableRemover, + LineNumberRemover, + SourceDebugRemover, + SourceFileRemover, + + // Shuffling + ShuffleFields, + ShuffleMethods, + + // Number + NumberPooler, + + // Optimization + KotlinMetadataRemover, + HideClassMembers, + RemoveSignatures, + FinalRemover, + InsnRemover, + EnumOptimiser, + NOPInsnRemover, + + + // Naming + MethodRenamer, + FieldRenamer, + ClassRenamer, + LocalVariableRenamer, + + // Reversing + DecompilerCrasher, + AntiDebug, + BadAnnotationCrasher, + ).forEach(Transformer::run) + + saveFile() + + info("zProtect has finished obfuscating in " + (System.currentTimeMillis() - startTime) + " milliseconds.") + } + + private fun openFile(file: File) { + if (file.name.endsWith(".jar")) { + ioMode = IOMode.JAR + openJar(JarFile(file)) + } else if (file.name.endsWith(".class")) { + ioMode = IOMode.CLASS + openClass(file) + } + } + + private fun openJar(jar: JarFile) { + val entries = jar.entries() + + while (entries.hasMoreElements()) { + val entry = entries.nextElement() + + val bytes: ByteArray = IBytecode.readBytes(jar.getInputStream(entry)) + + if (!entry.name.endsWith(".class")) { + files[entry.name] = bytes + } else { + val classNode = ClassNode() + ClassReader(bytes).accept(classNode, ClassReader.EXPAND_FRAMES) + classNodes[classNode.name] = classNode + ClassPath.get(classNode) + } + } + manifest = jar.manifest + } + + private fun openClass(file: File) { + val bytes: ByteArray = IBytecode.readBytes(file.inputStream()) + val classNode = ClassNode() + ClassReader(bytes).accept(classNode, ClassReader.EXPAND_FRAMES) + classNodes[classNode.name] = classNode + } + + private fun saveFile() { + when (ioMode) { + IOMode.JAR -> saveJar() + IOMode.CLASS -> saveClass() + } + } + + private fun saveJar() { + /* + not P + TODO: Figure out why CRC corrupter doesn't work with JarOutputStream + Even when we set ZipOutputStream and use the function it does not work... + Here is the function: + + fun corruptCRC32(zipOutputStream: ZipOutputStream?) { + try { + val field: Field = ZipOutputStream::class.java.getDeclaredField("crc") + field.isAccessible = true + field.set(zipOutputStream, object : CRC32() { + override fun getValue(): Long { + return -0x21523f22 + } + }) + } catch (ignored: NoSuchFieldException) { + } catch (ignored: IllegalAccessException) {} + } + + FIX THIS PLEASE... + */ + + val byteArrayOutputStream = ByteArrayOutputStream() + manifest.write(byteArrayOutputStream) + files["META-INF/MANIFEST.MF"] = byteArrayOutputStream.toByteArray() + + var location: String = outputJar.value!! + if (!location.endsWith(".jar")) location += ".jar" + + val jarPath = Paths.get(location) + Files.deleteIfExists(jarPath) + + val outJar = JarOutputStream( + Files.newOutputStream( + jarPath, + StandardOpenOption.CREATE, + StandardOpenOption.CREATE_NEW, + StandardOpenOption.WRITE + ) + ) + + classNodes.values.forEach { classNode -> + outJar.putNextEntry(JarEntry(classNode.name + ".class")) + outJar.write(IBytecode.writeBytes(classNode)) + outJar.closeEntry() + } + + files.entries.forEach { (key, value) -> + outJar.putNextEntry(JarEntry(key)) + outJar.write(value) + outJar.closeEntry() + } + + // Our unique identifier so we can check if a program has been obfuscated with zProtect or not. + // Set a comment in jar, can be seen once opening a jar in a zip viewer. + if (watermark.value == true) { + outJar.setComment("보호에 의해 난독화되고 오늘 보호 난독화 장치를 구입하십시오.") + + // Dummy class. + val dummy = ClassNode() + dummy.visit( + Opcodes.V1_5, + Opcodes.ACC_PUBLIC, + "보호에 의해 난독화되고 오늘 보호 난독화 장치를 구입하십시오.", + null, + "java/lang/Object", + null + ) + dummy.visitMethod(Random.nextInt(100), "\u0001", "(\u0001/)L\u0001/;", null, null) + try { + outJar.putNextEntry(JarEntry(dummy.name + ".class")) + outJar.write(IBytecode.toByteArrayDefault(dummy)) + info("Applied transformer: Watermark") + } catch (e: IOException) { + e.printStackTrace() + } + } + outJar.close() + } + + private fun saveClass() { + var location: String = outputJar.value!! + if (!location.endsWith(".class")) location += ".class" + + val classPath = Paths.get(location) + Files.deleteIfExists(classPath) + + val outputStream = Files.newOutputStream( + classPath, + StandardOpenOption.CREATE, + StandardOpenOption.CREATE_NEW, + StandardOpenOption.WRITE + ) + outputStream.write(IBytecode.writeBytes(classNodes.entries.iterator().next().value)) + outputStream.close() + } + + fun getClassNodes(): MutableMap { + return classNodes + } + + fun getFiles(): MutableMap { + return files + } + + fun getManifest(): Manifest { + return manifest + } + + fun getExclusions(): List { // TODO: Caching for faster performance. + val exclusionsCache: MutableList = mutableListOf() + if (exclusions.value == null) return exclusionsCache + for (i in 0 until exclusions.value!!.length()) exclusionsCache.add(exclusions.value!!.getString(i)) + return exclusionsCache + } + + fun getLibraries(): List { // TODO: Caching for faster performance. + val librariesCache: MutableList = mutableListOf() + if (libraries.value == null) return librariesCache + for (i in 0 until libraries.value!!.length()) librariesCache.add(libraries.value!!.getString(i)) + return librariesCache + } + + enum class IOMode { + CLASS, JAR; + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/api/ClassPath.kt b/src/main/java/dev/zprotect/obfuscator/api/ClassPath.kt new file mode 100644 index 0000000..70589a5 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/api/ClassPath.kt @@ -0,0 +1,116 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.api + +import dev.zprotect.obfuscator.api.interfaces.IBytecode +import org.objectweb.asm.ClassReader +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.Type +import java.util.jar.JarFile + +object ClassPath : HashMap() { + override fun get(key: String): ClassEntry? = super.get(key) ?: add(get(Class.forName(key.replace("/", ".")))) + fun get(classNode: ClassNode): ClassEntry = super.get(classNode.name) ?: add(ClassNodeEntry(classNode)) + fun get(clazz: Class<*>): ClassEntry = super.get(Type.getInternalName(clazz)) ?: add(ReflectionClassEntry(clazz)) + private fun add(classEntry: ClassEntry): ClassEntry = classEntry.apply { put(classEntry.getName(), classEntry) } + + fun load(paths: List) = paths.forEach { load(it) } + fun load(path: String) { + val jar = JarFile(path) + val entries = jar.entries() + + while (entries.hasMoreElements()) { + val entry = entries.nextElement() + + if (entry.name.endsWith(".class")) { + val bytes: ByteArray = IBytecode.readBytes(jar.getInputStream(entry)) + val classNode = ClassNode() + ClassReader(bytes).accept(classNode, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG) + put(classNode.name, ClassNodeEntry(classNode)) + } + } + } +} + +abstract class ClassEntry { + abstract fun getName(): String + abstract fun getSuper(): String? + abstract fun getAccess(): Int + abstract fun getInterfaces(): MutableList + abstract fun getFields(): MutableList + abstract fun getMethods(): MutableList +} + +data class MethodEntry(val owner: ClassEntry, val name: String, val desc: String) +data class FieldEntry(val owner: ClassEntry, val name: String, val desc: String) + +class ClassNodeEntry(private val classNode: ClassNode) : ClassEntry() { + override fun getName(): String = classNode.name + override fun getSuper(): String? = classNode.superName + override fun getAccess(): Int = classNode.access + override fun getInterfaces(): MutableList = classNode.interfaces + + override fun getFields(): MutableList = exploreFields + private val exploreFields: MutableList by lazy { + val list = mutableListOf() + for (fieldNode in classNode.fields) { + list.add(FieldEntry(this, fieldNode.name, fieldNode.desc)) + } + list + } + + override fun getMethods(): MutableList = exploreMethods + private val exploreMethods: MutableList by lazy { + val list = mutableListOf() + for (methodNode in classNode.methods) { + list.add(MethodEntry(this, methodNode.name, methodNode.desc)) + } + list + } +} + +class ReflectionClassEntry(private val clazz: Class<*>) : ClassEntry() { + override fun getName(): String = Type.getInternalName(clazz) + override fun getSuper(): String? = if (clazz.superclass == null) null else Type.getInternalName(clazz.superclass) + ?: if (getName() != "java/lang/Object") "java/lang/Object" else null + + override fun getAccess(): Int = clazz.modifiers + + override fun getInterfaces(): MutableList = exploreInterfaces + private val exploreInterfaces: MutableList by lazy { + val list = mutableListOf() + for (interfaces in clazz.interfaces) { + list.add(Type.getInternalName(interfaces)) + } + list + } + + override fun getFields(): MutableList = exploreFields + private val exploreFields: MutableList by lazy { + val list = mutableListOf() + for (field in clazz.declaredFields) { + list.add(FieldEntry(this, field.name, Type.getDescriptor(field.type))) + } + list + } + + override fun getMethods(): MutableList = exploreMethods + private val exploreMethods: MutableList by lazy { + val list = mutableListOf() + for (method in clazz.declaredMethods) { + list.add(MethodEntry(this, method.name, Type.getMethodDescriptor(method))) + } + list + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/api/Transformer.kt b/src/main/java/dev/zprotect/obfuscator/api/Transformer.kt new file mode 100644 index 0000000..68725d8 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/api/Transformer.kt @@ -0,0 +1,101 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.api + +import dev.zprotect.obfuscator.Obfuscator +import dev.zprotect.obfuscator.Obfuscator.info +import dev.zprotect.obfuscator.api.settings.BooleanConfig +import dev.zprotect.obfuscator.api.interfaces.INode +import dev.zprotect.obfuscator.api.interfaces.IBytecode +import org.objectweb.asm.commons.ClassRemapper +import org.objectweb.asm.commons.SimpleRemapper +import org.objectweb.asm.tree.ClassNode +import java.util.jar.Manifest +import java.util.stream.Collectors + +abstract class Transformer(val name: String, val description: String) : IBytecode, INode { + private val setting = BooleanConfig(name) + + val classMap: MutableMap + get() = Obfuscator.getClassNodes() + + val classes: List + get() = ArrayList(classMap.values) + + val filesMap: MutableMap + get() = Obfuscator.getFiles() + + val files: List + get() = ArrayList(filesMap.keys) + + val manifest: Manifest + get() = Obfuscator.getManifest() + + fun run() { + if (setting.value != null && setting.value!!) { + obfuscate() + info("Applied transformer: $name") + } + } + + protected abstract fun obfuscate() + + fun applyRemap(remap: Map?) { + val remapper = SimpleRemapper(remap) + for (node in classes) { + val copy = ClassNode() + val adapter = ClassRemapper(copy, remapper) + node.accept(adapter) + classMap.remove(node.name) + classMap[node.name] = copy + } + } + + fun getImplementations(target: ClassNode): List { + return classes.stream().filter { cn -> cn.interfaces.contains(target.name) }.collect(Collectors.toList()) + } + + fun getExtensions(target: ClassNode): List { + val extensions: MutableList = mutableListOf() + + classes.stream() + .filter { classNode -> classNode.superName == target.name } + .forEach { classNode -> + extensions.add(classNode) + extensions.addAll(getExtensions(classNode)) + } + + return extensions + } + + fun getImplementations(target: ClassEntry): List { + val implementations = mutableListOf() + for (classNode in classes.stream().filter { classNode -> classNode.interfaces.contains(target.getName()) }) { + implementations.add(ClassPath.get(classNode)) + implementations.addAll(getImplementations(ClassPath.get(classNode))) + } + return implementations + } + + fun getExtensions(target: ClassEntry): List { + val extensions = mutableListOf() + classes.stream() + .filter { classNode -> classNode.superName == target.getName() } + .forEach { classNode -> + extensions.add(ClassPath.get(classNode)) + extensions.addAll(getExtensions(ClassPath.get(classNode))) + } + return extensions + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/api/TransformerPriority.kt b/src/main/java/dev/zprotect/obfuscator/api/TransformerPriority.kt new file mode 100644 index 0000000..cfcbb7f --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/api/TransformerPriority.kt @@ -0,0 +1,18 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.api + +enum class TransformerPriority { + HIGHEST, HIGH, NORMAL, LOW, LOWEST +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/api/encryption/Dictionary.kt b/src/main/java/dev/zprotect/obfuscator/api/encryption/Dictionary.kt new file mode 100644 index 0000000..11dc337 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/api/encryption/Dictionary.kt @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.api.encryption + +import java.security.SecureRandom + +object Dictionary { + private val RANDOM = SecureRandom() + private val ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + + private var num = 0 + + fun getNewName(): String { + return getInAlphabet(num++) + } + + fun reset() { + num = 0 + } + + private fun getInAlphabet(i: Int): String { + return if (i < 0) "" else getInAlphabet(i / 52 - 1) + (toAscii(i % 52)).toChar() + } + + private fun toAscii(i: Int): Int { + return if (i < 26) i + 97 else i + 39 + } + + fun genRandomString(): String { + val length = RANDOM.nextInt(15 - 5) + 5 + val stringBuilder = StringBuilder() + for (i in 0 until length) { + val letter = RANDOM.nextInt(ALPHABET.length) + stringBuilder.append(ALPHABET[letter]) + } + return stringBuilder.toString() + } + + fun generateString(length: Int): String { + val length = length + val stringBuilder = StringBuilder() + for (i in 0 until length) { + val letter = RANDOM.nextInt(ALPHABET.length) + stringBuilder.append(ALPHABET[letter]) + } + return stringBuilder.toString() + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/api/insn/InsnBuilder.kt b/src/main/java/dev/zprotect/obfuscator/api/insn/InsnBuilder.kt new file mode 100644 index 0000000..55893fc --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/api/insn/InsnBuilder.kt @@ -0,0 +1,243 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.api.insn + +import org.objectweb.asm.Handle +import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.Type +import org.objectweb.asm.tree.* + +class InsnBuilder { + + val insnList = InsnList() + + operator fun InsnList.unaryPlus() = insnList.add(this) + operator fun AbstractInsnNode.unaryPlus() = insnList.add(this) + fun Int.insn() = InsnNode(this) + + fun insn(opcode: Int) = +InsnNode(opcode) + + fun nop() = insn(NOP) + + fun aconst_null() = insn(ACONST_NULL) + fun ldc(int: Int) = +getIntInsn(int) + fun ldc(long: Long) = +getLongInsn(long) + fun ldc(float: Float) = +getFloatInsn(float) + fun ldc(double: Double) = +getDoubleInsn(double) + fun ldc(string: String) = +LdcInsnNode(string) + fun ldc(type: Type) = +LdcInsnNode(type) + fun ldc(handle: Handle) = +LdcInsnNode(handle) + + fun istore(`var`: Int) = +VarInsnNode(ISTORE, `var`) + fun iload(`var`: Int) = +VarInsnNode(ILOAD, `var`) + fun lstore(`var`: Int) = +VarInsnNode(LSTORE, `var`) + fun lload(`var`: Int) = +VarInsnNode(LLOAD, `var`) + fun fstore(`var`: Int) = +VarInsnNode(FSTORE, `var`) + fun fload(`var`: Int) = +VarInsnNode(FLOAD, `var`) + fun dstore(`var`: Int) = +VarInsnNode(DSTORE, `var`) + fun dload(`var`: Int) = +VarInsnNode(DLOAD, `var`) + fun astore(`var`: Int) = +VarInsnNode(ASTORE, `var`) + fun aload(`var`: Int) = +VarInsnNode(ALOAD, `var`) + + fun iastore() = insn(IASTORE) + fun iaload() = insn(IALOAD) + fun lastore() = insn(LASTORE) + fun laload() = insn(LALOAD) + fun fastore() = insn(FASTORE) + fun faload() = insn(FALOAD) + fun dastore() = insn(DASTORE) + fun daload() = insn(DALOAD) + fun aastore() = insn(AASTORE) + fun aaload() = insn(AALOAD) + fun bastore() = insn(BASTORE) + fun baload() = insn(BALOAD) + fun castore() = insn(CASTORE) + fun caload() = insn(CALOAD) + fun sastore() = insn(SASTORE) + fun saload() = insn(SALOAD) + + + fun pop() = insn(POP) + fun pop2() = insn(POP2) + fun dup() = insn(DUP) + fun dup_x1() = insn(DUP_X1) + fun dup_x2() = insn(DUP_X2) + fun dup2() = insn(DUP2) + fun dup2_x1() = insn(DUP2_X1) + fun dup2_x2() = insn(DUP2_X2) + fun swap() = insn(SWAP) + + + fun iadd() = insn(IADD) + fun isub() = insn(ISUB) + fun imul() = insn(IMUL) + fun idiv() = insn(IDIV) + fun irem() = insn(IREM) + fun ineg() = insn(INEG) + fun ishl() = insn(ISHL) + fun ishr() = insn(ISHR) + fun iushr() = insn(IUSHR) + fun iand() = insn(IAND) + fun ior() = insn(IOR) + fun ixor() = insn(IXOR) + fun iinc(`var`: Int, incr: Int) = +IincInsnNode(`var`, incr) + + fun ladd() = insn(LADD) + fun lsub() = insn(LSUB) + fun lmul() = insn(LMUL) + fun ldiv() = insn(LDIV) + fun lrem() = insn(LREM) + fun lneg() = insn(LNEG) + fun lshl() = insn(LSHL) + fun lshr() = insn(LSHR) + fun lushr() = insn(LUSHR) + fun lor() = insn(LOR) + fun land() = insn(LAND) + fun lxor() = insn(LXOR) + + fun fadd() = insn(FADD) + fun fsub() = insn(FSUB) + fun fmul() = insn(FMUL) + fun fdiv() = insn(FDIV) + fun frem() = insn(FREM) + fun fneg() = insn(FNEG) + + fun dadd() = insn(DADD) + fun dsub() = insn(DSUB) + fun dmul() = insn(DMUL) + fun ddiv() = insn(DDIV) + fun drem() = insn(DREM) + fun dneg() = insn(DNEG) + + fun i2l() = insn(I2L) + fun i2f() = insn(I2F) + fun i2d() = insn(I2D) + fun i2b() = insn(I2B) + fun i2c() = insn(I2C) + fun i2s() = insn(I2S) + fun l2i() = insn(L2I) + fun l2f() = insn(L2F) + fun l2d() = insn(L2D) + fun f2i() = insn(F2I) + fun f2l() = insn(F2L) + fun f2d() = insn(F2D) + fun d2i() = insn(D2I) + fun d2l() = insn(D2L) + fun d2f() = insn(D2F) + + fun lcmp() = insn(LCMP) + fun fcmpl() = insn(FCMPL) + fun fcmpg() = insn(FCMPG) + fun dcmpl() = insn(DCMPL) + fun dcmpg() = insn(DCMPG) + + + fun goto(label: LabelNode) = +JumpInsnNode(GOTO, label) + fun jsr(label: LabelNode) = +JumpInsnNode(JSR, label) + + fun ifeq(label: LabelNode) = +JumpInsnNode(IFEQ, label) + fun ifne(label: LabelNode) = +JumpInsnNode(IFNE, label) + fun iflt(label: LabelNode) = +JumpInsnNode(IFLT, label) + fun ifle(label: LabelNode) = +JumpInsnNode(IFLE, label) + fun ifge(label: LabelNode) = +JumpInsnNode(IFGE, label) + fun ifgt(label: LabelNode) = +JumpInsnNode(IFGT, label) + + fun if_icmplt(label: LabelNode) = +JumpInsnNode(IF_ICMPLT, label) + fun if_icmple(label: LabelNode) = +JumpInsnNode(IF_ICMPLE, label) + fun if_icmpge(label: LabelNode) = +JumpInsnNode(IF_ICMPGE, label) + fun if_icmpgt(label: LabelNode) = +JumpInsnNode(IF_ICMPGT, label) + fun if_icmpeq(label: LabelNode) = +JumpInsnNode(IF_ICMPEQ, label) + fun if_icmpne(label: LabelNode) = +JumpInsnNode(IF_ICMPNE, label) + fun if_acmpeq(label: LabelNode) = +JumpInsnNode(IF_ACMPEQ, label) + + fun ifnull(label: LabelNode) = +JumpInsnNode(IFNULL, label) + fun ifnonnull(label: LabelNode) = +JumpInsnNode(IFNONNULL, label) + + fun lookupswitch(defaultLabel: LabelNode, lookup: Pair>) = + +LookupSwitchInsnNode(defaultLabel, lookup.first, lookup.second) + + fun getstatic(owner: String, name: String, desc: String) = +FieldInsnNode(GETSTATIC, owner, name, desc) + fun putstatic(owner: String, name: String, desc: String) = +FieldInsnNode(PUTSTATIC, owner, name, desc) + fun getfield(owner: String, name: String, desc: String) = +FieldInsnNode(GETFIELD, owner, name, desc) + fun putfield(owner: String, name: String, desc: String) = +FieldInsnNode(PUTFIELD, owner, name, desc) + + fun invokevirtual(owner: String, name: String, desc: String, `interface`: Boolean = false) = + +MethodInsnNode(INVOKEVIRTUAL, owner, name, desc, `interface`) + + fun invokespecial(owner: String, name: String, desc: String, `interface`: Boolean = false) = + +MethodInsnNode(INVOKESPECIAL, owner, name, desc, `interface`) + + fun invokestatic(owner: String, name: String, desc: String, `interface`: Boolean = false) = + +MethodInsnNode(INVOKESTATIC, owner, name, desc, `interface`) + + fun invokeinterface(owner: String, name: String, desc: String, `interface`: Boolean = false) = + +MethodInsnNode(INVOKEINTERFACE, owner, name, desc, `interface`) + + fun new(type: String) = +TypeInsnNode(NEW, type) + fun newarray(type: Int) = +IntInsnNode(NEWARRAY, type) + fun anewarray(desc: String) = +TypeInsnNode(ANEWARRAY, desc) + fun newboolarray() = newarray(T_BOOLEAN) + fun newchararray() = newarray(T_CHAR) + fun newbytearray() = newarray(T_BYTE) + fun newshortarray() = newarray(T_SHORT) + fun newintarray() = newarray(T_INT) + fun newlongarray() = newarray(T_LONG) + fun newfloatarray() = newarray(T_FLOAT) + fun newdoublearray() = newarray(T_DOUBLE) + + fun arraylength() = insn(ARRAYLENGTH) + + fun athrow() = insn(ATHROW) + + fun checkcast(descriptor: String) = +TypeInsnNode(CHECKCAST, descriptor) + fun instanceof(descriptor: String) = +TypeInsnNode(INSTANCEOF, descriptor) + + fun ireturn() = insn(IRETURN) + fun lreturn() = insn(LRETURN) + fun freturn() = insn(FRETURN) + fun dreturn() = insn(DRETURN) + fun areturn() = insn(ARETURN) + fun _return() = insn(RETURN) + + fun frame(type: Int, numLocal: Int, local: Array?, numStack: Int, stack: Array?) = + +FrameNode(type, numLocal, local, numStack, stack) + + fun getIntInsn(value: Int) = + when (value) { + in -1..5 -> InsnNode(value + 3) + in Byte.MIN_VALUE..Byte.MAX_VALUE -> IntInsnNode(BIPUSH, value) + in Short.MIN_VALUE..Short.MAX_VALUE -> IntInsnNode(SIPUSH, value) + else -> LdcInsnNode(value) + } + + fun getLongInsn(value: Long) = + when (value) { + in 0..1 -> InsnNode((value + 9).toInt()) + else -> LdcInsnNode(value) + } + + fun getFloatInsn(value: Float) = + when { + value % 1 == 0f && value in 0f..2f -> InsnNode((value + 11).toInt()) + else -> LdcInsnNode(value) + } + + fun getDoubleInsn(value: Double) = + when { + value % 1 == 0.0 && value in 0.0..1.0 -> InsnNode((value + 14).toInt()) + else -> LdcInsnNode(value) + } +} + +fun insnBuilder(builder: InsnBuilder.() -> Unit) = InsnBuilder().also(builder).insnList \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/api/interfaces/IBytecode.kt b/src/main/java/dev/zprotect/obfuscator/api/interfaces/IBytecode.kt new file mode 100644 index 0000000..d0c9bf7 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/api/interfaces/IBytecode.kt @@ -0,0 +1,72 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.api.interfaces + +import dev.zprotect.obfuscator.Obfuscator +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.MethodNode +import java.io.ByteArrayOutputStream +import java.io.InputStream + +interface IBytecode { + companion object : IBytecode + + fun isExcluded(name: String): Boolean { + val path = getPath(name) + for (exclusion in Obfuscator.getExclusions()) { + if (path.contains(exclusion)) return true + } + return false + } + + fun getPath(name: String): String { + if (!name.contains("/")) return "" + val reversedString = name.reversed() + val path = reversedString.substring(reversedString.indexOf("/")) + return path.reversed() + } + + fun getMethod(classNode: ClassNode, method: String): MethodNode? { + classNode.methods.forEach { methodNode -> + if (methodNode.name == method) return methodNode + } + + return null + } + + fun readBytes(inputStream: InputStream): ByteArray { + val bytes: ByteArray + val byteArrayOutputStream = ByteArrayOutputStream() + val buf = ByteArray(256) + var n: Int + while (inputStream.read(buf).also { n = it } != -1) byteArrayOutputStream.write(buf, 0, n) + bytes = byteArrayOutputStream.toByteArray() + + return bytes + } + + fun writeBytes(classNode: ClassNode): ByteArray { + val writer = ClassWriter(ClassWriter.COMPUTE_MAXS) + writer.newUTF8("Obfuscated with zProtect, visit https://zprotect.dev for more information.") + classNode.accept(writer) + return writer.toByteArray() + } + + fun toByteArrayDefault(classNode: ClassNode): ByteArray? { + val classWriter = ClassWriter(ClassWriter.COMPUTE_MAXS) + classNode.accept(classWriter) + return classWriter.toByteArray() + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/api/interfaces/INode.kt b/src/main/java/dev/zprotect/obfuscator/api/interfaces/INode.kt new file mode 100644 index 0000000..c82ff59 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/api/interfaces/INode.kt @@ -0,0 +1,64 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.api.interfaces + +import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.tree.AbstractInsnNode +import org.objectweb.asm.tree.InsnNode +import org.objectweb.asm.tree.IntInsnNode +import org.objectweb.asm.tree.LdcInsnNode + +interface INode { + + companion object : INode + + fun getLdcInt(int: Int): AbstractInsnNode { + if (int <= 32767 && int >= -32768) { + return IntInsnNode(SIPUSH, int) + } else if (int <= 127 && int >= -128) { + return IntInsnNode(BIPUSH, int) + } + + return when (int) { + -1 -> InsnNode(ICONST_M1) + 0 -> InsnNode(ICONST_0) + 1 -> InsnNode(ICONST_1) + 2 -> InsnNode(ICONST_2) + 3 -> InsnNode(ICONST_3) + 4 -> InsnNode(ICONST_4) + 5 -> InsnNode(ICONST_5) + else -> LdcInsnNode(int) + } + } + + fun getIntFromAin(ain: AbstractInsnNode): Int { + return when (ain.opcode) { + ICONST_M1 -> -1 + ICONST_0 -> 0 + ICONST_1 -> 1 + ICONST_2 -> 2 + ICONST_3 -> 3 + ICONST_4 -> 4 + ICONST_5 -> 5 + SIPUSH -> (ain as IntInsnNode).operand + BIPUSH -> (ain as IntInsnNode).operand + LDC -> (ain as LdcInsnNode).cst as Int + else -> -2 + } + } + + fun isReturn(ain: AbstractInsnNode): Boolean { + return ain.opcode in IRETURN..RETURN + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/api/settings/Config.kt b/src/main/java/dev/zprotect/obfuscator/api/settings/Config.kt new file mode 100644 index 0000000..e0aa73c --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/api/settings/Config.kt @@ -0,0 +1,62 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.api.settings + +import org.json.JSONArray +import org.json.JSONObject +import org.json.JSONTokener +import java.io.File +import java.io.FileInputStream + +open class Config(private val name: String, private val type: Type, private val read: (JSONObject) -> T) { + var value: T? = null + + companion object { + private lateinit var READ_OBJECT: JSONObject + private val TO_READ: ArrayList> = arrayListOf() + private val IS_INITIALIZED: Boolean + get() = ::READ_OBJECT.isInitialized + + fun read(file: File?) { + READ_OBJECT = JSONObject(JSONTokener(FileInputStream(file))) + TO_READ.forEach { it.read(READ_OBJECT) } + } + } + + init { + if (IS_INITIALIZED) read(READ_OBJECT) + else TO_READ.add(this) + } + + private fun read(jsonObject: JSONObject) { + value = if (jsonObject.has(name)) read.invoke(jsonObject) else null + } + + enum class Type { + ARRAY, STRING, BOOLEAN + } +} + +class ArrayConfig(name: String?) : Config(name!!, Type.ARRAY, { + it.getJSONArray(name) +}) + +class StringConfig(name: String?) : Config(name!!, Type.STRING, { + it.getString(name) +}) + +class BooleanConfig(name: String?) : Config(name!!, Type.BOOLEAN, { + it.getBoolean(name) +}) + diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/antitamper/AntiDebug.kt b/src/main/java/dev/zprotect/obfuscator/transformers/antitamper/AntiDebug.kt new file mode 100644 index 0000000..4615085 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/antitamper/AntiDebug.kt @@ -0,0 +1,102 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.antitamper + +import dev.zprotect.obfuscator.api.Transformer +import dev.zprotect.obfuscator.api.encryption.Dictionary +import dev.zprotect.obfuscator.api.insn.insnBuilder +import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.tree.LabelNode +import org.objectweb.asm.tree.MethodInsnNode +import org.objectweb.asm.tree.MethodNode + +object AntiDebug : Transformer("AntiDebug", "Blocks debugging options on terminal.") { + + private val debugTypes = arrayOf("-Xbootclasspath", "-Xdebug", "-agentlib", "-Xrunjdwp:", /*"-verbose"*/) + + override fun obfuscate() { + // Have fun. :) + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + classNode.methods.find { it.name.equals("") }.also { + val methodNode = makeMethod() + it?.instructions?.insertBefore( + it.instructions.last, + MethodInsnNode(INVOKESTATIC, classNode.name, methodNode.name, methodNode.desc, false) + ) + classNode.methods.add(methodNode) + } + } + } + + // TODO: Fix crashing with minecraft... ??? + private fun makeMethod(): MethodNode { + val method = MethodNode() + with(method) { + access = ACC_PRIVATE or ACC_STATIC + name = Dictionary.generateString(8) + desc = "()V" + signature = null + exceptions = null + maxStack = 2 + instructions = insnBuilder { + invokestatic( + "java/lang/management/ManagementFactory", + "getRuntimeMXBean", + "()Ljava/lang/management/RuntimeMXBean;", + false + ) + invokeinterface("java/lang/management/RuntimeMXBean", "getInputArguments", "()Ljava/util/List;", true) + invokeinterface("java/util/List", "iterator", "()Ljava/util/Iterator;", true) + astore(1) + val label1 = LabelNode() + +label1 + frame(F_APPEND, 1, arrayOf("java/util/Iterator"), 0, null) + aload(1) + invokeinterface("java/util/Iterator", "hasNext", "()Z", true) + val label2 = LabelNode() + ifeq(label2) + aload(1) + invokeinterface("java/util/Iterator", "next", "()Ljava/lang/Object;", true) + checkcast("java/lang/String") + astore(2) + +LabelNode() + aload(2) + ldc("-javaagent:") // -agentpath when? + invokevirtual("java/lang/String", "startsWith", "(Ljava/lang/String;)Z", false) + val label4 = LabelNode() + debugTypes.forEach { + ifne(label4) + aload(2) + ldc(it) + invokevirtual("java/lang/String", "startsWith", "(Ljava/lang/String;)Z", false) + } + val label5 = LabelNode() + ifeq(label5) + +label4 + frame(F_APPEND, 1, arrayOf("java/lang/String"), 0, null) + insn(ICONST_0) + invokestatic("java/lang/System", "exit", "(I)V", false) + +label5 + frame(F_CHOP, 1, null, 0, null) + goto(label1) + +label2 + frame(F_CHOP, 1, null, 0, null) + _return() + } + } + return method + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/decompiler/BadAnnotationCrasher.kt b/src/main/java/dev/zprotect/obfuscator/transformers/decompiler/BadAnnotationCrasher.kt new file mode 100644 index 0000000..1ba0081 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/decompiler/BadAnnotationCrasher.kt @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.decompiler + +import dev.zprotect.obfuscator.api.Transformer +import joptsimple.internal.Strings +import org.objectweb.asm.tree.AnnotationNode + + +object BadAnnotationCrasher : + Transformer( + "BadAnnotationCrasher", + "Generates invisible annotations which will cause Procyon to be very slow." + ) { + + private val string: String = Strings.repeat('\n', 40) // I think making this any higher is a bad idea... + + override fun obfuscate() { + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + if (classNode.invisibleAnnotations == null) + classNode.invisibleAnnotations = arrayListOf() + + classNode.invisibleAnnotations.add(getAnnotationNode()) + + classNode.fields.forEach { fieldNode -> + if (fieldNode.invisibleAnnotations == null) { + fieldNode.invisibleAnnotations = arrayListOf() + + fieldNode.invisibleAnnotations.add(getAnnotationNode()) + } + } + + classNode.methods.forEach { methodNode -> + if (methodNode.invisibleAnnotations == null) { + methodNode.invisibleAnnotations = arrayListOf() + + methodNode.invisibleAnnotations.add(getAnnotationNode()) + } + } + } + } + + private fun getAnnotationNode(): AnnotationNode? { + return AnnotationNode(string) + } +} + + diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/decompiler/DecompilerCrasher.kt b/src/main/java/dev/zprotect/obfuscator/transformers/decompiler/DecompilerCrasher.kt new file mode 100644 index 0000000..82d04ba --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/decompiler/DecompilerCrasher.kt @@ -0,0 +1,55 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.decompiler + +import dev.zprotect.obfuscator.api.Transformer +import org.objectweb.asm.Handle +import org.objectweb.asm.Label +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.* + +/* Use noverify for now, I would suggest not using this transformer and use + BadAnnotationCrasher instead even though it's weaker and more stupid. + */ + +object DecompilerCrasher : Transformer("DecompilerCrasher", "Fucks the decompiler.") { + + override fun obfuscate() { + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + // Just don't code in minecraft... + //if (classNode.name.contains("Mixin")) return@forEach + classNode.methods.filterNot { it.name == "" }.forEach { method -> + if (method.instructions.first != null) { + val label = LabelNode(Label()) + val insnList = InsnList().apply { + //add(emitIntPush(0)) + add(JumpInsnNode(Opcodes.IFNE, label)) + add(InvokeDynamicInsnNode("i am sad your java got yeeted", "()V", Handle(6, "a", "a", "(IIIIIIIIIIIIIIIIIII)I"))) + add(InsnNode(Opcodes.ACONST_NULL)) + add(TypeInsnNode(Opcodes.CHECKCAST, "give up and buy zprotect")) + // TODO: Push to stack lol + add(InsnNode(Opcodes.LDC)) // prob do trick idk + // if you push to stack the bytecode would probably be kinda valid ngl lmao + //add(InsnNode(Opcodes.POP)) Causes crash when you run the program... + add(label) + } + + method.instructions.insertBefore(method.instructions.first, insnList) + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/flow/Flow.kt b/src/main/java/dev/zprotect/obfuscator/transformers/flow/Flow.kt new file mode 100644 index 0000000..a673dac --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/flow/Flow.kt @@ -0,0 +1,57 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.flow + +import dev.zprotect.obfuscator.api.Transformer +import dev.zprotect.obfuscator.api.encryption.Dictionary +import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.tree.FieldInsnNode +import org.objectweb.asm.tree.FieldNode + +import org.objectweb.asm.tree.InsnNode +import org.objectweb.asm.tree.JumpInsnNode + +object Flow : Transformer("Flow", "Adds fake jumps, and such to code.") { + + override fun obfuscate() { + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + var hasField = false + val fieldName = Dictionary.generateString(8) + classNode.methods.forEach { methodNode -> + methodNode.instructions.filter { it.opcode == GOTO }.forEach { + hasField = true + with(methodNode) { + instructions.insertBefore(it, FieldInsnNode(GETSTATIC, classNode.name, fieldName, "Z")) + instructions.insert(it, InsnNode(ATHROW)) + instructions.insert(it, InsnNode(ACONST_NULL)) + instructions.set(it, JumpInsnNode(IFEQ, (it as JumpInsnNode).label)) + } + } + } + + checkCondition(hasField) { + classNode.fields.add(FieldNode(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, fieldName, "Z", null, null)) + } + } + } + + fun checkCondition(condition: Boolean, runnable: Runnable) { + if (!condition) { + return + } + runnable.run() + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/naming/ClassRenamer.kt b/src/main/java/dev/zprotect/obfuscator/transformers/naming/ClassRenamer.kt new file mode 100644 index 0000000..bbd73b7 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/naming/ClassRenamer.kt @@ -0,0 +1,42 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.naming + +import dev.zprotect.obfuscator.api.Transformer +import dev.zprotect.obfuscator.api.encryption.Dictionary +import dev.zprotect.obfuscator.api.settings.StringConfig + +object ClassRenamer : Transformer("ClassRenamer", "Renames class names.") { + + private val path = StringConfig("ClassRenamerPath") + + override fun obfuscate() { + val remap: MutableMap = HashMap() + + val prefix = if (path.value == null) "" else path.value + + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + val name = prefix + Dictionary.getNewName() + remap[classNode.name] = name + if (classNode.name.replace("/", ".") == manifest.mainAttributes.getValue("Main-Class")) + manifest.mainAttributes.putValue("Main-Class", name.replace("/", ".")) + } + + applyRemap(remap) + + Dictionary.reset() + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/naming/FieldRenamer.kt b/src/main/java/dev/zprotect/obfuscator/transformers/naming/FieldRenamer.kt new file mode 100644 index 0000000..5e887bf --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/naming/FieldRenamer.kt @@ -0,0 +1,57 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.naming + +import dev.zprotect.obfuscator.api.ClassEntry +import dev.zprotect.obfuscator.api.ClassPath +import dev.zprotect.obfuscator.api.Transformer +import dev.zprotect.obfuscator.api.encryption.Dictionary +import org.objectweb.asm.tree.FieldNode +import java.util.* + +// TODO: Choose if we want to use the regular Dictionary or use another format. + +object FieldRenamer : Transformer("FieldRenamer", "Renames field names.") { + + override fun obfuscate() { + val remap: MutableMap = mutableMapOf() + val fieldMap: MutableMap = mutableMapOf() + + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + classNode.fields + .forEach { fieldNode -> fieldMap[fieldNode] = ClassPath.get(classNode) } + } + + for ((fieldNode, owner) in fieldMap.entries) { + val name = Dictionary.getNewName() + + val stack = Stack() + stack.add(owner) + + while (!stack.empty()) { + val classEntry = stack.pop() + remap[classEntry.getName() + "." + fieldNode.name] = name + + stack.addAll(getExtensions(classEntry)) + stack.addAll(getImplementations(classEntry)) + } + } + + applyRemap(remap) + + Dictionary.reset() + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/naming/LocalVariableRenamer.kt b/src/main/java/dev/zprotect/obfuscator/transformers/naming/LocalVariableRenamer.kt new file mode 100644 index 0000000..718dbf1 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/naming/LocalVariableRenamer.kt @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.naming + +import dev.zprotect.obfuscator.api.Transformer +import dev.zprotect.obfuscator.api.encryption.Dictionary +import org.objectweb.asm.tree.LocalVariableNode + +object LocalVariableRenamer : Transformer("LocalVariableRenamer", "Obfuscates local variables.") { + + var hashMap = HashMap() + + override fun obfuscate() { + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + classNode.methods.stream() + .forEach { methodNode -> + //if (methodNode.localVariables == null) return@forEach + for (i in 0 until methodNode.localVariables.size) { + val localVariableNode: LocalVariableNode = methodNode.localVariables.get(i) + methodNode.localVariables.set( + i, + LocalVariableNode( + Dictionary.getNewName(), + localVariableNode.desc, + null, + localVariableNode.start, + localVariableNode.end, + i + ) + ) + hashMap[localVariableNode.desc] = java.lang.Boolean.TRUE + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/naming/MethodRenamer.kt b/src/main/java/dev/zprotect/obfuscator/transformers/naming/MethodRenamer.kt new file mode 100644 index 0000000..2d337f4 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/naming/MethodRenamer.kt @@ -0,0 +1,80 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.naming + +import dev.zprotect.obfuscator.api.ClassEntry +import dev.zprotect.obfuscator.api.ClassPath +import dev.zprotect.obfuscator.api.Transformer +import dev.zprotect.obfuscator.api.encryption.Dictionary +import dev.zprotect.obfuscator.hasAccess +import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.tree.MethodNode +import java.util.* + +object MethodRenamer : Transformer("MethodRenamer", "Renames method names.") { + private val badNames = arrayListOf("main") + + override fun obfuscate() { + val remap: MutableMap = mutableMapOf() + val methodMap: MutableMap = mutableMapOf() + + // bruh classNode.access and ACC_ANNOTATION == 0 && classNode.access and ACC_ENUM == 0 && classNode.access and ACC_ABSTRACT == 0 + + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) && classNode.hasAccess(ACC_ANNOTATION or ACC_ENUM or ACC_ABSTRACT) } + .forEach { classNode -> + classNode.methods.stream() + .filter { methodNode -> !badNames.contains(methodNode.name) && !methodNode.name.startsWith("<") && methodNode.hasAccess(ACC_NATIVE) } + .forEach { methodNode -> methodMap[methodNode] = ClassPath.get(classNode) } + } + + methods@ + for ((methodNode, owner) in methodMap.entries) { + val stack = Stack() + stack.add(owner) + + while (stack.isNotEmpty()) { + val classEntry = stack.pop() + + if (classEntry != owner && classEntry.getMethods() + .findLast { method -> method.name == methodNode.name && method.desc == methodNode.desc } != null + ) + continue@methods + + val parent = if (classEntry.getSuper() == null) null else ClassPath[classEntry.getSuper()] + if (parent != null) stack.push(parent) + + classEntry.getInterfaces().forEach { inter: String -> + val interfNode = ClassPath[inter] + if (interfNode != null) stack.push(interfNode) + } + } + + val name = Dictionary.getNewName() + stack.add(owner) + + while (stack.isNotEmpty()) { + val classEntry = stack.pop() + remap[classEntry.getName() + "." + methodNode.name + methodNode.desc] = name + + stack.addAll(getExtensions(classEntry)) + stack.addAll(getImplementations(classEntry)) + } + } + + applyRemap(remap) + + Dictionary.reset() + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/optimization/EnumOptimiser.kt b/src/main/java/dev/zprotect/obfuscator/transformers/optimization/EnumOptimiser.kt new file mode 100644 index 0000000..7a43afc --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/optimization/EnumOptimiser.kt @@ -0,0 +1,55 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.optimization + +import dev.zprotect.obfuscator.api.Transformer +import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.tree.MethodInsnNode + +object EnumOptimiser : Transformer("EnumOptimiser", "Removes clone call and return array.") { + + override fun obfuscate() { + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + if (classNode.access.hasAccess(ACC_ENUM)) { + val desc = "[L${classNode.name};" + + val valuesMethod = classNode.methods.firstOrNull { + it.name == "values" + && + it.desc == "()$desc" + && + it.instructions.size() >= 4 + } ?: return@forEach + + for (insnNode in valuesMethod.instructions) { + if (insnNode is MethodInsnNode) { + if ( + insnNode.opcode == INVOKEVIRTUAL + && + insnNode.name == "clone" + ) { + if (insnNode.next.opcode == CHECKCAST) { + valuesMethod.instructions.remove(insnNode.next) + } + valuesMethod.instructions.remove(insnNode) + } + } + } + } + } + } + private fun Int.hasAccess(access: Int) = this and access != 0 +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/optimization/FinalRemover.kt b/src/main/java/dev/zprotect/obfuscator/transformers/optimization/FinalRemover.kt new file mode 100644 index 0000000..91ad8d8 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/optimization/FinalRemover.kt @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.optimization + +import dev.zprotect.obfuscator.api.Transformer +import org.objectweb.asm.Opcodes.ACC_FINAL +import org.objectweb.asm.tree.FieldNode + +object FinalRemover : Transformer("FinalRemover", "Removes final.") { + + override fun obfuscate() { + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + for (i in 0 until classNode.fields.size) { + val fieldNode: FieldNode = classNode.fields.get(i) + if (fieldNode.access or ACC_FINAL !== 0) fieldNode.access = fieldNode.access and ACC_FINAL.inv() + classNode.fields.set(i, fieldNode) + } + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/optimization/HideClassMembers.kt b/src/main/java/dev/zprotect/obfuscator/transformers/optimization/HideClassMembers.kt new file mode 100644 index 0000000..3660361 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/optimization/HideClassMembers.kt @@ -0,0 +1,45 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.optimization + +import dev.zprotect.obfuscator.api.Transformer +import org.objectweb.asm.Opcodes + +//private const val ANNOTATION_REF = "java/lang/annotation/Annotation" + +object HideClassMembers : Transformer("HideClassMembers", "Mark classes as synthetic to hide them from bad decompilers.") { + + override fun obfuscate() { + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + + /*if (!classNode.editable) return + classNode.modified = true + + if (!classNode.dependencies.contains(ANNOTATION_REF)) { + classNode.access = classNode.access or Opcodes.ACC_SYNTHETIC + }*/ + + + classNode.methods.forEach { methodNode -> + methodNode.access = methodNode.access or Opcodes.ACC_SYNTHETIC or Opcodes.ACC_BRIDGE + } + + classNode.fields.forEach { fieldNode -> + fieldNode.access = fieldNode.access or Opcodes.ACC_SYNTHETIC + } + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/optimization/InsnRemover.kt b/src/main/java/dev/zprotect/obfuscator/transformers/optimization/InsnRemover.kt new file mode 100644 index 0000000..af2c62d --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/optimization/InsnRemover.kt @@ -0,0 +1,100 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.optimization + +import dev.zprotect.obfuscator.api.Transformer +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.* + +object InsnRemover : Transformer("InsnRemover", "Removes the instructions const_. and tableswitch.") { + + override fun obfuscate() { + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + classNode.methods.forEach { methodNode -> + val newInsnSet = InsnList() + insn@ for (abstractInsnNode in methodNode.instructions.toArray()) { + if (abstractInsnNode is TableSwitchInsnNode) { + + val labelNodeStart = LabelNode() + val clearStack = InsnNode(Opcodes.POP) + + val jumpInsnNodeStart = JumpInsnNode(Opcodes.GOTO, abstractInsnNode.dflt) + val labelNodeEnd = LabelNode() + val jumpInsnNodeEnd = JumpInsnNode(Opcodes.GOTO, labelNodeEnd) + + newInsnSet.add(jumpInsnNodeEnd) + newInsnSet.add(labelNodeStart) + newInsnSet.add(clearStack) + newInsnSet.add(jumpInsnNodeStart) + newInsnSet.add(labelNodeEnd) + + newInsnSet.add(InsnNode(Opcodes.DUP)) + newInsnSet.add(LdcInsnNode(-abstractInsnNode.min)) + newInsnSet.add(InsnNode(Opcodes.IADD)) + newInsnSet.add(JumpInsnNode(Opcodes.IFLT, labelNodeStart)) + newInsnSet.add(InsnNode(Opcodes.DUP)) + newInsnSet.add(LdcInsnNode(-abstractInsnNode.max)) + newInsnSet.add(InsnNode(Opcodes.IADD)) + newInsnSet.add(JumpInsnNode(Opcodes.IFGT, labelNodeStart)) + newInsnSet.add(InsnNode(Opcodes.DUP)) + newInsnSet.add(LdcInsnNode(-abstractInsnNode.min)) + newInsnSet.add(InsnNode(Opcodes.IADD)) + var labelIndex = 0 + for (label in abstractInsnNode.labels) { + val nextBranch = LabelNode() + newInsnSet.add(InsnNode(Opcodes.DUP)) + newInsnSet.add(JumpInsnNode(Opcodes.IFNE, nextBranch)) + newInsnSet.add(InsnNode(Opcodes.POP)) + newInsnSet.add(InsnNode(Opcodes.POP)) + newInsnSet.add(JumpInsnNode(Opcodes.GOTO, label)) + newInsnSet.add(nextBranch) + if (labelIndex + 1 != abstractInsnNode.labels.size) { + newInsnSet.add(LdcInsnNode(-1)) + newInsnSet.add(InsnNode(Opcodes.IADD)) + } + labelIndex++ + } + newInsnSet.add(InsnNode(Opcodes.POP)) + newInsnSet.add(JumpInsnNode(Opcodes.GOTO, labelNodeStart)) + } else { + when (abstractInsnNode.opcode) { + Opcodes.ICONST_M1, Opcodes.ICONST_0, Opcodes.ICONST_1, Opcodes.ICONST_2, Opcodes.ICONST_3, Opcodes.ICONST_4, Opcodes.ICONST_5 -> { + newInsnSet.add(LdcInsnNode(abstractInsnNode.opcode - 3)) + continue@insn + } + Opcodes.LCONST_0, Opcodes.LCONST_1 -> { + newInsnSet.add(LdcInsnNode(abstractInsnNode.opcode - 9L)) + continue@insn + } + Opcodes.FCONST_0, Opcodes.FCONST_1, Opcodes.FCONST_2 -> { + newInsnSet.add(LdcInsnNode(abstractInsnNode.opcode - 11f)) + continue@insn + } + Opcodes.DCONST_0, Opcodes.DCONST_1 -> { + newInsnSet.add(LdcInsnNode(abstractInsnNode.opcode - 14.0)) + continue@insn + } + } + } + newInsnSet.add(abstractInsnNode) + } + methodNode.instructions = newInsnSet + } + } + } +} + + diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/optimization/KotlinMetadataRemover.kt b/src/main/java/dev/zprotect/obfuscator/transformers/optimization/KotlinMetadataRemover.kt new file mode 100644 index 0000000..3ccee14 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/optimization/KotlinMetadataRemover.kt @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.optimization + +import dev.zprotect.obfuscator.api.Transformer + +object KotlinMetadataRemover : Transformer("KotlinMetadataRemover", "Removes Kotlin Metadata.") { + + override fun obfuscate() { + + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + + if (classNode.visibleAnnotations != null) { + classNode.visibleAnnotations = + classNode.visibleAnnotations.filter { it.desc != "Lkotlin/Metadata;" } + } + + val annotations = arrayOf( + "Lorg/jetbrains/annotations/NotNull;", + "Lorg/jetbrains/annotations/JvmName;", + "Lorg/jetbrains/annotations/Nullable;" + ) + + classNode.methods.forEach { method -> + if (method.invisibleAnnotations != null) { + method.invisibleAnnotations = + method.invisibleAnnotations.filterNot { annotations.contains(it.desc) } + } + + if (method.visibleAnnotations != null) { + method.visibleAnnotations = + method.visibleAnnotations.filterNot { annotations.contains(it.desc) } + } + } + + classNode.fields.forEach { field -> + if (field.invisibleAnnotations != null) { + field.invisibleAnnotations = + field.invisibleAnnotations.filterNot { annotations.contains(it.desc) } + } + + if (field.visibleAnnotations != null) { + field.visibleAnnotations = field.visibleAnnotations.filterNot { annotations.contains(it.desc) } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/optimization/NOPInsnRemover.kt b/src/main/java/dev/zprotect/obfuscator/transformers/optimization/NOPInsnRemover.kt new file mode 100644 index 0000000..ba71d4f --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/optimization/NOPInsnRemover.kt @@ -0,0 +1,31 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.optimization + +import dev.zprotect.obfuscator.api.Transformer +import org.objectweb.asm.Opcodes.NOP + +object NOPInsnRemover : Transformer("NOPInsnRemover", "Removes extended type information.") { + + override fun obfuscate() { + classes.stream() + .filter { cn -> !isExcluded(cn.name) } + .forEach { cn -> + cn.methods.forEach { mn -> + mn.instructions.removeAll { ain -> ain.opcode == NOP } + } + } + } +} + diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/optimization/RemoveSignatures.kt b/src/main/java/dev/zprotect/obfuscator/transformers/optimization/RemoveSignatures.kt new file mode 100644 index 0000000..d9d25e7 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/optimization/RemoveSignatures.kt @@ -0,0 +1,30 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.optimization + +import dev.zprotect.obfuscator.api.Transformer + +object RemoveSignatures : Transformer("RemoveSignatures", "Removes extended type information.") { + + override fun obfuscate() { + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + classNode.signature = null + classNode.methods.forEach { methodNode -> + methodNode.signature = null + } + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/poolers/NumberPooler.kt b/src/main/java/dev/zprotect/obfuscator/transformers/poolers/NumberPooler.kt new file mode 100644 index 0000000..1650bfa --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/poolers/NumberPooler.kt @@ -0,0 +1,101 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.poolers + +import dev.zprotect.obfuscator.api.Transformer +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.* + +object NumberPooler : Transformer("NumberPooler", "Moves numbers into an array.") { + + private const val numberPoolerArray = "¤¶§†!|~ÇüéäçêïîìæÆ¢£¥Pƒáíúñ¿¬½¼¡«»¦ßµ±°•·²€„…‡‰Š‹Œ˜™š›œŸ¨©®¯³´¸¹¾Ð×ØÞãðõ÷øüþ" + + override fun obfuscate() { + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + val numberMap: MutableMap = mutableMapOf() + classNode.methods.forEach { methodNode -> + methodNode.instructions.forEach { abstractInsnNode -> + if ((abstractInsnNode is LdcInsnNode && abstractInsnNode.cst is Int) || (abstractInsnNode.opcode == Opcodes.BIPUSH || abstractInsnNode.opcode == Opcodes.SIPUSH) + || (abstractInsnNode.opcode in Opcodes.ICONST_M1..Opcodes.ICONST_5) + ) { + numberMap[abstractInsnNode] = methodNode + } + } + } + + if (numberMap.isNotEmpty()) { + classNode.fields.add( + FieldNode( + (if (classNode.access and Opcodes.ACC_INTERFACE != 0) Opcodes.ACC_PUBLIC else Opcodes.ACC_PRIVATE) or (if (classNode.version > Opcodes.V1_8) 0 else Opcodes.ACC_FINAL) or Opcodes.ACC_STATIC, + numberPoolerArray, + "[I", + null, + null + ) + ) + + var clinit = getMethod(classNode, "") + if (clinit == null) { + clinit = MethodNode(Opcodes.ACC_STATIC, "", "()V", null, arrayOf()) + classNode.methods.add(clinit) + } + if (clinit.instructions == null) clinit.instructions = InsnList() + + val arrayInstructions = InsnList().apply { + add(getLdcInt(numberMap.size)) + add(IntInsnNode(Opcodes.NEWARRAY, Opcodes.T_INT)) + + for ((index, abstractInsnNode) in numberMap.keys.withIndex()) { + add(InsnNode(Opcodes.DUP)) + add(getLdcInt(index)) + add(getLdcInt(getIntFromAin(abstractInsnNode))) + add(InsnNode(Opcodes.IASTORE)) + + numberMap[abstractInsnNode]!!.instructions.insert(abstractInsnNode, InsnList().apply { + add( + FieldInsnNode( + Opcodes.GETSTATIC, + classNode.name, + numberPoolerArray, + "[I" + ) + ) + add(getLdcInt(index)) + add(InsnNode(Opcodes.IALOAD)) + }) + numberMap[abstractInsnNode]!!.instructions.remove(abstractInsnNode) + } + + add( + FieldInsnNode( + Opcodes.PUTSTATIC, + classNode.name, + numberPoolerArray, + "[I" + ) + ) + } + + if (clinit.instructions == null || clinit.instructions.first == null) { + clinit.instructions.add(arrayInstructions) + clinit.instructions.add(InsnNode(Opcodes.RETURN)) + } else { + clinit.instructions.insertBefore(clinit.instructions.first, arrayInstructions) + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/poolers/StringPooler.kt b/src/main/java/dev/zprotect/obfuscator/transformers/poolers/StringPooler.kt new file mode 100644 index 0000000..dd91085 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/poolers/StringPooler.kt @@ -0,0 +1,97 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.poolers + +import dev.zprotect.obfuscator.api.Transformer +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.* + +object StringPooler : Transformer("StringPooler", "Moves strings into an array.") { + + private const val stringPoolerArray = "¤¶§†!|~ÇüéäçêïîìæÆ¢£¥Pƒáíúñ¿¬½¼¡«»¦ßµ±°•·²€„…‡‰Š‹Œ˜™š›œŸ¨©®¯³´¸¹¾Ð×ØÞãðõ÷øüþp" + + override fun obfuscate() { + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + val ldcInsnNodeMap: MutableMap = mutableMapOf() + classNode.methods.forEach { methodNode -> + methodNode.instructions.forEach { abstractInsnNode -> + if (abstractInsnNode is LdcInsnNode && abstractInsnNode.cst is String) ldcInsnNodeMap[abstractInsnNode] = methodNode + } + } + + if (ldcInsnNodeMap.isNotEmpty()) { + classNode.fields.add( + FieldNode( + (if (classNode.access and Opcodes.ACC_INTERFACE != 0) Opcodes.ACC_PUBLIC else Opcodes.ACC_PRIVATE) or (if (classNode.version > Opcodes.V1_8) 0 else Opcodes.ACC_FINAL) or Opcodes.ACC_STATIC, + stringPoolerArray, + "[Ljava/lang/String;", + null, + null + ) + ) + + var clinit = getMethod(classNode, "") + if (clinit == null) { + clinit = MethodNode(Opcodes.ACC_STATIC, "", "()V", null, arrayOf()) + classNode.methods.add(clinit) + } + if (clinit.instructions == null) clinit.instructions = InsnList() + + val arrayInstructions = InsnList().apply { + add(getLdcInt(ldcInsnNodeMap.size)) // set array length + add(TypeInsnNode(Opcodes.ANEWARRAY, "java/lang/String")) // create array + + for ((index, ldc) in ldcInsnNodeMap.keys.withIndex()) { + add(InsnNode(Opcodes.DUP)) + add(getLdcInt(index)) + add(LdcInsnNode(ldc.cst as String)) + add(InsnNode(Opcodes.AASTORE)) + + ldcInsnNodeMap[ldc]!!.instructions.insert(ldc, InsnList().apply { + add( + FieldInsnNode( + Opcodes.GETSTATIC, + classNode.name, + stringPoolerArray, + "[Ljava/lang/String;" + ) + ) + add(getLdcInt(index)) + add(InsnNode(Opcodes.AALOAD)) + }) + ldcInsnNodeMap[ldc]!!.instructions.remove(ldc) + } + + add( + FieldInsnNode( + Opcodes.PUTSTATIC, + classNode.name, + stringPoolerArray, + "[Ljava/lang/String;" + ) + ) + } + + if (clinit.instructions == null || clinit.instructions.first == null) { + clinit.instructions.add(arrayInstructions) + clinit.instructions.add(InsnNode(Opcodes.RETURN)) + } else { + clinit.instructions.insertBefore(clinit.instructions.first, arrayInstructions) + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/shrinking/LineNumberRemover.kt b/src/main/java/dev/zprotect/obfuscator/transformers/shrinking/LineNumberRemover.kt new file mode 100644 index 0000000..47c0cb8 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/shrinking/LineNumberRemover.kt @@ -0,0 +1,29 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.shrinking + +import dev.zprotect.obfuscator.api.Transformer +import org.objectweb.asm.tree.LineNumberNode + +object LineNumberRemover : Transformer("LineNumberRemover", "Removes line numbers so StackTraces show (Unknown) on errors.") { + override fun obfuscate() { + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + classNode.methods.forEach { methodNode -> + methodNode.instructions.removeAll { abstractInsnNode -> abstractInsnNode is LineNumberNode } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/shrinking/LocalVariableRemover.kt b/src/main/java/dev/zprotect/obfuscator/transformers/shrinking/LocalVariableRemover.kt new file mode 100644 index 0000000..db4b3c4 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/shrinking/LocalVariableRemover.kt @@ -0,0 +1,32 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.shrinking + +import dev.zprotect.obfuscator.api.Transformer + +object LocalVariableRemover : Transformer("LocalVariableRemover", "Removes local variables.") { + + override fun obfuscate() { + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + classNode.methods.forEach { methodNode -> + if (methodNode.localVariables != null) { + methodNode.localVariables = null + } + + } + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/shrinking/RemoveInnerClasses.kt b/src/main/java/dev/zprotect/obfuscator/transformers/shrinking/RemoveInnerClasses.kt new file mode 100644 index 0000000..610de15 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/shrinking/RemoveInnerClasses.kt @@ -0,0 +1,30 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.shrinking + +import dev.zprotect.obfuscator.api.Transformer + +object RemoveInnerClasses : Transformer("RemoveInnerClasses", "Removes inner classes.") { + + override fun obfuscate() { + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + classNode.outerClass = null + classNode.outerMethod = null + classNode.outerMethodDesc = null + classNode.innerClasses.clear() + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/shrinking/SourceDebugRemover.kt b/src/main/java/dev/zprotect/obfuscator/transformers/shrinking/SourceDebugRemover.kt new file mode 100644 index 0000000..7e36853 --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/shrinking/SourceDebugRemover.kt @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.shrinking + +import dev.zprotect.obfuscator.api.Transformer + +object SourceDebugRemover : Transformer("SourceDebugRemover", "Removes the SourceDebug attribute.") { + + override fun obfuscate() { + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + classNode.sourceDebug = null + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/shrinking/SourceFileRemover.kt b/src/main/java/dev/zprotect/obfuscator/transformers/shrinking/SourceFileRemover.kt new file mode 100644 index 0000000..9b29dec --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/shrinking/SourceFileRemover.kt @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.shrinking + +import dev.zprotect.obfuscator.api.Transformer + +object SourceFileRemover : Transformer("SourceFileRemover", "Removes the SourceFile attribute.") { + + override fun obfuscate() { + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + classNode.sourceFile = null + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/shufflers/ShuffleFields.kt b/src/main/java/dev/zprotect/obfuscator/transformers/shufflers/ShuffleFields.kt new file mode 100644 index 0000000..a8bb1bf --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/shufflers/ShuffleFields.kt @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.shufflers + +import dev.zprotect.obfuscator.api.Transformer + +object ShuffleFields : Transformer("ShuffleFields", "Shuffles around fields.") { + + override fun obfuscate() { + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + classNode.fields.shuffle() + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/zprotect/obfuscator/transformers/shufflers/ShuffleMethods.kt b/src/main/java/dev/zprotect/obfuscator/transformers/shufflers/ShuffleMethods.kt new file mode 100644 index 0000000..99560db --- /dev/null +++ b/src/main/java/dev/zprotect/obfuscator/transformers/shufflers/ShuffleMethods.kt @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2022 zProtect + * + * This software is NOT free software. + * You may NOT distribute it or modify it without explicit permission. + * + * This software has been created in the hope that it will be useful, + * but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution + * or usage of any zProtect source code or products. + * + * You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program. + */ + +package dev.zprotect.obfuscator.transformers.shufflers + +import dev.zprotect.obfuscator.api.Transformer + +object ShuffleMethods : Transformer("ShuffleMethods", "Shuffles around methods.") { + + override fun obfuscate() { + classes.stream() + .filter { classNode -> !isExcluded(classNode.name) } + .forEach { classNode -> + classNode.methods.shuffle() + } + } +} \ No newline at end of file diff --git a/src/main/java/joptsimple/internal/Strings.java b/src/main/java/joptsimple/internal/Strings.java new file mode 100644 index 0000000..de0e999 --- /dev/null +++ b/src/main/java/joptsimple/internal/Strings.java @@ -0,0 +1,118 @@ +/* + The MIT License + + Copyright (c) 2004-2013 Paul R. Holser, Jr. + + 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. +*/ + +package joptsimple.internal; + +import java.util.Iterator; +import java.util.List; + +import static java.lang.System.getProperty; +import static java.util.Arrays.asList; + +/** + * @author Paul Holser + */ +public final class Strings { + public static final String EMPTY = ""; + public static final String SINGLE_QUOTE = "'"; + public static final String LINE_SEPARATOR = getProperty("line.separator"); + + private Strings() { + throw new UnsupportedOperationException(); + } + + /** + * Gives a string consisting of the given character repeated the given number of times. + * + * @param ch the character to repeat + * @param count how many times to repeat the character + * @return the resultant string + */ + public static String repeat(char ch, int count) { + StringBuilder buffer = new StringBuilder(); + + for (int i = 0; i < count; ++i) + buffer.append(ch); + + return buffer.toString(); + } + + /** + * Tells whether the given string is either {@code} or consists solely of whitespace characters. + * + * @param target string to check + * @return {@code true} if the target string is null or empty + */ + public static boolean isNullOrEmpty(String target) { + return target == null || EMPTY.equals(target); + } + + + /** + * Gives a string consisting of a given string prepended and appended with surrounding characters. + * + * @param target a string + * @param begin character to prepend + * @param end character to append + * @return the surrounded string + */ + public static String surround(String target, char begin, char end) { + return begin + target + end; + } + + /** + * Gives a string consisting of the elements of a given array of strings, each separated by a given separator + * string. + * + * @param pieces the strings to join + * @param separator the separator + * @return the joined string + */ + public static String join(String[] pieces, String separator) { + return join(asList(pieces), separator); + } + + /** + * Gives a string consisting of the string representations of the elements of a given array of objects, + * each separated by a given separator string. + * + * @param pieces the elements whose string representations are to be joined + * @param separator the separator + * @return the joined string + */ + public static String join(List pieces, String separator) { + StringBuilder buffer = new StringBuilder(); + + for (Iterator iter = pieces.iterator(); iter.hasNext(); ) { + buffer.append(iter.next()); + + if (iter.hasNext()) + buffer.append(separator); + } + + return buffer.toString(); + } +} +