Satoooonの物置

雑多に色々と何かをしている

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を使わせて頂いています。

github.com

入手方法は以下の通りです。

# 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を手元でコンパイルする方法です。そちらの方がソースコード付きで詳しく見れるのですが、コンパイルが面倒くさいという方はこちらの方法を試してみてください。

ubuntuglibchttp://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というツールが役に立つとインターネットには書いてあったんですが手元の環境ではうまくいかなかったので別の方法を紹介します。

gdbは二つの方法でデバッグシンボルを探します。

www.zeuthen.desy.de

一つ目は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)

パスをベタ書きしているので自分の環境でしか動かないです。使う人は各自合わせて使ってください。

gist.github.com

同様のツール

github.com

  • ldの入手&パッチ
  • libcにデバッグシンボルを付ける
  • solve.pyのテンプレートの作成

まで全部やってくれるツールです。なんか不安定だったので私は使っていません。

docs.pwntools.com

pwntoolsにもlibcにデバッグシンボルを付ける関数があるようです。こちらも自分の環境では動きませんでした。

多分他にもこのようなツールはあると思います。

終わりに

もっといい方法知っている人は教えて下さい。