Совершая платеж в Интернет-магазине или ином финансовом сервисе, ты наверняка инициируешь SSL-соединение где-то на серверной стороне с участием какого-нибудь Java-приложения. А теперь представь: что, если тебе нужно исследовать это соединение? В силу бизнес-ценности его нельзя сделать открытым даже в тестовом окружении. Устроить MITM с помощью Fiddler’а не даст привязка к настоящим сертификатам, и даже если ты раздобудешь приватный ключ сервера, успех не гарантирован. Тупик? Оказывается, нет! Трафик такого приложения можно расшифровать, если у тебя есть его перехват Wireshark’ом и… логи JVM.

 

Теория

Чтобы ухватить суть этого концепта, удели пару минут постижению его основ. Здесь будет рассказано, откуда и зачем берутся отладочные записи JVM, что такое сессионные ключи SSL и как все это смешать в Wireshark’е так, чтобы вскрыть зашифрованный трафик. Если какие-то из этих пунктов тебе уже известны, смело забивай на них и переходи дальше. Единственное, о чем здесь не пойдет речь, это как пользоваться Wireshark’ом – наверняка ты и сам можешь научить этому кого угодно.

 

Отладочные записи JVM

Ни для кого не секрет, что настройка и отладка защищенных соединений – задача отнюдь не тривиальная. Об этом догадывались и разработчики расширения JSSE для Java (реализация SSL/TLS), и поэтому любезно предусмотрели в нем возможность писать в стандартный вывод (будь то консоль или файл) некоторую информацию, которая может помочь в решении возможных проблем с соединениями (по сути, это данные, на которых строятся защищенные соединения).

«Спровоцировать» вывод этой информации можно при помощи специального аргумента при запуске JVM: javax.net.debug. Он может иметь разные значения в зависимости от того, что нужно вывести в лог, а JVM может сама подсказать, какие значения поддерживаются. Для того чтобы получить подсказку, нужно придать аргументу значение help (то есть java -Djavax.net.debug=help MyApp) и запустить приложение, использующее SSL (при этом само приложение не заработает, так как JVM завершится сразу после вывода справки):

all            turn on all debugging
ssl            turn on ssl debugging

The following can be used with ssl:
  record       enable per-record tracing
  handshake    print each handshake message
  keygen       print key generation data
  session      print session activity
  defaultctx   print default SSL initialization
  sslctx       print SSLContext tracing
  sessioncache print session cache tracing
  keymanager   print key manager tracing
  trustmanager print trust manager tracing
  pluggability print pluggability tracing

  handshake debugging can be widened with:
  data         hex dump of each handshake message
  verbose      verbose handshake message printing

  record debugging can be widened with:
  plaintext    hex dump of record plaintext
  packet       print raw SSL/TLS packets

Что значит «can be used with ssl» и «can be widened»? Это значит, что значения могут быть составными, то есть включать в себя уточнения и/или перечисления, разделяемые знаками «:» или «,». Например, запись вида javax.net.debug=ssl:record:plaintext говорит JVM, что мы хотим видеть отладочные записи от SSL (включая TLS), причем с трассировкой по каждой записи (record) в виде шестнадцатеричного дампа (plaintext).

Где именно мы увидим запрошенную информацию, зависит от того, куда перенаправлен этот самый «стандартный вывод» у исследуемого приложения. Для консольной программки ответ очевиден (консоль), для веб-приложения под сервлет-контейнером Tomcat это файл %catalina_base%/logs/catalina.out – словом, в каждом случае ответ может быть разным. Также не забывай, что в стандартный вывод, скорее всего, попадут не только записи об SSL, но и записи прикладной логики приложения; нужно быть готовым отсеивать одни от других.

Но ближе к делу. Запустив какую-нибудь программу с аргументом javax.net.debug=ssl, ты увидишь в логах… много чего, но главное – это имена SSL-сообщений, которыми обмениваются клиент и сервер. Все они (правда, не только они) начинаются с трех звездочек и выглядят примерно так:

*** ClientHello, TLSv1.2
...
*** ServerHello, TLSv1.2
...
*** ECDH ServerKeyExchange
...
*** ServerHelloDone
...
*** ECDHClientKeyExchange
...
*** Finished
...
*** Finished

Кстати, чтобы не путаться, давай условимся далее называть логами все, что попадает в стандартный вывод приложения, а под протоколом SSL понимать и протокол TLS (если не оговорено иное).

