Steganografia i jej analiza w Pythonie

Zgłosił się do mnie klient z projektem, którego celem było dojście do tego, czy w danym obrazku nie znajdują się ukryte informacje zakodowane poprzez steganografię.

Steganografia Kodek Online

Poniżej prezentuję skrypt w Pythonie, który wyciąga piksele w trybach horyzontalnych (czyli piksel po pikselu na wysokość obrazka) i wertykalnych z grafiki:

  • najmniej znaczące bity w składowych R
  • najmniej znaczące bity w składowych G
  • najmniej znaczące bity w składowych B
  • najmniej znaczące bity w składowych ALPHA
  • kombinowane najmniej znaczące bity w RGB
  • kombinowane najmniej znaczące bity w BGR
  • kombinowane najmniej znaczące bity w RGBA
  • kombinowane najmniej znaczące bity w ABGR

Są to najczęściej stosowane metody używane przez narzędzia do steganografii do ukrywania rozbitych danych w najmniej znaczących bitach składowych kolorów RGB.

from PIL import Image
from bitarray import bitarray


def dump(filename, content):
    f = open(filename, 'wb')
    f.write(content)
    f.close()


def stego(filename, filename_prefix):
    im = Image.open(filename)
    pixels = im.load()

    width, height = im.size

    #
    # RED, GREEN, BLUE least significant bits
    #
    byte1, byte2, byte3, byte4, bits = 0, 0, 0, 0, 0
    out1, out2, out3, out4 = bytearray(), bytearray(), bytearray(), bytearray()
    bit1, bit2, bit3, bit4 = bitarray(), bitarray(), bitarray(), bitarray()
    all1, all2, all3, all4 = bitarray(), bitarray(), bitarray(), bitarray()

    rgb, rgba = bitarray(), bitarray()

    xx = width
    yy = height
    horizontal = True

    if filename_prefix == "vertical":
        xx = height
        yy = width
        horizontal = False

    for x in range(xx):
        for y in range(yy):

            if horizontal is True:
                pixel = pixels[x, y]
            else:
                pixel = pixels[y, x]

            # separate R G B
            bit1.append(pixel[0] & 1)
            bit2.append(pixel[1] & 1)
            bit3.append(pixel[2] & 1)
            bit4.append(pixel[3] & 1)

            # combined RGB
            all1.append(pixel[0] & 1)
            all2.append(pixel[1] & 1)
            all3.append(pixel[2] & 1)
            all4.append(pixel[3] & 1)

            # RGB
            rgb.append(pixel[0] & 1)
            rgb.append(pixel[1] & 1)
            rgb.append(pixel[2] & 1)

            # RGBA
            rgba.append(pixel[0] & 1)
            rgba.append(pixel[1] & 1)
            rgba.append(pixel[2] & 1)
            rgba.append(pixel[3] & 1)

            # compose 8 bits
            if bits < 8:
                byte1 = (byte1 << 1) | (pixel[0] & 1)
                byte2 = (byte2 << 1) | (pixel[1] & 1)
                byte3 = (byte3 << 1) | (pixel[2] & 1)
                byte4 = (byte4 << 1) | (pixel[3] & 1)
                bits = bits + 1
            else:
                bits = 0
                out1.append(byte1 & 0xFF)
                out2.append(byte2 & 0xFF)
                out3.append(byte3 & 0xFF)
                out4.append(byte4 & 0xFF)

    dump(filename_prefix + "_1_RED.bin", out1)
    dump(filename_prefix + "_1_GREEN.bin", out2)
    dump(filename_prefix + "_1_BLUE.bin", out3)
    dump(filename_prefix + "_1_ALPHA.bin", out4)

    dump(filename_prefix + "_2_RED_BITS.bin", bit1)
    dump(filename_prefix + "_2_GREEN_BITS.bin", bit2)
    dump(filename_prefix + "_2_BLUE_BITS.bin", bit3)
    dump(filename_prefix + "_2_ALPHA_BITS.bin", bit4)

    bit1.reverse()
    bit2.reverse()
    bit3.reverse()
    bit4.reverse()

    dump(filename_prefix + "_3_RED_REV_BITS.bin", bit1)
    dump(filename_prefix + "_3_GREEN_REV_BITS.bin", bit2)
    dump(filename_prefix + "_3_BLUE_REV_BITS.bin", bit3)
    dump(filename_prefix + "_3_ALPHA_REV_BITS.bin", bit4)

    dump(filename_prefix + "_4_RED_BITS_RGB.bin", all1)
    dump(filename_prefix + "_4_GREEN_BITS_RGB.bin", all2)
    dump(filename_prefix + "_4_BLUE_BITS_RGB.bin", all3)
    dump(filename_prefix + "_4_ALPHA_BITS_RGB.bin", all4)

    all1.reverse()
    all2.reverse()
    all3.reverse()
    all4.reverse()

    dump(filename_prefix + "_5_RED_BITS_BGR.bin", all1)
    dump(filename_prefix + "_5_GREEN_BITS_BGR.bin", all2)
    dump(filename_prefix + "_5_BLUE_BITS_BGR.bin", all3)
    dump(filename_prefix + "_5_ALPHA_BITS_BGR.bin", all4)

    dump(filename_prefix + "_6_RGB_ALL_BITS.bin", rgb)
    dump(filename_prefix + "_6_RGBA_ALL_BITS.bin", rgba)

    rgb.reverse()
    rgba.reverse()

    dump(filename_prefix + "_6_RGB_ALL_REV_BITS.bin", rgb)
    dump(filename_prefix + "_6_RGBA_ALL_REV_BITS.bin", rgba)


if __name__ == '__main__':
    stego('IMG1.PNG', 'horizontal')
    stego('IMG1.PNG', 'vertical' )

