Pull to refresh

Исследование защиты программы TurboLaunch 5.1.3

Reading time 11 min
Views 5K

Простая защита или ломаем TurboLaunch 5.1.3 тремя способами (патч, сниффер, кейген).



Цель: TurboLaunch 5.1.3
Инструменты: OllyDbg 1.10, Dup2 (для создания патча), плагин CodeRipper для OllyDbg, Delphi 7 (для написания кейгена)

Был скучный вечер, я искал программу, которую хотелось бы «поковырять». Сначала правкой одного байта убрал триал у AWBackuper 4.0, это показалось очень не интересно, затем я вспомнил о программке, которая давно (года 4) стоит у меня на компьютере – это TurboLaunch от Savard Software. К ней существуют кейгены (наверное, около 10), но, ни в одном я не увидел одного маленького нюанса (хотя по большому счету он не практически не играет существенной роли), но о нем чуть позже.

И так, let’s begin…

Патчинг

Первым способом я выбрал патчинг, так как для новичков он является самым простым.
Грузим жертву в OllyDbg (дальше просто оля, olly). Запускаем и видим наг-окно, нажимаем «Enter My Registration Code»
image

Вводим свое имя (DimitarSerg) и «честно купленный» код 1234567890
Очень странно, но видим вот такое сообщение:
image

Поищем текст сообщения в текстовых строках. ПКМ-> Search for -> All referenced text strings.

Находим:
image

Чуть выше видим Jump from 00529CD3, то есть пришли мы оттуда.
Отлично, посмотрим, что там у нас:

«Классика»:
CALL TurboLau.0053AEB0
TEST AL,AL
JE @TurboLau_00529D99


Значит, идет проверка и в соответствии с результатом проверки прыжок.
Чуть ниже вот такие строки:

00529D1A |. BA F49D5200 MOV EDX,TurboLau.00529DF4 ; ASCII "REGISTERED TO: "
И такое:
00529D3B |. 68 0C9E5200 PUSH TurboLau.00529E0C ; ASCII "Thank you for registering! Be sure to check out our web site for updated versions of TurboLaunch and other programs written by "

Ну что же, тогда заглянем в 00529CCC |. E8 DF110100 CALL TurboLau.0053AEB0
там еще один колл, а внутри что-то похожее на процедуру регистрации. Ну и зачем нам процедура проверки, если можно обойтись и без нее ?!

И так в начало процедуры регистрации по адресу 540628 делаем «классический» патч
xor eax,eax // обнуляем EAX
inc eax // EAX = EAX +1
Retn // return

Сохраняем изменения. ПКМ-> Copy to executable -> All modifications -> Save file.

Сохраним его под новым именем, например, TurboLaunch1.exe
Хотим радоваться, но получаем в ответ такое:
image

Ай-ай-ай. Проверка целостности. Ну и Бог с ней:
Перегружаем программу в отладчике, затем ставим breakpoint на вызов функции MessageBoxA
bp MessageBoxA

В окне «Call Stack of main thread» смотрим, откуда пришли:
image

Делаем ПКМ-> Show Call и оказываемся по адресу
00450CA4 |. E8 8F75FBFF CALL <JMP.&user32.MessageBoxA> ; \MessageBoxA

Листаем выше и видим:
00450C7B |> \84DB TEST BL,BL
00450C7D |. 74 60 JE SHORT TurboLau.00450CDF

Опять проверка.
Меняем условный переход на безусловный (JE –> JMP), сохраняем изменения и стартуем. Ура, программа запускается.

Окна с просьбой купить больше не наблюдаем, в окне About видим «Registered to».
В общем, на кого-то зарегистрировано, вот и хорошо.

Сделать патч в Dup2 или uPPP не является проблемой.

Поиск серийного номера.

Как вы заметили выше, сообщение о правильном/неправильном серийном номере формируется в зависимости от результата

00529CD1 |. 84C0 TEST AL,AL

А строкой выше видим:
00529CCC |. E8 DF110100 CALL TurboLau.0053AEB0
Логично, что вся процедура генерации проходит здесь…

Смотрим:
0053AEB0 /$ 8B90 68010000 MOV EDX,DWORD PTR DS:[EAX+168]
0053AEB6 |. 8B80 64010000 MOV EAX,DWORD PTR DS:[EAX+164]
0053AEBC |. E8 67570000 CALL TurboLau.00540628
0053AEC1 \. C3 RETN


Ставим breakpoint по адресу 0053AEB0 и при вводе имени / регистрационного номера останавливаемся здесь и видим, что происходит считывание введенных данных, а значит, самое интересное находится в CALL TurboLau.00540628.

Трассируем по F7, видим считывание, какие-то превращения, другие операции и т.д. и т.п., в результате, если уделить этой процедуре пару минут внимания и терпения, то во время выполнения инструкции

00540758 |. 8B45 F4 MOV EAX,DWORD PTR SS:[EBP-C]

Мы увидим:
Stack SS:[0012F1AC]=00C66E8C, (ASCII «D1F74F-5L3GRT-3WDULJ»)
Хм, неужели правильный серийный номер ?! Пробуем: имя DimitarSerg и серийный номер D1F74F-5L3GRT-3WDULJ, программа поздравляет нас с приобретением.

Так как серийный номер виден в открытом виде, то сериал сниффер пишется на ура, нужно только посмотреть в регистр EAX по адресу, который идет сразу за 00540758, то есть 0054075B.

Я не буду описывать методику написания сериал сниффера, но скажу, что у команды AT4RE есть такой инструмент, как Serial Sniffer Creator, можете воспользоваться им, должно выглядеть приблизительно вот так:
image

Но я ее не рекомендую, так как она не сниффает серийники в юникоде и у нее частые глюки с интерфейсом.
На паблике есть несколько примеров (темплейтов) по созданию снифферов, поэтому ассемблер или Delphi в руки и вперед «допиливать».

Кейген

Ну и самое интересное — это, конечно же, написание кейгена.
Как я описывал выше, вся процедура генерации начинается по адресу 00540628
Вводим любые данные регистрации, трассируем. Обращаем внимание на вот это место:
image

Заглянем в этот колл. Видим некоторые операции, выполняемые над именем, вычисления. Скажу наперед, что здесь происходит первая часть вычислений, результат которых используется чуть-чуть позже. Я не «великий спец» по ассемблеру, поэтому использую плагин CodeRipper.

Я покажу два способа кейгена: инлайн ассемблерного кода на Делфи и полностью переведенный код на паскаль:

Copy Source | Copy HTML
  1. procedure GenClick( Dummy : Pointer; Sender: PControl );
  2. var NameBuffer,SerNum:String;
  3. len,sn_tmp:integer;
  4.   begin
  5. if Nm.Text<>'' then begin
  6.       NameBuffer:=Nm.Text;
  7.       len:=Length(Nm.Text);
  8.     ASM
  9.     PUSHAD
  10. mov ECX,NameBuffer
  11. mov EBX,len
  12. @TurboLau_0053F42E:
  13.         XOR EAX,EAX
  14.         MOV AL,BYTE PTR DS:[ECX]
  15.         SHL EAX,8
  16.         XOR EDX,EAX
  17.         MOV EAX,8
  18. @TurboLau_0053F43C:
  19.         TEST DH,080h
  20.         JE @TurboLau_0053F44B
  21.         ADD EDX,EDX
  22.         XOR EDX,01021h
  23.         JMP @TurboLau_0053F44D
  24. @TurboLau_0053F44B:
  25.         ADD EDX,EDX
  26. @TurboLau_0053F44D:
  27.         DEC EAX
  28.         JNZ @TurboLau_0053F43C
  29.         INC ECX
  30.         DEC EBX
  31.         JNZ @TurboLau_0053F42E
  32.        MOV EAX,EDX
  33.        AND EAX,0FFFFh
  34.        mov sn_tmp,EAX
  35.      POPAD
  36.      END;
  37. Edit.text:=Int2Hex((sn_tmp),4);
  38. end
  39. else
  40. Edit.Text:='Enter Your Name';
  41. End;


Зачем я привел процедуру отдельно? Мне так было легче, сразу видно правильность работы рипнутого кода. Например, для имени DimitarSerg sn_tmp = E330

