2024年8月6日 关于安卓录屏方案的讨论

最近要实现安卓上录屏的功能,并且需要同时记录操作,系统自带的录屏可以将操作显示在视频中,但需求是将操作单独放到一个文件中。于是只能考虑自己实现了。

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指令的格式为:

313029282726252423222120191817161514131211109876543210
01010100imm190cond

比对一下这一行的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容易。

  • 25
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值