V2版本:支持自定义安装目录
使用方法:将激活工具copy到typora.exe同目录后使用
任务摘要
逆向分析 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_1403BCAF0(ValidateIntegrityOrDie) 绕过: 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

评论(0)