#c #arm
#c #arm
Вопрос:
Я пытаюсь применить тип атаки по побочному каналу, о которой я читал в этой статье, которая пытается определить состояние выполнения по различиям в задержках IRQ на MCU с процессором cortex M4. Атака тщательно прерывает инструкции, которые выполняются сразу после перехода, и измеряет задержку прерывания. Когда разные ветви имеют инструкции разной длины, вы можете посмотреть на задержку прерывания, чтобы определить, в какой из этих ветвей произошло прерывание и произошла утечка части состояния программы.
Я написал простую функцию, которую я хочу атаковать описанным выше способом. Я использую системный таймер для генерации прерывания в правильный момент времени. Чтобы получить начальное хорошее значение для таймера прерывания, я использовал GDB, чтобы остановить программу на целевой строке, чтобы увидеть значение системы в то время.
Я реализовал очень простой обработчик прерываний, который
- загружает значение системного таймера из памяти
- вычитает это значение из значения перезагрузки, чтобы получить время, прошедшее с момента прерывания (т.е. задержку IRQ).
- очищает прерывание и
void __attribute__((interrupt("IRQ"))) SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
SysTick->CTRL amp;= 0xfffffffe; // disable SysTick (~SysTick_CTRL_ENABLE_Msk)
*timer_value = SysTick->VAL; // capture counter value (as quickly as possible)
*timer_value = SysTick->LOAD - *timer_value; // subtract it from reload value to get IRQ latency
SysTick->VAL = 0; // reset initial value
}
Однако я обнаружил, что я всегда получаю одинаковую задержку IRQ, независимо от прерванной инструкции. Я ожидаю, что задержка прерывания будет больше, когда прерывается более длинная инструкция.
Это функция, которую я написал для тестирования атаки
extern uint32_t *timer_value;
int sample_function(int *a, int *b){
/*
* function description -- store the smallest of the two value in a, if MEASURE_CYCLESS defined return the number
* of clock cycles that have been elapsed since the timer has been started
* r0 contains pointer to a
* r1 contains pointer to b
*/
__asm volatile(
/* push working registers */
"PUSH {r4-r8} n"
/* move counter into r8 */
"MOV r8, #10 n"
/* begin loop */
"begin_loop: n"
/* decrement counter variable*/
"SUB r8, r8, #1 n"
/* if counter variable not equal to 0, jump back to start of loop */
"CMP r8, #0 n"
/* if r8 not equal to 0, jump back to begin of loop*/
"BNE begin_loop n"
/* load a into r2 */
"LDR r2, [r0] n"
/* load b into r3 */
"LDR r3, [r1] n"
/* store a-b in r4, setting status flags -- if result is 0 Z flag is set */
"SUBS r4, r2, r3 n"
/* if a-b positive, a is larger otherwise, b is larger (assuming a not equal to b) */
"BPL a_larger n"
#ifdef SPY
/* load address of (*timer_value) into r4 -- use of LDR pseudo-instruction places constant in a literal pool*/
"LDR r4, =timer_value n"
/* Load (*timer_value) into r4 */
"LDR r4, [r4] n"
/* load address of Systick VAL into r5 */
"LDR r5, =0xe000e018 n"
/* Load value at address stored in R5 (= Systick Val) */
"LDR r5, [r5] n"
/* Move Systick Val into adress stored at r4 (= *timer_value = address of timer_value)*/
"STR r5, [r4] n"
#endif
"NOP n"
/*instruction that gets interrupted -- swap value*/
"STR r2, [r1] n"
/* load value at this address into r0 (return value) */
"STR r3, [r0] n"
"B end n"
"a_larger: n"
"MOV r0, #0 n" // instruction that gets interrupted
"end: POP {r4-r8}"
); // pop working registers
}
Обратите внимание, что раздел кода в #define
блоке используется для автоматического определения правильного значения перезагрузки таймера (вместо использования GDB), но в настоящее время я не использую значение, полученное таким образом.
У меня также есть пустой цикл, чтобы немного задержать инструкцию, которая должна быть прервана.
Прерываемая инструкция — это инструкция сразу после #define
блока. Когда я удаляю NOP
инструкцию, я все равно получаю ту же задержку прерывания. Когда я увеличиваю или уменьшаю значение таймера (чтобы прервать некоторые циклы раньше или позже) Я также по-прежнему получаю ту же задержку IRQ.
Я что-то здесь упускаю? Есть ли какое-то поведение, о котором я не знаю? Кроме того, важно ли использовать атрибут __attribute__((interrupt("IRQ"))
для обработчика прерываний?
Комментарии:
1. Я не могу себе представить, как GDB может получить точное считывание таймера. Похоже, тонна кода делает все, что вы пытаетесь сделать. Выполните тест на языке ассемблера, как на переднем плане, так и на прерывании, таким образом, вам не нужно нажимать какие-либо регистры, вы можете просто иметь несколько строк isr, ldr rd,[rn]; bx lr, где ldr считывает системный таймер. все для предварительной настройки теста.
2. каково ваше определение длинной инструкции. задача переднего плана может представлять собой цикл таких вещей, как nop, а затем такие вещи, как хранилища и такие вещи, как чтение, и тому подобное.
3. несомненно, будут различия в задержках, поскольку шина (ses) может быть загружена по-разному, Что влияет на время выборки isr или выполнение isr и т. Д. (ну, для systick это обработчик исключений, а не обработчик прерываний). но можете ли вы добраться туда, где вы можете это увидеть.
4. нет смысла пытаться установить значение тайм-аута, чтобы попытаться настроить таргетинг на инструкцию, если это подразумевалось здесь, просто выберите периодическое прерывание. период. задача переднего плана может сказать, проверить регистр, возможно, вы увеличиваете значение в обработчике, затем разветвляетесь и распечатываете один или два элемента, собранные в обработчике. затем вернитесь к циклу, который в основном представляет собой инструкции, которые вы надеетесь прервать. поэтому период должен быть достаточно длинным, чтобы это произошло. например, один раз в секунду.
5. вы определенно не хотите создавать для отладки и не видите, как здесь помогает отладчик, но можете видеть, как это мешает.
Ответ №1:
Это то, о чем я думал и комментировал.
бутстрэп
.thumb_func
reset:
bl notmain
ldr r4,=0xE000E018
ldr r0,=0xE000E010
mov r1,#7
str r1,[r0]
b hang
.thumb_func
hang:
nop
nop
nop
nop
nop
nop
nop
b hang
настройка uart и системного интерфейса
void notmain ( void )
{
uart_init();
hexstring(0x12345678);
PUT32(STK_CSR,4);
PUT32(STK_RVR,0xF40000);
PUT32(STK_CVR,0x00000000);
//PUT32(STK_CSR,7);
}
обработчик событий
.thumb_func
.globl systick_handler
systick_handler:
ldr r0,[r4]
ldr r5,[sp,#0x18]
push {r0,lr}
bl hexstrings
mov r0,r5
bl hexstring
pop {r0,pc}
возьмите таймер и адрес прерванной инструкции и распечатайте их.
00F3FFF4 08000054
00F3FFF4 08000056
00F3FFF4 08000058
00F3FFF4 0800005A
00F3FFF4 0800005C
00F3FFF4 0800005E
00F3FFF4 08000054
00F3FFF4 08000056
00F3FFF4 08000058
00F3FFF4 0800005A
00F3FFF4 08000050
08000050 <hang>:
8000050: bf00 nop
8000052: bf00 nop
8000054: bf00 nop
8000056: bf00 nop
8000058: bf00 nop
800005a: bf00 nop
800005c: bf00 nop
800005e: e7f7 b.n 8000050 <hang>
Из документации ARM.
Задержка прерывания
Существует максимальная задержка в двенадцать циклов от утверждения прерывания до выполнения первой инструкции ISR, когда к памяти, к которой осуществляется доступ, не применяются состояния ожидания. Когда реализована опция FPU и активен контекст с плавающей запятой, а отложенное стекирование не включено, эта максимальная задержка увеличивается до двадцати девяти циклов. Первые инструкции, которые должны быть выполнены, извлекаются параллельно с выталкиванием стека.
И эту последнюю строку мы, возможно, видим здесь. Вы можете попробовать различные инструкции, но эта архитектура имеет возможность перезапускать длительные инструкции (чтение и нажатие / нажатие, умножение и тому подобное). Я думаю, чтобы увидеть большую разницу в задержке, вам может потребоваться создать конфликт шины или общих ресурсов (против инструкций)
Кроме того, системный вызов является исключением, а не прерыванием, поэтому могут быть некоторые различия в отношении задержки.