├── .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 | [](https://github.com/TeamUltroid/Ultroid/stargazers)
10 | [](https://www.python.org/)
11 | [](https://github.com/codehasan/Dex2C/fork)
12 | [](https://github.com/codehasan/Dex2C/)
13 | [](https://github.com/codehasan/Dex2C/graphs/contributors)
14 | [](./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 | -
28 | About the project
29 |
32 |
33 | -
34 | Installation
35 |
40 |
41 | -
42 | Usage
43 |
47 |
48 | - How to change lib name
49 | - Roadmap
50 | - Contributing
51 | - License
52 | - Acknowledgments
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 | 
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 | 
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 | 
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