美文网首页AndroidDev进程通讯
Android Binder Hook的实现

Android Binder Hook的实现

作者: 郑海鹏 | 来源:发表于2016-12-26 23:17 被阅读720次

    1. 简述

    Binder Hook可以Hook掉当前App用到的系统Service服务。
    以LocationManager为例,在获取一个LocationManager时分为两步。第一,获取IBinder对象;第二:IBinder对象通过asInterface()转化为LocationMangerService对象。最后初始化LocationManager,application层用到的都是LocationManager。
    Hook的大致原理是:ServiceManager在获取某个Binder时,如果本地有缓存的Binder,就不再跨进程请求Binder了。我们可以在缓存中加入自己的Binder,使得ServiceManager查询本地缓存时得到一个自定义的CustomBinder对象,不再跨进程向系统请求。并且ILocationManager.Stub.asInterface(CustomBinder)方法返回我们自定义的Service对象。
    这里面有两个地方需要用到自定义的对象。由于我们只Hook其中一部分的功能,其他功能还需要保留,所以用动态代理的方式创建自定义的Binder和自定义的Service。

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

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

    2. Context 获取系统 Service 的流程

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

    Title: Context 获取系统Service的流程
    Context->SystemServiceRegistry: getSystemService(name)
    SystemServiceRegistry->ServiceFetcher: getSystemService(name)
    ServiceFetcher->ServiceManager: getService(contetx)
    ServiceManager->return: getService(name)
    

    Context 的 getSystemService() 方法调用了 SystemServiceRegistry 的 getSystemService() 方法。
    SystemServiceRegistry 中有一个常量 SYSTEM_SERVICE_FETCHERS,这是一个Map。保存了ServiceName和对应的ServiceFetcher。ServicFetcher是用于创建具体Service的类。ServiceFetcher 的关键方法是 createService() 方法。
    在 ServiceFetcher 的 createService() 方法中,调用了 ServiceManager.getService(name)方法。以 LocationManager 对应的 ServiceFetcher 为例,它的createService()方法源码如下:

    @Override
    public LocationManager createService(ContextImpl ctx) {
        IBinder b = ServiceManager.getService("location");
        return new LocationManager(ctx, ILocationManager.Stub.asInterface(b));
    }
    

    假如我们要修改 LocationManager 的 getLastKnownLocation() 方法(下文都是)。我们要做的就是让ServiceManager.getService("location")返回我们自定义的Binder。先看一下这个方法简化后的源码:

    public static IBinder getService(String name) {
        IBinder service = sCache.get(name);
        if (service != null) {
            return service;
        } else {
            return getIServiceManager().getService(name);
        }
    }
    

    sCache是一个Map,缓存了已经向系统请求过的Binder。如果需要让这个方法返回我们自己的binder,只需要事先往sCache中put一个自定义的Binder就行了。
    在put之前,需要先创建出一个自定义的Binder。这个Binder在被 ILocationManager.Stub.asInterface 处理后,可以返回一个自定义的 LocationManagerService。
    先看一下Binder的 asInterface() 的实现:

    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 的类型是 Test.Stub.Proxy
    2. 相同进程时,service 的类型是 Test.Stub
      X.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()方法。

    package com.zhp.binderhook;
    
    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() 方法。

    package com.zhp.binderhook;
    
    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了~

    package com.zhp.binderhook;
    
    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
                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. 测试

    package com.zhp.binderhook;
    
    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

    相关文章

      网友评论

      • jeepc:你好,此hook方法在小米手机上无效,请问一下你有没有思路

      本文标题:Android Binder Hook的实现

      本文链接:https://www.haomeiwen.com/subject/nndsvttx.html