Оглавление Проверка ключа .................................................................................................................................................... 2 Процедура обработки нажатия кнопки «Ввод» ............................................................................................ 2 Процедура проверки ключа ............................................................................................................................ 3 Описание алгоритма проверки ключа .......................................................................................................... 12 Модификация программы ................................................................................................................................. 13 1 Работа была выполнена с использованием отладчика OllyDbg 2.00.01 и дизассемблера IDA Pro 5.5.0.925t Проверка ключа Проверка регистрационного номера начинается, когда пользователь нажал кнопку «Ввод». В этом случае процедура, соответствующая этой кнопке, вызывает процедуру проверки ключа. Это было выяснено помощи отладчика, но адрес точки остановки для использования в отладчике, а также структура процедуры были получены при помощи дизассемблера. Процедура обработки нажатия кнопки «Ввод» 00401414 00401414 _TForm1_DoButtonClick proc near 00401414 var_8 = dword ptr -8 00401414 var_4 = dword ptr -4 00401414 00401415 00401417 push mov add ebp ebp, esp esp, 0FFFFFFF8h 0040141A 0040141D 00401420 00401425 00401426 mov mov call dec jnz [ebp+var_8], edx [ebp+var_4], eax sub_40145A ; вызов функции проверки ключа eax ; пост-обработка возвращаемого значения short loc_401440 ; прыжок, если ключ правильный 00401428 0040142A 0040142F push mov mov 00401434 00401439 0040143E mov call jmp 0 ecx, offset unk_4324E1 ; “Ошибка” edx, offset unk_4324B8 ; “Регистрационный номер ; указан неправильно” eax, dword_4369BC ; sub_425834 ; вызов процедуры вывода short loc_401456 ; сообщения через MessageBoxA ; = -8h 00401440 00401440 loc_401440: 00401440 push 0 00401442 mov ecx, offset unk_43250A ; “Сообщение” 00401447 mov edx, offset unk_4324E8 ; “Поздравляем! ; Вы зарегистрированы!” 0040144C mov eax, dword_4369BC 00401451 call sub_425834 00401456 00401456 loc_401456: 00401456 pop ecx 00401457 pop ecx 00401458 pop ebp 00401459 retn 00401459 _TForm1_DoButtonClick endp ; sp-analysis failed 00401459 2 Процедура проверки ключа Процедура содержит девять раз повторяющийся код, который выполняется, когда введённый ключ не прошёл очередную проверку из девяти возможных. При успешной проверке код «перепрыгивают». Этот код расположен внизу на графе. Для экономии бумаги и чернил он напечатан один раз, а в дальнейшем на него даются ссылки. Представленный здесь код был получен при помощи дизассемблера. Функциональное значение отдельных участков кода определено при помощи отладчика для нескольких вариантов входных данных. Указаны только те свойства, которые проявляются для всех типов входных данных. Все утверждения, в которых автор не вполне уверен, снабжены знаком вопроса (?). Дублированный код .text:0040145A sub_40145A .text:0040145A .text:0040145A .text:0040145A .text:0040145A .text:0040145A .text:0040145A .text:0040145A .text:0040145A .text:0040145A .text:0040145A .text:0040145A .text:0040145A .text:0040145A .text:0040145A .text:0040145A .text:0040145A .text:0040145A var_7C var_58 var_54 var_50 var_4C var_48 var_44 var_40 var_3C var_2C var_20 var_18 var_14 var_10 var_C var_8 var_4 .text:0040145A .text:0040145B .text:0040145D push mov add = = = = = = = = = = = = = = = = = proc near dword ptr -7Ch dword ptr -58h dword ptr -54h dword ptr -50h dword ptr -4Ch dword ptr -48h dword ptr -44h dword ptr -40h dword ptr -3Ch word ptr -2Ch dword ptr -20h byte ptr -18h byte ptr -14h byte ptr -10h byte ptr -0Ch byte ptr -8 byte ptr -4 ebp ebp, esp esp, 0FFFFFF84h ; = -0x7C = -124 Установка обработчика исключений (?). [EBP-0x20] — счётчик для обработчика исключений (?). Он также считает количество открытых указателей на строки. .text:00401460 .text:00401465 mov call eax, offset stru_432374 @__InitExceptBlockLDTC Открытие указателей в локальных переменных. Видимо происходит и одновременное выделение памяти (?). [EBP-0x20] — счётчик открытых указателей. При открытии происходит обнуление указателей. Их заполнение, по-видимому, происходит в ручном режиме. Используется функция sub_401940, которая записывает 32-х битный ноль в ячейку, на которую указывает EAX. .text:0040146A mov [ebp+var_2C], 8 3 .text:00401470 .text:00401473 lea call eax, [ebp+var_4] sub_401940 ; создание указателя в [EBP-4] .text:00401478 .text:0040147B .text:00401481 .text:00401487 .text:0040148A inc mov mov lea call [ebp+var_20] [ebp+var_2C], 14h [ebp+var_2C], 20h eax, [ebp+var_8] sub_401940 ; создание указателя в [EBP-8] .text:0040148F .text:00401492 .text:00401498 .text:0040149E .text:004014A1 inc mov mov lea call [ebp+var_20] [ebp+var_2C], 14h [ebp+var_2C], 2Ch eax, [ebp+var_C] sub_401940 ; создание указателя в [EBP-0x0C] .text:004014A6 .text:004014A9 .text:004014AF .text:004014B5 .text:004014B8 inc mov mov lea call [ebp+var_20] [ebp+var_2C], 14h [ebp+var_2C], 38h eax, [ebp+var_10] sub_401940 ; создание указателя в [EBP-0x10] .text:004014BD .text:004014BF .text:004014C2 .text:004014C8 mov inc mov mov edx, eax ; EAX не был изменён после вызова функции [ebp+var_20] ; счётчик открытых указателей ecx, dword_43691C ; указатель на указатель на код (?) eax, [ecx+1CCh] ; Функция, заполняющая структуру строки (?). Она записывает в локальную переменную [EBP-10h] указатель на ключевую строку и возвращает длину строки. .text:004014CE call sub_40A798 Теперь [EBP-10h] содержит указатель на ключевую строку. Копирование указателя (на строку) из [EBP-10h] в [EBP-4]. .text:004014D3 .text:004014D6 .text:004014D9 lea lea call edx, [ebp+var_10] ; указатель на указатель на строку ключа eax, [ebp+var_4] ; указатель на пустую ячейку sub_40F523 Закрытие указателя на строку в локальной переменной [EBP-10h]. Функция sub_40F4F4 обнуляет локальную переменную и уменьшает счётчик ссылок на строку на один. .text:004014DE .text:004014E1 .text:004014E4 .text:004014E9 dec lea mov call [ebp+var_20] eax, [ebp+var_10] ; edx, 2 ; параметр функции, определяющий её поведение sub_40F4F4 Вызов функции, возвращающей длину строки ключа из структуры. По сути, функция выполняет всего лишь две команды: mov eax, [eax] и mov eax, [eax-4]. .text:004014EE .text:004014F1 lea call eax, [ebp+var_4] sub_40F74E 4 Сравнение длины строки ключа с 0x0С = 12 — первый этап проверки ключа. .text:004014F6 .text:004014F9 cmp jz eax, 0Ch short loc_401541 ; ; прыжок, если проверка успешна Тот самый девять раз повторяющийся код: .text:004014FB .text:00401500 mov push eax, 1 eax ; возвращаемое значение — 1 ; Закрытие указателей. Для этого используется функция sub_40F4F4. .text:00401501 .text:00401504 .text:00401507 .text:0040150C dec lea mov call [ebp+var_20] eax, [ebp+var_C] edx, 2 sub_40F4F4 ; счётчик открытых указателей ; указатель на закрываемый указатель ; параметр поведения функции ; .text:00401511 .text:00401514 .text:00401517 .text:0040151C dec lea mov call [ebp+var_20] eax, [ebp+var_8] edx, 2 sub_40F4F4 .text:00401521 .text:00401524 .text:00401527 .text:0040152C dec lea mov call [ebp+var_20] eax, [ebp+var_4] edx, 2 sub_40F4F4 .text:00401531 .text:00401532 .text:00401535 .text:0040153C pop mov mov jmp eax ; восстановление возвращаемого значения EAX = 1 edx, [ebp+var_3C] ; large fs:0, edx ; loc_401921 ; прыжок на выход из процедуры Проверка длины пройдена успешно. Длина строки, введённой пользователем, равна 0x0C = 12. [EBP-14h]. Создание указателя в локальной переменной подстроки первых четырёх символов. Этот указатель потребуется для .text:00401541 loc_401541 .text:00401541 mov [ebp+var_2C], 44h .text:00401547 lea eax, [ebp+var_14] .text:0040154A call sub_401940 .text:0040154F push eax ; эта команда относится к функции выделения подстроки .text:00401550 inc [ebp+var_20] Копирование подстроки из строки ключа в отдельную строку. .text:00401553 .text:00401556 .text:0040155B .text:00401560 lea mov mov call eax, [ebp+var_4] ecx, 4 edx, 1 sub_40FF6F 5 ; ; ; ; указатель на строку номер последнего символа в строке номер первого символа в строке функция, копирующая подстроку Создана новая строка и в неё скопировано первых четыре символа строки ключа . Указатель на подстроку находится в локальной переменной [EBP-14h]. Копирование указателя из переменной [EBP-14h] в переменную [EBP-8]. .text:00401565 .text:00401568 .text:0040156B lea lea call edx, [ebp+var_14] eax, [ebp+var_8] sub_40F523 Закрытие указателя [EBP-14h]: .text:00401570 .text:00401573 .text:00401576 .text:0040157B dec lea mov call [ebp+var_20] eax, [ebp+var_14] edx, 2 sub_40F4F4 Открытие указателя в переменной [EBP-18h]: .text:00401580 .text:00401586 .text:00401589 mov lea call [ebp+var_2C], 50h eax, [ebp+var_18] sub_401940 Создание подстроки из последних восьми символов ключевой строки: .text:0040158E .text:0040158F .text:00401592 .text:00401595 .text:0040159A .text:0040159F push inc lea mov mov call eax ; адрес подстроки [ebp+var_20] eax, [ebp+var_4] ; адрес строки ecx, 8 ; восемь символов edx, 5 ; начиная с пятого sub_40FF6F Копирование указателя на подстроку из [EBP-18h] в [EBP-0Ch]: .text:004015A4 .text:004015A7 .text:004015AA lea lea call edx, [ebp+var_18] eax, [ebp+var_C] sub_40F523 Закрытие указателя [EBP-18h]: .text:004015AF .text:004015B2 .text:004015B5 .text:004015BA dec lea mov call [ebp+var_20] eax, [ebp+var_18] edx, 2 sub_40F4F4 Подготовка завершена. Созданы две подстроки: из первых четырёх и последних восьми символов ключевой строки. Указатели находятся в переменных [EBP-8] и [EBP-0Ch] соответственно. 6 Второй этап проверки строки ключа — проверка принадлежности символов к цифрам. .text:004015BF mov [ebp+var_40], 1 ; сброс счётчика Цикл: по символам из первой четвёрки. Счётчик — [EBP+var_40]. .text:004015C6 loc_4015C6: .text:004015C6 lea eax, [ebp+var_8] .text:004015C9 mov edx, [ebp+var_40] .text:004015CC call sub_401970 Разыменовывание EAX (указателя на первую часть строки) как байта, то есть считывание текущего символа в EAX со знаковым расширением. .text:004015D1 movsx ecx, byte ptr [eax] Вычитание 0x30 из текущего символа для получения цифры .text:004015D4 add ecx, 0FFFFFFD0h ; = -0x30 Запись текущего преобразованного символа в локальный буфер. .text:004015D7 .text:004015DA mov mov eax, [ebp+var_40] [ebp+eax*4+var_58], ecx Считывание из локального буфера текущего преобразованного символа и сравнение его с нулём: .text:004015DE .text:004015E1 .text:004015E6 mov cmp jge edx, [ebp+var_40] ; счётчик цикла [ebp+edx*4+var_58], 0 ; считывание и сравнение с нулём short loc_40162E ; прыжок если больше нуля < Дублированный код> Аналогично сравнение с девятью: .text:0040162E loc_40162E: .text:0040162E mov ecx, [ebp+var_40] ; счётчик цикла .text:00401631 cmp [ebp+ecx*4+var_58], 9 ; считывание и сравнение с девятью .text:00401636 jle short loc_40167E ; прыжок, если меньше девяти < Дублированный код> Инкремент счётчика и его сравнение с границей — с пятью: .text:0040167E loc_40167E: .text:0040167E inc [ebp+var_40] .text:00401681 cmp [ebp+var_40], 5 .text:00401685 jl loc_4015C6 ; увеличение на единицу ; сравнение с пятью ; прыжок на новую итерацию Конец цикла .text:0040168B mov [ebp+var_40], 1 7 ; сброс счётчика Цикл: по символам из оставшейся восьмёрки. Счётчик — [EBP+var_40] Происходит аналогичное посимвольное считывание из ключевой строки, вычитание 0x30 из каждого символа и сравнение с нулём и девяткой для выяснения, цифра ли это. .text:00401692 loc_401692: .text:00401692 lea eax, [ebp+var_C] .text:00401695 mov edx, [ebp+var_40] .text:00401698 call sub_401970 .text:0040169D .text:004016A0 movsx add ecx, byte ptr [eax] ; считывание текущего символа ecx, 0FFFFFFD0h ; вычитание 0x30 Запись в локальный буфер. Этот буфер потребуется при подсчёте суммы цифр строки в дальнейшем, если, конечно, строка пройдёт эту проверку. .text:004016A3 .text:004016A6 mov mov eax, [ebp+var_40] [ebp+eax*4+var_7C], ecx .text:004016AA .text:004016AD .text:004016B2 mov cmp jge edx, [ebp+var_40] [ebp+edx*4+var_7C], 0 short loc_4016FA ; ; считывание и сравнение с нулём ; прыжок в случае успеха <Дублированный код> .text:004016FA loc_4016FA: .text:004016FA mov ecx, [ebp+var_40] ; .text:004016FD cmp [ebp+ecx*4+var_7C], 9 ; считывание и сравнение с девяткой .text:00401702 jle short loc_40174A ; <Дублированный код> Инкремент счётчика и его сравнение с границей — с девятью: .text:0040174A loc_40174A: .text:0040174A inc [ebp+var_40] .text:0040174D cmp [ebp+var_40], 9 .text:00401751 jl loc_401692 Конец цикла Далее вычисляется сумма всех цифр второй части регистрационного номера, состоящей из восьми цифр (на этом этапе уже точно известно, что строка состоит из цифр). Эта сумма понадобится для дальнейших проверок регистрационного номера. .text:00401757 .text:00401759 .text:0040175C xor mov mov ecx, ecx [ebp+var_44], ecx [ebp+var_40], 1 Цикл: по восьмёрке. Счётчик — [EBP+var_40] Происходит суммирование всех цифр. 8 ; ; [EBP+var_44] будет хранить сумму ; Сброс счётчика .text:00401763 loc_401763: .text:00401763 mov eax, [ebp+var_40] .text:00401766 mov edx, [ebp+eax*4+var_7C] ; текущая цифра .text:0040176A add [ebp+var_44], edx ; суммирование .text:0040176D inc [ebp+var_40] .text:00401770 cmp [ebp+var_40], 9 .text:00401774 jl short loc_401763 Конец цикла К сумме прибавляется константа 0x7D = 125. Теперь значение лежит в границах 125 — 197. .text:00401776 add [ebp+var_44], 7Dh Далее начинается собственно математическая проверка введённого номера посредством проверки полученной суммы последних восьми его цифр. Она состоит из четырёх делений с остатком и сравнении остатков с заданными первой четвёркой цифр ключа значениями. Деление суммы с остатком на 0x0A = 10 .text:0040177A .text:0040177D .text:00401782 .text:00401783 .text:00401785 mov mov cdq idiv cmp eax, [ebp+var_44] ecx, 0Ah .text:00401788 jz short loc_4017D0 ecx edx, [ebp+var_54] ; ; ; ; ; ; ; ECX — делитель EDX:EAX — делимое Собственно деление EDX — остаток, он сравнивается с первой цифрой строки ключа прыжок в случае успеха <Дублированный код> Деление с остатком на 0x07. Аналогично предыдущему. .text:004017D0 loc_4017D0: .text:004017D0 mov eax, [ebp+var_44] .text:004017D3 mov ecx, 7 .text:004017D8 cdq .text:004017D9 idiv ecx .text:004017DB cmp edx, [ebp+var_50] .text:004017DE jz short loc_401826 <Дублированный код> Деление с остатком на 0x17 = 23 и затем остатка на 0x0A = 10 .text:00401826 loc_401826: .text:00401826 mov eax, [ebp+var_44] .text:00401829 mov ecx, 17h ; первый делитель .text:0040182E cdq ; EDX:EAX — первое делимое — сумма .text:0040182F idiv ecx ; деление суммы, остаток в EDX .text:00401831 mov eax, edx ; запись остатка на место делимого .text:00401833 mov ecx, 0Ah ; второй делитель .text:00401838 cdq ; расширение первого остатка в EDX:EAX 9 .text:00401839 .text:0040183B .text:0040183E idiv cmp jz ecx ; деление первого остатка, второй остаток в EDX edx, [ebp+var_4C] ; сравнение с четвёртой цифрой ключа short loc_401886 ; прыжок в случае успеха <Дублированный код> Деление с остатком на 0x13 = 19 и затем остатка на 0x0A = 10. Аналогично предыдущему. .text:00401886 loc_401886: .text:00401886 mov eax, [ebp+var_44] .text:00401889 mov ecx, 13h .text:0040188E cdq .text:0040188F idiv ecx .text:00401891 mov eax, edx .text:00401893 mov ecx, 0Ah .text:00401898 cdq .text:00401899 idiv ecx .text:0040189B cmp edx, [ebp+var_48] .text:0040189E jz short loc_4018E3 <Дублированный код> Все проверки прошли успешно. Ниже аналог дублированного кода для «правильного» случая. .text:004018E3 loc_4018E3: .text:004018E3 xor eax, eax ; обнуление EAX, он будет возвращён Закрытие указателей. .text:004018E5 push eax .text:004018E6 .text:004018E9 .text:004018EC .text:004018F1 dec lea mov call [ebp+var_20] eax, [ebp+var_C] edx, 2 sub_40F4F4 ; указатель на вторую подстроку .text:004018F6 .text:004018F9 .text:004018FC .text:00401901 dec lea mov call [ebp+var_20] eax, [ebp+var_8] edx, 2 sub_40F4F4 ; указатель на первую подстроку .text:00401906 .text:00401909 .text:0040190C .text:00401911 dec lea mov call [ebp+var_20] eax, [ebp+var_4] edx, 2 sub_40F4F4 ; указатель на строку .text:00401916 pop eax ; восстановление возвращаемого значения EAX = 0 .text:00401917 .text:0040191A mov mov edx, [ebp+var_3C] large fs:0, edx 10 Выход из процедуры. Возвращаемое значение — ноль в случае успешной проверки и один в случае неуспешной. .text:00401921 loc_401921: .text:00401921 .text:00401921 mov esp, ebp .text:00401923 pop ebp .text:00401924 retn .text:00401924 sub_40145A .text:00401924 endp 11 Описание алгоритма проверки ключа 1. Проверяется длина регистрационного номера. Для прохождения проверки длина должна быть равна двенадцати (12 = 0x0C). 2. В двух циклах проверяется, что первые четыре байта, а также оставшиеся восемь представляют собой цифры в кодировке ASCII, то есть лежат в диапазоне 0x30 — 0x39. Для прохождения проверки все символы ключа должны быть цифрами. 3. Вычисляется сумма последних восьми цифр. К сумме прибавляется константа 0x7D. 4. Полученное на шаге 3 число делится с остатком на 10. Остаток сравнивается с первой цифрой ключа. Для прохождения проверки должно выполняться равенство. 5. Полученное на шаге 3 число делится с остатком на 7. Остаток сравнивается со второй цифрой ключа. Для прохождения проверки должно выполняться равенство. 6. Полученное на шаге 3 число делится с остатком на 23. Остаток делится с остатком на 10. Второй остаток сравнивается с третьей цифрой ключа. Для прохождения проверки должно выполняться равенство. 7. Полученное на шаге 3 число делится с остатком на 19. Остаток делится с остатком на 10. Второй остаток сравнивается с четвёртой цифрой ключа. Для прохождения проверки должно выполняться равенство. 8. Если предыдущие проверки прошли успешно, регистрационный номер считается корректным. 12 Модификация программы Простейший способ сделать так, чтобы программа принимала все ключи, — изменить условный прыжок на безусловный после вызова функции проверки ключа. Процедура обработки нажатия кнопки «Ввод»: .text:00401414 _TForm1_DoButtonClick proc near .text:00401414 var_8 .text:00401414 var_4 = dword ptr -8 = dword ptr -4 .text:00401414 .text:00401415 .text:00401417 push mov add ebp ebp, esp esp, 0FFFFFFF8h .text:0040141A .text:0040141D .text:00401420 mov mov call .text:00401425 .text:00401426 dec jnz [ebp+var_8], edx [ebp+var_4], eax sub_40145A ; вызов функции проверки ключа ; Она возвращает: ; EAX = 1, если ключ неправильный ; EAX = 0, если ключ правильный eax short loc_401440 ; прыжок, если ключ правильный .text:00401428 .text:0040142A .text:0040142F .text:00401434 .text:00401439 .text:0040143E push mov mov mov call jmp 0 ecx, offset unk_4324E1 edx, offset unk_4324B8 eax, dword_4369BC sub_425834 short loc_401456 ; .text:00401440 .text:00401440 loc_401440: .text:00401440 push 0 .text:00401442 mov ecx, offset unk_43250A .text:00401447 mov edx, offset unk_4324E8 .text:0040144C mov eax, dword_4369BC .text:00401451 call sub_425834 .text:00401456 .text:00401456 loc_401456: .text:00401456 pop ecx .text:00401457 pop ecx .text:00401458 pop ebp .text:00401459 retn .text:00401459 Модификации подвергнуться должна команда jnz short loc_401440 по адресу 00401426, кодированная двумя байтами 75 18. Новая команда — jmp short loc_401440 с кодом EB 18. Тогда функция успеха будет запускаться в любом случае, независимо от результатов проверки. 13