Linux上执行内存中的脚本和程序

在Linux中可以不需要有脚本或者二进制程序的文件在文件系统上实际存在,只需要有对应的数据在内存中,就有办法执行这些脚本和程序。

原理其实很简单,Linux里有办法把某块内存映射成文件描述符,对于每一个文件描述符,Linux会在/proc/self/fd/<文件描述符>这个路径上创建一个对应描述符的实体,这个路径可以当成普通的文件来用,能正常从中读出数据,因此只要有可执行权限,就可以加载后运行。

其中第一步是创建内存到文件描述符的映射,这一步可以靠memfd_create这个系统调用实现。这个系统调用会返回一个文件描述符,关联到一块内存上,默认大小是0,大多数对普通文件描述符可行的操作对这个描述符也都可用,比如read,write,ftruncate,close。write数据进去的时候系统会自动分配合适长度的内存。当所有引用这块内存的fd被close之后,这块内存会被自动释放。

总之memfd_create提供了像操作文件一样操作内存的能力,是一切皆文件理念的体现之一。

而且memfd_create创建的页面默认有可执行权限,在proc底下的对应的描述符文件也有可执行权限。

所以我们只要把脚本或者二进制程序的数据写进memfd_create返回的描述符就已经做完前两步了。其中对于脚本有一些要求,需要带有Shebang(类似#!/usr/bin/env python3这种)。

有一点需要注意,虽然/proc/self/fd/<文件描述符>有描述符文件存在,但实际上这就是个软链接,而我们的数据全在内存里。

写入成功后可以利用execve执行proc下的描述符文件,也可以通过fexecve系统调用直接调用文件描述符。golang没提供fexecve,所以示例用exec.Cmd

例子:

golang 复制代码
package main

import (
	"fmt"
	"os"
	"os/exec"
	"time"

	"golang.org/x/sys/unix"
)

func main() {
    // 名字其实无所谓,传空字符传也许,名字只是方便debug没有其他影响
	fd, err := unix.MemfdCreate("memexec", unix.MFD_CLOEXEC)
	if err != nil {
		panic(err)
	}
	file := os.NewFile(uintptr(fd), "memexec")
	defer func() {
		if err := file.Close(); err != nil {
			panic(err)
		}
	}()
	_, err = file.Write([]byte("#!/usr/bin/env python\nimport math\nprint('Hello, world!')\n"))
	if err != nil {
		panic(err)
	}
	_, err = file.Write([]byte("print(f'{math.sqrt(2)=}')\n"))
	if err != nil {
		panic(err)
	}
    // 因为设置了CLOEXEC,子进程里execve之后看不到这个描述符,会导致调用失败
    // 所以只能用父进程的
	cmd := exec.Command(fmt.Sprintf("/proc/%d/fd/%d", os.Getpid(), fd))
	data, err := cmd.Output()
	fmt.Println("output:", string(data))
	if err != nil {
		panic(err)
	}
}

golang的话还以配合embed把二进制程序的数据提前嵌入程序内,这样写入的时候会比较方便。

安全性:memfd_create创建的东西默认有可执行权限,同时默认也是可写的,很可能会被恶意程序利用,所以目前内核也在推进解决这个问题已经添加了flag可以让不添加可执行权限,这里建议是遵守权限最小化的原则。

memfd原本的用途:用来在内存中创建文件(比如不想在存储器上创建文件时可以用这个),并可以在父子进程间传递(最好配合file sealing api使用,防止数据被意外修改);或者干脆当匿名共享内存用。执行内存中的程序是附带效果。

参考资料

https://magisterquis.github.io/2018/03/31/in-memory-only-elf-execution.html

相关推荐
s_little_monster18 分钟前
【Linux】网络--数据链路层--以太网
linux·运维·网络·经验分享·笔记·学习·计算机网络
belldeep3 小时前
WSL 安装 Debian 12 后,Linux 如何安装 curl , quickjs ?
linux·运维·debian·curl·quickjs
%#RulER#%5 小时前
哈工大计算机系统2024大作业——Hello的程序人生
linux·笔记·程序人生
y1x2y35 小时前
【HITCSAPP 哈工大计算机系统期末大作业】 程序人生-Hello’s P2P
linux·程序人生·ubuntu
God_archer5 小时前
程序人生-hello’s P2P
linux·c语言·汇编·程序人生
西域曼波王5 小时前
哈尔滨工业大学计算机系统大作业程序人生-Hello’s P2P
linux·c语言·编辑器·vim
无效的名字6 小时前
如何安装并使用RustDesk
linux·运维·服务器
Amo Xiang9 小时前
Python 解释器安装全攻略(适用于 Linux / Windows / macOS)
linux·windows·python·环境安装
我的golang之路果然有问题9 小时前
ElasticSearch+Gin+Gorm简单示例
大数据·开发语言·后端·elasticsearch·搜索引擎·golang·gin
电鱼智能的电小鱼10 小时前
虚拟现实教育终端技术方案——基于EFISH-SCB-RK3588的全场景国产化替代
linux·网络·人工智能·分类·数据挖掘·vr