Adım Adım Linux Kernel Exploitation - 1

Selam dostlar.

Öncelikle bu konuyu bir seri halinde yazacağımı bildirmek isterim. Bu seride, daha önceden yayınlanmış ve CVE kodu CVE-2017-11176 olan zafiyet takip edilerek bir Linux kernel (çekirdek) exploiti geliştirmek için adım adım izlenecek yol anlatılacaktır. Yani bu yazıda yapılan iş, zaten yayınlanmış bir zafiyetin uygulamasını yapmaktır.

Bu seriye, öncelikle yazılım hatasını (bug) anlamak ve onu kernel alanından tetiklemek için yama analiziyle başlanacak, ardından kademeli olarak bir PoC kodu oluşturulacaktır. PoC kodu daha sonra son kısımda ise, arbitrary code execution (keyfi kod çalıştırmak) için kullanılan bir arbitrary system call’a (keyfi sistem çağrısına) dönüştürülecektir.

Hedef kitle, Linux kernel’ının çalışma yapısını yeni öğrenmeye başlayanlar olduğu için, bu çalışma Linux kernel’ının çalışma yapısını ve istismarını zaten bilenler için çok ilginç olmayabilir. Çoğu kernel exploiting makalesi, okuyucunun kernek koduna zaten aşina olduğu ön kabulü ile yazıldığından, bu makaleleri anlamada eksiklikler ortaya çıkabilmektedir. Bu makalede, kernel veri yapısını ve önemli kod yollarını ön planda tutarak diğer makalelerde ortaya çıkan bu boşluğu doldurmaya çalışacağız. Yazının sonunda, istismarın her bir satırı ve bunların kernel üzerindeki etkisi okuyucu tarafından anlaşılmış olması amaçlanmaktadır.

Her şeyi tek bir makalede ele almak imkansız olsa da, istismarı geliştirmek için gereken her kernel yolunu (kernel path) açıklamaya çalışacağız. Bunu uygulamalı bir örnekle desteklenen rehberli bir Linux kernel turu olarak düşünebilirsiniz. Exploit kodu yazma, aslında Linux kernel’ını anlamanın iyi bir yoludur. Ek olarak, bazı hata ayıklama teknikleri, araçlar, yaygınca yapılan hatalar ve bunların nasıl düzeltileceğini göstermeye çalışacağım.

Burada geliştirilen exploit kodu, “mq_notify: double sock_put()” olarak da bilinen CVE-2017-11176 CVE koduna sahip zafiyetin exploit kodudur.
Not: Çoğu Linux işletim sistemi dağıtımı, 2017’nin ortalarında bu zafiyeti gidermek üzere yama da çıkaramamıştır. Ancak şu an elbette bu zafiyet dağıtımlarda giderilmiştir.

Burada gösterilen kernel kodu belirli bir sürümün sahip olduğu kodlara sahiptir (v2.6.32.x) ve bununla birlikte ilgili bug, 4.11.9 sürümüne kadar olan kernel’ları da etkiler. Bu versiyonun çok eski olduğu düşünülebilir, ancak hala birçok yerde kullanılıyor ve bu versiyonda kullanılan bazı kernel yollarını anlamak daha kolay olacaktır. Aynı zamanda bu yazıda kullanılandan daha yeni bir kernel’da eşdeğer kernel yolları bulmak oldukça mümkündür.

Burada oluşturulan exploit kodu bir hedef özelindedir. Bu nedenle, onu başka bir hedefte çalıştırmak için bazı değişiklikler gereklidir (structure offsets/layout, araçlar, fonksiyon adresleri vb). Exploit kodunun son halini burada bulabilirsiniz ancak exploit kodunu olduğu gibi sisteminizde çalıştırmayın, çünkü bu exploit kodu sisteminizi çökertecektir!

Güvenlik zafiyeti bulunan bir kernel’ın kaynak kodunu indirmeniz ve bu kernel çalışırken kodu takip etmeye çalışmanız (hatta daha iyisi, exploit kodunu bu sistemde çalıştırmanız) önerilmektedir.

