Skip to content
Back to skills

Re Fp Runtime

ASecurity

函数式语言运行时逆向(Haskell/OCaml):闭包/堆对象模型、调用约定、数据流优先策略。 触发词:Haskell逆向、OCaml逆向、GHC RTS、thunk、STG、OCaml runtime、闭包、函数式产物。

  • 112 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 19, 2026
datapythonrustgoc++

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned September 19, 2026

npx -y skills add dslsdzc/rev-skills --skill re-fp-runtime --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Re Fp Runtime?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Re Fp Runtime
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/dslsdzc-re-fp-runtime/badge)](https://www.skillsdirectory.com/skills/dslsdzc-re-fp-runtime)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: re-fp-runtime
type: atomic
description: >
  函数式语言运行时逆向(Haskell/OCaml):闭包/堆对象模型、调用约定、数据流优先策略。
  触发词:Haskell逆向、OCaml逆向、GHC RTS、thunk、STG、OCaml runtime、闭包、函数式产物。
capabilities: [lang-runtime-analysis]
---

# 函数式运行时逆向(Haskell / OCaml)

## 何时使用 / 何时不用

- 用:Haskell/OCaml 产物(GHC RTS 符号、OCaml block 头特征),需要还原闭包/堆对象、求值顺序、模式匹配分支
- 用:OCaml 原生/字节码产物判别与字节码分析(ocamlrun 脚本头特征)
- 不用:命令式语言产物(各归各技能:C++ → [[re-cpp-abi]]、Go → [[re-go]]、Rust → [[re-rust]])
- 不用:只需函数逻辑且控制流完整(函数式产物控制流打散,直接反编译收益低,见步骤 4)

## 工具准备

### readelf / llvm-nm(符号分析)

- 安装与验证见 [[re-cpp-abi]] 工具准备

### ghc 工具链(Haskell 侧,可选)

- Linux/macOS: GHC 安装包(`apt install ghc` / `brew install ghc` / ghcup);Windows: ghcup(`winget install ghcup` 或官网安装器);验证: `ghc --version`
- 用途: 同版本编译对照产物,验证 closure/info table 形态(GHC 版本差异大)

### ocamlobjinfo / ocamlopt(OCaml 侧,可选)

- Linux/macOS: OCaml 工具链(`apt install ocaml` / `brew install ocaml`);Windows: opam(`winget install OCaml.opam`)或官网安装器;验证: `ocamlobjinfo` 处理任意 .cmx 输出 CRC 与导入表
- 用途: 字节码产物/对象文件结构分析(ocamlobjinfo 可读 .cmo/.cmx/字节码可执行文件)

### Ghidra / IDA(反编译底座)

- 安装与验证见 [[re-ghidra]] / [[re-ida]]

### file / xxd / Python struct(字节级核对)

- 系统自带(`file`/`xxd`);Python 3 自带 `struct`
- 用途: 字节码产物判别(ocamlrun 脚本头)、closure 首字段/block 头字节验证(示例见 [[examples]])

## 操作步骤

按顺序执行;逆向着重**数据流**而非控制流(函数式产物控制流被打散,见坑 4)。每步产物存档(路径 + sha256,见 [[re-triage]])。

1. **运行时识别**:
   ```sh
   readelf -s sample | grep -iE 'ghc|stg_|RTS|HsMain|_closure|_info' | head   # GHC 特征
   readelf -s sample | grep -iE 'caml_' | head                                # OCaml 特征
   file sample                                                              # 字节码产物判别(脚本头)
   ```
   - GHC:`main`(C RTS 入口)+ RTS 运行时符号(`stg_*`/`hs_*`)+ 业务符号 `Main_main_closure`/`Main_main_info`(`模块_名字_closure/info` 形态)
   - OCaml 原生:`main` → `caml_main` → `caml_startup_common` → `caml_start_program` → `caml<模块>__entry`;`caml_startup`/`caml_startup_pooled` 是供 C 嵌入调用的等价入口(签名同为 void (char_os **argv),区别在 pooling 标志与异常行为),勿误当主链;`caml_*` 运行时符号(caml_alloc/caml_apply2/3 等)
   - **字节码 vs 原生**:`caml_start_program` 仅存在于 native 运行库(4.14.2 libasmrun.a 实测),是 native 特征;字节码判据用 `caml_interprete`(仅 libcamlrun.a 有);字节码产物判别用 `file`(`ocamlrun script executable`)/`xxd` 头(`#!...ocamlrun\n` 脚本头 + `T`/`C` 魔数 + 分节)
   - 入口链各版本一致:runtime/main.c 定义 `main` 并调用 `caml_main(argv)`(`caml_main` 定义于 startup_byt.c/startup_nat.c;原生链 `main → caml_main → caml_startup_common → caml_start_program`,4.14.2 实测地址见 [[examples]]);字节码运行库入口为 `caml_main → caml_startup_aux → caml_interprete`
   - 判别速查:GHC = `stg_*` 机械符号群 + `模块_名_closure/info` 对;OCaml 原生 = `caml_*` 群 + `caml<模块>__<名>_<id>`;OCaml 字节码 = `#!ocamlrun` 脚本头

2. **闭包与堆对象**:
   - GHC:thunk(未求值闭包)与已求值值的堆对象布局——closure 首字段即 info table 指针(实测字节验证见 [[examples]]);CAF 以 thunk 形式静态分配,首次引用才求值(惰性);`Main_main_closure` 是 CAF,其 info 指向 thunk 求值代码
   - GHC 值形态:未求值(thunk,info 指向求值代码)vs 已求值 WHNF(info 指向构造器头/函数头)——同地址空间的两种状态,求值后 closure 内容被覆写
   - OCaml:block 头(tag + 大小,64 位下 header = (size<<10)|(color<<8)|tag);tagged int 判定用值的最低位(bit 0,奇数=整数,偶数=指针/block)
   - 分析:字段与构造器是主要线索(数据流优先)

3. **调用约定**:
   - GHC:参数经栈传递;返回值在寄存器 R1-R3(盒值在 R1);entry 代码以 info table 为枢纽(`_info` 符号 = entry code)
   - OCaml:参数经寄存器(前 N 个)传递,闭包调用经 `caml_applyN`;原生代码调用闭包 = 寄存器装载 + `caml_apply2/3` 或直接跳 entry
   - 分析:先识别运行时包装(`caml_apply` / stg 入口)再进用户逻辑;尾调用优化使递归变跳转(无增长栈帧),按循环读
   - GHC 与 OCaml 共点:函数不是"被 call",而是"跳到 entry"——反编译里的 `jmp` 目标地址即函数入口,别按 call/ret 配对思维读

4. **分析策略(数据流优先)**:
   - 控制流打散:惰性求值导致求值顺序不可预测——静态控制流分析价值低
   - 数据流线索:闭包字段初始化点(构造器参数)、模式匹配分支(构造器标签分发)、字符串/常量引用
   - 产出:数据流图(构造器 → 字段 → 使用点)替代控制流图(与 [[re-analyze/analysis-contract]] 数据契约衔接)
   - 模式匹配还原:分支按构造器 tag 分发(OCaml)或 info 表指针比较(GHC)——tag/指针值 → 构造器序号;还原出构造器集合即还原出数据类型
   - 产出格式(供分析报告与下一环节消费):
     ```
     构造器 C1 (tag 0, 2 字段) ← 分配点 A (caml_alloc2 / info 表)
       字段0 ← 函数参数/常量(数据来源)
       字段1 ← 字符串池引用
     使用点: tag 比较 → 分支 B(业务逻辑)
     ```

5. **字节码产物(OCaml 特有)**:
   ```sh
   head -c 64 sample | xxd          # #!...ocamlrun 脚本头 + 魔数 T/C + 长度
   ocamlobjinfo sample              # 直接解析字节码可执行文件(导入单位/CRC)
   ```
   - 字节码 exe = 脚本头 + 魔数 `T` + 代码区 + 各分节数据;分节名(CODE/PRIM/DATA/SYMB/CRCS 等 4 字符)与大端长度表在文件尾部 TOC,文件以 `Caml1999X031` 收尾(结构见 [[layout]],字节样例见 [[examples]])
   - 字节码反汇编不是常规反编译(指令集为 OCaml bytecode 自定),分析入口用 ocamlobjinfo 的结构视图

## 跨域联合

- [[re-binary-core]] 网关:本技能归属(选择树「Haskell/OCaml 产物」分支)
- [[re-analyze/analysis-contract]]:数据流图按数据契约传递
- [[re-cpp-abi]]:vtable/info table 对照思路(表指针分派同构)

## 常见坑与陷阱

- **RTS 版本差异**:现象——closure 布局解读失败;原因——GHC/OCaml 版本演进;对策——按目标版本确认布局(本技能字段表基于 9.14/4.14 实测,见 [[layout]])
- **thunk 惰性求值误导**:现象——未求值闭包被当已求值数据;原因——惰性求值;对策——区分 thunk 头(info 指向求值代码)与已求值值(info 指向 WHNF 头);CAF 首引用前都是 thunk
- **OCaml 字节码非 native**:现象——反编译全是运行时包装;原因——字节码产物;对策——识别 ocamlrun 脚本头/字节码段特征后按字节码结构分析(非常规反编译;`caml_start_program` 仅 native 运行库有,是 native 特征;字节码判据用 `caml_interprete`)
- **控制流打散导致静态分析失效**:现象——函数体无连续逻辑;原因——函数式编译产物;对策——转数据流分析(步骤 4),不硬追控制流
- **tagged int 误读**:现象——整数被当指针/指针被当整数;原因——OCaml 值标记位;对策——按最低位区分(1=整数,0=指针),访问前先解标记(int >> 1 取真值)
- **GHC 模块名带 z 编码**:现象——符号 `GHCziInternalziTopHandler_runMainIO1_info` 难读;原因——`z`+小写转义特殊字符(GHC mangling:`zi`=`.` `zu`=下划线 `zz`=z `zc`=: `zh`=# 等);对策——按转义规则手工还原模块名(`GHCziInternal` → `GHC.Internal`),还原后与源码模块结构对应
- **info table 与 entry code 是同一指针的两个视图**:现象——info 指针处反汇编出的是字段表数据而非代码;原因——info table 指针指向 entry code,表字段在 entry code 之前;对策——反汇编从 info 指针处开始(即 entry),字段表按负偏移读
- **尾调用优化把递归变跳转**:现象——按 call 树分析递归逻辑断裂;原因——函数式编译器的尾调用优化(TCO);对策——`jmp` 回函数自身地址 = 递归,按循环语义读,别找增长栈帧
- **惰性求值顺序不可预测**:现象——按源码顺序单步动态分析对不上;原因——thunk 首次引用才求值,求值触发点在"需要值的地方"而非"产生值的地方";对策——动态分析聚焦数据依赖(哪个闭包被强制求值),静态聚焦字段初始化点,别假设执行顺序
- **跨运行时误判(GHC 机械符号当业务代码)**:现象——`stg_ap_*`/`stg_upd_frame_info` 等被当成业务逻辑分析;原因——STG 机械符号是求值机制;对策——先按 `stg_`/`hs_` 前缀把 RTS 机械符号排除,业务代码集中在 `模块_名_info` 与调用 `caml_applyN`/`caml_alloc*` 的片段

Files in this skill

  • SKILL.md9.7 KB
  • references/examples.md6.5 KB
  • references/layout.md5.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…