PIEが有効化されているELFファイルをデバッグする方法のメモ。
PIEとは
以下はSECCON実行委員会監修 セキュリティコンテストチャレンジブック P.97からの引用である。
Position Independent Executable (PIE)は実行コード内のアドレス参照を全て相対アドレスで行うことで、実行ファイルがメモリ上のどの位置に置かれても正常に実行できるようにコンパイルされた実行ファイルです。よく共有ライブラリに適用されるオプションですが、実行ファイルをメモリにロードする際の位置をランダムに決定できるようになるため、ASLRでは行われない実行ファイルの配置アドレスもランダム化されるようになります。
PIEが有効化されていると、プログラム実行時のアドレス配置がランダム化され、これがデバッガでブレークポイントをセットする際に妨げになる。(アドレスがランダム化されるので、ブレークポイントをセットしようにも、どのアドレスにセットすれば良いのか分からない。)
PIEが有効化されているELFファイルをGDBでデバッグする方法をググってみると、以下の方法が紹介されていた。
_start
やstart
にブレークポイントをセットする。(b _start
,b start
)main
にブレークポイントをセットする。(b main
)0
にブレークポイントをセットする。(b *0
)
今回は上記の方法とは別に自分が試してみて上手くいった方法をまとめてみた。今後、別の方法を見つけた場合は随時、記事をアップデートしていきたい。
検証用のプログラムと検証の流れについて
以下の検証用のプログラムを用意した。
#include <stdio.h>
#include <string.h>
int main(void)
{
char secret[9];
secret[0] = 'h';
secret[1] = 'o';
secret[2] = 'g';
secret[3] = 'e';
secret[4] = 'f';
secret[5] = 'u';
secret[6] = 'g';
secret[7] = 'a';
secret[8] = '\0';
char user_input[128];
printf("Enter your text:\n");
scanf("%s", user_input);
int n;
n = memcmp(secret, user_input, sizeof(secret));
if (n == 0) {
printf("You entered the correct secret text!\n");
}
else {
printf("That is incorrect text...\n");
}
return 0;
}
上記のプログラムはユーザーの入力した値がhogefuga
と等しいか比較し、等しかった場合はYou entered the correct secret text!
と表示し、等しく無い場合はThat is incorrect text…
と表示する。
上記のプログラムをPIEを有効化してコンパイルし、デバッガを駆使して秘密のテキストhogefuga
を突き止める。。。というのが今回の検証の流れである。(デバッグせずともアセンブリを見ればhogefuga
とい文字列を1文字ずつバッファに格納しているのがすぐ分かるが、今回はそれは考えない方向で)
PIEを有効化してコンパイル。
gcc -m32 pie-test.c -o pie-test -pie -fpie
$ file pie-test
pie-test: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=39506669713cbf93eac591e227ee660f243a9fce, not stripped
checksecでPIEが有効化されていることを確認した。
$ sudo checksec.sh --file=pie-test
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 72 Symbols No 0 0 pie-test
OSのASLR無効化 + lddコマンドでプログラムのベースアドレスを突き止める
まず、OSのASLRを無効化する。
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
続いてldd
コマンドでld.so
またはld-linux.so
のロード先のアドレスを確認する。
$ ldd pie-test
linux-gate.so.1 => (0xf7ffd000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e26000)
/lib/ld-linux.so.2 (0x56555000)
上記よりアドレス0x56555000
にld-linux.so.2
がロードされることが分かった。
ld.so
やld-linux.so
は動的リンカーまたは動的ローダーと呼ばれるもので、プログラムが利用する共有ライブラリを探し出すときに使われる。詳しくはこちら。
(自分の今までの経験上) これらの動的リンカーまたは動的ローダーはベースアドレスにロードされる。
上記の例だとld-linux.so.2
のロード先の0x56555000
がベースアドレスということになる。
プログラムのベースアドレスが判明すれば、解析したいコード部分の相対アドレスにベースアドレスを加算することで、実際のアドレスを取得してブレークポイントをセットすることができる。
試しに以下のmemcmpのcall命令にブレークポイントをセットしてみる。
$ objdump -d -M intel pie-test | grep -C 3 memcmp
--- snipped ---
76f: 89 44 24 04 mov DWORD PTR [esp+0x4],eax
773: 8d 44 24 23 lea eax,[esp+0x23]
777: 89 04 24 mov DWORD PTR [esp],eax
77a: e8 91 fd ff ff call 510 <memcmp@plt>
相対アドレス0x077a
にてmemcmpがcallされている。このcall命令の実際のアドレスを取得するには0x077a
にベースアドレスの0x56555000
を足してやれば良い。
>>> hex(0x56555000 + 0x077a)
'0x5655577a'
上記より、実際のアドレスは0x5655577a
であることが判明した。
アドレス0x5655577a
にブレークポイントをセットして実行し、memcmpの第一引数が格納されているメモリ部分 (eax)をダンプしたところ、秘密のテキストhogefuga
を確認できた。
$ gdb -q ./pie-test
Reading symbols from ./pie-test...(no debugging symbols found)...done.
(gdb) b *0x5655577a
Breakpoint 1 at 0x5655577a
(gdb) r
Starting program: /home/sansforensics/pie-test
Enter your text:
aaaa
Breakpoint 1, 0x5655577a in main ()
(gdb) x/s $eax
0xffffd063: "hogefuga"
以下のリンクは、このデバッグ方法を利用して解いたCTFチャレンジのWriteUpである。
ただし、対象のファイルが動的リンカー / ローダーを使用していない場合、この方法は使えない。