Xposed之開發Hook插件

題記:

Xposed作爲一個著名的Hook框架,早已經在移動安全行業家喻戶曉。今天寫這篇文章主要也是想下手玩玩這個框架,至於框架的安裝,雖然也會碰見很多問題,但是今天暫不附上教程,因爲最近精力有限,等有時間我會附上安裝教程,以及遇見的問題解決方法!

知識前導:

Hook技術

  • Hook英文翻譯爲“鉤子”,而鉤子就是在事件傳送到終點前截獲並監控事件的傳輸,像個鉤子鉤上事件一樣,並且能夠在鉤上事件時,處理一些自己特定的事件;

  • Hook使它能夠將自己的代碼“融入”被勾住(Hook)的進程中,成爲目標進程的一部分;

  • 在Andorid沙箱機制下,Hook是我們能通過一個程序改變其他程序某些行爲得以實現;

Hook分類

  1. 根據Android開發模式,Native模式(C/C++)和Java模式(Java)區分,在Android平臺上

    Java層級的Hook;

    Native層級的Hook;

  2. 根Hook對象與Hook後處理事件方式不同,Hook還分爲:

    消息Hook;

    API Hook;

  3. 針對Hook的不同進程上來說,還可以分爲:

    全局Hook;

    單個進程Hook;

Hook原理

Hook技術本質是函數調用,由於處於Linux用戶狀態,每個進程有自己獨立的進程控件,所以必須先注入所要Hook的進程空間,修改其內存中進程代碼,替換過程表的符號地址,通過ptrace函數附加進程,向遠程進程注入so庫,從而達到監控以及遠程進程關鍵函數掛鉤;

Hook工作流程

  1. Android相關內核函數:

    ptrace函數:跟蹤一個目標進程,結束跟蹤一個目標進程,獲取內存字節,像內存寫入地址;

    dlopen函數:以指定模式打開指定的動態鏈接庫文件;

    mmap函數:分配一段臨時的內存來完成代碼的存放;

  2. 向目標進程注入代碼總結後的步驟分爲以下幾步:

    1. 用ptrace函數attch上目標進程;

    2. 發現裝載共享庫so函數;

    3. 裝載指定的.so;

    4.讓目標進程的執行流程跳轉到注入的代碼執行;

    5. 使用ptrace函數的detach釋放目標集成;

Xposed原理分析

 Xposed框架的原理是修改系統文件,替換了/system/bin/app_process可執行文件,在啓動Zygote時加載額外的jar文件(/data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar),並執行一些初始化操作(執行XposedBridge的main方法)。然後我們就可以在這個Zygote上下文中進行某些hook操作。

在Android中,zygote是整個系統創建新進程的核心進程。zygote進程在內部會先啓動Dalvik虛擬機,繼而加載一些必要的系統資源和系統類,最後進入一種監聽狀態。

在之後的運作中,當其他系統模塊(比如AMS)希望創建新進程時,只需向zygote進程發出請求,zygote進程監聽到該請求後,會相應地fork出新的進程,於是這個新進程在初生之時,就先天具有了自己的Dalvik虛擬機以及系統資源。

zygote進程是由init進程啓動起來,由init.rc 腳本中關於zygote的描述可知:zygote對應的可執行文件就是/system/bin/app_process,也就是說系統啓動時會執行到這個可執行文件的main()函數裏。

知識小記

Xposed 提供了幾個接口類供xposed模塊繼承,不同的接口類對應不同的hook時機 IXposedHookZygoteInit zygote 初始化前就執行掛鉤,即loadModule執行時就掛鉤了 IXposedHookLoadPackage apk包加載的時候執行掛鉤,先將掛鉤函數保存起來,等加載apk函數執行後觸發callback (這裏的callback是xposed框架自己掛鉤的函數),再執行模塊註冊的掛鉤函數 IXposedHookInitPackageResources apk資源實例化時執行掛鉤,同上。

正文

Xposed的技術實現是通過系統進程Hook技術,所以必須要有root權限才行。一般通過刷機來實現,這裏我找了個兼容模擬器的Xposed來操作。這裏我通過三個簡單的Hook插件開發,來介紹下Xposed插件開發的基本方法。當然我會附上完整源碼來供大家學習研究。接下來,讓我們開啓上帝模式!

Hook裝載的apk程序包名

新建一個入口類並繼承並實現IXposedHookLoadPackage接口

如下操作,我們新建了一個Hook的類,並實現IXposedHookLoadPackage接口中的handleLoadPackage方法,然後對當前裝載的apk程序包名進行打印。

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
{
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
        // 打印裝載的apk程序包名
        XposedBridge.log("Launch app: " + loadPackageParam.packageName);
        XposedBridge.log("Hook已經成功了");
    }}

這是一個最簡單的一個Xposed插件,實現的功能就是對當前安卓虛擬機裝載的apk程序的報名進行Hook,並且打印出來。

Hook效果:

按鈕劫持Hook

首先自己寫一個粗糙的apk,實現的功能就是點擊界面的按鈕,就會彈出消息你未被劫持的消息!具體完整代碼如下:

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.ConstraintLayout
    xmlns: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">
