(動的解析してみる)
競技サーバでは,これらの実行ファイルが,この特殊命令が実装された
エミュレータ上で動作しています.
こちらを使えば,特殊命令が正常に実行できるはずです.
さらにそれらにはデバッガ(GDB)で接続して解析ができます.
いわゆる「リモートデバッグ」ができる,ということです.
そこで次は,GDBサーバに接続して,特殊命令の挙動を見てみましょう.
以下でBlackfin用のGDBを起動します.起動時に,Blackfin用の実行ファイルを
指定します.
このように実行ファイルを指定することで,実行ファイルから
デバッグ用の各種情報を得て,デバッグしやすくすることができます.
$ /usr/local/cross-gcc494/bin/bfin-elf-gdb bfin-elf.x GNU gdb (GDB) 7.12.1 ... (gdb)起動したらGDBサーバに接続します.
(gdb) target remote 127.0.0.1:10000 Remote debugging using 127.0.0.1:10000 ...(接続まで,しばらく時間がかかる)... 0x00001400 in start () (gdb)これでGDBサーバに接続できました.
nputs()という関数で表示が行われていたはずなので,
そこにブレークポイントを張って実行してみましょう.
GDBサーバに接続している場合には,プログラムはすでに動作開始しているため,
runでなくcontinueを実行することに注意してください.
(gdb) break nputs Breakpoint 1 at 0x1482 (gdb) continue Continuing. Breakpoint 1, 0x00001482 in nputs () (gdb)nputs()まで来たということは,そこまでにあった random_XXX_default()や decrypt()が実行できたということです.
nputs()では,何を表示しようとしているのでしょうか.
ここでBlackfinのアセンブラの基礎をちょっと説明します.
競技のインフォメーションページでは,アセンブラのサンプルとして
冒頭で説明した cross-gcc494-v1.0.zip を上げています.
これを展開すると,sample/bfin-elf.d というファイルがあります.
これがBlackfinのアセンブラのサンプルです.
また元となっているソースコードは sample/sample.c というファイルです.
sample.c と bfin-elf.d で,1を返すreturn_one()という関数と, 関数の第1/第2引数を返す return_arg1()/return_arg2()という関数を 比較してみます.
int return_one() { return 1; } ... int return_arg1(int a) { return a; } int return_arg2(int a, int b) { return b; }
00fe1418 <_return_one>: ... fe141e: 08 60 R0 = 0x1 (X); /* R0=0x1( 1) */ fe1420: 10 00 RTS; ... 00fe148c <_return_arg1>: ... fe1494: 10 00 RTS; ... 00fe1498 <_return_arg2>: ... fe149e: 01 30 R0 = R1; fe14a0: 10 00 RTS; ...return_one()を見ると,R0というレジスタに1を代入しています.
ということはBlackfinでは,関数の第1引数は戻り値と同様にレジスタR0, 第2引数はレジスタR1で渡されるのだと推測できます.
さてここまでわかったところで,GDBでの解析に話を戻します.
nputs()でブレークしている状態でレジスタの値を見てみます.
(gdb) info registers r0 0x1 1 r1 0x1bdc 7132 r2 0x24 36 r3 0x0 0 ...引数はレジスタR0以降で渡されるため,その付近のレジスタを見てみます.
実際にnputs()の逆アセンブル結果を見てみましょう.
00001480 <_nputs>: 1480: 67 01 [--SP] = RETS; 1482: a6 6f SP += -0xc; /* (-12) */ 1484: ff e3 ca ff CALL 0x1418 <___write>; 1488: 00 60 R0 = 0x0 (X); /* R0=0x0( 0) */ 148a: 66 6c SP += 0xc; /* ( 12) */ 148c: 27 01 RETS = [SP++]; 148e: 10 00 RTS;
__write()という関数を呼んでいます.writeシステムコールの呼び出しでしょうか.
たしかに,出力が行われるようです.
また戻り値としてレジスタR0にゼロが代入されているので,戻り値も返すようです.
そう考えると,nputs()は以下のような仕様の関数のようです.
int nputs(int fd, char *str, int len);つまり出力文字列は第2引数として,レジスタR1で渡されているように思われます.
ということでレジスタR1の指す先のメモリを読んでみましょう. ここに出力文字列があります.
(gdb) x/48c $r1 0x1bdc: 83 'S' 69 'E' 67 'C' 67 'C' 79 'O' 78 'N' 123 '{' 89 'Y' 0x1be4: 111 'o' 117 'u' 85 'U' 115 's' 101 'e' 77 'M' 97 'a' 110 'n' 0x1bec: 121 'y' 68 'D' 83 'S' 80 'P' 87 'W' 104 'h' 105 'i' 108 'l' 0x1bf4: 101 'e' 78 'N' 111 'o' 116 't' 75 'K' 110 'n' 111 'o' 119 'w' 0x1bfc: 105 'i' 110 'n' 103 'g' 125 '}' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0x1c04: 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' (gdb)これがデコードされたキーワードです.
SECCON{YouUseManyDSPWhileNotKnowing}