马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?站点注册
×
PB 编译成 PBD 后为什么容易被破解:真实原因、攻击链与六层防护(附实测组件)
阅读说明
1. 适用版本:基准 PB10,兼容 PB 12.5(第五节组件在两条轨均实机验证);第一节的编译器行为说明适用于 PB 全版本
2. 支持数据库:本文不涉及数据库,但要注意——内嵌 SQL 的原文会随 PBD 一起被带出去,见第一节
3. 操作系统与环境要求:Windows XP 及以上;第五节实测组件 nvo_pbd_guard 需要 PbIdea.dll(32 位)与可执行文件同级或在 PATH 上
4. 难度系数:★★☆☆☆(一至四节是概念与决策,人人能读;第五节集成只需会改应用对象的 open 事件)
5. 其它阅读说明:第五节所有读数均为 PB10 + PB12.5 双轨实机值,不是文档转述;附件含完整源码、6 个测试用例与全部验证证据,校验脚本拿到手可直接复跑 版本声明
- 本文以 PBIDEA 当前最新版本为准,版本源由维护方统一核验。
- 核验日期:2026-10-11;当次核验的 PBIDEA 核心运行库:18,133,504 字节 / 2572 个导出,md5 4437a43b3e40…。
- 版本识别口径:解析 PE 导出表。(目录名、文件体积、ls -la 都会骗人,不能作为版本判据。)
- 适用平台:Windows XP 及以上(XP / 7 / 10 / 11 均可);PowerBuilder 8 及以上(PB 8/9/10/10.5/11.x/12.x 及以上通用)。本文的实机验证环境为 Windows 10 + PB 10 与 PB 12.5。
- PBIDEA 版本会持续更新,本文只对上述核验时点的最新版本负责;后续若行为有变,以新版为准。
零、一句话结论
PBD 不是一个"已经编译掉、看不懂"的二进制,它是一份自描述的字节码容器:里面除了 p-code,还完整带着类元数据、对象定义、事件脚本、属性值、继承关系和内嵌 SQL 文本。
所以破解 PBD 从来不是"破译密码",而是"查表翻译"。只要有人把 p-code 的操作码表整理出来,剩下的工作就是体力活——而这套操作码表,社区里早就有人整理完了,而且到今天还在维护。
因此本文的结论不是"如何把 PBD 藏好",而是:把安全性建立在不随客户端一起发出去的东西上。
一、原因:为什么 PBD 天生容易被还原
1.1 PBD 里到底装了什么
按官方的说法,PowerBuilder 的编译器有两种输出:
| 编译方式 | 动态库扩展名 | 内容 | 特点 | | Pcode | .PBD | 伪代码(p-code)+ 对象元数据 | 体积小、可移植、由 PB 虚拟机解释执行 | | Machine code | .dll | PB 先生成 C 代码再编译 | 循环/浮点/整数/函数调用更快、体积大;不能被其它程序调用 |
同一个应用不能混用 PBD 与机器码 DLL——这是硬约束。
关键在 Pcode 那一列:p-code 是带符号的字节码,不是抹掉符号的本地机器码。它必须能被 PB 虚拟机解释,就必须自描述:
- 每个对象的类元数据(祖先、变量、函数签名)
- 每个事件的脚本字节码
- 每个属性的当前值
- 继承关系图
- 内嵌 SQL 的原文
这些都是"给虚拟机看的说明书"。反编译器要做的,就是把说明书和字节码重新拼回源码——这是翻译,不是破解。
1.2 反编译工具链已经成熟,而且仍在维护
| 工具 | 支持的 PB 版本 | 原理与现状 | | PBKiller(2.5.18) | PB 5 → 12.5 | 按版本挂载 vm*.dat 虚拟机指纹表(vm82.dat=PB8、vm117.dat=PB9、vm149.dat=PB10、vm169.dat=PB11、vm196.dat=PB12.5+),再配合 kernel.dat(运行时导出表)与 undw(DataWindow 解包器)。PB6–9 源码还原度约 95% | | PB DeCompiler | PB 5 → 12 | Simple / Professional 两种模式,可直接导出 SR,甚至重建 PBW+PBT;能自动识别编译模式 | | PowerRebuilder(Rust 实现,开源) | PB 7.0 – 12.5,591 条操作码(PB 6.0 为 256 条) | P-code 反汇编 → 控制流图 → SSA 中间表示 → 类型推断,最后可输出 Python / TypeScript / React / Vue / Svelte / Rust 等。作者实测在 38 KB – 3.3 MB 的 PBD(26 – 3212 个对象)上 100% 成功 |
注意 PB10 以后的变化:编译器加了控制流扁平化、字符串加密、符号剥离,确实让自动还原的成本上升了。但这是成本问题,不是"不可能"问题——上表里 PowerRebuilder 的覆盖范围一直写到 PB 12.5。
1.3 五个最常见的错判(逐条纠正)
① "编译成机器码 DLL 就安全了" —— 不成立。
这是流传最广的一条。做 PBD 保护工具的 PbGuard 作者对此有一句很直白的说明:
DLLs produced using the machine code builds include copies of the corresponding PBDs and therefore can be decompiled as well.
机器码构建出来的 DLL 里仍然带着对应 PBD 的副本,所以照样能反编译。换句话说,机器码只提升了运行速度,并没有把 p-code 从产物里拿走。
② "把应用全打包进 EXE 就藏住了" —— 不成立。
Appeon 官方论坛有一个很准确的比喻:对 p-code 而言,一个 EXE 概念上就等于"一个 PBD 文件 + 一个极小的引导器"(引导器负责库列表、应用对象名和加载正确的 PB 虚拟机)。也就是说,EXE 的大部分内容本来就是第一个 PBD。打包只是把文件换了个壳。
③ "改扩展名 / 加壳 / 改字符串就够了" —— 只提高一点点成本。
改扩展名对反编译器毫无影响(它认的是内容不是名字)。UPX 之类通用壳能挡住脚本小子,但 x64dbg 跟一遍运行时解密就能拿到内存里的明文模块;IDB/内存转储工具(Process Explorer、PETools)也能直接把解密后的 PBD 模块从内存里抠出来。
④ "字符串加密了就读不到逻辑" —— 只是让"读起来慢"一点。
字符串解密只是一个可复现的变换。攻击者只要在解密函数返回处下断点,所有字符串就同时在眼前铺开了。它增加的是耐心成本。
⑤ "用 PB Protect 就行了" —— 它不是 PB 自带的。
在被问到"PowerScript 有没有官方混淆方案"时,Appeon 的官方回答是:就 PowerScript 部分而言,我所知道唯一正规的方案是 PB Protect,并顺带指出通用混淆效果"好坏参半"。也就是说,第三方工具是必要的,且它保护的仍然是 PowerScript 层面,不是"客户端不可信"这个根本问题。
1.4 官方的正确做法其实已经给出过——只是在另一个产品里
Appeon 在 PowerServer 里有一整套"客户端应用保护",位于 Project 画笔 → Security 页。它的安全特性表值得原文对照着看:
| PowerServer 的安全特性 | 它解决什么 | | PBD encryption(加密 p-code 文件) | 静态拿到文件也读不出 p-code | | Application integrity validation(校验完整性) | 运行时对每个对象文件算哈希,检测未授权改动 | | Third-party DLL loading restriction(Strict 模式) | 阻止进程加载来路不明的第三方 DLL | | Advanced execution security flags | DEP / ASLR / CFG / SaveSEH | | Cookie validation | 会话语义校验 |
这张表的价值在于:它是一个"正确答案的模板"。经典 PB 没有现成的开关,但这五件事里有三件,我们自己用 PB 就能做——第 3、4 层就是照这个模板落地的。
二、攻击者的完整链条,以及我们能在哪一环拦住
| 阶段 | 攻击者做什么 | 常用工具 | 我们能不能拦 | | 1 拿到文件 | 复制 PBD / EXE | 资源管理器 | 不能(文件一定会到客户端) | | 2 静态还原 | PBD → 反汇编 → 源码 | PBKiller / PB DeCompiler / PowerRebuilder | 只能抬高成本(字符串加密、控制流混淆) | | 3 动态调试 | 跟运行时解密点、下断点 | x64dbg / OllyDbg | 只能抬高成本(反调试,可被绕过) | | 4 内存转储 | 从进程内存里抠出解密后的模块 | Process Explorer / PETools | 不能(同一进程内的明文一定会出现) | | 5 篡改重打包 | 改一个判断、去掉一个校验,再发出去 | 十六进制编辑器 | ✅ 能拦(完整性自校验) | | 6 换机器扩散 | 把你的程序拷到别的机器上跑 | 直接复制 | ✅ 能拦(机器绑定 / 加密狗) | | 7 白嫖核心算法 | 把关键算法或数据复制走 | 重写 | ✅ 能拦(关键逻辑不放客户端) |
请注意第 2、3、4 环的"不能/只能"——只要源码和数据在客户端,理论上就一定能被拿到。这不是技术不够好,而是这个模型本身的性质。
所以方案的重心,必须压在第 5、6、7 环。
三、分层方案(按性价比排序)
第 1 层:编译与打包选项(抬高成本,不构成护城河)
- 用 pbc210 打包时,/pd 参数控制是否为某个 PBL 生成 PBD/DLL(按 PBL 逐位指定,例如 /pd yny)——不需要暴露的 PBL 干脆不要生成。
- /o 优化开关(0=速度、1=空间、2=不优化)只在生成机器码时生效——也就是说,如果你走 Pcode,调它没有意义。
- /ge 清单、/le 执行级别、/ps 保护系统 UI 这些照常配好。
- 机器码 DLL 可以做数字签名(Appeon 也建议过这条)——它能防"被人替换成同名 DLL",但不防反编译。
- 不要把服务器地址、密钥、License 校验的常量以明文写进脚本。反编译后第一眼就会被看到。
这一层的定位:让顺手的攻击变得不顺手。不要指望它。
第 2 层:真正的护城河——把关键判定搬到服务端
既然第 2、4 环拦不住"源码被看到",那就让客户端里没有值得看的东西。
- 把权限判定、License 判定、核心算法、业务规则的最终裁决放到服务端,客户端只做展示与输入。
- 客户端拿到的是结果,不是规则。
- PB 侧实现方式:PowerServer,或自己写 REST/HTTP 客户端调用后端。
- Appeon 官方给的原始建议就是"尽可能把逻辑放到 Web 服务器上执行"。
这一层是所有方案里唯一带根本性的。前一层是加锁,这一层是"把金库搬走"。
第 3 层:完整性自校验(对应 PowerServer 的 Validate integrity)
有些程序确实必须离线跑,那就至少做到"改一个字节就起不来":
- 发布前对每个 PBD / DLL / 资源文件算 SHA-256,生成清单;
- 清单本身用非对称签名(或至少绑机器码),防止攻击者连清单一起改;
- 程序启动时重算摘要并比对,不一致直接退出。
关键实现细节(都踩过坑):
- 必须流式分块算哈希,否则遇上几百 MB 的资源文件内存会顶爆;
- 校验点不要只放一处:放在应用 open 事件、放在某个业务入口、再放一个"用到了才校验"的延迟点。只放一处,破解者改一处就够了;
- 自己校验自己这件事有个死结:校验代码本身也在被校验的文件里。所以真正敏感的东西,仍然要靠第 2 层或第 5 层。
第 4 层:资源与字符串加密
敏感资源(脚本模板、字典、图片、配置)不要以明文随程序发布:
- 用 SM4 / AES 加密后作为数据文件发布,运行时内存解密;
- 密钥不要硬编码在脚本里——那等于没加密。密钥应当来自:用户输入、License 文件、或第 5 层的硬件。
第 5 层:硬件加密狗(本地部署场景下唯一"拿得走的东西不在这台电脑上")
如果程序必须离线、必须本地跑、又必须有商业保护,加密狗是唯一能把"解密能力"从攻击者手里拿走的方案:
- 密钥在狗内生成、在狗内使用,永不导出——内存转储也抠不出私钥;
- 敏感运算(签名、摘要、对称加解密)在狗里做,主机只拿到结果;
- 程序与狗的序列号 + 机器指纹双重绑定。
这一层能同时覆盖第 4 环(内存转储拿不到密钥)和第 6 环(换机器跑不起来)。
第 6 层:运行期防护(反调试等)
枚举进程,检测常见调试器 / 抓包 / 内存工具是否在跑。
必须说清楚:这是启发式,改个进程名就绕过了。 它的价值是筛掉"随手试试"的人,以及给第 3 层争取时间——不能当成主要防线。
配套的还有 DEP / ASLR / CFG 这些编译期与系统级开关,成本和收益都很低,能开就开。
四、三种典型场景怎么组合
| 场景 | 推荐组合 | 说明 | | 联网业务系统(有服务器) | 第 2 层为主 + 第 3 层 | 判定逻辑全在服务端,客户端被反编译也只是个壳 | | 离线单机工具(必须本地跑、要卖钱) | 第 5 层 + 第 3 层 + 第 1 层 | 加密狗是核心;完整性校验防篡改;打包选项抬高门槛 | | 内部系统 / 防盗版压力低 | 第 3 层 + 第 4 层 + 第 1 层 | 重点防"内部人改一个判断绕开限制" |
无论哪种场景,都不要把第 1 层当解决方案。 这一层的全部意义是"让攻击成本从 10 分钟变成 2 小时"。
五、一个可以直接用的实测组件:nvo_pbd_guard
上面第 3 层说起来简单,落地有一堆坑。为了让这一层真的能用,我把完整性自校验写成了一个单文件、零依赖的 NVO,并在 PB10 / PB12.5 两条轨上做了实机验证。
5.1 设计要点
- 零依赖:只声明自己用到的几个 PBIDEA 外部函数,不引入 PBIDEA 的任何 PBL。一个 .sru 就能编译,PB10 与 PB12.5 同一份源码、一行不改。
- 流式哈希:Begin → Update(可多次) → Final → End,大文件不吃内存。
- 机器绑定:硬盘序列号 + CPU 序列号拼串后取 SHA-256,截取前 32 个十六进制字符(128 位)作为机器码。
- 清单带机器码:清单第 1 行写死机器码,换台机器直接不认。
5.2 对外接口
| 方法 | 作用 | | of_hash_file(f) / of_hash_algo(algo, f, ref size) | 流式算文件摘要 | | of_hash_text(s, algo) | 算字符串摘要 | | of_file_size(f) / of_set_chunk(n) / of_get_chunk() | 文件大小与分块控制 | | of_machine_id() | 取本机机器码(32 个十六进制字符,大写) | | of_scan_dir(dir, ext, sub, ref files[]) | 列目录取文件清单 | | of_make_manifest(files[], manifest, machine) | 生成完整性清单 | | of_manifest_machine(manifest) | 读清单里的机器码 | | of_verify(manifest, ref bad[]) | 逐条比对,返回不一致条数 | | of_check(manifest) | 启动闸门:机器码对 + 全部文件对,才返回 true | | of_list_process(ref names[]) / of_find_debugger(ref found[]) | 进程枚举与调试器探测 | | of_debugger_keywords() / of_last_error() | 关键字表与最近错误 |
5.3 核心代码(节选,完整源码见附件)
- /*====================================================================
- > 流式哈希一个文件
- > 返回 : 小写十六进制摘要; 失败返回空串, 原因看 of_last_error()
- >====================================================================*/
- public function string of_hash_algo (string as_algo, string as_file, ref longlong al_size);
- long ll_f
- long ll_n
- longlong ll_total
- string ls_hex
- blob lb_buf
- al_size = 0
- is_err = ''
- ll_f = FileOpen(as_file, StreamMode!, Read!, LockRead!)
- if ll_f < 0 then
- is_err = '文件打不开: ' + as_file
- return ''
- end if
- if not hashBegin(as_algo) then
- FileClose(ll_f)
- is_err = 'hashBegin 失败, 算法=' + as_algo
- return ''
- end if
- ll_total = 0
- ll_n = 1
- // ★ 坑: PB 的 DO...LOOP 没有裸写形式, 必须带 WHILE / UNTIL, 否则 C0031 Syntax error
- do while ll_n > 0
- // 每轮先把 blob 清空: 这样无论 FileReadEx 是"替换"还是"追加"语义,
- // 交给 hashUpdate 的都只是本次读到的那一块
- lb_buf = blob('')
- ll_n = FileReadEx(ll_f, lb_buf, il_chunk)
- if IsNull(ll_n) then
- is_err = 'FileReadEx 返回 null'
- exit
- end if
- if ll_n <= 0 then exit
- ll_total += ll_n
- if not hashUpdate(lb_buf) then
- is_err = 'hashUpdate 失败, 已读=' + String(ll_total)
- exit
- end if
- loop
- FileClose(ll_f)
- if Len(is_err) > 0 then
- hashEnd()
- return ''
- end if
- ls_hex = ''
- hashFinal(ls_hex)
- hashEnd()
- if Len(ls_hex) = 0 then
- is_err = 'hashFinal 没有返回摘要'
- return ''
- end if
- al_size = ll_total
- return Lower(ls_hex)
- end function
复制代码- /*====================================================================
- > 启动闸门: 机器码对得上 且 所有文件摘要都对得上, 才返回 true
- > 典型用法: 放在应用 open 事件里, 不通过就直接 HALT
- >====================================================================*/
- public function boolean of_check (string as_manifest);
- string ls_want
- string ls_now
- string ls_bad[]
- long ll_bad
- is_err = ''
- ls_want = of_manifest_machine(as_manifest)
- if Len(ls_want) = 0 then
- is_err = '清单缺少机器绑定信息'
- return false
- end if
- ls_now = of_machine_id()
- if Len(ls_now) = 0 then return false
- if ls_now <> ls_want then
- is_err = '机器不匹配: 本机=' + ls_now + ' 清单=' + ls_want
- return false
- end if
- ll_bad = of_verify(as_manifest, ls_bad)
- if ll_bad < 0 then return false
- if ll_bad > 0 then
- is_err = '有 ' + String(ll_bad) + ' 个文件校验不通过'
- return false
- end if
- return true
- end function
复制代码
用法就是两行:
- nvo_pbd_guard lnv_g
- lnv_g = create nvo_pbd_guard
- if not lnv_g.of_check('app.manifest') then
- MessageBox('程序完整性校验未通过', lnv_g.of_last_error())
- HALT CLOSE
- end if
- destroy lnv_g
复制代码
5.4 实机读数(PB10 与 PB12.5 两条轨,读数逐字节一致)
① 摘要正确性——用外部真值对,不是自己跟自己比
对一个 300,000 字节的二进制文件:
- [1] sha256(文件) = 7cae241d8dce74202d28c624c34bfafef96179e3235321b0c43805519f441e8f
- [2] 与 Python hashlib 一致 = true
- [3] sha256(文本) = 402818b2c4f66fd473edb94e3cb3e00452d10c6584613c9255cdb941cae538f3
- [4] 字符串摘要与 Python 一致 = true
- [5] sha256(文本文件) = e1264c7f6289a44fe39f92a86289d3123724b6ab2e47a61c697b17e295c1d5ce
- [6] 文本文件摘要与 Python 一致 = true
- [7] md5(abc) = 900150983cd24fb0d6963f7d28e17f72
- [8] 文件字节数 = 300000
- [9] 最近错误 = []
复制代码
PB 算出来的摘要与 Python hashlib 逐字符相同。这说明整条链路(分块读 → 二进制进哈希 → 输出十六进制)都是对的。
② 分块语义——换五种分块大小,结果必须完全相同
- [1] 分块 262144 -> 7cae241d8dce74202d28c624c34bfafef96179e3235321b0c43805519f441e8f
- [2] 分块 65536 -> 7cae241d8dce74202d28c624c34bfafef96179e3235321b0c43805519f441e8f
- [3] 分块 4096 -> 7cae241d8dce74202d28c624c34bfafef96179e3235321b0c43805519f441e8f
- [4] 分块 1 -> 7cae241d8dce74202d28c624c34bfafef96179e3235321b0c43805519f441e8f
- [5] 分块 999999 -> 7cae241d8dce74202d28c624c34bfafef96179e3235321b0c43805519f441e8f
- [6] 五种分块结果都等于真值 = true
- [7] 分块设 0 被拒 = 999999
复制代码
分块从 1 字节到 999,999 字节,摘要一字不差——这就是"流式"正确性的证据。
③ 篡改检测——改一个字节必须被抓到
- [1] 读出字节数 = 300000
- [2] 副本字节数 = 300000
- [3] 清单条数 = 2
- [4] 篡改前不一致数 = 0
- [5] 追加 2 字节后 = 300002 字节
- [6] 篡改后不一致数 = 1
- [7] 检出项 = 内容已被改动|testblob_t.bin
- [8] of_check 篡改后 = false
- [9] of_check 的错误 = 有 1 个文件校验不通过
- [10] 还原后不一致数 = 0
复制代码
只给副本末尾接了 2 个字节,第 6 步立刻报出「1 个文件不一致」,第 7 步指名道姓说是哪个文件,第 8 步闸门关上;改回去之后(第 10 步)又恢复正常——说明它认的是内容,不是时间戳之类的东西。
④ 机器绑定——换机器就认清单
- [1] 机器码 = E5C3A7AF……FAB098 (示例已打码)
- [2] 长度 = 32
- [3] 两次取值相同(稳定) = true
- [4] 绑定本机清单条数 = 1
- [5] 本机清单 of_check = true
- [6] 伪造清单里的机器码 = 00000000000000000000000000000000
- [7] 换机器后 of_check = false
- [8] 错误 = 机器不匹配: 本机=E5C3A7AF……FAB098 清单=00000000000000000000000000000000
- [9] 非清单文件取机器码 = 空(true)
复制代码
⑤ 目录与进程
- [1] 清单条数 = 2
- [2] 未改动时不一致数 = 0
- [5] of_check 未改动 = true
- [6] 清单缺失时 of_verify = -1 ← 清单丢了要报错,不能当成"没问题"
- [8] 扫到 *.pbtest 条数 = 6
- [9] 首条已剥掉序号前缀 = true ← FindFile 原始返回是 "序号,完整路径",组件已把前缀剥掉
- [10] 首条已是完整路径 = true
复制代码- [1] 进程枚举可用 = true
- [5] 关键字表条数 = 34
- [6] 关键字表非空 = true
复制代码进程总数与命中数是随机的,跨轨比对时按约定屏蔽,不参与"逐字节一致"的结论。
5.5 交付与验证
| 项目 | 结果 | | 源码编码 | GBK / CRLF / 无 BOM / 首行 $PBExportHeader$,bad = 0 | | 导入 | PB10 与 PB12.5 均 0 错(level 4 = 0) | | 全量编译 | 两条轨 rebuild --type full 均 0 错 | | 运行 | 6 个用例 × 2 轨 = 12 次运行,12 次 PASS | | 跨轨一致性 | 6 个用例的读数逐字节一致 6/6(其中进程统计的 2 行按约定屏蔽,不参与该结论) | | 静态检查 | PB12.5 导入前自检 0 错 0 提醒 |
附件 pbdguard_pb10.zip:26 个文件 / 38,331 字节 / md5 a30af8252a9cb7960c1b1d9231aacade。内含完整源码(src/nvo_pbd_guard.sru)、6 个测试用例、四绿验证的全部原始证据与三个校验脚本。包里所有文本都已脱敏(本机路径替换成 <工作目录> / <用户目录> 这类占位符),校验脚本用环境变量指定路径,拿到手就能直接复跑。
想核对"你手上的源码和我这里的证据是不是同一份":看 evidence/源码指纹.txt 里的 md5 / sha256。
六、能力边界(说实话的部分)
这一段比前面的方案更重要。
- 客户端拿到的东西,理论上一定能被还原。 第 3、4、6 层全部只能在"提高成本"这条轴上移动。唯一改变性质的是第 2 层(逻辑不在客户端)和第 5 层(密钥不在主机)。
- 完整性自校验有个死结:校验代码自己也在被校验的文件里。攻击者只要找到校验点并改掉跳转,校验就形同虚设。对策是多点校验 + 延迟校验 + 把校验结果和后面的业务运算耦合起来(让"跳过校验"直接导致算不出正确结果),但都不能彻底解决。
- 机器绑定会误伤正常用户:换硬盘、换 CPU、虚拟机迁移、系统重装都会导致机器码变化。商用产品需要预留"重新授权"的流程,不能只靠机器码硬绑。
- 反调试是启发式:进程名一改就绕过。
- 加密狗有成本和运维代价:要发狗、要管理、要处理丢失和补发。
所以给一句可执行的建议:先回答"我的核心资产到底是程序本身,还是程序里的某个规则/数据"。
- 如果是规则或数据 → 搬到服务端(第 2 层),这是投入产出比最高的一步,做了这一步,后面几层都只是补充。
- 如果程序必须离线独立交付且要卖钱 → 直接上加密狗(第 5 层),别在混淆上耗时间。
- 其它情况 → 用第 3 + 4 + 1 层,把"内部人随手改一个判断"这类事挡住,就达到目的了。
七、落地清单
发布前
- □ 确认哪些 PBL 需要生成 PBD(pbc210 /pd 逐位控制),不需要的不要生成
- □ 敏感常量(服务器地址、密钥、许可判定)不出现在脚本里
- □ 走机器码 DLL 的话,做代码签名
- □ 敏感资源加密后发布,密钥不硬编码
构建时
- □ 生成完整性清单:对每个 PBD / DLL / 资源算 SHA-256
- □ 清单第 1 行写机器码(of_make_manifest 会自动带)
- □ 清单文件本身纳入版本管理,改动必须留痕
运行时
- □ 应用 open 事件调用 of_check,不通过就 HALT
- □ 至少再加 1–2 个延迟校验点(业务入口处)
- □ 校验失败时不要只弹一个"请购买正版"——直接把后续计算做成算不出正确结果
上线后
- □ 建立"重新授权"流程(应对用户换硬件)
- □ 记录机器码与许可对应关系,便于排查
八、最后一句
PBD 容易破解这件事,不是 PowerBuilder 的缺陷,而是"把可执行逻辑交给不受你控制的机器"这件事本身的性质。任何语言、任何平台都一样,只是 PB 的字节码更自描述、社区工具更成熟,所以显得更"好拆"。
能做的从来不是"让它拆不开",而是"让它拆开了也没用"。 |