在 Linux 中通过 prctl() 更改可执行文件名
Change executable file name via prctl() in Linux
我需要更改 Linux 中的可执行文件的名称。可执行文件的名称通过 argv[0]
传递。它可以在 /proc/pid/cmdline
文件中检查,该文件由可执行文件的名称及其参数组成。我尝试将 prctl()
函数与 PR_SET_MM
参数和另一个可执行文件 hider.out.
的文件描述符一起使用
int prctl_routine(char* name)
{
errno = 0;
int fd = open(name, O_RDONLY);
if(fd < 0)
{
perror("open");
return EXIT_FAILURE;
}
int ret = prctl(PR_SET_MM, PR_SET_MM_EXE_FILE, fd, 0, 0);
if(ret < 0)
{
perror("prctl");
}
close(fd);
return 0;
}
int main(int argc, char* argv[])
{
// ...
// show pid to find the right process
pid_t pid = getpid();
std::cout << "pid = " << pid << std::endl;
prctl_routine(argv[1]);
sleep(1000);
// ...
return 0;
}
运行 这个程序像这样 ./a.out hider.out
并执行 cat /proc/pid/cmdline
我遇到了以下错误:
prctl: Operation is not permitted
问题 0:我是否正确执行带有 O_RDONLY
标志的 open()
函数?
问题一:man prctl 告诉:
“要更改符号 link,需要取消映射所有现有的可执行内存区域,包括那些由内核本身创建的内存区域”。
怎么做到的?
您正在寻找 prctl() 的 PR_SET_NAME 选项。手册(man 2 prctl)说:
PR_SET_NAME (since Linux 2.6.9)
Set the name of the calling thread, using the value in the location pointed to by (char *) arg2. The name can be up to 16 bytes long, including the terminating null byte.
(If the length of the string, including the terminating null byte, exceeds 16 bytes, the string is silently truncated.)
这是您程序中的一个示例(翻译成 C 语言,因为您用 C 而不是 C++ 标记了 post ;-):
#include <sys/prctl.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int prctl_routine(char *name)
{
int ret = prctl(PR_SET_NAME, name);
if(ret < 0)
{
perror("prctl");
}
return 0;
}
int main(int argc, char *argv[])
{
// ...
// show pid to find the right process
pid_t pid = getpid();
printf("pid = %d\n", pid);
if (argv[1]) {
prctl_routine(argv[1]);
sleep(1000);
// ...
}
return 0;
}
我编译它:
$ gcc ptitle.c
我运行它:
$ ./a.out foo
pid = 16812
然后我在 /proc:
中检查了它的名字
$ cat /proc/16812/status
Name: foo
[...]
因此,如果 PR_SET_NAME 没有回答您的问题,我可以建议在 LXC 的源代码中用 PR_SET_MM 做了什么。我在这里复制了他们的功能并安排它使其在 LXC 之外工作:
#include <sys/prctl.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
/*
* Sets the process title to the specified title. Note that this may fail if
* the kernel doesn't support PR_SET_MM_MAP (kernels <3.18).
*/
int setproctitle(char *title)
{
FILE *f = NULL;
int i, fd, len;
char *buf_ptr, *tmp_proctitle;
char buf[4096];
int ret = 0;
ssize_t bytes_read = 0;
static char *proctitle = NULL;
/*
* We don't really need to know all of this stuff, but unfortunately
* PR_SET_MM_MAP requires us to set it all at once, so we have to
* figure it out anyway.
*/
unsigned long start_data, end_data, start_brk, start_code, end_code,
start_stack, arg_start, arg_end, env_start, env_end, brk_val;
struct prctl_mm_map prctl_map;
f = fopen("/proc/self/stat", "r");
if (!f) {
fprintf(stderr, "fopen(stat): '%m' (%d)\n", errno);
return -1;
}
fd = fileno(f);
if (fd < 0) {
fprintf(stderr, "fileno(%p): '%m' (%d)\n", f, errno);
fclose(f);
return -1;
}
bytes_read = read(fd, buf, sizeof(buf) - 1);
if (bytes_read <= 0) {
fprintf(stderr, "read(): '%m' (%d)\n", errno);
fclose(f);
return -1;
}
buf[bytes_read] = '[=10=]';
/* Skip the first 25 fields, column 26-28 are start_code, end_code,
* and start_stack */
buf_ptr = strchr(buf, ' ');
for (i = 0; i < 24; i++) {
if (!buf_ptr) {
fclose(f);
return -1;
}
buf_ptr = strchr(buf_ptr + 1, ' ');
}
if (!buf_ptr) {
fclose(f);
return -1;
}
i = sscanf(buf_ptr, "%lu %lu %lu", &start_code, &end_code, &start_stack);
if (i != 3) {
fclose(f);
return -1;
}
/* Skip the next 19 fields, column 45-51 are start_data to arg_end */
for (i = 0; i < 19; i++) {
if (!buf_ptr) {
fclose(f);
return -1;
}
buf_ptr = strchr(buf_ptr + 1, ' ');
}
if (!buf_ptr) {
fclose(f);
return -1;
}
i = sscanf(buf_ptr, "%lu %lu %lu %*u %*u %lu %lu", &start_data,
&end_data, &start_brk, &env_start, &env_end);
if (i != 5) {
fclose(f);
return -1;
}
/* Include the null byte here, because in the calculations below we
* want to have room for it. */
len = strlen(title) + 1;
tmp_proctitle = realloc(proctitle, len);
if (!tmp_proctitle) {
fclose(f);
return -1;
}
proctitle = tmp_proctitle;
arg_start = (unsigned long)proctitle;
arg_end = arg_start + len;
brk_val = syscall(__NR_brk, 0);
prctl_map = (struct prctl_mm_map){
.start_code = start_code,
.end_code = end_code,
.start_stack = start_stack,
.start_data = start_data,
.end_data = end_data,
.start_brk = start_brk,
.brk = brk_val,
.arg_start = arg_start,
.arg_end = arg_end,
.env_start = env_start,
.env_end = env_end,
.auxv = NULL,
.auxv_size = 0,
.exe_fd = -1,
};
ret = prctl(PR_SET_MM, PR_SET_MM_MAP, &prctl_map,
sizeof(prctl_map), 0);
if (ret == 0)
(void)strncpy((char *)arg_start, title, len);
else
fprintf(stderr, "Failed to set cmdline\n");
fclose(f);
return ret;
}
int main(int argc, char *argv[])
{
// ...
// show pid to find the right process
pid_t pid = getpid();
printf("pid = %d\n", pid);
if (argv[1]) {
setproctitle(argv[1]);
// ...
}
sleep(1000);
return 0;
}
我编译并运行它:
$ gcc ptitle.c
$ ./a.out foo
pid = 3554
然后,cmdline被改变:
$ cat /proc/3554/cmdline
foo
但在这种情况下,stat 文件仍包含可执行文件名称:
$ cat /proc/3554/stat
3554 (a.out)[...]
状态文件也一样:
$ cat /proc/3554/status
Name: a.out
[...]
所以你可能还需要使用我前面回答的代码来更改后者。
我需要更改 Linux 中的可执行文件的名称。可执行文件的名称通过 argv[0]
传递。它可以在 /proc/pid/cmdline
文件中检查,该文件由可执行文件的名称及其参数组成。我尝试将 prctl()
函数与 PR_SET_MM
参数和另一个可执行文件 hider.out.
int prctl_routine(char* name)
{
errno = 0;
int fd = open(name, O_RDONLY);
if(fd < 0)
{
perror("open");
return EXIT_FAILURE;
}
int ret = prctl(PR_SET_MM, PR_SET_MM_EXE_FILE, fd, 0, 0);
if(ret < 0)
{
perror("prctl");
}
close(fd);
return 0;
}
int main(int argc, char* argv[])
{
// ...
// show pid to find the right process
pid_t pid = getpid();
std::cout << "pid = " << pid << std::endl;
prctl_routine(argv[1]);
sleep(1000);
// ...
return 0;
}
运行 这个程序像这样 ./a.out hider.out
并执行 cat /proc/pid/cmdline
我遇到了以下错误:
prctl: Operation is not permitted
问题 0:我是否正确执行带有 O_RDONLY
标志的 open()
函数?
问题一:man prctl 告诉: “要更改符号 link,需要取消映射所有现有的可执行内存区域,包括那些由内核本身创建的内存区域”。
怎么做到的?
您正在寻找 prctl() 的 PR_SET_NAME 选项。手册(man 2 prctl)说:
PR_SET_NAME (since Linux 2.6.9)
Set the name of the calling thread, using the value in the location pointed to by (char *) arg2. The name can be up to 16 bytes long, including the terminating null byte.
(If the length of the string, including the terminating null byte, exceeds 16 bytes, the string is silently truncated.)
这是您程序中的一个示例(翻译成 C 语言,因为您用 C 而不是 C++ 标记了 post ;-):
#include <sys/prctl.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int prctl_routine(char *name)
{
int ret = prctl(PR_SET_NAME, name);
if(ret < 0)
{
perror("prctl");
}
return 0;
}
int main(int argc, char *argv[])
{
// ...
// show pid to find the right process
pid_t pid = getpid();
printf("pid = %d\n", pid);
if (argv[1]) {
prctl_routine(argv[1]);
sleep(1000);
// ...
}
return 0;
}
我编译它:
$ gcc ptitle.c
我运行它:
$ ./a.out foo
pid = 16812
然后我在 /proc:
中检查了它的名字$ cat /proc/16812/status
Name: foo
[...]
因此,如果 PR_SET_NAME 没有回答您的问题,我可以建议在 LXC 的源代码中用 PR_SET_MM 做了什么。我在这里复制了他们的功能并安排它使其在 LXC 之外工作:
#include <sys/prctl.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
/*
* Sets the process title to the specified title. Note that this may fail if
* the kernel doesn't support PR_SET_MM_MAP (kernels <3.18).
*/
int setproctitle(char *title)
{
FILE *f = NULL;
int i, fd, len;
char *buf_ptr, *tmp_proctitle;
char buf[4096];
int ret = 0;
ssize_t bytes_read = 0;
static char *proctitle = NULL;
/*
* We don't really need to know all of this stuff, but unfortunately
* PR_SET_MM_MAP requires us to set it all at once, so we have to
* figure it out anyway.
*/
unsigned long start_data, end_data, start_brk, start_code, end_code,
start_stack, arg_start, arg_end, env_start, env_end, brk_val;
struct prctl_mm_map prctl_map;
f = fopen("/proc/self/stat", "r");
if (!f) {
fprintf(stderr, "fopen(stat): '%m' (%d)\n", errno);
return -1;
}
fd = fileno(f);
if (fd < 0) {
fprintf(stderr, "fileno(%p): '%m' (%d)\n", f, errno);
fclose(f);
return -1;
}
bytes_read = read(fd, buf, sizeof(buf) - 1);
if (bytes_read <= 0) {
fprintf(stderr, "read(): '%m' (%d)\n", errno);
fclose(f);
return -1;
}
buf[bytes_read] = '[=10=]';
/* Skip the first 25 fields, column 26-28 are start_code, end_code,
* and start_stack */
buf_ptr = strchr(buf, ' ');
for (i = 0; i < 24; i++) {
if (!buf_ptr) {
fclose(f);
return -1;
}
buf_ptr = strchr(buf_ptr + 1, ' ');
}
if (!buf_ptr) {
fclose(f);
return -1;
}
i = sscanf(buf_ptr, "%lu %lu %lu", &start_code, &end_code, &start_stack);
if (i != 3) {
fclose(f);
return -1;
}
/* Skip the next 19 fields, column 45-51 are start_data to arg_end */
for (i = 0; i < 19; i++) {
if (!buf_ptr) {
fclose(f);
return -1;
}
buf_ptr = strchr(buf_ptr + 1, ' ');
}
if (!buf_ptr) {
fclose(f);
return -1;
}
i = sscanf(buf_ptr, "%lu %lu %lu %*u %*u %lu %lu", &start_data,
&end_data, &start_brk, &env_start, &env_end);
if (i != 5) {
fclose(f);
return -1;
}
/* Include the null byte here, because in the calculations below we
* want to have room for it. */
len = strlen(title) + 1;
tmp_proctitle = realloc(proctitle, len);
if (!tmp_proctitle) {
fclose(f);
return -1;
}
proctitle = tmp_proctitle;
arg_start = (unsigned long)proctitle;
arg_end = arg_start + len;
brk_val = syscall(__NR_brk, 0);
prctl_map = (struct prctl_mm_map){
.start_code = start_code,
.end_code = end_code,
.start_stack = start_stack,
.start_data = start_data,
.end_data = end_data,
.start_brk = start_brk,
.brk = brk_val,
.arg_start = arg_start,
.arg_end = arg_end,
.env_start = env_start,
.env_end = env_end,
.auxv = NULL,
.auxv_size = 0,
.exe_fd = -1,
};
ret = prctl(PR_SET_MM, PR_SET_MM_MAP, &prctl_map,
sizeof(prctl_map), 0);
if (ret == 0)
(void)strncpy((char *)arg_start, title, len);
else
fprintf(stderr, "Failed to set cmdline\n");
fclose(f);
return ret;
}
int main(int argc, char *argv[])
{
// ...
// show pid to find the right process
pid_t pid = getpid();
printf("pid = %d\n", pid);
if (argv[1]) {
setproctitle(argv[1]);
// ...
}
sleep(1000);
return 0;
}
我编译并运行它:
$ gcc ptitle.c
$ ./a.out foo
pid = 3554
然后,cmdline被改变:
$ cat /proc/3554/cmdline
foo
但在这种情况下,stat 文件仍包含可执行文件名称:
$ cat /proc/3554/stat
3554 (a.out)[...]
状态文件也一样:
$ cat /proc/3554/status
Name: a.out
[...]
所以你可能还需要使用我前面回答的代码来更改后者。