White Hat への道 - TryHackMe Room: Scripting Task3 [Hard] -

White Hat Hackerになるまでの道のりを記しておく。

実際になったわけではなく、なるために勉強をすすめる際のメモを残しておこうと思う。

ここ数ヶ月、[TryHackMe](https://tryhackme.com)というサイトにどハマリ中。朝の出社前や帰宅後の１時間ほどは必ずサイトにアクセスして練習をしている。

数年後の自分のために、現時点での過去最高に時間がかかった問題についてコメントしておく。

該当のRoomは「[Scripting](https://tryhackme.com/room/scripting)」。　この中の「Task 3  [Hard] Encrypted Server Chit Chat」　というタスクに３日ほどかかった。

このタスクは、自前のKali Linux マシンからOpenVPN経由でターゲットホストへ、UDPリクエストを送りつけると、次の指示がもらえる。

この指示通りに進めていき、最終的なflagを探すという流れだ。

ちなみにこのタスクの最初の説明文は以下だ。

>The VM you have to connect to has a UDP server running on port 4000. Once connected to this UDP server, send a UDP message with the payload "hello" to receive more information. You will find some sort of encryption(using the AES-GCM cipher). Using the information from the server, write a script to retrieve the flag. Here are some useful thingsto keep in mind:

>sending and receiving data over a network is done in bytes
the PyCA encryption library and functions takes its inputs as bytes
AES GCM sends both encrypted plaintext and tag, and the server sends these values sequentially in the form of the encrypted plaintext followed by the tag
This machine may take up to 5 minutes to configure once deployed. Please be patient. 

>Use this general approach(use Python3 here as well):

>Use the Python sockets library to create a UDP socket and send the aforementioned packets to the server
use the PyCA encyption library and follow the instructions from the server

>What is the flag?


そしてたどり着いた答えが以下。

何をUDP requestとして送りつける必要があるのか、 keyや iv、 tag、チェックサムをどのように使うのか。　これらの解にたどり着くまでに、AES GCMとは何なのか、[PyCA encryption library](https://cryptography.io/en/latest/)の使い方（特に、どのクラスのどのメソッドを使用す
れば良いのか）などを調べていく必要があった。

<b>ネタバレ</b>なので、もしも上記のTaskにトライしてみたいと思った方は、ここで読み終わってほしい。

---
まず、AES-GCMとはなにかを調べる必要があった。　なぜならば、「iv」ってなんだ？という感じになり、何をすればよいかフリーズしてしまいそうだったからだ。

AES-GCMについてわかりやすかったのは以下のサイトだった。 AES-GCMで探すよりも、AESとは？とGCMとは？でまずは調べたほうがわかりやすいと思った。
https://www.mbsd.jp/research/20200901_2.html
https://www.ibm.com/support/knowledgecenter/ja/SSLTBW_2.3.0/com.ibm.zos.v2r3.csfb400/csfb4433.htm
https://ja.wikipedia.org/wiki/Galois/Counter_Mode

この暗号化方式を使用した場合、以下の２つの出力がある。
 * 暗号文 (Ciphered message)
 * タグ (認証タグ, tag)

そして、復号化するには下記の２つも必要だ。
 * key
 * iv (初期化ベクトル=nonce)
 
UDPサーバからは、これらが順に送信される。　それぞれが単独のUDPレスポンスで送られる。つまり、２回リクエストしないと受け取れない。　さらにいうと、３回目にアクセスすると、また同じようなものが送られてくる。　これが何なのかを理解するのにも時間がかかった。　結局は、２セット目の暗号文とタグのセットだった。

ライブラリの使い方でハマったポイントは、どのクラスの何をどう呼び出せばいいかがわからなかったこと。
最初は[AES-GCM](https://cryptography.io/en/latest/hazmat/primitives/aead.html#cryptography.hazmat.primitives.ciphers.aead.AESGCM)というラッパークラスで何度もトライしたが、うまく行かなかった。　そこで、ライブラリのサイトを読み漁ったところ、モード指定でアクセスできることがわかった。

あとは、チェックサムの使い方も戸惑った。　（復号化する前の）暗号文のチェックサムと比較するのか、それとも復号化後の文字列のチェックサムと比較するのかわからなかったからだ。　（もしかすると知識人が見るとアホなハマり方をしているのかもしれないが）　この解にたどり着くためには、ループでブンブン回して比較しまくった。

```python
import socket
import os
import hashlib
import time

from hmac import compare_digest
from cryptography.hazmat.primitives.ciphers import (
    Cipher, algorithms, modes
)
from cryptography.hazmat.backends import default_backend
from cryptography.exceptions import InvalidTag

UDP_IP = "10.10.135.119"
UDP_PORT = 4000
INIT_MESSAGE = b"hello"

DATA_LEN = 4096

print("UDP target IP: {}".format(UDP_IP))
print("UDP target port: {}".format(UDP_PORT))
print("message: {}".format(INIT_MESSAGE))

# send the greeting message
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(INIT_MESSAGE, (UDP_IP, UDP_PORT))

# receive the first response
data, addr = sock.recvfrom(DATA_LEN)
print("received message: {}".format(data))

# receive the second response
print("")
print("sending 'ready' for the next payload")
sock.sendto(b"ready", (UDP_IP, UDP_PORT))
data, addr = sock.recvfrom(DATA_LEN)
print("received message: {}".format(data))

key, iv = data.split()[:2]
key = key.split(b":")[-1]
iv = iv.split(b":")[-1]
print("key: {}".format(key))
print("iv: {}".format(iv))

checksum= data.split()[14]
print("checksum in hex: {}".format(checksum.hex()))

decrypted_msg = ""
while True:
    try:
        # receive the ciphered text
        print("")
        print("sending 'final' for cyphered flag")
        sock.sendto(b"final", (UDP_IP, UDP_PORT))
        cFlag, addr = sock.recvfrom(DATA_LEN)
        print("cFlag: {}".format(cFlag))
        print("cFlag in hex: {}".format(cFlag.hex()))

        # receive the tag
        print("")
        print("sending 'final' for the tag")
        sock.sendto(b"final", (UDP_IP, UDP_PORT))
        tag, addr = sock.recvfrom(DATA_LEN)
        print("tag: {}".format(tag))

        # decrypt the ciphered flag
        print("")
        
        decryptor = Cipher(
                algorithms.AES(key),
                modes.GCM(iv, tag),
                backend=default_backend()
        ).decryptor()

        decrypted_msg = decryptor.update(cFlag)
        print("decrypted_msg: {}".format(decrypted_msg))
        m = hashlib.sha256()
        m.update(decrypted_msg)
        decrypted_msg_checksum = m.digest()
        print("decrypted_msg_checksum: {}".format(decrypted_msg_checksum))

        # check the flag's SHA256 checksum
        if not compare_digest(checksum.hex(), decrypted_msg_checksum.hex()):
            print("")
            print("checksum donesnot match")
            print("              checksum: {}".format(checksum.hex()))
            print("decrypted_msg_checksum: {}".format(decrypted_msg_checksum.hex()))
            time.sleep(1)
            continue 
        else:
            print("checksum matched")
        
        break
    except InvalidTag as e:
        print("IngalidTag Exception")
        print(e)
    except KeyboardInterrupt as k:
        print(k)
        break

sock.close()
print("final process")
print("decrypted message: {}".format(decrypted_msg))
```