ctfshow-pwn71
题目:32位的ret2syscall
什么是ret2syscall
ret2syscall(return-to-syscall)就是:利用栈溢出控制寄存器,让程序直接执行 Linux 的系统调用,从而获得 shell 或执行任意系统级操作。
一句话总结:
不跳函数,不用 libc,不用 gadget,直接让程序执行 execve(“/bin/sh”,0,0) 这种系统调用。
checksec:
仅开启NX保护
ida分析:
有明显栈溢出漏洞、gets函数;题目描述以及各处都提示了让用 ret2syscall 来进行攻击,我们可以利用程序中的 gadgets 来获得 shell,而对应的 shell 获取则是利用系统调用。
大致思路是先栈溢出填充缓冲区,利用 gadgets 调用 syscall,也就是传递它的调用号 0xb,用 rax 寄存器传递,然后调用 execve 函数:
1 | execve("/bin/sh",NULL,NULL) |
execve函数:
execve 是 Linux 里最核心、最底层的“执行程序”系统调用。
它不是普通函数,而是 系统调用接口,用于让当前进程“变身”为另一个程序。
c语言原型:
1 | int execve(const char *pathname, char *const argv[], char *const envp[]); |
含义如下:
pathname:要执行的程序路径,例如 /bin/sh
argv[]:传给新程序的参数列表(argv[0] 通常是程序名)
envp[]:环境变量列表(如 PATH=…)
成功时 不会返回,因为当前进程已经被新程序替换。
失败时返回 -1,并设置 errno。
🔥 execve 的本质作用
execve 会把当前进程的代码段、数据段、堆、栈全部替换成新程序的内容。
也就是说:
PID 不变
但进程内容完全变成另一个程序
原来的代码不再执行
这就是为什么 execve 成功后不会返回。
在漏洞利用中,尤其是 ret2syscall,你会让寄存器变成:
1 | rax = 59 # execve 的系统调用号 |
这样内核就会执行:
1 | execve("/bin/sh", NULL, NULL) |
需要用到 eax 系统调用参数传递的寄存器的 gadget:
1 | ROPgadget --binary pwn --only "pop|ret"|grep eax |

execve 函数有三个参数,32 位架构靠栈传递参数,所以要有参数寄存器:ebx(第 1 参)、ecx(第 2 参)、edx(第 3 参),也就是:
1 | ROPgadget --binary pwn --only "pop|ret"|grep ebx |

同理,int 0x80
/bin/sh 的地址:
cyclic缓冲区大小:
exp:
1 | from pwn import * |


