В епоху цифрової революції та все більшої кількості інтернет-з’єднаних пристроїв, знання мережевих протоколів є ключовим для професіоналів у галузі кібербезпеки. Наш хакерський сайт представляє докладну інструкцію з аналізу мережевих протоколів, що дозволить вам розшифровувати, аналізувати та розуміти важливі аспекти мережевої комунікації. Розглядаючи популярні протоколи, такі як TCP, UDP, ICMP та інші, ми вивчаємо, як дані передаються через мережу, і які можливі загрози та вразливості можуть приховуватися в цих передачах. Для хакера, здатність читати та аналізувати мережевий трафік – це життєво важливий навик, який може виявити слабкі місця системи чи потенційні цілі для атаки. Кожен протокол має свою унікальну структуру та механізми передачі. Розуміння цих механізмів є ключовим для ефективного аналізу.
Наприклад, знаючи, як працює TCP three-way handshake, ви зможете визначити, чи відбувається спроба SYN flood атаки на вашу мережу. Ця частина з аналізу мережевих протоколів покликана надати вам конкретні засоби, методи та знання для глибокого дослідження мережевого трафіку. Від основ до продвинутих технік, ви дізнаєтеся про інструменти, такі як Wireshark, tcpdump та інші, що допоможуть вам в аналізі. Не дозвольте, щоб мережеві протоколи були для вас таємницею. З нашою допомогою ви зможете стати справжнім експертом у цій галузі, розширивши свої можливості та навички у сфері кібербезпеки. Запрошуємо вас на глибоке дослідження мережевого світу разом із нами!
Аналіз протоколу важливий для таких завдань, як зняття відбитків пальців, збір інформації та навіть операція. Але у світі Інтернету речей вам часто доведеться працювати з пропрієтарними, кастомними або новими мережевими протоколами. Ці протоколи можуть викликати труднощі в
Навіть якщо вам вдасться захопити мережевий трафік, сніффери пакетів, такі як Wireshark, зазвичай не можуть розпізнати те, що ви знайшли. Іноді вам потрібно використовувати нові інструменти для зв’язку з вашим пристроєм IoT.
У цьому розділі ми пояснимо процес аналізу мережевих комунікацій, зосередившись на проблемах, з якими ви зіткнетеся під час роботи з незвичайними протоколами. Почнемо з того, що дізнаємося, як проводити оцінку безпеки незнайомих мережевих протоколів і впроваджувати спеціальні інструменти для їх аналізу. Далі ми розширимо найпопулярніший аналізатор трафіку, Wireshark, написавши власний аналізатор протоколів. Після цього ми напишемо спеціальні модулі для Nmap, які будуть сканувати відбитки пальців і навіть атакувати будь-який новий мережевий протокол, який посміє стати у вас на шляху.
Приклади, наведені в цьому розділі, спрямовані не на щось незвичайне, а на DICOM є одним із найпоширеніших протоколів у медичних пристроях та клінічних системах. Однак майже жодні інструменти безпеки не підтримують DICOM, тому цей розділ допоможе вам працювати з будь-яким незвичайним мережевим протоколом, з яким ви можете зіткнутися в майбутньому.
Коли ви працюєте з незвичайними протоколами, краще аналізувати їх відповідно до методології. Дотримуйтесь процедури, описаної в цьому розділі, під час оцінки безпеки мережевого протоколу. Ми намагаємося охопити найважливіші завдання, включаючи збір інформації, аналіз, прототипування та тестування безпеки.
На етапі збору інформації постарайтеся знайти всі доступні вам відповідні ресурси. Але спочатку з’ясуйте, чи добре задокументований протокол: пошукайте його офіційну та неофіційну документацію.
Лістинг та встановлення клієнтів
Отримавши доступ до документації, знайдіть будь-які клієнтські програми, які можуть взаємодіяти з протоколом, і встановіть їх. Ви можете використовувати їх для тиражування та генерації трафіку за бажанням. Різні клієнти можуть впроваджувати протокол з невеликими відмінностями – зверніть увагу на ці відмінності! Також перевірте, чи є у програмістів написані реалізації протоколу на різних мовах програмування. Чим більше клієнтів і реалізацій ви знайдете, тим більше у вас шансів зібрати повну інформацію і відтворити мережеві повідомлення.
Виявлення залежних протоколів
З’ясуйте, чи дійсно протокол залежить від інших протоколів. Наприклад, протокол Server Message Block (SMB) зазвичай працює з NetBios через TCP/IP (NBT). Якщо ви пишете нові інструменти, вам потрібно знати всі залежності протоколу, щоб читати та розуміти повідомлення, а також створювати та надсилати нові. Обов’язково з’ясуйте, який транспортний протокол використовується у вашому протоколі. Це TCP чи UDP? А може, щось інше – СКТП?
Визначення порту протоколу
Визначте номер порту за замовчуванням для протоколу та визначте, чи може протокол працювати на альтернативних портах. Визначення порту за замовчуванням і можливість змінити цей порт є корисною інформацією, яку ви будете використовувати при написанні сканерів або інструментів для збору інформації. Наприклад, скрипти дослідження мережі Nmap можуть не працювати, якщо ми напишемо неправильне правило, а Wireshark може використовувати неправильний низькорівневий аналізатор/дешифратор (диссектор). Хоча існують обхідні шляхи для вирішення цих проблем, краще з самого початку мати надійні правила сканування.
Знайти додаткову документацію
Щоб отримати додаткову документацію або приклади захоплення, відвідайте веб-сайт Wireshark. Проект Wireshark часто включає захоплення пакетів і, як правило, є чудовим джерелом інформації. Проект використовує вікі (https://gitlab.com/wireshark/wireshark/-/ wikis/home/), яка дозволяє користувачам редагувати кожну сторінку.
Також зверніть увагу, в яких областях відсутня документація. Чи можете ви визначити особливості, які недостатньо добре описані? Відсутність документації може привести вас до цікавих відкриттів.
Перевірте, чи всі диссектори Wireshark коректно працюють з протоколом, який ви використовуєте. Чи може Wireshark правильно інтерпретувати та читати всі поля в повідомленнях протоколу?
Для цього спочатку перевірте, чи є у Wireshark аналізатор для протоколу і чи включений він: натисніть Analyze > Enabled Protocols (Аналізувати > Включені протоколи).
Якщо специфікації протоколу загальнодоступні, переконайтеся, що всі поля правильні. Диссектори часто припускаються помилок, особливо якщо мова йде про складні протоколи. Якщо ви помітили будь-які помилки, зверніть на них пильну увагу. Щоб отримати більше ідей, перегляньте список поширених вразливостей і вразливостей (CVE), для яких існують диссектори Wireshark.
На етапі аналізу генеруйте та відтворюйте трафік, щоб зрозуміти, як працює протокол. Мета полягає в тому, щоб отримати чітке розуміння загальної структури протоколу, включаючи його транспортний рівень, повідомлення та доступні операції.
Отримання копії мережевого трафіку
Залежно від типу пристрою існують різні способи прийому мережевого трафіку, які необхідно проаналізувати. Деякі з них можуть підтримувати конфігурації проксі-серверів прямо з коробки! Визначте, чи потрібно вам виконувати активний або пасивний аналіз мережевого трафіку. (Ви можете знайти кілька прикладів того, як це зробити, у книзі Джеймса Форшоу «Attacking Protocol-Level Networks».) Намагайтеся генерувати трафік для кожного доступного сценарію використання та генерувати якомога більше. Наявність різних клієнтів допоможе вам зрозуміти відмінності та нюанси в існуючих реалізаціях.
Одним із перших кроків на етапі аналізу має бути моніторинг трафіку та перевірка надісланих та отриманих пакетів. Можуть бути деякі очевидні проблеми, тому корисно зробити це, перш ніж переходити до активного аналізу. Веб-сайт https://gitlab.com/wireshark/wireshark/-/ wikis/SampleCaptures/ є чудовим ресурсом для пошуку загальнодоступних знімків.
Аналіз мережевого трафіку за допомогою Wireshark
Якщо Wireshark має диссектор, який може аналізувати трафік, який ви генеруєте, увімкніть його, поставивши галочку навпроти його назви у вікні Enabled Protocols. 5.2.
Тепер спробуйте знайти наступне:
Перші байти у повідомленні. Іноді перші байти в початковому з’єднанні або повідомлення є магічними: вони дають змогу швидко ідентифікувати службу.
Початкове з’єднання. Це важлива функція будь-якого протоколу. Зазвичай на цьому етапі ви дізнаєтесь про версію протоколу та підтримуваних функціях, включаючи такі функції безпеки, як шифрування. Повторення цього кроку також допоможе вам розробити сканери, щоб легко знаходити ці пристрої та служби у мережах.
Будь-які потоки TCP/UDP та загальні структури даних, що використовуються в протоколі. Іноді ви ідентифікуватимете рядки у відкритому тексті або загальні структури даних, такі як пакети, довжина яких додається до початку повідомлення.
Порядок байтів у протоколі. Деякі протоколи використовують змішаний порядок проходження байтів, що може викликати проблеми, якщо не буде виявлено на ранній стадії. Порядок байтів сильно відрізняється від протоколу до протоколу, але він необхідний створення правильних пакетів.
Структура повідомлень. Визначте різні заголовки та структури повідомлень, а також способи ініціалізації та закриття з’єднання.
Після того, як ви проаналізували протокол, ви можете почати створювати прототипи або перетворювати нотатки, які ви зібрали в результаті аналізу, в реальне програмне забезпечення, яке ви можете використовувати для зв’язку зі службою через протокол. Прототип дозволить вам переконатися, що ви правильно зрозуміли структуру пакетів кожного типу повідомлень. На цьому етапі важливо вибрати мову програмування, яка дозволить працювати дуже швидко. З цієї причини ми віддаємо перевагу динамічно типізованим скриптовим мовам, таким як Lua або Python. Перевірте, чи є доступні бібліотеки та фреймворки, які можна використовувати для прискорення розробки.
Якщо Wireshark не підтримує протокол, розробіть аналізатор, який допоможе з аналізом. Ми обговоримо цей процес нижче в розділі «Розробка диссектора Wireshark для протоколу Lua DICOM». Ми також будемо використовувати Lua для створення прототипу Nmap Scripting Engine для зв’язку зі службою.
Після того, як ви завершили аналіз, підтвердили свої припущення щодо протоколу та створили робочий прототип для зв’язку за допомогою сервісу DICOM, вам потрібно оцінити безпеку протоколу.
На додаток до загального процесу оцінки безпеки, описаного в розділі 3, розглянемо наступні ключові моменти.
Перевірте можливість атаки, пов’язаної з аутентифікацією сервера та клієнта.В ідеалі клієнт і сервер повинні автентифікувати один одного, цей процес відомий як взаємна автентифікація. Якщо це не так, ви можете видати себе за клієнта або сервер. Така поведінка може мати серйозні наслідки; Наприклад, одного разу ми провели імітаційну атаку клієнта, щоб підробити компонент бібліотеки ліків і ввести шахрайські бібліотеки ліків в інфузійний насос. Незважаючи на те, що дві кінцеві точки обмінюються даними через безпеку транспортного рівня (TLS), Це не могло запобігти атаці, оскільки не було взаємної автентифікації.
Навмисне неправильне кодування протоколу та перевірка на наявність флуд-атак. Крім того, спробуйте відтворити збої та виявити помилки. Фаззинг – це процес автоматичної подачі неправильно сформованих вхідних даних в систему з кінцевою метою пошуку помилок реалізації. У більшості випадків це призводить до збою системи. Чим складніший протокол, тим вищі шанси виявити дефекти, пов’язані з пошкодженням пам’яті. DICOM (про який піде мова нижче) є прекрасним прикладом. Враховуючи його складність, переповнення буфера та інші проблеми безпеки можна знайти в різних реалізаціях. Під час флуд-атак зловмисники надсилають велику кількість запитів до системи, щоб вичерпати ресурси системи, і в результаті система перестає відповідати. Типовим прикладом є TCP SYN, флуд-атака, яку можна зменшити за допомогою файлів cookie SYN.
Перевірте шифрування та цифровий підпис.Чи є дані конфіденційними? Чи можемо ми гарантувати цілісність даних? Наскільки надійні криптографічні алгоритми? Ми бачили, як постачальники впроваджували власні алгоритми шифрування, які завжди мали катастрофічні наслідки. Крім того, багато мережевих протоколів не вимагають цифрового підпису, що забезпечує автентифікацію повідомлень, цілісність даних і відсутність відмови. Наприклад, DICOM не використовує цифровий підпис, якщо він не використовується безпечно такий протокол, як Transport Layer Security (TLS), який вразливий до атак типу “людина посередині”.
Перевірте можливість атаки на застарілу версію. Це криптографічні атаки на протокол, які змушують систему використовувати менш якісний і менш безпечний режим роботи (наприклад, той, який надсилає дані у відкритому вигляді). Прикладами можуть служити атаки Padding Oracle on Downgradeed Legacy Encryption (POODLE) на рівні TLS/SSL. У цій атаці зловмисник “людина посередині” змушує клієнтів використовувати SSL 3.0 і використовує невід’ємну вразливість протоколу, щоб Крадіжка файлів cookie або паролів.
Перевірте, чи немає атаки резервування. Ці атаки спрацьовують, коли протокол має функції, які значно перевищують запит, оскільки зловмисники можуть зловживати цими функціями, щоб спричинити відмову в обслуговуванні. Прикладом може служити DDoS-атака на відображення mDNS, коли деякі реалізації mDNS відповідали на одноадресні запити, що надходять із джерел за межами локальної мережі. Ми розглянемо mDNS у главі 6.
У цьому розділі показано, як написати диссектор, який можна використовувати з Wireshark. Під час перевірки мережевих протоколів, які використовуються пристроями IoT, дуже важливо розуміти, як передаються дані, як генеруються повідомлення, а також чи задіяні функції, операції та механізми безпеки. Потім ми можемо почати модифікувати потоки даних, щоб знайти вразливості. Для написання нашого диссектора ми будемо використовувати Lua; Це дозволить швидко аналізувати перехоплені мережеві комунікації з невеликою кількістю коду. Ми перейдемо від представлення масиву інформації до читабельних повідомлень за допомогою лише кількох рядків коду.
У цій вправі ми зосередимося лише на підмножині функцій, необхідних для обробки повідомлень DICOM типу А (розглянутих у наступному розділі). Ще одна деталь, про яку слід пам’ятати при написанні диссекторів Wireshark для TCP в Lua, полягає в тому, що пакети можуть бути фрагментовані. Крім того, залежно від таких факторів, як повторна передача пакетів, несправність Wireshark або помилки конфігурації, обмежуючи розмір захоплених пакетів (за замовчуванням обмеження розміру пакета захоплення становить 262 144 байти), ми можемо отримати менше або більше одного повідомлення в сегменті TCP. Давайте поки що проігноруємо це і зосередимося на запитах A-ASSOCIATE, яких буде достатньо для ідентифікації сервісів DICOM при написанні сканера. Якщо ви хочете дізнатися більше про те, як боротися з фрагментацією TCP, перегляньте зразок файлу orthanc.lua у цій книзі або перейдіть до https:// nostarch.com/practical-iot-hacking/.
Lua — це скриптова мова для створення розширюваних або скриптових модулів у багатьох важливих проектах безпеки, таких як Nmap, Wireshark, і навіть комерційних продуктах безпеки, таких як NetMon від LogRhythm. Деякі продукти, якими ви користуєтеся щодня, швидше за все, працюють на базі Lua. Багато пристроїв IoT також використовують Lua через його невеликий двійковий розмір і добре задокументований API, що полегшує його використання для розширення проектів іншими мовами, такими як C, C++, Erlang і навіть Java. Це робить Lua ідеальним для вбудовування в додатки. Ви дізнаєтеся, як представляти дані та працювати з ними в Lua, а також як популярні програми, такі як Wireshark і Nmap, використовують Lua для розширення можливостей аналізу трафіку, дослідження мережі та використання вразливостей.
DICOM — це непатентований протокол, розроблений Американським коледжем радіології та Національною асоціацією виробників електротехніки. Він став міжнародним стандартом для передачі, зберігання та обробки інформації про медичні зображення. Хоча DICOM не є пропрієтарним, він є хорошим прикладом мережевого протоколу, реалізованого в багатьох медичних пристроях; Традиційні інструменти мережевої безпеки не дуже добре його підтримують. Зв’язок DICOM через TCP/IP є Двосторонній: клієнт запитує дію, а сервер її виконує, але при необхідності їх можна поміняти місцями. У термінології DICOM клієнт – це користувач сервісного виклику (Service Call User, SCU), а сервер – це Service Call Provider (SCP). Перш ніж ми почнемо писати код, давайте розглянемо деякі важливі повідомлення DICOM і структуру протокола.
Повідомлення DICOM C-ECHO служать, серед іншого, для обміну інформацією про програми, що викликають і викликають, об’єкти, версії, унікальні ідентифікатори (UID), імена та ролі. Ми зазвичай називаємо ці повідомлення запитами DICOM, оскільки вони дозволяють нам визначити, чи є постачальник послуг DICOM у мережі. У повідомленні C-ECHO використовується кілька повідомлень типу А, тому ми розглянемо їх у цьому розділі. Першим пакетом, який надсилає операція C-ECHO, є запит A-ASSOCIATE, якого достатньо для ідентифікації постачальника послуг DICOM. Інформацію про послугу можна отримати з відповіді A-ASSOCIATE.
Існує сім типів повідомлень типу А, які використовуються в повідомленнях C-ECHO:
запит A-ASSOCIATE (A-ASSOCIATE-RQ): запити, надіслані клієнтом для встановлення з’єднання DICOM;
A-ASSOCIATE accept (A-ASSOCIATE-AC): відповіді, надіслані сервером для ухвалення запиту DICOM A-ASSOCIATE;
відхилення A-ASSOCIATE (A-ASSOCIATE-RJ): відповіді, надіслані сервером, щоб відхилити запит DICOM A-ASSOCIATE;
(P-DATA-TF): пакети даних, надіслані сервером та клієнтом;
запит A-RELEASE (A-RELEASE-RQ): запит, надісланий клієнтом, щоб закрити з’єднання DICOM;
відповідь A-RELEASE (PDU A-RELEASE-RP): відповідь, надіслана сервером для підтвердження запиту A-RELEASE;
переривання A-ASSOCIATE (A-ABORT PDU): відповіді, надіслані сервером для скасування операції A-ASSOCIATE.
Усі ці PDU починаються з аналогічної структури пакета. Перша частина – це однобайтове ціле число без знака у форматі Big Endian, який вказує тип PDU. Друга частина – однобайтовий зарезервований розділ, встановлений у 0x0. Третя частина – інформація про довжину PDU, чотирибайтове ціле число без знаку у форматі Little Endian. Четверта частина – поле даних змінної довжини. Ця структуру показано на рис. 5.3.
Після того, як ми дізнаємося структуру повідомлення, ми можемо почати читати та аналізувати повідомлення DICOM. Використовуючи розмір кожного поля, ми можемо розрахувати зміщення при визначенні полів у наших прототипах для аналізу та взаємодії з сервісами DICOM.
Щоб виконати цю вправу, вам потрібно налаштувати сервер і клієнт DICOM. Orthanc — це надійний сервер DICOM з відкритим вихідним кодом, який працює на Windows, Linux і macOS. Встановіть його у своїй системі, переконайтеся, що прапорець DicomServerEnabled увімкнено у файлі конфігурації, і запустіть двійковий файл Orthanc. Якщо все працює правильно, у вас повинен бути сервер DICOM, запущений на порту TCP 4242 (порт за замовчуванням). Введіть команду orthanc, щоб переглянути наступні журнали з описом параметрів конфігурації:
$ ./Orthanc <timestamp> main.cpp:1305] Orthanc version: 1.4.2 <timestamp> OrthancInitialization.cpp:216] Using the default Orthanc configuration <timestamp> OrthancInitialization.cpp:1050] SQLite index directory: "XXX" <timestamp> OrthancInitialization.cpp:1120] Storage directory: "XXX" <timestamp> HttpClient.cpp:739] HTTPS will use the CA certificates from this file: ./orthancAndPluginsOSX.stable <timestamp> LuaContext.cpp:103] Lua says: Lua toolbox installed <timestamp> LuaContext.cpp:103] Lua says: Lua toolbox installed <timestamp> ServerContext.cpp:299] Disk compression is disabled <timestamp> ServerIndex.cpp:1449] No limit on the number of stored patients <timestamp> ServerIndex.cpp:1466] No limit on the size of the storage area <timestamp> ServerContext.cpp:164] Reloading the jobs from the last execution of Orthanc <timestamp> JobsEngine.cpp:281] The jobs engine has started with 2 threads <timestamp> main.cpp:848] DICOM server listening with AET ORTHANC on port: 4242 <timestamp> MongooseServer.cpp:1088] HTTP compression is enabled <timestamp> MongooseServer.cpp:1002] HTTP server listening on port: 8042 (HTTPS encryption is disabled, remote access is not allowed) <timestamp> main.cpp:667] Orthanc has started
Якщо ви не хочете встановлювати сервер Orthanc, ви можете знайти зразки перехоплених пакетів в онлайн-ресурсах для цієї книги або на сторінці Wireshark для зразків пакетів для DICOM.
Перш ніж перейти до коду, переконайтеся, що ви встановили Lua та ввімкнули його в налаштуваннях Wireshark. Перевірити, чи він доступний, можна у вікні Про Wireshark – див. 5.4.
Движок Lua за замовчуванням вимкнено. Щоб увімкнути його, встановіть для disable_lua булеву змінну значення false у файлі init.lua з каталогу встановлення Wireshark:
disable_lua = false
Після того, як ви переконалися, що Lua доступний і ввімкнений, переконайтеся, що підтримка Lua працює правильно, написавши тестовий сценарій і запустивши його наступним чином:
$ tshark -X lua_script: <ваш тестовый сценарий Lua>
Якщо ми включимо просту інструкцію print (наприклад, рядок друку «Hello from Lua») у тестовий файл, ми повинні побачити результат до того, як почнеться захоплення.
$ tshark -X lua_script:test.lua Hello from Lua Capturing on 'ens33'
У Windows вихідні дані можуть не відображатися, якщо використовується звичайний оператор друку. А ось функція report_failure() відкриє вікно з вашим повідомленням, тому можна обійтися і без оператора друку.
Визначимо наш новий диссектор протоколу за допомогою функції Proto(name, description). Як згадувалося раніше, він конкретно ідентифікує повідомлення DICOM типу А (одне з семи повідомлень, перерахованих раніше):
dicom_protocol = Proto("dicom-a", "DICOM A-Type message")
Далі ми визначаємо поля заголовків у Wireshark, щоб вони відповідали структурі DICOM PDU, розглянутій раніше, за допомогою класу ProtoField:
pdu_type = ProtoField.uint8("dicom-a.pdu_type","pduType", base.DEC, {[1]="ASSOC Request", [2]="ASSOC Accept", [3]="ASSOC Reject", [4]="Data", [5]="RELEASE Request", [6]="RELEASE Response", [7]="ABORT"}) -- unsigned 8-bit integer message_length = ProtoField.uint16("dicom-a.message_length", "messageLength", base.DEC) -- unsigned 16-bit integer dicom_protocol.fields = {pdu_type, message_length}
Ми використовуємо ці ProtoFields для додавання елементів у дерево аналізу. Для нашого аналізатора викличемо ProtoField двічі: один раз – для створення однобайтового беззнакового цілого числа для зберігання типу PDU та вдруге – для двох байтів для зберігання довжини повідомлення. Зверніть увагу на те, як ми призначили таблицю значень для типів PDU. Wireshark автоматично відобразить цю інформацію. Потім ми представляємо поля нашого аналізатора протоколу у вигляді таблиці Lua, що містить наші ProtoFields.
Далі оголошуємо нашу основну диссекторну функцію Dissector(), яка має три аргументи: буфер для парсингу Wireshark, інформацію про пакети та дерево, що відображає інформацію протоколу.
У цій функції Dissector() ми проаналізуємо наш протокол і додамо поля ProtoFields, які ми визначили раніше, до дерева, що містить інформацію про наш протокол.
function dicom_protocol.dissector(buffer, pinfo, tree) pinfo.cols.protocol = dicom_protocol.name local subtree = tree:add(dicom_protocol, buffer(), "DICOM PDU") subtree:add_le(pdu_type, buffer(0,1)) -- big endian subtree:add(message_length, buffer(2,4)) -- skip 1 byte end
Ми встановлюємо поле протоколу на ім’я протоколу, яке ми визначили в dicom_protocol.name. Для кожного елемента, який потрібно додати, ми використовуємо або add_le() для формату Big Endian, або add() для формату Little Endian, а також ProtoField та буферний діапазон для аналізу.
DissectorTable містить таблицю піддисекторів для протоколу, яка відображається через діалогове вікно декодування Wireshark.
local tcp_port = DissectorTable.get("tcp.port") tcp_port:add(4242, dicom_protocol)
Щоб завершити диссектор, просто додайте його в DissectorTable для портів TCP на порту 4242.
Лістинг кодів 5.1. Завершено дисектор DICOM типу А
dicom_protocol = Proto("dicom-a", "DICOM A-Type message") pdu_type = ProtoField.uint8("dicom-a.pdu_type", "pduType", base.DEC, {[1]="ASSOC Request", [2]="ASSOC Accept", [3]="ASSOC Reject", [4]="Data", [5]="RELEASE Request", [6]="RELEASE Response", [7]="ABORT"}) message_length = ProtoField.uint16("dicom-a.message_length", "messageLength", base.DEC) dicom_protocol.fields = {message_length, pdu_type} function dicom_protocol.dissector(buffer, pinfo, tree) pinfo.cols.protocol = dicom_protocol.name local subtree = tree:add(dicom_protocol, buffer(), "DICOM PDU") subtree:add_le(pdu_type, buffer(0,1)) subtree:add(message_length, buffer(2,4)) end local tcp_port = DissectorTable.get("tcp.port") tcp_port:add(4242, dicom_protocol)
Ми вмикаємо цей диссектор, поміщаючи файл Lua в каталог плагіна Wireshark, а потім перезавантажуючи Wireshark. Потім, аналізуючи захоплення DICOM, ми повинні побачити байт pduType і довжину повідомлення, що відображаються в стовпці DICOM PDU, який ми визначили в нашому виклику tree:add(). На рис. На малюнку 5.5 показано, як це виглядає в Wireshark. Також можна використовувати фільтри dicom-a. message_length і dicom-a.pdu_type які ми визначили для фільтрації трафіку.
Тепер ви можете чітко визначати тип PDU та довжину повідомлення в пакетах DICOM.
Аналізуючи запит C-ECHO за допомогою нашого нового аналізатора, ми повинні побачити, що він складається з різних повідомлень типу А, таких, як показано на рис. 5.5. Наступним кроком є аналіз даних, що містяться в цих пакетах DICOM.
Щоб показати, як ми можемо обробляти рядки в нашому диссекторі Lua, давайте додамо код до диссектора для аналізу повідомлення A-ASSOCIATE. Рис. На рисунку 5.6 представлена структура запиту A-ASSOCIATE.
Зверніть увагу на 16-байтові заголовки програм, що викликаються, і програм, що викликаються. Заголовок об’єкта додатку – це мітка, яка ідентифікує постачальника послуг. Повідомлення також містить 32-байтовий зарезервований розділ, який має бути заповнений нулем, а також елементи змінної довжини, включаючи елементи «Контекст програми», «Контекст презентації» та «Інформація про користувача».
Почнемо з вилучення полів повідомлень фіксованої довжини, включаючи рядкові значення абонента та викликану програму імені сутності. Це корисна інформація; часто сервіси не мають аутентифікації, тому, якщо у вас є правильний заголовок об’єкта програми, ви можете підключитися і почати вводити команди DICOM. Ми можемо визначити нові об’єкти ProtoField для нашого повідомлення запиту A-ASSOCIATE за допомогою наступного коду:
protocol_version = ProtoField.uint8("dicom-a.protocol_version", "protocolVersion", base.DEC) calling_application = ProtoField.string( "dicom-a.calling_app", "callingApplication") called_application = ProtoField.string("dicom-a.called_app", "calledApplication")
Для вилучення рядкових значень імен викликаних і викликаючих додатків використовуємо функцію ProtoField ProtoField. рядок. Передаємо йому ім’я, яке буде використовуватися у фільтрах, необов’язкове ім’я, яке буде відображатися в дереві v, формат відображення (базовий. ASCII або base. UNICODE) і необов’язкове поле опису. Початкове завантаження даних диссекторної функції
Після додавання нових ProtoFields як полів до диссектора протоколу, нам потрібно додати код для їх завантаження у функцію dissector, dicom_protocol.dissector(), щоб вони були включені в дерево відображення протоколу:
local pdu_id = buffer(0, 1):uint() -- Convert to unsigned int if pdu_id == 1 or pdu_id == 2 then -- ASSOC-REQ (1) / ASSOC-RESP (2) local assoc_tree = subtree:add(dicom_protocol, buffer(), "ASSOCIATE REQ/ RSP") assoc_tree:add(protocol_version, buffer(6, 2)) assoc_tree:add(calling_application, buffer(10, 16)) assoc_tree:add(called_application, buffer(26, 16)) end
Диссектор повинен додати витягнуті поля до піддерева в дереві протоколів. Щоб створити піддерево, викличте функцію add() з нашого існуючого дерева протоколів. Тепер наш простий аналізатор може визначати типи PDU, довжину повідомлення, тип повідомлення ASSOCIATE , протокол, виклик і викликані додатки.
Тепер, коли ми визначили та проаналізували розділи фіксованої довжини, давайте проаналізуємо поля повідомлень змінної довжини. У DICOM ми використовуємо ідентифікатори, які називаються контекстами, для зберігання, представлення та обміну різними характеристиками. Ми покажемо вам, як знайти три різні типи доступних контекстів: контекст програми, контекст презентації та контекст інформації про користувача, які мають різну кількість елементів полів. Але ми Ми не будемо писати код для розбору вмісту елемента.
Для кожного з контекстів додайте піддерево, яке відображає довжину контексту та змінну кількість елементів контексту. Змініть диссектор основного протоколу, щоб він виглядав так:
function dicom_protocol.dissector(buffer, pinfo, tree) pinfo.cols.protocol = dicom_protocol.name local subtree = tree:add(dicom_protocol, buffer(), "DICOM PDU") local pkt_len = buffer(2, 4):uint() local pdu_id = buffer(0, 1):uint() subtree:add_le(pdu_type, buffer(0,1)) subtree:add(message_length, buffer(2,4)) if pdu_id == 1 or pdu_id == 2 then -- ASSOC-REQ (1) / ASSOC-RESP (2) local assoc_tree = subtree:add(dicom_protocol, buffer(), "ASSOCIATE REQ/RSP") assoc_tree:add(protocol_version, buffer(6, 2)) assoc_tree:add(calling_application, buffer(10, 16)) assoc_tree:add(called_application, buffer(26, 16)) --Extract Application Context local context_variables_length = buffer(76,2):uint() local app_context_tree = assoc_tree:add(dicom_protocol, buffer(74, context_variables_ length + 4), "Application Context") app_context_tree:add(app_context_type, buffer(74, 1)) app_context_tree:add(app_context_length, buffer(76, 2)) app_context_tree:add(app_context_name, buffer(78, context_variables_length)) --Extract Presentation Context(s) local presentation_items_length = buffer(78 + context_variables_length + 2, 2):uint() local presentation_context_tree = assoc_tree:add(dicom_protocol, buffer(78 + context_ variables_length, presentation_items_length + 4), "Presentation Context") presentation_context_tree:add(presentation_context_type, buffer(78 + context_variables_ length, 1)) presentation_context_tree:add(presentation_context_length, buffer(78 + context_ variables_length + 2, 2)) -- TODO: Extract Presentation Context Items --Extract User Info Context local user_info_length = buffer(78 + context_variables_length + 2 + presentation_items_ length + 2 + 2, 2):uint() local userinfo_context_tree = assoc_tree:add(dicom_protocol, buffer(78 + context_ variables_length + presentation_items_length + 4, user_info_length + 4), "User Info Context") userinfo_context_tree:add(userinfo_length, buffer(78 + context_variables_length + 2 + presentation_items_length + 2 + 2, 2)) -- TODO: Extract User Info Context Items end end
При роботі з мережевими протоколами часто зустрічаються поля пе ремінної довжини, що вимагають обчислення зсувів. Дуже важливо, щоб ви отримали правильні значення довжини, тому що всі вирахування зсувів залежать від них.
Після внесення змін, згаданих у попередньому розділі, переконайтеся, що ваші пакети DICOM проаналізовані правильно, перевіривши отриману довжину. Тепер ви повинні побачити піддерево для кожного контексту (рисунок 5.8). Зауважте, що оскільки ми надаємо діапазон буферів у нових піддеревах, ви можете вибрати їх для вибору відповідного розділу. Знайдіть час, щоб переконатися, що контекст кожного протоколу DICOM розпізнається так, як ви очікували.
Якщо ви хочете попрактикуватися, рекомендуємо додавати поля з різних контекстів до диссектору. Ви можете завантажити пакет DICOM зі сторінки зразка пакета Wireshark, де ми розмістили пакет, що містить запит на відлуння DICOM. Ви також знайдете повний приклад, включаючи фрагментацію TCP, в онлайн-ресурсах цієї книги. Пам’ятайте, що ви можете в будь-який момент перезавантажити скрипти Lua, щоб протестувати свій останній диссектор, не перезапускаючи Wireshark, натиснувши «Аналізувати» > «Перезавантажити плагін Lua>s».
Раніше в цьому розділі ви дізналися, що DICOM має пінг-подібну утиліту під назвою C-Echo-запит, яка складається з кількох повідомлень типу A. Далі ви створили диссектор Lua для аналізу цих повідомлень за допомогою Wireshark. Далі ви будете використовувати Lua для вирішення іншої проблеми: написання сканера DICOM Services. Сканер віддалено ідентифікує постачальників послуг DICOM (DSP) у мережах, щоб активно тестувати їхні конфігурації та навіть запускати атаки. Оскільки Nmap добре відомий своїми можливостями сканування, а його скриптовий движок також працює на Lua, це ідеальний інструмент для написання такого сканера.
У цій вправі ми зосередимося на підмножині функцій, які беруть участь у надсиланні часткового запиту C-ECHO.
Давайте почнемо зі створення бібліотеки Nmap Scripting Engine для нашого коду, пов’язаного з DICOM. Ми будемо використовувати бібліотеку для зберігання будь-яких функцій, що використовуються під час створення та видалення сокетів, надсилання та отримання пакетів DICOM, а також таких дій, як прив’язка та запит послуг.
Nmap вже містить бібліотеки, які допомагають виконувати стандартні операції введення-виведення, сокети та інші завдання. Знайдіть хвилинку, щоб переглянути колекцію бібліотеки, щоб знати, що вже доступно. Ознайомтеся з документацією до цих скриптів і бібліотек на сторінці https://nmap.org/nsedoc/.
Як правило, бібліотеки Nmap Scripting Engine можна знайти в каталозі <installation>/nselib/. Знайдіть цей каталог і створіть файл з ім’ям dicom.lua. У цьому файлі почніть з оголошення інших стандартних бібліотек Lua та Nmap Scripting Engine, які слід використовувати. Також дайте середовищу ім’я для нової бібліотеки:
local nmap = require "nmap" local stdnse = require "stdnse" local string = require "string" local table = require "table" local nsedebug = require "nsedebug" _ENV = stdnse.module("dicom", stdnse.seeall)
У цьому випадку ми будемо використовувати чотири різні бібліотеки: дві бібліотеки Nmap Scripting Engine (nmap і stdnse) і дві стандартні бібліотеки Lua (string і table). Бібліотеки рядків і таблиць Lua, як не дивно, призначені для операцій над рядками та таблицями. В основному ми будемо використовувати обробку сокетів бібліотеки nmap, а також stdnse – для читання аргументів користувача та друку налагоджувальних операторів, коли це необхідно. Крім того, ми будемо використовувати корисну бібліотеку nsedebug, яка відображає різні типи даних у зручній для читання формі.
Тепер визначимо деякі константи для зберігання кодів PDU, значень UUID, а також мінімально та максимально допустимих розмірів пакетів. Це дозволить вам писати код, який буде більш читабельним і простішим у обслуговуванні. У мові Lua ми зазвичай визначаємо константи великими літерами:
local MIN_SIZE_ASSOC_REQ = 68 -- Минимальный размер запроса ASSOCIATE local MAX_SIZE_PDU = 128000 -- Максимальный размер любого PDU local MIN_HEADER_LEN = 6 -- Минимальная длина заголовка DICOM local PDU_NAMES = {} local PDU_CODES = {} local UID_VALUES = {} -- Таблица имен PDU для кодов PDU_CODES = { ASSOCIATE_REQUEST = 0x01, ASSOCIATE_ACCEPT = 0x02, ASSOCIATE_REJECT = 0x03, DATA = 0x04, RELEASE_REQUEST = 0x05, RELEASE_RESPONSE = 0x06, ABORT = 0x07 } -- Таблица имен UID для значений UID_VALUES = { VERIFICATION_SOP = "1.2.840.10008.1.1", -- Проверочный класс SOP APPLICATION_CONTEXT = "1.2.840.10008.3.1.1.1", -- Имя контекста приложения DICOM IMPLICIT_VR = "1.2.840.10008.1.2", -- Подразумевает VR Little Endian: Синтаксис по умолчанию для DICOM FIND_QUERY = "1.2.840.10008.5.1.4.1.2.2.1" -- Корень иноформационной модели запрос/ответ - FIND } -- Мы сохраняем имена, используя их коды и ключи для вывода на печать имен типов PDU for i, v in pairs(PDU_CODES) do PDU_NAMES[v] = i end
Тут визначено константні значення для поширених кодів операцій DICOM. Ми також визначили таблиці для представлення різних класів даних за допомогою UID та довжини пакетів, специфічні для DICOM. Тепер ми готові розпочати комунікацію з сервісом.
Для надсилання та отримання даних ми будемо використовувати бібліотеку механізму сценаріїв nmap. Оскільки створення та знищення сокетів є рутинною операцією, рекомендується писати функції для них у нашій новій бібліотеці. Напишемо нашу першу функцію dicom.start_connection(), яка створює сокет для сервісу DICOM:
--- -- start_connection(host, port) открывает сокет для службы DICOM -- -- @param host Host object -- @param port Port table -- @return (status, socket) Если status равен true, сокет возвращает объект DICOM -- Если status равен false, сокет возвращает сообщение об ошибке. --- function start_connection(host, port) local dcm = {} local status, err Анализ сетевых протоколов 141 dcm['socket'] = nmap.new_socket() status, err = dcm['socket']:connect(host, port, "tcp") if(status == false) then return false, "DICOM: невозможно подключиться к службе: " .. err end return true, dcm end
Таким же чином створюємо функції для відправки та прийому DICOM-пакетів:
-- send(dcm, data) Отправляет пакет DICOM через открытый сокет -- -- @param dcm объект DICOM -- @param data данные для отправки -- @return status равен True если данные отправлены корректно, и False в случае ошибки function send(dcm, data) local status, err stdnse.debug2("DICOM: Sending DICOM packet (%d bytes)", #data) if dcm["socket"] ~= nil then status, err = dcm["socket"]:send(data) if status == false then return false, err end else return false, "No socket available" end return true end -- receive(dcm) Чтение пакетов DICOM через открытый сокет -- -- @param dcm DICOM object -- @return (status, data) возвращает данные, если status равен true, иначе возвращает сообщение об ошибке. function receive(dcm) local status, data = dcm["socket"]:receive() if status == false then return false, data end stdnse.debug2("DICOM: receive() read %d bytes", #data) return true, data end
Функції send(dcm, data) і receive(dcm) використовують функції сокета Nmap send() і receive() відповідно. Вони звертаються до дескриптора з’єднання, що зберігається у змінній dcm[“socket”], для читання та запису пакетів DICOM u поверх сокета. Примітка stdnse.debug[1-9], який використовується для друку діагностичних операторів, коли Nmap запускає прапорець налагодження (-d). У цьому випадку використання stdnse.debug2() друкуватиметься, коли для рівня налагодження встановлено значення 2 або вище.
Тепер, коли ми налаштували базовий мережевий ввід/вивід, давайте створимо функції, які відповідають за генерацію повідомлень DICOM. Як згадувалося раніше, DICOM PDU використовує заголовок для визначення свого типу та довжини. У скриптовому движку Nmap ми використовуємо рядки для зберігання потоків байтів і функції string.pack() і string.unpack() для кодування та отримання інформації в різних форматах і порядку байтів. Спосіб використання string.pack() і string.unpack(), вам потрібно буде ознайомитися з рядками формату Lua, оскільки вам потрібно буде представити дані в різних форматах. Про них можна прочитати на https://www.lua. org/manual/5.3/manual.html#6.4.2. Знайдіть час, щоб дізнатися, як записувати порядок ендіанте та поширені конверсії.
--- -- pdu_header_encode(pdu_type, length) кодирует заголовок PDU DICOM -- -- @param pdu_type тип PDU – беззнаковое целое -- @param length длина сообщения DICOM -- @return (status, dcm) если status равен true, возвращает заголовок. -- если status равен false, dcm содержит сообщение об ошибке --- function pdu_header_encode(pdu_type, length) -- несколько простых проверок; мы не проверяем диапазоны, чтобы позволить пользователю создавать некорректные пакеты. if not(type(pdu_type)) == "number" then return false, "PDU должен быть беззнаковым целым. Диапазон:0-7" Анализ сетевых протоколов 143 end if not(type(length)) == "number" then return false, "Длина должна быть беззнаковым целым." end local header = string.pack("<B >B I4", pdu_type, -- тип PDU ( 1 байт – беззнаковое целое в формате Big Endian ) 0, -- раздел зарезервирован ( 1 байт, должен быть равен 0x0 ) length) -- длина PDU ( 4 байта – беззнаковое целое в формате Little Endian) if #header < MIN_HEADER_LEN then return false, "Заголовок не должен быть короче 6 байтов. Произошла ошибка." end return true, header end
Функція pdu_header_encode() кодує інформацію про тип та довжину PDU. Виконавши кілька простих перевірок, ми визначаємо змінну заголовка. Щоб закодувати потік байтів відповідно до порядку байтів та формату, ми використовуємо string. pack() і рядок формату <B> B I4, де <B – це один байт у Big Endian , а > B I4 – байт, за яким слідує ціле число без знака з чотирьох байтів ціна в Little Endian | Функція повертає логічне значення, стан операції, що представляє, і результат.
Крім того, нам потрібно написати функцію, яка надсилає та аналізує запити та відповіді A-ASSOCIATE. Як ви бачили раніше в цьому розділі, повідомлення запиту A-ASSOCIATE містить різні типи контекстів: програма, презентації та інформація про користувача. Оскільки це довша функція, давайте розіб’ємо її на частини.
Контекст застосунку явно визначає елементи та параметри служби. У DICOM часто можна побачити визначення інформаційних об’єктів (IOD), які представляють об’єкти даних, якими керують через центральний реєстр. Повний список IOD ви знайдете на http://dicom.nema.org/ dicom/2013/output/chtml/part06/chapter_A.html. Ми будемо читати ці IOD з постійних визначень, які ми розмістили на початку нашої бібліотеки. Давайте запустимо підключення DICOM і створимо контекст програми.
--- -- associate(host, port) Пытается связаться с провайдером службы DICOM путем отправки запроса A-ASSOCIATE. -- -- @param host объект хоста -- @param port объект порта -- @return (status, dcm) если status равен true, возвращает объект DICOM -- если status равен false, dcm содержит сообщение об ошибке --- function associate(host, port, calling_aet_arg, called_aet_arg) local application_context = "" local presentation_context = "" local userinfo_context = "" local status, dcm = start_connection(host, port) if status == false then return false, dcm end application_context = string.pack(">B B I2 c" .. #UID_VALUES["APPLICATION_CONTEXT"], 0x10, -- тип элемента (1 байт) 0x0, -- зарезервировано ( 1 байт) #UID_VALUES["APPLICATION_CONTEXT"], -- длина (2 байта) UID_VALUES["APPLICATION_CONTEXT"]) -- контекст приложения
Контекст програми складається з типу (один байт), зарезервованого поля (один байт), довжини контексту (два байти) і значення, представлене ідентифікаторами OID. Щоб уявити цю структуру в Lua ми використовуємо рядок формату B B I2 C[#length]. Можемо опустити значення розміру рядків в один байт. Ми створюємо контексти подання та інформації про користувача аналогічним чином. Ось контекст уявлення, який визначає Abstract Syntax (абстрактний синтаксис) та Transfer Syntax (Синтаксис передачі). Це набори правил для форматування та обміну об’єктами, і ми представляємо їх за допомогою IOD.
presentation_context = string.pack(">B B I2 B B B B B B I2 c" .. #UID_VALUES["VERIFICATION_ SOP"] .. "B B I2 c".. #UID_VALUES["IMPLICIT_VR"], 0x20, -- тип контекста представления ( 1 байт ) 0x0, -- зарезервировано ( 1 байт ) 0x2e, -- длина элемента ( 2 байта ) 0x1, -- идентификатор контекста представления ( 1 байт ) 0x0,0x0,0x0, -- зарезервировано ( 3 байта ) 0x30, -- дерево абстрактного синтаксиса ( 1 байт ) 0x0, -- зарезервировано ( 1 байт ) 0x11, -- длина элемента ( 2 байта ) UID_VALUES["VERIFICATION_SOP"], 0x40, -- синтаксис передачи ( 1 байт ) 0x0, -- зарезервировано ( 1 байт ) 0x11, -- длина элемента ( 2 байта ) UID_VALUES["IMPLICIT_VR"])
Зауважте, що контекстів представлення може бути декілька. Далі визначаємо контекст інформації про користувача:
local implementation_id = "1.2.276.0.7230010.3.0.3.6.2" local implementation_version = "OFFIS_DCMTK_362" userinfo_context = string.pack(">B B I2 B B I2 I4 B B I2 c" .. #implementation_id .. " B B I2 c".. #implementation_version, 0x50, -- тип 0x50 (1 байт) 0x0, -- зарезервировано ( 1 байт ) 0x3a, -- длина ( 2 байта ) 0x51, -- тип 0x51 ( 1 байт) 0x0, -- зарезервировано ( 1 байт ) 0x04, -- длина ( 2 байта ) 0x4000, -- данные ( 4 байта ) 0x52, -- тип 0x52 (1 байт ) 0x0, -- зарезервировано (1 байт ) 0x1b, -- длина (2 байта) implementation_id, -- идентификатор реализации ( байты #implementation_id) 0x55, -- тип 0x55 (1 байт) 0x0, -- зарезервировано (1 байт) #implementation_version, -- длина (2 байта) implementation_version)
Тепер у нас є три змінні, які містять контексти: application_context, presentation_context і userinfo_context.
Давайте додамо контексти, які ми щойно створили, до заголовка та запиту A-ASSOCIATE. Щоб дозволити іншим скриптам передавати аргументи нашій функції та використовувати різні значення для імен абонентів і абонентів програми, ми запропонуємо два варіанти: необов’язковий аргумент або введені користувачем дані. У рушії сценаріїв Nmap ви можете прочитати аргументи скрипту, надані –script-args, за допомогою функції Nmap stdnse.get_script_args() Наступним чином:
local called_ae_title = called_aet_arg or stdnse.get_script_args("dicom.called_aet") or "ANYSCP" local calling_ae_title = calling_aet_arg or stdnse.get_script_args("dicom.calling_aet") or "NMAP-DICOM" if #calling_ae_title > 16 or #called_ae_title > 16 then return false, "Calling/Called AET field can't be longer than 16 bytes." еnd
Структура, що містить заголовки компонентів додатку, повинна мати довжину 16 байт, тому ми використовуємо string.rep() для заповнення решти буфера пробілами:
--Fill the rest of buffer with %20 called_ae_title = called_ae_title .. string.rep(" ", 16 – #called_ae_title) calling_ae_title = calling_ae_title .. string.rep(" ", 16 – #calling_ae_title)
Тепер ми можемо визначати наші власні виклики та викликані імена об’єктів додатку за допомогою аргументів скрипту. Ми також можемо використовувати аргументи скрипту, щоб написати інструмент, який намагається вгадати правильний об’єкт програми, ніби ми вводимо пароль методом перебору. Визначення структури запиту A-ASSOCIATE
Давайте складемо наш запит A-ASSOCIATE. Визначаємо його структуру так само, як і в контексті:
-- ASSOCIATE request local assoc_request = string.pack(">I2 I2 c16 c16 c32 c" .. application_ context:len() .. " c" .. presentation_context:len() .. " c".. userinfo_context:len(), 0x1, -- версия протокола ( 2 байта ) 0x0, -- зарезервировано ( 2 байта должны быть равны 0x0 ) called_ae_title, -- заголовок вызываемого AE ( 16 байтов) calling_ae_title, -- заголовок вызывающего AE ( 16 байтов) 0x0, -- зарезервировано ( 32 должны быть равны 0x0 ) application_context, presentation_context, userinfo_context)
Ми починаємо з вказівки версії протоколу (два байти), зарезервованого розділу (два байти), заголовка об’єкта, що викликається програми (16 байтів), заголовка об’єкта програми (16 байтів), що викликає, інший зарезервований розділ (32 байти) і щойно створені контексти (додаток, презентація та інформація користувача). Тепер нашому запиту A-ASSOCIATE не вистачає лише заголовка. Настав час використовувати функцію dicom.pdu_header_encode(), яку ми визначили раніше, щоб згенерувати її:
local status, header = pdu_header_encode(PDU_CODES["ASSOCIATE_REQUEST"], #assoc_request) -- с заголовком может что-то пойти не так if status == false then return false, header end assoc_request = header .. assoc_request stdnse.debug2("PDU len minus header:%d", #assoc_request-#header) if #assoc_request < MIN_SIZE_ASSOC_REQ then return false, string.format("запрос ASSOCIATE должен содержать не менее %d байтов и мы пробуем послать %d.", MIN_SIZE_ASSOC_REQ, #assoc_request) end
Ми створюємо заголовок з типом PDU, що відповідає значенню запиту A-ASSOCIATE, а потім додаємо тіло повідомлення. Також додаємо тут логіку перевірки помилок. Тепер можна надіслати повний запит A-ASSOCIATE і прочитати відповідь за допомогою раніше визначених функцій для надсилання та читання пакетів DICOM:
status, err = send(dcm, assoc_request) if status == false then return false, string.format("Невозможно отправить запрос ASSOCIATE:%s", err) end status, err = receive(dcm) if status == false then return false, string.format("Невозможно прочитать запрос ASSOCIATE:%s", err) end if #err < MIN_SIZE_ASSOC_RESP then return false, "Ответ ASSOCIATE слишком короткий." end
Це круто! Далі необхідно визначити тип БРЖ, який використовується для прийняття або відхилення підключення.
На цьому етапі залишається лише розібрати відповідь з string.unpack(). Це схоже на string.pack(), і ми використовуємо рядки форматування для визначення структури, яку потрібно прочитати. У цьому випадку ми зчитуємо тип відповіді (один байт), зарезервоване поле (один байт), довжину (чотири байти) та версію протоколу (два байти), відповідно до рядка формату > B B I4 I2:
local resp_type, _, resp_length, resp_version = string.unpack(">B B I4 I2", err) stdnse.debug1("PDU тип:%d длина:%d Protocol:%d", resp_type, resp_length, resp_version)
Далі перевірте код відповіді, щоб перевірити, чи збігається він з кодом PDU для прийняття або відхилення ASSOCIATE:
if resp_type == PDU_CODES["ASSOCIATE_ACCEPT"] then stdnse.debug1("Обнаружено сообщение ASSOCIATE ACCEPT!") return true, dcm elseif resp_type == PDU_CODES["ASSOCIATE_REJECT"] then stdnse.debug1("Обнаружено сообщение ASSOCIATE REJECT!") return false, "получено ASSOCIATE REJECT " else return false, "Неопеределенный ответ:" .. resp_type end end -- end of function
Якщо ми отримаємо повідомлення про прийняття ASSOCIATE, ми повернемо true; в іншому випадку ми повернемо false.
Тепер, коли ми реалізували функцію зв’язку зі службою, ми створюємо скрипт, який завантажує бібліотеку та викликає функцію dicom.associate():
description = [[ Пытается обнаружить серверы DICOM (провайдеры служб DICOM) при помощи частичного запроса C-ECHO. Запросы C-ECHO также известны как проверочные пакеты DICOM для проверки соединения. Обычно проверочный пакет DICOM формируется так: * Client -> A-ASSOCIATE request -> Server * Server -> A-ASSOCIATE ACCEPT/REJECT -> Client * Client -> C-ECHO request -> Server * Server -> C-ECHO response -> Client * Client -> A-RELEASE request -> Server * Server -> A-RELEASE response -> Client В данном сценарии мы отправляем только запрос A-ASSOCIATE и ищем код успеха в ответе, поскольку это очевидный способ обнаружения провайдеров служб DICOM. ]] --- -- @usage nmap -p4242 --script dicom-ping <target> -- @usage nmap -sV --script dicom-ping <target> -- -- @output -- PORT STATE SERVICE REASON -- 4242/tcp open dicom syn-ack -- |_dicom-ping: провайдер служб DICOM обнаружен --- author = "Paulino Calderon <calderon()calderonpale.com>" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery", "default"} local shortport = require "shortport" local dicom = require "dicom" local stdnse = require "stdnse" local nmap = require "nmap" portrule = shortport.port_or_service({104, 2761, 2762, 4242, 11112}, "dicom", "tcp", "open") action = function(host, port) local dcm_conn_status, err = dicom.associate(host, port) if dcm_conn_status == false then stdnse.debug1("Association failed:%s", err) if nmap.verbosity() > 1 then return string.format("Association failed:%s", err) else return nil end end -- Мы убедились, что это DICOM, обновляем имя службы port.version.name = "dicom" nmap.set_port_version(host, port) return "DICOM Service Provider discovered" end
Спочатку ми заповнюємо деякі обов’язкові поля, такі як опис, автор, ліцензія, категорії та правило виконання. Ми оголошуємо основну функцію скрипта action як функцію Lua. Ви можете дізнатися більше про формати скриптів, прочитавши офіційну документацію (https://nmap.org/book/nse-script-format.html) або переглянувши офіційну колекцію скриптів. Якщо скрипт знаходить службу DICOM, він повертає такі дані:
Nmap scan report for 127.0.0.1 PORT STATE SERVICE REASON 4242/tcp open dicom syn-ack |_dicom-ping: DICOM Service Provider discovered Final times for host: srtt: 214 rttvar: 5000 to: 100000
В іншому випадку скрипт не повертає жодних результатів, оскільки, за замовчуванням, Nmap показує інформацію лише тоді, коли точно виявляє службу.
У цьому розділі ви дізналися, як працювати з протоколами нової мережі та створили інструменти для найпопулярніших фреймворків для сканування мережі (Nmap) та аналізу трафіку (Wireshark). Ви також дізналися, як виконувати ряд поширених операцій (створення спільних структур даних, обробка рядків і виконання мережевих операцій введення-виведення) для швидкого створення прототипів нових інструментів мережевої безпеки в Lua. Володіючи цими знаннями, ви зможете вирішувати проблеми, розглянуті в цьому розділі, а також інші, і відточувати свої навички Lua. У світі Інтернету речей, що постійно розвивається, можливість швидко писати нові інструменти мережевих операцій дуже зручна.
Крім того, пам’ятайте про дотримання методології під час проведення оцінки безпеки. Тема цього розділу є лише відправною точкою для розуміння та виявлення аномалій у мережевих протоколах. Оскільки тема настільки велика, ми не змогли охопити всі загальні завдання, пов’язані з аналізом протоколів, але ми настійно рекомендуємо вам прочитати книгу Джеймса Форшоу, згадану вище, «Атакуючі мережі на рівні протоколу».
Ми використовували матеріали з книги “The Definitive Guide to Attacking the Internet of Things ”, яку написали Фотиос Чанцис, Иоаннис Стаис,
Паулино Кальдерон, Евангелос Деирменцоглу и Бо Вудс.