更新日時で差をつけろ

もはや更新日時でしか差を付けられない

SECCON 2017 Online CTF に参加した

テスト期間中ですがチームBiPhoneからSECCON 2017 Online CTFに参加しました。
チームでは900点、個人では600点でした(Thanks入れて)。
pwn1問も解けなかったのが残念です。Vigenere3d, Baby Stack, Election, z80, printf_machineはどういう解法なのかあとで見てみます。
以下解いた3問のwrite-up。

Powerful_Shell

powerful_shell.ps1というPowerShellスクリプトが渡される。
LinuxにはPowerShellが入っていないので、インストールする。
https://github.com/PowerShell/PowerShell/releases/
ここからAppImageを落とすだけなので簡単。debを落としてもうまく動かなかった...
実行すると「SECCON」のロゴと鍵盤が出てくる。そしてNo Admin rights!などと言って落ちる。

中身はこんな感じ。

$ECCON+=[char](137-127);  
$ECCON+=[char](-905+918);  
$ECCON+=[char](873-863);                                                                                                                          
$ECCON+=[char](721-708);  
$ECCON+=[char](803-793);  
$ECCON+=[char](10426/802);  
Write-Progress -Activity "Extracting Script" -status "20040" -percentComplete 99;  
$ECCON+=[char](520-510);  
Write-Progress -Completed -Activity "Extracting Script";.([ScriptBlock]::Create($ECCON))  

文字を突っ込んでevalっぽいことをしているので、処理内容をファイルに書き出してみる。output.txtに内容が吐き出される。

Write-Progress -Activity "Extracting Script" -status "20040" -percentComplete 99;  
$ECCON+=[char](520-510);  
$ECCON | Out-File 'output.txt' -Append  
# Write-Progress -Completed -Activity "Extracting Script";.([ScriptBlock]::Create($ECCON))  

結果は下の通り。読んでみると、
- 権限のチェック <# Host Check #>
- 鍵盤を正しく打てたかチェック、正しければCorrect. Move to the next stage
- base64文字列$textをxorなどでごにょごにょしてスクリプトとして実行する(iexはElixirではないみたい)
という処理をしている。

とりあえず最後で何が実行されているのか見たい。
権限のチェック部分はまるまる削除しても大丈夫そうだが、打った鍵盤のデータ($f)は後で使っているのでこの$fを導きだす。

(略)  
   
<# Host Check #>
Write-Host -b 00 -f 15 Checking Host... Please wait... -n  
Try{  
    If ((Get-EventLog -LogName Security | Where EventID -Eq 4624).Length -Lt 1000) {  
        Write-Host "This host is too fresh!"  
        Exit  
    }  
}Catch{  
    Write-Host "Failed: No admin rights!"  
    Exit  
}  
Write-Host "Check passed"  
   
$keytone=@{'a'=261.63}  
$pk='a'  
ForEach($k in ('w','s','e','d','f','t','g','y','h','u','j','k')){  
    $keytone+=@{$k=$keytone[$pk]*[math]::pow(2,1/12)};$pk=$k    
}  
Write-Host -b 00 -f 15 "Play the secret melody."  
(略)  
Write-Host  
$stage1=@();$f="";  
While($stage1.length -lt 14){  
    $key=(Get-Host).ui.RawUI.ReadKey("NoEcho,IncludeKeyDown")  
    $k=[String]$key.Character  
    $f+=$k;  
    If($keytone.Contains($k)){  
        $stage1+=[math]::floor($keytone[$k])  
        [console]::beep($keytone[$k],500)  
    }  
}  
$secret=@(440,440,493,440,440,493,440,493,523,493,440,493,440,349)  
If($secret.length -eq $stage1.length){  
    For ($i=1; $i -le $secret.length; $i++) {  
        If($secret[$i] -ne $stage1[$i]){  
            Exit  
        }  
    }  
    x "Correct. Move to the next stage."  
}  
$text=@"  
YkwRUxVXQ05DQ1NOE1sVVU4TUxdTThBBFVdDTUwTURVTThMqFldDQUwdUxVRTBNEFVdAQUwRUxtT  
TBEzFVdDQU8RUxdTbEwTNxVVQUNOEFEVUUwdQBVXQ0NOE1EWUUwRQRtVQ0FME1EVUU8RThdVTUNM  
EVMVUUwRFxdVQUNCE1MXU2JOE0gWV0oxSk1KTEIoExdBSDBOE0MVO0NKTkAoERVDSTFKThNNFUwR  
FBVINUFJTkAqExtBSjFKTBEoF08RVRdKO0NKTldKMUwRQBc1QUo7SlNgTBNRFVdJSEZCSkJAKBEV  
QUgzSE8RQxdMHTMVSDVDSExCKxEVQ0o9SkwRQxVOE0IWSDVBSkJAKBEVQUgzThBXFTdDRExAKhMV  
Q0oxTxEzFzVNSkxVSjNOE0EWN0NITE4oExdBSjFMEUUXNUNTbEwTURVVSExCKxEVQ0o9SkwRQxVO  
EzEWSDVBSkJAKBEVQUgzThAxFTdDREwTURVKMUpOECoVThNPFUo3U0pOE0gWThNEFUITQBdDTBFK  
F08RQBdMHRQVQUwTSBVOEEIVThNPFUNOE0oXTBFDF0wRQRtDTBFKFU4TQxZOExYVTUwTSBVMEUEX  
TxFOF0NCE0oXTBNCFU4QQRVBTB1KFU4TThdMESsXQ04TRBVMEUMVThNXFk4TQRVNTBNIFUwRFBdP  
EUEXQ0ITShdME0EVThBXFU4TWxVDThNKF0wRMBdMETUbQ0wRShVOE0MWThMqFU1ME0gVTBFDF08R  
QxdMHUMVQUwTSBVOEEEVThNNFUwRNRVBTBFJF0wRQxtME0EVTBFAF0BOE0gVQhNGF0wTKhVBTxFK  
F0wdMxVOEzUXQ04QSBVOE0AVTBFVFUFMEUkXTBFDG0wTQRVMETMXQE4TSBVCE0MXTBNBFU4QQRVB  
TB1KFU4TQxdMEVYXTBEUG0NMEUoVThNBFk4TQRVCEygXQ0wRShdPEUMXTB1DFU4TQBdDThBIFU4T  
SBVMESgVQUwRSRdMEUYbTBMWFUNOE0gWThNCFUITFBdDTBFKF08RQxdMHUMVThNVF0NOEEgVThNN  
FUwRQxVOE0IWQUwRShtME0EVTBFVF08RQxdDQhNKF0wTQRVOEEEVThM9FUNOE0oXTBFFF0wRKBtD  
TBFKFU4TQRZOE0EVQhNAF0NMEUoXTxFDF0wdVRVOEzMXQ04QSBVOE00VTBFVFU4TQRZBTBFKG0wT  
RBVMESgXQE4TSBVCE0MXTBNBFU4QKhVBTB1KFU4TFBdMEUIXQ04TRBVMEUMVThNBFk4TNxVNTBNI  
FUwRQxdPEUMXTB01FUFME0gVThBBFU4TTRVMERQVQUwRSRdMEUMbTBNBFUwRQxdAThNIFUITQxdM  
E0EVThAxFUFMHUoVThNDF0wRVhdMEVUbQ0wRShVOE0QWThMWFU1ME0gVTBFDF08RRhdDQhNKF0wT  
QRVOEFcVQUwdShVOE0EXTBFFF0NOE0QVTBFDFU4TVxZOEyoVTUwTSBVMETMXTxFVF0NCE0oXTBNE  
FU4QQhVBTB1KFU4TQBdMERcXQ04TRBVMEUAVThNDFkFMEUobTBNCFUwRQRdAThNIFUITQRdMExYV  
QU8RShdMHUEVThNOF0NOEEgVThNIFUwRKBVBTBFJF0wRMxtMEzcVQ04TSBZOE0EVQhNVF0wTQRVB  
TxFKF0wdQxVOE0MXTBFFF0NOE0QVTBFGFU4TKhZBTBFKG0wTRBVMERQXQE4TSBVCE04XTBNXFUFP  
EUoXTB0zFU4TThdDThBIFU4TTRVMEUMVThMWFkFMEUobTBNCFUwRFBdAThNIFUITQxdME0EVThAx  
FUFMHUoVThNGF0wRQxdDThNEFUwRQRVOEyoWQUwRShtMEzcVTBFDF0BOE0gVQhMzF0wTFhVBTxFK  
F0wdMxVOExQXQ04QSBVOE0gVTBEUFUFMEUkXTBEzG0wTQRVDThNIFk4TQRVCEygXTBNEFUFPEUoX  
TB1DFU4TRhdDThBIFU4TTRVMEVUVQUwRSRdMERQbQ0wRShVOE0wWThNDFU1ME0gVTBFDF08RQxdM  
HTMVQUwTSBVOEEEVThNbFUwRNRVBTBFJF0wRQxtME0EVTBFAF0BOE0gVQhNDF0wTVxVOEEEVQUwd  
ShVOEzMXTBE2F0NOE0QVTBFBFU4TKhZBTBFKG0wTQRVMEUMXTxFDF0NCE0oXTBNBFU4QQRVOEzsV  
Q04TShdMEUAXTBFDG0wTQhVDThNIFk4TRBVCEygXQ0wRShdPEUYXTB0UFUFME0gVThBDFU4TTRVD  
ThNKF0wRQBdMEUMbTBNBFUNOE0gWThNBFUITQxdME0EVQU8RShdMHUMVThNVF0wRVhdDThNEFUwR  
RhVOEyoWQUwRShtME0MVTBEzF0BOE0gVQhNDF0wTQRVOEEEVQUwdShVOExQXTBFNF0NOE0QVTBFG  
FU4TRBZBTBFKG0wTRBVMERQXQE4TSBVCEzUXTBMWFUFPEUoXTB1DFU4TRhdDThBIFU4TTRVMEVUV  
QUwRSRdMERQbQ0wRShVOE0wWThNDFU1ME0gVTBFDF08RQxdMHTMVQUwTSBVOEEEVThNbFUwRNRVB  
TBFJF0wRQxtME0EVTBFAF0BOE0gVQhNDF0wTVxVOEEEVQUwdShVOEzMXTBE2F0NOE0QVTBFBFU4T  
KhZBTBFKG0wTQRVMEUMXTxFDF0NCE0oXTBNBFU4QQRVOEzsVQ04TShdMEUAXTBFDG0wTQhVDThNI  
Fk4TRBVCEygXQ0wRShdPEUYXTB0zFUFME0gVThBMFU4TSBVDThNKF0wRQxdMERQbQ0wRShVOE0IW  
ThNDFU1ME0gVTBFAF08RQRdDQhNKF0wTQxVOEBYVQUwdShVOE0EXTBFNF0NOE0QVTBFDFU4TKhZO  
E0QVTUwTSBVMEUYXTxFAF0NCE0oXTBNCFU4QFhVBTB1KFU4TQBdMEUIXQ04TRBVMEUAVThNDFkFM  
EUobTBNDFUwRFBdAThNIFUITQRdME0wVQU8RShdMHUMVThMoF0wRNhdDThNEFUwRRhVOEzEWQUwR  
ShtME0EVTBFGF0BOE0gVQhNDF0wTVxVBTxFKF0wdQxVOEygXTBE2FxROE10VShZOTBFTF2E=  
"@  
   
