使用Magisk+riru實現全局改機
前言
提到全局改機,我們想到修改的不是修改Android源碼就是利用Xposed改機,前者成本太高,後者只能修改Java層的數據不夠徹底。magisk是Android平台上功能強大的工具,利用它可以隨心所欲的修改系統。它或許幫我們在不修改系統源碼的情況實現相對完美的改機。那麼,riru是什麼?它和magisk有什麼關係?它又和我們的模塊有什麼關係?簡而言之,riru是magisk的模塊,本文寫的模塊也是magisk模塊,但是它又依賴於riru。那riru模塊提供了什麼功能呢?riru模塊能夠感知App的啟動,並且能夠給App注入一些代碼,依賴riru模塊的模塊也能擁有這個能力。我們實現改機,其實就是需要儘早的感知App啟動的,這樣我們才能儘早hook一些函數,使App啟動的時候獲取到的數據是我們修改後的數據。了解了相關知識,我們就可以開始編寫模塊了。
準備
riru模塊的作者寫了riru模塊的模板,我們在這個項目的基礎上改就行,首先將模塊克隆下來並使用Android Studio打開:
git clone //github.com/RikkaApps/Riru-ModuleTemplate
因為改機不僅要改Java層的數據,還要改Native層的數據,所以我們要用Native層的Hook框架,這裡用的Hook框架是Dobby。作者編譯了靜態鏈接文件,我們去下載下來用即可://github.com/jmpews/Dobby/releases/tag/latest
下載好後解壓,將android目錄里的子目錄全部放入Riru-ModuleTemplate
項目的module模塊下的libs目錄下(沒有則創建),然後將文件夾重命名:
- arm64 -> arm64-v8a
- armv7 -> armeabi-v7a
修改後的目錄結構應如下:
同時在module/src/main/cpp
目錄下創建一個dobby目錄,然後去下載dobby.h文件,來放入該目錄下。下載地址://github.com/jmpews/Dobby/blob/master/include/dobby.h
修改cmake文件,使編譯的時候,把dobby的靜態鏈接庫也鏈接上。修改module/src/main/cpp/CMakeLists.txt
include_directories(
dobby
)
add_library(local_dobby STATIC IMPORTED)
set_target_properties(local_dobby PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${ANDROID_ABI}/libdobby.a)
add_library(${MODULE_NAME} SHARED main.cpp ${CMAKE_CURRENT_BINARY_DIR}/config.cpp)
target_link_libraries(${MODULE_NAME} local_dobby log riru::riru)
準備工作完成,就可以開始編寫代碼了
編寫代碼
轉到module/src/main/cpp/main.cpp
文件,這是模塊的核心代碼文件,裏面定義一些不太豐滿的函數,等着我們去填補。
static void forkAndSpecializePost(JNIEnv *env, jclass clazz, jint res) {
// Called "after" com_android_internal_os_Zygote_nativeForkAndSpecialize in frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
// "res" is the return value of com_android_internal_os_Zygote_nativeForkAndSpecialize
if (res == 0) {
// In app process
// When unload allowed is true, the module will be unloaded (dlclose) by Riru
// If this modules has hooks installed, DONOT set it to true, or there will be SIGSEGV
// This value will be automatically reset to false before the "pre" function is called
riru_set_unload_allowed(true);
} else {
// In zygote process
}
}
static void specializeAppProcessPre(
JNIEnv *env, jclass clazz, jint *uid, jint *gid, jintArray *gids, jint *runtimeFlags,
jobjectArray *rlimits, jint *mountExternal, jstring *seInfo, jstring *niceName,
jboolean *startChildZygote, jstring *instructionSet, jstring *appDataDir,
jboolean *isTopApp, jobjectArray *pkgDataInfoList, jobjectArray *whitelistedDataInfoList,
jboolean *bindMountAppDataDirs, jboolean *bindMountAppStorageDirs) {
// Called "before" com_android_internal_os_Zygote_nativeSpecializeAppProcess in frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
// Parameters are pointers, you can change the value of them if you want
// Some parameters are not exist is older Android versions, in this case, they are null or 0
}
static void specializeAppProcessPost(
JNIEnv *env, jclass clazz) {
// Called "after" com_android_internal_os_Zygote_nativeSpecializeAppProcess in frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
// When unload allowed is true, the module will be unloaded (dlclose) by Riru
// If this modules has hooks installed, DONOT set it to true, or there will be SIGSEGV
// This value will be automatically reset to false before the "pre" function is called
riru_set_unload_allowed(true);
}
static void forkSystemServerPre(
JNIEnv *env, jclass clazz, uid_t *uid, gid_t *gid, jintArray *gids, jint *runtimeFlags,
jobjectArray *rlimits, jlong *permittedCapabilities, jlong *effectiveCapabilities) {
// Called "before" com_android_internal_os_Zygote_forkSystemServer in frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
// Parameters are pointers, you can change the value of them if you want
// Some parameters are not exist is older Android versions, in this case, they are null or 0
}
static void forkSystemServerPost(JNIEnv *env, jclass clazz, jint res) {
// Called "after" com_android_internal_os_Zygote_forkSystemServer in frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
if (res == 0) {
// In system server process
} else {
// In zygote process
}
}
static void onModuleLoaded() {
// Called when this library is loaded and "hidden" by Riru (see Riru's hide.cpp)
// If you want to use threads, start them here rather than the constructors
// __attribute__((constructor)) or constructors of static variables,
// or the "hide" will cause SIGSEGV
}
函數名和注釋都非常清晰,雖然函數很多,但是我們只需要在forkAndSpecializePost
函數中編寫hook代碼就可以。因為我們要hook代碼運行在App進程中
if (res == 0) {
// In app process
// When unload allowed is true, the module will be unloaded (dlclose) by Riru
// If this modules has hooks installed, DONOT set it to true, or there will be SIGSEGV
// This value will be automatically reset to false before the "pre" function is called
riru_set_unload_allowed(true);
}
當我們編寫Native層代碼的時候,想要獲取當前手機的機型信息,最好的方式是通過調用__system_property_get
來獲取,那麼我們可以hook這個函數。但在這之前,我們需要知道要修改哪個字段,這些字段又分別賦予什麼值。那這些信息通過讀取文件來得到好了,由模塊使用者來定義,編寫一個讀取配置的函數:
void readPropFile(const char* filename,std::map<std::string,std::string> &map){
FILE *fp = fopen(filename,"r");
if(fp == NULL){
return;
}
char buf[256];
while(fgets(buf,256,fp) != NULL){
char *sep = strchr(buf,'=');
*sep = '\0';
std::string key(buf);
std::string value(sep + 1);
map[key] = value;
}
fclose(fp);
}
readPropFile函數讀取一個文件,然後將每一行等號前面的內容作為key,等號後面的內容作為value放入傳進來的map,假設一個文件內容如下:
MODEL=Mi5
那麼它讀到的key是MODEL
,value是Mi5
知道要修改成什麼,就可以hook __system_property_get函數,下面是hook代碼
static int my__system_property_get(const char* name, char* value){
if(NULL == name || NULL == value){
return origin__system_property_get(name,value);
}
auto ret = propMap.find(name);
if(ret != propMap.end()){
const char* valueChs = ret->second.c_str();
strcpy(value,valueChs);
return strlen(valueChs);
}
return origin__system_property_get(name,value);
}
void initDobby(){
void* sym = DobbySymbolResolver(NULL,"__system_property_get");
if(NULL != sym) {
DobbyHook(sym, (void *) my__system_property_get, (void **) &origin__system_property_get);
}
}
其中:
- DobbySymbolResolver 是尋找函數符號地址
- DobbyHook 是啟用hook的函數
- my__system_property_get 是跳板函數,就是原函數被調用時會調用的函數
- origin__system_property_get 是原函數的地址
Hook代碼編寫完成後,在forkAndSpecializePost函數中調用initDobby函數開始我們的hook
static void forkAndSpecializePost(JNIEnv *env, jclass clazz, jint res) {
// Called "after" com_android_internal_os_Zygote_nativeForkAndSpecialize in frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
// "res" is the return value of com_android_internal_os_Zygote_nativeForkAndSpecialize
LOGE("forkAndSpecializePost");
if (res == 0) {
// In app process
readPropFile(PROP_CONF_PATH,propMap);
initDobby();
// When unload allowed is true, the module will be unloaded (dlclose) by Riru
// If this modules has hooks installed, DONOT set it to true, or there will be SIGSEGV
// This value will be automatically reset to false before the "pre" function is called
riru_set_unload_allowed(false);
} else {
// In zygote process
}
}
注意:riru_set_unload_allowed(false);
這一行,注釋明確寫道如果要進行hook必須設為false,不然會崩潰,事實也確實如此。
hook了native層的函數,實現修改層的機型數據,還需要修改Java層的數據,Java層的機型信息我們只要修改android.os.Build
類的字段。而這些字段不用Hook,直接設置即可。
static void setBuild(JNIEnv* env){
jclass BuildClass = env->FindClass("android/os/Build");
std::map<std::string,std::string>::iterator it;
for(it = buildMap.begin();it != buildMap.end();it++){
jstring key = env->NewStringUTF(it->first.c_str());
jstring value = env->NewStringUTF(it->second.c_str());
jfieldID buildField = env->GetStaticFieldID(BuildClass,it->first.c_str(),"Ljava/lang/String;");
if(env->ExceptionCheck()){
env->ExceptionClear();
continue;
}
env->SetStaticObjectField(BuildClass,buildField,value);
if(env->ExceptionCheck()){
env->ExceptionClear();
}
}
}
注意:在編寫代碼時要十分注意,如果代碼崩潰,會導致手機無法開機。
這樣我們的代碼基本寫完了
配置模塊信息
上文我們提到過,我們編寫的模塊雖然是依賴riru模塊,但是它本質也是Magisk模塊。那麼它也會有它的名字,版本,說明,作者信息等,然而這些是由我們開發者去配置的
這些配置信息可以在Riru-ModuleTemplate項目的module.gradle
中配置,這個文件的模板在module.example.gradle
,可以將module.example.gradle
重命名為module.gradle
然後進一步修改
ext {
/*
This name will be used in the name of the so file ("lib${moduleLibraryName}.so").
*/
moduleLibraryName = "template"
/* Minimal supported Riru API version, used in the version check of riru.sh */
moduleMinRiruApiVersion = 24
/* The version name of minimal supported Riru, used in the version check of riru.sh */
moduleMinRiruVersionName = "v24.0.0"
/* Maximum supported Riru API version, used in the version check of riru.sh */
moduleRiruApiVersion = 26
/*
Magisk module ID
Since Magisk use it to distinguish different modules, you should never change it.
Note, the older version of the template uses '-' instead of '_', if your are upgrading from
the older version, please pay attention.
*/
magiskModuleId = "riru_template"
moduleName = "Template"
moduleAuthor = "Template"
moduleDescription = "Riru module template. Requires Riru $moduleMinRiruVersionName or above."
moduleVersion = "v26.0.0"
moduleVersionCode = 26
}
編譯
Riru-ModuleTemplate項目編譯需要Jdk11以上,裝好後在項目的根目錄執行:
gradlew :module:assembleRelease
如果沒有錯誤,就會在項目的out目錄生成一個zip文件,這個就是我們編譯好的magisk模塊
刷入模塊
在手機上,安裝Magisk和riru模塊,安裝後,將編譯好的模塊zip文件推入手機,在MagiskManager中安裝我們的模塊:
效果
安裝後,重啟手機。打開MagiskManager看到我們的模塊已正常啟用:
去AIDA看看效果:
機型已經被我們修改,說明改機生效了。最後附上我寫的改機模塊://github.com/luoyesiqiu/Riru-gaiji