Računalniki Windows internet

Man syscalls (2): sistemski klici Linuxa. Linux - sistemski klici. Sistemski klici v Linuxu Prekinitve v arhitekturi x86


Sistemski klici

Doslej so morali vsi programi, ki smo jih izdelali, uporabljati dobro definirane mehanizme jedra za registracijo datotek /proc in gonilnikov naprav. To je super, če želite narediti nekaj, kar so že zagotovili programerji jedra, na primer napisati gonilnik naprave. Kaj pa, če želite narediti nekaj modnega, na nek način spremeniti vedenje sistema?

Točno tukaj programiranje jedra postane nevarno. Med pisanjem spodnjega primera sem uničil odprt sistemski klic. To je pomenilo, da ne morem odpreti nobenih datotek, ne morem zagnati nobenega programa in ne morem zapreti sistema z ukazom za zaustavitev. Moram izklopiti napajanje, da ga ustavim. Na srečo ni bila uničena nobena datoteka. Če želite zagotoviti, da tudi ne izgubite nobene datoteke, opravite sinhronizacijo, preden izdate ukaza insmod in rmmod.

Pozabite na datoteke /proc in datoteke naprav. So le majhne podrobnosti. Pravi komunikacijski proces jedra, ki ga uporabljajo vsi procesi, so sistemski klici. Ko proces zahteva storitev iz jedra (na primer odpiranje datoteke, zagon novega procesa ali zahtevanje dodatnega pomnilnika), se uporablja ta mehanizem. Če želite spremeniti obnašanje jedra na zanimive načine, je to pravo mesto. Mimogrede, če želite videti, katere sistemske klice je uporabil program, zaženite: strace .

Na splošno proces ne more dostopati do jedra. Ne more dostopati do pomnilnika jedra in ne more klicati funkcij jedra. Strojna oprema CPE uveljavlja to stanje (z razlogom se imenuje 'zaščiten način'). Sistemski klici so izjema od tega splošnega pravila. Proces napolni registre z ustreznimi vrednostmi in nato pokliče posebno navodilo, ki skoči na vnaprej določeno lokacijo v jedru (seveda jo berejo uporabniški procesi, vendar ne prepišejo.) Pod Intelovimi procesorji se to naredi prek prekinitve 0x80. Strojna oprema ve, da ko skočite na to lokacijo, ne delujete več v omejenem uporabniškem načinu. Namesto tega izvajate kot jedro operacijskega sistema, zato vam je dovoljeno delati, kar želite.

Mesto v jedru, kamor lahko proces skoči, se imenuje sistemski_klic. Postopek, ki je tam, preveri številko sistemskega klica, ki jedru natančno pove, kaj proces želi. Nato poišče tabelo sistemskih klicev (sys_call_table), da najde naslov funkcije jedra, ki jo je treba poklicati. Nato se pokliče želena funkcija in potem, ko vrne vrednost, se v sistemu izvede več preverjanj. Rezultat se nato vrne nazaj v proces (ali v drug proces, če se je proces končal). Če želite videti kodo, ki naredi vse to, je v izvorni datoteki arch/< architecture >/kernel/entry.S , za vrstico ENTRY(system_call).

Torej, če želimo spremeniti delovanje nekega sistemskega klica, moramo najprej napisati lastno funkcijo, ki bo naredila ustrezno stvar (običajno dodamo nekaj svoje kode in nato pokličemo izvirno funkcijo), nato spremenimo kazalec na sys_call_table, da pokaže na našo funkcijo. Ker bomo pozneje morda izbrisani in ne želimo pustiti sistema v nestanovitnem stanju, je pomembno, da cleanup_module obnovi tabelo v prvotno stanje.

Tukaj navedena izvorna koda je primer takega modula. Želimo "vohuniti" za nekim uporabnikom in poslati sporočilo prek printk vsakič, ko ta uporabnik odpre datoteko. Sistemski klic odprte datoteke zamenjamo z lastno funkcijo, imenovano our_sys_open. Ta funkcija preveri uid (ID uporabnika) trenutnega procesa in če je enak uid-u, za katerim vohunimo, pokliče printk, da prikaže ime datoteke, ki se odpre. Nato pokliče prvotno odprto funkcijo z enakimi parametri in dejansko odpre datoteko.

Funkcija init_module spremeni ustrezno lokacijo v sys_call_table in shrani izvirni kazalec v spremenljivko. Funkcija cleanup_module uporablja to spremenljivko za obnovitev vsega nazaj v normalno stanje. Ta pristop je nevaren zaradi možnosti, da dva modula spremenita isti sistemski klic. Predstavljajte si, da imamo dva modula, A in B. Odprti sistemski klic modula A se bo imenoval A_open, klic istega modula B pa B_open. Zdaj, ko je bil sistemski klic, vbrizgan v jedro, nadomeščen z A_open, ki bo poklical izvirni sys_open, ko bo opravil, kar mora storiti. Nato bo B vstavil v jedro in zamenjal sistemski klic z B_open, ki bo poklical tisto, kar misli, da je prvotni sistemski klic, vendar je dejansko A_open.

