Основы реверсинга: пишем кейген и боремся с антиотладкой

Основы реверсинга: пишем кейген и боремся с антиотладкой

Автор: Андрей Бирюков. 
Специально для Академии Кодебай

Основы реверсинга: пишем кейген и боремся с антиотладкой

В предыдущей статье мы рассмотрели основы ассемблера, поговорили об основных инструкциях, и начали анализировать крякмиксы. В этой статье мы продолжим заниматься практическим реверсингом, рассмотрим еще один crackme, напишем кейген к нему, а также рассмотрим некоторые приемы антиотладки.

Если в предыдущей статье мы осуществили патчинг, поменяли несколько байт в выполнимом файле для того, чтобы изменить логику работы программы, то теперь мы ни одного бита в выполнимых файлах менять не будем. Вместо этого мы попытаемся понять, по какому алгоритму осуществляется проверка и попробуем передать программе нужный ключ.

Алгоритм проверки ключа может работать по-разному. В простейшем случае можно просто осуществлять проверку переданного ключа на соответствие какому-то заранее зашитому в программе набору байт. В случае полного совпадения получаем Success, в противном случае - Fail.

Однако, наиболее распространены алгоритмы проверки ключа, генерирующие правильный ключ в процессе работы программы. Так, часто ключ генерируется на основе имени узла, на котором запускается программа, времени запуска, IP адреса и других параметров.

Есть и более творческие варианты проверок. Так, можно обращаться по сети к какому-либо узлу, например, по протоколу HTTP, и загружать с него нужный контент для проверки ключа. Также можно проверять наличие файла с ключом или процесса в памяти.

Кстати, все представленные методы могут использоваться для защиты реальных программ. От простейших проверок зашитого кода и до генерации сложных ключей, активации по сети или через файлы.

В этой статье мы разберем крякми с генерацией ключа, а также посмотрим простейший механизм защиты от отладки.

Виды механизмов защиты

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

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

Далее посмотрим вариант разбора крякми с генерацией пароля. Здесь в качестве рабочего инструмента мы будем использовать дизассемблер IDA. С его помощью мы посмотрим как выглядит код нашего крякмикса и попробуем понять принцип генерации пароля.

Разбор крякми

Для начала просто запустим наш крякмикс keygenme и посмотрим, что происходит. У нас запрашивают логин и пароль. После ввода случайных значений предсказуемо получаем сообщение о некорректном серийнике.

Разбор крякми

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

Нажимаем правую кнопку мыши, выбираем Поиск в -> Текущая область -> Ссылки на строки. В принципе для полного поиска по всему коду можно выбрать Все модули -> Ссылки на строки, но такой поиск займет больше времени.

Получим информацию о найденных текстовых строках.

Получим информацию о найденных текстовых строках.

Получим информацию о найденных текстовых строках.

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

Теперь нам достаточно будет перейти на нужный адрес по строке Serial is correct… Далее мы видим целый ряд проверок, по результатам каждой мы можем оказаться в блоке с Incorrect serial.

Видим целый ряд проверок, по результатам каждой мы можем оказаться в блоке с Incorrect serial

Если бы мы говорили о патчинге, как в прошлой статье, то нам было бы достаточно просто забить NOPами соответствующие команды. Но сейчас мы хотим разобраться в том, как проверяется серийный номер.

Для этого можно, конечно, поставить брейкпоинт сразу на адрес 0х401001, где начинается блок проверки, но есть некоторая вероятность, что мы до него не дойдем, так как ранее есть другие проверки. Поэтому лучше переместиться немного подальше, найти блок с вводом Username и Serial и поставить брейкпоинт там, с помощью клавиши F2. Далее запустим программу и введем осмысленные значения, например username и password. Я поставил брейк сразу после вызова функции, считывающей серийник.

можно, конечно, поставить брейкпоинт сразу на адрес 0х401001, где начинается блок проверки, но есть некоторая вероятность, что мы до него не дойдем, так как ранее есть другие проверки

Теперь мы знаем, по каким адресам в памяти хранятся Username и Serial. Далее можно пошагово перемещаться по программе, выполняя команды последовательно. Переходим на адрес 0х4010с0. Здесь довольно интересный блок кода. В нем программа берет поочередно блоки по четыре байта из серийного номера и на выходе возвращает в ECX количество этих блоков. Для значения password это будет 2, а после возращения из этой подпрограммы выполняется sub ECX,3 (вычитание из ECX). В нашем случае значение будет отрицательным, и следующая команда jne отправит нас на Incorrect Serial. Таким образом мы узнали, что длина серийника должна быть не меньше 12 символов. Проверим это. Введем password1234. Теперь ECX равно 3 и переход по jne не произойдет.

