Вступление
Предлполагается, что читатель уже ознакомился с первой и второй частью (правильнее даже сказать вариантами) данной статьи. В этой части, мы рассмотрим пример 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=nilthen 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); exceptend; end;
function GetProcAddress_(module: cardinal; ord: cardinal) : pointer; var exp : TPImageExportDirectory; begin
result:=nil; try
exp:=GetModuleExportDirectory(module); if exp<>nilthen with exp^ do if ordthen
result:=pointer(module+TPACardinal(module+AddressOfFunctions)^[ord]); exceptend; 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); ifnot result thenbegin
// Get undocumented VxDCall procedure
vxdcall:=GetProcAddress_(GetModuleHandle(kernel32),1); if @vxdcall=nilthen 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; exceptend; 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);
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 байт заголовка
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; beginend; //как-бы отметка, где кончается код наших перехватчиков.
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($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);