скрыть

скрыть

  Форум  

Delphi FAQ - Часто задаваемые вопросы

| Базы данных | Графика и Игры | Интернет и Сети | Компоненты и Классы | Мультимедиа |
| ОС и Железо | Программа и Интерфейс | Рабочий стол | Синтаксис | Технологии | Файловая система |



Google  
 

Исследование программы Audio Mp3 Maker v 1.10-1.13 by Wersion


Автор: Wersion

Программист коллегам:
- Сегодня на работе опять весь день @нанизмом занимался.
- Что, винды переставлял?
- Да нет... просто др#чил...

Итак, как это было. Когда-то, очень давно, я скачал эту довольно неплохую вещь для того, чтобы наделать Mp3-файлов с одного Audio-диска. У меня тогда не было ни времени, ни знаний, поэтому я решил воспользоваться Crack-сайтами. Нашедши около 7 серийных номеров к версии 1.10, я убедился, что ни один из них не работает. Позже я кое-что делал с этой программой, поменял кучу байтов,она даже стала похожей на зарегистрированную, но в конце концов выкинула мне сообщение о истечении *0-дневного срока.

И я закинул сию вещь на год. После, набравшись ума, вновь принялся за исследование.

Вот оно:

Что используем:

  • Softice 4.x & Icedump 6.x
  • Win32 Disassembler v X.X.
  • Delphi 4-6.

Начнём. Запускаем программу. Жмём Register, вводим имя и любой код. Видим сообщение "Invalid...", запоминаем. Запускаем W32Dasm. Ищем строку в String Data References. Я нашёл следующий код:


:0040D844 53 push ebx (1)
:0040D845 51 push ecx (2)
:0040D846 E8AE9E0000 call 004176F9   (3)
:0040D84B 83C408 add esp, 00000008
:0040D84E 85C0 test eax, eax (4)
:0040D850 6A00 push 00000000

* Possible StringData Ref from Data Obj ->"Message"
|
:0040D852 68BC7B4400 push 00447BBC
:0040D857 7518 jne 0040D871  (5)

* Possible StringData Ref from Data Obj ->"Thank you, please restart programs"
|
:0040D859 68E47D4400 push 00447DE4
:0040D85E 8BCE mov ecx, esi
:0040D860 E889CE0100 call 0042A6EE
:0040D865 8B16 mov edx, dword ptr [esi]
:0040D867 8BCE mov ecx, esi
:0040D869 FF92C4000000 call dword ptr [edx+000000C4]
:0040D86F EB37 jmp 0040D8A8

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040D857(C)
|

* Possible StringData Ref from Data Obj ->"Invalid reg code"
|
:0040D871 68D07D4400 push 00447DD0  (6)
:0040D876 8BCE mov ecx, esi
:0040D878 E871CE0100 call 0042A6EE
:0040D87D EB29 jmp 0040D8A8 

Итак, что же мы видим? В (1) и (2) функции передаются 2 параметра, в (3) она вызывается. В (4) мы проверяем значение EAX на равенство 0, потом в (5) прыгаем на (6) если это не так. Соответственно, начиная с (6) идёт подготовка и выдача сообщения о неверности РИ. Что мы делаем? Ставим брейкпоинт на (1), начинаем регистрироваться. Попадая в в Softice, делаем:


d ebx
d ecx

В одном из них адрес строки с нашим кодом, в другом - с реальным. Берём Icedump и копируем реальный код на диск. Вводим их там где надо и получаем зарегистрированный Audio Mp3 Maker®.

0Днак0!

Всё это только начало. Меня всё время мучали следующие вопросы: при такой защите Coxsoft (теперь они расцвели и стали MjSoft) загнулись бы на корню. Кроме того, мой код работал не только для версии 1.10, но и для 1.13. Следовательно, алгоритм его генерации не меняли никогда. Тогда трудно объяснить существование 7 неверных серийных номеров(см. начало).

Это всё значит, что код зависит от конфигурации компьютера или чего-нибудь ещё. На сем факте, не заметив оного, proudly обломались 4 команды и 3 крэкера(и все ост., кот. я не нашёл). MjSoft специально сделали такую ламерскую проверку(ост. на уровне), чтобы к ним скорее нашли с/н и оставили бы их в покое.

Наш кейген должен будет проделать всю нашу работу самостоятельно. Он станет интеллектуальным отладчиком. Берем Delphi 6 и пишем. Вот исходники:


program main;

{$APPTYPE CONSOLE} //мне так больше нравиться
{$R KeyGen.res} //иконка
uses
  SysUtils, Windows; //необходимый минимум
