返回介绍

3 CodeQL U-Boot Challenge

发布于 2024-09-12 23:59:25 字数 4102 浏览 0 评论 0 收藏 0

在 Github Learning Lab 中,有一个用于学习 CodeQL 的入门课程 - CodeQL U-Boot Challenge (C/C++)]( https://lab.github.com/GitHubtraining/codeql-u-boot-challenge-(cc++) )

  • 编写一个简单的查询,用于查询 strlen 函数的定义位置。
import cpp

from Function f
where f.getName() = "strlen"
select f, "a function named strlen"
  • 分析这个简单的查询,之后查询一下 memcpy 函数
import cpp

from Function f
where f.getName() = "memcpy"
select f, "a function named memcpy"
  • 使用不同的类以及不同的谓词。这里我们编写 QL 查找名为 ntohsntohl 以及 ntohll 的宏定义。
import cpp 

from Macro macro
# where macro.getName() = "ntohs" or macro.getName() = "ntohl" or macro.getName() = "ntohll"
where macro.getName().regexpMatch("ntoh(s|l|ll)")
select macro
  • 使用双变量。通过使用多个变量来描述复杂的代码关系,查询特定函数的调用位置。
import cpp

from FunctionCall c, Function f
where c.getTarget() = f and f.getName() == "memcpy"
select c
  • 使用 Step6 的技巧,查询宏定义的调用位置。
import cpp

from MacroInvocation mi
where mi.getMacro().getName().regexpMatch("ntoh(s|l|ll)")
select mi
  • 改变 select 的输出。查找这些宏调用所扩展到的顶级表达式(宏展开)。
import cpp

from MacroInvocation mi
where mi.getMacro().getName().regexpMatch("ntoh(s|l|ll)")
select mi.getExpr() # 注意这里的.getExpr()
  • 实现一个类。用 exists 关键字来引入一个临时变量,以设置当前类的数据集合;特征谓词在声明时会被调用以确定当前类的范围,类似于 C++ 构造函数。

特征谓词在声明时会被调用以确定当前类的范围,类似于 C++ 构造函数。查询语句中的类中,先通过 exists 量词创建一个临时变量 mi 来表示被调用的宏的名字,如果被调用的的宏展开后和当前代码片段相等,则这个表达式属于这个集合。

import cpp

class NetworkByteSwap extends Expr {
  NetworkByteSwap() {
    exists(MacroInvocation mi |
      mi.getMacroName().regexpMatch("ntoh(s|l|ll)") and
      this = mi.getExpr()
    )
  }
}

from NetworkByteSwap n
select n, "Network byte swap"
  • 污点追踪

借助前面几步,基本描述了 CodeQL 的使用。最后一个测试是使用 CodeQL 进行污点追踪。这里使用了 CodeQL 的全局污点追踪 (Global taint tracking)。新定义的 Config 类继承于 TaintTracking::Configuration 。类中重载的 isSource 谓语定义为污点的源头,而 isSink 定义为污点汇聚点。

有时候,远程输入的数据可能经过 ntoh 函数处理,通过转换字节序得到相应的数字。而 memcpy 的第 2 个参数如果控制不当,可造成数据溢出。将上面两个结论结合起来,如果有一个远程输入的数据通过字节序变换得到的数字,在未经过校验的情况下,作为了 memcpy 的第二个参数,那么就有可能造成数据溢出。

isSource 中,判断 sourceExpr 是否是 NetworkByteSwap 这个类,来判断污点的源头。

isSink 中,我们使用了辅助类 FunctionCall 判断函数调用是否为 memcpysink 的代码片段是否为 memcpy 的第二个参数;最后一句则是判断函数的第一个参数是否为常量,如果为常量的话基本不可能出现问题,所有忽略。

import cpp
import semmle.code.cpp.dataflow.TaintTracking
import DataFlow::PathGraph

# 设置用于交换网络数据的类
class NetworkByteSwap extends Expr {
  NetworkByteSwap() {
    exists(MacroInvocation mi |
      mi.getMacroName().regexpMatch("ntoh(s|l|ll)") and
      this = mi.getExpr()
    )
  }
}

# 设置污点跟踪的分析信息
class Config extends TaintTracking::Configuration {
  Config() { this = "NetworkToMemFuncLength" }

  override predicate isSource(DataFlow::Node source) { source.asExpr() instanceof NetworkByteSwap }

  override predicate isSink(DataFlow::Node sink) {
    exists(FunctionCall call |
      call.getTarget().getName() = "memcpy" and
      sink.asExpr() = call.getArgument(2) and
      not call.getArgument(1).isConstant()
    )
  }
}

# 查询
from Config cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink, source, sink, "Network byte swap flows to memcpy"

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文