[LINUX] Ersetzen Sie printf später durch LD_PRELOAD

TL;DR Zusammenfassung, wie vorgefertigte Binärfunktionen später ersetzt werden

Ersatzzielcode

Bereiten Sie ptintf und fprintf mit und ohne Argumente variabler Länge als zu ersetzende Beispiele vor.

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

Die Erstellungs- und Ausführungsergebnisse sind wie folgt.

Ergebnisse erstellen und ausführen


$ gcc print.c 
$ ./a.out 
printf no args
printf 1
fprintf no args
fprintf 1

Ersatzcode

Bereiten Sie als Nächstes den Code für den Ersatz durch LD_PRELOAD vor. Grundsätzlich können Sie die Definition der Funktion, die Sie ersetzen möchten, einfügen und neu definieren, unabhängig davon, ob Argumente variabler Länge vorhanden sind oder nicht.

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

Wenn Sie dies mit LD_PRELOAD angeben und ausführen, können Sie die Definition später ersetzen.

Ergebnisse erstellen und ausführen


$ gcc -shared -fPIC override.c -o liboverride.so
$ LD_PRELOAD=./liboverride.so ./a.out
override puts
override printf
override fwrite
override fprintf

Wenn jedoch in GCC das Argumentelement mit variabler Länge in printf und fprintf 0 ist, werden sie zur Kompilierungszeit durch put bzw. fwrite ersetzt. (GCC kann sogar durch -O0 ersetzt werden, in Klang 3.6 gibt es keinen Ersatz durch printf bei -O0, ersetzt durch -O1 oder höher)

$ 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 ist setzt
        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 ist 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

Wenn Sie diesen Ersatz in GCC deaktivieren möchten, erstellen Sie mit -fno-builtin-printf`` -fno-builtin-fprintf.

Build- und Ausführungsergebnisse mit deaktiviertem Ersatz


$ gcc -fno-builtin-printf -fno-builtin-fprintf print.c                                                                                                                
$ LD_PRELOAD=./liboverride.so ./a.out                                                                                                                                 
override printf
override printf
override fprintf
override fprintf

wichtiger Punkt

Natürlich ist es natürlich, aber wenn Sie dieselbe Funktion in der ersetzten Funktion erneut aufrufen, handelt es sich um einen rekursiven Aufruf, und Sie können nicht entkommen. Wenn Sie beispielsweise printf ersetzen, kann es in der Bibliothek verwendet werden, die beim Ersetzen verwendet wird. Sie müssen daher separat vorsichtig sein.

Rekursiver Aufruf


int puts(const char *s) {
        const char *c = "override puts\n";
        write(1, c, strlen(c)); // stdout

        puts("recursive call?"); //* Ich komme nicht aus Puts heraus

        return 0;
}

Nachschlagewerk

"BINARY HACKS" #HACK 60: Ersetzen Sie die gemeinsam genutzte Bibliothek durch LD_PRELOAD

Beziehung

Alle In / Out-Laufzeitfunktionen verfolgen - Qiita

Referenz

Ändern Sie das Verhalten der gemeinsam genutzten Linux-Bibliothek (.so). Viel Spaß beim Spielen mit LD_PRELOAD: Garbage In Garbage Out Lassen Sie uns die Funktion mit LD_PRELOAD verknüpfen! - Wie man binär geht Dynamische Bibliotheksfunktionen mit LD_PRELOAD | Siguniang's Blog überschreiben

Recommended Posts

Ersetzen Sie printf später durch LD_PRELOAD
11. Ersetzen Sie die Registerkarten durch Leerzeichen
Sonderzeichen durch sed ersetzen / löschen
Ersetzen Sie den Wörterbuchwert durch Python> update ()
Ersetzen Sie alles auf einmal durch sed