Delphi World - Исследование программы Audio Mp3 Maker v 1.10-1.13 by Wersion
Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Исследование программы 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. <- наставили меня на путь истинный.

Проект Delphi World © Выпуск 2002 - 2017
Автор проекта: Эксклюзивные курсы программирования