Вступление
Предлполагается, что читатель уже ознакомился с первой и второй частью (правильнее даже сказать вариантами) данной статьи. В этой части, мы рассмотрим пример 100% перехвата WinExec в ОС Win9x. Как уже отмечалось, вся проблема в том - что верхние 2 GB адресного пространства разделяются всеми процессами, и потому, защищены от записи. Хорошим примером решения данной проблемы может послужить дискуссия с ExpertsExchange (она находится в архиве с разобраным исходником).

Как это происходит
Сначала, мы с помощью т.н. VxDCall0, снимем защиту от записи страницы памяти, содержащей заголовок Kernel32.WinExec. Затем, мы напишем редирект с Kernel32.WinExec (по методу второй (для WinNT) части этой статьи) на спец-буфер (как в первой части этой статьи). Этот спец буфер будет находиться в общем адресном пространстве, и потому, будет доступен всем процессам. В нем (назовем его "загрузчик-распределитель") - мы, если необходимо, загрузим нашу DLL, содержащую ф-ции-перехватчики, а затем, собственно сделаем переход на сам перехватчик. Адрес перехватчика мы получим по индексу вызовом функции GetProcAddress. Очевидно, что для этого перехватчик ("NewWinExec") должен быть проэкспортирован по индексу (в данном примере - 1).




Теперь приведем листинг нашей DLL (скачать его можно ниже)
library p1;

{структура данных, проецирущихся во все процессы:
$9EFF0000
512 байт, содержащих имя нашей DLL'ки
Используется в качестве параметра для LoadLibrary/GetModuleHandle
$9EFF0200
256 байт для хранения оригинальных заголовков функций (по 6 байт на заголовок)
$9EFF0300
256 байт для хранения адресов различных API функций (по 4 байта на адрес)
$9EFF0400
<здесь идет код загрузчика/распределителя>
}

uses
Windows;


var
WinExecAdr: pointer; //временный pointer. Название не соответствует сути
cs, q: DWORD; // CS - CodeSize - размер, выделенный под см. $9EFF0400
Unpr: boolean = false; //Флаг для вызова "UnprotectExportTable" единожды



///// Здесь идут функции, заимствованные у Madshi, который перевел исходники BackOrifice 2k. См. Q_10186120.html на "ExpertsExchange"

const CENEWHDR = $003C; // offset of new EXE header
CEMAGIC = $5A4D; // old EXE magic id: 'MZ'
CPEMAGIC = $4550; // NT portable executable
type TImageExportDirectory = packed record
Characteristics : dword;
TimeDateStamp : dword;
MajorVersion : word;
MinorVersion : word;
Name : dword;
Base : dword;
NumberOfFunctions : dword;
NumberOfNames : dword;
AddressOfFunctions : cardinal;
AddressOfNames : cardinal;
AddressOfNameOrdinals : cardinal;
end;
TPImageExportDirectory = ^TImageExportDirectory;

type TPWord = ^word;
TAWord = array [0..maxInt shr 1-1] of word;
TPAWord = ^TAWord;
TACardinal = array [0..maxInt shr 2-1] of cardinal;
TPACardinal = ^TACardinal;
TAInteger = array [0..maxInt shr 2-1] of integer;
TPAInteger = ^TAInteger;



function FinalFunctionAddress(Code: Pointer): Pointer; stdcall;
var
rd: word;
c: cardinal;
begin
result:=code;
if result=nil then exit;
readprocessmemory(getcurrentprocess, code, @rd, 2, c);
if rd=$25ff then
begin
readprocessmemory(getcurrentprocess, pointer(dword(code)+2), @result, 4, c);
readprocessmemory(getcurrentprocess, result, @result, 4, c);
end;
end;

function GetModuleNtHeaders(module: cardinal) : PImageNtHeaders;
begin
result:=nil;
try
if TPWord(module)^<>CEMAGIC then exit;
result:=pointer(module+TPWord(module+CENEWHDR)^);
if result^.signature<>CPEMAGIC then result:=nil;
except result:=nil; end;
end;

