preche?

In questo post, si è discusso di un tool che fa largo uso del preloading di librerie condivise; quindi, su richiesta del buon Roberto, cerco di dare una breve panoramica dell’argomento:

LD_PRELOAD (così come /etc/ld.so.preload, che però, essendo modificabile solo da root, non ha gli stessi limiti di LD_PRELOAD per i binari setuid/setgid) permette di istruire il loader a caricare delle librerie condivise, prima di tutte le altre (cioè prima di quelle nei path specificati in ld.so.conf, utilizzati da ldconfig per creare l’ld.so.cache che verrà usato dal loader).

L’aspetto interessante del poter forzare il caricamento di una o più librerie prima di tutte le altre, è la possibilità di forzare i programmi a far uso di un’altra implementazione di una data funzione, offerta dalle librerie standard; oppure il poter creare degli hook a funzioni delle suddette librerie standard.

Ma bando alle ciance e, come dice un famoso troll, “Talk is cheap. Show me the code”, quindi ho scritto una mini-libreria di esempio, che mostra come si possano fare degli hook a funzioni della libreria standard (per rimanere in tema di execv*() e setuid(), visto che eravamo partiti da questo nel post su valgrind :) )

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>

#ifndef RTLD_NEXT
#       define RTLD_NEXT        ((void *) -1l)
#endif

static int checksetuid(const char *filename)
{
        struct stat lalala;
        stat(filename,&lalala);
        if(lalala.st_mode & S_ISUID)
                return 1;
        return 0;
}

int execv(const char *filename, char *const argv[])
{
        int (*real_execv)(const char *filename, char *const argv[]) = dlsym(RTLD_NEXT,"execv");
        if(checksetuid(filename)) {
                errno = EACCES;
                return -1;
        }
        return (*real_execv)(filename,argv);
}

int execve(const char *filename, char *const argv[], char *const envp[])
{
        int (*real_execve)(const char *filename, char *const argv[], char *const envp[]) = dlsym(RTLD_NEXT,"execve");
        if(checksetuid(filename)) {
                errno = EACCES;
                return -1;
        }
        return (*real_execve)(filename,argv,envp);
}

mini:/tmp/exechook# gcc -shared -fomit-frame-pointer execve.c -oexecve.so -ldl
mini:/tmp/exechook# export LD_PRELOAD=/tmp/exechook/execve.so
mini:/tmp/exechook# bash
mini:/tmp/exechook# ls
execve.c execve.so
mini:/tmp/exechook# mount
bash: /bin/mount: Permission denied
mini:/tmp/exechook# exit
mini:/tmp/exechook# mount
/dev/md0 on / type ext3 (rw,noatime,nodiratime,user_xattr,errors=remount-ro)
tmpfs on /lib/init/rw type tmpfs (rw,nosuid,mode=0755)
proc on /proc type proc (rw,noexec,nosuid,nodev)
sysfs on /sys type sysfs (rw,noexec,nosuid,nodev)
udev on /dev type tmpfs (rw,mode=0755)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)
devpts on /dev/pts type devpts (rw,noexec,nosuid,gid=5,mode=620)
fusectl on /sys/fs/fuse/connections type fusectl (rw)
/dev/mapper/md1_crypt on /home type ext3 (rw,noatime,nodiratime,user_xattr)
rpc_pipefs on /var/lib/nfs/rpc_pipefs type rpc_pipefs (rw)
nfsd on /proc/fs/nfsd type nfsd (rw)
mini:/tmp/exechook#

Quindi se il binario non è setuid (/bin/ls) tutto ok, altrimenti (/bin/mount è setuid) EACCES.

Il codice spero sia abbastanza chiaro, la libreria esporta le due funzioni execve() ed execv(), le suddette controllano che il file da eseguire non sia setuid; se lo è, tornano -1 e settano l’errore a EACCES, altrimenti chiamano la execv/execve della libc (usando un puntatore a funzione che si ricavano con la dlsym()) e tornano il valore tornato da quest’ultima.

I due hook possono funzionare solo grazie al fatto che la libreria viene caricata in preload, quindi quando un programma chiama le due funzioni, vengono utilizzate quelle della libreria in preload anzichè quelle della libc.

Il maggiore pregio di questa tecnica è il poter fare hook o sostituire qualunque funzione di qualunque libreria in maniera semplice (con la ptrace() esci pazzo) e rimanendo in userland (quindi codice portabile, niente ricompilazione del kernel con relativo reboot e niente sbattimenti a cercare la syscall_table nelle fratte in cui la spostano ogni volta che Torvalds si sveglia storto).

Il maggiore svantaggio è che basta utilizzare binari linkati staticamente per rendere completamente inutile la libreria in preload.

per approfondire (in ordine crescente di approfondimento :)

man ld.so; man dlsym; man ldconfig
http://tldp.org/HOWTO/Program-Library-HOWTO/index.html
http://sco.com/developers/gabi/latest/contents.html
http://www.trunix.org/programlama/os/cpu-elf/node1.html

Sempre a proposito di gestione della banda..

Leggendo il post sulla gestione della banda con apt, mi è venuto in mente che, con applicazioni che non supportano nativamente la suddetta funzionalità (o magari che, come apt, non supportano la gestione da riga di comando, ma solo come parametro globale; il che, quando si vuole limitare la banda in un singolo caso e non sempre, non è molto comodo), a qualcuno potrebbe tornare utile trickle (su debian apt-get install trickle), un piccolo software che permette la gestione della banda in user land (utilizza una shared library caricata in preload, con delle funzioni che fanno da wrapper a quelle normalmente utilizzate per la gestione dei socket; quindi ovviamente non funziona con applicazioni linkate staticamente); per rimanere sempre in tema di apt-get (ma, come detto, trickle funziona con qualsiasi applicazione faccia uso delle funzioni standard per la gestione dei socket e non sia linkata staticamente), segue un esempio d’uso di trickle per limitare la banda in download (volendo è possibile limitarla anche in upload con lo switch -u) a 100KB/s:


trickle -d 100 apt-get install openoffice.org

linux file capabilities

Stavo dando un’occhiata alle nuove (dal kernel 2.6.24) file capabilities di linux (molto comode in fase di hardening per eliminare il setuid root da alcuni binari tipo su,passwd,traceroute,ecc.. o per permettere ad alcuni demoni di girare con uid diverso da 0) quando, in casi non banali tipo traceroute (basta la CAP_NET_RAW) o di un qualsiasi demone cui serva uid 0 esclusivamente per fare bind su porta privilegiata (basta la CAP_NET_BIND_SERVICE), mi son reso conto che non è molto comodo cercare di ricavarsi quali siano le capabilities necessarie a un dato binario (affinche’ la sua istanza possa girare con uid diverso da 0) a colpi di strace/ltrace; quindi ho scritto un piccolo LKM con una jprobe nella cap_capable() che permette di ricavarsi quali sono le capabilities di cui fa uso un determinato binario; nel caso possa tornare utile a qualcuno, riporto di seguito i link al sorgente e al relativo Makefile (ovviamente vi serve un kernel con CONFIG_KPROBES abilitato :) )

trace_cap_capable.c
Makefile

P.S. se dovete testare le capabilities richieste da tanti binari, non vi conviene fare ogni volta rmmod e modprobe del LKM, cambiando di volta in volta il paramentro ‘progname’, è possibile modificare il parametro mentre il modulo è caricato, scrivendo direttamente in /sys in questo modo:

echo -e “nome_binario\c” > /sys/module/trace_cap_capable/parameters/progname