Uyarı: Lütfen bu yazı dizisinin boyutu sizi korkutmasın, yazının büyük bir bölümü kodlardan oluşmaktadır. Ancak şunu da unutmayın ki, gerçekten kernel exploiting’ine girmek istiyorsanız, büyük miktarlarda kod ve döküman okumaya hazır olmalısınız.

Bu makale, Linux kernel konusunun yalnızca küçük bir alt kümesini kapsamaktadır. Aşağıda belirtilmiş harika kitapları okumanızı tavsiye ederim:

  • Understanding the Linux Kernel (D. P. Bovet, M. Cesati)
  • Understanding Linux Network Internals (C. Benvenuti)
  • A guide to Kernel Exploitation: Attacking the Core (E. Perla, M. Oldani)
  • Linux Device Drivers (J. Corbet, A. Rubini, G. Kroah-Hartman)

LAB KURULUMU

Burada gösterilen kernel kodu belirli bir sürümün sahip olduğu kodlara sahiptir (v2.6.32.x). Ancak, exploit kodunu “Debian 8.6.0 (amd64) ISO” hedefi üzerinde uygulayabilirsiniz. Kodda exploit işlemimizi engellemeyecek küçüklükte değişiklikler bulunabilir, ancak bu bir problem oluşturmamaktadır.

Yukarıdaki imaj, 3.16.36 kernel sürümüne sahiptir. Bu imaj dosyasında ilgili bug’ın olduğu ve bu bug’ın belirtilen kernel’ın çökmesine neden olabildiği test edilmiş ve onaylanmıştır. Koddaki değişikliklerin çoğu, kernel exploitation’ın son aşamalarında yapılacaktır.

Yazılım hatası çeşitli konfigürasyonlarda/mimaride kullanılabilir olsa da, ileri aşamalarda herhangi bir sorun çıkmadan kullanmak için mevcut gereksinimler şunlardır:

  • Kernel sürümü 4.11.9’dan düşük olmalıdır (4.x’ten küçük sürümleri öneririz)
  • “amd64” (x86-64) mimarisinde çalışmak zorundadır
  • Debugging (hata ayıklaması) yapabilmek için root erişiminiz olmalıdır
  • Kernel, SLAB allocator kullanmalıdır
  • SMEP ayarı etkinleştirilmiş olmalıdır
  • kASLR ve SMAP devre dışı bırakılmış olmalıdır
  • Belleğin (Memory/RAM) 512MB ve üzeri olması gerekmektedir
  • Herhangi bir sayıda CPU. Tek CPU yeterli olacaktır. Bunun sebebini ilerleyen bölümlerde anlayacaksınız.

UYARI: Önerilen imaj dosyasındaki kernel farklılığından dolayı CPU sayısının 1 olarak ayarlanması tavsiye edilir. Aksi takdirde, reallocation’ın ek adımlara ihtiyacı olabilir.

Önerilen imaj dosyasındaki varsayılan yapılandırma ayarları, yukarıdaki tüm gereksinimleri karşılamaktadır. Exploit kodunu başka bir hedefe yönelik geliştirmek istiyorsanız, serinin ileri ki bölümlerine iyi bir şekilde odaklanmanız tavsiye edilmektedir.

SLAB/SMEP/SMAP’ın ne olduğunu bilmiyorsanız endişelenmeyin, bu kavramlar ileri kısımlarda ele alınacaktır.

UYARI: Debugging’i kolaylaştırmak için hedef Linux kernel’ını bir sanallaştırma platformuyla çalıştırmalısınız. Ancak, SMEP’i desteklememesi sebebiyle VirtualBox yazılımının kullanılması önerilmemektedir. Örneğin; VMWare yazılımının ücretsiz sürümünü veya SMEP’i destekleyen herhangi bir sanallaştırma aracını kullanabilirsiniz.

Sistem yüklendikten sonra (LiveCD üzerinde geliştirme yapılmamalıdır), sistem yapılandırmasının beklendiği gibi olup olmadığını kontrol etmemiz gerekmektedir.

SLAB/SMEP/SMAP/KASLR Durum Kontrolü

SMEP özelliğinin aktif olup olmadığını anlamak için aşağıdaki komutu çalıştırınız. Komutun çıktısında "smep” yer almalıdır:

$ grep "smep" /proc/cpuinfo
flags   : [...] smep bmi2 invpcid
                ^--- buradaki

