背景

手机需要打验证码

被否定的方案

  • 基于无障碍服务的模拟滑动 被否原因——需要额外安装软件
  • 基于shell执行sendevent命令 被否原因——滑动不流畅

最终方案

  • 基于shell向输入设备写入数据流

记录

首先进入shell然后执行getevent -lt

此时点击屏幕或者滑动屏幕,可以看到一系列输出

add device 1: /dev/input/event1
  name:     "hisi_on"
add device 2: /dev/input/event0
  name:     "hisi_gpio_key"
add device 3: /dev/input/event2
  name:     "fingerprint"
add device 4: /dev/input/event3
  name:     "HI6250_hi6555c_CARD Headset Jack"
add device 5: /dev/input/event4
  name:     "cyttsp5_mt"
could not get driver version for /dev/input/mice, Not a typewriter
[     109.609577] /dev/input/event4: EV_ABS       ABS_MT_TRACKING_ID   00000024
[     109.609577] /dev/input/event4: EV_ABS       ABS_MT_POSITION_X    000003d6
[     109.609577] /dev/input/event4: EV_ABS       ABS_MT_POSITION_Y    000002c7
[     109.609577] /dev/input/event4: EV_ABS       ABS_MT_PRESSURE      00000060
[     109.609577] /dev/input/event4: EV_SYN       SYN_REPORT           00000000
[     109.619927] /dev/input/event4: EV_ABS       ABS_MT_TRACKING_ID   ffffffff
[     109.619927] /dev/input/event4: EV_SYN       SYN_REPORT           00000000

这个时候能看到具体输入设备是什么,比如可能是/dev/input/event4

不同手机对应的可能不同

那么现在改成这个命令getevent -lt /dev/input/event4,输出就是下面这样

[     169.833199] EV_ABS       ABS_MT_TRACKING_ID   00000031
[     169.833199] EV_ABS       ABS_MT_POSITION_X    0000034d
[     169.833199] EV_ABS       ABS_MT_POSITION_Y    00000368
[     169.833199] EV_ABS       ABS_MT_PRESSURE      000000d9
[     169.833199] EV_SYN       SYN_REPORT           00000000
[     169.858516] EV_ABS       ABS_MT_TRACKING_ID   ffffffff
[     169.858516] EV_SYN       SYN_REPORT           00000000

既然可以getevent那么自然也能sendevent,具体参考文末的通过sendevent实现多点连续滑动

有了前面的输出结果,那么对应的就可以转换成sendevent命令,重现刚才的事件

use: sendevent device type code value

注意sendevent的参数是10进制

type code value对应的定义数值可以在系统内核源代码中的input.h文件找到,好在大部分都是一致的

将命令转换为sendevent指令,并放置于shell执行,可行,但是会和通过sendevent实现多点连续滑动评论中说到那样,一卡一卡的

即使向同一个shell中写入执行命令,依然难以解决这个问题

查阅sendevent.c源码,最终将数据写入设备的代码是

ret = write(fd, &event, sizeof(event));

event结构体的定义如下

struct input_event {
    struct timeval time;
    __u16 type;
    __u16 code;
    __s32 value;
};
struct timeval {
    time_t tv_sec;        /* seconds */
    suseconds_t tv_usec;    /* microseconds */
};
typedef unsigned long time_t;
typedef unsigned long suseconds_t;

如此一来就知道写入对应的数据类型了

为了准确获取数据,通过cat获取数据 -> cat /dev/input/event4 > /sdcard/tmp.bin

数据如下(16进制):

4C C7 B9 60 00 00 00 00 42 C9 05 00 00 00 00 00 03 00 39 00 36 00 00 00
4C C7 B9 60 00 00 00 00 42 C9 05 00 00 00 00 00 03 00 35 00 C9 02 00 00
4C C7 B9 60 00 00 00 00 42 C9 05 00 00 00 00 00 03 00 36 00 D0 02 00 00
4C C7 B9 60 00 00 00 00 42 C9 05 00 00 00 00 00 03 00 3A 00 E2 00 00 00
4C C7 B9 60 00 00 00 00 42 C9 05 00 00 00 00 00 00 00 00 00 00 00 00 00
4C C7 B9 60 00 00 00 00 77 4E 06 00 00 00 00 00 03 00 39 00 FF FF FF FF
4C C7 B9 60 00 00 00 00 77 4E 06 00 00 00 00 00 00 00 00 00 00 00 00 00

结合之前获取的输出简单总结如下,另外可以知道写入的数据是小端

时间(整数部分)时间(小数部分)typecodevalue
4字节4字节2字节2字节4字节

时间整数和小数部分后面都有4字节的0,emm不知道怎么来的

最终解析和生成二进制流的代码如下

import time
import struct

def parse(raw: bytes):
    if len(raw) != 24: return
    ts_1, _, ts_2, _, _type, _code, _value = struct.unpack('<LLLLHHI', raw)
    print(f'{ts_1}.{ts_2}', _type, _code, _value)

def generate(_type: int, _code: int, _value: int):
    ts_1, ts_2 = f'{time.time()}'.split('.')
    return struct.pack('<LLLLHHI', int(ts_1), 0, int(ts_2), 0, _type, _code, _value)

line = '4C C7 B9 60 00 00 00 00 42 C9 05 00 00 00 00 00 03 00 39 00 36 00 00 00'
raw = binascii.a2b_hex(''.join(line.split()))
parse(raw)
raw = generate(3, 57, 54)
print(binascii.b2a_hex(raw, ' ').decode('utf-8').upper())

可以进行上述转换后,现在可以将生成的文件通过cat写入/dev/input/event4来完成模拟滑动了

演示

参考