ログイン
編集不可のページディスカッション情報添付ファイル
ytoku/CTF/Writeup/AdventCalendarCTF2014/2014-12-15

MMA

password checker

問題

This checker tells me "wrong" everytime. There is something strange, you can discover hidden flag in the binary.

password-checker.xz

メモ

解法

gdb上で実行して,適宜ブレークポイントを仕掛けながら内部状態を観察する。

Perl_runops_standardで止めて1ステップずつ実行してどのような関数が呼ばれるか調べた。 Perl_pp_constに関しては次の方法で積まれた文字列を特定した。

(gdb) printf "%s %d\n", ((char**)((**sp)->sv_any))[0], ((int*)((**sp)->sv_any))[1]
password:  10

以下のような順で呼び出されていた。

Perl_pp_enter
Perl_pp_nextstate
Perl_pp_pushmark
Perl_pp_const ("password: ")
Perl_pp_print
Perl_pp_nextstate
Perl_pp_gv
Perl_pp_readline
Perl_pp_nextstate
Perl_pp_pushmark
Perl_pp_const ("wrong\n")
Perl_pp_print
Perl_pp_nextstate
Perl_pp_pushmark
Perl_pp_const ('"')
...
Perl_pp_const ('|')
Perl_pp_pushmark
Perl_pp_padav
Perl_pp_aassign
Perl_pp_leave

概ね次のようなプログラムが実行されていると考えられる。

   1 print "password: ";
   2 <>;
   3 print "wrong\n";
   4 @somewhere = qw(" ` ! $ # { / & _ ^ . [ ( * ) + % , \\ ] -), '', qw(: = ; |);

分岐すら無いが最後に怪しい文字列が積まれている。

そこで,パスワードを聞いてきたところで止めて名前空間上にどのような名前が存在するかを調べる。

(gdb) p Perl_eval_pv(my_perl, "print qq{$_\n} for keys %main::", 1)
...
_</home/vagrant/.plenv/versions/5.8.9/lib/perl5/5.8.9/i686-linux-multi/auto/B/C/C.so
flag
2
...

flagというそれらしい名前が得られた。これがどの名前空間に属するか調べるとサブルーチン名であることが分かる。

(gdb) p Perl_eval_pv(my_perl, "printf qq{%d\n}, defined &flag", 1)
1
$5 = (SV *) 0x81db078

flagを実行してみる。

(gdb) p Perl_eval_pv(my_perl, "flag()", 1)
Warning:
Cannot insert breakpoint 0.
Cannot access memory at address 0x9214f025

0xf7d97381 in siglongjmp () from /lib/i386-linux-gnu/libc.so.6
The program being debugged stopped while in a function called from GDB.
Evaluation of the expression containing the function
(Perl_eval_pv) will be abandoned.
When the function is done executing, GDB will silently stop.
(gdb) c
Continuing.
^_^
Warning:
Cannot insert breakpoint 0.
Cannot access memory at address 0x92f34c25

0xf7d97381 in siglongjmp () from /lib/i386-linux-gnu/libc.so.6

不穏なsiglongjmpをした上に^_^だけ表示されて停止してしまった。 flagの中で何が起きているかを調べるためにPerl_pp_entersubにブレークポイントを仕掛けた上で再実行してみる。

(gdb) b Perl_pp_entersub
Breakpoint 1 at 0x810219c: file pp_hot.c, line 2561.
(gdb) p Perl_eval_pv(my_perl, "flag()", 1)

Breakpoint 1, Perl_pp_entersub (my_perl=0x81d0008) at pp_hot.c:2561
2561    {
The program being debugged stopped while in a function called from GDB.
Evaluation of the expression containing the function
(Perl_eval_pv) will be abandoned.
When the function is done executing, GDB will silently stop.

Perl_pp_entersubからは抜けてPerl_runops_standardをステップ実行してどのような処理が行われているかを見ていく。

(gdb) fin
Run till exit from #0  Perl_pp_entersub (my_perl=0x81d0008) at pp_hot.c:2561
0x08100ce8 in Perl_runops_standard (my_perl=0x81d0008) at run.c:37
37          while ((PL_op = CALL_FPTR(PL_op->op_ppaddr)(aTHX))) {
Value returned is $6 = (OP *) 0x81bdf98 <cop_list+15064>
(gdb) s
38              PERL_ASYNC_CHECK();
(gdb) s
37          while ((PL_op = CALL_FPTR(PL_op->op_ppaddr)(aTHX))) {
(gdb) 
Perl_pp_nextstate (my_perl=0x81d0008) at pp_hot.c:50
50      {
(gdb) fin
Run till exit from #0  Perl_pp_nextstate (my_perl=0x81d0008) at pp_hot.c:50
0x08100ce8 in Perl_runops_standard (my_perl=0x81d0008) at run.c:37
37          while ((PL_op = CALL_FPTR(PL_op->op_ppaddr)(aTHX))) {
Value returned is $7 = (OP *) 0x8197828 <op_list+19848>
(gdb) s
38              PERL_ASYNC_CHECK();
(gdb) 
37          while ((PL_op = CALL_FPTR(PL_op->op_ppaddr)(aTHX))) {
(gdb) 
Perl_pp_pushmark (my_perl=0x81d0008) at pp_hot.c:81
81      {
(gdb) fin
Run till exit from #0  Perl_pp_pushmark (my_perl=0x81d0008) at pp_hot.c:81
0x08100ce8 in Perl_runops_standard (my_perl=0x81d0008) at run.c:37
37          while ((PL_op = CALL_FPTR(PL_op->op_ppaddr)(aTHX))) {
Value returned is $8 = (OP *) 0x81b9f58 <svop_list+11480>
(gdb) s
38              PERL_ASYNC_CHECK();
(gdb) 
37          while ((PL_op = CALL_FPTR(PL_op->op_ppaddr)(aTHX))) {
(gdb) 
Perl_pp_const (my_perl=0x81d0008) at pp_hot.c:43
43      {

constについては中身を見ておく。

(gdb) s
44          dSP;
(gdb) 
45          XPUSHs(cSVOP_sv);
(gdb) 
46          RETURN;
(gdb) printf "%s %d\n", ((char**)((**sp)->sv_any))[0], ((int*)((**sp)->sv_any))[1]
^_^
 4

実行時に表示された文字列である。

(gdb) fin
Run till exit from #0  Perl_pp_const (my_perl=0x81d0008) at pp_hot.c:43
0x08100ce8 in Perl_runops_standard (my_perl=0x81d0008) at run.c:37
37          while ((PL_op = CALL_FPTR(PL_op->op_ppaddr)(aTHX))) {
Value returned is $9 = (OP *) 0x81b6c40 <listop_list+6624>
(gdb) s
38              PERL_ASYNC_CHECK();
(gdb) 
37          while ((PL_op = CALL_FPTR(PL_op->op_ppaddr)(aTHX))) {
(gdb) 
Perl_pp_die (my_perl=0x81d0008) at pp_sys.c:445
445     {

なんと^_^をスタックに積んだ後dieしてしまった。先ほどのsiglongjmpの正体はeval内でのdieだったのだ。

ならばと,dieしないように,次の命令をwarnに書き換えてみる。タイミングはPerl_pp_constが呼ばれた直後である。

(gdb) b Perl_pp_const
(gdb) p Perl_eval_pv(my_perl, "flag()", 1)
Breakpoint 2 at 0x8100df7: file pp_hot.c, line 43.
(gdb) c
Continuing.

Breakpoint 2, Perl_pp_const (my_perl=0x81d0008) at pp_hot.c:43
43      {
(gdb) fin
Run till exit from #0  Perl_pp_const (my_perl=0x81d0008) at pp_hot.c:43
0x08100ce8 in Perl_runops_standard (my_perl=0x81d0008) at run.c:37
37          while ((PL_op = CALL_FPTR(PL_op->op_ppaddr)(aTHX))) {
Value returned is $13 = (OP *) 0x81b6c40 <listop_list+6624>
(gdb) s
38              PERL_ASYNC_CHECK();
(gdb) 
37          while ((PL_op = CALL_FPTR(PL_op->op_ppaddr)(aTHX))) {
(gdb) p *my_perl->Top
$14 = {op_next = 0x81bdfd0 <cop_list+15120>, 
  op_sibling = 0x81bdfd0 <cop_list+15120>, 
  op_ppaddr = 0x81415a0 <Perl_pp_die>, op_targ = 2, op_type = 171, 
  op_seq = 65535, op_flags = 5 '\005', op_private = 1 '\001'}

ここだ。op_ppaddrがdieを指しているのでwarnに書き換えてしまう。

(gdb) set variable my_perl->Top->op_ppaddr = Perl_pp_warn

しかしそのまま実行すると大量のuninitialized value警告が発生する。

(gdb) c
...
Use of uninitialized value in bitwise and (&) at password-checker.pl line 11.
Use of uninitialized value in bitwise and (&) at password-checker.pl line 11.
Use of uninitialized value in concatenation (.) or string at password-checker.pl line 11.
...

プログラムの本体の処理を思い出してみると,最後に謎の値を配列に代入していた。 そこでプログラムの実行が終わった後でflagを呼び出してみる。

(gdb) b Perl_pp_leave
(gdb) r

によりプログラムの実行が終わったところで止めて,先ほどの操作を再実行する。

...
ADCTF_0BFusC4Ti0n_PeRl_In_bIN4Ry[Inferior 1 (process 30508) exited normally]

こうしてフラグは得られた。長い戦いであった。

ytoku/CTF/Writeup/AdventCalendarCTF2014/2014-12-15 (最終更新日時 2014-12-19 12:12:46 更新者 ytoku)