QQ-bot 框架(服务)部署记录

某位可爱猫娘群友打算写一个QQ-Telegram互联机器人。因为逆向式QQ库的风控风险,有其他群友推荐使用 OpenShamrock 进行开发。考虑到 x86 转译效率大概不高,而手头正好有个 arm 设备(rock5b-rk3588);于是参考 Shamrock 的教程,在这块arm开发板上搭建一个 Shamrock 服务给群友用。

尽管过了一天之后,这个互联机器人项目由于考虑安全性问题被取消了,但是整个Shamrock 服务搭建过程还是颇有收获的,于是记录一下。

Shamrock 简介

☘ 基于 Xposed 实现 OneBot 标准的 QQ 机器人框架,原作者fuqiuluo已脱离开发,接下来由白池接手哦!本项目为OpenShamrock,不会有任何收费行为,欢迎大家的加入!

在Linux类服务器上部署(使用docker)

这个选择过于多元化,你可以使用redroid、docker-android,需要注意的是部分框架要求开起虚拟化才能使用哦!目前依旧是采用Lspatch+Shamrock方案在docker部署,因为安装magisk类需要修补镜像,难以实现。

这个东西^1,对初学者(比如我)来说基本上等于没说,感谢群友指点,查到了一些资料。

Xposed

Xposed是Android平台上的一个常用的HOOK框架,可以在不改变程序源代码的前提下,影响程序的运行。一个支持Xposed的Android应用程序被称为一个Xposed模块,用户可以在Xposed中安装各种各样的Xposed模块。^2

Xposed模块使用Java语言开发,这意味着开发人员可以使用Android Studio开发一个自己的Xposed模块,就像开发一个Android应用程序一样简单。

实际搭建 Shamrock 服务使用的是 LSPatch,这是 Xposed的一个变种;安装它无需 root 是最大的优势,不过相对而言需要修改应用的apk。

OneBot

OneBot 是一个聊天机器人应用接口标准,旨在统一不同聊天平台上的机器人应用开发接口,使开发者只需编写一次业务逻辑代码即可应用到多种机器人平台。^3

和本篇并无关系的思考:感觉之前的 Minecraft 通信插件的协议可以拿这个改改?算TODO了。

OpenShamrock

从服务来说,实际上是一个类似于 tdlib/telegram-bot-api 的服务,本质是把qq客户端行为转变成bot api调用服务行为。

从实现原理上,这个是一个直接hook android qq客户端的服务,直接从客户端获取聊天信息。因为使用标准客户端自然也不用担心因为协议暗桩等问题被风控(但是会因为聊天内容被风控)。

所以实际上是需要做什么呢?需要在 Android 系统里面安装一个(加装了hook的)qq 客户端,安装 LSPatch 框架,再安装 Shamrock 模块;Shamrock 开启一个对外服务,拦截qq的消息和事件并对外转发,以及收到外部消息后操作qq发出到群里。

当然在这之前,需要有一个 Android 系统,比如 Android 物理机(手机),虚拟机(模拟器), Android 容器等等

安装

安装Linux下的Android运行环境 - 使用 redroid^4

redroid (Remote anDroid) is a GPU accelerated AIC (Android In Cloud) solution. You can boot many instances in Linux host (Docker, podman, k8s etc.). redroid supports both arm64 and amd64 architectures. redroid is suitable for Cloud Gaming, Virtualise Phones, Automation Test and more.

关于 Android 容器为什么可以运行 Android 应用这一点,我并不是特别明白。查阅一些资料:___以下仅以 arm64 为例___,很明显在 Linux 发行版是不能直接运行 Android 应用的^5

安卓与 Linux 的关系

安卓实际上用到的是 Linux 的内核。因为 Linux 本身是开源的,所以谷歌的工程师们选择了在 Linux 内核基础之上做裁剪定制,这样他们就不需要从头开始来开发一个全新的系统。BTW,这在大厂当中也是一种比较常用的做法,比如 Playstation 的操作系统用的是 FreeBSD的内核,XBox 用的是 Windows NT 的内核。在安卓的手机上,在系统信息中你可以看到 Linux 内核的版本。

