Help us understand the problem. What is going on with this article?

LD_PRELOADでprintfを後から差し替える

More than 3 years have passed since last update.

TL;DR

ビルド済みのバイナリの関数を後から差し替える方法のまとめ

※本記事の方法は共有ライブラリの関数のみ有効

差し替え対象コード

差し替え対象のサンプルとしてptintfとfprintfのそれぞれ可変長引数ありとなしを用意する。

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

ビルドと実行結果は以下になる。

ビルドと実行結果
$ gcc print.c 
$ ./a.out 
printf no args
printf 1
fprintf no args
fprintf 1

差し替えコード

次にLD_PRELOADにて差し替えを行う用のコードを用意する。
可変長引数の有無にかかわらず基本的には差し替えたい関数の定義を持ってきて再定義すれば良い。

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

これをLD_PRELOADで指定して実行すれば、定義を後から差し替えることができる。

ビルドと実行結果
$ gcc -shared -fPIC override.c -o liboverride.so
$ LD_PRELOAD=./liboverride.so ./a.out
override puts
override printf
override fwrite
override fprintf

ただし、GCCでは printffprintf で可変長引数の要素が0の場合、コンパイル時にそれぞれ putsfwrite に置き替えられてしまう。(GCCは-O0でも置き替えは有効、clang 3.6では-O0ではprintfのまま置き換えなし、-O1以上で置き換え)

$ 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 が 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 が 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

GCCでこの置き替えをもし無効化したい場合は -fno-builtin-printf -fno-builtin-fprintf をつけてビルドすれば良い

差し替えを無効化したビルドと実行結果
$ gcc -fno-builtin-printf -fno-builtin-fprintf print.c                                                                                                                
$ LD_PRELOAD=./liboverride.so ./a.out                                                                                                                                 
override printf
override printf
override fprintf
override fprintf

注意点

当然といえば当然だけれども、差し替えた関数内で再度同じ関数を呼ぶと再帰呼び出しになり抜けられなくなる。
例えばprintfを差し替える場合は差し替え処理で使用したライブラリ内で使われていたりもするため、別途気をつける必要がある。

再帰呼び出し
int puts(const char *s) {
        const char *c = "override puts\n";
        write(1, c, strlen(c)); // stdout

        puts("recursive call?"); // ※putsから抜けられなくなる

        return 0;
}

参考書籍

『BINARY HACKS』 #HACK 60 : LD_PRELOADで共有ライブラリを差し替える

関連

実行時の関数全ての in/out をトレースする - Qiita

参考

Linux 共有ライブラリ(.so)の動作を変える。LD_PRELOADで楽しく遊ぶ:Garbage In Garbage Out
LD_PRELOADで関数フックしてみよう! - バイナリの歩き方
LD_PRELOADで動的ライブラリ関数を上書きする | Siguniang's Blog

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away