teppay’s log

技術ブログです。

TokyoWesterns CTF 4th 2018 Writeup

はじめに

  • Tokyo Westerns CTF 4th 2018にぼっち参戦した
  • 今回もwarmupしか解けなかった
  • お勉強が必要..
  • 複数のパケットに渡ってメッセージが隠されている系のやつは苦手だったので解けて嬉しかった
  • dec_dec_decは途中までで詰まって競技終了を迎えましたが,終了後writeupからヒントを貰ってとき直して追記しました(追記: 2018/9/3 18:45)

Writeup

Welcome[warmup]

Welcome TWCTF{Welcome_TokyoWesterns_CTF_2018!!}

SimpleAuth[warmup, web]

与えられたURLにアクセスすると以下のようなソースコードが表示される

<?php

require_once 'flag.php';

if (!empty($_SERVER['QUERY_STRING'])) {
    $query = $_SERVER['QUERY_STRING'];
    $res = parse_str($query);
    if (!empty($res['action'])){
        $action = $res['action'];
    }
}

if ($action === 'auth') {
    if (!empty($res['user'])) {
        $user = $res['user'];
    }
    if (!empty($res['pass'])) {
        $pass = $res['pass'];
    }

    if (!empty($user) && !empty($pass)) {
        $hashed_password = hash('md5', $user.$pass);
    }
    if (!empty($hashed_password) && $hashed_password === 'c019f6e5cd8aa0bbbcc6e994a54c757e') {
        echo $flag;
    }
    else {
        echo 'fail :(';
    }
}
else {
    highlight_file(__FILE__);
}

ソースを読むとURLのクエリ文字列がない時は実行されているファイル(このソースコード)が表示される
それ以外の部分を読むと一見ユーザ名とパスワードをくっつけてMD5ハッシュと比較するだけの弱い雑な認証にみえる
?action=auth&user=admin&pass=passwordというクエリ文字列をつけることで,ユーザ名admin,パスワードpasswordを投げる. ただし大きなミス?がある.

クエリ文字列をパースしてその結果を受け取っているな部分で使用されているparse_str関数について調べてみると,

void parse_str ( string $encoded_string [, array &$result ] ) URL 経由で渡されるクエリ文字列と同様に encoded_string を処理し、現在のスコープに変数をセットします。 (or in the array if result is provided)

この関数に戻り値はない.しかもパースの結果は現在のスコープの変数に入る.
つまり,?aaa=bbb&ccc=dddのようにクエリ文字列を指定すると

$aaa = "bbb";
$ccc = "ddd";

と書いたのと同じになるというわけなのでこれを利用すれば,変数$hashed_passwordに入る文字列をはじめから指定することが可能となる. またuserpassを指定してしまうとMD5ハッシュで上書きされてしまうので指定しない. よってクエリ文字列は,?action=auth&hashed_password=c019f6e5cd8aa0bbbcc6e994a54c757e

これを付けてアクセスするとフラグがきた TWCTF{d0_n0t_use_parse_str_without_result_param}

mondai.zip[warmup, misc]

zipファイルが与えられた以下の順序でといた

$ unzip mondai.zip
$ ls 
y0k0s0.zip

y0k0s0.zipにはパスワードがかかっていたのでJohnTheRipperを使ってみると

$ zip2john y0k0s0.zip > hash_y0k0s0.txt
$ john --show hash_y0k0s0.txt
y0k0s0.zip:y0k0s0:::::y0k0s0.zip

1 password hash cracked, 0 left

パスワードがy0k0s0とわかった

$ unzip -P y0k0s0 y0k0s0.zip
Archive:  y0k0s0.zip
  inflating: capture.pcapng
replace mondai.zip? [y]es, [n]o, [A]ll, [N]one, [r]ename: r
new name: mondai_2.zip
  inflating: mondai_2.zip

capture.pcapngと新しいmondai.zipがでてきたのでmondai_2.zipとrenameした mondai_2.zipにもパスワードがかかっているが,JohnTheRipperでは無理っぽい
そこでcapture.pcapngの方にヒントが隠されてるということだと思い,そっちをwiresharkで開いてみた.
中身はすべてICMPなパケットでこのどこかにzipのパスワードが隠されているはず. 正直良くわからなかったのでWiresharkでだらだら見ていたら,ICMPパケットのdataフィールドのLengthが全体的にasciiの範囲内にある気がしたので1番多いip.src == 192.168.11.3 and ip.dst == 192.168.11.5なパケットのdata.lenフィールドを取り出してasciiに変換してみた

$ tshark -r capture.pcapng -T fields -e data.len 'ip.src == 192.168.11.3 and ip.dst == 192.168.11.5' | awk '{printf "%c", $1}'
We1come

We1comeという文字列がでてきたのでパスワードに使ってみると

$ unzip -P We1come mondai_2.zip
Archive:  mondai_2.zip
  inflating: list.txt
replace mondai.zip? [y]es, [n]o, [A]ll, [N]one, [r]ename: r
new name: mondai_3.zip
  inflating: mondai_3.zip

list.txtmondai.zip(mondai_3.zipにrename)がでてきた.

$ head list.txt
nb`_hlPYgZ
dhuYP\pUks
uSXWbQPtiY
cnbs_Yeahn
ui\R_ReZ[m
gZW^b]Sjqg
^RT\nmZ`^`
mnXgPr\^[T
erRQSov[uY
lkWdn[_oTj

$ wc -l list.txt
999

このようにlist.txtにはパスワードらしき文字列が999こ入っている. この中に本物があると仮定して,これを一つずつ試していくことにした.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import zipfile

def unzip_passwd(filename, passwd):
    with zipfile.ZipFile(filename, 'r') as zip_f:
        try:
            zip_f.extractall(path='.', pwd=passwd)
            print(f'Password is {passwd}!!!')
            return True
        
        except:
            return False

def challange_all_passwd():
    pass_file = 'list.txt'
    zip_file = 'mondai_3.zip'

    with open(pass_file, 'r') as pass_f:
        for p in pass_f:
            p = p.replace('\n', '')
            p = p.replace('\r', '')
            p = p.encode('utf-8')
            ret = unzip_passwd(zip_file, p)
            if ret:
                break


if __name__ == '__main__':
    challange_all_passwd()

この実行の結果,パスワードはeVjbtTpvkUとわかった.

$ unzip -P eVjbtTpvkU mondai_3.zip
Archive:  mondai_3.zip
  inflating: 1c9ed78bab3f2d33140cbce7ea223894

1c9ed78bab3f2d33140cbce7ea223894というファイルがでてきた.
長い...Flagはまだか...

$ file 1c9ed78bab3f2d33140cbce7ea223894
1c9ed78bab3f2d33140cbce7ea223894: Zip archive data, at least v2.0 to extract

fileコマンドによるとこれもzipらしい
ファイル名がハッシュ値っぽいのでとりあえずググってみる. その結果happyhappyhappyという文字列のMD5ハッシュであることがわかった.

$ unzip -P happyhappyhappy 1c9ed78bab3f2d33140cbce7ea223894
Archive:  1c9ed78bab3f2d33140cbce7ea223894
replace mondai.zip? [y]es, [n]o, [A]ll, [N]one, [r]ename: r
new name: mondai_5.zip
  inflating: mondai_5.zip
 extracting: README.txt

はい.まだ終わりません.mondai.zipREADME.txtがでてきた

$ cat README.txt
password is too short

パスワードが短いということで最悪ブルートフォースすればいいかと思いながらJohnTheRipperを使ったらパスワードtoが求まった

$ unzip -P to mondai_5.zip
Archive:  mondai_5.zip
  inflating: secret.txt
  
$ cat secret.txt
Congratulation!
You got my secret!

Please replace as follows:
(1) = first password
(2) = second password
(3) = third password
...

TWCTF{(2)_(5)_(1)_(4)_(3)}

よって,やっっっっっと,
TWCTF{We1come_to_y0k0s0_happyhappyhappy_eVjbtTpvkU}

dec dec dec[warmup, reversing]

$ file dec_dec_dec
dec_dec_dec: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=0c8f77007398e1cb7857e481cff521122c1b2cb9, stripped

この通り64bitのELFを与えられた. IDAで開いて見てみると,コマンドライン引数でFlagを与えて実行すると,correctincorrectが表示されるよくあるタイプのやつだった 大まかには以下のような処理

  • mallocコマンドライン引数で与えられた文字列と同じ長さの領域(dest)を確保
  • strncpyで確保された領域にコマンドライン引数で与えられた文字列をdestにコピー
  • destにコピーされた文字列を関数sub1にわたし,戻り値をdestにもどす
  • destの文字列を関数sub2にわたし,戻り値をdestにもどす
  • destの文字列を関数sub3にわたし,戻り値をdestにもどす
  • 最後にdestの文字列とある文字列を比較して同じならcorrect

よってこの3つの関数を読み解けば良いわけでひとつずつ見ていく

sub1

関数の最初で'A~Za~z0~9+/'の文字列を読み込んでいる.これで頭に浮かんだのはBase64.そこでこの関数はBase64エンコードを行うものと判断.
デバッガで確かめた結果やはりBase64を求めるものだった.

sub2

これはただのRot13

sub3

ここがわからなかった... 競技終了後Writeupを確認してuuencodeだということがわかったので以降とき直し よく考えれば問題名dec_dec_decだし,上の2つエンコードだし,有名なエンコード手法を確認すれば解けたのでは??(怒)

3つの関数のエンコード手法がわかったのであとは順番にデコードすればいいだけ. 最後に比較される文字列を持ってくると@25-Q44E233=,>E-M34=,,$LS5VEQ45)M2S-),7-$/3Tのようなものだったので,(最後にスペースがあるので注意)
以下のようなファイルをuuencoded.txtという名前で保存

begin 777 out.txt
@25-Q44E233=,>E-M34=,,$LS5VEQ45)M2S-),7-$/3T 
 
end

あとは

$ uudecode uuencoded.txt
$ cat out.txt | tr A-Za-z N-ZA-Mn-za-m | base64 -D
TWCTF{base64_rot13_uu}