Securinets CTF Quals 2019 writeup
3/24~25にSecurinets CTFが開催されており、参加しました.
チームzer0pts
として参加し、結果は23393ptsで2位でした.
2位!!!!!
すごいですね(他人事感).例に漏れず、チームメンバーがつよつよでした.
今回はinsecure
ではなく、theoldmoon0602, ptr-yudai, st98プロと僕の4人でzer0pts
というチームで出ました.
僕がフラグを提出した問題は、Sanity Checkぐらいなんですが... まあ、先に解いてるけど、空いた(手をつける問題がない)時間に解いた問題もあるので、それらのwriteupを書こうと思います. あとは、部分的に手伝った問題とかですね.
[Crypto] Useless Admin (869pts)
OTP(One Time Password)を使ってるよ的な問題文と以下のファイルcipher.json
が渡されます.
{ "cipher_list": [ "1b0605000e14000d1b524802190b410700170e10054c11480807001806004e4f1f4f01480d411400531158141e1c100016535a480c000c031a000a160d421e004113010f13451e0c0100100a020a1a4e165f500d0c1e041a090b001d0515521c0a0410000a4f4b4d1d1c184d071600071c0a521d1706540940", "1e10524e001f11481c010010070b13024f0704590903094d0c000e4f0711000615001911454217161a1a45040149000a5218404f1e0012060b1b590a1048171741140c01174c0d49174f0c8d4fc7520211531b0b0c1e4f", "1d0c04451352001a000154431b014109450a0a0b000045490403520a1d16490008535848085942071c0d0c57101c0045111c40430c4e111c0b1b1c451d4f071712010508475518061d00060a1b0a1a4c165d", "160d074300061d071b524e06190b134e450a0b0a4d4c12411d004f014045491b4649074804001100011d4504520612451e165d53064e164e1d060d0d44541a0041031b0b06540d1a070004001d4b074800531c04101d4f", "1a1d524912521548120045021b4e1506490a0859150345531d12521b4e094909030003011148420453074d161e05540b071e4c451b000a084a1d1c04084c0b45060b060a4742070618534218070210484512020043100e191e5956111a1c001c1f0b5c", "1a1d5248000154041a1c47430d0b04000005015900140c4f04534f094e08490103000000045442111b11001b1b1d000917535a48004e021d4a0e0b0044491c03080a001a024c11490748074f02040054451a1d150c1b150d020d0e", "1a1d5249125215481613500a1b0f0d4e4d0d1c0d000700001d1c001b06004f1d0f5a11480745040a011100181c0c540d13000e44085404404a061716014e010c0308104e084e0d4911450506011853540a5304120a1a154c0a1843001b45541c481607051b431f480d001e0400000c531d01011d00124441010200190d0800000000000e54060001100a1b4d0b040d105347", "0a0607000913020d551300041d0f0f0a0003061f154c034f1b53530602004e0c030c541f0454110a1d5a001e0649190419165d00104f104e1b1a101101001b0b1705051b0642040c5341114f0e4b104f0803110b0a060f42", "160d074300061d071b524e06190b134e450a0b0a4d4c12411d004f014045491b4649074804001100011d4504520612451e165d53064e16424a1810110c00060d04440e1c02411c0c00544209001953540d165009021a1542", "1e10524e001f11481c010010070b13024f0704590903094d0c000e4f0711000615001911454217161a1a45040149000a5218404f1e0012060b1b590a1048171741140c01174c0d49174f4201001f534b0b1c074b", "1a49134d4113540a0713490d434e160f541700174f4c11480c53520a1d1100000000190d4549114512544d12000c540402034b4e0d491d40" ], "cipher_flag": "1a4905410f06110c55064f430a00054e540c0a591603174c0d5f000d1b110006414c1848164516111f1100111d1b54001c17474e0e001c011f1d0a4b" }
OTPを使った問題の経験がなかったので、とりあえず調べた結果、こちらのサイト
を見つけました.サイトを参照してもらえればわかるんですが、ほとんど同じなので今回の問題にしたものにコードを変更します.
import string import collections import sets, sys ciphers = ["1b0605000e14000d1b524802190b410700170e10054c11480807001806004e4f1f4f01480d411400531158141e1c100016535a480c000c031a000a160d421e004113010f13451e0c0100100a020a1a4e165f500d0c1e041a090b001d0515521c0a0410000a4f4b4d1d1c184d071600071c0a521d1706540940", "1e10524e001f11481c010010070b13024f0704590903094d0c000e4f0711000615001911454217161a1a45040149000a5218404f1e0012060b1b590a1048171741140c01174c0d49174f0c8d4fc7520211531b0b0c1e4f", "1d0c04451352001a000154431b014109450a0a0b000045490403520a1d16490008535848085942071c0d0c57101c0045111c40430c4e111c0b1b1c451d4f071712010508475518061d00060a1b0a1a4c165d", "160d074300061d071b524e06190b134e450a0b0a4d4c12411d004f014045491b4649074804001100011d4504520612451e165d53064e164e1d060d0d44541a0041031b0b06540d1a070004001d4b074800531c04101d4f", "1a1d524912521548120045021b4e1506490a0859150345531d12521b4e094909030003011148420453074d161e05540b071e4c451b000a084a1d1c04084c0b45060b060a4742070618534218070210484512020043100e191e5956111a1c001c1f0b5c", "1a1d5248000154041a1c47430d0b04000005015900140c4f04534f094e08490103000000045442111b11001b1b1d000917535a48004e021d4a0e0b0044491c03080a001a024c11490748074f02040054451a1d150c1b150d020d0e", "1a1d5249125215481613500a1b0f0d4e4d0d1c0d000700001d1c001b06004f1d0f5a11480745040a011100181c0c540d13000e44085404404a061716014e010c0308104e084e0d4911450506011853540a5304120a1a154c0a1843001b45541c481607051b431f480d001e0400000c531d01011d00124441010200190d0800000000000e54060001100a1b4d0b040d105347", "0a0607000913020d551300041d0f0f0a0003061f154c034f1b53530602004e0c030c541f0454110a1d5a001e0649190419165d00104f104e1b1a101101001b0b1705051b0642040c5341114f0e4b104f0803110b0a060f42", "160d074300061d071b524e06190b134e450a0b0a4d4c12411d004f014045491b4649074804001100011d4504520612451e165d53064e16424a1810110c00060d04440e1c02411c0c00544209001953540d165009021a1542", "1e10524e001f11481c010010070b13024f0704590903094d0c000e4f0711000615001911454217161a1a45040149000a5218404f1e0012060b1b590a1048171741140c01174c0d49174f4201001f534b0b1c074b", "1a49134d4113540a0713490d434e160f541700174f4c11480c53520a1d1100000000190d4549114512544d12000c540402034b4e0d491d40"] target_cipher = "1a4905410f06110c55064f430a00054e540c0a591603174c0d5f000d1b110006414c1848164516111f1100111d1b54001c17474e0e001c011f1d0a4b" def strxor(a, b): return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)]) # To store the final key final_key = [None]*150 # To store the positions we know are broken known_key_positions = set() # For each ciphertext for current_index, ciphertext in enumerate(ciphers): counter = collections.Counter() # for each other ciphertext for index, ciphertext2 in enumerate(ciphers): if current_index != index: # don't xor a ciphertext with itself for indexOfChar, char in enumerate(strxor(ciphertext.decode('hex'), ciphertext2.decode('hex'))): # Xor the two ciphertexts # If a character in the xored result is a alphanumeric character, it means there was probably a space character in one of the plaintexts (we don't know which one) if char in string.printable and char.isalpha(): counter[indexOfChar] += 1 # Increment the counter at this index knownSpaceIndexes = [] # Loop through all positions where a space character was possible in the current_index cipher for ind, val in counter.items(): # If a space was found at least 7 times at this index out of the 9 possible XORS, then the space character was likely from the current_index cipher! if val >= 7: knownSpaceIndexes.append(ind) #print knownSpaceIndexes # Shows all the positions where we now know the key! # Now Xor the current_index with spaces, and at the knownSpaceIndexes positions we get the key back! xor_with_spaces = strxor(ciphertext.decode('hex'),' '*150) for index in knownSpaceIndexes: # Store the key's value at the correct position final_key[index] = xor_with_spaces[index].encode('hex') # Record that we known the key at this position known_key_positions.add(index) # Construct a hex key from the currently known key, adding in '00' hex chars where we do not know (to make a complete hex string) final_key_hex = ''.join([val if val is not None else '00' for val in final_key]) # Xor the currently known key with the target cipher output = strxor(target_cipher.decode('hex'),final_key_hex.decode('hex')) print "Fix this sentence:" print ''.join([char if index in known_key_positions else '*' for index, char in enumerate(output)])+"\n" # WAIT.. MANUAL STEP HERE # This output are printing a * if that character is not known yet # fix the missing characters like this: "Let*M**k*ow if *o{*a" = "cure, Let Me know if you a" # if is too hard, change the target_cipher to another one and try again # and we have our key to fix the entire text! #sys.exit(0) #comment and continue if u got a good key target_plaintext = "i wanted to end the world, but i'll settle for ending yours." print "Fixed:" print target_plaintext+"\n" key = strxor(target_cipher.decode('hex'),target_plaintext) print "Decrypted msg:" for cipher in ciphers: print strxor(cipher.decode('hex'),key) print "\nPrivate key recovered: "+key+"\n"
Fix this sentence: * *anted to ind t** y*rl*, but i'*l*settle for endin* ****s. Fixed: i wanted to end the world, but i'll settle for ending yours. Decrypted msg: how often have i said that when you have excluded the imposs my name is sherlock holmes. it is my business to know what o never trust to general impressions, my boy, but concentrate education never ends, watson. it is a series of lessons with it is a great thing to start life with a small number of rea it has long been an axiom of mine that the little things are it is a capital mistake to theorize before one has data. ins you have a grand gift for silence, watson. it makes you quit education never ends, watson. it is a series of lessons, wit my name is sherlock holmes. it is my business to know what o i am a brain, watson. the rest of me is a mere appendix. Private key recovered: sir arthur conan doyale is one of the best writers. i enjoye
Fixedには上のFix this sentenceを直したものを直接コードに書きます.
Fix this sentenceを直すのが大変で、ひたすら調べてi wanted to ind t** y*rl*, but i'll settle for ending yours.
までたどり着いたんですが前半がわかりませんでした.
後にptr-yudaiが名言?であることを見つけ、Fixedはi wanted to end the world, but i'll settle for ending yours.
であることがわかりました.
Securinets{i wanted to end the world, but i'll settle for ending yours.}
[Crypto] RSA/Encoding error (1000pts) first solve!
zipファイルが渡されます.
解凍すると、以下のようなpngファイルが出てきます.
読み取ると、Hint: Every single information matters
で大した情報ではないです.
strings
コマンドにかけるとoutput.txtというものが見えるので、foremost
コマンドで取り出します.
output.txtが取れたので、中身を見ると以下のようなビット列が複数行に渡って書かれています.
11111.11111.00011.10110.00000.00000.00000.10111.10000.10001.00000.11111.10111.01111.00100.00000.10101.10011
ここからが本当にわからなくて、大変でした. theoldmoon0602とptr-yudaiによるとどうやら反復符号というものらしいです. これでoutput.txtの解釈はできても、RSA要素が全く出てきてません. 色々調べると、はじめのpngのexif情報でした. exiftoolにかけると以下になります.
$ exiftool ctf_image.png ExifTool Version Number : 10.80 File Name : ctf_image.png Directory : . File Size : 11 kB File Modification Date/Time : 2019:03:23 22:53:12+09:00 File Access Date/Time : 2019:03:25 16:07:35+09:00 File Inode Change Date/Time : 2019:03:24 13:14:23+09:00 File Permissions : rw-r--r-- File Type : PNG File Type Extension : png MIME Type : image/png Image Width : 887 Image Height : 521 Bit Depth : 8 Color Type : RGB with Alpha Compression : Deflate/Inflate Filter : Adaptive Interlace : Noninterlaced SRGB Rendering : Perceptual Gamma : 2.2 Pixels Per Unit X : 3780 Pixels Per Unit Y : 3780 Pixel Units : meters Comment : e = 2^16+1 Warning : [minor] Trailer data after PNG IEND chunk Image Size : 887x521 Megapixels : 0.462
これで、e = 2^16+1
、あとはエスパー?で画像の縦横のサイズが素数だとわかり、必要なものが揃いました.
d
を計算し、output.txtを1行ずつ復号してやるとでます.
from Crypto.Util.number import inverse with open("./output/zip/output.txt", "rb") as f: buf = f.read() #buf = buf.replace(b"\n", ".") p = 887 q = 521 e = 2**16 + 1 N = p*q phi = (p-1)*(q-1) d = inverse(e, phi) m = '' for a in buf.split(b"\n"): result = '' for block in a.split(b'.'): if block.count(b'1') > 2: result += '1' else: result += '0' c = int(result, 2) tmp = pow(c, d, N) m += hex(tmp)[2:-1].decode('hex') print m
A repetition code is a coding scheme that repeats the bits across a channel to achieve error-free communication. Given a stream of data to be transmitted, the data are divided into blocks of bits. Each block is transmitted some predetermined number of times. For example, to send the bit pattern "1011", the four-bit block can be repeated three times, thus producing "1011 1011 1011". However, if this twelve-bit pattern was received as "1010 1011 1011" where the first block is unlike the other two it can be determined that an error has occurred. hope you have understand it, please validate the task with the following Flag : xJbht0oWpsOa1e3WnXo9FDnUj3VZpZsuxMPVlYEN
Securinets{xJbht0oWpsOa1e3WnXo9FDnUj3VZpZsuxMPVlYEN}
[Forensics] Easy Trade (200pts)
pcapファイルが渡されます.
wiresharkで見ると、いくつかのtcpがあるのでtcp streamします(無条件反射).
あるパケットの中にPK
やflag.txt
を含んだバイト列があるので、zipだろうということで、摘出します.
tcp streamで内容を保存できますが、デフォルトではASCII
の保存になるので、下のShow and sava data as
をraw
に変更して保存します.
これでzipを手に入れることができたので、あとは解凍するだけですが、ここでパスワードを聞かれます.
ここは、tcpでgive me the key
とあったのでその後にあるパケットのsecurinetsXD
を入れると解凍できます.
これでflag.txt
を手に入れることができます.
Securinets{c2VjdXJpbmV0c3s5NTRmNjcwY2IyOTFlYzI3NmIxYTlmZjg0NTNlYTYwMX0}
これは、ptr-yudaiが秒で(5分もかからないぐらい)解いたのですが、僕は20分ぐらいかかりました. 圧倒的マッスルですね.
まとめ
1位、2位の繰り返しで激アツでした. また、今回は初チームということで楽しく24時間過ごせました(4時間ほど寝たけど...).
最近はCryptoを練習しているので、更にやっていきます.
チームメンバー、運営の皆様ありがとうございました.お疲れ様です.