排查麒麟系统.so加载失败:一次从表象到内核的追踪

上周插进去一个UKEY,跑完签名,问题解决了。但整个过程暴露出国产生态下一个典型盲区:我们习惯了在通用Linux上"为所欲为",却低估了定制化OS的深层改造。

症状:这不是权限问题

报错信息很明确:cannot map segment from shared object。第一反应是什么?chmod +x 对吧。没用。

第二反应:SELinux?getenforce 一看, enforcing 模式。兴冲冲 chcon -t bin_t 或者 setenforce 0,还是失败。因为麒麟的强制访问控制不止SELinux这一层,KySec模块工作在内核更深处,不受SELinux开关影响

第三反应:文件完整性?md5sum 对一遍,文件没坏。ldd 检查依赖,也都齐活。这时候就该意识到,这是信任链断裂,不是技术实现问题

定位:用strace看清拦截点

别猜,直接上 strace:

strace -f -e trace=memory,process java -jar your-app.jar

关键行:

mprotect(0x7f3c9b3d4000, 8192, PROT_READ|PROT_WRITE|PROT_EXEC) = -1 EPERM (Operation not permitted)

看到 EPERM 就该明白,内核拒绝了内存页的执行权限。这是在 execve 之后的动态加载阶段,KySec 的安全钩子拦截了未签名的 .so

再查内核日志:

dmesg | grep -i "kysec\|signature"

会出现类似 kysec: denied execution of unauthenticated binary 的审计信息。铁证如山。

签名:命令很简单,但顺序有讲究

拿到 UKEY 后,关键就一条命令:

kylinsigntool --sign --type=so --input=loader_linux_x64.so --output=loader_linux_x64.signed.so

但工程化时别这么直接。要注意:

  1. 先验证UKEY状态pkcs11-tool --list-objects 确认证书已识别。很多人插了KEY但没装驱动,浪费时间。

  2. 架构必须严格匹配:在 x86 上签名 ARM64 的 .so 不会报错,但最终加载会失败。建议在 CI 里用 file loader_linux_*.so 自动识别架构,分流到对应的签名机。

  3. 时间戳参数:加上 --timestamp=http://timestamp.digicert.com,否则证书过期后签名失效,已发布的软件会变砖。

  4. 输出不要覆盖原文件:保留 .signed 后缀,方便构建脚本做缓存判断——"已签名的不再重复签"。

打包:那个"-M"参数救了我

签名完替换 .so,重新打包 JAR。这里99%的人会踩坑:

# 错误示范
jar -cf0 demo.jar ./*

# 正确示范
jar -cfM0 demo.jar ./*

差别就在 -M。JAR 的 META-INF/MANIFEST.MF 是类加载的路线图。Virbox Protector 加固时可能已经修改了它。如果不带 -Mjar 命令会生成新的 manifest,可能丢失关键属性(比如 Main-Class 或自定义的 Permission 属性),导致 java -jar 找不到入口或类加载策略错乱。

验证打包jar -tf demo.jar | grep META-INF 确认 manifest 是你预期的那个,而不是新生成的。

验证:签完名不等于能运行

签名成功不代表万事大吉。在目标环境做三重检查:

# 1. 验证签名是否被系统识别
kysec_adm --query --file=loader_linux_x64.signed.so

# 2. 动态检查loader是否被加载
lsof -p $(pgrep java) | grep loader

# 3. 确保没有"双签"冲突
grep -q "CFBundleSignature" loader_linux_x64.signed.so && echo "Warning: macOS signature detected!"

第三点特别坑:有些跨平台保护工具会预置 macOS 的代码签名,和麒麟签名冲突,导致两者都失效。用 hexdump -C | grep -i signature 扫描一下,有杂签名先清掉。

教训总结

  1. 别拿通用Linux的经验套麒麟:它看起来是Linux,但安全机制是深度定制, setenforce 0 这种万能钥匙不存在。

  2. 签名是构建流程的一部分:不是上线前才做的"补充步骤"。要在CI/CD里集成,和编译、测试并列。

  3. 工具链要本地化:strace、dmesg、kysec_adm 这些才是麒麟环境调试的"老三样"。GDB在这里用处不大,因为问题出在加载前,而非运行时。

  4. 保留原始文件:加固、签名后的文件不要删除源头。某天要升级保护工具版本,没有原始JAR,只能从头再来。

最后提醒一句:UKEY的 PIN 码别写死在构建脚本里。用环境变量或密钥管理服务,否则日志一泄露,证书等于白买。

posted @ 2025-11-17 14:10  VirboxProtector  阅读(10)  评论(0)    收藏  举报