Kiwazaru
Padawan d'un super escargot

Messages : 284
Sujets : 26
Points: 139
Inscription : Mar 2012
|
[Python] La stéganographie BMP
Yop tout le monde !
Alors voilà , il y a quelques temps j'ai commencé à apprendre le python.
Langage qui pour moi et pour pas mal d'autre est un langage qui nous permet d'écrire des programmes très rapidement en CTF par exemple !
Après une semaine de RTFM et d'apprentissage des bases, j'ai décidé de me lancer dans un projet qui à la base.. ne devait pas en être un
La stéganographie BMP 
J'avais voulu en faire une épreuve, mais après un fail total de celle-ci (seul Luxerails l'a pown x)), je décide de poster le code commenté.
Le code parle de lui même ! (J'ai essayé de le commenter le plus possible !)
Code PYTHON :
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
"""
Usage: {} <options>".format(sys.argv[0]))
Options:
-p <picture_container> <file_to_hide> <eof> <xorkey> <output> (Pour coder l'image)
-u <encoded_picture> <eof> <xorkey> <output> (Pour decoder l'image)
-ns <port> <eof> <xorkey> <inc> <dec> (Pour lancer le serveur)
-nc <ip> <port> <picture> <eof> <xorkey> <inc> <dec> (Pour lancer le client)
--help (to get help)"""
"""
Le principe de la stéganographie , globalement est de cacher une donnée , une information dans une autre donnée lambda..
Nous allons voir dans ce code, la stéganographie BMP (Il est aussi possible de le faire pour le PNG, mais ce code n'est pas compatible).
Le but de la steganographie de ce format d'image, est de coder l'image à cacher, dans l'image que nous appellerons contenante.
Pour une image BMP par exemple, son codage est le suivant: R V B Padding (Octet R, Octet V, Octet B, Octet de padding).
Un octet est codé sur 8 bits. Dans ces 8 bits, nous avons un bit de poid fort (MSB) et un bit de poid faible (LSB). Vous vous en douterez,
le LSB aura beaucoup moins d'impact sur la couleur si nous le mondifions, que le MSB !
Le principe de cette stéganographie est donc tout simplement, de coder 8 bits d'un octet de l'image à cacher, sur les LSB de 8 octets de l'image contenante !
Petite dédicace à khaled, on va dire que ce code est sous licence WTFPL hein <img src="https://n-pn.fr/forum/images/smilies/biggrin.png" alt="Big Grin" title="Big Grin" class="smilie smilie_4" />
"""
import sys, struct, socket
def header(original_picture):
if original_picture[0:2] != 'BM':
print "The picture is not a bitmap !"
sys.exit("System Exit with Error: Bad file !")
# Liste les informations principales du fichier.
print "- Name of file: " + str(sys.argv[2])
print "- Type of file: " + original_picture[0:2] + " ( 2 bytes )"
print "- Size of file: " + str(struct.unpack("<I", original_picture[2:6])[0] / 1000) + " kB (" + str(struct.unpack("<I", original_picture[2:6])[0]) + " bytes) ( 4 bytes ) "
print "- Width: " + str(struct.unpack("<I",original_picture[18:22])[0]) + " px ( 4 bytes )"
print "- Heigth: " + str(struct.unpack("<I", original_picture[22:26])[0]) + " px ( 4 bytes )"
print "- Compression: " + str(struct.unpack("<I", original_picture[30:34])[0]) + " ( 4 bytes )"
print "- Horizontal Resolution: " + str(struct.unpack("<I", original_picture[38:42])[0]) + " ( 4 bytes )"
print "- Vertical Resolution: " + str(struct.unpack("<I", original_picture[42:46])[0]) + " ( 4 bytes )"
def encode(original_picture, hidden_picture, eof_file, xor):
""" ENCODE THE PICTURE """
# Converti la chaîne en liste.
hidden_picture = list(hidden_picture)
original_picture = list(original_picture)
# Padding pour avoir une taille de fichier BMP qui convient.
hidden_picture += eof_file
if len(hidden_picture) % 4 != 0:
hidden_picture += 'F' * (4 -(len(hidden_picture) % 4)) # On ajoute x* l'octet "F".
# On vérifie la taille des images (*8 car nous allons coder un octet de l'image à cacher sur 8 octets de l'image contenante)
if len(original_picture) < len(hidden_picture) * 8:
print "The original picture is too short !"
sys.exit("Picture too short !")
# Les variables.. <img src="https://n-pn.fr/forum/images/smilies/smile.png" alt="Smile" title="Smile" class="smilie smilie_1" />
i = 0 # Offset courant de l'image à cacher
y = 0 # Offset courant de l'image contenante
buf = ''.join(hidden_picture)
hide = ""
while i < len(buf):
hide += struct.pack("I", (struct.unpack("I", buf[i:i+4])[0] ^ xor)) # On XOR notre image à cacher.
i += 4
hide = list(hide) # On fou notre hide qui contient notre image xoré
i = 0
while i < len(hidden_picture):
key = 0x01 # La clé commence à 0x01 et on applique une opération SHL << 1 pour obtenir les valeurs croissantes -> 0x01 0x02 0x04 0x08 0x10 0x20 0x40 0x80 pour obtenir les bits 1 par 1
for n in xrange(0, 8):
original_picture[y] = chr((ord(original_picture[y]) & 0xFE) | ((ord(hide[i]) & key ) >> n) ^ 1) # AND 0xFE est utilisé pour obtenir le LSB de l'octet courant de l'image originale, FE != FF, l'opération OR est utilisée pour placer le bit dans le LSB, le SHR >> n est utilisé pour obtenir un et un seul bit de l'image à cacher.
# On xor notre bit obtenu de l'octet courant de l'image à cacher et on le XOR par 1 pour l'inverser <img src="https://n-pn.fr/forum/images/smilies/smile.png" alt="Smile" title="Smile" class="smilie smilie_1" />
key = key << 1 # On applique une opération SHL pour obtenir une clé de plus en plus grande pour obtenir nos bits dans l'ordre: LSB -> MSB
y += 1 # On incrémente l'offset y pour coder sur le LSB de l'octet suivant
i += 1 # Quand nous avons codé un octet de l'image à cacher (for n in xrange(0, 8)<img src="https://n-pn.fr/forum/images/smilies/smile.png" alt="Smile" title="Smile" class="smilie smilie_1" /> on incrémente l'offset i pour passer à l'octet suivant
return ''.join(original_picture)
def decode(encoded_picture, eof_file, xor):
""" DECODE THE PICTURE """
encoded_picture = list(encoded_picture)
buf = ""
rFile = "" # L'image décodée sera contenue dans cette variable
bit_X = [] # Liste des bits (sera utilisé plus tard)
i = 0
try:
while i < len(encoded_picture):
for x in xrange(0, 4): # Pour pouvoir xor 4 octets par la suite
for n in xrange(0, 8): # On prend les 8 LSB d'un octet de l'image contenante
bit_X.append( str((ord(encoded_picture[i]) & 0x1) ^ 1) ) # On ajoute le bit xoré par 1 pour réinverser l'inversion du codage à bit_X...
i += 1 # On incrémente de 1 l'offset i pour obtenir l'octet suivant
buf += chr(int(''.join(bit_X)[::-1], 2)) # On obtient la valeur ASCII du binaire obten. [::-1] est utilisé pour inverser la chaine puisque nous avons codé LSB -> MSB <img src="https://n-pn.fr/forum/images/smilies/smile.png" alt="Smile" title="Smile" class="smilie smilie_1" />
bit_X = []
buf = struct.unpack("I", buf)[0]
buf = buf ^ xor # On xor 4 octets pour obtenir le bout d'image complètement décodé // On pourrait faire octet par octet pour éviter la boucle for x in xrange(0, 4).
buf = struct.pack("I", buf)
rFile += buf # On ajoute le bout d'image à la variable finale.
buf = ""
except:
""" EXCEPT !"""
return rFile[:rFile.rfind(eof_file)] # On retourne l'image jusqu'Ã l'occurence de son EOF, pour ne pas casser l'image <img src="https://n-pn.fr/forum/images/smilies/smile.png" alt="Smile" title="Smile" class="smilie smilie_1" />
# Cette partie était pour le challenge, le principe est d'envoyer un message codé dans l'image BMP et de faire croire à un envoi d'image <img src="https://n-pn.fr/forum/images/smilies/smile.png" alt="Smile" title="Smile" class="smilie smilie_1" />
def server(src_port, eof_file, xorkey, inc, dec):
""" SOCKET HERE """
# On obtient les arguments..
xorkey = int(xorkey)
src_port = int(src_port)
bRecv = b""
i = 0
# On défini la partie socket
#bRecv = sClient.recv(4096)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('0.0.0.0', src_port))
s.listen(5)
print "Socket listen on port: " + str(src_port)
sClient, info = s.accept()
print "Connexion reçue: " + str(info)
buf = ""
data = ""
while True:
while not "EOF" in buf: # Trick pour éviter les bugs rencontré avec la fonction recv() <img src="https://n-pn.fr/forum/images/smilies/smile.png" alt="Smile" title="Smile" class="smilie smilie_1" /> On se base sur le fait que le message ne contiendra pas EOF..
buf = sClient.recv(8048)
data += buf
data = data[:len(data) - 3] # On vire l'eof
# Le trick suivant est tout con, mais je le trouve sympa <img src="https://n-pn.fr/forum/images/smilies/smile.png" alt="Smile" title="Smile" class="smilie smilie_1" /> On pourrait qualifier ça d'une transmition de données chiffrée asymétriquement même si ce n'est pas vraiment
# le cas. En fait, le principe est de définir des deux coté (client/serveur) une incrémentation de xorkey & une décrémentation de xorkey.
# Ainsi le programme alternera entre incrémenter la xorkey & la décrémenter, il deviendra alors quasiment impossible à un pirate ayant sniffé le réseau au moment
# de la transmition, de decoder le message.
# L'algorithme ci-dessous parle de lui même <img src="https://n-pn.fr/forum/images/smilies/wink.png" alt="Wink" title="Wink" class="smilie smilie_2" />
if i == 0:
xorkey += inc
data = decode(data[54:], eof_file, xorkey) # On appelle bien evidemment la fonction decode pour décoder le bouzin :p
i += 1
else:
xorkey -= dec
data = decode(data[54:], eof_file, xorkey) # Same
i -= 1
print data # On affiche le message
data = ""
buf = ""
s.close()
def client(dst_ip, dst_port, picture, header_bmp, eof_file, xorkey, inc, dec):
# On obitent les arguments ..
xorkey = int(xorkey)
dst_port = int(dst_port)
nickname = raw_input("Nickname: ")
nickname = "[" + nickname + "] : "
bSend = b""
i = 0
# On défini la partie socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((dst_ip, dst_port))
print "Connected to " + dst_ip + ":" + str(dst_port) + " -- Linux (CTRL+D) to escape :: Windows (CTRL+C) to escape" # Je ne suis pas sûr des raccourcis x)
try:
while True:
bSend = nickname + raw_input("Post: ")
if i == 0:
xorkey += inc
s.send(header_bmp + encode(picture, bSend, eof_file, xorkey) + "EOF") # On envois le message codé avec "EOF" rajouté à la fin pour le trick anti bug recv() :p
i += 1
else:
xorkey -= dec
s.send(header_bmp + encode(picture, bSend, eof_file, xorkey) + "EOF") # Same
i -= 1
except:
s.close()
sys.exit("Socket error")
def usage():
if len(sys.argv) < 3:
print ("Usage: {} <options>".format(sys.argv[0]))
print ("Options:")
print ("-p <picture_container> <file_to_hide> <eof> <xorkey> <output> (to encode a picture)")
print ("-u <encoded_picture> <eof> <xorkey> <output> (to decode a picture)")
print ("-ns <port> <eof> <xorkey> <inc> <dec> (to start server)")
print ("-nc <ip> <port> <picture> <eof> <xorkey> <inc> <dec> (to start the client)")
print ("--help (to get help)")
sys.exit("")
if __name__ == '__main__':
print sys.version
if len(sys.argv) < 2:
usage()
elif sys.argv[1] == '--help':
usage()
elif len(sys.argv) == 7 and sys.argv[1] == '-p': # len(sys.argv) == 6 , si vous lancez sans donner l'emplacement de python.exe !
#def encode(original_picture, hidden_picture, eof_file, xor):
try:
# On get les deux images.
original = open(sys.argv[2], 'rb')
original_picture = ''.join(original.readlines())
original.close()
hidden = open(sys.argv[3], 'rb')
hidden_picture = ''.join(hidden.readlines())
hidden.close()
# On prend le header BMP pour l'écriture en sortie.
header(original_picture) # On affiche les infos du fichier BMP
header_bmp = original_picture[:54]
original_picture = original_picture[54:]
output_file = open(sys.argv[6], 'wb+')
output_file.write(header_bmp + encode(original_picture, hidden_picture, sys.argv[4], int(sys.argv[5]))) # On passe les deux images à la fonction encode() <img src="https://n-pn.fr/forum/images/smilies/smile.png" alt="Smile" title="Smile" class="smilie smilie_1" />
print "[+] File " + sys.argv[6] + " was encoded !"
output_file.close()
except IOError:
print "No file !"
sys.exit("IOError")
elif len(sys.argv) == 6 and sys.argv[1] == '-u': # len(sys.argv) == 5 si vous lancez sans donner l'emplacement de python.exe !
#def decode(encoded_picture, eof_file, xor):
try:
# On get
encoded_pic = open(sys.argv[2], "rb")
encoded_picture = ''.join(encoded_pic.readlines())
encoded_pic.close()
header(encoded_picture) # On affiche les infos du fichier BMP
encoded_picture = encoded_picture[54:]
output_file = open(sys.argv[5], 'wb+')
output_file.write(decode(encoded_picture, sys.argv[3], int(sys.argv[4]))) # On passe l'image à la fonction decode() <img src="https://n-pn.fr/forum/images/smilies/smile.png" alt="Smile" title="Smile" class="smilie smilie_1" />
print "[+] File " + sys.argv[5] + " was decoded !"
output_file.close()
except IOError:
print "No file !"
sys.exit("IOError")
elif len(sys.argv) == 7 and sys.argv[1] == '-ns':
#def server(src_port, eof_file, xorkey, inc, dec):
server(int(sys.argv[2]), sys.argv[3], int(sys.argv[4]), int(sys.argv[5]), int(sys.argv[6])) # On lance le serveur
elif len(sys.argv) == 9 and sys.argv[1] == '-nc':
#def client(dst_ip, dst_port, picture, header_bmp, eof_file, xorkey, inc, dec):
# On obtient les arguments..
picture = open(sys.argv[4], 'r')
tPicture = ''.join(picture.readlines())
header_bmp = tPicture[:54]
tPicture = tPicture[54:]
picture.close()
client(sys.argv[2], int(sys.argv[3]), tPicture, header_bmp, sys.argv[5], int(sys.argv[6]), int(sys.argv[7]), int(sys.argv[8])) # On lance le client <img src="https://n-pn.fr/forum/images/smilies/smile.png" alt="Smile" title="Smile" class="smilie smilie_1" />
else:
usage()
Tout les remarques sont les bienvenues ! Je dis sûrement des bêtises dans mon truc et le bitwises operator ne me sont pas encore totalement familier !
Toucher au Kernel, c'est un peut comme se shooter au LSD, on pense pouvoir tout faire mais ça finit souvent mal.
|