无需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

相关文章

MySQL更新某个字段拼接固定字符串的实现

《MySQL更新某个字段拼接固定字符串的实现》在MySQL中,我们经常需要对数据库中的某个字段进行更新操作,本文就来介绍一下MySQL更新某个字段拼接固定字符串的实现,感兴趣的可以了解一下... 目录1. 查看字段当前值2. 更新字段拼接固定字符串3. 验证更新结果mysql更新某个字段拼接固定字符串 -

java实现延迟/超时/定时问题

《java实现延迟/超时/定时问题》:本文主要介绍java实现延迟/超时/定时问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java实现延迟/超时/定时java 每间隔5秒执行一次,一共执行5次然后结束scheduleAtFixedRate 和 schedu

Java Optional避免空指针异常的实现

《JavaOptional避免空指针异常的实现》空指针异常一直是困扰开发者的常见问题之一,本文主要介绍了JavaOptional避免空指针异常的实现,帮助开发者编写更健壮、可读性更高的代码,减少因... 目录一、Optional 概述二、Optional 的创建三、Optional 的常用方法四、Optio

在Android平台上实现消息推送功能

《在Android平台上实现消息推送功能》随着移动互联网应用的飞速发展,消息推送已成为移动应用中不可或缺的功能,在Android平台上,实现消息推送涉及到服务端的消息发送、客户端的消息接收、通知渠道(... 目录一、项目概述二、相关知识介绍2.1 消息推送的基本原理2.2 Firebase Cloud Me

Spring Boot项目中结合MyBatis实现MySQL的自动主从切换功能

《SpringBoot项目中结合MyBatis实现MySQL的自动主从切换功能》:本文主要介绍SpringBoot项目中结合MyBatis实现MySQL的自动主从切换功能,本文分步骤给大家介绍的... 目录原理解析1. mysql主从复制(Master-Slave Replication)2. 读写分离3.

Redis实现延迟任务的三种方法详解

《Redis实现延迟任务的三种方法详解》延迟任务(DelayedTask)是指在未来的某个时间点,执行相应的任务,本文为大家整理了三种常见的实现方法,感兴趣的小伙伴可以参考一下... 目录1.前言2.Redis如何实现延迟任务3.代码实现3.1. 过期键通知事件实现3.2. 使用ZSet实现延迟任务3.3

基于Python和MoviePy实现照片管理和视频合成工具

《基于Python和MoviePy实现照片管理和视频合成工具》在这篇博客中,我们将详细剖析一个基于Python的图形界面应用程序,该程序使用wxPython构建用户界面,并结合MoviePy、Pill... 目录引言项目概述代码结构分析1. 导入和依赖2. 主类:PhotoManager初始化方法:__in

springboot filter实现请求响应全链路拦截

《springbootfilter实现请求响应全链路拦截》这篇文章主要为大家详细介绍了SpringBoot如何结合Filter同时拦截请求和响应,从而实现​​日志采集自动化,感兴趣的小伙伴可以跟随小... 目录一、为什么你需要这个过滤器?​​​二、核心实现:一个Filter搞定双向数据流​​​​三、完整代码

SpringBoot利用@Validated注解优雅实现参数校验

《SpringBoot利用@Validated注解优雅实现参数校验》在开发Web应用时,用户输入的合法性校验是保障系统稳定性的基础,​SpringBoot的@Validated注解提供了一种更优雅的解... 目录​一、为什么需要参数校验二、Validated 的核心用法​1. 基础校验2. php分组校验3

Python实现AVIF图片与其他图片格式间的批量转换

《Python实现AVIF图片与其他图片格式间的批量转换》这篇文章主要为大家详细介绍了如何使用Pillow库实现AVIF与其他格式的相互转换,即将AVIF转换为常见的格式,比如JPG或PNG,需要的小... 目录环境配置1.将单个 AVIF 图片转换为 JPG 和 PNG2.批量转换目录下所有 AVIF 图