Следующий блок кода 0х401049 обрабатывает значение username. Обратим внимание на инструкции cld и repne scasb. Команды CLD и STD позволяют сбросить или установить флаг направления DF (Direction Flag). Команда CLD (Clear DF) сбрасывает флаг в значение 0, а команда STD (Set DF) устанавливает его в значение 1. Проще говоря, CLD говорит, что читать строку мы будем слева направо. Scasb это поиск в строке байтов, а при использовании префикса repne scas сканирует строку в поисках первого элемента, равного значению в регистре al (мы используем только младшую часть EAX).

Scasb это поиск в строке байтов, а при использовании префикса repne scas сканирует строку в поисках первого элемента, равного значению в регистре al (мы используем только младшую часть EAX)

В результате преобразований мы получим набор байт, находящихся по адресу 0х4020a0.

В результате преобразований мы получим набор байт, находящихся по адресу 0х4020a0.

Следующим блоком кода, на который будет выполнен переход является переход на адрес 0х401000. Здесь мы видим наш сгенерированный набор байтов по адресу 0х4020a0 и фрагмент исходного серийника, начинающийся с адреса 0х402053, то есть с четвертого байта.

Далее команда CMPSD сравнивает двойное слово из памяти по адресу DS:SI с двойным словом по адресу ES:DI. Аналогична по действию команде CMP. Очевидно, что в нашем случае значения не совпадут.

Давайте сразу посмотрим на код дальше. На третьей и восьмой позициях в серийнике должны быть знаки разделителя “-”.

На третьей и восьмой позициях в серийнике должны быть знаки разделителя “-”.

То есть формат серийника ХХ-ХХХХ-ХХХХ. По сути, мы уже можем на основе этих данных подготовить пару Username/Serial. В качестве имени пользователя оставляем слово username, а в качестве серийника указываем SN-E88E-54DF, где SN это любые два символа, их все равно не проверяют. Давайте проверим.

В качестве имени пользователя оставляем слово username, а в качестве серийника указываем SN-E88E-54DF, где SN это любые два символа, их все равно не проверяют.

Как видно, мы правильно нашли области кода, в которых осуществляется генерация и проверка ключа, теперь давайте рассмотрим основные принципы написания генератора ключей и заодно поработаем в дизассемблере IDA.

Пишем кейген

Откроем файл Crackme в IDA. Здесь мы видим несколько блоков кода, представленных в виде блоков со связями. Визуально такое отображение упрощает понимание принципов работы алгоритма.

Откроем файл Crackme в IDA

Как можно увидеть в левой части экрана указаны вызовы подпрограмм и, в частности, уже знакомые нам подпрограммы по адресам 0х4010F9 и 0х401049.

Как можно увидеть в левой части экрана указаны вызовы подпрограмм и, в частности, уже знакомые нам подпрограммы по адресам 0х4010F9 и 0х401049.

С точки зрения написания кейгена нас больше всего будет интересовать процедура, находящаяся по адресу 0х401049.

С точки зрения написания кейгена нас больше всего будет интересовать процедура, находящаяся по адресу 0х401049.

Если вы собираетесь писать свой кейген на языке высокого уровня, то вам необходимо будет реализовать все эти преобразования на соответствующем языке, но если вы будете использовать ассемблер, то можно просто скопировать фрагмент кода процедуры и поместить его в свой кейген. Для этого в IDA можно на нужной процедуре нажать правую кнопку мыши и выбрать Text view, а затем просто скопировать нужный фрагмент кода.

Ниже представлен фрагмент кода кейгена, который получает на вход username и serial как параметры командной строки и затем генерирует по ним нужный серийный номер.

START:

PUSH STD_OUTPUT_HANDLE

CALL GetStdHandle@4

MOV HANDL,EAX

CALL NUMPAR

CMP EAX,1

JE NO_PAR

MOV EDI,2

LEA EBX,USERNAME

CALL GETPAR

push    ebx

push    esi

push    edi

xor     eax, eax

lea     edi, USERNAME

mov     ecx, 0FFh

cld

repne scasb

not     cl

dec     ecx

xor     ebx, ebx

lea     esi, USERNAME

 

label4:

xor     eax, eax

lodsb

mul     cl

add     ebx, eax

inc     edi

dec     cl

jnz     short label4

 

xor     ebx, 13131313h

not     ebx

xor     ebx, 1234ABCDh

mov     eax, ebx

and     eax, 0F0F0F0Fh

and     ebx, 0F0F0F0F0h

shr     ebx, 4

 

LEA     esi,PASSWORD

mov     [ESI], eax

mov     [ESI+4], ebx

mov     ecx, 8

 

label7:

cmp     byte ptr [esi], 9

ja      short label5

 

or      byte ptr [esi], 30h

jmp     short label6

 

label5:

add     byte ptr [esi], 37h ; '7'

 

label6:

inc     esi

