photo of devloop

devloop :: blog

sécurité informatique, programmation, Linux et plus encore

Subscribe to RSS feed

Posts tagged with "C"

Mon langage de programmation idéal

, , , ...

Un billet sérieux en cette journée du 1er avril... whistle

Une question me trotte dans la tête depuis quelques temps : quel est mon langage de programmation idéal, celui qui me conviendra le mieux, qui me transformera en ninja du code ? ninja

J'y ai réfléchi et j'ai finalement noté les caractéristiques qu'il devrait avoir :
  • Il est multi-paradigme impératif et objet
  • Il a un typage statique fort
  • Il a des types entier pratique (du style uint, ulong) comme C#
  • Il se compile en langage machine
  • Il est multiplate-forme ou facilement portable
  • Il a un système de gestion de la mémoire, comme un garbage collector
  • Il a des types de base évolués (listes et dictionnaires comme en Python) et permet d'obtenir rapidement des résultats, c'est un langage de haut niveau
  • Il permet toutefois d'interagir avec le système comme c'est possible sous Linux en C
  • Il a une syntaxe proche du C (avec des accolades)
  • Ce n'est pas du Perl
  • Il a une API riche comme Java
  • Il a une ou des bibliothèques permettant de réaliser des interfaces graphiques

Il faut avouer qu'au début, c'était plutôt mal parti pour trouver un tel langage de programmation...
Mes recherches m'ont amené à jeter un oeil à différents langages :
  • C# : correspond pour de nombreux points, multiplate-forme, framework .NET fréquent sous Windows mais Mono rare sous Linux. Ca reste toutefois du bytecode.
  • Objective Caml a pas mal de points qui collent aussi... mais la syntaxe ne m'attire pas
  • C++ : je n'en ai pas forcément de bons souvenirs (pas de gestion de la mémoire avancé, me semblait casse-tête et brouillon) mais peut-être je devrais lui redonner une chance p En ajoutant une couche supplémentaire (comme les libs Qt) on obtient un résultat plus proche de mes attentes. La version C++0x pourrait valoir le coup.
  • D semble avoir tout ce que je souhaite, même si il y a encore peu de librairies et de documentation dans ce langage

J'ai donc testé le D, réussi à faire des programmes de base (basé sur sa librairie standard). La compilation d'un Hello World donne un exécutable assez lourd cow mais cela peut se compenser avec le temps gagné sur de plus grosses applications...
Mais je me suis alors rendu compte qu'il manque un point : la facilité de mise en place des outils pour programmer p J'ai beau me battre pour tenter d'installer et d'utiliser GtkD, je n'ai obenu aucun résultat satisfaisant...
Je vais peut-être devoir me remettre au C++ confused

Et vous, quelles sont les caractéristiques de votre langage de programmation idéal ?

Pseudo-terminaux, portes dérobées, telnet, tunneling et Marie-Antoinette

, , , ...

J'ai toujours trouvé aux réseaux un petit côté "magique", l'idée de pouvoir éditer un fichier qui se trouve à des milliers de kilomêtres de chez moi ma toujours parue fabuleuse wizard C'est peut-être pour cela que j'ai toujours admiré les TTYs (ouah c'est beau un terminal, surtout avec du ncurse p ) et ils sont restés longtemps pour moi un grand mystère, au moins jusqu'à ces derniers jours, moment fatidique où j'ai décidé de me lancer dans l'épineuse aventure des TTY/PTY.

Un terminal, kezako ?
Sous Linux, un terminal est avant tout un périphérique. On en trouve dans /dev (pty*, tty*) et dans /dev/pts (terminaux esclaves).
Le terminal se charge de faire le lien entre le clavier et le programme qui recevra les commandes. Il transforme la frappe de certaines touches du clavier en un "code d'échappement" que le programme saura comment interpréter.
Ces codes d'échappements sont définis dans des standards comme le VT100.

L'utilité d'un terminal par rappel à une ligne de commande "brute" est bien évidemment de pouvoir exécuter des programmes interactifs comme Vim, Emacs, top, nethack et bien d'autres... c'est pour dire à quel point ils sont devenus indispensables ;-)
Même si la plupart des backdoors existantes n'offrent pas le support des ttys, quelques-unes le proposent avec plus ou moins de réussite.

Canonique or not canonique ?
Dans la plupart des cas, elles sont inutilisables telles quelles car il n'y a pas de client associé. L'utilisation de netcat pour s'y connecter ne permet pas de profiter du terminal côté serveur car netcat ne gère pas le tty. Notamment netcat lit les données tappées de façon canonique (attente d'une fin de ligne pour traiter les données) alors qu'un terminal les lit de façon non-canonique : les données sont traitées à chaque frappe du clavier.
Le mode non-canonique est indispensable pour les programmes cités plus tôt (par exemple pouvoir taper simplement q pour quitter top sans avoir à appuyer sur entrée).

Toi comprendre ce que moi dire ?
Vient alors un autre problème : le terminal client et le terminal serveur doivent parler la même langue. Une bonne partie des lnormes existantes sont compatibles car basées sur les codes d'échappement ANSI mais ce n'est pas toujours le cas.
Le standard utilisé est habituellement défini par la variable d'environnement TERM sous le shell.

Deux solutions sont possibles pour faire dialoguer correctement le client et le serveur :
  • Utiliser un client et un serveur utilisant le même langage
  • Utiliser un protocole permettant au client et au serveur de se mettre d'accord sur le type de terminal à utiliser

Implémentations
La première solution est celle utilisée dans sorshell.c. Le client et le serveur se voient fixer la variable d'environnement TERM à "vt100". Le client est configuré en mode non-canonique est sans échos (voir plus loin).

L'autre solution a fait son apparition il y a maintenant longtemps avec le protocole telnet. Par ce protocole, le client et le serveur négocient le type de terminal ainsi que d'autres règles de transmission.
L'implémentation de telnet dans un logiciel ne facilitant pas les choses, certains ont préférés ruser comme pour Tiny Shell qui se contente de l'envoi du type de terminal utilisé.
Il existe tout de même un code en perl où toute la couche telnet est utilisée smile

Terminal client
Un dehors du mode canonique (ou non), le client ne doit pas interpréter certains signaux comme un Ctrl+C afin de les traduire pour les transmettre au serveur.
Les deux parties doivent aussi se mettre d'accord sur qui renvoie les données. Quand on frappe sur une touche depuis le client, on s'attend à ce qu'elle apparaisse à l'écran. Soit elle s'affiche au moment où on appuie sur la touche, soit quand le serveur nous la renvoit (il a fait un "écho"), soit les deux en même temps (dans ce cas là, le caractère s'affiche en double).
Le serveur renvoyant généralement plus de données que le client n'en envoit, le client est habituellement passé en non-écho et les caractères s'affichent alors lorsque le serveur les renvoit au client (le client ne renvoie pas les données du serveur).
Dans une session telnet, la taille de la fenêtre de terminal sera aussi négociée (généralement 80x25 par défaut).

Le rôle du terminal client consiste à lire les touches tapées sur l'entrée standard pour les restransmettre vers le serveur, le tout avec les caractéristiques citées précédemment.
Sa programmation est simple. Il faut d'abord récupérer une structure termios définissant le modèle du terminal actuel à l'aide de la fonction tcgetattr(), modifier cette structure selon nos souhaits pour enfin mettre à jour le terminal avec les nouveaux paramêtres (fonction tcsetattr()).

Le code utilisé en C dans sorshell est le suivant :
struct termios deftt,tt;

/* terminal init */
tcgetattr(0, &deftt); /* récupére les préférences du terminal */
tt = deftt; /* copie */
tt.c_oflag &= ~(OPOST);
tt.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); /* pas d'écho, ne recoit pas les signaux, mode non-canonique... */
tt.c_iflag &= ~(ICRNL); /* transforme les CR en NL */
tt.c_cc[VMIN] = 1; /* lit caractère après caractère */
tt.c_cc[VTIME] = 0; /* pas de délai de lecture */
tcsetattr(0, TCSADRAIN, &tt); /* mettre à jour le terminal */
...
tcsetattr(0, TCSADRAIN, &deftt); /* restauration */

stty
Sous Linux, la commande stty permet de modifier les paramêtres d'un terminal. On peut alors utiliser un client "brut" en modifiant le terminal courant, ce qui donnerait un script dans ce style :
stty -icanon -echo -isig
netcat serveur 9999 -v
stty icanon echo isig
clear

Un shell avec terminal pour pas un rond
Petite astuce trouvée sur pentestmonkey : on peut utiliser la richesse des API Python pour associer facilement un shell avec un pseudo-terminal.
La commande pour lancer un serveur fonctionnant avec le script client précédent sera la suivante :
netcat -e "python -c 'import pty; pty.spawn(\"/bin/sh\")'" -v -l -p 9999

Terminal serveur
Du côté du serveur, le terminal doit fonctionner en sens inverse : lire les codes d'échappement reçus et les transformer en signaux, déplacement de curseur etc.
La programmation du serveur est plus compliquée et se base sur l'utilisation d'un pseudo-terminal dont le fonctionnement est proche des pipe.
On a alors un pseudo-terminal (pty) maître sur lequel on écrit les données reçues par le réseau et un pty esclave (pts) qui reçoit les données et les filtre avant de les renvoyer au shell.

La programmation suivra la procédure suivante :
  1. Ouverture du périphérique /dev/ptmx qui va chercher un pty maître disponible et retourner son descripteur de fichier
  2. Appel à la fonction grantpt() pour modifier les droits d'accès au terminal esclave (le pts)
  3. Utilisation de la fonction unlockpt() pour dévérouiller le pts
  4. ptsname() permettra ensuite de récupérer le nom du pts
  5. Ouverture du pseudo-terminal esclave

Pour terminer, le programme fait un fork(), le processus fils redirige ses entrées/sorties vers le pts avant de lancer un shell et le processus père gère les entrées réseau.
C'est ce que fait sorshell.c (mis à part qu'il le fait en bas niveau).

Tunneling à travers Jabber (GTalk)
En Python, le nombre d'étapes a été fortement réduites et côté serveur, il suffit d'appeller les fonctions openpty() et fork() du module pty.
Pour illustrer tout cela j'ai développé un petit programme permettant de faire passer un shell/tty à travers une session XMPP. Le code se base sur l'utilisation des serveurs GoogleTalk mais peu être facilement modifié. Il utilise la librairie xmpppy
Le protocole XMPP ne permettant pas de faire facilement transiter des données brutes, celles-ci sont envoyées sous leur forme hexadécimale (encodage pour le caractère 'A' sera '61'). Quelques temporisations ont dû être rajoutées car le serveur GTalk n'hésite pas à couper la communication si les envois successifs sont trop rapprochés.

Ca fonctionne plutôt bien, si ce n'est que c'est assez lent. J'ai réussi à éditer avec VIM, naviguer dans une page de manuel etc. Par contre il faut faire attention avec les commandes qui envoient continuellement des données à l'écran (c'est le cas de top) qui risquent de faire bloquer le programme dans une boucle (avec un top -n <nombre d'affichages> ça passe).

Télécharger jabbertunnel.tar.bz2

Références :
www.iakovlev.org
sorshell.c
emse.fr : Saisies de données au clavier
win.py (Terminal Emulator)
Python Library Reference : termios
Python Library Reference : pty

Lecture de lignes depuis un flux en C

, ,

Quand on a besoin de lire des données depuis un fichier ou un socket on sait parfois sur quoi on va tomber si les données suivent une structure prédéfinie mais dans d'autres cas on ne sait pas quelles données vont être lues.
Ce cas est assez simple à gérer avec des langages dits "haut niveau" mais c'est plus difficile en C.
Quand j'étudie des codes sources en C je voit généralement deux cas qui sont :
  • l'utilisation d'un énorme buffer pour récupérer les données suivi des opérations effectuées sur ce buffer
  • la lecture octet par octet du flux pour éviter de faire des opérations complexes sur le buffer

La première solution fonctionne correctement si le buffer est toujours plus grand que les données reçues mais dans la plupart des cas que j'ai observé, cette éventualité n'a pas été prise en compte ce qui peut provoquer de nombreux bugs.
La seconde solution est plus robuste mais peu performante sad

Comme j'ai du faire face à cette problèmatique récemment je vous donne que j'ai écrit pour lire des lignes depuis un fichier. Le programme lit des données depuis un fichier par blocs de 16 octets et affiche chaque ligne de texte.

On a les variables suivantes :
  • buffer : un buffer de 16 octets utilisé uniquement pour la lecture des données
  • current : un pointeur sur le caractère en cours de lecture dans le buffer
  • str : un pointeur vers une chaine de caractères allouée dynamiquement qui contiendra la ligne en cours de traitement
  • i : un index sur le caractère en cours sur la ligne en cours
  • j : un index sur le caractère en cours sur le buffer

Si vous avez tout suivi, voici le code :
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc,char *argv[])
{
  char *current;
  int fd;
  char *str;
  char buff[17];
  int i,j;
  int size;
  int len;

  fd=open("fic.txt",O_RDONLY);
  i=0;
  size=16;
  str=malloc(size);
  while(1)
  {
    len=read(fd,buff,16);
    if(len<1)
    {
      close(fd);
      return 1;
    }
    current=buff;
    j=0;
    while(j<len)
    {
      if(*current=='\n')
      {
        str[i]='\0';
	printf("%s.\n",str);
	i=0;
        free(str);
        size=16;
	str=malloc(size);
	current++;
      }
      else
      {
	str[i++]=*current++;
      }
      j++;
    }
    if(i>0)
    {
      size+=16;
      str=realloc(str,size);
    }
  }
  free(str);
  close(fd);
  return 0;
}

Il doit y avoir quelques modifications à faire pour adapter la boucle à vos besoins. Je n'ai pas observé de bugs durant mes tests. N'hésitez pas à faire des remarques.

Deux de plus

, , , ...

Deux nouveaux codes de ma composition smile

Tout d'abord dns-sniff.c est un sniffeur qui affiche les noms de domaine demandés par votre machine. Il utilise la libpcap et surveille les requêtes DNS ipv4 émises (queries).
Ca peut servir à détecter des anomalies ou tout simplement les connexions plus ou moins dissimulées provoquées par les systèmes de régies et de tracking sur les sites Internet.
Je m'en sers d'ailleurs pour perfectionner mon fichier urlfilter.ini pour Opera wink
Le code n'est pas à 100% de moi, je me suis basé sur un petit sniffer appelé katsniff.c

