Ukryte wiadomości w screenshotach z gier, steganografia

Już jakiś czas temu użytkownicy pewnego forum zauważyli, że w ich screenach z World of Warcraft pojawia się dziwny wzór.  Te ukryte wiadomości to tzw. steganografia.

Screeny z World of Warcraft i Overwatch

Wiadomości zakodowane w ten sposób nie są widoczne gołym okiem. Aby było wiadomo o czym mamy do czynienia, wykonamy prosty eksperyment w grze World of Warcraft:

  1. Udajmy się do najjaśniejszego miejsca na mapie.
  2. Zróbmy screena.
  3. Powstałe zdjęcie poddajmy wyostrzeniu.

Efekt:

Ukryte wiadomości w screenie z World of Warcraft
Wyostrzony screen z World of Warcraft

Już na pierwszy rzut oka widać, że to nie jest zwykły szum powstały podczas kompresji. Zakłócenia powtarzają i układają się w regularny sposób. To jest swoisty bar-code. Taki sam efekt możemy osiągnąć powtarzając całą czynność w innej grze Blizzarda – Overwatch.

Ukryte wiadomości w Overwatch
Ukryta wiadomość w Overwatch

Co zostało przed nami ukryte?

Niestety nie możemy łatwo „przeczytać” co zawierają te wiadomości. Inżynieria wsteczna dostarczyła nam informacji, że dziwny kod zawiera dane takie jak: (w przypadku WoW’a) ID konta gracza, timestamp oraz IP realmu (serwera), do którego połączony jest gracz.

Możemy się domyślać, że służy to weryfikacji autentyczności zdjęcia. Wyobraźmy sobie dość kuriozalną sytuację w której gracz prywatnego serwera zgłasza buga do supportu Blizzarda. Gdyby takie zdarzenia miały miejsce i nie mogłyby zostać szybko zweryfikowane firma sama wpędziłaby się w tarapaty.

Metoda, którą stosuje Blizzard została opracowana przez firmę Digimarc i działa na mocy patentu http://www.google.co.uk/patents/US7653210.

Za ukryte wiadomości odpowiada Steganografia

Steganography is the practice of concealing a file, message, image, or video within another file, message, image, or video. ~ Wikipedia

Czyli innymi słowy – ukrywanie treści w innej treści. Niewątpliwą przewagą steganografii nad kryptografią jest to, że ukryta treść jest możliwa do odszyfrowana jedynie za pomocą naszego zmysłu np. wzroku. Innymi słowy – ukryta treść może zostać łatwo zinterpretowana przez człowieka, a program będzie miał problem znaleźć tę informację na poziomie kodu.

Chyba najbardziej powszechnym zastosowaniem steganografii jest umieszczanie na zdjęciu znaku wodnego. Ma to zapobiec piractwu, bądź upewnieniu się, że dane zdjęcie nie zostało zmodyfikowane w sposób nieautoryzowany.

Kącik „zrób to sam”

Teraz zajmiemy się ukrywaniem tekstu w zdjęciu. Użyjemy do tego metody najmniej znaczącego bitu, która jest jednym z najprostszych sposobów ukrywania informacji. Polega ona na wykorzystaniu tytułowego LSB (najbardziej po prawej) do zakodowania w nim informacji.

Rozkład pixela na barwy RBG
Rozkład pixela na barwy RBG

Technicznie, obraz to tablica pixeli. Jeżeli mamy do czynienia z obrazami kolorowymi, to każdy pixel składa się z 3 barw składowych RGB – Red Green Blue. Każda barwa ma swoja wartość jasności. Najczęściej stosowany jest 24-bitowy zapis kolorów. Na każdą barwę przypada wtedy 8 bitów, więc wartość jasności każdej z nich mieści się w przedziale 0-255. Gdybyśmy pracowali na skali szarości, to obraz miałby tylko jeden kanał – analogicznie w przypadku obrazu kolorowego z przeźroczystością mielibyśmy 4 kanały (dodatkowy jeden opisywałby stopień przeźroczystości).

Do zakodowania przykładu użyje Pythona z biblioteką OpenCV.

import cv2

cv2.namedWindow('image')
img = cv2.imread('res/img.jpg', cv2.IMREAD_COLOR)

while (1):
    cv2.imshow('image', img)

    if cv2.waitKey(20) == 27:
        break

cv2.destroyAllWindows()

Na początku wczytujemy sobie obrazek. Parametr cv2.IMREAD_COLOR upewnia nas, że wczytujemy zdjęcie w kolorze. Tworzymy okno „image” a pętle nieskończoną podtrzymujemy póki nie zostanie wciśnięty klawisz ESC. Ważna uwaga: kolory w OpenCV przechowywane są w formacie BGR (nie RGB).

Przygotujmy sobie teraz jakiś tekst, który będziemy chcieli ukryć.

def tobits(s):
    result = []
    for c in s:
        bits = bin(ord(c))[2:]
        bits = '00000000'[len(bits):] + bits
        result.extend([int(b) for b in bits])
    return result

text = "Tekst do ukrycia"
bin_text = tobits(text)

W tym momencie w zmiennej bin_text mamy już tablice bitów. Metoda tobits(text) została zaczerpnięta ze StackOverflow (link na końcu wpisu).

bits_amount = [1, 1, 1]

Utwórzmy sobie tablicę, która będzie przechowywała nasze ustawienia, odnośnie tego na ilu bitach w każdej barwie chcemy ukrywać naszą wiadomość. W tym przypadku każdemu pixelowi „kradniemu” 3 bity (po 1 z każdej barwy).

def gen_img(img, bit_amount, bin_text):
    img_out = img.copy()
    i = 0 #pozycja w naszej tablicy z wiadomoscia

    height, width, channels = img_out.shape

    for y in range(0, height):
        for x in range(0, width):
            pixel = img_out[y, x]

            # dla kazdego koloru
            for ci in range(0, 3):

                # dla kazdego bitu (na podstawie ustawien)
                for bi in reversed(range(0, bits_amount[ci]+1)):

                    # for bi=2 is like 11111101
                    mask = ~(1 << bi)
                    new_bit = bin_text[i] << bi

                    pixel[ci] = (pixel[ci] & mask) | new_bit

                    if i == len(bin_text)-1:
                        return

                    i += 1
    return img_out

Metoda gen_img() zwraca nam zmodyfikowany obraz. Zapis odbywa się zaczynając od góry zdjęcia. Zmiana interesującego nas bitu może wyglądać skomplikowanie, lecz w rzeczywistości to prosta operacja przedstawiona na zdjęciu poniżej:

Podmiana bitu w ciągu bitów
Podmiana bitu w ciągu bitów

Wystarczy teraz wywołać nową metodę img_new = gen_img() i uzyskany w ten sposób obraz przekazać do wyświetlenia cv2.imshow('image', img_new).

Warto pobawić się ustawieniami i poszukać wartości, dla których zaczynamy dostrzegać manipulacje na obrazie. Przy jednym „ukradzionym” bicie obrazu, nasze oko nie jest w stanie znaleźć różnicy. Jednak przy np. przy 4 ukradzionych bitach już zaczynamy dostrzegać zmiany. Na niektóre kolory, nasze oko jest wyczulone bardziej, na inne zaś mniej – dlatego rozkład bitów na kolorach, gdy mówimy o dostrzeżeniu zmian, nie musi być równomierny.

Podsumowując

Jak można zauważyć steganografia to bardzo obszerny temat. Jej zalety może dostrzec każdy, począwszy od fotografa nakładającego na swoje zdjęcie znak wodny, kończąc na firmach które używają steganografii do zabezpieczaniem się przed oszustwami.

Należy pamiętać, że niektóre metody ukrywania wiadomości w obrazie (np. metoda najmniej znaczącego bitu) nie są odporne na manipulacje obrazem.

Linki:

Zmiany na blogu

Tak jak pisałem w poprzednim wpisie Kolejny blog o programowaniu? Czemu nie!, wprowadziłem system komentarzy Disqus. Zapraszam do komentowania!