【Linux】【book】Binary Hacks の学習記録【SW】

下記の「Binary Hacks――ハッカー秘伝のテクニック100選」を使った学習記録のページです。

11年ほど前に学習しローカルに保存しておいた内容を、知識の整理を兼ねて WEB 上に残していく目的で記載しています。*1
 

Binary Hacks ―ハッカー秘伝のテクニック100選

Binary Hacks ―ハッカー秘伝のテクニック100選

  • 作者: 高林哲,鵜飼文敏,佐藤祐介,浜地慎一郎,首藤一幸
  • 出版社/メーカー: オライリー・ジャパン
  • 発売日: 2006/11/14
  • メディア: 単行本(ソフトカバー)
  • 購入: 23人 クリック: 383回
  • この商品を含むブログ (223件) を見る


1章 イントロダクション
 1 Binary Hack入門
 2 Binary Hack用語の基礎知識
 3 fileでファイルの種類をチェックする
 4 odでバイナリファイルをダンプする

2章 オブジェクトファイルHack
 5 ELF入門
 6 静的ライブラリと共有ライブラリ
 7 lddで共有ライブラリの依存関係をチェックする
 8 readelfでELFファイルの情報を表示する
 9 objdumpでオブジェクトファイルをダンプする
 10 objdumpでオブジェクトファイルを逆アセンブルする
 11 objcopyで実行ファイルにデータを埋め込む
 12 nmでオブジェクトファイルに含まれるシンボルをチェックする
 13 stringsでバイナリファイルから文字列を抽出する
 14 c++filtでC++のシンボルをデマングルする
 15 addr2lineでアドレスからファイル名と行番号を取得する
 16 stripでオブジェクトファイルからシンボルを削除する
 17 arで静的ライブラリを操作する
 18 CとC++のプログラムをリンクするときの注意点
 19 リンク時のシンボルの衝突に注意する
 20 GNU/Linuxの共有ライブラリを作るときPICでコンパイルするのはなぜか
 21 statifierで動的リンクの実行ファイルを擬似的に静的リンクにする

3章 GNUプログラミングHack
 22 GCCのGNU拡張入門
 23 GCCでインラインアセンブラを使う
 24 GCCのビルトイン関数による最適化を活用する
 25 glibcを使わないでHello Worldを書く
 26 TLS(スレッドローカルストレージ)を使う
 27 glibcでロードするライブラリをシステムに応じて切り替える
 28 リンクされているライブラリによってプログラムの動作を変える
 29 ライブラリの外に公開するシンボルを制限する
 30 ライブラリの外に公開するシンボルにバージョンをつけて動作を制御する
 31 main()の前に関数を呼ぶ
 32 GCCが生成したコードによる実行時コード生成
 33 スタックに置かれたコードの実行を許可/禁止する
 34 ヒープ上に置いたコードを実行する
 35 PIE(位置独立実行形式)を作成する
 36 C++でsynchronized methodを書く
 37 C++でシングルトンを生成する
 38 g++の例外処理を理解する(throw編)
 39 g++の例外処理を理解する(SjLj編)
 40 g++の例外処理を理解する(DWARF2編)
 41 g++ 例外処理のコストを理解する

4章 セキュアプログラミングHack
 42 GCCセキュアプログラミング入門
 43 -ftrapvで整数演算のオーバーフローを検出する
 44 Mudflap でバッファオーバーフローを検出する
 45 -D_FORTIFY_SOURCEでバッファオーバーフローを検出する
 46 -fstack-protectorでスタックを保護する
 47 bitmaskする定数は符号なしにする
 48 大きすぎるシフトに注意
 49 64ビット環境で0とNULLの違いに気を付ける
 50 POSIXのスレッドセーフな関数
 51 シグナルハンドラを安全に書く方法
 52 sigwaitで非同期シグナルを同期的に処理する
 53 sigsafeでシグナル処理を安全にする
 54 Valgrindでメモリリークを検出する
 55 Valgrindでメモリの不正アクセスを検出する
 56 Helgrindでマルチスレッドプログラムのバグを検出する
 57 fakerootで擬似的なroot権限でプロセスを実行する

