Burp Suite 2026.8 许可机制分析

样本是 PortSwigger 官网的 Professional / Community 2026.8 Desktop JAR(2026-08-24 发布)。Community 和 Professional 从 2026.4 起已经合成一份安装器、一份 JAR,差别不在文件,而在启动之后内存里那一枚「我是不是 Pro」的开关。

结论先放前面:

许可 blob 本身有 SHA256withRSA 和服务端激活;「现在是不是 Professional」最终落成一次引用比较。 会改 JVM 字节码的人,打的是后者,不是去还原 PortSwigger 私钥。这和 Typora「激活事务有 RSA、启动只看本地字典」是同一类结构,只是 Burp 多了一层隐藏 ClassLoader 和心跳。

下面按「先看清楚这摊,再讲网上的老加载器为什么有的还能跟、有的已经对不上号」写。

许可判定的两层:内容层有 RSA,开关层只看内存对象

样本

官方 Releases 下 Desktop JAR:

文件 burpsuite_desktop_v2026.8.jar
大小 729 488 519 字节(大半是内嵌 Chromium)
SHA-256 888f0588cf82a3b70d508dcbf07591184a26249e38cda8ea8b5a68f4b5a30789
Main-Class burp.StartBurp
Implementation-Version 53343
Java class major 65(Java 21)
保护 Zelix KlassMaster ZKM25.0.0(常量池水印)

核对哈希和官网一致之后再拆。包里 59 104 个条目,burp/ 下两万多个 class,名字几乎全是 Zxxx 这种 ZKM 名。明文搜 license 基本搜不到业务字符串,那是字符串加密,不是没有许可逻辑。

先建立一个心智模型

很多人一上来就想「序列号算法怎么算」。对 Burp 这件事,更先要问的是:

程序用什么来区分 Community 和 Professional?

Montoya API 里有公开枚举 BurpSuiteEditionCOMMUNITY / PROFESSIONAL / ENTERPRISE。功能闸(Intruder 全速、Scanner、Discover、Desktop REST API)读的是这个枚举,不是你有没有一份「长得像官方的 license 文本」。

枚举从哪来?2026.8 里大致是这条链:

1
2
3
4
5
6
7
许可文本
Zfqu 反射进自定义 ClassLoaderZwa8 / Zyc7 / Zp74
→ 解析成功得到 Zeq_(许可对象)
→ 在线或手工走 Activate.ashx
→ 写进 Java Preferences
Zn2m.ZW():Zeq_ 非空 → Pro 品牌 Zs56.Zu,否则 Community 品牌 Zs56.Zw
Zvqq.ZV():看到 Zu 就返回 PROFESSIONAL

从 license key 到 PROFESSIONAL 的整条链

所以有两层:

  1. 内容层:key 像不像、签过没有、build 对不对、激活响应能不能过隐藏解析器。这一层有 RSA,客户端没有私钥。
  2. 开关层:内存里有没有非空的 Zeq_。有就是 Pro。这一层是布尔。

网上从 2019 用到 2026 的加载器,打的几乎都是第 2 层的上游(让解析「成功」,或者直接改数学库让自签也算成功),很少有人去算官方私钥。

官方激活长什么样

文档写得很清楚,JAR 里也内嵌了同一份 HTML。

标准路径:账号页下 license key → 粘贴 → Next → 客户端 POST 到:

1
2
3
4
https://portswigger.net/activate/Activate.ashx
Content-Type: application/x-www-form-urlencoded

m=<urlencoded payload>

离线走 Manual activation:Copy URL → 浏览器打开官方激活页 → Copy request → 粘到网页 → 把 Activation response 贴回客户端。手工激活把协议口完整露在 UI 里,所以后来的 keygen 根本不用打真服务器,把 request/response 当本地 mock 就行。

