angstromCTF 2018 Writeup

この大会は2018/3/17 5:30(JST)~2018/3/22 5:00(JST)に開催されました。
今回もチームで参戦。結果は2955点で1516チーム中16位でした。
自分で解けた問題をWriteupとして書いておきます。

IRC (MISC 10)

freenodeで#angstromctfチャネルに入る。

11:04 *topic : Need help from an admin? All of the channel operators are admins. | You didn't forget your password, login is currently down! All will be well soon. | IRC flag: actf{irc} | ?ngstromCTF 2018 will start sometime soon PM EDT :: https://angstromctf.com/ | Everything is dead sorry...but soon it shall be reborn
actf{irc}

WARMUP (CRYPTO 10)

暗号はパッと見た感じシーザー暗号。

myjd{ij_fkwizq}

しかし復号できず。問題文の「it's a fine cipher.」からAffine暗号と推測。https://www.dcode.fr/affine-cipherブルートフォースで復号。

A=19,B=12	ACTFITBEGIN
actf{it_begins}

BACK TO BASE-ICS (CRYPTO 20)

Part 1: 011000010110001101110100011001100111101100110000011011100110010101011111011101000111011100110000010111110110011000110000
Part 2:	165 162 137 145 151 147 150 164 137 163 151 170 164 63 63 
Part 3: 6e5f7468317274797477305f733178
Part 4: dHlmMHVyX25vX20wcmV9

それぞれエンコードしているものを復号し、結合する。

Part1: 2進数
Part2: 8進数
Part3: hexエンコード
Part4: Base64エンコード
e1 = '011000010110001101110100011001100111101100110000011011100110010101011111011101000111011100110000010111110110011000110000'

flag1 = ''
for i in range(0, len(e1), 8):
    flag1 += chr(int(e1[i:i+8], 2))

e2 = '165 162 137 145 151 147 150 164 137 163 151 170 164 63 63 '
e2 = e2.split(' ')

flag2 = ''
for i in range(len(e2) - 1):
    flag2 += chr(int(e2[i], 8))

e3 = '6e5f7468317274797477305f733178'
flag3 = e3.decode('hex')

e4 = 'dHlmMHVyX25vX20wcmV9'
flag4 = e4.decode('base64')

flag = flag1 + flag2 + flag3 + flag4
print flag
actf{0ne_tw0_f0ur_eight_sixt33n_th1rtytw0_s1xtyf0ur_no_m0re}

XOR (CRYPTO 40)

XORキーは1文字のようなので、平文がactfで始まることを前提に復号する。

with open('ciphertext.txt', 'r') as f:
    data = f.read().strip().decode('hex')

key = ord(data[0]) ^ ord('a')

flag = ''
for i in range(len(data)):
    flag += chr(ord(data[i]) ^ key)

print flag
actf{hope_you_used_a_script}

INTRO TO RSA (CRYPTO 50)

p, q, e, cがわかっているので、そのまま復号する。

p = 169524110085046954319747170465105648233168702937955683889447853815898670069828343980818367807171215202643149176857117014826791242142210124521380573480143683660195568906553119683192470329413953411905742074448392816913467035316596822218317488903257069007949137629543010054246885909276872349326142152285347048927
q = 170780128973387404254550233211898468299200117082734909936129463191969072080198908267381169837578188594808676174446856901962451707859231958269401958672950141944679827844646158659922175597068183903642473161665782065958249304202759597168259072368123700040163659262941978786363797334903233540121308223989457248267
e = 65537
c = 4531850464036745618300770366164614386495084945985129111541252641569745463086472656370005978297267807299415858324820149933137259813719550825795569865301790252501254180057121806754411506817019631341846094836070057184169015820234429382145019281935017707994070217705460907511942438972962653164287761695982230728969508370400854478181107445003385579261993625770566932506870421547033934140554009090766102575218045185956824020910463996496543098753308927618692783836021742365910050093343747616861660744940014683025321538719970946739880943167282065095406465354971096477229669290277771547093476011147370441338501427786766482964
n = p * q

phi = (p - 1) * (q - 1)

x = 0
while True:
    if (phi * x + 1) % e == 0:
        d = (phi * x + 1) / e
        break
    x = x + 1

m = pow(c, d, n)

flag = ('%x' % m).decode('hex')
print flag
actf{rsa_is_reallllly_fun!!!!!!}

OFB (CRYPTO 120)

4の倍数の長さになるよう\x00を付ける。その後4バイトずつの配列にする。
4バイト単位で暗号化しているが、PNGのフォーマットを前提にx, a, cがわかる。

x1 = a * x0 + c mod m
x2 = a * x1 + c mod m
x3 = a * x2 + c mod m

x2 - x1 = a * (x1 - x0) mod m
x3 - x1 = a * (x2 - x0) mod m

a = (x2 - x1) * modinv(x1 - x0, m)
c = x1 - (x0 * a) mod m

これで4バイト単位にxor鍵が割り出せるので、復号できる。

import struct

def lcg(m, a, c, x):
    return (a * x + c) % m

def egcd(a, b):
   if a == 0:
       return (b, 0, 1)
   else:
       g, y, x = egcd(b % a, a)
       return (g, x - (b // a) * y, y)

m = pow(2, 32)

with open('flag.png.enc', 'rb') as f:
    ciphertext = f.read()

PNG_HEAD = '\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0d'

x = []
for i in range(0, len(PNG_HEAD), 4):
    iXor = int(ciphertext[i:i+4].encode('hex'), 16)
    iPlain = int(PNG_HEAD[i:i+4].encode('hex'), 16)
    x.append(iXor ^ iPlain)

x1 = x[2] - x[1]
alpha = x[1] - x[0]

g1, p1, q1 = egcd(alpha, m)

if g1 == 1:
    mod_inv = p1 % m
elif g1 == -1:
    mod_inv = - p1 % m
else:
    modinv = 0
a = (x1 * mod_inv) % m

c = (x[1] - x[0] * a) % m

flag = ''
x = x[0]
for i in range(0, len(ciphertext), 4):
    flag += struct.pack('>I', x ^ struct.unpack('>I', ciphertext[i:i+4])[0])
    x = lcg(m, a, c, x)

while True:
    if flag[-1] == '\x00':
        flag = flag[:-1]
    else:
        break

with open('flag.png', 'wb') as f:
    f.write(flag)

復号したPNG画像にフラグが書いてあった。
f:id:satou-y:20180326201114p:plain

actf{pad_rng}

SSH (CRYPTO 150)

公開鍵をPEM形式に変換し、内容を確認する。

$ ssh-keygen -f id_rsa.pub -e -m PKCS8 > id_rsa.pub.pem
$ openssl rsa -pubin -text < id_rsa.pub.pem
Public-Key: (2048 bit)
Modulus:
    00:be:5d:95:8b:09:b2:29:1c:f0:bd:36:51:1c:91:
    55:e2:29:f8:ae:8d:cc:ae:e1:55:2c:96:69:b8:1b:
    53:2a:36:3b:4f:34:76:94:12:86:7c:cf:92:cb:40:
    ad:de:f5:20:0a:05:dc:de:f0:9c:8d:a3:09:82:fa:
    54:13:d9:52:f4:e7:db:3d:a7:39:51:9f:ab:77:d5:
    74:de:52:36:6c:96:03:ac:e8:87:c0:cb:f3:2c:52:
    47:ce:c1:42:28:e8:a7:2a:a5:25:67:99:e5:4c:40:
    f3:a2:2d:46:42:cd:af:5e:0d:d0:77:33:11:58:e7:
    d8:4d:ba:87:56:d5:31:a4:bb:4d:2b:a3:e7:9c:29:
    97:2f:27:eb:8d:0b:f9:df:81:e2:e9:cd:a2:3b:0d:
    de:ad:23:c0:0a:ae:bf:a0:f5:38:32:77:a2:21:77:
    72:9a:9c:b5:ee:58:c4:70:19:b6:cb:32:2d:7f:b9:
    a4:1d:f3:a2:d5:62:df:d2:02:f9:06:3b:5e:5e:50:
    42:cf:ef:6d:dc:fe:41:23:28:67:e1:c1:22:a8:dc:
    c1:8c:e5:1e:fb:b8:cc:5f:9b:c0:f3:29:6f:10:91:
    ca:30:10:ed:85:12:73:d4:ca:40:67:57:53:da:89:
    6a:e5:fc:fa:01:59:3a:7c:84:d5:18:c5:03:c0:ae:
    e5:81
Exponent: 65537 (0x10001)
writing RSA key
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvl2ViwmyKRzwvTZRHJFV
4in4ro3MruFVLJZpuBtTKjY7TzR2lBKGfM+Sy0Ct3vUgCgXc3vCcjaMJgvpUE9lS
9OfbPac5UZ+rd9V03lI2bJYDrOiHwMvzLFJHzsFCKOinKqUlZ5nlTEDzoi1GQs2v
Xg3QdzMRWOfYTbqHVtUxpLtNK6PnnCmXLyfrjQv534Hi6c2iOw3erSPACq6/oPU4
MneiIXdympy17ljEcBm2yzItf7mkHfOi1WLf0gL5BjteXlBCz+9t3P5BIyhn4cEi
qNzBjOUe+7jMX5vA8ylvEJHKMBDthRJz1MpAZ1dT2olq5fz6AVk6fITVGMUDwK7l
gQIDAQAB
-----END PUBLIC KEY-----
n = 0x00be5d958b09b2291cf0bd36511c9155e229f8ae8dccaee1552c9669b81b532a363b4f34769412867ccf92cb40addef5200a05dcdef09c8da30982fa5413d952f4e7db3da739519fab77d574de52366c9603ace887c0cbf32c5247cec14228e8a72aa5256799e54c40f3a22d4642cdaf5e0dd077331158e7d84dba8756d531a4bb4d2ba3e79c29972f27eb8d0bf9df81e2e9cda23b0ddead23c00aaebfa0f5383277a22177729a9cb5ee58c47019b6cb322d7fb9a41df3a2d562dfd202f9063b5e5e5042cfef6ddcfe41232867e1c122a8dcc18ce51efbb8cc5f9bc0f3296f1091ca3010ed851273d4ca40675753da896ae5fcfa01593a7c84d518c503c0aee581

秘密鍵は大部分がマスクされている。与えられた情報から秘密鍵を復元することができれば、フラグが得られそう。
https://tls.mbed.org/kb/cryptography/asn1-key-structures-in-der-and-pemを参考

RSAPrivateKey ::= SEQUENCE {
  version           Version,
  modulus           INTEGER,  -- n
  publicExponent    INTEGER,  -- e
  privateExponent   INTEGER,  -- d
  prime1            INTEGER,  -- p
  prime2            INTEGER,  -- q
  exponent1         INTEGER,  -- d mod (p-1)
  exponent2         INTEGER,  -- d mod (q-1)
  coefficient       INTEGER,  -- (inverse of q) mod p
  otherPrimeInfos   OtherPrimeInfos OPTIONAL
}

バイナリで構成を見てみる。

version         0x0000 - 0x0006 (30 82 04 A3 02 01 00)	7
modulus         0x0007 - 0x010b (02 82 01 01)		261(257)
publicExponent  0x010c - 0x0110 (02 03) 010001		5(3)
privateExponent 0x0111 - 0x0214 (02 82 01 00)		260(256)
prime1          0x0215 - 0x0298 (02 81 81)		132(129)
prime2          0x0299 - 0x031c (02 81 81)		132(129)
exponent1       0x031d - 0x03a0 (02 81 81)		132(129)
exponent2       0x03a1 - 0x0423 (02 81 80)		131(128)
coefficient     0x0424 - 0x04a6 (02 81 80)		131(128)

最初の624バイトが不明。625バイト目以降144バイト分がわかっている。prime1の後半とprime2の前半しかわからない。

prime1: 625-665(下41バイトのみ判明)
prime2: 669-768(上100バイトのみ判明)

片方の素数の上位ビット半分以上が判明しているため、Coppersmith's Attackを行い、素数を割り出す。

# solve.sage
n = 0x00be5d958b09b2291cf0bd36511c9155e229f8ae8dccaee1552c9669b81b532a363b4f34769412867ccf92cb40addef5200a05dcdef09c8da30982fa5413d952f4e7db3da739519fab77d574de52366c9603ace887c0cbf32c5247cec14228e8a72aa5256799e54c40f3a22d4642cdaf5e0dd077331158e7d84dba8756d531a4bb4d2ba3e79c29972f27eb8d0bf9df81e2e9cda23b0ddead23c00aaebfa0f5383277a22177729a9cb5ee58c47019b6cb322d7fb9a41df3a2d562dfd202f9063b5e5e5042cfef6ddcfe41232867e1c122a8dcc18ce51efbb8cc5f9bc0f3296f1091ca3010ed851273d4ca40675753da896ae5fcfa01593a7c84d518c503c0aee581
e = 65537

priv_part = '''
YC2/ZTbmSZFL9t5Em+ic2ayw0nNUSI6XO7+3tcT9TABzh94t9YLhiDcCgYEA0LFZ
OUTgvmnWAkwGSo/6huQOu/7VmsM7OBdFntgotOJXALXFqCeT2PMXyWVc9/6ObUZj
z9LQUlT6mnzYwFrX4mPPOTY5nvCyjepQlSDA7w49yaRhXKCFRHmEieeFJqzrZoQG
'''

der = priv_part.decode('base64')
q_pre = der[44:].encode('hex')

kbits = 29 * 8
q_bar = int(q_pre + '0' * (29 * 2), 16)

PR.<x> = PolynomialRing(Zmod(n))
f = x + q_bar

x0 = f.small_roots(X=2^kbits, beta=0.3)[0]
q = x0 + q_bar
print q

この結果から、p, qは以下の通りとわかる。

q = 146549045227354172989110651205310632574067392372993711861623526130946979713469625772410411197033006823693645223316947254702263484103463390279967082549781844195965622071604103417650285219901124684816890970225510506869249766243257198851929421990982433654502509027141033118789478594390852225097012248592818058247
p = 163982139712862418555526520701188853132435025142588334581109287244065993155706892302449577647543501705795708316995057467949529976798218410513821698432879399377481736303821213515873396833696040164615779257072417113571942677691455176550027493593024586313878742269786944829130979576180492741390980797445630429239

秘密鍵を生成する。

$ rsatool.py -f PEM -o privkey.pem -p 163982139712862418555526520701188853132435025142588334581109287244065993155706892302449577647543501705795708316995057467949529976798218410513821698432879399377481736303821213515873396833696040164615779257072417113571942677691455176550027493593024586313878742269786944829130979576180492741390980797445630429239 -q 146549045227354172989110651205310632574067392372993711861623526130946979713469625772410411197033006823693645223316947254702263484103463390279967082549781844195965622071604103417650285219901124684816890970225510506869249766243257198851929421990982433654502509027141033118789478594390852225097012248592818058247
Using (p, q) to initialise RSA instance

n =
be5d958b09b2291cf0bd36511c9155e229f8ae8dccaee1552c9669b81b532a363b4f34769412867c
cf92cb40addef5200a05dcdef09c8da30982fa5413d952f4e7db3da739519fab77d574de52366c96
03ace887c0cbf32c5247cec14228e8a72aa5256799e54c40f3a22d4642cdaf5e0dd077331158e7d8
4dba8756d531a4bb4d2ba3e79c29972f27eb8d0bf9df81e2e9cda23b0ddead23c00aaebfa0f53832
77a22177729a9cb5ee58c47019b6cb322d7fb9a41df3a2d562dfd202f9063b5e5e5042cfef6ddcfe
41232867e1c122a8dcc18ce51efbb8cc5f9bc0f3296f1091ca3010ed851273d4ca40675753da896a
e5fcfa01593a7c84d518c503c0aee581

e = 65537 (0x10001)

d =
620075bb457b95d4d34ee586ae6957c87e090b7beeb2dd486712ec4c1ead1adf1e7b712bd6a10ee1
744f431a0228f512d076223617b2d0ebed3aa3bae3190faf0b2a003c75b2c2bb988ea882c7da42de
9bf7c922122c2cfd5542a87b2f9f35ded18281962b51338780a5ae1f2cc70d1023967db729a8167b
71d0a45a1c99590f3c4bfa59f6cb99808d1b8c4e5c0ca7feeec7f2c88f6cee0a343be1f1217c23c2
ec0e779740374cffda319dc8797a0b010a58b0ad79e33adc3ca088dcbf4fdb68da954c49b3aa693f
0e0545d6845e3413d720a2df5c98158e4b45d2bd6e2ea08672db20644ea9aec7c192c1087b970564
cd929d43e5ea32f1c1e19a266adb84ed

p =
e984b0820151d7ed6a86569073ea12e64d36fa33bc360082cd1e87ad0618f6dd6f0dbac674170c3d
11f1e8ea1208c48ee3c6313f7703138fc2d2dc8e94fdd4a427bd92a420de8a025d0423e80351acaa
2c92bfe5e11729602dbf6536e649914bf6de449be89cd9acb0d27354488e973bbfb7b5c4fd4c0073
87de2df582e18837

q =
d0b1593944e0be69d6024c064a8ffa86e40ebbfed59ac33b3817459ed828b4e25700b5c5a82793d8
f317c9655cf7fe8e6d4663cfd2d05254fa9a7cd8c05ad7e263cf3936399ef0b28dea509520c0ef0e
3dc9a4615ca08544798489e78526aceb668406cb284ebe0ee0120defe7405ffe7d76fb6bb469183c
b262822c9b6f3407

Saving PEM as privkey.pem

この秘密鍵SSH接続する。

$ ssh -i privkey.pem ctf@web.angstromctf.com -p 3004
The authenticity of host '[web.angstromctf.com]:3004 ([162.243.30.173]:3004)' can't be established.
ECDSA key fingerprint is 85:61:bc:f6:51:7c:c9:44:ec:b3:bd:e3:f6:fc:d0:d6.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[web.angstromctf.com]:3004,[162.243.30.173]:3004' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 16.04.4 LTS (GNU/Linux 4.4.0-116-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage
actf{ssh_keys_not_broken_enough}
Connection to web.angstromctf.com closed.
actf{ssh_keys_not_broken_enough}