在ARM Cortex-M3上编写一个简单的C任意代码执行漏洞



我正试图用C编写一个概念验证,演示在ARM Cortex-M3上从堆栈中的内存缓冲区执行代码。这将有助于证明正确使用ARM MPU可以防止此类攻击。我认为将一些代码放入堆栈的一种快速而肮脏的方法是从常规函数中复制代码,然后使用goto跳转到它,如下所示:

static void loopit(void)
{
printf("loopitn");
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

这似乎是UFSR中的INVSTATE位,它指示"非法使用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

这似乎没有任何意义——UFSR第3位的设置意味着"处理器试图访问协处理器"。这一次看PC,它似乎成功了跳跃,这很好,但后来有什么东西偏离了轨道,CPU似乎在执行奇怪的指令,而不是进入无限循环。我试着在goto之前关闭中断,并对printf进行注释,但没有成功。有什么问题以及如何解决的线索吗?

很抱歉滥用答案形式,我对你的代码进行了一点调整,它会从堆栈中闪烁一个LED:

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的投诉。

我最终没有使用goto,也没有尝试执行复制到堆栈内存中的函数中的任何函数。还要确保使用noinlineO0编译堆栈函数。

我使用以下代码将堆栈地址转换为函数指针:

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

不知道我为什么要把缓冲区做得这么大。我把函数复制到缓冲区的中间。

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(功能相同)。这绝对不在相互作用的指令列表中。请参阅体系结构参考手册。

最新更新