0x1 原理

先写一个简单的prctl函数调用的程序,跟踪一下prctl调用流程

#include <stdio.h> 
#include <sys/prctl.h>

int main(){
    prctl(PR_SET_NAME,"TRACE_PRCTL");
    puts("ok");
    return 0;
}

使用gdb 在security_task_prctl函数处断下来

linux kernel 4.4.10 /security/security.c

int security_task_prctl(int option, unsigned long arg2, unsigned long arg3,
             unsigned long arg4, unsigned long arg5)
{
    int thisrc;
    int rc = -ENOSYS;
    struct security_hook_list *hp;

    list_for_each_entry(hp, &security_hook_heads.task_prctl, list) {
        thisrc = hp->hook.task_prctl(option, arg2, arg3, arg4, arg5);
        if (thisrc != -ENOSYS) {
            rc = thisrc;
            if (thisrc != 0)
                break;
        }
    }
    return rc;
}

发现security_task_prctl 最终调用的 hp->hook.task_prctl

在汇编里面可以看到 使用 call QWORD PTR [rbx+0x18]

因此可以通过修改这个值,实现任意函数(6个参数以内)调用,但是还存在一个问题,第一个参数option的类型是int型

因此当我们传入一个64位的值,这个参数就不能用了(smap禁止访问用户空间数据),但是在32位下是没问题的。

32位下利用流程:借鉴看雪以为师傅文章中的思路(基于覆盖vdso的变形):

  • 劫持hp->hook.task_prctl,将其修改为set_memory_rw函数地址。
  • 获得vdso映射地址
  • 调用prctl,修改vdso映射区权限
  • 向vdso写入shellcode
  • 高权限进程调用

64位?call_usermodehelper函数

call_usermodehelper函数是内核运行用户程序的一个api,并且该程序有root的权限(关于这个函数具体的用法我也说不清楚了,自行谷歌吧),但是将task_prctl函数地址修改为call_usermodehelper也是不可行的,因此call_usermoderhelper第一个参数也是64位的,而且要求是完整路径比如("/bin/sh"),但是内核里面有几处函数,调用了这个函数,可以直接用的就是这个函数run_cmd

static int run_cmd(const char *cmd)
{
    char **argv;
    static char *envp[] = {
        "HOME=/",
        "PATH=/sbin:/bin:/usr/sbin:/usr/bin",
        NULL
    };
    int ret;
    argv = argv_split(GFP_KERNEL, cmd, NULL);
    if (argv) {
        ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
        argv_free(argv);
    } else {
        ret = -ENOMEM;
    }

    return ret;
}

内核中引用run_cmd的函数,可以发现poweroff_cmd跟reboot_cmd定义的方法是不一样的,poweroff_cmd是可以被修改的

char poweroff_cmd[POWEROFF_CMD_PATH_LEN] = "/sbin/poweroff";
static const char reboot_cmd[] = "/sbin/reboot";

static int run_cmd(const char *cmd)
{
    char **argv;
    static char *envp[] = {
        "HOME=/",
        "PATH=/sbin:/bin:/usr/sbin:/usr/bin",
        NULL
    };
    int ret;
    argv = argv_split(GFP_KERNEL, cmd, NULL);
    if (argv) {
        ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
        argv_free(argv);
    } else {
        ret = -ENOMEM;
    }

    return ret;
}

static int __orderly_reboot(void)
{
    int ret;

    ret = run_cmd(reboot_cmd);

    if (ret) {
        pr_warn("Failed to start orderly reboot: forcing the issue\n");
        emergency_sync();
        kernel_restart(NULL);
    }

    return ret;
}

static int __orderly_poweroff(bool force)
{
    int ret;

    ret = run_cmd(poweroff_cmd);

    if (ret && force) {
        pr_warn("Failed to start orderly shutdown: forcing the issue\n");

        /*
         * I guess this should try to kick off some daemon to sync and
         * poweroff asap.  Or not even bother syncing if we're doing an
         * emergency shutdown?
         */
        emergency_sync();
        kernel_power_off();
    }

    return ret;
}

64位利用流程

  • 任意写(局部任意写)
  • 找到vdso地址,计算内核基地址
  • 修改poweroff_cmd为自己的命令
  • 修改task_prctl为orderly_poweroff地址
  • 调用prctl函数

stringipc

stringipc这个题目是任意地址读,上一篇文章也已经分析了,因此也可以采用这种方法。

task_prctl函数偏移

preView