Состав сообщений и их роль могут меняться от версии протокола к версии. Для нас же пока важно лишь просто уметь находить их в логах, а также знать, что вместе эти сообщения составляют суть первого этапа SSL – рукопожатия (handshake).

Другой, чуть менее важный элемент логов – это шестнадцатеричный дамп SSL-записей. Чтобы он появился, в аргументе javax.net.debug должно присутствовать значение data, например javax.net.debug=ssl:handshake:data. Найти его в логах можно по характерным (весьма объемным) фрагментам вида:

[read] MD5 and SHA1 hashes:  len = 333
0000: 0C 00 01 49 03 00 17 41   04 06 6B 77 1F BB F3 D3  ...I...A..kw....
0010: 8E DF F8 76 FF 9E 9F 9F   D8 E0 4A 5B CC 88 15 72  ...v......J[...r
0020: 01 6C 26 A5 2C EC 3C 5D   00 CF 64 8C 46 08 9D 18  .l&.,.<]..d.F...
0030: DF 44 7F DA AA 9E 0F BE   C4 9A 42 88 E5 EB F4 9C  .D........B.....
0040: 0C FB 60 0E 4C 9F B3 54   59 06 01 01 00 02 D8 96  ..`.L..TY.......
...

Этот элемент содержит «сырой» вывод предшествующего ему SSL-сообщения (или нескольких). Строго говоря, без него можно попытаться обойтись, но с ним решить нашу задачу будет несколько проще.

Вот пока и все, что тебе пригодится знать об отладочных логах JVM. Теперь давай посмотрим, что нам может дать сам протокол SSL.

 

Рукопожатие в SSL: по ту сторону фокуса

В терминах протокола SSL этап начальных переговоров между клиентом и сервером относится к т.н. рукопожатию (handshake), а его результатами являются:

  1. Факт аутентификации сервера клиентом (а если требуется, то и наоборот);
  2. Выбор параметров шифрования;
  3. Материалы для получения сессионного ключа.

В контексте нашей задачи первый пункт этого выхлопа не интересен, а вот два следующих давай рассмотрим чуть подробнее.

Выбор параметров шифрования. Сервер выбирает его из вариантов, предлагаемых клиентом. Причем клиент предоставляет их в порядке своего предпочтения – чем раньше стоит набор параметров в списке вариантов, тем более он предпочтителен клиентом. Наборы, кроме прочего, различаются числом известных уязвимостей и стойкостью используемого шифра.

При настройке SSL-контекста в Java-клиенте можно манипулировать списком поддерживаемых параметров и этим склонять сервер к менее стойкому или более уязвимому шифру. Однако грамотно настроенный сервер, на котором админ позаботился об отключении таких дыр, ответит клиенту сообщением «Handshake failure», что в переводе с SSL-ского значит «Да пошёл ты!». От того, что будет выбрано в этом пункте, существенно зависят…

Материалы для получения сессионного ключа. Прежде всего, давай уточним что это такое и зачем оно нужно. Асимметричное шифрование – безусловно, классное изобретение, ведь оно позволяет обмениваться шифрованными сообщениями, не обмениваясь секретными ключами для их дешифрации. Однако у него есть один слишком важный недостаток: его вычислительная трудоемкость настолько высока, что применение на больших объемах данных (особенно при жестких требованиях к скорости) становится неудобным или даже невозможным.

Другое дело — симметричное шифрование. Мало того, что сами по себе его алгоритмы значительно быстрее, так алгоритм AES еще и имеет аппаратную поддержку во многих современных процессорах. Но оно обладает как раз тем недостатком, которого лишен асимметричный вариант: перед шифрованием стороны должны каким-то образом обменяться одним-единственным секретным ключом шифрования. Именно поэтому решение, реализованное в том числе в SSL, лежит где-то посередине – для быстрого поточного шифрования данных используется симметричное шифрование, а для предшествующего ему получения секретного ключа – асимметричное шифрование.

Слово «получение» в предыдущем предложении требует отдельного объяснения. Дело в том, что появление ключа симметричного шифрования (в SSL его принято называть сессионным ключом) можно обеспечить разными способами:

Схема 1. Способы получения сессионного ключа для симетричного шифрования
Схема 1. Способы получения сессионного ключа для симетричного шифрования

Исторически первой свое применение нашла наиболее естественная идея: «Пусть какая-нибудь сторона переговоров — например, клиент — сгенерирует сессионный ключ, а потом передаст его серверу, зашифровав его публичным ключом сервера». Такой подход называют «методом обмена», а реализующий его алгоритм – RSA (по аналогии с алгоритмом ассиметричного шифрования).

Этот метод долго и широко использовался в SSL и позднее в TLS (применительно к Java это версии до 1.6 включительно), но постепенно стал вытесняться из-за одной важной особенности – если злоумышленнику удастся скомпрометировать секретный асимметричный ключ сервера, он сможет дешифровать любые SSL-соединения этого сервера, как прошлые, так и будущие. Для этого ему нужно лишь перехватить SSL-сообщение с сессионным ключом, зашифрованное публичным ключом сервера, и расшифровать его украденным секретным ключом этого же сервера.

Спустя время на сцену вышла отнюдь не новая, но чрезвычайно полезная идея распространения ключей, предложенная Уитфилдом Диффи и Мартином Хеллманом. Созданный ими алгоритм позволяет сгенерировать общий секретный ключ, обмениваясь при этом лишь данными, не подверженными компрометации, то есть бесполезными с точки зрения злоумышленника. Примечательно, что по сравнению с RSA в этом методе диалог сторон идет чуть дольше, т.к. в него вводится дополнительное SSL-сообщение ServerKeyExchange, в котором сервер передает клиенту свои публичные компоненты для генерации общего ключа.

Такой подход стали называть «методом генерации», а соответствующий алгоритм ожидаемо получил имя «алгоритм Diffie-Hellman’а» (DH). Поскольку в этом методе секретный асимметричный ключ сервера больше не используется — его стало бессмысленно красть (разве что для других целей). Именно этот метод на сегодня является умолчательным в большинстве современных систем с защитой по SSL.

 

Дешифрация трафика в Wireshark: цифровая алхимия

Вот теперь, когда ты вооружен изложенными выше фактами, можно подступиться к главному вопросу – как расшифровать перехваченный трафик?

Первый вывод, который напрашивается из описания рукопожатия: каким бы ни был протокол (SSL либо TLS, любая версия), внутри него всегда работает симметричное шифрование, а значит, заполучив его ключ (сессионный), можно вообще не греть голову ассиметричной обвязкой. Отсюда же следствие: коль скоро методов получения сессионного ключа два, то и подхода к его компрометации, скорее всего, будет два, причем наверняка независимых.

Все это хорошо, но кто нам даст сессионный ключ, если он мало того что всякий раз генерируется новым где-то в дебрях клиента и сервера, так еще и никогда не передается в явном виде по сети? К счастью, разработчики тоже люди и им свойственно не только усложнять мир вокруг себя, но иногда и упрощать его. Яркий пример – некто Adam Langley, сотрудник Mozilla Foundation.

Несколько лет назад Адам работал над библиотекой NSS (Network Socket Security), которая используется в браузерах Firefox и Chrome для работы с SSL. В своей работе для отладки SSL-соединений Адам активно использовал Wireshark, предоставляя ему приватный ключ сервера для дешифрации трафика. Однако с распространением TLS на основе алгоритмов DH такой подход перестал работать, и Adam разработал новый.

Адам снабдил библиотеку NSS возможностью логировать в специальный файл некие данные, необходимые для деривации (получения) сессионных ключей, а также инициировал доработку на стороне Wireshark, которая позволила использовать этот файл для дешифрации трафика. Нехитрый формат этого файла был опубликован на сайте Mozilla Foundation. Благодаря тому, что в нем были учтены особенности как метода обмена, так и метода генерации, этот подход стал применим как для старых систем на основе RSA, так и для более новых на основе DH.

К слову, благодаря этой доработке ты уже сейчас можешь расшифровать трафик своего браузера, не обладая никаким специальным софтом, кроме Wireshark.

Но вернемся к нашей задаче. Взглянув на описание формата, ты без труда заметишь, что он, по сути, состоит лишь из нескольких значений, участвующих в рукопожатии. Или, более общими словами, из данных, на которых строится защищенное соединение. Ничего не напоминает?.. Бинго! Это ведь часть тех самых данных, которые выводит JVM, если в ее параметрах есть javax.net.debug! А это значит, ты можешь сформировать файл в формате NSS самостоятельно, опираясь только лишь на отладочные записи приложения.

После этого останется лишь натравить Wireshark (с открытым в нем шифрованным трафиком) на созданный файл, и готово – никакой SSL тебе больше не помеха. Ну да хватит разглагольствовать, к делу!

 

Практика

Давай применим полученные знания на каком-нибудь безобидном, но реальном примере.

 

Замес

Прежде всего, нам понадобится «подопытный кролик» - какое-нибудь Java-приложение с защищаемой сетевой активностью. К сожалению, готового Интернет-банка под рукой нет, поэтому возьмем что-нибудь попроще — например, JOSM, свободно распространяемый редактор карт в формате OpenStreetMap (OSM) с открытым исходным кодом. Это чистейшей воды Java-приложение (причем версии 7+), позволяющее создавать и редактировать карты, которые затем становятся доступны на всех сайтах/приложениях/устройствах, вовлеченных в проект OSM.

JOSM, редактор карт в формате OpenStreetMap. Наше подопытное Java-приложение с защищаемой сетевой активностью
JOSM, редактор карт в формате OpenStreetMap. Наше подопытное Java-приложение с защищаемой сетевой активностью

Несмотря на то, что программу можно скачать и запустить как самостоятельное десктопное приложение (в виде JAR архива, ссылка «Download josm-tested.jar»), сетевой экран показывает, что она активно общается со своим back-end’ом на сервере josm.openstreetmap.de, причем начиная с первых секунд работы. Что именно отправляет она на сервер, какие данные получает от него – остается только догадываться, если не уметь вскрывать ее трафик. Это мы и сделаем.

Скачай себе JAR-архив программы, а также убедись, что на компьютере установлена Java версии 7 или выше (JDK или JRE). Запускать программу не торопись, вместо этого…

Вспомним, что источниками данных при дешифрации для нас будут являться логи приложения и перехваченный Wireshark’ом трафик. Логи мы можем себе обеспечить, оснастив вызов JAR-файла программы, во-первых, опцией включения SSL-отладки, во-вторых, перенаправлением стандартного вывода в файл josm-out.log (пока не запускай):

java -Djavax.net.debug=ssl:handshake:data -jar josm-tested.jar > josm-out.log

Теперь к трафику. Запусти Wireshark (нужна версия не ниже 1.6) и открой окно опций захвата (Capture Options). Коль скоро мы заранее знаем, трафик к какому серверу мы хотим перехватить, давай укажем этот сервер в фильтре:

Говорим Wireshark отображать трафик только для нашего сервера
Говорим Wireshark отображать трафик только для нашего сервера

Это позволит нам заблаговременно исключить множество ненужных пакетов, пересылаемых другими процессами на твоей машине.

Итак, теперь все готово. Можно начинать!

 

Эксперимент

Чтобы успеть перехватить первые же пакеты трафика от программы, сниффер, очевидно, должен быть запущен заранее, поэтому стартуй его первым (кнопка Start Capture). Благодаря включенному фильтру, журнал перехваченных пакетов должен пока оставаться пустым.

Следующим шагом можно запускать исследуемую программу – стартуй и ее, причем именно тем (длинным) вызовом, что был составлен чуть выше.

JOSM потребуется несколько секунд на инициализацию, после которой экран должен озариться основным окном программы:

Основное окно JOSM
Основное окно JOSM

Казалось бы, ты еще ничего не делал с программой, а в сниффере уже числятся пакеты обмена данными с сервером:

Пакеты обмена данными с сервером сразу же начинают отображаться в Wireshark
Пакеты обмена данными с сервером сразу же начинают отображаться в Wireshark
...однако данные зашифрованы TLS 1.2
...однако данные зашифрованы TLS 1.2

Все, что пока видно – это то, что обмен зашифрован, но он действительно ведется с сервером josm.openstreetmap.de по протоколу TLSv1.2, а также видны те самые SSL-сообщения, которые мы видели в логах Java-приложения, когда разбирали назначение параметра javax.net.debug. Кстати, о логах. Не пора ли заглянуть в них?

К этому моменту указанный нами при запуске файл josm-out.log должен быть уже не пустым. В этом легко убедиться, открыв его:

...
keyStore type is : jks
keyStore provider is :
init keystore
init keymanager of type SunX509
trustStore is: C:\Program Files\Java\jre1.8.0_45\lib\security\cacerts
trustStore type is : jks
trustStore provider is :
init truststore
adding as trusted cert:
...
%% No cached client session
*** ClientHello, TLSv1.2
RandomCookie:  GMT: 1419259351 bytes = { 169, 124, ..., 176 }
Session ID:  {}
...
*** ServerHello, TLSv1.2
RandomCookie:  GMT: 1847027039 bytes = { 71, 90, ..., 205 }
Session ID:  {128, 18, ..., 184}
...
Image Fetcher 0, WRITE: TLSv1.2 Application Data, length = 264
Image Fetcher 0, READ: TLSv1.2 Application Data, length = 594
Image Fetcher 0, READ: TLSv1.2 Application Data, length = 1113
Image Fetcher 0, READ: TLSv1.2 Application Data, length = 26
Image Fetcher 0, READ: TLSv1.2 Application Data, length = 29

Как видишь, даже за эти считанные секунды наши «сита» забились массой всякого «песка», как непонятного и бесполезного, так и ценного, но засекреченного. Теперь давай аккуратно просеем его и извлечем золотые песчинки.

 

Разбор

Заметка для ленивых

То, чем мы займемся на этом шаге, призвано, в первую очередь, помочь тебе понять суть подхода к дешифрации с помощью логов. Это будет, по сути, ручной труд, который едва ли годится для «промышленной эксплуатации». Если же тебе не терпится просто получить результат, то воспользуйся утилиткой NSS Java Maker, доступной на GitHub. Для простых (не загроможденных) логов она автоматически выудит необходимые данные и скомпонует из них NSS-файл, готовый для передачи в Wireshark. Синтаксис ее вызова довольно прост:

java -jar nssjavamaker.jar путь/к/логу/java-ssl-debug.log

Такой вызов создаст в текущей директории выходной файл session-keys.nss. Если хочется на это повлиять или поиграться с другими опциями, загляни в Readme.md.

Схожую задачу, но без помощи логов (и, следовательно, с другими ограничениями) также решает утилитка jSSLKeyLog.

Раз уж мы намереваемся самостоятельно создать NSS-файл, нужно понять, какие данные в нем ожидает увидеть Wireshark. В описании формата сказано, что файл является последовательностью строк, которые начинаются либо с символа «#» (комментарий), либо с одного из двух идентификаторов:

  • RSA – в случае применения в рукопожатии метода обмена;
  • CLIENT_RANDOM – в случае метода генерации.

Наборы данных в строках в обоих случаях тоже разные. Значит, нам нужно понять, какой метод применялся в нашем случае. Решений здесь аж несколько.

По названию согласованного сторонами шифра. Его видно и в сниффере:

Название согласованного сторонами шифра в сниффере
Название согласованного сторонами шифра в сниффере

и в логах приложения:

*** ServerHello, TLSv1.2
RandomCookie:  GMT: -1877660808 bytes = { 156, 237, ... 194, 255 }
Session ID:  {54, 28, ... 168}
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
Compression Method: 0
Extension renegotiation_info, renegotiated_connection: <empty>

Как видишь, в первой части этого наименования (до слова WITH) упоминается аббревиатура DH, что указывает на использование алгоритма Diffie-Hellman’а в процессе получения сессионного ключа сторонами.

По наличию SSL-сообщения ServerKeyExchange. Если помнишь теорию, алгоритм DH отличается от RSA, кроме прочего, наличием сообщения ServerKeyExchange. В этом также можно убедиться по снифферу:

Сообщение ServerKeyExchange в сниффере
Сообщение ServerKeyExchange в сниффере

и по логам приложения:

*** ECDH ServerKeyExchange
Signature Algorithm SHA512withRSA
Server key: Sun EC public key, 256 bits
  public x coord: 69600755423936...3823300815
  public y coord: 4879693016...41769238244142
  parameters: secp256r1 [NIST P-256, X9.62 prime256v1] ...

Итак, теперь мы точно знаем, что в нашем соединении использовался метод генерации. Значит, формируемая нами строка будет начинаться с CLIENT_RANDOM.

Что дальше? Согласно все тому же описанию, дальше следует 64 символа шестнадцатеричного представления клиентского случайного значения. Это число (по спецификации SSL) генерируется клиентом в самом начале рукопожатия и передается серверу в первом же сообщении (ClientHello). Значит, его должно быть видно в сниффере. Можешь проверить, так и есть:

Шестнадцатиричное представление клиентской рандомной строки видно в снифере
Шестнадцатиричное представление клиентской рандомной строки видно в снифере

Обрати внимание, что случайное значение включает в себя не только то, что названо Random Bytes, но и предшествующее ему текущее время GMT Unix Time.

Поскольку для нас главным источником данных является лог приложения, надо найти аналогичное значение и в нем. Ориентируясь на сообщение ClientHello, сделать это несложно:

*** ClientHello, TLSv1.2
RandomCookie:  GMT: 1419238947 bytes = { 49, 160, 56, 142, 249, 126, 83, 58, 209, 191, 100, 144, 211, 134, 231, 115, 6, 38, 53, 254, 190, 242, 131, 125, 95, 123, 67, 215 }
Session ID:  {}

Однако здесь это значение приведено отдельно от времени, да еще и десятеричными числами, а нам нужны шестнадцатеричные. Можно, конечно, затеяться и перевести. А можно положиться на подзначение :data параметра javax.net.debug. Благодаря ему чуть ниже в лог выводится шестнадцатеричный дамп всего сообщения, откуда искомое значение и можно выудить:

***
[write] MD5 and SHA1 hashes:  len = 249
0000: 01 00 00 F5 03 03 55 98   DE 23 31 A0 38 8E F9 7E  ......U..#1.8...
0010: 53 3A D1 BF 64 90 D3 86   E7 73 06 26 35 FE BE F2  S:..d....s.&5...
0020: 83 7D 5F 7B 43 D7 00 00   70 C0 24 C0 28 00 3D C0  .._.C...p.$.(.=.
0030: 26 C0 2A 00 6B 00 6A C0   0A C0 14 00 35 C0 05 C0  &.*.k.j.....5...
0040: 0F 00 39 00 38 C0 23 C0   27 00 3C C0 25 C0 29 00  ..9.8.#.'.<.%.).

Заметь, что случайное значение начинается не с начала дампа, а с 7-го байта. Предыдущие 6 заняты параметрами сообщения ClientHello.

После аккуратного сбора в одну строчку, удаления пробелов и приведения к нижнему регистру у тебя должна получиться вот такая последовательность:

5598de2331a0388ef97e533ad1bf6490d386e773062635febef2837d5f7b43d7

Итого, у нас в распоряжении 32 байта клиентского случайного значения, которые за счет представления в шестнадцатеричной системе счисления дают требуемые 64 символа для формируемого NSS-файла.

Примечание для буквоедов

Возможно ты спросишь, чего ради в якобы секретный NSS-файл помещается никем не скрываемое значение CLIENT_RANDOM, видимое даже в нерасшифрованном перехваченном трафике? Это делается для того, чтобы Wireshark мог правильно выбрать последующий за ним ключ MasterSecret в тех случаях, когда и в трафике, и в NSS-файле содержится более одного соединения. Другими словами, это значение служит для Wireshark’а уникальным индексом в списке секретных ключей NSS-файла.

Идем дальше. Формат NSS предписывает нам поставить через пробел от случайного значения самое важное – главный секретный ключ MasterSecret (точнее, 96 символов его шестнадцатеричного представления). Это еще не сессионный ключ, но получить его, зная MasterSecret, для Wireshark’а уже не составляет труда.

И вот здесь своего зенита достигает идея использования отладочных записей JVM. Только в них мы можем откопать это значение, ибо по сети оно, по понятным причинам, никогда и ни в каком виде не передается, а значит, и в перехваченном трафике его искать бесполезно. А вот в логах найти его нетрудно, вскоре за сообщением ClientKeyExchange:

*** ECDHClientKeyExchange
ECDH Public value:  { 4, 167, ..., 244, 190 }
[write] MD5 and SHA1 hashes:  len = 70
...
Master Secret:
0000: AD 3D 48 A3 64 45 FB 55   21 92 44 5C CA CE 75 95  .=H.dE.U!.D\..u.
0010: 84 E6 95 79 E1 38 99 A1   39 92 C7 7D BE DE 62 CE  ...y.8..9.....b.
0020: 36 3A 18 36 4E 35 F9 A1   79 2A C7 0A 4D 0A 58 55  6:.6N5..y*..M.XU
Client write key:
...

Собрав эти значения в строку по аналогии с предыдущей, ты получишь вот такую последовательность:

ad3d48a36445fb552192445ccace759584e69579e13899a13992c77dbede62ce363a18364e35f9a1792ac70a4d0a5855

Отлично! Теперь у нас есть все три компонента строки NSS-файла, а значит, можно собрать и его первый вариант. Вспомнив, что комментарии, предваренные символом «#», вполне допустимы, ты можешь свести все накопленные данные воедино согласно формату. Должно получиться примерно вот так:

# SSL/TLS secrets log file, created by me
CLIENT_RANDOM 5598de2331a0388ef97e533ad1bf6490d386e773062635febef2837d5f7b43d7 ad3d48a36445fb552192445ccace759584e69579e13899a13992c77dbede62ce363a18364e35f9a1792ac70a4d0a5855

Прелесть, не правда ли? Согласен, не правда. Ну да ладно, лишь бы Wireshark’у понравилось.

Заметка для ретроградов

С методом генерации мы разобрались. А как быть, если в соединении применялся метод обмена (RSA)? Подход аналогичен – берем соответствующий формат строки NSS-файла и заполняем его. Отличия будет три.

Во-первых, строка начнется с идентификатора RSA.

Во-вторых, после нее последует не клиентское случайное значение, а первые 8 байт (16 символов) зашифрованного ключа PreMasterSecret. Его так же можно извлечь либо из трафика в Wireshark’е, либо из шестнадцатеричного дампа в логе (после SSL-сообщения ClientKeyExchange, см. ниже).

В-третьих, вместо MasterSecret в строку нужно прописать уже нешифрованное значение PreMasterSecret (точнее, 96 символов его шестнадцатеричного представления). Как и в случае с MasterSecret, откопать его можно только в логе, так как по сети он в явном виде, очевидно, не передается:

*** ClientKeyExchange, RSA PreMasterSecret, TLSv1
[write] MD5 and SHA1 hashes:  len = 262
0000: 10 00 01 02 01 00 75 FF   86 6E 23 BE CA 1C DB EA  ......u..n#.....
0010: E9 80 0A 1C C9 CF 76 25   6F FE 75 82 4C 97 3E D6  ......v%o.u.L.>.
...
0100: 2B 32 C2 99 B6 C1                                  +2....
main, WRITE: TLSv1 Handshake, length = 262
SESSION KEYGEN:
PreMaster Secret:
0000: 03 01 2A ED E7 4B EF A8   82 33 25 3E 32 07 BB 13  ..*..K...3%>2...
0010: 20 93 5A B2 06 69 65 12   67 4D F5 C6 DE E7 DF AA   .Z..ie.gM......
0020: 21 56 93 2B C5 59 63 1C   8F 3B B4 6A E3 8A 71 FF  !V.+.Yc..;.j..q.
CONNECTION KEYGEN:

После того, как все эти отличия собраны воедино, строки NSS-файла будут выглядеть примерно так:

# SSL/TLS secrets log file, generated by me
RSA 75ff866e23beca1c 03012aede74befa88233253e3207bb1320935ab206696512674df5c6dee7dfaa2156932bc559631c8f3bb46ae38a71ff

Все это, кстати, умеет делать и утилита NSS Java Maker. Все дальнейшие шаги для обоих методов аналогичны.

 

Вскрытие

Итак, у нас на операционном столе перехваченный в Wireshark трафик от JOSM, а в руках – мегаинъекция в виде NSS-файла. Для ее применения кликни правой кнопкой на любом SSL/TLS пакете трафика и выбери Protocol Preferences → Secure Socket Layer Preferences... В открывшемся окне в поле *(Pre)-Master-Secret log filename укажи путь к сформированному NSS-файлу:

Указываем путь к нашему NSS-файлу
Указываем путь к нашему NSS-файлу

Теперь жми ОК и дивись произошедшему. Если на предыдущих шагах не было ошибок, то вид трафика должен стать иным (сравни с первым снимком трафика в Wireshark):

Пакеты расшифрован! Осталось собрать их воедино
Пакеты расшифрован! Осталось собрать их воедино

Что изменилось? Во-первых, свою темную суть внезапно обличили пакеты 11 и 22. Во-вторых, и это десерт сегодняшнего мероприятия, изменились пакеты, которые раньше имели безликое название Application Data (14-18). Теперь они стали вполне осязаемыми сегментами некого целостного потока данных. Чтобы обозреть его целиком, вызови контекстное меню на любом из этих пакетов и выбери Follow SSL Stream:

Трафик расшифрован, перед тобой открытый GET-запрос в читабельном виде. Бинго!
Трафик расшифрован, перед тобой открытый GET-запрос в читабельном виде. Бинго!

Теперь можешь почувствовать себя сотрудником АНБ – пред тобой ничем более не прикрытый диалог клиента и сервера, изначально зашифрованный одним из самых совершенных на сегодняшний день средств – протоколом TLSv1.2.

 

Troubleshooting

В этом нетривиальном процессе есть 1000 и 1 момент, в который что-то может пойти не так.

Основной источник проблем (по опыту автора) – это сам Wireshark. Он весьма чувствителен к содержимому NSS-файлов, поэтому первое, с чего стоит начать разбор проблем, это проверить, нет ли отклонений от формата NSS (в том числе лишних или недостающих пробелов). Правда, при этом Wireshark же является и бесценным источником данных для выяснения причин ошибок – он может вести лог обработки NSS-файла и в нем буквально говорить, что идет не так.

Чтобы включить этот лог, укажи все в том же окне настроек SSL путь к файлу, в который он должен выводиться:

Указываем путь в логу ошибок парсинга NSS
Указываем путь в логу ошибок парсинга NSS

В этот лог могут попасть записи не только о работе с SSL, и Wireshark весьма подробен в логировании, поэтому указывать этот файл лучше непосредственно перед попыткой дешифрации трафика. Другие особенности работы с логами Wireshark ты можешь почерпнуть из работы Sally Vandeven’а (на английском, приложение B).

Также не забывай, что для исключения человеческого фактора работу по анализу логов приложения и составлению NSS-файла можно вообще поручить утилите NSS Java Maker.

 

Развлекушечки (факультатив)

Давай все же добьем тему и узнаем, какие данные летают между исследуемой программой и ее back-end’ом при запуске. Как не трудно заметить со снимка окна Follow SSL Stream, клиент (JOSM) запрашивает у сервера josm.openstreetmap.de некое изображение по адресу /browser/trunk/images/download.png. Тот незамедлительно его возвращает (HTTP-заголовок Content-Type: image/png). Тело ответа является двоичным представлением этого изображения.

Чтобы посмотреть его само, сохрани диалог в какой-нибудь файл с расширением *.PNG (кнопка Save As), затем открой текстовым редактором (только не стандартным Блокнотом, лучше Notepad++) и удали из него все, что стоит перед строкой «.PNG» и после строки «0». Сохрани и теперь открой уже как изображение. Если к моменту прочтения тобою этих строк авторы JOSM ничего не изменили, ты увидишь это:

Иконка тулбара — вот что за зашифрованный трафик ходил между сервером и клиентом при старте приложения :)
Иконка тулбара — вот что за зашифрованный трафик ходил между сервером и клиентом при старте приложения 🙂

Правильно, это та же самая иконка, которая красуется на кнопке «Скачать картографические данные с сервера OSM» внутри программы JOSM:

Она самая! Только зачем?
Она самая! Только зачем?

В чем смысл такого поведения – в проверке ли доступности сервера или в более изощренной бизнес-логике – нам, видимо, не понять, да и незачем. Быть может, несколько обидно провернуть такую работу, чтобы в конце получить какую-то бесполезную картинку. Но согласись, наивно было рассчитывать найти вместо нее логины и пароли руководства Пентагона. Главное – ты освоил принцип. Дальше – дело техники.

 

Вместо заключения

В этой статье был рассмотрен еще один подход к дешифрации трафика приложений на Java. В отличие от подходов, применявшихся ранее (дешифрация приватным ключом сервера в Wireshark и поддельным сертификатом в Fiddler), рассмотренный подход не зависит от метода деривации сессионного ключа и не требует изменения настроек доверия у какой-либо из сторон взаимодействия.

Рассмотренный подход относительно сложен в применении, что, однако, может быть скомпенсировано использованием специальной автоматизирующей утилиты NSS Java Maker. Надеюсь, он займет достойное место в твоем арсенале средств взлома и отладки защищенных соединений.

Оставить мнение