Не сильно хорошая, но все же защита от бездумного копирования устройства. А может быть и еще что-то. В общем, идея такая: зашифровать некоторые критические функции, без которых устройство работать не будет, хитрожопые алгоритмы или математика какая-нибудь. Причем, желательно не потерять удобную сборку и отладку, и использовать желательно без всяких указателей. Я использую arm-none-eabi-gcc в качестве тулчейна и CMake как систему сборки. Поэтому все нижесказанное относится именно к этой связки и для других компиляторов-сборщиков придется немного перепилить.
Итак постановка задачи: Нужно удобным образом пометить какие-то функции, которые будут зашифрованы при компиляции и расшифрованы на лету при работе процессора. Решение простое, поступим так же как компилятор поступает с инициализированными данными: вынесем нужные функции в отдельную секцию, у которой разделим LMA (Load Memory Address – адрес, по которому линковщик положит данные в скомпилированной прошивки и VMA (Virtual Memory Address – адрес, который будет использоваться при обращении к символам из этой секции). Делается это через скрипт линковки, добавлением секции по образу и подобию .data
:
1 2 3 4 5 6 7 | __xsectext = __etext + (__data_end__ - __data_start__); .xsec : AT ( __xsectext ) { PROVIDE(__xsec_start__ = .); KEEP(*(.xsec)); PROVIDE(__xsec_end__ = .); } > RAM |
Этот кусок в разделе SECTIONS скажет линковщику создать символ __xsectext
с LMA адресом (и записать в него правильный адрес) и создать секцию .xsec
с нужным VMA адресом, экспортировать адрес начала и конца секции и положить эту секцию в регион RAM (это у меня так оперативка в скрипте называется, что характерно). Теперь все функции с атрибутом section(«.xsec»), линковщик положит в эту секцию.
1 | uint32_t secure_foo()__attribute__ ((section (".xsec"))); |
Осталось при запуске (или в любой другой удобный момент) скопировать нужное количество байт из LMA адрес в VMA адрес, как-то обработав при этом. Пишем функцию:
1 2 3 4 5 6 7 | void prepare_xsec() { extern uint8_t __xsec_start__; extern uint8_t __xsec_end__; extern uint8_t __xsectext; memcpy(&__xsec_start__, &__xsectext, &__xsec_end__ - &__xsec_start__); } |
Это пока просто копирование, без всяких расшифровок. Но и секция у нас еще не зашифрована. Добавляем вызов prepare_xsec()
перед первым использованием функций содержащихся в ней – и проверяем как работает. А если все сделано правильно – то работает.
Теперь приступим к шифрации. Я просто переверну младший полубайт (out = in ^ 0x0F)
, этого более, чем достаточно для проверки – а дальше уже полный полет фантазии, серийник использовать, да хоть AES ключи с удаленного сервера или еще как-то.
Расшифровщик пишется просто, добавляем после memcpy
:
1 2 | uint8_t* ptr = &__xsec_start__; while(rv < &__xsec_end__) *(ptr ++) ^= 0x0F; |
и готово.
Теперь будем шифровать. Для этого нужно:
- Выковырять нужную секцию из elf
- Зашифровать её
- Запихнуть обратно.
Пункты 1 и 3 умеет (как ни странно) objcopy:
Вытаскиваем:
1 | arm-none-eabi-objcopy -j .xsec -Obinary firmware.elf xsec.bin |
Шифруем:
1 | xotor -i xsec.bin -o xsec_xor.bin -x 0x0F |
и запихиваем обратно (ну почти обратно, лучше сделаем еще один elf)
1 | arm-none-eabi-objcopy --update-section .xsec=xsec_xor.bin firmware.elf crypted.elf |
после чего с crypted.elf можно делать что угодно – выковыривать hex или bin, отлаживаться итд. Ну и осталось все это автоматизировать:
к CMakeLists.txt
моего шаблона добавляем
1 2 3 | ADD_CUSTOM_COMMAND(TARGET ${CMAKE_PROJECT_NAME}.elf POST_BUILD COMMAND ${CMAKE_OBJCOPY} ARGS -j .xsec -Obinary ${CMAKE_PROJECT_NAME}.elf xsec.bin ) ADD_CUSTOM_COMMAND(TARGET ${CMAKE_PROJECT_NAME}.elf POST_BUILD COMMAND xotor ARGS -i xsec.bin -o xsec_xor.bin -x 0x0F) ADD_CUSTOM_COMMAND(TARGET ${CMAKE_PROJECT_NAME}.elf POST_BUILD COMMAND ${CMAKE_OBJCOPY} ARGS --update-section .xsec=xsec_xor.bin ${CMAKE_PROJECT_NAME}.elf crypted.elf ) |
Ну и для запуска (в Clion, например) указываем нужный executable файл. Готово.
Что можно еще? Добавить проверку, какая версия запускается шифрованная или нужно просто скопировать:
Добавим маркер в начало секции:
1 2 3 4 5 6 7 | .xsec : AT ( __xsectext ) { PROVIDE(__xsec_start__ = .); KEEP(*(.xsec_marker)); KEEP(*(.xsec)); PROVIDE(__xsec_end__ = .); } > RAM |
Сделаем этот самый маркер и напишем функцию проверки:
1 2 3 4 5 | #define SEC_MARKER (0xBEED) volatile static uint32_t __attribute__ ((section (".xsec_marker"))) xsec_marker = SEC_MARKER; uint8_t check_crypt() { return (uint8_t)(xsec_marker != SEC_MARKER ? 1 : 0); } |
ну и расшифровывать теперь будем только по необходимости
1 | if(check_crypt()) while(ptr < &__xsec_end__) *(ptr ++) ^= 0x0F; |
вроде бы все. Да, тузла xotor
– просто ксорит входной файл с заданным значением и записывает в выходной. Сходу не вспомнил, чем можно воспользоваться, набросал самостоятельно.