L'autre code, c'est PHParanoid, un forum PHP/MySQL qui se concentre principalement sur la sécurité et l'anonymat de ses utilisateurs.
Il y a par exemple un système de détection de spam, un anti-brute-force au niveau du login et des protections contre les attaques par CSRF.
Dans l'ensemble je suis assez content de l'aspect extérieur du forum, même si il n'est pas super sexy par défaut (j'ai hésité à le nommer BunkerBB bigsmile ) Mais certains savent faire des miracles avec les CSS alors on peut surrement en faire quelque chose de (plus) beau.
Par contre le nombre de fonctionnalités reste très limité (aucun système de BBcode à l'heure actuelle)... conséquence directe : il intéresse peu de monde (deux téléchargements à l'heure actuelle :/ )

Sinon SUSE 10.1 c'était bien ; openSUSE 10.2 c'est mieux wink

Mise à jour : PHParanoid 0.2 en ligne

Programmation pour adultes

, , , ...

Attention le cours de programmation suivant peut heurter la sensibilité des plus jeunes p

Read more...

Coding is sexy

, , , ...

"99 Bottles of Beer" est le titre d'une chanson EtatsUnienne que chantent les enfants en colonie de vacance ou les scouts de là-bas.
C'est leur version de notre "X kilomètres à pied, ça use, ça use... X kilomètres à pied, ça use les souliers".
Wikipedia : 99 Bottles of Beer

Mais 99 Bottles of Beer c'est aussi un site qui ravira tout programmeur smile
L'objectif : Avoir pour chaque langage de programmation existant au moins un code qui va afficher les paroles de la chanson.
Une simple boucle ne suffit pas à régler le problème. En effet il faut partir de 99 avec :

99 bottles of beer on the wall, 99 bottles of beer.
Take one down and pass it around, 98 bottles of beer on the wall.


et continuer en décrémentant jusqu'à :

1 bottle of beer on the wall, 1 bottle of beer.
Take one down and pass it around, no more bottles of beer on the wall.


La dernière partie étant :

No more bottles of beer on the wall, no more bottles of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.


Bref il y a quelques difficultées à prendre en compte smile
Lyrics of the song 99 Bottles of Beer.

A l'heure où je tappe ces lignes, 99 Bottles of Beer propose 959 variations !!
On trouve de tout comme langages, du plus ancien au plus récent, du plus au niveau au plus bas niveau, du plus court au plus long...
Certains codes font peur, notemment Brainfuck et ses dérivés : Fromage, l33t ninja ou encore Malboge yikes

On peut trouver de véritables LPNI (langage de programmation non-identifié lol ) comme Shakespeare. L'objectif de ce langage de programmation étant, comme indiqué sur le site officiel, que le code source ressemble à une pièce de Shakespeare ^^

Il y a un bon nombre de macros pour différents logiciels (Word, Excell mais aussi Vim) ou encore des langages utilisés plus généralement pour la mise en page (nroff qui sert à créer les manpages)

Certains sont allés très très loin dans le modèle objet (regardez cette version Java et cette version Python qui rends possible l'internationalisation de la chanson) up
J'ai également apperçu un code qui utilise une librairie 'speech' pour faire en sorte que le programme vous récite la chanson dans vos haut-parleurs sing

Le site héberge un très bel exemple d'obfuscation de code en Perl. Chose qui se fait naturellement avec certains langages (décidemment je ne comprendrais jamais rien à sed bomb )
Pour finir je terminerais sur ce très beau module kernel en C et ce petit code Python (de la triche ? naaaaaan ! rolleyes )
Que les amateurs d'Assembleur se rassurent, il y a un bon nombre de déclinaisons pour ce langage barbare
Il suffit d'explorer le site wink

Ext2 et les effacements sécurisés

, , , ...

Dans ma solution de l'épreuve forensics du challenge Securitech 2006, vous avez peut-être été étonné de savoir à quel point il était aisé d'étudier une image d'un disque et d'en extraire des données, ce même si les fichiers ont été effacés.

La question que l'on peut se poser est "Pourquoi des mesures n'ont pas été prises au niveau des systèmes d'exploitation pour garantir une suppression efficace des données ?"
Personnellement je vois deux réponses. La première est tout simplement que si l'effacement sécurisé peut-être considéré comme une caractéristique utile (voire nécessaire), la possibilité de pouvoir récupérer des données malencontreusement effacés est toute aussi importante (et à surrement sauvé un bon nombre de personnes).
La seconde réponse pourrait être l'existence d'un lobby puissant qui a tout intérêt à ce que les particuliers n'aient pas de moyens d'effacer leurs données les plus sensibles (organisations gouvernementales etc).
Vous avez peut-être entendu parler de cette rumeur de backdoor dans Windows Vista demandée par le gouvernement britanique et qui lui aurait permis de passer à travers les systèmes de cryptage inclus dans le prochain Windows. Cette rumeur lancée par le site de la BBC a très vite été démentie mais à tout de même eu le temps de faire parler d'elle et de faire réfléchir.

L'hypothèse que des pressions soient exercées sur Microsoft pour empécher l'effacement sécurisé ne me parrait si extraordinaire que ça...
Linux propose bien une méthode d'effacement sécurisée par le biais de l'attribut 's' que l'on peut fixer avec la commande chattr (voir la page de manuel).

Afin de comprendre le fonctionnement du système de fichiers ext2, j'ai crée une petite partition sur un vieux disque et ai effectué mes tests en "boîte noire" à l'aide du live cd grml dont j'ai déjà parlé et qui contient différents outils d'analyse forensics ou de récupération de données.

Après un cfdisk obligatoire, j'ai écrasé le contenu de la partition puis l'ai formaté en ext2 :
# dd if=/dev/zero of=/dev/hda1
# mke2fs /dev/hda1

Cette opération me donne un système ext2 tout ce qu'il y a de plus standard (sans journalisation en plus).
A l'aide de la commande strings qui extrait les chaines de caractères d'un fichier, on peut déjà connaître le contenu du disque :
# strings /dev/hda1
lost+found

Créons maintenant un répertoire et un fichier. Ces données nous servirons de base pour la suite :
# mount /dev/hda1 /mnt
# ls /mnt/
.  ..  lost+found
# mkdir /mnt/mondir
# umount /mnt
# strings /dev/hda1
lost+found
mondir

# echo "ceci est un test" > /mnt/mondir/fichier.txt
# strings /dev/hda1
lost+found
mondir
ceci est un test
fichier.txt

Lors de mes tests j'ai passé beaucoup de temps à démonter puis remonter le système de fichier... pour gagner de la place je ne marque pas les commandes mount et umount qui seraient trop nombreuses
Utilisons les commandes du SleuthKit pou avoir un apperçu des informations présentes sur le disque :
# fls -r /dev/hda1
d/d 11: lost+found
d/d 1673:       mondir
+ r/r 1674:     fichier.txt
# fls -rm / /dev/hda1
0|/lost+found|0|11|16832|d/drwx------|2|0|0|0|12288|1149019267|1149019267|1149019267|1024|0
0|/mondir|0|1673|16877|d/drwxr-xr-x|2|0|0|0|1024|1149019487|1149019583|1149019583|1024|0
0|/mondir/fichier.txt|0|1674|33188|-/-rw-r--r--|1|0|0|0|17|1149019583|1149019583|1149019583|1024|0
# icat /dev/hda1 1674
ceci est un test

Notre fichier.txt possède le numéro d'inode 1674 et sa taille est de 17 octets.

Note : En analyse forensic on peut classer les données contenues sur le disque en plusieurs catégories.
  • Les données concernant le système de fichier lui-même : sa structure, son type, les caractéristiques qu'il propose
  • Le contenu des fichiers, organisées sous forme de blocks de taille fixe. Un block possède un état qui permet de savoir si un fichier l'utilise (allocated) ou s'il est libre pour créer un éventuel fichier (free ou unallocated)
  • Les métadonnées (metadata) : ce sont les données qui décrivent un fichier. Sous Linux on parle d'"inode". On y trouve les permissions, les dates de modification, dernier accès et changement d'état ainsi que les pointeurs vers les blocks de données que l'on a vu précédemment.
  • La catégorie des noms de fichiers. Il s'agit principalement de structures décrivant les répertoires. On peut décrire un répertoire comme un tableau dont chaque entrée contient un nom de fichier, sa longueur (la longueur du nom, pas du fichier lui-même) et le numéro d'inode correspondant au fichier
  • La catégorie applicative qui est la plupart du temps un fichier de journalisation. Cette catégorie ne nous intéresse pas puisque par défaut ext2 n'offre pas de système de journalisation

Les trois catégories ne nous retiendrons ici sont les noms de fichiers, les contenus des fichiers et enfin les métadonnées qui font le lien entre ces deux catégories.

Passons maintenant à l'action et effaçons notre fichier.txt :
# rm /mnt/mondir/fichier.txt
# strings /dev/hda1
lost+found
mondir
ceci est un test
fichier.txt

A première vue le résultat est plutôt navrant puisque l'on retrouve le nom du fichier effacé ainsi que son contenu ^^
Voyons ça de plus près :
# fls -rm / /dev/hda1
0|/lost+found|0|11|16832|d/drwx------|2|0|0|0|12288|1149019267|1149019267|1149019267|1024|0
0|/mondir|0|1673|16877|d/drwxr-xr-x|2|0|0|0|1024|1149019777|1149019780|1149019780|1024|0
0|/mondir/fichier.txt (deleted)|0|0|0|-/----------|0|0|0|0|0|0|0|0|1024|0
# ils /dev/hda1
class|host|device|start_time
ils|shirley||1149020073
st_ino|st_alloc|st_uid|st_gid|st_mtime|st_atime|st_ctime|st_mode|st_nlink|st_size|st_block0|st_block1
1|a|0|0|1149019267|1149019267|1149019267|0|0|0|0|0
1674|f|0|0|1149019583|1149019583|1149019780|100644|0|17|9217|0
# icat /dev/hda1 1674
ceci est un test

La commande fls s'intéresse aux noms de fichiers, elle explore les tableaux de répertoires et est en mesure de détecter les fichiers qui ont été effacés.
Comme écrit dans Forensic Discovery de Wietse Venema et Dan Farmer, la suppression d'un fichier a les conséquences suivantes sur la catégorie noms de fichiers :

When a file is deleted, the directory entry with the file name and inode number is marked as unused. Typically, the inode number is set to zero, so that the file name becomes disconnected from any file information. (...)
Names of deleted files can still be found by reading the directory with the strings command.


Nous avons donc perdu le lien entre le nom de fichier et ses métadonnées... heureusement la commande ils nous permet de récupérer la liste des inodes correspondant à des fichiers effacés.
La commande icat quand à elle nous affiche le contenu du fichier correspondant à l'inode qui lui passe en paramêtre.
Le livre de Venema et Farmer nous informa aussi que :

With 2.2 Linux kernels, the Linux Ext2fs (second extended) file system marks the directory entry as unused, but preserves the connections between directory entry, file attributes and file data blocks.


Autant dire que l'analyse forensics doit être très facile avec les noyaux 2.2... Heureusement pour nos données la situation a changée smile

Si l'on est parvenu à récupérer le contenu de notre fichier effacé avec icat, c'est parce que la taille du fichier est resté stockée sur le disque...
Voyons ce qu'il se passe si on tronque le fichier avant de l'effacer. Pour cela j'ai écris quelques lignes de code (trunc.c) :
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc,char *argv[])
{
  int fd;
  if(argc!=2)exit(1);
  fd=open(argv[1],O_WRONLY);
  ftruncate(fd,0);
  close(fd);
  unlink(argv[1]);
  return 0;
}

On compile et on lance :
# gcc -o trunc trunc.c -Wall -W -pedantic
# ./trunc /mnt/mondir/fichier.txt
# ils /dev/hda1
class|host|device|start_time
ils|shirley||1149021092
st_ino|st_alloc|st_uid|st_gid|st_mtime|st_atime|st_ctime|st_mode|st_nlink|st_size|st_block0|st_block1
1|a|0|0|1149020576|1149020576|1149020576|0|0|0|0|0
1674|f|0|0|1149021076|1149020953|1149021076|100644|0|0|0|0
# icat /dev/hda1 1674
#
# strings /dev/hda1
lost+found
mondir
Ceci est un test
fichier.txt

Cette fois la commande icat n'a pas pu récupérer le contenu du fichier. Mais le contenu du fichier est toujours lisible sur le disque. Un outil comme foremost est tout à fait capable d'extraire des fichiers en se fiant uniquement à leur entête.

Intéressons-nous maintenant au nom du fichier effacé toujours présent sur le disque. En repartant de notre base et en renommant deux fois le fichier avec des noms de même taille on obtient un résultat satisfaisant :
# mv /mnt/mondir /mnt/xxxxxx
# strings /dev/hda1
lost+found
mondir
xxxxxx
fichier.txt
Ceci est un test
# mv /mnt/xxxxxx /mnt/zzzzzz
# strings /dev/hda1
lost+found
zzzzzz
xxxxxx
Ceci est un test
fichier.txt

Nous sommes sur la bonne voie. On retrouve en effet ces principes dans le code source de la commande d'effacement sécurisée shred (shred.c).
Le ftruncate est présent dans la fonction principale do_wipefd
La technique utilisée pour les noms de fichiers est en revanche différente (faites un shred -u -v fichier pour comprendre vite fait le mécanisme) :

/*
* Repeatedly rename a file with shorter and shorter names,
* to obliterate all traces of the file name on any system that
* adds a trailing delimiter to on-disk file names and reuses
* the same directory slot.


Attaquons-nous maintenant au contenu du fichier... une seule solution : écraser les données par d'autres données.
# echo blahblah > /mnt/zzzzzz/fichier.txt
# strings /dev/hda1
lost+found
zzzzzz
xxxxxx
blahblah
fichier.txt
# perl -e "print 'A'x400" > /mnt/zzzzzz/fichier.txt
# strings /dev/hda1
lost+found
zzzzzz
AAAAA[...]AAAAA
fichier.txt
# echo blahblah > /mnt/zzzzzz/fichier.txt
# strings /dev/hda1
lost+found
zzzzzz
xxxxxx
blahblah
fichier.txt
# perl -e "print 'B'x4100" > /mnt/zzzzzz/fichier.txt
# strings /dev/hda1
lost+found
zzzzzz
BBBB[...]BBBBBB
fichier.txt
# echo blahblah > /mnt/zzzzzz/fichier.txt
# strings /dev/hda1
lost+found
zzzzzz
blahblah
BBBB[...]BBBBBB
fichier.txt

Les résultats semblaient satisfaisants au début... mais on s'apperçoit à la fin qu'il faut plus de données pour écraser la slack space et par conséquent l'ancien contenu du fichier.
Tout à l'heure je vous ai dis que le système gérait les données par blocks. Il ne peut pas manipuler chaque fichier à l'octet près, ce serait une énorme perte de temps.
A la création d'un fichier le système va allouer plusieurs blocks de données. D'après mes tests Linux en réserve au minimum deux. Le code suivant permet de connaître l'espace mémoire réservé pour un fichier (getstat.c) :
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>

int main(int argc,char *argv[])
{
  struct stat mystat;
  if(argc!=2)exit(1);
  if(stat(argv[1],&mystat)<0)exit(1);
  printf("Taille totale en octets: %d\nTaille de bloc pour E/S: %d\nNombre de blocs alloués: %d\n",
        mystat.st_size, mystat.st_blksize, mystat.st_blocks);
  return 0;
}

Voyons ce que ça donne :
# echo blahblah > /mnt/zzzzzz/fichier.txt
# ./getstat /mnt/zzzzzz/fichier.txt
Taille totale en octets: 9
Taille de bloc pour E/S: 4096
Nombre de blocs alloués: 2

Conlusion : pour un fichier de seulement 9 octets, le système en a réservé 8192 (2 x 4096). Il est possible de fixer cette taille lors de la création de la partition ext2. 4096 semble être un bon compromis entre vitesse d'accès et gaspillage de place.
Pour effacer de façon sûre notre fichier il faut donc écraser ces deux blocks de 4096 octets, le renommer plusieurs fois et rammener sa taille à zéro avant de l'effacer...
J'ai écris le code suivant pour réaliser cette opération (secrm.c) :
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc,char *argv[])
{
  int fd;
  char *buff;
  char *path1;
  char *path2;
  int i;
  struct stat mystat;
  if(argc!=2)exit(1);
  if(stat(argv[1],&mystat)<0)exit(1);
  if(!S_ISREG(mystat.st_mode))exit(1);
  printf("Taille totale en octets: %d\nTaille de bloc pour E/S: %d\nNombre de blocs alloués: %d\n",
        mystat.st_size, mystat.st_blksize, mystat.st_blocks);
  buff=(char*)malloc(strlen(basename(argv[1])));
  path1=strdup(argv[1]);
  path2=strdup(argv[1]);
  memset(buff,'x',strlen(basename(path1)));
  sprintf(path1,"%s/%s",dirname(path1),buff);
  printf("filename -> %s\n",path1);
  link(argv[1],path1);
  unlink(argv[1]);
  memset(buff,'z',strlen(basename(path2)));
  sprintf(path2,"%s/%s",dirname(path2),buff);
  link(path1,path2);
  unlink(path1);
  printf("filename -> %s\n",path2);
  free(path1);
  free(buff);
  buff=(char*)malloc(mystat.st_blksize);
  fd=open(path2,O_WRONLY);
  printf("Overwriting...\n");
  for(i=0;i<mystat.st_blocks;i++)
  {
    memset(buff,i,mystat.st_blksize);
    write(fd,buff,mystat.st_blksize);
  }
  free(buff);
  printf("Truncating...\n");
  fsync(fd);
  ftruncate(fd,0);
  close(fd);
  printf("Unlinking...\n");
  unlink(path2);
  free(path2);
  printf("Done !\n");
  return 0;
}

On reprends notre base et on teste :
# strings /dev/hda1
lost+found
mondir
Ceci est un test
fichier.txt
# ./secrm /mnt/mondir/fichier.txt
Taille totale en octets: 17
Taille de bloc pour E/S: 4096
Nombre de blocs alloués: 2
filename -> /mnt/mondir/xxxxxxxxxxx
filename -> /mnt/mondir/zzzzzzzzzzz
Overwriting...
Truncating...
Unlinking...
Done !
# strings /dev/hda1
lost+found
mondir
zzzzzzzzzzz
xxxxxxxxxxx
# fls -r /dev/hda1
d/d 11: lost+found
d/d 1673:       mondir
+ r/- * 0:      zzzzzzzzzzz
+ r/- * 0:      xxxxxxxxxxx
# ils /dev/hda1
class|host|device|start_time
ils|shirley||1149027322
st_ino|st_alloc|st_uid|st_gid|st_mtime|st_atime|st_ctime|st_mode|st_nlink|st_size|st_block0|st_block1
1|a|0|0|1149027164|1149027164|1149027164|0|0|0|0|0
1674|f|0|0|1149027269|1149027250|1149027269|100644|0|0|0|0
# icat /dev/hda1 1674
#

Mission accomplie ! yes

Evidemment toute implémentation en kernel land serait bien plus efficace...
Sur le sujet des anti-forensics et de l'effacement sécurisé, la référence reste les travaux d'un certain The Grugq.
Je vous conseille vivement The Defiler's Toolkit de sa création qui contient un code pour effacer les noms des fichiers effacés sur votre disque (klismafile) ainsi qu'un code qui va s'occuper des métadonnées (necrofile).

Bibliographie :
File System Forensic Analysis de Brian Carrier
Forensic Discovery de Wietse Venema et Dan Farmer
shred.c par Colin Plumb
Secure Data Deletion for Linux File Systems
The Ext2 File System sur The Linux Tutorial
Secure Deletion of Data from Magnetic and Solid-State Memory de Peter Gutmann
Can Intelligence Agencies Read Overwritten Data? A repsonse to Gutmann. de Daniel Feenberg
the grugq - secure deletion patch, kernel 2.4.24
Defeating Forensic Analysis on Unix par the grugq

Enfin pouvoir dormir... zzz

kikoo lol

, , ,

Voici un petit programme en rapport avec un précédent billet [fr] et que j'ai baptisé "kikoo lol" (on se demande pourquoi)
/* d3vl00p kik00 l0l xpldr!!1!1!1!1!1 ^_^ rofl */
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<time.h>
#define l0ol int
#define xpldr main
#define asv rand()
#define kikoO(x)printf("%c\n",x)
#define stfu time
#define omfg while(
#define mdr(x)printf("En %d coups\n",'\xD'-3-x)
#define l4m3 scanf("%d",
#define rofl srand
#define dtc 2147483647

l0ol lol,lo1,
l01,bizz
,p;
xpldr()
{rofl(stfu(NULL));
lo1=('^'^'_')+(l0ol)
(100.0*(asv/(dtc+1.0))),lol=0x0a;
omfg l01!=lo1 && lol--)/*
rofl b(.)(.)bi3s xpldr();
// stfu(); */ l4m3 &l01),bizz='<',
(l01>lo1)?kikoO(bizz):((l01<lo1)?kikoO
(++bizz+('.'/'.'))
:0);
dtc?!!!11|!1111 :p
;mdr(lol)
;}

Et vive le préprocesseur de gcc !! :D

Apprenez à écrire du code illisible

, ,

Pourquoi faire ?
  • Pour le plaisir
  • Pour cacher certaines instructions secrêtes
  • Pour être sûr que vous serez le seul à comprendre votre code (quoique c'est même pas sûr)
  • Pour participer à l'IOCCC
  • tout un tas de raisons plus originales les unes que les autres

Et afin de vous donner un point de départ, un certain SysSpider a crée un document qui vous donne quelques astuces pour rendre illisible vos codes en C.

C'est plutôt sympathique à lire et ça m'a permis de découvrir de nouvelles choses sur ce merveilleux langage qu'est le C smile

J'ai fait un petit code pour vous (oui je débute p ) :
#include<stdio.h>
#define p(i) putc(i,stdout)
main(a)
{a=11;char s[]="emsp\
X!pmmfI";while(a--)
p(--(a[s]));p(0x0d),
p(13^6+1);}


Page Wikipedia pour Obfuscated Code

Coup de gueule contre l'expansion du Ouebe

, , , ...

Il y a quelques jours je cherchais de la documentation sur les expressions régulières en C. Les manpages étant assez difficile à comprendre, je me suis rabattu sur Google.

Force est de constater que rechercher des astuces de programmation par les moteurs de recherche n'est plus ce que c'était auparavant.
Tout d'abord les résultats sont noyés au milieu de sites, forum etc dédiés au langage PHP... La simplicité et la popularité de ce langage est telle que le nombre de sites ne cesse d'augmenter rendant obligatoire l'utilisation des exceptions dans la syntaxe de Google.
Je repart donc avec des "-php" pour exclure un bon nombre de pages... Seulement "php" peut être simplement l'extension de la page indexée... Donc d'un côté on filtre un bon nombre de pages sans rapport avec le C mais on retire par la même occasion des pages qui pouvaient nous intéresser.
De plus, PHP étant très proche du C, on n'est pas au bout de nos misères...

Finalement je décide de restreindre mes recherches aux codes sources (fichiers avec l'extension .c) en utilisant l'option "filetype:c"
C'était sans compter sur... les CVS. Alors là vu le nombre autant rechercher un poil de barbe blond dans une meule de foin de 500 kilos !

Dégouté, j'ai fermé mon navigateur et j'ai finalement réglé mon problème sans utiliser les expressions régulières.

Quoiqu'il en soit cette histoire de PHP et de CVS devrait vraiment être prise en considération par les moteurs de recherche.