5章 ランタイムHack
 58 プログラムがmain()にたどりつくまで
 59 システムコールはどのように呼び出されるか
 60 LD_PRELOADで共有ライブラリを差し換える
 61 LD_PRELOAD で既存の関数をラップする
 62 dlopenで実行時に動的リンクする
 63 Cでバックトレースを表示する
 64 実行中のプロセスのパス名をチェックする
 65 ロードしている共有ライブラリをチェックする
 66 プロセスや動的ライブラリがマップされているメモリを把握する
 67 libbfdでシンボルの一覧を取得する
 68 C++ のシンボルを実行時にデマングルする
 69 ffcallでシグネチャを動的に決めて関数を呼ぶ
 70 libdwarfでデバッグ情報を取得する
 71 dumperで構造体のデータを見やすくダンプする
 72 オブジェクトファイルを自力でロードする
 73 libunwindでコールチェインを制御する
 74 GNU lightningでポータブルに実行時コード生成する
 75 スタック領域のアドレスを取得する
 76 sigaltstackでスタックオーバーフローに対処する
 77 関数へのenter/exitをフックする
 78 シグナルハンドラからプログラムの文脈を書き換える
 79 プログラムカウンタの値を取得する
 80 自己書き換えでプログラムの動作を変える
 81 SIGSEGVを使ってアドレスの有効性を確認する
 82 straceでシステムコールをトレースする
 83 ltraceで共有ライブラリの関数呼び出しをトレースする
 84 JockeyでLinuxのプログラムの実行を記録、再生する
 85 prelinkでプログラムの起動を高速化する
 86 livepatchで実行中のプロセスにパッチをあてる

6章 プロファイラ・デバッガHack
 87 gprofでプロファイルを調べる
 88 sysprofでお手軽にシステムプロファイルを調べる
 89 oprofileで詳細なシステムプロファイルを得る
 90 GDBで実行中のプロセスを操る
 91 ハードウェアのデバッグ機能を使う
 92 Cのプログラムの中でブレークポイントを設定する

7章 その他のHack
 93 Boehm GCの仕組み
 94 プロセッサのメモリオーダリングに注意
 95 Portable Coroutine Library(PCL)で軽量な並行処理を行う
 96 CPUのクロック数をカウントする
 97 浮動小数点数のビット列表現
 98 x86が持つ浮動小数点演算命令の特殊性
 99 結果が無限大やNaNになる演算でシグナルを発生させる
 100 文献案内

 
 

4

 
16進数表記で1バイトずつ(x1)表示する。オフセットは16進数表記(-Ax)

% od -t x1 -Ax $target | head -n 5

 
さらに ASCII 文字表示も加える(-t に z を付加)

% od -t x1z -Ax $target | head -n 5

 
-v でダンプを省略しない(デフォルトでは同一行が続く場合,省略する)

% od -tx1 -Ax -v $target | sed -n '45,49p'

 
--strings で文字列のダンプ機能(stringsコマンドを使う方が良い)

% od -Ax --strings $target | head -n 10

 
特定の位置(例:0x34バイト目)から表示

% od -t x1z -Ax -j 0x0034 $target | head -n1

 

62

次の挙動をするプログラム

1. dlopen.c の実行ファイル(dlsay)が実行時に hello.so をロードする
2. hello.so が提供する echo_hello(char *) を呼び出す
3. 2 のときに引数も渡す

dlopen1.c

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

void
invoke_dlopen(int argc, char * argv[])
{
    void * handle = NULL;
    char * error  = NULL;
    char *(*msg)();

    /* RTLD_LAZY ... ロード時にシンボルの値の解決をさぼる(LAZY) */
    handle = dlopen(argv[1], RTLD_LAZY);
    if(handle == NULL)
    {
        fprintf(stderr, "load error %s: %s\n", argv[1], dlerror());
        exit(1);
    }

    dlerror(); /* clear error */
    msg = (char *(*)()) dlsym(handle, argv[2]); /* 呼び出し側でキャストが必要 */
    error = dlerror();
    if(error)
    {
        fprintf(stderr, "dlsym error %s: %s\n", argv[2], error);
        exit(2);
    }

    printf("%s\n", (*msg)(argc > 3 ? argv[3] : NULL));
    dlclose(handle);

    exit(0);
}

