TL;DR Summary of how to replace pre-built binary functions later
Prepare variadic arguments with and without variable length arguments for ptintf and fprintf as samples to be replaced.
print.c
#include <stdio.h>
int main(int argc, char const* argv[])
{
printf("printf no args\n");
printf("printf %d\n", 1);
fprintf(stdout, "fprintf no args\n");
fprintf(stdout, "fprintf %d\n", 1);
return 0;
}
The build and execution results are as follows.
Build and execution results
$ gcc print.c
$ ./a.out
printf no args
printf 1
fprintf no args
fprintf 1
Next, prepare the code for replacement with LD_PRELOAD. Basically, you can bring in the definition of the function you want to replace and redefine it regardless of the presence or absence of variable length arguments.
override.c
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) {
const char *c = "override fwrite\n";
write(1, c, strlen(c)); // stdout
return 0;
}
int fprintf (FILE *__restrict __stream, const char *__restrict __format, ...) {
const char *c = "override fprintf\n";
write(1, c, strlen(c)); // stdout
return 0;
}
int puts(const char *s) {
const char *c = "override puts\n";
write(1, c, strlen(c)); // stdout
return 0;
}
int printf (const char *__restrict __format, ...) {
const char *c = "override printf\n";
write(1, c, strlen(c)); // stdout
return 0;
}
If you specify this with LD_PRELOAD and execute it, you can replace the definition later.
Build and execution results
$ gcc -shared -fPIC override.c -o liboverride.so
$ LD_PRELOAD=./liboverride.so ./a.out
override puts
override printf
override fwrite
override fprintf
However, in GCC, if the variable length argument element is 0 in printf
and fprintf
, they will be replaced with puts
and fwrite
at compile time, respectively. (GCC can be replaced with -O0, clang 3.6 does not replace printf with -O0, replaces with -O1 or higher)
$ gcc -S print.c
$ cat print.s
.file "print.c"
.section .rodata
.LC0:
.string "printf no args"
.LC1:
.string "printf %d\n"
.LC2:
.string "fprintf no args\n"
.LC3:
.string "fprintf %d\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl %edi, -4(%rbp)
movq %rsi, -16(%rbp)
movl $.LC0, %edi
call puts <-printf is puts
movl $1, %esi
movl $.LC1, %edi
movl $0, %eax
call printf
movq stdout(%rip), %rax
movq %rax, %rcx
movl $16, %edx
movl $1, %esi
movl $.LC2, %edi
call fwrite <-fprintf is fwrite
movq stdout(%rip), %rax
movl $1, %edx
movl $.LC3, %esi
movq %rax, %rdi
movl $0, %eax
call fprintf
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
.section .note.GNU-stack,"",@progbits
If you want to disable this replacement in GCC, build with -fno-builtin-printf`` -fno-builtin-fprintf
.
Build and execution result with replacement disabled
$ gcc -fno-builtin-printf -fno-builtin-fprintf print.c
$ LD_PRELOAD=./liboverride.so ./a.out
override printf
override printf
override fprintf
override fprintf
Of course, it is natural, but if you call the same function again in the replaced function, it will be a recursive call and you will not be able to escape. For example, when replacing printf, it may be used in the library used in the replacement process, so you need to be careful separately.
Recursive call
int puts(const char *s) {
const char *c = "override puts\n";
write(1, c, strlen(c)); // stdout
puts("recursive call?"); //* I can't get out of puts
return 0;
}
"BINARY HACKS" #HACK 60: Replace shared library with LD_PRELOAD
Trace all in / out functions at runtime --Qiita
Change the behavior of the Linux shared library (.so). Enjoy playing with LD_PRELOAD: Garbage In Garbage Out Let's hook the function with LD_PRELOAD! --How to walk binary Override dynamic library functions with LD_PRELOAD | Siguniang's Blog