Якщо ви знаєте протоколи, які безпосередньо взаємодіють з електронними компонентами системи, ви можете проникнути в пристрої IoT на фізичний рівень. Універсальний асинхронний приймач-передавач (UART) є одним з найпростіших протоколів розслідувань, і його використання є одним із найпростіших способів відкрити доступ до пристроїв IoT. Постачальники зазвичай використовують його для налагодження, а отже, ви можете рутувати через нього. Для цього вам знадобляться деякі спеціалізовані апаратні інструменти; Наприклад, зловмисники часто ідентифікують контакти UART на друкованій платі пристрою за допомогою мультиметра або логічного аналізатора, потім підключають до контактів перехідник USB-UART і відкривають послідовну налагоджувальну консоль з атакуючої робочої станції. У більшості випадків, зробивши це, ви опинитеся на кореневій оболонці.
Joint Test Action Group (JTAG) — це галузевий стандарт (визначений у IEEE 1491.1) для налагодження та тестування дедалі складніших друкованих плат. Інтерфейси JTAG на вбудованих пристроях дозволяють нам зчитувати і записувати вміст пам’яті, включаючи дамп всієї прошивки, надаючи можливість отримати повноцінний контроль над цільовим пристроєм. Serial Wire Debug (SWD) – це дуже схожий, навіть простіший, ніж JTAG, електричний інтерфейс, який ми розглянемо тут. Більша частина цього розділу присвячена практичній вправі: ви будете програмувати, налагоджувати та обманювати мікроконтролер, щоб обійти процес аутентифікації за допомогою UART та SWD. Але спочатку ми пояснимо внутрішню роботу цих протоколів і покажемо вам, як визначити терморегулятори UART і JTAG на друкованій платі за допомогою апаратного та програмного забезпечення.
UART – це послідовний протокол, що означає, що він передає дані між компонентами по одному біту за раз. З іншого боку, паралельні протоколи зв’язку передають дані кількома каналами одночасно. Поширені послідовні протоколи включають RS-232, I2C, SPI, CAN, Ethernet, HDMI, PCI Express і USB.
UART простіший за багато протоколів, з якими ви, напевно, стикалися. Для того, щоб зв’язок був синхронізований, передавач і приймач UART повинні домовитися про певну швидкість передачі даних в бод (біт в секунду). На мал 7.1 зображено формат пакету UART.
Як правило, лінія утримується на високому рівні (з логічним значенням 1), в той час як UART знаходиться в стані очікування. Потім, щоб подати сигнал про початок передачі даних, передавач посилає на приймач стартовий біт, під час якого сигнал залишається на низькому рівні (з логічним значенням 0). Далі передавач надсилає від п’яти до восьми бітів даних, що містять фактичне повідомлення, за якими слідує необов’язковий біт парності та один або два стоп-біти (з логічним значенням 1), залежно від конфігурації. Біт парності, який використовується для перевірки помилок, на практиці зустрічається рідко. Стоп-біт або біти вказують на кінець передачі.
Найпоширеніша конфігурація називається 8N1: вісім біт даних без парності і один стоп-біт. Наприклад, якщо ми хочемо відправити символ C або 0x43 в ASCII, в конфігурації 8N1 UART, ми повинні відправити наступні біти: 0 (початковий біт); 0, 1, 0, 0, 0, 0, 0, 1, 1 (двійкове значення 0x43) і 0 (стоп-біт).
Для зв’язку з UART може використовуватися різноманітне обладнання. Одним з простих варіантів є адаптер USB-to-Serial, аналогічний тому, який ми використовуємо нижче в розділі «Джейлбрейк пристрою за допомогою UART і SWD». Інші варіанти включають мікросхеми CP2102 або PL2303. Якщо ви новачок у зломі апаратного забезпечення, ми рекомендуємо придбати багатоцільовий інструмент, який підтримує протоколи, відмінні від UART, такі як Bus Pirate, Adafruit FT232H, Shikra або Attify Badge.
Список інструментів з їх описом і посиланнями на сайти, де їх можна придбати, представлений в розділі «Інструменти для злому інтернету речей» в кінці книги.
Щоб підключитися до пристрою через UART, спочатку потрібно знайти його чотири контакти, які зазвичай мають форму контактів або колодок (іноді з покритими отворами). Термін «терморегулятори» відноситься до схеми підключення цих контактів. Ми будемо використовувати ці терміни як синоніми. UART має чотири контакти: TX (передача), RX (прийом), Vcc (напруга живлення) і GND (земля). Для початку відкрийте корпус пристрою та вийміть друковану плату. Майте на увазі, що це може призвести до відмови в гарантійному обслуговуванні.
Ці чотири кеглі часто розміщуються поруч один з одним на дошці. Якщо вам пощастить, ви навіть можете знайти маркування, що позначає порти TX і RX, як показано на малюнку 7.2. При цьому Ви можете бути впевнені, що комплект з чотирьох контактів – це контакти UART.
В інших випадках ви можете побачити чотири колодки з наскрізними отворами поруч один з одним, наприклад, в маршрутизаторі TPLink (мал. 7.3). Це може бути пов’язано з тим, що постачальники видаляють контакти роз’єму UART з друкованої плати; Тоді, можливо, доведеться або припаяти до них дроти, або скористатися щупами. (Зонди — це фізичні пристрої, які підключають електронне випробувальне обладнання до пристрою, включаючи зонд, кабель і термін.
Також майте на увазі, що деякі пристрої емулюють порти UART, програмуючи контакти вводу/виводу загального призначення (GPIO), якщо на платі недостатньо місця для виділених апаратних контактів UART.
Якщо контакти UART позначені не так чітко, як показано тут, визначити їх на пристрої зазвичай можна двома способами: мультиметром або логічним аналізатором. Мультиметр вимірює напругу, струм і опір. Мати мультиметр у своєму арсеналі при зламі обладнання дуже важливо, оскільки він може служити різним цілям. Наприклад, ми зазвичай використовуємо його для перевірки цілісності електричних ланцюгів. Під час перевірки цілісності звуковий сигнал лунає, коли імпеданс ланцюга досить низький (менше кількох Ом), що вказує на те, що між двома точками, виміряними контактами мультиметра, є електричний зв’язок.
Хоча дешевий мультиметр може впоратися з цим завданням, ми рекомендуємо придбати надійний і точний прилад, якщо ви плануєте глибше зануритися в злом. Мультиметри True RMS більш точно вимірюють змінний струм. На малюнку 7.4 показаний типовий мультиметр.
Перед тим як визначити терморегулятори УАРТ мультиметром, переконайтеся, що прилад вимкнено. Чорний щуп слід підключити до гнізда COM мультиметра, а червоний – до гнізда VΩ.
Почнемо з визначення “наземного” виводу UART. Поверніть шкалу мультиметра в режим замкнутого контуру (зазвичай представлений чутним символом вібрації). Він може мати загальне положення перемикача з однією або декількома функціями – зазвичай це опір. Доторкніться кінцем чорного щупа до будь-якої заземленої металевої поверхні (області, безпосередньо підключеної до ланцюгів «заземлення»), незалежно від того, чи є він частиною друкованої плати, що перевіряється, чи чимось іншим.
Потім по черзі переміщуйте червоний щуп до кожного з контактів плати, які, як ви підозрюєте, можуть бути частиною терморегуляторів UART. Якщо ви почуєте звуковий сигнал мультиметра, контакт GND знайдено. Майте на увазі, що пристрій може мати кілька контактів GND, тому знайдений контакт не обов’язково є частиною терморегулятора UART.
Тепер знайдіть контакт живлення Vcc. Встановіть шкалу мультиметра в режим постійної напруги і встановіть її на 20 В. Не піднімайте чорний щуп з лінії заземлення. Доторкніться червоним щупом до передбачуваного контакту живлення та увімкніть пристрій. Якщо мультиметр вимірює напругу постійного струму 3,3 або 5 В, ви знайшли контакт Vcc. Якщо напруга відрізняється, доторкніться червоним щупом до наступного контакту, перезавантажте пристрій і виміряйте ще раз. Перевіряйте кожен контакт, доки не визначите Vcc.
Далі визначте порт TX. Залиште мультиметр в режимі постійного струму напругою не більше 20 В і продовжуйте торкатися поверхні землі чорним щупом. Доторкніться червоним щупом до призначеної площадки, потім вимкніть і знову увімкніть пристрій. Якщо напруга коливається протягом декількох секунд, а потім стабілізується на Vcc (3,3 або 5), швидше за все, ви знайшли порт TX. Це пов’язано з тим, що під час завантаження пристрій відправляє послідовні дані через цей порт TX для налагодження. Після завершення завантаження лінія UART переходить у режим очікування. Нагадаємо (мал. 7.1), що вільна лінія UART підтримує високий логічний рівень, а це означає, що її напруга дорівнює Vcc.
Якщо ви вже визначили решту портів UART, найближчим четвертим контактом, швидше за все, є порт RX. В іншому випадку ви можете ідентифікувати його, оскільки він має найменше коливання напруги та найнижчий рівень напруги серед усіх контактів UART.
УВАГА! Нічого страшного, якщо ви переплутаєте між собою порти UART RX і TX, так як дроти, що підключаються до них, легко поміняти місцями – наслідків не буде. Але якщо переплутати Vcc з GND і неправильно підключити дроти, пристрій може вийти з ладу.
Для більш точної ідентифікації контактів UART використовують логічний аналізатор – пристрій, який вловлює і відображає сигнали цифрової системи. Доступно багато типів логічних аналізаторів, починаючи від недорогих, таких як HiLetgo або Open Workbench Logic Sniffer, і закінчуючи професійними пристроями, такими як сімейство Saleae (мал. 7.5), які підтримують більш високу частоту дискретизації і є більш надійними.
Ми розглянемо використання логічного аналізатора в розділі «Використання логічного аналізатора для ідентифікації контактів UART».
Далі вам потрібно визначити швидкість передачі, яка використовується портами UART, інакше ви не зможете зв’язатися з пристроєм. З огляду на відсутність імпульсів синхронізації, швидкість передачі даних є єдиним способом синхронного зв’язку передавача і приймача.
Найпростіший спосіб визначити правильну швидкість передачі – підключитися до контакту TX і спробувати прочитати дані. Якщо отримані дані нечитабельні, вкажіть наступну можливу швидкість передачі даних, поки дані не стануть читабельними. Для цього можна використовувати адаптер USB-to-UART або багатоцільовий пристрій, наприклад Bus Pirate, у поєднанні з допоміжним скриптом, таким як baudrate.py (https://github.com/devttys) Крейга Хеффнера, щоб допомогти автоматизувати цей процес. Найпоширенішими варіантами трансферу є 9600, 38400, 19200, 57600 і 115200; кожен з них за замовчуванням перевіряється скриптом Хеффнера.
Як і UART, інтерфейси JTAG і SWD на вбудованих пристроях IoT можуть допомогти взяти під контроль пристрій. У цьому розділі ми розглянемо основи цих інтерфейсів і способи взаємодії з ними. У розділі «Злом пристрою за допомогою UART і SWD» ви знайдете докладний приклад взаємодії з SWD.
Оскільки виробники випускали менші та щільніші компоненти, тестувати їх ставало складніше. Інженери використовували процес тестування, званий «шпаргалкою цвяхів», щоб перевірити обладнання на наявність дефектів: вони помістили плату на матрицю підпружинених голчастих контактів і перевірили, чи є електричне з’єднання між потрібними штифтами. Коли виробники стали використовувати багатошарові плати і пакети мікросхем з припойними кульовими контактами, такі пристрої вже не забезпечували доступ до всіх вузлів на платі.
JTAG вирішив цю проблему, представивши більш ефективну альтернативу ліжечку з цвяхів – сканування країв. Сканування країв аналізує певні шаблони, зокрема вбудовані комірки сканування країв і регістри для кожного контакту. Використовуючи комірки сканування країв, інженери можуть легше, ніж будь-коли раніше, перевірити, що певна точка на друкованій платі правильно підключена до іншої точки.
Команди сканування країв
Стандарт JTAG визначає конкретні команди для сканування меж, включаючи наступне:
BYPASS – дозволяє тестувати конкретний чіп без накладних витрат проходження через інші чіпи;
SAMPLE / PRELOAD – бере зразок даних, що надходять на будову та виходять із нього, коли воно знаходиться в нормальному робочому режимі;
EXTEST – встановлює та зчитує стан контактів.
Пристрій має підтримувати ці команди, щоб вважатися сумісним із JTAG. Пристрої можуть підтримувати додаткові команди, такі як IDCODE (для ідентифікації пристрою), INTEST (для внутрішнього тестування пристрою) тощо. Ви можете зіткнутися з цими інструкціями, коли використовуєте такий інструмент, як JTAGulator (див. Ідентифікація контактів JTAG) для ідентифікації контактів JTAG.
Сканування країв включає тестування чотирипровідного тестового порту доступу (TAP), порту загального призначення, який забезпечує доступ до функцій підтримки тестів JTAG, вбудованих у компонент. У ньому використовується 16-ступінчаста машина станів, яка переходить зі стану в стан. Зверніть увагу, що JTAG не визначає жодного протоколу для введення або виходу даних з чіпа.
TAP використовує наступні п’ять сигналів:
Тестовий тактовий вхід (TCK). TCK – це годинники, які визначають, як часто контролер TAP буде виконувати одну дію (іншими словами, переходити в наступний стан в кінцевому пристрої). Тактова частота не вказана в стандарті JTAG. Пристрій, що виконує тест JTAG, може визначити його значення;
Вхід вибору тестового режиму (TMS). TMS керує кінцевим пристроєм. При кожному тактовому імпульсі контролер JTAG TAP пристрою перевіряє напругу на контакті TMS. Якщо напруга нижче певного порогу, сигнал вважається низьким і інтерпретується як 0; якщо напруга вище певного порогу, сигнал вважається високим і інтерпретується як 1;
Введення тестових даних (TDI).Це контакт, який відправляє дані на чіп через осередки сканування. Кожен провайдер відповідає за визначення протоколу зв’язку через цей PIN-код, оскільки JTAG його не визначає. Сигнал, представлений в TDI, дискретизується по висхідному краю TCK;
Вихід тестових даних (TDO).TDO – це контакт, через який надходять дані з чіпа. Згідно зі стандартом, зміни стану сигналу, що передається через ТДО, повинні відбуватися тільки на передній кромці ТСК;
Тестовий вхід скидання (TRST).Необов’язковий TRST скидає кінцевий стан до відомо хорошого стану. Активний на низькому рівні (0). Крім того, якщо TMS утримується на рівні 1 протягом п’яти послідовних тактових циклів, це спричиняє скидання, а також вихід TRST; отже, TRST є необов’язковим.
SWD – це двоконтактний електричний інтерфейс, який працює дуже схоже на JTAG. У той час як JTAG був створений в першу чергу для тестування чіпів і плат, SWD – це протокол, розроблений спеціально для налагодження ARM. З огляду на широке поширення процесорів ARM у світі Інтернету речей, SWD набуває все більшого значення. Якщо ви знайдете інтерфейс SWD, ви майже завжди можете взяти пристрій під повний контроль.
Інтерфейс SWD вимагає двох контактів: двонаправленого сигналу SWDIO, який є еквівалентом тактових контактів TDI, TDO і JTAG, і SWCLK, який є еквівалентом TCK в JTAG. Багато пристроїв підтримують Serial Wire або JTAG Debug Port (SWJ-DP), комбінований інтерфейс JTAG і SWD, який дозволяє підключати зонд SWD або JTAG до цільового пристрою.
Різноманітні інструменти дозволяють нам обмінюватися даними з JTAG і SWD. Популярні інструменти включають чіп Bus Blaster FT2232H, а також будь-які інструменти з чіпом FT232H, такі як патч-дошка Adafruit FT232H, Shikra або Attify Badge. Bus Pirate також може підтримувати JTAG, якщо ви завантажите в нього спеціальну прошивку, але ми не рекомендуємо використовувати цю функцію, оскільки вона може бути нестабільною. Black Magic Probe, спеціалізований інструмент злому JTAG і SWD, має вбудовану підтримку GNU Debugger (GDB), що корисно, оскільки вам не знадобляться додаткові програми, такі як Open On-Chip Debugger (OpenOCD) (див. розділ “Встановлення OpenOCD” нижче). Професійний інструмент налагодження Segger, J-Link Debug Probe, підтримує JTAG, SWD і навіть SPI, але поставляється з власницьким програмним забезпеченням. Якщо ви хочете працювати тільки з SWD, ви можете скористатися таким інструментом, як програматор ST-Link (див. розділ «Злом пристрою за допомогою UART і SWD» нижче).
Додаткові інструменти, їх описи та корисні посилання представлені в розділі «Інструменти для злому Інтернету речей».
Іноді на друкованій платі є маркування, що вказує на розташування роз’єму JTAG (мал 7.6). Але в більшості випадків вам доведеться самостійно знайти роз’єм, а також з’ясувати, які контакти відповідають чотирьом сигналам (TDI, TDO, TCK і TMS).
Ви можете використовувати кілька підходів для ідентифікації контактів JTAG на цільовому пристрої. Найшвидшим, але найдорожчим способом виявлення портів JTAG є використання JTAGulator, пристрою, створеного спеціально для цієї мети (хоча він також може виявляти терморегулятори UART). Інструмент, показаний на мал. 7.7, має 24 канали, які можна підключати до контактів плати. Він виконує сканування методом перебору, видаючи команди IDCODE і BYPASS edge scan для кожної комбінації контактів і чекає відповіді. Коли отримано відповідь, він відображає канал, що відповідає кожному сигналу JTAG, що дозволяє ідентифікувати терморегулятори JTAG.
Щоб використовувати JTAGulator, підключіть його до комп’ютера за допомогою USB-кабелю, а потім підключіться до нього через послідовний порт (наприклад, за допомогою екранної утиліти в Linux). Приклад зв’язку через послідовний порт буде показаний нижче в розділі «Підключення USB до послідовного адаптера». Ви можете подивитися, як творець JTAGulator Джо Гранд демонструє свою розробку: https://www.youtube.com/watch?v=uVIsbXzQOIU/.
Більш дешевим, але набагато повільнішим способом визначення терморегуляторів JTAG є використання утиліти JTAGenum (https://github. com/cyp hunk/JTAGenum/) завантажений на Arduino-сумісний мікроконтролер, такий як модулі STM32F103 Black/Blue Pill, про які ми розповімо в розділі «Злом пристрою за допомогою UART і SWD». За допомогою JTAGenum необхідно спочатку визначити контакти зондового пристрою, який ви будете використовувати для перерахування. Наприклад, для модуля STM32 Blue Pill ми вибрали наступні контакти (але ви можете їх змінити):
#elif defined(STM32) // STM32 bluepill, byte pins[] = { 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17, 18 , 19 , 21 , 22 };
Вам потрібно буде звернутися до схеми контактів пристрою, а потім підключити ці контакти до контрольних точок на цільовому пристрої. Далі вам потрібно прошити код Arduino JTAGenum (https://github.com/cyphunk/ JTAGenum/blob/master/JTAGenum.ino/) на пристрої та зв’язатися з ним через послідовний порт ( команда s просканує комбінації JTAG).
Третім способом ідентифікації контактів JTAG є перевірка друкованої плати на наявність одного з роз’ємів, показаних на мал. 7.8. У деяких випадках друковані плати можуть зручно забезпечувати інтерфейс Tag-Connect, який явно вказує на те, що плата також має роз’єм JTAG. Подивитися, як виглядає цей інтерфейс, можна на сторінці https://www.tag-connect.com/info/. Крім того, перевірка таблиць чіпсетів на друкованій платі може виявити шаблони терморегуляторів, що вказують на інтерфейси JTAG.
У цьому розділі ми будемо використовувати порти UART і SWD мікроконтролера для вилучення даних з пам’яті пристрою і обходу процедури аутентифікації прошитої програми. Для атаки на пристрій ми скористаємося двома інструментами: міні-програматором ST-Link і перехідником USB-UART.
Міні-програматор ST-Link (мал 7.9) дозволяє нам зв’язуватися з цільовим пристроєм за допомогою SWD.
Адаптер USB-UART (мал. 7.10) дозволяє нам зв’язуватися з контактами UART пристрою через USB-порт нашого комп’ютера. Цей адаптер є пристроєм транзисторно-транзисторної логіки (TTL), тобто він використовує напругу 0 і 5 В для представлення логічних рівнів 0 і 1 відповідно. Багато адаптерів використовують мікросхему FT232R, і ви можете легко знайти її, якщо пошукаєте в Інтернеті адаптери USB-послідовно.
Вам знадобиться не менше десяти перемичок, щоб з’єднати пристрої один з одним. Також рекомендуємо придбати макетну плату, на яку можна надійно встановити модуль Black Pill. Ви повинні мати можливість придбати ці апаратні компоненти онлайн. Ми спеціально вибрали компоненти, які тут використовуються, тому що їх легко знайти і вони недорогі. Але якщо потрібна альтернатива програматору ST-Link, можна використовувати Bus Blaster, а в якості альтернативи адаптеру USB-to-UART можна використовувати плату Bus Pirate.
Що стосується програмного забезпечення, ми будемо використовувати Arduino для кодування програми аутентифікації, яку ми атакуємо; для налагодження підійде OpenOCD з GDB. У наступних розділах показано, як налаштувати це середовище тестування та налагодження. Цільовий пристрій STM32F103C8T6 (чорна пігулка)
STM32F103xx — це дуже популярне сімейство недорогих мікроконтролерів, які використовуються в різних сферах застосування на промисловому, медичному та споживчому ринках. Такий мікроконтролер має 32-розрядне ядро RISC ARM Cortex-M3, що працює на частоті 72 МГц, флеш-пам’ять до 1 Мб, статичну оперативну пам’ять (SRAM) до 96 КБ, а також широкий спектр пристроїв введення-виведення та периферійних пристроїв.
Дві версії цього пристрою відомі як Blue Pill і Black Pill (в залежності від кольору плати). Ми будемо використовувати Black Pill (STM32F103C8T6) як цільовий пристрій. Основна відмінність між двома версіями полягає в тому, що Black Pill споживає менше енергії і сильніший за Blue Pill. Пристрій можна придбати в інтернет-магазинах. Рекомендуємо замовити плату з попередньо припаяними гребінчастими роз’ємами і прошитим завантажувачем Arduino: так вам не доведеться паяти роз’єми, і ви зможете використовувати пристрій безпосередньо через USB. Але в цій вправі ми покажемо вам, як завантажити програму для Black Pill без завантажувача Arduino.
На мал. 7.11 показано розташування контактів пристрою. Зверніть увагу, що не всі контакти стійкі до 5 В, тому ми не перевищимо 3,3 В. Якщо ви хочете дізнатися більше про нутрощі мікроконтролера STM32 в цілому, ось дуже хороший довідник: https://www.rlocman.ru/forum/showthread.php?t=22505.
Переконайтеся, що ви не підключаєте вихід 5 В до жодного з контактів 3.3 В, інакше ви, швидше за все, розіб’єте Black Pill.
Почнемо з програмування цільового пристрою за допомогою Arduino Integrated Development Environment (IDE). Arduino – це недорога, проста у використанні електронна платформа з відкритим вихідним кодом, яка дозволяє програмувати мікроконтролери за допомогою мови програмування Arduino. Його IDE містить текстовий редактор для написання коду, менеджер друкованої плати та бібліотеки, вбудовані функції для перевірки, компіляції та завантаження коду на плату Arduino, а також монітор послідовних портів для відображення вихідних даних з апаратного забезпечення.
Завантажити останню версію Arduino IDE можна за адресою https:// www.arduino.cc/en/Main/S oftware/. У нашому прикладі використовується версія 1.8.9 на Ubuntu 18.04.3 LTS, але не має значення, яка у вас операційна система. У Linux завантажте пакет вручну та дотримуйтесь інструкцій на веб-сторінці https://www.arduino.cc/en/guide/linux/. Крім того, якщо ви використовуєте дистрибутив на основі Debian, такий як Kali або Ubuntu, після введення наступної команди в терміналі буде встановлено все необхідне:
# apt-get install arduino
Після установки IDE завантажте останні файли ядра Arduino STM32 з GitHub, помістіть їх в папку hardware в каталозі Arduino sketches і запустіть сценарій установки правил udev.
$ wget https://github.com/rogerclarkmelbourne/Arduino_STM32/archive/master.zip $ unzip master.zip $ cp -r Arduino_STM32-master /home/ithilgore/Arduino/hardware/ $ cd /home/ithilgore/Arduino/hardware/Arduino_STM 32-master/tools/linux $ ./install.sh
Переконайтеся, що ви замінили ім’я користувача після /home/ на ім’я користувача, яке ви використовуєте. Якщо папки з обладнанням не існує, створіть її. Щоб дізнатися, де зберігаються мініатюри Arduino, запустіть Arduino IDE, ввівши arduino в терміналі або клацнувши значок Arduino на робочому столі. Потім виконайте команди File > >Preferences і вкажіть шлях до файлу в поле Розташування скетчбука. У нашому прикладі це /home/<ithilgore>/Arduino.
Вам також потрібно буде встановити 32-розрядну версію libusb-1.0 наступним чином (тому що вона потрібна утиліті st-link, яка поставляється в комплекті з Arduino STM32):
$ sudo apt-get install libusb-1.0-0:i386
Також встановлюємо плати Arduino SAM (Cortex-M3). Це ядра мікроконтролера Cortex-M3. Ядра – це низькорівневі API, які роблять певні мікроконтролери сумісними з вашою Arduino IDE. Ви можете встановити їх внутрішньо та Arduino IDE, натиснувши на Tools > Board > Boards >> Manager. Далі знайдіть дошки SAM. Натисніть Встановити (Встановити) на панелі Arduino SAM Boards (32-розрядна версія ARM Cortex-M3), що з’явиться. Ми використовували версію 1.6.12.
Останні інструкції зі встановлення Arduino STM32 можна знайти за адресою https://github.com/rogerclarkmelbourne/Arduino_STM32/wiki/Installation/.
OpenOCD — це безкоштовний інструмент тестування з відкритим вихідним кодом, який надає доступ JTAG та SWD через GDB до систем ARM, MIPS та RISC-V. Ми будемо використовувати його для налагодження Black Pill. Щоб встановити його на свою систему Linux, введіть такі команди:
$ sudo apt-get install libtool autoconf texinfo libusb-dev libftdi-dev libusb-1.0 $ git clone git://git.code.sf.net/p/openocd/code openocd $ cd openocd $ ./bootstrap $ ./configure --enable-maintainer-mode --disable-werror --enable-buspirate --enable-ftdi $ make $ sudo make install
Зауважте, що ви також встановлюєте libusb-1.0, який потрібно буде ввімкнути підтримку пристроїв Future Technology Devices International (FTDI). Потім скомпілюйте OpenOCD з вихідного коду. Це дозволяє нам увімкнути підтримку пристроїв FTDI та інструменту Bus Pirate.
Щоб дізнатися більше про OpenOCD, будь ласка, зверніться до детального посібника користувача: http://openocd.org/doc/html/index.html.
GDB — портативний налагоджувач, який працює на Unix-подібних системах. Він підтримує безліч цільових процесорів і мов програмування. Ми будемо використовувати GDB для віддаленого моніторингу та модифікації виконання цільової програми.
В Ubuntu вам потрібно буде встановити оригінальні пакунки gdb та gdb-multiarch, які розширюють підтримку GDB на декілька цільових архітектур, включаючи ARM (архітектура, що лежить в основі Black Pill). Ви можете зробити це, ввівши в термінал наступну команду:
$ sudo apt install gdb gdb-multiarch
Тепер напишемо програму на Arduino, яку завантажимо в Black Pill і спробуємо зламати. Під час тестування в реальному світі ви можете не мати доступу до вихідного коду пристрою, але ми показуємо його вам з двох причин. По-перше, ви дізнаєтеся, як код Arduino перетворюється в двійковий файл, який можна завантажити на пристрій. По-друге, коли ви налагоджуєте за допомогою OpenOCD та GDB, ви побачите, як код асемблера відповідає вихідному коду.
Програма в лістингу 7.1 використовує послідовний інтерфейс для відправки і отримання даних. Він імітує процес автентифікації, перевіряючи пароль. Якщо від користувача отримано правильний пароль, виводиться повідомлення «ACCESS GRANTED». В іншому випадку користувачеві буде запропоновано увійти знову.
Лістинг коду 7.1. Програма послідовного зв’язку Arduino для чіпа STM32F103
const byte bufsiz = 32; char buf[bufsiz]; boolean new_data = false; boolean start = true; void setup() { delay(3000); Serial1.begin(9600); } void loop() { if (start == true) { Serial1.print("Login: "); start = false; } recv_data(); if (new_data == true) validate(); } void recv_data() { static byte i = 0; static char last_char; char end1 = '\n'; char end2 = '\r'; char rc; while (Serial1.available() > 0 && new_data == false) { rc = Serial1.read(); // пропускаем следующий символ, если предыдущий был \r или \n и текущий \r или \n if ((rc == end1 || rc == end2) && (last_char == end2 || last_char == end1)) return; last_char = rc; if (rc != end1 && rc != end2) { buf[i++] = rc; if (i >= bufsiz) i = bufsiz - 1; } else { buf[i] = 'const byte bufsiz = 32; char buf[bufsiz]; boolean new_data = false; boolean start = true; void setup() { delay(3000); Serial1.begin(9600); } void loop() { if (start == true) { Serial1.print("Login: "); start = false; } recv_data(); if (new_data == true) validate(); } void recv_data() { static byte i = 0; static char last_char; char end1 = '\n'; char end2 = '\r'; char rc; while (Serial1.available() > 0 && new_data == false) { rc = Serial1.read(); // пропускаем следующий символ, если предыдущий был \r или \n и текущий \r или \n if ((rc == end1 || rc == end2) && (last_char == end2 || last_char == end1)) return; last_char = rc; if (rc != end1 && rc != end2) { buf[i++] = rc; if (i >= bufsiz) i = bufsiz - 1; } else { buf[i] = '\0'; // terminate the string i = 0; new_data = true; } } } void validate() { Serial1.println(buf); new_data = false; if (strcmp(buf, "sock-raw.org") == 0) Serial1.println("ACCESS GRANTED"); else { Serial1.println("Access Denied."); Serial1.print("Login: "); } }'; // terminate the string i = 0; new_data = true; } } } void validate() { Serial1.println(buf); new_data = false; if (strcmp(buf, "sock-raw.org") == 0) Serial1.println("ACCESS GRANTED"); else { Serial1.println("Access Denied."); Serial1.print("Login: "); } }
Почнемо з визначення чотирьох світових змінних. Змінна bufsiz містить кількість байтів для символьного масиву buf, у якому зберігаються байти, що надходять через послідовний порт від користувача або пристрою, що взаємодіє з портом. Змінна new_data – це логічне значення, яке стає істинним щоразу, коли основний цикл програми отримує новий рядок послідовних даних. Логічна змінна start істинна лише на першій ітерації основного циклу, тому вона виводить перше запрошення “Login”. Функція setup() є вбудованою у функції Arduino, яка виконується один раз під час ініціалізації програми. Усередині цієї функції ми ініціалізуємо послідовний інтерфейс (Serial1.begin) зі швидкістю 9600 біт за секунду.
Зверніть увагу, що Serial1 відрізняється від Serial, Serial2 та Serial3 – всі вони відповідають різним контактам UART на Black Pill. Об’єкт Serial1 відповідає контактам A9 та A10. Функція loop() – ще одна вбудована функція Arduino, яка автоматично викликається після setup(), зациклюється та виконує основну програму. Вона постійно викликає функцію recv_data(), яка відповідає за отримання та перевірку даних із послідовного порту.
Коли програма завершила отримання всіх байтів (це відбувається, коли new_data набуває значення true), loop() викликає функцію validate(), яка перевіряє, складають чи отримані байти правильну парольну фразу. Функція recv_data() починається з визначення двох статичних змінних (що означає, що їх значення зберігатиметься між кожним викликом цієї функції): i для ітерації через масив buf та last_char для зберігання останнього символу, який ми прочитали з послідовного порту. Цикл while перевіряє, чи є будь-які байти, доступні для читання з послідовного порту (через Serial1.available), зчитує наступний доступний байт за допомогою Serial1.read та перевіряє, чи є раніше збережений символ (який зберігається в last_char) символом повернення каретки ‘\ r’ або нового рядка ‘\ n’.
Цикл робить це, оскільки може зіткнутися з пристроями, що відправляють повернення каретки, нову рядок або те й інше, щоб завершити свої рядки під час відправлення послідовних даних. Якщо наступний байт не вказує кінець рядка, ми зберігаємо щойно прочитаний байт rc у buf та збільшуємо лічильник i на одиницю. Якщо i досягає кінця довжини буфер, програма більше не зберігає нові байти в буфері.
Якщо прочитаний байт означає кінець рядка, тобто користувач послідовного інтерфейсу, швидше за все, натиснув Enter, ми завершуємо рядок у масиві нулем, скидаємо лічильник i та встановлюємо для new_data значення true. У цьому випадку ми викликаємо функцію validate(), яка друкує отриманий рядок та порівнює його з правильним паролем. Якщо пароль правильний, виводиться ACCESS GRANTED (Доступ дозволений). В іншому випадку виводиться Access Denied («Доступ заборонено»), і користувачеві знову пропонується увійти до системи.
Тепер завантажте програму Arduino в Black Pill. Послідовність кроків залежить від того, чи придбали ви модуль з попередньо встановленим завантажувачем Arduino, але ми розглянемо обидва варіанти. Ще одним способом завантаження програми є використання послідовного адаптера, який дозволяє прошити власний завантажувач (див., наприклад, https://github.com/rogerclarkmelbourne/STM32duino-bootloader/), але ми не будемо тут висвітлювати цей процес – в інтернеті можна знайти безліч ресурсів на цю тему.
У будь-якому випадку, давайте скористаємося програматором ST-Link і запишемо програму на основну флеш-пам’ять. Якщо ви зіткнулися з проблемами запису у флеш-пам’ять, запишіть у вбудовану оперативну пам’ять. Основна проблема такого підходу полягає в тому, що вам доведеться перезавантажувати програму Arduino щоразу, коли ви вимикаєте та вимикаєте пристрій, оскільки вміст SRAM втрачається щоразу, коли пристрій вимикається.
Щоб переконатися, що ви завантажили програму в пам’ять BlackPill, потрібно вибрати правильний режим завантаження. STM32F10xxx має три різні режими завантаження, які можна вибрати за допомогою контактів BOOT1 і BOOT0, як показано в таблиці 1. 7.1. Зверніться до схеми контактів на рис. 7.11, щоб знайти ці два контакти на Black Pill.
Використовуйте перемичку, що йде в комплекті з Black Pill, щоб вибрати режим завантаження. Перемичка – це сукупність невеликих контактів в пластмасовому корпусі, які створюють електричний зв’язок між двома контактами (мал 7.12). Ви можете використовувати контакт перемички для підключення контактів вибору режиму завантаження до VDD (логічний 1) або GND (логічний 0).
Перемичку обох штифтів BOOT0 і BOOT1 Black Pill до GND. Якщо ви хочете записати в SRAM, вам потрібно підключити обидва контакти до VDD.
Щоб завантажити програму, спочатку переконайтеся, що перемички для BOOT0 і BOOT1 підключені до GND. Створіть новий файл в Arduino IDE, скопіюйте і вставте код в Listing 7.1, а потім збережіть файл. Ми використовували назву serial-simple. Натисніть «Інструменти» >> «Дошка» та виберіть «Загальна серія STM32F103C» у розділі STM32F1 «Плати». Потім натисніть «Інструменти» > >Variant і переконайтеся, що STM32F103C8 (20k оперативної пам’яті, 64k Flash ) встановлено; Він повинен бути встановлений за замовчуванням. Переконайтеся, що для методу Tools > >Upload встановлено значення STLink, а в ідеалі для параметра Оптимізувати встановлено значення Debug (-g) . Це гарантує, що налагоджувальні символи з’являться в кінцевому двійковому файлі. Інші параметри не змінюйте.
Якщо на Black Pill у вас був встановлений завантажувач Arduino, ви можете підключити його безпосередньо до комп’ютера через USB-кабель без програматора ST-Link. Потім вкажіть метод завантаження через завантажувач STM32duino замість STLink. Але в навчальних цілях ми будемо використовувати програматор ST-Link, тому вам не потрібно попередньо перепрошивати завантажувач.
Щоб завантажити програму в Black Pill, підключіть до цього модуля програматор ST-Link. За допомогою чотирьох перемичок підключіть контакти SWCLK, SWDIO, GND і 3.3V ST-Link до контактів CLK, DIO, GND, 3.3V Black Pill відповідно. Ці контакти розташовані в нижній частині роз’єму Black Pill. Як це виглядає, показано на мал.7.14 і 7.15.
УВАГА Не підключайте жодних пристроїв до USB-портів, доки підключення не буде завершено. Рекомендується уникати подачі живлення на пристрої при підключенні їх контактів. Таким чином ви уникнете випадкового короткого замикання контактів, яке може призвести до їх виходу з ладу при включенні пристроїв.
Тепер знайдіть контакти UART на пристрої. Вище ми показали, як це зробити за допомогою мультиметра, але тепер ми скористаємося логічним аналізатором, щоб визначити контакт UART TX. Контакт TX передає вихідний сигнал, тому його легко впізнати. Для цієї вправи ви можете використовувати недорогий логічний аналізатор USB HiLetgo з вісьмома каналами, оскільки він сумісний із програмним забезпеченням Saleae Logic, яке ми використовуємо.
Завантажте це програмне забезпечення для вашої операційної системи зі сторінки https://saleae.com/downloads/. (У нашому прикладі використовується версія для Linux.) Розпакуйте пакет у локальну папку, перейдіть до нього в терміналі та введіть наступне:
$ sudo ./Logic
Ця команда відкриє графічний інтерфейс Saleae Logic. Залиште його поки що відкритим.
Переконайтеся, що всі системи, що тестуються, вимкнені при підключенні до них щупів логічного аналізатора, щоб уникнути короткого замикання. У цьому випадку, оскільки Black Pill живиться від програматора ST-Link, тимчасово від’єднайте програматор від USB-порту комп’ютера. Пам’ятайте, що якщо ви відключите Black Pill після завантаження коду Arduino в SRAM замість flash, вам доведеться перезавантажувати код в модуль.
За допомогою перемички з’єднайте один із контактів GND логічного аналізатора з одним із контактів GND Black Pill так, щоб вони мали спільне «заземлення». Потім за допомогою ще двох перемичок з’єднайте канали CH0 і CH1 логічного аналізатора (всі контакти каналів повинні бути позначені) до висновків A9 і A10 Black Pill. Підключіть логічний аналізатор до USB-порту комп’ютера.
В інтерфейсі Saleae ви повинні побачити принаймні пару каналів на лівій панелі, кожен з яких відповідає одному з каналів логічного аналізатора висновків. Ви завжди можете додати більше каналів, якщо ваш логічний аналізатор підтримує їх, щоб ви могли відстежувати більше виходів одночасно. Додайте їх, натиснувши дві стрілки поруч із зеленою кнопкою «Пуск» (Пуск), щоб відкрити Налаштування. Потім ви можете вибрати, скільки каналів ви хочете відобразити, перемкнувши число поруч із кожним каналом.
У налаштуваннях збільште Швидкість на 50 кС/с і Тривалість до 20 секунд. Як правило, частота дискретизації цифрових сигналів повинна бути принаймні в чотири рази вищою за частоту їх повторення. Для повільного послідовного каналу частоти дискретизації 50 кСм/с більш ніж достатньо, хоча більш висока частота дискретизації не завадить. Що стосується тривалості, Тоді достатньо 20 секунд, щоб пристрій увімкнувся і почав передавати дані.
Натисніть кнопку «Пуск», щоб почати захоплення сигналів, і відразу ж увімкніть «Чорну пігулку», підключивши програматор ST-Link до USB-порту. Сеанс триватиме 20 секунд, але ви можете зупинити його в будь-який момент. Якщо дані по каналах не відображаються, спробуйте ввімкнути та знову ввімкнути Black Pill під час сеансу. У якийсь момент ви повинні побачити сигнал, що виходить з каналу, що відповідає контакту A9 (TX). Збільшуйте або зменшуйте масштаб, обертаючи коліщатко миші, щоб бачити їх чіткіше.
Щоб декодувати дані, натисніть кнопку «+» поруч із вкладкою «Аналізатори» в графічному інтерфейсі, виберіть «Асинхронний послідовний» у правій частині панелі, виберіть канал, на якому ви зчитуєте сигнал, і встановіть бітрейт на 9600. Примітка. Якщо ви не знаєте швидкість потоку, ви можете вибрати Використовувати автобод (Використовуйте автоматичне узгодження швидкості) і дозвольте програмі використовувати всю свою магію, щоб знайти потрібне значення. Тепер ви повинні побачити запрошуюче повідомлення Login: Arduino programs у вигляді серії UART пакетів в сигналі, який ви тільки що захопили (мал. 7.13).
Зверніть увагу на те, як пристрій на мал. 7.13 відправляє букву L, яка вказує на початок повідомлення про вхід. Зв’язок починається з незайнятої лінії (з логічним значенням 1). Потім «Чорна пігулка» надсилає початковий біт з логічним значенням 0, за яким слідують біти даних від найнижчого до найвищого. Буква L відповідає коду 0x4C в ASCII, або 00110010 в двійковому форматі, як ви можете бачити в передачі. Нарешті, Black Pill посилає стоп-біт (з логічним значенням 1) перед початком літери «o».
Ми розмістили два маркери часу (А1 і А2 на малюнку 7.13) по обидва боки від одного випадкового біта. Маркери часу – це своєрідні прапорці або мітки, які можна використовувати для вимірювання часу, що минув між будь-якими двома точками даних. Ми виміряли тривалість 100 мкс, що доводить, що швидкість передачі становить 9600 біт/с. (Для передачі одного біта потрібно 1/9600 с, або 0,000104 с, тобто близько 100 мкс.)
Щоб протестувати адаптер USB-UART, підключимо його до нашого комп’ютера. Деякі перехідники, в тому числі і той, який ми використовували, поставляються з перемичкою, попередньо встановленою на контактах RX і TX (малюнок 7.12). Перемичка з’єднує контакти RX і TX, створюючи петлю між ними. Це корисно для тестування адаптера, підключивши його до USB-порту комп’ютера, а потім відкривши програму-емулятор терміналу, наприклад screen або minicom, для цього порту. Спробуйте використовувати емулятор терміналу для надсилання послідовних даних на підключені пристрої. Якщо ви бачите, що в терміналі лунають натискання клавіш, це говорить про те, що адаптер справний. Справа в тому, що ваша клавіатура відправляє символи через USB-порт на контакт TX адаптера; через перемичку символи відправляються на контакт RX, а потім повертаються на комп’ютер через USB-порт.
Підключіть адаптер до комп’ютера (не знімаючи перемички), а потім введіть таку команду, щоб дізнатися, який маркер файлу пристрою призначено адаптеру:
$ sudo dmesg … usb 1-2.1: FTDI USB Serial Device converter now attached to ttyUSB0
Зазвичай, дескриптор /dev/ttyUSB0 призначається apter, якщо до порту не було підключено інші периферійні пристрої. Потім виконайте команду screen і передайте дескриптор файлу як аргумент:
$ screen /dev/ttyUSB0
Щоб вийти з екранного сеансу, натисніть комбінацію клавіш Ctrl+A, а потім \.
Ви також можете вказати бітову швидкість як другий аргумент. Щоб дізнатися поточну швидкість передачі адаптера, введіть наступне:
$ stty -F /dev/ttyUSB0 speed 9600 baud; line =0; …
Цей вихід показує, що адаптер має швидкість передачі 9600 бод.
Переконайтеся, що адаптер працює, а потім зніміть штифт-перемичку, оскільки вам потрібно підключити контакти RX і TX до Black Pill. Рис. На малюнку 7.14 показані необхідні з’єднання.
Підключіть контакт RX адаптера до контакту TX на Black Pill (контакт A9 у цьому прикладі). Потім підключіть контакт TX адаптера до контакту RX (A10). Важливо використовувати контакти А9 і А10, тому що вони відповідають інтерфейсу Serial1, який ми використовували в коді Arduino.
USB-адаптер повинен бути підключений до того ж проводу «заземлення», що і Balck Pill, оскільки пристрої використовують GND як точку відліку рівнів напруги. Вихід Clear to Send (CTS) також повинен бути підключений до лінії GND, оскільки він вважається активним на низькому рівні (тобто на логічному рівні 0). Якщо цей контакт не підключений до GND, він матиме високий рівень і адаптер не зможе відправити байти на Black Pill.
Підключивши Black Pill, ST-Link і USB-адаптер, підключіть ST-Link до USB-порту комп’ютера. Потім підключіть адаптер до USB-порту. Рис. На малюнку 7.15 представлений приклад конструкції.
УВАГА Зверніть увагу, що модуль Black Pill не підключений до жодного USB-порту. Замість цього він працює через програматор ST-Link. Якщо підключити Black Pill безпосередньо до будь-якого USB-порту, модуль або порт може вийти з ладу.
Тепер, коли тестова схема готова, поверніться до середовища розробки Arduino. Увімкніть докладне виведення, натиснувши File > Preferences і встановивши прапорець Показувати детальне виведення під час: компіляції. Потім натисніть Sketch > Upload >, щоб скомпілювати програму і завантажити її в Black Pill.
Оскільки ми включили детальний висновок в Arduino IDE, компіляція і завантаження програми повинні дати вам багато інформації про процес, включаючи тимчасову директорію, в якій зберігаються проміжні файли, необхідні для компіляції (рисунок 7.16).
У Linux цей каталог зазвичай виглядає як /tmp/arduino_build_336697, де останнє число є випадковим ідентифікатором (у вас, очевидно, буде інший), який змінюється з новими збірками. При складанні програми зверніть увагу на цей каталог: він вам знадобиться пізніше.
На цьому етапі відкрийте консоль Serial Monitor, натиснувши Інструменти > Serial >Monitor. Serial Monitor — це спливаюче вікно, яке може надсилати та отримувати дані UART до та з модуля Black Pill. За функціями він схожий на екранний інструмент, який використовувався раніше, але для зручності вбудований в Arduino IDE. Натисніть Інструменти > >Порт, щоб переконатися, що ви вибрали порт USB, до якого підключено послідовний адаптер USB. Переконайтеся, що швидкість передачі даних у налаштуваннях вікна Serial Monitor становить 9600, як ми вказали в коді. Після цього ви повинні побачити запит Login: нашої програми Arduino. Введіть зразок тексту для тестування програми. На мал. 7.17 наведено приклад сеансу.
Якщо ви введете будь-який текст, відмінний від sock-raw.org, ви отримаєте повідомлення про відмову в доступі. В іншому випадку з’явиться повідомлення ACCESS GRANTED.
Тепер настав час для основної вправи: налагодження та злому Black Pill. Якщо ви виконали всі попередні кроки, у вас повинна вийти повністю робоча налагоджувальна середовище, а Black Pill повинна містити написану нами програму Arduino.
Ми будемо використовувати OpenOCD для зв’язку з Black Pill через SWD через програматор ST-Link. Давайте зробимо це, щоб відкрити віддалений сеанс діагностики за допомогою GDB. Потім, використовуючи GDB, пройдіться по інструкціях програми і обійдіть її перевірку аутентифікації.
Давайте запустимо OpenOCD як сервер. Він нам потрібен для зв’язку з Black Pill через SWD. Щоб запустити його з ядром Black Pill STM32F103 за допомогою ST-Link, ми повинні вказати два відповідні конфігураційні файли за допомогою ключа -f:
$ sudo openocd -f /usr/local/share/openocd/scripts/interface/stlink.cfg -f /usr/local/share/ openocd/scripts/targets/stm32f1x.cfg [sudo] password for ithilgore: Open On-Chip Debugger 0.10.0+dev-00936-g0a13ca1a (2019-10-06-12:35) Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'. Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD Info : Listening on port 6666 for tcl connections Info : Listening on port 4444 for telnet connections Info : clock speed 1000 kHz Info : STLINK V2J31S7 (API v2) VID:PID 0483:3748 Info : Target voltage: 3.218073 Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints Info : Listening on port 3333 for gdb connections
Ці файли налаштувань допомагають OpenOCD зрозуміти, як взаємодіяти з пристроями, які використовують JTAG і SWD. Якщо ви встановили OpenOCD з джерела, як показано вище, ці файли налаштувань мають бути розташовані у каталозі /usr/local/share/ openocd. Після виконання команди OpenOCD почне приймати локальні з’єднання Telnet на порту TCP 4444 і з’єднання GDB на порту TCP 3333.
На цьому етапі ми відкриємо сеанс OpenOCD через Telnet і почнемо надсилати команди Black Pill через SWD. В іншому терміналі введіть:
$ telnet localhost 4444 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. Open On-Chip Debugger > reset init target halted due to debug-request, current mode: Thread xPSR: 0x01000000 pc: 0x08000538 msp: 0x20005000 > halt > flash banks #0 : stm32f1x.flash (stm32f1x) at 0x08000000, size 0x00000000, buswidth 0, chipwidth 0 > mdw 0x08000000 0x20 0x08000000: 20005000 08000539 080009b1 080009b5 080009b9 080009bd 080009c1 08000e15 0x08000020: 08000e15 08000e15 08000e15 08000e15 08000e15 08000e15 08000e15 08000e35 0x08000040: 08000e15 08000e15 08000e15 08000e15 08000e15 08000e15 08000a11 08000a35 0x08000060: 08000a59 08000a7d 08000aa1 080008f1 08000909 08000921 0800093d 08000959 > dump_image firmware-serial.bin 0x08000000 17812 dumped 17812 bytes in 0.283650s (61.971 KiB/s)
Команда reset init зупиняє цільовий пристрій та виконує повне скидання, виконуючи скрипт reset-init. Цей скрипт є обробником подій, який виконує такі завдання, як натрійка тактових імпульсів і тактової частоти JTAG. Ви можете знайти приклади цих обробників, якщо перегляньте файли .cfg каталогу openocd/scripts/target/. Команда halt надсилає запит на зупинку, щоб пристрій зупинився. і перейшов у режим налагодження.
Команда flash banks виводить однорядкове зведення кожної області флеш-пам’яті, яка була вказано у файлі OpenOCD .cfg (у цьому випадку stm32f1x.cfg). Вона виводить на друк основну флеш-пам’ять Black Pill, яка починається з адреси 0x08000000. Цей крок важливий, оскільки він допоможе вам визначити, з якого сегменту пам’яті потрібно зробити дамп мікропрограми. Зауважте, що іноді значення розміру вказується неправильно.
Найкращим ресурсом для цього кроку залишається перегляд таблиць даних. Потім ми надсилаємо 32-бітну команду доступу до пам’яті mdw, починаючи зі знайденого раніше адреси, щоб прочитати та відобразити перші 32 байти флеш-пам’яті. Нарешті вивантажуємо цільову пам’ять із цієї адреси (всього 17 812 байт) і зберігаємо її у файлі firmware-serial.bin у локальному каталозі нашого комп’ютера. Ми отримали номер 17 812, запросивши обсяг програмного файлу Arduino, завантаженого в флеш пам`ять. Для цього введіть наступну команду з тимчасового каталогу компілятора Arduino:
/tmp/arduino_build_336697 $ stat -c '%s' serial-simple.ino.bin 17812
Потім ви можете скористатися інструментами порівняння, такими як colordiff і xxd, щоб побачити, чи є якісь відмінності між файлом firmware-serial.bin який ми вивантажили з флеш-пам’яті.bin і файлом serial-simple.ino який ми завантажили через Arduino IDE. Якщо ви зберегли рівно стільки байтів, скільки і програма Arduino, різниці у виводі colordiff бути не повинно:
$ sudo apt install colordiff xxd $ colordiff -y <(xxd serial-simple.ino.bin) <(xxd firmware-serial.bin) | less
Ми заохочуємо вас поекспериментувати з іншими командами OpenOCD; Усі вони задокументовані на сайті інструменту. Ось корисна команда, яку можна спробувати:
> flash write_image erase custom_firmware.bin 0x08000000
Ви можете використовувати його для запису нової прошивки.
Давайте налагодимо та змінимо потік виконання програми Arduino за допомогою GDB. Якщо сервер OpenOCD вже запущено, ми можемо розпочати віддалений сеанс GDB. Щоб полегшити собі завдання, ми будемо використовувати файл у форматі Executable and Linkable File (ELF), створений під час компіляції програми Arduino. ELF — це стандартний формат файлів для виконуваних файлів, об’єктного коду, спільних бібліотек і дампів ядра в Unix-подібних системах. У цьому випадку він діє як проміжний файл під час компіляції.
Перейдіть до тимчасового каталогу, створеного під час компіляції. Переконайтеся, що ви змінили випадкову частину назви каталогу на значення, отримане з вашої власної компіляції Arduino. Потім, припускаючи, що ваша програма Arduino має назву serial-simple, запустіть віддалений сеанс GDB за допомогою команди gdb-multiarch з наведеними нижче аргументами:
$ cd /tmp/arduino_build_336697/ $ gdb-multiarch -q --eval-command="target remote localhost:3333" serial-simple.ino.elf Reading symbols from serial-simple.ino.elf...done. Remote debugging using localhost:3333 0x08000232 in loop () at /home/ithilgore/Arduino/serial-simple/serial-simple.ino:15 15 if (start == true) { (gdb)
Ця команда відкриє сеанс GDB і використає локальний виконуваний файл ELF (названий serial-simple.ino.elf), створений Arduino під час компіляції для налагоджувальних символів. Налагоджувальні символи — це примітивні типи даних, які дозволяють налагоджувачам отримувати доступ до такої інформації, як змінні та імена функцій, із вихідного коду двійкового файлу.
Тепер ви можете використовувати цей термінал для надсилання команд GDB. Почніть з введення команди info functions, щоб переконатися, що символи дійсно завантажені:
(gdb) info functions All defined functions: File /home/ithilgore/Arduino/hardware/Arduino_STM32-master/STM32F1/cores/maple/HardwareSerial. cpp: HardwareSerial *HardwareSerial::HardwareSerial(usart_dev*, unsigned char, unsigned char); int HardwareSerial::available(); … File /home/ithilgore/Arduino/serial-simple/serial-simple.ino: void loop(); void recv_data(); void setup(); void validate();
Тепер давайте поставимо точку зупинки функції validate(), тому що з назви випливає, що вона виконує якусь перевірку, яка може бути пов’язана з аутентифікацією.
(gdb) break validate Breakpoint 1 at 0x800015c: file /home/ithilgore/Arduino/serial-simple/serial-simple.ino, line 55.
Оскільки налагоджувальна інформація, записана у двійковому файлі ELF, повідомляє GDB, які вихідні файли були використані для його створення, ми можемо використовувати команду list для друку частин вихідного коду програми. Ця зручна функція рідко надається в реальних сценаріях зворотного проектування, де вам доведеться покладатися на команду дизасемблера, яка показує код асемблера замість вихідного коду. Ось результат обох команд:
(gdb) list validate, 55 void validate() { 56 Serial1.println(buf); 57 new_data = false; 58 59 if (strcmp(buf, "sock-raw.org") == 0) 60 Serial1.println("ACCESS GRANTED"); 61 else { 62 Serial1.println("Access Denied."); 63 Serial1.print("Login: "); 64 } (gdb) disassemble validate Dump of assembler code for function validate(): 0x0800015c <+0>: push {r3, lr} 0x0800015e <+2>: ldr r1, [pc, #56] ; (0x8000198 <validate()+60>) 0x08000160 <+4>: ldr r0, [pc, #56] ; (0x800019c <validate()+64>) 0x08000162 <+6>: bl 0x80006e4 <Print::println(char const*)> 0x08000166 <+10>: ldr r3, [pc, #56] ; (0x80001a0 <validate()+68>) 0x08000168 <+12>: movs r2, #0 0x0800016a <+14>: ldr r0, [pc, #44] ; (0x8000198 <validate()+60>) 0x0800016c <+16>: ldr r1, [pc, #52] ; (0x80001a4 <validate()+72>) 0x0800016e <+18>: strb r2, [r3, #0] 0x08000170 <+20>: bl 0x8002de8 <strcmp> 0x08000174 <+24>: cbnz r0, 0x8000182 <validate()+38> 0x08000176 <+26>: ldr r0, [pc, #36] ; (0x800019c <validate()+64>) …
Якщо у вас є тільки код асемблера, імпортуйте файл (в даному випадку serialsimple.ino.elf) в декомпілятор, подібний до тих, що надаються Ghidra або IDA Pro. Це вам дуже допоможе, оскільки переведе код асемблера на мову C, яка набагато простіша для читання (мал 7.18).
Якщо у вас є лише шістнадцятковий файл (наприклад, firmware-serial.bin) в результаті скидання прошивки з флеш-пам’яті, вам спочатку доведеться розібрати його за допомогою приладів ARM наступним чином:
$ arm-none-eabi-objdump -D -b binary -marm -Mforce-thumb firmware-serial.bin > output.s
Файл output.s міститиме асемблерний код. Тепер давайте подивимося, як ми можемо обійти процес простий автентифікації нашої мети. Щоб продовжити нормальне виконання програми, введіть continue (або, для стислості, c):
(gdb) continue Continuing.
Тепер програма очікує послідовного введення. Відкрийте монітор послідовного порту в Arduino IDE, як ми це робили раніше, введіть зразок пароля, наприклад test123, і натисніть Enter. На терміналі GDB ви повинні побачити, що спрацьовує точка зупинки для функції перевірки. Відтепер ми зробимо так, щоб GDB автоматично відображав наступну інструкцію, яка буде виконуватися при кожній зупинці програми: виконавши команду display/i$pc. Потім ми поступово перейдемо до однієї машинної інструкції за допомогою команди stepi, поки не дійдемо до виклику strcmp. Коли ми дійдемо до виклику Print::p rintln, давайте обійдемо його, тому що це нас не стосується в цьому контексті, як показано в лістингу 7.2:
Лістинг коду 7.2. Покрокова реалізація функції перевірки програми GDB
Breakpoint 1, validate () at /home/ithilgore/Arduino/serial-simple/serial-simple.ino:55 55 void validate() { (gdb) display/i $pc 1: x/i $pc => 0x800015c <validate()>: push {r3, lr} (gdb) stepi halted: PC: 0x0800015e 56 Serial1.println(buf); 3: x/i $pc => 0x800015e <validate()+2>: ldr r1, [pc, #56] ; (0x8000198 <validate()+60>) (gdb) stepi halted: PC: 0x08000160 0x08000160 56 Serial1.println(buf); 1: x/i $pc => 0x8000160 <validate()+4>: ldr r0, [pc, #56] ; (0x800019c <validate()+64>) (gdb) stepi halted: PC: 0x08000162 0x08000162 56 Serial1.println(buf); 1: x/i $pc => 0x8000162 <validate()+6>: bl 0x80006e4 <Print::println(char const*)> (gdb) next halted: PC: 0x080006e4 57 new_data = false; 1: x/i $pc => 0x8000166 <validate()+10>: ldr r3, [pc, #56] ; (0x80001a0 <validate()+68>) (gdb) stepi halted: PC: 0x08000168 0x08000168 57 new_data = false; 1: x/i $pc => 0x8000168 <validate()+12>: movs r2, #0 (gdb) stepi halted: PC: 0x0800016a 59 if (strcmp(buf, "sock-raw.org") == 0) 1: x/i $pc => 0x800016a <validate()+14>:ldr r0, [pc, #44] ; (0x8000198 <validate()+60>) (gdb) stepi halted: PC: 0x0800016c 0x0800016c 59 if (strcmp(buf, "sock-raw.org") == 0) 1: x/i $pc => 0x800016c <validate()+16>: ldr r1, [pc, #52] ; (0x80001a4 <validate()+72>) (gdb) stepi halted: PC: 0x0800016e 57 new_data = false; 1: x/i $pc => 0x800016e <validate()+18>: strb r2, [r3, #0] Уязвимости портов UART, JTAG и SWD 225 (gdb) stepi halted: PC: 0x08000170 59 if (strcmp(buf, "sock-raw.org") == 0) 1: x/i $pc => 0x8000170 <validate()+20>: bl 0x8002de8 <strcmp> (gdb) x/s $r0 0x200008ae <buf>: "test123" (gdb) x/s $r1 0x8003a48: "sock-raw.org"
Останні дві команди GDB (x/s $r0 та x/s $r1) відображають вміст регістрів r0 та r1 у вигляді рядків. Ці регістри повинні містити два аргументи, передані функції strcmp() Arduino, тому що, згідно стандарту виклику процедур ARM (APCS), перші чотири аргументи будь-якої функції передаються в перші чотири регістри ARM r0, r1, r2, r3. Це означає, що регістри r0 та r1 містять адреси рядки test123 (який ми вказали як пароль) та рядок дійсного пароля sock-raw.org, для якого виконується порівняння.
Ви можете відобразити всі регістри в GDB у будь-який час, виконавши команду info registers (або, для стислості, i r). Тепер ми можемо оминути аутентифікацію кількома способами. Найпростіший спосіб встановити значення r0 для sock-raw.org перед тим, як виконання досягне виклику strcmp(). Ви можете легко зробити це, ввівши наступну команду GDB:
set $r0="sock-raw.org"
Крім того, якщо ми не знаємо правильного рядкового значення парольної фрази, ми можемо обійти автентифікацію, обманом змусивши програму подумати, що strcmp() успішно. Для цього ми змінимо значення, що повертається strcmp(), як тільки воно повернеться. Зауважте, що strcmp() повертає 0 у разі успіху.
Ми можемо змінити значення, що повертається, за допомогою команди cbnz, яка призначена для порівняння та переходу до ненульового значення. Він перевіряє регістр в лівому операнді і, якщо він не дорівнює нулю, виконує розгалуження до призначення, вказаного в правому операнді. У цьому випадку регістром є r0, і він містить значення, що повертається strcmp():
0x08000170 <+20>: bl 0x8002de8 <strcmp> 0x08000174 <+24>: cbnz r0, 0x8000182 <validate()+38>
Тепер ми перейдемо до функції strcmp(), зробивши ще один koman do stepi, коли досягнемо його. Потім ми можемо вийти з нього, виконавши команду завершення. Безпосередньо перед виконанням команди cbnz змініть значення r0 на 0, що вказує на те, що strcmp() вдався:
(gdb) stepi halted: PC: 0x08002de8 0x08002de8 in strcmp () 3: x/i $pc => 0x8002de8 <strcmp>: orr.w r12, r0, r1 (gdb) finish Run till exit from #0 0x08002de8 in strcmp () 0x08000174 in validate () at /home/ithilgore/Arduino/serial-simple/serial-simple.ino:59 59 if (strcmp(buf, "sock-raw.org") == 0) 3: x/i $pc => 0x8000174 <validate()+24>: cbnz r0, 0x8000182 <validate()+38> (gdb) set $r0=0 (gdb) x/x $r0 0x0: 0x00 (gdb) c Continuing.
Коли ми це зробимо, наша програма не буде переходити на адресу пам’яті 0x8000182. Замість цього він продовжить виконувати інструкції відразу після cbnz. Якщо тепер ви дозволите продовжити роботу програми, ввівши continue, ви побачите повідомлення ACCESS GRANTED на послідовному моніторі Arduino, яке вказує на те, що ви успішно зламали програму. Є й інші способи зламати програму, але ми залишимо вам експериментувати самостійно.
У цьому розділі ви дізналися про UART, JTAG і SWD, а також про те, як ви можете використовувати ці протоколи для отримання повного доступу до свого пристрою. Більша частина глави була присвячена практичним вправам, де в якості цільового пристрою використовувався мікроконтролер STM32F103C8T6 (Black Pill). Ви дізналися, як розробити та запустити просту програму Arduino, яка виконує дуже просту процедуру аутентифікації через UART. Потім підключіться до пристрою за допомогою послідовного адаптера USB-UART. Ми використовували програматор ST-Link для доступу до SWD на цільовому пристрої через OpenOCD і, нарешті, вдалися до GDB для динамічного обходу функції аутентифікації.
Використання UART, а особливо JTAG і SWD, майже завжди означає, що ви можете мати повний доступ до пристрою, тому що ці інтерфейси були розроблені, щоб надати виробникам повні права налагодження для цілей тестування. Дізнайтеся, як максимально використовувати їхній потенціал, і ви дізнаєтеся, як зламувати пристрої IoT набагато ефективніше!
Ми використовували матеріали з книги “The Definitive Guide to Attacking the Internet of Things ”, яку написали Фотиос Чанцис, Иоаннис Стаис,
Паулино Кальдерон, Евангелос Деирменцоглу и Бо Вудс.