2019-03-27にFireEye Security Tech Lounge Vol.6が開催されました。
営業色を排した技術者向けのイベントです。 今回は春のCTFセンバツということで2時間以上のイベント時間を全てCTFに費やす構成になっていました。 現地では1問も解けなかったのですが面白い問題だったので後日取り組んだ結果を共有します。
FireEye株式会社様のご厚意で問題ファイルの公開許可を頂けました。 興味がある方はWriteupを読み進める前に挑戦してみてください。
Q1. packet
ICMPのパケットが与えられる。
全体を眺めていくとLengthが3種類あることが分かる。
- 先頭と末尾に98が多い
- 中程には153と99が多いが98も少し含まれている
何となく3種類のパケットの配置に規則性が見えてきた。 リクエストパケットだけに絞って観察するとさらに分かりやすい。 99と153が7つ続いた後に98が1つ出現している。 7つの値で表現できる情報といえばASCIIコードだ。
98をデリミタとして99=0、153=1に置き換えるスクリプトを書く。
答えは FLAG_ping2c
(ping to char?)
Q2. ctf
出題意図とかけ離れたところで苦しんだ問題。 ELFが与えられるが実行できない。 TrusugiやKaliはベースのOSが古くてglibcのバージョンを上げられないことが原因だった。 Ubuntu18.10に環境を移すことで解決した。
- 動かない環境の例
root@user-VirtualBox:/tmp# ./ctf
./ctf: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.27' not found (required by ./ctf)
root@user-VirtualBox:/tmp# apt list libc6 -a
Listing... Done
libc6/xenial-updates,now 2.23-0ubuntu11 amd64 [installed]
libc6/xenial-security 2.23-0ubuntu10 amd64
libc6/xenial 2.23-0ubuntu3 amd64
root@user-VirtualBox:/tmp# head -2 /etc/os-release
NAME="Ubuntu"
VERSION="16.04.6 LTS (Xenial Xerus)"
- これなら動く
root@user-VirtualBox:~# apt list libc6 -a
一覧表示... 完了
libc6/cosmic,now 2.28-0ubuntu1 amd64 [インストール済み、自動]
libc6/cosmic 2.28-0ubuntu1 i386
root@user-VirtualBox:~# head -2 /etc/os-release
NAME="Ubuntu"
VERSION="18.10 (Cosmic Cuttlefish)"
ということで動かしてみると何か送っているらしいメッセージが出力される。
root@user-VirtualBox:/tmp# ./ctf
Sending data...
enp0s3のパケットを取ってみるが何も見当たらないので処理の中身を覗いてみる。
127.0.0.1
が見えるのでloを見張らないとダメだったみたい。
[----------------------------------registers-----------------------------------]
RAX: 0x30 ('0')
RBX: 0x7fffffff9b00 --> 0x0
RCX: 0x7fffffff9ab0 --> 0x7ffff4a96130 (<_nss_files_getservbyname_r>: push r15)
RDX: 0x11
RSI: 0x7fffffff9ad0 --> 0x0
RDI: 0x4200106830 ("127.0.0.1")
RBP: 0x7fffffff9d20 --> 0x7fffffffa270 --> 0x4200006220 --> 0x4c0428 (movsxd rax,ebx)
RSP: 0x7fffffff9ad0 --> 0x0
RIP: 0x7ffff7a897aa (<gaih_inet+474>: test BYTE PTR [r15],0x40)
R8 : 0x3
R9 : 0x0
R10: 0x7ffff7b1eae0 --> 0x100000000
R11: 0x206
R12: 0x7ffff7b32692 --> 0x706475 (clc)
R13: 0x7fffffff9e20 --> 0x7fffffff9e30 --> 0x9006e69616d6f64 ('domain')
R14: 0x7ffff7b32688 --> 0x1100000002
R15: 0x42001068b0 --> 0x25 ('%')
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x7ffff7a89795 <gaih_inet+453>: mov DWORD PTR [rsi+0x10],0x0
0x7ffff7a8979c <gaih_inet+460>: mov DWORD PTR [rsi+0x24],0x0
0x7ffff7a897a3 <gaih_inet+467>: mov QWORD PTR [rsi],0x0
=> 0x7ffff7a897aa <gaih_inet+474>: test BYTE PTR [r15],0x40
パケットの中身はDNSクエリだった。 エンコードしたデータを分割送信しているっぽい。
結合したデータをデコードする。
最初はBase64かと思ったけどちゃんと読めないし、よく見ると使われている文字数が少なすぎる。 アルファベットと数字しか見えないので26+10で30前後? Base32を試したらPNGが入手できた。
答えは FLAG_DNS4Everything
Q3. reg
regファイルが与えられる。
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Mandiant\CTF]
"Anchovy"="QhdMGk...省略"
"Herring"="bVNbi9...省略"
"Mackerel"="$raw=Get-ItemProperty('hklm:\\Software\\Mandiant\\CTF')|Select-Object -ExpandProperty Herring;sal a New-Object;iex(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String($raw),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd()"
MackerelはPowershellコマンドなので処理を追いかけていく。
-
#1 ExpandPropertyは配列の名前を指定して値を取り出すオプション。 HKLM_SOFTWARE_Mandiant_CTFからHerringを取り出し$rawに格納している。
-
#2 $rawに格納したHerringをBase64デコードしている。
-
#3 Base64デコードされたHerringをInflateしている。
-
#4 Base64デコードおよびInflateされたHerringをASCIIエンコードしている
$raw=Get-ItemProperty('hklm:\\Software\\Mandiant\\CTF')|Select-Object -ExpandProperty Herring; # 1
sal a New-Object;
iex(
a IO.StreamReader(
(a IO.Compression.DeflateStream( # 3
[IO.MemoryStream][Convert]::FromBase64String($raw) # 2
,[IO.Compression.CompressionMode]::Decompress)
)
,[Text.Encoding]::ASCII
)
).ReadToEnd()
確認した通りにHerringを処理するとコマンドが確認できる。 カスタムメソッドのInvoke-AESを使ってAnchovyを復号する処理が書かれている。 末尾のiexは与えられた文字列をPowershellコマンドとして実行するメソッド。
iexをechoに置き換えることで実行されるコマンドを確認する。
PS C:\Users\a> $raw_obj = Invoke-AES -e_str $raw -method decrypt;
PS C:\Users\a> echo $raw_obj
${#} =+$( ) ;${!.*}=${#};${[/)}= ++ ${#} ;${).+}= ++${#} ; ${=-$} =++${#};${)}=++${#} ; ${+}= ++ ${#} ; ${``}= ++ ${#}; ${/} =++${#};${/[(}= ++ ${#
};${+-.} = ++${#} ; ${;%$} = "[" +"$(@{} )"[ ${/}]+"$(@{})"[ "${[/)}"+"${+-.}"] + "$( @{ } )"[ "${).+}" +"${!.*}"] +"$? "[ ${[/)} ] + "]" ;${#}="
".("$( @{} ) "[ "${[/)}" +"${)}"]+"$( @{} ) "["${[/)}" + "${``}" ]+ "$(@{ } ) "[${!.*}]+"$( @{}) "[ ${)} ] + "$?"[${[/)} ]+ "$(@{ } )"[ ${=-$}]
); ${#} ="$(@{ } )"["${[/)}" +"${)}" ] +"$(@{ })"[ ${)}] +"${#}"["${).+}"+"${/}" ] ;. ${#} ( " ${#}(${;%$}${``}${+}+ ${;%$}${[/)}${!.*}${!.*} + ${;%
...省略
難読化されているが「コマンド文字列の構築」→「文字列をコマンドとして実行」の流れは変わらない。 整形しながらキーになる部分を探していく。 $で始まる行でコマンド文字列のパーツを定義している。 最後の.で始まる行でパーツを組み合わせてコマンド実行している。
${#} =+$( ) ;
${!.*}=${#};
${[/)}= ++ ${#} ;
${).+}= ++${#} ;
${=-$} =++${#};
${)}=++${#} ;
${+}= ++ ${#} ;
${``}= ++ ${#};
${/} =++${#};
${/[(}= ++ ${#};
${+-.} = ++${#} ;
${;%$} = "[" +"$(@{} )"[ ${/}]+"$(@{})"[ "${[/)}"+"${+-.}"] + "$( @{ } )"[ "${).+}" +"${!.*}"] +"$? "[ ${[/)} ] + "]" ;
${#}="".("$( @{} ) "[ "${[/)}" +"${)}"]+"$( @{} ) "["${[/)}" + "${``}" ]+ "$(@{ } ) "[${!.*}]+"$( @{}) "[ ${)} ] + "$?"[${[/)} ]+ "$(@{ } )"[ ${=-$}] );
${#} ="$(@{ } )"["${[/)}" +"${)}" ] +"$(@{ })"[ ${)}] +"${#}"["${).+}"+"${/}" ] ;
. ${#} ( " ${#}(${;%$}${``}${+}+ ${;%$}${[/)}${!.*}${!.*} + ${;%$}${[/)}${!.*}${!.*}+${;%$}${)}${+}
...省略
.をechoに置き換えることで実行されるコマンドを確認する。
PS C:\Users\a> echo ${#} ( " ${#}(${;%$}${``}${+}+ ${;%$}${[/)}${!.*}${!.*} + ${;%$}${[/)}${!.*}${!.*}+${;%$}${)}${+}
...省略
iex
iex([CHar]65+ [CHar]100 + [CHar]100+[CHar]45 +[CHar]84+ [CHar]121 + [CHar]112+[CHar]101 + [CHar]32+ [CHar]45 +[CHar]65+[CHar]115 + [CHar]115 + [CHar]101
+[CHar]109+[CHar]98+ [CHar]108 + [CHar]121+[CHar]78 + [CHar]97+ [CHar]109+ [CHar]101+[CHar]32+ [CHar]83 + [CHar]121 +[CHar]115+ [CHar]116 +[CHar]101 + [
CHar]109+[CHar]46+[CHar]115+[CHar]112 + [CHar]101 + [CHar]101+[CHar]99 + [CHar]104+ [CHar]13+ [CHar]10+ [CHar]36+[CHar]115 + [CHar]112+ [CHar]101+[CHar]97
...省略
iexをechoに置き換えることで実行されるコマンドを確認する。
PS C:\Users\a> echo([CHar]65+ [CHar]100 + [CHar]100+[CHar]45 +[CHar]84+ [CHar]121 + [CHar]112+[CHar]101 + [CHar]32+ [CHar]45 +[CHar]65+[CHar]115 + [CHar]115 + [CHar]101
+[CHar]109+[CHar]98+ [CHar]108 + [CHar]121+[CHar]78 + [CHar]97+ [CHar]109+ [CHar]101+[CHar]32+ [CHar]83 + [CHar]121 +[CHar]115+ [CHar]116 +[CHar]101 + [
CHar]109+[CHar]46+[CHar]115+[CHar]112 + [CHar]101 + [CHar]101+[CHar]99 + [CHar]104+ [CHar]13+ [CHar]10+ [CHar]36+[CHar]115 + [CHar]112+ [CHar]101+[CHar]97
...省略
Add-Type -AssemblyName System.speech
$speak = New-Object System.Speech.Synthesis.SpeechSynthesizer
$speak.Speak("Listen! Listen! FLAG is FLAG_anglerfish" )
答えは FLAG_anglerfish
binary-pulsarさんのブログがとても参考になりました。
Q4. sc
独力で解ける気がしないので@ttt8933さんのWriteupを追いかけました。
この問題から得るべき教訓は一定のパターンが現れる16進数列はバイナリコードである可能性を疑え
ですね。
4883ec4031c0c745c80802150fc745cc046f6d35c745d02a2a6e6cc745d4
286b6334c745d82a342b68c745dc62286b63c745e0636a6c34c745e4282a
356fc745e86f6b2b28c745ec2a000000488d45c8488945f0e92e01000048
8b45f00fb60083f05b89c2488b45f08810488b45f00fb6003c600f8e8100
0000488b45f00fb6003c7a7f76488b45f00fb60083e86189c2488b45f088
10488b45f00fb60083c00d89c2488b45f08810488b45f00fb610660fbeca
89c8c1e00201c8c1e00429c866c1e80889c1c0f90389d0c0f80729c189c8
b91a0000000fafc129c289d0488b55f08802488b45f00fb60083c06189c2
488b45f08810e987000000488b45f00fb6003c407e7c488b45f00fb6003c
5a7f71488b45f00fb60083e84189c2488b45f08810488b45f00fb60083c0
0d89c2488b45f08810488b45f00fb610660fbeca89c8c1e00201c8c1e004
29c866c1e80889c1c0f90389d0c0f80729c189c8b91a0000000fafc129c2
89d0488b55f08802488b45f00fb60083c04189c2488b45f08810488345f0
01488b45f00fb60084c00f85c3feffff488d45c84889c6b801000000bf01
000000ba250000000f05c9c3
sub rspで変数領域を確保してDWORDを大量に宣言しており、確かにバイナリコードらしい特徴があります。
末尾はもっと特徴的です。 定義した変数のアドレスに対してシステムコールを呼び出し最後にRETしています。 warabanshi21さんのブログを参考にすると、[RBP-38]のアドレスから25byteを画面に出力する処理のようです。
0000000000000196 488D45C8 LEA RAX,[RBP-38]
000000000000019A 4889C6 MOV RSI,RAX
000000000000019D B801000000 MOV EAX,00000001
00000000000001A2 BF01000000 MOV EDI,00000001
00000000000001A7 BA25000000 MOV EDX,00000025
00000000000001AC 0F05 SYSCALL
00000000000001AE C9 LEAVE
00000000000001AF C3 RET
@ttt8933さんのwriteupはとても簡潔にまとめられています。 payloadを差し替えれば別のバイナリコードでも動くことを確認できたので今後も参考にさせていただきます。