読者です 読者をやめる 読者になる 読者になる

Gen's blog

CTFとか、競プロとか、その他諸々

DoubleS1405 CTF Writeup

Harekazeで参加。チームで 881ptを獲得し、順位は 9 位 (得点 130 チーム中)、私はそのうち250pt(+200pt)をとった。

[Rev 100] Exec

PowerPCのバイナリが渡される。powerpc-linux-gnu-objdump で.rodata領域を見ると、flagis_MY_TRAP_CARDという文字列と?\\x148\x05\x16k?7:0\x0d1?(p3"'というバイト列が見つかる。バイナリの中身を読む前にXORを試したらフラグが出てきた。

Flag: Y0u_le4rned_pow3rpc

[Crypto 150] RSA

最初、渡されるスクリプト(の一部)が以下のようになっていたが、明らかに間違っている(recv==encという比較であれば、送られてきたencをそのまま渡せばよい)ので、enc_dataもしくはSECRET_LOGIC()を行う前のデータとの比較であると想定した。

これは後ほど修正があった様子。

if True:
    enc_data = SECRET_LOGIC()
    enc = powmod(enc_data, e, n)
    conn.send("enc : %d\ne : %d\np : %d\nq : %d\n" % (enc, e, p, q))

    recv = conn.recv(1024)

    if end - start > 1:
        conn.send("time out")
        conn.close()

    if recv == enc:
        conn.send(FLAG)
        conn.close()

    conn.send("wrong")
    conn.close()

else:
    conn.close()
    print "error"

enc, e, p, qが送られてくるので、復号することは容易だが毎回送られてくるデータは異なる。そのうちの1つに着目すると、

復号結果: 8755777115341080594761456402455437710685895952551191971666112763595998769587018749591234410595235907226473499074309848886437298673992843291278177512204044380308909676435457547909426657896511491773395035272677030211195668187992227281682442084623728957

これを16進数に変換後、hexstringとみなしてデコードした結果: NDU0NDM4NTQ2OTA1MzQxNjQ5NTY4MDE1MzY4Njk5MDAyNzY5MTgxODYyNTYyNjEwNDI0OTI4MzAxNzMwNjA3MzczMTI3ODM4OTEwNjA=

さらにこれをbase64とみなしてデコードした結果: 45443854690534164956801536869900276918186256261042492830173060737312783891060

これを16進数に変換後、hexstringとみなしてデコードした結果: dxOmJnmqANLoRFzfGpfs1AW74l4VFlzt

というようになっていた。上でいうdxOmJnmqANLoRFzfGpfs1AW74l4VFlztを送り返すとFlagが貰える。

一応、以下のようなスクリプトを書いた。

from pwn import *
from base64 import b64decode
import Crypto.PublicKey.RSA

r = remote('203.251.182.94', 4000)

r.recvuntil('enc : ')
enc = int(r.recvuntil('\n')[:-1])
r.recvuntil('e : ')
e = int(r.recvuntil('\n')[:-1])
r.recvuntil('p : ')
p = int(r.recvuntil('\n')[:-1])
r.recvuntil('q : ')
q = int(r.recvuntil('\n')[:-1])

log.info(enc)
log.info(e)
log.info(p)
log.info(q)

def gcd(a, b):
    while b:
        a, b = b, a%b
    return a

def lcm(a, b):
    return a * b / gcd(a,b)

def gcd2(a, b):
    if b == 0:
        u = 1
        v = 0
    else:
        q = a / b
        r = a % b
        (u0, v0) = gcd2(b, r)
        u = v0
        v = u0 - q * v0
    return (u, v)

d = gcd2(e,lcm((p-1),(q-1)))[0]
rsa = Crypto.PublicKey.RSA.construct((p*q, long(e), d, p, q))

flag = hex(int(b64decode(hex(rsa.decrypt(enc))[2:].decode('hex'))))[2:].decode('hex')
log.info(flag)
r.send(flag)
log.info(r.recv())

Flag: Y34hh_RSA_1S_S0_E4sy

([For 200] AI Mouse)

リンちゃん(@jtwp470)がUSBのパケットを解析->マウスの軌跡を画像化までやってくれていて、ほとんどFlagはとれていた。しかし、字が汚すぎて判別がつかないという問題があり、Flagの読み取りだけお手伝いさせてもらった。(ほとんど漁夫の利みたいな感じになってしまったので、申し訳ない気持ち)

f:id:Gen_IUS:20170330222424j:plain

Flag: c4tch_th3_m0us3!

なんだけど、どう見ても最初のcは大文字だし、最後のが!ってのは無理があると思う…

ScytheCTF 2017 Writeup

Harekaze で ScytheCTF 2017 に参加しました。(1チーム3人までだったので、@hiwwさん、@st98_さんと一緒に出ました) チームは425ptを獲得して7位、私はそのうち200ptを獲得しました。

[Crypto 150] rsa-love

渡されたzipファイルの中身は以下のようになっています。

Archive:  crypto.zip
  Length     Date   Time    Name
 --------    ----   ----    ----
        0  03-08-17 22:59   crypto/
      512  03-08-17 17:58   crypto/ciphertext1
      512  03-08-17 17:58   crypto/ciphertext2
      895  03-08-17 22:57   crypto/makefile.pyc
      795  03-08-17 17:58   crypto/pubkey
 --------                   -------
     2714                   5 files

makefile.pycuncompyle2デコンパイルすると、以下のようになっています。

#Embedded file name: ./makefile.py
from Crypto.Util.number import *
from Crypto.PublicKey import RSA
flag = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
e = 17L
p = getPrime(2048)
q = getPrime(2048)
n = p * q
m = int(flag.encode('hex'), 16)
c1 = pow(m + 525689, e, n)
f = open('ciphertext1', 'w')
f.write(hex(c1)[2:-1].decode('hex'))
f.close()
c2 = pow(m + 614039, e, n)
f = open('ciphertext2', 'w')
f.write(hex(c2)[2:-1].decode('hex'))
f.close()
rsa = RSA.construct((n, e))
f = open('pubkey', 'w')
f.write(rsa.publickey().exportKey())
f.close()

少し調べると、Franklin-Reiter related message attackという攻撃手法が使えそうだということがわかりました。以下は参考にさせて頂いた記事です。

最終的には、下のようにsageスクリプトを書くとフラグが出てきました。

def related_message_attack(c1, c2, diff, e, n):
    PRx.<x> = PolynomialRing(Zmod(n))
    g1 = x^e - c1
    g2 = (x+diff)^e - c2

    def gcd(g1, g2):
        while g2:
            g1, g2 = g2, g1 % g2
        return g1.monic()

    return -gcd(g1, g2)[0]


if __name__ == '__main__':
    n = 861989937789654426343461243692067541646389136311279472446273771380858591625799045480051880016167718577658753128114583588383932472239887532172654339870957744753326309084673348480208200721928375516193389541702362452779965541554418677435608451699395256247138474396834520098936161405270620432424672291929888064130422062921966409668948497829705768760525774247650535053422643580738401556942324260544893782468891562816590270831614624237877973003686291610265574551629414163200950228047285602529516064690646175978064578814463967642861445483802710181935820557001553628723227309919458243310054535987861396335937057322519432286790724315958028243706586026951293434287385440723446396785106065925954282107811068208807162110744413874784050820334181968892572151375893316164421299682073611671735120835384408284028006058512208895055633703444944571240009593754857230895392515268894872261726043200570474182492569784574170294738999962439991665188355923101471407683779419743815417334789620414324520353716086440275136258345544515245749214223565224488530498725011173014355937540112656062646800663153687788062813622722398138482615418491235933205129196807880212257539452423260828545503342523786106829637208140030098553391575548646889945502359545728826142377267
    e = 17

    with open("ciphertext1", "r") as f:
        c1 = int(f.read().encode('hex'), 16)
    with open("ciphertext2", "r") as f:
        c2 = int(f.read().encode('hex'), 16)

    m1 = related_message_attack(c1, c2, 88350, e, n)
    flag = hex(long(m1 - 525689))[2:-1].decode('hex')
    print flag

[Stego 50] quick challenge

ソラちゃん(@st98_)が GitHub - jaybosamiya/busysteg: Hide information content into busy areas of images, optimally を見つけていたものの、ビルドにはOpenCVが必要とのことだったので、調べたら何故か入っていました。(いついれたかわからない…)

しかし、そのままビルドしようとするとオペランドの型が曖昧であるというようなエラーがでてビルドできませんでした…

--- a/busysteg.cpp
+++ b/busysteg.cpp
@@ -193,7 +193,7 @@ void hide_data(char* inimg, char* indata, char* outimg) {
   }
   long int fsize = fin.tellg();
   fin.seekg(0, ios_base::end);
-  fsize = fin.tellg() - fsize;
+  fsize = (long)fin.tellg() - fsize;
   fin.seekg(0, ios_base::beg);
   char *buf = new char[fsize + 16];
   memcpy(buf, "BUSYSTEG", 8);

以上のように変更することで無事ビルドし、flagを取り出すことができました。

BSidesSF CTF Writeup

Harekazeで参加。チームは3837ptで18位でした。 私は8問解いて1451pt入れました。

[Crypto 450] vhash

—- Due to a bug, the challenge might be easier than intended. Enjoy the free points! —-

サーバサイドのスクリプトがバグっていて、ハッシュが固定されている。cookieのusernameを書き換えるだけ。

FLAG:180e2300112ef5a4f23c93cfdec8d780

[For 40] easycap

TCPストリームを読むと書いてある。

FLAG:385b87afc8671dee07550290d16a8071

[For 666] matryoshka

地獄。有名なものからマイナーなものまで、様々な形式で多重にアーカイブ化されている。 途中までst98さんが解いてくれていた。詰まったところだけ下に記述する。

dact

fileコマンドを叩いても判別できなかったので、「"0x444354c3" magic」などでぐぐっているとDACT compressed dataとわかった。aptでアーカイバを入れて展開した。

freeze

fileコマンドを叩くと frozen file 2.1 と表示され、ぐぐるfreeze というコマンドに行き当たる。しかし、展開方法がなかなか見当たらなかった。悩んでいると、st98さんがFedoraのパッケージとGentooのパッケージを発見。Gentooのものを落としてビルドして、unfreeze コマンドで展開した。

zpaq

7kSt から始まるファイルが出てきて、ぐぐるとZPAQという形式だとわかる。しかし、Ubuntu 14.04のaptで入る zpaq コマンドが古すぎて展開できなかったので、最新版のソースを落としてビルドした後、展開した。

展開後

全て展開し終えると、FLAC形式のファイルが出てくる。ビットレートが8bitで、サンプリング周波数が1Hzというあたりから、データ部分がテキストになっているのではないかと思った(わざわざFLAC可逆圧縮)を使っていることもヒントになるかも)。というわけで、一旦WAVEに変換したのだが ffmpeg のデフォルトだとビットレートが16bitに変換されてしまい、ここで無限に時間を溶かした。結果的には下のような感じでうまくいく。

