Skip to content

feat(auth): recover the attach-instance launch token from process memory - #1

Merged
masknull merged 3 commits into
mainfrom
feat/attach-token-memory-recovery
Sep 16, 2026
Merged

masknull merged 3 commits into
mainfrom
feat/attach-token-memory-recovery

Conversation

@masknull

@masknull masknull commented Sep 16, 2026 •

Copy link
Copy Markdown
Owner

背景

attach(复用已有 dsh web 实例)时,壳没有自己的子进程控制台尾可读,拿不到 launch token —— 于是只要 401 认证墙出现,就只能弹窗问用户「是否重启接管」。

本次改成:先从运行中进程的内存里取回令牌并验证,失败才回退到原弹窗。

做法

新增 src-tauri/src/memtok.rs(纯 kernel32 裸 FFI,不加依赖;与 lib.rs 手写 user32 同风格):

  1. 枚举监听 3080 那个进程的可读内存区
  2. 收集候选:先只认打印行 token= 锚点之后的 43 字符,再退到「43 字符 base64url 的极大串」,附加尾字符约束 AEIMQUYcgkosw048(43 字符编码 32 字节时末位只承载 4 bit,是长度带来的数学必然,不是对内容的猜测)
  3. 逐个候选走 HTTP 交换,只有回 303 且签发 dsh-auth-* cookie 的才算数(形状永不作为证据)

四遍递进:锚点(跳 MEM_IMAGE)→ 极大串(跳 MEM_IMAGE)→ 极大串(全量)→ 极大串(去掉尾约束兜底)。

实测基线(决定了两个性能设计)

用参考 PowerShell 扫描器在本机真实的 3080 实例上实测:

pass 1 (skip images) : regions=1658 skippedImages=369 scanned=592.5MB skipped=161.0MB candidates=656 probed=649 wall=2.5s probe=2.7s earlyStop=yes

两个数字直接影响了实现:

  • 裸扫描出 656 个候选,而锚点法只有个位数 → 所以锚点优先(第一遍命中时总成本降到一次内存遍历)
  • 真令牌落在第 649 次探测 → 所以探测必须8 路并行,串行会让 HTTP 往返成为主要开销

两种扫描不是包含关系(令牌后面紧跟 base64url 字节时它就不是极大串),所以锚点法不能取代裸扫描,只能排在前面。

安全

  • 只读(ReadProcessMemory),不写目标进程内存
  • 令牌明文绝不写日志 —— 不落盘正是这套认证的设计前提;日志只记扫描统计(区域数/跳过映像/读取量/候选数/探测数/耗时/是否早停)

验证状态

本机没有 Rust 工具链,未做本地编译;编译由本 PR 的 CI 构建校验(windows-latest + pnpm tauri build),首个 commit 的构建已经通过。

运行期行为(能否真的扫到令牌)在本机无法验证——这是纯 Windows FFI 路径。算法逐条对齐了已在真实实例上验证过的 PowerShell 参考实现,上方基线数据即来自它。

CI 改动

build-shell.yml 增加 pull_request 触发:PR 只构建并上传 exe 以校验可编译;版本递增与 Release 发布限定在 main(if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main')。

deepseek added 3 commits September 16, 2026 22:29
An attached dsh web has no console tail of ours to read, so reusing a running
instance used to end at the restart-and-take-over prompt as soon as its 401
auth wall appeared. The shell now reads the launch token back out of the
listener process and navigates with it.

- new src-tauri/src/memtok.rs: raw kernel32 FFI, no new dependency; harvests
  maximal 43-char base64url runs plus the 32-byte tail-character constraint;
  three passes (skip MEM_IMAGE, then all memory, then all memory without the
  tail filter); 4MB chunks read by min(CPU,8) workers off an atomic range
  queue; probes every 16 candidates with early stop. A candidate is accepted
  only after a real HTTP exchange (303 + dsh-auth-* Set-Cookie) - shape is
  never proof
- dsh.rs: check_auth_wall_now runs the recovery before the takeover prompt;
  at most two scans per run and the same token is never navigated twice, so
  an intercepting front-auth plugin cannot turn into a scan loop
- the token never reaches the log: not landing on disk is the whole point of
  this auth
- CI: build-shell.yml also runs on pull requests to prove the change
  compiles; the version bump and the release stay restricted to main
Measured on this machine's live 3080 instance with the reference PowerShell
scanner: an unanchored whole-memory pass yields 656 candidates and the real
token sat on the 649th probe, while the anchored pass (the printed URL's
token=) sees single-digit candidates. Both numbers are now part of the design.

- pass 1 scans only what follows the token= anchor; the unanchored passes stay
  as the reliable fallback, because the two scans are not subsets of each other
  (a token followed by base64url bytes is not a maximal run)
- probe batches run on 8 lanes with an early stop, so a whole-memory pass no
  longer spends most of its time on serial HTTP round-trips
- CHANGELOG records the measured baseline
@masknull
masknull merged commit faa35c6 into main Sep 16, 2026
1 check passed
@masknull
masknull deleted the feat/attach-token-memory-recovery branch September 16, 2026 14:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant