st98 の日記帳


[ctf] Pragyan CTF 2017 の write-up

チーム Harekaze で Pragyan CTF 2017 に参加しました。最終的にチームで 385 点を獲得し、順位は 150 位 (得点 525 チーム中) でした。うち、私は 3 問を解いて 325 点を入れました。

以下、解いた問題の write-up です。

[Forensics 150] Interstellar

stegsolve.jar で Gray bits を見るとフラグが表示されました。

pragyanctf{Cooper_Brand}

[Miscellaneous 75] The Vault

まず与えられたファイルがどんなものか調べてみましょう。

$ 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}

[Reverse Engineering 100] MI6

私が問題を見た時点で @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_split100.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 の日記帳