概述
codeql是一款强大的静态扫描工具,通过codeql强大的自定义规则我们可以编写sql语句来搜索源码中我们可能感兴趣的代码。ctf中高版本libc的利用方法中很多都用利用了IO函数,即利用stdin/stdout/stderr->vtable 修改虚表指针指向对我们有用的伪造的vtable又或者其他存在可利用函数的_IO_xxx_jumps虚表,如:house_of_kiwi,因此我便诞生了使用codeql查找libc中所有跳转到IO的函数调用的想法,更方便的分析libc中的利用点,也看看能不能找出house_of_kiwi中使用的malloc_assert->fflush利用点。
环境搭建
codeql的工作方式是通过codeql-cli编译一遍源码,从中提取关键信息生产数据再编写sql来查询数据库。codeql主要由两部分组成,一个是codeql-cli可执行程序部分,另一个就相当于是codeql的标准库,编写sql需要用到其中的ql,一般的我们直接使用vscode-codeql-starter中包含的ql标准库就可以了。
本案例涉及的环境为:
macos: vscode + codeql 用于编写sql规则查询数据库
linux: codeql 编译libc
首先下载codeql-cli:
https://github.com/github/codeql-cli-binaries/releases
根据平台选择不同的二进制文件,下载解压添加到path即可。
然后下载vscode-codeql-stater解压,直接通过vscode打开其中code-workspace后缀文件打开工作区,安装官方codeql扩展并设置好codeql可执行文件的位置。
https://github.com/github/vscode-codeql-starter
在linux平台上编译glibc创建codeql数据库,glibc使用的是gnu官方2.34版本的,git克隆官方仓库切换到2.34版本,这里没有使用ubuntu版本的libc主要是因为下载的ubuntu版libc源码编译总是会出现各种各样的报错,没法一次创建好数据库,而官方版我在ubuntu20.04环境下直接可以顺利编译通过。
git clone https://sourceware.org/git/glibc.git
cd glibc
git checkout release/2.34/master
mkdir build
../configure
codeql database create -l=cpp -c make libc2.34
创建好数据库之后将数据库传输到本地,通过codeql插件选择数据库的目录,就可以开始编写sql来搜索我们感兴趣的代码了。
编写我的第一条sql helloworld测试
查找IO宏调用
众所周知libc中的io函数实际上都是调用stdout结构中的虚表跳转相应的函数,以fputs为例,fputs函数实际上通过宏_IO_sputn调用_IO_2_1_stdout.vtable表中的函数指针调用输出函数,而_IO_sputn是_IO_XSPUTN的宏,xsputn通过jump2宏访问vtable中的__xsputn成员,jump2通过_IO__jumps_func宏将stdout加上vtable的偏移获得vtable的指针,并且获得指针后会通过IO_validate_vtable验证vtable是否是合法的跳转表。
首先尝试编写sql查找上述这类宏调用,经过上面展开宏调用可以发现展开后的宏就相当于((_IO_jump_t)(stdout+vtable_offset))->func(parameter),可以定义PointerFieldAccess查找所有通过指针对成员的访问,再判断访问目标是否为_IO_jump_t结构体的成员,即可找出glibc中所有_io_xxx类型的宏调用。
由此可以编写sql语句:
import cpp
from PointerFieldAccess pa,Struct st
where pa.getTarget() = st.getAMember()
and st.getName() = "_IO_jump_t"
select pa
查看可以看到97个搜索结果,都是对vtable中函数指针调用的宏,接下来我们缩小搜索范围试图查找malloc过程中存在的可利用的io调用,再加上位置的删选条件,只查找malloc.c中的代码:
import cpp
from PointerFieldAccess pa,Struct st
where pa.getTarget() = st.getAMember()
and st.getName() = "_IO_jump_t"
and pa.getLocation().getFile().getShortName() = "malloc"
select pa
查看结果发现没有任何数据,查看之前的结果发现大部分的宏调用都被封装在函数中,而且malloc中没有任何地方是直接通过宏直接访问vtable中的函数。
改进之前的语句,增加对封装函数的调用:
from PointerFieldAccess pa,Struct st,FunctionCall fc
where pa.getTarget() = st.getAMember()
and st.getName() = "_IO_jump_t"
and fc.getTarget() = pa.getEnclosingDeclaration()
and fc.getLocation().getFile().getShortName() = "malloc"
select fc
从结果看仅有两个对fputs的调用,很明显malloc.c不可能只有两个io调用,仔细观察会发现存在两个问题:
一个是并一定会直接调用有宏定义的函数,也有可能是经过了几个函数最终调用了io函数,因此会漏掉很多最终调用了io的函数调用。
二是libc中很多函数定义了alias属性,相当于拥有一个别名,而通过这个别名访问的函数在codeql中通过FunctionCall.gettarget()查询目标得到的结果是别名,这里只判断了宏调用直接所属函数声明的函数名,因此无法查询到通过别名访问的函数调用。
为了解决上述问题,针对数据可能经过几个函数最总到达vtable中的io函数我们不好通过函数名来判断,可以通过codeql提供的全局污点追踪来判断数据是否最终到达了io函数,而alias属性只需要加一条获取函数attribute并判断参数名和调用io宏的函数名进行比较。
import cpp
import semmle.code.cpp.dataflow.TaintTracking
class MyTaintTrack extends TaintTracking::Configuration {
MyTaintTrack() { this = "MyTaintTrack" }
override predicate isSource(DataFlow::Node node) {
exists( FunctionCall fc| node.asExpr() = fc.getAnArgument() and
node.getLocation().getFile().getShortName() = "malloc" )
}
/**
* libc中的io调用多为通过宏访问vtable中的函数指针,通过查找指针访问基础类型为“_IO_jump_t"来检测该类宏
* 而libc的宏外面又都封装了函数,一般直接访问封装函数或者封装函数的alias别名,alias我们通过检测函数的属性来区分
* 将sink设置为IO封装函数的参数,来检测对IO的调用
*/
override predicate isSink(DataFlow::Node node) {
exists( PointerFieldAccess pa,Struct st,FunctionCall fc,Function f |
pa.getTarget() = st.getAMember() and
st.getName() = "_IO_jump_t" and
fc.getTarget() = f and
(f = pa.getEnclosingDeclaration() or
(f.getAnAttribute().getName() = "alias" and f.getAnAttribute().getAnArgument().toString() = pa.getEnclosingDeclaration().getName())) and
node.asExpr() = fc.getAnArgument())
}
}
from DataFlow::Node source,DataFlow::Node sink,MyTaintTrack config,FunctionCall fc
where config.hasFlow(source, sink)
and source.asExpr() = fc.getAnArgument()
select fc
这样就可以查找出libc所有参数流向vtable中io函数的函数调用,并且参数是可控的,从结果看malloc.c中所有的io调用都罗列出来了,并且fprintf函数下面还有__vfprintf_internal函数,经过了多个函数。
结果也包含了ctf中经常用到house_of_kiwi利用方法中的__malloc_assert->fflush,这也是我做这件事最开始的想法,通过codeql找到house_of_kiwi中的利用点。
参考教程
https://codeql.github.com/docs/codeql-language-guides/analyzing-data-flow-in-cpp/