LoRexxar's Blog | 信息技术分享
LoRexxar's Blog
马上订阅 LoRexxar's Blog | 信息技术分享 RSS 更新: https://lorexxar.cn/atom.xml
深入浅出Joern(一)Joern与CPG是什么?
从人们开始探索代码扫描这件事情开始,市面上就在不断地诞生着各种各样的工具,经过了几年的演变以及发展,对于白盒代码扫描这件事情来说,大家的观念也在逐渐趋同。
无论是基于IR(Intermediate Representation)、AST(abstract syntax trees)、CFG(control flow graphs)、PFG(program dependence graphs),又或者是其他的什么中间态。白盒代码扫描工具都在这个基础上做模拟执行、污点传播等等方案来分析挖掘漏洞。
而随着CodeQL的概念逐渐被大家接受之后,现在的代码扫描工具越来越趋近于将底层和上层拆解开来,由底层的引擎将代码统一化处理,然后使用者在上层通过编写规则或者语句就可以。主流的CodeQL、Checkmarx其实都使用了类似的方案。今天要说的Joern也是如此。
今天介绍的Joern有什么特殊的呢?
首先CodeQL本身不开源只能使用,偏偏微软还做了商业化限制,以微软喜欢秋后算账的风格来讲,实在无法确定深入研究CodeQL是否值得。
除此之外,市面上的很多白盒扫描工具其实是非静态的,扫描的时候不但需要配置复杂的运行环境,而且本身可能依赖编译过程,无论是自己使用还是商业化这都非常不实用。
个人认为白盒工具有着几个很重要的点
- 静态扫描,静态扫描的优势和便利程度才是白盒比较优势的一环,毕竟白盒不是灰盒,如果对编译环境和运行环境有依赖那为什么不使用更准确的灰盒
- 扫描速度,虽然这点是很多商业化白盒软件的通病,但无论在哪家公司的DevSecOps中,最终目标肯定是把安全检测加载上线前,那么无论是1分钟、3分钟还是5分钟,扫描速度会是第一优先级,比如CheckMarx动辄几小时的扫描肯定是不现实的
- 可diy性,当然对于大部分人来说这点其实并不是很重要,但能对引擎进行深入改造会是优化开发非常重要的一点,joern是开源的,在这方面他有很大的优势
- 可拓展性,市面上大部分的白盒扫描工具动辄支持几十种语言,比如说snoarqube这种,但实际上大部分拓展语言只支持非常简单的正则拓展,我一直觉得现代白盒软件很重要的一条路就是走通用性,这也是比较有名的一些白盒工具都选择的路,在白盒扫描过程中会刻意将统一结构拆分出去再做分析扫描。
今天介绍的joern的其实就是这类工具的一员,他最大的特点其实就是开源。
joern
joern是ShiftLeft 开发的一款基于CPG制作的白盒静态扫描工具,诞生的时间不算早应该就是2021年(具体记不清了)
和其他工具不同,他引用了一种叫做CPG(Code Property Graph)的中间结构作为处理结构,是由AST + CFG + PDG叠加而来,最终生成一张图,然后在图的基础上做分析和检测。和传统的基于单一AST或者CFG的工具相比,图结构一方面能承载更多的代码信息,另一方面,CPG也让后续的分析程序更具有通用性。

这就是一张很经典的范例图,用来展示CPG和其他几种的区别。
另一方面,在用户使用的Joern命令行上,Joern构建了一套基于OverflowDb的查询语言以供使用者可以在不需要知晓底层原理的基础上查询分析。
至于OverflowDB具体是什么,不是很关键,我们只需要了解joern就行了。
什么是CPG?
关于CPG可以看一篇官方写的基础的理论文章。
以下部分内容大量取材于上面这篇文章。
还有就是比较重要的joern的CPG标准文档
在介绍CPG之前,首先要先对图结构有个基础的概念,无论是图数据库又或者是图结构其实说白了就是把节点以及节点之间的关系以图的方式展示出来。就比如下图表明A和B就是朋友,B和C也是朋友。