Ну а дальше опять дело техники: используем плагин CodeRipper, начиная с адреса 5406BC до 540752 (видим, что это большой цикл генерации). На что хочу обратить внимание – это на некоторые коллы. CodeRipper пишет “;<= Jump/Call Address Not Resolved”, большинство из них не нужны и их можно удалить, но вот
005406EE |. E8 F52DECFF ||CALL TurboLau.004034E8
Этот колл ни в коем случае нельзя удалять, так как внутри:

Copy Source | Copy HTML
  1. @TurboLau_004034E8:
  2.         PUSH EBX
  3.         XOR EBX,EBX
  4.         IMUL EDX,DWORD PTR DS:[EBX+0542008h],08088405h
  5.         INC EDX
  6.         MOV DWORD PTR DS:[EBX+0542008h],EDX
  7.         MUL EDX
  8.         MOV EAX,EDX
  9.         POP EBX
  10.         RETN


Это, как мне подсказали (на тот момент не знал), что это стандартный Random, а DWORD PTR DS:[EBX+0542008h] (он же у меня sn_tmp) – это RandomSeed.

А в рипнутом коде уже не вызывать ее как отдельную процедуру, а просто скопипастить в место ее вызова, вот фрагмент:
Copy Source | Copy HTML
  1. @TurboLau_005406E9:
  2.         MOV EAX,021h
  3.         // -->CALL @TurboLau_004034E8 ;<= Jump/Call Address Not Resolved
  4.         // содержимое колла
  5.             PUSH EBX
  6.             XOR EBX,EBX
  7.             IMUL EDX,sn_tmp,08088405h
  8.             INC EDX
  9.             MOV sn_tmp,EDX
  10.             MUL EDX
  11.             MOV EAX,EDX
  12.             POP EBX
  13.         MOV EBX,EAX
  14.         INC EBX
  15.         MOV AL,BYTE PTR SS:[EBP-0Dh]
  16.         XOR AL,0FFh
  17.         AND EAX,0FFh
  18.         ADD EBX,EAX
  19.         DEC ESI
  20.         JNZ @TurboLau_005406E9


Еще хочу обратить внимание на строку
00540715 |. BA 1C085400 |MOV EDX,TurboLau.0054081C; ASCII «GF2DSA38HJKL7M4NZXCV5BY9UPT6R1EWQ40I1CP7Z7GOEPQLZ»

Ее можно назвать “ключевой”, так как от нее прямо зависит регистрационный код. В регистр edx записывается указатель на строку по адресу 0054081C.
Полный исходник кейгена на Delphi с асмовскими вставками и на чистом паскале прилагаются, ничего сложного там нет.

Ну и то, с чего я начинал рассказ: после всех этих генераций мы НЕ ОСТАНАВЛИВАЕМСЯ, а топаем дальше и видим какие-то имена/ники и т.д.

Естественно, это же примитивный блэклист:
Copy Source | Copy HTML
  1. @TurboLau_005407C4:
  2. MOV EAX,DWORD PTR SS:[EBP-4] ; Цикл проверки блэклиста
  3. MOV EDX,DWORD PTR DS:[ESI]
  4. CALL @TurboLau_00405030
  5. JNZ @TurboLau_005407D4
  6. XOR EBX,EBX
  7. JMP @TurboLau_005407DA
  8. @TurboLau_005407D4:
  9. ADD ESI,4
  10. DEC EDI
  11. JNZ @TurboLau_005407C4


Там проскакивают такие интересные ники, как Nitrogen / TSRh TeaM, REVENGE Crew, FiGHTiNG FOR FUN, TEAM VIRILITY… и т.д. Думаю, намек вы поняли.
Итого 44 имени. Не проблема скопипастить вручную, забить в строковой массив типа
BlackList:array [0..43] of string = (…), а при генерации смотреть, не является ли желаемое вами имя в блэклисте. (файл BlackList.txt тоже прилагаю).

Для “полного счастья” переводим код с асм-вставками на паскаль:

Copy Source | Copy HTML
  1. //…
  2. var
  3. BlackList:array [ 0..43] of string = ('zircon / pc97','freeware','registered user',<br/>'NuZ''c97','Registered','kOUGER! [CB4]','Cosmo Cramer 1997','Cosmo Cramer MJ13',<br/>'MJ13 Forever','cH/Phrozen Crew','Everybody','iCEMAN [uCF]','pank','Henry Pan',<br/>'iTR [CORE]','mpbaer','CORE/JES','Chen Borchang','n03l','ODIN 97','lgb/cORE''97',<br/>'MCC','blastsoft','CORE/DrRhui','Vizion/CORE','TEAM ViRiLiTY','Nambulu','NuZPc97',<br/>'Weazel','Phrozen Crew','TEAM VIRILITY','x3u','Reg Name','FiGHTiNG FOR FUN','RaSCaL [TMG]',<br/>'Nitros^21','TEAM TSRH','ttdown.com','Nitrogen / TSRh TeaM','Free Program','REVENGE Crew',<br/>'Vladimir Kasho','Alexej Melnikov','Seth W. Hinshaw');
  4. //…
  5. procedure generate;
  6. Var
  7. NameBuffer,SerNum:String;
  8. EDX,EAX,len,i,a,b,tmp1:integer;
  9. Textname: PChar;
  10. begin
  11.   len := GetWindowTextLengthA(TxtNameHwnd);
  12.   if len > 1 then
  13.   begin
  14.   { Get text from name input }
  15.   GetMem(Textname, len + 1);
  16.   GetWindowTextA(TxtNameHwnd,PAnsiChar(Textname),len + 1);
  17.   { Generate Serial }
  18.   KeyStr:= 'GF2DSA38HJKL7M4NZXCV5BY9UPT6R1EWQ40I1CP';
  19.   NameBuffer:=String(Textname);
  20.   SerNum:='';
  21. Randomize;
  22. EDX:= 0;
  23.     for i:=1 to len do
  24.     begin
  25.     EDX := EDX xor (ord(NameBuffer[i]) shl 8);
  26.     EAX := 8;
  27.       while EAX <>  0 do
  28.       begin;
  29.     if (EDX shr 8 and $0FF) >= $80
  30.     then
  31.         EDX := EDX shl 1 xor $1021
  32.             else
  33.                   EDX := EDX shl 1;
  34.     dec(EAX);
  35.     end;
  36.   end;
  37. RandSeed := EDX and $0FFFF;
  38. i:=1;
  39. while i <> $13 do
  40. begin
  41.       a :=  0;
  42.       b := $13 - i;
  43.       if b >  0 then
  44.       begin
  45.         while b >  0 do
  46.           begin
  47.             a:=Random(33);
  48.             inc(a);
  49.             if i > len then
  50.               tmp1:=ord(NameBuffer[i mod len])
  51.               else
  52.               tmp1:=ord(NameBuffer[i]);
  53.             tmp1:=tmp1 xor $0FF and $0FF;
  54.             a := a + tmp1;
  55.     dec(b);
  56.           end;
  57. end;
  58.       while a > $21 do
  59.         begin
  60.     a := a - $21;
  61.         end;
  62.       SerNum := SerNum + KeyStr[a];
  63.     inc(i);
  64. end;
  65. Insert('-',SerNum,7);
  66. Insert('-',SerNum,14);
  67. For i:= 0 to 43 do
  68.   begin
  69.     if NameBuffer = BlackList[i] then
  70.       begin
  71.       SerNum:='BLACKLISTED NAME';
  72.       Break;
  73.       end else ;
  74.   end;
  75.   { Display The Results }
  76.   SetWindowTextA(TxtSerialHwnd,PChar(SerNum));
  77.   FreeMem (Textname, len + 1);
  78.   end
  79.   Else
  80.   { Display Error }
  81.        SetWindowText(TxtSerialHwnd,'Not Enough Characters..');
  82. end;

В результате у меня получился вот такой вот кейген:
image

Размер 36,5 Кб (неупакованный).Такой маленький размер кейгена достигается за счет использование KOL (Key Objects Library) – библиотеки для Delphi.Можно еще сделать и с использованием WinApi, вклеить свое лого, добавить xm / v2m трек (так я для публичного кейгена и сделал), но мой совет – не используйте VCL, если пишете кейгены на Delphi… 400кб и больше – это не размер для кейгена!
Кому больше нравится чистый ассемблер – Masm (Fasm, Tasm) в руки.

На этом мой рассказ подходит к концу, надеюсь, кто-то что-нибудь да почерпнет для себя из данной статьи.
Tags:
Hubs:
+52
Comments 34
Comments Comments 34

Articles