← 返回首页
VIRTUALDESKTOP · 逆向工程档案
0x000000patch offline_lan(image *apk, locale zh_CN)

在 .NET IL 字节层
实施精确修改

VirtualDesktop 是一项离线 LAN 串流恢复工程。当前主线是 1.34.22.0(66 处 IL 补丁 / 5 个程序集,含水印 C2acked by dwgx); 1.34.19.01.34.18.0 的切片保留为谱系真源。项目不修改网络栈、不更改 DNS、不伪造任何平台凭证, 而是在 .NET IL 字节码层实施精确修改:定位并中和应用内的反篡改校验指令, 使运行时回退至 JIT 执行补丁后的 IL,从而在无外部网络的环境下恢复局域网串流。 Windows 端工具 VDH 与 APK 分开发布;头显包分 EN / ZH 两份,里面不带 VDH。

Keyboard.LoadContent — Graph ViewIDA · arm64
IDA View-AHex View-1Pseudocode-AStructures
ldfld_invalidHash brtrue.sIL_01E4 ; anti-tamper trap callvirtCanScroll callvirtFinish() ⟶ self-destruct ; patched (NOP×13) nop nop nop ... br.sIL_01E4 ⟶ continue ; dead code throw NullReferenceEx IL_01E4: ldarg.0 LoadContent ✓ ret 键盘正常加载,不自毁
0
处 IL 字节级补丁(22.0)
0
个 AOT .so 移除
0
字节 blob 逐字节重组
0
份实验报告复盘
CORE STRATEGY · 核心策略
0x0001A4void strip_aot_force_jit(image *apk) ; 168 × libaot-*.so

剥离 AOT 层,强制执行回退至 JIT

APK 里藏着两层代码:运行时优先执行的 AOT 机器码,和可被改写的 IL 字节码。 AOT 在运行时覆盖托管 IL —— 即使你改了 IL,机器码也照旧执行,JADX 这类工具更是只能看到 Xamarin 桥接层。 我们的解法不是去逆机器码,而是移除全部 AOT(22.0 为 172libaot-*.so),逼运行时回退到 JIT,让它执行我们补丁过的 IL

Layer A · 168 × libaot-*.so

AOT 机器码层(已移除)

AOTJIT-blocknative overridearm64precompiled opaque×172
Layer B · libassemblies.blob.so

IL 字节码层(补丁后执行)

NOPbr.sldc.i4.0 rettiny/fattoken code_size×66
▼ 删 AOT → 触发 JIT 回退 → 执行补丁后的 IL ▼
.NET 9Xamarin / MAUIXenko/Stride 引擎 ELF WrapperXABA StoreXALZ + LZ4HC Mono AOTOpenXRarm64-v8a
OUR APPROACH · 我们的路子
0x000A30bool patch_target_process(proc *p) ; not the network stack

修改目标进程,而非主机网络栈

本项目与常见做法存在本质区别:网络上流传的"汉化整合包"普遍采用 修改 hosts 劫持鉴权域名 + 安装原版 APK 的方式,改变的是整台头显的网络环境,应用本身未作任何修改。 本项目则相反:仅修改应用自身的字节码,不触碰任何网络配置。两种方式的对照如下:

他们 · hosts 劫持

改整机 DNS、把鉴权域名指到写死 IP、黑洞云端,装的是未补丁原版 APK。本质是绕过网络验证。

我们 · IL 字节补丁

在 .NET IL 层做精确手术,把自毁指令 NOP 掉,不碰 DNS、不重定向任何域名、完全可逆、全开源。

BUILD PIPELINE · 三段式管线
0x001E00image rebuild(blob *in, patch[] set) ; 13,336,408 bytes

解构 13,336,408 字节映像,逐字节修补并无损重组

没有任何现成工具能改这种 ELF → XABA assembly store → 每个程序集 XALZ/LZ4 压缩的 .NET PE 的多层封装。 所以我们自己写了三段式管线,逐字节解构再无损重组。

