From 99f2d563680c7203cacba6f6a0077141c597a4e9 Mon Sep 17 00:00:00 2001 From: CAI Kelun Date: Sat, 14 Nov 2020 22:47:08 +0800 Subject: [PATCH] first commit --- .gitignore | 11 + CONTRIBUTING.md | 47 ++ LICENSE | 21 + LICENSE-docs | 393 ++++++++++++ README.md | 181 ++++++ README.zh-CN.md | 181 ++++++ build.gradle | 60 ++ gradle.properties | 19 + gradle/publish.gradle | 129 ++++ gradle/sanitizer.gradle | 60 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 +++++ gradlew.bat | 84 +++ settings.gradle | 3 + xdl_lib/.gitignore | 1 + xdl_lib/build.gradle | 40 ++ xdl_lib/src/main/AndroidManifest.xml | 5 + xdl_lib/src/main/cpp/CMakeLists.txt | 46 ++ xdl_lib/src/main/cpp/xdl.c | 605 ++++++++++++++++++ xdl_lib/src/main/cpp/xdl.h | 66 ++ xdl_lib/src/main/cpp/xdl.map.txt | 12 + xdl_lib/src/main/cpp/xdl_const.h | 46 ++ xdl_lib/src/main/cpp/xdl_iterate.c | 295 +++++++++ xdl_lib/src/main/cpp/xdl_iterate.h | 41 ++ xdl_lib/src/main/cpp/xdl_lzma.c | 176 +++++ xdl_lib/src/main/cpp/xdl_lzma.h | 40 ++ xdl_lib/src/main/cpp/xdl_util.c | 106 +++ xdl_lib/src/main/cpp/xdl_util.h | 45 ++ xdl_sample/.gitignore | 1 + xdl_sample/build.gradle | 48 ++ xdl_sample/src/main/AndroidManifest.xml | 22 + xdl_sample/src/main/cpp/CMakeLists.txt | 21 + xdl_sample/src/main/cpp/sample.c | 142 ++++ .../hexhacking/xdl/sample/MainActivity.java | 20 + .../xdl/sample/MyCustomApplication.java | 16 + .../hexhacking/xdl/sample/NativeSample.java | 9 + .../drawable-v24/ic_launcher_foreground.xml | 30 + .../res/drawable/ic_launcher_background.xml | 170 +++++ .../src/main/res/layout/activity_main.xml | 47 ++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3593 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5339 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2636 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3388 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4926 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7472 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7909 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 11873 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10652 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 16570 bytes xdl_sample/src/main/res/values/colors.xml | 6 + xdl_sample/src/main/res/values/strings.xml | 3 + xdl_sample/src/main/res/values/themes.xml | 11 + 55 files changed, 3447 insertions(+) create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100755 LICENSE-docs create mode 100644 README.md create mode 100644 README.zh-CN.md create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/publish.gradle create mode 100644 gradle/sanitizer.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 xdl_lib/.gitignore create mode 100644 xdl_lib/build.gradle create mode 100644 xdl_lib/src/main/AndroidManifest.xml create mode 100644 xdl_lib/src/main/cpp/CMakeLists.txt create mode 100644 xdl_lib/src/main/cpp/xdl.c create mode 100644 xdl_lib/src/main/cpp/xdl.h create mode 100644 xdl_lib/src/main/cpp/xdl.map.txt create mode 100644 xdl_lib/src/main/cpp/xdl_const.h create mode 100644 xdl_lib/src/main/cpp/xdl_iterate.c create mode 100644 xdl_lib/src/main/cpp/xdl_iterate.h create mode 100644 xdl_lib/src/main/cpp/xdl_lzma.c create mode 100644 xdl_lib/src/main/cpp/xdl_lzma.h create mode 100644 xdl_lib/src/main/cpp/xdl_util.c create mode 100644 xdl_lib/src/main/cpp/xdl_util.h create mode 100644 xdl_sample/.gitignore create mode 100644 xdl_sample/build.gradle create mode 100644 xdl_sample/src/main/AndroidManifest.xml create mode 100644 xdl_sample/src/main/cpp/CMakeLists.txt create mode 100644 xdl_sample/src/main/cpp/sample.c create mode 100644 xdl_sample/src/main/java/io/hexhacking/xdl/sample/MainActivity.java create mode 100644 xdl_sample/src/main/java/io/hexhacking/xdl/sample/MyCustomApplication.java create mode 100644 xdl_sample/src/main/java/io/hexhacking/xdl/sample/NativeSample.java create mode 100644 xdl_sample/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 xdl_sample/src/main/res/drawable/ic_launcher_background.xml create mode 100644 xdl_sample/src/main/res/layout/activity_main.xml create mode 100644 xdl_sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 xdl_sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 xdl_sample/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 xdl_sample/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 xdl_sample/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 xdl_sample/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 xdl_sample/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 xdl_sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 xdl_sample/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 xdl_sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 xdl_sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 xdl_sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 xdl_sample/src/main/res/values/colors.xml create mode 100644 xdl_sample/src/main/res/values/strings.xml create mode 100644 xdl_sample/src/main/res/values/themes.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b3e9080 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.DS_Store + +build/ +.cxx/ +.gradle/ +.idea/ +*.iml +*.log +*.so +wrap.sh +local.properties \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..08fc969 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,47 @@ +# Contributing to xDL + +Welcome to the xDL project. Read on to learn more about our development process and how to propose bug fixes and improvements. + +## Issues + +We use GitHub issues to track public bugs and feature requests. Before creating an issue, please note the following: + +1. Please search existing issues before creating a new one. +2. Please ensure your description is clear and has sufficient instructions to be able to reproduce the issue. The more information the better. + + +## Branch Management + +There are 2 main branches: + +1. `master` branch + + * It's the latest (pre-)release branch. We use `master` for tags. + * **Please do NOT submit any PR on `master` branch.** + +2. `dev` branch + + * It's our stable developing branch. + * Once `dev` has passed our internal tests, it will be merged to `master` branch for the next release. + * **Please always submit PR on `dev` branch.** + + +## Pull Requests + +Please make sure the following is done when submitting a pull request: + +1. Fork the repo and create your branch from `master`. +2. Add the copyright notice to the top of any new files you've added. +3. Try your best to test your code. +4. Squash all of your commits into one meaningful commit. + + +## Code Style Guide + +1. 4 spaces for indentation rather than tabs. +2. Follow the C code style already in place. + + +## License + +By contributing to xDL, you agree that your contributions will be licensed under its [MIT LICENSE](LICENSE). diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9d8b7e6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-present, HexHacking Team. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSE-docs b/LICENSE-docs new file mode 100755 index 0000000..d21a91a --- /dev/null +++ b/LICENSE-docs @@ -0,0 +1,393 @@ +Attribution 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public licenses. +Notwithstanding, Creative Commons may elect to apply one of its public +licenses to material it publishes and in those instances will be +considered the "Licensor." Except for the limited purpose of indicating +that material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the public +licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/README.md b/README.md new file mode 100644 index 0000000..092cbee --- /dev/null +++ b/README.md @@ -0,0 +1,181 @@ +# xDL + +![](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat) +![](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat) +![](https://img.shields.io/badge/release-1.0.0-red.svg?style=flat) +![](https://img.shields.io/badge/Android-4.1%20--%2011-blue.svg?style=flat) +![](https://img.shields.io/badge/arch-armeabi--v7a%20%7C%20arm64--v8a%20%7C%20x86%20%7C%20x86__64-blue.svg?style=flat) + +xDL is an enhanced implementation of the Android DL series functions. + + +## Features + +* Enhanced `dlopen()` + `dlsym()` + `dladdr()`. + * Lookup all loaded ELF in the current process, including system libraries. + * Lookup dynamic link symbols in `.dynsym` + * Lookup debuging symbols in `.symtab` and "`.symtab` in `.gnu_debugdata`". +* Enhanced `dl_iterate_phdr()`. + * Compatible with Android 4.x on ARM32. + * Including linker / linker64 (for Android <= 8.x). + * Return full pathname instead of basename (for Android 5.x). + * Return app\_process32 / app\_process64 instead of package name. +* Support Android 4.1 - 11 (API level 16 - 30). +* Support armeabi-v7a, arm64-v8a, x86 and x86_64. +* MIT licensed. + + +## Artifacts Size + +| ABI | Compressed (KB) | Uncompressed (KB) | +| :---------- | --------------: | ----------------: | +| armeabi-v7a | 5.8 | 13.9 | +| arm64-v8a | 6.2 | 14.2 | +| x86 | 6.4 | 13.8 | +| x86_64 | 6.6 | 14.5 | + + +## Usage + +### 1. Add dependency + +```Gradle +dependencies { + implementation 'io.hexhacking.xdl:xdl-android-lib:1.0.0' +} +``` + +### 2. Specify one or more ABI(s) you need + +```Gradle +android { + defaultConfig { + ndk { + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' + } + } +} +``` + +### 3. Download header file + +Download the header file from [here](xdl_lib/src/main/cpp/xdl.h), pay attention to check the version number in the file. Then put the header file in the right place of your project. + +There is a sample app in the [xdl-sample](xdl_sample) folder you can refer to. + + +## API + +include xDL's header file: + +```c +#include "xdl.h" +``` + +### 1. `xdl_open()` and `xdl_close()` + +```c +void *xdl_open(const char *filename); +void xdl_close(void *handle); +``` + +They are very similar to `dlopen()` and `dlclose()`. But need to pay attention: `xdl_open()` can NOT actually "load" ELF from disk, it just "open" ELF that has been loaded into memory. + +`filename` can be basename or full pathname. However, Android linker has used the namespace mechanism since 8.0. If you pass basename, you need to make sure that no duplicate ELF is loaded into the current process. `xdl_open()` will only return the first matching ELF. Please consider this fragment of `/proc/self/maps` on Android 10: + +``` +756fc2c000-756fc7c000 r--p 00000000 fd:03 2985 /system/lib64/vndk-sp-29/libc++.so +756fc7c000-756fcee000 --xp 00050000 fd:03 2985 /system/lib64/vndk-sp-29/libc++.so +756fcee000-756fcef000 rw-p 000c2000 fd:03 2985 /system/lib64/vndk-sp-29/libc++.so +756fcef000-756fcf7000 r--p 000c3000 fd:03 2985 /system/lib64/vndk-sp-29/libc++.so +7571fdd000-757202d000 r--p 00000000 07:38 20 /apex/com.android.conscrypt/lib64/libc++.so +757202d000-757209f000 --xp 00050000 07:38 20 /apex/com.android.conscrypt/lib64/libc++.so +757209f000-75720a0000 rw-p 000c2000 07:38 20 /apex/com.android.conscrypt/lib64/libc++.so +75720a0000-75720a8000 r--p 000c3000 07:38 20 /apex/com.android.conscrypt/lib64/libc++.so +760b9df000-760ba2f000 r--p 00000000 fd:03 2441 /system/lib64/libc++.so +760ba2f000-760baa1000 --xp 00050000 fd:03 2441 /system/lib64/libc++.so +760baa1000-760baa2000 rw-p 000c2000 fd:03 2441 /system/lib64/libc++.so +760baa2000-760baaa000 r--p 000c3000 fd:03 2441 /system/lib64/libc++.so +``` + +### 2. `xdl_sym()` and `xdl_dsym()` + +```c +void *xdl_sym(void *handle, const char *symbol); +void *xdl_dsym(void *handle, const char *symbol); +``` + +They are very similar to `dlsym()`. They all takes a "handle" of an ELF returned by `xdl_open()` and the null-terminated symbol name, returning the address where that symbol is loaded into memory. + +`xdl_sym()` lookup "dynamic link symbols" in `.dynsym` as `dlsym()` does. + +`xdl_dsym()` lookup "debuging symbols" in `.symtab` and "`.symtab` in `.gnu_debugdata`". + +Notice: + +* All the dynamic link symbols that have been included in the debuging symbols. +* `xdl_dsym()` needs to load debuging symbols from disk file, and `xdl_sym()` only lookup dynamic link symbols from memory. So `xdl_dsym()` runs slower than `xdl_sym()`. If the symbol you are looking for is a dynamic link symbol, please use `xdl_sym()`. + +### 3. `xdl_addr()` + +``` +int xdl_addr(void *addr, Dl_info *info, char *tmpbuf, size_t tmpbuf_len); +``` + +`xdl_addr()` is similar to `dladdr()`. `xdl_addr()` can lookup not only dynamic link symbols, but also debugging symbols. + +`xdl_addr()` needs to pass an temporary buffer and the length of the buffer. This buffer is used to store the `dlpi_name` and `dli_sname` strings in `Dl_info *info` struct. It is recommended to pass a 512 or 1024 bytes buffer. After `xdl_addr()` returns, please ensure that the lifetime of `tmpbuf` is longer than or equal to the lifetime of `Dl_info *info` struct. + +### 4. `xdl_iterate_phdr()` + +```c +#define XDL_DEFAULT 0x00 +#define XDL_WITH_LINKER 0x01 +#define XDL_FULL_PATHNAME 0x02 + +int xdl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *), void *data, int flags); +``` + +`xdl_iterate_phdr()` is similar to `dl_iterate_phdr()`. But `xdl_iterate_phdr()` is compatible with android 4.x on ARM32. + +`xdl_iterate_phdr()` has an additional "flags" parameter, one or more flags can be bitwise-or'd in it: + +* `XDL_DEFAULT`: Default behavior. +* `XDL_WITH_LINKER`: Always including linker / linker64. +* `XDL_FULL_PATHNAME`: Always return full pathname instead of basename. + +These flags are needed because these capabilities require additional execution time, and you don't always need them. + + +## Official Repositories + +* https://github.com/hexhacking/xDL +* https://gitlab.com/hexhacking/xDL +* https://gitee.com/hexhacking/xDL + + +## Support + +* Check the [xdl-sample](xdl_sample). +* Communicate on [GitHub issues](https://github.com/hexhacking/xDL/issues). +* Email: caikelun@gmail.com +* QQ group: 603635869 + + +## Contributing + +[xDL Contributing Guide](CONTRIBUTING.md). + + +## License + +xDL is MIT licensed, as found in the [LICENSE](LICENSE) file. + +xDL documentation is Creative Commons licensed, as found in the [LICENSE-docs](LICENSE-docs) file. + + +## History + +[xCrash 2.x](https://github.com/hexhacking/xCrash/tree/4748d183c1395c54bfb760ec6c454966d52ab73f) contains a very rudimentary module [xc_dl](https://github.com/hexhacking/xCrash/blob/4748d183c1395c54bfb760ec6c454966d52ab73f/src/native/libxcrash/jni/xc_dl.c) for searching system library symbols, which has many problems in performance and compatibility. xCrash 2.x uses it to search a few symbols from libart, libc and libc++. + +Later, some other projects began to use the xc_dl module alone, including in some performance-sensitive usage scenarios. At this time, we began to realize that we need to rewrite this module, and we need a better implementation. diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..d2ff374 --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,181 @@ +# xDL + +![](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat) +![](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat) +![](https://img.shields.io/badge/release-1.0.0-red.svg?style=flat) +![](https://img.shields.io/badge/Android-4.1%20--%2011-blue.svg?style=flat) +![](https://img.shields.io/badge/arch-armeabi--v7a%20%7C%20arm64--v8a%20%7C%20x86%20%7C%20x86__64-blue.svg?style=flat) + +xDL 是 Android DL 系列函数的增强实现。 + + +## 特征 + +* 增强的 `dlopen()` + `dlsym()` + `dladdr()`。 + * 查询当前进程中所有已加载的 ELF,包括系统库。 + * 查询 `.dynsym` 中的动态链接符号。 + * 查询 `.symtab` 和 “`.gnu_debugdata` 里的 `.symtab`” 中的调试符号。 +* 增强的 `dl_iterate_phdr()`。 + * 兼容 ARM32 平台的 Android 4.x。 + * 在 Android <= 8.x 时,包含 linker / linker64。 + * 在 Android 5.x 中,返回完整的路径名(full pathname),而不是文件名(basename)。 + * 返回 app\_process32 / app\_process64,而不是包名。 +* 支持 Android 4.1 - 11 (API level 16 - 30)。 +* 支持 armeabi-v7a, arm64-v8a, x86 and x86_64。 +* 使用 MIT 许可证授权。 + + +## 产出物体积 + +| ABI | 压缩后 (KB) | 未压缩 (KB) | +| :---------- | ---------: | ---------: | +| armeabi-v7a | 5.8 | 13.9 | +| arm64-v8a | 6.2 | 14.2 | +| x86 | 6.4 | 13.8 | +| x86_64 | 6.6 | 14.5 | + + +## 使用 + +### 1. 增加依赖 + +```Gradle +dependencies { + implementation 'io.hexhacking.xdl:xdl-android-lib:1.0.0' +} +``` + +### 2. 指定一个或多个你需要的 ABI + +```Gradle +android { + defaultConfig { + ndk { + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' + } + } +} +``` + +### 3. 下载头文件 + +从 [这里](xdl_lib/src/main/cpp/xdl.h) 下载头文件,注意核对文件中的版本号。然后把头文件放在你工程的合适位置里。 + +你可以参考 [xdl-sample](xdl_sample) 文件中的示例 app。 + + +## API + +包含 xDL 的头文件: + +```c +#include "xdl.h" +``` + +### 1. `xdl_open()` 和 `xdl_close()` + +```c +void *xdl_open(const char *filename); +void xdl_close(void *handle); +``` + +它们和 `dlopen()` / `dlclose()` 很相似。但是需要注意:`xdl_open()` 并不能真正的从磁盘中“加载” ELF 文件,它只是从内存中“打开”已加载的 ELF 文件。 + +`filename` 可是是文件名(basename)也可以是完整的路径名(full pathname)。然而,Android linker 从 8.0 开始启用了 namespace 机制。如果你传递文件名,你需要确认当前进程中没有重名的 ELF 文件。`xdl_open()` 只会返回第一个匹配到的 ELF文件。请考虑以下 Android 10 中的 `/proc/self/maps` 片段: + +``` +756fc2c000-756fc7c000 r--p 00000000 fd:03 2985 /system/lib64/vndk-sp-29/libc++.so +756fc7c000-756fcee000 --xp 00050000 fd:03 2985 /system/lib64/vndk-sp-29/libc++.so +756fcee000-756fcef000 rw-p 000c2000 fd:03 2985 /system/lib64/vndk-sp-29/libc++.so +756fcef000-756fcf7000 r--p 000c3000 fd:03 2985 /system/lib64/vndk-sp-29/libc++.so +7571fdd000-757202d000 r--p 00000000 07:38 20 /apex/com.android.conscrypt/lib64/libc++.so +757202d000-757209f000 --xp 00050000 07:38 20 /apex/com.android.conscrypt/lib64/libc++.so +757209f000-75720a0000 rw-p 000c2000 07:38 20 /apex/com.android.conscrypt/lib64/libc++.so +75720a0000-75720a8000 r--p 000c3000 07:38 20 /apex/com.android.conscrypt/lib64/libc++.so +760b9df000-760ba2f000 r--p 00000000 fd:03 2441 /system/lib64/libc++.so +760ba2f000-760baa1000 --xp 00050000 fd:03 2441 /system/lib64/libc++.so +760baa1000-760baa2000 rw-p 000c2000 fd:03 2441 /system/lib64/libc++.so +760baa2000-760baaa000 r--p 000c3000 fd:03 2441 /system/lib64/libc++.so +``` + +### 2. `xdl_sym()` 和 `xdl_dsym()` + +```c +void *xdl_sym(void *handle, const char *symbol); +void *xdl_dsym(void *handle, const char *symbol); +``` + +它们和 `dlsym()` 很相似。 它们都需要传递一个 `xdl_open()` 返回的 “handle”,和一个 null 结尾的符号名字,返回该符号在内存中的加载地址。 + +`xdl_sym()` 从 `.dynsym` 中查询“动态链接符号,就像 `dlsym()` 做的那样。 + +`xdl_dsym()` 从 `.symtab` 和 “`.gnu_debugdata` 里的 `.symtab`” 中查询“调试符号”。 + +注意: + +* 调试符号中也包含了所有的动态链接符号。 +* `xdl_dsym()` 需要从磁盘文件中加载调试符号,而 `xdl_sym()` 只从内存中查询动态链接符号。所以 `xdl_dsym()` 比 `xdl_sym()` 运行的更慢。如果你在查询的是动态链接符号,请使用 `xdl_sym()`。 + +### 3. `xdl_addr()` + +``` +int xdl_addr(void *addr, Dl_info *info, char *tmpbuf, size_t tmpbuf_len); +``` + +`xdl_addr()` 和 `dladdr()` 很相似。`xdl_addr()` 不仅能查询动态链接符号,还能查询调试符号。 + +`xdl_addr()` 需要传递一个临时缓冲区和它的长度。这个缓冲区用于存放 `Dl_info *info` 结构体中的 `dlpi_name` 和 `dli_sname` 字符串。建议使用 512 或 1024 字节的缓冲区。`xdl_addr()` 返回以后,请保证 `tmpbuf` 的生命周期长于或等于 `Dl_info *info` 结构体的生命周期。 + +### 4. `xdl_iterate_phdr()` + +```c +#define XDL_DEFAULT 0x00 +#define XDL_WITH_LINKER 0x01 +#define XDL_FULL_PATHNAME 0x02 + +int xdl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *), void *data, int flags); +``` + +`xdl_iterate_phdr()` 和 `dl_iterate_phdr()` 很相似。但是 `xdl_iterate_phdr()` 兼容 ARM32 平台的 Android 4.x 系统。 + +`xdl_iterate_phdr()` 有一个额外的“flags”参数,一个或多个“flag”可以按位“或”后传递给它: + +* `XDL_DEFAULT`: 默认行为。 +* `XDL_WITH_LINKER`: 总是包含 linker / linker64。 +* `XDL_FULL_PATHNAME`: 总是返回完整的路径名(full pathname),而不是文件名(basename)。 + +需要这些 flags 的原因是,这些额外的能力也需要花费额外的执行时间,而你并不总是需要这些能力。 + + +## 官方仓库 + +* https://github.com/hexhacking/xDL +* https://gitlab.com/hexhacking/xDL +* https://gitee.com/hexhacking/xDL + + +## 技术支持 + +* 查看 [xdl-sample](xdl_sample)。 +* 在 [GitHub issues](https://github.com/hexhacking/xDL/issues) 交流。 +* 邮件:caikelun@gmail.com +* QQ 群:603635869 + + +## 贡献 + +[xDL Contributing Guide](CONTRIBUTING.md). + + +## 许可证 + +xDL 使用 [MIT 许可证](LICENSE)。 + +xDL 的文档使用 [Creative Commons 许可证](LICENSE-docs)。 + + +## 历史 + +[xCrash 2.x](https://github.com/hexhacking/xCrash/tree/4748d183c1395c54bfb760ec6c454966d52ab73f) 包含一个非常原始的 [xc_dl](https://github.com/hexhacking/xCrash/blob/4748d183c1395c54bfb760ec6c454966d52ab73f/src/native/libxcrash/jni/xc_dl.c) 模块,用它来查询系统库的符号,这个模块在性能和兼容性上都有一些问题。xCrash 2.x 使用它在 libart,libc 和 libc++ 中查询很少量的几个符号。 + +后来,一些其他的项目开始单独使用 xc_dl 模块,其中也包含一些性能敏感的场景。这时候,我们开始意识到我们需要重写这个模块,并且我们需要一个更好的实现。 diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..f7147b1 --- /dev/null +++ b/build.gradle @@ -0,0 +1,60 @@ +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'digital.wup:android-maven-publish:3.6.2' + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} + +ext { + minSdkVersion = 16 + compileSdkVersion = 30 + targetSdkVersion = 30 + buildToolsVersion = '30.0.2' + javaVersion = JavaVersion.VERSION_1_6 + ndkVersion = "21.3.6528147" + cmakeVersion = "3.10.2" + abiFilters = "armeabi-v7a,arm64-v8a,x86,x86_64" + useASAN = false + + POM_GROUP_ID = "io.hexhacking.xdl" + POM_ARTIFACT_ID = "xdl-android-lib" + POM_VERSION_NAME = "1.0.0" + + POM_NAME = "xDL Android Lib" + POM_DESCRIPTION = "xDL is an enhanced implementation of the Android DL series functions." + POM_URL = "https://github.com/hexhacking/xDL" + POM_INCEPTION_YEAR = "2020" + POM_PACKAGING = "aar" + + POM_SCM_CONNECTION = "https://github.com/hexhacking/xDL.git" + + POM_ISSUE_SYSTEM = "github" + POM_ISSUE_URL = "https://github.com/hexhacking/xDL/issues" + + POM_LICENCE_NAME = "The MIT License" + POM_LICENCE_URL = "https://opensource.org/licenses/MIT" + POM_LICENCE_DIST = "repo" + + POM_DEVELOPER_ID = "HexHacking" + POM_DEVELOPER_NAME = "HexHacking Team" + + BINTRAY_REPO = "maven" + BINTRAY_ORGANIZATION = "hexhacking" + BINTRAY_LICENCE = ['MIT'] +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..52f5917 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,19 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true \ No newline at end of file diff --git a/gradle/publish.gradle b/gradle/publish.gradle new file mode 100644 index 0000000..d837cc3 --- /dev/null +++ b/gradle/publish.gradle @@ -0,0 +1,129 @@ +apply plugin: 'digital.wup.android-maven-publish' +apply plugin: 'com.jfrog.bintray' + +def readPropertyFromLocalProperties(String key) { + Properties properties = new Properties() + try { + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + } catch (Exception e) { + println("load local.properties failed. msg: ${e.message}") + } + return properties.getProperty(key) +} + +def getCustomMavenRepo() { + return readPropertyFromLocalProperties('CUSTOM_MAVEN_REPO') +} + +def getCustomMavenUsername() { + return readPropertyFromLocalProperties('CUSTOM_MAVEN_USERNAME') +} + +def getCustomMavenPassword() { + return readPropertyFromLocalProperties('CUSTOM_MAVEN_PASSWORD') +} + +def getBintrayUser() { + return readPropertyFromLocalProperties('BINTRAY_USER') +} + +def getBintrayApikey() { + return readPropertyFromLocalProperties('BINTRAY_APIKEY') +} + +task sourcesJar(type: Jar) { + classifier = 'sources' + from android.sourceSets.main.java.srcDirs +} + +task javadoc(type: Javadoc) { + source = android.sourceSets.main.java.sourceFiles + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +publishing.publications { + mavenAar(MavenPublication) { + from components.android + + artifact sourcesJar + artifact javadocJar + + artifactId POM_ARTIFACT_ID + groupId POM_GROUP_ID + version POM_VERSION_NAME + + pom { + name = POM_NAME + description = POM_DESCRIPTION + url = POM_URL + inceptionYear = POM_INCEPTION_YEAR + packaging = POM_PACKAGING + scm { + connection = POM_SCM_CONNECTION + url = POM_URL + } + issueManagement { + system = POM_ISSUE_SYSTEM + url = POM_ISSUE_URL + } + licenses { + license { + name = POM_LICENCE_NAME + url = POM_LICENCE_URL + distribution = POM_LICENCE_DIST + } + } + developers { + developer { + id = POM_DEVELOPER_ID + name = POM_DEVELOPER_NAME + } + } + } + } +} + +// custom maven repo +publishing.repositories { + maven { + url getCustomMavenRepo() + credentials { + username getCustomMavenUsername() + password getCustomMavenPassword() + } + } +} + +// bintray maven repo +bintray { + user = getBintrayUser() + key = getBintrayApikey() + publications = ['mavenAar'] + pkg { + repo = BINTRAY_REPO + name = "${POM_GROUP_ID}:${POM_ARTIFACT_ID}" + userOrg = BINTRAY_ORGANIZATION + desc = POM_DESCRIPTION + websiteUrl = POM_URL + issueTrackerUrl = POM_ISSUE_URL + vcsUrl = POM_SCM_CONNECTION + licenses = BINTRAY_LICENCE + publicDownloadNumbers = true + publish = true + dryRun = false + + version { + name = POM_VERSION_NAME + released = new Date() + vcsTag = POM_VERSION_NAME + gpg { + sign = true + } + } + } +} diff --git a/gradle/sanitizer.gradle b/gradle/sanitizer.gradle new file mode 100644 index 0000000..3eb4dc4 --- /dev/null +++ b/gradle/sanitizer.gradle @@ -0,0 +1,60 @@ +project.afterEvaluate { + + preBuild.doFirst { + if (rootProject.ext.useASAN) { + def ndkLibDir = android.ndkDirectory.absolutePath + "/toolchains/llvm/prebuilt/" + def ABIs = rootProject.ext.abiFilters.split(",") + + for (String abi : ABIs) { + def arch = abi + if (abi == 'armeabi-v7a') { + arch = "arm" + } else if (abi == "arm64-v8a") { + arch = "aarch64" + } else if (abi == "x86") { + arch = "i686" + } else if (abi == "x86_64") { + arch = "x86_64" + } + + // create ASAN wrap.sh + def resDir = new File("src/main/resources/lib/" + abi) + resDir.mkdirs() + def wrapFile = new File(resDir, "wrap.sh") + wrapFile.withWriter { writer -> + writer.write('#!/system/bin/sh\n') + writer.write('HERE="$(cd "$(dirname "$0")" && pwd)"\n') + writer.write('export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1,detect_stack_use_after_return=1,check_initialization_order=true,quarantine_size_mb=64,color=never,new_delete_type_mismatch=0,sleep_before_dying=5,use_sigaltstack=0\n') + writer.write("ASAN_LIB=\$(ls \$HERE/libclang_rt.asan-${arch}-android.so)\n") + writer.write('if [ -f "$HERE/libc++_shared.so" ]; then\n') + writer.write(' export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so"\n') + writer.write('else\n') + writer.write(' export LD_PRELOAD="$ASAN_LIB"\n') + writer.write('fi\n') + writer.write('"\$@"\n') + } + println "sanitizer: [${abi}] create wrap.sh: " + wrapFile.absolutePath + + // copy ASAN libs + def libDir = new File("src/main/jniLibs/" + abi) + libDir.mkdirs() + FileTree tree = fileTree(dir: ndkLibDir).include("**/libclang_rt.asan-${arch}-android.so") + tree.each { File file -> + copy { + from file + into libDir.absolutePath + } + println "sanitizer: [${abi}] copy lib: ${file.absolutePath} -> ${libDir.absolutePath}" + } + } + } else { + delete 'src/main/resources/' + delete 'src/main/jniLibs/' + } + } +} + +clean.doFirst { + delete 'src/main/resources/' + delete 'src/main/jniLibs/' +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..f6b961fd5a86aa5fbfe90f707c3138408be7c718 GIT binary patch literal 54329 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

