小记一则不一样的Fastbin利用
在翻ZDI的历史博客时,看到一个NETGEAR路由器中的堆溢出利用,很是少见,一般路由器中大多是命令执行或栈溢出,很少见到有堆溢出相关的,便跟着走了一遍。
漏洞点及成因
漏洞在现在看来其实很无厘头,在文件上传逻辑中获取Content-Length值时通过对整个http的请求做stristr(s1, “Content-Length: “)来定位的,且没有其他的条件限制。
1 | .text:00017308 04 A0 80 E2 ADD R10, R0, #4 |
所以attacker在请求头中原Content-Length之前如果包含了伪造的字段,即可控制该值。这个值的计算逻辑也很有意思:
1 | v117 = stristr(v115 + 16, "\r\n") - (v115 + 16); |
可以看到是以\r\n作为结束符,在结束符之前的都将作为数字的str,计算时将每个字符的ascii减去0的ascii,然后组合到一起作为实际值来使用。这里的fileSize1是unsigned int,所以这里必然存在整数溢出问题。这个fileSize值在后面用作内存分配的值,申请fileSize的空间后再将上传的内存拷贝至对应位置。 所以在控制了fileSize值后,将其改成比上传内容实际值小便可以触发堆溢出的问题。
1 | if ( dword_1A870C ) |
漏洞的成因大致就是这样,我觉得完全是开发人员不严谨导致的;另一方面也是早起嵌入式开发人员习惯什么事情都自己来完成,所以解析字段时也就直接stristr去获取了,却没做严格的限制。同时在计算数字时也没有考虑到存在非数字字符的情况。
如何利用
利用部分主要记录的是堆相关的操作,至于路径选择以及其它跟路由器密切相关的部分这里就不记录了,感兴趣的可以去看原blog。
1 | ➜ checksec httpd |
不存在PIE所以我们不用考虑如何去获取heap base,把重点放在堆相关的利用上即可。现在有一个堆溢出且能将任意数据写入堆内存,常规思路有:
- 通过溢出直接覆盖后面堆块的链表指针去控制后面的堆分配,然覆盖got表…
- 溢出覆盖__malloc_hook、__free_hook的值,将其修改成gadget…
但是很可惜,这里这两种方式都不大行得通。由于固件中使用的是uClibc,属于glibc的最小libc版本,所以其中并没有__malloc_hook这种机制,所以方法二彻底废了,方法一行不通是因为漏洞点前后有其它因素影响:
- 溢出点分配的内存会存储在全局变量中,每次使用前会检查全局变量中是否有内容,有的话则先free对应内存将全局变量置0再分配
- memcpy拷贝内容触发堆溢出后,会返回错误页面,在这里会调用fopen函数,而在uClibc中fopen会触发两次内存操作,大小分别为0x60和0x1000,大致流程为:
1 | free(malloc(0x60)), free(malloc(0x1000)) |
看到0x60的时候很自然会想到fastbinDup,但是后面这个0x1000就让人头疼了,会触发malloc_consolidate来整理fastbin。这里简单介绍一下malloc_consolidate:
这个函数的作用就是将 fastbin 合并后置入 unsorted bin,一般调用的情况有以下几种:
- malloc
- malloc 的大小在 smallbin 范围内,若对应的 smallbin 没初始化的时候。
- 当申请大于 small bin 范围的堆快时(large chunk) **if (have_fastchunks(av))
**malloc_consolidate(av); - 投 topchunk 中没有空闲内存,向系统申请内存时,如果 fastbin 中有空间,则会先尝试整理 fastbin 看能否满足需求,不行再从 system 中申请
- free
- free 一块大内存后会合并其附近的空闲内存,如果合并后的 size 大于 FASTBIN_CONSOLIDATION_THRESHOLD 时,如果有 fastbins 则会调用 malloc_consolidate, 同时如果 top chunk 的 size 大于 trim_threshold,会向操作系统归还内存,也会调用 malloc_consolidate.
由于malloc_consolidate整理了fastbin,所以我们不能用fastbinDup,但是又没用其它更好的办法,似乎无解了。
这里的破局之法还是出现在uClibc中,毕竟是最小版本的glibc,所以其中很多实现都不怎么完善。uClibc中的free 和malloc与glibc中还是有比较大的不同,其中在释放fastbin时存在越界写问题:
1 | struct malloc_state { |
可以看到在访问fastbins数组时没有边界检查,而fastbins在malloc_state中位于max_fast之后,同时max_fast又是是否执行malloc_consolidate的判断条件,所以只要通过fastbins的越界写来修改max_fast,便可以控制malloc_consolidate让malloc中不整理fastbin,这样我们就可以使用fastbinDup来完成利用了。
控制max_fast的方法也比较好理解,根据fastbin_index可以看到我们只需要将设置成7或8时,index为-1,就可以访问到max_fast。sz又是堆块中表示size的字段,可以通过堆溢出去覆盖修改,所以修改max_fast也就不是问题了。
最后利用流程如下:
- 通过触发堆溢出漏洞修改下一个堆快的flag,覆盖REV_INUSE标志为0,使其错误地表示前一个chunk是空闲的;
- 由于错误的PREV_INUSE标志,我们可以malloc()返回一个与实际存在的块重叠的块。因此我们可以修改上一个堆快的size设置为不可能的 8;
- 当释放这个”size = 8”的堆快后并放置在fastbin 上时,malloc_stats->max_fast会被改成一个大值(堆快的地址)
- 当malloc_stats->max_fast被更改后,malloc(0x1000)便不会再调用malloc_consolidate,所以就可以使用fastbinDup了
- 再次触发堆溢出漏洞,溢出修改下一个空闲块的fd为free()的GOT地址
- 再次申请堆快,会返回free的GOT地址,我们可以向其中写入system 的plt地址
- 最后,在调用free的时候,会调用system,我们可以将其参数指向payload,从而实现RCE
小结
这里比较精巧的就是利用uClibc中free函数存在的OOB,是的可以控制malloc_consolidate是否被执行。后面对这个原语搜了一波,发现还有挺多利用中都有用到,是我愚钝了(lll¬ω¬)
在嵌入式设备中,除了常规的程序发掘外,对于一些使用的库也存在很多漏洞。有时候结合这些库中存在的问题可能会得到意想不到的结果。
小记一则不一样的Fastbin利用