= password checker = == 問題 == This checker tells me "wrong" everytime. There is something strange, you can discover hidden flag in the binary. password-checker.xz == メモ == * perlccされた物のDeparse方法: http://stackoverflow.com/questions/4048614/how-can-i-reverse-engineer-a-perl-program-that-has-been-compiled-with-perlcc == 解法 == 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 }}} 概ね次のようなプログラムが実行されていると考えられる。 {{{#!highlight perl print "password: "; <>; print "wrong\n"; @somewhere = qw(" ` ! $ # { / & _ ^ . [ ( * ) + % , \\ ] -), '', qw(: = ; |); }}} 分岐すら無いが最後に怪しい文字列が積まれている。 そこで,パスワードを聞いてきたところで止めて名前空間上にどのような名前が存在するかを調べる。 {{{ (gdb) p Perl_eval_pv(my_perl, "print qq{$_\n} for keys %main::", 1) ... _op_ppaddr)(aTHX))) { Value returned is $6 = (OP *) 0x81bdf98 (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 (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 (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 (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 (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 , op_sibling = 0x81bdfd0 , op_ppaddr = 0x81415a0 , 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] }}} こうしてフラグは得られた。長い戦いであった。