XPosed 自定义代码实现分析

加入自己的理解,转载自:https://blog.csdn.net/zjx839524906/article/details/81046844

一 概述

Xposed通过替换/system/bin/app_process程序控制zygote进程,使得app_process在启动过程中会加载XposedBridge.jar这个jar包,从而完成对Zygote进程及其创建的Dalvik Art虚拟机的劫持。

  • XposedBridge.jar:XposedBridge.jar是Xposed提供的jar文件,负责在Native层与FrameWork层进行交互。/system/bin/app_process进程启动过程中会加载该jar包,其它的Modules的开发与运行都是基于该jar包的。
  • Xposed:Xposed的C++部分,主要是用来替换/system/bin/app_process,并为XposedBridge提供JNI方法。
  • XposedTool: xposed编译的工具

二 xposed zygote 启动流程

zygote启动流程图

2.1 替换Zygote

  • /system/bin/app_process就是zygote进程,对应app_main.cpp,即可以启动zygote进程也可以启动app进程。
  • xposed通过替换app_process(对应xposed/app_main2.cpp, app_main.cpp为Davlik),使每个进程在启动时就加载XposedBridge.jar。
  • XposedBridge有一个Native(JNI)方法hookMethodNative,这个方法也在app_process中使用。这个函数提供一个方法对象利用Java的Reflection机制来对内置方法覆写。
//zygote 替换过程 xposed 刷机包中 flash-script.sh
install_and_link() { //1.将老的文件备份为_original  2.复制新的文件 3.通过ln命令链接新的文件
  TARGET=$1
  XPOSED="${1}_xposed"
  BACKUP="${1}_original" 
  if [ ! -f ./$XPOSED ]; then
    return
  fi
  cp_perm ./$XPOSED $XPOSED $2 $3 $4 $5
  if [ ! -f $BACKUP ]; then
    mv $TARGET $BACKUP || exit 1
    ln -s $XPOSED $TARGET || exit 1
    chcon -h 'u:object_r:system_file:s0' $TARGET 2>/dev/null
  fi
}


install_and_link  /system/bin/app_process32               0 2000 0755 u:object_r:zygote_exec:s0
install_overwrite /system/bin/dex2oat                     0 2000 0755 u:object_r:dex2oat_exec:s0
install_overwrite /system/bin/oatdump                     0 2000 0755
install_overwrite /system/bin/patchoat                    0 2000 0755 u:object_r:dex2oat_exec:s0
install_overwrite /system/lib/libart.so                   0    0 0644
install_overwrite /system/lib/libart-compiler.so          0    0 0644
install_overwrite /system/lib/libart-disassembler.so      0    0 0644
install_overwrite /system/lib/libsigchain.so              0    0 0644
install_nobackup  /system/lib/libxposed_art.so            0    0 0644
if [ $IS64BIT ]; then
  install_and_link  /system/bin/app_process64             0 2000 0755 u:object_r:zygote_exec:s0
  install_overwrite /system/lib64/libart.so               0    0 0644
  install_overwrite /system/lib64/libart-compiler.so      0    0 0644
  install_overwrite /system/lib64/libart-disassembler.so  0    0 0644
  install_overwrite /system/lib64/libsigchain.so          0    0 0644
  install_nobackup  /system/lib64/libxposed_art.so        0    0 0644
fi

2.2 加载XPosedBridge.jar

  • Init.rc启动app_process进程,以及被xposed替换。
// xposed app_main代码。
int main(int argc, char* const argv[])
{
    //......
    // Parse runtime arguments.  Stop at first unrecognized option.
    bool zygote = false;
    bool startSystemServer = false;
    bool application = false;
    String8 niceName;
    String8 className;

    //......
    if (zygote) {
        //初始化xposed的一些参数设置,导入XposedBridge.jar
        isXposedLoaded = xposed::initialize(true, startSystemServer, NULL, argc, argv);
        //启动Zygote进程
        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;
    }
}
  • xposed::initialize 初始化xposed的相关参数,环境变量等,将XposedBridge加入到JavaPath中
/** 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) {
        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;
    // 将XposedBridge加入到JavaPath中
    return addJarToClasspath();
}

2.3 runtimeStart

  • 启动虚拟机
// app_main2.cpp
static void runtimeStart(AppRuntime& runtime, const char *classname, const Vector<String8>& options, bool zygote)
{
#if PLATFORM_SDK_VERSION >= 23
  runtime.start(classname, options, zygote);
#else
  //......
#endif
}
  • AppRuntime的父类是AndroidRuntime,其start函数的代码如下,主要功能包括启动虚拟机,并利用反射调用className.main()。这里className=XposedBridge(在app_main main函数中传入)。
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    ALOGD(">>>>>> START %s uid %d <<<<<<\n",
            className != NULL ? className : "(unknown)", getuid());

    static const String8 startSystemServer("start-system-server");

    /*
     * 'startSystemServer == true' 意味着runtime被废弃,并且不再从init.rc中启动
     * 所以我们打印除了启动事件
     */
    for (size_t i = 0; i < options.size(); ++i) {
        if (options[i] == startSystemServer) {
           /* track our progress through the boot sequence */
           const int LOG_BOOT_PROGRESS_START = 3000;
           LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START,  ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
        }
    }

    const char* rootDir = getenv("ANDROID_ROOT");
    if (rootDir == NULL) {
        rootDir = "/system";
        if (!hasDir("/system")) {
            LOG_FATAL("No root directory specified, and /android does not exist.");
            return;
        }
        setenv("ANDROID_ROOT", rootDir, 1);
    }

    //const char* kernelHack = getenv("LD_ASSUME_KERNEL");
    //ALOGD("Found LD_ASSUME_KERNEL='%s'\n", kernelHack);

    /* 开启虚拟机 */
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    if (startVm(&mJavaVM, &env, zygote) != 0) {
        return;
    }
    onVmCreated(env);

    /*
     * 注册android函数
     */
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }

    /*
     * 利用反射调用输入类的main方法,并输入相关的参数列表,此处包括两个参数,类名和选项字符串
     */
    jclass stringClass;
    jobjectArray strArray;
    jstring classNameStr;

    stringClass = env->FindClass("java/lang/String");
    assert(stringClass != NULL);
    strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
    assert(strArray != NULL);
    classNameStr = env->NewStringUTF(className);
    assert(classNameStr != NULL);
    env->SetObjectArrayElement(strArray, 0, classNameStr);

    for (size_t i = 0; i < options.size(); ++i) {
        jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
        assert(optionsStr != NULL);
        env->SetObjectArrayElement(strArray, i + 1, optionsStr);
    }

    /*
     * 开启VM,该进程将称为VM的主要进程,并且一直运行直到VM退出
     */
    char* slashClassName = toSlashClassName(className != NULL ? className : "");
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
        if (startMeth == NULL) {
            ALOGE("JavaVM unable to find main() in '%s'\n", className);
            /* keep going */
        } else {
            env->CallStaticVoidMethod(startClass, startMeth, strArray);

#if 0
            if (env->ExceptionCheck())
                threadExitUncaughtException(env);
#endif
        }
    }
    free(slashClassName);

    ALOGD("Shutting down VM\n");
    if (mJavaVM->DetachCurrentThread() != JNI_OK)
        ALOGW("Warning: unable to detach main thread\n");
    if (mJavaVM->DestroyJavaVM() != 0)
        ALOGW("Warning: VM did not shut down cleanly\n");
}

2.4 AppRuntime.onVmCreated

  • Xposed重写了onVmCreated方法,
// app_main2.cpp
    virtual void onVmCreated(JNIEnv* env)
    {
        if (isXposedLoaded)
            xposed::onVmCreated(env);

        if (mClassName.isEmpty()) {
            return; // Zygote. Nothing to do here.
        }
        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<jclass>(env->NewGlobalRef(mClass));
    }

2.5 xposed::onVmCreated

  • 开启VM:xposed部分,主要功能包括确定当前运行时(dalvik或art),加载libxposed_art.so,初始化库文件
//xposed.cpp
void onVmCreated(JNIEnv* env) {
    // 确定当前运行时,dalvik或art
    const char* xposedLibPath = NULL;
    if (!determineRuntime(&xposedLibPath)) {
        ALOGE("Could not determine runtime, not loading Xposed");
        return;
    }

    // 加载合适的 libxposed_*.so
    void* xposedLibHandle = dlopen(xposedLibPath, RTLD_NOW);
    if (!xposedLibHandle) {
        ALOGE("Could not load libxposed: %s", dlerror());
        return;
    }

    // Clear previous errors
    dlerror();

    // 初始化库文件
    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
    // 初始化XposedShared数据结构,并通过函数指针来调用xposed::onVmCreatedCommon
    if (xposedInitLib(xposed)) {
        xposed->onVmCreated(env);
    }
}
  • 通过库文件名称来确定运行时,dalvik或这art
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;
}

2.6 xposedInitLib

  • 初始化XposedShared数据结构,并调用onVmCreatedCommon
bool xposedInitLib(xposed::XposedShared* shared) {
    xposed = shared;
    xposed->onVmCreated = &onVmCreatedCommon;
    return true;
}
  • 可以看到xposed->onVmCreated指向onVmCreatedCommon,所以2.5中调用的xposed->onVmCreated(env) <=> onVmCreatedCommon(env)

2.7 onVmCreatedCommon

void onVmCreatedCommon(JNIEnv* env) {
    //初始化XposedBridge环境和ZygoteService环境
    if (!initXposedBridge(env) || !initZygoteService(env)) {
        return;
    }
    // 开启虚拟机
    if (!onVmCreated(env)) {
        return;
    }

    xposedLoadedSuccessfully = true;
    return;
}

2.8 initXposedBridge

  • 主要功能是找到XposedBridge类,利用反射获取到地址,并注册XposedBridge的native函数
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<jclass>(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,其值为XposedBridge.handleHookedMethod的函数ID
    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;
}
  • 注册的native函数包括:
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));
}

2.9 initZygoteService

  • 主要功能是初始化classFileResult和constructorFileResult 注册 ZygoteService native函数
bool initZygoteService(JNIEnv* env) {
    // 寻找类 de/robv/android/xposed/services/ZygoteService
    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;
    }
    // 注册native函数
    if (register_natives_ZygoteService(env, zygoteServiceClass) != JNI_OK) {
        ALOGE("Could not register natives for '%s'", CLASS_ZYGOTE_SERVICE);
        env->ExceptionClear();
        return false;
    }
    //寻找类 de/robv/android/xposed/services/FileResult
    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<jclass>(env->NewGlobalRef(classFileResult));
    // 获取init的函数ID
    constructorFileResult = env->GetMethodID(classFileResult, "<init>", "(JJ)V");
    if (constructorFileResult == NULL) {
        ALOGE("ERROR: could not find constructor %s(long, long)", CLASS_FILE_RESULT);
        logExceptionStackTrace();
        env->ExceptionClear();
        return false;
    }

    return true;
}
  • 注册的native方法为
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));
}

2.10 onVmCreated

  • 此函数在启动VM过程中很早阶段被调用,主要功能为获取invokeOriginalMethodNative函数地址,并设置native方法
//libxposed_dalvik.cpp
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;
}
/** libxposed_art.cpp */
bool onVmCreated(JNIEnv*) {
    // TODO: Handle CLASS_MIUI_RESOURCES?
    ArtMethod::xposed_callback_class = classXposedBridge;
    ArtMethod::xposed_callback_method = methodXposedBridgeHandleHookedMethod;
    return true;
}

三 XposedBridge启动流程

XposedBridge流程图

3.1 XposedBridge.main

  • XposedBridge.jar的入口函数,主要功能包括初始化资源,配置SELinux,加载模块等。
protected static void main(String[] args) {
        // Initialize the Xposed framework and modules
        try {
            if (!hadInitErrors()) {
                initXResources();

                SELinuxHelper.initOnce();
                SELinuxHelper.initForProcess(null);

                runtime = getRuntime();
                XPOSED_BRIDGE_VERSION = getXposedVersion();

                if (isZygote) {
                    XposedInit.hookResources();
                    XposedInit.initForZygote();
                }

                XposedInit.loadModules();
            } else {
                Log.e(TAG, "Not initializing Xposed because of previous errors");
            }
        } catch (Throwable t) {
            Log.e(TAG, "Errors during Xposed initialization", t);
            disableHooks = true;
        }

        // Call the original startup code
        if (isZygote) {
            ZygoteInit.main(args);
        } else {
            RuntimeInit.main(args);
        }
    }

3.2 XposedBridge.initXResource

  • 初始化Xposed的资源,包括载入Dex文件,drawables文件,类加载器等
  • 此处应该是为hook resource做准备。在/data/dalvik-cache目录下 生成了xposed_XResourcesSuperClass.dex ,xposed_XTypedArraySuperClass.dex 两个文件。具体代码参考DexCreator.java
