排查麒麟系统.so加载失败:一次从表象到内核的追踪
<p>上周插进去一个UKEY,跑完签名,问题解决了。但整个过程暴露出国产生态下一个典型盲区:我们习惯了在通用Linux上"为所欲为",却低估了定制化OS的深层改造。</p><h2 id="症状这不是权限问题">症状:这不是权限问题</h2>
<p>报错信息很明确:<code>cannot map segment from shared object</code>。第一反应是什么?<code>chmod +x</code> 对吧。没用。</p>
<p>第二反应:SELinux?<code>getenforce</code> 一看, enforcing 模式。兴冲冲 <code>chcon -t bin_t</code> 或者 <code>setenforce 0</code>,还是失败。因为麒麟的强制访问控制不止SELinux这一层,KySec模块工作在内核更深处,<strong>不受SELinux开关影响</strong>。</p>
<p>第三反应:文件完整性?<code>md5sum</code> 对一遍,文件没坏。<code>ldd</code> 检查依赖,也都齐活。这时候就该意识到,<strong>这是信任链断裂,不是技术实现问题</strong>。</p>
<h2 id="定位用strace看清拦截点">定位:用strace看清拦截点</h2>
<p>别猜,直接上 strace:</p>
<pre><code class="language-bash">strace -f -e trace=memory,process java -jar your-app.jar
</code></pre>
<p>关键行:</p>
<pre><code>mprotect(0x7f3c9b3d4000, 8192, PROT_READ|PROT_WRITE|PROT_EXEC) = -1 EPERM (Operation not permitted)
</code></pre>
<p>看到 <code>EPERM</code> 就该明白,<strong>内核拒绝了内存页的执行权限</strong>。这是在 <code>execve</code> 之后的动态加载阶段,KySec 的安全钩子拦截了未签名的 <code>.so</code>。</p>
<p>再查内核日志:</p>
<pre><code class="language-bash">dmesg | grep -i "kysec\|signature"
</code></pre>
<p>会出现类似 <code>kysec: denied execution of unauthenticated binary</code> 的审计信息。铁证如山。</p>
<h2 id="签名命令很简单但顺序有讲究">签名:命令很简单,但顺序有讲究</h2>
<p>拿到 UKEY 后,关键就一条命令:</p>
<pre><code class="language-bash">kylinsigntool --sign --type=so --input=loader_linux_x64.so --output=loader_linux_x64.signed.so
</code></pre>
<p><strong>但工程化时别这么直接</strong>。要注意:</p>
<ol>
<li>
<p><strong>先验证UKEY状态</strong>:<code>pkcs11-tool --list-objects</code> 确认证书已识别。很多人插了KEY但没装驱动,浪费时间。</p>
</li>
<li>
<p><strong>架构必须严格匹配</strong>:在 x86 上签名 ARM64 的 <code>.so</code> 不会报错,但最终加载会失败。建议在 CI 里用 <code>file loader_linux_*.so</code> 自动识别架构,分流到对应的签名机。</p>
</li>
<li>
<p><strong>时间戳参数</strong>:加上 <code>--timestamp=http://timestamp.digicert.com</code>,否则证书过期后签名失效,已发布的软件会变砖。</p>
</li>
<li>
<p><strong>输出不要覆盖原文件</strong>:保留 <code>.signed</code> 后缀,方便构建脚本做缓存判断——"已签名的不再重复签"。</p>
</li>
</ol>
<h2 id="打包那个-m参数救了我">打包:那个"-M"参数救了我</h2>
<p>签名完替换 <code>.so</code>,重新打包 JAR。这里99%的人会踩坑:</p>
<pre><code class="language-bash"># 错误示范
jar -cf0 demo.jar ./*
# 正确示范
jar -cfM0 demo.jar ./*
</code></pre>
<p>差别就在 <code>-M</code>。JAR 的 <code>META-INF/MANIFEST.MF</code> 是类加载的路线图。Virbox Protector 加固时可能已经修改了它。如果不带 <code>-M</code>,<code>jar</code> 命令会生成新的 manifest,<strong>可能丢失关键属性</strong>(比如 <code>Main-Class</code> 或自定义的 <code>Permission</code> 属性),导致 <code>java -jar</code> 找不到入口或类加载策略错乱。</p>
<p><strong>验证打包</strong>:<code>jar -tf demo.jar | grep META-INF</code> 确认 manifest 是你预期的那个,而不是新生成的。</p>
<h2 id="验证签完名不等于能运行">验证:签完名不等于能运行</h2>
<p>签名成功不代表万事大吉。在目标环境做三重检查:</p>
<pre><code class="language-bash"># 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!"
</code></pre>
<p>第三点特别坑:有些跨平台保护工具会预置 macOS 的代码签名,和麒麟签名冲突,导致两者都失效。用 <code>hexdump -C | grep -i signature</code> 扫描一下,有杂签名先清掉。</p>
<h2 id="教训总结">教训总结</h2>
<ol>
<li>
<p><strong>别拿通用Linux的经验套麒麟</strong>:它看起来是Linux,但安全机制是深度定制, <code>setenforce 0</code> 这种万能钥匙不存在。</p>
</li>
<li>
<p><strong>签名是构建流程的一部分</strong>:不是上线前才做的"补充步骤"。要在CI/CD里集成,和编译、测试并列。</p>
</li>
<li>
<p><strong>工具链要本地化</strong>:strace、dmesg、kysec_adm 这些才是麒麟环境调试的"老三样"。GDB在这里用处不大,因为问题出在加载前,而非运行时。</p>
</li>
<li>
<p><strong>保留原始文件</strong>:加固、签名后的文件不要删除源头。某天要升级保护工具版本,没有原始JAR,只能从头再来。</p>
</li>
</ol>
<p>最后提醒一句:UKEY的 PIN 码别写死在构建脚本里。用环境变量或密钥管理服务,否则日志一泄露,证书等于白买。</p><br><br>
来源:https://www.cnblogs.com/virboxprotector/p/19232504
頁:
[1]