前言
joern是一个开源的源码分析工具,来自于安全顶会S&P的14年的一篇文章。目前网上关于joern的教程大部分是旧版的,考虑到目前还没有太多新版joern的教程,于是我就写了这篇文章。
相比其他工具,joern的优势:
- 能够生成代码属性图(CPG)
- 源码不需要编译也可以分析(适合分析固件、设备)
和其他成熟工具相比,也有一些劣势:
- 没有过程间分析
- 版本迭代比较快,不太稳定
安装
以下安装是在ubuntu 16.04下,其他环境应该可以(猜测)。
先安装java8(有的话可以无视)
sudo apt-get update
sudo apt-get install openjdk-8-jdk
直接安装
git clone https://github.com/ShiftLeftSecurity/joern
cd joern
sudo ./joern-install.sh
命令行输入joern,可以看到如下界面,就说明安装成功了。
基本用法
工程管理
导入代码
joern> importCode(inputPath="./tests/code",projectName="test")
- inputPath是代码文件夹路径(可以用相对路径,也可以用绝对路径)
- projectName是工程名,方便下次直接打开
用workspace命令可以查看有哪些工程。
用close命令可以关闭当前的工程
打开工程
joern> open(ProjectName)
查询
joern的查询大体上分为三个步骤:
- 选择起点
- 遍历图
- 筛选数据
以以下代码为例,我们要查询所有的调用printf语句的行数、代码文本、文件名:
#include <stdio.h>
int main() {
print_number(42);
return 0;
}
void print_number(int x) {
printf(x);
return;
}
因为要查询调用语句,所以我们的起点可以是cpg.call,我们可以先用命令cpg.call.head,来看下call包含什么属性。
joern> cpg.call.head
res28: Call = Call(
id -> 1000103L,
code -> "print_number(42)",
name -> "print_number",
order -> 1,
methodFullName -> "print_number",
argumentIndex -> 1,
signature -> "TODO",
typeFullName -> "ANY",
lineNumber -> Some(value = 4),
columnNumber -> Some(value = 4),
methodInstFullName -> None,
depthFirstOrder -> None,
internalFlags -> None,
dispatchType -> "STATIC_DISPATCH",
dynamicTypeHintFullName -> List()
)
可以看到这里的call是print_number,并不是我们想要的printf,所以我们可以设置一些条件,来在遍历的时候筛选我们想要的节点。这里我们可以用where来筛选,名字为printf的调用。下面命令的.l
表示.toList
的简写,是将结果输出为列表。
joern> cpg.call.where(_.name("printf")).l
res29: List[Call] = List(
Call(
id -> 1000111L,
code -> "printf(x)",
name -> "printf",
order -> 1,
methodFullName -> "printf",
argumentIndex -> 1,
signature -> "TODO",
typeFullName -> "ANY",
lineNumber -> Some(value = 9),
columnNumber -> Some(value = 4),
methodInstFullName -> None,
depthFirstOrder -> None,
internalFlags -> None,
dispatchType -> "STATIC_DISPATCH",
dynamicTypeHintFullName -> List()
)
)
最后就是筛选我们想要的信息(行数,代码文本,代码所在的文件名),这里用的是map来实现。map里有点类似lambda表达式的写法。
joern> cpg.call.where(_.name("printf")).map(
x=>(x.code,x.lineNumber,x.file.name.l)).l
res42: List[(String, Option[Integer], List[String])] = List(
("printf(x)", Some(value = 9), List("/home/iskindar/joern/tests/code/test.c"))
)
如果要将结果输出为json格式的文件的话,只要将上面命令的.l
改为.toJson |> "test.json"
,完整命令如下:
joern> cpg.call.where(_.name("printf")).map(
x=>(x.code,x.lineNumber,x.file.name.p)).toJson |> "test.json"
默认会在joern目录下生成一个test.json,你也可以加上路径。
其他小技巧
- 输入tab命令能够自动补全
- cpg.help可以查看帮助
- 除了.l能够输出列表,
.s
能够输出集合 - 运行外部脚本
比如要运行如下脚本,该脚本接受两个输入,cpgfile是代码属性图路径,outfile是结果的输出路径。这个脚本会将代码属性图里的方法名字都输出到outfile去。
@main def exec(cpgFile: String, outFile: String) = {
loadCpg(cpgFile)
cpg.method.name.l |> outFile
}
在命令行(不是joern的命令行)输入如下命令:
joern --script test.sc --params cpgFile=/home/iskindar/joern/workspace/test/cpg.bin,outFile=output.log
大概会冒出这样的提示,script finished successfully:
然后查看一下我们的output.log,也确实将工程里的所有方法都输出到output.log文件里了。
画各种图
官方文档也给出了画各种图的基本说明:https://docs.joern.io/exporting
目前joern支持的图的类型主要有:
- 代码属性图(CPG)
- 抽象语法树(AST)
- 控制流图(CFG)
- 控制依赖图(CDG)
- 数据依赖图(DDG)
- 程序依赖图(PDG)
命令行画图
在joern命令行可以用如下命令画上面的各种图。
cpg.method($name).plotDotCpg14
cpg.method($name).plotDotAst
cpg.method($name).plotDotCfg
# 我自己测试了下,发现下面三个在命令行好像生成不了图片,后面会介绍用别的方法来生成这三个图
cpg.method($name).plotDotCdg
cpg.method($name).plotDotDdg
cpg.method($name).plotDotPdg
比如画下面这个main函数的代码属性图。
#include <stdio.h>
int main() {
print_number(42);
return 0;
}
void print_number(int x) {
printf(x);
return;
}
输入命令:
cpg.method("main").plotDotCpg14
同时也可以在命令行输出dot文件,方便提取图中的信息
cpg.method($name).dotAst.l // output AST in dot format
cpg.method($name).dotCfg.l // output CFG in dot format
...
cpg.method($name).dotCpg14.l // output CPG'14 in dot format
例如:
joern-export 导出依赖图
如果按前面的安装正确后,直接在命令行输入joern-export --help
,应该会出现如下的输出。
iskindar@ubuntu:~/joern/workspace/test$ joern-export --help
Dump intermediate graph representations of code onto disk
Usage: joern-export [options] [cpg]
--help
cpg CPG file name ('cpg.bin' by default)
--out <value> output directory
--repr <value> representation to extract: [ast|cfg|ddg|cdg|pdg|cpg14]
由于目前的joern貌似不能在命令行中正确地生成依赖图,但是joern-export工具却可以。下面以生成ddg为例。
joern-export命令是基于代码属性图的,所以需要找到工程的代码属性图文件在哪里。如果是在命令行里导入代码的话,一般在joern/workspace/工程名文件夹下。
joern-export --out out-ddg --repr ddg cpg.bin
这里的out-ddg是输出ddg的dot文件的文件夹名字,joern-export会自动在当前目录下生成out-ddg。在运行完上面的命令后,我们可以发现该文件夹多了几个dot文件。
我们可以用dot命令来可视化dot文件,这里我们选0-ddg.dot。
dot -Tpng 0-ddg.dot -o 0-ddg.png
然后打开图片,可以发现我们生成了main函数的数据依赖图(DDG)。
总结
最后用一张思维导图来总结下joern工具的基本用法。后续有时间,会尝试一下如何用joern实现数据流分析。