この大会は2018/1/27 11:00(JST)~2018/1/28 23:00(JST)に開催されました。
今回もチームで参戦。結果は3987点で139チーム中10位でした。
自分で解けた問題をWriteupとして書いておきます。
CNVService (Crypt/ACM)
$ nc cnvservice.acebear.site 1337 ***************************CNVService***************************** * Challenge created by CNV * * My blog: https://chung96vn.blogspot.com * ***************************CNVService***************************** ********************Menu******************** * 1 - Register * * 2 - Login * ******************************************** Your choice: 1 *****************************REGISTER***************************** Name: 1 Username: 2 Cookie: PWg77PMNr9+uFv7AJVlwzob7DLRKNm7bpk7wY6l+JBBxnlH1lDq2HJXf53CIAgzXz70wj6nMNmgICMiq6l7CELxf80XHSkf5UsRnDr+CHOM= ***************************END REGISTER*************************** ********************Menu******************** * 1 - Register * * 2 - Login * ******************************************** Your choice:
ソースコードを見ると、以下のような処理になっている。
■1を選択 Nameを入力 Usernameを入力(rootはNG) すると、暗号化結果を表示 ◇Cookie.register(Name, Username) name: Nameをpadding iv: nameと特定文字列とのxor cookie: CNVService*user=[username]*[ctime()]*[__SECRET__] iv+cookieのAES暗号したものをBase64エンコードして返す。 ◇AES暗号 cookieをpadding ECB暗号モード利用 1ブロック目:ivとのxor --AES暗号--> 2ブロック目:1ブロック目暗号化文字列のmd5とのxor --AES暗号--> ■2を選択 クッキーを入力 ◇Cookie.authentication(cookie) cookieをBase64デコードする。 name: cookie前半16バイトと特定文字列とのxor name: unpaddingする AES復号する。 *区切りでチェック 1つ目:CNVService 最後:__SECRET__ 2つ目の最初の5文字:user= 2つ目:=区切りで2つ
username=rootの暗号化文字列を割り出す必要があるが、当然rootで登録はできない。以下のようなことを念頭に暗号化文字列を作成することを考える。
"CNVService*user=" ^ iv --AES ECB--> 暗号1ブロック目 "root*Sat Jan 27 " ^ md5(暗号1ブロック目).digest() --AES ECB--> 暗号2ブロック目 "13:43:37 2018*SS" ^ md5(暗号2ブロック目).digest() --AES ECB--> 暗号3ブロック目 "SSSSSS??????????" ^ md5(暗号3ブロック目).digest() --AES ECB--> 暗号4ブロック目
いろいろ試したが、__HEDDEN__, __SECRET__の値を知らずに解くことはできそうにない。
まず md5(__HIDDEN__).digest()を求めてみる。
import socket from hashlib import md5 from base64 import b64decode from base64 import b64encode def xor(dest, src): if len(dest) == 0: return src elif len(src) == 0: return dest elif len(dest) >= len(src): return ''.join(chr(ord(dest[i])^ord(src[i])) for i in range(len(src))) else: return ''.join(chr(ord(dest[i])^ord(src[i])) for i in range(len(dest))) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('cnvservice.acebear.site', 1337)) data = s.recv(1024) data += s.recv(1024) data += '1' print data s.sendall('1\n') data = s.recv(1024) data += s.recv(1024) print data name = 'xxxxxxxxxxxxxxxx' print name s.sendall(name + '\n') data = s.recv(1024) print data username = 'aaaa' print username s.sendall(username + '\n') data = s.recv(1024) data += s.recv(1024) print data cookie = data[8:].strip() cookie = b64decode(cookie) iv = cookie[:16] cipher_text = cookie[16:] HIDDEN = xor(iv, name) print HIDDEN.encode('hex')
この結果、md5(__HIDDEN__).digest()の16進表記は以下の通りであることがわかる。
0c6734e3fc02a0d0a119f1cf2a567fc1
次に__SECRET__を求めてみる。最初は以下のコードで__SECRET__の長さを確認する。
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('cnvservice.acebear.site', 1337)) data = s.recv(1024) data += s.recv(1024) data += '1' print data s.sendall('1\n') data = s.recv(1024) data += s.recv(1024) name = 'test' print data + name s.sendall(name + '\n') data = s.recv(1024) username = 'aaaaaaa' #<- change this string length print data + username s.sendall(username + '\n') data = s.recv(1024) print data cookie = data[8:].strip() print len(cookie.decode('base64'))
・aのとき、80 ・aaaaaaaのとき、80 ・aaaaaaaaのとき、96 CNVService*user= aaaaaaaa*Sat Jan 27 13:43:37 201 8*SSSSSSSSSSSSSS \x16........\x16
この結果__SECRET__の長さは14。
次に__SECRET__の後ろから1文字ずつはみ出させ、usernameに同じデータを作るように指定し、BFで該当ブロックの暗号データが同じになるものを探す。最初の1文字は以下のようなイメージ。
CNVService*user= aaaaaaaaa*Sat Ja n 27 13:43:37 20 18*SSSSSSSSSSSSS S\x15.......\x15
import socket from hashlib import md5 from base64 import b64decode from base64 import b64encode HIDDEN = '0c6734e3fc02a0d0a119f1cf2a567fc1'.decode('hex') P_HEAD = 'CNVService*user=' NAME = 'xxxxxxxxxxxxxxxx' BLOCK_SIZE = 16 pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * \ chr(BLOCK_SIZE - len(s) % BLOCK_SIZE) def xor(dest, src): if len(dest) == 0: return src elif len(src) == 0: return dest elif len(dest) >= len(src): return ''.join(chr(ord(dest[i])^ord(src[i])) for i in range(len(src))) else: return ''.join(chr(ord(dest[i])^ord(src[i])) for i in range(len(dest))) def right_enc(s, nth, plain, initFlg): data = s.recv(1024) if initFlg: data += s.recv(1024) data += '1' print data s.sendall('1\n') data = s.recv(1024) data += s.recv(1024) print data + NAME s.sendall(NAME + '\n') data = s.recv(1024) username = 'a' * (8 + nth) print data + username s.sendall(username + '\n') data = s.recv(1024) print data cookie = data[8:].strip() cookie = b64decode(cookie) iv = cookie[:16] cipher_text = cookie[16:] enc_block4 = cipher_text[48:64] enc_block5 = cipher_text[64:] return xor(plain, md5(enc_block4).digest()), enc_block5 def getname(xor_val): return xor(xor_val, xor(P_HEAD, HIDDEN)) def enc(s, name): data = s.recv(1024) data += '1' print data s.sendall('1\n') data = s.recv(1024) data += s.recv(1024) print data + name s.sendall(name + '\n') data = s.recv(1024) username = 'a' print data + username s.sendall(username + '\n') data = s.recv(1024) print data cookie = data[8:].strip() cookie = b64decode(cookie) iv = cookie[:16] cipher_text = cookie[16:] enc_block1 = cipher_text[:16] return enc_block1 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('cnvservice.acebear.site', 1337)) secret = '' initFlg = True for i in range(1, 15): for code in range(32, 127): print '[-] code =', code try_secret = pad(chr(code) + secret) p, c = right_enc(s, i, try_secret, initFlg) initFlg = False if enc(s, getname(p)) == c: secret = chr(code) + secret break print '[-] secret =', secret print '[+] secret =', secret
この結果、__SECRET__の値は以下の通りであることがわかる。
__SECRET__ = 'Thi5_i5_s3cr3t'
あとは適当なNameで1ブロック目から順に求めていく。
import socket from hashlib import md5 from base64 import b64decode from base64 import b64encode BLOCK_SIZE = 16 pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * \ chr(BLOCK_SIZE - len(s) % BLOCK_SIZE) def xor(dest, src): if len(dest) == 0: return src elif len(src) == 0: return dest elif len(dest) >= len(src): return ''.join(chr(ord(dest[i])^ord(src[i])) for i in range(len(src))) else: return ''.join(chr(ord(dest[i])^ord(src[i])) for i in range(len(dest))) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('cnvservice.acebear.site', 1337)) data = s.recv(1024) data += s.recv(1024) data += '1' print data s.sendall('1\n') data = s.recv(1024) data += s.recv(1024) name = 'xxxxxxxxxxxxxxxx' print data + name s.sendall(name + '\n') data = s.recv(1024) username = 'aaaa' print data + username s.sendall(username + '\n') data = s.recv(1024) print data cookie = b64decode(data[8:].strip()) iv = cookie[:16] cipher_text = cookie[16:] HIDDEN = xor(iv, name) p = [ 'CNVService*user=', 'root*Sat Jan 27 ', '13:43:37 2018*Th', pad('i5_i5_s3cr3t')] # ciphertext 1st block c = [cipher_text[:16]] # ciphertext 2nd, 3rd, 4th block for i in range(3): data = s.recv(1024) print data + '1' s.sendall('1\n') data = s.recv(1024) data += s.recv(1024) name2 = xor(xor(p[i+1], md5(c[i]).digest()), xor(p[0], HIDDEN)) print data + name2 s.sendall(name2 + '\n') data = s.recv(1024) username = 'a' print data + username s.sendall(username + '\n') data = s.recv(1024) print data cookie = b64decode(data[8:].strip()) cipher_text = cookie[16:] c.append(cipher_text[:16]) # make cookie data = s.recv(1024) print data + '2' cookie = iv for i in range(4): cookie += c[i] b64_cookie = b64encode(cookie) s.sendall('2\n') data = s.recv(1024) data += s.recv(1024) print data + b64_cookie s.sendall(b64_cookie + '\n') data = s.recv(1024) print data data = s.recv(1024) print data
実行結果は以下の通り。
: ********************Menu******************** * 1 - Register * * 2 - Login * ******************************************** Your choice: 2 *******************************LOGIN****************************** Cookie: dB9Mm4R62KjZYYm3Ui4HuX2M/X3aDOaMYiURc62G0iNH1X1MYq3cT/KLGBii3waxANcmeINSqA86qHKfYpL2fagUrLmrhK2qCQmiDLHHxxc= **************************LOGIN SUCCESS*************************** Welcome CNV service: xxxxxxxxxxxxxxxx Username: root Time register: Sat Jan 27 13:43:37 2018 ***************************Root Servive*************************** This is flag: AceBear{AES_CNV_is_s3cure_but_CNV_S3rvic3_i5_not_s3cure}
AceBear{AES_CNV_is_s3cure_but_CNV_S3rvic3_i5_not_s3cure}