Предисловие

Думаю, стоит сразу указать ссылку, т.к. дальше я буду ссылаться на видеозапись, которую, к сожалению, нельзя прикрепить на Хабр...
Ссылка на материалы: ССЫЛКА

Уровень сложности: средний

Тип: misc, network, coding

Условие задачи:

Хакеры взломали освещение жилого дома и пустили по нему бегущей строкой свои требования: миллион фруктов юдзу и вертолет. А что было в начале их требований?

Нам доступно: перехваченный трафик в формате pcap и видео запись, на которой видно как хакеры взламывают освещение жилого дома.

Решение

Для начала посмотрим видеозапись инцидента... Заметим, что мы попали в ситуацию, которая постоянно рассматривается в кинофильмах: хакеры взломали освещение дома и используют его как баннер :-)

Что ж... Откроем запись трафика в Wireshark. И увидим, что записаны пакеты ZigBee (это протокол обмена между энерго-эффективными IoT устройствами). Заметим, что у нас первые 65 пакетов — это команды Off. То есть происходит «очистка экрана». Для удобства отфильтруем пакеты:

zbee_nwk.dst != 0x0000 && zbee_nwk.dst != 0xffff && zbee_zcl

где получатель покета не 0x0000 (не злоумышленник) и не 0xffff (Broadcast),  а так же в пакете есть протокол ZigBee Cluster Library (в нём хранится команда On/Off).

Посмотрев по адресам destination в пакетах, мы можем понять, что матрица будет иметь размер 13х5. А "поковыряв" пакеты в сыром виде, заметим, что последний байт является командой Off(0x00) или On(0x01).

Пакет с командой Off
Пакет с командой Off
Пакет с командой On
Пакет с командой On

В целом, на этом решение и заканчивается... Дальше нужно просто распарсить все пакеты, которые удовлетворяют фильтру (1), а это всего лишь 5083 пакета :-)

Для парсинга будем использовать библиотеку Scapy. Подключаем следующие модули:

import scapy.all as scapy
from scapy.config           import conf
from scapy.layers.zigbee    import ZigbeeClusterLibrary
from scapy.layers.zigbee    import ZigbeeNWK
  • Conf нужен для уточнения, что мы работаем с пакетами ZigBee.

  • ZigbeeClusterLibrary нужен для проверки наличия данного протокола в пакете.

  • ZigbeeNWK нужен для проверки наличия данного протокола в пакете, а так же для получения источника и получателя пакета.

    Затем, собираем все наши пакеты с командами в один список.

 print("[*] Поиск пакетов с On/Off командами... ", end='')

    for i, pkt in enumerate(packets):
        if pkt.haslayer(ZigbeeNWK) and pkt.haslayer(ZigbeeClusterLibrary):
            znl = pkt[ZigbeeNWK]
            if znl.source != 0x0000 or znl.destination == 0xffff:
                continue
      
            raw = bytes(pkt)
            state = -1
            if raw[-1] == ON_CMD:
                state = ON_CMD
            if raw[-1] == OFF_CMD:
                state = OFF_CMD
                
            commands.append((pkt.time, znl.destination, state))

Группируем пакеты в кадры по временным задержкам. Кстати, найти эту задержку можно с помощью Wireshark: задержка между пакетами, которые отрисовывают один «кусок» символа имеют задержку 0.16 секунд.

def group_commands_by_time_window(commands:list, window_sec:float=0.8):
    if not commands:
        return []
    
    frames:         list  = []
    current_frame:  list  = []
    start_time:     float = commands[0][0]

    for t, addr, cmd in commands:
        if t - start_time <= window_sec:
            current_frame.append((addr, cmd)) 
        else:
            frames.append(current_frame)
            current_frame = [(addr, cmd)]
            start_time = t

    if current_frame:
        frames.append(current_frame)

    return frames

После группировки пакетов в кадры, преобразуем эти кадры в матрицы из 0 и 1.

def render_frame(frame:list, matr:list[list[int]], width:int, height:int) -> list[list[int]]:
    if len(matr) == 0:
        matrix: list[list[int]] = [
            [
                0 for i in range(width)
            ] for j in range(height)
        ]
    else:
        matrix = matr

    for addr, state in frame:
        cell_idx: int = addr % (width * height)
        row:      int = cell_idx // width
        col:      int = cell_idx % width

        if row < height and col < width:
            matrix[row][col] = state
    return matrix

Ну и выводим кадры с задержкой 0.16 секунд.

matrix: list[list[int]] = []
for i, frame in enumerate(frames):
    os.system('cls' if os.name == 'nt' else 'clear')
    print(f"=== Кадр #{i+1} ({len(frame)} команд) ===")

    matrix = render_frame(frame, matrix, height=HEIGHT, width=WIDTH)
    print_matrix(matrix)
    
    time.sleep(FRAME_INTERVAL)

Запустим наш скрипт и поучим графический вывод флага, как это было у хакеров, только вместо здания у нас терминал :-)

После запишем наш флаг: TCTF{WIR3L3SS_BUT_N0T_T00_S3CUR3}

Послесловие

Мы прошли решение задачи шаг за шагом. Жду каких-либо дополнений, замечаний или вопросов в комментариях :-)