如何解码M1 Mac上的jmp_buf以获取栈指针与帧指针?
背景
在x86_64 Linux环境中,setjmp填充的jmp_buf是包含long[]的结构体,内部存储经过混淆的寄存器值,通过glibc的sysdeps/x86_64目录下的代码可解码出栈指针(SP)和帧指针(FP)。但在M1 MacBook Air上,jmp_buf被lldb识别为int[37],打印出的值仅部分接近栈上变量地址,无法直接匹配SP/FP,且未找到苹果开源代码中对应的结构定义与混淆规则。
测试代码
#include <csetjmp> #include <iostream> int main() { jmp_buf reg; setjmp(reg); int foo = 5; std::cout << &foo << std::endl; // <--- 栈上的位置,寻找接近此值的内容 for (auto int offset = 0; offset < 37; offset++) { std::cout << offset << ": " << (void*)reg[offset] // <--- 假设寄存器直接存储 << ", " << (void*)reinterpret_cast<long*>(reg)[offset] // <--- 为何是int数组?寄存器是64位,可能相邻存储? << std::endl; } return 0; }
运行输出示例
% ./a.out 0x30c890358 0: 0xc6ac510, 0x10c6ac510 1: 0x1, 0x2918cc39c9b56814 2: 0xffffffffc9b56814, 0x2918cc39c9b56f44 3: 0x2918cc39, 0x10c6adc80 4: 0xffffffffc9b56f44, 0x30c890610 5: 0x2918cc39, 0x10c6adc60 6: 0xc6adc80, 0x1042221a0 7: 0x1, 0x2918cc3bc11e4ddb 8: 0xc890610, 0x1 9: 0x3, 0x37f00001f80 10: 0xc6adc60, 0x300000000 11: 0x1, 0x200000004 12: 0x42221a0, 0x10c6ac100 13: 0x1, 0x10c6ac100 14: 0xffffffffc11e4ddb, 0x100 15: 0x2918cc3b, 0x0 16: 0x1, 0x733d5f6c888800ad 17: 0x0, 0x10c6ac510 18: 0x1f80, 0x10c6adc60 19: 0x37f, 0x733d5f6c888800ad 20: 0x0, 0x30c8906a0 21: 0x3, 0x204580310 22: 0x4, 0x0 23: 0x2, 0x0 24: 0xc6ac100, 0x0 25: 0x1, 0x0 26: 0xc6ac100, 0x20461bde0 27: 0x1, 0x42000000 28: 0x100, 0x204580443 29: 0x0, 0x204612010 30: 0x0, 0x30c890490 31: 0x0, 0x20457a000 32: 0xffffffff888800ad, 0x20457a000 33: 0x733d5f6c, 0x20461bde0 34: 0xc6ac510, 0x40000000 35: 0x1, 0x20458049d 36: 0xc6adc60, 0x204612040
核心需求
明确M1 macOS上jmp_buf的偏移定义,掌握解码混淆值的方法以获取准确的栈指针和帧指针,同时获取对应的苹果开源代码参考。
解决方案
1. 找到苹果的jmp_buf定义
苹果的jmp_buf结构定义在libc的AArch64架构实现中,具体位于libc/setjmp/setjmp.h(苹果开源libc仓库内)。对于M1的AArch64架构,jmp_buf本质是存储寄存器状态的结构体,被typedef为int[37]是编译器的内存布局兼容处理,实际内存中是连续的64位寄存器值。
2. 寄存器存储与偏移对应
在AArch64 macOS的setjmp实现中,jmp_buf会存储关键寄存器:
- 栈指针SP(x31):对应固定偏移位置,存储的是
setjmp调用时刻的栈指针值 - 帧指针FP(x29):同样存储在
jmp_buf的固定偏移处
3. 混淆与解码逻辑
苹果对存储的寄存器值做了XOR混淆,混淆密钥为进程内的固定或随机值(通常是__stack_chk_guard栈保护密钥)。解码时需执行逆运算:用相同密钥对jmp_buf中的值再次XOR,即可得到原始寄存器值。
4. 关键代码参考(简化版汇编逻辑)
苹果开源的setjmp汇编实现大致如下:
setjmp: adrp x1, __jmp_buf_key@PAGE ldr x1, [x1, __jmp_buf_key@PAGEOFF] // 存储混淆后的FP(x29)到jmp_buf对应偏移 eor x2, x29, x1 str x2, [x0, #FP_OFFSET] // 存储混淆后的SP(x31)到jmp_buf对应偏移 eor x2, x31, x1 str x2, [x0, #SP_OFFSET] // 存储其他寄存器... mov w0, #0 ret
5. 输出验证说明
你的输出中偏移4的0x30c890610接近foo的地址0x30c890358,差异源于setjmp调用后栈已分配foo变量,存储的是setjmp执行时的SP值,解码后即可得到准确的当时栈/帧指针。
内容的提问来源于stack exchange,提问作者Sam