$ ffmpeg -i file.flac -acodec pcm_u8 file.wav
$ strings file.wav
RIFF
WAVEfmt
LIST
INFOISFT
Lavf57.56.100
dataw
.. - ... - .... . .. -. -.-. .-. . -.. .. -... .-.. . ... .... .-. .. -. -.- .. -. --. -- --- .-. ... . -.-. --- -.. .

モールス信号がでてきたので、適当なデコーダに投げる。

ITSTHEINCREDIBLESHRINKINGMORSECODE

[For 150] Latlong

渡されるファイルに対して file コマンドを叩くとWAVEであることがわかる。Audacityなどで色々いじってみたものの、何もわからないので放置していたが、運営からヒントが出た。

Hint - “Ax25 will lead you in the direction”

ぐぐると、AX.25というアマチュア無線で使われるプロトコルがあるらしい。デコーダを探すと、Releases · h2so5/Invasive · GitHubを見つけた。

flag{f4ils4f3c0mms}

これヒントなしでどうやって解くんだろう…

[Pwn 30] easyshell

シェルコードを送るだけ。

FLAG:c832b461f8772b49f45e6c3906645adb

[Rev 10] Easy

strings

FLAG:db2f62a36a018bce28e46d976e3f9864

[Rev 75] Skipper

スキッパーといえばはいふり

uname -n の結果、/proc/versionの内容、cpuid命令の結果をそれぞれ正しいか比較している。1番目と2番目は chroot を使って突破できたが、3番目をどうするか悩んでいた。苦し紛れにバイナリ中の比較対象の文字列を GenuineIntel に書き換えるとFlagが出てしまった。(入力文字列や比較対象の文字列がFlagの生成に影響があるのかと思っていた)他の人のWriteupを読むと、認証部分をデバッガで飛ばしても良いらしく、つらい気持ちになった。

FLAG:f51579e9ca38ba87d71539a9992887ff

[Web 30] easyauth

cookieのusername変えるだけ。Flagとっておくの忘れてた。

感想

Forensicsしか解けないのつらい。

BITSCTF 2017 writeup

Harekazeで参加し、チームは520ptで18位でした。 私は5問解いて140pt入れました。

[Crypto 20] Banana Princess

PDFっぽいファイルを渡されるが、そのままだと開けない。バイナリエディタなどで開くと、ヘッダなどがおかしいことがわかる。本来であれば %PDF-1.5 であるべき部分が %CQS-1.5 になっているので、rot13だとわかった。

import string

with open('./MinionQuest.pdf', 'rb') as f:
    pdf = list(f.read())
    for i, c in enumerate(pdf):
        if c in string.ascii_letters:
            pdf[i] = c.encode('rot13')
    with open('./flag.pdf', 'wb') as out:
        out.write(b''.join(pdf))

