通过 argv 操作进程名称和参数
Manipulating process name and arguments by way of argv
我有一个用 C 编写的程序,只能在 Linux 上运行。我希望能够更改进程名称,如 ps
命令中所示。为此,我直接更改 argv[0]
中的字符串,并使用来自主线程的 prctl(PR_SET_NAME, argv[0])
调用。我还想从动态加载的共享库访问 /proc/self/cmdline
,将来甚至可能从其他程序访问。
我读到要使其工作,我必须使用从 argv[0]
开始的原始内存 space。 ELF标准规定这个space和environ
space是[=15=]
分开的。查看 Postgres 代码中的 ps_status.c,可以看到他们将所有这些 space 用于 argv 字符串。实际上,当我 memset
这个 space 到 'a'
时,我可以在 ps
中看到超过 3000 个字符并从 /proc
文件系统中读取它。当我尝试使用此 space 动态(在运行时)在此 space 中创建新参数时,问题就开始了。 (我已经阅读并从基本测试中知道 Chrome/Chromium 做了类似的事情 - 通过命令行参数在 ps
中导出它的 fork
ed 进程的状态。)任何包含 NULL 分隔符的东西 space 触及原本的环境被视为结束。 (我最初在 cmdline 参数中有 105 个字符,我能够得到 130 个字符,但其他参数最多为 3000 个字符标记未被读取。)由此我得知系统记住了原始大小并且只让我 "read over" 直到字符串结束。 (更改 char** argv 指针没有帮助。)
但是 Chrome 以某种方式做到了这一点。查看 command_line.cc 来源,我看不到直接的方法。
甚至可以这样做吗?如果是这样,怎么办?告诉 Linux 内核 argv 内存和 argc 的大小改变了?
谢谢。
PR_SET_MM_ARG_START
和 PR_SET_MM_ARG_END
让你这样做,如果你是 root(更具体地说,如果进程有 CAP_SYS_RESOURCE
能力)。
用法:
prctl(PR_SET_NAME, constructed_argv[0]);
prctl(PR_SET_MM, PR_SET_MM_ARG_START, constructed_argv, 0, 0);
prctl(PR_SET_MM, PR_SET_MM_ARG_END, end_of_constructed_argv, 0, 0);
这是一个在 systemd 中有据可查的用法示例:
/* Now, let's tell the kernel about this new memory */
if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) {
/* HACK: prctl() API is kind of dumb on this point. The existing end address may already be
* below the desired start address, in which case the kernel may have kicked this back due
* to a range-check failure (see linux/kernel/sys.c:validate_prctl_map() to see this in
* action). The proper solution would be to have a prctl() API that could set both start+end
* simultaneously, or at least let us query the existing address to anticipate this condition
* and respond accordingly. For now, we can only guess at the cause of this failure and try
* a workaround--which will briefly expand the arg space to something potentially huge before
* resizing it to what we want. */
log_debug_errno(errno, "PR_SET_MM_ARG_START failed, attempting PR_SET_MM_ARG_END hack: %m");
if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0) {
log_debug_errno(errno, "PR_SET_MM_ARG_END hack failed, proceeding without: %m");
(void) munmap(nn, nn_size);
goto use_saved_argv;
}
if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) {
log_debug_errno(errno, "PR_SET_MM_ARG_START still failed, proceeding without: %m");
goto use_saved_argv;
}
} else {
/* And update the end pointer to the new end, too. If this fails, we don't really know what
* to do, it's pretty unlikely that we can rollback, hence we'll just accept the failure,
* and continue. */
if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0)
log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
}
Chrome没有什么特别的技巧;相反,这是作弊。它正在覆盖 environ
区域,但它使用空格而不是空字节来分隔参数。这在 ps
中看起来完全相同,但是如果您使用 xxd
或类似的文件检查 /proc/PID/environ
文件,您将看到它在做什么。这让它基本上忽略了你发现的 "Anything which contains the NULL delimiter in space reaching into originally environment is treated as an end."
的约束
我有一个用 C 编写的程序,只能在 Linux 上运行。我希望能够更改进程名称,如 ps
命令中所示。为此,我直接更改 argv[0]
中的字符串,并使用来自主线程的 prctl(PR_SET_NAME, argv[0])
调用。我还想从动态加载的共享库访问 /proc/self/cmdline
,将来甚至可能从其他程序访问。
我读到要使其工作,我必须使用从 argv[0]
开始的原始内存 space。 ELF标准规定这个space和environ
space是[=15=]
分开的。查看 Postgres 代码中的 ps_status.c,可以看到他们将所有这些 space 用于 argv 字符串。实际上,当我 memset
这个 space 到 'a'
时,我可以在 ps
中看到超过 3000 个字符并从 /proc
文件系统中读取它。当我尝试使用此 space 动态(在运行时)在此 space 中创建新参数时,问题就开始了。 (我已经阅读并从基本测试中知道 Chrome/Chromium 做了类似的事情 - 通过命令行参数在 ps
中导出它的 fork
ed 进程的状态。)任何包含 NULL 分隔符的东西 space 触及原本的环境被视为结束。 (我最初在 cmdline 参数中有 105 个字符,我能够得到 130 个字符,但其他参数最多为 3000 个字符标记未被读取。)由此我得知系统记住了原始大小并且只让我 "read over" 直到字符串结束。 (更改 char** argv 指针没有帮助。)
但是 Chrome 以某种方式做到了这一点。查看 command_line.cc 来源,我看不到直接的方法。
甚至可以这样做吗?如果是这样,怎么办?告诉 Linux 内核 argv 内存和 argc 的大小改变了?
谢谢。
PR_SET_MM_ARG_START
和 PR_SET_MM_ARG_END
让你这样做,如果你是 root(更具体地说,如果进程有 CAP_SYS_RESOURCE
能力)。
用法:
prctl(PR_SET_NAME, constructed_argv[0]);
prctl(PR_SET_MM, PR_SET_MM_ARG_START, constructed_argv, 0, 0);
prctl(PR_SET_MM, PR_SET_MM_ARG_END, end_of_constructed_argv, 0, 0);
这是一个在 systemd 中有据可查的用法示例:
/* Now, let's tell the kernel about this new memory */
if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) {
/* HACK: prctl() API is kind of dumb on this point. The existing end address may already be
* below the desired start address, in which case the kernel may have kicked this back due
* to a range-check failure (see linux/kernel/sys.c:validate_prctl_map() to see this in
* action). The proper solution would be to have a prctl() API that could set both start+end
* simultaneously, or at least let us query the existing address to anticipate this condition
* and respond accordingly. For now, we can only guess at the cause of this failure and try
* a workaround--which will briefly expand the arg space to something potentially huge before
* resizing it to what we want. */
log_debug_errno(errno, "PR_SET_MM_ARG_START failed, attempting PR_SET_MM_ARG_END hack: %m");
if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0) {
log_debug_errno(errno, "PR_SET_MM_ARG_END hack failed, proceeding without: %m");
(void) munmap(nn, nn_size);
goto use_saved_argv;
}
if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) {
log_debug_errno(errno, "PR_SET_MM_ARG_START still failed, proceeding without: %m");
goto use_saved_argv;
}
} else {
/* And update the end pointer to the new end, too. If this fails, we don't really know what
* to do, it's pretty unlikely that we can rollback, hence we'll just accept the failure,
* and continue. */
if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0)
log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
}
Chrome没有什么特别的技巧;相反,这是作弊。它正在覆盖 environ
区域,但它使用空格而不是空字节来分隔参数。这在 ps
中看起来完全相同,但是如果您使用 xxd
或类似的文件检查 /proc/PID/environ
文件,您将看到它在做什么。这让它基本上忽略了你发现的 "Anything which contains the NULL delimiter in space reaching into originally environment is treated as an end."