ACTF 2022 | secure connection

#ACTF_2022

from socket import socket
import socketserver
import argparse
from core import connection_engine, connection_handle_socket
import socket


def banner():
    print('''
     ___  ___  ___ _   _ _ __ ___  ___ ___  _ __  _ __  
    / __|/ _ \/ __| | | | '__/ _ \/ __/ _ \| '_ \| '_ \ 
    \__ \  __/ (__| |_| | | |  __/ (_| (_) | | | | | | |
    |___/\___|\___|\__,_|_|  \___|\___\___/|_| |_|_| |_|
                                                            
    CLIENT
    ''')


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("-a", "--address", required=True,
                        help="remote ip address")
    parser.add_argument(
        "-p", "--port", help="server running port", type=int, required=True)
    parser.add_argument("-d", "--dump", action="store_true", default=False,
                        help="dump payload of packet")
    parser.add_argument("-e", "--encrypt", action="store_true", default=False,
                        help="enable secure encrypted connection")
    args = parser.parse_args()

    HOST, PORT = args.address, args.port

    banner()

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    s.connect((HOST, PORT))

    handler = connection_handle_socket(s, "master", args.dump)
    connection_engine(handler, "master", args.encrypt)
import base64
from dataclasses import dataclass
from enum import Enum
from Crypto.Cipher import AES
import random
from telnetlib import SE
import libscrc


def bytes_xor_16(bytes1, bytes2):
    v1 = int.from_bytes(bytes1, 'big')
    v2 = int.from_bytes(bytes2, 'big')
    v3 = v1 ^ v2
    return (v3).to_bytes(16, 'big')


def secure_encrypt(key, plain):
    aes = AES.new(key=key, mode=AES.MODE_ECB)
    return aes.encrypt(plain)


def secure_encrypt_packet(key, plain, nonce):
    aes = AES.new(key=key, mode=AES.MODE_CCM, nonce=nonce)
    return aes.encrypt(plain)


def secure_decrypt_packet(key, plain, nonce):
    aes = AES.new(key=key, mode=AES.MODE_CCM, nonce=nonce)
    return aes.decrypt(plain)


def secure_confirm(key, r, p1, p2):
    return secure_encrypt(key, bytes_xor_16(secure_encrypt(key, bytes_xor_16(r, p1)), p2))


class PktOpcode(Enum):
    HELLO = 1
    SC_REQ = 2
    SC_RSP = 3
    M_CONFIRM = 4
    S_CONFIRM = 5
    M_RANDOM = 6
    S_RANDOM = 7
    DATA = 8


class connection_handle:
    def __init__(self, type) -> None:
        self.type = type

    def send(self, data) -> None:
        pass

    def recv(self, length) -> bytes:
        return b""


def dump_packet(prefix: str, pkt: bytes, logfile: str):
    log_content = ""
    print(prefix + "\t", end="")
    log_content += prefix + "\t"
    for i in range(len(pkt)):
        d = pkt[i]
        print(hex(d)[2:].rjust(2, '0'), end="")
        log_content += hex(d)[2:].rjust(2, '0')
        if (i + 1) % 16 == 0 and i + 1 != len(pkt):
            print("\n\t", end="")
            log_content += "\n\t"
        else:
            print(" ", end="")
            log_content += " "
    print("")
    log_content += "\n"
    with open(logfile, "a") as f:
        f.write(log_content)


class connection_handle_socket(connection_handle):
    def __init__(self, s, role, dump) -> None:
        super().__init__("socket")
        self.socket = s
        self.role = role
        self.dump = dump
        self.dumpfile = self.role + ".txt"

    def send(self, data) -> None:
        self.socket.send(data)
        if self.dump:
            dump_packet(">", data, self.dumpfile)

    def recv(self, length) -> bytes:
        data = self.socket.recv(length)
        if self.dump:
            dump_packet("<", data, self.dumpfile)
        return data


class connection_handle_request(connection_handle):
    def __init__(self, request, role, dump) -> None:
        super().__init__("request")
        self.request = request
        self.role = role
        self.dump = dump
        self.dumpfile = self.role + ".txt"

    def send(self, data) -> None:
        self.request.sendall(data)
        if self.dump:
            dump_packet(">", data, self.dumpfile)

    def recv(self, length) -> bytes:
        data = self.request.recv(length)
        if self.dump:
            dump_packet("<", data, self.dumpfile)
        return data


class connection_state:
    def __init__(self, role, encrypt) -> None:
        self.role = role
        self.local_counter = 0
        self.remote_counter = 0
        self.encrypt = encrypt
        self.initCRC = b""

    def inc_local_counter(self):
        self.local_counter += 1

    def inc_remote_counter(self):
        self.remote_counter += 1

    def calc_crc(self, pdu):
        initvalue = int.from_bytes(self.initCRC, "little")
        crc = libscrc.hacker24(data=pdu, poly=0x00065B, init=initvalue,
                               xorout=0x00000000, refin=True, refout=True)
        return crc.to_bytes(3, "little")

    def prepare_hello_packet(self):
        hello_packet = b""
        hello_packet += int(self.encrypt << 7 |
                            (PktOpcode.HELLO.value & 0x3f)).to_bytes(1, "little")
        hello_packet += int(3).to_bytes(1, "little")
        if not self.initCRC:
            self.initCRC = random.randbytes(3)
        hello_packet += self.initCRC
        hello_packet += self.calc_crc(hello_packet)
        # no encryption for hello
        return hello_packet

    def decrypt_data_packet(self, data):
        if self.encrypt:
            return secure_decrypt_packet(
                self.sessionkey, data, (self.remote_counter).to_bytes(
                    13, "little")
            )
        else:
            return data

    def prepare_data_packet(self, data, moredata):
        data_packet = b""
        data_packet += int(self.encrypt << 7 | moredata << 6 |
                           (PktOpcode.DATA.value & 0x3f)).to_bytes(1, "little")
        data_packet += len(data).to_bytes(1, "little")
        if self.encrypt:
            data_packet += secure_encrypt_packet(
                self.sessionkey, data, (self.local_counter).to_bytes(
                    13, "little")
            )
        else:
            data_packet += data
        data_packet += self.calc_crc(data_packet)
        return data_packet

    def prepare_sc_request_packet(self):
        sc_request_packet = b""
        sc_request_packet += int(self.encrypt << 7 |
                                 (PktOpcode.SC_REQ.value & 0x3f)).to_bytes(1, "little")
        sc_request_packet += int(16).to_bytes(1, "little")
        IV = random.randbytes(8)
        Secret = random.randbytes(8)
        self.IVm = IV
        self.Secretm = Secret
        sc_request_packet += IV
        sc_request_packet += Secret
        sc_request_packet += self.calc_crc(sc_request_packet)
        return sc_request_packet

    def prepare_sc_respond_packet(self):
        sc_request_packet = b""
        sc_request_packet += int(self.encrypt << 7 |
                                 (PktOpcode.SC_RSP.value & 0x3f)).to_bytes(1, "little")
        sc_request_packet += int(16).to_bytes(1, "little")
        IV = random.randbytes(8)
        Secret = random.randbytes(8)
        self.IVs = IV
        self.Secrets = Secret
        sc_request_packet += IV
        sc_request_packet += Secret
        sc_request_packet += self.calc_crc(sc_request_packet)
        return sc_request_packet

    def prepare_master_confirm_packet(self):
        master_confirm_packet = b""
        master_confirm_packet += int(self.encrypt << 7 |
                                     (PktOpcode.M_CONFIRM.value & 0x3f)).to_bytes(1, "little")
        master_confirm_packet += int(16).to_bytes(1, "little")
        master_random = random.randbytes(16)
        master_confirm = secure_confirm(
            self.numeric_key_bytes, master_random, b"\x00" * 16, b"\xff" * 16)
        self.MRandom = master_random
        self.MConfirm = master_confirm
        master_confirm_packet += master_confirm
        master_confirm_packet += self.calc_crc(master_confirm_packet)
        return master_confirm_packet

    def prepare_slave_confirm_packet(self):
        slave_confirm_packet = b""
        slave_confirm_packet += int(self.encrypt << 7 |
                                    (PktOpcode.S_CONFIRM.value & 0x3f)).to_bytes(1, "little")
        slave_confirm_packet += int(16).to_bytes(1, "little")
        slave_random = random.randbytes(16)
        slave_confirm = secure_confirm(
            self.numeric_key_bytes, slave_random, b"\x00" * 16, b"\xff" * 16)
        self.SRandom = slave_random
        self.SConfirm = slave_confirm
        slave_confirm_packet += slave_confirm
        slave_confirm_packet += self.calc_crc(slave_confirm_packet)
        return slave_confirm_packet

    def prepare_master_random_packet(self):
        master_random_packet = b""
        master_random_packet += int(self.encrypt << 7 |
                                    (PktOpcode.M_RANDOM.value & 0x3f)).to_bytes(1, "little")
        master_random_packet += int(16).to_bytes(1, "little")
        master_random_packet += self.MRandom
        master_random_packet += self.calc_crc(master_random_packet)
        return master_random_packet

    def prepare_slave_random_packet(self):
        slave_random_packet = b""
        slave_random_packet += int(self.encrypt << 7 |
                                   (PktOpcode.S_RANDOM.value & 0x3f)).to_bytes(1, "little")
        slave_random_packet += int(16).to_bytes(1, "little")
        slave_random_packet += self.SRandom
        slave_random_packet += self.calc_crc(slave_random_packet)
        return slave_random_packet

    def check_master_confirm(self, recvMrandom):
        should_Mconfirm = secure_confirm(
            self.numeric_key_bytes, recvMrandom, b"\x00" * 16, b"\xff" * 16)
        if should_Mconfirm == self.MConfirm:
            self.MRandom = recvMrandom
            return True
        else:
            return False

    def check_slave_confirm(self, recvSrandom):
        should_Sconfirm = secure_confirm(
            self.numeric_key_bytes, recvSrandom, b"\x00" * 16, b"\xff" * 16)
        if should_Sconfirm == self.SConfirm:
            self.SRandom = recvSrandom
            return True
        else:
            return False

    def setup_session(self):
        self.storekey = secure_encrypt(
            self.numeric_key_bytes, self.MRandom[:8] + self.SRandom[8:])
        self.sessionkey = secure_encrypt(
            self.storekey, self.Secretm + self.Secrets)


def showChoice():
    print("================ CHOICE ================")
    print("[0] Receive a message")
    print("[1] Send a message")
    print("[2] Leave")
    print("-----------------======-----------------")


def goodBye():
    print("-----------------======-----------------")
    print("See you next time\n")


def recieve_message(state: connection_state, handler: connection_handle):
    entire_data_payload = b""
    while True:
        dataheader = handler.recv(2)
        more_data = (dataheader[0] >> 6) & 0b1
        opcode = dataheader[0] & 0x3f
        if opcode != PktOpcode.DATA.value:
            print("Weird not data packet receieved")
            return
        datalength = dataheader[1]
        payload = handler.recv(datalength)
        crc = handler.recv(3)
        should_crc = state.calc_crc(dataheader + payload)
        payload = state.decrypt_data_packet(payload)
        state.inc_remote_counter()
        if crc != should_crc:
            print("CRC check failed in receive message")
            return
        entire_data_payload += payload
        if not more_data:
            break
    decoded_data = base64.b64decode(entire_data_payload).decode()
    print("Your recv : " + decoded_data)


def send_message(state: connection_state, handler: connection_handle):
    data = input("Your data > ").strip().encode()
    encoded_data = base64.b64encode(data)
    # split into segements
    encoded_data_len = len(encoded_data)
    for start in range(0, encoded_data_len, 255):
        if start + 255 > encoded_data_len:
            end = encoded_data_len
            moreData = False
        else:
            end = start + 255
            moreData = True
        data_segment = encoded_data[start: end]
        data_packet = state.prepare_data_packet(data_segment, moreData)
        handler.send(data_packet)
        state.inc_local_counter()


def master_hello_procedure(handler: connection_handle, state: connection_state):
    handler.send(state.prepare_hello_packet())
    state.inc_local_counter()
    hello_pkt = handler.recv(2 + 255 + 3)
    state.inc_remote_counter()
    if hello_pkt[0] & 0x3f != PktOpcode.HELLO.value:
        return False
    encrypt_or_not = (hello_pkt[0] >> 7) & 0b1
    if not encrypt_or_not:
        return True
    handler.send(state.prepare_sc_request_packet())
    state.inc_local_counter()
    numeric_key = int(input("Shared numeric key > "))
    numeric_key = numeric_key % 0x1000000
    state.numeric_key = numeric_key
    state.numeric_key_bytes = (numeric_key).to_bytes(16, "little")
    sc_respond_pkt = handler.recv(2 + 255 + 3)
    state.inc_remote_counter()
    if sc_respond_pkt[0] & 0x3f != int(PktOpcode.SC_RSP.value):
        return False
    recvIVs = sc_respond_pkt[2: 10]
    recvSecrets = sc_respond_pkt[10: 18]
    crc = sc_respond_pkt[18: 18 + 3]
    should_crc = state.calc_crc(sc_respond_pkt[:18])
    if crc != should_crc:
        print("CRC check failed in hello procedure")
        return False
    state.IVs = recvIVs
    state.Secrets = recvSecrets
    handler.send(state.prepare_master_confirm_packet())
    state.inc_local_counter()
    sconfirm_pkt = handler.recv(2 + 255 + 3)
    state.inc_remote_counter()
    if sconfirm_pkt[0] & 0x3f != int(PktOpcode.S_CONFIRM.value):
        return False
    recvSConfirm = sconfirm_pkt[2: 18]
    crc = sconfirm_pkt[18: 18 + 3]
    should_crc = state.calc_crc(sconfirm_pkt[:18])
    if crc != should_crc:
        print("CRC check failed in hello procedure")
        return False
    state.SConfirm = recvSConfirm
    handler.send(state.prepare_master_random_packet())
    state.inc_local_counter()
    srandom_pkt = handler.recv(2 + 255 + 3)
    state.inc_remote_counter()
    if srandom_pkt[0] & 0x3f != int(PktOpcode.S_RANDOM.value):
        return False
    recvSRandom = srandom_pkt[2: 18]
    crc = srandom_pkt[18: 18 + 3]
    should_crc = state.calc_crc(srandom_pkt[:18])
    if crc != should_crc:
        print("CRC check failed in hello procedure")
        return False
    if not state.check_slave_confirm(recvSRandom):
        return False
    state.setup_session()
    return True


def slave_hello_procedure(handler: connection_handle, state: connection_state):
    hello_pkt = handler.recv(2 + 255 + 3)
    state.inc_remote_counter()
    if hello_pkt[0] & 0x3f != int(PktOpcode.HELLO.value):
        return False
    encrypt_or_not = (hello_pkt[0] >> 7) & 0b1
    state.encrypt = encrypt_or_not
    hello_pkt_len = hello_pkt[1]
    hello_pkt_payload = hello_pkt[2: 2 + hello_pkt_len]
    state.initCRC = hello_pkt_payload
    handler.send(state.prepare_hello_packet())
    state.inc_local_counter()
    if not encrypt_or_not:
        return True
    else:
        sc_request_pkt = handler.recv(2 + 255 + 3)
        state.inc_remote_counter()
        if sc_request_pkt[0] & 0x3f != int(PktOpcode.SC_REQ.value):
            return False
        recvIVm = sc_request_pkt[2: 10]
        recvSecretm = sc_request_pkt[10: 18]
        crc = sc_request_pkt[18: 18 + 3]
        should_crc = state.calc_crc(sc_request_pkt[:18])
        if crc != should_crc:
            print("CRC check failed in hello procedure")
            return False
        state.IVm = recvIVm
        state.Secretm = recvSecretm
        handler.send(state.prepare_sc_respond_packet())
        state.inc_local_counter()
        numeric_key = int(input("Shared numeric key > "))
        numeric_key = numeric_key % 0x1000000
        state.numeric_key = numeric_key
        state.numeric_key_bytes = (numeric_key).to_bytes(16, "little")
        mconfirm_packet = handler.recv(2 + 255 + 3)
        state.inc_remote_counter()
        if mconfirm_packet[0] & 0x3f != int(PktOpcode.M_CONFIRM.value):
            return False
        recvMConfirm = mconfirm_packet[2: 18]
        crc = mconfirm_packet[18: 18 + 3]
        should_crc = state.calc_crc(mconfirm_packet[:18])
        if crc != should_crc:
            print("CRC check failed in hello procedure")
            return False
        state.MConfirm = recvMConfirm
        handler.send(state.prepare_slave_confirm_packet())
        state.inc_local_counter()
        mrandom_packet = handler.recv(2 + 255 + 3)
        state.inc_remote_counter()
        if mrandom_packet[0] & 0x3f != int(PktOpcode.M_RANDOM.value):
            return False
        recvMRandom = mrandom_packet[2: 18]
        crc = mrandom_packet[18: 18 + 3]
        should_crc = state.calc_crc(mrandom_packet[:18])
        if crc != should_crc:
            print("CRC check failed in hello procedure")
            return False
        if not state.check_master_confirm(recvMRandom):
            return False
        handler.send(state.prepare_slave_random_packet())
        state.inc_local_counter()
        state.setup_session()
        return True


def connection_engine(handler: connection_handle, role: str, encrypt: bool):
    state = connection_state(role, encrypt)
    if role == "master":
        if not master_hello_procedure(handler, state):
            print("hello fail")
            exit(1)
    if role == "slave":
        if not slave_hello_procedure(handler, state):
            print("hello fail")
            exit(1)

    while True:
        try:
            showChoice()
            choice = int(input("Your choice > "))
            if choice < 0 or choice > 2:
                raise ValueError
            if choice == 0:
                recieve_message(state, handler)
            elif choice == 1:
                send_message(state, handler)
            elif choice == 2:
                goodBye()
                return

        except ValueError as err:
            print("Bad Input T.T")
            continue
        except KeyboardInterrupt as err:
            goodBye()
            return
        # except Exception as err:
        #     print("Bad thing happens")
        #     print(err)
        #     return
> 01 03 6c 69 fa 95 c5 e6 
<    01 03 6c 69 fa 95 c5 e6 
>    08 30 53 47 56 73 62 47 38 67 64 47 68 6c 63 6d
    55 73 49 47 78 76 62 6d 63 67 64 47 6c 74 5a 53
    42 75 62 79 42 7a 5a 57 55 73 49 48 70 79 59 58
    68 34 9e ab 52 
<    08 44 
<    65 57 56 68 61 43 77 67 53 53 42 68 62 53 42 78
    64 57 6c 30 5a 53 42 69 64 58 4e 35 49 47 31 68
    61 32 6c 75 5a 79 42 42 51 31 52 47 49 47 4e 79
    65 58 42 30 62 79 42 6a 61 47 46 73 62 47 56 75
    5a 32 56 7a 
<    ab 08 96 
>    08 40 64 32 56 73 62 43 77 67 53 53 42 6a 59 57
    34 67 62 32 5a 6d 5a 58 49 67 65 57 39 31 49 47
    45 67 62 6d 39 30 49 47 4a 68 5a 43 42 7a 61 57
    64 75 61 57 34 67 59 32 68 68 62 47 78 6c 62 6d
    64 6c d1 e8 ac 
<    08 0c 
<    63 32 68 76 64 79 42 74 5a 51 3d 3d 
<    06 eb 3b 
>    08 34 62 47 56 30 4a 33 4d 67 5a 6d 6c 79 63 33
    51 67 5a 47 6c 32 5a 53 42 70 62 6e 52 76 49 48
    4e 6c 59 33 56 79 5a 53 42 6a 62 32 35 75 5a 57
    4e 30 61 57 39 75 2a 85 95 
>    81 03 d9 b2 df e9 3b f9 
<    81 03 d9 b2 df e9 3b f9 
>    82 10 ec 36 e5 b0 69 55 d9 95 56 7e e5 de 45 07
    37 f8 7d d5 57 
<    83 10 68 b3 de d5 b8 40 14 dc f3 fb 75 02 d9 39
    0e 34 a6 bf 63 
>    84 10 9f 51 36 ca cd 9f 2a 53 87 39 4b 7d 0c 1c
    XX XX 58 46 05 
<    85 10 XX d6 e4 XX XX 5c XX b7 ba 90 6e 57 05 5a
    8e c8 2d db b8 
>    86 10 4b d2 09 24 f0 c3 cd 30 ba 64 a0 f1 d9 64
    69 1e fa a2 d5 
<    87 10 dd 76 51 4f 57 36 81 3a a8 c2 17 8e XX f8
    2d 5b 6f 68 ec 
>    88 44 ee 49 1a 84 62 41 16 fb 68 5e 5d 47 14 94
    aa 6d 3e ac 7c 53 70 7c 46 50 50 90 7e a2 01 12
    04 06 90 02 5e 92 a6 1d d8 29 1b 50 d0 c1 69 13
    b9 cd 0f f5 29 0e da d9 c2 3d 69 38 46 49 76 5b
    84 7f 15 f2 21 ce 3e 4f b4 
<    c8 ff 
<    ea 4d 61 86 4a 51 5f e4 78 41 3b 4c 12 94 b5 7a
    38 82 07 14 5b 56 22 4a 50 91 6a be 01 12 1f 12
    80 10 6f c5 a5 77 a8 3a 1d 40 af 89 7a 07 a1 8d
    0c df 13 18 f2 d2 d2 7e 42 4c 55 57 5c 20 90 7d
    2d f2 47 8a 05 19 c8 17 06 33 f1 a9 4d b6 15 ac
    37 bb a6 48 c1 33 df f4 26 c2 0a 28 f9 12 5f e1
    fd 35 d0 af 55 07 01 85 16 92 62 6b 6f fa c7 43
    4f 92 b5 68 c2 66 53 36 52 de 21 86 43 23 03 38
    98 f5 14 fd 5c b0 ef 20 59 fe 9a b6 8e 29 17 d7
    5d 5c cf c6 a8 c2 1d ba 69 d7 3b b7 99 44 c3 8b
    b5 20 8f fe 67 e0 28 64 9a 40 6a 2b d7 1d 86 70
    f1 9f ef a7 19 cf db e6 72 f4 c5 8a 1e 2d 1c 09
    2c 3f 21 db 23 bf 63 f7 da 5d 78 90 56 02 f2 22
    e4 58 a5 ca 7a 04 83 5d 4c d9 0a 1a 5d 90 0a 78
    f6 75 16 ea 44 32 89 97 1a 7f e2 da 15 7d 60 ce
    1b 63 31 ac c8 7e f6 9c e9 58 9e fa 9c 54 69 
<    10 b5 31 
<    c8 ff 
<    84 11 de 79 f3 a0 cf b3 04 f6 df ec 30 5c 00 ca
    30 d7 69 82 9e 55 9b 42 8d c6 f0 ae 6d 8b 73 d9
    af bb bf a8 b4 f4 e5 ad 6b be 55 3b eb 34 97 88
    2b 8a 41 3f ee e3 20 f6 38 69 b7 9b 98 ac 6a 67
    83 e0 e5 de e5 e1 8e 80 43 13 e2 2e 56 38 3a fd
    b4 ea a5 44 87 ad 8a ec 5a 5e 01 6e 5d db 39 44
    81 39 57 e7 05 24 e0 58 e8 56 41 fa 4d cd b2 71
    4d 6a a4 79 16 0b 43 68 c8 db ad d6 6d 8d 8a 9e
    4c 8a 7f 58 45 54 f3 15 22 82 35 59 38 1e 75 4e
    8c c8 c6 a0 0b e2 6d 75 0d 78 49 36 6e cc b2 24
    90 9d c9 8b da 4e 51 81 15 3c 67 07 c0 f6 5c 9c
    6d a1 14 8c fe fd c7 7a 65 63 69 17 f9 3c 8c 0d
    44 7e bd 7e 49 89 4f b4 61 7a b6 b3 70 9e 2a b3
    b9 c9 fe 18 94 7e b4 50 85 e7 b9 e7 2c db c0 10
    92 ac 60 3c c2 f7 cb fb fb b6 9f f9 af fa ba 60
    9b 99 cf 35 69 4b 9b 9e f4 ca b3 df bc 1d 7b 
<    30 a6 be 
<    c8 ff 
<    4a 21 06 5d 5a b2 a0 e2 cb 4f 31 e2 2b dd d9 57
    6e 81 cd 31 05 dc 91 a9 fb 9d b0 dc ec 19 7b e8
    4e 44 1a 79 ec b4 15 53 85 2f 15 58 78 5d c3 1f
    03 62 08 a4 52 c3 57 b1 52 4c f5 6d bc df 98 5e
    64 35 b8 f6 17 4c fd 28 d9 2e 3d 30 ab e9 82 ee
    10 d8 0a 75 31 55 be d8 9c 85 ba d3 64 9b ed 2f
    2e 41 a5 3c 1a 1e dd 65 47 22 70 14 86 82 35 ac
    5e bb e6 e8 c7 cb 92 64 0d 0c dd 81 a6 91 35 ad
    3b 36 39 be e2 46 28 5c c5 13 cb 6d 21 64 47 34
    2c 59 6d 77 df e6 4a 06 66 7b 64 f4 b7 5a c7 c6
    03 cb 5c 02 ac ea f4 f7 80 ec 1c c4 3f ed 5f b8
    cf 19 4b 02 9d 8e 48 5f ff 93 69 5f 37 86 21 02
    b7 60 60 54 9e a9 d0 c5 f8 52 be 7c ed 74 e3 0d
    cd a4 bb 95 13 a9 57 fa e0 8e 41 aa 09 74 b5 b0
    45 67 f8 a4 9d a9 4c 0f c8 f2 82 0a 45 71 18 da
    ec e7 5a 4e d4 5d 0d b8 75 7c 47 a9 d1 85 e5 
<    64 27 6a 
<    88 5b 
<    a6 36 7b 6a a5 55 af 69 a9 a9 7d 0e 09 aa 48 86
    d5 27 20 c7 74 65 e3 37 18 76 8d 14 89 d9 d1 cc
    84 d0 ed 7b d6 04 55 00 2e 04 ee 7f ae 36 8c 47
    83 82 a2 ef 26 4b dd 91 73 d2 8c 29 31 5b 8f 3e
    3c 19 24 89 50 be d6 5f e7 88 e4 ac 13 71 26 85
    1b c8 8d 47 94 e6 41 85 9e 6f b2 
<    0b 37 68 
>    c8 ff b7 29 d4 27 d4 a9 d5 95 2e c3 ce cc 1e 70
    15 9c 27 c6 63 8d 8a 03 ed 6c f1 e4 f5 b1 43 96
    1e d9 a7 9f ae e8 90 f5 ec ad 63 9e 4f 09 ce 13
    cf bc 33 d8 4f 27 c8 ea 3a ce 11 78 a8 b1 8e 9f
    6b 5f ac e2 e8 eb ed c4 8f ae 7a 36 d5 00 60 0a
    53 ea 89 e8 c6 1a 95 c5 fc d8 54 45 71 15 63 fe
    16 64 d1 21 42 ee 11 2a f2 6d cb 73 40 a3 45 d0
    99 6a 49 52 b1 3f 1f 70 3d 4c 99 b1 b3 e9 02 87
    8f f7 45 ac 61 21 6b 49 d8 38 05 8d 0a 68 37 00
    1b 11 bc c6 c4 82 31 eb 51 44 5f 74 48 35 58 dd
    bc 11 9f f7 b9 85 cb 1e 69 b0 0b 42 48 75 e2 d0
    4d 96 65 f2 01 85 e9 97 bc 48 72 47 42 54 e1 2d
    99 54 76 59 b7 58 52 ba 5e 99 41 64 b7 cf 45 90
    49 f2 80 ff ff 1d b3 70 bd 72 90 ed b3 c5 37 d6
    a7 35 fa 99 3e 09 e8 c5 de bc d5 85 8a 98 f8 f4
    aa 4d c9 ce ce 01 3d 6f 95 8f da d7 87 e0 99 36
    44 de 22 74 
>    88 1d 10 13 37 5c 5c a9 83 e3 90 5a 58 f7 05 de
    88 33 7f b3 fc 34 1c da ab 9e ae cf 90 ab 8b 8c
    60 1f 
<    88 28 
<    18 ae e9 5e ca c0 9e e6 3d d2 87 07 b8 94 2d 4f
    2a 70 52 d7 1b fd 27 d8 1b cc ef fd 20 8a 14 63
    f9 a1 35 24 8d ef 57 81 
<    ec a0 94 
>    88 60 a2 16 25 39 df 5b ac 45 95 86 53 58 12 db
    74 a6 cb 54 1d d7 1f 64 ec 4d 12 71 9f 32 a6 de
    f8 99 e3 d7 eb 62 c4 12 77 02 17 3e c2 42 bc 32
    aa 5e 82 fe e8 ea 33 5b c4 ad 7d c8 f2 2e 20 59
    a3 04 19 17 1a be 73 af e6 5b fa a6 ad a3 2a 15
    78 8d 0d b6 b3 59 b0 be 7f a6 af 68 cd e6 e2 4c
    a9 5d 0b 4a cb 
<    88 08 
<    fd 81 d2 b5 8c 5e 32 06 
<    e7 8a 0e 
  • なげー

  • bluetoothの通信を模してるらしい

  • 通信されている重要なデータは master_secret, master_random, master_confirm, slave_secret, slave_random, slave_confirm

    • いくつかは1〜4バイト程度潰されている

    • プロトコルでは3バイトのCRCがついていて、これに合うように全探索すれば良さそう

    • *_confirm は 正しいnumeric keyの元で *_random を適当に暗号化した値

  • 互いに共有していた24bit 程度の数値(numeric key)と master_random, slave_random, master_secret, slave_secret を用いてセッションキーを作成してる

  • numeric key 全探索できそう

全探索

import libscrc
from itertools import product

from Crypto.Cipher import AES
from tqdm import tqdm


def calc_crc(initCRC, pdu):
    initvalue = int.from_bytes(initCRC, "little")
    crc = libscrc.hacker24(data=pdu, poly=0x00065B, init=initvalue,
                           xorout=0x00000000, refin=True, refout=True)
    return crc.to_bytes(3, "little")


def bytes_xor_16(bytes1, bytes2):
    v1 = int.from_bytes(bytes1, 'big')
    v2 = int.from_bytes(bytes2, 'big')
    v3 = v1 ^ v2
    return (v3).to_bytes(16, 'big')


def secure_encrypt(key, plain):
    aes = AES.new(key=key, mode=AES.MODE_ECB)
    return aes.encrypt(plain)


def secure_encrypt_packet(key, plain, nonce):
    aes = AES.new(key=key, mode=AES.MODE_CCM, nonce=nonce)
    return aes.encrypt(plain)


def secure_decrypt_packet(key, plain, nonce):
    aes = AES.new(key=key, mode=AES.MODE_CCM, nonce=nonce)
    return aes.decrypt(plain)


def secure_confirm(key, r, p1, p2):
    return secure_encrypt(key, bytes_xor_16(secure_encrypt(key, bytes_xor_16(r, p1)), p2))


initCRC = bytes.fromhex("d9 b2 df".replace(" ", ""))

# MASTER_CONFIRM: 9f 51 36 ca cd 9f 2a 53 87 39 4b 7d 0c 1c XX XX
# CRC           : 58 46 05
table = list(range(256))

master_prefix = bytes.fromhex("84109f5136cacd9f2a5387394b7d0c1c")
master_crc = bytes.fromhex("584605")
for pattern in product(table, repeat=2):
    data = master_prefix + bytes(pattern)
    if calc_crc(initCRC, data) == master_crc:
        print("master_confirm:", data[2:])
        master_confirm = data[2:]
        break
else:
    raise ValueError("X")


# S_CONFIRM
#  SLAVE_CONFIRM: XX d6 e4 XX XX 5c XX b7 ba 90 6e 57 05 5a 8e c8
# CRC           : 2d db b8

slave_postfix = bytes.fromhex("b7ba906e57055a8ec8")
slave_crc = bytes.fromhex("2ddbb8")
slave_confirm = b'\x02\xd6\xe4\xcdS\\\x86\xb7\xba\x90nW\x05Z\x8e\xc8'
# for pattern in product(table, repeat=4):
#     data = bytes([0x85, 0x10, pattern[0], 0xd6, 0xe4, pattern[1],
#                  pattern[2], 0x5c, pattern[3]]) + slave_postfix
#     if calc_crc(initCRC, data) == slave_crc:
#         print("slave_confirm:", data[2:])
#         slave_confirm = data[2:]
#         break
# else:
#     raise ValueError("X")


# S_RANDOM
#  SLAVE_RANDOM: dd 76 51 4f 57 36 81 3a a8 c2 17 8e XX f8 2d 5b
# CRC:           6f 68 ec

for x in range(256):
    data = bytes.fromhex("8710dd76514f5736813aa8c2178e") + \
        bytes([x, 0xf8, 0x2d, 0x5b])
    if calc_crc(initCRC, data) == bytes.fromhex("6f68ec"):
        print("slave_random:", data)
        slave_random = data[2:]
        break
else:
    raise ValueError("X")


master_iv = bytes.fromhex("ec 36 e5 b0 69 55 d9 95".replace(" ", ""))
master_secret = bytes.fromhex("56 7e e5 de 45 07 37 f8".replace(" ", ""))
master_random = bytes.fromhex(
    "4b d2 09 24 f0 c3 cd 30 ba 64 a0 f1 d9 64 69 1e".replace(" ", ""))

slave_iv = bytes.fromhex("83 10 68 b3 de d5 b8 40".replace(" ", ""))
slave_secret = bytes.fromhex("14 dc f3 fb 75 02 d9 39".replace(" ", ""))

for k in tqdm(range(0, 0x1000000)):
    numeric_key_bytes = (k).to_bytes(16, "little")
    should_Sconfirm = secure_confirm(
        numeric_key_bytes, slave_random, b"\x00" * 16, b"\xff" * 16)
    if should_Sconfirm == slave_confirm:
        print("slave_numberic_key_bytes:", numeric_key_bytes)
        break
else:
    raise ValueError("X")

for k in tqdm(range(9189958, 0x1000000)):
    numeric_key_bytes = (k).to_bytes(16, "little")
    should_Mconfirm = secure_confirm(
        numeric_key_bytes, master_random, b"\x00" * 16, b"\xff" * 16)
    if should_Mconfirm == master_confirm:
        print("master_numberic_key_bytes:", numeric_key_bytes)
        break
else:
    raise ValueError("X")

復号

from Crypto.Cipher import AES
import libscrc
import re


def bytes_xor_16(bytes1, bytes2):
    v1 = int.from_bytes(bytes1, 'big')
    v2 = int.from_bytes(bytes2, 'big')
    v3 = v1 ^ v2
    return (v3).to_bytes(16, 'big')


def secure_encrypt(key, plain):
    aes = AES.new(key=key, mode=AES.MODE_ECB)
    return aes.encrypt(plain)


def secure_encrypt_packet(key, plain, nonce):
    aes = AES.new(key=key, mode=AES.MODE_CCM, nonce=nonce)
    return aes.encrypt(plain)


def secure_decrypt_packet(key, plain, nonce):
    aes = AES.new(key=key, mode=AES.MODE_CCM, nonce=nonce)
    from base64 import b64decode
    p = aes.decrypt(plain)

    while True:
        try:
            return b64decode(p)
        except:
            p += b"="



def secure_confirm(key, r, p1, p2):
    return secure_encrypt(key, bytes_xor_16(secure_encrypt(key, bytes_xor_16(r, p1)), p2))


master_iv = bytes.fromhex("ec 36 e5 b0 69 55 d9 95".replace(" ", ""))
master_secret = bytes.fromhex("56 7e e5 de 45 07 37 f8".replace(" ", ""))
master_random = bytes.fromhex(
    "4b d2 09 24 f0 c3 cd 30 ba 64 a0 f1 d9 64 69 1e".replace(" ", ""))
master_confirm = b'\x9fQ6\xca\xcd\x9f*S\x879K}\x0c\x1c\x16\xfa'

slave_iv = "68 b3 de d5 b8 40 14 dc"
slave_secret = bytes.fromhex("f3 fb 75 02 d9 39 0e 34".replace(" ", ""))
slave_random = b'\xddvQOW6\x81:\xa8\xc2\x17\x8e|\xf8-['
slave_confirm = bytes.fromhex("02d6e4cd535c86b7ba906e57055a8ec8")

numeric_key_bytes = b'%=\x8c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
storekey = secure_encrypt(
    numeric_key_bytes, master_random[:8] + slave_random[8:])
sessionkey = secure_encrypt(
    storekey, master_secret + slave_secret)


should_Mconfirm = secure_confirm(
    numeric_key_bytes, master_random, b"\x00" * 16, b"\xff" * 16)
assert should_Mconfirm == master_confirm
should_Sconfirm = secure_confirm(
    numeric_key_bytes, slave_random, b"\x00" * 16, b"\xff" * 16)
assert should_Sconfirm == slave_confirm


master_counter = 4
slave_counter = 4

