├── .gitignore ├── LICENSE ├── README.md ├── androguard ├── LICENCE-2.0 ├── __init__.py ├── core │ ├── __init__.py │ ├── analysis │ │ ├── __init__.py │ │ ├── analysis.py │ │ ├── auto.py │ │ └── tags.py │ ├── androconf.py │ ├── api_specific_resources │ │ ├── __init__.py │ │ ├── aosp_permissions │ │ │ ├── permissions_10.json │ │ │ ├── permissions_13.json │ │ │ ├── permissions_14.json │ │ │ ├── permissions_15.json │ │ │ ├── permissions_16.json │ │ │ ├── permissions_17.json │ │ │ ├── permissions_18.json │ │ │ ├── permissions_19.json │ │ │ ├── permissions_21.json │ │ │ ├── permissions_22.json │ │ │ ├── permissions_23.json │ │ │ ├── permissions_24.json │ │ │ ├── permissions_25.json │ │ │ ├── permissions_26.json │ │ │ ├── permissions_27.json │ │ │ ├── permissions_28.json │ │ │ ├── permissions_29.json │ │ │ ├── permissions_30.json │ │ │ ├── permissions_31.json │ │ │ ├── permissions_32.json │ │ │ ├── permissions_33.json │ │ │ ├── permissions_34.json │ │ │ ├── permissions_4.json │ │ │ ├── permissions_5.json │ │ │ ├── permissions_6.json │ │ │ ├── permissions_7.json │ │ │ ├── permissions_8.json │ │ │ └── permissions_9.json │ │ └── api_permission_mappings │ │ │ ├── permissions_16.json │ │ │ ├── permissions_17.json │ │ │ ├── permissions_18.json │ │ │ ├── permissions_19.json │ │ │ ├── permissions_21.json │ │ │ ├── permissions_22.json │ │ │ ├── permissions_23.json │ │ │ ├── permissions_24.json │ │ │ └── permissions_25.json │ ├── bytecode.py │ ├── bytecodes │ │ ├── __init__.py │ │ ├── apk.py │ │ ├── axml │ │ │ ├── __init__.py │ │ │ └── types.py │ │ ├── dvm.py │ │ ├── dvm_types.py │ │ └── mutf8.py │ └── resources │ │ ├── __init__.py │ │ ├── public.py │ │ └── public.xml ├── decompiler │ ├── __init__.py │ ├── dad │ │ ├── __init__.py │ │ ├── basic_blocks.py │ │ ├── control_flow.py │ │ ├── dast.py │ │ ├── dataflow.py │ │ ├── decompile.py │ │ ├── graph.py │ │ ├── instruction.py │ │ ├── node.py │ │ ├── opcode_ins.py │ │ ├── util.py │ │ └── writer.py │ └── decompiler.py ├── misc.py ├── session.py └── util.py ├── dcc.cfg ├── dcc.py ├── dex2c ├── __init__.py ├── basic_blocks.py ├── compiler.py ├── graph.py ├── instruction.py ├── opcode_ins.py ├── util.py └── writer.py ├── filter.txt ├── keystore └── debug.keystore ├── loader └── DccApplication.smali ├── project └── jni │ ├── Android.mk │ ├── Application.mk │ └── nc │ ├── Dex2C.cpp │ ├── Dex2C.h │ ├── DynamicRegister.h │ ├── ScopedLocalRef.h │ ├── ScopedPthreadMutexLock.h │ ├── obfuscate.h │ ├── well_known_classes.cpp │ └── well_known_classes.h ├── requirements.txt ├── termux_install.sh └── tools ├── apksigner.jar ├── apktool ├── apktool.bat └── manifest-editor.jar /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/python 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python 3 | 4 | ### Python ### 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # test apk, zip & signing files 11 | *.apk 12 | *.idsig 13 | *.zip 14 | tools/apktool.jar 15 | 16 | # C extensions 17 | *.so 18 | 19 | # Distribution / packaging 20 | .Python 21 | build/ 22 | develop-eggs/ 23 | dist/ 24 | downloads/ 25 | eggs/ 26 | .eggs/ 27 | lib/ 28 | lib64/ 29 | parts/ 30 | sdist/ 31 | var/ 32 | wheels/ 33 | share/python-wheels/ 34 | *.egg-info/ 35 | .installed.cfg 36 | *.egg 37 | MANIFEST 38 | 39 | # PyInstaller 40 | # Usually these files are written by a python script from a template 41 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 42 | *.manifest 43 | *.spec 44 | 45 | # Installer logs 46 | pip-log.txt 47 | pip-delete-this-directory.txt 48 | 49 | # Unit test / coverage reports 50 | htmlcov/ 51 | .tox/ 52 | .nox/ 53 | .coverage 54 | .coverage.* 55 | .cache 56 | nosetests.xml 57 | coverage.xml 58 | *.cover 59 | *.py,cover 60 | .hypothesis/ 61 | .pytest_cache/ 62 | cover/ 63 | 64 | # Translations 65 | *.mo 66 | *.pot 67 | 68 | # Django stuff: 69 | *.log 70 | local_settings.py 71 | db.sqlite3 72 | db.sqlite3-journal 73 | 74 | # Flask stuff: 75 | instance/ 76 | .webassets-cache 77 | 78 | # Scrapy stuff: 79 | .scrapy 80 | 81 | # Sphinx documentation 82 | docs/_build/ 83 | 84 | # PyBuilder 85 | .pybuilder/ 86 | target/ 87 | 88 | # Jupyter Notebook 89 | .ipynb_checkpoints 90 | 91 | # IPython 92 | profile_default/ 93 | ipython_config.py 94 | 95 | # pyenv 96 | # For a library or package, you might want to ignore these files since the code is 97 | # intended to run in multiple environments; otherwise, check them in: 98 | # .python-version 99 | 100 | # pipenv 101 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 102 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 103 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 104 | # install all needed dependencies. 105 | #Pipfile.lock 106 | 107 | # poetry 108 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 109 | # This is especially recommended for binary packages to ensure reproducibility, and is more 110 | # commonly ignored for libraries. 111 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 112 | #poetry.lock 113 | 114 | # pdm 115 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 116 | #pdm.lock 117 | analyzer.py 118 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 119 | # in version control. 120 | # https://pdm.fming.dev/#use-with-ide 121 | .pdm.toml 122 | 123 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 124 | __pypackages__/ 125 | 126 | # Celery stuff 127 | celerybeat-schedule 128 | celerybeat.pid 129 | 130 | # SageMath parsed files 131 | *.sage.py 132 | 133 | # Environments 134 | .env 135 | .venv 136 | env/ 137 | venv/ 138 | ENV/ 139 | env.bak/ 140 | venv.bak/ 141 | 142 | # Spyder project settings 143 | .spyderproject 144 | .spyproject 145 | 146 | # Rope project settings 147 | .ropeproject 148 | 149 | # mkdocs documentation 150 | /site 151 | 152 | # mypy 153 | .mypy_cache/ 154 | .dmypy.json 155 | dmypy.json 156 | 157 | # Pyre type checker 158 | .pyre/ 159 | 160 | # pytype static type analyzer 161 | .pytype/ 162 | 163 | # Cython debug symbols 164 | cython_debug/ 165 | 166 | # PyCharm 167 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 168 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 169 | # and can be added to the global gitignore or merged into this file. For a more nuclear 170 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 171 | #.idea/ 172 | 173 | ### Python Patch ### 174 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration 175 | poetry.toml 176 | 177 | # ruff 178 | .ruff_cache/ 179 | 180 | # LSP config files 181 | pyrightconfig.json 182 | 183 | # End of https://www.toptal.com/developers/gitignore/api/python 184 | -------------------------------------------------------------------------------- /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 | 2 | 3 |
4 |

𝐃𝐞𝐱𝟐𝐂

