Xposed原理分析

碼頭工人發表於2020-08-02

安卓系統啟動

什麼zygote?

init是核心啟動的第一個使用者級程式,zygote是由init程式通過解析init.zygote.rc檔案而建立的,zygote所對應的具體可執行程式是app_process,所對應的原始檔是App_main.cpp,程式名稱為zygote。

init.zygote.rc:

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd
    writepid /dev/cpuset/foreground/tasks

安卓應用執行?

在ART模式下,zygote被init程式建立出來,用來孵化和啟動其他App。zygote程式具有App所需要的所有核心庫。

zygote程式複製

新的App程式在生成後,就會載入本App的程式程式碼(apk中的dex檔案)

Xposed介紹

Xposed是安卓系統上能夠修改系統或三方應用資訊的框架。

xposed_installer

Xposed構成

名稱 介紹
Xposed Xposed框架Native部分
XposedBridge Xposed向開發者提供的API與相應工具類庫
XposedInstaller Xposed框架Android端本地管理,環境框架,以及第三方module資源下載的工具

Xposed初始化大體工作流程

(1)xposed的主要介面在XposedBrigde.jar中,核心功能在替換的虛擬機器中實現。

(2)app_process是Android App的啟動程式(具體形式是zygote fork() 呼叫app_process作為Android app的載體)。

xposed初始化流程

原始碼分析

初始化

app_process有兩個對應原始檔,Android.mk會在編譯時根據sdk版本選擇對應原始檔作為入口(app_main.cpp或app_main2.cpp)

...

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
...
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

app_main#main

在系統開機時,會通過app_process去建立zygote虛擬機器,就會呼叫到app_main2.cpp中的main函式。

main函式中主要做兩件事:(1)初始化xposed;(2)建立虛擬機器

int main(int argc, char* const argv[])
{
    if (xposed::handleOptions(argc, argv))
        return 0;
    
    //程式碼省略...

    runtime.mParentDir = parentDir;
    // 初始化xposed,主要是將jar包新增至Classpath中
    isXposedLoaded = xposed::initialize(zygote, startSystemServer, className, argc, argv);
    if (zygote) {
        // 如果xposed初始化成功,將zygoteInit 替換為 de.robv.android.xposed.XposedBridge,然後建立虛擬機器
        runtime.start(isXposedLoaded ? XPOSED_CLASS_DOTS_ZYGOTE : "com.android.internal.os.ZygoteInit",
                startSystemServer ? "start-system-server" : "");
    } 
    ...
}

app_main#initialize

初始化xposed

(1)初始化xposed內相關變數

(2)呼叫addJarToClasspath將XposedBridge.jar新增至系統目錄。

bool initialize(bool zygote, bool startSystemServer, const char* className, int argc, char* const argv[]) {
    ...
    // 初始化xposed的相關變數
    xposed->zygote = zygote;
    xposed->startSystemServer = startSystemServer;
    xposed->startClassName = className;
    xposed->xposedVersionInt = xposedVersionInt;
    ...
    // 列印 release、sdk、manufacturer、model、rom、fingerprint、platform相關資料
    printRomInfo();

    // 主要在於將jar包加入Classpath
    return addJarToClasspath();
}

frameworks.base.core.jni.AndroidRuntime#start

建立對應虛擬機器

start做了4件事:

(1)建立虛擬機器

(2)初始化虛擬機器

(3)傳入呼叫類de.robv.android.xposed.XposedBridge

(4)初始化XposedBridge

/*
 * Start the Android runtime.  This involves starting the virtual machine
 * and calling the "static void main(String[] args)" method in the class
 * named by "className".
 *
 * Passes the main function two arguments, the class name and the specified
 * options string.
 */
void AndroidRuntime::start(const char* className, const Vector<String8>& options)
{
    /* start the virtual machine */
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    //建立虛擬機器
    if (startVm(&mJavaVM, &env) != 0) {
        return;
    }
    
    // 初始化虛擬機器,xposed對虛擬機器進行修改
    onVmCreated(env);
   
    // 虛擬機器初始化完成後,會呼叫傳入的de.robv.android.xposed.XposedBridge類,初始化java層XposedBridge.jar
    char* slashClassName = toSlashClassName(className);
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ...
    } else {
        
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
        ...
    }
}

Xposed.cpp#onVmCreated

xposed重寫了onVmCreated。

onVmCreated做了什麼:

1、xposedInitLib->onVmCreatedCommon->initXposedBridge,初始化XposedBridge

(1)將register_natives_XposedBridge中的函式註冊為Native方法

2、xposedInitLib->onVmCreatedCommon->onVmCreated,為xposed_callback_class與xposed_callback_method賦值;

(1)xposed_callback_class和xposed_callback_method變數賦值

3、 de.robv.android.xposed.XposedBridge#main,初始化java層XposedBridge.jar

(1)hook 住系統資源相關的方法;

(2)hook 住zygote 的相關方法;

(3)載入系統中已經安裝的xposed 模組。

void onVmCreated(JNIEnv* env) {
    // Determine the currently active runtime
    ...

    // Load the suitable libxposed_*.so for it 通過dlopen載入libxposed_art.so
    void* xposedLibHandle = dlopen(xposedLibPath, RTLD_NOW);
    ...

    // Initialize the library  初始化xposed相關庫
    bool (*xposedInitLib)(XposedShared* shared) = NULL;
    // 根據動態連結庫操作控制程式碼與符號,返回符號對應的地址
    *(void **) (&xposedInitLib) = dlsym(xposedLibHandle, "xposedInitLib");
    if (!xposedInitLib)  {
        ALOGE("Could not find function xposedInitLib");
        return;
    }
    ...
    // xposedInitLib -> onVmCreatedCommon -> initXposedBridge -> 註冊Xposed相關Native方法
    if (xposedInitLib(xposed)) {
        xposed->onVmCreated(env);
    }
}

libxposed_art.cpp#xposedInitLib

/** Called by Xposed's app_process replacement. */
bool xposedInitLib(XposedShared* shared) {
    xposed = shared;
    xposed->onVmCreated = &onVmCreatedCommon;
    return true;
}

libxposed_common.cpp#onVmCreatedCommon

void onVmCreatedCommon(JNIEnv* env) {
    if (!initXposedBridge(env) || !initZygoteService(env)) {
        return;
    }

    if (!onVmCreated(env)) {
        return;
    }

    xposedLoadedSuccessfully = true;
    return;
}

libxposed_common.cpp#initXposedBridge

bool initXposedBridge(JNIEnv* env) {
    classXposedBridge = env->FindClass(CLASS_XPOSED_BRIDGE);
    ...
    classXposedBridge = reinterpret_cast<jclass>(env->NewGlobalRef(classXposedBridge));
    ALOGI("Found Xposed class '%s', now initializing", CLASS_XPOSED_BRIDGE);
    // 將register_natives_XposedBridge中的函式註冊為Native方法
    if (register_natives_XposedBridge(env, classXposedBridge) != JNI_OK) {
        ALOGE("Could not register natives for '%s'", CLASS_XPOSED_BRIDGE);
        logExceptionStackTrace();
        env->ExceptionClear();
        return false;
    }

    // 獲取XposedBridge.jar中的handleHookedMethod方法,並將該方法賦值給methodXposedBridgeHandleHookedMethod,後續會賦值至全域性變數中
    methodXposedBridgeHandleHookedMethod = env->GetStaticMethodID(classXposedBridge, "handleHookedMethod",
        "(Ljava/lang/reflect/Member;ILjava/lang/Object;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
    ...
    return true;
}

libxposed_art.cpp#onVmCreated

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

de.robv.android.xposed.XposedBridge#main

虛擬機器初始化完成後,會呼叫傳入的de.robv.android.xposed.XposedBridge類,初始化java層XposedBridge.jar,呼叫main函式

(1)hook 系統資源相關的方法;

(2)hook zygote 的相關方法;

(3)載入系統中已經安裝的xposed 模組。

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

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

初始化結束。

例子

static final String TAG = "XposedTest001";
    //final XC_MethodReplacement replacementTrue = XC_MethodReplacement.returnConstant(true);

    public CheckSNHook(ClassLoader cl) {
        super();

        XposedBridge.log("hooking checkSN.");
        try {
            Class clz = (Class<?>) XposedHelpers.findClass("com.droider.crackme0201.MainActivity", cl);
            //XposedBridge.hookAllMethods(clz, "checkSN", replacementTrue);
            Log.d(TAG, "hooking clz");
            XposedHelpers.findAndHookMethod(clz,
                    "checkSN",
                    String.class, String.class,
                    new XC_MethodHook() {

                        @Override
                        protected void afterHookedMethod(MethodHookParam param)
                                throws Throwable {
                            XposedBridge.log("1CheckSN afterHookedMethod called.");
                            String s1 = (String) param.args[0];
                            String s2 = (String) param.args[1];

                            Log.d(TAG, "s1:" + s1);
                            Log.d(TAG, "s2:" + s2);
                            param.setResult(true);

                            super.afterHookedMethod(param);
                        }
                    });

        } catch (Exception e) {
            e.printStackTrace();
        }
        XposedBridge.log("1hook checkSN done.");
    }

Hook原理分析

XposedBridge#findAndHookMethod

1、根據函式名獲取對應Method物件

2、呼叫XposedBridge.hookMethod函式

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 method = clazz.getDeclaredMethod(methodName, parameterTypes);
        Method m = findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
        // 核心函式
        return XposedBridge.hookMethod(m, callback);
    }

XposedBridge#hookMethod

1、將回撥函式、引數型別、返回型別記錄到AdditionalHookInfo中

2、攔截指定函式呼叫,並使用其他函式替代(native函式)

public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
        ...
            // 將回撥函式、引數型別、返回型別記錄到AdditionalHookInfo中
            AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks, parameterTypes, returnType);
            // 攔截指定函式呼叫,並使用其他函式替代
            hookMethodNative(hookMethod, declaringClass, slot, additionalInfo);
        }

        return callback.new Unhook(hookMethod);
    }

private native synchronized static void hookMethodNative(Member method, Class<?> declaringClass, int slot, Object additionalInfo);

libxposed.cpp#hookMethodNative

1、查詢我們需要hook的java Method對應的ArtMethod (每一個java層函式在ART下都有一個對應的ArtMethod)

void XposedBridge_hookMethodNative(JNIEnv* env, jclass, jobject javaReflectedMethod,
            jobject, jint, jobject javaAdditionalInfo) {
    ...
        
    // 獲取Java層Method對應native層的ArtMethod指標,將java函式描述為ArtMethod,查詢我們需要hook的java Method對應的ArtMethod 
    ArtMethod* artMethod = ArtMethod::FromReflectedMethod(soa, javaReflectedMethod);

    // Hook the method
    artMethod->EnableXposedHook(soa, javaAdditionalInfo);
}

EnableXposedHook

1、建立原函式備份

2、建立 XposedHookInfo 儲存原函式、before函式、after函式

3、設定機器指令入口地址,此時跳入到GetQuickProxyInvokeHandler()地址

void ArtMethod::EnableXposedHook(ScopedObjectAccess& soa, jobject additional_info) {
  ...
      
  // 建立原函式備份
  auto* cl = Runtime::Current()->GetClassLinker();
  auto* linear_alloc = cl->GetAllocatorForClassLoader(GetClassLoader());
  ArtMethod* backup_method = cl->CreateRuntimeMethod(linear_alloc);
  backup_method->CopyFrom(this, cl->GetImagePointerSize());
  // 設定識別符號kAccXposedOriginalMethod
  backup_method->SetAccessFlags(backup_method->GetAccessFlags() | kAccXposedOriginalMethod);

  // Create a Method/Constructor object for the backup ArtMethod object
  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);

  // 建立 XposedHookInfo 儲存原函式、before函式、after函式(reflected_method:被hook的函式,XposedHookInfo包含回撥函式)
  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_info->additional_info = soa.Env()->NewGlobalRef(additional_info);
  hook_info->original_method = backup_method;
  ...
  //將entry_point_from_jni_指標指向hook資訊(目的是儲存),hook資訊包括原函式、before函式、after函式
  SetEntryPointFromJniPtrSize(reinterpret_cast<uint8_t*>(hook_info), sizeof(void*));
  
  // 設定機器指令入口地址,此時跳入到GetQuickProxyInvokeHandler()地址
  SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler());
  SetCodeItemOffset(0);

  // Adjust access flags.
  // 進行標誌位清除,此時這個ArtMethod物件對應是Hook後的方法,這個方法的實現不是native的
  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);
}

artQuickProxyInvokeHandler

extern "C" uint64_t artQuickProxyInvokeHandler(
            ArtMethod* proxy_method, mirror::Object* receiver, Thread* self, ArtMethod** sp)
            const bool is_xposed = proxy_method->IsXposedHookedMethod();//判斷 GetAccessFlags() 的kAccXposedHookedMethod 欄位
            ......
            if (is_xposed) {
                jmethodID proxy_methodid = soa.EncodeMethod(proxy_method);
                self->EndAssertNoThreadSuspension(old_cause);
                JValue result = InvokeXposedHandleHookedMethod(soa, shorty, rcvr_jobj, proxy_methodid, args);
                local_ref_visitor.FixupReferences();
                return result.GetJ();
            }
            ......
        }

InvokeXposedHandleHookedMethod

JValue InvokeXposedHandleHookedMethod(ScopedObjectAccessAlreadyRunnable& soa, const char* shorty, jobject rcvr_jobj, jmethodID method, std::vector<jvalue>& args) {
            //獲取ArtMethod 的 hookinfo 資訊,該資訊是EntryPointFromJniPtrSize所指向的資訊
            const XposedHookInfo* hookInfo = soa.DecodeMethod(method)->GetXposedHookInfo();
            //將hookinfo 轉為一個陣列,以便和java 層進行通訊呼叫
            jvalue invocation_args[5];
            invocation_args[0].l = hookInfo->reflectedMethod;
            invocation_args[1].i = 1;
            invocation_args[2].l = hookInfo->additionalInfo;
            invocation_args[3].l = rcvr_jobj;
            invocation_args[4].l = args_jobj;
            //通過CallStaticObjectMethodA 呼叫 xposed_callback_class 類裡面 xposed_callback_method 的方法
            //xposed_callback_class: XposedBridge.java
            //xposed_callback_method: handleHookedMethod 方法
            //ArtMethod 的這兩個值,在系統開機時 在 onVmCreated 進行賦值的
            jobject result = soa.Env()->CallStaticObjectMethodA(ArtMethod::xposed_callback_class,
                                       ArtMethod::xposed_callback_method,
                                       invocation_args);
        }

InvokeXposedHandleHookedMethod

(1)獲取ArtMethod 的 hookinfo 資訊,該資訊是EntryPointFromJniPtrSize所指向的資訊

(2)通過CallStaticObjectMethodA 呼叫 xposed_callback_class 類裡面 xposed_callback_method 的方法

(3)此處xposed_callback_class,xposed_callback_method 是libxposed_art****.cpp#onVmCreated重寫時做的事

const XposedHookInfo* GetXposedHookInfo() {
  DCHECK(IsXposedHookedMethod());
  // 前面儲存EntryPointFromJniPtrSize指向的資訊
  return reinterpret_cast<const XposedHookInfo*>(GetEntryPointFromJniPtrSize(sizeof(void*)));
}

GetXposedHookInfo:獲取EntryPointFromJniPtrSize儲存的資訊

Xposed.java#handleHookedMethod

