reverse-skills-max: initial public release (restored, redacted, hardened)

This commit is contained in:
baradok609hecl1pq6qi
2026-10-20 02:17:15 +03:00
commit da24c2bd20
2227 changed files with 443931 additions and 0 deletions

View File

@@ -0,0 +1,117 @@
# NuttX:FLAT / PROTECTED / KERNEL 三种内存模型
<CORE RULE>
分析 NuttX 之前**必须先问一句:这是 FLAT、PROTECTED 还是 KERNEL 构建?**
不知道是哪一种,反编译出来的 syscall 与地址语义**很容易全错**——同一个 API 在三种模型下的产物完全不同。
</CORE RULE>
## 一、三种模型
| 模型 | 结构 | 地址空间 |
|---|---|---|
| **FLAT** | OS 与应用在同一个平坦镜像里,可直接互相访问 | 单一 |
| **PROTECTED** | 两遍构建产出**两个 blob**:特权内核 blob + 非特权用户 blob;由 MPU 施加限制;**用户侧 API 是自动生成的 syscall proxy** | 内核态/用户态分离 |
| **KERNEL** | 内核地址空间 + **每个用户进程独立地址环境**;需要 MMU | 每进程独立 |
## 二、同一个 API,反编译结果完全不一样
以 `getpid()` 为例:
```
FLAT :直接 call 到 NuttX 的实现
PROTECTED :pid_t getpid(void) { return (pid_t)sys_call0(SYS_getpid); }
→ 特权切换 → 内核侧 stub → 真正的 getpid
```
- **PROTECTED 下面向用户的 API 是自动生成的 syscall proxy**:由 `syscall/syscall.csv` 经 `tools/mksyscalls` 生成;内核侧另有自动生成的 stub 把 syscall 映射回真实调用
- 因此:
```
getpid() 只有两三条汇编、马上 SVC/trap
→ 不要判 "libc 被 hook" 或 "syscall wrapper 异常"
→ 这是该构建方式的正常形态
```
**RE 判据**:看到大量"极短函数 + 立即陷入特权级"的模式,先确认是不是 PROTECTED 构建,再谈可疑性。
## 三、两个堆(PROTECTED 下)
- 默认情况下可以只有**一个用户空间堆**,被内核态与用户态代码共用——简单,但内核态的分配没有隔离
- 开启 `CONFIG_MM_MULTIHEAP=y` 与 `CONFIG_MM_KERNEL_HEAP=y` 后有两套分配器:
- **非受保护的用户堆**:内核与用户代码共用(分配器物理上位于用户地址空间,内核从用户 blob 头部的信息拿到它的地址)
- **受保护的内核堆**:只有 NuttX 内核可访问,构建进内核块;`CONFIG_MM_KERNEL_HEAPSIZE` 设其大小(默认 8192 字节),剩余内存给用户堆,**按最小内存保护区对齐后内核堆实际可能更大**
- 接口分工:`up_allocate_heap()` 返回用户堆,`up_allocate_kheap()` 提供内核堆
- 该配置项在 protected/kernel 构建下默认开启、在 flat 下默认关闭
**所以**:
```
地址 A malloc 出来用户能访问
地址 B malloc 出来用户 fault
→ 不一定是堆损坏,可能是分别来自 malloc 与 kmalloc
```
## 四、KERNEL 模型的堆布局
一个内核堆 + **每个 task group 一个用户堆**;只有内核堆在架构的 `up_allocate_kheap()` 里初始化。典型内存图:内核 `.data` / 内核 `.bss` / 内核 idle 栈 / 填充 / 用户 `.data` / 用户 `.bss` / 内核堆(`CONFIG_MM_KERNEL_HEAPSIZE`)/ 用户堆(延伸到 SRAM 末尾)。
## 五、驱动分 upper half 与 lower half
NuttX 驱动通常分两层:
| 层 | 位置 | 职责 |
|---|---|---|
| **upper half** | `drivers/`(通用、与硬件无关) | 用 `register_driver()` / `register_blockdriver()` 注册,实现 `read`/`write`/`close`/`ioctl`/`poll` 等标准接口,**通过回调调用 lower half** |
| **lower half** | `arch/<架构>/src/...` 或 `boards/<架构>/<芯片>/<板>/src` | 寄存器访问与配置、中断处理,按标准回调结构(如串口的 `uart_ops_s`)暴露能力 |
- 一个 upper half 可以服务**多个** lower half 实例(一对多)
- **板级逻辑负责创建 lower-half 实例并把它与通用 upper half 绑定**
- 网络驱动同构:接口在 `include/nuttx/net/netdev_lowerhalf.h`,upper half 为 `drivers/net/netdev_upperhalf.c`,注册用 `netdev_lower_register()` 一类接口
- 驱动初始化分阶段:`drivers_early_initialize()`(堆/调度器可用之前)→ `drivers_initialize()`(注册核心伪设备如 `/dev/null`、`/dev/zero`)→ `board_app_initialize()`(经 `boardctl` 注册板级 lower half)
**所以**:
```
open/read("/dev/foo")
→ VFS → 通用 upper half → ops 回调 → 板级 lower half → 真正的 MMIO
```
**只逆 `drivers/foo.c` 很可能找不到真正操作寄存器的地方。**
## 决策树
```
拿到 NuttX 目标
├─ 先判构建模型:FLAT / PROTECTED / KERNEL
│ ├─ 看是否存在用户 blob、MPU/MMU 使用、是否有一堆极短的 syscall 包装函数
│ └─ 判错 → 后面地址与调用语义全错
├─ 看到极短的 API 包装 + 立即 trap
│ └─ PROTECTED 的自动生成 proxy(syscall.csv → mksyscalls),不是 hook
├─ 内存相关异常
│ ├─ 区分 malloc / kmalloc 与 user heap / kernel heap
│ ├─ PROTECTED 下默认可能只有一个共享堆;启用 MM_KERNEL_HEAP 才有内核专用堆
│ └─ 地址"用户不可访问"可能只是它来自内核堆
├─ 找不到硬件操作代码
│ └─ 走 upper half → lower half 两层:drivers/ 是通用层,真正 MMIO 在 arch/ 或 boards/
└─ 时序/初始化相关
└─ 分清 drivers_early_initialize / drivers_initialize / board_app_initialize
```
## 工具与验证
- 构建模型:是否存在用户 blob、MPU/MMU 配置、syscall proxy 与自动生成的 stub
- 堆:`up_allocate_heap()` / `up_allocate_kheap()` 与相关 Kconfig 项
- 驱动:upper half(`drivers/`,注册接口)与 lower half(`arch/`、`boards/`,硬件实现)的绑定关系
- 验证:能同时说清「构建模型 + 该 API 走的是直接调用还是 syscall proxy + 地址属于哪个堆/哪个地址空间」
## 该平台的坑(汇总)
- **不先判 FLAT/PROTECTED/KERNEL**:同一 API 的产物与地址语义完全不同
- **把 syscall proxy 当真实实现**:PROTECTED 下用户侧 API 是自动生成的两三条汇编 + trap
- **把 proxy 判成 hook/libc 异常**:这是设计本身
- **忽略双堆**:用户不可访问的地址可能来自内核堆,不是堆损坏
- **以为所有构建都有内核堆**:`MM_KERNEL_HEAP` 在 flat 下默认关闭
- **只逆 `drivers/` 就找硬件操作**:真正 MMIO 通常在 `arch/` 或 `boards/` 的 lower half
- **忽略板级绑定逻辑**:实例由板级代码创建并与通用 upper half 绑定
- **混淆驱动初始化阶段**:early / drivers / board 三阶段可用的设施不同