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/