IT-Блог о программировании и операционных системах

Delphi. Как написать лоадер

Автор: Sunshine [www.sunshine2k.de]
Перевод: Rob [www.delphiday.blogspot.com]

От переводчика:
Данный тьюториал покажет основы написания лоудеров. Написан начинающим немецким реверсером и программистом. Изложено доступно, приведён исходный код лоудера. Перевод в некоторых местах вольный. Автор статьи постарался доступным языком, для таких же новичков как и он, обьяснить то, чего сам не мог понять из за отсутствия материала, то есть той базы, которой некоторое время не хватало ему. Статья будет очень интересна новичкам в реверсинге и программировании.


В данном тьюториале, я постараюсь объяснить, как написать лоудер для программы, если вы не хотите или не можете патчить программу стандартным способом, если например она запакована или проверяет свою контрольную сумму перед выполнением, или что либо ещё – причин для этого может быть много.
В своё время, я долго пытался найти статью, которая бы пролила свет на эту тему и тот туториал, что я нашел оказался на ассемблере, который я ещё мало понимал (пока!). Вот почему я решил написать статью, по программированию лоудера на Delphi.
Я написал простой крякми, который будет взломан с помощью нашего с вами лоудера. Но учтите, что реальную запакованную программу пропатчить будет сложнее, нежели наш крякми. Давайте начнём!



Взлом crackme


Итак, давайте запустим Example.exe. Мы вводим серийник (вводите что угодно), жмём на кнопку и нам показывается симпатичный мессаджбокс с текстом “Wrong! Try again”. Теперь запустим WDasm и натравим его на Example.exe. Выберите “String Reference” и найдите строку, которую нам показали в окне предупреждения о неверном серийном номере. После этого вы увидите примерно следующее:
:00403CBF E8CCFEFFFF       Call 00403B90
:00403CC4 8D85F8FDFFFF     lea eax, dword ptr [ebp+FFFFFDF8]
:00403CCA 8D95FFFDFFFF     lea edx, dword ptr [ebp+FFFFFDFF]
:00403CD0 B901020000       mov ecx, 00000201
:00403CD5 E836F1FFFF       call 00402E10
:00403CDA 8B85F8FDFFFF     mov eax, dword ptr [ebp+FFFFFDF8]
* Possible StringData Ref from Code Obj ->"Sunshine"
:00403CE0 BA703D4000       mov edx, 00403D70
:00403CE5 E83EF1FFFF       call 00402E28
:00403CEA 7519             jne 00403D05 <- просто занопьте эту инструкцию
:00403CEC 6A40             push 00000040
* Possible StringData Ref from Code Obj ->"Success"
:00403CEE 687C3D4000       push 00403D7C
* Possible StringData Ref from Code Obj ->"Perfect! All right!"
:00403CF3 68843D4000       push 00403D84
:00403CF8 A100654000       mov eax, dword ptr [00406500]
:00403CFD 50               push eax
* Reference To: user32.MessageBoxA, Ord:0000h
:00403CFE E89DFEFFFF       Call 00403BA0
:00403D03 EB17             jmp 00403D1C
* Referenced by a (U)nconditional or (C)onditional Jump at Address::00403CEA(C)
:00403D05 6A40             push 00000040
* Possible StringData Ref from Code Obj ->"Error"
:00403D07 68983D4000       push 00403D98
* Possible StringData Ref from Code Obj ->"Wrong! Try again!"
:00403D0C 68A03D4000       push 00403DA0
:00403D11 A100654000       mov eax, dword ptr [00406500]
:00403D16 50               push eax
* Reference To: user32.MessageBoxA, Ord:0000h
:00403D17 E884FEFFFF       Call 00403BA0


Теперь у нас есть всё, что нужно для написания лоудера. Мы должны по адресу 403CEA записать два нопа(90h). То есть, было: 7519 , стало 9090. Это понятно.

Программирование лоадера


program loader;
uses
Windows, Messages;
{$R Loader.RES}
var
 si : Startupinfo;
 pi : Process_Information;
 NewData : array[0..1] of byte = ($90,$90);
 NewDataSize : DWORD;
 Bytesread : DWORD;
 Olddata : array[0..1] of byte;
Begin
 ZeroMemory(@si,sizeof(si));   
 ZeroMemory(@pi,sizeof(pi));    
 FillChar(Si,Sizeof(si),0);
 Si.cb:=Sizeof(si);

 NewDataSize := sizeof(newdata);
 IF CreateProcess(nil,'Example.exe',nil,nil,FALSE,Create_Suspended,nil,nil,si,pi) = true then
  begin
   ReadProcessMemory(pi.hprocess,Pointer($403CEA),@olddata,2,bytesread);
 if (olddata[0] = $75) and (olddata[1] = $19) then
  begin
   WriteProcessMemory(pi.hProcess, Pointer($403CEA), @NewData, NewDataSize, bytesread);
   ResumeThread(pi.hThread);
   CloseHandle(pi.hProcess);
   CloseHandle(PI.hThread);
  end else
  begin
   Messagebox(0,pchar('Bytes not found! Wrong version?...'),pchar('Error'),mb_iconinformation);
   TerminateProcess(PI.hProcess,0);
   CloseHandle(PI.hProcess);
   CloseHandle(PI.hThread);
  end;
end;
end.


Анализ кода


Сначала мы создаём процесс, который нужно пропатчить в памяти. Делаем мы это вызвав API функцию CreateProcess. Очень важно то, что процесс мы должны создать с флагом CREATE_SUSPENDED. Так новый процесс стартанёт не сразу а только после того как мы вызовем функцию ResumeThread. Так у нас будет время, чтобы произвести все необходимые изменения.

Затем, мы считываем 2 байта из памяти процесса функцией ReadProcessMemory по адресу 403CEA в наш буфер и проверяем эти байты. Если они равняются $75 и $19, мы можем предположить что мы патчим верные значения. Если это не так, можно предположить что это не то приложение, или скорее всего не та версия.

При помощи функции WriteProcessMemory мы легко можем записать новые значения в память процесса по нужным нам адресам. Когда все изменения сделаны, мы можем отпустить процесс на выполнение функцией ResumeThread, передав ей в качестве параметра дескриптор процесса. Затем мы освобождаем дескрипторы, чтобы избежать утечек памяти.

Надеюсь я понятно смог объяснить фундаментальные основы программирования лоадеров. Если у вас есть вопросы или идеи, напишите мне.

Комментариев нет:

Отправка комментария