private static void initXResources() throws IOException {
        // 创建 XResources父类
        Resources res = Resources.getSystem();
        File resDexFile = ensureSuperDexFile("XResources", res.getClass(), Resources.class);

        // 创建 XTypedArray父类
        Class<?> taClass = TypedArray.class;
        try {
            TypedArray ta = res.obtainTypedArray(res.getIdentifier("preloaded_drawables", "array", "android"));
            taClass = ta.getClass();
            ta.recycle();
        } catch (Resources.NotFoundException nfe) {
            XposedBridge.log(nfe);
        }
        Runtime.getRuntime().gc();
        File taDexFile = ensureSuperDexFile("XTypedArray", taClass, TypedArray.class);

        // 为创建类注入一个ClassLoader,来作为XposedBridge ClassLoader的父类
        ClassLoader myCL =  XposedBridge.class.getClassLoader();
        String paths = resDexFile.getAbsolutePath() + File.pathSeparator + taDexFile.getAbsolutePath();
        PathClassLoader dummyCL = new PathClassLoader(paths, myCL.getParent());
        setObjectField(myCL, "parent", dummyCL);
    }

3.3 SELinuxHelper.initOnce

  • 初始化sIsSELinuxEnabled 变量
static void initOnce() {
        try {
            sIsSELinuxEnabled = SELinux.isSELinuxEnabled();
        } catch (NoClassDefFoundError ignored) {}
    }

3.4 SELinuxHelper.initForProcess

  • 如果系统SELinux开启状态,启动新的ZygoteService,启动新的system_server
static void initForProcess(String packageName) {
        if (sIsSELinuxEnabled) {
            if (packageName == null) {  // Zygote
                sServiceAppDataFile = new ZygoteService();
            } else if (packageName.equals("android")) {  //system_server
                sServiceAppDataFile = BinderService.getService(BinderService.TARGET_APP);
            } else {  // app
                sServiceAppDataFile = new DirectAccessService();
            }
        } else {
            sServiceAppDataFile = new DirectAccessService();
        }
    }

3.5 XposedInit.hookResources

static void hookResources() throws Throwable {
        if (SELinuxHelper.getAppDataFileService().checkFileExists(BASE_DIR + "conf/disable_resources")) {
            Log.w(TAG, "Found " + BASE_DIR + "conf/disable_resources, not hooking resources");
            disableResources = true;
            return;
        }
        //初始化Resource的native方法
        if (!XposedBridge.initXResourcesNative()) {
            Log.e(TAG, "Cannot hook resources");
            disableResources = true;
            return;
        }

        /*
         * getTopLevelResources(a)
         *   -> getTopLevelResources(b)
         *     -> key = new ResourcesKey()
         *     -> r = new Resources()
         *     -> mActiveResources.put(key, r)
         *     -> return r
         */

        final Class<?> classGTLR;
        final Class<?> classResKey;
        final ThreadLocal<Object> latestResKey = new ThreadLocal<>();
        // 根据不同的SDK版本做不同的hook操作
        if (Build.VERSION.SDK_INT <= 18) {
            classGTLR = ActivityThread.class;
            classResKey = Class.forName("android.app.ActivityThread$ResourcesKey");
        } else {
            classGTLR = Class.forName("android.app.ResourcesManager");
            classResKey = Class.forName("android.content.res.ResourcesKey");
        }

        if (Build.VERSION.SDK_INT >= 24) {
            hookAllMethods(classGTLR, "getOrCreateResources", new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    //......
                    }
                }
            });
        } else {
            hookAllConstructors(classResKey, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    latestResKey.set(param.thisObject);
                }
            });

            hookAllMethods(classGTLR, "getTopLevelResources", new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    latestResKey.set(null);
                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    //......
                }
            });

            if (Build.VERSION.SDK_INT >= 19) {
                // This method exists only on CM-based ROMs
                hookAllMethods(classGTLR, "getTopLevelThemedResources", new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        String resDir = (String) param.args[0];
                        cloneToXResources(param, resDir);
                    }
                });
            }
        }

        // Invalidate callers of methods overridden by XTypedArray
        if (Build.VERSION.SDK_INT >= 24) {
            Set<Method> methods = getOverriddenMethods(XResources.XTypedArray.class);
            XposedBridge.invalidateCallersNative(methods.toArray(new Member[methods.size()]));
        }

        // 利用XTypedArrays代替TypedArrays
        hookAllConstructors(TypedArray.class, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                TypedArray typedArray = (TypedArray) param.thisObject;
                Resources res = typedArray.getResources();
                if (res instanceof XResources) {
                    XposedBridge.setObjectClass(typedArray, XResources.XTypedArray.class);
                }
            }
        });

        // 替换系统资源
        XResources systemRes = (XResources) XposedBridge.cloneToSubclass(Resources.getSystem(), XResources.class);
        systemRes.initObject(null);
        setStaticObjectField(Resources.class, "mSystem", systemRes);

        XResources.init(latestResKey);
    }

3.6 XposedInit.loadModules

  • 将modules.list文件中的所有Xposed相关的模块全部加载。
static void loadModules() throws IOException {
        final String filename = BASE_DIR + "conf/modules.list";
        BaseService service = SELinuxHelper.getAppDataFileService();
        if (!service.checkFileExists(filename)) {
            Log.e(TAG, "Cannot load any modules because " + filename + " was not found");
            return;
        }

        ClassLoader topClassLoader = XposedBridge.BOOTCLASSLOADER;
        ClassLoader parent;
        while ((parent = topClassLoader.getParent()) != null) {
            topClassLoader = parent;
        }

        InputStream stream = service.getFileInputStream(filename);
        BufferedReader apks = new BufferedReader(new InputStreamReader(stream));
        String apk;
        while ((apk = apks.readLine()) != null) {
            loadModule(apk, topClassLoader);
        }
        apks.close();
    }

3.7 XposedInit.loadModule

过程可以概述为:

  1. 加载APK的dex文件,如需解压则解压后加载dex文件
  2. 加载dex文件中的所有与Xposed相关的类,包括IXposedHookZygoteInit,IXposedHookLoadPackage,IXposedHookInitPackageResources,IXposedHookCmdInit
private static void loadModule(String apk, ClassLoader topClassLoader) {
        Log.i(TAG, "Loading modules from " + apk);

        if (!new File(apk).exists()) {
            Log.e(TAG, "  File does not exist");
            return;
        }

        DexFile dexFile;
        try {
            dexFile = new DexFile(apk);
        } catch (IOException e) {
            Log.e(TAG, "  Cannot load module", e);
            return;
        }

        if (dexFile.loadClass(INSTANT_RUN_CLASS, topClassLoader) != null) {
            Log.e(TAG, "  Cannot load module, please disable \"Instant Run\" in Android Studio.");
            closeSilently(dexFile);
            return;
        }

        if (dexFile.loadClass(XposedBridge.class.getName(), topClassLoader) != null) {
            Log.e(TAG, "  Cannot load module:");
            Log.e(TAG, "  The Xposed API classes are compiled into the module's APK.");
            Log.e(TAG, "  This may cause strange issues and must be fixed by the module developer.");
            Log.e(TAG, "  For details, see: http://api.xposed.info/using.html");
            closeSilently(dexFile);
            return;
        }

        closeSilently(dexFile);

        ZipFile zipFile = null;
        InputStream is;
        try {
            zipFile = new ZipFile(apk);
            ZipEntry zipEntry = zipFile.getEntry("assets/xposed_init");
            if (zipEntry == null) {
                Log.e(TAG, "  assets/xposed_init not found in the APK");
                closeSilently(zipFile);
                return;
            }
            is = zipFile.getInputStream(zipEntry);
        } catch (IOException e) {
            Log.e(TAG, "  Cannot read assets/xposed_init in the APK", e);
            closeSilently(zipFile);
            return;
        }

        ClassLoader mcl = new PathClassLoader(apk, XposedBridge.BOOTCLASSLOADER);
        BufferedReader moduleClassesReader = new BufferedReader(new InputStreamReader(is));
        try {
            String moduleClassName;
            while ((moduleClassName = moduleClassesReader.readLine()) != null) {
                moduleClassName = moduleClassName.trim();
                if (moduleClassName.isEmpty() || moduleClassName.startsWith("#"))
                    continue;

                try {
                    Log.i(TAG, "  Loading class " + moduleClassName);
                    Class<?> moduleClass = mcl.loadClass(moduleClassName);

                    if (!IXposedMod.class.isAssignableFrom(moduleClass)) {
                        Log.e(TAG, "    This class doesn't implement any sub-interface of IXposedMod, skipping it");
                        continue;
                    } else if (disableResources && IXposedHookInitPackageResources.class.isAssignableFrom(moduleClass)) {
                        Log.e(TAG, "    This class requires resource-related hooks (which are disabled), skipping it.");
                        continue;
                    }

                    final Object moduleInstance = moduleClass.newInstance();
                    if (XposedBridge.isZygote) {
                    //加载所有实现这些接口的类
                        if (moduleInstance instanceof IXposedHookZygoteInit) {
                            IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
                            param.modulePath = apk;
                            param.startsSystemServer = startsSystemServer;
                            ((IXposedHookZygoteInit) moduleInstance).initZygote(param);
                        }

                        if (moduleInstance instanceof IXposedHookLoadPackage)
                            XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance));

                        if (moduleInstance instanceof IXposedHookInitPackageResources)
                            XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources) moduleInstance));
                    } else {
                        if (moduleInstance instanceof IXposedHookCmdInit) {
                            IXposedHookCmdInit.StartupParam param = new IXposedHookCmdInit.StartupParam();
                            param.modulePath = apk;
                            param.startClassName = startClassName;
                            ((IXposedHookCmdInit) moduleInstance).initCmdApp(param);
                        }
                    }
                } catch (Throwable t) {
                    Log.e(TAG, "    Failed to load class " + moduleClassName, t);
                }
            }
        } catch (IOException e) {
            Log.e(TAG, "  Failed to load module from " + apk, e);
        } finally {
            closeSilently(is);
            closeSilently(zipFile);
        }
    }