var
  BW, BREAD: Cardinal;
  Event: DEBUG_EVENT; //отладочное событие
  XCode: array[1..255] of byte; //массив с нашим кодом
  Answer: char; //ответ пользователя
  WriteAddress_0, cAddress_0: Longint; //нужные нам адреса
  WriteAddress_1, cAddress_1: Pointer;
  NewByte: byte; //его будем писать в тело программы
  CodeString: string; //строка с кодом
  StartupInfo: TStartupInfo; //информация для запуска
  ProcessInfo: TProcessInformation; //информация о процессе
  Context: _CONTEXT; //контекст нашего процесса -- сод. значения регистров и т.д.
  i: integer; //не помню что

function SeekInBuffer(Buffer: array of byte): Longint;
  //надо сначала найти нужный адрес
var
  posit: Integer;
begin
  Result := 0;
  for posit := 1 to 1024 do
  begin
    if Buffer[Posit] = $8B then
      //ищем наши инструкции в 16-м виде(см. начало(асм. листинг))
      if Buffer[Posit + 1] = $1B then //это инструкции до push ebx, push ecx
        if Buffer[Posit + 2] = $8B then
          if Buffer[Posit + 3] = $4D then
            if Buffer[Posit + 4] = $E8 then
              if Buffer[Posit + 5] = $53 then //$53=push ebx
                if Buffer[Posit + 6] = $51 then //$51=push ecx
                begin //тут, надеюсь, вс¸ ясно.
                  Result := Posit;
                  Exit;
                end
                else
                  Result := 0;
  end;

end;

function SeekBpPlace(ProcessHandle: Cardinal): Longint; //главная функция поиска
var
  lpBaseAdr: Pointer;
  BR: Cardinal;
  StartAddr, SeekResult: Longint;
  Buffer: array[1..1030] of byte; //для прочитанных кусков программы.
begin
  StartAddr := $401000;
  repeat
    asm //си¸ делается так, чтобы присвоить lpBaseAdr значение StartAddr
      mov eax, StartAddr //что обычными средствами Delphi сделать проблематично.
      mov lpBaseAdr, eax;
    end;
    ReadProcessMemory(ProcessHandle, lpBaseAdr, @Buffer, 1030, BR);
      //читаем кусок
    SeekResult := SeekInBuffer(Buffer); //ищем в н¸м наши инструкции
    if SeekResult = 0 then
    begin
      Inc(StartAddr, 1024); //если ничего не нашли, ид¸м дальше.
    end;
  until SeekResult <> 0; //пока поиск не закончится
  Result := SeekResult + StartAddr; //добавляем StartAddr и получаем искомое.
end;
//--------------------------------------------//
begin
  Writeln('THIS PROGRAM IS FOR EDUCATIONAL USE ONLY!');
  Writeln('YOU CAN USE IT ONLY IF YOU HAVE REGISTERED COPY OF AUDIO MP3 MAKER!');
  Writeln('OTHERWISE, IT IS ILLEGAL TO USE THIS PROGRAM!');
  Writeln;
  Write('Hit <Y>,<Enter> to agree or <N>,<Enter> to disagree/exit==>');
  Read(Answer);
  if UpCase(Answer) <> 'Y' then
    ExitProcess(0);
  if not FileExists('keyinfo.key') then //чтоб нас не использовали как крэк
  begin
    Writeln('You aren''t registered user!');
    Writeln('Hit <Enter> to exit...');
    Readln;
    ExitProcess(0);
  end;
  RenameFile('keyinfo.key', 'keyinfo.key.org'); //делаем резервную копию
  Writeln('Welcome.'); //тут, надеюсь, тоже вс¸ ясно
  Writeln(#4 + ' This is keygen for Audio Mp3 Maker v. 1.10/1.11/1.12/1.13!');
  Writeln(#4 + ' Tested on Audio Mp3 Maker v. 1.10/1.13');
  Writeln(#4 + ' Author: Wersion');
  Writeln(#4 + ' E-mail: wcrkgroup2002@mail.ru');
  Writeln('What do you need to do:');
  Writeln(#4 +
    'When program will be loaded, press ''Try it'' on the nag-screen');
  Writeln(#4 + 'After that, press ''Register'' in the program''s window!');
  Writeln(#4 + 'Enter your name.');
  Writeln(#4 + 'Enter any code, for example ''any code'' :-).');
  Writeln(#4 + 'Press''OK''');
  Writeln(#4 + 'Wait.');
  Sleep(10000);
  FillChar(StartupInfo, Sizeof(StartupInfo), #0); //заполняем структуру
  StartupInfo.cb := Sizeof(StartupInfo);
  StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
  StartupInfo.wShowWindow := SW_SHOWDEFAULT;
  if not FileExists('amm.exe') then //он, конечно, нам нужен.
  begin
    Writeln(#4 +
      ' File not found (''Amm.exe'') ! Please run me in program''s directory!');
    Readln;
    ExitProcess(0);
  end;
  {Созда¸м процесс и становимся отладчиком}
  if CreateProcess(nil, PChar('Amm.exe'), nil, nil, false,
    DEBUG_ONLY_THIS_PROCESS, nil, nil, StartupInfo, ProcessInfo) then
    Writeln(#4 + 'Process created successfully...')
  else
    ExitProcess(0);
  Writeln(#4 + 'Seeking target...');
  WriteAddress_0 := SeekBpPlace(ProcessInfo.hProcess) + 6;
    //сюда надо поставить брейкпоинт
  Writeln(#4 + ' Target found at 0x' + IntToHex(WriteAddress_0, 6));
  asm //вы это уже видели
    mov eax,WriteAddress_0
    mov WriteAddress_1,eax
  end;
  //--------------------------------------------//
  repeat
    WaitForDebugEvent(Event, 0);
    if event.dwDebugEventCode = EXIT_PROCESS_DEBUG_EVENT then
    begin
      Writeln(#4 + ' Process terminated by user! Unable to get code!');
      Readln;
      ExitProcess(0);
    end;
    if FindWindow(Pchar('#32770'), Pchar('Register')) <> 0 then
      //дожд¸мся начала регистрации
    begin
      ContinueDebugEvent(Event.dwProcessId, Event.dwThreadId, DBG_CONTINUE);
      //продолжаем отладку и выходим из цикла(бесконечного, кстати). Break;
    end;
    ContinueDebugEvent(Event.dwProcessId, Event.dwThreadId, DBG_CONTINUE);
  until false;
  NewByte := $CC;
    //инструкция INT 03, воспринимается нами(отладчиком) как брейкпоинт.
  WriteProcessMemory(ProcessInfo.hProcess, WriteAddress_1, @NewByte, 1, BW);
    //устанавливаем его
  Writeln(#4 + ' Target patched...');
  Writeln(#4 + ' Breakpoint enabled...');
  repeat //ждем появления INT 03
    WaitForDebugEvent(Event, Infinite);
    Writeln(#4 + ' Waiting...');
    if event.dwDebugEventCode = EXCEPTION_DEBUG_EVENT then
      if event.Exception.ExceptionRecord.ExceptionCode = EXCEPTION_BREAKPOINT
        then
      begin
        //дождались! Writeln(#04+' Breakpoint reached...');
        NewByte := $51; //восстанавливаем старую инструкцию push ecx
        WriteProcessMemory(ProcessInfo.hProcess, WriteAddress_1, @NewByte, 1,
          BW);
        Writeln(#4 + ' Target patched again (original restored)...');
        Context.ContextFlags := CONTEXT_INTEGER;
        GetThreadContext(ProcessInfo.hThread, Context); //получаем контекст
        cAddress_0 := Context.Ecx; //нам надо значение регистра Ecx
        asm
          mov eax,cAddress_0
          mov CAddress_1,eax
        end;
        Writeln(#4 + 'Reading code...');
        ReadProcessMemory(ProcessInfo.hProcess, cAddress_1, @Xcode, 255, BREAD);
          //читаем код с мусором в буфер
        TerminateProcess(ProcessInfo.hProcess, 0); //завершаем программу
        Writeln(#4 + 'Terminating process...');
        Break; //выходим из бесконечного цикла
      end;
    ContinueDebugEvent(Event.dwProcessId, Event.dwThreadId, DBG_CONTINUE);
  until false;
  //-----------------------------------------------------//
  CodeString := '';
  for i := 1 to BREAD do //очищаем код от мусора и су¸м в строку
  begin
    if Xcode[i] <> 0 then
      CodeString := CodeString + Chr(Xcode[i])
    else
      Break;
  end;
  Writeln(#4 + ' Your code: ' + CodeString);
  Writeln(#4 + ' Your have 1 minute to copy it to clipboard.');
  Writeln(#4 + ' Enjoy!');
  Sleep(60000);
  //собственно, это вс¸.
end.

Что сказать в заключение?

Что сделать кейген с чистого листа при пратически полном отсутствии документации по функциям отладки совсем не просто.

Created by Wersion. E-mail: wcrkgroup2002@mail.ru
Комментарии/вопросы - приветствуются.
Greats to: Iczelion, Dr. Golova.






Copyright © 2004-2016 "Delphi Sources". Delphi World FAQ




Группа ВКонтакте   Ссылка на Twitter   Группа на Facebook