​
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button" />
​
</android.support.constraint.ConstraintLayout>

實現功能如圖

接下來我們要對這個apk的按鈕的方法進行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接口,當系統加載應用包的時候回回調 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的靜態方法 findAndHookMethod就是hook函數的的方法,其參數對應爲   類名+loadPackageParam.classLoader(照寫)+方法名+參數類型(根據所hook方法的參數的類型,即有多少個寫多少個,加上.class)+XC_MethodHook回調接口;
            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("你已被劫持")將返回的結果設置成了你已被劫持
                    param.setResult("你已被劫持");
                }
            });
        }
    }
}

 

經過以上操作,當apk執行時會調用我們的Hook類,然後執行就會對apk對應的進程方法進行修改,達到Hook的效果。

Hook後效果如圖:

 

登陸劫持

上面進行的按鈕劫持的效果也許還不夠明顯或者說是有趣。大家可能跟我一樣喜歡登陸劫持密碼這樣的操作,首先我們還是自己寫一個簡單的登陸程序。

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控件第一個輸入框的名字
    EditText Pass;   //定義Plain Test控件第二個輸入框的名字
​
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Name = (EditText) findViewById(R.id.TEXT_NAME); //通過findViewById找到輸入框控件對應的id並給它起一個名字
        Pass = (EditText) findViewById(R.id.TEST_PASS);//通過findViewById找到輸入框控件對應的id並給它起一個名字
        Button Login = (Button) findViewById(R.id.BTN_Login);//通過findViewById找到按鈕控件對應的id並給它起一個名字
        Login.setOnClickListener(new View.OnClickListener() {  //監聽有沒有點擊按鈕控件 如果點擊了就會執行onClick函數
            @Override
            public void onClick(View view) {
                check(Name.getText().toString().trim(),Pass.getText().toString().trim()); //調用check函數
            }
        });
    }
    public void check(String name,String pass)   //自定義函數check 這裏用來檢查用戶名和密碼是否是cck和1234
    {
        if(name.equals("cck")&&pass.equals("1234"))
        {
            Toast.makeText(MainActivity.this,"登錄成功", Toast.LENGTH_SHORT).show();//彈框
        }
        else
            Toast.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">
​
    <TextView
        android: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"/>
​
    <TextView
        android: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"/>
​
    <EditText
        android: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"/>
​
    <EditText
        android: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"/>
​
    <Button
        android: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>

實現效果如圖:

只有當我們輸入正確的用戶名cck和密碼1234時纔會彈出登陸成功的消息。

這裏的我們要Hook的效果爲不管輸入什麼,都會顯示登陸成功,實現的手段就是Hook對應的方法,並對相應的參數進行修改,還是使用上面的回調方法來實現

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 {
​
    /**
     * 包加載時候的回調
     */
    public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
​
        // 將包名不是 com.example.xposedtest 的應用剔除掉,可以減少管理的類
        if (!lpparam.packageName.equals("com.example.xposedtest"))
            return;
        XposedBridge.log("Loaded app: " + lpparam.packageName);
​
        //第一個參數是className,表示被注入的方法所在的類
        //第二個參數是類加載器,照抄就行
        //第三個參數是被注入的方法名
        //第四五個參數是第三個參數的兩個形參的類型
        //最後一個參數是匿名內部類
        findAndHookMethod("com.example.xposedtest.MainActivity", lpparam.classLoader, "check", String.class,
                String.class, new XC_MethodHook() {
​
            /**
             * 該方法在check方法調用之前被調用,我們輸出一些日誌,並且捕獲參數的值。
             * 最後兩行的目的是改變參數的值。也就是說無論參數是什麼值,都會被替換爲name爲cck,pass爲1234
             * @param param
             * @throws Throwable
             */
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        XposedBridge.log("開始劫持了~");
                        XposedBridge.log("參數1 = " + param.args[0]);
                        XposedBridge.log("參數2 = " + param.args[1]);
                        param.args[0] = "cck";
                        param.args[1] = "1234";
                    }
             /**
             * 該方法在check方法調用之後被調用
             * @param param
             * @throws Throwable
             */
​
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        XposedBridge.log("劫持結束了~");
                        XposedBridge.log("參數1 = " + param.args[0]);
                        XposedBridge.log("參數2 = " + param.args[1]);
​
                    }
                });
    }
​
}

上述代碼我們通過對方法的參數進行了重賦值,來達到了我們想要的結果!

實現效果如下:

Xposed日誌如下:

Think one Think

      Xposed可以在不修改APK源碼的情況下,通過自己編寫的模塊來影響程序運行的框架服務,採用了插件機制,通過替換/system/bin/app_process程序控制zygote進程,使得app_process在啓動過程中會加載XposedBridge.jar這個jar包,從而完成對Zygote進程及其創建的Dalvik虛擬機的劫持,從而可以使我們開啓上帝模式,從原理上講,只要你對要操作的方法,參數的個數,類型瞭如指掌,那你就可以實現任意應用的任意方法的Hook。這也是Xposed插件開發的初衷!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章