5 | 6 | 7 | 8 | 9 | [![Stars](https://img.shields.io/github/stars/codehasan/Dex2C?color=yellow)](https://github.com/TeamUltroid/Ultroid/stargazers) 10 | [![Python](https://img.shields.io/badge/Python-v3.10.3-blue)](https://www.python.org/) 11 | [![Forks](https://img.shields.io/github/forks/codehasan/Dex2C?color=orange)](https://github.com/codehasan/Dex2C/fork) 12 | [![Size](https://img.shields.io/github/repo-size/codehasan/Dex2C?color=green)](https://github.com/codehasan/Dex2C/) 13 | [![Contributors](https://img.shields.io/github/contributors/codehasan/Dex2C?color=green)](https://github.com/codehasan/Dex2C/graphs/contributors) 14 | [![License](https://img.shields.io/badge/License-Apache-blue)](./LICENSE) 15 | 16 | 17 | 18 |

19 | Method-based AOT compiler that can wrap Dalvik bytecode with JNI native code. 20 |

21 |
22 | 23 | 24 |
25 | Table of contents 26 |
    27 |
  1. 28 | About the project 29 | 32 |
  2. 33 |
  3. 34 | Installation 35 | 40 |
  4. 41 |
  5. 42 | Usage 43 | 47 |
  6. 48 |
  7. How to change lib name
  8. 49 |
  9. Roadmap
  10. 50 |
  11. Contributing
  12. 51 |
  13. License
  14. 52 |
  15. Acknowledgments
  16. 53 |
54 |
55 | 56 | 57 | 58 | # About the project 59 | 60 | This project is inspired by [amimo/dcc](https://github.com/amimo/dcc), which aims to make it easy for everyone to use this tool. We automated plenty of processes that you had to do manually in the original DCC. Moreover, we always try to add new features to make this tool more usable in real-world applications. 61 | Check out Roadmap to know about the changes we made and also the changes we are planning to make in the feature. 62 | 63 | ### Built with 64 | 65 | 66 | * ![Python][Python-Badge] 67 | * [![Androguard][Androguard-Badge]][Androguard_Repository] 68 | 69 | 70 | 71 | # Installation 72 | 73 | Python 3.8 or higher is required for running this tool. So, make sure your python is up-to-date. 74 | 75 | 1. Clone the repo. 76 | ```bash 77 | git clone https://github.com/codehasan/dex2c 78 | ``` 79 | 2. Open the cloned directory. 80 | ```bash 81 | cd dex2c 82 | ``` 83 | 3. Download **Apktool** latest version from [bitbucket](https://bitbucket.org/iBotPeaches/apktool/downloads/) and save it in `tools` folder with the name `apktool.jar` 84 | ```bash 85 | wget -O tools/apktool.jar https://bitbucket.org/iBotPeaches/apktool/downloads/apktool_2.10.0.jar 86 | ``` 87 | 4. Download android NDK for your OS and extract it. Copy the folder path where `ndk-build` executable is located inside the extracted folder and configure `ndk_dir` in `dcc.cfg` 88 | 89 | ### Linux 90 | 91 | ![Linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black) 92 | 93 | 1. Install required dependencies. 94 | ```bash 95 | pip3 install -r requirements.txt 96 | ``` 97 | 2. Install JRE/JDK & zipalign if you don't have it installed. The recommended JDK version is 17. 98 | ```bash 99 | sudo apt-get install openjdk-17-jdk zipalign 100 | ``` 101 | 102 | ### Windows 103 | 104 | ![Windows](https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logo=windows&logoColor=white) 105 | 106 | > [!NOTE] 107 | > While this is supported on Windows, we strongly recommend using a Linux system for optimal compatibility and performance. If you're on Windows, consider using a Windows Subsystem for Linux (WSL) distribution to ensure a smoother and more reliable experience. The setup and instructions for WSL are the same as those for a [Linux](#linux). 108 | 109 | 1. Install required dependencies. 110 | ```bash 111 | pip3 install -r requirements.txt 112 | ``` 113 | 2. Install JRE/JDK from oracle if you don't have it installed. Search in Google, how to install JDK in Windows if you need more guidance on this topic. The recommended JDK version is 11 and up. 114 | 3. Make sure you've `zipalign` installed. You'll get it in Android SDK build-tools from here. Add it to your system path. 115 | 116 | ### Termux 117 | 118 | ![Android](https://img.shields.io/badge/Android-3DDC84?style=for-the-badge&logo=android&logoColor=white) 119 | 120 | One Step Installation: Run Below Command 121 | ```bash 122 | pkg install wget && wget -O termux_install.sh https://raw.githubusercontent.com/codehasan/dex2c/master/termux_install.sh && chmod -R +x termux_install.sh && ./termux_install.sh 123 | ``` 124 | > NOTE: Some users have reported about installation errors with latest `clang` on termux. If you happen to face the same issue, then uncomment the lines stated in `termux_install.sh` and then proceed with the installation. 125 | 126 | 127 | 128 | # Usage 129 | 130 | ### Filters 131 | 132 | Add all your filters in `filter.txt` file - one rule for each line. Filters are made using regex patterns. There are two types of filters available in **Dex2c** - whitelist, and blacklist. You can use them whenever you need them. 133 | 134 | #### WhiteList 135 | 136 | - Protect just one method in a specific class. 137 | ``` 138 | com/some/class;some_method(some_parameters)return_type 139 | ``` 140 | 141 | - Protect all methods in a specific class. 142 | ``` 143 | com/some/class;.* 144 | ``` 145 | 146 | - Protect all methods in all classes under a package path. 147 | ``` 148 | com/some/package/.*;.* 149 | ``` 150 | 151 | - Protect a method with the name onCreate in all classes. 152 | ``` 153 | .*;onCreate\(.* 154 | ``` 155 | 156 | #### BlackList 157 | 158 | Adding an exclamation `!` sign before a rule will mark that rule as a blacklist. 159 | 160 | - Exclude one method in a specific class from being protected. 161 | ``` 162 | !com/some/class;some_method(some_parameters)return_type 163 | ``` 164 | 165 | - Exclude all methods in a specific class from being protected. 166 | ``` 167 | !com/some/class;.* 168 | ``` 169 | 170 | - Exclude all methods in all classes under a package path from being protected. 171 | ``` 172 | !com/some/package/.*;.* 173 | ``` 174 | 175 | - Exclude a method with the name onCreate in all classes from being protected. 176 | ``` 177 | !.*;onCreate\(.* 178 | ``` 179 | 180 | 181 | ### Protect apps 182 | 183 | - Copy your apk file to `dex2c` folder where `dcc.py` is located and run this command. 184 | 185 | ```bash 186 | python3 dcc.py -a input.apk -o output.apk 187 | ``` 188 | 189 | - Run this command to know about all the other options available in dcc to find the best ones for your needs. 190 | 191 | ```bash 192 | python3 dcc.py --help 193 | ``` 194 | 195 | 196 | 197 | # How to change lib name 198 | 199 | Open `project/jni/Android.mk` file in the cloned directory. You will find a variable named `LOCAL_MODULE`, initially with the value `stub`. Please change it to your desired lib name. Keep in mind the following instructions to prevent possible errors. 200 | - Don't use spaces in lib name, use hyphen `-` or underscore `_` 201 | - Don't use any kind of symbols or punctuations in lib name other than underscores and hyphens 202 | - Don't start the lib name with the text `lib` itself 203 | 204 | 205 | 206 | 207 | # Roadmap 208 | 209 | - [x] Add custom lib loader 210 | - [x] Add new apksigner 211 | - [x] Add multi-dex support 212 | - [x] Add app abi handler 213 | - [x] Add signature configuration in `dcc.cfg` 214 | - [x] Add new options 215 | - [x] --skip-synthetic 216 | - [x] --custom-loader 217 | - [x] --force-keep-libs 218 | - [x] --obfuscate 219 | - [x] --dynamic-register 220 | 221 | See the [open issues](https://github.com/codehasan/dex2c/issues) for a full list of proposed features and known issues. 222 | 223 | 224 | 225 | 226 | # Contributing 227 | 228 | Contributions are what makes the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. 229 | 230 | If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". 231 | Don't forget to give this project a star! Thanks again! 232 | 233 | 1. Fork the project 234 | 2. Create your feature branch. (`git checkout -b feature/AmazingFeature`) 235 | 3. Commit your changes. (`git commit -m 'Add some AmazingFeature'`) 236 | 4. Push to the branch. (`git push origin feature/AmazingFeature`) 237 | 5. Open a pull request. 238 | 239 | 240 | 241 | 242 | # License 243 | 244 | Distributed under the Apache License. See `LICENSE.txt` for more information. 245 | 246 | 247 | 248 | 249 | # Acknowledgments 250 | 251 | ### Projects 252 | 253 | * [DCC](https://github.com/amimo/dcc) 254 | * [Androguard](https://github.com/androguard/androguard) 255 | 256 | ### People 257 | 258 | * Rahat - [Telegram](https://t.me/botxrahat) 259 | * GoldenBoot - [Telegram](https://t.me/goldenboot) 260 | 261 |

Go to top

262 | 263 | 264 | [Python-Badge]: https://img.shields.io/badge/Python-F6D049?style=for-the-badge&logo=python 265 | [Androguard-Badge]: https://img.shields.io/badge/Androguard-FFFFFF?style=for-the-badge&logo=android 266 | [Androguard_Repository]: https://github.com/androguard/androguard 267 | -------------------------------------------------------------------------------- /androguard/LICENCE-2.0: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /androguard/__init__.py: -------------------------------------------------------------------------------- 1 | # The current version of Androguard 2 | # Please use only this variable in any scripts, 3 | # to keep the version number the same everywhere. 4 | __version__ = "3.3.5" 5 | -------------------------------------------------------------------------------- /androguard/core/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /androguard/core/analysis/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /androguard/core/analysis/tags.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | class Enum: 5 | def __init__(self, names): 6 | self.names = names 7 | for value, name in enumerate(self.names): 8 | setattr(self, name.upper(), value) 9 | 10 | def tuples(self): 11 | return tuple(enumerate(self.names)) 12 | 13 | 14 | TAG_ANDROID = Enum( 15 | ['ANDROID', 'TELEPHONY', 'SMS', 'SMSMESSAGE', 'ACCESSIBILITYSERVICE', 16 | 'ACCOUNTS', 'ANIMATION', 'APP', 'BLUETOOTH', 'CONTENT', 'DATABASE', 17 | 'DEBUG', 'DRM', 'GESTURE', 'GRAPHICS', 'HARDWARE', 'INPUTMETHODSERVICE', 18 | 'LOCATION', 'MEDIA', 'MTP', 'NET', 'NFC', 'OPENGL', 'OS', 'PREFERENCE', 19 | 'PROVIDER', 'RENDERSCRIPT', 'SAX', 'SECURITY', 'SERVICE', 'SPEECH', 20 | 'SUPPORT', 'TEST', 'TEXT', 'UTIL', 'VIEW', 'WEBKIT', 'WIDGET', 21 | 'DALVIK_BYTECODE', 'DALVIK_SYSTEM', 'JAVA_REFLECTION']) 22 | 23 | TAG_REVERSE_ANDROID = dict((i[0], i[1]) for i in TAG_ANDROID.tuples()) 24 | 25 | TAGS_ANDROID = { 26 | TAG_ANDROID.ANDROID: [0, "Landroid"], 27 | TAG_ANDROID.TELEPHONY: [0, "Landroid/telephony"], 28 | TAG_ANDROID.SMS: [0, "Landroid/telephony/SmsManager"], 29 | TAG_ANDROID.SMSMESSAGE: [0, "Landroid/telephony/SmsMessage"], 30 | TAG_ANDROID.DEBUG: [0, "Landroid/os/Debug"], 31 | TAG_ANDROID.ACCESSIBILITYSERVICE: [0, "Landroid/accessibilityservice"], 32 | TAG_ANDROID.ACCOUNTS: [0, "Landroid/accounts"], 33 | TAG_ANDROID.ANIMATION: [0, "Landroid/animation"], 34 | TAG_ANDROID.APP: [0, "Landroid/app"], 35 | TAG_ANDROID.BLUETOOTH: [0, "Landroid/bluetooth"], 36 | TAG_ANDROID.CONTENT: [0, "Landroid/content"], 37 | TAG_ANDROID.DATABASE: [0, "Landroid/database"], 38 | TAG_ANDROID.DRM: [0, "Landroid/drm"], 39 | TAG_ANDROID.GESTURE: [0, "Landroid/gesture"], 40 | TAG_ANDROID.GRAPHICS: [0, "Landroid/graphics"], 41 | TAG_ANDROID.HARDWARE: [0, "Landroid/hardware"], 42 | TAG_ANDROID.INPUTMETHODSERVICE: [0, "Landroid/inputmethodservice"], 43 | TAG_ANDROID.LOCATION: [0, "Landroid/location"], 44 | TAG_ANDROID.MEDIA: [0, "Landroid/media"], 45 | TAG_ANDROID.MTP: [0, "Landroid/mtp"], 46 | TAG_ANDROID.NET: [0, "Landroid/net"], 47 | TAG_ANDROID.NFC: [0, "Landroid/nfc"], 48 | TAG_ANDROID.OPENGL: [0, "Landroid/opengl"], 49 | TAG_ANDROID.OS: [0, "Landroid/os"], 50 | TAG_ANDROID.PREFERENCE: [0, "Landroid/preference"], 51 | TAG_ANDROID.PROVIDER: [0, "Landroid/provider"], 52 | TAG_ANDROID.RENDERSCRIPT: [0, "Landroid/renderscript"], 53 | TAG_ANDROID.SAX: [0, "Landroid/sax"], 54 | TAG_ANDROID.SECURITY: [0, "Landroid/security"], 55 | TAG_ANDROID.SERVICE: [0, "Landroid/service"], 56 | TAG_ANDROID.SPEECH: [0, "Landroid/speech"], 57 | TAG_ANDROID.SUPPORT: [0, "Landroid/support"], 58 | TAG_ANDROID.TEST: [0, "Landroid/test"], 59 | TAG_ANDROID.TEXT: [0, "Landroid/text"], 60 | TAG_ANDROID.UTIL: [0, "Landroid/util"], 61 | TAG_ANDROID.VIEW: [0, "Landroid/view"], 62 | TAG_ANDROID.WEBKIT: [0, "Landroid/webkit"], 63 | TAG_ANDROID.WIDGET: [0, "Landroid/widget"], 64 | TAG_ANDROID.DALVIK_BYTECODE: [0, "Ldalvik/bytecode"], 65 | TAG_ANDROID.DALVIK_SYSTEM: [0, "Ldalvik/system"], 66 | TAG_ANDROID.JAVA_REFLECTION: [0, "Ljava/lang/reflect"], 67 | } 68 | 69 | 70 | class Tags: 71 | """ 72 | Handle specific tags 73 | 74 | :param patterns: 75 | :params reverse: 76 | """ 77 | 78 | def __init__(self, patterns=TAGS_ANDROID, reverse=TAG_REVERSE_ANDROID): 79 | self.tags = set() 80 | 81 | self.patterns = patterns 82 | self.reverse = TAG_REVERSE_ANDROID 83 | 84 | for i in self.patterns: 85 | self.patterns[i][1] = re.compile(self.patterns[i][1]) 86 | 87 | def emit(self, method): 88 | for i in self.patterns: 89 | if self.patterns[i][0] == 0: 90 | if self.patterns[i][1].search(method.get_class()) is not None: 91 | self.tags.add(i) 92 | 93 | def emit_by_classname(self, classname): 94 | for i in self.patterns: 95 | if self.patterns[i][0] == 0: 96 | if self.patterns[i][1].search(classname) is not None: 97 | self.tags.add(i) 98 | 99 | def get_list(self): 100 | return [self.reverse[i] for i in self.tags] 101 | 102 | def __contains__(self, key): 103 | return key in self.tags 104 | 105 | def __str__(self): 106 | return str([self.reverse[i] for i in self.tags]) 107 | 108 | def empty(self): 109 | return self.tags == set() 110 | -------------------------------------------------------------------------------- /androguard/core/androconf.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import logging 4 | import tempfile 5 | 6 | from androguard import __version__ 7 | from androguard.core.api_specific_resources import load_permission_mappings, load_permissions 8 | ANDROGUARD_VERSION = __version__ 9 | 10 | log = logging.getLogger("androguard.default") 11 | 12 | 13 | class InvalidResourceError(Exception): 14 | """ 15 | Invalid Resource Erorr is thrown by load_api_specific_resource_module 16 | """ 17 | pass 18 | 19 | 20 | def is_ascii_problem(s): 21 | """ 22 | Test if a string contains other chars than ASCII 23 | 24 | :param s: a string to test 25 | :return: True if string contains other chars than ASCII, False otherwise 26 | """ 27 | try: 28 | s.encode("ascii") 29 | return False 30 | except (UnicodeEncodeError, UnicodeDecodeError): 31 | return True 32 | 33 | 34 | class Color(object): 35 | Normal = "\033[0m" 36 | Black = "\033[30m" 37 | Red = "\033[31m" 38 | Green = "\033[32m" 39 | Yellow = "\033[33m" 40 | Blue = "\033[34m" 41 | Purple = "\033[35m" 42 | Cyan = "\033[36m" 43 | Grey = "\033[37m" 44 | Bold = "\033[1m" 45 | 46 | 47 | default_conf = { 48 | ## Configuration for executables used by androguard 49 | # Assume the binary is in $PATH, otherwise give full path 50 | "BIN_JADX": "jadx", 51 | # Dex2jar binary 52 | "BIN_DEX2JAR": "dex2jar.sh", 53 | 54 | # TODO Use apksigner instead 55 | "BIN_JARSIGNER": "jarsigner", 56 | 57 | "BIN_DED": "ded.sh", # TO BE REMOVED 58 | "BIN_JAD": "jad", # TO BE REMOVED 59 | "BIN_WINEJAD": "jad.exe", # TO BE REMOVED 60 | "BIN_FERNFLOWER": "fernflower.jar", # TO BE REMOVED 61 | "OPTIONS_FERNFLOWER": {"dgs": '1', # TO BE REMOVED 62 | "asc": '1'}, 63 | 64 | ## Runtime variables 65 | # A path to the temporary directory 66 | "TMP_DIRECTORY": tempfile.gettempdir(), 67 | 68 | # Function to print stuff 69 | "PRINT_FCT": sys.stdout.write, 70 | 71 | # Default API level, if requested API is not available 72 | "DEFAULT_API": 16, # this is the minimal API version we have 73 | 74 | # Session, for persistence 75 | "SESSION": None, 76 | 77 | # Recode strings when getting them from ClassManager 78 | # FIXME: Should be not needed anymore? 79 | "RECODE_ASCII_STRING": False, 80 | # Optional Function which can recode a string 81 | "RECODE_ASCII_STRING_METH": None, 82 | 83 | ## Color output configuration 84 | "COLORS": { 85 | "OFFSET": Color.Yellow, 86 | "OFFSET_ADDR": Color.Green, 87 | "INSTRUCTION_NAME": Color.Yellow, 88 | "BRANCH_FALSE": Color.Red, 89 | "BRANCH_TRUE": Color.Green, 90 | "BRANCH": Color.Blue, 91 | "EXCEPTION": Color.Cyan, 92 | "BB": Color.Purple, 93 | "NOTE": Color.Red, 94 | "NORMAL": Color.Normal, 95 | "OUTPUT": { 96 | "normal": Color.Normal, 97 | "registers": Color.Normal, 98 | "literal": Color.Green, 99 | "offset": Color.Purple, 100 | "raw": Color.Red, 101 | "string": Color.Red, 102 | "meth": Color.Cyan, 103 | "type": Color.Blue, 104 | "field": Color.Green, 105 | }, 106 | }, 107 | } 108 | 109 | 110 | class Configuration: 111 | instance = None 112 | 113 | def __init__(self): 114 | """ 115 | A Wrapper for the CONF object 116 | This creates a singleton, which has the same attributes everywhere. 117 | """ 118 | if not Configuration.instance: 119 | Configuration.instance = default_conf 120 | 121 | def __getattr__(self, item): 122 | return getattr(self.instance, item) 123 | 124 | def __getitem__(self, item): 125 | return self.instance[item] 126 | 127 | def __setitem__(self, key, value): 128 | self.instance[key] = value 129 | 130 | def __str__(self): 131 | return str(self.instance) 132 | 133 | def __repr__(self): 134 | return repr(self.instance) 135 | 136 | 137 | CONF = Configuration() 138 | 139 | 140 | def default_colors(obj): 141 | CONF["COLORS"]["OFFSET"] = obj.Yellow 142 | CONF["COLORS"]["OFFSET_ADDR"] = obj.Green 143 | CONF["COLORS"]["INSTRUCTION_NAME"] = obj.Yellow 144 | CONF["COLORS"]["BRANCH_FALSE"] = obj.Red 145 | CONF["COLORS"]["BRANCH_TRUE"] = obj.Green 146 | CONF["COLORS"]["BRANCH"] = obj.Blue 147 | CONF["COLORS"]["EXCEPTION"] = obj.Cyan 148 | CONF["COLORS"]["BB"] = obj.Purple 149 | CONF["COLORS"]["NOTE"] = obj.Red 150 | CONF["COLORS"]["NORMAL"] = obj.Normal 151 | 152 | CONF["COLORS"]["OUTPUT"]["normal"] = obj.Normal 153 | CONF["COLORS"]["OUTPUT"]["registers"] = obj.Normal 154 | CONF["COLORS"]["OUTPUT"]["literal"] = obj.Green 155 | CONF["COLORS"]["OUTPUT"]["offset"] = obj.Purple 156 | CONF["COLORS"]["OUTPUT"]["raw"] = obj.Red 157 | CONF["COLORS"]["OUTPUT"]["string"] = obj.Red 158 | CONF["COLORS"]["OUTPUT"]["meth"] = obj.Cyan 159 | CONF["COLORS"]["OUTPUT"]["type"] = obj.Blue 160 | CONF["COLORS"]["OUTPUT"]["field"] = obj.Green 161 | 162 | 163 | def disable_colors(): 164 | """ Disable colors from the output (color = normal)""" 165 | for i in CONF["COLORS"]: 166 | if isinstance(CONF["COLORS"][i], dict): 167 | for j in CONF["COLORS"][i]: 168 | CONF["COLORS"][i][j] = Color.normal 169 | else: 170 | CONF["COLORS"][i] = Color.normal 171 | 172 | 173 | def remove_colors(): 174 | """ Remove colors from the output (no escape sequences)""" 175 | for i in CONF["COLORS"]: 176 | if isinstance(CONF["COLORS"][i], dict): 177 | for j in CONF["COLORS"][i]: 178 | CONF["COLORS"][i][j] = "" 179 | else: 180 | CONF["COLORS"][i] = "" 181 | 182 | 183 | def enable_colors(colors): 184 | for i in colors: 185 | CONF["COLORS"][i] = colors[i] 186 | 187 | 188 | def save_colors(): 189 | c = {} 190 | for i in CONF["COLORS"]: 191 | if isinstance(CONF["COLORS"][i], dict): 192 | c[i] = {} 193 | for j in CONF["COLORS"][i]: 194 | c[i][j] = CONF["COLORS"][i][j] 195 | else: 196 | c[i] = CONF["COLORS"][i] 197 | return c 198 | 199 | 200 | def is_android(filename): 201 | """ 202 | Return the type of the file 203 | 204 | :param filename : the filename 205 | :returns: "APK", "DEX", None 206 | """ 207 | if not filename: 208 | return None 209 | 210 | with open(filename, "rb") as fd: 211 | f_bytes = fd.read() 212 | return is_android_raw(f_bytes) 213 | 214 | 215 | def is_android_raw(raw): 216 | """ 217 | Returns a string that describes the type of file, for common Android 218 | specific formats 219 | """ 220 | val = None 221 | 222 | # We do not check for META-INF/MANIFEST.MF, 223 | # as you also want to analyze unsigned APKs... 224 | # AndroidManifest.xml should be in every APK. 225 | # classes.dex and resources.arsc are not required! 226 | # if raw[0:2] == b"PK" and b'META-INF/MANIFEST.MF' in raw: 227 | # TODO this check might be still invalid. A ZIP file with stored APK inside would match as well. 228 | # probably it would be better to rewrite this and add more sanity checks. 229 | if raw[0:2] == b"PK" and b'AndroidManifest.xml' in raw: 230 | val = "APK" 231 | elif raw[0:3] == b"dex": 232 | val = "DEX" 233 | elif raw[0:3] == b"dey": 234 | val = "DEY" 235 | elif raw[0:4] == b"\x03\x00\x08\x00" or raw[0:4] == b"\x00\x00\x08\x00": 236 | val = "AXML" 237 | elif raw[0:4] == b"\x02\x00\x0C\x00": 238 | val = "ARSC" 239 | 240 | return val 241 | 242 | 243 | def show_logging(level=logging.INFO): 244 | """ 245 | enable log messages on stdout 246 | 247 | We will catch all messages here! From all loggers... 248 | """ 249 | logger = logging.getLogger() 250 | 251 | h = logging.StreamHandler(stream=sys.stderr) 252 | h.setFormatter(logging.Formatter(fmt="[%(levelname)-8s] %(name)s: %(message)s")) 253 | 254 | logger.addHandler(h) 255 | logger.setLevel(level) 256 | 257 | 258 | def set_options(key, value): 259 | """ 260 | .. deprecated:: 3.3.5 261 | Use :code:`CONF[key] = value` instead 262 | """ 263 | CONF[key] = value 264 | 265 | 266 | def rrmdir(directory): 267 | """ 268 | Recursivly delete a directory 269 | 270 | :param directory: directory to remove 271 | """ 272 | for root, dirs, files in os.walk(directory, topdown=False): 273 | for name in files: 274 | os.remove(os.path.join(root, name)) 275 | for name in dirs: 276 | os.rmdir(os.path.join(root, name)) 277 | os.rmdir(directory) 278 | 279 | 280 | def make_color_tuple(color): 281 | """ 282 | turn something like "#000000" into 0,0,0 283 | or "#FFFFFF into "255,255,255" 284 | """ 285 | R = color[1:3] 286 | G = color[3:5] 287 | B = color[5:7] 288 | 289 | R = int(R, 16) 290 | G = int(G, 16) 291 | B = int(B, 16) 292 | 293 | return R, G, B 294 | 295 | 296 | def interpolate_tuple(startcolor, goalcolor, steps): 297 | """ 298 | Take two RGB color sets and mix them over a specified number of steps. Return the list 299 | """ 300 | # white 301 | 302 | R = startcolor[0] 303 | G = startcolor[1] 304 | B = startcolor[2] 305 | 306 | targetR = goalcolor[0] 307 | targetG = goalcolor[1] 308 | targetB = goalcolor[2] 309 | 310 | DiffR = targetR - R 311 | DiffG = targetG - G 312 | DiffB = targetB - B 313 | 314 | buffer = [] 315 | 316 | for i in range(0, steps + 1): 317 | iR = R + (DiffR * i // steps) 318 | iG = G + (DiffG * i // steps) 319 | iB = B + (DiffB * i // steps) 320 | 321 | hR = str.replace(hex(iR), "0x", "") 322 | hG = str.replace(hex(iG), "0x", "") 323 | hB = str.replace(hex(iB), "0x", "") 324 | 325 | if len(hR) == 1: 326 | hR = "0" + hR 327 | if len(hB) == 1: 328 | hB = "0" + hB 329 | 330 | if len(hG) == 1: 331 | hG = "0" + hG 332 | 333 | color = str.upper("#" + hR + hG + hB) 334 | buffer.append(color) 335 | 336 | return buffer 337 | 338 | 339 | def color_range(startcolor, goalcolor, steps): 340 | """ 341 | wrapper for interpolate_tuple that accepts colors as html ("#CCCCC" and such) 342 | """ 343 | start_tuple = make_color_tuple(startcolor) 344 | goal_tuple = make_color_tuple(goalcolor) 345 | 346 | return interpolate_tuple(start_tuple, goal_tuple, steps) 347 | 348 | 349 | def load_api_specific_resource_module(resource_name, api=None): 350 | """ 351 | Load the module from the JSON files and return a dict, which might be empty 352 | if the resource could not be loaded. 353 | 354 | If no api version is given, the default one from the CONF dict is used. 355 | 356 | :param resource_name: Name of the resource to load 357 | :param api: API version 358 | :return: dict 359 | """ 360 | loader = dict(aosp_permissions=load_permissions, 361 | api_permission_mappings=load_permission_mappings) 362 | 363 | if resource_name not in loader: 364 | raise InvalidResourceError("Invalid Resource '{}', not in [{}]".format(resource_name, ", ".join(loader.keys()))) 365 | 366 | if not api: 367 | api = CONF["DEFAULT_API"] 368 | 369 | ret = loader[resource_name](api) 370 | 371 | if ret == {}: 372 | # No API mapping found, return default 373 | log.warning("API mapping for API level {} was not found! " 374 | "Returning default, which is API level {}".format(api, CONF['DEFAULT_API'])) 375 | ret = loader[resource_name](CONF['DEFAULT_API']) 376 | 377 | return ret 378 | 379 | -------------------------------------------------------------------------------- /androguard/core/api_specific_resources/__init__.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import re 4 | import logging 5 | 6 | log = logging.getLogger(__name__) 7 | 8 | 9 | class APILevelNotFoundError(Exception): 10 | pass 11 | 12 | 13 | def load_permissions(apilevel, permtype='permissions'): 14 | """ 15 | Load the Permissions for the given apilevel. 16 | 17 | The permissions lists are generated using this tool: https://github.com/U039b/aosp_permissions_extraction 18 | 19 | Has a fallback to select the maximum or minimal available API level. 20 | For example, if 28 is requested but only 26 is available, 26 is returned. 21 | If 5 is requested but 16 is available, 16 is returned. 22 | 23 | If an API level is requested which is in between of two API levels we got, 24 | the lower level is returned. For example, if 5,6,7,10 is available and 8 is 25 | requested, 7 is returned instead. 26 | 27 | :param apilevel: integer value of the API level 28 | :param permtype: either load permissions (:code:`'permissions'`) or 29 | permission groups (:code:`'groups'`) 30 | :return: a dictionary of {Permission Name: {Permission info} 31 | """ 32 | 33 | if permtype not in ['permissions', 'groups']: 34 | raise ValueError("The type of permission list is not known.") 35 | 36 | # Usually apilevel is supplied as string... 37 | apilevel = int(apilevel) 38 | 39 | root = os.path.dirname(os.path.realpath(__file__)) 40 | permissions_file = os.path.join(root, "aosp_permissions", "permissions_{}.json".format(apilevel)) 41 | 42 | levels = filter(lambda x: re.match(r'^permissions_\d+\.json$', x), os.listdir(os.path.join(root, "aosp_permissions"))) 43 | levels = list(map(lambda x: int(x[:-5].split('_')[1]), levels)) 44 | 45 | if not levels: 46 | log.error("No Permissions available, can not load!") 47 | return {} 48 | 49 | log.debug("Available API levels: {}".format(", ".join(map(str, sorted(levels))))) 50 | 51 | if not os.path.isfile(permissions_file): 52 | if apilevel > max(levels): 53 | log.warning("Requested API level {} is larger than maximum we have, returning API level {} instead.".format(apilevel, max(levels))) 54 | return load_permissions(max(levels), permtype) 55 | if apilevel < min(levels): 56 | log.warning("Requested API level {} is smaller than minimal we have, returning API level {} instead.".format(apilevel, max(levels))) 57 | return load_permissions(min(levels), permtype) 58 | 59 | # Missing level between existing ones, return the lower level 60 | lower_level = max(filter(lambda x: x < apilevel, levels)) 61 | log.warning("Requested API Level could not be found, using {} instead".format(lower_level)) 62 | return load_permissions(lower_level, permtype) 63 | 64 | with open(permissions_file, "r") as fp: 65 | return json.load(fp)[permtype] 66 | 67 | 68 | def load_permission_mappings(apilevel): 69 | """ 70 | Load the API/Permission mapping for the requested API level. 71 | If the requetsed level was not found, None is returned. 72 | 73 | :param apilevel: integer value of the API level, i.e. 24 for Android 7.0 74 | :return: a dictionary of {MethodSignature: [List of Permissions]} 75 | """ 76 | root = os.path.dirname(os.path.realpath(__file__)) 77 | permissions_file = os.path.join(root, "api_permission_mappings", "permissions_{}.json".format(apilevel)) 78 | 79 | if not os.path.isfile(permissions_file): 80 | return {} 81 | 82 | with open(permissions_file, "r") as fp: 83 | return json.load(fp) 84 | -------------------------------------------------------------------------------- /androguard/core/bytecodes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codehasan/dex2c/1866c6824941327cfcea294e717985be419f57c1/androguard/core/bytecodes/__init__.py -------------------------------------------------------------------------------- /androguard/core/bytecodes/axml/types.py: -------------------------------------------------------------------------------- 1 | # Type definiton for (type, data) tuples representing a value 2 | # See http://androidxref.com/9.0.0_r3/xref/frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h#262 3 | 4 | # The 'data' is either 0 or 1, specifying this resource is either 5 | # undefined or empty, respectively. 6 | TYPE_NULL = 0x00 7 | # The 'data' holds a ResTable_ref, a reference to another resource 8 | # table entry. 9 | TYPE_REFERENCE = 0x01 10 | # The 'data' holds an attribute resource identifier. 11 | TYPE_ATTRIBUTE = 0x02 12 | # The 'data' holds an index into the containing resource table's 13 | # global value string pool. 14 | TYPE_STRING = 0x03 15 | # The 'data' holds a single-precision floating point number. 16 | TYPE_FLOAT = 0x04 17 | # The 'data' holds a complex number encoding a dimension value 18 | # such as "100in". 19 | TYPE_DIMENSION = 0x05 20 | # The 'data' holds a complex number encoding a fraction of a 21 | # container. 22 | TYPE_FRACTION = 0x06 23 | # The 'data' holds a dynamic ResTable_ref, which needs to be 24 | # resolved before it can be used like a TYPE_REFERENCE. 25 | TYPE_DYNAMIC_REFERENCE = 0x07 26 | # The 'data' holds an attribute resource identifier, which needs to be resolved 27 | # before it can be used like a TYPE_ATTRIBUTE. 28 | TYPE_DYNAMIC_ATTRIBUTE = 0x08 29 | # Beginning of integer flavors... 30 | TYPE_FIRST_INT = 0x10 31 | # The 'data' is a raw integer value of the form n..n. 32 | TYPE_INT_DEC = 0x10 33 | # The 'data' is a raw integer value of the form 0xn..n. 34 | TYPE_INT_HEX = 0x11 35 | # The 'data' is either 0 or 1, for input "false" or "true" respectively. 36 | TYPE_INT_BOOLEAN = 0x12 37 | # Beginning of color integer flavors... 38 | TYPE_FIRST_COLOR_INT = 0x1c 39 | # The 'data' is a raw integer value of the form #aarrggbb. 40 | TYPE_INT_COLOR_ARGB8 = 0x1c 41 | # The 'data' is a raw integer value of the form #rrggbb. 42 | TYPE_INT_COLOR_RGB8 = 0x1d 43 | # The 'data' is a raw integer value of the form #argb. 44 | TYPE_INT_COLOR_ARGB4 = 0x1e 45 | # The 'data' is a raw integer value of the form #rgb. 46 | TYPE_INT_COLOR_RGB4 = 0x1f 47 | # ...end of integer flavors. 48 | TYPE_LAST_COLOR_INT = 0x1f 49 | # ...end of integer flavors. 50 | TYPE_LAST_INT = 0x1f 51 | 52 | -------------------------------------------------------------------------------- /androguard/core/bytecodes/dvm_types.py: -------------------------------------------------------------------------------- 1 | # This file contains dictionaries used in the Dalvik Format. 2 | 3 | # https://source.android.com/devices/tech/dalvik/dex-format#type-codes 4 | TYPE_MAP_ITEM = { 5 | 0x0: "TYPE_HEADER_ITEM", 6 | 0x1: "TYPE_STRING_ID_ITEM", 7 | 0x2: "TYPE_TYPE_ID_ITEM", 8 | 0x3: "TYPE_PROTO_ID_ITEM", 9 | 0x4: "TYPE_FIELD_ID_ITEM", 10 | 0x5: "TYPE_METHOD_ID_ITEM", 11 | 0x6: "TYPE_CLASS_DEF_ITEM", 12 | 0x1000: "TYPE_MAP_LIST", 13 | 0x1001: "TYPE_TYPE_LIST", 14 | 0x1002: "TYPE_ANNOTATION_SET_REF_LIST", 15 | 0x1003: "TYPE_ANNOTATION_SET_ITEM", 16 | 0x2000: "TYPE_CLASS_DATA_ITEM", 17 | 0x2001: "TYPE_CODE_ITEM", 18 | 0x2002: "TYPE_STRING_DATA_ITEM", 19 | 0x2003: "TYPE_DEBUG_INFO_ITEM", 20 | 0x2004: "TYPE_ANNOTATION_ITEM", 21 | 0x2005: "TYPE_ENCODED_ARRAY_ITEM", 22 | 0x2006: "TYPE_ANNOTATIONS_DIRECTORY_ITEM", 23 | } 24 | 25 | # https://source.android.com/devices/tech/dalvik/dex-format#access-flags 26 | ACCESS_FLAGS = { 27 | 0x1: 'public', 28 | 0x2: 'private', 29 | 0x4: 'protected', 30 | 0x8: 'static', 31 | 0x10: 'final', 32 | 0x20: 'synchronized', 33 | 0x40: 'bridge', 34 | 0x80: 'varargs', 35 | 0x100: 'native', 36 | 0x200: 'interface', 37 | 0x400: 'abstract', 38 | 0x800: 'strictfp', 39 | 0x1000: 'synthetic', 40 | 0x4000: 'enum', 41 | 0x8000: 'unused', 42 | 0x10000: 'constructor', 43 | 0x20000: 'synchronized', 44 | } 45 | 46 | # https://source.android.com/devices/tech/dalvik/dex-format#typedescriptor 47 | TYPE_DESCRIPTOR = { 48 | 'V': 'void', 49 | 'Z': 'boolean', 50 | 'B': 'byte', 51 | 'S': 'short', 52 | 'C': 'char', 53 | 'I': 'int', 54 | 'J': 'long', 55 | 'F': 'float', 56 | 'D': 'double', 57 | } 58 | 59 | -------------------------------------------------------------------------------- /androguard/core/bytecodes/mutf8.py: -------------------------------------------------------------------------------- 1 | import builtins 2 | from builtins import str 3 | import struct 4 | 5 | 6 | def chr(val): 7 | """ 8 | Patched Version of builtins.chr, to work with narrow python builds 9 | In those versions, the function unichr does not work with inputs >0x10000 10 | 11 | This seems to be a problem usually on older windows builds. 12 | 13 | :param val: integer value of character 14 | :return: character 15 | """ 16 | try: 17 | return builtins.chr(val) 18 | except ValueError as e: 19 | if "(narrow Python build)" in str(e): 20 | return struct.pack('i', val).decode('utf-32') 21 | else: 22 | raise e 23 | 24 | 25 | def decode(b): 26 | """ 27 | Decode bytes as MUTF-8 28 | See https://docs.oracle.com/javase/6/docs/api/java/io/DataInput.html#modified-utf-8 29 | for more information 30 | 31 | Surrogates will be returned as two 16 bit characters. 32 | 33 | :param b: bytes to decode 34 | :rtype: unicode (py2), str (py3) of 16bit chars 35 | :raises: UnicodeDecodeError if string is not decodable 36 | """ 37 | res = u"" 38 | 39 | b = iter(bytearray(b)) 40 | 41 | for x in b: 42 | if x >> 7 == 0: 43 | # Single char: 44 | res += chr(x & 0x7f) 45 | elif x >> 5 == 0b110: 46 | # 2 byte Multichar 47 | b2 = next(b) 48 | if b2 >> 6 != 0b10: 49 | raise UnicodeDecodeError("Second byte of 2 byte sequence does not looks right.") 50 | 51 | res += chr((x & 0x1f) << 6 | b2 & 0x3f) 52 | elif x >> 4 == 0b1110: 53 | # 3 byte Multichar 54 | b2 = next(b) 55 | b3 = next(b) 56 | if b2 >> 6 != 0b10: 57 | raise UnicodeDecodeError("Second byte of 3 byte sequence does not looks right.") 58 | if b3 >> 6 != 0b10: 59 | raise UnicodeDecodeError("Third byte of 3 byte sequence does not looks right.") 60 | 61 | res += chr((x & 0xf) << 12 | (b2 & 0x3f) << 6 | b3 & 0x3f) 62 | else: 63 | raise UnicodeDecodeError("Could not decode byte") 64 | 65 | return res 66 | 67 | 68 | class PeekIterator: 69 | """ 70 | A quick'n'dirty variant of an Iterator that has a special function 71 | peek, which will return the next object but not consume it. 72 | """ 73 | idx = 0 74 | 75 | def __init__(self, s): 76 | self.s = s 77 | 78 | def __iter__(self): 79 | return self 80 | 81 | def __next__(self): 82 | if self.idx == len(self.s): 83 | raise StopIteration() 84 | self.idx = self.idx + 1 85 | return self.s[self.idx - 1] 86 | 87 | def next(self): 88 | # py2 compliance 89 | return self.__next__() 90 | 91 | def peek(self): 92 | if self.idx == len(self.s): 93 | return None 94 | return self.s[self.idx] 95 | 96 | 97 | def patch_string(s): 98 | """ 99 | Reorganize a String in such a way that surrogates are printable 100 | and lonely surrogates are escaped. 101 | 102 | :param s: input string 103 | :return: string with escaped lonely surrogates and 32bit surrogates 104 | """ 105 | res = u'' 106 | it = PeekIterator(s) 107 | for c in it: 108 | if (ord(c) >> 10) == 0b110110: 109 | # High surrogate 110 | # Check for the next 111 | n = it.peek() 112 | if n and (ord(n) >> 10) == 0b110111: 113 | # Next is a low surrogate! Merge them together 114 | res += chr(((ord(c) & 0x3ff) << 10 | (ord(n) & 0x3ff)) + 0x10000) 115 | # Skip next char, as we already consumed it 116 | next(it) 117 | else: 118 | # Lonely high surrogate 119 | res += u"\\u{:04x}".format(ord(c)) 120 | elif (ord(c) >> 10) == 0b110111: 121 | # Lonely low surrogate 122 | res += u"\\u{:04x}".format(ord(c)) 123 | else: 124 | # Looks like a normal char... 125 | res += c 126 | return res 127 | 128 | -------------------------------------------------------------------------------- /androguard/core/resources/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codehasan/dex2c/1866c6824941327cfcea294e717985be419f57c1/androguard/core/resources/__init__.py -------------------------------------------------------------------------------- /androguard/core/resources/public.py: -------------------------------------------------------------------------------- 1 | import os 2 | from xml.dom import minidom 3 | 4 | _public_res = None 5 | # copy the newest sdk/platforms/android-?/data/res/values/public.xml here 6 | if _public_res is None: 7 | _public_res = {} 8 | root = os.path.dirname(os.path.realpath(__file__)) 9 | xmlfile = os.path.join(root, "public.xml") 10 | if os.path.isfile(xmlfile): 11 | with open(xmlfile, "r") as fp: 12 | _xml = minidom.parseString(fp.read()) 13 | for element in _xml.getElementsByTagName("public"): 14 | _type = element.getAttribute('type') 15 | _name = element.getAttribute('name') 16 | _id = int(element.getAttribute('id'), 16) 17 | if _type not in _public_res: 18 | _public_res[_type] = {} 19 | _public_res[_type][_name] = _id 20 | else: 21 | raise Exception("need to copy the sdk/platforms/android-?/data/res/values/public.xml here") 22 | 23 | SYSTEM_RESOURCES = { 24 | "attributes": { 25 | "forward": {k: v for k, v in _public_res['attr'].items()}, 26 | "inverse": {v: k for k, v in _public_res['attr'].items()} 27 | }, 28 | "styles": { 29 | "forward": {k: v for k, v in _public_res['style'].items()}, 30 | "inverse": {v: k for k, v in _public_res['style'].items()} 31 | } 32 | } 33 | 34 | 35 | 36 | if __name__ == '__main__': 37 | import json 38 | _resources = None 39 | if _resources is None: 40 | root = os.path.dirname(os.path.realpath(__file__)) 41 | resfile = os.path.join(root, "public.json") 42 | 43 | if os.path.isfile(resfile): 44 | with open(resfile, "r") as fp: 45 | _resources = json.load(fp) 46 | else: 47 | # TODO raise error instead? 48 | _resources = {} 49 | for _type in set([] + list(_public_res.keys()) + list(_resources.keys())): 50 | for k in set([] + list(_public_res.get(_type, {}).keys()) 51 | + list(_resources.get(_type, {}).keys())): 52 | a,b = _public_res.get(_type, {}).get(k), \ 53 | _resources.get(_type, {}).get(k), 54 | if a != b: 55 | print(k, a,b) 56 | print(None) -------------------------------------------------------------------------------- /androguard/decompiler/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /androguard/decompiler/dad/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /androguard/decompiler/dad/basic_blocks.py: -------------------------------------------------------------------------------- 1 | # This file is part of Androguard. 2 | # 3 | # Copyright (c) 2012 Geoffroy Gueguen 4 | # All Rights Reserved. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS-IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | from builtins import zip 19 | from builtins import range 20 | from builtins import object 21 | import logging 22 | from collections import defaultdict 23 | from androguard.decompiler.dad.opcode_ins import INSTRUCTION_SET 24 | from androguard.decompiler.dad.instruction import MoveExceptionExpression 25 | from androguard.decompiler.dad.node import Node 26 | from androguard.decompiler.dad.util import get_type 27 | 28 | logger = logging.getLogger('dad.basic_blocks') 29 | 30 | 31 | class BasicBlock(Node): 32 | def __init__(self, name, block_ins): 33 | super(BasicBlock, self).__init__(name) 34 | self.ins = block_ins 35 | self.ins_range = None 36 | self.loc_ins = None 37 | self.var_to_declare = set() 38 | self.catch_type = None 39 | 40 | def get_ins(self): 41 | return self.ins 42 | 43 | def get_loc_with_ins(self): 44 | if self.loc_ins is None: 45 | self.loc_ins = list(zip(range(*self.ins_range), self.ins)) 46 | return self.loc_ins 47 | 48 | def remove_ins(self, loc, ins): 49 | self.ins.remove(ins) 50 | self.loc_ins.remove((loc, ins)) 51 | 52 | def add_ins(self, new_ins_list): 53 | for new_ins in new_ins_list: 54 | self.ins.append(new_ins) 55 | 56 | def add_variable_declaration(self, variable): 57 | self.var_to_declare.add(variable) 58 | 59 | def number_ins(self, num): 60 | last_ins_num = num + len(self.ins) 61 | self.ins_range = [num, last_ins_num] 62 | self.loc_ins = None 63 | return last_ins_num 64 | 65 | def set_catch_type(self, _type): 66 | self.catch_type = _type 67 | 68 | 69 | class StatementBlock(BasicBlock): 70 | def __init__(self, name, block_ins): 71 | super(StatementBlock, self).__init__(name, block_ins) 72 | self.type.is_stmt = True 73 | 74 | def visit(self, visitor): 75 | return visitor.visit_statement_node(self) 76 | 77 | def __str__(self): 78 | return '%d-Statement(%s)' % (self.num, self.name) 79 | 80 | 81 | class ReturnBlock(BasicBlock): 82 | def __init__(self, name, block_ins): 83 | super(ReturnBlock, self).__init__(name, block_ins) 84 | self.type.is_return = True 85 | 86 | def visit(self, visitor): 87 | return visitor.visit_return_node(self) 88 | 89 | def __str__(self): 90 | return '%d-Return(%s)' % (self.num, self.name) 91 | 92 | 93 | class ThrowBlock(BasicBlock): 94 | def __init__(self, name, block_ins): 95 | super(ThrowBlock, self).__init__(name, block_ins) 96 | self.type.is_throw = True 97 | 98 | def visit(self, visitor): 99 | return visitor.visit_throw_node(self) 100 | 101 | def __str__(self): 102 | return '%d-Throw(%s)' % (self.num, self.name) 103 | 104 | 105 | class SwitchBlock(BasicBlock): 106 | def __init__(self, name, switch, block_ins): 107 | super(SwitchBlock, self).__init__(name, block_ins) 108 | self.switch = switch 109 | self.cases = [] 110 | self.default = None 111 | self.node_to_case = defaultdict(list) 112 | self.type.is_switch = True 113 | 114 | def add_case(self, case): 115 | self.cases.append(case) 116 | 117 | def visit(self, visitor): 118 | return visitor.visit_switch_node(self) 119 | 120 | def copy_from(self, node): 121 | super(SwitchBlock, self).copy_from(node) 122 | self.cases = node.cases[:] 123 | self.switch = node.switch[:] 124 | 125 | def update_attribute_with(self, n_map): 126 | super(SwitchBlock, self).update_attribute_with(n_map) 127 | self.cases = [n_map.get(n, n) for n in self.cases] 128 | for node1, node2 in n_map.items(): 129 | if node1 in self.node_to_case: 130 | self.node_to_case[node2] = self.node_to_case.pop(node1) 131 | 132 | def order_cases(self): 133 | values = self.switch.get_values() 134 | if len(values) < len(self.cases): 135 | self.default = self.cases.pop(0) 136 | for case, node in zip(values, self.cases): 137 | self.node_to_case[node].append(case) 138 | 139 | def __str__(self): 140 | return '%d-Switch(%s)' % (self.num, self.name) 141 | 142 | 143 | class CondBlock(BasicBlock): 144 | def __init__(self, name, block_ins): 145 | super(CondBlock, self).__init__(name, block_ins) 146 | self.true = None 147 | self.false = None 148 | self.type.is_cond = True 149 | 150 | def update_attribute_with(self, n_map): 151 | super(CondBlock, self).update_attribute_with(n_map) 152 | self.true = n_map.get(self.true, self.true) 153 | self.false = n_map.get(self.false, self.false) 154 | 155 | def neg(self): 156 | if len(self.ins) != 1: 157 | raise RuntimeWarning('Condition should have only 1 instruction !') 158 | self.ins[-1].neg() 159 | 160 | def visit(self, visitor): 161 | return visitor.visit_cond_node(self) 162 | 163 | def visit_cond(self, visitor): 164 | if len(self.ins) != 1: 165 | raise RuntimeWarning('Condition should have only 1 instruction !') 166 | return visitor.visit_ins(self.ins[-1]) 167 | 168 | def __str__(self): 169 | return '%d-If(%s)' % (self.num, self.name) 170 | 171 | 172 | class Condition(object): 173 | def __init__(self, cond1, cond2, isand, isnot): 174 | self.cond1 = cond1 175 | self.cond2 = cond2 176 | self.isand = isand 177 | self.isnot = isnot 178 | 179 | def neg(self): 180 | self.isand = not self.isand 181 | self.cond1.neg() 182 | self.cond2.neg() 183 | 184 | def get_ins(self): 185 | lins = [] 186 | lins.extend(self.cond1.get_ins()) 187 | lins.extend(self.cond2.get_ins()) 188 | return lins 189 | 190 | def get_loc_with_ins(self): 191 | loc_ins = [] 192 | loc_ins.extend(self.cond1.get_loc_with_ins()) 193 | loc_ins.extend(self.cond2.get_loc_with_ins()) 194 | return loc_ins 195 | 196 | def visit(self, visitor): 197 | return visitor.visit_short_circuit_condition(self.isnot, self.isand, 198 | self.cond1, self.cond2) 199 | 200 | def __str__(self): 201 | if self.isnot: 202 | ret = '!%s %s %s' 203 | else: 204 | ret = '%s %s %s' 205 | return ret % (self.cond1, ['||', '&&'][self.isand], self.cond2) 206 | 207 | 208 | class ShortCircuitBlock(CondBlock): 209 | def __init__(self, name, cond): 210 | super(ShortCircuitBlock, self).__init__(name, None) 211 | self.cond = cond 212 | 213 | def get_ins(self): 214 | return self.cond.get_ins() 215 | 216 | def get_loc_with_ins(self): 217 | return self.cond.get_loc_with_ins() 218 | 219 | def neg(self): 220 | self.cond.neg() 221 | 222 | def visit_cond(self, visitor): 223 | return self.cond.visit(visitor) 224 | 225 | def __str__(self): 226 | return '%d-SC(%s)' % (self.num, self.cond) 227 | 228 | 229 | class LoopBlock(CondBlock): 230 | def __init__(self, name, cond): 231 | super(LoopBlock, self).__init__(name, None) 232 | self.cond = cond 233 | 234 | def get_ins(self): 235 | return self.cond.get_ins() 236 | 237 | def neg(self): 238 | self.cond.neg() 239 | 240 | def get_loc_with_ins(self): 241 | return self.cond.get_loc_with_ins() 242 | 243 | def visit(self, visitor): 244 | return visitor.visit_loop_node(self) 245 | 246 | def visit_cond(self, visitor): 247 | return self.cond.visit_cond(visitor) 248 | 249 | def update_attribute_with(self, n_map): 250 | super(LoopBlock, self).update_attribute_with(n_map) 251 | self.cond.update_attribute_with(n_map) 252 | 253 | def __str__(self): 254 | if self.looptype.is_pretest: 255 | if self.false in self.loop_nodes: 256 | return '%d-While(!%s)[%s]' % (self.num, self.name, self.cond) 257 | return '%d-While(%s)[%s]' % (self.num, self.name, self.cond) 258 | elif self.looptype.is_posttest: 259 | return '%d-DoWhile(%s)[%s]' % (self.num, self.name, self.cond) 260 | elif self.looptype.is_endless: 261 | return '%d-WhileTrue(%s)[%s]' % (self.num, self.name, self.cond) 262 | return '%d-WhileNoType(%s)' % (self.num, self.name) 263 | 264 | 265 | class TryBlock(BasicBlock): 266 | def __init__(self, node): 267 | super(TryBlock, self).__init__('Try-%s' % node.name, None) 268 | self.try_start = node 269 | self.catch = [] 270 | 271 | # FIXME: 272 | @property 273 | def num(self): 274 | return self.try_start.num 275 | 276 | @num.setter 277 | def num(self, value): 278 | pass 279 | 280 | def add_catch_node(self, node): 281 | self.catch.append(node) 282 | 283 | def visit(self, visitor): 284 | visitor.visit_try_node(self) 285 | 286 | def __str__(self): 287 | return 'Try(%s)[%s]' % (self.name, self.catch) 288 | 289 | 290 | class CatchBlock(BasicBlock): 291 | def __init__(self, node): 292 | first_ins = node.ins[0] 293 | self.exception_ins = None 294 | if isinstance(first_ins, MoveExceptionExpression): 295 | self.exception_ins = first_ins 296 | node.ins.pop(0) 297 | super(CatchBlock, self).__init__('Catch-%s' % node.name, node.ins) 298 | self.catch_start = node 299 | self.catch_type = node.catch_type 300 | 301 | def visit(self, visitor): 302 | visitor.visit_catch_node(self) 303 | 304 | def visit_exception(self, visitor): 305 | if self.exception_ins: 306 | visitor.visit_ins(self.exception_ins) 307 | else: 308 | visitor.write(get_type(self.catch_type)) 309 | 310 | def __str__(self): 311 | return 'Catch(%s)' % self.name 312 | 313 | 314 | def build_node_from_block(block, vmap, gen_ret, exception_type=None): 315 | ins, lins = None, [] 316 | idx = block.get_start() 317 | for ins in block.get_instructions(): 318 | opcode = ins.get_op_value() 319 | if opcode == -1: # FIXME? or opcode in (0x0300, 0x0200, 0x0100): 320 | idx += ins.get_length() 321 | continue 322 | try: 323 | _ins = INSTRUCTION_SET[opcode] 324 | except IndexError: 325 | logger.error('Unknown instruction : %s.', ins.get_name().lower()) 326 | _ins = INSTRUCTION_SET[0] 327 | # fill-array-data 328 | if opcode == 0x26: 329 | fillarray = block.get_special_ins(idx) 330 | lins.append(_ins(ins, vmap, fillarray)) 331 | # invoke-kind[/range] 332 | elif 0x6e <= opcode <= 0x72 or 0x74 <= opcode <= 0x78: 333 | lins.append(_ins(ins, vmap, gen_ret)) 334 | # filled-new-array[/range] 335 | elif 0x24 <= opcode <= 0x25: 336 | lins.append(_ins(ins, vmap, gen_ret.new())) 337 | # move-result* 338 | elif 0xa <= opcode <= 0xc: 339 | lins.append(_ins(ins, vmap, gen_ret.last())) 340 | # move-exception 341 | elif opcode == 0xd: 342 | lins.append(_ins(ins, vmap, exception_type)) 343 | # monitor-{enter,exit} 344 | elif 0x1d <= opcode <= 0x1e: 345 | idx += ins.get_length() 346 | continue 347 | else: 348 | lins.append(_ins(ins, vmap)) 349 | idx += ins.get_length() 350 | name = block.get_name() 351 | # return* 352 | if 0xe <= opcode <= 0x11: 353 | node = ReturnBlock(name, lins) 354 | # {packed,sparse}-switch 355 | elif 0x2b <= opcode <= 0x2c: 356 | idx -= ins.get_length() 357 | values = block.get_special_ins(idx) 358 | node = SwitchBlock(name, values, lins) 359 | # if-test[z] 360 | elif 0x32 <= opcode <= 0x3d: 361 | node = CondBlock(name, lins) 362 | node.off_last_ins = ins.get_ref_off() 363 | # throw 364 | elif opcode == 0x27: 365 | node = ThrowBlock(name, lins) 366 | else: 367 | # goto* 368 | if 0x28 <= opcode <= 0x2a: 369 | lins.pop() 370 | node = StatementBlock(name, lins) 371 | return node 372 | -------------------------------------------------------------------------------- /androguard/decompiler/dad/control_flow.py: -------------------------------------------------------------------------------- 1 | # This file is part of Androguard. 2 | # 3 | # Copyright (c) 2012 Geoffroy Gueguen 4 | # All Rights Reserved. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS-IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | import logging 19 | from collections import defaultdict 20 | from androguard.decompiler.dad.basic_blocks import ( 21 | CatchBlock, Condition, LoopBlock, ShortCircuitBlock, TryBlock) 22 | from androguard.decompiler.dad.graph import Graph 23 | from androguard.decompiler.dad.node import Interval 24 | from androguard.decompiler.dad.util import common_dom 25 | 26 | logger = logging.getLogger('dad.control_flow') 27 | 28 | 29 | def intervals(graph): 30 | """ 31 | Compute the intervals of the graph 32 | Returns 33 | interval_graph: a graph of the intervals of G 34 | interv_heads: a dict of (header node, interval) 35 | """ 36 | interval_graph = Graph() # graph of intervals 37 | heads = [graph.entry] # list of header nodes 38 | interv_heads = {} # interv_heads[i] = interval of header i 39 | processed = dict([(i, False) for i in graph]) 40 | edges = defaultdict(list) 41 | 42 | while heads: 43 | head = heads.pop(0) 44 | 45 | if not processed[head]: 46 | processed[head] = True 47 | interv_heads[head] = Interval(head) 48 | 49 | # Check if there is a node which has all its predecessor in the 50 | # current interval. If there is, add that node to the interval and 51 | # repeat until all the possible nodes have been added. 52 | change = True 53 | while change: 54 | change = False 55 | for node in graph.rpo[1:]: 56 | if all( 57 | p in interv_heads[head] for p in graph.all_preds(node)): 58 | change |= interv_heads[head].add_node(node) 59 | 60 | # At this stage, a node which is not in the interval, but has one 61 | # of its predecessor in it, is the header of another interval. So 62 | # we add all such nodes to the header list. 63 | for node in graph: 64 | if node not in interv_heads[head] and node not in heads: 65 | if any( 66 | p in interv_heads[head] for p in graph.all_preds(node)): 67 | edges[interv_heads[head]].append(node) 68 | assert (node not in heads) 69 | heads.append(node) 70 | 71 | interval_graph.add_node(interv_heads[head]) 72 | interv_heads[head].compute_end(graph) 73 | 74 | # Edges is a mapping of 'Interval -> [header nodes of interval successors]' 75 | for interval, heads in edges.items(): 76 | for head in heads: 77 | interval_graph.add_edge(interval, interv_heads[head]) 78 | 79 | interval_graph.entry = graph.entry.interval 80 | if graph.exit: 81 | interval_graph.exit = graph.exit.interval 82 | 83 | return interval_graph, interv_heads 84 | 85 | 86 | def derived_sequence(graph): 87 | """ 88 | Compute the derived sequence of the graph G 89 | The intervals of G are collapsed into nodes, intervals of these nodes are 90 | built, and the process is repeated iteratively until we obtain a single 91 | node (if the graph is not irreducible) 92 | """ 93 | deriv_seq = [graph] 94 | deriv_interv = [] 95 | single_node = False 96 | 97 | while not single_node: 98 | 99 | interv_graph, interv_heads = intervals(graph) 100 | deriv_interv.append(interv_heads) 101 | 102 | single_node = len(interv_graph) == 1 103 | if not single_node: 104 | deriv_seq.append(interv_graph) 105 | 106 | graph = interv_graph 107 | graph.compute_rpo() 108 | 109 | return deriv_seq, deriv_interv 110 | 111 | 112 | def mark_loop_rec(graph, node, s_num, e_num, interval, nodes_in_loop): 113 | if node in nodes_in_loop: 114 | return 115 | nodes_in_loop.append(node) 116 | for pred in graph.all_preds(node): 117 | if s_num < pred.num <= e_num and pred in interval: 118 | mark_loop_rec(graph, pred, s_num, e_num, interval, nodes_in_loop) 119 | 120 | 121 | def mark_loop(graph, start, end, interval): 122 | logger.debug('MARKLOOP : %s END : %s', start, end) 123 | head = start.get_head() 124 | latch = end.get_end() 125 | nodes_in_loop = [head] 126 | mark_loop_rec(graph, latch, head.num, latch.num, interval, nodes_in_loop) 127 | head.startloop = True 128 | head.latch = latch 129 | return nodes_in_loop 130 | 131 | 132 | def loop_type(start, end, nodes_in_loop): 133 | if end.type.is_cond: 134 | if start.type.is_cond: 135 | if start.true in nodes_in_loop and start.false in nodes_in_loop: 136 | start.looptype.is_posttest = True 137 | else: 138 | start.looptype.is_pretest = True 139 | else: 140 | start.looptype.is_posttest = True 141 | else: 142 | if start.type.is_cond: 143 | if start.true in nodes_in_loop and start.false in nodes_in_loop: 144 | start.looptype.is_endless = True 145 | else: 146 | start.looptype.is_pretest = True 147 | else: 148 | start.looptype.is_endless = True 149 | 150 | 151 | def loop_follow(start, end, nodes_in_loop): 152 | follow = None 153 | if start.looptype.is_pretest: 154 | if start.true in nodes_in_loop: 155 | follow = start.false 156 | else: 157 | follow = start.true 158 | elif start.looptype.is_posttest: 159 | if end.true in nodes_in_loop: 160 | follow = end.false 161 | else: 162 | follow = end.true 163 | else: 164 | num_next = float('inf') 165 | for node in nodes_in_loop: 166 | if node.type.is_cond: 167 | if (node.true.num < num_next and 168 | node.true not in nodes_in_loop): 169 | follow = node.true 170 | num_next = follow.num 171 | elif (node.false.num < num_next and 172 | node.false not in nodes_in_loop): 173 | follow = node.false 174 | num_next = follow.num 175 | start.follow['loop'] = follow 176 | for node in nodes_in_loop: 177 | node.follow['loop'] = follow 178 | logger.debug('Start of loop %s', start) 179 | logger.debug('Follow of loop: %s', start.follow['loop']) 180 | 181 | 182 | def loop_struct(graphs_list, intervals_list): 183 | first_graph = graphs_list[0] 184 | for i, graph in enumerate(graphs_list): 185 | interval = intervals_list[i] 186 | for head in sorted(list(interval.keys()), key=lambda x: x.num): 187 | loop_nodes = [] 188 | for node in graph.all_preds(head): 189 | if node.interval is head.interval: 190 | lnodes = mark_loop(first_graph, head, node, head.interval) 191 | for lnode in lnodes: 192 | if lnode not in loop_nodes: 193 | loop_nodes.append(lnode) 194 | head.get_head().loop_nodes = loop_nodes 195 | 196 | 197 | def if_struct(graph, idoms): 198 | unresolved = set() 199 | for node in graph.post_order(): 200 | if node.type.is_cond: 201 | ldominates = [] 202 | for n, idom in idoms.items(): 203 | if node is idom and len(graph.reverse_edges.get(n, [])) > 1: 204 | ldominates.append(n) 205 | if len(ldominates) > 0: 206 | n = max(ldominates, key=lambda x: x.num) 207 | node.follow['if'] = n 208 | for x in unresolved.copy(): 209 | if node.num < x.num < n.num: 210 | x.follow['if'] = n 211 | unresolved.remove(x) 212 | else: 213 | unresolved.add(node) 214 | return unresolved 215 | 216 | 217 | def switch_struct(graph, idoms): 218 | unresolved = set() 219 | for node in graph.post_order(): 220 | if node.type.is_switch: 221 | m = node 222 | for suc in graph.sucs(node): 223 | if idoms[suc] is not node: 224 | m = common_dom(idoms, node, suc) 225 | ldominates = [] 226 | for n, dom in idoms.items(): 227 | if m is dom and len(graph.all_preds(n)) > 1: 228 | ldominates.append(n) 229 | if len(ldominates) > 0: 230 | n = max(ldominates, key=lambda x: x.num) 231 | node.follow['switch'] = n 232 | for x in unresolved: 233 | x.follow['switch'] = n 234 | unresolved = set() 235 | else: 236 | unresolved.add(node) 237 | node.order_cases() 238 | 239 | 240 | # TODO: deal with preds which are in catch 241 | def short_circuit_struct(graph, idom, node_map): 242 | def MergeNodes(node1, node2, is_and, is_not): 243 | lpreds = set() 244 | ldests = set() 245 | for node in (node1, node2): 246 | lpreds.update(graph.preds(node)) 247 | ldests.update(graph.sucs(node)) 248 | graph.remove_node(node) 249 | done.add(node) 250 | lpreds.difference_update((node1, node2)) 251 | ldests.difference_update((node1, node2)) 252 | 253 | entry = graph.entry in (node1, node2) 254 | 255 | new_name = '%s+%s' % (node1.name, node2.name) 256 | condition = Condition(node1, node2, is_and, is_not) 257 | 258 | new_node = ShortCircuitBlock(new_name, condition) 259 | for old_n, new_n in node_map.items(): 260 | if new_n in (node1, node2): 261 | node_map[old_n] = new_node 262 | node_map[node1] = new_node 263 | node_map[node2] = new_node 264 | idom[new_node] = idom[node1] 265 | idom.pop(node1) 266 | idom.pop(node2) 267 | new_node.copy_from(node1) 268 | 269 | graph.add_node(new_node) 270 | 271 | for pred in lpreds: 272 | pred.update_attribute_with(node_map) 273 | graph.add_edge(node_map.get(pred, pred), new_node) 274 | for dest in ldests: 275 | graph.add_edge(new_node, node_map.get(dest, dest)) 276 | if entry: 277 | graph.entry = new_node 278 | return new_node 279 | 280 | change = True 281 | while change: 282 | change = False 283 | done = set() 284 | for node in graph.post_order(): 285 | if node.type.is_cond and node not in done: 286 | then = node.true 287 | els = node.false 288 | if node in (then, els): 289 | continue 290 | if then.type.is_cond and len(graph.preds(then)) == 1: 291 | if node in (then.true, then.false): 292 | continue 293 | if then.false is els: # node && t 294 | change = True 295 | merged_node = MergeNodes(node, then, True, False) 296 | merged_node.true = then.true 297 | merged_node.false = els 298 | elif then.true is els: # !node || t 299 | change = True 300 | merged_node = MergeNodes(node, then, False, True) 301 | merged_node.true = els 302 | merged_node.false = then.false 303 | elif els.type.is_cond and len(graph.preds(els)) == 1: 304 | if node in (els.false, els.true): 305 | continue 306 | if els.false is then: # !node && e 307 | change = True 308 | merged_node = MergeNodes(node, els, True, True) 309 | merged_node.true = els.true 310 | merged_node.false = then 311 | elif els.true is then: # node || e 312 | change = True 313 | merged_node = MergeNodes(node, els, False, False) 314 | merged_node.true = then 315 | merged_node.false = els.false 316 | done.add(node) 317 | if change: 318 | graph.compute_rpo() 319 | 320 | 321 | def while_block_struct(graph, node_map): 322 | change = False 323 | for node in graph.rpo[:]: 324 | if node.startloop: 325 | change = True 326 | new_node = LoopBlock(node.name, node) 327 | node_map[node] = new_node 328 | new_node.copy_from(node) 329 | 330 | entry = node is graph.entry 331 | lpreds = graph.preds(node) 332 | lsuccs = graph.sucs(node) 333 | 334 | for pred in lpreds: 335 | graph.add_edge(node_map.get(pred, pred), new_node) 336 | 337 | for suc in lsuccs: 338 | graph.add_edge(new_node, node_map.get(suc, suc)) 339 | if entry: 340 | graph.entry = new_node 341 | 342 | if node.type.is_cond: 343 | new_node.true = node.true 344 | new_node.false = node.false 345 | 346 | graph.add_node(new_node) 347 | graph.remove_node(node) 348 | 349 | if change: 350 | graph.compute_rpo() 351 | 352 | 353 | def catch_struct(graph, idoms): 354 | block_try_nodes = {} 355 | node_map = {} 356 | for catch_block in graph.reverse_catch_edges: 357 | if catch_block in graph.catch_edges: 358 | continue 359 | catch_node = CatchBlock(catch_block) 360 | 361 | try_block = idoms[catch_block] 362 | try_node = block_try_nodes.get(try_block) 363 | if try_node is None: 364 | block_try_nodes[try_block] = TryBlock(try_block) 365 | try_node = block_try_nodes[try_block] 366 | 367 | node_map[try_block] = try_node 368 | for pred in graph.all_preds(try_block): 369 | pred.update_attribute_with(node_map) 370 | if try_block in graph.sucs(pred): 371 | graph.edges[pred].remove(try_block) 372 | graph.add_edge(pred, try_node) 373 | 374 | if try_block.type.is_stmt: 375 | follow = graph.sucs(try_block) 376 | if follow: 377 | try_node.follow = graph.sucs(try_block)[0] 378 | else: 379 | try_node.follow = None 380 | elif try_block.type.is_cond: 381 | loop_follow = try_block.follow['loop'] 382 | if loop_follow: 383 | try_node.follow = loop_follow 384 | else: 385 | try_node.follow = try_block.follow['if'] 386 | elif try_block.type.is_switch: 387 | try_node.follow = try_block.follow['switch'] 388 | else: # return or throw 389 | try_node.follow = None 390 | 391 | try_node.add_catch_node(catch_node) 392 | for node in graph.nodes: 393 | node.update_attribute_with(node_map) 394 | if graph.entry in node_map: 395 | graph.entry = node_map[graph.entry] 396 | 397 | 398 | def update_dom(idoms, node_map): 399 | for n, dom in idoms.items(): 400 | idoms[n] = node_map.get(dom, dom) 401 | 402 | 403 | def identify_structures(graph, idoms): 404 | Gi, Li = derived_sequence(graph) 405 | switch_struct(graph, idoms) 406 | loop_struct(Gi, Li) 407 | node_map = {} 408 | 409 | short_circuit_struct(graph, idoms, node_map) 410 | update_dom(idoms, node_map) 411 | 412 | if_unresolved = if_struct(graph, idoms) 413 | 414 | while_block_struct(graph, node_map) 415 | update_dom(idoms, node_map) 416 | 417 | loop_starts = [] 418 | for node in graph.rpo: 419 | node.update_attribute_with(node_map) 420 | if node.startloop: 421 | loop_starts.append(node) 422 | for node in loop_starts: 423 | loop_type(node, node.latch, node.loop_nodes) 424 | loop_follow(node, node.latch, node.loop_nodes) 425 | 426 | for node in if_unresolved: 427 | follows = [n for n in (node.follow['loop'], node.follow['switch']) if n] 428 | if len(follows) >= 1: 429 | follow = min(follows, key=lambda x: x.num) 430 | node.follow['if'] = follow 431 | 432 | catch_struct(graph, idoms) 433 | -------------------------------------------------------------------------------- /androguard/decompiler/dad/node.py: -------------------------------------------------------------------------------- 1 | # This file is part of Androguard. 2 | # 3 | # Copyright (c) 2012 Geoffroy Gueguen 4 | # All Rights Reserved. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS-IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | 19 | from builtins import object 20 | from future.utils import with_metaclass 21 | 22 | 23 | class MakeProperties(type): 24 | def __init__(cls, name, bases, dct): 25 | 26 | def _wrap_set(names, name): 27 | 28 | def fun(self, value): 29 | for field in names: 30 | self.__dict__[field] = (name == field) and value 31 | 32 | return fun 33 | 34 | def _wrap_get(name): 35 | 36 | def fun(self): 37 | return self.__dict__[name] 38 | 39 | return fun 40 | 41 | super(MakeProperties, cls).__init__(name, bases, dct) 42 | attrs = [] 43 | prefixes = ('_get_', '_set_') 44 | for key in list(dct.keys()): 45 | for prefix in prefixes: 46 | if key.startswith(prefix): 47 | attrs.append(key[4:]) 48 | delattr(cls, key) 49 | for attr in attrs: 50 | setattr(cls, attr[1:], 51 | property(_wrap_get(attr), _wrap_set(attrs, attr))) 52 | cls._attrs = attrs 53 | 54 | def __call__(cls, *args, **kwds): 55 | obj = super(MakeProperties, cls).__call__(*args, **kwds) 56 | for attr in cls._attrs: 57 | obj.__dict__[attr] = False 58 | return obj 59 | 60 | 61 | class LoopType(with_metaclass(MakeProperties, object)): 62 | _set_is_pretest = _set_is_posttest = _set_is_endless = None 63 | _get_is_pretest = _get_is_posttest = _get_is_endless = None 64 | 65 | def copy(self): 66 | res = LoopType() 67 | for key, value in self.__dict__.items(): 68 | setattr(res, key, value) 69 | return res 70 | 71 | 72 | class NodeType(with_metaclass(MakeProperties, object)): 73 | _set_is_cond = _set_is_switch = _set_is_stmt = None 74 | _get_is_cond = _get_is_switch = _get_is_stmt = None 75 | _set_is_return = _set_is_throw = None 76 | _get_is_return = _get_is_throw = None 77 | 78 | def copy(self): 79 | res = NodeType() 80 | for key, value in self.__dict__.items(): 81 | setattr(res, key, value) 82 | return res 83 | 84 | 85 | class Node(object): 86 | def __init__(self, name): 87 | self.name = name 88 | self.num = 0 89 | self.follow = {'if': None, 'loop': None, 'switch': None} 90 | self.looptype = LoopType() 91 | self.type = NodeType() 92 | self.in_catch = False 93 | self.interval = None 94 | self.startloop = False 95 | self.latch = None 96 | self.loop_nodes = [] 97 | 98 | def copy_from(self, node): 99 | self.num = node.num 100 | self.looptype = node.looptype.copy() 101 | self.interval = node.interval 102 | self.startloop = node.startloop 103 | self.type = node.type.copy() 104 | self.follow = node.follow.copy() 105 | self.latch = node.latch 106 | self.loop_nodes = node.loop_nodes 107 | self.in_catch = node.in_catch 108 | 109 | def update_attribute_with(self, n_map): 110 | self.latch = n_map.get(self.latch, self.latch) 111 | for follow_type, value in self.follow.items(): 112 | self.follow[follow_type] = n_map.get(value, value) 113 | self.loop_nodes = list(set(n_map.get(n, n) for n in self.loop_nodes)) 114 | 115 | def get_head(self): 116 | return self 117 | 118 | def get_end(self): 119 | return self 120 | 121 | def __repr__(self): 122 | return '%s' % self 123 | 124 | 125 | class Interval(object): 126 | def __init__(self, head): 127 | self.name = 'Interval-%s' % head.name 128 | self.content = set([head]) 129 | self.end = None 130 | self.head = head 131 | self.in_catch = head.in_catch 132 | head.interval = self 133 | 134 | def __contains__(self, item): 135 | # If the interval contains nodes, check if the item is one of them 136 | if item in self.content: 137 | return True 138 | # If the interval contains intervals, we need to check them 139 | return any(item in node for node in self.content 140 | if isinstance(node, Interval)) 141 | 142 | def add_node(self, node): 143 | if node in self.content: 144 | return False 145 | self.content.add(node) 146 | node.interval = self 147 | return True 148 | 149 | def compute_end(self, graph): 150 | for node in self.content: 151 | for suc in graph.sucs(node): 152 | if suc not in self.content: 153 | self.end = node 154 | 155 | def get_end(self): 156 | return self.end.get_end() 157 | 158 | def get_head(self): 159 | return self.head.get_head() 160 | 161 | def __len__(self): 162 | return len(self.content) 163 | 164 | def __repr__(self): 165 | return '%s(%s)' % (self.name, self.content) 166 | -------------------------------------------------------------------------------- /androguard/decompiler/dad/util.py: -------------------------------------------------------------------------------- 1 | # This file is part of Androguard. 2 | # 3 | # Copyright (c) 2012 Geoffroy Gueguen 4 | # All Rights Reserved. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS-IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | import logging 19 | 20 | logger = logging.getLogger('dad.util') 21 | 22 | TYPE_DESCRIPTOR = { 23 | 'V': 'void', 24 | 'Z': 'boolean', 25 | 'B': 'byte', 26 | 'S': 'short', 27 | 'C': 'char', 28 | 'I': 'int', 29 | 'J': 'long', 30 | 'F': 'float', 31 | 'D': 'double', 32 | } 33 | 34 | ACCESS_FLAGS_CLASSES = { 35 | 0x1: 'public', 36 | 0x2: 'private', 37 | 0x4: 'protected', 38 | 0x8: 'static', 39 | 0x10: 'final', 40 | 0x200: 'interface', 41 | 0x400: 'abstract', 42 | 0x1000: 'synthetic', 43 | 0x2000: 'annotation', 44 | 0x4000: 'enum', 45 | } 46 | 47 | ACCESS_FLAGS_FIELDS = { 48 | 0x1: 'public', 49 | 0x2: 'private', 50 | 0x4: 'protected', 51 | 0x8: 'static', 52 | 0x10: 'final', 53 | 0x40: 'volatile', 54 | 0x80: 'transient', 55 | 0x1000: 'synthetic', 56 | 0x4000: 'enum', 57 | } 58 | 59 | ACCESS_FLAGS_METHODS = { 60 | 0x1: 'public', 61 | 0x2: 'private', 62 | 0x4: 'protected', 63 | 0x8: 'static', 64 | 0x10: 'final', 65 | 0x20: 'synchronized', 66 | 0x40: 'bridge', 67 | 0x80: 'varargs', 68 | 0x100: 'native', 69 | 0x400: 'abstract', 70 | 0x800: 'strictfp', 71 | 0x1000: 'synthetic', 72 | 0x10000: 'constructor', 73 | 0x20000: 'declared_synchronized', 74 | } 75 | 76 | ACCESS_ORDER = [0x1, 0x4, 0x2, 0x400, 0x8, 0x10, 0x80, 0x40, 0x20, 0x100, 0x800, 77 | 0x200, 0x1000, 0x2000, 0x4000, 0x10000, 0x20000] 78 | 79 | TYPE_LEN = {'J': 2, 'D': 2, } 80 | 81 | 82 | def get_access_class(access): 83 | sorted_access = [i for i in ACCESS_ORDER if i & access] 84 | return [ACCESS_FLAGS_CLASSES.get(flag, 'unkn_%d' % flag) 85 | for flag in sorted_access] 86 | 87 | 88 | def get_access_method(access): 89 | sorted_access = [i for i in ACCESS_ORDER if i & access] 90 | return [ACCESS_FLAGS_METHODS.get(flag, 'unkn_%d' % flag) 91 | for flag in sorted_access] 92 | 93 | 94 | def get_access_field(access): 95 | sorted_access = [i for i in ACCESS_ORDER if i & access] 96 | return [ACCESS_FLAGS_FIELDS.get(flag, 'unkn_%d' % flag) 97 | for flag in sorted_access] 98 | 99 | 100 | def build_path(graph, node1, node2, path=None): 101 | """ 102 | Build the path from node1 to node2. 103 | The path is composed of all the nodes between node1 and node2, 104 | node1 excluded. Although if there is a loop starting from node1, it will be 105 | included in the path. 106 | """ 107 | if path is None: 108 | path = [] 109 | if node1 is node2: 110 | return path 111 | path.append(node2) 112 | for pred in graph.all_preds(node2): 113 | if pred in path: 114 | continue 115 | build_path(graph, node1, pred, path) 116 | return path 117 | 118 | 119 | def common_dom(idom, cur, pred): 120 | if not (cur and pred): 121 | return cur or pred 122 | while cur is not pred: 123 | while cur.num < pred.num: 124 | pred = idom[pred] 125 | while cur.num > pred.num: 126 | cur = idom[cur] 127 | return cur 128 | 129 | 130 | def merge_inner(clsdict): 131 | """ 132 | Merge the inner class(es) of a class: 133 | e.g class A { ... } class A$foo{ ... } class A$bar{ ... } 134 | ==> class A { class foo{...} class bar{...} ... } 135 | """ 136 | samelist = False 137 | done = {} 138 | while not samelist: 139 | samelist = True 140 | classlist = list(clsdict.keys()) 141 | for classname in classlist: 142 | parts_name = classname.rsplit('$', 1) 143 | if len(parts_name) > 1: 144 | mainclass, innerclass = parts_name 145 | innerclass = innerclass[:-1] # remove ';' of the name 146 | mainclass += ';' 147 | if mainclass in clsdict: 148 | clsdict[mainclass].add_subclass(innerclass, 149 | clsdict[classname]) 150 | clsdict[classname].name = innerclass 151 | done[classname] = clsdict[classname] 152 | del clsdict[classname] 153 | samelist = False 154 | elif mainclass in done: 155 | cls = done[mainclass] 156 | cls.add_subclass(innerclass, clsdict[classname]) 157 | clsdict[classname].name = innerclass 158 | done[classname] = done[mainclass] 159 | del clsdict[classname] 160 | samelist = False 161 | 162 | 163 | def get_type_size(param): 164 | """ 165 | Return the number of register needed by the type @param 166 | """ 167 | return TYPE_LEN.get(param, 1) 168 | 169 | 170 | def get_type(atype, size=None): 171 | """ 172 | Retrieve the java type of a descriptor (e.g : I) 173 | """ 174 | res = TYPE_DESCRIPTOR.get(atype) 175 | if res is None: 176 | if atype[0] == 'L': 177 | if atype.startswith('Ljava/lang'): 178 | res = atype[1:-1].lstrip('java/lang/').replace('/', '.') 179 | else: 180 | res = atype[1:-1].replace('/', '.') 181 | elif atype[0] == '[': 182 | if size is None: 183 | res = '%s[]' % get_type(atype[1:]) 184 | else: 185 | res = '%s[%s]' % (get_type(atype[1:]), size) 186 | else: 187 | res = atype 188 | logger.debug('Unknown descriptor: "%s".', atype) 189 | return res 190 | 191 | 192 | def get_params_type(descriptor): 193 | """ 194 | Return the parameters type of a descriptor (e.g (IC)V) 195 | """ 196 | params = descriptor.split(')')[0][1:].split() 197 | if params: 198 | return [param for param in params] 199 | return [] 200 | 201 | 202 | def create_png(cls_name, meth_name, graph, dir_name='graphs2'): 203 | """ 204 | Creates a PNG from a given :class:`~androguard.decompiler.dad.graph.Graph`. 205 | 206 | :param str cls_name: name of the class 207 | :param str meth_name: name of the method 208 | :param androguard.decompiler.dad.graph.Graph graph: 209 | :param str dir_name: output directory 210 | """ 211 | m_name = ''.join(x for x in meth_name if x.isalnum()) 212 | name = ''.join((cls_name.split('/')[-1][:-1], '#', m_name)) 213 | graph.draw(name, dir_name) 214 | -------------------------------------------------------------------------------- /androguard/misc.py: -------------------------------------------------------------------------------- 1 | from androguard.session import Session 2 | from androguard.decompiler import decompiler 3 | from androguard.core import androconf 4 | import hashlib 5 | import re 6 | import os 7 | from androguard.core.bytecodes.apk import APK 8 | from androguard.core.bytecodes.dvm import DalvikVMFormat 9 | from androguard.core.analysis.analysis import Analysis 10 | 11 | import logging 12 | log = logging.getLogger("androguard.misc") 13 | 14 | 15 | def init_print_colors(): 16 | from IPython.utils import coloransi, io 17 | androconf.default_colors(coloransi.TermColors) 18 | androconf.CONF["PRINT_FCT"] = io.stdout.write 19 | 20 | 21 | def get_default_session(): 22 | """ 23 | Return the default Session from the configuration 24 | or create a new one, if the session in the configuration is None. 25 | """ 26 | if androconf.CONF["SESSION"] is None: 27 | androconf.CONF["SESSION"] = Session() 28 | return androconf.CONF["SESSION"] 29 | 30 | 31 | def AnalyzeAPK(_file, session=None, raw=False): 32 | """ 33 | Analyze an android application and setup all stuff for a more quickly 34 | analysis! 35 | If session is None, no session is used at all. This is the default 36 | behaviour. 37 | If you like to continue your work later, it might be a good idea to use a 38 | session. 39 | A default session can be created by using :meth:`~get_default_session`. 40 | 41 | :param _file: the filename of the android application or a buffer which represents the application 42 | :type _file: string (for filename) or bytes (for raw) 43 | :param session: A session (default: None) 44 | :param raw: boolean if raw bytes are supplied instead of a filename 45 | :rtype: return the :class:`~androguard.core.bytecodes.apk.APK`, list of :class:`~androguard.core.bytecodes.dvm.DalvikVMFormat`, and :class:`~androguard.core.analysis.analysis.Analysis` objects 46 | """ 47 | log.debug("AnalyzeAPK") 48 | 49 | if session: 50 | log.debug("Using existing session {}".format(session)) 51 | if raw: 52 | data = _file 53 | filename = hashlib.md5(_file).hexdigest() 54 | else: 55 | with open(_file, "rb") as fd: 56 | data = fd.read() 57 | filename = _file 58 | 59 | digest = session.add(filename, data) 60 | return session.get_objects_apk(filename, digest) 61 | else: 62 | log.debug("Analysing without session") 63 | a = APK(_file, raw=raw) 64 | # FIXME: probably it is not necessary to keep all DalvikVMFormats, as 65 | # they are already part of Analysis. But when using sessions, it works 66 | # this way... 67 | d = [] 68 | dx = Analysis() 69 | for dex in a.get_all_dex(): 70 | df = DalvikVMFormat(dex, using_api=a.get_target_sdk_version()) 71 | dx.add(df) 72 | d.append(df) 73 | df.set_decompiler(decompiler.DecompilerDAD(d, dx)) 74 | 75 | dx.create_xref() 76 | 77 | return a, d, dx 78 | 79 | 80 | def AnalyzeDex(filename, session=None): 81 | """ 82 | Analyze an android dex file and setup all stuff for a more quickly analysis ! 83 | 84 | :param filename: the filename of the android dex file or a buffer which represents the dex file 85 | :type filename: string 86 | :param session: A session (Default None) 87 | 88 | :rtype: return a tuple of (sha256hash, :class:`DalvikVMFormat`, :class:`Analysis`) 89 | """ 90 | log.debug("AnalyzeDex") 91 | 92 | if not session: 93 | session = get_default_session() 94 | 95 | with open(filename, "rb") as fd: 96 | data = fd.read() 97 | 98 | return session.addDEX(filename, data) 99 | 100 | 101 | def AnalyzeODex(filename, session=None): 102 | """ 103 | Analyze an android odex file and setup all stuff for a more quickly analysis ! 104 | 105 | :param filename: the filename of the android dex file or a buffer which represents the dex file 106 | :type filename: string 107 | :param session: The Androguard Session to add the ODex to (default: None) 108 | 109 | :rtype: return a tuple of (sha256hash, :class:`DalvikOdexVMFormat`, :class:`Analysis`) 110 | """ 111 | log.debug("AnalyzeODex") 112 | 113 | if not session: 114 | session = get_default_session() 115 | 116 | with open(filename, "rb") as fd: 117 | data = fd.read() 118 | 119 | return session.addDEY(filename, data) 120 | 121 | 122 | def RunDecompiler(d, dx, decompiler_name): 123 | """ 124 | Run the decompiler on a specific analysis 125 | 126 | :param d: the DalvikVMFormat object 127 | :type d: :class:`DalvikVMFormat` object 128 | :param dx: the analysis of the format 129 | :type dx: :class:`VMAnalysis` object 130 | :param decompiler: the type of decompiler to use ("dad", "dex2jad", "ded") 131 | :type decompiler: string 132 | """ 133 | if decompiler_name is not None: 134 | log.debug("Decompiler ...") 135 | decompiler_name = decompiler_name.lower() 136 | # TODO put this into the configuration object and make it more dynamic 137 | # e.g. detect new decompilers and so on... 138 | if decompiler_name == "dex2jad": 139 | d.set_decompiler(decompiler.DecompilerDex2Jad( 140 | d, 141 | androconf.CONF["BIN_DEX2JAR"], 142 | androconf.CONF["BIN_JAD"], 143 | androconf.CONF["TMP_DIRECTORY"])) 144 | elif decompiler_name == "dex2fernflower": 145 | d.set_decompiler(decompiler.DecompilerDex2Fernflower( 146 | d, 147 | androconf.CONF["BIN_DEX2JAR"], 148 | androconf.CONF["BIN_FERNFLOWER"], 149 | androconf.CONF["OPTIONS_FERNFLOWER"], 150 | androconf.CONF["TMP_DIRECTORY"])) 151 | elif decompiler_name == "ded": 152 | d.set_decompiler(decompiler.DecompilerDed( 153 | d, 154 | androconf.CONF["BIN_DED"], 155 | androconf.CONF["TMP_DIRECTORY"])) 156 | elif decompiler_name == "jadx": 157 | d.set_decompiler(decompiler.DecompilerJADX(d, dx, jadx=androconf.CONF["BIN_JADX"])) 158 | else: 159 | d.set_decompiler(decompiler.DecompilerDAD(d, dx)) 160 | 161 | 162 | def sign_apk(filename, keystore, storepass): 163 | """ 164 | Use jarsigner to sign an APK file. 165 | 166 | :param filename: APK file on disk to sign (path) 167 | :param keystore: path to keystore 168 | :param storepass: your keystorage passphrase 169 | """ 170 | from subprocess import Popen, PIPE, STDOUT 171 | # TODO use apksigner instead of jarsigner 172 | cmd = Popen([androconf.CONF["BIN_JARSIGNER"], "-sigalg", "MD5withRSA", 173 | "-digestalg", "SHA1", "-storepass", storepass, "-keystore", 174 | keystore, filename, "alias_name"], 175 | stdout=PIPE, 176 | stderr=STDOUT) 177 | stdout, stderr = cmd.communicate() 178 | 179 | 180 | def clean_file_name(filename, unique=True, replace="_", force_nt=False): 181 | """ 182 | Return a filename version, which has no characters in it which are forbidden. 183 | On Windows these are for example <, /, ?, ... 184 | 185 | The intention of this function is to allow distribution of files to different OSes. 186 | 187 | :param filename: string to clean 188 | :param unique: check if the filename is already taken and append an integer to be unique (default: True) 189 | :param replace: replacement character. (default: '_') 190 | :param force_nt: Force shortening of paths like on NT systems (default: False) 191 | :return: clean string 192 | """ 193 | 194 | if re.match(r'[<>:"/\\|?* .\x00-\x1f]', replace): 195 | raise ValueError("replacement character is not allowed!") 196 | 197 | path, fname = os.path.split(filename) 198 | # For Windows see: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx 199 | # Other operating systems seems to be more tolerant... 200 | 201 | # Not allowed filenames, attach replace character if necessary 202 | if re.match(r'(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])', fname): 203 | fname += replace 204 | 205 | # reserved characters 206 | fname = re.sub(r'[<>:"/\\|?*\x00-\x1f]', replace, fname) 207 | # Do not end with dot or space 208 | fname = re.sub(r'[ .]$', replace, fname) 209 | 210 | if force_nt or os.name == 'nt': 211 | PATH_MAX_LENGTH = 230 # give extra space for other stuff... 212 | # Check filename length limit, usually a problem on older Windows versions 213 | if len(fname) > PATH_MAX_LENGTH: 214 | if "." in fname: 215 | f, ext = fname.rsplit(".", 1) 216 | fname = "{}.{}".format(f[:PATH_MAX_LENGTH-(len(ext)+1)], ext) 217 | else: 218 | fname = fname[:PATH_MAX_LENGTH] 219 | 220 | # Special behaviour... On Windows, there is also a problem with the maximum path length in explorer.exe 221 | # maximum length is limited to 260 chars, so use 250 to have room for other stuff 222 | if len(os.path.abspath(os.path.join(path, fname))) > 250: 223 | fname = fname[:250 - (len(os.path.abspath(path)) + 1)] 224 | 225 | if unique: 226 | counter = 0 227 | origname = fname 228 | while os.path.isfile(os.path.join(path, fname)): 229 | if "." in fname: 230 | # assume extension 231 | f, ext = origname.rsplit(".", 1) 232 | fname = "{}_{}.{}".format(f, counter, ext) 233 | else: 234 | fname = "{}_{}".format(origname, counter) 235 | counter += 1 236 | 237 | return os.path.join(path, fname) 238 | 239 | 240 | -------------------------------------------------------------------------------- /androguard/session.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import os 3 | import sys 4 | import collections 5 | from androguard.core.analysis.analysis import Analysis 6 | from androguard.core.bytecodes.dvm import DalvikVMFormat, DalvikOdexVMFormat 7 | from androguard.core.bytecodes.apk import APK 8 | from androguard.decompiler.decompiler import DecompilerDAD 9 | from androguard.core import androconf 10 | 11 | import pickle 12 | import logging 13 | import datetime 14 | 15 | log = logging.getLogger("androguard.session") 16 | 17 | 18 | def Save(session, filename=None): 19 | """ 20 | save your session to use it later. 21 | 22 | Returns the filename of the written file. 23 | If not filename is given, a file named `androguard_session_.ag` will 24 | be created in the current working directory. 25 | `` is a timestamp with the following format: `%Y-%m-%d_%H%M%S`. 26 | 27 | This function will overwrite existing files without asking. 28 | 29 | If the file could not written, None is returned. 30 | 31 | example:: 32 | 33 | s = session.Session() 34 | session.Save(s, "msession.ag") 35 | 36 | :param session: A Session object to save 37 | :param filename: output filename to save the session 38 | :type filename: string 39 | 40 | """ 41 | 42 | if not filename: 43 | filename = "androguard_session_{:%Y-%m-%d_%H%M%S}.ag".format(datetime.datetime.now()) 44 | 45 | if os.path.isfile(filename): 46 | log.warning("{} already exists, overwriting!") 47 | 48 | # Setting the recursion limit according to the documentation: 49 | # https://docs.python.org/3/library/pickle.html#what-can-be-pickled-and-unpickled 50 | # 51 | # Some larger APKs require a high recursion limit. 52 | # Tested to be above 35000 for some files, setting to 50k to be sure. 53 | # You might want to set this even higher if you encounter problems 54 | reclimit = sys.getrecursionlimit() 55 | sys.setrecursionlimit(50000) 56 | saved = False 57 | try: 58 | with open(filename, "wb") as fd: 59 | pickle.dump(session, fd) 60 | saved = True 61 | except RecursionError: 62 | log.exception("Recursion Limit hit while saving. " 63 | "Current Recursion limit: {}. " 64 | "Please report this error!".format(sys.getrecursionlimit())) 65 | # Remove partially written file 66 | os.unlink(filename) 67 | 68 | sys.setrecursionlimit(reclimit) 69 | return filename if saved else None 70 | 71 | 72 | def Load(filename): 73 | """ 74 | load your session! 75 | 76 | example:: 77 | 78 | s = session.Load("mysession.ag") 79 | 80 | :param filename: the filename where the session has been saved 81 | :type filename: string 82 | 83 | :rtype: the elements of your session :) 84 | 85 | """ 86 | with open(filename, "rb") as fd: 87 | return pickle.load(fd) 88 | 89 | 90 | class Session: 91 | """ 92 | A Session is able to store multiple APK, DEX or ODEX files and can be pickled 93 | to disk in order to resume work later. 94 | 95 | The main function used in Sessions is probably :meth:`add`, which adds files 96 | to the session and performs analysis on them. 97 | 98 | Afterwards, the files can be gathered using methods such as 99 | :meth:`get_objects_apk`, :meth:`get_objects_dex` or :meth:`get_classes`. 100 | 101 | example:: 102 | 103 | s = Session() 104 | digest = s.add("some.apk") 105 | 106 | print("SHA256 of the file: {}".format(digest)) 107 | 108 | a, d, dx = s.get_objects_apk("some.apk", digest) 109 | print(a.get_package()) 110 | 111 | # Reset the Session for a fresh set of files 112 | s.reset() 113 | 114 | digest2 = s.add("classes.dex") 115 | print("SHA256 of the file: {}".format(digest2)) 116 | for h, d, dx in s.get_objects_dex(): 117 | print("SHA256 of the DEX file: {}".format(h)) 118 | 119 | 120 | """ 121 | def __init__(self, export_ipython=False): 122 | """ 123 | Create a new Session object 124 | 125 | :param export_ipython: set to True in order to create attributes for the 126 | use in iPython 127 | """ 128 | self._setup_objects() 129 | self.export_ipython = export_ipython 130 | 131 | def save(self, filename=None): 132 | """ 133 | Save the current session, see also :func:`~androguard.session.Save`. 134 | """ 135 | return Save(self, filename) 136 | 137 | def _setup_objects(self): 138 | self.analyzed_files = collections.defaultdict(list) 139 | self.analyzed_digest = dict() 140 | self.analyzed_apk = dict() 141 | 142 | # Stores Analysis Objects 143 | # needs to be ordered to return the outermost element when searching for 144 | # classes 145 | self.analyzed_vms = collections.OrderedDict() 146 | 147 | # Dict of digest and DalvikVMFormat/DalvikOdexFormat 148 | # Actually not needed, as we have Analysis objects which store the DEX 149 | # files as well, but we do not remove it here for legacy reasons 150 | self.analyzed_dex = dict() 151 | 152 | def reset(self): 153 | """ 154 | Reset the current session, delete all added files. 155 | """ 156 | self._setup_objects() 157 | 158 | def isOpen(self): 159 | """ 160 | Test if any file was analyzed in this session 161 | 162 | :return: `True` if any file was analyzed, `False` otherwise 163 | """ 164 | return len(self.analyzed_digest) > 0 165 | 166 | def show(self): 167 | """ 168 | Print information to stdout about the current session. 169 | Gets all APKs, all DEX files and all Analysis objects. 170 | """ 171 | print("APKs in Session: {}".format(len(self.analyzed_apk))) 172 | for d, a in self.analyzed_apk.items(): 173 | print("\t{}: {}".format(d, a)) 174 | 175 | print("DEXs in Session: {}".format(len(self.analyzed_dex))) 176 | for d, dex in self.analyzed_dex.items(): 177 | print("\t{}: {}".format(d, dex)) 178 | 179 | print("Analysis in Session: {}".format(len(self.analyzed_vms))) 180 | for d, a in self.analyzed_vms.items(): 181 | print("\t{}: {}".format(d, a)) 182 | 183 | def addAPK(self, filename, data): 184 | """ 185 | Add an APK file to the Session and run analysis on it. 186 | 187 | :param filename: (file)name of APK file 188 | :param data: binary data of the APK file 189 | :return: a tuple of SHA256 Checksum and APK Object 190 | """ 191 | digest = hashlib.sha256(data).hexdigest() 192 | log.debug("add APK:%s" % digest) 193 | apk = APK(data, True) 194 | self.analyzed_apk[digest] = [apk] 195 | self.analyzed_files[filename].append(digest) 196 | self.analyzed_digest[digest] = filename 197 | 198 | dx = Analysis() 199 | self.analyzed_vms[digest] = dx 200 | 201 | for dex in apk.get_all_dex(): 202 | # we throw away the output... FIXME? 203 | self.addDEX(filename, dex, dx) 204 | 205 | log.debug("added APK:%s" % digest) 206 | return digest, apk 207 | 208 | def addDEX(self, filename, data, dx=None): 209 | """ 210 | Add a DEX file to the Session and run analysis. 211 | 212 | :param filename: the (file)name of the DEX file 213 | :param data: binary data of the dex file 214 | :param dx: an existing Analysis Object (optional) 215 | :return: A tuple of SHA256 Hash, DalvikVMFormat Object and Analysis object 216 | """ 217 | digest = hashlib.sha256(data).hexdigest() 218 | log.debug("add DEX:%s" % digest) 219 | 220 | log.debug("Parsing format ...") 221 | d = DalvikVMFormat(data) 222 | log.debug("added DEX:%s" % digest) 223 | 224 | self.analyzed_files[filename].append(digest) 225 | self.analyzed_digest[digest] = filename 226 | 227 | self.analyzed_dex[digest] = d 228 | 229 | if dx is None: 230 | dx = Analysis() 231 | 232 | dx.add(d) 233 | dx.create_xref() 234 | 235 | # TODO: If multidex: this will called many times per dex, even if already set 236 | for d in dx.vms: 237 | # TODO: allow different decompiler here! 238 | d.set_decompiler(DecompilerDAD(d, dx)) 239 | d.set_vmanalysis(dx) 240 | self.analyzed_vms[digest] = dx 241 | 242 | if self.export_ipython: 243 | log.debug("Exporting in ipython") 244 | d.create_python_export() 245 | 246 | return digest, d, dx 247 | 248 | def addDEY(self, filename, data, dx=None): 249 | """ 250 | Add an ODEX file to the session and run the analysis 251 | """ 252 | digest = hashlib.sha256(data).hexdigest() 253 | log.debug("add DEY:%s" % digest) 254 | d = DalvikOdexVMFormat(data) 255 | log.debug("added DEY:%s" % digest) 256 | 257 | self.analyzed_files[filename].append(digest) 258 | self.analyzed_digest[digest] = filename 259 | 260 | self.analyzed_dex[digest] = d 261 | 262 | if self.export_ipython: 263 | d.create_python_export() 264 | 265 | if dx is None: 266 | dx = Analysis() 267 | 268 | dx.add(d) 269 | dx.create_xref() 270 | 271 | for d in dx.vms: 272 | # TODO: allow different decompiler here! 273 | d.set_decompiler(DecompilerDAD(d, dx)) 274 | d.set_vmanalysis(dx) 275 | 276 | self.analyzed_vms[digest] = dx 277 | 278 | return digest, d, dx 279 | 280 | def add(self, filename, raw_data=None, dx=None): 281 | """ 282 | Generic method to add a file to the session. 283 | 284 | This is the main method to use when adding files to a Session! 285 | 286 | If an APK file is supplied, all DEX files are analyzed too. 287 | For DEX and ODEX files, only this file is analyzed (what else should be 288 | analyzed). 289 | 290 | Returns the SHA256 of the analyzed file. 291 | 292 | :param filename: filename to load 293 | :param raw_data: bytes of the file, or None to load the file from filename 294 | :param dx: An already exiting :class:`~androguard.core.analysis.analysis.Analysis` object 295 | :return: the sha256 of the file or None on failure 296 | """ 297 | if not raw_data: 298 | log.debug("Loading file from '{}'".format(filename)) 299 | with open(filename, "rb") as fp: 300 | raw_data = fp.read() 301 | 302 | ret = androconf.is_android_raw(raw_data) 303 | log.debug("Found filetype: '{}'".format(ret)) 304 | if not ret: 305 | return None 306 | 307 | if ret == "APK": 308 | digest, _ = self.addAPK(filename, raw_data) 309 | elif ret == "DEX": 310 | digest, _, _ = self.addDEX(filename, raw_data, dx) 311 | elif ret == "DEY": 312 | digest, _, _ = self.addDEY(filename, raw_data, dx) 313 | else: 314 | return None 315 | 316 | return digest 317 | 318 | def get_classes(self): 319 | """ 320 | Returns all Java Classes from the DEX objects as an array of DEX files. 321 | """ 322 | for idx, digest in enumerate(self.analyzed_vms): 323 | dx = self.analyzed_vms[digest] 324 | for vm in dx.vms: 325 | filename = self.analyzed_digest[digest] 326 | yield idx, filename, digest, vm.get_classes() 327 | 328 | def get_analysis(self, current_class): 329 | """ 330 | Returns the :class:`~androguard.core.analysis.analysis.Analysis` object 331 | which contains the `current_class`. 332 | 333 | :param current_class: The class to search for 334 | :type current_class: androguard.core.bytecodes.dvm.ClassDefItem 335 | :rtype: androguard.core.analysis.analysis.Analysis 336 | """ 337 | for digest in self.analyzed_vms: 338 | dx = self.analyzed_vms[digest] 339 | if dx.is_class_present(current_class.get_name()): 340 | return dx 341 | return None 342 | 343 | def get_format(self, current_class): 344 | """ 345 | Returns the :class:`~androguard.core.bytecodes.dvm.DalvikVMFormat` of a 346 | given :class:`~androguard.core.bytecodes.dvm.ClassDefItem`. 347 | 348 | :param current_class: A ClassDefItem 349 | """ 350 | return current_class.CM.vm 351 | 352 | def get_filename_by_class(self, current_class): 353 | """ 354 | Returns the filename of the DEX file where the class is in. 355 | 356 | Returns the first filename this class was present. 357 | For example, if you analyzed an APK, this should return the filename of 358 | the APK and not of the DEX file. 359 | 360 | :param current_class: ClassDefItem 361 | :returns: None if class was not found or the filename 362 | """ 363 | for digest, dx in self.analyzed_vms.items(): 364 | if dx.is_class_present(current_class.get_name()): 365 | return self.analyzed_digest[digest] 366 | return None 367 | 368 | def get_digest_by_class(self, current_class): 369 | """ 370 | Return the SHA256 hash of the object containing the ClassDefItem 371 | 372 | Returns the first digest this class was present. 373 | For example, if you analyzed an APK, this should return the digest of 374 | the APK and not of the DEX file. 375 | """ 376 | for digest, dx in self.analyzed_vms.items(): 377 | if dx.is_class_present(current_class.get_name()): 378 | return digest 379 | return None 380 | 381 | def get_strings(self): 382 | """ 383 | Yields all StringAnalysis for all unique Analysis objects 384 | """ 385 | seen = [] 386 | for digest, dx in self.analyzed_vms.items(): 387 | if dx in seen: 388 | continue 389 | seen.append(dx) 390 | yield digest, self.analyzed_digest[digest], dx.get_strings_analysis() 391 | 392 | def get_nb_strings(self): 393 | """ 394 | Return the total number of strings in all Analysis objects 395 | """ 396 | nb = 0 397 | seen = [] 398 | for digest, dx in self.analyzed_vms.items(): 399 | if dx in seen: 400 | continue 401 | seen.append(dx) 402 | nb += len(dx.get_strings_analysis()) 403 | return nb 404 | 405 | def get_all_apks(self): 406 | """ 407 | Yields a list of tuples of SHA256 hash of the APK and APK objects 408 | of all analyzed APKs in the Session. 409 | """ 410 | for digest, a in self.analyzed_apk.items(): 411 | yield digest, a 412 | 413 | def get_objects_apk(self, filename=None, digest=None): 414 | """ 415 | Returns APK, DalvikVMFormat and Analysis of a specified APK. 416 | 417 | You must specify either `filename` or `digest`. 418 | It is possible to use both, but in this case only `digest` is used. 419 | 420 | example:: 421 | 422 | s = Session() 423 | digest = s.add("some.apk") 424 | a, d, dx = s.get_objects_apk(digest=digest) 425 | 426 | example:: 427 | 428 | s = Session() 429 | filename = "some.apk" 430 | digest = s.add(filename) 431 | a, d, dx = s.get_objects_apk(filename=filename) 432 | 433 | :param filename: the filename of the APK file, only used of digest is None 434 | :param digest: the sha256 hash, as returned by :meth:`add` for the APK 435 | :returns: a tuple of (APK, [DalvikVMFormat], Analysis) 436 | """ 437 | if not filename and not digest: 438 | raise ValueError("Must give at least filename or digest!") 439 | 440 | if digest is None: 441 | digests = self.analyzed_files.get(filename) 442 | # Negate to reduce tree 443 | if not digests: 444 | return None, None, None 445 | digest = digests[0] 446 | 447 | a = self.analyzed_apk[digest][0] 448 | dx = self.analyzed_vms[digest] 449 | return a, dx.vms, dx 450 | 451 | def get_objects_dex(self): 452 | """ 453 | Yields all dex objects inclduing their Analysis objects 454 | 455 | :returns: tuple of (sha256, DalvikVMFormat, Analysis) 456 | """ 457 | # TODO: there is no variant like get_objects_apk 458 | for digest, d in self.analyzed_dex.items(): 459 | yield digest, d, self.analyzed_vms[digest] 460 | 461 | -------------------------------------------------------------------------------- /androguard/util.py: -------------------------------------------------------------------------------- 1 | import asn1crypto 2 | # Functions that might be useful 3 | 4 | 5 | def read(filename, binary=True): 6 | """ 7 | Open and read a file 8 | 9 | :param filename: filename to open and read 10 | :param binary: True if the file should be read as binary 11 | :return: bytes if binary is True, str otherwise 12 | """ 13 | with open(filename, 'rb' if binary else 'r') as f: 14 | return f.read() 15 | 16 | 17 | def get_certificate_name_string(name, short=False, delimiter=', '): 18 | """ 19 | Format the Name type of a X509 Certificate in a human readable form. 20 | 21 | :param name: Name object to return the DN from 22 | :param short: Use short form (default: False) 23 | :param delimiter: Delimiter string or character between two parts (default: ', ') 24 | 25 | :type name: dict or :class:`asn1crypto.x509.Name` 26 | :type short: boolean 27 | :type delimiter: str 28 | 29 | :rtype: str 30 | """ 31 | if isinstance(name, asn1crypto.x509.Name): 32 | name = name.native 33 | 34 | # For the shortform, we have a lookup table 35 | # See RFC4514 for more details 36 | _ = { 37 | 'business_category': ("businessCategory", "businessCategory"), 38 | 'serial_number': ("serialNumber", "serialNumber"), 39 | 'country_name': ("C", "countryName"), 40 | 'postal_code': ("postalCode", "postalCode"), 41 | 'state_or_province_name': ("ST", "stateOrProvinceName"), 42 | 'locality_name': ("L", "localityName"), 43 | 'street_address': ("street", "streetAddress"), 44 | 'organization_name': ("O", "organizationName"), 45 | 'organizational_unit_name': ("OU", "organizationalUnitName"), 46 | 'title': ("title", "title"), 47 | 'common_name': ("CN", "commonName"), 48 | 'initials': ("initials", "initials"), 49 | 'generation_qualifier': ("generationQualifier", "generationQualifier"), 50 | 'surname': ("SN", "surname"), 51 | 'given_name': ("GN", "givenName"), 52 | 'name': ("name", "name"), 53 | 'pseudonym': ("pseudonym", "pseudonym"), 54 | 'dn_qualifier': ("dnQualifier", "dnQualifier"), 55 | 'telephone_number': ("telephoneNumber", "telephoneNumber"), 56 | 'email_address': ("E", "emailAddress"), 57 | 'domain_component': ("DC", "domainComponent"), 58 | 'name_distinguisher': ("nameDistinguisher", "nameDistinguisher"), 59 | 'organization_identifier': ("organizationIdentifier", "organizationIdentifier"), 60 | } 61 | return delimiter.join(["{}={}".format(_.get(attr, (attr, attr))[0 if short else 1], name[attr]) for attr in name]) 62 | -------------------------------------------------------------------------------- /dcc.cfg: -------------------------------------------------------------------------------- 1 | { 2 | "apktool": "tools/apktool.jar", 3 | "ndk_dir": "path/to/ndkdir", 4 | "signature": { 5 | "keystore_path": "keystore/debug.keystore", 6 | "alias": "androiddebugkey", 7 | "keystore_pass": "android", 8 | "store_pass": "android", 9 | "v1_enabled": true, 10 | "v2_enabled": true, 11 | "v3_enabled": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /dex2c/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codehasan/dex2c/1866c6824941327cfcea294e717985be419f57c1/dex2c/__init__.py -------------------------------------------------------------------------------- /dex2c/basic_blocks.py: -------------------------------------------------------------------------------- 1 | # encoding=utf8 2 | # 3 | # Copyright (c) 2012 Geoffroy Gueguen 4 | # All Rights Reserved. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS-IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | import logging 19 | from builtins import object 20 | from collections import OrderedDict 21 | 22 | from dex2c.instruction import MoveResultExpression 23 | from dex2c.opcode_ins import INSTRUCTION_SET 24 | 25 | logger = logging.getLogger('dex2c.basic_blocks') 26 | 27 | 28 | class IrBasicBlock(object): 29 | def __init__(self, dvm_basicblock): 30 | self.dvm_basicblock = dvm_basicblock 31 | self.instr_list = [] 32 | 33 | # MoveParam指令会给参数生成一个局部引用(local reference), 使用局部引用引用参数 34 | # 不能将它放入instr_list, 因为第一个基本块可以是循环 35 | self.move_param_insns = [] 36 | self.var_to_declare = list() 37 | self.class_to_declare = list() 38 | self.field_to_declare = list() 39 | self.method_to_declare = list() 40 | self.num = -1 # 基本块编号 41 | 42 | # 处理异常相关 43 | self.in_catch = False 44 | self.catch_type = None 45 | 46 | # 构建SSA IR使用数据结构 47 | # Ref: <> by Matthias Braun 48 | self.filled = False 49 | self.sealed = False 50 | self.current_definitions = {} 51 | self.incomplete_phis = set() 52 | self.phis = set() 53 | 54 | self.catch_successors = set() 55 | 56 | if dvm_basicblock: 57 | self.start = self.dvm_basicblock.get_start() 58 | else: 59 | self.start = -1 60 | 61 | def get_instr_list(self): 62 | return self.instr_list 63 | 64 | def get_name(self): 65 | return self.label 66 | 67 | def add_ins_before(self, new_ins, before_ins): 68 | pos = self.instr_list.index(before_ins) 69 | self.instr_list = self.instr_list[:pos] + [new_ins] + self.instr_list[pos:] 70 | 71 | def remove_ins(self, ins): 72 | self.instr_list.remove(ins) 73 | 74 | def add_ins(self, new_ins): 75 | self.instr_list.append(new_ins) 76 | 77 | def add_variable_declaration(self, variable): 78 | self.var_to_declare.append(variable) 79 | 80 | def set_catch_type(self, _type): 81 | self.catch_type = _type 82 | 83 | def update_current_definition(self, register, var): 84 | self.current_definitions[register] = var 85 | 86 | def read_current_definition(self, register): 87 | return self.current_definitions[register] if register in self.current_definitions else None 88 | 89 | def visit(self, visitor): 90 | # pass 91 | visitor.visit_statement_node(self) 92 | 93 | def add_incomplete_phi(self, phi): 94 | self.incomplete_phis.add(phi) 95 | 96 | def get_incomplete_phis(self): 97 | return self.incomplete_phis 98 | 99 | def clear_incomplete_phis(self): 100 | self.incomplete_phis.clear() 101 | 102 | def add_phi(self, phi): 103 | self.phis.add(phi) 104 | 105 | def remove_phi(self, phi): 106 | if phi in self.phis: 107 | self.phis.remove(phi) 108 | 109 | def clear_phis(self): 110 | self.phis.clear() 111 | 112 | def add_catch_successor(self, node): 113 | if node not in self.catch_successors: 114 | self.catch_successors.add(node) 115 | 116 | @property 117 | def label(self): 118 | if self.dvm_basicblock: 119 | return 'BB_%x' % self.dvm_basicblock.start 120 | else: 121 | return 'BB_%d' % self.num 122 | 123 | def __str__(self): 124 | s = [self.label] 125 | for phi in self.phis: 126 | s.append(phi.print()) 127 | 128 | for ins in self.instr_list: 129 | s.append(str(ins)) 130 | return '\n'.join(s) 131 | 132 | 133 | class LandingPad(IrBasicBlock): 134 | def __init__(self, source): 135 | super(LandingPad, self).__init__(None) 136 | self.node = source 137 | self.handles = OrderedDict() 138 | 139 | def add_catch_handle(self, atype, node): 140 | if atype in self.handles and atype != 'Ljava/lang/Throwable;': 141 | raise Exception("duplicate catch handle for %s" % atype) 142 | 143 | if atype not in self.handles: 144 | self.handles[atype] = node 145 | 146 | @property 147 | def label(self): 148 | return 'EX_LandingPad_%d' % self.node.num 149 | 150 | def __str__(self): 151 | return '%d' % self.node.num 152 | 153 | 154 | def fill_node_from_block(irbuilder, node): 155 | block = node.dvm_basicblock 156 | idx = block.get_start() 157 | current_node = node 158 | for ins in block.get_instructions(): 159 | opcode = ins.get_op_value() 160 | if opcode == -1: # FIXME? or opcode in (0x0300, 0x0200, 0x0100): 161 | idx += ins.get_length() 162 | continue 163 | try: 164 | _ins = INSTRUCTION_SET[opcode] 165 | except IndexError: 166 | raise Exception('Unknown instruction : %s.', ins.get_name().lower()) 167 | # fill-array-data 168 | if opcode == 0x26: 169 | fillarray = block.get_special_ins(idx) 170 | lastins = _ins(ins, irbuilder, fillarray) 171 | current_node.add_ins(lastins) 172 | # invoke-kind[/range] 173 | elif 0x6e <= opcode <= 0x72 or 0x74 <= opcode <= 0x78: 174 | lastins = _ins(ins, irbuilder) 175 | current_node.add_ins(lastins) 176 | elif 0x24 <= opcode <= 0x25: 177 | lastins = _ins(ins, irbuilder) 178 | current_node.add_ins(lastins) 179 | # move-result* 180 | elif 0xa <= opcode <= 0xc: 181 | lastins = _ins(ins, irbuilder) 182 | current_node.add_ins(lastins) 183 | # move-exception 184 | elif opcode == 0xd: 185 | lastins = _ins(ins, irbuilder, current_node.catch_type) 186 | current_node.add_ins(lastins) 187 | # {packed,sparse}-switch 188 | elif 0x2b <= opcode <= 0x2c: 189 | values = block.get_special_ins(idx) 190 | lastins = _ins(ins, irbuilder, values) 191 | current_node.add_ins(lastins) 192 | else: 193 | lastins = _ins(ins, irbuilder) 194 | current_node.add_ins(lastins) 195 | # aget-object v1_y, v1_x, v0 196 | # 如果v1_x在该指令后不再使用,我们会将v1_x释放,但是v1_x,v1_y分配到同一个寄存器(v1),导致v1_y被释放. 197 | # 解决办法是,我们将其转化为两条指令 198 | # aget-object vTmp, v1, v0 199 | # move-result-object v1, vTmp 200 | # 类似iget-object 201 | if opcode == 0x46 or opcode == 0x54: 202 | vtmp = irbuilder.write_result_variable() 203 | val = lastins.get_value() 204 | lastins.set_value(vtmp) 205 | move_ins = MoveResultExpression(val, vtmp) 206 | move_ins.parent = current_node # 变量活跃分析会使用 207 | current_node.add_ins(move_ins) 208 | lastins.offset = idx 209 | lastins.parent = current_node 210 | if lastins.get_value(): 211 | lastins.get_value().definition = lastins 212 | 213 | idx += ins.get_length() 214 | lastins.next_offset = idx 215 | lastins.dvm_instr = ins 216 | 217 | current_node.filled = True 218 | return current_node 219 | -------------------------------------------------------------------------------- /dex2c/graph.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | 3 | from builtins import range 4 | # 5 | # Copyright (c) 2012 Geoffroy Gueguen 6 | # All Rights Reserved. 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS-IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | from builtins import str 20 | from collections import defaultdict 21 | 22 | from dex2c.basic_blocks import (IrBasicBlock, LandingPad) 23 | from dex2c.instruction import * 24 | 25 | logger = logging.getLogger('dex2c.graph') 26 | 27 | 28 | class Graph(object): 29 | def __init__(self): 30 | self.entry = None 31 | self.exit = None 32 | self.nodes = list() 33 | self.landing_pads = list() 34 | self.rpo = [] 35 | self.edges = defaultdict(list) 36 | self.catch_edges = defaultdict(list) 37 | self.reverse_edges = defaultdict(list) 38 | self.reverse_catch_edges = defaultdict(list) 39 | self.loc_to_ins = None 40 | self.loc_to_node = None 41 | self.offset_to_node = {} 42 | self.node_to_landing_pad = {} 43 | 44 | def sucs(self, node): 45 | return self.edges.get(node, [])[:] 46 | 47 | def all_sucs(self, node): 48 | return self.edges.get(node, []) + self.catch_edges.get(node, []) 49 | 50 | def all_catches(self, node): 51 | return self.catch_edges.get(node, [])[:] 52 | 53 | def preds(self, node): 54 | return [n for n in self.reverse_edges.get(node, []) if not n.in_catch] 55 | 56 | def all_preds(self, node): 57 | return (self.reverse_edges.get(node, []) + self.reverse_catch_edges.get( 58 | node, [])) 59 | 60 | def add_node(self, node): 61 | self.nodes.append(node) 62 | 63 | def add_landing_pad(self, pad): 64 | self.landing_pads.append(pad) 65 | 66 | def add_edge(self, e1, e2): 67 | lsucs = self.edges[e1] 68 | if e2 not in lsucs: 69 | lsucs.append(e2) 70 | lpreds = self.reverse_edges[e2] 71 | if e1 not in lpreds: 72 | lpreds.append(e1) 73 | 74 | def remove_edge(self, e1, e2): 75 | lsucs = self.edges[e1] 76 | if e2 in lsucs: 77 | lsucs.remove(e2) 78 | 79 | lpreds = self.reverse_edges[e2] 80 | if e1 in lpreds: 81 | lpreds.remove(e1) 82 | 83 | def add_catch_edge(self, e1, e2): 84 | lsucs = self.catch_edges[e1] 85 | if e2 not in lsucs: 86 | lsucs.append(e2) 87 | lpreds = self.reverse_catch_edges[e2] 88 | if e1 not in lpreds: 89 | lpreds.append(e1) 90 | 91 | def remove_node(self, node): 92 | preds = self.reverse_edges.get(node, []) 93 | for pred in preds: 94 | self.edges[pred].remove(node) 95 | 96 | succs = self.edges.get(node, []) 97 | for suc in succs: 98 | self.reverse_edges[suc].remove(node) 99 | 100 | exc_preds = self.reverse_catch_edges.pop(node, []) 101 | for pred in exc_preds: 102 | self.catch_edges[pred].remove(node) 103 | 104 | exc_succs = self.catch_edges.pop(node, []) 105 | for suc in exc_succs: 106 | self.reverse_catch_edges[suc].remove(node) 107 | 108 | self.nodes.remove(node) 109 | if node in self.rpo: 110 | self.rpo.remove(node) 111 | del node 112 | 113 | def number_ins(self): 114 | self.loc_to_ins = {} 115 | self.loc_to_node = {} 116 | num = 0 117 | for node in self.rpo: 118 | start_node = num 119 | num = node.number_ins(num) 120 | end_node = num - 1 121 | self.loc_to_ins.update(node.get_loc_with_ins()) 122 | self.loc_to_node[start_node, end_node] = node 123 | 124 | def get_ins_from_loc(self, loc): 125 | return self.loc_to_ins.get(loc) 126 | 127 | def get_node_from_loc(self, loc): 128 | for (start, end), node in self.loc_to_node.items(): 129 | if start <= loc <= end: 130 | return node 131 | 132 | def remove_ins(self, loc): 133 | ins = self.get_ins_from_loc(loc) 134 | self.get_node_from_loc(loc).remove_ins(loc, ins) 135 | self.loc_to_ins.pop(loc) 136 | 137 | def compute_rpo(self): 138 | """ 139 | Number the nodes in reverse post order. 140 | An RPO traversal visit as many predecessors of a node as possible 141 | before visiting the node itself. 142 | """ 143 | nb = len(self.nodes) + 1 144 | for node in self.post_order(): 145 | node.num = nb - node.po 146 | self.rpo = sorted(self.nodes, key=lambda n: n.num) 147 | 148 | def compute_block_order(self): 149 | list = sorted(self.nodes, key=lambda n: n.start) 150 | for num, node in enumerate(list): 151 | node.num = num 152 | return list 153 | 154 | def post_order(self): 155 | """ 156 | Return the nodes of the graph in post-order i.e we visit all the 157 | children of a node before visiting the node itself. 158 | """ 159 | 160 | def _visit(n, cnt): 161 | visited.add(n) 162 | for suc in self.all_sucs(n): 163 | if suc not in visited: 164 | for cnt, s in _visit(suc, cnt): 165 | yield cnt, s 166 | n.po = cnt 167 | yield cnt + 1, n 168 | 169 | visited = set() 170 | for _, node in _visit(self.entry, 1): 171 | yield node 172 | 173 | def draw(self, name, dname, draw_branches=True): 174 | from pydot import Dot, Edge,Node 175 | g = Dot() 176 | g.set_node_defaults(color='lightgray', 177 | style='filled', 178 | shape='box', 179 | fontname='Courier', 180 | fontsize='10') 181 | if len(self.nodes) == 1: 182 | g.add_node(Node(str(self.nodes[0]))) 183 | else: 184 | for node in sorted(self.nodes, key=lambda x: x.num): 185 | for suc in self.sucs(node): 186 | g.add_edge(Edge(str(node), str(suc), color='blue')) 187 | for except_node in self.catch_edges.get(node, []): 188 | g.add_edge(Edge(str(node), 189 | str(except_node), 190 | color='black', 191 | style='dashed')) 192 | 193 | g.write_png('%s/%s.png' % (dname, name)) 194 | 195 | def immediate_dominators(self): 196 | return dom_lt(self) 197 | 198 | def __len__(self): 199 | return len(self.nodes) 200 | 201 | def __repr__(self): 202 | return str(self.nodes) 203 | 204 | def __iter__(self): 205 | for node in self.nodes: 206 | yield node 207 | 208 | 209 | def dom_lt(graph): 210 | """Dominator algorithm from Lengaeur-Tarjan""" 211 | 212 | def _dfs(v, n): 213 | semi[v] = n = n + 1 214 | vertex[n] = label[v] = v 215 | ancestor[v] = 0 216 | for w in graph.all_sucs(v): 217 | if not semi[w]: 218 | parent[w] = v 219 | n = _dfs(w, n) 220 | pred[w].add(v) 221 | return n 222 | 223 | def _compress(v): 224 | u = ancestor[v] 225 | if ancestor[u]: 226 | _compress(u) 227 | if semi[label[u]] < semi[label[v]]: 228 | label[v] = label[u] 229 | ancestor[v] = ancestor[u] 230 | 231 | def _eval(v): 232 | if ancestor[v]: 233 | _compress(v) 234 | return label[v] 235 | return v 236 | 237 | def _link(v, w): 238 | ancestor[w] = v 239 | 240 | parent, ancestor, vertex = {}, {}, {} 241 | label, dom = {}, {} 242 | pred, bucket = defaultdict(set), defaultdict(set) 243 | 244 | # Step 1: 245 | semi = {v: 0 for v in graph.nodes} 246 | n = _dfs(graph.entry, 0) 247 | for i in range(n, 1, -1): 248 | w = vertex[i] 249 | # Step 2: 250 | for v in pred[w]: 251 | u = _eval(v) 252 | y = semi[w] = min(semi[w], semi[u]) 253 | bucket[vertex[y]].add(w) 254 | pw = parent[w] 255 | _link(pw, w) 256 | # Step 3: 257 | bpw = bucket[pw] 258 | while bpw: 259 | v = bpw.pop() 260 | u = _eval(v) 261 | dom[v] = u if semi[u] < semi[v] else pw 262 | # Step 4: 263 | for i in range(2, n + 1): 264 | w = vertex[i] 265 | dw = dom[w] 266 | if dw != vertex[semi[w]]: 267 | dom[w] = dom[dw] 268 | dom[graph.entry] = None 269 | return dom 270 | 271 | 272 | def bfs(start): 273 | to_visit = [start] 274 | visited = set([start]) 275 | while to_visit: 276 | node = to_visit.pop(0) 277 | yield node 278 | if node.exception_analysis: 279 | for _, _, exception in node.exception_analysis.exceptions: 280 | if exception not in visited: 281 | to_visit.append(exception) 282 | visited.add(exception) 283 | for _, _, child in node.childs: 284 | if child not in visited: 285 | to_visit.append(child) 286 | visited.add(child) 287 | 288 | 289 | def construct(start_block): 290 | bfs_blocks = bfs(start_block) 291 | 292 | graph = Graph() 293 | 294 | block_to_node = {} 295 | node_to_landing_pad = {} 296 | 297 | # 每个异常exception_analysis 对应一个分发异常的landingping_pad, 不同的block可以有相同的异常处理 298 | exception_to_landing_pad = {} 299 | 300 | for block in bfs_blocks: 301 | node = block_to_node.get(block) 302 | if node is None: 303 | node = IrBasicBlock(block) 304 | block_to_node[block] = node 305 | graph.add_node(node) 306 | 307 | if block.exception_analysis: 308 | if block.exception_analysis in exception_to_landing_pad: 309 | landing_pad = exception_to_landing_pad[block.exception_analysis] 310 | else: 311 | # 初始化 LandingPad 312 | landing_pad = LandingPad(node) 313 | graph.add_landing_pad(landing_pad) 314 | exception_to_landing_pad[block.exception_analysis] = landing_pad 315 | for _type, _, exception_target in block.exception_analysis.exceptions: 316 | catch_node = block_to_node.get(exception_target) 317 | if catch_node is None: 318 | catch_node = IrBasicBlock(exception_target) 319 | block_to_node[exception_target] = catch_node 320 | 321 | catch_node.set_catch_type(_type) 322 | catch_node.in_catch = True 323 | 324 | landing_pad.add_catch_handle(_type, catch_node) 325 | 326 | # 将catch 节点加入到node后继 327 | for _type, _, exception_target in block.exception_analysis.exceptions: 328 | catch_node = block_to_node.get(exception_target) 329 | assert catch_node is not None 330 | node.add_catch_successor(catch_node) 331 | graph.add_catch_edge(node, catch_node) 332 | # 更新节点对应的LandingPad 333 | node_to_landing_pad[node] = landing_pad 334 | 335 | for _, _, child_block in block.childs: 336 | child_node = block_to_node.get(child_block) 337 | if child_node is None: 338 | child_node = IrBasicBlock(child_block) 339 | block_to_node[child_block] = child_node 340 | graph.add_edge(node, child_node) 341 | 342 | graph.entry = block_to_node[start_block] 343 | 344 | graph.compute_rpo() 345 | 346 | offset_to_node = {} 347 | for node in graph.rpo: 348 | if node.start >= 0: 349 | offset_to_node[node.start] = node 350 | 351 | graph.node_to_landing_pad = node_to_landing_pad 352 | graph.offset_to_node = offset_to_node 353 | for node in graph.rpo: 354 | preds = [pred for pred in graph.all_preds(node) if pred.num < node.num] 355 | if preds and all(pred.in_catch for pred in preds): 356 | node.in_catch = True 357 | 358 | return graph 359 | 360 | 361 | -------------------------------------------------------------------------------- /dex2c/util.py: -------------------------------------------------------------------------------- 1 | # encoding=utf8 2 | # 3 | # Copyright (c) 2012 Geoffroy Gueguen 4 | # All Rights Reserved. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS-IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | import logging 19 | 20 | logger = logging.getLogger('dex2c.util') 21 | 22 | TYPE_DESCRIPTOR = { 23 | 'V': 'Void', 24 | 'Z': 'Boolean', 25 | 'B': 'Byte', 26 | 'S': 'Short', 27 | 'C': 'Char', 28 | 'I': 'Int', 29 | 'J': 'Long', 30 | 'F': 'Float', 31 | 'D': 'Double', 32 | } 33 | 34 | DECL_JNI_TYPE = { 35 | 'Z': 'jboolean', # jboolean 36 | 'B': 'jbyte', 37 | 'S': 'jshort', 38 | 'C': 'jchar', 39 | 'I': 'jint', 40 | 'J': 'jlong', 41 | 'F': 'jfloat', 42 | 'D': 'jdouble', 43 | 'V': 'void', 44 | } 45 | 46 | PRIMITIVE_TYPE_ORDER = { 47 | 'Z': 1, 48 | 'B': 2, 49 | 'S': 3, # signed 50 | 'C': 3, # unsigned 51 | 'I': 5, 52 | 'J': 6, 53 | 'F': 8, 54 | 'D': 9, 55 | } 56 | 57 | ACCESS_FLAGS_CLASSES = { 58 | 0x1: 'public', 59 | 0x2: 'private', 60 | 0x4: 'protected', 61 | 0x8: 'static', 62 | 0x10: 'final', 63 | 0x200: 'interface', 64 | 0x400: 'abstract', 65 | 0x1000: 'synthetic', 66 | 0x2000: 'annotation', 67 | 0x4000: 'enum', 68 | } 69 | 70 | ACCESS_FLAGS_FIELDS = { 71 | 0x1: 'public', 72 | 0x2: 'private', 73 | 0x4: 'protected', 74 | 0x8: 'static', 75 | 0x10: 'final', 76 | 0x40: 'volatile', 77 | 0x80: 'transient', 78 | 0x1000: 'synthetic', 79 | 0x4000: 'enum', 80 | } 81 | 82 | ACCESS_FLAGS_METHODS = { 83 | 0x1: 'public', 84 | 0x2: 'private', 85 | 0x4: 'protected', 86 | 0x8: 'static', 87 | 0x10: 'final', 88 | 0x20: 'synchronized', 89 | 0x40: 'bridge', 90 | 0x80: 'varargs', 91 | 0x100: 'native', 92 | 0x400: 'abstract', 93 | 0x800: 'strictfp', 94 | 0x1000: 'synthetic', 95 | 0x10000: 'constructor', 96 | 0x20000: 'declared_synchronized', 97 | } 98 | 99 | ACCESS_ORDER = [0x1, 0x4, 0x2, 0x400, 0x8, 0x10, 0x80, 0x40, 0x20, 0x100, 0x800, 100 | 0x200, 0x1000, 0x2000, 0x4000, 0x10000, 0x20000] 101 | 102 | TYPE_LEN = {'J': 2, 'D': 2, } 103 | 104 | 105 | def get_access_class(access): 106 | sorted_access = [i for i in ACCESS_ORDER if i & access] 107 | return [ACCESS_FLAGS_CLASSES.get(flag, 'unkn_%d' % flag) 108 | for flag in sorted_access] 109 | 110 | 111 | def get_access_method(access): 112 | sorted_access = [i for i in ACCESS_ORDER if i & access] 113 | return [ACCESS_FLAGS_METHODS.get(flag, 'unkn_%d' % flag) 114 | for flag in sorted_access] 115 | 116 | 117 | def get_access_field(access): 118 | sorted_access = [i for i in ACCESS_ORDER if i & access] 119 | return [ACCESS_FLAGS_FIELDS.get(flag, 'unkn_%d' % flag) 120 | for flag in sorted_access] 121 | 122 | 123 | def build_path(graph, node1, node2, path=None): 124 | """ 125 | Build the path from node1 to node2. 126 | The path is composed of all the nodes between node1 and node2, 127 | node1 excluded. Although if there is a loop starting from node1, it will be 128 | included in the path. 129 | """ 130 | if path is None: 131 | path = [] 132 | if node1 is node2: 133 | return path 134 | path.append(node2) 135 | for pred in graph.all_preds(node2): 136 | if pred in path: 137 | continue 138 | build_path(graph, node1, pred, path) 139 | return path 140 | 141 | 142 | def common_dom(idom, cur, pred): 143 | if not (cur and pred): 144 | return cur or pred 145 | while cur is not pred: 146 | while cur.num < pred.num: 147 | pred = idom[pred] 148 | while cur.num > pred.num: 149 | cur = idom[cur] 150 | return cur 151 | 152 | 153 | def merge_inner(clsdict): 154 | """ 155 | Merge the inner class(es) of a class: 156 | e.g class A { ... } class A$foo{ ... } class A$bar{ ... } 157 | ==> class A { class foo{...} class bar{...} ... } 158 | """ 159 | samelist = False 160 | done = {} 161 | while not samelist: 162 | samelist = True 163 | classlist = list(clsdict.keys()) 164 | for classname in classlist: 165 | parts_name = classname.rsplit('$', 1) 166 | if len(parts_name) > 1: 167 | mainclass, innerclass = parts_name 168 | innerclass = innerclass[:-1] # remove ';' of the name 169 | mainclass += ';' 170 | if mainclass in clsdict: 171 | clsdict[mainclass].add_subclass(innerclass, 172 | clsdict[classname]) 173 | clsdict[classname].name = innerclass 174 | done[classname] = clsdict[classname] 175 | del clsdict[classname] 176 | samelist = False 177 | elif mainclass in done: 178 | cls = done[mainclass] 179 | cls.add_subclass(innerclass, clsdict[classname]) 180 | clsdict[classname].name = innerclass 181 | done[classname] = done[mainclass] 182 | del clsdict[classname] 183 | samelist = False 184 | 185 | 186 | def get_type_size(param): 187 | """ 188 | Return the number of register needed by the type @param 189 | """ 190 | return TYPE_LEN.get(param, 1) 191 | 192 | 193 | def get_type(atype, size=None): 194 | """ 195 | Retrieve the java type of a descriptor (e.g : I) 196 | """ 197 | res = TYPE_DESCRIPTOR.get(atype) 198 | if res is None: 199 | if atype[0] == 'L': 200 | res = atype[1:-1] 201 | elif atype[0] == '[': 202 | res = atype 203 | else: 204 | res = atype 205 | logger.debug('Unknown descriptor: "%s".', atype) 206 | return res 207 | 208 | 209 | def get_fully_qualified_class_name(signature): 210 | res = TYPE_DESCRIPTOR.get(signature) 211 | if res is None: 212 | if signature[0] == 'L': 213 | res = signature[1:-1] 214 | else: 215 | res = signature 216 | logger.debug('Unknown descriptor: "%s".', signature) 217 | return res 218 | 219 | 220 | def get_type_descriptor(atype): 221 | res = TYPE_DESCRIPTOR.get(atype) 222 | if res is None: 223 | res = 'Object' 224 | return res 225 | 226 | 227 | def is_primitive_type(atype): 228 | return atype and atype in PRIMITIVE_TYPE_ORDER 229 | 230 | 231 | def get_params_type(descriptor): 232 | """ 233 | Return the parameters type of a descriptor (e.g (IC)V) 234 | """ 235 | params = descriptor.split(')')[0][1:].split() 236 | if params: 237 | return [param for param in params] 238 | return [] 239 | 240 | 241 | def get_method_triple(method, return_type=True): 242 | method_triple = method.get_triple() 243 | cls_name = method.class_name 244 | _, name, proto = method_triple 245 | if return_type: 246 | return cls_name, name, proto 247 | else: 248 | index = proto.find(')') 249 | proto = proto[:index + 1] 250 | return cls_name, name, proto 251 | 252 | 253 | def create_png(cls_name, meth_name, graph, dir_name='graphs2'): 254 | m_name = ''.join(x for x in meth_name if x.isalnum()) 255 | m_name = m_name.replace('<', '_').replace('>', '_') 256 | name = ''.join((cls_name.split('/')[-1][:-1], '#', m_name)) 257 | graph.draw(name, dir_name) 258 | 259 | 260 | def get_native_type(jtype): 261 | res = DECL_JNI_TYPE.get(jtype) 262 | if res is None: 263 | if jtype == 'Ljava/lang/String;': 264 | res = 'jstring' 265 | elif jtype[0] == 'L': 266 | res = 'jobject' 267 | elif jtype[0] == '[': 268 | res = 'jarray' 269 | else: 270 | res = 'jobject' 271 | logger.debug('Unknown descriptor: "%s".', jtype) 272 | return res 273 | 274 | 275 | def is_int(atype): 276 | return atype and atype in 'ZBCSIJ' 277 | 278 | 279 | def is_long(atype): 280 | return atype == 'J' 281 | 282 | 283 | def is_float(atype): 284 | return atype and atype in 'FD' 285 | 286 | 287 | def is_ref(atype): 288 | return atype and (atype[0] == 'L' or atype[0] == '[') 289 | 290 | 291 | def is_array(atype): 292 | return atype and atype[0] == '[' 293 | 294 | 295 | def is_java_lang_object(atype): 296 | return atype and atype == 'Ljava/lang/Object;' 297 | 298 | 299 | def is_java_lang_object_array(atype): 300 | return is_array(atype) and atype.endswith('Ljava/lang/Object;') 301 | 302 | 303 | # Use for variable declaration 304 | def get_cdecl_type(atype): 305 | if atype in 'ZBCSI': 306 | return 'jint' 307 | elif atype == 'J': 308 | return 'jlong' 309 | elif atype == 'F': 310 | return 'jfloat' 311 | elif atype == 'D': 312 | return 'jdouble' 313 | else: 314 | return 'jobject' 315 | 316 | 317 | # art/runtime/utils.cc 318 | def MangleForJni(name): 319 | result = '' 320 | for ch in name: 321 | if ('A' <= ch <= 'Z') or ('a' <= ch <= 'z') or ('0' <= ch <= '9'): 322 | result += ch 323 | elif ch == '.' or ch == '/': 324 | result += "_" 325 | elif ch == '_': 326 | result += "_1" 327 | elif ch == ';': 328 | result += "_2" 329 | elif ch == '[': 330 | result += "_3" 331 | else: 332 | result += '_0%04x' % (ord(ch)) 333 | return result 334 | 335 | 336 | def JniShortName(cls_name, method_name): 337 | assert cls_name[0] == 'L' 338 | assert cls_name[-1] == ';' 339 | cls_name = cls_name[1:-1] 340 | short_name = 'Java_' 341 | short_name += MangleForJni(cls_name) 342 | short_name += '_' 343 | short_name += MangleForJni(method_name) 344 | return short_name 345 | 346 | 347 | def JniLongName(cls_name, method_name, signature): 348 | long_name = '' 349 | long_name += JniShortName(cls_name, method_name) 350 | long_name += "__" 351 | signature = signature[1:] 352 | index = signature.find(')') 353 | long_name += MangleForJni(signature[:index]) 354 | return long_name 355 | 356 | 357 | def compare_primitive_type(type1, type2): 358 | if type1 is None and type2 is None: 359 | return 0 360 | if type1 is None: 361 | # type1 < type2 362 | return -1 363 | elif type2 is None: 364 | # type1 > type2 365 | return 1 366 | 367 | assert is_primitive_type(type1) 368 | assert is_primitive_type(type2) 369 | 370 | o1 = PRIMITIVE_TYPE_ORDER.get(type1) 371 | o2 = PRIMITIVE_TYPE_ORDER.get(type2) 372 | return o1 - o2 373 | 374 | 375 | def get_smaller_type(type1, type2): 376 | return type1 if compare_primitive_type(type1, type2) < 0 else type2 377 | 378 | 379 | def get_bigger_type(type1, type2): 380 | return type1 if compare_primitive_type(type1, type2) > 0 else type2 381 | 382 | 383 | def merge_array_type(type1, type2): 384 | assert is_array(type1) or is_array(type2) 385 | if is_java_lang_object(type2): 386 | return type2 387 | elif is_java_lang_object(type1): 388 | return type1 389 | if is_array(type1): 390 | if is_array(type2): 391 | new_type = merge_type(type1[1:], type2[1:]) 392 | if new_type: 393 | return '[' + new_type 394 | else: 395 | return None 396 | else: 397 | return 'Ljava/lang/Object;' 398 | else: 399 | return merge_array_type(type2, type1) 400 | 401 | 402 | # return bigger type 403 | def merge_reference_type(type1, type2): 404 | assert is_ref(type1) and is_ref(type2) 405 | if type1 == type2: 406 | return type1 407 | elif is_java_lang_object(type1) and is_ref(type2): 408 | return type1 409 | elif is_java_lang_object(type2) and is_ref(type1): 410 | return type2 411 | else: 412 | return 'Ljava/lang/Object;' 413 | 414 | 415 | def merge_type(type1, type2): 416 | if type1 is None and type2 is None: 417 | return None 418 | if type1 is None: 419 | return type2 420 | if type2 is None: 421 | return type1 422 | 423 | if (is_int(type1) and is_int(type2)) or \ 424 | (is_float(type1) and is_float(type2)): 425 | return get_bigger_type(type1, type2) 426 | elif is_array(type1) or is_array(type2): 427 | new_type = merge_array_type(type1, type2) 428 | if new_type is None: 429 | return 'Ljava/lang/Object;' 430 | else: 431 | return new_type 432 | elif is_ref(type1) or is_ref(type2): 433 | return merge_reference_type(type1, type2) 434 | else: 435 | return None 436 | 437 | 438 | def is_synthetic_method(method): 439 | return method.get_access_flags() & 0x1000 440 | 441 | 442 | def is_native_method(method): 443 | return method.get_access_flags() & 0x100 444 | 445 | 446 | def hex_escape_string(s): 447 | result = '' 448 | s = s.encode('utf8') 449 | for c in s: 450 | result += '\\x%02x' % c 451 | return result 452 | # ERROR: hex escape sequence out of range 453 | # result = '' 454 | # s = s.encode('utf8') 455 | # for c in s: 456 | # if 0x20 <= c < 0x7f: 457 | # if c in (0x22, 0x27, 0x5c): # " ' \ 458 | # result += '\\' 459 | # result += chr(c) 460 | # continue 461 | # if c < 0x20: 462 | # if c == 0x9: 463 | # result += '\\t' 464 | # elif c == 0xa: 465 | # result += '\\n' 466 | # elif c == 0xd: 467 | # result += '\\r' 468 | # else: 469 | # result += '\\x%02x' % c 470 | # continue 471 | # result += '\\x%02x' % c 472 | # return result 473 | 474 | 475 | def get_cdecl_name(clsname): 476 | assert is_ref(clsname) 477 | return clsname[1:-1].replace('/', '_').repace('[', '_') 478 | 479 | 480 | # ERROR: universal character name refers to a control character 481 | def string(s): 482 | """ 483 | Convert a string to a escaped ASCII representation including quotation marks 484 | :param s: a string 485 | :return: ASCII escaped string 486 | """ 487 | ret = [] 488 | for c in s: 489 | if ' ' <= c < '\x7f': 490 | if c == "'" or c == '"' or c == '\\': 491 | ret.append('\\') 492 | ret.append(c) 493 | continue 494 | elif c <= '\x7f': 495 | if c in ('\r', '\n', '\t'): 496 | # unicode-escape produces bytes 497 | ret.append(c.encode('unicode-escape').decode("ascii")) 498 | continue 499 | i = ord(c) 500 | ret.append('\\u') 501 | ret.append('%x' % (i >> 12)) 502 | ret.append('%x' % ((i >> 8) & 0x0f)) 503 | ret.append('%x' % ((i >> 4) & 0x0f)) 504 | ret.append('%x' % (i & 0x0f)) 505 | return ''.join(ret) 506 | -------------------------------------------------------------------------------- /filter.txt: -------------------------------------------------------------------------------- 1 | # Rules are made using regex patterns 2 | 3 | # Protect all methods in all classes 4 | # Not recommended for real world applications. 5 | .* 6 | -------------------------------------------------------------------------------- /keystore/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codehasan/dex2c/1866c6824941327cfcea294e717985be419f57c1/keystore/debug.keystore -------------------------------------------------------------------------------- /loader/DccApplication.smali: -------------------------------------------------------------------------------- 1 | .class public Lamimo/dcc/DccApplication; 2 | .super Landroid/app/Application; 3 | 4 | 5 | # direct methods 6 | .method static final constructor ()V 7 | .registers 1 8 | 9 | const-string v0, "stub" 10 | 11 | invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V 12 | 13 | return-void 14 | .end method 15 | 16 | .method public native constructor ()V 17 | .end method 18 | 19 | .method public static final native initDcc()V 20 | .end method 21 | 22 | 23 | -------------------------------------------------------------------------------- /project/jni/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH:= $(call my-dir) 2 | 3 | include $(CLEAR_VARS) 4 | LOCAL_MODULE := stub 5 | LOCAL_LDLIBS := -llog 6 | 7 | SOURCES := $(wildcard $(LOCAL_PATH)/nc/*.cpp) 8 | LOCAL_C_INCLUDES := $(LOCAL_PATH)/nc 9 | 10 | LOCAL_SRC_FILES := $(SOURCES:$(LOCAL_PATH)/%=%) 11 | 12 | include $(BUILD_SHARED_LIBRARY) 13 | -------------------------------------------------------------------------------- /project/jni/Application.mk: -------------------------------------------------------------------------------- 1 | APP_STL := c++_static 2 | APP_CPPFLAGS += -fvisibility=hidden 3 | APP_PLATFORM := android-19 4 | APP_ABI := armeabi-v7a arm64-v8a 5 | #APP_ABI := armeabi-v7a arm64-v8a x86_64 x86 6 | -------------------------------------------------------------------------------- /project/jni/nc/Dex2C.cpp: -------------------------------------------------------------------------------- 1 | #include "Dex2C.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "ScopedLocalRef.h" 9 | #include "ScopedPthreadMutexLock.h" 10 | #include "well_known_classes.h" 11 | #include "DynamicRegister.h" 12 | 13 | struct MemberTriple { 14 | MemberTriple(const char *cls_name, const char *name, const char *sig) : class_name_(cls_name), 15 | member_name_(name), 16 | signautre_(sig) {} 17 | 18 | const char *class_name_; 19 | const char *member_name_; 20 | const char *signautre_; 21 | 22 | bool operator<(const MemberTriple &member) const { 23 | if (class_name_ != member.class_name_) return class_name_ < member.class_name_; 24 | if (member_name_ != member.member_name_) return member_name_ < member.member_name_; 25 | if (signautre_ != member.signautre_) 26 | return signautre_ < member.signautre_; 27 | else 28 | return false; 29 | } 30 | }; 31 | 32 | static std::map resvoled_fields; 33 | static std::map resvoled_methods; 34 | static std::map resvoled_classes; 35 | static pthread_mutex_t resovle_method_mutex = PTHREAD_MUTEX_INITIALIZER; 36 | static pthread_mutex_t resovle_field_mutex = PTHREAD_MUTEX_INITIALIZER; 37 | static pthread_mutex_t resovle_class_mutex = PTHREAD_MUTEX_INITIALIZER; 38 | 39 | static const int max_global_reference = 1500; 40 | 41 | static void cache_well_known_classes(JNIEnv *env) { 42 | d2c::WellKnownClasses::Init(env); 43 | 44 | resvoled_classes[MemberTriple("Int", NULL, NULL)] = d2c::WellKnownClasses::primitive_int; 45 | resvoled_classes[MemberTriple("Long", NULL, NULL)] = d2c::WellKnownClasses::primitive_long; 46 | resvoled_classes[MemberTriple("Short", NULL, NULL)] = d2c::WellKnownClasses::primitive_short; 47 | resvoled_classes[MemberTriple("Char", NULL, NULL)] = d2c::WellKnownClasses::primitive_char; 48 | resvoled_classes[MemberTriple("Byte", NULL, NULL)] = d2c::WellKnownClasses::primitive_byte; 49 | resvoled_classes[MemberTriple("Boolean", NULL, 50 | NULL)] = d2c::WellKnownClasses::primitive_boolean; 51 | resvoled_classes[MemberTriple("Float", NULL, NULL)] = d2c::WellKnownClasses::primitive_float; 52 | resvoled_classes[MemberTriple("Double", NULL, NULL)] = d2c::WellKnownClasses::primitive_double; 53 | } 54 | 55 | void d2c_throw_exception(JNIEnv *env, const char *class_name, const char *message) { 56 | LOGD("d2c_throw_exception %s %s", class_name, message); 57 | ScopedLocalRef c(env, env->FindClass(class_name)); 58 | if (c.get()) { 59 | env->ThrowNew(c.get(), message); 60 | } 61 | } 62 | 63 | void d2c_filled_new_array(JNIEnv *env, jarray array, const char *type, jint count, ...) { 64 | va_list args; 65 | va_start(args, count); 66 | char ty = type[0]; 67 | bool ref = ty == '[' || ty == 'L'; 68 | for (int i = 0; i < count; i++) { 69 | if (ref) { 70 | env->SetObjectArrayElement((jobjectArray) array, i, (jobject) va_arg(args, long)); 71 | } else { 72 | int val = va_arg(args, jint); 73 | LOGD("idx = %d, val = %d", i, val); 74 | env->SetIntArrayRegion((jintArray) array, i, 1, &val); 75 | } 76 | } 77 | va_end(args); 78 | } 79 | 80 | int64_t d2c_double_to_long(double val) { 81 | int64_t result; 82 | if (val != val) { // NaN 83 | result = 0; 84 | } else if (val > static_cast(INT64_MAX)) { 85 | result = INT64_MAX; 86 | } else if (val < static_cast(INT64_MIN)) { 87 | result = INT64_MIN; 88 | } else { 89 | result = static_cast(val); 90 | } 91 | return result; 92 | } 93 | 94 | int64_t d2c_float_to_long(float val) { 95 | int64_t result; 96 | if (val != val) { // NaN 97 | result = 0; 98 | } else if (val > static_cast(INT64_MAX)) { 99 | result = INT64_MAX; 100 | } else if (val < static_cast(INT64_MIN)) { 101 | result = INT64_MIN; 102 | } else { 103 | result = static_cast(val); 104 | } 105 | return result; 106 | } 107 | 108 | int32_t d2c_double_to_int(double val) { 109 | int32_t result; 110 | if (val != val) { 111 | result = 0; 112 | } else if (val > static_cast(INT32_MAX)) { 113 | result = INT32_MAX; 114 | } else if (val < static_cast(INT32_MIN)) { 115 | result = INT32_MIN; 116 | } else { 117 | result = static_cast(val); 118 | } 119 | return result; 120 | } 121 | 122 | int32_t d2c_float_to_int(float val) { 123 | int32_t result; 124 | if (val != val) { 125 | result = 0; 126 | } else if (val > static_cast(INT32_MAX)) { 127 | result = INT32_MAX; 128 | } else if (val < static_cast(INT32_MIN)) { 129 | result = INT32_MIN; 130 | } else { 131 | result = static_cast(val); 132 | } 133 | return result; 134 | } 135 | 136 | bool d2c_is_instance_of(JNIEnv *env, jobject instance, const char *class_name) { 137 | if (instance == NULL) { 138 | return false; 139 | } 140 | 141 | ScopedLocalRef c(env, env->FindClass(class_name)); 142 | if (c.get()) { 143 | return env->IsInstanceOf(instance, c.get()); 144 | } else { 145 | return false; 146 | } 147 | } 148 | 149 | bool d2c_check_cast(JNIEnv *env, jobject instance, jclass clz, const char *class_name) { 150 | if (env->IsInstanceOf(instance, clz)) { 151 | return false; 152 | } else { 153 | d2c_throw_exception(env, "java/lang/ClassCastException", class_name); 154 | return true; 155 | } 156 | } 157 | 158 | bool d2c_resolve_class(JNIEnv *env, jclass *cached_class, const char *class_name) { 159 | if (*cached_class) { 160 | return false; 161 | } 162 | 163 | MemberTriple triple(class_name, NULL, NULL); 164 | 165 | if (max_global_reference > 0) { 166 | ScopedPthreadMutexLock lock(&resovle_class_mutex); 167 | 168 | auto iter = resvoled_classes.find(triple); 169 | if (iter != resvoled_classes.end()) { 170 | *cached_class = (jclass) iter->second; 171 | return false; 172 | } 173 | } 174 | 175 | jclass clz = env->FindClass(class_name); 176 | if (clz) { 177 | LOGD("resvoled class %s %zd", class_name, resvoled_classes.size()); 178 | if (max_global_reference > 0 && resvoled_classes.size() < max_global_reference) { 179 | ScopedPthreadMutexLock lock(&resovle_class_mutex); 180 | *cached_class = (jclass) env->NewGlobalRef(clz); 181 | resvoled_classes[triple] = *cached_class; 182 | env->DeleteLocalRef(clz); 183 | } else { 184 | *cached_class = clz; 185 | } 186 | return false; 187 | } else { 188 | return true; 189 | } 190 | } 191 | 192 | bool d2c_resolve_method(JNIEnv *env, jclass *cached_class, jmethodID *cached_method, bool is_static, 193 | const char *class_name, const char *method_name, const char *signature) { 194 | if (*cached_method) { 195 | return false; 196 | } 197 | 198 | if (d2c_resolve_class(env, cached_class, class_name)) { 199 | return true; 200 | } 201 | 202 | MemberTriple triple(class_name, method_name, signature); 203 | { 204 | ScopedPthreadMutexLock lock(&resovle_method_mutex); 205 | 206 | auto iter = resvoled_methods.find(triple); 207 | if (iter != resvoled_methods.end()) { 208 | *cached_method = iter->second; 209 | return false; 210 | } 211 | } 212 | 213 | if (is_static) { 214 | *cached_method = env->GetStaticMethodID(*cached_class, method_name, signature); 215 | } else { 216 | *cached_method = env->GetMethodID(*cached_class, method_name, signature); 217 | } 218 | 219 | if (*cached_method) { 220 | ScopedPthreadMutexLock lock(&resovle_method_mutex); 221 | resvoled_methods[triple] = *cached_method; 222 | } 223 | 224 | return *cached_method == NULL; 225 | } 226 | 227 | bool d2c_resolve_field(JNIEnv *env, jclass *cached_class, jfieldID *cached_field, bool is_static, 228 | const char *class_name, const char *field_name, const char *signature) { 229 | if (*cached_field) { 230 | return false; 231 | } 232 | 233 | if (d2c_resolve_class(env, cached_class, class_name)) { 234 | return true; 235 | } 236 | 237 | MemberTriple triple(class_name, field_name, signature); 238 | { 239 | ScopedPthreadMutexLock lock(&resovle_field_mutex); 240 | 241 | auto iter = resvoled_fields.find(triple); 242 | if (iter != resvoled_fields.end()) { 243 | *cached_field = iter->second; 244 | return false; 245 | } 246 | } 247 | 248 | if (is_static) { 249 | *cached_field = env->GetStaticFieldID(*cached_class, field_name, signature); 250 | } else { 251 | *cached_field = env->GetFieldID(*cached_class, field_name, signature); 252 | } 253 | 254 | if (*cached_field) { 255 | ScopedPthreadMutexLock lock(&resovle_field_mutex); 256 | resvoled_fields[triple] = *cached_field; 257 | } 258 | 259 | return *cached_field == NULL; 260 | } 261 | 262 | extern "C" { 263 | 264 | JNIEXPORT void JNICALL 265 | Java_amimo_dcc_DccApplication_initDcc__(JNIEnv *env, jobject thiz) { 266 | 267 | } 268 | 269 | JNIEXPORT void JNICALL 270 | Java_amimo_dcc_DccApplication__0003cinit_0003e__(JNIEnv *env, jobject thiz) { 271 | auto instance = (jobject) env->NewLocalRef(thiz); 272 | jclass application = env->FindClass("android/app/Application"); 273 | jmethodID init = env->GetMethodID(application, "", "()V"); 274 | 275 | env->CallVoidMethodA(instance, init, {}); 276 | } 277 | } 278 | 279 | JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { 280 | JNIEnv *env; 281 | 282 | if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) { 283 | return JNI_ERR; 284 | } 285 | 286 | jclass clz = env->FindClass("amimo/dcc/DccApplication"); 287 | 288 | if (!clz) 289 | exit(1); 290 | 291 | cache_well_known_classes(env); 292 | const char *result = dynamic_register_compile_methods(env); 293 | if (result != nullptr) 294 | { 295 | LOGD("d2c_throw_exception %s", result); 296 | return JNI_ERR; 297 | } 298 | return JNI_VERSION_1_6; 299 | } 300 | -------------------------------------------------------------------------------- /project/jni/nc/Dex2C.h: -------------------------------------------------------------------------------- 1 | #ifndef _DEX2C_H_ 2 | #define _DEX2C_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | //#define DEBUG 12 | 13 | #define D2C_RESOLVE_CLASS(cached_class, class_name) \ 14 | if (cached_class == NULL && d2c_resolve_class(env, &cached_class, class_name)) { \ 15 | goto EX_HANDLE; \ 16 | } 17 | 18 | #define D2C_RESOLVE_METHOD(cached_class, cached_method, class_name, method_name, signature) \ 19 | if (cached_method == NULL && d2c_resolve_method(env, &cached_class, &cached_method, false, class_name, method_name, signature)) { \ 20 | goto EX_HANDLE; \ 21 | } 22 | 23 | #define D2C_RESOLVE_STATIC_METHOD(cached_class, cached_method, class_name, method_name, signature) \ 24 | if (cached_method == NULL && d2c_resolve_method(env, &cached_class, &cached_method, true, class_name, method_name, signature)) { \ 25 | goto EX_HANDLE; \ 26 | } 27 | 28 | #define D2C_RESOLVE_FIELD(cached_class, cached_field, class_name, field_name, signature) \ 29 | if (cached_field == NULL && d2c_resolve_field(env, &cached_class, &cached_field, false, class_name, field_name, signature)) { \ 30 | goto EX_HANDLE; \ 31 | } 32 | 33 | #define D2C_RESOLVE_STATIC_FIELD(cached_class, cached_field, class_name, field_name, signature) \ 34 | if (cached_field == NULL && d2c_resolve_field(env, &cached_class, &cached_field, true, class_name, field_name, signature)) { \ 35 | goto EX_HANDLE; \ 36 | } 37 | 38 | #define D2C_CHECK_PENDING_EX \ 39 | if (env->ExceptionCheck()) { \ 40 | goto EX_HANDLE; \ 41 | } 42 | 43 | #define D2C_GET_PENDING_EX \ 44 | exception = env->ExceptionOccurred(); \ 45 | env->ExceptionClear(); \ 46 | 47 | #define D2C_GOTO_UNWINDBLOCK \ 48 | env->Throw(exception); \ 49 | env->DeleteLocalRef(exception); \ 50 | goto EX_UnwindBlock; 51 | 52 | #define D2C_NOT_NULL(obj) \ 53 | if (obj == NULL) { \ 54 | d2c_throw_exception(env, "java/lang/NullPointerException", "NullPointerException"); \ 55 | goto EX_HANDLE; \ 56 | } 57 | 58 | #define D2C_CHECK_CAST(obj, clz, class_name) \ 59 | if (d2c_check_cast(env, obj, clz, class_name)) { \ 60 | goto EX_HANDLE; \ 61 | } 62 | 63 | #ifdef DEBUG 64 | #define LOGD(...) \ 65 | __android_log_print(ANDROID_LOG_DEBUG, "Dex2C", __VA_ARGS__) 66 | #else 67 | #define LOGD(...) (0) 68 | #endif 69 | 70 | inline jdouble d2c_bitcast_to_double(uint64_t val) { 71 | union { 72 | double dest; 73 | uint64_t src; 74 | } conv; 75 | conv.src = val; 76 | return conv.dest; 77 | } 78 | 79 | inline jfloat d2c_bitcast_to_float(uint32_t val) { 80 | union { 81 | float dest; 82 | uint32_t src; 83 | } conv; 84 | conv.src = val; 85 | return conv.dest; 86 | } 87 | 88 | inline double d2c_long_to_double(int64_t l) { 89 | return static_cast(l); 90 | } 91 | 92 | inline float d2c_long_to_float(int64_t l) { 93 | return static_cast(l); 94 | } 95 | 96 | int64_t d2c_double_to_long(double val); 97 | 98 | int32_t d2c_double_to_int(double val); 99 | 100 | int64_t d2c_float_to_long(float val); 101 | 102 | int32_t d2c_float_to_int(float val); 103 | 104 | void d2c_filled_new_array(JNIEnv *env, jarray array, const char *type, jint count, ...); 105 | 106 | void d2c_throw_exception(JNIEnv *env, const char *name, const char *msg); 107 | 108 | inline bool d2c_is_instance_of(JNIEnv *env, jobject instance, jclass clz) { 109 | if (instance) { 110 | return env->IsInstanceOf(instance, clz); 111 | } else { 112 | return false; 113 | } 114 | } 115 | 116 | bool d2c_is_instance_of(JNIEnv *env, jobject instance, const char *class_name); 117 | 118 | inline bool d2c_is_same_object(JNIEnv *env, jobject obj1, jobject obj2) { 119 | if (obj1 == obj2) { 120 | return true; 121 | } 122 | if (obj1 && obj2) { 123 | return env->IsSameObject(obj1, obj2); 124 | } else { 125 | return false; 126 | } 127 | } 128 | 129 | /* The following functions return true if exception occurred */ 130 | bool d2c_check_cast(JNIEnv *env, jobject instance, jclass clz, const char *class_name); 131 | 132 | bool d2c_resolve_class(JNIEnv *env, jclass *cached_class, const char *class_name); 133 | 134 | bool d2c_resolve_method(JNIEnv *env, jclass *cached_class, jmethodID *cached_method, bool is_static, 135 | const char *class_name, const char *method_name, const char *signature); 136 | 137 | bool d2c_resolve_field(JNIEnv *env, jclass *cached_class, jfieldID *cached_field, bool is_static, 138 | const char *class_name, const char *field_name, const char *signature); 139 | 140 | #endif 141 | -------------------------------------------------------------------------------- /project/jni/nc/DynamicRegister.h: -------------------------------------------------------------------------------- 1 | #ifndef _DYNAMIC_REGISTER_H_ // !_DYNAMIC_REGISTER_H_ 2 | #define _DYNAMIC_REGISTER_H_ 3 | 4 | #include 5 | 6 | const char *dynamic_register_compile_methods(JNIEnv *env); 7 | 8 | #endif // !_DYNAMIC_REGISTER_H_ -------------------------------------------------------------------------------- /project/jni/nc/ScopedLocalRef.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef SCOPED_LOCAL_REF_H_included 18 | #define SCOPED_LOCAL_REF_H_included 19 | 20 | #include "jni.h" 21 | 22 | #include 23 | 24 | // A smart pointer that deletes a JNI local reference when it goes out of scope. 25 | template 26 | class ScopedLocalRef { 27 | public: 28 | ScopedLocalRef(JNIEnv* env, T localRef) : mEnv(env), mLocalRef(localRef) { 29 | } 30 | 31 | ~ScopedLocalRef() { 32 | reset(); 33 | } 34 | 35 | void reset(T ptr = NULL) { 36 | if (ptr != mLocalRef) { 37 | if (mLocalRef != NULL) { 38 | mEnv->DeleteLocalRef(mLocalRef); 39 | } 40 | mLocalRef = ptr; 41 | } 42 | } 43 | 44 | T release() __attribute__((warn_unused_result)) { 45 | T localRef = mLocalRef; 46 | mLocalRef = NULL; 47 | return localRef; 48 | } 49 | 50 | T get() const { 51 | return mLocalRef; 52 | } 53 | 54 | private: 55 | JNIEnv* mEnv; 56 | T mLocalRef; 57 | 58 | // Disallow copy and assignment. 59 | ScopedLocalRef(const ScopedLocalRef&); 60 | void operator=(const ScopedLocalRef&); 61 | }; 62 | 63 | #endif // SCOPED_LOCAL_REF_H_included 64 | -------------------------------------------------------------------------------- /project/jni/nc/ScopedPthreadMutexLock.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef SCOPED_PTHREAD_MUTEX_LOCK_H_included 18 | #define SCOPED_PTHREAD_MUTEX_LOCK_H_included 19 | 20 | #include 21 | 22 | /** 23 | * Locks and unlocks a pthread_mutex_t as it goes in and out of scope. 24 | */ 25 | class ScopedPthreadMutexLock { 26 | public: 27 | explicit ScopedPthreadMutexLock(pthread_mutex_t* mutex) : mMutexPtr(mutex) { 28 | pthread_mutex_lock(mMutexPtr); 29 | } 30 | 31 | ~ScopedPthreadMutexLock() { 32 | pthread_mutex_unlock(mMutexPtr); 33 | } 34 | 35 | private: 36 | pthread_mutex_t* mMutexPtr; 37 | 38 | // Disallow copy and assignment. 39 | ScopedPthreadMutexLock(const ScopedPthreadMutexLock&); 40 | void operator=(const ScopedPthreadMutexLock&); 41 | }; 42 | 43 | #endif // SCOPED_PTHREAD_MUTEX_LOCK_H_included 44 | -------------------------------------------------------------------------------- /project/jni/nc/obfuscate.h: -------------------------------------------------------------------------------- 1 | /* --------------------------------- ABOUT ------------------------------------- 2 | 3 | Original Author: Adam Yaxley 4 | Website: https://github.com/adamyaxley 5 | License: See end of file 6 | 7 | Obfuscate 8 | Guaranteed compile-time string literal obfuscation library for C++14 9 | 10 | Usage: 11 | Pass string literals into the AY_OBFUSCATE macro to obfuscate them at compile 12 | time. AY_OBFUSCATE returns a reference to an ay::obfuscated_data object with the 13 | following traits: 14 | - Guaranteed obfuscation of string 15 | The passed string is encrypted with a simple XOR cipher at compile-time to 16 | prevent it being viewable in the binary image 17 | - Global lifetime 18 | The actual instantiation of the ay::obfuscated_data takes place inside a 19 | lambda as a function level static 20 | - Implicitly convertible to a char* 21 | This means that you can pass it directly into functions that would normally 22 | take a char* or a const char* 23 | 24 | Example: 25 | const char* obfuscated_string = AY_OBFUSCATE("Hello World"); 26 | std::cout << obfuscated_string << std::endl; 27 | 28 | ----------------------------------------------------------------------------- */ 29 | 30 | #pragma once 31 | #if __cplusplus >= 202002L 32 | #define AY_CONSTEVAL consteval 33 | #else 34 | #define AY_CONSTEVAL constexpr 35 | #endif 36 | 37 | // Workaround for __LINE__ not being constexpr when /ZI (Edit and Continue) is enabled in Visual Studio 38 | // See: https://developercommunity.visualstudio.com/t/-line-cannot-be-used-as-an-argument-for-constexpr/195665 39 | #ifdef _MSC_VER 40 | #define AY_CAT(X,Y) AY_CAT2(X,Y) 41 | #define AY_CAT2(X,Y) X##Y 42 | #define AY_LINE int(AY_CAT(__LINE__,U)) 43 | #else 44 | #define AY_LINE __LINE__ 45 | #endif 46 | 47 | #ifndef AY_OBFUSCATE_DEFAULT_KEY 48 | // The default 64 bit key to obfuscate strings with. 49 | // This can be user specified by defining AY_OBFUSCATE_DEFAULT_KEY before 50 | // including obfuscate.h 51 | #define AY_OBFUSCATE_DEFAULT_KEY ay::generate_key(AY_LINE) 52 | #endif 53 | 54 | namespace ay 55 | { 56 | using size_type = unsigned long long; 57 | using key_type = unsigned long long; 58 | 59 | // libstdc++ has std::remove_cvref_t since C++20, but because not every user will be 60 | // able or willing to link to the STL, we prefer to do this functionality ourselves here. 61 | template 62 | struct remove_const_ref { 63 | using type = T; 64 | }; 65 | 66 | template 67 | struct remove_const_ref { 68 | using type = T; 69 | }; 70 | 71 | template 72 | struct remove_const_ref { 73 | using type = T; 74 | }; 75 | 76 | template 77 | struct remove_const_ref { 78 | using type = T; 79 | }; 80 | 81 | template 82 | using char_type = typename remove_const_ref::type; 83 | 84 | // Generate a pseudo-random key that spans all 8 bytes 85 | AY_CONSTEVAL key_type generate_key(key_type seed) 86 | { 87 | // Use the MurmurHash3 64-bit finalizer to hash our seed 88 | key_type key = seed; 89 | key ^= (key >> 33); 90 | key *= 0xff51afd7ed558ccd; 91 | key ^= (key >> 33); 92 | key *= 0xc4ceb9fe1a85ec53; 93 | key ^= (key >> 33); 94 | 95 | // Make sure that a bit in each byte is set 96 | key |= 0x0101010101010101ull; 97 | 98 | return key; 99 | } 100 | 101 | // Obfuscates or deobfuscates data with key 102 | template 103 | constexpr void cipher(CHAR_TYPE* data, size_type size, key_type key) 104 | { 105 | // Obfuscate with a simple XOR cipher based on key 106 | for (size_type i = 0; i < size; i++) 107 | { 108 | data[i] ^= CHAR_TYPE((key >> ((i % 8) * 8)) & 0xFF); 109 | } 110 | } 111 | 112 | // Obfuscates a string at compile time 113 | template 114 | class obfuscator 115 | { 116 | public: 117 | // Obfuscates the string 'data' on construction 118 | AY_CONSTEVAL obfuscator(const CHAR_TYPE* data) 119 | { 120 | // Copy data 121 | for (size_type i = 0; i < N; i++) 122 | { 123 | m_data[i] = data[i]; 124 | } 125 | 126 | // On construction each of the characters in the string is 127 | // obfuscated with an XOR cipher based on key 128 | cipher(m_data, N, KEY); 129 | } 130 | 131 | constexpr const CHAR_TYPE* data() const 132 | { 133 | return &m_data[0]; 134 | } 135 | 136 | AY_CONSTEVAL size_type size() const 137 | { 138 | return N; 139 | } 140 | 141 | AY_CONSTEVAL key_type key() const 142 | { 143 | return KEY; 144 | } 145 | 146 | private: 147 | 148 | CHAR_TYPE m_data[N]{}; 149 | }; 150 | 151 | // Handles decryption and re-encryption of an encrypted string at runtime 152 | template 153 | class obfuscated_data 154 | { 155 | public: 156 | obfuscated_data(const obfuscator& obfuscator) 157 | { 158 | // Copy obfuscated data 159 | for (size_type i = 0; i < N; i++) 160 | { 161 | m_data[i] = obfuscator.data()[i]; 162 | } 163 | } 164 | 165 | ~obfuscated_data() 166 | { 167 | // Zero m_data to remove it from memory 168 | for (size_type i = 0; i < N; i++) 169 | { 170 | m_data[i] = 0; 171 | } 172 | } 173 | 174 | // Returns a pointer to the plain text string, decrypting it if 175 | // necessary 176 | operator CHAR_TYPE* () 177 | { 178 | decrypt(); 179 | return m_data; 180 | } 181 | 182 | // Manually decrypt the string 183 | void decrypt() 184 | { 185 | if (m_encrypted) 186 | { 187 | cipher(m_data, N, KEY); 188 | m_encrypted = false; 189 | } 190 | } 191 | 192 | // Manually re-encrypt the string 193 | void encrypt() 194 | { 195 | if (!m_encrypted) 196 | { 197 | cipher(m_data, N, KEY); 198 | m_encrypted = true; 199 | } 200 | } 201 | 202 | // Returns true if this string is currently encrypted, false otherwise. 203 | bool is_encrypted() const 204 | { 205 | return m_encrypted; 206 | } 207 | 208 | private: 209 | 210 | // Local storage for the string. Call is_encrypted() to check whether or 211 | // not the string is currently obfuscated. 212 | CHAR_TYPE m_data[N]; 213 | 214 | // Whether data is currently encrypted 215 | bool m_encrypted{ true }; 216 | }; 217 | 218 | // This function exists purely to extract the number of elements 'N' in the 219 | // array 'data' 220 | template 221 | AY_CONSTEVAL auto make_obfuscator(const CHAR_TYPE(&data)[N]) 222 | { 223 | return obfuscator(data); 224 | } 225 | } 226 | 227 | // Obfuscates the string 'data' at compile-time and returns a reference to a 228 | // ay::obfuscated_data object with global lifetime that has functions for 229 | // decrypting the string and is also implicitly convertable to a char* 230 | #define AY_OBFUSCATE(data) AY_OBFUSCATE_KEY(data, AY_OBFUSCATE_DEFAULT_KEY) 231 | 232 | // Obfuscates the string 'data' with 'key' at compile-time and returns a 233 | // reference to a ay::obfuscated_data object with global lifetime that has 234 | // functions for decrypting the string and is also implicitly convertable to a 235 | // char* 236 | #define AY_OBFUSCATE_KEY(data, key) \ 237 | []() -> ay::obfuscated_data>& { \ 238 | static_assert(sizeof(decltype(key)) == sizeof(ay::key_type), "key must be a 64 bit unsigned integer"); \ 239 | static_assert((key) >= (1ull << 56), "key must span all 8 bytes"); \ 240 | using char_type = ay::char_type; \ 241 | constexpr auto n = sizeof(data)/sizeof(data[0]); \ 242 | constexpr auto obfuscator = ay::make_obfuscator(data); \ 243 | thread_local auto obfuscated_data = ay::obfuscated_data(obfuscator); \ 244 | return obfuscated_data; \ 245 | }() 246 | 247 | /* -------------------------------- LICENSE ------------------------------------ 248 | 249 | Public Domain (http://www.unlicense.org) 250 | 251 | This is free and unencumbered software released into the public domain. 252 | 253 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 254 | software, either in source code form or as a compiled binary, for any purpose, 255 | commercial or non-commercial, and by any means. 256 | 257 | In jurisdictions that recognize copyright laws, the author or authors of this 258 | software dedicate any and all copyright interest in the software to the public 259 | domain. We make this dedication for the benefit of the public at large and to 260 | the detriment of our heirs and successors. We intend this dedication to be an 261 | overt act of relinquishment in perpetuity of all present and future rights to 262 | this software under copyright law. 263 | 264 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 265 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 266 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE 267 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 268 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 269 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 270 | 271 | ----------------------------------------------------------------------------- */ 272 | -------------------------------------------------------------------------------- /project/jni/nc/well_known_classes.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include "well_known_classes.h" 22 | #include "ScopedLocalRef.h" 23 | 24 | #define LOG_FATAL(...) __android_log_print(ANDROID_LOG_FATAL, "WellKnownClasses", __VA_ARGS__) 25 | 26 | 27 | namespace d2c { 28 | 29 | jclass WellKnownClasses::java_lang_Double; 30 | jclass WellKnownClasses::java_lang_Float; 31 | jclass WellKnownClasses::java_lang_Long; 32 | jclass WellKnownClasses::java_lang_Integer; 33 | jclass WellKnownClasses::java_lang_Short; 34 | jclass WellKnownClasses::java_lang_Character; 35 | jclass WellKnownClasses::java_lang_Byte; 36 | jclass WellKnownClasses::java_lang_Boolean; 37 | 38 | jclass WellKnownClasses::primitive_double; 39 | jclass WellKnownClasses::primitive_float; 40 | jclass WellKnownClasses::primitive_long; 41 | jclass WellKnownClasses::primitive_int; 42 | jclass WellKnownClasses::primitive_short; 43 | jclass WellKnownClasses::primitive_char; 44 | jclass WellKnownClasses::primitive_byte; 45 | jclass WellKnownClasses::primitive_boolean; 46 | 47 | static jobject CachePrimitiveClass(JNIEnv *env, jclass c, const char *name, const char *signature) { 48 | jfieldID fid = env->GetStaticFieldID(c, name, signature); 49 | if (fid == NULL) { 50 | LOG_FATAL ( "Couldn't find field \"%s\" with signature \"%s\"", name, signature); 51 | } 52 | jobject val = env->GetStaticObjectField(c, fid); 53 | return env->NewGlobalRef(val); 54 | } 55 | 56 | jclass CacheClass(JNIEnv* env, const char* jni_class_name) { 57 | ScopedLocalRef c(env, env->FindClass(jni_class_name)); 58 | if (c.get() == NULL) { 59 | LOG_FATAL ("Couldn't find class: %s", jni_class_name); 60 | } 61 | return reinterpret_cast(env->NewGlobalRef(c.get())); 62 | } 63 | 64 | jfieldID CacheField(JNIEnv* env, jclass c, bool is_static, const char* name, const char* signature) { 65 | jfieldID fid = is_static ? env->GetStaticFieldID(c, name, signature) : env->GetFieldID(c, name, signature); 66 | if (fid == NULL) { 67 | LOG_FATAL ( "Couldn't find field \"%s\" with signature \"%s\"", name, signature); 68 | } 69 | return fid; 70 | } 71 | 72 | jmethodID CacheMethod(JNIEnv* env, jclass c, bool is_static, const char* name, const char* signature) { 73 | jmethodID mid = is_static ? env->GetStaticMethodID(c, name, signature) : env->GetMethodID(c, name, signature); 74 | if (mid == NULL) { 75 | LOG_FATAL ( "Couldn't find method \"%s\" with signature \"%s\"", name, signature); 76 | } 77 | return mid; 78 | } 79 | 80 | 81 | void WellKnownClasses::Init(JNIEnv* env) { 82 | java_lang_Double = CacheClass(env, "java/lang/Double"); 83 | java_lang_Float = CacheClass(env, "java/lang/Float"); 84 | java_lang_Long = CacheClass(env, "java/lang/Long"); 85 | java_lang_Integer = CacheClass(env, "java/lang/Integer"); 86 | java_lang_Short = CacheClass(env, "java/lang/Short"); 87 | java_lang_Character = CacheClass(env, "java/lang/Character"); 88 | java_lang_Byte = CacheClass(env, "java/lang/Byte"); 89 | java_lang_Boolean = CacheClass(env, "java/lang/Boolean"); 90 | 91 | primitive_double = static_cast(CachePrimitiveClass(env, java_lang_Double, "TYPE", 92 | "Ljava/lang/Class;")); 93 | primitive_float = static_cast(CachePrimitiveClass(env, java_lang_Float, "TYPE", 94 | "Ljava/lang/Class;")); 95 | primitive_long = static_cast(CachePrimitiveClass(env, java_lang_Long, "TYPE", 96 | "Ljava/lang/Class;")); 97 | primitive_int = static_cast(CachePrimitiveClass(env, java_lang_Integer, "TYPE", 98 | "Ljava/lang/Class;")); 99 | primitive_short = static_cast(CachePrimitiveClass(env, java_lang_Short, "TYPE", 100 | "Ljava/lang/Class;")); 101 | primitive_char = static_cast(CachePrimitiveClass(env, java_lang_Character, "TYPE", 102 | "Ljava/lang/Class;")); 103 | primitive_byte = static_cast(CachePrimitiveClass(env, java_lang_Byte, "TYPE", 104 | "Ljava/lang/Class;")); 105 | primitive_boolean = static_cast(CachePrimitiveClass(env, java_lang_Boolean, "TYPE", 106 | "Ljava/lang/Class;")); 107 | 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /project/jni/nc/well_known_classes.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef ART_RUNTIME_WELL_KNOWN_CLASSES_H_ 18 | #define ART_RUNTIME_WELL_KNOWN_CLASSES_H_ 19 | 20 | #include 21 | #include 22 | 23 | namespace d2c { 24 | 25 | // Various classes used in JNI. We cache them so we don't have to keep looking 26 | // them up. Similar to libcore's JniConstants (except there's no overlap, so 27 | // we keep them separate). 28 | 29 | jmethodID CacheMethod(JNIEnv* env, jclass c, bool is_static, const char* name, const char* signature); 30 | jclass CacheClass(JNIEnv* env, const char* jni_class_name); 31 | jfieldID CacheField(JNIEnv* env, jclass c, bool is_static, const char* name, const char* signature); 32 | 33 | struct WellKnownClasses { 34 | public: 35 | static void Init(JNIEnv* env); // Run before native methods are registered. 36 | 37 | static jclass java_lang_Double; 38 | static jclass java_lang_Float; 39 | static jclass java_lang_Long; 40 | static jclass java_lang_Integer; 41 | static jclass java_lang_Short; 42 | static jclass java_lang_Character; 43 | static jclass java_lang_Byte; 44 | static jclass java_lang_Boolean; 45 | 46 | static jclass primitive_double; 47 | static jclass primitive_float; 48 | static jclass primitive_long; 49 | static jclass primitive_int; 50 | static jclass primitive_short; 51 | static jclass primitive_char; 52 | static jclass primitive_byte; 53 | static jclass primitive_boolean; 54 | }; 55 | 56 | } // namespace art 57 | 58 | #endif // ART_RUNTIME_WELL_KNOWN_CLASSES_H_ 59 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | networkx 2 | pydot>=1.4.1 3 | future 4 | pyasn1 5 | cryptography 6 | lxml>=4.3.0 7 | asn1crypto>=0.24.0 8 | -------------------------------------------------------------------------------- /termux_install.sh: -------------------------------------------------------------------------------- 1 | if ! command -v termux-setup-storage; then 2 | echo "This script can be executed only on Termux" 3 | exit 1 4 | fi 5 | 6 | termux-setup-storage 7 | 8 | cd $HOME 9 | 10 | pkg update 11 | pkg upgrade -y 12 | pkg i -y ncurses-utils 13 | 14 | green="$(tput setaf 2)" 15 | nocolor="$(tput sgr0)" 16 | red="$(tput setaf 1)" 17 | blue="$(tput setaf 32)" 18 | yellow="$(tput setaf 3)" 19 | note="$(tput setaf 6)" 20 | 21 | echo "${green}━━━ Basic Requirements Setup ━━━${nocolor}" 22 | 23 | pkg install -y python git cmake rust clang make wget ndk-sysroot zlib libxml2 libxslt pkg-config libjpeg-turbo build-essential binutils openssl 24 | # UnComment below line if you face clang error during installation procedure 25 | # _file=$(find $PREFIX/lib/python3.11/_sysconfigdata*.py) 26 | # rm -rf $PREFIX/lib/python3.11/__pycache__ 27 | # sed -i 's|-fno-openmp-implicit-rpath||g' "$_file" 28 | pkg install -y python-cryptography 29 | LDFLAGS="-L${PREFIX}/lib/" CFLAGS="-I${PREFIX}/include/" pip install --upgrade wheel pillow 30 | pip install cython setuptools 31 | CFLAGS="-Wno-error=incompatible-function-pointer-types -O0" pip install --upgrade lxml 32 | 33 | echo "${green}━━━ Starting SDK Tools installation ━━━${nocolor}" 34 | if [ -d "android-sdk" ]; then 35 | echo "${red}Seems like sdk tools already installed, skipping...${nocolor}" 36 | elif [ -d "androidide-tools" ]; then 37 | rm -rf androidide-tools 38 | git clone https://github.com/AndroidIDEOfficial/androidide-tools 39 | cd androidide-tools/scripts 40 | ./idesetup -c 41 | else 42 | git clone https://github.com/AndroidIDEOfficial/androidide-tools 43 | cd androidide-tools/scripts 44 | ./idesetup -c 45 | fi 46 | 47 | echo "${yellow}ANDROID SDK TOOLS Successfully Installed!${nocolor}" 48 | 49 | cd $HOME 50 | echo 51 | echo "${green}━━━ Starting NDK installation ━━━${nocolor}" 52 | echo "Now You'll be asked about which version of NDK to isntall" 53 | echo "${note}If your Android Version is 9 or above then choose ${red}'9'${nocolor}" 54 | echo "${note}If your Android Version is below 9 or if you faced issues with '9' (A9 and above users) then choose ${red}'8'${nocolor}" 55 | echo "${red} If you're choosing other options then you're on your own and experiment yourself ¯⁠\⁠_⁠ಠ⁠_⁠ಠ⁠_⁠/⁠¯${nocolor}" 56 | if [ -f "ndk-install.sh" ]; then 57 | chmod +x ndk-install.sh && bash ndk-install.sh 58 | else 59 | cd && pkg upgrade && pkg install wget && wget https://github.com/MrIkso/AndroidIDE-NDK/raw/main/ndk-install.sh --no-verbose --show-progress -N && chmod +x ndk-install.sh && bash ndk-install.sh 60 | fi 61 | 62 | if [ -f "ndk-install.sh" ]; then 63 | rm ndk-install.sh 64 | fi 65 | 66 | if [ -d "$HOME/android-sdk/ndk/17.2.4988734" ]; then 67 | ndk_version="17.2.4988734" 68 | elif [ -d "$HOME/android-sdk/ndk/18.1.5063045" ]; then 69 | ndk_version="18.1.5063045" 70 | elif [ -d "$HOME/android-sdk/ndk/19.2.5345600" ]; then 71 | ndk_version="19.2.5345600" 72 | elif [ -d "$HOME/android-sdk/ndk/20.1.5948944" ]; then 73 | ndk_version="20.1.5948944" 74 | elif [ -d "$HOME/android-sdk/ndk/21.4.7075529" ]; then 75 | ndk_version="21.4.7075529" 76 | elif [ -d "$HOME/android-sdk/ndk/22.1.7171670" ]; then 77 | ndk_version="22.1.7171670" 78 | elif [ -d "$HOME/android-sdk/ndk/23.2.8568313" ]; then 79 | ndk_version="23.2.8568313" 80 | elif [ -d "$HOME/android-sdk/ndk/24.0.8215888" ]; then 81 | ndk_version="24.0.8215888" 82 | elif [ -d "$HOME/android-sdk/ndk/26.1.10909125" ]; then 83 | ndk_version="26.1.10909125" 84 | elif [ -d "$HOME/android-sdk/ndk/27.1.12297006" ]; then 85 | ndk_version="27.1.12297006" 86 | else 87 | echo "${red}You didn't Installed any ndk terminating!" 88 | exit 1 89 | fi 90 | echo "${yellow}ANDROID NDK Successfully Installed!${nocolor}" 91 | 92 | cd $HOME 93 | echo 94 | echo "${green}━━━ Setting up apktool ━━━${nocolor}" 95 | if [ -f "$PREFIX/bin/apktool.jar" ]; then 96 | echo "${blue}apktool is already installed${nocolor}" 97 | else 98 | sh -c 'wget https://bitbucket.org/iBotPeaches/apktool/downloads/apktool_2.10.0.jar -O $PREFIX/bin/apktool.jar' 99 | 100 | chmod +r $PREFIX/bin/apktool.jar 101 | 102 | sh -c 'wget https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/linux/apktool -O $PREFIX/bin/apktool' && chmod +x $PREFIX/bin/apktool || exit 2 103 | fi 104 | 105 | cd $HOME 106 | if [ -d "dex2c" ]; then 107 | cd dex2c 108 | elif [ -f "dcc.py" ] && [ -d "tools" ]; then 109 | : 110 | else 111 | git clone https://github.com/ratsan/dex2c || exit 2 112 | cd dex2c || exit 2 113 | fi 114 | 115 | if [ -f "$HOME/dex2c/tools/apktool.jar" ]; then 116 | rm $HOME/dex2c/tools/apktool.jar 117 | cp $PREFIX/bin/apktool.jar $HOME/dex2c/tools/apktool.jar 118 | else 119 | sh -c 'wget https://bitbucket.org/iBotPeaches/apktool/downloads/apktool_2.10.0.jar -O $HOME/dex2c/tools/apktool.jar' 120 | fi 121 | 122 | cd ~/dex2c 123 | python3 -m pip install -r requirements.txt || exit 2 124 | 125 | if [ -f "$HOME/.bashrc" ]; then 126 | echo -e "export ANDROID_HOME=$HOME/android-sdk\nexport PATH=\$PATH:$HOME/android-sdk/cmdline-tools/latest/bin\nexport PATH=\$PATH:$HOME/android-sdk/platform-tools\nexport PATH=\$PATH:$HOME/android-sdk/build-tools/34.0.4\nexport PATH=\$PATH:$HOME/android-sdk/ndk/$ndk_version\nexport ANDROID_NDK_ROOT=$HOME/android-sdk/ndk/$ndk_version" >> ~/.bashrc 127 | elif [ -f "$HOME/.zshrc" ]; then 128 | echo -e "export ANDROID_HOME=$HOME/android-sdk\nexport PATH=\$PATH:$HOME/android-sdk/cmdline-tools/latest/bin\nexport PATH=\$PATH:$HOME/android-sdk/platform-tools\nexport PATH=\$PATH:$HOME/android-sdk/build-tools/34.0.4\nexport PATH=\$PATH:$HOME/android-sdk/ndk/$ndk_version\nexport ANDROID_NDK_ROOT=$HOME/android-sdk/ndk/$ndk_version" >> ~/.zshrc 129 | else 130 | echo -e "export ANDROID_HOME=$HOME/android-sdk\nexport PATH=\$PATH:$HOME/android-sdk/cmdline-tools/latest/bin\nexport PATH=\$PATH:$HOME/android-sdk/platform-tools\nexport PATH=\$PATH:$HOME/android-sdk/build-tools/34.0.4\nexport PATH=\$PATH:$HOME/android-sdk/ndk/$ndk_version\nexport ANDROID_NDK_ROOT=$HOME/android-sdk/ndk/$ndk_version" >> $PREFIX/etc/bash.bashrc 131 | fi 132 | 133 | cat > $HOME/dex2c/dcc.cfg << EOL 134 | { 135 | "apktool": "tools/apktool.jar", 136 | "ndk_dir": "$HOME/android-sdk/ndk/${ndk_version}", 137 | "signature": { 138 | "keystore_path": "keystore/debug.keystore", 139 | "alias": "androiddebugkey", 140 | "keystore_pass": "android", 141 | "store_pass": "android", 142 | "v1_enabled": true, 143 | "v2_enabled": true, 144 | "v3_enabled": true 145 | } 146 | } 147 | EOL 148 | 149 | echo "${green}============================" 150 | echo "Great! dex2c installed successfully!" 151 | echo "============================${nocolor}" 152 | -------------------------------------------------------------------------------- /tools/apksigner.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codehasan/dex2c/1866c6824941327cfcea294e717985be419f57c1/tools/apksigner.jar -------------------------------------------------------------------------------- /tools/apktool: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright (C) 2007 The Android Open Source Project 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # This script is a wrapper for smali.jar, so you can simply call "smali", 18 | # instead of java -jar smali.jar. It is heavily based on the "dx" script 19 | # from the Android SDK 20 | 21 | # Set up prog to be the path of this script, including following symlinks, 22 | # and set up progdir to be the fully-qualified pathname of its directory. 23 | prog="$0" 24 | while [ -h "${prog}" ]; do 25 | newProg=`/bin/ls -ld "${prog}"` 26 | 27 | newProg=`expr "${newProg}" : ".* -> \(.*\)$"` 28 | if expr "x${newProg}" : 'x/' >/dev/null; then 29 | prog="${newProg}" 30 | else 31 | progdir=`dirname "${prog}"` 32 | prog="${progdir}/${newProg}" 33 | fi 34 | done 35 | oldwd=`pwd` 36 | progdir=`dirname "${prog}"` 37 | cd "${progdir}" 38 | progdir=`pwd` 39 | prog="${progdir}"/`basename "${prog}"` 40 | cd "${oldwd}" 41 | 42 | jarfile=apktool.jar 43 | libdir="$progdir" 44 | if [ ! -r "$libdir/$jarfile" ] 45 | then 46 | echo `basename "$prog"`": can't find $jarfile" 47 | exit 1 48 | fi 49 | 50 | javaOpts="" 51 | 52 | # If you want DX to have more memory when executing, uncomment the following 53 | # line and adjust the value accordingly. Use "java -X" for a list of options 54 | # you can pass here. 55 | # 56 | javaOpts="-Xmx1024M -Dfile.encoding=utf-8 -Djdk.util.zip.disableZip64ExtraFieldValidation=true -Djdk.nio.zipfs.allowDotZipEntry=true" 57 | 58 | # Alternatively, this will extract any parameter "-Jxxx" from the command line 59 | # and pass them to Java (instead of to dx). This makes it possible for you to 60 | # add a command-line parameter such as "-JXmx256M" in your ant scripts, for 61 | # example. 62 | while expr "x$1" : 'x-J' >/dev/null; do 63 | opt=`expr "$1" : '-J\(.*\)'` 64 | javaOpts="${javaOpts} -${opt}" 65 | shift 66 | done 67 | 68 | if [ "$OSTYPE" = "cygwin" ] ; then 69 | jarpath=`cygpath -w "$libdir/$jarfile"` 70 | else 71 | jarpath="$libdir/$jarfile" 72 | fi 73 | 74 | # add current location to path for aapt 75 | PATH=$PATH:`pwd`; 76 | export PATH; 77 | exec java $javaOpts -jar "$jarpath" "$@" 78 | -------------------------------------------------------------------------------- /tools/apktool.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | set BASENAME=apktool_ 4 | chcp 65001 2>nul >nul 5 | 6 | set java_exe=java.exe 7 | 8 | if defined JAVA_HOME ( 9 | set "java_exe=%JAVA_HOME%\bin\java.exe" 10 | ) 11 | 12 | rem Find the highest version .jar available in the same directory as the script 13 | setlocal EnableDelayedExpansion 14 | pushd "%~dp0" 15 | if exist apktool.jar ( 16 | set BASENAME=apktool 17 | goto skipversioned 18 | ) 19 | set max=0 20 | for /f "tokens=1* delims=-_.0" %%A in ('dir /b /a-d %BASENAME%*.jar') do if %%~B gtr !max! set max=%%~nB 21 | :skipversioned 22 | popd 23 | setlocal DisableDelayedExpansion 24 | 25 | rem Find out if the commandline is a parameterless .jar or directory, for fast unpack/repack 26 | if "%~1"=="" goto load 27 | if not "%~2"=="" goto load 28 | set ATTR=%~a1 29 | if "%ATTR:~0,1%"=="d" ( 30 | rem Directory, rebuild 31 | set fastCommand=b 32 | ) 33 | if "%ATTR:~0,1%"=="-" if "%~x1"==".apk" ( 34 | rem APK file, unpack 35 | set fastCommand=d 36 | ) 37 | 38 | :load 39 | "%java_exe%" -jar -Xmx1024M -Duser.language=en -Dfile.encoding=UTF8 -Djdk.util.zip.disableZip64ExtraFieldValidation=true -Djdk.nio.zipfs.allowDotZipEntry=true "%~dp0%BASENAME%%max%.jar" %fastCommand% %* 40 | 41 | rem No pause when ran non interactively 42 | for %%i in (%cmdcmdline%) do if /i "%%~i"=="/c" exit /b -------------------------------------------------------------------------------- /tools/manifest-editor.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codehasan/dex2c/1866c6824941327cfcea294e717985be419f57c1/tools/manifest-editor.jar --------------------------------------------------------------------------------