访问mmap分配的可执行页面时如何解决EXC_BAD_ACCESS(code=2)?在 Apple 芯片上(MacOS 和 M1 mac mini)

问题描述 投票:0回答:1

我编写了一个测试程序来实现类似于 Apple Silicon 上的自修改代码的功能。

int main() {
 
    uint8_t *instr;
    uint32_t instr1 = 0x8b000000; // add x0, x0, x0
    uint32_t instr2 = 0xd65f03c0; // ret
 
    if(pthread_jit_write_protect_supported_np() == 1)
        printf("jit write supported\n");
    else
        printf("jit write not supported\n");

    pthread_jit_write_protect_np(1);
    instr = (uint8_t*)mmap(NULL, 1024, PROT_READ|PROT_EXEC|PROT_WRITE, MAP_PRIVATE|MAP_ANON|MAP_JIT, 0, 0) + 4084;
    pthread_jit_write_protect_np(0);

    if(instr == MAP_FAILED){
        perror("mmap");
        exit(-1);
    }

    printf("instr addr : %lx\n", (uintptr_t)instr);
    memcpy(instr, &instr, 4);
    memcpy(instr+4, &instr2, 4);

    printf("instr1 is %x\n", *(uint32_t *)instr);
    printf("instr2 is %x\n", *(uint32_t *)(instr+4));

    asm volatile(
    "eor x0, x0, x0\n"
    "eor x1, x1, x1\n"
    "eor x2, x2, x2\n"
    "eor x3, x3, x3\n"
    );

    asm volatile(
    "ldr x1, %[ptr]\n"
    "br x1\n"
    ::[ptr]"m"(instr)
    );

    return 0;
}

我用mmap分配了一个4KB的内存区域,允许读、写和执行权限。然后,我使用 memcpy 将两条汇编指令写入该内存区域。之后,我使用内联汇编初始化寄存器 x1~x3 并将程序计数器 (pc) 分支到先前分配的页面。分支后,依次执行指令instr1和instr2。但是,当分支和访问内存区域时,程序会因 EXC_BAD_ACCESS code=2 错误而中止。

通过Google搜索,我开始意识到问题出在Apple的协同设计上。如果 Apple Silicon 内存中运行的代码未经协同设计,则访问似乎会被拒绝。因此,我一直在谷歌上寻找一种允许通过协同设计进行访问的方法。但是,我一直无法找到一种方法来对通过 mmap 分配的内存进行协同设计以允许访问。有什么办法可以解决这个问题吗?

c macos assembly codesign apple-silicon
1个回答
0
投票

直接的问题是,您调用

pthread_jit_write_protect_np(0)
并且永远不会将其翻转回
1
。这会使您的线程处于 JIT 页面为
rw-
的状态,因此尝试从那里执行将会出错。在
pthread_jit_write_protect_np(1)
之后拨打
memcpy()

下一个问题是你使用

ldr x1, %[ptr]
。这会从指针加载 x1
,而不是将指针移动到 
x1
,因此 
x1
 将是您写入的 8 个字节。将其替换为 
mov x1, %[ptr]
 并将 
"m"
 更改为 
"r"

然后是缓存。导入

<libkern/OSCacheControl.h>

 并在最终调用 
sys_dcache_flush(instr, 0x8)
 之前执行 
pthread_jit_write_protect_np(1)
,然后执行 
sys_icache_invalidate(instr, 0x8)

还有一个问题是你写错了:

memcpy(instr, &instr, 4);
您的意思是在这里获取

instr1

的地址,而不是
instr
。您当前正在写入指针的低 4 个字节。

现在您的 shellcode 已正确复制并实际执行,但

ret

 是一个问题。您使用 
br
 调用它,因此 
x30
 已过时,并指向最后一个函数返回的位置,这很可能是您的最后一个 
printf
。将 
br
 更改为 
blr

然后 - 为什么要映射

0x400

 字节,然后将 
0xff4
 添加到该指针?实际上这可能不是问题,因为arm64 XNU下的页面是16KiB,但只是......为什么?您至少可以不向指针添加任何内容,然后任何足够大的尺寸来容纳您的指令就可以了。

还有缺少的寄存器破坏者,它现在可能不会引起问题,但迟早会干扰编译器生成的代码。

© www.soinside.com 2019 - 2024. All rights reserved.