NULL pointers are generally used for validity detection. In fact, there is a conventional rule, that is, invalid pointers are not necessarily NULL, just for the sake of simplicity, as long as the pointer is invalid, the rule sets it to NULL. The result is NULL. This pointer is used to check the pointer validity. Therefore, it cannot be used for other purposes. In fact, NULL is 0, it represents a memory address numbered 0. Aside from that Convention, it is no different from other addr. Simply put, you can select another address as the pointer validity check, for example, 0x1234 and so on. The reason for not selecting other addresses is first, NULL is better to remember, and second, because NULL is 0, it is easy to make Boolean judgments. See the following program:
Void null_func () {printf ("aaaaaaaaaaaaaaaaaaaaaaaaaa/n");} void map_and_call_null () {char * addr = NULL; addr = mmap (NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); addr [0] = '/xff'; addr [1] = '/x25 '; * (unsigned int *) & addr [2] = 6; * (unsigned long *) & addr [6] = (unsigned long) & null_func; void (* aaa) (); aaa = NULL; // set to NULL (* aaa) ();} int main (void) {map_and_call_null (NULL );}
A piece of a is successfully printed, which indicates that NULL can be used as a normal address, so a vulnerability occurs, in theory, unless you completely block the access permission to the memory of the NULL address, this vulnerability cannot be remedied and can only be handled by the programmer. But completely blocking NULL does not comply with the design specifications. The process memory of the user space can be freely accessed by the process. No organization has the right to block the access permission of a piece of memory. Since NULL cannot be blocked, then, according to the rules and compiler features, the pointer in the kernel is initialized to NULL during initialization. If no correct value is given later, it will always be NULL, if a thread does not check the NULL pointer and calls a callback function that may be NULL, the Code mapped to the NULL address will be executed, all the codes mapped to are determined by the user process. In the kernel space, function pointers are generally initialized to a stub function for security reasons, and an error code is directly returned in the stub, another initialization method is to initialize it as a 0xc0000000 pointer. The user space cannot access the kernel space, so it cannot map anything to this address. The kernel space and user space are completely divided. The current kernel generally uses the stub function initialization method, but there are always some exceptions. As described in the vulnerability description, not all things comply with this Convention, therefore, some functions are not initialized as stub, and sendpage in file_operations of socket is one of them, which is implemented as follows:
static ssize_t sock_sendpage(struct file *file,...){struct socket *sock;int flags;sock = file->private_data;flags = !(file->f_flags & O_NONBLOCK) ? 0 : MSG_DONTWAIT;if (more)flags |= MSG_MORE;return sock->ops->sendpage(sock, page, offset, size, flags);}
If sock-> ops-> sendpage is not initialized, It is NULL. If the corresponding protocol family does not use this callback function, it will always be NULL, therefore, you only need to map the NULL address in the user space to the code that modifies the uid or euid to jump from the common permission to the root permission. However, the vulnerability quota is not as simple as the exploitation of kernel suicide vulnerabilities. Because the code is injected into the user space, the current macro of the kernel space cannot be used directly. The task_struct pointer of the current process must be obtained indirectly through the kernel stack, in fact, the current macro of the kernel space is also implemented in this way, but the kernel data structure cannot be dynamically used before the user space compiles the program, after the user space code is injected into the kernel (instead of the kernel, it directs the execution thread of the kernel space to call the user space code ), you can implement it again by following the current implementation method, which is not difficult for Kernel enthusiasts:
static inline unsigned long get_current_4k(void){unsigned long current = 0;asm volatile (" movl %%esp, %0;": "=r" (current));current = *(unsigned long *)(current & 0xfffff000);if (current < 0xc0000000 || current > 0xfffff000)return 0;return current;}
After finding the task_struct of the current process, it is difficult to find and change the uid/euid field, because the user space does not know how the task_struct of the running kernel is implemented, we can only guess through features. The information we know now is the uid, euid, and uid of the current process, the relative position of fields such as euid in task_struct, that is to say, although you do not know the absolute offset of uid, you know the relative offset information of euid and uid, so that you can search by byte, the Code is as follows:
Repeat: current = (unsigned int *) orig_current; (obtained by get_current_4k () while (unsigned long) current <(orig_current + 0x1000-17 )) & (current [0]! = Our_uid | current [1]! = Our_uid | current [2]! = Our_uid | current [3]! = Our_uid) current ++; if (unsigned long) current >=( orig_current + 0x1000-17) {if (orig_current = orig_current_4k) {orig_current = get_current_8k (); goto repeat;} return;} got_root = 1; memset (current, 0, sizeof (unsigned int) * 8 ); // finally modify the uid information of task_struct
In this way, the NULL Pointer Vulnerability can be used to escalate normal user permissions to root user permissions, but can this vulnerability work on windows? Let's do an experiment:
Unsigned long addr = XXX; // any address ranging from 0 to 64 K can be set to NULLchar * p = (char *) VirtualAlloc (LPVOID) addr, 0x1000, MEM_COMMIT, PAGE_READONLY); DWORD dwRequest; BOOL B = VirtualProtect (p, 0x1000, PAGE_READWRITE, & dwRequest );
After the above experiment, we found that both functions failed. Why? In fact, windows clearly defines a 64 K user access zone, that is, the memory in this zone is inaccessible, which avoids the above vulnerabilities in linux, but why does linux not do this? Haha, linux does not block NULL because of the separation of mechanisms and policies. The operating system kernel gives the user space the maximum freedom, does not specify how the memory is mapped, and can be mapped at will. If it is necessary to say that linux's NULL pointer is not blocked as a potential vulnerability, it can only be said that this vulnerability is caused by kernel paths that do not strictly verify whether the pointer is NULL rather than NULL, what needs to be done is not to block NULL, but to add NULL judgment where there is a vulnerability.