使用 Semmle QL 进行漏洞搜索 Part 2
字数 820 2025-08-26 22:11:40

Semmle QL 漏洞挖掘技术详解(第二部分)

1. 概述

本文是Semmle QL漏洞挖掘系列的第二部分,重点介绍如何使用Semmle QL进行实际的漏洞挖掘工作。在第一部分介绍了Semmle QL基础概念后,本文将通过Azure固件组件的安全审核案例,详细演示如何利用Semmle QL进行静态代码分析。

2. 目标分析环境

2.1 审查目标

  • 基于Linux的嵌入式设备
  • 连接服务后端和管理后端
  • 在两个接口之间传递操作数据
  • 主要攻击面:两个接口使用的管理协议

2.2 协议特点

  • 基于消息的管理协议
  • 超过400种不同的消息类型
  • 每种类型有独立的处理函数
  • 手动审计难度大,易出错

3. 攻击定义与建模

3.1 消息路由机制

协议在请求-响应基础上工作,消息类型由类别和命令编号标识:

MessageCategoryTable g_MessageCategoryTable[] = {
    { CMD_CATEGORY_BASE, g_CommandHandlers_Base },
    { CMD_CATEGORY_APP0, g_CommandHandlers_App0 },
    ...
    { NULL, NULL }
};

CommandHandlerTable g_CommandHandlers_Base[] = {
    { CMD_GET_COMPONENT_VER, sizeof(ComponentVerReq), GetComponentVer },
    { CMD_GET_GLOBAL_CONFIG, -1, GetGlobalConfig },
    ...
};

3.2 QL建模 - 命令处理程序表

class CommandHandlerTable extends Variable {
    CommandHandlerTable() {
        exists(Variable v | 
            v.hasName("g_MessageCategoryTable") and 
            this.getAnAccess() = v.getInitializer().getExpr().getAChild().getChild(1)
        )
    }
}

3.3 QL建模 - 消息处理函数

class MessageHandlerFunction extends Function {
    Expr tableEntry;
    
    MessageHandlerFunction() {
        exists(CommandHandlerTable table | 
            tableEntry = table.getInitializer().getExpr().getAChild()
        ) and 
        this = tableEntry.getChild(2).(FunctionAccess).getTarget()
    }
    
    int getExpectedRequestLength() {
        result = tableEntry.getChild(1).getValue().toInt()
    }
    
    Parameter getRequestDataPointer() {
        result = this.getParameter(0)
    }
    
    Parameter getRequestLength() {
        result = this.getParameter(1)
    }
}

4. 数据流分析技术

4.1 数据流库导入

import semmle.code.cpp.dataflow.DataFlow

4.2 基本数据流查询结构

class RequestDataFlowConfiguration extends DataFlow::Configuration {
    RequestDataFlowConfiguration() { this = "RequestDataFlowConfiguration" }
    
    override predicate isSource(DataFlow::Node source) { ... }
    
    override predicate isSink(DataFlow::Node sink) { ... }
    
    override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { ... }
    
    override predicate isBarrier(DataFlow::Node node) { ... }
}

from DataFlow::Node source, DataFlow::Node sink
where any(RequestDataFlowConfiguration c).hasFlow(source, sink)
select "Data flow from $@ to $@", source, sink

5. 内存安全漏洞检测

5.1 数组越界访问检测

override predicate isSource(DataFlow::Node source) {
    any(MessageHandlerFunction mhf).getRequestDataPointer() = source.asParameter()
}

override predicate isSink(DataFlow::Node sink) {
    exists(ArrayExpr ae | ae.getArrayOffset() = sink.asExpr())
}

override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
    exists(Expr e, FieldAccess fa | 
        node1.asExpr() = e and 
        node2.asExpr() = fa and
        fa.getQualifier*() = e and 
        not (fa.getParent() instanceof FieldAccess)
    )
}

override predicate isBarrier(DataFlow::Node node) {
    exists(ConditionalStmt condstmt | 
        node.asExpr().(VariableAccess).getTarget().getAnAccess() = 
            condstmt.getControllingExpr().getAChild*() and
        condstmt.getASuccessor+() = node.asExpr() and
        not (node.asExpr() = cs.getControllingExpr().getAChild*())
    )
}

5.2 危险函数调用检测

class ArgumentMustBeCheckedFunctionCall extends FunctionCall {
    int argToCheck;
    
    ArgumentMustBeCheckedFunctionCall() {
        (this.getTarget().hasName("memcpy") and argToCheck = 2) or
        (this.getTarget().hasName("_fmemcpy") and argToCheck = 2) or
        (this.getTarget().hasName("CalculateChecksum") and argToCheck = 1)
    }
    
    Expr getArgumentToCheck() {
        result = this.getArgument(argToCheck)
    }
}

override predicate isSink(DataFlow::Node sink) {
    exists(ArgumentMustBeCheckedFunctionCall fc | 
        fc.getArgumentToCheck() = sink.asExpr()
    )
}

6. 污点跟踪技术

6.1 污点跟踪库导入

import semmle.code.cpp.dataflow.TaintTracking

6.2 污点传播规则

predicate isTaintedString(Expr expSrc, Expr expDest) {
    exists(FunctionCall fc, Function f | 
        expSrc = fc.getArgument(1) and
        expDest = fc.getArgument(0) and
        f = fc.getTarget() and
        (f.hasName("memcpy") or f.hasName("_fmemcpy") or 
         f.hasName("memmove") or f.hasName("strcpy") or
         f.hasName("strncpy") or f.hasName("strcat") or
         f.hasName("strncat"))
    ) or
    exists(FunctionCall fc, Function f, int n | 
        expSrc = fc.getArgument(n) and
        expDest = fc.getArgument(0) and
        f = fc.getTarget() and
        ((f.hasName("sprintf") and n >= 1) or
         (f.hasName("snprintf") and n >= 2))
    )
}

override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
    isTaintedString(node1.asExpr(), node2.asExpr())
}

7. 路径遍历漏洞检测

7.1 文件操作函数定义

class FileOpenFunction extends Function {
    FileOpenFunction() {
        this.hasName("fopen") or this.hasName("open")
    }
    
    int getPathParameter() {
        result = 0  // filename parameter index
    }
}

override predicate isSink(DataFlow::Node sink) {
    exists(FunctionCall fc, FileOpenFunction fof | 
        fc.getTarget() = fof and
        fc.getArgument(fof.getPathParameter()) = sink.asExpr()
    )
}

7.2 函数调用图分析

predicate mayCallFunction(Function caller, FunctionCall fc) {
    fc.getEnclosingFunction() = caller or
    mayCallFunction(fc.getTarget(), fc)
}

from MessageHandlerFunction mhf, FunctionCall fc, FileOpenFunction fof
where mayCallFunction(mhf, fc) and
      fc.getTarget() = fof and
      not fc.getArgument(fof.getPathParameter()).isConstant()
select mhf, "$@ may have a path to $@", mhf, mhf.toString(), fc, fc.toString()

8. 实际成果

通过上述技术,在目标代码库中发现了:

  • 15个消息处理函数中的18个越界读写漏洞
  • 17个未验证参数的函数调用漏洞(多数为越界读取)
  • 2个消息处理函数中的3个整数下溢漏洞
  • 5个潜在路径遍历漏洞(确认2个)

总计发现33个易受攻击的消息处理函数。

9. 总结

Semmle QL提供了强大的静态分析能力:

  1. 可以自动化执行重复性代码审查任务
  2. 能够发现传统手动审计容易遗漏的漏洞
  3. 支持复杂的数据流和污点跟踪分析
  4. 可针对特定代码模式定制查询
  5. 在深度防御策略中发挥重要作用

通过结合数据流分析和污点跟踪技术,Semmle QL能够有效地识别各种类型的安全漏洞,包括内存安全问题和逻辑漏洞。

Semmle QL 漏洞挖掘技术详解(第二部分) 1. 概述 本文是Semmle QL漏洞挖掘系列的第二部分,重点介绍如何使用Semmle QL进行实际的漏洞挖掘工作。在第一部分介绍了Semmle QL基础概念后,本文将通过Azure固件组件的安全审核案例,详细演示如何利用Semmle QL进行静态代码分析。 2. 目标分析环境 2.1 审查目标 基于Linux的嵌入式设备 连接服务后端和管理后端 在两个接口之间传递操作数据 主要攻击面:两个接口使用的管理协议 2.2 协议特点 基于消息的管理协议 超过400种不同的消息类型 每种类型有独立的处理函数 手动审计难度大,易出错 3. 攻击定义与建模 3.1 消息路由机制 协议在请求-响应基础上工作,消息类型由类别和命令编号标识: 3.2 QL建模 - 命令处理程序表 3.3 QL建模 - 消息处理函数 4. 数据流分析技术 4.1 数据流库导入 4.2 基本数据流查询结构 5. 内存安全漏洞检测 5.1 数组越界访问检测 5.2 危险函数调用检测 6. 污点跟踪技术 6.1 污点跟踪库导入 6.2 污点传播规则 7. 路径遍历漏洞检测 7.1 文件操作函数定义 7.2 函数调用图分析 8. 实际成果 通过上述技术,在目标代码库中发现了: 15个消息处理函数中的18个越界读写漏洞 17个未验证参数的函数调用漏洞(多数为越界读取) 2个消息处理函数中的3个整数下溢漏洞 5个潜在路径遍历漏洞(确认2个) 总计发现33个易受攻击的消息处理函数。 9. 总结 Semmle QL提供了强大的静态分析能力: 可以自动化执行重复性代码审查任务 能够发现传统手动审计容易遗漏的漏洞 支持复杂的数据流和污点跟踪分析 可针对特定代码模式定制查询 在深度防御策略中发挥重要作用 通过结合数据流分析和污点跟踪技术,Semmle QL能够有效地识别各种类型的安全漏洞,包括内存安全问题和逻辑漏洞。