HGAME2020 Final Writeup

居然能苟進決賽
絕了


Misc - Good Video

題目:

Need a video to fresh your mind?

終於又見到misc了

先binwalk掃了一遍壓縮包和視頻文件,得到了一大堆東西。。。。。
(居然還有vmdk?)

查了一下頭部的EBML文件格式,發現這是一種面向未來的音視頻框架,而且之後的Matroska也是一種封裝格式
這些文件格式爲了支持未來出現的新壓縮格式因此靈活度很高,所以出題人可以在裏面塞很多文件(然而並沒有)
各種分離操作失敗以後嘗試直接對視頻下手。Matroska格式最典型的例子就是.mkv文件。於是在網上下載MKVToolNix並嘗試讀取文件的音軌、字幕、備註等信息,無果
然後突然想起出題人叫我們多看看視頻

於是仔細欣賞了一遍小姐姐,發現視頻裏有若干張黑白色的圖片閃過
用MKVToolNix看了一下幀的信息,因爲肉眼能很明顯看到黑白圖片,所以優先查看關鍵幀,結果如下

考慮到手動找幀太麻煩,而且MKVToolNix不便於導出,於是使用ffmpeg來導出視頻

.\ffmpeg\bin\ffmpeg -i 1.mkv .\out\%d.jpg  

(需事先新建.\out文件夾)
然後得到了22000+張圖片。。。。。

按照大小排序,先把過大的圖片刪掉,再篩選一下,然後找到了九張圖片

拼合一下得到完整二維碼,掃碼即可獲得flag

最終flag:hgame{gO0D_vId3O_EESvLoEPyC$zHlOJEHc0h&14}


Crypto - Response

題目:

Alice use mutual authentication protocol for her server, can you find her secret?
今日份的簽到題

以及服務端腳本:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import signal
import binascii
import socketserver

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

from Alice import KEY, MESSAGE

class Task(socketserver.BaseRequestHandler):

    def __init__(self, *args, **kargs):
        self.alice_prefix = b'Alice'
        self.server_prefix = b'Server'
        self.KEY = KEY
        super().__init__(*args, **kargs)

    def _recvall(self):
        BUFF_SIZE = 1024
        data = b''
        while True:
            part = self.request.recv(BUFF_SIZE)
            data += part
            if len(part) < BUFF_SIZE:
                break
        return data.strip()

    def send(self, msg, newline=True):
        try:
            if newline: 
                msg += b'\n'
            self.request.sendall(msg)
        except:
            pass

    def recv(self, prompt=b'> '):
        self.send(prompt, newline=False)
        return self._recvall()

    def encrypt(self, data):
        iv, pt = data[:AES.block_size], data[AES.block_size:]
        pt = pad(pt, AES.block_size)
        aes = AES.new(self.KEY, AES.MODE_CBC, iv=iv)
        ct = aes.encrypt(pt)
        return iv + ct

    def decrypt(self, data, unpad_pt=False):
        iv, ct = data[:AES.block_size], data[AES.block_size:]
        aes = AES.new(self.KEY, AES.MODE_CBC, iv=iv)
        pt = aes.decrypt(ct)
        if unpad_pt:
            pt = unpad(pt, AES.block_size)
        return pt

    def timeout_handler(self, signum, frame):
        self.send(b"\n\nSorry, time out.\n")
        raise TimeoutError

    def handle(self):
        signal.signal(signal.SIGALRM, self.timeout_handler)
        signal.alarm(60)

        try:
            iv = os.urandom(AES.block_size)

            for _ in range(3):
                name = self.recv(prompt=b'\nWho are you? ')
                if name == b'Alice':
                    # authenticate the server
                    hex_challenge = self.recv(
                        prompt=b'Give me your challenge (in hex): '
                    )
                    challenge = binascii.unhexlify(hex_challenge)
                    response = self.encrypt(
                        iv 
                        + self.server_prefix
                        + challenge
                    )
                    hex_response = binascii.hexlify(response)
                    self.send(b'The Response (in hex): ' + hex_response)

                    # authenticate Alice
                    challenge = os.urandom(AES.block_size)
                    hex_challenge = binascii.hexlify(challenge)
                    self.send(b'The Challenge (in hex): ' + hex_challenge)
                    hex_response = self.recv(
                        prompt=b'Give me your response (in hex): '
                    )
                    response = binascii.unhexlify(hex_response)
                    data = self.decrypt(response)
                    if (data.startswith(self.alice_prefix)
                            and challenge in data):
                        self.send(b'\nWelcome, Alice.')
                        self.send(b'Here is a message for you: ')
                        self.send(b'\t' + MESSAGE)
                    else:
                        self.send(b'Go away hacker!')
                else:
                    self.send(b"You shouldn't be here.")
                    break

        except:
            pass

        self.request.close()

class ForkedServer(socketserver.ForkingMixIn, socketserver.TCPServer):
    pass

if __name__ == "__main__":
    HOST, PORT = '0.0.0.0', 1234
    server = ForkedServer((HOST, PORT), Task)
    server.allow_reuse_address = True
    server.serve_forever()