3.8 IXposedHookZygoteInit.initZygote

public interface IXposedHookZygoteInit extends IXposedMod {
    /**
     * Zygote启动过程早期调用
     * @param startupParam 模块和启动进程的细节
     * @throws Throwable everything is caught, but will prevent further initialization of the module.
     */
    void initZygote(StartupParam startupParam) throws Throwable;
}

3.9 IXposedHookLoadPackage.handleLoadPackage

public interface IXposedHookLoadPackage extends IXposedMod {
    /**
     * 模块APP启动时调用该函数,它在before之前调用
     * {@link Application#onCreate} is called.
     * 此处自制模块可设置自己特殊的hooks
     *
     * @param lpparam app信息
     * @throws Throwable Everything the callback throws is caught and logged.
     */
    void handleLoadPackage(LoadPackageParam lpparam) throws Throwable;
}

3.10 IXposedHookPackageResources.handleInitPackageResources

public interface IXposedHookInitPackageResources extends IXposedMod {
    /**
     * APP资源初始化时调用,该函数可调用特殊的XResources类方法来代替资源
     * @param 资源参数信息
     * @throws Throwable Everything the callback throws is caught and logged.
     */
    void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable;

    /** @hide */
    final class Wrapper extends XC_InitPackageResources {
        private final IXposedHookInitPackageResources instance;
        public Wrapper(IXposedHookInitPackageResources instance) {
            this.instance = instance;
        }
        @Override
        public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
            instance.handleInitPackageResources(resparam);
        }
    }
}

四 hook过程函数调用分析

image.png

4.1 XposedHelper.findAndHookMethod

  • 主要功能是构造回调类,并调用XposedBridge.hookMethod方法
     /**
     * 查询函数并hook它,最后有一个参数需为hook的回调函数
     * @param className 待hook的函数对应的类名
     * @param classLoader 类加载器,解析目标和类参数
     * @param methodName 目标函数名
     * @param parameterTypesAndCallback 目标函数的参数类型和回调
     * @throws NoSuchMethodError In case the method was not found.
     * @throws ClassNotFoundError In case the target class or one of the parameter types couldn't be resolved.
     * @return An object which can be used to remove the callback again.
     */

    public static XC_MethodHook.Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback) {
        return findAndHookMethod(findClass(className, classLoader), methodName, parameterTypesAndCallback);
    }

    public static XC_MethodHook.Unhook findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback) {
        if (parameterTypesAndCallback.length == 0 || !(parameterTypesAndCallback[parameterTypesAndCallback.length-1] instanceof XC_MethodHook))
            throw new IllegalArgumentException("no callback defined");

        XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length-1];
        Method m = findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));

        return XposedBridge.hookMethod(m, callback);
    }

