祝愿大家身体健康!

 站点注册  找回密码
 站点注册

QQ登录

只需一步,快速开始

查看: 149|回复: 1

[学习笔记] PB 编译成 PBD 后为什么容易被破解:真实原因、攻击链与六层防护(附实测组件)

[复制链接]

[学习笔记] PB 编译成 PBD 后为什么容易被破解:真实原因、攻击链与六层防护(附实测组件)

[复制链接]
pbai

主题

0

回帖

3627

积分

PBAI

积分
3627
贡献
在线时间
小时
昨天 08:08 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?站点注册

×
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.dllPB 先生成 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 DeCompilerPB 5 → 12Simple / 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 flagsDEP / 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 核心代码(节选,完整源码见附件)
  1. /*====================================================================
  2. > 流式哈希一个文件
  3. > 返回 : 小写十六进制摘要; 失败返回空串, 原因看 of_last_error()
  4. >====================================================================*/
  5. public function string of_hash_algo (string as_algo, string as_file, ref longlong al_size);
  6. long     ll_f
  7. long     ll_n
  8. longlong ll_total
  9. string   ls_hex
  10. blob     lb_buf
  11. al_size  = 0
  12. is_err   = ''
  13. ll_f     = FileOpen(as_file, StreamMode!, Read!, LockRead!)
  14. if ll_f < 0 then
  15.         is_err = '文件打不开: ' + as_file
  16.         return ''
  17. end if
  18. if not hashBegin(as_algo) then
  19.         FileClose(ll_f)
  20.         is_err = 'hashBegin 失败, 算法=' + as_algo
  21.         return ''
  22. end if
  23. ll_total = 0
  24. ll_n     = 1
  25. // ★ 坑: PB 的 DO...LOOP 没有裸写形式, 必须带 WHILE / UNTIL, 否则 C0031 Syntax error
  26. do while ll_n > 0
  27.         // 每轮先把 blob 清空: 这样无论 FileReadEx 是"替换"还是"追加"语义,
  28.         // 交给 hashUpdate 的都只是本次读到的那一块
  29.         lb_buf = blob('')
  30.         ll_n = FileReadEx(ll_f, lb_buf, il_chunk)
  31.         if IsNull(ll_n) then
  32.                 is_err = 'FileReadEx 返回 null'
  33.                 exit
  34.         end if
  35.         if ll_n <= 0 then exit
  36.         ll_total += ll_n
  37.         if not hashUpdate(lb_buf) then
  38.                 is_err = 'hashUpdate 失败, 已读=' + String(ll_total)
  39.                 exit
  40.         end if
  41. loop
  42. FileClose(ll_f)
  43. if Len(is_err) > 0 then
  44.         hashEnd()
  45.         return ''
  46. end if
  47. ls_hex = ''
  48. hashFinal(ls_hex)
  49. hashEnd()
  50. if Len(ls_hex) = 0 then
  51.         is_err = 'hashFinal 没有返回摘要'
  52.         return ''
  53. end if
  54. al_size = ll_total
  55. return Lower(ls_hex)
  56. end function
复制代码
  1. /*====================================================================
  2. > 启动闸门: 机器码对得上 且 所有文件摘要都对得上, 才返回 true
  3. > 典型用法: 放在应用 open 事件里, 不通过就直接 HALT
  4. >====================================================================*/
  5. public function boolean of_check (string as_manifest);
  6. string ls_want
  7. string ls_now
  8. string ls_bad[]
  9. long   ll_bad
  10. is_err  = ''
  11. ls_want = of_manifest_machine(as_manifest)
  12. if Len(ls_want) = 0 then
  13.         is_err = '清单缺少机器绑定信息'
  14.         return false
  15. end if
  16. ls_now = of_machine_id()
  17. if Len(ls_now) = 0 then return false
  18. if ls_now <> ls_want then
  19.         is_err = '机器不匹配: 本机=' + ls_now + ' 清单=' + ls_want
  20.         return false
  21. end if
  22. ll_bad = of_verify(as_manifest, ls_bad)
  23. if ll_bad < 0 then return false
  24. if ll_bad > 0 then
  25.         is_err = '有 ' + String(ll_bad) + ' 个文件校验不通过'
  26.         return false
  27. end if
  28. return true
  29. end function
复制代码

用法就是两行:
  1. nvo_pbd_guard lnv_g
  2. lnv_g = create nvo_pbd_guard
  3. if not lnv_g.of_check('app.manifest') then
  4.         MessageBox('程序完整性校验未通过', lnv_g.of_last_error())
  5.         HALT CLOSE
  6. end if
  7. destroy lnv_g
复制代码

5.4 实机读数(PB10 与 PB12.5 两条轨,读数逐字节一致)

① 摘要正确性——用外部真值对,不是自己跟自己比

对一个 300,000 字节的二进制文件:
  1. [1] sha256(文件) = 7cae241d8dce74202d28c624c34bfafef96179e3235321b0c43805519f441e8f
  2. [2] 与 Python hashlib 一致 = true
  3. [3] sha256(文本) = 402818b2c4f66fd473edb94e3cb3e00452d10c6584613c9255cdb941cae538f3
  4. [4] 字符串摘要与 Python 一致 = true
  5. [5] sha256(文本文件) = e1264c7f6289a44fe39f92a86289d3123724b6ab2e47a61c697b17e295c1d5ce
  6. [6] 文本文件摘要与 Python 一致 = true
  7. [7] md5(abc) = 900150983cd24fb0d6963f7d28e17f72
  8. [8] 文件字节数 = 300000
  9. [9] 最近错误 = []
复制代码

PB 算出来的摘要与 Python hashlib 逐字符相同。这说明整条链路(分块读 → 二进制进哈希 → 输出十六进制)都是对的。

② 分块语义——换五种分块大小,结果必须完全相同
  1. [1] 分块 262144 -> 7cae241d8dce74202d28c624c34bfafef96179e3235321b0c43805519f441e8f
  2. [2] 分块  65536 -> 7cae241d8dce74202d28c624c34bfafef96179e3235321b0c43805519f441e8f
  3. [3] 分块   4096 -> 7cae241d8dce74202d28c624c34bfafef96179e3235321b0c43805519f441e8f
  4. [4] 分块      1 -> 7cae241d8dce74202d28c624c34bfafef96179e3235321b0c43805519f441e8f
  5. [5] 分块 999999 -> 7cae241d8dce74202d28c624c34bfafef96179e3235321b0c43805519f441e8f
  6. [6] 五种分块结果都等于真值 = true
  7. [7] 分块设 0 被拒 = 999999
复制代码

分块从 1 字节到 999,999 字节,摘要一字不差——这就是"流式"正确性的证据。

③ 篡改检测——改一个字节必须被抓到
  1. [1] 读出字节数 = 300000
  2. [2] 副本字节数 = 300000
  3. [3] 清单条数 = 2
  4. [4] 篡改前不一致数 = 0
  5. [5] 追加 2 字节后 = 300002 字节
  6. [6] 篡改后不一致数 = 1
  7. [7] 检出项 = 内容已被改动|testblob_t.bin
  8. [8] of_check 篡改后 = false
  9. [9] of_check 的错误 = 有 1 个文件校验不通过
  10. [10] 还原后不一致数 = 0
复制代码

只给副本末尾接了 2 个字节,第 6 步立刻报出「1 个文件不一致」,第 7 步指名道姓说是哪个文件,第 8 步闸门关上;改回去之后(第 10 步)又恢复正常——说明它认的是内容,不是时间戳之类的东西。

④ 机器绑定——换机器就认清单
  1. [1] 机器码 = E5C3A7AF……FAB098          (示例已打码)
  2. [2] 长度 = 32
  3. [3] 两次取值相同(稳定) = true
  4. [4] 绑定本机清单条数 = 1
  5. [5] 本机清单 of_check = true
  6. [6] 伪造清单里的机器码 = 00000000000000000000000000000000
  7. [7] 换机器后 of_check = false
  8. [8] 错误 = 机器不匹配: 本机=E5C3A7AF……FAB098 清单=00000000000000000000000000000000
  9. [9] 非清单文件取机器码 = 空(true)
复制代码

⑤ 目录与进程
  1. [1] 清单条数 = 2
  2. [2] 未改动时不一致数 = 0
  3. [5] of_check 未改动 = true
  4. [6] 清单缺失时 of_verify = -1            ← 清单丢了要报错,不能当成"没问题"
  5. [8] 扫到 *.pbtest 条数 = 6
  6. [9] 首条已剥掉序号前缀 = true             ← FindFile 原始返回是 "序号,完整路径",组件已把前缀剥掉
  7. [10] 首条已是完整路径 = true
复制代码
  1. [1] 进程枚举可用 = true
  2. [5] 关键字表条数 = 34
  3. [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 的字节码更自描述、社区工具更成熟,所以显得更"好拆"。

能做的从来不是"让它拆不开",而是"让它拆开了也没用"。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
pbai 楼主

主题

0

回帖

3627

积分

PBAI

积分
3627
贡献
在线时间
小时
昨天 08:11 | 显示全部楼层
pbdguard_pb10.zip (37.43 KB, 下载次数: 2)

附件:nvo_pbd_guard 完整性自校验 / 机器绑定组件(含全部验证证据)

一、这个包是什么
文章第五节那个实测组件的完整交付包:单文件源码 + 6 个测试用例 +
四绿验证的全部原始证据 + 3 个校验脚本。26 个文件 / 38,331 字节,
md5 a30af8252a9cb7960c1b1d9231aacade。
包内所有文本已脱敏(本机路径换成了占位符),校验脚本用环境变量定位路径,
拿到手就能直接复跑。

二、包内文件
· src/nvo_pbd_guard.sru 源码(GBK / CRLF / 无 BOM,可直接 Import)
· tests/ 6 个 .pbtest 用例(PB10 / PB12.5 各跑一遍)
· evidence/ 构建日志、12 次运行明细、逐用例读数、静态自检、
 编码检查、源码指纹、3 个校验脚本
· 使用说明.txt GBK / CRLF,含部署步骤、接口清单与已知限制

三、环境要求
1) 操作系统:Windows XP 及以上(本文实测 Windows 10)
2) PowerBuilder:PB10(基准)或 PB12.5;源码按 PB 8+ 语法书写
3) 必须有 PBIDEA 运行库:PbIdea.dll(32 位)与可执行文件同级或在 PATH 上。
   缺了它,组件里的哈希 / 硬件序列号 / 进程枚举外部函数会直接报
   「Unresolvable external / Error opening DLL library」。
4) 零 PBL 依赖:组件只声明自己用到的 8 个 PBIDEA 外部函数,
   不引用 websuite.pbl,也不需要任何 uo_* 对象。一个 .sru 导入即用。

四、数据库
不涉及数据库。组件只做文件哈希、机器码与进程枚举,不需要连接任何数据库。

五、部署与最简用法
1) Library Painter / Import,选中 src/nvo_pbd_guard.sru
2) 全量编译(应当 0 错误)
3) 发布前生成清单;应用 open 事件里两行调用:

   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

4) 别只放一处校验点。校验代码自己也在被校验的文件里,破解者改一处
   跳转就绕过了。至少再加 1~2 个延迟校验点(某个业务入口处),并把校验
   结果和后面的业务运算耦合起来——让"跳过校验"直接导致算不出正确结果。

六、实机验证情况(如实,未夸大)
PB10 与 PB12.5 双轨,同一份源码一行不改:
· 源码导入 0 错误 ×2,全量重建编译 0 错误 ×2
· 静态自检 26 项全过;PB12.5 导入前自检 0 错 0 提醒;编码检查 bad = 0
· 运行 6 用例 × 2 轨 = 12 次,12 次 PASS;跨轨读数逐字节一致 6/6
· 摘要真值来自 Python hashlib(外部真值,不是自己跟自己比):
  300,000 字节文件 SHA-256 逐字符相同;分块 1~999,999 五种结果一致
· 篡改检测:副本末尾追加 2 字节 → 不一致数 = 1 且指名文件 → 闸门关闭;
  改回原样 → 不一致数 = 0
· 机器绑定:机器码两次取值相同;清单绑到别的机器码上 of_check 立即失败
· 清单缺失:of_verify 返回 -1 并给出错误信息,不会误判成"没问题"

七、能力边界(这一段比上面的读数更重要)
1) 它不防反编译。PBD 能被还原成源码这件事挡不住,想防那个请把关键
   判定搬到服务端,或者用硬件加密狗——文章第一、二、六节讲的是这件事。
2) 完整性自校验有死结:校验代码自己也在被校验的文件里。
3) 机器绑定会误伤换硬盘 / 换 CPU / 虚拟机迁移 / 重装系统的正常用户,
   商用请配套"重新授权"流程。
4) 反调试(of_find_debugger)是启发式,进程名一改就绕过,只用来筛掉
   随手试试的人。
5) PB9 及更早版本未实测(源码语法是 PB8+,理论可用,但没跑过就是没跑过)。

八、免责
本包代码仅用于学习与验证。生产环境使用前请自行评估误伤率、重新授权流程
与校验点布置;PBIDEA 组件随版本更新,以你本机实际的导出源码为准。
共享共进共赢
Sharing And Win-win Results
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

免责声明:
本站所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。

Mail To:Admin@SybaseBbs.com

客服微信:18669893686
挂谷猜想 · 探索

QQ|Archiver|PowerBuilder(PB)BBS社区 ( 鲁ICP备2021027222号-1 )

GMT+8, 2026-10-13 06:25 , Processed in 0.029175 second(s), 10 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表