Może komuś się kiedyś przyda.

Samurai Cop

Nie wiem dlaczego nigdy tego nie widziałem, ale to prawdziwa gratka dla fanów samurajów, kina akcji lat 90 i kiczowatego poczucia humoru 🙂

Nic lepszego dzisiaj nie zobaczysz :D, przy okazji świetny plakat promujący

Upadek StackOverflow

Już dawno się przekonałem, że StackOverflow to miejsce, gdzie jedynie zaglądam jak już naprawdę nigdzie, nigdzie indziej nie znajduję rozwiązania programistycznych problemów.

Jednak przestałem już lata temu cokolwiek samemu tam pisać z prostej przyczyny. Miejsce to stało się moderacyjnym piekłem, gdzie moderatorzy poprawiają każdą możliwą odpowiedź. Dodadzą przecinki, zmienią skróty, zmienią nawet techniczne zwroty i kod.

Tak – zmieniają kod w moich i zapewne twoich odpowiedziach, sprawdź sam jeśli kiedykolwiek tam pisałeś, możesz się zdziwić. To główna przyczyna, dla której przestałem tam się udzielać.

Dla rozrywki przywracałem stan jednej z moich oryginalnych odpowiedzi, żeby pokazać co tam się odjaniepawla:

Ponad 17 razy przywracałem moją oryginalną odpowiedź, która latami była pierwszą odpowiedzią, a moderatorzy non stop mi ją modyfikowali.

W końcu dostałem bana… Można się tego było spodziewać, ale warto nad tym pomyśleć następnym razem gdy zechcesz cokolwiek tam odpowiedzieć – czy warto rozbudowywać miejsce, gdzie chcą twoich odpowiedzi, ale nie do końca takich jakie je piszesz. Absurd.

Złote gacie dla moderatora roku

Miejsce, które miało być przystanią dla programistów – zostało piekiełkiem dla moderatorów wyjętych żywcem z Wikipedii (przy okazji przypominam dlaczego nie warto wspierać finansowo Wikipedii).

Joel Spolsky i Jeff Atwood sprzedali w 2021 StackOverflow za 1.8 miliarda dolarów (przy okazji obrazili się na Elona Muska i oboje przestali pisać na Twitterze i przenieśli się na Mastodona) i mają wyjebane jajca, a moderatorzy i boty skutecznie przeczesują każdą odpowiedź i nabijają sobie punkty na edycjach żeby potem kandydować na moderatora roku…

Stare odpowiedzi, a nowe technologie

Od jakiegoś czasu zauważyłem, że na StackOverflow i satelickich stronach z tego uniwersum coraz częściej można dostrzec brak zaktualizowanych odpowiedzi na zmieniające się technologie programistyczne.

Nowe frameworki i wersje języków programowania oferują nowe rozwiązania starych problemów, a ich opisów brak, przykładem może być składnia Python 2 vs 3 czy np. używany przeze mnie framework PHP Yii2. Myślę, że to nie przypadek, że ludzie przestali pisać odpowiedzi i nie widzę, żeby się to zmieniło na plus w przyszłości – wręcz odwrotnie.

A jakie wy macie doświadczenia ze StackOverflow?

Kalkulator kodu radiowego Jaguar Alpine

Okres wakacyjny wyjątkowo nie sprzyja mojej aktywności zawodowej, ale udało mi się dodać kolejny kalkulator samochodowy do mojej kolekcji.

Tym razem padło na radioodtwarzacze marki Jaguar Alpine.

Jeśli padł Ci akumulator w twoim Jaguarze i posiadasz radio Alpine, skorzystaj z kalkulatora kodu radiowego, aby na podstawie numeru seryjnego radia wygenerować kod odbezpieczający i odblokować radio.

Jak odblokować radio samochodowe Jaguar Alpine

Przy okazji zaktualizowałem SDK pozwalające w prosty i przyjemny sposób generować masowo kody radiowe – Radio Code Calculator. Aktualizacja objęła pakiety dla PHP, JS i Pythona.

Scraper do Steam Store

Bardzo ciekawy artykuł prezentujący analizę kodu HTML oraz Java Script w Steam Store i stworzenia scrapera w C# do wyciągnięcia danych ze struktur strony, tematy bardzo bliskie reversingowi.

Chyba jeszcze nie dostali C&D od żadnej firmy, więc na razie możemy sobie poczytać o technicznych aspektach scrapingu, bo o prawnych to chyba za dużo nie wiedzą (jeszcze).

https://www.malwarebytes.com/blog/news/2023/01/untraceable-surveillance-firm-sued-for-scraping-facebook-and-instagram-data

https://news.ycombinator.com/item?id=12345952

Książka o C++ vs Stanisław

Jeśli chcecie się pośmiać w tym smutnym jak p#zda świecie to poczytajcie miażdżące techniczne opinie o książce o C++ polskiego autora Dawida F. na stronie Ceneo, której tytułu nawet nie wymienię w obawie o pozew autora. Raz mi już groził za to, że nie chciałem usunąć wywiadu, którego tu ten geniusz sam udzielił…

https://www.ceneo.pl/126846175#tab=reviews

Polecam komentarze użytkownika Stanisław – nie warto było drażnić lwa hehe, przejechał po niej merytorycznie jak walec po drodze. Rozrywka gwarantowana!

Mój komentarz do tego wszystkiego i do tego, co na Ceneo niektórzy sugerują, że autor wystawia sobie sam pozytywne opinie i oceny, przedstawi pięknie Taylor Swift:

A jak znacie jakieś dobre książki o C++ to zostawcie linka w komentarzu. Może się czegoś ciekawego nauczę 😀