4.2 XposedBridge.hookMethod

  • 主要功能是将回调加入到数组中,判断函数是否是第一次被hook,然后调用native层函数
    /**
     * hook任何带有特殊回调的函数(或构造函数),利用一些wrapper让实现更为简单
     *
     * @param hookMethod 被hook的方法
     * @param callback 回调
     * @return 一个可以用来删除hook的对象
     *
     * @see XposedHelpers#findAndHookMethod(String, ClassLoader, String, Object...)
     * @see XposedHelpers#findAndHookMethod(Class, String, Object...)
     * @see #hookAllMethods
     * @see XposedHelpers#findAndHookConstructor(String, ClassLoader, Object...)
     * @see XposedHelpers#findAndHookConstructor(Class, Object...)
     * @see #hookAllConstructors
     */
    public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
        // 判断被hook的是否为普通函数或构造函数
        if (!(hookMethod instanceof Method) && !(hookMethod instanceof Constructor<?>)) {
            throw new IllegalArgumentException("Only methods and constructors can be hooked: " + hookMethod.toString());
        } else if (hookMethod.getDeclaringClass().isInterface()) {
            throw new IllegalArgumentException("Cannot hook interfaces: " + hookMethod.toString());
        } else if (Modifier.isAbstract(hookMethod.getModifiers())) {
            throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod.toString());
        }

        boolean newMethod = false;
        // 将callback添加到列表中
        CopyOnWriteSortedSet<XC_MethodHook> callbacks;
        synchronized (sHookedMethodCallbacks) {
            callbacks = sHookedMethodCallbacks.get(hookMethod);
            if (callbacks == null) {
                callbacks = new CopyOnWriteSortedSet<>();
                sHookedMethodCallbacks.put(hookMethod, callbacks);
                newMethod = true;
            }
        }
        callbacks.add(callback);
        // 如果该方法第一次被hook
        if (newMethod) {
            Class<?> declaringClass = hookMethod.getDeclaringClass();
            int slot;
            Class<?>[] parameterTypes;
            Class<?> returnType;
            if (runtime == RUNTIME_ART) {
                slot = 0;
                parameterTypes = null;
                returnType = null;
            } else if (hookMethod instanceof Method) {
                slot = getIntField(hookMethod, "slot");
                parameterTypes = ((Method) hookMethod).getParameterTypes();
                returnType = ((Method) hookMethod).getReturnType();
            } else {
                slot = getIntField(hookMethod, "slot");
                parameterTypes = ((Constructor<?>) hookMethod).getParameterTypes();
                returnType = null;
            }

            AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks, parameterTypes, returnType);
            // 将beforehook和afterhook加入additionalInfo中,并调用native层hook方法
            hookMethodNative(hookMethod, declaringClass, slot, additionalInfo);
        }

        return callback.new Unhook(hookMethod);
    }

4.3 XposedBridge.hookMethodNative

  • 这个函数在3.1.3.5章节中注册native方法,其对应的native实现为XposedBridge_hookMethodNative
    /**
     * 拦截每次特殊函数调用,并调用一个处理函数来替代
     * @param method 用来拦截的函数
     */
    private native synchronized static void hookMethodNative(Member method, Class<?> declaringClass, int slot, Object additionalInfo);

4.4 XposedBridge_hookMethodNative

–>[Xposed][libxposed_art.cpp] hookMethodNative的Native层实现

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;
    }

    // 获取函数的ArtMethod,以hook
    ArtMethod* artMethod = ArtMethod::FromReflectedMethod(soa, javaReflectedMethod);

    // hook函数
    artMethod->EnableXposedHook(soa, javaAdditionalInfo);
}

4.5 FromReflectedMethod

–>[xposed-android-art] [art_method.cc]

ArtMethod* ArtMethod::FromReflectedMethod(const ScopedObjectAccessAlreadyRunnable& soa,
                                          jobject jlr_method) {
  auto* abstract_method = soa.Decode<mirror::AbstractMethod*>(jlr_method);
  DCHECK(abstract_method != nullptr);
  return abstract_method->GetArtMethod();
}

4.6 EnableXposedHook

  • –>[xposed-android-art] [art_method.cc]