2) 可以在Linux 桌面上运行安卓的应用吗?

因为其他 Linux 发行版本也没有 Dalvik / ART,所以安卓的应用在 Linux 桌面上不能直接运行。不过我们只需要将 Dalvik / ART 移植到 Linux 桌面环境就可以了。正是基于这个原理,市场上有很多安卓手机模拟器,不仅可以用在 Linux 桌面,还可以用在 Windows 以及 Mac 上。

所以可以推测,redroid 是共用了宿主机的内核,编译了一个 Android 上层环境,patch了一些远程模块和 gpu 模块,于是可以在这个环境下运行 Android 应用。

安装 redroid 其实很容易。

首先确保 Linux 内核的两个 kernel features 是启用的:

1
2
3
4
$ grep binder /proc/filesystem
nodev binder
$ grep ashmem /proc/misc
122 ashmem

这个与官网的教程略有不同。官网上使用 modprobe 去检测是否存在这两个模块。然而请教 rock5b 群友得知,rk3588 主线 armbian 镜像内核(>5.10)默认是带这两个模块的,但是 modprobe并不能检测到。

查资料的时候看到一个东西,记录一下

ashmem(注:主线内核在5.18 drop了这个模块,官方的替代方案是memfd,而且redroid也提供了androidboot.use_memfd=1来启用memfd支持,所以理论上可以不编译这个模块,不过这里稳妥起见,还是编译进去了)

然后直接拉取镜像运行即可。

此处使用 docker,选择 redroid/redroid:13.0.0-latest :

1
2
3
4
5
6
7
### 这并不是最终镜像
$ sudo docker run -itd \ # Allocate a pseudo-TTY & Keep STDIN open even if not attached & Run container in background and print container ID
--rm \ # Automatically remove the container when it exits
--privileged \ # Give extended privileges to this container
-v /home/data:/data \ # Bind mount a volume: /home/data at host to /data at container
-p 35555:5555 \ # Publish a container's port(s) to the host: export 5555 adb port in container to 35555 at host
redroid/redroid:13.0.0-latest

几个 Tips :

  1. /data 是安卓系统里面总的数据文件夹 (Files 的根目录是 /sdcard),似乎所有安装的 apk 等信息都在里面;因此将它映射出去可以不用每次都重新安装 apk

  2. 内部 5555 端口是 adb 调试端口;

  3. privileged 是一个很特殊的参数,让容器内的 root 和主机上的 root 权限相同;如果不加的话 容器内的 root 仅相当于主机的一个普通权限^6

    1
    2
    3
    $ docker run -t -i --rm ubuntu bash
    root@bc338942ef20:/# mount -t tmpfs none /mnt
    mount: permission denied

    This doesn’t work, because by default, Docker drops most potentially dangerous kernel capabilities, including CAP_SYS_ADMIN (which is required to mount filesystems). However, the --privileged flag allows it to run:

    1
    2
    3
    4
    5
    $ docker run -t -i --privileged ubuntu bash
    root@50e3f57e16e6:/# mount -t tmpfs none /mnt
    root@50e3f57e16e6:/# df -h
    Filesystem Size Used Avail Use% Mounted on
    none 1.9G 0 1.9G 0% /mnt

    The --privileged flag gives all capabilities to the container, and it also lifts all the limitations enforced by the device cgroup controller. In other words, the container can then do almost everything that the host can do. This flag exists to allow special use-cases, like running Docker within Docker.

    实际使用中,privileged 是个很危险的 options,应当被尽量避免。

    但是 redroid 应该是有对 host 设备访问的需求的,直接去除该参数会导致无法启动。

    [TODO] 想试试加哪些 capabilities^7 可以让redroid 跑起来

    Linux capabilities

    Linux capabilities are a mechanism for limiting the power of root. The Linux kernel splits the privileges of root (superuser) into a series of distinct units, called capabilities. In the case of rootless containers, container engines still use user namespace capabilities. These capabilities limit the power of root within the user namespace. Container engines launch the containers with a limited number of namespaces enabled to control what goes on inside of the container by default.