xs = [
    "8844ee491a84624116fb685e5d471494aa6d3eac7c53707c465050907ea20112040690025e92a61dd8291b50d0c16913b9cd0ff5290edad9c23d69384649765b847f15f221ce3e4fb4",
    "c8ffea4d61864a515fe478413b4c1294b57a388207145b56224a50916abe01121f1280106fc5a577a83a1d40af897a07a18d0cdf1318f2d2d27e424c55575c20907d2df2478a0519c8170633f1a94db615ac37bba648c133dff426c20a28f9125fe1fd35d0af550701851692626b6ffac7434f92b568c266533652de21864323033898f514fd5cb0ef2059fe9ab68e2917d75d5ccfc6a8c21dba69d73bb79944c38bb5208ffe67e028649a406a2bd71d8670f19fefa719cfdbe672f4c58a1e2d1c092c3f21db23bf63f7da5d78905602f222e458a5ca7a04835d4cd90a1a5d900a78f67516ea443289971a7fe2da157d60ce1b6331acc87ef69ce9589efa9c546910b531",
    "c8ff8411de79f3a0cfb304f6dfec305c00ca30d769829e559b428dc6f0ae6d8b73d9afbbbfa8b4f4e5ad6bbe553beb3497882b8a413feee320f63869b79b98ac6a6783e0e5dee5e18e804313e22e56383afdb4eaa54487ad8aec5a5e016e5ddb3944813957e70524e058e85641fa4dcdb2714d6aa479160b4368c8dbadd66d8d8a9e4c8a7f584554f31522823559381e754e8cc8c6a00be26d750d7849366eccb224909dc98bda4e5181153c6707c0f65c9c6da1148cfefdc77a65636917f93c8c0d447ebd7e49894fb4617ab6b3709e2ab3b9c9fe18947eb45085e7b9e72cdbc01092ac603cc2f7cbfbfbb69ff9affaba609b99cf35694b9b9ef4cab3dfbc1d7b30a6be",
    "c8ff4a21065d5ab2a0e2cb4f31e22bddd9576e81cd3105dc91a9fb9db0dcec197be84e441a79ecb41553852f1558785dc31f036208a452c357b1524cf56dbcdf985e6435b8f6174cfd28d92e3d30abe982ee10d80a753155bed89c85bad3649bed2f2e41a53c1a1edd6547227014868235ac5ebbe6e8c7cb92640d0cdd81a69135ad3b3639bee246285cc513cb6d216447342c596d77dfe64a06667b64f4b75ac7c603cb5c02aceaf4f780ec1cc43fed5fb8cf194b029d8e485fff93695f37862102b76060549ea9d0c5f852be7ced74e30dcda4bb9513a957fae08e41aa0974b5b04567f8a49da94c0fc8f2820a457118daece75a4ed45d0db8757c47a9d185e564276a",
    "885ba6367b6aa555af69a9a97d0e09aa4886d52720c77465e33718768d1489d9d1cc84d0ed7bd60455002e04ee7fae368c478382a2ef264bdd9173d28c29315b8f3e3c19248950bed65fe788e4ac137126851bc88d4794e641859e6fb20b3768",
    "c8ffb729d427d4a9d5952ec3cecc1e70159c27c6638d8a03ed6cf1e4f5b143961ed9a79faee890f5ecad639e4f09ce13cfbc33d84f27c8ea3ace1178a8b18e9f6b5face2e8ebedc48fae7a36d500600a53ea89e8c61a95c5fcd85445711563fe1664d12142ee112af26dcb7340a345d0996a4952b13f1f703d4c99b1b3e902878ff745ac61216b49d838058d0a6837001b11bcc6c48231eb51445f74483558ddbc119ff7b985cb1e69b00b424875e2d04d9665f20185e997bc4872474254e12d99547659b75852ba5e994164b7cf459049f280ffff1db370bd7290edb3c537d6a735fa993e09e8c5debcd5858a98f8f4aa4dc9cece013d6f958fdad787e0993644de2274",
    "881d1013375c5ca983e3905a58f705de88337fb3fc341cdaab9eaecf90ab8b8c601f",
    "882818aee95ecac09ee63dd28707b8942d4f2a7052d71bfd27d81bcceffd208a1463f9a135248def5781eca094",
    "8860a2162539df5bac459586535812db74a6cb541dd71f64ec4d12719f32a6def899e3d7eb62c4127702173ec242bc32aa5e82fee8ea335bc4ad7dc8f22e2059a30419171abe73afe65bfaa6ada32a15788d0db6b359b0be7fa6af68cde6e24ca95d0b4acb",
    "8808fd81d2b58c5e3206e78a0e",
]


def calc_crc(initCRC, pdu):
    initvalue = int.from_bytes(initCRC, "little")
    crc = libscrc.hacker24(data=pdu, poly=0x00065B, init=initvalue,
                           xorout=0x00000000, refin=True, refout=True)
    return crc.to_bytes(3, "little")


initCRC = bytes.fromhex("d9 b2 df".replace(" ", ""))


# for x in xs:
#     y = bytes.fromhex(x)
#     assert y[1] == len(y[2:-3])
#     assert calc_crc(initCRC, y[:-3]) == y[-3:]
#     for cnt in range(10):
#         print(secure_decrypt_packet(
#             sessionkey, y[2:-3], (cnt).to_bytes(13, "little")
#         ))


data = re.split(r"\s+", open("data.txt").read())

master_counter = 4
slave_counter = 4
p = 0
while p < len(data):
    if data[p] == '>' or data[p] == "<":
        master = True if data[p] == ">" else False

        cnt = int(data[p+2], 16)
        p += 3
        x = 0
        buf = ""
        while x < cnt:
            if data[p + x] == ">" or data[p + x] == "<":
                p += 1
                continue
            buf += data[p + x]
            x += 1
        p += x + 3  # crc
        if master:
            print(secure_decrypt_packet(
                sessionkey, bytes.fromhex(buf), (master_counter).to_bytes(
                    13, "little")
            ))
            master_counter += 1
        else:
            print(secure_decrypt_packet(
                sessionkey, bytes.fromhex(buf), (slave_counter).to_bytes(
                    13, "little")
            ))
            slave_counter += 1

    else:
        print(data[p-1:p+1])
        p += 1