Главная » Статьи » Распаковка

Распаковка ASProtect 1.23 RC4
Цель

crackme - http://www.alex2kx.nm.ru/aspr123crackme.rar
Инструменты
SoftIce for NT
SuperBpm for NT
IceExt
Loader 2.0 by Syd
Hiew
Imprec 1.6
LordPE
PEeditor 1.7

Вступление

В этой статье я расскажу как распаковать столь популярный в наше время пакер ASProtect, а именно версию 1.23 RC 4. Для большей наглядности я разделю статью на 2 части: восстановления импорта, восстановления спертых байтов (Stolen Bytes) и нахождение OEP. Целю нашей распаковки будет мною созданный crackme, и упакованный ASProtect 1.23 RC 4 build 08.07. Итак приступим....

Восстановление импорта.

Я начал распаковку программы именно с восстановления импорта, а не с получения дампа и поиска OEP. Это связано с тем, что существует простой способ нахождения создания импорта, а затем уже в нужном месте можно брать дамп. Как всегда грузим программу под отладчиком, для этого я использую Loader by Syd, но это только если у вас 2000 или XP. Этот загрузчик, в отличии от загрузчиков типа break’n’enter не требует восстановления затертого байта. Для тех, кто им ни разу не пользовался объясню как его использовать. Для начала скачайте его, он входит в комплект stripper’a by syd и его можно скачать на сайте http://www.wasm.ru/. Затем необходимо открыть окно sice’a, и написать I3HERE ON, потом открыть файл и нажать Break on Oep. Чтобы не возникло проблем с обнаружением отладчика и с несрабатыванием бряков рекомендуется включить IceExt и SuperBpm. Дальше используем загрузчик. Как только мы оказались в отладчике на EP программы, ставим такой бряк: bpm mapviewoffile x, почему - объясню ниже. Как вы наверное уже знаете, я надеюсь, что для получения адреса функции служит GetProcAddress. Эта функция используется много раз в аспре. После распаковки программы в память функция GetProcAddress используется как раз для определения адресов апи функций и заполнения этими адресами таблицу импорта. После проверки и определения CRC программы, с помощью чего юзаются конструкции CreateFileA, CreateFileMapping, MapViewOfFile.... можно уже ставить бряк на GetProcAddress, тогда прога уже распакована в память. После того как сработает бряк на MapViewOfFile, а он может сработать два раза для обладателей XP c дополнительными темами XP, удаляем этот бряк и ставим бряк на GetProcAddress. Бряк на GetProcAddress сработает несколько раз, нас интересует срабатывание именно в таком месте:

001B:008E2F34 E88722FFFF CALL KERNEL32!GetProcAddress

001B:008E2F39 8945F8 MOV [EBP-08],EAX SS:0012FE04=00000000

001B:008E2F3C 837DF800 CMP DWORD PTR [EBP-08],00

001B:008E2F40 0F85DA000000 JNZ 008E3020Б
Далее нажимаем F12 до тех пор, пока не окажемся в таком месте:

001B:008E32B4 E847FCFFFF CALL 008E2F00 <- От сюда мы вышли
001B:008E32B9 E87EFEFFFF CALL 008E313C

001B:008E32BE 8B17 MOV EDX,[EDI]

001B:008E32C0 8902 MOV [EDX],EAX

001B:008E32C2 EB7E JMP 008E3342
Мы оказались в процедуре создания импорта. Тепер можем посмотреть, что творится в eax. А там как раз адрес АПИ функции. Следующий call, который находиться по адресу 008E32B9, принимает адресс апи функции и по нему возвращает адрес соответствующего переходника. Чтобы нам получить относительно чистый импорт мы должны обойти этот call, а именно за’nop’ить. Выглядит это так:

001B:008E32B4 E847FCFFFF CALL 008E2F00
001B:008E32B9 90 NOP

001B:008E32BA 90 NOP

001B:008E32BB 90 NOP

001B:008E32BC 90 NOP

001B:008E32BD 90 NOP

001B:008E32BE 8B17 MOV EDX,[EDI]

001B:008E32C0 8902 MOV [EDX],EAX
Если внимательно приглядеться, то по адресу 008E32C0 происходит заполнение таблицы импорта адресами АПИ функций. Нам необходимо определить начало этой таблицы импорта, для этого становимся на 008E32BE и смотрим, что находиться в edx. В edx как раз начало нашего импорта: 004420F0. Теперь жмем F12, оказываемся в таком месте:

001B:008E3541 B904000000 MOV ECX,00000004
001B:008E3546 01CE ADD ESI,ECX

001B:008E3548 E8E7FCFFFF CALL 008E3234

001B:008E354D 5B POP EBX

001B:008E354E EBCE JMP 008E351E

