CWE_Checker笔记

https://github.com/fkie-cad/cwe_checker 的一些记录,目标是fix一个bug

Week1

  • 一个typo的PR(#387
  • 搭remote debug(source code)
  • 学Rust,到Rust Course的2.6:模式匹配
  • 看博客、讨论:
    https://cascades-sjtu.github.io/posts/cwe-chekcer/
    What is the logic of discovering cwe?
    Questions about internal mechanism of cwe_checker
  • 了解项目结构
  • 看文档
    • CWE119 Buffer Overflow:
      误报:1.指针推断(Pointer Inference)过程不准确;2.不确定边界大小时,会以找到的最小边界为准
      漏报:1.指针推断无法确定任何边界;2.指针推断只能检测到溢出整个栈帧的情况,例如off-by-one就检测不出;3.函数调用时被调用函数缓冲区大小不确定;4.Checker本身无法识别全局内存的溢出

20230210164343

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
cwe_checker
├─ bare_metal # 裸机程序分析的config样例
├─ cwe_checker_to_ida # 把分析结果生成ida anotation script
├─ ghidra_plugin # 把分析结果(--json)生成ghidra可用的文件
├─ src
│ ├─ caller
│ │ └─ src # cwe_checker主函数
| | # 包括接收cmd参数、读取文件、调用lib.rs里的各个分析模块
│ ├─ cwe_checker_lib
│ │ └─ src
│ │ ├─ abstract_domain
│ │ ├─ analysis
│ | ├─ checkers # 所有CWE的check代码,
| | | 大部分是调用analysis目录下的方法
│ | ├─ intermediate_representation
│ | ├─ lib.rs
│ | ├─ checkers.rs
│ | ├─ pcode
│ | ├─ pipeline
│ | └─ utils
│ ├─ ghidra
│ │ └─ src # 在lib里被调用,提取ghidra分析结果,
| | 包括变量、跳转关系等信息,以pcode表示
│ └─ installer
│ └─ src # 用于本地安装cwe_checker
└─ test # cwe对应漏洞源码样本以及test build脚本

Week2

上周最后那张图里的问题:
CWE-119、CWE-125、CWE-787: 都属于缓冲区溢出,119-缓冲区边界内操作的限制不恰当,125-越界读,787-越界写

main.rs、构建CFG、检查cwe_676及cwe-467的过程

  • check过程:
  1. 从ghidra中提取反编译的信息(在/pipeline/里实现)
  2. 生成控制流图
  3. 部分CWE的检测需要进行计算函数签名、指针推理、字符串抽象
  4. 依次执行每个CWE检测模块
  • cwe_676(使用不安全函数)检测过程:
  1. 把调用到的库函数和配置文件里标注的危险函数进行对比,得到dangerous symbol
  2. 找到所有调用危险函数的用户函数,以及它们发起调用的地址
  • 生成控制流图:
    4步(TODO),看了第一步

  • cwe-476(空指针解引用):
    污点分析(TODO)

Found some issues:
What’s the meaning of subs?
Why a certain block is both start and end?
What’s the meaning of block.term.jmps?(the meaning of ‘tid’)
What is the logic to check CWE476(NULL Pointer Dereference)?
(Mine ask) Some comments seems confusing

Week3

Overall:CFG
字符串抽象(默认不用)<-指针推理<-函数签名
三个过程的含义,每一个过程的输出
CWE676:

只用了project.term.subs,即子函数,
并非基于函数进行搜索

cwe119和函数签名计算都要先进行不动点计算

提取ghidra信息时一开始sub向量中的每一个.[].term.block不是对应基本块,粒度更细

CWE-Checker-Week-3&4

Week5

南大《软件分析》的数据流分析和过程间分析(部分)

One issue: https://github.com/fkie-cad/cwe_checker/issues/395
CWE-125&787分别准确地指越界读和越界写;剩下不确定是读还是写越界的统统报CWE119
CWE-119的checker的下一步进展:跟踪值之间的关系,以及哪些值和缓冲区大小有关
在free()误报CWE119的场景基本上是由于误认为释放的堆大小为0
CWE119目前只能判断出编译时规定的内存分配大小,对运行时才知道分配大小的情况不会告警

20230310163355
分析的主要部分其实在Computation部分,包括过程内和过程间的分析

Week6

检测b00k时,检测到’free(0)'的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
compute_with_max_steps()执行worklist算法,循环,结点运行次数超过max_step(100)则放到not_stable_list里作为新的worklist
->fixpoint.rs -- update_node(),获得结点连接的所有边并迭代update_edge(),
->fixpoint.rs -- update_edge(),获得edge连接的两个node
->forward_interprocedural_fixpoint.rs -- update_edge(),迭代,更新edge终点的值(?),
从edge的权值判断edge是属于什么类型的(jmp、call、....如下图)
/*
block分支->trait_impls.rs -- update_def(),判断语句是load/store/变量声明,以及是否溢出
(store和load分支)->state.rs() -- check_address_access,CWE125和787都会进行
-> ....
*/
ExternCallStub分支->trait_impls.rs -- update_call_stub(),根据call.term的结构进行跳转。
直接跳转(Call)分支->stubs.rs -- handle_call(),匹配被调用的函数名
不属于列举的那些函数-> stub.rs -- handle_generic_call(),取每一个传参的寄存器进行遍历
->mod.rs -- check_param_at_call(),在发起调用的地方是否产生了越界
->vsa_result_impl.rs -- eval_parameter_arg_at_call(),取state和context,调用↓
->access_handling.rs -- eval_parameter_arg(),根据parameter参数的结构(寄存器或栈)进行匹配
->access_handling.rs -- eval,获得eval_recursive()的结果(),执行replace_if_global_pointer()
->access_handling.rs -- eval_recursive(),对参数进行匹配,递归计算当前状态下表达式的值。
已知大小的寄存器或变量->access_handling.rs -- get_register() /*一些处理*/
->access_handling -- replace_if_global_pointer(),free()不经过,没有细看
->state.rs -- check_address_access(),
遍历address.relative_values,如果未计算该堆栈的边界->state.rs -- compute_bounds_of_id()
->bounds_computation.rs -- compute_bounds_of_id(),通过调用的地址判断在堆还是栈上
堆->mod.rs -- compute_size_of_heap_object(),计算创建堆的大小,如果有多个可能的值则返回最小值
->param_replacement.rs -- recursively_substitute_param_values_context_sensitive(),
在检测 free()的时候直接返回了object_size,
相当于pointer inference 分析出的 malloc_tid_to_object_size_map
->param_replacement.rs -- recursively_substitute_param_values()
中间有个循环,但是没有进去
->data.rs -- replace_all_ids(),更新结点,准备下一轮循环,这边也跳过了
->mod.rs -- try_to_offset_interval(),把从address.relative_values(堆/栈起始和结束地址的
偏移位置等)得到的offset.interval转成64位数据,赋值给lower_offset,upper_offset
->bitvector.rs -- into_resize_signed(),把两个bound扩展/截断,位数为object_size.size
->bounds_computation.rs -- compute_bounds_of_param_id(),返回NULL
函数在此处给lower_bound和upper_bound返回了NULL
->bounds_computation.rs -- object_size.try_to_offset(),把object_size变成0
运行了一些无关的循环(if let Some()匹配不成功),返回lower_bound和upper_bound均为NULL
(函数起始有一段注释"where the bounds may be `None` if they could not be determined")
通过into_resize_signed()比较lower_bound、upper_bound和位数(8字节)把两个bound的
len重新设为64,指针还是NULL,并插入self.object_lower_bounds
判断(upper_bound < upper_offset + (u64::from(value_size) as i64)为真,记录溢出,输出越界访问的警告
merge_node_value(),准备检查后续结点
执行完所有checker后统一输出warning

在最后检查是否溢出时,value_size是固定的; ~upper_offset来自pointer inference~ ;upper_bound在compute_size_of_heap_object()返回了NULL,因此可能是该函数执行的过程中有问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pub enum Edge<'a> {
//Block边,从start指向end
//用户定义的jmp和call
//外部函数跳转,用ExternCallStub直接略过
Block,
Jump(&'a Term<Jmp>, Option<&'a Term<Jmp>>),
Call(&'a Term<Jmp>),
ExternCallStub(&'a Term<Jmp>),
//在call某个函数的过程中,对应为:
//虚拟的CallSource块指向虚拟的CallReturn块;
//从子函数的Blkend指向虚拟的CallReturn块;
//从发出跳转的Blkend指向虚拟的CallSource块;
//从虚拟的CallReturn块指向后续的BlkStart块;
CrCallStub,
CrReturnStub,
CallCombine(&'a Term<Jmp>),
ReturnCombine(&'a Term<Jmp>),
}

internal_function_call
extern_calls
node_edge

Hitcon Traning Lab13的heapcreator(off-by-one)啥都没报
写了一个简单的栈溢出也没有任何告警

1
2
3
4
5
6
7
8
9
#include<stdio.h>
int main(){
char a[10];
for (int i=0;i<=14;i++){
scanf("%c\n",&a[i]);
puts(a);
}
puts(a);
}

binabsinspector对几个文件的分析结果,第一个和最后一个对了:

1
2
3
4
5
b00k:
BinAbsInspector.java> [WARN] CWE190: Potential Integer Overflow due to tainted input from source of __isoc99_scanf(FUN_00100f55[0, 0, 10127f]) at inside of "FUN_00100f55()" @ 001010a6
BinAbsInspector.java> [WARN] CWE190: Potential Integer Overflow due to tainted input from source of __isoc99_scanf(FUN_00100a89[0, 0, 101240]) at inside of "main()" @ 00101259
BinAbsInspector.java> [WARN] CWE190: Potential Integer Overflow due to tainted input from source of __isoc99_scanf(FUN_00100a89[0, 0, 101240]) at inside of "main()" @ 00101268
BinAbsInspector.java> [WARN] CWE190: Potential Integer Overflow due to tainted input from source of __isoc99_scanf(FUN_00100f55[0, 0, 10127f]) at inside of "FUN_00100f55()" @ 00100fec