Распаковка PESpin 0.7 Содержание Введени Инструменты Поиск OEP Восстановление импорта Умный в гору не пойдёт Заключение Введение Сегодня я покажу как вручную распаковать протектор PESpin. Итак, подопытную программу вы можете скачать тут: http://www.tuts4you.com/blogs/download.php?view.338 Что такое? Там же тутор есть. - скажет читатель. Да есть и кое-где мы будем на него опираться. Просто я расскажу больше чем в туторе. Вы в этом убедитесь. Эта статья посвящена скорее тому, как обойти злобный протектор. Инструменты Ollydbg + plugins(OllyScript,OllyDump,CmdBar) Imp Rec И всё!!! Поиск OEP Грузим прогу в Olly. Давайте просто запустим программу, F9. Она тормозится на каком то исключении, нам первые исключения не интересны. Идём дальше. Code: 004001C9 FFFF ??? ; Unknown command 004001CB FFFF ??? ; Unknown command 004001CD FFFF ??? ; Unknown command 004001CF FFFF ??? ; Unknown command 004001D1 FFFF ??? ; Unknown command 004001D3 FFFF ??? ; Unknown command 004001D5 FFFF ??? ; Unknown command 004001D7 FFFF ??? ; Unknown command Code: 00407F17 2BDB SUB EBX,EBX 00407F19 64:8F03 POP DWORD PTR FS:[EBX] 00407F1C 58 POP EAX 00407F1D 5D POP EBP И программа запущена. Скорее всего OEP будет идти после последнего или предпоследнего исключения (чаще после последнего). Ставим бряк на esp-4.Через CmdBar, набрав hr esp-4. Или можно, пройдя pushad, выбрать в окне регистров esp, нажать на правую и выбрать Follow in dump. В окне дампа, выделяем первые четыре байта, на правую Breakpoint > Hardware, on access > Dword. F9, и идём до последнего исключения, ещё Shift-F9. Ctrl-A - и Олька анализирует код. Code: 00407087 61 POPAD 00407088 6A 00 PUSH 0 ; <----- Мы тут 0040708A EB 01 JMP SHORT packed.0040708D 0040708C E3 68 JECXZ SHORT packed.004070F6 0040708E 97 XCHG EAX,EDI 0040708F 70 40 JO SHORT packed.004070D1 00407091 00E9 ADD CL,CH Дальше трейсим по F8. После прыжка попадаем на пару байт ниже. Code: 0040708D 68 97704000 PUSH packed.00407097 00407092 -E9 7DA0FFFF JMP packed.00401114 Мда странно. Зачем такие переходы? Лан идём дальше. Code: 00401114 .-E9 04EF4600 JMP 0087001D 00401119 FF DB FF 0040111A $-E9 6BEF4600 JMP 0087008A 0040111F FF DB FF Хм ничего не напоминает? Мне да!! Переходы на импортируемые функции. Но нефакт. Дальше. 0087001D EB 01 JMP SHORT 00870020 Code: 00870020 8BFF MOV EDI,EDI ; packed.00407B1D 00870022 55 PUSH EBP 00870023 8BEC MOV EBP,ESP 00870025 837D 08 00 CMP DWORD PTR SS:[EBP+8],0 00870029 EB 07 JMP SHORT 00870032 Идём до прыжка, и f8. 00870032 ^EB F8 JMP SHORT 0087002C Дальше. 0087002C -E9 01B5F97B JMP kernel32.7C80B532 Ещё =). 7C80B532 74 18 JE SHORT kernel32.7C80B54C Мы в kernel32, а если взглянуть ниже иожно увидеть вызов GetModuleHandleW. Посмотрим на пару байт выше. Code: 7C80B529 > 8BFF MOV EDI,EDI 7C80B52B 55 PUSH EBP 7C80B52C 8BEC MOV EBP,ESP 7C80B52E 837D 08 00 CMP DWORD PTR SS:[EBP+8],0 Помоему я где то это уже видел. Смотри 00870020. Хех он что, у API байты ворует? Откуда всё началось? Code: 00407088 6A 00 PUSH 0 ; 0040708A EB 01 JMP SHORT packed.0040708D Протектор возвращяет на место значения раегостров (popad). А дальше судя по всему идёт API. OEP? Надо проверить нашу догадку. Трейсим дальше. Скажем, не далеко от вызова GetModuleHendleA, мы попадаем в USER32, посмотрев ниже, мы видим: Code: 77D4891B 6A 02 PUSH 2 77D4891D FF75 18 PUSH DWORD PTR SS:[EBP+18] 77D48920 FF75 14 PUSH DWORD PTR SS:[EBP+14] 77D48923 FF75 10 PUSH DWORD PTR SS:[EBP+10] 77D48926 50 PUSH EAX 77D48927 56 PUSH ESI 77D48928 E8 69DFFFFF CALL USER32.DialogBoxIndirectParamAorW Это скорее всего DialogBoxParamA. Из чего можно сделать вывод, что 00407088 - это искомый OEP. Удаляем бряк с esp. Ну и снимаем дамп естественно, через OllyDump. Т.к. до OEP, в процессе исследования, придётся доходить часто, напишем скрипт: go_tEP.txt Code: var real_OEP mov real_OEP,00407088 // найденый OEP var ex_OEP mov ex_OEP,00407F17 // последнее исключение ff: eoe fOEP esto fOEP: cmp eip,ex_OEP jne ff bp real_OEP esto bc real_OEP cmt eip," <------------- OEP" an real_OEP ret ex_OEP - это то самое исключение, после которого идёт OEP. Сначала мы идём до последнего исключения. Это делается для того, что бы нам ничего не мешало в процессе прохода до OEP. Вобщем после того как доходим до последнего исключения, ставим бряк на OEP и esto (Shift-F9). Мы на OEP. Согласитесь, использовать скрипт очень удобно, он экономит время. Восстановление импорта Да, будет нелегко, но мы справимся. Итак, перезапускаем программу, вызываем наш скрипт go_to_oep. Т.к. протектор прыгает на пару байт дальше адреса функции, ImpRec нам не поможет. Придётся делать список функций, что бы составиь файл для ImpRec. Есть несколько способов решения данной проблемы. Первый: Нужно поставтить бряки на все вызовы функций (я имею ввиду на jump'ы начиная с 0040110E). И запустить программу (F9), дальше, когда сработают бряки, идти до функций и подниматься на пару байт вверх. F10 > Search for >Name in all modules. Опять F10 > Sort by > Address (Это упростит поиск). Ищем полученный адрес. Например: Первый раз бряк сработает тут ( GetModuleHandleA, но мы этого пока не знаем =) ) 00401114 .-E9 04EF4600 JMP 0087001D F8 пока не попадём сюда: 7C80B532 74 18 JE SHORT kernel32.7C80B54C Вверх на пару байт. Как мы знаем функция начинается c 7C80B529 > 8BFF MOV EDI,EDI Следовательно в окне All names ищем 7C80B529. 7C80B529 .text Export GetModuleHandleA И так с каждой функцией. Втрой: Мы всё ещё на OEP. Откроем окно memory map (Alt-M), и посмотрим как в памяти рапсоложенна наша прога. Code: 00400000 00001000 packed PE header Imag RWE RWE 00401000 00001000 packed .petite code Imag RWE RWE 00402000 00001000 packed .petite data Imag RWE RWE 00403000 00001000 packed .petite Imag RWE RWE 00404000 00002000 packed .rsrc resources Imag RWE RWE 00406000 00003000 packed .petite SFX,imports Imag RWE RWE 00410000 00103000 Map R R 00520000 00129000 Map R E R E 00820000 00003000 Map R R 00830000 00008000 Priv RW RW 00840000 00001000 Priv RW RW 00850000 00001000 Priv RW RW 00860000 00004000 Priv RW RW 00870000 00001000 Priv RWE RWE 5D5B0000 00001000 COMCTL32 PE header Imag R RWE 5D5B1000 00070000 COMCTL32 .text code,imports Imag R RWE Мы знаем, что в процессе работы она гуляет по всем байтам до 5D5B0000. Вернёмся назад. Мы знаем, что код программы и протетора выполняется от 00400000 до 5DB0000. Давайте задаим параметры трассировки Debug > Set condition (Ctrl-T). В первом и во втором полях EIP is outside the range пишем 00400000 и 5DB0000 соответственно. Ставим галочку напротив EIP is outside the range, OK. Ctrl-F11 и мы тут: 7C80B532 74 18 JE SHORT kernel32.7C80B54C Ну дальше опять поднимаемся на пару байт вверх. Ищем адрес функции в Name in all Modules. И когда мы закончим у нас будет вот такой список: Code: KERNEL32---------------- 40110E All names, item 17785 Address=7C81CAA2 kernel32 Section=.text Type=Export (Known) Name=ExitProcess 401114 All names, item 5136 Address=7C80B529 kernel32 Section=.text Type=Export (Known) Name=GetModuleHandleA USER32---------------- 40111A Names in USER32, item 15 Address=77D3B4B1 Section=.text Type=Export (Known) Name=BeginPaint 401120 All names, item 1707 Address=77D488E1 USER32 Section=.text Type=Export (Known) Name=DialogBoxParamA 401126 All names, item 9904 Address=77D46CC9 USER32 Section=.text Type=Export (Known) Name=EndDialog 40112C All names, item 9704 Address=77D3B4C5 USER32 Section=.text Type=Export (Known) Name=EndPaint 401132 All names, item 9901 Address=77D467A8 USER32 Section=.text Type=Export (Known) Name=LoadBitmapA 401138 Names in USER32, item 825 Address=77D3E2AE Section=.text Type=Export (Known) Name=SendMessageA GDI32---------------- 40113E All names, item 12302 Address=77F16DC0 GDI32 Section=.text Type=Export (Known) Name=BitBlt 401144 All names, item 12283 Address=77F15E10 GDI32 Section=.text Type=Export (Known) Name=CreateCompatibleDC 40114A All names, item 12301 Address=77F16CA6 GDI32 Section=.text Type=Export (Known) Name=DeleteDC 401150 All names, item 12300 Address=77F16A3B GDI32 Section=.text Type=Export (Known) Name=DeleteObject 401156 All names, item 12279 Address=77F159A0 GDI32 Section=.text Type=Export (Known) Name=SelectObject Дальше нам нужно составить файл импорта для ImpRec. paced.txt Code: Target: C:\packed.exe OEP: 00007088 IATRVA: 0000306C IATSize: 00000100 FThunk: 00003070 NbFunc: 00000006 1 00003070 user32.dll 000E BeginPaint 1 00003074 user32.dll 009F DialogBoxParamA 1 00003078 user32.dll 00C7 EndDialog 1 0000307C user32.dll 00C9 EndPaint 1 00003080 user32.dll 01B6 LoadBitmapA 1 00003084 user32.dll 023C SendMessageA FThunk: 0000308C NbFunc: 00000002 1 0000308C kernel32.dll 00B0 ExitProcess 1 00003090 kernel32.dll 016F GetModuleHandleA FThunk: 00003098 NbFunc: 00000005 1 00003098 gdi32.dll 0013 BitBlt 1 0000309C gdi32.dll 002E CreateCompatibleDC 1 000030A0 gdi32.dll 008D DeleteDC 1 000030A4 gdi32.dll 0090 DeleteObject 1 000030A8 gdi32.dll 020F SelectObject Открывеам ImpRec вибираем процесс и жмём на Load tree, выбираем packed.txt. Fix'ируем на dumped.exe. Запускаем наш dumped_.exe. Что такое? он не работает =(. Ну естественно надо ещё jump'ы подправить. Открываем dumped_.exe. Ctrl-G > 0040110E. Мда гора мусора. Code: 0040110E E9 DB E9 0040110F ED DB ED 00401110 EE DB EE 00401111 46 DB 46 ; CHAR 'F' 00401112 00 DB 00 Ctrl-B,галочка с keep size убрана, пишем FF25, Ok. Появился jump. Code: 0040110E FF25 EE4600FF JMP DWORD PTR DS:[FF0046EE] Теперь откопируем его столько раз, сколько у нас функций. Теперь исправляем адреса. Адреса функций храняться в .mackt. У меня это адрес 00409000. Там лежат адреса наших функций. Так вот исправляем наши джампы. Вместо FF0046EE будет адрес адреса функции. Вот: Code: 0040110E -FF25 A2CA817C JMP DWORD PTR DS:[kernel32.ExitProcess] Все jump должны остаться на своиз местах, нльзя менять местами функции. Как мне разобраться что есть что в окне дампа .mackt? - спросит читатель. Действительно трудно, но вспомнить адреса апи вам поможет плагин APIFinder. Умный в гору не пойдёт Как всё геморойно! Скажет чиатель и будет прав. Я тоже не люблю возиться с импортом. Главное найти OEP, а там.... Но этот протектор ворует байты у API, что очень затрудняет взлом. И тут вступаю в дело я =)). А что если написать скрипт который сам восстановит IAT. Приступим. Итак код прохода до OEP есть. Вернёмся в packed.exe. Что делать дальше? Идея очень проста. Поставим бряки на все джампы. Code: var y // первый jump mov y,0040110E f_IAT: bp y add y,6 cmp y,0040115C jne f_IAT eob f_bpx run Дальше по мере срабатывания бряков, мы будим их удалять. Когда сработает бряк, мы будем к примеру тут: 00401114 .-E9 04EF4600 JMP 0087001D Введём переменную, в которую будем сохранять адрес jmp. var otcuda Проходим jmp'ы: sto sto И мы тут: 00870020 8BFF MOV EDI,EDI ; packed.00407B1D Нужно посчитать растояние до перехода к переходу на переход к функции =). Тобишь до сюда: 00870029 EB 07 JMP SHORT 00870032 Мы же должны знать сколько байт спёр протектор. Code: findop eip,#EB07# mov count,$RESULT sub count,eip Конечно перед этим вводим переменную count. Дальше идём до этого jump'а: Code: sol_byte: sti cmp eip,$RESULT jne sol_byte Проходим джампы: Code: sto sto sto И мы в kernel32, ну или где там будем. Вычисляем адрес начала функции: Code: mov x,eip sub x,count и сохраняем его в какю-нибудь не нужную программе область. Я выбрал 0040115F. Пишем туда адрес функции и снимаем бряк с джампа. Code: mov [cuda],x bc otcuda Теперь на место старого пишем новый. Code: mov [otcuda],25FF add otcuda,2 mov [otcuda],cuda Увеличиваем cuda на 4. И: Code: run jmp f_bpx Вот скрипт: Code: var real_OEP mov real_OEP,00407088 // найденый OEP var ex_OEP mov ex_OEP,00407F17 // последнее исключение ff: eoe fOEP esto fOEP: cmp eip,ex_OEP jne ff bp real_OEP esto bc real_OEP cmt eip," <------------- OEP" an real_OEP var count var x var y mov y,0040110E var otcuda var cuda mov cuda,0040115F f_IAT: bp y add y,6 cmp y,0040115C jne f_IAT eob f_bpx run f_bpx: mov otcuda,eip sto sto findop eip,#EB07# mov count,$RESULT sub count,eip sol_byte: sti cmp eip,$RESULT jne sol_byte sto sto sto mov x,eip sub x,count mov [cuda],x bc otcuda mov [otcuda],25FF add otcuda,2 mov [otcuda],cuda add cuda,4 run jmp f_bpx Запускаем его. После того как окно запустится и перестанет бегать строчка: Ctrl-G > 0040110E. И вот что там: Code: 0040110E .-E9 EDEE4600 JMP 00870000 00401113 FF DB FF 00401114 -FF25 5F114000 JMP DWORD PTR DS:[40115F] ; kernel32.GetModuleHandleA 0040111A -FF25 6B114000 JMP DWORD PTR DS:[40116B] ; USER32.BeginPaint 00401120 -FF25 63114000 JMP DWORD PTR DS:[401163] ; USER32.DialogBoxParamA 00401126 $-E9 21EF4600 JMP 0087004C 0040112B FF DB FF 0040112C -FF25 7B114000 JMP DWORD PTR DS:[40117B] ; USER32.EndPaint 00401132 -FF25 67114000 JMP DWORD PTR DS:[401167] ; USER32.LoadBitmapA 00401138 $-E9 81EF4600 JMP 008700BE 0040113D FF DB FF 0040113E -FF25 77114000 JMP DWORD PTR DS:[401177] ; GDI32.BitBlt 00401144 -FF25 6F114000 JMP DWORD PTR DS:[40116F] ; GDI32.CreateCompatibleDC 0040114A -FF25 7F114000 JMP DWORD PTR DS:[40117F] ; GDI32.DeleteDC 00401150 $-E9 AAEF4600 JMP 008700FF 00401155 FF DB FF 00401156 -FF25 73114000 JMP DWORD PTR DS:[401173] ; GDI32.SelectObject Да определились не все функции, но ведь не все бряки сработали. Закрываем программу. И строчка опять побежала, после остановки: Ctrl-G > 0040110E. Code: 0040110E .-FF25 8F114000 JMP DWORD PTR DS:[40118F] ; kernel32.ExitProcess 00401114 .-FF25 5F114000 JMP DWORD PTR DS:[40115F] ; kernel32.GetModuleHandleA 0040111A $-FF25 6B114000 JMP DWORD PTR DS:[40116B] ; USER32.BeginPaint 00401120 .-FF25 63114000 JMP DWORD PTR DS:[401163] ; USER32.DialogBoxParamA 00401126 $-FF25 8B114000 JMP DWORD PTR DS:[40118B] ; USER32.EndDialog 0040112C $-FF25 7B114000 JMP DWORD PTR DS:[40117B] ; USER32.EndPaint 00401132 $-FF25 67114000 JMP DWORD PTR DS:[401167] ; USER32.LoadBitmapA 00401138 $-FF25 83114000 JMP DWORD PTR DS:[401183] ; USER32.SendMessageA 0040113E $-FF25 77114000 JMP DWORD PTR DS:[401177] ; GDI32.BitBlt 00401144 $-FF25 6F114000 JMP DWORD PTR DS:[40116F] ; GDI32.CreateCompatibleDC 0040114A $-FF25 7F114000 JMP DWORD PTR DS:[40117F] ; GDI32.DeleteDC 00401150 $-FF25 87114000 JMP DWORD PTR DS:[401187] ; GDI32.DeleteObject 00401156 $-FF25 73114000 JMP DWORD PTR DS:[401173] ; GDI32.SelectObject 0040115C 00 DB 00 0040115D 00 DB 00 0040115E 00 DB 00 0040115F . 29B5807C DD kernel32.GetModuleHandleA 00401163 . E188D477 DD USER32.DialogBoxParamA 00401167 . A867D477 DD USER32.LoadBitmapA 0040116B . B1B4D377 DD USER32.BeginPaint 0040116F . 105EF177 DD GDI32.CreateCompatibleDC 00401173 . A059F177 DD GDI32.SelectObject 00401177 . C06DF177 DD GDI32.BitBlt 0040117B . C5B4D377 DD USER32.EndPaint 0040117F . A66CF177 DD GDI32.DeleteDC 00401183 . AEE2D377 DD USER32.SendMessageA 00401187 . 3B6AF177 DD GDI32.DeleteObject 0040118B . C96CD477 DD USER32.EndDialog 0040118F . A2CA817C DD kernel32.ExitProcess Копируем всё это великолепие через binary copy. Перезапускаем программу, идём до OEP, Ctrl-A, Ctrl-G > 0040110E, вставляем всё что накопровали. Ctrl-A, поднимитесь выше!! Code: 004010A5 |. 50 PUSH EAX ; /pPaintstruct 004010A6 |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd 004010A9 |. E8 6C000000 CALL packed.0040111A ; \BeginPaint 004010AE |. 8945 BC MOV DWORD PTR SS:[EBP-44],EAX 004010B1 |. FF75 BC PUSH DWORD PTR SS:[EBP-44] ; /hDC 004010B4 |. E8 8B000000 CALL packed.00401144 ; \CreateCompatibleDC Без комментариев!! Заключение Не парься %)!