Ciele
- Priprav si prostredie
- Pozri sa, čo o tebe vie obyčajná fotka
- Ukry správu najhrubším možným spôsobom
- Ukry správu metódou LSB
- Over, či je to naozaj nedetegovateľné
- Znič správu tak, ako to urobí bežná aplikácia
Úvod
Na prednáške sme hovorili o tom, že steganografia skrýva samotnú existenciu správy, a že sa posudzuje štyrmi vlastnosťami, ktoré si navzájom protirečia.
Na tomto cvičení si to overíš. Ukryješ správu dvoma rôznymi spôsobmi, pozrieš sa, či sa to dá spoznať, a nakoniec ju jedným bežným úkonom zničíš.
Všetko robíš vo vlastnom Pythone, žiadny špeciálny nástroj netreba.
Postup
Krok 1: Priprav si prostredie
Potrebuješ Python a jednu knižnicu na prácu s obrázkami.
pip install pillow
Úloha 1.1
Priprav si dva súbory v jednom adresári — jednu fotku z telefónu (ponechaj ju presne tak, ako prišla z telefónu, nič s ňou nerob) a jeden obrázok vo formáte PNG. Ak PNG nemáš, vyrob si ho z fotky:
from PIL import Image
Image.open("fotka.jpg").convert("RGB").save("nosic.png")
Poznámka: Formát PNG je dôležitý. Uchováva každý bod presne tak, ako bol. Prečo to je podstatné, uvidíš v poslednom kroku.
Krok 2: Pozri sa, čo o tebe vie obyčajná fotka
Skôr než začneš niečo ukrývať, pozri sa, čo je v tom súbore už teraz.
from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS
img = Image.open("fotka.jpg")
exif = img.getexif()
for tag_id, value in exif.items():
print(TAGS.get(tag_id, tag_id), "=", value)
gps = exif.get_ifd(0x8825)
for tag_id, value in gps.items():
print("GPS", GPSTAGS.get(tag_id, tag_id), "=", value)
Úloha 2.1
Zapíš si, čo všetko sa o tebe z tej fotky dá zistiť. Pozri sa najmä na značku a model zariadenia, dátum a čas vzniku a na údaje GPS.
Upozornenie: Toto nie je steganografia. Je to opak — údaje, ktoré tam niekto uložil otvorene a ty si o tom nevedel. Práve preto je forenzná analýza metaúdajov jednou z metód steganalýzy. Často odhalí nezrovnalosť skôr, než sa niekto vôbec pozrie na samotný obsah.
Úloha 2.2
Zamysli sa nad otázkou — keby si chcel poslať fotku a nechcel prezradiť, kde a kedy vznikla, stačí ju orezať alebo zmenšiť? Overenie je jednoduché: fotku zmenši, ulož a spusti na ňu ten istý výpis znova.
Krok 3: Ukry správu najhrubším možným spôsobom
Najjednoduchšia technika vôbec — správu jednoducho pripojíš na koniec súboru. Prehliadač obrázkov prestane čítať tam, kde sa obrázok končí, a zvyšok ignoruje.
with open("nosic.png", "rb") as f:
data = f.read()
with open("hruby.png", "wb") as f:
f.write(data + b"TAJNA SPRAVA: stretnutie o 18:00")
Úloha 3.1
Otvor súbor hruby.png v prehliadači obrázkov. Zobrazí sa normálne?
Potom porovnaj veľkosť oboch súborov.
A teraz sa postav na druhú stranu a správu nájdi:
with open("hruby.png", "rb") as f:
data = f.read()
print(data[-60:])
Úloha 3.2
Odpovedz si na tri otázky. Ako veľkú kapacitu má táto metóda? Akú má robustnosť? A aká je jej nedetegovateľnosť?
Poznámka: Táto technika má neobmedzenú kapacitu a nulovú nedetegovateľnosť. Nájde ju ktokoľvek, kto sa na súbor pozrie čímkoľvek iným než prehliadačom obrázkov. Je to presne ten kompromis, o ktorom bola reč na prednáške.
Krok 4: Ukry správu metódou LSB
Teraz to isté poriadne. Správu zapíšeš do najmenej významných bitov jednotlivých farebných zložiek.
from PIL import Image
def ukry(vstup, vystup, sprava):
img = Image.open(vstup).convert("RGB")
bity = "".join(format(b, "08b") for b in sprava.encode("utf-8"))
bity += "00000000" * 3 # znacka konca spravy
pixely = list(img.getdata())
if len(bity) > len(pixely) * 3:
raise ValueError("sprava je prilis dlha pre tento nosic")
novy, i = [], 0
for r, g, b in pixely:
kanaly = [r, g, b]
for k in range(3):
if i < len(bity):
kanaly[k] = (kanaly[k] & ~1) | int(bity[i])
i += 1
novy.append(tuple(kanaly))
img.putdata(novy)
img.save(vystup)
ukry("nosic.png", "stego.png", "Stretnutie o 18:00 pri hlavnej brane")
A extrakcia:
from PIL import Image
def najdi(vstup):
img = Image.open(vstup).convert("RGB")
bity = ""
for r, g, b in img.getdata():
for k in (r, g, b):
bity += str(k & 1)
bajty = [bity[i:i+8] for i in range(0, len(bity) - 7, 8)]
vysledok = bytearray()
nuly = 0
for b in bajty:
hodnota = int(b, 2)
if hodnota == 0:
nuly += 1
if nuly == 3:
break
else:
nuly = 0
vysledok.append(hodnota)
return vysledok.decode("utf-8", errors="replace")
print(najdi("stego.png"))
Úloha 4.1
Spusti oboje a over, že správa prejde tam aj späť. Potom otvor nosic.png
a stego.png vedľa seba a skús nájsť rozdiel voľným okom.
Úloha 4.2
Zisti, aká je maximálna kapacita tvojho nosiča. Vynásob počet bodov tromi, vydeľ ôsmimi a porovnaj to s veľkosťou samotného obrázka.
Krok 5: Over, či je to naozaj nedetegovateľné
Voľným okom si rozdiel nenašiel. To ale neznamená, že tam nie je.
from PIL import Image
a = list(Image.open("nosic.png").convert("RGB").getdata())
b = list(Image.open("stego.png").convert("RGB").getdata())
zmenene = sum(1 for x, y in zip(a, b) if x != y)
print("zmenenych bodov:", zmenene, "z", len(a))
print("maximalny rozdiel:", max(abs(p - q) for x, y in zip(a, b) for p, q in zip(x, y)))
Úloha 5.1
Zapíš si, koľko bodov sa zmenilo a aký je najväčší rozdiel v hodnote.
Teraz sa pozri na štatistiku posledných bitov. V bežnej fotografii je posledný bit prakticky náhodný šum. Po vložení správy sa jeho rozloženie zmení.
from PIL import Image
def pomer_jednotiek(subor, kolko=200000):
data = list(Image.open(subor).convert("RGB").getdata())[:kolko]
bity = [k & 1 for p in data for k in p]
return sum(bity) / len(bity)
print("povodny:", round(pomer_jednotiek("nosic.png"), 4))
print("stego: ", round(pomer_jednotiek("stego.png"), 4))
Úloha 5.2
Porovnaj obe čísla. Potom skús to isté s oveľa dlhšou správou — napríklad zopakuj text stokrát — a pozri sa, ako sa čísla rozídu.
Poznámka: Toto je presne štatistická steganalýza, o ktorej bola reč na prednáške. Všimni si, že si nepotreboval poznať kľúč ani prečítať správu. Stačilo zistiť, že tam niečo je — a tým je metóda považovaná za prelomenú.
Krok 6: Znič správu tak, ako to urobí bežná aplikácia
Nakoniec si over robustnosť. Ulož stegomédium do formátu so stratovou kompresiou — presne to, čo s obrázkom urobí väčšina služieb, ktorými ho pošleš.
from PIL import Image
Image.open("stego.png").save("stego.jpg", quality=90)
Image.open("stego.jpg").save("stego_spat.png")
print(najdi("stego_spat.png")[:60])
Úloha 6.1
Pozri sa, čo vyšlo. Správa je preč, hoci obrázok vyzerá rovnako.
Úloha 6.2
Vysvetli vlastnými slovami, prečo sa to stalo. Nápoveda je v tom, čo stratová kompresia s hodnotami bodov robí.
Záver
Čo budeš mať po tomto cvičení pripravené pre mentora:
- Zoznam toho, čo sa dalo vyčítať z metaúdajov tvojej fotky.
- Veľkosti súborov pred vložením správy a po ňom pri oboch metódach.
- Počet zmenených bodov a najväčší rozdiel v hodnote pri metóde LSB.
- Pomer jednotiek v posledných bitoch pre pôvodný nosič a pre stegomédium, pri krátkej aj pri dlhej správe.
A odpovede na tieto otázky:
- Ktorá z dvoch vyskúšaných metód má väčšiu kapacitu a ktorá lepšiu nedetegovateľnosť? Prečo to nemôže byť tá istá?
- Potreboval si na to, aby si prítomnosť správy odhalil, poznať kľúč? Čo z toho vyplýva pre posudzovanie steganografickej metódy?
- Prečo prevod do formátu so stratovou kompresiou správu zničil, hoci obrázok vyzerá nezmenene?
Doplňujúce úlohy
Úloha A.1
Uprav funkciu na ukrývanie tak, aby správu nevkladala do bodov za sebou, ale podľa poradia určeného kľúčom. Zamysli sa, čo to zmení na výsledku štatistického testu z piateho kroku.
Úloha A.2
Vyskúšaj vložiť do nosiča namiesto textu iný, menší obrázok. Koľko bodov má mať nosič, aby sa doň zmestil?