├── .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 | [](https://github.com/AeonLucid/AndroidNativeEmu/actions)
4 | [](https://pypi.org/project/androidemu/)
5 | 
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 |
--------------------------------------------------------------------------------