进程启动后更改/ proc / PID / environ


Answers:


12

在Linux上,您可以覆盖堆栈上环境字符串的值。

因此,您可以通过用零或其他任何内容覆盖条目来隐藏该条目:

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

int main(int argc, char* argv[], char* envp[]) {
  char cmd[100];

  while (*envp) {
    if (strncmp(*envp, "k=", 2) == 0)
      memset(*envp, 0, strlen(*envp));

    envp++;
  }

  sprintf(cmd, "cat /proc/%u/environ", getpid());

  system(cmd);
  return 0;
}

运行方式:

$ env -i a=foo k=v b=bar ./wipe-env | hd
00000000  61 3d 66 6f 6f 00 00 00  00 00 62 3d 62 61 72 00  |a=foo.....b=bar.|
00000010

k=v已被覆盖用\0\0\0

请注意,setenv("k", "", 1)要覆盖该值将不起作用,因为在这种情况下,将"k="分配一个新字符串。

如果您还没有k使用setenv()/ 修改环境变量putenv(),那么您还应该能够执行以下操作来获取k=v堆栈上字符串的地址(其中之一):

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


int main(int argc, char* argv[]) {
  char cmd[100];
  char *e = getenv("k");

  if (e) {
    e -= strlen("k=");
    memset(e, 0, strlen(e));
  }

  sprintf(cmd, "cat /proc/%u/environ", getpid());

  system(cmd);
  return 0;
}

但是请注意,它仅删除一个的的k=v环境中收到的条目。通常,只有一个,但没有什么可以阻止任何人同时传递k=v1和传递给env列表中的k=v2(和k=v两次)execve()。过去,这就是造成安全漏洞的原因,例如CVE-2016-2381bash当用相同的名称导出变量和函数时,确实有可能在shellshock之前发生。

无论如何,总会有一个小窗口,在此期间env var字符串尚未被覆盖,因此,如果通过公开秘密信息,您可能想找到另一种方法来将秘密信息传递给命令(例如管道)/proc/pid/environ是一个问题。

还要注意,与相反/proc/pid/cmdline/proc/pid/environment只有具有相同euid或root的进程才能访问(或,只有在进程的euid和ruid与看起来不一样的情况下才是root)。

您可以在中向它们隐藏该值/proc/pid/environ,但是它们仍然可以从内存中获取该字符串的任何其他副本,例如通过将调试器附加到该副本。

请参阅https://www.kernel.org/doc/Documentation/security/Yama.txt,以了解防止至少非root用户执行此操作的方法。


8

它一直没有必要覆盖上面(不是真的琴弦)主线程的在Linux堆栈自2010年以来。

两个/proc/self/cmdline/proc/self/environ是由该方法本身在运行时修改的,通过调用的力prctl()功能分别与PR_SET_MM_ARG_START+ PR_SET_MM_ARG_ENDPR_SET_MM_ENV_START+ PR_SET_MM_ENV_END。这些直接设置内存指针到该进程的应用程序的内存空间,内核为每个进程举行,被用于检索的内容/proc/${PID}/cmdline/proc/${PID}/environ,因此命令行和环境报告的ps命令。

因此,只需要构造一个新的参数或环境字符串(而不是向量,请注意,指向的内存必须是实际的字符串数据,已连接并由-定界),然后告诉内核它在哪里。

prctl(2)功能和手册页在Linux手册页中进行了记录environ(7)。什么是没有记载的是,内核拒绝任何试图设置结束地址,或起始地址低于结束地址上方的起始地址; 或将任一地址(重新)设置为零。此外,这也不是Bryan Donlan在2009年提出的原始机制,该机制允许原子地在单个操作中设置开始和结束。而且,内核无法提供获取这些指针的当前值的方法。

这使得使用修改环境和命令行区域非常棘手prctl()。一个人必须调用该prctl()函数最多四次,因为第一次尝试可能导致尝试将起始指针设置为高于结束指针,具体取决于新旧数据在内存中的位置。一个人把它称为进一步如果想要确保这不会导致其他进程的机会之窗在系统上检查进程的内存空间的任意范围内时,新的开始/结束期间的四倍已设置,但尚未设置新的结束/开始。

一次原子系统调用即可一次性设置整个范围,对于应用程序来说,安全使用它要容易得多。

进一步的皱纹是,因为没有很好的理由(给出的检查在内核中,原始数据区的重写性能反正,和事实等同不是特权操作上的任何BSD系统的),在Linux上,这需要超级用户特权。

我写的相当简单setprocargv()setprocenvv()我的工具集功能,即采用这种。从内置工具集(例如setenv和)中进行链式加载程序foreground,从而反映了Linux允许的链式命令参数和环境。

#/ package / admin / nosh / command / clearenv setenv WIBBLE摆动前景暂停\; 真实&
[1] 1057
#hexdump -C / proc / 1057 / cmdline
00000000 66 6f 72 65 67 72 6f 75 6e 64 00 70 61 75 73 65 |前景暂停|
00000010 00 3b 00 74 72 75 65 00 |。;。true。|
00000018
#hexdump -C / proc / 1057 / environ
00000000 57 49 42 42 4c 45 3d 77 6f 62 62 6c 65 00 |摆动=摆动。
0000000e
#hexdump -C / proc / 1058 / cmdline
00000000 70 61 75 73 65 00 |暂停。|
00000006
#hexdump -C / proc / 1058 / environ
00000000 57 49 42 42 4c 45 3d 77 6f 62 62 6c 65 00 |摆动=摆动。
0000000e
# 

请注意,这并不妨碍跟踪进程并通过其他方式(而不是通过这两个伪文件)直接访问其内存的事物,并且当然会在修改字符串之前留出一个窗口,以便可以看到此信息,就像覆盖主线程堆栈上的数据一样。就像覆盖数据一样,这也不考虑在各种情况下(在堆上)复制环境的语言运行库。通常,不要认为这是将“秘密”传递给程序的良好机制,因为(例如)让该程序将一个开放文件描述符继承到一个未命名管道的读取端,并完全在您的控制下读取一个输入缓冲区然后擦拭。

进一步阅读


2
从内核3.18开始,可以使用采用结构prctl_mm_map且不需要root的PR_SET_MM_MAP
filbranden

2
JdeBP,@filbranden由于内核3.5,你可以阅读的ENV的当前值/ argv的指针/proc/$pid/stat(除了其他值,你可能需要struct prctl_mm_map)。另请参阅我的filter_env.c示例以获取小型演示。JdeBP,您可以向setprocargv()/ setprocenvv()函数添加链接吗?
maxschlepzig
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.