
类型:静态代码分析框架
核心概念:Code Property Graph (CPG) = AST + CFG + PDG
语言:Scala
查询语言:CPGQL
官网:https://joern.io
GitHub:https://github.com/joernio/joern
一、Code Property Graph (CPG) 核心概念
CPG = AST + CFG + PDG
┌─────────────────────────────────────────────────────────────┐
│ Code Property Graph (CPG) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ AST │ 抽象语法树 │
│ │ (语法结构) │ if/while/赋值/函数调用... │
│ └─────────────┘ │
│ + │
│ ┌─────────────┐ │
│ │ CFG │ 控制流图 │
│ │ (执行路径) │ 程序执行的分支路径 │
│ └─────────────┘ │
│ + │
│ ┌─────────────┐ │
│ │ PDG │ 程序依赖图 │
│ │ (数据依赖) │ 变量定义→使用 的数据流关系 │
│ └─────────────┘ │
│ │
│ = 一个统一图结构,支持跨函数数据流追踪 │
└─────────────────────────────────────────────────────────────┘
三张图的融合
| 图类型 | 表示内容 | 漏洞分析用途 |
| AST | 语法结构(语法树节点) | 定位函数、变量、表达式 |
| CFG | 控制流(分支跳转路径) | 分析 if/switch/循环 执行路径 |
| PDG | 数据依赖(变量流向) | 污点追踪:source → sink |
二、Joern 在 codebadger 中的架构
codebadger MCP Server
│
│ Docker API
↓
┌─────────────────────────────────────────────────────────────┐
│ Joern Docker Container │
│ (codebadger-joern-server) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ Joern CLI │ CPG 生成 │
│ │ joern-parse │ 源码 → CPG.bin │
│ └─────────────────┘ │
│ │ │
│ ↓ │
│ ┌─────────────────┐ │
│ │ CPG 文件 │ playground/cpgs/{hash}/cpg.bin │
│ │ (二进制图) │ 包含完整的 AST+CFG+PDG │
│ └─────────────────┘ │
│ │ │
│ ↓ │
│ ┌─────────────────┐ │
│ │ Joern Server │ 查询执行 │
│ │ (Scala REPL) │ 加载 CPG → 执行 CPGQL 查询 │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
三、Joern 工作流程
3.1 CPG 生成
// Joern CLI 命令
joern-parse --language c /path/to/codebase
// 输出
生成 cpg.bin(二进制 CPG 文件)
生成过程:
- 解析源码 → AST
- 构建 CFG(控制流分析)
- 构建 PDG(数据依赖分析)
- 合并为 CPG → 序列化为 cpg.bin
3.2 CPGQL 查询执行
// 加载 CPG
importCode("path/to/cpg.bin")
// 查询所有函数
cpg.method.name.l
// 查询所有调用点
cpg.call.code.l
// 污点追踪:从 getenv 到 system
val sources = cpg.call.name("getenv")
val sinks = cpg.call.name("system")
sink.reachableByFlows(source).l
四、CPGQL 查询示例
4.1 查找所有外部函数调用
cpg.call
.where(_.methodName.not(".*internal.*"))
.code.l
4.2 查找缓冲区操作
cpg.call
.name("strcpy", "memcpy", "sprintf")
.code.l
4.3 污点追踪
// 定义污点源
val sources = cpg.call.name("getenv", "fgets", "read")
// 定义污点汇
val sinks = cpg.call.name("system", "strcpy", "sprintf")
// 追踪数据流
sinks.reachableByFlows(sources).map(_.elements).l
4.4 程序切片
// 从某个调用点向后切片
cpg.call.name("malloc")
.newSink
.reachableBy(cpg.method.astNodes)
.l
4.5 查找危险函数调用
// 查找所有 system() 调用
cpg.call.name("system").code.l
// 查找参数来源
cpg.call.name("system")
.argument.code.l
4.6 查找整数溢出模式
// 查找乘法运算后用于内存分配
cpg.call.name("malloc")
.where(_.argument.isCall.name("mul", "*"))
.code.l
五、codebadger 对 Joern 的封装
| codebadger 工具 | Joern 底层调用 |
| generate_cpg | joern-parse --language {lang} {path} |
| list_methods | cpg.method.name.l |
| get_call_graph | cpg.method.callIn.l + cpg.method.callOut.l |
| find_taint_sources | cpg.call.name(getenv, fgets, ...) |
| find_taint_flows | sink.reachableByFlows(source) |
| get_program_slice | sink.reachableBy(cpg.method.astNodes) |
| find_use_after_free | free() → 后续引用追踪 |
| find_double_free | 多个 free() 调用点检测 |
| find_null_pointer_deref | 空指针解引用模式匹配 |
| find_integer_overflow | malloc 参数溢出检测 |
| find_format_string_vulns | printf 非字面量格式参数 |
| find_heap_overflow | 堆缓冲区写入越界 |
| find_stack_overflow | 栈缓冲区写入越界 |
| find_toctou | access/stat + open 分离操作 |
| find_uninitialized_reads | 变量未初始化使用 |
六、Joern 技术优势
| 优势 | 说明 |
| 跨函数分析 | PDG 支持跨函数数据流追踪(突破 LLM token 限制) |
| 多语言支持 | C/C++/Java/Python/JS/Go/C#/PHP/Ruby/Swift/Kotlin (12 种) |
| 可扩展查询 | CPGQL 是 Scala DSL,可写任意复杂查询 |
| 高效缓存 | CPG.bin 可持久化,代码不变无需重新生成 |
| 精确语义 | 三图融合提供完整语义信息(AST+CFG+PDG) |
| 开源免费 | Apache 2.0 许可证,社区活跃 |
七、与其他静态分析工具对比
| 工具 | 类型 | 跨函数分析 | 污点追踪 | LLM 集成 |
| Joern | CPG | ✓ 精确 | ✓ 原生 | ✗ 需封装 |
| CodeQL | Datalog | ✓ 精确 | ✓ 原生 | ✗ 需封装 |
| Semgrep | Pattern | ✗ 单函数 | ✗ 无 | ✓ 直接 |
| Clang Static Analyzer | Path-sensitive | ✓ 精确 | ✓ 原生 | ✗ 需封装 |
| RAG/grep | 文本 | ✗ 无 | ✗ 无 | ✓ 直接 |
codebadger 的独特价值:Joern 的精确语义分析 + MCP 协议 LLM 集成。
八、Joern 核心数据结构
8.1 CPG 节点类型
| 节点类型 | 说明 | 示例 |
| Method | 函数/方法定义 | int foo(int x) {...} |
| Call | 函数调用 | foo(42) |
| Local | 局部变量 | int x = 10; |
| Identifier | 标识符引用 | x, y, foo |
| Literal | 字面量 | 42, "hello", true |
| Block | 代码块 | { ... } |
| If/Else/While/For | 控制结构 | if (x > 0) {...} |
| Return | 返回语句 | return x; |
| Member | 类成员 | class.field |
| TypeDecl | 类型声明 | struct Foo {...} |
8.2 CPG 边类型
| 边类型 | 说明 | 用途 |
| AST 边 | 语法父子关系 | 结构遍历 |
| CFG 边 | 控制流跳转 | 执行路径分析 |
| DDG 边 | 数据依赖(定义→使用) | 污点追踪 |
| CDG 边 | 控制依赖 | 条件影响分析 |
| CALL 边 | 函数调用关系 | 调用图构建 |
| EVAL_TYPE 边 | 类型信息 | 类型推断 |
九、部署 Joern
9.1 codebadger Docker(推荐)
# 启动预配置的 Joern 容器
docker compose up -d
# 验证服务
docker ps | grep joern
9.2 独立安装
# 安装 Joern
curl -L "https://github.com/joernio/joern/releases/latest/download/joern-install.sh" | sh
# 生成 CPG
joern-parse /path/to/code
# 启动交互式查询
joern
# 在 REPL 中查询
importCode("cpg.bin")
cpg.method.name.l
9.3 配置选项
# Java 堆内存(大代码库需要更多内存)
JOERN_JAVA_OPTS="-Xmx16G -Xms8G"
# CPG 生成超时
CPG_GENERATION_TIMEOUT=600 # 10 分钟
# 最大仓库大小
MAX_REPO_SIZE_MB=500
十、CPG 论文背景
原始论文:
The Code Property Graph — A New Program Representation for Security Analysis
Fabian Yamaguchi, Nico Golde, Daniel Arp, Konrad Rieck
NDSS 2014 (Network and Distributed System Security Symposium)
https://www.usenix.org/conference/ndss14
论文贡献:
- 提出 Code Property Graph 统一表示(AST + CFG + PDG)
- 设计 CPGQL 查询语言
- 验证在漏洞检测中的有效性(分析 6 个真实 CVE)
- 奠定 Joern 的理论基础
十一、Joern 应用场景
| 场景 | 用途 |
| 漏洞扫描 | 检测 UAF/Double-Free/Overflow/TOCTOU 等 |
| 代码审计 | 理解大型代码库结构和数据流 |
| 安全研究 | 发现未公开漏洞(如 codebadger 的 5 个 CVE) |
| 补丁验证 | 确认修复是否完整覆盖漏洞路径 |
| 逆向分析 | Ghidra 二进制 CPG 支持 |
| LLM 增强 | 为 AI Agent 提供精确语义导航 |
十二、总结
三句话总结
- 理论基础:CPG = AST + CFG + PDG,三图融合提供完整代码语义
- 技术优势:跨函数数据流追踪、12 种语言支持、可扩展 CPGQL 查询
- 应用价值:codebadger 封装后成为 LLM Agent 的漏洞检测引擎,发现 5 个真实 CVE
核心价值表
| 价值 | 说明 |
| 精确语义分析 | 三图融合提供完整语义信息,超越纯文本分析 |
| 跨函数追踪 | PDG 支持跨边界数据流追踪,突破 LLM token 限制 |
| 开源生态 | Apache 2.0 许可证,活跃社区,持续迭代 |
| LLM 集成桥梁 | codebadger MCP Server 让 Joern 能被 AI Agent 直接调用 |
参考文献