|Unpack Crunch1.0 #4|
[ Jak debugować i rozpakować Crunch1.0 ]

Minęło trochę czasu odkąd pisałem o rozpakowywaniu plików uruchomieniowych spakowanych różnymi exe-pakerami. Za ostanie miesiące, lata pojawiło się wiele nowych pakeró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 paker/crypter firmy www.bit-arts.com o nazwie Crunch 1.0. Pierwsze spotkanie z nim sprawiło, że postanowiłem się nim zająć i sprobować 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ą.

[ Tutorial ]

Postaram się wam przedstawić jak zbudować uniwersalny skrypt do ProcDump'a rozpakowujący Crunch'a gdyż narazie 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ędnion 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 Cruncha (wszystkie mają tą samą nazwe) 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 bedzie wyglądac 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ć ProcDumpowi znaleˇć 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łapke. Musimy to zrobić w ten sposób ponieważ ProcDump musie 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 nie działanie programu. TRW nie zmienia wpisów w IDT więc nim możemy spokojnie śledzić taki kod. 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] i 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'ow ( 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 wiec 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 wiec 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 to pierwszą kopie 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


[ Wnioski i uwagi ]

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ę nie ma co już go poprawiać.
O czym świadczy cały ten przykład.., a no o tym, że każdy spakowany czy zaszyfrowany program uruchomieniowy można rozpakować, bez względu na ilość i jakość zabezpieczeń prze tym. Crunch nie jest złym pakerem.., 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.

made by by Gustaw Kit - 2000



All rights reserved for CRACKPL 1998 - 2oo2 . Designed by hauer