linux ptrace 读内存,[原创] Linux ptrace详细分析系列(一)

2023-11-20 23:59

本文主要是介绍linux ptrace 读内存,[原创] Linux ptrace详细分析系列(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Linux ptrace 详解

(该系列将深入分析Linux ptrace的方方面面,争取一次把它搞定,对后续调试或开发都有益处。不定期更新。)

备注:文章中使用的Linux内核源码版本为Linux 5.9,使用的Linux版本为Linux ubuntu 5.4.0-65-generic

一、简述

ptrace系统调用提供了一个进程(tracer)可以控制另一个进程(tracee)运行的方法,并且tracer可以监控和修改tracee的内存和寄存器,主要用作实现断点调试和系统调用追踪。

tracee首先要被attach到tracer上,这里的attach以线程为对象,在多线程场景(这里的多线程场景指的使用clone CLONE_THREAD flag创建的线程组)下,每个线程可以分别被attach到tracer上。ptrace的命令总是以下面的调用格式发送到指定的tracee上:

一个进程可以通过调用fork()函数来初始化一个跟踪,并让生成的子进程执行PTRACE_TRACEME,然后执行execve(一般情况下)来启动跟踪。进程也可以使用PTRACE_ATTACH或PTRACE_SEIZE进行跟踪。

当处于被跟踪状态时,tracee每收到一个信号就会stop,即使是某些时候信号是被忽略的。tracer将在下一次调用waitpid或与wait相关的系统调用之一)时收到通知。该调用会返回一个状态值,包含tracee停止的原因。tracee发生stop时,tracer可以使用各种ptrace的request来检查和修改tracee。然后,tracer使tracee继续运行,选择性地忽略所传递的信号(甚至传递一个与原来不同的信号)。

当tracer结束跟踪后,发送PTRACE_DETACH信号释放tracee,tracee可以在常规状态下继续运行。

二、函数原型及初步使用

1. 函数原型

ptrace的原型如下:

其中request参数表明执行的行为(后续将重点介绍), pid参数标识目标进程,addr参数表明执行peek和poke操作的地址,data参数则对于poke操作,指明存放数据的地址,对于peek操作,指明获取数据的地址。

返回值,成功执行时,PTRACE_PEEK请求返回所请求的数据,其他情况时返回0,失败则返回-1。

2. 函数定义

ptrace的内核实现在kernel/ptrace.c文件中,内核接口是SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr, unsigned long, data)。其代码如下,整体逻辑简单,需要注意的是对PTRACE_TRACEME和PTRACE_ATTACH进行了特殊处理(对于该函数的参数后续将进行深入解析)。

系统调用都改为了SYSCALL_DEFINE的方式。如何获得上面的定义的呢?这里需要穿插一下SYSCALL_DEFINE的定义(syscall.h):

宏定义进行展开:

__SYSCALL_DEFINEx中的x表示系统调用的参数个数,且sys_ptrace的宏定义如下:

所以对应的__SYSCALL_DEFINEx应该是SYSCALL_DEFINE4,这与上面的定义SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr, unsigned long, data)一致。

仔细观察上面的代码可以发现,函数定义其实在最后一行,结尾没有分号,然后再加上花括号即形成完整的函数定义。前面的几句代码并不是函数的实现(详细的分析可以跟踪源码,出于篇幅原因此处不放出每个宏定义的跟踪)。

定义的转换过程:

而对__MAP宏和__SC_DECL宏的定义如下:

按照如上定义继续进行展开

最后调用asmlinkage long sys_ptrace(long request, long pid, unsigned long addr, unsigned long data);。

为什么要将系统调用定义成宏?主要是因为2个内核漏洞CVE-2009-0029,CVE-2010-3301,Linux 2.6.28及以前版本的内核中,将系统调用中32位参数传入64位的寄存器时无法作符号扩展,可能导致系统崩溃或提权漏洞。

内核开发者通过将系统调用的所有输入参数都先转化成long类型(64位),再强制转化到相应的类型来规避这个漏洞。

2. 初步使用

1. 最简单的ls跟踪

首先通过一个简单的例子来熟悉一下ptrace的使用:

运行结果如下:

3782a4e3fa910b52e0674b2d0dce48ee.png

打印出系统调用号,并等待用户输入。查看/usr/include/x86_64-linux-gnu/asm/unistd_64.h文件(64位系统)查看59对应的系统调用:

0efa386323630b1a995179ad1f1a3383.png

59号恰好为execve函数调用。对上面的过程进行简单总结:

父进程通过调用fork()来创建子进程,在子进程中,执行execl()之前,先运行ptrace(),request参数设置为PTRACE_TRACEME来告诉kernel当前进程正在被trace。当有信号量传递到该进程,进程会stop,提醒父进程在wait()调用处继续执行。然后调用execl(),执行成功后,新程序运行前,SIGTRAP信号量被发送到该进程,子进程停止,父进程在wait()调用处收到通知,获取子进程的控制权,查看子进程内存和寄存器相关信息。

当发生系统调用时,kernel保存了rax寄存器的原始内容,其中存放的是系统调用号,我们可以使用request参数为PTRACE_PEEKUSER的ptrace来从子进程的USER段读取出该值。

系统调用检查结束后,子进程通过调用request参数为PTRACE_CONT的ptrace函数继续执行。

2. 系统调用查看参数

执行结果:

d6a0b9b65ca1768eaf44dcebe3806855.png

在上面的程序中,跟踪的是wirte的系统调用,ls命令总计进行了三次write的调用。request参数为PTEACE_SYSCALL时的ptrace使kernel在进行系统调用进入或退出时stop子进程,这等价于执行PTRACE_CONT并在下一次系统调用进入或退出时stop。

wait系统调用中的status变量用于检查子进程是否已退出,这是用来检查子进程是否被ptrace停掉或是否退出的典型方法。而宏WIFEXITED则表示了子进程是否正常结束(例如通过调用exit或者从main返回等),正常结束时返回true。

3. 系统调用参数-改进版

前面有介绍PTRACE_GETREGS参数,使用它来获取寄存器的值相比前面一种方法要简单很多:

执行结果:

5a06338f656de541466475dd24e41598.png

整体输出与前面的代码无所差别,但在代码开发上使用了PTRACE_GETREGS来获取子进程的寄存器的值,简洁了很多。

参考文献

[1]. https://www.linuxjournal.com/article/6100

[2]. https://blog.csdn.net/u012417380/article/details/60468697

[3]. Linux ptrace man page

最后于 2021-3-5 14:38

被有毒编辑

,原因:

这篇关于linux ptrace 读内存,[原创] Linux ptrace详细分析系列(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

Linux云服务器手动配置DNS的方法步骤

《Linux云服务器手动配置DNS的方法步骤》在Linux云服务器上手动配置DNS(域名系统)是确保服务器能够正常解析域名的重要步骤,以下是详细的配置方法,包括系统文件的修改和常见问题的解决方案,需要... 目录1. 为什么需要手动配置 DNS?2. 手动配置 DNS 的方法方法 1:修改 /etc/res

Linux创建服务使用systemctl管理详解

《Linux创建服务使用systemctl管理详解》文章指导在Linux中创建systemd服务,设置文件权限为所有者读写、其他只读,重新加载配置,启动服务并检查状态,确保服务正常运行,关键步骤包括权... 目录创建服务 /usr/lib/systemd/system/设置服务文件权限:所有者读写js,其他

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

Linux挂载linux/Windows共享目录实现方式

《Linux挂载linux/Windows共享目录实现方式》:本文主要介绍Linux挂载linux/Windows共享目录实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录文件共享协议linux环境作为服务端(NFS)在服务器端安装 NFS创建要共享的目录修改 NFS 配

linux系统中java的cacerts的优先级详解

《linux系统中java的cacerts的优先级详解》文章讲解了Java信任库(cacerts)的优先级与管理方式,指出JDK自带的cacerts默认优先级更高,系统级cacerts需手动同步或显式... 目录Java 默认使用哪个?如何检查当前使用的信任库?简要了解Java的信任库总结了解 Java 信

Linux命令rm如何删除名字以“-”开头的文件

《Linux命令rm如何删除名字以“-”开头的文件》Linux中,命令的解析机制非常灵活,它会根据命令的开头字符来判断是否需要执行命令选项,对于文件操作命令(如rm、ls等),系统默认会将命令开头的某... 目录先搞懂:为啥“-”开头的文件删不掉?两种超简单的删除方法(小白也能学会)方法1:用“--”分隔命

Python内存管理机制之垃圾回收与引用计数操作全过程

《Python内存管理机制之垃圾回收与引用计数操作全过程》SQLAlchemy是Python中最流行的ORM(对象关系映射)框架之一,它提供了高效且灵活的数据库操作方式,本文将介绍如何使用SQLAlc... 目录安装核心概念连接数据库定义数据模型创建数据库表基本CRUD操作创建数据读取数据更新数据删除数据查