第五届强网杯的虚拟化系列题目,EzCloud 15解295分,比赛期间花了不少时间调试EzCloud的堆风水。
这题实现了一个HTTP服务,题目意图应该是模拟类似VMware vSphere的Web管理界面(虽然非常简陋)。下面简单说明一下程序主要逻辑和解题目标。
Recon
程序实现了以下的路由函数,分析后发现只要完成登录然后通过GET请求访问/flag
就能读取flag。
1 | unsigned __int64 __fastcall post_handler(request *a1) |
1 | unsigned __int64 __fastcall get_handler(request *a1) |
登录过程会在堆上新建如下的cred结构体,用于保存后续操作相关变量。登录操作会比较请求中的Login-ID
和从/dev/urandom
读取的0x40 byte随机数是否相同,相同的话会把cred->is_login
设置为1,后续读flag操作会判断is_login
字段,只有等于1的情况才会返回flag。因此攻击目标是把堆上的cred->is_login
改写掉
1 | struct cred |
程序中定义了一个重要的结构体,我们把它命名为strbuf
。
1 | struct __attribute__((aligned(8))) strbuf |
解析HTTP请求中的几乎所有字符串操作都和这个结构体有关。包括URL、fields、body的解析等,相关的操作有两个,在解析HTTP请求的过程中有大量使用,基本就是当成局部字符串变量在使用,有点类似自定义的一个strdup
函数。
1 | void __fastcall make_strbuf(strbuf *dst, size_t sz, const void *src) |
程序的漏洞在于/notepad
的逻辑,里面实现了new
, edit
, delete
三个操作。其中new部分存在内存未初始化漏洞。如果HTTP请求中的Content-Type
不为application/x-www-form-urlencoded
的,解析产生的req->body.buf==0
,那么下面第13行的make_strbuf
将不会起作用,导致12行malloc
的strbuf
未被初始化。
1 | if ( cred->notes[n] ) |
未初始化的strbuf->buf
会残留一个0x20 chunk的指针,strbuf->sz
残留一个很大的值,结合edit操作就能构造一个堆溢出的漏洞。
1 | if ( req->body.sz > cred->notes[idx]->sz ) |
Exploit
漏洞利用的思路并不复杂,我的思路是通过解析HTTP请求中的make_strbuf
和free_strbuf
形成堆喷射,让tcache中的chunk+0x18的位置残留我们可以控制的大数值。再edit的时候就能分配出能够堆溢出的note了。然后通过堆溢出构造tcache poison改写某个tcache bin的fd,最后分配出cred所在的内存再改写is_login
字段。
难点在于解析HTTP请求过程太多make_strbuf
调用了,每次到达notepad函数tcache bins都会发生变化,尝试很久才确定了通过HTTP fields来构造堆喷的方法,而堆溢出的时候也要考虑到覆盖的chunk在free过程中不会报错。
第一天晚上通过partial overwritechunk->fd
搞出一个本地能有1/16成功的exp。然而打远程的时候发现远程堆布局和本地不同。调试发现是exp发送HTTP请求的时候用了payload.ljust(0x1000, "\x00")
,发送过程可能和本地存在差异导致堆布局不同。第二天去掉padding的部分,调试堆风水先泄露堆地址,再计算堆地址偏移完整改写tcache bin的FD。最终版本exp打本地和打远程的堆地址还是存在0x20的偏移差异,好在不影响最终效果。
exp.py
1 | # QWB{EzCloud is easy to be admin!!} |
系列题目的第二问EzQtest放在下一篇,不然篇幅太长了。