ruri 启动 chroot 容器
ruri 启动 chroot 容器
这一过程在 Android 上运行,需要有 root 权限
在仿真途中遇到了一个 DSPLib.py
文件,每次仿真都会加载一次,开头明摆着写着 VPICRYPTV2
;要是它放在内部加载,恐怕也不会注意到,放在用户可见的目录中加载,这有些引发好奇心了,于是看看到底从哪里加载到源码的。下面记一下查找解密位置的流程:
首先,是个 .py,加载后是
module,先看看加载流程,发现了一个内置库
vpi_tc_internal
[2026-03-07 13:36:28] vpi_tc_internal info: |
它没有 __file__
字段,说明其是内置或故意隐藏的,应该嵌入在解释器内部;先观察执行效果,它使用
check_header 方法检查数据头部,另一个
init_module 方法从 module spec
加载,解密并初始化这一模块,这是 spec 的结构:
[2026-03-07 13:36:28] spec info: |
不过,spec 被初始化后,已经编译为字节码了,没有
__source__ 字段:
[2026-03-07 12:50:20] Source exec info: |
劫持 exec 会导致相关的 scipy
组件初始化出错,看起来没有很好的方法,并且无法保留注释等内容,接下来看下究竟是哪里进行了解密。
仿真由界面调用启动,界面程序为 pde.exe,命令行仿真程序为
ptcl.exe,在 System Informer 下可以看到创建的进程:
"C:\Program Files\VPI\VPIdesignSuite 11.1\simeng\bin\x64\ptcl.exe" -port 7801 -id 1 -dir "C:\Users\zsig\AppData\Local\Temp\VPIDS111\jobs\6" -x |
IDA 静态分析 ptcl.exe,搜索
VPICRYPTV2,寻找到一个字符串,发现字符串初始化结构:
int sub_140021720() |
这是一个字符串对象的初始化过程,atexit
是销毁逻辑,初始化时将固定位置的字符串复制到这一对象的数据区,IDA
中暂时标注为 VPICRYPTV2_string 对象;
接下来,寻找对象引用,按 x
找交叉引用,能够找到两个主要使用点,有一个较短,再向上找引用是
check_header
的逻辑,显然,并无解密逻辑;另一个伪代码较长,后续流程较为复杂,应该是要排查的内容:
if ( (unsigned int)compare_string_objects(v60, &VPICRYPTV1_string) |
上来就是一个字符串比较,清晰明了,进入这一分支的是解密流程,跳过压缩文件相关的逻辑,要看的是
VPICRYPTV2 解密逻辑:
v50 = 0; |
排查这些步骤中的函数,标注了
get_VPICRYPTV2_key_string,其内部逻辑执行了混淆的 blowfish
秘钥构建,密钥是由 off_141A4A5E8
位置的三个字符串交叉生成的,这三个原始字符串似乎是开发人员随手复制的,例如
Error: no file existing,重新混合组合成二进制秘钥字符串,下面是偏移处的数据:
.rdata:0000000141A4A5E8 off_141A4A5E8 dq offset aErrorNoFileExi |
不过,这里的逻辑有些难以辨识,这一
get_VPICRYPTV2_key_string
函数给了一个明确的返回值,考虑直接动态调试查看秘钥情况。
由于 ptcl.exe 由 pde.exe
界面程序创建进程,先调试
pde.exe,断点设置在创建进程相关接口:
bp CreateProcessW; |
和 ptcl.exe 相关的执行停止在
CreateProcessW,对于 CreateProcessW
有六个参数:
在 x64 调用约定中,前 4 个参数在寄存器(RCX, RDX, R8, R9),后面的参数在栈上。
RCX: lpApplicationNameRDX: lpCommandLineR8: lpProcessAttributesR9: lpThreadAttributesRDX 指针位置的数据的确是 ptcl.exe
相关的命令行指令,从 System Informer 可以看到环境变量和运行参数,使用
x64dbg.exe 启动 ptcl.exe 开始调试:
$env:BNROOT = "C:\Program Files\VPI\VPIdesignSuite 11.1\simeng" |
x64dbg 跳过 pde.exe 的 CreateProcessW
进程创建,IDA 内找到 ptcl.exe 的断点在 ret 之前:
.text:000000014124094C cmp rsi, 3 |
例如 .text:0000000141240966 地址,由于 IDA
静态时默认起始地址 0x140000000,对应的实际运行时断点位置为
ptcl.exe + 0x14124F5D2 - 0x140000000;
x64dbg 下 Ctrl+G 在 x64dbg.exe
中导航到这一地址位置设置断点,F9 继续运行
ptcl.exe 和 pde.exe 使二者正常完成初始化 HTTP
通信,开始读取文件和解密(bp fopen
会无法初始化,还是需要手动设用户段的断点):
00007FF7A9D90935 | 48:FFC3 | inc rbx | |
停止在 00007FF7A9D90966
(IDA内0x141240956附近),RAX 内部地址为
000000FEC42FE638,是函数返回的目标字符串对象指针,RAX
内存镜像 dump 000000FEC42FE638:
000000FEC42FE638 50 B9 FC 43 D2 02 00 00 31 34 3A 33 32 6E 00 00 P¹üCÒ...14:32n.. |
前 8 字节为
50 B9 FC 43 D2 02 00 00,转换成地址是:0x000002D243FCB950,这是数据指针;偏移
16 字节(+0x10)处为
52 00 00 00 00 00 00 00,十六进制 0x52 =
十进制 82,说明 Blowfish 算法使用的密钥长度是 82 字节。
去数据区寻找秘钥 dump 0x000002D243FCB950:
000002D243FCB950 36 39 72 31 31 34 6F 31 31 34 3A 33 32 6E 31 31 69r114o114:32n11 |
可以看到秘钥为
69r114o114:32n111 102i108e32e120i115t105n10380r111f105l101 98a10068i115k32f117l108
看起来仍然是混淆的,这并不是个好现象,不过秘钥指针传递到了 v24,一定是被后续使用到。
继续静态分析,看后续代码
blowfish_start_decrypt,v24
传递给函数第四个参数,函数内 a4 就是这一秘钥字符串:
// Hidden C++ exception states: #wind=3 |
sub_14123DBC0 使用 a4 字符串赋值给了
v13,v13 在下一个
blowfish_decrypt_init 函数中被处理为 Blowfish 解密需要的
S-Box 和
P-Array,存入a1 + 0x68(104)和a1 + 0x24(32):
void __fastcall blowfish_decrypt_init(__int64 a1, _QWORD *a2) |
之后 blowfish_crypt_exec 使用了 a1 中储存的
S-Box 和 P-Array,进行分块解密,返回值 v11
为数据长度,这是最终解密操作的核心:
v14 = blowfish_crypt_exec((_DWORD)a1, a2, 4, *a3 - 4, (__int64)v12, 0); |
数据写入到前面申请的地址空间
v12 = (void *)operator new(*a3 - 4);,v12
作为第五个参数传递到函数中:
__int64 __fastcall blowfish_crypt_exec(_DWORD *a1, int a2, int a3, int a4, __int64 a5, int a6) |
因此,在上一级函数 blowfish_start_decrypt动态调试,返回
v12 前打一个断点,读取 v12(R12)
即为解密后的数据,a1(RDI) + 0x68 和
a1(RDI) + 0x20 的两个偏置位置则为 S-Box 和 P-Array
数据,现在继续动态调试:
00007FF7A9D9F6E0 | E8 BBF1FFFF | call ptcl.7FF7A9D9E8A0 | |
停止在 00007FF7A9D9F6F6,对应 IDA 的
.text:000000014124F6F6,做内存转储
dump R12,直接报错解密后数据即可。
查看对应的 RDI + 0x20 和 RDI + 0x68
地址位置的 S-Box 和 P-Array,RDI + 0x20 可以直接转储,18 个
P-Array 秘钥连续储存,RDI + 0x68 的 S-Box
数据是字符串对象,同理找到其数据区地址
0x000000FEC42FE760:
RDI + 0x20 |
获取数据区内容 dump 0x000000FEC42FE760:
000002D244557290 F4 18 A3 4C 20 78 81 CD 81 D1 D8 91 10 E1 E9 F2 ô.£L x.Í.ÑØ..áéò |
有了 S-Box 和 P-Array,也可以手动做 Blowfish ECB 解码获取数据
import struct |
这样就能离线解码出目标文件了:
Header: b'VPICRYPTV2' |
总之这一代码库的加密部分,使用 Blowfish ECB 已经是加密的老一辈了,而且代码库也没有更多的做混淆或检测;寻找解密部分还算顺利,这次就说到这吧
拉取 Redroid
(root) 容器,并寻找到其网卡名称,如 br-207943f7b031
安装 mitmproxy,可选 redirect.py 来控制流,在 8888 端口启动即可
transparent --listen-host :: --listen-port 8888 -s redirect.py |
加载 Netfilter NAT 相关的内核模块:
sudo modprobe nf_nat |
设定一个 chain,引导流量到 8888
# IPv4 HTTP |
检查 rulelist:
sudo nft list ruleset |
应存在引导流量的 chain ,在 ipv4 ipv6 中,各有一个:
chain mitmproxy_prerouting { |
添加跳转规则:
# IPv4 jump rule |
这样,透明代理构建完成
为了捕获 HTTPS 流,需要将 Android 设备的系统证书做修改,信任 mitmproxy 的证书
确保证书文件存在于主机路径:~/.mitmproxy/mitmproxy-ca-cert.cer
如上证书,安装 mitmproxy 运行之后应当出现
使用 adb ,将证书推送到 Redroid 容器的临时目录:
adb push ~/.mitmproxy/mitmproxy-ca-cert.cer /data/local/tmp/ |
通过 ADB 连接到容器:
adb shell |
在容器内执行以下命令,获取证书的哈希值(用于重命名文件):
# 进入临时目录 |
利用 Magisk 的 root 权限将证书复制到系统证书存储:
# 挂载系统分区为可读写 |
系统分区的 mount 可能存在不同,查看 df,根据 Android
版本选择分区修改
重启容器,使证书生效:
reboot |
PaddlePaddle AiStudio 平台提供 V100 GPU,然而默认只有 PaddlePaddle
百度自有框架,Conda 环境不独立,使用 external-libraries
文件夹存在路径缺陷,所以这里记录改环境到 Torch
的过程,包含一些语音处理包的安装。
目标环境:Python 3.11 + Torch 2.5.1 cu118
在 GTNH 2.7.2 上,对于 Journey Map 传送点的支持需要 op 管理员权限,给管理员权限容易刷物件和其他意外,所以需要限制权限同时又能方便移动。
系统带有工具 switcherooctl 可以切换系统使用的 gpu
卡,若默认连接显示到集成显卡,使用此工具时,将使用独显运行应用
若无法运行,可以使用 vkcube 来检查 Vulcan
的可用性,工具来自于包 vulkan-tools 直接 apt 安装即可。
可能出现无法调用情况如下:
vkEnumerateInstanceExtensionProperties failed to find the VK_KHR_surface extension. Do you have a compatible Vulkan installable client driver (ICD) installed? Please look at the Getting Started guide for additional information. |
此时需要检查 icd 文件是否可用
ls /usr/share/vulkan/icd.d/nvidia_icd.json |
若无文件,则需要安装库
sudo apt install vulkan-tools libvulkan1 libvulkan1:i386 |
若安装后仍不可用,检查是否有 Nvidia-wrapper
$ ls /usr/share/vulkan/icd.d/ |
恢复配置:
sudo mv /usr/share/vulkan/icd.d/nvidia_icd.disabled_by_nv_vulkan_wrapper /usr/share/vulkan/icd.d/nvidia_icd.json |
此时再切换独显运行可行。
注意,我并未发现哪部分程序给出了
nv_vulkan_wrapper,若可行,从添加 wrapper
的程序关闭可能更合理
在解决 icd 问题后,环境变量也可以用作切换运行的方式,毕竟
switcherooctl launch 的原理本质也是切换环境变量。
例如使用 Steam 的 Proton 9.0-4 运行 Windows
应用,独显启动:
先要确保手动在 Steam 中添加可执行文件到游戏库中,并在命令行启动的
Steam 上获取其编号,或者在集显运行时查看其环境变量中的
SteamGameId
之后,为应用添加必要的环境变量,无需启动 Steam 即可运行应用本体
注意,最好替换 SteamGameId ,这是 Steam
做运行环境隔离的措施,使用程序自己的环境运行更稳定
此外,一定要替换 proton 的运行路径,我的路径只是
Proton 9.0 配置,随着版本更新,指令会有所不同。
此指令从 ps -aux | grep proton 获取,对于后续 Steam 启动
proton 的指令,可以通过如上方案获取启动指令模版。
__NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia __VK_LAYER_NV_optimus=NVIDIA_only VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/nvidia_icd.json SteamGameId=2907931734 STEAM_COMPAT_DATA_PATH=$HOME/.local/share/Steam/steamapps/compatdata/$SteamGameId STEAM_COMPAT_CLIENT_INSTALL_PATH=~/.local/share/Steam/ python3 "$HOME/.local/share/Steam/steamapps/common/Proton 9.0 (Beta)/proton" waitforexitandrun "/media/zsig/生活日常/Games/Grand Theft Auto V/GTA5.exe" |
Wine + dxvk 手动运行也可以,例如使用 jadeite 启动 Unity 程序
DXVK_HUD="fps,frametimes,version,gpuload" GST_PLUGIN_PATH="" HOST_LANG="zh_CN.UTF-8" HOST_LC_ALL="zh_CN.UTF-8" LANG="zh_CN.UTF-8" LC_ALL="zh_CN.UTF-8" LD_LIBRARY_PATH="/home/zsig/.var/app/moe.launcher.the-honkers-railway-launcher/data/honkers-railway-launcher/runners/wine-10.1-staging-tkg-amd64/lib:/home/zsig/.var/app/moe.launcher.the-honkers-railway-launcher/data/honkers-railway-launcher/runners/wine-10.1-staging-tkg-amd64/lib/wine/x86_64-unix:/home/zsig/.var/app/moe.launcher.the-honkers-railway-launcher/data/honkers-railway-launcher/runners/wine-10.1-staging-tkg-amd64/lib/wine/i386-unix" WINEARCH="win64" WINEFSYNC="1" WINEPREFIX="/home/zsig/.var/app/moe.launcher.the-honkers-railway-launcher/data/honkers-railway-launcher/prefix" WINE_FULLSCREEN_FSR="1" WINE_FULLSCREEN_FSR_MODE="balanced" WINE_FULLSCREEN_FSR_STRENGTH="2" JADEITE_ALLOW_UNKNOWN=1 bash -c "'/home/zsig/.var/app/moe.launcher.the-honkers-railway-launcher/data/honkers-railway-launcher/runners/wine-10.1-staging-tkg-amd64/bin/wine64' '/home/zsig/.var/app/moe.launcher.the-honkers-railway-launcher/data/honkers-railway-launcher/patch/jadeite.exe' '/home/zsig/Documents/SkyLines/Cities - Skylines II/Cities2.exe' -- " |
I2CDevLib仓库
选用Linux上驱动I2C和MPU6050的代码,克隆LinuxI2CDev文件夹到本地,然后进入到文件夹中,创建一个main.cpp用来创建与Python的函数接口,可以自定义。这里的代码没有考虑零偏,只是从DMP取出四元数换算得到结果的,实际用的时候有不小的零偏,可以添加上初始化时的零偏纠正过程。
#include <stdio.h> |
配置CmakeLists.txt,当然,在配置成动态链接库之前,可以编译成可执行程序验证正确性。
cmake_minimum_required(VERSION 3.10) |
创建build文件夹,进入build文件夹,执行cmake ..,然后make,即可看到libmpu6050_lib.so,记住路径,或者移动到python代码同目录下
使用如下代码,可以加载并自动获取yaw, pitch, roll角度。这里使用单独的线程,因为不连续读取时会出现错误数据,原因未知。
# 加载动态链接库 |
检查RFkill列表,蓝牙hci设备是否被Block
rfkill list |
$ rfkill list |
若blocked yes则
rfkill unblock 3 |
若需要恢复关闭状态节约电源,使用block命令即可
sudo hciconfig hci0 piscan |
若有需要可以进入bluetoothctl管理,使用discoverable on、pairable on来开启蓝牙
以L2CAP协议传输数据为例
import bluetooth |
客户端在配对后连接
import bluetooth |
即可正常传输数据
前提:有转换好Onnx模型
git clone https://github.com/airockchip/rknn-toolkit2.git --depth 1 |
应该有如下文件夹,RockChip合并了之前分散的项目,并且在此代码库更新新内容,旧仓库不再更新
├── rknn-toolkit2 |
先要在个人主机(非板端),根据rknn-toolkit2/packages中的版本号来选择python版本,并且安装对应版本的rknn-toolkit2
注意,随着rknn-toolkit2的更新,版本铁定会不同,看好版本号
例如主机Ubuntu22.04.4(x86_64),packages文件夹内有
. |
使用conda创建环境
conda create -n rknn python=3.10 |
激活环境
conda activate rknn |
安装依赖和本体
pip install -r requirements_cp310-2.1.0.txt |
此时已安装完成rknn-toolkit2
任意创建文件夹,例如
mkdir onnx2rknn |
将转换代码写入到 onnx2rknn.py
from rknn.api import RKNN |
更改需要输入的onnx模型和输出rknn模型位置
与主机端同理,到packages文件夹下,创建conda环境,安装指定包
. |
创建
conda create -n rknn python=3.10 |
安装rknn-toolkit-lite2和opencv
pip install rknn_toolkit_lite2-2.1.0-cp310-cp310-linux_aarch64.whl |
pip install opencv-python |
测试运行
cd ../examples/resnet18 |
可能会报错
W rknn-toolkit-lite2 version: 2.1.0 |
若出现同样的错误,那么应该Rknpu2的lib等在板端是古董级别的,需要更新下
首先到方才克隆的根目录中rknpu2/runtime文件夹下,复制需要的文件
sudo cp Linux/librknn_api/aarch64/librknnrt.so /usr/lib/librknnrt.so |
另一个librknn_api.so不需要更新
然后应该能正常跑test.py了
W rknn-toolkit-lite2 version: 2.1.0 |
示例代码如下
import time |
ps. 转换似乎有不少问题,推理不出来正确结果,不过流程确实是这样,或许是onnx模型用到了特殊的算子,转换有失误
ssh-keygen -a 1000 -t ed25519 |
cat id_rsa.pub >> authorized_keys |
拉取id_rsa文件到本地(没有.pub),使用openssh登录
ssh -i C:\Users\Lenovo\.ssh\id_rsa [username]@[hostname] -p 22 |