一,问题背景

最近在做安卓自动化的时候,发现使用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

android 跨进程 timeout 异常 remoteTimeoutException android跨进程点击_android

(2)android.hardware.input.InputManager

android 跨进程 timeout 异常 remoteTimeoutException android跨进程点击_Android_02

(3)android.hardware.input.IInputManager

android 跨进程 timeout 异常 remoteTimeoutException android跨进程点击_Android_03


最后是调用Binder.transect进行跨进程调用。被调用者是系统服务,可追朔到InputManagerService的injectInputEvent。(4)/frameworks/base/services/java/com/android/server/input/InputManagerService.java

android 跨进程 timeout 异常 remoteTimeoutException android跨进程点击_点击事件_04


在这个函数中,终于发现了我们要找的SecurityException INPUT_EVENT_INJECTION_PERMISSION_DENIED是jni层的nativeInjectInputEvent。

(5)/frameworks/base/services/jni/com_android_server_input_InputManagerService.cpp

android 跨进程 timeout 异常 remoteTimeoutException android跨进程点击_跨进程通讯_05

(6)/frameworks/base/services/input/InputDispatcher.cpp

android 跨进程 timeout 异常 remoteTimeoutException android跨进程点击_java_06

终于发现了INPUT_EVENT_INJECTION_PERMISSION_DENIED踪迹,继续查看checkInjectionPermission:

android 跨进程 timeout 异常 remoteTimeoutException android跨进程点击_android_07

查看系统日志,发现是造成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 命令可以获取事件流信息。如下图所示:

android 跨进程 timeout 异常 remoteTimeoutException android跨进程点击_Android_08

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注入命令

android 跨进程 timeout 异常 remoteTimeoutException android跨进程点击_点击事件_09

android 跨进程 timeout 异常 remoteTimeoutException android跨进程点击_java_10

(4)通过hook方法绕过权限验证

通过之前的原因分析,我们可以看出实在checkInjectpermission这个函数的时候验证失败导致的,checkInjectpermission函数中还调用了hasInjectPermission函数,hasInjectPermission的的函数实现如下:
哎呀,我去,到这里才发现injectorUid==0的话,这个权限检查就过了。。。。。。

那么Uid为0代表什么呢?UID 为0 代表着是root账号,是最高权限。

那么通过hook方式通过权限检查的思想就是:hook中native InjectInputEvent,,将uid的值改为0,这样就能够通过里面hasInjectPermission的权限验证了。

代码实现如下:

android 跨进程 timeout 异常 remoteTimeoutException android跨进程点击_跨进程通讯_11

这种需要手机连着电脑,在pc端执行monkey命令,并不是很方便,因此不推荐

除了以上介绍的几种方法之外,还有一种方法,不过我还没尝试,有兴趣的可以试试,就是以父进程的方式启动另外你需要注入点击事件的app,这样的话,它就是你的子进程,就可以随机进行点击。Ps之前讲的这些方法都需要root权限,小编比较推荐使用hook的方式。