<5G2M1WZP5 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 F{|7XC?P&l2 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..8d3aa46 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sun Nov 08 14:07:21 CST 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..74758d6 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,3 @@ +include ':xdl_lib' +include ':xdl_sample' +rootProject.name = "xDL" diff --git a/xdl_lib/.gitignore b/xdl_lib/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/xdl_lib/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/xdl_lib/build.gradle b/xdl_lib/build.gradle new file mode 100644 index 0000000..78c4347 --- /dev/null +++ b/xdl_lib/build.gradle @@ -0,0 +1,40 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + ndkVersion rootProject.ext.ndkVersion + defaultConfig { + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + externalNativeBuild { + cmake { + abiFilters rootProject.ext.abiFilters.split(",") + if(rootProject.ext.useASAN) { + arguments "-DANDROID_ARM_MODE=arm" + arguments "-DUSEASAN=ON" + } + } + } + } + externalNativeBuild { + cmake { + path "src/main/cpp/CMakeLists.txt" + version rootProject.ext.cmakeVersion + } + } + compileOptions { + sourceCompatibility rootProject.ext.javaVersion + targetCompatibility rootProject.ext.javaVersion + } + buildTypes { + debug { + minifyEnabled false + } + release { + minifyEnabled false + } + } +} + +apply from: rootProject.file('gradle/publish.gradle') diff --git a/xdl_lib/src/main/AndroidManifest.xml b/xdl_lib/src/main/AndroidManifest.xml new file mode 100644 index 0000000..45a7966 --- /dev/null +++ b/xdl_lib/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/xdl_lib/src/main/cpp/CMakeLists.txt b/xdl_lib/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000..a87c5cd --- /dev/null +++ b/xdl_lib/src/main/cpp/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.4.1) + +add_compile_options( + -std=c11 + -Weverything + -Werror) + +file(GLOB XDL_SRC + *.c) + +add_library(xdl SHARED + ${XDL_SRC}) + +target_include_directories(xdl PUBLIC + .) + +target_link_libraries(xdl + dl) + +if(USEASAN) + +target_compile_options(xdl PUBLIC + -fsanitize=address + -fno-omit-frame-pointer) + +set_target_properties(xdl PROPERTIES + LINK_FLAGS " \ + -fsanitize=address") + +else() + +target_compile_options(xdl PUBLIC + -Oz + -flto + -ffunction-sections + -fdata-sections) + +set_target_properties(xdl PROPERTIES + LINK_FLAGS " \ + -O3 \ + -flto \ + -Wl,--exclude-libs,ALL \ + -Wl,--gc-sections \ + -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/xdl.map.txt") + +endif() diff --git a/xdl_lib/src/main/cpp/xdl.c b/xdl_lib/src/main/cpp/xdl.c new file mode 100644 index 0000000..c65ac4f --- /dev/null +++ b/xdl_lib/src/main/cpp/xdl.c @@ -0,0 +1,605 @@ +// Copyright (c) 2020-present, HexHacking Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +// Created by caikelun on 2020-10-04. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xdl.h" +#include "xdl.h" +#include "xdl_iterate.h" +#include "xdl_util.h" +#include "xdl_lzma.h" +#include "xdl_const.h" + +#define XDL_DYNSYM_IS_EXPORT_SYM(shndx) (SHN_UNDEF != (shndx)) +#define XDL_SYMTAB_IS_EXPORT_SYM(shndx) (SHN_UNDEF != (shndx) && !((shndx) >= SHN_LORESERVE && (shndx) <= SHN_HIRESERVE)) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" + +typedef struct xdl +{ + char *pathname; + uintptr_t load_bias; + const ElfW(Phdr) *dlpi_phdr; + ElfW(Half) dlpi_phnum; + + // + // (1) for searching symbols from .dynsym + // + + bool dynsym_try_load; + ElfW(Sym) *dynsym; // .dynsym + const char *dynstr; // .dynstr + + // .hash (SYSV hash for .dynstr) + struct + { + const uint32_t *buckets; + uint32_t buckets_cnt; + const uint32_t *chains; + uint32_t chains_cnt; + } sysv_hash; + + // .gnu.hash (GNU hash for .dynstr) + struct + { + const uint32_t *buckets; + uint32_t buckets_cnt; + const uint32_t *chains; + uint32_t symoffset; + const ElfW(Addr) *bloom; + uint32_t bloom_cnt; + uint32_t bloom_shift; + } gnu_hash; + + // + // (2) for searching symbols from .symtab + // + + bool symtab_try_load; + uintptr_t base; + + int file_fd; + uint8_t *file; + size_t file_sz; + + uint8_t *debugdata; // decompressed .gnu_debugdata + size_t debugdata_sz; + + ElfW(Sym) *symtab; // .symtab + size_t symtab_cnt; + char *strtab; // .strtab + size_t strtab_sz; +} xdl_t; + +#pragma clang diagnostic pop + +// load from memory +static int xdl_dynsym_load(xdl_t *self) +{ + // find the dynamic segment + ElfW(Dyn) *dynamic = NULL; + for(size_t i = 0; i < self->dlpi_phnum; i++) + { + const ElfW(Phdr) *phdr = &(self->dlpi_phdr[i]); + if(PT_DYNAMIC == phdr->p_type) + { + dynamic = (ElfW(Dyn) *)(self->load_bias + phdr->p_vaddr); + break; + } + } + if(NULL == dynamic) return -1; + + // iterate the dynamic segment + for(ElfW(Dyn) * entry = dynamic; entry && entry->d_tag != DT_NULL; entry++) + { + switch (entry->d_tag) + { + case DT_SYMTAB: //.dynsym + self->dynsym = (ElfW(Sym) *)(self->load_bias + entry->d_un.d_ptr); + break; + case DT_STRTAB: //.dynstr + self->dynstr = (const char *)(self->load_bias + entry->d_un.d_ptr); + break; + case DT_HASH: //.hash + self->sysv_hash.buckets_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[0]; + self->sysv_hash.chains_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[1]; + self->sysv_hash.buckets = &(((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[2]); + self->sysv_hash.chains = &(self->sysv_hash.buckets[self->sysv_hash.buckets_cnt]); + break; + case DT_GNU_HASH: //.gnu.hash + self->gnu_hash.buckets_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[0]; + self->gnu_hash.symoffset = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[1]; + self->gnu_hash.bloom_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[2]; + self->gnu_hash.bloom_shift = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[3]; + self->gnu_hash.bloom = (const ElfW(Addr) *)(self->load_bias + entry->d_un.d_ptr + 16); + self->gnu_hash.buckets = (const uint32_t *)(&(self->gnu_hash.bloom[self->gnu_hash.bloom_cnt])); + self->gnu_hash.chains = (const uint32_t *)(&(self->gnu_hash.buckets[self->gnu_hash.buckets_cnt])); + break; + default: + break; + } + } + + if(NULL == self->dynsym || NULL == self->dynstr || (0 == self->sysv_hash.buckets_cnt && 0 == self->gnu_hash.buckets_cnt)) + { + self->dynsym = NULL; + self->dynstr = NULL; + self->sysv_hash.buckets_cnt = 0; + self->gnu_hash.buckets_cnt = 0; + return -1; + } + + return 0; +} + +// load from disk and memory +static int xdl_symtab_load(xdl_t *self, ElfW(Shdr) *shdr_debugdata) +{ + ElfW(Ehdr) *ehdr; + uint8_t *elf; + size_t elf_sz; + + if(NULL == shdr_debugdata) + { + // get base address + uintptr_t vaddr_min = UINTPTR_MAX; + for(size_t i = 0; i < self->dlpi_phnum; i++) + { + const ElfW(Phdr) *phdr = &(self->dlpi_phdr[i]); + if(PT_LOAD == phdr->p_type) + { + if(vaddr_min > phdr->p_vaddr) vaddr_min = phdr->p_vaddr; + } + } + if(UINTPTR_MAX == vaddr_min) goto err; + self->base = self->load_bias + vaddr_min; + + // open file + if(0 > (self->file_fd = open(self->pathname, O_RDONLY | O_CLOEXEC))) goto err; + + // get file size + struct stat st; + if(0 != fstat(self->file_fd, &st)) goto err; + self->file_sz = (size_t)st.st_size; + + // mmap file + if(MAP_FAILED == (self->file = (uint8_t *)mmap(NULL, self->file_sz, PROT_READ, MAP_PRIVATE, self->file_fd, 0))) goto err; + + // for ELF parsing + ehdr = (ElfW(Ehdr) *)self->base; + elf = self->file; + elf_sz = self->file_sz; + } + else + { + // decompress the .gnu_debugdata section + if(0 != xdl_lzma_decompress(self->file + shdr_debugdata->sh_offset, shdr_debugdata->sh_size, &self->debugdata, &self->debugdata_sz)) goto err; + + // for ELF parsing +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-align" + ehdr = (ElfW(Ehdr) *)self->debugdata; +#pragma clang diagnostic pop + elf = self->debugdata; + elf_sz = self->debugdata_sz; + } + + // check ELF size + if(0 == ehdr->e_shnum) goto err; + if(elf_sz < ehdr->e_shoff + ehdr->e_shentsize * ehdr->e_shnum) goto err; + + // get .shstrtab + if(SHN_UNDEF == ehdr->e_shstrndx) goto err; + ElfW(Shdr) *shdr_shstrtab = (ElfW(Shdr) *)((uintptr_t)elf + ehdr->e_shoff + ehdr->e_shstrndx * ehdr->e_shentsize); + char *shstrtab = (char *)((uintptr_t)elf + shdr_shstrtab->sh_offset); + + for(size_t i = 0; i < ehdr->e_shnum; i++) + { + ElfW(Shdr) *shdr = (ElfW(Shdr) *)((uintptr_t)elf + ehdr->e_shoff + i * ehdr->e_shentsize); + + if(SHT_SYMTAB == shdr->sh_type && 0 == strcmp(".symtab", shstrtab + shdr->sh_name)) + { + // get and check .strtab + if(shdr->sh_link >= ehdr->e_shnum) continue; + ElfW(Shdr) *shdr_strtab = (ElfW(Shdr) *)((uintptr_t)elf + ehdr->e_shoff + shdr->sh_link * ehdr->e_shentsize); + if(SHT_STRTAB != shdr_strtab->sh_type) continue; + + // found the .symtab and .strtab + self->symtab = (ElfW(Sym) *)((uintptr_t)elf + shdr->sh_offset); + self->symtab_cnt = shdr->sh_size / shdr->sh_entsize; + self->strtab = (char *)((uintptr_t)elf + shdr_strtab->sh_offset); + self->strtab_sz = shdr_strtab->sh_size; + return 0; // OK + } + else if(SHT_PROGBITS == shdr->sh_type && 0 == strcmp(".gnu_debugdata", shstrtab + shdr->sh_name) && NULL == shdr_debugdata) + { + return xdl_symtab_load(self, shdr); + } + } + + err: + if(MAP_FAILED != self->file) + { + munmap(self->file, self->file_sz); + self->file = MAP_FAILED; + } + if(self->file_fd >= 0) + { + close(self->file_fd); + self->file_fd = -1; + } + if(NULL != self->debugdata) + { + free(self->debugdata); + self->debugdata = NULL; + } + self->base = 0; + self->file_sz = 0; + self->debugdata_sz = 0; + self->symtab = NULL; + self->symtab_cnt = 0; + self->strtab = NULL; + self->strtab_sz = 0; + return -1; +} + +static int xdl_open_iterate_cb(struct dl_phdr_info *info, size_t size, void *arg) +{ + (void)size; + + uintptr_t *pkg = (uintptr_t *)arg; + xdl_t **self = (xdl_t **)*pkg++; + const char *filename = (const char *)*pkg; + + // check load_bias and pathname + if(0 == info->dlpi_addr || NULL == info->dlpi_name) return 0; + if('/' == filename[0] || '[' == filename[0]) + { + // full pathname + if(0 != strcmp(info->dlpi_name, filename)) return 0; + } + else + { + // basename ? + size_t basename_len = strlen(filename); + size_t pathname_len = strlen(info->dlpi_name); + if(1 + basename_len > pathname_len) return 0; + if(0 != strcmp(info->dlpi_name + (pathname_len - basename_len), filename)) return 0; + if('/' != *(info->dlpi_name + (pathname_len - basename_len) - 1)) return 0; + } + + // found the target ELF + if(NULL == ((*self) = calloc(1, sizeof(xdl_t)))) return 1; // failed + if(NULL == ((*self)->pathname = strdup(info->dlpi_name))) + { + free(*self); + *self = NULL; + return 1; // failed + } + (*self)->load_bias = info->dlpi_addr; + (*self)->dlpi_phdr = info->dlpi_phdr; + (*self)->dlpi_phnum = info->dlpi_phnum; + (*self)->file_fd = -1; + (*self)->file = MAP_FAILED; + (*self)->dynsym_try_load = false; + (*self)->symtab_try_load = false; + return 1; // OK +} + +void *xdl_open(const char *filename) +{ + if(NULL == filename) return NULL; + + xdl_t *self = NULL; + uintptr_t pkg[2] = {(uintptr_t)&self, (uintptr_t)filename}; + + int iterate_flags = XDL_FULL_PATHNAME; + if(xdl_util_ends_with(filename, XDL_CONST_BASENAME_LINKER)) iterate_flags |= XDL_WITH_LINKER; + xdl_iterate_phdr(xdl_open_iterate_cb, pkg, iterate_flags); + + return (void *)self; +} + +void xdl_close(void *handle) +{ + if(NULL == handle) return; + + xdl_t *self = (xdl_t *)handle; + + if(MAP_FAILED != self->file) munmap(self->file, self->file_sz); + if(self->file_fd >= 0) close(self->file_fd); + if(NULL != self->debugdata) free(self->debugdata); + if(NULL != self->pathname) free(self->pathname); + free(self); +} + +static uint32_t xdl_sysv_hash(const uint8_t *name) +{ + uint32_t h = 0, g; + + while(*name) + { + h = (h << 4) + *name++; + g = h & 0xf0000000; + h ^= g; + h ^= g >> 24; + } + return h; +} + +static uint32_t xdl_gnu_hash(const uint8_t *name) +{ + uint32_t h = 5381; + + while(*name) + { + h += (h << 5) + *name++; + } + return h; +} + +static ElfW(Sym) *xdl_dynsym_find_symbol_use_sysv_hash(xdl_t *self, const char* sym_name) +{ + uint32_t hash = xdl_sysv_hash((const uint8_t *)sym_name); + + for(uint32_t i = self->sysv_hash.buckets[hash % self->sysv_hash.buckets_cnt]; 0 != i; i = self->sysv_hash.chains[i]) + { + ElfW(Sym) *sym = self->dynsym + i; + if(0 != strcmp(self->dynstr + sym->st_name, sym_name)) continue; + return sym; + } + + return NULL; +} + +static ElfW(Sym) *xdl_dynsym_find_symbol_use_gnu_hash(xdl_t *self, const char* sym_name) +{ + uint32_t hash = xdl_gnu_hash((const uint8_t *)sym_name); + + static uint32_t elfclass_bits = sizeof(ElfW(Addr)) * 8; + size_t word = self->gnu_hash.bloom[(hash / elfclass_bits) % self->gnu_hash.bloom_cnt]; + size_t mask = 0 | (size_t)1 << (hash % elfclass_bits) + | (size_t)1 << ((hash >> self->gnu_hash.bloom_shift) % elfclass_bits); + + //if at least one bit is not set, this symbol is surely missing + if((word & mask) != mask) return NULL; + + //ignore STN_UNDEF + uint32_t i = self->gnu_hash.buckets[hash % self->gnu_hash.buckets_cnt]; + if(i < self->gnu_hash.symoffset) return NULL; + + //loop through the chain + while(1) + { + ElfW(Sym) *sym = self->dynsym + i; + uint32_t sym_hash = self->gnu_hash.chains[i - self->gnu_hash.symoffset]; + + if((hash | (uint32_t)1) == (sym_hash | (uint32_t)1)) + { + if(0 == strcmp(self->dynstr + sym->st_name, sym_name)) + { + return sym; + } + } + + //chain ends with an element with the lowest bit set to 1 + if(sym_hash & (uint32_t)1) break; + + i++; + } + + return NULL; +} + +void *xdl_sym(void *handle, const char *symbol) +{ + xdl_t *self = (xdl_t *)handle; + + // load .dynsym only once + if(!self->dynsym_try_load) + { + self->dynsym_try_load = true; + if(0 != xdl_dynsym_load(self)) return NULL; + } + + // check .dynsym + if(NULL == self->dynsym) return NULL; + + // find symbol + ElfW(Sym) *sym = NULL; + if(self->gnu_hash.buckets_cnt > 0) + { + // use GNU hash (.gnu.hash -> .dynsym -> .dynstr), O(x) + O(1) + O(1) + sym = xdl_dynsym_find_symbol_use_gnu_hash(self, symbol); + } + if(NULL == sym && self->sysv_hash.buckets_cnt > 0) + { + // use SYSV hash (.hash -> .dynsym -> .dynstr), O(x) + O(1) + O(1) + sym = xdl_dynsym_find_symbol_use_sysv_hash(self, symbol); + } + if(NULL == sym || !XDL_DYNSYM_IS_EXPORT_SYM(sym->st_shndx)) return NULL; + + return (void *)(self->load_bias + sym->st_value); +} + +void *xdl_dsym(void *handle, const char *symbol) +{ + xdl_t *self = (xdl_t *)handle; + + // load .symtab only once + if(!self->symtab_try_load) + { + self->symtab_try_load = true; + if(0 != xdl_symtab_load(self, NULL)) return NULL; + } + + // check .symtab + if(NULL == self->symtab) return NULL; + + // find symbol + for(size_t i = 0; i < self->symtab_cnt; i++) + { + ElfW(Sym) *sym = self->symtab + i; + + if(!XDL_SYMTAB_IS_EXPORT_SYM(sym->st_shndx)) continue; + if(0 != strncmp(self->strtab + sym->st_name, symbol, self->strtab_sz - sym->st_name)) continue; + + return (void *)(self->load_bias + sym->st_value); + } + + return NULL; +} + +static ElfW(Sym) *xdl_find_symtab_by_addr(xdl_t *self, void * addr) +{ + uintptr_t offset = (uintptr_t)addr - self->load_bias; + + // find symbol + for(size_t i = 0; i < self->symtab_cnt; i++) + { + ElfW(Sym) *sym = self->symtab + i; + + if(!XDL_SYMTAB_IS_EXPORT_SYM(sym->st_shndx)) continue; + if(offset < sym->st_value || offset >= sym->st_value + sym->st_size) continue; + + return sym; + } + + return NULL; +} + +static int xdl_addr_by_iterate_cb(struct dl_phdr_info *info, size_t size, void *arg) +{ + (void)size; + + uintptr_t *pkg = (uintptr_t *)arg; + uintptr_t addr = *pkg++; + Dl_info *dlinfo = (Dl_info *)*pkg++; + char **tmpbuf = (char **)*pkg++; + size_t *tmpbuf_len = (size_t *)*pkg; + + // check load_bias + if(addr < info->dlpi_addr) return 0; + + uintptr_t vaddr = addr - info->dlpi_addr; + for(size_t i = 0; i < info->dlpi_phnum; i++) + { + const ElfW(Phdr) *phdr = &(info->dlpi_phdr[i]); + + // check program header + if(PT_LOAD != phdr->p_type) continue; + if(vaddr < phdr->p_vaddr || vaddr >= phdr->p_vaddr + phdr->p_memsz) continue; + + // found it + dlinfo->dli_fbase = (void *)info->dlpi_addr; + strlcpy(*tmpbuf, info->dlpi_name, *tmpbuf_len); + dlinfo->dli_fname = *tmpbuf; + dlinfo->dli_sname = NULL; + dlinfo->dli_saddr = NULL; + + size_t len = strlen(*tmpbuf) + 1; + *tmpbuf += len; + *tmpbuf_len -= len; + return 1; // OK + } + + return 0; +} + +static int xdl_addr_by_iterate(void *addr, Dl_info *info, char **tmpbuf, size_t *tmpbuf_len) +{ + info->dli_fname = NULL; + uintptr_t pkg[4] = {(uintptr_t)addr, (uintptr_t)info, (uintptr_t)tmpbuf, (uintptr_t)tmpbuf_len}; + + xdl_iterate_phdr(xdl_addr_by_iterate_cb, pkg, XDL_WITH_LINKER | XDL_FULL_PATHNAME); + + return NULL == info->dli_fname ? 0 : 1; +} + +int xdl_addr(void *addr, Dl_info *info, char *tmpbuf, size_t tmpbuf_len) +{ + if(NULL == addr || NULL == info || NULL == tmpbuf || 0 == tmpbuf_len) return 0; + int r = dladdr(addr, info); + + if(0 == r) // dladdr() failed + { + r = xdl_addr_by_iterate(addr, info, &tmpbuf, &tmpbuf_len); + if(0 == r) return 0; // xdl_addr_by_iterate() failed + } + + // match ELF failed + if(NULL == info->dli_fname || '\0' == info->dli_fname[0]) return r; + + // check address failed + if(addr < info->dli_fbase) return r; + + // match symbol failed, but we can try to find symbol again from .symtab + if(NULL == info->dli_sname || NULL == info->dli_saddr) + { + // open + xdl_t *self = (xdl_t *)xdl_open(info->dli_fname); + if(NULL == self) return r; + + // find symbol name + if((uintptr_t)addr >= self->load_bias) + { + // load .symtab + self->symtab_try_load = true; + if(0 == xdl_symtab_load(self, NULL)) + { + // find symbol name by address + ElfW(Sym) * sym = xdl_find_symtab_by_addr(self, addr); + if(NULL != sym) + { + strlcpy(tmpbuf, self->strtab + sym->st_name, tmpbuf_len); + info->dli_sname = tmpbuf; + info->dli_saddr = (void *)(self->load_bias + sym->st_value); + } + } + } + + // close + xdl_close((void *)self); + } + + return r; +} + +int xdl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *), void *data, int flags) +{ + return xdl_iterate_phdr_impl(callback, data, flags); +} diff --git a/xdl_lib/src/main/cpp/xdl.h b/xdl_lib/src/main/cpp/xdl.h new file mode 100644 index 0000000..d746ba2 --- /dev/null +++ b/xdl_lib/src/main/cpp/xdl.h @@ -0,0 +1,66 @@ +// Copyright (c) 2020-present, HexHacking Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +// Created by caikelun on 2020-10-04. + +// +// xDL version: 1.0.0 +// +// You can always get the latest version from: +// https://github.com/hexhacking/xDL +// + +#ifndef IO_HEXHACKING_XDL +#define IO_HEXHACKING_XDL + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// +// Enhanced dlfcn APIs +// +void *xdl_open(const char *filename); +void xdl_close(void *handle); + +void *xdl_sym(void *handle, const char *symbol); +void *xdl_dsym(void *handle, const char *symbol); + +int xdl_addr(void *addr, Dl_info *info, char *tmpbuf, size_t tmpbuf_len); + +// +// Enhanced dl_iterate_phdr() +// +#define XDL_DEFAULT 0x00 +#define XDL_WITH_LINKER 0x01 +#define XDL_FULL_PATHNAME 0x02 + +int xdl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *), void *data, int flags); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/xdl_lib/src/main/cpp/xdl.map.txt b/xdl_lib/src/main/cpp/xdl.map.txt new file mode 100644 index 0000000..6501bf7 --- /dev/null +++ b/xdl_lib/src/main/cpp/xdl.map.txt @@ -0,0 +1,12 @@ +{ + global: + xdl_open; + xdl_close; + xdl_sym; + xdl_dsym; + xdl_addr; + xdl_iterate_phdr; + + local: + *; +}; diff --git a/xdl_lib/src/main/cpp/xdl_const.h b/xdl_lib/src/main/cpp/xdl_const.h new file mode 100644 index 0000000..6b3bebf --- /dev/null +++ b/xdl_lib/src/main/cpp/xdl_const.h @@ -0,0 +1,46 @@ +// Copyright (c) 2020-present, HexHacking Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +// Created by caikelun on 2020-10-04. + +#ifndef IO_HEXHACKING_XDL_CONST +#define IO_HEXHACKING_XDL_CONST + +#ifndef __LP64__ +#define XDL_CONST_PATHNAME_LINKER "/system/bin/linker" // we only use this when Android < 8.1 +#define XDL_CONST_BASENAME_LINKER "linker" +#define XDL_CONST_PATHNAME_LZMA "/system/lib/liblzma.so" +#else +#define XDL_CONST_PATHNAME_LINKER "/system/bin/linker64" // we only use this when Android < 8.1 +#define XDL_CONST_BASENAME_LINKER "linker64" +#define XDL_CONST_PATHNAME_LZMA "/system/lib64/liblzma.so" +#endif + +#define XDL_CONST_SYM_LINKER_MUTEX "__dl__ZL10g_dl_mutex" + +#define XDL_CONST_SYM_LZMA_CRCGEN "CrcGenerateTable" +#define XDL_CONST_SYM_LZMA_CRC64GEN "Crc64GenerateTable" +#define XDL_CONST_SYM_LZMA_CONSTRUCT "XzUnpacker_Construct" +#define XDL_CONST_SYM_LZMA_ISFINISHED "XzUnpacker_IsStreamWasFinished" +#define XDL_CONST_SYM_LZMA_FREE "XzUnpacker_Free" +#define XDL_CONST_SYM_LZMA_CODE "XzUnpacker_Code" + +#endif diff --git a/xdl_lib/src/main/cpp/xdl_iterate.c b/xdl_lib/src/main/cpp/xdl_iterate.c new file mode 100644 index 0000000..8d482d4 --- /dev/null +++ b/xdl_lib/src/main/cpp/xdl_iterate.c @@ -0,0 +1,295 @@ +// Copyright (c) 2020-present, HexHacking Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +// Created by caikelun on 2020-10-04. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xdl_iterate.h" +#include "xdl.h" +#include "xdl_util.h" +#include "xdl_const.h" + +/* + * ========================================================================================================= + * API-LEVEL ANDROID-VERSION SOLUTION + * ========================================================================================================= + * 16 4.1 /proc/self/maps + * 17 4.2 /proc/self/maps + * 18 4.3 /proc/self/maps + * 19 4.4 /proc/self/maps + * 20 4.4W /proc/self/maps + * --------------------------------------------------------------------------------------------------------- + * 21 5.0 dl_iterate_phdr() + __dl__ZL10g_dl_mutex + linker/linker64 in /proc/self/maps + * 22 5.1 dl_iterate_phdr() + __dl__ZL10g_dl_mutex + linker/linker64 in /proc/self/maps + * --------------------------------------------------------------------------------------------------------- + * 23 6.0 dl_iterate_phdr() + linker/linker64 in /proc/self/maps + * 24 7.0 dl_iterate_phdr() + linker/linker64 in /proc/self/maps + * 25 7.1 dl_iterate_phdr() + linker/linker64 in /proc/self/maps + * 26 8.0 dl_iterate_phdr() + linker/linker64 in /proc/self/maps + * --------------------------------------------------------------------------------------------------------- + * >= 27 >= 8.1 dl_iterate_phdr() + * ========================================================================================================= + */ + +extern __attribute((weak)) int dl_iterate_phdr(int (*)(struct dl_phdr_info *, size_t, void *), void *); + +// Android 5.0/5.1 linker's global mutex in .symtab +static pthread_mutex_t *xdl_iterate_linker_mutex = NULL; + +static void xdl_iterate_linker_mutex_init() +{ + void *linker = xdl_open(XDL_CONST_PATHNAME_LINKER); + if(NULL == linker) return; + + xdl_iterate_linker_mutex = xdl_dsym(linker, XDL_CONST_SYM_LINKER_MUTEX); + + xdl_close(linker); +} + +static uintptr_t xdl_iterate_get_min_vaddr(struct dl_phdr_info *info) +{ + uintptr_t min_vaddr = UINTPTR_MAX; + for(size_t i = 0; i < info->dlpi_phnum; i++) + { + const ElfW(Phdr) *phdr = &(info->dlpi_phdr[i]); + if(PT_LOAD == phdr->p_type) + { + if(min_vaddr > phdr->p_vaddr) min_vaddr = phdr->p_vaddr; + } + } + return min_vaddr; +} + +static int xdl_iterate_open_or_rewind_maps(FILE **maps) +{ + if(NULL == *maps) + { + *maps = fopen("/proc/self/maps", "r"); + if(NULL == *maps) return -1; + } + else + rewind(*maps); + + return 0; +} + +static uintptr_t xdl_iterate_get_pathname_from_maps(struct dl_phdr_info *info, char *buf, size_t buf_len, FILE **maps) +{ + // get base address + uintptr_t min_vaddr = xdl_iterate_get_min_vaddr(info); + if(UINTPTR_MAX == min_vaddr) return 0; // failed + uintptr_t base = (uintptr_t)(info->dlpi_addr + min_vaddr); + + // open or rewind maps-file + if(0 != xdl_iterate_open_or_rewind_maps(maps)) return 0; // failed + + char line[1024]; + while(fgets(line, sizeof(line), *maps)) + { + // check base address + uintptr_t start, end; + if(2 != sscanf(line, "%"SCNxPTR"-%"SCNxPTR" r", &start, &end)) continue; + if(base < start) break; // failed + if(base >= end) continue; + + // get pathname + char *pathname = strchr(line, '/'); + if(NULL == pathname) break; // failed + xdl_util_trim_ending(pathname); + + // found it + strlcpy(buf, pathname, buf_len); + return (uintptr_t)buf; // OK + } + + return 0; // failed +} + +static int xdl_iterate_by_linker_cb(struct dl_phdr_info *info, size_t size, void *arg) +{ + uintptr_t *pkg = (uintptr_t *)arg; + xdl_iterate_phdr_cb_t cb = (xdl_iterate_phdr_cb_t)*pkg++; + void *cb_arg = (void *)*pkg++; + FILE **maps = (FILE **)*pkg++; + uintptr_t linker_load_bias = *pkg++; + int flags = (int)*pkg; + + if(0 == info->dlpi_addr || NULL == info->dlpi_name || '\0' == info->dlpi_name[0]) return 0; // ignore invalid ELF + if(linker_load_bias == info->dlpi_addr) return 0; // ignore linker if we have returned it already + + if('/' != info->dlpi_name[0] && '[' != info->dlpi_name[0] && (0 != (flags & XDL_FULL_PATHNAME))) + { + // get pathname from /proc/self/maps + char buf[512]; + uintptr_t pathname = xdl_iterate_get_pathname_from_maps(info, buf, sizeof(buf), maps); + if(0 == pathname) return 0; // ignore this ELF + + // callback + struct dl_phdr_info info_fixed; + info_fixed.dlpi_addr = info->dlpi_addr; + info_fixed.dlpi_name = (const char *)pathname; + info_fixed.dlpi_phdr = info->dlpi_phdr; + info_fixed.dlpi_phnum = info->dlpi_phnum; + return cb(&info_fixed, size, cb_arg); + } + else + { + // callback + return cb(info, size, cb_arg); + } +} + +static uintptr_t xdl_iterate_find_linker_base(FILE **maps) +{ + // open or rewind maps-file + if(0 != xdl_iterate_open_or_rewind_maps(maps)) return 0; // failed + + size_t linker_pathname_len = strlen(" "XDL_CONST_PATHNAME_LINKER); + + char line[1024]; + while(fgets(line, sizeof(line), *maps)) + { + // check pathname + size_t line_len = xdl_util_trim_ending(line); + if(line_len < linker_pathname_len)continue; + if(0 != memcmp(line + line_len - linker_pathname_len, " "XDL_CONST_PATHNAME_LINKER, linker_pathname_len)) continue; + + // get base address + uintptr_t base, offset; + if(2 != sscanf(line, "%"SCNxPTR"-%*"SCNxPTR" r%*2sp %"SCNxPTR" ", &base, &offset)) continue; + if(0 != offset) continue; + if(0 != memcmp((void *)base, ELFMAG, SELFMAG)) continue; + + // find it + return base; + } + + return 0; +} + +static int xdl_iterate_do_callback(xdl_iterate_phdr_cb_t cb, void *cb_arg, uintptr_t base, const char *pathname, uintptr_t *load_bias) +{ + ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base; + + struct dl_phdr_info info; + info.dlpi_name = pathname; + info.dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff); + info.dlpi_phnum = ehdr->e_phnum; + + // get load bias + uintptr_t min_vaddr = xdl_iterate_get_min_vaddr(&info); + if(UINTPTR_MAX == min_vaddr) return 0; // ignore invalid ELF + info.dlpi_addr = (ElfW(Addr))(base - min_vaddr); + if(NULL != load_bias) *load_bias = info.dlpi_addr; + + return cb(&info, sizeof(struct dl_phdr_info), cb_arg); +} + +static int xdl_iterate_by_linker(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags) +{ + if(NULL == dl_iterate_phdr) return -1; + + FILE *maps = NULL; + + // for linker/linker64 in Android version < 8.1 (API level 27) + uintptr_t linker_base = 0, linker_load_bias = 0; + if((flags & XDL_WITH_LINKER) && xdl_util_get_api_level() < __ANDROID_API_O_MR1__) + { + linker_base = xdl_iterate_find_linker_base(&maps); + if(0 != linker_base) + { + if(0 != xdl_iterate_do_callback(cb, cb_arg, linker_base, XDL_CONST_PATHNAME_LINKER, &linker_load_bias)) return 0; + } + } + + // for other ELF + uintptr_t pkg[5] = {(uintptr_t)cb, (uintptr_t)cb_arg, (uintptr_t)&maps, linker_load_bias, (uintptr_t)flags}; + if(NULL != xdl_iterate_linker_mutex) pthread_mutex_lock(xdl_iterate_linker_mutex); + dl_iterate_phdr(xdl_iterate_by_linker_cb, pkg); + if(NULL != xdl_iterate_linker_mutex) pthread_mutex_unlock(xdl_iterate_linker_mutex); + + if(NULL != maps) fclose(maps); + return 0; +} + +#if defined(__arm__) || defined(__i386__) +static int xdl_iterate_by_maps(xdl_iterate_phdr_cb_t cb, void *cb_arg) +{ + FILE *maps = fopen("/proc/self/maps", "r"); + if(NULL == maps) return 0; + + char line[1024]; + while(fgets(line, sizeof(line), maps)) + { + // Try to find an ELF which loaded by linker. This is almost always correct in android 4.x. + uintptr_t base, offset; + if(2 != sscanf(line, "%"SCNxPTR"-%*"SCNxPTR" r-xp %"SCNxPTR" ", &base, &offset)) continue; + if(0 != offset) continue; + if(0 != memcmp((void *)base, ELFMAG, SELFMAG)) continue; + + // get pathname + char *pathname = strchr(line, '/'); + if(NULL == pathname) break; + xdl_util_trim_ending(pathname); + + // callback + if(0 != xdl_iterate_do_callback(cb, cb_arg, base, pathname, NULL)) break; + } + + fclose(maps); + return 0; +} +#endif + +int xdl_iterate_phdr_impl(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags) +{ + int api_level = xdl_util_get_api_level(); + + // get linker's __dl__ZL10g_dl_mutex for Android 5.0/5.1 + static bool linker_mutex_inited = false; + if(__ANDROID_API_L__ == api_level || __ANDROID_API_L_MR1__ == api_level) + { + if(!linker_mutex_inited) + { + linker_mutex_inited = true; + xdl_iterate_linker_mutex_init(); + } + } + + // iterate by /proc/self/maps in Android 4.x (Android 4.x only supports arm32 and x86) +#if defined(__arm__) || defined(__i386__) + if(api_level < __ANDROID_API_L__) + return xdl_iterate_by_maps(cb, cb_arg); +#endif + + // iterate by dl_iterate_phdr() + return xdl_iterate_by_linker(cb, cb_arg, flags); +} diff --git a/xdl_lib/src/main/cpp/xdl_iterate.h b/xdl_lib/src/main/cpp/xdl_iterate.h new file mode 100644 index 0000000..28cb9ee --- /dev/null +++ b/xdl_lib/src/main/cpp/xdl_iterate.h @@ -0,0 +1,41 @@ +// Copyright (c) 2020-present, HexHacking Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +// Created by caikelun on 2020-10-04. + +#ifndef IO_HEXHACKING_XDL_ITERATE +#define IO_HEXHACKING_XDL_ITERATE + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int (*xdl_iterate_phdr_cb_t)(struct dl_phdr_info *info, size_t size, void *arg); +int xdl_iterate_phdr_impl(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/xdl_lib/src/main/cpp/xdl_lzma.c b/xdl_lib/src/main/cpp/xdl_lzma.c new file mode 100644 index 0000000..238a969 --- /dev/null +++ b/xdl_lib/src/main/cpp/xdl_lzma.c @@ -0,0 +1,176 @@ +// Copyright (c) 2020-present, HexHacking Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +// Created by caikelun on 2020-11-08. + +#include +#include +#include +#include +#include +#include +#include +#include +#include "xdl_lzma.h" +#include "xdl.h" +#include "xdl_util.h" +#include "xdl_const.h" + +// LZMA data type definition +#define SZ_OK 0 +typedef struct ISzAlloc ISzAlloc; +typedef const ISzAlloc * ISzAllocPtr; +struct ISzAlloc +{ + void *(*Alloc)(ISzAllocPtr p, size_t size); + void (*Free)(ISzAllocPtr p, void *address); /* address can be 0 */ +}; +typedef enum +{ + CODER_STATUS_NOT_SPECIFIED, /* use main error code instead */ + CODER_STATUS_FINISHED_WITH_MARK, /* stream was finished with end mark. */ + CODER_STATUS_NOT_FINISHED, /* stream was not finished */ + CODER_STATUS_NEEDS_MORE_INPUT /* you must provide more input bytes */ +} ECoderStatus; +typedef enum +{ + CODER_FINISH_ANY, /* finish at any point */ + CODER_FINISH_END /* block must be finished at the end */ +} ECoderFinishMode; + +// LZMA function type definition +typedef void (*xdl_lzma_crcgen_t)(void); +typedef void (*xdl_lzma_crc64gen_t)(void); +typedef void (*xdl_lzma_construct_t)(void *, ISzAllocPtr); +typedef int (*xdl_lzma_isfinished_t)(const void *); +typedef void (*xdl_lzma_free_t)(void *); +typedef int (*xdl_lzma_code_t)(void *, uint8_t *, size_t *, const uint8_t *, size_t *, ECoderFinishMode, ECoderStatus *); +typedef int (*xdl_lzma_code_q_t)(void *, uint8_t *, size_t *, const uint8_t *, size_t *, int, ECoderFinishMode, ECoderStatus *); + +// LZMA function pointor +static xdl_lzma_construct_t xdl_lzma_construct = NULL; +static xdl_lzma_isfinished_t xdl_lzma_isfinished = NULL; +static xdl_lzma_free_t xdl_lzma_free = NULL; +static void *xdl_lzma_code = NULL; + +// LZMA init +static void xdl_lzma_init() +{ + void *lzma = xdl_open(XDL_CONST_PATHNAME_LZMA); + if(NULL == lzma) return; + + xdl_lzma_crcgen_t crcgen = NULL; + xdl_lzma_crc64gen_t crc64gen = NULL; + if(NULL == (crcgen = (xdl_lzma_crcgen_t)xdl_sym(lzma, XDL_CONST_SYM_LZMA_CRCGEN))) goto end; + if(NULL == (crc64gen = (xdl_lzma_crc64gen_t)xdl_sym(lzma, XDL_CONST_SYM_LZMA_CRC64GEN))) goto end; + if(NULL == (xdl_lzma_construct = (xdl_lzma_construct_t)xdl_sym(lzma, XDL_CONST_SYM_LZMA_CONSTRUCT))) goto end; + if(NULL == (xdl_lzma_isfinished = (xdl_lzma_isfinished_t)xdl_sym(lzma, XDL_CONST_SYM_LZMA_ISFINISHED))) goto end; + if(NULL == (xdl_lzma_free = (xdl_lzma_free_t)xdl_sym(lzma, XDL_CONST_SYM_LZMA_FREE))) goto end; + if(NULL == (xdl_lzma_code = xdl_sym(lzma, XDL_CONST_SYM_LZMA_CODE))) goto end; + crcgen(); + crc64gen(); + + end: + xdl_close(lzma); +} + +// LZMA internal alloc / free +static void* xdl_lzma_internal_alloc(ISzAllocPtr p, size_t size) +{ + (void)p; + return malloc(size); +} +static void xdl_lzma_internal_free(ISzAllocPtr p, void *address) +{ + (void)p; + free(address); +} + +int xdl_lzma_decompress(uint8_t *src, size_t src_size, uint8_t **dst, size_t *dst_size) +{ + size_t src_offset = 0; + size_t dst_offset = 0; + size_t src_remaining; + size_t dst_remaining; + ISzAlloc alloc = {.Alloc = xdl_lzma_internal_alloc, .Free = xdl_lzma_internal_free}; + long long state[4096 / sizeof(long long)]; // must be enough, 8-bit aligned + ECoderStatus status; + int api_level = xdl_util_get_api_level(); + + // init and check + static bool inited = false; + if(!inited) + { + xdl_lzma_init(); + inited = true; + } + if(NULL == xdl_lzma_code) return -1; + + xdl_lzma_construct(&state, &alloc); + + *dst_size = 2 * src_size; + *dst = NULL; + do + { + *dst_size *= 2; + if(NULL == (*dst = realloc(*dst, *dst_size))) + { + xdl_lzma_free(&state); + return -1; + } + + src_remaining = src_size - src_offset; + dst_remaining = *dst_size - dst_offset; + + int result; + if(api_level >= __ANDROID_API_Q__) + { + xdl_lzma_code_q_t lzma_code_q = (xdl_lzma_code_q_t)xdl_lzma_code; + result = lzma_code_q(&state, *dst + dst_offset, &dst_remaining, src + src_offset, &src_remaining, 1, CODER_FINISH_ANY, &status); + } + else + { + xdl_lzma_code_t lzma_code = (xdl_lzma_code_t)xdl_lzma_code; + result = lzma_code(&state, *dst + dst_offset, &dst_remaining, src + src_offset, &src_remaining, CODER_FINISH_ANY, &status); + } + if(SZ_OK != result) + { + free(*dst); + xdl_lzma_free(&state); + return -1; + } + + src_offset += src_remaining; + dst_offset += dst_remaining; + } while (status == CODER_STATUS_NOT_FINISHED); + + xdl_lzma_free(&state); + + if(!xdl_lzma_isfinished(&state)) + { + free(*dst); + return -1; + } + + *dst_size = dst_offset; + *dst = realloc(*dst, *dst_size); + return 0; +} diff --git a/xdl_lib/src/main/cpp/xdl_lzma.h b/xdl_lib/src/main/cpp/xdl_lzma.h new file mode 100644 index 0000000..58d4dc5 --- /dev/null +++ b/xdl_lib/src/main/cpp/xdl_lzma.h @@ -0,0 +1,40 @@ +// Copyright (c) 2020-present, HexHacking Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +// Created by caikelun on 2020-11-08. + +#ifndef IO_HEXHACKING_XDL_LZMA +#define IO_HEXHACKING_XDL_LZMA + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int xdl_lzma_decompress(uint8_t *src, size_t src_size, uint8_t **dst, size_t *dst_size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/xdl_lib/src/main/cpp/xdl_util.c b/xdl_lib/src/main/cpp/xdl_util.c new file mode 100644 index 0000000..47d7340 --- /dev/null +++ b/xdl_lib/src/main/cpp/xdl_util.c @@ -0,0 +1,106 @@ +// Copyright (c) 2020-present, HexHacking Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +// Created by caikelun on 2020-10-04. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xdl_util.h" +#include "xdl_const.h" + +bool xdl_util_starts_with(const char *str, const char *start) +{ + while(*str && *str == *start) + { + str++; + start++; + } + + return '\0' == *start; +} + +bool xdl_util_ends_with(const char* str, const char* ending) +{ + size_t str_len = strlen(str); + size_t ending_len = strlen(ending); + + if(ending_len > str_len) return 0; + + return 0 == strcmp(str + (str_len - ending_len), ending) ? true : false; +} + +size_t xdl_util_trim_ending(char *start) +{ + char *end = start + strlen(start); + while(start < end && isspace((int)(*(end - 1)))) + { + end--; + *end = '\0'; + } + return (size_t)(end - start); +} + +static int xdl_util_get_api_level_from_build_prop(void) +{ + char buf[128]; + int api_level = -1; + + FILE *fp = fopen("/system/build.prop", "r"); + if(NULL == fp) goto end; + + while(fgets(buf, sizeof(buf), fp)) + { + if(xdl_util_starts_with(buf, "ro.build.version.sdk=")) + { + api_level = atoi(buf + 21); + break; + } + } + fclose(fp); + + end: + return (api_level > 0) ? api_level : -1; +} + +int xdl_util_get_api_level(void) +{ + static int xdl_util_api_level = -1; + + if(xdl_util_api_level < 0) + { + int api_level = android_get_device_api_level(); + if(api_level < 0) + api_level = xdl_util_get_api_level_from_build_prop(); // compatible with unusual models + if(api_level < __ANDROID_API_J__) + api_level = __ANDROID_API_J__; + + __atomic_store_n(&xdl_util_api_level, api_level, __ATOMIC_SEQ_CST); + } + + return xdl_util_api_level; +} diff --git a/xdl_lib/src/main/cpp/xdl_util.h b/xdl_lib/src/main/cpp/xdl_util.h new file mode 100644 index 0000000..d7a7c4c --- /dev/null +++ b/xdl_lib/src/main/cpp/xdl_util.h @@ -0,0 +1,45 @@ +// Copyright (c) 2020-present, HexHacking Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +// Created by caikelun on 2020-10-04. + +#ifndef IO_HEXHACKING_XDL_UTIL +#define IO_HEXHACKING_XDL_UTIL + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool xdl_util_starts_with(const char *str, const char *start); +bool xdl_util_ends_with(const char* str, const char* ending); + +size_t xdl_util_trim_ending(char *start); + +int xdl_util_get_api_level(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/xdl_sample/.gitignore b/xdl_sample/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/xdl_sample/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/xdl_sample/build.gradle b/xdl_sample/build.gradle new file mode 100644 index 0000000..c182852 --- /dev/null +++ b/xdl_sample/build.gradle @@ -0,0 +1,48 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + ndkVersion rootProject.ext.ndkVersion + + defaultConfig { + applicationId "io.hexhacking.xdl" + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 1 + versionName "1.0" + ndk { + abiFilters rootProject.ext.abiFilters.split(",") + } + } + + externalNativeBuild { + cmake { + path "src/main/cpp/CMakeLists.txt" + version rootProject.ext.cmakeVersion + } + } + + compileOptions { + sourceCompatibility rootProject.ext.javaVersion + targetCompatibility rootProject.ext.javaVersion + } + + buildTypes { + debug { + minifyEnabled false + } + release { + minifyEnabled false + } + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'com.google.android.material:material:1.2.1' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation project(':xdl_lib') +} + +apply from: rootProject.file('gradle/sanitizer.gradle') diff --git a/xdl_sample/src/main/AndroidManifest.xml b/xdl_sample/src/main/AndroidManifest.xml new file mode 100644 index 0000000..33af04c --- /dev/null +++ b/xdl_sample/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/xdl_sample/src/main/cpp/CMakeLists.txt b/xdl_sample/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000..f469458 --- /dev/null +++ b/xdl_sample/src/main/cpp/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.4.1) + +add_compile_options( + -std=c11 + -Weverything + -Werror) + +string(TOLOWER ${CMAKE_BUILD_TYPE} BUILD_TYPE_DIRNAME) +link_directories( + ../../../../xdl_lib/build/intermediates/stripped_native_libs/${BUILD_TYPE_DIRNAME}/out/lib/${ANDROID_ABI} +) + +add_library(sample SHARED + sample.c) + +target_include_directories(sample PUBLIC + ../../../../xdl_lib/src/main/cpp) + +target_link_libraries(sample + xdl + log) diff --git a/xdl_sample/src/main/cpp/sample.c b/xdl_sample/src/main/cpp/sample.c new file mode 100644 index 0000000..580e0b2 --- /dev/null +++ b/xdl_sample/src/main/cpp/sample.c @@ -0,0 +1,142 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xdl.h" + +#define SAMPLE_JNI_VERSION JNI_VERSION_1_6 +#define SAMPLE_JNI_CLASS_NAME "io/hexhacking/xdl/sample/NativeSample" + +// log +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" +#define LOG(fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, "xdl_tag", fmt, ##__VA_ARGS__) +#pragma clang diagnostic pop +#define LOG_END "*** --------------------------------------------------------------" + +#if defined(__LP64__) +#define BASENAME_LINKER "linker64" +#define PATHNAME_LIBC "/system/lib64/libc.so" +#define PATHNAME_LIBC_Q "/apex/com.android.runtime/lib64/bionic/libc.so" +#define PATHNAME_LIBCPP "/system/lib64/libc++.so" +#else +#define BASENAME_LINKER "linker" +#define PATHNAME_LIBC "/system/lib/libc.so" +#define PATHNAME_LIBC_Q "/apex/com.android.runtime/lib/bionic/libc.so" +#define PATHNAME_LIBCPP "/system/lib/libc++.so" +#endif + +#define PATHNAME_LIBC_FIXED (android_get_device_api_level() < __ANDROID_API_Q__ ? PATHNAME_LIBC : PATHNAME_LIBC_Q) + +static int callback(struct dl_phdr_info *info, size_t size, void *arg) +{ + (void)size, (void)arg; + + LOG(">>> %"PRIxPTR" %s", (uintptr_t)info->dlpi_addr, info->dlpi_name); + + return 0; +} + +static void sample_test_iterate(void) +{ + LOG("+++ xdl_iterate_phdr(XDL_DEFAULT)"); + xdl_iterate_phdr(callback, NULL, XDL_DEFAULT); + LOG(LOG_END); + usleep(100 * 1000); + + LOG("+++ xdl_iterate_phdr(XDL_WITH_LINKER)"); + xdl_iterate_phdr(callback, NULL, XDL_WITH_LINKER); + LOG(LOG_END); + usleep(100 * 1000); + + LOG("+++ xdl_iterate_phdr(XDL_FULL_PATHNAME)"); + xdl_iterate_phdr(callback, NULL, XDL_FULL_PATHNAME); + LOG(LOG_END); + usleep(100 * 1000); + + LOG("+++ xdl_iterate_phdr(XDL_WITH_LINKER | XDL_FULL_PATHNAME)"); + xdl_iterate_phdr(callback, NULL, XDL_WITH_LINKER | XDL_FULL_PATHNAME); + LOG(LOG_END); + usleep(100 * 1000); +} + +static void sample_test_dlsym(const char *filename, const char *symbol, bool debug_symbol) +{ + LOG("+++ xdl_open + %s + xdl_addr", debug_symbol ? "xdl_dsym" : "xdl_sym"); + + // xdl_open + void *symbol_addr = NULL; + void *handle = xdl_open(filename); + LOG(">>> xdl_open(%s) : handle %"PRIxPTR, filename, (uintptr_t)handle); + + // xdl_dsym / xdl_sym + if(NULL != handle) + { + symbol_addr = (debug_symbol ? xdl_dsym : xdl_sym)(handle, symbol); + xdl_close(handle); + } + LOG(">>> %s(%s) : addr %"PRIxPTR, debug_symbol ? "xdl_dsym" : "xdl_sym", symbol, (uintptr_t)symbol_addr); + + // xdl_addr + Dl_info info; + char buf[512]; + if(0 == xdl_addr(symbol_addr, &info, buf, sizeof(buf))) + LOG(">>> xdl_addr(%"PRIxPTR") : FAILED", (uintptr_t)symbol_addr); + else + LOG(">>> xdl_addr(%"PRIxPTR") : %"PRIxPTR" %s, %"PRIxPTR" %s", (uintptr_t)symbol_addr, + (uintptr_t)info.dli_fbase, (NULL == info.dli_fname ? "(NULL)" : info.dli_fname), + (uintptr_t)info.dli_saddr, (NULL == info.dli_sname ? "(NULL)" : info.dli_sname)); + + LOG(LOG_END); +} + +static void sample_test(JNIEnv *env, jobject thiz) +{ + (void)env; + (void)thiz; + + // iterate + sample_test_iterate(); + + // linker + sample_test_dlsym(BASENAME_LINKER, "__dl__ZL10g_dl_mutex", true); + + // libc.so + sample_test_dlsym(PATHNAME_LIBC_FIXED, "android_set_abort_message", false); + sample_test_dlsym(PATHNAME_LIBC_FIXED, "je_mallctl", true); + + // libc++.so + sample_test_dlsym(PATHNAME_LIBCPP, "_ZNSt3__14cerrE", false); + sample_test_dlsym(PATHNAME_LIBCPP, "abort_message", true); +} + +static JNINativeMethod sample_jni_methods[] = { + { + "nativeTest", + "()V", + (void *)sample_test + } +}; + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) +{ + JNIEnv *env; + jclass cls; + + (void)reserved; + + if(NULL == vm) return JNI_ERR; + if(JNI_OK != (*vm)->GetEnv(vm, (void **)&env, SAMPLE_JNI_VERSION)) return JNI_ERR; + if(NULL == env || NULL == *env) return JNI_ERR; + if(NULL == (cls = (*env)->FindClass(env, SAMPLE_JNI_CLASS_NAME))) return JNI_ERR; + if(0 != (*env)->RegisterNatives(env, cls, sample_jni_methods, sizeof(sample_jni_methods) / sizeof(sample_jni_methods[0]))) return JNI_ERR; + + return SAMPLE_JNI_VERSION; +} diff --git a/xdl_sample/src/main/java/io/hexhacking/xdl/sample/MainActivity.java b/xdl_sample/src/main/java/io/hexhacking/xdl/sample/MainActivity.java new file mode 100644 index 0000000..842dc00 --- /dev/null +++ b/xdl_sample/src/main/java/io/hexhacking/xdl/sample/MainActivity.java @@ -0,0 +1,20 @@ +package io.hexhacking.xdl.sample; + +import androidx.appcompat.app.AppCompatActivity; + +import android.os.Bundle; +import android.util.Log; +import android.view.View; + +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + } + + public void onTestClick(View view) { + NativeSample.test(); + } +} diff --git a/xdl_sample/src/main/java/io/hexhacking/xdl/sample/MyCustomApplication.java b/xdl_sample/src/main/java/io/hexhacking/xdl/sample/MyCustomApplication.java new file mode 100644 index 0000000..cdb1961 --- /dev/null +++ b/xdl_sample/src/main/java/io/hexhacking/xdl/sample/MyCustomApplication.java @@ -0,0 +1,16 @@ +package io.hexhacking.xdl.sample; + +import android.app.Application; +import android.content.Context; +import android.util.Log; + +public class MyCustomApplication extends Application { + + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + + System.loadLibrary("xdl"); + System.loadLibrary("sample"); + } +} diff --git a/xdl_sample/src/main/java/io/hexhacking/xdl/sample/NativeSample.java b/xdl_sample/src/main/java/io/hexhacking/xdl/sample/NativeSample.java new file mode 100644 index 0000000..6d89a9b --- /dev/null +++ b/xdl_sample/src/main/java/io/hexhacking/xdl/sample/NativeSample.java @@ -0,0 +1,9 @@ +package io.hexhacking.xdl.sample; + +public class NativeSample { + public static void test() { + nativeTest(); + } + + private static native void nativeTest(); +} diff --git a/xdl_sample/src/main/res/drawable-v24/ic_launcher_foreground.xml b/xdl_sample/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/xdl_sample/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/xdl_sample/src/main/res/drawable/ic_launcher_background.xml b/xdl_sample/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/xdl_sample/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xdl_sample/src/main/res/layout/activity_main.xml b/xdl_sample/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..cad0cad --- /dev/null +++ b/xdl_sample/src/main/res/layout/activity_main.xml @@ -0,0 +1,47 @@ + + + + + + + +