[LINUX] Replace printf later with LD_PRELOAD

TL;DR Summary of how to replace pre-built binary functions later

Replacement target code

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

Replacement code

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

important point

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;
}

Reference book

"BINARY HACKS" #HACK 60: Replace shared library with LD_PRELOAD

Relation

Trace all in / out functions at runtime --Qiita

reference

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

Recommended Posts

Replace printf later with LD_PRELOAD
11. Replace tabs with spaces
Replace / delete special symbols with sed
Replace dictionary value with Python> update ()
Replace all at once with sed