39
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-03-28

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

39
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
39
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?