libc.soを差し替えてプログラムを動かす（のは無理そう）

libc.soはld-linux-x86-64.soの構造体を参照している。関数ではなく構造体なので、メンバの配置が一致している必要があり、異なるオプションでビルドされていると、誤ったメンバを参照してしまう。これをどうにかするのは難しそう。

libc.soとld-linux-x86-64.soの整合性の問題なので、libcと一緒にld-linux-x86-64.soが配布されているならば、動かすことができる。追記を参照。

# 詳細

CTFのうち、サーバーで動作している攻撃対象プログラムの脆弱性を突いてフラグを盗み出せという問題（PwnやExploitと呼ばれる）では、攻撃対象プログラムとともに、サーバーで使われてるlibc.soが提供されることが多い。

Pwnでは、バッファオーバーフローなどの脆弱性を経由してシェルを動かし、サーバー内のflag.txtなどのファイルを読む。攻撃対象のプログラムのコードだけではシェルを実行することができない場合には、libcのsystem関数などを使ってシェルを実行する。systemなどのアドレスはlibcによって異なるので、サーバーで実際に使われているlibcが必要になる。

単にsystemを呼び出すだけならば、systemのアドレスが分かれば良いのだが、[ASLR](https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9%E7%A9%BA%E9%96%93%E9%85%8D%E7%BD%AE%E3%81%AE%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E5%8C%96)によって攻撃をする度にアドレスが変わるので、実際には、

1. 攻撃対象プログラムが使用しているprintfなどの関数のアドレスを得る
2. ASLRでは関数の相対位置はずれないので、printfからsystemのアドレスを計算する
3. systemを呼び出す

という流れになる。また、system関数の引数に渡したい`/bin/sh`という文字列も、攻撃中に与えることが無理ならば、libcの中にあるものを使うことになる。攻撃するスクリプトをいきなり完成させることは難しいので、与えられたプログラムを手元で動かしてデバッグする。このときは、プログラムは手元のlibcで動作するから、リモートのサーバーを攻撃するときには各関数や文字列のアドレスを調節することになる。この作業が面倒なので、手元のlibcを与えられたlibcに差し替えて、プログラムを動かせると楽。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">これをやりたいのだけど、何か方法は無いのだろうか。<br>&quot;与えられたlibcで手元で動かしたかったのでいろいろ試行錯誤していたが結局だめだった&quot;<br>SECCON 2016 online のwriteup - 忖度<a href="https://t.co/3uxMBBlYKt">https://t.co/3uxMBBlYKt</a></p>&mdash; kusanoさん@がんばらない (@kusano_k) <a href="https://twitter.com/kusano_k/status/808362465125797888">2016年12月12日</a></blockquote>

この前の[SECCON 2016 オンライン予選](http://qiita.com/kusano_k/items/047595d34142713b4dcf)のjmperで試してみる。x64。この問題はsetjmpのjmp_bufをヒープオーバーフローで書き換える必要があるらしい。単にsystem関数を呼び出すよりも、libcへの依存が大きそうなので、特に与えられたlibcでデバッグしたい（setjmpやmallocの動作はそうそう変わらないと思うけど）。

とりあえず、バイナリエディタでjmper中の`libc.so.6`を`./bc.so.6`に書き換え、与えられたlibcを`bc.so.6`にリネームして同じディレクトリに置いた。

![image](https://qiita-image-store.s3.amazonaws.com/0/25276/6f20900b-8dd4-ad8a-b526-a11810a0d01c.png)

わざわざこんなことをしなくても、`LD_PRELOAD`を使うという手もあるらしい。

<blockquote class="twitter-tweet" data-conversation="none" data-lang="ja"><p lang="ja" dir="ltr"><a href="https://twitter.com/kusano_k">@kusano_k</a> 普通にLD_PRELOADじゃだめですかね</p>&mdash; れっくす (@xrekkusu) <a href="https://twitter.com/xrekkusu/status/808363958746824704">2016年12月12日</a></blockquote>

動かしてみたところ、Segmentation fault。残念。

```shell-session
kusano@ubuntu:~/ctf/seccon2016q$ ./jmper_bc
Segmentation fault (コアダンプ)
```

gdbで動かして、どこで落ちたかを見てみると、\_\_libc_start_main。

```
[----------------------------------registers-----------------------------------]
RAX: 0xec8148ff8941d589
RBX: 0x0
RCX: 0x0
RDX: 0x470
RSI: 0x7fffffffe5d8 --> 0x7fffffffe7f7 ("/home/kusano/ctf/seccon2016q/jmper_bc")
RDI: 0x7ffff7ffe5d8 --> 0x7ffff7ffe168 --> 0x0
RBP: 0xf7de9950
RSP: 0x7fffffffe500 --> 0x7fffffffe5d8 --> 0x7fffffffe7f7 ("/home/kusano/ctf/seccon2016q/jmper_bc")
RIP: 0x7ffff7a33fdf (<__libc_start_main+399>:   call   rax)
R8 : 0x7ffff7dd1e80 --> 0x0
R9 : 0x7ffff7de78e0 (push   rbp)
R10: 0xd ('\r')
R11: 0x1
R12: 0x0
R13: 0x7ffff7de70f0 (push   r15)
R14: 0x7ffff7ffe168 --> 0x0
R15: 0x0
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7ffff7a33fd3 <__libc_start_main+387>:      add    rdx,0x47
   0x7ffff7a33fd7 <__libc_start_main+391>:      shl    rdx,0x4
   0x7ffff7a33fdb <__libc_start_main+395>:      lea    rdi,[r14+rdx*1]
=> 0x7ffff7a33fdf <__libc_start_main+399>:      call   rax
   0x7ffff7a33fe1 <__libc_start_main+401>:      add    r12d,0x1
   0x7ffff7a33fe5 <__libc_start_main+405>:      mov    r13,QWORD PTR [r13+0x40]
   0x7ffff7a33fe9 <__libc_start_main+409>:      cmp    ebp,r12d
   0x7ffff7a33fec <__libc_start_main+412>:      jne    0x7ffff7a33fc7 <__libc_start_main+375>
```

めちゅくちゃなアドレスをcallしようとしている。対応するlibcのソースコードはこの部分。

```c
 248       struct audit_ifaces *afct = GLRO(dl_audit);
 249       struct link_map *head = GL(dl_ns)[LM_ID_BASE]._ns_loaded;
 250       for (unsigned int cnt = 0; cnt < GLRO(dl_naudit); ++cnt)
 251         {
 252           if (afct->preinit != NULL)
 253             afct->preinit (&head->l_audit[cnt].cookie);
 254 
 255           afct = afct->next;
 256         }
```

[libc-start.c](https://sourceware.org/git/?p=glibc.git;a=blob;f=csu/libc-start.c;h=99c040ab971500353147fc7ac1037182a3a3eeec;hb=ffb8455c439cda679d4be05b6ebcbaa375334af4#l253)

GLROマクロなどの定義は[ldsodefs.h](https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/generic/ldsodefs.h;h=288f5fe32e7a75530777499a4ea55aeaa75e4023;hb=ffb8455c439cda679d4be05b6ebcbaa375334af4#l600)にあって、`afct`は`extern struct rtld_global_ro _rtld_global_ro`のメンバを指すようになっている。

libcのインポートテーブルなどを確認すると、ld-linux-x86-64.soを参照している。`#ifdef`マクロが大量にあって、メンバの有無が変わっているので、どれかが異なっていると誤ったメンバを参照してしまう。

```shell-session
kusano@ubuntu:~/ctf/seccon2016q$ ldd bc.so.6
        /lib64/ld-linux-x86-64.so.2 (0x0000559a127d8000)
        linux-vdso.so.1 =>  (0x00007fff52dba000)
kusano@ubuntu:~/ctf/seccon2016q$ readelf -s bc.so.6 | grep _rtld_global_ro
     6: 0000000000000000     0 OBJECT  GLOBAL DEFAULT  UND _rtld_global_ro@GLIBC_PRIVATE (24)
kusano@ubuntu:~/ctf/seccon2016q$ readelf -s /lib64/ld-linux-x86-64.so.2 | grep _rtld_global_ro
    26: 0000000000225ca0   368 OBJECT  GLOBAL DEFAULT   17 _rtld_global_ro@@GLIBC_PRIVATE
```

gdbでafctの値を確認してみても、ld-2.23.soのメモリになっている。

```
[-------------------------------------code-------------------------------------]
   0x7ffff7a33fa8 <__libc_start_main+344>:      call   QWORD PTR [rdx+0xd0]
   0x7ffff7a33fae <__libc_start_main+350>:      jmp    0x7ffff7a33ef3 <__libc_start_main+163>
   0x7ffff7a33fb3 <__libc_start_main+355>:      mov    r13,QWORD PTR [rax+0x120]
=> 0x7ffff7a33fba <__libc_start_main+362>:      mov    rax,QWORD PTR [rip+0x39be27]        # 0x7ffff7dcfde8
   0x7ffff7a33fc1 <__libc_start_main+369>:      xor    r12d,r12d
   0x7ffff7a33fc4 <__libc_start_main+372>:      mov    r14,QWORD PTR [rax]
   0x7ffff7a33fc7 <__libc_start_main+375>:      mov    rax,QWORD PTR [r13+0x18]
   0x7ffff7a33fcb <__libc_start_main+379>:      test   rax,rax
```

```
gdb-peda$ i proc map
process 30812
Mapped address spaces:

          Start Addr           End Addr       Size     Offset objfile
            0x400000           0x401000     0x1000        0x0 /home/kusano/ctf/seccon2016q/jmper_bc
            0x601000           0x602000     0x1000     0x1000 /home/kusano/ctf/seccon2016q/jmper_bc
            0x602000           0x603000     0x1000     0x2000 /home/kusano/ctf/seccon2016q/jmper_bc
      0x7ffff7a12000     0x7ffff7bcc000   0x1ba000        0x0 /home/kusano/ctf/seccon2016q/bc.so.6
      0x7ffff7bcc000     0x7ffff7dcc000   0x200000   0x1ba000 /home/kusano/ctf/seccon2016q/bc.so.6
      0x7ffff7dcc000     0x7ffff7dd0000     0x4000   0x1ba000 /home/kusano/ctf/seccon2016q/bc.so.6
      0x7ffff7dd0000     0x7ffff7dd2000     0x2000   0x1be000 /home/kusano/ctf/seccon2016q/bc.so.6
      0x7ffff7dd2000     0x7ffff7dd7000     0x5000        0x0
      0x7ffff7dd7000     0x7ffff7dfd000    0x26000        0x0 /lib/x86_64-linux-gnu/ld-2.23.so
      0x7ffff7ff3000     0x7ffff7ff8000     0x5000        0x0
      0x7ffff7ff8000     0x7ffff7ffa000     0x2000        0x0 [vvar]
      0x7ffff7ffa000     0x7ffff7ffc000     0x2000        0x0 [vdso]
      0x7ffff7ffc000     0x7ffff7ffd000     0x1000    0x25000 /lib/x86_64-linux-gnu/ld-2.23.so
      0x7ffff7ffd000     0x7ffff7ffe000     0x1000    0x26000 /lib/x86_64-linux-gnu/ld-2.23.so
      0x7ffff7ffe000     0x7ffff7fff000     0x1000        0x0
      0x7ffffffde000     0x7ffffffff000    0x21000        0x0 [stack]
  0xffffffffff600000 0xffffffffff601000     0x1000        0x0 [vsyscall]
```

ビルドオプションごとのld-linux-x86-64.soを用意しておくという手もあるかもしれないが、そんなことをするよりは、[作問者](http://shift-crops.hatenablog.com/entry/2016/12/11/155902)が使っている[ライブラリ](https://github.com/shift-crops/sc_pwn/)みたいなものを作っておくほうが良さそうである。

Win32 APIのcbSizeのように、構造体の中身が異なっても動く仕組みがあれば良いのに……。

# 追記

ld-linux-x86-64.soを特に使うわけでもないのに、libc.soと一緒に配布している問題をちらほら見る。例えばこの問題。

https://github.com/wani-hackase/wanictf21spring-writeup/tree/main/pwn/08-gacha-breaker

同じ環境のld-linux-x86-64.soとlibc.soがあれば、動かすことができる。そもそもld-linux-x86-64.soが何かというとプログラムをロードするためのプログラムである。配布されたld-linux-x86-64.soで問題プログラムをロードすれば良い。

```shell-session
$ LD_PRELOAD=./libc-2.31.so ./ld-2.31.so ./pwn08
Coins : 1500
Gacha stack : 0
1. do_gacha!
2. view_history
3. clear_history
4. ceiling
9. exit
```

ld-linux-x86-64.soはELFファイルに絶対パスが埋め込まれている。上のように起動するとデバッガなどで扱いづらいなら、書き換えれば良い。それを行ってくれる`patchelf`というコマンドがある。また、`patchelf`では共有ライブラリを探すパスも書き換えることができる。

```shell-session
$ cp pwn08 pwn08_2
$ cp libc-2.31.so libc.so.6
$ patchelf --set-rpath . --set-interpreter ./ld-2.31.so ./pwn08_2
$ ldd pwn08_2
        linux-vdso.so.1 (0x00007ffd31f64000)
        libc.so.6 => ./libc.so.6 (0x00007ff73398a000)
        ./ld-2.31.so => /lib64/ld-linux-x86-64.so.2 (0x00007ff73395d000)
$ ./pwn08_2
Coins : 1500
Gacha stack : 0
1. do_gacha!
2. view_history
3. clear_history
4. ceiling
9. exit
```

カレントディレクトリが変わっても動くようにしたいならば、相対パスではなく絶対パスで指定しましょう。

この記事を最初に書いたときには使っていなかった[pwntools](https://github.com/Gallopsled/pwntools)を使うようになった。各種シンボルのアドレスはpwntoolsの関数で取れるから、ローカルとリモートでpwntoolsに読み込ませるlibcを変えるだけ。この記事のようなシチュエーションだとそもそもあまり困っていない。

今でも差し替えたいのはヒープ問題を解くときである。`malloc`周りの挙動はlibcのバージョン間で大きく変わる。ヒープ問では[Pwngdb](https://github.com/scwuaptx/Pwngdb)が便利で、ヒープの状況が見られる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/276575f5-7464-b77d-991a-a30f92907386.png)

で、libcを差し替えると、差し替えたlibcのデバッグ情報が無くて`main_arena`のアドレスが取れないため、この機能が動かない。

```
gdb-peda$ parseheap
Can not get libc version
Cannot get main_arena's symbol address. Make sure you install libc debug file (libc6-dbg & libc6-dbg:i386 for debian package).
can't find heap info
```

libcのバージョンごとにVMを用意しておくのが一番かもしれない。