BCTF 2017 供養(Writeup)

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)

Spidermonkeyjsshellが動いている。 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)

関連リンク