Eğer çıktıda “smep” yer almıyorsa, cat /proc/cmdline komutunun çıktısında “nosmep” olmaması gerekmektedir. Eğer varsa, /etc/default/grub dosyasını düzenlemeniz ve aşağıdaki satırları değiştirmeniz gerekmektedir:

# /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="quiet"              // "nosmep" içermemelidir
GRUB_CMDLINE_LINUX="initrd=/install/initrd.gz"  // "nosmep" içermemelidir

Ardından update-grub komutunu çalıştırdıktan sonra sisteminizi yeniden başlatın. Daha sonra bu özellik halen devre dışı ise (cat /proc/cpuinfo komutu ile kontrol edebilirsiniz), başka bir sanallaştırma aracı kullanmanız gerekmektedir.

SMAP özelliği için ise tam tersini yapmanız gerekecek. Öncelikle grep /proc/cpuinfo komutu ile çıktıda “nosmap” olup olmadığı kontrol edilmelidir. Eğer çıktıda “smap” yok ise, her şey yolunda demektir. Aksi takdirde, grub yapılandırma dosyanıza “nosmap” eklemeniz gerekmektedir. Ardından update-grub komutunu çalıştırıp sisteminizi yeniden başlatmanız gerekmektedir.

Burada geliştirilen exploit kodu, sabit kodlanmış adresleri kullanmaktadır. Bu sebeple, kASLR özelliği devre dışı bırakılmalıdır. kASLR özelliği, ASLR (Address Space Layout Randomization) özelliğinin kernel için yapılmış şeklidir. kASLR özelliğini devre dışı bırakmak için grub komut satırına “nokaslr” seçeneğini ekleyebilirsiniz (nosmap özelliğinde yapıldığı gibi). İşlemler sonucunda grub komut satırı aşağıdaki gibi olmalıdır:

GRUB_CMDLINE_LINUX_DEFAULT="quiet nokaslr nosmap"
GRUB_CMDLINE_LINUX="initrd=/install/initrd.gz"

Son olarak, çekirdeğimizin SLAB allocator kullandığından emin olmamız gerekmektedir. Aşağıdaki komut ile kernel’ın SLAB allocator kullandığını doğrulayabilirsiniz:

$ grep "CONFIG_SL.B=" /boot/config-$(uname -r)
CONFIG_SLAB=y

Çıktının CONFIG_SLAB=y olması gerekmektedir. Debian varsayılan olarak SLAB kullanırken Ubuntu varsayılan olarak SLUB kullanır. Eğer hedef kernel SLAB kullanmıyorsa, kernel’ı yeniden derlemeniz gerekir. Yeniden derleyebilmek için ise işletim sistemi dağıtımının dokümantasyonuna bakabilirsiniz.

Yeniden belirtmekte fayda var, yukarıda paylaştığım ISO dosyası tüm bu gereksinimleri karşıladığı için onu kullanmanızı öneririm.

SystemTap’i Yükleme

Daha öncede belirtildiği gibi, kullanılması tavsiye edilen ISO dosyası bug barındıran zafiyetli bir kernel’ı çalıştırır (3.16.36 (uname -v) sürümüne sahiptir ve 3.16.47 versiyonunda yama yapılarak zafiyet giderilmiştir).

UYARI: Kernel’ı güncelleyebileceği için otomatik SystemTap kurulum prosedürünü uygulamamalısınız!

Bu nedenle, zafiyetli sürümümüz için gerekli olan .deb paketlerini indirmemiz ve bu paketleri manuel olarak sistemimize yüklememiz gerekecek. İhtiyacımız olacak paketlerin listesi aşağıdadır:

  • linux-image-3.16.0-4-amd64_3.16.36-1+deb8u1_amd64.deb
  • linux-image-3.16.0-4-amd64-dbg_3.16.36-1+deb8u1_amd64.deb
  • linux-headers-3.16.0-4-amd64_3.16.36-1+deb8u1_amd64.deb

Gerekli paketleri bu linkten indirebilirsiniz, dilerseniz aşağıdaki komutları çalıştırarak indirmeniz mümkün:

# wget https://snapshot.debian.org/archive/debian-security/20160904T172241Z/pool/updates/main/l/linux/linux-image-3.16.0-4-amd64_3.16.36-1%2Bdeb8u1_amd64.deb
# wget https://snapshot.debian.org/archive/debian-security/20160904T172241Z/pool/updates/main/l/linux/linux-image-3.16.0-4-amd64-dbg_3.16.36-1%2Bdeb8u1_amd64.deb
# wget https://snapshot.debian.org/archive/debian-security/20160904T172241Z/pool/updates/main/l/linux/linux-headers-3.16.0-4-amd64_3.16.36-1%2Bdeb8u1_amd64.deb

Paketleri indirdikten sonra aşağıdaki komutlar ile yükleyebilirsiniz:

# dpkg -i linux-image-3.16.0-4-amd64_3.16.36-1+deb8u1_amd64.deb
# dpkg -i linux-image-3.16.0-4-amd64-dbg_3.16.36-1+deb8u1_amd64.deb
# dpkg -i linux-headers-3.16.0-4-amd64_3.16.36-1+deb8u1_amd64.deb

Yüklemeleri tamamladıktan sonra sisteminizi yeniden başlatıp aşağıdaki komut ile systemtap’i sisteminize kurunuz:

# apt install systemtap

Son olarak her şeyin doğru yüklendiğinden emin olmak için aşağıdaki komutu çalıştırınız:

