网络安全检测|网络安全服务|网络安全扫描-香港墨客投资移动版

主页 > 业界资讯 > 网络安全预防措施

Linux堆溢出漏洞利用之unlink

[收起] 文章目录

0x00 前言

之前我们深入了解了glibc malloc的运行机制(文章链接请看文末▼),下面就让我们开始真正的堆溢出漏洞利用学习吧。说实话,写这类文章,我是比较怂的,因为我当前从事的工作跟漏洞挖掘完全无关,学习这部分知识也纯粹是个人爱好,于周末无聊时打发下时间,甚至我最初的目标也仅仅是能快速看懂、复现各种漏洞利用POC而已…鉴于此,后续的文章大致会由两种内容构成:1)各种相关文章的总结,再提炼;2)某些好文章的翻译及拓展。本文两者皆有,主要参考文献见这里。

0x01 背景介绍

首先,存在漏洞的程序如下:

#!c /* Heap overflow vulnerable program. */ #include <stdlib.h> #include <string.h> int main( int argc, char * argv[] ) { char * first, * second; /*[1]*/ first = malloc( 666 ); /*[2]*/ second = malloc( 12 ); if(argc!=1) /*[3]*/ strcpy( first, argv[1] ); /*[4]*/ free( first ); /*[5]*/ free( second ); /*[6]*/ return( 0 ); }

在代码3中存在一个堆溢出漏洞:如果用户输入的argv1的大小比first变量的666字节更大的话,那么输入的数据就有可能覆盖掉下一个chunk的chunk header——这可以导致任意代码执行。而攻击的核心思路就是利用glibc malloc的unlink机制。

上述程序的内存图如下所示:

Linux堆溢出漏洞利用之unlink

0x02 unlink技术原理 2.1 基本知识介绍

unlink攻击技术就是利用”glibc malloc”的内存回收机制,将上图中的second chunk给unlink掉,并且,在unlink的过程中使用shellcode地址覆盖掉free函数(或其他函数也行)的GOT表项。这样当程序后续调用free函数的时候(如上面代码[5]),就转而执行我们的shellcode了。显然,核心就是理解glibc malloc的free机制。

在正常情况下,free的执行流程如下文所述:

PS: 鉴于篇幅,这里主要介绍非mmaped的chunks的回收机制,回想一下在哪些情况下使用mmap分配新的chunk,哪些情况下不用mmap?

一旦涉及到free内存,那么就意味着有新的chunk由allocated状态变成了free状态,此时glibc malloc就需要进行合并操作——向前以及(或)向后合并。这里所谓向前向后的概念如下:将previous free chunk合并到当前free chunk,叫做向后合并;将后面的free chunk合并到当前free chunk,叫做向前合并。

一、向后合并:

相关代码如下:

#!c /*malloc.c int_free函数中*/ /*这里p指向当前malloc_chunk结构体,bck和fwd分别为当前chunk的向后和向前一个free chunk*/ /* consolidate backward */ if (!prev_inuse(p)) { prevsize = p->prev_size; size += prevsize; //修改指向当前chunk的指针,指向前一个chunk。 p = chunk_at_offset(p, -((long) prevsize)); unlink(p, bck, fwd); } //相关函数说明: /* Treat space at ptr + offset as a chunk */ #define chunk_at_offset(p, s) ((mchunkptr) (((char *) (p)) + (s))) /*unlink操作的实质就是:将P所指向的chunk从双向链表中移除,这里BK与FD用作临时变量*/ #define unlink(P, BK, FD) { \ FD = P->fd; \ BK = P->bk; \ FD->bk = BK; \ BK->fd = FD; \ ... }

首先检测前一个chunk是否为free,这可以通过检测当前free chunk的PREV_INUSE(P)比特位知晓。在本例中,当前chunk(first chunk)的前一个chunk是allocated的,因为在默认情况下,堆内存中的第一个chunk总是被设置为allocated的,即使它根本就不存在。

如果为free的话,那么就进行向后合并:

1)将前一个chunk占用的内存合并到当前chunk;
2)修改指向当前chunk的指针,改为指向前一个chunk。
3)使用unlink宏,将前一个free chunk从双向循环链表中移除(这里最好自己画图理解,学过数据结构的应该都没问题)。

在本例中由于前一个chunk是allocated的,所以并不会进行向后合并操作。

二、向前合并操作:

首先检测next chunk是否为free。那么如何检测呢?很简单,查询next chunk之后的chunk的 PREV_INUSE (P)即可。相关代码如下:

#!c …… /*这里p指向当前chunk*/ nextchunk = chunk_at_offset(p, size); …… nextsize = chunksize(nextchunk); …… if (nextchunk != av->top) { /* get and clear inuse bit */ nextinuse = inuse_bit_at_offset(nextchunk, nextsize);//判断nextchunk是否为free chunk /* consolidate forward */ if (!nextinuse) { //next chunk为free chunk unlink(nextchunk, bck, fwd); //将nextchunk从链表中移除 size += nextsize; // p还是指向当前chunk只是当前chunk的size扩大了,这就是向前合并! } else clear_inuse_bit_at_offset(nextchunk, 0); …… }

(责任编辑:admin)