$plain=@()  
$byteString = [System.Convert]::FromBase64String($text)  
$xordData = $(for ($i = 0; $i -lt $byteString.length; ) {  
    for ($j = 0; $j -lt $f.length; $j++) {  
        $plain+=$byteString[$i] -bxor $f[$j]  
        $i++  
        if ($i -ge $byteString.Length) {  
            $j = $f.length  
        }  
    }  
})  
iex([System.Text.Encoding]::ASCII.GetString($plain))  

鍵盤のチェック部分では、打った文字が$fに、それに対応する音階$keytone$stage1に追加される。そして$stage1$secretと等しければチェックに通る。さくっとRubyを書いてキーに対応する音階を出してみたけど、今思えばPowerShellコードを切り取ってきて動かせばよかった。

require 'pp'  
keytone = {"a" =&gt; 261.63}  
   
cur = "a"  
"wsedftgyhujk".chars.each do |ch|  
  keytone[ch] = keytone[cur] * 1.0594630943593  
  cur = ch  
end  
   
pp keytone  
$ ruby beep.rb  
{"a"=&gt;261.63,  
 "w"=&gt;277.18732937722365,  
 "s"=&gt;293.66974569918386,  
 "e"=&gt;311.13225749816604,  
 "d"=&gt;329.6331442840015,  
 "f"=&gt;349.23415104651383,  
 "t"=&gt;370.0006943236827,  
 "g"=&gt;392.00208052325837,  
 "y"=&gt;415.3117372264548,  
 "h"=&gt;440.0074582456763,  
 "u"=&gt;466.17166325413467,  
 "j"=&gt;493.89167285384707,  
 "k"=&gt;523.2600000000278}  

あとは$secret=@(440,440,493,440,440,493,440,493,523,493,440,493,440,349)に当てはめると$fhhjhhjhjkjhjhfとわかる。そしてコードの$fを書き換え、$textの変換結果をファイルに書き込めばよい。ここでもなぜかRubyで書き直したりして時間つぶれた。もっと素材の味を活かさないと。
最後の行のiexを外すだけで出力してくれる。

[System.Text.Encoding]::ASCII.GetString($plain)  

出力結果はこれ。

${;}=+$();${=}=${;};${+}=++${;};${@}=++${;};${.}=++${;};${[}=++${;};  
${]}=++${;};${(}=++${;};${)}=++${;};${&amp;}=++${;};${|}=++${;};  
${"}="["+"$(@{})"[${)}]+"$(@{})"["${+}${|}"]+"$(@{})"["${@}${=}"]+"$?"[${+}]+"]";  
${;}="".("$(@{})"["${+}${[}"]+"$(@{})"["${+}${(}"]+"$(@{})"[${=}]+"$(@{})"[${[}]+"$?"[${+}]+"$(@{})"[${.}]);  
${;}="$(@{})"["${+}${[}"]+"$(@{})"[${[}]+"${;}"["${@}${)}"];"${"}${.}${(}+${"}${(}${|}+${"}${(}${)}+${"}${(}${)}+${"}${)}${|}+${"}${)}${&amp;}+${"}${(}${+}+${"}${&amp;}${@}+${"}${+}${=}${+}+${"}${|}${)}+${"}${+}${=}${=}+${"}${[}${]}+${"}${)}${@}+${"}${+}${+}${+}+${"}${+}${+}${]}+${"}${+}${+}${(}+${"}${.}${@}+${"}${[}${]}+${"}${&amp;}${=}+${"}${+}${+}${[}+${"}${+}${+}${+}+${"}${+}${=}${|}+${"}${+}${+}${@}+${"}${+}${+}${(}+${"}${.}${@}+${"}${.}${|}+${"}${(}${|}+${"}${+}${+}${=}+${"}${+}${+}${(}+${"}${+}${=}${+}+${"}${+}${+}${[}+${"}${.}${@}+${"}${+}${+}${(}+${"}${+}${=}${[}+${"}${+}${=}${+}+${"}${.}${@}+${"}${+}${+}${@}+${"}${|}${)}+${"}${+}${+}${]}+${"}${+}${+}${]}+${"}${+}${+}${|}+${"}${+}${+}${+}+${"}${+}${+}${[}+${"}${+}${=}${=}+${"}${.}${|}+${"}${+}${.}+${"}${+}${=}+${"}${)}${.}+${"}${+}${=}${@}+${"}${[}${=}+${"}${.}${(}+${"}${(}${|}+${"}${(}${)}+${"}${(}${)}+${"}${)}${|}+${"}${)}${&amp;}+${"}${.}${@}+${"}${[}${]}+${"}${+}${=}${+}+${"}${+}${+}${.}+${"}${.}${@}+${"}${.}${|}+${"}${&amp;}${=}+${"}${[}${&amp;}+${"}${+}${+}${|}+${"}${(}${|}+${"}${+}${+}${[}+${"}${.}${(}+${"}${)}${@}+${"}${]}${+}+${"}${[}${|}+${"}${[}${|}+${"}${.}${|}+${"}${[}${+}+${"}${+}${@}${.}+${"}${+}${.}+${"}${+}${=}+${"}${|}+${"}${&amp;}${)}+${"}${+}${+}${[}+${"}${+}${=}${]}+${"}${+}${+}${(}+${"}${+}${=}${+}+${"}${[}${]}+${"}${)}${@}+${"}${+}${+}${+}+${"}${+}${+}${]}+${"}${+}${+}${(}+${"}${.}${@}+${"}${.}${|}+${"}${)}${+}+${"}${+}${+}${+}+${"}${+}${+}${+}+${"}${+}${=}${=}+${"}${.}${@}+${"}${)}${[}+${"}${+}${+}${+}+${"}${|}${&amp;}+${"}${.}${.}+${"}${.}${|}+${"}${]}${|}+${"}${+}${.}+${"}${+}${=}+${"}${|}+${"}${&amp;}${)}+${"}${+}${+}${[}+${"}${+}${=}${]}+${"}${+}${+}${(}+${"}${+}${=}${+}+${"}${[}${]}+${"}${)}${@}+${"}${+}${+}${+}+${"}${+}${+}${]}+${"}${+}${+}${(}+${"}${.}${@}+${"}${.}${[}+${"}${&amp;}${.}+${"}${(}${|}+${"}${(}${)}+${"}${(}${)}+${"}${)}${|}+${"}${)}${&amp;}+${"}${+}${@}${.}+${"}${.}${(}+${"}${(}${|}+${"}${(}${)}+${"}${(}${)}+${"}${)}${|}+${"}${)}${&amp;}+${"}${+}${@}${]}+${"}${.}${[}+${"}${+}${.}+${"}${+}${=}+${"}${+}${@}${]}|${;}"|&amp;${;}  

SCKOSEN を思い出す難読コード。これを実行するとEnter the password:と聞かれる。

一つびっくりしたのが、${}で囲うことで記号を変数名にできること。最初は手動で解いていたが、途中で${;}"iex"なのであれば、最後の|&amp;${;}(HHKB初心者殺しの記号)を取り除けばコードが実行されないのではと思い取り除いて実行してみた。

[CHar]36+[CHar]69+[CHar]67+[CHar]67+[CHar]79+[CHar]78+[CHar]61+[CHar]82+[CHar]101+[CHar]97+[CHar]100+[CHar]45+[CHar]72+[CHar]111+[CHar]115+[CHar]116+[CHar]32+[CHar]45+[CHar]80+[CHar]114+[CHar]111+[CHar]109+[CHar]112+[CHar]116+[CHar]32+[CHar]39+[CHar]69+[CHar]110+[CHar]116+[CHar]101+[CHar]114+[CHar]32+[CHar]116+[CHar]104+[CHar]101+[CHar]32+[CHar]112+[CHar]97+[CHar]115+[CHar]115+[CHar]119+[CHar]111+[CHar]114+[CHar]100+[CHar]39+[CHar]13+[CHar]10+[CHar]73+[CHar]102+[CHar]40+[CHar]36+[CHar]69+[CHar]67+[CHar]67+[CHar]79+[CHar]78+[CHar]32+[CHar]45+[CHar]101+[CHar]113+[CHar]32+[CHar]39+[CHar]80+[CHar]48+[CHar]119+[CHar]69+[CHar]114+[CHar]36+[CHar]72+[CHar]51+[CHar]49+[CHar]49+[CHar]39+[CHar]41+[CHar]123+[CHar]13+[CHar]10+[CHar]9+[CHar]87+[CHar]114+[CHar]105+[CHar]116+[CHar]101+[CHar]45+[CHar]72+[CHar]111+[CHar]115+[CHar]116+[CHar]32+[CHar]39+[CHar]71+[CHar]111+[CHar]111+[CHar]100+[CHar]32+[CHar]74+[CHar]111+[CHar]98+[CHar]33+[CHar]39+[CHar]59+[CHar]13+[CHar]10+[CHar]9+[CHar]87+[CHar]114+[CHar]105+[CHar]116+[CHar]101+[CHar]45+[CHar]72+[CHar]111+[CHar]115+[CHar]116+[CHar]32+[CHar]34+[CHar]83+[CHar]69+[CHar]67+[CHar]67+[CHar]79+[CHar]78+[CHar]123+[CHar]36+[CHar]69+[CHar]67+[CHar]67+[CHar]79+[CHar]78+[CHar]125+[CHar]34+[CHar]13+[CHar]10+[CHar]125|iex  

ここから更に|iexを取り除いてPowerShellから実行させるとFLAGが出てくる。SECCON{P0wEf$H311}

$ECCON=Read-Host -Prompt 'Enter the password'  
If($ECCON -eq 'P0wEr$H311'){  
    Write-Host 'Good Job!';  
    Write-Host "SECCON{$ECCON}"  
}  

Log Search

解いたチームが多かったものの最後まで解けず焦った。
Elasticsearchの仕様を活かしてログからフラグを探すみたい。
他のチームのHTTPリクエストもログにどんどん溜まっていく。

CTFの開始時間である15:00より前に記録されているログに、FLAGが隠されているのではないかと考えて、ログの表示件数を増やすか時刻で絞り込んで検索したくなった。

PHPからElasticsearchに投げるクエリの形式はJSONだそうなので、SQLiの感覚で" } }, { "match": {"response": "404などと打ってみるも失敗。

Elasticsearchのドキュメントを調べると、query string queryというものを見つけた。これを使えばtimestamp属性で絞りこめるのでは?
そしてtimestamp:{* TO "09/Dec/2017:22:00:00 +0900"] AND response:"200" と入れてみたがダメだった。
timestamp:"09/Dec/2017:22:00:00 +0900" AND response:"400"はうまく行った。

「んん〜??これ本当に100か〜?」と思いつつさらに調べると、@timestampというデフォルトのフィールドがあるそうだ。
https://github.com/elastic/elasticsearch-dsl-py/issues/49

The problem is that you have a field 'timestamp' that is a string, not a date so a range filter won't work on it as you expect. In that case you need to use the '@timestamp' field in your filter which looks like it is a date.

@timestamp:{* TO 2017-12-09T10:00:00+09:00} AND response:"200" AND flagと打つとこんな画面が。
Gyazo
アクセスするとFLAGが。
SECCON{N0SQL_1njection_for_Elasticsearch!}

こういうことを避けるためにQiitaは自前でquery string queryを実装している

JPEG file

灰色のJPG画像が渡される。1bit書き換えればいいみたい。
JPEG(JEIF)のフォーマットを調べたものの、どこが間違っているか分からなかったので1bitずつ反転した画像を40000枚ほど生成した。ファイルが多すぎて面倒だったので8x8=64枚ごとに整理していい感じのを探した。

bytes = File.binread "tktk.jpg"  
   
bytes.chars.each_with_index do |ch, i|  
    memo = bytes[i]  
   
    memo_byte = bytes[i].unpack("H*")[0].hex  
    8.times do |k|   
        byte = memo_byte ^ (1&lt;&lt;k) # change 1 bit  
        bytes[i] = byte.chr  
        File.binwrite("results/#{8*i+k}.jpg", bytes)  
    end  
   
    bytes[i] = memo  
end  
   
(93024/8).times do |i| # 横に8連結, 93024はファイルサイズ  
    scr = "convert +append"  
    8.times do |k|  
        scr += " results/#{8*i+k}.jpg"  
    end  
    scr += " results/al#{i}.jpg"  
   
    system scr  
end  
   
(93024/8/8).times do |i| # それをさらに縦に8連結  
    scr = "convert -append"  
    8.times do |k|  
        scr += " results/al#{8*i+k}.jpg"  
    end  
    scr += " results/bl#{i}.jpg"  
   
    system scr  
end  


SECCON{jp3g_study}

来年は100位に入りたいなあ(遠い目)