Перевод: 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, передав ей в качестве параметра дескриптор процесса. Затем мы освобождаем дескрипторы, чтобы избежать утечек памяти.
Надеюсь я понятно смог объяснить фундаментальные основы программирования лоадеров. Если у вас есть вопросы или идеи, напишите мне.
Комментариев нет:
Отправить комментарий