Написать простой эксплойт выполнения произвольного кода C на ARM Cortex-M3?

Я пытаюсь написать доказательство концепции на C, которое демонстрирует выполнение кода из буфера памяти в стеке на ARM Cortex-M3. Это будет полезно для демонстрации того, что правильное использование ARM MPU может предотвратить такую ​​атаку. Я подумал, что быстрый и грязный способ поместить код в стек - это скопировать его из обычной функции, а затем использовать goto, чтобы перейти к нему следующим образом:

static void loopit(void)
{
    printf("loopit\n");
    while (1);
}

void attack(void)
{
    uint8_t buffer[64] __attribute__((aligned(4)));
    memcpy(buffer, loopit, sizeof(buffer));
    goto *((void *) (int) buffer);
}

Я ожидал, что когда я вызову функцию атаки, она скопирует код в стек, перейдет к нему, распечатает сообщение и войдет в бесконечный цикл. Однако вместо этого я получаю исключение со следующими значениями в регистрах ошибок:

HFSR = 0x40000000
CFSR = 0x00020000
PSR  = 0x60000000

Похоже, что это бит INVSTATE в UFSR, который указывает на «незаконное использование EPSR», который, как я прочитал, обычно связан с попыткой инструкции BX перейти на адрес с LSB, установленным на 0, который процессор интерпретирует как функцию с в нем код не-Thumb, но процессоры Cortex-M допускают только код Thumb. Я вижу, что memcpy получает нечетный адрес для функции loopit, поскольку я предполагаю, что компилятор выполняет операцию ИЛИ реальный адрес памяти с 1. Итак, исправление, которое я думаю, заключалось бы в том, чтобы переписать мою функцию атаки следующим образом:

void attack(void)
{
    uint8_t buffer[64] __attribute__((aligned(4)));
    memcpy(buffer, ((int) loopit) & ~1, sizeof(buffer));
    goto *((void *) ((int) buffer) | 1);
}

Однако после этого я получаю другое исключение с регистрами ошибок:

HFSR = 0x40000000
CFSR = 0x00080000
PSR  = 0x81000000

В этом нет никакого смысла: установленный бит 3 UFSR означает, что «процессор попытался получить доступ к сопроцессору». Глядя на ПК, на этот раз кажется, что прыжок прошел успешно, и это здорово, но затем что-то пошло не так, и ЦП, похоже, выполняет странные инструкции и не входит в бесконечный цикл. Я попытался отключить прерывания перед goto и также закомментировать printf, но безуспешно. Есть какие-нибудь подсказки, что происходит не так и как заставить это работать?


person satur9nine    schedule 11.11.2017    source источник
comment
Вы пытались увидеть ассемблерный код, созданный компилятором C функции loopit ()? Это может подсказать, почему копия или ее выполнение не работают должным образом.   -  person Breaking not so bad    schedule 11.11.2017
comment
Чтобы убедиться, что вы работаете на «голом железе» - никакая ОС или MMU не мешают автоматически запускать код в пространстве данных? Запуск отладчика, вероятно, ответит на множество вопросов, в том числе на то, к чему именно вы переходите.   -  person Michael Dorgan    schedule 11.11.2017
comment
Почему преобразование (int) в (void *) ((int) buffer), в зависимости от размера int и размера указателей, это int преобразование может изменить адрес. Будет ли goto *((void *) buffer); работать лучше?   -  person Breaking not so bad    schedule 11.11.2017
comment
У меня работает после того, как я исправлю ошибки компиляции в вашем коде. Никаких сбоев, зацикливание внутри буфера. Какой компилятор вы используете?   -  person A.K.    schedule 11.11.2017
comment
Вызов printf () или любой другой функции вызывает ошибку, потому что сгенерированная инструкция BL использует относительную адресацию, переходя на случайный адрес в ОЗУ.   -  person A.K.    schedule 11.11.2017
comment
Когда я компилирую с -O3, компилятор исключает вызов memcpy (), потому что он считает, что buffer вообще не используется!   -  person A.K.    schedule 11.11.2017


Ответы (3)


Извините за злоупотребление формой ответа, я немного адаптировал ваш код, и он мигает светодиодом прямо из стека:

void (*_delay_ms)(uint32_t) = delay_ms;

static void loopit(void)
{
    while (1)
    {
        GPIOC->ODR ^= 1 << 13;
        _delay_ms(125);
    }
}

void attack(void)
{
    volatile uint8_t buffer[64] __attribute__((aligned(4)));
    memcpy(buffer, (void *)((uint32_t) loopit & ~1), sizeof(buffer));
    goto *(void *)((uint32_t) buffer | 1);
}

Интересно, как скоро я получу жалобы на UB.

person A.K.    schedule 11.11.2017
comment
Я бы больше пожаловался, если бы ему потребовалась кроссплатформенная совместимость. Запуск самогенерируемой сборки уже нарушает почти все правила, которые должна иметь ОС, и код будет запускать только очень специфический ассемблер, поэтому он как бы кричит для ответа типа взлома. Так до следующего выпуска компилятора и все сломается ... - person Michael Dorgan; 13.11.2017

В итоге я не использовал goto и не пытался выполнять какие-либо функции из функции, скопированной в память стека. Также не забудьте скомпилировать функцию стека с noinline и O0.

Я использовал следующий код для преобразования адреса стека в указатель функции:

// Needed a big buffer and copied to the middle of it
#define FUNC_SIZE 256
#define BUF_SIZE (FUNC_SIZE * 3)

uint8_t mybuf[BUF_SIZE] __attribute__((aligned(8)));
uintptr_t stackfunc = (uintptr_t) mybuf;
stackfunc += FUNC_SIZE;

memcpy((void *) stackfunc, (void *) (((uintptr_t) &flashfunc) & ~1), FUNC_SIZE);

void (*jump_to_stack)(void) = (void (*)(void)) ((uintptr_t) stackfunc | 1);
jump_to_stack();

Не знаю, почему мне пришлось сделать буфер таким большим. Я скопировал функцию в середину буфера.

person satur9nine    schedule 14.09.2018

void attack(void)
{
    uint16_t buffer[64];
    goto *((void *) (((unsigned int)(buffer)) | 1));
}

вы просили его сделать ветку, ему не нужен lsbit, установленный для ветки, конечно, обмен филиалами. В этом случае позвольте инструменту делать свое дело. Или, если есть проблема, используйте язык ассемблера для выполнения ветвления, чтобы вы могли конкретно контролировать используемую инструкцию и, следовательно, адрес.

00000000 <attack>:
   0:   b0a0        sub sp, #128    ; 0x80
   2:   2301        movs    r3, #1
   4:   466a        mov r2, sp
   6:   4313        orrs    r3, r2
   8:   469f        mov pc, r3
   a:   46c0        nop         ; (mov r8, r8)

В данном случае даже не ветвь, а mov pc (функционально то же самое). Которого определенно нет в списке взаимодействующих инструкций. См. Справочное руководство по архитектуре.

person old_timer    schedule 16.09.2018
comment
Но вы ничего не помещали в буфер, вам нужен код, чтобы продемонстрировать, что он работает. - person satur9nine; 18.09.2018
comment
Я сделал в ответ, что скопировал это. Вы сказали, что я не использовал goto. Хотя это было интересное решение, проблема не в goto, но проблема с адресом в goto была проблемой. Тогда, конечно, вы должны поместить код в буфер. - person old_timer; 19.09.2018