Ra$cal KeyGenMe 2
Написал как автор крякми. То есть на что я делал ставку. Ждём солюшена человека, взломавшего сей крякми...
Итак. Основная фича этого творения - антиотладка используется с интересной целью. Не просто выдаёт месаджбокс и выходит. А данные получаемые от проверок изменяют внутренний ключ, который участвует в проверке. Таким образом если программа отлаживается и хоть одна проверка обнаружила отладчик, то найденный ключ не подойдёт при запуске программы без отладчика. Теперь про реализацию...
Сначала была придумана константа - 0x7DA3EF10. Далее вспомнил какие проверки знаю. Всего решил сделать 5 штук. Соотв решил какие биты в константе должны быть изменены. 3 бита должны быть изменены, 2 должны остаться. 0x7923EF14. Далее для сложности обнаружения обращений к этому дворду (в олли очень просто найти всю работу с конкретным адресом в секции данных. то есть легко локализовать и изменения и использование этой константы) сделал так. Дворд был разбит на 2: ControlDW_L = 0x5ca3ef14 и ControlDW_H = 0xba617923. В сладших словах находятся части константы. А для собсно скрытия обращений сделал ещё две переменные. Итог - static DWORD DummyBefore = 0x547298f0, ControlDW_L = 0x5ca3ef14, ControlDW_H = 0xba617923, DummyAfter = 0xba45910f;
Компилятор сделает так что эти переменные в памяти находятся последовательно. Следовательно чтоб обратиться к нужной достаточно получить адрес подставной переменной и и прибавить/удалить 4. Или 8. Вобщем как это работает:
Код:
_asm{
lea eax, DummyBefore;
add eax, 8;
mov eax, [eax]; получили ControlDW_H
}
Дальше...
Для реализации идеи пришлось придумывать, как связать возвращаемое значение проверки (причём не просто cmp jnz, а именно то значение, которое возвращает апи(например) при наличии отладчика и при его отсутствии). Так для IsDebuggerPresent задумано, что при отсутствии отладчика он вернёт 0. То есть если вернёт 1 константа будет испорчена...
Код:
temp = IsDebuggerPresent();
_asm{
lea eax, DummyBefore;
add eax, 8;
mov ecx, temp;
xor [eax], ecx;
}
Ещё из применённых проверок.
Код:
004016D0 >/$ 55 PUSH EBP
004016D1 |. 8BEC MOV EBP,ESP
004016D3 |. 83EC 08 SUB ESP,8
004016D6 |. 8D45 FC LEA EAX,DWORD PTR SS:[EBP-4]
004016D9 |. 50 PUSH EAX ; /pThreadId
004016DA |. 6A 00 PUSH 0 ; |CreationFlags = 0
004016DC |. 6A 00 PUSH 0 ; |pThreadParm = NULL
004016DE |. 68 30164000 PUSH KeyGenMe.CheckMemoryCrc ; |ThreadFunction = KeyGenMe.CheckMemoryCrc
004016E3 |. 6A 00 PUSH 0 ; |StackSize = 0
004016E5 |. 6A 00 PUSH 0 ; |pSecurity = NULL
004016E7 |. FF15 1C104000 CALL NEAR DWORD PTR DS:[<&KERNEL32.CreateThread>] ; \CreateThread
004016ED |. C745 F8 00000000 MOV DWORD PTR SS:[EBP-8],0
004016F4 |. EB 09 JMP SHORT KeyGenMe.004016FF
004016F6 |> 8B4D F8 /MOV ECX,DWORD PTR SS:[EBP-8]
004016F9 |. 83C1 01 |ADD ECX,1
004016FC |. 894D F8 |MOV DWORD PTR SS:[EBP-8],ECX
004016FF |> 817D F8 88130000 CMP DWORD PTR SS:[EBP-8],1388
00401706 |. 7D 30 |JGE SHORT KeyGenMe.00401738
00401708 |. 817D F8 D0070000 |CMP DWORD PTR SS:[EBP-8],7D0
0040170F |. 7E 25 |JLE SHORT KeyGenMe.00401736
00401711 |. 817D F8 C4090000 |CMP DWORD PTR SS:[EBP-8],9C4
00401718 |. 7D 1C |JGE SHORT KeyGenMe.00401736
0040171A |. C705 18214000 01>|MOV DWORD PTR DS:[CanCheck],1
00401724 |. 6A 00 |PUSH 0 ; /Timeout = 0. ms
00401726 |. FF15 18104000 |CALL NEAR DWORD PTR DS:[<&KERNEL32.Sleep>] ; \Sleep
0040172C |. C705 18214000 00>|MOV DWORD PTR DS:[CanCheck],0
00401736 |>^ EB BE \JMP SHORT KeyGenMe.004016F6
00401738 |> 8BE5 MOV ESP,EBP
0040173A |. 5D POP EBP
0040173B \. C3 RETN
Вот пример. Цикл в 5000 итераций. Причём с сомнительным содержимым. Тут надежда что поставят бряк на 3 последние команды цикла. А суть простая - Sleep (0) приводит к прекращению обработки текущего потока (его квант процессорного времени обнуляется) и начале работы другого потока процесса (если такой есть). А он, как видим немного выше, как раз создаётся. Как видно из инфы в pdb, функцию я назвал CheckMemoryCrc.Так вот Sleep (0) приведёт к тому, что второй поток будет сработывать при вызове Sleep(0)...
Код:
00401630 >/. 55 PUSH EBP
00401631 |. 8BEC MOV EBP,ESP
00401633 |> 813D 14214000 C7>/CMP DWORD PTR DS:[ThreadCounter],0C7
0040163D |. 75 32 |JNZ SHORT KeyGenMe.00401671
0040163F |. 8B0D 28204000 |MOV ECX,DWORD PTR DS:[MemoryCrc]
00401645 |. BA 01000000 |MOV EDX,1
0040164A |. 22D1 |AND DL,CL
0040164C |. C1C9 10 |ROR ECX,10
0040164F |. 22D1 |AND DL,CL
00401651 |. C1C9 08 |ROR ECX,8
00401654 |. 22D1 |AND DL,CL
00401656 |. C1C2 06 |ROL EDX,6
00401659 |. 8D05 18204000 |LEA EAX,DWORD PTR DS:[DummyBefore]
0040165F |. 83C0 08 |ADD EAX,8
00401662 |. C1C2 04 |ROL EDX,4
00401665 |. 3110 |XOR DWORD PTR DS:[EAX],EDX
00401667 |. C705 28204000 00>|MOV DWORD PTR DS:[MemoryCrc],0
00401671 |> 813D 14214000 C8>|CMP DWORD PTR DS:[ThreadCounter],0C8
0040167B |. 7E 0B |JLE SHORT KeyGenMe.00401688
0040167D |. 833D 18214000 01 |CMP DWORD PTR DS:[CanCheck],1
00401684 |. 75 02 |JNZ SHORT KeyGenMe.00401688
00401686 |. EB 40 |JMP SHORT KeyGenMe.004016C8
00401688 |> A1 14214000 |MOV EAX,DWORD PTR DS:[ThreadCounter]
0040168D |. 83C0 01 |ADD EAX,1
00401690 |. A3 14214000 |MOV DWORD PTR DS:[ThreadCounter],EAX
00401695 |. C705 28204000 00>|MOV DWORD PTR DS:[MemoryCrc],0
0040169F |. 833D 14214000 0F |CMP DWORD PTR DS:[ThreadCounter],0F
004016A6 |. 75 05 |JNZ SHORT KeyGenMe.004016AD
004016A8 |. E8 03010000 |CALL KeyGenMe.HRChecker
004016AD |> B9 D0164000 |MOV ECX,KeyGenMe.MemoryCheckFaker
004016B2 |. 8B51 68 |MOV EDX,DWORD PTR DS:[ECX+68]
004016B5 |. 8915 28204000 |MOV DWORD PTR DS:[MemoryCrc],EDX
004016BB |. 6A 00 |PUSH 0 ; /Timeout = 0. ms
004016BD |. FF15 18104000 |CALL NEAR DWORD PTR DS:[<&KERNEL32.Sleep>] ; \Sleep
004016C3 |.^ E9 6BFFFFFF \JMP KeyGenMe.00401633
004016C8 |> 5D POP EBP
004016C9 \. C3 RETN
Здесь тоже цикл и Sleep (0). То есть тут просто синхронизированы потоки. Далее.
Установка бряка в олли осуществляется просто. В целевой процесс пишут байт 0xCC по нужному адресу. Поэтому я читаю 4 байта с конца функции (куда я ожидаю установки бряка).
Код:
004016B2 |. 8B51 68 |MOV EDX,DWORD PTR DS:[ECX+68]
004016B5 |. 8915 28204000 |MOV DWORD PTR DS:[MemoryCrc],EDX
Прикинув как выглядят байты 0x8B, 0x5D, 0xC3 и 0xCC я нашёл, что СС - нечётное число, остальные чётные. Соответственно первый бит равен единице, если бряк не установлен, и 0, если установлен. Эта проверка должна изменить константу. Если хоть один бряк установлен бит будет обнулён.
Код:
0040163F |. 8B0D 28204000 |MOV ECX,DWORD PTR DS:[MemoryCrc]
00401645 |. BA 01000000 |MOV EDX,1
0040164A |. 22D1 |AND DL,CL
0040164C |. C1C9 10 |ROR ECX,10
0040164F |. 22D1 |AND DL,CL
00401651 |. C1C9 08 |ROR ECX,8
00401654 |. 22D1 |AND DL,CL
00401656 |. C1C2 06 |ROL EDX,6
00401659 |. 8D05 18204000 |LEA EAX,DWORD PTR DS:[DummyBefore]
0040165F |. 83C0 08 |ADD EAX,8
00401662 |. C1C2 04 |ROL EDX,4
00401665 |. 3110 |XOR DWORD PTR DS:[EAX],EDX
Плюс ещё есть вызов 004016A8 |. E8 03010000 |CALL KeyGenMe.HRChecker
Эта проверка проверят, установлен ли хардбряк. Для этого вызывает исключение и проверяет переданнвый контекст потока в функцию обработчик исключения, в котором было исключение. Отладчик для полноты картины ставит в контекст ВСЕХ потов хард бряки, заданные пользователем в каком-то одном. Чтобы мы не пропустили никаких интересующих нас событий. Следовательно, даже если бряк поставлен в другом потоке, в нашем созданном потоке он тоже будет. Поэтому в контексте можно проверить их наличие.
Код:
00401748 |. 33DB XOR EBX,EBX
0040174A |. 0B59 04 OR EBX,DWORD PTR DS:[ECX+4]
0040174D |. 0B59 08 OR EBX,DWORD PTR DS:[ECX+8]
00401750 |. 0B59 0C OR EBX,DWORD PTR DS:[ECX+C]
00401753 |. 0B59 10 OR EBX,DWORD PTR DS:[ECX+10]
00401756 |. 0B59 18 OR EBX,DWORD PTR DS:[ECX+18]
00401759 |. 8D05 24204000 LEA EAX,DWORD PTR DS:[DummyAfter]
0040175F |. 83E8 04 SUB EAX,4
00401762 |. 3118 XOR DWORD PTR DS:[EAX],EBX
Эта проверка тоже не должна изменить никакой бит, ибо используется значение регитсров dr, и если хоть 1 задан, то константа опять не будет валидной.
Собсно есть ещё проверки, но логика их такая же. Перечислю только их суть - проверка отладочного порта и фича с обработкой CloseHandle с неправильным параметром.
Теперь что с алгоритмом. Как я и говорил он простой. Напишу тока код моего генератора
Код:
DWORD Control = 0x7DA3EF10;
void Generate(char* UserName, char* Password)
{
DWORD Hash = 0;
long Summ = 0;
DWORD Pass = 0;
for (int i = strlen (UserName); i >= 0; i--){
Hash ^= UserName[i];
_asm{
rol Hash, 8;
}
Summ += UserName[i];
}
Pass = (Summ + Hash) ^ Control;
_itoa(Pass, Password, 16);
int len = strlen (Password);
if (len < 8){
int d;
d = 8 - len;
memcpy (Password + d, Password, len);
memset (Password, '0', d);
Password [8] = '\0';
}
}