Показать сообщение отдельно
Старый 14.12.2006, 15:56   #10
Deementor
ViP
 
Пол:Мужской
Регистрация: 17.09.2006
Сообщений: 1,182
Репутация: 1592
По умолчанию Re: "Формат" винта из под винды

Теперь мы можем не используя драйвер выполнять участки кода в нулевом кольце. Но что мы можем сделать? Работая в драйвере мы можем использовать функции экспортируемые ядром и другими драйверами, и с помощью них взаимодействовать с системой, а здесь наш код лишен такой возможности по той причине, что мы не знаем адреса необходимых нам функций. Также, при работе в драйвере мы имеем другое содержимое сегментных регистров, которое заполняется при выполнении системного вызова, а в данном случае мы имеем те же регистры, что и в режиме пользователя. Поэтому для выполнения каких-либо практических задач с применением этого метода нам нужно получить адреса необходимых нам функций ядра и изменять содержимое регистра FS, через который осуществляется доступ к системным структурам связанным с текущим процессом. В режиме пользователя и в режиме ядра этот регистр указывает на совершенно разные структуры. Для получения адресов API ядра можно использовать следующую методику: загрузить ядро (ntoskrnl.exe) с помощью LoadLibraryEx с установленным флагом DONT_RESOLVE_DLL_REFERENCES в наше адресное пространство, функция возвратит нам адрес MZ заголовка ядра подгруженного в User Space. Теперь с помощью GetProcAddress мы можем получить адрес интересующей нас функции в User Space. Разность адреса функции и адреса подгруженного ядра будет смещением начала функции в ядре. Теперь нам нужно узнать адрес загрузки ядра в Kernel Space и прибавить к нему полученное смещение, и у нас будет адрес нужной нам функции в Kernel Space. Определить адрес загрузки системного модуля в Kernel Space можно путем вызова Nativa API функции ZwQuerySystemInformation с классом SystemModuleInformation, функция вернет нам список загруженных модулей и информацию о них. Для получения адреса загрузки модуля в Kernel Space можно использовать следующую функцию:
{
Получение виртуального адреса для модуля
загруженного в системное адресное пространство.
}
function GetKernelModuleAddress(pModuleName: PChar): dword;
var
Info: PSYSTEM_MODULE_INFORMATION_EX;
R: dword;
begin
Result := 0;
Info := GetInfoTable(SystemModuleInformation);
for r := 0 to Info^.ModulesCount do
if lstrcmpi(PChar(dword(@Info^.Modules[r].ImageName)
+ Info^.Modules[r].ModuleNameOffset), pModuleName) = 0 then
begin
Result := dword(Info^.Modules[r].Base);
break;
end;
VirtualFree(Info, 0, MEM_RELEASE);
end;
После чего адрес нужной нам функции ядра вычисляется следующим образом:
{ Получение адреса ядерной API в системном адресном пространстве. }
Function GetKernelProcAddress(lpProcName: PChar): dword;
var
uProc: dword;
begin
uProc := dword(GetProcAddress(dKernelBase, lpProcName));
if uProc > 0 then Result := (uProc - dKernelBase) + KernelBase
else Result := 0;
end;
где dKernelBase - адрес ядра загруженного в User Space, KernelBase - адрес ядра в Kernel Space. Теперь, нам нужно получить таким способом адреса всех необходимых функций и сохранить их в глобальных переменных, после чего их можно будет легко вызывать из Ring0 кода. Также не следует забывать а необходимости перезагрузки регистра FS, что делается следующим кодом:
{ перезагрузка регистра FS и вызов Ring0 кода }
procedure Ring0CallProc;
asm
cli
pushad
mov di, $30
mov fs, di
call Ring0ProcAdr
mov di, $3B
mov fs, di
popad
sti
retf
end;
Попробуем теперь найти применение описанной методике. Для начала напишем функцию Ring0CopyMemory, которая будет копировать участки памяти в нулевом кольце, что позволит нам работать с памятью ядра. Но неприятная особенность копирования памяти в нулевом кольце состоит в том, что попытка доступа к невыделенной памяти неизбежно ведет к синему экрану, поэтому перед копированием желательно проверить переданные указатели на валидность. Для этого в ядре существует функция MmIsAddressValid, которая принимает проверяемый указатель, и возвращает в случае валидности указателя EAX > 0, или 0, если память невыделенна. Получим адрес этой функции по вышеописанной методике и занесем его в переменную AdrMmIsValid. После этого функция копирования памяти будет иметь следующий вид:
{
Копирование участка памяти из 0 кольца.
Можно работать с памятью ядра.
ВНИМАНИЕ! некорректная запись в память ядра приведет к падению системы!
}
Procedure Ring0CopyMemory(Source, Destination: pointer; Size: dword);
var
Data : packed record
Src: pointer;
Dst: pointer;
Size: dword;
end;

Procedure Ring0Call;
asm
//проверка адресов
mov ebx, eax
mov eax, [ebx]
push eax
call AdrMmIsValid
test eax, eax
jz @Exit
mov eax, [ebx]
add eax, [ebx + $08]
push eax
call AdrMmIsValid
test eax, eax
jz @Exit
mov eax, [ebx + $04]
push eax
call AdrMmIsValid
test eax, eax
jz @Exit
mov eax, [ebx + $04]
add eax, [ebx + $08]
push eax
call AdrMmIsValid
test eax, eax
jz @Exit
//копирование
mov esi, [ebx]
mov edi, [ebx + $04]
mov ecx, [ebx + $08]
rep movsb
@Exit:
ret
end;

begin
Data.Src := Source;
Data.Dst := Destination;
Data.Size := Size;
VirtualLock(Source, Size);
VirtualLock(Destination, Size);
CallRing0(@Ring0Call, @Data);
VirtualUnlock(Source, Size);
VirtualUnlock(Destination, Size);
end;
Также следует обратить внимание на то, что перед копированием делается попытка закрепить копируемые страницы в памяти, так как при их отсутствии, из за того, что переход в нулевое кольцо совершен без уведомления системы и изменения соответствующих системных структур, страничная ошибка может быть неправильно обработана и подкачка не выполнена, что приведет к падению системы. Естественно, после копирования закрепленные страницы следует разблокировать. При применении для перехода в нулевое кольцо шлюза вызова, злоупотреблять этой процедурой не следует, так как при невозможности закрепления копируемых областей в памяти мы можем получить нестабильность в работе процедуры, и как следствие - частые синие экраны.
Теперь нам доступна память ядра, попробуем поработать с некоторыми ядерными структурами. Для начала, попробуем получить список всех процессов в системе. Казалось бы, зачем для этого лазить в нулевое кольцо, ведь есть ToolHelp API, которые весьма просты в использовании. Но недостаток перечисления процессов через API в том, что они могут быть перехвачены и процесс может быть скрыт. Сейчас мы попробуем получить список процессов на самом низком уровне - из структур ядра.
Каждый процесс в памяти ядра представлен структурой EPROCESS, которая хранит в себе информацию о параметрах этого процесса, а также содержит в себе ссылки на структуры предыдущего и следующего процессов в списке. Эта структура имеет различный формат для разных версий Windows NT, поэтому я не буду приводить её целиком, а рассмотрю только важные её части. Допустим, мы уже получили указатель на структуру EPROCESS интересующего нас процесса, теперь нам нужно извлеч из нее имя процесса, его Process Id и id родительского процесса. Для Windows XP имя процесса находится в структуре EPROCESS по смещению 174h и занимает 16 байт в ANSI кодировке, ProcessId - имеет смещение 84h и размер dword, Parrent Process ID имеет смещение 14Ch. В Windows 2000 эти данные имеют соответственно смещения 1FCh, 9Сh и 1С8h. В структуре EPROCESS каждого процесса содержатся указатели на структуры следующего и предыдущего процессов. Список структур EPROCESS всех процессов в системе начинается с переменной ядра PsActiveProcessesLink, которая ядром не экспортируется, но её можно получить с помощью анализа двухсвязного списка структур EPROCESS.
Deementor вне форума
 
Ответить с цитированием Вверх
 
Время генерации страницы 0.02176 секунды с 10 запросами