Zdaj, če najprej odstranite B, bo vse v redu: to bo samo obnovilo sistemski klic na A_open, ki kliče izvirnik. Če pa odstranite A in nato odstranite B, se bo sistem zrušil. Odstranitev A bo obnovila sistemski klic na izvirnik, sys_open, pri čemer bo B izrezan iz zanke. Potem, ko bo B odstranjen, bo obnovil sistemski klic na tisto, kar meni, da je izvirnik. Pravzaprav bo klic usmerjen v A_open, ki ni več v pomnilniku. Na prvi pogled se zdi, da bi lahko to težavo rešili tako, da preverimo, ali je sistemski klic enak naši odprti funkciji, in če je tako, ne spreminjajte vrednosti tega klica (da B ne spremeni sistemskega klica, ko ga odstranite), ampak to bi še vedno pomenilo najhujšo težavo. Ko je A odstranjen, vidi, da je bil sistemski klic spremenjen v B_open, tako da ne kaže več na A_open, zato ne bo obnovil kazalca na sys_open, preden je bil odstranjen iz pomnilnika. Na žalost bo B_open še vedno poskušal poklicati A_open, ki ni več v pomnilniku, tako da se bo sistem še vedno zrušil tudi brez odstranitve B.

Vidim dva načina za preprečevanje te težave. Prvič: obnovite klic na prvotno vrednost sys_open. Žal sys_open ni del tabele sistemskega jedra v /proc/ksyms , zato do nje ne moremo dostopati. Druga rešitev je uporaba števca povezav, da preprečite raztovarjanje modula. To je dobro za običajne module, slabo pa za "izobraževalne" module.

/* syscall.c * * Sistemski klic "krade" vzorec */ /* Avtorske pravice (C) 1998-99 Ori Pomerantz */ /* Potrebne datoteke z glavo */ /* Standardno v modulih jedra */ #include /* Delamo z jedrom */ #include /* Natančneje, modul */ /* Obravnava CONFIG_MODVERSIONS */ #if CONFIG_MODVERSIONS==1 #define MODVERSIONS #include #endif #vključi /* Seznam sistemskih klicev */ /* Za trenutno (procesno) strukturo potrebujemo * to, da vemo, kdo je trenutni uporabnik. */ #vključi /* V 2.2.3 /usr/include/linux/version.h vključuje * makro za to, 2.0.35 pa ne - zato ga po potrebi dodam * tukaj. */ #ifndef KERNEL_VERSION #define KERNEL_VERSION(a ,b,c) ((a)*65536+(b)*256+(c)) #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) #include #endif /* Tabela sistemskih klicev (tabela funkcij). To * samo definiramo kot zunanje in jedro * ga bo zapolnilo namesto nas, ko smo insmod"ed */ extern void *sys_call_table; /* UID, za katerim želimo vohuniti - bo napolnjen iz * ukazne vrstice */ int uid; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) MODULE_PARM(uid, "i"); #endif /* Kazalec na izvirni sistemski klic. Razlog * ohranimo to, namesto da pokličemo izvirno funkcijo * (sys_open), ker je morda nekdo drug morda * zamenjal sistemski klic pred nami funkcijo v tem modulu - in bi jo lahko * odstranili, preden smo mi. * Je statična spremenljivka, zato ni izvožena. */ asmlinkage int (*original_call)(const char *, int, int); /* Iz nekega razloga mi je v 2.2.3 current->uid dal * nič, ni pravi ID uporabnika. Poskušal sem najti, kaj je šlo * narobe, vendar tega nisem mogel storiti v kratkem času, in * sem len - zato bom uporabil sistemski klic, da dobim * uid, tako, kot bi postopek. * * Iz nekega razloga je po ponovnem prevajanju jedra ta * težava izginila. */ asmlinkage int (*getuid_call)(); /* Funkcija, s katero "bomo zamenjali sys_open (funkcija *, ki jo kličete, ko pokličete odprt sistemski klic). Če želite * poiskati natančen prototip, s številom in vrsto * argumentov, najprej poiščemo izvirno funkcijo * (to" s na fs/open.c). * * Teoretično to pomeni, da smo vezani na * trenutno različico jedra. V praksi se sistemski klici * skoraj nikoli ne spremenijo (to bi uničilo * in zahtevalo ponovno prevajanje programov, saj so sistemski klici * vmesnik med jedrom in * procesi).*/ asmlinkage int our_sys_open(const char *ime datoteke, zastavice int, način int) ( int i = 0; char ch; /* Preverite, ali je to uporabnik, za katerim vohunimo */ if (uid == getuid_call()) ( /* getuid_call je sistemski klic getuid, * ki daje uid uporabnika, ki je * pognal proces, ki je poklical sistem * klic smo dobili */ /* Prijavite datoteko, če je relevantno */ printk("Odprl datoteko %d: ", uid); do ( #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) get_user(ch, ime datoteke+i); #else ch = get_user(ime datoteke+ i) ); #endif i++; printk("%c", ch); ) while (ch != 0); printk("\n"); ) /* Pokličite izvirni sys_open - sicer izgubimo * možnost odpiranja datoteke */ vrni izvirni_klic(ime datoteke, zastavice, način); ) /* Inicializirajte modul - zamenjajte sistemski klic */ int init_module() ( /* Opozorilo - prepozno za to zdaj, morda pa za * naslednjič. .. */ printk("Nevaren sem. Upam, da si naredil "); printk("sinhroniziraj, preden si me insmod"ed.\n"); printk("Moj nasprotnik, cleanup_module(), je sodo"); printk("bolj nevarno. Če\n"); printk("cenite svoj datotečni sistem, bo"); printk("be \"sync; rmmod\" \n"); printk("ko odstranite ta modul.\n"); /* Ohranite kazalec na izvirno funkcijo v * original_call, nato pa zamenjajte sistemski klic * v tabeli sistemskih klicev z our_sys_open */ original_call = sys_call_table[__NR_open]; sys_call_table[__NR_open] = our_sys_open; /* Če želite dobiti naslov funkcije za sistemski * klic foo, pojdite na sys_call_table[__NR_foo]. */ printk("Vohunjenje za UID:%d\n", uid); /* Pridobite sistemski klic za getuid */ getuid_call = sys_call_table[__NR_getuid]; vrni 0; ) /* Čiščenje - prekliči registracijo ustrezne datoteke iz /proc */ void cleanup_module() ( /* Vrni sistemski klic nazaj na normalno stanje */ if (sys_call_table[__NR_open] != our_sys_open) ( printk("Nekdo drug se je tudi igral z "); printk("odprt sistemski klic\n"); printk("Sistem je morda ostal v "); printk("nestabilno stanje.\n"); ) sys_call_table[__NR_open] = original_call; )

