【问题标题】:Change executable file name via prctl() in Linux在 Linux 中通过 prctl() 更改可执行文件名
【发布时间】:2021-01-31 23:56:35
【问题描述】:

我需要在 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() 函数是否正确?

问题 1:man prctl 告诉: “要更改符号链接,需要取消映射所有现有的可执行内存区域,包括内核自己创建的内存区域”。

怎么可能?

【问题讨论】:

  • 你不是在找setproctitle吗?
  • @zwol 我不想更改进程的标题。我只想从/proc/pid/cmdline 隐藏可执行文件名或将其替换为另一个

标签: c linux system-calls


【解决方案1】:

您正在寻找 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 语言;-):

#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
[...]

【讨论】:

  • 感谢您的回答。您考虑过设置调用线程的名称。但目标是从/proc/pid/cmdline 隐藏执行文件 的名称。所以它应该看起来像cat /proc/16812/cmdline -> foo
  • 我添加了一个受LXC源代码启发的答案。这可能符合您的需要。
【解决方案2】:

所以,如果 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] = '\0';

    /* 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

然后,修改命令行:

$ cat /proc/3554/cmdline
foo

但在这种情况下,stat 文件仍然包含可执行文件名:

$ cat /proc/3554/stat  
3554 (a.out)[...]

状态文件也是如此:

$ cat /proc/3554/status
Name:   a.out
[...]

所以你可能还需要使用我之前答案的代码来更改后者。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-20
    • 1970-01-01
    • 2012-04-28
    • 2021-09-02
    • 1970-01-01
    • 1970-01-01
    • 2022-01-04
    相关资源
    最近更新 更多