Wersja i historia tego faq
Podziękowania
Kontakty z autorami tekstów
Co to jest ten exe packer - smola
Różnica między packerem a crypterem - smola
Jak dziala taki program - smola
Jak rozpoznać czy i czym program jest spakowany - smola
Metoda standardowa - smola
Procdump
Zrzucanie zawartości procesu z pamięci do pliku exe - smola (niepełny)
Procdump i Bhrama server - smola
SoftIce i DumpIce - smola
TRW2000
Odbudowa tabeli importów w plikach PE - TiTi/BLiZZARD (tłum. smola)
Skrypty w Procdumpie na przykładzie UPX v.84 - smola
Jak rozpakowywać pliki for newbies - bEANUS^CookieCrK
Jak rozpakować programy spakowane nieznanymi exe-packerami pod Win - GustawKit
Usunięcie ProcDumpem pakietu Vbox4 z Ulead COOL 3D v2.0 - GustawKit
Komercyjne zabezpieczenie softSENTRY Release 2.008 - GustawKit
Jak debugować i rozpakować Crunch v1.0 - GustawKit
Nagłówek PE - smola (niepełny)
Nagłówek sekcji
Tabela importów
Słownik pojęć
Lista przydatnych tekstów - smola
Linki do stron
Ze względu na dość duże zainteresowanie zagadnieniami związanymi
z plikami exe, ich pakowaniem, rozpakowywaniem i budową a także
tym że od dłuższego już czasu zajmuję się prawie wyłącznie
tym tematem, postanowiłem coś o tym wszystkim napisać. Mam
nadzieję że to pomoże chociaż kilku osobom poznać głębiej
ten temat i zachęci do własnych eksperymentów. Chciałbym również
aby ten tutorial przeistoczył się w coś w rodzaju FAQ a
mianowicie każdy kto czuje się na siłach i posiada jakąś
wiedzę w tym temacie dołożył do niego swoje 3 grosze. Sądzę,
że dzięki istnieniu kilku grup crackerskich w Polsce a co za
tym idzie wielu specjalistów ten tutorial stanie się prawdziwą
skarbnicą wiedzy. Więc jeśli już napisałeś coś na ten
temat, chcesz przetłumaczyć jakiś tekst albo rozpakowałeś już
jakiegoś progsa, to skontaktuj się ze mną (smola@pcplus.com.pl) a ja
z pewnością umieszczę Twój tekst wraz z Twoją ksywką. Tekst
nie musi być formatowany, mile widziane polskie znaki, najlepiej
w windows (notatnik rulez! :). Prawdopodobnie od następnej
wersji będzie on wydawany w formie pliku html więc będzie prościej
coś znaleść i oczywiście będzie go można umieścić na
swojej stronie www.
smola
Najnowsza wersja tego faq będzie zawsze dostępna na www.crackpl.site.pl i www.smola.prv.pl.
Najprościej jest to program mający za zadanie ukryć dane w wykonywalnym pliku exe przed oczami niepowołanych osób, np. przed crackerami, a przez to utrudnić zminę jego zawartości, jak również zmniejszenie objętości zajmowanej przez plik na dysku (nie zawsze). Generalnie exepackery dzielą się na dwie podstawowe grupy:
Najczęściej jednak spotyka się pakery które zawierają w sobie zarówno procedury pakujące oraz szyfrujące. W zasadzie to i packer jest swojego rodzaju crypterem bo kod wyjściowy programu jest totalną "kaszanką" i nie przypomina normalnego kodu programu.
Wypadałoby teraz wyjaśnić dlaczego używam nazwy packer dla
obu tych rodzajów. Otóż w potocznym języku zwykło się tak mówić
na wszelkie programy modyfikujące plik wykonywalny. Dla dokładniejszego
określenia można oczywiście użyć słowa crypter ale
zazwyczaj wystarczy powiedzieć że progs jest spakowany jakimś
exepackerem. Warto jeszcze dodać, że dla większości pakerów
istnieją ich przeciwne odpowiedniki zwane depakerami (zwanymi również
depacker, unpacker, decruncher), czyli programy przywracające
zawartość spakowanych programów do ich pierwotnych wersji.
smola
W zasadzie to po wyciągnięciu wniosków z poprzedniego
tekstu można się tego domyślić. :) Otóż program spakowany
packerem jest zawsze krótszy niż przed spakowaniem. To chyba
oczywiste. Natomiast w przypadku cryptera plik wyjściowy jest dłuższy
niż oryginalny (np. PE-Pack), oczywiście jeśli program nie
jest dodatkowo skompresowany.
smola
Ogólna zasada działania exepackerow jest podobna do działania
loadera czy nawet wirusa :), tzn. najpierw jest uruchamiana
procedura rozpakowująca która to odtwarza strukturę execa
sprzed kompresji i kiedy taki exec jest już odtworzony następuje
skok do oryginalnego EP (entry point - to taki punkt startowy od
którego zaczyna się wykonywać program). Nasze zadanie polega
na znalezieniu właśnie tego miejsca i wykonaniu zrzutu zawartości
pamięci tego odbudowanego execa. Nie zawsze jest to proste, często
taki packer zwiera procedury antydebug, zbędny kod utrudniający
śledzenie jak również może zamazywać cześć nieistotnych już
danych w kodzie execa a to się wiąże z tym że po zrobieniu
zrzutu pamięci taki exec po prostu nie będzie działał.
Jedynym wyjściem jest tylko analiza krok po kroku procedury
rozpakowującej execa pod debuggerem.
smola
Oto kilka zasad które nas upewniają że program jest spakowany (kolejność od najbardziej wiarygodnego):
Generalnie dobrym zwyczajem przed debugowaniem każdego proga
jest obejrzenie jego kodu pod jakimś hex edytorem i ew. Procdumpem. Najczęściej na podstawie powyższych
testów można już określić rodzaj użytego exepackera i podjąć
stosowne kroki celem rozpakowania. Nieocenionym programem tutaj
jest oczywiście Procdump. Jeśli jednak wciąż nie wiemy czym
spakowany jest dany progs to możemy jeszcze użyć jakiegoś
analizatora. Jest to specjalny program który rozpoznaje
kilkadziesiąt rodzajów formatów plików. Wartym użycia jest
np. FileInfo. Jego obecna wersja to 2.12 z dnia 31/01/2000. Jeśli
i to nie pomaga to niestety pozostaje tylko jego prześledzenie
pod debuggerem krok po kroku. Niezastąpionymi są wtedy TRW2000 albo SoftIce
+ DumpIce.
smola
Stosunkowo najprościej i najszybciej jest rozpakować jakiś
program używając depakera napisanego tylko do konkretnego
pakera. Czyli np. do programu spakowanego aspack'iem najlepiej użyć
unaspack. Zazwyczaj depakery mają taką samą nazwę jak paker z
tym że zawierają przedrostek 'Un' bądź 'De'. Niestety wciąż
są wypuszczane coraz to nowsze wersje programów pakujących które
są niekompatybilne w dół (tzn. że depaker napisany do wersji
np. 1.30 najczęściej nie działa z wersją 1.31) w wyniku czego
jesteśmy zmuszeni do użycia innych metod dekompresji. Mimo
wszystko jest to i tak najlepszy sposób rozpakowywania, ponieważ
w 99% przypadków rozpakowany program wygląda tak samo jak przed
kompresją.
smola
Termin ten zwany również ProcessDump polega na przeniesieniu procesu z pamięci komputera do wykonywalnego pliku exe. W zasadzie można to zrobić na dwa sposoby: albo przez FullDump albo przez PartialDump. Pierwszy z nich polega na zrzuceniu zawartości procesu w jednym całym kawałku czyli wszystkie sekcje programu znajdują się w tych miejscach na które wskazują ich RVA. Mankamentem tego rozwiązania jest to że taki plik może zajmować bardzo dużo miejsca na dysku i dlatego jest też druga metoda znana pod nazwą PartialDump, czyli zrzut procesu (a w zasadzie jego poszczególnych sekcji) z pamięci po kawałku. Ten sposób pozwala na znaczne oszczędności miejsca na dysku jednak zmusza nas do późniejszego ręcznego "posklejania" tych wszystkich sekcji. Jakiego by jednak sposobu nie użyć, to i tak czeka nas ręczna odbudowa takiego pliku. Przede wszystkim chodzi tutaj o sekcję importów, czyli miejsce w pliku PE gdzie znajdują się nazwy wszystkich funkcji z jakich korzysta program. W uproszczeniu problem leży w tym iż w miejscach gdzie powinny być wskaźniki (pointer) na stringi z nazwami funkcji są rzeczywiste adresy tych funkcji. Dokładny opis tego zagadnienia wraz ze sposobem naprawy znajduje się w Odbudowa tabeli importów w plikach PE.
(to jeszcze nie koniec)
Dzięki uaktywnieniu Bhrama Server możemy zdalnie sterować ProcDumpem czy to poprzez pluginy (m.in. do vbox, securom czy petit) czy SoftIce (z zainstalowanym DumpIce). Generalnie chodzi o to aby przejść prockę depakujacą exepackera, zatrzymać dalsze wykonywanie procesu i przekazać sterowanie do Procdumpa który to pięknie nam zrzuca pamięć procesu i zapisuje na dysk gotowego execa. Bhrama jest właśnie takim łącznikiem.
Komunikacja z Bhrama polega na samodzielnym rozpakowaniu programu ew. ominięciu procedur antydebug, zamrożeniu śledzonego procesu (suspend) i umieszczeniu jego identyfikatora PID (process ID) oraz dodatkowych opcji rozpakowania (szczególowe info w pliku bhrama.txt dołączonym do Procdumpa) w odpowiednim miejscu struktury BhramaComStruc, znalezieniu uchwytu okna 'ProcDump32 - Dumper Server' a następnie wysłaniu tego wszystkiego funkcją SendMessage i zakończenia własnego procesu.
SendMessage uchwyt_okna_Bhrama_Server,WM_COPYDATA,0,offset DataServices
W zależności od potrzeby Bhrama oferuje 2 rodzaje usługi:
Full Dump (wszystkim zajmie się Procdump) albo Partial Dump. W
tym drugim przypadku musimy sami określić początek i długość
obszaru pamięci do zrzucenia. Po wysłaniu komunikatu WM_COPYDATA
Procdump automatycznie odwali za nas całą "czarną robotę"
i zapisze gotowego exeka do pliku. Więcej szczegółów można
znaleść w dokumentacji Procdumpa jak również poprzez analizę
źródeł pluginów.
smola
SoftIce jak już pewinie wiesz jest debuggerem, czyli programem mogącym śledzić wykonywanie się innego programu krok po kroku. Natomiast DumpIce jest swoistego rodzaju nakładką na SI, dodającą jemu wiele nowych możliwości. Obecnie DumpIce jest w wersji 6.11 (26/01/2000) i pozwala nam nawet na słuchanie swoich ulubionych mp3 w trakcie debugowania... :) Ale to nie jest jego najważniejszą cechą. Otóż po zainstalowaniu go umożliwia nam bezproblemowe zrzucenie zawartości pamięci bezpośrednio z SI do pliku, jak równeż może się komunikować przez Bhrama serwer z Procdumpem. A co to oznacza to chyba nie muszę mówić. Wystarczy że uruchomimy Procdumpa i uaktywnimy Bhrama Serwer, następnie przejdziemy w SI przez procedure depakującą exepackera, ustawimy sie na oryginalnym EP (entry point) i wpiszemy komendę:
PAGEIN B ProcDump32 - Dumper Server
a Procdump nam pięknie zrzuci proces do pliku exe. Działa bezbłędnie. Jeśli masz jakieś kłopoty z działaniem tej komendy to dopisz sobie do winice.dat następującą linijkę:
F3=" PAGEIN B ProcDump32 - Dumper Server; "
Po przeładowaniu systemu naciśnięcie klawisza F3 powoduje
zapisanie gotowego exeka na dysk (oczywiście Bhrama Server musi
być aktywny).
smola
Tekst napisany 18.07.99 przez TiTi/BLiZZARD
Dość swobodnie przetłumaczony 3.02.00 (rev. 01) przez smola/crackpl
Cześć wszystkim! Napisałem ten tekst, ponieważ w czasie kiedy pracowałem nad programem typu process dumper (program który zrzuca zawartość uruchomionego procesu do pliku exe) testowałem wiele pakerów/enkrypterów które czyniły tablicę importów nieużyteczną i zrzucone pliki exe wymagały odbudowywania tej tablicy. Szukając w wielu miejscach w sieci informacji na ten temat nie znalazłem nic konkretnego, więc jeśli się tym interesujesz, to masz przed sobą tego małego helpa.
Dla przykładu program spakowany Petite v2.1 po zrzuceniu z pamięci do pliku wymaga odbudowania tabeli importów (a dokładniej jej skorygowania) aby plik exe uruchamiał się poprawnie. Podobnie jest w przypadku programów spakowanych aspack'iem, pepack'iem, pesentry itp. To właśnie dlatego funkcje odbudowy tabeli importów są niezbędne w każdym programie zrzucającym proces do pliku. Np. Phoenix Engine napisany przez G-RoM/UCF użyty w ProcDump'ie albo PERebuilder napisany przez Virogen/PC i mnie.
Temat ten jest bardzo specyficzny i dość skomplikowany, dlatego zakładam że znasz już strukturę plików exe. Jeśli nie, to powinieneś zapoznać się z jakąś dokumentacją na ten temat (na końcu pliku zamieściłem spis użytecznych tekstów - smola).
Najpierw parę słów o tabeli importów oraz RVA/VA (uwaga, autor w oryginale używa zamiennie słów offset i adres - smola).
Względny adres tabeli importów (ang. import table relative virtual address - RVA) jest zapisany w odpowiednim miejscu w nagłówku pliku PE. Miejsce to nazywa się directory entry a jego offset można wyliczyć następująco: offset nagłówka PE + 80h. Adres ten nie odpowiada offsetowi tabeli importów w pliku (z wyjątkiem sytuacji kiedy plik został zrzucony z pamięci), więc pierwszą rzeczą jaką trzeba zrobić jest znalezienie tabeli importów w pliku PE i przeliczenie RVA na odpowiedni adres wirtualny VA (virtual address). Jest kilka sposobów aby to osiągnąć. Można napisać własną procedurę "mielącą" (parser) nagłówki sekcji (sections directory) i wyliczającą poprawny adres wirtualny VA ale łatwiej jest to zrobić poprzez użycie funkcji API która została specjalnie do tego celu stworzona. Funkcją tą jest zawarta w bibliotece IMAGEHLP.DLL (biblioteka używana przez Windows 9x oraz Windows NT) i nazywa się ImageRvaToVa. Poniżej jej opis (pełny jej opis jest zawarty w msdn library):
# LPVOID ImageRvaToVa( # IN PIMAGE_NT_HEADERS NtHeaders, # IN LPVOID Base, # IN DWORD Rva, # IN OUT PIMAGE_SECTION_HEADER *LastRvaSection #); # #Parametry : # NtHeaders # Wskaźnik do struktury IMAGE_NT_HEADERS. Tą strukturę # można uzyskać poprzez wywołanie funkcji ImageNtHeader. # Base # Określa adres bazowy obrazu pliku który jest mapowany # wewnątrz pamięci poprzez wywołanie funkcji MapViewOfFile. # Rva # Określa gdzie ma położyć RVA. # LastRvaSection # Wskaźnik do struktury IMAGE_SECTION_HEADER która # opisuje ostatnią sekcję RVA. Parametr opcjonalny. # Jeśli podany, wskazuje na zmienną która zawiera # wartość ostatniej sekcji użytej do określenia # obrazu pliku do przeliczenia RVA na VA.
Jak widać jest bardzo prosta w użyciu... :) Po prostu musisz
mieć mapę swojego pliku PE w pamięci i wtedy wywołujesz tą
funkcję aby otrzymać poprawny VA do tabeli importów.
Zauważ, że powyżej pominąłem wszystkie znaczniki RVA/VA, ale
nie zapomnij o przeliczeniu jednego na drugi kiedy będziesz
odczytywać lub zapisywać RVA z/do pliku PE podczas jego
odbudowy.
Poniżej jest pełny opis odbudowania zmienionej tabeli importów w pliku PE skompresowanego przez petite v2.1 (plik został zrzucony bezpośrednio z pamięci).
00 są reprezentowane przez spację ' ' inne znaki są reprezentowane przez '-' 0000C1E8h : 00 00 00 00 00 00 00 00 00 00 00 00 BA C2 00 00 ---- 0000C1F8h : 38 C2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ---- 0000C208h : C5 C2 00 00 44 C2 00 00 00 00 00 00 00 00 00 00 -------- 0000C218h : 00 00 00 00 D2 C2 00 00 54 C2 00 00 00 00 00 00 -------- 0000C228h : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000C238h : 7F 89 E7 77 4C BC E8 77 00 00 00 00 E6 9F F1 77 -------- ---- 0000C248h : 1A 38 F1 77 10 40 F1 77 00 00 00 00 4F 1E D8 77 -------- ---- 0000C258h : 00 00 00 00 00 00 4D 65 73 73 61 67 65 42 6F 78 MessageBox 0000C268h : 41 00 00 00 77 73 70 72 69 6E 74 66 41 00 00 00 A wsprintfA 0000C278h : 45 78 69 74 50 72 6F 63 65 73 73 00 00 00 4C 6F ExitProcess Lo 0000C288h : 61 64 4C 69 62 72 61 72 79 41 00 00 00 00 47 65 adLibraryA Ge 0000C298h : 74 50 72 6F 63 41 64 64 72 65 73 73 00 00 00 00 tProcAddress 0000C2A8h : 47 65 74 4F 70 65 6E 46 69 6C 65 4E 61 6D 65 41 GetOpenFileNameA 0000C2B8h : 00 00 55 53 45 52 33 32 2E 64 6C 6C 00 4B 45 52 USER32.dll_KER 0000C2C8h : 4E 45 4C 33 32 2E 64 6C 6C 00 63 6F 6D 64 6C 67 NEL32.dll_comdlg 0000C2D8h : 33 32 2E 64 6C 6C 00 00 00 00 00 00 00 00 00 00 32.dll
Jak widać tabela importów podzielona jest na trzy główne części:
od C1E8h->C237h : Tablica ze strukturą IMAGE_IMPORT_DESCRIPTOR,
każdy jeden odpowiada importowanej bibliotece DLL.
Koniec tablicy jest wypełniony zerami.
IMAGE_IMPORT_DESCRIPTOR struct
OriginalFirstThunk dd 0 ;RVA do original unbound IAT
TimeDateStamp dd 0 ;tutaj nie używany
ForwarderChain dd 0 ;tutaj nie używany
Name dd 0 ;RVA do stringu z nazwą biblioteki DLL
FirstThunk dd 0 ;RVA do tablicy IAT
IMAGE_IMPORT_DESCRIPTOR ends
od C238h->C25Bh : Tablica z długimi słowami (DWORD) wywołująca 'IAT'
wskazywany przez FirstThunk ze struktury IMAGE_IMPORT_DESCRIPTOR
Każdy DWORD z tej tablicy odpowiada importowanej funkcji.
od C25Ch->C2DDh : Są to stringi z nazwami importowanych funkcji oraz
bibliotek DLL. Problem leży w tym że nie ma tu określonego
porządku, czasem nazwy bibliotek są przed nazwami funkcji,
czasami odwrotnie a nieraz są wymieszane ze sobą.
OriginalFirstThunk jest tablicą IAT (import address table - IAT) którą PE loader poszukuje w pierwszej kolejności. Jeśli występuje, to wtedy PE loader używa jej do skorygowania ewentualnych problemów w tablicy FirstThunk IAT. Jednorazowo wczytuje do pamięci każdy DWORD z tablicy FirstThunk zawierający RVA do stringu z nazwa funkcji i podmienia go z RVA na prawdziwy adres funkcji (ta lokacja w pamięci nie może być używana kiedy wywoływana jest ta funkcja). Więc najprościej: to nie jest problem z tablicą importów, bo jest ona odpowiednio wypełniana a OriginalFirstThunk zostaje niezmieniony.
Po tym krótkim wyjaśnieniu dochodzimy do sedna problemu. Otóż jeśli spróbujesz teraz uruchomić taki plik exe zawierający tabelę pokazaną powyżej, nie zostanie on wczytany a windows wyświetli komunikat o błędzie. Dlaczego? Ano dlatego, że tablica OriginalFirstThunk została usunięta.
Zapamiętaj to, że dla każdej struktury IMAGE_IMPORT_DESCRIPTOR w tej tabeli importów OriginalFirstThunk zawiera wartość 00000000h. Hm, domyślamy się, że kiedy plik exe jest uruchamiany to PE loader próbuje wziąć nazwy importowanych funkcji z tablicy FirstThunk, ale jak sobie przypominasz, ta tablica nie zawiera RVA do stringów z nazwami funkcji, tylko RVA do adresów funkcji w pamięci.
To co teraz powinniśmy uczynić aby otrzymać działający plik exe, to odbudowa zawartości tablicy FirstThunk poprzez wstawienie do niej wartości wskazujących na stringi z nazwami funkcji które widzieliśmy w trzeciej części tabeli importów. Wbrew pozorom nie jest to trudne zadanie, ale potrzebujemy wiedzieć który IAT odpowiada danej funkcji jako że stringi z nazwami funkcji nie są poukładane w ten sam sposób jak członkowie FirstThunk.
No więc dla każdego IAT potrzebujemy zidentyfikować nazwę funkcji która jemu odpowiada. W tej chwili mamy już nazwy bibliotek DLL, ponieważ mamy IMAGE_IMPORT_DESCRIPTOR. Wskaźniki DWORD do ich nazw nie zostały oczywiście zmienione.
Jak już wspomniałem wcześniej, każdy uszkodzony IAT jest RVA do adresu funkcji w pamięci. Te adresy w trakcie trwania sesji, czyli dopóki nie zrestartujesz kompa, nie zmieniają się na inne, więc możemy porównać je z adresami funkcji na które wskazuje uszkodzony IAT i ustawić na nim wskaźnik (pointer) do stringów z nazwami funkcji.
Aby to zrobić skorzystamy z bardzo użytecznej funkcji API z biblioteki Kernel32.dll która nosi nazwę GetProcAddress. Zwraca nam ona adres zadanej funkcji. Poniżej jej opis:
GetProcAddress(
HMODULE hModule, // uchwyt (handle) do biblioteki DLL
LPCSTR lpProcName // nazwa funkcji
);
Parametr hModule jest uchwytem do biblioteki DLL (określa on adres bazowy (base address) obszaru modułu w pamięci), który to możemy odczytać używając kolejnej funkcji API GetModuleHandleA:
HMODULE GetModuleHandle(
LPCTSTR lpModuleName // adres nazwy modułu dla którego zwrócony zostanie uchwyt
);
lpModuleName jest po prostu wskaźnikiem do nazwy pliku DLL który to możemy pobrać z członka struktury IMAGE_IMPORT_DESCRIPTOR.Name
lpProcName jest wskaźnikiem do stringu zawierającego nazwę funkcji.
Więc aby naprawić uszkodzony IAT, musimy sprawdzić wszystkie nazwy funkcji występujące w trzeciej części tabeli importów dopóki GetProcAddress nie zwróci nam adresu szukanej funkcji.
Wiedz, że czasem funkcja jest importowana poprzez liczbę (ordinal number). Te numery są słowami (WORDS) i wyliczać je należy następująco: offset nazwa_funkcji - 2. Tak więc Twoja procedura "mieląca" powinna sprawdzać jak dana funkcja jest importowana; czy przez nazwę czy przez numer.
Spróbuje wyjaśnić w jaki sposób należy naprawić pierwszą importowaną funkcję z pierwszej importowanej biblioteki DLL na podstawie powyższego opisu tabeli importów.
Opisałem ogólny sposób postępowania. Oczywiście działa on tylko na DLL'e które są aktualnie wczytane do pamięci. Dla pozostałych musisz je najpierw wczytać lub przekształcić ich tablice eksportów aby znaleźć poprawne adresy funkcji.
To chyba tyle. Komentarze, raporty o jakości itp. miłe
rzeczy mile widziane... :)
smola
Aby rozpakować takiego UPX należy posiadać Procdumpa 1.5 (obecna wersja to 1.6.2), jakiś hex edytor np. Hex Workshop i oczywiście Notepada... :) Pierwszym krokiem który należy uczynić jest użycie standardowego skryptu UPX z Procdumpa. Niestety w moim przypadku on nie działał a kilka prób jego modyfikacji również nie przyniosło żadnych rezultatów. No to zabrałem się za samego execa. I tu właśnie jest ta sytuacja o której piszesz z niemożnością użycia loadera z si. Ale my już wiemy co trzeba zrobić z tym fantem (vide wcześniejsze problemy z pająkiem itp. - polecam poczytać archiwum listy crackpl), więc zmieniamy characteristic pierwszej sekcji UPX0 z e0000040 na e0000020.
Gdyby Procdump z jakiś powodów nie mógł wczytać execa (czasami tak mam) to należy użyć jakiegoś hex edytora (np. Hex Workshop lub Hiew). Atrybuty sekcji znajdują się na samym końcu nagłówka sekcji i zaraz przed nazwą następnej sekcji. Pamiętać należy że bajty zapisywane są w notacji intela czyli np. liczba 12345678h jest zapisana jako 78563412h.
Dobra, teraz odpalasz loadera i swój prog i lądujesz w si. Jesteś? :) Ok. Więc teraz przewijasz okienko code aż dochodzisz do miejsca w którym następuje ostatni skok jmp xxxxxxxx. W tym to właśnie miejscu procedura rozpakowująca skacze do rozpakowanego i gotowego już do użycia pliku exe. Zapisujesz ten adres gdzie ma nastąpić skok np. jmp 401000 oraz mnemoniki tego skoku czyli e9 i bajty odpowiadające długości skoku (w si można włączyć ich podgląd komendą code on) i wychodzisz z si oraz zamykasz tego proga. Teraz dla odmiany odpalasz Notepada i edytujesz plik scrip.ini z Procdumpa. Twoja wersja UPX powinna wyglądać tak:
Pxx=UPX 0.84
gdzie xx jest kolejnym numerem skryptu, nazwa może być dowolna.[UPX 0.84]
L1=OBJR
L2=LOOK E9,xx,xx,xx,xx
L3=BP
L4=EIP
L5=STEP
OPTL1=00000000
OPTL2=01010001
OPTL3=01010001
OPTL4=00030000
OPTL5=00000000
gdzie xx,xx,xx,xx są właśnie tymi bajtami odpowiadającymi długości skoku z jmp, nazwa oczywiście taka sama jak powyżej.
Zapisujesz to wszystko, odpalasz ProcDumpa i rozpakowujesz
swojego execa używając tego skryptu. Po rozpakowaniu możesz
jeszcze (ale nie musisz) ustawić poprawny entry point bo ten
wskazuje na adres jmp xxxxxxxx a nie na sam xxxxxxxx. EP
obliczasz odejmując od xxxxxxxx adres bazowy (image base), czyli
np. xxxxxxxx to 401000 a image base to 400000, wiec entry point
to 1000.
smola
Aby SI poprawnie działał trzeba go zpatchować. Służy do tego IceDump v5.17 (jest już 6.11). Więc jedziemy. Po rozpakowaniu IceDump’a wchodzimy do katalogu Patcher i odpalamy plik patcher.bat aby podać wersję, ścieżkę i akcję czyli czy zrobić backup czy odtworzyć już zpatchowany winice.exe. Ja mam wersję 3.25 więc wpisuję:
Patcher 325 c:\soft P
"P" służy do tego aby zrobić backup w pliku winice.id. Ten kto ma SI v4.0 wpisuje zamiast 325 - 400. Natomiast 'c:\soft' to katalog gdzie masz zainstalowanego SI. Proste chyba. :) Ok, po zpatchowaniu winice.exe należy zrestartować system aby móc korzystać z dodatkowych opcji jakie daje DumpIce.
Jedziemy dalej. Uruchamiamy ProcDumpa i opcję Bhrama Server. Teraz proponuje spakować jakiś program aspackiem, np. calc.exe. Włączamy więc ascpacka i robimy kaszankę... Ok, tyko jak to później rozpakować? Już się robi. Należałoby najpierw złapać w SI Entry Point ale to nie jest takie łatwe, bowiem EP jest zmieniony przez aspacka. Ja opisze inny sposób. Każdy program możemy złapać na przerwaniu 3 czyli CCh. I tak też zrobimy. Wchodzimy do Hiew'a ładujemy spakowanego calc.exe i teraz idziemy do Entry Point, czyli zmieniamy wyświetlanie pliku w hexach -> F4 potem F8 i F5 i jesteśmy w Entry Point. Jest tam mnemonik PUSHAD czyli polecenie dla procesora wysyłające wszystkie rejestry na stos. Zamiast niego wpisujemy Int 3. Czyli F3, F2 i Int 3. Zapisujemy zmiany naciskając F9. Teraz przechodzimy do SI gdzie musimy włączyć łapanie programu na przerwanie 3 (jeśli tego nie zrobimy to nasz program wygeneruje wyjątek o błędzie), więc wpisujemy:
i3here on
OK, po tym zabiegu uruchamiamy calc.exe... WOW, program się zatrzymał i mamy jakiegoś tam calla... Tylko że wcześniejszy kod pushad został zmieniony na CCh i coś trzeba z tym zrobić. Jakby tu cofnąć kod? To proste, od EIP trzeba odjąć jeden. Mamy EIP = 00413001h, zmieniamy go na 00413000. Oto kod wskazywany przez EP:
0137:00412FFF FFCC DEC ESP 0137:00413001 E800000000 CALL 00413006 0137:00413006 5D POP EBP 0137:00413007 81ED0A4A4400 SUB EBP,00444A0A 0137:0041300D BB044A4400 MOV EBX,00444A04 0137:00413012 03DD ADD EBX,EBP 0137:00413014 2B9DB1504400 SUB EBX,[EBP+004450B1] 0137:0041301A 83BDAC50440000 CMP DWORD PTR [EBP+004450AC],00
Pod adresem 00412FFFh mamy FFCCh. CCh to wiadomo Int 3 i właśnie na to złapał się SI. I teraz aby została wykonana komenda PUSHAD musimy przywrócic orginalny EP (zmiana EIP z 00413001h na 00413000h) i dopisac mnemonik PUSHAD, więc zmieniamy najpierw EIP:
r eip eip-1
Teraz zmieniamy mnemonik Int 3 na PUSHAD:
a eip
Komenda ‘a’ uaktywnia wbudowany w SI prosty kompilator, więc teraz wpisujemy:
pushad [RETURN][ESC]
Ok, jedziemy dalej. Musimy wyjść z calla rozpakowującego kod execa. Naciskamy F10 aż dojdziemy do kodu:
0137:004132F2 0BC9 OR ECX,ECX 0137:004132F4 741C JZ 00413312 0137:004132F6 781A JS 00413312 0137:004132F8 AC LODSB 0137:004132F9 3CE8 CMP AL,E8 0137:004132FB 7408 JZ 00413305 0137:004132FD 3CE9 CMP AL,E9 0137:004132FF 7404 JZ 00413305 0137:00413301 43 INC EBX 0137:00413302 49 DEC ECX 0137:00413303 EBED JMP 004132F2 0137:00413305 291E SUB [ESI],EBX 0137:00413307 83C305 ADD EBX,05 0137:0041330A 83C604 ADD ESI,04 0137:0041330D 83E905 SUB ECX,05 0137:00413310 EBE0 JMP 004132F2
Tutaj jest procedurka która ładuje rozpakowane bajty dopóki ECX jest różne od zera. Musimy albo wciskać F10 tyle razy ile jest w ECX albo napisać:
g 00413312
OK, jesteśmy dalej. Teraz już możemy nacisnąć F12 bowiem program został rozpakowany. SI zatrzyma nam się w tym miejscu:
0137:0040534E 64A100000000 MOV EAX,FS:[00000000] 0137:00405354 55 PUSH EBP 0137:00405355 8BEC MOV EBP,ESP 0137:00405357 6AFF PUSH FF 0137:00405359 6830A64000 PUSH 0040A630 0137:0040535E 686C654000 PUSH 0040656C 0137:00405363 50 PUSH EAX 0137:00405364 64892500000000 MOV FS:[00000000],ESP
I to właśnie tu zaczyna się nasz rozpakowany program! wuuaaalaaaaaaaaa! Teraz czas zrzucić to przez Bhrama Server w Procdumpie, czyli wydajemy komendę:
Pagein B ProcDump32 Dumper – Server
Co oznacza że całość pamięci zostanie przepuszczona przez okienko Bhrama Server a Procdump się grzecznie zapyta o nazwę pliku. I już. Mamy rozpakowanego execa. !!! moje gratulacje !!!
bEANUS^CookieCrK
http://www.cookiecrk.z.pl/
e-mail: cookiecrk@z.pl
Kompresja plików exe może sprawić początkującym trochę kłopotów z dobraniem się do programu. Dostaję często pytania na ten temat więc postanowiłem rozpocząć serię tekstów na ten temat. Na początek informacje o których dyskutowaliśmy na liście. Postanowiłem napisać parę rad jak dobrać się do takich plików. Za przykład posłuży nam WinAmp v2.00 i świetny programik ProcDump 1.0 beta. Otóż pewne jest, że każdy plik uruchomieniowy który jest skompresowany, względnie szyfrowany, można zawsze zdekompresować, ponieważ to właśnie się dzieje w pamięci podczas uruchomienia programu. Często program tak skompresowany zawiera sztuczki antydebug a szczególnie anty SoftIce. Problem sztuczek anty na razie pominę bo nie jest on istotny w naszym przykładzie (może opiszę innym razem). Cóż, na początek zawsze należy przyglądnąć się startowemu kodowi programu (tam gdzie skacze 'entry point'). Otóż, dla programów Win32 często taki kod wygląda podobnie i wywołuje kilka standardowych startowych funkcji API. Dla np. Notatnika Win95 przy Entry Point 1000h taki kod wygląda mniej więcej:
014F:00401000 55 PUSH EBP --> Entry Point 014F:00401001 8BEC MOV EBP,ESP 014F:00401003 83EC44 SUB ESP,44 014F:00401006 56 PUSH ESI 014F:00401007 FF1548734000 CALL [KERNEL32!GetCommandLineA] 014F:0040100D 8BF0 MOV ESI,EAX 014F:0040100F 8A00 MOV AL,[EAX]................ coś tam014F:0040104C 50 PUSH EAX 014F:0040104D FF1558734000 CALL [KERNEL32!GetStartupInfoA]..............., coś tam014F:00401064 6A00 PUSH 00 014F:00401066 6A00 PUSH 00 014F:00401068 FF155C734000 CALL [KERNEL32!GetModuleHandleA] 014F:0040106E 50 PUSH EAX 014F:0040106F E87B0E0000 CALL 00401EEF -->uruchomienie programu 014F:00401074 50 PUSH EAX 014F:00401075 8BF0 MOV ESI,EAX 014F:00401077 FF1554734000 CALL [KERNEL32!ExitProcess] --> zakończenie programu.
Dla większości programów wygląda to podobnie. Natomiast w przypadku kompresji lub szyfracji programu w miejscu Eentry Point jest z reguły funkcja dekompresująca lub deszyfrująca. Wygląda to tak:
W taki wypadku należałoby znaleźć adres gdzie w pamięci lokowany jest dekompresowany kod i moment skoku do niego. Cóż, nie będę opisywał sposobu jak to szukać bo kod dekompresji jest z reguły krótki i można go prześledzić czy to SoftIce czy innym debuggerem. Przyglądnijmy się w takim razie kodowi startowemu WinAmp 2.0:
:u 4d1000 l f014F:004D1000 669C PUSHF 014F:004D1002 60 PUSHAD 014F:004D1003 E8CA000000 CALL 004D10D2 ---> skok do proc. dekompresji 014F:004D1008 0300 ADD EAX,[EAX] 014F:004D100A 0400 ADD AL,00 014F:004D100C 0500060007 ADD EAX,07000600:u eip l 8f014F:004D10D2 58 POP EAX 014F:004D10D3 2C08 SUB AL,08 014F:004D10D5 50 PUSH EAX...coś tam014F:004D1108 50 PUSH EAX 014F:004D1109 800424BF ADD BYTE PTR [ESP],BF 014F:004D110D 833A00 CMP DWORD PTR [EDX],00 014F:004D1110 0F84A7140000 JZ 004D25BD ---> zakończenie dekompresji 014F:004D1116 F70200000080 TEST DWORD PTR [EDX],80000000 014F:004D111C 741B JZ 004D1139...itd.014F:004D25BD 8B6C2418 MOV EBP,[ESP+18] 014F:004D25C1 8BFD MOV EDI,EBP 014F:004D25C3 81EF00004000 SUB EDI,00400000 014F:004D25C9 85FF TEST EDI,EDI 014F:004D25CB 7443 JZ 004D2610 ---> tu jakieś sprawdzenia...itd.:u eip l 2f014F:004D2617 81C62A160000 ADD ESI,0000162A 014F:004D261D 6A05 PUSH 05 014F:004D261F 59 POP ECX 014F:004D2620 F3A4 REPZ MOVSB 014F:004D2622 61 POPAD 014F:004D2623 669D POPF 014F:004D2625 E94653F5FF JMP 00427970 ---> skok do głównego programu 014F:004D262A E96B69F5FF JMP 00428F9A
Po czym poznałem, że w tym miejscu jest skok do głównego programu, ano po tym że tam już zaczyna się standardowy kod z wywołaniami funkcji startowych API. Zresztą jak sobie na starcie wyświetlimy zawartość pamięci d cs:00427970 to zobaczymy podczas śledzenia, że funkcja dekompresji właśnie tam zapisuje dane. Najważniejszy jest dla nas JMP 00427970 po którym następuje wykonanie już zdekompresowanego kodu a jak do niego dojdziemy to już obojętne (nawet metodą prób i błędów).
Teraz wykorzystamy program ProcDump do dekompresji. Umożliwia on oprócz dekompresji dowolnie spakowanych exe (co nie zawsze działa), zdefiniowanie za pomocą skryptu sposobu śledzenia i zapisania dekompresowanego pliku konkretnym packerem. Jest tam plik skript.ini w którym właśnie to definiujemy. Są tam już zdefiniowane Shrinker, PEShield, WWPack. Program wykorzystuje kilka komend do takiej definicji, zresztą sami zaglądnijcie do pliku to zobaczycie. W każdym razie dodajemy nowa sekcję np. WinAmp.
[INDEX] P1=PEShield ... P7=WinAmp[WinAmp] L1=LOOK E9,46,53,F5,FF L2=BP L3=STEP
O co chodzi w tym skrypcie? Po prostu: szukaj ciągu bajtów naszego skoku JMP 00427970 (jego postaci szesnastkowej), po jego znalezieniu zastaw na nim pułapkę i po jej wykonaniu zapisuj śledzony program jako zdekompresowany. Proste prawda. Uruchamiamy ProcDump wybieramy trace nasz typ WinAmp, wybieramy WinAmp.exe i program pięknie się dekompresuje i co najważniejsze działa po tej dekompresji.
Myślę, ze ProcDump wart jest zainteresowania i przećwiczenia np. właśnie na WinAmp. Zawsze możemy trafić na program skompresowany nieznanym kompresorem i wtedy damy sobie radę. Ci co nie znają ProcDump powinni go jak najszybciej ściągnąć.
GustawKit - 16 listopad 1998
W poprzedniej części przedstawiłem sposób rozpakowywania plików exe za pomocą ProcDump. Okazuje się, że program ten może posłużyć nam do zcrackowania programów zabezpieczonych komercyjnymi pakietami typu TimeLock lub Vbox4 by PreviewSoftware. Zasada działania tych zabezpieczeń jest stosunkowo prosta. Za przykład weźmiemy program Ulead Cool3D v2.0 (PCWK 12A/98). Jeżeli załadujemy program U3dedit2.exe do debuggera (np. SoftIce) pojawi się nam następujący kod :
014F:004F1000 PUSH DWORD PTR [ESP+0C] 014F:004F1004 PUSH DWORD PTR [ESP+0C] 014F:004F1008 PUSH DWORD PTR [ESP+0C] 014F:004F100C PUSH 55E239F5 ---> przygotowanie adresu 014F:004F1011 PUSH 55AD2D76 ---> spakowanych danych 014F:004F1016 PUSH 55E23DA9 014F:004F101B PUSH 55E23D53 014F:004F1020 CALL [004F11F0] ---> wywołanie 1 funkcji vbox4 014F:004F1026 PUSH FFFFFFFF 014F:004F102B CALL EAX ---> wywołanie programu 014F:004F102D RET 000C
Funkcja CALL [004F11F0] wywołuje procedury (PreviewExecGate...) z bibliotek vbox4, które dekodują pierwszą część danych programu a w EAX jest zwracany nowy Entry Point (w moim przypadku F0000) do rozkodowanych danych. Śledząc dalej program w CALL EAX (F8) przechodzimy do nowego fragmentu kodu :
014F:004F0000 PUSH DWORD PTR [ESP+0C] 014F:004F0004 PUSH DWORD PTR [ESP+0C] 014F:004F0008 PUSH DWORD PTR [ESP+0C] 014F:004F000C PUSH B6A4DD7F 014F:004F0011 PUSH BBC60E1F 014F:004F0016 PUSH 6D171A8C 014F:004F001B PUSH 415F4B5A 014F:004F0020 CALL [004F01D4] ---> następna funkcja vbox4 014F:004F0026 PUSH FFFFFFFF 014F:004F002B CALL EAX 014F:004F002D RET 000C
Jak widzimy kod jest analogiczny do poprzedniego. CALL [4F01D4] wywołuje funkcję vbox4, która uruchamia procedury sprawdzające warunki trial i w przypadku pomyślnego spełnienia dekoduje resztę programu i danych a w EAX jest zwracany adres rzeczywistego programu (w naszym wypadku Cool3D). W przypadku niepomyślnego sprawdzenia warunków trial w EAX zwracany jest adres funkcji ExitProcess, czyli zakończenia programu. Jeżeli prześledzimy dalej nasz kod ( w CALL EAX - F8) to wejdziemy na nasz główny program pod adresem 6CF20. Adres ten jest wart zapamiętania, gdyż jest on po prostu Entry Point głównego programu, który jest już pełną wersją bez vbox4.
Ok, wiemy już wszystko. Rozwiązanie problemu vbox4 polegać będzie na przeniesieniu z pamięci rozpakowanego programu i zapisaniu go do pliku uruchomieniowego. Można się pokusić o wykorzystanie SoftIce i np. SoftDump do zapisania pamięci w plik. Wymaga to jednak dobrej znajomości struktury PE exe i jest uciążliwe a opisywanie tego nie jest naszym celem. Jest prostsze rozwiązanie ponieważ znamy wspaniały program ProcDump, który umożliwia nam zapisanie procesów z pamięci do pliku przy utrzymaniu struktury plików uruchomieniowych.
Jak już opisywałem w poprzedniej części ProcDump posiada plik script.ini w którym zapisywane są instrukcje debugowania dekodowanych programów. W najnowszej wersji ProcDump32 1.1.6 (jest już 1.6.2) powinna być już gotowa sekcja dla Vbox :
[VBOX Dialog] L1=LOOK FF,D0 ; szukamy pierwszego call eax L2=BP ; pułapka na znalezionym adresie L3=BPREG EAX ; ustawia pułapkę na adresie zawartym w rejestrze EAX ; a jak wiemy tam jest adres następnej części kodu L4=OBJR ; ustala adres podstawowy szukania na aktualne EIP L5=LOOK FF,D0 ; szuka drugiego call eax L6=BP ; zastawia na nim pułapkę L7=STEP ; i dalej już śledzi zapisując rozpakowany program
Ważne jest odpowiednie ustawienie opcji procesu ładowania i rekonstrukcji pliku. Ponieważ program spakowany za pomocą vbox4 ma także spakowane segmenty i tabele danych musimy zaznaczyć opcje rekonstrukcji, tzn. Create New Import. W razie problemów włączamy także opcje Ignore Faults w sekcji Trace.
Po pozytywnym rozpakowaniu pliku możemy także usunąć sekcję WeiJunLi ze struktury naszego nowego pliku (w opcji PE Editor). Z ciekawości zaglądnijmy jaki jest Entry Point naszego pliku - 6CF20. Ok, właśnie taki miał być.
GustawKit - 24 listopad 1998
Myślę, że wszyscy którzy zaglądneli na strony CrackPl potrafią już obslugiwac ProcDump i są w stanie rozpakować większość plików uruchomieniowych spakowanych lub szyfrowanych. Okazuje się, że na podobnej zasadzie działa większość komercyjnych pakietów autoryzacyjnych do ograniczeń czasowych i wersji trial oprogramowania. Znamy już takie pakiety jak TimeLock lub Vbox, które mimo zastosowania skomplikowanych algorytmów szyfracji i rozbudowanych procedur ochrony dają się w kilka sekund usunąć z programów. Z reguły są dwa rodzaje takich pakietów zabezpieczeń, jedne które wprowadzają procedury i funkcje już na poziomie pisania kodu programu i po kompilacji są na trwałe wbudowane w program i drugie dołączające i szyfrujące już gotowe pliki uruchomieniowe. Te pierwsze mogą być lepsze lub gorsze ale czasami mogą spełniać swoją rolę, natomiast te drugie są po prostu do niczego. Przykładem było już TimeLock lub Vbox, teraz przedstawię inne softSENTRY oferowane m.in. w zestawie www.componentsource.com i można je znaleść m.in. na płytkach1/99 tego zestawu.
Ogólne założenia programu są dobre, program umożliwia przygotowanie skryptów tworzących procedury trial takie jak time limit, limit uruchomień, okienka rejestracyjne, okienka informacyjne i generowanie kodu dołączanego do wykonywalnych plików exe lub bibliotek dll. Właściwie wszystko pięknie, tyle że usunięcie tego kodu z plików uruchomieniowych nie nastręczy żadnych problemów nawet początkującym crackerom a nawet wręcz ułatwi im zcrackowanie programu.
Zobaczmy co ten program wyrabia z kodem np. w pliku sentry32.exe (sam pakiet jest zabezpieczony swoim kodem). Po wstępnej analizie kodu startowego, znajdujemy sprawdzanie ograniczeń i wywoływanie odpowiednich okienek dialogowych lub informacyjnych. Można się bawić w modyfikacje tego kodu ale nie ma to sensu. W każdym razie dochodzimy do takiego fragmentu kodu :
014F:004A3B27 FF1558A44A00 CALL [USER32!UnregisterClassA] 014F:004A3B2D E87E000000 CALL 004A3BB0 014F:004A3B32 E819000000 CALL 004A3B50 014F:004A3B37 8B45B8 MOV EAX,[EBP-48] 014F:004A3B3A 50 PUSH EAX 014F:004A3B3B FF15D0A34A00 CALL [KERNEL32!ExitProcess] 014F:004A3B41 8B45B8 MOV EAX,[EBP-48] 014F:004A3B44 E900000000 JMP 004A3B49
Wywołanie CALL 4A3B50 wywołuje następną procedurę, która nas będzie interesować. Szukamy bowiem kodu głównego programu z pominięciem kodu pakietu SoftSENTRY. Obejrzyjmy więc to wywołanie :
014F:004A3B50 56 PUSH ESI 014F:004A3B51 A1C0894A00 MOV EAX,[004A89C0] 014F:004A3B56 33C9 XOR ECX,ECX 014F:004A3B58 8B7002 MOV ESI,[EAX+02] 014F:004A3B5B 337006 XOR ESI,[EAX+06] 014F:004A3B5E 33700A XOR ESI,[EAX+0A] 014F:004A3B61 033548884A00 ADD ESI,[004A8848] 014F:004A3B67 A1C0894A00 MOV EAX,[004A89C0] 014F:004A3B6C 41 INC ECX 014F:004A3B6D 41 INC ECX 014F:004A3B6E 8B5006 MOV EDX,[EAX+06] 014F:004A3B71 31548EF8 XOR [ECX*4+ESI-08],EDX 014F:004A3B75 A1C0894A00 MOV EAX,[004A89C0] 014F:004A3B7A 8B500A MOV EDX,[EAX+0A] 014F:004A3B7D 31548EFC XOR [ECX*4+ESI-04],EDX 014F:004A3B81 83F914 CMP ECX,14 014F:004A3B84 7CE1 JL 004A3B67 014F:004A3B86 8B0DF4884A00 MOV ECX,[004A88F4] 014F:004A3B8C E88FF5FFFF CALL 004A3120 014F:004A3B91 FFD6 CALL ESI <---- !!!! 014F:004A3B93 6A00 PUSH 00 014F:004A3B95 6820634A00 PUSH 004A6320 014F:004A3B9A 6810634A00 PUSH 004A6310
Krótkie prześledzenie tego kodu pozwoli nam znaleść wywołanie CALL ESI, które jak się okazuje wywołuje nasz główny program. Widzimy tu fragment kodu przygotowujący EntryPoint do kodu głównego i zachowujący go w rejestrze ESI. Wywołanie Call ESI prowadzi do :
014F:00414CA0 64A100000000 MOV EAX,FS:[00000000] 014F:00414CA6 55 PUSH EBP 014F:00414CA7 8BEC MOV EBP,ESP 014F:00414CA9 6AFF PUSH FF 014F:00414CAB 68B8424400 PUSH 004442B8 014F:00414CB0 68C4AA4100 PUSH 0041AAC4 014F:00414CB5 50 PUSH EAX 014F:00414CB6 64892500000000 MOV FS:[00000000],ESP 014F:00414CBD 83EC60 SUB ESP,60 014F:00414CC0 53 PUSH EBX 014F:00414CC1 56 PUSH ESI 014F:00414CC2 57 PUSH EDI 014F:00414CC3 8965E8 MOV [EBP-18],ESP 014F:00414CC6 FF15B0374500 CALL [KERNEL32!GetVersion] 014F:00414CCC A3A4D74400 MOV [0044D7A4],EAX
To jest już nasz główny program, zaczynający się w moim wypadku od cs:414CA0 i taka wartość była zapisana w ESI. Ponieważ jak pisałem, kody zabezpieczeń przyłączane są do gotowego pliku uruchomieniowego ich usunięcie nie powinno sprawić problemów. Należy dojść do początku głównego programu i zapisać obraz pliku z pamięci na dysk i odtworzyć nową strukturę pliku exe. Sposobów jest wiele ale najłatwiejszy to ProcDump. Tworzymy sobie nową sekcje w pliku script.ini
[softSentry] L1=LOOK E8,19,00,00,00 ; szukamy pierwszego call 43a3b50 L2=BP ; pułapka na znalezionym adresie L3=LOOK FF,D6 ; szuka drugiego call ESI L6=BP ; zastawia na nim pułapke L7=STEP ; i dalej już śledzi zapisując rozpakowany program
No i program pięknie nam usunie zabezpieczenia SoftSENTRY. Jeżeli jeszcze zaglądniemy do nagłówka pliku znajdziemy niepotrzebną sekcje 20/20teaćj, którą możemy usunąc za pomocą edytora PE zawartego w ProcDump.
Proste, prawda? Szkoda tylko, że praca nad niezłym pakietem zabezpieczeń owocuje tak kiepskim zabezpieczeniem. Po prostu przerost formy nad treścią.
GustawKit - 13 grudnia 1998
Minęło trochę czasu odkąd pisałem o rozpakowywaniu plików uruchomieniowych spakowanych różnymi exepackerami. Przez ostatnie miesiące, lata pojawiło się wiele nowych packerów i crypterów. Pojawiło się także dużo narzędzi wspomagających rozpakowywanie. IMHO najbardziej uniwersalnymi i praktycznymi narzędziami są nadal SoftIce i ProcDump, dołączył do nich także znakomity debuger TRW2000. Ostatnio pojawił się ciekawy packer/crypter firmy http://www.bit-arts.com/ o nazwie Crunch 1.0. Pierwsze spotkanie z nim sprawiło, że postanowiłem się nim zająć i spróbować go rozpakować ponieważ zauważyłem kilka ciekawostek w kodzie i z biegu nie udało mi się go rozpakować uniwersalnymi narzędziami. Za przykład użyjemy samego Crunch'a, który jest spakowany sobą.
Postaram się wam przedstawić jak zbudować uniwersalny skrypt do ProcDump'a rozpakowujący Crunch'a, gdyż na razie nie ma żadnego dedykowanego unpackera a uniwersalne nie radzą sobie z nim. Z uwagi na to, że program wywala się w obecności SoftIce (ma to robić wg. autorów) do debugowania użyjemy TRW2000. Podczas debugowania okazuję się, że w kodzie jest kilka interesujących miejsc, które mocno utrudniają jego debugowanie i muszą być uwzględnione przy tworzeniu skryptu do ProcDumpa. Dlatego też proces tworzenia skryptu będę przeplatał z fragmentami kodu Crunch'a w celu lepszego zrozumienia zagadnienia i ewentualnego szybkiego znalezienia tego kodu na podstawie adresów czy ciągu bajtów podczas debugowania. Polecam także zmienić od razu nazwy sekcji spakowanego Crunch'a (wszystkie mają tą samą nazwę), ponieważ będzie nam łatwiej się zorientować kiedy przeskakujemy z jednej sekcji do drugiej, Oki, startujemy śledzenie crunch.exe. Na starcie EIP=0067E00 pojawia się na mniej więcej taki kod:
017F:0067E000 55 PUSH EBP 017F:0067E001 E800000000 CALL 0067E006 017F:0067E006 5D POP EBP 017F:0067E007 83ED06 SUB EBP,00000006 ... 017F:0067E0C0 8BC8 MOV ECX,EAX 017F:0067E0C2 F3A4 REP MOVSB !!!!! 017F:0067E0C4 8B07 MOV EAX,[EDI] 017F:0067E0C6 D5 AAD 017F:0067E0C7 81C2F3080E00 ADD EDX,000E08F3 017F:0067E0CD 52 PUSH EDX 017F:0067E0CE 33C0 XOR EAX,EAX
Tu już pojawia się pierwsza zasadzka w instrukcji REP MOVSB. Otóż ta instrukcja powoduje kopiowanie fragmentu kodu akurat w miejsce następnej instrukcji i po jej wykonaniu kod będzie wyglądać tak:
017F:0067E0C0 8BC8 MOV ECX,EAX 017F:0067E0C2 F3A4 REP MOVSB 017F:0067E0C4 8BD5 MOV EDX,EBP 017F:0067E0C6 81C2F3080000 ADD EDX,000008F3 017F:0067E0CC 52 PUSH EDX 017F:0067E0CD 33C0 XOR EAX,EAX
Tu jest pierwszy moment, który musimy uwzględnić przy tworzeniu skryptu. Ponieważ po instrukcji REP MOVSB dalszy kod się zmienia należy pozwolić by ProcDump znalazł miejsce kiedy to nastąpi:
LOOK F3,A4 / ADD 2 / BP / OBJR
ProcDump przeszuka kod za bajtami REP MOVSB i do EIP ze znalezionymi bajtami doda 2 i na takim adresie założy pułapkę. Musimy to zrobić w ten sposób, ponieważ ProcDump musi się zatrzymać już po wykonaniu REP MOVSB. Dalej znajdujemy podobny kod, który kopiuje fragmenty kodu tym razem w procedurze CALL.
017F:0067E0FE FFD2 CALL Near EDX 017F:0067E100 CC INT 03 017F:0067E101 CC INT 03 017F:0067E102 E8A6080000 CALL 0067E9AD 017F:0067E107 E81C060000 CALL 0067E728
a po wywołaniu CALL kod wygląda:
017F:0067E0FE FFD2 CALL Near EDX 017F:0067E100 EB05 JMP 0067E107 017F:0067E102 E8A6080000 CALL 0067E9AD 017F:0067E107 E81C060000 CALL 0067E728
Jeżeli prześledzimy procedurę w wywołaniu CALL znajdziemy ciekawy kod, który wykłada program, jeżeli SoftIce jest w pamięci. Otóż ten fragment kodu: JMP 0067E107 generowany jest na podstawie wartości z tablicy IDT. Jak wiemy gdy SI jest zainstalowany w tablicy są inne wpisy dla przerwań (INT1/IN3) niż w czystym systemie. To właśnie sprawdza Crunch, i jeżeli są tam wartości wpisane przez SI generuje bzdurne wartości powodując niedziałanie programu. TRW nie zmienia wpisów w IDT więc nim możemy spokojnie śledzić taki kod (wracając jeszcze do tego IDT, to jest tutaj obliczana różnica pomiędzy adresami wektorów przerwań. Konkretnie od adresu INT3 jest odejmowany adres INT1 i jeśli wynikiem jest 1Eh to program się ładnie zawiesi. Taka sytuacja występuje gdy SI jest aktywny. W "czystym" systemie różnica ta wynosci 10h - smola). Ten fragment kodu musimy także uwzględnić w naszym skrypcie:
LOOK FF,D2 / BP / WALK /OBJR / LOOK 74,02,58,c3 /ADD 3 /BP / WALK /OBJR
Znajdujemy kod CALL'a i na nim zakładamy pułapkę. Później wykonujemy jedną instrukcję i przypisujemy bazowy EIP do dalszego działania skryptu. Ponieważ jednak ProcDump sam nie chciał mi się zatrzymać po powrocie z CALL, więc poszuka moment powrotu RET z CALL i na nim się zatrzyma, wykona jedną instrukcję czyli wyjdzie z CALL'a i przypisze bazowy EIP już po tym. Dalej debugując kod programu po wielu różnych cudach z kodem dochodzimy do:
017F:00680DA3 E900FFFFFF JMP 00680CA8 017F:00680DA8 8B8522130000 MOV EAX,[EBP+00001322] 017F:00680DAE 01858F220000 ADD [EBP+0000228F],EAX 017F:00680DB4 FFA5F22A0000 JMP Near [EBP+00002AF2] !!!! 017F:00680DBA C3 RET 017F:0067E107 E81C060000 CALL 0067E728
gdzie interesująco wygląda skok JMP [EBP+2AF2] który skacze do:
017F:0067E0C4 61 POPAD 017F:0067E0C5 5D POP EBP 017F:0067E0C6 8B858F220000 MOV EAX,[EBP+0000228F] 017F:0067E0CC 5D POP EBP 017F:0067E0CD FFE0 JMP Near EAX !!!
A ten jump EAX jest do :-)) cs:401000 czyli kodu głównego programu.
017F:00401000 A19CA54E00 MOV EAX,[004EA59C] 017F:00401005 C1E002 SHL EAX,02 017F:00401008 A3A0A54E00 MOV [004EA5A0],EAX 017F:0040100D 57 PUSH EDI 017F:0040100E 51 PUSH ECX 017F:0040100F 33C0 XOR EAX,EAX 017F:00401011 BF686C4F00 MOV EDI,004F6C68
Spróbujmy dopisać do skryptu szukanie tych jump'ów czyli:
LOOK FF,A5,F2,2A,00,00 / BP
Szukanie jednak bajtów kodów tych skoków przez ProcDumpa w momencie ostatniego zatrzymania skryptu (po wyjściu z CALL) nie daje rezultatu. A raczej daje tylko okazuje się, że kody tych jump'ów znajdują się pod innymi adresami ( kod pierwszego jump jest pod cs:0067F062) niż wynika to z debugera no i mimo ich znalezienia pułapka BP nie zadziała. Co więc robimy? Będąc w debugerze w czasie śledzenia jeszcze pod adresem 017F:0067E100 (czyli w momencie kiedy ProcDump zacznie szukać tego JMP) zaglądamy co jest tam gdzie ma być ten JMP czyli pod adres 017F:00680DB4. Okazuje się, że jest co innego. No więc zakładamy pułapkę na zapisie pod ten adres (BP 017F:00680DB4 W) i puszczamy program dalej. Okazuje się, że ten kod jest kopiowany w to miejsce z innego w tym fragmencie:
017F:0067E2B0 F3A4 REP MOVSB !!!! 017F:0067E2B2 5F POP EDI 017F:0067E2B3 89BD022B0000 MOV [EBP+00002B02],EDI 017F:0067E2B9 33D2 XOR EDX,EDX
Wniosek z tego, że skrypt musi najpierw znaleźć miejsce kopiowania, zatrzymać się na nim i szukać JMP'ów dopiero po ich skopiowaniu. A więc:
LOOK F3,A4 / ADD 2 / BP /OBJR
I dopiero po tym poszukać końcowych JMP'ów:
LOOK FF,A5,F2,2A,00,00 / REPL 90,90,90,90,90,90 / LOOK FF,A5,F2,2A,00,00 /BP /WALK /OBJR
LOOK FF,E0 / BP / STEP
ProcDump najpierw znajdzie pierwszą kopię bajtów (którą wcześniej znajdywał), które są już zbędne bo skopiowane pod właściwy adres, więc aby je pominąć zastępujemy je NOP'ami i szukamy ponownie. I to by było praktycznie wszystko. Końcowy pełny skrypt wygląda następująco:
[Crunch] L1=LOOK F3,A4 L2=ADD 2 L3=BP L4=OBJR L5=LOOK FF,D2 L6=BP L7=WALK L8=OBJR L9=LOOK 74,02,58,c3 LA=ADD 3 LB=BP LC=WALK LD=OBJR LE=LOOK F3,A4 LF=ADD 2 L10=BP L11=OBJR L12=LOOK FF,A5,F2,2A,00,00 L13=REPL 90,90,90,90,90,90 L14=LOOK FF,A5,F2,2A,00,00 L15=BP L16=WALK L17=OBJR L18=LOOK FF,E0 L19=BP L1A=STEP OPTL1=00000000 OPTL2=01010001 OPTL3=01010001 OPTL4=00030000 OPTL5=00000000
Prawdopodobnie istnieją inne drogi śledzenia i stworzenia skryptu w inny sposób. To co przedstawiłem jest końcowym efektem mojej pracy bez żadnych optymalizacji. Skrypt działa poprawnie na programach spakowanych Crunch'em więc nie ma co już go poprawiać. O czym świadczy cały ten przykład? Ano o tym, że każdy spakowany czy zaszyfrowany program uruchomieniowy można rozpakować, bez względu na ilość i jakość zabezpieczeń przed tym. Crunch nie jest złym packerem, myślę, że w większości przypadków mógłby uniemożliwić i zniechęcić do modyfikacji i łamania programów nim zabezpieczonych. Mógłby gdyby nie ProcDump, TRW i CrackPL :-). Przykro mi, że tak bezwzględnie potraktowałem Crunch i chłopaków z BitArts ale zrobiłem to tylko dlatego, że ich produkt uznałem za ciekawy i warty poświęcenia mojego czasu. Wszelkie wymienione najnowsze narzędzia i programy można znaleźć na stronach http://www.cookiecrk.z.pl/ , http://www.ptasiek.px.pl/ , http://www.protools.cjb.net/ , http://www.playtools.cjb.net/ i innych, z linków wymienionych stron.
GustawKit - 9 luty 2000
Nagłówek PE (portable executable header) znajduje się w pliku exe pod offsetm wskazywanym przez członka (member) struktury IMAGE_DOS_HEADER.e_lfanew. Struktura IMAGE_DOS_HEADER jest umieszczona w pliku pod offsetem 0 czyli znajduje się na samym jego początku i zaczyna sie wszystkim chyba znaną dwubajtową sygnaturą 'MZ':
IMAGE_DOS_HEADER STRUCT DWORD e_magic WORD ? e_cblp WORD ? e_cp WORD ? e_crlc WORD ? e_cparhdr WORD ? e_minalloc WORD ? e_maxalloc WORD ? e_ss WORD ? e_sp WORD ? e_csum WORD ? e_ip WORD ? e_cs WORD ? e_lfarlc WORD ? e_ovno WORD ? e_res WORD 4 dup(?) e_oemid WORD ? e_oeminfo WORD ? e_res2 WORD 10 dup(?) e_lfanew DWORD ? IMAGE_DOS_HEADER ENDS
Jako że znamy już offset PE headera to poniżej jego struktura:
IMAGE_NT_HEADERS STRUCT DWORD Signature DWORD ? FileHeader IMAGE_FILE_HEADER <> OptionalHeader IMAGE_OPTIONAL_HEADER32 <> IMAGE_NT_HEADERS ENDS
I co tu widać? Oczywiście sygnaturke pliku (w naszym przypadku znane 'PE') i następne wskaźniki do kolejnych struktur. Na pierwszy ogień idzie IMAGE_FILE_HEADER:
IMAGE_FILE_HEADER STRUCT DWORD Machine WORD ? NumberOfSections WORD ? TimeDateStamp DWORD ? PointerToSymbolTable DWORD ? NumberOfSymbols DWORD ? SizeOfOptionalHeader WORD ? Characteristics WORD ? IMAGE_FILE_HEADER ENDS
Znaczenie poszczególnych członków tej struktury:
Machine - określa pod jakim systemem ma się uruchamiać kod tego execa: IMAGE_FILE_MACHINE_I386 (0x14c) - Intel 80386 procesor lub lepszy 0x014d - Intel 80486 procesor lub lepszy 0x014e - Intel Pentium procesor lub lepszy 0x0160 - R3000 (MIPS) procesor, duży indianin (big endian), z tymi indianinami chodzi o zapis liczb w pamięci, intel zapisuje liczbę 12345678h jako 78563412h, natomiast motorola normalnie czyli 12345678h, niepytajcie się tylko który jest który :) IMAGE_FILE_MACHINE_R3000 (0x162) - R3000 (MIPS) procesor, mały indianin (little endian) IMAGE_FILE_MACHINE_R4000 (0x166) - R4000 (MIPS) procesor, mały indianin IMAGE_FILE_MACHINE_R10000 (0x168) - R10000 (MIPS) procesor, mały indianin IMAGE_FILE_MACHINE_ALPHA (0x184) - DEC Alpha AXP procesor IMAGE_FILE_MACHINE_POWERPC (0x1F0) - IBM Power PC, mały indianinNumberOfSections - informuje o ilości sekcji w pliku a dokładniej o ilości nagłówków sekcji (to własnie w nagłówkach sekcji są zawarte m.in. informacje o ich położeniu, wielkości i "słynnych" :) characteristicsTimeDateStamp - określa czas kiedy plik został stworzonyPointerToSymbolTable oraz NumberOfSymbols - używane do przechowywania informacji dla debuggera, ze względu na brak danych najlepiej ustawiać je na wartość 0SizeOfOptionalHeader - rozmiar struktury IMAGE_OPTIONAL_HEADER32 równy 224 bajtomCharacteristics - charakterystyka pliku czyli atrybuty, część z tych flag jest ważna tylko w przypadku plików object lub bibliotek (szczegółowa rozpiska poszczególnych bitów jest zawarta w pliku PE.txt)
Dobrze, wiemy już jak wygląda struktura IMAGE_FILE_HEADER więc obejrzyjmy kolejną strukturę wskazywaną przez członka IMAGE_NT_HEADERS.OptionalHeader:
IMAGE_OPTIONAL_HEADER equ <IMAGE_OPTIONAL_HEADER32> IMAGE_OPTIONAL_HEADER32 STRUCT DWORD Magic WORD ? MajorLinkerVersion BYTE ? MinorLinkerVersion BYTE ? SizeOfCode DWORD ? SizeOfInitializedData DWORD ? SizeOfUninitializedData DWORD ? AddressOfEntryPoint DWORD ? BaseOfCode DWORD ? BaseOfData DWORD ? ImageBase DWORD ? SectionAlignment DWORD ? FileAlignment DWORD ? MajorOperatingSystemVersion WORD ? MinorOperatingSystemVersion WORD ? MajorImageVersion WORD ? MinorImageVersion WORD ? MajorSubsystemVersion WORD ? MinorSubsystemVersion WORD ? Win32VersionValue DWORD ? SizeOfImage DWORD ? SizeOfHeaders DWORD ? CheckSum DWORD ? Subsystem WORD ? DllCharacteristics WORD ? SizeOfStackReserve DWORD ? SizeOfStackCommit DWORD ? SizeOfHeapReserve DWORD ? SizeOfHeapCommit DWORD ? LoaderFlags DWORD ? NumberOfRvaAndSizes DWORD ? DataDirectory IMAGE_DATA_DIRECTORY IMAGE_NUMBEROF_DIRECTORY_ENTRIES dup(<>) IMAGE_OPTIONAL_HEADER32 ENDS
Znaczenie poszczególnych członków tej struktury:
(to jeszcze nie koniec)
Magic -MajorLinkerVersion -MinorLinkerVersion -SizeOfCode -SizeOfInitializedData -SizeOfUninitializedData -AddressOfEntryPoint -BaseOfCode -BaseOfData -ImageBase -SectionAlignment -FileAlignment -MajorOperatingSystemVersion -MinorOperatingSystemVersion -MajorImageVersion -MinorImageVersion -MajorSubsystemVersion -MinorSubsystemVersion -Win32VersionValue -SizeOfImage -SizeOfHeaders -CheckSum -Subsystem -DllCharacteristics -SizeOfStackReserve -SizeOfStackCommit -SizeOfHeapReserve -SizeOfHeapCommit -LoaderFlags -NumberOfRvaAndSizes -DataDirectory - IMAGE_DATA_DIRECTORY IMAGE_NUMBEROF_DIRECTORY_ENTRIES dup(<>)
IMAGE_DATA_DIRECTORY STRUCT DWORD VirtualAddress DWORD ? isize DWORD ? IMAGE_DATA_DIRECTORY ENDS
IMAGE_EXPORT_DIRECTORY STRUCT DWORD Characteristics DWORD ? TimeDateStamp DWORD ? MajorVersion WORD ? MinorVersion WORD ? nName DWORD ? nBase DWORD ? NumberOfFunctions DWORD ? NumberOfNames DWORD ? AddressOfFunctions DWORD ? AddressOfNames DWORD ? AddressOfNameOrdinals DWORD ? IMAGE_EXPORT_DIRECTORY ENDS
IMAGE_DIRECTORY_ENTRY_EXPORT equ 0 IMAGE_DIRECTORY_ENTRY_IMPORT equ 1 IMAGE_DIRECTORY_ENTRY_RESOURCE equ 2 IMAGE_DIRECTORY_ENTRY_EXCEPTION equ 3 IMAGE_DIRECTORY_ENTRY_SECURITY equ 4 IMAGE_DIRECTORY_ENTRY_BASERELOC equ 5 IMAGE_DIRECTORY_ENTRY_DEBUG equ 6 IMAGE_DIRECTORY_ENTRY_ARCHITECTURE equ 7 IMAGE_DIRECTORY_ENTRY_GLOBALPTR equ 8 IMAGE_DIRECTORY_ENTRY_TLS equ 9 IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG equ 10 IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT equ 11 IMAGE_DIRECTORY_ENTRY_IAT equ 12 IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT equ 13 IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR equ 14
Poniżej spis materiałów które można namierzyć w sieci tytuł/nazwa pliku/rozmiar:
copyright (c) 2000
smola.crackpl.2ooo