代码分析工具joern的基本用法

 

前言

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实现数据流分析。

 

参考链接

(完)