0CTF 2017 部分Pwn小结
又是一年0ctf,这次Pwn的题目相较于Web分值高了很多,而且套路也十分多样化,也是学习到了不少。这边稍微记录一下一部分(简单)题目的解法。
EasietPrintf
程序在进行一次任意地址读之后有一次格式化字符串的机会,之后直接exit
。在程序启用Full RELRO的情况下,选择覆盖libc中的_IO_2_1_stdout_
结构的虚表,因为printf在将所有输入解析之后会调用其中的某个函数进行输出,我们可以其改为system
,而这个结构自身会作为参数传入,覆盖虚表之后将一个sh\0\0
写到整个结构头部即可。
BabyHeap 2017
一个相当classical的heap利用,在堆上有着任意长度overflow的能力,唯一的限制在于calloc
的分配会清空整个chunk.这里我是用extend alloc chunk的方法在已经分配的chunk中overlap出一个non-fastbin的free chunk,从而dump出libc的地址。之后使用fastbin attack覆盖__malloc_hook
就可以了。
UploadCenter
整个程序是一个模拟PNG上传的服务器,不过实际只会读取PNG的头部和块信息。程序的主要漏洞在上传时根据图片的width*height
信息来mmap
一块内存存储数据,但在delete时又通过图片自身的长度大小来进行munmap
操作,这就造成了一个歧义。另外,在程序填写name的函数中其实能够leak出栈上之前没有洗掉的数据。
为了利用这个歧义,我们需要合理地进行mmap
操作,之后启动程序中的一个monitor线程,这个线程在每次有图片上传时会打出通知信息,其他时候会等待一个条件。我们先上传几张图片,保证mmap
的空间到达libc的上方低地址位置,之后启动这个线程,再上传图片,会发现之后的数据刚好处于线程栈的上方,这时利用这个歧义munmap
线程栈的这个page,之后再上传一个更大的文件mmap
回这块空间即可控制线程栈。
最后需要注意的是需要布置一下线程栈原先的数据保证线程不会crash顺利从libc返回,之后由于线程输入和主线程输入的竞争,需要考虑一下ROP的构造方式。
Pages
这个程序整体不长,首先从/dev/urandom
产生64bytes的随机序列,然后会进行一次fork
,子进程根据这个随机序列每个byte最低位是0或1来mmap
不同的内存地址,之后清空这个随机序列,seccomp
限制自身只能进行exit
的syscall,之后执行输入的shellcode.而父进程则会ptrace子进程,从其0x300000000
的位置取出64bytes的数据比较其最低位与之前的随机序列是否一致,若一致则给出flag。
现在问题就成了:如何在不进行系统调用的情况下用shellcode探测某块内存是否被mmap
,在比赛当时我觉得应该是x86有某些可以不会引起Segmentation Fault的非特权访存指令可以做到,不过当时并没有找到合适的。在比赛之后才知道貌似是《Prefetch Side-Channel Attacks: Bypassing SMAP and Kernel ASLR》这篇论文中提到的一种Side Channel攻击,简单来说就是利用CPU访存时,由于Cache的作用导致所需时钟周期不同而形成的Side Channel。为了完成这一攻击,确实需要一些非特权指令:
prefetch
: 通知CPU从主存中预取数据进入Cache,参数是一个内存地址。rdtsc
: ReaD Time Stamp Counter,取得CPU当前的时钟周期,可以用来精确计时,实际也可以用rdtscp
。结果会被保存在rax
与rdx
中。cpuid
、mfence
: 这两条都是论文中提到的用来提高计时精度的,因为现代CPU的流水线和乱序执行机制可能导致计时出现误差,这里用这两条指令降低它们带来的影响。
prefetch
确实是一条unusual instruction,其参数可以是任意虚拟地址,而且即使Vitrual Address对应的区域没有被mmap
仍然可以保证不出错,但是其指令行为不固定。我猜测,如果不断prefetch
一块存在的VA,则之后的预取操作会由于Cache中已经存在而需要较少的时钟周期;但如果不断prefetch
一块不存在的VA,则每次都需要去主存中找,导致需要较多的时钟周期。这样通过时间的不同即可判断这一块究竟是不是被mmap
过了。经过测试,确实迭代一定次数之后,产生了肉眼可见的时间差,应该是相当reliable的。
exp参考了jinmo的writeup,结合论文我又加了一些改进
1 | #!/usr/bin/env python |