【问题标题】:Can you access another program's stack/heap if you know the address?如果您知道地址,您可以访问另一个程序的堆栈/堆吗?
【发布时间】:2021-10-22 05:53:17
【问题描述】:

只是一个杂项问题。 我有一个 C++ 程序:

#include <stdio.h>

int main() 
{
    while (1) {
        int value1 = 1;
        printf("%p\n", (void *)&value1);
        void* address = (void *)&value1;
        int value2 = *(int*)address;
        printf("%d\n",value2);
    }
    return 0;
}

它的作用是获取value1的地址,将其存储在address中,然后获取address中的变量并将其存储在value2中。 这很好用,但是,我想从另一个 C++ 程序访问该变量。我试过这个(这个文件中有两种方法):

#include<iostream>
#include <cstdint>

int main() {
    std::cout << "before seg fault" << std::flush;
    uintptr_t p = <address>;
    int value = *reinterpret_cast<int *>(p);
    int value2 = *(int*)<address>;
    std::cout << "after seg fault"<< std::flush;
    std::cout << value;
    return 0;
}

这会在尝试访问该值时导致分段错误,这可能是因为操作系统不希望我访问此值,或者因为它在此实例中不存在。

无论这看起来多么不切实际和愚蠢,有没有办法克服这个问题?还是不可能?作为旁注,我为什么应该/为什么不应该这样做?

编辑:我已经批准了 Chris 的回答,因为当您在 linux 上使用 root 运行它时它工作得很好。要求也适用于 Mac 和 Windows 的东西会不会太过分了?我曾尝试在 Mac 系统上运行 poke 程序,但它给出了错误:无法访问 pid 26112:: No such file or directory,由以下行调用: fprintf(stderr, "usage: %s pid address value\n", av[0]);

【问题讨论】:

  • 查找“共享内存”。可以这样做,但它相对复杂,依赖于操作系统,并且需要特别注意避免两个程序访问内存时出现竞争条件。
  • 我想从另一个 C++ 程序访问该变量你的操作系统会阻止这种情况并使地址无用,因为地址是虚拟的每个进程都有自己的地址空间。
  • @NateEldredge 在技术上是错误的答案。共享内存不会让一个进程访问其他进程的堆栈或堆。
  • @SergeyA:是的 - 必须使用在共享块中专门分配的对象。但它是最接近的常用机制。
  • 根据操作系统、硬件和执行代码的权限级别,这可能是可能的。当然,你不需要这个。

标签: c++ pointers memory segmentation-fault ram


【解决方案1】:

每个进程都有自己的地址空间,程序中的地址指的是该地址空间,独立于任何其他地址空间中的地址。因此,当您将显式值放入这样的指针并使用它时,您将在此过程中获得该地址的任何内容(这可能是无效的,因此您会收到 SEGFAULT 或类似错误),而不是访问其他进程.

在大多数操作系统上,有一些方法可以访问另一个进程的地址空间,但需要获得权限。比如在Linux上,这个poke程序可以修改另一个进程的地址空间:

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

int main(int ac, char **av) {
    char    name[64];
    int     fd;
    if (ac != 4) {
        fprintf(stderr, "usage: %s pid address value\n", av[0]);
        exit(1); }
    sprintf(name, "/proc/%.10s/mem", av[1]);
    if ((fd = open(name, O_WRONLY)) < 0) {
        fprintf(stderr, "Can't access pid %s", av[1]);
        perror(":");
        exit(1); }
    lseek(fd, strtol(av[2], 0, 0), SEEK_SET);
    if (write(fd, av[3], strlen(av[3]) + 1) < 0)
        perror("write");
    return 0;
}

如果您随后运行此程序:

#include <stdio.h>
#include <unistd.h>

int main() {
    char data[16] = "test";
    while (1) {
        printf("pid = %d, &data = %p, data = %s\n", getpid(), &data, data);
        sleep(2);
    }
}

它会打印出类似这样的行:

pid = 6376, &data = 0x7ffe255f3190, data = test
pid = 6376, &data = 0x7ffe255f3190, data = test

如果你在另一个窗口/终端运行./poke 6376 0x7ffe255f3190 Hello,它会变成

pid = 6376, &data = 0x7ffe255f3190, data = Hello
pid = 6376, &data = 0x7ffe255f3190, data = Hello

