Unpacking PESpin 1.3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Author: taha Level: для продвинутых новичков Date: 25.o4.2oo7 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Довольно приличный протектор. На мой взгляд один из самых интересных. Tutorial’ов на русском не встечал, а те что были на английском даже читать не стал. Что можно почерпнуть для себя из статьи состоящей из фраз “листните на пару экранов вверх”, “патичть здесь”? Для меня в первую очередь важна логика действий,а не “scroll waaaay up”. Жертву нашёл тут Content - Tools - Anti-deb - Finding OEP * The ESP trick * Stolen bytes - Fixing IAT * Length disassembler * Scripting - Dump & Rebuild Import - links - PS Tools - Shadow (модификация OllyDbg) * plugin AnalyzeThis * plugin ODBGScript * plugin OllyDump * plugin OllyPad * plugin CommandLine - OllyScript Editor (необязательно) - ImpRec Anti-deb Первое, что необходимо сделать проверить содержит ли жертва антиотладочные приёмы. Для этого необходимо просто запустить файл в Olly. Не забудьте отключить остановку на исключениях! Запускаем (F9). Где то всеже останавливаемся, но это не важно, идём дальше Shift-F9, файл запустился. Хм.. Меня, если често, это смутило. Потому что редкий протектор не использует хотябы IsDebuggerPresent. Ну раз нет так нет... Finding OEP Начнём с EP. Точка входа выглядит вот так Code: 004170D4 > /EB 01 JMP SHORT UnPackMe.004170D7 004170D6 |68 60E80000 PUSH 0E860 004170DB 0000 ADD BYTE PTR DS:[EAX],AL 004170DD 8B1C24 MOV EBX,DWORD PTR SS:[ESP] Сразу виден древний фокус “прыжок в середину команды”. Суть в том, что когда дизассемблер Olly проанализирует JMP SHORT UnPackMe.004170D7, то естественно примется за следущий байт - следущую комманду, а следущий байт 68h - оппкод команды push xxx. Врезультате чего, дизасемблерный листинг будет неверным. Этого бы не произошло, еслиб команды эмулировались (как в IDA) и байт 68h был бы проанализирован как db 68h. Тут на помощь Olly придёт плагин AnalyzeThis. Code: 004170D4 > $ /EB 01 JMP SHORT UnPackMe.004170D7 004170D6 |68 DB 68 ; CHAR 'h' 004170D7 > \60 PUSHAD 004170D8 . E8 00000000 CALL UnPackMe.004170DD 004170DD $ 8B1C24 MOV EBX,DWORD PTR SS:[ESP] Вот так... Вдальнейшем этот плагин будет нераз выручать нас от такого рода приёмов. Сразу же в глаза бросается команда pushad. И естественно сразу вспоминаем “The ESP trick”. The ESP trick Суть в том, что протектор не должен наследить, тоесть ему необходимо вернуть значения регистров и выравнить стек. Здесь мне нравится следущее сравнение: “Вы работаете с некоторым текстовым документом, Вы выделяете и копируете первый параграф. Переписываете его, немного меняете как вдруг приходите к заключению, что старый был лучше. Вы вновь выделяете первый параграф и жмёте “paste”.” Естественно pushad – будет играть роль “copy”, а popad – “paste”. В CommandLine пишем команду “hr esp-4”, он поместит hardware breakpoint на кадр стека, в котором будет лежать первый сохранившийся регистр (это будет eax). Проходим pushad по F8 и запускаем программу (F9). Опять возникает исключение, но мы смело жмём Shift-F9. В результате чего окажемся здесь: Code: 00418AF5 . 61 POPAD 00418AF6 . 87D2 XCHG EDX,EDX ; ntdll.KiFastSystemCallRet 00418AF8 . 4A DEC EDX 00418AF9 . C7C1 67639244 MOV ECX,44926367 00418AFF . 85C1 TEST ECX,EAX Сразу видно, что после popad идёт ненужный мусор, так что смело шагаем по F8. Stolen bytes Однако здесь стоит останоиться. Типичная точка входа.. Кроме переходов и мусорных байтов естественно. 00418B43 PUSH EBP 00418B44 JMP SHORT UnPackMe.00418B47 00418B46 DB 34 00418B47 MOV EBP,ESP 00418B49 JMP SHORT UnPackMe.00418B4C 00418B4B DB 0C 00418B4C PUSH UnPackMe.00418B56 00418B51 JMP UnPackMe.0040A485 Скорее всего – это украденые байты. Для полной уверенности я прошёлся по jump’ам до первого call’а. Fixing IAT Как Вы наверно уже заметили, протектор ворует байты не только у точки входа, но и у API. Нужно его отучить это делать. Для этого нам понадобиться вызов какой-нибудь функции. Возьмём к примеру следущую Code: 00418B62 . FF15 F4114000 CALL NEAR DWORD PTR DS:[4011F4] Зайдём внутрь... 008900E9 JMP SHORT 008900EC … 008900EC MOV EDI,EDI 008900EE PUSH EBP 008900EF MOV EBP,ESP 008900F1 CMP DWORD PTR SS:[EBP+8],0 008900F5 JMP SHORT 008900FE … 008900FE JMP SHORT 008900F8 … 008900F8 JMP kernel32.7C80B6AA … 7C80B6AA JE SHORT kernel32.7C80B6C4 7C80B6AC PUSH DWORD PTR SS:[EBP+8] 7C80B6AF CALL kernel32.7C80E074 7C80B6B4 TEST EAX,EAX 7C80B6B6 JE SHORT kernel32.7C80B6C0 7C80B6B8 PUSH DWORD PTR DS:[EAX+4] 7C80B6BB CALL kernel32.GetModuleHandleW 7C80B6C0 POP EBP 7C80B6C1 RETN 4 7C80B6C4 MOV EAX,DWORD PTR FS:[18] 7C80B6CA MOV EAX,DWORD PTR DS:[EAX+30] 7C80B6CD MOV EAX,DWORD PTR DS:[EAX+8] 7C80B6D0 JMP SHORT kernel32.7C80B6C0 Это вызов GetModuleHandleA! Только вот первые 9 байт перенесены по адресу 008900EC. Итак, запоминаем адеса 4011F4,8900E9, 7C80B6A1(GetModuleHandleA). Поставим hardware breakpoint на GetModuleHandleA. В результате мы будем знать когда и зачем PESpin обращается к GetModuleHandleA. Запускаем программу (F9). И останавливаемся вот здесь: Code: 004194B4 3808 CMP BYTE PTR DS:[EAX],CL 004194B6 75 03 JNZ SHORT UnPackMe.004194BB EAX 7C80B6A1 kernel32.GetModuleHandleA ECX FFFF2BCC Этот кусок кода проверяет первый байт API на присутсвие breakpoint’а, установленного помещением int3. Вот зачем нужно использовать hardware breakpoint’ы. Идём дальше... Code: 00417BE7 8B18 MOV EBX,DWORD PTR DS:[EAX] ... 00417BF5 80FB EB CMP BL,0EB Протектор проверяет наличие переходов вначале API. Кстати если в начало поставить jmp 401000, то байты украдены не будут. Идём дальше... Мы окажемся где то здесь Code: 004181B9 . C0EC 04 SHR AH,4 004181BC . 2AC4 SUB AL,AH 004181BE .^ 73 F6 JNB SHORT UnPackMe.004181B6 004181C0 . 8A47 FF MOV AL,BYTE PTR DS:[EDI-1] 004181C3 . 24 0F AND AL,0F 004181C5 . 3C 0C CMP AL,0C 004181C7 . 75 03 JNZ SHORT UnPackMe.004181CC 004181C9 . 5A POP EDX 004181CA . F7D2 NOT EDX 004181CC > 42 INC EDX 004181CD . 3C 00 CMP AL,0 004181CF . 74 42 JE SHORT UnPackMe.00418213 004181D1 . 3C 01 CMP AL,1 004181D3 .^ 74 DB JE SHORT UnPackMe.004181B0 004181D5 . 83C7 51 ADD EDI,51 004181D8 . 3C 0A CMP AL,0A Мы явно в какойто функции... Нужно обследовать её снчала. А что бы узнать где начало нужно проанализировать стек. 0012FF64 004186BB UnPackMe.004186BB 0012FF68 7C80B6A1 kernel32.GetModuleHandleA 0012FF6C 00417C5A RETURN to UnPackMe.00417C5A from UnPackMe.004180F3 0012FF70 7C80B6A1 kernel32.GetModuleHandleA 0012FF74 004010E7 UnPackMe.004010E7 Функция получает на вход адрес API! Уже интересно! Посмотрим начало функции... Length disassembler Code: 004180F3 $ 60 PUSHAD 004180F4 . FC CLD 004180F5 . 33D2 XOR EDX,EDX 004180F7 . 8B7424 24 MOV ESI,DWORD PTR SS:[ESP+24] 004180FB . 8BEC MOV EBP,ESP 004180FD . 68 1CF79710 PUSH 1097F71C 00418102 . 68 80671CF7 PUSH F71C6780 00418107 . 68 18973817 PUSH 17389718 0041810C . 68 18B71C10 PUSH 101CB718 Как только я это увидел, у меня сразу же возникло чувство, что где-то я это уже встречал. Точнее я знал где. Что то подобное было в сорце VirXasm32 (это дизассемблер длин такой). Смотрите таблица заносится в стек, затем идут проверки на наличие префикса, затем разбор, поиск в таблице. В итоге функция вернет длину опкода. Вообще если протектор ворует байты, то в него 100% должен быть встроен дизассемблер длин. Его задача найти длину комманды. Потому что длина команд нефиксированна, а протектеру нужно знать сколько байт копировать. Если скопировать меньше то программа упадёт, что крайне нежелательно. Ну вот с этой функцией разобрались, пройдём её по F8. Code: 00417C54 > \50 PUSH EAX 00417C55 . E8 99040000 CALL UnPackMe.004180F3 00417C5A . 83C4 04 ADD ESP,4 00417C5D . 91 XCHG EAX,ECX Заметьте в eax лежит размер “mov edi,edi” – два байта. Идём дальше... Code: 00417CA3 > \F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI] 00417CA5 . 8BC6 MOV EAX,ESI 00417CA7 . 8BF7 MOV ESI,EDI Вот копируется первый оппкод в 008900EС. Дальше покругу, длина команды <–> копипаст. Следовательно где-то впереди должен заноситься адрес 008900E9 в 4011F4. Поставим breakpoint на доступ к 4011F4. И вот оно: Code: 00417F77 . 8902 MOV DWORD PTR DS:[EDX],EAX Но вот встаёт проблема! Между Code: 00417C54 > \50 PUSH EAX и Code: 00417F77 . 8902 MOV DWORD PTR DS:[EDX],EAX протектор так активно работает со стеком и нарезает такие круги, что патчить будет себе дороже. Но ведь можно написать скрипт! Мы знаем где взять настоящий адрес и куда кладётся левый. Scripting Собственно всё как я сказал: ставим breakpoint на 00417c54, чтобы получить адрес API, убираем бряк, чтобы не тормазиться при работе дизассемблера длин, ставим breakpoint на 00417F77, чтобы поместить в eax настоящий адрес. Сравнение с 7C834D41 сделано для того чтоб не пропустить конец таблицы и выгрузить скрипт. 7C834D41 – это последняя используемая функция. Code: var func var IAT var addr mov func,00417c54 mov IAT,00417F77 bphws func,"x" next: run mov addr,eax bphwc func bphws IAT,"x" run mov eax,addr bphwc IAT bphws func,"x" cmp eax,7C834D41 jne next bphwc func ret Не забудьте удалить все ненужные breakpoint’ы. Оставить нужно только один – на oep. Запускаем скрипт, тормазимся на oep. Ну вот... Code: 00418B43 > \55 PUSH EBP 00418B44 . EB 01 JMP SHORT UnPackMe.00418B47 00418B46 34 DB 34 ; CHAR '4' 00418B47 > 89E5 MOV EBP,ESP 00418B49 . EB 01 JMP SHORT UnPackMe.00418B4C 00418B4B 0C DB 0C 00418B4C > 68 568B4100 PUSH UnPackMe.00418B56 00418B51 .- E9 2F19FFFF JMP UnPackMe.0040A485 00418B56 . 68 220340D1 PUSH D1400322 ; /pModule = D1400322 ??? 00418B5B . 810424 DEFCBF>ADD DWORD PTR SS:[ESP],2EBFFCDE ; | 00418B62 . FF15 F4114000 CALL NEAR DWORD PTR DS:[4011F4] ; \GetModuleHandleA 00418B68 . EB 01 JMP SHORT UnPackMe.00418B6B У меня всё определилось. =) Dump & Rebuild Import Снимаем дамп с помощью плагина OllyDump (галочку с Rebuild Import убрать). Чтож, теперь ImpRec’ом восстановим импорт. Выбираем процесс.. Code: Target: D:\Cracking\lab\PESpin\UnPackMe_PeSpin1.3.f.exe OEP: 00018B43 IATRVA: 0000119C IATSize: 00000034 FThunk: 0000119C NbFunc: 00000007 1 0000119C user32.dll 001B CallNextHookEx 1 000011A0 user32.dll 010F GetDesktopWindow 1 000011A4 user32.dll 0175 GetWindowRect 1 000011A8 user32.dll 01DD MessageBoxA 1 000011AC user32.dll 028B SetWindowsHookExA 1 000011B0 user32.dll 029A SystemParametersInfoA 1 000011B4 user32.dll 02AF UnhookWindowsHookEx FThunk: 000011EC NbFunc: 0000000B 1 000011EC kernel32.dll 00B7 ExitProcess 1 000011F0 kernel32.dll 013F GetCurrentThreadId 1 000011F4 kernel32.dll 0176 GetModuleHandleA 1 000011F8 kernel32.dll 01EB GlobalAlloc 1 000011FC kernel32.dll 01F2 GlobalFree 1 00001200 kernel32.dll 0203 HeapAlloc 1 00001204 kernel32.dll 0204 HeapCompact 1 00001208 kernel32.dll 0205 HeapCreate 1 0000120C kernel32.dll 0207 HeapDestroy 1 00001210 kernel32.dll 0209 HeapFree 1 00001214 kernel32.dll 03A4 lstrcat Теперь Fix Dump. Готово! У меня работает! Links www.tuts4you.com www.reversing.be arteam.accessroot.com PS С Вами был taha. О ошибках и неточностях писать в ПМ.