如同題目所說,確實是簽到題,整個題就裸的CBC字節翻轉攻擊。但由於自己對這一方式不熟,再加上足夠沙雕,浪費了很多時間

一開始根據題目提示去搜索Challenge/Response認證,沒有找到明顯的攻擊方法。再加上之前玩PM3的時候瞭解到這種認證的應用範圍很廣,應該比較安全,所以準備從CBC下手
做Week4的CBCBC時瞭解到針對CBC的攻擊方式一般是字節翻轉攻擊(byte flipping attack)和填充提示攻擊(padding oracle attack),上次考了後者,那麼這次應該就是字節翻轉攻擊了。

字節翻轉攻擊的教程網上很多,其原理是通過修改當前密文塊來將下一個塊的明文改爲我們想要的內容。分析服務端的源碼可知,當我們提交的response可以被解密成"Alice"+含challenge的文本時即可得到MESSAGE(因爲這裏response的內容不是唯一指定的,所以不會涉及到多次加密構造密文,只需要一輪雙方的challenge/response即可得到結果)(感謝出題人)

其具體原理是,根據CBC模式加解密的流程圖,解密過程中總有P[i] = C[i-1] ^ Mid[i]
(Mid[i]在本題中是通過C[i]和KEY通過AES算法解密得到的)
因此可以得到P[i] ^ C[i-1] = Mid[i]
而如果令C[i-1]=P[i] ^ C[i-1] ^ T[i](T[i]爲我們希望服務器解密後得到的明文塊)
則有P’[i] = P[i] ^ C[i-1] ^ T[i] ^ Mid[i] = Mid[i] ^ T[i] ^ Mid[i] = T[i],也即達到了修改服務器解密結果的目的
(當i=1時,第一個塊P[1]對應的C[1-1]爲IV,IV是加/解密時使用的初始化向量)
觀察本題中的decrypt函數,其使用的IV來自response(由Alice提供)而不是服務器在加密時生成的IV,這也爲構造第一個塊的"Alice"提供了可能(而且解密時不考慮填充,進一步降低難度)
因此對於一輪雙方的challenge/response,修改第i個塊就需要提交構造過的i-1塊密文和正確的i塊密文,爲了符合題目要求,需要至少提交4個塊的內容作爲response(構造的IV+原始第1塊密文+構造密文+原始第3塊密文),使得服務器解密獲得”Alice…“塊+廢棄塊+challenge塊以通過驗證
而由於一開始先驗證服務器的身份,因此能夠提前獲得本次連接中正確的IV以及用作素材的明文/密文對

具體攻擊思路如下:
定義一個對bytes的異或函數

XOR = lambda s1, s2: bytes([x ^ y for x, y in zip(s1, s2)])

先提交b’\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00’(10個\x00)作爲驗證服務器的challenge
服務器對P[1]:b’Server\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00’(剛好是一個塊的大小)進行加密,返回IV+C[1]+C[padding](最後一個塊用處不大)
之後服務器提供response4alice
此時令attack_IV=IV ^ P[1] ^ T[1] (T[1] = b’Alice\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00’)
令attack_ciphtxt=C[1] ^ P[1] ^ response4alice
提交attack_IV + C[1] + attack_ciphtxt + C[1]
即可通過驗證

攻擊腳本如下(當中變量命名和wp中稍有不同):

from socket import socket
from telnetlib import Telnet
from time import sleep
from binascii import hexlify, unhexlify
import re

XOR = lambda s1, s2: bytes([x ^ y for x, y in zip(s1, s2)])

mychalnge = b'Server\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
sendtext = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

sock = socket()
sock.connect(("xxx.xxx.xxx.xxx", xxxxx))
tel = Telnet()
tel.sock = sock
sleep(0.1)

text = tel.read_until(b'? ').decode()
print("Server:" + text)

tel.write(b"Alice")
sleep(0.1)

text = tel.read_until(b': ').decode()
print("Server:" + text)

tel.write(hexlify(sendtext))
sleep(0.1)

text = tel.read_until(b'The Challenge (in hex): ').decode()
print("Server:" + text)

c = unhexlify(re.search(r'(?<=(: )).+(?=\n)', text).group(0))
# print('###',c)
iv = c[:16]
cc = c[16:32]
target = b'Alice\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
attack_iv = XOR(iv, mychalnge)
print("!!!",attack_iv)
attack_iv = XOR(attack_iv, target)

text = tel.read_until(b'se (in hex): ').decode()
print("Server:" + text)

# print("###", re.search(r'.+(?=\n)', text).group(0))
srvchalnge = unhexlify(re.search(r'.+(?=\n)', text).group(0))
attack_cipher = XOR(iv, mychalnge)
attack_cipher = XOR(attack_cipher, srvchalnge)

tel.write(hexlify(attack_iv + cc + attack_cipher + cc))
tel.interact()

最終服務器返回信息如下:

Welcome, Alice.
Here is a message for you: 
        The flag is hgame{RefL3cti0n_@tt4ck_wiTh_c8C~B1t-fLiPpiNg}, keep it secret.

最終flag:hgame{RefL3cti0n_@tt4ck_wiTh_c8C~B1t-fLiPpiNg}

沙雕操作:
沒有注意到服務器計算response時會先加上一個”Server“,直接將16個b’\x00’作爲P[1]
構造response時寫成了attack_IV + P[1] + attack_ciphtxt + P[1],爲此試了2個小時


Misc - Good Video(未做完)
只差5分鐘…

題目:

Nothing but a pcapng.
【修復補給】http://oss-east.zhouweitong.site/hgamefinal/flag.png.zip,懂的人自然懂  

一開始沒有修復補給,只有一個含pcapng的壓縮包

丟到wireshark裏面,發現主要是SMTP的包,其中還有一些TELNET的包
一番亂點之後在文件→導出對象→IMF…中找到了幾個奇怪的包

經過分析,第一個包內含有一個RSA私鑰文件,第二個包內是一個壓縮文件。兩者都是通過base64來傳輸的
導出base64文本文件後通過一個簡單的腳本來恢復文件

import base64
strs=open("C:\\hgame\\final\\out.txt", "r").read()
res = base64.b64decode(strs)
f = open("C:\\hgame\\final\\1.zip", "wb+")
f.write(res)

之後打開壓縮文件,提示壓縮文件被損壞。經過WinRAR修復之後得到了一個加密的壓縮包。嘗試通過處理僞加密的方法來處理壓縮包,結果解壓文件時校驗錯誤,說明壓縮包確實有密碼

打開RSA私鑰文件,裏面的內容沒有異常
在wireshark當中繼續搜尋郵件相關的包,發現了一些登錄郵箱時的認證信息

將這些文本用於解壓,均提示密碼錯誤
之後搜索http包,沒有結果
後來在瀏覽包的時候偶然發現有TLS包

之前做含TLS包題目的時候是通過瀏覽器的日誌文件導入密鑰,從而解密包,然而此題中並沒有這樣的日誌文件(或者說暫時沒找到)
而剛纔已經獲得了一個RSA文件,結合相關郵件內容中提到”web dev“,該文件也許可以用來解密TLS包
依次進入編輯→首選項→協議(Protocols)→TLS(或SSL)→RSA keys list,新建一個條目,在Key File一欄當中加入剛纔的RSA私鑰文件並保存,之後wireshark中便出現了http包

追蹤HTTP流,最後得到了一個pgp文件(仍然需要先base64解碼)

之前聽說過pgp可用於消息加密和數字簽名,結合加密壓縮文件中的”flag.png.gpg“,這個pgp文件應該是用來解密flag.png的
(pgp(pretty good privacy)是一套用於加密或驗證的系統(或者規範?),而gpg(GnuPG, Gnu Privacy Guard)是基於OpenPGP標準開發的自由軟件)
然而壓縮文件密碼的問題還是沒有解決掉
考慮到pcapng文件中有好幾個telnet包,於是嘗試從當中提取信息。當中有若干個包僅含單個字符,拼接後大概是netstat -ano,exit,tasklist之類的cmd命令(此時是按大小排序,挖坑)。而之後較大的telnet包中則含有這些命令的運行結果,但是有很多亂碼

之後實在找不到思路了,於是py了一下出題人

把幾個較大的包中的內容整理了一下,得到以下結果:

然而並沒有什麼發現
之後百度"wireshark telnet",得知telnet的登陸過程中其密碼也是以單個字符的方式發送出去的。經過一番尋找和拼接,找到了登錄時提交的"hgame_final"和"WelcomeToHgameFinal"兩個字符串
(因爲按大小排序後"login"和"password"兩個包出現在後面,難以找到其對應的輸入信息)
經測試,後者可以解壓提取出的壓縮文件,但是解壓過程中卻提示壓縮包已損壞。此時使用【修復補給】當中提供的壓縮包進行解壓,得到了flag.png.gpg
之後命令行下用HTTP流中的pgp文件解密出flag.png

gpg --import 1.pgp
gpg -o out.png -d flag.png.gpg
gpg --list-keys
gpg --delete-secret-keys 1ABD371F65FED93CF29F5314A70843D7B05D5C19
gpg --delete-keys 1ABD371F65FED93CF29F5314A70843D7B05D5C19

先在kali中直接打開文件,發現CRC error,binwalk識別到zlib數據,但是改名爲zip文件後仍然無法打開文件(其實zlib和zip不是一個東西)。後來把文件轉移到Windows下打開,顯示正常,用StegSolve把大部分功能都用了一遍,無果
(然後比賽就結束了)
冷靜下來後回憶起之前看到過png文件的實際尺寸可能和屬性中的尺寸不一樣,於是在網上搜索處理方法。用16進制編輯器將文件頭中的長度數據度變大之後即可看到flag

最終flag:hgame{Re4LlY_gO0D_P4ck3ts_92252043}(沒能提交到平臺上,不知道有沒有打錯字)


提前裝了sage和z3,看了一大堆線性反饋移位寄存器,對着BM算法想了很久,結果對第二道Crypto還是束手無策
畢竟是final

2020.03.07

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章