简体   繁体   中英

Write a simple C arbitrary code execution exploit on ARM Cortex-M3?

I'm trying to write a proof of concept in C that demonstrates code execution from a memory buffer in the stack on an ARM Cortex-M3. This will be useful to demonstrate that using the ARM MPU correctly can prevent such an attack. I figured a quick and dirty way to get some code into the stack is to copy it from a regular function and then use a goto to jump to it like so:

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);
}

I would expect that when I call the attack function it will copy code into the stack, jumps to it, print the message and go into an infinite loop. However instead I get an exception with the following values in the fault registers:

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

This seems to be the INVSTATE bit in the UFSR which indicates "illegal use of the EPSR", which I read is usually due to the BX instruction attempting to jump to an address with the LSB set to 0 which the processor interprets as a function with non-Thumb code in it, but Cortex-M processors only allow Thumb code. I see that the memcpy is being given an odd address for the loopit function since I assume the compiler is ORing the real memory address with 1 . So the fix I would think would be to rewrite my attack function like so:

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

However after doing that I get a different exception with fault registers:

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

This doesn't seem to make any sense the UFSR bit 3 set means "the processor has attempted to access a coprocessor". Looking at the PC this time it appears the jump succeeded which is great but then something went off the rails and the CPU appears to be executing strange instructions and not going into the infinite loop. I tried turning off interrupts before the goto and commenting out the printf as well but no luck. Any clue what is going wrong and how to make it work?

Sorry for abusing the answer form, I have adapted your code a little and it blinks a LED right from the stack:

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);
}

I wonder how soon I get complaints about UB.

I ended up not using goto and not trying to execute any functions from the function copied into stack memory. Also be sure to compile the stack function with noinline and O0 .

I used the following code to cast the stack address into a function pointer:

// 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();

Not sure why I had to make the buffer so big. I copied the function to the middle of the buffer.

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

you asked it to do a branch, it does not need the lsbit set for a branch, a branch exchange sure. In this case let the tool do its job. Or if there is a concern use assembly language to perform the branch so that you can specifically control the instruction used and thus the address.

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)

Not even a branch in this case but a mov pc (functionally the same). Which is definitely not on the list of interworked instructions. See the architectural reference manual.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM