Jak donosi DailyDarkWeb rzekomo wykradziono bazę danych serwisu LinkedIn z 2023 roku z 2.5 milionami rekordów. Jeśli to prawda, to będzie oznaczało, że LinkedIn postanowiło kontynuować bogatą tradycję, w której co ileś lat wykradane są miliony danych profilowych z ich serwisu.
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ę.

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.
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.

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://news.ycombinator.com/item?id=12345952
