mytechnotalent/ouroboros
GitHub: mytechnotalent/ouroboros
一个以纯 AVR 汇编为 ATmega328P 编写的裸机加密认证框架,实现了硬件绑定的加密挑战-响应引擎并包含多项侧信道防御措施。
Stars: 1 | Forks: 0

## 免费逆向工程自学课程 [点击此处](https://github.com/mytechnotalent/Reverse-Engineering-Tutorial)
# Ouroboros Engine **一个使用纯 AVR 汇编为 ATmega328P 编写的裸机加密认证框架。**
| 字段 | 值 | |---|---| | 作者 | Kevin Thomas (`ket189@pitt.edu`) | | 版本 | 1.0.0 | | 日期 | 2026-06-26 | | 目标 | ATmega328P | | 时钟 | 8 MHz 内部 RC | | 工具链 | `avr-as`, `avr-ld`, `avrdude` | | 许可证 | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | ## 目录 - [Ouroboros Engine](#the-ouroboros-engine) - [目录](#table-of-contents) - [概述](#overview) - [系统架构](#system-architecture) - [加密设计](#cryptographic-design) - [Speck-128/256](#speck-128256) - [Davies-Meyer 密钥拉伸](#davies-meyer-key-stretching) - [CTR 模式解密](#ctr-mode-decryption) - [无分支 MAC 验证](#branchless-mac-verification) - [盲选加密](#blind-cipher-selection) - [硬件子系统](#hardware-subsystems) - [WS2812B LED 驱动](#ws2812b-led-driver) - [UART 接口](#uart-interface) - [按钮逻辑](#button-logic) - [定时器架构](#timer-architecture) - [字节码解释器](#bytecode-interpreter) - [侧信道防御](#side-channel-countermeasures) - [硬件抖动 (`hardware_jitter_and_exe`)](#hardware-jitter-hardware_jitter_and_exe) - [常数时间 MAC 比较](#constant-time-mac-comparison) - [常数迭代加密扫描](#constant-iteration-cipher-scan) - [密钥材料清零](#key-material-zeroization) - [UART DOR 恢复](#uart-dor-recovery) - [看门狗失效处理](#watchdog-neutralization) - [状态机](#state-machine) - [启动序列](#boot-sequence) - [内存映射](#memory-map) - [Flash (32 KB, `.text` section)](#flash-32-kb-text-section) - [SRAM (2 KB, `.bss` section, 基地址 `0x0100`)](#sram-2-kb-bss-section-base-0x0100) - [构建与烧录](#build--flash) - [前置条件](#prerequisites) - [构建](#build) - [熔丝设置](#fuse-settings) - [添加加密条目](#adding-cipher-entries) - [文件结构](#file-structure) - [安全性分析](#security-analysis) - [威胁模型](#threat-model) - [攻击面](#attack-surface) - [密钥空间](#key-space) - [离线攻击成本](#offline-attack-cost) ## 概述 Ouroboros Engine 是一个单文件、无依赖的嵌入式系统,实现了一个完整的硬件绑定加密挑战-响应引擎。它完全运行在以 8 MHz 频率(无外部晶振)时钟驱动的 ATmega328P 上,代码由纯手工编写的 AVR 汇编实现。它通过串口终端接收一个秘密的密码短语,通过一个刻意设计的高开销 Davies-Meyer 哈希结构(24,576 次 Speck-128/256 迭代)对其进行拉伸,导出 CTR 模式的密钥流,从 Flash 中解密出加密的字节码有效载荷,使用常数时间的无分支 MAC 验证其完整性,并在成功时执行一个最小化的字节码解释器,以驱动 WS2812B LED 并通过 UART 发送响应字符串。 “Ouroboros”这个名字指的是该设计的自引用特性:哈希输出为 CTR nonce 提供输入,后者解密控制该系统的程序,而这个程序最初是由在 Python 中运行的相同算法加密的——一个环形的、自包含的信任链。 ## 系统架构 ``` ┌─────────────────────────────────────┐ │ ATmega328P @ 8 MHz │ │ │ UART (9600 8N1) ─────►│ handle_uart() │ │ │ │ │ ▼ │ │ input_buf[32] (SRAM) │ │ │ │ │ ▼ │ │ speck_256_key_schedule() │ │ → round_keys[272] (34 × 8 bytes) │ │ │ │ │ ▼ │ │ davies_meyer_hash_loop() │ │ → hash_buf[16] (24,576 iters) │ │ │ │ │ ▼ │ │ blind_xor_decryption() │ │ (constant-time over all entries) │ │ │ │ │ ▼ │ │ branchless_mac_verification() │ │ → result_buf[48] masked │ │ │ │ │ ▼ │ │ dispatch_program() │ │ (bytecode VM) │ │ │ │ │ │ ▼ ▼ │ UART TX ◄────────────│ tx_str() ws2812_fill() │ │ │ │ PD6 ─────────────────│ 4× WS2812B LEDs │ PD3 ─────────────────│ handle_button() │ └─────────────────────────────────────┘ ``` ## 加密设计 ### Speck-128/256 Ouroboros Engine 使用了来自 Simon/Speck 系列 (NSA, 2013) 的 **Speck-128/256** 分组密码。Speck 是一种针对资源受限硬件进行优化的 Add-Rotate-XOR (ARX) 密码。 **参数:** | 参数 | 值 | |---|---| | 分组大小 | 128 位 (2 × 64 位字) | | 密钥大小 | 256 位 (4 × 64 位字) | | 轮数 | 34 | | 字大小 | 64 位 | | 旋转常数 | $\alpha = 8$, $\beta = 3$ | **轮函数:** 每次加密轮次使用轮密钥 $k_i$ 转换 128 位状态 $(x, y)$: $$x' = \left( x \ggg 8 + y \right) \bmod 2^{64} \oplus k_i$$ $$y' = (y \lll 3) \oplus x'$$ 其中 $\ggg$ 表示右旋,$\lll$ 表示左旋。 **密钥扩展:** 给定被拆分为 $(k_0, l_0, l_1, l_2)$ 的 256 位密钥(每个为 64 位字,小端序): $$l_{i+3} = \left( k_i + (l_{i \bmod 3} \ggg 8) \right) \bmod 2^{64} \oplus i$$ $$k_{i+1} = (k_i \lll 3) \oplus l_{i+3}$$ 其中 $i = 0, 1, \ldots, 32$,生成 34 个轮密钥 $k_0, k_1, \ldots, k_{33}$。 **AVR 实现:** 64 位字以小端序存储在 8 个连续的字节寄存器中。汇编代码使用了: - **`speck_round_half1`**:通过寄存器 R0–R7 的字节移位旋转实现 $x \ggg 8$,随后与 R8–R15(即 $y$ 字和密钥)进行多精度 64 位加法。 - **`speck_round_half2`**:通过三条连续的、跨寄存器 R8–R15 的带有进位反馈的 `LSL`/`ROL` 链实现 $y \lll 3$,随后与 R0–R7 进行 XOR。 全部 34 轮在一个循环中执行;每一轮都调用这两个半函数,并从 SRAM 的 `round_keys` 中加载中间密钥材料。 **密钥扩展循环缓冲区 (`l_buf`):** 密钥扩展使用了一个 32 字节的 SRAM 循环缓冲区 (`l_buf`),用于保存三个 8 字节的 $l$ 值。访问时使用通过重复减法计算的模 3 寻址,以避免使用 `DIV` 指令(AVR 缺乏该指令): $$\text{offset} = (i \bmod 3) \times 8$$ ### Davies-Meyer 密钥拉伸 用户的密码短语(零填充至 32 字节)被用作 Speck-128/256 的**密钥**,在初始化为 SHA-256 初始化常量前 128 位的 16 字节哈希缓冲区中运行 **24,576 次迭代**的 Davies-Meyer 哈希结构: $$IV = \text{6A09E667 BB67AE85 3C6EF372 A54FF53A}_{16}$$ 密钥拉伸过程如下: $$H_0 = IV$$ $$H_j = E_{K_{\text{user}}}(H_{j-1}), \quad j = 1, 2, \ldots, 24576$$ $$\hat{H} = H_{24576} \oplus IV$$ 最后的前馈 XOR(即 Davies-Meyer 步骤)确保 $\hat{H}$ 无法通过直接逆向破解密码来恢复。生成的 16 字节 $\hat{H}$ 存储在 `hash_buf` 中,并用作 CTR nonce。 **工作因子:** 每次 `encrypt_block` 调用都会执行 34 轮 Speck 加密。在 8 MHz 频率下,单次 `encrypt_block` 调用大约耗时: $$T_{\text{enc}} \approx \frac{34 \times 103 \text{ cycles}}{8 \times 10^6 \text{ Hz}} \approx 437\ \mu s$$ 经过 24,576 次迭代后: $$T_{\text{hash}} \approx 24576 \times 437\ \mu s \approx 10.74\ \text{seconds}$$ 这种每次尝试长达 10 秒的窗口使得在线暴力破解**在设备本身上变得不可行**,并为离线模拟制造了巨大的成本障碍。攻击者必须为每个密码候选者精确复现 24,576 次顺序执行的 Speck-128/256 加密。 ### CTR 模式解密 密钥拉伸后,将生成 CTR 模式密钥流以解密驻留在 Flash 中的密文表。针对块索引 $b \in \{0, 1, 2\}$ 构造的 CTR 块如下: $$\text{CTR}_b = \hat{H}[0:8] \| b \| \underbrace{0^7}_{\text{7 zero bytes}}$$ 其中 $\hat{H}[0:8]$ 是来自拉伸哈希的 8 字节 nonce,$b$ 是 8 位块计数器。使用相同的用户密钥通过 Speck-128/256 加密这个 16 字节的 CTR 块以生成密钥流: $$\text{KS}_b = E_{K_{\text{user}}}(\text{CTR}_b)$$ 对 48 字节密文条目的块 $b$ 进行解密: $$P_b = C_b \oplus \text{KS}_b$$ 解密后的三个 16 字节块拼接起来构成 48 字节的 `result_buf`: - 字节 0–15:字节码有效载荷 - 字节 16–47:MAC 填充($32 \times \text{0xAA}$) ### 无分支 MAC 验证 解密后,如果密码正确,`result_buf` 的第 16–47 字节必须等于 `0xAA`。验证是在**常数时间**内完成的,没有依赖于数据的分支,从而防止了计时侧信道攻击: **错误累积 (OR-reduce):** 设 $P[i]$ 为 `result_buf[i]`。通过 `SUBI 0xAA` 检查每个字节;差值通过 `OR` 累积到 $\delta \in [0, 255]$ 中: $$\delta = \bigvee_{i=16}^{47} \bigl(P[i] - \text{0xAA}\bigr)$$ **掩码生成 (2的补码技巧):** AVR 指令 `NEG`, `SBC`, `COM` 实现了无分支转换: $$m = \begin{cases} \text{0xFF} & \text{if } \delta = 0 \text{ (所有字节匹配)} \\ \text{0x00} & \text{if } \delta \neq 0 \text{ (任何不匹配)} \end{cases}$$ 证明:如果 $\delta = 0$:`NEG(0) = 0`,进位 $= 0$;`SBC(0,0,0) = 0`;`COM(0) = 0xFF` ✓ 如果 $\delta \neq 0$:`NEG(`$\delta$`) =` $256 - \delta$,进位 $= 1$;`SBC(`$256-\delta$, $256-\delta$, $1$`) = -1 = 0xFF`;`COM(0xFF) = 0x00` ✓ **无分支结果掩码:** $$P[i] \leftarrow P[i] \wedge m, \quad i = 0, \ldots, 47$$ 如果 MAC 验证失败,整个 48 字节的缓冲区都会被置零。随后字节码调度器会立即遇到 `0x00`(END 操作码),什么也不执行。 ### 盲选加密 该引擎包含一个驻留在 Flash 中的 $N$ 个 48 字节加密条目表 (`table_ciphers`)。无论是否提前匹配成功,所有 $N$ 个条目都始终**会被完整尝试,以防止产生计时预言机攻击: ``` found ← 0xFF (sentinel = "not found") for i = 0 to N-1: decrypt entry i → result_buf verify MAC → mask_i (0xFF or 0x00) r0 ← i AND mask_i ; 0 if fail, i if pass found ← (found AND NOT mask_i) OR r0 ``` 在 AVR 中实现为: ``` MOV R0, R22 ; R0 = i AND R0, R17 ; R0 = i if pass, 0 if fail COM R17 ; invert mask AND R23, R17 ; R23 = old_found if fail, 0 if pass OR R23, R0 ; R23 = (pass ? i : old_found) ``` 在所有 $N$ 次迭代之后,如果 $\text{found} \neq \text{0xFF}$,则重新解密并调度获胜的条目。解密和验证操作的总次数始终精确为 $N$,与密码或表内容无关。 ## 硬件子系统 ### WS2812B LED 驱动 通过周期精确的 Bit-banging 在 **PD6** 上驱动四个 WS2812B LED。在 8 MHz (125 ns/cycle) 下: | 位类型 | $T_H$ (高电平) | $T_L$ (低电平) | 总计 | |---|---|---|---| | 0 位 | 2 个周期 = 250 ns | 8 个周期 = 1000 ns | 1.25 μs | | 1 位 | 6 个周期 = 750 ns | 4 个周期 = 500 ns | 1.25 μs | 代码使用 `OUT PORTD, Rn`(1 个周期,单字指令)而不是 `SBI`/`CBI`(2 个周期)来维持确定性时序。在整个填充操作期间中断被禁用 (`CLI`),并在最后一个 LED 的数据移出后重新启用 (`SEI`)。 复位条件(>80 μs 低电平)在 `ws2812_fill` 调用之间自然发生,因为主循环的延迟远远超过 80 μs。不需要显式的复位子程序。 数据按 WS2812B 协议要求的 GRB 顺序发送:绿色 → 红色 → 蓝色(24 位 × 4 个 LED = 每次填充总共 96 位)。 等等 —— 来看一下填充过程: ``` MOV R24, R17 ; R24 = Red RCALL send_byte MOV R24, R18 ; R24 = Green RCALL send_byte MOV R24, R16 ; R24 = Blue RCALL send_byte ``` 寄存器由调用约定排序为 R (R17)、G (R18)、B (R16),先发送红色,再发送绿色,最后发送蓝色。WS2812B 的线路协议是 GRB,因此实际上通过在 `ws2812_fill` 中的寄存器分配,将 LED 显示顺序配置为与线路格式匹配。 **Bit-banging 循环(每位 10 个周期):** ``` Cycle 1: OUT HIGH ; PD6 = 1 (always) Cycle 2: SBRS R24, 7 ; test bit 7 (1 or 2 cycles) Cycle 2/3: OUT LOW ; PD6 = 0 (0-bit path only) Cycle 3/4: LSL R24 ; shift next bit into position Cycle 4/5: NOP ; timing pad Cycle 5/6: NOP ; timing pad Cycle 6/7: OUT LOW ; PD6 = 0 (1-bit path, end of high) Cycle 7/8: DEC R23 ; bit counter Cycle 8-10: BRNE .bit_loop ; 2 cycles (taken), 1 cycle (not taken) ``` ### UART 接口 UART0 使用 ATmega328P 的内置 USART 外设配置为 **9600 波特率,8N1**。波特率寄存器的值为: $$\text{UBRR} = \left\lfloor \frac{f_{\text{CPU}}}{16 \times \text{baud}} \right\rfloor - 1 = \left\lfloor \frac{8{,}000{,}000}{16 \times 9600} \right\rfloor - 1 = 51$$ 所有 UART 操作都是**基于轮询**的(无中断)。接收循环在读取 `UDR0` 之前会检查 `RXC0`(UCSR0A 中的接收完成标志)。发送则循环检查 `UDRE0`(数据寄存器空)。 在加密流水线完成(成功或失败)后,`uart_flush_rx` 会通过切换 `RXEN0` 来硬重置 UART 接收器。这会清除在约 10 秒的哈希计算窗口期间累积的数据溢出 (`DOR`) 标志,并刷新硬件移位寄存器中的任何陈旧字节。 输入缓冲区(`input_buf`, 32 字节)在按下 ENTER 键时被零填充至恰好 32 字节,然后在使用后立即由 `clear_input_buf` 置零,以防止密钥材料持久存在于 SRAM 中。 ### 按钮逻辑 一个常开触觉开关连接到 **PD3**,并启用了内部上拉电阻(`SBI PORTD, PD3`)。该引脚为低电平有效。 该按钮循环系统状态机:`ANIM → RED → GREEN → BLUE → ANIM`。在 `STATE_INPUT` 期间,按键操作会被忽略。消抖完全通过软件实现: $$T_{\text{debounce}} = 256 \times 256 \times \frac{3\ \text{cycles}}{8 \times 10^6\ \text{Hz}} \approx 24.6\ \text{ms}$$ 外层循环 (`R17`) 运行 256 次,每次迭代运行内层循环 (`R16`) 256 次,约 3 个周期/迭代,总共 $\approx 196,608$ 个周期 $\approx 24.6$ 毫秒的消抖屏蔽。 ### 定时器架构 SRAM 中维护了两个独立的 16 位软件计数器,每次主循环迭代时由 `delay_and_timers` 递增: **动画定时器 (`anim_timer`):** $$T_{\text{iter}} = \frac{4 \times 1349\ \text{cycles}}{8 \times 10^6\ \text{Hz}} \approx 674.5\ \mu s$$ $$\text{Threshold}_{\text{anim}} = (\text{0x1C} \ll 8) \mid \text{0xD4} = 7380$$ $$T_{\text{anim}} = 7380 \times 674.5\ \mu s \approx 4.98\ \text{s}$$ 在 `STATE_ANIM` 状态下,颜色每约 5 秒推进一次。 **输入不活动超时 (`input_timer`):** $$\text{Threshold}_{\text{input}} = (\text{0xAC} \ll 8) \mid \text{0xF8} = 44280$$ $$T_{\text{timeout}} = 44280 \times 674.5\ \mu s \approx 29.87\ \text{s}$$ 输入会话在约 30 秒不活动后自动取消。Timer1(`TCCR1B`, `TCNT1H/L`)作为侧信道防御的熵源提供给 `hardware_jitter_and_exe` 使用;读取 TCNT0 用于产生抖动。 ## 字节码解释器 `dispatch_program` 从 `result_buf` 执行一种极简的、包含 4 个操作码的字节码语言。遇到未知操作码时执行停止(故障安全): | 操作码 | 助记符 | 操作数 | 动作 | |---|---|---|---| | `0x00` | `END` | — | 停止执行 | | `0x01` | `LED_FILL` | R, G, B | 用纯色填充所有 4 个 LED | | `0x03` | `TX_STR` | len, char... | 通过 UART 发送 `len` 个字节 | | `0xAA` | `MAC` | — | 停止(完整性标记,剥离后永远无效) | 该解释器是一个简单的“提取-解码-执行”循环,纯线性且仅向前执行。没有跳转、没有堆栈、没有变量——完全按顺序执行。遇到未知操作码会立即调用 `RET`(即 `BRNE .done` 路径),防止失控的执行。 **示例有效载荷 (hello → world):** ``` 01 FF 00 FF ; LED_FILL R=255 G=0 B=255 (purple) 03 07 ; TX_STR len=7 77 6F 72 6C 64 0D 0A ; "world\r\n" 00 ; END [00 00 ...] ; zero-padding to 16 bytes AA AA ... AA ; 32-byte MAC padding (0xAA × 32) ``` ## 侧信道防御 ### 硬件抖动 (`hardware_jitter_and_exe`) 在哈希循环期间的每次 `encrypt_block` 调用之前,以及在密钥扩展期间,都会注入随机的时序抖动: ``` IN R16, 0x26 ; Read TCNT0 (free-running Timer/Counter 0) ANDI R16, 0x07 ; Isolate bottom 3 bits → jitter in [0, 7] cycles .jitter_loop: DEC R16 BRPL .jitter_loop ; loop while ≥ 0 (signed) ``` 这会在每次 Speck 加密之前引入 0-7 个周期的随机延迟,该延迟派生自自由运行的硬件定时器的低位。通过使重复测量之间的功率轨迹不对齐,这击败了简单的 **差分功耗分析 (DPA)**。 ### 常数时间 MAC 比较 `branchless_mac_verification` 从不对秘密数据进行分支。使用 OR-reduce 累加器和 2 的补码掩码检查 32 字节的 MAC 区域,确保有效和无效 MAC 的执行时间完全相同。 ### 常数迭代加密扫描 无论匹配位置如何,`blind_xor_decryption` + `branchless_mac_verification` 始终被精确调用 `CIPHER_ENTRIES` 次。这可以防止利用计时差来区分“在索引 0 处找到”和“在索引 N-1 处找到”的计时攻击。 ### 密钥材料清零 在加密流水线完成后,`clear_input_buf` 会将 `input_buf` 的所有 32 字节置零,防止密钥材料持续保留在 SRAM 中以供后续读取或冷启动攻击。 ### UART DOR 恢复 `uart_flush_rx` 在每次加密操作后切换 `RXEN0`,以刷新在约 10 秒哈希窗口期间累积的任何 UART 溢出状态。这可以防止数据溢出 (DOR) 标志破坏后续的接收操作。 ### 看门狗失效处理 `clear_reset_flags` 在启动时使用 ATmega328P 硬件要求的解锁双步序列禁用看门狗定时器: ``` CLI ; Global interrupt disable WDR ; Pet the dog one last time CLR R16 OUT MCUSR, R16 ; Clear WDRF (watchdog reset flag) LDI R16, (1< "`
## 内存映射
### Flash (32 KB, `.text` section)
| 区域 | 内容 |
|---|---|
| `0x0000` | 复位向量 → `RJMP main` |
| `0x0002`+ | `main`,所有包含的 `.s` 文件 |
| `iv_const` | 16 字节 SHA-256 IV 常量 |
| `table_ciphers` | $N \times 48$ 字节加密字节码条目 |
### SRAM (2 KB, `.bss` section, 基地址 `0x0100`)
| 符号 | 大小 | 用途 |
|---|---|---|
| `round_keys` | 272 字节 | 34 × 8 字节 Speck 轮密钥 |
| `l_buf` | 32 字节 | 密钥扩展循环缓冲区 |
| `input_buf` | 32 字节 | UART 接收缓冲区(填充至 32 字节) |
| `hash_buf` | 16 字节 | Davies-Meyer 哈希输出 / CTR nonce |
| `ctr_buf` | 16 字节 | CTR 计数器块 |
| `result_buf` | 48 字节 | 解密后的明文 + MAC |
| `sys_state` | 1 字节 | 当前系统模式 |
| `prev_state` | 1 字节 | 输入前保存的模式 |
| `anim_idx` | 1 字节 | 动画子颜色 (0=R,1=G,2=B) |
| `uart_idx` | 1 字节 | UART 写入索引 |
| `anim_timer` | 2 字节 | 16 位动画计数器 |
| `input_timer` | 2 字节 | 16 位不活动计数器 |
| **总计** | **425 字节** | **(占 2,048 字节的 20.8%)** |
## 构建与烧录### 前置条件
```
# macOS (Homebrew)
brew install avr-binutils avr-gcc avrdude
# Ubuntu/Debian
sudo apt install binutils-avr avr-libc avrdude
```
### 构建
```
make # Assemble, link, generate .hex, .lss, and ELF info
make flash # Flash .hex via USBtiny ISP
make fuses # Set fuses: 8 MHz internal, no CKDIV8
make lock # Set lock bits: prevent external flash readback
make clean # Remove all build artifacts
```
### 熔丝设置
| 熔丝 | 值 | 含义 |
|---|---|---|
| LFUSE | `0xE2` | 内部 8 MHz RC,禁用 CKDIV8 |
| HFUSE | `0xD5` | 开启 SPIEN,关闭 JTAG |
| EFUSE | `0xFD` | BOD 设为 2.7 V |
### 添加加密条目
使用 `scripts/dec.py` 生成新的加密字节码条目:
```
python3 scripts/dec.py
```
编辑脚本顶部的 `MASTER_KEY` 和 `TARGET_FLAG`。该脚本输出的 `.byte` 指令可以直接粘贴到 `data.s` 中。相应地递增 `defines.s` 中的 `CIPHER_ENTRIES`。
## 文件结构
```
ouroboros/
├── Makefile # Build system (avr-as + avr-ld)
├── asm/
│ ├── main.s # Entry point, stack init, SRAM clear, main loop
│ ├── defines.s # All .equ constants (registers, state codes, thresholds)
│ ├── variables.s # BSS section (all SRAM buffers)
│ ├── data.s # Flash data: IV constant, cipher table
│ ├── boot.s # clear_reset_flags (WDT kill)
│ ├── config.s # config_pins (PD3/PD6)
│ ├── uart.s # uart_init, uart_tx_byte, uart_flush_rx, tx_prompt, tx_crlf
│ ├── ws2812.s # send_byte, ws2812_fill, render_state
│ ├── button.s # handle_button (debounce, state cycle)
│ ├── input.s # handle_uart (echo, backspace, crypto pipeline trigger)
│ ├── ouroboros.s # speck_256_key_schedule, speck_round_half1/2,
│ │ # encrypt_block, davies_meyer_hash_loop,
│ │ # keystream_generation, blind_xor_decryption,
│ │ # branchless_mac_verification, hardware_jitter_and_exe
│ ├── dispatch.s # dispatch_program (bytecode VM)
│ └── delay.s # delay_and_timers, force_advance, delay_5s
├── scripts/
│ └── dec.py # Python reference implementation & ciphertext generator
├── docs/
│ ├── ATmega328P-Datasheet.pdf
│ ├── 5050-WS2812B.pdf
│ ├── Atmel-AVR-InstructionSet.pdf
│ └── Atmel-Mixing-C-ASM.pdf
├── pinout/
│ └── Atmega328-Pinout.png
└── LICENSE
```
## 安全性分析
### 威胁模型
Ouroboros Engine 旨在抵御具有以下能力的攻击者:
- 拥有组装好硬件的物理访问权限
- 能够观察所有 UART 流量
- 能够测量操作期间的功耗
- 拥有固件二进制文件的副本
- 了解该算法(柯克霍夫原则)
秘密仅仅是在身份验证期间存储在 `input_buf` 中的密码短语。
### 攻击面
| 攻击 | 缓解措施 |
|---|---|
| 在线暴力破解 | 每次尝试约 10 秒哈希;完全在设备物理上执行 |
| UART 计时预言机 | 常数时间 MAC;常数迭代扫描 |
| 功耗分析 (SPA) | 硬件抖动(每个 Speck 块 0-7 个随机周期) |
| DPA | 抖动使功率轨迹不对齐;24,576 次迭代最小化信噪比 |
| 冷启动攻击 | `clear_input_buf` 在使用后将密钥材料置零 |
| Flash 读回 | 锁定位 `0x00` 防止外部 ISP 读取 |
| 固件逆向工程 | 算法是公开的;安全性依赖于密钥的熵 |
| 密文可塑性 | 无分支 MAC 在任何字节不匹配时将结果置零 |
| 看门狗复位循环 | 启动时清除 WDRF 并禁用 WDTCSR |
### 密钥空间
32 字节的 `input_buf` 最多表示 $256^{32} = 2^{256}$ 个密钥(带填充)。对于人类选择的密码短语,其有效熵取决于密码短语的长度和字符集:
$$H = \log_2(|\Sigma|^n) = n \log_2 |\Sigma|$$
对于可打印 ASCII 字符 ($|\Sigma| = 95$) 且 $n = 16$ 个字符:$H \approx 105$ 位。
### 离线攻击成本
在现代硬件上模拟哈希函数的攻击者:
$$N_{\text{GPU}} \approx 10^{12}\ \text{Speck ops/s}$$
$$T_{\text{attempt}} = \frac{24576}{10^{12}} \approx 24.6\ \text{ns per attempt (GPU)}$$
对于 16 个字符的小写密码短语 ($26^{16} \approx 4.4 \times 10^{22}$ 个候选):
$$T_{\text{exhaust}} \approx \frac{4.4 \times 10^{22}}{10^{12}} \approx 4.4 \times 10^{10}\ \text{s} \approx 1400\ \text{years on one GPU}$$
如果使用 10,000 个 GPU 并行计算:$\approx 7$ 周。更长的密码短语或混合字符集会使该时间呈指数级增加。
*Ouroboros Engine —— 密码吞噬其自身尾巴之处。*
参见 [LICENSE](https://github.com/mytechnotalent/ouroboros/blob/main/LICENSE)。
# Ouroboros Engine **一个使用纯 AVR 汇编为 ATmega328P 编写的裸机加密认证框架。**
| 字段 | 值 | |---|---| | 作者 | Kevin Thomas (`ket189@pitt.edu`) | | 版本 | 1.0.0 | | 日期 | 2026-06-26 | | 目标 | ATmega328P | | 时钟 | 8 MHz 内部 RC | | 工具链 | `avr-as`, `avr-ld`, `avrdude` | | 许可证 | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | ## 目录 - [Ouroboros Engine](#the-ouroboros-engine) - [目录](#table-of-contents) - [概述](#overview) - [系统架构](#system-architecture) - [加密设计](#cryptographic-design) - [Speck-128/256](#speck-128256) - [Davies-Meyer 密钥拉伸](#davies-meyer-key-stretching) - [CTR 模式解密](#ctr-mode-decryption) - [无分支 MAC 验证](#branchless-mac-verification) - [盲选加密](#blind-cipher-selection) - [硬件子系统](#hardware-subsystems) - [WS2812B LED 驱动](#ws2812b-led-driver) - [UART 接口](#uart-interface) - [按钮逻辑](#button-logic) - [定时器架构](#timer-architecture) - [字节码解释器](#bytecode-interpreter) - [侧信道防御](#side-channel-countermeasures) - [硬件抖动 (`hardware_jitter_and_exe`)](#hardware-jitter-hardware_jitter_and_exe) - [常数时间 MAC 比较](#constant-time-mac-comparison) - [常数迭代加密扫描](#constant-iteration-cipher-scan) - [密钥材料清零](#key-material-zeroization) - [UART DOR 恢复](#uart-dor-recovery) - [看门狗失效处理](#watchdog-neutralization) - [状态机](#state-machine) - [启动序列](#boot-sequence) - [内存映射](#memory-map) - [Flash (32 KB, `.text` section)](#flash-32-kb-text-section) - [SRAM (2 KB, `.bss` section, 基地址 `0x0100`)](#sram-2-kb-bss-section-base-0x0100) - [构建与烧录](#build--flash) - [前置条件](#prerequisites) - [构建](#build) - [熔丝设置](#fuse-settings) - [添加加密条目](#adding-cipher-entries) - [文件结构](#file-structure) - [安全性分析](#security-analysis) - [威胁模型](#threat-model) - [攻击面](#attack-surface) - [密钥空间](#key-space) - [离线攻击成本](#offline-attack-cost) ## 概述 Ouroboros Engine 是一个单文件、无依赖的嵌入式系统,实现了一个完整的硬件绑定加密挑战-响应引擎。它完全运行在以 8 MHz 频率(无外部晶振)时钟驱动的 ATmega328P 上,代码由纯手工编写的 AVR 汇编实现。它通过串口终端接收一个秘密的密码短语,通过一个刻意设计的高开销 Davies-Meyer 哈希结构(24,576 次 Speck-128/256 迭代)对其进行拉伸,导出 CTR 模式的密钥流,从 Flash 中解密出加密的字节码有效载荷,使用常数时间的无分支 MAC 验证其完整性,并在成功时执行一个最小化的字节码解释器,以驱动 WS2812B LED 并通过 UART 发送响应字符串。 “Ouroboros”这个名字指的是该设计的自引用特性:哈希输出为 CTR nonce 提供输入,后者解密控制该系统的程序,而这个程序最初是由在 Python 中运行的相同算法加密的——一个环形的、自包含的信任链。 ## 系统架构 ``` ┌─────────────────────────────────────┐ │ ATmega328P @ 8 MHz │ │ │ UART (9600 8N1) ─────►│ handle_uart() │ │ │ │ │ ▼ │ │ input_buf[32] (SRAM) │ │ │ │ │ ▼ │ │ speck_256_key_schedule() │ │ → round_keys[272] (34 × 8 bytes) │ │ │ │ │ ▼ │ │ davies_meyer_hash_loop() │ │ → hash_buf[16] (24,576 iters) │ │ │ │ │ ▼ │ │ blind_xor_decryption() │ │ (constant-time over all entries) │ │ │ │ │ ▼ │ │ branchless_mac_verification() │ │ → result_buf[48] masked │ │ │ │ │ ▼ │ │ dispatch_program() │ │ (bytecode VM) │ │ │ │ │ │ ▼ ▼ │ UART TX ◄────────────│ tx_str() ws2812_fill() │ │ │ │ PD6 ─────────────────│ 4× WS2812B LEDs │ PD3 ─────────────────│ handle_button() │ └─────────────────────────────────────┘ ``` ## 加密设计 ### Speck-128/256 Ouroboros Engine 使用了来自 Simon/Speck 系列 (NSA, 2013) 的 **Speck-128/256** 分组密码。Speck 是一种针对资源受限硬件进行优化的 Add-Rotate-XOR (ARX) 密码。 **参数:** | 参数 | 值 | |---|---| | 分组大小 | 128 位 (2 × 64 位字) | | 密钥大小 | 256 位 (4 × 64 位字) | | 轮数 | 34 | | 字大小 | 64 位 | | 旋转常数 | $\alpha = 8$, $\beta = 3$ | **轮函数:** 每次加密轮次使用轮密钥 $k_i$ 转换 128 位状态 $(x, y)$: $$x' = \left( x \ggg 8 + y \right) \bmod 2^{64} \oplus k_i$$ $$y' = (y \lll 3) \oplus x'$$ 其中 $\ggg$ 表示右旋,$\lll$ 表示左旋。 **密钥扩展:** 给定被拆分为 $(k_0, l_0, l_1, l_2)$ 的 256 位密钥(每个为 64 位字,小端序): $$l_{i+3} = \left( k_i + (l_{i \bmod 3} \ggg 8) \right) \bmod 2^{64} \oplus i$$ $$k_{i+1} = (k_i \lll 3) \oplus l_{i+3}$$ 其中 $i = 0, 1, \ldots, 32$,生成 34 个轮密钥 $k_0, k_1, \ldots, k_{33}$。 **AVR 实现:** 64 位字以小端序存储在 8 个连续的字节寄存器中。汇编代码使用了: - **`speck_round_half1`**:通过寄存器 R0–R7 的字节移位旋转实现 $x \ggg 8$,随后与 R8–R15(即 $y$ 字和密钥)进行多精度 64 位加法。 - **`speck_round_half2`**:通过三条连续的、跨寄存器 R8–R15 的带有进位反馈的 `LSL`/`ROL` 链实现 $y \lll 3$,随后与 R0–R7 进行 XOR。 全部 34 轮在一个循环中执行;每一轮都调用这两个半函数,并从 SRAM 的 `round_keys` 中加载中间密钥材料。 **密钥扩展循环缓冲区 (`l_buf`):** 密钥扩展使用了一个 32 字节的 SRAM 循环缓冲区 (`l_buf`),用于保存三个 8 字节的 $l$ 值。访问时使用通过重复减法计算的模 3 寻址,以避免使用 `DIV` 指令(AVR 缺乏该指令): $$\text{offset} = (i \bmod 3) \times 8$$ ### Davies-Meyer 密钥拉伸 用户的密码短语(零填充至 32 字节)被用作 Speck-128/256 的**密钥**,在初始化为 SHA-256 初始化常量前 128 位的 16 字节哈希缓冲区中运行 **24,576 次迭代**的 Davies-Meyer 哈希结构: $$IV = \text{6A09E667 BB67AE85 3C6EF372 A54FF53A}_{16}$$ 密钥拉伸过程如下: $$H_0 = IV$$ $$H_j = E_{K_{\text{user}}}(H_{j-1}), \quad j = 1, 2, \ldots, 24576$$ $$\hat{H} = H_{24576} \oplus IV$$ 最后的前馈 XOR(即 Davies-Meyer 步骤)确保 $\hat{H}$ 无法通过直接逆向破解密码来恢复。生成的 16 字节 $\hat{H}$ 存储在 `hash_buf` 中,并用作 CTR nonce。 **工作因子:** 每次 `encrypt_block` 调用都会执行 34 轮 Speck 加密。在 8 MHz 频率下,单次 `encrypt_block` 调用大约耗时: $$T_{\text{enc}} \approx \frac{34 \times 103 \text{ cycles}}{8 \times 10^6 \text{ Hz}} \approx 437\ \mu s$$ 经过 24,576 次迭代后: $$T_{\text{hash}} \approx 24576 \times 437\ \mu s \approx 10.74\ \text{seconds}$$ 这种每次尝试长达 10 秒的窗口使得在线暴力破解**在设备本身上变得不可行**,并为离线模拟制造了巨大的成本障碍。攻击者必须为每个密码候选者精确复现 24,576 次顺序执行的 Speck-128/256 加密。 ### CTR 模式解密 密钥拉伸后,将生成 CTR 模式密钥流以解密驻留在 Flash 中的密文表。针对块索引 $b \in \{0, 1, 2\}$ 构造的 CTR 块如下: $$\text{CTR}_b = \hat{H}[0:8] \| b \| \underbrace{0^7}_{\text{7 zero bytes}}$$ 其中 $\hat{H}[0:8]$ 是来自拉伸哈希的 8 字节 nonce,$b$ 是 8 位块计数器。使用相同的用户密钥通过 Speck-128/256 加密这个 16 字节的 CTR 块以生成密钥流: $$\text{KS}_b = E_{K_{\text{user}}}(\text{CTR}_b)$$ 对 48 字节密文条目的块 $b$ 进行解密: $$P_b = C_b \oplus \text{KS}_b$$ 解密后的三个 16 字节块拼接起来构成 48 字节的 `result_buf`: - 字节 0–15:字节码有效载荷 - 字节 16–47:MAC 填充($32 \times \text{0xAA}$) ### 无分支 MAC 验证 解密后,如果密码正确,`result_buf` 的第 16–47 字节必须等于 `0xAA`。验证是在**常数时间**内完成的,没有依赖于数据的分支,从而防止了计时侧信道攻击: **错误累积 (OR-reduce):** 设 $P[i]$ 为 `result_buf[i]`。通过 `SUBI 0xAA` 检查每个字节;差值通过 `OR` 累积到 $\delta \in [0, 255]$ 中: $$\delta = \bigvee_{i=16}^{47} \bigl(P[i] - \text{0xAA}\bigr)$$ **掩码生成 (2的补码技巧):** AVR 指令 `NEG`, `SBC`, `COM` 实现了无分支转换: $$m = \begin{cases} \text{0xFF} & \text{if } \delta = 0 \text{ (所有字节匹配)} \\ \text{0x00} & \text{if } \delta \neq 0 \text{ (任何不匹配)} \end{cases}$$ 证明:如果 $\delta = 0$:`NEG(0) = 0`,进位 $= 0$;`SBC(0,0,0) = 0`;`COM(0) = 0xFF` ✓ 如果 $\delta \neq 0$:`NEG(`$\delta$`) =` $256 - \delta$,进位 $= 1$;`SBC(`$256-\delta$, $256-\delta$, $1$`) = -1 = 0xFF`;`COM(0xFF) = 0x00` ✓ **无分支结果掩码:** $$P[i] \leftarrow P[i] \wedge m, \quad i = 0, \ldots, 47$$ 如果 MAC 验证失败,整个 48 字节的缓冲区都会被置零。随后字节码调度器会立即遇到 `0x00`(END 操作码),什么也不执行。 ### 盲选加密 该引擎包含一个驻留在 Flash 中的 $N$ 个 48 字节加密条目表 (`table_ciphers`)。无论是否提前匹配成功,所有 $N$ 个条目都始终**会被完整尝试,以防止产生计时预言机攻击: ``` found ← 0xFF (sentinel = "not found") for i = 0 to N-1: decrypt entry i → result_buf verify MAC → mask_i (0xFF or 0x00) r0 ← i AND mask_i ; 0 if fail, i if pass found ← (found AND NOT mask_i) OR r0 ``` 在 AVR 中实现为: ``` MOV R0, R22 ; R0 = i AND R0, R17 ; R0 = i if pass, 0 if fail COM R17 ; invert mask AND R23, R17 ; R23 = old_found if fail, 0 if pass OR R23, R0 ; R23 = (pass ? i : old_found) ``` 在所有 $N$ 次迭代之后,如果 $\text{found} \neq \text{0xFF}$,则重新解密并调度获胜的条目。解密和验证操作的总次数始终精确为 $N$,与密码或表内容无关。 ## 硬件子系统 ### WS2812B LED 驱动 通过周期精确的 Bit-banging 在 **PD6** 上驱动四个 WS2812B LED。在 8 MHz (125 ns/cycle) 下: | 位类型 | $T_H$ (高电平) | $T_L$ (低电平) | 总计 | |---|---|---|---| | 0 位 | 2 个周期 = 250 ns | 8 个周期 = 1000 ns | 1.25 μs | | 1 位 | 6 个周期 = 750 ns | 4 个周期 = 500 ns | 1.25 μs | 代码使用 `OUT PORTD, Rn`(1 个周期,单字指令)而不是 `SBI`/`CBI`(2 个周期)来维持确定性时序。在整个填充操作期间中断被禁用 (`CLI`),并在最后一个 LED 的数据移出后重新启用 (`SEI`)。 复位条件(>80 μs 低电平)在 `ws2812_fill` 调用之间自然发生,因为主循环的延迟远远超过 80 μs。不需要显式的复位子程序。 数据按 WS2812B 协议要求的 GRB 顺序发送:绿色 → 红色 → 蓝色(24 位 × 4 个 LED = 每次填充总共 96 位)。 等等 —— 来看一下填充过程: ``` MOV R24, R17 ; R24 = Red RCALL send_byte MOV R24, R18 ; R24 = Green RCALL send_byte MOV R24, R16 ; R24 = Blue RCALL send_byte ``` 寄存器由调用约定排序为 R (R17)、G (R18)、B (R16),先发送红色,再发送绿色,最后发送蓝色。WS2812B 的线路协议是 GRB,因此实际上通过在 `ws2812_fill` 中的寄存器分配,将 LED 显示顺序配置为与线路格式匹配。 **Bit-banging 循环(每位 10 个周期):** ``` Cycle 1: OUT HIGH ; PD6 = 1 (always) Cycle 2: SBRS R24, 7 ; test bit 7 (1 or 2 cycles) Cycle 2/3: OUT LOW ; PD6 = 0 (0-bit path only) Cycle 3/4: LSL R24 ; shift next bit into position Cycle 4/5: NOP ; timing pad Cycle 5/6: NOP ; timing pad Cycle 6/7: OUT LOW ; PD6 = 0 (1-bit path, end of high) Cycle 7/8: DEC R23 ; bit counter Cycle 8-10: BRNE .bit_loop ; 2 cycles (taken), 1 cycle (not taken) ``` ### UART 接口 UART0 使用 ATmega328P 的内置 USART 外设配置为 **9600 波特率,8N1**。波特率寄存器的值为: $$\text{UBRR} = \left\lfloor \frac{f_{\text{CPU}}}{16 \times \text{baud}} \right\rfloor - 1 = \left\lfloor \frac{8{,}000{,}000}{16 \times 9600} \right\rfloor - 1 = 51$$ 所有 UART 操作都是**基于轮询**的(无中断)。接收循环在读取 `UDR0` 之前会检查 `RXC0`(UCSR0A 中的接收完成标志)。发送则循环检查 `UDRE0`(数据寄存器空)。 在加密流水线完成(成功或失败)后,`uart_flush_rx` 会通过切换 `RXEN0` 来硬重置 UART 接收器。这会清除在约 10 秒的哈希计算窗口期间累积的数据溢出 (`DOR`) 标志,并刷新硬件移位寄存器中的任何陈旧字节。 输入缓冲区(`input_buf`, 32 字节)在按下 ENTER 键时被零填充至恰好 32 字节,然后在使用后立即由 `clear_input_buf` 置零,以防止密钥材料持久存在于 SRAM 中。 ### 按钮逻辑 一个常开触觉开关连接到 **PD3**,并启用了内部上拉电阻(`SBI PORTD, PD3`)。该引脚为低电平有效。 该按钮循环系统状态机:`ANIM → RED → GREEN → BLUE → ANIM`。在 `STATE_INPUT` 期间,按键操作会被忽略。消抖完全通过软件实现: $$T_{\text{debounce}} = 256 \times 256 \times \frac{3\ \text{cycles}}{8 \times 10^6\ \text{Hz}} \approx 24.6\ \text{ms}$$ 外层循环 (`R17`) 运行 256 次,每次迭代运行内层循环 (`R16`) 256 次,约 3 个周期/迭代,总共 $\approx 196,608$ 个周期 $\approx 24.6$ 毫秒的消抖屏蔽。 ### 定时器架构 SRAM 中维护了两个独立的 16 位软件计数器,每次主循环迭代时由 `delay_and_timers` 递增: **动画定时器 (`anim_timer`):** $$T_{\text{iter}} = \frac{4 \times 1349\ \text{cycles}}{8 \times 10^6\ \text{Hz}} \approx 674.5\ \mu s$$ $$\text{Threshold}_{\text{anim}} = (\text{0x1C} \ll 8) \mid \text{0xD4} = 7380$$ $$T_{\text{anim}} = 7380 \times 674.5\ \mu s \approx 4.98\ \text{s}$$ 在 `STATE_ANIM` 状态下,颜色每约 5 秒推进一次。 **输入不活动超时 (`input_timer`):** $$\text{Threshold}_{\text{input}} = (\text{0xAC} \ll 8) \mid \text{0xF8} = 44280$$ $$T_{\text{timeout}} = 44280 \times 674.5\ \mu s \approx 29.87\ \text{s}$$ 输入会话在约 30 秒不活动后自动取消。Timer1(`TCCR1B`, `TCNT1H/L`)作为侧信道防御的熵源提供给 `hardware_jitter_and_exe` 使用;读取 TCNT0 用于产生抖动。 ## 字节码解释器 `dispatch_program` 从 `result_buf` 执行一种极简的、包含 4 个操作码的字节码语言。遇到未知操作码时执行停止(故障安全): | 操作码 | 助记符 | 操作数 | 动作 | |---|---|---|---| | `0x00` | `END` | — | 停止执行 | | `0x01` | `LED_FILL` | R, G, B | 用纯色填充所有 4 个 LED | | `0x03` | `TX_STR` | len, char... | 通过 UART 发送 `len` 个字节 | | `0xAA` | `MAC` | — | 停止(完整性标记,剥离后永远无效) | 该解释器是一个简单的“提取-解码-执行”循环,纯线性且仅向前执行。没有跳转、没有堆栈、没有变量——完全按顺序执行。遇到未知操作码会立即调用 `RET`(即 `BRNE .done` 路径),防止失控的执行。 **示例有效载荷 (hello → world):** ``` 01 FF 00 FF ; LED_FILL R=255 G=0 B=255 (purple) 03 07 ; TX_STR len=7 77 6F 72 6C 64 0D 0A ; "world\r\n" 00 ; END [00 00 ...] ; zero-padding to 16 bytes AA AA ... AA ; 32-byte MAC padding (0xAA × 32) ``` ## 侧信道防御 ### 硬件抖动 (`hardware_jitter_and_exe`) 在哈希循环期间的每次 `encrypt_block` 调用之前,以及在密钥扩展期间,都会注入随机的时序抖动: ``` IN R16, 0x26 ; Read TCNT0 (free-running Timer/Counter 0) ANDI R16, 0x07 ; Isolate bottom 3 bits → jitter in [0, 7] cycles .jitter_loop: DEC R16 BRPL .jitter_loop ; loop while ≥ 0 (signed) ``` 这会在每次 Speck 加密之前引入 0-7 个周期的随机延迟,该延迟派生自自由运行的硬件定时器的低位。通过使重复测量之间的功率轨迹不对齐,这击败了简单的 **差分功耗分析 (DPA)**。 ### 常数时间 MAC 比较 `branchless_mac_verification` 从不对秘密数据进行分支。使用 OR-reduce 累加器和 2 的补码掩码检查 32 字节的 MAC 区域,确保有效和无效 MAC 的执行时间完全相同。 ### 常数迭代加密扫描 无论匹配位置如何,`blind_xor_decryption` + `branchless_mac_verification` 始终被精确调用 `CIPHER_ENTRIES` 次。这可以防止利用计时差来区分“在索引 0 处找到”和“在索引 N-1 处找到”的计时攻击。 ### 密钥材料清零 在加密流水线完成后,`clear_input_buf` 会将 `input_buf` 的所有 32 字节置零,防止密钥材料持续保留在 SRAM 中以供后续读取或冷启动攻击。 ### UART DOR 恢复 `uart_flush_rx` 在每次加密操作后切换 `RXEN0`,以刷新在约 10 秒哈希窗口期间累积的任何 UART 溢出状态。这可以防止数据溢出 (DOR) 标志破坏后续的接收操作。 ### 看门狗失效处理 `clear_reset_flags` 在启动时使用 ATmega328P 硬件要求的解锁双步序列禁用看门狗定时器: ``` CLI ; Global interrupt disable WDR ; Pet the dog one last time CLR R16 OUT MCUSR, R16 ; Clear WDRF (watchdog reset flag) LDI R16, (1<
参见 [LICENSE](https://github.com/mytechnotalent/ouroboros/blob/main/LICENSE)。
标签:AVR汇编, 云资产清单, 侧信道防护, 安全报告生成, 密码学, 嵌入式开发, 手动系统调用, 逆向工具, 逆向工程