Copy Link
Add to Bookmark
Report

BFi numero 12 file 01 French

eZine's profile picture
Published in 
Butchered From Inside
 · 5 years ago

  

-[ BFi - version française ]--------------------------------------------------
BFi est une e-zine écritte par la communauté hacker italienne.
Les codes sources complets et la version originale en italien sont
disponible içi:
http://bfi.freaknet.org/dev/BFi12-dev-01.tar.gz
http://www.s0ftpj.org/bfi/dev/BFi12-dev-01.tar.gz
Version française traduite par tleil4X <tleil4x@tiscali.it>
------------------------------------------------------------------------------



==============================================================================
-------------------[ BFi12-dev - fichier 01 - 13/01/2003 ]--------------------
==============================================================================


-[ DiSCLAiMER ]---------------------------------------------------------------
Tout le matériel contenu dans BFi a but esclusivement informatif et
éducatif. Les auteurs de BFi ne se chargent d'aucune responsabilité
pour des éventuel dommages à choses ainsi que à personnes, dus à
l'emploi de code, programmes, informations, techniques contenus dans
la revue.
BFi est un libre et autonome moyen d'éxpression; comme nous auteurs
nous sommes libres d'écrire BFi, tu est libre de continuer dans ta
lecture ou alors de t'arreter içi. Par conséquent, si tu te sens
outragé par les thèmes traités et/ou par la façon dont ils sont
traités, * interrompt immédiatement ta lecture et éfface ces fichiers
de ton ordinateur *.
En continuant, toi lecteur, tu te prends toute la responsabilité de
l'emploi que tu feras des indications contenus dans BFi.
Il est interdit de publier BFi sur les newsgroup et la diffusion de
*parties* de la revue: vous pouvez distribuer BFi tout entier et dans
ça forme originale.
------------------------------------------------------------------------------




-[ HACKiNG ]------------------------------------------------------------------
---[ FiND HiDDEN RESiDENT PR0CESSES
-----[ twiz <twiz@email.it>
sgrakkyu <sgrakkyu@libero.it>


---[ Préambule


Cet article se compose essentiellement en deux parties: l'une, la premiére,
cherchera d'analyser les bases téoriques de FHRP, en décrivant concepts, les
applications aux kernel linux et l'autre part "pratique" qui présentera
les objectifs et le code effectif.
La premiére partie n'est pas strictement nécessaire pour utilizer FHRP, mais
elle fournit les bases (et aussi idées) pour le comprendre et, peut-être,
l'améliorer ou l'adapter aux propres exigences :)

Juste à titre de précisation, FHRP est un module écrit pour le kernel linux
2.4, seulement pour les systèmes uni-procésseurs, avec l'objectif de trouver
des éventuels processus cachés sur une machine en utilizant la valeur du
registre cr3 come signature.
A part ça une (je dirais bonne :)) partie des idées et du code devrait être
applicable aussi à d'autres systèmes opératifs, à système SMP et, pourquoi
pas, à d'autres architectures.


---[ Les processus et le scheduler


Dénicher des processus cachés, nous avons dit. Je donne pour aquis que vous
tous sachez qu'est-ce que c'est un processus et qu'elle vous soit claire
l'"abstration" qu'on fait dans le kernel linux, ou ce serait plus correct
parler de task, vu que linux voit et gestit kernel thread et processus en
userland fondamentalement à la mème façon, c'est-à-dire avec une struct
task_struct .

[remarque: Ceci ne veut pas dire qu'il n'y a pas de différence entre kthread
et processus dans userland, en effect, par exemple, la struct mm_struct d'un
kthread sera toujours égal à NULL, par ce que la virtual memory qui accéde est
celle directement mapped en kernel space. Un autre important exemple c'est que
actuellement dans le thread "base" du kernel 2.4 la full preemption patch
n'est pas appliquée, et donc les kernel thread et n'importe quel chose marche
en kernel space n'est pas pre-emptable]

A la même façon nous nous perdons pas en discours sur ce que c'est et comment
fonctionne un scheduler, il y a plein de livre et de textes sur le web qui
traitent l'argument (pour une liste consulté le reference au fond)... dans une
nutshell on peut définir le scheduler la part de système operatif qui s'occupe
de choisir, entre plusieurs processus concurrents pour la CPU, celui à faire
marcher effectivement, selon un certain algorithme (il y a beaucoup
d'algorithme possible et plusieurs peuvent être les objectifs.. vous pouvez
penser seulement aux différences entre un système batch et un système real
time).
En outre il peut y avoir différentes situations où le scheduler est rappelé,
par exemple quand un processus termine ou se bloque en attente d'une resource
particulière (ex. i/o sur une porte) ou bien quand celle-ci devient
disponible, ou encore dans le cas qu'il utilize un fork ou il ait finit son
time quantum.

Une dernière definition peut étre utile c'est la différence entre scheduling
*non-preemptive* et *preemptive*. Selon le premier cas le scheduler choisit un
processus et il le laisse en exécution jusqu'il n'a pas terminé son travail,
bloque ou volontairement libére la cpu; dans le deuxième cas le processus a un
certain time quantum, après lequel il est suspendu et un autre proc est
choisit.
Si on y applique une "priorité" entre les processus (Priority Scheduling
Algorithm), un processus à plus haute priorité qui devient disponible (ex.
après qu'il s'est liberé une resource sur laquelle elle était bloquée) sera
schedulé et le processus précedent sera suspendu.
Conditio sine qua non du preemptive scheduling est évidemment la présence d'un
timer interrupt.

Vu qu'on, comme nous avont dit, travaillera avec le kernel linux (thread de
développement 2.4) et sur un système x86 à 32bit uniprocesseur, nous
continuont et nous allont détailler comme tous ça est implémenter.
Il y a au moin 3 textes trouvable online (réferences [2] [3] et [4]) qui
analyse, même trés approfondissement, le scheduler du kernel linux, aussi bien
UP que SMP; donc aussi dans ce cas nous nous limiteront à analyser les points
qui plus nous interesse, en cherchant d'approfondir le plus possible ceux-ci
et en laissant aux interessés d'approfondir au-dela de la lecture de ces
textes.
La meilleure façon de comprendre le scheduler linux reste toutefois lire
kernel/sched.c et quelque partie de kernel/timer.c (sys_alarm et sys_nanosleep
sont écrite dans se fichier), plus include/linux/sched.h et time[r].h .

A un moment donné un task peut se trouver dans un de ces 5 états (membre
task->state de la struct task_struct ) :

<include/linux/sched.h>
[snip]

#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define TASK_ZOMBIE 4
#define TASK_STOPPED 8

[snip]

TASK_RUNNING -> Le task est sur la runqueue et attend de conséquent la CPU.
Touts les processus sur la runqueue sont en état TASK_RUNNING, alors que le
contraire peut ne pas être vrai; l'action d'assigner TASK_RUNNING à un
processus et le mettre en runqueue n'est pas atomique.

TASK_INTERRUPTIBLE -> Le task est sleeping, mais il peut être reveillé par un
signal ou bien s'il finit le timer du sleep assigné avec schedule_timeout() .
Quand un processus va avec sleep en TASK_INTERRUPTIBLE, sa task_struct est
inserée dans la waitqueue liée à la resource sur laquelle elle etait bloquée.

TASK_UNINTERRUPTIBLE -> Le task est sleeping, mais c'est garantit qu'il y
restera jusqu'à l'expire du timer attribué par la schedule_timeout() .
Cette option est utilizé rarement à l'intérieur du kernel linux, mais elle
sera utile dans le cas d'un device driver qui dois attendre qu'une opération
finisse; si interrompu, elle pourrait retourner une fausse valeur ou laisser
le device en état impredictible ou alors corrompu.

TASK_STOPPED -> Le task a été stoppé ou par un signal ou parcequ'on le cherche
avec ptrace (à un PTRACE_ATTACH est envoyé un SIGSTOP au child). Un task qui
est TASK_STOPPED n'est pas évidemment sur la runqueue et sur aucune waitqueue.

L'état TASK_ZOMBIE nous intéresse pas trop, le task a simplement terminé son
exécution, mais le père n'a pas exécuté wait() sur son status. Ces processus
deviennent fils "adoptifs" d'init, qui périodiquement exécute des wait(), pour
les eliminer de facto.

Ce qui nous intéresse remaquer c'est que *seulement* les TASK_RUNNING
concourent pour la CPU, tandis qu'à tous les autres n'est pas donnée la CPU (à
moin que, évidemment, ils ne soient pas réveillés par un expire pour les
processus en timeout, ou, à part des TASK_UNINTERRUPTIBLE, par un sugnal).
L'intéret de ceci et le fait que les TASK_STOPPED ne finissent dans aucune
waitqueue seront plus clairs quand ont analysera les bases de FHRP et surtout
le système des signature des processus.

-----] Case study n.1 -> schedule_timeout()

Nous avons dejà introduit la notion de timeout et de schedule_timeout() ;
maintenant voyons comment le kernel linux le gére. Il faut surement commencer
par la fonction schedule_timeout() , contenue dans kernel/sched.c .

signed long schedule_timeout(signed long timeout)

Cette fonction reçoit comme paramètre le "temps" en jiffies pendant lequel le
processus devra rester en sleep. Les jiffies ne sont rien d'autre que le
nombre de clock ticks à partir du départ de la machine; donc la valeur de la
variable jiffies est incrementer à chaque timer interrupt.
FHRP marche en grande partie grace au timer interrupt et on verra après le
méchanisme en détail.

A présent, après avoir déclaré une struct timer_list (utilizé pour configuré
le timeout) et un long expire, un switch sur la valeur du timeout est exécuté
(pour faciliter la lecture nous avons enlevé les commentaires à sched.c):

switch (timeout)
{
case MAX_SCHEDULE_TIMEOUT:
schedule();
goto out;
default:
{
printk(KERN_ERR "schedule_timeout: wrong timeout "
"value %lx from %p\n", timeout,
__builtin_return_address(0));
current->state = TASK_RUNNING;
goto out;
}
}

Entre les deux cas il nous intéresse surtout MAX_SCHEDULE_TIMEOUT : dans ce
cas là il ne sera établi aucun timer, mais il sera tout simplement appelé le
scheduler, de façon à ce que le processus, mit précédemment en
TASK_INTERRUPTIBLE (en genéral ;)) ou TASK_UNINTERRUPTIBLE, sorte de la
runqueue et il en soit schedulé un autre. Donc le processus ne se reveillera
pas après n'importe quel temps.
Le cas MAX_SCHEDULE_TIMEOUT est tout de suite intéressant dans FHRP vu que la
sys_accept (dans la wait_for_connect() ) passe justement ce paramètre à la
schedule_timeout , en creant quelque problème pour trouver les processus
cachés qui soit en attente sur une porte. La solution et l'importance de ceci,
comme d'abitude, vous seront plus claire après l'analyse pratique de FHRP.