Najpogosteje je koda za sistemski klic oštevilčena __NR_xxx, opredeljena v /usr/include/asm/unistd.h, lahko najdete v izvorni kodi jedra Linux v funkciji sys_xxx(). (Tabelo klicev za i386 najdete v /usr/src/linux/arch/i386/kernel/entry.S.) Od tega pravila je veliko izjem, predvsem zaradi dejstva, da je večina starih sistemskih klicev zamenjana z novimi in brez sistema. Na platformah z lastniško emulacijo OS, kot so parisc, sparc, sparc64 in alpha, obstaja veliko dodatnih sistemskih klicev; mips64 ima tudi celoten nabor 32-bitnih sistemskih klicev.

Sčasoma se je vmesnik nekaterih sistemskih klicev po potrebi spremenil. Eden od razlogov za te spremembe je bila potreba po povečanju velikosti struktur ali skalarnih vrednosti, posredovanih v sistemski klic. Zaradi teh sprememb so se na nekaterih arhitekturah (in sicer na starem 32-bitnem i386) pojavile različne skupine podobnih sistemskih klicev (npr. skrajšati(2) in okrniti64(2)), ki opravljajo enake naloge, vendar se razlikujejo po velikosti svojih argumentov. (Kot že omenjeno, to ne vpliva na aplikacije: ovoji glibc opravijo nekaj dela, da sprožijo pravilen sistemski klic, kar zagotavlja združljivost ABI za starejše binarne datoteke.) Primeri sistemskih klicev, ki imajo več različic:

*Trenutno obstajajo tri različne različice stat(2): sys_stat() (mesto __NR_oldstat), sys_newstat() (mesto __NR_stat) in sys_stat64() (mesto __NR_stat64), slednji je trenutno v uporabi. Podobna situacija z lstat(2) in fstat(2). * Podobno opredeljeno __NR_oldolduname, __NR_olduname in __NR_uname za klice sys_olduname(), sys_uname() in sys_newuname(). * Linux 2.0 ima novo različico vm86(2) se imenujejo nova in stara različica jedrskih postopkov sys_vm86old() in sys_vm86(). * Linux 2.4 ima novo različico getrlimit(2) se imenuje nova in stara različica jedrskih postopkov sys_old_getrlimit() (mesto __NR_getrlimit) in sys_getrlimit() (mesto __NR_ugetrlimit). * V Linuxu 2.4 je bila velikost polja ID uporabnika in skupine povečana s 16 na 32 bitov. Za podporo tej spremembi je bilo dodanih več sistemskih klicev (npr. chown32(2), getuid32(2), getgroups32(2), setresuid32(2)), ki opusti prejšnje klice z istimi imeni, vendar brez pripone "32". * Linux 2.4 je dodal podporo za dostop do velikih datotek (katerih velikosti in odmiki ne ustrezajo 32-bitnim) v aplikacijah na 32-bitnih arhitekturah. To je zahtevalo spremembe sistemskih klicev, ki delujejo z velikostmi datotek in odmiki. Dodani so bili naslednji sistemski klici: fcntl64(2), getdents64(2), stat64(2), statfs64(2), okrniti64(2) in njihove analogne, ki obdelujejo deskriptorje datotek ali simbolne povezave. Ti sistemski klici odpravijo stare sistemske klice, ki so z izjemo klicev "stat" poimenovani enako, vendar nimajo pripone "64".

Na novejših platformah, ki imajo samo 64-bitni dostop do datotek in 32-bitni UID/GID (npr. alpha, ia64, s390x, x86-64), obstaja samo ena različica sistemskih klicev za UID/GID in dostop do datotek. Na platformah (običajno 32-bitne platforme), ki imajo klice *64 in *32, so druge različice zastarele.

* Izzivi rt_sig* dodano jedru 2.2 za podporo dodatnih signalov v realnem času (glej signal(7)). Ti sistemski klici opuščajo stare sistemske klice z istim imenom, vendar brez predpone "rt_". * V sistemskih klicih izberite(2) in mmap(2) uporabljenih je pet ali več argumentov, kar je povzročilo težave pri določanju, kako so bili argumenti posredovani na i386. Kot rezultat, medtem ko na drugih arhitekturah klice sys_select() in sys_mmap() tekmo __NR_izberi in __NR_mmap, na i386 ustrezajo old_select() in old_mmap() (postopki z uporabo kazalca na blok argumentov). Trenutno ni več problem s posredovanjem več kot petih argumentov in obstaja __NR__izberi novice, kar se natančno ujema sys_select(), in ista situacija z __NR_mmap2.

To gradivo je modifikacija istoimenskega članka Vladimirja Meshkova, objavljenega v reviji "System Administrator"

To gradivo je kopija člankov Vladimirja Meshkova iz revije "System Administrator". Te članke najdete na spodnjih povezavah. Spremenjenih je bilo tudi nekaj primerov programskih izvornih besedil – izboljšanih, dodelanih. (Primer 4.2 je bil močno spremenjen, saj je bilo treba prestreči nekoliko drugačen sistemski klic) URL-ji: http://www.samag.ru/img/uploaded/p.pdf http://www.samag.ru/img/ naloženo/a3. pdf