最后,安装 scrcpy Android 远程桌面 连接 <host-ip>:35555 就可以看到里面的 Android 界面了

安装 Android 内 Shamrock 运行环境

这部分就是单纯的安装步骤就是了

  1. 把 QQ 的 apk 传上去(并不是安装)

    1
    ./adb push /path/to/QQ/Android_8.9.68.11565_64.apk /sdcard
  2. 安装 LSPatch

    1
    ./adb install /path/to/lspatch_installer/manager-v0.6-398-release.apk
  3. 安卓里面打开 LSPatch

    (从下往上滑可以打开所有应用列表)

    打开 LSPatch 并在管理页面选择 + 新建修补,从存储目录选择 QQ apk;

    第一次按 + 会先让你选个路径放修补过的 apk;

    修补模式默认且应该优先选择 本地模式,这样方便直接更新 Shamrock 模块而不用重新修补,缺点是需要 LSPatch 保持后台运行;

    其他配置保持默认,然后点击开始修补,修补完成后会提示安装(如果已经安装会提示卸载);

    这边又有个小坑:修完了之后 redroid 环境其实不太好让 LSPatch 直接装,要点 return 然后自己去路径下面找apk装;

  4. 安装修补好的 QQ apk 并打开(可以不登录)

  5. 安装 Shamrock 模块

    1
    ./adb install /path/to/lspatch_installer/Shamrock-v1.0.7-dev.6201d12-arm64.apk
  6. 在 LSPatch 管理页面点击修补好的 QQ ,选择 模块作用域 勾选上 Shamrock 模块然后保存;

  7. 启动 Shamrock 并重新启动 QQ 客户端,此时 Shamrock 会显示已激活;

  8. 现在整个服务已经开始运行了,进入 Shamrock 配置端口等参数;

性能调优

docker 资源管理与限制

部署结束了以后,监测资源占用峰值能达到 CPU 700% Mem 2.2GiB 不愧是你啊QQ;

为了避免过热以及确保整个服务器不要崩溃,感觉需要对容器的资源进行一定的限制。

docker 本身有限制 cpu 使用率和内存使用率的接口:

Flag Shorthand Default Description
--cpu-count CPU count (Windows only)
--cpu-percent CPU percent (Windows only)
--cpu-period Limit CPU CFS (Completely Fair Scheduler) period
--cpu-quota Limit CPU CFS (Completely Fair Scheduler) quota
--cpu-rt-period API 1.25+ Limit CPU real-time period in microseconds
--cpu-rt-runtime API 1.25+ Limit CPU real-time runtime in microseconds
--cpu-shares -c CPU shares (relative weight)
--cpus API 1.25+ Number of CPUs
--cpuset-cpus CPUs in which to allow execution (0-3, 0,1)
--cpuset-mems MEMs in which to allow execution (0-3, 0,1)
Flag Shorthand Default Description
--memory -m Memory limit
--memory-reservation Memory soft limit
--memory-swap Swap limit equal to memory plus swap: ‘-1’ to enable unlimited swap
--memory-swappiness -1 Tune container memory swappiness (0 to 100)

CPU 的限制先放一边;

内存限制其实是给了一个最大值,超过最大值会 OOMkill (或者hang住) ;

但是在这种情况下实并不能很好地完成任务。原因是,QQ自然自己完全不可能去调整自己的内存使用,不过已知的事实是QQ在2G内存的手机上也可以勉强使用,那我这里不得不让内部的 Android 系统(指ART等)去“提前”调用 swap 或者改变内存策略来以牺牲性能为代价维持整个服务的运转。但是如何欺骗系统呢?

询问了一下群友,这似乎是一个非常……不规范的想法:

容器里面跑的进程实际上就是一个分割了上下文的普通「进程」

它是通过 cgroup 和划分 namespace 来实现的

你现在问的这个问题是虚拟化 vm 的行为,而非 docker/containerd 的

那这个时候,如何让一个容器里面的 kernel 和进程能访问到的东西表现得像是在一个 vm 里面的呢

我还真不知道,也许你亲自给他挂一堆自己修改过的 /proc /run /var 下面的 meminfo 的文件,也许是可以让它觉得它自己被限制的

也许,但我不清楚,我并不熟悉虚拟化技术实际背后的原理,感觉需要创建一个完全模拟出来的一切物理设备才行

from @Neko Ayaka

但是以中间那个 idea 为线索,还真发现了有一个软件模块可以做到这一点:

lxcfs 可以自动检测容器的运行限制 并将限制反映在”伪造”的系统信息中;只要容器内读取时用 “伪造”的系统信息接口代替真正的,就可以做到“欺骗”容器内的 Android 系统。

1
2
3
4
5
6
7
8
9
10
docker run -it -m 256m --memory-swap 256m \
-v /var/lib/lxcfs/proc/cpuinfo:/proc/cpuinfo:rw \
-v /var/lib/lxcfs/proc/diskstats:/proc/diskstats:rw \
-v /var/lib/lxcfs/proc/meminfo:/proc/meminfo:rw \
-v /var/lib/lxcfs/proc/stat:/proc/stat:rw \
-v /var/lib/lxcfs/proc/swaps:/proc/swaps:rw \
-v /var/lib/lxcfs/proc/uptime:/proc/uptime:rw \
-v /var/lib/lxcfs/proc/slabinfo:/proc/slabinfo:rw \
-v /var/lib/lxcfs/sys/devices/system/cpu:/sys/devices/system/cpu:rw \
ubuntu:18.04 /bin/bash

设备

redroid 宣称支持 gpu 加速。rk3588 带一个 gpu 支持 mali 似乎可以用于加速,但是实际上似乎没有效果;

翻阅 redroid repo issues 倒是找到一篇正正好相关的:

问题已经解决,感谢回答。原因是设备节点 /dev/mali0,在宿主机中 ls -i /dev/mali0 inode id 和容器中的 inode id不一致导致的。

运行时添加 --mount=type=bind,source=/dev/mali0,destination=/dev/mali0

姑且先加上了,并没有验证

运行小结

最后的启动命令为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ sudo docker run -itd --rm --privileged \
--cpus 2 \
-m 2048m \
--mount=type=bind,source=/dev/mali0,destination=/dev/mali0 \
--mount=type=bind,source=/home/android,destination=/data \
-v /var/lib/lxcfs/proc/cpuinfo:/proc/cpuinfo:rw \
-v /var/lib/lxcfs/proc/diskstats:/proc/diskstats:rw \
-v /var/lib/lxcfs/proc/meminfo:/proc/meminfo:rw \
-v /var/lib/lxcfs/proc/stat:/proc/stat:rw \
-v /var/lib/lxcfs/proc/swaps:/proc/swaps:rw \
-v /var/lib/lxcfs/proc/uptime:/proc/uptime:rw \
-v /var/lib/lxcfs/proc/slabinfo:/proc/slabinfo:rw \
-v /var/lib/lxcfs/sys/devices/system/cpu:/sys/devices/system/cpu:rw \
-p 35555:5555 \
redroid/redroid:13.0.0-latest

实测 使用群友的小号,加入多个群聊,观察12小时,系统正常运行;

1
2
CONTAINER ID   NAME                 CPU %     MEM USAGE / LIMIT   MEM %     NET I/O           BLOCK I/O        PIDS
f94362ac6c41 determined_swanson 128.68% 1.424GiB / 2GiB 71.19% 11.7MB / 10.6MB 1.52GB / 921MB 1074

Conclusion & TODO

  • 去除privileged 并探究加哪些 capabilities 可以让 redroid 跑起来
  • 测试 gpu 加速是否可用
  • 试试bot?
  • 更全面的理解一下 docker/k8s(k3s) 文件结构并规划如何映射磁盘路径
  • 完善防火墙

感谢一下从零开始指导我 Shamrock相关知识的瓜瓜、米米,解决了我容器技术相关疑惑的Neko,共同工作在开发bot的白天天,还有各个群友的陪伴!

Author: DWCarrot
Link: https://dwcarrot.github.io/blog/2023/12/29/Notes-QQ-Bot-Framework/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.