Dans touts les autres cas (default :) il y a seulement un check (comme c'est
écrit dans les commentaires du source *PARANOIAQUE* :)) pour vérifier qu'il ne
soit pas passé une valeur negative à schedule_timeout (chose qui de toute
façon ne devrai *jamais* arriver). Dans tel cas quand même schedule_timeout
retournera 0 .

Si rien de tout cela se produit (et c'est le cas le plus probable) la fonction
se comportera comme ça:

expire = timeout + jiffies;

init_timer(&timer);
timer.expires = expire;
timer.data = (unsigned long) current;
timer.function = process_timeout;

add_timer(&timer);
schedule();
del_timer_sync(&timer);

timeout = expire - jiffies;

Rien de particulier, tout simplement la valeur de expire est calculé (on somme
la valeur actuelle de la variable jiffies à la valeur de delay contenu dans
timeout) et les champ de la struct timer_list sont remplis. Ce qui nous
intéresse c'est que la fonction qui sera rappelée la première pour réveiller
le proc en sleep est process_timeout .
add_timer(&timer) ajoute le proc dans la global list des timer actifs, tandis
que la del_timer_sync(&timer) est utilisée pour eviter les race condition dans
le cas ou la fonction retourne avant le moment juste (ex. reveillée par un
signal). Dans ce cas la il sera retourné 'timeout', cet-à-dire le temps passé
depuit le set du timer.
Pour approfondir le fonctionnement de schedule_timeout(), les commentaires de
sched.c devraient être suffisant :)

Avant de continuer, il faut dire deux mots sur comment un processus peut être
mit en sleep, avec ou sans timeout. Les cas sont fondamentalement deux: une
invocation que j'appelerais "manuelle" de schedule_timeout() ou alors
l'utilisation d'une des interruptible_sleep_on_timeout /
interruptible_sleep_on / sleep_on_timeout / sleep_on .

Un exemple d'invocation "manuelle" se trouve aussi dans la sys_nanosleep() ,
la syscall qui est invoquée quand dans les code en C nous écrivons, par
exemple, sleep(10) .
Survolons les parties qui regardent les processus realtime ( task->policy mise
sur SCHED_RR ou SCHED_FIFO); les lignes qui nous interessent sont:

current->state = TASK_INTERRUPTIBLE;
expire = schedule_timeout(expire);

Dans ce cas içi il n'y a pas besoin de régler une waitqueue, vu que le
processus n'est pas en train d'attendre (c'est-à-dire de bloquer en attendant)
une resource, mais il reste tout simplement en "sleep" pour quelque temps.
Dans le cas où le processus se bloque en attente d'un événement, si
schedule_timeout() est "manuellement" invoqué, dans les lignes precédentes on
utilize et on ajoute a une waitqueue_head un wait_queue.
Nous verront un exemple quand nous analyseront en bref la wait_for_connect() .

Les différentes *sleep_on* (astérisques utilisés comme regexp ;)) font
éxactement la même chose; tout simplement elles écrivent toujour une waitqueue
en l'ajoutant à la wait_queue_head_t struct passée comme argument et elles
s'occupent à l'intérieur de la fonction d'écrire l'état du proc et d'appeler,
si nécessaire, schedule_timeout() .
La différence entre timeout ou non est obtenu, avec schedule_timeout(), selon
si le timeout est différent ou égal a MAX_SCHEDULE_TIMEOUT .

-----] Case study n.2 -> Les étapes d'un processus qui se réveille

