在OS X中读取其他进程的内存?

25

我一直在尝试理解如何在Mac OS X上读取其他进程的内存,但是我没有太多的运气。我在网上看到很多示例使用ptracePEEKDATA等选项,但是在BSD上没有这个选项 [man ptrace]

int pid = fork();
if (pid > 0) {
    // mess around with child-process's memory
}

在Mac OS X上如何读写另一个进程的内存?


1
我特别关注的是在我无法控制其他进程的情况下 -- 也就是在探查别人的程序以查看它正在做什么之后(可能是在禁用ASLR之后)。 - Jeremy
1
我不明白您想要读取进程的哪个部分。您是想获取核心转储文件中的墙壁进程地址空间吗?您需要编写处理线程和(有时)共享库的代码。如果只是查看程序二进制文件在内存中的情况,大多数情况下您不需要这些代码。 - user2284570
@user2284570 我对观察或操作一个进程的工作内存感兴趣,而不是可执行代码。 - Jeremy
“操作工作内存”。你的帖子只包含“读取”一词。你是不是也需要在进程地址空间中写入?如果是这样,你可能需要一个十六进制编辑器。它对文件的处理方式相同,但你需要使用进程作为参数。我会尝试看看它是否存在于XNU / Darwin或bsd4.3中(因为苹果基于此BSD构建了他们的操作系统)。 - user2284570
在进程地址空间中编写代码将是一件非常不错的事情。我之前听说过在Linux上访问进程内存的方法,但我不确定它们是否适用于BSD,特别地,我也不确定在OS X中有多少已经被移除,而在其他BSD发行版中则仍然可用。感谢您的建议。 - Jeremy
虚拟寻址类似于文件。当您在文件中查看地址时,它与驱动器上的实际地址不同。文件可能被分段,但操作系统会使其看起来像单个部分。程序实际上并不关心分配或大小,操作系统会处理这些问题。在RAM中是相同的虚拟地址。如果库进行系统调用并写入一个程序不应该写入的特殊区域,则会导致页面错误。由于寄存器有地址,因此它们位于地址空间中。请参见:https://upload.wikimedia.org/wikipedia/commons/3/32/Virtual_address_space_and_physical_address_space_relationship.svg - user2284570
8个回答

16

使用task_for_pid()或其他方法获取目标进程的任务端口,然后您可以使用vm_read()vm_write()等直接操作进程的地址空间。


还有vm_remap,它允许您将外部进程的内存映射到自己的虚拟机中。 - Sergey L.
获取进程的任务端口后,我该如何获取其地址空间?然后,我该如何在地址空间中进一步搜索特定字符串?你能给个例子吗? - snakeninny

12

Matasano Chargen曾经发表过一篇关于将某些调试代码移植到OS X的好文章,其中包括学习如何在另一个进程中读写内存(以及其他内容)。

必须要工作,否则GDB就不能用:

事实证明,苹果公司在无限的智慧下,已经削弱了ptrace()。 OS X手册列出了以下请求代码:

  • PT_ATTACH —选择要调试的进程
  • PT_DENY_ATTACH — 进程可以阻止自己被调试
    [...]

没有提到读取或写入内存或寄存器。 但是如果手册未在错误代码部分还提到了PT_GETREGSPT_SETREGSPT_GETFPREGSPT_SETFPREGS。 那么我检查了ptrace.h。 在那里我找到了:

  • PT_READ_I —读取指令字
  • PT_READ_D — 读取数据字
  • PT_READ_U — 如果您足够老,还可以读取U区域数据
    [...]

这解决了一个问题。我可以读写内存以设置中断点。 但是我仍然无法访问寄存器,而我需要能够操作EIP。


9

我知道这篇帖子已经有100年的历史了,但是对于从搜索引擎来到这里的人:

xnumem 正好可以满足您需要操作和读取进程间内存的需求。

// Create new xnu_proc instance
xnu_proc *Process = new xnu_proc();

// Attach to pid (or process name)
Process->Attach(getpid());

// Manipulate memory
int i = 1337, i2 = 0;
i2 = process->memory().Read<int>((uintptr_t)&i);

// Detach from process
Process->Detach();

3
那个链接已经出现了404错误。我认为它已经移动到https://github.com/gordio/xnumem。 - P i

6
如果您想要在进程之间共享内存块,可以查看shm_open(2)和mmap(2)。在一个进程中分配一块内存并将路径(对于shm_open)传递给另一个进程,两个进程都可以一起使用它。这比像Chris Hanson所提到的在另一个进程的地址空间中游荡要安全得多。当然,如果您无法控制两个进程,则此方法无济于事。
(请注意,shm_open的最大路径长度似乎为26个字节,尽管这似乎没有任何文档记录。)
// Create shared memory block
void* sharedMemory = NULL;
size_t shmemSize = 123456;
const char* shmName = "mySharedMemPath";        
int shFD = shm_open(shmName, (O_CREAT | O_EXCL | O_RDWR), 0600);
if (shFD >= 0) {
    if (ftruncate(shFD, shmemSize) == 0) {
        sharedMemory = mmap(NULL, shmemSize, (PROT_READ | PROT_WRITE), MAP_SHARED, shFD, 0);
        if (sharedMemory != MAP_FAILED) {
            // Initialize shared memory if needed
            // Send 'shmemSize' & 'shmemSize' to other process(es)
        } else handle error
    } else handle error
    close(shFD);        // Note: sharedMemory still valid until munmap() called
} else handle error

...
Do stuff with shared memory
...

// Tear down shared memory
if (sharedMemory != NULL) munmap(sharedMemory, shmemSize);
if (shFD >= 0) shm_unlink(shmName);





// Get the shared memory block from another process
void* sharedMemory = NULL;
size_t shmemSize = 123456;              // Or fetched via some other form of IPC
const char* shmName = "mySharedMemPath";// Or fetched via some other form of IPC
int shFD = shm_open(shmName, (O_RDONLY), 0600); // Can be R/W if you want
if (shFD >= 0) {
    data = mmap(NULL, shmemSize, PROT_READ, MAP_SHARED, shFD, 0);
    if (data != MAP_FAILED) {
        // Check shared memory for validity
    } else handle error
    close(shFD);        // Note: sharedMemory still valid until munmap() called
} else handle error


...
Do stuff with shared memory
...

// Tear down shared memory
if (sharedMemory != NULL) munmap(sharedMemory, shmemSize);
// Only the creator should shm_unlink()

3
对于shm_open的使用存在若干限制,除了提到的路径长度问题之外,在MacOSX中还有一个内核状态设置,将进程的共享内存大小限制在4MB。你可以通过在命令行上执行sysctl -A并搜索“kern.sysv.shmmax”来查看此设置。 - fixermark
不完全如此,似乎只有SYSTEM V IPC中的ftok/shmget/shmat函数受到了大约4MB的限制。如果您使用POSIX shm_open/ftruncate/mmap解决方案(如上所述),则限制可能达到甚至高达16MB(取决于内核)。我们在OSX 10.5-10.9上使用16MB块而没有出现问题(早期版本甚至在10.4上也是如此)。 - Hofi

2
你想使用共享内存方法进行进程间通信。有关其他常见方法的概述,请参见这里
我很快就在这本中找到了你需要的所有UNIX通用API(比我想象的要多得多)。你应该在将来购买它。这本书是一组(几百页)打印出来的man页面,现代机器上很少安装。
每个man页面都详细描述了一个C函数。我很快就在其中找到了shmat()、shmctl()、shmdt()和shmget()等内容,但我没有进行详尽的搜索,也许还有其他内容。
它看起来有点过时,但是:是的,它是现代UNIX操作系统的基本用户空间API,可以追溯到古老的80年代。
更新:书中描述的大多数函数都是POSIX C头文件的一部分,您不需要安装任何东西。有少数例外,比如“curses”等原始库。

@JeremyBanks 我需要在你的评论下再写一个答案,但我有几个问题:你知道虚拟寻址从用户进程的角度来看是什么吗(注意:维基百科没有真正解释这个)?你知道进程隔离是什么以及它如何处理IPC吗?这是操作系统提供的基本服务,用于给予内存空间隔离:想象一下,一个没有管理员权限的正在运行的进程可以读取另一个进程上的未加密密码。启动一个ring0驱动程序就是黑客实现这一目标的目的。 - user2284570
@JeremyBanks:有一件事我忘了:是的,你可以使用/dev/mem和/dev/kmem,但从Mac OS X的x86版本开始,苹果出于许多原因删除了这些设备,包括安全性。Linux也采取了类似的措施,通过一个配置编译选项,大多数Linux发行版启用了它:禁用对/dev/mem的完全访问(即使是root),并将其限制为仅限程序(如Xorg)使用的地址。 - user2284570
我对于虚拟内存的了解不够,也不清楚现代操作系统中它是如何工作的细节,但我知道这会破坏内存隔离并且需要管理员权限。我可以想象最简单的情况,比如修改 C 程序中的 static int,它可能会被分配到同一内存位置,或以某种方式被定位。我有点想象一些查看游戏状态的游戏机器人就是这样运作的。我不确定这是否可行。 - Jeremy
@JeremyBanks:我找到了这个实用工具源代码 - user2284570

2
我确实找到了你需要的短实现(只有一个源文件(main.c))。它是专门为XNU设计的。使用关键字“dump process memory os x”在谷歌搜索中,它排名前十。
源代码在这里,但从虚拟地址空间的严格角度来看,你可能更感兴趣这个问题:OS X:如何在不关闭进程的情况下生成核心转储?(还可以看看这个)。当你查看gcore源代码时,由于需要处理线程及其状态,这相当复杂...
在大多数Linux发行版中,gcore程序现在是GDB包的一部分。我认为OSX版本是与xcode/开发工具一起安装的。
更新:wxHexEditor是一款可以编辑设备的编辑器。它也可以像常规文件一样编辑进程内存。它可以在所有UNIX机器上工作。

有趣的东西,我会看一下。谢谢。 - Jeremy

1
一般来说,我建议您使用常规的open()函数打开一个临时文件。一旦在两个进程中都打开了该文件,您可以从文件系统中unlink()它,这样您就可以像使用shm_open一样设置好了。这个过程与Scott Marcy为shm_open指定的过程非常相似。
这种方法的缺点是,如果执行unlink()的进程崩溃,您将得到一个未使用的文件,并且没有进程负责清理它。这个缺点与shm_open共享,因为如果没有shm_unlink给定的名称,该名称将保留在共享内存空间中,可以由未来的进程shm_open。

1

在进程背后操纵其内存是一件危险的事情,充满了风险。这就是为什么Mac OS X(像任何Unix系统一样)具有受保护的内存,并使进程彼此隔离。

当然可以做到:有共享内存的设施,可以在明确合作的进程之间进行共享。还有一些方法可以操作其他进程的地址空间,只要执行此操作的进程具有明确的权限(由安全框架授予)。但这是供编写调试工具的人使用的。对于在Mac OS X上进行的绝大多数开发来说,这不应该是正常的甚至是罕见的事件。


我猜你经常听到许多“请坐下...”类型的笑话,对吧?:P - horseyguy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接