一,问题背景
最近在做安卓自动化的时候,发现使用instrument自带的Instrumention.sendPointerSync向其他应用程序发送点击事件的时候,没有效果,而且报出错误:
Permission denied,injecting event from pid XXX XXX uid to window XXX owned by uid XXX.
简单翻译过来就是:从一个应用程序向另外一个应用程序发送事件,因为两个程序的uid不一致,导致权限不够。
二,错误原因:
下面是我从网上找到的关于这个错误原因的答案,原文可参考:http://www.java123.net/984624.html
一、分析 android.app.Instrumentation的sendpointerSync的实现原理
(1)android.app.Instrumentation
(2)android.hardware.input.InputManager
(3)android.hardware.input.IInputManager
最后是调用Binder.transect进行跨进程调用。被调用者是系统服务,可追朔到InputManagerService的injectInputEvent。(4)/frameworks/base/services/java/com/android/server/input/InputManagerService.java
在这个函数中,终于发现了我们要找的SecurityException INPUT_EVENT_INJECTION_PERMISSION_DENIED是jni层的nativeInjectInputEvent。
(5)/frameworks/base/services/jni/com_android_server_input_InputManagerService.cpp
(6)/frameworks/base/services/input/InputDispatcher.cpp
终于发现了INPUT_EVENT_INJECTION_PERMISSION_DENIED踪迹,继续查看checkInjectionPermission:
查看系统日志,发现是造成Permission验证失败的原因是:当前windowHandle(被测app)->owneruid与注入者(instrument)->injectoruid不一致。并且windowHandle(被测app)的owneruid竟然是1000(系统账户)!
因为从1.5后android系统做了限制,不允许跨进程注入,这个方法只能在自己这个程序内用,home出去就不行了。
三,解决方案:
跨进程点击常用的解决方案如下:
(1)第一种只用adb命令
命令很简单,比如说点击事件: input tap 100 100 (点击 坐标 100,100)
(2)直接往linux底层/dev/input/event*写事件
第一、分析android界面捕获事件的流程。
用户在屏幕上点击一下后,程序里面的OnClickListener是怎样收到这个事件的。
大致流程如下:户点击-(硬件驱动部分)硬件产生一个中断,往/dev/input/event*写入一个相应的信号->jni部分,android循环读取/dev/input/event*的事件,再分发给WindowManagerServer,最后再发到相应的ViewGroup和View。这里可以通过往/dev/input/event*写信号的方式,来达到模拟事件的目的,接下来关心的就是信号的协议了。
第二、按键协议分析
通过adb shell getevent 命令可以获取事件流信息。如下图所示:
BS_MT_TRACKING_ID //是用来追踪一次点击的标识,每次点击累加,与最后的ABS_MT_TRACKING_ID和SYN_REPORT 形成一个完整的点击事件。第二个参数就是点击的x坐标,第三个是y坐标。
得到这些值后,我们就可以组织我们的点击事件了,注意这里输出的都是16进制的,使用命令时要使用十进制的数字。每种手机的事件流可能不太一样,我们适配了红米note的事件流,如下所示:
HM NOTE 1LTEW
sendevent /dev/input/event2 3 57 710
sendevent /dev/input/event2 3 53 x
sendevent /dev/input/event2 3 54 y
sendevent /dev/input/event2 0 0 0
sendevent /dev/input/event2 3 57 4294967295
sendevent /dev/input/event2 0 0 0
第三、sendevent原理分析
我们使用了sendevent命令进行点击。翻看安卓的sendevent源码我们可以看到,其实现就是对event文件进行写入操作。那么我们也可以做同样的事情。具体的jni代码实现如下。在实现中,为了实现快速的点击,我只是第一次写事件时进行文件的打开操作,之后都用同一个文件句柄进行写入。用完了之后再通过jni接口释放掉该句柄。 具体的源码实现请参考安卓源码:
https://github.com/android/platform_system_core/blob/master/toolbox/sendevent.c
第四、封装sendevent注入命令
(4)通过hook方法绕过权限验证
通过之前的原因分析,我们可以看出实在checkInjectpermission这个函数的时候验证失败导致的,checkInjectpermission函数中还调用了hasInjectPermission函数,hasInjectPermission的的函数实现如下:
哎呀,我去,到这里才发现injectorUid==0的话,这个权限检查就过了。。。。。。
那么Uid为0代表什么呢?UID 为0 代表着是root账号,是最高权限。
那么通过hook方式通过权限检查的思想就是:hook中native InjectInputEvent,,将uid的值改为0,这样就能够通过里面hasInjectPermission的权限验证了。
代码实现如下:
这种需要手机连着电脑,在pc端执行monkey命令,并不是很方便,因此不推荐
除了以上介绍的几种方法之外,还有一种方法,不过我还没尝试,有兴趣的可以试试,就是以父进程的方式启动另外你需要注入点击事件的app,这样的话,它就是你的子进程,就可以随机进行点击。Ps之前讲的这些方法都需要root权限,小编比较推荐使用hook的方式。