Android對(duì)so進(jìn)行簡(jiǎn)單hook思路解析
1、什么是Hook
Hook 技術(shù)又叫做鉤子函數(shù),在系統(tǒng)沒(méi)有調(diào)用該函數(shù)之前,鉤子程序就先捕獲該消息,鉤子函數(shù)先得到控制權(quán),這時(shí)鉤子函數(shù)既可以加工處理(改變)該函數(shù)的執(zhí)行行為,還可以強(qiáng)制結(jié)束消息的傳遞。簡(jiǎn)單來(lái)說(shuō),就是把系統(tǒng)的程序拉出來(lái)變成我們自己執(zhí)行代碼片段。
2、對(duì)App的so進(jìn)行Hook的一種思路
我們知道現(xiàn)在JNI在Android開(kāi)發(fā)中是特別重要的,使用JNI有什么好處呢?
- Preference,C/C++在運(yùn)行性能上面甩Java幾條GAI
- Security,更多的加密解密還是放在Native上。
優(yōu)點(diǎn)不止這兩點(diǎn),比如在Native里面開(kāi)辟空間并不受JVM管理,JVM怎么使用native memory。這里不再贅述。
本文提供一種對(duì)Android上so庫(kù)進(jìn)行Hook的一種思路,不涉及ELF的查看修改,不改動(dòng)對(duì)方的調(diào)用方式。 思路就是一招偷梁換柱,用自己的so替換App的so,讓對(duì)象調(diào)用自己的so的時(shí)候調(diào)用我們自己寫(xiě)的so,我們?cè)僬{(diào)用原來(lái)的so,這樣就可以獲得對(duì)方so方法的輸入輸出。
可以應(yīng)用在想獲取對(duì)方App的數(shù)據(jù)傳遞格式或者無(wú)法破解對(duì)方的加解密,但是可以通過(guò)hook獲取對(duì)方的數(shù)據(jù)格式再調(diào)用對(duì)方的加解密方法得到自己想要的結(jié)果。
3、一個(gè)最基本的JNI sample程序代表目標(biāo)宿主Apk
這里我們自己準(zhǔn)備一個(gè)宿主Apk,直接用AndroidStudio新建一個(gè)支持JNI的工程,勾選Include C++ support,默認(rèn)生成一個(gè)Android工程。
默認(rèn)的MainActivity.java和native-lib.cpp分別長(zhǎng)這樣子:
//MainActivity public class MainActivity extends Activity { static { System.loadLibrary("native-lib"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tv = (TextView) findViewById(R.id.sample_text); tv.setText(stringFromJNI()); } public native String stringFromJNI(String param); } //native-lib.cpp #include <jni.h> #include <string> extern "C" JNIEXPORT jstring JNICALL Java_com_hook_yocn_MainActivity_stringFromJNI( JNIEnv* env, jobject /* this */) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); }
我們?yōu)榱诉壿嬊逦晕⒆鲆稽c(diǎn)修改,邏輯是希望傳入一個(gè)字符串,然后在C++里面對(duì)字符串的每個(gè)字符都+1操作,也就是 java -> kbwb。改完之后代碼長(zhǎng)這個(gè)樣子:
//MainActivity.java public class MainActivity extends Activity { static { System.loadLibrary("native-lib"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tv = (TextView) findViewById(R.id.sample_text); tv.setText(stringFromJNI("java")); } public native String stringFromJNI(String param); } //native-lib.cpp #include <jni.h> #include <string> #include <android/log.h> #define LOG_TAG "hook" #define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) extern "C" JNIEXPORT jstring JNICALL Java_com_hook_yocn_MainActivity_stringFromJNI(JNIEnv *env, jobject obj, jstring param) { int length = (env)->GetStringLength(param); const char *nativeString = (env)->GetStringUTFChars(param, 0); char *resultChars = new char[length + 1]; for (int i = 0; i < length; ++i) { resultChars[i] = nativeString[i] + 1; } resultChars[length] = '\0'; std::string par = resultChars; LOGE("輸入?yún)?shù)->%s,長(zhǎng)度:%d", nativeString, length); LOGE("輸出結(jié)果->%s", resultChars); return env->NewStringUTF(resultChars); }
我們跑一下,如果正常的話,輸出應(yīng)該是:
現(xiàn)在準(zhǔn)備工作就做完了,我們有了一個(gè)目標(biāo)宿主Apk,下面開(kāi)始著手進(jìn)行Hook。
4、開(kāi)始Hook
我們需要一臺(tái)root了的手機(jī)或者一個(gè)Android模擬器,這里我用模擬器演示。
先理一下思路,按照我們的理解,需要以下幾步:
- 4.1、 編寫(xiě)Hook的代碼,并打包成so
- 4.2、 找到目標(biāo)app我們要替換的so的存放目錄,把對(duì)方的so復(fù)制一份,用我們自己寫(xiě)的so替換掉對(duì)方的so。
- 4.3、 重新運(yùn)行app查看調(diào)用結(jié)果
我們按照思路一步步往下走:
4.1、編寫(xiě)Hook代碼
因?yàn)樯厦娴乃拗鰽pk是我們自己寫(xiě)的,所以我們知道調(diào)用的方法名字,而面對(duì)一個(gè)我們并不了解的陌生Apk的時(shí)候,Apk尤其是so對(duì)我們來(lái)說(shuō)完全是一個(gè)黑盒,這個(gè)時(shí)候我們?cè)趺粗酪趺淳帉?xiě)Hook的代碼呢?
- 使用IDA查看對(duì)方的so文件,這個(gè)我也不熟悉,大牛隨便用。
- 換個(gè)思路,so我們看不了,但是我們可以查看java代碼,可以從java代碼中找思路。
- 什么都不用,直接運(yùn)行,哪個(gè)方法報(bào)錯(cuò)我們就準(zhǔn)備神呢么方法。我們用這個(gè)方法講解。
所以我們先編一個(gè)空的so出來(lái),命名為libhook.so
,或者隨便找個(gè)so直接到第4.2
步,找到app的so存放目錄。這個(gè)目錄一般在data/data/packageName/lib/xxx.so
,比如我現(xiàn)在宿主包名是com.ahook.yocn
,需要hook的so叫做libnative-lib.so
,所以目錄應(yīng)該是在/data/data/com.ahook.yocn/lib/native-lib.so
。
我們直接在terminal里面執(zhí)行:
// 我們自己的libhook.so push到內(nèi)存卡并且重命名為libnative-lib.so adb push libhook.so /mnt/sdcard/libnative-lib.so // 進(jìn)到手機(jī)目錄 adb shell // 獲取root權(quán)限 su // 到so存放目錄 cd /data/data/com.ahook.yocn/lib // 宿主的so拷貝一份,因?yàn)槲覀冞€要調(diào)用,起個(gè)別名,我們還要用 cp libnative-lib.so libnative-lib-src.so // 將我們push進(jìn)來(lái)的so拷貝到當(dāng)前目錄并覆蓋宿主apk的so cp /mnt/sdcard/libnative-lib.so .
su是為了獲取root權(quán)限,因?yàn)閐ata/data/目錄需要root權(quán)限才可以進(jìn)。我們退出app(如果沒(méi)有退出的話),重新打開(kāi)app,不出意料會(huì)報(bào)錯(cuò),如果沒(méi)有報(bào)錯(cuò)可能是上面的so拷貝沒(méi)有生效,需要double check。
2019-07-06 21:49:45.531 12052-12052/com.ahook.yocn E/com.ahook.yocn: No implementation found for java.lang.String com.hook.yocn.MainActivity.stringFromJNI(java.lang.String) (tried Java_com_hook_yocn_MainActivity_stringFromJNI and Java_com_hook_yocn_MainActivity_stringFromJNI__Ljava_lang_String_2) 2019-07-06 21:49:45.533 12052-12052/com.ahook.yocn E/AndroidRuntime: FATAL EXCEPTION: main Process: com.ahook.yocn, PID: 12052 java.lang.UnsatisfiedLinkError: No implementation found for java.lang.String com.hook.yocn.MainActivity.stringFromJNI(java.lang.String) (tried Java_com_hook_yocn_MainActivity_stringFromJNI and Java_com_hook_yocn_MainActivity_stringFromJNI__Ljava_lang_String_2) at com.hook.yocn.MainActivity.stringFromJNI(Native Method) at com.hook.yocn.MainActivity.onCreate(MainActivity.java:21) ......
報(bào)錯(cuò)告訴我們有一個(gè)全限定名為com.hook.yocn.MainActivity.stringFromJNI的方法沒(méi)有找到,這個(gè)方法接受一個(gè)String參數(shù),并且有一個(gè)String返回值~
So,我們找到了宿主Apk調(diào)用的第一個(gè)方法的名字,并且知道它的參數(shù)和返回值,我們可以開(kāi)始干活了。
// hook.cpp #include <jni.h> #include <string.h> #include <vector> #include <stdio.h> #include <android/log.h> #include <android/bitmap.h> #include <unistd.h> #include <sys/stat.h> #include <string> #define LOG_TAG "hook" #define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) using namespace std; extern "C" { #include <stdio.h> #include <stdlib.h> #include <dlfcn.h> //宿主動(dòng)態(tài)鏈接庫(kù)路徑 #define LIB_CACULATE_PATH "/data/data/com.ahook.yocn/lib/libnative-lib-src.so" //函數(shù)指針 typedef jstring (*CAC_FUNC)(JNIEnv *env, jobject thiz, jstring param); jstring callFunc(JNIEnv *env, jobject thiz, jstring param) { void *handle; char *error; CAC_FUNC cac_func = NULL; //打開(kāi)動(dòng)態(tài)鏈接庫(kù) handle = dlopen(LIB_CACULATE_PATH, RTLD_LAZY); if (!handle) { LOGV("dlopen: %s\n", dlerror()); } //清除之前存在的錯(cuò)誤 dlerror(); //獲取一個(gè)函數(shù) *(void **) (&cac_func) = dlsym(handle, "Java_com_hook_yocn_MainActivity_stringFromJNI"); if ((error = const_cast<char *>(dlerror())) != NULL) { LOGV("dlsym: %s\n", error); } jstring ret = (*cac_func)(env, thiz, param); // printfJstring(env, thiz, ret); //關(guān)閉動(dòng)態(tài)鏈接庫(kù) // dlclose(handle); return ret; } JNIEXPORT jstring JNICALL Java_com_hook_yocn_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz, jstring param) { LOGE("Java_com_hook_yocn_MainActivity_stringFromJNI"); string hookPre = "Hook_Head "; string paramString = (env)->GetStringUTFChars(param, 0) + hookPre; string modifyString = hookPre + paramString; jstring modifyParam = env->NewStringUTF(modifyString.c_str()); jstring ss = callFunc(env, thiz, modifyParam); string rawResult = (env)->GetStringUTFChars(ss, 0); string hookEndString = rawResult + " Hook_End"; return env->NewStringUTF(hookEndString.c_str()); } } //extern "C" //Android.mk LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_LDLIBS := -llog -ljnigraphics LOCAL_MODULE := hook APP_PROJECT_PATH:= LOCAL_PATH LOCAL_SRC_FILES := hook.cpp LOCAL_CFLAGS = -ffast-math -O3 -funroll-loops include $(BUILD_SHARED_LIBRARY) //Application.mk APP_STL := gnustl_static APP_CPPFLAGS := -frtti -fexceptions APP_ABI := x86 APP_PLATFORM := android-28
代碼很簡(jiǎn)單
- 實(shí)現(xiàn)一個(gè)Java_com_hook_yocn_MainActivity_stringFromJNI的JNI方法接受一個(gè)jstring,返回一個(gè)jstring。
- 找到宿主原來(lái)的so,存到LIB_CACULATE_PATH里,使用dlopen方法打開(kāi)并且調(diào)用它自己的stringFromJNI方法并且得到一個(gè)jstring返回值。這里我們可以對(duì)方法的輸入輸出任意修改
我本身更熟悉ndk-build,如果熟悉makefile,也可以用makefile。因?yàn)槲矣玫氖悄M器所以Application.mk里面APP_ABI數(shù)x86,如果用真機(jī)的是arm架構(gòu)的可以修改成armeabi-v7a
,編寫(xiě)完之后目錄結(jié)構(gòu)差不多這樣子的,然后進(jìn)到j(luò)ni目錄執(zhí)行ndk-build可以得到libhook.so。
得到了so之后我們執(zhí)行
adb push libhook.so /mnt/sdcard/libnative-lib.so ...
然后重復(fù)上面的代碼替換掉宿主的so。執(zhí)行完之后重新打開(kāi)app,應(yīng)該能看到下面這樣的輸出。
如果能夠得到結(jié)果就說(shuō)明我們hook成功了??赡芩拗鱝pk的方法不止一個(gè),我們成功模擬了第一個(gè)方法后后面的還會(huì)報(bào)錯(cuò),所以我們需要一直重復(fù)上面的步驟直到運(yùn)行正?;蛘叩玫轿覀兿胍臄?shù)據(jù)為止。
我們整理一下思路:
- 找到目標(biāo)app我們要替換的so的存放目錄,把對(duì)方的so復(fù)制一份。
- 打個(gè)空的so包后者隨便找個(gè)so,用我們自己的so替換掉對(duì)方的so。
- 重新運(yùn)行app查看調(diào)用結(jié)果,這時(shí)候肯定會(huì)出錯(cuò),根據(jù)出錯(cuò)的全限定方法名編寫(xiě)我們的hook代碼
- 根據(jù)全限定名和輸入輸出編寫(xiě)宿主需要的代碼,用輸入調(diào)用宿主的so得到輸出,對(duì)輸出加工后返回回去,相當(dāng)于一個(gè)代理模式
- 重復(fù)1234步直到宿主app運(yùn)行正?;蛘叩玫轿覀兿胍臄?shù)據(jù)。
如果有問(wèn)題可以去看源碼,本文源碼:github.com/yocn/HookSo…
以上就是Android對(duì)so進(jìn)行簡(jiǎn)單hook思路解析的詳細(xì)內(nèi)容,更多關(guān)于Android so hook的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android實(shí)現(xiàn)在列表List中顯示半透明小窗體效果的控件用法詳解
這篇文章主要介紹了Android實(shí)現(xiàn)在列表List中顯示半透明小窗體效果的控件用法,結(jié)合實(shí)例形式分析了Android半透明提示框的實(shí)現(xiàn)與設(shè)置技巧,需要的朋友可以參考下2016-06-06Android Studio和阿里云數(shù)據(jù)庫(kù)實(shí)現(xiàn)一個(gè)遠(yuǎn)程聊天程序
本文主要介紹了Android Studio和阿里云數(shù)據(jù)庫(kù)實(shí)現(xiàn)一個(gè)遠(yuǎn)程聊天程序,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11Flutter質(zhì)感設(shè)計(jì)之列表項(xiàng)
這篇文章主要為大家詳細(xì)介紹了Flutter質(zhì)感設(shè)計(jì)之列表項(xiàng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08Android中生成、使用Json數(shù)據(jù)實(shí)例
這篇文章主要介紹了Android中生成、使用Json數(shù)據(jù)實(shí)例,本文直接給出了實(shí)現(xiàn)代碼,相對(duì)容易理解,需要的朋友可以參考下2014-10-10Android 帶有彈出收縮動(dòng)畫(huà)的扇形菜單實(shí)例
本篇文章主要介紹了Android 帶有彈出收縮動(dòng)畫(huà)的扇形菜單實(shí)例,具有一定的參考價(jià)值,有興趣的可以了解一下2017-06-06Android開(kāi)發(fā)實(shí)現(xiàn)Gallery畫(huà)廊效果的方法
這篇文章主要介紹了Android開(kāi)發(fā)實(shí)現(xiàn)Gallery畫(huà)廊效果的方法,結(jié)合具體實(shí)例形式分析了Android使用Gallery實(shí)現(xiàn)畫(huà)廊功能的具體操作技巧與相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-06-06Android實(shí)現(xiàn)開(kāi)機(jī)自動(dòng)啟動(dòng)Service或app的方法
這篇文章主要介紹了Android實(shí)現(xiàn)開(kāi)機(jī)自動(dòng)啟動(dòng)Service或app的方法,結(jié)合實(shí)例形式分析了Android開(kāi)機(jī)自啟動(dòng)程序的具體步驟與相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-07-07Android 簡(jiǎn)單服務(wù)定位器模式實(shí)現(xiàn)
這篇文章主要介紹了Android 簡(jiǎn)單服務(wù)定位器模式實(shí)現(xiàn),幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-03-03