TT#7 Read Out Protection w STM32

Na rynku elektronicznym znajduje się wiele urządzeń, które mają podobny wygląd i spełniają zbliżone zadanie. Bardzo często o ich unikalności świadczy nie tyle sam sprzęt (płytka, dobrane komponenty), co oprogramowanie. To ono pozwala klientowi zdecydować, który produkt sprawdza się lepiej, jest bardziej niezawodny, ma więcej ciekawych funkcjonalności. W jaki sposób chronić nasze oprogramowanie przed ingerencją z zewnątrz?

Materiał jest częścią Tips and Tricks – serii artykułów dotyczących ciekawostek o STM32

czyli przydatnych, choć rzadko opisywanych elementów z ekosystemu STM32.

Realizujemy projekt urządzenia. Kod napisany, przeszedł gruntowne testy. Czas rozpocząć produkcję. Tylko co zrobić, aby nasz program nie został przez kogoś odczytany z pamięci mikrokontrolera i skopiowany? Z jednej strony odczyt pamięci na pierwszy rzut oka może komuś niewiele dać – w końcu są to tylko niezrozumiałe dla człowieka ciągi liczb. Poza tym, że ktoś może wgrać kod na inny układ, a to może być już spora szkoda. Są także osoby, które specjalizują się w dekodowaniu kodu maszynowego. Podobno są na to metody – to wyższa szkoła inżynierii wstecznej. Możemy jednak znacznie utrudnić działanie skierowane przeciwko naszemu produktowi. Jedną z metod jest blokowanie przed odczytem pamięci (Read Protection).

Opis konfiguracji ochrony przed odczytem pamięci zostanie przedstawiony na przykładzie mikrokontrolera z serii STM32L4.

Mechanizm blokowania przed odczytem pamięci wbudowany w STM32 jest aktywowany przez ustawienie bajtu konfiguracyjnego o nazwie RDP i zresetowanie układu. RDP chroni pamięć główną Flash, bajty opcyjne (Option Bytes), rejestry zapasowe (RTC_BKPxR w RTC) oraz SRAM2. STM32 oferuje trzy stopnie ochrony: 0, 1 lub 2.

Poziom 0 (No protection) oznacza brak ochrony pamięci przed odczytem. Jest to stan domyślny, w którym można wykonywać wszystkie operacje – odczyt, programowanie, czyszczenie pamięci. Stan ten jest aktywny, gdy w rejestrze RDP jest wpisana wartość 0xAA.

Poziom 1 (Read protection) oznacza, że program może korzystać z zasobów mikrokontrolera (wykonuje się „normalnie”, ma pełen dostęp do pamięci Flash, SRAM2, rejestrów zapasowych oraz konfiguracyjnych). W trybie debugowania lub gdy kod jest uruchamiany z pamięci RAM lub programu ładującego (bootloader), pamięć główna Flash, rejestry zapasowe (RTC_BKPxR w RTC) i SRAM2 są całkowicie niedostępne. W tych trybach dostęp do pamięci Flash w trybie odczytu lub zapisu generuje błąd magistrali i przerwanie Hard Fault. Poziom 1 jest aktywny, gdy w rejestrze RDP jest wpisana wartość inna niż 0xAA i 0xCC – najczęściej jest to 0xBB, co pozwala zachować analogiczność do pozostałych wartości. W tym trybie mamy możliwość połączenia się z układem, odczytania Option Bytes, ale nie mamy dostępu do zawartości pamięci Flash.

Poziom 2 (No debug) to najmocniejszy poziom zabezpieczeń przed odczytem. Na tym poziomie wdrożone są zabezpieczenia poziomu 1, a ponadto port debugowania, rozruch z pamięci RAM i rozruch z pamięci systemowej nie są już dostępne. Załadowany do pamięci przed zmianą rejestru RDP program działa normalnie, ma dostęp do pamięci Flash, jednak na bajtach konfiguracyjnych (Option Bytes) może wykonywać tylko operację odczytu. Ustawienie poziomu 2 to operacja nieodwracalna, ponieważ nie mamy już możliwości programowania Option Bytes. Poziom 2 jest aktywny, gdy w rejestrze RDP jest wpisana wartość 0xCC.

Jak już wcześniej wspomniałem, zmiana poziomu ochrony przed zapisem odbywa się poprzez zaprogramowanie rejestru RDP. Z poziomu 0 na 1 zmiany możemy dokonać w każdej chwili i bez żadnych konsekwencji. Poziom 2 możemy ustawić zarówno z poziomu 0, jak i 1, operacja ustawienia poziomu 2 jest nieodwracalna, co oznacza, że nie będziemy mieli już możliwości modyfikacji załadowanego programu. W dokumentacji do STM32 znajdziemy również informację, że producent również nie ma możliwości odwrócenia tej operacji, dlatego należy się poważnie zastanowić przed jej wykonaniem – wiąże się to z zablokowaniem mikrokontrolera na zawsze.

Zmiana poziomu ochrony z wyższego na niższy (z 1 na 0) wiąże się z wywołaniem operacji erase, czyli czyszczenia pamięci mikrokontrolera. Po wpisaniu do rejestru RDP wartości 0xAA mamy ponownie pełen dostęp do pamięci Flash, jednak tracimy wszelkie informacje, które były w niej zawarte. Proces zmiany poziomu RDP przedstawiony został obrazowo na poniższej grafice.

Poziom ochrony możemy zmienić m.in. za pomocą aplikacji STM32CubeProgrammer. W tym celu po połączeniu z mikrokontrolerem przechodzimy do zakładki OB (Option Bytes). Wybieramy wartość w polu Value i konfigurujemy za pomocą Apply.

Poziom RDP możemy również zmienić z poziomu kodu programu. Wykorzystujemy w tym celu strukturę FLASH_OBProgramInitTypeDef. Przed zmianą Option Bytes powinniśmy odblokować dostęp do pamięci Flash, a po zmianie ponownie zablokować. Pozwoli to uniknąć niepożądanych operacji w trakcie konfiguracji. Poniżej instrukcje potrzebne do wykonania takiej operacji.

FLASH_OBProgramInitTypeDef obInit;

HAL_FLASH_Unlock();
HAL_FLASH_OB_Unlock();

obInit.OptionType = OPTIONBYTE_RDP;
obInit.RDPLevel  = OB_RDP_LEVEL_1;

HAL_FLASHEx_OBProgram(&obInit);
HAL_FLASH_OB_Launch();

HAL_FLASH_OB_Lock();
HAL_FLASH_Lock();

Rejestr RDP pozwala nam zatem na zmianę sposobu dostępu do pamięci Flash, a co za tym idzie zabezpieczenie pamięci mikrokontrolera przed odczytem przez kogoś z zewnątrz.

Poziom 1 pozwala na swobodny powrót do normalnego dostępu do pamięci mikrokontrolera, tracąc jedynie program w niej umieszczony, dzięki czemu mamy nadal możliwość np. aktualizacji oprogramowania.

Poziom 2 wiąże się z całkowitym zablokowaniem pamięci układu bez możliwości cofnięcia tej operacji. Powinien być zatem używany tylko w wyjątkowych sytuacjach, gdy mamy pewność, że już nigdy nie będziemy chcieli zmieniać wsadu mikrokontrolera, a zależy nam na wyjątkowo mocnej ochronie zawartości pamięci Flash.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *