EXE-packery, czyli jak w dawnych czasach pliki pakowano i zabezpieczano

dodał 20 września 2018 o 19:00 w kategorii HowTo  z tagami:
EXE-packery, czyli jak w dawnych czasach pliki pakowano i zabezpieczano

Prawie każdy programista języków niskiego i średniego poziomu spotyka się z pojęciem kompresji kodu wykonywalnego, czyli użyciem programów popularnie nazywanych EXE-packerami. W niniejszym artykule dowiecie się, dlaczego powstały, jak działają oraz dlaczego są popularne do dziś.

Nie dotykaj, bo się nie wgra…

Pierwsze techniki kompresji kodu powstawały już w czasach komputerów, które do zapisu oraz odczytu programów korzystały z magnetofonów i kaset – mowa tutaj przede wszystkim o najpopularniejszych wówczas komputerach Commodore 64 oraz ZX Spectrum. Aby wczytać program do pamięci, należało odtworzyć taśmę od początku do końca. Standardowy zapis nie był optymalny pod względem czasowym (ładowanie mogło trwać nawet do 15 minut), programiści wpadli więc na pomysł, by skompresować zapisany na taśmie kod, a następnie dekompresować go bezpośrednio do pamięci w trakcie ładowania. W takim przypadku najpierw – za pomocą standardowej metody – ładowany był tzw. „program Turbo” , który przejmował całą obsługę magnetofonu, odczytując skompresowaną zawartość z dalszej części taśmy i dekompresując ją do pamięci. W ten sposób ładowanie programu skracało się np. z 15 minut do około 3-5, w zależności od rozmiaru oryginalnego kodu. Po odczytaniu całej zawartości następowało uruchomienie.

Technika ta była najczęściej nazywana „Turbo Tape” lub „Fast Loader”, a programy pozwalające zapisywać, odczytywać i kopiować programy zapisane takimi metodami nazywały się ich różnymi wariacjami. Po pewnym czasie większość programów same w sobie zawierały już wspomniany program ładujący, wyświetlając np. w trakcie ładowania logo producenta, a programy ładujące gry bardzo często odtwarzały w trakcie ładowania ich ścieżkę dźwiękową.

Czas na pliki

Po rozpowszechnieniu się komputerów klasy PC użytkownicy i twórcy oprogramowania zaczęli zmagać się z problemami rozmiarów aplikacji i gier. Twarde dyski miały dość małą pojemność i były na początku bardzo drogie, a przy coraz bardziej rozbudowanych produkcjach zwykle jedna dyskietka (o pojemności najpierw 360 KB, ostatecznie 1,44 MB) mogła nie być w stanie pomieścić danego tytułu wraz ze wszystkimi towarzyszącymi plikami lub dodatkowymi programami. Postanowiono więc skorzystać z metody kompresji kodu wykonywalnego – powstały programy umożliwiające skompresowanie pliku .COM lub .EXE, nadal pozostawiając go uruchamialnym bez konieczności ręcznej dekompresji przed załadowaniem. Co prawda, istniały wówczas już programy takie jak PKZIP czy ARJ, jednak służyły one do kompresji całych plików i katalogów bez możliwości korzystania z nich bez konieczności ręcznego rozpakowania.

Należy tutaj wspomnieć o archiwach SFX – są to pliki wykonywalne .EXE, których zadaniem jest rozpakowanie dołączonego do ich kodu archiwum (najczęściej ZIP, 7z lub CAB) bez konieczności posiadania odpowiedniego programu dekompresującego. Obecnie najczęściej spotykaną formą takich archiwów są instalatory aplikacji, które oprócz rozpakowania plików do odpowiednich folderów przeprowadzają również konfigurację systemu, tworzą odpowiednie skróty itd. – z EXE-packerami nie mają one jednak nic wspólnego. Każdy popularny dziś archiwizator posiada możliwość utworzenia archiwum w takiej formie.

W ten oto sposób coraz bardziej rozbudowane programy mogły zmniejszyć swoją objętość na dysku (zwykle główny program w formie pliku .EXE był największym plikiem). Podobnie jak wyżej opisana metoda w przypadku taśm, plik .EXE po kompresji składał się z dwóch części: małego programu rozpakowującego kod aplikacji do pamięci (zwykle kilkaset bajtów) i samego skompresowanego kodu. Jednym z najpopularniejszych EXE-packerów, jakie powstały za czasów systemów z rodziny DOS, był rozpowszechniony jako freeware program LZEXE autorstwa Fabrice’a Bellarda (ciekawostka: jest on również autorem takich projektów jak ffmpeg oraz QEMU).

Po sukcesie LZEXE dość szybko zaczęły pojawiać się kolejne tego typu programy wykorzystujące bardziej efektywne algorytmy kompresji, a także komercyjne EXE-packery, takie jak PKLITE autorstwa firmy tworzącej znanego już wówczas PKZIP-a czy wywodzący się z naszego rodzimego podwórka WWPACK (niestety nie zdobył on dużej popularności).

Fragment pliku .EXE skompresowanego za pomocą PKLITE z widoczną charakterystyczną sygnaturą

Jako ciekawostkę można tutaj dodać informację, że za pomocą programu PKLITE w wersji 1.15 skompresowano dość sporą część narzędzi systemu MS-DOS w wersjach 6.x (np. FORMAT.COM) i niektóre programy systemów Windows 95, 98 i Me działające w graficznym trybie DOS (np. SCANDISK.EXE).

Twórcy komercyjnych rozwiązań dodawali od siebie dodatkowe funkcje do użycia w swojej aplikacji, np. autor programu mógł sprawdzać w trakcie jego działania, czy kod nie został rozpakowany ręcznie i przywrócony do pierwotnej formy. Dla prawie każdego popularnego EXE-packera zwykle powstawał program typu EXE-unpacker (przywracający oryginalny plik .EXE) albo taka funkcja była dostępna w samym kompresorze. Zaraz, zaraz…

Pozytywne efekty uboczne

Ach, no tak – rozkwit programów typu shareware! Kompresowanie kodu aplikacji nie tylko zmniejszało rozmiary plików wykonywalnych, ale także w pewien sposób zabezpieczało je przed modyfikacjami (np. w celu usunięcia ograniczeń wersji testowej, by funkcja przyjmująca kod rejestracyjny akceptowała dowolny jako prawidłowy). Modyfikacja skompresowanego kodu bez jego wcześniejszej dekompresji nie jest bowiem możliwa. Twórcy komercyjnych EXE-packerów oferowali nabywcom pełnych wersji możliwość dodania dodatkowych zabezpieczeń w celu uniemożliwienia ręcznego rozpakowania lub podejrzenia kodu, wspomniane już wcześniej funkcje do sprawdzenia, czy program nadal jest spakowany, a także dodatkowe funkcje sprawdzające integralność kodu w trakcie wykonywania skompresowanej aplikacji (ich brak uniemożliwiał prawidłowe funkcjonowanie programu). Oczywiście pewne grupy użytkowników poradziły sobie z tymi „dodatkami” – wymagało to jednak o wiele więcej pracy niż przywrócenie pierwotnej wersji pliku .EXE i modyfikacja „czystego” kodu. Jest to jednak temat na kolejny artykuł.

Nadchodzi rewolucja: UPX

Rok 1998 przyniósł pewną rewolucję w temacie EXE-packerów – do tej pory nie istniał jeszcze żaden EXE-packer, który potrafiłby kompresować nie tylko kod 16-bitowych plików .EXE uruchamianych w systemie DOS, ale również 32-bitowe aplikacje Win32. Zaczęły pojawiać się pierwsze tego typu narzędzia, obsługujące 32-bitowy kod, a najpopularniejsze z nich to wydany na licencji Open Source UPX oraz wydane komercyjnie programy ASPack i PECompact.

Ekran opisu programu UPX

Ponieważ budowa 32-bitowych plików EXE jest o wiele bardziej skomplikowana (oprócz samego kodu mogą one w sobie zawierać także inne zasoby, np. tablice tekstów, obrazki, ikony, kursory), bardzo często zdarzało się, że kod po kompresji po prostu nie uruchamiał się bądź działał błędnie, gdy chciał skorzystać z zasobów zamieszczonych w pliku z własnym kodem. Dlaczego więc te trzy narzędzia stały się najpopularniejsze? Ich twórcy największy nacisk położyli na kompatybilność ze wszystkimi wersjami systemów operacyjnych, dostępnymi kompilatorami oraz możliwymi danymi umieszczonymi oprócz kodu. Oprócz zwykłych plików .EXE obsługiwały również inne korzystające z tego samego formatu, jak np. DLL czy OCX (kontrolki ActiveX).

Twórcy UPX-a poszli jednak o krok dalej. Pozostałe narzędzia skupiały się tylko na kodzie Win32 (opcjonalnie obsługując .DLL), a dzięki temu, że sam projekt był udostępniany na licencji open source, obsługa większej liczby formatów plików została szybko rozbudowana przez społeczność. W początkowych wersjach UPX obsługiwał jedynie 16-bitowe pliki .EXE, .SYS i .COM działające w systemach DOS oraz 32-bitowe .EXE i .DLL. Obecnie kompresuje również pliki binarne uruchamiane pod systemami Linux, macOS, Windows CE, a nawet dwóch architekturach retro – Sony PlayStation (PS1) oraz Atari ST. Istnieje także możliwość użycia kompresji LZMA zamiast domyślnej, opracowanej przez autorów (aczkolwiek takie pliki będą uruchamiać się o wiele wolniej ze względu na skomplikowany algorytm), stosowanej w popularnym programie archiwizującym 7-Zip (udostępnianym również na licencji open source).

UPX jest obecnie najpopularniejszym EXE-packerem na całym świecie.

EXE-packery nadal są wykorzystywane

Pomimo upływu lat oraz ogromnego postępu w zwiększaniu pojemności dysków EXE-packery nie przestały być używane, a sam UPX jest bardzo często dołączany do różnego rodzaju kompilatorów, nawet tych komercyjnych. Dlaczego nadal korzystamy z EXE-packerów?

  • Użycie EXE-packera zabezpiecza kod aplikacji przed łatwym podejrzeniem go lub wprowadzeniem modyfikacji.
  • W przypadku dużych plików .EXE załadowanie skompresowanej wersji i jej rozpakowanie w pamięci może być szybsze niż załadowanie oryginalnego pliku bezpośrednio z dysku (a tym bardziej, gdy plik jest pobieramy z sieci lub uruchamiamy z powolnego nośnika danych).
  • Pliki .EXE bardzo często są wyraźnie mniejsze niż ich oryginalne wersje spakowane tradycyjnymi archiwizatorami (np. ZIP) – algorytmy EXE-packerów opracowane są specjalnie do kompresji kodu i są bardziej efektywne niż uniwersalne algorytmy kompresji (ma to znaczenie np. przy dystrybucji oprogramowania – przygotowanie jak najmniejszego pakietu instalacyjnego).
  • Niektóre bardzo zaawansowane obecnie komercyjne EXE-packery pozwalają na połączenie pliku .EXE oraz dodatkowych wymaganych przez niego plików .DLL (lub innych wykorzystywanych przez aplikację) do pojedynczego pliku .EXE, eliminując w ten sposób możliwe błędy powstałe w wyniku posiadania przez użytkownika niekompatybilnych bibliotek czy konieczności dystrybuowania dodatkowych plików.

Minusem użycia EXE-packerów jest minimalne opóźnienie względem uruchomienia głównego kodu programu od jego załadowania do pamięci – musi on zostać najpierw rozpakowany, zanim zostanie wykonany. Tutaj autorzy najpopularniejszych narzędzi starali się w taki sposób dostosowywać użyte algorytmy, by były one jak najszybsze przy dekompresji. W praktyce nawet na bardzo starych komputerach nie odczujemy tego opóźnienia. Twórcy programu UPX określili w 2000 roku, iż pliki skompresowane za pomocą ich narzędzia rozpakowują około 13 MB kodu w ciągu sekundy na starym procesorze Pentium 133 – dziś jest więc to ze względu na o wiele większą moc współczesnych komputerów nie do zauważenia.

Jeżeli macie ochotę pobawić się starymi EXE-packerami, możecie znaleźć całkiem ciekawą ich kolekcję na stronie ExeTools.com. Natraficie tam również na inne narzędzia do „zabawy” z plikami wykonywalnymi (strona ta niestety nie jest aktualizowana od 2002 roku).

Co dalej?

Sama idea kompresowania kodu wykonywalnego znalazła swoje dalsze zastosowanie w językach skryptowych oraz interpretowanych. Powstały kompresory kodu źródłowego takich języków jak chociażby PHP, Python czy JavaScript. Można także znaleźć w sieci kompresory kodu HTML i CSS. Temat ten nie ograniczył się więc tylko do plików .EXE i kodu maszynowego wykonywanego bezpośrednio przez procesor. Cały czas ponadto powstawały (i powstają nadal) także swego rodzaju wewnętrzne rywalizacje między autorami EXE-packerów: który z kompresorów będzie bardziej zoptymalizowany pod względem rozmiaru pliku wynikowego, rozmiaru kodu dekompresującego czy szybkości działania danego algorytmu kompresji. Polecam zaawansowanym programistom samodzielnie przetestować działanie takich narzędzi wobec własnych plików .EXE (a jeśli chodzi o platformę .NET – omówimy to w następnym artykule z tej serii).