Comme on a tout juste vu, à l'expire du timer établit par schedule_timeout() ,
la fonction appelée est process_timeout() , qui recoit comme paramètre un
unsigned long qui n'est rien d'autre que le pointeur à la task_struct qui est
allée en sleep.
A partir de ce mouvement on se retrouvera à sautiller entre différentes
fonctions, chacune servant de "wrapper" à la suivante, jusqu'à arriver à la
try_to_wake_up() , qui est la fonction qu'effectivement reveillera le
processus en question.
Ce qu'on fera dans ce deuxième case study ce sera parcourir les étapes
en cherchant de mettre en évidence les parties les plus intéressantes pour
FHRP et les raisons qui nous on portés à hooker certains points du code plutot
que d'autres.

La première fonction qui est rappelée est donc la process_timeout et c'est
aussi la fonction que FHRP hooke pour vérifier ce type de processus. La
fonction en soi, comme tout les wrapper, est tres simple:

static void process_timeout(unsigned long __data)
{
struct task_struct * p = (struct task_struct *) __data;

wake_up_process(p);
}

On y trouve déclaré un pointeur et avec un cast on le fait pointer au
processus qui etait allé en sleep et aprés on appele la wake_up_process .
process_timeout() est aussi la fonction qui est hooké à l'intérieur de FHRP,
à cause de certaines raisons:
- C'est la première fonction appelé quand il s'agit de réveiller un processus,
ce qui ce traduit dans le ne devoir pas dépendre de d'autres fonctions qui
auraient pu être hookées par l'attaquant et donc rapporter des fauts
résultats.
- Elle est trés courte et il est donc possible la re-écrire completement dans
l'hook, de façon à avoir la certitude que rien se mettra au millieu.
- Si on place un proc en schedule_timeout et, pour quelque raison, ce proc
n'est pas passé à wake_up_process et, donc, il n'arrive pas à try_to_wake_up
ce proc ne ce réveillera plus... ceci en FHRP est utilisé comme métode un
peu "rude" (mais efficace) pour rendre inoffensif un éventuel processus
"nocif".

La wake_up_process() est elle aussi un fonction wrapper, qui appele la
try_to_wake_up() .
Elle est utilisée, par exemple, quand on envoit un SIGCONT à un processus
( kernel/signal.c ).

inline int wake_up_process(struct task_struct * p)
{
return try_to_wake_up(p, 0);
}

Comme nous avons dit avant elle ne fait rien d'autre que rappeler la
try_to_wake_up, en passant comme deuxième paramètre (int synchronous , on le
verra bientot) "0", c'est-à-dire la demande de rappeler reschedule_idle() , en
plus d'insérer le module dans la runqueue.
Le résultat de tout ceci est que, en reschedule_idle() , il sera calculé la
"goodness" (attravers la dynamic priority) du processus reveillé et, si il
se révelera avoir un priorité superieure par rapport au processus courrent,
il ce passera un context switch et le processus à peine réveillé obtiendra
immédiatement la CPU.
Voyons donc la try_to_make_up() :

static inline int try_to_wake_up(struct task_struct * p, int synchronous)
{
unsigned long flags;
int success = 0;

spin_lock_irqsave(&runqueue_lock, flags);
p->state = TASK_RUNNING;
if (task_on_runqueue(p))
goto out;
add_to_runqueue(p);
if (!synchronous || !(p->cpus_allowed & (1 << smp_processor_id())))
reschedule_idle(p);
success = 1;
out:
spin_unlock_irqrestore(&runqueue_lock, flags);
return success;
}

Même cette fonction est assez simple:

- on y gagne un lock sur la runqueue avec spin_lock_irqsave et, en plus que le
lock, les interrupt sont disabilités sur la CPU courante, si SMP, (en UP ça
ce traduit dans la classique succesion save_flags() , cli() ,
restore_flags() ) et dans flags on y sauve l'interrupt state du processeur;

- le state du processeur est changé à TASK_RUNNING et on controle si le
processus se trouve sur la runqueue (si c'est come ça le lock est enlevé,
l'interrupt state est restauré grace à flags et ça retourne 0);

[note: l'état du processus est porté à TASK_RUNNING sans aucun check sur le
state précedent et sur le fait qu'il soit été "eventuellement" modifié. C'est
la raison pourquoi en portant à TASK_STOPPED manuellement les processus, ceux
qu'ils avaient un timer encore à "finir" sont réveillés... rien de trop
préoccupant, vu qu'on peut décider de laisser réveiller *uniquement* les
processus avec une signature/cr3 valable]

- le task est ajouté à la runqueue et, si syncronous == 0 ou si il n'est pas
possible faire marcher le processus sur la CPU courrante (condition que en
UP est *toujour* fausse) ( cpus_allowed n'est rien d'autre que une bitmap
qui liste les CPU valides pour le switch), reschedule_idle() est appelé.
Dans touts les cas success est établi à 1, pour indiquer que le task a été
inséré dans la runqueue avec succès.

La reschedule_idle() est la fonction qui s'occupe, comme nous avons dit
quelque paragraphes auparavant, de vérifier si la goodness de processus
réveillé est meilleur de celle du processus en exécution et, si c'est ainsi,
de "porter" à un context switch en faveur du processus réveillé.
Le code est assez compliqué en SMP (en effect il a comme objectif de "trouver"
une idle cpu pour y faire marcher le processus dessus), tandis qu'il se réduit
qu'à quelque ligne en UP:

int this_cpu = smp_processor_id();
struct task_struct *tsk;

tsk = cpu_curr(this_cpu);
if (preemption_goodness(tsk, p, this_cpu) > 1)
tsk->need_resched = 1;

En tsk on y recupère le processus courrant sur la CPU, tandis que
preemption_goodness ne fait rien d'autre que soustraire la goodness du
processus réveillé à celle du proc courrant. Si le chifre est supérieur à un
ça veut dire que la priorité du proc réveillé est supérieure et need_resched
est établit à 1; ce qui force un appel au scheduler au premier ret_from_intr
ou syscall.
La need_reschedule() , comme c'est écrit aussi dans un commentaire dans
kernel/sched.c , est absolument timing critical; effectivement si vous vous
rappelez à partir de la try_to_wake_up elle est appelée avec le lock établi
sur la runqueue et ce n'est pas possibile demander le tasklist_lock .

Vu le bon nombre de textes approfondits trouvable online (Reference [2]
[3] et [4]... en plus que sched.c ) nous nous attarteront pas sur d'autres
parties du scheduler linux, comme par exemple la goodness (qui n'est rien
d'autre que le core du scheduling algorithm) ou alors schedule() en soi
parceque elles sont amplement traitées si bien dans Linux Kernel Internals 2.4
[2] que dans le chapitre de Understanding the linux kernel disponible pour le
télechargement [3].

------] Case study n.3 -> PIT et alentour, augmenter la fréquence du clock

Comme nous avons déja eu façon de dire le timer interrupt est la conditio sine
qua non d'un scheduling preemptive. C'est en effect grace au timer interrupt
qu'on peut périodiquement baisser le time quantum d'un processus
(task->counter) et établir, si égal à 0, task->need_resched à 1 de façon à
invoquer le scheduler à la prochaine ret_from_intr ou ret_from_sys_call et
obtenir ainsi un context switch.

Voyons comme exemple le kernel linux; la fréquence (modifiable au compile time
tout simplement en changant le nombre de HZ dans asm/param.h ... par default
c'est égal à 100) est établi à 1 tick chaque 10ms.
C'est clair que si nous augmentons le nombre de HZ nous obtiendrons un système
avec un response time supérieur; c'est-à-dire le temps passé entre l'envoi
d'un ordre et l'exécution du même, mais aussi un overhead supérieur, du au
fait qu'il augmente considérablement les context switch et chaque processus a
globalement à chaque epoch "moin de temps à disposition" pour marcher, vu que
son counter finirait en un temps inférieur.
A la même façon ça va de soi que si le nombre de HZ diminue on obtiendra
des temps de réponse toujour plus longs.

Toute les deux actions (augmenter et diminuer la fréquence du timer interrupt)
portent des avantages à certain processus, tandis qu'ils en pénalisent
d'autres.
Une shell ou alors n'importe quelle application interactive gagne profit avec
l'accroissement, tandis qu'une opération comme un find sur tout le disque dur
ou un backup est avantagée d'un delay supérieur entre les context switch.

FHRP, quand il est chargé, hausse la fréquence du timer interrupt, jusqu'à
1 tick chaque milliseconde (valeur qui est évidemment modifiable, comme on
verra quand on analysera cette partie là de code), mais appele aussi la
routine originalle pour l'handling du timer interrupt avec la fréquence
classique de 10ms. Tout ceci nous permet de controller plusieur fois entre un
"effectif" timer interrupt et l'autre, ce qui marche effectivement sur la CPU.

Nous voyons maintenant comment tout ça c'est possible et surtout ce que permet
le raising du timer interrupt et comment le kernel linux gére tout ça. A la
fin de l'analyse on proposera aussi un trés simple module qui permet de
augmenter et baisser la fréquence comme il nous plait.

[note: cette part n'est pas strictement nécessaire pour la compréhention du
fonctionnement de FHRP et, probablement, intéressera surement plus les
passionnés d'architecture et devices à niveau bas. Si vous n'etes pas
intéressé vous pouvez bien la sauter ou la lire par curiosité sans vous vous
arreter trop sur les détails.
En outre, même si une partie de la description vaut aussi pour les systéme
SMP, les parties analysés et le code proposé sont seulement pour UP.]

On commence par l'architecture. Nous analyserons le 8253/8254 (même si de
facto on verra seulement le 8253, vu qu'on analysera pas les extentions du
8254)
Programmable Interrupt Timer chip.
Entre les 3 canaux disponibles sur le PIT, le seul qu'on examinera est le
canal/timer 0, c'est-à-dire le device que le kernel linux utilise pour
surveiller le temps (timer interrupt).

[note: pour approfondir sur tout ce qu'on ne traitera pas içi et, dans le cas
présent, sur les autres deux canaux, c'est-à-dire le canal/timer 1 qui
controle le refresh de la DRAM et le canal/timer 2 qui est lié au speaker vous
pouvez consulter les reférences [5] et [6].]

Tous les trois timer du chip 8253 sont controlés par le même clock signal, qui
vient de l'oscillation du quarz sur la fiche mère. La fréquence de ce clock
signal est à peu près 1.1931 MHz.
Chaque timer à un compteur, programmable, qui conte chaque combien de temps ou
apres combien de temps (selon l'Operation Mode, décrit plus loin) envoyer son
propre "signal".
Le timer 0 du chip 8253 est relié au PIC (Programmable Interrupt Controller)
8259 qui s'occupe à la base d'écouter sur 8 sources d'interrupt et de passer
ces même, un à la fois, à la CPU avec un méchanisme de priorité grace auquel
un interrupt plus important peut interrompre un autre et reçevoir la CPU pour
soi.
Le PIC 8259 permet de "masker" certain interrupt à l'aide des 8 bit (un par
interrupt) de l'IRM (Interrupt Mask Register); en effect un bit placé a 1
dans l'IRM empéchera a cet interrupt là d'"atteindre" la CPU pour être servi.

[note: en realité les IRQ actuellement sont plus que 8: ils sont le double,
c'est-à-dire 16, divisés en 8 Master, directement joins à la CPU, et 8 Slave
qui passent attravers l'IRQ2. Toutefois il ne me sembre pas le cas
d'approfondir ça, ci quelqu'un est intéressé il peut consulter la Reference
[5]]

Le timer 0 du chip 8253 est lié à l'IRQ0 du 8259, c'est-à-dire l'Interrupt
Request (comme ceci est définit un interrupt source qui passe par le 259) à
plus haute priorité.
Comme nous avons dit plusieur fois, la fréquence de default à l'intérieur du
kernel linux de cet interrupt est 100 Hz, ou bien 1 tick à chaque 10ms.
Ceci s'obtien en établissant le compteur su timer au nombre 11932 (grace à
la simple division on obtien *environ* 100Hz ou 10ms); on va voir comment
c'est calculé dans le kernel linux.
La macro qui exécute le cifre est LATCH:

<include/linux/timex.h>

#define LATCH ((CLOCK_TICK_RATE + HZ/2) / HZ) /* For divider */

CLOCK_TICK_RATE est définit dans include/asm/timex.h et c'est égal à 1193180.
La raison de cette division est facilement déductible. LATCH est une macro
générique, qui s'adapte à tous les controller de toutes les architectures
supportés, tandis que CLOCK_TICK_RATE , c'est-à-dire la fréquence à laquelle
les interrupt arrivent du quarz (ok... c'est pas exactement du quarz ;))
change d'architecture à architecture.
Le résultat est donc 11932.

Avant de voir comment ce cifre est mit dans le compteur rélatif au
canal/timer 0 , il est nécessaire parler un peu des portes auquelles sont
liés le PIT.
Le PIT est lié à 4 porte:
- 0x40 - compteur du canal 0
- 0x41 - compteur du canal 1
- 0x42 - compteur du canal 2
- 0x43 - Mode Control Register

La porte 0x43 est celle qui nous intéresse le plus, parceque, avant de pouvoir
établir le compteur, il faut "l'instruire" sur comment se comporter. Ceci est
fait avec une out sur la porte 0x43.
Le Mode Control Register est composté de 8 bit:

| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|___| |___| |_______| _

- Le bit 0 sert comme switch pour indiquer comment la valeur du counter sera
passé, c'est-à-dire si en binaire à 16 bit ou si en BCD à 4 decades.
- Le bit 123 établissent un des 6 mode possibles (pour une description de
chaque mode, aller voir la Reference [5]). Le mode à établir qui nous
intéresse directement est le 2, c'est-à-dire génerer une impulsion chaque fois
que x cycles (counter) sont passés.
- Les bit 4 et 5 sont les Read/Write/Latch format bits, dans notre cas ils
seront établits tout les deux à 1, pour indiquer qu'on passera à la porte
indiquée par les bit 6 et 7, avec deux out succésif, avant le LSB et puis le
MSB du cifre à insérer dans le compteur.
- Les bit 6 et 7 indiquent quel compteur nous irons modifier et donc à quelle
porte il faut s'attendre de reçevoir les out. Dans notre cas on les établira
tous les deux à 0, pour indiquer le compteur du canal 0.

Pour résumer, donc, la bitmask à passer sera 00110100, c'est-à-dire 0x34 en
hex, et c'est éxactement ce que fait le kernel dans arch/i386/kernel/i8259.c :

outb_p(0x34,0x43); /* binary, mode 2, LSB/MSB, ch 0 */
outb_p(LATCH & 0xff , 0x40); /* LSB */
outb(LATCH >> 8 , 0x40); /* MSB */

Tous ça devrait être suffisemment clair et ne pas nécessité d'explication.

La dernière chose qui nous reste à faire c'est montrer une application
pratique; le module que je colle içi est écrit en syntaxe intel at&t (celle du
gas pour nous comprendre) et permet de passer come paramêtre le cifre du
counter à établir.
Le cifre de default augmentera la fréquence du clock à 1000Hz... vous pouvez
éssayer de l'insmoder et à lancer quelque programme intéractif, genre top ou
exécuter des commandes sur la shell. Si vous avez le framebuffer votre curseur
paraitra devenir fou :)

<-| fhrp/citf.s |->
/*
* citf.s *
* Change the interrupt timer frequency via lkm *
* *
* This module gets the value to set in the counter of channel 0 via parameter 'freq' and *
* sets it. Default value is 0x4a9, the value you'll get setting HZ to 1000 inside kernel *
* I list there some value you could find useful to know : *
* for HZ == 100 (default in linux kernel) -> 0x2e9c *
* for HZ == 1000 (default in this lkm) -> 0x4a9 *
* for HZ == 50 -> 0x5d38 *
*
* Example : insmod citf.o freq=0x5d38
*/


.globl init_module
.globl cleanup_module

.globl freq
.data
.align 4
.size freq, 4
freq:
.long 0x4a9

.text
.align 4

start:

init_module:
pushl %ebp
movl %esp,%ebp
xorl %eax, %eax
pushfl
cli
movb $0x34, %al
outb %al, $0x43
movw freq, %ax
outb %al, $0x40
movb %ah, %al
outb %al, $0x40
popfl
xorl %eax, %eax
leave
ret

cleanup_module:
pushl %ebp
movl %esp,%ebp
xorl %eax, %eax
pushfl
cli
movb $0x34, %al
outb %al, $0x43
movw $0x2e9c, %ax
outb %al, $0x40
movb %ah, %al
outb %al, $0x40
popfl
leave
ret


.globl __module_parm_freq
.section .modinfo
__module_kernel_version:
.ascii "kernel_version=2.4.9\0"
__module_parm_freq:
.ascii "parm_freq=i\0"
<-X->

J'ajoute içi de suite un simple code C pour calculer les cifre à passer
comme paramètre, disons surtout pour commodité, vu que j'espère que ça vous
soit clair comment ça ce calcule :)

<-| fhrp/freq.c |->
#define MY_LATCH(x) ((1193180 + x/2) / x) /* For divider */

main(int argc, char **argv)
{
int i = atoi(argv[1]);
printf("%x\n", MY_LATCH(i) );
}
<-X->


---[ FHRP: les objectifs, les idées et l'implémentation

Après cette partie "théorique" c'est le moment de présenter le tool en sois
et d'en analyser les objectifs et les idées qui ont portés à son écriture.
A la fin de la présentation on proposera aussi des éventuelles idées pour
améliorer ou développer certaine fonction.

------] Les objectifs

L'objectif de FHRP sur la fin est un seul, trouver les processus qu'ils aient
été cachés par l'attacker. On part d'un principe: les processus ont été cachés
en les éliminant *au moin* de la task_struct double chained list (la raison
vous sera plus claire bientot) et on veut trouver même un hypothétique
processus qui, décroché de n'importe quelle liste du kernel (c'est-à-dire
runqueue, pidhash list et task list), reçoit des "quantum" de CPU par example
grace à un manual switching à bas niveau ou grace à une grosse modification du
scheduler en soi (même avec toutes les limitations et les difficultés qu'ils y
a à implémenter un switch manuel ou un hook au scheduler).

La première chose qui nous sert pour arriver à notre but c'est avoir un façon
pour reconnaitre les processus, une sorte de signature qui distingue les
processus 'bon' des processus 'mauvais'.
Le chois a été le contenu de cr3, le quatrième des control register, parceque:

- Il est indispensable pour l'exécution du processus.

Le registre cr3 (dit aussi PDBR - Page Directory Base Register) contient
l'adresse physique de la base de la page directory et deux flag (PCD et PWD).
Seulement les 20 most-significative bits sont spécifié, tandis que pour les
autres 12 on assume que ça vaut 0.
Justement parceque c'est indispensable pour l'exécution d'un proc il ne peut
pas être "faked".

- Il est facile à récupérer.

Le registre cr3 est contenu dans chaque task_struct en task->mm->pgd (pour le
confronter il faut se rappelé de le "traduire" en adresse physique avec
__pa() ) et c'est donc rapide le récupérer pour créer le database des cr3
connus.
En plus il est rapidement récupérable même pendant que le processus est en
exécution sur la CPU (movl %cr3, %eax) et c'est une bonne nouvelle, vu qu'on
est en interrupt time et le clock est augmenté à 1000Hz.

Le cr3 est chargé par le kernel au moment du context switch grace à
switch_mm() , avec une simple instruction inline assembly:

asm volatile("movl %0,%%cr3": :"r" (__pa(next->pgd)));

Maintenant qu'on a trouvé une signature qui marche, il nous sert un endroit
où nous placer pour pouvoir constament surveiller la CPU et le contenu du cr3
register.

[note: pour facilité la compréhension, on a divisé le code de FHRP en 4
fichier: 3 sources .c et un include .h; on va maintenant présenter les
charactéristiques plus importantes et les points les plus intéressants, le
reste vous pouver vous le lire dans les fichiers mêmes ;)]

------] cr3-timer.c

A l'intérieur de ce fichier on y trouve les fonction raise_timer() et
restore_timer() qui permettrons d'hausser à l'insmod et reporter au numéro
standard la fréquence du timer interrupt. Si vous avez lu le case study n.3
vous devriez les comprendre tout de suite (en verité elles ne sont rien
d'autre que la transposition en C du code de citf.s); si vous l'avez sauté, la
seule chose qui peut vous intéresser maintenant c'est que pendant la période
où le module sera linké au kernel, il se passera un timer interrupt chaque ms.
Le choix d'hausser à 1000Hz a été pris pour avoir plus de probabilité de
trouver un processus sur la CPU et il s'est démontré bien équilibré avec un
possible overhead que nos fonctions de check créent pendant l'interrupt time.

handler_new() est le nouveau handler qu'on va utiliser pour le timer
interrupt. Cet handler calcule, grace à HZ et MY_HZ, la fréquence avec
laquelle appeler le scheduler de façon que la succession des context switch ne
change pas.
Toujour à l'intérieur de handler_new on y est exécute un controle entre le
cr3 couramment chargé et la liste des cr3 valides.

Comme on découvre en allant voir timer.c et en suivant les différentes
fonctions appelées, il y a beaucoup de points où on pouvait hooker pour
obtenir plus ou moin le même effet. On a choisi de ce placer sur le début de
la chaine, en remplacant l'adresse de notre nouvelle fonction à l'adresse
contenu dans la struct irqhandler irq0 (l'adresse pour l'handler du timer
interrupt). De cette façon on finira par re-écrire même des autres éventuels
hook avec l'intention de *gérer* de quelque sorte les processus cachés.
La procédure qu'on utilise pour gérer un interrupt n'est pas le target de cet
article et donc je n'expliquerai pas trop comment est géré la IDT et le
méchanisme lié au switching en kernel land; c'est plus intéressant, pour
comprendre le fonctionnement de FHRP, comment sont invoqué les ISRs (Interrupt
Service Routine).

La structure fondamentale qui contient la ISR c'est la struct irqaction
</include/linux/interrupt.h>

struct irqaction {
void (*handler)(int. void *, struct pt_regs *);
unsigned long flags;
unsigned long mask;
const char *name;
void *dev_id;
struct irqaction *next;
};

- le premier champ identifie la véritable ISR, c'est-à-dire la fonction de
handler qui gérera l'interrupt;
- flags établit la modalité avec laquelle cette routine doit être exécuté...
parmi les settings plus importants je mentionne la possibilité d'exécuter la
routine avec les interrupt disabilité (SA_INTERRUPT) et de pouvoir partager
la IRQ line avec d'autres device (SA_SHIRQ);
- le champ name est seulement un identificatif qui donne le nom à l'I/O device
en question;
- dev_id identifie le Major/Minor number du device;
- next est un pointeur à une autre struct irqaction, ça nous permet d'avoir,
dans le cas où la IRQ line soit partagé (SA_SHIRQ), une liste de structures
chacune relative à son propre device et à sa propre routine.

Vu qu'on ce trouve à travailler avec la irq0, voyons comment elle est déclaré:

static struct irqaction irq0 = { timer_interrupt, SA_INTERRUPT, 0, "timer", NULL, NULL};

L'adresse de la struct irq0, déduite avec nm ou de System.map , est à la base
des fonctions set_irq et restore_irq , qui s'occupent d'établir notre nouvelle
struct irqaction (et donc en pratique notre nouveau handler) et de rétablir
l'état original.

------] cr3-func.c

Dans ce fichier on y trouve quelques fonctions déterminantes pour le
fonctionnement de FHRP et qui augmentent sensiblement la probabilité, même si
elle est déjà assez élevé, de trouver un processus caché.
Les deux première fonctions qu'on va voir sont stop_all_process_safe() et
resume_all_process() , relatives, respectivement, au "STOP" des processus
et au "réveil" de ceux-ci à travail conclu.
L'avantage de bloquer tous les processus 'bons' nous permets d'augmenter la
probabilité de scheduling d'un processus caché; ceci permet de facto au
scheduler de laisser la CPU seulement aux kthread, aux processus 'safe' (qu'on
va bientot voir) et évidemment aux éventuels processus occultés.

La fonction stop_all_process_safe() énumére toute la série des processus
'légaux' actifs sur la machine au moment du chargement du module (en
parcourrant la task_struct double chained list avec list_for_each ) et arréte
un à un tous les processus en leurs envoyant une force_sig(SIGSTOP, p) .
Cependant pas *tous* les processus sont stoppés, vu la présence des controles:

....
if(p->mm)
....
if((t != pid_bash_safe) && (t != SAFE_P_KLOGD) && (t != SAFE_P_SYSLOGD) && (t != SAFE_P_INIT))
....
if((p->state != TASK_UNINTERRUPTIBLE) && (p!=current))
....

En effect ils sont laissés actifs:
- les kernel thread -> i kernel thread, comme on a déjà dit, n'ont pas une
mm_struct (donc ils accédent directement à la mémoire mappé en kspace) et donc
chaque essai d'accéder au pgd, en plus d'être théoriquement inutile, se
traduirait dans un segfault;
- la bash parent de insmod -> son pid est récupéré de p->p_opptr->pid et
permet d'avoir une shell avec laquelle exécuter rmmod (en évitant le freeze
complet de la machine) et, pourquoi pas, d'autres programmes (avec des
limitations, sinon leurs cr3 seraient interprétés comme 'mauvais' et, s'ils
utiliseraient schedule_timeout, ils ne seraient plus reveillés);
- klogd et syslogd -> qui permettent à notre module d'envoyer des méssages sur
la console;
- init ;
- les processus en TASK_UNINTERRUPTIBLE -> avant-tout envoyer un signal à ces
processus n'aurait aucun effect immédiat, ils continuraient à rester en sleep
jusqu'à la fin du timeout et/ou de l'opération (généralement de I/O). En outre
les processus en TASK_UNINTERRUPTIBLE se rencontre rarement et, même s'ils se
reveillent pendant que notre module est actif, ils seraient correctement gérés
par l'hook de la process_timeout ;
- le current -> stopper le current donne dans beaucoup de cas quelques
problèmes.
Dans le cas présent current est insmod et donc l'arréter ne permettrait pas à
notre module de se charger et, trés probablement, ça porterait à un freeze de
la machine. En plus ça nous intéresse pas trop insmod, vu que son destin se
sera celui d'être terminé juste après avoir chargé notre module.

resume_all_process() n'est rien d'autre que l'opposé de
stop_all_process_safe() et utilise à nouveau force_sig pour envoyer un SIGCONT
(dans le cas d'un problème à re-acquérir la tty de la part de quelques
processus, des "fg" devraient être suffisant).

La dernière mais cruciale fonction qu'on trouve içi (et qui a déjà été
"avancé") est take_global_page_dir() , qui s'occupe de récupérer le cr3 du
processus courant.

unsigned long int take_global_page_dir()
{
__asm__ __volatile__ ("movl %cr3, %eax");
}

------] cr3-main.c

Ceci est le coeur du module et, en plus des init_module et cleanup_module (qui
organisent le fonctionnement du module), ils y sont aussi d'autres fonctions
intéressantes.
Avant tout on y construit un tableau des processus 'autorisés', avec la
fonction routine_set_table() , en suite on y fait l'hook à la process_timeout.

Cet hook est très simple (comme tous les hook à wrapper) et nous permet de
controller tous les processus qui se "reveillent" (et que peut-être
retourneraient tout de suite à dormir, vu qu'il n'y a aucune resource
disponible, sans recevoir cpu en userspace). Il est permit seulement aux
processus 'autorisés' de se reveillés, tandis que les autres sont mis aux
oubliettes et au 99% (à moin que l'attacker n'ait pas creé un controle
parallel) ne recevront plus la CPU, en devenant comme ça inoffensif.

La deuxième fonction qu'on analyse est la check_listening_socket() .
Cette fonction nous permet de trouver des éventuels processus cachés qui
soient en attente, après une accept() , sur une certaine porte.
Avant tout il faut expliquer le besoin de cette fonction. Prenont par example
une "socket tcp" en écoute sur une porte (comme une banale backdoor par
example).
Le dernier pas de la accept() sur laquelle elle sleepera plus tard est dans
net/ipv4/tcp.c la wait_for_connect()

static int wait_for_connect(struct sock * sk, long timeo)
{
DECLARE_WAITQUEUE(wait, current);
int err;

add_wait_queue_exclusive(sk->sleep, &wait);
for (;;) {
current->state = TASK_INTERRUPTIBLE;
release_sock(sk);
if (sk->tp_pinfo.af_tcp.accept_queue == NULL)
timeo = schedule_timeout(timeo);
[...]

Celle-ci est la partie qui nous intéresse. On y trouve déclaré la waitqueue,
un controle si il y a quelqu'un en 'accept_queue' pour le socket et, si ce
n'est pas le cas, schedule_timeout est appelé.
Toutefois l'hook sur la process_timeout dans ce cas nous aide pas du tout;
en effect dans le cas classique qu'on prend en considération, timeout est
imposté a MAX_SCHEDULE_TIMEOUT .
Comme on a vu dans le case study relatif à la schedule_timeout , une valeur
de MAX_SCHEDULE_TIMEOUT n'établit aucun timer, mais, tout simplement, rappéle
le scheduler et envoi la socket à dormir, jusqu'à quand un signal (d'abitude
SIGIO) ne s'occupera pas de le réveiller.

A présent nous avons besoin de:
- une façon pour pouvoir énumérer tous les socket en listening;
- une façon pour *remonter* a partir de la struct socket en listening à la
task_struct du processus qui la controle.

La solution devrait être assez claire en regardant le code; avant tout on
prend les différentes hash table du listening socket (tcp, udp et raw), et
puit, remonté à la sock struct, on obtient wait_queue_head struct grace à
sk->sleep . En énumérant ancore celle çi on trouve les 'possibles'
struct wait_queue grace auquelles on peut remonter à la struct task_struct
et içi fare un check.
Ce check est fait par check_wait_process() . Dans le report du résultat, à
l'unload du module, on y trouve notifié aussi la porte et le type de socket
relié.
Une autre possible idée (et peut-être la plus naturelle) c'etait celle de
remonter à la struct file grace à sock et faire une espèce de pattern matching
avec les struct file qu'on peut atteindre de la task_struct , mais ç'aurait
denaturé l'idée à la base de FHRP, c'est-à-dire le check du cr3.

[note: si vous donnez un coup d'oeil à net/netsyms.c vous verrez que deux
"simboles" qui nous intéressent, udp_hash et tcp_hashinfo , sont exportés
seulement si au moin un des CONFIG_IPV6_MODULE , CONFIG_KHTTPD ,
CONFIG_KHTTPD_MODULE est choisi. Le problème se résou rapidement avec l'hook
de deux autres fonctions, mais, vu que c'est un tool du coté admin, ce n'est
pas ajouté dans le code (si vous le voulez deux #ifdef devraient suffir ;)).
La solution plus rapide reste re-compiler avec, par example,
CONFIG_IPV6_MODULE .]

-----] config.h

L'header de FHRP contient par commencer les #define pour l'hook de quelques
fonctions (ex. process_timeout() ) ou pour accéder à quelque struct au niveau
kernel (ex. irq0 ou la listening raw socket hash table), qui doivent être
correctement établits avec nm ou une System.map ajourné.
Le nom du "pattern" à cherchez est mit dans les commentaires à coté de chaque
#define .

Toujour dans ce fichier vous devriez établir les pid de syslogd et klogd, en
étant ceux-ci deux démons engagés au startup de la machine ils devraient
maintenir toujours le même pid aussi après les reboot.

Les dernières deux parties qui pourraient vous intéresser configurer sont
MY_HZ , qui décide à quelle fréquence porter le clock et MAX_RESULTS qui
décide quel est le nombre maximum de resultats à rendre. Toutes les deux sont
impostées à une valeur de default qui devrait marcher. Au maximum si on y
trouve retourné 10 résultats ça pourrait être utile, par sécurité, refaire
l'essai avec plusieurs résultats.
Si vous voulez, vous pouvez aussi, avec une petite modification, changer
MAX_RESULTS et rendre le total des results configurable à insmod-time avec
un MODULE_PARM .

La dernière partie où on va s'arrété en config.h est compare_cr3() . La
fonction a été déclaré static inline, de façon à éviter une CALL à celle-ci
(nous sommes en interrupt time et quelque cycle en moin nous aident... sans
oublier que une CALL tend à flusher la pipeline).
La fonction a été structurée pour être la plus "optimisée" possible, par
example avec l'implementation d'une sorte de cache qui garde en mémoire
le dernier cr3 trouvé (en effect, en ayant le clock élevé de dix fois, il
est fort probable que le même cr3 mauvais, au moment où le programme tourne
sur la cpu pendant un quantum ou plus, soit trouver plusiers fois. La cache
nous permet d'éviter de devoir lire la liste des cr3 trouvés chaque fois...
n'oublions pas qu'on est en interrupt time et on a le clock à une fréquence
plus haute).

-----] Conclusions, possibles modifications et amélioration

Commençons avec le fonctionnement... comme ça vous sera clair, ceci n'est pas
un module pensé pour être résident; au contraire, ça devrait être suffisant
quelques secondes (à moin que vous ne vouliez pas être sur contre sleep(100)
ou des longs sleep... mais des controles croisés les trouveraient), le temps
de deux epochs de finir, et vous devriez avoir une photographie de ce qui ce
passe sur votre machine.
En plus le module rend toujour un FAUX POSITIF. C'est le contenu du cr3 de
rmmod... nous avons préféré le faire rendre, en imprimant avant la valeur sur
le video, pour être plus sur. Ca va de soit qu'un simple hidden_task - 1
enlève ce faux positif... mais ça convient être paranoique.
Voyons quand même un example pratique du fonctionnement du module:

root@twiz:/home/twiz/cr3/cr3-dev# insmod fhrp.o
[snip]
Pid: 79 Context: 2577000
Pid: 83 Context: 25da000
Pid: 85 Context: 2523000
Pid: 93 Context: 3c9c000
Pid: 94 Context: 3e1a000
Pid: 95 Context: 3cd1000
Pid: 96 Context: 3bd4000
Pid: 97 Context: 3b75000
Pid: 98 Context: 3ccd000
Pid: 99 Context: 24af000
Pid: 100 Context: 233d000
Pid: 101 Context: 23cc000
Pid: 194 Context: 3a36000
[snip]
Setting up process_timeout hook..
root@twiz:/home/twiz/cr3/cr3-dev# rmmod fhrp
Restoring process_timeout..
Ripristining all process...
Leaving Module
Hidden Processes Foud : 1
Cuurent-> deve essere rmmod: 2139000
cr3 malign : 2139000 pid : 672 got from Interrupt handler
root@twiz:/home/twiz/cr3/cr3-dev#

Le cr3 malign reporté n'est rien d'autre que le faux positif dont on parler.
Si vous voulez tester l'efficace du module sur une backdoor en listen ou un
processus que vous exécutez, vous pouvez lui faire "oublier" de ramasser le
cr3 pendant le collect du tableau (un simple if (p->pid == piddàoublier) )
ou bien essayer un module qui cache des processus en les enlevant de la task
list. Nos test nous on donner des résultats positifs :)

Continuons avec une chose qu'on a déjà dit, mais qui est particulièrement
important: ce module n'est pas une panacée, si un ps troyen ou un code qui
modifit proc et quelques syscall est en train de caher le processus, ce module
ne peut pas trop faire, au maximum vous faire voir tous ls processus à insmod
time. Ils y sont d'autres façons pour controler, par example voir la liste de
task_struct (comme fait le module à insmod time), utiliser un ps safe ou
vérifier les md5sum (si le redirect n'est pas à kernel level), ou alors
utiliser KSTAT pour controler les syscall.

En fait il n'y a pas *le* tool, mais un travail combiné de plusieurs tool
quand il s'agit de vérifier l'intégrité d'une machine. Ce module aide en
trouvant les hide plus complexes, ceux qui détachent le processus des listes
connues, en allant agir à très bas niveau et en s'appuyant très peu aux
fonctions du kernel.

Rien est 100% valable quand soit l'attacker soit le sysadmin peuvent
travailler en kernel space. L'attacker pourrait avoir modifié la
create_module() et pourrait faire pattern matching parmi les opcodes du module
à la recherche d'un movl %cr3, %eax ou d'autres points. A présent on pourrait
assombrir le code, le rendre auto-modifiant, faire simplement pushl %cr3,
popl %eax...
insérer des random 'nop-like' à l'intérieur du code même (pas nécessairement
un NOP est \x90 ;)).
Encore, l'attacker pourrait faire une analyse statistique de l'accès à
force_sig , controler si il est incremental, fréquent (ce qui vourrait dire
que notre module est en chargement) et envoi seulement SIGSTOP, et donc
stopper le processus caché jusqu'à la réception des SIGCONT.
Mais à ce point là on pourrait *manuellement* stopper les processus et les
re-exécuter... on aurait quelques petits problèmes en plus (même si c'a été
testé... et c'est assez sur aussi comme ça ;)), mais on "roule" aussi se
controle. En bref, ce sont des décors possibles une fois qu'on sait qu'est ce
qu'il 'pourrait arriver', mais ça n'exclu pas que ce module soit utile et
valide dans beaucoup de cas. En plus, gros changements comme le check de
opcode ou de la force_sig devraient être rapides à voir avec un dump et en
disassemblant ces fonctions et, pourquoi pas, en cherchant un jmp *%eax ou
pushl/ret ou movl/ret "suspectes" ;)

Le fait même que le module a été écrit à très bas niveau et n'est pas résident
nous donne déjà un bon degré de protection (on re-écrit nous la irq0, et donc
on change aussi un possible hook d'un attacker, et comme ça on agit dans
beaucoup de situation), mais, évidemment, pas le 100% de securité :)

Le module comme ça comme il est laisse ouvert quelques améliorements qui ne
sont pas inclus dans la version de release. Parmi ceci:
- Controle des kernel thread - Il n'y a aucun controle à kspace (surtout
parceque la métode du cr3, comme on a dèjà expliqué, ne peut pas s'appliquer),
cepandant cr3 n'est pas la seule signature valide. Tests bien positifs ont été
fait aussi en utilisant comme signature la valeur de %esp ( p->thread.esp ).
Un rapide coup d'oeil à l'implémentation de get_current/GET_CURRENT devrait
vous expliquer comment faire.
- Controle manuel d'autres waitqueue - Aussi celle çi est possible à travers
les wait_queue_t à l'intérieur de la task_struct . La fonction utilisée pour
les socket est (volontairement) assez générique, pour pouvoir justement être
adapté à ce scenario.
- Elimination des processus trouvés - Aussi ça c'est possible. On a
p->thread.esp , donc (si vous avez regardé get_current et vous avez bien
compris son fonctionnement ça vous sera clair...) on sait comment accéder à la
task_struct . A présent ce n'est pas compliqué se comporter comme pseudo-exit
et éliminer les lock à la mm, les file descriptor ouverts (etc.), elever les
link possibles et libérer la mémoire. C'est aussi possible déduire
pratiquement toutes les informations imaginables relatives au processus en
question. Mais (il y a un mais), pas beaucoup de ces donnés sont *absoluments
nécessaires* (pensez à un manual switching) et l'attacker pourrait les avoir
modifiés exprès pour faire crasher notre module.
- L'irq0 n'est pas le seul point où on peut nous insérer pour avoir un
controle "scandé par le temps", c'est aussi possible profiter du RTC... en
plus en SMP-contest certaines choses pourraient devoir être modifiées...
la Reference [6] donne quelque detail en plus sur comment le faire.

Dit ça nous croyons (et nous espérons) que vous trouverez ce tool intéréssant,
ainsi que nous espérons vous ayez trouvé les informations contenus dans cet
article utiles et/ou valables. Pour tous doutes, critiques, améliorations,
patch & co les contacts voie email sont écrits a coté du titre :)

Avant de passer aux References, quelques remerciements/shoutouts à vecna,
Dark-Angel, albe et rene @ irc.kernelnewbies.org pour quelques discussions sur
le scheduler, les systèmes SMP et d'autres.
Un salut va aux gars du racl (la partie su PIT est sur mesure pour vous :),
ndtwiz), à _oink (merci pour la carte postale ;) ndtwiz) et à "tous ceux qui
nous conaissent"
(ce qui fait très appel téléphonique à un show télévisif).
Je pense qu'on a dit assez de conneries :)


---[ References

[1] - Modern Operating Systems - Second Edition - Andrew S. Tanenbaum

[2] - Linux Kernel Internals 2.4 - Tigran Aivazian
http://www.moses.uklinux.net/patches/lki.html

[3] - Understanding the Linux Kernel - Bovet, Cesati - Ch10 "Scheduling"
http://www.oreilly.com/catalog/linuxkernel/chapter/ch10.html

[4] - For Kernel_Newbies By a Kernel_Newbie - A.R.Karthick
http://www.freeos.com/articles/4536/

[5] - http://www.nondot.org/sabre/os/articles/MiscellaneousDevices/

[6] - Timer-related functionality in Linux kernels 2.x.x - Andre Derric Balsa
http://www.cse.msu.edu/~zhengpei/tech/Linux/timerin2.2.htm

[7] - Linux Kernel Sources 2.4.*


-[ WEB ]----------------------------------------------------------------------

http://www.bfi.cx
http://bfi.freaknet.org
http://www.s0ftpj.org/bfi/


-[ E-MAiL ]-------------------------------------------------------------------

bfi@s0ftpj.org


-[ PGP ]----------------------------------------------------------------------

-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: 2.6.3i
mQENAzZsSu8AAAEIAM5FrActPz32W1AbxJ/LDG7bB371rhB1aG7/AzDEkXH67nni
DrMRyP+0u4tCTGizOGof0s/YDm2hH4jh+aGO9djJBzIEU8p1dvY677uw6oVCM374
nkjbyDjvBeuJVooKo+J6yGZuUq7jVgBKsR0uklfe5/0TUXsVva9b1pBfxqynK5OO
lQGJuq7g79jTSTqsa0mbFFxAlFq5GZmL+fnZdjWGI0c2pZrz+Tdj2+Ic3dl9dWax
iuy9Bp4Bq+H0mpCmnvwTMVdS2c+99s9unfnbzGvO6KqiwZzIWU9pQeK+v7W6vPa3
TbGHwwH4iaAWQH0mm7v+KdpMzqUPucgvfugfx+kABRO0FUJmSTk4IDxiZmk5OEB1
c2EubmV0PokBFQMFEDZsSu+5yC9+6B/H6QEBb6EIAMRP40T7m4Y1arNkj5enWC/b
a6M4oog42xr9UHOd8X2cOBBNB8qTe+dhBIhPX0fDJnnCr0WuEQ+eiw0YHJKyk5ql
GB/UkRH/hR4IpA0alUUjEYjTqL5HZmW9phMA9xiTAqoNhmXaIh7MVaYmcxhXwoOo
WYOaYoklxxA5qZxOwIXRxlmaN48SKsQuPrSrHwTdKxd+qB7QDU83h8nQ7dB4MAse
gDvMUdspekxAX8XBikXLvVuT0ai4xd8o8owWNR5fQAsNkbrdjOUWrOs0dbFx2K9J
l3XqeKl3XEgLvVG8JyhloKl65h9rUyw6Ek5hvb5ROuyS/lAGGWvxv2YJrN8ABLo=
=o7CG
-----END PGP PUBLIC KEY BLOCK-----


==============================================================================
-----------------------------------[ EOF ]------------------------------------
==============================================================================

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT