Проверка ключа

advertisement
Оглавление
Проверка ключа .................................................................................................................................................... 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
Download