Xposed之开发Hook插件
題記:
Xposed作為一個(gè)著名的Hook框架,早已經(jīng)在移動(dòng)安全行業(yè)家喻戶曉。今天寫這篇文章主要也是想下手玩玩這個(gè)框架,至于框架的安裝,雖然也會(huì)碰見很多問題,但是今天暫不附上教程,因?yàn)樽罱τ邢?#xff0c;等有時(shí)間我會(huì)附上安裝教程,以及遇見的問題解決方法!
知識(shí)前導(dǎo):
Hook技術(shù)
-
Hook英文翻譯為“鉤子”,而鉤子就是在事件傳送到終點(diǎn)前截獲并監(jiān)控事件的傳輸,像個(gè)鉤子鉤上事件一樣,并且能夠在鉤上事件時(shí),處理一些自己特定的事件;
-
Hook使它能夠?qū)⒆约旱拇a“融入”被勾住(Hook)的進(jìn)程中,成為目標(biāo)進(jìn)程的一部分;
-
在Andorid沙箱機(jī)制下,Hook是我們能通過一個(gè)程序改變其他程序某些行為得以實(shí)現(xiàn);
Hook分類
根據(jù)Android開發(fā)模式,Native模式(C/C++)和Java模式(Java)區(qū)分,在Android平臺(tái)上
Java層級(jí)的Hook;
Native層級(jí)的Hook;
根Hook對(duì)象與Hook后處理事件方式不同,Hook還分為:
消息Hook;
API Hook;
針對(duì)Hook的不同進(jìn)程上來說,還可以分為:
全局Hook;
單個(gè)進(jìn)程Hook;
Hook原理
Hook技術(shù)本質(zhì)是函數(shù)調(diào)用,由于處于Linux用戶狀態(tài),每個(gè)進(jìn)程有自己獨(dú)立的進(jìn)程控件,所以必須先注入所要Hook的進(jìn)程空間,修改其內(nèi)存中進(jìn)程代碼,替換過程表的符號(hào)地址,通過ptrace函數(shù)附加進(jìn)程,向遠(yuǎn)程進(jìn)程注入so庫(kù),從而達(dá)到監(jiān)控以及遠(yuǎn)程進(jìn)程關(guān)鍵函數(shù)掛鉤;
Hook工作流程
Android相關(guān)內(nèi)核函數(shù):
ptrace函數(shù):跟蹤一個(gè)目標(biāo)進(jìn)程,結(jié)束跟蹤一個(gè)目標(biāo)進(jìn)程,獲取內(nèi)存字節(jié),像內(nèi)存寫入地址;
dlopen函數(shù):以指定模式打開指定的動(dòng)態(tài)鏈接庫(kù)文件;
mmap函數(shù):分配一段臨時(shí)的內(nèi)存來完成代碼的存放;
向目標(biāo)進(jìn)程注入代碼總結(jié)后的步驟分為以下幾步:
1. 用ptrace函數(shù)attch上目標(biāo)進(jìn)程;
2. 發(fā)現(xiàn)裝載共享庫(kù)so函數(shù);
3. 裝載指定的.so;
4.讓目標(biāo)進(jìn)程的執(zhí)行流程跳轉(zhuǎn)到注入的代碼執(zhí)行;
5. 使用ptrace函數(shù)的detach釋放目標(biāo)集成;
Xposed原理分析
?Xposed框架的原理是修改系統(tǒng)文件,替換了/system/bin/app_process可執(zhí)行文件,在啟動(dòng)Zygote時(shí)加載額外的jar文件(/data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar),并執(zhí)行一些初始化操作(執(zhí)行XposedBridge的main方法)。然后我們就可以在這個(gè)Zygote上下文中進(jìn)行某些hook操作。
在Android中,zygote是整個(gè)系統(tǒng)創(chuàng)建新進(jìn)程的核心進(jìn)程。zygote進(jìn)程在內(nèi)部會(huì)先啟動(dòng)Dalvik虛擬機(jī),繼而加載一些必要的系統(tǒng)資源和系統(tǒng)類,最后進(jìn)入一種監(jiān)聽狀態(tài)。
在之后的運(yùn)作中,當(dāng)其他系統(tǒng)模塊(比如AMS)希望創(chuàng)建新進(jìn)程時(shí),只需向zygote進(jìn)程發(fā)出請(qǐng)求,zygote進(jìn)程監(jiān)聽到該請(qǐng)求后,會(huì)相應(yīng)地fork出新的進(jìn)程,于是這個(gè)新進(jìn)程在初生之時(shí),就先天具有了自己的Dalvik虛擬機(jī)以及系統(tǒng)資源。
zygote進(jìn)程是由init進(jìn)程啟動(dòng)起來,由init.rc 腳本中關(guān)于zygote的描述可知:zygote對(duì)應(yīng)的可執(zhí)行文件就是/system/bin/app_process,也就是說系統(tǒng)啟動(dòng)時(shí)會(huì)執(zhí)行到這個(gè)可執(zhí)行文件的main()函數(shù)里。
知識(shí)小記
Xposed 提供了幾個(gè)接口類供xposed模塊繼承,不同的接口類對(duì)應(yīng)不同的hook時(shí)機(jī) IXposedHookZygoteInit zygote 初始化前就執(zhí)行掛鉤,即loadModule執(zhí)行時(shí)就掛鉤了 IXposedHookLoadPackage apk包加載的時(shí)候執(zhí)行掛鉤,先將掛鉤函數(shù)保存起來,等加載apk函數(shù)執(zhí)行后觸發(fā)callback (這里的callback是xposed框架自己掛鉤的函數(shù)),再執(zhí)行模塊注冊(cè)的掛鉤函數(shù) IXposedHookInitPackageResources apk資源實(shí)例化時(shí)執(zhí)行掛鉤,同上。
正文
Xposed的技術(shù)實(shí)現(xiàn)是通過系統(tǒng)進(jìn)程Hook技術(shù),所以必須要有root權(quán)限才行。一般通過刷機(jī)來實(shí)現(xiàn),這里我找了個(gè)兼容模擬器的Xposed來操作。這里我通過三個(gè)簡(jiǎn)單的Hook插件開發(fā),來介紹下Xposed插件開發(fā)的基本方法。當(dāng)然我會(huì)附上完整源碼來供大家學(xué)習(xí)研究。接下來,讓我們開啟上帝模式!
Hook裝載的apk程序包名
新建一個(gè)入口類并繼承并實(shí)現(xiàn)IXposedHookLoadPackage接口
如下操作,我們新建了一個(gè)Hook的類,并實(shí)現(xiàn)IXposedHookLoadPackage接口中的handleLoadPackage方法,然后對(duì)當(dāng)前裝載的apk程序包名進(jìn)行打印。
package com.example.test; ? import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.callbacks.XC_LoadPackage; /** * Created by Ethan on 2018/11/18. */ public class Hook implements IXposedHookLoadPackage {@Overridepublic void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {// 打印裝載的apk程序包名XposedBridge.log("Launch app: " + loadPackageParam.packageName);XposedBridge.log("Hook已經(jīng)成功了");}}這是一個(gè)最簡(jiǎn)單的一個(gè)Xposed插件,實(shí)現(xiàn)的功能就是對(duì)當(dāng)前安卓虛擬機(jī)裝載的apk程序的報(bào)名進(jìn)行Hook,并且打印出來。
Hook效果:
按鈕劫持Hook
首先自己寫一個(gè)粗糙的apk,實(shí)現(xiàn)的功能就是點(diǎn)擊界面的按鈕,就會(huì)彈出消息你未被劫持的消息!具體完整代碼如下:
MainActivity:
package com.example.ceshi; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; ? public class MainActivity extends AppCompatActivity {private Button button; ?public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main); ?button = (Button) findViewById(R.id.button); ?button.setOnClickListener(new View.OnClickListener() {public void onClick(View v) {Toast.makeText(MainActivity.this, toastMessage(), Toast.LENGTH_SHORT).show();}});} ?public String toastMessage() {return "我未被劫持";} }activity_main.xml:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.example.ceshi.MainActivity"> ?<Buttonandroid:id="@+id/button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Button" /> ? </android.support.constraint.ConstraintLayout>實(shí)現(xiàn)功能如圖
接下來我們要對(duì)這個(gè)apk的按鈕的方法進(jìn)行Hook,并且修改方法。
package com.example.xposed; 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 HookToast implements IXposedHookLoadPackage { ? //Module繼承了IXposedHookLoadPackage接口,當(dāng)系統(tǒng)加載應(yīng)用包的時(shí)候回回調(diào) handleLoadPackage;public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {//過濾包名,定位要Hook的包名if (loadPackageParam.packageName.equals("com.example.ceshi")) { ?//定位要Hook的具體的類名Class clazz = loadPackageParam.classLoader.loadClass("com.example.ceshi.MainActivity");//Hook的方法為toastMessage,XposedHelpers的靜態(tài)方法 findAndHookMethod就是hook函數(shù)的的方法,其參數(shù)對(duì)應(yīng)為 類名+loadPackageParam.classLoader(照寫)+方法名+參數(shù)類型(根據(jù)所hook方法的參數(shù)的類型,即有多少個(gè)寫多少個(gè),加上.class)+XC_MethodHook回調(diào)接口;XposedHelpers.findAndHookMethod(clazz, "toastMessage", new XC_MethodHook() {protected void beforeHookedMethod(MethodHookParam param) throws Throwable {super.beforeHookedMethod(param);} ?protected void afterHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable {//param.setResult("你已被劫持")將返回的結(jié)果設(shè)置成了你已被劫持param.setResult("你已被劫持");}});}} }?
經(jīng)過以上操作,當(dāng)apk執(zhí)行時(shí)會(huì)調(diào)用我們的Hook類,然后執(zhí)行就會(huì)對(duì)apk對(duì)應(yīng)的進(jìn)程方法進(jìn)行修改,達(dá)到Hook的效果。
Hook后效果如圖:
?
登陸劫持
上面進(jìn)行的按鈕劫持的效果也許還不夠明顯或者說是有趣。大家可能跟我一樣喜歡登陸劫持密碼這樣的操作,首先我們還是自己寫一個(gè)簡(jiǎn)單的登陸程序。
MainActivity:
package com.example.xposedtest; ? import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; ? public class MainActivity extends AppCompatActivity {EditText Name; //定義Plain Test控件第一個(gè)輸入框的名字EditText Pass; //定義Plain Test控件第二個(gè)輸入框的名字 ?protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Name = (EditText) findViewById(R.id.TEXT_NAME); //通過findViewById找到輸入框控件對(duì)應(yīng)的id并給它起一個(gè)名字Pass = (EditText) findViewById(R.id.TEST_PASS);//通過findViewById找到輸入框控件對(duì)應(yīng)的id并給它起一個(gè)名字Button Login = (Button) findViewById(R.id.BTN_Login);//通過findViewById找到按鈕控件對(duì)應(yīng)的id并給它起一個(gè)名字Login.setOnClickListener(new View.OnClickListener() { //監(jiān)聽有沒有點(diǎn)擊按鈕控件 如果點(diǎn)擊了就會(huì)執(zhí)行onClick函數(shù)@Overridepublic void onClick(View view) {check(Name.getText().toString().trim(),Pass.getText().toString().trim()); //調(diào)用check函數(shù)}});}public void check(String name,String pass) //自定義函數(shù)check 這里用來檢查用戶名和密碼是否是cck和1234{if(name.equals("cck")&&pass.equals("1234")){Toast.makeText(MainActivity.this,"登錄成功", Toast.LENGTH_SHORT).show();//彈框}elseToast.makeText(MainActivity.this,"登錄失敗", Toast.LENGTH_SHORT).show();//彈框} }activity_main.xml:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"tools:context="com.example.xposedtest.MainActivity"> ?<TextViewandroid:text="用戶名:"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentTop="true"android:layout_alignParentLeft="true"android:layout_alignParentStart="true"android:layout_marginLeft="63dp"android:layout_marginStart="63dp"android:layout_marginTop="77dp"android:id="@+id/textView"/> ?<TextViewandroid:text="密碼:"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="77dp"android:id="@+id/textView2"android:layout_below="@+id/textView"android:layout_alignLeft="@+id/textView"android:layout_alignStart="@+id/textView"/> ?<EditTextandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:inputType="textPersonName"android:ems="10"android:layout_alignBottom="@+id/textView"android:layout_toRightOf="@+id/textView"android:layout_toEndOf="@+id/textView"android:id="@+id/TEXT_NAME"/> ?<EditTextandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:inputType="textPassword"android:ems="10"android:layout_alignBottom="@+id/textView2"android:layout_toRightOf="@+id/textView2"android:layout_toEndOf="@+id/textView2"android:id="@+id/TEST_PASS"/> ?<Buttonandroid:text="登錄"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@+id/TEST_PASS"android:layout_toRightOf="@+id/textView"android:layout_toEndOf="@+id/textView"android:layout_marginLeft="13dp"android:layout_marginStart="13dp"android:layout_marginTop="130dp"android:id="@+id/BTN_Login"/> ? </RelativeLayout>實(shí)現(xiàn)效果如圖:
只有當(dāng)我們輸入正確的用戶名cck和密碼1234時(shí)才會(huì)彈出登陸成功的消息。
這里的我們要Hook的效果為不管輸入什么,都會(huì)顯示登陸成功,實(shí)現(xiàn)的手段就是Hook對(duì)應(yīng)的方法,并對(duì)相應(yīng)的參數(shù)進(jìn)行修改,還是使用上面的回調(diào)方法來實(shí)現(xiàn)
package com.example.xposeddeluhook; ? import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.XC_MethodHook; ? ? import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.callbacks.XC_LoadPackage; ? import static de.robv.android.xposed.XposedHelpers.findAndHookMethod; public class Hookdenglu implements IXposedHookLoadPackage { ?/*** 包加載時(shí)候的回調(diào)*/public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { ?// 將包名不是 com.example.xposedtest 的應(yīng)用剔除掉,可以減少管理的類if (!lpparam.packageName.equals("com.example.xposedtest"))return;XposedBridge.log("Loaded app: " + lpparam.packageName); ?//第一個(gè)參數(shù)是className,表示被注入的方法所在的類//第二個(gè)參數(shù)是類加載器,照抄就行//第三個(gè)參數(shù)是被注入的方法名//第四五個(gè)參數(shù)是第三個(gè)參數(shù)的兩個(gè)形參的類型//最后一個(gè)參數(shù)是匿名內(nèi)部類findAndHookMethod("com.example.xposedtest.MainActivity", lpparam.classLoader, "check", String.class,String.class, new XC_MethodHook() { ?/*** 該方法在check方法調(diào)用之前被調(diào)用,我們輸出一些日志,并且捕獲參數(shù)的值。* 最后兩行的目的是改變參數(shù)的值。也就是說無論參數(shù)是什么值,都會(huì)被替換為name為cck,pass為1234* @param param* @throws Throwable*/protected void beforeHookedMethod(MethodHookParam param) throws Throwable {XposedBridge.log("開始劫持了~");XposedBridge.log("參數(shù)1 = " + param.args[0]);XposedBridge.log("參數(shù)2 = " + param.args[1]);param.args[0] = "cck";param.args[1] = "1234";}/*** 該方法在check方法調(diào)用之后被調(diào)用* @param param* @throws Throwable*/ ?protected void afterHookedMethod(MethodHookParam param) throws Throwable {XposedBridge.log("劫持結(jié)束了~");XposedBridge.log("參數(shù)1 = " + param.args[0]);XposedBridge.log("參數(shù)2 = " + param.args[1]); ?}});} ? }上述代碼我們通過對(duì)方法的參數(shù)進(jìn)行了重賦值,來達(dá)到了我們想要的結(jié)果!
實(shí)現(xiàn)效果如下:
Xposed日志如下:
Think one Think
? ? ? Xposed可以在不修改APK源碼的情況下,通過自己編寫的模塊來影響程序運(yùn)行的框架服務(wù),采用了插件機(jī)制,通過替換/system/bin/app_process程序控制zygote進(jìn)程,使得app_process在啟動(dòng)過程中會(huì)加載XposedBridge.jar這個(gè)jar包,從而完成對(duì)Zygote進(jìn)程及其創(chuàng)建的Dalvik虛擬機(jī)的劫持,從而可以使我們開啟上帝模式,從原理上講,只要你對(duì)要操作的方法,參數(shù)的個(gè)數(shù),類型了如指掌,那你就可以實(shí)現(xiàn)任意應(yīng)用的任意方法的Hook。這也是Xposed插件開發(fā)的初衷!
總結(jié)
以上是生活随笔為你收集整理的Xposed之开发Hook插件的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Unity 图片分割将spirte保存在
- 下一篇: 递归、加法原理,如何分解问题(独立且完备