private static Object handleHookedMethod(Member method, int originalMethodId, Object additionalInfoObj,
            Object thisObject, Object[] args) throws Throwable {
        AdditionalHookInfo additionalInfo = (AdditionalHookInfo) additionalInfoObj;
        ...
        // call "before method" callbacks
        int beforeIdx = 0;
        do {
            try {
                ((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param);
            } catch (Throwable t) {
                XposedBridge.log(t);

                // reset result (ignoring what the unexpectedly exiting callback did)
                param.setResult(null);
                param.returnEarly = false;
                continue;
            }

            if (param.returnEarly) {
                // skip remaining "before" callbacks and corresponding "after" callbacks
                beforeIdx++;
                break;
            }
        } while (++beforeIdx < callbacksLength);

        // call original method if not requested otherwise
        if (!param.returnEarly) {
            try {
                param.setResult(invokeOriginalMethodNative(method, originalMethodId,
                        additionalInfo.parameterTypes, additionalInfo.returnType, param.thisObject, param.args));
            } catch (InvocationTargetException e) {
                param.setThrowable(e.getCause());
            }
        }

        // call "after method" callbacks
        int afterIdx = beforeIdx - 1;
        do {
            Object lastResult =  param.getResult();
            Throwable lastThrowable = param.getThrowable();

            try {
                ((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param);
            } catch (Throwable t) {
                XposedBridge.log(t);

                // reset to last result (ignoring what the unexpectedly exiting callback did)
                if (lastThrowable == null)
                    param.setResult(lastResult);
                else
                    param.setThrowable(lastThrowable);
            }
        } while (--afterIdx >= 0);

        // return
        if (param.hasThrowable())
            throw param.getThrowable();
        else
            return param.getResult();
    }

XposedBridge.java 類的handleHookedMethod 方法,真正去處理 before、Original、after 這三個方法的呼叫關係。

ART函式呼叫原理

每一個Java函式在ART(虛擬機器)內部都由一個ArtMethod物件表示,ArtMethod物件中包含了函式名、引數型別、方法體程式碼入口地址等。

class ArtMethod {
 ...
 protect:
  HeapReference<Class> declaring_class_;
  HeapReference<ObjectArray<ArtMethod>> dex_cache_resolved_methods_;
  HeapReference<ObjectArray<Class>> dex_cache_resolved_types_;
  uint32_t access_flags_;
  uint32_t dex_code_item_offset_;
  uint32_t dex_method_index_;
  uint32_t method_index_;
  struct PACKED(4) PtrSizedFields {
    void* entry_point_from_interpreter_;
    // 用於儲存jni函式資訊,非jni函式的無用,所以經常被hook框架將原方法儲存在entry_point_from_jni_
    void* entry_point_from_jni_;
    // ART HOOK常見的方法是替換入口點,執行hook的函式。(此處指向的是彙編程式碼,執行的是已經預處理過的機器碼)
    void* entry_point_from_quick_compiled_code_;
 
#if defined(ART_USE_PORTABLE_COMPILER)
    void* entry_point_from_portable_compiled_code_;
#endif
  } ptr_sized_fields_;
  static GcRoot<Class> java_lang_reflect_ArtMethod_;
}
Art介面機器碼入口

替換entrypoint。將原函式對應的ArtMethod物件中entrypoint指向的機器碼替換為目標函式的機器碼,即可達到hook的目的。

總結

(1)準備包名、函式、引數型別、回撥函式呼叫Hook介面

(2)Xposed在找到art虛擬機器中找到方法對應的ArtMethod物件

(3)對ArtMethod物件進行備份

(4)修改備份物件的機器指令入口

(5)回撥handleHookedMethod函式

參考

Xposed 原始碼剖析1(初始話相關):https://blog.csdn.net/xiaolli/article/details/107506138

Xposed 原始碼剖析2:https://blog.csdn.net/a314131070/article/details/81092526

Xposed 原始碼剖析3:https://blog.csdn.net/a314131070/article/details/81092548

Xposed 原始碼剖析4:https://blog.csdn.net/xiaolli/article/details/107517039

Xposed 原始碼剖析5:https://egguncle.github.io/2018/02/04/xposed-art-hook-%E6%B5%85%E6%9E%90/

Xposed dalvik 原始碼剖析6:https://bbs.pediy.com/thread-247030.htm

ART入口點替換分析:https://www.jianshu.com/p/820eceabf219

ArtMethod結構:https://zhuanlan.zhihu.com/p/92267192

ArtMethod結構:https://bbs.pediy.com/thread-248898.htm

Dalvik與ART:https://www.jianshu.com/p/59d98244fb52

定製xposed:https://blog.csdn.net/qq_35834055/article/details/103256122

相關文章