无需Ptrace就能实现Linux进程间代码注入

2023-11-28 12:10

本文主要是介绍无需Ptrace就能实现Linux进程间代码注入,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文讲的是 无需Ptrace就能实现Linux进程间代码注入

无需Ptrace就能实现Linux进程间代码注入

ptrace系统调用

ptrace系统调从名字上看是用于进程跟踪的,它提供了父进程可以观察和控制其子进程执行的能力,并允许父进程检查和替换子进程的内核镜像(包括寄存器)的值。其基本原理是: 当使用了ptrace跟踪后,所有发送给被跟踪的子进程的信号(除了SIGKILL),都会被转发给父进程,而子进程则会被阻塞,这时子进程的状态就会被系统标注为TASK_TRACED。而父进程收到信号后,就可以对停止下来的子进程进行检查和修改,然后让子进程继续运行。    

ptrace是如此的强大,以至于有很多大家所常用的工具都基于ptrace来实现。

ptrace可以实时监测和修改另一个进程的运行,它是如此的强大以至于曾经因为它在unix-like平台(如Linux, *BSD)上产生了各种漏洞。

所以,今天我要跟大家介绍的是在不使用ptrace的情况下获得代码注入。由于使用此方法不需要任何系统调用(sys call),因此使用一种简单且无所不在的语言来完成代码注入是可能的。

在不使用ptrace的情况下获得代码注入,就允许用户执行任意的本机代码,当只有标准的Bash shell和coreutils可用时,就可以制作一个从内存中执行二进制的有效载荷绕过noexecmountflag(挂载命令)。

无需Ptrace的进程间代码注入

Linux上的/proc文件系统提供了对Linux系统运行的内省(Introspection),每个进程在文件系统中都有自己的目录,其中包含有关流程及其内部的详细信息。在这个目录中,有两个伪文件,分别是maps文件和mem文件。

maps文件包含分配给二进制文件的所有内存区域架构,以及所有包含的动态库。不过,这个信息现在相对敏感,因为每个库位置的偏移量是由ASLR随机化的。

mem文件提供了流程使用的完整内存空间的稀疏架构,结合从maps文件获得的偏移量,可以使用mem文件读取和写入进程的内存空间。如果偏移量是错误的,或者从开始位置按顺序读取文件,将返回读写错误,因为这相当于是读取不可访问的未分配内存。

译者注:内省(Introspection)是面向对象语言和环境的一个强大特性,它是对象揭示自己作为一个运行时对象的详细信息的一种能力。这些详细信息包括对象在继承树上的位置,对象是否遵循特定的协议,以及是否可以响应特定的消息。

假定没有其他限制访问控制(如SELinux或AppArmor),这些目录中的文件的读写权限是由位于/proc/sys/kernel/yama中的ptrace_scope文件决定的。Linux内核提供了可以设置不同值的文档,比如,对于Linux进程间代码注入,就有两层设置。较低的安全性设置(0和1)允许在同一uid下的任何进程,或者只是父进程,分别写入进程/ proc/${PID}/ mem文件。这些设置中的任何一个都可以进行代码注入。而更安全的设置,2和3,将限制admin写入,或者完全禁止访问。目前,大多数主要操作系统默认设置为“1”,只允许进程的父进程写入其/ proc/${PID}/ mem文件。

这种代码注入方法要使用这些文件,并且这个过程的栈存储在一个标准内存区域内。这可以通过读取一个进程的maps文件看到:

$ grep stack /proc/self/maps
7ffd3574b000-7ffd3576c000 rw-p 00000000 00:00 0                          [stack]

其中,栈包含返回地址(在不使用“链接寄存器”存储返回地址的架构上,例如ARM),因此函数知道返回地址后应在哪个位置继续执行。通常,在诸如缓冲区溢出之类的攻击中,栈是要被覆盖的,而ROP技术则会对目标过程进行控制。ROP技术是用攻击者控制的返回地址替换原始返回地址。这将允许攻击者在每次执行ret指令时通过控制执行流调用自定义函数或系统调用。

虽然此代码注入并不依赖于任何类型的缓冲区溢出,但我确实使用了一个ROP链。考虑到我获得的访问级别,我可以直接将栈写入/ proc/${PID}/ mem中。

因此,该方法使用/proc/self/maps文件来查找ASLR随机偏移量,从中我可以定位目标进程内的函数。使用这些函数地址,我可以替换当前栈上的正常返回地址,并获得进程的控制。为了确保在重写栈时,进程处于预期状态,我使用sleep命令作为被覆盖的从属进程。sleep指令会在系统调用中使用nanosleep,这意味着sleep指令将在几乎整个运行(不包括安装和拆卸)中使用相同的函数。这就使我有足够的机会在系统调用返回之前覆盖整个流程的栈,这样,我将控制我自定义的ROP指令片段(gadget)链。为了确保系统调用执行时栈指针的位置,我会将NOP sled作为载荷的前缀,这样,栈指针几乎就可以指向任何有效的位置,而这些位置在返回后,又会增加栈指针,直到它得到并执行我的有效载荷。

这些注入代码可以在https://github.com/GDSSecurity/Cexigua上找到,不过,为了限制这个脚本的外部依赖,我做出了一些努力,因为在一些非常受限制的环境中,实用程序二进制文件可能不可用。当前的依赖性列表是:

GNU grep(必须支持- fao -byte-offset)

dd(用于读取或写入到一个文件的绝对偏移量)

Bash(用于数学和其他高级脚本特性)

