Linux进程伪装:动态修改/proc/self/exe

Linux进程伪装:动态修改/proc/self/exe

我们知道/proc/pid目录下记录了进程的很多信息,比如/proc/pid/cmdline是进程启动时的命令行、/proc/pid/exe是一个软连接指向了该进程可执行ELF、/proc/pid/cwd是一个软连接指向了进程运行的当前路径等等。
为监控查杀恶意进程,主动防御软件在一个进程在启动时,会记录该进程ID、命令行、可执行ELF路径等。其中进程ID、命令行都可以非常容易的动态修改,这里直接给出代码,网上资料很多,细节不再赘述。

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] = '';
    }
    //修改argv[0]
    (void) strcpy(g_main_Argv[0], buf);

    p = &g_main_Argv[0][i];
    while (p < g_main_LastArgv)
        *p++ = '';
    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]。变换进程ID代码就更加简单

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

父进程退出,子进程继续,进程ID改变、逻辑不变。

修改/proc/self/exe链接的几种办法

execve系列函数

这是最直接的办法,execve会在进程ID不变的情况下,将进程内容全部替换、重新装载进程,所以/proc/self/exe也同样被替换了。比如:

//file: a.c
int main()
{
    printf("PID=%dn", getpid());
    getchar();
    return 0;
}

将a.c编译生成a,直接./a执行a,查看该进程的/proc/pid/exe

root@ubuntu1804:~# ls -l /proc/94635/exe 
lrwxrwxrwx 1 root root 0 Sep  9 16:13 /proc/94635/exe -> /root/a

修改代码为

//file: a.c
int main()
{
    printf("PID=%dn", getpid());
    execve("/bin/sh", 0, 0);
    return 0;
}

重新编译执行,查看其exe已经变化

root@ubuntu1804:~# ls -l /proc/94656/exe
lrwxrwxrwx 1 root root 0 Sep  9 16:14 /proc/94656/exe -> /bin/dash

为了更具有实用性,可以使用内存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)’

root@ubuntu1804:~# ls -l /proc/94901/exe
lrwxrwxrwx 1 root root 0 Sep  9 16:47 /proc/94901/exe -> '/memfd: (deleted)'

prctl(PR_SET_MM, PR_SET_MM_EXE_FILE, …)

又是prctl系统调用提供的能力,man中原文如下:

PR_SET_MM_EXE_FILE
                     Supersede the /proc/pid/exe symbolic link with a
                     new one pointing to a new executable file
                     identified by the file descriptor provided in arg3
                     argument.  The file descriptor should be obtained
                     with a regular open(2) call.

                     To change the symbolic link, one needs to unmap all
                     existing executable memory areas, including those
                     created by the kernel itself (for example the
                     kernel usually creates at least one executable
                     memory area for the ELF .text section).

                     In Linux 4.9 and earlier, the PR_SET_MM_EXE_FILE
                     operation can be performed only once in a process's
                     lifetime; attempting to perform the operation a
                     second time results in the error EPERM.  This
                     restriction was enforced for security reasons that
                     were subsequently deemed specious, and the
                     restriction was removed in Linux 4.10 because some
                     user-space applications needed to perform this
                     operation more than once.

需要注意的是,这里有一个前提:one needs to unmap all existing executable memory areas, including those created by the kernel itself。
翻译:要把旧的/proc/self/exe映射的内存都unmap掉,才能使用此命令字。这个操作几乎所有的加壳软件都会做,比如开源的UPX。先写如下代码:

//file: a.c
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 %sn", exe);

    prctl_routine("/bin/sh");

    memset(exe, 0, sizeof(exe));
    readlink("/proc/self/exe", exe, 255);
    printf("After %sn", exe);

    return 0;
}

以上代码尝试动态修改/proc/self/exe,由于这里不再使用execve,因此可以使用readlink读取exe链接内容进行前后对比。直接编译执行会报错:

Before /root/a
prctl: Device or resource busy
After /root/a

我们对a进行UPX加壳,UPX壳在解壳过程中会对映射的可执行ELF内存进行unmap

gcc a.c -o a -static
upx a -o a_upx

之后我们执行a_upx,发现/proc/self/exe成功修改为/bin/dash

Before /root/a_upx
After /bin/dash

其他方式

待探索

转载自

https://xz.aliyun.com/t/10235

本文转载自https://xz.aliyun.com/t/10235,只做本站测试使用,本文观点不代表安强科技社区立场。

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022年3月18日 下午12:31
下一篇 2022年3月18日 下午2:29

相关推荐

发表回复

您的电子邮箱地址不会被公开。