dec     ecx

jnz     label7

 

mov     ESI,offset PASSWORD

mov     EDI,offset PASSWORD1+3

MOV EAX,[ESI]

MOV [EDI],EAX

mov     ESI,offset PASSWORD+4

mov     EDI,offset PASSWORD1+8

MOV EAX,[ESI]

MOV [EDI],EAX

 

 

PUSH 0

PUSH OFFSET NUMW

PUSH 12

PUSH OFFSET PASSWORD1

PUSH HANDL

CALL WriteConsoleA@20

 

pop     edi

pop     esi

pop     ebx

 

NO_PAR:

PUSH 0

CALL ExitProcess@4

;retn

 

NUMPAR PROC

CALL GetCommandLineA@0

MOV ESI,EAX ;указатель на строку

XOR ECX,ECX ;счетчик

MOV EDX,1 ;признак

L1:

CMP BYTE PTR [ESI],0

JE L4

CMP BYTE PTR [ESI],32

JE L3

ADD ECX,EDX ;номер параметра

MOV EDX,0

JMP L2

L3:

OR EDX,1

L2:

INC ESI

JMP L1

L4:

MOV EAX,ECX

RET

NUMPAR ENDP

;получить параметр из командной строки

;EBX - указывает на буфер, куда будет помещен параметр

;в буфер помещается строка с нулем на конце

;EDI - номер параметра

GETPAR PROC

CALL GetCommandLineA@0

MOV ESI,EAX ;указатель на строку

XOR ECX,ECX ;счетчик

MOV EDX,1 ;признак

L1:

CMP BYTE PTR [ESI],0

JE L4

CMP BYTE PTR [ESI],32

JE L3

ADD ECX,EDX ;номер параметра

MOV EDX,0

JMP L2

L3:

OR EDX,1

L2:

CMP ECX,EDI

JNE L5

MOV AL,BYTE PTR [ESI]

MOV BYTE PTR [EBX],AL

INC EBX

L5:

INC ESI

JMP L1

L4:

MOV BYTE PTR [EBX],0

RET

GETPAR ENDP

_TEXT ENDS

END START

 

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

Про антиотладку

Приложения могут содержать механизмы защиты от работы под отладчиком. Самый простой вариант проверки наличия отладчика в системе - это вызов функции IsDebuggerPresent.

Так, если следующей фрагмент кода будет выполняться без отладчика, то будет выполнен переход на метку No_debug, а если под отладчиком (значение EAX не равно нулю), то выполнится ExitProcess.

start:

        call [IsDebuggerPresent]

        add esp, 4

        cmp eax, 0

        je No_debug

         push 0

         call [ExitProcess]

 

No_debug:

Вариант немного посложнее, который часто можно встретить в различных crackme. Мы можем обратиться к блоку окружения процесса (PEB), который заполняется загрузчиком операционной системы. Он содержит много полей: например, отсюда можно узнать информацию о текущем модуле, окружении и загруженных модулях. Получить структуру PEB можно, обратившись к ней напрямую по адресу fs:[30h]. Так в примере ниже мы в первых двух строках обращаемся к PEB, а в третьей извлекаем значение BegingDebugged.

start:

mov eax, [fs:18h]

mov eax, [eax+30h]

movzx eax, byte [eax+2]

or eax, eax

je No_debug

push 0

call [ExitProcess]

No_debug:

Альтернативный вариант написания:

mov eax, [fs:18h]; ????????? ?? TEB

mov eax, [eax+30h]; ????????? ?? PEB

ADD eax,68h

movzx eax, byte [eax]; ???? BegingDebugged ? PEB

cmp eax, 0

 

je No_debug

 

        push 0

        call [ExitProcess]

 

No_debug:

 

Это самые простые примеры борьбы с отладчиком. В более сложных случаях применяются вызовы различных функций операционной системы и работа с прерываниями.

 

Заключение

В этой статье на примере крякмикса мы рассмотрели генерацию серийного ключа и написание кейгена. Также мы посмотрели, как строится работа с дизассемблером и другими инструментами для реверсинга.

Конечно, в этой и предыдущей статье мы разобрали лишь малую часть реверсинга, показав лишь основные методы решения crackme. В следующей статье мы будем говорить о реверсивном инжиниринге приложений, написанных на .NET.

Ну а на нашем курсе по реверсивному инжинирингу ПО под Windows можно изучить практические аспекты реверсинга различных видов приложений, в том числе и вредоносного кода.

Ну, а на нашем курсе по реверсивному инжинирингу ПО под Windows можно изучить практические аспекты реверсинга различных видов приложений, в том числе и вредоносного кода.

 

Телефон: +7 499 444 17 50 | 8 800 444 17 50 бесплатно по России | E-mail: [email protected]
Все курсы Партнерам Возврат Контакты