============================================================================== ------------[ BFi numero 9, anno 3 - 03/11/2000 - file 10 di 21 ]------------- ============================================================================== -[ HACKiNG ]------------------------------------------------------------------ ---[ PEEK DELLE STRUTTURE iNTERNE DEL KERNEL ViA /DEV/KMEM -----[ pIGpEN , FuSyS Della serie: ---[ s c e g l i e r e l a v i a m e n o b a t t u t a ]--- by pIGpEN & FuSyS ----------------- Questo progetto vede la luce partendo dalla considerazione nei confronti di rootkit e troyan vari. E' spesso difficile avere fiducia nei propri binari, dopo aver subito una intrusione. Questa difficolta' si fa insormontabile nel momento in cui venga modificato il kernel mediante ricompilazione statica o mediante il link di codice attravero i moduli caricabili nel kernel stesso. L'obbiettivo e' fornire un tool sufficientemente robusto e flessibile che possa gettare almeno un po' di luce sull'ombra degli attaccanti. Bypassando i normali mezzi di raccolta delle informazioni, e osservando direttamente lo stato del kernel attraverso il file /dev/kmem , possiamo accorgerci di quello che non dovremmo [ovviamente secondo il punto di vista del nostro attaccante ;P] ----[ BSD ----[ KSec - Kernel Security Checker (ex. FreeBSD, OpenBSD) ---[ SYSTEM CALL KSec e' in grado di trovare da user space se alcune syscall sono state modificate (via lkm, kld o altro). Questa e' la maniera meno invasiva e altrettanto sicura... Checking for Altered Syscall: exit syscall points to 0xc0137264, function is at 0xc0137264 : ok fork syscall points to 0xc0137cdc, function is at 0xc0137cdc : ok read syscall points to 0xc014ada4, function is at 0xc014ada4 : ok write syscall points to 0xc014b144, function is at 0xc014b144 : ok open syscall points to 0xc0169cdc, function is at 0xc0169cdc : ok close syscall points to 0xc013553c, function is at 0xc013553c : ok link syscall points to 0xc016a430, function is at 0xc016a430 : ok unlink syscall points to 0xc016a8d0, function is at 0xc016a8d0 : ok chdir syscall points to 0xc0169acc, function is at 0xc0169acc : ok fchdir syscall points to 0xc016998c, function is at 0xc016998c : ok mknod syscall points to 0xc0169fd8, function is at 0xc0169fd8 : ok chmod syscall points to 0xc016b578, function is at 0xc016b578 : ok chown syscall points to 0xc016b774, function is at 0xc016b774 : ok getpid syscall points to 0xc013c0e4, function is at 0xc013c0e4 : ok mount syscall points to 0xc0168c70, function is at 0xc0168c70 : ok unmount syscall points to 0xc01693a4, function is at 0xc01693a4 : ok setuid syscall points to 0xc013c3b8, function is at 0xc013c3b8 : ok getuid syscall points to 0xc013c1b4, function is at 0xc013c1b4 : ok geteuid syscall points to 0xc013c1d8, function is at 0xc013c1d8 : ok ioctl syscall points to 0xc014b508, function is at 0xc014b508 : ok fcntl syscall points to 0xc0134f10, function is at 0xc0134f10 : ok setsockopt syscall points to 0xc015ca70, function is at 0xc015ca70 : ok getsockopt syscall points to 0xc015caf4, function is at 0xc015caf4 : ok setsid syscall points to 0xc013c294, function is at 0xc013c294 : ok setegid syscall points to 0xc013c508, function is at 0xc013c508 : ok seteuid syscall points to 0xc013c444, function is at 0xc013c444 : ok fstat syscall points to 0xc013565c, function is at 0xc013565c : ok getdirentries syscall points to 0xc016c8dc, function is at 0xc016c8dc : ok getdents syscall points to 0xc016cad0, function is at 0xc016cad0 : ok modstat syscall points to 0xc013042c, function is at 0xc013042c : ok modfind syscall points to 0xc013051c, function is at 0xc013051c : ok kldload syscall points to 0xc0130ecc, function is at 0xc0130ecc : ok kldunload syscall points to 0xc0130fa4, function is at 0xc0130fa4 : ok kldfind syscall points to 0xc0131014, function is at 0xc0131014 : ok kldnext syscall points to 0xc01310a0, function is at 0xc01310a0 : ok kldstat syscall points to 0xc0131118, function is at 0xc0131118 : ok kldfirstmod syscall points to 0xc0131220, function is at 0xc0131220 : ok getsid syscall points to 0xc013c174, function is at 0xc013c174 : ok ---[ FreeBSD LiNKER FiLES & MODULES In FreeBSD ogni link files puo' contenere piu' moduli. Mentre e' possibile vedere i link files, non esiste un vero comando per vedere i moduli interni. Questo vuol dire che molti freebsd attackers non badano molto al nome di questi ma pensano piu' che altro a nascondere come possono i link files. Kernel linker files name: kernel, id: 1, addr: 0xc0100000, refs: 3, size: 2272c0 flags: 1, userrefs: 1, dependancies: 0, file_ops at 0xc0299c5c Modules in this file: Id=1 (rootbus) mod_handler at 0xc01467a0 refs=1 Id=2 (isa/vga) mod_handler at 0xc0146838 refs=1 Id=3 (isa/sc) mod_handler at 0xc0146838 refs=1 Id=4 (isa/sio) mod_handler at 0xc0146838 refs=1 Id=5 (atkbdc/psm) mod_handler at 0xc0146838 refs=1 Id=6 (isa/ppc) mod_handler at 0xc0146838 refs=1 Id=7 (isa/atkbdc) mod_handler at 0xc0146838 refs=1 Id=8 (atkbdc/atkbd) mod_handler at 0xc0146838 refs=1 Id=9 (nexus/pcib) mod_handler at 0xc0146838 refs=1 Id=10 (nexus/npx) mod_handler at 0xc0146838 refs=1 Id=11 (fdc/fd) mod_handler at 0xc0146838 refs=1 Id=12 (isa/fdc) mod_handler at 0xc0146838 refs=1 Id=13 (root/nexus) mod_handler at 0xc0146838 refs=1 Id=14 (nexus/apm) mod_handler at 0xc0146838 refs=1 Id=15 (scrndr-vga) mod_handler at 0xc023c388 refs=1 Id=16 (scterm-sc) mod_handler at 0xc023aebc refs=1 Id=17 (nexus/eisa) mod_handler at 0xc0146838 refs=1 Id=18 (isab/eisa) mod_handler at 0xc0146838 refs=1 Id=19 (eisa/mainboard) mod_handler at 0xc0146838 refs=1 Id=20 (isa/ed) mod_handler at 0xc0146838 refs=1 Id=21 (atapci/ata) mod_handler at 0xc0146838 refs=1 Id=22 (pci/atapci) mod_handler at 0xc0146838 refs=1 Id=23 (isa/ata) mod_handler at 0xc0146838 refs=1 Id=24 (isa/pnp) mod_handler at 0xc0146838 refs=1 Id=25 (isa/isahint) mod_handler at 0xc0146838 refs=1 Id=26 (isa/unknown) mod_handler at 0xc0146838 refs=1 Id=27 (nexus/isa) mod_handler at 0xc0146838 refs=1 Id=28 (isab/isa) mod_handler at 0xc0146838 refs=1 Id=29 (pci/ign) mod_handler at 0xc0146838 refs=1 Id=30 (pci/chip) mod_handler at 0xc0146838 refs=1 Id=31 (pci/isab) mod_handler at 0xc0146838 refs=1 Id=32 (pci/pcib) mod_handler at 0xc0146838 refs=1 Id=33 (pcib/pci) mod_handler at 0xc0146838 refs=1 Id=34 (ppbus/ppi) mod_handler at 0xc0146838 refs=1 Id=35 (ppc/ppbus) mod_handler at 0xc0146838 refs=1 Id=36 (ppbus/lpt) mod_handler at 0xc0146838 refs=1 Id=37 (ppbus/plip) mod_handler at 0xc0146838 refs=1 Id=38 (pci/ed) mod_handler at 0xc0146838 refs=1 Id=39 (fpu) mod_handler at 0xc024d7f8 refs=1 Id=40 (mfs) mod_handler at 0xc01653e0 refs=1 Id=41 (ufs) mod_handler at 0xc01653e0 refs=1 Id=42 (nfs) mod_handler at 0xc01653e0 refs=1 Id=43 (msdos) mod_handler at 0xc01653e0 refs=1 Id=44 (procfs) mod_handler at 0xc01653e0 refs=1 Id=45 (cd9660) mod_handler at 0xc01653e0 refs=1 Id=46 (ipfw) mod_handler at 0xc0190fa0 refs=1 Id=47 (dummynet) mod_handler at 0xc018e530 refs=1 Id=48 (if_tun) mod_handler at 0xc0182648 refs=1 Id=49 (if_sl) mod_handler at 0xc0181328 refs=1 Id=50 (if_ppp) mod_handler at 0xc017f54c refs=1 Id=51 (if_loop) mod_handler at 0xc017eae0 refs=1 Id=52 (if_gif) mod_handler at 0xc017e4fc refs=1 Id=53 (if_faith) mod_handler at 0xc017e344 refs=1 Id=54 (shell) mod_handler at 0xc012f63c refs=1 Id=55 (elf) mod_handler at 0xc012f4f8 refs=1 Id=56 (aout) mod_handler at 0xc012e20c refs=1 Id=57 (ipfilter) mod_handler at 0xc01a56a8 refs=1 name: linux.ko, id: 2, addr: 0xc08ba000, refs: 1, size: 10000 flags: 1, userrefs: 1, dependancies: 1, file_ops at 0xc0299c5c Modules in this file: Id=58 (linuxelf) mod_handler at 0xc08c6d8c refs=1 Id=59 (linuxaout) mod_handler at 0xc08c74e8 refs=1 name: logo_saver.ko, id: 3, addr: 0xc08db000, refs: 1, size: 4000 flags: 1, userrefs: 1, dependancies: 1, file_ops at 0xc0299c5c Modules in this file: Id=60 (logo_saver) mod_handler at 0xc08db920 refs=1 ---[ PROMiSC DETECTiON Ksec si basa su tre livelli per trovare un'interfaccia in modalita' promiscua da locale garantisce di scovare i dumper, di sapere se questi sono in modalita' promiscua, quali processi hanno aperto bpf e molto altro... 1) controllo dei descrittori di bpf all'interno della struttura ifnet (con questa opzione troviamo le interfacce in modalita' promiscua in modo sicuro, infatti il descrittore non puo' essere rimosso e ne deriva che siamo sempre in grado di trovare un dumper) Il problema e': "Questo dumper e' in promisc mode?" Lo si puo' sapere da piu' elementi: il piu' banale (e meno affidabile) e' l'IFF_PROMISC nelle flag della struttura ifnet (if_flags). Questo potrebbe pero' essere coperto in piu' modi, ecco perche' ksec lo confronta con la flag bd_promisc della struttura bpf_d). E' stato dimostrato in BFi 8 che coprendo pure questo siamo in grado di dare un'oscurita' del promisc mode maggiore anche se temporanea :O Ksec inoltre e' in grado di distingure se un dumper in modalita' non promiscua riceve cmq pacchetti non generati/destinati a quell'host come effetto di un altro dumper in modalita' promiscua. Questo e' un "bug" ben conosciuto del supporto bpf; in realta' non si puo' parlare di baco quanto di una scelta nell'implementazione: infatti si preferisce far ricevere i pacchetti promiscui a tutti i descrittori che possono pero' filtrarli se l'utente ha settato una struttura bpf_program, passandola al supporto kernel attraverso una ioctl(BIOCSETF) vedi bpf(4). Nel kernel questo e' visibile dalla bpf_mtap(): for (d = bp->bif_dlist; d != 0; d = d->bd_next) { ++d->bd_rcount; slen = bpf_filter(d->bd_filter, pkt, pktlen, pktlen); /* * se non c'e' un filtro questa funzione ritorna -1 * e cosi' prende il pacchetto */ if (slen != 0) catchpacket(d, pkt, pktlen, slen, bcopy); } Esempio: $ ksec -i ed0 Interface: ed0 (internal index = 2) Ether 00:4f:4c:05:3a:31 State: up bcast running promisc simplex multicast Addr: 192.168.1.2 NetMask: 0xffffff00 Broadcast: 192.168.1.255 Expected 1 promisc listener/s Found a nopromisc listener: (0 rcvd pkt, 0 drop pkt) on #1 listener Found a promisc listener: (12 rcvd pkt, 0 drop pkt) on #2 listener Warning 1 nopromisc dumpers probably receive promisc packets as effect of another file requesting this mode... Information for this Ethernet interface: (address length = 6, header length = 14) MTU: 1500 Linespeed: 10000000 87 packets received, 0 packets sent 0 input errors, 89 output errors on interface 1424 collisions on interface 15331 octets received, 16755 octets sent 0 packets received, 2 packets sent via multicast 0 dropped on input 0 destined for unsupported protocols 2) controllo dei file aperti dai processi cercando quelli con major device = a quello di bpf (FreeBSD) In pratica si scorre la tabella dei processi guardando per ognuno i file aperti da questi si ricava i vnode considerando solamente quelli che interessano bpf (se ci sono). In FreeBSD questo avviene leggendo dai vnode la struttura specinfo e poi checkando che questa abbia il major device = a quello di bpf (23) ovvero: if((spec.si_udev >> 8) & 0xff)==23) // bpf! oppure: if(strstr(spec.si_name, "bpf")) // oppure strncmp() chiaro :) // bpf! Il risultato e' il seguente: checking for proc with bpf descriptor opened (use -i all to detect interface) Process with id 545 (tcpdump) has opened bpf1 Process with id 447 (trafd) has opened bpf0 3) controllo della character device switch di bpf (FreeBSD) Quest'ultimo livello controlla se la cdevsw e' stata modificata e quindi se i puntatori interni alla cdevsw puntano alle funzioni corrette o meno. Checking BPF cdevsw: cdevsw open function points to: 0xc017b11c bpfopen() is at: 0xc017b11c ok cdevsw close function points to: 0xc017b1b0 bpfclose() is at: 0xc017b1b0 ok cdevsw read function points to: 0xc017b208 bpfread() is at: 0xc017b208 ok cdevsw write function points to: 0xc017b304 bpfwrite() is at: 0xc017b304 ok cdevsw ioctl function points to: 0xc017b3dc bpfioctl() is at: 0xc017b3dc ok cdevsw poll function points to: 0xc017b988 bpfpoll() is at: 0xc017b988 ok ---[ PR0T0C0L MODiFiCATION Come avevo dimostrato e come fanno anche alcuni supporti, la inetsw[] e' facilmente modificabile via kld e lkm ... Questo vuol dire che le funzioni di un protocollo possono essere cambiate in mille modi per bypassare fw, non loggare particolari pacchetti, accettare condizioni particolari in maniera privilegiata (es. introduzione di nuovi icmp code che scrivono su file o ricevono comandi) e tante altre cose spiacevoli. Tutto questo pero' puo' essere facilmente scovato nella stessa maniera che avevo utilizzato per le syscall e per la cdevsw di bpf. L'unico problema dovuto al tempo e' che la pr_usrreqs (in FreeBSD) non l'ho ancora implementata e quindi i puntatori interni alla struttura non sono letti e checkati. Checking integrity for inetsw: IP: inet domain at: 0xc02a19c0 ipproto domain points to: 0xc02a19c0 ok ip_init at: 0xc0191b64 ipproto init points to: 0xc0191b64 ok ip_slowtimo at: 0xc0192840 ipproto slowtimo points to: 0xc0192840 ok ip_drain at: 0xc01928b8 ipproto drain points to: 0xc01928b8 ok UDP: pr_input: ok (addr: 0xc019c378) pr_ctlinput: ok (addr: 0xc019c9b8) pr_ctloutput: ok (addr: 0xc0194390) pr_usrreq: ok (addr: 0xc02a3600) TCP: pr_input: ok (addr: 0xc0195fa0) pr_ctlinput: ok (addr: 0xc019a7d0) pr_ctloutput: ok (addr: 0xc019bf50) pr_usrreq: ok (addr: 0xc02a3280) RAW: pr_input: ok (addr: 0xc0195140) pr_ctlinput: ok (addr: 0xc01956a4) pr_ctloutput: ok (addr: 0xc0195540) pr_usrreq: ok (addr: 0xc02a2b00) ICMP: pr_input: ok (addr: 0xc0191258) pr_ctlinput: / pr_ctloutput: ok (addr: 0xc0195540) pr_usrreq: ok (addr: 0xc02a2b00) IGMP: pr_input: ok (addr: 0xc018a5f4) pr_ctlinput: / pr_ctloutput: ok (addr: 0xc0195540) pr_usrreq: ok (addr: 0xc02a2b00) IP Packet Log puo' modificare la protosw del protocollo IP, ksec dovrebbe essere in grado di dire se le funzioni puntano al codice di iplog o no. Es. nel mio OpenBSD 2.7 di casa ip_slowtimo at: 0xe01864ac ipproto init points to: 0xe0192b04 altered it points to iplinit (IP Packet log Support) ---[ E i PROCESSI?!? In BSD se avessi lasciato questo avrei fatto la stessa cosa che fa il comando ps(1)... Nel senso che esso utilizza gia' la kvm per leggere i processi, in particolare la chiamata kvm_getprocs(3) per cui ho tolto questa parte che secondo me era ridondante... Puo' darsi che in futuro la implementi in altri modi... Comunque la tabella dei processi e' stata utilizzata all'interno di ksec per altre cose... per esempio in FreeBSD per controllare quali processi avevano aperto bpf ---[ CONCLUSIONE Per quanto i simboli possano essere sovrascritti, l'hacker possa ricompilare il kernel o si sfruttino altri modi per eludere ksec, questo e' comunque un tool utile per controllare il proprio sistema e che fornisce buoni spunti a chi voglia addentrarsi dentro il kernel. Molti metodi di copertura di un programma che legga i valori direttamente del kernel, non sono proprio banali e richiedono una conoscenza del kernel in cui si agisce, delle sue strutture e delle sue funzioni ... Un metodo per FreeBSD di copertura dei simboli si ottiene guardando l'implementazione della libreria kvm, questa nella ricerca di essi sfrutta il supporto kld chiamando la kldsym()... e' quindi possibile modificare questa per far ritornare in determinati simboli i nostri indirizzi, ma ci sono pure altri modi ... <-| kmem/bsd/kldsym.c crc32: 3523658060 |-> /* * Questo semplice kld elude la libreria kvm in FreeBSD, facendo credere che * il simbolo kldsym sia locato nella stessa posizione di hacked_kldsym, * ksec (basandosi su kvm) non potra' localizzare cosi' la differenza tra dove * punta la syscall kldsym e la vera locazione in cui questa risiede... * * Questo comportamento e' riproducibile su ogni simbolo, ovviamente occorre * sempre coprire la kldsym visto che noi andiamo a modificare almeno questa * chiamata di sistema :) * * Un comportamento simile si potrebbe ottenere cambiando la lookup_symbol() * nelle strutture linker_file_ops * * Nota: nm(1) non viene eluso da questo kld, in quanto gestisce da solo il * posizionamento all'interno di un file nella ricerca dei simboli, non * sfruttando quindi la chiamata di sistema kldsym() * * E' altrettanto chiaro che questa implementazione vale solo per FreeBSD * mentre su altri sistemi la kvm e' differente * * pIGpEN [ S0ftPj Y2K ] */ #include #include #include #include #include #include #include #include #include #include static int hacked_kldsym __P((struct proc *, struct kldsym_args *)); extern linker_file_list_t linker_files; /* this has a sym */ static int module_handler(module_t mod, int cmd, void *arg) { switch(cmd) { case MOD_LOAD: sysent[SYS_kldsym].sy_call = (sy_call_t *)hacked_kldsym; break; case MOD_UNLOAD: sysent[SYS_kldsym].sy_call = (sy_call_t *)kldsym; break; } return 0; } static moduledata_t KldSym = { "kldsym", module_handler, NULL }; DECLARE_MODULE(kldsym, KldSym, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); static int hacked_kldsym(struct proc *p, struct kldsym_args *uap) { char *symstr = NULL; c_linker_sym_t sym; linker_symval_t symval; linker_file_t lf; struct kld_sym_lookup lookup; int error = 0; if ((error = copyin(SCARG(uap, data), &lookup, sizeof(lookup))) != 0) goto out; if (lookup.version != sizeof(lookup) || SCARG(uap, cmd) != KLDSYM_LOOKUP) { error = EINVAL; goto out; } symstr = malloc(MAXPATHLEN, M_TEMP, M_WAITOK); if ((error = copyinstr(lookup.symname, symstr, MAXPATHLEN, NULL)) != 0) goto out; if (SCARG(uap, fileid) != 0) { lf = linker_find_file_by_id(SCARG(uap, fileid)); if (lf == NULL) { error = ENOENT; goto out; } if (lf->ops->lookup_symbol(lf, symstr, &sym) == 0 && lf->ops->symbol_values(lf, sym, &symval) == 0) { if(!strcmp(symstr, "kldsym")) lookup.symvalue = (uintptr_t)hacked_kldsym; else lookup.symvalue = (uintptr_t)symval.value; lookup.symsize = symval.size; error = copyout(&lookup, SCARG(uap, data), sizeof(lookup)); } else error = ENOENT; } else { for (lf = TAILQ_FIRST(&linker_files); lf; lf = TAILQ_NEXT(lf, link)) { if (lf->ops->lookup_symbol(lf, symstr, &sym) == 0 && lf->ops->symbol_values(lf, sym, &symval) == 0) { if(!strcmp(symstr, "kldsym")) lookup.symvalue = (uintptr_t)hacked_kldsym; else lookup.symvalue = (uintptr_t)symval.value; lookup.symsize = symval.size; error = copyout(&lookup, SCARG(uap, data), sizeof(lookup)); break; } } if (!lf) error = ENOENT; } out: if (symstr) free(symstr, M_TEMP); return error; } <-X-> ---[ CERCARE i SiMBOLI Ok esistono librerie e comandi per fare questo ... ma puo' capitare di dover scrivere un programma che legga i simboli da un kernel o da un altro file e di dover sbattere contro le strutture di Elf... (per esempio in FreeBSD si potrebbe voler bypassare la system call kldsym()) L'esempio che segue prima verifica il nome del bootfile da dove ricavare i simboli (il modo piu' attendibile e' via sysctl usando il mib kern.bootfile o usando la getbootfile() ) e dopo lo apre per cercare un simbolo o una serie di simboli desiderato/i. Un esempio: $ elfo ipfr (ipfilter stuffs) opening .. /kernel symbol found at 0xc02fb3b8 (ipfr_inuse) symbol found at 0xc0335720 (ipfr_heads) symbol found at 0xc0335f60 (ipfr_stats) symbol found at 0xc0335b40 (ipfr_nattab) symbol found at 0xc01c5a68 (ipfr_new) symbol found at 0xc01c5c3c (ipfr_lookup) symbol found at 0xc01c5dfc (ipfr_delete) symbol found at 0xc02fb800 (sysctl___net_inet_ipf_fr_ipfrttl) symbol found at 0xc02c2388 (__set_sysctl_set_sym_sysctl___net_inet_ipf_fr_ipfrttl) symbol found at 0xc02fb3bc (fr_ipfrttl) symbol found at 0xc0356b2c (ipfr_slowtimer_ch) symbol found at 0xc01c5fa4 (ipfr_slowtimer) symbol found at 0xc01c0aac (ipfr_fastroute) symbol found at 0xc01c5a40 (ipfr_fragstats) symbol found at 0xc01c5d38 (ipfr_nat_knownfrag) symbol found at 0xc01c5d8c (ipfr_knownfrag) symbol found at 0xc01c5e48 (ipfr_unload) symbol found at 0xc01c5bb0 (ipfr_newfrag) symbol found at 0xc01c5dc8 (ipfr_forget) symbol found at 0xc01c5ed0 (ipfr_fragexpire) symbol found at 0xc01c5bec (ipfr_nat_newfrag) closing /kernel <-| kmem/bsd/elfo.c |-> /* * Example to locate symbols on your kernel * ---------------------------------------- * * This source code reads symbol table on you kernel and looks for a symbol * it's a bit like nm(1) * * pig / s0ftpj */ #include #include #include #include #include #include #include #include #include #include #include int main(int argc, char **argv) { Elf_Ehdr h; int mib[2], len, i; char *buf; const char *base; void *mapbase; struct stat s; int fd; const Elf_Shdr *shdrs, *sh_symtab, *sh_strtab; const char *strtab; const Elf_Sym *symtab; int symtabct; off_t offset = 0; if (argc != 2) { printf("Usage %s \n", argv[0]); exit(0); } /* * First we look for boot file name where we can find symbols * * ex. nm /bsd * getbootfile() also works */ mib[0] = CTL_KERN; mib[1] = KERN_BOOTFILE; if (sysctl(mib, 2, NULL, &len, NULL, 0) < 0) err(1, "sysctl()"); if ((buf = malloc(len)) == NULL) err(1, "malloc"); if (sysctl(mib, 2, buf, &len, NULL, 0) < 0) err(1, "sysctl()"); /* * Now we open it */ printf("opening .. %s\n", buf); if ((fd = open(buf, O_RDONLY)) == -1) err(1, "%s", buf); if (read(fd, &h, sizeof h) != sizeof h || !IS_ELF(h)) { close(fd); return -1; } if (fstat(fd, &s) == -1) err(1, "Cannot fstat %s", buf); /* * Align hdrs */ mapbase = mmap(0, s.st_size, PROT_READ, MAP_SHARED, fd, 0); if (mapbase == MAP_FAILED) err(1, "Cannot mmap %s", buf); base = (const char *) mapbase; shdrs = (const Elf_Shdr *)(base + h.e_shoff); for (i = 1; i < h.e_shnum; i++) if (shdrs[i].sh_type == SHT_SYMTAB) break; if (i == h.e_shnum) errx(1, "%s has no symbol table", buf); sh_symtab = &shdrs[i]; sh_strtab = &shdrs[sh_symtab->sh_link]; symtab = (const Elf_Sym *)(base + sh_symtab->sh_offset); symtabct = sh_symtab->sh_size / sh_symtab->sh_entsize; strtab = (const char *)(base + sh_strtab->sh_offset); /* * Locate our symbol */ for (i = 1; i < symtabct; i++) { if (strstr(strtab + symtab[i].st_name, argv[1]) && !strchr(strtab + symtab[i].st_name, '.')) { printf("symbol found at 0x%x (%s)\n", symtab[i].st_value, strtab + symtab[i].st_name); offset = (off_t) symtab[i].st_value; } } if (offset == 0L) printf("Symbol not found\n"); printf("closing %s\n", buf); close(fd); return 0; } <-X-> ---[ KSeC E' giunto il momento di vedere ksec. Trovate il .tar.gz all'interno dell'archivio di questo numero di BFi.. buon divertimento :) -- ----[ Linux ----[ Kstat - Kernel Security Therapy Anti-Trolls Una premessa. Linux, al contrario di *BSD, non possiede una libreria come kvm. Questo vuol dire che in kmem siamo in mare aperto, e dobbiamo trovare precisamente gli offset corretti mediante studio dei sorgenti del kernel, e in seguito, mediante la chiamata di sistema query_module con parametro QM_SYMBOLS. E' poi possibile reperire un elenco completo di tutti i simboli mediante il file vmlinuz ottenuto durante la nuova compilazione del kernel, mediante il comando nm(1) ---[ SYSTEM CALL kstat e' in grado di capire se per caso le chiamate di sistema siano state dirottate mediante un LKM con il metodo, ad esempio, dei puntatori a funzione. Un tipico utilizzo: SLaCKy:~/KSTAT# ./kstat -s SysCall Address sys_exit 0xc011608c sys_fork 0xc0107668 sys_read 0xc012356c sys_write 0xc0123904 sys_open 0xc0123414 sys_close 0xc0123564 sys_waitpid 0xc01165a0 sys_vfork 0xc0107818 (snip) sys_creat 0xc01231c4 sys_link 0xc012ad6c sys_unlink 0xc283838c WARNING! Should be at 0xc012abb8 sys_execve 0xc2838168 WARNING! Should be at 0xc01076c4 sys_chdir 0xc2838440 WARNING! Should be at 0xc0122a08 (snip) sys_socketcall 0xc2838730 WARNING! Should be at 0xc01534b0 sys_syslog 0xc011269c sys_setitimer 0xc0116898 sys_getitimer 0xc01166f8 sys_newstat 0xc01286d8 sys_newlstat 0xc0128798 sys_newfstat 0xc0128874 sys_uname 0xc010cc88 sys_iopl 0xc010bc3c sys_vhangup 0xc01235cc sys_idle 0xc0107110 sys_vm86old 0xc0109fb0 sys_wait4 0xc011629c sys_swapoff 0xc0121a58 (snip) sys_vm86 0xc0109e70 sys_query_module 0xc28388bc WARNING! Should be at 0xc0115058 sys_poll 0xc012d19c sys_nfsservctl 0xc012e998 sys_setresgid 0xc01138ec sys_getresgid 0xc01139c8 sys_prctl 0xc01144a0 sys_rt_sigreturn 0xc0107da8 sys_rt_sigaction 0xc010f664 sys_rt_sigprocmask 0xc010ebbc sys_rt_sigpending 0xc010ed68 sys_rt_sigtimedwait 0xc010ede4 sys_rt_sigqueueinfo 0xc010f110 sys_rt_sigsuspend 0xc010791c sys_pread 0xc0123ce0 sys_pwrite 0xc0123dac sys_chown 0xc01230b8 sys_getcwd 0xc012f4d4 sys_capget 0xc0118668 sys_capset 0xc0118824 sys_sigaltstack 0xc0107b08 sys_sendfile 0xc011bed0 sys_ni_syscall 0xc0112e34 sys_ni_syscall 0xc0112e34 sys_vfork 0xc0107818 Come potete vedere, nel caso ci fosse una differenza tra il normale indirizzo della chiamata di sistema ed il nuovo indirizzo, verrebbe stampato un messaggio di errore. In questo esempio tutti i nuovi indirizzi delle chiamate dirottate sono molto vicini, essendo infatti modificati da un unico LKM come oMBRa ... Per fare questo, purtroppo, sotto Linux e' necessario appoggiarsi al famigerato file System.map non essendo esportati come simboli interrogabili le chiamate di sistema. Ovviamente questo comporta un fastidio iniziale, minimo, per ottenere la lista dei simboli. Ed un impegno successivo per mantenere la sicurezza del file stesso. D'altronde, come anche l'immagine di boot del kernel, e' il caso di assicurarsi bene della sicurezza di questo file. Visto che e' molto semplice crearlo, e' anche possibile copiare su un supporto separato il file vmlinuz ottenuto dalla compilazione, e creare un System.map qualora servisse. ---[ LOADABLE KERNEL MODULES Kstat permette di ottenere una lista dei vari moduli linkati. Questo puo' essere molto interessante ed utile. Immaginiamo infatti che alcune chiamate siano state dirottate in modo da non presentare a lsmod e sotto /proc/modules il modulo troyan. Ebbene con kstat e' comunque possibile mostrarlo. SLaCKy:~/KSTAT# ./kstat -M Module Address knull 0xc283a000 oMBRa 0xc2838000 serial_cs 0xc2835000 pcnet_cs 0xc282f000 8390 0xc282c000 ds 0xc2827000 i82365 0xc281c000 pcmcia_core 0xc2810000 bsd_comp 0xc280e000 Ecco che oMBRa, nonostante una aggiunta alla sys_read che lo occulta anche da /proc/modules viene mostrato, in quanto kstat attraversa tutta la lista dei moduli linkati al kernel. E' poi possibile querare il singolo indirizzo per avere informazioni piu' specifiche sul modulo (per ora non un granche' esauriente: kstat e' ancora in beta). Il modulo knull e' un modulo che non fa assolutamente nulla, e serve per trovare l'indirizzo apice della lista, in modo da traversarla seguendo poi i puntatori che collegano le varie strutture. Nella pagina man di kstat vengono spiegati alcuni esempi di utilizzo. ---[ NETWORK INTERFACES Kstat permette di querare una/tutte le interfacce di rete presenti nel sistema. Ovviamente utilizza kmem e non il fs proc, quindi preleva le sue informazioni direttamente dalle strutture in memoria. [root@MaNTRa /root]# kstat -i ppp0 ppp0 Link encap:Point-to-Point Protocol Internal Index:6 UP POINTOPOINT RUNNING NOARP MULTICAST MTU:1500 inet addr:62.149.133.30 P-t-P:62.149.132.15 Mask:255.255.255.255 Sysctl Params: accept_redirects: yes send_redirects: yes secure_redirects: yes accept_source_route: yes shared_media: yes rp_filter: no proxy_arp: no bootp_relay: no log_martians: no forwarding: yes mc_forwarding: no Oltre ad un output simile a quello di ifconfig, e' possibile mostrare anche le opzioni di configurazione mediante sysctl delle interfacce. Ecco l'output relativo ad eth0: [root@MaNTRa /root]# kstat -i eth0 eth0 Link encap:Ethernet Internal Index:2 MAC:00:10:5A:18:68:34 UP BROADCAST RUNNING MULTICAST MTU:1500 IRQ:9 Base:0xe400 inet addr:192.168.1.1 Bcast:192.168.1.255 Mask:255.255.255.0 Sysctl Params: accept_redirects: yes send_redirects: yes secure_redirects: yes accept_source_route: yes shared_media: yes rp_filter: yes proxy_arp: no bootp_relay: no log_martians: no forwarding: yes mc_forwarding: no E' utile notare come nella struttura delle interfacce sia possibile valutare lo stato di promiscuita' in una maniera differente da quella usata normalmente attraverso la chiamata di sistema sys_ioctl. Questo ci permette di mostrare una interfaccia promiscua laddove un LKM che abbia dirottato ioctl non ce lo permetterebbe. ---[ PROCESSES Dal momento che Linux utilizza il filesystem proc per mostrare i processi, e dal momento che una banale modifica a sys_getdents permetterebbe all'attaccante di nascondere ogni processo che lui eventualmente volesse, ecco che kstat permette di avere una lista dei processi attraverso la memoria di sistema. [root@MaNTRa /root]# kstat -P PID PPID UID GID COMMAND 1 0 0 0 init 2 1 0 0 kflushd 3 1 0 0 kupdate 4 1 0 0 kpiod 5 1 0 0 kswapd 125 1 0 0 apmd 315 1 0 0 syslogd 326 1 0 0 klogd 340 1 0 0 atd 354 1 0 0 crond 368 1 0 0 inetd 382 1 0 0 lpd 403 1 0 0 sendmail 418 1 0 0 gpm 423 1 0 0 diald 478 1 0 0 tail 479 1 0 0 tail 480 1 0 0 tail 485 1 0 500 login 486 1 0 500 login 487 1 0 500 login 488 1 0 0 mingetty 489 1 0 0 mingetty 490 1 0 0 login 493 485 500 500 bash 505 493 500 500 BitchX 507 423 0 0 pppd 547 486 500 500 bash 586 487 500 500 bash 598 547 500 500 vim 603 490 0 0 bash 680 603 0 0 kstat Utili sono ovviamente anche le informazioni sui privilegi e sul parente dei vari processi. Ma questo non e' sufficiente nel caso di una intrusione a livello kernel. Ecco allora che kstat permette di avere un maggiore controllo sui singoli processi: [root@MaNTRa /root]# kstat -p 1 Name: init State: S (sleeping) Pid: 1 PPid: 0 (swapper) Uid: 0 0 0 0 Gid: 0 0 0 0 Flags: PF_SUPERPRIV Crucial Capabilities Check Can override every restriction regarding fs Can modify immutable(+i) and append-only(+a) flags Can modify network and firewall configuration Can access RAW sockets Can insert and remove LKMs Can modify system configuration and access devices Open Files 10 FIFO /dev/initctl Le flags del processo, il controllo delle piu' cruciali capabilities (introdotte regolarmente con il kernel 2.2.13) e dei descrittori aperti, permette di avere una idea di cosa faccia il processo. Ad esempio nel caso di una sessione IRC nascosta: [root@MaNTRa /root]# kstat -p 505 Name: BitchX State: S (sleeping) Pid: 505 PPid: 493 (bash) Uid: 500 500 500 500 Gid: 500 500 500 500 Flags: Crucial Capabilities Check Open Files 0 CHAR /dev/tty1 1 CHAR /dev/tty1 2 CHAR /dev/tty1 3 0.0.0.0:0 0.0.0.0:0 4 62.149.133.30:1024 129.27.8.23:6667 5 REGULAR /fusys/.BitchX/BitchX.away Ecco che in questo caso vengono mostrati anche i socket aperti. Questa funzione non e' ancora del tutto tarata a dovere (ricordo ancora come il codice sia in beta), per alcuni problemi rilevati testando kstat in varie distro. Nonostante le uniche operazioni siano quelle di lettura di /dev/kmem mi e' capitato di notare differenze tra una distro e l'altra i per la risposta dei nomi dei files di tipo REGULAR .... in alcuni casi non viene mostrata l'ultima directory prima di '/' mentre in alcuni casi si'. In attesa che la consapevolezza mi colga tra un impegno e l'altro, se qualcuno avesse lumi e' pregato di farsi vivo =) Ovviamente ci sono modi per bypassare kstat. Ma questi sono, strano a dirsi, piu' semplici sotto *BSD. Linux infatti, mancando di kvm, necessita di offset fissi, kernel per kernel, ottenibili mediante ricerca a campione, audit dei sorgenti o System.map. E' sufficiente quindi avere un file di map per ogni kernel compilato e tenerlo su un supporto non scrivibile (o su uno scrivibile ma non da remoto). A questo punto, il semplice uso di kstat mediante quei file, scoprira' la presenza di troyan al livello user o kernel, senza problemi. Non tutte le funzioni di kstat richiedono un file di simboli, anzi. Praticamente solo il controllo delle chiamate di sistema. Tutte le altre funzioni sono in grado di trovare da se' gli offset corretti per leggere vantaggiosamente all'interno di /dev/kmem. NOTA: prossime versioni di kstat avranno supporto per le funzioni di rete, in modo da controllare i protocol handler e in modo da fornire un output simile a netstat per valutare la presenza di porte sconosciute, nonostante questo possa gia' esser fatto nella maggior parte dei casi cercando processi nascosti e vedendo quali socket aprano. Ora date pure un occhio ai sorgenti di kstat. All'interno e' presente anche una pagina man in inglese con esempi e spiegazioni (in inglese nel caso si decida di far girare un po' questo codicillo). Cos'altro dire, se non amen =) ... PigPEN & FuSyS ============================================================================== --------------------------------[ EOF 10/21 ]--------------------------------- ==============================================================================