Android Binder Hook的實現(xiàn)

1. 簡述

Binder Hook 可以 Hook 掉當(dāng)前進程用到的系統(tǒng) Service 服務(wù)亦鳞。
以 LocationManager 為例,在獲取一個 LocationManager 時分為兩步:
(1) 獲取 IBinder 對象迅矛;
(2) 通過 IBinder 的 asInterface() 方法轉(zhuǎn)化為 LocationMangerService 對象妨猩,接著初始化 LocationManager

application 層用到的都是 LocationManager 對象秽褒。

原理:

  1. ServiceManager 在首次獲取某個 Service 的 Binder 后册赛,會把 Binder 對象緩存在 ServiceManager#sCahce 映射表中。后續(xù)再獲取時震嫉,會先檢查 sCache 中是否已經(jīng)存在緩存對象,如果有則返回這個緩存對象牡属。所以我們可以通過反射的方式票堵,往 sCahce 中 put 一個自定義的 Binder,這樣獲取到的 Binder 對象就會是我們自定義的 Binder 了逮栅。
  2. 上層代碼在調(diào)用 LocationManager 時悴势,用到的都是 Service 對象,這個對象是在 ILocationManager.Stub.asInterface(CustomBinder) 方法返回的措伐。 asInterface(customeBinder) 最終會調(diào)用到 CustomBinder # queryLocalInterface() 方法特纤,我們需要重寫這個方法,返回自定義的 Service 對象侥加。

整個過程需要利用反射設(shè)置一個自定義的 Binder 對象和一個自定義的 Service 對象捧存。由于我們只 Hook 其中一部分的功能,其他功能還需要保留担败,所以要用動態(tài)代理的方式創(chuàng)建自定義對象昔穴。

在理解后面的內(nèi)容前你需要了解這些知識點:

  1. 一點點 Binder 的知識,知道 IBinder 轉(zhuǎn) IInterface 的大致流程提前;
  2. Java 的動態(tài)代理吗货。

2. Context 獲取系統(tǒng) Service 的流程

Activity 等類在獲取系統(tǒng) Service 時,都是調(diào)用 getSystemService(serviceName) 方法獲取的狈网。

屏幕快照 2019-02-13 19.39.00.png

這是一段不太重要的過程:
Context # getSystemService() 方法調(diào)用了 SystemServiceRegistry # getSystemService() 方法宙搬。
SystemServiceRegistry 中有一個常量 SYSTEM\_SERVICE\_FETCHERS笨腥,這是一個 Map。保存了 ServiceName 和對應(yīng)的 ServiceFetcher勇垛。ServiceFetcher 是用于創(chuàng)建具體 Service 對象的類脖母。ServiceFetcher 的關(guān)鍵方法是 createService() 方法。
ServiceFetcher # createService() 方法中窥摄,調(diào)用了 ServiceManager.getService(name) 方法镶奉。

Context # getSystemService() 方法最終會調(diào)用到 ServiceManager # getService() 方法中。以 LocationManager 對應(yīng)的 ServiceFetcher 為例崭放,它的 createService() 方法源碼如下:

// Android 8.0 android.app.SystemServiceRegistry.java

@Override
public LocationManager createService(ContextImpl ctx) throws ServiceNotFoundException {
    IBinder b = ServiceManager.getServiceOrThrow(Context.LOCATION_SERVICE);
    return new LocationManager(ctx, ILocationManager.Stub.asInterface(b));
}

假如我們要 Hook 掉 LocationManager # getLastKnownLocation() 方法(下文都是)哨苛。我們要做的就是讓
ServiceManager.getService(Context.LOCATION_SERVICE) 返回我們自定義的 Binder 對象。
先看一下這個方法的源碼:

// Android 8.0 android.os.ServiceManager.java

public static IBinder getService(String name) {
    try {
        IBinder service = sCache.get(name);
        if (service != null) {
            return service;
        } else {
            return Binder.allowBlocking(getIServiceManager().getService(name));
        }
    } catch (RemoteException e) {
        Log.e(TAG, "error in getService", e);
    }
    return null;
}

sCache 是一個 Map币砂,緩存了已經(jīng)向系統(tǒng)請求過的 Binder建峭。如果需要讓這個方法返回我們自己的 binder 對象,只需要事先往 sCache 中 put 一個自定義的 Binder 對象就行了决摧。
在 put 之前亿蒸,需要先創(chuàng)建出一個自定義的 Binder。這個 Binder 在被 ILocationManager.Stub.asInterface 處理后掌桩,可以返回一個自定義的 LocationManagerService 對象边锁。
先看一下 Binder 的 asInterface() 的實現(xiàn):

// Android 8.0 android.location.ILocationManager.java

public static android.location.ILocationManager asInterface(android.os.IBinder obj) {
    if ((obj == null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof android.location.ILocationManager))) {
        return ((android.location.ILocationManager) iin);
    }
    return new android.location.ILocationManager.Stub.Proxy(obj);
}

如果把 queryLocalInterface()方法返回一個自定義的Service,使得走 if 語句內(nèi)部波岛,不走 else茅坛,那就算是Hook 成功了。

誤區(qū): asInterface(binder) 就是把 binder 做了一次類型轉(zhuǎn)換
實際上XXX service = XXX.Stub.asInterface(binder)的返回值根據(jù) binder 的來源有兩種情況:

  1. 跨進程時则拷,service 的類型是 XXX.Stub.Proxy
  2. 相同進程時贡蓖,service 的類型是 XXX.Stub
    XXX.Stub.asInterface(binder);得到的返回值并不一定是binder自己,并且調(diào)用系統(tǒng)服務(wù)時肯定不是binder自己煌茬。

3 創(chuàng)建自定義的 Service 和 Binder 對象

3.1 自定義的 Service 對象

假設(shè)我們想讓系統(tǒng)的 LocationManager 返回的位置信息全是在天安門(116.23, 39.54)斥铺。那我們需要使得 LocatitionManagerService 的 getLastLocation() 方法 返回的全是 (116.23, 39.54)。
由于我們不能直接拿到系統(tǒng)的這個Service對象坛善,可以先用反射的方式拿到系統(tǒng)的LocationManagerService晾蜘。然后攔截 getLastLocation() 方法。

import android.location.Location;
import android.location.LocationManager;
import android.os.IBinder;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 動態(tài)代理時用到的 Handler
 * @author ZHP
 * @since 16/12/25 17:36
 */

public class ServiceHookHandler implements InvocationHandler {

    private Object mOriginService;

    /**
     * @param binder 系統(tǒng)原始的Binder對象
     */
    @SuppressWarnings("unchecked")
    public ServiceHookHandler(IBinder binder) {
        try {
            // 由于可以拿到 Binder 對象,但無法拿到 Service 的對象, 所以我們要手動獲取
            // 即: this.mOriginService = ILocationManager.Stub.asInterface(binder);
            Class ILocationManager$Stub = Class.forName("android.location.ILocationManager$Stub");
            Method asInterface = ILocationManager$Stub.getDeclaredMethod("asInterface", IBinder.class);
            this.mOriginService = asInterface.invoke(null, binder);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }



    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        switch(method.getName()) {
            case "getLastLocation":
                // 一直返回天安門的坐標(biāo)
                Location location = new Location(LocationManager.GPS_PROVIDER);
                location.setLongitude(116.23);
                location.setLatitude(39.54);
                return location;

            default:
                return method.invoke(this.mOriginService, args);
        }
    }
}

3.2 自定義的 Binder 對象

原生的Binder對象在調(diào)用 queryLocalInterface() 方法時會返回原生的Service對象浑吟。我們希望返回3.1中的自定義Service笙纤。所以這里攔截 queryLocalInterface() 方法。

import android.os.IBinder;
import android.os.IInterface;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 動態(tài)代理時用到的Handler,用于創(chuàng)建自定義Binder
 * @author ZHP
 * @since 16/12/25 17:36
 */

public class BinderHookHandler implements InvocationHandler {

    private IBinder mOriginBinder;

    private Class ILocationManager;

    @SuppressWarnings("unchecked")
    public BinderHookHandler(IBinder binder) {
        this.mOriginBinder = binder;
        try {
            this.ILocationManager = Class.forName("android.location.ILocationManager");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        switch (method.getName()) {
            // 使得返回自定義的Service
            case "queryLocalInterface":
                ClassLoader classLoader = mOriginBinder.getClass().getClassLoader();
                Class[] interfaces = new Class[] {IInterface.class, IBinder.class, ILocationManager};
                ServiceHookHandler handler = new ServiceHookHandler(this.mOriginBinder);
                return Proxy.newProxyInstance(classLoader, interfaces, handler);

            default:
                return method.invoke(mOriginBinder, args);

        }
    }
}

4. 將自定義的 Binder 注入到 ServiceManager 中

有了自定義的 Binder 后组力,將它注入到 ServiceManger 的 sCache 變量中就完成 Hook 了~

import android.content.Context;
import android.os.IBinder;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;

/**
 * @author ZHP
 * @since 16/12/25 17:56
 */

public class HookManager {

    @SuppressWarnings("unchecked")
    public static boolean hookLocationManager() {
        try {
            // 1. 獲取系統(tǒng)自己的Binder
            Class ServiceManager = Class.forName("android.os.ServiceManager");
            Method getService = ServiceManager.getDeclaredMethod("getService", String.class);
            IBinder binder = (IBinder) getService.invoke(null, Context.LOCATION_SERVICE);

            // 2. 創(chuàng)建我們自己的Binder省容,動態(tài)代理了 queryLocalInterface 方法。
            ClassLoader classLoader = binder.getClass().getClassLoader();
            Class[] interfaces = {IBinder.class};
            BinderHookHandler handler = new BinderHookHandler(binder);
            IBinder customBinder = (IBinder) Proxy.newProxyInstance(classLoader, interfaces, handler);

            // 3. 獲取 ServiceManager 中的 sCache
            Field sCache = ServiceManager.getDeclaredField("sCache");
            sCache.setAccessible(true);
            Map<String, IBinder> cache = (Map<String, IBinder>) sCache.get(null);

            // 4. 將自定義的 Binder 對象替換掉舊的系統(tǒng) Binder
            cache.put(Context.LOCATION_SERVICE, customBinder);
            sCache.setAccessible(false);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

5. 測試

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Hook
        HookManager.hookLocationManager();
    }

    /**
     * 請求定位信息
     */
    private void requestLocation() {
        // 定位權(quán)限檢測
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            Log.i("鄭海鵬", "沒有定位權(quán)限");
            Toast.makeText(this, "沒有定位權(quán)限", Toast.LENGTH_SHORT).show();
            return;
        }

        // 獲取位置并顯示
        LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
        Location location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
        String message = "(" + location.getLongitude() + ", " + location.getLatitude() + ")";
        Toast.makeText(this, message, Toast.LENGTH_LONG).show();
        Log.i("鄭海鵬", message);
    }

    public void onClick(View view) {
        this.requestLocation();
    }
}

當(dāng)onClick被調(diào)用的時候燎字,Toast和Log都會顯示天安門的坐標(biāo)(116.23, 39.54)腥椒。證明Hook成功阿宅!

你甚至可以用Binder Hook的方式Hook掉 ActivityManager

團隊博客同名文章:http://www.reibang.com/p/fcb832a2b411
轉(zhuǎn)載必須注明出處:http://www.reibang.com/p/5c2c3fc4286b`

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末笼蛛,一起剝皮案震驚了整個濱河市洒放,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌滨砍,老刑警劉巖往湿,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異惋戏,居然都是意外死亡领追,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門响逢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绒窑,“玉大人,你說我怎么就攤上這事舔亭⌒┡颍” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵钦铺,是天一觀的道長订雾。 經(jīng)常有香客問我,道長矛洞,這世上最難降的妖魔是什么葬燎? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮缚甩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘窑邦。我一直安慰自己擅威,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布冈钦。 她就那樣靜靜地躺著郊丛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瞧筛。 梳的紋絲不亂的頭發(fā)上厉熟,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機與錄音较幌,去河邊找鬼揍瑟。 笑死,一個胖子當(dāng)著我的面吹牛乍炉,可吹牛的內(nèi)容都是我干的绢片。 我是一名探鬼主播滤馍,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼底循!你這毒婦竟也來了巢株?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤熙涤,失蹤者是張志新(化名)和其女友劉穎阁苞,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體祠挫,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡那槽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了茸歧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倦炒。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖软瞎,靈堂內(nèi)的尸體忽然破棺而出逢唤,到底是詐尸還是另有隱情,我是刑警寧澤涤浇,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布鳖藕,位于F島的核電站,受9級特大地震影響只锭,放射性物質(zhì)發(fā)生泄漏著恩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一蜻展、第九天 我趴在偏房一處隱蔽的房頂上張望喉誊。 院中可真熱鬧,春花似錦纵顾、人聲如沸伍茄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽敷矫。三九已至,卻和暖如春汉额,著一層夾襖步出監(jiān)牢的瞬間曹仗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工蠕搜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留怎茫,地道東北人。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓妓灌,卻偏偏與公主長得像遭居,于是被迫代替她去往敵國和親啼器。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容