daisvke/woody-woodpacker
GitHub: daisvke/woody-woodpacker
一个用 C 语言实现的 ELF 64 位二进制打包器,通过对原始代码加密并注入运行时解包 stub 来转换可执行文件格式。
Stars: 0 | Forks: 0
# **woody-woodpacker**
## **目录**
* [概述](#overview)
* [执行流程](#execution-flow)
* [运行时行为](#runtime-behavior)
* [ELF 64-bit 文件结构](#elf-64-bit-file-structure)
* [注入机制](#injection-mechanism)
* [Stub 补丁系统](#stub-patch-system)
* [入口点重定向](#entry-point-redirection)
* [加密系统](#encryption-system)
* [命令](#commands)
* [自动化测试套件](#automated-test-suite)
* [调试工具](#debug-tools)
* [兼容性说明](#compatibility-notes)
# **概述**
本项目实现了一个**支持运行时解包的 64-bit ELF packer**。
它将一个 ELF 二进制文件转换为一个新的可执行文件(`woody`),其中嵌入了:
* 原始可执行 segment 的加密副本
* 注入到二进制文件中的自定义解包 stub
在运行时,stub 会在内存中重建原始程序并将执行权交还给它,从而在仅改变二进制表示形式的同时,保持完全一致的行为。
# **执行流程**
```
Original ELF
→ Parse ELF headers & program headers
→ Locate executable PT_LOAD segment
→ Extract segment (includes .text region)
→ Encrypt payload (XOR + additive cipher)
→ Inject stub into executable segment region
(padding or shifting mode)
→ Patch stub with metadata (offsets, sizes, key)
→ Modify ELF entry point (e_entry → stub)
→ Generate "woody"
Runtime:
→ Kernel loads ELF
→ Execution starts in stub
→ Stub decrypts payload in memory
→ Restores original code
→ Prints: ....WOODY.....
→ Jumps to original entry point
→ Original program executes
```
# **运行时行为**
在执行阶段,一个打包后的 `/bin/whoami` 表现如下:
```
....WOODY.....
username
```
除了打印出的字符串外,最终行为与原始二进制文件保持完全一致。
# **ELF 64-bit 文件结构**
```
+----------------------------------------------------+
| ELF Header |
| (64 bytes, ELF64_Ehdr structure) |
+----------------------------------------------------+
| Program Header Table |
| (Variable size, ELF64_Phdr entries) |
| describes memory-mapped segments |
+----------------------------------------------------+
| Text Section |
| (Executable code inside PT_LOAD segment) |
+----------------------------------------------------+
| Data Section |
| (Initialized data inside PT_LOAD segment) |
+----------------------------------------------------+
| Symbol Table Section |
| (ELF64_Sym entries) |
+----------------------------------------------------+
| String Table Section |
| ("name1\0name2\0...") |
+----------------------------------------------------+
| Section Header Table (optional) |
| (ELF64_Shdr entries) |
+----------------------------------------------------+
```
# **注入机制**
stub 被注入到**可执行 PT_LOAD segment**中,该 segment 通常包含:
* `.text`
* 可执行数据页
* segment 之间的 padding
它不限于仅在 `.text` 中,而是放置在**可执行内存映射区域**内部。
## **1. Padding 注入(首选模式)**
当可执行 segment 内部存在足够空间时使用。
条件:
```
sizeof_stub ≤ padding_size
```
其中:
```
padding_size = next_segment.p_offset - injection_offset
```
行为:
* stub 直接写入到现有的 padding 中
* 无需 ELF 重定位
* 无需对 headers 进行结构性修改
```
[ PT_LOAD (code + .text) | padding | STUB ]
```
## **2. 位移注入(后备模式)**
当 padding 空间不足或被明确要求时使用。
### 步骤 1 — 扩展 segment 大小
```
p_filesz += sizeof_stub;
p_memsz += sizeof_stub;
```
### 步骤 2 — 重定位 ELF 结构
注入点之后的所有 headers 都会被更新:
* 程序头偏移量发生位移
* 节头偏移量发生位移
* ELF 节头表偏移量被更新
### 步骤 3 — 重建二进制布局
```
[ original ELF ]
[ STUB inserted ]
[ shifted ELF data ]
```
# **Stub 补丁系统**
注入的 stub 会使用运行时元数据进行 patch:
```
ww_t_patch patch;
```
这包括:
* 从 stub 到原始入口点的偏移量
* `.text` 节相对于 stub 的偏移量
* `.text` 大小
* segment 重定位偏移量
二进制文件内部的布局:
```
[ STUB ........ PATCH ........ KEY ]
```
# **入口点重定向**
ELF 入口点被修改:
```
e_entry = injection_addr;
```
结果:
```
Before: entry → main()
After: entry → stub → main()
```
# **加密系统**
## XOR 加密
* 对称加密
* 加密和解密使用相同的函数
## 加法层
在 XOR 之前应用:
```
byte → +offset → XOR → encrypted
encrypted → XOR → -offset → original
```
# **命令**
## 基本用法
```
make # Build project
./woody_woodpacker [OPTIONS]
# 使用默认选项 make 并运行 packer,然后运行打包后的二进制文件,全程使用 valgrind
make run
# 示例:以 verbose 模式、padding injection 模式运行 packer,然后运行该二进制文件
./woody_woodpacker /bin/ls -i p -v && ./woody
```
## 选项
### 详细模式
显示有关以下内容的详细信息:
* ELF 解析
* 注入偏移量
* padding 分析
* 重定位操作
* stub patch 值
```
-v
--verbose
```
### 注入模式:padding
尝试将 stub 注入到现有的可执行 segment padding 中。
```
-i p
--injection-type padding
```
* 最安全的模式
* 无需重构 ELF
* 需要足够的可用空间
### 注入模式:shift
当 padding 不足或被明确选择时,强制对 ELF 进行结构性修改。
```
-i s
--injection-type shift
```
* 重写 ELF 布局
* 位移 headers 和 segments
* 保证注入成功
以下是一个可以直接放入的整洁 README 部分,符合你的项目基调和该测试脚本:
## 自动化测试套件
该项目包含一个简单的验证脚本,用于跨多个 ELF 二进制文件测试打包行为。
它会构建项目,在一组示例二进制文件上运行 packer,并验证执行输出的一致性。
### 测试内容
对于 `resources/` 目录中的每个二进制文件,脚本会:
* 使用 `make` 构建项目
* 在两种模式下执行 packer:
* `-i s`(shift 注入模式)
* `-i p`(padding 注入模式)
* 运行生成的打包后二进制文件(`./woody`)
* 检查执行输出中是否包含成功签名:
....WOODY....
* 报告每个二进制文件和模式的结果:
* 🟢 PASS → 检测到正确的执行
* 🔴 FAIL → 输出缺失或无效
### 如何运行测试
```
chmod +x test.sh
./test.sh
```
### 预期结构
```
project/
├── resources/
│ ├── bin1
│ ├── bin2
│ └── ...
├── woody_woodpacker
├── woody
└── test.sh
```
# **调试工具**
```
readelf -l [filename] # Check program headers of the file
readelf -S [filename] # Check section headers
hexdump -C [filename] # Check the file in hex format
vimdiff [filename 1] [filename 2] # Check the difference between two files
# 从 code.o 中提取 .text section
objcopy --dump-section .text=code-raw code.o
# 以十六进制格式打印地址 0x401040 处加载的文件内容
gdb ./woody
run (or r)
x/16xw 0x401040
# 如果基地址为 0x4011ad,则在相对地址 11ad 处添加 breakpoint
b *0x4011ad
# 显示 ELF entrypoint
gdb ./woody
starti
info files
info proc map
# 产生一个 trace trap 以在当前位置停止执行(在调试时很有用)
int3
```
# 兼容性说明
虽然该工具适用于许多标准的 Linux x86-64 ELF 二进制文件,但某些二进制文件可能无法打包,或者在打包后可能变得不稳定。
## 为什么某些二进制文件会失败
并非所有 ELF 二进制文件都具有相同的布局特征。shift 注入模式取决于能否安全地:
* 扩展 `PT_LOAD` segment(`p_filesz` / `p_memsz`)
* 正确位移所有后续的程序头和节头
* 保留 segment 之间有效的页面对齐内存映射
* 在入口点重定向后保持一致的执行流程
由于以下原因,某些二进制文件无法安全满足这些条件:
* **紧凑的 segment 打包**
* `PT_LOAD` segment 之间没有足够的连续空间
* Stub 溢出越过相邻的映射区域
* **复杂或加固的 ELF 布局**
* 多个重叠或紧密耦合的 `PT_LOAD` segment
* 特殊用途的 loader 假设(例如 glibc,ld-linux 优化)
* **RELRO / TLS / 动态 loader 约束**
* 如果位移不正确,`GNU_RELRO` 和 `DYNAMIC` segment 可能会损坏
* 运行时 loader 期望严格的偏移量以进行重定位和链接
* **PIE + ASLR 边缘情况**
* 某些二进制文件依赖于 segment 之间精确的相对布局
* 布局中的微小改变都可能使运行时假设失效
# **额外想法**
- 支持 32-bit
- 自定义 key 参数
- 支持 PE / Mach-O
- 压缩
标签:ELF文件, 二进制处理, 加壳工具, 壳代码, 客户端加密, 密码学应用, 系统底层