Reference:
Weak symbols and libc
Most libc implementations declare their methods "weak" so users can intercept them. This is not always as convenient as it seems. Let's look at how to intercept malloc.
// mymalloc.c
#define _GNU_SOURCE // Could have been defined with -D on command-line
#include "stddef.h"
#include "dlfcn.h"
#include "stdio.h"
#include "stdlib.h"
void* malloc(size_t sz) {
void *(*libc_malloc)(size_t) = dlsym(RTLD_NEXT, "malloc");
printf("malloced %zu bytes\n", sz);
return libc_malloc(sz);
}
int main() {
char* x = malloc(100);
return 0;
}
This program will enter an infinite loop until it segfaults. This is because dlsym calls malloc.
$ clang mymalloc.c
$ ./a.out
Segmentation fault (core dumped)
For such cases, GNU's libc used to provide special hooks such as __malloc_hook...but they became deprecated. Now the best way is to MITM via the loader and LD_PRELOAD.
// mtrace.c
#include <stdio.h>
#include <dlfcn.h>
static void* (*real_malloc)(size_t) = nullptr;
void *malloc(size_t size) {
if(!real_malloc) {
real_malloc = dlsym(RTLD_NEXT, "malloc");
}
printf("malloc(%d) = ", size);
return real_malloc(size);
}
$ clang -shared -fPIC -D_GNU_SOURCE -o mtrace.so mtrace.c
$ LD_PRELOAD=./mtrace.so ls
malloc(472) = 0xaaab24e4b2a0
malloc(120) = 0xaaab24e4b480
malloc(1024) = 0xaaab24e4b500
malloc(5) = 0xaaab24e4b910
...
$
Trivia: Weak symbols are also paramount for C++ and especially the STL (see below).
-
Both mymalloc.c and mtrace.c demonstrate symbol interposition, but not the use of weak symbols. Specifically, malloc is defined as a strong symbol in glibc (malloc.c). As noted in the manual:
The GNU C Library supports replacing the built-in malloc implementation with a different allocator with the same interface. For dynamically linked programs, this happens through ELF symbol interposition, either using shared object dependencies or LD_PRELOAD. For static linking, the malloc replacement library must be linked in before linking against libc.a (explicitly or implicitly).
-
Based on my testing, both mymalloc.c and mtrace.c crash at runtime due to recursion caused by printf (but not dlsym). This happens because printf internally calls malloc (filedoalloc.c), while dlsym only calls malloc in error-handling paths (dlerror.c). Replacing:
printf("malloced %zu bytes\n", sz);
with:
char buffer[50];
sprintf(buffer, "malloced %zu bytes\n", sz);
write(STDOUT_FILENO, buffer, strlen(buffer));
allows mymalloc.c to run successfully (CE). The same applies to mtrace.c.
-
To match the output shown above, the format string used in mtrace.c should be (with the changes from item 2 applied):
sprintf(buffer, "malloc(%d) = %p\n", size, ptr);
where ptr is the return value of real_malloc(size).
Reference:
Both
mymalloc.candmtrace.cdemonstrate symbol interposition, but not the use of weak symbols. Specifically,mallocis defined as a strong symbol inglibc(malloc.c). As noted in the manual:Based on my testing, both
mymalloc.candmtrace.ccrash at runtime due to recursion caused byprintf(but notdlsym). This happens becauseprintfinternally callsmalloc(filedoalloc.c), whiledlsymonly callsmallocin error-handling paths (dlerror.c). Replacing:with:
allows
mymalloc.cto run successfully (CE). The same applies tomtrace.c.To match the output shown above, the format string used in
mtrace.cshould be (with the changes from item 2 applied):where
ptris the return value ofreal_malloc(size).