function GetModuleExportDirectory(module: cardinal) : TPImageExportDirectory;
begin
result:=nil;
try
result:=pointer(module + GetModuleNtHeaders( module )^.OptionalHeader.DataDirectory [IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
except end;
end;

function GetProcAddress_(module: cardinal; ord: cardinal) : pointer;
var exp : TPImageExportDirectory;
begin
result:=nil;
try
exp:=GetModuleExportDirectory(module);
if exp<>nil then
with exp^ do
if ordthen
result:=pointer(module+TPACardinal(module+AddressOfFunctions)^[ord]);
except end;
end;

function UnprotectExportTable(p: pointer) : boolean; stdcall;
var
fa : cardinal; // firstAddress
fp,np : cardinal; // firstPage / numPages
vxdcall : pointer;
begin
if unpr then exit;
unpr:=true;
result:=false;
try
fa:=dword(p);
fp:=fa div 4096;
np:=1;
dec(fa,fa mod 4096);
result:=not IsBadWritePtr(pointer(fa),np*4096);
if not result then begin
// Get undocumented VxDCall procedure
vxdcall:=GetProcAddress_(GetModuleHandle(kernel32),1);
if @vxdcall=nil then exit;
asm
push 020060000h // PC_WRITEABLE | PC_USER | PC_STATIC
push 0FFFFFFFFh // Keep all previous bits
push dword ptr [np] // dword ptr [mbi+0Ch] # of pages
push dword ptr [fp] // dword ptr [ped] page #
push 1000Dh // _PageModifyPermissions (win32_service_table #)
call dword ptr [vxdcall] // VxDCall0
end;
result:=not IsBadWritePtr(pointer(fa),np*4096);
end;
except end;
end;


///// Спасибо Madshi, далее продолжим без него


procedure Patch(Buf: PChar; NewF, OldF: pointer); //пишет редирект на NewF в указанном месте (OldF); Предыдущие 6 байт заголовка сохраняются в Buf
var
q, jto: DWORD;
instr: byte;
begin
ReadProcessMemory(GetCurrentProcess, OldF, Buf, 6, q);

instr:=$E9; // JMP
WriteProcessMemory(GetCurrentProcess, OldF, @instr, 1, q);
jto:=-Dword(OldF)+dword(NewF)-5;
WriteProcessMemory(GetCurrentProcess, pointer(Dword(OldF)+1), @jto, 4, q);
end;

procedure UnPatch(Buf: PChar; NewF, OldF: pointer);
begin
WriteProcessMemory(GetCurrentProcess, OldF, Buf, 6, q);
end;


function NewWinExec(cmd: pchar; scmd: DWORD):DWORD; stdcall; //функция, которая определит возможность выполнения WinExec
var
tmp: pchar;
dd: pointer;
begin
if 1=1 then // if True then
begin
beep(500,500);

UnprotectExportTable(FinalFunctionAddress(@WinExec));
dd:=pointer($9EFF0200); //здесь находятся оригинальные 6 байт WinExec'а
GetMem(tmp, 6);
ReadProcessMemory(GetCurrentProcess, FinalFunctionAddress(@WinExec), tmp, 6, q); //сохраняем подправленный заголовок (ибо мы его убираем только на время)
WriteProcessMemory(GetCurrentProcess, FinalFunctionAddress(@WinExec), dd, 6, q); //восстанавливаем оригинальные 6 байт заголовка

beep(300,500); //по-больше звука =)
result:=WinExec(cmd, scmd);

WriteProcessMemory(GetCurrentProcess, FinalFunctionAddress(@WinExec), tmp, 6, q); //ставим перехватчик обратно
FreeMem(tmp);
end;
end;

function Interceptor1: dword; stdcall; assembler; //этод код будет исполнять любой процесс, который вызовет WinExec. Поэтому мы его сделали самодостаточным.
{на абстрактном Pas'e это выглядело бы так:
var
hmod: dword;
begin
hmod:=GetModuleHandle('???\p1.dll');
if hmod=0 then hmod:=LoadLibrary('???\p1.dll');
hmod:=GetProcAddress(hmod, 1); // т.е. берем первую экспортируемую нами ф-цию (NewWinExec)
goto hmod;
end;
}

asm
push $9eff0000 //PModuleName
call dword ptr[$9eff0300] //@GetModuleHandle
cmp eax, 0
jne @@c
push $9eff0000 //PModuleName
call dword ptr[$9eff0304] //@LoadLibrary
cmp eax, 0
jne @@c
@@e:
ret 8 //prevent crash if something went wrong
@@c:
push 1 //NewWinExec index (=1)
push eax //адрес найденой процедуры (NewWinExec)
call dword ptr[$9eff0308] //@GetProcAddress
cmp eax, 0
je @@e
push eax
ret //JMP NewWinExec
end;

procedure EndOfCode; begin end; //как-бы отметка, где кончается код наших перехватчиков.

procedure Load; stdcall;
var
c: dword;
by: pchar;
begin
UnprotectExportTable(FinalFunctionAddress(@WinExec)); //делаем память доступной для записи
cs:=cardinal(@EndOfCode)-cardinal(@Interceptor1); // размер кода, который мы сделаем общим
VirtualAlloc(pointer($9EFF0000),cs,MEM_COMMIT or MEM_RESERVE,PAGE_EXECUTE_READWRITE);

WinExecAdr:=pointer($9EFF0400);

Move(pointer(@Interceptor1)^,WinExecAdr^,cs); //записываем код распределителя

WinExecAdr:=pointer($9EFF0000);
GetMem(by, 512);
c:=GetModuleFileName(hInstance, by, 512);
by[c+1]:=#0;
Move(by^, WinExecAdr^, 512); //записываем наше имя в $9EFF0000
FreeMem(by);

WinExecAdr:=pointer($9EFF0400); //правим заголовок WinExec'а
Patch(pointer($9EFF0200), WinExecAdr, FinalFunctionAddress(@WinExec));

WinExecAdr:=pointer($9EFF0200); //помещаем оригинальный заголовок в общую память, дабы перехватчики могли их использовать для "временного снятия" самих себя
Move(pointer($9EFF0200)^,WinExecAdr^,6);

WinExecAdr:=pointer($9EFF0300); //заносим адреса некоторых API ф-ций
c:=DWORD(FinalFunctionAddress(@GetModuleHandle));
Move(c,WinExecAdr^,4);
WinExecAdr:=pointer($9EFF0304);
c:=DWORD(FinalFunctionAddress(@LoadLibrary));
Move(c,WinExecAdr^,4);
WinExecAdr:=pointer($9EFF0308);
c:=DWORD(FinalFunctionAddress(@GetProcAddress));
Move(c,WinExecAdr^,4);


Beep(500, 500);
end;

procedure Unload; stdcall;
begin
UnPatch(pointer($9EFF0200), nil, FinalFunctionAddress(@WinExec));
VirtualFree(pointer($9EFF0000),0, MEM_RELEASE);
Beep(1000, 1000);
end;

exports
NewWinExec index 1,
Load name 'Load',
Unload name 'Unload';
end.


Copyright
Перепечатка статьи разрешается только с уведомления автора и ссылкой на первоисточник.

Примеры
Разобранный выше пример
Top100 Rambler's Top100
Hosted by uCoz