martes, 30 de octubre de 2012

Outra de esteganografía con imáxes [python]

Fai pouco máis de un mes, hacklego amosaranos unha técnica de esteganografía neste mesmo blogue, daquela a forma de ocultar a información era deixala o final do arquivo, polo que a imaxe non se vía modificada, esta técnica é chamada EOF (de End Of File, "Fin do arquivo").

Desta vez ímos probar outra forma de facelo, LSB (de Least Significant Bit, "Bit menos significativo"), isto é, en vez de mante-la integridade dos arquivos por separado, ó arquivo a ocultar esconderase nos bits con menor significado da imaxe, supoñamos que cada píxel está composto por 3 cores "primarias": vermello, verde e azul, cada un deles está a súa vez representado por un número de bits, por exemplo, nun BMP de 24 bits 8 deles corresponderían a cada cor. Se temos 8 bits para representar a forza do vermello, cambiar o máis significativo nótase 128 veces máis que o menos significativo, cambiar un faría moi claro o cambio mentres que cambiar o outro é indistiguible a simple vista.


Esta técnica ten ventaxes e inconvintes, as ventaxes é que non se pode atopar buscando cadeas e non modifica o tamaño do arquivo, mentres que os inconvintes son que só permite almacear arquivos 8 veces máis pequenos que a imaxe (menos se é un formato con compresión), e é prácticamente inútil cos formatos de compresión con perdas (coma por exemplo JPEG).

Ímos escreber entón un programa que aplique esta técnica para gardar unha mensaxe, por simplicidade ímos supoñer arquivos BMP de 24 bits, así podemos simplemente ignora-los primeiros 54 bytes de cabeceira e ir directamente os datos, esta será a imaxe que empregaremos para as probas:

O primeiro paso será ter unha forma de convertir a mensaxe a súa representación en binario, por exemplo convertir 'Proba' en '0101000001110010011011110110001001100001',
para isto so necesitamos ir collendo os caracteres da mensaxe, convertilos a binario con `bin`, extendelos ata que ocupen un octeto e ir metendoos nunha lista:
def pad(string, length, pad_with):
    while len(string) < length:
        string = pad_with + string
    return string

def message_to_bits(message):
    bits = []
    for byte in message:
        bits.append(pad(bin(ord(byte))[2:], 8, "0"))
    return ''.join(bits)

Feito isto para modificalos datos só temos que ir tomando cada bit e introducilo na imaxe, se o facemos a partir do byte 54 estamos seguros de non pisar os datos da cabeceira:
from array import array

def insert_lsb_message(data, message):
    data = array('c', data) # Convertimo-lo dato nun array para poder mutalo directamente

    offset = 54 # Ignoramos os primeiros 54 bytes, corresponden a cabeceira
    bits = message_to_bits(message)
    for bit in bits:
        # Convertímo-la cadea a unha representación como enteiro para poder facer isto...
        bit = int(bit)

        # Cambiamo-lo último bit polo valor escollido
        tmp = (ord(data[offset]) & 0xFE) | bit
        data[offset] = chr(tmp)

        # Na próxima iremos a polo seguinte byte
        offset += 1

    return data


if __name__ == '__main__':
    if len(argv) != 4:
        print "%s <input> <output> <message>" % argv[0]
        exit(0)

    fin = open(argv[1], "rb")
    fout = open(argv[2], "wb")
    message = argv[3]

    fout.write(insert_lsb_message(fin.read(), message))

Se o utilizamos así, obteremos ísto
python lsbin.py proba.bmp proba_out.bmp 'Hackliza!'


Agora a face-lo paso inverso, recolectamos os bits menos significativos dos datos para pasarllo despois a unha función que os convirta na mensaxe:
def read_lsb_message(data):
    bits = []

    # Non temos máis posibilidade que considerar todos os bits dende a cabeceira
    for byte in data[54:]:
        bits.append(ord(byte) & 1) # Engadimos o bit a lista

    print bits_to_message(bits)

Para convertilos en mensaxe teremos que agrupalos en octetos:
def bits_to_byte(bits):
    # Toma un par de bits, multiplica o da esquerda por dous e sumalle o da dereita
    # elimina os dous e engade o resultado no seu lugar, repíteo ata que so queda un
    result = reduce(lambda x, y: x * 2 + y, bits)

    return chr(result)


def bits_to_message(bits):
    message = []

    # Agrupa os bits por grupos de 8 e pasaos por bits_to_byte
    for index in xrange(len(bits) / 8):
        message.append(bits_to_byte(bits[index*8:(index+1)*8]))

    return ''.join(message)

Se o aplicamos o último arquivo que xeramos:
python lsbout.py proba_out.bmp 
Hackliza!�����������������������������������������������������������������������
�������������������������������������������������������������������������������������
�������������������������������������������������������������������������������������
�������������������������������������������������������������������������������������
�������������������������������������������������������������������������������������
�������������������������������������������������������������������������������������
�������������������������������������������������������������������������������������
�������������������������������������������������������������������������������������
�������������������������������������������������������������������������������������
����������������������������������������...
O resultado conten moitos caracteres raros, pero comeza pola mensaxe que introduciramos :) e a diferencia é invisible á vista


E iso é todo, por último os códigos completos por se alguén quere trastear con eles:
lsbin.py [ http://pastebin.com/UY0wnLJG ]
lsbout.py [ http://pastebin.com/pDy4xKjA ]

No hay comentarios:

Publicar un comentario