CodeQL U-Boot for C/C++
前言
这是一个来自github learning lab的项目:
CodeQL U-Boot Challenge (C/C++)
Step1:Welcome
本课程是为了帮助您快速学习 CodeQL,我们的查询语言和代码分析引擎。 目标是使用 CodeQL 及其库分析 C/C++ 代码,在称为 U-Boot 的开源软件中找到几个远程代码执行 (RCE) 漏洞。 要找到真正的漏洞,您需要编写一系列查询,使它们在课程的每一步都更加精确
Step2:安装环境
CodeQL的安装无需多言,
在此处下载U-Boot的代码数据库:
然后将其加入工作区即可
Step3:第一条查询语句
这里我们要完成第一个查询语句:找到名称为strlen的函数
编辑3_function_definitions.ql
,代码如下:
1 | import cpp |
这样就能找到strlen函数的定义点了
然后就是将其提交到step-3分支:
1 | git checkout main |
或者提交到main分支也可以:
1 | git add . |
然后等待check完成即可
Step4:查询解析
这一步骤就是对前面查询语句的解析
除此之外,需要完成对memcpy函数的查询
4_memcpy_definitions.ql
如下:
1 | import cpp |
然后提交
Step5:使用不同的类及其谓词
我们想要识别从网络数据提供的整数值。 发现这些的一个好方法是寻找网络排序转换宏的使用,例如 ntohl、ntohll 和 ntohs
在查询的 from 部分中,您声明了一些变量,并说明了这些变量的类型。 类型告诉我们变量的可能值是什么
在前面的查询中,您正在查询类 Function 中的值以在源代码中查找函数。 我们必须查询不同的类型才能在源代码中找到宏
编辑文件 5_macro_definitions.ql
编写一个查询,查找名为 ntohs、ntohl 或 ntohll 的宏的定义 可以使用 or 关键字来组合您希望至少满足一个条件的多个条件。 在这里,我们对三个可能的宏名称感兴趣。
代码如下:
1 | import cpp |
也可以:
1 | import cpp |
Step6:关联两个变量
在第 4 步中,您编写了一个查询,用于查找代码库中名为 memcpy 的函数的定义。 现在,我们想在代码库中找到所有对 memcpy 的调用
一种方法是声明两个变量:一个表示函数,一个表示函数调用。 然后,您必须在 where 部分中创建这些变量之间的关系,以便它们仅限于名为 memcpy 的函数,并准确调用这些函数
这一步骤的关键其实是要找到正在调用的目标函数的谓词
可以参考以下示例:
1 | //Finds calls to std::map<...>::find() |
编辑6_memcpy_calls.ql
:
1 | import cpp |
我们可以把其简化为:
1 | import cpp |
Step7:关联两个变量
在第 5 步中,您编写了一个查询,用于查找代码库中名为 ntohs、ntohl 和 ntohll 的宏的定义。 现在,我们想在代码库中找到这些宏的所有调用
这将类似于您在第 6 步中所做的,在该步骤中您为函数和函数调用创建变量,并限制它们查找特定函数及其调用
注意:宏调用是源代码中调用特定宏的位置。 这类似于函数调用是源代码中调用特定函数的位置
编辑7_macro_invocations.ql
:
1 | import cpp |
Step8:更改选定的输出
在上一步中,您找到了我们感兴趣的宏的调用。修改您的查询以找到这些宏调用扩展为的顶级表达式
注意:表达式是可以在运行时具有值的源代码元素。调用宏可以将各种源代码元素带入范围,包括表达式
使用上一个查询编辑文件8_macro_expressions.ql
使用选择部分中的getExpr()
谓词返回所需的表达式
如下:
1 | import cpp |
Step9:编写自己的类
在这一步中,我们将学习如何编写自己的 CodeQL 类。 这将帮助我们使查询的逻辑更具可读性、更易于重用和更易于细化
我们希望找到与上一步相同的结果,即对应于 ntohl、ntohs 和 ntohll 宏调用的顶级表达式。 如果我们可以直接引用所有这样的表达式将会很有用,就像我们可以使用标准库中的MacroInvocation
来引用所有宏调用一样
我们将定义一个类来准确描述这组表达式,并在本课程的最后一步中使用它
Expr 类是所有表达式的集合,我们对更具体的表达式集感兴趣,所以我们编写的类将是 Expr 的子类
到目前为止,我们已经在查询子句的 from 部分声明了变量。 有时我们在查询的其他部分需要临时变量,并且不想在查询子句中暴露它们。 exists 关键字可以帮助我们做到这一点。 它是一个量词:它引入临时变量并检查它们是否满足特定条件
例如:
1 | from Person t |
此查询选择所有头发颜色为字符串的人。 所以我们会得到所有不秃头的人,因为我们能够找到一个定义他们头发颜色的 c。 除了知道它存在之外,我们在查询中实际上并不需要 c
我们需要编写自己的NetworkByteSwap
类
使用以下模板编辑文件 9_class_network_byteswap.ql
:
1 | import cpp |
这个类扩展了 Expr,这意味着它是 Expr 的一个子类,它首先从 Expr 中获取所有值。 现在您需要将其限制为仅我们感兴趣的表达式,这些表达式满足步骤 8 的条件
您可以通过编辑特征谓词 NetworkByteSwap() { … } 来做到这一点。 该模板包括存在量词,这将有所帮助
在 exists 中声明一个引用宏调用的临时变量
将此宏调用约束在存在的条件部分。在步骤 8 中使用查询的 where 部分中的相同逻辑
ql代码如下:
1 | import cpp |
Step10:数据流和污点跟踪分析
在第 9 步中,我们在源代码中发现了可能具有从远程输入提供的整数的表达式,因为它们正在通过调用 ntoh、ntohll 或 ntohs 进行处理。这些可以被认为是远程输入的来源
在第 6 步中,我们找到了对 memcpy 的调用。当它们的长度参数由远程用户控制时,这些调用可能是不安全的。它们的长度参数可以被认为是接收器:它们不应该在没有进一步验证的情况下接收用户控制的值
结合这些信息,我们知道,如果受污染的数据在 memcpy 调用的长度参数中从网络整数源流向接收器,则代码很容易受到攻击
但是,我们如何知道来自特定源的数据是否可能到达特定接收器?这称为数据流或污点跟踪分析。鉴于结果的数量(数百个 memcpy 调用和大量宏调用),手动对所有这些情况进行分类将是相当多的工作
为了使我们的分类工作更容易,我们将让 CodeQL 为我们做这个分析
您现在将编写一个查询来跟踪从网络控制的整数到 memcpy 长度参数的污染数据流。结果,您会发现 9 个真正的漏洞!
为此,我们将使用 CodeQL 污点跟踪库。该库允许您描述源和接收器,当来自给定源的受污染数据流向接收器时,它的谓词hasFlowPath
成立
使用下面的模板编辑文件 10_taint_tracking.ql
:
1 | /** |
- 从第 9 步复制并粘贴您对 NetworkByteSwap 类的定义
- 编写 isSource 谓词。这应该识别调用 ntohl、ntohs 或 ntohll 中的表达式
- 您已经在第 9 步的 NetworkByteSwap 类中描述了这些表达式。这里我们需要检查源是否对应于属于该类的值
- 要检查一个值是否属于 CodeQL 类,请使用
instanceof
构造 - 请注意,源变量是 DataFlow::Node 类型,而您的 NetworkByteSwap 类是 Expr 的子类,因此我们不能只编写 NetworkByteSwap 的源实例。 (试试这个,编译器会给你一个错误。)在源代码上使用自动完成来发现让我们将其视为 Expr 的谓词
- 编写 isSink 谓词: sink 应该是调用 memcpy 的 size 参数 使用自动完成查找返回函数调用的第 n 个参数的谓词 使用您在编写 isSource 时发现的谓词将接收器视为 Expr
运行您的查询。请注意,第一次运行将比之前的查询花费一点时间,因为数据流分析更复杂
代码如下:
1 | /** |
可以看到确实找出了如下的可疑点: