寻找x86-64双字节非法Opcode及执行异常解决方法
解决x86-64非法双字节Opcode测试中栈指针修改导致的程序卡住问题
问题根源
当测试修改RSP/RBP的Opcode(如0F CC)时,直接在当前线程/进程执行会破坏栈结构——栈指针指向非法地址后,后续指令(包括异常处理逻辑)无法正常执行,导致程序卡住或崩溃。线程隔离无效的核心原因是:线程共享进程地址空间,修改RSP依然会破坏自身线程栈,进而导致线程无法正常退出或触发无法捕获的异常。
最优解决方案:子进程+临时栈隔离
用子进程完全隔离执行环境,同时在子进程内设置独立临时栈,彻底避免测试Opcode破坏系统栈。父进程仅负责创建子进程、传递测试Opcode、通过子进程退出状态判断是否触发非法指令异常。
具体实现步骤
父进程:
- 通过命令行、共享内存或管道传递要测试的双字节Opcode。
- 创建子进程并等待其退出。
- 检查子进程退出状态:若因
SIGILL(非法指令信号)退出,则该Opcode是目标Opcode;若正常退出或因其他信号退出,则排除。
子进程:
- 分配可执行+可写的内存页,写入测试Opcode+主动退出指令(避免合法Opcode执行后代码跑飞)。
- 分配足够大的临时栈,手动将RSP切换到临时栈顶部(栈向下生长,需指向内存块末尾)。
- 跳转到测试代码页执行,确保无论执行结果如何,子进程都能触发信号或正常退出。
优化后的测试代码示例
#include <stdio.h> #include <stdlib.h> #include <sys/mman.h> #include <sys/wait.h> #include <signal.h> #include <unistd.h> #define STACK_SIZE 4096 void child_test(unsigned char *opcode) { // 分配可执行内存存储测试指令 void *code_page = mmap(NULL, 16, PROT_EXEC | PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (code_page == MAP_FAILED) exit(1); // 写入双字节Opcode + exit(0)指令(防止合法指令执行后跑飞) unsigned char *code = (unsigned char*)code_page; code[0] = opcode[0]; code[1] = opcode[1]; // 手动构造exit(0)机器码:mov rdi,0; mov rax,60; syscall code[2] = 0xbf; code[3] = 0x00; code[4] = 0x00; code[5] = 0x00; code[6] = 0x00; code[7] = 0xb8; code[8] = 0x3c; code[9] = 0x00; code[10] = 0x00; code[11] = 0x00; code[12] = 0x0f; code[13] = 0x05; // 分配临时栈(MAP_GROWSDOWN模拟系统栈生长方向) void *stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN, -1, 0); if (stack == MAP_FAILED) exit(1); // 切换到临时栈并执行测试代码 void (*test_func)(void) = (void(*)(void))code_page; __asm__ volatile( "movq %0, %%rsp\n" // 将RSP指向临时栈顶部 "call *%1\n" // 执行测试指令 : : "r"((char*)stack + STACK_SIZE), "r"(test_func) : "memory" ); exit(0); } int main(int argc, char *argv[]) { if (argc != 3) { fprintf(stderr, "Usage: %s <opcode1> <opcode2>\n", argv[0]); return 1; } unsigned char opcode[2] = { strtoul(argv[1], NULL, 16), strtoul(argv[2], NULL, 16) }; pid_t pid = fork(); if (pid == -1) { perror("fork"); return 1; } else if (pid == 0) { child_test(opcode); } else { int status; waitpid(pid, &status, 0); if (WIFSIGNALED(status) && WTERMSIG(status) == SIGILL) { printf("Opcode 0x%02X 0x%02X triggers illegal instruction exception\n", opcode[0], opcode[1]); } else { printf("Opcode 0x%02X 0x%02X does NOT trigger illegal instruction exception\n", opcode[0], opcode[1]); } } return 0; }
关键细节说明
- 临时栈作用:即使测试Opcode修改RSP,也只会破坏临时栈,不会影响子进程的系统栈,确保异常处理或退出逻辑能正常执行。
- 退出指令的必要性:对于合法Opcode(执行后不触发异常),添加
exit(0)避免代码跑飞至未知内存区域,导致子进程行为不可控。 - 信号判断逻辑:父进程通过
waitpid获取子进程退出状态,SIGILL信号是触发非法指令异常的明确标识。
补充:32位合法但64位非法的单字节Opcode
以下单字节Opcode在IA-32模式下合法,但在x86-64长模式下属于非法指令:
0x67(地址大小前缀,单独使用无有效指令对应)0x66(操作数大小前缀,单独使用无有效指令对应)0x9B(WAIT指令,x86-64下需配合前缀才合法)0xDA/0xDB/0xDC/0xDD的部分浮点指令变体(无64位对应编码)0xF6/0xF7的部分操作码变体(未定义64位执行形式)
内容的提问来源于stack exchange,提问作者vengy
相关产品推荐
相关产品推荐