# stap -v -e 'probe vfs.read {printf("read performed\n"); exit()}'
stap: Symbol `SSL_ImplementedCiphers' has different size in shared object, consider re-linking
Pass 1: parsed user script and 106 library script(s) using 87832virt/32844res/5328shr/28100data kb, in 100usr/10sys/118real ms.
Pass 2: analyzed script: 1 probe(s), 1 function(s), 3 embed(s), 0 global(s) using 202656virt/149172res/6864shr/142924data kb, in 1180usr/730sys/3789real ms.
Pass 3: translated to C into "/tmp/stapWdpIWC/stap_1390f4a5f16155a0227289d1fa3d97a4_1464_src.c" using 202656virt/149364res/7056shr/142924data kb, in 0usr/20sys/23real ms.
Pass 4: compiled C into "stap_1390f4a5f16155a0227289d1fa3d97a4_1464.ko" in 6310usr/890sys/13392real ms.
Pass 5: starting run.
read performed                                      // <--------------
Pass 5: run completed in 10usr/20sys/309real ms.

Son Kontroller

systemtap pakerine ek olarak, exploit kodunu derlemek ve çalıştırmak için hedef kernel kullanılacaktır, bu nedenle aşağıdaki komutu çalıştırmalısınız:
# apt install binutils gcc

Şimdi ise exploit kodunu indirin.

$ wget https://raw.githubusercontent.com/lexfo/cve-2017-11176/master/cve-2017-11176.c

Önerilen ISO ve makale hedefleri arasındaki kaynak kod farklılıkları nedeniyle, exploit kodunda yer alan “used-after-freed” nesnesi “kmalloc-1024” yerine “kmalloc-2048” önbellek değerine tanımlanmıştır. Yani exploit kodunun çalışabilmesi için exploit kodunda aşağıdaki değişikliğin yapılması gerekmektedir:

#define KMALLOC_TARGET 2048 // 1024 yerine 2048 yazdık

Bu değişikliğin sebebini ileri kısımları okuduğunuzda daha iyi anlayacaksınız. Şimdi yapmamız gereken, aşağıda gösterildiği gibi kodu derleyip çalıştırmak:

$ gcc -fpic -O0 -std=c99 -Wall -pthread cve-2017-11176.c -o exploit
$ ./exploit
[ ] -={ CVE-2017-11176 Exploit }=-
[+] successfully migrated to CPU#0
[+] userland structures allocated:
[+] g_uland_wq_elt = 0x120001000
[+] g_fake_stack   = 0x20001000
[+] ROP-chain ready
[ ] optmem_max = 20480
[+] can use the 'ancillary data buffer' reallocation gadget!
[+] g_uland_wq_elt.func = 0xffffffff8107b6b8
[+] reallocation data initialized!
[ ] initializing reallocation threads, please wait...
[+] 200 reallocation threads ready!
[+] reallocation ready!
[+] 300 candidates created
[+] parsing '/proc/net/netlink' complete
[+] adjacent candidates found!
[+] netlink candidates ready:
[+] target.pid = -4590
[+] guard.pid  = -4614
[ ] preparing blocking netlink socket
[+] receive buffer reduced
[ ] flooding socket
[+] flood completed
[+] blocking socket ready
[+] netlink fd duplicated (unblock_fd=403, sock_fd2=404)
[ ] creating unblock thread...
[+] unblocking thread has been created!
[ ] get ready to block
[ ][unblock] closing 576 fd
[ ][unblock] unblocking now
[+] mq_notify succeed
[ ] creating unblock thread...
[+] unblocking thread has been created!
[ ] get ready to block
[ ][unblock] closing 404 fd
[ ][unblock] unblocking now
[ 55.395645] Freeing alive netlink socket ffff88001aca5800
[+] mq_notify succeed
[+] guard socket closed
[ 60.399964] general protection fault: 0000 [#1] SMP
... cut (other crash dump info) ...

<<< HIT CTRL-C >>>

Bu, hedef için oluşturulmadığından exploit kodu başarısız oldu ve bize root shell’i vermedi. İleride göreceğiniz üzere, exploit kodu değişiklik gerektiriyor. Ancak, bu exploit kodu ilgili bug’ın barındığını bizlere doğrulamaktadır.

UYARI: Hedefimiz ile önerilen ISO arasındaki diğer kod farklılıkları nedeniyle, bazı kernel çökmeleri almayacaksınız. Bunun nedeni, kernel’ın belirli bir bug’da (yukarıdaki gibi) otomatik olarak çökmek yerine exploit kodunu kapatması veya öldürmesidir. Ancak kernel exploit kodunun çalıştığı anda kararsız bir durumda ve her an çökebilir konumdadır. Exploit kodunu okuduğunuz taktirde kodların arasındaki farkları anlayabilirsiniz.

Kernel Kaynak Kodunu Edinmek

Sistem, kurulumundan sonra kullanıma hazır hale getirildikten sonraki adım, kernel kaynak kodunu edinmektir. Eski bir kernel kullandığımızdan dolayı onu aşağıdaki komutları kullanarak manuel olarak indirmemiz gerekecek:

# wget https://snapshot.debian.org/archive/debian-security/20160904T172241Z/pool/updates/main/l/linux/linux-source-3.16_3.16.36-1%2Bdeb8u1_all.deb

Yüklemek için ise:
# dpkg -i linux-source-3.16_3.16.36-1+deb8u1_all.deb

Kernel kaynak kodu şu konumda bulunmalı: “/usr/src/linux-source-3.16.tar.xz

Hedef kernel çok fazla çökeceğinden, kernel kodunu analiz etmek ve exploit kodunu geliştirmek için ana sisteminizi kullanmanız gerekecektir. Yani bu kaynak kodunu ana sisteminize de indirmenizde fayda var. Hedef zafiyetli makine yalnızca exploit kodunu derlemek/çalıştırmak ve SystemTap işlemi için, SSH aracılığıyla kullanılmalıdır.

Buradan sonraki adımlarda dilediğiniz kod tarama uygulamasını kullanabilirsiniz. Ancak sembollere verimli bir şekilde çapraz referans (cross-reference) verebilmeniz gerektiğini unutmamanız gerekmektedir. Linux, milyonlarca satır koddan oluşan bir kernel olduğu için, iyi bir kod tarama uygulaması olmadan bu kodların içinde kaybolmanız çok olasıdır.

cscope uygulamasının bir çok çekirdek geliştirici tarafından kullanıldığı görülmektedir. Çapraz referans verme işlemini şu şekilde ya da aşağıdaki kod ile yapabilirsiniz:

cscope -kqRubv

Kernel “freestanding” konumda çalışırken system library başlıklarını hariç tutan “-k” parametresine dikkat etmekte fayda var. cscope veritabanı oluşturma işlemi birkaç dakika sürcektir. Ardından cscope eklentisi olan bir metin düzenleyicisi seçmenizde fayda var. (örneğin; vim, emacs)

Artık ilk kernel exploit kodunuzu geliştirmeye hazırsınız!

Hadi kolay gelsin! :)

6 Beğeni