Escalating privileges
We've covered how an arbitrary write primitive can be used to escalate privileges or execute arbitrary code in a lot of these different discussions, the Houses covered in our heap exploitation section, for example. In all of these examples, once the attacker has all the information they need from previous sensitive information disclosures to build their final payload, these techniques usually conduct one final, important write to gain control of the vulnerable process in order to execute arbitrary code. In this section we'll list some interesting targets that attackers write to to gain control of the process. This list will not be all-encompassing as I'm sure there are plenty of other candidates that can be used to hijack the process, these are just ones that are more popular within the community.
Global Offset Table
The GOT is a common location for attackers to target given they have an arbitrary write primitive. Whenever a program calls functions from linked libraries, the GOT will be used by the program to either resolve the symbol or call the symbol stored there. Attackers will commonly overwrite a GOT entry with the symbol of a different function they want the program to call, usually ensuring that the next couple of call operations conducted by the program include the now corrupted GOT entry. While this is a common target for arbitrary writes to gain arbitrary code execution, keep in mind that full RELRO mitigates this tactic, resolving all entries within the GOT and setting it to read-only before the program starts. [2]
Partial RELRO
In order to complete our knowledge about the Relocation Read-Only (RELRO)
protection mechanism, Partial RELRO is a security mitigation where the
Global Offset Table is relocated to reside above the .bss
section. There
existed previous methods of attacking the GOT where attackers would utilize
a buffer overflow of a global variable contained in the .bss
section to
corrupt the GOT - for some reason the .bss
used to reside above the GOT
in ELF programs.
Compilers now enforce Partial RELRO by default, placing the GOT above the
.bss
in order to mitigate this attack surface. [7]
initial@glibc
This write target is pretty cool. The initial
data structure in glibc
is
a read/writeable segment in memory that is used to contain/register sensitive
information that glibc
tracks for the current process. For an attacker, the
more interesting information they would most likely go after are the function
pointers registered by atexit()
. My understanding of this data structure and
its use to gain control of the process is derived from 0x00 CTF 2017's
challenge, "Left". [3] In the Left challenge, the program
gives the attacker a glibc
leak, one arbitrary read and one arbitrary write
and then calls exit
immediately after. The attacker must use this arbitrary
read to leak the mangled pointer of _dl_fini
stored in the initial
section
of glibc
. _dl_fini
is a function pointer that is always registered in the
initial
section of glibc
, and all of the function pointers stored in
initial
are unmangled and executed when exit
is called. The attacker uses
the exposed, mangled pointer of _dl_fini
to derive the secret stored in
Thread Local Storage (TLS) and then mangles a one_gadget
address in the
correct manner to forge a valid initial
entry. The attacker then uses a
single arbitrary write to overwrite the _dl_fini
pointer in initial
. Once
the program calls exit
, the one_gadget
entry in initial
will be executed,
allowing the attacker to gain an interactive shell.
_IO_list_all@glibc
This is an interesting one as well, and its usefulness is showcased in
The House of Orange. In The
House of Orange, the attacker uses the write primitive provided by an
Unsortedbin Attack to write a main_arena
address to the _IO_list_all
symbol
in glibc
. This symbol keeps track of all the open _IO_FILE
structures for
the process, and the attacker uses this to kick off some File Stream Oriented
Programming to gain control of the process for arbitrary code execution.
[5] The attacker forces the process to experience a SIGABRT
rasied by checks in malloc()
which leads to glibc
attempting to call the
overflow
function of all _IO_FILE
structures present in the linked list of
_IO_list_all
. Because _IO_list_all
now points to the main_arena
, it
attempts to treat the main_arena
as an _IO_FILE
structure and attempts to
execute the overflow
function registered in its vtable
. This obviously fails
but, instead of failing completely, glibc
just moves onto the next _IO_FILE
structure pointed to by main_arena
's chain
attribute (since glibc
assumes
main_arena
is an _IO_FILE
structure at this point). It just so happens that
this chain
attribute points to the smallbin
, a location where the attacker
controls the data. Using this, the attacker ensures that a fake, but valid,
_IO_FILE
structure resides in this location and fools glibc
into calling
overflow
from its forged vtable
. The attacker ensures that overflow
just
ends up being a pointer to system@glibc
, and the argument being passed to
this call is the current _IO_FILE
struct. The attacker ensures that
"/bin/sh"
resides at the first word of bytes in the _IO_FILE
struct, thus
fooling the process into executing system("/bin/sh")
.
__malloc_hook@glibc
and __free_hook@glibc
And, finally, we have our usual suspects, the __malloc_hook
and the
__free_hook
. These are kinda the easy button of arbitrary write targets
within glibc
if we know that our target program is making calls to malloc
and free
in the future. As discussed in
The House of Force, the
__malloc_hook
and the __free_hook
are symbols in glibc
that were
originally implemented to allow programmers the ability to register functions
that will be executed when calls malloc()
or free()
are made. The thought
process was to allow programmers the ability to gather memory usage statistics
or to install their own versions of these functions, etc.
Attackers use these hooks to redirect program execution with just one arbitrary
write, and they usually overwrite these locations with the address of a
one_gadget
or the symbol of system()
. It's not outside the realm of
possibility, however, that they could use these hooks to pivot to the location
of a ROP
chain which could possibly lead to the execution of more complex
shellcode.
Honorable mention: _dl_open_hook@glibc
I won't go into too much detail with this one seeing as we've covered so many
other options already, but I recently just learned about this technique which
surprisingly doesn't have a lot of coverage on the internet. In
POCORGTFO 2018 [6], an author covers what they call the
"House of Fun", a frontlink
attack in malloc()
that people assumed was
mitigated but actually went unpatched until glibc 2.30
. With the arbitrary
write that they gain through this technique, the attackers target
_dl_open_hook
which is a hook where programmers can register a function
pointer to be called when dlopen()
is called by the program. A little known
fact is that malloc_printerr
when causes a SIGABRT
and the program attempts
to dump the memory mapping of the process, it needs to call dlopen
on
libgcc_s.so.1
in order to access the __backtrace
function. With this in
mind, the attackers write a one_gadget
to _dl_open_hook->dlopen_mode
and
force malloc()
to encounter corrupted heap metadata, causing it to call
malloc_printerr
which leads to dlopen()
which leads to the program
executing sys_execve("/bin/sh")
.
Conclusion
As we can see, viable write targets to gain arbitrary code execution within a process are aplenty. It's up to the attacker to ensure they understand what conditions need to be present in order to get reliable code execution - there's also some creativity required.
References
- The House of Force
- https://ctf101.org/binary-exploitation/relocation-read-only/
- https://ctftime.org/writeup/8385
- The House of Orange
- https://ctf-wiki.github.io/ctf-wiki/pwn/linux/io_file/fsop/
- https://www.alchemistowl.org/pocorgtfo/pocorgtfo18.pdf
- https://ctf101.org/binary-exploitation/relocation-read-only/