CTF PWN题总结
pwn题总结
拟态ctf
这周打拟态的比赛,做PWN题的时候发现一件事,题目感觉都不太难,但是我却做的很慢,一题差不多都需要好几个小时才能做出来。
分析了一下原因,首先是我本人性格有点慢悠悠的,做题速度本来就慢。其次,就是工作了以后,打CTF的次数大大减少了,一个月都不一定会做一道PWN题,以前学的一些知识点自然而然就忘了,生疏了。然后又要重新开始学习,这就大大增加了我做题(对我来说简单的题,以前会的)的时间。
所以我现在试着想一些办法,来减少一下重新学习这部分的时间。所以准备对这次的拟态ctf的几道pwn题进行一次总结。
本次比赛,一共有7道题,其中一道题无解,剩下的6道题都很简单,两道栈溢出的题,4道堆溢出的题。
做题的第一部都是先找洞,然后才是根据存在的漏洞研究利用方法。找洞的过程就没办法总结了,全看个人灵性。
主要记录下每道题的利用思路。
easy-stack
最简单的栈溢出,还提供了格式化字符串。这种题太简单了,没啥好说的,感觉我几年不做pwn,再看到这种题,也还是能很快做出来。
格式化字符串泄漏地址,栈溢出ROP执行one_gadget/system(/bin/sh)
goodnote
本题情况简述:堆题,64位,libc 2.27,可控malloc长度,可以free,可以edit堆,可以show堆信息
限制:保护全开,只能同时分配16个堆
该题存在两个漏洞:
- malloc(input * 4),可以让input * 4 < input
- 字符串输出堆的内容,而不是通过长度字段输出
第一个漏洞可以导致在编辑堆的时候,导致堆溢出,第二个洞可以泄漏地址。
在保护全开的堆题中,大部分的解题思路都需要先泄漏地址(house of romen可以不泄漏地址)。
泄漏libc地址
分配一个unsortedbin,然后释放,这样该堆的fd/bk位置就是libc地址。
通过第一漏洞,在unsortedbin上面一个堆进行堆溢出,把unsortedbin的头部都填充非0字符。这样show上面这个堆的时候,就可以泄漏libc地址。
利用
堆的题目一般的利用思路都是写malloc_hook
或者free_hook
。
本题的libc为2.27,所以可以利用tcache进行任意写。
- 连续申请3个长度为0x20的堆A, B, C,首先释放A,然后释放C。两个堆都会被放入tcache中,然后C指向A。
- B堆利用第一个漏洞,可以编辑C堆的fd,让其指向free_hook,这样再申请的第二个0x20的堆,就是free_hook地址,可以任意编辑free_hook的值。
- free_hook可以改成system,也可以改成one_gadget,利用到这里就很简单了。
rbsystem
本题情况简述:堆题,64位,libc 2.27,可控malloc长度,可以edit堆,可以show堆信息
限制:保护全开,需要fopen(/dev/urandom)才能edit堆,而且edit堆使用的是fread,所以edit堆的内容不可控,
该题存在两个漏洞:
- edit堆有一个offset参数,可以是负数,也就是说可以edit该堆之前的所有堆
- 字符串输出堆的内容,而不是通过长度字段输出
让edit可控
edit如果不可控的话,很难进行后续的利用了,所以之前,首先需要找一个方法,让edit可控。
因为edit的数据都是通过fread写入堆的,所以我们可以想办法把urandom的文件描述符改成0,这样就可以变成从标准输入读数据了。
再通过fopen打开一个文件后,FILE_IO结构体会被储存在堆上,所以我们可以首先fopen文件,生成FILE_IO的堆,然后我们在自己add一个堆,利用第一个漏洞,算好到FILE_IO -> fileno的偏移。fread一个字节到该地址,因为fread的数据是随机的,所以有1/256的概率成功写入0。直接爆破就完事了,不过fread默认会直接读0x1000字节到缓冲区,我们还需要把缓冲区给情况,要不然无法check是否成功把fileno改成了0。
泄漏libc地址
泄漏libc地址我使用的方法是,修改FILE_IO的read_ptr和read_end指针到FILE_IO->chain的地址,然后通过fread写入数据到堆,这时候写入的数据就是chain的值,就是libc的地址,然后通过show功能,就能泄漏出libc地址了。
利用
因为libc的版本是2.27,所以这里又利用到了tcache的特性,因为tcache的结构体也储存在堆上,我们可以通过第一个漏洞来修改tcache的链表,达到任意写的目的。
pwn_emmm
本题情况简述:堆题,64位,libc 2.23,可控malloc长度,可以free,可以edit堆,可以show堆信息
限制:保护全开,每次add,都会自动分配一个长度为0x20的堆,只能输出可以edit堆的信息,该堆内容不可控,只有该堆可以被free,只能同时分配21个堆
该题存在两个漏洞:
- free无限制,可以导致2free
- 字符串输出堆的内容,而不是通过长度字段输出
泄漏libc地址
本题的libc泄漏比较复杂,堆构造也比较麻烦,感觉一段时间后再做这种题,我还是需要花大量时间。
总结下大致思路吧。
通过2free,只修改fd的最后一字节,比如有一个能被释放的堆A,长度为0x20字节。那么通过2free,修改fd的最后一字节,使fd的地址指向A-0x10,然后通过对fastbin的控制,让这个地址的堆分配给可以edit内容的堆,并且把A也分配给可以edit内容的堆。这样我们就可以修改堆A的size字段,修改到unsortedbin的长度,再次释放A,这样fd和bk都是libc地址。
然后再输出A,这样就能得到libc地址。
这里需要注意,size不是随意修改的,2.23的libc会通过该size查找下一个堆块,确定下一个堆块size的有效性。
利用
泄漏libc虽然复杂,但是利用的难度会更低,2.23版本的libc中,fastbin的分配会checkfastbin的size字段的,所以没法想2.27的tcache做到任意地址写。
一般在free_hook之前的内存中是找不到有效size的。但是在malloc_hook中却找得到有效size:0x7f。所以这题的利用思路就是通过2free,写malloc_hook。
但是在最后利用的时候,却发现one_gadget没一个能用的。随后通过google搜到一个姿势,大致逻辑如下:
malloc_hook -> libc_relloc + 0x14 -> relloc_hook -> one_gadget
最后使用这个思路成功执行shell了。
不过还有一种思路,可以通过unsorted bin在free_hook上方踩出size,但是因为没有具体的例题,还不清楚该利用的具体逻辑,下次一定研究🐦。
newpad
本题情况简述:堆题,64位,libc 2.23,可控malloc长度,可以free,可以edit堆,可以show堆信息
限制:保护全开,malloc的长度限制在0x100以内,edit肯定会附加’\0’结尾,只能同时分配4个堆
该题存在两个漏洞:
- off-by-one
- 就算堆被释放了,也能输出
泄漏libc地址
这个就简单了,申请一个unsorted bin,然后再释放,然后再输出,就能泄漏出libc地址了
利用
off-by-one的利用思路好像是挺多的,我使用的思路如下:
- 申请堆A,B,C,通过off-by-one漏洞,修改B的size,大小为B+C的大小。
- 然后释放B,再申请一个B+C大小的堆块,这个堆块的地址就是B了,这个时候通过edit B就能修改C。
- A和C大小一样,先释放A,再释放C,这样C的fd指向了A,然后我们再修改fd为malloc_hook,这样我们再次申请的第二个堆就为malloc_hook。
之后的利用思路上面有类似的,就不多说了。
easy-stack2
这次比赛除了常规题还新增了提高题,其实就是一道正常题,挂上了他们的拟态系统。我个人感觉他们就是来宣传他们产品的,就是不让选手做的。但是不知道他们处于什么原因,这道easy-stack改简单了,变得能做了。
先来说说这种题的大致逻辑,简单的来说,就是有多台挂了该题的机器,我们就假设3台吧。我们访问的ip地址,可以算总控机器,把我们发送的请求同时发给这三台设备,然后得到返回,如果返回不一样(一开始我们还猜测是判断相似度,但是问了主办方,确认了是需要完全一样),则表示收到攻击,退出。
所以在开了aslr的机器上,不能进行leak地址,因为三台机器的libc地址只有极端情况下才会遇到一样的情况。所以只要leak libc地址,那么三台机器的返回肯定不一样,程序就退出了。
所以我们只能在不leak的情况下进行攻击,或者能让三台设备leak返回的结果一样。
最开始easy-stack2题目是开了canary的,所以根本没法泄漏出canary,也导致这题长时间没人做出来(估计是无解题)。
随后该题改简单了,就一个read的栈溢出,也没有canary,不需要leak的利用方法可以使用dlresolve。
虽然前段时间学过,但是现在我已经玩了dlresolve的各个结构体了,太难了真的。不过后来发现我学弟veritas[1]写了一个快速利用dlresovle的函数:
1 | def ret2dl_resolve_linkmap_x64(ELF_obj,known_offset_addr,two_offset,linkmap_addr): |
有了这个函数,就大大缩减了我的做题时间,不需要我再重新学dlresolve了。
来解释一下这个函数:
- ELF_obj也就是通过pwntools的ELF打开的对象
- known_offset_addr参数是需要我们找一个地址,满足下面的条件:
assert *(known_offset_addr-8) & 0x0000ff0000000000 != 0
,一般就是找got表,比如read_got - two_offset参数,比较我最终想执行的是system函数,那么该参数就是libc中system-read的值
- linkmap_addr也就是我们会把linkmap写哪,比如写到地址A上,一般就是bss段找地方写
- 写入地址A的数据就是返回值[0]
- 调用的rop就是返回值[1]
不过这种函数只适用于能修改link_map地址的利用情况。
newpad2
这题想了好久,还找到可以绕过0x100长度的方法,但是没屁用,最后问了主办方,确认了这题就是无解的,白通宵了,哎。。。
期间想了house of romen,但是发现edit每次会有\0结尾,特别致命,如果没有,感觉还可以做。
祥云杯2020
Beauty_Of_ChangChun
本题情况
- 2.31的libc
- 只有一次malloc机会,大小不可控
- 存在2free
- 存在UAF
- 可以任意alloc,但是大小控制在0x80-0x100之间,fastbin默认最大为0x80,所以这题不考fastbin,考smallbin的利用
利用思路
- alloc不会从tcache申请堆
- malloc先从tcache申请堆
- 在从smallbin申请内存后,如果smallbin还有值,tcache没满,这个时候会把smallbin移动到tcache里面,详细代码如下:
1 | if (in_smallbin_range (nb)) |
- 通过上述情况,可以修改mmap里面的随机数变成一个arena区域的地址,这样就能通过功能5获取flag了
影流之主
本题情况
- 每个功能有次数限制
- libc2.23
- 正常malloc,大小不可控,fastbin
- 考点是普通的fastbin的2free
- 难点是泄漏libc地址
利用
- 虽然每个功能有次数限制,但是存在下溢,在0的时候空跑一次,就相当于没限制了
- scanf输入的字符大于等于0x400的时候会malloc一个堆,在fastbin和smallbin共有的大小区间,当申请一个大于fastbin的堆,会把fastbin转换成smallbin
- 通过上述思路,让scanf申请一个大堆,把fastbin转换成smallbin,就能泄漏出libc地址了。
- 然后就是正常的2free流程
garden
本题情况
- libc2.29
- 正常malloc,不过只能同时存在9个堆,大小不可控为0x100,正常free,不存在漏洞
- 只有在malloc的时候才能写堆,之后不能编辑堆的内容
- 强行造了一个函数能让堆发生2free,只能执行一次
- 强行造了一个不能free的malloc,但是大小比正常malloc的小,为0x20
利用
- 考点应该是tcache的2free的利用,但是2.29的tcache存在检测,在bk位置有一个标志位。
- 本题思路就是想办法把tcache的bk标志位修改或清空,但是在malloc后不能编辑堆,导致增加了难度。
- 但是因为堆的数量有限制,所以也没办法通过fastbin进行利用。
- 所以现在的思路是使用该题强行加的malloc功能使堆发生偏移。
- 填满tcache,用到了7个
- unsortbin,使用强行造的洞free,这个时候可以泄漏libc地址(堆A)
- 再把unsortbin上面的堆free掉,这样就形成了一个0x220的unsortbin
- malloc 7次,把tcache清空
- malloc 0x20,会从unsortbin中进行分配,unsorbin大小变成0x1f0
- malloc 1次,从unsorbin中进行分配,分配到的堆包含到堆A(这个堆块还能正常free一次)(堆B)
- 然后释放堆A和B,填充tcache,让下一次malloc获取到的是堆B,并且能修改到堆A的fd字段,之后就是正常的2free流程的思路了。
把嘴闭上
本题情况
- libc 2.23
- 正常malloc,大小在范围内可控
- 正常free,没有漏洞
- 没有leak,题目强行帮你leak了一下libc地址
- 可以自由控制mallopt函数
利用
- mallopt在修改选项的时候都会执行一下
malloc_consolidate
- 使用mallopt(M_MXFAST, 1)可以把global_max_fast修改为0
- 在
malloc_consolidate
函数中,当global_max_fast=0的时候,会被认为堆未初始化,会对arena的bins和top字段进行初始化,把top设置为arena区域的地址。 - 正常情况下malloc初始化的时候也会把top设置为arena区域的地址,但是随后还会执行
sysmalloc
,把top修改为正常的堆地址。 - 通过上述思路,可以把top chunk修改为arena区域的地址,然后算好和freehook之前的距离,多次malloc,可以成功申请到freehook的地址,修改freehook地址,然后执行/bin/sh
引用
CTF PWN题总结