Repository: iqiyi/xHook Branch: master Commit: 391d0c410362 Files: 83 Total size: 253.2 KB Directory structure: gitextract_ohqx2nmx/ ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── LICENSE-docs ├── README.md ├── README.zh-CN.md ├── build_libs.sh ├── clean_libs.sh ├── docs/ │ ├── overview/ │ │ ├── android_plt_hook_overview.zh-CN.md │ │ └── code/ │ │ ├── .gitignore │ │ ├── build.sh │ │ ├── clean.sh │ │ ├── libtest/ │ │ │ └── jni/ │ │ │ ├── Android.mk │ │ │ ├── Application.mk │ │ │ ├── test.c │ │ │ └── test.h │ │ ├── main/ │ │ │ └── jni/ │ │ │ ├── Android.mk │ │ │ ├── Application.mk │ │ │ └── main.c │ │ └── run.sh │ └── xhooklogo.sketch ├── install_libs.sh ├── libbiz/ │ └── jni/ │ ├── Android.mk │ ├── Application.mk │ └── biz.c ├── libtest/ │ └── jni/ │ ├── Android.mk │ ├── Application.mk │ └── test.c ├── libxhook/ │ └── jni/ │ ├── Android.mk │ ├── Application.mk │ ├── queue.h │ ├── tree.h │ ├── xh_core.c │ ├── xh_core.h │ ├── xh_elf.c │ ├── xh_elf.h │ ├── xh_errno.h │ ├── xh_jni.c │ ├── xh_log.c │ ├── xh_log.h │ ├── xh_util.c │ ├── xh_util.h │ ├── xh_version.c │ ├── xh_version.h │ ├── xhook.c │ └── xhook.h └── xhookwrapper/ ├── .gitignore ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── qiyi/ │ │ ├── test/ │ │ │ ├── NativeHandler.java │ │ │ └── Test.java │ │ └── xhookwrapper/ │ │ └── MainActivity.java │ └── res/ │ ├── drawable/ │ │ └── ic_launcher_background.xml │ ├── drawable-v24/ │ │ └── ic_launcher_foreground.xml │ ├── layout/ │ │ └── activity_main.xml │ ├── mipmap-anydpi-v26/ │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ └── values/ │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── biz/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── qiyi/ │ │ └── biz/ │ │ ├── Biz.java │ │ └── NativeHandler.java │ └── res/ │ └── values/ │ └── strings.xml ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── xhook/ ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src/ └── main/ ├── AndroidManifest.xml ├── java/ │ └── com/ │ └── qiyi/ │ └── xhook/ │ ├── NativeHandler.java │ └── XHook.java └── res/ └── values/ └── strings.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store libxhook/libs/ libxhook/obj/ libbiz/libs/ libbiz/obj/ libtest/libs/ libtest/obj/ #for xhookwrapper build/ .gradle/ .idea/ local.properties *.iml *.log *.so xhookwrapper/xhook/libs/ ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to xHook Welcome to the xHook 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 iQIYI's 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 xHook, you agree that your contributions will be licensed under its [MIT LICENSE](LICENSE). ================================================ FILE: LICENSE ================================================ Copyright (c) 2018-present, iQIYI, Inc. All rights reserved. Most source code in xhook are MIT licensed. Some other source code have BSD-style licenses. A copy of the MIT License is included in this file. Source code Licensed under the BSD 2-Clause License =================================================== tree.h Copyright 2002 Niels Provos All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Source code Licensed under the BSD 3-Clause License =================================================== queue.h Copyright (c) 1991, 1993 The Regents of the University of California. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Terms of the MIT License ======================== 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. ================================================ FILE: LICENSE-docs ================================================ 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. ================================================ FILE: README.md ================================================

xhook

# xHook ![](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.2.0-red.svg?style=flat) ![](https://img.shields.io/badge/Android-4.0%20--%2010-blue.svg?style=flat) ![](https://img.shields.io/badge/arch-armeabi%20%7C%20armeabi--v7a%20%7C%20arm64--v8a%20%7C%20x86%20%7C%20x86__64-blue.svg?style=flat) [README 中文版](README.zh-CN.md) [Android PLT hook 概述 中文版](docs/overview/android_plt_hook_overview.zh-CN.md) xHook is a PLT (Procedure Linkage Table) hook library for Android native ELF (executable and shared libraries). xHook has been keeping optimized for stability and compatibility. ## Features * Support Android 4.0 - 10 (API level 14 - 29). * Support armeabi, armeabi-v7a, arm64-v8a, x86 and x86_64. * Support **ELF HASH** and **GNU HASH** indexed symbols. * Support **SLEB128** encoded relocation info. * Support setting hook info via regular expressions. * Do not require root permission or any system permissions. * Do not depends on any third-party shared libraries. ## Build * Download [Android NDK r16b](https://developer.android.com/ndk/downloads/revision_history.html), set environment PATH. (support for armeabi has been removed since r17) * Build and install the native libraries. ``` ./build_libs.sh ./install_libs.sh ``` ## Demo ``` cd ./xhookwrapper/ ./gradlew assembleDebug adb install ./app/build/outputs/apk/debug/app-debug.apk ``` ## API External API header file: `libxhook/jni/xhook.h` ### 1. Register hook info ```c int xhook_register(const char *pathname_regex_str, const char *symbol, void *new_func, void **old_func); ``` In current process's memory space, in every loaded ELF which pathname matches regular expression `pathname_regex_str`, every PLT entries to `symbol` will be **replaced with** `new_func`. The original one will be saved in `old_func`. The `new_func` **must** have the same function declaration as the original one. Return zero if successful, non-zero otherwise. The regular expression for `pathname_regex_str` only support **POSIX BRE (Basic Regular Expression)**. ### 2. Ignore some hook info ```c int xhook_ignore(const char *pathname_regex_str, const char *symbol); ``` Ignore some hook info according to `pathname_regex_str` and `symbol`, from registered hooks by `xhook_register`. If `symbol` is `NULL`, xhook will ignore all symbols from ELF which pathname matches `pathname_regex_str`. Return zero if successful, non-zero otherwise. The regular expression for `pathname_regex_str` only support **POSIX BRE**. ### 3. Do hook ```c int xhook_refresh(int async); ``` Do the real hook operations according to the registered hook info. Pass `1` to `async` for asynchronous hook. Pass `0` to `async` for synchronous hook. Return zero if successful, non-zero otherwise. xhook will keep a global cache for saving the last ELF loading info from `/proc/self/maps`. This cache will also be updated in `xhook_refresh`. With this cache, `xhook_refresh` can determine which ELF is newly loaded. We only need to do hook in these newly loaded ELF. ### 4. Clear cache ```c void xhook_clear(); ``` Clear all cache owned by xhook, reset all global flags to default value. If you confirm that all PLT entries you want have been hooked, you could call this function to save some memory. ### 5. Enable/Disable debug info ```c void xhook_enable_debug(int flag); ``` Pass `1` to `flag` for enable debug info. Pass `0` to `flag` for disable. (**disabled** by default) Debug info will be sent to logcat with tag `xhook`. ### 6. Enable/Disable SFP (segmentation fault protection) ```c void xhook_enable_sigsegv_protection(int flag); ``` Pass `1` to `flag` for enable SFP. Pass `0` to `flag` for disable. (**enabled** by default) xhook is NOT a compliant business layer library. We have to calculate the value of some pointers directly. Reading or writing the memory pointed to by these pointers will cause a segmentation fault in some unusual situations and environment. The APP crash rate increased which caused by xhook is about one ten-millionth (0.0000001) according to our test. (The increased crash rate is also related to the ELFs and symbols you need to hook). Finally, we have to use some trick to prevent this harmless crashing. We called it SFP (segmentation fault protection) which consists of: `sigaction()`, `SIGSEGV`, `siglongjmp()` and `sigsetjmp()`. **You should always enable SFP for release-APP, this will prevent your app from crashing. On the other hand, you should always disable SFP for debug-APP, so you can't miss any common coding mistakes that should be fixed.** ## Examples ```c //detect memory leaks xhook_register(".*\\.so$", "malloc", my_malloc, NULL); xhook_register(".*\\.so$", "calloc", my_calloc, NULL); xhook_register(".*\\.so$", "realloc", my_realloc, NULL); xhook_register(".*\\.so$", "free", my_free, NULL); //inspect sockets lifecycle xhook_register(".*\\.so$", "getaddrinfo", my_getaddrinfo, NULL); xhook_register(".*\\.so$", "socket", my_socket, NULL); xhook_register(".*\\.so$", "setsockopt" my_setsockopt, NULL); xhook_register(".*\\.so$", "bind", my_bind, NULL); xhook_register(".*\\.so$", "listen", my_listen, NULL); xhook_register(".*\\.so$", "connect", my_connect, NULL); xhook_register(".*\\.so$", "shutdown", my_shutdown, NULL); xhook_register(".*\\.so$", "close", my_close, NULL); //filter off and save some android log to local file xhook_register(".*\\.so$", "__android_log_write", my_log_write, NULL); xhook_register(".*\\.so$", "__android_log_print", my_log_print, NULL); xhook_register(".*\\.so$", "__android_log_vprint", my_log_vprint, NULL); xhook_register(".*\\.so$", "__android_log_assert", my_log_assert, NULL); //tracking (ignore linker and linker64) xhook_register("^/system/.*$", "mmap", my_mmap, NULL); xhook_register("^/vendor/.*$", "munmap", my_munmap, NULL); xhook_ignore (".*/linker$", "mmap"); xhook_ignore (".*/linker$", "munmap"); xhook_ignore (".*/linker64$", "mmap"); xhook_ignore (".*/linker64$", "munmap"); //defense to some injection attacks xhook_register(".*com\\.hacker.*\\.so$", "malloc", my_malloc_always_return_NULL, NULL); xhook_register(".*/libhacker\\.so$", "connect", my_connect_with_recorder, NULL); //fix some system bug xhook_register(".*some_vendor.*/libvictim\\.so$", "bad_func", my_nice_func, NULL); //ignore all hooks in libwebviewchromium.so xhook_ignore(".*/libwebviewchromium.so$", NULL); //hook now! xhook_refresh(1); ``` ## Support * [GitHub Issues](https://github.com/iqiyi/xHook/issues) * [GitHub Discussions](https://github.com/iqiyi/xHook/discussions) ## Contributing See [xHook Contributing Guide](CONTRIBUTING.md). ## License xHook is MIT licensed, as found in the [LICENSE](LICENSE) file. xHook documentation is Creative Commons licensed, as found in the [LICENSE-docs](LICENSE-docs) file. ================================================ FILE: README.zh-CN.md ================================================

xhook

# xHook ![](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.2.0-red.svg?style=flat) ![](https://img.shields.io/badge/Android-4.0%20--%2010-blue.svg?style=flat) ![](https://img.shields.io/badge/arch-armeabi%20%7C%20armeabi--v7a%20%7C%20arm64--v8a%20%7C%20x86%20%7C%20x86__64-blue.svg?style=flat) [README English Version](README.md) [Android PLT hook 概述 中文版](docs/overview/android_plt_hook_overview.zh-CN.md) xHook 是一个针对 Android 平台 ELF (可执行文件和动态库) 的 PLT (Procedure Linkage Table) hook 库。 xHook 一直在稳定性和兼容性方面做着持续的优化。 ## 特征 * 支持 Android 4.0 - 10(API level 14 - 29)。 * 支持 armeabi,armeabi-v7a,arm64-v8a,x86 和 x86_64。 * 支持 **ELF HASH** 和 **GNU HASH** 索引的符号。 * 支持 **SLEB128** 编码的重定位信息。 * 支持通过正则表达式批量设置 hook 信息。 * 不需要 root 权限或任何系统权限。 * 不依赖于任何的第三方动态库。 ## 编译 * 下载 [Android NDK r16b](https://developer.android.com/ndk/downloads/revision_history.html),设置 PATH 环境变量。(对 armeabi 的支持,从 r17 版本开始被移除了) * 编译和安装 native 库。 ``` ./build_libs.sh ./install_libs.sh ``` ## Demo ``` cd ./xhookwrapper/ ./gradlew assembleDebug adb install ./app/build/outputs/apk/debug/app-debug.apk ``` ## API 外部 API 头文件: `libxhook/jni/xhook.h` ### 1. 注册 hook 信息 ```c int xhook_register(const char *pathname_regex_str, const char *symbol, void *new_func, void **old_func); ``` 在当前进程的内存空间中,在每一个符合正则表达式 `pathname_regex_str` 的已加载ELF中,每一个调用 `symbol` 的 PLT 入口点的地址值都将给替换成 `new_func`。之前的 PLT 入口点的地址值将被保存在 `old_func` 中。 `new_func` 必须具有和原函数同样的函数声明。 成功返回 0,失败返回 非0。 `pathname_regex_str` 只支持 **POSIX BRE (Basic Regular Expression)** 定义的正则表达式语法。 ### 2. 忽略部分 hook 信息 ```c int xhook_ignore(const char *pathname_regex_str, const char *symbol); ``` 根据 `pathname_regex_str` 和 `symbol`,从已经通过 `xhook_register` 注册的 hook 信息中,忽略一部分 hook 信息。如果 `symbol` 为 `NULL`,xhook 将忽略所有路径名符合正则表达式 `pathname_regex_str` 的 ELF。 成功返回 0,失败返回 非0。 `pathname_regex_str` 只支持 **POSIX BRE** 定义的正则表达式语法。 ### 3. 执行 hook ```c int xhook_refresh(int async); ``` 根据前面注册的 hook 信息,执行真正的 hook 操作。 给 `async` 参数传 `1` 表示执行异步的 hook 操作,传 `0` 表示执行同步的 hook 操作。 成功返回 0,失败返回 非0。 xhook 在内部维护了一个全局的缓存,用于保存最后一次从 `/proc/self/maps` 读取到的 ELF 加载信息。每次一调用 `xhook_refresh` 函数,这个缓存都将被更新。xhook 使用这个缓存来判断哪些 ELF 是这次新被加载到内存中的。我们每次只需要针对这些新加载的 ELF 做 hook 就可以了。 ### 4. 清除缓存 ```c void xhook_clear(); ``` 清除 xhook 的缓存,重置所有的全局标示。 如果你确定你需要的所有 PLT 入口点都已经被替换了,你可以调用这个函数来释放和节省一些内存空间。 ### 5. 启用/禁用 调试信息 ```c void xhook_enable_debug(int flag); ``` 给 `flag` 参数传 `1` 表示启用调试信息,传 `0` 表示禁用调试信息。 (默认为:**禁用**) 调试信息将被输出到 logcat,对应的 TAG 为:`xhook`。 ### 6. 启用/禁用 SFP (段错误保护) ```c void xhook_enable_sigsegv_protection(int flag); ``` 给 `flag` 参数传 `1` 表示启用 SFP,传 `0` 表示禁用 SFP。 (默认为:**启用**) xhook 并不是一个常规的业务层的动态库。在 xhook 中,我们不得不直接计算一些内存指针的值。在一些极端的情况和环境下,读或者写这些指针指向的内存会发生段错误。根据我们的测试,xhook 的行为将导致 APP 崩溃率增加 “一千万分之一” (0.0000001)。(具体崩溃率可能会增加多少,也和你想要 hook 的库和符号有关)。最终,我们不得不使用某些方法来防止这些无害的崩溃。我们叫它SFP (段错误保护),它是由这些调用和值组成的:`sigaction()`, `SIGSEGV`, `siglongjmp()` 和 `sigsetjmp()`。 **在 release 版本的 APP 中,你应该始终启用 SFP,这能防止你的 APP 因为 xhook 而崩溃。在 debug 版本的 APP 中,你应该始终禁用 SFP,这样你就不会丢失那些一般性的编码失误导致的段错误,这些段错误是应该被修复的。** ## 例子 ```c //监测内存泄露 xhook_register(".*\\.so$", "malloc", my_malloc, NULL); xhook_register(".*\\.so$", "calloc", my_calloc, NULL); xhook_register(".*\\.so$", "realloc", my_realloc, NULL); xhook_register(".*\\.so$", "free", my_free, NULL); //监控 sockets 生命周期 xhook_register(".*\\.so$", "getaddrinfo", my_getaddrinfo, NULL); xhook_register(".*\\.so$", "socket", my_socket, NULL); xhook_register(".*\\.so$", "setsockopt" my_setsockopt, NULL); xhook_register(".*\\.so$", "bind", my_bind, NULL); xhook_register(".*\\.so$", "listen", my_listen, NULL); xhook_register(".*\\.so$", "connect", my_connect, NULL); xhook_register(".*\\.so$", "shutdown", my_shutdown, NULL); xhook_register(".*\\.so$", "close", my_close, NULL); //过滤出和保存部分安卓 log 到本地文件 xhook_register(".*\\.so$", "__android_log_write", my_log_write, NULL); xhook_register(".*\\.so$", "__android_log_print", my_log_print, NULL); xhook_register(".*\\.so$", "__android_log_vprint", my_log_vprint, NULL); xhook_register(".*\\.so$", "__android_log_assert", my_log_assert, NULL); //追踪某些调用 (忽略 linker 和 linker64) xhook_register("^/system/.*$", "mmap", my_mmap, NULL); xhook_register("^/vendor/.*$", "munmap", my_munmap, NULL); xhook_ignore (".*/linker$", "mmap"); xhook_ignore (".*/linker$", "munmap"); xhook_ignore (".*/linker64$", "mmap"); xhook_ignore (".*/linker64$", "munmap"); //防御某些注入攻击 xhook_register(".*com\\.hacker.*\\.so$", "malloc", my_malloc_always_return_NULL, NULL); xhook_register(".*/libhacker\\.so$", "connect", my_connect_with_recorder, NULL); //修复某些系统 bug xhook_register(".*some_vendor.*/libvictim\\.so$", "bad_func", my_nice_func, NULL); //忽略 libwebviewchromium.so 的所有 hook 信息 xhook_ignore(".*/libwebviewchromium.so$", NULL); //现在执行 hook! xhook_refresh(1); ``` ## 技术支持 * [GitHub Issues](https://github.com/iqiyi/xHook/issues) * [GitHub Discussions](https://github.com/iqiyi/xHook/discussions) ## 贡献 请阅读 [xHook Contributing Guide](CONTRIBUTING.md)。 ## 许可证 xHook 使用 [MIT 许可证](LICENSE)。 xHook 的文档使用 [Creative Commons 许可证](LICENSE-docs)。 ================================================ FILE: build_libs.sh ================================================ #!/bin/bash ndk-build -C ./libxhook/jni ndk-build -C ./libbiz/jni ndk-build -C ./libtest/jni ================================================ FILE: clean_libs.sh ================================================ #!/bin/bash ndk-build -C ./libbiz/jni clean ndk-build -C ./libxhook/jni clean ndk-build -C ./libtest/jni clean ================================================ FILE: docs/overview/android_plt_hook_overview.zh-CN.md ================================================ # Android PLT hook 概述 ## 获取代码和资源 你始终可以从 [这里](https://github.com/iqiyi/xHook/blob/master/docs/overview/android_plt_hook_overview.zh-CN.md) 访问本文的最新版本。 文中使用的示例代码可以从 [这里](https://github.com/iqiyi/xHook/tree/master/docs/overview/code) 获取。文中提到的 xhook 开源项目可以从 [这里](https://github.com/iqiyi/xHook) 获取。 ## 开始 ### 新的动态库 我们有一个新的动态库:libtest.so。 > 头文件 test.h ```c #ifndef TEST_H #define TEST_H 1 #ifdef __cplusplus extern "C" { #endif void say_hello(); #ifdef __cplusplus } #endif #endif ``` > 源文件 test.c ```c #include #include void say_hello() { char *buf = malloc(1024); if(NULL != buf) { snprintf(buf, 1024, "%s", "hello\n"); printf("%s", buf); } } ``` `say_hello` 的功能是在终端打印出 `hello\n` 这6个字符(包括结尾的 `\n`)。 我们需要一个测试程序:main。 > 源文件 main.c ```c #include int main() { say_hello(); return 0; } ``` 编译它们分别生成 libtest.so 和 main。运行一下: ``` caikelun@debian:~$ adb push ./libtest.so ./main /data/local/tmp caikelun@debian:~$ adb shell "chmod +x /data/local/tmp/main" caikelun@debian:~$ adb shell "export LD_LIBRARY_PATH=/data/local/tmp; /data/local/tmp/main" hello caikelun@debian:~$ ``` 太棒了!libtest.so 的代码虽然看上去有些愚蠢,但是它居然可以正确的工作,那还有什么可抱怨的呢?赶紧在新版 APP 中开始使用它吧! 遗憾的是,正如你可能已经发现的,libtest.so 存在严重的内存泄露问题,每调用一次 `say_hello` 函数,就会泄露 1024 字节的内存。新版 APP 上线后崩溃率开始上升,各种诡异的崩溃信息和报障信息跌撞而至。 ### 面临的问题 幸运的是,我们修复了 libtest.so 的问题。可是以后怎么办呢?我们面临 2 个问题: 1. 当测试覆盖不足时,如何及时发现和准确定位线上 APP 的此类问题? 2. 如果 libtest.so 是某些机型的系统库,或者第三方的闭源库,我们如何修复它?如果监控它的行为? ### 怎么做? 如果我们能对动态库中的函数调用做 hook(替换,拦截,窃听,或者你觉得任何正确的描述方式),那就能够做到很多我们想做的事情。比如 hook `malloc`,`calloc`,`realloc` 和 `free`,我们就能统计出各个动态库分配了多少内存,哪些内存一直被占用没有释放。 这真的能做到吗?答案是:hook 我们自己的进程是完全可以的。hook 其他进程需要 root 权限(对于其他进程,没有 root 权限就没法修改它的内存空间,也没法注入代码)。幸运的是,我们只要 hook 自己就够了。 ## ELF ### 概述 ELF(Executable and Linkable Format)是一种行业标准的二进制数据封装格式,主要用于封装可执行文件、动态库、object 文件和 core dumps 文件。 使用 google NDK 对源代码进行编译和链接,生成的动态库或可执行文件都是 ELF 格式的。用 readelf 可以查看 ELF 文件的基本信息,用 objdump 可以查看 ELF 文件的反汇编输出。 ELF 格式的概述可以参考 [这里](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format),完整定义可以参考 [这里](http://refspecs.linuxbase.org/elf/elf.pdf)。其中最重要的部分是:ELF 文件头、SHT(section header table)、PHT(program header table)。 ### ELF 文件头 ELF 文件的起始处,有一个固定格式的定长的文件头(32 位架构为 52 字节,64 位架构为 64 字节)。ELF 文件头以 magic number `0x7F 0x45 0x4C 0x46` 开始(其中后 3 个字节分别对应可见字符 `E` `L` `F`)。 libtest.so 的 ELF 文件头信息: ``` caikelun@debian:~$ arm-linux-androideabi-readelf -h ./libtest.so ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: ARM Version: 0x1 Entry point address: 0x0 Start of program headers: 52 (bytes into file) Start of section headers: 12744 (bytes into file) Flags: 0x5000200, Version5 EABI, soft-float ABI Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 8 Size of section headers: 40 (bytes) Number of section headers: 25 Section header string table index: 24 ``` ELF 文件头中包含了 SHT 和 PHT 在当前 ELF 文件中的起始位置和长度。例如,libtest.so 的 SHT 起始位置为 12744,长度 40 字节;PHT 起始位置为 52,长度 32字节。 ![](https://raw.githubusercontent.com/iqiyi/xHook/master/docs/overview/res/elfheader.png) ### SHT(section header table) ELF 以 section 为单位来组织和管理各种信息。ELF 使用 SHT 来记录所有 section 的基本信息。主要包括:section 的类型、在文件中的偏移量、大小、加载到内存后的虚拟内存相对地址、内存中字节的对齐方式等。 libtest.so 的 SHT: ``` caikelun@debian:~$ arm-linux-androideabi-readelf -S ./libtest.so There are 25 section headers, starting at offset 0x31c8: Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .note.android.ide NOTE 00000134 000134 000098 00 A 0 0 4 [ 2] .note.gnu.build-i NOTE 000001cc 0001cc 000024 00 A 0 0 4 [ 3] .dynsym DYNSYM 000001f0 0001f0 0003a0 10 A 4 1 4 [ 4] .dynstr STRTAB 00000590 000590 0004b1 00 A 0 0 1 [ 5] .hash HASH 00000a44 000a44 000184 04 A 3 0 4 [ 6] .gnu.version VERSYM 00000bc8 000bc8 000074 02 A 3 0 2 [ 7] .gnu.version_d VERDEF 00000c3c 000c3c 00001c 00 A 4 1 4 [ 8] .gnu.version_r VERNEED 00000c58 000c58 000020 00 A 4 1 4 [ 9] .rel.dyn REL 00000c78 000c78 000040 08 A 3 0 4 [10] .rel.plt REL 00000cb8 000cb8 0000f0 08 AI 3 18 4 [11] .plt PROGBITS 00000da8 000da8 00017c 00 AX 0 0 4 [12] .text PROGBITS 00000f24 000f24 0015a4 00 AX 0 0 4 [13] .ARM.extab PROGBITS 000024c8 0024c8 00003c 00 A 0 0 4 [14] .ARM.exidx ARM_EXIDX 00002504 002504 000100 08 AL 12 0 4 [15] .fini_array FINI_ARRAY 00003e3c 002e3c 000008 04 WA 0 0 4 [16] .init_array INIT_ARRAY 00003e44 002e44 000004 04 WA 0 0 1 [17] .dynamic DYNAMIC 00003e48 002e48 000118 08 WA 4 0 4 [18] .got PROGBITS 00003f60 002f60 0000a0 00 WA 0 0 4 [19] .data PROGBITS 00004000 003000 000004 00 WA 0 0 4 [20] .bss NOBITS 00004004 003004 000000 00 WA 0 0 1 [21] .comment PROGBITS 00000000 003004 000065 01 MS 0 0 1 [22] .note.gnu.gold-ve NOTE 00000000 00306c 00001c 00 0 0 4 [23] .ARM.attributes ARM_ATTRIBUTES 00000000 003088 00003b 00 0 0 1 [24] .shstrtab STRTAB 00000000 0030c3 000102 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), y (noread), p (processor specific) ``` 比较重要,且和 hook 关系比较大的几个 section 是: * `.dynstr`:保存了所有的字符串常量信息。 * `.dynsym`:保存了符号(symbol)的信息(符号的类型、起始地址、大小、符号名称在 `.dynstr` 中的索引编号等)。函数也是一种符号。 * `.text`:程序代码经过编译后生成的机器指令。 * `.dynamic`:供动态链接器使用的各项信息,记录了当前 ELF 的外部依赖,以及其他各个重要 section 的起始位置等信息。 * `.got`:Global Offset Table。用于记录外部调用的入口地址。动态链接器(linker)执行重定位(relocate)操作时,这里会被填入真实的外部调用的绝对地址。 * `.plt`:Procedure Linkage Table。外部调用的跳板,主要用于支持 lazy binding 方式的外部调用重定位。(Android 目前只有 MIPS 架构支持 lazy binding) * `.rel.plt`:对外部函数直接调用的重定位信息。 * `.rel.dyn`:除 `.rel.plt` 以外的重定位信息。(比如通过全局函数指针来调用外部函数) ![](https://raw.githubusercontent.com/iqiyi/xHook/master/docs/overview/res/elfpltgot.png) ### PHT(program header table) ELF 被加载到内存时,是以 segment 为单位的。一个 segment 包含了一个或多个 section。ELF 使用 PHT 来记录所有 segment 的基本信息。主要包括:segment 的类型、在文件中的偏移量、大小、加载到内存后的虚拟内存相对地址、内存中字节的对齐方式等。 libtest.so 的 PHT: ``` caikelun@debian:~$ arm-linux-androideabi-readelf -l ./libtest.so Elf file type is DYN (Shared object file) Entry point 0x0 There are 8 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x00000034 0x00000034 0x00100 0x00100 R 0x4 LOAD 0x000000 0x00000000 0x00000000 0x02604 0x02604 R E 0x1000 LOAD 0x002e3c 0x00003e3c 0x00003e3c 0x001c8 0x001c8 RW 0x1000 DYNAMIC 0x002e48 0x00003e48 0x00003e48 0x00118 0x00118 RW 0x4 NOTE 0x000134 0x00000134 0x00000134 0x000bc 0x000bc R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10 EXIDX 0x002504 0x00002504 0x00002504 0x00100 0x00100 R 0x4 GNU_RELRO 0x002e3c 0x00003e3c 0x00003e3c 0x001c4 0x001c4 RW 0x4 Section to Segment mapping: Segment Sections... 00 01 .note.android.ident .note.gnu.build-id .dynsym .dynstr .hash .gnu.version .gnu.version_d .gnu.version_r .rel.dyn .rel.plt .plt .text .ARM.extab .ARM.exidx 02 .fini_array .init_array .dynamic .got .data 03 .dynamic 04 .note.android.ident .note.gnu.build-id 05 06 .ARM.exidx 07 .fini_array .init_array .dynamic .got ``` 所有类型为 `PT_LOAD` 的 segment 都会被动态链接器(linker)映射(mmap)到内存中。 ### 连接视图(Linking View)和执行视图(Execution View) * 连接视图:ELF 未被加载到内存执行前,以 section 为单位的数据组织形式。 * 执行视图:ELF 被加载到内存后,以 segment 为单位的数据组织形式。 我们关心的 hook 操作,属于动态形式的内存操作,因此主要关心的是执行视图,即 ELF 被加载到内存后,ELF 中的数据是如何组织和存放的。 ![](https://raw.githubusercontent.com/iqiyi/xHook/master/docs/overview/res/elfview.png) ### .dynamic section 这是一个十分重要和特殊的 section,其中包含了 ELF 中其他各个 section 的内存位置等信息。在执行视图中,总是会存在一个类型为 `PT_DYNAMIC` 的 segment,这个 segment 就包含了 .dynamic section 的内容。 无论是执行 hook 操作时,还是动态链接器执行动态链接时,都需要通过 `PT_DYNAMIC` segment 来找到 .dynamic section 的内存位置,再进一步读取其他各项 section 的信息。 libtest.so 的 .dynamic section: ``` caikelun@debian:~$ arm-linux-androideabi-readelf -d ./libtest.so Dynamic section at offset 0x2e48 contains 30 entries: Tag Type Name/Value 0x00000003 (PLTGOT) 0x3f7c 0x00000002 (PLTRELSZ) 240 (bytes) 0x00000017 (JMPREL) 0xcb8 0x00000014 (PLTREL) REL 0x00000011 (REL) 0xc78 0x00000012 (RELSZ) 64 (bytes) 0x00000013 (RELENT) 8 (bytes) 0x6ffffffa (RELCOUNT) 3 0x00000006 (SYMTAB) 0x1f0 0x0000000b (SYMENT) 16 (bytes) 0x00000005 (STRTAB) 0x590 0x0000000a (STRSZ) 1201 (bytes) 0x00000004 (HASH) 0xa44 0x00000001 (NEEDED) Shared library: [libc.so] 0x00000001 (NEEDED) Shared library: [libm.so] 0x00000001 (NEEDED) Shared library: [libstdc++.so] 0x00000001 (NEEDED) Shared library: [libdl.so] 0x0000000e (SONAME) Library soname: [libtest.so] 0x0000001a (FINI_ARRAY) 0x3e3c 0x0000001c (FINI_ARRAYSZ) 8 (bytes) 0x00000019 (INIT_ARRAY) 0x3e44 0x0000001b (INIT_ARRAYSZ) 4 (bytes) 0x0000001e (FLAGS) BIND_NOW 0x6ffffffb (FLAGS_1) Flags: NOW 0x6ffffff0 (VERSYM) 0xbc8 0x6ffffffc (VERDEF) 0xc3c 0x6ffffffd (VERDEFNUM) 1 0x6ffffffe (VERNEED) 0xc58 0x6fffffff (VERNEEDNUM) 1 0x00000000 (NULL) 0x0 ``` ## 动态链接器(linker) 安卓中的动态链接器程序是 linker。源码在 [这里](https://android.googlesource.com/platform/bionic/+/master/linker/)。 动态链接(比如执行 dlopen)的大致步骤是: 1. 检查已加载的 ELF 列表。(如果 libtest.so 已经加载,就不再重复加载了,仅把 libtest.so 的引用计数加一,然后直接返回。) 2. 从 libtest.so 的 .dynamic section 中读取 libtest.so 的外部依赖的 ELF 列表,从此列表中剔除已加载的 ELF,最后得到本次需要加载的 ELF 完整列表(包括 libtest.so 自身)。 3. 逐个加载列表中的 ELF。加载步骤: * 用 `mmap` 预留一块足够大的内存,用于后续映射 ELF。(`MAP_PRIVATE` 方式) * 读 ELF 的 PHT,用 `mmap` 把所有类型为 `PT_LOAD` 的 segment 依次映射到内存中。 * 从 .dynamic segment 中读取各信息项,主要是各个 section 的虚拟内存相对地址,然后计算并保存各个 section 的虚拟内存绝对地址。 * 执行重定位操作(relocate),这是最关键的一步。重定位信息可能存在于下面的一个或多个 secion 中:`.rel.plt`, `.rela.plt`, `.rel.dyn`, `.rela.dyn`, `.rel.android`, `.rela.android`。动态链接器需要逐个处理这些 `.relxxx` section 中的重定位诉求。根据已加载的 ELF 的信息,动态链接器查找所需符号的地址(比如 libtest.so 的符号 `malloc`),找到后,将地址值填入 `.relxxx` 中指明的**目标地址**中,这些“**目标地址**”一般存在于`.got` 或 `.data` 中。 * ELF 的引用计数加一。 4. 逐个调用列表中 ELF 的构造函数(constructor),这些构造函数的地址是之前从 .dynamic segment 中读取到的(类型为 `DT_INIT` 和 `DT_INIT_ARRAY`)。各 ELF 的构造函数是按照依赖关系逐层调用的,先调用被依赖 ELF 的构造函数,最后调用 libtest.so 自己的构造函数。(ELF 也可以定义自己的析构函数(destructor),在 ELF 被 unload 的时候会被自动调用) 等一下!我们似乎发现了什么!再看一遍重定位操作(relocate)的部分。难道我们只要从这些 `.relxxx` 中获取到“**目标地址**”,然后在“**目标地址**”中重新填上一个新的函数地址,这样就完成 hook 了吗?也许吧。 ## 追踪 静态分析验证一下还是很容易的。以 armeabi-v7a 架构的 libtest.so 为例。先看一下 say_hello 函数对应的汇编代码吧。 ``` caikelun@debian:~/$ arm-linux-androideabi-readelf -s ./libtest.so Symbol table '.dynsym' contains 58 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 FUNC GLOBAL DEFAULT UND __cxa_finalize@LIBC (2) 2: 00000000 0 FUNC GLOBAL DEFAULT UND snprintf@LIBC (2) 3: 00000000 0 FUNC GLOBAL DEFAULT UND malloc@LIBC (2) 4: 00000000 0 FUNC GLOBAL DEFAULT UND __cxa_atexit@LIBC (2) 5: 00000000 0 FUNC GLOBAL DEFAULT UND printf@LIBC (2) 6: 00000f61 60 FUNC GLOBAL DEFAULT 12 say_hello ............... ............... ``` 找到了!`say_hello` 在地址 `f61`,对应的汇编指令体积为 `60`(10 进制)字节。用 objdump 查看 `say_hello` 的反汇编输出。 ``` caikelun@debian:~$ arm-linux-androideabi-objdump -D ./libtest.so ............... ............... 00000f60 : f60: b5b0 push {r4, r5, r7, lr} f62: af02 add r7, sp, #8 f64: f44f 6080 mov.w r0, #1024 ; 0x400 f68: f7ff ef34 blx dd4 f6c: 4604 mov r4, r0 f6e: b16c cbz r4, f8c f70: a507 add r5, pc, #28 ; (adr r5, f90 ) f72: a308 add r3, pc, #32 ; (adr r3, f94 ) f74: 4620 mov r0, r4 f76: f44f 6180 mov.w r1, #1024 ; 0x400 f7a: 462a mov r2, r5 f7c: f7ff ef30 blx de0 f80: 4628 mov r0, r5 f82: 4621 mov r1, r4 f84: e8bd 40b0 ldmia.w sp!, {r4, r5, r7, lr} f88: f001 ba96 b.w 24b8 <_Unwind_GetTextRelBase@@Base+0x8> f8c: bdb0 pop {r4, r5, r7, pc} f8e: bf00 nop f90: 7325 strb r5, [r4, #12] f92: 0000 movs r0, r0 f94: 6568 str r0, [r5, #84] ; 0x54 f96: 6c6c ldr r4, [r5, #68] ; 0x44 f98: 0a6f lsrs r7, r5, #9 f9a: 0000 movs r0, r0 ............... ............... ``` 对 `malloc` 函数的调用对应于指令 `blx dd4`。跳转到了地址 `dd4`。看看这个地址里有什么吧: ``` caikelun@debian:~$ arm-linux-androideabi-objdump -D ./libtest.so ............... ............... 00000dd4 : dd4: e28fc600 add ip, pc, #0, 12 dd8: e28cca03 add ip, ip, #12288 ; 0x3000 ddc: e5bcf1b4 ldr pc, [ip, #436]! ; 0x1b4 ............... ............... ``` 果然,跳转到了 `.plt` 中,经过了几次地址计算,最后跳转到了地址 `3f90` 中的值指向的地址处,`3f90` 是个函数指针。 稍微解释一下:因为 arm 处理器使用 3 级流水线,所以第一条指令取到的 `pc` 的值是当前执行的指令地址 + `8`。 于是:`dd4` + `8` + `3000` + `1b4` = `3f90`。 地址 `3f90` 在哪里呢: ``` caikelun@debian:~$ arm-linux-androideabi-objdump -D ./libtest.so ............... ............... 00003f60 <.got>: ... 3f70: 00002604 andeq r2, r0, r4, lsl #12 3f74: 00002504 andeq r2, r0, r4, lsl #10 ... 3f88: 00000da8 andeq r0, r0, r8, lsr #27 3f8c: 00000da8 andeq r0, r0, r8, lsr #27 3f90: 00000da8 andeq r0, r0, r8, lsr #27 ............... ............... ``` 果然,在 `.got` 里。 顺便再看一下 `.rel.plt`: ``` caikelun@debian:~$ arm-linux-androideabi-readelf -r ./libtest.so Relocation section '.rel.plt' at offset 0xcb8 contains 30 entries: Offset Info Type Sym.Value Sym. Name 00003f88 00000416 R_ARM_JUMP_SLOT 00000000 __cxa_atexit@LIBC 00003f8c 00000116 R_ARM_JUMP_SLOT 00000000 __cxa_finalize@LIBC 00003f90 00000316 R_ARM_JUMP_SLOT 00000000 malloc@LIBC ............... ............... ``` `malloc` 的地址居然正好存放在 `3f90` 里,这绝对不是巧合啊!还等什么,赶紧改代码吧。我们的 main.c 应该改成这样: ```c #include void *my_malloc(size_t size) { printf("%zu bytes memory are allocated by libtest.so\n", size); return malloc(size); } int main() { void **p = (void **)0x3f90; *p = (void *)my_malloc; // do hook say_hello(); return 0; } ``` 编译运行一下: ``` caikelun@debian:~$ adb push ./main /data/local/tmp caikelun@debian:~$ adb shell "chmod +x /data/local/tmp/main" caikelun@debian:~$ adb shell "export LD_LIBRARY_PATH=/data/local/tmp; /data/local/tmp/main" Segmentation fault caikelun@debian:~$ ``` 思路是正确的。但之所以还是失败了,是因为这段代码存在下面的 3 个问题: 1. `3f90` 是个相对内存地址,需要把它换算成绝对地址。 2. `3f90` 对应的绝对地址很可能没有写入权限,直接对这个地址赋值会引起段错误。 3. 新的函数地址即使赋值成功了,`my_malloc` 也不会被执行,因为处理器有指令缓存(instruction cache)。 我们需要解决这些问题。 ## 内存 ### 基地址 在进程的内存空间中,各种 ELF 的加载地址是随机的,只有在运行时才能拿到加载地址,也就是**基地址**。我们需要知道 ELF 的基地址,才能将相对地址换算成绝对地址。 没有错,熟悉 Linux 开发的聪明的你一定知道,我们可以直接调用 `dl_iterate_phdr`。详细的定义见 [这里](http://man7.org/linux/man-pages/man3/dl_iterate_phdr.3.html)。 嗯,先等等,多年的 Android 开发被坑经历告诉我们,还是再看一眼 NDK 里的 `linker.h` 头文件吧: ```c #if defined(__arm__) #if __ANDROID_API__ >= 21 int dl_iterate_phdr(int (*__callback)(struct dl_phdr_info*, size_t, void*), void* __data) __INTRODUCED_IN(21); #endif /* __ANDROID_API__ >= 21 */ #else int dl_iterate_phdr(int (*__callback)(struct dl_phdr_info*, size_t, void*), void* __data); #endif ``` 为什么?!ARM 架构的 Android 5.0 以下版本居然不支持 `dl_iterate_phdr`!我们的 APP 可是要支持 Android 4.0 以上的所有版本啊。特别是 ARM,怎么能不支持呢?!这还让不让人写代码啦! 幸运的是,我们想到了,我们还可以解析 `/proc/self/maps`: ``` root@android:/ # ps | grep main ps | grep main shell 7884 7882 2616 1016 hrtimer_na b6e83824 S /data/local/tmp/main root@android:/ # cat /proc/7884/maps cat /proc/7884/maps address perms offset dev inode pathname --------------------------------------------------------------------- ........... ........... b6e42000-b6eb5000 r-xp 00000000 b3:17 57457 /system/lib/libc.so b6eb5000-b6eb9000 r--p 00072000 b3:17 57457 /system/lib/libc.so b6eb9000-b6ebc000 rw-p 00076000 b3:17 57457 /system/lib/libc.so b6ec6000-b6ec9000 r-xp 00000000 b3:19 753708 /data/local/tmp/libtest.so b6ec9000-b6eca000 r--p 00002000 b3:19 753708 /data/local/tmp/libtest.so b6eca000-b6ecb000 rw-p 00003000 b3:19 753708 /data/local/tmp/libtest.so b6f03000-b6f20000 r-xp 00000000 b3:17 32860 /system/bin/linker b6f20000-b6f21000 r--p 0001c000 b3:17 32860 /system/bin/linker b6f21000-b6f23000 rw-p 0001d000 b3:17 32860 /system/bin/linker b6f25000-b6f26000 r-xp 00000000 b3:19 753707 /data/local/tmp/main b6f26000-b6f27000 r--p 00000000 b3:19 753707 /data/local/tmp/main becd5000-becf6000 rw-p 00000000 00:00 0 [stack] ffff0000-ffff1000 r-xp 00000000 00:00 0 [vectors] ........... ........... ``` maps 返回的是指定进程的内存空间中 `mmap` 的映射信息,包括各种动态库、可执行文件(如:linker),栈空间,堆空间,甚至还包括字体文件。maps 格式的详细说明见 [这里](http://man7.org/linux/man-pages/man5/proc.5.html)。 我们的 libtest.so 在 maps 中有 3 行记录。offset 为 `0` 的第一行的起始地址 `b6ec6000` 在**绝大多数情况下**就是我们寻找的**基地址**。 ### 内存访问权限 maps 返回的信息中已经包含了权限访问信息。如果要执行 hook,就需要写入的权限,可以使用 `mprotect` 来完成: ```c #include int mprotect(void *addr, size_t len, int prot); ``` 注意修改内存访问权限时,只能以“页”为单位。`mprotect` 的详细说明见 [这里](http://man7.org/linux/man-pages/man2/mprotect.2.html)。 ### 指令缓存 注意 `.got` 和 `.data` 的 section 类型是 `PROGBITS`,也就是执行代码。处理器可能会对这部分数据做缓存。修改内存地址后,我们需要清除处理器的指令缓存,让处理器重新从内存中读取这部分指令。方法是调用 `__builtin___clear_cache`: ```c void __builtin___clear_cache (char *begin, char *end); ``` 注意清除指令缓存时,也只能以“页”为单位。`__builtin___clear_cache` 的详细说明见 [这里](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html)。 ## 验证 ### 修改 main.c 我们把 `main.c` 修改为: ```c #include #include #include #include #include #include #define PAGE_START(addr) ((addr) & PAGE_MASK) #define PAGE_END(addr) (PAGE_START(addr) + PAGE_SIZE) void *my_malloc(size_t size) { printf("%zu bytes memory are allocated by libtest.so\n", size); return malloc(size); } void hook() { char line[512]; FILE *fp; uintptr_t base_addr = 0; uintptr_t addr; //find base address of libtest.so if(NULL == (fp = fopen("/proc/self/maps", "r"))) return; while(fgets(line, sizeof(line), fp)) { if(NULL != strstr(line, "libtest.so") && sscanf(line, "%"PRIxPTR"-%*lx %*4s 00000000", &base_addr) == 1) break; } fclose(fp); if(0 == base_addr) return; //the absolute address addr = base_addr + 0x3f90; //add write permission mprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ | PROT_WRITE); //replace the function address *(void **)addr = my_malloc; //clear instruction cache __builtin___clear_cache((void *)PAGE_START(addr), (void *)PAGE_END(addr)); } int main() { hook(); say_hello(); return 0; } ``` 重新编译运行: ``` caikelun@debian:~$ adb push ./main /data/local/tmp caikelun@debian:~$ adb shell "chmod +x /data/local/tmp/main" caikelun@debian:~$ adb shell "export LD_LIBRARY_PATH=/data/local/tmp; /data/local/tmp/main" 1024 bytes memory are allocated by libtest.so hello caikelun@debian:~$ ``` 是的,成功了!我们并没有修改 libtest.so 的代码,甚至没有重新编译它。我们仅仅修改了 main 程序。 libtest.so 和 main 的源码放在 github 上,可以从 [这里](https://github.com/iqiyi/xhook/tree/master/docs/overview/code) 获取到。(根据你使用的编译器不同,或者编译器的版本不同,生成的 libtest.so 中,也许 `malloc` 对应的地址不再是 `0x3f90`,这时你需要先用 readelf 确认,然后再到 `main.c` 中修改。) ### 使用 xhook 当然,我们已经开源了一个叫 xhook 的工具库。使用 xhook,你可以更优雅的完成对 libtest.so 的 hook 操作,也不必担心硬编码 `0x3f90` 导致的兼容性问题。 ```c #include #include #include #include void *my_malloc(size_t size) { printf("%zu bytes memory are allocated by libtest.so\n", size); return malloc(size); } int main() { xhook_register(".*/libtest\\.so$", "malloc", my_malloc, NULL); xhook_refresh(0); say_hello(); return 0; } ``` xhook 支持 armeabi, armeabi-v7a 和 arm64-v8a。支持 Android 4.0 (含) 以上版本 (API level >= 14)。经过了产品级的稳定性和兼容性验证。可以在 [这里](https://github.com/iqiyi/xhook) 获取 `xhook`。 总结一下 xhook 中执行 PLT hook 的流程: 1. 读 maps,获取 ELF 的内存首地址(start address)。 2. 验证 ELF 头信息。 3. 从 PHT 中找到类型为 `PT_LOAD` 且 offset 为 `0` 的 segment。计算 ELF 基地址。 4. 从 PHT 中找到类型为 `PT_DYNAMIC` 的 segment,从中获取到 `.dynamic` section,从 `.dynamic` section中获取其他各项 section 对应的内存地址。 5. 在 `.dynstr` section 中找到需要 hook 的 symbol 对应的 index 值。 6. 遍历所有的 `.relxxx` section(重定位 section),查找 symbol index 和 symbol type 都匹配的项,对于这项重定位项,执行 hook 操作。hook 流程如下: * 读 maps,确认当前 hook 地址的内存访问权限。 * 如果权限不是可读也可写,则用 `mprotect` 修改访问权限为可读也可写。 * 如果调用方需要,就保留 hook 地址当前的值,用于返回。 * 将 hook 地址的值替换为新的值。(执行 hook) * 如果之前用 `mprotect` 修改过内存访问权限,现在还原到之前的权限。 * 清除 hook 地址所在内存页的处理器指令缓存。 ## FAQ ### 可以直接从文件中读取 ELF 信息吗? 可以。而且对于格式解析来说,读文件是最稳妥的方式,因为 ELF 在运行时,原理上有很多 section 不需要一直保留在内存中,可以在加载完之后就从内存中丢弃,这样可以节省少量的内存。但是从实践的角度出发,各种平台的动态链接器和加载器,都不会这么做,可能它们认为增加的复杂度得不偿失。所以我们从内存中读取各种 ELF 信息就可以了,读文件反而增加了性能损耗。另外,某些系统库 ELF 文件,APP 也不一定有访问权限。 ### 计算基地址的精确方法是什么? 正如你已经注意到的,前面介绍 libtest.so 基地址获取时,为了简化概念和编码方便,用了“**绝大多数情况下**”这种不应该出现的描述方式。对于 hook 来说,精确的基地址计算流程是: 1. 在 maps 中找到找到 offset 为 `0`,且 `pathname` 为目标 ELF 的行。保存该行的 start address 为 `p0`。 2. 找出 ELF 的 PHT 中第一个类型为 `PT_LOAD` 且 offset 为 `0` 的 segment,保存该 segment 的虚拟内存相对地址(`p_vaddr`)为 `p1`。 3. `p0` - `p1` 即为该 ELF 当前的基地址。 绝大多数的 ELF 第一个 `PT_LOAD` segment 的 `p_vaddr` 都是 `0`。 另外,之所以要在 maps 里找 offset 为 `0` 的行,是因为我们在执行 hook 之前,希望对内存中的 ELF 文件头进行校验,确保当前操作的是一个有效的 ELF,而这种 ELF 文件头只能出现在 offset 为 `0` 的 mmap 区域。 可以在 Android linker 的源码中搜索“load_bias”,可以找到很多详细的注释说明,也可以参考 linker 中对 `load_bias_` 变量的赋值程序逻辑。 ### 目标 ELF 使用的编译选项对 hook 有什么影响? 会有一些影响。 对于外部函数的调用,可以分为 3 中情况: 1. 直接调用。无论编译选项如何,都可以被 hook 到。外部函数地址始终保存在 `.got` 中。 2. 通过全局函数指针调用。无论编译选项如何,都可以被 hook 到。外部函数地址始终保存在 `.data` 中。 3. 通过局部函数指针调用。如果编译选项为 -O2(默认值),调用将被优化为直接调用(同情况 1)。如果编译选项为 -O0,则在执行 hook 前已经被赋值到临时变量中的外部函数的指针,通过 PLT 方式无法 hook;对于执行 hook 之后才被赋值的,可以通过 PLT 方式 hook。 一般情况下,产品级的 ELF 很少会使用 -O0 进行编译,所以也不必太纠结。但是如果你希望你的 ELF 尽量不被别人 PLT hook,那可以试试使用 -O0 来编译,然后尽量早的将外部函数的指针赋值给局部函数指针变量,之后一直使用这些局部函数指针来访问外部函数。 总之,查看 C/C++ 的源代码对这个问题的理解没有意义,需要查看使用不同的编译选项后,生成的 ELF 的反汇编输出,比较它们的区别,才能知道哪些情况由于什么原因导致无法被 PLT hook。 ### hook 时遇到偶发的段错误是什么原因?如何处理? 我们有时会遇到这样的问题: * 读取 `/proc/self/maps` 后发现某个内存区域的访问权限为**可读**,当我们读取该区域的内容做 ELF 文件头校验时,发生了段错误(sig: SIGSEGV, code: SEGV_ACCERR)。 * 已经用 `mprotect()` 修改了某个内存区域的访问权限为**可写**,`mprotect()` 返回修改成功,然后再次读取 `/proc/self/maps` 确认对应内存区域的访问权限确实为**可写**,执行写入操作(替换函数指针,执行 hook)时发生段错误(sig: SIGSEGV, code: SEGV_ACCERR)。 * 读取和验证 ELF 文件头成功了,根据 ELF 头中的相对地址值,进一步读取 PHT 或者 `.dynamic` section 时发生段错误(sig: SIGSEGV, code: SEGV_ACCERR 或 SEGV_MAPERR)。 可能的原因是: * 进程的内存空间是多线程共享的,我们在执行 hook 时,其他线程(甚至 linker)可能正在执行 `dlclose()`,或者正在用 `mprotect()` 修改这块内存区域的访问权限。 * 不同厂家、机型、版本的 Android ROM 可能有未公开的行为,比如在某些情况下对某些内存区域存在**写保护**或者**读保护**机制,而这些保护机制并不反应在 `/proc/self/maps` 的内容中。 问题分析: * 读内存时发生段错误其实是无害的。 * 我在 hook 执行的流程中,需要直接通过计算内存地址的方式来写入数据的地方只有一处:即替换函数指针的最关键的那一行。只要其他地方的逻辑没有错误,这里就算写入失败了,也不会对其他内存区域造成破坏。 * 加载运行安卓平台的 APP 进程时,加载器已经向我们注入了 signal handler 的注册逻辑,以便 APP 崩溃时与系统的 `debuggerd` 守护进程通讯,`debuggerd` 使用 `ptrace` 调试崩溃进程,获取需要的崩溃现场信息,记录到 tombstone 文件中,然后 APP 自杀。 * 系统会精确的把段错误信号发送给“发生段错误的线程”。 * 我们希望能有一种隐秘的,且可控的方式来避免段错误引起 APP 崩溃。 先明确一个观点:不要只从应用层程序开发的角度来看待段错误,段错误不是洪水猛兽,它只是内核与用户进程的一种正常的交流方式。当用户进程访问了无权限或未 mmap 的虚拟内存地址时,内核向用户进程发送 SIGSEGV 信号,来通知用户进程,仅此而已。只要段错误的发生位置是可控的,我们就可以在用户进程中处理它。 解决方案: * 当 hook 逻辑进入我们认为的危险区域(直接计算内存地址进行读写)之前,通过一个全局 `flag` 来进行标记,离开危险区域后将 `flag` 复位。 * 注册我们自己的 signal handler,只捕获段错误。在 signal handler 中,通过判断 `flag` 的值,来判断当前线程逻辑是否在危险区域中。如果是,就用 `siglongjmp` 跳出 signal handler,直接跳到我们预先设置好的“危险区域以外的下一行代码处”;如果不是,就恢复之前加载器向我们注入的 signal handler,然后直接返回,这时系统会再次向我们的线程发送段错误信号,由于已经恢复了之前的 signal handler,这时会进入默认的系统 signal handler 中走正常逻辑。 * 我们把这种机制简称为:SFP (segmentation fault protection,段错误保护) * 注意:SFP需要一个开关,让我们随时能够开启和关闭它。在 APP 开发调试阶段,SFP 应该始终被关闭,这样就不会错过由于编码失误导致的段错误,这些错误是应该被修复的;在正式上线后 SFP 应该被开启,这样能保证 APP 不会崩溃。(当然,以采样的形式部分关闭 SFP,用以观察和分析 hook 机制本身导致的崩溃,也是可以考虑的) 具体代码可以参考 `xhook` 中的实现,在源码中搜索 `siglongjmp` 和 `sigsetjmp`。 ### ELF 内部函数之间的调用能 hook 吗? 我们这里介绍的 hook 方式为 PLT hook,不能做 ELF 内部函数之间调用的 hook。 inline hook 可以做到,你需要先知道想要 hook 的内部函数符号名(symbol name)或者地址,然后可以 hook。 有很多开源和非开源的 inline hook 实现,比如: * substrate:http://www.cydiasubstrate.com/ * frida:https://www.frida.re/ inline hook 方案强大的同时可能带来以下的问题: * 由于需要直接解析和修改 ELF 中的机器指令(汇编码),对于不同架构的处理器、处理器指令集、编译器优化选项、操作系统版本可能存在不同的兼容性和稳定性问题。 * 发生问题后可能难以分析和定位,一些知名的 inline hook 方案是闭源的。 * 实现起来相对复杂,难度也较大。 * 未知的坑相对较多,这个可以自行 google。 建议如果 PLT hook 够用的话,就不必尝试 inline hook 了。 ## 联系作者 * caikelun@gmail.com * https://github.com/caikelun ## 许可证 Copyright (c) 2018, 爱奇艺, Inc. All rights reserved. 本文使用 [Creative Commons 许可证](https://creativecommons.org/licenses/by/4.0/legalcode) 授权。 ================================================ FILE: docs/overview/code/.gitignore ================================================ .DS_Store main/libs/ main/obj/ libtest/libs/ libtest/obj/ ================================================ FILE: docs/overview/code/build.sh ================================================ #!/bin/bash ndk-build -C ./libtest/jni ndk-build -C ./main/jni ================================================ FILE: docs/overview/code/clean.sh ================================================ #!/bin/bash ndk-build -C ./main/jni clean ndk-build -C ./libtest/jni clean ================================================ FILE: docs/overview/code/libtest/jni/Android.mk ================================================ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := test LOCAL_SRC_FILES := test.c LOCAL_CFLAGS := -Wall -Wextra -Werror #-O0 LOCAL_CONLYFLAGS := -std=c11 include $(BUILD_SHARED_LIBRARY) ================================================ FILE: docs/overview/code/libtest/jni/Application.mk ================================================ APP_ABI := armeabi-v7a APP_PLATFORM := android-14 ================================================ FILE: docs/overview/code/libtest/jni/test.c ================================================ #include #include void say_hello() { char *buf = malloc(1024); if(NULL != buf) { snprintf(buf, 1024, "%s", "hello\n"); printf("%s", buf); } } ================================================ FILE: docs/overview/code/libtest/jni/test.h ================================================ #ifndef TEST_H #define TEST_H 1 #ifdef __cplusplus extern "C" { #endif void say_hello(); #ifdef __cplusplus } #endif #endif ================================================ FILE: docs/overview/code/main/jni/Android.mk ================================================ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := test LOCAL_SRC_FILES := $(LOCAL_PATH)/../../libtest/libs/$(TARGET_ARCH_ABI)/libtest.so LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../../libtest/jni include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := main LOCAL_SRC_FILES := main.c LOCAL_SHARED_LIBRARIES := test LOCAL_CFLAGS := -Wall -Wextra -Werror -fPIE LOCAL_CONLYFLAGS := -std=c11 LOCAL_LDLIBS += -fPIE -pie include $(BUILD_EXECUTABLE) ================================================ FILE: docs/overview/code/main/jni/Application.mk ================================================ APP_ABI := armeabi-v7a APP_PLATFORM := android-14 ================================================ FILE: docs/overview/code/main/jni/main.c ================================================ #include #include #include #include #include #include #define PAGE_START(addr) ((addr) & PAGE_MASK) #define PAGE_END(addr) (PAGE_START(addr) + PAGE_SIZE) void *my_malloc(size_t size) { printf("%zu bytes memory are allocated by libtest.so\n", size); return malloc(size); } void hook() { char line[512]; FILE *fp; uintptr_t base_addr = 0; uintptr_t addr; //find base address of libtest.so if(NULL == (fp = fopen("/proc/self/maps", "r"))) return; while(fgets(line, sizeof(line), fp)) { if(NULL != strstr(line, "libtest.so") && sscanf(line, "%"PRIxPTR"-%*lx %*4s 00000000", &base_addr) == 1) break; } fclose(fp); if(0 == base_addr) return; //the absolute address addr = base_addr + 0x3f90; //add write permission mprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ | PROT_WRITE); //replace the function address *(void **)addr = my_malloc; //clear instruction cache __builtin___clear_cache((void *)PAGE_START(addr), (void *)PAGE_END(addr)); } int main() { hook(); say_hello(); return 0; } ================================================ FILE: docs/overview/code/run.sh ================================================ #!/bin/bash adb push ./main/libs/armeabi-v7a/libtest.so ./main/libs/armeabi-v7a/main /data/local/tmp adb shell "chmod +x /data/local/tmp/main" adb shell "export LD_LIBRARY_PATH=/data/local/tmp; /data/local/tmp/main" ================================================ FILE: install_libs.sh ================================================ #!/bin/bash mkdir -p ./xhookwrapper/xhook/libs/armeabi mkdir -p ./xhookwrapper/xhook/libs/armeabi-v7a mkdir -p ./xhookwrapper/xhook/libs/arm64-v8a mkdir -p ./xhookwrapper/xhook/libs/x86 mkdir -p ./xhookwrapper/xhook/libs/x86_64 cp -f ./libxhook/libs/armeabi/libxhook.so ./xhookwrapper/xhook/libs/armeabi/ cp -f ./libxhook/libs/armeabi-v7a/libxhook.so ./xhookwrapper/xhook/libs/armeabi-v7a/ cp -f ./libxhook/libs/arm64-v8a/libxhook.so ./xhookwrapper/xhook/libs/arm64-v8a/ cp -f ./libxhook/libs/x86/libxhook.so ./xhookwrapper/xhook/libs/x86/ cp -f ./libxhook/libs/x86_64/libxhook.so ./xhookwrapper/xhook/libs/x86_64/ mkdir -p ./xhookwrapper/biz/libs/armeabi mkdir -p ./xhookwrapper/biz/libs/armeabi-v7a mkdir -p ./xhookwrapper/biz/libs/arm64-v8a mkdir -p ./xhookwrapper/biz/libs/x86 mkdir -p ./xhookwrapper/biz/libs/x86_64 cp -f ./libbiz/libs/armeabi/libbiz.so ./xhookwrapper/biz/libs/armeabi/ cp -f ./libbiz/libs/armeabi-v7a/libbiz.so ./xhookwrapper/biz/libs/armeabi-v7a/ cp -f ./libbiz/libs/arm64-v8a/libbiz.so ./xhookwrapper/biz/libs/arm64-v8a/ cp -f ./libbiz/libs/x86/libbiz.so ./xhookwrapper/biz/libs/x86/ cp -f ./libbiz/libs/x86_64/libbiz.so ./xhookwrapper/biz/libs/x86_64/ mkdir -p ./xhookwrapper/app/libs/armeabi mkdir -p ./xhookwrapper/app/libs/armeabi-v7a mkdir -p ./xhookwrapper/app/libs/arm64-v8a mkdir -p ./xhookwrapper/app/libs/x86 mkdir -p ./xhookwrapper/app/libs/x86_64 cp -f ./libtest/libs/armeabi/libtest.so ./xhookwrapper/app/libs/armeabi/ cp -f ./libtest/libs/armeabi-v7a/libtest.so ./xhookwrapper/app/libs/armeabi-v7a/ cp -f ./libtest/libs/arm64-v8a/libtest.so ./xhookwrapper/app/libs/arm64-v8a/ cp -f ./libtest/libs/x86/libtest.so ./xhookwrapper/app/libs/x86/ cp -f ./libtest/libs/x86_64/libtest.so ./xhookwrapper/app/libs/x86_64/ ================================================ FILE: libbiz/jni/Android.mk ================================================ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := xhook LOCAL_SRC_FILES := $(LOCAL_PATH)/../../libxhook/libs/$(TARGET_ARCH_ABI)/libxhook.so LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../../libxhook/jni include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := biz LOCAL_SRC_FILES := biz.c LOCAL_SHARED_LIBRARIES := xhook LOCAL_CFLAGS := -Wall -Wextra -Werror LOCAL_CONLYFLAGS := -std=c11 LOCAL_LDLIBS := -llog include $(BUILD_SHARED_LIBRARY) ================================================ FILE: libbiz/jni/Application.mk ================================================ APP_ABI := armeabi armeabi-v7a arm64-v8a x86 x86_64 APP_PLATFORM := android-14 ================================================ FILE: libbiz/jni/biz.c ================================================ #include #include #include #include #include #include "xhook.h" static int my_system_log_print(int prio, const char* tag, const char* fmt, ...) { va_list ap; char buf[1024]; int r; snprintf(buf, sizeof(buf), "[%s] %s", (NULL == tag ? "" : tag), (NULL == fmt ? "" : fmt)); va_start(ap, fmt); r = __android_log_vprint(prio, "xhook_system", buf, ap); va_end(ap); return r; } static int my_libtest_log_print(int prio, const char* tag, const char* fmt, ...) { va_list ap; char buf[1024]; int r; snprintf(buf, sizeof(buf), "[%s] %s", (NULL == tag ? "" : tag), (NULL == fmt ? "" : fmt)); va_start(ap, fmt); r = __android_log_vprint(prio, "xhook_libtest", buf, ap); va_end(ap); return r; } void Java_com_qiyi_biz_NativeHandler_start(JNIEnv* env, jobject obj) { (void)env; (void)obj; xhook_register("^/system/.*\\.so$", "__android_log_print", my_system_log_print, NULL); xhook_register("^/vendor/.*\\.so$", "__android_log_print", my_system_log_print, NULL); xhook_register(".*/libtest\\.so$", "__android_log_print", my_libtest_log_print, NULL); //just for testing xhook_ignore(".*/liblog\\.so$", "__android_log_print"); //ignore __android_log_print in liblog.so xhook_ignore(".*/libjavacore\\.so$", NULL); //ignore all hooks in libjavacore.so } ================================================ FILE: libtest/jni/Android.mk ================================================ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := test LOCAL_SRC_FILES := test.c LOCAL_CFLAGS := -Wall -Wextra -Werror -O0 LOCAL_CONLYFLAGS := -std=c11 LOCAL_LDLIBS := -llog include $(BUILD_SHARED_LIBRARY) ================================================ FILE: libtest/jni/Application.mk ================================================ APP_ABI := armeabi armeabi-v7a arm64-v8a x86 x86_64 APP_PLATFORM := android-14 ================================================ FILE: libtest/jni/test.c ================================================ #include #include #include #include typedef int (*my_log_t)(int prio, const char* tag, const char* fmt, ...); my_log_t my_global_log_ptr = (my_log_t)__android_log_print; static void *new_thread_func(void *arg) { (void)arg; my_log_t my_local_log_ptr2 = (my_log_t)__android_log_print; unsigned int i = 0; while(1) { my_log_t my_local_log_ptr = (my_log_t)__android_log_print; __android_log_print(ANDROID_LOG_DEBUG, "mytest", "call directly. %u\n", i); my_global_log_ptr(ANDROID_LOG_DEBUG, "mytest", "call from global ptr. %u\n", i); my_local_log_ptr(ANDROID_LOG_DEBUG, "mytest", "call from local ptr. %u\n", i); my_local_log_ptr2(ANDROID_LOG_DEBUG, "mytest", "call from local ptr2. %u (definitely failed when compiled with -O0)\n", i); i++; sleep(1); } return NULL; } void Java_com_qiyi_test_NativeHandler_start(JNIEnv* env, jobject obj) { (void)env; (void)obj; pthread_t tid; pthread_create(&tid, NULL, &new_thread_func, NULL); } ================================================ FILE: libxhook/jni/Android.mk ================================================ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := xhook LOCAL_SRC_FILES := xhook.c \ xh_core.c \ xh_elf.c \ xh_jni.c \ xh_log.c \ xh_util.c \ xh_version.c LOCAL_C_INCLUDES := $(LOCAL_PATH) LOCAL_CFLAGS := -Wall -Wextra -Werror -fvisibility=hidden LOCAL_CONLYFLAGS := -std=c11 LOCAL_LDLIBS := -llog include $(BUILD_SHARED_LIBRARY) ================================================ FILE: libxhook/jni/Application.mk ================================================ APP_ABI := armeabi armeabi-v7a arm64-v8a x86 x86_64 APP_PLATFORM := android-14 ================================================ FILE: libxhook/jni/queue.h ================================================ /*- * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)queue.h 8.5 (Berkeley) 8/20/94 * $FreeBSD: stable/9/sys/sys/queue.h 252365 2013-06-29 04:25:40Z lstewart $ */ #ifndef QUEUE_H #define QUEUE_H /* #include */ #define __containerof(ptr, type, field) ((type *)((char *)(ptr) - ((char *)&((type *)0)->field))) /* * This file defines four types of data structures: singly-linked lists, * singly-linked tail queues, lists and tail queues. * * A singly-linked list is headed by a single forward pointer. The elements * are singly linked for minimum space and pointer manipulation overhead at * the expense of O(n) removal for arbitrary elements. New elements can be * added to the list after an existing element or at the head of the list. * Elements being removed from the head of the list should use the explicit * macro for this purpose for optimum efficiency. A singly-linked list may * only be traversed in the forward direction. Singly-linked lists are ideal * for applications with large datasets and few or no removals or for * implementing a LIFO queue. * * A singly-linked tail queue is headed by a pair of pointers, one to the * head of the list and the other to the tail of the list. The elements are * singly linked for minimum space and pointer manipulation overhead at the * expense of O(n) removal for arbitrary elements. New elements can be added * to the list after an existing element, at the head of the list, or at the * end of the list. Elements being removed from the head of the tail queue * should use the explicit macro for this purpose for optimum efficiency. * A singly-linked tail queue may only be traversed in the forward direction. * Singly-linked tail queues are ideal for applications with large datasets * and few or no removals or for implementing a FIFO queue. * * A list is headed by a single forward pointer (or an array of forward * pointers for a hash table header). The elements are doubly linked * so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before * or after an existing element or at the head of the list. A list * may be traversed in either direction. * * A tail queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are doubly * linked so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before or * after an existing element, at the head of the list, or at the end of * the list. A tail queue may be traversed in either direction. * * For details on the use of these macros, see the queue(3) manual page. * * SLIST LIST STAILQ TAILQ * _HEAD + + + + * _HEAD_INITIALIZER + + + + * _ENTRY + + + + * _INIT + + + + * _EMPTY + + + + * _FIRST + + + + * _NEXT + + + + * _PREV - + - + * _LAST - - + + * _FOREACH + + + + * _FOREACH_FROM + + + + * _FOREACH_SAFE + + + + * _FOREACH_FROM_SAFE + + + + * _FOREACH_REVERSE - - - + * _FOREACH_REVERSE_FROM - - - + * _FOREACH_REVERSE_SAFE - - - + * _FOREACH_REVERSE_FROM_SAFE - - - + * _INSERT_HEAD + + + + * _INSERT_BEFORE - + - + * _INSERT_AFTER + + + + * _INSERT_TAIL - - + + * _CONCAT - - + + * _REMOVE_AFTER + - + - * _REMOVE_HEAD + - + - * _REMOVE + + + + * _SWAP + + + + * */ /* * Singly-linked List declarations. */ #define SLIST_HEAD(name, type, qual) \ struct name { \ struct type *qual slh_first; /* first element */ \ } #define SLIST_HEAD_INITIALIZER(head) \ { NULL } #define SLIST_ENTRY(type, qual) \ struct { \ struct type *qual sle_next; /* next element */ \ } /* * Singly-linked List functions. */ #define SLIST_INIT(head) do { \ SLIST_FIRST((head)) = NULL; \ } while (0) #define SLIST_EMPTY(head) ((head)->slh_first == NULL) #define SLIST_FIRST(head) ((head)->slh_first) #define SLIST_NEXT(elm, field) ((elm)->field.sle_next) #define SLIST_FOREACH(var, head, field) \ for ((var) = SLIST_FIRST((head)); \ (var); \ (var) = SLIST_NEXT((var), field)) #define SLIST_FOREACH_FROM(var, head, field) \ for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \ (var); \ (var) = SLIST_NEXT((var), field)) #define SLIST_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = SLIST_FIRST((head)); \ (var) && ((tvar) = SLIST_NEXT((var), field), 1); \ (var) = (tvar)) #define SLIST_FOREACH_FROM_SAFE(var, head, field, tvar) \ for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \ (var) && ((tvar) = SLIST_NEXT((var), field), 1); \ (var) = (tvar)) #define SLIST_INSERT_HEAD(head, elm, field) do { \ SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \ SLIST_FIRST((head)) = (elm); \ } while (0) #define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \ SLIST_NEXT((slistelm), field) = (elm); \ } while (0) #define SLIST_REMOVE_AFTER(elm, field) do { \ SLIST_NEXT(elm, field) = \ SLIST_NEXT(SLIST_NEXT(elm, field), field); \ } while (0) #define SLIST_REMOVE_HEAD(head, field) do { \ SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \ } while (0) #define SLIST_REMOVE(head, elm, type, field) do { \ if (SLIST_FIRST((head)) == (elm)) { \ SLIST_REMOVE_HEAD((head), field); \ } \ else { \ struct type *curelm = SLIST_FIRST((head)); \ while (SLIST_NEXT(curelm, field) != (elm)) \ curelm = SLIST_NEXT(curelm, field); \ SLIST_REMOVE_AFTER(curelm, field); \ } \ } while (0) #define SLIST_SWAP(head1, head2, type) do { \ struct type *swap_first = SLIST_FIRST(head1); \ SLIST_FIRST(head1) = SLIST_FIRST(head2); \ SLIST_FIRST(head2) = swap_first; \ } while (0) /* * List declarations. */ #define LIST_HEAD(name, type, qual) \ struct name { \ struct type *qual lh_first; /* first element */ \ } #define LIST_HEAD_INITIALIZER(head) \ { NULL } #define LIST_ENTRY(type, qual) \ struct { \ struct type *qual le_next; /* next element */ \ struct type *qual *le_prev; /* address of previous next element */ \ } /* * List functions. */ #define LIST_INIT(head) do { \ LIST_FIRST((head)) = NULL; \ } while (0) #define LIST_EMPTY(head) ((head)->lh_first == NULL) #define LIST_FIRST(head) ((head)->lh_first) #define LIST_NEXT(elm, field) ((elm)->field.le_next) #define LIST_PREV(elm, head, type, field) \ ((elm)->field.le_prev == &LIST_FIRST((head)) ? NULL : \ __containerof((elm)->field.le_prev, struct type, field.le_next)) #define LIST_FOREACH(var, head, field) \ for ((var) = LIST_FIRST((head)); \ (var); \ (var) = LIST_NEXT((var), field)) #define LIST_FOREACH_FROM(var, head, field) \ for ((var) = ((var) ? (var) : LIST_FIRST((head))); \ (var); \ (var) = LIST_NEXT((var), field)) #define LIST_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = LIST_FIRST((head)); \ (var) && ((tvar) = LIST_NEXT((var), field), 1); \ (var) = (tvar)) #define LIST_FOREACH_FROM_SAFE(var, head, field, tvar) \ for ((var) = ((var) ? (var) : LIST_FIRST((head))); \ (var) && ((tvar) = LIST_NEXT((var), field), 1); \ (var) = (tvar)) #define LIST_INSERT_HEAD(head, elm, field) do { \ if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \ LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field); \ LIST_FIRST((head)) = (elm); \ (elm)->field.le_prev = &LIST_FIRST((head)); \ } while (0) #define LIST_INSERT_BEFORE(listelm, elm, field) do { \ (elm)->field.le_prev = (listelm)->field.le_prev; \ LIST_NEXT((elm), field) = (listelm); \ *(listelm)->field.le_prev = (elm); \ (listelm)->field.le_prev = &LIST_NEXT((elm), field); \ } while (0) #define LIST_INSERT_AFTER(listelm, elm, field) do { \ if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL) \ LIST_NEXT((listelm), field)->field.le_prev = \ &LIST_NEXT((elm), field); \ LIST_NEXT((listelm), field) = (elm); \ (elm)->field.le_prev = &LIST_NEXT((listelm), field); \ } while (0) #define LIST_REMOVE(elm, field) do { \ if (LIST_NEXT((elm), field) != NULL) \ LIST_NEXT((elm), field)->field.le_prev = \ (elm)->field.le_prev; \ *(elm)->field.le_prev = LIST_NEXT((elm), field); \ } while (0) #define LIST_SWAP(head1, head2, type, field) do { \ struct type *swap_tmp = LIST_FIRST((head1)); \ LIST_FIRST((head1)) = LIST_FIRST((head2)); \ LIST_FIRST((head2)) = swap_tmp; \ if ((swap_tmp = LIST_FIRST((head1))) != NULL) \ swap_tmp->field.le_prev = &LIST_FIRST((head1)); \ if ((swap_tmp = LIST_FIRST((head2))) != NULL) \ swap_tmp->field.le_prev = &LIST_FIRST((head2)); \ } while (0) /* * Singly-linked Tail queue declarations. */ #define STAILQ_HEAD(name, type, qual) \ struct name { \ struct type *qual stqh_first;/* first element */ \ struct type *qual *stqh_last;/* addr of last next element */ \ } #define STAILQ_HEAD_INITIALIZER(head) \ { NULL, &(head).stqh_first } #define STAILQ_ENTRY(type, qual) \ struct { \ struct type *qual stqe_next; /* next element */ \ } /* * Singly-linked Tail queue functions. */ #define STAILQ_INIT(head) do { \ STAILQ_FIRST((head)) = NULL; \ (head)->stqh_last = &STAILQ_FIRST((head)); \ } while (0) #define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) #define STAILQ_FIRST(head) ((head)->stqh_first) #define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) #define STAILQ_LAST(head, type, field) \ (STAILQ_EMPTY((head)) ? NULL : \ __containerof((head)->stqh_last, struct type, field.stqe_next)) #define STAILQ_FOREACH(var, head, field) \ for((var) = STAILQ_FIRST((head)); \ (var); \ (var) = STAILQ_NEXT((var), field)) #define STAILQ_FOREACH_FROM(var, head, field) \ for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \ (var); \ (var) = STAILQ_NEXT((var), field)) #define STAILQ_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = STAILQ_FIRST((head)); \ (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ (var) = (tvar)) #define STAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \ for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \ (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ (var) = (tvar)) #define STAILQ_INSERT_HEAD(head, elm, field) do { \ if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \ (head)->stqh_last = &STAILQ_NEXT((elm), field); \ STAILQ_FIRST((head)) = (elm); \ } while (0) #define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \ if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL) \ (head)->stqh_last = &STAILQ_NEXT((elm), field); \ STAILQ_NEXT((tqelm), field) = (elm); \ } while (0) #define STAILQ_INSERT_TAIL(head, elm, field) do { \ STAILQ_NEXT((elm), field) = NULL; \ *(head)->stqh_last = (elm); \ (head)->stqh_last = &STAILQ_NEXT((elm), field); \ } while (0) #define STAILQ_CONCAT(head1, head2) do { \ if (!STAILQ_EMPTY((head2))) { \ *(head1)->stqh_last = (head2)->stqh_first; \ (head1)->stqh_last = (head2)->stqh_last; \ STAILQ_INIT((head2)); \ } \ } while (0) #define STAILQ_REMOVE_AFTER(head, elm, field) do { \ if ((STAILQ_NEXT(elm, field) = \ STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \ (head)->stqh_last = &STAILQ_NEXT((elm), field); \ } while (0) #define STAILQ_REMOVE_HEAD(head, field) do { \ if ((STAILQ_FIRST((head)) = \ STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \ (head)->stqh_last = &STAILQ_FIRST((head)); \ } while (0) #define STAILQ_REMOVE(head, elm, type, field) do { \ if (STAILQ_FIRST((head)) == (elm)) { \ STAILQ_REMOVE_HEAD((head), field); \ } \ else { \ struct type *curelm = STAILQ_FIRST((head)); \ while (STAILQ_NEXT(curelm, field) != (elm)) \ curelm = STAILQ_NEXT(curelm, field); \ STAILQ_REMOVE_AFTER(head, curelm, field); \ } \ } while (0) #define STAILQ_SWAP(head1, head2, type) do { \ struct type *swap_first = STAILQ_FIRST(head1); \ struct type **swap_last = (head1)->stqh_last; \ STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \ (head1)->stqh_last = (head2)->stqh_last; \ STAILQ_FIRST(head2) = swap_first; \ (head2)->stqh_last = swap_last; \ if (STAILQ_EMPTY(head1)) \ (head1)->stqh_last = &STAILQ_FIRST(head1); \ if (STAILQ_EMPTY(head2)) \ (head2)->stqh_last = &STAILQ_FIRST(head2); \ } while (0) /* * Tail queue declarations. */ #define TAILQ_HEAD(name, type, qual) \ struct name { \ struct type *qual tqh_first; /* first element */ \ struct type *qual *tqh_last; /* addr of last next element */ \ } #define TAILQ_HEAD_INITIALIZER(head) \ { NULL, &(head).tqh_first } #define TAILQ_ENTRY(type, qual) \ struct { \ struct type *qual tqe_next; /* next element */ \ struct type *qual *tqe_prev; /* address of previous next element */ \ } /* * Tail queue functions. */ #define TAILQ_INIT(head) do { \ TAILQ_FIRST((head)) = NULL; \ (head)->tqh_last = &TAILQ_FIRST((head)); \ } while (0) #define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) #define TAILQ_FIRST(head) ((head)->tqh_first) #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) #define TAILQ_PREV(elm, headname, field) \ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) #define TAILQ_LAST(head, headname) \ (*(((struct headname *)((head)->tqh_last))->tqh_last)) #define TAILQ_FOREACH(var, head, field) \ for ((var) = TAILQ_FIRST((head)); \ (var); \ (var) = TAILQ_NEXT((var), field)) #define TAILQ_FOREACH_FROM(var, head, field) \ for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \ (var); \ (var) = TAILQ_NEXT((var), field)) #define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = TAILQ_FIRST((head)); \ (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ (var) = (tvar)) #define TAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \ for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \ (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ (var) = (tvar)) #define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ for ((var) = TAILQ_LAST((head), headname); \ (var); \ (var) = TAILQ_PREV((var), headname, field)) #define TAILQ_FOREACH_REVERSE_FROM(var, head, headname, field) \ for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \ (var); \ (var) = TAILQ_PREV((var), headname, field)) #define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ for ((var) = TAILQ_LAST((head), headname); \ (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ (var) = (tvar)) #define TAILQ_FOREACH_REVERSE_FROM_SAFE(var, head, headname, field, tvar) \ for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \ (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ (var) = (tvar)) #define TAILQ_INSERT_HEAD(head, elm, field) do { \ if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \ TAILQ_FIRST((head))->field.tqe_prev = \ &TAILQ_NEXT((elm), field); \ else \ (head)->tqh_last = &TAILQ_NEXT((elm), field); \ TAILQ_FIRST((head)) = (elm); \ (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \ } while (0) #define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ TAILQ_NEXT((elm), field) = (listelm); \ *(listelm)->field.tqe_prev = (elm); \ (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \ } while (0) #define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL) \ TAILQ_NEXT((elm), field)->field.tqe_prev = \ &TAILQ_NEXT((elm), field); \ else \ (head)->tqh_last = &TAILQ_NEXT((elm), field); \ TAILQ_NEXT((listelm), field) = (elm); \ (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \ } while (0) #define TAILQ_INSERT_TAIL(head, elm, field) do { \ TAILQ_NEXT((elm), field) = NULL; \ (elm)->field.tqe_prev = (head)->tqh_last; \ *(head)->tqh_last = (elm); \ (head)->tqh_last = &TAILQ_NEXT((elm), field); \ } while (0) #define TAILQ_CONCAT(head1, head2, field) do { \ if (!TAILQ_EMPTY(head2)) { \ *(head1)->tqh_last = (head2)->tqh_first; \ (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ (head1)->tqh_last = (head2)->tqh_last; \ TAILQ_INIT((head2)); \ } \ } while (0) #define TAILQ_REMOVE(head, elm, field) do { \ if ((TAILQ_NEXT((elm), field)) != NULL) \ TAILQ_NEXT((elm), field)->field.tqe_prev = \ (elm)->field.tqe_prev; \ else \ (head)->tqh_last = (elm)->field.tqe_prev; \ *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \ } while (0) #define TAILQ_SWAP(head1, head2, type, field) do { \ struct type *swap_first = (head1)->tqh_first; \ struct type **swap_last = (head1)->tqh_last; \ (head1)->tqh_first = (head2)->tqh_first; \ (head1)->tqh_last = (head2)->tqh_last; \ (head2)->tqh_first = swap_first; \ (head2)->tqh_last = swap_last; \ if ((swap_first = (head1)->tqh_first) != NULL) \ swap_first->field.tqe_prev = &(head1)->tqh_first; \ else \ (head1)->tqh_last = &(head1)->tqh_first; \ if ((swap_first = (head2)->tqh_first) != NULL) \ swap_first->field.tqe_prev = &(head2)->tqh_first; \ else \ (head2)->tqh_last = &(head2)->tqh_first; \ } while (0) #endif ================================================ FILE: libxhook/jni/tree.h ================================================ /* $NetBSD: tree.h,v 1.8 2004/03/28 19:38:30 provos Exp $ */ /* $OpenBSD: tree.h,v 1.7 2002/10/17 21:51:54 art Exp $ */ /* $FreeBSD: stable/9/sys/sys/tree.h 189204 2009-03-01 04:57:23Z bms $ */ /*- * Copyright 2002 Niels Provos * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef TREE_H #define TREE_H /* #include */ #ifndef __unused #define __unused __attribute__((__unused__)) #endif /* * This file defines data structures for different types of trees: * splay trees and red-black trees. * * A splay tree is a self-organizing data structure. Every operation * on the tree causes a splay to happen. The splay moves the requested * node to the root of the tree and partly rebalances it. * * This has the benefit that request locality causes faster lookups as * the requested nodes move to the top of the tree. On the other hand, * every lookup causes memory writes. * * The Balance Theorem bounds the total access time for m operations * and n inserts on an initially empty tree as O((m + n)lg n). The * amortized cost for a sequence of m accesses to a splay tree is O(lg n); * * A red-black tree is a binary search tree with the node color as an * extra attribute. It fulfills a set of conditions: * - every search path from the root to a leaf consists of the * same number of black nodes, * - each red node (except for the root) has a black parent, * - each leaf node is black. * * Every operation on a red-black tree is bounded as O(lg n). * The maximum height of a red-black tree is 2lg (n+1). */ #define SPLAY_HEAD(name, type) \ struct name { \ struct type *sph_root; /* root of the tree */ \ } #define SPLAY_INITIALIZER(root) \ { NULL } #define SPLAY_INIT(root) do { \ (root)->sph_root = NULL; \ } while (/*CONSTCOND*/ 0) #define SPLAY_ENTRY(type) \ struct { \ struct type *spe_left; /* left element */ \ struct type *spe_right; /* right element */ \ } #define SPLAY_LEFT(elm, field) (elm)->field.spe_left #define SPLAY_RIGHT(elm, field) (elm)->field.spe_right #define SPLAY_ROOT(head) (head)->sph_root #define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL) /* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */ #define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \ SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \ SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ (head)->sph_root = tmp; \ } while (/*CONSTCOND*/ 0) #define SPLAY_ROTATE_LEFT(head, tmp, field) do { \ SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \ SPLAY_LEFT(tmp, field) = (head)->sph_root; \ (head)->sph_root = tmp; \ } while (/*CONSTCOND*/ 0) #define SPLAY_LINKLEFT(head, tmp, field) do { \ SPLAY_LEFT(tmp, field) = (head)->sph_root; \ tmp = (head)->sph_root; \ (head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \ } while (/*CONSTCOND*/ 0) #define SPLAY_LINKRIGHT(head, tmp, field) do { \ SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ tmp = (head)->sph_root; \ (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \ } while (/*CONSTCOND*/ 0) #define SPLAY_ASSEMBLE(head, node, left, right, field) do { \ SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \ SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\ SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \ SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \ } while (/*CONSTCOND*/ 0) /* Generates prototypes and inline functions */ #define SPLAY_PROTOTYPE(name, type, field, cmp) \ void name##_SPLAY(struct name *, struct type *); \ void name##_SPLAY_MINMAX(struct name *, int); \ struct type *name##_SPLAY_INSERT(struct name *, struct type *); \ struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \ \ /* Finds the node with the same key as elm */ \ static __inline struct type * \ name##_SPLAY_FIND(struct name *head, struct type *elm) \ { \ if (SPLAY_EMPTY(head)) \ return(NULL); \ name##_SPLAY(head, elm); \ if ((cmp)(elm, (head)->sph_root) == 0) \ return (head->sph_root); \ return (NULL); \ } \ \ static __inline struct type * \ name##_SPLAY_NEXT(struct name *head, struct type *elm) \ { \ name##_SPLAY(head, elm); \ if (SPLAY_RIGHT(elm, field) != NULL) { \ elm = SPLAY_RIGHT(elm, field); \ while (SPLAY_LEFT(elm, field) != NULL) { \ elm = SPLAY_LEFT(elm, field); \ } \ } else \ elm = NULL; \ return (elm); \ } \ \ static __inline struct type * \ name##_SPLAY_MIN_MAX(struct name *head, int val) \ { \ name##_SPLAY_MINMAX(head, val); \ return (SPLAY_ROOT(head)); \ } /* Main splay operation. * Moves node close to the key of elm to top */ #define SPLAY_GENERATE(name, type, field, cmp) \ struct type * \ name##_SPLAY_INSERT(struct name *head, struct type *elm) \ { \ if (SPLAY_EMPTY(head)) { \ SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \ } else { \ int __comp; \ name##_SPLAY(head, elm); \ __comp = (cmp)(elm, (head)->sph_root); \ if(__comp < 0) { \ SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\ SPLAY_RIGHT(elm, field) = (head)->sph_root; \ SPLAY_LEFT((head)->sph_root, field) = NULL; \ } else if (__comp > 0) { \ SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\ SPLAY_LEFT(elm, field) = (head)->sph_root; \ SPLAY_RIGHT((head)->sph_root, field) = NULL; \ } else \ return ((head)->sph_root); \ } \ (head)->sph_root = (elm); \ return (NULL); \ } \ \ struct type * \ name##_SPLAY_REMOVE(struct name *head, struct type *elm) \ { \ struct type *__tmp; \ if (SPLAY_EMPTY(head)) \ return (NULL); \ name##_SPLAY(head, elm); \ if ((cmp)(elm, (head)->sph_root) == 0) { \ if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \ (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\ } else { \ __tmp = SPLAY_RIGHT((head)->sph_root, field); \ (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\ name##_SPLAY(head, elm); \ SPLAY_RIGHT((head)->sph_root, field) = __tmp; \ } \ return (elm); \ } \ return (NULL); \ } \ \ void \ name##_SPLAY(struct name *head, struct type *elm) \ { \ struct type __node, *__left, *__right, *__tmp; \ int __comp; \ \ SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ __left = __right = &__node; \ \ while ((__comp = (cmp)(elm, (head)->sph_root)) != 0) { \ if (__comp < 0) { \ __tmp = SPLAY_LEFT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if ((cmp)(elm, __tmp) < 0){ \ SPLAY_ROTATE_RIGHT(head, __tmp, field); \ if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ break; \ } \ SPLAY_LINKLEFT(head, __right, field); \ } else if (__comp > 0) { \ __tmp = SPLAY_RIGHT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if ((cmp)(elm, __tmp) > 0){ \ SPLAY_ROTATE_LEFT(head, __tmp, field); \ if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ break; \ } \ SPLAY_LINKRIGHT(head, __left, field); \ } \ } \ SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ } \ \ /* Splay with either the minimum or the maximum element \ * Used to find minimum or maximum element in tree. \ */ \ void name##_SPLAY_MINMAX(struct name *head, int __comp) \ { \ struct type __node, *__left, *__right, *__tmp; \ \ SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ __left = __right = &__node; \ \ while (1) { \ if (__comp < 0) { \ __tmp = SPLAY_LEFT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if (__comp < 0){ \ SPLAY_ROTATE_RIGHT(head, __tmp, field); \ if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ break; \ } \ SPLAY_LINKLEFT(head, __right, field); \ } else if (__comp > 0) { \ __tmp = SPLAY_RIGHT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if (__comp > 0) { \ SPLAY_ROTATE_LEFT(head, __tmp, field); \ if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ break; \ } \ SPLAY_LINKRIGHT(head, __left, field); \ } \ } \ SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ } #define SPLAY_NEGINF -1 #define SPLAY_INF 1 #define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y) #define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y) #define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y) #define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y) #define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \ : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF)) #define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \ : name##_SPLAY_MIN_MAX(x, SPLAY_INF)) #define SPLAY_FOREACH(x, name, head) \ for ((x) = SPLAY_MIN(name, head); \ (x) != NULL; \ (x) = SPLAY_NEXT(name, head, x)) /* Macros that define a red-black tree */ #define RB_HEAD(name, type) \ struct name { \ struct type *rbh_root; /* root of the tree */ \ } #define RB_INITIALIZER(root) \ { NULL } #define RB_INIT(root) do { \ (root)->rbh_root = NULL; \ } while (/*CONSTCOND*/ 0) #define RB_BLACK 0 #define RB_RED 1 #define RB_ENTRY(type) \ struct { \ struct type *rbe_left; /* left element */ \ struct type *rbe_right; /* right element */ \ struct type *rbe_parent; /* parent element */ \ int rbe_color; /* node color */ \ } #define RB_LEFT(elm, field) (elm)->field.rbe_left #define RB_RIGHT(elm, field) (elm)->field.rbe_right #define RB_PARENT(elm, field) (elm)->field.rbe_parent #define RB_COLOR(elm, field) (elm)->field.rbe_color #define RB_ROOT(head) (head)->rbh_root #define RB_EMPTY(head) (RB_ROOT(head) == NULL) #define RB_SET(elm, parent, field) do { \ RB_PARENT(elm, field) = parent; \ RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \ RB_COLOR(elm, field) = RB_RED; \ } while (/*CONSTCOND*/ 0) #define RB_SET_BLACKRED(black, red, field) do { \ RB_COLOR(black, field) = RB_BLACK; \ RB_COLOR(red, field) = RB_RED; \ } while (/*CONSTCOND*/ 0) #ifndef RB_AUGMENT #define RB_AUGMENT(x) do {} while (0) #endif #define RB_ROTATE_LEFT(head, elm, tmp, field) do { \ (tmp) = RB_RIGHT(elm, field); \ if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field)) != NULL) { \ RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \ } \ RB_AUGMENT(elm); \ if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \ if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ else \ RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ } else \ (head)->rbh_root = (tmp); \ RB_LEFT(tmp, field) = (elm); \ RB_PARENT(elm, field) = (tmp); \ RB_AUGMENT(tmp); \ if ((RB_PARENT(tmp, field))) \ RB_AUGMENT(RB_PARENT(tmp, field)); \ } while (/*CONSTCOND*/ 0) #define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \ (tmp) = RB_LEFT(elm, field); \ if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field)) != NULL) { \ RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \ } \ RB_AUGMENT(elm); \ if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \ if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ else \ RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ } else \ (head)->rbh_root = (tmp); \ RB_RIGHT(tmp, field) = (elm); \ RB_PARENT(elm, field) = (tmp); \ RB_AUGMENT(tmp); \ if ((RB_PARENT(tmp, field))) \ RB_AUGMENT(RB_PARENT(tmp, field)); \ } while (/*CONSTCOND*/ 0) /* Generates prototypes and inline functions */ #define RB_PROTOTYPE(name, type, field, cmp) \ RB_PROTOTYPE_INTERNAL(name, type, field, cmp,) #define RB_PROTOTYPE_STATIC(name, type, field, cmp) \ RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __unused static) #define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \ attr void name##_RB_INSERT_COLOR(struct name *, struct type *); \ attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\ attr struct type *name##_RB_REMOVE(struct name *, struct type *); \ attr struct type *name##_RB_INSERT(struct name *, struct type *); \ attr struct type *name##_RB_FIND(struct name *, struct type *); \ attr struct type *name##_RB_NFIND(struct name *, struct type *); \ attr struct type *name##_RB_NEXT(struct type *); \ attr struct type *name##_RB_PREV(struct type *); \ attr struct type *name##_RB_MINMAX(struct name *, int); \ \ /* Main rb operation. * Moves node close to the key of elm to top */ #define RB_GENERATE(name, type, field, cmp) \ RB_GENERATE_INTERNAL(name, type, field, cmp,) #define RB_GENERATE_STATIC(name, type, field, cmp) \ RB_GENERATE_INTERNAL(name, type, field, cmp, __unused static) #define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \ attr void \ name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \ { \ struct type *parent, *gparent, *tmp; \ while ((parent = RB_PARENT(elm, field)) != NULL && \ RB_COLOR(parent, field) == RB_RED) { \ gparent = RB_PARENT(parent, field); \ if (parent == RB_LEFT(gparent, field)) { \ tmp = RB_RIGHT(gparent, field); \ if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ RB_COLOR(tmp, field) = RB_BLACK; \ RB_SET_BLACKRED(parent, gparent, field);\ elm = gparent; \ continue; \ } \ if (RB_RIGHT(parent, field) == elm) { \ RB_ROTATE_LEFT(head, parent, tmp, field);\ tmp = parent; \ parent = elm; \ elm = tmp; \ } \ RB_SET_BLACKRED(parent, gparent, field); \ RB_ROTATE_RIGHT(head, gparent, tmp, field); \ } else { \ tmp = RB_LEFT(gparent, field); \ if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ RB_COLOR(tmp, field) = RB_BLACK; \ RB_SET_BLACKRED(parent, gparent, field);\ elm = gparent; \ continue; \ } \ if (RB_LEFT(parent, field) == elm) { \ RB_ROTATE_RIGHT(head, parent, tmp, field);\ tmp = parent; \ parent = elm; \ elm = tmp; \ } \ RB_SET_BLACKRED(parent, gparent, field); \ RB_ROTATE_LEFT(head, gparent, tmp, field); \ } \ } \ RB_COLOR(head->rbh_root, field) = RB_BLACK; \ } \ \ attr void \ name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \ { \ struct type *tmp; \ while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \ elm != RB_ROOT(head)) { \ if (RB_LEFT(parent, field) == elm) { \ tmp = RB_RIGHT(parent, field); \ if (RB_COLOR(tmp, field) == RB_RED) { \ RB_SET_BLACKRED(tmp, parent, field); \ RB_ROTATE_LEFT(head, parent, tmp, field);\ tmp = RB_RIGHT(parent, field); \ } \ if ((RB_LEFT(tmp, field) == NULL || \ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ (RB_RIGHT(tmp, field) == NULL || \ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ RB_COLOR(tmp, field) = RB_RED; \ elm = parent; \ parent = RB_PARENT(elm, field); \ } else { \ if (RB_RIGHT(tmp, field) == NULL || \ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\ struct type *oleft; \ if ((oleft = RB_LEFT(tmp, field)) \ != NULL) \ RB_COLOR(oleft, field) = RB_BLACK;\ RB_COLOR(tmp, field) = RB_RED; \ RB_ROTATE_RIGHT(head, tmp, oleft, field);\ tmp = RB_RIGHT(parent, field); \ } \ RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ RB_COLOR(parent, field) = RB_BLACK; \ if (RB_RIGHT(tmp, field)) \ RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\ RB_ROTATE_LEFT(head, parent, tmp, field);\ elm = RB_ROOT(head); \ break; \ } \ } else { \ tmp = RB_LEFT(parent, field); \ if (RB_COLOR(tmp, field) == RB_RED) { \ RB_SET_BLACKRED(tmp, parent, field); \ RB_ROTATE_RIGHT(head, parent, tmp, field);\ tmp = RB_LEFT(parent, field); \ } \ if ((RB_LEFT(tmp, field) == NULL || \ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ (RB_RIGHT(tmp, field) == NULL || \ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ RB_COLOR(tmp, field) = RB_RED; \ elm = parent; \ parent = RB_PARENT(elm, field); \ } else { \ if (RB_LEFT(tmp, field) == NULL || \ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\ struct type *oright; \ if ((oright = RB_RIGHT(tmp, field)) \ != NULL) \ RB_COLOR(oright, field) = RB_BLACK;\ RB_COLOR(tmp, field) = RB_RED; \ RB_ROTATE_LEFT(head, tmp, oright, field);\ tmp = RB_LEFT(parent, field); \ } \ RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ RB_COLOR(parent, field) = RB_BLACK; \ if (RB_LEFT(tmp, field)) \ RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\ RB_ROTATE_RIGHT(head, parent, tmp, field);\ elm = RB_ROOT(head); \ break; \ } \ } \ } \ if (elm) \ RB_COLOR(elm, field) = RB_BLACK; \ } \ \ attr struct type * \ name##_RB_REMOVE(struct name *head, struct type *elm) \ { \ struct type *child, *parent, *old = elm; \ int color; \ if (RB_LEFT(elm, field) == NULL) \ child = RB_RIGHT(elm, field); \ else if (RB_RIGHT(elm, field) == NULL) \ child = RB_LEFT(elm, field); \ else { \ struct type *left; \ elm = RB_RIGHT(elm, field); \ while ((left = RB_LEFT(elm, field)) != NULL) \ elm = left; \ child = RB_RIGHT(elm, field); \ parent = RB_PARENT(elm, field); \ color = RB_COLOR(elm, field); \ if (child) \ RB_PARENT(child, field) = parent; \ if (parent) { \ if (RB_LEFT(parent, field) == elm) \ RB_LEFT(parent, field) = child; \ else \ RB_RIGHT(parent, field) = child; \ RB_AUGMENT(parent); \ } else \ RB_ROOT(head) = child; \ if (RB_PARENT(elm, field) == old) \ parent = elm; \ (elm)->field = (old)->field; \ if (RB_PARENT(old, field)) { \ if (RB_LEFT(RB_PARENT(old, field), field) == old)\ RB_LEFT(RB_PARENT(old, field), field) = elm;\ else \ RB_RIGHT(RB_PARENT(old, field), field) = elm;\ RB_AUGMENT(RB_PARENT(old, field)); \ } else \ RB_ROOT(head) = elm; \ RB_PARENT(RB_LEFT(old, field), field) = elm; \ if (RB_RIGHT(old, field)) \ RB_PARENT(RB_RIGHT(old, field), field) = elm; \ if (parent) { \ left = parent; \ do { \ RB_AUGMENT(left); \ } while ((left = RB_PARENT(left, field)) != NULL); \ } \ goto color; \ } \ parent = RB_PARENT(elm, field); \ color = RB_COLOR(elm, field); \ if (child) \ RB_PARENT(child, field) = parent; \ if (parent) { \ if (RB_LEFT(parent, field) == elm) \ RB_LEFT(parent, field) = child; \ else \ RB_RIGHT(parent, field) = child; \ RB_AUGMENT(parent); \ } else \ RB_ROOT(head) = child; \ color: \ if (color == RB_BLACK) \ name##_RB_REMOVE_COLOR(head, parent, child); \ return (old); \ } \ \ /* Inserts a node into the RB tree */ \ attr struct type * \ name##_RB_INSERT(struct name *head, struct type *elm) \ { \ struct type *tmp; \ struct type *parent = NULL; \ int comp = 0; \ tmp = RB_ROOT(head); \ while (tmp) { \ parent = tmp; \ comp = (cmp)(elm, parent); \ if (comp < 0) \ tmp = RB_LEFT(tmp, field); \ else if (comp > 0) \ tmp = RB_RIGHT(tmp, field); \ else \ return (tmp); \ } \ RB_SET(elm, parent, field); \ if (parent != NULL) { \ if (comp < 0) \ RB_LEFT(parent, field) = elm; \ else \ RB_RIGHT(parent, field) = elm; \ RB_AUGMENT(parent); \ } else \ RB_ROOT(head) = elm; \ name##_RB_INSERT_COLOR(head, elm); \ return (NULL); \ } \ \ /* Finds the node with the same key as elm */ \ attr struct type * \ name##_RB_FIND(struct name *head, struct type *elm) \ { \ struct type *tmp = RB_ROOT(head); \ int comp; \ while (tmp) { \ comp = cmp(elm, tmp); \ if (comp < 0) \ tmp = RB_LEFT(tmp, field); \ else if (comp > 0) \ tmp = RB_RIGHT(tmp, field); \ else \ return (tmp); \ } \ return (NULL); \ } \ \ /* Finds the first node greater than or equal to the search key */ \ attr struct type * \ name##_RB_NFIND(struct name *head, struct type *elm) \ { \ struct type *tmp = RB_ROOT(head); \ struct type *res = NULL; \ int comp; \ while (tmp) { \ comp = cmp(elm, tmp); \ if (comp < 0) { \ res = tmp; \ tmp = RB_LEFT(tmp, field); \ } \ else if (comp > 0) \ tmp = RB_RIGHT(tmp, field); \ else \ return (tmp); \ } \ return (res); \ } \ \ /* ARGSUSED */ \ attr struct type * \ name##_RB_NEXT(struct type *elm) \ { \ if (RB_RIGHT(elm, field)) { \ elm = RB_RIGHT(elm, field); \ while (RB_LEFT(elm, field)) \ elm = RB_LEFT(elm, field); \ } else { \ if (RB_PARENT(elm, field) && \ (elm == RB_LEFT(RB_PARENT(elm, field), field))) \ elm = RB_PARENT(elm, field); \ else { \ while (RB_PARENT(elm, field) && \ (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\ elm = RB_PARENT(elm, field); \ elm = RB_PARENT(elm, field); \ } \ } \ return (elm); \ } \ \ /* ARGSUSED */ \ attr struct type * \ name##_RB_PREV(struct type *elm) \ { \ if (RB_LEFT(elm, field)) { \ elm = RB_LEFT(elm, field); \ while (RB_RIGHT(elm, field)) \ elm = RB_RIGHT(elm, field); \ } else { \ if (RB_PARENT(elm, field) && \ (elm == RB_RIGHT(RB_PARENT(elm, field), field))) \ elm = RB_PARENT(elm, field); \ else { \ while (RB_PARENT(elm, field) && \ (elm == RB_LEFT(RB_PARENT(elm, field), field)))\ elm = RB_PARENT(elm, field); \ elm = RB_PARENT(elm, field); \ } \ } \ return (elm); \ } \ \ attr struct type * \ name##_RB_MINMAX(struct name *head, int val) \ { \ struct type *tmp = RB_ROOT(head); \ struct type *parent = NULL; \ while (tmp) { \ parent = tmp; \ if (val < 0) \ tmp = RB_LEFT(tmp, field); \ else \ tmp = RB_RIGHT(tmp, field); \ } \ return (parent); \ } #define RB_NEGINF -1 #define RB_INF 1 #define RB_INSERT(name, x, y) name##_RB_INSERT(x, y) #define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y) #define RB_FIND(name, x, y) name##_RB_FIND(x, y) #define RB_NFIND(name, x, y) name##_RB_NFIND(x, y) #define RB_NEXT(name, x, y) name##_RB_NEXT(y) #define RB_PREV(name, x, y) name##_RB_PREV(y) #define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF) #define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF) #define RB_FOREACH(x, name, head) \ for ((x) = RB_MIN(name, head); \ (x) != NULL; \ (x) = name##_RB_NEXT(x)) #define RB_FOREACH_FROM(x, name, y) \ for ((x) = (y); \ ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \ (x) = (y)) #define RB_FOREACH_SAFE(x, name, head, y) \ for ((x) = RB_MIN(name, head); \ ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \ (x) = (y)) #define RB_FOREACH_REVERSE(x, name, head) \ for ((x) = RB_MAX(name, head); \ (x) != NULL; \ (x) = name##_RB_PREV(x)) #define RB_FOREACH_REVERSE_FROM(x, name, y) \ for ((x) = (y); \ ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \ (x) = (y)) #define RB_FOREACH_REVERSE_SAFE(x, name, head, y) \ for ((x) = RB_MAX(name, head); \ ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \ (x) = (y)) #endif ================================================ FILE: libxhook/jni/xh_core.c ================================================ // Copyright (c) 2018-present, iQIYI, Inc. 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 2018-04-11. #include #include #include #include #include #include #include #include #include #include #include "queue.h" #include "tree.h" #include "xh_errno.h" #include "xh_log.h" #include "xh_elf.h" #include "xh_version.h" #include "xh_core.h" #define XH_CORE_DEBUG 0 //registered hook info collection typedef struct xh_core_hook_info { #if XH_CORE_DEBUG char *pathname_regex_str; #endif regex_t pathname_regex; char *symbol; void *new_func; void **old_func; TAILQ_ENTRY(xh_core_hook_info,) link; } xh_core_hook_info_t; typedef TAILQ_HEAD(xh_core_hook_info_queue, xh_core_hook_info,) xh_core_hook_info_queue_t; //ignored hook info collection typedef struct xh_core_ignore_info { #if XH_CORE_DEBUG char *pathname_regex_str; #endif regex_t pathname_regex; char *symbol; //NULL meaning for all symbols TAILQ_ENTRY(xh_core_ignore_info,) link; } xh_core_ignore_info_t; typedef TAILQ_HEAD(xh_core_ignore_info_queue, xh_core_ignore_info,) xh_core_ignore_info_queue_t; //required info from /proc/self/maps typedef struct xh_core_map_info { char *pathname; uintptr_t base_addr; xh_elf_t elf; RB_ENTRY(xh_core_map_info) link; } xh_core_map_info_t; static __inline__ int xh_core_map_info_cmp(xh_core_map_info_t *a, xh_core_map_info_t *b) { return strcmp(a->pathname, b->pathname); } typedef RB_HEAD(xh_core_map_info_tree, xh_core_map_info) xh_core_map_info_tree_t; RB_GENERATE_STATIC(xh_core_map_info_tree, xh_core_map_info, link, xh_core_map_info_cmp) //signal handler for SIGSEGV //for xh_elf_init(), xh_elf_hook(), xh_elf_check_elfheader() static int xh_core_sigsegv_enable = 1; //enable by default static struct sigaction xh_core_sigsegv_act_old; static volatile int xh_core_sigsegv_flag = 0; static sigjmp_buf xh_core_sigsegv_env; static void xh_core_sigsegv_handler(int sig) { (void)sig; if(xh_core_sigsegv_flag) siglongjmp(xh_core_sigsegv_env, 1); else sigaction(SIGSEGV, &xh_core_sigsegv_act_old, NULL); } static int xh_core_add_sigsegv_handler() { struct sigaction act; if(!xh_core_sigsegv_enable) return 0; if(0 != sigemptyset(&act.sa_mask)) return (0 == errno ? XH_ERRNO_UNKNOWN : errno); act.sa_handler = xh_core_sigsegv_handler; if(0 != sigaction(SIGSEGV, &act, &xh_core_sigsegv_act_old)) return (0 == errno ? XH_ERRNO_UNKNOWN : errno); return 0; } static void xh_core_del_sigsegv_handler() { if(!xh_core_sigsegv_enable) return; sigaction(SIGSEGV, &xh_core_sigsegv_act_old, NULL); } static xh_core_hook_info_queue_t xh_core_hook_info = TAILQ_HEAD_INITIALIZER(xh_core_hook_info); static xh_core_ignore_info_queue_t xh_core_ignore_info = TAILQ_HEAD_INITIALIZER(xh_core_ignore_info); static xh_core_map_info_tree_t xh_core_map_info = RB_INITIALIZER(&xh_core_map_info); static pthread_mutex_t xh_core_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t xh_core_cond = PTHREAD_COND_INITIALIZER; static volatile int xh_core_inited = 0; static volatile int xh_core_init_ok = 0; static volatile int xh_core_async_inited = 0; static volatile int xh_core_async_init_ok = 0; static pthread_mutex_t xh_core_refresh_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_t xh_core_refresh_thread_tid; static volatile int xh_core_refresh_thread_running = 0; static volatile int xh_core_refresh_thread_do = 0; int xh_core_register(const char *pathname_regex_str, const char *symbol, void *new_func, void **old_func) { xh_core_hook_info_t *hi; regex_t regex; if(NULL == pathname_regex_str || NULL == symbol || NULL == new_func) return XH_ERRNO_INVAL; if(xh_core_inited) { XH_LOG_ERROR("do not register hook after refresh(): %s, %s", pathname_regex_str, symbol); return XH_ERRNO_INVAL; } if(0 != regcomp(®ex, pathname_regex_str, REG_NOSUB)) return XH_ERRNO_INVAL; if(NULL == (hi = malloc(sizeof(xh_core_hook_info_t)))) return XH_ERRNO_NOMEM; if(NULL == (hi->symbol = strdup(symbol))) { free(hi); return XH_ERRNO_NOMEM; } #if XH_CORE_DEBUG if(NULL == (hi->pathname_regex_str = strdup(pathname_regex_str))) { free(hi->symbol); free(hi); return XH_ERRNO_NOMEM; } #endif hi->pathname_regex = regex; hi->new_func = new_func; hi->old_func = old_func; pthread_mutex_lock(&xh_core_mutex); TAILQ_INSERT_TAIL(&xh_core_hook_info, hi, link); pthread_mutex_unlock(&xh_core_mutex); return 0; } int xh_core_ignore(const char *pathname_regex_str, const char *symbol) { xh_core_ignore_info_t *ii; regex_t regex; if(NULL == pathname_regex_str) return XH_ERRNO_INVAL; if(xh_core_inited) { XH_LOG_ERROR("do not ignore hook after refresh(): %s, %s", pathname_regex_str, symbol ? symbol : "ALL"); return XH_ERRNO_INVAL; } if(0 != regcomp(®ex, pathname_regex_str, REG_NOSUB)) return XH_ERRNO_INVAL; if(NULL == (ii = malloc(sizeof(xh_core_ignore_info_t)))) return XH_ERRNO_NOMEM; if(NULL != symbol) { if(NULL == (ii->symbol = strdup(symbol))) { free(ii); return XH_ERRNO_NOMEM; } } else { ii->symbol = NULL; //ignore all symbols } #if XH_CORE_DEBUG if(NULL == (ii->pathname_regex_str = strdup(pathname_regex_str))) { free(ii->symbol); free(ii); return XH_ERRNO_NOMEM; } #endif ii->pathname_regex = regex; pthread_mutex_lock(&xh_core_mutex); TAILQ_INSERT_TAIL(&xh_core_ignore_info, ii, link); pthread_mutex_unlock(&xh_core_mutex); return 0; } static int xh_core_check_elf_header(uintptr_t base_addr, const char *pathname) { if(!xh_core_sigsegv_enable) { return xh_elf_check_elfheader(base_addr); } else { int ret = XH_ERRNO_UNKNOWN; xh_core_sigsegv_flag = 1; if(0 == sigsetjmp(xh_core_sigsegv_env, 1)) { ret = xh_elf_check_elfheader(base_addr); } else { ret = XH_ERRNO_SEGVERR; XH_LOG_WARN("catch SIGSEGV when check_elfheader: %s", pathname); } xh_core_sigsegv_flag = 0; return ret; } } static void xh_core_hook_impl(xh_core_map_info_t *mi) { //init if(0 != xh_elf_init(&(mi->elf), mi->base_addr, mi->pathname)) return; //hook xh_core_hook_info_t *hi; xh_core_ignore_info_t *ii; int ignore; TAILQ_FOREACH(hi, &xh_core_hook_info, link) //find hook info { if(0 == regexec(&(hi->pathname_regex), mi->pathname, 0, NULL, 0)) { ignore = 0; TAILQ_FOREACH(ii, &xh_core_ignore_info, link) //find ignore info { if(0 == regexec(&(ii->pathname_regex), mi->pathname, 0, NULL, 0)) { if(NULL == ii->symbol) //ignore all symbols return; if(0 == strcmp(ii->symbol, hi->symbol)) //ignore the current symbol { ignore = 1; break; } } } if(0 == ignore) xh_elf_hook(&(mi->elf), hi->symbol, hi->new_func, hi->old_func); } } } static void xh_core_hook(xh_core_map_info_t *mi) { if(!xh_core_sigsegv_enable) { xh_core_hook_impl(mi); } else { xh_core_sigsegv_flag = 1; if(0 == sigsetjmp(xh_core_sigsegv_env, 1)) { xh_core_hook_impl(mi); } else { XH_LOG_WARN("catch SIGSEGV when init or hook: %s", mi->pathname); } xh_core_sigsegv_flag = 0; } } static void xh_core_refresh_impl() { char line[512]; FILE *fp; uintptr_t base_addr; uintptr_t prev_base_addr = 0; char perm[5]; char prev_perm[5] = "---p"; unsigned long offset; unsigned long prev_offset = 0; int pathname_pos; char *pathname; char prev_pathname[512] = {0}; size_t pathname_len; xh_core_map_info_t *mi, *mi_tmp; xh_core_map_info_t mi_key; xh_core_hook_info_t *hi; xh_core_ignore_info_t *ii; int match; xh_core_map_info_tree_t map_info_refreshed = RB_INITIALIZER(&map_info_refreshed); if(NULL == (fp = fopen("/proc/self/maps", "r"))) { XH_LOG_ERROR("fopen /proc/self/maps failed"); return; } while(fgets(line, sizeof(line), fp)) { if(sscanf(line, "%"PRIxPTR"-%*lx %4s %lx %*x:%*x %*d%n", &base_addr, perm, &offset, &pathname_pos) != 3) continue; // do not touch the shared memory if (perm[3] != 'p') continue; // Ignore permission PROT_NONE maps if (perm[0] == '-' && perm[1] == '-' && perm[2] == '-') continue; //get pathname while(isspace(line[pathname_pos]) && pathname_pos < (int)(sizeof(line) - 1)) pathname_pos += 1; if(pathname_pos >= (int)(sizeof(line) - 1)) continue; pathname = line + pathname_pos; pathname_len = strlen(pathname); if(0 == pathname_len) continue; if(pathname[pathname_len - 1] == '\n') { pathname[pathname_len - 1] = '\0'; pathname_len -= 1; } if(0 == pathname_len) continue; if('[' == pathname[0]) continue; // Find non-executable map, we need record it. Because so maps can begin with // an non-executable map. if (perm[2] != 'x') { prev_offset = offset; prev_base_addr = base_addr; memcpy(prev_perm, perm, sizeof(prev_perm)); strcpy(prev_pathname, pathname); continue; } // Find executable map if offset == 0, it OK, // or we need check previous map for base address. if (offset != 0) { if (strcmp(prev_pathname, pathname) || prev_offset != 0 || prev_perm[0] != 'r') { continue; } // The previous map is real begin map base_addr = prev_base_addr; } //check pathname //if we need to hook this elf? match = 0; TAILQ_FOREACH(hi, &xh_core_hook_info, link) //find hook info { if(0 == regexec(&(hi->pathname_regex), pathname, 0, NULL, 0)) { TAILQ_FOREACH(ii, &xh_core_ignore_info, link) //find ignore info { if(0 == regexec(&(ii->pathname_regex), pathname, 0, NULL, 0)) { if(NULL == ii->symbol) goto check_finished; if(0 == strcmp(ii->symbol, hi->symbol)) goto check_continue; } } match = 1; check_continue: break; } } check_finished: if(0 == match) continue; //check elf header format //We are trying to do ELF header checking as late as possible. if(0 != xh_core_check_elf_header(base_addr, pathname)) continue; //check existed map item mi_key.pathname = pathname; if(NULL != (mi = RB_FIND(xh_core_map_info_tree, &xh_core_map_info, &mi_key))) { //exist RB_REMOVE(xh_core_map_info_tree, &xh_core_map_info, mi); //repeated? //We only keep the first one, that is the real base address if(NULL != RB_INSERT(xh_core_map_info_tree, &map_info_refreshed, mi)) { #if XH_CORE_DEBUG XH_LOG_DEBUG("repeated map info when update: %s", line); #endif free(mi->pathname); free(mi); continue; } //re-hook if base_addr changed if(mi->base_addr != base_addr) { mi->base_addr = base_addr; xh_core_hook(mi); } } else { //not exist, create a new map info if(NULL == (mi = (xh_core_map_info_t *)malloc(sizeof(xh_core_map_info_t)))) continue; if(NULL == (mi->pathname = strdup(pathname))) { free(mi); continue; } mi->base_addr = base_addr; //repeated? //We only keep the first one, that is the real base address if(NULL != RB_INSERT(xh_core_map_info_tree, &map_info_refreshed, mi)) { #if XH_CORE_DEBUG XH_LOG_DEBUG("repeated map info when create: %s", line); #endif free(mi->pathname); free(mi); continue; } //hook xh_core_hook(mi); //hook } } fclose(fp); //free all missing map item, maybe dlclosed? RB_FOREACH_SAFE(mi, xh_core_map_info_tree, &xh_core_map_info, mi_tmp) { #if XH_CORE_DEBUG XH_LOG_DEBUG("remove missing map info: %s", mi->pathname); #endif RB_REMOVE(xh_core_map_info_tree, &xh_core_map_info, mi); if(mi->pathname) free(mi->pathname); free(mi); } //save the new refreshed map info tree xh_core_map_info = map_info_refreshed; XH_LOG_INFO("map refreshed"); #if XH_CORE_DEBUG RB_FOREACH(mi, xh_core_map_info_tree, &xh_core_map_info) XH_LOG_DEBUG(" %"PRIxPTR" %s\n", mi->base_addr, mi->pathname); #endif } static void *xh_core_refresh_thread_func(void *arg) { (void)arg; pthread_setname_np(pthread_self(), "xh_refresh_loop"); while(xh_core_refresh_thread_running) { //waiting for a refresh task or exit pthread_mutex_lock(&xh_core_mutex); while(!xh_core_refresh_thread_do && xh_core_refresh_thread_running) { pthread_cond_wait(&xh_core_cond, &xh_core_mutex); } if(!xh_core_refresh_thread_running) { pthread_mutex_unlock(&xh_core_mutex); break; } xh_core_refresh_thread_do = 0; pthread_mutex_unlock(&xh_core_mutex); //refresh pthread_mutex_lock(&xh_core_refresh_mutex); xh_core_refresh_impl(); pthread_mutex_unlock(&xh_core_refresh_mutex); } return NULL; } static void xh_core_init_once() { if(xh_core_inited) return; pthread_mutex_lock(&xh_core_mutex); if(xh_core_inited) goto end; xh_core_inited = 1; //dump debug info XH_LOG_INFO("%s\n", xh_version_str_full()); #if XH_CORE_DEBUG xh_core_hook_info_t *hi; TAILQ_FOREACH(hi, &xh_core_hook_info, link) XH_LOG_INFO(" hook: %s @ %s, (%p, %p)\n", hi->symbol, hi->pathname_regex_str, hi->new_func, hi->old_func); xh_core_ignore_info_t *ii; TAILQ_FOREACH(ii, &xh_core_ignore_info, link) XH_LOG_INFO(" ignore: %s @ %s\n", ii->symbol ? ii->symbol : "ALL ", ii->pathname_regex_str); #endif //register signal handler if(0 != xh_core_add_sigsegv_handler()) goto end; //OK xh_core_init_ok = 1; end: pthread_mutex_unlock(&xh_core_mutex); } static void xh_core_init_async_once() { if(xh_core_async_inited) return; pthread_mutex_lock(&xh_core_mutex); if(xh_core_async_inited) goto end; xh_core_async_inited = 1; //create async refresh thread xh_core_refresh_thread_running = 1; if(0 != pthread_create(&xh_core_refresh_thread_tid, NULL, &xh_core_refresh_thread_func, NULL)) { xh_core_refresh_thread_running = 0; goto end; } //OK xh_core_async_init_ok = 1; end: pthread_mutex_unlock(&xh_core_mutex); } int xh_core_refresh(int async) { //init xh_core_init_once(); if(!xh_core_init_ok) return XH_ERRNO_UNKNOWN; if(async) { //init for async xh_core_init_async_once(); if(!xh_core_async_init_ok) return XH_ERRNO_UNKNOWN; //refresh async pthread_mutex_lock(&xh_core_mutex); xh_core_refresh_thread_do = 1; pthread_cond_signal(&xh_core_cond); pthread_mutex_unlock(&xh_core_mutex); } else { //refresh sync pthread_mutex_lock(&xh_core_refresh_mutex); xh_core_refresh_impl(); pthread_mutex_unlock(&xh_core_refresh_mutex); } return 0; } void xh_core_clear() { //stop the async refresh thread if(xh_core_async_init_ok) { pthread_mutex_lock(&xh_core_mutex); xh_core_refresh_thread_running = 0; pthread_cond_signal(&xh_core_cond); pthread_mutex_unlock(&xh_core_mutex); pthread_join(xh_core_refresh_thread_tid, NULL); xh_core_async_init_ok = 0; } xh_core_async_inited = 0; //unregister the sig handler if(xh_core_init_ok) { xh_core_del_sigsegv_handler(); xh_core_init_ok = 0; } xh_core_inited = 0; pthread_mutex_lock(&xh_core_mutex); pthread_mutex_lock(&xh_core_refresh_mutex); //free all map info xh_core_map_info_t *mi, *mi_tmp; RB_FOREACH_SAFE(mi, xh_core_map_info_tree, &xh_core_map_info, mi_tmp) { RB_REMOVE(xh_core_map_info_tree, &xh_core_map_info, mi); if(mi->pathname) free(mi->pathname); free(mi); } //free all hook info xh_core_hook_info_t *hi, *hi_tmp; TAILQ_FOREACH_SAFE(hi, &xh_core_hook_info, link, hi_tmp) { TAILQ_REMOVE(&xh_core_hook_info, hi, link); #if XH_CORE_DEBUG free(hi->pathname_regex_str); #endif regfree(&(hi->pathname_regex)); free(hi->symbol); free(hi); } //free all ignore info xh_core_ignore_info_t *ii, *ii_tmp; TAILQ_FOREACH_SAFE(ii, &xh_core_ignore_info, link, ii_tmp) { TAILQ_REMOVE(&xh_core_ignore_info, ii, link); #if XH_CORE_DEBUG free(ii->pathname_regex_str); #endif regfree(&(ii->pathname_regex)); free(ii->symbol); free(ii); } pthread_mutex_unlock(&xh_core_refresh_mutex); pthread_mutex_unlock(&xh_core_mutex); } void xh_core_enable_debug(int flag) { xh_log_priority = (flag ? ANDROID_LOG_DEBUG : ANDROID_LOG_WARN); } void xh_core_enable_sigsegv_protection(int flag) { xh_core_sigsegv_enable = (flag ? 1 : 0); } ================================================ FILE: libxhook/jni/xh_core.h ================================================ // Copyright (c) 2018-present, iQIYI, Inc. 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 2018-04-11. #ifndef XH_CORE_H #define XH_CORE_H 1 #ifdef __cplusplus extern "C" { #endif int xh_core_register(const char *pathname_regex_str, const char *symbol, void *new_func, void **old_func); int xh_core_ignore(const char *pathname_regex_str, const char *symbol); int xh_core_refresh(int async); void xh_core_clear(); void xh_core_enable_debug(int flag); void xh_core_enable_sigsegv_protection(int flag); #ifdef __cplusplus } #endif #endif ================================================ FILE: libxhook/jni/xh_elf.c ================================================ // Copyright (c) 2018-present, iQIYI, Inc. 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 2018-04-11. #include #include #include #include #include #include #include #include #include #include #include #include "xh_errno.h" #include "xh_log.h" #include "xh_util.h" #include "xh_elf.h" #define XH_ELF_DEBUG 0 #ifndef EI_ABIVERSION #define EI_ABIVERSION 8 #endif #if defined(__arm__) #define XH_ELF_R_GENERIC_JUMP_SLOT R_ARM_JUMP_SLOT //.rel.plt #define XH_ELF_R_GENERIC_GLOB_DAT R_ARM_GLOB_DAT //.rel.dyn #define XH_ELF_R_GENERIC_ABS R_ARM_ABS32 //.rel.dyn #elif defined(__aarch64__) #define XH_ELF_R_GENERIC_JUMP_SLOT R_AARCH64_JUMP_SLOT #define XH_ELF_R_GENERIC_GLOB_DAT R_AARCH64_GLOB_DAT #define XH_ELF_R_GENERIC_ABS R_AARCH64_ABS64 #elif defined(__i386__) #define XH_ELF_R_GENERIC_JUMP_SLOT R_386_JMP_SLOT #define XH_ELF_R_GENERIC_GLOB_DAT R_386_GLOB_DAT #define XH_ELF_R_GENERIC_ABS R_386_32 #elif defined(__x86_64__) #define XH_ELF_R_GENERIC_JUMP_SLOT R_X86_64_JUMP_SLOT #define XH_ELF_R_GENERIC_GLOB_DAT R_X86_64_GLOB_DAT #define XH_ELF_R_GENERIC_ABS R_X86_64_64 #endif #if defined(__LP64__) #define XH_ELF_R_SYM(info) ELF64_R_SYM(info) #define XH_ELF_R_TYPE(info) ELF64_R_TYPE(info) #else #define XH_ELF_R_SYM(info) ELF32_R_SYM(info) #define XH_ELF_R_TYPE(info) ELF32_R_TYPE(info) #endif //iterator for plain PLT typedef struct { uint8_t *cur; uint8_t *end; int is_use_rela; } xh_elf_plain_reloc_iterator_t; static void xh_elf_plain_reloc_iterator_init(xh_elf_plain_reloc_iterator_t *self, ElfW(Addr) rel, ElfW(Word) rel_sz, int is_use_rela) { self->cur = (uint8_t *)rel; self->end = self->cur + rel_sz; self->is_use_rela = is_use_rela; } static void *xh_elf_plain_reloc_iterator_next(xh_elf_plain_reloc_iterator_t *self) { if(self->cur >= self->end) return NULL; void *ret = (void *)(self->cur); self->cur += (self->is_use_rela ? sizeof(ElfW(Rela)) : sizeof(ElfW(Rel))); return ret; } //sleb128 decoder typedef struct { uint8_t *cur; uint8_t *end; } xh_elf_sleb128_decoder_t; static void xh_elf_sleb128_decoder_init(xh_elf_sleb128_decoder_t *self, ElfW(Addr) rel, ElfW(Word) rel_sz) { self->cur = (uint8_t *)rel; self->end = self->cur + rel_sz; } static int xh_elf_sleb128_decoder_next(xh_elf_sleb128_decoder_t *self, size_t *ret) { size_t value = 0; static const size_t size = 8 * sizeof(value); size_t shift = 0; uint8_t byte; do { if(self->cur >= self->end) return XH_ERRNO_FORMAT; byte = *(self->cur)++; value |= ((size_t)(byte & 127) << shift); shift += 7; } while(byte & 128); if(shift < size && (byte & 64)) { value |= -((size_t)(1) << shift); } *ret = value; return 0; } //iterator for sleb128 decoded packed PLT typedef struct { xh_elf_sleb128_decoder_t decoder; size_t relocation_count; size_t group_size; size_t group_flags; size_t group_r_offset_delta; size_t relocation_index; size_t relocation_group_index; ElfW(Rela) rela; ElfW(Rel) rel; ElfW(Addr) r_offset; size_t r_info; ssize_t r_addend; int is_use_rela; } xh_elf_packed_reloc_iterator_t; const size_t RELOCATION_GROUPED_BY_INFO_FLAG = 1; const size_t RELOCATION_GROUPED_BY_OFFSET_DELTA_FLAG = 2; const size_t RELOCATION_GROUPED_BY_ADDEND_FLAG = 4; const size_t RELOCATION_GROUP_HAS_ADDEND_FLAG = 8; static int xh_elf_packed_reloc_iterator_init(xh_elf_packed_reloc_iterator_t *self, ElfW(Addr) rel, ElfW(Word) rel_sz, int is_use_rela) { int r; memset(self, 0, sizeof(xh_elf_packed_reloc_iterator_t)); xh_elf_sleb128_decoder_init(&(self->decoder), rel, rel_sz); self->is_use_rela = is_use_rela; if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), &(self->relocation_count)))) return r; if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), (size_t *)&(self->r_offset)))) return r; return 0; } static int xh_elf_packed_reloc_iterator_read_group_fields(xh_elf_packed_reloc_iterator_t *self) { int r; size_t val; if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), &(self->group_size)))) return r; if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), &(self->group_flags)))) return r; if(self->group_flags & RELOCATION_GROUPED_BY_OFFSET_DELTA_FLAG) if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), &(self->group_r_offset_delta)))) return r; if(self->group_flags & RELOCATION_GROUPED_BY_INFO_FLAG) if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), (size_t *)&(self->r_info)))) return r; if((self->group_flags & RELOCATION_GROUP_HAS_ADDEND_FLAG) && (self->group_flags & RELOCATION_GROUPED_BY_ADDEND_FLAG)) { if(0 == self->is_use_rela) { XH_LOG_ERROR("unexpected r_addend in android.rel section"); return XH_ERRNO_FORMAT; } if(0 != (r = xh_elf_sleb128_decoder_next(&(self->decoder), &val))) return r; self->r_addend += (ssize_t)val; } else if(0 == (self->group_flags & RELOCATION_GROUP_HAS_ADDEND_FLAG)) { self->r_addend = 0; } self->relocation_group_index = 0; return 0; } static void *xh_elf_packed_reloc_iterator_next(xh_elf_packed_reloc_iterator_t *self) { size_t val; if(self->relocation_index >= self->relocation_count) return NULL; if(self->relocation_group_index == self->group_size) { if(0 != xh_elf_packed_reloc_iterator_read_group_fields(self)) return NULL; } if(self->group_flags & RELOCATION_GROUPED_BY_OFFSET_DELTA_FLAG) { self->r_offset += self->group_r_offset_delta; } else { if(0 != xh_elf_sleb128_decoder_next(&(self->decoder), &val)) return NULL; self->r_offset += val; } if(0 == (self->group_flags & RELOCATION_GROUPED_BY_INFO_FLAG)) if(0 != xh_elf_sleb128_decoder_next(&(self->decoder), &(self->r_info))) return NULL; if(self->is_use_rela && (self->group_flags & RELOCATION_GROUP_HAS_ADDEND_FLAG) && (0 == (self->group_flags & RELOCATION_GROUPED_BY_ADDEND_FLAG))) { if(0 != xh_elf_sleb128_decoder_next(&(self->decoder), &val)) return NULL; self->r_addend += (ssize_t)val; } self->relocation_index++; self->relocation_group_index++; if(self->is_use_rela) { self->rela.r_offset = self->r_offset; self->rela.r_info = self->r_info; self->rela.r_addend = self->r_addend; return (void *)(&(self->rela)); } else { self->rel.r_offset = self->r_offset; self->rel.r_info = self->r_info; return (void *)(&(self->rel)); } } //ELF header checker int xh_elf_check_elfheader(uintptr_t base_addr) { ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base_addr; //check magic if(0 != memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) return XH_ERRNO_FORMAT; //check class (64/32) #if defined(__LP64__) if(ELFCLASS64 != ehdr->e_ident[EI_CLASS]) return XH_ERRNO_FORMAT; #else if(ELFCLASS32 != ehdr->e_ident[EI_CLASS]) return XH_ERRNO_FORMAT; #endif //check endian (little/big) if(ELFDATA2LSB != ehdr->e_ident[EI_DATA]) return XH_ERRNO_FORMAT; //check version if(EV_CURRENT != ehdr->e_ident[EI_VERSION]) return XH_ERRNO_FORMAT; //check type if(ET_EXEC != ehdr->e_type && ET_DYN != ehdr->e_type) return XH_ERRNO_FORMAT; //check machine #if defined(__arm__) if(EM_ARM != ehdr->e_machine) return XH_ERRNO_FORMAT; #elif defined(__aarch64__) if(EM_AARCH64 != ehdr->e_machine) return XH_ERRNO_FORMAT; #elif defined(__i386__) if(EM_386 != ehdr->e_machine) return XH_ERRNO_FORMAT; #elif defined(__x86_64__) if(EM_X86_64 != ehdr->e_machine) return XH_ERRNO_FORMAT; #else return XH_ERRNO_FORMAT; #endif //check version if(EV_CURRENT != ehdr->e_version) return XH_ERRNO_FORMAT; return 0; } //ELF hash func static uint32_t xh_elf_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; } //GNU hash func static uint32_t xh_elf_gnu_hash(const uint8_t *name) { uint32_t h = 5381; while(*name != 0) { h += (h << 5) + *name++; } return h; } static ElfW(Phdr) *xh_elf_get_first_segment_by_type(xh_elf_t *self, ElfW(Word) type) { ElfW(Phdr) *phdr; for(phdr = self->phdr; phdr < self->phdr + self->ehdr->e_phnum; phdr++) { if(phdr->p_type == type) { return phdr; } } return NULL; } static ElfW(Phdr) *xh_elf_get_first_segment_by_type_offset(xh_elf_t *self, ElfW(Word) type, ElfW(Off) offset) { ElfW(Phdr) *phdr; for(phdr = self->phdr; phdr < self->phdr + self->ehdr->e_phnum; phdr++) { if(phdr->p_type == type && phdr->p_offset == offset) { return phdr; } } return NULL; } static int xh_elf_hash_lookup(xh_elf_t *self, const char *symbol, uint32_t *symidx) { uint32_t hash = xh_elf_hash((uint8_t *)symbol); const char *symbol_cur; uint32_t i; for(i = self->bucket[hash % self->bucket_cnt]; 0 != i; i = self->chain[i]) { symbol_cur = self->strtab + self->symtab[i].st_name; if(0 == strcmp(symbol, symbol_cur)) { *symidx = i; XH_LOG_INFO("found %s at symidx: %u (ELF_HASH)\n", symbol, *symidx); return 0; } } return XH_ERRNO_NOTFND; } static int xh_elf_gnu_hash_lookup_def(xh_elf_t *self, const char *symbol, uint32_t *symidx) { uint32_t hash = xh_elf_gnu_hash((uint8_t *)symbol); static uint32_t elfclass_bits = sizeof(ElfW(Addr)) * 8; size_t word = self->bloom[(hash / elfclass_bits) % self->bloom_sz]; size_t mask = 0 | (size_t)1 << (hash % elfclass_bits) | (size_t)1 << ((hash >> self->bloom_shift) % elfclass_bits); //if at least one bit is not set, this symbol is surely missing if((word & mask) != mask) return XH_ERRNO_NOTFND; //ignore STN_UNDEF uint32_t i = self->bucket[hash % self->bucket_cnt]; if(i < self->symoffset) return XH_ERRNO_NOTFND; //loop through the chain while(1) { const char *symname = self->strtab + self->symtab[i].st_name; const uint32_t symhash = self->chain[i - self->symoffset]; if((hash | (uint32_t)1) == (symhash | (uint32_t)1) && 0 == strcmp(symbol, symname)) { *symidx = i; XH_LOG_INFO("found %s at symidx: %u (GNU_HASH DEF)\n", symbol, *symidx); return 0; } //chain ends with an element with the lowest bit set to 1 if(symhash & (uint32_t)1) break; i++; } return XH_ERRNO_NOTFND; } static int xh_elf_gnu_hash_lookup_undef(xh_elf_t *self, const char *symbol, uint32_t *symidx) { uint32_t i; for(i = 0; i < self->symoffset; i++) { const char *symname = self->strtab + self->symtab[i].st_name; if(0 == strcmp(symname, symbol)) { *symidx = i; XH_LOG_INFO("found %s at symidx: %u (GNU_HASH UNDEF)\n", symbol, *symidx); return 0; } } return XH_ERRNO_NOTFND; } static int xh_elf_gnu_hash_lookup(xh_elf_t *self, const char *symbol, uint32_t *symidx) { if(0 == xh_elf_gnu_hash_lookup_def(self, symbol, symidx)) return 0; if(0 == xh_elf_gnu_hash_lookup_undef(self, symbol, symidx)) return 0; return XH_ERRNO_NOTFND; } static int xh_elf_find_symidx_by_name(xh_elf_t *self, const char *symbol, uint32_t *symidx) { if(self->is_use_gnu_hash) return xh_elf_gnu_hash_lookup(self, symbol, symidx); else return xh_elf_hash_lookup(self, symbol, symidx); } static int xh_elf_replace_function(xh_elf_t *self, const char *symbol, ElfW(Addr) addr, void *new_func, void **old_func) { void *old_addr; unsigned int old_prot = 0; unsigned int need_prot = PROT_READ | PROT_WRITE; int r; //already replaced? //here we assume that we always have read permission, is this a problem? if(*(void **)addr == new_func) return 0; //get old prot if(0 != (r = xh_util_get_addr_protect(addr, self->pathname, &old_prot))) { XH_LOG_ERROR("get addr prot failed. ret: %d", r); return r; } if(old_prot != need_prot) { //set new prot if(0 != (r = xh_util_set_addr_protect(addr, need_prot))) { XH_LOG_ERROR("set addr prot failed. ret: %d", r); return r; } } //save old func old_addr = *(void **)addr; if(NULL != old_func) *old_func = old_addr; //replace func *(void **)addr = new_func; //segmentation fault sometimes if(old_prot != need_prot) { //restore the old prot if(0 != (r = xh_util_set_addr_protect(addr, old_prot))) { XH_LOG_WARN("restore addr prot failed. ret: %d", r); } } //clear cache xh_util_flush_instruction_cache(addr); XH_LOG_INFO("XH_HK_OK %p: %p -> %p %s %s\n", (void *)addr, old_addr, new_func, symbol, self->pathname); return 0; } static int xh_elf_check(xh_elf_t *self) { if(0 == self->base_addr) { XH_LOG_ERROR("base_addr == 0\n"); return 1; } if(0 == self->bias_addr) { XH_LOG_ERROR("bias_addr == 0\n"); return 1; } if(NULL == self->ehdr) { XH_LOG_ERROR("ehdr == NULL\n"); return 1; } if(NULL == self->phdr) { XH_LOG_ERROR("phdr == NULL\n"); return 1; } if(NULL == self->strtab) { XH_LOG_ERROR("strtab == NULL\n"); return 1; } if(NULL == self->symtab) { XH_LOG_ERROR("symtab == NULL\n"); return 1; } if(NULL == self->bucket) { XH_LOG_ERROR("bucket == NULL\n"); return 1; } if(NULL == self->chain) { XH_LOG_ERROR("chain == NULL\n"); return 1; } if(1 == self->is_use_gnu_hash && NULL == self->bloom) { XH_LOG_ERROR("bloom == NULL\n"); return 1; } return 0; } #if XH_ELF_DEBUG static void xh_elf_dump_elfheader(xh_elf_t *self) { static char alpha_tab[17] = "0123456789ABCDEF"; int i; uint8_t ch; char buff[EI_NIDENT * 3 + 1]; for(i = 0; i < EI_NIDENT; i++) { ch = self->ehdr->e_ident[i]; buff[i * 3 + 0] = alpha_tab[(int)((ch >> 4) & 0x0F)]; buff[i * 3 + 1] = alpha_tab[(int)(ch & 0x0F)]; buff[i * 3 + 2] = ' '; } buff[EI_NIDENT * 3] = '\0'; XH_LOG_DEBUG("Elf Header:\n"); XH_LOG_DEBUG(" Magic: %s\n", buff); XH_LOG_DEBUG(" Class: %#x\n", self->ehdr->e_ident[EI_CLASS]); XH_LOG_DEBUG(" Data: %#x\n", self->ehdr->e_ident[EI_DATA]); XH_LOG_DEBUG(" Version: %#x\n", self->ehdr->e_ident[EI_VERSION]); XH_LOG_DEBUG(" OS/ABI: %#x\n", self->ehdr->e_ident[EI_OSABI]); XH_LOG_DEBUG(" ABI Version: %#x\n", self->ehdr->e_ident[EI_ABIVERSION]); XH_LOG_DEBUG(" Type: %#x\n", self->ehdr->e_type); XH_LOG_DEBUG(" Machine: %#x\n", self->ehdr->e_machine); XH_LOG_DEBUG(" Version: %#x\n", self->ehdr->e_version); XH_LOG_DEBUG(" Entry point address: %"XH_UTIL_FMT_X"\n", self->ehdr->e_entry); XH_LOG_DEBUG(" Start of program headers: %"XH_UTIL_FMT_X" (bytes into file)\n", self->ehdr->e_phoff); XH_LOG_DEBUG(" Start of section headers: %"XH_UTIL_FMT_X" (bytes into file)\n", self->ehdr->e_shoff); XH_LOG_DEBUG(" Flags: %#x\n", self->ehdr->e_flags); XH_LOG_DEBUG(" Size of this header: %u (bytes)\n", self->ehdr->e_ehsize); XH_LOG_DEBUG(" Size of program headers: %u (bytes)\n", self->ehdr->e_phentsize); XH_LOG_DEBUG(" Number of program headers: %u\n", self->ehdr->e_phnum); XH_LOG_DEBUG(" Size of section headers: %u (bytes)\n", self->ehdr->e_shentsize); XH_LOG_DEBUG(" Number of section headers: %u\n", self->ehdr->e_shnum); XH_LOG_DEBUG(" Section header string table index: %u\n", self->ehdr->e_shstrndx); } static void xh_elf_dump_programheader(xh_elf_t *self) { ElfW(Phdr) *phdr = self->phdr; size_t i; XH_LOG_DEBUG("Program Headers:\n"); XH_LOG_DEBUG(" %-8s " \ "%-"XH_UTIL_FMT_FIXED_S" " \ "%-"XH_UTIL_FMT_FIXED_S" " \ "%-"XH_UTIL_FMT_FIXED_S" " \ "%-"XH_UTIL_FMT_FIXED_S" " \ "%-"XH_UTIL_FMT_FIXED_S" " \ "%-8s " \ "%-s\n", "Type", "Offset", "VirtAddr", "PhysAddr", "FileSiz", "MemSiz", "Flg", "Align"); for(i = 0; i < self->ehdr->e_phnum; i++, phdr++) { XH_LOG_DEBUG(" %-8x " \ "%."XH_UTIL_FMT_FIXED_X" " \ "%."XH_UTIL_FMT_FIXED_X" " \ "%."XH_UTIL_FMT_FIXED_X" " \ "%."XH_UTIL_FMT_FIXED_X" " \ "%."XH_UTIL_FMT_FIXED_X" " \ "%-8x " \ "%"XH_UTIL_FMT_X"\n", phdr->p_type, phdr->p_offset, phdr->p_vaddr, phdr->p_paddr, phdr->p_filesz, phdr->p_memsz, phdr->p_flags, phdr->p_align); } } static void xh_elf_dump_dynamic(xh_elf_t *self) { ElfW(Dyn) *dyn = self->dyn; size_t dyn_cnt = (self->dyn_sz / sizeof(ElfW(Dyn))); size_t i; XH_LOG_DEBUG("Dynamic section contains %zu entries:\n", dyn_cnt); XH_LOG_DEBUG(" %-"XH_UTIL_FMT_FIXED_S" " \ "%s\n", "Tag", "Val"); for(i = 0; i < dyn_cnt; i++, dyn++) { XH_LOG_DEBUG(" %-"XH_UTIL_FMT_FIXED_X" " \ "%-"XH_UTIL_FMT_X"\n", dyn->d_tag, dyn->d_un.d_val); } } static void xh_elf_dump_rel(xh_elf_t *self, const char *type, ElfW(Addr) rel_addr, ElfW(Word) rel_sz) { ElfW(Rela) *rela; ElfW(Rel) *rel; ElfW(Word) cnt; ElfW(Word) i; ElfW(Sym) *sym; if(self->is_use_rela) { rela = (ElfW(Rela) *)(rel_addr); cnt = rel_sz / sizeof(ElfW(Rela)); } else { rel = (ElfW(Rel) *)(rel_addr); cnt = rel_sz / sizeof(ElfW(Rel)); } XH_LOG_DEBUG("Relocation section '.rel%s%s' contains %u entries:\n", (self->is_use_rela ? "a" : ""), type, cnt); XH_LOG_DEBUG(" %-"XH_UTIL_FMT_FIXED_S" " \ "%-"XH_UTIL_FMT_FIXED_S" " \ "%-8s " \ "%-8s " \ "%-8s " \ "%s\n", "Offset", "Info", "Type", "Sym.Idx", "Sym.Val", "Sym.Name"); const char *fmt = " %."XH_UTIL_FMT_FIXED_X" " \ "%."XH_UTIL_FMT_FIXED_X" " \ "%.8x " \ "%.8u " \ "%.8x " \ "%s\n"; for(i = 0; i < cnt; i++) { if(self->is_use_rela) { sym = &(self->symtab[XH_ELF_R_SYM(rela[i].r_info)]); XH_LOG_DEBUG(fmt, rela[i].r_offset, rela[i].r_info, XH_ELF_R_TYPE(rela[i].r_info), XH_ELF_R_SYM(rela[i].r_info), sym->st_value, self->strtab + sym->st_name); } else { sym = &(self->symtab[XH_ELF_R_SYM(rel[i].r_info)]); XH_LOG_DEBUG(fmt, rel[i].r_offset, rel[i].r_info, XH_ELF_R_TYPE(rel[i].r_info), XH_ELF_R_SYM(rel[i].r_info), sym->st_value, self->strtab + sym->st_name); } } } static void xh_elf_dump_symtab(xh_elf_t *self) { if(self->is_use_gnu_hash) return; ElfW(Word) symtab_cnt = self->chain_cnt; ElfW(Word) i; XH_LOG_DEBUG("Symbol table '.dynsym' contains %u entries:\n", symtab_cnt); XH_LOG_DEBUG(" %-8s " \ "%-"XH_UTIL_FMT_FIXED_S" " \ "%s\n", "Idx", "Value", "Name"); for(i = 0; i < symtab_cnt; i++) { XH_LOG_DEBUG(" %-8u " \ "%."XH_UTIL_FMT_FIXED_X" " \ "%s\n", i, self->symtab[i].st_value, self->strtab + self->symtab[i].st_name); } } static void xh_elf_dump(xh_elf_t *self) { if(xh_log_priority < ANDROID_LOG_DEBUG) return; XH_LOG_DEBUG("Elf Pathname: %s\n", self->pathname); XH_LOG_DEBUG("Elf bias addr: %p\n", (void *)self->bias_addr); xh_elf_dump_elfheader(self); xh_elf_dump_programheader(self); xh_elf_dump_dynamic(self); xh_elf_dump_rel(self, ".plt", self->relplt, self->relplt_sz); xh_elf_dump_rel(self, ".dyn", self->reldyn, self->reldyn_sz); xh_elf_dump_symtab(self); } #endif int xh_elf_init(xh_elf_t *self, uintptr_t base_addr, const char *pathname) { if(0 == base_addr || NULL == pathname) return XH_ERRNO_INVAL; //always reset memset(self, 0, sizeof(xh_elf_t)); self->pathname = pathname; self->base_addr = (ElfW(Addr))base_addr; self->ehdr = (ElfW(Ehdr) *)base_addr; self->phdr = (ElfW(Phdr) *)(base_addr + self->ehdr->e_phoff); //segmentation fault sometimes //find the first load-segment with offset 0 ElfW(Phdr) *phdr0 = xh_elf_get_first_segment_by_type_offset(self, PT_LOAD, 0); if(NULL == phdr0) { XH_LOG_ERROR("Can NOT found the first load segment. %s", pathname); return XH_ERRNO_FORMAT; } #if XH_ELF_DEBUG if(0 != phdr0->p_vaddr) XH_LOG_DEBUG("first load-segment vaddr NOT 0 (vaddr: %p). %s", (void *)(phdr0->p_vaddr), pathname); #endif //save load bias addr if(self->base_addr < phdr0->p_vaddr) return XH_ERRNO_FORMAT; self->bias_addr = self->base_addr - phdr0->p_vaddr; //find dynamic-segment ElfW(Phdr) *dhdr = xh_elf_get_first_segment_by_type(self, PT_DYNAMIC); if(NULL == dhdr) { XH_LOG_ERROR("Can NOT found dynamic segment. %s", pathname); return XH_ERRNO_FORMAT; } //parse dynamic-segment self->dyn = (ElfW(Dyn) *)(self->bias_addr + dhdr->p_vaddr); self->dyn_sz = dhdr->p_memsz; ElfW(Dyn) *dyn = self->dyn; ElfW(Dyn) *dyn_end = self->dyn + (self->dyn_sz / sizeof(ElfW(Dyn))); uint32_t *raw; for(; dyn < dyn_end; dyn++) { switch(dyn->d_tag) //segmentation fault sometimes { case DT_NULL: //the end of the dynamic-section dyn = dyn_end; break; case DT_STRTAB: { self->strtab = (const char *)(self->bias_addr + dyn->d_un.d_ptr); if((ElfW(Addr))(self->strtab) < self->base_addr) return XH_ERRNO_FORMAT; break; } case DT_SYMTAB: { self->symtab = (ElfW(Sym) *)(self->bias_addr + dyn->d_un.d_ptr); if((ElfW(Addr))(self->symtab) < self->base_addr) return XH_ERRNO_FORMAT; break; } case DT_PLTREL: //use rel or rela? self->is_use_rela = (dyn->d_un.d_val == DT_RELA ? 1 : 0); break; case DT_JMPREL: { self->relplt = (ElfW(Addr))(self->bias_addr + dyn->d_un.d_ptr); if((ElfW(Addr))(self->relplt) < self->base_addr) return XH_ERRNO_FORMAT; break; } case DT_PLTRELSZ: self->relplt_sz = dyn->d_un.d_val; break; case DT_REL: case DT_RELA: { self->reldyn = (ElfW(Addr))(self->bias_addr + dyn->d_un.d_ptr); if((ElfW(Addr))(self->reldyn) < self->base_addr) return XH_ERRNO_FORMAT; break; } case DT_RELSZ: case DT_RELASZ: self->reldyn_sz = dyn->d_un.d_val; break; case DT_ANDROID_REL: case DT_ANDROID_RELA: { self->relandroid = (ElfW(Addr))(self->bias_addr + dyn->d_un.d_ptr); if((ElfW(Addr))(self->relandroid) < self->base_addr) return XH_ERRNO_FORMAT; break; } case DT_ANDROID_RELSZ: case DT_ANDROID_RELASZ: self->relandroid_sz = dyn->d_un.d_val; break; case DT_HASH: { //ignore DT_HASH when ELF contains DT_GNU_HASH hash table if(1 == self->is_use_gnu_hash) continue; raw = (uint32_t *)(self->bias_addr + dyn->d_un.d_ptr); if((ElfW(Addr))raw < self->base_addr) return XH_ERRNO_FORMAT; self->bucket_cnt = raw[0]; self->chain_cnt = raw[1]; self->bucket = &raw[2]; self->chain = &(self->bucket[self->bucket_cnt]); break; } case DT_GNU_HASH: { raw = (uint32_t *)(self->bias_addr + dyn->d_un.d_ptr); if((ElfW(Addr))raw < self->base_addr) return XH_ERRNO_FORMAT; self->bucket_cnt = raw[0]; self->symoffset = raw[1]; self->bloom_sz = raw[2]; self->bloom_shift = raw[3]; self->bloom = (ElfW(Addr) *)(&raw[4]); self->bucket = (uint32_t *)(&(self->bloom[self->bloom_sz])); self->chain = (uint32_t *)(&(self->bucket[self->bucket_cnt])); self->is_use_gnu_hash = 1; break; } default: break; } } //check android rel/rela if(0 != self->relandroid) { const char *rel = (const char *)self->relandroid; if(self->relandroid_sz < 4 || rel[0] != 'A' || rel[1] != 'P' || rel[2] != 'S' || rel[3] != '2') { XH_LOG_ERROR("android rel/rela format error\n"); return XH_ERRNO_FORMAT; } self->relandroid += 4; self->relandroid_sz -= 4; } //check elf info if(0 != xh_elf_check(self)) { XH_LOG_ERROR("elf init check failed. %s", pathname); return XH_ERRNO_FORMAT; } #if XH_ELF_DEBUG xh_elf_dump(self); #endif XH_LOG_INFO("init OK: %s (%s %s PLT:%u DYN:%u ANDROID:%u)\n", self->pathname, self->is_use_rela ? "RELA" : "REL", self->is_use_gnu_hash ? "GNU_HASH" : "ELF_HASH", self->relplt_sz, self->reldyn_sz, self->relandroid_sz); return 0; } static int xh_elf_find_and_replace_func(xh_elf_t *self, const char *section, int is_plt, const char *symbol, void *new_func, void **old_func, uint32_t symidx, void *rel_common, int *found) { ElfW(Rela) *rela; ElfW(Rel) *rel; ElfW(Addr) r_offset; size_t r_info; size_t r_sym; size_t r_type; ElfW(Addr) addr; int r; if(NULL != found) *found = 0; if(self->is_use_rela) { rela = (ElfW(Rela) *)rel_common; r_info = rela->r_info; r_offset = rela->r_offset; } else { rel = (ElfW(Rel) *)rel_common; r_info = rel->r_info; r_offset = rel->r_offset; } //check sym r_sym = XH_ELF_R_SYM(r_info); if(r_sym != symidx) return 0; //check type r_type = XH_ELF_R_TYPE(r_info); if(is_plt && r_type != XH_ELF_R_GENERIC_JUMP_SLOT) return 0; if(!is_plt && (r_type != XH_ELF_R_GENERIC_GLOB_DAT && r_type != XH_ELF_R_GENERIC_ABS)) return 0; //we found it XH_LOG_INFO("found %s at %s offset: %p\n", symbol, section, (void *)r_offset); if(NULL != found) *found = 1; //do replace addr = self->bias_addr + r_offset; if(addr < self->base_addr) return XH_ERRNO_FORMAT; if(0 != (r = xh_elf_replace_function(self, symbol, addr, new_func, old_func))) { XH_LOG_ERROR("replace function failed: %s at %s\n", symbol, section); return r; } return 0; } int xh_elf_hook(xh_elf_t *self, const char *symbol, void *new_func, void **old_func) { uint32_t symidx; void *rel_common; xh_elf_plain_reloc_iterator_t plain_iter; xh_elf_packed_reloc_iterator_t packed_iter; int found; int r; if(NULL == self->pathname) { XH_LOG_ERROR("not inited\n"); return XH_ERRNO_ELFINIT; //not inited? } if(NULL == symbol || NULL == new_func) return XH_ERRNO_INVAL; XH_LOG_INFO("hooking %s in %s\n", symbol, self->pathname); //find symbol index by symbol name if(0 != (r = xh_elf_find_symidx_by_name(self, symbol, &symidx))) return 0; //replace for .rel(a).plt if(0 != self->relplt) { xh_elf_plain_reloc_iterator_init(&plain_iter, self->relplt, self->relplt_sz, self->is_use_rela); while(NULL != (rel_common = xh_elf_plain_reloc_iterator_next(&plain_iter))) { if(0 != (r = xh_elf_find_and_replace_func(self, (self->is_use_rela ? ".rela.plt" : ".rel.plt"), 1, symbol, new_func, old_func, symidx, rel_common, &found))) return r; if(found) break; } } //replace for .rel(a).dyn if(0 != self->reldyn) { xh_elf_plain_reloc_iterator_init(&plain_iter, self->reldyn, self->reldyn_sz, self->is_use_rela); while(NULL != (rel_common = xh_elf_plain_reloc_iterator_next(&plain_iter))) { if(0 != (r = xh_elf_find_and_replace_func(self, (self->is_use_rela ? ".rela.dyn" : ".rel.dyn"), 0, symbol, new_func, old_func, symidx, rel_common, NULL))) return r; } } //replace for .rel(a).android if(0 != self->relandroid) { xh_elf_packed_reloc_iterator_init(&packed_iter, self->relandroid, self->relandroid_sz, self->is_use_rela); while(NULL != (rel_common = xh_elf_packed_reloc_iterator_next(&packed_iter))) { if(0 != (r = xh_elf_find_and_replace_func(self, (self->is_use_rela ? ".rela.android" : ".rel.android"), 0, symbol, new_func, old_func, symidx, rel_common, NULL))) return r; } } return 0; } ================================================ FILE: libxhook/jni/xh_elf.h ================================================ // Copyright (c) 2018-present, iQIYI, Inc. 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 2018-04-11. #ifndef XH_ELF_H #define XH_ELF_H 1 #include #include #include #ifdef __cplusplus extern "C" { #endif typedef struct { const char *pathname; ElfW(Addr) base_addr; ElfW(Addr) bias_addr; ElfW(Ehdr) *ehdr; ElfW(Phdr) *phdr; ElfW(Dyn) *dyn; //.dynamic ElfW(Word) dyn_sz; const char *strtab; //.dynstr (string-table) ElfW(Sym) *symtab; //.dynsym (symbol-index to string-table's offset) ElfW(Addr) relplt; //.rel.plt or .rela.plt ElfW(Word) relplt_sz; ElfW(Addr) reldyn; //.rel.dyn or .rela.dyn ElfW(Word) reldyn_sz; ElfW(Addr) relandroid; //android compressed rel or rela ElfW(Word) relandroid_sz; //for ELF hash uint32_t *bucket; uint32_t bucket_cnt; uint32_t *chain; uint32_t chain_cnt; //invalid for GNU hash //append for GNU hash uint32_t symoffset; ElfW(Addr) *bloom; uint32_t bloom_sz; uint32_t bloom_shift; int is_use_rela; int is_use_gnu_hash; } xh_elf_t; int xh_elf_init(xh_elf_t *self, uintptr_t base_addr, const char *pathname); int xh_elf_hook(xh_elf_t *self, const char *symbol, void *new_func, void **old_func); int xh_elf_check_elfheader(uintptr_t base_addr); #ifdef __cplusplus } #endif #endif ================================================ FILE: libxhook/jni/xh_errno.h ================================================ // Copyright (c) 2018-present, iQIYI, Inc. 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 2018-04-11. #ifndef XH_ERRNO_H #define XH_ERRNO_H 1 #define XH_ERRNO_UNKNOWN 1001 #define XH_ERRNO_INVAL 1002 #define XH_ERRNO_NOMEM 1003 #define XH_ERRNO_REPEAT 1004 #define XH_ERRNO_NOTFND 1005 #define XH_ERRNO_BADMAPS 1006 #define XH_ERRNO_FORMAT 1007 #define XH_ERRNO_ELFINIT 1008 #define XH_ERRNO_SEGVERR 1009 #endif ================================================ FILE: libxhook/jni/xh_jni.c ================================================ // Copyright (c) 2018-present, iQIYI, Inc. 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 2018-04-11. #include #include "xhook.h" #define JNI_API_DEF(f) Java_com_qiyi_xhook_NativeHandler_##f JNIEXPORT jint JNI_API_DEF(refresh)(JNIEnv *env, jobject obj, jboolean async) { (void)env; (void)obj; return xhook_refresh(async ? 1 : 0); } JNIEXPORT void JNI_API_DEF(clear)(JNIEnv *env, jobject obj) { (void)env; (void)obj; xhook_clear(); } JNIEXPORT void JNI_API_DEF(enableDebug)(JNIEnv *env, jobject obj, jboolean flag) { (void)env; (void)obj; xhook_enable_debug(flag ? 1 : 0); } JNIEXPORT void JNI_API_DEF(enableSigSegvProtection)(JNIEnv *env, jobject obj, jboolean flag) { (void)env; (void)obj; xhook_enable_sigsegv_protection(flag ? 1 : 0); } ================================================ FILE: libxhook/jni/xh_log.c ================================================ // Copyright (c) 2018-present, iQIYI, Inc. 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 2018-04-11. #include #include "xh_log.h" android_LogPriority xh_log_priority = ANDROID_LOG_WARN; ================================================ FILE: libxhook/jni/xh_log.h ================================================ // Copyright (c) 2018-present, iQIYI, Inc. 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 2018-04-11. #ifndef XH_LOG_H #define XH_LOG_H 1 #include #ifdef __cplusplus extern "C" { #endif extern android_LogPriority xh_log_priority; #define XH_LOG_TAG "xhook" #define XH_LOG_DEBUG(fmt, ...) do{if(xh_log_priority <= ANDROID_LOG_DEBUG) __android_log_print(ANDROID_LOG_DEBUG, XH_LOG_TAG, fmt, ##__VA_ARGS__);}while(0) #define XH_LOG_INFO(fmt, ...) do{if(xh_log_priority <= ANDROID_LOG_INFO) __android_log_print(ANDROID_LOG_INFO, XH_LOG_TAG, fmt, ##__VA_ARGS__);}while(0) #define XH_LOG_WARN(fmt, ...) do{if(xh_log_priority <= ANDROID_LOG_WARN) __android_log_print(ANDROID_LOG_WARN, XH_LOG_TAG, fmt, ##__VA_ARGS__);}while(0) #define XH_LOG_ERROR(fmt, ...) do{if(xh_log_priority <= ANDROID_LOG_ERROR) __android_log_print(ANDROID_LOG_ERROR, XH_LOG_TAG, fmt, ##__VA_ARGS__);}while(0) #ifdef __cplusplus } #endif #endif ================================================ FILE: libxhook/jni/xh_util.c ================================================ // Copyright (c) 2018-present, iQIYI, Inc. 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 2018-04-11. #include #include #include #include #include #include #include #include #include #include #include #include #include "xh_util.h" #include "xh_errno.h" #include "xh_log.h" #define PAGE_START(addr) ((addr) & PAGE_MASK) #define PAGE_END(addr) (PAGE_START(addr + sizeof(uintptr_t) - 1) + PAGE_SIZE) #define PAGE_COVER(addr) (PAGE_END(addr) - PAGE_START(addr)) int xh_util_get_mem_protect(uintptr_t addr, size_t len, const char *pathname, unsigned int *prot) { uintptr_t start_addr = addr; uintptr_t end_addr = addr + len; FILE *fp; char line[512]; uintptr_t start, end; char perm[5]; int load0 = 1; int found_all = 0; *prot = 0; if(NULL == (fp = fopen("/proc/self/maps", "r"))) return XH_ERRNO_BADMAPS; while(fgets(line, sizeof(line), fp)) { if(NULL != pathname) if(NULL == strstr(line, pathname)) continue; if(sscanf(line, "%"PRIxPTR"-%"PRIxPTR" %4s ", &start, &end, perm) != 3) continue; if(perm[3] != 'p') continue; if(start_addr >= start && start_addr < end) { if(load0) { //first load segment if(perm[0] == 'r') *prot |= PROT_READ; if(perm[1] == 'w') *prot |= PROT_WRITE; if(perm[2] == 'x') *prot |= PROT_EXEC; load0 = 0; } else { //others if(perm[0] != 'r') *prot &= ~PROT_READ; if(perm[1] != 'w') *prot &= ~PROT_WRITE; if(perm[2] != 'x') *prot &= ~PROT_EXEC; } if(end_addr <= end) { found_all = 1; break; //finished } else { start_addr = end; //try to find the next load segment } } } fclose(fp); if(!found_all) return XH_ERRNO_SEGVERR; return 0; } int xh_util_get_addr_protect(uintptr_t addr, const char *pathname, unsigned int *prot) { return xh_util_get_mem_protect(addr, sizeof(addr), pathname, prot); } int xh_util_set_addr_protect(uintptr_t addr, unsigned int prot) { if(0 != mprotect((void *)PAGE_START(addr), PAGE_COVER(addr), (int)prot)) return 0 == errno ? XH_ERRNO_UNKNOWN : errno; return 0; } void xh_util_flush_instruction_cache(uintptr_t addr) { __builtin___clear_cache((void *)PAGE_START(addr), (void *)PAGE_END(addr)); } ================================================ FILE: libxhook/jni/xh_util.h ================================================ // Copyright (c) 2018-present, iQIYI, Inc. 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 2018-04-11. #ifndef XH_UTILS_H #define XH_UTILS_H 1 #ifdef __cplusplus extern "C" { #endif #if defined(__LP64__) #define XH_UTIL_FMT_LEN "16" #define XH_UTIL_FMT_X "llx" #else #define XH_UTIL_FMT_LEN "8" #define XH_UTIL_FMT_X "x" #endif #define XH_UTIL_FMT_FIXED_X XH_UTIL_FMT_LEN XH_UTIL_FMT_X #define XH_UTIL_FMT_FIXED_S XH_UTIL_FMT_LEN "s" int xh_util_get_mem_protect(uintptr_t addr, size_t len, const char *pathname, unsigned int *prot); int xh_util_get_addr_protect(uintptr_t addr, const char *pathname, unsigned int *prot); int xh_util_set_addr_protect(uintptr_t addr, unsigned int prot); void xh_util_flush_instruction_cache(uintptr_t addr); #ifdef __cplusplus } #endif #endif ================================================ FILE: libxhook/jni/xh_version.c ================================================ // Copyright (c) 2018-present, iQIYI, Inc. 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 2018-04-11. #include "xh_version.h" #define XH_VERSION_MAJOR 1 #define XH_VERSION_MINOR 2 #define XH_VERSION_EXTRA 0 #define XH_VERSION ((XH_VERSION_MAJOR << 16) | (XH_VERSION_MINOR << 8) | (XH_VERSION_EXTRA)) #define XH_VERSION_TO_STR_HELPER(x) #x #define XH_VERSION_TO_STR(x) XH_VERSION_TO_STR_HELPER(x) #define XH_VERSION_STR XH_VERSION_TO_STR(XH_VERSION_MAJOR) "." \ XH_VERSION_TO_STR(XH_VERSION_MINOR) "." \ XH_VERSION_TO_STR(XH_VERSION_EXTRA) #if defined(__arm__) #define XH_VERSION_ARCH "arm" #elif defined(__aarch64__) #define XH_VERSION_ARCH "aarch64" #elif defined(__i386__) #define XH_VERSION_ARCH "x86" #elif defined(__x86_64__) #define XH_VERSION_ARCH "x86_64" #else #define XH_VERSION_ARCH "unknown" #endif #define XH_VERSION_STR_FULL "libxhook "XH_VERSION_STR" ("XH_VERSION_ARCH")" unsigned int xh_version() { return XH_VERSION; } const char *xh_version_str() { return XH_VERSION_STR; } const char *xh_version_str_full() { return XH_VERSION_STR_FULL; } ================================================ FILE: libxhook/jni/xh_version.h ================================================ // Copyright (c) 2018-present, iQIYI, Inc. 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 2018-04-11. #ifndef XH_VERSION_H #define XH_VERSION_H 1 #ifdef __cplusplus extern "C" { #endif unsigned int xh_version(); const char *xh_version_str(); const char *xh_version_str_full(); #ifdef __cplusplus } #endif #endif ================================================ FILE: libxhook/jni/xhook.c ================================================ // Copyright (c) 2018-present, iQIYI, Inc. 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 2018-04-11. #include "xh_core.h" #include "xhook.h" int xhook_register(const char *pathname_regex_str, const char *symbol, void *new_func, void **old_func) { return xh_core_register(pathname_regex_str, symbol, new_func, old_func); } int xhook_ignore(const char *pathname_regex_str, const char *symbol) { return xh_core_ignore(pathname_regex_str, symbol); } int xhook_refresh(int async) { return xh_core_refresh(async); } void xhook_clear() { return xh_core_clear(); } void xhook_enable_debug(int flag) { return xh_core_enable_debug(flag); } void xhook_enable_sigsegv_protection(int flag) { return xh_core_enable_sigsegv_protection(flag); } ================================================ FILE: libxhook/jni/xhook.h ================================================ // Copyright (c) 2018-present, iQIYI, Inc. 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 2018-04-11. #ifndef XHOOK_H #define XHOOK_H 1 #ifdef __cplusplus extern "C" { #endif #define XHOOK_EXPORT __attribute__((visibility("default"))) int xhook_register(const char *pathname_regex_str, const char *symbol, void *new_func, void **old_func) XHOOK_EXPORT; int xhook_ignore(const char *pathname_regex_str, const char *symbol) XHOOK_EXPORT; int xhook_refresh(int async) XHOOK_EXPORT; void xhook_clear() XHOOK_EXPORT; void xhook_enable_debug(int flag) XHOOK_EXPORT; void xhook_enable_sigsegv_protection(int flag) XHOOK_EXPORT; #ifdef __cplusplus } #endif #endif ================================================ FILE: xhookwrapper/.gitignore ================================================ *.iml .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build /captures .externalNativeBuild ================================================ FILE: xhookwrapper/app/.gitignore ================================================ /build ================================================ FILE: xhookwrapper/app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 29 defaultConfig { applicationId "com.qiyi.xhookwrapper" minSdkVersion 14 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } sourceSets { main { jniLibs.srcDirs = ['libs'] } } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation project(':xhook') implementation project(':biz') implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' } ================================================ FILE: xhookwrapper/app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: xhookwrapper/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: xhookwrapper/app/src/main/java/com/qiyi/test/NativeHandler.java ================================================ package com.qiyi.test; /** * Created by caikelun on 18/01/2018. */ public class NativeHandler { private static final NativeHandler ourInstance = new NativeHandler(); public static NativeHandler getInstance() { return ourInstance; } private NativeHandler() { } public native void start(); } ================================================ FILE: xhookwrapper/app/src/main/java/com/qiyi/test/Test.java ================================================ package com.qiyi.test; /** * Created by caikelun on 18/01/2018. */ public class Test { private static final Test ourInstance = new Test(); public static Test getInstance() { return ourInstance; } private Test() { } public synchronized void init() { System.loadLibrary("test"); } public synchronized void start() { com.qiyi.test.NativeHandler.getInstance().start(); } } ================================================ FILE: xhookwrapper/app/src/main/java/com/qiyi/xhookwrapper/MainActivity.java ================================================ package com.qiyi.xhookwrapper; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //load xhook com.qiyi.xhook.XHook.getInstance().init(this.getApplicationContext()); if(!com.qiyi.xhook.XHook.getInstance().isInited()) { return; } //com.qiyi.xhook.XHook.getInstance().enableDebug(true); //default is false //com.qiyi.xhook.XHook.getInstance().enableSigSegvProtection(false); //default is true //load and run your biz lib (for register hook points) com.qiyi.biz.Biz.getInstance().init(); com.qiyi.biz.Biz.getInstance().start(); //xhook do refresh com.qiyi.xhook.XHook.getInstance().refresh(false); //load and run the target lib com.qiyi.test.Test.getInstance().init(); com.qiyi.test.Test.getInstance().start(); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } //xhook do refresh again com.qiyi.xhook.XHook.getInstance().refresh(false); //xhook do refresh again for some reason, //maybe called after some System.loadLibrary() and System.load() //* new Thread(new Runnable() { @Override public void run() { while(true) { com.qiyi.xhook.XHook.getInstance().refresh(true); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); //*/ } } ================================================ FILE: xhookwrapper/app/src/main/res/drawable/ic_launcher_background.xml ================================================ ================================================ FILE: xhookwrapper/app/src/main/res/drawable-v24/ic_launcher_foreground.xml ================================================ ================================================ FILE: xhookwrapper/app/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: xhookwrapper/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml ================================================ ================================================ FILE: xhookwrapper/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml ================================================ ================================================ FILE: xhookwrapper/app/src/main/res/values/colors.xml ================================================ #3F51B5 #303F9F #FF4081 ================================================ FILE: xhookwrapper/app/src/main/res/values/strings.xml ================================================ xhookwrapper ================================================ FILE: xhookwrapper/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: xhookwrapper/biz/.gitignore ================================================ /build ================================================ FILE: xhookwrapper/biz/build.gradle ================================================ apply plugin: 'com.android.library' android { compileSdkVersion 29 defaultConfig { minSdkVersion 14 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } sourceSets { main { jniLibs.srcDirs = ['libs'] } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' } ================================================ FILE: xhookwrapper/biz/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: xhookwrapper/biz/src/main/AndroidManifest.xml ================================================ ================================================ FILE: xhookwrapper/biz/src/main/java/com/qiyi/biz/Biz.java ================================================ package com.qiyi.biz; /** * Created by caikelun on 18/01/2018. */ public class Biz { private static final Biz ourInstance = new Biz(); public static Biz getInstance() { return ourInstance; } private Biz() { } public synchronized void init() { System.loadLibrary("biz"); } public synchronized void start() { com.qiyi.biz.NativeHandler.getInstance().start(); } } ================================================ FILE: xhookwrapper/biz/src/main/java/com/qiyi/biz/NativeHandler.java ================================================ package com.qiyi.biz; /** * Created by caikelun on 18/01/2018. */ public class NativeHandler { private static final NativeHandler ourInstance = new NativeHandler(); public static NativeHandler getInstance() { return ourInstance; } private NativeHandler() { } public native void start(); } ================================================ FILE: xhookwrapper/biz/src/main/res/values/strings.xml ================================================ biz ================================================ FILE: xhookwrapper/build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.3.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { google() jcenter() } } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: xhookwrapper/gradle/wrapper/gradle-wrapper.properties ================================================ #Tue Mar 26 19:19:54 CST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip ================================================ FILE: xhookwrapper/gradle.properties ================================================ # 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=-Xmx1536m # 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 ================================================ FILE: xhookwrapper/gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # 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 case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; esac # 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 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" ] ; 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 # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: xhookwrapper/gradlew.bat ================================================ @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 @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= set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @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 Windowz variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_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=%* goto execute :4NT_args @rem Get arguments from the 4NT Shell from JP Software 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 ================================================ FILE: xhookwrapper/settings.gradle ================================================ include ':app', ':xhook', ':biz' ================================================ FILE: xhookwrapper/xhook/.gitignore ================================================ /build ================================================ FILE: xhookwrapper/xhook/build.gradle ================================================ apply plugin: 'com.android.library' apply plugin: 'maven' def version = "1.2.0" android { compileSdkVersion 29 buildToolsVersion '29.0.2' defaultConfig { minSdkVersion 14 targetSdkVersion 29 } compileOptions { sourceCompatibility JavaVersion.VERSION_1_6 targetCompatibility JavaVersion.VERSION_1_6 } buildTypes { debug { minifyEnabled false useProguard false } release { minifyEnabled false useProguard false } } sourceSets { main { jniLibs.srcDirs = ['libs'] } } uploadArchives { repositories { mavenDeployer { repository(url: "") { authentication(userName: "", password: "") } pom.version = "$version" pom.artifactId = "xhook" pom.groupId = "com.qiyi.xhook" } } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) } ================================================ FILE: xhookwrapper/xhook/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: xhookwrapper/xhook/src/main/AndroidManifest.xml ================================================ ================================================ FILE: xhookwrapper/xhook/src/main/java/com/qiyi/xhook/NativeHandler.java ================================================ package com.qiyi.xhook; /** * Created by caikelun on 18/01/2018. */ public class NativeHandler { private static final NativeHandler ourInstance = new NativeHandler(); public static NativeHandler getInstance() { return ourInstance; } private NativeHandler() { } public native int refresh(boolean async); public native void clear(); public native void enableDebug(boolean flag); public native void enableSigSegvProtection(boolean flag); } ================================================ FILE: xhookwrapper/xhook/src/main/java/com/qiyi/xhook/XHook.java ================================================ package com.qiyi.xhook; import android.content.Context; import android.util.Log; /** * Created by caikelun on 18/01/2018. */ public class XHook { private static final XHook ourInstance = new XHook(); private static boolean inited = false; public static XHook getInstance() { return ourInstance; } private XHook() { } /** * Check if xhook has inited. * @return true if xhook has inited, false otherwise. */ public synchronized boolean isInited() { return inited; } /** * Init xhook. * @param ctx The application context. * @return true if successful, false otherwise. */ public synchronized boolean init(Context ctx) { if(inited) { return true; } try { System.loadLibrary("xhook"); inited = true; } catch (Throwable e) { try { System.load(ctx.getFilesDir().getParent() + "/lib/libxhook.so"); inited = true; } catch (Throwable ex) { ex.printStackTrace(); Log.e("xhook", "load libxhook.so failed"); } } return inited; } /** * Re-hook after System.loadLibrary() and System.load(). * @param async true if to refresh in async mode; otherwise, refresh in sync mode. * @return 0 if successful, false otherwise. */ public synchronized void refresh(boolean async) { if(!inited) { return; } try { com.qiyi.xhook.NativeHandler.getInstance().refresh(async); } catch (Throwable ex) { ex.printStackTrace(); Log.e("xhook", "xhook native refresh failed"); } } /** * Clear all cache. */ public synchronized void clear() { if(!inited) { return; } try { com.qiyi.xhook.NativeHandler.getInstance().clear(); } catch (Throwable ex) { ex.printStackTrace(); Log.e("xhook", "xhook native clear failed"); } } /** * Enable/disable the debug log to logcat. (disabled by default) * @param flag the bool flag. */ public synchronized void enableDebug(boolean flag) { if(!inited) { return; } try { com.qiyi.xhook.NativeHandler.getInstance().enableDebug(flag); } catch (Throwable ex) { ex.printStackTrace(); Log.e("xhook", "xhook native enableDebug failed"); } } /** * Enable/disable the segmentation fault protection. (enabled by default) * @param flag the bool flag. */ public synchronized void enableSigSegvProtection(boolean flag) { if (!inited) { return; } try { com.qiyi.xhook.NativeHandler.getInstance().enableSigSegvProtection(flag); } catch (Throwable ex) { ex.printStackTrace(); Log.e("xhook", "xhook native enableSigSegvProtection failed"); } } } ================================================ FILE: xhookwrapper/xhook/src/main/res/values/strings.xml ================================================ xhook