本篇介紹
在搞逆向的時候,Hook是個很必要的手段,通過Hook就可以了解內部的運行機制,甚至進行修改。對于Hook Java方法,用的比較多的就是Xposed,本篇就介紹下Xposed的配置,使用,原理。
1 Xposed 配置
1.1 刷入twrp
twrp, TeamWin Recovery Project,是一個三方recovery,似乎于win PE,通過twrp,我們就可以刷入各種包,非常方便。
筆者這邊的設備是Pixel 4,下載鏈接,如果是其他設備,就可能需要再搜一搜,twrp針對不同設備都有專門的包,非常全。
1.1.1 手機root
手機root 最簡單的方法就是在某寶上花個幾塊錢,root了就行,如果自己想嘗試,就需要先解oem鎖,fastboot 鎖,再刷入root包。
1.1.2 刷twrp
Pixel 4 是A/B分區(qū),不能一下子刷入twrp鏡像,只能先刷入tmrp 鏡像
fastboot boot twrp.img
這時候就會進入twrp的recovery界面,如下:

繼續(xù)push twrp zip包到手機sdcard目錄:
adb push twrp.zip /sdcard
然后點擊install,選中twrp.zip 進行安裝就好了。
這個過程比較容易,可是筆者在這兒折騰了好長時間,主要原因是twrp目前在lineage 19 (android 12)上有點問題,現(xiàn)象就是無法進入twrp的recovery,看了下原因,是缺少libion.so。 剛開始也打算替換成lineage 18 (android 11),后來發(fā)現(xiàn)無法降級,只能先刷成 pixel的 factory鏡像, 再刷 twrp,然后用twrp刷 lineage 18, 再用lineage 18刷twrp。
1.1.3 安裝magisk
magsik 可以看成是一個特殊的文件系統(tǒng),可以提供root能力的同時又隱藏已被root的事實,這樣一些防root的應用也可以在設備上正常使用了,和xposed的關系如下:

從magisk網站下載最新的magisk apk,重命名為zip,然后push到sdcard下。
adb push magisk.zip /sdcard
進入twrp 模式:
adb reboot recovery
選擇magisk.zip 進行安裝。
安裝好后,再打開magisk,效果如下:

需要注意的是我們直接下載的magisk.apk,如果真當apk 直接安裝的話,Superuser和Modules部分是不能用的。簡單點的操作就是用twrp刷。
上面有2個install,可以直接點點,就裝上了。
1.1.4 安裝riru和EdXposed
由于Xposed只能支持到O版本,如果要用Xposed,只能安裝EdXposed等擴展版本。
先安裝以下apk:
riru Manager apk
EdXposed Mananger apk
再通過Magisk如下zip:
riru core
Edxposed YAHFA
先把這兩個zip push到sdcard下:
adb push xxx.zip /sdcard
然后安裝:

記得先安裝riru,安裝完后重啟下。
再回到系統(tǒng),點開那兩個apk:


能看到類似信息,那說明安裝好了。
1.1.5 安裝總結
這一個小節(jié)介紹下我們安裝的幾個模塊,不涉及操作了。
xposed: 通過替換zygote的進程app_process 提供hook能力
magisk: 通過overlay方式避免修改app_process
riru: 以動態(tài)的方式讓app_process 擁有hook能力,本質上就是通過ro.dalvik.vm.native.bridge屬性讓zygote加載一個so,通過該so提供hook能力。
YAHFA:android art上的一個hook框架
看到這些介紹,現(xiàn)在就知道我們安裝的那些zip,apk分別用來干啥了吧?
2 Module演示
接下來我們通過一個Demo演示下Xposed的能力,比如我們需要hook下微信,讓微信顯示我們自己定義的字符。
1.1 環(huán)境準備
首先創(chuàng)建一個工程,無Activity就行。
接下來修改AndroidManifest.xml
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Hookdemo">
// 聲明這是一個xposed模塊
<meta-data
android:name="xposedmodule"
android:value="true" />
// 模塊描述
<meta-data
android:name="xposeddescription"
android:value="微信hook" />
// 支持的版本
<meta-data
android:name="xposedminversion"
android:value="53" />
</application>
然后在app的gradle 里添加下依賴:
compileOnly 'de.robv.android.xposed:api:82'
compileOnly 'de.robv.android.xposed:api:82:sources'
接下來新建一個類實現(xiàn)IXposedHookLoadPackage 接口,這兒就是hook的行為了,代碼基本可以自解釋:
package com.example.hookdemo;
import android.util.Log;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
public class MyHookClass implements IXposedHookLoadPackage {
private static final String TAG = "shanks";
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if (!lpparam.packageName.contains("com.tencent.mm")) {
return;
}
hookClassMethod(android.widget.TextView.class, Collections.singletonList("setText"), null, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
if (param.args.length == 0) {
Log.i(TAG, "length 0");
return;
}
Object value = param.args[0];
param.args[0] = "hooked";
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
}
});
}
private void hookClassMethod(Class<?> clazz, List<String> includes, List<String> excludes, XC_MethodHook hook) {
Method[] methods = clazz.getDeclaredMethods();
if (methods == null || methods.length == 0) {
return;
}
for (Method method : methods) {
if (includes != null && !includes.isEmpty() && !includes.contains(method.getName())) {
continue;
}
if (excludes != null && excludes.contains(method.getName())) {
continue;
}
Class<?>[] paramTypes = method.getParameterTypes();
Object[] types = new Object[paramTypes.length + 1];
System.arraycopy(paramTypes, 0, types, 0, paramTypes.length);
types[paramTypes.length] = hook;
try {
XposedHelpers.findAndHookMethod(clazz, method.getName(), types);
Log.i(TAG, "findAndHookMethod " + clazz.getName() + " " + method.getName());
} catch (Throwable e) {
Log.e(TAG, "findAndHookMethod " + method.getName() + " failed.", e);
}
}
}
}
接下來就是最后一步,告訴xposed 那個類是hook的入口類,新建一個目錄,操作如下:

目錄名字就是assets,然后新建一個txt文件,名字是xposed_init,內容就是hook類,比如對于我們的demo,內容就是
com.example.hookdemo.MyHookClass接下來點擊run,這樣就安裝到手機里了,由于沒有Activity,桌面上會看不到,不過沒關系,打開xposed manager,選擇modules,然后打開:

手機重啟下。
重啟后,打開微信,接下來就可以見證效果了:

3 Xposed 原理
接下來我們從代碼層面看下Xposed的原理.
代碼路徑:
xposed framework: https://github.com/rovo89/XposedBridge
xposed: https://github.com/rovo89/Xposed
大致介紹下xposed之前的流程,android 按下開機鍵后,就會執(zhí)行ROM上的一段代碼,這段代碼會加載bootloader,bootloader會讀硬件參數(shù),然后加載kernel,kernel負責系統(tǒng)的初始化,文件系統(tǒng),內存管理,中斷表,頁表等都搞好后,就通過0號進程fork 出 1號進程,也就是init,init先掛載文件系統(tǒng),各種分區(qū),然后再解析rc,啟動rc里面定義的服務,當然zygote也是其中一個,如果init是android 的祖先的話,zygote就是Android應用的祖先。zygote啟動的時候會先fork一下,創(chuàng)建出systemserver,這里就會啟動android系統(tǒng)服務,比如AMS,Servicemanager,WMS,PMS等等各種耳熟能詳?shù)姆斩际沁@兒拉起來的,zygote 最后就會啟動一個socket,然后靜靜等著就好了。
如果我們點擊一個桌面圖標,這時候launch就會請求ams 啟動這個app,ams 會請求zygote fork進程,然后在這個進程里面啟動這個應用。
xposed就是用zygote開始的,我們需要了解的是xposed如何支持我們hook java方法的,帶著這個疑問就開始這段旅途吧:
故事的開始是zygote的main函數(shù):
int main(int argc, char* const argv[])
{
// 解析xpose的參數(shù),比如版本之類的
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<String8> 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) {
// 走xposed的流程,初始化xposed的參數(shù),
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) {
// fork app流程,也是走xposed的話入口會不一樣
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;
}
}
runtimeStart其實就是啟動XPOSED_CLASS_DOTS_ZYGOTE,直接看下XPOSED_CLASS_DOTS_ZYGOTE (XposedBridge.java),執(zhí)行的方法是main:
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();
// 通過hook 系統(tǒng)方法來加載模塊自己定義的hook
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);
}
}
這兒主要是初始化資源,看下loadModules:
/**
* Try to load all modules defined in <code>BASE_DIR/conf/modules.list</code>
*/
/*package*/ 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();
}
這兒就是提前l(fā)oad modules.list中的module,其實也就是我們寫好的module??纯慈绾渭虞dmodule的:
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;
}
// 這就是我們在gradle里面用compileOnly被攔截的原因
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);
// 這個就是我們寫hook入口的文件
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);
}
}
}
這兒就是從xposed_init中讀取我們寫的hook類 ,然后加載,看下如何加載的:
public static void hookLoadPackage(XC_LoadPackage callback) {
synchronized (sLoadedPackageCallbacks) {
sLoadedPackageCallbacks.add(callback);
}
}
這兒就是將我們實現(xiàn)的接口當成回調保存起來了。現(xiàn)在還沒看到我們寫的模塊hook是如何被加載的,繼續(xù)看下initForZygote:
/**
* Hook some methods which we want to create an easier interface for developers.
*/
/*package*/ static void initForZygote() throws Throwable {
if (needsToCloseFilesForFork()) {
XC_MethodHook callback = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.closeFilesBeforeForkNative();
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.reopenFilesAfterForkNative();
}
};
Class<?> zygote = findClass("com.android.internal.os.Zygote", null);
hookAllMethods(zygote, "nativeForkAndSpecialize", callback);
hookAllMethods(zygote, "nativeForkSystemServer", callback);
}
final HashSet<String> loadedPackagesInProcess = new HashSet<>(1);
// normal process initialization (for new Activity, Service, BroadcastReceiver etc.)
findAndHookMethod(ActivityThread.class, "handleBindApplication", "android.app.ActivityThread.AppBindData", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
ActivityThread activityThread = (ActivityThread) param.thisObject;
ApplicationInfo appInfo = (ApplicationInfo) getObjectField(param.args[0], "appInfo");
String reportedPackageName = appInfo.packageName.equals("android") ? "system" : appInfo.packageName;
SELinuxHelper.initForProcess(reportedPackageName);
ComponentName instrumentationName = (ComponentName) getObjectField(param.args[0], "instrumentationName");
if (instrumentationName != null) {
Log.w(TAG, "Instrumentation detected, disabling framework for " + reportedPackageName);
XposedBridge.disableHooks = true;
return;
}
CompatibilityInfo compatInfo = (CompatibilityInfo) getObjectField(param.args[0], "compatInfo");
if (appInfo.sourceDir == null)
return;
setObjectField(activityThread, "mBoundApplication", param.args[0]);
loadedPackagesInProcess.add(reportedPackageName);
LoadedApk loadedApk = activityThread.getPackageInfoNoCheck(appInfo, compatInfo);
XResources.setPackageNameForResDir(appInfo.packageName, loadedApk.getResDir());
XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(XposedBridge.sLoadedPackageCallbacks); // 讀取hook 回調
lpparam.packageName = reportedPackageName;
lpparam.processName = (String) getObjectField(param.args[0], "processName");
lpparam.classLoader = loadedApk.getClassLoader();
lpparam.appInfo = appInfo;
lpparam.isFirstApplication = true;
XC_LoadPackage.callAll(lpparam); // 加載
if (reportedPackageName.equals(INSTALLER_PACKAGE_NAME))
hookXposedInstaller(lpparam.classLoader);
}
});
// ...
這兒注冊了handleBindApplication的hook,這樣在應用啟動的時候會attach到AMS,然后ams就會發(fā)一個BindApplication調用過來,這時候應用就會觸發(fā)handleBindApplication,然后將我們注冊的回調再加載下,加載操作就是通過XC_LoadPackage.callAll完成的, 這里會調用每個Hook的call方法,直接看Hook的call
protected void call(Param param) throws Throwable {
if (param instanceof LoadPackageParam)
handleLoadPackage((LoadPackageParam) param);
}
終于看到了我們實現(xiàn)的方法了。
繼續(xù)看下onVmCreated,因為AndroidRuntime其實就是創(chuàng)建并啟動虛擬機,然后執(zhí)行給定類的main方法,那創(chuàng)建虛擬機后,native就會收到回調:
virtual void onVmCreated(JNIEnv* env)
{
// 調用xposed的初始化方法
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<jclass>(env->NewGlobalRef(mClass));
}
繼續(xù)看下xposed的onVmCreate方法:
/** Load the libxposed_*.so library for the currently active runtime. */
void onVmCreated(JNIEnv* env) {
// Determine the currently active runtime
const char* xposedLibPath = NULL;
// 根據(jù)是art還是dalvik加載不同的so
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
// 初始化so
if (xposedInitLib(xposed)) {
xposed->onVmCreated(env);
}
}
目前大部分都是art虛擬機了,我們看下art的xposedInitLib:
/** Called by Xposed's app_process replacement. */
bool xposedInitLib(XposedShared* shared) {
xposed = shared;
xposed->onVmCreated = &onVmCreatedCommon;
return true;
}
那接下來就是調用onVmCreatedCommon:
void onVmCreatedCommon(JNIEnv* env) {
if (!initXposedBridge(env) || !initZygoteService(env)) {
return;
}
// 這兒就是將上面兩個init獲取的method id保存起來
if (!onVmCreated(env)) {
return;
}
xposedLoadedSuccessfully = true;
return;
}
終于看到初始化操作了,先看下initXposedBridge:
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));
// 注冊jni 方法
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;
}
// 保存handleHookedMethod的method 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;
}
注冊了哪些方法呢?
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));
}
這幾個就是我們hook的時候會調用的幾個方法,尤其是hookMethodNative
繼續(xù)看下initZygoteService:
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;
}
// 還是注冊方法,不過不影響主要流程,可以簡單帶過
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<jclass>(env->NewGlobalRef(classFileResult));
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;
}
到了現(xiàn)在,應用啟動是完成了。
我們再看下調用的findAndHookMethod的內容:
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);
}
找到方法,然后執(zhí)行hookMathod, 只要有classloader,找類用反射就可以了??聪耯ookMethod:
public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
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;
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);
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);
hookMethodNative(hookMethod, declaringClass, slot, additionalInfo);
}
return callback.new Unhook(hookMethod);
}
這兒就是獲取這個method的類,參數(shù)等信息,然后跳到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;
}
// Get the ArtMethod of the method to be hooked.
ArtMethod* artMethod = ArtMethod::FromReflectedMethod(soa, javaReflectedMethod);
// Hook the method
artMethod->EnableXposedHook(soa, javaAdditionalInfo);
}
這兒就是直接將回調設置給art虛擬機了。
void ArtMethod::EnableXposedHook(ScopedObjectAccess& soa, jobject additional_info) {
if (UNLIKELY(IsXposedHookedMethod())) {
// Already hooked
return;
} else if (UNLIKELY(IsXposedOriginalMethod())) {
// This should never happen
ThrowIllegalArgumentException(StringPrintf("Cannot hook the method backup: %s", PrettyMethod(this).c_str()).c_str());
return;
}
// Create a backup of the ArtMethod object
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->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);
// Save extra information in a separate structure, stored instead of the native method
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;
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*));
SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler());
SetCodeItemOffset(0);
// Adjust access flags.
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);
}
這兒就是告訴虛擬機在解釋這個方法的時候,走我們自己定義的方法。
到了這兒基本流程就介紹完了。