Hacknite si Linux

Hneď na začiatku musím priznať, že sa nebudeme nikam nabúravať. Naopak, skúsime oživiť historický význam slova hacker a povŕtať sa v systéme. Niečo pri tom objavíme, niečo pokazíme a možno sa trocha pobavíme. Kto sa chce zúčastniť, nech má zdrojáky jadra poruke.

Inšpirácia

Pri konfigurácii jadra som si všimol nasledovnú voľbu:

.

Jej popis hovorí:

Root Plug Support (SECURITY_ROOTPLUG)

This is a sample LSM module that should only be used as such. It prevents any programs running with egid == 0 if a specific USB device is not present in the system.

See http://www.linuxjournal.com/article.php?sid=6279 for more information about this module.

If you are unsure how to answer this question, answer N.

Teda, že root_plug, je modul, ktorý bol napísaný, ako ukážka demonštrujúca, ako písať svoje vlastné "bezpečnostné moduly". Kto chce viac detailov o histórii tohoto modulu, nech sa pozrie na ten článok v Linux Journal-e.

Linux Security modules

Na čo sú dobré bezpečnostné moduly? Linuxové jadro používa linuxové bezpečnostné moduly LSM, teda sady pointrov na funkcie, ktoré rozhodujú o tom, či je nejaká akcia povolená, alebo nie. Tieto pointre sú združené v štruktúre security_operations definovanej v súbore include/linux/security.h. Tam sa v komentároch dočítame, že tieto funkcie rozhodujú o tom, či je dovolené vytvoriť súbor, primountovať súborový systém, urobiť chroot() a podobne.

Každý security modul môže naplniť takúto štruktúru pointrami na svoje funkcie. Jadro potom pri operáciach, ktoré sú citlivé z bezpečnostného hľadiska, prejde túto štruktúru z každého bezpečnostného modulu a zavolá funkciu prislúchajúcu danej situácii. Teda napríklad ak jadro dostane požiadavku vytvoriť nový adresár, zavolá sa funkcia (*inode_mkdir)(). Ak funkcia vráti nulu, volanie sa podarí. Ak napr. vráti kód EPERM, volanie skončí s "permission denied" a žiaden nový adresár sa konať nebude.

Root plug je ukážkový modul, ktorý definuje funkciu rootplug_bprm_check_security() a priradzuje pointer na ňu do premennej bprm_check_security dátovej štruktúry security_operations. Táto funkcia sa zavolá, keď systém dostane požiadavku na vytvorenie nového procesu. Implementovaná je tak, že zavolá funkciu usb_find_device(int vendor_id, int product_id).

static int rootplug_bprm_check_security (struct linux_binprm *bprm)
{
        struct usb_device *dev;
        ...
        if (bprm->e_gid == 0) {
                dev = usb_find_device(vendor_id, product_id);
                if (!dev) {
                        ...
                        return -EPERM;
                }
                usb_put_dev(dev);
        }

        return 0;
}

Tá zistí či sa na USB zbernici nachádza zariadenie s daným ID. Ak áno, akcia je dovolená, ak nie vráti sa chyba o nedostatočnych prístupových právach.

Vnútri jadrového modulu

Premenné vendor_id a product_id sú naplnené pomocou makier:

module_param(vendor_id, uint, 0400);
MODULE_PARM_DESC(vendor_id, "USB Vendor ID of device to look for");

V prvom riadku je makro, ktoré definuje premennú vendor_id, typu uint s defaultnou hodnotu 0400. V druhom riadku je slovný popis tohoto parametra. Tieto informácie spolu s ďalšími makrami:

MODULE_DESCRIPTION("Root Plug sample LSM module, written for Linux Journal article");
MODULE_LICENSE("GPL");
používa program modinfo z balíka module-init-tools:
$ /sbin/modinfo root_plug
filename:       /lib/modules/2.6.20/kernel/security/root_plug.ko
description:    Root Plug sample LSM module, written for Linux Journal article
license:        GPL
vermagic:       2.6.20 preempt mod_unload K8
depends:        commoncap
srcversion:     564B87C638B0C8397B26F50
parm:           debug:Debug enabled or not (bool)
parm:           product_id:USB Product ID of device to look for (uint)
parm:           vendor_id:USB Vendor ID of device to look for (uint)

Root Plug akcii

LSM Root Plug sa zavolá len pri požiadavke na vytvorenie nového procesu. To znamená, že procesov, ktoré boli spustené ešťe pred zavedením modulu, sa kontrola netýka. Náš shell beží ďalej, démony bežia ďalej. Ale ak démon potrebuje spustiť nový process (čo bežne robí napr. inetd), tak bez pripojeného správneho USB zariadenia sa mu to nepodarí.

ID zariadenia (v hexa) na USB zbernici prezradí /sbin/lsusb:

$ lsusb
Bus 1 Device 3: ID 0718:0081 Imation Corp.
Bus 1 Device 1: ID 0000:0000
...

Ak teda chceme zaviesť root plug s tým, že bude kontrolovať prítomnosť môjho USB kľúča, urobíme to takto:

# modprobe root_plug vendor_id=0x0718 product_id=0x0081

Pozor na prefix 0x ak ho nepoužijete, tak modul naparsuje parametre inak ako s predstavujeme. V mojom prípade ID čísla začínajú nulou a tak ich kód bude interpretovať ako oktálové hodnoty. ID začínajúce číslicou, budú interpretované dekadicky. Tak či onak, keď dôjde na lámanie chleba tak sa nebudú zhodovať s identifikátorom, ktorý sa prečíta z kľúča. A budeme v pr... ;-) Odstrániť modul príkazom rmmod nebude možné, lebo by to bol nový proces bežiaci pod účtom root-a. Neurobíme ani slušný shutdown.

