Авторские статьи Unpacking PESpin

Discussion in 'Статьи' started by taha, 26 Apr 2007.

  1. taha

    taha Elder - Старейшина

    Joined:
    20 Aug 2006
    Messages:
    399
    Likes Received:
    330
    Reputations:
    251
    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. О ошибках и неточностях писать в ПМ.
     
    12 people like this.