001B:008E3550 61 POPAD
Как мы видим, здесь цикл. На необходимо поставить бряк на 008E3550. Дальше трейсим, по пути встретиться SEH трюк :

001B:008E359C 648920 MOV FS:[EAX],ESP FS:00000000=817CDE84

001B:008E359F 3100 XOR [EAX],EAX

001B:008E35A1 EB01 JMP 008E35A4
Так вот, дойдя до 008E359C, необходимо ставить сразу бряк на 008E35A1, чтоб обойти XOR [EAX],EAX , иначе произойдет exception. Дальше доходим до такого места:

001B:008E35FC 60 PUSHAD
001B:008E35FD 68AC638E00 PUSH 008E63AC

001B:008E3602 8D45F4 LEA EAX,[EBP-0C]

001B:008E3605 FF35A47A8E00 PUSH DWORD PTR [008E7AA4]

001B:008E360B FF10 CALL [EAX] DS:0012FF7C=008FA164

001B:008E360D 61 POPAD
По адресу 008E360B, происходит эмуляция таких функций как: GetModuleHandleA, GetProcAddress, GetVersion и других. Вам может показаться, что за’nop’ив этот call мы получим чистый импорт, без переходников. Но реально же по адресам в таблице импорта лежат не адреса этих апи функций, а левые адреса. Так что быдем ручками чистить импорт :) Ставим бряк на 008E360D и зацикливаем прогу. (a [enter] jmp eip [enter] - для тех кто в танке). Теперь можем сделать два дела сразу: сдампить прогу и получить таблицу импорта. Для начала сдампим прогу, я использовал LordPe. Выбираем в списке процессов нашу зацикленную программу и нажимаем правой кнопкой мыши. Далее выбираем Dump Full и сохраняем наш дамп. Теперь будем разбираться с импортом. Запускаем ImpRec, выбираем в процессах нашу зацикленную прогу, далее в поле RVA пишем адрес начала нашей таблицы импорта с учетом imagebase, т.е. мы впишем 0420F0. Размер таблицы импорта мы не знаем поэтому рекомендуется ставить значения побольше, лишнее потом можно удалить. Так как мой crackme программа относительно небольшая по размеру, я оставил значение по умолчанию - 1000. Теперь жмем GetImports. Что-то получили :) теперь будем чистить этот импорт. Жмем на ShowInvalid, оказываемся в таком месте:



Неизвестные адреса, это адреса аспровских переходников о которых я говорил выше. Дальше необходимо определять, что эта за апи функция. Для упрощения и наглядности я ниже покажу вам как выглядят эти переходники/эмуляторы и объясню почему именно такой-то переходник есть такая-то функция.

GetProcAddress:

008E17A4 push ebp

008E17A5 mov ebp,esp

008E17A7 mov edx,[ebp+C]

008E17AA mov eax,[ebp+8]

008E17AD mov ecx,[8E6484] // DWORD value: 008E6304

008E17B3 mov ecx,[ecx]

008E17B5 cmp ecx,eax

008E17B7 jnz short 008E17C2

008E17B9 mov eax,[edx*4+8E63D8] // DWORD value: 00000000

008E17C0 jmp short 008E17C9

008E17C2 push edx

008E17C3 push eax

008E17C4 call 008D51C0 // = kernel32.dll/018A/GetProcAddress

008E17C9 pop ebp

008E17CA retn 8
Понять, что это за эмулируемая функция, можно, если прерваться на адресе 008E17A4 и проследить, как будет выполняться программа. В конце концов, я приду на адрес 008E17C4 и выполнится функция GetProcAddress, которая вернет адрес запрашиваемой функции в регистре eax и с тех пор, eax больше меняться не будет. Значит это так и есть функция GetProcAddress.

GetModuleHandleA:
008E1C64 push ebp

008E1C65 mov ebp,esp

008E1C67 mov eax,[ebp+8]

008E1C6A test eax,eax

008E1C6C jnz short 008E1C81

008E1C6E cmp dword ptr [8E7AA4],400000

008E1C78 jnz short 008E1C81

008E1C7A mov eax,[8E7AA4] // DWORD value: 00400000

008E1C7F jmp short 008E1C87

008E1C81 push eax

008E1C82 call 008D51B8 // = kernel32.dll/0168/GetModuleHandleA

008E1C87 pop ebp

008E1C88 retn 4
Аналогично как и с GetProcAddress. Замечу, что значение 00400000 было получено где-то заранее, и по адресу 008E1C6A проверяется первый переданный параметр функции, если он равен 0, то в eax заносится значение 00400000, так как GetModuleHandleA(0)=00400000.

GetCommandLineA:
008E1CD8 push 0

008E1CDA call 008D51B8 // = kernel32.dll/0168/GetModuleHandleA

008E1CDF push dword ptr [8E7E14] // DWORD value: 0A280105

008E1CE5 pop eax

008E1CE6 mov eax,[8E7E24] // DWORD value: 00142378

008E1CEC retn
По адресу 008E1CDA вызывается некая функция-пустышка, так как никакой роли она не играет. По адресу 008E1CE6 в eax заноситься какое-то значение. Если в отладчике посмотреть, что находиться по этому адресу (d eax), то мы увидем полный путь к файлу. Следовательно это и есть функция GetCommandLineA.

LockResource:

008E1CC8 push ebp
008E1CC9 mov ebp,esp

008E1CCB mov eax,[8E7E24] // DWORD value: 00142378

008E1CD1 mov eax,[ebp+8]

008E1CD4 pop ebp

008E1CD5 retn 4

Эта функция возвращает то же самое значение, что и принимает. Т.е. когда происходит вызов функции call, то в esp помещается адрес возврата. Когда происходит команда push ebp, в esp уже заноситься значение ebp, и указатель стека смещается. Следовательно адрес возврата будет находиться по адресу [esp+4], а передаваемый параметр по адресу [esp+8]. Команда mov ebp,esp помещает начало стэка в ebp, поэтому значение [ebp+8] будет аналогично [esp+8]. А по этому адресу лежит передаваемый параметр, он же возвращается функцией. Так хитро аспр эмулирует LockResource

GetVersion:

008E1C8C push dword ptr [8E7E14] // DWORD value: 0A280105
008E1C92 pop eax

008E1C93 retn

GetCurrentProcessId:

008E1CC0 mov eax,[8E7E20] // DWORD value: 00000658

008E1CC5 retn
Можем заметить, что в eax заносится небольшое значение. А если посмотреть в ImpRec к примеру, то в списках процессов, напротив нашего файла будет находиться как раз такое значение. Это есть идентификатор текущего процесса. Значит этот код эмулирует GetCurrentProcessId

FreeResource:

008E1CF0 push ebp
008E1CF1 mov ebp,esp

008E1CF3 mov eax,[8E7E24] // DWORD value: 00142378

008E1CF9 pop ebp

008E1CFA retn 4
Сначала вам может показаться, что это еще одна эмуляция GetCommandLineA. Но функция GetCommandLineA не имеет параметров ! Аспротект так хитро эмулирует FreeResource. Для того, чтобы продемонстрировать еще одну эмуляцию, я в своем crackme сделал наг, окошко из ресурсов. Поэтому следующая функция эмулируется аспром вот так:

DialogBoxParamA:

008E1D14 push ebp

008E1D15 mov ebp,esp

008E1D17 push ebx

008E1D18 mov ebx,[ebp+8]

008E1D1B mov eax,[ebp+18]

008E1D1E push eax

008E1D1F mov eax,[ebp+14]

008E1D22 push eax

008E1D23 mov eax,[ebp+10]

008E1D26 push eax

008E1D27 push 5

008E1D29 mov eax,[ebp+C]

008E1D2C push eax

008E1D2D push ebx

008E1D2E call 008D5158 // = kernel32.dll/00D5/FindResourceA

008E1D33 push eax

008E1D34 push ebx

008E1D35 call 008D51F8 // = kernel32.dll/0234/LoadResource

008E1D3A push eax

008E1D3B call 008D5200 // = kernel32.dll/0242/LockResource

008E1D40 push eax

008E1D41 push ebx

008E1D42 call 008D5228 // = user32.dll/009C/DialogBoxIndirectParamA

008E1D47 pop ebx

008E1D48 pop ebp

008E1D49 retn 14
Если в отладчике набрать u DialogBoxParamA, то мы увидем точно такой же код, как и использует аспротект при умуляции.
Еще есть эмуляция, которой в нашем случае не было, это эмуляция GetCurrentProcess. Там в эмуляторе заноситься в eax DWORD value: FFFFFFFF. Запомните !
Чтобы самому посмотреть, как выглядит код переходника/эмулятора, жмем правой кнопкой мыши по адресу и выбираем Dissassemler / Hex View. Как только определили функцию, кликаем два раза по этой строке и выбираем в списке функцию. Еще один момент. Вы можете увидеть такое:



Т.е. F49C293 никак на адрес не смахивает, в данном месте это является "разделителем" между адресами апи функций разных библиотек. Выбираем, жмем Cut thunk(s). У нас появилась новая ветка :) Вот в таком духе делаем всё. В конце вы встретите кучу мусора, т.к. адрес мы не точный ставили, а примерный. Поэтому после последней определившейся апи функции жмем Cut thunk(s), а потом самая последняя ветвь и будет мусором, выбираем, жмем Delete thunk(s). В итоге вы получите чистый импорт, как и я получил:



Теперь жмем Fix Dump, выбираем наш дамп. Теперь мы получили уже почти полную распакованную прогу. Нам осталось только восстановить спертые байты и найти OEP. Итак часть вторая...

Восстановление спертых байтов и поиск OEP

Грузим еще раз прогу, на EP проги ставим всем знакоый бряк bpm esp-4. На третий раз бряк сработает в нужном нам месте, дальше пойдет мусорный код, в котором и выполняются спертые команды. Для начала хотелось бы маленьки обратить внимание на термин "спертые байты". В нашем случае аспр забирает начало нашей проги, которая написана на Delphi. Начало у Delphi прог почти всегда такое, ну по крайней мере первые три инструкции:
push ebp

mov ebp,esp

add esp, xxxx или sub esp, xxxxx

mov eax, yyyy

call zzzzz // InitExe

В большинстве случаев, где-то 90 %, аспр забирает байты до первого call’a. В нашем случае именно так и происходит. Мусорный код выглядит примерно так:

001B:008F5A89 61 POPAD
001B:008F5A8A 3EEB02 JMP 008F5A8F

001B:008F5A8D CD20 INT 20 VXDJmp CD02,6B26

001B:008F5A93 20EB AND BL,CH

001B:008F5A95 02CD ADD CL,CH

001B:008F5A97 2068A8 AND [EAX-58],CH

001B:008F5A9A 8EC8 MOV CS,AX

001B:008F5A9C 185389 SBB [EBX-77],DL

001B:008F5A9F 7424 JZ 008F5AC5

001B:008F5AA1 0464 ADD AL,64

001B:008F5AA3 EB01 JMP 008F5AA6

001B:008F5AA5 9A8D642404EB01 CALL 01EB:0424648D

001B:008F5AAC F081DEA3FCBE4E LOCK SBB ESI,4EBEFCA3
Некоторые не любят копаться в этом мусорном коде, потому что он бывает очень большим и напутанным. Но мы попробуем это сделать. Трейсим по F8 немного этот код пока сразу же в глаза нам не попадется вот это:
001B:008F5AE6 E8558BEC81 CALL 827BE640

001B:008F5AE7 55 PUSH EBP

001B:008F5AE8 8BEC MOV EBP,ESP

001B:008F5AEA 81EC0C000000 SUB ESP,0000000C

001B:008F5AF0 F2EB01 REPNZ JMP 008F5AF4

001B:008F5AF3 E8668135FD CALL FDC4DC5E
Здесь как раз выполняются наши спертые команды. Замечу, что команда sub esp, 0000000C слишком большая, т.е. занимает целых 5 байт ! На деле эта команда выглядит так add esp, -0C ! и занимает всего 3 байта. Теперь нам осталось только узнать yyyy, трейсим наш мусорный код до ret и смотрим что у нас хранится в eax, а в eax у нас как раз нужное нам значение yyyyy. Т.е. в мусорном коде мы не найдем инструкцию mov eax, yyyyy. Значение yyyyy генерится теким способом, но нам это впринципе не важно, т.к. если уж аспр забрал команды, то он должен и также их восстановить, и как раз у нас при выходе из мусорного кода в eax будет нужный адрес. После мусорного кода мы попадем на переходник GetModuleHandleA. Аспр - первый пакер, который при краже байт стал заходить в call, в нашем случае call zzzzz. Теперь жмем F12, чтобы оказаться в главной ветви программы, т.е. рядом с OEP. Это выглядит так:
001B:0043F50B E8B469FCFF CALL 00405EC4

001B:0043F510 6A00 PUSH 00 <- мы здесь

001B:0043F512 68F4F24300 PUSH 0043F2F4

001B:0043F517 6A00 PUSH 00

001B:0043F519 6A64 PUSH 64
Мы вышли как раз из call zzzzz. Выше этого call’a есть пустые байты, на месте которых должны были находиться спертые команды. Туда мы и будем вписывать спертые байты. Размер спертых байт составляет: 1 (push ebp)+2 (mov ebp,esp)+3 (add esp,-0C)+5 (mov eax,0043F3C8)=11 или B (hex) байт. Поэтому 0043F50B-B=0043F500, это и есть наше OEP ! Открываем файл в HIEW, по адресу 0043F500 начинаем писать:
push ebp

mov ebp, esp

add esp, -0C

mov eax, 0043F3C8
Теперь подправляем EP нашего дампа на 0003F500, например с помощью PEeditor, и можете смело запускать дамп. Если вы все сделали правильно, могу вас поздравить, вы теперь умеете распаковывать ASProtect 1.23 :). От вас осталось убрать наг :) :)
На последок хотелось бы передать приветы этим людям: MozgC[TSRh], Kerghan, Hex, Angel_aka_K$, Madness, YMY, Bad_Guy и многим другим.


Источник: http://www.dotfix.net
Категория: Распаковка | Добавил: -=Hellsing=- (17.06.2009) | Автор: -= ALEX =- E
Просмотров: 3036 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]