【问题标题】:Write a simple C arbitrary code execution exploit on ARM Cortex-M3?在 ARM Cortex-M3 上写一个简单的 C 任意代码执行漏洞?
【发布时间】:2017-11-11 01:15:52
【问题描述】:

我正在尝试用 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

这似乎是 UFSR 中的 INVSTATE 位,表示“非法使用 EPSR”,我读到这通常是由于 BX 指令试图跳转到 LSB 设置为 0 的地址,处理器将其解释为一个包含非 Thumb 代码的函数,但 Cortex-M 处理器只允许 Thumb 代码。我看到 memcpy 被赋予了 loopit 函数的奇数地址,因为我假设编译器正在使用 1 对实际内存地址进行 ORing。所以我认为的解决方法是像这样重写我的攻击函数:

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 但没有运气。任何线索出了什么问题以及如何使其工作?

【问题讨论】:

  • 您是否尝试查看由 C 编译器生成的 loopit() 函数的汇编代码?这可能会提示为什么副本或其执行无法按预期工作。
  • 为了确保您运行的是裸机 - 没有操作系统或 MMU 会自动阻止您在数据空间中运行代码?在调试器中运行可能会回答很多问题,包括你到底要跳转到什么。
  • 为什么(int) 转换为(void *) ((int) buffer),取决于int 的大小和指针的大小,int 转换可能会改变地址。 goto *((void *) buffer); 会更好吗?
  • 修复代码中的编译错误后,它对我有用。没有故障,在缓冲区内循环。你用什么编译器?
  • 调用 printf() 或任何其他函数会导致错误,因为生成的 BL 指令使用相对寻址,跳转到 RAM 中的随机地址。

标签: c exploit cortex-m


【解决方案1】:

很抱歉滥用了回答表单,我对您的代码做了一些修改,它会从堆栈中闪烁一个 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 的投诉。

【讨论】:

  • 如果它需要跨平台兼容,我会抱怨更多。运行自生成的程序集已经违反了操作系统的几乎所有规则,并且代码只会运行一个非常特定的汇编程序,因此,它有点像黑客类型的答案。直到下一个编译器发布,一切都会中断......
【解决方案2】:

我最终没有使用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();

不知道为什么我必须让缓冲区这么大。我将函数复制到缓冲区的中间。

【讨论】:

    【解决方案3】:
    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(功能相同)。这绝对不在交互指令列表中。请参阅架构参考手册。

    【讨论】:

    • 但是你没有在缓冲区中放任何东西,需要一些代码来证明它是有效的。
    • 我在复制这个的答案中做了。你说我最终没有使用 goto。尽管 goto 不是问题所在的有趣解决方案,但在 goto 中弄乱地址是一个问题。然后当然你必须把代码放在缓冲区中。
    猜你喜欢
    • 1970-01-01
    • 2014-06-08
    • 1970-01-01
    • 1970-01-01
    • 2011-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多