Ak sa USB zariadenie nájde, tak ako root môžeme napr. spraviť:

# ls /etc/passwd
/etc/passwd

Bez kľúčika ako root

# ls /etc/passwd
-su: /bin/ls: Operation not permitted

Ale bez kľúčika ako obyčajný užívateľ:

$ ls /etc/passwd
/etc/passwd

LSM Capability

Ak ste to skúšali so mnou, pravdepodobne ste narazili na problém:

# modprobe root_plug vendor_id=0718 product_id=0081
FATAL: Error inserting root_plug
(/lib/modules/2.6.20/kernel/security/root_plug.ko): Invalid argument

Vo svete LSM totiž root plug nie je sám. Security modulov môže byť v systéme nahraných niekoľko. Pokiaľ nie je nahraný žiaden, tak sa pužijú funkcie definované v súbore security/dummy.c. Na konci toho súboru sú pointre na tieto funkcie popriradzované do štruktúry security_operations:

void security_fixup_ops (struct security_operations *ops)
{
        set_to_dummy_if_null(ops, ptrace);
        set_to_dummy_if_null(ops, capget);
...

Jednotlivé bezpečnostné moduly sa môžu reťaziť. Modul pri svojom zavedení môže predefinovať niektoré z funkcií na seba. Funkcia mod_reg_security() v súbore security/security.c sa postará o to, že funkcie, ktoré nový LSM nepredefinoval sa zoberú z dummy.c (funkcia verify()). Predefinované funkcie môžu buď definitívne rozhodnúť o povolení operácie, alebo môžu rozhodnutie delegovať na nadradený LSM. Alebo môžu urobiť oboje - ak prvý LSM v poradí operáciu zamietne, tak je zamietnutá, ale v opačnom prípade môže rozhodnutie prenechať na ďalší modul v poradí.

Zádrhel je v tom, že za normálnych okolností je v jadre zapnutý LSM capability. To je ten, ktorý má na screenshot-e popis "Default Linux Capabilities". Na to aby sme sa mohli hrať so svojím root plug, musíme niečo s capability spraviť, pretože nepredefinováva funkciu, ktorá by umožnila nad neho nahrať ďalší LSM. Buď ho odstránime (za predpokladu, že ho máme skompilovaný ako modul)

# rmmod capability
# modprobe root_plug vendor_id=0718 product_id=0081
# 

, alebo ho upravíme tak, aby dovoľoval nahratie ďalšieho modulu nad seba. Ani jedna z týchto možností nie je ideálna. Ak urobíme chybu v úpravách capability, môžeme si vyrobiť slušnú bezpečnostnú dieru do systému. Ak capability nebudeme mať vôbec, má to tiež nepríjemné následky. Jeden z prvých, ktorý som si všimol je, že sa pri boote nespustí bind DNS server. Ten totiž pradvepodobne volanie capget() (viď manuálové stránky capget(2) a capabilities(7)) Ako som zistil, môj Slackware do dokonca ošetruje v bootovacích skriptoch a ak sa bind nepodarí spustiť pokúsi sa modul capability nahrať a spustiť bind znova.

Úpravy

Rozhodol som sa preto pre tretiu variantu. Nahrám root plug a na neho nahrám capability. Drobný problém je v tom, že root plug to v originále takisto nepodporuje a budeme ho musieť mierne upraviť. Konkrétne musíme dodať funkcie (*register_security)() a (*unregister_security)(). Tie dovolia nahratie nadradeného LSM.

static int rootplug_register_security(const char *name, struct security_operations *ops_to_reg)
{
       int retval=0;
       if (next_ops_in_chain==NULL)
       { /* we are the only LSM in chain */

               printk (KERN_INFO "RootPlug: registered next in chain: %s \n", name);
               next_ops_in_chain=ops_to_reg;
               return 0; /* RootPlug: return "OK, the next LSM is allowed to register" */
       }
       else
       {/*there is another LSM after us, decision is up to next LSM in chain*/ 

        /*Note: Not sure if this is really needed, I have no 3rd LSM to test
          with. If next LSM is "capability",
          than next_ops_in_chain->bprm_check_security is
          dummy_register_security - and that returns EINVAL */

               retval=next_ops_in_chain->register_security(name,ops_to_reg);
               printk (KERN_INFO "RootPlug: next_ops_in_chain "
                       "register_security() returned %d \n", retval);
               return retval;
       }
}

static int rootplug_unregister_security(const char *name, struct security_operations *ops_to_unreg)
{
       if (next_ops_in_chain==ops_to_unreg)
               next_ops_in_chain=NULL;
       printk(KERN_INFO "RootPlug: unregistering %s\n",name);

       return 0; /* allow */
 }

Aby sa tieto funkcie skutočne použili, treba pointrami na ne nainicializovať príslušné položky štruktúry security_operations:

static struct security_operations rootplug_security_ops = {
        ...
        .register_security =            rootplug_register_security,
        .unregister_security =          rootplug_unregister_security,
};

Po prekompilovaní a nainštalovaní modulu môžeme odstrániť capability, nainštalovať root plug a vrátiť capability naspäť:

# rmmod capability
# modprobe root_plug vendor_id=0718 product_id=0081
# ls
-su: /bin/ls: Operation not permitted
# modprobe capability
#

No a to je celé. Na záver ešte pár poznámok: