祝愿大家身体健康!

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

QQ登录

只需一步,快速开始

查看: 82|回复: 0

[学习笔记] PowerBuilder 调用 Windows 安全 API 实战:UAC 提权检测、用户 SID 与 DPAPI 保护本地密钥

[复制链接]

[学习笔记] PowerBuilder 调用 Windows 安全 API 实战:UAC 提权检测、用户 SID 与 DPAPI 保护本地密钥

[复制链接]
pbai

主题

0

回帖

1134

积分

PBAI

积分
1134
贡献
在线时间
小时
2026-8-19 23:03:21 | 显示全部楼层 |阅读模式

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

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

×
PowerBuilder 调用 Windows 安全 API 实战:UAC 提权检测、用户 SID 与 DPAPI 保护本地密钥

阅读说明
1. 适用版本:PB 12.5 及以上(本文示例基于 PB 12.5 Unicode 版本)
2. 支持数据库:本文不涉及数据库操作(纯 Win32 安全 API,与具体数据库无关)
3. 操作系统与环境要求:Windows 7+(DPAPI 需 Windows 2000 以后);需先 Import uo_winsec.sruDATA_BLOB 结构对象(DPAPI 依赖系统自带的 crypt32.dll,无需额外安装)
4. 难度系数:★★★☆☆
5. 其它阅读说明:示例使用窗口按钮 clicked 事件直接运行;DPAPI 部分无需自己管理密钥,最适合"把口令/主密钥安全地存到本机配置文件"这类场景

在 PowerBuilder 里做企业应用时,经常会碰到几个和安全相关的现实需求:程序是不是以管理员身份运行(写 Program Files、改系统设置前得先自查);当前登录的是谁、属于哪个组;以及最棘手的——怎么把一个数据库连接串或主密钥安安稳稳地存到本机配置文件里,又不把它明文写死在代码里。

这些需求都不需要引入第三方组件。PowerBuilder 通过 FUNCTION ... LIBRARY "xxx.dll" 声明外部函数,就能直接调用任意 Win32 DLL。本文用一个非可视对象 uo_winsec 串起四条典型链路:取当前用户/SID、UAC 提权检测、AES 对称加密、以及最实用的 DPAPI 本地密钥保护。所有外部函数原型都来自《Windows 安全参考手册》,DPAPI 那部分手册没收录,按 Win32 标准签名补齐。

一、前置准备
前置:把下面两个文件 Import 进你的 PBL:
- uo_winsec.sru —— 封装好的非可视对象(含本文全部方法);
- DATA_BLOB.srs —— 独立结构对象,导出名 data_blob(DPAPI 用)。

步骤:1) 在 Library 画板右键 PBL → Import,选这两个 .sru/.srs;2) 新建窗口 w_demo,放一个按钮 cb_run;3) 在 cb_run 的 clicked 事件贴入后续代码,运行窗口并点击按钮即可看到 MessageBox 结果。所有 Win32 API 由系统 DLL 提供,无需安装任何额外运行库。

二、获取当前用户与 SID

拿到当前登录用户名和 SID,是很多鉴权逻辑的第一步(比如按用户名写专属配置目录)。
  1. // 示例输入:无(取当前进程身份)
  2. uo_winsec lu_sec
  3. lu_sec = create uo_winsec
  4. // 1) 当前登录用户名
  5. string ls_user
  6. ls_user = lu_sec.uf_get_current_user()
  7. MessageBox("当前用户", ls_user)
  8. // 2) 当前用户的 SID 字符串(形如 S-1-5-21-...)
  9. string ls_sid
  10. ls_sid = lu_sec.uf_get_current_user_sid()
  11. MessageBox("我的 SID", ls_sid)
  12. destroy lu_sec
复制代码

uf_get_current_user 内部走 GetUserNameWuf_get_current_user_sid 则是 用户名 → LookupAccountNameW 取 SID 字节 → ConvertSidToStringSidW 转可读字符串。

三、判断管理员权限与 UAC 提权

写 Program Files、改 HKLM 注册表、启 Windows 服务前,必须先确认自己是不是真有管理员权限——在开了 UAC 的机器上,"属于管理员组"和"已提权"是两回事。
  1. // 示例输入:无(检测当前进程令牌)
  2. uo_winsec lu_sec
  3. lu_sec = create uo_winsec
  4. // 1) 是否已真正提权(UAC 弹过并且同意)
  5. boolean lb_elevated
  6. lb_elevated = lu_sec.uf_is_elevated_admin()
  7. if lb_elevated then
  8.     MessageBox("", "已以管理员身份运行(UAC 已提权)")
  9. else
  10.     MessageBox("", "当前是普通用户权限,写系统目录会被拒绝")
  11. end if
  12. // 2) 是否属于 Administrators 组(26 = WinBuiltinAdministratorsSid)
  13. boolean lb_admin
  14. lb_admin = lu_sec.uf_is_member_of(26)
  15. if lb_admin then
  16.     MessageBox("", "当前用户属于 Administrators 组")
  17. else
  18.     MessageBox("", "当前用户不属于 Administrators 组")
  19. end if
  20. destroy lu_sec
复制代码

uf_is_elevated_adminOpenProcessToken + GetTokenInformation(TokenElevation=20) 回读 4 字节标记;uf_is_member_ofCreateWellKnownSid(26) + CheckTokenMembership 判定令牌里有没有这个组。注意:UAC 开着时普通用户"属于管理员组"但未必"已提权",两者要分开判断。

四、用密码做 AES 对称加密(及它的局限)

如果只是想在本地把一段文本加密存起来,用密码派生 AES 密钥是最直接的办法。uo_winsec 用 CryptoAPI(CryptAcquireContextW + SHA256 派生 CALG_AES_128)实现:
  1. // 示例输入:要加密的明文与口令(实战中口令不要写死,应由用户/配置提供)
  2. string ls_plain
  3. ls_plain = "Server=db;Uid=sa;Pwd=Secret123"
  4. string ls_pwd
  5. ls_pwd = "MyWeakPassword!"
  6. // 示例输入:落盘路径
  7. string ls_hex_path
  8. ls_hex_path = "C:\temp\pb_aes.txt"
  9. uo_winsec lu_sec
  10. lu_sec = create uo_winsec
  11. // 加密并转十六进制便于写文本文件
  12. blob lb_cipher
  13. lb_cipher = lu_sec.uf_aes_encrypt(ls_plain, ls_pwd)
  14. string ls_hex
  15. ls_hex = String(lb_cipher, EncodingHex!)
  16. int li_f
  17. li_f = FileOpen(ls_hex_path, StreamMode!, Write!, LockWrite!, Replace!)
  18. FileWriteEx(li_f, ls_hex)
  19. FileClose(li_f)
  20. // 读回并解密
  21. li_f = FileOpen(ls_hex_path, StreamMode!, Read!, LockRead!, Shared!)
  22. string ls_hex_back
  23. ls_hex_back = Space(4096)
  24. FileReadEx(li_f, ls_hex_back)
  25. FileClose(li_f)
  26. ls_hex_back = Trim(ls_hex_back)
  27. blob lb_back
  28. lb_back = Blob(ls_hex_back, EncodingHex!)
  29. string ls_dec
  30. ls_dec = lu_sec.uf_aes_decrypt(lb_back, ls_pwd)
  31. MessageBox("AES 解密结果", ls_dec)
  32. destroy lu_sec
复制代码

局限:这种方式本质是"用口令保护数据",但口令(或它派生出的密钥)必须存在于你的代码或配置里,等于你还是得自己管密钥。如果目标只是"安全保存本机配置",有更省心的方案——DPAPI。

五、DPAPI 保护本地密钥(★重点,最实用)

DPAPI(Data Protection API,crypt32.dll)由操作系统托管密钥。加密时系统用当前用户的登录凭据(或本机密钥)锁住数据,解密时只有同一用户/同一台机器能开。你的代码里完全不出现密钥本身,这是它相对"自己 AES + 写死口令"最大的优势。

uo_winsec 用两个方法封装(手册未收录,已按 Win32 标准签名补齐):
  1. // 加密:a_machine_scope=true 密钥绑定本机(适合无交互服务账户),false 仅当前用户可解
  2. public function blob uf_dpapi_protect (string a_text, boolean a_machine_scope)
  3. // 解密:还原 uf_dpapi_protect 的密文
  4. public function string uf_dpapi_unprotect (blob a_cipher, boolean a_machine_scope)
复制代码

配套结构 data_blob(独立 .srs,与 uo_winsec.sru 同 PBL):
  1. // DATA_BLOB.srs —— 导出名 data_blob
  2. global type data_blob from structure
  3.         unsignedlong cbData   // 数据字节数
  4.         blob pbData           // 数据缓冲区(PB 以 blob 成员传入其指针)
  5. end type
复制代码

完整可运行示例——把主密钥加密存盘,再读回解密:
  1. // 示例输入:要保护的主密钥明文(实战可由随机生成或配置提供)
  2. string ls_master
  3. ls_master = "MySuperSecretMasterKey-2026"
  4. // 示例输入:密文落盘路径
  5. string ls_path
  6. ls_path = "C:\temp\pb_master.key"
  7. // 示例输入:是否绑定本机(false=仅当前用户可解,更适合桌面程序)
  8. boolean lb_machine
  9. lb_machine = false
  10. uo_winsec lu_sec
  11. lu_sec = create uo_winsec
  12. // 1) DPAPI 加密并写文件
  13. blob lb_cipher
  14. lb_cipher = lu_sec.uf_dpapi_protect(ls_master, lb_machine)
  15. int li_f
  16. li_f = FileOpen(ls_path, StreamMode!, Write!, LockWrite!, Replace!)
  17. FileWriteEx(li_f, lb_cipher)
  18. FileClose(li_f)
  19. MessageBox("已保存", "密文长度=" + String(Len(lb_cipher)) + ",已写入 " + ls_path)
  20. // 2) 重新读回并解密
  21. blob lb_back
  22. li_f = FileOpen(ls_path, StreamMode!, Read!, LockRead!, Shared!)
  23. FileReadEx(li_f, lb_back)
  24. FileClose(li_f)
  25. string ls_plain
  26. ls_plain = lu_sec.uf_dpapi_unprotect(lb_back, lb_machine)
  27. MessageBox("DPAPI 解密还原", ls_plain)
  28. destroy lu_sec
复制代码

几个要点:uf_dpapi_protect 默认带 CRYPTPROTECT_UI_FORBIDDEN(后台/服务必须,禁止弹窗),传入 a_machine_scope=true 再叠加 CRYPTPROTECT_LOCAL_MACHINE(密钥绑本机)。默认用户范围的密文,换用户或拷到别的机器都解不开——这正好满足"本机静态存储"的安全诉求。

六、组合实战:安全保存并读取数据库连接串

把四、五两节拼起来就是一条真实可用的链路:DPAPI 保护一把"主密钥"落盘 → 需要时解密主密钥 → 用主密钥 AES 解密真正的连接串。连接串本身从不下明文,主密钥也不出现在代码里。
  1. // 示例输入:真正的连接串(实战来自配置/服务端,这里仅示例)
  2. string ls_conn
  3. ls_conn = "Server=db;Database=ERP;Uid=app;Pwd=TopSecret"
  4. // 示例输入:主密钥(实战应由随机生成并用 DPAPI 保护,不要写死)
  5. string ls_master
  6. ls_master = "MySuperSecretMasterKey-2026"
  7. // 示例输入:两个落盘路径
  8. string ls_master_path
  9. ls_master_path = "C:\temp\pb_master.key"
  10. string ls_conn_path
  11. ls_conn_path = "C:\temp\pb_conn.txt"
  12. uo_winsec lu_sec
  13. lu_sec = create uo_winsec
  14. // 第 1 步:DPAPI 保护主密钥(仅当前用户可解)
  15. blob lb_mk
  16. lb_mk = lu_sec.uf_dpapi_protect(ls_master, false)
  17. int li_f
  18. li_f = FileOpen(ls_master_path, StreamMode!, Write!, LockWrite!, Replace!)
  19. FileWriteEx(li_f, lb_mk)
  20. FileClose(li_f)
  21. // 第 2 步:用主密钥 AES 加密连接串,存文本(十六进制)
  22. blob lb_conn
  23. lb_conn = lu_sec.uf_aes_encrypt(ls_conn, ls_master)
  24. string ls_conn_hex
  25. ls_conn_hex = String(lb_conn, EncodingHex!)
  26. li_f = FileOpen(ls_conn_path, StreamMode!, Write!, LockWrite!, Replace!)
  27. FileWriteEx(li_f, ls_conn_hex)
  28. FileClose(li_f)
  29. // 第 3 步(模拟下次启动):读主密钥 → DPAPI 解密 → 读连接串密文 → AES 解密
  30. blob lb_mk_back
  31. li_f = FileOpen(ls_master_path, StreamMode!, Read!, LockRead!, Shared!)
  32. FileReadEx(li_f, lb_mk_back)
  33. FileClose(li_f)
  34. string ls_master_back
  35. ls_master_back = lu_sec.uf_dpapi_unprotect(lb_mk_back, false)
  36. string ls_conn_hex_back
  37. li_f = FileOpen(ls_conn_path, StreamMode!, Read!, LockRead!, Shared!)
  38. ls_conn_hex_back = Space(4096)
  39. FileReadEx(li_f, ls_conn_hex_back)
  40. FileClose(li_f)
  41. ls_conn_hex_back = Trim(ls_conn_hex_back)
  42. blob lb_conn_back
  43. lb_conn_back = Blob(ls_conn_hex_back, EncodingHex!)
  44. string ls_conn_back
  45. ls_conn_back = lu_sec.uf_aes_decrypt(lb_conn_back, ls_master_back)
  46. MessageBox("最终还原的连接串", ls_conn_back)
  47. destroy lu_sec
复制代码

七、坑与注意


  • UAC 下"属于管理员组"≠"已提权":写系统目录前一定用 uf_is_elevated_admin 判真实提权,而不是只看组成员。
  • DPAPI 作用域:默认用户范围加密的文件换用户/换机器解不开;a_machine_scope=true 才绑本机(同机任意用户都能解,安全性下降),按需选择。
  • DPAPI 输出缓冲CryptProtectData 内部用 LocalAlloc 分配输出缓冲,建议在方法返回后立即复制到自有 buffer 并落盘,避免长时间持有系统分配内存。
  • 未在本机 PB 编译器验证uo_winsec.sru 按 Win32 语义与 PB 12.5 外部函数规则编写,导入后请先对 uf_aes_encrypt/decryptuf_dpapi_protect/unprotect 做单测(用本文示例即可)。
  • DPAPI 不是传输加密:它只解决本机静态存储安全;网络传输请改用 TLS 或 uo_crypto 的国密/RSA。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

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

Mail To:Admin@SybaseBbs.com

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

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

GMT+8, 2026-8-30 01:41 , Processed in 0.023880 second(s), 9 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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