├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── dbnavigator.xml ├── encodings.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── debug │ └── app.aab ├── proguard-rules.pro ├── release │ └── output.json └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── yk │ │ └── dexdeapplication │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── yk │ │ │ └── dexdeapplication │ │ │ ├── App.java │ │ │ ├── DateUtils.java │ │ │ ├── MainActivity.java │ │ │ ├── MyBroadCastReciver.java │ │ │ ├── MyProvider.java │ │ │ └── MyService.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── yk │ └── dexdeapplication │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── proxy_core ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── proxy_core │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── proxy_core │ │ │ ├── CrashHandler.java │ │ │ ├── EncryptUtil.java │ │ │ ├── ProxyApplication.java │ │ │ ├── ProxyUtils.java │ │ │ └── Zip.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── example │ └── proxy_core │ └── ExampleUnitTest.java ├── proxy_tools ├── .gitignore ├── build.gradle ├── dexjks.jks ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── proxy_tools │ │ │ ├── DexUtils.java │ │ │ ├── EncryptUtil.java │ │ │ ├── Main.java │ │ │ └── Zip.java │ │ └── res │ │ └── values │ │ └── strings.xml ├── temp │ ├── AndroidManifest.xml │ ├── R.txt │ ├── classes.jar │ └── res │ │ └── values │ │ └── values.xml └── ykun.jks └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/assetWizardSettings.xml 41 | .idea/dictionaries 42 | .idea/libraries 43 | .idea/caches 44 | 45 | # Keystore files 46 | # Uncomment the following line if you do not want to check your keystore files in. 47 | #*.jks 48 | 49 | # External native build folder generated in Android Studio 2.2 and later 50 | .externalNativeBuild 51 | 52 | # Google Services (e.g. APIs or Firebase) 53 | google-services.json 54 | 55 | # Freeline 56 | freeline.py 57 | freeline/ 58 | freeline_project_description.json 59 | 60 | # fastlane 61 | fastlane/report.xml 62 | fastlane/Preview.html 63 | fastlane/screenshots 64 | fastlane/test_output 65 | fastlane/readme.md 66 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 26 | 27 | 28 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /.idea/dbnavigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [文章博客地址](https://juejin.im/post/5cf3ee295188256aa76bb1e1) 2 | ## 简介 3 | 4 | 现在随意在应用市场下载一个 APK 文件然后反编译,95% 以上基本上都是经过混淆,加密,或第三方加固(第三方加固也是这个原理),那么今天我们就对 Dex 来进行加密解密。让反编译无法正常阅读项目源码。 5 | 6 | **加密后的结构** 7 | 8 | **APK 分析** 9 | 10 | ![](https://user-gold-cdn.xitu.io/2019/6/3/16b18eeecfed7d98?w=1365&h=646&f=jpeg&s=104055) 11 | 通过 AS 工具分析加密后的 APK 文件,查看 dex 是报错的,要的就是这个效果。 12 | 13 | **反编译效果** 14 | 15 | ![](https://user-gold-cdn.xitu.io/2019/6/2/16b18dbb0cd919a2?w=1344&h=756&f=jpeg&s=152627) 16 | 17 | ## 想要对 Dex 加密 ,先来了解什么是 64 K 问题 18 | 19 | 想要详细了解 64 k 的问题可以参考[官网]() 20 | 21 | 随着 Android 平台的持续成长,Android 应用的大小也在增加。当您的应用及其引用的库达到特定大小时,您会遇到构建错误,指明您的应用已达到 Android 应用构建架构的极限。早期版本的构建系统按如下方式报告这一错误: 22 | 23 | ```java 24 | Conversion to Dalvik format failed: 25 | Unable to execute dex: method ID not in [0, 0xffff]: 65536 26 | ``` 27 | 28 | 较新版本的 Android 构建系统虽然显示的错误不同,但指示的是同一问题: 29 | 30 | ```java 31 | trouble writing output: 32 | Too many field references: 131000; max is 65536. 33 | You may try using --multi-dex option. 34 | ``` 35 | 36 | 这些错误状况都会显示下面这个数字:65,536。这个数字很重要,因为它代表的是单个 Dalvik Executable (DEX) 字节码文件内的代码可调用的引用总数。本节介绍如何通过启用被称为 *Dalvik 可执行文件分包*的应用配置来越过这一限制,使您的应用能够构建并读取 Dalvik 可执行文件分包 DEX 文件。 37 | 38 | ### 关于 64K 引用限制 39 | 40 | #### Android 5.0 之前版本的 Dalvik 可执行文件分包支持 41 | 42 | Android 5.0(API 级别 21)之前的平台版本使用 Dalvik 运行时来执行应用代码。默认情况下,Dalvik 限制应用的每个 APK 只能使用单个 `classes.dex` 字节码文件。要想绕过这一限制,您可以使用 [Dalvik 可执行文件分包支持库](https://developer.android.com/tools/support-library/features.html?hl=zh-cn#multidex),它会成为您的应用主要 DEX 文件的一部分,然后管理对其他 DEX 文件及其所包含代码的访问。 43 | 44 | #### Android 5.0 及更高版本的 Dalvik 可执行文件分包支持 45 | 46 | Android 5.0(API 级别 21)及更高版本使用名为 ART 的运行时,后者原生支持从 APK 文件加载多个 DEX 文件。ART 在应用安装时执行预编译,扫描 `classesN.dex` 文件,并将它们编译成单个 `.oat` 文件,供 Android 设备执行。因此,如果您的 `minSdkVersion` 为 21 或更高值,则不需要 Dalvik 可执行文件分包支持库。 47 | 48 | ### 解决 64K 限制 49 | 50 | 1. 如果您的 `minSdkVersion` 设置为 21 或更高值,您只需在模块级 `build.gradle` 文件中将 `multiDexEnabled` 设置为 `true`,如此处所示: 51 | 52 | ```java 53 | android { 54 | defaultConfig { 55 | ... 56 | minSdkVersion 21 57 | targetSdkVersion 28 58 | multiDexEnabled true 59 | } 60 | ... 61 | } 62 | ``` 63 | 64 | 但是,如果您的 `minSdkVersion` 设置为 20 或更低值,则您必须按如下方式使用 [Dalvik 可执行文件分包支持库](https://developer.android.com/tools/support-library/features.html?hl=zh-cn#multidex): 65 | 66 | - 修改模块级 `build.gradle` 文件以启用 Dalvik 可执行文件分包,并将 Dalvik 可执行文件分包库添加为依赖项,如此处所示 67 | 68 | ```java 69 | android { 70 | defaultConfig { 71 | ... 72 | minSdkVersion 15 73 | targetSdkVersion 28 74 | multiDexEnabled true 75 | } 76 | ... 77 | } 78 | 79 | dependencies { 80 | compile 'com.android.support:multidex:1.0.3' 81 | } 82 | ``` 83 | 84 | - 当前 Application extends MultiDexApplication {...} 或者 MultiDex.install(this); 85 | 86 | 2. 通过混淆 开启 ProGuard 移除未使用的代码,构建代码压缩。 87 | 88 | 3. 减少第三方库的直接依赖,尽可能下载源码,需要什么就用什么没必要依赖整个项目。 89 | 90 | ## Dex 加密与解密 91 | 92 | ![](https://user-gold-cdn.xitu.io/2019/6/2/16b18dbfe0ee1c3c?w=960&h=720&f=jpeg&s=22617) 93 | 94 | **流程:** 95 | 96 | 1. 拿到 APK 解压得到所有的 dex 文件。 97 | 2. 通过 Tools 来进行加密,并把加密后的 dex 和代理应用 class.dex 合并,然后重新签名,对齐,打包。 98 | 3. 当用户安装 APK 打开进入代理解密的 Application 时,反射得到 dexElements 并将解密后的 dex 替换 DexPathList 中的 dexElements . 99 | 100 | ### Dex 文件加载过程 101 | 102 | 既然要查 Dex 加载过程,那么得先知道从哪个源码 class 入手,既然不知道那么我们就先打印下 ClassLoader ; 103 | 104 | ![](https://user-gold-cdn.xitu.io/2019/6/2/16b18dc27dad72a8?w=761&h=438&f=jpeg&s=58192) 105 | 106 | 下面就以一个流程图来详细了解下 Dex 加载过程吧 107 | 108 | ![](https://user-gold-cdn.xitu.io/2019/6/2/16b18dc848c69036?w=1110&h=793&f=jpeg&s=35355) 109 | 110 | 最后我们得知在 **findClass(String name,List sup)** 遍历 **dexElements** 找到 Class 并交给 Android 加载。 111 | 112 | ### Dex 解密 113 | 114 | 现在我们知道 dex 加载流程了 , 那么我们怎么进行来对 dex 解密勒,刚刚我们得知需要遍历 dexElements 来找到 Class 那么我们是不是可以在遍历之前 ,初始化 dexElements 的时候。反射得到 dexElements 将我们解密后的 dex 交给 dexElements 。下面我们就通过代码来进行解密 dex 并替换 DexPathList 中的 dexElements; 115 | 116 | 1. 得到当前加密了的 APK 文件 并解压 117 | 118 | ```java 119 | //得到当前加密了的APK文件 120 | File apkFile=new File(getApplicationInfo().sourceDir); 121 | //把apk解压 app_name+"_"+app_version目录中的内容需要boot权限才能用 122 | File versionDir = getDir(app_name+"_"+app_version,MODE_PRIVATE); 123 | File appDir=new File(versionDir,"app"); 124 | File dexDir=new File(appDir,"dexDir"); 125 | ``` 126 | 127 | 2. 得到我们需要加载的 Dex 文件 128 | 129 | ```java 130 | //把apk解压到appDir 131 | Zip.unZip(apkFile,appDir); 132 | //获取目录下所有的文件 133 | File[] files=appDir.listFiles(); 134 | for (File file : files) { 135 | String name=file.getName(); 136 | if(name.endsWith(".dex") && !TextUtils.equals(name,"classes.dex")){ 137 | try{ 138 | AES.init(AES.DEFAULT_PWD); 139 | //读取文件内容 140 | byte[] bytes=Utils.getBytes(file); 141 | //解密 142 | byte[] decrypt=AES.decrypt(bytes); 143 | //写到指定的目录 144 | FileOutputStream fos=new FileOutputStream(file); 145 | fos.write(decrypt); 146 | fos.flush(); 147 | fos.close(); 148 | dexFiles.add(file); 149 | 150 | }catch (Exception e){ 151 | e.printStackTrace(); 152 | } 153 | } 154 | } 155 | ``` 156 | 157 | 3. 把解密后的 dex 加载到系统 158 | 159 | ```java 160 | private void loadDex(List dexFiles, File versionDir) throws Exception{ 161 | //1.获取pathlist 162 | Field pathListField = Utils.findField(getClassLoader(), "pathList"); 163 | Object pathList = pathListField.get(getClassLoader()); 164 | //2.获取数组dexElements 165 | Field dexElementsField=Utils.findField(pathList,"dexElements"); 166 | Object[] dexElements=(Object[])dexElementsField.get(pathList); 167 | //3.反射到初始化dexElements的方法 168 | Method makeDexElements=Utils.findMethod(pathList,"makePathElements",List.class,File.class,List.class); 169 | 170 | ArrayList suppressedExceptions = new ArrayList(); 171 | Object[] addElements=(Object[])makeDexElements.invoke(pathList,dexFiles,versionDir,suppressedExceptions); 172 | 173 | //合并数组 174 | Object[] newElements= (Object[])Array.newInstance(dexElements.getClass().getComponentType(),dexElements.length+addElements.length); 175 | System.arraycopy(dexElements,0,newElements,0,dexElements.length); 176 | System.arraycopy(addElements,0,newElements,dexElements.length,addElements.length); 177 | 178 | //替换 DexPathList 中的 element 数组 179 | dexElementsField.set(pathList,newElements); 180 | } 181 | ``` 182 | 183 | 解密已经完成了,下面来看看加密吧,这里为什么先说解密勒,因为 加密涉及到 签名,打包,对齐。所以留到最后讲。 184 | 185 | ### Dex 加密 186 | 187 | 1. 制作只包含解密代码的 dex 188 | 189 | ```java 190 | 1. sdk\build-tools 中执行下面命令 会得到包含 dex 的 jar 191 | dx --dex --output out.dex in.jar 192 | 2. 通过 exec 执行 193 | File aarFile=new File("proxy_core/build/outputs/aar/proxy_core-debug.aar"); 194 | File aarTemp=new File("proxy_tools/temp"); 195 | Zip.unZip(aarFile,aarTemp); 196 | File classesJar=new File(aarTemp,"classes.jar"); 197 | File classesDex=new File(aarTemp,"classes.dex"); 198 | String absolutePath = classesDex.getAbsolutePath(); 199 | String absolutePath1 = classesJar.getAbsolutePath(); 200 | //dx --dex --output out.dex in.jar 201 | //dx --dex --output //D:\Downloads\android_space\DexDEApplication\proxy_tools\temp\classes.dex //D:\Downloads\android_space\DexDEApplication\proxy_tools\temp\classes.jar 202 | Process process=Runtime.getRuntime().exec("cmd /c dx --dex --output "+classesDex.getAbsolutePath() 203 | +" "+classesJar.getAbsolutePath()); 204 | process.waitFor(); 205 | if(process.exitValue()!=0){ 206 | throw new RuntimeException("dex error"); 207 | } 208 | ``` 209 | 210 | 2. 加密 apk 中的 dex 文件 211 | 212 | ```java 213 | File apkFile=new File("app/build/outputs/apk/debug/app-debug.apk"); 214 | File apkTemp=new File("app/build/outputs/apk/debug/temp"); 215 | Zip.unZip(apkFile,apkTemp); 216 | //只要dex文件拿出来加密 217 | File[] dexFiles=apkTemp.listFiles(new FilenameFilter() { 218 | @Override 219 | public boolean accept(File file, String s) { 220 | return s.endsWith(".dex"); 221 | } 222 | }); 223 | //AES加密了 224 | AES.init(AES.DEFAULT_PWD); 225 | for (File dexFile : dexFiles) { 226 | byte[] bytes = Utils.getBytes(dexFile); 227 | byte[] encrypt = AES.encrypt(bytes); 228 | FileOutputStream fos=new FileOutputStream(new File(apkTemp, 229 | "secret-"+dexFile.getName())); 230 | fos.write(encrypt); 231 | fos.flush(); 232 | fos.close(); 233 | dexFile.delete(); 234 | } 235 | ``` 236 | 237 | 3. 把 dex 放入 apk 加压目录,重新压成 apk 文件 238 | 239 | ```java 240 | File apkTemp=new File("app/build/outputs/apk/debug/temp"); 241 | File aarTemp=new File("proxy_tools/temp"); 242 | File classesDex=new File(aarTemp,"classes.dex"); 243 | classesDex.renameTo(new File(apkTemp,"classes.dex")); 244 | File unSignedApk=new File("app/build/outputs/apk/debug/app-unsigned.apk"); 245 | Zip.zip(apkTemp,unSignedApk); 246 | ``` 247 | 248 | **现在可以看下加密后的文件,和未加密的文件** 249 | 250 | 未加密 apk: 251 | 252 | ![](https://user-gold-cdn.xitu.io/2019/6/2/16b18dce19cea368?w=1547&h=789&f=gif&s=307772) 253 | 254 | 加密后的 apk (现在只能看见代理 Application ) 255 | 256 | ![](https://user-gold-cdn.xitu.io/2019/6/2/16b18ddbef398ad5?w=1547&h=789&f=gif&s=367894) 257 | 258 | ### 打包 259 | 260 | #### [对齐](https://developer.android.google.cn/studio/command-line/zipalign.html) 261 | 262 | ```java 263 | //apk整理对齐工具 未压缩的数据开头均相对于文件开头部分执行特定的字节对齐,减少应用运行内存。 264 | zipalign -f 4 in.apk out.apk 265 | 266 | //比对 apk 是否对齐 267 | zipalign -c -v 4 output.apk 268 | 269 | //最后提示 Verification succesful 说明对齐成功了 270 | 236829 res/mipmap-xxxhdpi-v4/ic_launcher.png (OK - compressed) 271 | 245810 res/mipmap-xxxhdpi-v4/ic_launcher_round.png (OK - compressed) 272 | 260956 resources.arsc (OK - compressed) 273 | 317875 secret-classes.dex (OK - compressed) 274 | 2306140 secret-classes2.dex (OK - compressed) 275 | 2477544 secret-classes3.dex (OK - compressed) 276 | Verification succesful 277 | ``` 278 | 279 | #### 签名打包 apksigner 280 | 281 | ```java 282 | //sdk\build-tools\24.0.3 以上,apk签名工具 283 | apksigner sign --ks jks文件地址 --ks-key-alias 别名 --ks-pass pass:jsk密码 --key-pass pass:别名密码 --out out.apk in.apk 284 | ``` 285 | 286 | ## 总结 287 | 288 | 其实原理就是把主要代码通过命令 *dx* 生成 dex 文件,然后把加密后的 dex 合并在代理 class.dex 中。这样虽然还是能看见代理中的代码,但是主要代码已经没有暴露出来了,就已经实现了我们想要的效果。如果封装的好的话(JNI 中实现主要解密代码),基本上就哈也看不见了。ClassLoader 还是很重要的,热修复跟热加载都是这原理。学到这里 DEX 加解密已经学习完了,如果想看自己试一试可以参考我的代码 289 | 290 | [代码传送阵]() 291 | 292 | **如何学习本项目:** 293 | 294 | ![](https://devyk.oss-cn-qingdao.aliyuncs.com/blog/20200321010522.gif) 295 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.yk.dexdeapplication" 7 | minSdkVersion 21 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled true 16 | shrinkResources true 17 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 18 | } 19 | debug { 20 | minifyEnabled true 21 | shrinkResources true 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 23 | 'proguard-rules.pro' 24 | } 25 | } 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | implementation 'com.android.support:appcompat-v7:28.0.0' 31 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 32 | testImplementation 'junit:junit:4.12' 33 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 34 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 35 | implementation project(':proxy_core') 36 | } 37 | -------------------------------------------------------------------------------- /app/debug/app.aab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/DexEncryptionDecryption/43453801ee30ec1e0dfe6b1ac79b94fb7867f7bf/app/debug/app.aab -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # This is a configuration file for ProGuard. 2 | # http://proguard.sourceforge.net/index.html#manual/usage.html 3 | # 4 | # Starting with version 2.2 of the Android plugin for Gradle, this file is distributed together with 5 | # the plugin and unpacked at build-time. The files in $ANDROID_HOME are no longer maintained and 6 | # will be ignored by new version of the Android plugin for Gradle. 7 | 8 | # Optimizations can be turned on and off in the 'postProcessing' DSL block. 9 | # The configuration below is applied if optimizations are enabled. 10 | # Adding optimization introduces certain risks, since for example not all optimizations performed by 11 | # ProGuard works on all versions of Dalvik. The following flags turn off various optimizations 12 | # known to have issues, but the list may not be complete or up to date. (The "arithmetic" 13 | # optimization can be used if you are only targeting Android 2.0 or later.) Make sure you test 14 | # thoroughly if you go this route. 15 | # --------------------------------------------基本指令区--------------------------------------------# 16 | # 指定代码的压缩级别(在0~7之间,默认为5) 17 | -optimizationpasses 5 18 | # 是否使用大小写混合(windows大小写不敏感,建议加入) 19 | -dontusemixedcaseclassnames 20 | # 是否混淆非公共的库的类 21 | -dontskipnonpubliclibraryclasses 22 | # 是否混淆非公共的库的类的成员 23 | -dontskipnonpubliclibraryclassmembers 24 | # 混淆时是否做预校验(Android不需要预校验,去掉可以加快混淆速度) 25 | -dontpreverify 26 | # 混淆时是否记录日志(混淆后会生成映射文件) 27 | -verbose 28 | 29 | 30 | # 混淆时所采用的算法(谷歌推荐算法) 31 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*,!code/allocation/variable 32 | 33 | 34 | # 将文件来源重命名为“SourceFile”字符串 35 | -renamesourcefileattribute SourceFile 36 | 37 | # 保持注解不被混淆 38 | -keepattributes *Annotation* 39 | -keep class * extends java.lang.annotation.Annotation {*;} 40 | 41 | # 保持泛型不被混淆 42 | -keepattributes Signature 43 | # 保持反射不被混淆 44 | -keepattributes EnclosingMethod 45 | # 保持异常不被混淆 46 | -keepattributes Exceptions 47 | # 保持内部类不被混淆 48 | -keepattributes Exceptions,InnerClasses 49 | # 抛出异常时保留代码行号 50 | -keepattributes SourceFile,LineNumberTable 51 | 52 | # --------------------------------------------默认保留区--------------------------------------------# 53 | # 保持基本组件不被混淆 54 | -keep public class * extends android.app.Fragment 55 | -keep public class * extends android.app.Activity 56 | -keep public class * extends android.app.Application 57 | -keep public class * extends android.app.Service 58 | -keep public class * extends android.content.BroadcastReceiver 59 | -keep public class * extends android.content.ContentProvider 60 | -keep public class * extends android.app.backup.BackupAgentHelper 61 | -keep public class * extends android.preference.Preference 62 | 63 | # 保持 Google 原生服务需要的类不被混淆 64 | -keep public class com.google.vending.licensing.ILicensingService 65 | -keep public class com.android.vending.licensing.ILicensingService 66 | 67 | # Support包规则 68 | -dontwarn android.support.** 69 | -keep public class * extends android.support.v4.** 70 | -keep public class * extends android.support.v7.** 71 | -keep public class * extends android.support.annotation.** 72 | 73 | # 保持 native 方法不被混淆 74 | -keepclasseswithmembernames class * { 75 | native ; 76 | } 77 | 78 | # 保留自定义控件(继承自View)不被混淆 79 | -keep public class * extends android.view.View { 80 | *** get*(); 81 | void set*(***); 82 | public (android.content.Context); 83 | public (android.content.Context, android.util.AttributeSet); 84 | public (android.content.Context, android.util.AttributeSet, int); 85 | } 86 | 87 | # 保留指定格式的构造方法不被混淆 88 | -keepclasseswithmembers class * { 89 | public (android.content.Context, android.util.AttributeSet); 90 | public (android.content.Context, android.util.AttributeSet, int); 91 | } 92 | 93 | # 保留在 Activity 中的方法参数是view的方法(避免布局文件里面onClick被影响) 94 | -keepclassmembers class * extends android.app.Activity { 95 | public void *(android.view.View); 96 | } 97 | 98 | # 保持枚举 enum 类不被混淆 99 | -keepclassmembers enum * { 100 | public static **[] values(); 101 | public static ** valueOf(java.lang.String); 102 | } 103 | 104 | # 保持R(资源)下的所有类及其方法不能被混淆 105 | -keep class **.R$* { *; } 106 | 107 | # 保持 Parcelable 序列化的类不被混淆(注:aidl文件不能去混淆) 108 | -keep class * implements android.os.Parcelable { 109 | public static final android.os.Parcelable$Creator *; 110 | } 111 | 112 | # 需要序列化和反序列化的类不能被混淆(注:Java反射用到的类也不能被混淆) 113 | -keepnames class * implements java.io.Serializable 114 | 115 | # 保持 Serializable 序列化的类成员不被混淆 116 | -keepclassmembers class * implements java.io.Serializable { 117 | static final long serialVersionUID; 118 | private static final java.io.ObjectStreamField[] serialPersistentFields; 119 | !static !transient ; 120 | !private ; 121 | !private ; 122 | private void writeObject(java.io.ObjectOutputStream); 123 | private void readObject(java.io.ObjectInputStream); 124 | java.lang.Object writeReplace(); 125 | java.lang.Object readResolve(); 126 | } 127 | 128 | # 保持 BaseAdapter 类不被混淆 129 | -keep public class * extends android.widget.BaseAdapter { *; } 130 | # 保持 CusorAdapter 类不被混淆 131 | -keep public class * extends android.widget.CusorAdapter{ *; } 132 | 133 | # --------------------------------------------webView区--------------------------------------------# 134 | # WebView处理,项目中没有使用到webView忽略即可 135 | # 保持Android与JavaScript进行交互的类不被混淆 136 | -keep class **.AndroidJavaScript { *; } 137 | -keepclassmembers class * extends android.webkit.WebViewClient { 138 | public void *(android.webkit.WebView,java.lang.String,android.graphics.Bitmap); 139 | public boolean *(android.webkit.WebView,java.lang.String); 140 | } 141 | -keepclassmembers class * extends android.webkit.WebChromeClient { 142 | public void *(android.webkit.WebView,java.lang.String); 143 | } 144 | 145 | # 网络请求相关 146 | -keep public class android.net.http.SslError 147 | 148 | # --------------------------------------------删除代码区--------------------------------------------# 149 | # 删除代码中Log相关的代码 150 | -assumenosideeffects class android.util.Log { 151 | public static boolean isLoggable(java.lang.String, int); 152 | public static int v(...); 153 | public static int i(...); 154 | public static int w(...); 155 | public static int d(...); 156 | public static int e(...); 157 | } 158 | 159 | # DateUtils 类不被混淆 160 | -keep public class com.yk.dexdeapplication.DateUtils{*;} -------------------------------------------------------------------------------- /app/release/output.json: -------------------------------------------------------------------------------- 1 | [{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] -------------------------------------------------------------------------------- /app/src/androidTest/java/com/yk/dexdeapplication/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.yk.dexdeapplication; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.yk.dexdeapplication", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/yk/dexdeapplication/App.java: -------------------------------------------------------------------------------- 1 | package com.yk.dexdeapplication; 2 | 3 | import android.app.Application; 4 | import android.util.Log; 5 | import android.widget.Toast; 6 | 7 | public class App extends Application { 8 | @Override 9 | public void onCreate() { 10 | super.onCreate(); 11 | Log.i("DevYK", "MyApplication onCreate()"); 12 | Toast.makeText(getApplicationContext(), "Application 替换成功", Toast.LENGTH_LONG).show(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/yk/dexdeapplication/DateUtils.java: -------------------------------------------------------------------------------- 1 | package com.yk.dexdeapplication; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | 6 | public class DateUtils { 7 | 8 | private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-mm-dd hh:mm:ss"); 9 | 10 | public void setCurrentDate(Date currentDate) { 11 | this.currentDate = simpleDateFormat.format(currentDate); 12 | } 13 | 14 | private String currentDate; 15 | 16 | public String getCurrentDate(){ 17 | return currentDate; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/yk/dexdeapplication/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.yk.dexdeapplication; 2 | 3 | import android.content.ComponentName; 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | import android.os.Bundle; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.util.Log; 9 | import android.widget.Toast; 10 | 11 | import com.example.proxy_core.ProxyUtils; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.lang.reflect.Field; 16 | import java.lang.reflect.Method; 17 | import java.util.ArrayList; 18 | import java.util.Date; 19 | import java.util.List; 20 | 21 | public class MainActivity extends AppCompatActivity { 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_main); 27 | 28 | 29 | Log.i("DevYK", "activity:" + getApplication()); 30 | Log.i("DevYK", "activity:" + getApplicationContext()); 31 | Log.i("DevYK", "activity:" + getApplicationInfo().className); 32 | 33 | startService(new Intent(this, MyService.class)); 34 | 35 | Intent intent = new Intent("com.yk.dexdeapplication_devyk"); 36 | intent.setComponent(new ComponentName(getPackageName(), MyBroadCastReciver.class.getName 37 | ())); 38 | 39 | sendBroadcast(intent); 40 | 41 | getContentResolver().delete(Uri.parse("content://com.yk.dexdeapplication.MyProvider"), null, 42 | null); 43 | 44 | 45 | byte[] src = new byte[]{1,2,3,4}; 46 | byte[] dest = new byte[10]; 47 | 48 | System.arraycopy(src,0,dest,0,src.length); 49 | Log.d("",""); 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/yk/dexdeapplication/MyBroadCastReciver.java: -------------------------------------------------------------------------------- 1 | package com.yk.dexdeapplication; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.util.Log; 7 | 8 | 9 | public class MyBroadCastReciver extends BroadcastReceiver { 10 | 11 | 12 | @Override 13 | public void onReceive(Context context, Intent intent) { 14 | Log.i("DevYK", "reciver:" + context); 15 | Log.i("DevYK","reciver:" + context.getApplicationContext()); 16 | Log.i("DevYK","reciver:" + context.getApplicationInfo().className); 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/yk/dexdeapplication/MyProvider.java: -------------------------------------------------------------------------------- 1 | package com.yk.dexdeapplication; 2 | 3 | import android.content.ContentProvider; 4 | import android.content.ContentValues; 5 | import android.database.Cursor; 6 | import android.net.Uri; 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.Nullable; 9 | import android.util.Log; 10 | 11 | 12 | 13 | public class MyProvider extends ContentProvider { 14 | 15 | @Override 16 | public boolean onCreate() { 17 | Log.i("DevYK", "provider onCreate:" + getContext()); 18 | Log.i("DevYK", "provider onCreate:" + getContext().getApplicationContext()); 19 | Log.i("DevYK", "provider onCreate:" + getContext().getApplicationInfo().className); 20 | return true; 21 | } 22 | 23 | @Nullable 24 | @Override 25 | public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String 26 | selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { 27 | return null; 28 | } 29 | 30 | @Nullable 31 | @Override 32 | public String getType(@NonNull Uri uri) { 33 | return null; 34 | } 35 | 36 | @Nullable 37 | @Override 38 | public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { 39 | return null; 40 | } 41 | 42 | @Override 43 | public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] 44 | selectionArgs) { 45 | Log.i("DevYK", "provider delete:" + getContext()); 46 | return 0; 47 | } 48 | 49 | @Override 50 | public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String 51 | selection, @Nullable String[] selectionArgs) { 52 | return 0; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/yk/dexdeapplication/MyService.java: -------------------------------------------------------------------------------- 1 | package com.yk.dexdeapplication; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.os.IBinder; 6 | import android.support.annotation.Nullable; 7 | import android.util.Log; 8 | 9 | 10 | 11 | public class MyService extends Service { 12 | 13 | 14 | @Nullable 15 | @Override 16 | public IBinder onBind(Intent intent) { 17 | return null; 18 | } 19 | 20 | @Override 21 | public void onCreate() { 22 | super.onCreate(); 23 | Log.i("DevYK", "service:" + getApplication()); 24 | Log.i("DevYK", "service:" + getApplicationContext()); 25 | Log.i("DevYK", "service:" + getApplicationInfo().className); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/DexEncryptionDecryption/43453801ee30ec1e0dfe6b1ac79b94fb7867f7bf/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/DexEncryptionDecryption/43453801ee30ec1e0dfe6b1ac79b94fb7867f7bf/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/DexEncryptionDecryption/43453801ee30ec1e0dfe6b1ac79b94fb7867f7bf/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/DexEncryptionDecryption/43453801ee30ec1e0dfe6b1ac79b94fb7867f7bf/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/DexEncryptionDecryption/43453801ee30ec1e0dfe6b1ac79b94fb7867f7bf/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/DexEncryptionDecryption/43453801ee30ec1e0dfe6b1ac79b94fb7867f7bf/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/DexEncryptionDecryption/43453801ee30ec1e0dfe6b1ac79b94fb7867f7bf/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/DexEncryptionDecryption/43453801ee30ec1e0dfe6b1ac79b94fb7867f7bf/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/DexEncryptionDecryption/43453801ee30ec1e0dfe6b1ac79b94fb7867f7bf/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/DexEncryptionDecryption/43453801ee30ec1e0dfe6b1ac79b94fb7867f7bf/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DexDEApplication 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/yk/dexdeapplication/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.yk.dexdeapplication; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | 18 | 19 | @Test 20 | public void testClassLoad(){ 21 | String path = getClass().getClassLoader().toString(); 22 | System.out.printf("dex load :" + path); 23 | 24 | 25 | } 26 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | jcenter() 7 | 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.4.0' 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | 15 | 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/DexEncryptionDecryption/43453801ee30ec1e0dfe6b1ac79b94fb7867f7bf/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed May 29 21:16:22 GMT+08:00 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /proxy_core/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /proxy_core/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 21 10 | targetSdkVersion 28 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | 30 | implementation 'com.android.support:appcompat-v7:28.0.0' 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 33 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 34 | } 35 | -------------------------------------------------------------------------------- /proxy_core/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /proxy_core/src/androidTest/java/com/example/proxy_core/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.example.proxy_core; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.example.proxy_core.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /proxy_core/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /proxy_core/src/main/java/com/example/proxy_core/CrashHandler.java: -------------------------------------------------------------------------------- 1 | package com.example.proxy_core; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageInfo; 5 | import android.content.pm.PackageManager; 6 | import android.content.pm.PackageManager.NameNotFoundException; 7 | import android.os.Build; 8 | import android.os.Environment; 9 | import android.util.Log; 10 | 11 | import java.io.File; 12 | import java.io.FileOutputStream; 13 | import java.io.PrintWriter; 14 | import java.io.StringWriter; 15 | import java.io.Writer; 16 | import java.lang.Thread.UncaughtExceptionHandler; 17 | import java.lang.reflect.Field; 18 | import java.text.DateFormat; 19 | import java.text.SimpleDateFormat; 20 | import java.util.Date; 21 | import java.util.HashMap; 22 | import java.util.Map; 23 | 24 | /** 25 | * UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告. 26 | * 27 | * @author user 28 | */ 29 | public class CrashHandler implements UncaughtExceptionHandler { 30 | 31 | public static final String TAG = "CrashHandler"; 32 | 33 | //系统默认的UncaughtException处理类 34 | private UncaughtExceptionHandler mDefaultHandler; 35 | //CrashHandler实例 36 | private static CrashHandler INSTANCE = new CrashHandler(); 37 | //程序的Context对象 38 | private Context mContext; 39 | //用来存储设备信息和异常信息 40 | private Map infos = new HashMap(); 41 | 42 | //用于格式化日期,作为日志文件名的一部分 43 | private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); 44 | 45 | // 日志地址 46 | private String logPath; 47 | private String dir; 48 | private ICrashHandlerListener iCrashHandlerListener; 49 | 50 | /** 51 | * 保证只有一个CrashHandler实例 52 | */ 53 | private CrashHandler() { 54 | } 55 | 56 | /** 57 | * 获取CrashHandler实例 ,单例模式 58 | */ 59 | public static CrashHandler getInstance() { 60 | return INSTANCE; 61 | } 62 | 63 | /** 64 | * 初始化 65 | * 66 | * @param context 67 | */ 68 | public CrashHandler init(Context context) { 69 | mContext = context; 70 | //获取系统默认的UncaughtException处理器 71 | mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); 72 | //设置该CrashHandler为程序的默认处理器 73 | Thread.setDefaultUncaughtExceptionHandler(this); 74 | return this; 75 | 76 | } 77 | 78 | /** 79 | * 当UncaughtException发生时会转入该函数来处理 80 | */ 81 | @Override 82 | public void uncaughtException(Thread thread, final Throwable ex) { 83 | if (!handleException(ex) && mDefaultHandler != null) { 84 | //如果用户没有处理则让系统默认的异常处理器来处理 85 | mDefaultHandler.uncaughtException(thread, ex); 86 | } else { 87 | /* try { 88 | ArmsUtils.obtainAppComponentFromContext(mContext).appManager().killAll(); 89 | RestartAPPTool.restartAPP(mContext); 90 | } catch (Exception e) { 91 | e.printStackTrace(); 92 | } 93 | //重启APP 94 | //打印日志地址 95 | LogUtils.debugInfo(TAG,ex.getMessage()); 96 | Message message = new Message(); 97 | message.what = AppManager.APP_EXIT; 98 | AppManager.post(message); 99 | *//**杀死整个进程**//* 100 | android.os.Process.killProcess(android.os.Process.myPid());*/ 101 | } 102 | } 103 | 104 | 105 | private void sendMail(Throwable message) { 106 | 107 | } 108 | 109 | /** 110 | * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成. 111 | * 112 | * @param ex 113 | * @return true:如果处理了该异常信息;否则返回false. 114 | */ 115 | private boolean handleException(final Throwable ex) { 116 | if (ex == null) { 117 | return false; 118 | } 119 | new Thread(){ 120 | @Override 121 | public void run() { 122 | try { 123 | Thread.sleep(2000); 124 | } catch (InterruptedException e) { 125 | e.printStackTrace(); 126 | } 127 | 128 | } 129 | }.start(); 130 | 131 | Log.e("异常是=============:", ex.getMessage()); 132 | //收集设备参数信息 133 | collectDeviceInfo(mContext); 134 | //保存日志文件 135 | String s = saveCrashInfo2File(ex); 136 | 137 | return true; 138 | } 139 | 140 | /** 141 | * 收集设备参数信息 142 | * 143 | * @param ctx 144 | */ 145 | public void collectDeviceInfo(Context ctx) { 146 | try { 147 | PackageManager pm = ctx.getPackageManager(); 148 | PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES); 149 | if (pi != null) { 150 | String versionName = pi.versionName == null ? "null" : pi.versionName; 151 | String versionCode = pi.versionCode + ""; 152 | infos.put("versionName", versionName); 153 | infos.put("versionCode", versionCode); 154 | } 155 | } catch (NameNotFoundException e) { 156 | Log.e(TAG, "an error occured when collect package info", e); 157 | } 158 | Field[] fields = Build.class.getDeclaredFields(); 159 | for (Field field : fields) { 160 | try { 161 | field.setAccessible(true); 162 | infos.put(field.getName(), field.get(null).toString()); 163 | Log.d(TAG, field.getName() + " : " + field.get(null)); 164 | } catch (Exception e) { 165 | Log.e(TAG, "an error occured when collect crash info", e); 166 | } 167 | } 168 | } 169 | 170 | /** 171 | * 保存错误信息到文件中 172 | * 173 | * @param ex 174 | * @return 返回文件名称, 便于将文件传送到服务器 175 | */ 176 | private String saveCrashInfo2File(Throwable ex) { 177 | StringBuffer sb = new StringBuffer(); 178 | for (Map.Entry entry : infos.entrySet()) { 179 | String key = entry.getKey(); 180 | String value = entry.getValue(); 181 | sb.append(key + "=" + value + "\n"); 182 | } 183 | 184 | Writer writer = new StringWriter(); 185 | PrintWriter printWriter = new PrintWriter(writer); 186 | ex.printStackTrace(printWriter); 187 | Throwable cause = ex.getCause(); 188 | while (cause != null) { 189 | cause.printStackTrace(printWriter); 190 | cause = cause.getCause(); 191 | } 192 | printWriter.close(); 193 | String result = writer.toString(); 194 | sb.append(result); 195 | Log.e(TAG+"错误日志----",sb.toString()); 196 | try { 197 | long timestamp = System.currentTimeMillis(); 198 | String time = formatter.format(new Date()); 199 | String fileName = "BeiJing" + time + "-" + timestamp + ".txt"; 200 | if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 201 | String path = Environment.getExternalStorageDirectory() + "/yangkun/demo/"; 202 | File dir = new File(path); 203 | if (!dir.exists()) { 204 | dir.mkdirs(); 205 | } 206 | FileOutputStream fos = new FileOutputStream(path + fileName); 207 | logPath = path + fileName; 208 | fos.write(sb.toString().getBytes()); 209 | fos.close(); 210 | } 211 | 212 | if (iCrashHandlerListener != null) 213 | iCrashHandlerListener.onCrash(sb.toString()); 214 | 215 | return fileName; 216 | } catch (Exception e) { 217 | Log.e(TAG, "an error occured while writing file...", e); 218 | } 219 | return null; 220 | } 221 | 222 | 223 | public interface ICrashHandlerListener{ 224 | void onCrash(String crash); 225 | } 226 | 227 | public void setCrashHandlerListener(ICrashHandlerListener iCrashHandlerListener){ 228 | this.iCrashHandlerListener = iCrashHandlerListener; 229 | } 230 | } -------------------------------------------------------------------------------- /proxy_core/src/main/java/com/example/proxy_core/EncryptUtil.java: -------------------------------------------------------------------------------- 1 | package com.example.proxy_core; 2 | 3 | 4 | import java.io.UnsupportedEncodingException; 5 | import java.security.InvalidKeyException; 6 | import java.security.NoSuchAlgorithmException; 7 | 8 | import javax.crypto.BadPaddingException; 9 | import javax.crypto.Cipher; 10 | import javax.crypto.IllegalBlockSizeException; 11 | import javax.crypto.NoSuchPaddingException; 12 | import javax.crypto.spec.SecretKeySpec; 13 | 14 | /** 15 | * @Description: AES算法封装 16 | */ 17 | public class EncryptUtil { 18 | 19 | /** 20 | * 加密算法 21 | */ 22 | private static final String ENCRY_ALGORITHM = "AES"; 23 | 24 | 25 | 26 | /** 27 | * 加密算法/加密模式/填充类型 28 | * 本例采用AES加密,ECB加密模式,PKCS5Padding填充 29 | */ 30 | private static final String CIPHER_MODE = "AES/ECB/PKCS5Padding"; 31 | 32 | public static final byte[] ivBytes = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 33 | 34 | /** 35 | * 设置iv偏移量 36 | * 本例采用ECB加密模式,不需要设置iv偏移量 37 | */ 38 | private static final String IV_ = null; 39 | 40 | /** 41 | * 设置加密字符集 42 | * 本例采用 UTF-8 字符集 43 | */ 44 | private static final String CHARACTER = "UTF-8"; 45 | 46 | /** 47 | * 设置加密密码处理长度。 48 | * 不足此长度补0; 49 | */ 50 | private static final int PWD_SIZE = 16; 51 | 52 | /** 53 | * 密码处理方法 54 | * 如果加解密出问题, 55 | * 请先查看本方法,排除密码长度不足补"0",导致密码不一致 56 | * @param password 待处理的密码 57 | * @return 58 | * @throws UnsupportedEncodingException 59 | */ 60 | private static byte[] pwdHandler(String password) throws UnsupportedEncodingException { 61 | byte[] data = null; 62 | if (password == null) { 63 | password = ""; 64 | } 65 | StringBuffer sb = new StringBuffer(PWD_SIZE); 66 | sb.append(password); 67 | while (sb.length() < PWD_SIZE) { 68 | sb.append("0"); 69 | } 70 | if (sb.length() > PWD_SIZE) { 71 | sb.setLength(PWD_SIZE); 72 | } 73 | 74 | data = sb.toString().getBytes("UTF-8"); 75 | 76 | return data; 77 | } 78 | 79 | //======================>原始加密<====================== 80 | 81 | /** 82 | * 原始加密 83 | * @param clearTextBytes 明文字节数组,待加密的字节数组 84 | * @param pwdBytes 加密密码字节数组 85 | * @return 返回加密后的密文字节数组,加密错误返回null 86 | */ 87 | public static byte[] encrypt(byte[] clearTextBytes, byte[] pwdBytes) { 88 | try { 89 | // 1 获取加密密钥 90 | SecretKeySpec keySpec = new SecretKeySpec(pwdBytes, ENCRY_ALGORITHM); 91 | 92 | // 2 获取Cipher实例 93 | Cipher cipher = Cipher.getInstance(CIPHER_MODE); 94 | 95 | // 查看数据块位数 默认为16(byte) * 8 =128 bit 96 | // System.out.println("数据块位数(byte):" + cipher.getBlockSize()); 97 | 98 | // 3 初始化Cipher实例。设置执行模式以及加密密钥 99 | cipher.init(Cipher.ENCRYPT_MODE, keySpec); 100 | 101 | // 4 执行 102 | byte[] cipherTextBytes = cipher.doFinal(clearTextBytes); 103 | 104 | // 5 返回密文字符集 105 | return cipherTextBytes; 106 | 107 | } catch (NoSuchPaddingException e) { 108 | e.printStackTrace(); 109 | } catch (NoSuchAlgorithmException e) { 110 | e.printStackTrace(); 111 | } catch (BadPaddingException e) { 112 | e.printStackTrace(); 113 | } catch (IllegalBlockSizeException e) { 114 | e.printStackTrace(); 115 | } catch (InvalidKeyException e) { 116 | e.printStackTrace(); 117 | } catch (Exception e) { 118 | e.printStackTrace(); 119 | } 120 | return null; 121 | } 122 | 123 | /** 124 | * 原始解密 125 | * @param cipherTextBytes 密文字节数组,待解密的字节数组 126 | * @param pwdBytes 解密密码字节数组 127 | * @return 返回解密后的明文字节数组,解密错误返回null 128 | */ 129 | public static byte[] decrypt(byte[] cipherTextBytes, byte[] pwdBytes) { 130 | 131 | try { 132 | // 1 获取解密密钥 133 | SecretKeySpec keySpec = new SecretKeySpec(pwdBytes, ENCRY_ALGORITHM); 134 | 135 | // 2 获取Cipher实例 136 | Cipher cipher = Cipher.getInstance(CIPHER_MODE); 137 | 138 | // 查看数据块位数 默认为16(byte) * 8 =128 bit 139 | // System.out.println("数据块位数(byte):" + cipher.getBlockSize()); 140 | 141 | // 3 初始化Cipher实例。设置执行模式以及加密密钥 142 | cipher.init(Cipher.DECRYPT_MODE, keySpec); 143 | 144 | // 4 执行 145 | byte[] clearTextBytes = cipher.doFinal(cipherTextBytes); 146 | 147 | // 5 返回明文字符集 148 | return clearTextBytes; 149 | 150 | } catch (NoSuchAlgorithmException e) { 151 | e.printStackTrace(); 152 | } catch (InvalidKeyException e) { 153 | e.printStackTrace(); 154 | } catch (NoSuchPaddingException e) { 155 | e.printStackTrace(); 156 | } catch (BadPaddingException e) { 157 | e.printStackTrace(); 158 | } catch (IllegalBlockSizeException e) { 159 | e.printStackTrace(); 160 | } catch (Exception e) { 161 | e.printStackTrace(); 162 | } 163 | // 解密错误 返回null 164 | return null; 165 | } 166 | 167 | 168 | //======================>HEX<====================== 169 | 170 | /** 171 | * HEX加密 172 | * @param clearText 明文,待加密的内容 173 | * @param password 密码,加密的密码 174 | * @return 返回密文,加密后得到的内容。加密错误返回null 175 | */ 176 | public static String encryptHex(String clearText, String password) { 177 | try { 178 | // 1 获取加密密文字节数组 179 | byte[] cipherTextBytes = encrypt(clearText.getBytes(CHARACTER), pwdHandler(password)); 180 | 181 | // 2 对密文字节数组进行 转换为 HEX输出密文 182 | String cipherText = byte2hex(cipherTextBytes); 183 | 184 | // 3 返回 HEX输出密文 185 | return cipherText; 186 | } catch (UnsupportedEncodingException e) { 187 | e.printStackTrace(); 188 | } catch (Exception e) { 189 | e.printStackTrace(); 190 | } 191 | // 加密错误返回null 192 | return null; 193 | } 194 | 195 | /** 196 | * HEX解密 197 | * @param cipherText 密文,带解密的内容 198 | * @param password 密码,解密的密码 199 | * @return 返回明文,解密后得到的内容。解密错误返回null 200 | */ 201 | public static String decryptHex(String cipherText, String password) { 202 | try { 203 | // 1 将HEX输出密文 转为密文字节数组 204 | byte[] cipherTextBytes = hex2byte(cipherText); 205 | 206 | // 2 将密文字节数组进行解密 得到明文字节数组 207 | byte[] clearTextBytes = decrypt(cipherTextBytes, pwdHandler(password)); 208 | 209 | // 3 根据 CHARACTER 转码,返回明文字符串 210 | return new String(clearTextBytes, CHARACTER); 211 | } catch (UnsupportedEncodingException e) { 212 | e.printStackTrace(); 213 | } catch (Exception e) { 214 | e.printStackTrace(); 215 | } 216 | // 解密错误返回null 217 | return null; 218 | } 219 | 220 | /*字节数组转成16进制字符串 */ 221 | public static String byte2hex(byte[] bytes) { // 一个字节的数, 222 | StringBuffer sb = new StringBuffer(bytes.length * 2); 223 | String tmp = ""; 224 | for (int n = 0; n < bytes.length; n++) { 225 | // 整数转成十六进制表示 226 | tmp = (Integer.toHexString(bytes[n] & 0XFF)); 227 | if (tmp.length() == 1) { 228 | sb.append("0"); 229 | } 230 | sb.append(tmp); 231 | } 232 | return sb.toString().toUpperCase(); // 转成大写 233 | } 234 | 235 | /*将hex字符串转换成字节数组 */ 236 | private static byte[] hex2byte(String str) { 237 | if (str == null || str.length() < 2) { 238 | return new byte[0]; 239 | } 240 | str = str.toLowerCase(); 241 | int l = str.length() / 2; 242 | byte[] result = new byte[l]; 243 | for (int i = 0; i < l; ++i) { 244 | String tmp = str.substring(2 * i, 2 * i + 2); 245 | result[i] = (byte) (Integer.parseInt(tmp, 16) & 0xFF); 246 | } 247 | return result; 248 | } 249 | 250 | public static void main(String[] args) { 251 | String test = encryptHex("test", "1234567800000000"); 252 | System.out.println(test); 253 | 254 | System.out.println(decryptHex(test, "1234567800000000")); 255 | } 256 | } -------------------------------------------------------------------------------- /proxy_core/src/main/java/com/example/proxy_core/ProxyApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.proxy_core; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.content.pm.ApplicationInfo; 6 | import android.content.pm.PackageManager; 7 | import android.os.Bundle; 8 | import android.os.SystemClock; 9 | import android.text.TextUtils; 10 | import android.util.Log; 11 | import android.widget.Toast; 12 | 13 | import java.io.File; 14 | import java.io.FileOutputStream; 15 | import java.io.IOException; 16 | import java.lang.reflect.Array; 17 | import java.lang.reflect.Field; 18 | import java.lang.reflect.Method; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | public class ProxyApplication extends Application { 23 | 24 | //定义好解密后的文件的存放路径 25 | private String app_name; 26 | private String app_version; 27 | 28 | private String TAG = this.getClass().getSimpleName(); 29 | private long endTime; 30 | 31 | /** 32 | * ActivityThread创建Application之后调用的第一个方法 33 | * 可以在这个方法中进行解密,同时把dex交给android去加载 34 | */ 35 | @Override 36 | protected void attachBaseContext(Context base) { 37 | super.attachBaseContext(base); 38 | //用于在解密过程中如果出现异常,可以进行捕获 39 | CrashHandler.getInstance().init(base); 40 | long startTime = SystemClock.currentThreadTimeMillis(); 41 | 42 | //1. 获取用户填入的metadata 43 | getMetaData(); 44 | 45 | //2. 得到当前加密了的APK文件,也就是安装包 46 | // File apkFile=new File(Environment.getExternalStorageDirectory()+"/app-signed-aligned.apk"); 47 | File apkFile=new File(getApplicationInfo().sourceDir); 48 | 49 | //3. 把apk解压 app_name+"_"+app_version 目录中的内容需要 boot 权限才能用 50 | File versionDir =getDir("DevYK", Context.MODE_PRIVATE); 51 | File appDir=new File(versionDir,"app"); 52 | File dexDir=new File(appDir,"dexDir"); 53 | 54 | //4. 得到我们需要加载的Dex文件 55 | List dexFiles=new ArrayList<>(); 56 | //进行解密(最好做MD5文件校验) 57 | if(!dexDir.exists() || dexDir.list().length==0){ 58 | //把apk解压到appDir 59 | Zip.unZip(apkFile,appDir); 60 | //获取目录下所有的文件 61 | File[] files=appDir.listFiles(); 62 | for (File file : files) { 63 | String name=file.getName(); 64 | if(name.endsWith(".dex") && !TextUtils.equals(name,"classes.dex")){ 65 | try{ 66 | //读取文件内容 67 | byte[] bytes= ProxyUtils.getBytes(file); 68 | //5. 解密 dex 69 | byte[] decrypt = EncryptUtil.decrypt(bytes,EncryptUtil.ivBytes); 70 | //写到指定的目录 71 | FileOutputStream fos=new FileOutputStream(file); 72 | fos.write(decrypt); 73 | fos.flush(); 74 | fos.close(); 75 | dexFiles.add(file); 76 | 77 | }catch (Exception e){ 78 | e.printStackTrace(); 79 | } 80 | } 81 | } 82 | }else{ 83 | for (File file : dexDir.listFiles()) { 84 | dexFiles.add(file); 85 | } 86 | } 87 | 88 | try{ 89 | //6.把解密后的文件加载到系统 90 | loadDex(dexFiles,versionDir); 91 | 92 | endTime = SystemClock.currentThreadTimeMillis() - startTime; 93 | Log.d(TAG,"解密完成! 共耗时:" + endTime +" ms"); 94 | 95 | }catch (Exception e){ 96 | e.printStackTrace(); 97 | } 98 | 99 | 100 | } 101 | 102 | private void loadDex(List dexFiles, File versionDir) throws Exception{ 103 | //1.先从 ClassLoader 中获取 pathList 的变量 104 | Field pathListField = ProxyUtils.findField(getClassLoader(), "pathList"); 105 | //1.1 得到 DexPathList 类 106 | Object pathList = pathListField.get(getClassLoader()); 107 | //1.2 从 DexPathList 类中拿到 dexElements 变量 108 | Field dexElementsField= ProxyUtils.findField(pathList,"dexElements"); 109 | //1.3 拿到已加载的 dex 数组 110 | Object[] dexElements=(Object[])dexElementsField.get(pathList); 111 | 112 | //2. 反射到初始化 dexElements 的方法,也就是得到加载 dex 到系统的方法 113 | Method makeDexElements= ProxyUtils.findMethod(pathList,"makePathElements",List.class,File.class,List.class); 114 | //2.1 实例化一个 集合 makePathElements 需要用到 115 | ArrayList suppressedExceptions = new ArrayList(); 116 | //2.2 反射执行 makePathElements 函数,把已解码的 dex 加载到系统,不然是打不开 dex 的,会导致 crash 117 | Object[] addElements=(Object[])makeDexElements.invoke(pathList,dexFiles,versionDir,suppressedExceptions); 118 | 119 | //3. 实例化一个新数组,用于将当前加载和已加载的 dex 合并成一个新的数组 120 | Object[] newElements= (Object[])Array.newInstance(dexElements.getClass().getComponentType(),dexElements.length+addElements.length); 121 | //3.1 将系统中的已经加载的 dex 放入 newElements 中 122 | System.arraycopy(dexElements,0,newElements,0,dexElements.length); 123 | //3.2 将解密后已加载的 dex 放入新数组中 124 | System.arraycopy(addElements,0,newElements,dexElements.length,addElements.length); 125 | 126 | //4. 将合并的新数组重新设置给 DexPathList的 dexElements 127 | dexElementsField.set(pathList,newElements); 128 | } 129 | 130 | private void getMetaData() { 131 | try{ 132 | ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo( 133 | getPackageName(), PackageManager.GET_META_DATA); 134 | Bundle metaData=applicationInfo.metaData; 135 | if(null!=metaData){ 136 | if(metaData.containsKey("app_name")){ 137 | app_name=metaData.getString("app_name"); 138 | } 139 | if(metaData.containsKey("app_version")){ 140 | app_version=metaData.getString("app_version"); 141 | } 142 | } 143 | 144 | }catch(Exception e){ 145 | e.printStackTrace(); 146 | } 147 | } 148 | 149 | /** 150 | * 开始替换application 151 | */ 152 | @Override 153 | public void onCreate() { 154 | super.onCreate(); 155 | try { 156 | Toast.makeText(getApplicationContext(),"解密完成! 共耗时:" + endTime +" ms" ,Toast.LENGTH_LONG).show(); 157 | bindRealApplicatin(); 158 | } catch (Exception e) { 159 | e.printStackTrace(); 160 | } 161 | } 162 | 163 | 164 | boolean isBindReal; 165 | Application delegate; 166 | 167 | 168 | private void bindRealApplicatin() throws Exception { 169 | if (isBindReal) { 170 | return; 171 | } 172 | if (TextUtils.isEmpty(app_name)) { 173 | return; 174 | } 175 | //1. 得到 attachBaseContext(context) 传入的上下文 ContextImpl 176 | Context baseContext = getBaseContext(); 177 | //2. 拿到真实 APK APPlication 的 class 178 | Class delegateClass = Class.forName(app_name); 179 | //3. 反射实例化,其实 Android 中四大组件都是这样实例化的。 180 | delegate = (Application) delegateClass.newInstance(); 181 | 182 | //3.1 得到 Application attach() 方法 也就是最先初始化的 183 | Method attach = Application.class.getDeclaredMethod("attach", Context.class); 184 | attach.setAccessible(true); 185 | //执行 Application#attach(Context) 186 | //3.2 将真实的 Application 和假的 Application 进行替换。想当于自己手动控制 真实的 Application 生命周期 187 | attach.invoke(delegate, baseContext); 188 | 189 | 190 | // ContextImpl---->mOuterContext(app) 通过Application的attachBaseContext回调参数获取 191 | //4. 拿到 Context 的实现类 192 | Class contextImplClass = Class.forName("android.app.ContextImpl"); 193 | //4.1 获取 mOuterContext Context 属性 194 | Field mOuterContextField = contextImplClass.getDeclaredField("mOuterContext"); 195 | mOuterContextField.setAccessible(true); 196 | //4.2 将真实的 Application 交于 Context 中。这个根据源码执行,实例化 Application 下一个就行调用 setOuterContext 函数,所以需要绑定 Context 197 | // app = mActivityThread.mInstrumentation.newApplication( 198 | // cl, appClass, appContext); 199 | // appContext.setOuterContext(app); 200 | mOuterContextField.set(baseContext, delegate); 201 | 202 | // ActivityThread--->mAllApplications(ArrayList) ContextImpl的mMainThread属性 203 | //5. 拿到 ActivityThread 变量 204 | Field mMainThreadField = contextImplClass.getDeclaredField("mMainThread"); 205 | mMainThreadField.setAccessible(true); 206 | //5.1 拿到 ActivityThread 对象 207 | Object mMainThread = mMainThreadField.get(baseContext); 208 | 209 | // ActivityThread--->>mInitialApplication 210 | //6. 反射拿到 ActivityThread class 211 | Class activityThreadClass=Class.forName("android.app.ActivityThread"); 212 | //6.1 得到当前加载的 Application 类 213 | Field mInitialApplicationField = activityThreadClass.getDeclaredField("mInitialApplication"); 214 | mInitialApplicationField.setAccessible(true); 215 | //6.2 将 ActivityThread 中的 Applicaiton 替换为 真实的 Application 可以用于接收相应的声明周期和一些调用等 216 | mInitialApplicationField.set(mMainThread,delegate); 217 | 218 | 219 | // ActivityThread--->mAllApplications(ArrayList) ContextImpl的mMainThread属性 220 | //7. 拿到 ActivityThread 中所有的 Application 集合对象,这里是多进程的场景 221 | Field mAllApplicationsField = activityThreadClass.getDeclaredField("mAllApplications"); 222 | mAllApplicationsField.setAccessible(true); 223 | ArrayList mAllApplications =(ArrayList) mAllApplicationsField.get(mMainThread); 224 | //7.1 删除 ProxyApplication 225 | mAllApplications.remove(this); 226 | //7.2 添加真实的 Application 227 | mAllApplications.add(delegate); 228 | 229 | // LoadedApk------->mApplication ContextImpl的mPackageInfo属性 230 | //8. 从 ContextImpl 拿到 mPackageInfo 变量 231 | Field mPackageInfoField = contextImplClass.getDeclaredField("mPackageInfo"); 232 | mPackageInfoField.setAccessible(true); 233 | //8.1 拿到 LoadedApk 对象 234 | Object mPackageInfo=mPackageInfoField.get(baseContext); 235 | 236 | //9 反射得到 LoadedApk 对象 237 | // @Override 238 | // public Context getApplicationContext() { 239 | // return (mPackageInfo != null) ? 240 | // mPackageInfo.getApplication() : mMainThread.getApplication(); 241 | // } 242 | Class loadedApkClass=Class.forName("android.app.LoadedApk"); 243 | Field mApplicationField = loadedApkClass.getDeclaredField("mApplication"); 244 | mApplicationField.setAccessible(true); 245 | //9.1 将 LoadedApk 中的 Application 替换为 真实的 Application 246 | mApplicationField.set(mPackageInfo,delegate); 247 | 248 | //修改ApplicationInfo className LooadedApk 249 | 250 | //10. 拿到 LoadApk 中的 mApplicationInfo 变量 251 | Field mApplicationInfoField = loadedApkClass.getDeclaredField("mApplicationInfo"); 252 | mApplicationInfoField.setAccessible(true); 253 | //10.1 根据变量反射得到 ApplicationInfo 对象 254 | ApplicationInfo mApplicationInfo = (ApplicationInfo)mApplicationInfoField.get(mPackageInfo); 255 | //10.2 将我们真实的 APPlication ClassName 名称赋值于它 256 | mApplicationInfo.className=app_name; 257 | 258 | //11. 执行 代理 Application onCreate 声明周期 259 | delegate.onCreate(); 260 | 261 | //解码完成 262 | isBindReal = true; 263 | } 264 | 265 | /** 266 | * 让代码走入if中的第三段中 267 | * @return 268 | */ 269 | @Override 270 | public String getPackageName() { 271 | if(!TextUtils.isEmpty(app_name)){ 272 | return ""; 273 | } 274 | return super.getPackageName(); 275 | } 276 | 277 | /** 278 | * 这个函数是如果在 AndroidManifest.xml 中定义了 ContentProvider 那么就会执行此处 : installProvider,简介调用该函数 279 | * @param packageName 280 | * @param flags 281 | * @return 282 | * @throws PackageManager.NameNotFoundException 283 | */ 284 | @Override 285 | public Context createPackageContext(String packageName, int flags) throws PackageManager.NameNotFoundException { 286 | if(TextUtils.isEmpty(app_name)){ 287 | return super.createPackageContext(packageName, flags); 288 | } 289 | try { 290 | bindRealApplicatin(); 291 | } catch (Exception e) { 292 | e.printStackTrace(); 293 | } 294 | return delegate; 295 | 296 | } 297 | } 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | -------------------------------------------------------------------------------- /proxy_core/src/main/java/com/example/proxy_core/ProxyUtils.java: -------------------------------------------------------------------------------- 1 | package com.example.proxy_core; 2 | 3 | import java.io.File; 4 | import java.io.RandomAccessFile; 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.Method; 7 | import java.util.Arrays; 8 | 9 | 10 | public class ProxyUtils { 11 | 12 | /** 13 | * 读取文件 14 | * @param file 15 | * @return 16 | * @throws Exception 17 | */ 18 | public static byte[] getBytes(File file) throws Exception { 19 | RandomAccessFile r = new RandomAccessFile(file, "r"); 20 | byte[] buffer = new byte[(int) r.length()]; 21 | r.readFully(buffer); 22 | r.close(); 23 | return buffer; 24 | } 25 | 26 | /** 27 | * 反射获得 指定对象(当前-》父类-》父类...)中的 成员属性 28 | * @param instance 29 | * @param name 30 | * @return 31 | * @throws NoSuchFieldException 32 | */ 33 | public static Field findField(Object instance, String name) throws NoSuchFieldException { 34 | Class clazz = instance.getClass(); 35 | //反射获得 36 | while (clazz != null) { 37 | try { 38 | Field field = clazz.getDeclaredField(name); 39 | //如果无法访问 设置为可访问 40 | if (!field.isAccessible()) { 41 | field.setAccessible(true); 42 | } 43 | return field; 44 | } catch (NoSuchFieldException e) { 45 | //如果找不到往父类找 46 | clazz = clazz.getSuperclass(); 47 | } 48 | } 49 | throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass()); 50 | } 51 | 52 | 53 | /** 54 | * 反射获得 指定对象(当前-》父类-》父类...)中的 函数 55 | * @param instance 56 | * @param name 57 | * @param parameterTypes 58 | * @return 59 | * @throws NoSuchMethodException 60 | */ 61 | public static Method findMethod(Object instance, String name, Class... parameterTypes) 62 | throws NoSuchMethodException { 63 | Class clazz = instance.getClass(); 64 | while (clazz != null) { 65 | try { 66 | Method method = clazz.getDeclaredMethod(name, parameterTypes); 67 | if (!method.isAccessible()) { 68 | method.setAccessible(true); 69 | } 70 | return method; 71 | } catch (NoSuchMethodException e) { 72 | //如果找不到往父类找 73 | clazz = clazz.getSuperclass(); 74 | } 75 | } 76 | throw new NoSuchMethodException("Method " + name + " with parameters " + Arrays.asList 77 | (parameterTypes) + " not found in " + instance.getClass()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /proxy_core/src/main/java/com/example/proxy_core/Zip.java: -------------------------------------------------------------------------------- 1 | package com.example.proxy_core; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileOutputStream; 6 | import java.io.InputStream; 7 | import java.util.Enumeration; 8 | import java.util.zip.CRC32; 9 | import java.util.zip.CheckedOutputStream; 10 | import java.util.zip.ZipEntry; 11 | import java.util.zip.ZipFile; 12 | import java.util.zip.ZipOutputStream; 13 | 14 | 15 | public class Zip { 16 | 17 | private static void deleteFile(File file){ 18 | if (file.isDirectory()){ 19 | File[] files = file.listFiles(); 20 | for (File f: files) { 21 | deleteFile(f); 22 | } 23 | }else{ 24 | file.delete(); 25 | } 26 | } 27 | 28 | /** 29 | * 解压zip文件至dir目录 30 | * @param zip 31 | * @param dir 32 | */ 33 | public static void unZip(File zip, File dir) { 34 | try { 35 | deleteFile(dir); 36 | ZipFile zipFile = new ZipFile(zip); 37 | //zip文件中每一个条目 38 | Enumeration entries = zipFile.entries(); 39 | //遍历 40 | while (entries.hasMoreElements()) { 41 | ZipEntry zipEntry = entries.nextElement(); 42 | //zip中 文件/目录名 43 | String name = zipEntry.getName(); 44 | //原来的签名文件 不需要了 45 | if (name.equals("META-INF/CERT.RSA") || name.equals("META-INF/CERT.SF") || name 46 | .equals("META-INF/MANIFEST.MF")) { 47 | continue; 48 | } 49 | //空目录不管 50 | if (!zipEntry.isDirectory()) { 51 | File file = new File(dir, name); 52 | //创建目录 53 | if (!file.getParentFile().exists()) { 54 | file.getParentFile().mkdirs(); 55 | } 56 | //写文件 57 | FileOutputStream fos = new FileOutputStream(file); 58 | InputStream is = zipFile.getInputStream(zipEntry); 59 | byte[] buffer = new byte[2048]; 60 | int len; 61 | while ((len = is.read(buffer)) != -1) { 62 | fos.write(buffer, 0, len); 63 | } 64 | is.close(); 65 | fos.close(); 66 | } 67 | } 68 | zipFile.close(); 69 | } catch (Exception e) { 70 | e.printStackTrace(); 71 | } 72 | } 73 | 74 | /** 75 | * 压缩目录为zip 76 | * @param dir 待压缩目录 77 | * @param zip 输出的zip文件 78 | * @throws Exception 79 | */ 80 | public static void zip(File dir, File zip) throws Exception { 81 | zip.delete(); 82 | // 对输出文件做CRC32校验 83 | CheckedOutputStream cos = new CheckedOutputStream(new FileOutputStream( 84 | zip), new CRC32()); 85 | ZipOutputStream zos = new ZipOutputStream(cos); 86 | //压缩 87 | compress(dir, zos, ""); 88 | zos.flush(); 89 | zos.close(); 90 | } 91 | 92 | /** 93 | * 添加目录/文件 至zip中 94 | * @param srcFile 需要添加的目录/文件 95 | * @param zos zip输出流 96 | * @param basePath 递归子目录时的完整目录 如 lib/x86 97 | * @throws Exception 98 | */ 99 | private static void compress(File srcFile, ZipOutputStream zos, 100 | String basePath) throws Exception { 101 | if (srcFile.isDirectory()) { 102 | File[] files = srcFile.listFiles(); 103 | for (File file : files) { 104 | // zip 递归添加目录中的文件 105 | compress(file, zos, basePath + srcFile.getName() + "/"); 106 | } 107 | } else { 108 | compressFile(srcFile, zos, basePath); 109 | } 110 | } 111 | 112 | private static void compressFile(File file, ZipOutputStream zos, String dir) 113 | throws Exception { 114 | // temp/lib/x86/libdn_ssl.so 115 | String fullName = dir + file.getName(); 116 | // 需要去掉temp 117 | String[] fileNames = fullName.split("/"); 118 | //正确的文件目录名 (去掉了temp) 119 | StringBuffer sb = new StringBuffer(); 120 | if (fileNames.length > 1){ 121 | for (int i = 1;i 2 | proxy_core 3 | 4 | -------------------------------------------------------------------------------- /proxy_core/src/test/java/com/example/proxy_core/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.example.proxy_core; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /proxy_tools/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /proxy_tools/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 21 10 | targetSdkVersion 28 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | 30 | implementation 'com.android.support:appcompat-v7:28.0.0' 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 33 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 34 | } 35 | -------------------------------------------------------------------------------- /proxy_tools/dexjks.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/DexEncryptionDecryption/43453801ee30ec1e0dfe6b1ac79b94fb7867f7bf/proxy_tools/dexjks.jks -------------------------------------------------------------------------------- /proxy_tools/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /proxy_tools/src/main/java/com/example/proxy_tools/DexUtils.java: -------------------------------------------------------------------------------- 1 | package com.example.proxy_tools; 2 | 3 | import java.io.File; 4 | import java.io.RandomAccessFile; 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.Method; 7 | import java.util.Arrays; 8 | 9 | 10 | public class DexUtils { 11 | 12 | /** 13 | * 读取文件 14 | * @param file 15 | * @return 16 | * @throws Exception 17 | */ 18 | public static byte[] getBytes(File file) throws Exception { 19 | RandomAccessFile r = new RandomAccessFile(file, "r"); 20 | byte[] buffer = new byte[(int) r.length()]; 21 | r.readFully(buffer); 22 | r.close(); 23 | return buffer; 24 | } 25 | 26 | /** 27 | * 反射获得 指定对象(当前-》父类-》父类...)中的 成员属性 28 | * @param instance 29 | * @param name 30 | * @return 31 | * @throws NoSuchFieldException 32 | */ 33 | public static Field findField(Object instance, String name) throws NoSuchFieldException { 34 | Class clazz = instance.getClass(); 35 | //反射获得 36 | while (clazz != null) { 37 | try { 38 | Field field = clazz.getDeclaredField(name); 39 | //如果无法访问 设置为可访问 40 | if (!field.isAccessible()) { 41 | field.setAccessible(true); 42 | } 43 | return field; 44 | } catch (NoSuchFieldException e) { 45 | //如果找不到往父类找 46 | clazz = clazz.getSuperclass(); 47 | } 48 | } 49 | throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass()); 50 | } 51 | 52 | 53 | /** 54 | * 反射获得 指定对象(当前-》父类-》父类...)中的 函数 55 | * @param instance 56 | * @param name 57 | * @param parameterTypes 58 | * @return 59 | * @throws NoSuchMethodException 60 | */ 61 | public static Method findMethod(Object instance, String name, Class... parameterTypes) 62 | throws NoSuchMethodException { 63 | Class clazz = instance.getClass(); 64 | while (clazz != null) { 65 | try { 66 | Method method = clazz.getDeclaredMethod(name, parameterTypes); 67 | if (!method.isAccessible()) { 68 | method.setAccessible(true); 69 | } 70 | return method; 71 | } catch (NoSuchMethodException e) { 72 | //如果找不到往父类找 73 | clazz = clazz.getSuperclass(); 74 | } 75 | } 76 | throw new NoSuchMethodException("Method " + name + " with parameters " + Arrays.asList 77 | (parameterTypes) + " not found in " + instance.getClass()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /proxy_tools/src/main/java/com/example/proxy_tools/EncryptUtil.java: -------------------------------------------------------------------------------- 1 | package com.example.proxy_tools; 2 | 3 | 4 | import java.io.UnsupportedEncodingException; 5 | import java.security.InvalidKeyException; 6 | import java.security.NoSuchAlgorithmException; 7 | 8 | import javax.crypto.BadPaddingException; 9 | import javax.crypto.Cipher; 10 | import javax.crypto.IllegalBlockSizeException; 11 | import javax.crypto.NoSuchPaddingException; 12 | import javax.crypto.spec.SecretKeySpec; 13 | 14 | /** 15 | * @Description: AES算法封装 16 | */ 17 | public class EncryptUtil{ 18 | 19 | /** 20 | * 加密算法 21 | */ 22 | private static final String ENCRY_ALGORITHM = "AES"; 23 | 24 | 25 | 26 | /** 27 | * 加密算法/加密模式/填充类型 28 | * 本例采用AES加密,ECB加密模式,PKCS5Padding填充 29 | */ 30 | private static final String CIPHER_MODE = "AES/ECB/PKCS5Padding"; 31 | 32 | public static final byte[] ivBytes = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 33 | 34 | /** 35 | * 设置iv偏移量 36 | * 本例采用ECB加密模式,不需要设置iv偏移量 37 | */ 38 | private static final String IV_ = null; 39 | 40 | /** 41 | * 设置加密字符集 42 | * 本例采用 UTF-8 字符集 43 | */ 44 | private static final String CHARACTER = "UTF-8"; 45 | 46 | /** 47 | * 设置加密密码处理长度。 48 | * 不足此长度补0; 49 | */ 50 | private static final int PWD_SIZE = 16; 51 | 52 | /** 53 | * 密码处理方法 54 | * 如果加解密出问题, 55 | * 请先查看本方法,排除密码长度不足补"0",导致密码不一致 56 | * @param password 待处理的密码 57 | * @return 58 | * @throws UnsupportedEncodingException 59 | */ 60 | private static byte[] pwdHandler(String password) throws UnsupportedEncodingException { 61 | byte[] data = null; 62 | if (password == null) { 63 | password = ""; 64 | } 65 | StringBuffer sb = new StringBuffer(PWD_SIZE); 66 | sb.append(password); 67 | while (sb.length() < PWD_SIZE) { 68 | sb.append("0"); 69 | } 70 | if (sb.length() > PWD_SIZE) { 71 | sb.setLength(PWD_SIZE); 72 | } 73 | 74 | data = sb.toString().getBytes("UTF-8"); 75 | 76 | return data; 77 | } 78 | 79 | //======================>原始加密<====================== 80 | 81 | /** 82 | * 原始加密 83 | * @param clearTextBytes 明文字节数组,待加密的字节数组 84 | * @param pwdBytes 加密密码字节数组 85 | * @return 返回加密后的密文字节数组,加密错误返回null 86 | */ 87 | public static byte[] encrypt(byte[] clearTextBytes, byte[] pwdBytes) { 88 | try { 89 | // 1 获取加密密钥 90 | SecretKeySpec keySpec = new SecretKeySpec(pwdBytes, ENCRY_ALGORITHM); 91 | 92 | // 2 获取Cipher实例 93 | Cipher cipher = Cipher.getInstance(CIPHER_MODE); 94 | 95 | // 查看数据块位数 默认为16(byte) * 8 =128 bit 96 | // System.out.println("数据块位数(byte):" + cipher.getBlockSize()); 97 | 98 | // 3 初始化Cipher实例。设置执行模式以及加密密钥 99 | cipher.init(Cipher.ENCRYPT_MODE, keySpec); 100 | 101 | // 4 执行 102 | byte[] cipherTextBytes = cipher.doFinal(clearTextBytes); 103 | 104 | // 5 返回密文字符集 105 | return cipherTextBytes; 106 | 107 | } catch (NoSuchPaddingException e) { 108 | e.printStackTrace(); 109 | } catch (NoSuchAlgorithmException e) { 110 | e.printStackTrace(); 111 | } catch (BadPaddingException e) { 112 | e.printStackTrace(); 113 | } catch (IllegalBlockSizeException e) { 114 | e.printStackTrace(); 115 | } catch (InvalidKeyException e) { 116 | e.printStackTrace(); 117 | } catch (Exception e) { 118 | e.printStackTrace(); 119 | } 120 | return null; 121 | } 122 | 123 | /** 124 | * 原始解密 125 | * @param cipherTextBytes 密文字节数组,待解密的字节数组 126 | * @param pwdBytes 解密密码字节数组 127 | * @return 返回解密后的明文字节数组,解密错误返回null 128 | */ 129 | public static byte[] decrypt(byte[] cipherTextBytes, byte[] pwdBytes) { 130 | 131 | try { 132 | // 1 获取解密密钥 133 | SecretKeySpec keySpec = new SecretKeySpec(pwdBytes, ENCRY_ALGORITHM); 134 | 135 | // 2 获取Cipher实例 136 | Cipher cipher = Cipher.getInstance(CIPHER_MODE); 137 | 138 | // 查看数据块位数 默认为16(byte) * 8 =128 bit 139 | // System.out.println("数据块位数(byte):" + cipher.getBlockSize()); 140 | 141 | // 3 初始化Cipher实例。设置执行模式以及加密密钥 142 | cipher.init(Cipher.DECRYPT_MODE, keySpec); 143 | 144 | // 4 执行 145 | byte[] clearTextBytes = cipher.doFinal(cipherTextBytes); 146 | 147 | // 5 返回明文字符集 148 | return clearTextBytes; 149 | 150 | } catch (NoSuchAlgorithmException e) { 151 | e.printStackTrace(); 152 | } catch (InvalidKeyException e) { 153 | e.printStackTrace(); 154 | } catch (NoSuchPaddingException e) { 155 | e.printStackTrace(); 156 | } catch (BadPaddingException e) { 157 | e.printStackTrace(); 158 | } catch (IllegalBlockSizeException e) { 159 | e.printStackTrace(); 160 | } catch (Exception e) { 161 | e.printStackTrace(); 162 | } 163 | // 解密错误 返回null 164 | return null; 165 | } 166 | 167 | 168 | //======================>HEX<====================== 169 | 170 | /** 171 | * HEX加密 172 | * @param clearText 明文,待加密的内容 173 | * @param password 密码,加密的密码 174 | * @return 返回密文,加密后得到的内容。加密错误返回null 175 | */ 176 | public static String encryptHex(String clearText, String password) { 177 | try { 178 | // 1 获取加密密文字节数组 179 | byte[] cipherTextBytes = encrypt(clearText.getBytes(CHARACTER), pwdHandler(password)); 180 | 181 | // 2 对密文字节数组进行 转换为 HEX输出密文 182 | String cipherText = byte2hex(cipherTextBytes); 183 | 184 | // 3 返回 HEX输出密文 185 | return cipherText; 186 | } catch (UnsupportedEncodingException e) { 187 | e.printStackTrace(); 188 | } catch (Exception e) { 189 | e.printStackTrace(); 190 | } 191 | // 加密错误返回null 192 | return null; 193 | } 194 | 195 | /** 196 | * HEX解密 197 | * @param cipherText 密文,带解密的内容 198 | * @param password 密码,解密的密码 199 | * @return 返回明文,解密后得到的内容。解密错误返回null 200 | */ 201 | public static String decryptHex(String cipherText, String password) { 202 | try { 203 | // 1 将HEX输出密文 转为密文字节数组 204 | byte[] cipherTextBytes = hex2byte(cipherText); 205 | 206 | // 2 将密文字节数组进行解密 得到明文字节数组 207 | byte[] clearTextBytes = decrypt(cipherTextBytes, pwdHandler(password)); 208 | 209 | // 3 根据 CHARACTER 转码,返回明文字符串 210 | return new String(clearTextBytes, CHARACTER); 211 | } catch (UnsupportedEncodingException e) { 212 | e.printStackTrace(); 213 | } catch (Exception e) { 214 | e.printStackTrace(); 215 | } 216 | // 解密错误返回null 217 | return null; 218 | } 219 | 220 | /*字节数组转成16进制字符串 */ 221 | public static String byte2hex(byte[] bytes) { // 一个字节的数, 222 | StringBuffer sb = new StringBuffer(bytes.length * 2); 223 | String tmp = ""; 224 | for (int n = 0; n < bytes.length; n++) { 225 | // 整数转成十六进制表示 226 | tmp = (java.lang.Integer.toHexString(bytes[n] & 0XFF)); 227 | if (tmp.length() == 1) { 228 | sb.append("0"); 229 | } 230 | sb.append(tmp); 231 | } 232 | return sb.toString().toUpperCase(); // 转成大写 233 | } 234 | 235 | /*将hex字符串转换成字节数组 */ 236 | private static byte[] hex2byte(String str) { 237 | if (str == null || str.length() < 2) { 238 | return new byte[0]; 239 | } 240 | str = str.toLowerCase(); 241 | int l = str.length() / 2; 242 | byte[] result = new byte[l]; 243 | for (int i = 0; i < l; ++i) { 244 | String tmp = str.substring(2 * i, 2 * i + 2); 245 | result[i] = (byte) (Integer.parseInt(tmp, 16) & 0xFF); 246 | } 247 | return result; 248 | } 249 | 250 | public static void main(String[] args) { 251 | String test = encryptHex("test", "1234567800000000"); 252 | System.out.println(test); 253 | 254 | System.out.println(decryptHex(test, "1234567800000000")); 255 | } 256 | } -------------------------------------------------------------------------------- /proxy_tools/src/main/java/com/example/proxy_tools/Main.java: -------------------------------------------------------------------------------- 1 | package com.example.proxy_tools; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.FilenameFilter; 6 | import java.io.IOException; 7 | 8 | public class Main { 9 | 10 | /** 11 | * 制作 Dex 命令 12 | */ 13 | private static String DX_PATH = "/Users/devyk/Data/Android/SDK/build-tools/26.0.1/dx --dex --output "; 14 | 15 | /** 16 | * 制作 对齐 命令 17 | */ 18 | private static String ZIPALIGN = "/Users/devyk/Data/Android/SDK/build-tools/26.0.1/zipalign -v -p 4 "; 19 | 20 | /** 21 | * 制作 签名打包 命令 22 | */ 23 | private static String APKSIGNER = "/Users/devyk/Data/Android/SDK/build-tools/26.0.1/apksigner sign --ks "; 24 | 25 | /** 26 | * 记录执行制作 dex 的次数,第一次执行成功但是没有生成 27 | */ 28 | private static int count = 0; 29 | 30 | public static void main(String[] args) throws Exception { 31 | 32 | 33 | /** 34 | * 1.制作只包含解密代码的dex文件 35 | */ 36 | makeDecodeDex(); 37 | 38 | /** 39 | * 2.加密APK中所有的dex文件 40 | */ 41 | encryptApkAllDex(); 42 | 43 | /** 44 | * 3.把dex放入apk解压目录,重新压成apk文件 45 | */ 46 | makeApk(); 47 | /** 48 | * 4.对齐 49 | */ 50 | zipalign(); 51 | /** 52 | * 5. 签名打包 53 | */ 54 | jksToApk(); 55 | } 56 | 57 | 58 | /** 59 | * 1.制作只包含解密代码的dex文件 60 | */ 61 | public static void makeDecodeDex() throws IOException, InterruptedException { 62 | if (count >= 2) return; 63 | File aarFile = new File("proxy_core/build/outputs/aar/proxy_core-debug.aar"); 64 | File aarTemp = new File("proxy_tools/temp"); 65 | Zip.unZip(aarFile, aarTemp); 66 | File classesJar = new File(aarTemp, "classes.jar"); 67 | File classesDex = new File(aarTemp, "classes.dex"); 68 | //dx --dex --output out.dex in.jar 69 | //dx --dex --output D:\Downloads\android_space\DexDEApplication\proxy_tools\temp\classes.dex D:\Downloads\android_space\DexDEApplication\proxy_tools\temp\classes.jar 70 | // Windows 执行 71 | // Process process = Runtime.getRuntime().exec("cmd /c dx --dex --output " + classesDex.getAbsolutePath() 72 | //MAC 执行 73 | Process process = Runtime.getRuntime().exec(DX_PATH + classesDex.getAbsolutePath() 74 | + " " + classesJar.getAbsolutePath()); 75 | process.waitFor(); 76 | if (process.exitValue() != 0) { 77 | throw new RuntimeException("dex error"); 78 | } 79 | 80 | if (!classesDex.exists()) makeDecodeDex(); 81 | System.out.println("makeDecodeDex--ok"); 82 | count++; 83 | } 84 | 85 | /** 86 | * 2.加密APK中所有的dex文件 87 | */ 88 | public static void encryptApkAllDex() throws Exception { 89 | File apkFile = new File("app/build/outputs/apk/debug/app-debug.apk"); 90 | File apkTemp = new File("app/build/outputs/apk/debug/temp"); 91 | Zip.unZip(apkFile, apkTemp); 92 | //只要dex文件拿出来加密 93 | File[] dexFiles = apkTemp.listFiles(new FilenameFilter() { 94 | @Override 95 | public boolean accept(File file, String s) { 96 | return s.endsWith(".dex"); 97 | } 98 | }); 99 | //AES加密了 100 | // AES.init(AES.DEFAULT_PWD); 101 | for (File dexFile : dexFiles) { 102 | byte[] bytes = DexUtils.getBytes(dexFile); 103 | byte[] encrypt = EncryptUtil.encrypt(bytes, EncryptUtil.ivBytes); 104 | FileOutputStream fos = new FileOutputStream(new File(apkTemp, 105 | "secret-" + dexFile.getName())); 106 | fos.write(encrypt); 107 | fos.flush(); 108 | fos.close(); 109 | dexFile.delete(); 110 | 111 | } 112 | System.out.println("encryptApkAllDex--ok"); 113 | } 114 | 115 | /** 116 | * 3.把dex放入apk解压目录,重新压成apk文件 117 | */ 118 | private static void makeApk() throws Exception { 119 | File apkTemp = new File("app/build/outputs/apk/debug/temp"); 120 | File aarTemp = new File("proxy_tools/temp"); 121 | File classesDex = new File(aarTemp, "classes.dex"); 122 | classesDex.renameTo(new File(apkTemp, "classes.dex")); 123 | File unSignedApk = new File("app/build/outputs/apk/debug/app-unsigned.apk"); 124 | Zip.zip(apkTemp, unSignedApk); 125 | System.out.println("makeApk--ok"); 126 | } 127 | 128 | /** 129 | * 4. 对齐 130 | */ 131 | private static void zipalign() throws IOException, InterruptedException { 132 | File unSignedApk = new File("app/build/outputs/apk/debug/app-unsigned.apk"); 133 | // zipalign -v -p 4 my-app-unsigned.apk my-app-unsigned-aligned.apk 134 | File alignedApk = new File("app/build/outputs/apk/debug/app-unsigned-aligned.apk"); 135 | //Windows 执行 136 | // Process process = Runtime.getRuntime().exec("cmd /c zipalign -v -p 4 " + unSignedApk.getAbsolutePath() 137 | //MAC 执行 138 | Process process = Runtime.getRuntime().exec(ZIPALIGN + unSignedApk.getAbsolutePath() 139 | + " " + alignedApk.getAbsolutePath()); 140 | process.waitFor(); 141 | 142 | //zipalign -v -p 4 D:\Downloads\android_space\DexDEApplication\app\build\outputs\apk\debug\app-unsigned.apk D:\Downloads\android_space\DexDEApplication\app\build\outputs\apk\debug\app-unsigned-aligned.apk 143 | // System.out.println(process.waitFor() == 0 ? "zipalign成功" : "zipalign失败"); 144 | 145 | System.out.println("zipalign---ok"); 146 | } 147 | 148 | /** 149 | * 签名 打包 150 | * 151 | * @throws IOException 152 | */ 153 | public static void jksToApk() throws IOException, InterruptedException { 154 | // apksigner sign --ks my-release-key.jks --out my-app-release.apk my-app-unsigned-aligned.apk 155 | //apksigner sign --ks jks文件地址 --ks-key-alias 别名 --ks-pass pass:jsk密码 --key-pass pass:别名密码 --out out.apk in.apk 156 | File signedApk = new File("app/release/app-signed-aligned.apk"); 157 | File jks = new File("proxy_tools/dexjks.jks"); 158 | File alignedApk = new File("app/build/outputs/apk/debug/app-unsigned-aligned.apk"); 159 | //apksigner sign --ks D:\Downloads\android_space\DexDEApplication\proxy_tools\dexjks.jks --ks-key-alias yangkun --ks-pass pass:123123 --key-pass pass:123123 --out D:\Downloads\android_space\DexDEApplication\app\build\outputs\apk\debug\app-signed-aligned.apk D:\Downloads\android_space\DexDEApplication\app\build\outputs\apk\debug\app-unsigned-aligned.apk 160 | //apksigner sign --ks my-release-key.jks --out my-app-release.apk my-app-unsigned-aligned.apk 161 | 162 | //Windows 执行 163 | // Process process = Runtime.getRuntime().exec("cmd /c apksigner sign --ks " + jks.getAbsolutePath() 164 | //MAC 执行 165 | Process process = Runtime.getRuntime().exec(APKSIGNER + jks.getAbsolutePath() 166 | + " --ks-key-alias yangkun --ks-pass pass:123123 --key-pass pass:123123 --out " 167 | + signedApk.getAbsolutePath() + " " + alignedApk.getAbsolutePath()); 168 | process.waitFor(); 169 | if (process.exitValue() != 0) { 170 | throw new RuntimeException("dex error"); 171 | } 172 | System.out.println("打包成功->" + signedApk.getAbsolutePath()); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /proxy_tools/src/main/java/com/example/proxy_tools/Zip.java: -------------------------------------------------------------------------------- 1 | package com.example.proxy_tools; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileOutputStream; 6 | import java.io.InputStream; 7 | import java.util.Enumeration; 8 | import java.util.zip.CRC32; 9 | import java.util.zip.CheckedOutputStream; 10 | import java.util.zip.ZipEntry; 11 | import java.util.zip.ZipFile; 12 | import java.util.zip.ZipOutputStream; 13 | 14 | 15 | public class Zip { 16 | 17 | private static void deleteFile(File file){ 18 | if (file.isDirectory()){ 19 | File[] files = file.listFiles(); 20 | for (File f: files) { 21 | deleteFile(f); 22 | } 23 | }else{ 24 | file.delete(); 25 | } 26 | } 27 | 28 | /** 29 | * 解压zip文件至dir目录 30 | * @param zip 31 | * @param dir 32 | */ 33 | public static void unZip(File zip, File dir) { 34 | try { 35 | deleteFile(dir); 36 | ZipFile zipFile = new ZipFile(zip); 37 | //zip文件中每一个条目 38 | Enumeration entries = zipFile.entries(); 39 | //遍历 40 | while (entries.hasMoreElements()) { 41 | ZipEntry zipEntry = entries.nextElement(); 42 | //zip中 文件/目录名 43 | String name = zipEntry.getName(); 44 | //原来的签名文件 不需要了 45 | if (name.equals("META-INF/CERT.RSA") || name.equals("META-INF/CERT.SF") || name 46 | .equals("META-INF/MANIFEST.MF")) { 47 | continue; 48 | } 49 | //空目录不管 50 | if (!zipEntry.isDirectory()) { 51 | File file = new File(dir, name); 52 | //创建目录 53 | if (!file.getParentFile().exists()) { 54 | file.getParentFile().mkdirs(); 55 | } 56 | //写文件 57 | FileOutputStream fos = new FileOutputStream(file); 58 | InputStream is = zipFile.getInputStream(zipEntry); 59 | byte[] buffer = new byte[2048]; 60 | int len; 61 | while ((len = is.read(buffer)) != -1) { 62 | fos.write(buffer, 0, len); 63 | } 64 | is.close(); 65 | fos.close(); 66 | } 67 | } 68 | zipFile.close(); 69 | } catch (Exception e) { 70 | e.printStackTrace(); 71 | } 72 | } 73 | 74 | /** 75 | * 压缩目录为zip 76 | * @param dir 待压缩目录 77 | * @param zip 输出的zip文件 78 | * @throws Exception 79 | */ 80 | public static void zip(File dir, File zip) throws Exception { 81 | zip.delete(); 82 | // 对输出文件做CRC32校验 83 | CheckedOutputStream cos = new CheckedOutputStream(new FileOutputStream( 84 | zip), new CRC32()); 85 | ZipOutputStream zos = new ZipOutputStream(cos); 86 | //压缩 87 | compress(dir, zos, ""); 88 | zos.flush(); 89 | zos.close(); 90 | } 91 | 92 | /** 93 | * 添加目录/文件 至zip中 94 | * @param srcFile 需要添加的目录/文件 95 | * @param zos zip输出流 96 | * @param basePath 递归子目录时的完整目录 如 lib/x86 97 | * @throws Exception 98 | */ 99 | private static void compress(File srcFile, ZipOutputStream zos, 100 | String basePath) throws Exception { 101 | if (srcFile.isDirectory()) { 102 | File[] files = srcFile.listFiles(); 103 | for (File file : files) { 104 | // zip 递归添加目录中的文件 105 | compress(file, zos, basePath + srcFile.getName() + "/"); 106 | } 107 | } else { 108 | compressFile(srcFile, zos, basePath); 109 | } 110 | } 111 | 112 | private static void compressFile(File file, ZipOutputStream zos, String dir) 113 | throws Exception { 114 | // temp/lib/x86/libdn_ssl.so 115 | String fullName = dir + file.getName(); 116 | // 需要去掉temp 117 | String[] fileNames = fullName.split("/"); 118 | //正确的文件目录名 (去掉了temp) 119 | StringBuffer sb = new StringBuffer(); 120 | if (fileNames.length > 1){ 121 | for (int i = 1;i 2 | proxy_core 3 | 4 | -------------------------------------------------------------------------------- /proxy_tools/temp/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /proxy_tools/temp/classes.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/DexEncryptionDecryption/43453801ee30ec1e0dfe6b1ac79b94fb7867f7bf/proxy_tools/temp/classes.jar -------------------------------------------------------------------------------- /proxy_tools/temp/res/values/values.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | proxy_core 4 | -------------------------------------------------------------------------------- /proxy_tools/ykun.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkun19921001/DexEncryptionDecryption/43453801ee30ec1e0dfe6b1ac79b94fb7867f7bf/proxy_tools/ykun.jks -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', 'proxy_core', 'proxy_tools' 2 | --------------------------------------------------------------------------------