weggli debug
关于Weggli
AST Pattern Search
核心是使用和 tree-sitter
库,然后搞了 query-tree
来在 AST
上进行搜索,这只能说是匹配特定的代码片段,还达不到程序分析的那个级别,所以理论上只能过程内分析,而且没有上下文啥的 :D 直白点说的话,像是AST
的正则表达式,不过某种意义上来说对于使用白盒方案快速召回一些漏洞也是一种借鉴吧。
当然我也用这个工具做了一些扩展,结合其他工具解决了一些问题,目前看来这个东西还是具有一定的可玩性的 :D
Weggli如何工作
看代码,调试分析
idea配置
安装Rust
插件,调试的话,会默认再去安装Native Debugging Support
,有了这俩东西就可以调试了
配置传递给weggli的参数的话跟在 --
后面即可 :
1 | run --package weggli --bin weggli -- "{$func($b);system($b);}" -R "func=printf$" /path/to/src |
工作流程
只描述核心流程
query-tree 构建
1 | let work: Vec<WorkItem> = args |
构造 WorkItem{qt, identifiers}
- qt : query-tree, tree-sitter的Tree对象
- identifiers : 标识符,query中”终结符”
调用链:
1 | main |
修正pattern : weggli处理了“不合法的”格式,如:
memcpy(a,b,size)
->memcpy(a,b,size);
memcpy(a,b,size);
->{memcpy(a,b,size);}
1 validate_query(&tree, p, force_query)? // 返回 TreeCursor对象,用于遍历AST
语法合法性检查,如果 force_query
为True
,意味着忽略这些语法错误
如 :
1 | "{$func($b);_($b);}" |
对应 :
1 | (translation_unit |
同时还不允许 :
返回的是 : c.goto_first_child();
,即 花括号中间的内容
1
2
3
4
5
6
7
8 Ok(build_query_tree(
p,
&mut c,
is_cpp,
Some(regex_constraints.clone()),
))
_build_query_tree(source, cursor, 0, is_cpp, false, false, regex_constraints)
QueryTree数据结构:
1 | pub struct QueryTree { |
转换的tree_sitter query
(核心逻辑都在 builder.rs
的 QueryBuilder.build
)
1
2 Translate the tree below `c` into a tree-sitter query string.
"{$func($b);_($b);}"
1 | tree_sitter query 1: ((call_expression function:[(identifier) (field_expression) (field_identifier)] @0 arguments:(argument_list [(identifier) (field_expression) (field_identifier)] @1)) )([(identifier) (field_expression) (field_identifier)] @2 ) |
深度优先的方式递归生成query tree string,按照AST解析出来不同的节点,后面跟着的 @x
用来区分不同的 identifier
,方便后面做匹配。
如简单的 {printf(var, bar);}
生成的 query-tree
是 :
1 | ((call_expression |
结合tree-sitter的playground来看就很容易看明白了:
query执行(pattern 匹配)
在执行query之前会做
对于需要正则匹配的
identifer
做合法性确认1
2
3
4
5
6for v in regex_constraints.variables() {
if !variables.contains(v) {
eprintln!("'{}' is not a valid query variable", v.red());
std::process::exit(1)
}
}确定待解析源码文件(
Verify that the --include and --exclude regexes are valid.
) 主要是根据后缀来
随后就是通过管道来处理,分为:
- 文件读取 & AST解析
let (ast_tx, ast_rx) = mpsc::channel();
- QueryTree 匹配 & 结果输出
let (results_tx, results_rx) = mpsc::channel();
1 | // Spawn worker to iterate through files, parse potential matches and forward ASTs |
**这玩意描述起来就像个流水线 :D **
详细描述的话就是:在有了 query-tree
就需要把目标文件,解析(parse_files_worker
)成 (Tree, source_code)
,结果发送到 ast_tx
,然后从ast_rx
获取这些信息来执行查询操作(execute_queries_worker
);结果放在 result_tx
,后面处理结果的函数会从result_rx
获取,然后输出。
1 | parse_files_worker(files, ast_tx, w, cpp); |
TODO: 需要细读逻辑
这里简单的加一句print之类的可以来看看每次query的时候目标tree是啥样的(生成过程和query tree类似)
1 | // Run query |
所以这就转换成了一个字符串匹配的问题,结合之前的 -R
,能支持正则匹配,所以说weggli是在AST上搞正则匹配一点都没说错 :D
multi-query(-p
参数)
漏洞模型测试
Question - query construction 这个issue里提到了这个场景,先还原一下场景 :
vuln.c
是个类似的情况,尝试query
1 |
|
- 匹配函数定义(
vuln
) - 匹配func call
vuln(argv[1])
假如没有对vuln的调用,那就不回返回结果
multi-query 实现
这块逻辑主要在 multi_query_worker
,即存在多个workitem的时候会触发,就是在匹配的时候会结合这些query,即将第一个query匹配到的结果先收集起来
1 | let mut query_results = Vec::with_capacity(num_queries); |
然后根据后面的query去做过滤,找到满足的pattern就打印出来
1 | let filter = |x: &mut Vec<ResultsCtx>, y: &mut Vec<ResultsCtx>| { |
方便调试做的修改
1. 打印 query-tree 和 源码 AST方便定位问题
query-tree的话增加一个 -v
参数就行,会把query tree打印出来
少量代码测试这样是可以的,也可以使用log模块把信息打出来,不过数据太多了。
1 | diff --git a/src/main.rs b/src/main.rs |
直观多了 :