
يوفر هذا المستودع بيئة تعليمية لفهم كيفية عمل استغلال RCE في Exim لثغرة CVE-2018-6789.
هذه مجموعة من الملفات والنصوص البرمجية والملاحظات... لإعداد بيئة للتحقيق في ثغرة Exim RCE (CVE-2018-6789). يمكن استخدامها لتنقيح أخطاء Exim، وكتابة الاستغلالات، وتتبّع استدعاءات دوال Exim، والتعرّف على نظام إدارة الذاكرة المخصص في Exim (storeblocks)، ومعرفة كيفية عمل استغلال حقيقي في العالم الواقعي، ...
يجب استخدامها لأغراض أكاديمية فقط!
vagrant-libvirt)نزّل الكود المصدري لـ Exim بتنفيذ الأمر التالي
$ git submodule update --init
يوجد ملف Vagrantfile في الدليل الجذر. يستخدم libvirt كمزوّد للافتراضية (virtualization provider). يتم سحب الصندوق (box) مباشرة من مرآة Fedora عبر box_url لأن تنزيلات Vagrant Cloud معطّلة حالياً (ترحيل HCP)؛ تنشر Fedora صناديق libvirt و VirtualBox فقط هناك (بدون VMware)، و box_url المباشر خاص بالمزوّد، لذا يستهدف هذا الإعداد libvirt فقط.
# -*- mode: ruby -*-
# vi: set ft=ruby :
memory = 8192 # in MiB
cpus = 4
Vagrant.configure("2") do |config|
# Vagrant Cloud downloads are broken; pull the box from Fedora's mirror
config.vm.box = "fedora-44-cloud-base"
config.vm.box_url = "https://download.fedoraproject.org/pub/fedora/linux/releases/44/Cloud/x86_64/images/Fedora-Cloud-Base-Vagrant-libvirt-44-1.7.x86_64.vagrant.libvirt.box"
config.vm.provider "libvirt" do |lv|
lv.memory = memory
lv.cpus = cpus
end
config.vm.provision "shell", inline: <<-SHELL
/vagrant/scripts/setup_vm.sh
SHELL
end
يمكنك تغيير الإعداد كما تشاء، ولكن ضع في اعتبارك أنه على سبيل المثال يستخدم سكربت setup_vm.sh أداة dnf لتثبيت الحزم. إذا كنت تريد استخدام Ubuntu فيجب عليك استبدال أسطر dnf install بـ apt-get install وتعديل أسماء الحزم وفقاً لذلك. ومع ذلك، لا يوجد ضمان بأن يعمل الإعداد بشكل صحيح.
عندما تكون راضياً عن إعدادك، شغّل فقط:
$ vagrant up
لإعداد الجهاز، وبعد ذلك:
$ vagrant ssh
للاتصال به. إذا كنت لا تعرف كيفية استخدام Vagrant فاطّلع هنا: https://www.vagrantup.com/intro/getting-started/
يقوم Vagrant بربط الدليل الحالي (أي المستودع الذي استنسخته للتو) كدليل مشترك في /vagrant. لإنشاء وتشغيل صورة Docker الخاصة بـ Exim، أدخل الأوامر التالية داخل جهازك الافتراضي (vagrant ssh)
[vagrant@localhost ~]$ cd /vagrant
[vagrant@localhost vagrant]$ ./scripts/reset_docker.sh
ستستغرق المرة الأولى وقتاً أطول بكثير لأن Exim سيُبنى من المصدر. إذا عدّلت نصوص تنقيح الأخطاء أو ملفات أخرى سيتم نسخها إلى حاوية docker، يمكنك دائماً استخدام ./scripts/reset_docker.sh لإعادة بناء صورة Docker. وبالتأكيد يمكنك أيضاً اقتطاع الأسطر اللازمة من السكربت وتشغيلها كأوامر منفردة.
عند انتهاء كل شيء يجب أن ترى وحدة تحكم جذرية (root console):
Successfully tagged exim:latest
787f310ef922a1e519cf8bb47f1c4fed5f510da705e7ceefd48f160c980e969c
root@787f310ef922:/opt#
قد تبدو السلاسل الغريبة مختلفة على جهازك، لكنك الآن داخل حاوية Debian Docker تعمل داخل جهاز Fedora الافتراضي على جهازك المضيف.
أولاً، يمكنك إنشاء جلستَي SSH عبر vagrant ssh في نافذتي طرفية. يمكن استخدام إحداهما لتشغيل الاستغلالات والتفاعل مع Exim عبر SMTP. والأخرى تُستخدم لبدء وتشغيل وتنقيح... Exim داخل حاوية Docker. ASLR معطّل في الجهاز الافتراضي حتى تتمكن من تعيين نقاط توقف (breakpoints) موثوقة لا تتغير أثناء جلسات التنقيح.
جلسة مثال:
الطرفية الأولى:
$ vagrant ssh
[vagrant@localhost vagrant]$
الطرفية الثانية:
$ vagrant ssh
[vagrant@localhost vagrant]$ cd /vagrant
[vagrant@localhost vagrant]$ ./scripts/reset_docker.sh
...
# now you are inside the Debian Docker container
root@99296cf63016:/opt# ./run_exim.sh
root@99296cf63016:/opt# ./attach_exim.sh
يخرج سكربت run_exim.sh ويستمر Exim في العمل في الخلفية. يجب أن يُرفق سكربت ./attach_exim.sh أداة gdb بعملية خفير (daemon) Exim الجارية ويعطيك مخرجات مثل هذه:
...
pwndbg: loaded 170 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Attaching to process 14
Reading symbols from /usr/exim/bin/exim-4.89_1-1-fc6d6586-XX-1...done.
...
0x00007ffff6b7f5e3 in __select_nocancel () at ../sysdeps/unix/syscall-template.S:84
84 ../sysdeps/unix/syscall-template.S: No such file or directory.
Breakpoint 1 at 0x5555555c03d2: file smtp_in.c, line 1762.
Breakpoint 2 at 0x5555555c051d: file smtp_in.c, line 1884.
Breakpoint 3 at 0x55555556a2c8: file base64.c, line 154.
Breakpoint 4 at 0x5555555c6aca: file smtp_in.c, line 3690.
Exim يعمل وينتظر الطلبات. نقاط التوقف المضبوطة تأتي من ملف debugging/breakpoints. يمكنك استخدام Ctrl+C لمقاطعة العملية وإعطاء التحكم إلى gdb. يمكنك أيضاً تشغيل أحد سكربتات الاستغلال المرفقة لاختبار ما إذا كان كل شيء يعمل كما هو متوقع:
الطرفية الأولى:
[vagrant@localhost ~]$ cd /vagrant/sploits/
[vagrant@localhost sploits]$ ./sploit_0.py
[+] Opening connection to localhost on port 25: Done
الطرفية الثانية:
Thread 2.1 "exim" hit Breakpoint 2, smtp_reset (reset_point=reset_point@entry=0x555555843078) at smtp_in.c:1884
1884 {
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────
RAX 0x555555843078 ◂— 0x0
RBX 0x0
RCX 0x555555824b40 (store_last_get) —▸ 0x555555843078 ◂— 0x0
RDX 0x555555820b30 (yield_length) ◂— 0x15800001c38
RDI 0x555555843078 ◂— 0x0
RSI 0x0
R8 0x3
R9 0x52
R10 0x73
R11 0x246
R12 0x5555555ec7fa ◂— 'daemon.c'
R13 0x555555843078 ◂— 0x0
R14 0x0
R15 0x0
RBP 0x5555555ee3db ◂— and byte ptr [rax], ah /* ' %s\n' */
RSP 0x7ffffffbe528 —▸ 0x5555555c31d1 (smtp_setup_msg+67) ◂— mov dword ptr [rip + 0x260b6d], 0
RIP 0x5555555c051d (smtp_reset) ◂— push rbp
────────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────────
► 0x5555555c051d <smtp_reset> push rbp
0x5555555c051e <smtp_reset+1> push rbx
0x5555555c051f <smtp_reset+2> sub rsp, 8
0x5555555c0523 <smtp_reset+6> mov rbp, rdi
0x5555555c0526 <smtp_reset+9> mov qword ptr [rip + 0x263657], 0 <0x555555823b88>
0x5555555c0531 <smtp_reset+20> mov dword ptr [rip + 0x263645], 0 <0x555555823b80>
0x5555555c053b <smtp_reset+30> mov dword ptr [rip + 0x26364f], 0 <0x555555823b94>
0x5555555c0545 <smtp_reset+40> mov dword ptr [rip + 0x263699], 0 <0x555555823be8>
0x5555555c054f <smtp_reset+50> mov dword ptr [rip + 0x263687], 0 <0x555555823be0>
0x5555555c0559 <smtp_reset+60> mov dword ptr [rip + 0x263679], 0 <0x555555823bdc>
0x5555555c0563 <smtp_reset+70> mov dword ptr [rip + 0x263677], 0 <0x555555823be4>
────────────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────────────
In file: /opt/exim/src/src/smtp_in.c
1879 Returns: nothing
1880 */
1881
1882 static void
1883 smtp_reset(void *reset_point)
► 1884 {
1885 recipients_list = NULL;
1886 rcpt_count = rcpt_defer_count = rcpt_fail_count =
1887 raw_recipients_count = recipients_count = recipients_list_max = 0;
1888 cancel_cutthrough_connection("smtp reset");
1889 message_linecount = 0;
────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────
00:0000│ rsp 0x7ffffffbe528 —▸ 0x5555555c31d1 (smtp_setup_msg+67) ◂— mov dword ptr [rip + 0x260b6d], 0
01:0008│ 0x7ffffffbe530 —▸ 0x7ffffffbe600 ◂— 0x0
02:0010│ 0x7ffffffbe538 —▸ 0x7ffffffbe540 —▸ 0x555555605f1a ◂— add byte ptr [rip + 0x25203a73], ah
03:0018│ 0x7ffffffbe540 —▸ 0x555555605f1a ◂— add byte ptr [rip + 0x25203a73], ah
04:0020│ 0x7ffffffbe548 —▸ 0x555555843078 ◂— 0x0
05:0028│ 0x7ffffffbe550 ◂— 0x0
06:0030│ 0x7ffffffbe558 —▸ 0x7ffff6b7f5e3 (__select_nocancel+10) ◂— cmp rax, -0xfff
07:0038│ 0x7ffffffbe560 ◂— 0x7ffffffbe560
──────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────
► f 0 5555555c051d smtp_reset
f 1 5555555c31d1 smtp_setup_msg+67
f 2 55555556de43 daemon_go+10909
f 3 55555556de43 daemon_go+10909
f 4 555555583ca5 main+21601
f 5 7ffff6abe2e1 __libc_start_main+241
──────────────────────────────────────────────────────────────────────────────────────────────────────────
Breakpoint smtp_reset
pwndbg>
يمكنك حذف جميع نقاط التوقف بالأمر d والمتابعة بالأمر c للسماح لسكربت sploit_0.py بالعمل حتى ينتهي:
الطرفية الثانية:
Breakpoint smtp_reset
pwndbg> d
pwndbg> c
Continuing.
[Inferior 2 (process 42) exited with code 01]
الطرفية الأولى:
...
220 787f310ef922 ESMTP Exim 4.89_1-1-fc6d6586-XX Mon, 02 Mar 2020 14:47:24 +0000
250-787f310ef922 Hello test.example.org [172.17.0.1]
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250-AUTH PLAIN
250-CHUNKING
250-PRDR
250 HELP
501 Invalid base64 data
[*] Closed connection to localhost port 25
إذا خرجت العملية المُفترعة (forked process) التي قمت بتنقيحها للتو (المرفقة بها)، على سبيل المثال [Inferior 2 (process 42) exited with code 01]، يمكنك إنهاء gdb وتشغيل ./attach_exim.sh مرة أخرى.
تحت debugging يمكنك العثور على بعض السكربتات التي قد تكون مفيدة. أحد السكربتات هو showmem.py. يتيح لك فحص storeblocks الخاصة بـ Exim وقطع الكومة (heap chunks) المقابلة. يمكنك تشغيله داخل gdb باستخدام الأمر smem:
pwndbg> smem
...
[SHOWMEM]: 0x5555558402b0: heap chunk of size 0x000004b0 (used) / data:
[SHOWMEM]: 0x555555840760: heap chunk of size 0x00000030 (used) / data: /lib/x86_64-linux-gnu
[SHOWMEM]: 0x555555840790: heap chunk of size 0x00000050 (used) / data: ...UUU
[SHOWMEM]: 0x5555558407e0: heap chunk of size 0x000000e0 (used) / data:
[SHOWMEM]: 0x5555558408c0: heap chunk of size 0x00000370 (free) / data: .~....
[SHOWMEM]: 0x555555840c30: heap chunk of size 0x00000040 (used) / data:
[SHOWMEM]: 0x555555840c70: heap chunk of size 0x00002020 (used) / data:
[SHOWMEM]: 0x555555840c80: storeblock of size 0x00002000 / data:
[SHOWMEM]: 0x555555842c90: heap chunk of size 0x00002020 (used) / data:
[SHOWMEM]: 0x555555842ca0: storeblock of size 0x00002000 / data: root
[SHOWMEM]: 0x555555844cb0: heap chunk of size 0x00008010 (used) / data:
[SHOWMEM]: 0x55555584ccc0: heap chunk of size 0x00002010 (used) / data:
[SHOWMEM]: 0x55555584ecd0: heap chunk of size 0x00001010 (used) / data: 220 99296cf63016 ESMTP Exim 4.89_1-1-fc6d6586-XX M
[SHOWMEM]: 0x55555584fce0: heap chunk of size 0x0001d320 (free) / data:
المناطق ذات المسافة البادئة (indented) هي الـ storeblocks والمناطق الأخرى هي قطع كومة (glibc). حالياً هذا تقريب لأنني لم أتحقق من القطع قيد الاستخدام مع قوائم glibc الحرة، لذلك قد تكون هناك بعض المؤشرات الخاطئة للكتل المستخدمة/الحرة. يمكنك دائماً استخدام أوامر pwndbg مثل bins و heap... كمصدر إضافي للمعلومات!
ملاحظة: مع libvirt / KVM تتم مزامنة مجلد /vagrant عبر rsync، لذا بعد تعديل الملفات على المضيف قم بتشغيل vagrant rsync (أو أبقِ vagrant rsync-auto يعمل) لنسخ تغييراتك إلى الجهاز الافتراضي قبل إعادة بناء الحاوية.
.
├── debugging # GDB related scripts
│ ├── breakpoints
│ ├── gdbinit
│ ├── showmem.py
│ └── trace.py
├── Dockerfile # Dockerfile to build and debug Exim
├── Exim # Source code for the vulnerable Exim version
├── exim_code_backup # backup of the Exim's vulnerable source code
│ └── exim-fc6d65867e82009a6e0671771728d41d3423a790.zip
├── exim_files # Patched Exim files to build Exim correctly
│ ├── configure
│ ├── eximon.conf
│ ├── Makefile
│ └── Makefile-Linux
├── README.md
├── scripts # Helper scripts to debug Exim
│ ├── attach_exim.sh
│ ├── reset_docker.sh
│ ├── run_exim.sh
│ └── setup_vm.sh
├── sploits # Incremental exploit scripts and a script to find the 0xf1 byte
│ ├── exim_0xf1.py
│ ├── smtp.py
│ ├── sploit_0.py
│ ├── sploit_1.py
│ ├── sploit_2.py
│ ├── sploit_3.py
│ ├── sploit_4.py
│ ├── sploit_5.py
│ ├── sploit_6.py
│ ├── sploit_7.py
│ ├── sploit_8.py
│ ├── sploit_9.py
│ └── sploit_10.py
└── Vagrantfile # Vagrantfile to create the VM that hosts the Docker container
يمكن العثور على سكربتات الاستغلال تحت sploits. وهي مبنية بشكل تدريجي لتسهيل فهم الخطوات المختلفة. وهي مطابقة تقريباً للخطوات المقدمة من @straightblast426 على medium.com.
sploit_10.py هو السكربت النهائي الذي يجب أن يعرض RCE في سكربت واحد. لا يقوم هذا السكربت بإنشاء قشرة عكسية (reverse shell) بل ينشئ ملفاً تحت /tmp. يمكنك تعديل ذلك عن طريق تحرير السطر التالي:
cmd = '/bin/bash -c "touch /tmp/pwned"'
يرجى ملاحظة أن سكربت sploit_10.py المرفق ليس الطريقة الوحيدة لاستغلال الثغرة. هناك، على سبيل المثال، طرق أخرى لترتيب الكومة!
عملية الإعداد (grooming) حساسة للبيئة: أبقِ host_lookup = معطّلاً (اسم عميل DNS العكسي يلوّث الكومة التي يجهّزها الاستغلال) والحاوية على debian:bullseye (الإعداد ينجو فقط مع glibc 2.31 الخاص بـ Debian، وليس مع glibc الخاص بـ Ubuntu أو bookworm). هذا يعني أيضاً أن هذا المستودع سيعمل (كما هو) فقط طالما يوجد توزيعة متاحة توفر glibc 2.31 (صدرت لأول مرة في 2020)!
سيتم تغيير مؤشر next بعنوان معروف مسبقاً (يجب أن يعمل ذلك أيضاً إذا استخدمت الإعداد المطابق). إذا قمت بإطلاق هذا الاستغلال ضد نسخة أخرى من Exim قيد التشغيل فلن يعمل (الاحتمالات ضعيفة جداً). يمكنك تجاوز ASLR ببعض التخمين القسري (brute-forcing). يعمل هذا بشكل جيد لأن Exim يفترع (يستنسخ) نفسه للتعامل مع طلبات العملاء. وهذا يعني أن تخطيط الذاكرة الكلي يبقى كما هو وتحصل على فرصة واقعية لتخمين مؤشر next قسرياً. إذا كنت لا تعرف ما هو مؤشر next فيجب عليك قراءة المراجع أولاً.
إذا بنيت exim بشكل مختلف قليلاً، فقد يكون موقع مؤشر next مختلفاً. يجب عليك العثور على عنوان storeblock الذي يحتوي على acl_smtp_rcpt. اتبع هذه الخطوات واضبط المؤشر في ملفات sploit_xx.py المعنية:
# connect to VM with: vagrant ssh
./run_exim.sh
./attach_exim.sh
# pwndbg starts, then Ctrl-C
pwndbg> smem
[SHOWMEM]: 0x55555562e6b0: heap chunk of size 0x00002020 (used) / data: .2cUUU
[SHOWMEM]: 0x55555562e6c0: storeblock of size 0x00002000 / data: /usr/exim/configure
...
الـ storeblock ذو العنوان 0x55555562e6c0 يحتوي أيضاً على acl_smtp_rcpt:
pwndbg> p acl_smtp_rcpt
$1 = (uschar *) 0x55555562e7f0 "acl_check_rcpt"
لذا في سكربتات الاستغلال، استبدله بالعنوان كما يظهر مع الأمر smem:
# ...
# address of the ACL strings storeblock (not the chunk)
address_of_acl_storeblock = 0x55555562E6C0
# ...