SCTF2019 Crypto-warmup writeup

題外話

其實這道題在比勝過程中並無解出來,思路徹底想偏致使無解就放棄了,後來研究了大佬的writeup大半天才看懂。。。
html

正文

nc獲取題目信息,返回一段明文和密文,要求輸入一段明文和密文。
題目源碼:python

# server.py
#!/usr/bin/python
# -*- coding: utf-8 -*-

from Crypto.Cipher import AES
from Crypto.Util.strxor import strxor
from Crypto.Random import get_random_bytes
from FLAG import flag

class MAC:
    def __init__(self):
        self.key = get_random_bytes(16)
        self.iv = get_random_bytes(16)

    def pad(self, msg):
        pad_length = 16 - len(msg) % 16
        return msg + chr(pad_length) * pad_length

    def unpad(self, msg):
        return msg[:-ord(msg[-1])]

    def code(self, msg):
        res = chr(0)*16
        for i in range(len(msg)/16):
            res = strxor(msg[i*16:(i+1)*16], res)
        aes = AES.new(self.key, AES.MODE_CBC, self.iv)
        return aes.encrypt(res).encode('hex')

    def identity(self, msg, code):
        if self.code(msg) == code:
            msg = self.unpad(msg)
            if msg == 'please send me your flag':
                print 'remote: ok, here is your flag:%s' % flag
            else:
                print 'remote: I got it'
        else:
            print 'remote: hacker!'


if __name__ == '__main__':
    mac = MAC()
    message = 'see you at three o\'clock tomorrow'
    print 'you seem to have intercepted something:{%s:%s}' %(mac.pad(message).encode('hex'), mac.code(mac.pad(message)))
    print 'so send your message:'
    msg = raw_input()
    print 'and your code:'
    code = raw_input()
    mac.identity(msg.decode('hex'), code)
    exit()

