Historie tego formatu można znaleźć w internecie i nie będziemy się na niej za bardzo skupiać. Jest to chyba najlepszy format do ukrywania danych w pikselach, ponieważ zawiera prostą kompresje bezstratną i w momencie zapisu pliku nasze piksele nie ulegają zmianie, oczywiście mowa tutaj o plikach bez palety barw, w przeciwieństwie do formatów takich jak JPG, prowadzi to do dużych rozmiarów pliku.
Struktura pliku bmp.
Jak to wygląda w praktyce. Wystarczy uruchomić Painta i stworzyć obrazek składający się z czterech białych pikseli. Kolor biały to RBG[255,255,255] w systemie szesnastkowym jego reprezentacja to [FF, FF, FF] pozwoli nam to wyodrębnić nagłówek od danych. Aby pominąć obszar RGBQUAD[] który przy naszym prostym projekcie wszystko nam zasłoni, zapiszcie plik bitmapy o głębi 24 bity lub większej, wtedy paleta ta nie występuje. Zaczniemy od BITMAPFILEHEADER:
bfType – identyfikator pliku BMP, zazwyczaj BMP przyjmuje wielkość WORD (szerokość 16 bitów lub 2 bajtów), tutaj nic nie schowamy.
bfSize – całkowita wielkość pliku, istnieje możliwość ukrycia w tym miejscu 32 bitów, większość programów ignorują to pole, jest tylko jedna zasada, wartość musi być większa od wielkości pliku, 46 odpowiada wartości 70 i właśnie tyle zajmuje nasz plik 70B. Powiedzmy, że chcemy ukryć w tym miejscu słowo ATAK w ASCII słowo to reprezentują cyfry (65,84,65,75) w systemie szesnastkowym (41,54,41,4B) , wartości te czyta się od prawej do lewej 46 00 00 00 = 00000046 = 70 < 1262572609= 4B 41 54 41 =41 54 41 4B :
plik nie uległ uszkodzeniu, można go bez problemu przeglądać, wiadomość została ukryta.
bfReserved1 i bfReserved2– Pola zarezerwowane na przyszłość, póki co nie są wykorzystywane więc mogą posłużyć do przechowania kolejnych 32 bitów informacji:
bfOffBits – Pozycja danych w pliku, jest to pole w którym możemy ukryć dane ale może to doprowadzić do uszkodzenia naszego pliku. Wszystko zależy od programu którym będzie wyświetlany nasz obrazek. Jeśli program zignoruje to pole i założy, że dane obrazka znajdują się tuż za nagłówkiem, nasz obrazek zostanie załadowany prawidłowo, jeśli nie wystąpi błąd:
Wystarczy dodać dodatkową linijkę FF FF FF FF FF FF 00 00 i zmienić wartość 36 (54) na 3E (62) efekt:
Plik nie został uszkodzony ponieważ jego struktura została zachowana, w taki sposób można przemycić tyle informacji ile się chce, ale nie wszystkie programy graficzne da się oszukać podobno.
Kolejnym elementem jest trochę zależny od programu w którym utworzyliśmy nasz plik. Rozpoznajemy ją po wielkości nagłówka. Wyróżniamy następujące rodzaje nagłówków mapy bitowej:
Wielkość nagłówka | Nazwa nagłówka | Wspierane systemy operacyjne | Dodane funkcjonalności (rosnąco) | w plikach stworzonych przez program |
---|---|---|---|---|
C | BITMAPCOREHEADER OS21XBITMAPHEADER |
OS/2 i wszystkie wersje Windows począwszy od Windows 3.0 | ||
40 | BITMAPCOREHEADER2 OS22XBITMAPHEADER |
OS/2 | Dodaje pół-tonowanie. Dodaje kompresje RLE oraz Huffman 1D. | |
28 | BITMAPINFOHEADER | wszystkie wersje Windows począwszy od Windows 3.0 | Likwiduje kompresje RLE-24 oraz Huffman 1D. Dodaje obsługę 16bpp oraz 32bpp pikseli. Dodaje opcjonalną maskę bitów RGB. | Adobe Photoshop |
34 | BITMAPV2INFOHEADER | Brak danych. | Usuwa opcjonalną maskę bitów RGB. Dodaje obowiązkową maskę bitów RGB. | |
38 | BITMAPV3INFOHEADER | Brak danych. | Dodaje obowiązkową maskę bitową dla kanału alfa. | Adobe Photoshop |
6C | BITMAPV4HEADER | wszystkie wersje Windows począwszy od 95/NT4 | Dodaje typ przestrzeni barw oraz korekcje gamma. | |
7C | BITMAPV5HEADER | Windows 98/2000 i nowsze. | Dodaje profile kolorów ICC. |
(tabela pochodzi z Wkipedi)
Paint tworzy pliki bitmapy z nagłówkiem BITMAPINFOHEADER który składa się z elementów:
biSize – Wielkość nagłówka, w tym wypadku 28 (40 bajtów), po tej wielkości aplikacje rozpoznają czy nagłówkiem jest faktycznie BITMAPINFOHEADER, i czy plik BMP jest na pewno wersją Windows V3 formatu BMP
biWidth, biHeight – szerokość, wysokość bitmapy oraz głębie kolorów, czyli ilości bitów które opisują każdy kolejny piksel (18 =24 = 8+8+8). W przypadku użycia kanału alfa można ten element poszerzyć do 20 (20=32=8+8+8+8)
biPlanes (czwarte pole)- mówi o ilości płaszczyzn jego wielkość jest domyślnie 01 00, przez niektóre programy jest ignorowana więc w wyjątkowych okolicznościach można użyć go do przechowania informacji.
biBitCount – Liczba bitów na piksel (18 = 24 = 8+8+8)
biCompression – Rodzaj zastosowanego kodowania/kompresji
biSizeImage – określające całkowitą wielkość bitmapy po ewentualnej dekompresji
iXPelsPerMeter i biYPelsPerMeter – mówią o poziomej i pionowej ilości pikseli przypadających na metr (informacja analogiczna do DPI, ang. Dots Per Inch). Informacje te są potrzebne głównie w przypadku drukowania danej bitmapy. Jeśli nie zależy nam na poprawności drukowania możemy wykorzystać łącznie 64 bity na własny użytek.
biClrUsed – które mówi o ilości kolorów w palecie barw. Jeżeli to pole jest wyzerowane, przyjmuje się że ilość kolorów w palecie jest równa liczbie 2 podniesionej do potęgi biBitCount. 2^24 co daje 16777216 = 256*256*256 możliwości
biClrImportant – mówiące o ilości istotnych kolorów w palecie. Stanowcza większość aplikacji ignoruje jednak to pole, dzięki czemu może ono zostać użyte do przechowania 32 bitów danych niezwiązanych z bitmapą.
Każda linia danych obrazku kończy się zespołem dwóch zerowych bajtów 00 00 który oddziela jedną linie od drugiej.
Dzięki tym danym, nasz plik wie gdzie się zaczyna i kończy. dzięki temu można napisać prosty program który będzie dodawał bajty innego pliku lub tekst przed piksele lub po nich bez uszkodzenia pliku graficznego. Najpierw dodamy sobie dane przed dane obrazu, w tym celu musimy poznać wartość z nagłówka pliku:
dodać bajty wiadomości lub pliku i odpowiednio zmienić tą wartość. Lecz najpierw musimy pobrać tą wartość. Ukrycie pliku tekstowego w pliku graficznym wyglądało by następująco:
Przykład programu w języku vb.net wykorzystujący powyższą metodę:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
Imports System.Windows.Forms Module Module1 Sub Main() 'Otwiera dialog z użytkownikiem Console.WriteLine("Co chesz zrobić? Wpisz cyfrę zadania." + vbNewLine + "1.Wydobądź ukryty plik." _ + vbNewLine + "2.Ukryj plik.") Dim zad As String Do zad = Console.ReadLine() If zad = "1" Or zad = "2" Then Exit Do Else Console.WriteLine("możesz wybrać tylko cyfrę 1 lub 2.") End If Loop Dim open_dialog As New OpenFileDialog() open_dialog.InitialDirectory = "c:\" open_dialog.Filter = "Image Files (*.bmp)| *.bmp" If open_dialog.ShowDialog() = System.Windows.Forms.DialogResult.OK Then If zad = 2 Then ''Ukrywanie pliku Dim File_bytes As Byte() = IO.File.ReadAllBytes(open_dialog.FileName) Dim poczateDanych() As Byte = {File_bytes(10), File_bytes(11), File_bytes(12), File_bytes(13)} Dim przesuniecie As Integer = BitConverter.ToInt32(poczateDanych, 0) Dim ZmienionePrzesuniecie As Integer = 0 Dim plikDoUkrycia As New OpenFileDialog() plikDoUkrycia.InitialDirectory = "c:\" plikDoUkrycia.Filter = "All Files|*.*" If plikDoUkrycia.ShowDialog() = System.Windows.Forms.DialogResult.OK Then Dim File_DoUkrycia As Byte() = IO.File.ReadAllBytes(plikDoUkrycia.FileName) Console.WriteLine("Oryginalna wartość: " + przesuniecie.ToString + " w bajtach: " + BitConverter.ToString(poczateDanych)) ZmienionePrzesuniecie = przesuniecie + File_DoUkrycia.Count Dim zmiana() As Byte = BitConverter.GetBytes(ZmienionePrzesuniecie) Console.WriteLine("Wartość po dodaniu bajtów ukrywanych: " + ZmienionePrzesuniecie.ToString _ + " w bajtach: " + BitConverter.ToString(zmiana)) 'Pobiera nagłówek bez bfReserved1 i bfReserved2 Dim poczatek(9) As Byte Array.Copy(File_bytes, poczatek, 6) Dim nowyplik(poczatek.Length + zmiana.Length - 1) As Byte Array.Copy(poczatek, nowyplik, poczatek.Length) 'dodaje nagłówek do nowego pliku i w miejscu bfReserved1 i bfReserved2 zapisuje oryginalny 'początek danych Array.Copy(poczateDanych, 0, nowyplik, poczatek.Length - 4, poczateDanych.Length) 'dodaje nowy bfOffBits zwiększony o ilość bitów w pliku ukrywanym Array.Copy(zmiana, 0, nowyplik, poczatek.Length, zmiana.Length) Array.Resize(nowyplik, przesuniecie) 'dodaje przestrzeń między bfOffBits a danymi obrazka może to być np. BITMAPINFOHEADER Array.Copy(File_bytes, 14, nowyplik, 14, przesuniecie - 14) Dim poczatekdanych As Integer = nowyplik.Length Array.Resize(nowyplik, nowyplik.Length + File_DoUkrycia.Length) 'dodaje bajty pliku ukrywanego Array.Copy(File_DoUkrycia, 0, nowyplik, poczatekdanych, File_DoUkrycia.Length) poczatekdanych = nowyplik.Length Array.Resize(nowyplik, nowyplik.Length + (File_bytes.Length - (nowyplik.Length - File_DoUkrycia.Length))) 'dodaje reszte bajtów danych obrazka Array.Copy(File_bytes, przesuniecie, nowyplik, poczatekdanych, File_bytes.Length - przesuniecie) 'Sekcja zapisuje obrazek na dysku Console.WriteLine() Console.Write("Podaj nazwę pliku pod którycm chcesz zapisać plik bmp: ") Dim nazwapliku As String = Console.ReadLine() IO.File.WriteAllBytes("C:\Users\piotr\Desktop\" + nazwapliku + ".bmp", nowyplik) Console.WriteLine("Bajty zostały ukryte. Zapisano plik: " + "C:\Users\piotr\Desktop\" + nazwapliku + ".bmp") End If Console.ReadLine() Else Dim File_bytes As Byte() = IO.File.ReadAllBytes(open_dialog.FileName) 'Pobiera oryginalny początek danych i spreparowany poczatek danych Dim poczateDanych() As Byte = {File_bytes(10), File_bytes(11), File_bytes(12), File_bytes(13)} Dim przesuniecie As Integer = BitConverter.ToInt32(poczateDanych, 0) Dim poczateDanychOryginalny() As Byte = {File_bytes(6), File_bytes(7), File_bytes(8), File_bytes(9)} Dim przesuniecieOryginalne As Integer = BitConverter.ToInt32(poczateDanychOryginalny, 0) Dim nowyplik(przesuniecie - przesuniecieOryginalne - 1) As Byte 'przy ich pomocy wydobywa dane i pliku Array.Copy(File_bytes, przesuniecieOryginalne, nowyplik, 0, przesuniecie - przesuniecieOryginalne) 'Sekcja zapisuje bajty na dysku Console.WriteLine() Console.Write("Podaj nazwę pliku pod którycm chcesz zapisać plik bmp: ") Dim nazwapliku As String = Console.ReadLine() IO.File.WriteAllBytes("C:\Users\piotr\Desktop\" + nazwapliku, nowyplik) Console.WriteLine("Bajty zostały odzyskane. Zapisano plik: " + "C:\Users\piotr\Desktop\" + nazwapliku) Console.ReadLine() End If End If End Sub End Module |
Kolejnym elementem nieopisanym jeszcze jest paleta barw. Jej budowa jest w zasadzie bardzo prosta, składa się z dołączonym opisem kolorów w formacie BGRA, tak to odwrócony RGB i dodatkowo dochodzi kanał alfa lub jak niektórzy twierdzą bajty zarezerwowane. Paleta dołączona do pliku bmp może składać się z 8, 16,24,256 kolorów. Jak takie coś wygląda, zrobiłem wam programik do analizy pliku bmp:
do pobrania: Analizator_plikow_BMP
kod programu w języku vb.net
zamiast kolorów, tak jak to było wcześnie, mamy tylko indeksy odwołujące się do tabeli, i tak plik bmp o wielkości 1×1 piksel o kolorze zielonym z 256 kolorową paletą kolorów zielony256.bmp. Wybieramy go przy użyciu programu do analizy:
Nasze dane obrazu to 113 0 0 0, dane wyświetliłem w formacie dec, czyli w systemie dziesiętnym. Nasza liczba 113 to po prostu odwołanie do konkretnego elementu palety pod adresem 113, pod którym znajduje się BGRA[ 40, C0, 20 ,00] co po przekształceniu na system dziesiętny daje kolor RGB(32,192,64), co odpowiada naszemu pikselowi:
Trochę to inaczej wygląda z 16-kolorową paletą kolorów. Jak wiemy jeden bajt, przyjmuje wartość z zakresu od 0 do 255, dlatego 256-kolorowa paleta ma 256 kolorów, jeśli paleta ma 16 kolorów, to nie ma sensy opisywać ją ośmioma bitami, wystarczy opisać ją czterema, ponieważ zakres permutacji czterech bitów to szesnaście 0000 (0) – 1111 (16) dlatego jednym bajtem w pliku z 16-kolorów opisujemy dwa kolory. Stwórzmy sobie plik bmp 1×4 z kolorami: pomarańcz, niebieski, żółty, zielony 1x4_16-kolorowa.bmp włączamy program:
Jeśli będzie tylko 8 kolorów w palecie, wtedy kolory będą indeksowane przez trzy bity, cztery przez dwa, dwa kolory przez jeden, na takiej samej zasadzie jak powyżej. Czy istnieje możliwość ukrycia w takiej palecie lub w jej danych informacji? Trochę tak i trochę nie, sama paleta stwarza nam ograniczenia w ukryciu wiadomości, zastosowanie metody LSB może znacząco zniekształcić obrazek. Można oczywiście ukryć wiadomość w kanale Alfa, nie spowoduje to uszkodzenie pliku, ponieważ większość programów po prostu go ignoruje, co daje nam 256 bajtów które można wykorzystać do ukrycia wiadomości. Wystarczy namierzyć tabelę i pobrać jej bajty kanału alfa.
program do pobrania: UkrywanieTekstuWpaleciekolorow
Program w prosty sposób zgaduje, czy plik ma paletę kolorów i jakiej jest wielkości, na podstawie wielkości nagłówka i początku danych. Następnie zmienia wartość bajtów palety kolorów pliku (co czwarty). Paleta kolorów to duże ograniczenie, można zrobić jeszcze coś odwrotnego, można dzięki niej zamienić plik tekstowy na obrazek. Może nie jest to jakiś wybitny sposób ukrywania wiadomości, ale zdarza się, że pliki graficzne są uszkodzone. Zdarzyło wam się może, że obrazek był w połowie widoczny a w połowie nie? Sposób jest bardzo prosty, namierzamy bfOffBits i zmieniamy bajty do końca pliku. Dodałem też offset który wynosi trzy białe znaki, jeśli program znajdzie trzy bajty o wartości 255 obok siebie, wtedy przerywa działanie. Można to zmienić i zamienić je na czarne piksele, jak kto woli. Jak wygląda taki sposób ukrywania wiadomości:
Jeśli jesteście ciekawi jak taki kotek wygląda i chcecie się mu przyjrzeć z bliska to proszę:
Widzicie, ten pasek na dole obrazka, to nasza ukryta wiadomość. Dla zwykłego szaraka, jest to po prostu uszkodzony plik graficzny, jeśli tekst jest krótki, to nawet nie będzie zauważalny.
Program do ukrywania tekstu: UkrywanieTekstuWplikuZpaletaKolorowWdanychPliku