该脚本的一般流程是在后台启动sleep拷贝并记录其进程id(PID),如上所述,sleep命令是一个理想的注入对象,因为在整个运行期间它只执行一个函数,这意味着当覆盖栈时,我不会以意想不到的状态结束。使用这个进程,我就可以发现实例化时哪些库被加载。

使用/proc/${PID}/maps,我就可以尝试找到所有我需要的gadget。如果我在自动加载的库中找不到一个gadget,我将到/usr/lib的系统库中扩展我的搜索,如果我在其他库中找到该gadget,我就可以到下一个进程中使用LD_PRELOAD加载该库。这将使丢失的gadget用于我的载荷。除此之外,我还验证了我发现的gadget(使用一个纯粹的grep命令)也位于加载库的 .text部分。如果gadget不存在,那么它们就有可能在执行时未被加载到可执行内存中,当我试图返回到这个gadget时,就会导致运行崩溃。一句话,这个“预加载”阶段应该会导致包含从标准加载库中丢失的gadget的库的空列表。

一旦我确认所有的gadget都可以提供给我,那我就会启动另一个sleep进程。如果有必要的话,LD_PRELOAD额外的库。现在,我重新在库中找到这些gadget,然后将它们迁移到正确的ASLRbase,这样我就知道这些gadget在目标区域的内存空间中的位置,而不仅仅是在磁盘上的二进制文件。如上所述,我在提交使用它之前,会验证该gadget是否位于可执行内存区域。

我需要的gadget列表相对较短,对于以上的NOP sled,我需要一个NOP来填充所有要求函数调用的寄存器,以及一个用于调用标准函数的gadget。利用该函数组合,我就可以调用任何函数或系统调用,但不允许我执行任何类型的逻辑。一旦这些gadget被找到,我就可以将有效载荷描述文件中的伪指令转换成一个ROP有效载荷。例如,对于一个64位系统,line的“syscall 60 0”将转换为ROPgadget,将“60”加载到RAX寄存器、“0”到RDI,以及一个syscallgadget。这将产生40字节的数据,即3个地址和2个常量,总共8个字节。在执行时,这个系统调用将调用exit(0)。

我还可以调用PLT中的函数,包括从外部库导入的函数,例如glibc。为了定位这些函数的偏移量,它们是由指针而不是系统调用来调用的,所以我需要首先在目标库中解析ELF段头,以找到函数偏移量。一旦我有了偏移量,我就可以将这些设备重新定位,并将它们添加到我的载荷中。

除此之外,我还处理了字符串参数,因为我知道内存栈的位置,因此我可以将字符串附加到有效载荷,并在必要时添加指向它们的指针。例如,fexecvesyscall需要参数数组的char * *。在注入我的载荷之前,我可以生成指针数组,并在执行时将栈上的指针指向一个指针数组,以便将一个正常的栈分配char * *一起使用。

一旦有效载荷被完全序列化,我就可以使用dd在过程中覆盖栈,以及从/proc/${PID}/maps文件中获得栈的偏移量。为了确保我不会遇到任何权限问题,必须使用“exec dd”行来结束注入脚本,它用dd流程替换bash进程,因此将父进程的所有权限从bash转移到dd。

在栈被覆盖之后,我就可以等待sleep二进制程序返回的nanosleepsyscall,这时我的ROP链就获得了应用程序的控制权,载荷将被执行。

以ROP链被注入的特定载荷可以合理地避开一些运行时逻辑(runtime logic)。由于目前,我使用的有效载荷是一个简单的open/memfd_create/sendfile/fexecve程序。它将目标二进制文件与文件系统noexecmountflag分离,然后将二进制文件从内存中执行,绕过noexec限制。由于sleep二进制文件是由bash执行的,因此不可能与二进制文件交互,因为它在dd退出后没有父进程。为了绕过这个限制,可以使用在libfuse分布中存在的一个示例,假定fuse在目标系统上存在:passthrough二进制文件,那么将创建根文件系统的镜像挂载到目标目录。这个新的挂载不是挂载的noexec,因此可以到一个二进制文件浏览这个新的挂载,然后执行。

点此链接,你可以看到允许在当前目录中执行二进制文件是如何作为shell的标准子进程进行的有效载荷。

为了加快执行速度,在预加载和主运行之间缓存由其各自的ASLR base来缓存的gadget将是有用的。这可以通过使用声明-p向磁盘转储关联数组来实现,但是该方法不一定总是合适的。所以你还可以使用重新架构脚本,以在主bash进程的相同环境中执行有效载荷脚本,而不是使用$()执行的子进程。

通过取消对GNU grep的需求,可以进一步限制外部依赖关系。虽然在发现gadget时被认为太慢了,但是可能有更多的优化代码。

所以,这种技术的明显缓解策略是将ptrace_scope设置为一个更严格的值。虽然不能完全禁用系统上的ptrace,但是对于普通用户来说,是无法使用ptrace的,你可以通过向/etc/sysctl.conf添加kernel.yama.ptrace_scope=2来设置。

其他缓解策略包括Seccomp、SELinux或 Apparmor 的组合,以限制获取/proc/${PID}/map或/prop/${PID}/mem这样敏感文件的权限。另外,点击该链接获取Bash ROP和POC代码。




原文发布时间为:2017年9月11日
本文作者:xiaohui
本文来自云栖社区合作伙伴嘶吼,了解相关信息可以关注嘶吼网站。
原文链接

这篇关于无需Ptrace就能实现Linux进程间代码注入的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/429675

相关文章

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo