# cdecl_jump_to_new_stack **Repository Path**: a5k3rn3l/cdecl_jump_to_new_stack ## Basic Information - **Project Name**: cdecl_jump_to_new_stack - **Description**: 纯汇编实现的栈管理例程,通过 `jmp_to_new_stack` 和 `my_longjmp` 实现双栈上下文切换。用于创建包装阻塞函数的非阻塞 API。仅包含原始 shellcode - 零依赖。 - **Primary Language**: Unknown - **License**: LGPL-2.1 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-06-11 - **Last Updated**: 2025-06-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # x86/x64 有栈协程相关 shellcode 库 [English](Readme.md) | 简体中文 使用 [NASM](https://www.nasm.us/pub/nasm/releasebuilds/2.16.03/win64/nasm-2.16.03-win64.zip) 编译。建议把 `nasm.exe` 放到 `C:\Windows` 里,这样就到处都可以直接以命令行的方式调用了。 ## 如何使用 当你需要实现一个被调用者轮询调用的非阻塞 API 里,不得不调用阻塞式函数的时候,如果你还能通过这个阻塞式函数支持的回调函数来逃离它的阻塞并假装你的函数可以非阻塞返回给你的函数的调用者的时候,你需要通过以下逻辑来实现: * 调用阻塞式函数前,分配内存用作新栈(需 16 字节对齐),调用我的 `jmp_to_new_stack` 跳转到新栈,在新栈上执行一个回调函数,回调函数负责调用你的阻塞式函数。这样阻塞式函数使用的是你的新栈。 * 在阻塞式函数的回调函数被调用的时候,调用我的 `my_longjmp` 跳转到你的函数设置的 `setjmp()` 跳转目标,这样你的函数就可以返回给你的调用者了(并且也切换到了原始栈),以此可以假装你的函数是非阻塞的。 * 为何要用 `my_longjmp`?因为我去除了多余的 C++ RAII 资源释放规则和异常的抛出与处理检测,从而实现成功的跳转,并切换栈为你的原始栈。 * 当用户再次调用你的 API 的时候,你需要重新回到你那个被调用的回调函数的里面。因此你要用 `my_longjmp` 再跳转回去,此时切换到了新栈。 * 正因为我去除了多余的 C++ RAII 资源释放规则和异常的抛出与处理检测,从而不影响原本的阻塞式函数它自己执行 RAII 资源释放规则和异常处理。 * 阻塞式函数返回后,`jmp_to_new_stack` 通过 `my_longjmp` 方式返回,在你的函数里设置 `setjmp()` 跳转目标,即可回到旧栈。 ## 如何编译 * Windows: 双击 `build.bat` ,它会提示请按任意键继续(此时编译已经完成,你得到了所有的 bin 文件);按任意键后,每个 `bin` 被 Python 脚本处理为 `uint32_t` 数组或者 `uint64_t` 数组,自己拷贝到你的 C/C++ 项目里去。 * Linux: 你自己想办法。Linux 发行版的包管理器肯定有 NASM,并且 Linux 也可以执行我的 Python 脚本。我的 Python 脚本有 shebang,设置 `+x` 属性就可以执行。 ## Windows 下集成为函数 注意里面的数组都是指令,这些指令就是用 NASM 编译后,用我的 Python 脚本生成的数组。指令都是对齐了的。 ```c #include __declspec(noreturn) void _my_longjmp(jmp_buf jb, int value) { #ifdef _M_IX86 static const uint32_t shellcode_my_longjmp[] = { 0x0424548B, 0x0824448B, 0x8301F883, 0x2A8B00D0, 0x8B045A8B, 0x728B087A, 0x10628B0C, 0xFF04C483, 0x90901462, }; #elif defined(_M_X64) static const uint64_t shellcode_my_longjmp[] = { 0x4808598B48D08948, 0x4818698B4810618B, 0x4C28798B4820718B, 0x4C38698B4C30618B, 0x0F48798B4C40718B, 0x5C69D9E2DB5851AE, 0x6F0F6660716F0F66, 0x80816F0F44667079, 0x896F0F4466000000, 0x6F0F446600000090, 0x0F4466000000A091, 0x4466000000B0996F, 0x66000000C0A16F0F, 0x000000D0A96F0F44, 0x0000E0B16F0F4466, 0x00F0B96F0F446600, 0x9090905061FF0000, }; #else // If your computer is not x86 nor x64, implement your own `longjmp()` shellcode for your CPU. longjmp(jb, value); #endif static DWORD dwOldProtect = 0; if (!dwOldProtect) VirtualProtect((void *)shellcode_my_longjmp, sizeof shellcode_my_longjmp, PAGE_EXECUTE_READ, &dwOldProtect); void(*my_longjmp)(jmp_buf jb, int value) = (void *)shellcode_my_longjmp; my_longjmp(jb, value); } __declspec(noreturn) void _jmp_to_new_stack(void *stack_buffer, size_t stack_size, void(*function_to_run)(void *userdata), void *userdata, jmp_buf returning, int longjmp_val) { #ifdef _M_IX86 static const uint32_t shellcode_jmp_to_new_stack[] = { 0x608be089, 0x08600304, 0xff1870ff, 0x70ff1470, 0x0c50ff10, 0x6804c483, 0xdeadbeef, 0x909002eb, 0x0424548b, 0x0824448b, 0x8301f883, 0x2a8b00d0, 0x8b045a8b, 0x728b087a, 0x10628b0c, 0xff04c483, 0x90901462, }; #elif defined(_M_X64) static const uint64_t shellcode_jmp_to_new_stack[] = { 0x0148cc8948e08948, 0x4c2870ff3070ffd4, 0xff4120ec8348c989, 0xeb5a5920c48348d0, 0x9090909090909007, 0x4808598b48d08948, 0x4818698b4810618b, 0x4c28798b4820718b, 0x4c38698b4c30618b, 0x0f48798b4c40718b, 0x5c69d9e2db5851ae, 0x6f0f6660716f0f66, 0x80816f0f44667079, 0x896f0f4466000000, 0x6f0f446600000090, 0x0f4466000000a091, 0x4466000000b0996f, 0x66000000c0a16f0f, 0x000000d0a96f0f44, 0x0000e0b16f0f4466, 0x00f0b96f0f446600, 0x9090905061ff0000, }; #else fprintf(stderr, "[UNIMPLEMENTED] Please provide your shellcode for your CPU to implement `jmp_to_new_stack()` by doing these steps:\n"); fprintf(stderr, "[UNIMPLEMENTED] Save your current stack pointer register to a volatile register whichever you'd like to use, the volatile register stores your original stack pointer and could help you to retrieve your parameters;\n"); fprintf(stderr, "[UNIMPLEMENTED] Set your stack pointer register to the end of my stack buffer: `(size_t)stack_buffer + stack_size`;\n"); fprintf(stderr, "[UNIMPLEMENTED] After moving to the new stack, save your 5th and 6th paramters to the new stack;\n"); fprintf(stderr, "[UNIMPLEMENTED] Retrieve your 4th parameter `userdata` from the original stack (using the saved stack pointer in the volatile register);\n"); fprintf(stderr, "[UNIMPLEMENTED] Your 3rd parameter is a pointer to a callback function (the function to run on the new stack). Call it and pass `userdata`. NOTE: This function will destroy your volatile register as usual occasion;\n"); fprintf(stderr, "[UNIMPLEMENTED] After calling the function, balance your stack;\n"); fprintf(stderr, "[UNIMPLEMENTED] Retrieve your 5th (`jmp_buf`) and 6th (`longjmp_value`) parameters from where you saved them on the new stack, these parameters are for returning to your previous stack via a `longjmp()`;\n"); fprintf(stderr, "[UNIMPLEMENTED] IMPORTANT: Both stacks are actively changing, do not attempt to restore the stack pointer directly;\n"); fprintf(stderr, "[UNIMPLEMENTED] Implement and execute a `longjmp(jmp_buf, longjmp_value)` to return to the original stack\n"); assert(0); #endif static DWORD dwOldProtect = 0; if (!dwOldProtect) VirtualProtect((void *)shellcode_jmp_to_new_stack, sizeof shellcode_jmp_to_new_stack, PAGE_EXECUTE_READ, &dwOldProtect); void(*jmp_to_new_stack)(void *, size_t, void(*)(void *), void *, jmp_buf, int) = (void *)shellcode_jmp_to_new_stack; jmp_to_new_stack(stack_buffer, stack_size, function_to_run, userdata, returning, longjmp_val); } ```