見出し画像

合宿で主催したCTFの全問解説

合宿でCTFを主催した記事の続きです。
この記事では僕が自作した問題の全問解説を行ないます。
合宿で行なわれたCTFについては前記事を見てください。
問題と解き方を羅列するだけです。
全問に僕の感想が入っています。

Welcome to OCTF
tutorial : 10

問題
the flag is octf{enjoy_together}

flag
octf{enjoy_together}

感想
幹事長が作れって言ったから作った
上級生がサクッと回答しちゃったからほとんど意味がなかったらしい

GreatestClickMan
proce55ing : 100

問題

ファイルはこちら
code/millionMouse.jar
greatestClickMan.pde
が入っている。
greatestClickManのコードは以下

millionMouse mm;
       
void setup(){
       size(100,100);
       mm=new millionMouse(this);
}
void draw(){
       mm.octf(0);
}

解き方
とりあえず画面が小さいので、size(1200,800);にする
ProcessingIDEの設定から、Ctrl+Enterでコード補完をしてくれる
millionMouseクラスの関数cheatを探す
setup内でmm.cheat(1000000);を実行
flagとしてoctf{master_Takahashi}が表示される

flag
octf{master_Takahashi}

作り方
ライブラリを作る

package millionMouse
       
import processing.core.*
       
public class millionMouse(val pa: PApplet) {
    var count=0
    var pFlag=false
    public fun octf(color: Int){
        pa.textAlign(processing.core.PConstants.CENTER,processing.core.PConstants.CENTER)
        if(pFlag!=pa.mousePressed){
            count++
            pFlag=pa.mousePressed
        }
        if(count<1000000){
            pa.fill(color)
            pa.textSize(20.0f)
            pa.text("Click million times!",pa.width/2.0f,pa.height/2.0f)
        }else{
            pa.fill(255f,0f,0f)
            pa.textSize(100.0f)
            pa.text("octf{master_Takahashi}",pa.width/2.0f,pa.height/2.0f)
        }
    }
       
    public fun p(){
        println(count)
    }
       
    public fun cheat(c: Int){
        count+=c
        println(/*AA略*/)
    }
       
    fun main(args: Array<String>) {
    
    }
}
       

AAはどうでも良いので略
ライブラリ化の方法はれじん先輩のブログを見た
http://pvcresin.hatenablog.com/entry/2016/03/17/223048

感想
デコンパイルする人もいるらしい
みんな解けててうれしいよ

GET THE POINTS!!!
proce55ing : 200

問題
http://octf.ga/q/04/

解き方
ソースを読む
http://octf.ga/q/04/point.php?num=10 からJSONを取ってるらしい

[[260,459],[378,120],[92,167],[435,165],[330,143],[31,512],[752,443],[580,763],[853,785],[418,820]]

processingでコード書く

String url="http://octf.ga/q/04/point.php?num=5000";
       
void setup(){
    size(1000,900);
    background(255);
    fill(0);
    JSONArray json=loadJSONArray(url);
    for(int i=0;i<json.size();i++){
        int x=json.getJSONArray(i).getInt(0);
        int y=json.getJSONArray(i).getInt(1);
        ellipse(x,y,20,20);
    }
}

解き方2
developer toolのコンソールに以下を書き込む

noLoop();
var request = new XMLHttpRequest();
request.open("GET", "./point.php?num=5000");
request.addEventListener("load", loadPoint);
request.send();

感想
みんな結構苦しんでたらしい
解けなかったチームも1つあった
B2には正攻法で解いて欲しい

foolproof
Crypt : 50

問題

ixrd`dubfwe?ua?akuoows?dein?jwt*

解き方
JISキーボードの配列を見る
それぞれの文字の1つ右にあるキーを押す

flag
octf{finger_is_slipped_from_key}

感想
合宿前日の夜中に考えた
幹事長がしばらく悩んでた
幹事長のキーボードはUS配列
当日はわりと高速で解かれた

Nyctereutes procyonoides
Crypt : 100

問題

6aatfta6aa3ta7tttt4tt66att7ttbaaaa53tt75at6aaaaaaattatatdt6ttdata6t5a72a5f5ttat6a6taa16t3ataa61taa74tttat69ttataatttaaa6atafa6attaet5faaat69ttaaa7ta35aatfttt73atatt6af5fttatt3ataattttaa6tta7tttttatatta2tt3t3tt617att42attt1ttatata7tttad

解き方
これはたぬきの画像
文字列からtとaを抜く
16進数になるので、こういうサイトで文字列に直す

解き方2
幹事長がワンライナーで解いた
ワンライナーだけど見づらいので改行して見やすくします (progfay)

'6aatfta6aa3ta7tttt4tt66att7ttbaaaa53tt75at6aaaaaaattatatdt6ttdata6t5a72a5f5ttat6a6taa16t3ataa61taa74tttat69ttataatttaaa6atafa6attaet5faaat69ttaaa7ta35aatfttt73atatt6af5fttatt3ataattttaa6tta7tttttatatta2tt3t3tt617att42attt1ttatata7tttad'
       .replace(/[ta]/g, '')          // tとaを削除
       .split(/(.{2})/)               // 2文字毎に分割
       .filter(s => s)                // 空文字が入ってしまうのでそれを除去
       .map(hex => parseInt(hex, 16)) // 16進数から10進数へ変換
       .map(String.fromCharCode)      // 10進数から文字列に変換
       .join('')                      // 文字をつなぎ合わせる

flag
octf{Summer_Vacation_is_so_6r3at!}

感想
家族の付き添いで行った病院の中で作ったので、Greatなsummer vacationではまったくない日だった
try.kotlinのサイトをスマホで操作しながら作った
問題名はタヌキの学名から
まあそれなりに簡単だったのかなと思う

Single Chain
Crypt : 150

問題
http://octf.ga/q/14/

Tikv ny i zcqghn jafjga otetvlss. Axwp csdfcw gvu zd bdoi zv bywhr ixve chibbhaugoc. Icc T gvxdb mbvp ge zqx xzpkt hugv eivbhvnv brzrwslxhxqi kbi srf hjp qnlwg my hb kwx. Hxrde D tphtf vsrkaprxr gvpj kxyho rn cg e npvc? Fus xzfm jb qtci d uxvjuox vg vzpxx wp z gkaii zmwdpzps. Wgk ztxkolf, lyzwa://sbcvbv.vih, fstqu://ehpxy.ob.yf, hl aqa. Jv qnmob lp sobycatl pl shpuh bnv xpeqa ebrq WOB. Vemn, qka'b hdvht kzx vjpudr urss. Bqo ryov zk ixpc{srz_dpxu_bqo_zrli_gjhvgaj}.

解き方
初めから数えた文字数の分だけ、アルファベットを辞書順にシフトしている
2回出てくるhttpsとかoctfから気がつくはず
コードは省略

作り方

<?php
$text="This is a pretty simple question."
    ." Some people may be able to solve them immediately."
    ." But I think that if you stick with ordinary cryptography you can not solve it at all."
    ." Shall I write something that seems to be a hint?"
    ." The hint of such a problem is fixed as a fixed sentence."
    ." For example, https://google.com, https://yahoo.co.jp, or etc."
    ." It might be familiar to those who often play CTF."
    ." Well, let's write the answer soon."
    ." The flag is octf{try_also_the_next_problem}.";
       
$lowers = 'abcdefghijklmnopqrstuvwxyz';
$uppers = strtoupper($lowers);
$cipher="";
       
for($i=0;$i<strlen($text);$i++){
    $lc=strpos($lowers,$text[$i]);
    $uc=strpos($uppers,$text[$i]);
    if($lc>-1){
        $next=$lc+$i;
        for(;$next>=strlen($lowers);){
            $next-=strlen($lowers);
        }
        $cipher.=$lowers[$next];
    }elseif ($uc>-1){
        $next=$uc+$i;
        for(;$next>=strlen($uppers);){
            $next-=strlen($uppers);
        }
        $cipher.=$uppers[$next];
    }else{
        $cipher.=$text[$i];
    }
}
       
echo $cipher;
       
?>

flag
octf{try_also_the_next_problem}

感想
最初はDouble Chainだけだった
1問だけだと難しすぎるのでこれも作った
元々は2つ組み合わさっていないという意味でSingle Chainという名前にしたのだけど、会計が「文字列が連鎖して暗号になってるよね」みたいなことを言ってたのでそういう命名だということにした
Crypt大魔王みたいな人が一瞬で解いてしまったのは笑うしかなかった

Double Chain
Crypt : 250

問題
http://octf.ga/q/15/

解き方
5秒に1回文字列が変わる暗号文
Single Chainの構造の暗号の全体を、シフト数をミリ秒にしたシーザー暗号にかける
実際どうやって気づくんだろう
気づいたらコード書くだけだよ

作り方

<script>
    setTimeout(function () {
        window.location.reload();
    },5000);
</script>
<?php
$text="This is a very difficult question."
    ." So, you will have trouble to solve it very much."
    ." This page changes the character string displayed by time."
    ." Shall I write something that seems to be a hint?"
    ." When solving the cipher, it is good to rely on fixed sentences that do not change."
    ." For example, https://google.com, https://yahoo.co.jp, or etc."
    ." It may be familiar to those who were watching the movie The Imitation Game."
    ." Well, let's write the answer soon."
    ." The flag is octf{immediate_update_encryption_is_interesting}.";
       
$lowers = 'abcdefghijklmnopqrstuvwxyz';
$uppers = strtoupper($lowers);
$cipher="";
       
$arrTime = explode('.',microtime(true));
$dist=$arrTime[1];
       
for($i=0;$i<strlen($text);$i++){
    $lc=strpos($lowers,$text[$i]);
    $uc=strpos($uppers,$text[$i]);
    if($lc>-1){
        $next=$lc+(int)$dist+$i;
        for(;$next>=strlen($lowers);){
            $next-=strlen($lowers);
        }
        $cipher.=$lowers[$next];
    }elseif ($uc>-1){
        $next=$uc+(int)$dist+$i;
        for(;$next>=strlen($uppers);){
            $next-=strlen($uppers);
        }
        $cipher.=$uppers[$next];
    }else{
        $cipher.=$text[$i];
    }
}
       
echo $cipher;
       
?>

flag
octf{immediate_update_encryption_is_interesting}

感想
元々300点問題のつもりで作ってた
僕自身解けると思ってなかった
アップロードしたら会計が一瞬で解いたので、300点にする気が失せて250点問題として公開した
当日はSingle Chainが1チームでも解かれたら公開しようと思っていた
公開時点では翌日まで解かれることはないだろうと思っていた
公開した瞬間Crypt大魔王みたいなのが一瞬で解いてしまったので膝から崩れ落ちた
最終的に全チーム解いたので結構すごいと思う

READ SOURCE
Web : 50

問題
http://octf.ga/q/03
画像がにゅーって来るから読めない

解き方
ソースを読む
http://octf.ga/q/03/flag.png の画像を取ってきてる

感想
一瞬で解けると思った
案の定一瞬で解かれたので安心
flagがにゅーってなる見た目が好き

ESPer Stego
Stego : 300

問題
ファイルはこちら
ファイルが壊れてて読めないとか言われるときもある画像
Windowsのフォトで開くとこんな

Github的にはこんな

ヒント
10点

からころも きつつなれにし つましあれば はるばるきぬる たびをしぞおもふ

50点

たくさんの文字がバイナリに含まれているので
てきとうな文字をとりあえずうえからじゅんに
よんでみるともしかしたらわかるかもしれない
みたいなことを考えていた

解き方
画像の下の方が壊れているように見える
画像の言うとおりバイナリエディタで開く
一番下を見る

おん?

おっ!

flag
octf{you_can_become_a_computer!}

感想
エスパー問が1問くらいあってもいいじゃないと思いながら作った
けど僕はあんまりエスパーだと思ってない
アップロードしたら幹事長と会計がボロクソ言ったのでかなしいとおもった
300点問題にするから許してって言って押し切った
2人の予想は見事に当たり、解けたのは1チームだけ(しかも公開してかなり経ってから)
ごめんなさい

Twitter Adventure
Recon : 100

問題
onsen-2017-summerに関するツイートを順に探す問題
個人情報に関わるので公開しない

解き方
Twitterで検索をかける
去年の合宿は9/14~15
参加者リストはドライブに残っている
until:2017-09-16を付けて参加者のTwitterを片っ端から調べれば良いんじゃない?

感想
もともとこういう問題は会計が作るものだと思ってた
作られないので自分で作った
僕はonsen-2017-summerに行ってない
行ってない人も解くので、作るのに向いてるのかもとも思ったよ
PHP書くのがめんどくさかった
しばらく全然解かれなくてびびった
結局1チームしか解かなかったので、もったいないなあと思った
各チームに去年の参加者がいるようにチーム分けしたのだけどなあ

easy ping
Network : 50

問題
ファイルはこちら
pcapngファイルが配られる

解き方
$ strings easy_ping.pcapng
flagが出てくる

作り方
Wi-Fiを切る
VMでwiresharkを開く
$ ping VM外のパソコンのIP -p octf{ping_2_you}
wiresharkでパケットをキャプチャする
保存する

感想
会計がNetwork問としてI input the flag 1とI input the flag 2を作ったので、触発された
pingコマンドを使って疎通確認をするときに使われるICMPファイルは、パケットの長さを一定にするためにダミーデータを混ぜる
そこのダミーデータを編集したらわりかし難しい問題になるのではないかと思って作った
キャプチャし終わってからstringsしたら普通に出てきたのですごい悲しかった

dinner
Misc : 50

問題

解き方
昨日の夕食の献立の紙の写真
同じ紙を探す

flag
octf{南瓜の豆乳ポタージュ}

感想
ノリと勢いで作りました
本当にごめんなさい

exhausted QR
Misc : 100

問題

解き方
塗り絵して

作り方
「青い空を見上げればいつもそこに白い猫」を使う
元画像

ロバーツフィルタでエッジ検出

ラプラシアンフィルタでエッジ検出

ラプラシアンフィルタでさらにエッジ検出

ラプラシアンフィルタでさらにさらにエッジ検出

ソーベルフィルタでエッジ検出

感想
初日にすごい勢いで問題を解かれてしまって、焦った僕が一瞬で作った問題
解けるかどうか確認をしないで追加した
しかし塗り絵すればいいだけなので、解けることは分かっていた
解かれてほんとに安心した
これを解いてくれた神絵師はすごい

Russian Toy
Misc : 100

問題
ファイルはこちら
flag.zipが配られる

解き方
解凍すると中にまたflag.zipが入っている
さらに解凍するとflag.zipと一文字の名前が付いた0バイトのファイルが入ってる
繰り返す
最後のzipにはpasswordがかかっている
今までのフォルダにある0バイトのファイルを繋げると、password is matryoshka
パスワードにmatryoshkaを入れると開き、flagが出る

作り方
手作業で作った

感想
初日の解かれる速さに焦った僕が急いで作った問題2
単純で簡単

Flag Slot!!!
Misc : 150

問題
http://octf.ga/q/06/

解き方
コードを読むとこんなところがある

var array = [111,99,116,102,123,85,110,105,99,111,100,101,95,105,115,95,86,101,114,114,114,121,95,70,117,110,125];

それぞれString.fromCodePoint(array[i])などして戻してくれ

感想
自分で作ったけど最初はクソ問だと思ってた
本番では想定しなかった解き方があって一番笑った
作ってよかった

VOICE
Misc : 300

問題

PGraphics pg=createGraphics(1000, 600);
for (int c=0; c*1000<array[0].length; c++) {
         pg.beginDraw();
         pg.background(255);
         for (int i=0; i<1000&&c*1000+i<array[0].length; i++) {
                 pg.point(i, map(array[0][c*1000+i], -1, 1, 100, height/2-100));
                 pg.point(i, map(array[1][c*1000+i], -1, 1, height/2+100, height-100));
         }
         pg.endDraw();
         pg.save("image"+c+".png");
}

ファイルはこちら
こんな感じの画像が943枚入ってるzip

ヒント
50点
https://fms-cat.tumblr.com/post/135847863609/processingで作曲をする-part-1

解き方
ヒントの中にあるarrayToWav関数をそのまま使う
RATEは44100
幹事長のコードがよかったので借りる

final int RATE = 44100;
float[][] array = new float [2][21000 * 944];
       
void setup() {
    for (int i = 0; i < 944; i++) {
        PImage img = loadImage("img/image" + i + ".png");
       for (int j = 0; j < 1000; j++) {
            int a = -1;
            int b = -1;
            for (int k = 0; k < 600; k++) {
                if (img.get(j, k) == -1) continue;
                if (a == -1) {
                    a = k;
                } else {
                    b = k;
                    array[0][i * 1000 + j] = map(a, 100, 200, -1, 1);
                    array[1][i * 1000 + j] = map(b, 400, 500, -1, 1);
                    break;
                }
            }
        }
    }
          
    arrayToWav(array, "flag.wav");
    println("finished");
    exit();
}

声が聞こえる

作り方

   int RATE=44100;
   float [][] wav;
   
   void setup() {
           size(1000, 600);
           byte[] file=loadBytes("flag.wav");
           wav=wavToArray(file);
   
           PGraphics pg=createGraphics(1000, 600);
           for (int c=0; c*1000<wav[0].length; c++) {
                   pg.beginDraw();
                   pg.background(255);
                   for (int i=0; i<1000&&c*1000+i<wav[0].length; i++) {
                           pg.point(i, map(wav[0][c*1000+i], -1, 1, 100, height/2-100));
                           pg.point(i, map(wav[1][c*1000+i], -1, 1, height/2+100, height-100));
                   }
                   pg.endDraw();
                   pg.save("image"+c+".png");
           }
           exit();
   }
   
   void draw() {
   }
   
   float[][] wavToArray(byte[] file) {
           float [][] wave=new float[2][(file.length-44)/4];
           for (int i=0; i<wave[0].length; i++) {
                   wave[0][i]=float(file[44+i*4+0]+(file[44+i*4+1]<<8))/32768.0;
                   wave[1][i]=float(file[44+i*4+2]+(file[44+i*4+3]<<8))/32768.0;
           }
           return wave;
   }

感想
今回の目玉、僕の最推し問
僕はFMS_Cat先輩のProcessingで音楽作る講座(2016年開講)を受けていたので、絶対に使いたかった
解いたときの爽快感を感じて欲しかった
幹事長が簡単だって言うので250点問題にしてたけど、公開してから300点問題に戻した
結局1チームしか解いてくれなかったのは深い悲しみ

ABnormal backspace
Misc : 300

問題
Processing製と思われるexeファイルがjavaランタイムごと配られる
この問題はファイルが大きいため省略

ヒント
50点

解き方
起動すると、WSLで開いてくれというメッセージが表示されて閉じる
仕方なくWSLで開くと、コンソールに以下のメッセージが表示されて閉じる
hello. my name is mayoneko. thank you for participation.
リダイレクトでコンソールのメッセージをtxtに保存する

バックスペースのエスケープシーケンスを消す

解き方2
shellsample.jarをJDでデコンパイルする

import http.requests.GetRequest;
import processing.core.PApplet;
       
public class shellsample
    extends PApplet
{
    String URL = "kwws=22jrr1jo27]M}lM";
               
    public void setup()
    {
        background(0);
        textAlign(3, 3);
        textSize(20.0F);
        fill(255);
        text("open with WSL\n./shellsample.exe", this.width / 2, this.height / 2);
        String data = "";
        char no = '\003';
        for (int i = 0; i < this.URL.length(); i++) {
            data = data + String.valueOf((char)(this.URL.charAt(i) - no));
        }
        GetRequest get = new GetRequest(data);
        get.send();
        String result = get.getContent();
        println(result);
    }
               
    public void draw()
    {
        if (this.frameCount > 50) {
            exit();
        }
    }
               
    public void settings()
    {
        size(300, 200);
    }
               
    public static void main(String[] passedArgs)
    {
    String[] appletArgs = { "shellsample" };
        if (passedArgs != null) {
            PApplet.main(concat(appletArgs, passedArgs));
        } else {
            PApplet.main(appletArgs);
        }
    }
}

URLという変数に入っている暗号化された文字列を復号化している
復号化されたURLにgetリクエストを送っている
復号化されたURLはhttp://goo.gl/4ZJziJ
アクセスすると送られてくる文字列が見える
バックスペースを消したら答え

flag
octf{you_remenbered_my_presentation?}

感想
唯一解かれなかった通常問題
僕がABPro2018でプレゼンした内容を用いた問題
内容としてはそれほど難しくないつもりだった
二通り解法あるし
でも解かれないとは思っていた
デコンパイルする人はそもそもいないと思っていた

Casquette Search
Extreme : 150

問題
https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3262.486102429498!2d139.10908497545333!3d35.14449577932423!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x0%3A0xff4101bb2adfee30!2z44Ki44Kv44Ot44K544OX44Op44K25rmv5rKz5Y6f!5e0!3m2!1sja!2sjp!4v1533914424410
注意:この問題は9月19日12時までに回答してください

解き方
問題名はキャスケットサーチ
リンクを開く
The Google Maps Embed API must be used in an iframe.
言われたとおりiframeタグで包む

<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3262.486102429498!2d139.10908497545333!3d35.14449577932423!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x0%3A0xff4101bb2adfee30!2z44Ki44Kv44Ot44K544OX44Op44K25rmv5rKz5Y6f!5e0!3m2!1sja!2sjp!4v1533914424410" />

これをブラウザで開くとアクロスプラザ湯河原の地図が表示される
行くとキャスケットをかぶった僕がいます

感想
公開してすぐにアクロスプラザ湯河原と全体Slackに投稿されてしまう
しかし誰もこない
暇すぎる
走ってアクロスプラザ湯河原に向かったのに、10時くらいから2時間くらい座って待ってた
固い椅子に座ってパソコン開いてたら隣におじいさんとかおばあさんとかが座ってきたりした
2人がけの椅子になぜか3人で座ってる時間が発生した
暇すぎてつらかったので途中で制限時間を設けさせてもらった
つらかった
一番つらい問題だった
最後にみんななだれ込んできたので全チームにflagは配った
flagを渡したのに解答しなかったチームがあったのは笑った

mayoneko's Chain
Extreme : 600

問題
問題自体は伏せて、解き方だけ置いておく
下みたいなバイナリが次々表示される動画が配られる

解き方
1-1

頑張ってバイナリを最初から順にバイナリエディタに入れる

1-2
$ binwalk binary.mp4
中に含まれるjpgを取り出す

1-3
$ strings binary.mp4
出力された文字列の\nだけ取り除く

出てくる文字列は
gyazoのURLになっている
アクセスすると文字列が薄い灰色で書かれた画像が表示される
この画像の背景色を変えて、文字列を書き写し、base64でデコードする
そのbase64を開くと以下のような画像が入っている

このロッカーの中にflagが入ってる

感想
誰も解こうとすらしてくれなかった
悲しい
みんなバイナリに触ろう

この記事が気に入ったらサポートをしてみませんか?