Не так давно мне попался на глаза занимательный Python-фреймворк под названием angr. Он предназначен для анализа всевозможных исполняемых файлов под различные платформы. И, несмотря на незнание мной Python'а и нелюбовь к паскаледобному синтаксису, я все же решил потратить некоторое время на его изучение, так как на одном CTF'е увидел занятный вариант решения задания по реверс-инжинирингу с его использованием.
Перейду сразу к сути: с помощью него можно, например, закейгенить произвольный CrackMe почти не занимаясь разбором алгоритма проверки ключа (конечно, речь не идет о ситуациях, когда для генерации ключа используются лучшие практики ассиметричной криптографии и прочие штуки).
Итак, для тестов я нашел в интернете небольшой CrackMe с названием crackme01_x64.exe и логотипом Лаборатории Касперского. Давайте проведем начальный анализ и посмотрим, что он из себя представляет. Для начала просто запустим его:
Что ж, ничего необычного, нужно подобрать серийный номер, также при нажатии мы можем наблюдать сообщение: Fail, Serial is invalid !!!. Теперь откроем файл в каком-нибудь дизассемблере, который поддерживает 64-битные приложения, я для этой цели воспользуюсь x64dbg.
Мы видим стандартный пролог и вызов функции DialogBoxParam, который создает диалог с окном, которое мы наблюдали при запуске программы, по адреу 140001110 расположена процедура, в которой реализована логика работы диалога, перейдем к ней.
Человек, который хотя бы немного знаком с WinAPI и конвенцией вызовов fastcall, сразу увидит, что тут нет ничего интересного, кроме вызова функции по адресу 140001000, которой в качестве аргументов передается строка, полученная предшествующим вызовом GetDlgItemText, и ее длина:
1 2 3 4 5 6 |
call qword ptr ds:[<&GetDlgItemTextA>] lea rcx,qword ptr ss:[rsp+20] ; Указатель на строку с серийным номером mov edx,eax ; Размер строки (количество символов, которые скопировал в буфер вызов GetDlgItemText) call crackme01_x64.140001000 test eax,eax je crackme01_x64.140001191 |
Далее идет проверка, что вернул вызов функции 140001000, и если это 1, то серийный номер корректный. Рассмотрим функцию проверки серийного номера:
Мы наблюдаем большой фрагмент кода, который не поместился у меня на экране и который мне чрезвычайно лень анализировать, хотя я все же отмечу несколько особенностей, которые нам понадобятся в дальнейшем:
1. Почти в самом начале мы видим строку:
1 |
cmp edx,13 |
Как вы помните, в edx была передана длина строки серийного номера. Это значит, что искомый серийный номер должен состоять из 19 символов (0x13 = 19).
2. Почему собственно 19 символов? Странный размер, но обратите внимание на код чуть ниже:
1 2 3 4 5 6 7 8 9 10 11 |
xor edx,edx lea rax,qword ptr ds:[r8+4] mov ecx,edx nop nop cmp byte ptr ds:[rax],2D jne crackme01_x64.14000100C add ecx,1 add rax,5 cmp ecx,3 jb crackme01_x64.140001020 |
Это может быть не слишком очевидно для человека, который не привык копаться в ассемблере, но тут проверяется каждый 5 символ серийного номера, и он должен быть равен 0x2D или символу "-" (без кавычек). Вообще, эту логику можно увидеть, просто введя произвольный серийный номер и немного потрассировав в отладчике.
3. Самый простой пункт. В нем я просто отмечу, что функция возвращает 1 (ее мы и хотим) только при возвращении (инструкция ret) по адресу 00000001400010FC, а адреса 0000000140001012 и 0000000140001108, по которым тоже расположена инструкция ret, нас не интересуют, так как им предшествует код xor eax,eax, а это означает, что функция вернет 0.
На этом анализ функции закончен, мы почти ничего не знаем о том, как проверяется серийный номер, знаем только его размер и наличие трех тире в нем. Воспользуемся фреймворком, чтобы найти серийный номер. Приведу сразу весь код с комментариями:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
import angr def main(): SERIAL_SIZE = 19 # Подгрузим наш crackme project = angr.Project('crackme01_x64.exe') # Зададим некоторое начальное состояние, с которого будет производиться эмуляция кода # В качестве начальной точки зададим адрес функции, проверяющий серийный номер state = project.factory.blank_state(addr = 0x140001000) # Зададим состояние регистров, которыми будет оперировать эмулятор # В RCX положим некоторый произвольный адрес, по которому хранится подбираемый серийный номер # В RDX размер серийного номера state.regs.rcx = serial_address = 0x100000 state.regs.rdx = SERIAL_SIZE # Для каждого символа серийного номера зададим диапазон допустимых значений # Для 4, 9 и 14: это тире # Для остальных: a-zA-Z0-9 for i in xrange(0, SERIAL_SIZE): if i != 4 and i != 9 and i != 14: cond_numeric_f = state.memory.load(serial_address + i, 1) >= ord('0') cond_numeric_t = state.memory.load(serial_address + i, 1) <= ord('9') cond_alpha_lc_f = state.memory.load(serial_address + i, 1) >= ord('a') cond_alpha_lc_t = state.memory.load(serial_address + i, 1) <= ord('z') cond_alpha_uc_f = state.memory.load(serial_address + i, 1) >= ord('A') cond_alpha_uc_t = state.memory.load(serial_address + i, 1) <= ord('Z') state.add_constraints( state.se.Or( state.se.And(cond_numeric_f, cond_numeric_t), state.se.And(cond_alpha_lc_f, cond_alpha_lc_t), state.se.And(cond_alpha_uc_f, cond_alpha_uc_t) ) ) else: state.add_constraints(state.memory.load(serial_address + i, 1) == ord('-')) # Создадим объект PathGroup для последующей эмуляции # http://angr.io/api-doc/angr.html#module-angr.path_group path_group = project.factory.path_group(state) # Запустим поиск возможных решений, указав адрес в коде, куда мы хотим попасть # А также адреса, которых следует избегать result = path_group.explore ( find = 0x1400010ee, avoid = [0x14000100c, 0x1400010fd] ) if result.found: # Вернем первое найденное решение solution = path_group.found[0].state return solution.se.any_str(solution.memory.load(serial_address, SERIAL_SIZE)) else: return 'Not found :(' if __name__ == "__main__": print main() |
Как вы можете видеть, в коде я использую немного другие адреса в качестве параметров find и avoid. Поясню: это адреса, куда ведут условные переходы, за которыми в дальнейшем следуют инструкции ret, адреса которых я упоминал выше.
Итак, запускаем скрипт:
1 |
python keygen.py |
И за какие-нибудь 5-10 секунд получаем:
1 |
0084-6990-0135-8004 |
Вуаля, мы получили серийный номер почти что без анализа алгоритма проверки. Давайте проверим этот номер вручную:
Номер подходит! Классно же?
CrackMe + скрипт: скачать
Интересно.
Но это просто брут-форс получается, как ничего не знали про алгоритм проверки, так и не знаем.
В следующий раз напишу про z3, который требует изучения алгоритма и решает систему закономерностей, которым подчиняется серийный номер
Не могли бы вы сделать небольшую статью для новичков по установке angr? Ибо у меня возникли множественные проблемы при его инсталляции и в винде (WIN 10), и в линуксе (UBUNTU 16.04).
Не наблюдаю никаких проблем на чистом образе Ubuntu 16.04.
apt install python-pip
pip install angr
и все отлично ставится.
В крайнем случае можно воспользоваться docker'ом.
Чтож, Благодарю. Буду пытаться еще)
Frida говорят что хороший Фреймворк для реверса, может напишешь про него и особенно анализ моб приложений
Как установить angr на Windows?
http://docs.angr.io/INSTALL.html
Ух ты, питон не люблю (потому что я тупой), но штука забавная. Правда область применения, наверное, только подобные задачки.
То есть можно сформулировать примерно такое правило: если крякми можно решить исправив один условный переход (или заставив возвращать "1" нужную процедуру), значит и фреймворк вероятно поможет.
А какой крякми так не решается? Во всех крякми цель - получить ключ или имя-ключ, суть обычно в разборе алгоритма, а если там ассиметричная криптография какая-нибудь, то это уже отдельный вопрос.
Если алгоритм вычисления размазан по всей программе, ключ проверяется в разных местах программы по частям, напичкан ловушками, в этом случае прилаживание фреймворка само по себе может стать вызовом.
получается сумма цифр в первой паре 12 дальше 24 потом 9 и снова 12
адаптировал под python3 и изменения в АПИ angr, незнаю правильно/неправильно, первым находит другой серийный номер, и он успешно проходит проверку: https://pastebin.com/7U5TsQSC