nmicic/ephrun

GitHub: nmicic/ephrun

一个用于在不可信 Linux 主机上加密分发和内存执行 ELF 二进制文件的工具,结合多种安全密钥交付方式实现防篡改运行。

Stars: 0 | Forks: 0

# ephrun — 加密 ELF 执行系统 用于 Linux 的加密二进制分发与执行系统,采用 X25519 + XSalsa20-Poly1305 (libsodium sealed boxes)、Linux kernel keyring 进行密钥存储,并利用 memfd 实现防篡改的内存执行。 ## 架构 ``` genkey → pub.bin + priv.bin (X25519 keypair) elfenc_pack → .elfenc file (sealed encrypted ELF) kcap_pack → capsule.bin (KCAP3 code-protected key wrapper) kcap_unpack → capsule.bin → priv (cross-platform CLI unwrap, for tooling) elfdec-run → executes .elfenc (decrypt + memfd + fexecve, Linux only) Key distribution: add_keyring*.sh → local keyring injection elfdec-ssh-pushkey.sh → remote via SSH keypushd / keypush_send → remote via UDP sealed box ``` ## 目录结构 ``` ephrun/ Core encryption/decryption tools keypush/ UDP key distribution (daemon + sender) ``` ## 快速开始 ``` # 安装依赖 (Ubuntu/Debian) sudo apt-get install -y build-essential libsodium-dev libkeyutils-dev # 构建全部 (顶层 Makefile) make # builds core tools + keypush # 或者从 ephrun/ 目录构建 cd ephrun make # builds all tools for this platform make STATIC=1 # static linking (for deployment to remote machines) # 生成 keypair ./genkey # creates pub.bin + priv.bin # 构建并加密测试二进制文件 gcc -O2 hello.c -o hello ./elfenc_pack pub.bin hello hello.elfenc # 运行它 (最简单模式 — 基于文件的 keys) mkdir -p ~/.elfenc cp pub.bin priv.bin ~/.elfenc/ chmod 600 ~/.elfenc/priv.bin ./elfdec-run ./hello.elfenc # → "hello from encrypted ELF!" ``` ## 4 种密钥获取模式 `elfdec-run` 会按以下优先顺序尝试获取私钥。 系统将使用第一个成功获取的密钥,其余的将被跳过。 ``` Priority 1: ELFDEC_CODE → capsule (code-protected key) Priority 2: ELFDEC_KEYID → keyring by numeric key ID Priority 3: ELFDEC_LABEL → keyring by label search Priority 4: ELFDEC_KEYPATH → file-based (priv.bin + pub.bin) or: ~/.elfenc/ → default file fallback ``` ### 模式 1 — Capsule (ELFDEC_CODE) Capsule 使用密码/代码包装私钥。原始的 `priv.bin` 永远不会触及目标磁盘——仅部署加密的 capsule。 在运行时,`elfdec-run` 使用代码在内存中解密 capsule。 **加密:** 使用 Argon2id (默认 M=64 MiB, T=3, P=1) 进行密钥派生的 XChaCha20-Poly1305 AEAD —— `K = Argon2id(code, salt)`。 **工作原理:** ``` ┌──────────────┐ │ kcap_pack │ priv.bin + --code "mysecret" │ (build-time)│ ──────────────────────────────────▶ capsule.bin └──────────────┘ (encrypted priv key) │ deploy capsule.bin to target │ (safe — encrypted, can't be used without code) ▼ ┌──────────────┐ │ elfdec-run │ ELFDEC_CODE="mysecret" ─────────────────────▶│ (runtime) │ │ 1. finds capsule │ 2. derives K from code │ 3. decrypts → priv key │ 4. decrypts .elfenc │ 5. executes via memfd └──────────────┘ ``` **分步示例:** ``` # ── 构建时 (在受信任的机器上) ── # 1. 生成 keypair ./genkey # → pub.bin, priv.bin # 2. 加密你的 binary ./elfenc_pack pub.bin myapp myapp.elfenc # 3. 创建 capsule (用 code 包装 priv.bin) gcc -O2 kcap_pack.c -lsodium -o kcap_pack # KCAP3 binary capsule (kcap_pack 目前写入的唯一输出格式): ./kcap_pack --label prod/myapp --code "s3cret-C0de" --in priv.bin --out capsule.bin # `--json` 已弃用并会打印警告;KCAP3 仅支持 binary。 # `--ttl` 也已弃用 (TTL 曾是 KCAP2 wrapper 关注的问题)。 # 4. 部署到目标:myapp.elfenc + capsule.bin (priv.bin 保留在此处 — 永不部署) # ── 运行时 (在目标机器上) ── # 选项 A:通过 ELFDEC_KEYPATH 将 capsule 存放在磁盘上 mkdir -p /opt/myapp/keys cp capsule.bin /opt/myapp/keys/ # elfdec-run looks for capsule.bin (or legacy capsule.json) here ELFDEC_CODE="s3cret-C0de" ELFDEC_KEYPATH=/opt/myapp/keys ./elfdec-run ./myapp.elfenc # 选项 B:将 capsule 存储在 kernel keyring 中 (无需磁盘文件) keyctl padd user "elfdec_caps:prod/myapp" @s < capsule.bin ELFDEC_CODE="s3cret-C0de" ELFDEC_LABEL="prod/myapp" ./elfdec-run ./myapp.elfenc # ▲ # 在 keyring 中搜索 "elfdec_caps:prod/myapp",使用 code 解密 ``` **在 elfdec-run 中的 Capsule 查找顺序:** 1. Keyring:在 `@s` 然后在 `@u` 中搜索名为 `elfdec_caps:` 的密钥 2. 文件:`$ELFDEC_KEYPATH/capsule.bin`,然后是 `$ELFDEC_KEYPATH/capsule.json` ### 模式 2 — 按 ID 查找 Keyring (ELFDEC_KEYID) 当您知道确切的数字密钥 ID 时使用(例如由 `keyctl padd` 或 `keypushd` 返回)。 ``` # 将 raw private key 注入到 session keyring 中 KEYID=$(keyctl padd user "elfdec:prod/myapp" @s < priv.bin) keyctl setperm "$KEYID" 0x3f030000 # owner: view+read, possessor: all keyctl timeout "$KEYID" 300 # auto-expire in 5 minutes # 使用数字 key ID 运行 ELFDEC_KEYID="$KEYID" ./elfdec-run ./myapp.elfenc ``` ### 模式 3 — 按 Label 查找 Keyring (ELFDEC_LABEL) 当密钥位于具有已知 label 的 keyring 中时使用。`elfdec-run` 会先在 `@s` (session) 然后在 `@u` (user) 中搜索名为 `elfdec:` 的密钥。 ``` # 注入带 label 的 key keyctl padd user "elfdec:prod/myapp" @s < priv.bin keyctl setperm "$(keyctl search @s user 'elfdec:prod/myapp')" 0x3f030000 # 使用 label 运行 — elfdec-run 会搜索 "elfdec:prod/myapp" ELFDEC_LABEL="prod/myapp" ./elfdec-run ./myapp.elfenc # 如果未设置 ELFDEC_LABEL,则使用 .elfenc 文件路径作为 label ./elfdec-run ./myapp.elfenc # searches for "elfdec:" ``` **用于 keyring 注入的辅助脚本:** - `add_keyring4.sh` — 功能全面:root/用户检测、权限回退循环、可访问性测试 - `add_keyring8.sh` — 简洁的生产版本:session keyring、权限设置、TTL、`@u` 链接 ### 模式 4 — 基于文件 (ELFDEC_KEYPATH 或 ~/.elfenc/) 最简单的模式。磁盘上只需要 `priv.bin` —— 公钥会通过 `crypto_scalarmult_base()` 自动派生。通过文件系统权限进行保护。 ``` # 自定义 key 目录 sudo mkdir -p /etc/elfenc sudo cp priv.bin /etc/elfenc/ sudo chmod 600 /etc/elfenc/priv.bin ELFDEC_KEYPATH=/etc/elfenc ./elfdec-run ./myapp.elfenc # 默认目录 (~/.elfenc/) — 无需环境变量 mkdir -p ~/.elfenc cp priv.bin ~/.elfenc/ chmod 600 ~/.elfenc/priv.bin ./elfdec-run ./myapp.elfenc # automatically finds ~/.elfenc/priv.bin, derives pub ``` ## 环境变量参考 | 变量 | 使用者 | 描述 | |----------|---------|-------------| | `ELFDEC_CODE` | elfdec-run | 用于解密 capsule 的密码/代码(触发模式 1) | | `ELFDEC_KEYID` | elfdec-run | 来自 kernel keyring 的数字密钥 ID(模式 2) | | `ELFDEC_LABEL` | elfdec-run | 用于 keyring 搜索和 capsule 查找的密钥 label(模式 3 / 模式 1) | | `ELFDEC_KEYPATH` | elfdec-run | 包含 `{priv,pub}.bin` 或 `capsule.{bin,json}` 的目录(模式 4 / 模式 1) | ## 密钥分发方式 ### 本地:add_keyring 脚本 ``` # 全功能 (检测 root 与普通用户,尝试多种 permission masks) bash add_keyring4.sh # 精简的生产版本 bash add_keyring8.sh ``` ### 通过 SSH 远程:elfdec-ssh-pushkey.sh 通过 SSH stdin 将原始密钥字节流式传输到远程 kernel keyring 中。 密钥永远不会触及远程磁盘。 ``` # 将 key 推送到远程主机 (默认 TTL:300s) ./elfdec-ssh-pushkey.sh -H user@remote -l prod/myapp -k priv.bin # 使用自定义 TTL ./elfdec-ssh-pushkey.sh -H user@remote -l prod/myapp -k priv.bin -t 600 # 返回 KEYID — 在远程机器上使用它: # ELFDEC_KEYID= /usr/local/bin/elfdec-run ./myapp.elfenc ``` ### 通过 UDP 远程:keypushd + keypush_send 用于通过网络进行自动化/程序化的密钥分发。 **协议流程:** ``` ┌─────────────┐ ┌─────────────┐ │ keypushd │ 1. prints bootstrap JSON │ operator │ │ (target) │ ◀─── stdout ────────────▶ │ (control) │ │ │ {ip, port, srv_pk, │ │ │ │ token, expires} │ │ │ │ │ │ │ │ 2. sealed UDP datagram │ keypush_send │ │ recvfrom() │ ◀──── network ─────────── │ (sender) │ │ │ crypto_box_seal( │ │ │ │ {label, ttl, token, │ │ │ │ key_b64}) │ │ │ │ │ │ │ add_key() │ 3. ACK/NAK JSON reply │ │ │ keyring @s │ ─────── UDP ──────────▶ │ │ └─────────────┘ {ok, keyid, ttl} └─────────────┘ ``` **`srv_pk` (服务器公钥) 不是预先生成的** —— keypushd 每次启动时都会创建一个临时的 X25519 密钥对,并在 bootstrap JSON 中以 base64 格式打印出公钥。 操作员将 `srv_pk` 和 `token` 复制到发送方(带外传输:终端、管道等)。 ``` # 构建 (同一个目录下的 keypushd + keypush_send) cd keypush && make # ── 步骤 1:在目标机器上启动 daemon ── ./keypushd --bind 0.0.0.0 --port 9999 --label prod/myapp --ttl 300 # 输出 (示例): # {"ip":"0.0.0.0","port":9999,"srv_pk":"xK7b+...BASE64...","token":"K3J7QRST...","expires":1706500000} # ▲ ▲ # │ │ # ephemeral pubkey (base64) one-time auth token (base32) # 将此复制到 --srv-pk-b64 将此复制到 --token # 包含所有选项的完整示例: ./keypushd --bind 10.8.0.2 --port 0 --label prod/myapp --ttl 600 --window 120 --link-user --detach # ▲ port=0 选择一个随机空闲端口 ▲ window = bootstrap expiry (秒) # ── 步骤 2:从控制机器发送 key ── cat priv.bin | ./keypush_send \ --ip 10.8.0.2 --port 9999 \ --srv-pk-b64 "xK7b+...BASE64..." \ --token "K3J7QRST..." \ --label prod/myapp --ttl 300 --wait-ack # 输出 (如果使用 --wait-ack):{"ok":true,"keyid":827867509,"ttl":300} # ── 步骤 3:在目标机器上运行 ── ELFDEC_LABEL="prod/myapp" ./elfdec-run ./myapp.elfenc ``` **keypushd CLI 选项:** | 标志 | 默认值 | 描述 | |------|---------|-------------| | `--bind IP` | *(必填)* | 绑定 UDP socket 的 IP 地址 | | `--port N` | `0` (随机) | UDP 端口;0 = 由操作系统选择空闲端口 | | `--label L` | *(来自 payload)* | 如果发送方省略,则使用的默认 label | | `--ttl N` | `300` | 最大密钥 TTL(以秒为单位) | | `--window N` | `60` | bootstrap token 过期前的秒数 | | `--detach` | off | 打印 bootstrap JSON 后 Fork 到后台 | | `--link-user` | off | 同时将密钥链接到 `@u` (user keyring) 以实现持久化 | **keypush_send CLI 选项:** | 标志 | 默认值 | 描述 | |------|---------|-------------| | `--ip IP` | *(必填)* | 目标 keypushd IP 地址 | | `--port N` | *(必填)* | 目标 keypushd UDP 端口 | | `--srv-pk-b64 B64` | *(必填)* | 服务器公钥(来自 bootstrap JSON `srv_pk` 字段的 base64) | | `--token T` | *(必填)* | 认证 token(来自 bootstrap JSON `token` 字段的 base32) | | `--label L` | *(必填)* | 密钥 label(例如 `prod/myapp`) | | `--ttl N` | `300` | 请求的密钥 TTL(以秒为单位,服务器会根据其 `--ttl` 最大值进行限制) | | `--wait-ack` | off | 等待最多 3 秒以获取 ACK/NAK JSON 回复 | ## binfmt_misc — 透明的 .elfenc 执行 向内核注册 ELFENC1 魔数,以便可以直接执行 `.elfenc` 文件: ``` sudo bash register_elfenc.sh # 现在 .elfenc 文件可以像常规可执行文件一样工作: chmod +x myapp.elfenc ELFDEC_LABEL="prod/myapp" ./myapp.elfenc ``` ## 文件参考 ### 核心工具 | 文件 | 编译 | 描述 | |------|-------|-------------| | `ephrun/genkey.c` | `gcc -O2 genkey.c -lsodium -o genkey` | 生成 X25519 密钥对 (pub.bin + priv.bin) | | `ephrun/elfenc_pack.c` | `gcc -O2 elfenc_pack.c -lsodium -o elfenc_pack` | 加密 ELF → ELFENC1 格式 | | `ephrun/kcap_pack.c` | `gcc -O2 kcap_pack.c -lsodium -o kcap_pack` | 创建 KCAP3 capsule(由 Argon2id 包装的密钥,参数在 header 中) | | `ephrun/kcap_unpack.c` | `gcc -O2 kcap_unpack.c -lsodium -o kcap_unpack` | 跨平台 CLI:解包 KCAP1/KCAP2/KCAP3 → 私钥(用于工具/CI) | | `ephrun/elfdec-run.c` | `gcc -O2 -D_GNU_SOURCE elfdec-run.c -lsodium -lkeyutils -o elfdec-run` | 解密并执行 .elfenc(4 级密钥获取;D-16 加固回退,D-17 环境变量清除,D-18 ptrace 检查) | | `ephrun/hello.c` | `gcc -O2 hello.c -o hello` | 用于流水线的最小化测试二进制文件 | ### 库 / 头文件 | 文件 | 描述 | |------|-------------| | `ephrun/kcap.h` | 共享的 KCAP capsule header(KCAP1/KCAP2/KCAP3 分发,Argon2id + 遗留 SHA256 KDF,D-15 参数策略下限/上限) | | `ephrun/libexec_key.h` | 仅头文件的密钥加载库(文件、keyring、环境变量、capsule) | ### 密钥分发 | 文件 | 描述 | |------|-------------| | `ephrun/add_keyring4.sh` | 功能全面的本地 keyring 设置(root/用户,权限回退,测试) | | `ephrun/add_keyring8.sh` | 简洁的生产环境 keyring 设置 | | `ephrun/elfdec-ssh-pushkey.sh` | 通过 SSH stdin 将密钥推送到远程主机 | | `keypush/keypushd.c` | UDP 密钥接收 daemon(仅限 Linux) | | `keypush/keypush_send.c` | UDP 密钥发送方(跨平台:Linux + macOS) | ### 测试与诊断 | 文件 | 编译 | 描述 | |------|-------|-------------| | `test.sh` | `bash test.sh` | **完整的端到端测试套件** —— 构建所有工具,测试全部 4 种密钥模式,包含负面测试 | | `ephrun/test_key.c` | `gcc -O2 test_key.c -lkeyutils -o test_key` | 验证 keyring 中是否存在某个密钥 | | `ephrun/keyring_selftest.c` | `gcc -O2 -D_GNU_SOURCE keyring_selftest.c -lsodium -lkeyutils -o keyring_selftest` | 自测:keyring 添加/读取 + crypto_secretbox 往返测试 | | `ephrun/keyring_crypto_test.c` | `make` (在 ephrun/ 目录下) | 使用 keyring 获取的密钥进行 AES-256-CBC 往返测试 (OpenSSL) | ### 辅助脚本 | 文件 | 描述 | |------|-------------| | `ephrun/install.sh` | 完整安装:依赖 + 构建 + 密钥生成 + 测试加密/运行 | | `ephrun/elf_comp.sh` | 编译 elfdec-run + 安装密钥到 /etc/elfenc | | `ephrun/genkey.sh` | 一步完成编译并运行 genkey | | `ephrun/run.sh` | 测试脚本:KEYID 和 LABEL 执行模式 | | `ephrun/register_elfenc.sh` | 通过 binfmt_misc 注册 .elfenc | ## 依赖项 | 软件包 | 使用者 | 用途 | |---------|---------|---------| | `libsodium-dev` | 所有 C 程序 | X25519, XSalsa20-Poly1305, XChaCha20, SHA256 | | `libkeyutils-dev` | elfdec-run, keypushd, 测试 | Linux kernel keyring API | | `libssl-dev` | 仅 keyring_crypto_test | OpenSSL EVP (AES-256-CBC) | | `build-essential` | 所有 | GCC 工具链 | ``` # Ubuntu/Debian sudo apt-get install -y build-essential libsodium-dev libkeyutils-dev libssl-dev # RHEL/CentOS/Fedora sudo yum install -y keyutils-libs-devel libsodium-devel gcc openssl-devel # macOS (仅限 keypush_send — keypushd 和 elfdec-run 需要 Linux keyring) brew install libsodium ``` ## 安全特性 - **传输:** X25519 + XSalsa20-Poly1305 认证加密 (sealed boxes) - **密钥存储:** Linux kernel keyring(受内存保护、进程隔离、支持 TTL) - **执行:** Sealed memfd (MFD_EXEC + F_SEAL_SHRINK/GROW/WRITE) —— 二进制文件永远不会触及磁盘 - **进程加固:** PR_SET_DUMPABLE=0, PR_SET_NO_NEW_PRIVS=1 - **内存安全:** 对所有敏感数据使用 sodium_memzero,对解密的 capsule 密钥使用 sodium_mlock - **Capsule 加密:** 使用 Argon2id 密钥派生的 XChaCha20-Poly1305 AEAD;KCAP3 在 header 中存储 T/M/P(默认 T=3, M=64 MiB, P=1),并通过 AAD 绑定整个 64 字节的 header。KCAP2 (Argon2id,固定参数) 和 KCAP1 (SHA256 KDF) 仍可读取以用于迁移。 - **构建加固:** -fstack-protector-strong, -D_FORTIFY_SOURCE=2, -Wl,-z,relro,-z,now - **禁用核心转储:** keypushd 中将 RLIMIT_CORE 设置为零;elfdec-run 中设置 PR_SET_DUMPABLE=0 - **可移植的二进制格式:** ELFENC1, KCAP1, KCAP2, KCAP3 使用显式的小端序编码以实现跨架构兼容性 - **`/dev/shm` 回退加固 (D-16):** 仅在发生 `fexecve` ETXTBSY 时触发;临时文件在执行 `execveat(rofd, "", argv, envp, AT_EMPTY_PATH)` 之前被取消链接,因此工作负载从一个无法访问的 inode 运行;如果执行失败,该 将通过 `/proc/self/fd/N` 被擦除。O_EXCL 保护了短暂的 tmpfs 创建窗口。需要 Linux ≥ 3.19。 - **环境变量清除 (D-17):** `elfdec-run` 在执行任何 `fexecve` / `execveat` 之前,会从 `envp` 中剥离 `ELFDEC_CODE / KEYID / LABEL / KEYPATH / CAP / ALLOW_TRACE`,因此工作负载的 `/proc//environ` 不会泄漏解包输入数据。 - **Ptrace 防御 (D-18):** `elfdec-run` 在启动时读取 `/proc/self/status`,如果 `TracerPid != 0`,则在进行任何解密工作之前中止。`ELFDEC_ALLOW_TRACE=1` 可在开发环境下绕过。拥有 root 权限的用户可以通过操纵 `/proc` 来击败此防御;这只是为了提高攻击门槛,并非安全保证。 ## 检测 Docker / VM 环境 在部署到租用的服务器时,了解您是在容器(较弱的隔离)还是真实的 VM 中运行非常重要。ephrun 可以保护磁盘上的二进制文件,但拥有 root 权限的 Docker 宿主机可以检查进程内存。 ``` # 快速检查 — 在目标机器上运行以下任意或全部命令: # 1. Docker sentinel 文件 (最可靠) ls -la /.dockerenv 2>/dev/null && echo "DOCKER" || echo "not docker (by sentinel)" # 2. Cgroup — 在 cgroup 路径中查找 "docker" 或 "containerd" cat /proc/1/cgroup 2>/dev/null | grep -qiE 'docker|containerd' && echo "DOCKER" || echo "not docker (by cgroup)" # 3. systemd-detect-virt (如果已安装) # 返回 "docker"、"lxc"、"kvm"、"vmware"、"xen"、"none" 等。 systemd-detect-virt 2>/dev/null || echo "(not installed)" # 4. 容器特定的 mounts mount | grep -q 'overlay' && echo "likely container (overlay fs)" # 5. PID 1 — 在容器中通常不是 systemd/init cat /proc/1/cmdline | tr '\0' ' ' | head -c 200 # systemd 或 /sbin/init → 真实 VM; /bin/sh 或自定义 entrypoint → 容器 # 6. DMI / 产品名称 (VM 会暴露 hypervisor 信息) cat /sys/class/dmi/id/product_name 2>/dev/null # "QEMU"、"VMware Virtual Platform"、"VirtualBox" 等 → 真实 VM # 空或权限被拒绝 → 可能是容器 # 7. Kernel keyring — 容器通常具有受限的 keyring 访问权限 keyctl show @s 2>/dev/null && echo "keyring OK" || echo "keyring restricted (container?)" ``` **总结:** - 容器 (Docker/LXC):宿主机对你的进程拥有 root 权限 —— 请使用较短的密钥 TTL,在执行前才推送密钥,尽量缩短执行窗口期 - 真实的 VM (KVM/QEMU/VMware):更强的隔离 —— 宿主机仍然可以快照 RAM,但需要通过内存取证才能提取内容 - 在您不拥有的硬件上,两者都不是绝对安全的;ephrun 将攻击门槛从“复制文件”提高到了“实时的内存取证” ## 开发说明 本项目是 AI 辅助的 —— 不过在 2026 年,有什么不是呢?设计、代码和发布决策仍由维护者掌握。
标签:C/C++, DNS 反向解析, HTTP工具, Raspberry Pi, 事务性I/O, 内存执行, 可执行文件加密, 客户端加密, 密码学, 密钥分发, 手动系统调用