Imate vprašanja? Potem ste tukaj: [email protected]

  • 2. Modul jedra, ki ga je mogoče naložiti
  • 4. Primeri prestrezanja sistemskih klicev na osnovi LKM
    • 4.1 Onemogoči ustvarjanje imenika

1. Splošni pogled na arhitekturo Linuxa

Najbolj splošen pogled nam omogoča, da vidimo dvonivojski model sistema. jedro<=>progs V sredini (na levi) je jedro sistema. Jedro je neposredno v interakciji z računalniško strojno opremo in izolira aplikacijske programe od arhitekturnih značilnosti. Jedro ima nabor storitev, ki so na voljo za aplikacijske programe. Storitve jedra vključujejo V/I operacije (odpiranje, branje, pisanje in upravljanje datotek), ustvarjanje in upravljanje procesov, njihovo sinhronizacijo in medprocesno komunikacijo. Vse aplikacije zahtevajo storitve jedra prek sistemskih klicev.

Drugi nivo sestavljajo aplikacije oziroma naloge, tako sistemske, ki določajo funkcionalnost sistema, kot aplikacijske, ki zagotavljajo uporabniški vmesnik Linuxa. Kljub zunanji heterogenosti aplikacij pa so sheme za interakcijo z jedrom enake.

Interakcija z jedrom poteka prek standardnega vmesnika sistemskega klica. Vmesnik sistemskega klica je nabor storitev jedra in določa obliko zahtev za storitve. Proces zahteva storitev tako, da pokliče določeno proceduro jedra, ki je videti kot običajen klic funkcije knjižnice. Jedro izvrši zahtevo v imenu procesa in procesu vrne zahtevane podatke.

V zgornjem primeru program odpre datoteko, iz nje prebere podatke in zapre datoteko. V tem primeru operacijo odpiranja (odpiranja), branja (branja) in zapiranja (zapiranja) datoteke izvede jedro na zahtevo naloge, odpiranje (2), branje (2) in zapiranje (2). ) funkcije so sistemski klici.

/* Vir 1.0 */ #include main () ( int fd; char buf; /* Odpri datoteko - pridobi povezavo (deskriptor datoteke) fd */ fd = open("file1",O_RDONLY); /* Preberi 80 znakov v buffer buf */ read( fd, buf , sizeof(buf)); /* Zaprite datoteko */ close(fd); ) /* EOF */ Celoten seznam sistemskih klicev OS Linux najdete v /usr/include/asm/unistd.h . Oglejmo si zdaj mehanizem za opravljanje sistemskih klicev v tem primeru. Prevajalnik, ki je izpolnil funkcijo open() za odpiranje datoteke, jo pretvori v sestavljalsko kodo, naloži številko sistemskega klica, ki ustreza tej funkciji, in njene parametre v registre procesorja in nato pokliče prekinitev 0x80. Naslednje vrednosti se naložijo v registre procesorja:

  • v register EAX - številka sistemskega klica. Torej je v našem primeru številka sistemskega klica 5 (glejte __NR_open).
  • v register EBX - prvi parameter funkcije (za open() je kazalec na niz, ki vsebuje ime datoteke, ki se odpira.
  • v register ECX - drugi parameter (pravice dostopa do datotek)
Tretji parameter se naloži v register EDX, v tem primeru ga nimamo. Za izvedbo sistemskega klica v OS Linux se uporablja funkcija system_call, ki je definirana (odvisno od arhitekture v tem primeru i386) v datoteki /usr/src/linux/arch/i386/kernel/entry.S. Ta funkcija je vstopna točka za vse sistemske klice. Jedro se na prekinitev 0x80 odzove s klicem funkcije system_call, ki je v bistvu upravljavec prekinitev 0x80.

Da se prepričamo, da smo na pravi poti, si oglejmo kodo za funkcijo open() v sistemski knjižnici libc:

# gdb -q /lib/libc.so.6 (gdb) disas odprt Izpis zbirne kode za funkcijo odprto: 0x000c8080 : pokliči 0x1082be< __i686.get_pc_thunk.cx >0x000c8085 : dodaj $0x6423b,%ecx 0x000c808b : cmpl $0x0.0x1a84(%ecx) 0x000c8092 : jne 0xc80b1 0x000c8094 : potisnite %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 ... Kot lahko vidite v zadnjih vrsticah, se parametri posredujejo v registre EDX, ECX, EBX, zadnji register EAX pa je napolnjen s številko sistemskega klica, ki je, kot že vemo, 5 .

Zdaj pa se vrnimo k mehanizmu sistemskih klicev. Torej, jedro pokliče upravljalnik prekinitev 0x80 - funkcijo system_call. System_call potisne kopije registrov, ki vsebujejo klicne parametre, v sklad z uporabo makra SAVE_ALL in pokliče želeno sistemsko funkcijo z ukazom za klic. Tabela kazalcev na funkcije jedra, ki izvajajo sistemske klice, se nahaja v matriki sys_call_table (glejte datoteko arch/i386/kernel/entry.S). Številka sistemskega klica, ki se nahaja v registru EAX, je indeks tega niza. Če torej EAX vsebuje vrednost 5, bo poklicana funkcija jedra sys_open(). Zakaj je potreben makro SAVE_ALL? Razlaga tukaj je zelo preprosta. Ker so skoraj vse sistemske funkcije jedra zapisane v C, iščejo svoje parametre na skladu. In parametri se potisnejo v sklad z SAVE_ALL! Vrnjena vrednost sistemskega klica je shranjena v registru EAX.

Zdaj pa poglejmo, kako prestreči sistemski klic. Pri tem nam bo v pomoč mehanizem naložljivih modulov jedra.

2. Modul jedra, ki ga je mogoče naložiti

Naložljiv modul jedra (LKM - Loadable Kernel Module) je koda, ki se izvaja v prostoru jedra. Glavna značilnost LKM je zmožnost dinamičnega nalaganja in razkladanja brez ponovnega zagona celotnega sistema ali ponovnega prevajanja jedra.

Vsak LKM je sestavljen iz dveh glavnih funkcij (najmanj):

  • funkcija inicializacije modula. Klicano, ko je LKM naložen v pomnilnik: int init_module(void) ( ... )
  • Funkcija razlaganja modula: void cleanup_module(void) ( ... )
Tukaj je primer najpreprostejšega modula: /* Source 2.0 */ #include int init_module(void) ( printk("Hello World\n"); return 0; ) void cleanup_module(void) ( printk("Adijo\n"); ) /* EOF */ Prevedi in naloži modul. Nalaganje modula v pomnilnik se izvede z ukazom insmod, ogled naloženih modulov pa z ukazom lsmod: # gcc -c -DMODULE -I /usr/src/linux/include/ src-2.0.c # insmod src-2.0.o Opozorilo: nalaganje src-2.0 .o bo uničilo jedro: ni naložen licencni modul src-2.0, z opozorili # dmesg | tail -n 1 Pozdravljeni svet # lsmod | grep src src-2.0 336 0 (neuporabljeno) # rmmod src-2.0 # dmesg | rep -n 1 Adijo

3. Algoritem za prestrezanje sistemskega klica na osnovi LKM

Za implementacijo modula, ki prestreže sistemski klic, je treba definirati algoritem za prestrezanje. Algoritem je naslednji:
  • shranite kazalec na izvirni (izvirni) klic, da ga je mogoče obnoviti
  • ustvarite funkcijo, ki izvaja nov sistemski klic
  • zamenjaj klice v tabeli sistemskih klicev sys_call_table, t.j. nastavi ustrezen kazalec na nov sistemski klic
  • ob koncu dela (ko je modul razložen) obnovite prvotni sistemski klic z uporabo predhodno shranjenega kazalca
Sledenje vam omogoča, da ugotovite, kateri sistemski klici so vključeni v delovanje uporabniške aplikacije. S sledenjem lahko določite, kateri sistemski klic je treba prestreči, da bi prevzeli nadzor nad aplikacijo. # ltrace -S ./src-1.0 ... odprt("datoteka1", 0, 01 SYS_open("datoteka1", 0, 01) = 3<... open resumed>) = 3 branje (3, SYS_read(3, "123\n", 80) = 4<... read resumed>"123\n", 80) = 4 zapri (3 SYS_close(3) = 0<... close resumed>) = 0 ... Zdaj imamo dovolj informacij, da začnemo preučevati primere implementacij modulov, ki prestrežejo sistemske klice.

4. Primeri prestrezanja sistemskih klicev na osnovi LKM

4.1 Onemogoči ustvarjanje imenika

Ko je imenik ustvarjen, se pokliče funkcija jedra sys_mkdir. Parameter je niz, ki vsebuje ime imenika, ki ga želite ustvariti. Razmislite o kodi, ki prestreže ustrezen sistemski klic. /* Vir 4.1 */ #include #vključi #vključi /* Izvozi tabelo sistemskih klicev */ extern void *sys_call_table; /* Definiraj kazalec za shranjevanje izvirnega klica */ int (*orig_mkdir)(const char *path); /* Ustvarimo lasten sistemski klic. Naš klic ne naredi ničesar, samo vrne null */ int own_mkdir(const char *path) ( return 0; ) /* Med inicializacijo modula shranite kazalec na prvotni klic in zamenjajte sistemski klic */ int init_module(void) ( orig_mkdir =sys_call_table; sys_call_table=own_mkdir; printk("sys_mkdir zamenjan\n"); return(0); ) /* Ob razkladanju obnovite prvotni klic */ void cleanup_module(void) ( sys_call_table=orig_mkdir; printk("sys_mkdir premaknjen nazaj \n "); ) /* EOF */ Če želite pridobiti objektni modul, zaženite naslednji ukaz in zaženite nekaj poskusov v sistemu: # gcc -c -DMODULE -I/usr/src/linux/include/ src-3.1. c # dmesg | tail -n 1 sys_mkdir zamenjan # mkdir test # ls -ald test ls: test: ni takšne datoteke ali imenika # rmmod src-3.1 # dmesg | tail -n 1 sys_mkdir premaknjen nazaj # mkdir test # ls -ald test drwxr-xr-x 2 root root 4096 2003-12-23 03:46 test Kot lahko vidite, ukaz "mkdir" ne deluje oziroma nič zgodi. Raztovarjanje modula je dovolj za obnovitev delovanja sistema. Kar je bilo storjeno zgoraj.

4.2 Skrivanje vnosa datoteke v imeniku

Ugotovimo, kateri sistemski klic je odgovoren za branje vsebine imenika. Za to bomo napisali še en testni fragment, ki bere trenutni imenik: /* Source 4.2.1 */ #include #vključi int main() ( DIR *d; struct dirent *dp; d = opendir("."); dp = readdir(d); vrni 0; ) /* EOF */ Pridobi izvedljivo datoteko in sled: # gcc -o src -3.2.1 src-3.2.1.c # ltrace -S ./src-3.2.1 ... opendir("." SYS_OPEN (".", 100352, 010005141300) = 3 SYS_FSTAT64 (3, 0xbffffff79C, 0x4014C2C0, 3, 0xBFFFF874) = 0x_FCNTL64 (3, 2, 1, 1, 0x4014C2C0) = 0 SYS_BRK (null) = 0x080495F4 SYS_BRK (0x0806A5F4) = 0x0806a5f4 SYS_brk(NULL) = 0x0806a5f4 SYS_brk(0x0806b000) = 0x0806b000<... opendir resumed>) = 0x08049648 readdir(0x08049648 SYS_getdents64(3, 0x08049678, 4096, 0x40014400, 0x4014c2c0) = 528<... readdir resumed>) = 0x08049678 ... Bodite pozorni na zadnjo vrstico. Vsebino imenika bere funkcija getdents64 (getdents je možen v drugih jedrih). Rezultat je shranjen kot seznam struktur tipa struct dirent, funkcija pa sama vrne dolžino vseh vnosov v imeniku. Zanimata nas dve področji te strukture:
  • d_reclen - velikost zapisa
  • d_name - ime datoteke
Če želite skriti vnos datoteke o datoteki (z drugimi besedami, narediti nevidno), morate prestreči sistemski klic sys_getdents64, poiskati ustrezen vnos na seznamu prejetih struktur in ga izbrisati. Razmislite o kodi, ki izvaja to operacijo (avtor izvirne kode je Michal Zalewski): /* Vir 4.2.2 */ #include #vključi #vključi #vključi #vključi #vključi #vključi #vključi extern void *sys_call_table; int (*orig_getdents)(u_int fd, struct dirent *dirp, u_int count); /* Definiraj naš sistemski klic */ int own_getdents(u_int fd, struct dirent *dirp, u_int count) ( unsigned int tmp, n; int t; struct dirent64 ( int d_ino1,d_ino2; int d_off1,d_off2; nepodpisani kratki d_reclen; unsigned char d_type; char d_name; ) *dirp2, *dirp3; /* Ime datoteke, ki jo želimo skriti */ char hide = "file1"; /* Določi dolžino vnosov v imeniku */ tmp = (*orig_getdents )(fd,dirp ,count); if (tmp>0) ( /* Dodeli pomnilnik za strukturo prostora jedra in vanj kopiraj vsebino imenika */ dirp2 = (struct dirent64 *)kmalloc(tmp,GFP_KERNEL) ; copy_from_user(dirp2,dirp,tmp) ; /* Prikličemo drugo strukturo in shranimo vrednost dolžine vnosov v imenik */ dirp3 = dirp2; t = tmp; /* Začnemo iskati našo datoteko */ medtem ko (t >0) ( /* Preberi dolžino prvega vnosa in določi preostalo dolžino vnosov v imeniku */ n = dirp3->d_reclen; t -= n; /* Preverite, ali se ime datoteke iz trenutnega vnosa ujema z tisti, ki ga iščemo */ if (strstr((char *)&(dirp3->d_name), (char *)&hide) != NULL) ( /* Če je tako, potem prepiši vnos in izračunaj novo vrednost za dolžino vnosov v imeniku */ memcpy(dirp3, (char *)dirp3+dirp3->d_reclen, t) ; tmp -=n; ) /* Postavite kazalec na naslednji vnos in nadaljujte z iskanjem */ dirp3 = (struct dirent64 *)((char *)dirp3+dirp3->d_reclen); ) /* Vrne rezultat in prosti pomnilnik */ copy_to_user(dirp,dirp2,tmp); brezplačno (dirp2); ) /* Vrne vrednost dolžine vnosov v imeniku */ vrni tmp; ) /* Funkcije inicializacije in razkladanja modulov imajo standardno obliko */ int init_module(void) ( orig_getdents = sys_call_table; sys_call_table=own_getdents; vrni 0; ) void cleanup_module() ( sys_call_table=orig_getdents; ) kodo, opazite, kako "file1" izgine, kar smo želeli dokazati.

5. Metoda neposrednega dostopa do naslovnega prostora jedra /dev/kmem

Najprej teoretično razmislimo, kako se prestrezanje izvaja z metodo neposrednega dostopa do naslovnega prostora jedra, nato pa nadaljujemo s praktičnim izvajanjem.

Neposreden dostop do naslovnega prostora jedra omogoča datoteka naprave /dev/kmem. Ta datoteka prikazuje ves razpoložljiv virtualni naslovni prostor, vključno z izmenjalno particijo (swap-area). Za delo z datoteko kmem se uporabljajo standardne sistemske funkcije - open(), read(), write(). Če odpremo /dev/kmem na standardni način, se lahko sklicujemo na kateri koli naslov v sistemu, tako da ga nastavimo kot odmik v tej datoteki. To metodo je razvil Silvio Cesare.

Do sistemskih funkcij se dostopa tako, da se parametri funkcije naložijo v procesorske registre in nato pokličejo programsko prekinitev 0x80. Upravljavec za to prekinitev, funkcija system_call, potisne klicne parametre v sklad, pridobi naslov klicane sistemske funkcije iz tabele sys_call_table in prenese nadzor na ta naslov.

S polnim dostopom do naslovnega prostora jedra lahko dobimo celotno vsebino tabele sistemskih klicev, t.j. naslove vseh sistemskih funkcij. S spremembo naslova katerega koli sistemskega klica ga s tem prestrežemo. Toda za to morate poznati naslov tabele ali, z drugimi besedami, odmik v datoteki /dev/kmem, v kateri se ta tabela nahaja.

Če želite določiti naslov tabele sys_call_table, morate najprej izračunati naslov funkcije system_call. Ker je ta funkcija obdelovalec prekinitev, poglejmo, kako se prekinitve obravnavajo v zaščitenem načinu.

V realnem načinu pri registraciji prekinitve procesor dostopa do tabele prekinitvenih vektorjev, ki je vedno na samem začetku pomnilnika in vsebuje dvobesedne naslove upravljavcev prekinitev. V zaščitenem načinu je analog tabele prekinitvenih vektorjev Tabela deskriptorjev prekinitev (IDT), ki se nahaja v operacijskem sistemu zaščitenega načina. Da lahko procesor dostopa do te tabele, mora biti njegov naslov naložen v register prekinitvenih deskriptorjev tabele (IDTR). Tabela IDT vsebuje deskriptorje za obravnavo prekinitev, ki vključujejo zlasti njihove naslove. Ti deskriptorji se imenujejo prehodi (gateways). Procesor, ko je registriral prekinitev, pridobi prehod iz IDT po njegovi številki, določi naslov upravljavca in nanj prenese nadzor.

Za izračun naslova funkcije system_call iz tabele IDT je ​​potrebno izvleči prekinitvena vrata int $0x80 in iz njih naslov ustreznega obdelovalca, t.j. naslov funkcije system_call. V funkciji system_call do tabele system_call_table dostopate z ukazom za klic<адрес_таблицы>(,%eax,4). Ko smo našli opcode (podpis) tega ukaza v datoteki /dev/kmem, bomo našli tudi naslov tabele sistemskih klicev.

Za določitev opcode uporabimo razhroščevalnik in razstavimo funkcijo system_call:

# gdb -q /usr/src/linux/vmlinux (gdb) disas system_call Izpis zbirne kode za funkcijo system_call: 0xc0194cbc : potisnite %eax 0xc0194cbd : cld 0xc0194cbe : potisnite %es 0xc0194cbf : potisnite %ds 0xc0194cc0 : potisnite %eax 0xc0194cc1 : potisnite %ebp 0xc0194cc2 : potisnite %edi 0xc0194cc3 : potisnite %esi 0xc0194cc4 : potisnite %edx 0xc0194cc5 : potisnite %ecx 0xc0194cc6 : potisnite %ebx 0xc0194cc7 : mov $0x18,%edx 0xc0194ccc : mov %edx,%ds 0xc0194cce : mov %edx,%es 0xc0194cd0 : mov $0xffffe000,%ebx 0xc0194cd5 : in %esp,%ebx 0xc0194cd7 : testb $0x2.0x18(%ebx) 0xc0194cdb : jne 0xc0194d3c 0xc0194cdd : cmp $0x10e,%eax 0xc0194ce2 : jae 0xc0194d69 0xc0194ce8 : pokliči *0xc02cbb0c(,%eax,4) 0xc0194cef : mov %eax,0x18(%esp,1) 0xc0194cf3 : nop Konec izpisa asemblerja. Vrstica "call *0xc02cbb0c(,%eax,4)" je klic tabele sys_call_table. Vrednost 0xc02cbb0c je naslov tabele (najverjetneje bodo vaše številke drugačne). Pridobite opcode tega ukaza: (gdb) x/xw system_call+44 0xc0194ce8 : 0x0c8514ff Našli smo ukazno kodo ukaza sys_call_table. Enako je \xff\x14\x85. 4 bajti, ki sledijo, so naslov tabele. To lahko preverite tako, da vnesete ukaz: (gdb) x/xw system_call+44+3 0xc0194ceb : 0xc02cbb0c Tako, če poiščemo zaporedje \xff\x14\x85 v datoteki /dev/kmem in preberemo 4 bajte, ki sledijo njemu, dobimo naslov tabele sistemskih klicev sys_call_table. Če poznamo njen naslov, lahko dobimo vsebino te tabele (naslove vseh sistemskih funkcij) in spremenimo naslov katerega koli sistemskega klica tako, da ga prestrežemo.

Razmislite o psevdokodi, ki izvaja operacijo prestrezanja:

readaddr(old_syscall, scr + SYS_CALL*4, 4); writeaddr(nov_syscall, scr + SYS_CALL*4, 4); Funkcija readaddr prebere naslov sistemskega klica iz tabele sistemskih klicev in ga shrani v spremenljivko old_syscall. Vsak vnos v tabeli sys_call_table traja 4 bajte. Zahtevani naslov se nahaja na odmiku sct + SYS_CALL*4 v datoteki /dev/kmem (tu je sct naslov tabele sys_call_table, SYS_CALL je serijska številka sistemskega klica). Funkcija writeaddr prepiše naslov sistemskega klica SYS_CALL z naslovom funkcije new_syscall in ta funkcija bo servisirala vse klice sistemskega klica SYS_CALL.

Zdi se, da je vse preprosto in cilj je dosežen. Vendar ne pozabimo, da delamo v uporabniškem naslovnem prostoru. Če v ta naslovni prostor postavimo novo sistemsko funkcijo, potem ko pokličemo to funkcijo, bomo dobili lepo sporočilo o napaki. Od tod sklep - nov sistemski klic je treba postaviti v naslovni prostor jedra. Če želite to narediti, morate: pridobiti blok pomnilnika v prostoru jedra, v ta blok postaviti nov sistemski klic.

Pomnilnik lahko dodelite v prostoru jedra s funkcijo kmalloc. Vendar ne morete neposredno poklicati funkcije jedra iz uporabniškega naslovnega prostora, zato uporabljamo naslednji algoritem:

  • če poznamo naslov tabele sys_call_table, dobimo naslov nekega sistemskega klica (na primer sys_mkdir)
  • definiramo funkcijo, ki izvede klic funkcije kmalloc. Ta funkcija vrne kazalec na blok pomnilnika v naslovnem prostoru jedra. Pokličimo to funkcijo get_kmalloc
  • shrani prvih N bajtov sistemskega klica sys_mkdir, kjer je N velikost funkcije get_kmalloc
  • prepiši prvih N bajtov klica sys_mkdir s funkcijo get_kmalloc
  • izvedemo klic sistemskega klica sys_mkdir, s čimer zaženemo funkcijo get_kmalloc za izvedbo
  • obnovite prvih N bajtov sistemskega klica sys_mkdir
Posledično bomo imeli blok pomnilnika, ki se nahaja v prostoru jedra.

Toda za izvedbo tega algoritma potrebujemo naslov funkcije kmalloc. Najdete ga na več načinov. Najpreprosteje je ta naslov prebrati iz datoteke System.map ali ga določiti s pomočjo razhroščevalnika gdb (print &kmalloc). Če ima jedro omogočeno podporo za module, lahko naslov kmalloc določite s funkcijo get_kernel_syms(). O tej možnosti bomo še razpravljali. Če ni podpore za module jedra, bo treba naslov funkcije kmalloc iskati z opkodo ukaza za klic kmalloc - podobno kot za tabelo sys_call_table.

Funkcija kmalloc vzame dva parametra: velikost zahtevanega pomnilnika in specifikator GFP. Da bi našli opcode, bomo uporabili razhroščevalnik in razstavili katero koli funkcijo jedra, ki vsebuje klic funkcije kmalloc.

# gdb -q /usr/src/linux/vmlinux (gdb) disas inter_module_register Izpis zbirne kode za funkcijo inter_module_register: 0xc01a57b4 : potisnite %ebp 0xc01a57b5 : potisnite %edi 0xc01a57b6 : potisnite %esi 0xc01a57b7 : potisnite %ebx 0xc01a57b8 : pod $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 : pokličite 0xc01bea2a ... Ne glede na to, kaj funkcija počne, je glavna stvar v njej tisto, kar potrebujemo - klic funkcije kmalloc. Bodite pozorni na zadnje vrstice. Najprej se parametri naložijo v sklad (register esp kaže na vrh sklada), nato pa sledi klic funkcije. Specifikator GFP se najprej naloži v sklad ($0x1f0,0x4(%esp,1). Za različice jedra 2.4.9 in višje je ta vrednost 0x1f0. Poiščite opcode za ta ukaz: (gdb) x/xw inter_module_register+ 19 0xc01a57c7 : 0x042444c7 Če najdemo to kodo operacije, lahko izračunamo naslov funkcije kmalloc. Na prvi pogled je naslov te funkcije argument za klicno navodilo, vendar to ni povsem res. Za razliko od funkcije system_call tukaj navodilo ni naslov kmalloc, temveč odmik do njega glede na trenutni naslov. To bomo preverili z definiranjem opcode klica ukaza 0xc01bea2a: (gdb) x/xw inter_module_register+34 0xc01a57d6 : 0x01924fe8 Prvi bajt je e8, ki je opcode klicnega navodila. Poiščite vrednost argumenta tega ukaza: (gdb) x/xw inter_module_register+35 0xc01a57d7 : 0x0001924f Zdaj, če dodamo trenutni naslov 0xc01a57d6, odmik 0x0001924f in 5 bajtov ukaza, dobimo zahtevani naslov funkcije kmalloc - 0xc01bea2a.

S tem so teoretični izračuni zaključeni in z uporabo zgornje tehnike bomo prestregli sistemski klic sys_mkdir.

6. Primer prestrezanja z uporabo /dev/kmem

/* vir 6.0 */ #vključi #vključi #vključi #vključi #vključi #vključi #vključi #vključi /* Številka sistemskega klica za prestrezanje */ #define _SYS_MKDIR_ 39 #define KMEM_FILE "/dev/kmem" #define MAX_SYMS 4096 /* opis formata registra IDTR */ struct ( nepodpisana kratka omejitev; nepodpisana osnova int; ) __attribute__ ((pakirano) ) idtr; /* opis formata vrat prekinitve tabele IDT */ struct ( nepodpisani kratki izklop1; nepodpisani kratki sel; nepodpisani char brez, zastavice; nepodpisani kratki izklop2; ) __attribute__ ((pakirano)) idt; /* Opis strukture za funkcijo get_kmalloc */ struct kma_struc ( ulong (*kmalloc) (uint, int); // - naslov funkcije kmalloc int size; // - velikost pomnilnika za dodelitev zastavic int; // - zastava, za jedra > 2.4.9 = 0x1f0 (GFP) ulong mem; ) __attribute__ ((pakirano)) kmalloc; /* Funkcija, ki dodeli samo blok pomnilnika v naslovnem prostoru jedra */ int get_kmalloc(struct kma_struc *k) ( k->mem = k->kmalloc(k->size, k->flags); vrni 0 ; ) /* Funkcija, ki vrne naslov funkcije (potrebna za iskanje kmalloc) */ ulong get_sym(char *n) ( struct kernel_sym tab; int numsyms; int i; numsyms = get_kernel_syms(NULL); if (numsyms > 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 >dump Odpremo datoteko dump in poiščemo podatke, ki nas zanimajo: 080485a4 g F .text 00000032 get_kmalloc 080486b1 g F .text 0000000a new_mkdir Zdaj dodajmo te vrednosti v naš program: ulong get_kmalloc_2; ulong get_kmalloc_addr=0x080485a4 ; ulong new_mkdir_size=0x0a; ulong new_mkdir_addr=0x080486b1; Zdaj pa ponovno prevedemo program. Ko ga zaženemo za izvedbo, bomo prestregli sistemski klic sys_mkdir. Vse klice sys_mkdir bo zdaj obravnavala funkcija new_mkdir.

Konec papirja/EOP

Zmogljivost kode iz vseh razdelkov je bila testirana na jedru 2.4.22. Pri pripravi poročila so bili uporabljeni materiali s strani