經過identity函數可知,本身輸入的明文在服務端加密後要等於本身輸入的密文(也就是self.code(msg) == code,才能獲得flag。dom

同時題目的坑點(也是我思路想歪的地方)就在這地方, 由於要求你輸入的明文加密後等於你輸入的密文,同時,加密使用的AES的CBC模式,對你的明文的加密是使用的和返回的第一段明文密文相同的iv和key,所以天然想到要獲得iv和key才能求出要輸入的密文。可是iv和key是經過隨機數生成的,因此就沒法用這個方法。ide

那麼換一種思路,咱們看看加密函數code如何工做的:函數

def code(self, msg):
        res = chr(0)*16
        for i in range(len(msg)/16):
            res = strxor(msg[i*16:(i+1)*16], res)
        aes = AES.new(self.key, AES.MODE_CBC, self.iv)
        return aes.encrypt(res).encode('hex')

既然AES沒法破解,那麼先看一下送進AES加密前明文,是將原明文分爲每16位一組而後接連異或獲得的16位字符串res,而且這個res咱們能夠求出來,也就至關於知道「明文」了。加密

若是咱們直接使用第一段返回的'code'做爲本身輸入的'code',那麼咱們只須要構造一串明文,使其經過code函數中AES加密前的操做,獲得的結果,等於第一段的'res',這樣輸入到服務端加密後的結果也等於了輸入的'code'(即第一段code)。3d

好了,如今就開始考慮如何構造明文。code

爲了獲得flag,咱們倒着推明文,看identity函數,知足msg == 'please send me your flag'才能獲得flag,其上一步是msg = self.unpad(msg)要求輸入的明文脫去填充後的字符串是「please send me your flag」,看一下unpad函數,msg[:-ord(msg[-1])]和填充函數pad是相反的做用,即取末尾字符的ASCII碼錶示的範圍以前的字符串。server

如今咱們將「please send me your flag」的長度記做len1 == 24,因爲AES加密的要求,字符串填充後的長度要知足爲16的倍數,所以這裏可填充8+16n個字符(n=0,1,2...)。根據unpad函數,必須讓填充後的字符串最末尾一個字符的ASCII碼可表示填充的位數。htm

先跑一下code函數中的一部分(就是AES加密前),得出第一段密文前一階段的'res':

message = '73656520796f75206174207468726565206f27636c6f636b20746f6d6f72726f770f0f0f0f0f0f0f0f0f0f0f0f0f0f0f'
for i in range(len(msg)/16):
    res = strxor(msg[i*16:(i+1)*16], res)
print(res.encode('hex'))
# res結果爲24054d4c1a0f19444e0f4016080f1805

目標就是咱們填充後的字符串異或處理後與上面的res(這裏記做res1)相等。

咱們設「please send me your flag」加上8位填充的32位並進行16位分組後獲得的兩個16位字符串爲m1和m2(暫時先無論填充是什麼)。由異或運算的性質可知,a XOR b XOR b = a。設m3爲一個能使m1 XOR m2 XOR m3 XOR m4== res1的16位分組,由此可知m3 == res1 XOR m1 XOR m2 XOR m4。同時要知足字符串最末尾一個字符的ASCII碼可表示填充的位數,而且此時字符串總長度位48,所以就再加一個16位m4分組保證最後一位表示填充長度(這裏是40)。
那麼如何獲得m3呢,就是用code函數裏的異或方式。

到此咱們就徹底知道要構造的輸入明文了。

下面是解題的完整代碼:

from Crypto.Cipher import AES
from Crypto.Util.strxor import strxor
from Crypto.Random import get_random_bytes

flag = 'test'

class MAC:
    def __init__(self):
        self.key = get_random_bytes(16)
        self.iv = get_random_bytes(16)

    def pad(self, msg):
        pad_length = 16 - len(msg) % 16
        return msg + chr(pad_length) * pad_length

    def unpad(self, msg):
        return msg[:-ord(msg[-1])]

    def code(self, msg):
        res = chr(0)*16
        for i in range(len(msg)/16):
            res = strxor(msg[i*16:(i+1)*16], res)
        print(res.encode('hex'))  # 輸出res1
        aes = AES.new(self.key, AES.MODE_CBC, self.iv)

        return aes.encrypt(res).encode('hex')

    def identity(self, msg, code):
        if self.code(msg) == code:
            msg = self.unpad(msg)
            if msg == 'please send me your flag':
                print 'remote: ok, here is your flag:%s' % flag
            else:
                print 'remote: I got it'
        else:
            print 'remote: hacker!'
            
if __name__ == '__main__':
    mac = MAC()
    message = 'see you at three o\'clock tomorrow'
    print 'you seem to have intercepted something:{%s:%s}' %(mac.pad(message).encode('hex'), mac.code(mac.pad(message)))
    print 'so send your message:'
    msg = 'please send me your flag'
    # 使用和pad函數同樣的填充方式先構成64位
    msg_p = msg + chr(64 - len(msg)) * (64 - len(msg))
    # 生成m1 XOR m2 XOR m4
    res = chr(0)*16
    for i in range(len(msg_p)/16 - 1):
        res = strxor(msg_p[i*16:(i+1)*16], res)
    # 最終輸入的明文字符串
    # strxor("24054d4c1a0f19444e0f4016080f1805".decode('hex'), res) 即爲上文的m3
    msg_p = msg_p[:32] + strxor("24054d4c1a0f19444e0f4016080f1805".decode('hex'), res) + msg_p[32:38]
    # 輸出明文串
    print(msg_p.encode('hex'))
    print 'and your code:'
    code = raw_input()
    mac.identity(msg_p.encode('hex').decode('hex'), code)
    exit()

將獲得的明文字符串輸入,再將服務端返回的密文輸入,就獲得flag了。

參考

我主要參考這篇文章寫出來的,因此思路也大都汲取自這位大佬的:
SCTF2019 部分題目WriteUp 搞懂以後感受這道題真是妙啊(雖然我沒作出來)。

相關文章
相關標籤/搜索