$ k=v p &
[1] 3028
有什么办法可以改变不提的p
内容/proc/3028/environ
k=v
尽管 p
还在运行吗?
答案1
在 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
环境中收到的条目的数量。通常,只有一个,但没有什么可以阻止任何人在传递给 的 env 列表中同时传递k=v1
and k=v2
(或两次) 。这就是过去出现安全漏洞的原因,例如k=v
execve()
CVE-2016-2381。bash
当使用相同名称导出变量和函数时,在 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 用户执行此操作的方法。
答案2
没有必要覆盖上面的字符串(实际上不是在) 自 2010 年以来 Linux 上的主线程堆栈。
/proc/self/cmdline
和都/proc/self/environ
可以在运行时由进程本身修改,通过prctl()
分别使用PR_SET_MM_ARG_START
+PR_SET_MM_ARG_END
或PR_SET_MM_ENV_START
+调用函数PR_SET_MM_ENV_END
。它们直接将内存指针设置到进程的应用程序内存空间中,该内存空间由内核为每个进程保存,用于检索/proc/${PID}/cmdline
和的内容/proc/${PID}/environ
,从而检索命令报告的命令行和环境ps
。
因此,只需构造一个新的参数或环境字符串(注意,不是向量,指向的内存必须是实际的字符串数据,连接和␀
分隔)并告诉内核它在哪里。
Linux 函数手册页prctl(2)
和environ(7)
手册页中对此都有记录。什么是不是据记载,内核拒绝任何将起始地址设置为高于结束地址或将结束地址设置为低于起始地址的尝试;或(重新)将任一地址设置为零。此外,这并不是 Bryan Donlan 在 2009 年提出的原始机制,该机制允许在单个操作中原子地设置开始和结束。此外,内核没有提供任何方法得到这些指针的当前值。
这使得它变得棘手调整环境和命令行区域prctl()
。必须调用该prctl()
函数最多四次,因为第一次尝试可能会导致尝试将起始指针设置为高于结束指针,具体取决于旧数据和新数据在内存中的位置。人们必须称其为更远如果要确保这不会导致系统上的其他进程在设置新的开始/结束但新的结束期间有机会检查进程内存空间的任意范围,则四次/start 还没有。
对于应用程序来说,一次设置整个范围的单个原子系统调用会更容易安全地使用。
进一步的问题是,没有真正充分的理由(考虑到内核中的检查,原始数据区域的可重写性)反正,并且事实上,等效操作在任何 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 |foreground.pause| 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 |WIBBLE=摆动。| 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 |WIBBLE=摆动。| 0000000e #
请注意,这不会妨碍跟踪进程并通过其他方式(而不是通过这两个伪文件)直接访问其内存,当然,在修改字符串之前会留下一个窗口,可以在其中看到此信息,只是就像覆盖主线程堆栈上方的数据一样。就像覆盖数据的情况一样,这并没有考虑到在各种情况下(在堆上)复制环境的语言运行时库。一般来说,不要认为这是一种将“秘密”传递给程序的好机制,就像让它继承一个打开的文件描述符到未命名管道的读取端,读入完全在您控制下的输入缓冲区一样然后你擦拭。
进一步阅读
- 蒂莫·西拉宁 (2009-10-02)。为 prctl() 添加了 PR_SET_PROCTITLE_AREA 选项。 Linux 内核邮件列表。
- https://unix.stackexchange.com/a/432681/5132
- 丹尼尔·J·伯恩斯坦。 检查密码接口。 cr.yp.to。
- https://github.com/jdebp/nosh/blob/master/source/setprocargv.cpp
- https://github.com/jdebp/nosh/blob/master/source/setprocenvv.cpp