├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── paranoid ├── build.gradle ├── core │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── joom │ │ └── paranoid │ │ ├── DeobfuscatorHelper.java │ │ ├── Obfuscate.java │ │ └── RandomHelper.java ├── gradle-plugin │ ├── .gitignore │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── joom │ │ │ └── paranoid │ │ │ └── plugin │ │ │ ├── AllClassesTransformRegisterAction.kt │ │ │ ├── BackupClassesTask.kt │ │ │ ├── GradleExtensions.kt │ │ │ ├── ObfuscationSeedCalculator.kt │ │ │ ├── ParanoidExtension.kt │ │ │ ├── ParanoidPlugin.kt │ │ │ ├── ParanoidTransformTask.kt │ │ │ └── ScopedArtifactsRegisterAction.kt │ │ └── resources │ │ └── META-INF │ │ └── gradle-plugins │ │ └── com.joom.paranoid.properties ├── gradle.properties ├── pablo.gradle ├── processor │ ├── build.gradle │ └── src │ │ └── main │ │ └── kotlin │ │ └── com │ │ └── joom │ │ └── paranoid │ │ └── processor │ │ ├── Analyzer.kt │ │ ├── CachedObfuscatedTypeRegistry.kt │ │ ├── ClassVisitorExtensions.kt │ │ ├── DeobfuscatorGenerator.kt │ │ ├── FileSinkFactory.kt │ │ ├── Model.kt │ │ ├── ObfuscatedTypeRegistry.kt │ │ ├── ObfuscatedTypeRegistryExtensions.kt │ │ ├── ObfuscatedTypeRegistryImpl.kt │ │ ├── ParanoidException.kt │ │ ├── ParanoidProcessor.kt │ │ ├── Patcher.kt │ │ ├── RemoveObfuscateClassPatcher.kt │ │ ├── StandaloneClassWriter.kt │ │ ├── StringConstantsClassPatcher.kt │ │ ├── StringLiteralsClassPatcher.kt │ │ ├── StringRegistry.kt │ │ ├── Types.kt │ │ ├── Validator.kt │ │ ├── commons │ │ └── CloseableExtensions.kt │ │ ├── logging │ │ └── LoggerExtensions.kt │ │ ├── model │ │ └── Deobfuscator.kt │ │ └── watermark │ │ ├── ParanoidAttribute.kt │ │ ├── WatermarkChecker.kt │ │ └── WatermarkClassVisitor.kt ├── settings.gradle └── src │ └── test │ └── java │ └── com │ └── joom │ └── paranoid │ └── plugin │ ├── GradleDistribution.kt │ └── ParanoidPluginTest.kt ├── processor-tests ├── all-chars-string │ ├── build.gradle │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── joom │ │ │ └── paranoid │ │ │ └── processor │ │ │ └── allcharsstring │ │ │ └── AllCharsStringTest.java │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── joom │ │ └── paranoid │ │ └── processor │ │ └── allcharsstring │ │ ├── AllCharString.kt │ │ └── MainActivity.kt ├── loads-of-strings │ ├── build.gradle │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── joom │ │ │ └── paranoid │ │ │ └── processor │ │ │ └── loadsofstrings │ │ │ └── LoadsOfStringsTest.java │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── joom │ │ └── paranoid │ │ └── processor │ │ └── loadsofstrings │ │ ├── LoadsOfStrings.kt │ │ └── MainActivity.kt ├── long-string │ ├── build.gradle │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── joom │ │ │ └── paranoid │ │ │ └── processor │ │ │ └── longstring │ │ │ └── LongStringTest.java │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── joom │ │ └── paranoid │ │ └── processor │ │ └── longstring │ │ ├── LongString.kt │ │ └── MainActivity.kt ├── subproject-android │ ├── build.gradle │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── joom │ │ └── paranoid │ │ └── processor │ │ └── subproject │ │ └── android │ │ └── AndroidConstants.java ├── subproject-java │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── joom │ │ └── paranoid │ │ └── processor │ │ └── subproject │ │ └── java │ │ └── JavaConstants.java └── subproject │ ├── build.gradle │ └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── joom │ │ └── paranoid │ │ └── processor │ │ └── subproject │ │ └── MainActivityTest.kt │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── joom │ └── paranoid │ └── processor │ └── subproject │ └── MainActivity.kt ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── release.keystore └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── joom │ │ └── paranoid │ │ └── sample │ │ └── MainActivityTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── joom │ │ │ └── paranoid │ │ │ └── sample │ │ │ └── MainActivity.kt │ └── res │ │ ├── layout │ │ └── main_activity.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── joom │ └── paranoid │ └── sample │ └── SampleTest.java ├── settings.gradle └── versions.gradle /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [ push, pull_request ] 3 | 4 | env: 5 | GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4g -Dorg.gradle.daemon=false -Dkotlin.incremental=false" 6 | PABLO_OPTS: | 7 | -Ppablo.repository.maven.name=Sonatype \ 8 | -Ppablo.repository.maven.url=https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ \ 9 | -Ppablo.repository.maven.username=${{ secrets.SONATYPE_USERNAME }} \ 10 | -Ppablo.repository.maven.password=${{ secrets.SONATYPE_PASSWORD }} \ 11 | -Ppablo.signing.keyId=${{ secrets.SIGNING_KEY_ID }} \ 12 | -Ppablo.signing.password=${{ secrets.SIGNING_PASSWORD }} \ 13 | -Ppablo.signing.secretKey='${{ secrets.SIGNING_SECRET_KEY }}' 14 | 15 | jobs: 16 | build: 17 | name: Build 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v2 23 | 24 | - name: Validate Gradle Wrapper 25 | uses: gradle/wrapper-validation-action@v1 26 | 27 | - name: Configure JDK 28 | uses: actions/setup-java@v2 29 | with: 30 | distribution: 'temurin' 31 | java-version: '17' 32 | 33 | - name: Publish bootstrap artifacts 34 | run: ./gradlew -p paranoid publishToMavenLocal -Pdevelopment=false -Ppablo.shadow.enabled=true ${{ env.PABLO_OPTS }} 35 | 36 | - name: Run functional test 37 | run: ./gradlew -p paranoid -Ppablo.shadow.enabled=true functionalTest 38 | 39 | - uses: actions/upload-artifact@v3 40 | if: failure() 41 | with: 42 | name: functional-test-report 43 | path: paranoid/build/reports/tests/functionalTest 44 | 45 | - name: Run unit tests 46 | run: ./gradlew check -x lint -Pdevelopment=false -Dorg.gradle.unsafe.configuration-cache=true 47 | 48 | test: 49 | name: Test 50 | runs-on: macos-latest 51 | strategy: 52 | matrix: 53 | api-level: [ 21, 29 ] 54 | target: [ default ] 55 | arch: [ x86_64 ] 56 | 57 | needs: 58 | - build 59 | 60 | steps: 61 | - name: Checkout 62 | uses: actions/checkout@v2 63 | 64 | - name: Configure JDK 65 | uses: actions/setup-java@v2 66 | with: 67 | distribution: 'temurin' 68 | java-version: '17' 69 | 70 | - name: Publish bootstrap artifacts 71 | run: ./gradlew -p paranoid publishToMavenLocal -Pdevelopment=false -Ppablo.shadow.enabled=true ${{ env.PABLO_OPTS }} 72 | 73 | - name: Run integration tests 74 | uses: reactivecircus/android-emulator-runner@v2 75 | with: 76 | api-level: ${{ matrix.api-level }} 77 | target: ${{ matrix.target }} 78 | arch: ${{ matrix.arch }} 79 | script: ./gradlew connectedCheck -Pdevelopment=false -Dorg.gradle.unsafe.configuration-cache=true 80 | 81 | publish: 82 | name: Publish 83 | runs-on: ubuntu-latest 84 | if: contains(github.ref, '/tags/v') 85 | needs: 86 | - build 87 | - test 88 | 89 | steps: 90 | - name: Checkout 91 | uses: actions/checkout@v2 92 | 93 | - name: Configure JDK 94 | uses: actions/setup-java@v2 95 | with: 96 | distribution: 'temurin' 97 | java-version: '17' 98 | 99 | - name: Upload Artifacts 100 | run: ./gradlew -p paranoid publishMavenPublicationToSonatypeRepository -Pdevelopment=false -Ppablo.shadow.enabled=true ${{ env.PABLO_OPTS }} 101 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.jar 3 | *.zip 4 | *.tar 5 | *.apk 6 | *.ap_ 7 | 8 | # Files for the Dalvik VM 9 | *.dex 10 | 11 | # Java class files 12 | *.class 13 | 14 | # Generated files 15 | bin/ 16 | gen/ 17 | 18 | # Gradle files 19 | .gradle/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # IntelliJ IDEA and Android Studio files 29 | .idea/ 30 | *.iml 31 | 32 | # Gradle Wrapper 33 | !gradle/wrapper/gradle-wrapper.jar 34 | 35 | # Pablo 36 | pablo.properties 37 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build](https://github.com/joomcode/paranoid/actions/workflows/build.yml/badge.svg)](https://github.com/joomcode/paranoid/actions/workflows/build.yml) 2 | 3 | Paranoid 4 | ======== 5 | 6 | String obfuscator for Android applications. 7 | 8 | Usage 9 | ----- 10 | In order to make Paranoid work with your project you have to apply the Paranoid Gradle plugin 11 | to the project. Please notice that the Paranoid plugin must be applied **after** the Android 12 | plugin. 13 | 14 | ```groovy 15 | buildscript { 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | dependencies { 21 | classpath 'com.joom.paranoid:paranoid-gradle-plugin:0.3.14' 22 | } 23 | } 24 | 25 | apply plugin: 'com.android.application' 26 | apply plugin: 'com.joom.paranoid' 27 | ``` 28 | 29 | Now you can just annotate classes with strings that need to be obfuscated with `@Obfuscate`. 30 | After you project compiles every string in annotated classes will be obfuscated. 31 | 32 | The plugin must be applied to every Gradle module that has classes with `@Obfuscate` annotation. 33 | 34 | Configuration 35 | ------------- 36 | Paranoid plugin can be configured using `paranoid` extension: 37 | ```groovy 38 | paranoid { 39 | // ... 40 | } 41 | 42 | ``` 43 | 44 | The extension contains the following properties: 45 | - `obfuscationSeed` - `Integer`. A seed that can be used to make obfuscation stable across builds. Default value is `null`, which means that the seed 46 | is computed from input files on each build. 47 | - `applyToBuildTypes` - Allows to apply paranoid transform for specific build types. Possible values are `'ALL'`, `'NONE'`, `'NOT_DEBUGGABLE'`. Default value is `'ALL'` 48 | - `enabled` — `boolean`. Allows to disable obfuscation for the project. Default value is `true`. *Deprecated*. Use `applyToBuildTypes = 'NONE'` 49 | 50 | How it works 51 | ------------ 52 | Let's say you have an `Activity` that contains some string you want to be obfuscated. 53 | 54 | ```java 55 | @Obfuscate 56 | public class MainActivity extends AppCompatActivity { 57 | private static final String QUESTION = "Q: %s"; 58 | private static final String ANSWER = "A: %s"; 59 | 60 | @Override 61 | protected void onCreate(final Bundle savedInstanceState) { 62 | super.onCreate(savedInstanceState); 63 | setContentView(R.layout.main_activity); 64 | 65 | final TextView questionTextView = (TextView) findViewById(R.id.questionTextView); 66 | questionTextView.setText(String.format(QUESTION, "Does it work?")); 67 | 68 | final TextView answerTextView = (TextView) findViewById(R.id.answerTextView); 69 | answerTextView.setText(String.format(ANSWER, "Sure it does!")); 70 | } 71 | } 72 | ``` 73 | 74 | The class contains both string constants (`QUESTION` and `ANSWER`) and string literals. 75 | After compilation this class will be transformed to something like this. 76 | 77 | ```java 78 | 79 | @Obfuscate 80 | public class MainActivity extends AppCompatActivity { 81 | private static final String QUESTION = Deobfuscator.getString(4); 82 | private static final String ANSWER = Deobfuscator.getString(5); 83 | 84 | protected void onCreate(final Bundle savedInstanceState) { 85 | super.onCreate(savedInstanceState); 86 | setContentView(R.layout.main_activity); 87 | 88 | final TextView questionTextView = (TextView) findViewById(R.id.questionTextView); 89 | questionTextView.setText(String.format(Deobfuscator.getString(0), Deobfuscator.getString(1))); 90 | 91 | final TextView answerTextView = (TextView) findViewById(R.id.answerTextView); 92 | answerTextView.setText(String.format(Deobfuscator.getString(2), Deobfuscator.getString(3))); 93 | } 94 | } 95 | 96 | ``` 97 | 98 | License 99 | ======= 100 | Copyright 2023 SIA Joom 101 | 102 | Licensed under the Apache License, Version 2.0 (the "License"); 103 | you may not use this file except in compliance with the License. 104 | You may obtain a copy of the License at 105 | 106 | http://www.apache.org/licenses/LICENSE-2.0 107 | 108 | Unless required by applicable law or agreed to in writing, software 109 | distributed under the License is distributed on an "AS IS" BASIS, 110 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 111 | See the License for the specific language governing permissions and 112 | limitations under the License. 113 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | apply from: 'versions.gradle' 3 | } 4 | 5 | allprojects { 6 | group = PARANOID_GROUP 7 | version = PARANOID_VERSION 8 | 9 | buildscript { 10 | repositories { 11 | google() 12 | mavenLocal() 13 | mavenCentral() 14 | } 15 | 16 | dependencies { 17 | classpath "com.android.tools.build:gradle:$androidToolsVersion" 18 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" 19 | classpath "com.joom.paranoid:paranoid-gradle-plugin:$version" 20 | } 21 | } 22 | 23 | repositories { 24 | google() 25 | mavenLocal() 26 | mavenCentral() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | android.useAndroidX=true 2 | development=true 3 | pablo.shadow.enabled=false 4 | org.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC -Dfile.encoding=UTF-8 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joomcode/paranoid/d6b85a6efff7b357dc65181dbdae86d517a81942/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /paranoid/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | apply from: '../versions.gradle' 3 | 4 | allprojects { 5 | group = PARANOID_GROUP 6 | version = PARANOID_VERSION 7 | 8 | buildscript { 9 | repositories { 10 | google() 11 | mavenCentral() 12 | } 13 | 14 | dependencies { 15 | classpath "com.android.tools.build:gradle:$androidToolsVersion" 16 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" 17 | classpath "io.michaelrocks.pablo:pablo:$pabloVersion" 18 | } 19 | } 20 | 21 | repositories { 22 | google() 23 | mavenCentral() 24 | } 25 | } 26 | } 27 | 28 | apply plugin: 'kotlin' 29 | 30 | dependencies { 31 | testImplementation gradleTestKit() 32 | testImplementation "junit:junit:$junitVersion" 33 | } 34 | 35 | tasks.register("functionalTest", Test.class) { 36 | description = "Runs the functional tests." 37 | group = "verification" 38 | 39 | testClassesDirs = sourceSets.test.output.classesDirs 40 | classpath = sourceSets.test.runtimeClasspath 41 | } 42 | 43 | afterEvaluate { 44 | tasks.named("functionalTest") { 45 | subprojects.each { 46 | dependsOn(it.tasks.named("publishToMavenLocal")) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /paranoid/core/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply from: "$rootDir/pablo.gradle" 3 | 4 | sourceCompatibility = javaVersion 5 | targetCompatibility = javaVersion 6 | 7 | jar { 8 | destinationDirectory.set(file('build/jar')) 9 | } 10 | -------------------------------------------------------------------------------- /paranoid/core/src/main/java/com/joom/paranoid/DeobfuscatorHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid; 18 | 19 | public class DeobfuscatorHelper { 20 | public static final int MAX_CHUNK_LENGTH = 0x1fff; 21 | 22 | private DeobfuscatorHelper() { 23 | // Cannot be instantiated. 24 | } 25 | 26 | public static String getString(final long id, final String[] chunks) { 27 | long state = RandomHelper.seed(id & 0xffffffffL); 28 | state = RandomHelper.next(state); 29 | final long low = (state >>> 32) & 0xffff; 30 | state = RandomHelper.next(state); 31 | final long high = (state >>> 16) & 0xffff0000; 32 | final int index = (int) ((id >>> 32) ^ low ^ high); 33 | state = getCharAt(index, chunks, state); 34 | final int length = (int) ((state >>> 32) & 0xffffL); 35 | final char[] chars = new char[length]; 36 | 37 | for (int i = 0; i < length; ++i) { 38 | state = getCharAt(index + i + 1, chunks, state); 39 | chars[i] = (char) ((state >>> 32) & 0xffffL); 40 | } 41 | 42 | return new String(chars); 43 | } 44 | 45 | private static long getCharAt(final int charIndex, final String[] chunks, final long state) { 46 | final long nextState = RandomHelper.next(state); 47 | final String chunk = chunks[charIndex / MAX_CHUNK_LENGTH]; 48 | return nextState ^ ((long) chunk.charAt(charIndex % MAX_CHUNK_LENGTH) << 32); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /paranoid/core/src/main/java/com/joom/paranoid/Obfuscate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid; 18 | 19 | import java.lang.annotation.Documented; 20 | import java.lang.annotation.ElementType; 21 | import java.lang.annotation.Retention; 22 | import java.lang.annotation.RetentionPolicy; 23 | import java.lang.annotation.Target; 24 | 25 | @Target(ElementType.TYPE) 26 | @Retention(RetentionPolicy.CLASS) 27 | @Documented 28 | public @interface Obfuscate { 29 | } 30 | -------------------------------------------------------------------------------- /paranoid/core/src/main/java/com/joom/paranoid/RandomHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid; 18 | 19 | public class RandomHelper { 20 | private RandomHelper() { 21 | // Cannot be instantiated. 22 | } 23 | 24 | public static long seed(final long x) { 25 | final long z = (x ^ (x >>> 33)) * 0x62a9d9ed799705f5L; 26 | return ((z ^ (z >>> 28)) * 0xcb24d0a5c88c35b3L) >>> 32; 27 | } 28 | 29 | public static long next(final long state) { 30 | short s0 = (short) (state & 0xffff); 31 | short s1 = (short) ((state >>> 16) & 0xffff); 32 | short next = s0; 33 | next += s1; 34 | next = rotl(next, 9); 35 | next += s0; 36 | 37 | s1 ^= s0; 38 | s0 = rotl(s0, 13); 39 | s0 ^= s1; 40 | s0 ^= (s1 << 5); 41 | s1 = rotl(s1, 10); 42 | 43 | long result = next; 44 | result <<= 16; 45 | result |= s1; 46 | result <<= 16; 47 | result |= s0; 48 | return result; 49 | } 50 | 51 | private static short rotl(final short x, final int k) { 52 | return (short) ((x << k) | (x >>> (32 - k))); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /paranoid/gradle-plugin/.gitignore: -------------------------------------------------------------------------------- 1 | generated/ -------------------------------------------------------------------------------- /paranoid/gradle-plugin/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'kotlin' 2 | apply plugin: 'idea' 3 | apply from: "$rootDir/pablo.gradle" 4 | 5 | targetCompatibility = javaVersion 6 | sourceCompatibility = javaVersion 7 | 8 | compileKotlin { 9 | kotlinOptions { 10 | jvmTarget = javaVersion 11 | } 12 | } 13 | 14 | dependencies { 15 | compileOnly gradleApi() 16 | compileOnly "com.android.tools.build:gradle:$androidToolsVersion" 17 | compileOnly "com.android.tools.build:gradle-api:$androidToolsVersion" 18 | 19 | relocate project(':processor') 20 | 21 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" 22 | } 23 | 24 | final File generatedDir = new File(projectDir, "generated") 25 | final File generatedJavaSourcesDir = new File(generatedDir, "main/java") 26 | 27 | task generateBuildClass { 28 | inputs.property('version', version) 29 | outputs.dir generatedDir 30 | 31 | doLast { 32 | final File buildClassFile = new File(generatedJavaSourcesDir, "com/joom/paranoid/plugin/Build.java") 33 | buildClassFile.parentFile.mkdirs() 34 | buildClassFile.text = "" + 35 | "package com.joom.paranoid.plugin;\n" + 36 | "\n" + 37 | "public class Build {\n" + 38 | " public static final String VERSION = \"$version\";\n" + 39 | "}\n" 40 | } 41 | } 42 | 43 | sourceSets { 44 | main { 45 | java.srcDirs += generatedJavaSourcesDir 46 | } 47 | } 48 | 49 | tasks.getByPath(JavaPlugin.COMPILE_JAVA_TASK_NAME).dependsOn(tasks.generateBuildClass) 50 | tasks.named('compileKotlin', org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class) { 51 | it.dependsOn(tasks.generateBuildClass) 52 | } 53 | 54 | clean.doFirst { 55 | delete generatedDir 56 | } 57 | 58 | idea { 59 | module { 60 | generatedSourceDirs += generatedJavaSourcesDir 61 | } 62 | } 63 | 64 | pablo { 65 | shadow { 66 | relocate 'com.joom.paranoid' 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /paranoid/gradle-plugin/src/main/java/com/joom/paranoid/plugin/AllClassesTransformRegisterAction.kt: -------------------------------------------------------------------------------- 1 | package com.joom.paranoid.plugin 2 | 3 | import com.android.build.api.artifact.MultipleArtifact 4 | import com.android.build.api.variant.Variant 5 | import org.gradle.api.tasks.TaskProvider 6 | 7 | internal object AllClassesTransformRegisterAction { 8 | fun register(variant: Variant, provider: TaskProvider) { 9 | @Suppress("DEPRECATION") 10 | variant.artifacts.use(provider) 11 | .wiredWith(ParanoidTransformTask::inputDirectories, ParanoidTransformTask::outputDirectory) 12 | .toTransform(MultipleArtifact.ALL_CLASSES_DIRS) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /paranoid/gradle-plugin/src/main/java/com/joom/paranoid/plugin/BackupClassesTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.plugin 18 | 19 | import org.gradle.api.DefaultTask 20 | import org.gradle.api.tasks.InputFiles 21 | import org.gradle.api.tasks.OutputDirectories 22 | import org.gradle.api.tasks.TaskAction 23 | import java.io.File 24 | 25 | abstract class BackupClassesTask : DefaultTask() { 26 | 27 | @InputFiles 28 | var classesDirs: List = emptyList() 29 | 30 | @OutputDirectories 31 | var backupDirs: List = emptyList() 32 | 33 | @TaskAction 34 | fun process() { 35 | validate() 36 | 37 | classesDirs.zip(backupDirs) { classesDir, backupDir -> 38 | if (!classesDir.exists()) { 39 | backupDir.deleteRecursively() 40 | } else { 41 | copyFiles(classesDir, backupDir) 42 | } 43 | } 44 | } 45 | 46 | private fun copyFiles(classesDir: File, backupDir: File) { 47 | backupDir.deleteRecursively() 48 | classesDir.copyRecursively(backupDir, overwrite = true) 49 | } 50 | 51 | private fun validate() { 52 | require(classesDirs.isNotEmpty()) { "classesDirs is not set" } 53 | require(backupDirs.isNotEmpty()) { "backupDirs is not set" } 54 | require(classesDirs.size == backupDirs.size) { "classesDirs and backupDirs must have equal size" } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /paranoid/gradle-plugin/src/main/java/com/joom/paranoid/plugin/GradleExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.joom.paranoid.plugin 2 | 3 | import com.android.build.api.variant.AndroidComponentsExtension 4 | import com.android.build.api.variant.ApplicationAndroidComponentsExtension 5 | import com.android.build.api.variant.LibraryAndroidComponentsExtension 6 | import com.android.build.gradle.BaseExtension 7 | import com.android.build.gradle.internal.publishing.AndroidArtifacts 8 | import org.gradle.api.Project 9 | import org.gradle.api.Task 10 | import org.gradle.api.artifacts.ArtifactCollection 11 | import org.gradle.api.artifacts.Configuration 12 | import org.gradle.api.artifacts.component.ComponentIdentifier 13 | import org.gradle.api.artifacts.type.ArtifactTypeDefinition 14 | import org.gradle.api.plugins.JavaPluginExtension 15 | import org.gradle.api.tasks.SourceSet 16 | import org.gradle.api.tasks.SourceSetContainer 17 | import org.gradle.api.tasks.TaskContainer 18 | import org.gradle.api.tasks.TaskProvider 19 | import org.gradle.api.tasks.compile.JavaCompile 20 | 21 | val Project.sourceSets: SourceSetContainer 22 | get() { 23 | val extension = extensions.getByType(JavaPluginExtension::class.java) 24 | return extension.sourceSets 25 | } 26 | 27 | val Project.hasAndroid: Boolean 28 | get() = extensions.findByName("android") is BaseExtension 29 | val Project.hasJava: Boolean 30 | get() = extensions.findByType(JavaPluginExtension::class.java) != null 31 | val Project.android: BaseExtension 32 | get() = extensions.getByName("android") as BaseExtension 33 | val Project.java: JavaPluginExtension 34 | get() = extensions.getByType(JavaPluginExtension::class.java) 35 | 36 | val Project.androidComponents: AndroidComponentsExtension<*, *, *>? 37 | get() = applicationAndroidComponents ?: libraryAndroidComponents 38 | val Project.applicationAndroidComponents: ApplicationAndroidComponentsExtension? 39 | get() = extensions.findByName("androidComponents") as? ApplicationAndroidComponentsExtension 40 | val Project.libraryAndroidComponents: LibraryAndroidComponentsExtension? 41 | get() = extensions.findByName("androidComponents") as? LibraryAndroidComponentsExtension 42 | 43 | inline fun Project.registerTask(name: String): TaskProvider { 44 | return tasks.register(name, T::class.java) 45 | } 46 | 47 | inline fun Project.getTaskByName(name: String): TaskProvider { 48 | return tasks.named(name, T::class.java) 49 | } 50 | 51 | val SourceSetContainer.main: SourceSet 52 | get() = getByName("main") 53 | val SourceSetContainer.test: SourceSet 54 | get() = getByName("test") 55 | 56 | val TaskContainer.compileJava: JavaCompile 57 | get() = getByName("compileJava") as JavaCompile 58 | val TaskContainer.compileTestJava: JavaCompile 59 | get() = getByName("compileTestJava") as JavaCompile 60 | 61 | operator fun TaskContainer.get(name: String): Task? { 62 | return findByName(name) 63 | } 64 | 65 | fun Configuration.incomingJarArtifacts(componentFilter: ((ComponentIdentifier) -> Boolean)? = null): ArtifactCollection { 66 | return incoming 67 | .artifactView { configuration -> 68 | configuration.attributes { attributes -> 69 | @Suppress("UnstableApiUsage") 70 | attributes.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, AndroidArtifacts.ArtifactType.CLASSES_JAR.type) 71 | } 72 | 73 | componentFilter?.let { 74 | configuration.componentFilter(it) 75 | } 76 | } 77 | .artifacts 78 | } 79 | -------------------------------------------------------------------------------- /paranoid/gradle-plugin/src/main/java/com/joom/paranoid/plugin/ObfuscationSeedCalculator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.plugin 18 | 19 | import java.io.File 20 | 21 | object ObfuscationSeedCalculator { 22 | 23 | fun calculate(inputs: List, fileSelector: (T) -> File): Int { 24 | return inputs.maxLastModified { getLastModified(fileSelector(it)) }.hashCode() 25 | } 26 | 27 | private fun getLastModified(file: File): Long { 28 | return file.walk().asIterable().maxLastModified { it.lastModified() } 29 | } 30 | 31 | private fun Iterable.maxLastModified(lastModified: (T) -> Long): Long { 32 | var result = 0L 33 | forEach { 34 | result = maxOf(result, lastModified(it)) 35 | } 36 | return result 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /paranoid/gradle-plugin/src/main/java/com/joom/paranoid/plugin/ParanoidExtension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.plugin 18 | 19 | import com.joom.paranoid.processor.logging.getLogger 20 | import java.io.File 21 | import kotlin.properties.Delegates 22 | import kotlin.properties.ReadWriteProperty 23 | 24 | open class ParanoidExtension { 25 | 26 | @Deprecated(IS_ENABLED_DEPRECATION_WARNING, replaceWith = ReplaceWith("applyToBuildTypes")) 27 | var isEnabled: Boolean by deprecatedProperty(true, IS_ENABLED_DEPRECATION_WARNING) { enabled -> 28 | if (enabled) applyToBuildTypes = BuildType.ALL else BuildType.NONE 29 | } 30 | 31 | var obfuscationSeed: Int? = null 32 | var applyToBuildTypes: BuildType = BuildType.ALL 33 | 34 | private inline fun deprecatedProperty(initial: T, message: String, crossinline onChange: (T) -> Unit = {}): ReadWriteProperty { 35 | return Delegates.observable(initial) { _, _, new -> 36 | getLogger().warn("WARNING: $message") 37 | onChange(new) 38 | } 39 | } 40 | } 41 | 42 | enum class BuildType { 43 | NONE, 44 | ALL, 45 | NOT_DEBUGGABLE 46 | } 47 | 48 | private const val IS_ENABLED_DEPRECATION_WARNING = "paranoid.enabled is deprecated. Use paranoid.applyToBuildTypes" 49 | -------------------------------------------------------------------------------- /paranoid/gradle-plugin/src/main/java/com/joom/paranoid/plugin/ParanoidPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.plugin 18 | 19 | import com.android.build.api.AndroidPluginVersion 20 | import com.android.build.api.variant.Variant 21 | import org.gradle.api.GradleException 22 | import org.gradle.api.Plugin 23 | import org.gradle.api.Project 24 | import org.gradle.api.artifacts.component.ProjectComponentIdentifier 25 | import org.gradle.api.plugins.JavaPlugin 26 | import org.gradle.api.tasks.compile.JavaCompile 27 | import java.io.File 28 | 29 | class ParanoidPlugin : Plugin { 30 | private lateinit var project: Project 31 | override fun apply(project: Project) { 32 | this.project = project 33 | 34 | val extension = project.extensions.create("paranoid", ParanoidExtension::class.java) 35 | project.addDependencies(getDefaultConfiguration()) 36 | 37 | if (!project.hasAndroid && project.hasJava) { 38 | registerParanoidForJava(extension) 39 | return 40 | } 41 | 42 | if (!project.hasAndroid) { 43 | throw GradleException("Paranoid plugin must be applied *AFTER* Android plugin") 44 | } 45 | 46 | val androidComponentsExtension = project.androidComponents ?: throw GradleException("Failed to get androidComponents extension") 47 | if (androidComponentsExtension.pluginVersion < MINIMUM_VERSION) { 48 | throw GradleException("Paranoid requires Android Gradle Plugin version ${MINIMUM_VERSION.major}.${MINIMUM_VERSION.minor}.${MINIMUM_VERSION.micro}") 49 | } 50 | 51 | registerParanoid(extension) 52 | } 53 | 54 | private fun registerParanoidForJava(extension: ParanoidExtension) { 55 | val mainSourceSet = project.sourceSets.main 56 | val classesTask = project.tasks.named(mainSourceSet.classesTaskName) 57 | val compileTask = project.getTaskByName(mainSourceSet.compileJavaTaskName) 58 | val paranoidTask = project.registerTask(formatParanoidTaskName(project.name)) 59 | val backupClassesTask = project.registerTask(formatBackupClassesTaskName(project.name)) 60 | val input = mainSourceSet.output.classesDirs.files 61 | val output = project.layout.buildDirectory.dir("intermediates/paranoid/classes") 62 | val backupDirs = computeBackupDirs(project.buildDir, output.get().asFile, input) 63 | val runtimeClasspath = project.configurations.named(mainSourceSet.runtimeClasspathConfigurationName) 64 | 65 | backupClassesTask.configure { task -> 66 | task.classesDirs = input.toList() 67 | task.backupDirs = backupDirs.toList() 68 | } 69 | 70 | paranoidTask.configure { task -> 71 | val javaCompileTask = compileTask.get() 72 | task.obfuscationSeed = extension.obfuscationSeed 73 | task.bootClasspath.setFrom(javaCompileTask.options.bootstrapClasspath?.files.orEmpty()) 74 | task.classpath.setFrom(javaCompileTask.classpath) 75 | task.validationClasspath.setFrom(runtimeClasspath.map { it.incomingJarArtifacts { it is ProjectComponentIdentifier }.artifactFiles }) 76 | task.inputDirectories.set(backupDirs.map { file -> project.layout.dir(project.provider { file }).get() }) 77 | task.outputDirectory.set(input.single()) 78 | task.onlyIf { extension.applyToBuildTypes != BuildType.NONE } 79 | 80 | task.mustRunAfter(compileTask) 81 | task.dependsOn(compileTask) 82 | } 83 | 84 | backupClassesTask.configure { task -> 85 | task.onlyIf { extension.applyToBuildTypes != BuildType.NONE } 86 | } 87 | 88 | backupClassesTask.configure { it.dependsOn(compileTask) } 89 | paranoidTask.configure { it.dependsOn(backupClassesTask) } 90 | classesTask.configure { it.dependsOn(paranoidTask) } 91 | } 92 | 93 | private fun registerParanoid(extension: ParanoidExtension) { 94 | project.applicationAndroidComponents?.apply { 95 | onVariants(selector().all()) { variant -> 96 | if (extension.applyToBuildTypes.isVariantFit(variant)) { 97 | variant.createParanoidTransformTask(extension, validateClasspath = true) 98 | } 99 | } 100 | } 101 | 102 | project.libraryAndroidComponents?.apply { 103 | onVariants(selector().all()) { variant -> 104 | if (extension.applyToBuildTypes.isVariantFit(variant)) { 105 | variant.createParanoidTransformTask(extension, validateClasspath = false) 106 | } 107 | } 108 | } 109 | } 110 | 111 | private fun BuildType.isVariantFit(variant: Variant): Boolean { 112 | return when (this) { 113 | BuildType.NONE -> false 114 | BuildType.ALL -> true 115 | BuildType.NOT_DEBUGGABLE -> !variant.isDebuggable() 116 | } 117 | } 118 | 119 | private fun Variant.isDebuggable(): Boolean { 120 | return buildType?.let { project.android.buildTypes.getByName(it) }?.isDebuggable ?: false 121 | } 122 | 123 | private fun Variant.createParanoidTransformTask( 124 | extension: ParanoidExtension, 125 | validateClasspath: Boolean, 126 | ) { 127 | val taskProvider = project.registerTask(formatParanoidTaskName(name)) 128 | val androidComponentsExtension = project.androidComponents ?: error("Failed to get androidComponents extension") 129 | 130 | if (androidComponentsExtension.pluginVersion >= SCOPED_ARTIFACTS_VERSION) { 131 | ScopedArtifactsRegisterAction.register(this, taskProvider) 132 | } else { 133 | AllClassesTransformRegisterAction.register(this, taskProvider) 134 | } 135 | 136 | val runtimeClasspath = project.configurations.getByName("${name}RuntimeClasspath") 137 | taskProvider.configure { task -> 138 | task.obfuscationSeed = extension.obfuscationSeed 139 | task.validateClasspath = validateClasspath 140 | task.validationClasspath.setFrom( 141 | runtimeClasspath.incomingJarArtifacts { it is ProjectComponentIdentifier }.artifactFiles 142 | ) 143 | task.classpath.setFrom( 144 | runtimeClasspath.incomingJarArtifacts().artifactFiles 145 | ) 146 | 147 | task.bootClasspath.setFrom(project.android.bootClasspath) 148 | } 149 | } 150 | 151 | private fun formatParanoidTaskName(variantName: String): String { 152 | return "${ParanoidTransformTask.TASK_PREFIX}${variantName.replaceFirstChar { it.uppercase() }}" 153 | } 154 | 155 | private fun formatBackupClassesTaskName(variantName: String): String { 156 | return "paranoidBackupClassesTask${variantName.replaceFirstChar { it.uppercase() }}" 157 | } 158 | 159 | private fun computeBackupDirs(buildDir: File, paranoidDir: File, classesDirs: Collection): Collection { 160 | return classesDirs.map { classesDir -> 161 | val relativeFile = classesDir.relativeToOrSelf(buildDir) 162 | 163 | File(paranoidDir, relativeFile.path) 164 | } 165 | } 166 | 167 | private fun getDefaultConfiguration(): String { 168 | return JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME 169 | } 170 | 171 | private fun Project.addDependencies(configurationName: String) { 172 | dependencies.add(configurationName, "com.joom.paranoid:paranoid-core:${Build.VERSION}") 173 | } 174 | 175 | private companion object { 176 | private val SCOPED_ARTIFACTS_VERSION = AndroidPluginVersion(major = 7, minor = 4, micro = 0) 177 | private val MINIMUM_VERSION = AndroidPluginVersion(major = 7, minor = 2, micro = 0) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /paranoid/gradle-plugin/src/main/java/com/joom/paranoid/plugin/ParanoidTransformTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.plugin 18 | 19 | import com.joom.paranoid.processor.ParanoidProcessor 20 | import org.gradle.api.DefaultTask 21 | import org.gradle.api.GradleScriptException 22 | import org.gradle.api.file.ConfigurableFileCollection 23 | import org.gradle.api.file.Directory 24 | import org.gradle.api.file.DirectoryProperty 25 | import org.gradle.api.file.RegularFile 26 | import org.gradle.api.file.RegularFileProperty 27 | import org.gradle.api.logging.LogLevel 28 | import org.gradle.api.provider.ListProperty 29 | import org.gradle.api.tasks.CacheableTask 30 | import org.gradle.api.tasks.Classpath 31 | import org.gradle.api.tasks.Input 32 | import org.gradle.api.tasks.InputFiles 33 | import org.gradle.api.tasks.Optional 34 | import org.gradle.api.tasks.OutputDirectory 35 | import org.gradle.api.tasks.OutputFile 36 | import org.gradle.api.tasks.TaskAction 37 | 38 | @CacheableTask 39 | abstract class ParanoidTransformTask : DefaultTask() { 40 | @get:InputFiles 41 | @get:Classpath 42 | abstract val inputClasses: ListProperty 43 | 44 | @get:InputFiles 45 | @get:Classpath 46 | abstract val inputDirectories: ListProperty 47 | 48 | @get:InputFiles 49 | @get:Classpath 50 | abstract val classpath: ConfigurableFileCollection 51 | 52 | @get:InputFiles 53 | @get:Classpath 54 | abstract val validationClasspath: ConfigurableFileCollection 55 | 56 | @get:InputFiles 57 | @get:Classpath 58 | abstract val bootClasspath: ConfigurableFileCollection 59 | 60 | @get:OutputFile 61 | @get:Optional 62 | abstract val output: RegularFileProperty 63 | 64 | @get:OutputDirectory 65 | @get:Optional 66 | abstract val outputDirectory: DirectoryProperty 67 | 68 | @Input 69 | @Optional 70 | var obfuscationSeed: Int? = null 71 | 72 | @Input 73 | var validateClasspath: Boolean = false 74 | 75 | private val projectName = computeProjectName() 76 | 77 | init { 78 | logging.captureStandardOutput(LogLevel.INFO) 79 | } 80 | 81 | @TaskAction 82 | fun process() { 83 | try { 84 | validate() 85 | 86 | cleanOutput() 87 | 88 | val outputFile = when { 89 | output.isPresent -> output.asFile 90 | outputDirectory.isPresent -> outputDirectory.asFile 91 | else -> error("output or outputDirectory is not set") 92 | } 93 | 94 | val processor = ParanoidProcessor( 95 | obfuscationSeed = calculateObfuscationSeed(), 96 | inputs = inputClasses.get().map { it.asFile } + inputDirectories.get().map { it.asFile }, 97 | output = outputFile.get(), 98 | classpath = classpath.files, 99 | bootClasspath = bootClasspath.files, 100 | validationClasspath = validationClasspath.files, 101 | validateClasspath = validateClasspath, 102 | projectName = projectName, 103 | ) 104 | 105 | processor.process() 106 | } catch (exception: Exception) { 107 | throw GradleScriptException("Failed to transform with paranoid", exception) 108 | } 109 | } 110 | 111 | private fun cleanOutput() { 112 | if (output.isPresent) { 113 | output.get().asFile.delete() 114 | } 115 | 116 | if (outputDirectory.isPresent) { 117 | outputDirectory.get().asFile.deleteRecursively() 118 | } 119 | } 120 | 121 | private fun calculateObfuscationSeed(): Int { 122 | obfuscationSeed?.let { 123 | return it 124 | } 125 | 126 | return ObfuscationSeedCalculator.calculate(inputClasses.get()) { it.asFile } 127 | } 128 | 129 | private fun validate() { 130 | require(inputClasses.get().isNotEmpty() || inputDirectories.get().isNotEmpty()) { "inputClasses or inputDirectories is not set" } 131 | require(output.isPresent || outputDirectory.isPresent) { "output or outputDirectory is not set" } 132 | } 133 | 134 | private fun computeProjectName(): String { 135 | return (project.path + name.replace(TASK_PREFIX, ":")).replace(':', '$') 136 | } 137 | 138 | companion object { 139 | const val TASK_PREFIX = "paranoidTransformClasses" 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /paranoid/gradle-plugin/src/main/java/com/joom/paranoid/plugin/ScopedArtifactsRegisterAction.kt: -------------------------------------------------------------------------------- 1 | package com.joom.paranoid.plugin 2 | 3 | import com.android.build.api.artifact.ScopedArtifact 4 | import com.android.build.api.variant.ScopedArtifacts 5 | import com.android.build.api.variant.Variant 6 | import org.gradle.api.tasks.TaskProvider 7 | 8 | internal object ScopedArtifactsRegisterAction { 9 | fun register(variant: Variant, provider: TaskProvider) { 10 | variant.artifacts.forScope(ScopedArtifacts.Scope.PROJECT) 11 | .use(provider) 12 | .toTransform(ScopedArtifact.CLASSES, ParanoidTransformTask::inputClasses, ParanoidTransformTask::inputDirectories, ParanoidTransformTask::output) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /paranoid/gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.joom.paranoid.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.joom.paranoid.plugin.ParanoidPlugin -------------------------------------------------------------------------------- /paranoid/gradle.properties: -------------------------------------------------------------------------------- 1 | ../gradle.properties -------------------------------------------------------------------------------- /paranoid/pablo.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'io.michaelrocks.pablo' 2 | 3 | def shadowEnabled = project.providers.gradleProperty("pablo.shadow.enabled").map { it.toBoolean() } 4 | configurations.getByName("relocate") { relocateConfiguration -> 5 | if (shadowEnabled.get()) { 6 | return 7 | } 8 | 9 | configurations.getByName(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME).extendsFrom(relocateConfiguration) 10 | configurations.getByName(JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME).extendsFrom(relocateConfiguration) 11 | } 12 | 13 | pablo { 14 | propertiesFile = rootProject.file('pablo.properties') 15 | 16 | pom { 17 | name = 'Paranoid' 18 | description = 'A string obfuscator for Android applications.' 19 | inceptionYear = '2016' 20 | url = 'https://github.com/joomcode/paranoid' 21 | 22 | licenses { 23 | license { 24 | name = 'The Apache License, Version 2.0' 25 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 26 | distribution = 'repo' 27 | } 28 | } 29 | developers { 30 | developer { 31 | id = 'MichaelRocks' 32 | name = 'Michael Rozumyanskiy' 33 | email = 'michael.rozumyanskiy@gmail.com' 34 | } 35 | } 36 | scm { 37 | connection = 'scm:git:git://github.com/joomcode/paranoid.git' 38 | developerConnection = 'scm:git:ssh://git@github.com/joomcode/paranoid.git' 39 | url = 'https://github.com/joomcode/paranoid' 40 | } 41 | } 42 | 43 | signing { 44 | enabled.set(providers.gradleProperty("development").map { !it.toBoolean() }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /paranoid/processor/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'kotlin' 2 | apply from: "$rootDir/pablo.gradle" 3 | 4 | sourceCompatibility = javaVersion 5 | targetCompatibility = javaVersion 6 | 7 | compileKotlin { 8 | kotlinOptions { 9 | jvmTarget = javaVersion 10 | } 11 | } 12 | 13 | compileTestKotlin { 14 | kotlinOptions { 15 | jvmTarget = javaVersion 16 | } 17 | } 18 | 19 | test { 20 | testLogging.showStandardStreams = true 21 | } 22 | 23 | dependencies { 24 | implementation project(':core') 25 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" 26 | implementation "ch.qos.logback:logback-classic:$logbackVersion" 27 | 28 | relocate "org.ow2.asm:asm:$asmVersion" 29 | relocate "org.ow2.asm:asm-commons:$asmVersion" 30 | relocate "com.joom.grip:grip:$gripVersion" 31 | 32 | testImplementation "junit:junit:$junitVersion" 33 | } 34 | 35 | jar { 36 | destinationDirectory.set(file('build/jar')) 37 | } 38 | 39 | pablo { 40 | shadow { 41 | relocate 'com.joom.paranoid' 42 | relocate 'com.joom.grip', 'com.joom.paranoid.grip' 43 | relocate 'org.objectweb.asm', 'com.joom.paranoid.asm' 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/Analyzer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor 18 | 19 | import com.joom.grip.Grip 20 | import com.joom.grip.and 21 | import com.joom.grip.classes 22 | import com.joom.grip.fields 23 | import com.joom.grip.from 24 | import com.joom.grip.isFinal 25 | import com.joom.grip.isStatic 26 | import com.joom.grip.mirrors.FieldMirror 27 | import com.joom.grip.mirrors.Type 28 | import com.joom.grip.withFieldInitializer 29 | import java.io.File 30 | 31 | class Analyzer(private val grip: Grip) { 32 | fun analyze(inputs: List): AnalysisResult { 33 | val typesToObfuscate = findTypesToObfuscate(inputs) 34 | val obfuscationConfigurationsByType = typesToObfuscate.associateBy( 35 | { it }, 36 | { createObfuscationConfiguration(it) } 37 | ) 38 | return AnalysisResult(obfuscationConfigurationsByType) 39 | } 40 | 41 | private fun findTypesToObfuscate(inputs: List): Set { 42 | val registry = newObfuscatedTypeRegistry(grip.classRegistry).withCache() 43 | val query = grip select classes from inputs where registry.shouldObfuscate() 44 | return query.execute().types.toHashSet() 45 | } 46 | 47 | private fun createObfuscationConfiguration(type: Type.Object): ClassConfiguration { 48 | val fields = findConstantStringFields(type) 49 | val stringConstantsByName = fields.associateBy( 50 | { it.name }, 51 | { it.value as String } 52 | ) 53 | return ClassConfiguration(type, stringConstantsByName) 54 | } 55 | 56 | private fun findConstantStringFields(type: Type.Object): Collection { 57 | val mirror = grip.classRegistry.getClassMirror(type) 58 | val query = grip select fields from mirror where (isStatic() and isFinal() and withFieldInitializer()) 59 | return query.execute()[type].orEmpty() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/CachedObfuscatedTypeRegistry.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor 18 | 19 | import com.joom.grip.mirrors.Type 20 | 21 | class CachedObfuscatedTypeRegistry( 22 | private val registry: ObfuscatedTypeRegistry 23 | ) : ObfuscatedTypeRegistry { 24 | 25 | private val cache = mutableMapOf() 26 | 27 | override fun shouldObfuscate(type: Type.Object): Boolean { 28 | return cache.getOrPut(type) { 29 | registry.shouldObfuscate(type) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/ClassVisitorExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor 18 | 19 | import org.objectweb.asm.ClassVisitor 20 | import org.objectweb.asm.commons.GeneratorAdapter 21 | import org.objectweb.asm.commons.Method 22 | 23 | inline fun ClassVisitor.newMethod(access: Int, method: Method, body: GeneratorAdapter.() -> Unit) { 24 | GeneratorAdapter(access, method, null, null, this).apply { 25 | visitCode() 26 | body() 27 | returnValue() 28 | endMethod() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/DeobfuscatorGenerator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor 18 | 19 | import com.joom.grip.ClassRegistry 20 | import com.joom.grip.mirrors.toAsmType 21 | import com.joom.paranoid.processor.model.Deobfuscator 22 | import org.objectweb.asm.ClassVisitor 23 | import org.objectweb.asm.ClassWriter 24 | import org.objectweb.asm.Opcodes 25 | import org.objectweb.asm.Opcodes.ACC_PUBLIC 26 | import org.objectweb.asm.Opcodes.ACC_SUPER 27 | import org.objectweb.asm.Type 28 | import org.objectweb.asm.commons.Method 29 | 30 | class DeobfuscatorGenerator( 31 | private val deobfuscator: Deobfuscator, 32 | private val stringRegistry: StringRegistry, 33 | private val classRegistry: ClassRegistry 34 | ) { 35 | 36 | fun generateDeobfuscator(): ByteArray { 37 | val writer = StandaloneClassWriter(ClassWriter.COMPUTE_MAXS or ClassWriter.COMPUTE_FRAMES, classRegistry) 38 | writer.visit( 39 | Opcodes.V1_6, 40 | ACC_PUBLIC or ACC_SUPER, 41 | deobfuscator.type.internalName, 42 | null, 43 | OBJECT_TYPE.internalName, 44 | null 45 | ) 46 | 47 | writer.generateFields() 48 | writer.generateStaticInitializer() 49 | writer.generateDefaultConstructor() 50 | writer.generateGetStringMethod() 51 | 52 | writer.visitEnd() 53 | return writer.toByteArray() 54 | } 55 | 56 | private fun ClassVisitor.generateFields() { 57 | visitField( 58 | Opcodes.ACC_PRIVATE or Opcodes.ACC_STATIC or Opcodes.ACC_FINAL, 59 | CHUNKS_FIELD_NAME, 60 | CHUNKS_FIELD_TYPE.descriptor, 61 | null, 62 | null 63 | ).apply { 64 | visitEnd() 65 | } 66 | } 67 | 68 | private fun ClassVisitor.generateStaticInitializer() { 69 | newMethod(Opcodes.ACC_STATIC, METHOD_STATIC_INITIALIZER) { 70 | val chunks = stringRegistry.getAllChunks() 71 | push(chunks.size) 72 | newArray(CHUNKS_ELEMENT_TYPE) 73 | putStatic(deobfuscator.type.toAsmType(), CHUNKS_FIELD_NAME, CHUNKS_FIELD_TYPE) 74 | 75 | getStatic(deobfuscator.type.toAsmType(), CHUNKS_FIELD_NAME, CHUNKS_FIELD_TYPE) 76 | chunks.forEachIndexed { index, chunk -> 77 | dup() 78 | push(index) 79 | push(chunk) 80 | arrayStore(CHUNKS_ELEMENT_TYPE) 81 | } 82 | pop() 83 | } 84 | } 85 | 86 | private fun ClassVisitor.generateDefaultConstructor() { 87 | newMethod(Opcodes.ACC_PUBLIC, METHOD_DEFAULT_CONSTRUCTOR) { 88 | loadThis() 89 | invokeConstructor(TYPE_OBJECT, METHOD_DEFAULT_CONSTRUCTOR) 90 | } 91 | } 92 | 93 | private fun ClassVisitor.generateGetStringMethod() { 94 | newMethod(Opcodes.ACC_PUBLIC or Opcodes.ACC_STATIC, deobfuscator.deobfuscationMethod) { 95 | loadArg(0) 96 | getStatic(deobfuscator.type.toAsmType(), CHUNKS_FIELD_NAME, CHUNKS_FIELD_TYPE) 97 | invokeStatic(DEOBFUSCATOR_HELPER_TYPE.toAsmType(), METHOD_GET_STRING) 98 | } 99 | } 100 | 101 | companion object { 102 | private val METHOD_STATIC_INITIALIZER = Method("", "()V") 103 | private val METHOD_DEFAULT_CONSTRUCTOR = Method("", "()V") 104 | 105 | private val METHOD_GET_STRING = Method("getString", "(J[Ljava/lang/String;)Ljava/lang/String;") 106 | 107 | private val TYPE_OBJECT = Type.getObjectType("java/lang/Object") 108 | 109 | private const val CHUNKS_FIELD_NAME = "chunks" 110 | private val CHUNKS_FIELD_TYPE = Type.getType("[Ljava/lang/String;") 111 | private val CHUNKS_ELEMENT_TYPE = CHUNKS_FIELD_TYPE.elementType 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/FileSinkFactory.kt: -------------------------------------------------------------------------------- 1 | package com.joom.paranoid.processor 2 | 3 | import com.joom.grip.io.DirectoryFileSink 4 | import com.joom.grip.io.EmptyFileSink 5 | import com.joom.grip.io.FileSink 6 | import com.joom.grip.io.JarFileSink 7 | import java.io.File 8 | 9 | internal fun createFileSink(outputFile: File): FileSink { 10 | return when (outputFile.fileType) { 11 | FileType.EMPTY -> EmptyFileSink 12 | FileType.DIRECTORY -> DirectoryFileSink(outputFile) 13 | FileType.JAR -> PatchedJarFileSink(JarFileSink(outputFile)) 14 | } 15 | } 16 | 17 | private class PatchedJarFileSink(private val delegate: FileSink) : FileSink by delegate { 18 | private val directories = HashSet() 19 | 20 | override fun createDirectory(path: String) { 21 | if (directories.add(path)) { 22 | delegate.createDirectory(path) 23 | } 24 | } 25 | } 26 | 27 | private val File.fileType: FileType 28 | get() = when { 29 | extension.endsWith("jar", ignoreCase = true) -> FileType.JAR 30 | !exists() || isDirectory -> FileType.DIRECTORY 31 | else -> error("Unknown file type for file $this") 32 | } 33 | 34 | private enum class FileType { 35 | EMPTY, 36 | DIRECTORY, 37 | JAR 38 | } 39 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/Model.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor 18 | 19 | import com.joom.grip.mirrors.Type 20 | 21 | data class AnalysisResult( 22 | val configurationsByType: Map 23 | ) 24 | 25 | data class ClassConfiguration( 26 | val container: Type.Object, 27 | val constantStringsByFieldName: Map 28 | ) 29 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/ObfuscatedTypeRegistry.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor 18 | 19 | import com.joom.grip.mirrors.Type 20 | 21 | interface ObfuscatedTypeRegistry { 22 | fun shouldObfuscate(type: Type.Object): Boolean 23 | } 24 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/ObfuscatedTypeRegistryExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor 18 | 19 | import com.joom.grip.ClassRegistry 20 | import com.joom.grip.Grip 21 | import com.joom.grip.mirrors.Type 22 | import com.joom.grip.mirrors.Typed 23 | import com.joom.grip.objectType 24 | 25 | fun newObfuscatedTypeRegistry(classRegistry: ClassRegistry): ObfuscatedTypeRegistry { 26 | return ObfuscatedTypeRegistryImpl(classRegistry) 27 | } 28 | 29 | fun ObfuscatedTypeRegistry.withCache(): ObfuscatedTypeRegistry { 30 | return this as? CachedObfuscatedTypeRegistry ?: CachedObfuscatedTypeRegistry(this) 31 | } 32 | 33 | fun ObfuscatedTypeRegistry.shouldObfuscate(): (Grip, Typed) -> Boolean { 34 | return objectType { _, type -> shouldObfuscate(type) } 35 | } 36 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/ObfuscatedTypeRegistryImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor 18 | 19 | import com.joom.grip.ClassRegistry 20 | import com.joom.grip.mirrors.ClassMirror 21 | import com.joom.grip.mirrors.Type 22 | import com.joom.grip.mirrors.getObjectTypeByInternalName 23 | 24 | class ObfuscatedTypeRegistryImpl( 25 | private val classRegistry: ClassRegistry 26 | ) : ObfuscatedTypeRegistry { 27 | 28 | override fun shouldObfuscate(type: Type.Object): Boolean { 29 | val mirror = findClassMirror(type) ?: return false 30 | if (OBFUSCATE_TYPE in mirror.annotations) { 31 | return true 32 | } 33 | 34 | return mirror.enclosingType?.let { shouldObfuscate(it) } ?: false 35 | } 36 | 37 | private fun findClassMirror(type: Type.Object): ClassMirror? { 38 | return try { 39 | classRegistry.getClassMirror(type) 40 | } catch (exception: IllegalArgumentException) { 41 | // Sometimes Kotlin generates erroneous bytecode with InnerClass attribute referencing an non-existent class. 42 | extractOuterType(type)?.let { findClassMirror(it) } 43 | } 44 | } 45 | 46 | private fun extractOuterType(type: Type.Object): Type.Object? { 47 | val internalName = type.internalName 48 | val outerInternalName = internalName.substringBeforeLast('$', "") 49 | if (outerInternalName.isEmpty()) { 50 | return null 51 | } 52 | 53 | return getObjectTypeByInternalName(outerInternalName) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/ParanoidException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor 18 | 19 | class ParanoidException : Exception { 20 | constructor() : super() 21 | constructor(message: String?) : super(message) 22 | constructor(message: String?, cause: Throwable?) : super(message, cause) 23 | constructor(cause: Throwable?) : super(cause) 24 | } 25 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/ParanoidProcessor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor 18 | 19 | import com.joom.grip.Grip 20 | import com.joom.grip.GripFactory 21 | import com.joom.grip.io.IoFactory 22 | import com.joom.grip.mirrors.getObjectTypeByInternalName 23 | import com.joom.paranoid.processor.commons.closeQuietly 24 | import com.joom.paranoid.processor.logging.getLogger 25 | import com.joom.paranoid.processor.model.Deobfuscator 26 | import org.objectweb.asm.Opcodes 27 | import org.objectweb.asm.Type 28 | import org.objectweb.asm.commons.Method 29 | import java.io.File 30 | import java.nio.file.Paths 31 | 32 | class ParanoidProcessor( 33 | private val obfuscationSeed: Int, 34 | private val inputs: List, 35 | private val output: File, 36 | private val classpath: Collection, 37 | private val validationClasspath: Collection, 38 | private val bootClasspath: Collection, 39 | private val projectName: String, 40 | private val validateClasspath: Boolean, 41 | private val asmApi: Int = Opcodes.ASM9, 42 | ) { 43 | 44 | private val logger = getLogger() 45 | 46 | private val grip: Grip = GripFactory.newInstance(asmApi).create(inputs + classpath + bootClasspath + validationClasspath) 47 | private val stringRegistry = StringRegistryImpl(obfuscationSeed) 48 | private val validator: Validator = Validator(grip, asmApi) 49 | 50 | fun process() { 51 | dumpConfiguration() 52 | 53 | if (validateClasspath) { 54 | validator.validate(validationClasspath) 55 | } 56 | 57 | val analysisResult = Analyzer(grip).analyze(inputs) 58 | analysisResult.dump() 59 | 60 | val deobfuscator = createDeobfuscator() 61 | logger.info("Prepare to generate {}", deobfuscator) 62 | 63 | val sources = inputs.map { 64 | IoFactory.createFileSource(it) 65 | } 66 | 67 | val sink = createFileSink(output) 68 | 69 | try { 70 | Patcher(deobfuscator, stringRegistry, analysisResult, grip.classRegistry, asmApi).copyAndPatchClasses(sources, sink) 71 | val deobfuscatorBytes = DeobfuscatorGenerator(deobfuscator, stringRegistry, grip.classRegistry) 72 | .generateDeobfuscator() 73 | val deobfuscatorPath = "${deobfuscator.type.internalName}.class" 74 | 75 | sink.createFile(deobfuscatorPath, deobfuscatorBytes) 76 | sink.flush() 77 | } finally { 78 | sources.forEach { 79 | it.closeQuietly() 80 | } 81 | sink.closeQuietly() 82 | } 83 | } 84 | 85 | private fun dumpConfiguration() { 86 | logger.info("Starting ParanoidProcessor:") 87 | logger.info(" inputs = {}", inputs) 88 | logger.info(" output = {}", output) 89 | logger.info(" classpath = {}", classpath) 90 | logger.info(" bootClasspath = {}", bootClasspath) 91 | logger.info(" projectName = {}", projectName) 92 | } 93 | 94 | private fun AnalysisResult.dump() { 95 | if (configurationsByType.isEmpty()) { 96 | logger.info("No classes to obfuscate") 97 | } else { 98 | logger.info("Classes to obfuscate:") 99 | configurationsByType.forEach { 100 | val (type, configuration) = it 101 | logger.info(" {}:", type.internalName) 102 | configuration.constantStringsByFieldName.forEach { 103 | val (field, string) = it 104 | logger.info(" {} = \"{}\"", field, string) 105 | } 106 | } 107 | } 108 | } 109 | 110 | private fun createDeobfuscator(): Deobfuscator { 111 | val deobfuscatorInternalName = "com/joom/paranoid/Deobfuscator${composeDeobfuscatorNameSuffix()}" 112 | val deobfuscatorType = getObjectTypeByInternalName(deobfuscatorInternalName) 113 | val deobfuscationMethod = Method("getString", Type.getType(String::class.java), arrayOf(Type.LONG_TYPE)) 114 | return Deobfuscator(deobfuscatorType, deobfuscationMethod) 115 | } 116 | 117 | private fun composeDeobfuscatorNameSuffix(): String { 118 | val normalizedProjectName = projectName.filter { it.isLetterOrDigit() || it == '_' || it == '$' } 119 | return if (normalizedProjectName.isEmpty() || normalizedProjectName.startsWith('$')) { 120 | normalizedProjectName 121 | } else { 122 | "$$normalizedProjectName" 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/Patcher.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor 18 | 19 | import com.joom.grip.ClassRegistry 20 | import com.joom.grip.io.FileSink 21 | import com.joom.grip.io.FileSource 22 | import com.joom.grip.mirrors.Type 23 | import com.joom.grip.mirrors.getObjectTypeByInternalName 24 | import com.joom.paranoid.processor.logging.getLogger 25 | import com.joom.paranoid.processor.model.Deobfuscator 26 | import org.objectweb.asm.ClassReader 27 | import org.objectweb.asm.ClassVisitor 28 | import org.objectweb.asm.ClassWriter 29 | import org.objectweb.asm.Opcodes 30 | 31 | class Patcher( 32 | private val deobfuscator: Deobfuscator, 33 | private val stringRegistry: StringRegistry, 34 | private val analysisResult: AnalysisResult, 35 | private val classRegistry: ClassRegistry, 36 | private val asmApi: Int, 37 | ) { 38 | 39 | private val logger = getLogger() 40 | 41 | fun copyAndPatchClasses(sources: List, sink: FileSink) { 42 | sources.forEach { 43 | copyAndPatchClasses(it, sink) 44 | sink.flush() 45 | } 46 | } 47 | 48 | private fun copyAndPatchClasses(source: FileSource, sink: FileSink) { 49 | logger.info("Patching...") 50 | logger.info(" Input: {}", source) 51 | logger.info(" Output: {}", sink) 52 | 53 | source.listFiles { name, type -> 54 | when (type) { 55 | FileSource.EntryType.CLASS -> copyAndPatchClass(source, sink, name) 56 | FileSource.EntryType.FILE -> source.copyFileTo(sink, name) 57 | FileSource.EntryType.DIRECTORY -> sink.createDirectory(name) 58 | } 59 | } 60 | } 61 | 62 | private fun copyAndPatchClass(source: FileSource, sink: FileSink, name: String) { 63 | if (!maybePatchClass(source, sink, name)) { 64 | source.copyFileTo(sink, name) 65 | } 66 | } 67 | 68 | private fun getObjectTypeFromFile(relativePath: String): Type.Object? { 69 | if (relativePath.endsWith(".class")) { 70 | val internalName = relativePath.substringBeforeLast(".class").replace('\\', '/') 71 | return getObjectTypeByInternalName(internalName) 72 | } 73 | return null 74 | } 75 | 76 | private fun maybePatchClass(source: FileSource, sink: FileSink, name: String): Boolean { 77 | val type = getObjectTypeFromFile(name) ?: run { 78 | logger.error("Skip patching for {}", name) 79 | return false 80 | } 81 | 82 | val configuration = analysisResult.configurationsByType[type] 83 | val hasObfuscateAnnotation = OBFUSCATE_TYPE in classRegistry.getClassMirror(type).annotations 84 | if (configuration == null && !hasObfuscateAnnotation) { 85 | return false 86 | } 87 | 88 | logger.debug("Patching class {}", name) 89 | val reader = ClassReader(source.readFile(name)) 90 | val writer = StandaloneClassWriter(ClassWriter.COMPUTE_MAXS or ClassWriter.COMPUTE_FRAMES, classRegistry) 91 | val shouldObfuscateLiterals = reader.access and Opcodes.ACC_INTERFACE == 0 92 | val patcher = 93 | writer 94 | .wrapIf(hasObfuscateAnnotation) { RemoveObfuscateClassPatcher(asmApi, it) } 95 | .wrapIf(configuration != null) { StringLiteralsClassPatcher(deobfuscator, stringRegistry, asmApi, it) } 96 | .wrapIf(configuration != null && shouldObfuscateLiterals) { StringConstantsClassPatcher(configuration!!, asmApi, it) } 97 | reader.accept(patcher, ClassReader.SKIP_FRAMES) 98 | sink.createFile(name, writer.toByteArray()) 99 | return true 100 | } 101 | 102 | private inline fun ClassVisitor.wrapIf(condition: Boolean, wrapper: (ClassVisitor) -> ClassVisitor): ClassVisitor { 103 | return if (condition) wrapper(this) else this 104 | } 105 | 106 | private fun FileSource.copyFileTo(sink: FileSink, name: String) { 107 | sink.createFile(name, readFile(name)) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/RemoveObfuscateClassPatcher.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor 18 | 19 | import com.joom.paranoid.processor.watermark.WatermarkClassVisitor 20 | import org.objectweb.asm.AnnotationVisitor 21 | import org.objectweb.asm.ClassVisitor 22 | 23 | class RemoveObfuscateClassPatcher( 24 | asmApi: Int, 25 | delegate: ClassVisitor, 26 | ) : WatermarkClassVisitor(asmApi, delegate) { 27 | 28 | private val obfuscateDescriptor = OBFUSCATE_TYPE.descriptor 29 | 30 | override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? { 31 | return if (obfuscateDescriptor != desc) super.visitAnnotation(desc, visible) else null 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/StandaloneClassWriter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor 18 | 19 | import com.joom.grip.ClassRegistry 20 | import com.joom.grip.mirrors.ClassMirror 21 | import com.joom.grip.mirrors.Type 22 | import com.joom.grip.mirrors.getObjectType 23 | import com.joom.grip.mirrors.getObjectTypeByInternalName 24 | import com.joom.paranoid.processor.logging.getLogger 25 | import org.objectweb.asm.ClassReader 26 | import org.objectweb.asm.ClassWriter 27 | 28 | class StandaloneClassWriter : ClassWriter { 29 | private val logger = getLogger() 30 | private val classRegistry: ClassRegistry 31 | 32 | constructor(flags: Int, classRegistry: ClassRegistry) : super(flags) { 33 | this.classRegistry = classRegistry 34 | } 35 | 36 | constructor(classReader: ClassReader, flags: Int, classRegistry: ClassRegistry) : super(classReader, flags) { 37 | this.classRegistry = classRegistry 38 | } 39 | 40 | override fun getCommonSuperClass(type1: String, type2: String): String { 41 | val hierarchy = HashSet() 42 | for (mirror in classRegistry.findClassHierarchy(getObjectTypeByInternalName(type1))) { 43 | hierarchy.add(mirror.type) 44 | } 45 | 46 | for (mirror in classRegistry.findClassHierarchy(getObjectTypeByInternalName(type2))) { 47 | if (mirror.type in hierarchy) { 48 | logger.debug("[getCommonSuperClass]: {} & {} = {}", type1, type2, mirror.access) 49 | return mirror.type.internalName 50 | } 51 | } 52 | 53 | logger.warn("[getCommonSuperClass]: {} & {} = NOT FOUND ", type1, type2) 54 | return OBJECT_INTERNAL_NAME 55 | } 56 | 57 | private fun ClassRegistry.findClassHierarchy(type: Type.Object): Sequence { 58 | return generateSequence(getClassMirror(type)) { 59 | it.superType?.let { getClassMirror(it) } 60 | } 61 | } 62 | 63 | companion object { 64 | private val OBJECT_INTERNAL_NAME = getObjectType().internalName 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/StringConstantsClassPatcher.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor 18 | 19 | import com.joom.grip.mirrors.toAsmType 20 | import com.joom.paranoid.processor.logging.getLogger 21 | import com.joom.paranoid.processor.watermark.WatermarkClassVisitor 22 | import org.objectweb.asm.ClassVisitor 23 | import org.objectweb.asm.FieldVisitor 24 | import org.objectweb.asm.MethodVisitor 25 | import org.objectweb.asm.Opcodes.ACC_PRIVATE 26 | import org.objectweb.asm.Opcodes.ACC_STATIC 27 | import org.objectweb.asm.Type 28 | import org.objectweb.asm.commons.GeneratorAdapter 29 | import org.objectweb.asm.commons.Method 30 | 31 | class StringConstantsClassPatcher( 32 | private val configuration: ClassConfiguration, 33 | asmApi: Int, 34 | delegate: ClassVisitor, 35 | ) : WatermarkClassVisitor(asmApi, delegate) { 36 | 37 | private val logger = getLogger() 38 | 39 | private var isStaticInitializerPatched = false 40 | 41 | override fun visit( 42 | version: Int, 43 | access: Int, 44 | name: String, 45 | signature: String?, 46 | superName: String?, 47 | interfaces: Array? 48 | ) { 49 | super.visit(version, access, name, signature, superName, interfaces) 50 | isStaticInitializerPatched = configuration.constantStringsByFieldName.isEmpty() 51 | } 52 | 53 | override fun visitField(access: Int, name: String, desc: String, signature: String?, value: Any?): FieldVisitor? { 54 | val newValue = if (name in configuration.constantStringsByFieldName) null else value 55 | return super.visitField(access, name, desc, signature, newValue) 56 | } 57 | 58 | override fun visitMethod( 59 | access: Int, 60 | name: String, 61 | desc: String, 62 | signature: String?, 63 | exceptions: Array? 64 | ): MethodVisitor { 65 | val visitor = super.visitMethod(access, name, desc, signature, exceptions) 66 | if (name == STATIC_INITIALIZER_METHOD.name) { 67 | return createStaticInitializerPatcher(visitor, access, name, desc) 68 | } else { 69 | return visitor 70 | } 71 | } 72 | 73 | override fun visitEnd() { 74 | if (!isStaticInitializerPatched) { 75 | GeneratorAdapter(ACC_PRIVATE or ACC_STATIC, STATIC_INITIALIZER_METHOD, null, null, this).apply { 76 | visitCode() 77 | returnValue() 78 | endMethod() 79 | } 80 | } 81 | 82 | super.visitEnd() 83 | } 84 | 85 | private fun createStaticInitializerPatcher( 86 | visitor: MethodVisitor, 87 | access: Int, 88 | name: String, 89 | desc: String 90 | ): MethodVisitor { 91 | if (!isStaticInitializerPatched) { 92 | isStaticInitializerPatched = true 93 | return object : GeneratorAdapter(api, visitor, access, name, desc) { 94 | override fun visitCode() { 95 | logger.info("{}:", configuration.container.internalName) 96 | logger.info(" Patching ...") 97 | super.visitCode() 98 | for ((field, value) in configuration.constantStringsByFieldName) { 99 | push(value) 100 | putStatic(configuration.container.toAsmType(), field, STRING_TYPE) 101 | } 102 | } 103 | } 104 | } else { 105 | return visitor 106 | } 107 | } 108 | 109 | companion object { 110 | private val STATIC_INITIALIZER_METHOD = Method("", Type.VOID_TYPE, arrayOf()) 111 | private val STRING_TYPE = Type.getType(String::class.java) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/StringLiteralsClassPatcher.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor 18 | 19 | import com.joom.grip.mirrors.toAsmType 20 | import com.joom.paranoid.processor.logging.getLogger 21 | import com.joom.paranoid.processor.model.Deobfuscator 22 | import com.joom.paranoid.processor.watermark.WatermarkClassVisitor 23 | import org.objectweb.asm.ClassVisitor 24 | import org.objectweb.asm.MethodVisitor 25 | import org.objectweb.asm.commons.GeneratorAdapter 26 | 27 | class StringLiteralsClassPatcher( 28 | private val deobfuscator: Deobfuscator, 29 | private val stringRegistry: StringRegistry, 30 | asmApi: Int, 31 | delegate: ClassVisitor, 32 | ) : WatermarkClassVisitor(asmApi, delegate) { 33 | 34 | private val logger = getLogger() 35 | 36 | private var className: String = "" 37 | 38 | override fun visit( 39 | version: Int, 40 | access: Int, 41 | name: String, 42 | signature: String?, 43 | superName: String?, 44 | interfaces: Array? 45 | ) { 46 | super.visit(version, access, name, signature, superName, interfaces) 47 | className = name 48 | } 49 | 50 | override fun visitMethod( 51 | access: Int, 52 | name: String, 53 | desc: String, 54 | signature: String?, 55 | exceptions: Array? 56 | ): MethodVisitor { 57 | val visitor = super.visitMethod(access, name, desc, signature, exceptions) 58 | return object : GeneratorAdapter(api, visitor, access, name, desc) { 59 | override fun visitLdcInsn(constant: Any) { 60 | if (constant is String) { 61 | replaceStringWithDeobfuscationMethod(constant) 62 | } else { 63 | super.visitLdcInsn(constant) 64 | } 65 | } 66 | 67 | private fun replaceStringWithDeobfuscationMethod(string: String) { 68 | logger.info("{}.{}{}:", className, name, desc) 69 | logger.info(" Obfuscating string literal: \"{}\"", string) 70 | val stringId = stringRegistry.registerString(string) 71 | push(stringId) 72 | invokeStatic(deobfuscator.type.toAsmType(), deobfuscator.deobfuscationMethod) 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/StringRegistry.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor 18 | 19 | import com.joom.paranoid.DeobfuscatorHelper 20 | import com.joom.paranoid.RandomHelper 21 | 22 | interface StringRegistry { 23 | fun registerString(string: String): Long 24 | fun getAllChunks(): List 25 | } 26 | 27 | class StringRegistryImpl( 28 | seed: Int 29 | ) : StringRegistry { 30 | 31 | private val seed = seed.toLong() and 0xffff_ffffL 32 | private val builder = StringBuilder() 33 | 34 | override fun registerString(string: String): Long { 35 | var mask = 0L 36 | var state = RandomHelper.seed(seed) 37 | state = RandomHelper.next(state) 38 | mask = mask or (state and 0xffff_0000_0000L) 39 | state = RandomHelper.next(state) 40 | mask = mask or ((state and 0xffff_0000_0000L) shl 16) 41 | val index = builder.length 42 | val id = seed or ((index.toLong() shl 32) xor mask) 43 | 44 | state = RandomHelper.next(state) 45 | builder.append((((state ushr 32) and 0xffffL) xor string.length.toLong()).toInt().toChar()) 46 | 47 | for (char in string) { 48 | state = RandomHelper.next(state) 49 | builder.append((((state ushr 32) and 0xffffL) xor char.code.toLong()).toInt().toChar()) 50 | } 51 | 52 | return id 53 | } 54 | 55 | override fun getAllChunks(): List { 56 | return builder.toString().chunked(DeobfuscatorHelper.MAX_CHUNK_LENGTH) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/Types.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor 18 | 19 | import com.joom.grip.mirrors.getObjectType 20 | import com.joom.paranoid.DeobfuscatorHelper 21 | import com.joom.paranoid.Obfuscate 22 | 23 | val OBJECT_TYPE = getObjectType() 24 | val OBFUSCATE_TYPE = getObjectType() 25 | val DEOBFUSCATOR_HELPER_TYPE = getObjectType() 26 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/Validator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor 18 | 19 | import com.joom.grip.Grip 20 | import com.joom.grip.classes 21 | import com.joom.paranoid.processor.watermark.WatermarkChecker 22 | import java.io.File 23 | 24 | class Validator(private val grip: Grip, private val asmApi: Int) { 25 | 26 | fun validate(inputs: Collection) { 27 | val errors = ArrayList() 28 | val registry = newObfuscatedTypeRegistry(grip.classRegistry).withCache() 29 | val query = grip select classes from inputs where registry.shouldObfuscate() 30 | query.execute().types.forEach { 31 | val file = grip.fileRegistry.findFileForType(it) ?: run { 32 | errors += "File not found for class ${it.className}" 33 | return@forEach 34 | } 35 | 36 | if (!WatermarkChecker.isParanoidClass(file, asmApi)) { 37 | errors += "Class ${it.className} is not processed by paranoid, is paranoid plugin applied to module?" 38 | } 39 | } 40 | 41 | if (errors.isNotEmpty()) { 42 | throw ParanoidException(errors.joinToString(separator = "\n")) 43 | } 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/commons/CloseableExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor.commons 18 | 19 | import java.io.Closeable 20 | import java.io.IOException 21 | 22 | fun Closeable.closeQuietly() { 23 | try { 24 | close() 25 | } catch (exception: IOException) { 26 | // Ignore the exception. 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/logging/LoggerExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor.logging 18 | 19 | import org.slf4j.LoggerFactory 20 | 21 | fun T.getLogger() = getLogger(javaClass) 22 | fun getLogger(name: String) = LoggerFactory.getLogger(name) 23 | fun getLogger(type: Class<*>) = LoggerFactory.getLogger(type) 24 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/model/Deobfuscator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor.model 18 | 19 | import com.joom.grip.mirrors.Type 20 | import org.objectweb.asm.commons.Method 21 | 22 | data class Deobfuscator( 23 | val type: Type.Object, 24 | val deobfuscationMethod: Method 25 | ) 26 | -------------------------------------------------------------------------------- /paranoid/processor/src/main/kotlin/com/joom/paranoid/processor/watermark/ParanoidAttribute.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 SIA Joom 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.joom.paranoid.processor.watermark 18 | 19 | import org.objectweb.asm.Attribute 20 | import org.objectweb.asm.ByteVector 21 | import org.objectweb.asm.ClassReader 22 | import org.objectweb.asm.ClassWriter 23 | import org.objectweb.asm.Label 24 | 25 | class ParanoidAttribute private constructor(type: String) : Attribute(type) { 26 | constructor() : this("Paranoid") 27 | 28 | override fun read( 29 | classReader: ClassReader, 30 | offset: Int, 31 | length: Int, 32 | buffer: CharArray?, 33 | codeOffset: Int, 34 | labels: Array