Affichage des articles dont le libellé est padding. Afficher tous les articles
Affichage des articles dont le libellé est padding. Afficher tous les articles

jeudi 6 janvier 2011

NDH Steganographie Epreuve 3

J'ai décidé pendant mon temps libre de m'attaquer aux challenges publics de la nuit du hack 2010 qui a eu lieu le 19 Juin 2010. Comme ces challenges se déroulent sur une nuit, ils sont relativement rapides à faire et je dois dire très bien faits. Je félicite donc les concepteurs de ces challenges, car ils sont très intéressants sans être trop prise de tête. L'épreuve 3 de steganographie illustre parfaitement mon post sur le padding BMP. On ressort donc mon petit programme python que l'on peut trouver dans ce post et on l'exécute sur notre image.
time0ut# ./bmp.py space.bmp
Taille padding : 842
Padding non nul !
Ca ne fait pas de doute, ça sent l'information cachée dans le padding à plein nez ! Le problème c'est qu'en stéganographie il peut être facile de savoir si oui ou non une information est cachée dans le support, par contre savoir extraire cette information est d'une toute autre difficulté.

On va commencer doucement et simplement afficher le contenu de ce padding, avec un peu de chance on trouvera quelque chose de connu. Pour cela, je m'appuie sur la classe BMP codée dans mon post sur le padding.
...
img = BMP(sys.argv[1]);
print "".join(bmp.padding());
Et on execute le programme !
time0ut# ./ep3.py space.bmp
bGUgaGFzaCBlc3QgOiA2Njk3NmI1ZDNiYWNjNzQwOWNkODNiNGIzMTM5NDcxYw==
Hum, ça sent bon. On voit clairement que c'est de la base64, l'extraction de l'information ne sera donc pas difficile ! On modifie le programme.
...
img = BMP(sys.argv[1]);
print base64.b64decode("".join(bmp.padding()));
Et on conclut !
time0ut# ./ep3.py space.bmp
le hash est : 66976b5d3bacc7409cd83b4b3139471c
Voilà résolution terminée !

mercredi 5 janvier 2011

Padding BMP

Suite à mon introduction sur le concept de la stéganographie, voilà un exemple appliqué sur les images bitmap plus communément appelées BMP. Cette technique assez basique fonctionne exclusivement sur les images BMP car elle utilise une des particularités de ce format : le padding (le bourrage en français). La norme BMP dit que chaque ligne de l'image doit être codée par un nombre d'octets multiple de 4. Si ce n'est pas il faut combler le manque d'octets avec la valeur nulle. On appelle ça le padding.
  On se retrouve donc avec un fichier possédant des octets qui n'apportent aucune information et qui sont même complètement ignorés par la totalité des programmes. Il est donc possible d'utiliser ce padding pour cacher des données à l'intérieur. L'image apparaitra totalement inchangée dans votre viewer préférée. Cette technique très simple n'est possible que si une ligne n'est pas codée avec un nombre d'octets multiple de 4 bien entendu, sinon le padding sera inexistant. On peut donc écrire la formule suivante qui donnera la taille du nombre d'octet que l'on peut cacher dans une image BMP en utilisant cette technique :
Nb_Bytes = L*((4-((C*S)%4))%4)

L : Nombre de lignes de l'image

C : Nombre de colonnes de l'image

S : Taille en octet d'un pixel

Petit exemple : la première image ici est l'image originale.

 La deuxième image contient un message caché de 173 octets (on aurait pu aller jusqu'à 508 !).
 
 Comme on peut le voir elles ont exactement le même aspect et font exactement le même poids. Le programme python suivant permet de savoir si oui ou non l'image que l'on passe en paramètre ne contient que des octets nuls dans son padding (attention à n'utiliser que sur des images BMP non indexées).
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys,struct

class BMP:
 def __init__(self,path):
  f = open(path,"rb")
  data = f.read()
  f.close()

  names = ["magick","size_file","creator1","creator2","start_data","header_image","width","height","plan","colors","compression","raw_size","horizontal","vertical","color_palette","important_colors"]

  values = struct.unpack('<2sLHHLLLLHHLLLLLL',data[:54])
  self.data = data[54:]

  self.header = dict()

  for i in range(len(names)):
   self.header[names[i]] = values[i]

  # On stocke quelques éléments pour ne pas être obligé de toujours les calculer
  self.size_pixel = self.header['colors']/8
  self.padding_size = self.padding_size()

 # Retourne la taille du padding par ligne
 def padding_size(self):
  return ((4-(self.header['width']*self.size_pixel)%4)%4)

 # Retourne un tableau contenant le padding dans le fichier bmp
 def padding(self):
  r = []

  if self.padding_size != 0:
   start = 0
   for row in range(self.header['height']):
    start += (self.size_pixel*self.header['width'])
    r += struct.unpack('c'*self.padding_size,self.data[start:start+self.padding_size])
    start += self.padding_size

  return r

if len(sys.argv) != 2:
 print "Manque le fichier bmp"
 sys.exit(1)

bmp = BMP(sys.argv[1])
print "Taille padding : %i" % (bmp.padding_size*bmp.header["height"])
for e in bmp.padding():
 if e != 0:
  print "Padding non nul !"
  sys.exit(0)

print "Padding nul"
sys.exit(0)
Cette technique à l'avantage de ne pas du tout altérer l'image, et de ne pas modifier sa taille. Cependant, outre le fait qu'elle soit très basique, il faut bien faire attention aux informations cachées dans le padding. La simple ouverture de l'image dans un fichier texte peut révéler des parties de l'information cachée, qui se trouvera en clair. Un élément important avec cette technique à préciser est la structure d'un fichier BMP. Contrairement à la plupart des formats d'images les lignes sont stockées dans l'ordre inverse : la dernière ligne est codée, puis l'avant dernière, ... et enfin la première. De plus (mais ça n'a pas réellement d'impact ici), le codage d'un pixel se fait dans l'ordre bleu-vert-rouge, contrairement aux rouge-vert-bleu habituels.