出力されたPDFを開くと、Flagの部分が黒塗りされていて見えない。どこかで見たことある問題だなあと思いつつ、LibreOfficeで開いて黒塗り部分を削除すると、Flagが読めた。

BITSCTF{save_the_kid}

[Crypto 60] Sherlock

渡された final.txt を読むと、不自然に大文字になっている箇所があるので、スクリプトを書いて取り出すと ZEROONE という文字列の羅列になっていることがわかった。それぞれ数字の0と1に置換して、8bitずつ文字と解釈するとFlagが出てきた。

import string

with open('./final.txt', 'r') as f:
    txt = f.read()
    flag = []
    for c in txt:
        if c in string.ascii_uppercase:
            flag.append(c)
    bin = ''.join(flag).replace('ZERO', '0').replace('ONE', '1')
    print(''.join([chr(int(bin[i:i+8],2)) for i in range(0, len(bin), 8)]))

BITSCTF{h1d3_1n_pl41n_5173}

[Forensics 10] Woodstock-1

pcapファイルを渡されるので、Wiresharkで開いてみるとTCPのパケットが沢山ある。とりあえずTCPストリームを見てみたらFlagがあった。

BITSCTF{such_s3cure_much_w0w}

[Forensics 30] flagception

わけがわからず放置していたら、megumishさんがトップページのロゴの画像にある旗の色がおかしいことを発見。拡大して見てみると、

f:id:Gen_IUS:20170206100233p:plain

というようなパターンが見つかる。ちょうど1行に8パターンあるので、濃い方を1、薄い方を0として文字にすると、最初がBとわかったので、そのまま人力で解いた。解いたあと目が痛かったので、素直にソルバ書けばよかったと後悔した。

BITSCTF{f1agc3pt10n}

[Rev 20] Mission improbable

ファイル名にある TEENSY31ぐぐると、マイコンが出てくる。そのマイコンで動作するバイナリなんだろうなあと思いつつ、ディスアセンブル方法をぐぐると、

avr-objdump -j .sec1 -d -m avr5 MissionImprobable.TEENSY31.hex

で一応ディスアセンブルはできることがわかった。

全然読む気が起きないので、もうちょっとぐぐると、

avr-objcopy –I ihex –O binary MissionImprobable.TEENSY31.hex MissionImprobable.TEENSY31.bin

で、ふつうのバイナリに変換できることがわかった。変換後にstringsをかけるとFlagが見えた。

BITCTF{B4d_bad_U5B}

感想

pwnの練習しなさすぎて、自明っぽいpwnすら解けなかったので練習しなければな、と思いました。

Electronでスクリーンショットを即ツイートするための何かを作った話

この記事は FUN Advent Calendar 2016 10日目の記事(になる予定だったもの)です。

f:id:Gen_IUS:20161212215147j:plainf:id:Gen_IUS:20161212215332j:plain

本当に申し訳ない。

言い訳

SECCON 2016に出てました。チームはHarekazeに誘って頂いて、1300ptで61位でした。私はVoIPで音声聞き取ったのが全盛期でした。もっと精進したい。


本題

私は3年前ぐらいに今使用しているMacbook Pro Retina 13を購入しました。(ちょうど推薦合格したあとだったので、本当にこれぐらいの時期だったと思います)

その頃の私はターミナルの存在すら知りませんでしたが、購入したばかりのMBPを色々といじってみていました。その中で感動した機能の一つが、スクリーンショットです。もちろんWindowsでもスクリーンショットは撮れますが、Macスクリーンショット機能はより強力に感じました。

Command+Shift+3で全体、Command+Shift+4で矩形選択、矩形選択中にSpaceでウィンドウ単位で撮影可能。いつでも撮りたいときにすぐ撮れて、煩わしさを感じさせません。

しかし、それ故にちょっと残念だなと思うことがありました。

スクリーンショットを撮影した後にすること、それは9割Twitterです。(脳内調べ)

せっかくスクリーンショットをストレスなく撮影できるのに、その後、Twitterクライアントを開き、「Add Image」のようなボタンを押して、スクリーンショットディレクトリを探し、目当てのスクリーンショットを探すという作業が入ってしまいます。

面倒くさいなあと思った私は、3年越しにそれを解消する何かを作ることにしました。

これです↓

ちょっとわかりにくいかもしれませんが、上のメニューバーにあるオレンジ色のアイコンが私の作ったものです。アイコンをクリックすると、ツイートを入力するテキストエリアと、最新のスクリーンショットを表示します。GIFではマウスが動いていますが、ウィンドウが開いた時点でテキストエリアにフォーカスが移るようにしたので、そのままツイートを入力できます。

ツイートは、よくある感じで、(Ctrl|Command)+Enterで送信することができます。ツイートを送信したらそのままウィンドウも隠れます。

つまり、以前は

  1. Twitterクライアントを開く
  2. 画像を追加するためのボタンを押す
  3. スクリーンショットディレクトリを探す
  4. 目的のスクリーンショットを探す
  5. ツイートを入力
  6. 送信

のような手順を踏んでいたのが、

  1. アイコンをクリック
  2. ツイートを入力
  3. (Ctrl|Command)+Enter

で済むようになったわけです!

あ、一応名前もつけたんですが、ss-fast-tweetといいます。

技術的な話

Electronを使って実装しました。Electronを知らない人は、この辺を読むと詳しく書かれています。

Electronでアプリケーションを作ってみよう - Qiita

ざっくりいうと、メインプロセスとレンダラプロセスという2つのプロセスで成り立っていて、メインプロセスはNode.js、レンダラプロセスはChromiumが動いています。この2つのプロセスでプロセス間通信を行い、簡単にGUIアプリケーションが作れるよ!という感じでしょうか。

HTMLやCSSJavaScriptなどの知識がそのまま使えるので、Web系やってたけどネイティブアプリ作りたいって人にはかなりオススメです。

で、さっきのss-fast-tweetは、


ウィンドウが表示されるのをトリガーにして、メインプロセスで最新のスクリーンショットのパスを取得し、レンダラプロセスに渡す。

レンダラプロセスは、imgタグのsrc属性を渡されたパスに書き換える。


(Ctrl|Command)+Enterが押されると、textareaの内容をメインプロセスに渡す。

メインプロセスがツイートを行う。


雑に書くとこんな処理をしています。

まとまらない

普段私はあんまり何か作ろうとか思わないんですよね。確かに、自動化したいなと思うことはあるんですが、自動化する手間すらも面倒くさいと感じてしまい…という感じです。

でも、Electronを使ってみたら結構さっくり作れたので、これからも何か作るかもしれないです。

実は、色々と忙しかったせいで、Electronを触ったのは実質3~5日ぐらいだと思います。 Node.jsとかも初だったので、完全に学びながらでしたが、ある程度のものは出来ました(少なくとも私は満足しています)。 この手軽さもElectronの魅力の一つかなと思います。

Processingよりも本格的なアプリを作りたい1年生とかにもいいんじゃないでしょうか。

まとめる

Electronはいいぞ!!

追記

一応GitHubリポジトリを作りました github.com

SIGINTを受け取ったときのsleepの終了状態で詰まった話

誰も書かなそうなので、FUN Advent Calendar 2016 1日目の記事です。
某ステムプログラミングの課題の途中で感じた違和感について調べてみたという話です。

$whoami

学部3年、@neglect_ypです。
素因数分解をする某プロジェクトに所属しています。

終了ステータスが何か変だぞ…?

以前に作ったオレオレシェルを改造し、シグナルハンドラを用いてCtrl-Cを押した際に実行中のプロセスにSIGINTを送れるようにしようというのが、今回の課題でした。

実装自体はサクッと行い、一見うまくSIGINTを送れているようにも見えたのですが、終了ステータスが0であるという表示が出ており、頭に「?」が浮かびました。SIGINTを送って中断したということは、正常に終了してはいないのだから、終了ステータスが0であるのはおかしいのではないか?という考えです。

とりあえず、検証用にサイズの小さいコードを書いてみました。

結果は変わらず、終了ステータスは0でした。zsh上では終了ステータスが130になることも確認しました。

ここで僕がした勘違いは、「sleepはSIGINTによって終了したときは、終了ステータスが130になるのが正常な動作なんだ!」と思い込んでしまったことです。

そして、「sleepを実行していたプロセスの終了ステータスが格納されている変数から、どうにかして130という値が取り出せるはずだ」という思考になって、結構な時間を費やしてしまいました。

つまりどういうこと?

なんかうまくまとまらなかったので無理やり結論に…

zsh上でsleepに対してSIGINTを送って、echo $?したときに出てくる数字は、zshが勝手に決めています。 勝手にというと語弊があるかもしれませんが、POSIXでは、「シグナルによって終了したプロセスの終了ステータスは128より大きい値」であることが要求されているようです。しかし、具体的な値は定められていません。

そこで、zshbashなどのシェルでは「128+シグナルの番号」という形で終了ステータスを表しているようです。違う実装になっているシェルもあり、以下に詳しく載っています。

したがって130という値は128+2(SIGINT)の計算結果であり、sleepがSIGINTを受け取ったときにexit(130)するわけではありません。

思ったこと

私の主観ですが、本学はWebサービスを作ったりだとか人工知能に興味がある人が多いと思っています。逆に、私が興味を持っている、システムプログラミングやセキュリティの分野はあまり人気がないと感じていて、もっと好きになってほしいなあと思っています。こんな記事でしたが、興味を持っていただけたなら幸いです。

続きを読む

SANS NETWARS 2016に参加しました

SANS NETWARSトーナメント2016 | イベントのご案内 | 情報セキュリティのNRIセキュア

8/19(金) 〜 8/20(土) に開催された,SANS NETWARS 2016に参加してきました.

SANS NETWARSは,今年で2回目の参加です.

1日目に講義を受け,2日目にCTFをやるという日程で,充実した内容でした.

内容の公開はNGなので,詳しくは書けませんが,去年の内容よりもボリュームが増えていたし,CTFの問題もすべて変更されていて新鮮な気持ちで取り組めました.

去年は2日目のCTFの順位が32位だったので,今年は20位ぐらいを目指そうと思っていました.

結果は,23位でした.それなりに目標に近く悪くない結果だったと個人的には思います.

f:id:Gen_IUS:20160820202641p:plain

もう少しで解けそうだった問題が1問あったので,それだけが心残りです.来年も開催されれば,リベンジしに行きたいと思います.

チーム戦になるかもしれないという話もあったので,足を引っ張らないように今から精進したいと思います.