チーム Harekaze で Pragyan CTF 2017 に参加しました。最終的にチームで 385 点を獲得し、順位は 150 位 (得点 525 チーム中) でした。うち、私は 3 問を解いて 325 点を入れました。
以下、解いた問題の write-up です。
stegsolve.jar で Gray bits を見るとフラグが表示されました。
pragyanctf{Cooper_Brand}
まず与えられたファイルがどんなものか調べてみましょう。
$ file ./file
./file: Keepass password database 1.x KDB, 3 groups, 4 entries, 50000 key transformation rounds
KeePass というパスワードマネージャのデータベースのようです。
マスターパスワードは 1~3 文字、使われた文字種も問題文から分かっているので John the Ripper で探しましょう。
s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*'
for a in s:
for b in s:
for c in s:
print a + b + c
$ python s.py > wordlist.lst
$ keepass2john -i 50000 file > file.john
$ john -format=keepass -wordlist=wordlist.lst file.john
...
しばらく待つとマスターパスワードは k18
であると分かりました。
pragyanctf{closed_no_more}
私が問題を見た時点で @neglect_yp さんが、渡されたファイルはくっついている tar.gz を展開した後 ./installer reverse_1.rb false
を実行しているようだと調べていました。
reverse_1.rb は以下のような内容でした。
class Fixnum
def random_split(set = nil, repeats = false)
set ||= 1..self
set = [*set]
return if set.empty? || set.min > self || set.inject(0, :+) < self
tried_numbers = []
while (not_tried = (set - tried_numbers).select {|n| n <= self }).any?
tried_numbers << number = not_tried.sample
return [number] if number == self
new_set = set.dup
new_set.delete_at(new_set.index(number)) unless repeats
randomized_rest = (self-number).random_split(new_set, repeats)
return [number] + randomized_rest if randomized_rest
end
end
end
class String
def ^( other )
b1 = self.unpack("U*")
b2 = other.unpack("U*")
longest = [b1.length,b2.length].max
b1 = [0]*(longest-b1.length) + b1
b2 = [0]*(longest-b2.length) + b2
b1.zip(b2).map{ |a,b| a^b }.pack("U*")
end
end
a2= Array.new
a= Array.new
string = gets
a=string.upcase.chars
sum = 0
length1 = a.length
for i in 0..a.length-1 ## /n is worth 10 characters change to length-1 at the end
a[i] = (a[i].ord)^61
sum = sum + a[i].ord
end
for i in 0..length1-1
a2[i] = a[i].to_i.random_split(20..30)
end
# Print the final output array which will be used for reversing
for i in 0..a2.length-1
print a2[i].join(" ") + " "
end
Fixnum#random_split
は 100.random_split(20..30)
の場合 [25, 24, 28, 23]
というような、要素を全て足すと 100 になる配列を返すメソッドのようです。
雑に総当りしましょう。
s = '26 25 30 28 22 25 20 23 21 29 22 24 26 23 21 26 27 20 28 22 25 23 30 29 23 28 24 20 21 26 25 20 23 27 23 29 25 22 23 26 27 29 24 23 30 21 25 24 26 20 24 22 21 30 26 20 25 24 21 23 27 29 26 22 20 21 23 22 30 26 29 26 28 27 22 20 27 29 26 30 28 27 26 23 29 21 22 25 27 24 21 29 25 24 20 25 23 22 30 28 27 29 25 20 24 21 23 20 23 21 29 26'
s = [int(x) for x in s.split(' ')]
i = 0
res = ''
while i < len(s):
if ord('A') <= sum(s[i:i+5]) ^ 61 <= ord('Z'):
d = 5
elif ord('A') <= sum(s[i:i+4]) ^ 61 <= ord('Z'):
d = 4
res += chr(sum(s[i:i+d]) ^ 61)
i += d
print res
PRAGYANCTFIGAMVFMUAFEPQ^
と PRAGYANCTF から始まるものの意味が通らない文字列が出てきました。ちょっといじってもう一度やってみましょう。
s = '26 25 30 28 22 25 20 23 21 29 22 24 26 23 21 26 27 20 28 22 25 23 30 29 23 28 24 20 21 26 25 20 23 27 23 29 25 22 23 26 27 29 24 23 30 21 25 24 26 20 24 22 21 30 26 20 25 24 21 23 27 29 26 22 20 21 23 22 30 26 29 26 28 27 22 20 27 29 26 30 28 27 26 23 29 21 22 25 27 24 21 29 25 24 20 25 23 22 30 28 27 29 25 20 24 21 23 20 23 21 29 26'
s = [int(x) for x in s.split(' ')]
i = 47
res = ''
while i < len(s):
if sum(s[i:i+3]) ^ 61 in [ord('{'), ord('}')]:
d = 3
elif ord('A') <= sum(s[i:i+5]) ^ 61 <= ord('Z'):
d = 5
elif ord('A') <= sum(s[i:i+4]) ^ 61 <= ord('Z'):
d = 4
res += chr(sum(s[i:i+d]) ^ 61)
i += d
print res
{FLAGSARECOOL}
という文字列が出てきました。
pragyanctf{flagsarecool}
何もかもが guessing という感じでした。おととし参加したときはそういう印象はなかったのですが。
st98.github.io / st98 の日記帳