Linux进程伪装:动态修改/proc/self/exe
字数 1253 2025-08-24 07:48:23

Linux进程伪装:动态修改/proc/self/exe 技术详解

1. 背景知识

在Linux系统中,/proc/pid目录下记录了进程的许多重要信息:

  • /proc/pid/cmdline:进程启动时的命令行参数
  • /proc/pid/exe:指向进程可执行ELF文件的软链接
  • /proc/pid/cwd:指向进程运行当前路径的软链接

安全监控软件通常会记录这些信息来检测恶意进程。因此,攻击者需要掌握修改这些信息的技术来隐藏自身。

2. 进程信息修改技术

2.1 修改进程名(cmdline)

通过修改argv[0]可以改变进程名,示例代码:

extern char **environ;
static char **g_main_Argv = NULL;  // pointer to argument vector
static char *g_main_LastArgv = NULL;  // end of argv

void setproctitle_init(int argc, char **argv, char **envp) {
    int i;
    for (i = 0; envp[i] != NULL; i++) // calc envp num
        continue;
    environ = (char **)malloc(sizeof(char *) * (i + 1)); // malloc envp pointer
    
    for (i = 0; envp[i] != NULL; i++) {
        environ[i] = malloc(sizeof(char) * strlen(envp[i]));
        strcpy(environ[i], envp[i]);
    }
    environ[i] = NULL;
    
    g_main_Argv = argv;
    if (i > 0)
        g_main_LastArgv = envp[i - 1] + strlen(envp[i - 1]);
    else
        g_main_LastArgv = argv[argc - 1] + strlen(argv[argc - 1]);
}

void setproctitle(const char *fmt, ...) {
    char *p;
    int i;
    char buf[MAXLINE];
    extern char **g_main_Argv;
    extern char *g_main_LastArgv;
    va_list ap;
    
    p = buf;
    va_start(ap, fmt);
    vsprintf(p, fmt, ap);
    va_end(ap);
    
    i = strlen(buf);
    if (i > g_main_LastArgv - g_main_Argv[0] - 2) {
        i = g_main_LastArgv - g_main_Argv[0] - 2;
        buf[i] = '\0';
    }
    
    // 修改argv[0]
    (void)strcpy(g_main_Argv[0], buf);
    p = &g_main_Argv[0][i];
    while (p < g_main_LastArgv)
        *p++ = '\0';
    g_main_Argv[1] = NULL;
    
    // 调用prctl设置进程名
    prctl(PR_SET_NAME, buf);
}

使用示例:

int main(int argc, char *argv[]) {
    setproctitle_init(argc, argv, environ);
    setproctitle("[sshd]");  // 将进程名修改为[sshd]
    // ...
}

2.2 修改进程ID

通过fork()可以改变进程ID:

// fork返回值为0表示是子进程;大于0表示父进程;小于0表示错误
if (fork() > 0)
    exit(0);  // 父进程退出,子进程继续

3. 修改/proc/self/exe的技术

3.1 使用execve系列函数

execve会在保持进程ID不变的情况下替换整个进程内容,包括/proc/self/exe

示例代码:

int main() {
    printf("PID=%d\n", getpid());
    execve("/bin/sh", 0, 0);  // 替换为/bin/sh
    return 0;
}

高级用法:使用内存ELF文件结合execveat系统调用

static unsigned char elf_file_data[] = {
    // ELF文件内容
};

#define PAGE_SZ 0x1000
#define PAGE_ALIGN(size) (size % PAGE_SZ == 0 ? size : ((size / PAGE_SZ + 1)*PAGE_SZ))

int main() {
    int fd = memfd_create("", 1);
    const int len = sizeof(elf_file_data);
    ftruncate(fd, len);
    
    char *shm = mmap(NULL, PAGE_ALIGN(len), 3, 1, fd, 0);
    if (shm == MAP_FAILED) {
        perror("mmap");
        return -1;
    }
    
    memcpy(shm, elf_file_data, len);
    munmap(shm, PAGE_ALIGN(len));
    execveat(fd, "", 0, 0, 0x1000);
    return 0;
}

执行后/proc/pid/exe会变为'/memfd: (deleted)'

3.2 使用prctl(PR_SET_MM, PR_SET_MM_EXE_FILE, ...)

prctl系统调用提供了直接修改/proc/pid/exe的能力。

重要前提:需要先unmap所有现有的可执行内存区域(包括内核创建的,如ELF的.text段)。

示例代码:

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() {
    char exe[256] = {0};
    readlink("/proc/self/exe", exe, 255);
    printf("Before %s\n", exe);
    
    prctl_routine("/bin/sh");
    
    memset(exe, 0, sizeof(exe));
    readlink("/proc/self/exe", exe, 255);
    printf("After %s\n", exe);
    return 0;
}

注意事项

  1. 普通编译的程序直接运行会报错:"Device or resource busy"
  2. 需要使用UPX等加壳工具先处理程序,因为加壳过程会unmap原有内存映射

操作步骤:

gcc a.c -o a -static
upx a -o a_upx  # 使用UPX加壳
./a_upx         # 运行加壳后的程序

成功执行后/proc/self/exe会被修改为/bin/dash

4. 技术要点总结

  1. 进程伪装三要素

    • 修改进程名(cmdline)
    • 修改进程ID
    • 修改可执行文件路径(/proc/self/exe)
  2. /proc/self/exe修改方法对比

方法 优点 缺点 适用场景
execve 简单直接 会替换整个进程 需要完全替换进程时
execveat+memfd 隐蔽性强 需要准备内存ELF 高级隐蔽需求
prctl 不替换进程 需要先unmap内存 需要保持进程状态时
  1. 安全注意事项
    • prctl方法在Linux 4.9及之前版本只能使用一次
    • 从Linux 4.10开始取消了这一限制
    • 使用prctl前必须确保所有可执行内存区域已被unmap

5. 防御建议

对于安全防护软件,检测此类伪装技术需要注意:

  1. 不要仅依赖/proc信息进行进程识别
  2. 结合多种检测手段,如内存特征分析、行为监控等
  3. 特别注意使用memfd_create和execveat组合的隐蔽技术
  4. 监控prctl系统调用的使用情况
Linux进程伪装:动态修改/proc/self/exe 技术详解 1. 背景知识 在Linux系统中, /proc/pid 目录下记录了进程的许多重要信息: /proc/pid/cmdline :进程启动时的命令行参数 /proc/pid/exe :指向进程可执行ELF文件的软链接 /proc/pid/cwd :指向进程运行当前路径的软链接 安全监控软件通常会记录这些信息来检测恶意进程。因此,攻击者需要掌握修改这些信息的技术来隐藏自身。 2. 进程信息修改技术 2.1 修改进程名(cmdline) 通过修改 argv[0] 可以改变进程名,示例代码: 使用示例: 2.2 修改进程ID 通过 fork() 可以改变进程ID: 3. 修改/proc/self/exe的技术 3.1 使用execve系列函数 execve 会在保持进程ID不变的情况下替换整个进程内容,包括 /proc/self/exe 。 示例代码: 高级用法:使用内存ELF文件结合 execveat 系统调用 执行后 /proc/pid/exe 会变为 '/memfd: (deleted)' 3.2 使用prctl(PR_ SET_ MM, PR_ SET_ MM_ EXE_ FILE, ...) prctl 系统调用提供了直接修改 /proc/pid/exe 的能力。 重要前提 :需要先unmap所有现有的可执行内存区域(包括内核创建的,如ELF的.text段)。 示例代码: 注意事项 : 普通编译的程序直接运行会报错:"Device or resource busy" 需要使用UPX等加壳工具先处理程序,因为加壳过程会unmap原有内存映射 操作步骤: 成功执行后 /proc/self/exe 会被修改为 /bin/dash 4. 技术要点总结 进程伪装三要素 : 修改进程名(cmdline) 修改进程ID 修改可执行文件路径(/proc/self/exe) /proc/self/exe修改方法对比 : | 方法 | 优点 | 缺点 | 适用场景 | |------|------|------|----------| | execve | 简单直接 | 会替换整个进程 | 需要完全替换进程时 | | execveat+memfd | 隐蔽性强 | 需要准备内存ELF | 高级隐蔽需求 | | prctl | 不替换进程 | 需要先unmap内存 | 需要保持进程状态时 | 安全注意事项 : prctl方法在Linux 4.9及之前版本只能使用一次 从Linux 4.10开始取消了这一限制 使用prctl前必须确保所有可执行内存区域已被unmap 5. 防御建议 对于安全防护软件,检测此类伪装技术需要注意: 不要仅依赖/proc信息进行进程识别 结合多种检测手段,如内存特征分析、行为监控等 特别注意使用memfd_ create和execveat组合的隐蔽技术 监控prctl系统调用的使用情况