├── .github └── workflows │ └── main.yml ├── .gitignore ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── README_cn.md ├── examples ├── debug_utils.py ├── example.py ├── example_binaries │ └── 32 │ │ ├── libc.so │ │ ├── libcms.so │ │ ├── libdl.so │ │ ├── libm.so │ │ ├── libnative-lib.so │ │ ├── libnative-lib_jni.so │ │ └── libstdc++.so ├── example_douyin.py ├── example_jiagu.py ├── example_jni.py ├── misc │ └── app_process32 └── vfs │ ├── proc │ ├── self │ │ └── maps │ └── sys │ │ └── vm │ │ └── overcommit_memory │ └── sys │ └── devices │ └── system │ └── cpu │ ├── online │ └── online.meta_emu ├── pyproject.toml ├── setup.cfg ├── src └── androidemu │ ├── __init__.py │ ├── config.py │ ├── const │ ├── __init__.py │ ├── android.py │ └── linux.py │ ├── cpu │ ├── __init__.py │ ├── interrupt_handler.py │ ├── syscall_handler.py │ ├── syscall_handlers.py │ ├── syscall_hooks.py │ └── syscall_hooks_memory.py │ ├── data │ ├── __init__.py │ ├── fork_info.py │ └── socket_info.py │ ├── emulator.py │ ├── emulator_error.py │ ├── hooker.py │ ├── internal │ ├── __init__.py │ ├── arm.py │ ├── module.py │ ├── modules.py │ └── symbol_resolved.py │ ├── java │ ├── __init__.py │ ├── classes │ │ ├── __init__.py │ │ ├── constructor.py │ │ ├── executable.py │ │ └── method.py │ ├── constant_values.py │ ├── helpers │ │ ├── __init__.py │ │ └── native_method.py │ ├── java_class_def.py │ ├── java_classloader.py │ ├── java_field_def.py │ ├── java_method_def.py │ ├── java_vm.py │ ├── jni_const.py │ ├── jni_env.py │ ├── jni_ref.py │ └── reference_table.py │ ├── libs │ └── libvendorconn_32.so │ ├── memory │ ├── __init__.py │ ├── allocator.py │ ├── allocator_heap.py │ ├── allocator_incremental.py │ ├── memory_access.py │ ├── memory_manager.py │ └── memory_pointer.py │ ├── native │ ├── __init__.py │ └── hooks.py │ ├── tracer.py │ ├── utils │ ├── __init__.py │ └── memory_helpers.py │ └── vfs │ ├── __init__.py │ ├── file_helpers.py │ └── file_system.py ├── tests ├── __init__.py ├── memory │ ├── __init__.py │ └── test_heap_allocator.py ├── test_binaries │ └── 32 │ │ └── test_native.so └── test_native.py └── tools └── gen_jni_env.py /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Python Package 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | test: 9 | runs-on: ${{ matrix.platform }} 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | platform: [ ubuntu-latest, windows-latest, macos-latest ] 14 | python-version: [ '3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13' ] 15 | exclude: 16 | # macos-latest is ARM-based and python 3.7 is not present 17 | - platform: macos-latest 18 | python-version: 3.7 19 | # python 3.7 is not present on ubuntu 24.04 20 | - platform: ubuntu-latest 21 | python-version: 3.7 22 | include: 23 | - platform: ubuntu-22.04 24 | python-version: 3.7 25 | steps: 26 | - uses: actions/checkout@v4 27 | - name: Set up Python 28 | uses: actions/setup-python@v5 29 | with: 30 | python-version: ${{ matrix.python-version }} 31 | - name: Install dependencies 32 | run: | 33 | python -m pip install -U pip setuptools tox tox-gh-actions 34 | - name: Install to system 35 | run: | 36 | python -m pip install . 37 | - name: Run tox for Test 38 | run: | 39 | tox 40 | - name: Run example*.py for test 41 | shell: bash 42 | run: | 43 | cd examples 44 | for file in $(ls example*.py); do 45 | echo "::group::Run $file" 46 | python3 "$file" 47 | echo "::endgroup::" 48 | done 49 | 50 | build: 51 | needs: [ test ] 52 | runs-on: ubuntu-latest 53 | steps: 54 | - uses: actions/checkout@v4 55 | - name: Set up Python 56 | uses: actions/setup-python@v5 57 | with: 58 | python-version: '3.x' 59 | - name: Install dependencies 60 | run: | 61 | python -m pip install -U pip setuptools build 62 | - name: Build wheel and create SDist 63 | run: | 64 | python -m build . 65 | - uses: actions/upload-artifact@v4 66 | with: 67 | path: ${{ github.workspace }}/dist/* 68 | 69 | publish: 70 | needs: [ build ] 71 | runs-on: ubuntu-latest 72 | if: startsWith(github.ref, 'refs/tags') 73 | steps: 74 | - uses: actions/download-artifact@v4 75 | with: 76 | merge-multiple: true 77 | path: dist 78 | 79 | - name: Publish distribution to PyPI 80 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 81 | with: 82 | user: __token__ 83 | password: ${{ secrets.PYPI_API_TOKEN }} 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/venv,macos,python,windows,pycharm+all 2 | # Edit at https://www.gitignore.io/?templates=venv,macos,python,windows,pycharm+all 3 | 4 | ### macOS ### 5 | # General 6 | .DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | 10 | # Icon must end with two \r 11 | Icon 12 | 13 | # Thumbnails 14 | ._* 15 | 16 | # Files that might appear in the root of a volume 17 | .DocumentRevisions-V100 18 | .fseventsd 19 | .Spotlight-V100 20 | .TemporaryItems 21 | .Trashes 22 | .VolumeIcon.icns 23 | .com.apple.timemachine.donotpresent 24 | 25 | # Directories potentially created on remote AFP share 26 | .AppleDB 27 | .AppleDesktop 28 | Network Trash Folder 29 | Temporary Items 30 | .apdisk 31 | 32 | ### PyCharm+all ### 33 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 34 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 35 | 36 | # User-specific stuff 37 | .idea/**/workspace.xml 38 | .idea/**/tasks.xml 39 | .idea/**/usage.statistics.xml 40 | .idea/**/dictionaries 41 | .idea/**/shelf 42 | 43 | # Generated files 44 | .idea/**/contentModel.xml 45 | 46 | # Sensitive or high-churn files 47 | .idea/**/dataSources/ 48 | .idea/**/dataSources.ids 49 | .idea/**/dataSources.local.xml 50 | .idea/**/sqlDataSources.xml 51 | .idea/**/dynamic.xml 52 | .idea/**/uiDesigner.xml 53 | .idea/**/dbnavigator.xml 54 | 55 | # Gradle 56 | .idea/**/gradle.xml 57 | .idea/**/libraries 58 | 59 | # Gradle and Maven with auto-import 60 | # When using Gradle or Maven with auto-import, you should exclude module files, 61 | # since they will be recreated, and may cause churn. Uncomment if using 62 | # auto-import. 63 | # .idea/modules.xml 64 | # .idea/*.iml 65 | # .idea/modules 66 | # *.iml 67 | # *.ipr 68 | 69 | # CMake 70 | cmake-build-*/ 71 | 72 | # Mongo Explorer plugin 73 | .idea/**/mongoSettings.xml 74 | 75 | # File-based project format 76 | *.iws 77 | 78 | # IntelliJ 79 | out/ 80 | 81 | # mpeltonen/sbt-idea plugin 82 | .idea_modules/ 83 | 84 | # JIRA plugin 85 | atlassian-ide-plugin.xml 86 | 87 | # Cursive Clojure plugin 88 | .idea/replstate.xml 89 | 90 | # Crashlytics plugin (for Android Studio and IntelliJ) 91 | com_crashlytics_export_strings.xml 92 | crashlytics.properties 93 | crashlytics-build.properties 94 | fabric.properties 95 | 96 | # Editor-based Rest Client 97 | .idea/httpRequests 98 | 99 | # Android studio 3.1+ serialized cache file 100 | .idea/caches/build_file_checksums.ser 101 | 102 | ### PyCharm+all Patch ### 103 | # Ignores the whole .idea folder and all .iml files 104 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 105 | 106 | .idea/ 107 | 108 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 109 | 110 | *.iml 111 | modules.xml 112 | .idea/misc.xml 113 | *.ipr 114 | 115 | # Sonarlint plugin 116 | .idea/sonarlint 117 | 118 | ### Python ### 119 | # Byte-compiled / optimized / DLL files 120 | __pycache__/ 121 | *.py[cod] 122 | *$py.class 123 | 124 | # C extensions 125 | *.so 126 | 127 | # Distribution / packaging 128 | .Python 129 | build/ 130 | develop-eggs/ 131 | dist/ 132 | downloads/ 133 | eggs/ 134 | .eggs/ 135 | lib/ 136 | lib64/ 137 | parts/ 138 | sdist/ 139 | var/ 140 | wheels/ 141 | pip-wheel-metadata/ 142 | share/python-wheels/ 143 | *.egg-info/ 144 | .installed.cfg 145 | *.egg 146 | MANIFEST 147 | 148 | # PyInstaller 149 | # Usually these files are written by a python script from a template 150 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 151 | *.manifest 152 | *.spec 153 | 154 | # Installer logs 155 | pip-log.txt 156 | pip-delete-this-directory.txt 157 | 158 | # Unit test / coverage reports 159 | htmlcov/ 160 | .tox/ 161 | .nox/ 162 | .coverage 163 | .coverage.* 164 | .cache 165 | nosetests.xml 166 | coverage.xml 167 | *.cover 168 | .hypothesis/ 169 | .pytest_cache/ 170 | 171 | # Translations 172 | *.mo 173 | *.pot 174 | 175 | # Django stuff: 176 | *.log 177 | local_settings.py 178 | db.sqlite3 179 | db.sqlite3-journal 180 | 181 | # Flask stuff: 182 | instance/ 183 | .webassets-cache 184 | 185 | # Scrapy stuff: 186 | .scrapy 187 | 188 | # Sphinx documentation 189 | docs/_build/ 190 | 191 | # PyBuilder 192 | target/ 193 | 194 | # Jupyter Notebook 195 | .ipynb_checkpoints 196 | 197 | # IPython 198 | profile_default/ 199 | ipython_config.py 200 | 201 | # pyenv 202 | .python-version 203 | 204 | # pipenv 205 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 206 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 207 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 208 | # install all needed dependencies. 209 | #Pipfile.lock 210 | 211 | # celery beat schedule file 212 | celerybeat-schedule 213 | 214 | # SageMath parsed files 215 | *.sage.py 216 | 217 | # Environments 218 | .env 219 | .venv 220 | env/ 221 | venv/ 222 | ENV/ 223 | env.bak/ 224 | venv.bak/ 225 | 226 | # Spyder project settings 227 | .spyderproject 228 | .spyproject 229 | 230 | # Rope project settings 231 | .ropeproject 232 | 233 | # mkdocs documentation 234 | /site 235 | 236 | # mypy 237 | .mypy_cache/ 238 | .dmypy.json 239 | dmypy.json 240 | 241 | # Pyre type checker 242 | .pyre/ 243 | 244 | ### venv ### 245 | # Virtualenv 246 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 247 | [Bb]in 248 | [Ii]nclude 249 | [Ll]ib 250 | [Ll]ib64 251 | [Ll]ocal 252 | [Ss]cripts 253 | pyvenv.cfg 254 | pip-selfcheck.json 255 | 256 | ### Windows ### 257 | # Windows thumbnail cache files 258 | Thumbs.db 259 | Thumbs.db:encryptable 260 | ehthumbs.db 261 | ehthumbs_vista.db 262 | 263 | # Dump file 264 | *.stackdump 265 | 266 | # Folder config file 267 | [Dd]esktop.ini 268 | 269 | # Recycle Bin used on file shares 270 | $RECYCLE.BIN/ 271 | 272 | # Windows Installer files 273 | *.cab 274 | *.msi 275 | *.msix 276 | *.msm 277 | *.msp 278 | 279 | # Windows shortcuts 280 | *.lnk 281 | 282 | # End of https://www.gitignore.io/api/venv,macos,python,windows,pycharm+all 283 | 284 | !/examples/**/*.so 285 | !/tests/**/*.so 286 | *.idb -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-exclude tests * 2 | recursive-include src/androidemu *.so 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidNativeEmu 2 | 3 | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/AeonLucid/AndroidNativeEmu/main.yml?style=for-the-badge)](https://github.com/AeonLucid/AndroidNativeEmu/actions) 4 | [![PyPI](https://img.shields.io/pypi/v/androidemu?style=for-the-badge)](https://pypi.org/project/androidemu/) 5 | ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/androidemu?style=for-the-badge) 6 | 7 | Allows you to partly emulate an Android native library. 8 | 9 | This is an educational project to learn more about the ELF file format and [Unicorn](https://github.com/unicorn-engine/unicorn). 10 | 11 | > Read me for chinese readers [中文README](README_cn.md) 12 | 13 | ## Features 14 | 15 | - Emulation of the [JNI Invocation API](https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html) so `JNI_OnLoad` can be called properly. 16 | - Emulation of native memory for malloc / memcpy. 17 | - Emulation of syscalls (SVC #0) instruction. 18 | - Hooking through the symbol table. 19 | - All JavaVM, JNIEnv and hooked functions are handled by python. 20 | - Enable VFP support. 21 | 22 | ## Installation 23 | 24 | You can install AndroidNativeEmu with pip. 25 | 26 | ``` 27 | pip install androidemu 28 | ``` 29 | 30 | ## TODO 31 | 32 | - Improve file descriptors in `vfs/file_system.py` so they are re-useable. 33 | - Add a way for the VirtualFileSystem to give back dynamic files, such as `/proc/self/status`, `/proc/self/status` but also `/dev/urandom`. 34 | - Library consumers must be able to easily rebuild the needed Java classes for a native library, which are used by the native library through the JNIEnv. 35 | - ~~Classes~~ 36 | - ~~Objects~~ 37 | - ~~Methods~~ 38 | - ~~Native methods~~ 39 | - Fields 40 | - Types 41 | - Reflection 42 | 43 | ## Dependencies 44 | 45 | - [Unicorn CPU emulator framework](https://github.com/unicorn-engine/unicorn) 46 | - [Keystone assembler framework](https://github.com/keystone-engine/keystone) 47 | 48 | ## Resources 49 | 50 | All resources used while developing AndroidNativeEmu. 51 | 52 | ### Text sources 53 | - https://greek0.net/elf.html 54 | - https://stackoverflow.com/questions/13908276/loading-elf-file-in-c-in-user-space 55 | - https://programtalk.com/python-examples/pyelftools.elftools.elf.relocation.Relocation/ 56 | - http://infocenter.arm.com/help/topic/com.arm.doc.ihi0044f/IHI0044F_aaelf.pdf 57 | - https://wiki.osdev.org/ELF_Tutorial 58 | - https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html 59 | - https://android.googlesource.com/platform/dalvik/+/donut-release/vm/Jni.c 60 | 61 | ### Code sources 62 | - https://github.com/lunixbochs/usercorn 63 | - https://github.com/slick1015/pad_unpacker (SVC 0 instruction) 64 | -------------------------------------------------------------------------------- /README_cn.md: -------------------------------------------------------------------------------- 1 | # AndroidNativeEmu 2 | AndroidNativeEmu 让你能够跨平台模拟Android Native库函数,比如JNI_OnLoad、Java_XXX_XX等函数。 3 | fork from : https://github.com/AeonLucid/AndroidNativeEmu 4 | 5 | ## 特性 6 | - 模拟 [JNI Invocation API](https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html) so `JNI_OnLoad` can be called properly. 7 | - 模拟 memory、malloc、memcpy 8 | - 支持拦截系统调用(SVC #0) 9 | - 通过符号Hook 10 | - 所有 JavaVM, JNIEnv 和 hooked functions 都可以用python来处理 11 | - 支持 VFP 12 | - 支持文件系统(也就是说你可以模拟maps、status等文件) 13 | 14 | ## 本人瞎改 15 | 小弟不才,修改了一些代码,使其能够运行libcms的leviathan. 16 | - 添加 自动调用InitArray初始化代码,基于重定位表解析。 17 | - 添加 修改对象引用的值 18 | - 实现 getcpu() 系统调用 19 | - 实现 setByteArrayRegion 20 | - JNI中动态注册Native函数失败将不再报错(libcms中注册了大量不需要的函数) 21 | 22 | ## 使用方法 23 | 运行环境:python 3.7 必须! 24 | 1. Clone the repository 25 | 2. Run `pip install -r requirements.txt` 26 | 3. Run `python example.py` 27 | 28 | Windows上可以跑,自行尝试。 29 | 30 | 31 | ## 依赖库 32 | - [Unicorn CPU emulator framework](https://github.com/unicorn-engine/unicorn) 33 | - [Keystone assembler framework](https://github.com/keystone-engine/keystone) 34 | 35 | 36 | ## 初始化模拟器 37 | ```python 38 | # Initialize emulator 39 | emulator = Emulator( 40 | vfp_inst_set=True, 41 | vfs_root=posixpath.join(posixpath.dirname(__file__), "vfs") 42 | ) 43 | ``` 44 | 45 | ## 如何定义Java类呢? 46 | 47 | ### Jni 中会调用到的类 48 | 注意看看各项参数的定义 49 | ```python 50 | class java_lang_System(metaclass=JavaClassDef, jvm_name='java/lang/System'): 51 | def __init__(self): 52 | pass 53 | 54 | @java_method_def(name='getProperty', args_list=["jstring"] ,signature='(Ljava/lang/String;)Ljava/lang/String;', native=False) 55 | def getProperty(self, *args, **kwargs): 56 | print (args[0].value) 57 | return "2.1.0" 58 | ``` 59 | ### 我们的目标类 60 | ```python 61 | class XGorgen(metaclass=JavaClassDef, jvm_name='com/ss/sys/ces/a'): 62 | def __init__(self): 63 | pass 64 | 65 | @java_method_def(name='leviathan', signature='(I[B)[B', native=True) 66 | def leviathan(self, uc): 67 | pass 68 | 69 | def test(self): 70 | pass 71 | ``` 72 | 73 | ### 模拟stacktrace的类 74 | ```python 75 | class java_lang_Thread(metaclass=JavaClassDef, jvm_name='java/lang/Thread'): 76 | def __init__(self): 77 | pass 78 | 79 | @java_method_def(name="currentThread", signature='()Ljava/lang/Thread;', native=False) 80 | def currentThread(self, *args, **kwargs): 81 | return java_lang_Thread() 82 | 83 | @java_method_def(name="getStackTrace", signature='()[Ljava/lang/StackTraceElement;', native=False) 84 | def getStackTrace(self, *args, **kwargs): 85 | return [java_lang_StackTraceElement("dalvik.system.VMStack"), 86 | java_lang_StackTraceElement("java.lang.Thread"), 87 | java_lang_StackTraceElement("com.ss.sys.ces.a"), 88 | java_lang_StackTraceElement("com.yf.douyintool.MainActivity"), 89 | java_lang_StackTraceElement("java.lang.reflect.Method"), 90 | java_lang_StackTraceElement("java.lang.reflect.Method"), 91 | java_lang_StackTraceElement("android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener"), 92 | java_lang_StackTraceElement("android.view.View"), 93 | java_lang_StackTraceElement("android.os.Handler"), 94 | java_lang_StackTraceElement("android.os.Handler"), 95 | java_lang_StackTraceElement("android.os.Looper"), 96 | java_lang_StackTraceElement("android.app.ActivityThread"), 97 | java_lang_StackTraceElement("java.lang.reflect.Method"), 98 | java_lang_StackTraceElement("java.lang.reflect.Method"), 99 | java_lang_StackTraceElement("com.android.internal.os.ZygoteInit$MethodAndArgsCaller"), 100 | java_lang_StackTraceElement("com.android.internal.os.ZygoteInit"), 101 | java_lang_StackTraceElement("dalvik.system.NativeStart") 102 | ] 103 | ``` 104 | 更多的类请见example 105 | 106 | ## 注册类 107 | ```python 108 | emulator.java_classloader.add_class(XGorgen) 109 | emulator.java_classloader.add_class(java_lang_System) 110 | emulator.java_classloader.add_class(java_lang_Thread) 111 | emulator.java_classloader.add_class(java_lang_StackTraceElement) 112 | ``` 113 | 114 | ## 调用JNI_OnLoad 115 | init array 已经自动调用了,SO如果有加密也没关系。 116 | ```python 117 | # 添加依赖库 118 | emulator.load_library("samples/example_binaries/32/libdl.so") 119 | emulator.load_library("samples/example_binaries/32/libc.so") 120 | emulator.load_library("samples/example_binaries/32/libstdc++.so") 121 | emulator.load_library("samples/example_binaries/32/libm.so") 122 | 123 | lib_module = emulator.load_library("samples/example_binaries/32/libcms.so") 124 | 125 | # JNI_OnLoad will call 'RegisterNatives'. 126 | emulator.call_symbol(lib_module, 'JNI_OnLoad', emulator.java_vm.address_ptr, 0x00) 127 | 128 | ``` 129 | 130 | ## 调用native 方法 131 | ```python 132 | x = XGorgen() 133 | data = 'acde74a94e6b493a3399fac83c7c08b35D58B21D9582AF77647FC9902E36AE70f9c001e9334e6e94916682224fbe4e5f00000000000000000000000000000000' 134 | data = bytearray(bytes.fromhex(data)) 135 | result = x.leviathan(emulator, 1562848170, data) 136 | ``` -------------------------------------------------------------------------------- /examples/debug_utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from unicorn.arm_const import * 4 | 5 | logger = logging.getLogger(__name__) 6 | 7 | 8 | def hook_code(uc, address, size, user_data): 9 | instruction = uc.mem_read(address, size) 10 | instruction_str = ''.join('{:02x} '.format(x) for x in instruction) 11 | 12 | logger.debug('# Tracing instruction at 0x%x, instruction size = 0x%x, instruction = %s' % (address, size, instruction_str)) 13 | 14 | if instruction == b"\x00\x00\x00\x00": 15 | logger.error("Uh oh, we messed up.") 16 | uc.emu_stop() 17 | 18 | 19 | def hook_block(uc, address, size, user_data): 20 | instruction = uc.mem_read(address, size) 21 | instruction_str = ''.join('{:02x} '.format(x) for x in instruction) 22 | 23 | logger.debug('# Block at 0x%x, instruction size = 0x%x, instruction = %s' % (address, size, instruction_str)) 24 | 25 | 26 | def hook_unmapped(uc, access, address, length, value, context): 27 | pc = uc.reg_read(UC_ARM_REG_PC) 28 | 29 | logger.debug("mem unmapped: pc: %x access: %x address: %x length: %x value: %x" % (pc, access, address, length, value)) 30 | uc.emu_stop() 31 | return True 32 | 33 | 34 | def hook_mem_write(uc, access, address, size, value, user_data): 35 | pc = uc.reg_read(UC_ARM_REG_PC) 36 | logger.debug(">>> Memory WRITE at 0x%x, data size = %u, data value = 0x%x, pc: %x" % (address, size, value, pc)) 37 | 38 | 39 | def hook_mem_read(uc, access, address, size, value, user_data): 40 | pc = uc.reg_read(UC_ARM_REG_PC) 41 | data = uc.mem_read(address, size) 42 | logger.debug(">>> Memory READ at 0x%x, data size = %u, pc: %x, data value = 0x%s" % (address, size, pc, data.hex())) 43 | 44 | 45 | def hook_interrupt(uc, intno, data): 46 | logger.debug(">>> Triggering interrupt %d" % intno) 47 | return 48 | -------------------------------------------------------------------------------- /examples/example.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | 4 | from unicorn import UC_HOOK_CODE 5 | from unicorn.arm_const import * 6 | 7 | from androidemu.emulator import Emulator 8 | 9 | # Configure logging 10 | logging.basicConfig( 11 | stream=sys.stdout, 12 | level=logging.DEBUG, 13 | format="%(asctime)s %(levelname)7s %(name)34s | %(message)s" 14 | ) 15 | 16 | logger = logging.getLogger(__name__) 17 | 18 | # Initialize emulator 19 | emulator = Emulator(vfp_inst_set=True) 20 | emulator.load_library("example_binaries/32/libc.so", do_init=False) 21 | lib_module = emulator.load_library("example_binaries/32/libnative-lib.so", do_init=False) 22 | 23 | # Show loaded modules. 24 | logger.info("Loaded modules:") 25 | 26 | for module in emulator.modules: 27 | logger.info("[0x%x] %s" % (module.base, module.filename)) 28 | 29 | 30 | # Add debugging. 31 | def hook_code(uc, address, size, user_data): 32 | instruction = uc.mem_read(address, size) 33 | instruction_str = ''.join('{:02x} '.format(x) for x in instruction) 34 | 35 | print('# Tracing instruction at 0x%x, instruction size = 0x%x, instruction = %s' % (address, size, instruction_str)) 36 | 37 | 38 | emulator.uc.hook_add(UC_HOOK_CODE, hook_code) 39 | 40 | # Runs a method of "libnative-lib.so" that calls an imported function "strlen" from "libc.so". 41 | emulator.call_symbol(lib_module, '_Z4testv') 42 | 43 | print("String length is: %i" % emulator.uc.reg_read(UC_ARM_REG_R0)) 44 | -------------------------------------------------------------------------------- /examples/example_binaries/32/libc.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/examples/example_binaries/32/libc.so -------------------------------------------------------------------------------- /examples/example_binaries/32/libcms.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/examples/example_binaries/32/libcms.so -------------------------------------------------------------------------------- /examples/example_binaries/32/libdl.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/examples/example_binaries/32/libdl.so -------------------------------------------------------------------------------- /examples/example_binaries/32/libm.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/examples/example_binaries/32/libm.so -------------------------------------------------------------------------------- /examples/example_binaries/32/libnative-lib.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/examples/example_binaries/32/libnative-lib.so -------------------------------------------------------------------------------- /examples/example_binaries/32/libnative-lib_jni.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/examples/example_binaries/32/libnative-lib_jni.so -------------------------------------------------------------------------------- /examples/example_binaries/32/libstdc++.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/examples/example_binaries/32/libstdc++.so -------------------------------------------------------------------------------- /examples/example_douyin.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import posixpath 3 | import sys 4 | 5 | from unicorn import UcError, UC_HOOK_CODE, UC_HOOK_MEM_UNMAPPED, Uc, UC_PROT_ALL 6 | from unicorn.arm_const import * 7 | 8 | from androidemu.emulator import Emulator 9 | from androidemu.java.helpers.native_method import native_method 10 | from androidemu.java.java_class_def import JavaClassDef 11 | from androidemu.java.java_method_def import java_method_def 12 | 13 | import debug_utils 14 | 15 | 16 | class XGorgen(metaclass=JavaClassDef, jvm_name='com/ss/sys/ces/a'): 17 | def __init__(self): 18 | pass 19 | 20 | @java_method_def(name='leviathan', signature='(I[B)[B', native=True) 21 | def leviathan(self, uc): 22 | pass 23 | 24 | def test(self): 25 | pass 26 | 27 | 28 | class secuni_b(metaclass=JavaClassDef, jvm_name='com/ss/sys/secuni/b/c'): 29 | def __init__(self): 30 | pass 31 | 32 | @java_method_def(name='n0', signature='(Landroid/content/Context;)[B', native=True) 33 | def n0(self, uc): 34 | pass 35 | 36 | @java_method_def(name='n1', signature='(Landroid/content/Context;Ljava/lang/String;)I', native=True) 37 | def n1(self, uc): 38 | pass 39 | 40 | 41 | class UserInfo(metaclass=JavaClassDef, jvm_name='com/ss/android/common/applog/UserInfo'): 42 | def __init__(self): 43 | pass 44 | 45 | 46 | class java_lang_System(metaclass=JavaClassDef, jvm_name='java/lang/System'): 47 | def __init__(self): 48 | pass 49 | 50 | @java_method_def(name='getProperty', args_list=["jstring"], signature='(Ljava/lang/String;)Ljava/lang/String;', 51 | native=False) 52 | def getProperty(self, *args, **kwargs): 53 | print(args[0].value) 54 | return "2.1.0" 55 | 56 | 57 | class java_lang_StackTraceElement(metaclass=JavaClassDef, jvm_name='java/lang/StackTraceElement'): 58 | def __init__(self, _name): 59 | self.name = _name 60 | 61 | @java_method_def(native=False, name='getClassName', signature="()Ljava/lang/String;") 62 | def getClassName(self, *args, **kwargs): 63 | return self.name 64 | 65 | 66 | class java_lang_Thread(metaclass=JavaClassDef, jvm_name='java/lang/Thread'): 67 | def __init__(self): 68 | pass 69 | 70 | @java_method_def(name="currentThread", signature='()Ljava/lang/Thread;', native=False) 71 | def currentThread(self, *args, **kwargs): 72 | return java_lang_Thread() 73 | 74 | @java_method_def(name="getStackTrace", signature='()[Ljava/lang/StackTraceElement;', native=False) 75 | def getStackTrace(self, *args, **kwargs): 76 | return [java_lang_StackTraceElement("dalvik.system.VMStack"), 77 | java_lang_StackTraceElement("java.lang.Thread"), 78 | java_lang_StackTraceElement("com.ss.sys.ces.a"), 79 | java_lang_StackTraceElement("com.yf.douyintool.MainActivity"), 80 | java_lang_StackTraceElement("java.lang.reflect.Method"), 81 | java_lang_StackTraceElement("java.lang.reflect.Method"), 82 | java_lang_StackTraceElement("android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener"), 83 | java_lang_StackTraceElement("android.view.View"), 84 | java_lang_StackTraceElement("android.os.Handler"), 85 | java_lang_StackTraceElement("android.os.Handler"), 86 | java_lang_StackTraceElement("android.os.Looper"), 87 | java_lang_StackTraceElement("android.app.ActivityThread"), 88 | java_lang_StackTraceElement("java.lang.reflect.Method"), 89 | java_lang_StackTraceElement("java.lang.reflect.Method"), 90 | java_lang_StackTraceElement("com.android.internal.os.ZygoteInit$MethodAndArgsCaller"), 91 | java_lang_StackTraceElement("com.android.internal.os.ZygoteInit"), 92 | java_lang_StackTraceElement("dalvik.system.NativeStart") 93 | ] 94 | 95 | 96 | # Configure logging 97 | logging.basicConfig( 98 | stream=sys.stdout, 99 | level=logging.DEBUG, 100 | format="%(asctime)s %(levelname)7s %(name)34s | %(message)s" 101 | ) 102 | 103 | logger = logging.getLogger(__name__) 104 | 105 | # Initialize emulator 106 | emulator = Emulator( 107 | vfp_inst_set=True, 108 | vfs_root=posixpath.join(posixpath.dirname(__file__), "vfs") 109 | ) 110 | 111 | # Register Java class. 112 | # emulator.java_classloader.add_class(MainActivity) 113 | emulator.java_classloader.add_class(XGorgen) 114 | emulator.java_classloader.add_class(secuni_b) 115 | emulator.java_classloader.add_class(UserInfo) 116 | emulator.java_classloader.add_class(java_lang_System) 117 | emulator.java_classloader.add_class(java_lang_Thread) 118 | emulator.java_classloader.add_class(java_lang_StackTraceElement) 119 | 120 | # Load all libraries. 121 | emulator.load_library("./example_binaries/32/libdl.so") 122 | emulator.load_library("./example_binaries/32/libc.so") 123 | emulator.load_library("./example_binaries/32/libstdc++.so") 124 | emulator.load_library("./example_binaries/32/libm.so") 125 | lib_module = emulator.load_library("./example_binaries/32/libcms.so") 126 | 127 | # Show loaded modules. 128 | logger.info("Loaded modules:") 129 | 130 | for module in emulator.modules: 131 | logger.info("=> 0x%08x - %s" % (module.base, module.filename)) 132 | 133 | # Debug 134 | # emulator.uc.hook_add(UC_HOOK_CODE, debug_utils.hook_code) 135 | emulator.uc.hook_add(UC_HOOK_MEM_UNMAPPED, debug_utils.hook_unmapped) 136 | # emulator.uc.hook_add(UC_HOOK_MEM_WRITE, debug_utils.hook_mem_write) 137 | # emulator.uc.hook_add(UC_HOOK_MEM_READ, debug_utils.hook_mem_read) 138 | 139 | try: 140 | # Run JNI_OnLoad. 141 | # JNI_OnLoad will call 'RegisterNatives'. 142 | emulator.call_symbol(lib_module, 'JNI_OnLoad', emulator.java_vm.address_ptr, 0x00) 143 | 144 | # bypass douyin checks 145 | with open("./misc/app_process32", 'rb') as ap: 146 | data = ap.read() 147 | len1 = len(data) + 1024 - (len(data) % 1024) 148 | emulator.uc.mem_map(0xab006000, len1) 149 | emulator.uc.mem_write(0xab006000, data) 150 | 151 | x = XGorgen() 152 | data = 'acde74a94e6b493a3399fac83c7c08b35D58B21D9582AF77647FC9902E36AE70f9c001e9334e6e94916682224fbe4e5f00000000000000000000000000000000' 153 | data = bytearray(bytes.fromhex(data)) 154 | result = x.leviathan(emulator, 1562848170, data) 155 | 156 | print(''.join(['%02x' % b for b in result])) 157 | # 037d560d0000903e34fb093f1d21e78f3bdf3fbebe00b124becc 158 | # 036d2a7b000010f4d05395b7df8b0ec2b5ec085b938a473a6a51 159 | # 036d2a7b000010f4d05395b7df8b0ec2b5ec085b938a473a6a51 160 | 161 | # 0300000000002034d288fe8d6b95b778105cc36eade709d2b500 162 | # 0300000000002034d288fe8d6b95b778105cc36eade709d2b500 163 | # 0300000000002034d288fe8d6b95b778105cc36eade709d2b500 164 | # Dump natives found. 165 | 166 | # for method in MainActivity.jvm_methods.values(): 167 | # if method.native: 168 | # logger.info("- [0x%08x] %s - %s" % (method.native_addr, method.name, method.signature)) 169 | except UcError as e: 170 | print("Exit at %x" % emulator.uc.reg_read(UC_ARM_REG_PC)) 171 | raise 172 | -------------------------------------------------------------------------------- /examples/example_jiagu.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import posixpath 3 | import sys 4 | 5 | from unicorn import UcError, UC_HOOK_CODE, UC_HOOK_MEM_UNMAPPED 6 | from unicorn.arm_const import * 7 | 8 | from androidemu.emulator import Emulator 9 | from androidemu.java.java_class_def import JavaClassDef 10 | from androidemu.java.java_method_def import java_method_def 11 | 12 | import debug_utils 13 | 14 | 15 | # Create java class. 16 | class MainActivity(metaclass=JavaClassDef, jvm_name='local/myapp/testnativeapp/MainActivity'): 17 | 18 | def __init__(self): 19 | pass 20 | 21 | @java_method_def(name='stringFromJNI', signature='()Ljava/lang/String;', native=True) 22 | def string_from_jni(self, uc): 23 | pass 24 | 25 | def test(self): 26 | pass 27 | 28 | 29 | # Configure logging 30 | logging.basicConfig( 31 | stream=sys.stdout, 32 | level=logging.DEBUG, 33 | format="%(asctime)s %(levelname)7s %(name)34s | %(message)s" 34 | ) 35 | 36 | logger = logging.getLogger(__name__) 37 | 38 | # Initialize emulator 39 | emulator = Emulator( 40 | vfp_inst_set=True, 41 | vfs_root=posixpath.join(posixpath.dirname(__file__), "vfs") 42 | ) 43 | 44 | # Register Java class. 45 | emulator.java_classloader.add_class(MainActivity) 46 | 47 | # Load all libraries. 48 | emulator.load_library("example_binaries/32/libdl.so") 49 | emulator.load_library("example_binaries/32/libc.so") 50 | emulator.load_library("example_binaries/32/libstdc++.so") 51 | emulator.load_library("example_binaries/32/libm.so") 52 | lib_module = emulator.load_library("example_binaries/32/libnative-lib_jni.so") 53 | 54 | # Show loaded modules. 55 | logger.info("Loaded modules:") 56 | 57 | for module in emulator.modules: 58 | logger.info("=> 0x%08x - %s" % (module.base, module.filename)) 59 | 60 | # Debug 61 | # emulator.uc.hook_add(UC_HOOK_CODE, debug_utils.hook_code) 62 | # emulator.uc.hook_add(UC_HOOK_MEM_UNMAPPED, debug_utils.hook_unmapped) 63 | # emulator.uc.hook_add(UC_HOOK_MEM_WRITE, debug_utils.hook_mem_write) 64 | # emulator.uc.hook_add(UC_HOOK_MEM_READ, debug_utils.hook_mem_read) 65 | 66 | try: 67 | # Run JNI_OnLoad. 68 | # JNI_OnLoad will call 'RegisterNatives'. 69 | emulator.call_symbol(lib_module, 'JNI_OnLoad', emulator.java_vm.address_ptr, 0x00) 70 | emulator.uc.hook_add(UC_HOOK_MEM_UNMAPPED, debug_utils.hook_unmapped) 71 | 72 | # Do native stuff. 73 | emulator.uc.hook_add(UC_HOOK_CODE, debug_utils.hook_code) 74 | main_activity = MainActivity() 75 | logger.info("Response from JNI call: %s" % main_activity.string_from_jni(emulator)) 76 | 77 | # Dump natives found. 78 | logger.info("Exited EMU.") 79 | logger.info("Native methods registered to MainActivity:") 80 | 81 | for method in MainActivity.jvm_methods.values(): 82 | if method.native: 83 | logger.info("- [0x%08x] %s - %s" % (method.native_addr, method.name, method.signature)) 84 | except UcError as e: 85 | print("Exit at %x" % emulator.uc.reg_read(UC_ARM_REG_PC)) 86 | raise 87 | -------------------------------------------------------------------------------- /examples/example_jni.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import posixpath 3 | import sys 4 | 5 | from unicorn import UcError, UC_HOOK_MEM_UNMAPPED, UC_HOOK_CODE 6 | from unicorn.arm_const import * 7 | 8 | from androidemu.emulator import Emulator 9 | from androidemu.java.java_class_def import JavaClassDef 10 | from androidemu.java.java_method_def import java_method_def 11 | 12 | import debug_utils 13 | 14 | 15 | # Create java class. 16 | class MainActivity(metaclass=JavaClassDef, jvm_name='local/myapp/testnativeapp/MainActivity'): 17 | 18 | def __init__(self): 19 | pass 20 | 21 | @java_method_def(name='stringFromJNI', signature='()Ljava/lang/String;', native=True) 22 | def string_from_jni(self, uc): 23 | pass 24 | 25 | def test(self): 26 | pass 27 | 28 | 29 | # Configure logging 30 | logging.basicConfig( 31 | stream=sys.stdout, 32 | level=logging.DEBUG, 33 | format="%(asctime)s %(levelname)7s %(name)34s | %(message)s" 34 | ) 35 | 36 | logger = logging.getLogger(__name__) 37 | 38 | # Initialize emulator 39 | emulator = Emulator( 40 | vfp_inst_set=True, 41 | vfs_root=posixpath.join(posixpath.dirname(__file__), "vfs") 42 | ) 43 | 44 | # emulator.uc.hook_add(UC_HOOK_CODE, debug_utils.hook_code) 45 | # emulator.uc.hook_add(UC_HOOK_MEM_UNMAPPED, debug_utils.hook_unmapped) 46 | 47 | # Register Java class. 48 | emulator.java_classloader.add_class(MainActivity) 49 | 50 | # Load all libraries. 51 | emulator.load_library("example_binaries/32/libdl.so") 52 | emulator.load_library("example_binaries/32/libc.so") 53 | emulator.load_library("example_binaries/32/libstdc++.so") 54 | emulator.load_library("example_binaries/32/libm.so") 55 | lib_module = emulator.load_library("example_binaries/32/libnative-lib_jni.so") 56 | 57 | # Show loaded modules. 58 | logger.info("Loaded modules:") 59 | 60 | for module in emulator.modules: 61 | logger.info("=> 0x%08x - %s" % (module.base, module.filename)) 62 | 63 | # Debug 64 | # emulator.uc.hook_add(UC_HOOK_CODE, debug_utils.hook_code) 65 | # emulator.uc.hook_add(UC_HOOK_MEM_UNMAPPED, debug_utils.hook_unmapped) 66 | # emulator.uc.hook_add(UC_HOOK_MEM_WRITE, debug_utils.hook_mem_write) 67 | # emulator.uc.hook_add(UC_HOOK_MEM_READ, debug_utils.hook_mem_read) 68 | 69 | try: 70 | # Run JNI_OnLoad. 71 | # JNI_OnLoad will call 'RegisterNatives'. 72 | emulator.call_symbol(lib_module, 'JNI_OnLoad', emulator.java_vm.address_ptr, 0x00) 73 | 74 | # Do native stuff. 75 | main_activity = MainActivity() 76 | logger.info("Response from JNI call: %s" % main_activity.string_from_jni(emulator)) 77 | 78 | # Dump natives found. 79 | logger.info("Exited EMU.") 80 | logger.info("Native methods registered to MainActivity:") 81 | 82 | for method in MainActivity.jvm_methods.values(): 83 | if method.native: 84 | logger.info("- [0x%08x] %s - %s" % (method.native_addr, method.name, method.signature)) 85 | except UcError as e: 86 | print("Exit at %x" % emulator.uc.reg_read(UC_ARM_REG_PC)) 87 | raise 88 | -------------------------------------------------------------------------------- /examples/misc/app_process32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/examples/misc/app_process32 -------------------------------------------------------------------------------- /examples/vfs/proc/sys/vm/overcommit_memory: -------------------------------------------------------------------------------- 1 | 0 -------------------------------------------------------------------------------- /examples/vfs/sys/devices/system/cpu/online: -------------------------------------------------------------------------------- 1 | 0-3 -------------------------------------------------------------------------------- /examples/vfs/sys/devices/system/cpu/online.meta_emu: -------------------------------------------------------------------------------- 1 | { 2 | "st_dev": 0, 3 | "__st_ino": 0, 4 | "st_mode": 0, 5 | "st_nlink": 0, 6 | "st_uid": 0, 7 | "st_gid": 0, 8 | "st_rdev": 0, 9 | "st_size": 0, 10 | "st_blksize": 0, 11 | "st_blocks": 0, 12 | "st_atime": 0, 13 | "st_atime_ns": 0, 14 | "st_mtime": 0, 15 | "st_mtime_ns": 0, 16 | "st_ctime": 0, 17 | "st_ctime_ns": 0, 18 | "st_ino": 0 19 | } -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "build"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "androidemu" 7 | version = "0.0.4" 8 | requires-python = ">=3.7" 9 | authors = [ 10 | { name = "AeonLucid", email = "aeonlucid@gmail.com" }, 11 | ] 12 | description = "Allows you to partly emulate an Android native library." 13 | readme = "README.md" 14 | license = { text = "GPLv3" } 15 | keywords = ["emulation", "android"] 16 | classifiers = [ 17 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 18 | "Topic :: System :: Emulators", 19 | "Intended Audience :: Developers", 20 | 'Programming Language :: Python :: 3.7', 21 | 'Programming Language :: Python :: 3.8', 22 | 'Programming Language :: Python :: 3.9', 23 | 'Programming Language :: Python :: 3.10', 24 | 'Programming Language :: Python :: 3.11', 25 | 'Programming Language :: Python :: 3.12', 26 | 'Programming Language :: Python :: 3.13', 27 | ] 28 | dependencies = [ 29 | "unicorn==2.1.2", 30 | "pyelftools==0.31", 31 | "hexdump==3.3", 32 | "keystone-engine==0.9.2" 33 | ] 34 | 35 | [project.urls] 36 | Repository = "https://github.com/AeonLucid/AndroidNativeEmu" 37 | "Bug Tracker" = "https://github.com/AeonLucid/AndroidNativeEmu/issues" 38 | 39 | [project.optional-dependencies] 40 | test = [ 41 | "pytest", 42 | "pytest-cov", 43 | ] 44 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [tox:tox] 2 | envlist = py{37,38,39,310,311,312,313}-{linux,macos,windows} 3 | 4 | [gh-actions] 5 | python = 6 | 3.7: py37 7 | 3.8: py38 8 | 3.9: py39 9 | 3.10: py310 10 | 3.11: py311 11 | 3.12: py312 12 | 3.13: py313 13 | 14 | [testenv] 15 | deps = pytest 16 | commands = 17 | pytest 18 | -------------------------------------------------------------------------------- /src/androidemu/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/src/androidemu/__init__.py -------------------------------------------------------------------------------- /src/androidemu/config.py: -------------------------------------------------------------------------------- 1 | WRITE_FSTAT_TIMES = True 2 | -------------------------------------------------------------------------------- /src/androidemu/const/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/src/androidemu/const/__init__.py -------------------------------------------------------------------------------- /src/androidemu/const/android.py: -------------------------------------------------------------------------------- 1 | PR_SET_NAME = 15 2 | PR_SET_VMA = 0x53564d41 3 | -------------------------------------------------------------------------------- /src/androidemu/const/linux.py: -------------------------------------------------------------------------------- 1 | CLOCK_REALTIME = 0 2 | CLOCK_MONOTONIC = 1 3 | CLOCK_PROCESS_CPUTIME_ID = 2 4 | CLOCK_THREAD_CPUTIME_ID = 3 5 | CLOCK_MONOTONIC_RAW = 4 6 | CLOCK_REALTIME_COARSE = 5 7 | CLOCK_MONOTONIC_COARSE = 6 8 | CLOCK_BOOTTIME = 7 9 | CLOCK_REALTIME_ALARM = 8 10 | CLOCK_BOOTTIME_ALARM = 9 11 | 12 | FUTEX_WAIT = 0 13 | FUTEX_WAKE = 1 14 | FUTEX_FD = 2 15 | FUTEX_REQUEUE = 3 16 | FUTEX_CMP_REQUEUE = 4 17 | FUTEX_WAKE_OP = 5 18 | FUTEX_LOCK_PI = 6 19 | FUTEX_UNLOCK_PI = 7 20 | FUTEX_TRYLOCK_PI = 8 21 | FUTEX_WAIT_BITSET = 9 22 | FUTEX_WAKE_BITSET = 10 23 | FUTEX_WAIT_REQUEUE_PI = 11 24 | FUTEX_CMP_REQUEUE_PI = 12 25 | 26 | FUTEX_PRIVATE_FLAG = 128 27 | FUTEX_CLOCK_REALTIME = 256 28 | -------------------------------------------------------------------------------- /src/androidemu/cpu/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/src/androidemu/cpu/__init__.py -------------------------------------------------------------------------------- /src/androidemu/cpu/interrupt_handler.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from unicorn import * 4 | from unicorn.arm_const import * 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | class InterruptHandler: 10 | 11 | """ 12 | :type uc Uc 13 | """ 14 | def __init__(self, uc): 15 | self._uc = uc 16 | self._uc.hook_add(UC_HOOK_INTR, self._hook_interrupt) 17 | self._handlers = dict() 18 | 19 | def _hook_interrupt(self, uc, intno, data): 20 | if intno in self._handlers: 21 | self._handlers[intno](uc) 22 | else: 23 | logger.error("Unhandled interrupt %d at %x, stopping emulation" % (intno, self._uc.reg_read(UC_ARM_REG_PC))) 24 | self._uc.emu_stop() 25 | 26 | def set_handler(self, intno, handler): 27 | self._handlers[intno] = handler 28 | -------------------------------------------------------------------------------- /src/androidemu/cpu/syscall_handler.py: -------------------------------------------------------------------------------- 1 | class SyscallHandler: 2 | 3 | def __init__(self, idx, name, arg_count, callback): 4 | self.idx = idx 5 | self.name = name 6 | self.arg_count = arg_count 7 | self.callback = callback 8 | -------------------------------------------------------------------------------- /src/androidemu/cpu/syscall_handlers.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from unicorn import * 4 | from unicorn.arm_const import * 5 | 6 | from androidemu.cpu.interrupt_handler import InterruptHandler 7 | from androidemu.cpu.syscall_handler import SyscallHandler 8 | from androidemu.utils import memory_helpers 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | class SyscallHandlers: 14 | 15 | """ 16 | :type interrupt_handler InterruptHandler 17 | """ 18 | def __init__(self, interrupt_handler): 19 | self._handlers = dict() 20 | interrupt_handler.set_handler(2, self._handle_syscall) 21 | 22 | def set_handler(self, idx, name, arg_count, callback): 23 | self._handlers[idx] = SyscallHandler(idx, name, arg_count, callback) 24 | 25 | def _handle_syscall(self, uc): 26 | idx = uc.reg_read(UC_ARM_REG_R7) 27 | args = [uc.reg_read(reg_idx) for reg_idx in range(UC_ARM_REG_R0, UC_ARM_REG_R6 + 1)] 28 | 29 | if idx in self._handlers: 30 | handler = self._handlers[idx] 31 | args = args[:handler.arg_count] 32 | args_formatted = ", ".join(["%08x" % arg for arg in args]) 33 | logger.debug("Executing syscall %s(%s) at 0x%x" % (handler.name, args_formatted, 34 | uc.reg_read(UC_ARM_REG_PC))) 35 | 36 | try: 37 | result = handler.callback(uc, *args) 38 | except: 39 | logger.error("An error occured during in %x syscall hander, stopping emulation" % idx) 40 | uc.emu_stop() 41 | raise 42 | 43 | if result is not None: 44 | uc.reg_write(UC_ARM_REG_R0, result) 45 | else: 46 | 47 | error = "Unhandled syscall 0x%x (%u) at 0x%x, stopping emulation" % (idx, idx, 48 | uc.reg_read(UC_ARM_REG_PC)) 49 | uc.emu_stop() 50 | raise RuntimeError(error) 51 | -------------------------------------------------------------------------------- /src/androidemu/cpu/syscall_hooks.py: -------------------------------------------------------------------------------- 1 | import calendar 2 | import logging 3 | import math 4 | import os 5 | import time 6 | from random import randint 7 | 8 | import hexdump 9 | from unicorn import Uc 10 | 11 | from androidemu.const.android import * 12 | from androidemu.const.linux import * 13 | from androidemu.cpu.syscall_handlers import SyscallHandlers 14 | from androidemu.data import socket_info 15 | from androidemu.data.fork_info import ForkInfo 16 | from androidemu.data.socket_info import SocketInfo 17 | from androidemu.internal.modules import Modules 18 | from androidemu.utils import memory_helpers 19 | 20 | OVERRIDE_TIMEOFDAY = False 21 | OVERRIDE_TIMEOFDAY_SEC = 0 22 | OVERRIDE_TIMEOFDAY_USEC = 0 23 | 24 | OVERRIDE_CLOCK = False 25 | OVERRIDE_CLOCK_TIME = 0 26 | 27 | logger = logging.getLogger(__name__) 28 | 29 | 30 | class SyscallHooks: 31 | 32 | """ 33 | :type uc Uc 34 | :type syscall_handler SyscallHandlers 35 | """ 36 | def __init__(self, uc, syscall_handler, modules: Modules): 37 | self._uc = uc 38 | self._syscall_handler = syscall_handler 39 | self._syscall_handler.set_handler(0xB, "execve", 3, self._handle_execve) 40 | self._syscall_handler.set_handler(0x43, "sigaction", 3, self._null) 41 | self._syscall_handler.set_handler(0x48, "sigsuspend", 3, self._null) 42 | self._syscall_handler.set_handler(0x14, "getpid", 0, self._getpid) 43 | self._syscall_handler.set_handler(0x4E, "gettimeofday", 2, self._handle_gettimeofday) 44 | self._syscall_handler.set_handler(0x72, "wait4", 4, self._handle_wait4) 45 | self._syscall_handler.set_handler(0xAC, "prctl", 5, self._handle_prctl) 46 | self._syscall_handler.set_handler(0xE0, "gettid", 0, self._gettid) 47 | self._syscall_handler.set_handler(0xa2, "nanosleep", 0, self._null) 48 | self._syscall_handler.set_handler(0xAF, "sigprocmask", 3, self._null) 49 | self._syscall_handler.set_handler(0xBE, "vfork", 0, self._handle_vfork) 50 | self._syscall_handler.set_handler(0xF0, "futex", 6, self._handle_futex) 51 | self._syscall_handler.set_handler(0xF8, "exit_group", 1, self._exit_group) 52 | self._syscall_handler.set_handler(0x107, "clock_gettime", 2, self._handle_clock_gettime) 53 | self._syscall_handler.set_handler(0x119, "socket", 3, self._socket) 54 | self._syscall_handler.set_handler(0x11a, "bind", 3, self._bind) 55 | self._syscall_handler.set_handler(0x11b, "connect", 3, self._connect) 56 | self._syscall_handler.set_handler(0x14e, "faccessat", 4, self._faccessat) 57 | self._syscall_handler.set_handler(0x159, "getcpu", 3, self._getcpu) 58 | self._syscall_handler.set_handler(0x14e, "faccessat", 4, self._faccessat) 59 | self._syscall_handler.set_handler(0x14, "getpid", 0, self._getpid) 60 | self._syscall_handler.set_handler(0xe0, "gettid", 0, self._gettid) 61 | # self._syscall_handler.set_handler(0x180,"null1",0, self._null) 62 | self._syscall_handler.set_handler(0x10c, "tgkill", 3, self._tgkill) 63 | self._syscall_handler.set_handler(0x180, "getrandom", 3, self._getrandom) 64 | self._syscall_handler.set_handler(0xf0002, "cacheflush", 0, self._null) 65 | self._modules = modules 66 | self._clock_start = time.time() 67 | self._clock_offset = randint(1000, 2000) 68 | self._socket_id = 0x100000 69 | self._sockets = dict() 70 | self._fork = None 71 | 72 | def _getpid(self, uc): 73 | return 21458 74 | 75 | def _handle_execve(self, uc, pathname_ptr, argv, envp): 76 | pathname = memory_helpers.read_utf8(uc, pathname_ptr) 77 | args = [] 78 | while True: 79 | arg_ptr = int.from_bytes(uc.mem_read(argv, 4), byteorder='little') 80 | 81 | if arg_ptr == 0: 82 | break 83 | 84 | args.append(memory_helpers.read_utf8(uc, arg_ptr)) 85 | argv = argv + 4 86 | 87 | # Set errno. 88 | errno_ptr = self._modules.find_symbol_name('__errno') 89 | uc.mem_write(errno_ptr, int(13).to_bytes(4, byteorder='little')) 90 | 91 | logger.warning('Exec %s %s' % (pathname, args)) 92 | return 0 93 | 94 | def _null(self, uc, *args): 95 | logger.warning('Skipping syscall, returning 0') 96 | return 0 97 | 98 | def _gettid(self, uc): 99 | return 0x2211 100 | 101 | def _faccessat(self, uc, filename, pathname, mode, flag): 102 | file = memory_helpers.read_utf8(uc, pathname) 103 | return 0 104 | 105 | def _getcpu(self, uc, _cpu, node, cache): 106 | if _cpu != 0: 107 | uc.mem_write(_cpu, int(1).to_bytes(4, byteorder='little')) 108 | return 0 109 | 110 | def _handle_gettimeofday(self, uc, tv, tz): 111 | """ 112 | If either tv or tz is NULL, the corresponding structure is not set or returned. 113 | """ 114 | 115 | if tv != 0: 116 | if OVERRIDE_TIMEOFDAY: 117 | uc.mem_write(tv + 0, int(OVERRIDE_TIMEOFDAY_SEC).to_bytes(4, byteorder='little')) 118 | uc.mem_write(tv + 4, int(OVERRIDE_TIMEOFDAY_USEC).to_bytes(4, byteorder='little')) 119 | else: 120 | timestamp = time.time() 121 | (usec, sec) = math.modf(timestamp) 122 | usec = abs(int(usec * 100000)) 123 | 124 | uc.mem_write(tv + 0, int(sec).to_bytes(4, byteorder='little')) 125 | uc.mem_write(tv + 4, int(usec).to_bytes(4, byteorder='little')) 126 | 127 | if tz != 0: 128 | uc.mem_write(tz + 0, int(-120).to_bytes(4, byteorder='little')) # minuteswest -(+GMT_HOURS) * 60 129 | uc.mem_write(tz + 4, int().to_bytes(4, byteorder='little')) # dsttime 130 | 131 | return 0 132 | 133 | def _handle_wait4(self, uc, upid, stat_addr, options, ru): 134 | """ 135 | on success, returns the process ID of the terminated child; on error, -1 is returned. 136 | """ 137 | return upid 138 | 139 | def _handle_prctl(self, uc, option, arg2, arg3, arg4, arg5): 140 | """ 141 | int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5); 142 | See: 143 | - https://linux.die.net/man/2/prctl 144 | - https://github.com/torvalds/linux/blob/master/include/uapi/linux/prctl.h 145 | 146 | For PR_SET_VMA: 147 | - https://android.googlesource.com/platform/bionic/+/263325d/libc/include/sys/prctl.h 148 | - https://sourceforge.net/p/strace/mailman/message/34329772/ 149 | """ 150 | 151 | if option == PR_SET_NAME: 152 | # arg2 contains ptr to a name. 153 | logger.debug('prctl PR_SET_NAME: %s' % memory_helpers.read_cString(uc, arg2)[0]) 154 | return 0 155 | elif option == PR_SET_VMA: 156 | # arg5 contains ptr to a name. 157 | logger.debug('prctl PR_SET_VMA: %s' % memory_helpers.read_cString(uc, arg5)[0]) 158 | return 0 159 | else: 160 | raise NotImplementedError("Unsupported prctl option %d (0x%x)" % (option, option)) 161 | 162 | def _handle_vfork(self, uc): 163 | """ 164 | Upon successful completion, vfork() shall return 0 to the child process 165 | and return the process ID of the child process to the parent process. 166 | 167 | Otherwise, -1 shall be returned to the parent, no child process shall be created, 168 | and errno shall be set to indicate the error. 169 | """ 170 | if self._fork is not None: 171 | raise NotImplementedError('Already forked.') 172 | 173 | self._fork = ForkInfo(uc, self._getpid(uc) + 1) 174 | 175 | # Current execution becomes the fork, save all registers so we can return to vfork later for the main process. 176 | # See exit_group. 177 | self._fork.save_state() 178 | 179 | return 0 180 | 181 | def _handle_futex(self, uc, uaddr, op, val, timeout, uaddr2, val3): 182 | """ 183 | See: https://linux.die.net/man/2/futex 184 | """ 185 | 186 | if op & FUTEX_WAIT: 187 | raise NotImplementedError() 188 | elif op & FUTEX_WAKE: 189 | wakes_at_most = val 190 | return 0 191 | elif op & FUTEX_FD: 192 | raise NotImplementedError() 193 | elif op & FUTEX_REQUEUE: 194 | raise NotImplementedError() 195 | elif op & FUTEX_CMP_REQUEUE: 196 | raise NotImplementedError() 197 | 198 | return 0 199 | 200 | def _exit_group(self, uc, status): 201 | if self._fork is not None: 202 | pid = self._fork.pid 203 | 204 | self._fork.load_state() 205 | self._fork = None 206 | 207 | # We exit the child process, registers were restored to vfork. 208 | return pid 209 | 210 | raise Exception('Application shutdown all threads, status %u' % status) 211 | 212 | def _handle_clock_gettime(self, uc, clk_id, tp_ptr): 213 | """ 214 | The functions clock_gettime() retrieve the time of the specified clock clk_id. 215 | 216 | The clk_id argument is the identifier of the particular clock on which to act. A clock may be system-wide and 217 | hence visible for all processes, or per-process if it measures time only within a single process. 218 | 219 | clock_gettime(), clock_settime() and clock_getres() return 0 for success, or -1 for failure (in which case 220 | errno is set appropriately). 221 | """ 222 | 223 | if clk_id == CLOCK_REALTIME: 224 | # Its time represents seconds and nanoseconds since the Epoch. 225 | clock_real = calendar.timegm(time.gmtime()) 226 | 227 | uc.mem_write(tp_ptr + 0, int(clock_real).to_bytes(4, byteorder='little')) 228 | uc.mem_write(tp_ptr + 4, int(0).to_bytes(4, byteorder='little')) 229 | return 0 230 | elif clk_id == CLOCK_MONOTONIC or clk_id == CLOCK_MONOTONIC_COARSE: 231 | if OVERRIDE_CLOCK: 232 | uc.mem_write(tp_ptr + 0, int(OVERRIDE_CLOCK_TIME).to_bytes(4, byteorder='little')) 233 | uc.mem_write(tp_ptr + 4, int(0).to_bytes(4, byteorder='little')) 234 | else: 235 | clock_add = time.time() - self._clock_start # Seconds passed since clock_start was set. 236 | 237 | uc.mem_write(tp_ptr + 0, int(self._clock_start + clock_add).to_bytes(4, byteorder='little')) 238 | uc.mem_write(tp_ptr + 4, int(0).to_bytes(4, byteorder='little')) 239 | return 0 240 | else: 241 | raise NotImplementedError("Unsupported clk_id: %d (%x)" % (clk_id, clk_id)) 242 | 243 | def _socket(self, uc, family, type_in, protocol): 244 | socket_id = self._socket_id + 1 245 | socket = SocketInfo() 246 | socket.domain = family 247 | socket.type = type_in 248 | socket.protocol = protocol 249 | 250 | self._sockets[socket_id] = socket 251 | self._socket_id = self._socket_id + 1 252 | 253 | return socket_id 254 | 255 | def _bind(self, uc, fd, addr, addr_len): 256 | socket = self._sockets.get(fd, None) 257 | 258 | if socket is None: 259 | raise Exception('Expected a socket') 260 | 261 | if socket.domain != socket_info.AF_UNIX and socket.type != socket_info.SOCK_STREAM: 262 | raise Exception('Unexpected socket domain / type.') 263 | 264 | # The struct is confusing.. 265 | socket.addr = uc.mem_read(addr + 3, addr_len - 3).decode(encoding="utf-8") 266 | 267 | logger.info('Binding socket to ://%s' % socket.addr) 268 | 269 | return 0 270 | 271 | def _connect(self, uc, fd, addr, addr_len): 272 | """ 273 | If the connection or binding succeeds, zero is returned. 274 | On error, -1 is returned, and errno is set appropriately. 275 | """ 276 | hexdump.hexdump(uc.mem_read(addr, addr_len)) 277 | 278 | # return 0 279 | raise NotImplementedError() 280 | 281 | def _tgkill(self, uc, tgid, tid, sig): 282 | """ 283 | The tgkill() system call can be used to send any signal to any thread in the same thread group. 284 | """ 285 | return 0 286 | 287 | def _getrandom(self, uc, buf, count, flags): 288 | uc.mem_write(buf, b"\x01" * count) 289 | return count 290 | -------------------------------------------------------------------------------- /src/androidemu/cpu/syscall_hooks_memory.py: -------------------------------------------------------------------------------- 1 | from unicorn import Uc 2 | from androidemu.cpu.syscall_handlers import SyscallHandlers 3 | from androidemu.memory.memory_manager import MemoryManager 4 | 5 | from androidemu.memory import UC_MEM_ALIGN, align 6 | 7 | class SyscallHooksMemory: 8 | 9 | def __init__(self, uc: Uc, memory: MemoryManager, syscall_handler: SyscallHandlers): 10 | self._uc = uc 11 | self._memory = memory 12 | self._syscall_handler = syscall_handler 13 | self._syscall_handler.set_handler(0x5B, "munmap", 2, self._handle_munmap) 14 | self._syscall_handler.set_handler(0x7D, "mprotect", 3, self._handle_mprotect) 15 | self._syscall_handler.set_handler(0xC0, "mmap2", 6, self._handle_mmap2) 16 | self._syscall_handler.set_handler(0xDC, "madvise", 3, self._handle_madvise) 17 | 18 | def _handle_munmap(self, uc, addr, len_in): 19 | self._memory.mapping_unmap(addr, len_in) 20 | 21 | def _handle_mmap2(self, uc, addr, length, prot, flags, fd, offset): 22 | """ 23 | void *mmap2(void *addr, size_t length, int prot, int flags, int fd, off_t pgoffset); 24 | """ 25 | 26 | # MAP_FILE 0 27 | # MAP_SHARED 0x01 28 | # MAP_PRIVATE 0x02 29 | # MAP_FIXED 0x10 30 | # MAP_ANONYMOUS 0x20 31 | 32 | if((flags & 0x10) != 0): 33 | if self._handle_mprotect(uc, addr, length, prot) == 0: 34 | return addr 35 | 36 | return -1 37 | 38 | return self._memory.mapping_map(length, prot) 39 | 40 | def _handle_madvise(self, uc, start, len_in, behavior): 41 | """ 42 | int madvise(void *addr, size_t length, int advice); 43 | The kernel is free to ignore the advice. 44 | On success madvise() returns zero. On error, it returns -1 and errno is set appropriately. 45 | """ 46 | # We don't need your advise. 47 | return 0 48 | 49 | def _handle_mprotect(self, uc, addr, len_in, prot): 50 | """ 51 | int mprotect(void *addr, size_t len, int prot); 52 | 53 | mprotect() changes protection for the calling process's memory page(s) containing any part of the address 54 | range in the interval [addr, addr+len-1]. addr must be aligned to a page boundary. 55 | """ 56 | 57 | addr2, len_in = align(addr, len_in, True) 58 | if addr2 != addr: 59 | return -1 60 | 61 | self._memory.mapping_protect(addr, len_in, prot) 62 | return 0 63 | -------------------------------------------------------------------------------- /src/androidemu/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/src/androidemu/data/__init__.py -------------------------------------------------------------------------------- /src/androidemu/data/fork_info.py: -------------------------------------------------------------------------------- 1 | from unicorn import Uc 2 | from unicorn.arm_const import * 3 | 4 | 5 | class ForkInfo: 6 | 7 | def __init__(self, uc: Uc, pid): 8 | self._uc = uc 9 | self._registers = dict() 10 | self.pid = pid 11 | 12 | def save_state(self): 13 | """ 14 | We are forking, so save everything there is to save. 15 | """ 16 | for i in range(UC_ARM_REG_INVALID, UC_ARM_REG_ENDING + 1): 17 | self._registers[i] = self._uc.reg_read(i) 18 | 19 | def load_state(self): 20 | for i in range(UC_ARM_REG_INVALID, UC_ARM_REG_ENDING + 1): 21 | self._uc.reg_write(i, self._registers[i]) 22 | -------------------------------------------------------------------------------- /src/androidemu/data/socket_info.py: -------------------------------------------------------------------------------- 1 | # https://github.com/torvalds/linux/blob/master/include/linux/socket.h 2 | AF_UNIX = 1 3 | 4 | # http://students.mimuw.edu.pl/SO/Linux/Kod/include/linux/socket.h.html 5 | SOCK_STREAM = 1 6 | 7 | 8 | class SocketInfo: 9 | 10 | def __init__(self): 11 | self.domain = 0 12 | self.type = 0 13 | self.protocol = 0 14 | -------------------------------------------------------------------------------- /src/androidemu/emulator.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from random import randint 4 | 5 | import hexdump 6 | from unicorn import Uc, UC_ARCH_ARM, UC_MODE_ARM 7 | from unicorn.arm_const import UC_ARM_REG_SP, UC_ARM_REG_LR, UC_ARM_REG_R0, UC_ARM_REG_C13_C0_3 8 | 9 | from androidemu.cpu.interrupt_handler import InterruptHandler 10 | from androidemu.cpu.syscall_handlers import SyscallHandlers 11 | from androidemu.cpu.syscall_hooks import SyscallHooks 12 | from androidemu.cpu.syscall_hooks_memory import SyscallHooksMemory 13 | from androidemu.hooker import Hooker 14 | from androidemu.internal.modules import Modules 15 | from androidemu.java.helpers.native_method import native_write_args 16 | from androidemu.java.java_classloader import JavaClassLoader 17 | from androidemu.java.java_vm import JavaVM 18 | from androidemu.memory import STACK_ADDR, STACK_SIZE, HOOK_MEMORY_BASE, HOOK_MEMORY_SIZE 19 | from androidemu.memory.memory_manager import MemoryManager 20 | from androidemu.native.hooks import NativeHooks 21 | from androidemu.tracer import Tracer 22 | from androidemu.utils.memory_helpers import write_utf8 23 | from androidemu.vfs.file_system import VirtualFileSystem 24 | 25 | logger = logging.getLogger(__name__) 26 | 27 | 28 | class Emulator: 29 | """ 30 | :type uc Uc 31 | :type modules Modules 32 | """ 33 | def __init__(self, vfs_root: str = None, vfp_inst_set: bool = False): 34 | # Unicorn. 35 | self.uc = Uc(UC_ARCH_ARM, UC_MODE_ARM) 36 | 37 | if vfp_inst_set: 38 | self._enable_vfp() 39 | 40 | # Android 41 | self.system_properties = {"libc.debug.malloc.options": ""} 42 | 43 | # Stack. 44 | self.uc.mem_map(STACK_ADDR, STACK_SIZE) 45 | self.uc.reg_write(UC_ARM_REG_SP, STACK_ADDR + STACK_SIZE) 46 | 47 | # Executable data. 48 | self.modules = Modules(self) 49 | self.memory_manager = MemoryManager(self.uc) 50 | 51 | # CPU 52 | self.interrupt_handler = InterruptHandler(self.uc) 53 | self.syscall_handler = SyscallHandlers(self.interrupt_handler) 54 | self.syscall_hooks = SyscallHooks(self.uc, self.syscall_handler, self.modules) 55 | self.syscall_hooks_memory = SyscallHooksMemory(self.uc, self.memory_manager, self.syscall_handler) 56 | 57 | # File System 58 | if vfs_root is not None: 59 | self.vfs = VirtualFileSystem(vfs_root, self.syscall_handler) 60 | else: 61 | self.vfs = None 62 | 63 | # Hooker 64 | self.uc.mem_map(HOOK_MEMORY_BASE, HOOK_MEMORY_SIZE) 65 | self.hooker = Hooker(self, HOOK_MEMORY_BASE, HOOK_MEMORY_SIZE) 66 | 67 | # JavaVM 68 | self.java_classloader = JavaClassLoader() 69 | self.java_vm = JavaVM(self, self.java_classloader, self.hooker) 70 | 71 | # Native 72 | self.native_hooks = NativeHooks(self, self.memory_manager, self.modules, self.hooker) 73 | 74 | # Tracer 75 | self.tracer = Tracer(self.uc, self.modules) 76 | 77 | # Thread. 78 | self._setup_thread_register() 79 | 80 | # https://github.com/unicorn-engine/unicorn/blob/8c6cbe3f3cabed57b23b721c29f937dd5baafc90/tests/regress/arm_fp_vfp_disabled.py#L15 81 | def _enable_vfp(self): 82 | # MRC p15, #0, r1, c1, c0, #2 83 | # ORR r1, r1, #(0xf << 20) 84 | # MCR p15, #0, r1, c1, c0, #2 85 | # MOV r1, #0 86 | # MCR p15, #0, r1, c7, c5, #4 87 | # MOV r0,#0x40000000 88 | # FMXR FPEXC, r0 89 | code = '11EE501F' 90 | code += '41F47001' 91 | code += '01EE501F' 92 | code += '4FF00001' 93 | code += '07EE951F' 94 | code += '4FF08040' 95 | code += 'E8EE100A' 96 | # vpush {d8} 97 | code += '2ded028b' 98 | 99 | address = 0x1000 100 | mem_size = 0x1000 101 | code_bytes = bytes.fromhex(code) 102 | 103 | try: 104 | self.uc.mem_map(address, mem_size) 105 | self.uc.mem_write(address, code_bytes) 106 | self.uc.reg_write(UC_ARM_REG_SP, address + mem_size) 107 | 108 | self.uc.emu_start(address | 1, address + len(code_bytes)) 109 | finally: 110 | self.uc.mem_unmap(address, mem_size) 111 | 112 | def _setup_thread_register(self): 113 | """ 114 | Set up thread register. 115 | This is currently not accurate and just filled with garbage to ensure the emulator does not crash. 116 | 117 | https://developer.arm.com/documentation/ddi0211/k/system-control-coprocessor/system-control-coprocessor-register-descriptions/c13--thread-and-process-id-registers 118 | """ 119 | thread_info_size = 64 120 | thread_info = self.memory_manager.allocate(thread_info_size * 5) 121 | 122 | thread_info_1 = thread_info + (thread_info_size * 0) 123 | thread_info_2 = thread_info + (thread_info_size * 1) 124 | thread_info_3 = thread_info + (thread_info_size * 2) 125 | thread_info_4 = thread_info + (thread_info_size * 3) 126 | thread_info_5 = thread_info + (thread_info_size * 4) 127 | 128 | # Thread name 129 | write_utf8(self.uc, thread_info_5, "AndroidNativeEmu") 130 | 131 | # R4 132 | self.uc.mem_write(thread_info_2 + 0x4, int(thread_info_5).to_bytes(4, byteorder='little')) 133 | self.uc.mem_write(thread_info_2 + 0xC, int(thread_info_3).to_bytes(4, byteorder='little')) 134 | 135 | # R1 136 | self.uc.mem_write(thread_info_1 + 0x4, int(thread_info_4).to_bytes(4, byteorder='little')) 137 | self.uc.mem_write(thread_info_1 + 0xC, int(thread_info_2).to_bytes(4, byteorder='little')) 138 | self.uc.reg_write(UC_ARM_REG_C13_C0_3, thread_info_1) 139 | 140 | def load_library(self, filename, do_init=True): 141 | libmod = self.modules.load_module(filename) 142 | if do_init: 143 | logger.debug("Calling init for: %s " % filename) 144 | for fun_ptr in libmod.init_array: 145 | logger.debug("Calling init function: %x " % fun_ptr) 146 | self.call_native(fun_ptr, 0, 0, 0) 147 | return libmod 148 | 149 | def call_symbol(self, module, symbol_name, *argv, is_return_jobject=True): 150 | symbol = module.find_symbol(symbol_name) 151 | 152 | if symbol is None: 153 | logger.error('Unable to find symbol \'%s\' in module \'%s\'.' % (symbol_name, module.filename)) 154 | return 155 | 156 | return self.call_native(symbol.address, *argv, is_return_jobject=is_return_jobject) 157 | 158 | def call_native(self, addr, *argv, is_return_jobject=True): 159 | # Detect JNI call 160 | is_jni = False 161 | 162 | if len(argv) >= 1: 163 | is_jni = argv[0] == self.java_vm.address_ptr or argv[0] == self.java_vm.jni_env.address_ptr 164 | 165 | # TODO: Write JNI args to local ref table if jni. 166 | 167 | try: 168 | # Execute native call. 169 | self.uc.reg_write(UC_ARM_REG_SP, STACK_ADDR + STACK_SIZE) 170 | native_write_args(self, *argv) 171 | stop_pos = randint(HOOK_MEMORY_BASE, HOOK_MEMORY_BASE + HOOK_MEMORY_SIZE) | 1 172 | self.uc.reg_write(UC_ARM_REG_LR, stop_pos) 173 | self.uc.emu_start(addr, stop_pos - 1) 174 | 175 | # Read result from locals if jni. 176 | if is_jni and is_return_jobject: 177 | result_idx = self.uc.reg_read(UC_ARM_REG_R0) 178 | result = self.java_vm.jni_env.get_local_reference(result_idx) 179 | 180 | if result is None: 181 | return result 182 | 183 | return result.value 184 | else: 185 | return self.uc.reg_read(UC_ARM_REG_R0) 186 | finally: 187 | # Clear locals if jni. 188 | if is_jni: 189 | self.java_vm.jni_env.clear_locals() 190 | 191 | def dump(self, out_dir): 192 | os.makedirs(out_dir) 193 | 194 | for begin, end, prot in [reg for reg in self.uc.mem_regions()]: 195 | filename = "{:#010x}-{:#010x}.bin".format(begin, end) 196 | pathname = os.path.join(out_dir, filename) 197 | with open(pathname, "w") as f: 198 | f.write(hexdump.hexdump(self.uc.mem_read(begin, end - begin), result='return')) 199 | -------------------------------------------------------------------------------- /src/androidemu/emulator_error.py: -------------------------------------------------------------------------------- 1 | class EmulatorError(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /src/androidemu/hooker.py: -------------------------------------------------------------------------------- 1 | from keystone import Ks, KS_ARCH_ARM, KS_MODE_THUMB 2 | from unicorn import * 3 | from unicorn.arm_const import * 4 | 5 | STACK_OFFSET = 8 6 | 7 | 8 | # Utility class to create a bridge between ARM and Python. 9 | class Hooker: 10 | """ 11 | :type emu androidemu.emulator.Emulator 12 | """ 13 | 14 | def __init__(self, emu, base_addr, size): 15 | self._emu = emu 16 | self._keystone = Ks(KS_ARCH_ARM, KS_MODE_THUMB) 17 | self._size = size 18 | self._current_id = 0xFF00 19 | self._hooks = dict() 20 | self._hook_magic = base_addr 21 | self._hook_start = base_addr + 4 22 | self._hook_current = self._hook_start 23 | self._emu.uc.hook_add(UC_HOOK_CODE, self._hook, None, self._hook_start, self._hook_start + size) 24 | 25 | def _get_next_id(self): 26 | idx = self._current_id 27 | self._current_id += 1 28 | return idx 29 | 30 | def write_function(self, func): 31 | # Get the hook id. 32 | hook_id = self._get_next_id() 33 | hook_addr = self._hook_current 34 | 35 | # Create the ARM assembly code. 36 | # Make sure to update STACK_OFFSET if you change the PUSH/POP. 37 | asm = "PUSH {R4,LR}\n" \ 38 | "MOV R4, #" + hex(hook_id) + "\n" \ 39 | "MOV R4, R4\n" \ 40 | "POP {R4,PC}" 41 | 42 | asm_bytes_list, asm_count = self._keystone.asm(bytes(asm, encoding='ascii')) 43 | 44 | if asm_count != 4: 45 | raise ValueError("Expected asm_count to be 4 instead of %u." % asm_count) 46 | 47 | # Write assembly code to the emulator. 48 | self._emu.uc.mem_write(hook_addr, bytes(asm_bytes_list)) 49 | 50 | # Save results. 51 | self._hook_current += len(asm_bytes_list) 52 | self._hooks[hook_id] = func 53 | 54 | return hook_addr 55 | 56 | def write_function_table(self, table): 57 | if not isinstance(table, dict): 58 | raise ValueError("Expected a dictionary for the function table.") 59 | 60 | index_max = int(max(table, key=int)) + 1 61 | 62 | # First, we write every function and store its result address. 63 | hook_map = dict() 64 | 65 | for index, func in table.items(): 66 | hook_map[index] = self.write_function(func) 67 | 68 | # Then we write the function table. 69 | table_bytes = b"" 70 | table_address = self._hook_current 71 | 72 | for index in range(0, index_max): 73 | address = hook_map[index] if index in hook_map else 0 74 | table_bytes += int(address + 1).to_bytes(4, byteorder='little') # + 1 because THUMB. 75 | 76 | self._emu.uc.mem_write(table_address, table_bytes) 77 | self._hook_current += len(table_bytes) 78 | 79 | # Then we write the a pointer to the table. 80 | ptr_address = self._hook_current 81 | self._emu.uc.mem_write(ptr_address, table_address.to_bytes(4, byteorder='little')) 82 | self._hook_current += 4 83 | 84 | return ptr_address, table_address 85 | 86 | def _hook(self, uc, address, size, user_data): 87 | # Check if instruction is "MOV R4, R4" 88 | if size != 2 or self._emu.uc.mem_read(address, size) != b"\x24\x46": 89 | return 90 | 91 | # Find hook. 92 | hook_id = self._emu.uc.reg_read(UC_ARM_REG_R4) 93 | hook_func = self._hooks[hook_id] 94 | 95 | # Call hook. 96 | try: 97 | hook_func(self._emu) 98 | except: 99 | # Make sure we catch exceptions inside hooks and stop emulation. 100 | uc.emu_stop() 101 | raise 102 | -------------------------------------------------------------------------------- /src/androidemu/internal/__init__.py: -------------------------------------------------------------------------------- 1 | PF_X = 0x1 # Executable 2 | PF_W = 0x2 # Writable 3 | PF_R = 0x4 # Readable 4 | 5 | 6 | def get_segment_protection(prot_in): 7 | prot = 0 8 | 9 | if (prot_in & PF_R) != 0: 10 | prot |= 1 11 | 12 | if (prot_in & PF_W) != 0: 13 | prot |= 2 14 | 15 | if (prot_in & PF_X) != 0: 16 | prot |= 4 17 | 18 | return prot 19 | -------------------------------------------------------------------------------- /src/androidemu/internal/arm.py: -------------------------------------------------------------------------------- 1 | # From http://infocenter.arm.com/help/topic/com.arm.doc.ihi0044f/IHI0044F_aaelf.pdf 2 | 3 | R_ARM_ABS32 = 2 4 | R_ARM_GLOB_DAT = 21 5 | R_ARM_JUMP_SLOT = 22 6 | R_ARM_RELATIVE = 23 7 | 8 | R_AARCH64_GLOB_DAT = 1025 9 | R_AARCH64_JUMP_SLOT = 1026 10 | R_AARCH64_RELATIVE = 1027 11 | -------------------------------------------------------------------------------- /src/androidemu/internal/module.py: -------------------------------------------------------------------------------- 1 | class Module: 2 | 3 | """ 4 | :type filename str 5 | :type base int 6 | :type size int 7 | """ 8 | def __init__(self, filename, address, size, symbols_resolved, init_array=[]): 9 | self.filename = filename 10 | self.base = address 11 | self.size = size 12 | self.symbols = symbols_resolved 13 | self.symbol_lookup = dict() 14 | self.init_array = list(init_array) 15 | 16 | # Create fast lookup. 17 | for symbol_name, symbol in self.symbols.items(): 18 | if symbol.address != 0: 19 | self.symbol_lookup[symbol.address] = (symbol_name, symbol) 20 | 21 | def find_symbol(self, name): 22 | if name in self.symbols: 23 | return self.symbols[name] 24 | return None 25 | 26 | def is_symbol_addr(self, addr): 27 | if addr in self.symbol_lookup: 28 | return self.symbol_lookup[addr](0) 29 | else: 30 | return None 31 | 32 | -------------------------------------------------------------------------------- /src/androidemu/internal/modules.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from elftools.elf.elffile import ELFFile 4 | from elftools.elf.relocation import RelocationSection 5 | from elftools.elf.sections import SymbolTableSection 6 | from unicorn import UC_PROT_ALL 7 | 8 | from androidemu.internal import get_segment_protection, arm 9 | from androidemu.internal.module import Module 10 | from androidemu.internal.symbol_resolved import SymbolResolved 11 | 12 | import struct 13 | 14 | from androidemu.memory import align 15 | 16 | logger = logging.getLogger(__name__) 17 | 18 | 19 | class Modules: 20 | """ 21 | :type emu androidemu.emulator.Emulator 22 | :type modules list[Module] 23 | """ 24 | def __init__(self, emu): 25 | self.emu = emu 26 | self.modules = list() 27 | self.symbol_hooks = dict() 28 | 29 | def add_symbol_hook(self, symbol_name, addr): 30 | self.symbol_hooks[symbol_name] = addr 31 | 32 | def find_symbol(self, addr): 33 | for module in self.modules: 34 | if addr in module.symbol_lookup: 35 | return module.symbol_lookup[addr] 36 | return None, None 37 | 38 | def find_symbol_name(self, name): 39 | return self._elf_lookup_symbol(name) 40 | 41 | def find_module(self, addr): 42 | for module in self.modules: 43 | if module.base == addr: 44 | return module 45 | return None 46 | 47 | def load_module(self, filename): 48 | logger.debug("Loading module '%s'." % filename) 49 | 50 | with open(filename, 'rb') as fstream: 51 | elf = ELFFile(fstream) 52 | 53 | dynamic = elf.header.e_type == 'ET_DYN' 54 | 55 | if not dynamic: 56 | raise NotImplementedError("Only ET_DYN is supported at the moment.") 57 | 58 | # Parse program header (Execution view). 59 | 60 | # - LOAD (determinate what parts of the ELF file get mapped into memory) 61 | load_segments = [x for x in elf.iter_segments() if x.header.p_type == 'PT_LOAD'] 62 | 63 | # Find bounds of the load segments. 64 | bound_low = 0 65 | bound_high = 0 66 | 67 | for segment in load_segments: 68 | if segment.header.p_memsz == 0: 69 | continue 70 | 71 | if bound_low > segment.header.p_vaddr: 72 | bound_low = segment.header.p_vaddr 73 | 74 | high = segment.header.p_vaddr + segment.header.p_memsz 75 | 76 | if bound_high < high: 77 | bound_high = high 78 | 79 | # Retrieve a base address for this module. 80 | (load_base, _) = self.emu.memory_manager.reserve_module(bound_high - bound_low) 81 | 82 | logger.debug('=> Base address: 0x%x' % load_base) 83 | 84 | for segment in load_segments: 85 | prot = get_segment_protection(segment.header.p_flags) 86 | prot = prot if prot != 0 else UC_PROT_ALL 87 | 88 | (seg_addr, seg_size) = align(load_base + segment.header.p_vaddr, segment.header.p_memsz, True) 89 | 90 | self.emu.uc.mem_map(seg_addr, seg_size, prot) 91 | self.emu.uc.mem_write(load_base + segment.header.p_vaddr, segment.data()) 92 | 93 | rel_section = None 94 | for section in elf.iter_sections(): 95 | if not isinstance(section, RelocationSection): 96 | continue 97 | rel_section = section 98 | break 99 | 100 | # Parse section header (Linking view). 101 | dynsym = elf.get_section_by_name(".dynsym") 102 | dynstr = elf.get_section_by_name(".dynstr") 103 | 104 | # Find init array. 105 | init_array_size = 0 106 | init_array_offset = 0 107 | init_array = [] 108 | for x in elf.iter_segments(): 109 | if x.header.p_type == "PT_DYNAMIC": 110 | for tag in x.iter_tags(): 111 | if tag.entry.d_tag == "DT_INIT_ARRAYSZ": 112 | init_array_size = tag.entry.d_val 113 | elif tag.entry.d_tag == "DT_INIT_ARRAY": 114 | init_array_offset = tag.entry.d_val 115 | 116 | for _ in range(int(init_array_size / 4)): 117 | # covert va to file offset 118 | for seg in load_segments: 119 | if seg.header.p_vaddr <= init_array_offset < seg.header.p_vaddr + seg.header.p_memsz: 120 | init_array_foffset = init_array_offset - seg.header.p_vaddr + seg.header.p_offset 121 | fstream.seek(init_array_foffset) 122 | data = fstream.read(4) 123 | fun_ptr = struct.unpack('I', data)[0] 124 | if fun_ptr != 0: 125 | # fun_ptr += load_base 126 | init_array.append(fun_ptr + load_base) 127 | # print ("find init array for :%s %x" % (filename, fun_ptr)) 128 | else: 129 | # search in reloc 130 | for rel in rel_section.iter_relocations(): 131 | rel_info_type = rel['r_info_type'] 132 | rel_addr = rel['r_offset'] 133 | if rel_info_type == arm.R_ARM_ABS32 and rel_addr == init_array_offset: 134 | sym = dynsym.get_symbol(rel['r_info_sym']) 135 | sym_value = sym['st_value'] 136 | init_array.append(load_base + sym_value) 137 | # print ("find init array for :%s %x" % (filename, sym_value)) 138 | break 139 | init_array_offset += 4 140 | 141 | # Resolve all symbols. 142 | symbols_resolved = dict() 143 | 144 | for section in elf.iter_sections(): 145 | if not isinstance(section, SymbolTableSection): 146 | continue 147 | 148 | itersymbols = section.iter_symbols() 149 | next(itersymbols) # Skip first symbol which is always NULL. 150 | for symbol in itersymbols: 151 | symbol_address = self._elf_get_symval(elf, load_base, symbol) 152 | if symbol_address is not None: 153 | symbols_resolved[symbol.name] = SymbolResolved(symbol_address, symbol) 154 | 155 | # Relocate. 156 | for section in elf.iter_sections(): 157 | if not isinstance(section, RelocationSection): 158 | continue 159 | 160 | for rel in section.iter_relocations(): 161 | sym = dynsym.get_symbol(rel['r_info_sym']) 162 | sym_value = sym['st_value'] 163 | 164 | rel_addr = load_base + rel['r_offset'] # Location where relocation should happen 165 | rel_info_type = rel['r_info_type'] 166 | 167 | # https://static.docs.arm.com/ihi0044/e/IHI0044E_aaelf.pdf 168 | # Relocation table for ARM 169 | if rel_info_type == arm.R_ARM_ABS32: 170 | # Read value. 171 | offset = int.from_bytes(self.emu.uc.mem_read(rel_addr, 4), byteorder='little') 172 | # Create the new value. 173 | value = load_base + sym_value + offset 174 | # Check thumb. 175 | if sym['st_info']['type'] == 'STT_FUNC': 176 | value = value | 1 177 | # Write the new value 178 | self.emu.uc.mem_write(rel_addr, value.to_bytes(4, byteorder='little')) 179 | elif rel_info_type == arm.R_ARM_GLOB_DAT or \ 180 | rel_info_type == arm.R_ARM_JUMP_SLOT: 181 | # Resolve the symbol. 182 | if sym.name in symbols_resolved: 183 | value = symbols_resolved[sym.name].address 184 | 185 | # Write the new value 186 | self.emu.uc.mem_write(rel_addr, value.to_bytes(4, byteorder='little')) 187 | elif rel_info_type == arm.R_ARM_RELATIVE: 188 | if sym_value == 0: 189 | # Load address at which it was linked originally. 190 | value_orig_bytes = self.emu.uc.mem_read(rel_addr, 4) 191 | value_orig = int.from_bytes(value_orig_bytes, byteorder='little') 192 | 193 | # Create the new value 194 | value = load_base + value_orig 195 | 196 | # Write the new value 197 | self.emu.uc.mem_write(rel_addr, value.to_bytes(4, byteorder='little')) 198 | else: 199 | raise NotImplementedError() 200 | else: 201 | logger.error("Unhandled relocation type %i." % rel_info_type) 202 | 203 | # Store information about loaded module. 204 | module = Module(filename, load_base, bound_high - bound_low, symbols_resolved, init_array) 205 | self.modules.append(module) 206 | 207 | return module 208 | 209 | def _elf_get_symval(self, elf, elf_base, symbol): 210 | if symbol.name in self.symbol_hooks: 211 | return self.symbol_hooks[symbol.name] 212 | 213 | if symbol['st_shndx'] == 'SHN_UNDEF': 214 | # External symbol, lookup value. 215 | target = self._elf_lookup_symbol(symbol.name) 216 | if target is None: 217 | # Extern symbol not found 218 | if symbol['st_info']['bind'] == 'STB_WEAK': 219 | # Weak symbol initialized as 0 220 | return 0 221 | else: 222 | logger.error('=> Undefined external symbol: %s' % symbol.name) 223 | return None 224 | else: 225 | return target 226 | elif symbol['st_shndx'] == 'SHN_ABS': 227 | # Absolute symbol. 228 | return elf_base + symbol['st_value'] 229 | else: 230 | # Internally defined symbol. 231 | return elf_base + symbol['st_value'] 232 | 233 | def _elf_lookup_symbol(self, name): 234 | for module in self.modules: 235 | if name in module.symbols: 236 | symbol = module.symbols[name] 237 | 238 | if symbol.address != 0: 239 | return symbol.address 240 | 241 | return None 242 | 243 | def __iter__(self): 244 | for x in self.modules: 245 | yield x 246 | -------------------------------------------------------------------------------- /src/androidemu/internal/symbol_resolved.py: -------------------------------------------------------------------------------- 1 | class SymbolResolved: 2 | 3 | def __init__(self, address, symbol): 4 | self.address = address 5 | self.symbol = symbol 6 | -------------------------------------------------------------------------------- /src/androidemu/java/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/src/androidemu/java/__init__.py -------------------------------------------------------------------------------- /src/androidemu/java/classes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/src/androidemu/java/classes/__init__.py -------------------------------------------------------------------------------- /src/androidemu/java/classes/constructor.py: -------------------------------------------------------------------------------- 1 | from androidemu.java.classes.executable import Executable 2 | from androidemu.java.java_class_def import JavaClassDef 3 | from androidemu.java.java_field_def import JavaFieldDef 4 | from androidemu.java.java_method_def import JavaMethodDef 5 | 6 | 7 | class Constructor(metaclass=JavaClassDef, 8 | jvm_name='java/lang/reflect/Constructor', 9 | jvm_fields=[ 10 | JavaFieldDef('slot', 'I', False, ignore=True), 11 | JavaFieldDef('declaringClass', 'Ljava/lang/Class;', False)], 12 | jvm_super=Executable): 13 | 14 | def __init__(self, clazz: JavaClassDef, method: JavaMethodDef): 15 | self._clazz = clazz 16 | self._method = method 17 | self.slot = method.jvm_id 18 | self.declaringClass = self._clazz 19 | self.accessFlags = method.modifier 20 | -------------------------------------------------------------------------------- /src/androidemu/java/classes/executable.py: -------------------------------------------------------------------------------- 1 | from androidemu.java.java_class_def import JavaClassDef 2 | from androidemu.java.java_field_def import JavaFieldDef 3 | 4 | 5 | class Executable(metaclass=JavaClassDef, 6 | jvm_name='java/lang/reflect/Executable', 7 | jvm_fields=[ 8 | JavaFieldDef('accessFlags', 'I', False) 9 | ]): 10 | 11 | def __init__(self): 12 | pass 13 | -------------------------------------------------------------------------------- /src/androidemu/java/classes/method.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from androidemu.emulator_error import EmulatorError 4 | from androidemu.java.classes.executable import Executable 5 | from androidemu.java.java_class_def import JavaClassDef 6 | from androidemu.java.java_field_def import JavaFieldDef 7 | from androidemu.java.java_method_def import java_method_def, JavaMethodDef 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | class Method(metaclass=JavaClassDef, 13 | jvm_name='java/lang/reflect/Method', 14 | jvm_fields=[ 15 | JavaFieldDef('slot', 'I', False, ignore=True), 16 | JavaFieldDef('declaringClass', 'Ljava/lang/Class;', False), 17 | ], 18 | jvm_super=Executable): 19 | 20 | def __init__(self, clazz: JavaClassDef, method: JavaMethodDef): 21 | super().__init__() 22 | self._clazz = clazz 23 | self._method = method 24 | self.slot = method.jvm_id 25 | self.declaringClass = self._clazz 26 | self.accessFlags = method.modifier 27 | 28 | @staticmethod 29 | @java_method_def( 30 | name="getMethodModifiers", 31 | signature="(Ljava/lang/Class;I)I", 32 | args_list=['jobject', 'jint'], 33 | ignore=True 34 | ) 35 | def get_method_modifiers(emu, clazz_obj, jvm_method_id): 36 | clazz = clazz_obj.value 37 | method = clazz.find_method_by_id(jvm_method_id) 38 | 39 | logger.debug('get_method_modifiers(%s, %s)' % (clazz.jvm_name, method.name)) 40 | 41 | if method.modifier is None: 42 | raise EmulatorError('No modifier was given to class %s method %s' % (clazz.jvm_name, method.name)) 43 | 44 | return method.modifier 45 | -------------------------------------------------------------------------------- /src/androidemu/java/constant_values.py: -------------------------------------------------------------------------------- 1 | # https://docs.oracle.com/javase/7/docs/api/constant-values.html 2 | 3 | MODIFIER_PUBLIC = 1 4 | MODIFIER_PRIVATE = 2 5 | MODIFIER_PROTECTED = 4 6 | MODIFIER_STATIC = 8 7 | MODIFIER_FINAL = 16 8 | MODIFIER_SYNCHRONIZED = 32 9 | MODIFIER_VOLATILE = 64 10 | MODIFIER_TRANSIENT = 128 11 | MODIFIER_NATIVE = 256 12 | MODIFIER_INTERFACE = 512 13 | MODIFIER_ABSTRACT = 1024 14 | MODIFIER_STRICT = 2048 15 | -------------------------------------------------------------------------------- /src/androidemu/java/helpers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/src/androidemu/java/helpers/__init__.py -------------------------------------------------------------------------------- /src/androidemu/java/helpers/native_method.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | 3 | from unicorn import Uc 4 | from unicorn.arm_const import * 5 | 6 | from androidemu.hooker import STACK_OFFSET 7 | from androidemu.java.java_class_def import JavaClassDef 8 | from androidemu.java.jni_const import JNI_ERR 9 | from androidemu.java.jni_ref import jobject, jstring, jobjectArray, jbyteArray, jclass 10 | 11 | 12 | def native_write_args(emu, *argv): 13 | amount = len(argv) 14 | 15 | if amount == 0: 16 | return 17 | 18 | if amount >= 1: 19 | native_write_arg_register(emu, UC_ARM_REG_R0, argv[0]) 20 | 21 | if amount >= 2: 22 | native_write_arg_register(emu, UC_ARM_REG_R1, argv[1]) 23 | 24 | if amount >= 3: 25 | native_write_arg_register(emu, UC_ARM_REG_R2, argv[2]) 26 | 27 | if amount >= 4: 28 | native_write_arg_register(emu, UC_ARM_REG_R3, argv[3]) 29 | 30 | if amount >= 5: 31 | sp_start = emu.uc.reg_read(UC_ARM_REG_SP) 32 | sp_current = sp_start - STACK_OFFSET # Need to offset because our hook pushes one register on the stack. 33 | sp_current = sp_current - (4 * (amount - 4)) # Reserve space for arguments. 34 | sp_end = sp_current 35 | 36 | for arg in argv[4:]: 37 | emu.uc.mem_write(sp_current, native_translate_arg(emu, arg).to_bytes(4, byteorder='little')) 38 | sp_current = sp_current + 4 39 | 40 | emu.uc.reg_write(UC_ARM_REG_SP, sp_end) 41 | 42 | 43 | def native_read_args(uc, args_count): 44 | native_args = [] 45 | 46 | if args_count >= 1: 47 | native_args.append(uc.reg_read(UC_ARM_REG_R0)) 48 | 49 | if args_count >= 2: 50 | native_args.append(uc.reg_read(UC_ARM_REG_R1)) 51 | 52 | if args_count >= 3: 53 | native_args.append(uc.reg_read(UC_ARM_REG_R2)) 54 | 55 | if args_count >= 4: 56 | native_args.append(uc.reg_read(UC_ARM_REG_R3)) 57 | 58 | sp = uc.reg_read(UC_ARM_REG_SP) 59 | sp = sp + STACK_OFFSET # Need to offset by 4 because our hook pushes one register on the stack. 60 | 61 | if args_count >= 5: 62 | for x in range(0, args_count - 4): 63 | native_args.append(int.from_bytes(uc.mem_read(sp + (x * 4), 4), byteorder='little')) 64 | 65 | return native_args 66 | 67 | 68 | def native_translate_arg(emu, val): 69 | if isinstance(val, int): 70 | return val 71 | elif isinstance(val, str): 72 | return emu.java_vm.jni_env.add_local_reference(jstring(val)) 73 | elif isinstance(val, list): 74 | return emu.java_vm.jni_env.add_local_reference(jobjectArray(val)) 75 | elif isinstance(val, bytearray): 76 | return emu.java_vm.jni_env.add_local_reference(jbyteArray(val)) 77 | elif isinstance(type(val), JavaClassDef): 78 | # TODO: Look into this, seems wrong.. 79 | return emu.java_vm.jni_env.add_local_reference(jobject(val)) 80 | elif isinstance(val, JavaClassDef): 81 | return emu.java_vm.jni_env.add_local_reference(jclass(val)) 82 | else: 83 | raise NotImplementedError("Unable to write response '%s' type '%s' to emulator." % (str(val), type(val))) 84 | 85 | 86 | def native_write_arg_register(emu, reg, val): 87 | emu.uc.reg_write(reg, native_translate_arg(emu, val)) 88 | 89 | 90 | def native_method(func): 91 | def native_method_wrapper(*argv): 92 | """ 93 | :type self 94 | :type emu androidemu.emulator.Emulator 95 | :type uc Uc 96 | """ 97 | 98 | emu = argv[1] if len(argv) == 2 else argv[0] 99 | uc = emu.uc 100 | 101 | args = inspect.getfullargspec(func).args 102 | args_count = len(args) - (2 if 'self' in args else 1) 103 | 104 | if args_count < 0: 105 | raise RuntimeError("NativeMethod accept at least (self, uc) or (uc).") 106 | 107 | native_args = native_read_args(uc, args_count) 108 | 109 | if len(argv) == 1: 110 | result = func(uc, *native_args) 111 | else: 112 | result = func(argv[0], uc, *native_args) 113 | 114 | if result is not None: 115 | native_write_arg_register(emu, UC_ARM_REG_R0, result) 116 | else: 117 | uc.reg_write(UC_ARM_REG_R0, JNI_ERR) 118 | 119 | return native_method_wrapper 120 | -------------------------------------------------------------------------------- /src/androidemu/java/java_class_def.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import itertools 3 | import logging 4 | 5 | logger = logging.getLogger(__name__) 6 | 7 | 8 | class JavaClassDef(type): 9 | next_jvm_id = itertools.count(start=1) 10 | next_jvm_method_id = itertools.count(start=0xd2000000, step=4) 11 | next_jvm_field_id = itertools.count(start=0xe2000000, step=4) 12 | 13 | def __init__(cls, name, base, ns, jvm_name=None, jvm_fields=None, jvm_ignore=False, jvm_super=None): 14 | cls.jvm_id = next(JavaClassDef.next_jvm_id) 15 | cls.jvm_name = jvm_name 16 | cls.jvm_methods = dict() 17 | cls.jvm_fields = dict() 18 | cls.jvm_ignore = jvm_ignore 19 | cls.jvm_super = jvm_super 20 | 21 | # Register all defined Java methods. 22 | for func in inspect.getmembers(cls, predicate=inspect.isfunction): 23 | if hasattr(func[1], 'jvm_method'): 24 | method = func[1].jvm_method 25 | method.jvm_id = next(JavaClassDef.next_jvm_method_id) 26 | cls.jvm_methods[method.jvm_id] = method 27 | 28 | # Register all defined Java fields. 29 | if jvm_fields is not None: 30 | for jvm_field in jvm_fields: 31 | jvm_field.jvm_id = next(JavaClassDef.next_jvm_field_id) 32 | cls.jvm_fields[jvm_field.jvm_id] = jvm_field 33 | 34 | type.__init__(cls, name, base, ns) 35 | 36 | def __new__(mcs, name, base, ns, **kargs): 37 | return type.__new__(mcs, name, base, ns) 38 | 39 | def register_native(self, name, signature, ptr_func): 40 | found = False 41 | found_method = None 42 | 43 | # Search for a defined jvm method. 44 | for method in self.jvm_methods.values(): 45 | if method.name == name and method.signature == signature: 46 | method.native_addr = ptr_func 47 | found = True 48 | found_method = method 49 | break 50 | 51 | if not found: 52 | x = "Register native ('%s', '%s') failed on class %s." % (name, signature, self.__name__) 53 | logger.warning(x) 54 | return 55 | # raise RuntimeError("Register native ('%s', '%s') failed on class %s." % (name, signature, self.__name__)) 56 | logger.debug("Registered native function ('%s', '%s') to %s.%s" % (name, signature, 57 | self.__name__, found_method.func_name)) 58 | 59 | def find_method(cls, name, signature): 60 | for method in cls.jvm_methods.values(): 61 | if method.name == name and method.signature == signature: 62 | return method 63 | 64 | return None 65 | 66 | def find_method_by_id(cls, jvm_id): 67 | return cls.jvm_methods[jvm_id] 68 | 69 | def find_field(cls, name, signature, is_static): 70 | for field in cls.jvm_fields.values(): 71 | if field.name == name and field.signature == signature and field.is_static == is_static: 72 | return field 73 | 74 | return None 75 | 76 | def find_field_by_id(cls, jvm_id): 77 | try: 78 | if cls.jvm_super is not None: 79 | return cls.jvm_super.find_field_by_id(jvm_id) 80 | except KeyError: 81 | pass 82 | 83 | return cls.jvm_fields[jvm_id] 84 | -------------------------------------------------------------------------------- /src/androidemu/java/java_classloader.py: -------------------------------------------------------------------------------- 1 | from androidemu.java.java_class_def import JavaClassDef 2 | 3 | 4 | class JavaClassLoader: 5 | 6 | """ 7 | :type class_by_id dict[int, JavaClassDef] 8 | :type class_by_name dict[string, JavaClassDef] 9 | """ 10 | def __init__(self): 11 | self.class_by_id = dict() 12 | self.class_by_name = dict() 13 | 14 | def add_class(self, clazz): 15 | if not isinstance(clazz, JavaClassDef): 16 | raise ValueError('Expected a JavaClassDef.') 17 | 18 | if clazz.jvm_name in self.class_by_name: 19 | raise KeyError('The class \'%s\' is already registered.' % clazz.jvm_name) 20 | 21 | self.class_by_id[clazz.jvm_id] = clazz 22 | self.class_by_name[clazz.jvm_name] = clazz 23 | 24 | def find_class_by_id(self, jvm_id): 25 | if jvm_id not in self.class_by_id: 26 | return None 27 | 28 | return self.class_by_id[jvm_id] 29 | 30 | def find_class_by_name(self, name): 31 | if name not in self.class_by_name: 32 | return None 33 | 34 | return self.class_by_name[name] 35 | -------------------------------------------------------------------------------- /src/androidemu/java/java_field_def.py: -------------------------------------------------------------------------------- 1 | class JavaFieldDef: 2 | 3 | def __init__(self, name, signature, is_static, static_value=None, ignore=False): 4 | self.jvm_id = None # Assigned by JavaClassDef. 5 | self.name = name 6 | self.signature = signature 7 | self.is_static = is_static 8 | self.static_value = static_value 9 | self.ignore = ignore 10 | 11 | if self.is_static and self.static_value is None: 12 | raise ValueError('Static value may not be None for a static field.') 13 | -------------------------------------------------------------------------------- /src/androidemu/java/java_method_def.py: -------------------------------------------------------------------------------- 1 | class JavaMethodDef: 2 | 3 | def __init__(self, func_name, func, name, signature, native, args_list=None, modifier=None, ignore=None): 4 | self.jvm_id = None # Assigned by JavaClassDef. 5 | self.func_name = func_name 6 | self.func = func 7 | self.name = name 8 | self.signature = signature 9 | self.native = native 10 | self.native_addr = None 11 | self.args_list = args_list 12 | self.modifier = modifier 13 | self.ignore = ignore 14 | 15 | 16 | def java_method_def(name, signature, native=False, args_list=None, modifier=None, ignore=False): 17 | def java_method_def_real(func): 18 | def native_wrapper(self, emulator, *argv): 19 | return emulator.call_native( 20 | native_wrapper.jvm_method.native_addr, 21 | emulator.java_vm.jni_env.address_ptr, # JNIEnv* 22 | 0xFA, # this, TODO: Implement proper "this", a reference to the Java object inside which this native 23 | # method has been declared in 24 | *argv # Extra args. 25 | ) 26 | 27 | def normal_wrapper(*args, **kwargs): 28 | result = func(*args, **kwargs) 29 | return result 30 | 31 | wrapper = native_wrapper if native else normal_wrapper 32 | wrapper.jvm_method = JavaMethodDef(func.__name__, wrapper, name, signature, native, 33 | args_list=args_list, 34 | modifier=modifier, 35 | ignore=ignore) 36 | return wrapper 37 | 38 | return java_method_def_real 39 | -------------------------------------------------------------------------------- /src/androidemu/java/java_vm.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from androidemu.hooker import Hooker 4 | from androidemu.java.helpers.native_method import native_method 5 | from androidemu.java.java_classloader import JavaClassLoader 6 | from androidemu.java.jni_const import * 7 | from androidemu.java.jni_env import JNIEnv 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | # https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html 13 | # This class attempts to mimic the JNIInvokeInterface table. 14 | class JavaVM: 15 | 16 | """ 17 | :type class_loader JavaClassLoader 18 | :type hooker Hooker 19 | """ 20 | def __init__(self, emu, class_loader, hooker): 21 | (self.address_ptr, self.address) = hooker.write_function_table({ 22 | 3: self.destroy_java_vm, 23 | 4: self.attach_current_thread, 24 | 5: self.detach_current_thread, 25 | 6: self.get_env, 26 | 7: self.attach_current_thread 27 | }) 28 | 29 | self.jni_env = JNIEnv(emu, class_loader, hooker) 30 | 31 | @native_method 32 | def destroy_java_vm(self, uc): 33 | raise NotImplementedError() 34 | 35 | @native_method 36 | def attach_current_thread(self, uc): 37 | raise NotImplementedError() 38 | 39 | @native_method 40 | def detach_current_thread(self, uc): 41 | # TODO: NooOO idea. 42 | pass 43 | 44 | @native_method 45 | def get_env(self, uc, java_vm, env, version): 46 | logger.debug("java_vm: 0x%08x" % java_vm) 47 | logger.debug("env: 0x%08x" % env) 48 | logger.debug("version: 0x%08x" % version) 49 | 50 | uc.mem_write(env, self.jni_env.address_ptr.to_bytes(4, byteorder='little')) 51 | 52 | logger.debug("JavaVM->GetENV() was called!") 53 | 54 | return JNI_OK 55 | 56 | @native_method 57 | def attach_current_thread_as_daemon(self, uc): 58 | raise NotImplementedError() 59 | -------------------------------------------------------------------------------- /src/androidemu/java/jni_const.py: -------------------------------------------------------------------------------- 1 | JNI_FALSE = 0 2 | JNI_TRUE = 1 3 | 4 | JNI_VERSION_1_1 = 0x00010001 5 | JNI_VERSION_1_2 = 0x00010002 6 | JNI_VERSION_1_4 = 0x00010004 7 | JNI_VERSION_1_6 = 0x00010006 8 | 9 | JNI_OK = 0 # no error 10 | JNI_ERR = -1 # generic error 11 | JNI_EDETACHED = -2 # thread detached from the VM 12 | JNI_EVERSION = -3 # JNI version error 13 | JNI_ENOMEM = -4 # Out of memory 14 | JNI_EEXIST = -5 # VM already created 15 | JNI_EINVAL = -6 # Invalid argument 16 | 17 | JNI_COMMIT = 1 # copy content, do not free buffer 18 | JNI_ABORT = 2 # free buffer w/o copying back 19 | -------------------------------------------------------------------------------- /src/androidemu/java/jni_ref.py: -------------------------------------------------------------------------------- 1 | class jvalue: 2 | 3 | def __init__(self, value=None): 4 | self.value = value 5 | 6 | 7 | class jobject: 8 | 9 | def __init__(self, value=None): 10 | self.value = value 11 | 12 | 13 | class jclass(jobject): 14 | 15 | def __init__(self, value=None): 16 | super().__init__(value) 17 | 18 | 19 | class jstring(jobject): 20 | 21 | def __init__(self, value=None): 22 | super().__init__(value) 23 | 24 | 25 | class jarray(jobject): 26 | 27 | def __init__(self, value=None): 28 | super().__init__(value) 29 | 30 | 31 | class jobjectArray(jarray): 32 | 33 | def __init__(self, value=None): 34 | super().__init__(value) 35 | 36 | 37 | class jbooleanArray(jarray): 38 | 39 | def __init__(self, value=None): 40 | super().__init__(value) 41 | 42 | 43 | class jbyteArray(jarray): 44 | 45 | def __init__(self, value=None): 46 | super().__init__(value) 47 | 48 | 49 | class jcharArray(jarray): 50 | 51 | def __init__(self, value=None): 52 | super().__init__(value) 53 | 54 | 55 | class jshortArray(jarray): 56 | 57 | def __init__(self, value=None): 58 | super().__init__(value) 59 | 60 | 61 | class jintArray(jarray): 62 | 63 | def __init__(self, value=None): 64 | super().__init__(value) 65 | 66 | 67 | class jlongArray(jarray): 68 | 69 | def __init__(self, value=None): 70 | super().__init__(value) 71 | 72 | 73 | class jfloatArray(jarray): 74 | 75 | def __init__(self, value=None): 76 | super().__init__(value) 77 | 78 | 79 | class jdoubleArray(jarray): 80 | 81 | def __init__(self, value=None): 82 | super().__init__(value) 83 | 84 | 85 | class jthrowable(jobject): 86 | 87 | def __init__(self, value=None): 88 | super().__init__(value) 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/androidemu/java/reference_table.py: -------------------------------------------------------------------------------- 1 | from androidemu.java.jni_ref import * 2 | 3 | 4 | class ReferenceTable: 5 | 6 | """ 7 | :type _table dict[int, jobject|None] 8 | """ 9 | def __init__(self, start=1, max_entries=1024): 10 | self._table = dict() 11 | self._start = start 12 | self._size = max_entries 13 | 14 | def set(self, idx, newobj): 15 | if not isinstance(newobj, jobject): 16 | raise ValueError('Expected a jobject.') 17 | 18 | if idx not in self._table: 19 | raise ValueError('Expected a index.') 20 | 21 | self._table[idx] = newobj 22 | 23 | def add(self, obj): 24 | if not isinstance(obj, jobject): 25 | raise ValueError('Expected a jobject.') 26 | 27 | # Search a free index. 28 | index = self._start 29 | while index in self._table: 30 | index += 1 31 | 32 | # Add to table. 33 | self._table[index] = obj 34 | 35 | # Return local reference. 36 | return index 37 | 38 | def remove(self, obj): 39 | # TODO: Test 40 | index = None 41 | for i in range(self._start, self._start + len(self._table)): 42 | if self._table[i] is obj: 43 | index = i 44 | break 45 | 46 | if index is None: 47 | return False 48 | 49 | self._table[index] = None 50 | return True 51 | 52 | def get(self, idx): 53 | if idx not in self._table: 54 | return None 55 | 56 | return self._table[idx] 57 | 58 | def in_range(self, idx): 59 | return self._start <= idx < self._start + self._size 60 | 61 | def clear(self): 62 | self._table.clear() 63 | -------------------------------------------------------------------------------- /src/androidemu/libs/libvendorconn_32.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/src/androidemu/libs/libvendorconn_32.so -------------------------------------------------------------------------------- /src/androidemu/memory/__init__.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | 3 | # Memory addresses 4 | 5 | STACK_ADDR = 0x10000000 6 | STACK_SIZE = 0x00100000 7 | 8 | HOOK_MEMORY_BASE = 0x20000000 9 | HOOK_MEMORY_SIZE = 0x00200000 10 | 11 | MODULES_MIN = 0xA0000000 12 | MODULES_MAX = 0xC0000000 13 | 14 | HEAP_MIN = 0xD0000000 15 | HEAP_MAX = 0xD0200000 16 | 17 | MAPPING_MIN = 0xE0000000 18 | MAPPING_MAX = 0xF0000000 19 | 20 | # Alignment 21 | UC_MEM_ALIGN = 0x1000 22 | 23 | 24 | def align(addr, size, growl): 25 | to = ctypes.c_uint64(UC_MEM_ALIGN).value 26 | mask = ctypes.c_uint64(0xFFFFFFFFFFFFFFFF).value ^ ctypes.c_uint64(to - 1).value 27 | right = addr + size 28 | right = (right + to - 1) & mask 29 | addr &= mask 30 | size = right - addr 31 | if growl: 32 | size = (size + to - 1) & mask 33 | return addr, size 34 | -------------------------------------------------------------------------------- /src/androidemu/memory/allocator.py: -------------------------------------------------------------------------------- 1 | class AllocatorError(Exception): 2 | pass 3 | 4 | 5 | class HeapAllocatorError(AllocatorError): 6 | pass 7 | 8 | 9 | class IncrementalAllocatorError(AllocatorError): 10 | pass 11 | -------------------------------------------------------------------------------- /src/androidemu/memory/allocator_heap.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from unicorn import Uc, UC_PROT_READ, UC_PROT_WRITE 4 | 5 | from androidemu.memory.allocator import HeapAllocatorError 6 | 7 | 8 | class HeapBlock: 9 | next: Optional['HeapBlock'] 10 | 11 | def __init__(self): 12 | self.address = 0 13 | self.size = 0 14 | self.free = False 15 | self.next = None 16 | 17 | 18 | class HeapAllocator: 19 | """ 20 | Distributes allocated memory using a simple malloc implementation. 21 | https://danluu.com/malloc-tutorial/ 22 | """ 23 | 24 | def __init__(self, start: int, end: int, uc: Uc = None): 25 | """ 26 | :param int start: Start address of the heap. 27 | :param int end: End address of the heap. 28 | """ 29 | self._start = start 30 | self._pos = start 31 | self._end = end 32 | self._head = None 33 | 34 | if uc is not None: 35 | self._init_uc(uc) 36 | 37 | def allocate(self, size: int) -> int: 38 | """ 39 | :param int size: The amount of bytes to allocate. 40 | """ 41 | if size <= 0: 42 | return 0 43 | 44 | block = None 45 | 46 | if self._head is None: 47 | block = self._create_block(size) 48 | self._head = block 49 | else: 50 | block, prev = self._find_free_block(size) 51 | 52 | if not block: 53 | block = self._create_block(size, prev) 54 | elif block.size != size: 55 | block = self._split_block(block, size) 56 | 57 | block.free = False 58 | 59 | return block.address 60 | 61 | def free(self, address: int): 62 | if address == 0: 63 | return 64 | 65 | block, prev = self._find_block(address) 66 | 67 | if block is None: 68 | raise HeapAllocatorError('Attempted to free non existing block at 0x%x' % address) 69 | 70 | block.free = True 71 | 72 | self._merge_block(block) 73 | self._merge_block(prev) 74 | 75 | def _create_block(self, size: int, last: HeapBlock = None) -> HeapBlock: 76 | """ 77 | Create a block and add it to the end of the heap list. 78 | """ 79 | # Create new block. 80 | block = HeapBlock() 81 | block.address = self._increment_data(size) 82 | block.size = size 83 | block.free = False 84 | block.next = None 85 | 86 | # Append to last block. 87 | if last is not None: 88 | last.next = block 89 | 90 | return block 91 | 92 | def _find_block(self, address: int) -> (Optional[HeapBlock], Optional[HeapBlock]): 93 | """ 94 | Finds the block that was assigned to the given address. 95 | """ 96 | prev = None 97 | block = self._head 98 | 99 | while block is not None and block.address != address: 100 | prev = block 101 | block = block.next 102 | 103 | return block, prev 104 | 105 | def _find_free_block(self, size: int) -> (Optional[HeapBlock], Optional[HeapBlock]): 106 | """ 107 | Attempts to find a free block that can contain the requested size. 108 | """ 109 | prev = None 110 | block = self._head 111 | 112 | while block is not None and not (block.free and block.size >= size): 113 | prev = block 114 | block = block.next 115 | 116 | return block, prev 117 | 118 | def _merge_block(self, block: HeapBlock): 119 | """ 120 | Merges the given block and it's next block together if both are free. 121 | """ 122 | if block is None: 123 | return 124 | 125 | if block.free and block.next is not None and block.next.free: 126 | block.size = block.size + block.next.size 127 | block.next = block.next.next 128 | 129 | def _split_block(self, block: HeapBlock, size: int) -> HeapBlock: 130 | """ 131 | Splits a block into the requested size by making the given block smaller 132 | and appending the remainder as a new block. 133 | """ 134 | if not block.free: 135 | raise HeapAllocatorError('Attempted to split non-free block') 136 | 137 | # Create new block. 138 | new_block = HeapBlock() 139 | new_block.address = block.address + size 140 | new_block.size = block.size - size 141 | new_block.free = True 142 | new_block.next = block.next 143 | 144 | # Assign the block as the next of the current block. 145 | block.next = new_block 146 | 147 | # Resize current block. 148 | block.size = size 149 | 150 | return block 151 | 152 | def _increment_data(self, size: int): 153 | """ 154 | Increments the current pointer, which simulates the sbrk call. 155 | https://linux.die.net/man/2/sbrk 156 | """ 157 | res = self._pos 158 | self._pos += size 159 | return res 160 | 161 | def _init_uc(self, uc: Uc): 162 | uc.mem_map(self._start, self._end - self._start, UC_PROT_READ | UC_PROT_WRITE) 163 | -------------------------------------------------------------------------------- /src/androidemu/memory/allocator_incremental.py: -------------------------------------------------------------------------------- 1 | from androidemu.memory import align 2 | from androidemu.memory.allocator import IncrementalAllocatorError 3 | 4 | 5 | class IncrementalAllocator: 6 | """ 7 | Distributes memory using a simple increment. 8 | It is assumed that the memory region is not mapped before hand. 9 | 10 | This is mostly used for loading modules. 11 | """ 12 | 13 | def __init__(self, start: int, end: int): 14 | self._pos = start 15 | self._end = end 16 | 17 | def reserve(self, size) -> (int, int): 18 | """ 19 | Returns an Unicorn page aligned mapping. 20 | """ 21 | (_, size_aligned) = align(0, size, True) 22 | ret = self._pos 23 | self._pos += size_aligned 24 | 25 | if self._pos > self._end: 26 | raise IncrementalAllocatorError("Reserve went out of bounds") 27 | 28 | return ret, size_aligned 29 | -------------------------------------------------------------------------------- /src/androidemu/memory/memory_access.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from unicorn import Uc 4 | 5 | 6 | class MemoryAccess(ABC): 7 | 8 | def __init__(self, uc: Uc): 9 | self._uc = uc 10 | 11 | def write(self, addr: int, data): 12 | self._uc.mem_write(addr, data) 13 | 14 | @abstractmethod 15 | def write_u8(self, addr: int, value: int): 16 | pass 17 | 18 | @abstractmethod 19 | def write_u16(self, addr: int, value: int): 20 | pass 21 | 22 | @abstractmethod 23 | def write_u32(self, addr: int, value: int): 24 | pass 25 | 26 | @abstractmethod 27 | def write_u64(self, addr: int, value: int): 28 | pass 29 | 30 | def _write_int(self, addr, value, byte_count): 31 | self._uc.mem_write(addr, value.to_bytes(byte_count, byteorder='little')) 32 | 33 | 34 | class MemoryAccess32(MemoryAccess): 35 | 36 | def __init__(self, uc: Uc): 37 | super().__init__(uc) 38 | 39 | def write_u8(self, addr: int, value: int): 40 | self._write_int(addr, value, 1) 41 | 42 | def write_u16(self, addr: int, value: int): 43 | self._write_int(addr, value, 2) 44 | 45 | def write_u32(self, addr: int, value: int): 46 | self._write_int(addr, value, 4) 47 | 48 | def write_u64(self, addr: int, value: int): 49 | self._write_int(addr, value, 8) 50 | 51 | 52 | class MemoryAccess64(MemoryAccess): 53 | 54 | def __init__(self, uc: Uc): 55 | super().__init__(uc) 56 | 57 | def write_u8(self, addr: int, value: int): 58 | self._write_int(addr, value, 1) 59 | 60 | def write_u16(self, addr: int, value: int): 61 | self._write_int(addr, value, 2) 62 | 63 | def write_u32(self, addr: int, value: int): 64 | self._write_int(addr, value, 4) 65 | 66 | def write_u64(self, addr: int, value: int): 67 | self._write_int(addr, value, 8) 68 | -------------------------------------------------------------------------------- /src/androidemu/memory/memory_manager.py: -------------------------------------------------------------------------------- 1 | from unicorn import Uc 2 | 3 | from androidemu.memory import * 4 | from androidemu.memory.allocator_heap import HeapAllocator 5 | from androidemu.memory.allocator_incremental import IncrementalAllocator 6 | 7 | 8 | class MemoryManager: 9 | 10 | def __init__(self, uc: Uc): 11 | self._uc = uc 12 | self._heap = HeapAllocator(HEAP_MIN, HEAP_MAX, uc) 13 | self._modules = IncrementalAllocator(MODULES_MIN, MODULES_MAX) 14 | self._mappings = IncrementalAllocator(MAPPING_MIN, MAPPING_MAX) 15 | 16 | def allocate(self, size: int) -> int: 17 | """ 18 | Allocate bytes on the heap. 19 | """ 20 | return self._heap.allocate(size) 21 | 22 | def free(self, addr: int): 23 | """ 24 | Free bytes on the heap. 25 | """ 26 | self._heap.free(addr) 27 | 28 | def reserve_module(self, size) -> (int, int): 29 | """ 30 | Reserve bytes for a module. 31 | The caller is responsible for mapping the address into Unicorn. 32 | """ 33 | return self._modules.reserve(size) 34 | 35 | def mapping_map(self, size: int, prot: int) -> int: 36 | """ 37 | Memory mapping for the mmap syscall. 38 | """ 39 | (addr, size_aligned) = self._mappings.reserve(size) 40 | 41 | self._uc.mem_map(addr, size_aligned, prot) 42 | 43 | return addr 44 | 45 | def mapping_unmap(self, addr: int, size: int): 46 | """ 47 | Memory unmapping for the unmap syscall. 48 | """ 49 | if MAPPING_MIN <= addr <= MAPPING_MAX: 50 | self._uc.mem_unmap(addr, size) 51 | 52 | def mapping_protect(self, addr: int, size: int, prot: int): 53 | """ 54 | Memory unmapping for the unmap syscall. 55 | """ 56 | if MAPPING_MIN <= addr <= MAPPING_MAX: 57 | self._uc.mem_protect(addr, size, prot) 58 | -------------------------------------------------------------------------------- /src/androidemu/memory/memory_pointer.py: -------------------------------------------------------------------------------- 1 | from unicorn import Uc 2 | 3 | 4 | class Pointer: 5 | 6 | def __init__(self, uc: Uc, address: int): 7 | self._uc = uc 8 | self._address = address 9 | 10 | def write_int(self, offset: int, value: int): 11 | self._uc.mem_write(self._address + offset, value.to_bytes(4, byteorder='little')) 12 | 13 | def read_int(self, offset: int) -> int: 14 | data = self._uc.mem_read(self._address + offset, 4) 15 | address = int.from_bytes(data, byteorder='little') 16 | 17 | return address 18 | 19 | def read_ptr(self, offset: int) -> 'Pointer': 20 | return Pointer(self._uc, self.read_int(offset)) 21 | -------------------------------------------------------------------------------- /src/androidemu/native/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/src/androidemu/native/__init__.py -------------------------------------------------------------------------------- /src/androidemu/native/hooks.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | from androidemu.hooker import Hooker 5 | from androidemu.internal.modules import Modules 6 | 7 | from androidemu.java.helpers.native_method import native_method, native_read_args 8 | from androidemu.memory.memory_manager import MemoryManager 9 | from androidemu.utils import memory_helpers 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | class NativeHooks: 15 | 16 | def __init__(self, emu, memory: MemoryManager, modules: Modules, hooker: Hooker): 17 | self._emu = emu 18 | self._memory = memory 19 | self._modules = modules 20 | self.atexit = [] 21 | 22 | modules.add_symbol_hook('__system_property_get', hooker.write_function(self.system_property_get) + 1) 23 | modules.add_symbol_hook('__android_log_print', hooker.write_function(self.android_log_print) + 1) 24 | modules.add_symbol_hook('dlopen', hooker.write_function(self.dlopen) + 1) 25 | modules.add_symbol_hook('dlclose', hooker.write_function(self.dlclose) + 1) 26 | modules.add_symbol_hook('dladdr', hooker.write_function(self.dladdr) + 1) 27 | modules.add_symbol_hook('dlsym', hooker.write_function(self.dlsym) + 1) 28 | modules.add_symbol_hook('vfprintf', hooker.write_function(self.vfprintf) + 1) 29 | modules.add_symbol_hook('pthread_create', hooker.write_function(self.nop('pthread_create')) + 1) 30 | modules.add_symbol_hook('pthread_join', hooker.write_function(self.nop('pthread_join')) + 1) 31 | modules.add_symbol_hook('fprintf', hooker.write_function(self.nop('fprintf')) + 1) 32 | modules.add_symbol_hook('dlerror', hooker.write_function(self.nop('dlerror')) + 1) 33 | 34 | @native_method 35 | def system_property_get(self, uc, name_ptr, buf_ptr): 36 | name = memory_helpers.read_utf8(uc, name_ptr) 37 | logger.debug("Called __system_property_get(%s, 0x%x)" % (name, buf_ptr)) 38 | 39 | if name in self._emu.system_properties: 40 | memory_helpers.write_utf8(uc, buf_ptr, self._emu.system_properties[name]) 41 | else: 42 | raise ValueError('%s was not found in system_properties dictionary.' % name) 43 | 44 | return None 45 | 46 | @native_method 47 | def android_log_print(self, uc, log_level, log_tag_ptr, log_format_ptr): 48 | params_count = len(locals()) 49 | log_tag = memory_helpers.read_utf8(uc, log_tag_ptr) 50 | fmt = memory_helpers.read_utf8(uc, log_format_ptr) 51 | 52 | args_type = [] 53 | args_count = 0 54 | i = 0 55 | while i < len(fmt): 56 | if fmt[i] == '%': 57 | if fmt[i+1] in ['s', 'd', 'p']: 58 | args_type.append(fmt[i+1]) 59 | args_count += 1 60 | i += 1 61 | i += 1 62 | 63 | other_args = native_read_args(uc, params_count - 2 + args_count)[params_count-2:] 64 | args = [] 65 | for i in range(args_count): 66 | if args_type[i] == 's': 67 | args.append(memory_helpers.read_utf8(uc, other_args[i])) 68 | elif args_type[i] == 'd' or args_type[i] == 'p': 69 | args.append(other_args[i]) 70 | 71 | # python not support %p format 72 | fmt = fmt.replace('%p', '0x%x') 73 | logger.debug("Called __android_log_print(%d, %s, %s)" % (log_level, log_tag, fmt % tuple(args))) 74 | 75 | return None 76 | 77 | @native_method 78 | def dlopen(self, uc, path): 79 | path = memory_helpers.read_utf8(uc, path) 80 | logger.debug("Called dlopen(%s)" % path) 81 | 82 | if path == 'libvendorconn.so': 83 | lib = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'libs', 'libvendorconn_32.so')) 84 | mod = self._emu.load_library(lib) 85 | 86 | return mod.base 87 | 88 | return None 89 | 90 | @native_method 91 | def dlclose(self, uc, handle): 92 | """ 93 | The function dlclose() decrements the reference count on the dynamic library handle handle. 94 | If the reference count drops to zero and no other loaded libraries use symbols in it, then the dynamic library is unloaded. 95 | """ 96 | logger.debug("Called dlclose(0x%x)" % handle) 97 | return 0 98 | 99 | @native_method 100 | def dladdr(self, uc, addr, info): 101 | logger.debug("Called dladdr(0x%x, 0x%x)" % (addr, info)) 102 | 103 | infos = memory_helpers.read_uints(uc, info, 4) 104 | Dl_info = {} 105 | 106 | isfind = False 107 | for mod in self._modules.modules: 108 | if mod.base <= addr < mod.base + mod.size: 109 | dli_fname = self._memory.allocate(len(mod.filename) + 1) 110 | memory_helpers.write_utf8(uc, dli_fname, mod.filename + '\x00') 111 | memory_helpers.write_uints(uc, addr, [dli_fname, mod.base, 0, 0]) 112 | return 1 113 | 114 | @native_method 115 | def dlsym(self, uc, handle, symbol): 116 | symbol_str = memory_helpers.read_utf8(uc, symbol) 117 | logger.debug("Called dlsym(0x%x, %s)" % (handle, symbol_str)) 118 | 119 | if handle == 0xffffffff: 120 | sym = self._modules.find_symbol_name(symbol_str) 121 | else: 122 | module = self._modules.find_module(handle) 123 | 124 | if module is None: 125 | raise Exception('Module not found for address 0x%x' % symbol) 126 | 127 | sym = module.find_symbol(symbol) 128 | 129 | if sym is None: 130 | return 0 131 | 132 | raise NotImplementedError 133 | 134 | @native_method 135 | def vfprintf(self, uc, FILE, format, va_list): 136 | # int vfprintf ( FILE * stream, const char * format, va_list arg ); 137 | struct_FILE = memory_helpers.read_byte_array(uc, FILE, 18) 138 | c_string = memory_helpers.read_utf8(uc, format) 139 | 140 | args = [] 141 | result_string = "" 142 | for i in range(0,len(c_string)): 143 | if c_string[i] == '%': 144 | if c_string[i+1] == "d": 145 | args.append(memory_helpers.read_uints(uc,va_list,1)[0]) 146 | elif c_string[i+1] == "c": 147 | args.append(chr(memory_helpers.read_byte_array(uc,va_list,1)[0])) 148 | elif c_string[i+1] == "s": 149 | s_addr = memory_helpers.read_ptr(uc, va_list) 150 | args.append(memory_helpers.read_cString(uc, s_addr)[0]) 151 | else: 152 | result_string += c_string[i:i+2] 153 | # TODO more format support 154 | va_list += 4 155 | result_string += "{0["+str(len(args)-1)+"]}" 156 | continue 157 | if i>=1: 158 | if c_string[i-1] == '%' or c_string[i] == '%': 159 | continue 160 | result_string += c_string[i] 161 | 162 | result_string = result_string.format(args) 163 | logger.debug("Called vfprintf(%r)" % result_string) 164 | 165 | 166 | def nop(self, name): 167 | @native_method 168 | def nop_inside(emu): 169 | raise NotImplementedError('Symbol hook not implemented %s' % name) 170 | return nop_inside 171 | -------------------------------------------------------------------------------- /src/androidemu/tracer.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from unicorn import * 4 | 5 | from androidemu.internal.modules import Modules 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | class Tracer: 11 | 12 | def __init__(self, uc: Uc, modules: Modules): 13 | self._uc = uc 14 | self._modules = modules 15 | 16 | def enable(self): 17 | self._uc.hook_add(UC_HOOK_BLOCK, self._hook_block) 18 | 19 | def _hook_block(self, uc: Uc, address, size, user_data): 20 | (name, symbol) = self._modules.find_symbol(address | 1) 21 | 22 | if symbol is not None: 23 | print(name) 24 | -------------------------------------------------------------------------------- /src/androidemu/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/src/androidemu/utils/__init__.py -------------------------------------------------------------------------------- /src/androidemu/utils/memory_helpers.py: -------------------------------------------------------------------------------- 1 | import hexdump 2 | import struct 3 | 4 | 5 | def hex_dump(uc, address, size): 6 | data = uc.mem_read(address, size) 7 | return hexdump.hexdump(data) 8 | 9 | 10 | def read_ptr(uc, address): 11 | return int.from_bytes(uc.mem_read(address, 4), byteorder='little') 12 | 13 | 14 | def read_byte_array(uc, address, size): 15 | return uc.mem_read(address, size) 16 | 17 | 18 | def read_utf8(uc, address): 19 | buffer_address = address 20 | buffer_read_size = 32 21 | buffer = b"" 22 | null_pos = None 23 | 24 | # Keep reading until we read something that contains a null terminator. 25 | while null_pos is None: 26 | buf_read = uc.mem_read(buffer_address, buffer_read_size) 27 | if b'\x00' in buf_read: 28 | null_pos = len(buffer) + buf_read.index(b'\x00') 29 | buffer += buf_read 30 | buffer_address += buffer_read_size 31 | 32 | return buffer[:null_pos].decode("utf-8") 33 | 34 | 35 | def read_cString(uc, address): 36 | # read string null-terminated, return string and length 37 | buffer_address = address 38 | buffer_read_size = 1 39 | buffer = b"" 40 | null_pos = None 41 | 42 | while null_pos is None: 43 | buf_read = uc.mem_read(buffer_address, buffer_read_size) 44 | if b'\x00' in buf_read: 45 | null_pos = len(buffer) + buf_read.index(b'\x00') 46 | buffer += buf_read 47 | buffer_address += buffer_read_size 48 | 49 | return buffer[:null_pos].decode("utf-8"),null_pos 50 | 51 | 52 | def read_uints(uc, address, num=1): 53 | data = uc.mem_read(address, num * 4) 54 | return struct.unpack("I" * num, data) 55 | 56 | 57 | def write_utf8(uc, address, value): 58 | uc.mem_write(address, value.encode(encoding="utf-8") + b"\x00") 59 | 60 | 61 | def write_uints(uc, address, num): 62 | l = [] 63 | if not isinstance(num, list): 64 | l = [num] 65 | else: 66 | l = num 67 | 68 | for v in l: 69 | uc.mem_write(address, int(v).to_bytes(4, byteorder='little')) 70 | address += 4 71 | -------------------------------------------------------------------------------- /src/androidemu/vfs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/src/androidemu/vfs/__init__.py -------------------------------------------------------------------------------- /src/androidemu/vfs/file_helpers.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import pathlib 4 | from os import stat_result 5 | 6 | from unicorn import Uc 7 | 8 | from androidemu.config import WRITE_FSTAT_TIMES 9 | 10 | 11 | def stat64(path: pathlib.Path): 12 | meta_path = path.with_suffix(".meta_emu") 13 | 14 | if not os.path.exists(meta_path): 15 | meta_path_dir = os.path.dirname(meta_path) 16 | 17 | if not os.path.isdir(meta_path_dir): 18 | os.makedirs(meta_path_dir) 19 | 20 | with open(meta_path, 'w') as f: 21 | json.dump({ 22 | 'st_dev': 0, 23 | '__st_ino': 0, 24 | 'st_mode': 0, 25 | 'st_nlink': 0, 26 | 'st_uid': 0, 27 | 'st_gid': 0, 28 | 'st_rdev': 0, 29 | 'st_size': 0, 30 | 'st_blksize': 0, 31 | 'st_blocks': 0, 32 | 'st_atime': 0, 33 | 'st_atime_ns': 0, 34 | 'st_mtime': 0, 35 | 'st_mtime_ns': 0, 36 | 'st_ctime': 0, 37 | 'st_ctime_ns': 0, 38 | 'st_ino': 0 39 | }, fp=f, indent=4) 40 | 41 | with open(meta_path, 'r') as f: 42 | return json.load(fp=f) 43 | 44 | 45 | def stat_to_memory(uc: Uc, buf_ptr, stat, write_times): 46 | uc.mem_write(buf_ptr, stat['st_dev'].to_bytes(8, byteorder='little')) 47 | uc.mem_write(buf_ptr + 8, int(0).to_bytes(4, byteorder='little')) # PAD 4 48 | uc.mem_write(buf_ptr + 12, stat['__st_ino'].to_bytes(4, byteorder='little')) 49 | uc.mem_write(buf_ptr + 16, stat['st_mode'].to_bytes(4, byteorder='little')) 50 | uc.mem_write(buf_ptr + 20, stat['st_nlink'].to_bytes(4, byteorder='little')) 51 | uc.mem_write(buf_ptr + 24, stat['st_uid'].to_bytes(4, byteorder='little')) 52 | uc.mem_write(buf_ptr + 28, stat['st_gid'].to_bytes(4, byteorder='little')) 53 | uc.mem_write(buf_ptr + 32, stat['st_rdev'].to_bytes(8, byteorder='little')) 54 | uc.mem_write(buf_ptr + 40, int(0).to_bytes(4, byteorder='little')) # PAD 4 55 | uc.mem_write(buf_ptr + 44, int(0).to_bytes(4, byteorder='little')) # PAD 4 56 | uc.mem_write(buf_ptr + 48, stat['st_size'].to_bytes(8, byteorder='little')) 57 | uc.mem_write(buf_ptr + 56, stat['st_blksize'].to_bytes(4, byteorder='little')) 58 | uc.mem_write(buf_ptr + 60, int(0).to_bytes(4, byteorder='little')) # PAD 4 59 | uc.mem_write(buf_ptr + 64, stat['st_blocks'].to_bytes(8, byteorder='little')) 60 | 61 | if write_times: 62 | uc.mem_write(buf_ptr + 72, stat['st_atime'].to_bytes(4, byteorder='little')) 63 | uc.mem_write(buf_ptr + 76, stat['st_atime_ns'].to_bytes(4, byteorder='little')) 64 | uc.mem_write(buf_ptr + 80, stat['st_mtime'].to_bytes(4, byteorder='little')) 65 | uc.mem_write(buf_ptr + 84, stat['st_mtime_ns'].to_bytes(4, byteorder='little')) 66 | uc.mem_write(buf_ptr + 88, stat['st_ctime'].to_bytes(4, byteorder='little')) 67 | uc.mem_write(buf_ptr + 92, stat['st_ctime_ns'].to_bytes(4, byteorder='little')) 68 | else: 69 | uc.mem_write(buf_ptr + 72, int(0).to_bytes(4, byteorder='little')) 70 | uc.mem_write(buf_ptr + 76, int(0).to_bytes(4, byteorder='little')) 71 | uc.mem_write(buf_ptr + 80, int(0).to_bytes(4, byteorder='little')) 72 | uc.mem_write(buf_ptr + 84, int(0).to_bytes(4, byteorder='little')) 73 | uc.mem_write(buf_ptr + 88, int(0).to_bytes(4, byteorder='little')) 74 | uc.mem_write(buf_ptr + 92, int(0).to_bytes(4, byteorder='little')) 75 | 76 | uc.mem_write(buf_ptr + 96, stat['st_ino'].to_bytes(8, byteorder='little')) 77 | -------------------------------------------------------------------------------- /src/androidemu/vfs/file_system.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import pathlib 4 | import posixpath 5 | 6 | from unicorn import UC_HOOK_MEM_UNMAPPED, UC_HOOK_MEM_WRITE, UC_HOOK_MEM_READ, UC_HOOK_BLOCK 7 | 8 | from androidemu.config import WRITE_FSTAT_TIMES 9 | from androidemu.cpu.syscall_handlers import SyscallHandlers 10 | from androidemu.utils import memory_helpers 11 | from androidemu.vfs import file_helpers 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | OVERRIDE_URANDOM = False 16 | OVERRIDE_URANDOM_BYTE = b"\x00" 17 | 18 | 19 | class VirtualFile: 20 | 21 | def __init__(self, name, file_descriptor, name_virt=None): 22 | self.name = name 23 | self.name_virt = name_virt 24 | self.descriptor = file_descriptor 25 | 26 | 27 | class VirtualFileSystem: 28 | 29 | def __init__(self, root_path: str, syscall_handler: SyscallHandlers): 30 | self._root_path = pathlib.Path(root_path).resolve() 31 | 32 | # TODO: Improve fd logic. 33 | self._file_descriptor_counter = 3 34 | self._file_descriptors = dict() 35 | self._file_descriptors[0] = VirtualFile('stdin', 0) 36 | self._file_descriptors[1] = VirtualFile('stdout', 1) 37 | self._file_descriptors[2] = VirtualFile('stderr', 2) 38 | 39 | syscall_handler.set_handler(0x3, "read", 3, self._handle_read) 40 | syscall_handler.set_handler(0x5, "open", 3, self._handle_open) 41 | syscall_handler.set_handler(0x6, "close", 1, self._handle_close) 42 | syscall_handler.set_handler(0x13, "lseek", 3, self._handle_lseek) 43 | syscall_handler.set_handler(0x21, "access", 2, self._handle_access) 44 | syscall_handler.set_handler(0x92, "writev", 3, self._handle_writev) 45 | syscall_handler.set_handler(0xC3, "stat64", 2, self._handle_stat64) 46 | syscall_handler.set_handler(0xC5, "fstat64", 2, self._handle_fstat64) 47 | syscall_handler.set_handler(0x142, "openat", 4, self._handle_openat) 48 | syscall_handler.set_handler(0x147, "fstatat64", 4, self._handle_fstatat64) 49 | 50 | def translate_path(self, filename) -> pathlib.Path: 51 | if filename.startswith("/"): 52 | filename = filename[1:] 53 | 54 | if os.name == 'nt': 55 | filename = filename.replace(':', '_') 56 | 57 | file_path = self._root_path.joinpath(filename).resolve() 58 | 59 | # is_relative_to is new in version 3.9 60 | if not hasattr(file_path, 'is_relative_to'): 61 | try: 62 | file_path.relative_to(self._root_path) 63 | except: 64 | # if file_path is not in the subpath of self._root_path, ValueError is raised 65 | raise RuntimeError("Emulator tried to read outside vfs ('%s' not in '%s')." % (file_path, self._root_path)) 66 | else: 67 | if not file_path.is_relative_to(self._root_path): 68 | raise RuntimeError("Emulator tried to read outside vfs ('%s' not in '%s')." % (file_path, self._root_path)) 69 | 70 | return file_path 71 | 72 | def _store_fd(self, name, name_virt, file_descriptor): 73 | next_fd = self._file_descriptor_counter 74 | self._file_descriptor_counter += 1 75 | self._file_descriptors[next_fd] = VirtualFile(name, file_descriptor, name_virt=name_virt) 76 | return next_fd 77 | 78 | def _open_file(self, filename): 79 | # Special cases, such as /dev/urandom. 80 | orig_filename = filename 81 | 82 | if filename == '/dev/urandom': 83 | logger.info("File opened '%s'" % filename) 84 | return self._store_fd('/dev/urandom', None, 'urandom') 85 | 86 | file_path = self.translate_path(filename) 87 | 88 | if os.path.isfile(file_path): 89 | logger.info("File opened '%s'" % orig_filename) 90 | flags = os.O_RDWR 91 | if hasattr(os, "O_BINARY"): 92 | flags |= os.O_BINARY 93 | return self._store_fd(orig_filename, file_path, os.open(file_path, flags=flags)) 94 | else: 95 | logger.warning("File does not exist '%s'" % orig_filename) 96 | return -1 97 | 98 | def _handle_read(self, uc, fd, buf_addr, count): 99 | """ 100 | ssize_t read(int fd, void *buf, size_t count); 101 | 102 | On files that support seeking, the read operation commences at the current file offset, and the file offset 103 | is incremented by the number of bytes read. If the current file offset is at or past the end of file, 104 | no bytes are read, and read() returns zero. 105 | 106 | If count is zero, read() may detect the errors described below. In the absence of any errors, or if read() 107 | does not check for errors, a read() with a count of 0 returns zero and has no other effects. 108 | 109 | If count is greater than SSIZE_MAX, the result is unspecified. 110 | """ 111 | if fd <= 2: 112 | raise NotImplementedError("Unsupported read operation for file descriptor %d." % fd) 113 | 114 | if fd not in self._file_descriptors: 115 | logger.warning("No such file descriptor index %s in VirtualFileSystem" % fd) 116 | uc.emu_stop() 117 | 118 | file = self._file_descriptors[fd] 119 | 120 | logger.info("Reading %d bytes from '%s'" % (count, file.name)) 121 | 122 | if file.descriptor == 'urandom': 123 | if OVERRIDE_URANDOM: 124 | buf = OVERRIDE_URANDOM_BYTE * count 125 | else: 126 | buf = os.urandom(count) 127 | else: 128 | buf = os.read(file.descriptor, count) 129 | 130 | result = len(buf) 131 | uc.mem_write(buf_addr, buf) 132 | return result 133 | 134 | def _handle_open(self, uc, filename_ptr, flags, mode): 135 | """ 136 | int open(const char *pathname, int flags, mode_t mode); 137 | 138 | return the new file descriptor, or -1 if an error occurred (in which case, errno is set appropriately). 139 | """ 140 | filename = memory_helpers.read_utf8(uc, filename_ptr) 141 | 142 | return self._open_file(filename) 143 | 144 | def _handle_close(self, uc, fd): 145 | """ 146 | int close(int fd); 147 | 148 | close() closes a file descriptor, so that it no longer refers to any file and may be reused. Any record locks 149 | (see fcntl(2)) held on the file it was associated with, and owned by the process, are removed (regardless of 150 | the file descriptor that was used to obtain the lock). 151 | 152 | close() returns zero on success. On error, -1 is returned, and errno is set appropriately. 153 | """ 154 | if fd not in self._file_descriptors: 155 | return 0 156 | 157 | file = self._file_descriptors[fd] 158 | 159 | if fd <= 2: 160 | # if file.name == 'stdin': 161 | # uc.hook_add(UC_HOOK_BLOCK, debug_utils.hook_block) 162 | logger.info("File closed '%s'" % file.name) 163 | return 0 164 | 165 | if file.descriptor != 'urandom': 166 | logger.info("File closed '%s'" % file.name) 167 | os.close(file.descriptor) 168 | else: 169 | logger.info("File closed '%s'" % '/dev/urandom') 170 | 171 | return 0 172 | 173 | def _handle_lseek(self, uc, fd, offset, whence): 174 | if fd not in self._file_descriptors: 175 | raise NotImplementedError("Unknown fd") 176 | 177 | file = self._file_descriptors[fd] 178 | 179 | return os.lseek(file.descriptor, offset, whence) 180 | 181 | def _handle_access(self, uc, filename_ptr, flags): 182 | filename = memory_helpers.read_utf8(uc, filename_ptr) 183 | filename_virt = self.translate_path(filename) 184 | 185 | logger.warning("Path '%s' exists %s" % (filename, os.path.isfile(filename_virt))) 186 | 187 | if os.path.isfile(filename_virt): 188 | return 0 189 | 190 | return -1 191 | 192 | def _handle_writev(self, uc, fd, vec, vlen): 193 | if fd == 2: 194 | for i in range(0, vlen): 195 | addr = memory_helpers.read_ptr(uc, (i * 8) + vec) 196 | size = memory_helpers.read_ptr(uc, (i * 8) + vec + 4) 197 | data = bytes(uc.mem_read(addr, size)).decode(encoding='UTF-8') 198 | 199 | logger.error('Writev %s' % data) 200 | 201 | return 0 202 | 203 | raise NotImplementedError() 204 | 205 | def _handle_stat64(self, uc, filename_ptr, buf_ptr): 206 | filename = memory_helpers.read_utf8(uc, filename_ptr) 207 | 208 | logger.info("File stat64 '%s'" % filename) 209 | 210 | pathname = self.translate_path(filename) 211 | 212 | if not os.path.exists(pathname): 213 | logger.warning('> File was not found.') 214 | return -1 215 | 216 | logger.warning('> File was found.') 217 | 218 | # stat = file_helpers.stat64(path=pathname) 219 | # stat = os.stat(path=file_path, dir_fd=None, follow_symlinks=False) 220 | # file_helpers.stat_to_memory(uc, buf_ptr, stat, WRITE_FSTAT_TIMES) 221 | 222 | return 0 223 | 224 | def _handle_fstat64(self, uc, fd, buf_ptr): 225 | """ 226 | These functions return information about a file. No permissions are required on the file itself, but-in the 227 | case of stat() and lstat() - execute (search) permission is required on all of the directories in path that 228 | lead to the file. 229 | 230 | fstat() is identical to stat(), except that the file to be stat-ed is specified by the file descriptor fd. 231 | """ 232 | if fd not in self._file_descriptors: 233 | return -1 234 | 235 | file = self._file_descriptors[fd] 236 | logger.info("File stat64 '%s'" % file.name) 237 | 238 | stat = file_helpers.stat64(file.name_virt) 239 | # stat = os.fstat(file.descriptor) 240 | file_helpers.stat_to_memory(uc, buf_ptr, stat, WRITE_FSTAT_TIMES) 241 | 242 | return 0 243 | 244 | def _handle_openat(self, uc, dfd, filename_ptr, flags, mode): 245 | """ 246 | int openat(int dirfd, const char *pathname, int flags, mode_t mode); 247 | 248 | On success, openat() returns a new file descriptor. 249 | On error, -1 is returned and errno is set to indicate the error. 250 | 251 | EBADF 252 | dirfd is not a valid file descriptor. 253 | ENOTDIR 254 | pathname is relative and dirfd is a file descriptor referring to a file other than a directory. 255 | """ 256 | filename = memory_helpers.read_utf8(uc, filename_ptr) 257 | 258 | if not filename.startswith("/") and dfd != 0: 259 | raise NotImplementedError("Directory file descriptor has not been implemented yet.") 260 | 261 | return self._open_file(filename) 262 | 263 | def _handle_fstatat64(self, uc, dirfd, pathname_ptr, buf, flags): 264 | """ 265 | int fstatat(int dirfd, const char *pathname, struct stat *buf, int flags); 266 | 267 | If the pathname given in pathname is relative, then it is interpreted relative to the directory referred 268 | to by the file descriptor dirfd (rather than relative to the current working directory of the calling process, 269 | as is done by stat(2) for a relative pathname). 270 | 271 | If pathname is relative and dirfd is the special value AT_FDCWD, 272 | then pathname is interpreted relative to the current working directory of the calling process (like stat(2)). 273 | 274 | If pathname is absolute, then dirfd is ignored. 275 | 276 | flags can either be 0, or include one or more of the following flags .. 277 | 278 | On success, fstatat() returns 0. On error, -1 is returned and errno is set to indicate the error. 279 | """ 280 | pathname = memory_helpers.read_utf8(uc, pathname_ptr) 281 | 282 | if not pathname.startswith('/'): 283 | raise NotImplementedError("Directory file descriptor has not been implemented yet.") 284 | 285 | if not flags == 0: 286 | if flags & 0x100: # AT_SYMLINK_NOFOLLOW 287 | pass 288 | if flags & 0x800: # AT_NO_AUTOMOUNT 289 | pass 290 | # raise NotImplementedError("Flags has not been implemented yet.") 291 | 292 | logger.info("File fstatat64 '%s'" % pathname) 293 | pathname = self.translate_path(pathname) 294 | 295 | if not os.path.exists(pathname): 296 | logger.warning('> File was not found.') 297 | return -1 298 | 299 | logger.warning('> File was found.') 300 | 301 | stat = file_helpers.stat64(path=pathname) 302 | # stat = os.stat(path=file_path, dir_fd=None, follow_symlinks=False) 303 | file_helpers.stat_to_memory(uc, buf, stat, WRITE_FSTAT_TIMES) 304 | 305 | return 0 306 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/tests/__init__.py -------------------------------------------------------------------------------- /tests/memory/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/tests/memory/__init__.py -------------------------------------------------------------------------------- /tests/memory/test_heap_allocator.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from androidemu.memory.allocator_heap import HeapAllocator 4 | 5 | HEAP_START = 0x1000 6 | HEAP_END = 0x2000 7 | 8 | 9 | class TestHeapAllocator(unittest.TestCase): 10 | 11 | def test_allocate(self): 12 | heap = HeapAllocator(HEAP_START, HEAP_END) 13 | 14 | self.assertEqual(HEAP_START, heap.allocate(32)) 15 | self.assertEqual(HEAP_START + 32, heap.allocate(32)) 16 | self.assertEqual(HEAP_START + 64, heap.allocate(32)) 17 | 18 | def test_allocate_with_simple_free(self): 19 | heap = HeapAllocator(HEAP_START, HEAP_END) 20 | 21 | self.assertEqual(HEAP_START, heap.allocate(32)) 22 | self.assertEqual(HEAP_START + 32, heap.allocate(32)) 23 | self.assertEqual(HEAP_START + 64, heap.allocate(32)) 24 | 25 | # Free block in the middle. 26 | heap.free(HEAP_START + 32) 27 | 28 | # Expect allocation to take the middle block. 29 | self.assertEqual(HEAP_START + 32, heap.allocate(32)) 30 | 31 | # Expect allocation to create a new block. 32 | self.assertEqual(HEAP_START + 96, heap.allocate(32)) 33 | 34 | def test_allocate_with_merge(self): 35 | heap = HeapAllocator(HEAP_START, HEAP_END) 36 | 37 | self.assertEqual(HEAP_START, heap.allocate(32)) 38 | self.assertEqual(HEAP_START + 32, heap.allocate(32)) # Free 39 | self.assertEqual(HEAP_START + 64, heap.allocate(32)) # Free 40 | self.assertEqual(HEAP_START + 96, heap.allocate(32)) 41 | 42 | # Free two blocks in the middle. 43 | heap.free(HEAP_START + 32) 44 | heap.free(HEAP_START + 64) 45 | 46 | # Expect allocation to take the space in the middle. 47 | self.assertEqual(HEAP_START + 32, heap.allocate(64)) 48 | 49 | # Expect allocation to create a new block. 50 | self.assertEqual(HEAP_START + 128, heap.allocate(32)) 51 | 52 | # Free all blocks. 53 | heap.free(HEAP_START) 54 | heap.free(HEAP_START + 32) 55 | heap.free(HEAP_START + 96) 56 | heap.free(HEAP_START + 128) 57 | 58 | # Expect allocation to take the start. 59 | self.assertEqual(HEAP_START, heap.allocate(32)) 60 | 61 | def test_allocate_with_split(self): 62 | heap = HeapAllocator(HEAP_START, HEAP_END) 63 | 64 | self.assertEqual(HEAP_START, heap.allocate(32)) 65 | self.assertEqual(HEAP_START + 32, heap.allocate(32)) 66 | self.assertEqual(HEAP_START + 64, heap.allocate(32)) 67 | 68 | # Free block in the middle. 69 | heap.free(HEAP_START + 32) 70 | 71 | # Expect allocation to take the middle block. 72 | self.assertEqual(HEAP_START + 32, heap.allocate(16)) 73 | 74 | # Expect allocation to take the middle block. 75 | self.assertEqual(HEAP_START + 48, heap.allocate(16)) 76 | 77 | # Expect allocation to create a new block. 78 | self.assertEqual(HEAP_START + 96, heap.allocate(32)) 79 | -------------------------------------------------------------------------------- /tests/test_binaries/32/test_native.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AeonLucid/AndroidNativeEmu/37de28d4a8404b1ad737fed049ab4aaac401551e/tests/test_binaries/32/test_native.so -------------------------------------------------------------------------------- /tests/test_native.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import sys 4 | import unittest 5 | 6 | from androidemu.emulator import Emulator 7 | 8 | logging.basicConfig( 9 | stream=sys.stdout, 10 | level=logging.DEBUG, 11 | format="%(asctime)s %(levelname)7s %(name)34s | %(message)s" 12 | ) 13 | 14 | dir_samples = os.path.join(os.path.dirname(__file__), "..", "examples") 15 | 16 | 17 | class TestNative(unittest.TestCase): 18 | 19 | def testOneArg(self): 20 | # Initialize emulator 21 | emulator = Emulator( 22 | vfp_inst_set=True, 23 | vfs_root=os.path.join(dir_samples, "vfs") 24 | ) 25 | 26 | emulator.load_library(os.path.join(dir_samples, "example_binaries", "32", "libdl.so")) 27 | emulator.load_library(os.path.join(dir_samples, "example_binaries", "32", "libc.so")) 28 | emulator.load_library(os.path.join(dir_samples, "example_binaries", "32", "libstdc++.so")) 29 | module = emulator.load_library(os.path.join(os.path.dirname(__file__), "test_binaries", "32", "test_native.so")) 30 | 31 | res = emulator.call_symbol(module, 'Java_com_aeonlucid_nativetesting_MainActivity_testOneArg', emulator.java_vm.jni_env.address_ptr, 0x00, 'Hello') 32 | 33 | self.assertEqual('Hello', res) 34 | 35 | def testSixArg(self): 36 | # Initialize emulator 37 | emulator = Emulator( 38 | vfp_inst_set=True, 39 | vfs_root=os.path.join(dir_samples, "vfs") 40 | ) 41 | 42 | emulator.load_library(os.path.join(dir_samples, "example_binaries", "32", "libdl.so")) 43 | emulator.load_library(os.path.join(dir_samples, "example_binaries", "32", "libc.so")) 44 | emulator.load_library(os.path.join(dir_samples, "example_binaries", "32", "libstdc++.so")) 45 | module = emulator.load_library(os.path.join(os.path.dirname(__file__), "test_binaries", "32", "test_native.so")) 46 | 47 | res = emulator.call_symbol(module, 'Java_com_aeonlucid_nativetesting_MainActivity_testSixArg', emulator.java_vm.jni_env.address_ptr, 0x00, 'One', 'Two', 'Three', 'Four', 'Five', 'Six') 48 | 49 | self.assertEqual('OneTwoThreeFourFiveSix', res) 50 | -------------------------------------------------------------------------------- /tools/gen_jni_env.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def convert(name): 5 | s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) 6 | return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() 7 | 8 | 9 | table = """NULL, 10 | NULL, 11 | NULL, 12 | NULL, 13 | GetVersion, 14 | 15 | DefineClass, 16 | FindClass, 17 | 18 | FromReflectedMethod, 19 | FromReflectedField, 20 | ToReflectedMethod, 21 | 22 | GetSuperclass, 23 | IsAssignableFrom, 24 | 25 | ToReflectedField, 26 | 27 | Throw, 28 | ThrowNew, 29 | ExceptionOccurred, 30 | ExceptionDescribe, 31 | ExceptionClear, 32 | FatalError, 33 | 34 | PushLocalFrame, 35 | PopLocalFrame, 36 | 37 | NewGlobalRef, 38 | DeleteGlobalRef, 39 | DeleteLocalRef, 40 | IsSameObject, 41 | NewLocalRef, 42 | EnsureLocalCapacity, 43 | 44 | AllocObject, 45 | NewObject, 46 | NewObjectV, 47 | NewObjectA, 48 | 49 | GetObjectClass, 50 | IsInstanceOf, 51 | 52 | GetMethodID, 53 | 54 | CallObjectMethod, 55 | CallObjectMethodV, 56 | CallObjectMethodA, 57 | CallBooleanMethod, 58 | CallBooleanMethodV, 59 | CallBooleanMethodA, 60 | CallByteMethod, 61 | CallByteMethodV, 62 | CallByteMethodA, 63 | CallCharMethod, 64 | CallCharMethodV, 65 | CallCharMethodA, 66 | CallShortMethod, 67 | CallShortMethodV, 68 | CallShortMethodA, 69 | CallIntMethod, 70 | CallIntMethodV, 71 | CallIntMethodA, 72 | CallLongMethod, 73 | CallLongMethodV, 74 | CallLongMethodA, 75 | CallFloatMethod, 76 | CallFloatMethodV, 77 | CallFloatMethodA, 78 | CallDoubleMethod, 79 | CallDoubleMethodV, 80 | CallDoubleMethodA, 81 | CallVoidMethod, 82 | CallVoidMethodV, 83 | CallVoidMethodA, 84 | 85 | CallNonvirtualObjectMethod, 86 | CallNonvirtualObjectMethodV, 87 | CallNonvirtualObjectMethodA, 88 | CallNonvirtualBooleanMethod, 89 | CallNonvirtualBooleanMethodV, 90 | CallNonvirtualBooleanMethodA, 91 | CallNonvirtualByteMethod, 92 | CallNonvirtualByteMethodV, 93 | CallNonvirtualByteMethodA, 94 | CallNonvirtualCharMethod, 95 | CallNonvirtualCharMethodV, 96 | CallNonvirtualCharMethodA, 97 | CallNonvirtualShortMethod, 98 | CallNonvirtualShortMethodV, 99 | CallNonvirtualShortMethodA, 100 | CallNonvirtualIntMethod, 101 | CallNonvirtualIntMethodV, 102 | CallNonvirtualIntMethodA, 103 | CallNonvirtualLongMethod, 104 | CallNonvirtualLongMethodV, 105 | CallNonvirtualLongMethodA, 106 | CallNonvirtualFloatMethod, 107 | CallNonvirtualFloatMethodV, 108 | CallNonvirtualFloatMethodA, 109 | CallNonvirtualDoubleMethod, 110 | CallNonvirtualDoubleMethodV, 111 | CallNonvirtualDoubleMethodA, 112 | CallNonvirtualVoidMethod, 113 | CallNonvirtualVoidMethodV, 114 | CallNonvirtualVoidMethodA, 115 | 116 | GetFieldID, 117 | 118 | GetObjectField, 119 | GetBooleanField, 120 | GetByteField, 121 | GetCharField, 122 | GetShortField, 123 | GetIntField, 124 | GetLongField, 125 | GetFloatField, 126 | GetDoubleField, 127 | SetObjectField, 128 | SetBooleanField, 129 | SetByteField, 130 | SetCharField, 131 | SetShortField, 132 | SetIntField, 133 | SetLongField, 134 | SetFloatField, 135 | SetDoubleField, 136 | 137 | GetStaticMethodID, 138 | 139 | CallStaticObjectMethod, 140 | CallStaticObjectMethodV, 141 | CallStaticObjectMethodA, 142 | CallStaticBooleanMethod, 143 | CallStaticBooleanMethodV, 144 | CallStaticBooleanMethodA, 145 | CallStaticByteMethod, 146 | CallStaticByteMethodV, 147 | CallStaticByteMethodA, 148 | CallStaticCharMethod, 149 | CallStaticCharMethodV, 150 | CallStaticCharMethodA, 151 | CallStaticShortMethod, 152 | CallStaticShortMethodV, 153 | CallStaticShortMethodA, 154 | CallStaticIntMethod, 155 | CallStaticIntMethodV, 156 | CallStaticIntMethodA, 157 | CallStaticLongMethod, 158 | CallStaticLongMethodV, 159 | CallStaticLongMethodA, 160 | CallStaticFloatMethod, 161 | CallStaticFloatMethodV, 162 | CallStaticFloatMethodA, 163 | CallStaticDoubleMethod, 164 | CallStaticDoubleMethodV, 165 | CallStaticDoubleMethodA, 166 | CallStaticVoidMethod, 167 | CallStaticVoidMethodV, 168 | CallStaticVoidMethodA, 169 | 170 | GetStaticFieldID, 171 | 172 | GetStaticObjectField, 173 | GetStaticBooleanField, 174 | GetStaticByteField, 175 | GetStaticCharField, 176 | GetStaticShortField, 177 | GetStaticIntField, 178 | GetStaticLongField, 179 | GetStaticFloatField, 180 | GetStaticDoubleField, 181 | 182 | SetStaticObjectField, 183 | SetStaticBooleanField, 184 | SetStaticByteField, 185 | SetStaticCharField, 186 | SetStaticShortField, 187 | SetStaticIntField, 188 | SetStaticLongField, 189 | SetStaticFloatField, 190 | SetStaticDoubleField, 191 | 192 | NewString, 193 | 194 | GetStringLength, 195 | GetStringChars, 196 | ReleaseStringChars, 197 | 198 | NewStringUTF, 199 | GetStringUTFLength, 200 | GetStringUTFChars, 201 | ReleaseStringUTFChars, 202 | 203 | GetArrayLength, 204 | 205 | NewObjectArray, 206 | GetObjectArrayElement, 207 | SetObjectArrayElement, 208 | 209 | NewBooleanArray, 210 | NewByteArray, 211 | NewCharArray, 212 | NewShortArray, 213 | NewIntArray, 214 | NewLongArray, 215 | NewFloatArray, 216 | NewDoubleArray, 217 | 218 | GetBooleanArrayElements, 219 | GetByteArrayElements, 220 | GetCharArrayElements, 221 | GetShortArrayElements, 222 | GetIntArrayElements, 223 | GetLongArrayElements, 224 | GetFloatArrayElements, 225 | GetDoubleArrayElements, 226 | 227 | ReleaseBooleanArrayElements, 228 | ReleaseByteArrayElements, 229 | ReleaseCharArrayElements, 230 | ReleaseShortArrayElements, 231 | ReleaseIntArrayElements, 232 | ReleaseLongArrayElements, 233 | ReleaseFloatArrayElements, 234 | ReleaseDoubleArrayElements, 235 | 236 | GetBooleanArrayRegion, 237 | GetByteArrayRegion, 238 | GetCharArrayRegion, 239 | GetShortArrayRegion, 240 | GetIntArrayRegion, 241 | GetLongArrayRegion, 242 | GetFloatArrayRegion, 243 | GetDoubleArrayRegion, 244 | SetBooleanArrayRegion, 245 | SetByteArrayRegion, 246 | SetCharArrayRegion, 247 | SetShortArrayRegion, 248 | SetIntArrayRegion, 249 | SetLongArrayRegion, 250 | SetFloatArrayRegion, 251 | SetDoubleArrayRegion, 252 | 253 | RegisterNatives, 254 | UnregisterNatives, 255 | 256 | MonitorEnter, 257 | MonitorExit, 258 | 259 | GetJavaVM, 260 | 261 | GetStringRegion, 262 | GetStringUTFRegion, 263 | 264 | GetPrimitiveArrayCritical, 265 | ReleasePrimitiveArrayCritical, 266 | 267 | GetStringCritical, 268 | ReleaseStringCritical, 269 | 270 | NewWeakGlobalRef, 271 | DeleteWeakGlobalRef, 272 | 273 | ExceptionCheck, 274 | 275 | NewDirectByteBuffer, 276 | GetDirectBufferAddress, 277 | GetDirectBufferCapacity, 278 | 279 | GetObjectRefType""" 280 | 281 | index = 0 282 | functions = dict() 283 | 284 | for entry in table.split("\n"): 285 | entry = entry.strip() 286 | 287 | if len(entry) == 0: 288 | continue 289 | 290 | if entry.endswith(","): 291 | entry = entry[:-1] 292 | 293 | func_index = index 294 | func_name = convert(entry) 295 | index += 1 296 | 297 | if entry == 'NULL': 298 | continue 299 | 300 | functions[func_index] = func_name 301 | 302 | # write_function_table entries 303 | for (index, name) in functions.items(): 304 | print(" %d: self.%s," % (index, name)) 305 | 306 | # write functions 307 | for (index, name) in functions.items(): 308 | print(""" 309 | @native_method 310 | def %s(self, uc, env): 311 | raise NotImplementedError()""" % name) 312 | --------------------------------------------------------------------------------