没什么实用价值,一种记叙文和学习笔记的结合体。
0.
回老家收到新需求:能不能处理一下这台旧手机,让我在家打上单位的钉钉打卡?
旧手机型号华为荣耀7i,安卓版本4.0.3。看到华为两个字,我仿佛看到了我失败的未来。
1.
首先想到的必然是虚拟定位。但钉钉显然会防范这种方法。搜索资料得钉钉似乎会扫描应用列表检测相关软件;如果禁止它读取应用列表,它也会不让你打卡。接下来的问题是如何返回给它一份假的应用列表。
第二个方法是安装远程操控,但钉钉也会检测远程操控软件比如向日葵。以及远程操控手机不如远程操控同事,下一个。
第三个方法是定时任务。写一个或者下一个定时任务程序,每天到点进入钉钉打个卡。听上去不太保险。或许可以和第二个结合一下,远程发送一条命令然后自动打卡,但回家这几天显然不够我从零写一个程序装上。
2.
于是开始研究第一个方案。既然要返回给它一份假的应用列表,那么应该要涉及到hook和root。一个广泛能搜到的方案是Magisk+LPosed+Hide My AppList。于是开始研究第一步,怎么装Magisk。事实上LPosed已经要求安卓版本8.1+,但当时没有发现。版本的阴影将伴随接下来几个小时。
打开Magisk官网的安装教程,很详细,大喜。阅读一番决定先装一个adb。遥想上次用adb还是上次。adb装起来很容易,先adb devices看看设备有没有连接上,没找到。于是点七次版本号打开开发者模式,打开usb调试,好连上了。adb push一个文件,好这根诺基亚的数据线和华为还挺适配,还有传数据功能,继续下一步,找.img文件。
在这里找到了对应版本的卡刷包,虽然要三块钱,就当交学费了。然后下了HuaweiUpdateExtractor把UPDATE.APP里面的BOOT.img提取出来。把Magisk安装包和boot一起adb push到手机里,修复boot,然后adb pull把修复好的文件下载回电脑,开始fastboot。
fastboot flash boot $path_to _boot.img
失败了。想起手机的bl锁还没解。华为解bl锁要解锁码,但解锁码17还是18年官方就不再提供了。如果绕过解锁码需要root,但我root需要解锁码(bushi
安卓在5.0进行了一次大更新,修复了很多漏洞。所以这个4.0的手机理论上可以尝试各种root工具。但是要回退各种版本各种试,为什么不试试万能的某宝呢。打开橘色软件,问第一家做不了,第二家能搞,装了远程控制软件,大喜,准备观察学习。店家接管电脑后又装了个usb redirector,直接导过去,啥都看不到,败兴而归。过了一会把解锁码发过来了,收费五元,也行吧。
然后继续刷机,很快啊,嗡的一下,重启了。Magisk还是没装好。又试了一次,感觉不太对劲,一看Magisk 24.0有点太先进了,只能装22.0。于是装了22.0,发现Lposed刷不进去,寄。
3.
虽然已经失败了但决定学习一下,一会Magisk一会Lposed一会riru有点晕,闲着没事就是除了毕设什么都可以学一学。先从最开始的hook技术看吧。
4.
hook,就是执行过程中拦截将要执行的函数,让它运行自己写的函数。有点像override。学了一点点,大概是在程序运行前加载自己的程序进行替换,比如利用java反射,JNI重定向达到这个目的。
这里提到了XPosed,XPosed已经停止维护了。LPosed是基于XPosed的一个分支,可以用在后面的系统上。XPosed通过修改zygote进程,也就是fork所有进程的进程,来在程序运行前执行自己的插件。fork出来的进程具备父进程的资源,也就包含了XPosed的修改。
在Android使用Dalvik虚拟机的时候,因为采用JIT即时编译,所以方案是将hook目标方法设定为一个native方法,然后绑定自己的方法上去。到了ART也就是Android5.0之后,因为JIT太慢,ART选择了预编译,意味着等到创建新进程出来再动手脚已经太晚了,XPosed选择重新编译 libart.so,ART的动态库,使得我们可以直接修改一个方法对应的汇编地址,然后跳转执行自己的方法。
可以看出XPosed会平等地在每个进程都进行注入,让应用启动变慢。LPosed可以指定目标app,改良了这一点。
5.
既然要修改zygote,那么接下来研究一下zygisk或者riru是怎么做到的。由于Magisk先用的riru,那么就先看riru。一开始riru选择替换了一个zygote会用到的系统库。刚看到的时候没理解,因为一直在想改zygote代码的事。但注入讲究一个不能改别人的的东西,所以只能对它会用到的资源下手了。后来riru发现除了zygote也有别的进程会用到这个库,从而导致一些错误,于是在riru22中换了“原生桥接”的方法,源于通过系统的native bridge实现注入zygote 。
这个方法里提到了so库,.so类似Windows下的.dll,但要适配不同的手机CPU架构,不同架构生成不同.so文件。而native bridge可以把一种架构下的代码换成另一种。在加载zygote对应的可执行文件app_process的时候又会用到native bridge,而native bridge有什么特点呢,它会调用dlopen,dlopen将打开一个新库,并把它装入内存,我猜dlopen意思就是dll open。接下来的事虽然没有看懂但原文说得很明白:
dlopen会执行目标库的.init_array中的所有函数,而让自己的函数进入.init_array实际上只需要声明
__attribute__((constructor))
就好了,完全没有难度啊!
总之是一些C或者C++的一些特性。现在我们应该找到它dlopen的库,也就是runtime_options.ReleaseOrDefault(Opt::NativeBridge),然后把自己的代码放进去。这个库叫ro.dalvik.vm.native.bridge,好现在编译一个自己的函数,加上__attribute__((constructor)),把名字改成ro.dalvik.vm.native.bridge放进去,目标达到了。虽然会产生一些问题但接下来看原文就好了,作者不仅很牛能想到这些办法,文章还写得非常丝滑,喜欢!
6.
接下来应该看看zygisk的,但zygisk的意思是Magisk注入zygote的方案。所以先看看Magisk。
Magisk的用处是获取root权限。先了解一下一般如何获取root权限。假设这是一个普通的linux系统,我们输入su和密码,就可以拿到root权限。如果我们试图对Android做同样的事情,会显示没有su这个文件。实际上Android里也是有这个命令的,但它不对手机用户开放,怕你把手机整成砖。那么接下来就是找到su程序,把它的路径放到环境变量里。
之前装Magisk的时候是下载了一个boot.img修复完刷进去。Magisk通过处理boot.img,替换系统init过程为自己的magiskinit,在里面加载修改系统的各种模块,更具体的过程可以看这里;同时,Magisk可以看作一个文件系统挂载在/sbin上,这里面有su,也就能root了。之前看说systemless的意思是不修改system分区而达到root的目的,困惑了半天,后来发现mount --bind a b的意思是,操作后访问b,访问到的实际上是a。那确实是没改变system哈。
突然发现上面两个链接是同一个作者的,膜了。
那么zygisk也可以同理类比得它是替换了app_process实现的劫持zygote。
7.
最终成果:吃了三天家里好吃的饭,香!