Repository: rovo89/Xposed Branch: master Commit: aa7ae1dbd63d Files: 24 Total size: 150.3 KB Directory structure: gitextract_pu6aq7zs/ ├── .gitignore ├── ART.mk ├── Android.mk ├── Dalvik.mk ├── MODULE_LICENSE_APACHE2 ├── NOTICE ├── 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 ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *~ ================================================ FILE: ART.mk ================================================ ########################################################## # Library for ART-specific functions ########################################################## include $(CLEAR_VARS) include art/build/Android.common_build.mk $(eval $(call set-target-local-clang-vars)) $(eval $(call set-target-local-cflags-vars,ndebug)) ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 23))) LOCAL_C_INCLUDES += \ external/valgrind \ external/valgrind/include else include external/libcxx/libcxx.mk LOCAL_C_INCLUDES += \ external/valgrind/main \ external/valgrind/main/include endif LOCAL_SRC_FILES += \ libxposed_common.cpp \ libxposed_art.cpp LOCAL_C_INCLUDES += \ art/runtime \ external/gtest/include ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 24))) LOCAL_C_INCLUDES += bionic/libc/private endif LOCAL_SHARED_LIBRARIES += \ libart \ liblog \ libcutils \ libandroidfw \ libnativehelper LOCAL_CFLAGS += \ -DPLATFORM_SDK_VERSION=$(PLATFORM_SDK_VERSION) \ -DXPOSED_WITH_SELINUX=1 LOCAL_MODULE := libxposed_art LOCAL_MODULE_TAGS := optional LOCAL_STRIP_MODULE := keep_symbols LOCAL_MULTILIB := both # Always build both architectures (if applicable) ifeq ($(TARGET_IS_64_BIT),true) $(LOCAL_MODULE): $(LOCAL_MODULE)$(TARGET_2ND_ARCH_MODULE_SUFFIX) endif include $(BUILD_SHARED_LIBRARY) ================================================ FILE: Android.mk ================================================ ########################################################## # Customized app_process executable ########################################################## LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 21))) LOCAL_SRC_FILES := app_main2.cpp LOCAL_MULTILIB := both LOCAL_MODULE_STEM_32 := app_process32_xposed LOCAL_MODULE_STEM_64 := app_process64_xposed else LOCAL_SRC_FILES := app_main.cpp LOCAL_MODULE_STEM := app_process_xposed endif LOCAL_SRC_FILES += \ xposed.cpp \ xposed_logcat.cpp \ xposed_service.cpp \ xposed_safemode.cpp LOCAL_SHARED_LIBRARIES := \ libcutils \ libutils \ liblog \ libbinder \ libandroid_runtime \ libdl LOCAL_CFLAGS += -Wall -Werror -Wextra -Wunused LOCAL_CFLAGS += -DPLATFORM_SDK_VERSION=$(PLATFORM_SDK_VERSION) ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 17))) LOCAL_SHARED_LIBRARIES += libselinux LOCAL_CFLAGS += -DXPOSED_WITH_SELINUX=1 endif ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 22))) LOCAL_WHOLE_STATIC_LIBRARIES := libsigchain LOCAL_LDFLAGS := -Wl,--version-script,art/sigchainlib/version-script.txt -Wl,--export-dynamic endif ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 23))) LOCAL_SHARED_LIBRARIES += libwilhelm endif LOCAL_MODULE := xposed LOCAL_MODULE_TAGS := optional LOCAL_STRIP_MODULE := keep_symbols # Always build both architectures (if applicable) ifeq ($(TARGET_IS_64_BIT),true) $(LOCAL_MODULE): $(LOCAL_MODULE)$(TARGET_2ND_ARCH_MODULE_SUFFIX) endif include $(BUILD_EXECUTABLE) ########################################################## # Library for Dalvik-/ART-specific functions ########################################################## ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 21))) include frameworks/base/cmds/xposed/ART.mk else include frameworks/base/cmds/xposed/Dalvik.mk endif ================================================ FILE: Dalvik.mk ================================================ ########################################################## # Library for Dalvik-specific functions ########################################################## include $(CLEAR_VARS) LOCAL_SRC_FILES := \ libxposed_common.cpp \ libxposed_dalvik.cpp LOCAL_C_INCLUDES += \ dalvik \ dalvik/vm \ external/stlport/stlport \ bionic \ bionic/libstdc++/include \ libcore/include LOCAL_SHARED_LIBRARIES := \ libdvm \ liblog \ libdl \ libnativehelper ifeq ($(PLATFORM_SDK_VERSION),15) LOCAL_SHARED_LIBRARIES += libutils else LOCAL_SHARED_LIBRARIES += libandroidfw endif LOCAL_CFLAGS := -Wall -Werror -Wextra -Wunused -Wno-unused-parameter LOCAL_CFLAGS += -DPLATFORM_SDK_VERSION=$(PLATFORM_SDK_VERSION) ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 17))) LOCAL_CFLAGS += -DXPOSED_WITH_SELINUX=1 endif LOCAL_MODULE := libxposed_dalvik LOCAL_MODULE_TAGS := optional LOCAL_STRIP_MODULE := keep_symbols include $(BUILD_SHARED_LIBRARY) ================================================ FILE: MODULE_LICENSE_APACHE2 ================================================ ================================================ FILE: NOTICE ================================================ Original work Copyright (c) 2005-2008, The Android Open Source Project Modified work Copyright (c) 2013, rovo89 and Tungstwenty Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: app_main.cpp ================================================ /* * Main entry of app process. * * Starts the interpreted runtime, then starts up the application. * */ #define LOG_TAG "appproc" #include #include #include #include #include #include #include #if PLATFORM_SDK_VERSION >= 16 #include #endif #include #include #include "xposed.h" #include static bool isXposedLoaded = false; namespace android { void app_usage() { fprintf(stderr, "Usage: app_process [java-options] cmd-dir start-class-name [options]\n"); fprintf(stderr, " with Xposed support\n"); } void _atrace_set_tracing_enabled(bool enabled) { if (xposed::getSdkVersion() < 18) return; dlerror(); // Clear existing errors void (*PTR_atrace_set_tracing_enabled)(bool); *(void **) (&PTR_atrace_set_tracing_enabled) = dlsym(RTLD_DEFAULT, "atrace_set_tracing_enabled"); const char *error; if ((error = dlerror()) != NULL) { ALOGE("Could not find address for function atrace_set_tracing_enabled: %s", error); } else { PTR_atrace_set_tracing_enabled(enabled); } } class AppRuntime : public AndroidRuntime { public: AppRuntime() : mParentDir(NULL) , mClassName(NULL) , mClass(NULL) , mArgC(0) , mArgV(NULL) { } #if 0 // this appears to be unused const char* getParentDir() const { return mParentDir; } #endif const char* getClassName() const { return mClassName; } virtual void onVmCreated(JNIEnv* env) { if (isXposedLoaded) xposed::onVmCreated(env); if (mClassName == NULL) { return; // Zygote. Nothing to do here. } /* * This is a little awkward because the JNI FindClass call uses the * class loader associated with the native method we're executing in. * If called in onStarted (from RuntimeInit.finishInit because we're * launching "am", for example), FindClass would see that we're calling * from a boot class' native method, and so wouldn't look for the class * we're trying to look up in CLASSPATH. Unfortunately it needs to, * because the "am" classes are not boot classes. * * The easiest fix is to call FindClass here, early on before we start * executing boot class Java code and thereby deny ourselves access to * non-boot classes. */ char* slashClassName = toSlashClassName(mClassName); mClass = env->FindClass(slashClassName); if (mClass == NULL) { ALOGE("ERROR: could not find class '%s'\n", mClassName); } free(slashClassName); mClass = reinterpret_cast(env->NewGlobalRef(mClass)); } virtual void onStarted() { sp proc = ProcessState::self(); ALOGV("App process: starting thread pool.\n"); proc->startThreadPool(); AndroidRuntime* ar = AndroidRuntime::getRuntime(); ar->callMain(mClassName, mClass, mArgC, mArgV); IPCThreadState::self()->stopProcess(); } virtual void onZygoteInit() { // Re-enable tracing now that we're no longer in Zygote. _atrace_set_tracing_enabled(true); sp proc = ProcessState::self(); ALOGV("App process: starting thread pool.\n"); proc->startThreadPool(); } virtual void onExit(int code) { if (mClassName == NULL) { // if zygote IPCThreadState::self()->stopProcess(); } AndroidRuntime::onExit(code); } const char* mParentDir; const char* mClassName; jclass mClass; int mArgC; const char* const* mArgV; }; } using namespace android; /* * sets argv0 to as much of newArgv0 as will fit */ static void setArgv0(const char *argv0, const char *newArgv0) { strlcpy(const_cast(argv0), newArgv0, strlen(argv0)); } int main(int argc, char* const argv[]) { if (xposed::handleOptions(argc, argv)) return 0; #if PLATFORM_SDK_VERSION >= 16 #ifdef __arm__ /* * b/7188322 - Temporarily revert to the compat memory layout * to avoid breaking third party apps. * * THIS WILL GO AWAY IN A FUTURE ANDROID RELEASE. * * http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=7dbaa466 * changes the kernel mapping from bottom up to top-down. * This breaks some programs which improperly embed * an out of date copy of Android's linker. */ char value[PROPERTY_VALUE_MAX]; property_get("ro.kernel.qemu", value, ""); bool is_qemu = (strcmp(value, "1") == 0); if ((getenv("NO_ADDR_COMPAT_LAYOUT_FIXUP") == NULL) && !is_qemu) { int current = personality(0xFFFFFFFF); if ((current & ADDR_COMPAT_LAYOUT) == 0) { personality(current | ADDR_COMPAT_LAYOUT); setenv("NO_ADDR_COMPAT_LAYOUT_FIXUP", "1", 1); execv("/system/bin/app_process", argv); return -1; } } unsetenv("NO_ADDR_COMPAT_LAYOUT_FIXUP"); #endif #endif // These are global variables in ProcessState.cpp mArgC = argc; mArgV = argv; mArgLen = 0; for (int i=0; i #include #include #include #include #include #include #include #include #include #include #include #include #include // for AID_SYSTEM #include "xposed.h" #include static bool isXposedLoaded = false; namespace android { static void app_usage() { fprintf(stderr, "Usage: app_process [java-options] cmd-dir start-class-name [options]\n"); fprintf(stderr, " with Xposed support\n"); } class AppRuntime : public AndroidRuntime { public: AppRuntime(char* argBlockStart, const size_t argBlockLength) : AndroidRuntime(argBlockStart, argBlockLength) , mClass(NULL) { } void setClassNameAndArgs(const String8& className, int argc, char * const *argv) { mClassName = className; for (int i = 0; i < argc; ++i) { mArgs.add(String8(argv[i])); } } virtual void onVmCreated(JNIEnv* env) { if (isXposedLoaded) xposed::onVmCreated(env); if (mClassName.isEmpty()) { return; // Zygote. Nothing to do here. } /* * This is a little awkward because the JNI FindClass call uses the * class loader associated with the native method we're executing in. * If called in onStarted (from RuntimeInit.finishInit because we're * launching "am", for example), FindClass would see that we're calling * from a boot class' native method, and so wouldn't look for the class * we're trying to look up in CLASSPATH. Unfortunately it needs to, * because the "am" classes are not boot classes. * * The easiest fix is to call FindClass here, early on before we start * executing boot class Java code and thereby deny ourselves access to * non-boot classes. */ char* slashClassName = toSlashClassName(mClassName.string()); mClass = env->FindClass(slashClassName); if (mClass == NULL) { ALOGE("ERROR: could not find class '%s'\n", mClassName.string()); env->ExceptionDescribe(); } free(slashClassName); mClass = reinterpret_cast(env->NewGlobalRef(mClass)); } virtual void onStarted() { sp proc = ProcessState::self(); ALOGV("App process: starting thread pool.\n"); proc->startThreadPool(); AndroidRuntime* ar = AndroidRuntime::getRuntime(); ar->callMain(mClassName, mClass, mArgs); IPCThreadState::self()->stopProcess(); } virtual void onZygoteInit() { #if PLATFORM_SDK_VERSION <= 22 // Re-enable tracing now that we're no longer in Zygote. atrace_set_tracing_enabled(true); #endif sp proc = ProcessState::self(); ALOGV("App process: starting thread pool.\n"); proc->startThreadPool(); } virtual void onExit(int code) { if (mClassName.isEmpty()) { // if zygote IPCThreadState::self()->stopProcess(); } AndroidRuntime::onExit(code); } String8 mClassName; Vector mArgs; jclass mClass; }; } using namespace android; static size_t computeArgBlockSize(int argc, char* const argv[]) { // TODO: This assumes that all arguments are allocated in // contiguous memory. There isn't any documented guarantee // that this is the case, but this is how the kernel does it // (see fs/exec.c). // // Also note that this is a constant for "normal" android apps. // Since they're forked from zygote, the size of their command line // is the size of the zygote command line. // // We change the process name of the process by over-writing // the start of the argument block (argv[0]) with the new name of // the process, so we'd mysteriously start getting truncated process // names if the zygote command line decreases in size. uintptr_t start = reinterpret_cast(argv[0]); uintptr_t end = reinterpret_cast(argv[argc - 1]); end += strlen(argv[argc - 1]) + 1; return (end - start); } static void maybeCreateDalvikCache() { #if defined(__aarch64__) static const char kInstructionSet[] = "arm64"; #elif defined(__x86_64__) static const char kInstructionSet[] = "x86_64"; #elif defined(__arm__) static const char kInstructionSet[] = "arm"; #elif defined(__i386__) static const char kInstructionSet[] = "x86"; #elif defined (__mips__) && !defined(__LP64__) static const char kInstructionSet[] = "mips"; #elif defined (__mips__) && defined(__LP64__) static const char kInstructionSet[] = "mips64"; #else #error "Unknown instruction set" #endif const char* androidRoot = getenv("ANDROID_DATA"); LOG_ALWAYS_FATAL_IF(androidRoot == NULL, "ANDROID_DATA environment variable unset"); char dalvikCacheDir[PATH_MAX]; const int numChars = snprintf(dalvikCacheDir, PATH_MAX, "%s/dalvik-cache/%s", androidRoot, kInstructionSet); LOG_ALWAYS_FATAL_IF((numChars >= PATH_MAX || numChars < 0), "Error constructing dalvik cache : %s", strerror(errno)); int result = mkdir(dalvikCacheDir, 0711); LOG_ALWAYS_FATAL_IF((result < 0 && errno != EEXIST), "Error creating cache dir %s : %s", dalvikCacheDir, strerror(errno)); // We always perform these steps because the directory might // already exist, with wider permissions and a different owner // than we'd like. result = chown(dalvikCacheDir, AID_ROOT, AID_ROOT); LOG_ALWAYS_FATAL_IF((result < 0), "Error changing dalvik-cache ownership : %s", strerror(errno)); result = chmod(dalvikCacheDir, 0711); LOG_ALWAYS_FATAL_IF((result < 0), "Error changing dalvik-cache permissions : %s", strerror(errno)); } #if defined(__LP64__) static const char ABI_LIST_PROPERTY[] = "ro.product.cpu.abilist64"; static const char ZYGOTE_NICE_NAME[] = "zygote64"; #else static const char ABI_LIST_PROPERTY[] = "ro.product.cpu.abilist32"; static const char ZYGOTE_NICE_NAME[] = "zygote"; #endif static void runtimeStart(AppRuntime& runtime, const char *classname, const Vector& options, bool zygote) { #if PLATFORM_SDK_VERSION >= 23 runtime.start(classname, options, zygote); #else // try newer variant (5.1.1_r19 and later) first void (*ptr1)(AppRuntime&, const char*, const Vector&, bool); *(void **) (&ptr1) = dlsym(RTLD_DEFAULT, "_ZN7android14AndroidRuntime5startEPKcRKNS_6VectorINS_7String8EEEb"); if (ptr1 != NULL) { ptr1(runtime, classname, options, zygote); return; } // fall back to older variant void (*ptr2)(AppRuntime&, const char*, const Vector&); *(void **) (&ptr2) = dlsym(RTLD_DEFAULT, "_ZN7android14AndroidRuntime5startEPKcRKNS_6VectorINS_7String8EEE"); if (ptr2 != NULL) { ptr2(runtime, classname, options); return; } // should not happen LOG_ALWAYS_FATAL("app_process: could not locate AndroidRuntime::start() method."); #endif } int main(int argc, char* const argv[]) { if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { // Older kernels don't understand PR_SET_NO_NEW_PRIVS and return // EINVAL. Don't die on such kernels. if (errno != EINVAL) { LOG_ALWAYS_FATAL("PR_SET_NO_NEW_PRIVS failed: %s", strerror(errno)); return 12; } } if (xposed::handleOptions(argc, argv)) { return 0; } AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv)); // Process command line arguments // ignore argv[0] argc--; argv++; // Everything up to '--' or first non '-' arg goes to the vm. // // The first argument after the VM args is the "parent dir", which // is currently unused. // // After the parent dir, we expect one or more the following internal // arguments : // // --zygote : Start in zygote mode // --start-system-server : Start the system server. // --application : Start in application (stand alone, non zygote) mode. // --nice-name : The nice name for this process. // // For non zygote starts, these arguments will be followed by // the main class name. All remaining arguments are passed to // the main method of this class. // // For zygote starts, all remaining arguments are passed to the zygote. // main function. // // Note that we must copy argument string values since we will rewrite the // entire argument block when we apply the nice name to argv0. int i; for (i = 0; i < argc; i++) { if (argv[i][0] != '-') { break; } if (argv[i][1] == '-' && argv[i][2] == 0) { ++i; // Skip --. break; } runtime.addOption(strdup(argv[i])); } // Parse runtime arguments. Stop at first unrecognized option. bool zygote = false; bool startSystemServer = false; bool application = false; String8 niceName; String8 className; ++i; // Skip unused "parent dir" argument. while (i < argc) { const char* arg = argv[i++]; if (strcmp(arg, "--zygote") == 0) { zygote = true; niceName = ZYGOTE_NICE_NAME; } else if (strcmp(arg, "--start-system-server") == 0) { startSystemServer = true; } else if (strcmp(arg, "--application") == 0) { application = true; } else if (strncmp(arg, "--nice-name=", 12) == 0) { niceName.setTo(arg + 12); } else if (strncmp(arg, "--", 2) != 0) { className.setTo(arg); break; } else { --i; break; } } Vector args; if (!className.isEmpty()) { // We're not in zygote mode, the only argument we need to pass // to RuntimeInit is the application argument. // // The Remainder of args get passed to startup class main(). Make // copies of them before we overwrite them with the process name. args.add(application ? String8("application") : String8("tool")); runtime.setClassNameAndArgs(className, argc - i, argv + i); } else { // We're in zygote mode. maybeCreateDalvikCache(); if (startSystemServer) { args.add(String8("start-system-server")); } char prop[PROP_VALUE_MAX]; if (property_get(ABI_LIST_PROPERTY, prop, NULL) == 0) { LOG_ALWAYS_FATAL("app_process: Unable to determine ABI list from property %s.", ABI_LIST_PROPERTY); return 11; } String8 abiFlag("--abi-list="); abiFlag.append(prop); args.add(abiFlag); // In zygote mode, pass all remaining arguments to the zygote // main() method. for (; i < argc; ++i) { args.add(String8(argv[i])); } } if (!niceName.isEmpty()) { runtime.setArgv0(niceName.string()); set_process_name(niceName.string()); } if (zygote) { isXposedLoaded = xposed::initialize(true, startSystemServer, NULL, argc, argv); runtimeStart(runtime, isXposedLoaded ? XPOSED_CLASS_DOTS_ZYGOTE : "com.android.internal.os.ZygoteInit", args, zygote); } else if (className) { isXposedLoaded = xposed::initialize(false, false, className, argc, argv); runtimeStart(runtime, isXposedLoaded ? XPOSED_CLASS_DOTS_TOOLS : "com.android.internal.os.RuntimeInit", args, zygote); } else { fprintf(stderr, "Error: no class name or --zygote supplied.\n"); app_usage(); LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied."); return 10; } } ================================================ FILE: fd_utils-inl.h ================================================ /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "JNIHelp.h" #include "ScopedPrimitiveArray.h" static const char* kPathPrefixWhitelist[] = { "/data/app/", "/data/app-private/", "/system/app/", "/system/priv-app/", "/vendor/app/", "/vendor/priv-app/", }; static const char* kFdPath = "/proc/self/fd"; // Keeps track of all relevant information (flags, offset etc.) of an // open zygote file descriptor. class FileDescriptorInfo { public: // Create a FileDescriptorInfo for a given file descriptor. Returns // |NULL| if an error occurred. static FileDescriptorInfo* createFromFd(int fd) { struct stat f_stat; // This should never happen; the zygote should always have the right set // of permissions required to stat all its open files. if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) { ALOGE("Unable to stat fd %d : %s", fd, strerror(errno)); return NULL; } if (S_ISSOCK(f_stat.st_mode)) { std::string socket_name; if (!GetSocketName(fd, &socket_name)) { return NULL; } if (!IsWhitelisted(socket_name)) { //ALOGE("Socket name not whitelisted : %s (fd=%d)", socket_name.c_str(), fd); return NULL; } return new FileDescriptorInfo(fd); } std::string file_path; if (!Readlink(fd, &file_path)) { return NULL; } if (!IsWhitelisted(file_path)) { //ALOGE("Not whitelisted : %s", file_path.c_str()); return NULL; } // We only handle whitelisted regular files and character devices. Whitelisted // character devices must provide a guarantee of sensible behaviour when // reopened. // // S_ISDIR : Not supported. (We could if we wanted to, but it's unused). // S_ISLINK : Not supported. // S_ISBLK : Not supported. // S_ISFIFO : Not supported. Note that the zygote uses pipes to communicate // with the child process across forks but those should have been closed // before we got to this point. if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) { ALOGE("Unsupported st_mode %d", f_stat.st_mode); return NULL; } // File descriptor flags : currently on FD_CLOEXEC. We can set these // using F_SETFD - we're single threaded at this point of execution so // there won't be any races. const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD)); if (fd_flags == -1) { ALOGE("Failed fcntl(%d, F_GETFD) : %s", fd, strerror(errno)); return NULL; } // File status flags : // - File access mode : (O_RDONLY, O_WRONLY...) we'll pass these through // to the open() call. // // - File creation flags : (O_CREAT, O_EXCL...) - there's not much we can // do about these, since the file has already been created. We shall ignore // them here. // // - Other flags : We'll have to set these via F_SETFL. On linux, F_SETFL // can only set O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, and O_NONBLOCK. // In particular, it can't set O_SYNC and O_DSYNC. We'll have to test for // their presence and pass them in to open(). int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL)); if (fs_flags == -1) { ALOGE("Failed fcntl(%d, F_GETFL) : %s", fd, strerror(errno)); return NULL; } // File offset : Ignore the offset for non seekable files. const off_t offset = TEMP_FAILURE_RETRY(lseek64(fd, 0, SEEK_CUR)); // We pass the flags that open accepts to open, and use F_SETFL for // the rest of them. static const int kOpenFlags = (O_RDONLY | O_WRONLY | O_RDWR | O_DSYNC | O_SYNC); int open_flags = fs_flags & (kOpenFlags); fs_flags = fs_flags & (~(kOpenFlags)); return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset); } bool Detach() const { const int dev_null_fd = open("/dev/null", O_RDWR); if (dev_null_fd < 0) { ALOGE("Failed to open /dev/null : %s", strerror(errno)); return false; } if (dup2(dev_null_fd, fd) == -1) { ALOGE("Failed dup2 on socket descriptor %d : %s", fd, strerror(errno)); return false; } if (close(dev_null_fd) == -1) { ALOGE("Failed close(%d) : %s", dev_null_fd, strerror(errno)); return false; } return true; } bool Reopen() const { if (is_sock) { return true; } // NOTE: This might happen if the file was unlinked after being opened. // It's a common pattern in the case of temporary files and the like but // we should not allow such usage from the zygote. const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags)); if (new_fd == -1) { ALOGE("Failed open(%s, %d) : %s", file_path.c_str(), open_flags, strerror(errno)); return false; } if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) { close(new_fd); ALOGE("Failed fcntl(%d, F_SETFD, %x) : %s", new_fd, fd_flags, strerror(errno)); return false; } if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) { close(new_fd); ALOGE("Failed fcntl(%d, F_SETFL, %x) : %s", new_fd, fs_flags, strerror(errno)); return false; } if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) { close(new_fd); ALOGE("Failed lseek64(%d, SEEK_SET) : %s", new_fd, strerror(errno)); return false; } if (TEMP_FAILURE_RETRY(dup2(new_fd, fd)) == -1) { close(new_fd); ALOGE("Failed dup2(%d, %d) : %s", fd, new_fd, strerror(errno)); return false; } close(new_fd); return true; } const int fd; const struct stat stat; const std::string file_path; const int open_flags; const int fd_flags; const int fs_flags; const off_t offset; const bool is_sock; private: FileDescriptorInfo(int pfd) : fd(pfd), stat(), open_flags(0), fd_flags(0), fs_flags(0), offset(0), is_sock(true) { } FileDescriptorInfo(struct stat pstat, const std::string& pfile_path, int pfd, int popen_flags, int pfd_flags, int pfs_flags, off_t poffset) : fd(pfd), stat(pstat), file_path(pfile_path), open_flags(popen_flags), fd_flags(pfd_flags), fs_flags(pfs_flags), offset(poffset), is_sock(false) { } // Returns true iff. a given path is whitelisted. static bool IsWhitelisted(const std::string& path) { for (size_t i = 0; i < (sizeof(kPathPrefixWhitelist) / sizeof(kPathPrefixWhitelist[0])); ++i) { if (path.compare(0, strlen(kPathPrefixWhitelist[i]), kPathPrefixWhitelist[i]) == 0) { return true; } } return false; } // TODO: Call android::base::Readlink instead of copying the code here. static bool Readlink(const int fd, std::string* result) { char path[64]; snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); // Code copied from android::base::Readlink starts here : // Annoyingly, the readlink system call returns EINVAL for a zero-sized buffer, // and truncates to whatever size you do supply, so it can't be used to query. // We could call lstat first, but that would introduce a race condition that // we couldn't detect. // ext2 and ext4 both have PAGE_SIZE limitations, so we assume that here. char* buf = new char[4096]; ssize_t len = readlink(path, buf, 4096); if (len == -1) { delete[] buf; return false; } result->assign(buf, len); delete[] buf; return true; } // Returns the locally-bound name of the socket |fd|. Returns true // iff. all of the following hold : // // - the socket's sa_family is AF_UNIX. // - the length of the path is greater than zero (i.e, not an unnamed socket). // - the first byte of the path isn't zero (i.e, not a socket with an abstract // address). static bool GetSocketName(const int fd, std::string* result) { sockaddr_storage ss; sockaddr* addr = reinterpret_cast(&ss); socklen_t addr_len = sizeof(ss); if (TEMP_FAILURE_RETRY(getsockname(fd, addr, &addr_len)) == -1) { ALOGE("Failed getsockname(%d) : %s", fd, strerror(errno)); return false; } #if PLATFORM_SDK_VERSION <= 23 if (addr->sa_family == AF_NETLINK) { (*result) = "@netlink@"; return true; } #endif if (addr->sa_family != AF_UNIX) { //ALOGE("Unsupported socket (fd=%d) with family %d", fd, addr->sa_family); return false; } const sockaddr_un* unix_addr = reinterpret_cast(&ss); size_t path_len = addr_len - offsetof(struct sockaddr_un, sun_path); // This is an unnamed local socket, we do not accept it. if (path_len == 0) { //ALOGE("Unsupported AF_UNIX socket (fd=%d) with empty path.", fd); return false; } // This is a local socket with an abstract address, we do not accept it. if (unix_addr->sun_path[0] == '\0') { //ALOGE("Unsupported AF_UNIX socket (fd=%d) with abstract address.", fd); return false; } // If we're here, sun_path must refer to a null terminated filesystem // pathname (man 7 unix). Remove the terminator before assigning it to an // std::string. if (unix_addr->sun_path[path_len - 1] == '\0') { --path_len; } result->assign(unix_addr->sun_path, path_len); return true; } // DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo); FileDescriptorInfo(const FileDescriptorInfo&); void operator=(const FileDescriptorInfo&); }; // A FileDescriptorTable is a collection of FileDescriptorInfo objects // keyed by their FDs. class FileDescriptorTable { public: // Creates a new FileDescriptorTable. This function scans // /proc/self/fd for the list of open file descriptors and collects // information about them. Returns NULL if an error occurs. static FileDescriptorTable* Create() { DIR* d = opendir(kFdPath); if (d == NULL) { ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno)); return NULL; } int dir_fd = dirfd(d); dirent* e; std::unordered_map open_fd_map; while ((e = readdir(d)) != NULL) { const int fd = ParseFd(e, dir_fd); if (fd == -1) { continue; } FileDescriptorInfo* info = FileDescriptorInfo::createFromFd(fd); if (info == NULL) { continue; } info->Detach(); open_fd_map[fd] = info; } if (closedir(d) == -1) { ALOGE("Unable to close directory : %s", strerror(errno)); return NULL; } return new FileDescriptorTable(open_fd_map); } // Reopens all file descriptors that are contained in the table. void Reopen() { std::unordered_map::const_iterator it; for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) { const FileDescriptorInfo* info = it->second; if (info != NULL) { info->Reopen(); delete info; } } } private: FileDescriptorTable(const std::unordered_map& map) : open_fd_map_(map) { } static int ParseFd(dirent* e, int dir_fd) { char* end; const int fd = strtol(e->d_name, &end, 10); if ((*end) != '\0') { return -1; } // Don't bother with the standard input/output/error, they're handled // specially post-fork anyway. if (fd <= STDERR_FILENO || fd == dir_fd) { return -1; } return fd; } // Invariant: All values in this unordered_map are non-NULL. std::unordered_map open_fd_map_; // DISALLOW_COPY_AND_ASSIGN(FileDescriptorTable); FileDescriptorTable(const FileDescriptorTable&); void operator=(const FileDescriptorTable&); }; ================================================ FILE: libxposed_art.cpp ================================================ /** * This file includes functions specific to the ART runtime. */ #define LOG_TAG "Xposed" #include "xposed_shared.h" #include "libxposed_common.h" #if PLATFORM_SDK_VERSION >= 21 #include "fd_utils-inl.h" #endif #include "thread.h" #include "common_throws.h" #if PLATFORM_SDK_VERSION >= 23 #include "art_method-inl.h" #else #include "mirror/art_method-inl.h" #endif #include "mirror/object-inl.h" #include "mirror/throwable.h" #include "native/scoped_fast_native_object_access.h" #include "reflection.h" #include "scoped_thread_state_change.h" #include "well_known_classes.h" #if PLATFORM_SDK_VERSION >= 24 #include "mirror/abstract_method.h" #include "thread_list.h" #endif using namespace art; #if PLATFORM_SDK_VERSION < 23 using art::mirror::ArtMethod; #endif namespace xposed { //////////////////////////////////////////////////////////// // Library initialization //////////////////////////////////////////////////////////// /** Called by Xposed's app_process replacement. */ bool xposedInitLib(XposedShared* shared) { xposed = shared; xposed->onVmCreated = &onVmCreatedCommon; return true; } /** Called very early during VM startup. */ bool onVmCreated(JNIEnv*) { // TODO: Handle CLASS_MIUI_RESOURCES? ArtMethod::xposed_callback_class = classXposedBridge; ArtMethod::xposed_callback_method = methodXposedBridgeHandleHookedMethod; return true; } //////////////////////////////////////////////////////////// // Utility methods //////////////////////////////////////////////////////////// void logExceptionStackTrace() { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); #if PLATFORM_SDK_VERSION >= 23 XLOG(ERROR) << self->GetException()->Dump(); #else XLOG(ERROR) << self->GetException(nullptr)->Dump(); #endif } //////////////////////////////////////////////////////////// // JNI methods //////////////////////////////////////////////////////////// void XposedBridge_hookMethodNative(JNIEnv* env, jclass, jobject javaReflectedMethod, jobject, jint, jobject javaAdditionalInfo) { // Detect usage errors. ScopedObjectAccess soa(env); if (javaReflectedMethod == nullptr) { #if PLATFORM_SDK_VERSION >= 23 ThrowIllegalArgumentException("method must not be null"); #else ThrowIllegalArgumentException(nullptr, "method must not be null"); #endif return; } // Get the ArtMethod of the method to be hooked. ArtMethod* artMethod = ArtMethod::FromReflectedMethod(soa, javaReflectedMethod); // Hook the method artMethod->EnableXposedHook(soa, javaAdditionalInfo); } jobject XposedBridge_invokeOriginalMethodNative(JNIEnv* env, jclass, jobject javaMethod, jint isResolved, jobjectArray, jclass, jobject javaReceiver, jobjectArray javaArgs) { ScopedFastNativeObjectAccess soa(env); if (UNLIKELY(!isResolved)) { ArtMethod* artMethod = ArtMethod::FromReflectedMethod(soa, javaMethod); if (LIKELY(artMethod->IsXposedHookedMethod())) { javaMethod = artMethod->GetXposedHookInfo()->reflected_method; } } #if PLATFORM_SDK_VERSION >= 23 return InvokeMethod(soa, javaMethod, javaReceiver, javaArgs); #else return InvokeMethod(soa, javaMethod, javaReceiver, javaArgs, true); #endif } void XposedBridge_setObjectClassNative(JNIEnv* env, jclass, jobject javaObj, jclass javaClazz) { ScopedObjectAccess soa(env); StackHandleScope<3> hs(soa.Self()); Handle clazz(hs.NewHandle(soa.Decode(javaClazz))); #if PLATFORM_SDK_VERSION >= 23 if (!Runtime::Current()->GetClassLinker()->EnsureInitialized(soa.Self(), clazz, true, true)) { #else if (!Runtime::Current()->GetClassLinker()->EnsureInitialized(clazz, true, true)) { #endif XLOG(ERROR) << "Could not initialize class " << PrettyClass(clazz.Get()); return; } Handle obj(hs.NewHandle(soa.Decode(javaObj))); Handle currentClass(hs.NewHandle(obj->GetClass())); if (clazz->GetObjectSize() != currentClass->GetObjectSize()) { std::string msg = StringPrintf("Different object sizes: %s (%d) vs. %s (%d)", PrettyClass(clazz.Get()).c_str(), clazz->GetObjectSize(), PrettyClass(currentClass.Get()).c_str(), currentClass->GetObjectSize()); #if PLATFORM_SDK_VERSION >= 23 ThrowIllegalArgumentException(msg.c_str()); #else ThrowIllegalArgumentException(nullptr, msg.c_str()); #endif return; } obj->SetClass(clazz.Get()); } void XposedBridge_dumpObjectNative(JNIEnv*, jclass, jobject) { // TODO Can be useful for debugging UNIMPLEMENTED(ERROR|LOG_XPOSED); } jobject XposedBridge_cloneToSubclassNative(JNIEnv* env, jclass, jobject javaObject, jclass javaClazz) { ScopedObjectAccess soa(env); StackHandleScope<3> hs(soa.Self()); Handle obj(hs.NewHandle(soa.Decode(javaObject))); Handle clazz(hs.NewHandle(soa.Decode(javaClazz))); Handle dest(hs.NewHandle(obj->Clone(soa.Self(), clazz.Get()))); return soa.AddLocalReference(dest.Get()); } void XposedBridge_removeFinalFlagNative(JNIEnv* env, jclass, jclass javaClazz) { ScopedObjectAccess soa(env); StackHandleScope<1> hs(soa.Self()); Handle clazz(hs.NewHandle(soa.Decode(javaClazz))); uint32_t flags = clazz->GetAccessFlags(); if ((flags & kAccFinal) != 0) { clazz->SetAccessFlags(flags & ~kAccFinal); } } jint XposedBridge_getRuntime(JNIEnv*, jclass) { return 2; // RUNTIME_ART } #if PLATFORM_SDK_VERSION >= 21 static FileDescriptorTable* gClosedFdTable = NULL; void XposedBridge_closeFilesBeforeForkNative(JNIEnv*, jclass) { gClosedFdTable = FileDescriptorTable::Create(); } void XposedBridge_reopenFilesAfterForkNative(JNIEnv*, jclass) { gClosedFdTable->Reopen(); delete gClosedFdTable; gClosedFdTable = NULL; } #endif #if PLATFORM_SDK_VERSION >= 24 void XposedBridge_invalidateCallersNative(JNIEnv* env, jclass, jobjectArray javaMethods) { ScopedObjectAccess soa(env); auto* runtime = Runtime::Current(); auto* cl = runtime->GetClassLinker(); // Invalidate callers of the given methods. auto* abstract_methods = soa.Decode*>(javaMethods); size_t count = abstract_methods->GetLength(); for (size_t i = 0; i < count; i++) { auto* abstract_method = abstract_methods->Get(i); if (abstract_method == nullptr) { continue; } ArtMethod* method = abstract_method->GetArtMethod(); cl->InvalidateCallersForMethod(soa.Self(), method); } // Now instrument the stack to deoptimize methods which are being called right now. ScopedThreadSuspension sts(soa.Self(), kSuspended); ScopedSuspendAll ssa(__FUNCTION__); MutexLock mu(soa.Self(), *Locks::thread_list_lock_); runtime->GetThreadList()->ForEach([](Thread* thread, void*) SHARED_REQUIRES(Locks::mutator_lock_) { Runtime::Current()->GetInstrumentation()->InstrumentThreadStack(thread); }, nullptr); } #endif } // namespace xposed ================================================ FILE: libxposed_common.cpp ================================================ /** * This file includes functions shared by different runtimes. */ #define LOG_TAG "Xposed" #include "libxposed_common.h" #include "JNIHelp.h" #include #define private public #if PLATFORM_SDK_VERSION == 15 #include #else #include #endif #undef private namespace xposed { //////////////////////////////////////////////////////////// // Variables //////////////////////////////////////////////////////////// bool xposedLoadedSuccessfully = false; xposed::XposedShared* xposed = NULL; jclass classXposedBridge = NULL; static jclass classXResources = NULL; static jclass classFileResult = NULL; jmethodID methodXposedBridgeHandleHookedMethod = NULL; static jmethodID methodXResourcesTranslateResId = NULL; static jmethodID methodXResourcesTranslateAttrId = NULL; static jmethodID constructorFileResult = NULL; //////////////////////////////////////////////////////////// // Forward declarations //////////////////////////////////////////////////////////// static int register_natives_XposedBridge(JNIEnv* env, jclass clazz); static int register_natives_XResources(JNIEnv* env, jclass clazz); static int register_natives_ZygoteService(JNIEnv* env, jclass clazz); //////////////////////////////////////////////////////////// // Utility methods //////////////////////////////////////////////////////////// /** Read an integer value from a configuration file. */ int readIntConfig(const char* fileName, int defaultValue) { FILE *fp = fopen(fileName, "r"); if (fp == NULL) return defaultValue; int result; int success = fscanf(fp, "%i", &result); fclose(fp); return (success >= 1) ? result : defaultValue; } //////////////////////////////////////////////////////////// // Library initialization //////////////////////////////////////////////////////////// bool initXposedBridge(JNIEnv* env) { classXposedBridge = env->FindClass(CLASS_XPOSED_BRIDGE); if (classXposedBridge == NULL) { ALOGE("Error while loading Xposed class '%s':", CLASS_XPOSED_BRIDGE); logExceptionStackTrace(); env->ExceptionClear(); return false; } classXposedBridge = reinterpret_cast(env->NewGlobalRef(classXposedBridge)); ALOGI("Found Xposed class '%s', now initializing", CLASS_XPOSED_BRIDGE); if (register_natives_XposedBridge(env, classXposedBridge) != JNI_OK) { ALOGE("Could not register natives for '%s'", CLASS_XPOSED_BRIDGE); logExceptionStackTrace(); env->ExceptionClear(); return false; } methodXposedBridgeHandleHookedMethod = env->GetStaticMethodID(classXposedBridge, "handleHookedMethod", "(Ljava/lang/reflect/Member;ILjava/lang/Object;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"); if (methodXposedBridgeHandleHookedMethod == NULL) { ALOGE("ERROR: could not find method %s.handleHookedMethod(Member, int, Object, Object, Object[])", CLASS_XPOSED_BRIDGE); logExceptionStackTrace(); env->ExceptionClear(); return false; } return true; } bool initZygoteService(JNIEnv* env) { jclass zygoteServiceClass = env->FindClass(CLASS_ZYGOTE_SERVICE); if (zygoteServiceClass == NULL) { ALOGE("Error while loading ZygoteService class '%s':", CLASS_ZYGOTE_SERVICE); logExceptionStackTrace(); env->ExceptionClear(); return false; } if (register_natives_ZygoteService(env, zygoteServiceClass) != JNI_OK) { ALOGE("Could not register natives for '%s'", CLASS_ZYGOTE_SERVICE); env->ExceptionClear(); return false; } classFileResult = env->FindClass(CLASS_FILE_RESULT); if (classFileResult == NULL) { ALOGE("Error while loading FileResult class '%s':", CLASS_FILE_RESULT); logExceptionStackTrace(); env->ExceptionClear(); return false; } classFileResult = reinterpret_cast(env->NewGlobalRef(classFileResult)); constructorFileResult = env->GetMethodID(classFileResult, "", "(JJ)V"); if (constructorFileResult == NULL) { ALOGE("ERROR: could not find constructor %s(long, long)", CLASS_FILE_RESULT); logExceptionStackTrace(); env->ExceptionClear(); return false; } return true; } void onVmCreatedCommon(JNIEnv* env) { if (!initXposedBridge(env) || !initZygoteService(env)) { return; } if (!onVmCreated(env)) { return; } xposedLoadedSuccessfully = true; return; } //////////////////////////////////////////////////////////// // JNI methods //////////////////////////////////////////////////////////// jboolean XposedBridge_hadInitErrors(JNIEnv*, jclass) { return !xposedLoadedSuccessfully; } jobject XposedBridge_getStartClassName(JNIEnv* env, jclass) { return env->NewStringUTF(xposed->startClassName); } jboolean XposedBridge_startsSystemServer(JNIEnv*, jclass) { return xposed->startSystemServer; } jint XposedBridge_getXposedVersion(JNIEnv*, jclass) { return xposed->xposedVersionInt; } jboolean XposedBridge_initXResourcesNative(JNIEnv* env, jclass) { classXResources = env->FindClass(CLASS_XRESOURCES); if (classXResources == NULL) { ALOGE("Error while loading XResources class '%s':", CLASS_XRESOURCES); logExceptionStackTrace(); env->ExceptionClear(); return false; } classXResources = reinterpret_cast(env->NewGlobalRef(classXResources)); if (register_natives_XResources(env, classXResources) != JNI_OK) { ALOGE("Could not register natives for '%s'", CLASS_XRESOURCES); env->ExceptionClear(); return false; } methodXResourcesTranslateResId = env->GetStaticMethodID(classXResources, "translateResId", "(ILandroid/content/res/XResources;Landroid/content/res/Resources;)I"); if (methodXResourcesTranslateResId == NULL) { ALOGE("ERROR: could not find method %s.translateResId(int, XResources, Resources)", CLASS_XRESOURCES); logExceptionStackTrace(); env->ExceptionClear(); return false; } methodXResourcesTranslateAttrId = env->GetStaticMethodID(classXResources, "translateAttrId", "(Ljava/lang/String;Landroid/content/res/XResources;)I"); if (methodXResourcesTranslateAttrId == NULL) { ALOGE("ERROR: could not find method %s.findAttrId(String, XResources)", CLASS_XRESOURCES); logExceptionStackTrace(); env->ExceptionClear(); return false; } return true; } void XResources_rewriteXmlReferencesNative(JNIEnv* env, jclass, jlong parserPtr, jobject origRes, jobject repRes) { using namespace android; ResXMLParser* parser = (ResXMLParser*)parserPtr; const ResXMLTree& mTree = parser->mTree; uint32_t* mResIds = (uint32_t*)mTree.mResIds; ResXMLTree_attrExt* tag; int attrCount; if (parser == NULL) return; do { switch (parser->next()) { case ResXMLParser::START_TAG: tag = (ResXMLTree_attrExt*)parser->mCurExt; attrCount = dtohs(tag->attributeCount); for (int idx = 0; idx < attrCount; idx++) { ResXMLTree_attribute* attr = (ResXMLTree_attribute*) (((const uint8_t*)tag) + dtohs(tag->attributeStart) + (dtohs(tag->attributeSize)*idx)); // find resource IDs for attribute names int32_t attrNameID = parser->getAttributeNameID(idx); // only replace attribute name IDs for app packages if (attrNameID >= 0 && (size_t)attrNameID < mTree.mNumResIds && dtohl(mResIds[attrNameID]) >= 0x7f000000) { size_t attNameLen; const char16_t* attrName = mTree.mStrings.stringAt(attrNameID, &attNameLen); jint attrResID = env->CallStaticIntMethod(classXResources, methodXResourcesTranslateAttrId, env->NewString((const jchar*)attrName, attNameLen), origRes); if (env->ExceptionCheck()) goto leave; mResIds[attrNameID] = htodl(attrResID); } // find original resource IDs for reference values (app packages only) if (attr->typedValue.dataType != Res_value::TYPE_REFERENCE) continue; jint oldValue = dtohl(attr->typedValue.data); if (oldValue < 0x7f000000) continue; jint newValue = env->CallStaticIntMethod(classXResources, methodXResourcesTranslateResId, oldValue, origRes, repRes); if (env->ExceptionCheck()) goto leave; if (newValue != oldValue) attr->typedValue.data = htodl(newValue); } continue; case ResXMLParser::END_DOCUMENT: case ResXMLParser::BAD_DOCUMENT: goto leave; default: continue; } } while (true); leave: parser->restart(); } jboolean ZygoteService_checkFileAccess(JNIEnv* env, jclass, jstring filenameJ, jint mode) { #if XPOSED_WITH_SELINUX ScopedUtfChars filename(env, filenameJ); return xposed->zygoteservice_accessFile(filename.c_str(), mode) == 0; #else // XPOSED_WITH_SELINUX return false; #endif // XPOSED_WITH_SELINUX } jobject ZygoteService_statFile(JNIEnv* env, jclass, jstring filenameJ) { #if XPOSED_WITH_SELINUX ScopedUtfChars filename(env, filenameJ); struct stat st; int result = xposed->zygoteservice_statFile(filename.c_str(), &st); if (result != 0) { if (errno == ENOENT) { jniThrowExceptionFmt(env, "java/io/FileNotFoundException", "No such file or directory: %s", filename.c_str()); } else { jniThrowExceptionFmt(env, "java/io/IOException", "%s while reading %s", strerror(errno), filename.c_str()); } return NULL; } return env->NewObject(classFileResult, constructorFileResult, (jlong) st.st_size, (jlong) st.st_mtime); #else // XPOSED_WITH_SELINUX return NULL; #endif // XPOSED_WITH_SELINUX } jbyteArray ZygoteService_readFile(JNIEnv* env, jclass, jstring filenameJ) { #if XPOSED_WITH_SELINUX ScopedUtfChars filename(env, filenameJ); int bytesRead = 0; char* content = xposed->zygoteservice_readFile(filename.c_str(), &bytesRead); if (content == NULL) { if (errno == ENOENT) { jniThrowExceptionFmt(env, "java/io/FileNotFoundException", "No such file or directory: %s", filename.c_str()); } else { jniThrowExceptionFmt(env, "java/io/IOException", "%s while reading %s", strerror(errno), filename.c_str()); } return NULL; } jbyteArray ret = env->NewByteArray(bytesRead); if (ret != NULL) { jbyte* arrptr = (jbyte*)env->GetPrimitiveArrayCritical(ret, 0); if (arrptr) { memcpy(arrptr, content, bytesRead); env->ReleasePrimitiveArrayCritical(ret, arrptr, 0); } } free(content); return ret; #else // XPOSED_WITH_SELINUX return NULL; #endif // XPOSED_WITH_SELINUX } //////////////////////////////////////////////////////////// // JNI methods registrations //////////////////////////////////////////////////////////// int register_natives_XposedBridge(JNIEnv* env, jclass clazz) { const JNINativeMethod methods[] = { NATIVE_METHOD(XposedBridge, hadInitErrors, "()Z"), NATIVE_METHOD(XposedBridge, getStartClassName, "()Ljava/lang/String;"), NATIVE_METHOD(XposedBridge, getRuntime, "()I"), NATIVE_METHOD(XposedBridge, startsSystemServer, "()Z"), NATIVE_METHOD(XposedBridge, getXposedVersion, "()I"), NATIVE_METHOD(XposedBridge, initXResourcesNative, "()Z"), NATIVE_METHOD(XposedBridge, hookMethodNative, "(Ljava/lang/reflect/Member;Ljava/lang/Class;ILjava/lang/Object;)V"), NATIVE_METHOD(XposedBridge, setObjectClassNative, "(Ljava/lang/Object;Ljava/lang/Class;)V"), NATIVE_METHOD(XposedBridge, dumpObjectNative, "(Ljava/lang/Object;)V"), NATIVE_METHOD(XposedBridge, cloneToSubclassNative, "(Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object;"), NATIVE_METHOD(XposedBridge, removeFinalFlagNative, "(Ljava/lang/Class;)V"), #if PLATFORM_SDK_VERSION >= 21 NATIVE_METHOD(XposedBridge, invokeOriginalMethodNative, "!(Ljava/lang/reflect/Member;I[Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"), NATIVE_METHOD(XposedBridge, closeFilesBeforeForkNative, "()V"), NATIVE_METHOD(XposedBridge, reopenFilesAfterForkNative, "()V"), #endif #if PLATFORM_SDK_VERSION >= 24 NATIVE_METHOD(XposedBridge, invalidateCallersNative, "([Ljava/lang/reflect/Member;)V"), #endif }; return env->RegisterNatives(clazz, methods, NELEM(methods)); } int register_natives_XResources(JNIEnv* env, jclass clazz) { const JNINativeMethod methods[] = { NATIVE_METHOD(XResources, rewriteXmlReferencesNative, "(JLandroid/content/res/XResources;Landroid/content/res/Resources;)V"), }; return env->RegisterNatives(clazz, methods, NELEM(methods)); } int register_natives_ZygoteService(JNIEnv* env, jclass clazz) { const JNINativeMethod methods[] = { NATIVE_METHOD(ZygoteService, checkFileAccess, "(Ljava/lang/String;I)Z"), NATIVE_METHOD(ZygoteService, statFile, "(Ljava/lang/String;)L" CLASS_FILE_RESULT ";"), NATIVE_METHOD(ZygoteService, readFile, "(Ljava/lang/String;)[B"), }; return env->RegisterNatives(clazz, methods, NELEM(methods)); } } // namespace xposed ================================================ FILE: libxposed_common.h ================================================ #ifndef LIBXPOSED_COMMON_H_ #define LIBXPOSED_COMMON_H_ #include "xposed_shared.h" #ifndef NATIVE_METHOD #define NATIVE_METHOD(className, functionName, signature) \ { #functionName, signature, reinterpret_cast(className ## _ ## functionName) } #endif #define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0]))) namespace xposed { #define CLASS_XPOSED_BRIDGE "de/robv/android/xposed/XposedBridge" #define CLASS_XRESOURCES "android/content/res/XResources" #define CLASS_MIUI_RESOURCES "android/content/res/MiuiResources" #define CLASS_ZYGOTE_SERVICE "de/robv/android/xposed/services/ZygoteService" #define CLASS_FILE_RESULT "de/robv/android/xposed/services/FileResult" ///////////////////////////////////////////////////////////////// // Provided by common part, used by runtime-specific implementation ///////////////////////////////////////////////////////////////// extern jclass classXposedBridge; extern jmethodID methodXposedBridgeHandleHookedMethod; extern int readIntConfig(const char* fileName, int defaultValue); extern void onVmCreatedCommon(JNIEnv* env); ///////////////////////////////////////////////////////////////// // To be provided by runtime-specific implementation ///////////////////////////////////////////////////////////////// extern "C" bool xposedInitLib(xposed::XposedShared* shared); extern bool onVmCreated(JNIEnv* env); extern void prepareSubclassReplacement(JNIEnv* env, jclass clazz); extern void logExceptionStackTrace(); extern jint XposedBridge_getRuntime(JNIEnv* env, jclass clazz); extern void XposedBridge_hookMethodNative(JNIEnv* env, jclass clazz, jobject reflectedMethodIndirect, jobject declaredClassIndirect, jint slot, jobject additionalInfoIndirect); extern void XposedBridge_setObjectClassNative(JNIEnv* env, jclass clazz, jobject objIndirect, jclass clzIndirect); extern jobject XposedBridge_cloneToSubclassNative(JNIEnv* env, jclass clazz, jobject objIndirect, jclass clzIndirect); extern void XposedBridge_dumpObjectNative(JNIEnv* env, jclass clazz, jobject objIndirect); extern void XposedBridge_removeFinalFlagNative(JNIEnv* env, jclass clazz, jclass javaClazz); #if PLATFORM_SDK_VERSION >= 21 extern jobject XposedBridge_invokeOriginalMethodNative(JNIEnv* env, jclass, jobject javaMethod, jint, jobjectArray, jclass, jobject javaReceiver, jobjectArray javaArgs); extern void XposedBridge_closeFilesBeforeForkNative(JNIEnv* env, jclass clazz); extern void XposedBridge_reopenFilesAfterForkNative(JNIEnv* env, jclass clazz); #endif #if PLATFORM_SDK_VERSION >= 24 extern void XposedBridge_invalidateCallersNative(JNIEnv*, jclass, jobjectArray javaMethods); #endif } // namespace xposed #endif // LIBXPOSED_COMMON_H_ ================================================ FILE: libxposed_dalvik.cpp ================================================ /** * This file includes functions specific to the Dalvik runtime. */ #define LOG_TAG "Xposed" #include "libxposed_dalvik.h" #include "xposed_offsets.h" #include namespace xposed { //////////////////////////////////////////////////////////// // Forward declarations //////////////////////////////////////////////////////////// bool initMemberOffsets(JNIEnv* env); void hookedMethodCallback(const u4* args, JValue* pResult, const Method* method, ::Thread* self); void XposedBridge_invokeOriginalMethodNative(const u4* args, JValue* pResult, const Method* method, ::Thread* self); //////////////////////////////////////////////////////////// // Variables //////////////////////////////////////////////////////////// static ClassObject* objectArrayClass = NULL; static size_t arrayContentsOffset = 0; static void* PTR_gDvmJit = NULL; //////////////////////////////////////////////////////////// // Library initialization //////////////////////////////////////////////////////////// /** Called by Xposed's app_process replacement. */ bool xposedInitLib(xposed::XposedShared* shared) { xposed = shared; xposed->onVmCreated = &onVmCreatedCommon; return true; } /** Called very early during VM startup. */ bool onVmCreated(JNIEnv* env) { if (!initMemberOffsets(env)) return false; jclass classMiuiResources = env->FindClass(CLASS_MIUI_RESOURCES); if (classMiuiResources != NULL) { ClassObject* clazz = (ClassObject*)dvmDecodeIndirectRef(dvmThreadSelf(), classMiuiResources); if (dvmIsFinalClass(clazz)) { ALOGD("Removing final flag for class '%s'", CLASS_MIUI_RESOURCES); clazz->accessFlags &= ~ACC_FINAL; } } env->ExceptionClear(); Method* xposedInvokeOriginalMethodNative = (Method*) env->GetStaticMethodID(classXposedBridge, "invokeOriginalMethodNative", "(Ljava/lang/reflect/Member;I[Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"); if (xposedInvokeOriginalMethodNative == NULL) { ALOGE("ERROR: could not find method %s.invokeOriginalMethodNative(Member, int, Class[], Class, Object, Object[])", CLASS_XPOSED_BRIDGE); dvmLogExceptionStackTrace(); env->ExceptionClear(); return false; } dvmSetNativeFunc(xposedInvokeOriginalMethodNative, XposedBridge_invokeOriginalMethodNative, NULL); objectArrayClass = dvmFindArrayClass("[Ljava/lang/Object;", NULL); if (objectArrayClass == NULL) { ALOGE("Error while loading Object[] class"); dvmLogExceptionStackTrace(); env->ExceptionClear(); return false; } return true; } bool initMemberOffsets(JNIEnv* env) { PTR_gDvmJit = dlsym(RTLD_DEFAULT, "gDvmJit"); if (PTR_gDvmJit == NULL) { offsetMode = MEMBER_OFFSET_MODE_NO_JIT; } else { offsetMode = MEMBER_OFFSET_MODE_WITH_JIT; } ALOGD("Using structure member offsets for mode %s", xposedOffsetModesDesc[offsetMode]); MEMBER_OFFSET_COPY(DvmJitGlobals, codeCacheFull); int overrideCodeCacheFull = readIntConfig(XPOSED_OVERRIDE_JIT_RESET_OFFSET, -1); if (overrideCodeCacheFull > 0 && overrideCodeCacheFull < 0x400) { ALOGI("Offset for DvmJitGlobals.codeCacheFull is overridden, new value is 0x%x", overrideCodeCacheFull); MEMBER_OFFSET_VAR(DvmJitGlobals, codeCacheFull) = overrideCodeCacheFull; } // detect offset of ArrayObject->contents jintArray dummyArray = env->NewIntArray(1); if (dummyArray == NULL) { ALOGE("Could allocate int array for testing"); dvmLogExceptionStackTrace(); env->ExceptionClear(); return false; } jint* dummyArrayElements = env->GetIntArrayElements(dummyArray, NULL); arrayContentsOffset = (size_t)dummyArrayElements - (size_t)dvmDecodeIndirectRef(dvmThreadSelf(), dummyArray); env->ReleaseIntArrayElements(dummyArray,dummyArrayElements, 0); env->DeleteLocalRef(dummyArray); if (arrayContentsOffset < 12 || arrayContentsOffset > 128) { ALOGE("Detected strange offset %d of ArrayObject->contents", arrayContentsOffset); return false; } return true; } //////////////////////////////////////////////////////////// // Utility methods //////////////////////////////////////////////////////////// /** Portable clone of dvmSetObjectArrayElement() */ inline void setObjectArrayElement(const ArrayObject* obj, int index, Object* val) { uintptr_t arrayContents = (uintptr_t)obj + arrayContentsOffset; ((Object **)arrayContents)[index] = val; dvmWriteBarrierArray(obj, index, index + 1); } /** Wrapper used by the common part of the library. */ void logExceptionStackTrace() { dvmLogExceptionStackTrace(); } /** Check whether a method is already hooked. */ inline bool isMethodHooked(const Method* method) { return (method->nativeFunc == &hookedMethodCallback); } //////////////////////////////////////////////////////////// // JNI methods //////////////////////////////////////////////////////////// /** This is called when a hooked method is executed. */ void hookedMethodCallback(const u4* args, JValue* pResult, const Method* method, ::Thread* self) { if (!isMethodHooked(method)) { dvmThrowNoSuchMethodError("Could not find Xposed original method - how did you even get here?"); return; } XposedHookInfo* hookInfo = (XposedHookInfo*) method->insns; Method* original = (Method*) hookInfo; Object* originalReflected = hookInfo->reflectedMethod; Object* additionalInfo = hookInfo->additionalInfo; // convert/box arguments const char* desc = &method->shorty[1]; // [0] is the return type. Object* thisObject = NULL; size_t srcIndex = 0; size_t dstIndex = 0; // for non-static methods determine the "this" pointer if (!dvmIsStaticMethod(original)) { thisObject = (Object*) args[0]; srcIndex++; } ArrayObject* argsArray = dvmAllocArrayByClass(objectArrayClass, strlen(method->shorty) - 1, ALLOC_DEFAULT); if (argsArray == NULL) { return; } while (*desc != '\0') { char descChar = *(desc++); JValue value; Object* obj; switch (descChar) { case 'Z': case 'C': case 'F': case 'B': case 'S': case 'I': value.i = args[srcIndex++]; obj = (Object*) dvmBoxPrimitive(value, dvmFindPrimitiveClass(descChar)); dvmReleaseTrackedAlloc(obj, self); break; case 'D': case 'J': value.j = dvmGetArgLong(args, srcIndex); srcIndex += 2; obj = (Object*) dvmBoxPrimitive(value, dvmFindPrimitiveClass(descChar)); dvmReleaseTrackedAlloc(obj, self); break; case '[': case 'L': obj = (Object*) args[srcIndex++]; break; default: ALOGE("Unknown method signature description character: %c", descChar); obj = NULL; srcIndex++; } setObjectArrayElement(argsArray, dstIndex++, obj); } // call the Java handler function JValue result; dvmCallMethod(self, (Method*) methodXposedBridgeHandleHookedMethod, NULL, &result, originalReflected, (int) original, additionalInfo, thisObject, argsArray); dvmReleaseTrackedAlloc(argsArray, self); // exceptions are thrown to the caller if (dvmCheckException(self)) { return; } // return result with proper type ClassObject* returnType = dvmGetBoxedReturnType(method); if (returnType->primitiveType == PRIM_VOID) { // ignored } else if (result.l == NULL) { if (dvmIsPrimitiveClass(returnType)) { dvmThrowNullPointerException("null result when primitive expected"); } pResult->l = NULL; } else { if (!dvmUnboxPrimitive(result.l, returnType, pResult)) { dvmThrowClassCastException(result.l->clazz, returnType); } } } void XposedBridge_hookMethodNative(JNIEnv* env, jclass clazz, jobject reflectedMethodIndirect, jobject declaredClassIndirect, jint slot, jobject additionalInfoIndirect) { // Usage errors? if (declaredClassIndirect == NULL || reflectedMethodIndirect == NULL) { dvmThrowIllegalArgumentException("method and declaredClass must not be null"); return; } // Find the internal representation of the method ClassObject* declaredClass = (ClassObject*) dvmDecodeIndirectRef(dvmThreadSelf(), declaredClassIndirect); Method* method = dvmSlotToMethod(declaredClass, slot); if (method == NULL) { dvmThrowNoSuchMethodError("Could not get internal representation for method"); return; } if (isMethodHooked(method)) { // already hooked return; } // Save a copy of the original method and other hook info XposedHookInfo* hookInfo = (XposedHookInfo*) calloc(1, sizeof(XposedHookInfo)); memcpy(hookInfo, method, sizeof(hookInfo->originalMethodStruct)); hookInfo->reflectedMethod = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(reflectedMethodIndirect)); hookInfo->additionalInfo = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(additionalInfoIndirect)); // Replace method with our own code SET_METHOD_FLAG(method, ACC_NATIVE); method->nativeFunc = &hookedMethodCallback; method->insns = (const u2*) hookInfo; method->registersSize = method->insSize; method->outsSize = 0; if (PTR_gDvmJit != NULL) { // reset JIT cache char currentValue = *((char*)PTR_gDvmJit + MEMBER_OFFSET_VAR(DvmJitGlobals,codeCacheFull)); if (currentValue == 0 || currentValue == 1) { MEMBER_VAL(PTR_gDvmJit, DvmJitGlobals, codeCacheFull) = true; } else { ALOGE("Unexpected current value for codeCacheFull: %d", currentValue); } } } /** * Simplified copy of Method.invokeNative(), but calls the original (non-hooked) method * and has no access checks. Used to call the real implementation of hooked methods. */ void XposedBridge_invokeOriginalMethodNative(const u4* args, JValue* pResult, const Method* method, ::Thread* self) { Method* meth = (Method*) args[1]; if (meth == NULL) { meth = dvmGetMethodFromReflectObj((Object*) args[0]); if (isMethodHooked(meth)) { meth = (Method*) meth->insns; } } ArrayObject* params = (ArrayObject*) args[2]; ClassObject* returnType = (ClassObject*) args[3]; Object* thisObject = (Object*) args[4]; // null for static methods ArrayObject* argList = (ArrayObject*) args[5]; // invoke the method pResult->l = dvmInvokeMethod(thisObject, meth, argList, params, returnType, true); return; } void XposedBridge_setObjectClassNative(JNIEnv* env, jclass clazz, jobject objIndirect, jclass clzIndirect) { Object* obj = (Object*) dvmDecodeIndirectRef(dvmThreadSelf(), objIndirect); ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef(dvmThreadSelf(), clzIndirect); if (clz->status < CLASS_INITIALIZED && !dvmInitClass(clz)) { ALOGE("Could not initialize class %s", clz->descriptor); return; } obj->clazz = clz; } void XposedBridge_dumpObjectNative(JNIEnv* env, jclass clazz, jobject objIndirect) { Object* obj = (Object*) dvmDecodeIndirectRef(dvmThreadSelf(), objIndirect); dvmDumpObject(obj); } jobject XposedBridge_cloneToSubclassNative(JNIEnv* env, jclass clazz, jobject objIndirect, jclass clzIndirect) { Object* obj = (Object*) dvmDecodeIndirectRef(dvmThreadSelf(), objIndirect); ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef(dvmThreadSelf(), clzIndirect); jobject copyIndirect = env->AllocObject(clzIndirect); if (copyIndirect == NULL) return NULL; Object* copy = (Object*) dvmDecodeIndirectRef(dvmThreadSelf(), copyIndirect); size_t size = obj->clazz->objectSize; size_t offset = sizeof(Object); memcpy((char*)copy + offset, (char*)obj + offset, size - offset); if (IS_CLASS_FLAG_SET(clz, CLASS_ISFINALIZABLE)) dvmSetFinalizable(copy); return copyIndirect; } void XposedBridge_removeFinalFlagNative(JNIEnv* env, jclass, jclass javaClazz) { ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(dvmThreadSelf(), javaClazz); if (dvmIsFinalClass(clazz)) { clazz->accessFlags &= ~ACC_FINAL; } } jint XposedBridge_getRuntime(JNIEnv* env, jclass clazz) { return 1; // RUNTIME_DALVIK } } // namespace android ================================================ FILE: libxposed_dalvik.h ================================================ #ifndef LIBXPOSED_DALVIK_H_ #define LIBXPOSED_DALVIK_H_ #define ANDROID_SMP 1 #include "Dalvik.h" #include "libxposed_common.h" namespace xposed { #define XPOSED_OVERRIDE_JIT_RESET_OFFSET XPOSED_DIR "conf/jit_reset_offset" struct XposedHookInfo { struct { Method originalMethod; // copy a few bytes more than defined for Method in AOSP // to accomodate for (rare) extensions by the target ROM int dummyForRomExtensions[4]; } originalMethodStruct; Object* reflectedMethod; Object* additionalInfo; }; } // namespace xposed #endif // LIBXPOSED_DALVIK_H_ ================================================ FILE: xposed.cpp ================================================ /** * This file includes functions called directly from app_main.cpp during startup. */ #define LOG_TAG "Xposed" #include "xposed.h" #include "xposed_logcat.h" #include "xposed_safemode.h" #include "xposed_service.h" #include #include #include #include #include #include #include #include #include #include #include #if PLATFORM_SDK_VERSION >= 18 #include #else #include #endif namespace xposed { //////////////////////////////////////////////////////////// // Variables //////////////////////////////////////////////////////////// XposedShared* xposed = new XposedShared; static int sdkVersion = -1; static char* argBlockStart; static size_t argBlockLength; const char* xposedVersion = "unknown (invalid " XPOSED_PROP_FILE ")"; uint32_t xposedVersionInt = 0; //////////////////////////////////////////////////////////// // Functions //////////////////////////////////////////////////////////// /** Handle special command line options. */ bool handleOptions(int argc, char* const argv[]) { parseXposedProp(); if (argc == 2 && strcmp(argv[1], "--xposedversion") == 0) { printf("Xposed version: %s\n", xposedVersion); return true; } if (argc == 2 && strcmp(argv[1], "--xposedtestsafemode") == 0) { printf("Testing Xposed safemode trigger\n"); if (detectSafemodeTrigger(shouldSkipSafemodeDelay())) { printf("Safemode triggered\n"); } else { printf("Safemode not triggered\n"); } return true; } // From Lollipop coding, used to override the process name argBlockStart = argv[0]; uintptr_t start = reinterpret_cast(argv[0]); uintptr_t end = reinterpret_cast(argv[argc - 1]); end += strlen(argv[argc - 1]) + 1; argBlockLength = end - start; return false; } /** Initialize Xposed (unless it is disabled). */ bool initialize(bool zygote, bool startSystemServer, const char* className, int argc, char* const argv[]) { #if !defined(XPOSED_ENABLE_FOR_TOOLS) if (!zygote) return false; #endif if (isMinimalFramework()) { ALOGI("Not loading Xposed for minimal framework (encrypted device)"); return false; } xposed->zygote = zygote; xposed->startSystemServer = startSystemServer; xposed->startClassName = className; xposed->xposedVersionInt = xposedVersionInt; #if XPOSED_WITH_SELINUX xposed->isSELinuxEnabled = is_selinux_enabled() == 1; xposed->isSELinuxEnforcing = xposed->isSELinuxEnabled && security_getenforce() == 1; #else xposed->isSELinuxEnabled = false; xposed->isSELinuxEnforcing = false; #endif // XPOSED_WITH_SELINUX if (startSystemServer) { xposed::logcat::printStartupMarker(); } else if (zygote) { // TODO Find a better solution for this // Give the primary Zygote process a little time to start first. // This also makes the log easier to read, as logs for the two Zygotes are not mixed up. sleep(10); } printRomInfo(); if (startSystemServer) { if (!determineXposedInstallerUidGid() || !xposed::service::startAll()) { return false; } xposed::logcat::start(); #if XPOSED_WITH_SELINUX } else if (xposed->isSELinuxEnabled) { if (!xposed::service::startMembased()) { return false; } #endif // XPOSED_WITH_SELINUX } #if XPOSED_WITH_SELINUX // Don't let any further forks access the Zygote service if (xposed->isSELinuxEnabled) { xposed::service::membased::restrictMemoryInheritance(); } #endif // XPOSED_WITH_SELINUX // FIXME Zygote has no access to input devices, this would need to be check in system_server context if (zygote && !isSafemodeDisabled() && detectSafemodeTrigger(shouldSkipSafemodeDelay())) disableXposed(); if (isDisabled() || (!zygote && shouldIgnoreCommand(argc, argv))) return false; return addJarToClasspath(); } /** Print information about the used ROM into the log */ void printRomInfo() { char release[PROPERTY_VALUE_MAX]; char sdk[PROPERTY_VALUE_MAX]; char manufacturer[PROPERTY_VALUE_MAX]; char model[PROPERTY_VALUE_MAX]; char rom[PROPERTY_VALUE_MAX]; char fingerprint[PROPERTY_VALUE_MAX]; char platform[PROPERTY_VALUE_MAX]; #if defined(__LP64__) const int bit = 64; #else const int bit = 32; #endif property_get("ro.build.version.release", release, "n/a"); property_get("ro.build.version.sdk", sdk, "n/a"); property_get("ro.product.manufacturer", manufacturer, "n/a"); property_get("ro.product.model", model, "n/a"); property_get("ro.build.display.id", rom, "n/a"); property_get("ro.build.fingerprint", fingerprint, "n/a"); property_get("ro.product.cpu.abi", platform, "n/a"); ALOGI("-----------------"); ALOGI("Starting Xposed version %s, compiled for SDK %d", xposedVersion, PLATFORM_SDK_VERSION); ALOGI("Device: %s (%s), Android version %s (SDK %s)", model, manufacturer, release, sdk); ALOGI("ROM: %s", rom); ALOGI("Build fingerprint: %s", fingerprint); ALOGI("Platform: %s, %d-bit binary, system server: %s", platform, bit, xposed->startSystemServer ? "yes" : "no"); if (!xposed->zygote) { ALOGI("Class name: %s", xposed->startClassName); } ALOGI("SELinux enabled: %s, enforcing: %s", xposed->isSELinuxEnabled ? "yes" : "no", xposed->isSELinuxEnforcing ? "yes" : "no"); } /** Parses /system/xposed.prop and stores selected values in variables */ void parseXposedProp() { FILE *fp = fopen(XPOSED_PROP_FILE, "r"); if (fp == NULL) { ALOGE("Could not read %s: %s", XPOSED_PROP_FILE, strerror(errno)); return; } char buf[512]; while (fgets(buf, sizeof(buf), fp) != NULL) { char* key = buf; // Ignore leading spaces for the key while (isspace(*key)) key++; // Skip comments if (*key == '#') continue; // Find the key/value separator char* value = strchr(buf, '='); if (value == NULL) continue; // Ignore trailing spaces for the key char* tmp = value; do { *tmp = 0; tmp--; } while (isspace(*tmp)); // Ignore leading spaces for the value do { value++; } while (isspace(*value)); // Remove trailing newline tmp = strpbrk(value, "\n\r"); if (tmp != NULL) *tmp = 0; // Handle this entry if (!strcmp("version", key)) { int len = strlen(value); if (len == 0) continue; tmp = (char*) malloc(len + 1); strlcpy(tmp, value, len + 1); xposedVersion = tmp; xposedVersionInt = atoi(xposedVersion); } } fclose(fp); return; } /** Returns the SDK version of the system */ int getSdkVersion() { if (sdkVersion < 0) { char sdkString[PROPERTY_VALUE_MAX]; property_get("ro.build.version.sdk", sdkString, "0"); sdkVersion = atoi(sdkString); } return sdkVersion; } /** Check whether Xposed is disabled by a flag file */ bool isDisabled() { if (zygote_access(XPOSED_LOAD_BLOCKER, F_OK) == 0) { ALOGE("Found %s, not loading Xposed", XPOSED_LOAD_BLOCKER); return true; } return false; } /** Create a flag file to disable Xposed. */ void disableXposed() { int fd; // FIXME add a "touch" operation to xposed::service::membased fd = open(XPOSED_LOAD_BLOCKER, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); if (fd >= 0) close(fd); } /** Check whether safemode is disabled. */ bool isSafemodeDisabled() { if (zygote_access(XPOSED_SAFEMODE_DISABLE, F_OK) == 0) return true; else return false; } /** Check whether the delay for safemode should be skipped. */ bool shouldSkipSafemodeDelay() { if (zygote_access(XPOSED_SAFEMODE_NODELAY, F_OK) == 0) return true; else return false; } /** Ignore the broadcasts by various Superuser implementations to avoid spamming the Xposed log. */ bool shouldIgnoreCommand(int argc, const char* const argv[]) { if (argc < 4 || strcmp(xposed->startClassName, "com.android.commands.am.Am") != 0) return false; if (strcmp(argv[2], "broadcast") != 0 && strcmp(argv[2], "start") != 0) return false; bool mightBeSuperuser = false; for (int i = 3; i < argc; i++) { if (strcmp(argv[i], "com.noshufou.android.su.RESULT") == 0 || strcmp(argv[i], "eu.chainfire.supersu.NativeAccess") == 0) return true; if (mightBeSuperuser && strcmp(argv[i], "--user") == 0) return true; const char* lastComponent = strrchr(argv[i], '.'); if (!lastComponent) continue; if (strcmp(lastComponent, ".RequestActivity") == 0 || strcmp(lastComponent, ".NotifyActivity") == 0 || strcmp(lastComponent, ".SuReceiver") == 0) mightBeSuperuser = true; } return false; } /** Adds a path to the beginning of an environment variable. */ static bool addPathToEnv(const char* name, const char* path) { char* oldPath = getenv(name); if (oldPath == NULL) { setenv(name, path, 1); } else { char newPath[4096]; int neededLength = snprintf(newPath, sizeof(newPath), "%s:%s", path, oldPath); if (neededLength >= (int)sizeof(newPath)) { ALOGE("ERROR: %s would exceed %" PRIuPTR " characters", name, sizeof(newPath)); return false; } setenv(name, newPath, 1); } return true; } /** Add XposedBridge.jar to the Java classpath. */ bool addJarToClasspath() { ALOGI("-----------------"); // Do we have a new version and are (re)starting zygote? Then load it! /* FIXME if you can if (xposed->startSystemServer && access(XPOSED_JAR_NEWVERSION, R_OK) == 0) { ALOGI("Found new Xposed jar version, activating it"); if (rename(XPOSED_JAR_NEWVERSION, XPOSED_JAR) != 0) { ALOGE("Could not move %s to %s", XPOSED_JAR_NEWVERSION, XPOSED_JAR); return false; } } */ if (access(XPOSED_JAR, R_OK) == 0) { if (!addPathToEnv("CLASSPATH", XPOSED_JAR)) return false; ALOGI("Added Xposed (%s) to CLASSPATH", XPOSED_JAR); return true; } else { ALOGE("ERROR: Could not access Xposed jar '%s'", XPOSED_JAR); return false; } } /** Callback which checks the loaded shared libraries for libdvm/libart. */ static bool determineRuntime(const char** xposedLibPath) { FILE *fp = fopen("/proc/self/maps", "r"); if (fp == NULL) { ALOGE("Could not open /proc/self/maps: %s", strerror(errno)); return false; } bool success = false; char line[256]; while (fgets(line, sizeof(line), fp) != NULL) { char* libname = strrchr(line, '/'); if (!libname) continue; libname++; if (strcmp("libdvm.so\n", libname) == 0) { ALOGI("Detected Dalvik runtime"); *xposedLibPath = XPOSED_LIB_DALVIK; success = true; break; } else if (strcmp("libart.so\n", libname) == 0) { ALOGI("Detected ART runtime"); *xposedLibPath = XPOSED_LIB_ART; success = true; break; } } fclose(fp); return success; } /** Load the libxposed_*.so library for the currently active runtime. */ void onVmCreated(JNIEnv* env) { // Determine the currently active runtime const char* xposedLibPath = NULL; if (!determineRuntime(&xposedLibPath)) { ALOGE("Could not determine runtime, not loading Xposed"); return; } // Load the suitable libxposed_*.so for it void* xposedLibHandle = dlopen(xposedLibPath, RTLD_NOW); if (!xposedLibHandle) { ALOGE("Could not load libxposed: %s", dlerror()); return; } // Clear previous errors dlerror(); // Initialize the library bool (*xposedInitLib)(XposedShared* shared) = NULL; *(void **) (&xposedInitLib) = dlsym(xposedLibHandle, "xposedInitLib"); if (!xposedInitLib) { ALOGE("Could not find function xposedInitLib"); return; } #if XPOSED_WITH_SELINUX xposed->zygoteservice_accessFile = &service::membased::accessFile; xposed->zygoteservice_statFile = &service::membased::statFile; xposed->zygoteservice_readFile = &service::membased::readFile; #endif // XPOSED_WITH_SELINUX if (xposedInitLib(xposed)) { xposed->onVmCreated(env); } } /** Set the process name */ void setProcessName(const char* name) { memset(argBlockStart, 0, argBlockLength); strlcpy(argBlockStart, name, argBlockLength); set_process_name(name); } /** Determine the UID/GID of Xposed Installer. */ bool determineXposedInstallerUidGid() { if (xposed->isSELinuxEnabled) { struct stat* st = (struct stat*) mmap(NULL, sizeof(struct stat), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (st == MAP_FAILED) { ALOGE("Could not allocate memory in determineXposedInstallerUidGid(): %s", strerror(errno)); return false; } pid_t pid; if ((pid = fork()) < 0) { ALOGE("Fork in determineXposedInstallerUidGid() failed: %s", strerror(errno)); munmap(st, sizeof(struct stat)); return false; } else if (pid == 0) { // Child. #if XPOSED_WITH_SELINUX if (setcon(ctx_app) != 0) { ALOGE("Could not switch to %s context", ctx_app); exit(EXIT_FAILURE); } #endif // XPOSED_WITH_SELINUX if (TEMP_FAILURE_RETRY(stat(XPOSED_DIR, st)) != 0) { ALOGE("Could not stat %s: %s", XPOSED_DIR, strerror(errno)); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } // Parent. int status; if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) { munmap(st, sizeof(struct stat)); return false; } xposed->installer_uid = st->st_uid; xposed->installer_gid = st->st_gid; munmap(st, sizeof(struct stat)); return true; } else { struct stat st; if (TEMP_FAILURE_RETRY(stat(XPOSED_DIR, &st)) != 0) { ALOGE("Could not stat %s: %s", XPOSED_DIR, strerror(errno)); return false; } xposed->installer_uid = st.st_uid; xposed->installer_gid = st.st_gid; return true; } } /** Switch UID/GID to the ones of Xposed Installer. */ bool switchToXposedInstallerUidGid() { if (setresgid(xposed->installer_gid, xposed->installer_gid, xposed->installer_gid) != 0) { ALOGE("Could not setgid(%d): %s", xposed->installer_gid, strerror(errno)); return false; } if (setresuid(xposed->installer_uid, xposed->installer_uid, xposed->installer_uid) != 0) { ALOGE("Could not setuid(%d): %s", xposed->installer_uid, strerror(errno)); return false; } return true; } /** Drop all capabilities except for the mentioned ones */ void dropCapabilities(int8_t keep[]) { struct __user_cap_header_struct header; struct __user_cap_data_struct cap[2]; memset(&header, 0, sizeof(header)); memset(&cap, 0, sizeof(cap)); header.version = _LINUX_CAPABILITY_VERSION_3; header.pid = 0; if (keep != NULL) { for (int i = 0; keep[i] >= 0; i++) { cap[CAP_TO_INDEX(keep[i])].permitted |= CAP_TO_MASK(keep[i]); } cap[0].effective = cap[0].inheritable = cap[0].permitted; cap[1].effective = cap[1].inheritable = cap[1].permitted; } capset(&header, &cap[0]); } /** * Checks whether the system is booting into a minimal Android framework. * This is the case when the device is encrypted with a password that * has to be entered on boot. /data is a tmpfs in that case, so we * can't load any modules anyway. * The system will reboot later with the full framework. */ bool isMinimalFramework() { char voldDecrypt[PROPERTY_VALUE_MAX]; property_get("vold.decrypt", voldDecrypt, ""); return ((strcmp(voldDecrypt, "trigger_restart_min_framework") == 0) || (strcmp(voldDecrypt, "1") == 0)); } } // namespace xposed ================================================ FILE: xposed.h ================================================ #ifndef XPOSED_H_ #define XPOSED_H_ #include "xposed_shared.h" #define XPOSED_PROP_FILE "/system/xposed.prop" #if defined(__LP64__) #define XPOSED_LIB_DIR "/system/lib64/" #else #define XPOSED_LIB_DIR "/system/lib/" #endif #define XPOSED_LIB_DALVIK XPOSED_LIB_DIR "libxposed_dalvik.so" #define XPOSED_LIB_ART XPOSED_LIB_DIR "libxposed_art.so" #define XPOSED_JAR "/system/framework/XposedBridge.jar" #define XPOSED_JAR_NEWVERSION XPOSED_DIR "bin/XposedBridge.jar.newversion" #define XPOSED_LOAD_BLOCKER XPOSED_DIR "conf/disabled" #define XPOSED_SAFEMODE_NODELAY XPOSED_DIR "conf/safemode_nodelay" #define XPOSED_SAFEMODE_DISABLE XPOSED_DIR "conf/safemode_disable" #define XPOSED_CLASS_DOTS_ZYGOTE "de.robv.android.xposed.XposedBridge" #define XPOSED_CLASS_DOTS_TOOLS "de.robv.android.xposed.XposedBridge$ToolEntryPoint" #if XPOSED_WITH_SELINUX #include #define ctx_system ((security_context_t) "u:r:system_server:s0") #if PLATFORM_SDK_VERSION >= 23 #define ctx_app ((security_context_t) "u:r:untrusted_app:s0:c512,c768") #else #define ctx_app ((security_context_t) "u:r:untrusted_app:s0") #endif // PLATFORM_SDK_VERSION >= 23 #endif // XPOSED_WITH_SELINUX namespace xposed { bool handleOptions(int argc, char* const argv[]); bool initialize(bool zygote, bool startSystemServer, const char* className, int argc, char* const argv[]); void printRomInfo(); void parseXposedProp(); int getSdkVersion(); bool isDisabled(); void disableXposed(); bool isSafemodeDisabled(); bool shouldSkipSafemodeDelay(); bool shouldIgnoreCommand(int argc, const char* const argv[]); bool addJarToClasspath(); void onVmCreated(JNIEnv* env); void setProcessName(const char* name); bool determineXposedInstallerUidGid(); bool switchToXposedInstallerUidGid(); void dropCapabilities(int8_t keep[] = NULL); bool isMinimalFramework(); } // namespace xposed #endif // XPOSED_H_ ================================================ FILE: xposed_logcat.cpp ================================================ /** * This file includes the Xposed service, which is especially used to work around SELinux restrictions. */ #define LOG_TAG "Xposed" #include #include #include #include #include #include #include "xposed.h" #include "xposed_service.h" #include "xposed_logcat.h" namespace xposed { namespace logcat { //////////////////////////////////////////////////////////// // Declarations //////////////////////////////////////////////////////////// #define AID_LOG 1007 #define CAP_SYSLOG 34 char marker[50]; //////////////////////////////////////////////////////////// // Functions //////////////////////////////////////////////////////////// static void execLogcat() { int8_t keep[] = { CAP_SYSLOG, -1 }; xposed::dropCapabilities(keep); // Execute a logcat command that will keep running in the background if (zygote_access(XPOSEDLOG_CONF_ALL, F_OK) == 0) { execl("/system/bin/logcat", "logcat", "-v", "time", // include timestamps in the log (char*) 0); } else { execl("/system/bin/logcat", "logcat", "-v", "time", // include timestamps in the log "-s", // be silent by default, except for the following tags "XposedStartupMarker:D", // marks the beginning of the current log "Xposed:I", // Xposed framework and default logging "appproc:I", // app_process "XposedInstaller:I", // Xposed Installer "art:F", // ART crashes (char*) 0); } // We only get here in case of errors ALOGE("Could not execute logcat: %s", strerror(errno)); exit(EXIT_FAILURE); } #ifndef dprintf static inline int dprintf(int fd, const char *format, ...) { char* message; va_list args; va_start(args, format); int size = vasprintf(&message, format, args); if (size > 0) { write(fd, message, size); free(message); } va_end(args); return size; } #endif static void runDaemon(int pipefd) { xposed::setProcessName("xposed_logcat"); xposed::dropCapabilities(); umask(0); int logfile = open(XPOSEDLOG, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (logfile < 0) { ALOGE("Could not open %s: %s", XPOSEDLOG, strerror(errno)); exit(EXIT_FAILURE); } FILE* pipe = fdopen(pipefd, "r"); if (pipe == NULL) { ALOGE("fdopen failed for pipe file descriptor %d: %s", pipefd, strerror(errno)); exit(EXIT_FAILURE); } char buf[512]; bool foundMarker = false; long totalSize = 0; while (fgets(buf, sizeof(buf), pipe) != NULL) { if (buf[0] == '-') continue; // beginning of if (!foundMarker) { if (strstr(buf, "XposedStartupMarker") != NULL && strstr(buf, marker) != NULL) { foundMarker = true; } continue; } int len = strlen(buf); write(logfile, buf, len); totalSize += len; if (totalSize > XPOSEDLOG_MAX_SIZE) { dprintf(logfile, "\nReached maximum log size (%'d kB), further lines won't be logged.\n", XPOSEDLOG_MAX_SIZE / 1024); exit(EXIT_FAILURE); } } ALOGE("Broken pipe to logcat: %s", strerror(ferror(pipe))); close(logfile); exit(EXIT_FAILURE); } void printStartupMarker() { sprintf(marker, "Current time: %d, PID: %d", (int) time(NULL), getpid()); ALOG(LOG_DEBUG, "XposedStartupMarker", marker, NULL); } void start() { // Fork to create a daemon pid_t pid; if ((pid = fork()) < 0) { ALOGE("Fork for Xposed logcat daemon failed: %s", strerror(errno)); return; } else if (pid != 0) { return; } // Ensure that we're allowed to read all log entries if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) { ALOGE("Failed to keep capabilities: %s", strerror(errno)); } const gid_t groups[] = { AID_LOG }; setgroups(1, groups); if (!xposed::switchToXposedInstallerUidGid()) { exit(EXIT_FAILURE); } #if XPOSED_WITH_SELINUX if (xposed->isSELinuxEnabled) { if (setcon(ctx_app) != 0) { ALOGE("Could not switch to %s context", ctx_app); exit(EXIT_FAILURE); } } #endif // XPOSED_WITH_SELINUX int err = rename(XPOSEDLOG, XPOSEDLOG_OLD); if (err < 0 && errno != ENOENT) { ALOGE("%s while renaming log file %s -> %s", strerror(errno), XPOSEDLOG, XPOSEDLOG_OLD); exit(EXIT_FAILURE); } int pipeFds[2]; if (pipe(pipeFds) < 0) { ALOGE("Could not allocate pipe for logcat output: %s", strerror(errno)); exit(EXIT_FAILURE); } fcntl(pipeFds[0], F_SETPIPE_SZ, 1048576); if ((pid = fork()) < 0) { ALOGE("Fork for logcat execution failed: %s", strerror(errno)); exit(EXIT_FAILURE); } else if (pid == 0) { close(pipeFds[0]); if (dup2(pipeFds[1], STDOUT_FILENO) == -1) { ALOGE("Could not redirect stdout: %s", strerror(errno)); exit(EXIT_FAILURE); } if (dup2(pipeFds[1], STDERR_FILENO) == -1) { ALOGE("Could not redirect stdout: %s", strerror(errno)); exit(EXIT_FAILURE); } execLogcat(); } else { close(pipeFds[1]); runDaemon(pipeFds[0]); } // Should never reach this point exit(EXIT_FAILURE); } } // namespace logcat } // namespace xposed ================================================ FILE: xposed_logcat.h ================================================ #ifndef XPOSED_LOGCAT_H_ #define XPOSED_LOGCAT_H_ #define XPOSEDLOG XPOSED_DIR "log/error.log" #define XPOSEDLOG_OLD XPOSEDLOG ".old" #define XPOSEDLOG_CONF_ALL XPOSED_DIR "conf/log_all" #define XPOSEDLOG_MAX_SIZE 5*1024*1024 namespace xposed { namespace logcat { void printStartupMarker(); void start(); } // namespace logcat } // namespace xposed #endif /* XPOSED_LOGCAT_H_ */ ================================================ FILE: xposed_offsets.h ================================================ /* Certain compile time parameters result in different offsets for members in structures. This file defines the offsets for members which cannot be accessed otherwise and some macros to simplify accessing them. */ #define MEMBER_OFFSET_ARRAY(type,member) offsets_array_ ## type ## _ ## member #define MEMBER_OFFSET_VAR(type,member) offset_ ## type ## _ ## member #define MEMBER_TYPE(type,member) offset_type_ ## type ## _ ## member #define MEMBER_PTR(obj,type,member) \ ( (MEMBER_TYPE(type,member)*) ( (char*)(obj) + MEMBER_OFFSET_VAR(type,member) ) ) #define MEMBER_VAL(obj,type,member) *MEMBER_PTR(obj,type,member) #define MEMBER_OFFSET_DEFINE(type,member,offsets...) \ static int MEMBER_OFFSET_ARRAY(type,member)[] = { offsets }; \ static int MEMBER_OFFSET_VAR(type,member); #define MEMBER_OFFSET_COPY(type,member) MEMBER_OFFSET_VAR(type,member) = MEMBER_OFFSET_ARRAY(type,member)[offsetMode] // here are the definitions of the modes and offsets enum xposedOffsetModes { MEMBER_OFFSET_MODE_WITH_JIT, MEMBER_OFFSET_MODE_NO_JIT, }; static xposedOffsetModes offsetMode; const char* xposedOffsetModesDesc[] = { "WITH_JIT", "NO_JIT", }; MEMBER_OFFSET_DEFINE(DvmJitGlobals, codeCacheFull, 120, 0) #define offset_type_DvmJitGlobals_codeCacheFull bool // helper to determine the required values (compile with XPOSED_SHOW_OFFSET=true) #ifdef XPOSED_SHOW_OFFSETS template struct RESULT; #ifdef WITH_JIT #pragma message "WITH_JIT is defined" #else #pragma message "WITH_JIT is not defined" #endif RESULT SIZEOF_Method; RESULT SIZEOF_Thread; RESULT OFFSETOF_DvmJitGlobals_codeCacheFull; #endif ================================================ FILE: xposed_safemode.cpp ================================================ /* * Detects input combinations for recovering from bootloops. * * The safemode trigger is detected if exactly one of the physical keys is pressed in * the first 2 seconds after detection startup (or already held down), and a total of * 5 consecutive presses of that same key are performed in the subsequent 5 seconds. * * 2 short vibrations are performed when the first key is pressed; an additional * vibration is performed for each subsequent press of the same key, and a final * long vibration is performed if the trigger was successful. * * The initial 2-second delay can be disabled through configuration; in that case, * one of the keys must already be pressed when the detection starts, otherwise * the detection fails and no delays are introduced. * * References: * /frameworks/base/services/input/EventHub.cpp (AOSP) * /include/uapi/linux/input.h (Linux) * Using the Input Subsystem, Linux Journal */ #include "xposed_safemode.h" #include #include #include #include #include #include #include #include #define INITIAL_DELAY 2 #define DETECTION_TIMEOUT 5 #define DETECTION_PRESSES 5 #define VIBRATOR_CONTROL "/sys/class/timed_output/vibrator/enable" #define VIBRATION_SHORT 150 #define VIBRATION_LONG 500 #define VIBRATION_INTERVAL 200 static const char *DEVICE_PATH = "/dev/input"; #define MAX_DEVICES 4 #define test_bit(bit, array) (array[bit/8] & (1<<(bit%8))) static const int physical_keycodes[] = { KEY_VOLUMEDOWN, KEY_VOLUMEUP, KEY_POWER, KEY_HOME, KEY_BACK, KEY_MENU, KEY_CAMERA }; static void vibrate(int count, int duration_ms, int interval_ms) { int fd; int len; char value[30]; if ((fd = open(VIBRATOR_CONTROL, O_RDWR)) < 0) // Failed to open the control file, ignore it return; len = sprintf(value, "%d\n", duration_ms); for (int i = 0; i < count; i++) { if (i != 0) // Pause between the several vibrations usleep((duration_ms + interval_ms) * 1000); // Vibrate (asynchronously) write(fd, value, len); } close(fd); } /* * Enumerates the existing input devices and opens handles for the ones that * report the relevant keys. * * Arguments: * - *fds: is filled on output with the file handles for the opened devices * - max_fds: maximum available entries in the fds array * - *pressedKey: is filled on output with * 0 if no key was found being held down at this instant * -1 if more than one key was found being held down * id of the pressed key, if only a single one was being held down * Returns: * - the number of opened device handles, filled in the *fds output parameter * - 0 if no devices were opened */ static int openKeyDevices(int *fds, int max_fds, int *pressedKey) { char devname[PATH_MAX]; char *filename; DIR *dir; struct dirent *de; int count = 0; // No key was detected as pressed, for the moment *pressedKey = 0; dir = opendir(DEVICE_PATH); if(dir == NULL) return 0; strcpy(devname, DEVICE_PATH); filename = devname + strlen(devname); *filename++ = '/'; while (count < max_fds && (de = readdir(dir))) { // Skip '.' and '..' if(de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) continue; strcpy(filename, de->d_name); int fd = open(devname, O_RDWR | O_CLOEXEC); if(fd < 0) // Skip files that could not be opened continue; // Check if this device reports one of the relevant keys uint8_t keyBitmask[(KEY_MAX + 1) / 8]; memset(keyBitmask, 0, sizeof(keyBitmask)); ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBitmask)), keyBitmask); bool reportsKeys = false; for (size_t i = 0; i < sizeof(physical_keycodes) / sizeof(physical_keycodes[0]); i++) { if (test_bit(physical_keycodes[i], keyBitmask)) { reportsKeys = true; break; } } if (!reportsKeys) { // This device doesn't report any of the relevant keys // Skip to the next one close(fd); continue; } fds[count++] = fd; // Check if one of the keys is currently pressed on this device, to report it to the caller memset(keyBitmask, 0, sizeof(keyBitmask)); ioctl(fd, EVIOCGKEY(sizeof(keyBitmask)), keyBitmask); for (size_t i = 0; i < sizeof(physical_keycodes) / sizeof(physical_keycodes[0]); i++) { if (test_bit(physical_keycodes[i], keyBitmask)) { // One of the relevant keys was detected as held down // We'll report it to be pressed, but only if there isn't more than one key being pressed if (*pressedKey == 0) { // No key was being pressed, this one will be reported *pressedKey = physical_keycodes[i]; } else { // Another key was already found to be pressed, report multiple keys to the caller *pressedKey = -1; break; } } } } closedir(dir); return count; } /* * Computes the remaining time, in ms, from the current time to the supplied expiration moment */ int getRemainingTime(struct timespec expiration) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); if (now.tv_sec > expiration.tv_sec) return 0; else return (expiration.tv_sec - now.tv_sec) * 1000 + (expiration.tv_nsec - now.tv_nsec) / 1000000; } namespace xposed { bool detectSafemodeTrigger(bool skipInitialDelay) { int efd = -1; int fds[MAX_DEVICES]; int deviceCount = 0; int pressedKey = 0; int triggerPresses = 0; bool result = false; // Open input devices that report one of the relevant physical keys deviceCount = openKeyDevices(fds, sizeof(fds) / sizeof(fds[0]), &pressedKey); if (deviceCount == 0) // No input devices found, abort detection goto leave; if (pressedKey < 0) // More than one key was initially pressed // Immediately report a negative detection, with no further delays goto leave; if (pressedKey == 0 && skipInitialDelay) // A single key wasn't held down and the initial delay is disabled // Immediately report a negative detection, with no further delays goto leave; // Prepare waiting mechanism for received events in all devices if ((efd = epoll_create(deviceCount)) < 0) // Failed to create the epoll handle, abort goto leave; // Register each device descriptor in the epoll handle struct epoll_event eventPollItems[MAX_DEVICES]; for (int i = 0; i < deviceCount; i++) { memset(&eventPollItems[i], 0, sizeof(eventPollItems[i])); eventPollItems[i].events = EPOLLIN; eventPollItems[i].data.fd = fds[i]; if (epoll_ctl(efd, EPOLL_CTL_ADD, fds[i], &eventPollItems[i])) // Failed to add device descriptor to the epoll handle, abort goto leave; } int timeout_ms; struct timespec expiration; clock_gettime(CLOCK_MONOTONIC, &expiration); expiration.tv_sec += INITIAL_DELAY; // Wait up to INITIAL_DELAY seconds for an initial keypress, it no key was initially down while (pressedKey == 0 && (timeout_ms = getRemainingTime(expiration)) > 0) { // Wait for next input event in one of the opened devices int pollResult = epoll_wait(efd, eventPollItems, sizeof(eventPollItems) / sizeof(eventPollItems[0]), timeout_ms); if (pollResult < 0) // Failed to wait for event, abort goto leave; // Loop through the opened devices where a new event is available for (int i = 0; i < pollResult; i++) { struct input_event evt; int32_t readSize = read(eventPollItems[i].data.fd, &evt, sizeof(evt)); if (readSize != sizeof(evt)) // Invalid size read, ignore continue; if (evt.type != EV_KEY) // Only consider key events continue; if (evt.value != 1) // Ignore key releases, we're monitoring presses continue; for (size_t j = 0; j < sizeof(physical_keycodes) / sizeof(physical_keycodes[0]); j++) { if (evt.code == physical_keycodes[j]) { // One of the keys was pressed, end the initial detection // No need to check for duplicate keys, as the events are reported sequentially // and multiple presses can't be reported at once pressedKey = evt.code; break; } } } } if (pressedKey == 0) // No key was pressed during the initial delay or upfront, so the detection has failed goto leave; // Notify the user that the safemode sequence has been started and we're waiting for // the remaining key presses vibrate(2, VIBRATION_SHORT, VIBRATION_INTERVAL); // Detection will wait at most DETECTION_TIMEOUT seconds clock_gettime(CLOCK_MONOTONIC, &expiration); expiration.tv_sec += DETECTION_TIMEOUT; // Initial key press is counted as well triggerPresses++; // Loop waiting for the same key to be pressed the appropriate number of times, a different key to // be pressed, or the timeout to be reached while (triggerPresses < DETECTION_PRESSES && (timeout_ms = getRemainingTime(expiration)) > 0) { // Wait for next input event int pollResult = epoll_wait(efd, eventPollItems, sizeof(eventPollItems) / sizeof(eventPollItems[0]), timeout_ms); if (pollResult < 0) // Failed to wait for event, abort goto leave; // Loop through the opened devices where a new event is available for (int i = 0; i < pollResult; i++) { struct input_event evt; int32_t readSize = read(eventPollItems[i].data.fd, &evt, sizeof(evt)); if (readSize != sizeof(evt)) // Invalid size read, ignore continue; if (evt.type != EV_KEY) // Only consider key events continue; if (evt.value != 1) // Ignore key releases, we're monitoring presses continue; for (size_t j = 0; j < sizeof(physical_keycodes) / sizeof(physical_keycodes[0]); j++) { if (evt.code == physical_keycodes[j]) { if (pressedKey == evt.code) { // The same key was pressed again, increment the counter and notify the user triggerPresses++; // The final key press will be confirmed with a long vibration later if (triggerPresses < DETECTION_PRESSES) vibrate(1, VIBRATION_SHORT, 0); } else { // A key was pressed other than the initial one // Abort the detection and avoid further delays goto leave; } break; } } } } // Was safemode successfully triggered? if (triggerPresses >= DETECTION_PRESSES) { vibrate(1, VIBRATION_LONG, 0); result = true; } leave: if (efd >= 0) close(efd); for (int i = 0; i < deviceCount; i++) close(fds[i]); return result; } } ================================================ FILE: xposed_safemode.h ================================================ #ifndef XPOSED_SAFEMODE_H_ #define XPOSED_SAFEMODE_H_ namespace xposed { bool detectSafemodeTrigger(bool skipInitialDelay); } #endif // XPOSED_SAFEMODE_H_ ================================================ FILE: xposed_service.cpp ================================================ /** * This file includes the Xposed services, which are especially used to work around SELinux restrictions. */ #define LOG_TAG "Xposed" #include "xposed.h" #include "xposed_service.h" #include #include #include #include #include #include #include #include #define __STDC_FORMAT_MACROS #include #include #define UID_SYSTEM 1000 using namespace android; namespace xposed { namespace service { //////////////////////////////////////////////////////////// // Declarations //////////////////////////////////////////////////////////// bool running = false; //////////////////////////////////////////////////////////// // Memory-based communication (used by Zygote) //////////////////////////////////////////////////////////// namespace membased { enum State { STATE_NOT_RUNNING, STATE_IDLE, STATE_SERVICE_ACTION, STATE_SERVER_RESPONSE, }; enum Action { OP_NONE, OP_ACCESS_FILE, OP_STAT_FILE, OP_READ_FILE, }; struct AccessFileData { // in char path[PATH_MAX]; int mode; // out int result; }; struct StatFileData { // in char path[PATH_MAX]; // inout struct stat st; // out int result; }; struct ReadFileData { // in char path[PATH_MAX]; int offset; // out int totalSize; int bytesRead; bool eof; char content[32*1024]; }; struct MemBasedState { pthread_mutex_t workerMutex; pthread_cond_t workerCond; State state; Action action; int error; union { AccessFileData accessFile; StatFileData statFile; ReadFileData readFile; } data; }; MemBasedState* shared = NULL; pid_t zygotePid = 0; bool canAlwaysAccessService = false; inline static void initSharedMutex(pthread_mutex_t* mutex) { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); pthread_mutex_init(mutex, &attr); pthread_mutexattr_destroy(&attr); } inline static void initSharedCond(pthread_cond_t* cond) { pthread_condattr_t cattr; pthread_condattr_init(&cattr); pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED); pthread_cond_init(cond, &cattr); pthread_condattr_destroy(&cattr); } static bool init() { shared = (MemBasedState*) mmap(NULL, sizeof(MemBasedState), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (shared == MAP_FAILED) { ALOGE("Could not allocate memory for Zygote service: %s", strerror(errno)); shared = NULL; return false; } zygotePid = getpid(); canAlwaysAccessService = true; initSharedMutex(&shared->workerMutex); initSharedCond(&shared->workerCond); shared->state = STATE_NOT_RUNNING; shared->action = OP_NONE; shared->error = 0; return true; } void restrictMemoryInheritance() { madvise(shared, sizeof(MemBasedState), MADV_DONTFORK); canAlwaysAccessService = false; } static inline bool isServiceAccessible() { if (!canAlwaysAccessService && (shared == NULL || zygotePid != getpid())) { ALOGE("Zygote service is not accessible from PID %d, UID %d", getpid(), getuid()); shared = NULL; errno = EPERM; return false; } return true; } // Server implementation void* looper(void* unused __attribute__((unused))) { pthread_mutex_lock(&shared->workerMutex); shared->state = STATE_IDLE; pthread_cond_broadcast(&shared->workerCond); while (1) { while (shared->state != STATE_SERVICE_ACTION) { pthread_cond_wait(&shared->workerCond, &shared->workerMutex); } switch (shared->action) { case OP_ACCESS_FILE: { struct AccessFileData* data = &shared->data.accessFile; data->result = TEMP_FAILURE_RETRY(access(data->path, data->mode)); if (data->result != 0) { shared->error = errno; } } break; case OP_STAT_FILE: { struct StatFileData* data = &shared->data.statFile; data->result = TEMP_FAILURE_RETRY(stat(data->path, &data->st)); if (data->result != 0) { shared->error = errno; } } break; case OP_READ_FILE: { struct ReadFileData* data = &shared->data.readFile; struct stat st; if (stat(data->path, &st) != 0) { shared->error = errno; break; } data->totalSize = st.st_size; FILE *f = fopen(data->path, "r"); if (f == NULL) { shared->error = errno; break; } if (data->offset > 0 && fseek(f, data->offset, SEEK_SET) != 0) { shared->error = ferror(f); fclose(f); break; } data->bytesRead = fread(data->content, 1, sizeof(data->content), f); shared->error = ferror(f); data->eof = feof(f); fclose(f); } break; case OP_NONE: { ALOGE("No-op call to membased service"); break; } default: { ALOGE("Invalid action in call to membased service"); break; } } shared->state = STATE_SERVER_RESPONSE; pthread_cond_broadcast(&shared->workerCond); } pthread_mutex_unlock(&shared->workerMutex); return NULL; } // Client implementation static inline bool waitForRunning(int timeout) { if (shared == NULL || timeout < 0) return false; struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 5; int rc = 0; pthread_mutex_lock(&shared->workerMutex); while (shared->state == STATE_NOT_RUNNING && rc == 0) { rc = pthread_cond_timedwait(&shared->workerCond, &shared->workerMutex, &ts); } pthread_mutex_unlock(&shared->workerMutex); return rc == 0; } static inline void waitForIdle() { pthread_mutex_lock(&shared->workerMutex); while (shared->state != STATE_IDLE) { pthread_cond_wait(&shared->workerCond, &shared->workerMutex); } } static inline void callService(Action action) { shared->action = action; shared->state = STATE_SERVICE_ACTION; shared->error = 0; pthread_cond_broadcast(&shared->workerCond); while (shared->state != STATE_SERVER_RESPONSE) { pthread_cond_wait(&shared->workerCond, &shared->workerMutex); } } static inline void makeIdle() { shared->action = OP_NONE; shared->state = STATE_IDLE; pthread_cond_broadcast(&shared->workerCond); pthread_mutex_unlock(&shared->workerMutex); } int accessFile(const char* path, int mode) { if (!isServiceAccessible()) return -1; if (strlen(path) > sizeof(AccessFileData::path) - 1) { errno = ENAMETOOLONG; return -1; } waitForIdle(); struct AccessFileData* data = &shared->data.accessFile; strcpy(data->path, path); data->mode = mode; callService(OP_ACCESS_FILE); makeIdle(); errno = shared->error; return shared->error ? -1 : data->result; } int statFile(const char* path, struct stat* st) { if (!isServiceAccessible()) return -1; if (strlen(path) > sizeof(StatFileData::path) - 1) { errno = ENAMETOOLONG; return -1; } waitForIdle(); struct StatFileData* data = &shared->data.statFile; strcpy(data->path, path); callService(OP_STAT_FILE); memcpy(st, &data->st, sizeof(struct stat)); makeIdle(); errno = shared->error; return shared->error ? -1 : data->result; } char* readFile(const char* path, int* bytesRead) { if (!isServiceAccessible()) return NULL; char* result = NULL; int offset = 0, totalSize = 0; if (bytesRead) *bytesRead = 0; if (strlen(path) > sizeof(ReadFileData::path) - 1) { errno = ENAMETOOLONG; return NULL; } waitForIdle(); struct ReadFileData* data = &shared->data.readFile; strcpy(data->path, path); data->offset = 0; callService(OP_READ_FILE); if (shared->error) goto bail; totalSize = data->totalSize; result = (char*) malloc(totalSize + 1); result[totalSize] = 0; memcpy(result, data->content, data->bytesRead); while (!data->eof) { offset += data->bytesRead; data->offset = offset; callService(OP_READ_FILE); if (shared->error) goto bail; if (offset + data->bytesRead > totalSize) { shared->error = EBUSY; goto bail; } memcpy(result + offset, data->content, data->bytesRead); } if (bytesRead) *bytesRead = offset + data->bytesRead; bail: makeIdle(); if (shared->error && result) { free(result); result = NULL; } errno = shared->error; return result; } } // namespace membased //////////////////////////////////////////////////////////// // Binder service //////////////////////////////////////////////////////////// namespace binder { #define XPOSED_BINDER_SYSTEM_SERVICE_NAME "user.xposed.system" #define XPOSED_BINDER_APP_SERVICE_NAME "user.xposed.app" class IXposedService: public IInterface { public: DECLARE_META_INTERFACE(XposedService); virtual int test() const = 0; virtual status_t addService(const String16& name, const sp& service, bool allowIsolated = false) const = 0; virtual int accessFile(const String16& filename, int32_t mode) const = 0; virtual int statFile(const String16& filename, int64_t* size, int64_t* mtime) const = 0; virtual status_t readFile(const String16& filename, int32_t offset, int32_t length, int64_t* size, int64_t* mtime, uint8_t** buffer, int32_t* bytesRead, String16* errormsg) const = 0; enum { TEST_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION, ADD_SERVICE_TRANSACTION, ACCESS_FILE_TRANSACTION, STAT_FILE_TRANSACTION, READ_FILE_TRANSACTION, }; }; class BnXposedService: public BnInterface { public: virtual status_t onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0); }; class BpXposedService: public BpInterface { public: BpXposedService(const sp& impl) : BpInterface(impl) {} virtual int test() const { Parcel data, reply; data.writeInterfaceToken(IXposedService::getInterfaceDescriptor()); remote()->transact(TEST_TRANSACTION, data, &reply); if (reply.readExceptionCode() != 0) return -1; return reply.readInt32(); } virtual status_t addService(const String16& name, const sp& service, bool allowIsolated = false) const { Parcel data, reply; data.writeInterfaceToken(IXposedService::getInterfaceDescriptor()); data.writeString16(name); data.writeStrongBinder(service); data.writeInt32(allowIsolated ? 1 : 0); status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply); return err == NO_ERROR ? reply.readExceptionCode() : err; } virtual status_t accessFile(const String16& name, int32_t mode) const { Parcel data, reply; data.writeInterfaceToken(IXposedService::getInterfaceDescriptor()); data.writeString16(name); data.writeInt32(mode); remote()->transact(ACCESS_FILE_TRANSACTION, data, &reply); if (reply.readExceptionCode() != 0) return -1; errno = reply.readInt32(); return (errno == 0) ? 0 : -1; } virtual status_t statFile(const String16& name, int64_t* size, int64_t* mtime) const { Parcel data, reply; data.writeInterfaceToken(IXposedService::getInterfaceDescriptor()); data.writeString16(name); remote()->transact(STAT_FILE_TRANSACTION, data, &reply); if (reply.readExceptionCode() != 0) return -1; errno = reply.readInt32(); if (errno != 0) return -1; int64_t size1 = reply.readInt64(); int64_t mtime1 = reply.readInt64(); if (size != NULL) *size = size1; if (mtime != NULL) *mtime = mtime1; return 0; } virtual status_t readFile(const String16& filename, int32_t offset, int32_t length, int64_t* size, int64_t* mtime, uint8_t** buffer, int32_t* bytesRead, String16* errormsg) const { Parcel data, reply; data.writeInterfaceToken(IXposedService::getInterfaceDescriptor()); data.writeString16(filename); data.writeInt32(offset); data.writeInt32(length); int64_t size1 = 0; int64_t mtime1 = 0; if (size != NULL) size1 = *size; if (mtime != NULL) mtime1 = *mtime; data.writeInt64(size1); data.writeInt64(mtime1); remote()->transact(READ_FILE_TRANSACTION, data, &reply); if (reply.readExceptionCode() != 0) return -1; status_t err = reply.readInt32(); const String16& errormsg1(reply.readString16()); size1 = reply.readInt64(); mtime1 = reply.readInt64(); int32_t bytesRead1 = reply.readInt32(); if (size != NULL) *size = size1; if (mtime != NULL) *mtime = mtime1; if (bytesRead != NULL) *bytesRead = bytesRead1; if (errormsg) *errormsg = errormsg1; if (bytesRead1 > 0 && bytesRead1 <= (int32_t)reply.dataAvail()) { *buffer = (uint8_t*) malloc(bytesRead1 + 1); *buffer[bytesRead1] = 0; reply.read(*buffer, bytesRead1); } else { *buffer = NULL; } errno = err; return (errno == 0) ? 0 : -1; } }; IMPLEMENT_META_INTERFACE(XposedService, "de.robv.android.xposed.IXposedService"); status_t BnXposedService::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { switch (code) { case TEST_TRANSACTION: { CHECK_INTERFACE(IXposedService, data, reply); reply->writeNoException(); reply->writeInt32(test()); return NO_ERROR; } break; case ADD_SERVICE_TRANSACTION: { CHECK_INTERFACE(IXposedService, data, reply); String16 which = data.readString16(); sp b = data.readStrongBinder(); bool allowIsolated = (data.readInt32() != 0); reply->writeInt32(addService(which, b, allowIsolated)); return NO_ERROR; } break; case ACCESS_FILE_TRANSACTION: { CHECK_INTERFACE(IXposedService, data, reply); String16 filename = data.readString16(); int32_t mode = data.readInt32(); status_t result = accessFile(filename, mode); int err = errno; reply->writeNoException(); reply->writeInt32(result == 0 ? 0 : err); return NO_ERROR; } break; case STAT_FILE_TRANSACTION: { CHECK_INTERFACE(IXposedService, data, reply); String16 filename = data.readString16(); int64_t size, time; status_t result = statFile(filename, &size, &time); int err = errno; reply->writeNoException(); if (result == 0) { reply->writeInt32(0); reply->writeInt64(size); reply->writeInt64(time); } else { reply->writeInt32(err); } return NO_ERROR; } break; case READ_FILE_TRANSACTION: { CHECK_INTERFACE(IXposedService, data, reply); String16 filename = data.readString16(); int32_t offset = data.readInt32(); int32_t length = data.readInt32(); int64_t size = data.readInt64(); int64_t mtime = data.readInt64(); uint8_t* buffer = NULL; int32_t bytesRead = -1; String16 errormsg; status_t err = readFile(filename, offset, length, &size, &mtime, &buffer, &bytesRead, &errormsg); reply->writeNoException(); reply->writeInt32(err); reply->writeString16(errormsg); reply->writeInt64(size); reply->writeInt64(mtime); if (bytesRead > 0) { reply->writeInt32(bytesRead); reply->write(buffer, bytesRead); free(buffer); } else { reply->writeInt32(bytesRead); // empty array (0) or null (-1) } return NO_ERROR; } break; default: return BBinder::onTransact(code, data, reply, flags); } } class XposedService : public BnXposedService { public: XposedService(bool system); virtual int test() const; virtual status_t addService(const String16& name, const sp& service, bool allowIsolated = false) const; virtual status_t accessFile(const String16& filename16, int32_t mode) const; virtual status_t statFile(const String16& filename, int64_t* size, int64_t* mtime) const; virtual status_t readFile(const String16& filename16, int32_t offset, int32_t length, int64_t* size, int64_t* mtime, uint8_t** buffer, int32_t* bytesRead, String16* errormsg) const; private: bool isSystem; }; static String16 formatToString16(const char* fmt, ...) { char* message; va_list args; va_start(args, fmt); int size = vasprintf(&message, fmt, args); String16 result(message, size); free(message); va_end(args); return result; } XposedService::XposedService(bool system) : isSystem(system) {} int XposedService::test() const { pid_t pid = IPCThreadState::self()->getCallingPid(); ALOGD("This is PID %d, test method was called from PID %d", getpid(), pid); return getpid(); } status_t XposedService::addService(const String16& name, const sp& service, bool allowIsolated) const { uid_t uid = IPCThreadState::self()->getCallingUid(); if (!isSystem || (uid != xposed->installer_uid)) { ALOGE("Permission denied, not adding service %s", String8(name).string()); errno = EPERM; return -1; } sp sm = defaultServiceManager(); #if PLATFORM_SDK_VERSION >= 16 return sm->addService(name, service, allowIsolated); #else return sm->addService(name, service); #endif } status_t XposedService::accessFile(const String16& filename16, int32_t mode) const { uid_t caller = IPCThreadState::self()->getCallingUid(); if (caller != UID_SYSTEM) { ALOGE("UID %d is not allowed to use the Xposed service", caller); errno = EPERM; return -1; } const char* filename = String8(filename16).string(); return TEMP_FAILURE_RETRY(access(filename, mode)); } status_t XposedService::statFile(const String16& filename16, int64_t* size, int64_t* time) const { uid_t caller = IPCThreadState::self()->getCallingUid(); if (caller != UID_SYSTEM) { ALOGE("UID %d is not allowed to use the Xposed service", caller); errno = EPERM; return -1; } const char* filename = String8(filename16).string(); struct stat st; status_t result = TEMP_FAILURE_RETRY(stat(filename, &st)); if (result == 0) { *size = st.st_size; *time = st.st_mtime; } return result; } status_t XposedService::readFile(const String16& filename16, int32_t offset, int32_t length, int64_t* size, int64_t* mtime, uint8_t** buffer, int32_t* bytesRead, String16* errormsg) const { uid_t caller = IPCThreadState::self()->getCallingUid(); if (caller != UID_SYSTEM) { ALOGE("UID %d is not allowed to use the Xposed service", caller); return EPERM; } *buffer = NULL; *bytesRead = -1; // Get file metadata const char* filename = String8(filename16).string(); struct stat st; if (stat(filename, &st) != 0) { status_t err = errno; if (errormsg) *errormsg = formatToString16("%s during stat() on %s", strerror(err), filename); return err; } if (S_ISDIR(st.st_mode)) { if (errormsg) *errormsg = formatToString16("%s is a directory", filename); return EISDIR; } // Don't load again if file is unchanged if (*size == st.st_size && *mtime == (int32_t)st.st_mtime) { return 0; } *size = st.st_size; *mtime = st.st_mtime; // Check range if (offset > 0 && offset >= *size) { if (errormsg) *errormsg = formatToString16("offset %d >= size %" PRId64 " for %s", offset, *size, filename); return EINVAL; } else if (offset < 0) { offset = 0; } if (length > 0 && (offset + length) > *size) { if (errormsg) *errormsg = formatToString16("offset %d + length %d > size %" PRId64 " for %s", offset, length, *size, filename); return EINVAL; } else if (*size == 0) { *bytesRead = 0; return 0; } else if (length <= 0) { length = *size - offset; } // Allocate buffer *buffer = (uint8_t*) malloc(length + 1); if (*buffer == NULL) { if (errormsg) *errormsg = formatToString16("allocating buffer with %d bytes failed", length + 1); return ENOMEM; } (*buffer)[length] = 0; // Open file FILE *f = fopen(filename, "r"); if (f == NULL) { status_t err = errno; free(*buffer); *buffer = NULL; if (errormsg) *errormsg = formatToString16("%s during fopen() on %s", strerror(err), filename); return err; } // Seek to correct offset if (offset > 0 && fseek(f, offset, SEEK_SET) != 0) { free(*buffer); *buffer = NULL; status_t err = ferror(f); fclose(f); if (errormsg) *errormsg = formatToString16("%s during fseek() to offset %d for %s", strerror(err), offset, filename); return err; } // Read the file *bytesRead = fread(*buffer, 1, length, f); status_t err = ferror(f); if (err != 0) { free(*buffer); *buffer = NULL; *bytesRead = -1; if (errormsg) *errormsg = formatToString16("%s during fread(), read %d bytes for %s", strerror(err), *bytesRead, filename); } // Close the file fclose(f); return err; } } // namespace binder //////////////////////////////////////////////////////////// // General //////////////////////////////////////////////////////////// static void systemService() { xposed::setProcessName("xposed_service_system"); xposed::dropCapabilities(); #if XPOSED_WITH_SELINUX if (xposed->isSELinuxEnabled) { if (setcon(ctx_system) != 0) { ALOGE("Could not switch to %s context", ctx_system); exit(EXIT_FAILURE); } } #endif // XPOSED_WITH_SELINUX // Initialize the system service sp sm(defaultServiceManager()); #if PLATFORM_SDK_VERSION >= 16 status_t err = sm->addService(String16(XPOSED_BINDER_SYSTEM_SERVICE_NAME), new binder::XposedService(true), true); #else status_t err = sm->addService(String16(XPOSED_BINDER_SYSTEM_SERVICE_NAME), new binder::XposedService(true)); #endif if (err != NO_ERROR) { ALOGE("Error %d while adding system service %s", err, XPOSED_BINDER_SYSTEM_SERVICE_NAME); exit(EXIT_FAILURE); } sp ps(ProcessState::self()); ps->startThreadPool(); #if PLATFORM_SDK_VERSION >= 18 ps->giveThreadPoolName(); #endif IPCThreadState::self()->joinThreadPool(); } static void appService() { xposed::setProcessName("xposed_service_app"); if (!xposed::switchToXposedInstallerUidGid()) { exit(EXIT_FAILURE); } xposed::dropCapabilities(); #if XPOSED_WITH_SELINUX if (xposed->isSELinuxEnabled) { if (setcon(ctx_app) != 0) { ALOGE("Could not switch to %s context", ctx_app); exit(EXIT_FAILURE); } } #endif // XPOSED_WITH_SELINUX // We have to register the app service by using the already running system service as a proxy sp sm(defaultServiceManager()); sp systemBinder = sm->getService(String16(XPOSED_BINDER_SYSTEM_SERVICE_NAME)); sp xposedSystemService = interface_cast(systemBinder); status_t err = xposedSystemService->addService(String16(XPOSED_BINDER_APP_SERVICE_NAME), new binder::XposedService(false), true); // Check result for the app service registration if (err != NO_ERROR) { ALOGE("Error %d while adding app service %s", err, XPOSED_BINDER_APP_SERVICE_NAME); exit(EXIT_FAILURE); } #if XPOSED_WITH_SELINUX // Initialize the memory-based Zygote service if (xposed->isSELinuxEnabled) { pthread_t thMemBased; if (pthread_create(&thMemBased, NULL, &membased::looper, NULL) != 0) { ALOGE("Could not create thread for memory-based service: %s", strerror(errno)); exit(EXIT_FAILURE); } } #endif // XPOSED_WITH_SELINUX sp ps(ProcessState::self()); ps->startThreadPool(); #if PLATFORM_SDK_VERSION >= 18 ps->giveThreadPoolName(); #endif IPCThreadState::self()->joinThreadPool(); } bool checkMembasedRunning() { // Ensure that the memory based service is running if (!membased::waitForRunning(5)) { ALOGE("Xposed's Zygote service is not running, cannot work without it"); return false; } return true; } bool startAll() { if (xposed->isSELinuxEnabled && !membased::init()) { return false; } // system context service pid_t pid; if ((pid = fork()) < 0) { ALOGE("Fork for Xposed service in system context failed: %s", strerror(errno)); return false; } else if (pid == 0) { systemService(); // Should never reach this point exit(EXIT_FAILURE); } // app context service if ((pid = fork()) < 0) { ALOGE("Fork for Xposed service in app context failed: %s", strerror(errno)); return false; } else if (pid == 0) { appService(); // Should never reach this point exit(EXIT_FAILURE); } if (xposed->isSELinuxEnabled && !checkMembasedRunning()) { return false; } return true; } #if XPOSED_WITH_SELINUX bool startMembased() { if (!xposed->isSELinuxEnabled) { return true; } if (!membased::init()) { return false; } pid_t pid; if ((pid = fork()) < 0) { ALOGE("Fork for Xposed Zygote service failed: %s", strerror(errno)); return false; } else if (pid == 0) { xposed::setProcessName("xposed_zygote_service"); if (!xposed::switchToXposedInstallerUidGid()) { exit(EXIT_FAILURE); } xposed::dropCapabilities(); if (setcon(ctx_app) != 0) { ALOGE("Could not switch to %s context", ctx_app); exit(EXIT_FAILURE); } membased::looper(NULL); // Should never reach this point exit(EXIT_FAILURE); } return checkMembasedRunning(); } #endif // XPOSED_WITH_SELINUX } // namespace service } // namespace xposed ================================================ FILE: xposed_service.h ================================================ #ifndef XPOSED_SERVICE_H_ #define XPOSED_SERVICE_H_ #include #include namespace xposed { namespace service { bool startAll(); #if XPOSED_WITH_SELINUX bool startMembased(); namespace membased { int accessFile(const char* path, int mode); int statFile(const char* path, struct stat* stat); char* readFile(const char* path, int* bytesRead); void restrictMemoryInheritance(); } // namespace membased #endif // XPOSED_WITH_SELINUX } // namespace service static inline int zygote_access(const char *pathname, int mode) { #if XPOSED_WITH_SELINUX if (xposed->isSELinuxEnabled) return xposed::service::membased::accessFile(pathname, mode); #endif // XPOSED_WITH_SELINUX return access(pathname, mode); } } // namespace xposed #endif /* XPOSED_SERVICE_H_ */ ================================================ FILE: xposed_shared.h ================================================ /** * These declarations are needed for both app_process and the libraries. */ #ifndef XPOSED_SHARED_H_ #define XPOSED_SHARED_H_ #include #include "cutils/log.h" #include "jni.h" #ifndef ALOG #define ALOG LOG #define ALOGD LOGD #define ALOGD LOGD #define ALOGE LOGE #define ALOGI LOGI #define ALOGV LOGV #endif #if PLATFORM_SDK_VERSION >= 24 #define XPOSED_DIR "/data/user_de/0/de.robv.android.xposed.installer/" #else #define XPOSED_DIR "/data/data/de.robv.android.xposed.installer/" #endif namespace xposed { struct XposedShared { // Global variables bool zygote; bool startSystemServer; const char* startClassName; uint32_t xposedVersionInt; bool isSELinuxEnabled; bool isSELinuxEnforcing; uid_t installer_uid; gid_t installer_gid; // Provided by runtime-specific library, used by executable void (*onVmCreated)(JNIEnv* env); #if XPOSED_WITH_SELINUX // Provided by the executable, used by runtime-specific library int (*zygoteservice_accessFile)(const char* path, int mode); int (*zygoteservice_statFile)(const char* path, struct stat* st); char* (*zygoteservice_readFile)(const char* path, int* bytesRead); #endif }; extern XposedShared* xposed; } // namespace xposed #endif // XPOSED_SHARED_H_