libcにデバッグシンボルを付ける方法と自動化
あの日見たmain_arenaのアドレスを僕達はまだ知らない
某日某CTF:
「残るpwn問はheap問のみ、頑張って倒すか。まずは配布されたlibcを確認して……」
❯ ./libc.so.6 GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3) stable release version 2.35. ...
「げ、libc 2.35か。だいぶ新しいバージョンだけどまぁ何とかなるだろ。ld持ってきてpatchelfして…よし、gdbで見てみるか。適当に操作してから、heapコマンドで確認を……」
pwndbg> heap heap: This command only works with libc debug symbols. They can probably be installed via the package manager of your choice. See also: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html E.g. on Ubuntu/Debian you might need to do the following steps (for 64-bit and 32-bit binaries): sudo apt-get install libc6-dbg sudo dpkg --add-architecture i386 sudo apt-get install libc-dbg:i386
「ん?」
pwndbg> bin bins: This command only works with libc debug symbols. ... (同文)
「待てよ?」
pwndbg> print main_arena No symbol table is loaded. Use the "file" command.
「デバッグシンボルが……入っていない……!!!」
というわけで、libc6-dbgパッケージに対応していないlibcにデバッグシンボルを付けていきます。
バイナリをパッチする
(デバッグシンボルとは関係ないんですけど、ついでに紹介します)
配布されたlibcでバイナリを動かすには、まず実行時に動的ライブラリの解決などをするldを入手する必要があります。
簡単に入手するためのツールは色々あるらしいんですが、私はlibc-databaseを使わせて頂いています。
入手方法は以下の通りです。
# ubuntuのlibcと各libcのメタデータをダウンロード ./get ubuntu # libcのバージョンを特定 ./identify /path/to/libc # libcをダウンロード ./download {libc_version} # この中にldが入っているのでコピー cp ./libs/{libc_version}/ld-linux-x86-64.so.2 /path/to/chall_dir
ldが手に入ったら、patchelfというツールを使うとバイナリが参照しているライブラリの参照を上書きすることができます。
patchelf --set-interpreter /path/to/ld --replace-needed libc.so.6 /path/to/libc /path/to/chall_binary
これで配布されたlibcでバイナリを動かすことができます。
glibcのデバッグシンボルを入手する
一番わかりやすいのはglibcを手元でコンパイルする方法です。そちらの方がソースコード付きで詳しく見れるのですが、コンパイルが面倒くさいという方はこちらの方法を試してみてください。
ubuntuのglibcは http://archive.ubuntu.com/ubuntu/pool/main/g/glibc/ で配布されています。その中にあるlibc6-dbgで始まるdebの中に、libcのデバッグシンボルが入っています。
冒頭のケースだとlibc6-dbg_2.35-0ubuntu3_amd64.deb
にあたります。これをダウンロード&展開(ar vx
)してdata.tar.zstを展開すると、./usr/lib/debug/.build-id/*/*.debug
というファイルがずらりと出てくると思います。これがデバッグシンボルです。
.build-id/*/*
の部分はコンパイル時に付けられるBuildID
に対応しています。libcのBuildIDはfile
コマンドで確認できると思うので、対応するデバッグシンボルを見つけましょう。
libcにデバッグシンボルを付ける
これでlibcに対応するデバッグシンボルを入手できました。このデバッグシンボルをlibcに付けるのにeu-unstrip
というツールが役に立つとインターネットには書いてあったんですが手元の環境ではうまくいかなかったので別の方法を紹介します。
一つ目はBuildIDで、debug-file-directoryに設定されているパスから対応するデバッグシンボルを探します。しかし、これだといちいちdebug-file-directoryを指定しないといけないし少し面倒です。
二つ目はdebug linkで、デバッグシンボルの場所をバイナリ中で指定する方法です。今回はこれを使ってみます。
objcopy --remove-section=.gnu_debuglink /path/to/libc objcopy --add-gnu-debuglink=/path/to/debug_symbol /path/to/libc
このようにするとgdbがデバッグシンボルを見つけることができます。
pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x55555555a000 Size: 0x291 Free chunk (unsortedbin) | PREV_INUSE Addr: 0x55555555a290 Size: 0x511 fd: 0x7ffff7fadce0 bk: 0x7ffff7fadce0 Allocated chunk Addr: 0x55555555a7a0 Size: 0x510 Top chunk | PREV_INUSE Addr: 0x55555555acb0 Size: 0x20351 pwndbg> p main_arena $2 = { mutex = 0, flags = 0, have_fastchunks = 0, fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, top = 0x55555555acb0, last_remainder = 0x0, bins = {...}, binmap = {0, 0, 0, 0}, next = 0x7ffff7fadc80 <main_arena>, next_free = 0x0, attached_threads = 1, system_mem = 135168, max_system_mem = 135168 }
追記(6/13): これも面倒くさい方は手に入れたdeb内にある./usr/lib/debug/.build-id/*/*
をgdbがデフォルトで調べる/usr/lib/debug/.build-id/
に移動するだけでも動くと思います。
自動化
雑にスクリプトを書いてみました。(MIT LICENCE)
パスをベタ書きしているので自分の環境でしか動かないです。使う人は各自合わせて使ってください。
同様のツール
- ldの入手&パッチ
- libcにデバッグシンボルを付ける
- solve.pyのテンプレートの作成
まで全部やってくれるツールです。なんか不安定だったので私は使っていません。
pwntoolsにもlibcにデバッグシンボルを付ける関数があるようです。こちらも自分の環境では動きませんでした。
多分他にもこのようなツールはあると思います。
終わりに
もっといい方法知っている人は教えて下さい。