pwn用にlibcを調べる/用意する
これは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