【讨论】:

  • 请注意,这必须以 root 身份运行。
  • 除非您启用了额外的安全性,否则这两个程序只需要由同一用户运行,而不必是 root。
  • 哦。第一次在 Ubuntu 21.04 hirsute 上尝试此操作时出现“权限被拒绝”错误
  • Ubuntu默认设置了更高的安全性——你可以通过echo 0 &gt;/proc/sys/kernel/yama/ptrace_scope开启基于正常权限的访问。
  • @GameDevelopement 旁注:现代操作系统倾向于通过随机化地址空间来增强安全性。这使攻击者无法确切知道您的程序堆栈将在哪里,但它也会让您的生活更加艰难,因为您也不知道。
【解决方案2】:

这会在尝试访问该值时导致分段错误,这可能是因为操作系统不希望我访问此值,或者因为它在此实例中不存在。

后者。一个进程的虚拟内存地址对另一个进程没有意义。除非试图读取任意地址的进程偶然在同一虚拟地址中分配了内存,否则操作系统会注意到它正在做无意义的事情,并终止它以防止进程执行可能有坏事的无意义的事情后果。

如果你知道地址,你能访问另一个程序的栈/堆吗?

在 C++ 中没有标准的方法来做到这一点。该语言缺乏多进程的概念。

进程通信的一种合作方式是使用共享内存。在 C++ 中没有标准的分配共享内存的方法。

在不合作的情况下读取另一个进程的内存也是可能的,尽管操作系统通常会出于明显的原因默认阻止普通用户这样做 - 除非这些进程属于同一组。在 C++ 中也没有执行此操作的标准方法。

我为什么应该/为什么不应该这样做?

协作共享内存对于进程间通信很有用。请注意,还有其他替代方案,例如管道和套接字(但标准 C++ 中没有替代方案)。

另一个进程的非合作读取在用户空间程序中很少有用。我想这样做的调试器是这种罕见的用例之一。

【讨论】:

  • The non-co-operative reading of another process is rarely useful. 内核驱动不一样。
  • @SergeyA 我想说内核驱动程序只占所有程序的一小部分,因此“很少”仍然适用。
  • 然而,他们经常这样做,对于从事内核开发的人来说,这是面包和黄油。因此,当您说“很少有用”时,感觉就像做内核开发没有用。在我看来,像“在狭窄的情况下适当”这样的说法听起来更好。
  • @SergeyA 不,这意味着很少进行内核开发(考虑到一般程序员,而不是特别考虑内核开发人员)。我添加了该声明适用于用户空间的限定条件。
【解决方案3】:

这会在尝试访问该值时导致分段错误,这可能是因为操作系统不希望我访问此值,或者因为它在此实例中不存在。

两者兼而有之:它不存在于您进程的地址空间中因为操作系统不希望您访问其他进程的内存,因此指示内存硬件不要将其映射到您的进程的地址空间。

在一些旧的单用户操作系统中,您可以访问所有内存,因为没有真正的进程隔离、用户或虚拟内存硬件的概念来强制执行任何分离。

不再这样做的原因是因为它的扩展性真的很糟糕。一旦你有一些后台任务,如果它们没有被定期安排(或者如果它们花费太长时间来让处理器),事情就会开始中断。一旦你有几个进程,如果它们不小心践踏彼此的内存,事情就会开始崩溃。

所以现在我们有自包含进程,它们只能通过相互协议进行通信(例如通过映射一些共享内存),但不能践踏彼此的变量。

无论这看起来多么不切实际和愚蠢,有没有办法克服这个问题?还是不可能?作为旁注,我为什么应该/为什么不应该这样做?

您不应该这样做(即,正是您所要求的),原因与操作系统阻止您的原因相同:它会导致系统的随机部分出现无法诊断的错误和安全漏洞。

如果要在进程之间共享内存,可以使用共享内存。它是非常特定于平台的,因此您需要查找特定平台的 API,或使用 Boost.Interprocess。

但是请注意,相同的内存可能会在每个使用它的进程中映射到不同的地址,因此您无法在其中有效地存储指针(但偏移量是可以的),并且实现诸如节点之类的东西需要做很多工作 -基于共享内存中的容器(但 Boost.Interprocess 有您可以使用的容器)。

【讨论】:

    猜你喜欢
    • 2018-09-22
    • 1970-01-01
    • 1970-01-01
    • 2010-09-22
    • 2014-02-03
    • 2016-06-28
    • 2023-03-20
    • 2022-10-20
    • 1970-01-01
    相关资源
    最近更新 更多