Для начала определимся с внедряемым кодом. Он будет передавать функции
LoadLibraryA имя подгружаемой DLL’ки, вызывать LoadLibraryA и запускать
программу (прыжок на Original Entry Point). Пусть это будет что-то вроде
этого:
db 0E8h dd 00000000 // это call $+5; Он нужен для получения регистра
EIP pop ecx // получаем EIP add ecx,13 //
Получаем адрес имени DLL’ки push ecx // передаём
его в качестве параметра LoadLibraryA call
[$00403344] // вызов LoadLibraryA. (адрес
вызова мы будем модифицировать в зависимости от программы) jmp ProgStart db 4Dh db 79h db 4Fh db 77h db 6Eh db
2Eh db 64h db 6Ch db 6Ch db 0h // здесь имя
DLL’ки. (в нашем случае MyOwn.dll + #0) ProgStart: db 0e9h db 0 db 0 db 0 db 0 // это прыжок на Original Entry Point. (в дальнейшем тоже
модифицируем).
Этот код нужно откомпилить, что бы получить
машинный код:
:004033AC E800000000 call
004033B1 :004033B1 59 pop ecx :004033B2 83C10C add ecx,
0000000C :004033B5 51 push ecx
:004033B6 E889FFFFFF Call 00403344 //
этот адрес мы модифицируем.
(LoadLibraryA) :004033BB EB0A jmp
004033C7 :004033BD 4D dec ebp :004033BE 794F jns 0040340F :004033C0
776E ja 00403430 :004033C2 2E BYTE 02eh
:004033C3 64 BYTE
064h
:004033C4 6C insb :004033C5 6C insb :004033C6 00E9 add cl,
ch :004033C8 00000000 BYTE 4 DUP(0)
Что ж… Теперь можно перейти
к написанию модифицирующей части. Немного теории: требуется открыть
модифицируемую прогу, найти в её таблице импорта указатель на LoadLibraryA,
найти свободное место, в которое мы запишем код (в 95% программ есть "целинные
участки” в секциях .text, CODE, DATA или .data). Поменять Original Entry Point
на наш. Вот пример кода:
program
Exefiles;
{$APPTYPE CONSOLE} uses windows, ImageHlp;
type
IMAGE_IMPORT_DESCRIPTOR=record // запись в
таблице импорта OriginalFirstThunk:
DWORD; TimeDateStamp : DWORD; ForwarderChain : DWORD; Name :
DWORD; FirstThunk : DWORD; end;
TOE=record // структура секции Name:array[0..7] of
char; SizeOfSect:dword; RVA:dword; RealSize:dword; RVAinPE:dword; a:array[0..11]
of byte; Attr:dword; end;
const FuncName='LoadLibraryA';
var
s:array[0..12] of
char; h:integer; oe:toe; _:IMAGE_DOS_HEADER; IOH:IMAGE_NT_HEADERS; __,hel:cardinal; LLA:dword; a,i:shortint; iid:
IMAGE_IMPORT_DESCRIPTOR; SavedRVA,NewRVA:cardinal; mas:array[0..11] of
byte=($E8,0,0,0,0,$59,$83,$C1,$0d,$51,$FF,$15); // наш код mas1:array[0..18]
of byte=($EB,$0A,$4D,$79,$4F,$77,$6E,$2E
,$64,$6C,$6C,0,$90,$90,$90,$90,$90,$90,$E9); // я дополнил код nop’ами. (Что б в Win32Dasm всё видно было)
// За RVA2Offset
& Offset2RVA спасибо R4D][ :) function
RVA2Offset(hFile: THANDLE;RVA: Cardinal): Cardinal; var Base:
Pointer; ISH : PIMAGESECTIONHEADER; INH : PIMAGENTHEADERS; hFM :
THANDLE; begin Result:=0; hFM:=CreateFileMapping(hFile,nil,PAGE_READONLY,0,0,nil); Base:=MapViewOfFile(hFM,FILE_MAP_READ,0,0,0); if
Base=nil
then begin UnMapViewOfFile(Base); CloseHandle(hFM); exit; end; INH:=ImageNTHeader(Base); if
INH=nil
then begin UnMapViewOfFile(Base); CloseHandle(hFM); exit; end; ISH:=ImageRVAToSection(INH,Base,RVA); if
ISH=nil
then begin UnMapViewOfFile(Base); CloseHandle(hFM); exit; end; Result:=RVA-ISH.VirtualAddress+ISH.PointerToRawData; UnMapViewOfFile(Base); CloseHandle(hFM); end;
function
Offset2RVA(hFile: THANDLE;Offset: Cardinal): Cardinal; var Base:
Pointer; ISH : PIMAGESECTIONHEADER; INH : PIMAGENTHEADERS; hFM :
THANDLE; x :
Integer; begin Result:=0; hFM:=CreateFileMapping(hFile,nil,PAGE_READONLY,0,0,nil); Base:=MapViewOfFile(hFM,FILE_MAP_READ,0,0,0); if
Base=nil
then begin UnMapViewOfFile(Base); CloseHandle(hFM); exit; end; INH:=ImageNTHeader(Base); if
INH=nil
then begin UnMapViewOfFile(Base); CloseHandle(hFM); exit; end; ISH:=PIMAGESECTIONHEADER(DWORD(INH)+sizeof(IMAGE_NT_HEADERS)); for
x:=0 to INH.FileHeader.NumberOfSections do begin if
(Offset>=ISH.PointerToRawData) and
(Offset<=ISH.PointerToRawData+ISH.SizeOfRawData) then
break; inc(ISH); end; Result:=Offset+ISH.VirtualAddress-ISH.PointerToRawData; UnMapViewOfFile(Base); CloseHandle(hFM); end;
begin Writeln('Enter
Exe-File name'); Readln(s); h:=Createfile(s,GENERIC_READ or GENERIC_WRITE,
0,nil,OPEN_EXISTING,0,0); if h=-1 then begin Writeln('Wrong
handle'); Readln; exit; end; ReadFile(h,_,sizeof(_),__,nil); SetFilePointer(h,_._lfanew,0,FILE_BEGIN); ReadFile(h,IOH,sizeof(IOH),__,nil); SavedRVA:=Rva2offset(h,ioh.OptionalHeader.AddressOfEntryPoint); if
ioh.Signature<>$00004550 then begin writeln('Wrong
magic'); readln; CloseHandle(h); exit; end; __:=rva2offset(h,ioh.OptionalHeader.DataDirectory[1].VirtualAddress); _._lfanew:=SetFilePointer(h,__,0,FILE_BEGIN); iid.FirstThunk:=1;
// лезем в чанки искать LoadLibrayA while
(s<>'LoadLibraryA')and(iid.FirstThunk<>0) do
begin ReadFile(h,IID,sizeof(IID),__,nil); inc(_._lfanew,__); __:=rva2offset(h,IID.Name); SetFilePointer(h,__,0,FILE_BEGIN); Readfile(h,s,12,__,nil); if
(s='kernel32.dll')or(s='KERNEL32.dll') then begin // Borland и Microsoft пишут kernel32 каждый по своему hel:=rva2offset(h,IID.FirstThunk); repeat SetFilePointer(h,hel,0,FILE_BEGIN); Readfile(h,LLA,4,__,nil); inc(hel,__); __:=rva2offset(h,LLA)+2; SetFilePointer(h,__,0,FILE_BEGIN); Readfile(h,s,12,__,nil); until
(s=FuncName)or(LLA=0); end; SetFilePointer(h,_._lfanew,0,FILE_BEGIN); end; if
s<>FuncName then exit; SetFilePointer(h,0,0,FILE_BEGIN); //
нашли адрес LoadLibraryA'a и теперь можем (и
даже должны :)) записать код в файл. readFile(h,_,sizeof(_),__,nil); SetFilePointer(h,_._lfanew+sizeof(ioh),0,FILE_BEGIN); oe.SizeOfSect:=1; while
(((oe.Name<>'DATA') and (oe.Name<>'.text')and
(oe.Name<>'CODE') and (oe.Name<>'.data'))or
(oe.RealSize-oe.SizeOfSect<40))and (oe.SizeOfSect<>0) do //
Будем помещать код в секции данных или кода.
Так же нам важен и размер неиспользуемой части секции. (что б наш код
поместился) readfile(h,oe,sizeof(oe),__,nil); if oe.Name='' then
exit; newRva:=SetFilePointer(h,rva2offset(h,oe.RVA)+oe.SizeOfSect,0,FILE_BEGIN);
// Теперь всё формируем и пишем Writefile(h,mas,sizeof(mas),__,nil); hel:=Offset2Rva(h,hel)+ioh.OptionalHeader.ImageBase
-4; Writefile(h,hel,sizeof(hel),__,nil); Writefile(h,mas1,sizeof(mas1),__,nil); ioh.OptionalHeader.AddressOfEntryPoint:=ioh.OptionalHeader.AddressOfEntryPoint-Offset2Rva(
h,SetFilePointer(h, 0, 0, FILE_CURRENT)+4);// вычисляем значение для прыжка. Оно равно разности между последующим RVA и
Original RVA.
Writefile(h,ioh.OptionalHeader.AddressOfEntryPoint,sizeof(ioh.OptionalHeader.AddressOfEntryPoint),__,nil); // теперь записываем
все модификации. newrva:=Offset2Rva(h,
newrva); SetFilePointer(h, 0, 0, FILE_BEGIN); readFile(h, _, sizeof(_),
__, nil); SetFilePointer(h, _._lfanew, 0,
FILE_BEGIN); ioh.OptionalHeader.AddressOfEntryPoint:=newrva; Writefile(h,
ioh, sizeof(ioh), __, nil); CloseHandle(h); end.
Код DLL’ки
же зависит от того, что мы пишем. Я для примера написал DLL’ку, которая ведёт
лог запусков программы:
library
MyOwn;
uses windows, sysutils;
var
f:text; begin assignfile(f, 'test.txt'); if fileexists('test.txt')then
append(f) else rewrite(f); writeln(f,
datetimetostr(now)); closefile(f); end.
|