EnableXposedHook,会创建一个hookInfo 将original、before、after 方法存入,然后通过 SetEntryPointFromJni 方法将hookinfo 保存到ArtMethod 中。
在之后 通过SetEntryPointFromInterpreter 方法强制设置 java 方法为,本地机器码执行
通过SetEntryPointFromQuickCompiledCode 改变机器码执行时的函数入口为 art_quick_proxy_invoke_handler
到此函数的整个hook 流程就已经完成。

void ArtMethod::EnableXposedHook(ScopedObjectAccess& soa, jobject additional_info) {
  if (UNLIKELY(IsXposedHookedMethod())) {
    // 如果函数已被hook则返回
    return;
  } else if (UNLIKELY(IsXposedOriginalMethod())) {
    // Xposed功能未打开则返回
    ThrowIllegalArgumentException(StringPrintf("Cannot hook the method backup: %s", PrettyMethod(this).c_str()).c_str());
    return;
  }

  // 创建ArtMethod对象的备份
  auto* cl = Runtime::Current()->GetClassLinker();
  auto* linear_alloc = cl->GetAllocatorForClassLoader(GetClassLoader());
  ArtMethod* backup_method = cl->CreateRuntimeMethod(linear_alloc);
  backup_method->CopyFrom(this, cl->GetImagePointerSize());
   //添加设置 backup_method的flags 为kAccXposedOriginalMethod,表示其为hook方法的原方法
  backup_method->SetAccessFlags(backup_method->GetAccessFlags() | kAccXposedOriginalMethod);


  // 创建一个函数或构造函数对象,以备份ArtMethod对象
  mirror::AbstractMethod* reflected_method;
  if (IsConstructor()) {
    reflected_method = mirror::Constructor::CreateFromArtMethod(soa.Self(), backup_method);
  } else {
    reflected_method = mirror::Method::CreateFromArtMethod(soa.Self(), backup_method);
  }
  reflected_method->SetAccessible<false>(true);


  //创建hookinfo,用于保存,hook的原方法,与其 before && after 方法
  XposedHookInfo* hook_info = reinterpret_cast<XposedHookInfo*>(linear_alloc->Alloc(soa.Self(), sizeof(XposedHookInfo)));
  hook_info->reflected_method = soa.Vm()->AddGlobalRef(soa.Self(), reflected_method);
  //添加hook的方法(before && after 方法)
  hook_info->additional_info = soa.Env()->NewGlobalRef(additional_info);
  //添加 backup_method(原方法)
  hook_info->original_method = backup_method;

  ScopedThreadSuspension sts(soa.Self(), kSuspended);
  jit::ScopedJitSuspend sjs;
  gc::ScopedGCCriticalSection gcs(soa.Self(),
                                  gc::kGcCauseXposed,
                                  gc::kCollectorTypeXposed);
  ScopedSuspendAll ssa(__FUNCTION__);

  cl->InvalidateCallersForMethod(soa.Self(), this);

  jit::Jit* jit = art::Runtime::Current()->GetJit();
  if (jit != nullptr) {
    jit->GetCodeCache()->MoveObsoleteMethod(this, backup_method);
  }

  SetEntryPointFromJniPtrSize(reinterpret_cast<uint8_t*>(hook_info), sizeof(void*));
  // 重新设置 EntryPointFromQuickCompiledCode 值为GetQuickProxyInvokeHandler 的返回值art_quick_proxy_invoke_handler
  //以此达到hook的目的
  SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler());

  SetCodeItemOffset(0);

  // 调用获取flag
  const uint32_t kRemoveFlags = kAccNative | kAccSynchronized | kAccAbstract | kAccDefault | kAccDefaultConflict;
  SetAccessFlags((GetAccessFlags() & ~kRemoveFlags) | kAccXposedHookedMethod);

  MutexLock mu(soa.Self(), *Locks::thread_list_lock_);
  Runtime::Current()->GetThreadList()->ForEach(StackReplaceMethodAndInstallInstrumentation, this);
}

//GetQuickProxyInvokeHandler 用于获取xposed 框架下,函数以本地机器码执行时的首地址(即java层函数的执行从这个C 函数还是的)
static inline const void* GetQuickProxyInvokeHandler() {
    return reinterpret_cast<const void*>(art_quick_proxy_invoke_handler);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容