时时勤拂拭,勿使惹尘埃

TOC

Categories

weggli:C/C++源码审计工具


0x0 简述

weggli工具是由google projectzero开发的C 和 C++ 代码库语义搜索工具,仅支持源码文件搜索,可以理解为增强版grep。
https://github.com/googleprojectzero/weggli
weggli 根据用户提供的查询对抽象语法树执行模式匹配。它的查询语言类似于 C 和 C++ 代码,可以轻松将可疑代码模式转换为查询。

weggli 受到 SemgrepCoccinellejoernCodeQL 等工具的启发,但做出了一些不同的设计决策:

  • C++ 支持:weggli 对现代 C++ 构造提供支持,例如 lambda 表达式、基于范围的 for 循环和 constexprs。
  • 简单安装:weggli属于开箱即用,不需要构建软件编译,并且可以使用不完整的源或缺少的依赖项。
  • 交互式 : weggli 是为交互式使用和快速查询性能而设计的。大多数情况下,weggli 查询会比 grep 搜索快。目的是启用交互式工作流,可以在代码审查和查询创建 / 改进之间快速切换。
  • 模糊匹配:weggli 的默认模式匹配旨在为特定查询找到尽可能多的(有用的)匹配项。虽然这会增加误报的风险,但它简化了查询创建。例如,查询 $x = 10; 将匹配赋值表达式 ( foo = 10;) 和声明 ( int bar = 10;)。

0x1 使用weggli

0x11 安装weggli

weggli使用需要通过cargo来安装,Cargo是Rust的代码组织管理和项目构建工具:

$ cargo install weggli

macOS也可以使用brew来安装:

$ brew install weggli

或者下载源码进行编译安装:

# optional: install rust 
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
git clone https://github.com/googleprojectzero/weggli.git
cd weggli; cargo build --release
./target/release/weggli

0x12 使用方式

安装完成后,直接在被检测代码根目录执行weggli及相应规则即可,不附带规则直接执行会显示用法说明

附带规则执行,若执行完无内容,则表明未搜索到目标代码;出现内容即命中规则,会显现对应文件和行数,并高亮显示匹配中的部分:

0x13 语法简单说明

weggli的规则语法非常接近于C/C++代码,
比如规则:'{_ $buf[_]; memcpy($buf,_,_);}'
会查找所有直接写入堆栈缓冲区的对memcpy的调用。
除了普通的C和c++结构,weggli的查询语法支持以下特性:

  1. __通配符,将匹配任何AST节点
  2. $var:变量匹配标识符,可以匹配任意的类型、字段。
    1. 其中 --regex 选项可以强制进行正则匹配
  3. _(..):子表达式,用来匹配()中的任意代码
  4. not:否定子查询,只显示不匹配的结果
  5. strict:严格匹配模式,关闭模糊匹配,可减少误报
  6. <path>:检索路径,. 即检索当前目录

0x2 整理的规则

参考:
weggli examples
Playing with Weggli

未做NULL的指针:
weggli '{not: $fv==NULL; not: $fv!=NULL *$v;}' .

调用写入堆栈缓冲区的 memcpy:
weggli '{ _ $buf[_]; memcpy($buf,_,_);}' .

不检查返回值的 foo 调用:
weggli '{ strict: foo(_);}' .

潜在易受攻击的 snprintf() :
weggli '{ $ret = snprintf($b,_,_); $b[$ret] = _;}' .

可能未初始化的指针:
weggli '{ _* $p;NOT: $p = _;$func(&$p);}' .

潜在不安全的 WeakPtr 用法:
weggli --cpp '{$x = _.GetWeakPtr(); DCHECK($x); $x->_;}' .

基于函数参数执行写入堆栈缓冲区的函数。
weggli '_ $fn(_ $limit) { _ $buf[_]; for (_; $i<$limit; _) { $buf[$i]=_; }}' .

名称中带有字符串 decode 的函数
weggli -R func=decode '_ $func(_) {_;}' .

要查找kmalloc乘法溢出:
weggli --unique -R 'a!=^[A-Z_]+$' 'kmalloc($a * _);' .
weggli --unique -R 'a!=^[A-Z_]+$' 'malloc($a * _);' .

分配中发生的溢出,而不是在使用中发生的溢出:
weggli --unique 'kmalloc($a + _); memcpy(_, _, $a);' .
weggli --unique 'malloc($a + _); memcpy(_, _, $a);' .

C 中的一个典型错误是使用sizeof(ptr)而不是sizeof(type of the pointed thing):
weggli -R 'func=^mem' --unique '$a * _; $func(_ , _, sizeof($a));' .

复制函数 likememcpy和它的朋友应该总是复制到目标的大小,而不是源的大小:
weggli --unique -R 'func=co?py' -R 'size=sizeof|strlen' '$func($dest, $src, $size($src));' .
weggli --unique -R 'func=co?py' '$func($dest, $src, $size($src));' .

琐碎的双重释放:
weggli --unique '{ kfree($a); NOT: goto _; NOT: break; NOT: continue; NOT: return; NOT: $a = _; kfree($a);}' .

变长数组有风险,容易出错;如果长度大于堆栈大小,则会发生堆栈溢出,并且错误检查的可能性:
weggli --unique '_ $func(_ $len) {NOT: _ = $buf[$len];NOT: $buf[$len] = _;_ $buf[$len];}' .

释放堆栈分配变量:
weggli --unique '$a = alloca(_); free($a);' .

未指定的参数顺序:
weggli --unique '$f($a++, $b++)' .
weggli --unique '$f(++$a, ++$b)' .
weggli --unique '$f($a--, $b--)' .
weggli --unique '$f(--$a, --$b)' .

被零除:
weggli --unique '$a = 0; _ / $a' .

相同条件:
weggli --unique 'if ($a); else if ($a);' .

并非所有数据都已初始化或存在内核指针:
weggli --unique '{ NOT: $a = memdup_user(_); NOT: memset($a); NOT: memset($a->$b); copy_to_user(_, $a, sizeof(*$a));}' .

KASLR 绕过:
weggli -R 'a=addr' 'dev_info($a);' .

分配字符串时不考虑终端snprintf 0:
weggli --unique '$a = snprintf(0, 0, _); malloc($a);' .

错误的snprintf用法:
weggli --unique '$pos = snprintf(_ + $pos);' .

类型混淆释放的方法:
weggli --cpp --unique '$a = new _; $b = (_) $a; delete $b;' .

0x3 脚本批量扫描

由于weggli只能一次执行一条检测规则,所以写了个python脚本可以批量扫描,主要代码如下:

def weggli_code_check():
    print ("-----------------------------------------------------------------")
    print ("-----------------------------------------------------------------",file=f)
    print ("c/c++源码审计...")
    print ("c/c++源码审计...",file=f)
    cmd = "weggli '{$ret = snprintf($b,_,_);$b[$ret] = _;}' ."
    print ("潜在易受攻击的snprintf(),cmd = ",cmd)
    print ("潜在易受攻击的snprintf(),cmd = ",cmd,file=f)
    out_temp = tempfile.TemporaryFile(mode='w+')
    fileno = out_temp.fileno()
    try:
        p = subprocess.Popen(cmd,shell=True,stdout=fileno,stderr=fileno,preexec_fn=os.setsid)
        p.communicate(timeout=10)
        p.wait()
    except subprocess.TimeoutExpired:
        p.kill()
        print('超时自动结束任务...')
    out_temp.seek(0)
    rt = out_temp.read()
    print (rt)
    print (rt,file=f)
    out_temp.close()

    ...

效果如下,执行完一条规则扫描后,立即执行下一条规则扫描,并且对每个规则扫描进行定时,超时即结束该规则扫描任务,但该方式会导致高亮显示失效:

0x4 后续思考

怎样丰富规则,减少误报,找出更多实际的漏洞