Binder Hook技术实战(AudioService)

来源:互联网 发布:异人启示录 知乎 编辑:程序博客网 时间:2024/05/14 02:02

         近期在工作中遇到了,需要按音量键跳转上一页或者下一页,但是有的Fragment页面包含视频播放功能,由于应用运行于手机锁屏状态,所以需要隐藏系统默认音量弹框的问题。最终虽然找到解决了音量框异常弹出的问题(由于VideoView设置viewController异常占用系统焦点,Activity无法正常接收到KeyEven按键事件,最终由系统AudioService响应引起),但是hook实现也记录下加深印象,仅供后来者参考。


源码如下:

Hook帮助类

/**
 * Created by liuyutang on 16-12-28.
 */

public class HookHelper {
    private static final String TAG = "HookHelper";

    public static void initBinderHook(BinderHookHandler.IAdjustVolumeListener listener) {
        try {
            final String AUDIO_SERVICE = "audio";
            final String MEDIA_SESSION_SERVICE = "media_session";
            Class<?> serviceManager = Class.forName("android.os.ServiceManager");
            Method getService = serviceManager.getDeclaredMethod("getService", String.class);
            // ServiceManager里面管理的原始的AudioManager Binder对象
            // 一般来说这是一个Binder代理对象
            IBinder rawBinder = null;
            if (android.os.Build.VERSION.SDK_INT >= 21) {
                rawBinder = (IBinder) getService.invoke(null, MEDIA_SESSION_SERVICE);
            } else if (android.os.Build.VERSION.SDK_INT >= 19) {
                rawBinder = (IBinder) getService.invoke(null, AUDIO_SERVICE);
            }

            // Hook 掉这个Binder代理对象的 queryLocalInterface 方法
            // 然后在 queryLocalInterface 返回一个IInterface对象, hook掉我们感兴趣的方法即可.
            IBinder hookedBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(),
                    new Class<?>[]{IBinder.class},
                    new BinderProxyHookHandler(rawBinder,listener));

            // 把这个hook过的Binder代理对象放进ServiceManager的cache里面
            // 以后查询的时候 会优先查询缓存里面的Binder, 这样就会使用被我们修改过的Binder了
            Field cacheField = serviceManager.getDeclaredField("sCache");
            cacheField.setAccessible(true);
            Map<String, IBinder> cache = (Map) cacheField.get(null);
            if (android.os.Build.VERSION.SDK_INT >= 21) {
                cache.put(MEDIA_SESSION_SERVICE, hookedBinder);
            } else if (android.os.Build.VERSION.SDK_INT >= 19) {
                cache.put(AUDIO_SERVICE, hookedBinder);
            }
        } catch (Exception e) {
            e.printStackTrace();
            Log.d("LYT", "inithook=========error,msg=" + e.getStackTrace().toString());
        }
    }
}



Hook以后处理类:

/**
 * Created by user on 16-12-27.
 */

public class BinderHookHandler implements InvocationHandler {
    private static final String TAG = "BinderHookHandler";

    // 原始的Service对象 (IInterface)
    Object base;
    IAdjustVolumeListener avlistener;

    public BinderHookHandler(IBinder base, Class<?> stubClass, IAdjustVolumeListener listener) {
        try {
            Method asInterfaceMethod = stubClass.getDeclaredMethod("asInterface", IBinder.class);
            this.base = asInterfaceMethod.invoke(null, base);
            avlistener = listener;
        } catch (Exception e) {
            throw new RuntimeException("hooked failed!");
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Log.d(TAG, "hook method=" + method.getName());
        if ("dispatchAdjustVolume".equals(method.getName())) {
            if (null != avlistener) {
                if (avlistener.isHookAble()) {
                    if (android.os.Build.VERSION.SDK_INT >= 21) {
                        if (args.length == 3) {
                            if (args[2] instanceof Integer) {
                                Integer tmp = (Integer) args[2];
                                args[2] = tmp | ~AudioManager.FLAG_SHOW_UI;
                                return base;
                            }
                        }
                    } else if (android.os.Build.VERSION.SDK_INT >= 19) {
                        if (args.length == 3) {
                            if (args[1] instanceof Integer) {
                                Integer tmp = (Integer) args[1];
                                args[1] = tmp | ~AudioManager.FLAG_SHOW_UI;
                            }
                        }
                    }
                }
            } else {
                throw new Exception("avlistener is null");
            }

        }

        return method.invoke(base, args);
    }

    /**
     * 监听是否需要hook
     */
    public interface IAdjustVolumeListener {
        boolean isHookAble();
    }
}



实现Service Hook的代理类源码:

/**
 * Created by user on 16-12-27.
 */

public class BinderProxyHookHandler implements InvocationHandler {
    private static final String TAG = "BinderProxyHookHandler";
    // 绝大部分情况下,这是一个BinderProxy对象
    // 只有当Service和我们在同一个进程的时候才是Binder本地对象
    // 这个基本不可能
    IBinder base;

    Class<?> stub;

    Class<?> iinterface;
    BinderHookHandler.IAdjustVolumeListener avlistener;
    public BinderProxyHookHandler(IBinder base,BinderHookHandler.IAdjustVolumeListener listener) {
        this.base = base;
        try {
            if(android.os.Build.VERSION.SDK_INT >=21 ) {
                this.stub = Class.forName("android.media.session.ISessionManager$Stub");
                this.iinterface = Class.forName("android.media.session.ISessionManager");
            }else if(android.os.Build.VERSION.SDK_INT >= 19){
                this.stub = Class.forName("android.media.session.IAudioService$Stub");
                this.iinterface = Class.forName("android.media.session.IAudioService");
            }
            avlistener = listener;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("queryLocalInterface".equals(method.getName())) {
            Log.d(TAG, "hook queryLocalInterface");
            // 这里直接返回真正被Hook掉的Service接口
            // 这里的 queryLocalInterface 就不是原本的意思了
            // 我们肯定不会真的返回一个本地接口, 因为我们接管了 asInterface方法的作用
            // 因此必须是一个完整的 asInterface 过的 IInterface对象, 既要处理本地对象,也要处理代理对象
            // 这只是一个Hook点而已, 它原始的含义已经被我们重定义了; 因为我们会永远确保这个方法不返回null
            // 让 IClipboard.Stub.asInterface 永远走到if语句的else分支里面
            return Proxy.newProxyInstance(proxy.getClass().getClassLoader(),
                    // asInterface 的时候会检测是否是特定类型的接口然后进行强制转换
                    // 因此这里的动态代理生成的类型信息的类型必须是正确的
                    new Class[] { IBinder.class, IInterface.class, this.iinterface},
                    new BinderHookHandler(base, stub,avlistener));
        }
        return method.invoke(base, args);
    }
}



在Activity的OnCreate()中如下调用即可:

             HookHelper.initBinderHook(listener);

0 0
原创粉丝点击