weggli:C/C++源码审计工具
0x0 简述
weggli工具是由google projectzero开发的C 和 C++ 代码库语义搜索工具,仅支持源码文件搜索,可以理解为增强版grep。
https://github.com/googleprojectzero/weggli
weggli 根据用户提供的查询对抽象语法树执行模式匹配。它的查询语言类似于 C 和 C++ 代码,可以轻松将可疑代码模式转换为查询。
weggli 受到 Semgrep、Coccinelle、joern 和 CodeQL 等工具的启发,但做出了一些不同的设计决策:
- 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的查询语法支持以下特性:
_
:_
通配符,将匹配任何AST节点$var
:变量匹配标识符,可以匹配任意的类型、字段。- 其中
--regex
选项可以强制进行正则匹配
- 其中
_(..)
:子表达式,用来匹配()中的任意代码not
:否定子查询,只显示不匹配的结果strict
:严格匹配模式,关闭模糊匹配,可减少误报<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 后续思考
怎样丰富规则,减少误报,找出更多实际的漏洞
大佬666
回复删除