build_zh.py — 三段式无损重组13,336,408 B → signed APK
ELF + XABA
解析 / 切片
13,336,408 B 输入
.NET PE ×N
IL 补丁 / 重压
LZ4 lvl-12 · 防 OVERFLOW
no-AOT APK
回填 / 签名
−168 .so · v3+v2
01
EXTRACT + PATCH
binary_patch.py

解析 ELF→定位 XABA 标记→按 28 字节描述符切片→LZ4 解压还原 .NET PE; 再解析 PE section、RVA→文件偏移,逐方法读 IL header(tiny/fat),打字节级补丁后以伪装名写回。

02
REPACK STORE
repack_blob_v102.py

LZ4 high-compression(lvl 12) 重压每个补丁后 DLL,逐条比对原压缩大小防 OVERFLOW, 重建全部 XALZ 记录与描述符表,按体积增量修正 ELF 的 e_shoff 与 section sh_size

03
REBUILD APK
build_v12_no_aot.py

剔除旧签名与全部 168 个 libaot-*.so,补丁 blob 以 ZIP_STORED 回填(mmap 加载要求), zipalign 4 对齐,apksigner v3+v2 签名。18.0 产出 signed_v12_no_aot.apk; 22.0 对位脚本是 build_v22.py;19.0 是 build_v19.py / build_zh_v19.py

VERSION LINEAGE · 18.0 → 19.0
0x002190map retarget_by_name(module *m) ; never trust Entry#

版本一变,偏移全变 —— 定位必须按程序集名字扫描

上方 CFG 与导航条上的 44 处黄色刻度是 1.34.18.0 的教学切片(5 个程序集,含把 Xenko.OpenXR.dll::Kill() 打成 ret)。 当前主线 1.34.19.0 把同一套语义迁到新 blob:entry 181→185、索引全偏移、官方删掉那个空壳 Kill(), token 0x0A0002F1 变成真 Process.Kill()。补丁数变成 56 / 4 个程序集。硬编码 Entry# 或 RVA 跨版本即崩。

程序集1.34.18.01.34.19.0
OpenTK-1.1 / EglContext#43#42
VirtualDesktop.Mobile#52#51
Xenko.OpenXR Kill()#49 ret已删除 · 改 NOP 自毁 call
Xenko.VR ReleaseVR#61#60
VrApp / Path C 昵称#81 Shared#82 Android
19
EXTRACT + PATCH
binary_patch_19.py

按 module name 定位 4 个 PE,56 处带字节断言的 IL 补丁。码率上限 500→960。异步状态机改 MoveNext,不在 55 字节 wrapper 里硬塞。

PC
RUNTIME NICK
pathc_v19.py

Path C:运行时读 /sdcard/vd_account.txt,缺文件走烤入默认。一份签名 APK 发给不同人,改昵称不重签。

CI
INSTALL BAT
build_install_scripts.py

EN/ZH 安装脚本单一生成源。cmd 块语法只在真 Windows 上爆;bat_test/ + GitHub Actions 52 用例跑真 cmd,不再靠静态审查。

VERSION LINEAGE · 19.0 → 22.0
0x002280map discover_lan_udp(port 38850) ; DualMode [::]

1.34.22.0:局域网发现在 Net.dll,水印从 18.0 接回来

22.0 blob 仍是 185 entries,Mobile/OpenTK 序号没再漂,但 发现协议在 #53 VirtualDesktop.Net.dll。 Streamer 听的是 IPv6 DualMode [::]:38850,不是 0.0.0.0。Quest UdpClient(38850) 绑死端口会空列表。 水印 C2acked by dwgx / C2rcked by dwgx 按 18.0 的 48e/48f 写回电脑页警告栏。 Path C(/sdcard/vd_account.txt)在 22 上打坏 MemberRef,冻结包禁用。 头显包与 Windows 端 VDH 分开:APK zip 不带 VDH;VDH 走独立仓库做检查更新。

程序集1.34.19.01.34.22.0
OpenTK-1.1#42#42
VirtualDesktop.Mobile#51#51 · 544256B
VirtualDesktop.Net FindComputers未补#53 临时 UDP + ff02::1
Xenko.OpenXRKill 已删#59 face-tracking → ret
Xenko.VR ReleaseVR#60 → ret#60 禁止 ret(每帧 CreateHmd)
VrApp PAT / 水印#82 Path C#82 烤入 dwgx · 48e/48f
22
EXTRACT + PATCH
binary_patch_22.py

66 处 IL。出货码率上限 500(不再烤 960)。Kill token 0x0A0002F7。#53 发现。假 PAT 只许 MoveNext ldflda+SetResult。加码率走 VDH Write IL。

UDP
LAN PROBE
probe_lan_38850.py

AesStream 帧 + OculusQuest=1 + 账户名。本机探测 1344B 回复即 Streamer 局域网活着。

ZH
LOCALIZE
localize_v22.py

localize_v22.py + bundle 汉化 + NotoSC(Verdana32 保持 Offline)。ZH APK SHA 39C52DF5…。上限同样 500。

NOTES · 2026-08-28 现场
0x0022F0log session_notes(quest, streamer, vdh)

最近做了什么、哪些坑已经钉死

下面是 22.0 真机 LAN 通了之后的动作,不是计划。GitHub Pages 这份档案跟着仓库 docs/about.html 走。

动作结果 / 不要再踩
08-28Quest 1.34.22.0 LAN 冻结包真机电脑列表可用。EN SHA B0604A84…712A 上限 500。签名 56464486。HorizonOS 只点「打开应用」,点「恢复」会装回商店 10704。旧包 8DEEF4FF 曾烤 960,已作废。
08-28#53 FindComputersAsyncAndroid 绑死 UDP 38850 会空列表。改临时端口 + 强制 ff02::1。Streamer 听的是 DualMode [::]:38850
08-28水印 48e/48f电脑页 C2acked by dwgx / C2rcked by dwgx。发现路径仍跳过 Wi-Fi/Meta abort。
08-28pathc_v22.py禁跑。Computers tab:Memberref parent unknown: class: 6, index 1。昵称烤在 #US 0x1220 dwgx
08-28分发 README 乱码PowerShell here-string 把 UTF-8 中文编坏。现改为仓库里的 UTF-8 源文件拷进 zip。install_zh.bat 仍是 GBK + chcp 936
08-28VDH 0.4.7C# net48 WinForms。仓库 dwgx/VirtualDesktopHelper tag v0.4.7。Write IL 改 APK 码率上限。LZ4 打进 exe。OTA 读 VERSION.txt + Release SHA-256。
08-28APK 与 VDH 拆包EN / ZH 头显 zip 不带 VDH。选文件夹安装时只认已知 SHA;不是我们的包会警告。
account dwgx UDP 38850 AesStream no Path C on 22 no VDH inside APK zip
SURGICAL IL PATCHES · 字节级手术
0x0001E4il neutralize_trap(opcode brtrue_s) ; → br.s

一处 brtrue.s → br.s 即中和一个反篡改自毁陷阱

补丁的核心原则:将触发自毁逻辑的调用替换为 NOP 指令使其失效,同时不生成任何伪造凭证。 下方为键盘组件反篡改校验的实际修改过程——原逻辑在签名校验失败时调用 Activity.Finish() 终止进程:

Keyboard.LoadContent @0x15868 — IL diff
✗ 原始 IL(会自毁)
IL_0042 callvirt CanScroll // _pnCapsLock=null IL_0047 brtrue.s IL_0060 IL_0049 callvirt Activity.Finish() IL_004e throw / kill
✓ 补丁后(继续运行)
IL_0042 nop nop nop // trap 中和 IL_0047 br.s IL_0060 // 无条件跳过 IL_0049 nop nop nop nop nop IL_004e // 自毁已移除

下面这张表是 1.34.18.0 教学集:44 处补丁分布在 5 个程序集。1.34.19.0 的对应关系见上一节版本谱系(56 / 4, Kill 空壳已不存在)。

程序集(伪装名)作用域补丁分布
VirtualDesktop.Mobile
→ Xenko.dll
主逻辑 / 游戏循环 / ControlPanel / 虚拟键盘27
VirtualDesktop.Mobile.Shared
→ ...TypeConverter.dll
签名 / entitlement 自检14
VirtualDesktop.Net
→ OpenTK-1.1
EGL 上下文 MakeCurrent(surfaceless)1
Xenko.OpenXR
→ VD.Core
进程管理 Kill()1
Xenko.VR
→ Xenko.Native.dll
VR 硬件 OpenXRHMD.ReleaseVR1
@0x15868Keyboard::LoadContentanti-tamper

反篡改 trap 绕过:_invalidHash 触发 NullRef trap,签名校验失败后调 Activity.Finish() 自毁。以控制流改写 + 字节填充双重中和。

brtrue.s → br.sNOP×13throw 移除
@0x0A3F0OpenXRHMD::MakeCurrentlifecycle

渲染生命周期重连:构造 surfaceless EGL context(WindowInfo.Surface → IntPtr.Zero),放行 xrCreateSession,强制 _isPaused=false 维持 GameLoop 持续 Tick()

ldc.i4.0 → _isPausedsurfaceless EGL
@0x1C2D4ControlPanel::RecreateCylindergeometry

菜单不可见根因定位:离线模式下 streamingSource=0 致 cylinder 尺寸归零。NOP 掉条件门,几何重建恢复,菜单重新渲染。

条件门 NOPcylinder 重建
specECMA-335 · IL 重写约束invariant

IL 字节级不变量:同步 Fat header 的 code_sizeret 后 NOP 填充、清空 SEH 异常表、精确改写 metadata token —— 任一错位即 InvalidProgramException

code_size 同步SEH 清空token 改写
FEASIBILITY PROOF · 可行性论证
0x002C18bool lan_admit(account *a, peer *b) ; no platform creds

LAN 准入基于账号匹配,无需伪造平台凭证

通过对 PC Streamer(dnSpy 反编译)与 APK 侧交叉分析得出关键裁决:LAN 准入门是 账号字符串匹配 + AES 对称加密,而不是平台签名校验。所以把自毁 NOP 掉就够了,根本不必造假 token。

lan_handshake.pcapng — Quest ⇄ PC Streamer捕获 6 帧
No.SourceDestinationProtoInfo
1Quest255.255.255.255UDP:38850 → broadcast · Discover (RSA pubkey + Platform=1B + AccountID)
2PCQuestUDP:38850 · Offer — 账号字符串匹配命中 owner[2/5]
3QuestPCTCP:38810 · SYN — 建立直连会话
4PCQuestTCP:38810 · ConnectionID / AES Key / IV 下发
5QuestPCAES对称加密直连建立 · 无 UserProof · 无 entitlement
6PCQuestAESH.264 串流帧 :38810-38840 · 会话直连成功

发现包里没有 UserProof

UDP 发现包只含 RSA 公钥串、1 字节 Platform、UTF-8 AccountID —— 不含任何 Meta entitlement,不含 UserProof

UserProof 只在云注册路径

它仅用于 vrdesktop.net/ComputerRegistry 的注册调用。离线 LAN 完全绕开该路径,因此补丁只需使应用在校验失败时不再触发自毁逻辑。

LOCALIZATION · 简体中文汉化
0x0034F0void rewrite_strings(bundle *ui, locale zh_CN)

重写引擎 bundle 内的 UI 字符串资源表

界面文本不在 APK 的 Android 资源里,而是打包进 Xenko/Stride 引擎的私有 bundle (assets/data/db/bundles/default.bundle)。我们在补丁管线里多挂一步:把汉化后的两个 bundle 以 STORED 原样回填,出一个可直接安装的中文 APK —— 补丁与汉化一次成型,同一把签名。

Hex View — default.bundle · String TableUTF-8 · zh_CN
原始字符串
Settings
8 bytes · ASCII
汉化后
设置
6 bytes · UTF-8 · 同槽回填
GET INVOLVED · 了解更多
0xFFFFFFrepo open_source(void) ; full history

完整工程史已开源

补丁脚本、反编译参考、126 份实验报告复盘的完整决策链,以及这个站点的离线汉化构建流程,都在仓库里。