一、前言
之前寫的 「裝X指南之Xposed安裝與配置」,有人反饋手機 root 風險較大,而且操作成本高,有沒有什麼方法是不需要 root 就能夠實現 hook 的或者不需要 Xposed 也能玩起外掛的?於是就有了這篇文章,離開 Xposed ,帶你免 root 實現 hook!
二、VirtualApp
1.關於 VirtualApp 的介紹
VA目前被廣泛應用於外掛化開發、無感知熱更新、自動化、多開等技術領域,但它決不僅限於此,Android本身就是一個極其開放的平臺,免安裝執行APK這一Feature開啟了無限可能-----這都取決於您的想象力。
感謝 asLody 開源,據說他寫這個專案才高二,佩服佩服~
- VirtualApp 專案地址:
2.VirtualApp 的原理
VirtualApp
偽造了一套 framework
程式碼,實現所有在其程式啟動的應用,都執行在一個虛擬空間(注:個人理解,如有錯誤,還請指出)。
- VirtualApp 原始碼學習與原理分析
3.VirtualApp 使用問題
Github 上的程式碼,作者已經沒有繼續開源更新了,可以看到後續的所有修改,都在作者的商業版上操作,所以有可能在使用上會出現一些 bug
。
其實可以看到「商業版」,不管穩定性與相容性,都做了很大的修復和改動,最重要的是,支援 Dalvik 和 Art 的 Java Hook( API 同 Xposed ),可惜在作者沒有公開原始碼的情況下,我們個人不可能為了學習去購買「商業版」~
特別說明:作者明確指出,如果專案需要投入商業使用,請購買「商業版」。我們這裡僅做技術學習使用
4.VirtualHook 介紹
上文說到我們無法使用「商業版」的 VirtualApp
,來進行 Hook ,準確來說是作者沒把 Hook 的 Api 公開。
下面我要介紹另一個基於 VirtualApp
改造的專案 —— VirtualHook(區分:VirtualApp
與 VirtualHook
的區別,不要搞混了,後文使用 VirtualHook 來實踐),感謝 rk700 開源 VirtualHook 與 YAHFA
1)VirtualHook 專案地址:
2)VirtualHook 構成:
VirtualHook is a tool for hooking application without root permission. It is based on two projects:
- VirtualApp. It's a plugin framework which allows running applications in its virtual space.
- YAHFA . It's a hook framework for ART which allows hooking Java method of the application.
3)VirtualHook 注入
- 關鍵的地方,
VirtualHook
修改VirtualApp
的核心程式碼,提供 Hook 注入程式碼的視窗 - 以下是在
VirtualApp
裡面VClienImpl
類注入的關鍵程式碼
DexClassLoader dexClassLoader = new DexClassLoader(apkPath,
VEnvironment.getDalvikCacheDirectory().getAbsolutePath(),
libPath,
appClassLoader);
// YAHFA do hook
HookMain.doHookDefault(dexClassLoader, appClassLoader);
複製程式碼
public void findAndBackupAndHook(Class targetClass, String methodName,
String methodSig, Method hook, Method backup);
複製程式碼
三、YAHFA
1.YAHFA 介紹
YAHFA(Yet Another Hook Framework for ART)
是基於 ART 的 Hook 框架,支援 Android 5.0 ~ 9.0
版本的 Java 方法的 Hook 與替代 。而 VirtualHook 則是靠 YAHFA 實現的免 Root Hook。
- 來自看雪論壇:
- YAHFA 專案地址:
2.YAHFA 原理
我是看不太懂裡面的原理,但是還是把別人的分析過程,貼出來給大家,希望看懂的朋友,不吝分享:
- 寫文的時候,好像作者的部落格掛了,不過還是寫上吧
- csdn 一位博主分享的原理分析
3.YAHFA Hook
解釋一下相關變數與方法:
- className:指定要 hook 的類名
- methodName:指定要 hook 的方法
- methodSig:指定要 hook 的方法簽名
- hook():該方法是你 hook 方法需要處理的邏輯,這裡執行 hook 相關操作
- backup():是原方法的呼叫,一般不需要重寫什麼
1)普通方法
如 Log.e()
方法。程式碼如下:
public class Hook_Log_e {
public static String className = "android.util.Log";
public static String methodName = "e";
public static String methodSig = "(Ljava/lang/String;Ljava/lang/String;)I";
public static int hook(String tag, String msg) {
Log.w("YAHFA", "in Log.e(): "+tag+", "+msg);
return backup(tag, msg);
}
public static int backup(String tag, String msg) {
Log.w("YAHFA", "Log.e() should not be here");
return 1;
}
}
複製程式碼
2)靜態方法
靜態方法和靜態差不多,區別就是,靜態的方法在hook和origin的引數中,少一個 Object 的引數。如 URI.create()
方法。程式碼如下:
public class Hook_url {
public static String className = "java.net.URI";
public static String methodName = "create";
public static String methodSig = "(Ljava/lang/String;)Ljava/net/URI;";
public static Object hook(String url)
{
// 改變 url 的值
url = "http://www.baidu.com";
return origin(url);
}
public static Object origin(String url)
{
Log.w("YAHFA", "String.startsWith() should not be here");
return url;
}
}
複製程式碼
3)匿名內部類
內部類只是編譯時的概念,一旦編譯成功,就會出現兩個不同的類,例如,類outClass
中有個intClass
,那麼編譯後就出現一個名為outClass.class
和一個outClass$intClass.class
的類。所以className
中就要指定類路徑為a.b.c.outClass$intClass
4.獲取方法的簽名描述符
1)方法簽名描述符組成,括號內是引數的簽名,括號外是返回值的簽名:
如 Log.e() 裡面的方法:
public static int e(String tag, String msg)
對應
(Ljava/lang/String;Ljava/lang/String;)I
複製程式碼
2)各型別參照表
- 除了 boolean 和 long 型別分別是 Z 和 J 外,其他的描述符對應的都是 Java 型別名的大寫首字母。另外,void 的描述符為 V
File Desciptor | Java Language Type |
---|---|
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
V | void |
[ | array |
L + 型別描符 + ; | 引用型別 |
- 說明:
- 陣列用 [ 表示,二維陣列 [[ 表示。如:
[Ljava/lang/String;
對應String[]
,[[Ljava/lang/Object;
對應Object[][]
- 引用型別注意前面的 L , “/” 分割和 “;” ,不要遺漏了。如:
Lcom/tencent/wcdb/Cursor;
- String 是物件,所以是:
Ljava/lang/String;
- 還是不知道怎麼寫的話,可以通過以下 adb 命令找到方法簽名描述符:
1. 檢視 Java 類的方式 javap -s java.awt.Label
2. 檢視 Android 類的方式 javap -s -bootclasspath "D:\Program Files\Android\android-sdk\platforms\android-25\android.jar" -classpath bin/classes android.app.Activity
3. 檢視第三方 Jar 的類的方式 javap -s -classpath "D:\AMap_Location.jar" com.amap.api.location.AMapLocation
複製程式碼
四、VirtualHook 搭配 YAHFA 使用教程
我們這裡是使用 VirtualHook
來實踐 。總體步驟如下:
git clone VirtualHook
工程或者下載原始碼- 新建
module
並配置為外掛 - 將
module
打包成 apk,並放到手機裡面 - 在
VirtualHook
裡面,克隆目標 App 和載入外掛 apk
專案目錄結構如下:
app
和lib
是VirtualApp
相關程式碼YAHFA
是Hook
框架程式碼demoHookPlugin
是外掛module
1.配置外掛 module
配置外掛 module
的 AndroidManifest.xml
的 meta-data
的值,設定 value
為 true
<application
android:label="@string/app_name">
<meta-data
android:name="yahfa.hook.plugin"
android:value="true"
/>
</application>
複製程式碼
2.配置 Hook 類
假如我們需要 Hook 處理 Log.e()
方法,新建一個 Hook_Log_e
類,並在 lab.galaxy.yahfa.HookInfo
配置(不配置的話,hook 不生效),程式碼如下:
public class HookInfo {
public static String[] hookItemNames = {
"lab.galaxy.yahfa.demoPlugin.Hook_Log_e",
};
}
複製程式碼
注意:HookInfo
類的包名,如果需要改的話,要同時改 HookMain.doHookDefault()
方法裡面的包名。
public static void doHookDefault(ClassLoader patchClassLoader, ClassLoader originClassLoader) {
try {
Class<?> hookInfoClass = Class.forName("lab.galaxy.yahfa.HookInfo", true, patchClassLoader);
String[] hookItemNames = (String[])hookInfoClass.getField("hookItemNames").get(null);
for(String hookItemName : hookItemNames) {
doHookItemDefault(patchClassLoader, hookItemName, originClassLoader);
}
hookInfoClasses.add(hookInfoClass);
}
catch (Exception e) {
e.printStackTrace();
}
}
複製程式碼
3.驗證結果
- 這裡我寫了個 hook 微信啟動頁的 onCreate() 方法。
public class Hook_Wx_Launcher {
public static String className = "com.tencent.mm.ui.LauncherUI";
public static String methodName = "onCreate";
public static String methodSig = "(Landroid/os/Bundle;)V";
public static Activity LauncherUi;
public static void hook(Object thiz, Bundle b) {
Log.w("czc", "LauncherUI oncreate");
return "";
}
public static void backup(Object thiz, Bundle b) {
Log.w("YAHFA", "LauncherUI backup");
return;
}
}
複製程式碼
- 安裝打包好的外掛apk,外掛左上有個小圖示,以作區別,同時克隆 微信 到
VirtualHook
裡面
- hook 成功列印出 log
更多技術分享,請加微信公眾號——碼農茅草屋: