codeql实践之查找house_of_kiwi利用点

kkkbbb 2022-09-06 10:11:00

概述

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测试

image-20220902213713823

查找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是否是合法的跳转表。

image-20220902215001648

image-20220902214728228

image-20220902215412961

首先尝试编写sql查找上述这类宏调用,经过上面展开宏调用可以发现展开后的宏就相当于((_IO_jump_t)(stdout+vtable_offset))->func(parameter),可以定义PointerFieldAccess查找所有通过指针对成员的访问,再判断访问目标是否为_IO_jump_t结构体的成员,即可找出glibc中所有_io_xxx类型的宏调用。

image-20220902221147925

由此可以编写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中的函数。

image-20220902223148591

改进之前的语句,增加对封装函数的调用:

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()查询目标得到的结果是别名,这里只判断了宏调用直接所属函数声明的函数名,因此无法查询到通过别名访问的函数调用。

image-20220903011218269

image-20220903011124913

为了解决上述问题,针对数据可能经过几个函数最总到达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

image-20220903012538890

这样就可以查找出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/

https://www.anquanke.com/post/id/235598#h3-3

评论

kkkbbb

这个人很懒,没有留下任何介绍

随机分类

二进制安全 文章:77 篇
逻辑漏洞 文章:15 篇
浏览器安全 文章:36 篇
安全管理 文章:7 篇
Ruby安全 文章:2 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

D

Da22le

因为JdbcRowSetImpl是通过JNDI来实现rce的,191以后对JND

xiaokonglong

师傅我有个问题。1.2.24哪里有个问题,为什么什么是161-191?

C

CDxiaodong

0rz

U

Uesaka

说加入AD变简单的可能没遇到死亡题,2333~

Mas0n

附件: https://pan.baidu.com/s/1rbW_SQiRpv1

目录