最近要实现安卓上录屏的功能,并且需要同时记录操作,系统自带的录屏可以将操作显示在视频中,但需求是将操作单独放到一个文件中。于是只能考虑自己实现了。
Android应用开发
那么首先想到的就是自己开发一个应用,实现这个功能。由于本人对Android开发不是很熟悉,于是在GitHub上找开源项目,找到了一个录屏的项目https://github.com/yrom/ScreenRecorder,但这个项目只能支持到Android SDK28,也就是从Android 10 及以上需要自己实现一个Service(https://github.com/yrom/ScreenRecorder/issues/41#issuecomment-517211105),感觉很麻烦。再加上用Android Studio配置这个项目时也出现了各种版本问题,最终放弃。
Tasker
突然想到Tasker有没有这样的功能,于是仔细一找,还真有个叫做“截屏”的任务类型,其实就是录屏,但是不知道为什么就是录不出来,于是只能找别的办法。
screenrecord+Tasker
在查找资料时,偶然看到Android中有一个自带的screenrecord
命令在/system/bin
目录下,发现确实能实现录屏功能,那这就好办了,只需要加一个调用的方式就行了,那么自然就想到了Tasker,用Tasker调用shell执行screenrecord命令,这样就方便多了。
于是再仔细查找screenrecord的资料:android 调用 screenrecord 实现录屏 ;Android 4.4自带屏幕录制命令screenrecord ,去掉180s限制。
发现screenrecord的录屏有180秒的限制,能通过更改源代码解决,于是赶紧找到Android这个部分的源码:
screenrecord.cpp - Android Code Search
再看看怎么编译这些代码:要求 | Android Open Source Project:
如果是校验代码,至少需要 100GB 可用磁盘空间;如果要进行编译,则还需要 150GB。如果要进行多次编译或使用 ccache,则需要更多空间。
竟然编译一个一百多KB的文件需要下载一百多GB的代码,这也太麻烦了。再看看这个screenrecord.cpp需要的头文件,
#include <gui/ISurfaceComposer.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
#include <gui/ISurfaceComposer.h>
#include <media/MediaCodecBuffer.h>
#include <media/NdkMediaCodec.h>
#include <media/NdkMediaFormatPriv.h>
#include <media/NdkMediaMuxer.h>
#include <media/openmax/OMX_IVCommon.h>
#include <media/stagefright/MediaCodec.h>
#include <media/stagefright/MediaCodecConstants.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/PersistentSurface.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/AMessage.h>
#include <mediadrm/ICrypto.h>
#include <ui/DisplayMode.h>
#include <ui/DisplayState.h>
全是和整个项目有关的,没法部分编译。
再想想有没有什么别的办法吧。
直接改编译好的程序
突然灵光一闪,这个180的数字在编译好的程序中肯定是一个常量,那么只要在编译好的程序中把这个常量改了不就行了吗。
先拿模拟器试验一下:
adb pull /system/bin/screenrecord
用VSCode打开screenshot,用十六进制编辑器打开,180转成十六进制是0xb4,系统是x86_64架构,小端序,那么直接搜索十六进制的B4000000,搜到了30个,使用二分法(或多分法),这里是六分法,先把五个B4000000替换成C8000000(十六进制的200),然后
adb push screenrecord /data/local/tmp/
adb shell /data/local/tmp/screenrecord --verbose --time-limit 190 /data/local/tmp/xxx.mp4
看看有没有效果,没有效果就再改五个,有效果了就撤回更改,看看撤回到哪个的时候又没效果了,那么就是应该改这个位置。
最后成功找到(每个设备的地址可能不一样,以实际情况为准):
- 改0x62500B位置的B4000000能正常录屏
- 改0x6AC00D位置的B4000000是修改错误提示信息中的180
这时我想,那么在手机上肯定也是这样改就行了,但实际上没有那么简单。
在arm64架构中的更改
在按照上面的方法,我也在手机上操作了一番,但是并没有效果,还是会提示超过180秒就无法录屏,于是继续查找资料,发现objdump
命令可以反编译,于是赶紧反编译一下手机上的screenrecord
和模拟器上的screenrecord
。
模拟器上的:
objdump -d /system/bin/screenrecord
/system/bin/screenrecord: file format elf64-x86-64
Disassembly of section .plt:
0000000000005330 <__libc_init@plt-0x10>:
5330: ff 35 72 86 01 00 push 0x18672(%rip) # 1d9a8 <_ZTVN7android12SortedVectorINS_12DisplayStateEEE@@Base+0x808>
5336: ff 25 74 86 01 00 jmp *0x18674(%rip) # 1d9b0 <_ZTVN7android12SortedVectorINS_12DisplayStateEEE@@Base+0x810>
533c: 90 nop
533d: 90 nop
533e: 90 nop
533f: 90 nop
......
objdump -d /system/bin/screenrecord
手机上的:
screenrecord: file format elf64-littleaarch64
Disassembly of section .text:
0000000000014000 <_ZNK7android12SortedVectorINS_12DisplayStateEE12do_constructEPvm@@Base-0x24dc>:
14000: 910003e0 mov x0, sp
14004: 14000001 b 14008 <_ZNK7android12SortedVectorINS_12DisplayStateEE12do_constructEPvm@@Base-0x24d4>
14008: d100c3ff sub sp, sp, #0x30
1400c: a9027bfd stp x29, x30, [sp, #32]
14010: 910083fd add x29, sp, #0x20
14014: 90000048 adrp x8, 1c000 <glUniform1i@plt+0x1860>
......
将这两个文件都用VSCode打开看看,此时终于明白了,模拟器是x86_64架构,编译出来的文件中直接含有十六进制表示的B4000000
这个常量。手机是arm64架构,编译出的文件中应该也有这个常量,但不是直接以B4000000
的形式表现的(如果一条指令的立即数位数不够存放32位的B4000000
,那可能会用两条指令分别存放高位和低位)。
于是搜索0xB4,查找这个常量可能出现的位置,还真找到了:
1430c: 7102d11f cmp w8, #0xb4
14310: f0000028 adrp x8, 1b000 <glUniform1i@plt+0x860>
14314: b9000500 str w0, [x8, #4]
14318: 54ffeca3 b.cc 140ac
其实一共找到了16个出现0xB4的地方,但是我感觉这个地方是最有可能是需要改的地方,因为这个操作码是cmp
显然是比较的意思,那么肯定是把一个变量和180比较,再加上后面有一个b.cc
(查资料发现是分支操作),那就很符合“当参数满足条件时继续执行”了。
找到了之后有两个思路,一是改这个常量,将其改得更大,但之前说过,这个常量无法超过立即数的最大限制,如果要超过限制,需要使用多条指令,但插入指令的操作会使后面的指令地址全部偏移,很有可能出现问题。
所以这里使用另一个思路:之前是“如果参数小于180就继续执行”,那么只需要改操作符,改成“如果参数不等于180就继续执行”,
于是查找arm中的cmp指令:ARM cmp命令详解_arm cmp指令-CSDN博客,看到CC表示小于,那结合后面的b.cc
操作码,显然这就是要改的指令了,于是查找arm64指令的官方文档:Arm A-profile A64 Instruction Set Architecture,看到带条件B指令的格式为:
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 | imm19 | 0 | cond |
比对一下这一行的54ffeca3
,转成二进制:
0101 0100 1111 1111 1110 1100 1010 0011
前8位01010100
符合,再看最后4位,0011
,表示条件是CC,中间的19位立即数左移两位就是跳转的相对地址。
那么只需要把最后4位条件改成NE(不相等),在官方文档中可以看到NE对应的条件码是0001,改好之后就变成了54ffeca1
。
现在知道应该怎么改了,接下来就需要在原始的screenrecord文件中找到对应的位置,这里发现直接查找54ffeca3
找不到,猜想可能是大小端序的问题,于是查找a3ecff54
,找到了,将其中的a3
改成a1
,保存后
adb push screenrecord /data/local/tmp/
adb shell /data/local/tmp/screenrecord --verbose --time-limit 190 /data/local/tmp/xxx.mp4
终于成功了,能录屏超过180秒的时间了,但是不知道为什么,改了这个应该会在相等的时候提示错误,但是指定了--time-limit 180
也不提示错误还是正常录屏。
感想
Android开发看来还是很麻烦啊,反汇编再修改二进制都比开发Android容易。