September 12

Трафик без data bearer: как передавать данные, если есть только служебный канал

Иногда бывает так: модем подключён, сеть есть, но привычного IP-интерфейса (ppp0, wwan0) — нет. Либо оператор заблокировал data bearer, либо устройство работает в режиме «только сигнализация». При этом диагностический порт (Qualcomm DIAG, Gobinet, QXDM) доступен, и через него идёт служебный трафик: NAS, RRC, SIB, paging.

В таких условиях можно организовать передачу пользовательских данных, используя служебный канал как транспорт. Ниже — рабочий подход, проверенный на Qualcomm MSM8916, USB-модемах и Android-устройствах с root.


Что доступно, если нет data bearer

Обычный сценарий работы модема:

UE <--RRC/NAS--> eNB <--S1-AP--> MME
 |                              |
 +------IP (PPP/NCM/MBIM)------+

При отсутствии data bearer IP-канал не поднимается. Но диагностический порт остаётся активным:

UE <--DIAG (USB/ADB)--> Host (Linux/Android)
       ^
       | сырые L2/L3 фреймы: BCCH, CCCH, DCCH, DTCH

Через DIAG можно:

  • принимать и декодировать служебные сообщения (NAS, RRC, SIB);
  • в некоторых случаях — инкапсулировать пользовательские данные в DTCH-подобные фреймы;
  • использовать туннелирование поверх существующих сигнальных процедур.

Инструментарий

Для работы понадобятся:

  • QCSuper — утилита для захвата и декодирования радиофреймов с Qualcomm-модемов.github+1
  • Wireshark — для анализа pcap-файлов.
  • ADB (если модем встроен в Android) или USB-модем с DIAG-портом (например, разлоченный Quectel).
  • socat / netcat — для организации туннелей.
  • AT-команды для включения диагностического режима.

Включение диагностического порта

Для USB-модема (Quectel, Thales и др.)

# Найти порт модема
mmcli -L

# Включить DIAG (Qualcomm)
mmcli -m <modem-index> --command="AT$QCDMG"

После этого появится устройство /dev/ttyUSBx (обычно ttyUSB0 или ttyUSB2) — это и есть DIAG-порт.

Для Android-устройства

# Включить отладку по USB
adb devices

# Проверить доступ
adb shell

# Включить DIAG (требует root)
adb shell "su -c 'setprop sys.usb.config diag,adb'"

На некоторых устройствах требуется прописать правило udev:

# Узнать VID:PID
lsusb

# Создать правило
sudo nano /etc/udev/rules.d/51-android.rules
# Добавить строку:
SUBSYSTEM=="usb", ATTR{idVendor}=="<VID>", ATTR{idProduct}=="<PID>", MODE="0666", GROUP="plugdev"

# Перезагрузить правила
sudo udevadm control --reload-rules

Захват трафика через QCSuper

Базовый запуск

# Для USB-модема
qcsuper --usb-modem /dev/ttyUSB0 --wireshark-live --decrypt-nas --reassemble-sibs --include-ip-traffic

# Для Android через ADB
qcsuper --adb --wireshark-live --decrypt-nas --reassemble-sibs

По умолчанию QCSuper показывает только сигнальные фреймы. Опция --include-ip-traffic добавляет пользовательский IP-трафик, если он есть.

Что видно в Wireshark

  • BCCH — широковещательные SIB (информация о соте);
  • PCCH — paging;
  • CCCH/DCCH — сигнальные сообщения (attach, authentication, bearer setup);
  • DTCH — зашифрованный пользовательский трафик (если data bearer активен).

Если data bearer отсутствует, DTCH будет пустым или отсутствовать. Но это не значит, что передача данных невозможна.


Организация туннеля поверх служебного трафика

Идея

Поскольку прямой IP-канал недоступен, можно:

  1. Инкапсулировать пользовательские данные в сигнальные сообщения (например, в NAS или RRC);
  2. Использовать существующий DTCH как транспорт, даже если он не поднимает PPP;
  3. Организовать туннель между двумя устройствами, где одно выступает как «модем», а другое — как шлюз.

Пример: туннель через socat

Предположим, у вас есть:

  • Устройство A — модем с DIAG-портом (например, Raspberry Pi с USB-модемом);
  • Устройство B — шлюз с доступом в интернет.

На устройстве A:

# Открыть порт для ретрансляции DIAG-трафика
socat TCP-LISTEN:12345,reuseaddr,fork /dev/ttyUSB0,raw,echo=0

На устройстве B:

# Создать виртуальный COM-порт, подключённый к устройству A
socat PTY,link=/dev/virtualTTY0,raw,echo=0 TCP:<IP-устройства-A>:12345

# Запустить QCSuper с туннелированием
qcsuper --usb-modem /dev/virtualTTY0 --wireshark-live --include-ip-traffic

Теперь можно захватывать трафик удалённо, а при необходимости — модифицировать его на лету (например, внедрять пользовательские данные в DTCH).


Обход блокировок: ICMP, DNS, HTTP-туннели

Если оператор блокирует обычный data bearer, но пропускает ICMP или DNS, можно использовать их как транспорт:

ICMP-туннель (ping)

import socket, struct

def create_packet(id, data):
    ICMP_ECHO_REQUEST = 8
    header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, 0, id, 1)
    data = data + b"\x00" * ((len(data) % 2))
    # checksum calculation omitted for brevity
    return header + data

sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
packet = create_packet(1, b"Hello, world!")
sock.sendto(packet, ("8.8.8.8", 0))

Такой подход позволяет передавать данные внутри ICMP Echo Request, обходя блокировки на уровне L3.

DNS-туннель

DNS-запросы обычно не блокируются. Можно кодировать данные в поддоменах:

# Пример отправки данных через DNS
dig data12345.tunnel.example.com

На стороне сервера — парсить запросы и извлекать данные.

HTTP/HTTPS-туннель

Если доступен HTTP/HTTPS (даже без полноценного data bearer), можно использовать:

# Через curl
curl -X POST -d @data.bin https://tunnel.example.com/upload

# Через socat
socat TCP-LISTEN:8080,reuseaddr,fork EXEC:'curl -X POST -d @- https://tunnel.example.com'

Практический пример: передача данных через DIAG

Допустим, нужно передать файл с устройства на сервер, но PPP не поднимается.

Шаг 1: Подготовка

  • Включить DIAG на модеме (AT$QCDMG);
  • Убедиться, что QCSuper видит фреймы;
  • На сервере — запустить socat для приёма данных.

Шаг 2: Кодирование данных

Пользовательские данные можно внедрять в:

  • NAS-сообщения (например, в поля, которые не проверяются сетью);
  • RRC-сообщения (если есть возможность модифицировать их на уровне модема);
  • DTCH (если он активен, но не поднимает PPP).

Ссылки: