🤖

pwn用にlibcを調べる/用意する

2022/12/06に公開

これはCTF tools/tips Advent Calendar 2022の六日目の記事です。

モチベーション

  • pwnチャレンジではlibcを渡されることがよくあるが、そのライブラリのバージョンなどを知りたい
  • libcが渡されなくても、攻撃先を偵察をしてその情報からlibcを推測したい
  • 特定のバージョンのlibcやldを用意して、攻撃先の環境を再現したい。

libcの情報を調べる

libc.so.6 を持っている場合

libc.so.6 は動的ライブラリですが、実は直接実行することができます。例えば、筆者の環境で /lib/libc.so.6 を実行すると以下の出力が得られます。

GNU C Library (GNU libc) stable release version 2.36.
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 12.2.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
Minimum supported kernel: 4.4.0
For bug reporting instructions, please see:
<https://bugs.archlinux.org/>.

一番上の出力に注目すると、バージョン2.36であるという情報を得ることができます。
これは少なくとも GNU Libc では共通しており、バージョン情報などが文字列定数として .rodata に保存されていることを意味します。
なので、この情報を strings 等で調べることでバージョンを特定できます。

$ strings ./libc.so.6 | grep "GNU C"
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9) stable release version 2.31.
Compiled by GNU CC version 9.3.0.

この場合、libcのバージョンは2.31です。

libc.so.6 を持っていない時

問題でバイナリだけ渡され、攻撃中に相手のlibcを特定しなければならない時は、関数のオフセットを調べ libc-database を使うと良いです。

動的ライブラリである libc には、それぞれ関数がどこに配置されるかのオフセットがファイルによって決まっています。
また、それらはASLRが有効であっても、下位3nibble(1.5bytes)は一定です。

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

int main() {
  printf("%p\n", system);
  printf("%x\n", (size_t)system & 0xfff);
}

例えば、これを何度も実行してみると、実際に下位のアドレスは変化しないことがわかります。

sh-5.1$ ./show_system
0x7f2be28c63d0
3d0
sh-5.1$ ./show_system
0x7f59b0bf03d0
3d0
sh-5.1$ ./show_system
0x7f5335bb53d0
3d0

ここでは、0x3d0 という値が固定されています。
先程、これらのアドレスがファイルによって決まると言いましたが、実際にnmコマンドを用いて確認できます。

$ nm -D /lib/libc.so.6 | grep system 
00000000000493d0 T __libc_system@@GLIBC_PRIVATE
00000000001458a0 T svcerr_systemerr@GLIBC_2.2.5
00000000000493d0 W system@@GLIBC_2.2.5

systemの下位アドレスを見ると、やはり0x3d0で同じです。
逆に、これらの値をlibcの特定に利用することができます。

具体的な攻撃シナリオとしては、バイナリ中にGOT領域を読み込めるようにし、バイナリ中で使われているlibcの関数の下位アドレスを盗むなどが挙げられます。

実際に攻撃に使うにあたって、すべてのバージョンのlibc.so.6を用意し手動で検索するのは効率が悪いので、専用のツールを使うことになります。
オンラインツールではhttps://libc.blukat.me/が便利です。

ローカルで動かしたいときはniklasb/libc-databaseが良いでしょう(予めすべてのlibcのダウンロードが必要なので少し面倒)。

これ以外のマイナーlibcを使っている場合や独自ビルドの場合、この方法は使えません。
代わりに、libcが入っている領域を直接読み出すことでバイナリを読み出し、ROPGadgetを作るといった面倒な工程が発生する可能性があります。

libcを用意する

さて、バージョンの特定が済んだら、実際にそのlibcを用意したり、関連する動的ライブラリをまとめてダウンロード・管理したいです。
そういう時はmatrix1001/glibc-all-in-oneを使うと楽です。

このツールはubuntuのlibcを自動でダウンロード・展開してくれるツールで、update_listを使ってリストを更新し、downloadでライブラリをローカルに落とすことができます。例えば、ubuntu20.04の最新のlibcが欲しい場合、

$ ./download 2.31-0ubuntu9.9_amd64

とすることで、2.31-0ubuntu9.9_amd64をダウンロードして、さらにそれを ./libs/2.31-0ubuntu9.9_amd64 の下に展開してくれます。
また、デバッグ情報も自動的にダウンロードし、./libs/2.31-0ubuntu9.9_amd64/.debug に展開されます。

これらのライブラリで対象のバイナリを動かすには

  • patchelfを使う方法
  • ld-linux-x86-64.so.2を使う方法

今回は後者の方法でtarget_binaryを動かしてみます。

$ /path/to/glibc-all-in-one/libs/libc-version/ld-linux-x86-64.so.2 --library-path /path/to/glibc-all-in-one/libs/libc-version ./target_binary

とすれば良いです。

終わりに

誤字・脱字・間違い等は、ご指摘いただければ修正します。
コメントでもTwitter(@iwancof_ptr)でも

Discussion