换到代码中说白了就是通过图的方式展示代码中不同节点之间的关系,而这个节点可能是代码块,可能是函数块,可能是变量块,他们之间会通过边的属性来展示节点之间的关系。

而代码在编译执行前会经过几个复杂的步骤,在经过最简单的词法分析和语法分析,代码就会被转为AST(abstract syntax trees)也就是抽象语法树,这也是普遍会用到的通用结构,因为从AST开始不同语言的差异就是就很小了,也会有非常标准的结构。

AST则是一个经典的树结构,这算是数据结构当中非常经典的一个,通过遍历树结构我们就能得到更底层的某种结构,比如IR就是这类结构的一种,这种结构会具有更强的执行顺序,相应的也会模糊掉一些语法。
而CFG(**control flow graphs**)是一种更强调执行流的结构,节点和节点之间只有调用关系,而且会有比较强的代码执行顺序,边上会展示执行相应的条件。

而另一个比较有用但是比较少见的就是PDG(**program dependence graphs**),PDF也是一种图关系,通过图来展示代码节点之间的依赖关系,他更强调的是节点和节点之间的关系,节点之间的边会展示数据节点的影响关系,所以图结构会更复杂,但会更易于寻找节点之间的关系。
下面这张图就是一张PDG,上面的两个对于x和y的定义会单向影响后续节点的变化,这种联动关系很清晰,这就是PDG的优势。

但无论是AST、CFG、PDG或者是IR等数据结构,又或者是某个原创的中间结构,他们的目标都是一致的,就是用更通用的方式解释代码,这整体可以算作编译原理的前端。它本质上没有实际的区别,无非就是哪种通用结构被拿来做代码分析。

而CPG在这个环境下主体由AST、CFG、PDG多种结构融合而来,我觉得它最大的特点就是利用了图结构庞大的信息容纳能力(毕竟图本身并不是二维的,图结构可以很复杂),可以保证我们在代码分析中遇到任何情况都可以在CPG中找到相应的答案和场景。这是图结构相比其他中间结构解决方案难以比拟的优势。

joern做了什么?
而joern作为一个白盒的代码分析工具,主要做了两部分。
第一部分是实现了一种方案来比较通用的代码转CPG,他的原理也很简单,用已经有的某个组件来实现语义分析部分,然后把不同的AST转成统一的AST,最终转成目标CPG。

而第二部分就是,在已有的CPG基础上,实现了一套查询语法,类似CodeQL,这种,允许通过这种语法来构造不同的查询逻辑实现最终的目标
1 | > def source = cpg.identifier.typeFullName(".*HttpServletRequest.*") |
拿上面这段代码举例子就是,寻找java当中的代码执行漏洞范围,通过简单的指定source和sink就能实现漏洞挖掘,中间的步骤被封装起来。
它同样支持你使用复杂的Scala脚本进行代码的扫描和处理。通过Scala可以实现更复杂的查询和数据流分析。

相比其他的某个白盒工具来说,joern的优势有一点儿非常特例,这点在CodeQL中也有很强烈的体现,就是大部分的白盒扫描工具对于底层的包裹非常严密,很多工具你只能简单的拿来扫描漏洞。
一方面你无法清楚的知道,从这次扫描中你做了什么事情得到了什么东西,甚至无法知道这些漏洞是怎么被扫描或者是没有被扫描到。
另一方面,如果你的目标并不是单纯的扫描漏洞,而是想要通过工具辅助分析代码,比如想知道某个函数如何访问到,这种问题大概率没有答案。
如果对Joern的设计理念感兴趣,可以看看设计者写的文章
或者看看设计师的PPT
使用joern
根据官网的文档,我们可以快捷的安装joern环境
1 | wget https://github.com/joernio/joern/releases/latest/download/joern-install.sh... |