internet pencereler Android
Genişletmek

Linux - Syscalls. Linux'ta sistem çağrıları

Bu malzeme, "Sistem Yöneticisi" dergisinde yayınlanan Vladimir Meshkov tarafından makalenin makalesinin bir modifikasyonudur.

Bu matreal, Zhuranala "Sistem Yöneticisi" ile Vladimir Meshkov makalelerinin kopyalarıdır. Bu makaleler aşağıdaki bağlantılara göre bulunabilir. Programların kaynak metinlerinin bazı örnekleri de değiştirildi, geliştirildi, değiştirildi. (Örnek 4.2, biraz farklı bir sistem çağrısına girmem gerektiğinden) URL'ler: http://www.samag.ru/img/uploaded/p.pdf http://www.samag.ru/img/uploaded / A3. PDF.

Sorularım var? Sonra buradayım: [E-posta Korumalı]

  • 2. İndirilebilir çekirdek modülü
  • 4. LKM'ye dayalı sistem çağrısının görüşü örnekleri
    • 4.1 Katalog oluşturmanın yasaklanması

1. Linux mimarisinde genel Wang

En yaygın dalga, iki seviyeli bir sistem modeli görmenizi sağlar. çekirdek<=> Merkezdeki progs (solda) sistemin çekirdeğini buldu. Çekirdek doğrudan, bilgisayarın bir donanım parçası ile etkileşime girer, uygulamalı programları mimarlığın özelliklerinden izole etmek. Çekirdek, uygulamalar tarafından sağlanan bir dizi hizmete sahiptir. Çekirdek hizmetleri arasında giriş / çıkış işlemlerini (dosyaları açın, okuyun, yazın ve yönetin), süreçleri oluşturma ve yönetme, senkronizasyon ve işleme etkileşimi. Tüm uygulamalar sistem çağrıları aracılığıyla çekirdek hizmetleri istemek.

İkinci seviye, Linux kullanıcı arayüzünü sağlayan hem sistemik, sistem işlevselliğini ve uygulamasını tanımlayan uygulamalar veya görevlerdir. Bununla birlikte, uygulamaların dış heterojenliğine rağmen, çekirdeğe olan etkileşim şemaları aynıdır.

Çekirdekle etkileşim, standart sistem arama arayüzünden oluşur. Sistem arama arayüzü bir dizi çekirdek hizmettir ve Hizmetler İsteklerinin formatını tanımlar. İşlem, hizmetin sistem aracılığıyla, belirli bir çekirdek prosedürünü, harici olarak günlük olarak kütüphane işlevine benzer şekilde benzer şekilde çağırır. İşlem adına çekirdek bir istek yerine getirir ve gerekli verileri sürece geri döndürür.

Örnekte, program dosyayı açar, verileri ondan okur ve bu dosyayı kapatır. Aynı zamanda, açılış işlemi (açık), okuma (okunur) ve dosyanın kapanması, görevin talebi üzerine çekirdek tarafından yapılır ve (2) işlevi, okuma (2) ve kapanır (2) ) İşlev sistem çağrılarıdır.

/ * Kaynak 1.0 * / #include ana () () (int fd; char buf; / * dosyayı açın - bir bağlantı alıyoruz (dosya tanımlayıcısı) FD * / FD \u003d Open ("file1", o_rdonly); / * Buf 80 karakter * (FD, BUF, SizeOF (BUF)); / * * / Kapat dosyasını (FD);) / * EOF * / OS Linux sisteminin tam listesi /OSR/include/asm/Unistd.h dosya. Şimdi sistem çağrıları yapmak için mekanizmayı düşünelim. bu örnek. Derleyici, dosyayı açmak için Açık () işlevini karşılaştıktan sonra, bir montaj koduna dönüştürerek, bu fonksiyona karşılık gelen sistem arama numarasını ve işlemci kayıtlarına parametrelerini ve daha sonra kesme 0x80'in aramasını sağlar. Aşağıdaki değerler işlemci kayıtlarına yüklenir:

  • eAX Kayıt, sistem arama numarasıdır. Bu nedenle, bizim durumumuz için, sistem arama numarası 5'tir (bkz. __NR_OPEN).
  • eBX kaydı ilk fonksiyon parametresidir (açık () için açılan dosyanın adını içeren bir dize için bir işaretçidir.
  • eCX Kayıtlarında - İkinci parametre (dosya erişim hakları)
Üçüncü parametre EDX kaydına yüklenir, bu durumda eksiktir. Sistem çağrısını OS Linux'ta gerçekleştirmek için, System_Call işlevi, /usr/src/ling/arch/i386/Kernel/ENtry.s dosyasında tanımlanan (bu durumda i386'daki mimariye bağlı olarak) tanımlanmıştır. Bu özellik, tüm sistem çağrılarının giriş noktasıdır. Çekirdek, 0x80'in kesilmesine göre, aslında bir kesme işleyicisi 0x80 olan System_Call işlevine bakın.

Doğru yolda olduğumuzdan emin olmak için, LIBC sistem kitaplığındaki açık () işlev kodunu göz önünde bulundurun:

# Gdb -q / lib/libc.so.6 (GDB) Montajcı Dökümü Açık Döküm İçin kod İşlev Açık: 0x000C8080 : 0x1082be'yi arayın.< __i686.get_pc_thunk.cx > 0x000C8085. : $ 0x6423b $ ekleyin,% ECX 0x000C808B : CMPL $ 0x0.0x1A84 (% ECX) 0x000C8092 : Jne 0xc80b1 0x000C8094 : İtin% ebx 0x000c8095 : MOV 0x10 (% esp, 1),% EDX 0x000C8099 : MOV 0XC (% ESP, 1),% ECX 0x000C809D : MOV 0x8 (% ESP, 1),% EBX 0x000C80A1 : MOV $ 0x5,% eax 0x000C80A6 : Int 0x80 $ ... Son satırlarda farketmek zor değil, parametreler EDX kayıtlarına, ECX, EBX'e aktarılır ve sistem arama numarası, 5 tanesini zaten bildiğimiz için son EAX kaydına yerleştirilir.

Ve şimdi sistem arama mekanizmasının değerlendirilmesine geri dönelim. Böylece, çekirdek bir kesme işleyicisi 0x80 - System_Call işlevi çağırır. System_Call, Save_all makroyu kullanarak STEK'te arama parametrelerini içeren kayıtların kopyalarını yerleştirir ve arama komutu istenen sistem fonksiyonuna neden olur. Sistem çağrılarını uygulayan çekirdek fonksiyonlarının işaretçi tablosu SYS_CALL_TABLE Dizisinde bulunur (bkz. Arch / I386 / Kernel / Giriş.s). EAX kaydında bulunan sistem arama numarası bu dizideki bir dizintir. Böylece, EAX'te 5 değeri varsa, SYS_OPEN () çekirdek işlevi neden olur. Neden bir makro save_all ihtiyacınız var? Buradaki açıklama çok basittir. Neredeyse tüm çekirdek sistemi işlevleri C'ye yazıldığından, yığındaki parametrelerini arıyorlar. Ve parametreler, Save_all kullanarak yığın üzerine yerleştirilir! Sistem çağrısı tarafından döndürülen değer EAX kaydında saklanır.

Şimdi sistem çağrısının nasıl kesileceğini öğrenelim. Bu yüklü çekirdek modüllerinin bu mekanizmasında bize yardımcı olacaktır.

2. İndirilebilir çekirdek modülü

Downloadlanabilir çekirdek modülü (genellikle LKM yüklenebilir çekirdek modülünün kabul edildi) - çekirdek alanında gerçekleştirilen program kodu. Ana özellik LKM, tüm sistemi yeniden başlatmaya veya çekirdeğin yeniden derlenmesi gerekmeden dinamik yükleme ve boşaltma yeteneğidir.

Her LKM iki temel işlevden oluşur (minimum):

  • modül başlatma özelliği. Bellekte LKM yüklerken çağrılır: int init_module (geçersiz) (...)
  • modül boşaltma işlevi: Void CleanUp_Module (boşluk) (...)
En basit modülün bir örneğini verelim: / * Source 2.0 * / #include İnt init_module (geçersiz) ("merhaba dünya \\ n"); geri dönüş 0;) Void CleanUp_Module (geçersiz) (PrintK ("Güle güle \\ n");) / * EOF * / Derleme ve modülü yükleyin. Modülü hafızaya indirmek InsMod komutu tarafından gerçekleştirilir ve yüklenen modülleri LSMOD komutu tarafından görüntülenir: # GCC -C -DModule -i / USR / SRC / Linux / Dahil / SRC-2.0.C / Linux / Dahil / SRC-2.0.C # InSMOD SRC-2.0.O UYARI: Yükleniyor SRC-2.0 .O Kernel'i terk edecek: NO Lisans Modülü SRC-2.0 yüklü, uyarılar ile # DMESG | Kuyruğu -N 1 merhaba dünya # lsmod | GREP SRC SRC-2.0 336 0 (Kullanılmayan) # RMMOD SRC-2.0 # DMESG | Kuyruk -N 1 Hoşçakal

3. LKM'ye dayalı olarak ele geçirilen sistem çağrısı için algoritma

Bir modülü uygulamak, bir sistem çağrısını yakalamak için, önleme algoritmasını belirlemek gerekir. Algoritma aşağıdakilerdir:
  • İşaretçiyi restore etmek için orijinal (kaynak) meydan okumaya kaydet
  • yeni bir sistem araması uygulayan bir özellik oluşturun
  • sYS_CALL_Table sistem çağrısı tablosunda, yedek bir arama yapın, yani karşılık gelen işaretçiyi yeni bir sistem çağrısına yapılandırın
  • İşin sonunda (modülü boşaltırken), daha önce kaydedilmiş bir işaretçi kullanarak orijinal sistem çağrısını geri yükleyin.
Kullanıcı uygulaması tarafından hangi sistem çağrılarının kullanıldığını öğrenmek için izlemeye izin verir. İzleyerek, uygulamanın kontrolünü ele geçirmek için hangi sistem çağrısının hangi sistem çağrısının yakalanması gerektiğini belirleyebilirsiniz. # ltrace -s ./src-1.0 ... Open ("file1", 0, 01 Sys_open ("file1", 0, 01) \u003d 3<... open resumed>) \u003d 3 oku (3, Sys_read (3, "123 \\ n", 80) \u003d 4<... read resumed> "123 \\ n", 80) \u003d 4 Kapat (3 Sys_close (3) \u003d 0<... close resumed>) \u003d 0 ... Şimdi, sistem çağrılarını engelleyen modüllerin uygulanmasının örneklerini öğrenmeye başlamak için yeterli bilgiye sahibiz.

4. LKM'ye dayalı sistem çağrısının görüşü örnekleri

4.1 Katalog oluşturmanın yasaklanması

Bir dizin oluştururken, SYS_MKDIR çekirdek işlevi denir. Bir parametre olarak, dizinin oluşturduğu dizinin adıyla yer alan dize. İlgili sistem çağrısını engelleyen kodu düşünün. / * Kaynak 4.1 * / #include #Dahil etmek. #Dahil etmek. / * Sistem çağrısı tablosu * / dış void * SYS_CALL_TABLE; / * Orijinal çağrıyı kaydetmek için işaretçiyi belirleyin * / int (* orig_mkdir) (Const char * yolu); / * Kendi sistem mücadelenizi oluşturun. Aramamız hiçbir şey yapmaz, sadece sıfır değeri döndürür * / int Own_mkdir (Dönüş 0;) / * Modül başlatma sırasında, işaretçiyi orijinal aramaya kaydettik ve sistem çağrısını değiştirir * / int_module ( Geçersiz) (orig_mkdir \u003d sys_call_table; sys_call_table \u003d ovn_mkdir; printk ("SYS_MKDIR, \\ n"); geri dönüş (0);) / * Yüklediğinizde, orijinal çağrıyı geri yükleyin * / Void Cleanup_Module (Void) (sys_call_table \u003d orig_mkdir; printk ("Sys_mkdir geri döndü \\ n");) / * eof * / Bir nesne modülü elde etmek için aşağıdaki komutu yürüteceğiz ve sistemde bir dizi deney yapacağız: # GCC -C -DModule -i / USR / SRC / Linux / Dahil / SRC-3.1.C # DMESG | Kuyruğu -N 1 sys_mkdir değiştirildi # mkdir testi # ls -ald testi Ls: Test: Böyle bir dosya veya dizin yok # RMMOD SRC-3.1 # DMESG | Kuyruk -N 1 sys_mkdir geri taşındı # mkdir test # ls -ald test DRWXR-XR-XR 2 Kök Kök 4096 2003-12-23 03:46 Test "MKDIR" komutunun çalışmadığını ya da hiçbir şeyden emin olabileceğinden emin olabilirsiniz. olur. Sistemin performansını geri yüklemek için modülü boşaltmak için yeterlidir. Yukarıda ne yapılır.

4.2 Katalogdaki dosya girişini gizleme

Kataloğun içeriğini okumaktan hangi sistem çağrısının sorumlu olduğunu tanımlıyoruz. Bunu yapmak için, geçerli dizinle ilgilenen başka bir test parçası yazın: / * Kaynak 4.2.1 * / #include #Dahil etmek. int ana () () () (Dir * d; struct dirent * dp; d \u003d opendir ("."); dp \u003d readdir (d); geri dönüş 0;) / * EOF * / Biz yürütülebilir bir dosya alır ve izleme işlemi: # gcc -O SRC-3.2.1 SRC-3.2.1.C # ltrace -s ./src-3.2.1 ... Opendir ("." SYS_OPEN (".", 100352, 010005141300) \u003d 3 SYS_FSTAT64 (3, 0XBFFFFF79C, 0x4014C2C0, 3, 0XBFFFFFF874) \u003d 0 SYS_FCNTL64 (3, 2, 1, 1, 0x4014C2C0) \u003d 0 SYS_BRK (NULL) \u003d 0x080495F4 SYS_BRK (0x0806A5F4) \u003d 0x0806A5F4 SYS_BRK (NULL) \u003d 0x0806A5F4 SYS_BRK (0x0806B000) \u003d 0x0806B000<... opendir resumed>) \u003d 0x08049648 Readdir (0x08049648 Sys_gettents64 (3, 0x08049678, 4096, 0x40014400, 0x4014c2c0) \u003d 528<... readdir resumed>) \u003d 0x08049678 ... Son dizeye dikkat etmelisin. Dizin içeriği GetDents64 işlevi tarafından okunur (GetDents diğer çekirdeklerde mümkündür). Sonuç, bir yapı destent yapılarının bir listesi olarak tutulur ve işlevin kendisi, dizindeki tüm girişlerin uzunluğunu döndürür. Bizim için, bu yapının iki alanı ilginçtir:
  • d_Reclen - Yazma boyutu
  • d_NAME - Dosya Adı
Dosya giriş dosyasını gizlemek için (başka bir deyişle, görünmez hale getirmek için), Sys_getdents64 sistem çağrısını engellemelisiniz, uygun girişi bulup ortaya çıkan yapıların listesinde silmek zorundasınız. Bu işlemi yapan kodu düşünün (orijinal kodun yazarı - Michal Zalewski): / * Kaynak 4.2.2 * / #include #Dahil etmek. #Dahil etmek. #Dahil etmek. #Dahil etmek. #Dahil etmek. #Dahil etmek. #Dahil etmek. dış void * sys_call_table; int (* orig_gettents) (U_int FD, Struct Dirent * Dirp, U_int Count); / * Sistem çağrınızı belirleyin * / int Own_gettents (U_int FD, Strut Dirent * Dirp, U_int Sayısı) (İmzasız Int TMP, N; INT T; Struct Dirent64 (int d_ino1, d_Olno2; int d_off1, d_off2; imzasız kısa d_reclen; imzasız char d_type; char d_name;) * dirp2, * dirp3; / * Gizlemek istediğimiz dosya adı * / char gizle \u003d "file1"; / * * / tmp \u003d (* orig_gettents) dizinindeki girişlerin uzunluğunu belirler ( Fd, dirp, sayım); eğer (tmp\u003e 0) (/ * Çekirdek alanındaki yapı için belleği seçin ve dizinin içeriğini kopyalayın * / dirp2 \u003d (Struct Dirent64 *) Kmalloc (TMP, GFP_KERNEL); COPY_FROM_USER (Dirp2, Dirp, TMP); / * İkinci yapıyı kullanıyoruz ve kayıt uzunluğunun değerini * / dirP3 \u003d DIRP2; T \u003d TMP3 \u003d DIRP2; T \u003d TMP; / * * / *'yu aramaya başlayalım * / Dosya (T\u003e 0) (/ * ilk kayıt uzunluğunu okuyun ve Kalan Günlük Uzunluğunu Dizin * / N \u003d Dirp3-\u003e D_Reclen; t - \u003d n'de belirleyin; / * Dosya adının örtüşmediğini kontrol ediyoruz. İstenilen * / eğer mevcut giriş (Strstr ((((((((char *) & (Dirp3-\u003e d_name), (char *) & Gizle! \u003d null) (/ * eğer öyleyse, sonra kaydı takın ve * / Memcy Dizin (Dirp3, (char *) Dirp3 + Dirp3-\u003e D_Reclen, T) kayıt uzunluğunun yeni değerini hesaplayın; Tmp - \u003d n; ) / * İşaretçiyi aşağıdaki girişe yerleştirin ve * / dirP3 \u003d (Struct Dirent64 *) ((char *) Dirp3 + Dirp3-\u003e d_reclen için aramaya devam edin; ) / * Sonucu iade ediyoruz ve hafızayı serbest bırakıyoruz * / COPY_TO_USER (DIRP, DIRP2, TMP); KFFree (Dirp2); ) / * * / Dönüş TMP dizininde kayıt uzunluğunun değerini döndürün; ) / * Modülün başlatma ve boşaltma işlevleri standart bir görünüme sahiptir * / int_module (geçersiz) (orig_gettents \u003d sys_call_table; sys_call_table \u003d ovn_gettents; 0;) void cleanup_module () (sys_call_table \u003d orig_gettents;) / * eof * / derleme Bu kod, ispatlamak için gerekli olan "File1" nasıl kaybolduğunu unutmayın.

5. Doğrudan Erişim Yöntemi / Dev / KMem Doğrudan Erişim Yöntemi

Önce teorik olarak, çekirdeğin adres alanına doğrudan erişim yöntemini nasıl durdurun ve ardından pratik uygulamaya devam edin.

Çekirdek adres alanına doğrudan erişim, cihaz / dev / kmem dosyasını sağlar. Bu dosya, takaslar (takas alanı) dahil olmak üzere tüm mevcut sanal adres alanını görüntüler. KMem dosyasıyla çalışmak için standart sistem fonksiyonları kullanılır - açık (), okuma (), yaz (). Standart yöntemi / dev / KMem'i açmak, bu dosyada bir vardiya olarak ayarlayarak sistemdeki herhangi bir adreye başvurabiliriz. Bu yöntem Silvio Cesare tarafından geliştirilmiştir.

Sistem fonksiyonlarına, fonksiyonun parametrelerini işlemci kayıtlarına ve ardından 0x80 yazılımının kesilmesine yapılan aramayı yükleyerek gerçekleştirilir. Bu kesintinin işleyicisi, System_call işlevi, Arama parametrelerini STEK'te yer alır, SYS_CALL_TABLE tablosundan denilen sistem işlevinin adresini alır ve kontrolü bu adrese aktarır.

Çekirdeğin adres alanına tam erişime sahip olmak, sistem arama tablosunun tüm içeriğini alabiliriz, yani. Tüm sistem fonksiyonlarının adresleri. Herhangi bir sistem mücadelesinin adresini değiştirerek, böylelikle görüşmeyi uygulayarak. Ancak bunun için, masanın adresini veya başka bir deyişle, / dev / KMem dosyasındaki ofset, bu tablonun bulunduğu ofset bilmeniz gerekir.

SYS_CALL_TABLE tablosunun adresini belirlemek için önce System_Call işlevinin adresini hesaplamanız gerekir. Bu özellik kesme işleyicisi olduğundan, kesintilerin korumalı modda nasıl işlendiğine bakalım.

Gerçek modda, kesintiye kaydolurken işlemci, her zaman hafızanın en başında olan ve kesme işlem programlarının arama adreslerini içeren kesme vektörleri tablosu olarak adlandırılır. Korunan modda, analog kesme vektörleri tablosu, güvenli mod işletim sisteminde bulunan kesme tanımlayıcı tablosu (IDT, Kesinti Tanımlayıcı Tablosu). İşlemcinin bu tablonun ile iletişime geçmesi için, adresi IDTR kaydına indirilmelidir (kesme tanımlayıcısı tablosu kaydı, kesme tanımlayıcı tablo kaydı). IDT tablosu, özellikle adreslerinin dahil olduğu kesintisiz işleyicilerin yapıcılarını içerir. Bu tanımlayıcıların ağ geçitleri (valfler) denir. İşlemci, kesmeyi kaydetme, ağ geçidi IDT'den IDT'den alınır, işleyicinin adresini belirler ve kontrolüne iletir.

IDT tablosundan System_call işlevinin adresini hesaplamak için, Intrutt Gateway Int $ 0x80'i ve bundan itibaren - karşılık gelen işleyicinin adresi, yani System_call işlevinin adresi. System_call işlevinde, System_Call_Table tablosuna erişim, ARAMA komutu tarafından gerçekleştirilir.<адрес_таблицы>(,% Ex, 4). / Dev / kmem dosyasında bu komutun bir görünümünü (imzası) bulduktan sonra, sistem arama tablosunun adresini buluruz.

Görünümünü belirlemek için, hata ayıklayıcısını kullanın ve System_Call işlevini sökünüz:

# GDB -Q / USR / SRC / Linux / Vmlinux (GDB) Disas System_Call Montajcı Kodunun Dökümü Fonksiyon System_Call: 0xC0194CBC :% Eax 0xc0194cbd itin : CLD 0xC0194CBE. :% ES 0XC0194CBF itin :% Ds 0xc0194cc0 itin :% Eax 0xc0194cc1 itin : İtin% ebp 0xc0194cc2 :% Edi 0xc0194cc3 itin :% ESI 0xC0194CC4 itmek :% EDx 0xc0194cc5 itin :% Ecx 0xc0194cc6 itin : İtin% ebx 0xc0194cc7 : MOV $ 0x18,% EDX 0xC0194CCC : MOV% EDX,% ds 0xc0194cce : MOV% EDX,% ES 0XC0194CD0 : MOV $ 0xffffe000,% EBX 0xC0194CD5 : ve% ESP,% EBX 0xC0194CD7 : Testb $ 0x2.0x18 (% EBX) 0xC0194CDB : JNE 0xC0194D3C. 0xC0194CDD. : CMP $ 0x10E,% EAX 0xC0194CE2 : JAE 0xC0194D69 0xC0194CE8. : Çağrı * 0xC02CBBB0C (,% EAX, 4) 0xC0194CEF : MOV% EAX, 0x18 (% ESP, 1) 0xC0194CF3 : Assembler dökümü nop. Satır "Çağrı * 0xC02CBBB0C (,% EAX, 4)" ve SYS_CALL_TABLE tablosuna hitap edin. 0xC02CBB0C değeri, tablo adresidir (çoğu muhtemelen başkalarına sahip olacaksınız). Bu ekibin görünümünü alıyoruz: (GDB) X / XW SYSTEM_CALL + 44 0XC0194CE8 : 0x0C8514FF Çağrı komutunun görünümünü SYS_CALL_TABLE tablosuna bulduk. \\ Xff \\ x14 \\ x85'e eşittir. Aşağıdaki 4 bayt, tablonun adresidir. Bunu, komutu girerek doğrulayabilirsiniz: (GDB) X / XW SYSTEM_CALL + 44 + 3 0XC0194CEB : 0xC02CBB0C Böylece, / dev / kmem dosyasında \\ xff \\ x14 \\ x85 dizisini bulmak ve aşağıdaki 4 bayt göz önüne alındığında, SYS_CALL_Table sistem çağrısı tablosunun adresini alırız. Adresini bilmek, bu tablonun içeriğini (tüm sistem fonksiyonlarının adresleri) alabilir ve herhangi bir sistem mücadelesinin adresini değiştirerek, onu yakalayarak.

Durdurma işlemini gerçekleştiren bir psödokodu düşünün:

Readaddr (old_syscall, scr + sys_call * 4, 4); Writeaddr (NEW_SYSCALL, SCR + SYS_CALL * 4, 4); ReadAddr özelliği, sistem çağrısının adresini sistem çağrısı tablosundan okur ve Old_SyScall değişkenine kaydeder. Sys_call_table tablosundaki her giriş 4 bayt kaplar. İstenilen adres, SCT + SYS_CALL * 4 Flakon / Dev / KMem'deki ofsette bulunur (burada SCT - SYS_CALL_TABLE tablo adres adresi, SYS_CALL - sistem çağrısının sırası numarası). WRiteaddr fonksiyonu, SYS_CALL adresinin yeni_syscall adresinin adresini yazar ve SYS_CALL sistem çağrısına olan tüm erişim bu işlev tarafından hizmet verilecektir.

Bak, her şey basit ve hedef elde edildi. Ancak, kullanıcının adres alanında çalıştığımızı hatırlayalım. Bu adres alanına yeni bir sistem işlevi yerleştirirseniz, bu işlevi ararken, güzel bir hata mesajı alacağız. Bu nedenle çıktı - Yeni bir sistem çağrısı, çekirdeğin adres alanına yerleştirilmelidir. Bunu yapmak için gereklidir: Kernel alanında bir bellek bloğu alın, bu blokta yeni bir sistem çağrısı yapın.

Kumsalloc işlevini kullanarak çekirdek alanındaki belleği seçin. Ancak, çekirdeğin doğrudan fonksiyonuna neden olmak için, kullanıcının adres alanından gelen adres alanından aşağıdaki algoritmayı kullanmak için kullanılamaz:

  • sYS_CALL_TABLE tablo adresini bilmek, bazı sistem çağrısının adresini alıyoruz (örneğin, SYS_MKDIR)
  • kMALLOC işlevine hitap eden işlevi belirleyin. Bu özellik, çekirdeğin adres alanındaki bellek bloğuna bir işaretçi döndürür. Bu işlevi arayalım Get_Kmalloc
  • İlk n bayt sistemini kaydedin Sys_mkdir, burada n Get_kmalloc işlevinin boyutudur.
  • İlk n bayt çağrısının üzerine yazma SYS_MKDIR FONKSİYONU GET_KMALLOC
  • sYS_MKDIR sistem çağrısına erişim, böylece get_kmalloc işlevini yürütmek için çalışıyoruz
  • sYS_MKDIR sistem çağrısının ilk n baytını geri yükleriz
Sonuç olarak, bertarafımızda çekirdek alanında bulunan bir bellek bloğu olacaktır.

Ancak bu algoritmayı uygulamak için, KMALLOC işlevinin adresine ihtiyacımız var. Birkaç şekilde bulmak mümkündür. En kolay olanı, bu adresi SYSTEM.MAP dosyasından dikkate almak veya GSYİH hata ayıklayıcısının yardımı ile belirlemektir (Print & Kmalloc). Çekirdek çekerde etkinse, KMALLOC adresi GET_KERNEL_SYMS () işlevi kullanılarak tanımlanabilir. Bu seçenek aşağıda tartışılacaktır. Çekirdek modüllerinin desteği eksikse, KMALLOC işlevi adresi KMALLOC çağrı takımı açıcısı tarafından arama yapacak - Sys_call_table tablosu için nasıl yapıldığına benzer.

KMALLOC özelliği iki parametre alır: istenen hafızanın boyutu ve GFP belirticisi. Görünümü aramak için, hata ayıklayıcısını kullanırız ve Kernel'in herhangi bir fonksiyonunu Kernel'in herhangi bir işlevini parçalayın.

# GDB -Q / USR / SRC / Linux / Vmlinux (GDB) DisaS Inter_Module_Register Montajcı Kodunun Dökümü Inter_Module_Register: 0xC01A57B4 : İtin% ebp 0xc01a57b5 :% Edi 0xc01a57b6 itin :% ESI 0xC01A57B7 itin :% Ebx 0xc01a57b8 itin : 0x10 $,% ESP 0XC01A57BB : MOV 0x24 (% ESP, 1),% EBX 0xC01A57BF : MOV 0x28 (% ESP, 1),% ESI 0xC01A57C3 : MOV 0x2C (% ESP, 1),% EBP 0xC01A57C7 : MOVL $ 0x1F0.0x4 (% ESP, 1) 0xC01A57CF : MOVL $ 0x14, (% ESP, 1) 0xC01A57D6 : 0xc01bea2a'yı arayın. ... İşlevin ne yaptığı önemli değil, bu konudaki ana şey ihtiyacımız olan şeydir - KMALLOC işlevini arayın. Son çizgiye dikkat ediyoruz. İlk olarak yığın halinde (ESP kaydı, Yığın kümesinin) parametrelerinin yüklendiğini gösterir ve ardından işlev çağrılır. GFP belirticisi (% 0x1F0x4 $ (% ESP, 1) ilk önce yığın halinde yüklenir. 2.4.9 ve yukarıdaki sürümler için, bu değer 0x1F0'dır. Bu komutun görünümünü bulacağız: (GDB) X / XW Inter_Module_Register + 19 0xC01A57C7 : 0x0424444C7 Bu görünümü bulursak, KMALLOC işlevinin adresini hesaplayabiliriz. İlk düzeltici, bu işlevin adresi arama talimatının argümanıdır, ancak bu tam olarak değil. System_call işlevinin aksine, talimat KMalloc'un adresi değil, ancak geçerli adrese göre bir kayma değildir. Bundan emin olun Calld Call 0xC01bea2a: (GDB) X / XW Inter_Module_Register + 34 0xC01A57D6 : 0x01924FE8 İlk bayt E8'e eşittir - bu, arama talimatının kapağıdır. Bu komutun argümanının değerini bulacağız: (GDB) X / XW Inter_Module_Register + 35 0xC01A57D7 : 0x0001924F Şimdi mevcut adresini 0xc01a57D6, 0x0001924F ve komutun 5 baytını kapatırsak, daha sonra KMALLOC işlevinin istenen adresini elde ediyoruz - 0xc01bea2a.

Bu amaçla, teorik hesaplamalar ve yukarıdaki yöntemi kullanarak, SYS_MKDIR sistem çağrısını yakalayacaktır.

6. Araç / dev / KMem ile yapılan müdahale örneği

/ * Kaynak 6.0 * / #include #Dahil etmek. #Dahil etmek. #Dahil etmek. #Dahil etmek. #Dahil etmek. #Dahil etmek. #Dahil etmek. / * Interlence için sistem arama numarası * / #define _sys_mkdir_ 39 #define Kmem_File "/ dev / kmem" #define max_syms 4096 / * IDTR * / struct kayıt formatının açıklaması (imzasız kısa limit; işaretsiz int taban;) __Attribute__ (( Paketlenmiş))) idtr; / * IDT'nin açıklaması * / yapı tablosu kesme ağ geçidi formatının (imzasız Kısa KAPALI1; İmzasız Kısa Sel; İmzasız Char Yok, Bayraklar; İmzasız Kısa KAPALI2;) __Attribute__ ((PACTED)) IDT; / * Get_kmalloc * / struct KMA_STRUC işlevi için yapının açıklaması (ULONG (* KMALLOC) (UNG, INT); // - Kmalloc int fonksiyon adresi; // - int bayrakları için bellek boyutu; // - zehir için bayrak\u003e 2.4.9 \u003d 0x1f0 (GFP) ULG MEM;) __Attribute__ ((paketlenmiş)) Kmalloc; / * Hafıza ünitesini yalnızca Nucleus * / Int Get_Kmalloc'un adres alanına vurgulayan fonksiyon (Struct KMA_struc * k) (K-\u003e MEM \u003d K-\u003e KMALLOC (K-\u003e Boyut, K-\u003e Bayrakları); 0 ;) / * İşlevin adresini döndüren fonksiyon (KMalloc'u aramanız gerekir) * / ULONG GET_SYM (CHAR * N) (struct gonel_sym sekmesi; int numsyms; int i; numsyms \u003d get_kernel_syms (null); NUMSYMS\u003e Max_syms || Numsyms< 0) return 0; get_kernel_syms(tab); for (i = 0; i < numsyms; i++) { if (!strncmp(n, tab[i].name, strlen(n))) return tab[i].value; } return 0; } /* Наша новая системная функция, ничего не делает;) */ int new_mkdir(const char *path) { return 0; } /* Читает из /dev/kmem с offset size данных в buf */ static inline int rkm(int fd, uint offset, void *buf, uint size) { if (lseek(fd, offset, 0) != offset){ printf("lseek err\n"); return 0; } if (read(fd, buf, size) != size) return 0; return size; } /* Аналогично, но только пишет в /dev/kmem */ static inline int wkm(int fd, uint offset, void *buf, uint size) { if (lseek(fd, offset, 0) != offset) return 0; if (write(fd, buf, size) != size) return 0; return size; } /* Читает из /dev/kmem данные размером 4 байта */ static inline int rkml(int fd, uint offset, ulong *buf) { return rkm(fd, offset, buf, sizeof(ulong)); } /* Аналогично, но только пишет */ static inline int wkml(int fd, uint offset, ulong buf) { return wkm(fd, offset, &buf, sizeof(ulong)); } /* Функция для получения адреса sys_call_table */ ulong get_sct(int kmem) { ulong sys_call_off; // - адрес обработчика // прерывания int $0x80 (функция system_call) char *p; char sc_asm; asm("sidt %0" : "=m" (idtr)); if (!rkm(kmem, idtr.base+(8*0x80), &idt, sizeof(idt))) return 0; sys_call_off = (idt.off2 << 16) | idt.off1; if (!rkm(kmem, sys_call_off, &sc_asm, 128)) return 0; p = (char *)memmem(sc_asm, 128, "\xff\x14\x85", 3) + 3; printf("call for sys_call_table at %08x\n",p); if (p) return *(ulong *)p; return 0; } /* Функция для определения адреса функции kmalloc */ ulong get_kma(ulong pgoff) { uint i; unsigned char buf, *p, *p1; int kmemz; ulong ret; ret = get_sym("kmalloc"); if (ret) { printf("\nZer gut!\n"); return ret; } kmemz = open("/dev/kmem", O_RDONLY); if (kmemz < 0) return 0; for (i = pgoff+0x100000; i < (pgoff + 0x1000000); i += 0x10000){ if (!rkm(kmemz, i, buf, sizeof(buf))) return 0; p1=(char *)memmem(buf,sizeof(buf),"\x68\xf0\x01\x00",4); if(p1) { p=(char *)memmem(p1+4,sizeof(buf),"\xe8",1)+1; if (p) { close(kmemz); return *(unsigned long *)p+i+(p-buf)+4; } } } close(kmemz); return 0; } int main() { int kmem; // !! - пустые, нужно подставить ulong get_kmalloc_size; // - размер функции get_kmalloc !! ulong get_kmalloc_addr; // - адрес функции get_kmalloc !! ulong new_mkdir_size; // - размер функции-перехватчика!! ulong new_mkdir_addr; // - адрес функции-перехватчика!! ulong sys_mkdir_addr; // - адрес системного вызова sys_mkdir ulong page_offset; // - нижняя граница адресного // пространства ядра ulong sct; // - адрес таблицы sys_call_table ulong kma; // - адрес функции kmalloc unsigned char tmp; kmem = open(KMEM_FILE, O_RDWR, 0); if (kmem < 0) return 0; sct = get_sct(kmem); page_offset = sct & 0xF0000000; kma = get_kma(page_offset); printf("OK\n" "page_offset\t\t:\t0x%08x\n" "sys_call_table\t:\t0x%08x\n" "kmalloc()\t\t:\t0x%08x\n", page_offset,sct,kma); /* Найдем адрес sys_mkdir */ if (!rkml(kmem, sct+(_SYS_MKDIR_*4), &sys_mkdir_addr)) { printf("Cannot get addr of %d syscall\n", _SYS_MKDIR_); perror("er: "); return 1; } /* Сохраним первые N байт вызова sys_mkdir */ if (!rkm(kmem, sys_mkdir_addr, tmp, get_kmalloc_size)) { printf("Cannot save old %d syscall!\n", _SYS_MKDIR_); return 1; } /* Перепишем первые N байт, функцией get_kmalloc */ if (!wkm(kmem, sys_mkdir_addr,(void *)get_kmalloc_addr, get_kmalloc_size)) { printf("Can"t overwrite our syscall %d!\n",_SYS_MKDIR_); return 1; } kmalloc.kmalloc = (void *) kma; //- адрес функции kmalloc kmalloc.size = new_mkdir_size; //- размер запращевоемой // памяти (размер функции-перехватчика new_mkdir) kmalloc.flags = 0x1f0; //- спецификатор GFP /* Выполним сис. вызов sys_mkdir, тем самым выполним нашу функцию get_kmalloc */ mkdir((char *)&kmalloc,0); /* Востановим оригинальный вызов sys_mkdir */ if (!wkm(kmem, sys_mkdir_addr, tmp, get_kmalloc_size)) { printf("Can"t restore syscall %d !\n",_SYS_MKDIR_); return 1; } if (kmalloc.mem < page_offset) { printf("Allocated memory is too low (%08x < %08x)\n", kmalloc.mem, page_offset); return 1; } /* Оторбразим результаты */ printf("sys_mkdir_addr\t\t:\t0x%08x\n" "get_kmalloc_size\t:\t0x%08x (%d bytes)\n\n" "our kmem region\t\t:\t0x%08x\n" "size of our kmem\t:\t0x%08x (%d bytes)\n\n", sys_mkdir_addr, get_kmalloc_size, get_kmalloc_size, kmalloc.mem, kmalloc.size, kmalloc.size); /* Разместим в пространстве ядра наш новый сис. вызво */ if(!wkm(kmem, kmalloc.mem, (void *)new_mkdir_addr, new_mkdir_size)) { printf("Unable to locate new system call !\n"); return 1; } /* Перепишем таблицу sys_call_table на наш новый вызов */ if(!wkml(kmem, sct+(_SYS_MKDIR_*4), kmalloc.mem)) { printf("Eh ..."); return 1; } return 1; } /* EOF */ Скомпилируем полученый код и определим адреса и размеры функций get_kmalloc и new_mkdir. Запускать полученое творение рано! Для вычисления адресов и размеров воспользуемся утилитой objdump: # gcc -o src-6.0 src-6.0.c # objdump -x ./src-6.0 > Dökümü boşaltma dosyasını açın ve ilginin verilerini bize gönderin: 080485A4 g f .Text 00000032 Get_kmalloc 080486B1 g f .Text 0000000a new_mkdir Şimdi programımızda bu değeri yapacağız: ULONG GET_KMALLOC_SIZE \u003d 0x32; ULONG GET_KMALLOC_ADDR \u003d 0x080485A4; ULONG NEW_MKDIR_SIZE \u003d 0x0A; ULONG NEW_MKDIR_ADDR \u003d 0x080486B1; Şimdi programı yeniden birleştirin. Yürütme çalıştıktan sonra, Sys_mkdir sistem mücadelesini engelleyeceğiz. Sys_mkdir çağrısına yapılan tüm aramalar şimdi New_MKDIR işlevi tarafından servis edilecektir.

Kağıt Sonu / EOP

Kodun tüm bölümlerden çalıştırılması Kernel 2.4.22'de kontrol edildi. Rapor hazırlarken, site malzemeleri kullanılmıştır.

En sık, sistem çağrısı kodu ile tanımlanan __nr_xxx numarasıyla /usr/include/asm/Unistd.h.fonksiyondaki Linux çekirdeğinin kaynak kodunda bulunabilir sys_xxx.(). (I386 için çağrı tablosu bulunabilir /usr/src/linux/arch/i386/kernel/entry.s..) Bu kuraldan, esas olarak eski sistem çağrılarının çoğunun yeni ile değiştirilmesi nedeniyle, herhangi bir sistemi olmayan bir şeyle değiştirilir. Parisc, SPARC, SPARC64 ve ALPA gibi öz-emülasyonlu platformlarda, birçok ek sistem çağrısı vardır; MIPS64 için ayrıca 32 bitlik sistem aramalarının tam bir seti vardır.

Zamanla, gerekirse, bazı sistem çağrılarının arayüzünde değişiklikler meydana geldi. Bu tür değişikliklerin nedenlerinden biri, iletilen sistem çağrısının yapılarının veya skaler değerlerinin boyutunu arttırmaktı. Bazı mimarilerdeki bu değişiklikler nedeniyle (yani, eski 32-bit i386'da çeşitli benzer sistem çağrılarının çeşitli grupları (örneğin, kesik.(2) ve truncate64.(2)), aynı görevleri gerçekleştiren, ancak argümanlarının boyutunda farklılık gösterir. (Zaten belirtildiği gibi, uygulamaları etkilemez: Glibc Wolved Fonksiyonları, doğru sistem çağrısını başlatmak için bazı eylemler gerçekleştirir ve bu, eski ikili dosyalar için ABI uyumluluğu sağlar.) Birkaç versiyona sahip sistem çağrılarının örnekleri:

* Şu anda üç tane var Çeşitli versiyonlar uydurmak(2): sys_stat.() (bir yer __Nr_oldstat.), sys_newstat() (bir yer __Nr_stat.) BEN. sys_stat64.() (bir yer __Nr_stat64.), ikincisi şu anda kullanılır. Benzer durum S. lstat.(2) ve fstat.(2). * Benzer şekilde, tanımlanmış __Nr_oldolduname., __Nr_olduname. ve __Nr_uname Aramalar için sys_olduname.(), sys_uname.() BEN. sys_newuname(). * Linux 2.0'da göründü yeni bir versiyon vm86.(2), nükleer prosedürlerin yeni ve eski sürümleri denir sYS_VM86OLD.() BEN. sYS_VM86.(). * Linux 2.4'te yeni bir versiyona sahip getRlimit.(2) Nükleer prosedürlerin yeni ve eski versiyonları denir sys_old_getrlimit.() (bir yer __NR_GETRLIMIT.) BEN. sys_getrlimit.() (bir yer __NR_UGETRLIMIT.). * Linux 2.4'te kullanıcı kimliğini ve 16 ila 32 bit arasındaki grupları artırdı. Bu değişikliği desteklemek için, birkaç sistem araması eklendi (örneğin, chown32.(2), getuid32.(2), getGroups32.(2), setresuid32.(2)), erken aramaları aynı isimlerle kaldıran, ancak "32" soneki olmadan. * Linux 2.4, 32 bit mimarilerindeki uygulamalarda büyük dosyalara (boyut ve ofset 32 \u200b\u200bbit uymaz) için destek eklendi. Bunu yapmak için, dosya üzerinde boyut ve yer değiştirmelerle çalışan sistem çağrılarında değişiklik yapmak gerekliydi. Aşağıdaki sistem çağrıları eklendi: fcntl64.(2), getdents64.(2), stat64.(2), statfs64.(2), truncate64.(2) ve dosya tanımlayıcılarını veya sembolik bağlantıları kullanan analogları. Bu sistem, "stat" aramaları da "stat" aramaları da aranıyor, ancak "64" eki yok.

Sadece 64 bit dosya erişimi ve 32-bit UID / GID (örneğin, Alpha, IA64, S390x, X86-64) olan yeni platformlarda, UID / GID ve dosya erişimi için sistem çağrısının yalnızca bir versiyonu var. Platformlarda (genellikle 32 bit platformlar), * 64 ve * 32 aramaların bulunduğu diğer sürümler modası geçmiştir.

* Aramalar rt_sig * Ek gerçek zamanlı sinyalleri desteklemek için Kernel 2.2'ye eklendi (bkz. sinyal.(7)). Bu sistem, eski sistem çağrılarını aynı isimlerle ortadan kaldırır, ancak "RT_" öneki olmadan. * Sistem zorluklarında seçmek(2) ve mmap(2) Beş veya daha fazla argüman kullanılır, bu da I386'ya argümanları iletme yöntemini belirleme sorununa neden olur. Bunun soruşturmasında, diğer mimarilerde de aradılar sYS_SELECT.() BEN. sys_mmap() karşılık gelir __NR_SELECT. ve __Nr_mmap, i386'da karşılık gelirler old_Select.() BEN. old_mpap() (Argüman bloğuna bir işaretçi kullanan prosedürler). Şu anda artık beşten fazla argüman transferinde bir sorun yok ve __Nr__NewSelect.bu tam olarak karşılık gelir sYS_SELECT.() ve aynı durumla __Nr_mmap2..

Çok fazla Walrus'a kadar, konuşmanın zamanı geldi.
L. Carolle (B. Strawbook'taki alıntı)

İdare yerine.

Genel olarak Linux çekirdeğinin iç aygıtının konusu üzerine, özellikle çeşitli alt sistemleri ve sistem zorlukları, siparişe göre yazılmış ve yeniden yazılmıştır. Muhtemelen, her bir kendi kendine saygılı yazara buna yazmalı, çünkü her bir kendi kendini saygılı programcının kendi dosya yöneticisini yazması gerektiği gibi :) Profesyonel bir IT-RAITER değilim ve genel olarak, paramimi sadece yararına yapıyorum. Öncelikle çalışılanları çok hızlı unutmamak. Ancak, eğer birisi kullanışlı olursa, seyahat notlarımı elbette, elbette sadece mutlu olacağım. Genel olarak, yulaf lapası petrolü bozmaz, bu nedenle kimsenin bahsetmeyecekleri bir şeyi yazmak veya tanımlamak bile mümkün olabilir.

Teori. Sistem aramaları nedir?

Başlatılmamış ne tür (veya işletim sistemi) ne tür bir şey olduğunu açıklarsa, genellikle aşağıdakileri söylerler: Bilgisayarın kendisi bir demir parçasıdır, ancak yazılım, bu, bu demir parçasından faydalanmanın mümkün olduğu için, . Elbette kaba, ancak genel olarak, doğru bir şey. Muhtemelen işletim sistemi ve sistem zorlukları söylerdim. Aslında, farklı işletim sistemi sistemlerinde farklı şekillerde uygulanabilir, bu aralığın sayısı emekli olabilir, ancak bir şekilde veya başka bir şekilde, bir veya başka birinde, sistem arama mekanizması herhangi bir işletim sistemindedir. Her gün kullanıcı açıkça veya açıkça dosyalarla çalışmıyor. Tabii ki, en sevdiği MS Word "E veya Notepad" e'de düzenlemek için bir dosyayı açıkça açabilir ve belki de sadece oyuncuyu çalıştırın, bu arada, bu arada, hangi dosyada depolanır. Aç, açılış yükleyici çalıştırılabilir dosyaları açmalı ve okumalısınız. Buna karşılık, oyuncak da iş sürecinde düzinelerce dosyayı açabilir ve okuyabilir. Doğal olarak, dosyalar yalnızca okunamıyor, aynı zamanda yazmak için de (her zaman değil, ancak burada da hak ve ayrık erişim hakkında değil :)). Bütün bunlar çekirdeğe yöneliyor (mikronik işletim sistemindeki durum farklı olabilir, ancak şimdi tartışmamızın nesnesi - Linux'umuzun itirazına gireceğiz, bu anı görmezden geliyorum). Yeni bir sürecin üretimi de OS çekirdeği tarafından sağlanan hizmettir. Bütün bunlar harika, ne gibi modern işlemciler Gigahertz bantlarının frekanslarında çalışın ve birçok milyon transistörden oluşur, ancak daha sonra ne var? Evet, eğer başka bir mekanizma olmasaydı, hangi özel uygulamaların oldukça sıradan ve aynı anda, gerekli şeyleri ( aslında, bu önemsiz eylemler herhangi bir eksiklikte bir kullanıcı uygulaması değil, işletim sistemi - AVT'nin çekirdeğinde gerçekleştirilir.), o zaman işletim sistemi kendi başına bir şeydi - kesinlikle işe yaramaz ya da aksine, her kullanıcı uygulamasının kendisi olmalıydı işletim sistemiTüm ihtiyaçlarınızı bağımsız olarak hizmet etmek. Şirin, değil mi?

Böylece, bir sistem çağrısının tanımına ilk yaklaşımın tanımına yaklaştık: bir sistem çağrısı, OS Kernel'in ikincisinin talebi üzerine bir kullanıcı uygulaması sağladığı bir hizmettir. Böyle bir servis, dosyanın zaten bahsedilen açılması, oluşturma, okuma, yazma, yeni bir işlem oluşturma, bir işlem tanımlayıcısı (PID), montaj olabilir dosya sistemi, Sonunda sistemi durdur. Gerçek hayatta, sistem çağrıları burada listelendiğinden çok daha fazlasıdır.

Bir sistem çağrısı neye benziyor ve bu nedir? Eh, yukarıda belirtilenlerden, sistem çağrısının, karşılık gelen bir görünüme sahip bir çekirdek alt rutin olduğu açıktır. Win9x / DOS altında programlama deneyimi olanlar, muhtemelen Int 0x21'i tümü (veya en azından bazıları) sayısız fonksiyonunu hatırlatır. Ancak, tüm UNIX sistemi çağrılarıyla ilgili küçük bir özellik var. Anlaşmaya göre, sistem çağrısını uygulayan işlev n argümanları alabilir veya bunları hiç kabul etmiyor olabilir, ancak bir şekilde ya da başka bir şekilde, işlevin int değerini iade etmesi gerekir. Negatif olmayan herhangi bir değer, sistem arama fonksiyonunun başarılı bir şekilde yürütülmesi olarak yorumlanır ve bu nedenle en fazla sistem araması olun. Değer sıfırdan az, hatanın bir özelliğidir ve aynı zamanda bir hata kodu içerir (Hata Kodları, Dahil / ASM-Genel / Errno-Base.h'da tanımlanır ve / asm-jenerik / errno.h başlıkları dahil) ). Sistem çağrıları için Linux ağ geçidinde, yakın zamana kadar bir int 0x80 kesintiye çıktı, Windows'ta (yanılmıyorsam, XP Service Pack 2 versiyonuna kadar), bu ağ geçidi 0x2e kesmesi. Yine, Linux çekirdeğinde, yakın zamana kadar, tüm sistem çağrıları System_Call () işlevi tarafından işlendi. Bununla birlikte, daha sonra ortaya çıktığında, 0x80 ağ geçidi aracılığıyla akan sistemin klasik mekanizması, işlemci performansında önemli bir düşüşe yol açar. Intel pentium. 4. Bu nedenle, sanal dinamik paylaşılan nesnelerin yöntemi Klasik mekanizmayı değiştirmeye (DSO - Dinamik paylaşılan nesne dosyası. Doğru çeviri için sayılmaz, ancak DSO, Windows kullanıcılarının DLL - dinamik olarak indirilebilir ve bileşen kütüphanesi olarak bilindiği biliniyor. ) - vdso. Klasikten yeni bir yöntem arasındaki fark nedir? Başlamak için, 0x80 kapısı boyunca çalışan klasik yöntemle anlıyoruz.

Linux'ta Klasik Sistem Çağrı Servis Mekanizması.

X86 mimarlığında kesintiler.

Yukarıda belirtildiği gibi, kullanıcı uygulamalarına servis yapmanın daha önce 0x80 (int 0x80). IA-32 mimarisinin işletimi, kesme işlemleri tarafından yönetilmektedir (kesinlikle konuşursak, bu, X86'ya dayanan tüm sistemler ile ilgilidir). Belirli bir olay meydana geldiğinde (yeni bir TIK zamanlayıcısı, bazı cihazlarda herhangi bir aktivite, hatalar - sıfıra, vb.), Bir kesme oluşturulur. Kesinti (kesme) olarak adlandırılır, çünkü genellikle kodun normal seyrini keser. Kesintiler, donanım ve yazılımı (donanım ve yazılım kesintileri) bölünmeye kabul edilir. Donanım kesintileri, sistemik ve çevresel cihazlar tarafından üretilen kesintilerdir. Bazı cihazlara ihtiyacınız varsa, OS çekirdeği (cihaz), kesme sorgu satırında (IRQ - kesme istek satırında) bir sinyal oluşturur. Bu, karşılık gelen sinyalin, işlemcinin belirli girişlerinde üretildiği gerçeğine, işlemcinin, talimatların akışını durdurduğu ve daha önce ne olduğunu bulur ve ne yapılması gerekiyor. Donanım, doğası gereği asenkron durdurur. Bu, kesintinin herhangi bir zamanda gerçekleşebileceği anlamına gelir. dışında Çevresel aygıtlarİşlemcinin kendisi kesintiler üretebilir (veya daha kesin, donanım istisnaları - donanım istisnaları - örneğin, zaten sıfırdan bahsedilen bölünme). Bu, işletim sisteminin anormal bir durumun ortaya çıkması üzerine bildirilmesi için yapılır, böylece işletim sistemi böyle bir durumun ortaya çıkmasına cevaben bir tür harekete geçebilir. Kesintiyi işten çıkardıktan sonra, işlemci kesintiye uğramış programın tamamlanmasına geri döner. Kesme, kullanıcı uygulaması tarafından başlatılabilir. Böyle bir kesinti yazılım denir. Yazılım kesintileri, donanımın aksine, senkronize. Bunlar., Koduna neden olan bir kesmeyi ararken, kesilinceye kadar askıya alınır. Kesme işleyicisini terk ederken, yığın içindeki (int) kesildikten sonra istifin üzerine daha önce kaydedilen (kesintiyi çağırırken) kaydedilen uzun mesafeli bir adrese döner. Kesinti işleyicisi, kodun bir ikamet (sürekli hafızada). Kural olarak, bu küçük bir programdır. Her ne kadar Linux çekirdeği hakkında konuşursak, o zaman her zaman çok küçük kesme işleyicisi yoktur. Kesme işleyicisi vektör tarafından belirlenir. Vektör, bu endekle durdurması gereken başlangıç \u200b\u200bkodunun adresinden (segment ve ofset) daha fazla bir şey değildir. Kesintilerle çalışmak, gerçek modda önemli ölçüde farklıdır ve işlemcinin çalışma modunda (korumalı mod) (bu, size burada ve sonra Intel işlemcileri ve bunlarla uyumlu olduğumuzu hatırlatırız). Gerçek (korunmasız) işlemci modunda, kesme işlemcileri, her zaman hafızanın başında depolanan vektörleri tarafından belirlenir. Vektör çizimlerinden istenen adresi seçmek, aynı zamanda kesme numarası olan dizinde oluşur. Kendi işleyicisini kesmek için bir vektörün tanımlanmış bir dizinle üzerine yazmak atanabilir.

Korunan modda, kesme işleyicileri (ağ geçitleri, kapılar veya valfler) artık vektörler tablosunu kullanarak belirlenmez. Bu tablonun yerine, vana tablosu kullanılır veya daha doğru, kesme tablosu - IDT (kesme çizelticileri tablosu). Bu tablo çekirdek tarafından oluşturulur ve adresi IDTR işlemci kaydında saklanır. Bu kayıt doğrudan mevcut değil. Bununla çalışma, yalnızca LIDT / SIDT talimatlarını kullanarak mümkündür. Bunlardan ilki (LIDT), işlenende belirtilen IDTR değerini yükler ve temel adres Kesme Decript Tabloları, İkinci (SIDT), IDTR'de bulunan tablonun adresini belirtilen işlenene saklar. Tablo tanımlayıcı tanımlayıcılarından yapılan segmentle ilgili bir bilgi seçeneği olarak, segment tanımlayıcısının bir bölümünün, korumalı modda kesilmeye hizmet verilmesi de oluşur. Hafıza koruması desteklenir intel İşlemciler CPU I80286 ile başlayarak (şu anda sunulduğu formda değil, eğer sadece 286, eğer 286), bu nedenle Linux bu işlemciler üzerinde çalışamazlar) ve i80386 ve bu nedenle işlemci bağımsız olarak gerekli tüm örnekleri üretir ve Bu nedenle, korunan modun tüm inceliklerine derinleşmesi gerektiği (yani, Linux korumalı modda çalışır) yapmayacağız. Ne yazık ki, ne de yeteneklerin ne de yetenekleri, kesme işlem mekanizmasında uzun süre güvenli bir modda durmamıza izin vermemektedir. Evet, bu makaleyi yazarken bu amaç değildi. Burada H86 aile işlemcilerinin çalışmalarında belirtilen tüm bilgiler oldukça yüzeyseldir ve yalnızca çekirdeğin sistem çağrılarının işletme mekanizmasını biraz daha iyi anlamak için verilir. Doğrudan kodlayıcı kodundan öğrenebileceğiniz bir şey, olsa da, neler olduğunu tam olarak anlamak için, korumalı rejimin ilkelerine aşina olmanız önerilir. Başlangıç \u200b\u200bdeğerlerini dolduran kod kodu (ancak yüklemez!) IDT, ARCH / I386 / Kernel / Head.s: / * * SETUP_IDT * * * Ignore_int, kesme kapılarına işaret eden 256 girişli bir IDT oluşturur. Aslında yüklenmez * IDT - sadece çağrı etkinleştirildikten sonra yapılabilir * ve çekirdek, page_offset'e taşındı. Kesme * Başka bir yerde etkinleştiriliyor, biz göreceli olabiliriz * Her şey yolundayız. * * Uyarı:% ESI Bu fonksiyon boyunca yaşıyor. * / 1.setup_idt: 2. Leave Ignore_int,% EDX 3. MOVL $ (__ Kernel_CS<< 16),%eax 4. movw %dx,%ax /* selector = 0x0010 = cs */ 5. movw $0x8E00,%dx /* interrupt gate - dpl=0, present */ 6. lea idt_table,%edi 7. mov $256,%ecx 8.rp_sidt: 9. movl %eax,(%edi) 10. movl %edx,4(%edi) 11. addl $8,%edi 12. dec %ecx 13. jne rp_sidt 14..macro set_early_handler handler,trapno 15. lea \handler,%edx 16. movl $(__KERNEL_CS << 16),%eax 17. movw %dx,%ax 18. movw $0x8E00,%dx /* interrupt gate - dpl=0, present */ 19. lea idt_table,%edi 20. movl %eax,8*\trapno(%edi) 21. movl %edx,8*\trapno+4(%edi) 22..endm 23. set_early_handler handler=early_divide_err,trapno=0 24. set_early_handler handler=early_illegal_opcode,trapno=6 25. set_early_handler handler=early_protection_fault,trapno=13 26. set_early_handler handler=early_page_fault,trapno=14 28. ret Koddaki çeşitli yorumlar: Verilen kod AT & T Assembler'in çeşitleri üzerine yazılmıştır, bu nedenle asscaten'in her zamanki Intel notasyonundaki bilgisi sadece karıştırılabilir. Operandların sırasındaki en önemli fark. Sipariş Intel Notation için tanımlanırsa - "Batarya"< "источник", то для ассемблера AT&T порядок прямой. Регистры процессора, как правило, должны иметь префикс "%", непосредственные значения (константы) префиксируются символом доллара "$". Синтаксис AT&T традиционно используется в Un*x-системах.

2-4 satırlarında verilen örnekte, tüm varsayılan kesintilerin işleyicisinin adresi ayarlanır. Varsayılan işleyici, hiçbir şey yapmayan Ignore_int işlevidir. Böyle bir fişin varlığı, bu aşamadaki tüm kesintilerin doğru şekilde işlenmesi için gereklidir, çünkü diğerleri henüz artık (TRAD, TRAPS (TRAPS) kodunda biraz daha düşük ayarlanır - Tuzaklar Hakkında, Intel Mimarlık Manuel Referansı ya da böyle bir şey, burada tuzağa dokunmayacağız). 5. satırda, valf tipi ayarlanır. 6. sırada, IDT tablonuzun adresini dizin kaydında indiriyoruz. Masanın her biri 8 bayt, 255 giriş içermelidir. 8-13 satırlarında, tüm tabloyu EAX ve EDX kayıtları tarafından belirlenen aynı değerlerle doldururuz - yani, bu, Ignore_int işleyicisini ifade eden bir kesme valfidir. Hemen aşağıda, tuzakları (tuzaklar) - dizeleri 14-22 yüklemek için bir makro tanımlarız. Yukarıda belirtilen makroyu kullanarak 23-26 numaralı. başarısızlık (14). Parantez içinde, uygun bir acil durum tarafından üretilen sayıları "kesintiler" verilir. İşlemci tipini Kemer / I386 / Kernel / Kafa bölümündeki kontrol etmeden önce, IDT tablosu SETUP_IDT çağrısı ile ayarlanır: / * * Sistemi Başlat 32-bit Kurulumu. "Gerçek" işlemler için 16 bit modunda yapılan şeyleri * yeniden yapmamız gerekiyor. * / 1. SETUP_IDT ... 2. Çağrı Check_x87 3. LGDT Early_GDT_DESCR 4. LIDT IDT_DESCRİşlemcinin tipini (CO) bulduktan ve 3 ve 4 satırlarındaki tüm hazırlık işlemlerini gerçekleştirdikten sonra, çekirdeğin ilk gözeneklerinde kullanılacak GDT ve IDT tablolarını yükleriz.

Sistem çağrıları ve int 0x80.

Kesintilerden sistem zorluklarına geri dönecek. Öyleyse, bir tür hizmet isteyen sürecine hizmet etmek için neler gerekiyor? Başlamak için, en çok etkilenen seviye 0 (Ring 0, CPL \u003d 0), halka 3 (CPL \u003d 3 seviyesi), çünkü çünkü Çekirdek kod, en yüksek turistik mekanlara sahip segmentte bulunur. Ek olarak, işleyici kodu işlemin hizmet edeceği için gereklidir. Bunun için 0x80 ağ geçidinin kullanıldığı içindir. Sistem çağrıları oldukça fazla olmasına rağmen, herkes için tek bir giriş noktası kullanırlar - Int 0x80. KAPA / I386 / KERNEL / TRAPS.C :: Trap_init () işlevini ararken işleyicinin kendisi ayarlanır: void __init trap_init (void) (... set_system_gate (syscall_vector, & system_call); ...)Biz Trap_init () bu çizgi ile daha fazla ilgileniyoruz. Aynı dosyada, yukarıdaki SET_SYSTEM_GATE () fonksiyon koduna bakabilirsiniz: Statik void __init set_system_gate (işaretsiz int n, void * addr) (_SET_GATE (N, DESTAPE_TRAP | DESTAPE_DPL3, ADDR, __KERNEL_CS);)Burada valfin 0x80'i kestiğini (yani, bu değer, Syscall_Vector makrosu tarafından belirleneceğine inanıyorsunuz) görebilirsiniz :)) Nitelikler DPL \u003d 3 seviyesi olan bir tuzak (tuzak) olarak monte edilebilir (Halka 3) , yani Bu kesme, kullanıcı alanından ararken yakalanacaktır. Halka 3'ten Ring 0 T.O'da geçişle ilgili sorun. Çözüldü. _SET_GATE () işlevi, dahil / asm-i386 / desc.h başlık dosyasında tanımlanır. Aşağıdaki meraklılar için, kod, mükemmellik olmadan gösterilir: Statik inline void _SET_GATE (int kapısı, imzasız int tipi, boşluk * ADDR, imzasız kısa seg) (__u32 a, b; pack_gate (& a, & b, (imzasız uzun) addr, seg, tip, 0); write_idt_entry (idt_table) , Gate, A, B);)Trap_init () işlevine dönelim. Init / Main.c'deki Start_Kernel () işlevinden çağrılır. Trap_init () koduna bakarsanız, bu özelliğin, IDT tablosunun bazı değerlerine karşılık geldiği açıktır (Early_Page_Fault, Early_Divide_Rerr, Early_Llegal_Opcode, Early_Protection_Pault) zaten işlemde kullanılacak olanlarla değiştirildi. Çekirdek çalışıyor. Öyleyse, pratikte özüne geldik ve tüm sistem çağrılarının üniform bir şekilde işlendiğini biliyoruz - Int 0x80 Gateway. Int 0x80 için bir işleyici olarak, tekrar olduğu gibi, ARCH / I386 / KERNEL / TRAPS.C :: Trap_INIT () kodunun yukarıdaki çizelgesinden görülür, System_call () işlevini ayarlar.

system_call ().

System_call () işlevinin kodu, Arch / I386 / Kernel / Giriş.s dosyasında ve şöyle görünür: # Sistem çağrısı işleyici saplı girişi (System_call) Ring0_int_frame # "Kullanıcı alanına giremezsiniz. ve böylece testbiye ihtiyacı var ve testb * / testw $ (_ tif_syscall_emu | _tif_syscall_trace | _tif_seccall_trace | _tif_seccomp | _tif_syscall_audit), ti_flags (% ebp) jnz syscall_trace_tentry cmpl $ (nr_syscalls),% eax jae syscall_badsyssysysysysy_call: Call * sys_call_table (,% eax , 4) MOVL% EAX, PT_EAX (% ESP) # iade değerini saklayın ...Kod tamamen gösterilmez. Gördüğünüz gibi, birinci System_Call () bir istifi halka 0'da çalışacak şekilde yapılandırır, EAX üzerinden iletilen değeri yığına kaydeder, tüm kayıtları yığına kaydeder, arayan verilerini alır ve iletilen değerinin olup olmadığını kontrol eder Sistem araması, sistem çağrısı tablosunun sınırlarına gitmez ve daha sonra, EAX olarak iletilen değeri bir argüman olarak, System_Call () egzersizlerini kullanarak, tablonun hangi elemanına EAX'e işaret eden gerçek bir sistem çıkış işleyicisine uygulanır. Şimdi gerçek moddan eski kesici vektörlerin eski tablosunu hatırlayın. Hiçbir şey hatırlatmıyor? Gerçekte, elbette, her şey daha karmaşık. Özellikle, sistem çağrısı, sonuçları çekirdek yığınından kullanıcı yığına kopyalamalı, geri dönüş kodunu ve biraz daha şeyleri geçmelidir. EAX'te belirtilen argüman mevcut bir sistem çağrısına başvurmadığında (değer aralığındadır), Syscall_Badsys etiketine gidin. Burada, EAX'in değeri olması gereken yer değiştirme yığını içinde, -enosys'ın değeri girilir - sistem çağrısı uygulanmaz. Bu yürütme sistemi_call () tamamlandı.

Sistem çağrısı tablosu, ARCH / I386 / KERNEL / SYSCALL_TABLE.S dosyasında bulunur ve oldukça basit bir görünüme sahiptir: Giriş (sys_call_table) .long sys_restart_syscall / * 0 - eski "kurulum ()" Sistem çağrısı, yeniden başlatma için kullanılan * / .long sys_exit .long sys_fork .long sys_read .long sys_write .long sys_open / * 5 * / .long sys_close .long SYS_WAITPID .LONG SYS_CREAT ...Başka bir deyişle, tüm tablo, bu fonksiyonların servis edildiği sistem arama numaralarının sırasına göre bulunan bir dizi işlev adresinden başka bir şey değildir. Tablo, çift motorlu kelimelerin (veya daha fazla seven 32 bit kelimenin) normal dizisidir. Sistem çağrıları servis eden fonksiyonların bir kısmının kodu, platform bağımlı parçada - ARCH / I386 / KERNEL / SYS_I386.C ve KERNEL / SYS.C'deki platforma bağlı olmayan bir parçada bulunur.

Sistem zorlukları ve 0x80 valfinde durum budur.

Linux'ta Yeni Sistem Çağrı İşleme Mekanizması. SYSENTER / SYSEXIT.

Çok hızlı bir şekilde bahsedildiği gibi, Gaita 0x80'e dayanan sistem çağrılarının geleneksel bir işleme yönteminin kullanımının, Intel Pentium işlemcilerindeki performans kaybına itmektir. Bu nedenle, Linus Torvalds, Kernel'de yeni bir mekanizma uyguladı. SYSENTTER / SYSEXIT talimatları ve çekirdeğin verimliliğini Pentium II ve daha yüksek işlemciyle donatılmış makinelerde arttırmak için tasarlanmıştır (SYSENTER / SYSEXIT talimatları tarafından desteklenen Pentium II + Intel işlemcilerindendir). Yeni bir mekanizmanın özü nedir? Garip bir şekilde yeterince, ancak özü aynı kalır. Yürütme değişti. Intel belgelerine göre, Sysenter talimatı "Hızlı Sistem Çağrıları" mekanizmasının bir parçasıdır. Özellikle, bu talimat, bir seviyesinden diğerine hızlı geçiş için optimize edilmiştir. Daha kesin olarak, halka 0 (halka 0, CPL \u003d 0) geçişini hızlandırır. Aynı zamanda, işletim sistemi sysenter talimatını kullanmak için işlemciyi hazırlamalıdır. Bu ayar, işletim sistemi çekirdeğini yüklerken ve başlatırken bir kez yapılır. SySenter'ı ararken, işlemci kayıtlarını daha önce kurulmuş işletim sistemine bağlı kayıtlara göre ayarlar. Özellikle, segment kaydı ve talimat işaretçisinin kaydı kurulur - CS: EIP, yanı sıra yığın segmenti ve Yığın Vertix göstergesi - SS, ESP. Kodun yeni segmentine geçiş ve ofset, halka 3 ila 0'dan gerçekleştirilir.

Sysexit talimatı ters işlem gerçekleştirir. 3. (CPL \u003d 3) içindeki ilgi çekici yerlerden 0 seviyesinden hızlı bir geçiş üretir. Bu durumda, kod segment kaydı, işlemcinin makine bağımlı kayıtında depolanan 16+ CS segment değerine ayarlanır. EIP kaydı, EDX kaydının içeriğine girilir. SS, SS 24'e girilir ve CS değerleri, sysenter talimatının çalışması için bağlamın hazırlanması sırasında makineye bağlı işlemci kaydında makinee bağlı işlemci kaydında kaydedilir. ESP, ECX kaydının içeriğini girdi. SYSENTER / SYSEXIT talimatlarının çalışması için gereken değerler aşağıdaki adreslerde depolanır:

  1. SYSENTER_CS_MSR 0x174, sistem çağrısı işleyici kodunun girildiği bölüm değerinin girildiği bir kod segmentidir.
  2. SYSENTER_ESP_MSR 0x175, bir sistem arama işleyicisi için yığının üst kısmına bir işaretçidir.
  3. SYSENTER_EIP_MSR 0x176 - Kod segmentinin içindeki ofsete işaretçi. Sistem arama işleyicisinin başlangıç \u200b\u200bkodunu belirtir.
Bu adresler, isimleri olmayan modele bağlı kayıtlara atıfta bulunur. Değerler, WRMSR ifadesini kullanarak modele bağlı kayıtlara kaydedilirken, EDX: EAX, sırasıyla 64 bit makine kelimesinin korkunç ve genç bir parçası içermesi gerekir ve ECX, kaydın adresini listelenmelidir. kaydedilecek. Linux'ta, modele bağlı kayıtların adresleri başlık dosyasında tanımlanır / ASM-I368 / MSR-index.h aşağıdaki gibidir (en azından sürüm 2.6.22'ye kadar, en azından başlık dosyasında tanımlandılar / ASM-I386) / Msr.h, Size Linux 2.6.22 çekirdeği örneğinde sistem çağrılarının mekanizmasını düşündüğümüzü hatırlatacağım): #DEFINE MSR_IA32_SYSENTER_CS 0x00000174 #define msr_ia32_sysenter_esp 0x00000175 #define msr_ia32_sysender_eip 0x00000176Modele bağlı kayıtları kurmaktan sorumlu olan temel kod, ARCT / I386 / SYSENTER.C dosyasındadır ve şöyle görünür: 1. void enable_sep_cpu (geçersiz) (2. int cpu \u003d get_cpu (); 3. yapı TSS_struct * TSS \u003d & Per_CPU (init_tss, cpu); 4. Eğer (! Boot_cpu_has (x86_feature_sep)) (5. Put_CPU (); 6 . Geri dönüş;) 7. TSS-\u003e x86_TSS.SS1 \u003d __KERNEL_CS; 8. TSS-\u003e X86_TSS.ESP1 \u003d SizeOF (Struct TSS_struct) + (imzasız uzun) TSS; 9. WRMSR (MSR_IA32_SYSENTER_CS, __KERNEL_CS, 0); 10. WRMSR (MSR_IA32_SYSENTER_ESP, TSS-\u003e X86_TSS.ESP1, 0); 11. WRMSR (MSR_IA32_SYSENTER_EIP, (İmzasız Uzun) Sysenter_Entry, 0); 12. Put_CPU ();)Burada, TSS değişkeninde, görev devlet segmentini tanımlayan yapının adresini elde ediyoruz. TSS (görev durumu segmenti), görevin bağlamını tanımlamak için kullanılır ve X86 mimarisi için çoklu görevli bir donanım destek mekanizmasının bir parçasıdır. Ancak, Linux pratik olarak görev bağlamının donanım anahtarını kullanmaz. Intel belgelerine göre, başka bir göreve geçmek, Dahili geçiş talimatlarını (JMP veya Call), TSS bölümüne veya GDT'deki GDT valf tanımlayıcısına (LDT) yürüterek yapılır. Özel bir işlemci kayıt, programcı için görünmez - TR (Görev Kayıt Ol - Sorun Kayıt) Görev Tanımlayıcı Seçiciyi içerir. Bu kaydı yüklerken, yazılım görünmez baz kayıtları ve TR ile ilişkili bir sınır da indirilir.

Linux'un görev bağlamlarının donanım anahtarlamasını kullanmadığı gerçeğine rağmen, çekirdek, sistemde kurulu olan her işlemci için TSS kaydını ayarlamak zorunda kalır. Bu, işlemcinin kullanıcı modundan çekirdek moduna geçtiğinde, çekirdek yığın adresini TSS'den alır. Ek olarak, I / O bağlantı noktalarına erişimi kontrol etmek için TSS gereklidir. TSS, bağlantı noktalarına bir erişim hakları haritası bulunur. Bu karta dayanarak, giriş / çıkış talimatlarını kullanarak her işlem için bağlantı noktalarına erişimi kontrol etmek mümkün olur. Burada tss-\u003e x86_tss.esp1, çekirdeğin yığını gösterir. __Kernel_CS doğal olarak kodlayıcı kod segmentini gösterir. EIP yer değiştirmesi, SYSENTER_ENTRY () işlevinin adresini gösterir.

SYSENTER_ENTRY () işlevi, Arch / I386 / Kernel / Giriş.s dosyasında tanımlanır ve bu tür: / * SYSENTER_RETURN, VSYSCALL sayfasındaki "SYSENTER" talimatından sonra işaret eder. Sembolü tanımlayan vsyscall-sysentry.s bakın. * / # SYSENTER çağrı işleyici saplı giriş (SYSENTER_ENTRY) CFI_STARTPROC SIMPLE SIMPLE CFI_SIGNAL_FRAME CFI_DEF_CFA ESP, 0 CFI_REGister ESP, EBP MOVL TSSS_SYSENTER_ESP0 (% ESP),% ESP SYSENTER_PAST_ESP: / * * Bu IRQS'i takip etmenize gerek yok: Syscall * Engelli IRQS ve burada doğrudan giriş yaptıktan sonra bunu etkinleştiriyoruz: * / Enable_Tinterraps (CLBR_NONE) Pushl $ (__ user_ds) CFI_ADJust_CFA_OFFSET 4 / * CFI_REL_OFFSET SS, 0 * / Pushl% EBP CFI_ADJUST_CFA_OFFSET 4 CFI_REL_OFFSET ESP, 0 Pushl CFI_ADJust_CFA_OFFSET 4 Pushl $ (__ user_cs ) CFI_ADJUST_CFA_OFFSET 4 / * CFI_REL_OFFSET CS, 0 * / / * * Push Standration_Thread_info () -\u003e SYSENTER_RETURN Yığına. * Tinik bir ofset düzeltmesi gereklidir - 4 * 4, yukarıda itilen 4 kelimenin * anlamına gelir; +8 COMPLE_THREAD "ın ESP0 ayarına karşılık gelir. * / Pushl (Ti_SySenter_return-thread_Size + 8 + 4 * 4) (% ESP) CFI_ADJUST_CFA_OFFSET 4 CFI_REL_OFFSET EIP, 0 / * * Potansiyel altıncı argümanı kullanıcı yığından yükleyin. * Güvenlik hakkında dikkatli olun . * / SPPL $ __ Page_Offset-3,% EBP JAE SYSCALL_FAULT 1: MOVL (% EBP),% EBP .Section __ex_table, "a" .Alng 4 .Long 1b, Syscall_Fault .Previous pushl% eax cfi_adjust_cfa_offset 4 save_All Get_Thread_info (% EBP) / * Not, _TIF_SECCOMP Bit 8'dir ve böylece TestW'ye ihtiyaç duyar ve TestB * / TestW $ (_ TIF_SYSCALL_EMU | _TIF_SECCALL_TRACE | _TIF_SECCOMPL_TRACE | _TIF_SECCOMP | _TIF_SYSCALL_AUDIT), TI_FLAGS (% EBP) JNZ SYSCALL_TRACE_ENTRY CMPL $ (nr_syscalls),% ex Jae Syscall_Badsys * sys_call_table (% eax, 4) movl% eax, pt_eax (% ESP) Disable_Interrupt (clbr_any) trace_irqs_off movl ti_flags (% ebp),% ecx testw $ _TIF_ALLWork_Mask,% cx jne syscall_exit_work / * bir şey değiştirirse Ayrıca SYSEXIT * / MOVL PT_EIP (% ESP),% EDX MOVL PT_OLDESP (% ESP),% ECX XORL% EBP,% EBP TRACE_IRQS_ON 1: MOV PT_FS (% ESP),% FS \u200b\u200benable_interrupt_sysexit cfi_endproc .Pushsection .fixup, "AX" 2: MOVL $ 0, PT_FS (% ESP) JMP 1B .Section __ex_table, "a" .ALIGN 4 .LONG 1B, 2B .POPS ENDPROC (SYSENTER_ENTRY)System_call () durumunda olduğu gibi, ana çalışma çağrısında * SYS_CALL_TABLE (,% EAX, 4) dize yapılır. Belirli bir sistem çağrı işleyicisi burada denir. Böylece, biraz değiştiği görülebilir. Kesinti vektörünün şimdi demir içine tıkanmış ve işlemci, cazibe merkezlerinin bir seviyesinden başka bir değişiklik için daha hızlı bir şekilde daha hızlı yardımcı olur, yalnızca aynı içeriğe sahip yürütmenin bazı detaylarını değiştirmemize yardımcı olur. Doğru, bu değişiklikler bitmiyor. Anlatının neden başladığını hatırla. En başında sanal paylaşılan nesneler hakkında bahsettim. Öyleyse, sistem çağrısının uygulanmasından önce, LIBC kütüphanesinden bir kesme çağrısına benziyordu (kütüphanenin bağlam anahtarlarının sayısını azaltmak için bazı fonksiyonlar yaptığına rağmen), şimdi VDSO sistem çağrısı olabilir. Neredeyse doğrudan, Libc'in katılımı olmadan yapılabilir. Ayrıca doğrudan, yine bir kesme olarak gerçekleştirilebilir. Ancak şimdi arama, dinamik olarak bir bileşen kütüphanesinden (DSO) dışa aktarılan her zamanki fonksiyon olarak istenebilir. Çekirdek yüklenirken, hangi mekanizmanın bu platform için kullanılacağını belirler. Koşullara bağlı olarak, çekirdek giriş noktasını sistem çağrısını gerçekleştiren bir fonksiyona ayarlar. Ayrıca, fonksiyon, Linux-Gate.so.1 kütüphane kullanıcı alanına aktarılır. Linux-Gate.so.1 kütüphane fiziksel olarak diskte mevcut değildir. Eğer koyabilirseniz, çekirdek tarafından öykünür ve sistemin ne kadar çalıştığını tam olarak var. Sistemi durdurursanız, kök FS'yi başka bir sistemden baltalayın, o zaman bu dosyayı kök FS'de bulamazsınız. Aslında, bir çalışma sisteminde bile bulamayacaksınız. Fiziksel olarak, sadece değil. Bu yüzden Linux-Gate.so.1.1 vdso gibi bir şeydir - yani. Sanal dinamik olarak paylaşılan nesne. Çekirdek, dinamik kütüphaneyi bu şekilde her işlemin adres alanında görüntüler. Aşağıdaki komutu çalıştırırsanız, bunun kolay olduğundan emin olun: [E-posta Korumalı]: ~ $ Cat / Proc / Self / Haritalar 08048000-0804C000 R-XP 00000000 08:01 46 / Bin / Cat 0804C000-0804D000 RW-P 00003000 08:01 46 / Bin / Cat 0804D000-0806E000 RW-P 0804D000 00:00 0 ... B7FDF000-B7FE1000 RW-P 00019000 08:01 2066 /Lib/ld-2.5.So BFFD2000-BFFE8000 RW-P BFFD2000 00:00 0 FFFFE000-FFFFFF000 R-XP 00000000 00:00 0Burada en son satır ilginin amacıdır: FFFFE000-FFFFFF000 R-XP 00000000 00:00 0Yukarıdaki örnekten, nesnenin hafızaya girdiği görülebilir, neredeyse adres alanının arka bahçelerinde, tam olarak bir sayfa - 4096 bayt. Başka bir deney çizelim: [E-posta Korumalı]: ~ $ Ldd `Hangi kedi linux-gate.so.1 \u003d\u003e (0xffffe000) libc.so.6 \u003d\u003e / lib/tls/i686/cmov/libc.so.6 (0xB7E87000) / lib / ld-linux .So.2 (0xb7fdf000) [E-posta Korumalı]: ~ $ Ldd `hangi gcc` linux-gate.so.1 \u003d\u003e (0xffffe000) libc.so.6 \u003d\u003e / lib/tls/i686/cmov/libc.so.6 (0xB7E3C000) / lib / ld-linux .So.2 (0xB7F94000) [E-posta Korumalı]:~$ Burada sadece iki başvuru yaptık. Kütüphanenin, işlemin bir adres alanında aynı kalıcı adrese göre görüntüleneceği görülebilir - 0xffffe000. Şimdi bu hafıza sayfasında ne kaydedilenleri görmeye çalışalım ...

Paylaşılan VDSO kodunun kullanıldığı bellek sayfası dökülmesini yapın sonraki program: #include #include #include int ana () () () (char * vdso \u003d 0xffffe000; char * tampon; dosya * f; arabellek \u003d malloc (4096); eğer (! Tampon) çıkış (1); Memcy (Tampon, VDSO, 4096) eğer (! (F \u003d fopen ("test.dump", "w + b"))))) (ücretsiz (tampon); çıkış (1);) fwrite (tampon, 4096, 1, f); f1 (f) ); Ücretsiz (tampon); geri dönüş 0;)Kesinlikle, ekibi kullanarak daha kolay yapılmadan önce, dD if \u003d / proc / self / mem \u003d test.dump bs \u003d 4096 skip \u003d 1048574 sayım \u003d 1Ancak sürüm 2.6.22 veya belki daha önce bile başlayan çekirdekler, artık işlem belleğini artık dosyaya / proc / `pid` / mem. Bu dosya, açıkça, uyumluluk için kaydedilir, ancak daha fazla bilgi içermiyor.

CommSise ve verilen programı çalıştırın. Elde edilen kodu sökmeye çalışalım: [E-posta Korumalı]: ~ / Tmp $ objdump --DisAssemble ./test.dump ./test.dump: Dosya dosyası formatı elf32-i386 Bölümün sökülmesi .Text: FFFFE400<__kernel_vsyscall>: FFFFE400: 51 Push% ECX FFFFE401: 52 Push% EDX FFFFE402: 55 Push% EBP FFFFE403: 89 E5 MOV% ESP,% EBP FFFFE405: 0F 34 SYSENTER ... FFFFFE40E: EB F3 JMP FFFFE403<__kernel_vsyscall+0x3> FFFFE410: 5D POP% EBP FFFFE411: 5A POP% EDX FFFFE412: 59 POP% ECX FFFFE413: C3 RET ... [E-posta Korumalı]: ~ / Tmp $Burada, avuç içi gibi sistem çağrıları için ağ geçidimizdir. İşlem (veya, LIBC Sistem Kütüphanesi), __Kernel_vsyscall işlevini çağırmak, 0xffffe400 (bizim durumumuzda) adrese düşer. Sonra, __KERNEL_VSYSCALL ECX, EDX, EDX kayıtlarının içeriğini kaydeder EDX, EDX kayıtları, EDX kayıtları, ECX ve EDX kayıt kayıtları hakkında daha önce daha önce konuştuk, daha sonra kullanıcının yığını geri yüklemek için kullanılır. Sysenter talimatı yürütülür, "Interptice Interrupt" ve sonuç olarak, SYSENTER_ENTRY'ye bir sonraki geçiş (yukarıya bakınız). 0xffffe40e'deki JMP talimatı, sistem mücadelesini bir dizi 6 argümanla yeniden başlatmak için eklenir (bkz. Http://lkml.org/lkml/2002/12/18/). Sayfada yayınlanan kod, ARCT / I386 / KERNEL / VSYSCALL-ENTERN.S (veya ARCH / I386 / KERNEL / VSYSCALL-INT80.S) Tuzak 0x80 içindir). __Kernel_vsyscall işlevinin adresinin kalıcı olduğunu tespit etmiş olsa da, ancak olmadığı bir görüş var. Genellikle, __kernel_vsyscall () içindeki giriş noktasının konumu, AT_SYSINFO parametresini kullanarak Elf-Auxv vektörü tarafından bulunabilir. Elf-auxv vektörü, başlarken ve program sırasında gerekli çeşitli bilgileri içerdiğinde yığın boyunca işleme iletilen bilgileri içerir. Özellikle bu vektör, sürecin ortamının değişkenlerini, argümanları, vb.

Doğrudan __Kernel_vsyscall işlevine nasıl başvurulacağı konusunda küçük bir örnek: #Dahil etmek. int pid; İnt main () (__asm \u200b\u200b("MOVL $ 20,% eax \\ n" "Çağrı *% GS: 0x10 \\ n" "MOVL% EAX, PID \\ N"); PrintF ("PID:% D \\ N" , PID); 0 döndürme;)Bu örnek Manu Garg sayfasından alınır, http://www.manugarg.com. Böylece, verilen örnekte, GETPID () sistem çağrısını () (20 numaralı veya başka türlü __nr_getpid) yaparız. AT_SYSInfo değişkeninin aranmasında işlem yığınına tırmanmamasına rağmen, LIBC.SOSY sistem kitaplığının AT_SYSINFO değişkeninin akış kontrol ünitesine (TCB - iplik kontrol bloğu) değeri ile kopyalandığını kullanıyoruz. Bu bilgi bloğu, kural olarak, GS'deki seçiciyi ifade eder. Ofset 0x10'da istenen bir parametre olduğu ve% GS'de depolanan adrese bir arama yaptığını varsayıyoruz.

Sonuçlar.

Aslında, pratik olarak, özel performans, bu FSCF'nin (Hızlı Sistem Çağrı Tesisi) desteğiyle bile, her zaman elde etmek mümkün değildir. Sorun şu ki, böylece işlem nadiren doğrudan çekirdeğe atıfta bulunur. Ve bunun için iyi sebepler var. LIBC kütüphanesini kullanmak, çekirdek versiyonundan bağımsız olarak, programın taşınabilirliğini garanti etmenizi sağlar. Ve çoğu sistem çağrısının gittiği standart sistem kütüphanesinden geçiyor. FSCF'yi destekleyen bir platform için monte edilmiş en son çekirdeği toplamlasanız ve kursanız bile, bu bir verimlilik artışının garantisi değildir. Gerçek şu ki, LIGC.SOSY sistem kitaplığınızın Int 0x80'i kullanmak için düzleştirileceği ve yalnızca GliBC'nin geçişi ile başa çıkabileceğidir. VDSO ve __KERNEL_VSYSCALL arayüzü ve __KERNEL_VSYSCALL, GliBC'de desteklenir mi, dürüstçe cevap vermeyi zor buluyorum.

Bağlantılar.

Manu Garg "s sayfası, http://www.manugarg.com
Scatter / Thougohts Johan Petersson, http://www.trilithium.com/johan/2005/08/linux-gate/ tarafından toplayın.
Eski İyi Anlamak Linux çekirdeği onsuz nerede :)
Ve tabi ki, kaynak Kodları Linux (2.6.22)