├── .gitignore ├── ART.mk ├── Android.mk ├── Dalvik.mk ├── MODULE_LICENSE_APACHE2 ├── NOTICE ├── README.md ├── app_main.cpp ├── app_main2.cpp ├── fd_utils-inl.h ├── libxposed_art.cpp ├── libxposed_common.cpp ├── libxposed_common.h ├── libxposed_dalvik.cpp ├── libxposed_dalvik.h ├── xposed.cpp ├── xposed.h ├── xposed_logcat.cpp ├── xposed_logcat.h ├── xposed_offsets.h ├── xposed_safemode.cpp ├── xposed_safemode.h ├── xposed_service.cpp ├── xposed_service.h └── xposed_shared.h /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | -------------------------------------------------------------------------------- /ART.mk: -------------------------------------------------------------------------------- 1 | ########################################################## 2 | # Library for ART-specific functions 3 | ########################################################## 4 | 5 | include $(CLEAR_VARS) 6 | 7 | include art/build/Android.common_build.mk 8 | $(eval $(call set-target-local-clang-vars)) 9 | $(eval $(call set-target-local-cflags-vars,ndebug)) 10 | 11 | ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 23))) 12 | LOCAL_C_INCLUDES += \ 13 | external/valgrind \ 14 | external/valgrind/include 15 | else 16 | include external/libcxx/libcxx.mk 17 | LOCAL_C_INCLUDES += \ 18 | external/valgrind/main \ 19 | external/valgrind/main/include 20 | endif 21 | 22 | LOCAL_SRC_FILES += \ 23 | libxposed_common.cpp \ 24 | libxposed_art.cpp 25 | 26 | LOCAL_C_INCLUDES += \ 27 | art/runtime \ 28 | external/gtest/include \ 29 | bionic/libc/private 30 | 31 | LOCAL_SHARED_LIBRARIES += \ 32 | libart \ 33 | liblog \ 34 | libcutils \ 35 | libandroidfw \ 36 | libnativehelper 37 | 38 | LOCAL_CFLAGS += \ 39 | -DPLATFORM_SDK_VERSION=$(PLATFORM_SDK_VERSION) \ 40 | -DXPOSED_WITH_SELINUX=1 41 | 42 | LOCAL_MODULE := libxposed_art 43 | LOCAL_MODULE_TAGS := optional 44 | LOCAL_STRIP_MODULE := keep_symbols 45 | LOCAL_MULTILIB := both 46 | 47 | # Always build both architectures (if applicable) 48 | ifeq ($(TARGET_IS_64_BIT),true) 49 | $(LOCAL_MODULE): $(LOCAL_MODULE)$(TARGET_2ND_ARCH_MODULE_SUFFIX) 50 | endif 51 | 52 | include $(BUILD_SHARED_LIBRARY) 53 | -------------------------------------------------------------------------------- /Android.mk: -------------------------------------------------------------------------------- 1 | ########################################################## 2 | # Customized app_process executable 3 | ########################################################## 4 | 5 | LOCAL_PATH:= $(call my-dir) 6 | include $(CLEAR_VARS) 7 | 8 | ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 21))) 9 | LOCAL_SRC_FILES := app_main2.cpp 10 | LOCAL_MULTILIB := both 11 | LOCAL_MODULE_STEM_32 := app_process32 12 | LOCAL_MODULE_STEM_64 := app_process64 13 | else 14 | LOCAL_SRC_FILES := app_main.cpp 15 | LOCAL_MODULE_STEM := app_process 16 | endif 17 | 18 | LOCAL_SRC_FILES += \ 19 | xposed.cpp \ 20 | xposed_logcat.cpp \ 21 | xposed_service.cpp \ 22 | xposed_safemode.cpp 23 | 24 | LOCAL_SHARED_LIBRARIES := \ 25 | libcutils \ 26 | libutils \ 27 | liblog \ 28 | libbinder \ 29 | libandroid_runtime \ 30 | libdl 31 | 32 | LOCAL_CFLAGS += -Wall -Werror -Wextra -Wunused 33 | LOCAL_CFLAGS += -DPLATFORM_SDK_VERSION=$(PLATFORM_SDK_VERSION) 34 | 35 | ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 17))) 36 | LOCAL_SHARED_LIBRARIES += libselinux 37 | LOCAL_CFLAGS += -DXPOSED_WITH_SELINUX=1 38 | endif 39 | 40 | ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 22))) 41 | LOCAL_WHOLE_STATIC_LIBRARIES := libsigchain 42 | LOCAL_LDFLAGS := -Wl,--version-script,art/sigchainlib/version-script.txt -Wl,--export-dynamic 43 | endif 44 | 45 | LOCAL_MODULE := app_process 46 | LOCAL_MODULE_TAGS := optional 47 | LOCAL_STRIP_MODULE := keep_symbols 48 | 49 | # Always build both architectures (if applicable) 50 | ifeq ($(TARGET_IS_64_BIT),true) 51 | $(LOCAL_MODULE): $(LOCAL_MODULE)$(TARGET_2ND_ARCH_MODULE_SUFFIX) 52 | endif 53 | 54 | include $(BUILD_EXECUTABLE) 55 | 56 | ########################################################## 57 | # Library for Dalvik-/ART-specific functions 58 | ########################################################## 59 | ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 21))) 60 | include frameworks/base/cmds/app_process/ART.mk 61 | else 62 | include frameworks/base/cmds/app_process/Dalvik.mk 63 | endif 64 | -------------------------------------------------------------------------------- /Dalvik.mk: -------------------------------------------------------------------------------- 1 | ########################################################## 2 | # Library for Dalvik-specific functions 3 | ########################################################## 4 | 5 | include $(CLEAR_VARS) 6 | 7 | LOCAL_SRC_FILES := \ 8 | libxposed_common.cpp \ 9 | libxposed_dalvik.cpp 10 | 11 | LOCAL_C_INCLUDES += \ 12 | dalvik \ 13 | dalvik/vm \ 14 | external/stlport/stlport \ 15 | bionic \ 16 | bionic/libstdc++/include \ 17 | libcore/include 18 | 19 | LOCAL_SHARED_LIBRARIES := \ 20 | libdvm \ 21 | liblog \ 22 | libdl \ 23 | libnativehelper 24 | 25 | ifeq ($(PLATFORM_SDK_VERSION),15) 26 | LOCAL_SHARED_LIBRARIES += libutils 27 | else 28 | LOCAL_SHARED_LIBRARIES += libandroidfw 29 | endif 30 | 31 | LOCAL_CFLAGS := -Wall -Werror -Wextra -Wunused -Wno-unused-parameter 32 | LOCAL_CFLAGS += -DPLATFORM_SDK_VERSION=$(PLATFORM_SDK_VERSION) 33 | 34 | ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 17))) 35 | LOCAL_CFLAGS += -DXPOSED_WITH_SELINUX=1 36 | endif 37 | 38 | LOCAL_MODULE := libxposed_dalvik 39 | LOCAL_MODULE_TAGS := optional 40 | LOCAL_STRIP_MODULE := keep_symbols 41 | 42 | include $(BUILD_SHARED_LIBRARY) 43 | -------------------------------------------------------------------------------- /MODULE_LICENSE_APACHE2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abforce/xposed_app_process/92f9820b22155c1f41354c6dceaf2c4e911449ae/MODULE_LICENSE_APACHE2 -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | 2 | Original work Copyright (c) 2005-2008, The Android Open Source Project 3 | Modified work Copyright (c) 2013, rovo89 and Tungstwenty 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | 15 | Apache License 16 | Version 2.0, January 2004 17 | http://www.apache.org/licenses/ 18 | 19 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 20 | 21 | 1. Definitions. 22 | 23 | "License" shall mean the terms and conditions for use, reproduction, 24 | and distribution as defined by Sections 1 through 9 of this document. 25 | 26 | "Licensor" shall mean the copyright owner or entity authorized by 27 | the copyright owner that is granting the License. 28 | 29 | "Legal Entity" shall mean the union of the acting entity and all 30 | other entities that control, are controlled by, or are under common 31 | control with that entity. For the purposes of this definition, 32 | "control" means (i) the power, direct or indirect, to cause the 33 | direction or management of such entity, whether by contract or 34 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 35 | outstanding shares, or (iii) beneficial ownership of such entity. 36 | 37 | "You" (or "Your") shall mean an individual or Legal Entity 38 | exercising permissions granted by this License. 39 | 40 | "Source" form shall mean the preferred form for making modifications, 41 | including but not limited to software source code, documentation 42 | source, and configuration files. 43 | 44 | "Object" form shall mean any form resulting from mechanical 45 | transformation or translation of a Source form, including but 46 | not limited to compiled object code, generated documentation, 47 | and conversions to other media types. 48 | 49 | "Work" shall mean the work of authorship, whether in Source or 50 | Object form, made available under the License, as indicated by a 51 | copyright notice that is included in or attached to the work 52 | (an example is provided in the Appendix below). 53 | 54 | "Derivative Works" shall mean any work, whether in Source or Object 55 | form, that is based on (or derived from) the Work and for which the 56 | editorial revisions, annotations, elaborations, or other modifications 57 | represent, as a whole, an original work of authorship. For the purposes 58 | of this License, Derivative Works shall not include works that remain 59 | separable from, or merely link (or bind by name) to the interfaces of, 60 | the Work and Derivative Works thereof. 61 | 62 | "Contribution" shall mean any work of authorship, including 63 | the original version of the Work and any modifications or additions 64 | to that Work or Derivative Works thereof, that is intentionally 65 | submitted to Licensor for inclusion in the Work by the copyright owner 66 | or by an individual or Legal Entity authorized to submit on behalf of 67 | the copyright owner. For the purposes of this definition, "submitted" 68 | means any form of electronic, verbal, or written communication sent 69 | to the Licensor or its representatives, including but not limited to 70 | communication on electronic mailing lists, source code control systems, 71 | and issue tracking systems that are managed by, or on behalf of, the 72 | Licensor for the purpose of discussing and improving the Work, but 73 | excluding communication that is conspicuously marked or otherwise 74 | designated in writing by the copyright owner as "Not a Contribution." 75 | 76 | "Contributor" shall mean Licensor and any individual or Legal Entity 77 | on behalf of whom a Contribution has been received by Licensor and 78 | subsequently incorporated within the Work. 79 | 80 | 2. Grant of Copyright License. Subject to the terms and conditions of 81 | this License, each Contributor hereby grants to You a perpetual, 82 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 83 | copyright license to reproduce, prepare Derivative Works of, 84 | publicly display, publicly perform, sublicense, and distribute the 85 | Work and such Derivative Works in Source or Object form. 86 | 87 | 3. Grant of Patent License. Subject to the terms and conditions of 88 | this License, each Contributor hereby grants to You a perpetual, 89 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 90 | (except as stated in this section) patent license to make, have made, 91 | use, offer to sell, sell, import, and otherwise transfer the Work, 92 | where such license applies only to those patent claims licensable 93 | by such Contributor that are necessarily infringed by their 94 | Contribution(s) alone or by combination of their Contribution(s) 95 | with the Work to which such Contribution(s) was submitted. If You 96 | institute patent litigation against any entity (including a 97 | cross-claim or counterclaim in a lawsuit) alleging that the Work 98 | or a Contribution incorporated within the Work constitutes direct 99 | or contributory patent infringement, then any patent licenses 100 | granted to You under this License for that Work shall terminate 101 | as of the date such litigation is filed. 102 | 103 | 4. Redistribution. You may reproduce and distribute copies of the 104 | Work or Derivative Works thereof in any medium, with or without 105 | modifications, and in Source or Object form, provided that You 106 | meet the following conditions: 107 | 108 | (a) You must give any other recipients of the Work or 109 | Derivative Works a copy of this License; and 110 | 111 | (b) You must cause any modified files to carry prominent notices 112 | stating that You changed the files; and 113 | 114 | (c) You must retain, in the Source form of any Derivative Works 115 | that You distribute, all copyright, patent, trademark, and 116 | attribution notices from the Source form of the Work, 117 | excluding those notices that do not pertain to any part of 118 | the Derivative Works; and 119 | 120 | (d) If the Work includes a "NOTICE" text file as part of its 121 | distribution, then any Derivative Works that You distribute must 122 | include a readable copy of the attribution notices contained 123 | within such NOTICE file, excluding those notices that do not 124 | pertain to any part of the Derivative Works, in at least one 125 | of the following places: within a NOTICE text file distributed 126 | as part of the Derivative Works; within the Source form or 127 | documentation, if provided along with the Derivative Works; or, 128 | within a display generated by the Derivative Works, if and 129 | wherever such third-party notices normally appear. The contents 130 | of the NOTICE file are for informational purposes only and 131 | do not modify the License. You may add Your own attribution 132 | notices within Derivative Works that You distribute, alongside 133 | or as an addendum to the NOTICE text from the Work, provided 134 | that such additional attribution notices cannot be construed 135 | as modifying the License. 136 | 137 | You may add Your own copyright statement to Your modifications and 138 | may provide additional or different license terms and conditions 139 | for use, reproduction, or distribution of Your modifications, or 140 | for any such Derivative Works as a whole, provided Your use, 141 | reproduction, and distribution of the Work otherwise complies with 142 | the conditions stated in this License. 143 | 144 | 5. Submission of Contributions. Unless You explicitly state otherwise, 145 | any Contribution intentionally submitted for inclusion in the Work 146 | by You to the Licensor shall be under the terms and conditions of 147 | this License, without any additional terms or conditions. 148 | Notwithstanding the above, nothing herein shall supersede or modify 149 | the terms of any separate license agreement you may have executed 150 | with Licensor regarding such Contributions. 151 | 152 | 6. Trademarks. This License does not grant permission to use the trade 153 | names, trademarks, service marks, or product names of the Licensor, 154 | except as required for reasonable and customary use in describing the 155 | origin of the Work and reproducing the content of the NOTICE file. 156 | 157 | 7. Disclaimer of Warranty. Unless required by applicable law or 158 | agreed to in writing, Licensor provides the Work (and each 159 | Contributor provides its Contributions) on an "AS IS" BASIS, 160 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 161 | implied, including, without limitation, any warranties or conditions 162 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 163 | PARTICULAR PURPOSE. You are solely responsible for determining the 164 | appropriateness of using or redistributing the Work and assume any 165 | risks associated with Your exercise of permissions under this License. 166 | 167 | 8. Limitation of Liability. In no event and under no legal theory, 168 | whether in tort (including negligence), contract, or otherwise, 169 | unless required by applicable law (such as deliberate and grossly 170 | negligent acts) or agreed to in writing, shall any Contributor be 171 | liable to You for damages, including any direct, indirect, special, 172 | incidental, or consequential damages of any character arising as a 173 | result of this License or out of the use or inability to use the 174 | Work (including but not limited to damages for loss of goodwill, 175 | work stoppage, computer failure or malfunction, or any and all 176 | other commercial damages or losses), even if such Contributor 177 | has been advised of the possibility of such damages. 178 | 179 | 9. Accepting Warranty or Additional Liability. While redistributing 180 | the Work or Derivative Works thereof, You may choose to offer, 181 | and charge a fee for, acceptance of support, warranty, indemnity, 182 | or other liability obligations and/or rights consistent with this 183 | License. However, in accepting such obligations, You may act only 184 | on Your own behalf and on Your sole responsibility, not on behalf 185 | of any other Contributor, and only if You agree to indemnify, 186 | defend, and hold each Contributor harmless for any liability 187 | incurred by, or claims asserted against, such Contributor by reason 188 | of your accepting any such warranty or additional liability. 189 | 190 | END OF TERMS AND CONDITIONS 191 | 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This copy is a fork of rovo89's app_process. All credit goes to @rovo89. 2 | 3 | What I've done on this copy: 4 | - Change Android.mk to build `app_process32` and `app_process64` instead of those with `_xpsoed` extension. 5 | - Compatibility with Android N. 6 | 7 | Please see [this repository](https://github.com/abforce/xposed_art_n)'s README.md for more information. 8 | 9 | License for my contribution is MIT. 10 | -------------------------------------------------------------------------------- /app_main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Main entry of app process. 3 | * 4 | * Starts the interpreted runtime, then starts up the application. 5 | * 6 | */ 7 | 8 | #define LOG_TAG "appproc" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #if PLATFORM_SDK_VERSION >= 16 19 | #include 20 | #endif 21 | 22 | #include 23 | #include 24 | 25 | #include "xposed.h" 26 | #include 27 | 28 | static bool isXposedLoaded = false; 29 | 30 | 31 | namespace android { 32 | 33 | void app_usage() 34 | { 35 | fprintf(stderr, 36 | "Usage: app_process [java-options] cmd-dir start-class-name [options]\n"); 37 | fprintf(stderr, " with Xposed support\n"); 38 | } 39 | 40 | void _atrace_set_tracing_enabled(bool enabled) 41 | { 42 | if (xposed::getSdkVersion() < 18) 43 | return; 44 | 45 | dlerror(); // Clear existing errors 46 | 47 | void (*PTR_atrace_set_tracing_enabled)(bool); 48 | *(void **) (&PTR_atrace_set_tracing_enabled) = dlsym(RTLD_DEFAULT, "atrace_set_tracing_enabled"); 49 | 50 | const char *error; 51 | if ((error = dlerror()) != NULL) { 52 | ALOGE("Could not find address for function atrace_set_tracing_enabled: %s", error); 53 | } else { 54 | PTR_atrace_set_tracing_enabled(enabled); 55 | } 56 | } 57 | 58 | class AppRuntime : public AndroidRuntime 59 | { 60 | public: 61 | AppRuntime() 62 | : mParentDir(NULL) 63 | , mClassName(NULL) 64 | , mClass(NULL) 65 | , mArgC(0) 66 | , mArgV(NULL) 67 | { 68 | } 69 | 70 | #if 0 71 | // this appears to be unused 72 | const char* getParentDir() const 73 | { 74 | return mParentDir; 75 | } 76 | #endif 77 | 78 | const char* getClassName() const 79 | { 80 | return mClassName; 81 | } 82 | 83 | virtual void onVmCreated(JNIEnv* env) 84 | { 85 | if (isXposedLoaded) 86 | xposed::onVmCreated(env); 87 | 88 | if (mClassName == NULL) { 89 | return; // Zygote. Nothing to do here. 90 | } 91 | 92 | /* 93 | * This is a little awkward because the JNI FindClass call uses the 94 | * class loader associated with the native method we're executing in. 95 | * If called in onStarted (from RuntimeInit.finishInit because we're 96 | * launching "am", for example), FindClass would see that we're calling 97 | * from a boot class' native method, and so wouldn't look for the class 98 | * we're trying to look up in CLASSPATH. Unfortunately it needs to, 99 | * because the "am" classes are not boot classes. 100 | * 101 | * The easiest fix is to call FindClass here, early on before we start 102 | * executing boot class Java code and thereby deny ourselves access to 103 | * non-boot classes. 104 | */ 105 | char* slashClassName = toSlashClassName(mClassName); 106 | mClass = env->FindClass(slashClassName); 107 | if (mClass == NULL) { 108 | ALOGE("ERROR: could not find class '%s'\n", mClassName); 109 | } 110 | free(slashClassName); 111 | 112 | mClass = reinterpret_cast(env->NewGlobalRef(mClass)); 113 | } 114 | 115 | virtual void onStarted() 116 | { 117 | sp proc = ProcessState::self(); 118 | ALOGV("App process: starting thread pool.\n"); 119 | proc->startThreadPool(); 120 | 121 | AndroidRuntime* ar = AndroidRuntime::getRuntime(); 122 | ar->callMain(mClassName, mClass, mArgC, mArgV); 123 | 124 | IPCThreadState::self()->stopProcess(); 125 | } 126 | 127 | virtual void onZygoteInit() 128 | { 129 | // Re-enable tracing now that we're no longer in Zygote. 130 | _atrace_set_tracing_enabled(true); 131 | 132 | sp proc = ProcessState::self(); 133 | ALOGV("App process: starting thread pool.\n"); 134 | proc->startThreadPool(); 135 | } 136 | 137 | virtual void onExit(int code) 138 | { 139 | if (mClassName == NULL) { 140 | // if zygote 141 | IPCThreadState::self()->stopProcess(); 142 | } 143 | 144 | AndroidRuntime::onExit(code); 145 | } 146 | 147 | 148 | const char* mParentDir; 149 | const char* mClassName; 150 | jclass mClass; 151 | int mArgC; 152 | const char* const* mArgV; 153 | }; 154 | 155 | } 156 | 157 | using namespace android; 158 | 159 | /* 160 | * sets argv0 to as much of newArgv0 as will fit 161 | */ 162 | static void setArgv0(const char *argv0, const char *newArgv0) 163 | { 164 | strlcpy(const_cast(argv0), newArgv0, strlen(argv0)); 165 | } 166 | 167 | int main(int argc, char* const argv[]) 168 | { 169 | if (xposed::handleOptions(argc, argv)) 170 | return 0; 171 | 172 | #if PLATFORM_SDK_VERSION >= 16 173 | #ifdef __arm__ 174 | /* 175 | * b/7188322 - Temporarily revert to the compat memory layout 176 | * to avoid breaking third party apps. 177 | * 178 | * THIS WILL GO AWAY IN A FUTURE ANDROID RELEASE. 179 | * 180 | * http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=7dbaa466 181 | * changes the kernel mapping from bottom up to top-down. 182 | * This breaks some programs which improperly embed 183 | * an out of date copy of Android's linker. 184 | */ 185 | char value[PROPERTY_VALUE_MAX]; 186 | property_get("ro.kernel.qemu", value, ""); 187 | bool is_qemu = (strcmp(value, "1") == 0); 188 | if ((getenv("NO_ADDR_COMPAT_LAYOUT_FIXUP") == NULL) && !is_qemu) { 189 | int current = personality(0xFFFFFFFF); 190 | if ((current & ADDR_COMPAT_LAYOUT) == 0) { 191 | personality(current | ADDR_COMPAT_LAYOUT); 192 | setenv("NO_ADDR_COMPAT_LAYOUT_FIXUP", "1", 1); 193 | execv("/system/bin/app_process", argv); 194 | return -1; 195 | } 196 | } 197 | unsetenv("NO_ADDR_COMPAT_LAYOUT_FIXUP"); 198 | #endif 199 | #endif 200 | 201 | // These are global variables in ProcessState.cpp 202 | mArgC = argc; 203 | mArgV = argv; 204 | 205 | mArgLen = 0; 206 | for (int i=0; i 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include // for AID_SYSTEM 25 | 26 | #include "xposed.h" 27 | #include 28 | 29 | static bool isXposedLoaded = false; 30 | 31 | 32 | namespace android { 33 | 34 | static void app_usage() 35 | { 36 | fprintf(stderr, 37 | "Usage: app_process [java-options] cmd-dir start-class-name [options]\n"); 38 | fprintf(stderr, " with Xposed support\n"); 39 | } 40 | 41 | class AppRuntime : public AndroidRuntime 42 | { 43 | public: 44 | AppRuntime(char* argBlockStart, const size_t argBlockLength) 45 | : AndroidRuntime(argBlockStart, argBlockLength) 46 | , mClass(NULL) 47 | { 48 | } 49 | 50 | void setClassNameAndArgs(const String8& className, int argc, char * const *argv) { 51 | mClassName = className; 52 | for (int i = 0; i < argc; ++i) { 53 | mArgs.add(String8(argv[i])); 54 | } 55 | } 56 | 57 | virtual void onVmCreated(JNIEnv* env) 58 | { 59 | if (isXposedLoaded) 60 | xposed::onVmCreated(env); 61 | 62 | if (mClassName.isEmpty()) { 63 | return; // Zygote. Nothing to do here. 64 | } 65 | 66 | /* 67 | * This is a little awkward because the JNI FindClass call uses the 68 | * class loader associated with the native method we're executing in. 69 | * If called in onStarted (from RuntimeInit.finishInit because we're 70 | * launching "am", for example), FindClass would see that we're calling 71 | * from a boot class' native method, and so wouldn't look for the class 72 | * we're trying to look up in CLASSPATH. Unfortunately it needs to, 73 | * because the "am" classes are not boot classes. 74 | * 75 | * The easiest fix is to call FindClass here, early on before we start 76 | * executing boot class Java code and thereby deny ourselves access to 77 | * non-boot classes. 78 | */ 79 | char* slashClassName = toSlashClassName(mClassName.string()); 80 | mClass = env->FindClass(slashClassName); 81 | if (mClass == NULL) { 82 | ALOGE("ERROR: could not find class '%s'\n", mClassName.string()); 83 | env->ExceptionDescribe(); 84 | } 85 | free(slashClassName); 86 | 87 | mClass = reinterpret_cast(env->NewGlobalRef(mClass)); 88 | } 89 | 90 | virtual void onStarted() 91 | { 92 | sp proc = ProcessState::self(); 93 | ALOGV("App process: starting thread pool.\n"); 94 | proc->startThreadPool(); 95 | 96 | AndroidRuntime* ar = AndroidRuntime::getRuntime(); 97 | ar->callMain(mClassName, mClass, mArgs); 98 | 99 | IPCThreadState::self()->stopProcess(); 100 | } 101 | 102 | virtual void onZygoteInit() 103 | { 104 | #if PLATFORM_SDK_VERSION <= 22 105 | // Re-enable tracing now that we're no longer in Zygote. 106 | atrace_set_tracing_enabled(true); 107 | #endif 108 | 109 | sp proc = ProcessState::self(); 110 | ALOGV("App process: starting thread pool.\n"); 111 | proc->startThreadPool(); 112 | } 113 | 114 | virtual void onExit(int code) 115 | { 116 | if (mClassName.isEmpty()) { 117 | // if zygote 118 | IPCThreadState::self()->stopProcess(); 119 | } 120 | 121 | AndroidRuntime::onExit(code); 122 | } 123 | 124 | 125 | String8 mClassName; 126 | Vector mArgs; 127 | jclass mClass; 128 | }; 129 | 130 | } 131 | 132 | using namespace android; 133 | 134 | static size_t computeArgBlockSize(int argc, char* const argv[]) { 135 | // TODO: This assumes that all arguments are allocated in 136 | // contiguous memory. There isn't any documented guarantee 137 | // that this is the case, but this is how the kernel does it 138 | // (see fs/exec.c). 139 | // 140 | // Also note that this is a constant for "normal" android apps. 141 | // Since they're forked from zygote, the size of their command line 142 | // is the size of the zygote command line. 143 | // 144 | // We change the process name of the process by over-writing 145 | // the start of the argument block (argv[0]) with the new name of 146 | // the process, so we'd mysteriously start getting truncated process 147 | // names if the zygote command line decreases in size. 148 | uintptr_t start = reinterpret_cast(argv[0]); 149 | uintptr_t end = reinterpret_cast(argv[argc - 1]); 150 | end += strlen(argv[argc - 1]) + 1; 151 | return (end - start); 152 | } 153 | 154 | static void maybeCreateDalvikCache() { 155 | #if defined(__aarch64__) 156 | static const char kInstructionSet[] = "arm64"; 157 | #elif defined(__x86_64__) 158 | static const char kInstructionSet[] = "x86_64"; 159 | #elif defined(__arm__) 160 | static const char kInstructionSet[] = "arm"; 161 | #elif defined(__i386__) 162 | static const char kInstructionSet[] = "x86"; 163 | #elif defined (__mips__) && !defined(__LP64__) 164 | static const char kInstructionSet[] = "mips"; 165 | #elif defined (__mips__) && defined(__LP64__) 166 | static const char kInstructionSet[] = "mips64"; 167 | #else 168 | #error "Unknown instruction set" 169 | #endif 170 | const char* androidRoot = getenv("ANDROID_DATA"); 171 | LOG_ALWAYS_FATAL_IF(androidRoot == NULL, "ANDROID_DATA environment variable unset"); 172 | 173 | char dalvikCacheDir[PATH_MAX]; 174 | const int numChars = snprintf(dalvikCacheDir, PATH_MAX, 175 | "%s/dalvik-cache/%s", androidRoot, kInstructionSet); 176 | LOG_ALWAYS_FATAL_IF((numChars >= PATH_MAX || numChars < 0), 177 | "Error constructing dalvik cache : %s", strerror(errno)); 178 | 179 | int result = mkdir(dalvikCacheDir, 0711); 180 | LOG_ALWAYS_FATAL_IF((result < 0 && errno != EEXIST), 181 | "Error creating cache dir %s : %s", dalvikCacheDir, strerror(errno)); 182 | 183 | // We always perform these steps because the directory might 184 | // already exist, with wider permissions and a different owner 185 | // than we'd like. 186 | result = chown(dalvikCacheDir, AID_ROOT, AID_ROOT); 187 | LOG_ALWAYS_FATAL_IF((result < 0), "Error changing dalvik-cache ownership : %s", strerror(errno)); 188 | 189 | result = chmod(dalvikCacheDir, 0711); 190 | LOG_ALWAYS_FATAL_IF((result < 0), 191 | "Error changing dalvik-cache permissions : %s", strerror(errno)); 192 | } 193 | 194 | #if defined(__LP64__) 195 | static const char ABI_LIST_PROPERTY[] = "ro.product.cpu.abilist64"; 196 | static const char ZYGOTE_NICE_NAME[] = "zygote64"; 197 | #else 198 | static const char ABI_LIST_PROPERTY[] = "ro.product.cpu.abilist32"; 199 | static const char ZYGOTE_NICE_NAME[] = "zygote"; 200 | #endif 201 | 202 | static void runtimeStart(AppRuntime& runtime, const char *classname, const Vector& options, bool zygote) 203 | { 204 | #if PLATFORM_SDK_VERSION >= 23 205 | runtime.start(classname, options, zygote); 206 | #else 207 | // try newer variant (5.1.1_r19 and later) first 208 | void (*ptr1)(AppRuntime&, const char*, const Vector&, bool); 209 | *(void **) (&ptr1) = dlsym(RTLD_DEFAULT, "_ZN7android14AndroidRuntime5startEPKcRKNS_6VectorINS_7String8EEEb"); 210 | 211 | if (ptr1 != NULL) { 212 | ptr1(runtime, classname, options, zygote); 213 | return; 214 | } 215 | 216 | // fall back to older variant 217 | void (*ptr2)(AppRuntime&, const char*, const Vector&); 218 | *(void **) (&ptr2) = dlsym(RTLD_DEFAULT, "_ZN7android14AndroidRuntime5startEPKcRKNS_6VectorINS_7String8EEE"); 219 | 220 | if (ptr2 != NULL) { 221 | ptr2(runtime, classname, options); 222 | return; 223 | } 224 | 225 | // should not happen 226 | LOG_ALWAYS_FATAL("app_process: could not locate AndroidRuntime::start() method."); 227 | #endif 228 | } 229 | 230 | int main(int argc, char* const argv[]) 231 | { 232 | if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { 233 | // Older kernels don't understand PR_SET_NO_NEW_PRIVS and return 234 | // EINVAL. Don't die on such kernels. 235 | if (errno != EINVAL) { 236 | LOG_ALWAYS_FATAL("PR_SET_NO_NEW_PRIVS failed: %s", strerror(errno)); 237 | return 12; 238 | } 239 | } 240 | 241 | if (xposed::handleOptions(argc, argv)) { 242 | return 0; 243 | } 244 | 245 | AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv)); 246 | // Process command line arguments 247 | // ignore argv[0] 248 | argc--; 249 | argv++; 250 | 251 | // Everything up to '--' or first non '-' arg goes to the vm. 252 | // 253 | // The first argument after the VM args is the "parent dir", which 254 | // is currently unused. 255 | // 256 | // After the parent dir, we expect one or more the following internal 257 | // arguments : 258 | // 259 | // --zygote : Start in zygote mode 260 | // --start-system-server : Start the system server. 261 | // --application : Start in application (stand alone, non zygote) mode. 262 | // --nice-name : The nice name for this process. 263 | // 264 | // For non zygote starts, these arguments will be followed by 265 | // the main class name. All remaining arguments are passed to 266 | // the main method of this class. 267 | // 268 | // For zygote starts, all remaining arguments are passed to the zygote. 269 | // main function. 270 | // 271 | // Note that we must copy argument string values since we will rewrite the 272 | // entire argument block when we apply the nice name to argv0. 273 | 274 | int i; 275 | for (i = 0; i < argc; i++) { 276 | if (argv[i][0] != '-') { 277 | break; 278 | } 279 | if (argv[i][1] == '-' && argv[i][2] == 0) { 280 | ++i; // Skip --. 281 | break; 282 | } 283 | runtime.addOption(strdup(argv[i])); 284 | } 285 | 286 | // Parse runtime arguments. Stop at first unrecognized option. 287 | bool zygote = false; 288 | bool startSystemServer = false; 289 | bool application = false; 290 | String8 niceName; 291 | String8 className; 292 | 293 | ++i; // Skip unused "parent dir" argument. 294 | while (i < argc) { 295 | const char* arg = argv[i++]; 296 | if (strcmp(arg, "--zygote") == 0) { 297 | zygote = true; 298 | niceName = ZYGOTE_NICE_NAME; 299 | } else if (strcmp(arg, "--start-system-server") == 0) { 300 | startSystemServer = true; 301 | } else if (strcmp(arg, "--application") == 0) { 302 | application = true; 303 | } else if (strncmp(arg, "--nice-name=", 12) == 0) { 304 | niceName.setTo(arg + 12); 305 | } else if (strncmp(arg, "--", 2) != 0) { 306 | className.setTo(arg); 307 | break; 308 | } else { 309 | --i; 310 | break; 311 | } 312 | } 313 | 314 | Vector args; 315 | if (!className.isEmpty()) { 316 | // We're not in zygote mode, the only argument we need to pass 317 | // to RuntimeInit is the application argument. 318 | // 319 | // The Remainder of args get passed to startup class main(). Make 320 | // copies of them before we overwrite them with the process name. 321 | args.add(application ? String8("application") : String8("tool")); 322 | runtime.setClassNameAndArgs(className, argc - i, argv + i); 323 | } else { 324 | // We're in zygote mode. 325 | maybeCreateDalvikCache(); 326 | 327 | if (startSystemServer) { 328 | args.add(String8("start-system-server")); 329 | } 330 | 331 | char prop[PROP_VALUE_MAX]; 332 | if (property_get(ABI_LIST_PROPERTY, prop, NULL) == 0) { 333 | LOG_ALWAYS_FATAL("app_process: Unable to determine ABI list from property %s.", 334 | ABI_LIST_PROPERTY); 335 | return 11; 336 | } 337 | 338 | String8 abiFlag("--abi-list="); 339 | abiFlag.append(prop); 340 | args.add(abiFlag); 341 | 342 | // In zygote mode, pass all remaining arguments to the zygote 343 | // main() method. 344 | for (; i < argc; ++i) { 345 | args.add(String8(argv[i])); 346 | } 347 | } 348 | 349 | if (!niceName.isEmpty()) { 350 | runtime.setArgv0(niceName.string()); 351 | set_process_name(niceName.string()); 352 | } 353 | 354 | if (zygote) { 355 | isXposedLoaded = xposed::initialize(true, startSystemServer, NULL, argc, argv); 356 | runtimeStart(runtime, isXposedLoaded ? XPOSED_CLASS_DOTS_ZYGOTE : "com.android.internal.os.ZygoteInit", args, zygote); 357 | } else if (className) { 358 | isXposedLoaded = xposed::initialize(false, false, className, argc, argv); 359 | runtimeStart(runtime, isXposedLoaded ? XPOSED_CLASS_DOTS_TOOLS : "com.android.internal.os.RuntimeInit", args, zygote); 360 | } else { 361 | fprintf(stderr, "Error: no class name or --zygote supplied.\n"); 362 | app_usage(); 363 | LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied."); 364 | return 10; 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /fd_utils-inl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | #include "JNIHelp.h" 36 | #include "ScopedPrimitiveArray.h" 37 | 38 | static const char* kPathPrefixWhitelist[] = { 39 | "/data/app/", 40 | "/data/app-private/", 41 | "/system/app/", 42 | "/system/priv-app/", 43 | "/vendor/app/", 44 | "/vendor/priv-app/", 45 | }; 46 | 47 | static const char* kFdPath = "/proc/self/fd"; 48 | 49 | // Keeps track of all relevant information (flags, offset etc.) of an 50 | // open zygote file descriptor. 51 | class FileDescriptorInfo { 52 | public: 53 | // Create a FileDescriptorInfo for a given file descriptor. Returns 54 | // |NULL| if an error occurred. 55 | static FileDescriptorInfo* createFromFd(int fd) { 56 | struct stat f_stat; 57 | // This should never happen; the zygote should always have the right set 58 | // of permissions required to stat all its open files. 59 | if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) { 60 | ALOGE("Unable to stat fd %d : %s", fd, strerror(errno)); 61 | return NULL; 62 | } 63 | 64 | if (S_ISSOCK(f_stat.st_mode)) { 65 | std::string socket_name; 66 | if (!GetSocketName(fd, &socket_name)) { 67 | return NULL; 68 | } 69 | 70 | if (!IsWhitelisted(socket_name)) { 71 | //ALOGE("Socket name not whitelisted : %s (fd=%d)", socket_name.c_str(), fd); 72 | return NULL; 73 | } 74 | 75 | return new FileDescriptorInfo(fd); 76 | } 77 | 78 | std::string file_path; 79 | if (!Readlink(fd, &file_path)) { 80 | return NULL; 81 | } 82 | 83 | if (!IsWhitelisted(file_path)) { 84 | //ALOGE("Not whitelisted : %s", file_path.c_str()); 85 | return NULL; 86 | } 87 | 88 | // We only handle whitelisted regular files and character devices. Whitelisted 89 | // character devices must provide a guarantee of sensible behaviour when 90 | // reopened. 91 | // 92 | // S_ISDIR : Not supported. (We could if we wanted to, but it's unused). 93 | // S_ISLINK : Not supported. 94 | // S_ISBLK : Not supported. 95 | // S_ISFIFO : Not supported. Note that the zygote uses pipes to communicate 96 | // with the child process across forks but those should have been closed 97 | // before we got to this point. 98 | if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) { 99 | ALOGE("Unsupported st_mode %d", f_stat.st_mode); 100 | return NULL; 101 | } 102 | 103 | // File descriptor flags : currently on FD_CLOEXEC. We can set these 104 | // using F_SETFD - we're single threaded at this point of execution so 105 | // there won't be any races. 106 | const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD)); 107 | if (fd_flags == -1) { 108 | ALOGE("Failed fcntl(%d, F_GETFD) : %s", fd, strerror(errno)); 109 | return NULL; 110 | } 111 | 112 | // File status flags : 113 | // - File access mode : (O_RDONLY, O_WRONLY...) we'll pass these through 114 | // to the open() call. 115 | // 116 | // - File creation flags : (O_CREAT, O_EXCL...) - there's not much we can 117 | // do about these, since the file has already been created. We shall ignore 118 | // them here. 119 | // 120 | // - Other flags : We'll have to set these via F_SETFL. On linux, F_SETFL 121 | // can only set O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, and O_NONBLOCK. 122 | // In particular, it can't set O_SYNC and O_DSYNC. We'll have to test for 123 | // their presence and pass them in to open(). 124 | int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL)); 125 | if (fs_flags == -1) { 126 | ALOGE("Failed fcntl(%d, F_GETFL) : %s", fd, strerror(errno)); 127 | return NULL; 128 | } 129 | 130 | // File offset : Ignore the offset for non seekable files. 131 | const off_t offset = TEMP_FAILURE_RETRY(lseek64(fd, 0, SEEK_CUR)); 132 | 133 | // We pass the flags that open accepts to open, and use F_SETFL for 134 | // the rest of them. 135 | static const int kOpenFlags = (O_RDONLY | O_WRONLY | O_RDWR | O_DSYNC | O_SYNC); 136 | int open_flags = fs_flags & (kOpenFlags); 137 | fs_flags = fs_flags & (~(kOpenFlags)); 138 | 139 | return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset); 140 | } 141 | 142 | bool Detach() const { 143 | const int dev_null_fd = open("/dev/null", O_RDWR); 144 | if (dev_null_fd < 0) { 145 | ALOGE("Failed to open /dev/null : %s", strerror(errno)); 146 | return false; 147 | } 148 | 149 | if (dup2(dev_null_fd, fd) == -1) { 150 | ALOGE("Failed dup2 on socket descriptor %d : %s", fd, strerror(errno)); 151 | return false; 152 | } 153 | 154 | if (close(dev_null_fd) == -1) { 155 | ALOGE("Failed close(%d) : %s", dev_null_fd, strerror(errno)); 156 | return false; 157 | } 158 | 159 | return true; 160 | } 161 | 162 | bool Reopen() const { 163 | if (is_sock) { 164 | return true; 165 | } 166 | 167 | // NOTE: This might happen if the file was unlinked after being opened. 168 | // It's a common pattern in the case of temporary files and the like but 169 | // we should not allow such usage from the zygote. 170 | const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags)); 171 | 172 | if (new_fd == -1) { 173 | ALOGE("Failed open(%s, %d) : %s", file_path.c_str(), open_flags, strerror(errno)); 174 | return false; 175 | } 176 | 177 | if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) { 178 | close(new_fd); 179 | ALOGE("Failed fcntl(%d, F_SETFD, %x) : %s", new_fd, fd_flags, strerror(errno)); 180 | return false; 181 | } 182 | 183 | if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) { 184 | close(new_fd); 185 | ALOGE("Failed fcntl(%d, F_SETFL, %x) : %s", new_fd, fs_flags, strerror(errno)); 186 | return false; 187 | } 188 | 189 | if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) { 190 | close(new_fd); 191 | ALOGE("Failed lseek64(%d, SEEK_SET) : %s", new_fd, strerror(errno)); 192 | return false; 193 | } 194 | 195 | if (TEMP_FAILURE_RETRY(dup2(new_fd, fd)) == -1) { 196 | close(new_fd); 197 | ALOGE("Failed dup2(%d, %d) : %s", fd, new_fd, strerror(errno)); 198 | return false; 199 | } 200 | 201 | close(new_fd); 202 | 203 | return true; 204 | } 205 | 206 | const int fd; 207 | const struct stat stat; 208 | const std::string file_path; 209 | const int open_flags; 210 | const int fd_flags; 211 | const int fs_flags; 212 | const off_t offset; 213 | const bool is_sock; 214 | 215 | private: 216 | FileDescriptorInfo(int pfd) : 217 | fd(pfd), 218 | stat(), 219 | open_flags(0), 220 | fd_flags(0), 221 | fs_flags(0), 222 | offset(0), 223 | is_sock(true) { 224 | } 225 | 226 | FileDescriptorInfo(struct stat pstat, const std::string& pfile_path, int pfd, int popen_flags, 227 | int pfd_flags, int pfs_flags, off_t poffset) : 228 | fd(pfd), 229 | stat(pstat), 230 | file_path(pfile_path), 231 | open_flags(popen_flags), 232 | fd_flags(pfd_flags), 233 | fs_flags(pfs_flags), 234 | offset(poffset), 235 | is_sock(false) { 236 | } 237 | 238 | // Returns true iff. a given path is whitelisted. 239 | static bool IsWhitelisted(const std::string& path) { 240 | for (size_t i = 0; i < (sizeof(kPathPrefixWhitelist) / sizeof(kPathPrefixWhitelist[0])); ++i) { 241 | if (path.compare(0, strlen(kPathPrefixWhitelist[i]), kPathPrefixWhitelist[i]) == 0) { 242 | return true; 243 | } 244 | } 245 | return false; 246 | } 247 | 248 | // TODO: Call android::base::Readlink instead of copying the code here. 249 | static bool Readlink(const int fd, std::string* result) { 250 | char path[64]; 251 | snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); 252 | 253 | // Code copied from android::base::Readlink starts here : 254 | 255 | // Annoyingly, the readlink system call returns EINVAL for a zero-sized buffer, 256 | // and truncates to whatever size you do supply, so it can't be used to query. 257 | // We could call lstat first, but that would introduce a race condition that 258 | // we couldn't detect. 259 | // ext2 and ext4 both have PAGE_SIZE limitations, so we assume that here. 260 | char* buf = new char[4096]; 261 | ssize_t len = readlink(path, buf, 4096); 262 | if (len == -1) { 263 | delete[] buf; 264 | return false; 265 | } 266 | 267 | result->assign(buf, len); 268 | delete[] buf; 269 | return true; 270 | } 271 | 272 | // Returns the locally-bound name of the socket |fd|. Returns true 273 | // iff. all of the following hold : 274 | // 275 | // - the socket's sa_family is AF_UNIX. 276 | // - the length of the path is greater than zero (i.e, not an unnamed socket). 277 | // - the first byte of the path isn't zero (i.e, not a socket with an abstract 278 | // address). 279 | static bool GetSocketName(const int fd, std::string* result) { 280 | sockaddr_storage ss; 281 | sockaddr* addr = reinterpret_cast(&ss); 282 | socklen_t addr_len = sizeof(ss); 283 | 284 | if (TEMP_FAILURE_RETRY(getsockname(fd, addr, &addr_len)) == -1) { 285 | ALOGE("Failed getsockname(%d) : %s", fd, strerror(errno)); 286 | return false; 287 | } 288 | 289 | #if PLATFORM_SDK_VERSION <= 23 290 | if (addr->sa_family == AF_NETLINK) { 291 | (*result) = "@netlink@"; 292 | return true; 293 | } 294 | #endif 295 | 296 | if (addr->sa_family != AF_UNIX) { 297 | //ALOGE("Unsupported socket (fd=%d) with family %d", fd, addr->sa_family); 298 | return false; 299 | } 300 | 301 | const sockaddr_un* unix_addr = reinterpret_cast(&ss); 302 | 303 | size_t path_len = addr_len - offsetof(struct sockaddr_un, sun_path); 304 | // This is an unnamed local socket, we do not accept it. 305 | if (path_len == 0) { 306 | //ALOGE("Unsupported AF_UNIX socket (fd=%d) with empty path.", fd); 307 | return false; 308 | } 309 | 310 | // This is a local socket with an abstract address, we do not accept it. 311 | if (unix_addr->sun_path[0] == '\0') { 312 | //ALOGE("Unsupported AF_UNIX socket (fd=%d) with abstract address.", fd); 313 | return false; 314 | } 315 | 316 | // If we're here, sun_path must refer to a null terminated filesystem 317 | // pathname (man 7 unix). Remove the terminator before assigning it to an 318 | // std::string. 319 | if (unix_addr->sun_path[path_len - 1] == '\0') { 320 | --path_len; 321 | } 322 | 323 | result->assign(unix_addr->sun_path, path_len); 324 | return true; 325 | } 326 | 327 | 328 | // DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo); 329 | FileDescriptorInfo(const FileDescriptorInfo&); 330 | void operator=(const FileDescriptorInfo&); 331 | }; 332 | 333 | // A FileDescriptorTable is a collection of FileDescriptorInfo objects 334 | // keyed by their FDs. 335 | class FileDescriptorTable { 336 | public: 337 | // Creates a new FileDescriptorTable. This function scans 338 | // /proc/self/fd for the list of open file descriptors and collects 339 | // information about them. Returns NULL if an error occurs. 340 | static FileDescriptorTable* Create() { 341 | DIR* d = opendir(kFdPath); 342 | if (d == NULL) { 343 | ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno)); 344 | return NULL; 345 | } 346 | int dir_fd = dirfd(d); 347 | dirent* e; 348 | 349 | std::unordered_map open_fd_map; 350 | while ((e = readdir(d)) != NULL) { 351 | const int fd = ParseFd(e, dir_fd); 352 | if (fd == -1) { 353 | continue; 354 | } 355 | 356 | FileDescriptorInfo* info = FileDescriptorInfo::createFromFd(fd); 357 | if (info == NULL) { 358 | continue; 359 | } 360 | info->Detach(); 361 | open_fd_map[fd] = info; 362 | } 363 | 364 | if (closedir(d) == -1) { 365 | ALOGE("Unable to close directory : %s", strerror(errno)); 366 | return NULL; 367 | } 368 | return new FileDescriptorTable(open_fd_map); 369 | } 370 | 371 | // Reopens all file descriptors that are contained in the table. 372 | void Reopen() { 373 | std::unordered_map::const_iterator it; 374 | for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) { 375 | const FileDescriptorInfo* info = it->second; 376 | if (info != NULL) { 377 | info->Reopen(); 378 | delete info; 379 | } 380 | } 381 | } 382 | 383 | private: 384 | FileDescriptorTable(const std::unordered_map& map) 385 | : open_fd_map_(map) { 386 | } 387 | 388 | static int ParseFd(dirent* e, int dir_fd) { 389 | char* end; 390 | const int fd = strtol(e->d_name, &end, 10); 391 | if ((*end) != '\0') { 392 | return -1; 393 | } 394 | 395 | // Don't bother with the standard input/output/error, they're handled 396 | // specially post-fork anyway. 397 | if (fd <= STDERR_FILENO || fd == dir_fd) { 398 | return -1; 399 | } 400 | 401 | return fd; 402 | } 403 | 404 | // Invariant: All values in this unordered_map are non-NULL. 405 | std::unordered_map open_fd_map_; 406 | 407 | // DISALLOW_COPY_AND_ASSIGN(FileDescriptorTable); 408 | FileDescriptorTable(const FileDescriptorTable&); 409 | void operator=(const FileDescriptorTable&); 410 | }; 411 | -------------------------------------------------------------------------------- /libxposed_art.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes functions specific to the ART runtime. 3 | */ 4 | 5 | #define LOG_TAG "Xposed" 6 | 7 | #include "xposed_shared.h" 8 | #include "libxposed_common.h" 9 | #include "fd_utils-inl.h" 10 | 11 | #include "thread.h" 12 | #include "common_throws.h" 13 | #if PLATFORM_SDK_VERSION >= 23 14 | #include "art_method-inl.h" 15 | #else 16 | #include "mirror/art_method-inl.h" 17 | #endif 18 | #include "mirror/object-inl.h" 19 | #include "mirror/throwable.h" 20 | #include "native/scoped_fast_native_object_access.h" 21 | #include "reflection.h" 22 | #include "scoped_thread_state_change.h" 23 | #include "well_known_classes.h" 24 | 25 | using namespace art; 26 | 27 | #if PLATFORM_SDK_VERSION < 23 28 | using art::mirror::ArtMethod; 29 | #endif 30 | 31 | namespace xposed { 32 | 33 | 34 | //////////////////////////////////////////////////////////// 35 | // Library initialization 36 | //////////////////////////////////////////////////////////// 37 | 38 | /** Called by Xposed's app_process replacement. */ 39 | bool xposedInitLib(XposedShared* shared) { 40 | xposed = shared; 41 | xposed->onVmCreated = &onVmCreatedCommon; 42 | return true; 43 | } 44 | 45 | /** Called very early during VM startup. */ 46 | bool onVmCreated(JNIEnv*) { 47 | // TODO: Handle CLASS_MIUI_RESOURCES? 48 | ArtMethod::xposed_callback_class = classXposedBridge; 49 | ArtMethod::xposed_callback_method = methodXposedBridgeHandleHookedMethod; 50 | return true; 51 | } 52 | 53 | 54 | //////////////////////////////////////////////////////////// 55 | // Utility methods 56 | //////////////////////////////////////////////////////////// 57 | void logExceptionStackTrace() { 58 | ScopedObjectAccess soa(Thread::Current()); 59 | 60 | #if PLATFORM_SDK_VERSION >= 23 61 | XLOG(ERROR) << soa.Self()->GetException()->Dump(); 62 | #else 63 | XLOG(ERROR) << soa.Self()->GetException(nullptr)->Dump(); 64 | #endif 65 | } 66 | 67 | /** Lay the foundations for XposedBridge.setObjectClassNative() */ 68 | void prepareSubclassReplacement(JNIEnv* env, jclass clazz) { 69 | // clazz is supposed to replace its superclass, so make sure enough memory is allocated 70 | ScopedObjectAccess soa(env); 71 | mirror::Class* sub = soa.Decode(clazz); 72 | mirror::Class* super = sub->GetSuperClass(); 73 | super->SetObjectSize(sub->GetObjectSize()); 74 | } 75 | 76 | 77 | //////////////////////////////////////////////////////////// 78 | // JNI methods 79 | //////////////////////////////////////////////////////////// 80 | 81 | void XposedBridge_hookMethodNative(JNIEnv* env, jclass, jobject javaReflectedMethod, 82 | jobject, jint, jobject javaAdditionalInfo) { 83 | ScopedObjectAccess soa(env); 84 | 85 | // Detect usage errors. 86 | if (javaReflectedMethod == nullptr) { 87 | #if PLATFORM_SDK_VERSION >= 23 88 | ThrowIllegalArgumentException("method must not be null"); 89 | #else 90 | ThrowIllegalArgumentException(nullptr, "method must not be null"); 91 | #endif 92 | return; 93 | } 94 | 95 | // Get the ArtMethod of the method to be hooked. 96 | ArtMethod* artMethod = ArtMethod::FromReflectedMethod(soa, javaReflectedMethod); 97 | 98 | // Hook the method 99 | artMethod->EnableXposedHook(soa, javaAdditionalInfo); 100 | } 101 | 102 | jobject XposedBridge_invokeOriginalMethodNative(JNIEnv* env, jclass, jobject javaMethod, 103 | jint isResolved, jobjectArray, jclass, jobject javaReceiver, jobjectArray javaArgs) { 104 | ScopedFastNativeObjectAccess soa(env); 105 | if (UNLIKELY(!isResolved)) { 106 | ArtMethod* artMethod = ArtMethod::FromReflectedMethod(soa, javaMethod); 107 | if (LIKELY(artMethod->IsXposedHookedMethod())) { 108 | javaMethod = artMethod->GetXposedHookInfo()->reflectedMethod; 109 | } 110 | } 111 | #if PLATFORM_SDK_VERSION >= 23 112 | return InvokeMethod(soa, javaMethod, javaReceiver, javaArgs); 113 | #else 114 | return InvokeMethod(soa, javaMethod, javaReceiver, javaArgs, true); 115 | #endif 116 | } 117 | 118 | void XposedBridge_setObjectClassNative(JNIEnv* env, jclass, jobject javaObj, jclass javaClazz) { 119 | ScopedObjectAccess soa(env); 120 | mirror::Class* clazz = soa.Decode(javaClazz); 121 | StackHandleScope<1> hs(soa.Self()); 122 | Handle c(hs.NewHandle(clazz)); 123 | #if PLATFORM_SDK_VERSION >= 23 124 | if (!Runtime::Current()->GetClassLinker()->EnsureInitialized(soa.Self(), c, true, true)) { 125 | #else 126 | if (!Runtime::Current()->GetClassLinker()->EnsureInitialized(c, true, true)) { 127 | #endif 128 | XLOG(ERROR) << "Could not initialize class " << PrettyClass(clazz); 129 | return; 130 | } 131 | mirror::Object* obj = soa.Decode(javaObj); 132 | obj->SetClass(clazz); 133 | } 134 | 135 | void XposedBridge_dumpObjectNative(JNIEnv*, jclass, jobject) { 136 | // TODO Can be useful for debugging 137 | UNIMPLEMENTED(ERROR|LOG_XPOSED); 138 | } 139 | 140 | jobject XposedBridge_cloneToSubclassNative(JNIEnv* env, jclass, jobject javaObject, jclass javaClazz) { 141 | ScopedObjectAccess soa(env); 142 | mirror::Object* obj = soa.Decode(javaObject); 143 | mirror::Class* clazz = soa.Decode(javaClazz); 144 | mirror::Object* dest = obj->Clone(soa.Self(), clazz->GetObjectSize()); 145 | dest->SetClass(clazz); 146 | return soa.AddLocalReference(dest); 147 | } 148 | 149 | jint XposedBridge_getRuntime(JNIEnv*, jclass) { 150 | return 2; // RUNTIME_ART 151 | } 152 | 153 | static FileDescriptorTable* gClosedFdTable = NULL; 154 | 155 | void XposedBridge_closeFilesBeforeForkNative(JNIEnv*, jclass) { 156 | gClosedFdTable = FileDescriptorTable::Create(); 157 | } 158 | 159 | void XposedBridge_reopenFilesAfterForkNative(JNIEnv*, jclass) { 160 | gClosedFdTable->Reopen(); 161 | delete gClosedFdTable; 162 | gClosedFdTable = NULL; 163 | } 164 | 165 | } // namespace xposed 166 | -------------------------------------------------------------------------------- /libxposed_common.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes functions shared by different runtimes. 3 | */ 4 | 5 | #define LOG_TAG "Xposed" 6 | 7 | #include "libxposed_common.h" 8 | #include "JNIHelp.h" 9 | #include 10 | 11 | #define private public 12 | #if PLATFORM_SDK_VERSION == 15 13 | #include 14 | #else 15 | #include 16 | #endif 17 | #undef private 18 | 19 | namespace xposed { 20 | 21 | //////////////////////////////////////////////////////////// 22 | // Variables 23 | //////////////////////////////////////////////////////////// 24 | 25 | bool xposedLoadedSuccessfully = false; 26 | xposed::XposedShared* xposed = NULL; 27 | jclass classXposedBridge = NULL; 28 | static jclass classXResources = NULL; 29 | static jclass classFileResult = NULL; 30 | 31 | jmethodID methodXposedBridgeHandleHookedMethod = NULL; 32 | static jmethodID methodXResourcesTranslateResId = NULL; 33 | static jmethodID methodXResourcesTranslateAttrId = NULL; 34 | static jmethodID constructorFileResult = NULL; 35 | 36 | 37 | //////////////////////////////////////////////////////////// 38 | // Forward declarations 39 | //////////////////////////////////////////////////////////// 40 | 41 | static int register_natives_XposedBridge(JNIEnv* env, jclass clazz); 42 | static int register_natives_XResources(JNIEnv* env, jclass clazz); 43 | static int register_natives_ZygoteService(JNIEnv* env, jclass clazz); 44 | 45 | 46 | //////////////////////////////////////////////////////////// 47 | // Utility methods 48 | //////////////////////////////////////////////////////////// 49 | 50 | /** Read an integer value from a configuration file. */ 51 | int readIntConfig(const char* fileName, int defaultValue) { 52 | FILE *fp = fopen(fileName, "r"); 53 | if (fp == NULL) 54 | return defaultValue; 55 | 56 | int result; 57 | int success = fscanf(fp, "%i", &result); 58 | fclose(fp); 59 | 60 | return (success >= 1) ? result : defaultValue; 61 | } 62 | 63 | 64 | //////////////////////////////////////////////////////////// 65 | // Library initialization 66 | //////////////////////////////////////////////////////////// 67 | 68 | bool initXposedBridge(JNIEnv* env) { 69 | classXposedBridge = env->FindClass(CLASS_XPOSED_BRIDGE); 70 | if (classXposedBridge == NULL) { 71 | ALOGE("Error while loading Xposed class '%s':", CLASS_XPOSED_BRIDGE); 72 | logExceptionStackTrace(); 73 | env->ExceptionClear(); 74 | return false; 75 | } 76 | classXposedBridge = reinterpret_cast(env->NewGlobalRef(classXposedBridge)); 77 | 78 | ALOGI("Found Xposed class '%s', now initializing", CLASS_XPOSED_BRIDGE); 79 | if (register_natives_XposedBridge(env, classXposedBridge) != JNI_OK) { 80 | ALOGE("Could not register natives for '%s'", CLASS_XPOSED_BRIDGE); 81 | logExceptionStackTrace(); 82 | env->ExceptionClear(); 83 | return false; 84 | } 85 | 86 | methodXposedBridgeHandleHookedMethod = env->GetStaticMethodID(classXposedBridge, "handleHookedMethod", 87 | "(Ljava/lang/reflect/Member;ILjava/lang/Object;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"); 88 | if (methodXposedBridgeHandleHookedMethod == NULL) { 89 | ALOGE("ERROR: could not find method %s.handleHookedMethod(Member, int, Object, Object, Object[])", CLASS_XPOSED_BRIDGE); 90 | logExceptionStackTrace(); 91 | env->ExceptionClear(); 92 | return false; 93 | } 94 | 95 | return true; 96 | } 97 | 98 | bool initZygoteService(JNIEnv* env) { 99 | jclass zygoteServiceClass = env->FindClass(CLASS_ZYGOTE_SERVICE); 100 | if (zygoteServiceClass == NULL) { 101 | ALOGE("Error while loading ZygoteService class '%s':", CLASS_ZYGOTE_SERVICE); 102 | logExceptionStackTrace(); 103 | env->ExceptionClear(); 104 | return false; 105 | } 106 | if (register_natives_ZygoteService(env, zygoteServiceClass) != JNI_OK) { 107 | ALOGE("Could not register natives for '%s'", CLASS_ZYGOTE_SERVICE); 108 | env->ExceptionClear(); 109 | return false; 110 | } 111 | 112 | classFileResult = env->FindClass(CLASS_FILE_RESULT); 113 | if (classFileResult == NULL) { 114 | ALOGE("Error while loading FileResult class '%s':", CLASS_FILE_RESULT); 115 | logExceptionStackTrace(); 116 | env->ExceptionClear(); 117 | return false; 118 | } 119 | classFileResult = reinterpret_cast(env->NewGlobalRef(classFileResult)); 120 | 121 | constructorFileResult = env->GetMethodID(classFileResult, "", "(JJ)V"); 122 | if (constructorFileResult == NULL) { 123 | ALOGE("ERROR: could not find constructor %s(long, long)", CLASS_FILE_RESULT); 124 | logExceptionStackTrace(); 125 | env->ExceptionClear(); 126 | return false; 127 | } 128 | 129 | return true; 130 | } 131 | 132 | void onVmCreatedCommon(JNIEnv* env) { 133 | if (!initXposedBridge(env) || !initZygoteService(env)) { 134 | return; 135 | } 136 | 137 | jclass classXTypedArray = env->FindClass(CLASS_XTYPED_ARRAY); 138 | if (classXTypedArray == NULL) { 139 | ALOGE("Error while loading XTypedArray class '%s':", CLASS_XTYPED_ARRAY); 140 | logExceptionStackTrace(); 141 | env->ExceptionClear(); 142 | return; 143 | } 144 | prepareSubclassReplacement(env, classXTypedArray); 145 | 146 | if (!onVmCreated(env)) { 147 | return; 148 | } 149 | 150 | xposedLoadedSuccessfully = true; 151 | return; 152 | } 153 | 154 | 155 | //////////////////////////////////////////////////////////// 156 | // JNI methods 157 | //////////////////////////////////////////////////////////// 158 | 159 | jboolean XposedBridge_hadInitErrors(JNIEnv*, jclass) { 160 | return !xposedLoadedSuccessfully; 161 | } 162 | 163 | jobject XposedBridge_getStartClassName(JNIEnv* env, jclass) { 164 | return env->NewStringUTF(xposed->startClassName); 165 | } 166 | 167 | jboolean XposedBridge_startsSystemServer(JNIEnv*, jclass) { 168 | return xposed->startSystemServer; 169 | } 170 | 171 | jint XposedBridge_getXposedVersion(JNIEnv*, jclass) { 172 | return xposed->xposedVersionInt; 173 | } 174 | 175 | jboolean XposedBridge_initXResourcesNative(JNIEnv* env, jclass) { 176 | classXResources = env->FindClass(CLASS_XRESOURCES); 177 | if (classXResources == NULL) { 178 | ALOGE("Error while loading XResources class '%s':", CLASS_XRESOURCES); 179 | logExceptionStackTrace(); 180 | env->ExceptionClear(); 181 | return false; 182 | } 183 | classXResources = reinterpret_cast(env->NewGlobalRef(classXResources)); 184 | 185 | if (register_natives_XResources(env, classXResources) != JNI_OK) { 186 | ALOGE("Could not register natives for '%s'", CLASS_XRESOURCES); 187 | env->ExceptionClear(); 188 | return false; 189 | } 190 | 191 | methodXResourcesTranslateResId = env->GetStaticMethodID(classXResources, "translateResId", 192 | "(ILandroid/content/res/XResources;Landroid/content/res/Resources;)I"); 193 | if (methodXResourcesTranslateResId == NULL) { 194 | ALOGE("ERROR: could not find method %s.translateResId(int, XResources, Resources)", CLASS_XRESOURCES); 195 | logExceptionStackTrace(); 196 | env->ExceptionClear(); 197 | return false; 198 | } 199 | 200 | methodXResourcesTranslateAttrId = env->GetStaticMethodID(classXResources, "translateAttrId", 201 | "(Ljava/lang/String;Landroid/content/res/XResources;)I"); 202 | if (methodXResourcesTranslateAttrId == NULL) { 203 | ALOGE("ERROR: could not find method %s.findAttrId(String, XResources)", CLASS_XRESOURCES); 204 | logExceptionStackTrace(); 205 | env->ExceptionClear(); 206 | return false; 207 | } 208 | 209 | return true; 210 | } 211 | 212 | void XResources_rewriteXmlReferencesNative(JNIEnv* env, jclass, 213 | jlong parserPtr, jobject origRes, jobject repRes) { 214 | 215 | using namespace android; 216 | 217 | ResXMLParser* parser = (ResXMLParser*)parserPtr; 218 | const ResXMLTree& mTree = parser->mTree; 219 | uint32_t* mResIds = (uint32_t*)mTree.mResIds; 220 | ResXMLTree_attrExt* tag; 221 | int attrCount; 222 | 223 | if (parser == NULL) 224 | return; 225 | 226 | do { 227 | switch (parser->next()) { 228 | case ResXMLParser::START_TAG: 229 | tag = (ResXMLTree_attrExt*)parser->mCurExt; 230 | attrCount = dtohs(tag->attributeCount); 231 | for (int idx = 0; idx < attrCount; idx++) { 232 | ResXMLTree_attribute* attr = (ResXMLTree_attribute*) 233 | (((const uint8_t*)tag) 234 | + dtohs(tag->attributeStart) 235 | + (dtohs(tag->attributeSize)*idx)); 236 | 237 | // find resource IDs for attribute names 238 | int32_t attrNameID = parser->getAttributeNameID(idx); 239 | // only replace attribute name IDs for app packages 240 | if (attrNameID >= 0 && (size_t)attrNameID < mTree.mNumResIds && dtohl(mResIds[attrNameID]) >= 0x7f000000) { 241 | size_t attNameLen; 242 | const char16_t* attrName = mTree.mStrings.stringAt(attrNameID, &attNameLen); 243 | jint attrResID = env->CallStaticIntMethod(classXResources, methodXResourcesTranslateAttrId, 244 | env->NewString((const jchar*)attrName, attNameLen), origRes); 245 | if (env->ExceptionCheck()) 246 | goto leave; 247 | 248 | mResIds[attrNameID] = htodl(attrResID); 249 | } 250 | 251 | // find original resource IDs for reference values (app packages only) 252 | if (attr->typedValue.dataType != Res_value::TYPE_REFERENCE) 253 | continue; 254 | 255 | jint oldValue = dtohl(attr->typedValue.data); 256 | if (oldValue < 0x7f000000) 257 | continue; 258 | 259 | jint newValue = env->CallStaticIntMethod(classXResources, methodXResourcesTranslateResId, 260 | oldValue, origRes, repRes); 261 | if (env->ExceptionCheck()) 262 | goto leave; 263 | 264 | if (newValue != oldValue) 265 | attr->typedValue.data = htodl(newValue); 266 | } 267 | continue; 268 | case ResXMLParser::END_DOCUMENT: 269 | case ResXMLParser::BAD_DOCUMENT: 270 | goto leave; 271 | default: 272 | continue; 273 | } 274 | } while (true); 275 | 276 | leave: 277 | parser->restart(); 278 | } 279 | 280 | 281 | jboolean ZygoteService_checkFileAccess(JNIEnv* env, jclass, jstring filenameJ, jint mode) { 282 | #if XPOSED_WITH_SELINUX 283 | ScopedUtfChars filename(env, filenameJ); 284 | return xposed->zygoteservice_accessFile(filename.c_str(), mode) == 0; 285 | #else // XPOSED_WITH_SELINUX 286 | return false; 287 | #endif // XPOSED_WITH_SELINUX 288 | } 289 | 290 | jobject ZygoteService_statFile(JNIEnv* env, jclass, jstring filenameJ) { 291 | #if XPOSED_WITH_SELINUX 292 | ScopedUtfChars filename(env, filenameJ); 293 | 294 | struct stat st; 295 | int result = xposed->zygoteservice_statFile(filename.c_str(), &st); 296 | if (result != 0) { 297 | if (errno == ENOENT) { 298 | jniThrowExceptionFmt(env, "java/io/FileNotFoundException", "No such file or directory: %s", filename.c_str()); 299 | } else { 300 | jniThrowExceptionFmt(env, "java/io/IOException", "%s while reading %s", strerror(errno), filename.c_str()); 301 | } 302 | return NULL; 303 | } 304 | 305 | return env->NewObject(classFileResult, constructorFileResult, (jlong) st.st_size, (jlong) st.st_mtime); 306 | #else // XPOSED_WITH_SELINUX 307 | return NULL; 308 | #endif // XPOSED_WITH_SELINUX 309 | } 310 | 311 | jbyteArray ZygoteService_readFile(JNIEnv* env, jclass, jstring filenameJ) { 312 | #if XPOSED_WITH_SELINUX 313 | ScopedUtfChars filename(env, filenameJ); 314 | 315 | int bytesRead = 0; 316 | char* content = xposed->zygoteservice_readFile(filename.c_str(), &bytesRead); 317 | if (content == NULL) { 318 | if (errno == ENOENT) { 319 | jniThrowExceptionFmt(env, "java/io/FileNotFoundException", "No such file or directory: %s", filename.c_str()); 320 | } else { 321 | jniThrowExceptionFmt(env, "java/io/IOException", "%s while reading %s", strerror(errno), filename.c_str()); 322 | } 323 | return NULL; 324 | } 325 | 326 | jbyteArray ret = env->NewByteArray(bytesRead); 327 | if (ret != NULL) { 328 | jbyte* arrptr = (jbyte*)env->GetPrimitiveArrayCritical(ret, 0); 329 | if (arrptr) { 330 | memcpy(arrptr, content, bytesRead); 331 | env->ReleasePrimitiveArrayCritical(ret, arrptr, 0); 332 | } 333 | } 334 | 335 | free(content); 336 | return ret; 337 | #else // XPOSED_WITH_SELINUX 338 | return NULL; 339 | #endif // XPOSED_WITH_SELINUX 340 | } 341 | 342 | //////////////////////////////////////////////////////////// 343 | // JNI methods registrations 344 | //////////////////////////////////////////////////////////// 345 | 346 | int register_natives_XposedBridge(JNIEnv* env, jclass clazz) { 347 | const JNINativeMethod methods[] = { 348 | NATIVE_METHOD(XposedBridge, hadInitErrors, "()Z"), 349 | NATIVE_METHOD(XposedBridge, getStartClassName, "()Ljava/lang/String;"), 350 | NATIVE_METHOD(XposedBridge, getRuntime, "()I"), 351 | NATIVE_METHOD(XposedBridge, startsSystemServer, "()Z"), 352 | NATIVE_METHOD(XposedBridge, getXposedVersion, "()I"), 353 | NATIVE_METHOD(XposedBridge, initXResourcesNative, "()Z"), 354 | NATIVE_METHOD(XposedBridge, hookMethodNative, "(Ljava/lang/reflect/Member;Ljava/lang/Class;ILjava/lang/Object;)V"), 355 | NATIVE_METHOD(XposedBridge, setObjectClassNative, "(Ljava/lang/Object;Ljava/lang/Class;)V"), 356 | NATIVE_METHOD(XposedBridge, dumpObjectNative, "(Ljava/lang/Object;)V"), 357 | NATIVE_METHOD(XposedBridge, cloneToSubclassNative, "(Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object;"), 358 | #if PLATFORM_SDK_VERSION >= 21 359 | NATIVE_METHOD(XposedBridge, invokeOriginalMethodNative, 360 | "!(Ljava/lang/reflect/Member;I[Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"), 361 | NATIVE_METHOD(XposedBridge, closeFilesBeforeForkNative, "()V"), 362 | NATIVE_METHOD(XposedBridge, reopenFilesAfterForkNative, "()V"), 363 | #endif 364 | }; 365 | return env->RegisterNatives(clazz, methods, NELEM(methods)); 366 | } 367 | 368 | int register_natives_XResources(JNIEnv* env, jclass clazz) { 369 | const JNINativeMethod methods[] = { 370 | NATIVE_METHOD(XResources, rewriteXmlReferencesNative, "(JLandroid/content/res/XResources;Landroid/content/res/Resources;)V"), 371 | }; 372 | return env->RegisterNatives(clazz, methods, NELEM(methods)); 373 | } 374 | 375 | int register_natives_ZygoteService(JNIEnv* env, jclass clazz) { 376 | const JNINativeMethod methods[] = { 377 | NATIVE_METHOD(ZygoteService, checkFileAccess, "(Ljava/lang/String;I)Z"), 378 | NATIVE_METHOD(ZygoteService, statFile, "(Ljava/lang/String;)L" CLASS_FILE_RESULT ";"), 379 | NATIVE_METHOD(ZygoteService, readFile, "(Ljava/lang/String;)[B"), 380 | }; 381 | return env->RegisterNatives(clazz, methods, NELEM(methods)); 382 | } 383 | 384 | } // namespace xposed 385 | -------------------------------------------------------------------------------- /libxposed_common.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBXPOSED_COMMON_H_ 2 | #define LIBXPOSED_COMMON_H_ 3 | 4 | #include "xposed_shared.h" 5 | 6 | #ifndef NATIVE_METHOD 7 | #define NATIVE_METHOD(className, functionName, signature) \ 8 | { #functionName, signature, reinterpret_cast(className ## _ ## functionName) } 9 | #endif 10 | #define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0]))) 11 | 12 | namespace xposed { 13 | 14 | #define CLASS_XPOSED_BRIDGE "de/robv/android/xposed/XposedBridge" 15 | #define CLASS_XRESOURCES "android/content/res/XResources" 16 | #define CLASS_MIUI_RESOURCES "android/content/res/MiuiResources" 17 | #define CLASS_XTYPED_ARRAY "android/content/res/XResources$XTypedArray" 18 | #define CLASS_ZYGOTE_SERVICE "de/robv/android/xposed/services/ZygoteService" 19 | #define CLASS_FILE_RESULT "de/robv/android/xposed/services/FileResult" 20 | 21 | 22 | ///////////////////////////////////////////////////////////////// 23 | // Provided by common part, used by runtime-specific implementation 24 | ///////////////////////////////////////////////////////////////// 25 | extern jclass classXposedBridge; 26 | extern jmethodID methodXposedBridgeHandleHookedMethod; 27 | 28 | extern int readIntConfig(const char* fileName, int defaultValue); 29 | extern void onVmCreatedCommon(JNIEnv* env); 30 | 31 | 32 | ///////////////////////////////////////////////////////////////// 33 | // To be provided by runtime-specific implementation 34 | ///////////////////////////////////////////////////////////////// 35 | extern "C" bool xposedInitLib(xposed::XposedShared* shared); 36 | extern bool onVmCreated(JNIEnv* env); 37 | extern void prepareSubclassReplacement(JNIEnv* env, jclass clazz); 38 | extern void logExceptionStackTrace(); 39 | 40 | extern jint XposedBridge_getRuntime(JNIEnv* env, jclass clazz); 41 | extern void XposedBridge_hookMethodNative(JNIEnv* env, jclass clazz, jobject reflectedMethodIndirect, 42 | jobject declaredClassIndirect, jint slot, jobject additionalInfoIndirect); 43 | extern void XposedBridge_setObjectClassNative(JNIEnv* env, jclass clazz, jobject objIndirect, jclass clzIndirect); 44 | extern jobject XposedBridge_cloneToSubclassNative(JNIEnv* env, jclass clazz, jobject objIndirect, jclass clzIndirect); 45 | extern void XposedBridge_dumpObjectNative(JNIEnv* env, jclass clazz, jobject objIndirect); 46 | 47 | #if PLATFORM_SDK_VERSION >= 21 48 | extern jobject XposedBridge_invokeOriginalMethodNative(JNIEnv* env, jclass, jobject javaMethod, jint, jobjectArray, 49 | jclass, jobject javaReceiver, jobjectArray javaArgs); 50 | extern void XposedBridge_closeFilesBeforeForkNative(JNIEnv* env, jclass clazz); 51 | extern void XposedBridge_reopenFilesAfterForkNative(JNIEnv* env, jclass clazz); 52 | #endif 53 | 54 | } // namespace xposed 55 | 56 | #endif // LIBXPOSED_COMMON_H_ 57 | -------------------------------------------------------------------------------- /libxposed_dalvik.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes functions specific to the Dalvik runtime. 3 | */ 4 | 5 | #define LOG_TAG "Xposed" 6 | 7 | #include "libxposed_dalvik.h" 8 | #include "xposed_offsets.h" 9 | 10 | #include 11 | 12 | namespace xposed { 13 | 14 | //////////////////////////////////////////////////////////// 15 | // Forward declarations 16 | //////////////////////////////////////////////////////////// 17 | 18 | bool initMemberOffsets(JNIEnv* env); 19 | void prepareSubclassReplacement(jclass clazz); 20 | void hookedMethodCallback(const u4* args, JValue* pResult, const Method* method, ::Thread* self); 21 | void XposedBridge_invokeOriginalMethodNative(const u4* args, JValue* pResult, const Method* method, ::Thread* self); 22 | 23 | 24 | //////////////////////////////////////////////////////////// 25 | // Variables 26 | //////////////////////////////////////////////////////////// 27 | 28 | static ClassObject* objectArrayClass = NULL; 29 | static size_t arrayContentsOffset = 0; 30 | static void* PTR_gDvmJit = NULL; 31 | 32 | 33 | //////////////////////////////////////////////////////////// 34 | // Library initialization 35 | //////////////////////////////////////////////////////////// 36 | 37 | /** Called by Xposed's app_process replacement. */ 38 | bool xposedInitLib(xposed::XposedShared* shared) { 39 | xposed = shared; 40 | xposed->onVmCreated = &onVmCreatedCommon; 41 | return true; 42 | } 43 | 44 | /** Called very early during VM startup. */ 45 | bool onVmCreated(JNIEnv* env) { 46 | if (!initMemberOffsets(env)) 47 | return false; 48 | 49 | jclass classMiuiResources = env->FindClass(CLASS_MIUI_RESOURCES); 50 | if (classMiuiResources != NULL) { 51 | ClassObject* clazz = (ClassObject*)dvmDecodeIndirectRef(dvmThreadSelf(), classMiuiResources); 52 | if (dvmIsFinalClass(clazz)) { 53 | ALOGD("Removing final flag for class '%s'", CLASS_MIUI_RESOURCES); 54 | clazz->accessFlags &= ~ACC_FINAL; 55 | } 56 | } 57 | env->ExceptionClear(); 58 | 59 | Method* xposedInvokeOriginalMethodNative = (Method*) env->GetStaticMethodID(classXposedBridge, "invokeOriginalMethodNative", 60 | "(Ljava/lang/reflect/Member;I[Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"); 61 | if (xposedInvokeOriginalMethodNative == NULL) { 62 | ALOGE("ERROR: could not find method %s.invokeOriginalMethodNative(Member, int, Class[], Class, Object, Object[])", CLASS_XPOSED_BRIDGE); 63 | dvmLogExceptionStackTrace(); 64 | env->ExceptionClear(); 65 | return false; 66 | } 67 | dvmSetNativeFunc(xposedInvokeOriginalMethodNative, XposedBridge_invokeOriginalMethodNative, NULL); 68 | 69 | objectArrayClass = dvmFindArrayClass("[Ljava/lang/Object;", NULL); 70 | if (objectArrayClass == NULL) { 71 | ALOGE("Error while loading Object[] class"); 72 | dvmLogExceptionStackTrace(); 73 | env->ExceptionClear(); 74 | return false; 75 | } 76 | 77 | return true; 78 | } 79 | 80 | bool initMemberOffsets(JNIEnv* env) { 81 | PTR_gDvmJit = dlsym(RTLD_DEFAULT, "gDvmJit"); 82 | 83 | if (PTR_gDvmJit == NULL) { 84 | offsetMode = MEMBER_OFFSET_MODE_NO_JIT; 85 | } else { 86 | offsetMode = MEMBER_OFFSET_MODE_WITH_JIT; 87 | } 88 | ALOGD("Using structure member offsets for mode %s", xposedOffsetModesDesc[offsetMode]); 89 | 90 | MEMBER_OFFSET_COPY(DvmJitGlobals, codeCacheFull); 91 | 92 | int overrideCodeCacheFull = readIntConfig(XPOSED_OVERRIDE_JIT_RESET_OFFSET, -1); 93 | if (overrideCodeCacheFull > 0 && overrideCodeCacheFull < 0x400) { 94 | ALOGI("Offset for DvmJitGlobals.codeCacheFull is overridden, new value is 0x%x", overrideCodeCacheFull); 95 | MEMBER_OFFSET_VAR(DvmJitGlobals, codeCacheFull) = overrideCodeCacheFull; 96 | } 97 | 98 | // detect offset of ArrayObject->contents 99 | jintArray dummyArray = env->NewIntArray(1); 100 | if (dummyArray == NULL) { 101 | ALOGE("Could allocate int array for testing"); 102 | dvmLogExceptionStackTrace(); 103 | env->ExceptionClear(); 104 | return false; 105 | } 106 | 107 | jint* dummyArrayElements = env->GetIntArrayElements(dummyArray, NULL); 108 | arrayContentsOffset = (size_t)dummyArrayElements - (size_t)dvmDecodeIndirectRef(dvmThreadSelf(), dummyArray); 109 | env->ReleaseIntArrayElements(dummyArray,dummyArrayElements, 0); 110 | env->DeleteLocalRef(dummyArray); 111 | 112 | if (arrayContentsOffset < 12 || arrayContentsOffset > 128) { 113 | ALOGE("Detected strange offset %d of ArrayObject->contents", arrayContentsOffset); 114 | return false; 115 | } 116 | 117 | return true; 118 | } 119 | 120 | 121 | //////////////////////////////////////////////////////////// 122 | // Utility methods 123 | //////////////////////////////////////////////////////////// 124 | 125 | /** Portable clone of dvmSetObjectArrayElement() */ 126 | inline void setObjectArrayElement(const ArrayObject* obj, int index, Object* val) { 127 | uintptr_t arrayContents = (uintptr_t)obj + arrayContentsOffset; 128 | ((Object **)arrayContents)[index] = val; 129 | dvmWriteBarrierArray(obj, index, index + 1); 130 | } 131 | 132 | /** Lay the foundations for XposedBridge.setObjectClassNative() */ 133 | void prepareSubclassReplacement(JNIEnv*, jclass clazz) { 134 | // clazz is supposed to replace its superclass, so make sure enough memory is allocated 135 | ClassObject* sub = (ClassObject*) dvmDecodeIndirectRef(dvmThreadSelf(), clazz); 136 | ClassObject* super = sub->super; 137 | super->objectSize = sub->objectSize; 138 | } 139 | 140 | /** Wrapper used by the common part of the library. */ 141 | void logExceptionStackTrace() { 142 | dvmLogExceptionStackTrace(); 143 | } 144 | 145 | /** Check whether a method is already hooked. */ 146 | inline bool isMethodHooked(const Method* method) { 147 | return (method->nativeFunc == &hookedMethodCallback); 148 | } 149 | 150 | //////////////////////////////////////////////////////////// 151 | // JNI methods 152 | //////////////////////////////////////////////////////////// 153 | 154 | /** This is called when a hooked method is executed. */ 155 | void hookedMethodCallback(const u4* args, JValue* pResult, const Method* method, ::Thread* self) { 156 | if (!isMethodHooked(method)) { 157 | dvmThrowNoSuchMethodError("Could not find Xposed original method - how did you even get here?"); 158 | return; 159 | } 160 | 161 | XposedHookInfo* hookInfo = (XposedHookInfo*) method->insns; 162 | Method* original = (Method*) hookInfo; 163 | Object* originalReflected = hookInfo->reflectedMethod; 164 | Object* additionalInfo = hookInfo->additionalInfo; 165 | 166 | // convert/box arguments 167 | const char* desc = &method->shorty[1]; // [0] is the return type. 168 | Object* thisObject = NULL; 169 | size_t srcIndex = 0; 170 | size_t dstIndex = 0; 171 | 172 | // for non-static methods determine the "this" pointer 173 | if (!dvmIsStaticMethod(original)) { 174 | thisObject = (Object*) args[0]; 175 | srcIndex++; 176 | } 177 | 178 | ArrayObject* argsArray = dvmAllocArrayByClass(objectArrayClass, strlen(method->shorty) - 1, ALLOC_DEFAULT); 179 | if (argsArray == NULL) { 180 | return; 181 | } 182 | 183 | while (*desc != '\0') { 184 | char descChar = *(desc++); 185 | JValue value; 186 | Object* obj; 187 | 188 | switch (descChar) { 189 | case 'Z': 190 | case 'C': 191 | case 'F': 192 | case 'B': 193 | case 'S': 194 | case 'I': 195 | value.i = args[srcIndex++]; 196 | obj = (Object*) dvmBoxPrimitive(value, dvmFindPrimitiveClass(descChar)); 197 | dvmReleaseTrackedAlloc(obj, self); 198 | break; 199 | case 'D': 200 | case 'J': 201 | value.j = dvmGetArgLong(args, srcIndex); 202 | srcIndex += 2; 203 | obj = (Object*) dvmBoxPrimitive(value, dvmFindPrimitiveClass(descChar)); 204 | dvmReleaseTrackedAlloc(obj, self); 205 | break; 206 | case '[': 207 | case 'L': 208 | obj = (Object*) args[srcIndex++]; 209 | break; 210 | default: 211 | ALOGE("Unknown method signature description character: %c", descChar); 212 | obj = NULL; 213 | srcIndex++; 214 | } 215 | setObjectArrayElement(argsArray, dstIndex++, obj); 216 | } 217 | 218 | // call the Java handler function 219 | JValue result; 220 | dvmCallMethod(self, (Method*) methodXposedBridgeHandleHookedMethod, NULL, &result, 221 | originalReflected, (int) original, additionalInfo, thisObject, argsArray); 222 | 223 | dvmReleaseTrackedAlloc(argsArray, self); 224 | 225 | // exceptions are thrown to the caller 226 | if (dvmCheckException(self)) { 227 | return; 228 | } 229 | 230 | // return result with proper type 231 | ClassObject* returnType = dvmGetBoxedReturnType(method); 232 | if (returnType->primitiveType == PRIM_VOID) { 233 | // ignored 234 | } else if (result.l == NULL) { 235 | if (dvmIsPrimitiveClass(returnType)) { 236 | dvmThrowNullPointerException("null result when primitive expected"); 237 | } 238 | pResult->l = NULL; 239 | } else { 240 | if (!dvmUnboxPrimitive(result.l, returnType, pResult)) { 241 | dvmThrowClassCastException(result.l->clazz, returnType); 242 | } 243 | } 244 | } 245 | 246 | 247 | void XposedBridge_hookMethodNative(JNIEnv* env, jclass clazz, jobject reflectedMethodIndirect, 248 | jobject declaredClassIndirect, jint slot, jobject additionalInfoIndirect) { 249 | // Usage errors? 250 | if (declaredClassIndirect == NULL || reflectedMethodIndirect == NULL) { 251 | dvmThrowIllegalArgumentException("method and declaredClass must not be null"); 252 | return; 253 | } 254 | 255 | // Find the internal representation of the method 256 | ClassObject* declaredClass = (ClassObject*) dvmDecodeIndirectRef(dvmThreadSelf(), declaredClassIndirect); 257 | Method* method = dvmSlotToMethod(declaredClass, slot); 258 | if (method == NULL) { 259 | dvmThrowNoSuchMethodError("Could not get internal representation for method"); 260 | return; 261 | } 262 | 263 | if (isMethodHooked(method)) { 264 | // already hooked 265 | return; 266 | } 267 | 268 | // Save a copy of the original method and other hook info 269 | XposedHookInfo* hookInfo = (XposedHookInfo*) calloc(1, sizeof(XposedHookInfo)); 270 | memcpy(hookInfo, method, sizeof(hookInfo->originalMethodStruct)); 271 | hookInfo->reflectedMethod = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(reflectedMethodIndirect)); 272 | hookInfo->additionalInfo = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(additionalInfoIndirect)); 273 | 274 | // Replace method with our own code 275 | SET_METHOD_FLAG(method, ACC_NATIVE); 276 | method->nativeFunc = &hookedMethodCallback; 277 | method->insns = (const u2*) hookInfo; 278 | method->registersSize = method->insSize; 279 | method->outsSize = 0; 280 | 281 | if (PTR_gDvmJit != NULL) { 282 | // reset JIT cache 283 | char currentValue = *((char*)PTR_gDvmJit + MEMBER_OFFSET_VAR(DvmJitGlobals,codeCacheFull)); 284 | if (currentValue == 0 || currentValue == 1) { 285 | MEMBER_VAL(PTR_gDvmJit, DvmJitGlobals, codeCacheFull) = true; 286 | } else { 287 | ALOGE("Unexpected current value for codeCacheFull: %d", currentValue); 288 | } 289 | } 290 | } 291 | 292 | 293 | /** 294 | * Simplified copy of Method.invokeNative(), but calls the original (non-hooked) method 295 | * and has no access checks. Used to call the real implementation of hooked methods. 296 | */ 297 | void XposedBridge_invokeOriginalMethodNative(const u4* args, JValue* pResult, 298 | const Method* method, ::Thread* self) { 299 | Method* meth = (Method*) args[1]; 300 | if (meth == NULL) { 301 | meth = dvmGetMethodFromReflectObj((Object*) args[0]); 302 | if (isMethodHooked(meth)) { 303 | meth = (Method*) meth->insns; 304 | } 305 | } 306 | ArrayObject* params = (ArrayObject*) args[2]; 307 | ClassObject* returnType = (ClassObject*) args[3]; 308 | Object* thisObject = (Object*) args[4]; // null for static methods 309 | ArrayObject* argList = (ArrayObject*) args[5]; 310 | 311 | // invoke the method 312 | pResult->l = dvmInvokeMethod(thisObject, meth, argList, params, returnType, true); 313 | return; 314 | } 315 | 316 | void XposedBridge_setObjectClassNative(JNIEnv* env, jclass clazz, jobject objIndirect, jclass clzIndirect) { 317 | Object* obj = (Object*) dvmDecodeIndirectRef(dvmThreadSelf(), objIndirect); 318 | ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef(dvmThreadSelf(), clzIndirect); 319 | if (clz->status < CLASS_INITIALIZED && !dvmInitClass(clz)) { 320 | ALOGE("Could not initialize class %s", clz->descriptor); 321 | return; 322 | } 323 | obj->clazz = clz; 324 | } 325 | 326 | void XposedBridge_dumpObjectNative(JNIEnv* env, jclass clazz, jobject objIndirect) { 327 | Object* obj = (Object*) dvmDecodeIndirectRef(dvmThreadSelf(), objIndirect); 328 | dvmDumpObject(obj); 329 | } 330 | 331 | jobject XposedBridge_cloneToSubclassNative(JNIEnv* env, jclass clazz, jobject objIndirect, jclass clzIndirect) { 332 | Object* obj = (Object*) dvmDecodeIndirectRef(dvmThreadSelf(), objIndirect); 333 | ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef(dvmThreadSelf(), clzIndirect); 334 | 335 | jobject copyIndirect = env->AllocObject(clzIndirect); 336 | if (copyIndirect == NULL) 337 | return NULL; 338 | 339 | Object* copy = (Object*) dvmDecodeIndirectRef(dvmThreadSelf(), copyIndirect); 340 | size_t size = obj->clazz->objectSize; 341 | size_t offset = sizeof(Object); 342 | memcpy((char*)copy + offset, (char*)obj + offset, size - offset); 343 | 344 | if (IS_CLASS_FLAG_SET(clz, CLASS_ISFINALIZABLE)) 345 | dvmSetFinalizable(copy); 346 | 347 | return copyIndirect; 348 | } 349 | 350 | jint XposedBridge_getRuntime(JNIEnv* env, jclass clazz) { 351 | return 1; // RUNTIME_DALVIK 352 | } 353 | 354 | } // namespace android 355 | -------------------------------------------------------------------------------- /libxposed_dalvik.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBXPOSED_DALVIK_H_ 2 | #define LIBXPOSED_DALVIK_H_ 3 | 4 | #define ANDROID_SMP 1 5 | #include "Dalvik.h" 6 | 7 | #include "libxposed_common.h" 8 | 9 | namespace xposed { 10 | 11 | #define XPOSED_OVERRIDE_JIT_RESET_OFFSET XPOSED_DIR "conf/jit_reset_offset" 12 | 13 | struct XposedHookInfo { 14 | struct { 15 | Method originalMethod; 16 | // copy a few bytes more than defined for Method in AOSP 17 | // to accomodate for (rare) extensions by the target ROM 18 | int dummyForRomExtensions[4]; 19 | } originalMethodStruct; 20 | 21 | Object* reflectedMethod; 22 | Object* additionalInfo; 23 | }; 24 | 25 | } // namespace xposed 26 | 27 | #endif // LIBXPOSED_DALVIK_H_ 28 | -------------------------------------------------------------------------------- /xposed.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes functions called directly from app_main.cpp during startup. 3 | */ 4 | 5 | #define LOG_TAG "Xposed" 6 | 7 | #include "xposed.h" 8 | #include "xposed_logcat.h" 9 | #include "xposed_safemode.h" 10 | #include "xposed_service.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #if PLATFORM_SDK_VERSION >= 18 22 | #include 23 | #else 24 | #include 25 | #endif 26 | 27 | namespace xposed { 28 | 29 | //////////////////////////////////////////////////////////// 30 | // Variables 31 | //////////////////////////////////////////////////////////// 32 | 33 | XposedShared* xposed = new XposedShared; 34 | static int sdkVersion = -1; 35 | static char* argBlockStart; 36 | static size_t argBlockLength; 37 | 38 | const char* xposedVersion = "unknown (invalid " XPOSED_PROP_FILE ")"; 39 | uint32_t xposedVersionInt = 0; 40 | 41 | //////////////////////////////////////////////////////////// 42 | // Functions 43 | //////////////////////////////////////////////////////////// 44 | 45 | /** Handle special command line options. */ 46 | bool handleOptions(int argc, char* const argv[]) { 47 | parseXposedProp(); 48 | 49 | if (argc == 2 && strcmp(argv[1], "--xposedversion") == 0) { 50 | printf("Xposed version: %s\n", xposedVersion); 51 | return true; 52 | } 53 | 54 | if (argc == 2 && strcmp(argv[1], "--xposedtestsafemode") == 0) { 55 | printf("Testing Xposed safemode trigger\n"); 56 | 57 | if (detectSafemodeTrigger(shouldSkipSafemodeDelay())) { 58 | printf("Safemode triggered\n"); 59 | } else { 60 | printf("Safemode not triggered\n"); 61 | } 62 | return true; 63 | } 64 | 65 | // From Lollipop coding, used to override the process name 66 | argBlockStart = argv[0]; 67 | uintptr_t start = reinterpret_cast(argv[0]); 68 | uintptr_t end = reinterpret_cast(argv[argc - 1]); 69 | end += strlen(argv[argc - 1]) + 1; 70 | argBlockLength = end - start; 71 | 72 | return false; 73 | } 74 | 75 | /** Initialize Xposed (unless it is disabled). */ 76 | bool initialize(bool zygote, bool startSystemServer, const char* className, int argc, char* const argv[]) { 77 | #if !defined(XPOSED_ENABLE_FOR_TOOLS) 78 | if (!zygote) 79 | return false; 80 | #endif 81 | 82 | if (isMinimalFramework()) { 83 | ALOGI("Not loading Xposed for minimal framework (encrypted device)"); 84 | return false; 85 | } 86 | 87 | xposed->zygote = zygote; 88 | xposed->startSystemServer = startSystemServer; 89 | xposed->startClassName = className; 90 | xposed->xposedVersionInt = xposedVersionInt; 91 | 92 | #if XPOSED_WITH_SELINUX 93 | xposed->isSELinuxEnabled = is_selinux_enabled() == 1; 94 | xposed->isSELinuxEnforcing = xposed->isSELinuxEnabled && security_getenforce() == 1; 95 | #else 96 | xposed->isSELinuxEnabled = false; 97 | xposed->isSELinuxEnforcing = false; 98 | #endif // XPOSED_WITH_SELINUX 99 | 100 | if (startSystemServer) { 101 | xposed::logcat::printStartupMarker(); 102 | } else if (zygote) { 103 | // TODO Find a better solution for this 104 | // Give the primary Zygote process a little time to start first. 105 | // This also makes the log easier to read, as logs for the two Zygotes are not mixed up. 106 | sleep(10); 107 | } 108 | 109 | printRomInfo(); 110 | 111 | if (startSystemServer) { 112 | if (!xposed::service::startAll()) { 113 | return false; 114 | } 115 | xposed::logcat::start(); 116 | #if XPOSED_WITH_SELINUX 117 | } else if (xposed->isSELinuxEnabled) { 118 | if (!xposed::service::startMembased()) { 119 | return false; 120 | } 121 | #endif // XPOSED_WITH_SELINUX 122 | } 123 | 124 | #if XPOSED_WITH_SELINUX 125 | // Don't let any further forks access the Zygote service 126 | if (xposed->isSELinuxEnabled) { 127 | xposed::service::membased::restrictMemoryInheritance(); 128 | } 129 | #endif // XPOSED_WITH_SELINUX 130 | 131 | // FIXME Zygote has no access to input devices, this would need to be check in system_server context 132 | if (zygote && !isSafemodeDisabled() && detectSafemodeTrigger(shouldSkipSafemodeDelay())) 133 | disableXposed(); 134 | 135 | if (isDisabled() || (!zygote && shouldIgnoreCommand(argc, argv))) 136 | return false; 137 | 138 | return addJarToClasspath(); 139 | } 140 | 141 | /** Print information about the used ROM into the log */ 142 | void printRomInfo() { 143 | char release[PROPERTY_VALUE_MAX]; 144 | char sdk[PROPERTY_VALUE_MAX]; 145 | char manufacturer[PROPERTY_VALUE_MAX]; 146 | char model[PROPERTY_VALUE_MAX]; 147 | char rom[PROPERTY_VALUE_MAX]; 148 | char fingerprint[PROPERTY_VALUE_MAX]; 149 | char platform[PROPERTY_VALUE_MAX]; 150 | #if defined(__LP64__) 151 | const int bit = 64; 152 | #else 153 | const int bit = 32; 154 | #endif 155 | 156 | property_get("ro.build.version.release", release, "n/a"); 157 | property_get("ro.build.version.sdk", sdk, "n/a"); 158 | property_get("ro.product.manufacturer", manufacturer, "n/a"); 159 | property_get("ro.product.model", model, "n/a"); 160 | property_get("ro.build.display.id", rom, "n/a"); 161 | property_get("ro.build.fingerprint", fingerprint, "n/a"); 162 | property_get("ro.product.cpu.abi", platform, "n/a"); 163 | 164 | ALOGI("-----------------"); 165 | ALOGI("Starting Xposed version %s, compiled for SDK %d", xposedVersion, PLATFORM_SDK_VERSION); 166 | ALOGI("Device: %s (%s), Android version %s (SDK %s)", model, manufacturer, release, sdk); 167 | ALOGI("ROM: %s", rom); 168 | ALOGI("Build fingerprint: %s", fingerprint); 169 | ALOGI("Platform: %s, %d-bit binary, system server: %s", platform, bit, xposed->startSystemServer ? "yes" : "no"); 170 | if (!xposed->zygote) { 171 | ALOGI("Class name: %s", xposed->startClassName); 172 | } 173 | ALOGI("SELinux enabled: %s, enforcing: %s", 174 | xposed->isSELinuxEnabled ? "yes" : "no", 175 | xposed->isSELinuxEnforcing ? "yes" : "no"); 176 | } 177 | 178 | /** Parses /system/xposed.prop and stores selected values in variables */ 179 | void parseXposedProp() { 180 | FILE *fp = fopen(XPOSED_PROP_FILE, "r"); 181 | if (fp == NULL) { 182 | ALOGE("Could not read %s: %s", XPOSED_PROP_FILE, strerror(errno)); 183 | return; 184 | } 185 | 186 | char buf[512]; 187 | while (fgets(buf, sizeof(buf), fp) != NULL) { 188 | char* key = buf; 189 | // Ignore leading spaces for the key 190 | while (isspace(*key)) key++; 191 | 192 | // Skip comments 193 | if (*key == '#') 194 | continue; 195 | 196 | // Find the key/value separator 197 | char* value = strchr(buf, '='); 198 | if (value == NULL) 199 | continue; 200 | 201 | // Ignore trailing spaces for the key 202 | char* tmp = value; 203 | do { *tmp = 0; tmp--; } while (isspace(*tmp)); 204 | 205 | // Ignore leading spaces for the value 206 | do { value++; } while (isspace(*value)); 207 | 208 | // Remove trailing newline 209 | tmp = strpbrk(value, "\n\r"); 210 | if (tmp != NULL) 211 | *tmp = 0; 212 | 213 | // Handle this entry 214 | if (!strcmp("version", key)) { 215 | int len = strlen(value); 216 | if (len == 0) 217 | continue; 218 | tmp = (char*) malloc(len + 1); 219 | strlcpy(tmp, value, len + 1); 220 | xposedVersion = tmp; 221 | xposedVersionInt = atoi(xposedVersion); 222 | } 223 | } 224 | fclose(fp); 225 | 226 | return; 227 | } 228 | 229 | /** Returns the SDK version of the system */ 230 | int getSdkVersion() { 231 | if (sdkVersion < 0) { 232 | char sdkString[PROPERTY_VALUE_MAX]; 233 | property_get("ro.build.version.sdk", sdkString, "0"); 234 | sdkVersion = atoi(sdkString); 235 | } 236 | return sdkVersion; 237 | } 238 | 239 | /** Check whether Xposed is disabled by a flag file */ 240 | bool isDisabled() { 241 | if (zygote_access(XPOSED_LOAD_BLOCKER, F_OK) == 0) { 242 | ALOGE("Found %s, not loading Xposed", XPOSED_LOAD_BLOCKER); 243 | return true; 244 | } 245 | return false; 246 | } 247 | 248 | /** Create a flag file to disable Xposed. */ 249 | void disableXposed() { 250 | int fd; 251 | // FIXME add a "touch" operation to xposed::service::membased 252 | fd = open(XPOSED_LOAD_BLOCKER, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); 253 | if (fd >= 0) 254 | close(fd); 255 | } 256 | 257 | /** Check whether safemode is disabled. */ 258 | bool isSafemodeDisabled() { 259 | if (zygote_access(XPOSED_SAFEMODE_DISABLE, F_OK) == 0) 260 | return true; 261 | else 262 | return false; 263 | } 264 | 265 | /** Check whether the delay for safemode should be skipped. */ 266 | bool shouldSkipSafemodeDelay() { 267 | if (zygote_access(XPOSED_SAFEMODE_NODELAY, F_OK) == 0) 268 | return true; 269 | else 270 | return false; 271 | } 272 | 273 | /** Ignore the broadcasts by various Superuser implementations to avoid spamming the Xposed log. */ 274 | bool shouldIgnoreCommand(int argc, const char* const argv[]) { 275 | if (argc < 4 || strcmp(xposed->startClassName, "com.android.commands.am.Am") != 0) 276 | return false; 277 | 278 | if (strcmp(argv[2], "broadcast") != 0 && strcmp(argv[2], "start") != 0) 279 | return false; 280 | 281 | bool mightBeSuperuser = false; 282 | for (int i = 3; i < argc; i++) { 283 | if (strcmp(argv[i], "com.noshufou.android.su.RESULT") == 0 284 | || strcmp(argv[i], "eu.chainfire.supersu.NativeAccess") == 0) 285 | return true; 286 | 287 | if (mightBeSuperuser && strcmp(argv[i], "--user") == 0) 288 | return true; 289 | 290 | char* lastComponent = strrchr(argv[i], '.'); 291 | if (!lastComponent) 292 | continue; 293 | 294 | if (strcmp(lastComponent, ".RequestActivity") == 0 295 | || strcmp(lastComponent, ".NotifyActivity") == 0 296 | || strcmp(lastComponent, ".SuReceiver") == 0) 297 | mightBeSuperuser = true; 298 | } 299 | 300 | return false; 301 | } 302 | 303 | /** Adds a path to the beginning of an environment variable. */ 304 | static bool addPathToEnv(const char* name, const char* path) { 305 | char* oldPath = getenv(name); 306 | if (oldPath == NULL) { 307 | setenv(name, path, 1); 308 | } else { 309 | char newPath[4096]; 310 | int neededLength = snprintf(newPath, sizeof(newPath), "%s:%s", path, oldPath); 311 | if (neededLength >= (int)sizeof(newPath)) { 312 | ALOGE("ERROR: %s would exceed %d characters", name, (int) sizeof(newPath)); 313 | return false; 314 | } 315 | setenv(name, newPath, 1); 316 | } 317 | return true; 318 | } 319 | 320 | /** Add XposedBridge.jar to the Java classpath. */ 321 | bool addJarToClasspath() { 322 | ALOGI("-----------------"); 323 | 324 | // Do we have a new version and are (re)starting zygote? Then load it! 325 | /* 326 | FIXME if you can 327 | if (xposed->startSystemServer && access(XPOSED_JAR_NEWVERSION, R_OK) == 0) { 328 | ALOGI("Found new Xposed jar version, activating it"); 329 | if (rename(XPOSED_JAR_NEWVERSION, XPOSED_JAR) != 0) { 330 | ALOGE("Could not move %s to %s", XPOSED_JAR_NEWVERSION, XPOSED_JAR); 331 | return false; 332 | } 333 | } 334 | */ 335 | 336 | if (access(XPOSED_JAR, R_OK) == 0) { 337 | if (!addPathToEnv("CLASSPATH", XPOSED_JAR)) 338 | return false; 339 | 340 | ALOGI("Added Xposed (%s) to CLASSPATH", XPOSED_JAR); 341 | return true; 342 | } else { 343 | ALOGE("ERROR: Could not access Xposed jar '%s'", XPOSED_JAR); 344 | return false; 345 | } 346 | } 347 | 348 | /** Callback which checks the loaded shared libraries for libdvm/libart. */ 349 | static bool determineRuntime(const char** xposedLibPath) { 350 | FILE *fp = fopen("/proc/self/maps", "r"); 351 | if (fp == NULL) { 352 | ALOGE("Could not open /proc/self/maps: %s", strerror(errno)); 353 | return false; 354 | } 355 | 356 | bool success = false; 357 | char line[256]; 358 | while (fgets(line, sizeof(line), fp) != NULL) { 359 | char* libname = strrchr(line, '/'); 360 | if (!libname) 361 | continue; 362 | libname++; 363 | 364 | if (strcmp("libdvm.so\n", libname) == 0) { 365 | ALOGI("Detected Dalvik runtime"); 366 | *xposedLibPath = XPOSED_LIB_DALVIK; 367 | success = true; 368 | break; 369 | 370 | } else if (strcmp("libart.so\n", libname) == 0) { 371 | ALOGI("Detected ART runtime"); 372 | *xposedLibPath = XPOSED_LIB_ART; 373 | success = true; 374 | break; 375 | } 376 | } 377 | 378 | fclose(fp); 379 | return success; 380 | } 381 | 382 | /** Load the libxposed_*.so library for the currently active runtime. */ 383 | void onVmCreated(JNIEnv* env) { 384 | // Determine the currently active runtime 385 | const char* xposedLibPath = NULL; 386 | if (!determineRuntime(&xposedLibPath)) { 387 | ALOGE("Could not determine runtime, not loading Xposed"); 388 | return; 389 | } 390 | 391 | // Load the suitable libxposed_*.so for it 392 | void* xposedLibHandle = dlopen(xposedLibPath, RTLD_NOW); 393 | if (!xposedLibHandle) { 394 | ALOGE("Could not load libxposed: %s", dlerror()); 395 | return; 396 | } 397 | 398 | // Clear previous errors 399 | dlerror(); 400 | 401 | // Initialize the library 402 | bool (*xposedInitLib)(XposedShared* shared) = NULL; 403 | *(void **) (&xposedInitLib) = dlsym(xposedLibHandle, "xposedInitLib"); 404 | if (!xposedInitLib) { 405 | ALOGE("Could not find function xposedInitLib"); 406 | return; 407 | } 408 | 409 | #if XPOSED_WITH_SELINUX 410 | xposed->zygoteservice_accessFile = &service::membased::accessFile; 411 | xposed->zygoteservice_statFile = &service::membased::statFile; 412 | xposed->zygoteservice_readFile = &service::membased::readFile; 413 | #endif // XPOSED_WITH_SELINUX 414 | 415 | if (xposedInitLib(xposed)) { 416 | xposed->onVmCreated(env); 417 | } 418 | } 419 | 420 | /** Set the process name */ 421 | void setProcessName(const char* name) { 422 | memset(argBlockStart, 0, argBlockLength); 423 | strlcpy(argBlockStart, name, argBlockLength); 424 | set_process_name(name); 425 | } 426 | 427 | 428 | /** Drop all capabilities except for the mentioned ones */ 429 | void dropCapabilities(int8_t keep[]) { 430 | struct __user_cap_header_struct header; 431 | struct __user_cap_data_struct cap[2]; 432 | memset(&header, 0, sizeof(header)); 433 | memset(&cap, 0, sizeof(cap)); 434 | header.version = _LINUX_CAPABILITY_VERSION_3; 435 | header.pid = 0; 436 | 437 | if (keep != NULL) { 438 | for (int i = 0; keep[i] >= 0; i++) { 439 | cap[CAP_TO_INDEX(keep[i])].permitted |= CAP_TO_MASK(keep[i]); 440 | } 441 | cap[0].effective = cap[0].inheritable = cap[0].permitted; 442 | cap[1].effective = cap[1].inheritable = cap[1].permitted; 443 | } 444 | 445 | capset(&header, &cap[0]); 446 | } 447 | 448 | /** 449 | * Checks whether the system is booting into a minimal Android framework. 450 | * This is the case when the device is encrypted with a password that 451 | * has to be entered on boot. /data is a tmpfs in that case, so we 452 | * can't load any modules anyway. 453 | * The system will reboot later with the full framework. 454 | */ 455 | bool isMinimalFramework() { 456 | char voldDecrypt[PROPERTY_VALUE_MAX]; 457 | property_get("vold.decrypt", voldDecrypt, ""); 458 | return ((strcmp(voldDecrypt, "trigger_restart_min_framework") == 0) || 459 | (strcmp(voldDecrypt, "1") == 0)); 460 | } 461 | 462 | } // namespace xposed 463 | -------------------------------------------------------------------------------- /xposed.h: -------------------------------------------------------------------------------- 1 | #ifndef XPOSED_H_ 2 | #define XPOSED_H_ 3 | 4 | #include "xposed_shared.h" 5 | 6 | #define XPOSED_PROP_FILE "/system/xposed.prop" 7 | 8 | #if defined(__LP64__) 9 | #define XPOSED_LIB_DIR "/system/lib64/" 10 | #else 11 | #define XPOSED_LIB_DIR "/system/lib/" 12 | #endif 13 | #define XPOSED_LIB_DALVIK XPOSED_LIB_DIR "libxposed_dalvik.so" 14 | #define XPOSED_LIB_ART XPOSED_LIB_DIR "libxposed_art.so" 15 | #define XPOSED_JAR "/system/framework/XposedBridge.jar" 16 | #define XPOSED_JAR_NEWVERSION XPOSED_DIR "bin/XposedBridge.jar.newversion" 17 | #define XPOSED_LOAD_BLOCKER XPOSED_DIR "conf/disabled" 18 | #define XPOSED_SAFEMODE_NODELAY XPOSED_DIR "conf/safemode_nodelay" 19 | #define XPOSED_SAFEMODE_DISABLE XPOSED_DIR "conf/safemode_disable" 20 | 21 | #define XPOSED_CLASS_DOTS_ZYGOTE "de.robv.android.xposed.XposedBridge" 22 | #define XPOSED_CLASS_DOTS_TOOLS "de.robv.android.xposed.XposedBridge$ToolEntryPoint" 23 | 24 | #if XPOSED_WITH_SELINUX 25 | #include 26 | #define ctx_system ((security_context_t) "u:r:system_server:s0") 27 | #if PLATFORM_SDK_VERSION >= 23 28 | #define ctx_app ((security_context_t) "u:r:untrusted_app:s0:c512,c768") 29 | #else 30 | #define ctx_app ((security_context_t) "u:r:untrusted_app:s0") 31 | #endif // PLATFORM_SDK_VERSION >= 23 32 | #endif // XPOSED_WITH_SELINUX 33 | 34 | namespace xposed { 35 | 36 | bool handleOptions(int argc, char* const argv[]); 37 | bool initialize(bool zygote, bool startSystemServer, const char* className, int argc, char* const argv[]); 38 | void printRomInfo(); 39 | void parseXposedProp(); 40 | int getSdkVersion(); 41 | bool isDisabled(); 42 | void disableXposed(); 43 | bool isSafemodeDisabled(); 44 | bool shouldSkipSafemodeDelay(); 45 | bool shouldIgnoreCommand(int argc, const char* const argv[]); 46 | bool addJarToClasspath(); 47 | void onVmCreated(JNIEnv* env); 48 | void setProcessName(const char* name); 49 | void dropCapabilities(int8_t keep[] = NULL); 50 | bool isMinimalFramework(); 51 | 52 | } // namespace xposed 53 | 54 | #endif // XPOSED_H_ 55 | -------------------------------------------------------------------------------- /xposed_logcat.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes the Xposed service, which is especially used to work around SELinux restrictions. 3 | */ 4 | 5 | #define LOG_TAG "Xposed" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "xposed.h" 14 | #include "xposed_service.h" 15 | #include "xposed_logcat.h" 16 | 17 | 18 | namespace xposed { 19 | namespace logcat { 20 | 21 | //////////////////////////////////////////////////////////// 22 | // Declarations 23 | //////////////////////////////////////////////////////////// 24 | 25 | #define AID_LOG 1007 26 | #define CAP_SYSLOG 34 27 | char marker[50]; 28 | 29 | 30 | //////////////////////////////////////////////////////////// 31 | // Functions 32 | //////////////////////////////////////////////////////////// 33 | 34 | static void execLogcat() { 35 | // Ensure that we're allowed to read all log entries 36 | setresgid(AID_LOG, AID_LOG, AID_LOG); 37 | int8_t keep[] = { CAP_SYSLOG, -1 }; 38 | xposed::dropCapabilities(keep); 39 | 40 | // Execute a logcat command that will keep running in the background 41 | if (zygote_access(XPOSEDLOG_CONF_ALL, F_OK) == 0) { 42 | execl("/system/bin/logcat", "logcat", 43 | "-v", "time", // include timestamps in the log 44 | (char*) 0); 45 | } else { 46 | execl("/system/bin/logcat", "logcat", 47 | "-v", "time", // include timestamps in the log 48 | "-s", // be silent by default, except for the following tags 49 | "XposedStartupMarker:D", // marks the beginning of the current log 50 | "Xposed:I", // Xposed framework and default logging 51 | "appproc:I", // app_process 52 | "XposedInstaller:I", // Xposed Installer 53 | "art:F", // ART crashes 54 | (char*) 0); 55 | } 56 | 57 | // We only get here in case of errors 58 | ALOGE("Could not execute logcat: %s", strerror(errno)); 59 | exit(EXIT_FAILURE); 60 | } 61 | 62 | #ifndef dprintf 63 | static inline int dprintf(int fd, const char *format, ...) { 64 | char* message; 65 | va_list args; 66 | va_start(args, format); 67 | int size = vasprintf(&message, format, args); 68 | if (size > 0) { 69 | write(fd, message, size); 70 | free(message); 71 | } 72 | va_end(args); 73 | return size; 74 | } 75 | #endif 76 | 77 | static void runDaemon(int pipefd) { 78 | xposed::setProcessName("xposed_logcat"); 79 | xposed::dropCapabilities(); 80 | 81 | umask(0); 82 | int logfile = open(XPOSEDLOG, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); 83 | if (logfile < 0) { 84 | ALOGE("Could not open %s: %s", XPOSEDLOG, strerror(errno)); 85 | exit(EXIT_FAILURE); 86 | } 87 | 88 | FILE* pipe = fdopen(pipefd, "r"); 89 | if (pipe == NULL) { 90 | ALOGE("fdopen failed for pipe file descriptor %d: %s", pipefd, strerror(errno)); 91 | exit(EXIT_FAILURE); 92 | } 93 | 94 | char buf[512]; 95 | bool foundMarker = false; 96 | long totalSize = 0; 97 | while (fgets(buf, sizeof(buf), pipe) != NULL) { 98 | if (buf[0] == '-') 99 | continue; // beginning of 100 | 101 | if (!foundMarker) { 102 | if (strstr(buf, "XposedStartupMarker") != NULL && strstr(buf, marker) != NULL) { 103 | foundMarker = true; 104 | } 105 | continue; 106 | } 107 | 108 | int len = strlen(buf); 109 | write(logfile, buf, len); 110 | 111 | totalSize += len; 112 | if (totalSize > XPOSEDLOG_MAX_SIZE) { 113 | dprintf(logfile, "\nReached maximum log size (%'d kB), further lines won't be logged.\n", XPOSEDLOG_MAX_SIZE / 1024); 114 | exit(EXIT_FAILURE); 115 | } 116 | } 117 | 118 | ALOGE("Broken pipe to logcat: %s", strerror(ferror(pipe))); 119 | close(logfile); 120 | exit(EXIT_FAILURE); 121 | } 122 | 123 | void printStartupMarker() { 124 | sprintf(marker, "Current time: %d, PID: %d", (int) time(NULL), getpid()); 125 | ALOG(LOG_DEBUG, "XposedStartupMarker", marker, NULL); 126 | } 127 | 128 | void start() { 129 | // Fork to create a daemon 130 | pid_t pid; 131 | if ((pid = fork()) < 0) { 132 | ALOGE("Fork for Xposed logcat daemon failed: %s", strerror(errno)); 133 | return; 134 | } else if (pid != 0) { 135 | return; 136 | } 137 | 138 | #if XPOSED_WITH_SELINUX 139 | if (xposed->isSELinuxEnabled) { 140 | if (setcon(ctx_app) != 0) { 141 | ALOGE("Could not switch to %s context", ctx_app); 142 | exit(EXIT_FAILURE); 143 | } 144 | } 145 | #endif // XPOSED_WITH_SELINUX 146 | 147 | int err = rename(XPOSEDLOG, XPOSEDLOG_OLD); 148 | if (err < 0 && errno != ENOENT) { 149 | ALOGE("%s while renaming log file %s -> %s", strerror(errno), XPOSEDLOG, XPOSEDLOG_OLD); 150 | } 151 | 152 | int pipeFds[2]; 153 | if (pipe(pipeFds) < 0) { 154 | ALOGE("Could not allocate pipe for logcat output: %s", strerror(errno)); 155 | exit(EXIT_FAILURE); 156 | } 157 | 158 | if ((pid = fork()) < 0) { 159 | ALOGE("Fork for logcat execution failed: %s", strerror(errno)); 160 | exit(EXIT_FAILURE); 161 | } else if (pid == 0) { 162 | close(pipeFds[1]); 163 | runDaemon(pipeFds[0]); 164 | } else { 165 | close(pipeFds[0]); 166 | if (dup2(pipeFds[1], STDOUT_FILENO) == -1) { 167 | ALOGE("Could not redirect stdout: %s", strerror(errno)); 168 | exit(EXIT_FAILURE); 169 | } 170 | if (dup2(pipeFds[1], STDERR_FILENO) == -1) { 171 | ALOGE("Could not redirect stdout: %s", strerror(errno)); 172 | exit(EXIT_FAILURE); 173 | } 174 | execLogcat(); 175 | } 176 | 177 | // Should never reach this point 178 | exit(EXIT_FAILURE); 179 | } 180 | 181 | } // namespace logcat 182 | } // namespace xposed 183 | -------------------------------------------------------------------------------- /xposed_logcat.h: -------------------------------------------------------------------------------- 1 | #ifndef XPOSED_LOGCAT_H_ 2 | #define XPOSED_LOGCAT_H_ 3 | 4 | #define XPOSEDLOG XPOSED_DIR "log/error.log" 5 | #define XPOSEDLOG_OLD XPOSEDLOG ".old" 6 | #define XPOSEDLOG_CONF_ALL XPOSED_DIR "conf/log_all" 7 | #define XPOSEDLOG_MAX_SIZE 5*1024*1024 8 | 9 | namespace xposed { 10 | namespace logcat { 11 | 12 | void printStartupMarker(); 13 | void start(); 14 | 15 | } // namespace logcat 16 | } // namespace xposed 17 | 18 | #endif /* XPOSED_LOGCAT_H_ */ 19 | -------------------------------------------------------------------------------- /xposed_offsets.h: -------------------------------------------------------------------------------- 1 | /* 2 | Certain compile time parameters result in different offsets 3 | for members in structures. This file defines the offsets for 4 | members which cannot be accessed otherwise and some macros 5 | to simplify accessing them. 6 | */ 7 | 8 | #define MEMBER_OFFSET_ARRAY(type,member) offsets_array_ ## type ## _ ## member 9 | #define MEMBER_OFFSET_VAR(type,member) offset_ ## type ## _ ## member 10 | #define MEMBER_TYPE(type,member) offset_type_ ## type ## _ ## member 11 | 12 | #define MEMBER_PTR(obj,type,member) \ 13 | ( (MEMBER_TYPE(type,member)*) ( (char*)(obj) + MEMBER_OFFSET_VAR(type,member) ) ) 14 | #define MEMBER_VAL(obj,type,member) *MEMBER_PTR(obj,type,member) 15 | 16 | #define MEMBER_OFFSET_DEFINE(type,member,offsets...) \ 17 | static int MEMBER_OFFSET_ARRAY(type,member)[] = { offsets }; \ 18 | static int MEMBER_OFFSET_VAR(type,member); 19 | #define MEMBER_OFFSET_COPY(type,member) MEMBER_OFFSET_VAR(type,member) = MEMBER_OFFSET_ARRAY(type,member)[offsetMode] 20 | 21 | 22 | // here are the definitions of the modes and offsets 23 | enum xposedOffsetModes { 24 | MEMBER_OFFSET_MODE_WITH_JIT, 25 | MEMBER_OFFSET_MODE_NO_JIT, 26 | }; 27 | static xposedOffsetModes offsetMode; 28 | const char* xposedOffsetModesDesc[] = { 29 | "WITH_JIT", 30 | "NO_JIT", 31 | }; 32 | 33 | MEMBER_OFFSET_DEFINE(DvmJitGlobals, codeCacheFull, 120, 0) 34 | #define offset_type_DvmJitGlobals_codeCacheFull bool 35 | 36 | 37 | 38 | // helper to determine the required values (compile with XPOSED_SHOW_OFFSET=true) 39 | #ifdef XPOSED_SHOW_OFFSETS 40 | template struct RESULT; 41 | #ifdef WITH_JIT 42 | #pragma message "WITH_JIT is defined" 43 | #else 44 | #pragma message "WITH_JIT is not defined" 45 | #endif 46 | RESULT SIZEOF_Method; 47 | RESULT SIZEOF_Thread; 48 | RESULT OFFSETOF_DvmJitGlobals_codeCacheFull; 49 | #endif 50 | 51 | 52 | -------------------------------------------------------------------------------- /xposed_safemode.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Detects input combinations for recovering from bootloops. 3 | * 4 | * The safemode trigger is detected if exactly one of the physical keys is pressed in 5 | * the first 2 seconds after detection startup (or already held down), and a total of 6 | * 5 consecutive presses of that same key are performed in the subsequent 5 seconds. 7 | * 8 | * 2 short vibrations are performed when the first key is pressed; an additional 9 | * vibration is performed for each subsequent press of the same key, and a final 10 | * long vibration is performed if the trigger was successful. 11 | * 12 | * The initial 2-second delay can be disabled through configuration; in that case, 13 | * one of the keys must already be pressed when the detection starts, otherwise 14 | * the detection fails and no delays are introduced. 15 | * 16 | * References: 17 | * /frameworks/base/services/input/EventHub.cpp (AOSP) 18 | * /include/uapi/linux/input.h (Linux) 19 | * Using the Input Subsystem, Linux Journal 20 | */ 21 | #include "xposed_safemode.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #define INITIAL_DELAY 2 33 | #define DETECTION_TIMEOUT 5 34 | 35 | #define DETECTION_PRESSES 5 36 | 37 | #define VIBRATOR_CONTROL "/sys/class/timed_output/vibrator/enable" 38 | #define VIBRATION_SHORT 150 39 | #define VIBRATION_LONG 500 40 | #define VIBRATION_INTERVAL 200 41 | 42 | static const char *DEVICE_PATH = "/dev/input"; 43 | #define MAX_DEVICES 4 44 | 45 | #define test_bit(bit, array) (array[bit/8] & (1<<(bit%8))) 46 | 47 | static const int physical_keycodes[] = { KEY_VOLUMEDOWN, KEY_VOLUMEUP, KEY_POWER, 48 | KEY_HOME, KEY_BACK, KEY_MENU, KEY_CAMERA }; 49 | 50 | 51 | 52 | static void vibrate(int count, int duration_ms, int interval_ms) { 53 | int fd; 54 | int len; 55 | char value[30]; 56 | 57 | if ((fd = open(VIBRATOR_CONTROL, O_RDWR)) < 0) 58 | // Failed to open the control file, ignore it 59 | return; 60 | 61 | len = sprintf(value, "%d\n", duration_ms); 62 | for (int i = 0; i < count; i++) { 63 | if (i != 0) 64 | // Pause between the several vibrations 65 | usleep((duration_ms + interval_ms) * 1000); 66 | // Vibrate (asynchronously) 67 | write(fd, value, len); 68 | } 69 | close(fd); 70 | } 71 | 72 | 73 | /* 74 | * Enumerates the existing input devices and opens handles for the ones that 75 | * report the relevant keys. 76 | * 77 | * Arguments: 78 | * - *fds: is filled on output with the file handles for the opened devices 79 | * - max_fds: maximum available entries in the fds array 80 | * - *pressedKey: is filled on output with 81 | * 0 if no key was found being held down at this instant 82 | * -1 if more than one key was found being held down 83 | * id of the pressed key, if only a single one was being held down 84 | * Returns: 85 | * - the number of opened device handles, filled in the *fds output parameter 86 | * - 0 if no devices were opened 87 | */ 88 | static int openKeyDevices(int *fds, int max_fds, int *pressedKey) { 89 | char devname[PATH_MAX]; 90 | char *filename; 91 | DIR *dir; 92 | struct dirent *de; 93 | 94 | int count = 0; 95 | // No key was detected as pressed, for the moment 96 | *pressedKey = 0; 97 | 98 | dir = opendir(DEVICE_PATH); 99 | if(dir == NULL) 100 | return 0; 101 | 102 | strcpy(devname, DEVICE_PATH); 103 | filename = devname + strlen(devname); 104 | *filename++ = '/'; 105 | while (count < max_fds && (de = readdir(dir))) { 106 | // Skip '.' and '..' 107 | if(de->d_name[0] == '.' && 108 | (de->d_name[1] == '\0' || 109 | (de->d_name[1] == '.' && de->d_name[2] == '\0'))) 110 | continue; 111 | 112 | strcpy(filename, de->d_name); 113 | int fd = open(devname, O_RDWR | O_CLOEXEC); 114 | if(fd < 0) 115 | // Skip files that could not be opened 116 | continue; 117 | 118 | // Check if this device reports one of the relevant keys 119 | uint8_t keyBitmask[(KEY_MAX + 1) / 8]; 120 | memset(keyBitmask, 0, sizeof(keyBitmask)); 121 | ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBitmask)), keyBitmask); 122 | bool reportsKeys = false; 123 | for (size_t i = 0; i < sizeof(physical_keycodes) / sizeof(physical_keycodes[0]); i++) { 124 | if (test_bit(physical_keycodes[i], keyBitmask)) { 125 | reportsKeys = true; 126 | break; 127 | } 128 | } 129 | if (!reportsKeys) { 130 | // This device doesn't report any of the relevant keys 131 | // Skip to the next one 132 | close(fd); 133 | continue; 134 | } 135 | 136 | fds[count++] = fd; 137 | 138 | // Check if one of the keys is currently pressed on this device, to report it to the caller 139 | memset(keyBitmask, 0, sizeof(keyBitmask)); 140 | ioctl(fd, EVIOCGKEY(sizeof(keyBitmask)), keyBitmask); 141 | for (size_t i = 0; i < sizeof(physical_keycodes) / sizeof(physical_keycodes[0]); i++) { 142 | if (test_bit(physical_keycodes[i], keyBitmask)) { 143 | // One of the relevant keys was detected as held down 144 | // We'll report it to be pressed, but only if there isn't more than one key being pressed 145 | if (*pressedKey == 0) { 146 | // No key was being pressed, this one will be reported 147 | *pressedKey = physical_keycodes[i]; 148 | } else { 149 | // Another key was already found to be pressed, report multiple keys to the caller 150 | *pressedKey = -1; 151 | break; 152 | } 153 | } 154 | } 155 | } 156 | 157 | closedir(dir); 158 | return count; 159 | } 160 | 161 | 162 | /* 163 | * Computes the remaining time, in ms, from the current time to the supplied expiration moment 164 | */ 165 | int getRemainingTime(struct timespec expiration) { 166 | struct timespec now; 167 | clock_gettime(CLOCK_MONOTONIC, &now); 168 | if (now.tv_sec > expiration.tv_sec) 169 | return 0; 170 | else 171 | return (expiration.tv_sec - now.tv_sec) * 1000 + (expiration.tv_nsec - now.tv_nsec) / 1000000; 172 | } 173 | 174 | 175 | 176 | namespace xposed { 177 | 178 | bool detectSafemodeTrigger(bool skipInitialDelay) { 179 | 180 | int efd = -1; 181 | int fds[MAX_DEVICES]; 182 | int deviceCount = 0; 183 | int pressedKey = 0; 184 | int triggerPresses = 0; 185 | bool result = false; 186 | 187 | // Open input devices that report one of the relevant physical keys 188 | deviceCount = openKeyDevices(fds, sizeof(fds) / sizeof(fds[0]), &pressedKey); 189 | if (deviceCount == 0) 190 | // No input devices found, abort detection 191 | goto leave; 192 | 193 | if (pressedKey < 0) 194 | // More than one key was initially pressed 195 | // Immediately report a negative detection, with no further delays 196 | goto leave; 197 | 198 | if (pressedKey == 0 && skipInitialDelay) 199 | // A single key wasn't held down and the initial delay is disabled 200 | // Immediately report a negative detection, with no further delays 201 | goto leave; 202 | 203 | // Prepare waiting mechanism for received events in all devices 204 | if ((efd = epoll_create(deviceCount)) < 0) 205 | // Failed to create the epoll handle, abort 206 | goto leave; 207 | 208 | // Register each device descriptor in the epoll handle 209 | struct epoll_event eventPollItems[MAX_DEVICES]; 210 | for (int i = 0; i < deviceCount; i++) { 211 | memset(&eventPollItems[i], 0, sizeof(eventPollItems[i])); 212 | eventPollItems[i].events = EPOLLIN; 213 | eventPollItems[i].data.fd = fds[i]; 214 | if (epoll_ctl(efd, EPOLL_CTL_ADD, fds[i], &eventPollItems[i])) 215 | // Failed to add device descriptor to the epoll handle, abort 216 | goto leave; 217 | } 218 | 219 | int timeout_ms; 220 | struct timespec expiration; 221 | clock_gettime(CLOCK_MONOTONIC, &expiration); 222 | expiration.tv_sec += INITIAL_DELAY; 223 | 224 | // Wait up to INITIAL_DELAY seconds for an initial keypress, it no key was initially down 225 | while (pressedKey == 0 && (timeout_ms = getRemainingTime(expiration)) > 0) { 226 | // Wait for next input event in one of the opened devices 227 | int pollResult = epoll_wait(efd, eventPollItems, sizeof(eventPollItems) / sizeof(eventPollItems[0]), timeout_ms); 228 | if (pollResult < 0) 229 | // Failed to wait for event, abort 230 | goto leave; 231 | 232 | // Loop through the opened devices where a new event is available 233 | for (int i = 0; i < pollResult; i++) { 234 | struct input_event evt; 235 | int32_t readSize = read(eventPollItems[i].data.fd, &evt, sizeof(evt)); 236 | if (readSize != sizeof(evt)) 237 | // Invalid size read, ignore 238 | continue; 239 | 240 | if (evt.type != EV_KEY) 241 | // Only consider key events 242 | continue; 243 | if (evt.value != 1) 244 | // Ignore key releases, we're monitoring presses 245 | continue; 246 | 247 | for (size_t j = 0; j < sizeof(physical_keycodes) / sizeof(physical_keycodes[0]); j++) { 248 | if (evt.code == physical_keycodes[j]) { 249 | // One of the keys was pressed, end the initial detection 250 | // No need to check for duplicate keys, as the events are reported sequentially 251 | // and multiple presses can't be reported at once 252 | pressedKey = evt.code; 253 | break; 254 | } 255 | } 256 | } 257 | } 258 | if (pressedKey == 0) 259 | // No key was pressed during the initial delay or upfront, so the detection has failed 260 | goto leave; 261 | 262 | // Notify the user that the safemode sequence has been started and we're waiting for 263 | // the remaining key presses 264 | vibrate(2, VIBRATION_SHORT, VIBRATION_INTERVAL); 265 | 266 | 267 | // Detection will wait at most DETECTION_TIMEOUT seconds 268 | clock_gettime(CLOCK_MONOTONIC, &expiration); 269 | expiration.tv_sec += DETECTION_TIMEOUT; 270 | 271 | // Initial key press is counted as well 272 | triggerPresses++; 273 | 274 | // Loop waiting for the same key to be pressed the appropriate number of times, a different key to 275 | // be pressed, or the timeout to be reached 276 | while (triggerPresses < DETECTION_PRESSES && (timeout_ms = getRemainingTime(expiration)) > 0) { 277 | // Wait for next input event 278 | int pollResult = epoll_wait(efd, eventPollItems, sizeof(eventPollItems) / sizeof(eventPollItems[0]), timeout_ms); 279 | if (pollResult < 0) 280 | // Failed to wait for event, abort 281 | goto leave; 282 | 283 | // Loop through the opened devices where a new event is available 284 | for (int i = 0; i < pollResult; i++) { 285 | struct input_event evt; 286 | int32_t readSize = read(eventPollItems[i].data.fd, &evt, sizeof(evt)); 287 | if (readSize != sizeof(evt)) 288 | // Invalid size read, ignore 289 | continue; 290 | 291 | if (evt.type != EV_KEY) 292 | // Only consider key events 293 | continue; 294 | if (evt.value != 1) 295 | // Ignore key releases, we're monitoring presses 296 | continue; 297 | 298 | for (size_t j = 0; j < sizeof(physical_keycodes) / sizeof(physical_keycodes[0]); j++) { 299 | if (evt.code == physical_keycodes[j]) { 300 | if (pressedKey == evt.code) { 301 | // The same key was pressed again, increment the counter and notify the user 302 | triggerPresses++; 303 | // The final key press will be confirmed with a long vibration later 304 | if (triggerPresses < DETECTION_PRESSES) 305 | vibrate(1, VIBRATION_SHORT, 0); 306 | } else { 307 | // A key was pressed other than the initial one 308 | // Abort the detection and avoid further delays 309 | goto leave; 310 | } 311 | break; 312 | } 313 | } 314 | } 315 | } 316 | 317 | // Was safemode successfully triggered? 318 | if (triggerPresses >= DETECTION_PRESSES) { 319 | vibrate(1, VIBRATION_LONG, 0); 320 | result = true; 321 | } 322 | 323 | leave: 324 | if (efd >= 0) 325 | close(efd); 326 | for (int i = 0; i < deviceCount; i++) 327 | close(fds[i]); 328 | 329 | return result; 330 | 331 | } 332 | 333 | } 334 | 335 | -------------------------------------------------------------------------------- /xposed_safemode.h: -------------------------------------------------------------------------------- 1 | #ifndef XPOSED_SAFEMODE_H_ 2 | #define XPOSED_SAFEMODE_H_ 3 | 4 | namespace xposed { 5 | 6 | bool detectSafemodeTrigger(bool skipInitialDelay); 7 | 8 | } 9 | 10 | #endif // XPOSED_SAFEMODE_H_ 11 | 12 | -------------------------------------------------------------------------------- /xposed_service.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes the Xposed services, which are especially used to work around SELinux restrictions. 3 | */ 4 | 5 | #define LOG_TAG "Xposed" 6 | 7 | #include "xposed.h" 8 | #include "xposed_service.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #define __STDC_FORMAT_MACROS 19 | #include 20 | #include 21 | 22 | #define UID_SYSTEM 1000 23 | 24 | using namespace android; 25 | 26 | namespace xposed { 27 | namespace service { 28 | 29 | //////////////////////////////////////////////////////////// 30 | // Declarations 31 | //////////////////////////////////////////////////////////// 32 | 33 | bool running = false; 34 | 35 | 36 | //////////////////////////////////////////////////////////// 37 | // Memory-based communication (used by Zygote) 38 | //////////////////////////////////////////////////////////// 39 | 40 | namespace membased { 41 | 42 | enum State { 43 | STATE_NOT_RUNNING, 44 | STATE_IDLE, 45 | STATE_SERVICE_ACTION, 46 | STATE_SERVER_RESPONSE, 47 | }; 48 | 49 | enum Action { 50 | OP_NONE, 51 | OP_ACCESS_FILE, 52 | OP_STAT_FILE, 53 | OP_READ_FILE, 54 | }; 55 | 56 | struct AccessFileData { 57 | // in 58 | char path[PATH_MAX]; 59 | int mode; 60 | // out 61 | int result; 62 | }; 63 | 64 | struct StatFileData { 65 | // in 66 | char path[PATH_MAX]; 67 | // inout 68 | struct stat st; 69 | // out 70 | int result; 71 | }; 72 | 73 | struct ReadFileData { 74 | // in 75 | char path[PATH_MAX]; 76 | int offset; 77 | // out 78 | int totalSize; 79 | int bytesRead; 80 | bool eof; 81 | char content[32*1024]; 82 | }; 83 | 84 | struct MemBasedState { 85 | pthread_mutex_t workerMutex; 86 | pthread_cond_t workerCond; 87 | State state; 88 | Action action; 89 | int error; 90 | union { 91 | AccessFileData accessFile; 92 | StatFileData statFile; 93 | ReadFileData readFile; 94 | } data; 95 | }; 96 | 97 | MemBasedState* shared = NULL; 98 | pid_t zygotePid = 0; 99 | bool canAlwaysAccessService = false; 100 | 101 | inline static void initSharedMutex(pthread_mutex_t* mutex) { 102 | pthread_mutexattr_t attr; 103 | pthread_mutexattr_init(&attr); 104 | pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); 105 | pthread_mutex_init(mutex, &attr); 106 | pthread_mutexattr_destroy(&attr); 107 | } 108 | 109 | inline static void initSharedCond(pthread_cond_t* cond) { 110 | pthread_condattr_t cattr; 111 | pthread_condattr_init(&cattr); 112 | pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED); 113 | pthread_cond_init(cond, &cattr); 114 | pthread_condattr_destroy(&cattr); 115 | } 116 | 117 | static bool init() { 118 | shared = (MemBasedState*) mmap(NULL, sizeof(MemBasedState), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); 119 | if (shared == MAP_FAILED) { 120 | ALOGE("Could not allocate memory for Zygote service: %s", strerror(errno)); 121 | shared = NULL; 122 | return false; 123 | } 124 | 125 | zygotePid = getpid(); 126 | canAlwaysAccessService = true; 127 | 128 | initSharedMutex(&shared->workerMutex); 129 | initSharedCond(&shared->workerCond); 130 | shared->state = STATE_NOT_RUNNING; 131 | shared->action = OP_NONE; 132 | shared->error = 0; 133 | return true; 134 | } 135 | 136 | void restrictMemoryInheritance() { 137 | madvise(shared, sizeof(MemBasedState), MADV_DONTFORK); 138 | canAlwaysAccessService = false; 139 | } 140 | 141 | static inline bool isServiceAccessible() { 142 | if (!canAlwaysAccessService && (shared == NULL || zygotePid != getpid())) { 143 | ALOGE("Zygote service is not accessible from PID %d, UID %d", getpid(), getuid()); 144 | shared = NULL; 145 | errno = EPERM; 146 | return false; 147 | } 148 | return true; 149 | } 150 | 151 | // Server implementation 152 | void* looper(void* unused __attribute__((unused))) { 153 | pthread_mutex_lock(&shared->workerMutex); 154 | shared->state = STATE_IDLE; 155 | pthread_cond_broadcast(&shared->workerCond); 156 | while (1) { 157 | while (shared->state != STATE_SERVICE_ACTION) { 158 | pthread_cond_wait(&shared->workerCond, &shared->workerMutex); 159 | } 160 | 161 | switch (shared->action) { 162 | case OP_ACCESS_FILE: { 163 | struct AccessFileData* data = &shared->data.accessFile; 164 | data->result = TEMP_FAILURE_RETRY(access(data->path, data->mode)); 165 | if (data->result != 0) { 166 | shared->error = errno; 167 | } 168 | } break; 169 | 170 | case OP_STAT_FILE: { 171 | struct StatFileData* data = &shared->data.statFile; 172 | data->result = TEMP_FAILURE_RETRY(stat(data->path, &data->st)); 173 | if (data->result != 0) { 174 | shared->error = errno; 175 | } 176 | } break; 177 | 178 | case OP_READ_FILE: { 179 | struct ReadFileData* data = &shared->data.readFile; 180 | struct stat st; 181 | 182 | if (stat(data->path, &st) != 0) { 183 | shared->error = errno; 184 | break; 185 | } 186 | 187 | data->totalSize = st.st_size; 188 | 189 | FILE *f = fopen(data->path, "r"); 190 | if (f == NULL) { 191 | shared->error = errno; 192 | break; 193 | } 194 | 195 | if (data->offset > 0 && fseek(f, data->offset, SEEK_SET) != 0) { 196 | shared->error = ferror(f); 197 | fclose(f); 198 | break; 199 | } 200 | 201 | data->bytesRead = fread(data->content, 1, sizeof(data->content), f); 202 | shared->error = ferror(f); 203 | data->eof = feof(f); 204 | 205 | fclose(f); 206 | } break; 207 | 208 | case OP_NONE: { 209 | ALOGE("No-op call to membased service"); 210 | break; 211 | } 212 | 213 | default: { 214 | ALOGE("Invalid action in call to membased service"); 215 | break; 216 | } 217 | } 218 | 219 | shared->state = STATE_SERVER_RESPONSE; 220 | pthread_cond_broadcast(&shared->workerCond); 221 | } 222 | 223 | pthread_mutex_unlock(&shared->workerMutex); 224 | return NULL; 225 | } 226 | 227 | // Client implementation 228 | static inline bool waitForRunning(int timeout) { 229 | if (shared == NULL || timeout < 0) 230 | return false; 231 | 232 | struct timespec ts; 233 | clock_gettime(CLOCK_REALTIME, &ts); 234 | ts.tv_sec += 5; 235 | int rc = 0; 236 | pthread_mutex_lock(&shared->workerMutex); 237 | while (shared->state == STATE_NOT_RUNNING && rc == 0) { 238 | rc = pthread_cond_timedwait(&shared->workerCond, &shared->workerMutex, &ts); 239 | } 240 | pthread_mutex_unlock(&shared->workerMutex); 241 | return rc == 0; 242 | } 243 | 244 | static inline void waitForIdle() { 245 | pthread_mutex_lock(&shared->workerMutex); 246 | while (shared->state != STATE_IDLE) { 247 | pthread_cond_wait(&shared->workerCond, &shared->workerMutex); 248 | } 249 | } 250 | 251 | static inline void callService(Action action) { 252 | shared->action = action; 253 | shared->state = STATE_SERVICE_ACTION; 254 | shared->error = 0; 255 | pthread_cond_broadcast(&shared->workerCond); 256 | 257 | while (shared->state != STATE_SERVER_RESPONSE) { 258 | pthread_cond_wait(&shared->workerCond, &shared->workerMutex); 259 | } 260 | } 261 | 262 | static inline void makeIdle() { 263 | shared->action = OP_NONE; 264 | shared->state = STATE_IDLE; 265 | pthread_cond_broadcast(&shared->workerCond); 266 | pthread_mutex_unlock(&shared->workerMutex); 267 | } 268 | 269 | int accessFile(const char* path, int mode) { 270 | if (!isServiceAccessible()) 271 | return -1; 272 | 273 | if (strlen(path) > sizeof(AccessFileData::path) - 1) { 274 | errno = ENAMETOOLONG; 275 | return -1; 276 | } 277 | 278 | waitForIdle(); 279 | 280 | struct AccessFileData* data = &shared->data.accessFile; 281 | strcpy(data->path, path); 282 | data->mode = mode; 283 | 284 | callService(OP_ACCESS_FILE); 285 | 286 | makeIdle(); 287 | errno = shared->error; 288 | return shared->error ? -1 : data->result; 289 | } 290 | 291 | int statFile(const char* path, struct stat* st) { 292 | if (!isServiceAccessible()) 293 | return -1; 294 | 295 | if (strlen(path) > sizeof(StatFileData::path) - 1) { 296 | errno = ENAMETOOLONG; 297 | return -1; 298 | } 299 | 300 | waitForIdle(); 301 | 302 | struct StatFileData* data = &shared->data.statFile; 303 | strcpy(data->path, path); 304 | 305 | callService(OP_STAT_FILE); 306 | 307 | memcpy(st, &data->st, sizeof(struct stat)); 308 | 309 | makeIdle(); 310 | errno = shared->error; 311 | return shared->error ? -1 : data->result; 312 | } 313 | 314 | char* readFile(const char* path, int* bytesRead) { 315 | if (!isServiceAccessible()) 316 | return NULL; 317 | 318 | char* result = NULL; 319 | int offset = 0, totalSize = 0; 320 | 321 | if (bytesRead) 322 | *bytesRead = 0; 323 | 324 | if (strlen(path) > sizeof(ReadFileData::path) - 1) { 325 | errno = ENAMETOOLONG; 326 | return NULL; 327 | } 328 | 329 | waitForIdle(); 330 | 331 | struct ReadFileData* data = &shared->data.readFile; 332 | strcpy(data->path, path); 333 | data->offset = 0; 334 | 335 | callService(OP_READ_FILE); 336 | if (shared->error) 337 | goto bail; 338 | 339 | totalSize = data->totalSize; 340 | result = (char*) malloc(totalSize + 1); 341 | result[totalSize] = 0; 342 | memcpy(result, data->content, data->bytesRead); 343 | 344 | while (!data->eof) { 345 | offset += data->bytesRead; 346 | data->offset = offset; 347 | 348 | callService(OP_READ_FILE); 349 | if (shared->error) 350 | goto bail; 351 | 352 | if (offset + data->bytesRead > totalSize) { 353 | shared->error = EBUSY; 354 | goto bail; 355 | } 356 | 357 | memcpy(result + offset, data->content, data->bytesRead); 358 | } 359 | 360 | if (bytesRead) 361 | *bytesRead = offset + data->bytesRead; 362 | 363 | bail: 364 | makeIdle(); 365 | if (shared->error && result) { 366 | free(result); 367 | result = NULL; 368 | } 369 | errno = shared->error; 370 | return result; 371 | } 372 | 373 | } // namespace membased 374 | 375 | 376 | //////////////////////////////////////////////////////////// 377 | // Binder service 378 | //////////////////////////////////////////////////////////// 379 | 380 | namespace binder { 381 | 382 | #define XPOSED_BINDER_SYSTEM_SERVICE_NAME "user.xposed.system" 383 | #define XPOSED_BINDER_APP_SERVICE_NAME "user.xposed.app" 384 | 385 | class IXposedService: public IInterface { 386 | public: 387 | DECLARE_META_INTERFACE(XposedService); 388 | virtual int test() const = 0; 389 | virtual status_t addService(const String16& name, 390 | const sp& service, 391 | bool allowIsolated = false) const = 0; 392 | virtual int accessFile(const String16& filename, 393 | int32_t mode) const = 0; 394 | virtual int statFile(const String16& filename, 395 | int64_t* size, 396 | int64_t* mtime) const = 0; 397 | virtual status_t readFile(const String16& filename, 398 | int32_t offset, 399 | int32_t length, 400 | int64_t* size, 401 | int64_t* mtime, 402 | uint8_t** buffer, 403 | int32_t* bytesRead, 404 | String16* errormsg) const = 0; 405 | 406 | enum { 407 | TEST_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION, 408 | ADD_SERVICE_TRANSACTION, 409 | ACCESS_FILE_TRANSACTION, 410 | STAT_FILE_TRANSACTION, 411 | READ_FILE_TRANSACTION, 412 | }; 413 | }; 414 | 415 | class BnXposedService: public BnInterface { 416 | public: 417 | virtual status_t onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0); 418 | }; 419 | 420 | class BpXposedService: public BpInterface { 421 | public: 422 | BpXposedService(const sp& impl) : BpInterface(impl) {} 423 | 424 | virtual int test() const { 425 | Parcel data, reply; 426 | data.writeInterfaceToken(IXposedService::getInterfaceDescriptor()); 427 | remote()->transact(TEST_TRANSACTION, data, &reply); 428 | if (reply.readExceptionCode() != 0) return -1; 429 | return reply.readInt32(); 430 | } 431 | 432 | virtual status_t addService(const String16& name, const sp& service, 433 | bool allowIsolated = false) const { 434 | Parcel data, reply; 435 | data.writeInterfaceToken(IXposedService::getInterfaceDescriptor()); 436 | data.writeString16(name); 437 | data.writeStrongBinder(service); 438 | data.writeInt32(allowIsolated ? 1 : 0); 439 | status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply); 440 | return err == NO_ERROR ? reply.readExceptionCode() : err; 441 | } 442 | 443 | virtual status_t accessFile(const String16& name, int32_t mode) const { 444 | Parcel data, reply; 445 | data.writeInterfaceToken(IXposedService::getInterfaceDescriptor()); 446 | data.writeString16(name); 447 | data.writeInt32(mode); 448 | 449 | remote()->transact(ACCESS_FILE_TRANSACTION, data, &reply); 450 | if (reply.readExceptionCode() != 0) return -1; 451 | 452 | errno = reply.readInt32(); 453 | return (errno == 0) ? 0 : -1; 454 | } 455 | 456 | virtual status_t statFile(const String16& name, int64_t* size, int64_t* mtime) const { 457 | Parcel data, reply; 458 | data.writeInterfaceToken(IXposedService::getInterfaceDescriptor()); 459 | data.writeString16(name); 460 | 461 | remote()->transact(STAT_FILE_TRANSACTION, data, &reply); 462 | if (reply.readExceptionCode() != 0) return -1; 463 | 464 | errno = reply.readInt32(); 465 | if (errno != 0) return -1; 466 | 467 | int64_t size1 = reply.readInt64(); 468 | int64_t mtime1 = reply.readInt64(); 469 | if (size != NULL) *size = size1; 470 | if (mtime != NULL) *mtime = mtime1; 471 | return 0; 472 | } 473 | 474 | virtual status_t readFile(const String16& filename, int32_t offset, int32_t length, 475 | int64_t* size, int64_t* mtime, uint8_t** buffer, int32_t* bytesRead, String16* errormsg) const { 476 | Parcel data, reply; 477 | data.writeInterfaceToken(IXposedService::getInterfaceDescriptor()); 478 | data.writeString16(filename); 479 | data.writeInt32(offset); 480 | data.writeInt32(length); 481 | int64_t size1 = 0; 482 | int64_t mtime1 = 0; 483 | if (size != NULL) size1 = *size; 484 | if (mtime != NULL) mtime1 = *mtime; 485 | data.writeInt64(size1); 486 | data.writeInt64(mtime1); 487 | 488 | remote()->transact(READ_FILE_TRANSACTION, data, &reply); 489 | if (reply.readExceptionCode() != 0) return -1; 490 | 491 | status_t err = reply.readInt32(); 492 | const String16& errormsg1(reply.readString16()); 493 | size1 = reply.readInt64(); 494 | mtime1 = reply.readInt64(); 495 | int32_t bytesRead1 = reply.readInt32(); 496 | if (size != NULL) *size = size1; 497 | if (mtime != NULL) *mtime = mtime1; 498 | if (bytesRead != NULL) *bytesRead = bytesRead1; 499 | if (errormsg) *errormsg = errormsg1; 500 | 501 | if (bytesRead1 > 0 && bytesRead1 <= (int32_t)reply.dataAvail()) { 502 | *buffer = (uint8_t*) malloc(bytesRead1 + 1); 503 | *buffer[bytesRead1] = 0; 504 | reply.read(*buffer, bytesRead1); 505 | } else { 506 | *buffer = NULL; 507 | } 508 | 509 | errno = err; 510 | return (errno == 0) ? 0 : -1; 511 | } 512 | }; 513 | 514 | IMPLEMENT_META_INTERFACE(XposedService, "de.robv.android.xposed.IXposedService"); 515 | 516 | status_t BnXposedService::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { 517 | switch (code) { 518 | case TEST_TRANSACTION: { 519 | CHECK_INTERFACE(IXposedService, data, reply); 520 | reply->writeNoException(); 521 | reply->writeInt32(test()); 522 | return NO_ERROR; 523 | } break; 524 | 525 | case ADD_SERVICE_TRANSACTION: { 526 | CHECK_INTERFACE(IXposedService, data, reply); 527 | String16 which = data.readString16(); 528 | sp b = data.readStrongBinder(); 529 | bool allowIsolated = (data.readInt32() != 0); 530 | reply->writeInt32(addService(which, b, allowIsolated)); 531 | return NO_ERROR; 532 | } break; 533 | 534 | case ACCESS_FILE_TRANSACTION: { 535 | CHECK_INTERFACE(IXposedService, data, reply); 536 | String16 filename = data.readString16(); 537 | int32_t mode = data.readInt32(); 538 | status_t result = accessFile(filename, mode); 539 | int err = errno; 540 | reply->writeNoException(); 541 | reply->writeInt32(result == 0 ? 0 : err); 542 | return NO_ERROR; 543 | } break; 544 | 545 | case STAT_FILE_TRANSACTION: { 546 | CHECK_INTERFACE(IXposedService, data, reply); 547 | String16 filename = data.readString16(); 548 | int64_t size, time; 549 | status_t result = statFile(filename, &size, &time); 550 | int err = errno; 551 | reply->writeNoException(); 552 | if (result == 0) { 553 | reply->writeInt32(0); 554 | reply->writeInt64(size); 555 | reply->writeInt64(time); 556 | } else { 557 | reply->writeInt32(err); 558 | } 559 | return NO_ERROR; 560 | } break; 561 | 562 | case READ_FILE_TRANSACTION: { 563 | CHECK_INTERFACE(IXposedService, data, reply); 564 | String16 filename = data.readString16(); 565 | int32_t offset = data.readInt32(); 566 | int32_t length = data.readInt32(); 567 | int64_t size = data.readInt64(); 568 | int64_t mtime = data.readInt64(); 569 | uint8_t* buffer = NULL; 570 | int32_t bytesRead = -1; 571 | String16 errormsg; 572 | 573 | status_t err = readFile(filename, offset, length, &size, &mtime, &buffer, &bytesRead, &errormsg); 574 | 575 | reply->writeNoException(); 576 | reply->writeInt32(err); 577 | reply->writeString16(errormsg); 578 | reply->writeInt64(size); 579 | reply->writeInt64(mtime); 580 | if (bytesRead > 0) { 581 | reply->writeInt32(bytesRead); 582 | reply->write(buffer, bytesRead); 583 | free(buffer); 584 | } else { 585 | reply->writeInt32(bytesRead); // empty array (0) or null (-1) 586 | } 587 | return NO_ERROR; 588 | } break; 589 | 590 | default: 591 | return BBinder::onTransact(code, data, reply, flags); 592 | } 593 | } 594 | 595 | class XposedService : public BnXposedService { 596 | public: 597 | XposedService(bool system); 598 | 599 | virtual int test() const; 600 | virtual status_t addService(const String16& name, 601 | const sp& service, 602 | bool allowIsolated = false) const; 603 | virtual status_t accessFile(const String16& filename16, 604 | int32_t mode) const; 605 | virtual status_t statFile(const String16& filename, 606 | int64_t* size, 607 | int64_t* mtime) const; 608 | virtual status_t readFile(const String16& filename16, 609 | int32_t offset, 610 | int32_t length, 611 | int64_t* size, 612 | int64_t* mtime, 613 | uint8_t** buffer, 614 | int32_t* bytesRead, 615 | String16* errormsg) const; 616 | 617 | private: 618 | bool isSystem; 619 | }; 620 | 621 | static String16 formatToString16(const char* fmt, ...) { 622 | char* message; 623 | va_list args; 624 | va_start(args, fmt); 625 | int size = vasprintf(&message, fmt, args); 626 | String16 result(message, size); 627 | free(message); 628 | va_end(args); 629 | return result; 630 | } 631 | 632 | XposedService::XposedService(bool system) 633 | : isSystem(system) {} 634 | 635 | int XposedService::test() const { 636 | pid_t pid = IPCThreadState::self()->getCallingPid(); 637 | ALOGD("This is PID %d, test method was called from PID %d", getpid(), pid); 638 | return getpid(); 639 | } 640 | 641 | status_t XposedService::addService(const String16& name, const sp& service, 642 | bool allowIsolated) const { 643 | uid_t uid = IPCThreadState::self()->getCallingUid(); 644 | if (!isSystem || (uid != 0)) { 645 | ALOGE("Permission denied, not adding service %s", String8(name).string()); 646 | errno = EPERM; 647 | return -1; 648 | } 649 | sp sm = defaultServiceManager(); 650 | #if PLATFORM_SDK_VERSION >= 16 651 | return sm->addService(name, service, allowIsolated); 652 | #else 653 | return sm->addService(name, service); 654 | #endif 655 | } 656 | 657 | status_t XposedService::accessFile(const String16& filename16, int32_t mode) const { 658 | uid_t caller = IPCThreadState::self()->getCallingUid(); 659 | if (caller != UID_SYSTEM) { 660 | ALOGE("UID %d is not allowed to use the Xposed service", caller); 661 | errno = EPERM; 662 | return -1; 663 | } 664 | const char* filename = String8(filename16).string(); 665 | return TEMP_FAILURE_RETRY(access(filename, mode)); 666 | } 667 | 668 | status_t XposedService::statFile(const String16& filename16, int64_t* size, int64_t* time) const { 669 | uid_t caller = IPCThreadState::self()->getCallingUid(); 670 | if (caller != UID_SYSTEM) { 671 | ALOGE("UID %d is not allowed to use the Xposed service", caller); 672 | errno = EPERM; 673 | return -1; 674 | } 675 | const char* filename = String8(filename16).string(); 676 | struct stat st; 677 | status_t result = TEMP_FAILURE_RETRY(stat(filename, &st)); 678 | if (result == 0) { 679 | *size = st.st_size; 680 | *time = st.st_mtime; 681 | } 682 | return result; 683 | } 684 | 685 | status_t XposedService::readFile(const String16& filename16, int32_t offset, int32_t length, 686 | int64_t* size, int64_t* mtime, uint8_t** buffer, int32_t* bytesRead, String16* errormsg) const { 687 | 688 | uid_t caller = IPCThreadState::self()->getCallingUid(); 689 | if (caller != UID_SYSTEM) { 690 | ALOGE("UID %d is not allowed to use the Xposed service", caller); 691 | return EPERM; 692 | } 693 | 694 | *buffer = NULL; 695 | *bytesRead = -1; 696 | 697 | // Get file metadata 698 | const char* filename = String8(filename16).string(); 699 | struct stat st; 700 | if (stat(filename, &st) != 0) { 701 | status_t err = errno; 702 | if (errormsg) *errormsg = formatToString16("%s during stat() on %s", strerror(err), filename); 703 | return err; 704 | } 705 | 706 | if (S_ISDIR(st.st_mode)) { 707 | if (errormsg) *errormsg = formatToString16("%s is a directory", filename); 708 | return EISDIR; 709 | } 710 | 711 | // Don't load again if file is unchanged 712 | if (*size == st.st_size && *mtime == (int32_t)st.st_mtime) { 713 | return 0; 714 | } 715 | 716 | *size = st.st_size; 717 | *mtime = st.st_mtime; 718 | 719 | // Check range 720 | if (offset > 0 && offset >= *size) { 721 | if (errormsg) *errormsg = formatToString16("offset %d >= size %" PRId64 " for %s", offset, *size, filename); 722 | return EINVAL; 723 | } else if (offset < 0) { 724 | offset = 0; 725 | } 726 | 727 | if (length > 0 && (offset + length) > *size) { 728 | if (errormsg) *errormsg = formatToString16("offset %d + length %d > size %" PRId64 " for %s", offset, length, *size, filename); 729 | return EINVAL; 730 | } else if (*size == 0) { 731 | *bytesRead = 0; 732 | return 0; 733 | } else if (length <= 0) { 734 | length = *size - offset; 735 | } 736 | 737 | // Allocate buffer 738 | *buffer = (uint8_t*) malloc(length + 1); 739 | if (*buffer == NULL) { 740 | if (errormsg) *errormsg = formatToString16("allocating buffer with %d bytes failed", length + 1); 741 | return ENOMEM; 742 | } 743 | (*buffer)[length] = 0; 744 | 745 | // Open file 746 | FILE *f = fopen(filename, "r"); 747 | if (f == NULL) { 748 | status_t err = errno; 749 | free(*buffer); 750 | *buffer = NULL; 751 | if (errormsg) *errormsg = formatToString16("%s during fopen() on %s", strerror(err), filename); 752 | return err; 753 | } 754 | 755 | // Seek to correct offset 756 | if (offset > 0 && fseek(f, offset, SEEK_SET) != 0) { 757 | free(*buffer); 758 | *buffer = NULL; 759 | status_t err = ferror(f); 760 | fclose(f); 761 | if (errormsg) *errormsg = formatToString16("%s during fseek() to offset %d for %s", strerror(err), offset, filename); 762 | return err; 763 | } 764 | 765 | // Read the file 766 | *bytesRead = fread(*buffer, 1, length, f); 767 | status_t err = ferror(f); 768 | if (err != 0) { 769 | free(*buffer); 770 | *buffer = NULL; 771 | *bytesRead = -1; 772 | if (errormsg) *errormsg = formatToString16("%s during fread(), read %d bytes for %s", strerror(err), *bytesRead, filename); 773 | } 774 | 775 | // Close the file 776 | fclose(f); 777 | 778 | return err; 779 | } 780 | 781 | } // namespace binder 782 | 783 | 784 | //////////////////////////////////////////////////////////// 785 | // General 786 | //////////////////////////////////////////////////////////// 787 | 788 | static void systemService() { 789 | xposed::setProcessName("xposed_service_system"); 790 | xposed::dropCapabilities(); 791 | 792 | #if XPOSED_WITH_SELINUX 793 | if (xposed->isSELinuxEnabled) { 794 | if (setcon(ctx_system) != 0) { 795 | ALOGE("Could not switch to %s context", ctx_system); 796 | exit(EXIT_FAILURE); 797 | } 798 | } 799 | #endif // XPOSED_WITH_SELINUX 800 | 801 | // Initialize the system service 802 | sp sm(defaultServiceManager()); 803 | #if PLATFORM_SDK_VERSION >= 16 804 | status_t err = sm->addService(String16(XPOSED_BINDER_SYSTEM_SERVICE_NAME), new binder::XposedService(true), true); 805 | #else 806 | status_t err = sm->addService(String16(XPOSED_BINDER_SYSTEM_SERVICE_NAME), new binder::XposedService(true)); 807 | #endif 808 | if (err != NO_ERROR) { 809 | ALOGE("Error %d while adding system service %s", err, XPOSED_BINDER_SYSTEM_SERVICE_NAME); 810 | exit(EXIT_FAILURE); 811 | } 812 | 813 | sp ps(ProcessState::self()); 814 | ps->startThreadPool(); 815 | #if PLATFORM_SDK_VERSION >= 18 816 | ps->giveThreadPoolName(); 817 | #endif 818 | IPCThreadState::self()->joinThreadPool(); 819 | } 820 | 821 | static void appService(bool useSingleProcess) { 822 | xposed::setProcessName(useSingleProcess ? "xposed_service" : "xposed_service_app"); 823 | xposed::dropCapabilities(); 824 | 825 | #if XPOSED_WITH_SELINUX 826 | if (xposed->isSELinuxEnabled) { 827 | if (setcon(ctx_app) != 0) { 828 | ALOGE("Could not switch to %s context", ctx_app); 829 | exit(EXIT_FAILURE); 830 | } 831 | } 832 | #endif // XPOSED_WITH_SELINUX 833 | 834 | sp sm(defaultServiceManager()); 835 | status_t err; 836 | if (useSingleProcess) { 837 | // Initialize the system service here as this is the only service process 838 | #if PLATFORM_SDK_VERSION >= 16 839 | err = sm->addService(String16(XPOSED_BINDER_SYSTEM_SERVICE_NAME), new binder::XposedService(true), true); 840 | #else 841 | err = sm->addService(String16(XPOSED_BINDER_SYSTEM_SERVICE_NAME), new binder::XposedService(true)); 842 | #endif 843 | if (err != NO_ERROR) { 844 | ALOGE("Error %d while adding system service %s", err, XPOSED_BINDER_SYSTEM_SERVICE_NAME); 845 | exit(EXIT_FAILURE); 846 | } 847 | 848 | // The app service can be registered directly 849 | #if PLATFORM_SDK_VERSION >= 16 850 | err = sm->addService(String16(XPOSED_BINDER_APP_SERVICE_NAME), new binder::XposedService(false), true); 851 | #else 852 | err = sm->addService(String16(XPOSED_BINDER_APP_SERVICE_NAME), new binder::XposedService(false)); 853 | #endif 854 | } else { 855 | // We have to register the app service by using the already running system service as a proxy 856 | sp systemBinder = sm->getService(String16(XPOSED_BINDER_SYSTEM_SERVICE_NAME)); 857 | sp xposedSystemService = interface_cast(systemBinder); 858 | err = xposedSystemService->addService(String16(XPOSED_BINDER_APP_SERVICE_NAME), new binder::XposedService(false), true); 859 | } 860 | 861 | // Check result for the app service registration 862 | if (err != NO_ERROR) { 863 | ALOGE("Error %d while adding app service %s", err, XPOSED_BINDER_APP_SERVICE_NAME); 864 | exit(EXIT_FAILURE); 865 | } 866 | 867 | #if XPOSED_WITH_SELINUX 868 | // Initialize the memory-based Zygote service 869 | if (xposed->isSELinuxEnabled) { 870 | pthread_t thMemBased; 871 | if (pthread_create(&thMemBased, NULL, &membased::looper, NULL) != 0) { 872 | ALOGE("Could not create thread for memory-based service: %s", strerror(errno)); 873 | exit(EXIT_FAILURE); 874 | } 875 | } 876 | #endif // XPOSED_WITH_SELINUX 877 | 878 | sp ps(ProcessState::self()); 879 | ps->startThreadPool(); 880 | #if PLATFORM_SDK_VERSION >= 18 881 | ps->giveThreadPoolName(); 882 | #endif 883 | IPCThreadState::self()->joinThreadPool(); 884 | } 885 | 886 | bool checkMembasedRunning() { 887 | // Ensure that the memory based service is running 888 | if (!membased::waitForRunning(5)) { 889 | ALOGE("Xposed's Zygote service is not running, cannot work without it"); 890 | return false; 891 | } 892 | 893 | return true; 894 | } 895 | 896 | bool startAll() { 897 | bool useSingleProcess = !xposed->isSELinuxEnabled; 898 | if (xposed->isSELinuxEnabled && !membased::init()) { 899 | return false; 900 | } 901 | 902 | // system context service 903 | pid_t pid; 904 | if (useSingleProcess) { 905 | ALOGD("Using a single process for Xposed services"); 906 | } else if ((pid = fork()) < 0) { 907 | ALOGE("Fork for Xposed service in system context failed: %s", strerror(errno)); 908 | return false; 909 | } else if (pid == 0) { 910 | systemService(); 911 | // Should never reach this point 912 | exit(EXIT_FAILURE); 913 | } 914 | 915 | // app context service 916 | if ((pid = fork()) < 0) { 917 | ALOGE("Fork for Xposed service in app context failed: %s", strerror(errno)); 918 | return false; 919 | } else if (pid == 0) { 920 | appService(useSingleProcess); 921 | // Should never reach this point 922 | exit(EXIT_FAILURE); 923 | } 924 | 925 | if (xposed->isSELinuxEnabled && !checkMembasedRunning()) { 926 | return false; 927 | } 928 | 929 | return true; 930 | } 931 | 932 | #if XPOSED_WITH_SELINUX 933 | bool startMembased() { 934 | if (!xposed->isSELinuxEnabled) { 935 | return true; 936 | } 937 | 938 | if (!membased::init()) { 939 | return false; 940 | } 941 | 942 | pid_t pid; 943 | if ((pid = fork()) < 0) { 944 | ALOGE("Fork for Xposed Zygote service failed: %s", strerror(errno)); 945 | return false; 946 | } else if (pid == 0) { 947 | xposed::setProcessName("xposed_zygote_service"); 948 | xposed::dropCapabilities(); 949 | if (setcon(ctx_app) != 0) { 950 | ALOGE("Could not switch to %s context", ctx_app); 951 | exit(EXIT_FAILURE); 952 | } 953 | membased::looper(NULL); 954 | // Should never reach this point 955 | exit(EXIT_FAILURE); 956 | } 957 | 958 | return checkMembasedRunning(); 959 | } 960 | #endif // XPOSED_WITH_SELINUX 961 | 962 | } // namespace service 963 | } // namespace xposed 964 | -------------------------------------------------------------------------------- /xposed_service.h: -------------------------------------------------------------------------------- 1 | #ifndef XPOSED_SERVICE_H_ 2 | #define XPOSED_SERVICE_H_ 3 | 4 | #include 5 | #include 6 | 7 | namespace xposed { 8 | namespace service { 9 | bool startAll(); 10 | 11 | #if XPOSED_WITH_SELINUX 12 | bool startMembased(); 13 | 14 | namespace membased { 15 | int accessFile(const char* path, int mode); 16 | int statFile(const char* path, struct stat* stat); 17 | char* readFile(const char* path, int* bytesRead); 18 | void restrictMemoryInheritance(); 19 | } // namespace membased 20 | #endif // XPOSED_WITH_SELINUX 21 | 22 | } // namespace service 23 | 24 | static inline int zygote_access(const char *pathname, int mode) { 25 | #if XPOSED_WITH_SELINUX 26 | if (xposed->isSELinuxEnabled) 27 | return xposed::service::membased::accessFile(pathname, mode); 28 | #endif // XPOSED_WITH_SELINUX 29 | 30 | return access(pathname, mode); 31 | } 32 | 33 | } // namespace xposed 34 | 35 | #endif /* XPOSED_SERVICE_H_ */ 36 | -------------------------------------------------------------------------------- /xposed_shared.h: -------------------------------------------------------------------------------- 1 | /** 2 | * These declarations are needed for both app_process and the libraries. 3 | */ 4 | 5 | #ifndef XPOSED_SHARED_H_ 6 | #define XPOSED_SHARED_H_ 7 | 8 | #include 9 | 10 | #include "cutils/log.h" 11 | #include "jni.h" 12 | 13 | #ifndef ALOG 14 | #define ALOG LOG 15 | #define ALOGD LOGD 16 | #define ALOGD LOGD 17 | #define ALOGE LOGE 18 | #define ALOGI LOGI 19 | #define ALOGV LOGV 20 | #endif 21 | 22 | #define XPOSED_DIR "/data/data/de.robv.android.xposed.installer/" 23 | 24 | namespace xposed { 25 | 26 | struct XposedShared { 27 | // Global variables 28 | bool zygote; 29 | bool startSystemServer; 30 | const char* startClassName; 31 | uint32_t xposedVersionInt; 32 | bool isSELinuxEnabled; 33 | bool isSELinuxEnforcing; 34 | 35 | // Provided by runtime-specific library, used by executable 36 | void (*onVmCreated)(JNIEnv* env); 37 | 38 | #if XPOSED_WITH_SELINUX 39 | // Provided by the executable, used by runtime-specific library 40 | int (*zygoteservice_accessFile)(const char* path, int mode); 41 | int (*zygoteservice_statFile)(const char* path, struct stat* st); 42 | char* (*zygoteservice_readFile)(const char* path, int* bytesRead); 43 | #endif 44 | }; 45 | 46 | extern XposedShared* xposed; 47 | 48 | } // namespace xposed 49 | 50 | #endif // XPOSED_SHARED_H_ 51 | --------------------------------------------------------------------------------