namdicul's blog

気ままに更新します. CTFと暗号理論について勉強中です.

"Cpaw CTF" Q23.[Reversing]またやらかした!

またprintf()をし忘れたプログラムが見つかった。 
とある暗号を解くプログラムらしい… 
reversing200

 Answer:

 

今回はかなり書くことが多そうです. 勉強になったので細かくわかりやすく書いていこうと思います. 

さて, Reversing問題なのでとりあえずファイルをダウンロードしてfileコマンドでどんな種類のファイルなのかを調べます. すると前回と同様ELF形式であることがわかったので, 実行権限を与えて実行してみます. 今回は何も表示されませんでした. 

 

前回同様にstringsコマンドを使ったり, ltraceコマンド, straceコマンドを実行してもflagは得られそうにありませんでした. そこで使用したのは「gdbデバッグ」です. 

 

実は僕もgdbデバッグはそんなに使用したことがなかったので, 今回はflagを得るのに非常に苦労しました. こちらのサイトを参考にさせていただきました...感謝です. 

 

 

さて, ここからgdbデバッグについて詳しく述べていこうと思います. そんなの知ってるよ!!って方は読み飛ばしてもらって構いません. 

 まず, gdbの起動方法は, 

$ gdb

または

$ gdb (デバッギーのファイル名)

または

$ gdb -p (デバッギーのプロセスID)

 です. 

 

続いて, 逆アセンブルについて説明します. 逆アセンブルをするには, disasコマンド(もしくはxコマンド)を使用してやります. 

gdb$ disas (関数名)

gdb$ disas (関数内での開始アドレス) (関数内での終了アドレス)

gdb$ x/(命令数) i (先頭アドレス)

 

gdbでは, デフォルトではAT&T記法が用いられていますが, Intel記法の方がわかりやすいと思いますので, 今回はIntel記法を用いて説明をしたいと思います. Intel記法に直すには, ホームディレクトリ下に「.gdbinit」ファイルを作成し, 「set disassembly-flavor intel」と記述します(こちらのサイトを参考にさせていただきました.ありがとうございます). 

 

続いて, ブレークポイントについて説明をしたいと思います. ブレークポイントをセットするには, 

gdb$ b (関数名)

または

gdb$ b (アドレス)

と打ってやります. ブレークポイントは複数セットできます. 現在どこにブレークポイントをセットしているかを確認するには, 

gdb$ i b 

 というコマンドを使用してあげます(info breakの略です).

 

また, ブレークポイントを無効化するには, 

gdb$ d (ブレークポイントの番号)

としてあげます.  一時的に無効化, 有効化してやる場合は「disable」コマンドと「enable」コマンドを使用してあげます(引数はdコマンドの同じです).

 

続いて, 実行をするには「rコマンド」を打ってやります. すると, ブレークポイント, もしくは例外が起こるまでプログラムが実行されます. ステップ実行をするには「niコマンド」もしくは「siコマンド」を使用します(両者の違いについては, サブルーチンの中に入って実行するかしないかという点があります. niは実行せず, siは実行します).

次のブレークポイントまで実行を続ける場合は「cコマンド」を使用します. 

 

続いて, レジスタの値を読み出すには, 以下のコマンドを使用します. 

gdb$ p (レジスタ名)

gdb$ i r               /* レジスタ全部の内容を表示 */

 

メモリの内容を表示したい場合は, xコマンドを使用してやります. 

gdb$ x/(表示する数)(メモリサイズ)(表示フォーマット) (表示するメモリの先頭アドレス)

 

メモリサイズに関しては, 

 b: BYTE(1バイト)

 h: HALFWORD(2バイト)

 w: WORD(4バイト)

g: GIANTWORD(8バイト)

 

表示フォーマットに関しては, 

s: 文字列

i: 命令

x: 16進数

 

 ここから問題解説に入っていこうと思います. 

 

printf文を書き忘れているということは, flagは実行中メモリに確保されているはずなので,gdbデバッグしてみましょう. まず, main関数を逆アセンブルしてみましょう.

 

tomonorihirata@tomonorihirata-VirtualBox:~/ctf/cpaw$ gdb ./rev200
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./rev200...(no debugging symbols found)...done.
(gdb) disas main
Dump of assembler code for function main:
0x080483ed <+0>: push ebp
0x080483ee <+1>: mov ebp,esp
0x080483f0 <+3>: push edi
0x080483f1 <+4>: push ebx
0x080483f2 <+5>: add esp,0xffffff80
0x080483f5 <+8>: mov DWORD PTR [ebp-0x78],0x7a
0x080483fc <+15>: mov DWORD PTR [ebp-0x74],0x69
0x08048403 <+22>: mov DWORD PTR [ebp-0x70],0x78
0x0804840a <+29>: mov DWORD PTR [ebp-0x6c],0x6e
0x08048411 <+36>: mov DWORD PTR [ebp-0x68],0x62
0x08048418 <+43>: mov DWORD PTR [ebp-0x64],0x6f
0x0804841f <+50>: mov DWORD PTR [ebp-0x60],0x7c
0x08048426 <+57>: mov DWORD PTR [ebp-0x5c],0x6b
0x0804842d <+64>: mov DWORD PTR [ebp-0x58],0x77
0x08048434 <+71>: mov DWORD PTR [ebp-0x54],0x78
0x0804843b <+78>: mov DWORD PTR [ebp-0x50],0x74
0x08048442 <+85>: mov DWORD PTR [ebp-0x4c],0x38
0x08048449 <+92>: mov DWORD PTR [ebp-0x48],0x38
0x08048450 <+99>: mov DWORD PTR [ebp-0x44],0x64
0x08048457 <+106>: mov DWORD PTR [ebp-0x7c],0x19
0x0804845e <+113>: lea ebx,[ebp-0x40]
0x08048461 <+116>: mov eax,0x0
0x08048466 <+121>: mov edx,0xe
0x0804846b <+126>: mov edi,ebx
0x0804846d <+128>: mov ecx,edx
0x0804846f <+130>: rep stos DWORD PTR es:[edi],eax
0x08048471 <+132>: mov DWORD PTR [ebp-0x80],0x0
0x08048478 <+139>: jmp 0x8048491 <main+164>
0x0804847a <+141>: mov eax,DWORD PTR [ebp-0x80]
0x0804847d <+144>: mov eax,DWORD PTR [ebp+eax*4-0x78]
0x08048481 <+148>: xor eax,DWORD PTR [ebp-0x7c]
0x08048484 <+151>: mov edx,eax
0x08048486 <+153>: mov eax,DWORD PTR [ebp-0x80]
0x08048489 <+156>: mov DWORD PTR [ebp+eax*4-0x40],edx
0x0804848d <+160>: add DWORD PTR [ebp-0x80],0x1
0x08048491 <+164>: cmp DWORD PTR [ebp-0x80],0xd
0x08048495 <+168>: jle 0x804847a <main+141>
0x08048497 <+170>: mov eax,0x0
0x0804849c <+175>: sub esp,0xffffff80
0x0804849f <+178>: pop ebx
---Type <return> to continue, or q <return> to quit---
0x080484a0 <+179>: pop edi
0x080484a1 <+180>: pop ebp
0x080484a2 <+181>: ret
End of assembler dump.
(gdb)

 

 今回はIntel記法を使用しています.

 逆アセンブルした結果のうち, <+8>から<+106>までのところをみてみましょう. mov命令がたくさん並んでいるのがわかります. mov命令とは値の代入を行う命令です. たとえば,<+8>では, 

ebp-0x78番地に, 0x7aを代入しろと命令しています. また, <+113>では, lea命令が使われていますが, これは右側のオペランドを計算して左側のオペランドに代入するということをしています.

 

次に, <+132>以降でなにをやっているかというと, 先ほど格納した値と0x19との排他的論理和を計算してメモリに格納するということを繰り返しています. 14個の値を格納したので14回ループさせていることがわかりますね. これを確かめるために, 以下のコマンドを実行してみます.

 

gdb$ b *0x08048497
gdb$ c
gdb$ x/32hs $ebp-0x80

レジスタの内容については「i rコマンド」で確かめてください.) これによって, 排他的論理和の計算結果後のメモリの内容を確かめることができます. そこにflagは隠されていますね.