V2版本:支持自定义安装目录

使用方法:将激活工具copy到typora.exe同目录后使用

Typora 1.13.6 离线激活分析

任务摘要

逆向分析 Typora 1.13.6 离线注册机制,实现离线激活、ASAR 完整性绕过、网络验证拦截和多开支持。

当前阶段

COMPLETED — 全部目标达成,验证通过。

目标架构分析

应用类型: Electron 应用 (Chromium + Node.js) 安装路径: C:\Program Files\Typora\

关键文件

文件 说明
Typora.exe Electron 壳程序,含 ASAR 完整性校验
resources/app.asar 主应用 ASAR 包
app.asar → atom.compiled.dist.jsc V8 字节码,379,904 字节,含全部业务逻辑
app.asar → launch.dist.js 启动加载器,加载 V8 字节码
resources/page-dist/ 前端页面(许可证、偏好设置等)

ASAR 包格式

双 pickle 格式:[4: outer_size] [4: inner_total] [4: inner_data] [4: string_len] [JSON] [pad] [data...]

Electron Fuses

Fuse 状态
RunAsNode Disabled
EnableNodeCliInspectArguments Disabled
EnableEmbeddedAsarIntegrityValidation Disabled(已 patch)
OnlyLoadAppFromAsar Enabled

保护机制分析

T1 - ASAR 文件完整性校验(EXE 层)

位置: Typora.exe 函数 sub_1403BCAF0ValidateIntegrityOrDie 绕过: NOP 两个条件跳转 — 偏移 0x3BC1D8 (6-byte NOP) + 0x3BC203 (2-byte NOP)

T2 - launch.dist.js SHA256 完整性校验(V8 字节码层)

机制: atom.compiled.dist.jsc 通过 crypto.createHash('sha256') 计算加载的 launch.dist.js 内容哈希,与编译时嵌入的期望值比较。校验失败时输出 "checksum failed" 并静默退出(exit code 0)。

关键发现: 原始 launch.dist.js"use strict"; 开头。从 ASAR 提取后需判断是否包含此前缀,避免重复拼接导致 hash 不匹配:

if ld_orig.startswith('"use strict";'):
    orig_hash = sha256(ld_orig)      # 直接计算
else:
    orig_hash = sha256('"use strict";' + ld_orig)  # 补前缀再计算

绕过: Hook crypto.createHash,检测修改后文件中的独有标记 _c.publicDecrypt,命中时返回原始 hash。

T3 - RSA 签名验证 + 网络许可证验证

机制: 离线 token 用 RSA 私钥签名 → crypto.publicDecrypt() 验证 → 联网续期验证 绕过:

  • Hook crypto.publicDecrypt 伪造解密结果(含 lastRetry 动态时间戳)

  • Hook dns.lookup 阻断 typora 域名(typora.io / typoraio / dian.typora)

  • Hook electron-fetch / electron.net.request 拦截请求

T4 - 12 小时续期窗口

机制: renewLicense 从 SLicense 日期解析出 lastRetry(当天午夜本地时间的 UTC),若 now - lastRetry > 12h 则尝试在线续期,失败后执行 "[renewL]: unfill due to renew fail" 清除激活。

绕过: SLicense 日期设为远未来(10年后),lastRetry 始终为未来时间,now - lastRetry < 0 < 12h,续期检查永远通过。

T5 - 单实例锁

绕过: Hook app.requestSingleInstanceLock 返回 true,跳过 second-instance 事件

核心算法还原

指纹计算

fingerprint = crypto.createHash('sha256')
  .update(MachineGuid)    // Windows: HKLM\SOFTWARE\Microsoft\Cryptography
  .update('typora')       // 追加固定盐值 "typora"
  .digest('base64')
  .substring(0, 10)
  .replace(/\+/g, 'a')
  .replace(/\//g, 'b')

激活数据 JSON

{
  "deviceId": "<HOSTNAME_UPPER> | <USERNAME> | Windows",
  "email": "user@typora.io",
  "license": "XXXXXX-XXXXXX-XXXXXX-XXXXXX",
  "type": "",
  "fingerprint": "<10-char-base64>",
  "renew": "{uuid}",
  "lastRetry": "<ISO-8601 timestamp>"
}

注册表格式

HKCU\SOFTWARE\Typora\SLicense = <AES_token>#0#<far_future_date>

  • AES_token = base64(IV + AES-256-CBC(JSON{fingerprint, email, license, type}))

  • 日期使用 10 年后确保续期窗口永远有效

License 格式

  • 24 字符:22 数据 + 2 校验

  • 字符集: [L23456789ABCDEFGHJKMNPQRSTUVWXYZ](去除 I/O/Q/1/0)

  • 格式: XXXXXX-XXXXXX-XXXXXX-XXXXXX

  • 校验: 偶数位/奇数位各求和取模

Hook 注入方式

将 hook 代码以 IIFE 形式注入 launch.dist.js 开头(替换原始 "use strict";),原始字节码加载器附在后面。所有 hook 在 IIFE 闭包内运行,避免变量名冲突。

Hook 列表

Hook 目标 功能
crypto.publicDecrypt RSA 验证 解密失败时返回伪造的激活数据
crypto.createHash 文件完整性 检测到修改文件时返回原始 hash
dns.lookup DNS 解析 typora 相关域名重定向到 127.0.0.1
Module._load (electron) 多开 + 网络 requestSingleInstanceLock 始终返回 true,net.request 拦截 typora 请求
Module._load (fetch) fetch 请求 typora 域名请求返回 403

验证结果

测试项 结果 证据
编辑器正常显示 窗口标题 "Typora"
SLicense 存活 failCount=0,注册表值不被清除
网络验证拦截 DNS 重定向 + net.request/fetch 双重拦截
多开支持 多实例同时运行
ASAR 完整性绕过 EXE NOP + createHash hook
重装兼容 自动检测原始 ASAR,内存提取,无需清理缓存
重复运行跳过 EXE 已 patch / ASAR 已修改时自动跳过

事实

  • Typora 1.13.6 是 Electron 应用,主逻辑在 V8 字节码中(atom.compiled.dist.jsc

  • 离线激活 token 使用 RSA 签名,客户端 publicDecrypt 验证

  • 机器指纹使用 SHA256(MachineGuid + "typora") 的 base64 前10字符(+→a, /→b 替换)

  • ASAR 完整性校验在 EXE 层(ValidateIntegrityOrDie)和 V8 字节码层双重保护

  • 字节码层 hash 校验的期望值在编译时嵌入,为原始 launch.dist.js 的 SHA256

  • 许可证续期通过 electron-fetch 请求 store.typora.io

  • renewLicense 从 SLicense 日期计算 lastRetry(午夜本地时间转 UTC),12 小时内免续期

  • 单实例锁通过 Electron app.requestSingleInstanceLock 实现

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。