瞎操作:Android开发板上TeamViewer的自动绑定

1,159 阅读6分钟

背景

  笔者目前从事的项目是线下无人值守的智能终端设备的开发,因此远程功能对异常调试以及异常恢复至关重要。固件经过修改出厂时就已经把teamviewer打入其中,且开发者可以获得root权限。正常流程出厂前会有人员将当前机器的远程绑定到公司账户下。但是如果固件经过恢复出厂设置的操作的话,那么远程就需要重新绑定,所以远程的自动绑定就特别重要。

  正常面对这种需求当然首先去找官方文档,但是翻遍Teamviewer提供的api接口也没有找到相关的实现;没办法只好拨打官方提供的技术支持电话进行咨询,经过了无数次的等待终于接通,但是技术人员的回复是不提供这个功能。

  在官方支持无果的情况下,笔者只好寻找其他的解决思路,通常面对这种问题第一个解决思路是通过抓包去分析绑定流程的数据传输进行解决。拿到所属账户的用户id以及设置的唯一标识:AndroidID去进行模拟绑定。

调起应用:

点击分配:

分配成功:

  注:笔者公司绑定TeamViewer的方式为通过打开teamviewer分配的固定的url链接通过浏览器调起Teamviewer应用进行绑定。

  通过抓包可以发现绑定时候去先获取当前机器所属的账户组,然后点击分配按钮时通过获取当前机器的信息去服务器进行当前账户的分配。分配成功后会返回当前机器的远程id和连接时候的密码。 分析到这里好像已经解决了我们的需求。但是理想很丰满,现实很骨感。一顿骚操作以后发现模拟数据去请求后台也并不能在后台显示这台绑定的设备,这种方法宣告失败。

  我们再回头分析一下,Teamviewer内部的绑定并不是透明的,我们仅仅通过抓包的数据去进行hook好像并不代表它内部的所有流程。可能在进行网络请求后还进行了其他的操作,但是我们并不能知道。分析到这里下一步好像应该去做反编译去获取到它内部的流程操作。但是笔者再一顿骚操作后发现其内部进行了混淆,并且之前对反编译也没有什么研究,那么这种解决思路暂时先放一放。

  我们再分析一下正常绑定的流程,手动打开一个链接->通过浏览器调起应用->点击应用内的分配按钮->分配成功。整个过程关键的一步是手动点击按钮去实现。机智的你可能已经知道我们下一步的思路是通过模拟点击去进行自动绑定的实现。

  但是模拟点击的实现有个问题,我们不可能每次开机以后都去进行一次模拟点击的操作,在已经绑定的情况下再去做重复的点击无疑是多此一举。那么就需要去分析是否已经绑定过,正常的思路有两个:

  • 通过api去获取当前机器是否已经绑定
  • 本地是否有数据记录是否绑定

  毫无疑问去分析本地数据会更快一点,再结合之前说到的情况:机器恢复出厂设置的情况下已经绑定的远程会失效。那么我们可以分析得知本地肯定会存储一些必要的数据。那我们直接去data/data 里面去找一下。找了半天,终于在/data/data/com.teamviewer.host.market/files/global.conf路径下面发现了点东西。

  可以发现OwningManagerAccountName和OwningManagerCompanyName表示当前所属账户信息的。在没有绑定的情况下这两个字段是不存在的。那么问题就解决了。在每次开机时通过判断这两个字段是否去需要绑定。   是否绑定的问题已经解决了,那么接下来就到了重点。上面的流程分析提到:显示弹窗->点击弹窗——>绑定完成。首先是判断是否已经进入到绑定的承载界面:

    private fun getTopService(): Boolean {
        val exec = Shell(true).exec("dumpsys activity | grep  mFocusedActivity")
        return exec.message.contains("com.teamviewer.host.ui.HostActivity")
    }

判断弹窗

  通过获取位于栈顶的进程来判断是否浏览器成功调起应用,然后是判断弹窗是否显示以及按钮的位置,方法比较多:1.我们知道Android的窗口都是通过WinddowManager来管理的,WindowManager是一种方式; 2.另外一种方法是通过uiautomatorviewer来获取。这里着重讲一下uiautomatorviewer的方式。平常在开发过程中我们经常会使用Android sdk tools下面的uiautomatorviewer来获取当前屏幕正在显示的View层级关系以及所属位置。

  同样的我们可以直接在Android 的shell环境下直接执行

 Shell(true).exec("uiautomator dump")

  执行这个命令,会直接在sdcard下生成一个window_dump.xml文件。我们通过解析xml分析出来当前是否处于弹窗状态以及需要模拟点击的按钮位置。

模拟点击

  说到模拟点击,你脑子里第一个想到的是什么?大多数情况下可能想到的都是AccessibilityService。AccessibilityService根据官方的介绍,是指开发者通过增加类似contentDescription的属性,从而在不修改代码的情况下,让残障人士能够获得使用体验的优化,大家可以打开AccessibilityService来试一下,点击区域,可以有语音或者触摸的提示,帮助残障人士使用App(这段介绍是我抄的)。但是AccessibilityService使用的前提是需要用户手动授权,Android默认是关闭的,对于笔者的业务场景是无法满足需求的。

  那可以考虑另外一种思路,直接输入input事件。我们在平常开发中经常会用到adb shell input tap xx xx 来表示一个点击事件。xx xx 代表Android坐标系下的坐标。同样的还有swipe代表滑动。插一句题外话 adb shell 工具使用的好对于平常的开发是大有裨益的。

Shell(true).exec("input tap xxx xxx")

  到此,我们已经解决了模拟点击的步骤。点击完成再去本地文件中看一下发现所属的账户信息已经写入成功了。同时在后台也发现了这台设备已经在线,可以愉快的连接上去搞事情啦。

结语

  以上就是笔者面对这个需求从懵逼到最后实现整个需求的过程。事后看来有很多没有按照Android开发规范来进行操作,如获取弹窗是直接dump出xml来进行解析而不是通过WindowManager来进行操作。但是想分享的是整个需求的实现思路。希望能对你有一点点帮助。