CTF
glibc
pwn

argv[0] leak と glibc の対策

32C3 CTFの readme を解いていてargv[0] leakというテクニックを知り手で動かしてみたのですが、うまく行きませんでした。
原因がわかったのでまとめておきます。

argv[0] leakとは

katagaitai勉強会 #4の資料にくわしく載っています。

SSPを有効にしているとき、バッファオーバーフローでデータを大量に書き込みCanaryを書き換えると*** stack smashing detected ***: ./hogehoge terminatedと表示され終了します。
そのときの./hogehogeという文字列を指すポインタはスタックに積まれているので、それを書き換えてあげれば./hogehogeの部分を任意のアドレスのデータに置き換えてリークすることができます。

環境間の差異

しかし、先に書いたとおり自分のUbuntu 17.10ではCanaryを書き換えても... <unknown> terminatedとしか表示されませんでした。
なぜだろうと思い別のCentOSにバイナリをコピーしてみると以下のように想定どおり表示されました。

centos
$ ./test_ssp
*** stack smashing detected ***: ./test_ssp terminated
Segmentation fault (コアダンプ)

libcのバージョンを比べてみます。こちらがCentOS。glibc 2.17。

centos
$ /lib64/libc.so.6
GNU C Library (GNU libc) stable release version 2.17, by Roland McGrath et al.

動作しなかったUbuntu。glibc 2.26。

ubuntu
$ /lib/x86_64-linux-gnu/libc.so.6
GNU C Library (Ubuntu GLIBC 2.26-0ubuntu2) stable release version 2.26, by Roland McGrath et al.

バージョンにより違いがあると考えて、とりあえずソースを落としてみました。
GNUからダウンロードできます。

以下が動作した環境のglibc 2.17のソースコード。
Canaryチェックにひっかかると__stack_chk_failを経由して__fortify_failが呼ばれます。

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


extern char **__libc_argv attribute_hidden;

void
__attribute__ ((noreturn))
__fortify_fail (msg)
     const char *msg;
{
  /* The loop is added only to keep gcc happy.  */
  while (1)
    __libc_message (2, "*** %s ***: %s terminated\n",
            msg, __libc_argv[0] ?: "<unknown>");
}
libc_hidden_def (__fortify_fail)

そしてこれが動作しなかった環境のglibc 2.26のソースです。
__libc_argv[0] may point to the corrupted stack.と明記され、バックトレースを有効にしない限り情報を表示せず<unknown>とするようになっています。これが原因だったようです。

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


extern char **__libc_argv attribute_hidden;

void
__attribute__ ((noreturn)) internal_function
__fortify_fail_abort (_Bool need_backtrace, const char *msg)
{
  /* The loop is added only to keep gcc happy.  Don't pass down
     __libc_argv[0] if we aren't doing backtrace since __libc_argv[0]
     may point to the corrupted stack.  */
  while (1)
    __libc_message (need_backtrace ? (do_abort | do_backtrace) : do_abort,
            "*** %s ***: %s terminated\n",
            msg,
            (need_backtrace && __libc_argv[0] != NULL
             ? __libc_argv[0] : "<unknown>"));
}

void
__attribute__ ((noreturn)) internal_function
__fortify_fail (const char *msg)
{
  __fortify_fail_abort (true, msg);
}

libc_hidden_def (__fortify_fail)
libc_hidden_def (__fortify_fail_abort)

思いもしないところから情報が漏れるんだなあ…
まだまだLinux, pwnに対する知識が足りないと実感します。