本地不落明文 ~/.BurpSuite/*.key,而是 Preferences.userNodeForPackage(burp.StartBurp)。ZKM 解开后能看到这几个键:

含义
license + 运行模式序号 当前 license 原文
expired_license 过期 license
use_community_edition 强制走 Community
对 license 做摘要后再存 激活响应

只改 Preferences、不提供能过隐藏解析器的 blob,启动仍会进「需要激活 / 无效许可」。

启动失败的退出枚举也值得看一眼,因为它把后患写在名字上:

  • INVALID_LICENSE / LICENSE_EXPIRED
  • LICENSE_WAS_NUKED(远程撤销)
  • HEARTBEAT_FAILED / HEARTBEAT_FORBIDDEN

心跳明文是 PUT /api/heartbeatContent-Type: application/json。FORBIDDEN 会关进程。老加载器不管这个;2026 年的 agent README 才开始 mock HTTP,说明 PortSwigger 后来把失败路径接到了杀进程。

保护栈:ZKM 不够,后面还有 ClassLoader

ZKM 25 做了三件常见的事:类名搅乱、字符串加密、部分控制流。解密方法是每类一份 a(int, int)。把 StartBurp 的调用对子拿出来反射执行,就能解出 Activate.ashxburp.testing、Prefs 键这些明文。

真正藏校验算法的是三层自定义 ClassLoader:

  • burp.Zwa8:解析 license key(Zfqu.ZtClass.forName 调进来)
  • burp.Zyc7:造 activation request
  • burp.Zp74:解析 activation response

它们自己很小(8~11 KB),真正干活的 class 是运行时 defineClass 进去的,还带 MD5 形常量做完整性。静态在 JAR 里搜 burpr0x! 或 4096-bit 模数,2026.8 明文是 0 命中——不是没了,是加密进隐藏类了。

另外还有一组 Zv1 / Z_g 的 SHA256withRSA,模数是 1024/2048。对过公开 keygen 里的 PKCS8:对不上。那是给写文件流验签的(更新包之类),不是 license keygen 的那把。

网上的加载器在打什么

公开能看到源码的,是反编译出来的 BurpLoaderKeygen 一族。用户侧仪式从 Java 8 到 2026 几乎没变:

  1. -javaagent:loader.jar 起官方 JAR
  2. keygen 出一段文本,贴进 Burp
  3. 选 Manual activation
  4. request / response 互贴

Keygen 造的不是官网合法许可:NUL 分隔字段(名字、full、过期约 2099)→ 自己的 RSA 签名 → DES 信封(历史上密钥是 burpr0x!)→ Base64。

Loader 四刀,故意不看 ZKM 类名

认什么 干什么
换模 BigInteger.oddModPow 吃到硬编码的 4096-bit 官方 n 换成破解者自己的 n,自签就能过
拆解析 burp/*>110 KB、描述符 ([Ljava/lang/Object;Ljava/lang/Object;)V、指令 >2 万 整段方法清掉,改成 DES 解码
跳完整性 同上大类里第二次 new Exception 条件跳改成 GOTO
Bounty OkHttp 回包 顺带伪造 Burp Bounty 的 LicenseSpring JSON

更早一代(scz / surferxyz,Burp 1.x~2.0)是改 BigInteger.compareTo + -Xbootclasspath/p。Java 9 砍了这条启动参数,Burp 2.1 也不再明文放那两组 n,哲学没变:数学比较动手脚,不破私钥。

公开加载器四刀在 2026.8 上还能不能打中

2026.8 上,老启发式已经对不齐

用公开 loader 的体积规则扫本包:burp/* 大于 110 KB、方法是 (Object[], Object)V、指令超过 2 万——命中 0 个类

包里确实有一堆 ~217 KB 的巨型 class,但描述符对不上。许可解析在不到 12 KB 的 ClassLoader 里,描述符是 (Object[], Object)Z,代码只有几百到两千字节。体积启发式是为「算法直接摊在一个超大方法里」准备的,2026.8 把算法藏进 defineClass 之后,这刀空了。

这就是为什么「把网上 2025 的 loader 挂上去」不一定还能 Pro:不是许可模型变了,是认入口的指纹变了

开关层本身没变。Zn2m.ZW 仍然是:

  • 许可存储还没就绪 → null
  • Zeq_ 为空 → Community 品牌
  • 否则 → Pro 品牌

Zvqq.ZV 看到 Pro 品牌就返回 PROFESSIONAL。直接补这一层,不依赖 110 KB 启发式,也不依赖那组 4096-bit n 还在不在 oddModPow 里。

验证:标题不够,要看功能面

改开关之后,只看窗口写不写 Professional 不够。对照做了三组:

同一份官方 JAR:有加载器 vs 去掉再开

有加载器、有显示

  • 窗口:Burp Suite Professional v2026.8
  • Help → License:Professional +「Manage license key」(Community 是升级页)
  • Dashboard:「All issues found by the scanner」
  • Intruder 菜单:Do active scan / Do passive scan / Scan defined insertion points
  • View:Discover、Collaborator
  • REST 127.0.0.1:1337:这套 Desktop API 本身就是 Pro 的;openapi.json 里有 POST /scan
  • 扫描器知识库 184 条(含 SQL 注入、命令注入)
  • POST /v0.1/scan201 Created,任务进引擎(新项目默认 paused,那是暂停不是拒绝)

去掉加载器再开

  • 窗口只叫 Burp Suite,停在选 Community / Professional 的向导
  • 没有 1337、没有 8080
  • 采两分钟 一条 TCP 都没有——心跳客户端还没挂上
  • 关掉这个窗,进程退出

心跳

有加载器时如果把心跳发送 nop 掉,后期同样采不到 PUT /api/heartbeat。那不是服务器认了假许可,是客户端根本没把心跳打出去。无许可对象时心跳调度也不会启动。要验证「假许可能不能过心跳」,得放真 PUT,看会不会 HEARTBEAT_FORBIDDEN

一句话:功能面能开,靠的是运行时闸;不是官方许可,去掉加载器立刻回去。

和 Typora 比一眼

Typora 1.14.9 Burp 2026.8
激活事务 服务端 RSA SHA256withRSA + Activate.ashx
每次启动 本地 AES 字典有没有 email/license 内存里有没有 Zeq_
客户端私钥 没有 没有
额外对抗 Hardened Runtime(但又放了 DYLD) ZKM + 隐藏 ClassLoader + 心跳杀进程
开关能不能补

两边都不是「需要私钥才能用」。Burp 只是把开关藏得更深、杀进程更积极。

厂商如果真想加固

ZKM 换名对加载器几乎无效,人家按体积和描述符认。要加成本,得动结构:

  1. 不要用「Zeq_ 非空 / 引用等于 Zs56.Zu」这种单点开关。把 SKU 散到 Scanner、Intruder、项目保存等多处带签名的能力票。
  2. 心跳不要只在一个 ScheduledExecutorService 里;和功能调用耦合并校验服务端挑战。
  3. 隐藏 ClassLoader 的 MD5 常量可以和类一起改。完整性应绑到启动后的服务端挑战,而不是只比本地哈希。
  4. 激活 TLS 钉扎到 PortSwigger 证书,避免自定义 SSLSocketFactory 把信任链放太宽。
  5. 手工激活口是产品需求,但响应解析如果只靠客户端公钥,加载器仍然可以换模或抽掉解析函数。

参考

本文是对官方 2026.8 Desktop JAR 的许可架构还原和对照实验,不提供即用加载器或密钥材料。


Burp Suite 2026.8 许可机制分析
https://noeplex.com/2026/09/13/Burp-Suite-许可机制/
作者
To1y5
发布于
2026年9月13日
许可协议