int
main(int argc, char * argv[])
{
    invoke_dlopen(argc, argv);

    return 0;
}

 
hello.c

#include <stdio.h>

char *
echo_hello(char * arg)
{
    static char buf[4096];

    snprintf(buf, sizeof(buf), "Hello, %s", arg);

    return buf;
}

 
Makefile

all: dlopen1.c hello.c
	gcc -Wall -o dlsay dlopen1.c -ldl
	gcc -shared -fPIC -o hello.so hello.c
	./dlsay ./hello.so echo_hello hoge

clean:
	rm -f *.o *.so *~ dlsay

RTLD_LAZY と RTLD_NOW

dlopen(3)で指定している RTLD_LAZY というのは,ロード時にシンボルの値の解決をさぼる(lazy)という意味。
RTLD_LAYZ の代わりに RTLD_NOW を指定すると,ロード時にシンボルの値の解決をする。
もしシンボルの値の解決に失敗すると,dlopen(3) 自体が失敗となる。
RTLD_LAZY の場合は解決できないシンボルがあっても dlsym(3) で調べるまで解決しないので,dlopen(3)はエラーとはならない。
なお,プログラムでは RTLD_LAZY となっていても,環境変数に LD_BIND_NOW に何か文字列を設定しておくと,RTLD_NOW と同じ動作をするようになる。

RTLD_LOCAL と RTLD_GLOBAL

RTLD_LAZY もしくは,RTLD_NOW に対して RTLD_LOCAL や RTLD_GLOBAL のどちらかを設定することが出来る。
RTLD_GLOBAL にすると dlopen(3) で読み込んだ共有オブジェクトにあるシンボルが他の共有オブジェクトでのシンボル解決にも自動的に使われるようになる。
デフォルトは RTLD_LOCAL で,dlopen(3) で読み込んだ共有オブジェクトのシンボルは他には影響しない。

dlopen(3) で返されたハンドルを使うことで,その共有オブジェクトに含まれるシンボルの値を得ることが出来る。
そのために使うのが,dlsym(3)で,dlsym に共有オブジェクトのハンドルとシンボル名を与えると,その共有オブジェクトの中で定義されているシンボルの値を返す。
シンボルが見つからない場合は NULL を返す。(エラーの内容は dlerror() で確認出来る)

dlsym(3) で取得出来るのはシンボルの値だけなので,そのシンボルがどのような変数,関数なのかは呼び出し側が適当にキャストする必要がある。

dlclose(3)を呼び出すと,そのハンドルに対応した共有オブジェクトのマッピングが外される。
同じ共有オブジェクトが複数 dlopen(3) されている時は,dlopen(3) された回数 dlclose(3) されるまで実際にはマッピングが残ったままになる。

 

66

 
66 プロセスや動的ライブラリがマップされているメモリを把握する

プロセスが起動すると,その実行バイナリファイルや動的ローダによってロードされた共有ライブラリファイルが仮想メモリ空間にマッピングされる。
(どのようなファイルがマッピングされているかは,#65 の /proc/$$/mapsを確認することで確認出来るが) 仮想メモリの範囲がどのようになっているかは, 下記のように pmap コマンドで確認することが出来る。

以下、sleep コマンドで pmap の挙動を確認する。

1. sleep コマンドを実行する

プロセスID hal 25619 である。

% /bin/sleep 10000 &
[4] 25619
% ps auxw | grep sleep
Neko  25619  0.0  0.0   4748   500 pts/1    S    03:13   0:00 /bin/sleep 10000
Neko  25621  0.0  0.1   5132   840 pts/1    S+   03:13   0:00 grep -i sleep

 
2. pmap を実行する

% pmap 25619

25619:   /bin/sleep 10000
00110000      4K r-x--    [ anon ]      ★ 4KBアライメント = ページサイズが4KBと判明
00111000   1356K r-x--  /lib/libc-2.7.so
00264000      8K r-x--  /lib/libc-2.7.so
00266000      4K rwx--  /lib/libc-2.7.so
00267000     12K rwx--    [ anon ]
002c4000    108K r-x--  /lib/ld-2.7.so
002df000      4K r-x--  /lib/ld-2.7.so
002e0000      4K rwx--  /lib/ld-2.7.so
08048000     16K r-x--  /bin/sleep      ★ 下記 3 の .interp 〜 .plt セクションに該当する(読み+実行専用)
0804c000      8K rw---  /bin/sleep      ★ 下記 3 の .text 〜 に該当する(読み+書き領域(多分グローバル変数領域))
08262000    132K rw---    [ anon ]      ★ 恐らく,Heap領域であり,高位アドレスに向かって伸びる
b7cea000    952K r----  /usr/lib/locale/locale-archive
b7dd8000   2048K r----  /usr/lib/locale/locale-archive
b7fd8000      4K rw---    [ anon ]
b7fec000      4K rw---    [ anon ]
bf88b000     84K rw---    [ stack ]     ★ スタック領域。低位アドレスに向かって伸びる
 total     4748K

 

3. readelf を実行してセクションを確認する

% readelf -S /bin/sleep 

There are 32 section headers, starting at offset 0x48ac:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0 0
  [ 1] .interp           PROGBITS        08048134 000134 000013 00   A  0   0 1
  [ 2] .note.ABI-tag     NOTE            08048148 000148 000020 00   A  0   0 4
  [ 3] .note.gnu.build-i NOTE            08048168 000168 000024 00   A  0   0 4
  [ 4] .gnu.hash         GNU_HASH        0804818c 00018c 000040 04   A  5   0 4
  [ 5] .dynsym           DYNSYM          080481cc 0001cc 000310 10   A 28   1 4
  [ 6] .gnu.liblist      GNU_LIBLIST     080484dc 0004dc 000028 14   A 28   0 4
  [ 7] .gnu.conflict     RELA            08048504 000504 0000d8 0c   A  5   0 4
  [ 8] .gnu.version      VERSYM          08048722 000722 000062 02   A  5   0 2
  [ 9] .gnu.version_r    VERNEED         08048784 000784 000080 00   A 28   1 4
  [10] .rel.dyn          REL             08048804 000804 000058 08   A  5   0 4
  [11] .rel.plt          REL             0804885c 00085c 000128 08   A  5  13 4
  [12] .init             PROGBITS        08048984 000984 000017 00  AX  0   0 4
  [13] .plt              PROGBITS        0804899c 00099c 000260 04  AX  0   0 4
  [14] .text             PROGBITS        08048c00 000c00 001fd8 00  AX  0   0 16
  [15] .fini             PROGBITS        0804abd8 002bd8 00001c 00  AX  0   0 4
  [16] .rodata           PROGBITS        0804ac00 002c00 000900 00   A  0   0 32
  [17] .eh_frame_hdr     PROGBITS        0804b500 003500 000164 00   A  0   0 4
  [18] .eh_frame         PROGBITS        0804b664 003664 000574 00   A  0   0 4
  [19] .ctors            PROGBITS        0804cbd8 003bd8 000008 00  WA  0   0 4
  [20] .dtors            PROGBITS        0804cbe0 003be0 000008 00  WA  0   0 4
  [21] .jcr              PROGBITS        0804cbe8 003be8 000004 00  WA  0   0 4
  [22] .data.rel.ro      PROGBITS        0804cc00 003c00 000060 00  WA  0   0 32
  [23] .dynamic          DYNAMIC         0804cc60 003c60 0000c8 08  WA 28   0 4
  [24] .got              PROGBITS        0804cd28 003d28 000030 04  WA  0   0 4
  [25] .got.plt          PROGBITS        0804cd58 003d58 0000a0 04  WA  0   0 4
  [26] .data             PROGBITS        0804cdf8 003df8 000018 00  WA  0   0 4
  [27] .bss              PROGBITS        0804ce20 003e20 000164 00  WA  0   0 32
  [28] .dynstr           STRTAB          0804cf84 003f84 000258 00   A  0   0 1
  [29] .gnu_debuglink    PROGBITS        00000000 0041dc 000010 00      0   0 4
  [30] .gnu.prelink_undo PROGBITS        00000000 0041ec 000594 01      0   0 4
  [31] .shstrtab         STRTAB          00000000 004780 00012c 00      0   0 1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

 

*1:低階層の知識とはいえ、さすがに10年も経つと本書の内容も現在の環境と乖離しつつある箇所が現れ始めている。可能なら最近の動向に合せて刷新してもらえるとありがたい