Arbitrary Read/Write
Arbitrary Read
fwrite
Set the
_fileno
to the file descriptor ofstdout
Set
_flag & ~_IO_NO_WRITES
Set
_flag |= _IO_CURRENTLY_PUTTING
Set the
write_base
andwrite_ptr
to memory address which you want to readSet
_IO_read_end
equal to_IO_write_base
or though not always reliable, in pwntools:
fp = FileStructure(0x0)
payload = fp.read(addr, 0x120)
io.send(payload)
fclose
upon fclose()
, if buffer is not empty, it will be flushed (written to the _fileno
)
Set the
_fileno
to the file descriptor of choiceSet
write_base
to the start of write addressSet
write_ptr
to the end of write address
be careful of closing if _fileno
is set to stdout
or stdin
explained in-depth in this writeup:
we can trace the function calls as the following,
int
_IO_new_fclose (FILE *fp)
{
int status;
CHECK_FILE(fp, EOF);
// ...
/* First unlink the stream. */
if (fp->_flags & _IO_IS_FILEBUF)
_IO_un_link ((struct _IO_FILE_plus *) fp);
_IO_acquire_lock (fp);
// ...
_IO_FINISH (fp); // <-- OUR INTEREST
// ...
_IO_deallocate_file (fp);
return status;
}
versioned_symbol (libc, _IO_new_fclose, _IO_fclose, GLIBC_2_1);
strong_alias (_IO_new_fclose, __new_fclose)
versioned_symbol (libc, __new_fclose, fclose, GLIBC_2_1);
which then calls _IO_FINISH()
,
typedef void (*_IO_finish_t) (FILE *, int); /* finalize */
#define _IO_FINISH(FP) JUMP1 (__finish, FP, 0)
#define _IO_WFINISH(FP) WJUMP1 (__finish, FP, 0)
referring to the jump table, we can see that the implementation for __finish
is _IO_new_file_finish()
void
_IO_new_file_finish (FILE *fp, int dummy)
{
if (_IO_file_is_open (fp))
{
_IO_do_flush (fp); // <-- OUR INTEREST
if (!(fp->_flags & _IO_DELETE_DONT_CLOSE))
_IO_SYSCLOSE (fp);
}
_IO_default_finish (fp, 0);
}
libc_hidden_ver (_IO_new_file_finish, _IO_file_finish)
which will call the flush macro _IO_do_flush()
and is defined as follows:
#define _IO_do_flush(_f) \
((_f)->_mode <= 0 \
? _IO_do_write(_f, (_f)->_IO_write_base, \
(_f)->_IO_write_ptr-(_f)->_IO_write_base) \
: _IO_wdo_write(_f, (_f)->_wide_data->_IO_write_base, \
((_f)->_wide_data->_IO_write_ptr \
- (_f)->_wide_data->_IO_write_base)))
which writes write_base
up until write_ptr
puts
refer to fwrite.
Arbitrary Write
fread
Set the
_fileno
to file descriptor ofstdin
Set
_flag &~ _IO_NO_READS
Set
_flag |= _IO_CURRENTLY_PUTTING
Set
read_base
equals toread_ptr
toNULL
Set the
buf_base
andbuf_end
to memory address which you want to writebuf_end - buf_base
> size of fread (BUFFER SIZE > READ SIZE) (or other function, scanf, etc)
or though not always reliable, in pwntools:
fp = FileStructure(0x0)
payload = fp.write(addr, 0x120)
io.send(payload)
fwrite
fwrite(const void *buf, size_t size, size_t count, FILE *fp);
Set
fp->_flags
to_IO_MAGIC | ~_IO_LINE_BUF | ~_IO_CURRENTLY_PUTTING
Set
buf
to the data wish to be writtenSet
fp->write_ptr
to the start of write addressSet
fp->write_end
to the end of write address
we can trace the function calls as the following,
size_t
_IO_fwrite (const void *buf, size_t size, size_t count, FILE *fp)
{
// ...
_IO_acquire_lock (fp);
if (_IO_vtable_offset (fp) != 0 || _IO_fwide (fp, -1) == -1)
written = _IO_sputn (fp, (const char *) buf, request); // <-- OUR INTEREST
// ...
}
libc_hidden_def (_IO_fwrite)
which calls _IO_sputn()
macro:
// https://elixir.bootlin.com/glibc/glibc-2.31/source/libio/libioP.h#L379
#define _IO_sputn(__fp, __s, __n) _IO_XSPUTN (__fp, __s, __n)
// https://elixir.bootlin.com/glibc/glibc-2.31/source/libio/libioP.h#L176
#define _IO_XSPUTN(FP, DATA, N) JUMP2 (__xsputn, FP, DATA, N)
#define _IO_WXSPUTN(FP, DATA, N) WJUMP2 (__xsputn, FP, DATA, N)
referring to the jump table, we can see that the implementation for __xsputn
is _IO_new_file_xsputn()
size_t
_IO_new_file_xsputn (FILE *f, const void *data, size_t n)
{
// ....
size_t to_do = n;
// ....
size_t count = 0;
// ....
if ((f->_flags & _IO_LINE_BUF) && (f->_flags & _IO_CURRENTLY_PUTTING)) // [0]
{
// ..
}
else if (f->_IO_write_end > f->_IO_write_ptr) // <-- NEEDS TO BE SATISFIED [1]
count = f->_IO_write_end - f->_IO_write_ptr; /* Space available. */
/* Then fill the buffer. */
if (count > 0) // <-- NEEDS TO BE SATISFIED [2]
{
if (count > to_do) // <-- NEEDS TO BE SATISFIED [3]
count = to_do;
f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count); // <-- OUR INTEREST
s += count;
to_do -= count;
}
if (to_do + must_flush > 0)
{
// ...
}
return n - to_do;
}
libc_hidden_ver (_IO_new_file_xsputn, _IO_file_xsputn)
our interest lies in __mempcpy (f->_IO_write_ptr, s, count);
where it will do memcpy
of destination write_ptr
and source s
(buf).
first, note that initially count
and to_do
is set to 0 as default, then modified over the course of the function execution.
at [3] we need count
to be bigger than to_do
which can be easily done because it is 0 as default, so we just need to somehow able to change count
to be bigger than 0 which also a condition in [2]
though the block at [0] also modifies count
, I think its the route at [1] is more trivial and intuitive. so we need to satisfy [1] while dissatisfy [0].
when all of those requirements are fulfilled, it will then call memcpy
which corresponds to write profit.
Last updated