Android Binder Hook的实现

1. 简述

Binder Hook 可以 Hook 掉当前进程用到的系统 Service 服务。
以 LocationManager 为例,在获取一个 LocationManager 时分为两步:
(1) 获取 IBinder 对象;
(2) 通过 IBinder 的 asInterface() 方法转化为 LocationMangerService 对象,接着初始化 LocationManager

application 层用到的都是 LocationManager 对象。

原理:

  1. ServiceManager 在首次获取某个 Service 的 Binder 后,会把 Binder 对象缓存在 ServiceManager#sCahce 映射表中。后续再获取时,会先检查 sCache 中是否已经存在缓存对象,如果有则返回这个缓存对象。所以我们可以通过反射的方式,往 sCahce 中 put 一个自定义的 Binder,这样获取到的 Binder 对象就会是我们自定义的 Binder 了。
  2. 上层代码在调用 LocationManager 时,用到的都是 Service 对象,这个对象是在 ILocationManager.Stub.asInterface(CustomBinder) 方法返回的。 asInterface(customeBinder) 最终会调用到 CustomBinder # queryLocalInterface() 方法,我们需要重写这个方法,返回自定义的 Service 对象。

整个过程需要利用反射设置一个自定义的 Binder 对象和一个自定义的 Service 对象。由于我们只 Hook 其中一部分的功能,其他功能还需要保留,所以要用动态代理的方式创建自定义对象。

在理解后面的内容前你需要了解这些知识点:

  1. 一点点 Binder 的知识,知道 IBinder 转 IInterface 的大致流程;
  2. Java 的动态代理。

2. Context 获取系统 Service 的流程

Activity 等类在获取系统 Service 时,都是调用 getSystemService(serviceName) 方法获取的。

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

这是一段不太重要的过程:
Context # getSystemService() 方法调用了 SystemServiceRegistry # getSystemService() 方法。
SystemServiceRegistry 中有一个常量 SYSTEM\_SERVICE\_FETCHERS,这是一个 Map。保存了 ServiceName 和对应的 ServiceFetcher。ServiceFetcher 是用于创建具体 Service 对象的类。ServiceFetcher 的关键方法是 createService() 方法。
ServiceFetcher # createService() 方法中,调用了 ServiceManager.getService(name) 方法。

Context # getSystemService() 方法最终会调用到 ServiceManager # getService() 方法中。以 LocationManager 对应的 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,缓存了已经向系统请求过的 Binder。如果需要让这个方法返回我们自己的 binder 对象,只需要事先往 sCache 中 put 一个自定义的 Binder 对象就行了。
在 put 之前,需要先创建出一个自定义的 Binder。这个 Binder 在被 ILocationManager.Stub.asInterface 处理后,可以返回一个自定义的 LocationManagerService 对象。
先看一下 Binder 的 asInterface() 的实现:

// 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 语句内部,不走 else,那就算是Hook 成功了。

误区: asInterface(binder) 就是把 binder 做了一次类型转换
实际上XXX service = XXX.Stub.asInterface(binder)的返回值根据 binder 的来源有两种情况:

  1. 跨进程时,service 的类型是 XXX.Stub.Proxy
  2. 相同进程时,service 的类型是 XXX.Stub
    XXX.Stub.asInterface(binder);得到的返回值并不一定是binder自己,并且调用系统服务时肯定不是binder自己。

3 创建自定义的 Service 和 Binder 对象

3.1 自定义的 Service 对象

假设我们想让系统的 LocationManager 返回的位置信息全是在天安门(116.23, 39.54)。那我们需要使得 LocatitionManagerService 的 getLastLocation() 方法 返回的全是 (116.23, 39.54)。
由于我们不能直接拿到系统的这个Service对象,可以先用反射的方式拿到系统的LocationManagerService。然后拦截 getLastLocation() 方法。

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

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

/**
 * 动态代理时用到的 Handler
 * @author ZHP
 * @since 16/12/25 17:36
 */

public class ServiceHookHandler implements InvocationHandler {

    private Object mOriginService;

    /**
     * @param binder 系统原始的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":
                // 一直返回天安门的坐标
                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对象在调用 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;

/**
 * 动态代理时用到的Handler,用于创建自定义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. 获取系统自己的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. 创建我们自己的Binder,动态代理了 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 对象替换掉旧的系统 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() {
        // 定位权限检测
        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("郑海鹏", "没有定位权限");
            Toast.makeText(this, "没有定位权限", 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();
    }
}

当onClick被调用的时候,Toast和Log都会显示天安门的坐标(116.23, 39.54)。证明Hook成功!

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

团队博客同名文章:http://www.jianshu.com/p/fcb832a2b411
转载必须注明出处:http://www.jianshu.com/p/5c2c3fc4286b`

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容