ANTICHAT.XYZ    VIDEO.ANTICHAT.XYZ    НОВЫЕ СООБЩЕНИЯ    ФОРУМ  
Баннер 1   Баннер 2

ANTICHAT — форум по информационной безопасности, OSINT и технологиям

ANTICHAT — русскоязычное сообщество по безопасности, OSINT и программированию. Форум ранее работал на доменах antichat.ru, antichat.com и antichat.club, и теперь снова доступен на новом адресе — forum.antichat.xyz.
Форум восстановлен и продолжает развитие: доступны архивные темы, добавляются новые обсуждения и материалы.
⚠️ Старые аккаунты восстановить невозможно — необходимо зарегистрироваться заново.
Вернуться   Форум АНТИЧАТ > Программирование > Реверсинг
   
Ответ
 
Опции темы Поиск в этой теме Опции просмотра

  #11  
Старый 09.06.2008, 05:56
Ra$cal
Постоянный
Регистрация: 16.08.2006
Сообщений: 640
Провел на форуме:
1354067

Репутация: 599


По умолчанию

CrackMe: crackme_vm by _Great_
Цель: найти ключ попутно закейгенить
Сложность: средне
link: ссылка

Давно я не писал на ачате, пора нарушить обет молчания. Да и есть хороший повод поболтать - крякми с вм.
Кто не знает, почему так существенно упоминание о ВМ, послушайте кратенький эпос, в чем сложность взлома таких программ: када вы ломаете обычные программы, вы видите в отладчике команды процессора(мнемокод), видите панель регистров, окно дампа памяти, брекпоинты итп. Вы хорошо знаете набор команд данного процессора и можете легко понять алгоритм по диизасм листингу, отбрасывая кучи полиморфа и мусора. Да, вы профи, и противник знает это, поэтому он идет ва-банк. Единственный способ лишить вас преимущества - играть на другом поле (читай работать на другом процессоре). Чем это грозит - незнакомая структура команд, незнакомые сами команды. Никакой мнемоники - только машкоды виртуального процессора. Никаких доков об архитектуре процессора - только рабочий интерпритатор и кучка пикода. Это все равно что велогонщику дать велосипед с квадратными колесами. Чтобы взломать такую программу вам придется сначала разобраться со структурой команд вм, дальше декодировать опкоды вм, дальше декомпилировать пикод программу, чтобы получить последовательность команд вм в понятном для осознания виде, чтобы из них составить алгоритм работы пикода. А чтобы сделать это, вы должны не просто знать реальный процессор, вы должны еще хорошо соображать и быть самым что ни на есть реверсером. Поэтому защита программ с помощью вм - серьезное препятстсвие для многих крякеров. Кстати мой крякми с вм поломали только пара человек, и то не с ачата =) Поэтому мне эта тема близка, и я покажу, что вм - это не приговор, просто хороший повод понапрягать свой мозг.

Ну а теперь к делу. У нас имеется ехе небольшого веса (всего 6кб). Не пожат. Видим создание диалога. Топаем на оконную процедуру - PUSH crackme_.00402534 ; |DlgProc = crackme_.00402534
Убираем анализ кода, т.к. криво вставленные байты сильно пугают анализатор олли.
Код:
00402568    PUSH 13
Сюда бряк и поехали. Вводим name - Ra$cal , key - lalala. Заходим в пару колов и стоим тут
Код:
0040259E    CALL NEAR DWORD PTR DS:[<&NTDLL.sscanf>]     ; ntdll.sscanf
Это есть функция, которая рабирает строку на переменные по строке-формату. Стмотрим в стек

Код:
0006FBB0   00401569   |s = "lalala"
0006FBB4   00402596   |format = "%x"
0006FBB8   0040157D   OFFSET <crackme_.iKey>
%x - значит будет попытка считать из строки s 16тиричное число и записать его по адресу iKey. Значит ключ - DWORD. Есть вариант его набрутить. Но наша цель - вм. Поэтому отпускаем программу по F9 и вводим нормальное число - BAADF00D. По адресу iKey видим это число. Продолжаем.

Код:
004024C3  |.  MOV DWORD PTR DS:[<vm_regs>], EAX            ;  <crackme_.name>
004024C8  |.  PUSH EAX                                     ; /String
004024C9  |.  CALL NEAR DWORD PTR DS:[<&KERNEL32.lstrlenA>>; \lstrlenA
004024CF  |.  MOV DWORD PTR DS:[<uname_len>], EAX
Здесь кладется в секцию данных указатель на имя. Видно мой лейбл. Но об этом позже. Дальше кладется длина имени.

Код:
004024D4  |.  MOV EAX, DWORD PTR SS:[EBP+C]
004024D7  |.  MOV DWORD PTR DS:[<iKey2>], EAX
Здесь сохраняется числовой ключ.

Код:
004024DC  |.  MOV DWORD PTR DS:[401404], E98FB720
004024E6  |.  MOV DWORD PTR DS:[401420], 21525253
И две странных константы.

Код:
004024F0  |.  PUSH crackme_.004014BB
004024F5  |.  CALL crackme_.00402474
Спуск в вм.

Код:
0040248D  |.  MOV DWORD PTR DS:[<vm_eip>], EAX                      ;  p_code_addr
00402492  |.  MOV DWORD PTR DS:[40142C], OFFSET <crackme_.vm_regs>
0040249C  |>  /CALL <crackme_.vm_proc>
004024A1  |.  |TEST AL, AL
004024A3  |.^ \JE SHORT crackme_.0040249C
Это цикл обработки пикода. Как видим, он продолжается, пока vm_proc не вернет число, отличное от 0. Ну а теперь самое веселое - погружение в интерпритатор вм.
Поясню, как я пришел к выводу о назначении переменной vm_eip.

Код:
004020F2   .  MOV ESI, DWORD PTR DS:[<vm_eip>]                      ;  crackme_.004014BB
004020F8   .  LODS BYTE PTR DS:[ESI]                                ;  первый байт - опкод
Здесь идет чтение байта по адресу, взятому из переменной. Дальше будет считан следующий байт. Чисто логически понимаем, что тут нечего больше считывать, кроме как пикод. Посмотрим на дамп памяти.

Код:
004014BB   06 44 00 00 00 00 00 00  
004014C3   00 00 03 05 00 00 00 00  
004014CB   00 00 00 00 04 25 00 00  
004014D3   00 00 00 00 00 00 43 03  
004014DB   00 00 00 00 00 00 00 00 
004014E3   06 13 00 00 00 00 00 00  
004014EB   00 00 04 34 00 00 00 00  
004014F3   00 00 00 00 0A 00 00 00
Можете увидеть закономерности? Идут 2 ненулевых байта, затем 8 нулеых. Закономерность устойчива. Далее будут ненулевые байты на месте нулевых, это вполне логично, иначе бы зачем в командах держать пустые поля. Чисто на удачу можно сделать предположение о длине команды - 10 байт. А пока изучим команды, которые обрабатывают считанный пикод:

Код:
004020F8   .  LODS BYTE PTR DS:[ESI]                                ;  первый байт - опкод. esi += 1
004020F9   .  MOV BL, AL
004020FB   .  MOV CL, AL
004020FD   .  AND AL, 3F                                            ;  сохраняются 6 млабших бит
004020FF   .  MOV BYTE PTR SS:[EBP-14], AL
00402102   .  SHR BL, 7                                             ;  сохраняется старший бит номер 8
00402105   .  MOV BYTE PTR SS:[EBP-13], BL                          ;  8ой бит
00402108   .  SHR CL, 6                                             ;  сохраняется бит номер 7
0040210B   .  AND CL, 1
0040210E   .  MOV BYTE PTR SS:[EBP-F], CL                           ;  7ой бит
00402111   .  LODS BYTE PTR DS:[ESI]                                ;  второй байт. esi += 1
00402112   .  MOV BL, AL
00402114   .  AND BL, 0F                                            ;  осталяем 4 бита младших
00402117   .  SHR AL, 4                                             ;  переходим к следующим 4м битам
0040211A   .  MOV BYTE PTR SS:[EBP-B], BL                           ;  byte2_4_bits_low
0040211D   .  MOV BYTE PTR SS:[EBP-A], AL                           ;  byte2_4_bits_high
00402120   .  MOV ECX, DWORD PTR DS:[ESI+4]                         ;  следующие 4 байта пропускаются (vm_command+6)
00402123   .  PUSH DWORD PTR SS:[EBP-F]
00402126   .  CALL <crackme_.decode_cmd_argument>
0040212B   .  MOV DWORD PTR SS:[EBP-5], EAX                         ;  загрузить в ebp-5 содержимое регистра вм high
0040212E   .  MOV AL, BYTE PTR SS:[EBP-B]
00402131   .  MOV ECX, DWORD PTR DS:[ESI]							; считать дворд по адресу vm_command+2
00402133   .  PUSH DWORD PTR SS:[EBP-13]
00402136   .  CALL <crackme_.decode_cmd_argument>
0040213B   .  MOV DWORD PTR SS:[EBP-9], EAX                         ;  загрузить в ebp-9 содержимое регистра вм low
BYTE opcode = (*(BYTE*)vm_eip) & 0x3F; // 0x3F = 111111b - сохраняем младшие 6 бит, старшие обнуляем
BYTE bit_13 = (*(BYTE*)vm_eip) >> 7; // остался только восьмой бит
BYTE bit_f = ((*(BYTE*)vm_eip) >> 6) & 1; // сохранили седьмой бит и сбросили восьмой
Визуально первый байт команды раскладывается так:
X X XXXXXX
| | |_ opcode
| |___ bit_f
|_____ bit_13
Смотрим обработку второго считанного байта

BYTE field_b = (*(BYTE*)vm_eip+1) & 0x0F; // 0x0F = 1111b - сохраняем младшие 4 бита
BYTE field_a = (*(BYTE*)vm_eip+1) >> 4; // сохраняем старшие 4 бита
итого второй байт делится на биты так:
XXXX XXXX
| |_ field_b
|______ field_a

Причем если смотреть на байт, то здесь выполняется отделение первого и второго символов в байте. Например если байт будет равен 0xF5, то разложение даст 0x0F и 0x05. Это упростит нам наблюдение.
Так же видим чтение двух двордов здесь 00402120 и здесь 00402131.
DWORD dw_1 = *(DWORD*)((BYTE*)vm_eip+6);
DWORD dw_2 = *(DWORD*)((BYTE*)vm_eip+2);

Теперь к функции decode_cmd_argument

Код:
0040203A  |.  CMP AL, 9	
0040203C  |.  JLE SHORT crackme_.00402055
0040203E  |.  CMP AL, 0A
00402040  |.  JE SHORT crackme_.00402065
00402042  |.  CMP AL, 0B
00402044  |.  JE SHORT crackme_.0040206C
00402046  |.  CMP AL, 0C
00402048  |.  JE SHORT crackme_.00402073
0040204A  |.  PUSH 1
0040204C  |.  CALL crackme_.00402000
00402051  |.  LEAVE
00402052  |.  RETN 4
00402055  |>  PUSH ECX
00402056  |.  MOVZX ECX, AL
00402059  |.  MOV EAX, DWORD PTR DS:[ECX*4+401400]
00402060  |.  POP ECX
00402061  |.  ADD EAX, ECX
00402063  |.  JMP SHORT crackme_.00402075
00402065  |>  MOV EAX, DWORD PTR DS:[<vm_eip>]
0040206A  |.  JMP SHORT crackme_.00402075
0040206C  |>  MOV EAX, DWORD PTR DS:[40142C]
00402071  |.  JMP SHORT crackme_.00402075
00402073  |>  MOV EAX, ECX
00402075  |>  CMP BYTE PTR SS:[EBP+8], 1
00402079  |.  JNZ SHORT crackme_.0040207D
0040207B  |.  MOV EAX, DWORD PTR DS:[EAX]
0040207D  |>  LEAVE
0040207E  \.  RETN 4
В al лежит field_a. Проверяется на 9. Если меньше либо равно - идем к получению адреса, используя al как индекс. Итого имеем границу в 10 элементов. На другие джампы пока не смотрим. Дальше к считанному адресу прибавляется содержимое ecx. Ecx задается перед колом - это как раз считанный дворд из команды - dw_1.

Код:
00402075  |> CMP BYTE PTR SS:[EBP+8], 1
00402079  |. JNZ SHORT crackme_.0040207D
0040207B  |. MOV EAX, DWORD PTR DS:[EAX]
Здесь идет проверка байта, который так же передается функции, но через стек (bit_f). Если он = 1 - то идет разыменовывание eax. Иначе eax остается без изменения. Таким образом эта функция использует field_a для вычисления адреса элемента в массиве, и бит bit_f для чтения содержимого этого адреса. Вполне похоже на работу с регистрами вм. Пока мы можем только предполагать - в этом сложность исследований вм. Чтобы выявить закономерность нужно выполнить несколько пикод команд.
На выходе из функции имеем следующий момент

Код:
0040212B   .  MOV DWORD PTR SS:[EBP-5], EAX
Результат декодирования кладется в переменную. Дальше все то же самое, но для второй половины пикод команды. Результат декодирования пишется сюда

Код:
0040213B   .  MOV DWORD PTR SS:[EBP-9], EAX
Для понятливости назовем ebp-5 как r1, а ebp-9 как r2. Дальше начинается кутерьма с проверкой байта ebp-14, который есть 6 бит первого байта пикод команды. Это нам говорит о назначении этих битов - выбор операции. Значит это - код операции. Это можно утверждать достаточно уверенно. У нас код равен шести. Доходим до обработчика

Код:
00402231   > CMP BYTE PTR SS:[EBP-14], 6
00402235   . JNZ SHORT crackme_.0040225F
00402237   . MOV EDX, DWORD PTR SS:[EBP-9]
0040223A   . XOR EDX, DWORD PTR SS:[EBP-5]
0040223D   . MOV ECX, DWORD PTR DS:[ESI]
0040223F   . MOV AL, BYTE PTR SS:[EBP-B]
00402242   . PUSH DWORD PTR SS:[EBP-13]
00402245   . CALL <crackme_.save_to_vm_register>
Что мы здесь видим. Берется r2, и ксорится с r1. В ecx кладется dw_2(используется при вычислении r2), в al field_b(индекс регистра, адрес которого возвращается в r2), в стек bit_13, то поле, которое используется при вычислении r2 из индекса регистра. Пока все сходится - все поля относятся к одному логическому полю.

Код:
00402084  |.  PUSH EDI
00402085  |.  CMP AL, 9
00402087  |.  JLE SHORT crackme_.004020A1
00402089  |.  CMP AL, 0A
0040208B  |.  JE SHORT crackme_.004020B7
0040208D  |.  CMP AL, 0B
0040208F  |.  JE SHORT crackme_.004020BF
00402091  |.  CMP AL, 0C
00402093  |.  JE SHORT crackme_.004020C7
004020A1  |>  PUSH ECX
004020A2  |.  MOVZX ECX, AL
004020A5  |.  LEA EDI, DWORD PTR DS:[ECX*4+401400]
004020AC  |.  POP ECX
004020AD  |.  CMP BYTE PTR SS:[EBP+8], 1
004020B1  |.  JNZ SHORT crackme_.004020B5
004020B3  |.  ADD EDI, ECX
004020B5  |>  JMP SHORT crackme_.004020DD
004020B7  |>  LEA EDI, DWORD PTR DS:[<vm_eip>]
004020BD  |.  JMP SHORT crackme_.004020DD
004020BF  |>  LEA EDI, DWORD PTR DS:[40142C]
004020C5  |.  JMP SHORT crackme_.004020DD
004020C7  |>  CMP BYTE PTR SS:[EBP+8], 1
004020CB  |.  JE SHORT crackme_.004020D9
004020D9  |>  MOV EDI, ECX
004020DB  |.  JMP SHORT crackme_.004020E5
004020DD  |>  CMP BYTE PTR SS:[EBP+8], 1
004020E1  |.  JNZ SHORT crackme_.004020E5
004020E3  |.  MOV EDI, DWORD PTR DS:[EDI]
004020E5  |>  MOV DWORD PTR DS:[EDI], EDX
004020E7  |.  POP EDI
004020E8  |.  LEAVE
004020E9  \.  RETN 4
код похож на предыдующую функцию. так же выборка адреса регистра по индексу. только добавление к адресу поля dw_2 происходит по биту bit_13, так же через этот бит опять идет управление разыменовыванием. Ну а главная строка - 004020E5 MOV DWORD PTR DS:[EDI], EDX. Здесь значение регистра edx пишется по адресу, полученному из индекса и базового адреса. Вобщем, это запись в регистр вм.
Все. Выходим, проходим, и вот мы вышли из обработчика. Одна команда выполнена. Теперь надо бы закрепить наши предположения. Начинаем рассматривать вторую команду.
код следующий - 03 05. Если наша теория верна, то имеем следующее: 03 - opcode = 3. Биты обнулены. Индекс первого регистра - 0, второй индекс - 5.

Это считывание первого регистра. Там лежит наше имя.
DS:[00401400]=00401555 (<crackme_.name>), ASCII "Ra$cal"
EAX=00000000

Теперь должно будет считывать значение регистра 5. Так ли?

Код:
DS:[00401414]=0040155B (crackme_.0040155B)
EAX=00401505 (crackme_.00401505)
00401400 - базовый адрес регистров. 5*4 - 20 -> 14h. DS:[00401414] - да, наши предположения оправдываются. Теперь ищем обработчик для опкода 3.
Код:
004021B0   .  MOV EDX, DWORD PTR SS:[EBP-5] 
004021B3   .  MOV ECX, DWORD PTR DS:[ESI]
004021B5   .  MOV AL, BYTE PTR SS:[EBP-B]
004021B8   .  PUSH DWORD PTR SS:[EBP-13]
004021BB   .  CALL <crackme_.save_to_vm_register>
В edx кладется r1. А в r1 мы считывали имя. Дальше в al кладется индекс регистра из второго поля - 5. Проверим, запишется ли туда указатель на имя.

004020E5 |> MOV DWORD PTR DS:[EDI], EDX
EDX=00401555 (<crackme_.name>), ASCII "Ra$cal"
DS:[00401414]=0040155B (crackme_.0040155B)

Все верно. Итак, теперь можно заключить о структуре команд
Код:
1        23 4      5    6    7        8  
XXXXXXXX XX XXXXXX XXXX XXXX XXXXXXXX XXXXXXXX
Вот структура команды. Теперь объединим поля логически
1 - пока нам не известно, так что пропускаем
2 - бит, отвечающий за разыменовывание поля 6. типа mod r/m в архитектуре ia32 - modrm2
3 - бит, отвечающий за разыменовывание поля 5. типа mod r/m в архитектуре ia32 - modrm1
4 - код опеации - opcode
5 - индекс регистра, который будет помещен в r1 - rivm1 (register index vm 1)
6 - индекс регистра, который будет помещен в r2 - rivm2
7 - поле, используемое c битом 3 и полем 6 - data2
8 - поле, используемое с битом 2 и полем 5 - data1

Вот мы разобрали команды. Теперь нада разобрать все используемые опкоды. Пока картина следующая
06 - xor
03 - mov
Еще важный момент. Результат всегда кладется в r2, т.е. поле rivm2 является указанием как источника операнда, так и указанием получателя результата.

Итого мы можем преобразовать пикод уже в уме
004014BB: 06 44 -> xor rvm4, rvm4 ;// обнуляем rvm4
004014C5: 03 05 -> mov rvm5, rvm0 ;// rvm5 теперь содержит указатель на имя

Теперь нада упомянуть, что часть из регистров проинициализированны вне интерпритатора вм. Например rvm0 содержит изначально имя. Пока продолжим. Дальше я буду давать дизасм и останавливаться лишь на тех моментах, которые еще не были продебажины (использование modrm1 и modrm2, и использование data1 и data2). Остальные просто приводятся листинг обработчика опкода и декомпилированная команда

004014CF: 04 25 -> add rvm5, rvm2 ;// в rvm2 лежит длина имени. таким образом мы переходим к концу имени - видимо будет использована как проверка конца цикла обработки имени
Код:
004021DB   .  MOV EDX, DWORD PTR SS:[EBP-9] ;edx = r2
004021DE   .  ADD EDX, DWORD PTR SS:[EBP-5] ;r1 + r2
004014D9: 43 03 -> mov rvm3, [rvm0] ;// в rvm3 лежит 4 байта - букв из имени
Вот здесь modrm1 = 1. Смотрим, что изменится при выполнении декодирования rivm1.

Код:
00402075  |> CMP BYTE PTR SS:[EBP+8], 1
00402079  |. JNZ SHORT crackme_.0040207D
0040207B  |. MOV EAX, DWORD PTR DS:[EAX]
DS:[00401555]=63246152
EAX=00401555 (<crackme_.name>), ASCII "Ra$cal"

Угу, считываются символы из имени, а не адрес имени. Так что все так, как мы и предполагали. Опкод - 3, то есть mov
mov rvm3, [rvm0] ;// в rvm3 лежит 4 байта - букв из имени

Код:
004021C0   .  CMP BYTE PTR SS:[EBP-13], 1
004021C4   .  JE SHORT crackme_.004021D0
004021C6   .  CMP BYTE PTR SS:[EBP-B], 0A
004021CA   .  JE crackme_.00402470
Обсудим эту проверку. Если modrm1 = 0, т.е. мы изменили регистр вм, а не память, на которую указывал регистр, то проверяется регистр. Если индекс регистра = 0x0A, то мы выходим не трогая vm_eip, иначе выполняется переход к следующей команде. Тут следует упомянуть, что у данной вм использованн естественный порядок следования команд, как и у ia32, адрес следующей команды получается добавлением к vm_eip длины команды. Но для реализации условных переходов приходится нарушать это следование, или указывая процессору адрес, на который надо перейти (ia32 не позволяет писать в регистр eip), в данной же вм запись в регистр указатель команды очевидно возможна, и чтобы не испортить адрес перехода, естественное следование предотвращается при записи в vm_eip. Кароче, это просто проверка на случай команд вида jmp или mov vm_eip, чтобы не убить правильный адрес суммированием - пропускается вот этот код
00402467 > > ADD ESI, 8
0040246A . MOV DWORD PTR DS:[<vm_eip>], ESI

004014E3: 06 13 -> xor rvm3, rvm1
В rvm1 лежит константа - E98FB720

004024DC |. MOV DWORD PTR DS:[401404], E98FB720

004014ED: 04 34 -> add rvm4, rvm3 ;// rvm4 обнулили в самом начале, добавляем результат хэша

004014F7: 0A 00 -> inc rvm0 ;// к следующей букве в имени


Код:
0040230D   .  MOV EDX, DWORD PTR SS:[EBP-9]
00402310   .  INC EDX
Просто инкремент

00401501: 0F 50 cmp rvm0, rvm5 ;// сравнить текущий указатель в имени и указатель на конец имени

Код:
00402365   .  MOV EAX, DWORD PTR SS:[EBP-9]
00402368   .  MOV EBX, DWORD PTR SS:[EBP-5]
0040236B   .  CMP EAX, EBX
А это cmp

0040150B: 14 00 -> jg vm_eip+20

Код:
004023C5   > CMP BYTE PTR SS:[EBP-14], 14
004023C9   . JNZ SHORT crackme_.004023DC
004023CB   . CMP BYTE PTR DS:[<LE_F>], 1
004023D2   . JE SHORT crackme_.004023D7
004023D4   . ADD ESI, 0A
004023D7   > JMP <crackme_.lbl_end_command>
Jump is taken
004023D7=crackme_.004023D7

Тут проверяется флаг LE. Если он установлен, значит текущий адрес в имени меньше либо равен конца имени. Поэтому выполнится следующая команда. Когда адрес в имени уйдет за конец, выполнится изменение esi, проскочив следующую команду. Т.е. в этой вм условный переход возможен только на 1 команду вперед. Посмотрим, что произойдет дальше.

00401515: 03 CA 00000000 004014D9 mov vm_eip, 004014D9
А вот и использование поля data1. Посмотрим, что произойдет при декодировании первого rivm1

Код:
00402046  |.  CMP AL, 0C
00402048  |.  JE SHORT crackme_.00402073
00402073  |>  MOV EAX, ECX
Получается следующая картина. Когда rivm1 = 0x0C, на выход поступит содержимое ecx. А ecx - это есть data1. Итого на eax будет 004014D9
Поле 2 декдируется так тоже не по индексу

Код:
0040203E  |.  CMP AL, 0A
00402040  |.  JE SHORT crackme_.00402065
00402065  |>  MOV EAX, DWORD PTR DS:[<vm_eip>]
 
Ответить с цитированием

  #12  
Старый 09.06.2008, 05:57
Ra$cal
Постоянный
Регистрация: 16.08.2006
Сообщений: 640
Провел на форуме:
1354067

Репутация: 599


По умолчанию

При rivm, равном 0x0A мы получим содержимое vm_eip. Но не это главное. Код операции - 3. А это запись по декодированному rvim2 декодированного rvim1. Т.е. то, что обсуждалось выше - это принудетльное задание адреса следующей команды, эмуляция безусловного перехода с абсолютным адресом. Прокрутите окно выше и вы увидите, куда идет прыжок - 004014D9: 43 03 -> mov rvm3, [rvm0] ;// в rvm3 лежит 4 байта - букв из имени. Т.е. повторение чтения байтов, смещенных на один символ, ксор на константу, и прибавление к rvm4 результата ксора. И так, пока не уйдем за конец строки. Как мы проскочим цикл? Нада поставить бряк на обработчике опкода 0x14, и ждать, когда LE_F будет = 0. Встали тут

Код:
004023D4   .  ADD ESI, 0A
0040151F: 0F 74 -> cmp rvm4, rvm7 ;// сравнить регистр, в котором сумма хэшей с регистром, в котором введенный номер. rvm4 = 568AA687

00401529: 10 00 jnz vm_eip+20


Код:
004023DC   > CMP BYTE PTR SS:[EBP-14], 10
004023E0   . JNZ SHORT crackme_.004023F0
004023E2   . CMP BYTE PTR DS:[<Z_F>], 1
004023E9   . JE SHORT crackme_.004023EE
004023EB   . ADD ESI, 0A
004023EE   > JMP SHORT <crackme_.lbl_end_command>
Проверка флага нуля. У нас не ноль, поэтому проходит через одну команду. Запоминаем это место.

0040153D: 03 C4 00000000 00000000 -> mov rvm4, 0

00401547: 3F 00 -> ret

Код:
00402440   > \807D EC 3F            CMP BYTE PTR SS:[EBP-14], 3F
00402444   .  75 11                 JNZ SHORT crackme_.00402457
00402446   .  83C6 08               ADD ESI, 8
00402449   .  8935 28144000         MOV DWORD PTR DS:[<vm_eip>], ESI
0040244F   .  30C0                  XOR AL, AL
00402451   .  FEC0                  INC AL
00402453   .  C9                    LEAVE
00402454   .  C3                    RETN
При 3F vm_eip правится, и в al кладется 1, что вызовет прекращение работы цикла, выполняющего пикод. Значит это ret. Теперь отпускаем по F9, ставим хард бряк на доступ к байту 00401529 (10), но вводим в поле ключа 568AA687. Считываение проходим, идем до проверки флага. Флаг = 1, и перепрыгивания команды не происходит. Смотрим, какую пикод команду выполнит интерпритатор

00401533: 03 C8 00000000 87148712 -> mov rvm8, 87148712
Дальше опять выполняется обнуление rvm4 и выход. Теперь выходим и топаем медленно, высматривая сравнения

Код:
00402501  |.  CMP DWORD PTR DS:[401420], 87148712
0040250B  |.  JNZ SHORT crackme_.0040250E
0040250D  |.  INC EAX
Вот и все. Последний момент - таблица опкодов и алгоритм.

Код:
6 - xor r2, r1
3 - mov r2, r1
4 - add r2, r1
A - inc r1; mov r2, r1
F - cmp r2, r1
14 - jg $+0x0A
10 - jnz $+0x0A
3F - ret
Код:
mov r0, name
mov r1, E98FB720
mov r7, key

xor r4, r4

mov r5, name
add r5, name_len

lbl_NEXT_1:
mov r3, dword_ptr [r0]
xor r3, r1
add r4, r3
inc r0 ;// к следующей букве имени
cmp r0, r5
jle lbl_NEXT_1

cmp r4, r7
jnz lbl_EXIT
mov r8, 87148712
lbl_EXIT:
mov r4, 0
ret

Заключение: эта вм не представляет никаких сложностей, т.к. написана прямо и без мусора. Работает она скорее как эмулятор, так как у нее нет специфичных своих команд, мы с легкостью перевели каждую команду вм в команду процессора x86. Нету ни банальной смены логического базиса, ни разложения команд на мелкие, мы смогли на глаз декомпилировать пикод команды, так что это очень хорошая мишень для начинающих. Кстати структура команд очень напоминает мне микрокоманды процессора, для которого в этом семестре в качестве курсового проекта я кодировал микропрограммы в машкодах =)
ЗЫ: объемно получилось, ибо разжевывал подробно, чтобы могли понять общий алгоритм и ход действия при отломе вм.

Последний раз редактировалось Ra$cal; 14.06.2008 в 16:15..
 
Ответить с цитированием
Ответ



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Украдены 40 миллионов номеров кредитных и дебетных карт Visa и MasterCard DRON-ANARCHY Мировые новости 0 22.07.2005 19:49



Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
 


Быстрый переход




ANTICHAT.XYZ