sg
-
Автор темы
- #1
Доброго дня, прекрасный и замечательный форум YouGame.biz. Хотел бы предоставить Вам небольшую выдержку о том как работает защита аимвара и какие методы есть, чтобы её побороть.
Что касательно проверок через cpuid, то просто я шёл по пути самурая, так как паттерн на них составлять идея говна, то я просто взял поиск по инструкции в x64dbg и проверял, есть ли дальше в коде mov'ы eax, ebx, ecx, edx. В eax регистре передаются параметры для получения строки бренда процессора.
Пример кода:
Чеки на KUSER_SHARED_DATA. Тут уже всё не так однозначно, так как на поиск данной проверки ушёл месяц, а точнее я просто 3 дня поковырял и забил, а потом в последние 3 дня сабки всё нашёл и отломал. Собственно, стоить упомянуть, что все чеки не лежат открыто. Например вот это:
r9+r11-16E001 будет равно 0x7ffe0260, а это указатель на KUSER_SHARED_DATA.NtBuildNumber
И последнее, мануальные импорты, отчасти самая весёлая часть, потому что на все импорты ( рассмотрим их чуть ниже ), есть по несколько CRC-check функций, я не подсчитывал сколько их точно, но много и бороться с ними бесполезно, так как проверки выполнены также как и с KUSER_SHARED_DATA.NtBuildNumber. Вызов импорта выглядит так:
Кратко разберём логику, присутствует базовая "обфускация" адреса импорта через разные инструкции ror, rol, xor, not, sub, add. А также в некоторых случаях ( как здесь ) на стек пушится другой также обфусцированный return-адрес. В принципе ничего сложного, ищем по паттерну все jmp r10/r11/rax, потом ищем mov на найденный регистр и параллельно сохраняем все операции с ним, для того, чтобы при фиксе импортов пропатчить лишь первый mov r??, 0x????????????????. Сурса фикса импортов не будет, потому что сами можете написать, Zydis & unicorn в руки и погнали. А вот что касается CRC чеков ? Тут всё интереснее, как я и говорил, тут система такая же, как и с проверкой структуры KUSER_SHARED_DATA. Но при этом проверяемый адрес увеличивается на 4 байта, то есть проверяется прям весь код вызова импорта, так что патчить импорты на ранних этапах нелья и как я это обошёл расскажу дальше.
Далее, парсим импорты по тому алгоритму, который я описал, берём любой импорт, ставим хардварный бряк в дебаггере на доступ к памяти этого импорта и выпадаем на CRC чек. Важная деталь, некоторые CRC-чеки проверяются другими CRC-чеками и запатчить их для наших корыстных целей не прям выйдет, так что лучше поискать другой.
Нашли CRC-чек ? Почти финалочка. Выбираем место где будет ставить int3 бряк и сохраняем инструкции. По хорошему стоит для этого написать либу, которая будет копировать инструкцию, которую патчим, аллокать память, записывать её туда и после выполнения джампить на следующую, но мне впадлу и я просто ручками восстановил код. После установки патча, сохраняем то количество, сколько она выполнилась и чекая, когда данное количество отработало, то восстанавливаем все импорты. Но есть закономерный вопрос, а как до этого фиксить импорты ?
А вот тут вступает в силу VEH, чекая что ExceptionCode == STATUS_ACCESS_VIOLATION и проверка, что Rip равен одному из адресов импортов. Меняем Rip на валидный и всё у нас замечательно.
Остаётся только самое тяжкое, это правильно пропатчить cpuid. По каким-то причинам на них CRC никак не распространяется, что даёт нам возможность спокойной их патчить даже пока чит не полностью проинициализировался. Я все это искал ручками, так как это просто надёжнее, нужно учитывать, что после инструкции cpuid, обязательно мувается информация которую и запросили через eax. Ну вы об этом и так знаете из моих слов выше. Просто патчим их на int3 и обрабатываем eax в VEH хендлере.
Вот и всё, инструкция для кряка готова. Грустно конечно, что дошло до того, что я её выпускаю, но надеюсь это сможет кому-то дать новые идеи для секурити или методов кряков. Всю более детальную информацию вы можете найти на репозитории ниже:
Принцип защиты
Бинарник чита накрывается собственным протектором, логика которого построена на том, чтобы чит был максимально под одну сессию. То есть проверку исходя из инструкции cpuid, структуры KUSER_SHARED_DATA и мануальных импортов, которые "зашифрованы".Что касательно проверок через cpuid, то просто я шёл по пути самурая, так как паттерн на них составлять идея говна, то я просто взял поиск по инструкции в x64dbg и проверял, есть ли дальше в коде mov'ы eax, ebx, ecx, edx. В eax регистре передаются параметры для получения строки бренда процессора.
Пример кода:
C++:
00000023B6B81AEE | 66:0F43F2 | cmovae si,dx
00000023B6B81AF2 | 66:F7DA | neg dx
00000023B6B81AF5 | 0FA2 | cpuid ; вызов инструкции
00000023B6B81AF7 | 41:80D9 AF | sbb r9b,AF
00000023B6B81AFB | 44:0FBFD6 | movsx r10d,si
00000023B6B81AFF | 41:0F9FC2 | setg r10b
00000023B6B81B03 | 41:89442A 03 | mov dword ptr ds:[r10+rbp+3],eax ; помещаем результат выполнения инструкции в стек
00000023B6B81B08 | 4F:8D8409 A6048B25 | lea r8,qword ptr ds:[r9+r9+258B04A6]
00000023B6B81B10 | 41:0FB7C2 | movzx eax,r10w
00000023B6B81B14 | 42:895C95 FC | mov dword ptr ss:[rbp+r10*4-4],ebx ; и тут
00000023B6B81B19 | 41:C0E1 06 | shl r9b,6
00000023B6B81B1D | 8BDE | mov ebx,esi
00000023B6B81B1F | 42:894C55 FA | mov dword ptr ss:[rbp+r10*2-6],ecx ; и так
00000023B6B81B24 | 42:895415 F7 | mov dword ptr ss:[rbp+r10-9],edx ; и даже так
Код:
00000023B69C4F81 | 43:8B8C19 FF1FE9FF | mov ecx,dword ptr ds:[r9+r11-16E001]
И последнее, мануальные импорты, отчасти самая весёлая часть, потому что на все импорты ( рассмотрим их чуть ниже ), есть по несколько CRC-check функций, я не подсчитывал сколько их точно, но много и бороться с ними бесполезно, так как проверки выполнены также как и с KUSER_SHARED_DATA.NtBuildNumber. Вызов импорта выглядит так:
Код:
00000023B66F89B8 | 49:BA E038087F722BF97F | mov r10,7FF92B727F0838E0 |
00000023B66F89C2 | 48:C1C8 0E | ror rax,E |
00000023B66F89C6 | 4D:85D2 | test r10,r10 |
00000023B66F89C9 | 49:81C2 EE7D6E2A | add r10,2A6E7DEE |
00000023B66F89D0 | 49:F7D2 | not r10 |
00000023B66F89D3 | 48:39C0 | cmp rax,rax |
00000023B66F89D6 | 49:81EA EA0FD448 | sub r10,48D40FEA |
00000023B66F89DD | 48:05 D1D90401 | add rax,104D9D1 |
00000023B66F89E3 | 48:2D 0F9CD901 | sub rax,1D99C0F |
00000023B66F89E9 | 49:C1CA 02 | ror r10,2 |
00000023B66F89ED | 4D:85D2 | test r10,r10 |
00000023B66F89F0 | 48:85C0 | test rax,rax |
00000023B66F89F3 | 48:C1C0 06 | rol rax,6 |
00000023B66F89F7 | 49:81F2 F25A8428 | xor r10,28845AF2 |
00000023B66F89FE | 48:39C0 | cmp rax,rax |
00000023B66F8A01 | 49:81EA A414B543 | sub r10,43B514A4 |
00000023B66F8A08 | 49:F7D2 | not r10 |
00000023B66F8A0B | 48:F7D0 | not rax |
00000023B66F8A0E | 49:C1CA 0E | ror r10,E |
00000023B66F8A12 | 48:2D E0D3837C | sub rax,7C83D3E0 |
00000023B66F8A18 | 4D:39D2 | cmp r10,r10 |
00000023B66F8A1B | 48:85C0 | test rax,rax |
00000023B66F8A1E | 48:C1C0 16 | rol rax,16 |
00000023B66F8A22 | 50 | push rax |
00000023B66F8A23 | 41:FFE2 | jmp r10 |
Принцип кряка
Первое что нам нужно, так это задампить бинарник. Для этого я использую связку SSDT хуков в кернеле и EfiGuard для отключения патчгуарда и проверки подписей загружаемых драйверов. Поскольку бинарник записывается через кернел драйвер аимвара, то никакие вызовы NtWriteVirtualMemory не проходят, но они всегда создают поток на точке входа с флагом 6 используя NtCreateThreadEx. Соответственно, в хуке на создание потока проверяем флаг и просто возвращаем SUCCESS, таки образом спуфая старт чита и можем легко его задампить через что угодно.Далее, парсим импорты по тому алгоритму, который я описал, берём любой импорт, ставим хардварный бряк в дебаггере на доступ к памяти этого импорта и выпадаем на CRC чек. Важная деталь, некоторые CRC-чеки проверяются другими CRC-чеками и запатчить их для наших корыстных целей не прям выйдет, так что лучше поискать другой.
Нашли CRC-чек ? Почти финалочка. Выбираем место где будет ставить int3 бряк и сохраняем инструкции. По хорошему стоит для этого написать либу, которая будет копировать инструкцию, которую патчим, аллокать память, записывать её туда и после выполнения джампить на следующую, но мне впадлу и я просто ручками восстановил код. После установки патча, сохраняем то количество, сколько она выполнилась и чекая, когда данное количество отработало, то восстанавливаем все импорты. Но есть закономерный вопрос, а как до этого фиксить импорты ?
А вот тут вступает в силу VEH, чекая что ExceptionCode == STATUS_ACCESS_VIOLATION и проверка, что Rip равен одному из адресов импортов. Меняем Rip на валидный и всё у нас замечательно.
Остаётся только самое тяжкое, это правильно пропатчить cpuid. По каким-то причинам на них CRC никак не распространяется, что даёт нам возможность спокойной их патчить даже пока чит не полностью проинициализировался. Я все это искал ручками, так как это просто надёжнее, нужно учитывать, что после инструкции cpuid, обязательно мувается информация которую и запросили через eax. Ну вы об этом и так знаете из моих слов выше. Просто патчим их на int3 и обрабатываем eax в VEH хендлере.
Вот и всё, инструкция для кряка готова. Грустно конечно, что дошло до того, что я её выпускаю, но надеюсь это сможет кому-то дать новые идеи для секурити или методов кряков. Всю более детальную информацию вы можете найти на репозитории ниже:
Пожалуйста, авторизуйтесь для просмотра ссылки.