_Great_
21.08.2007, 22:59
Article: Коммуникация с драйвером через прерывания
Author: Great
Date: 21.08.2007
Lang: C++ / ASM
Некоторые личности сильно задолбали вопросами как можно вызвать код драйвера из юзермодного приложения, причем не используя DeviceIoControl и вообще не создавая девайсов.
Ответ простой - зарегистрировать в системе свое прерывание. Про палевность этого метода промолчу - в конце концов, спалить из ринг0 можно все, что угодно.
Итак, мы собрались установить новый обработчик в таблице дескрипторов прерываний. Подробно о прерываниях и о том, как это сделать, я описал в своей статье "Прерывания в защищенном режиме процессора IA-32", поэтому подробно останавливаться на этом не буду.
Пусть наш желаемый вектор равен F3. Тогда в юзермоде можно будет выполнить что-то типа
mov eax, 2 ; номер функции
call sys_stub
для вызова функции 2 нашего прерывания (пусть оно имеет много функций), где sys_stub состоит из
sys_stub:
int 0xF3
ret
Удобно, не правда ли, чем создавать девайс, а потом его открывать и делать DeviceIoControl?
Установку прерывания мы осуществим функцией ConnectSoftwareInterrupt(), которая получит регистр IDTR и создаст запись о дескрипторе прерывания.
Выглядит эта функция следующим образом:
PVOID
ConnectSoftwareInterrupt(
IN BYTE Interrupt,
IN PVOID Handler
)
/*
Arguments:
Interrupt - Number of interrupt to connect to
Handler - Address of handler routine
Return Value:
Address of old handler
--*/
{
DWORD OldCr0;
DWORD OldHandler;
IDTR Idtr;
//
// Disable WP and hardware interrupts; get IDTR
//
OldCr0 = DisableWP();
__asm pushfd;
__asm cli;
__asm sidt [Idtr];
//
// Fill IDT entry
//
OldHandler = Idtr.Table[Interrupt].OffsetLow | ( Idtr.Table[Interrupt].OffsetHigh << 16 );
Idtr.Table[Interrupt].OffsetLow = (WORD) ( (DWORD)Handler ) & 0xFFFF;
Idtr.Table[Interrupt].OffsetHigh = (WORD) ( (DWORD)Handler >> 16 ) & 0xFFFF;
Idtr.Table[Interrupt].Present = 1;
Idtr.Table[Interrupt].Default = 1;
Idtr.Table[Interrupt].DPL = 3;
Idtr.Table[Interrupt].Selector = 0x0008;
//
// Restore hardware interrupts and CR0 value
//
__asm popfd;
RestoreWP(OldCr0);
return (PVOID) OldHandler;
}
Эта функция сперва отключает бит WP в регистре CR0, чтобы разрешить запись на системные страницы, на коих распологается IDT - мы ведь собираемся ее модифицировать.
Далее запрещаются прерывания - установка вектора должна быть атомарной операцией. Потом мы получаем регистр IDTR и модифицируем запись в IDT, чтобы она указывала на новый обработчик.
После этих нехитрых манипуляций мы восстаналиваем запрещенные прерывания, восстанавливаем старое значение CR0 и возвращаем адрес старого обработчика.
После этого нетрудно предположить вероятный код DriverEntry и DriverUnload:
#define OUR_INT_NO 0xF3
PVOID OldHandler;
void DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
DPRINT ("[~] DriverUnload()\n");
IntConnectSoftwareInterrupt( OUR_INT_NO, OldHandler );
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
DriverObject->DriverUnload = DriverUnload;
DPRINT("[~] DriverEntry()\n");
OldHandler = IntConnectSoftwareInterrupt( OUR_INT_NO, NewHandler );
DPRINT("[+] Driver initialization successful\n");
return STATUS_SUCCESS;
}
Далее мы определим две функции, которые будут мапировать и размапировать пользовательский буфер - нам не пойдет прямая работа с юзермодными адресами, т.к. код может содержать повышения и понижения IRQL, что немедленно скажется в виде бсода, если соответствующие адреса были выгружены в своп.
Рассмотрим функцию MapUserBuffer подробнее. Чтобы осуществить задуманное, нам придется заблокировать пользовательский буфер в физической памяти - это делает API MmProbeAndLockPages() и отмапировать его в системное адресное пространство (>2 гб) с помощью API MmMapLockedPagesSpecifyCache.
Можно было, конечно, ограничиться блокированием буфера в физической памяти - все равно ошибка страницы не возникнет, т.к. диспетчер памяти не посмеет выгрузить заблокированный буффер. Но некоторые API ядра не любят, когда адреса аргументов лежат ниже 2 гб, поэтому для пущей безопасности, наглядности и важности отмапируем буфер в системное адресное пространство.
Важно понимать, что в данном случае создается вторая проекция того же буфера - если мы изменим значение по полученному отмапированному системному адресу, изменится и пользовательский буфер - поддержка проекций буферов в ОС Windows реализована совершенно прозрачно для нас.
Обе эти API MmProbeAndLockPages и MmMapLockedPagesSpecifyCache требуют, чтобы буфер описывала структура MDL (Memory Descriptor List - Описатель Участка Памяти, перевод не дословный). Эту структуру можно создать функцией IoAllocateMdl, передав ей в качестве первых двух аргументов начало буфера и его длину. Освобождается эта структура вызовом IoFreeMdl после размапирования.
Api MmProbeAndLockPages в случае успеха блокирует страницы буфера в физической памяти (ОЗУ). Но в случае неудачи она выбрасывает исключение, которое нужно поймать в блоке __try/__except и обработать - мы просто уничтожим MDL и вернем NULL, идентифицируя таким образом ошибку.
Ну и последующий вызов MmMapLockedPagesSpecifyCache создает проекцию буфера на системные адреса, возвращая адрес проекции для последуюзего использования.
Функция UnmapUserBuffer() проделывает противоположные операции - уничтожается проекция через MmUnmapLockedPages, снимается блокировка страниц через MmUnlockPages и уничтожается MDL через IoFreeMdl.
Теперь напишем обработчик нашего прерывания. Вот мы попали в начало обработчика.. сначала не помешало бы перезагрузить селекторы ds,es,fs их соответствующими значениями для ring0:
DWORD FunctionNumber, Arguments, ArgumentsLength;
__declspec(naked) void NewHandler( void )
{
// Мы в обработчике прерывания. Инициализируем селекторы значениями селекторов ринг0 сегментов
__asm {
push fs
push es
push ds
push 0x30
pop fs
push 0x23
pop ds
push 0x23
pop es
После этого можно получить параметры прерывания, которые юзермодный код должен был передать в трех регистрах eax, ecx, edx:
//
// Получаем параметры
//
// При вызове прерывания юзермодный код должен поместить в регистры:
// EAX = номер функции
// ECX = размер аргументов
// EDX = указатель на аргументы
//
mov FunctionNumber, eax
mov ArgumentsLength, ecx
mov Arguments, edx
}
Теперь все готово, чтобы обработать прерывание. Вынесем весь код обработки (с логической точки зрения) в функцию ProcessInterrupt(), а здесь лишь вызовем ее и выполним возврат из прерывания:
// Весь код обработки будет ТАМ
ProcessInterrupt( );
// Восстанавливаем старые селекторы и выходим из прерывания
__asm {
pop ds
pop es
pop fs
iretd
}
}
Что ж. С технической частью покончено. Осталось включить фантазию и написать код для обработки прерывания. Мы поступим следующим образом: юзермодный код должен передавать в регистрах eax,ecx,edx параметры - в комментариях выше написано, что должно содержаться в каждом регистре.
Мы реализуем три функции с номерами 0, 1 и 2 для пользовательского кода. Функция 0 будет просто показывать сообщение и всё, функция 1 попытается прочитать аргументы, а функция 2 попробует записать число 12345678h по адресу пользовательского буфера, заданного в первом аргументе.
Естественно, аргументы и буфера нужно отмапировать по обозначенным выше причинам нашей функцией MapUserBuffer().
Приступим:
// Тут код обработчика нашего прерывания
ULONG ProcessInterrupt( )
{
ULONG Status = STATUS_UNSUCCESSFUL;
// debug break;
//debugbreak();
DPRINT("INT F3 call, FunctionNumber=0x%08x, Arguments=0x%08x, ArgumentsLength=0x%08x\n", FunctionNumber, Arguments, ArgumentsLength);
switch( FunctionNumber )
{
case 0: // Функция 0 - покажем сообщение. Аргументов нет
DPRINT("Function 0 invoked!\n");
Status = STATUS_SUCCESS;
break;
case 1: // Функция 1 - прочитаем пользовательские аргументы
{
PMDL Mdl;
PVOID MappedArgs = MapUserBuffer( (PVOID)Arguments, ArgumentsLength, FALSE /*read*/, &Mdl );
DPRINT("Function 1 invoked, arguments mapped at address 0x%08x\n", MappedArgs);
if( MappedArgs )
{
if( ArgumentsLength >= 4 )
{
for( ULONG i=0; i<ArgumentsLength; i+=4 ) {
DPRINT("Argument[%d]: 0x%08x\n", i/4, ((ULONG*)MappedArgs)[i/4]);
}
Status = STATUS_SUCCESS;
}
else
{
DPRINT("Too small args\n");
Status = STATUS_INFO_LENGTH_MISMATCH;
}
UnmapUserBuffer( MappedArgs, Mdl );
}
else
{
DPRINT("Arguments mapping failed\n");
Status = STATUS_ACCESS_VIOLATION;
}
break;
}
case 2: // Функция 2 - запишем чтонибудь в пользовательский буфер, его адрес задается первым аргументом, а длина - вторым
{
if( ArgumentsLength != sizeof(ULONG)*2 ) // не 2 аргумента?
{
DPRINT("Arguments length mismatch: 0x%08x\n", ArgumentsLength);
Status = STATUS_INFO_LENGTH_MISMATCH;
break;
}
PMDL ArgMdl;
PVOID MappedArgs = MapUserBuffer( (PVOID)Arguments, ArgumentsLength, FALSE /*read*/, &ArgMdl );
if( MappedArgs )
{
DPRINT("Arguments mapped at address 0x%08x\n", MappedArgs);
PMDL BufMdl;
PVOID MappedBuffer = MapUserBuffer( ((PVOID*)MappedArgs)[0], ((ULONG*)MappedArgs)[1], TRUE /*write*/, &BufMdl );
if( MappedBuffer )
{
DPRINT("Buffer mapped at address 0x%08x\n", MappedBuffer);
if( ((ULONG*)MappedArgs)[1] >= 4 ) // длина буфера больше 4? да - пишем дворд
{
*(ULONG*)(((ULONG*)MappedArgs)[0]) = 0x12345678;
DPRINT("Data written\n");
Status = STATUS_SUCCESS;
}
else
{
DPRINT("Too small args\n");
Status = STATUS_INFO_LENGTH_MISMATCH;
}
UnmapUserBuffer( MappedBuffer, BufMdl );
}
else
{
DPRINT("Buffer mapping failed\n");
Status = STATUS_ACCESS_VIOLATION;
}
UnmapUserBuffer( MappedArgs, ArgMdl );
}
break;
}
default:
DPRINT("Unknown function number: 0x%08x\n", FunctionNumber);
Status = STATUS_INVALID_PARAMETER;
}
return Status;
}
Поскольку этот код содержит в основном логику, то в технических пояснениях он, я думаю, не нуждается.
Рассмотрим теперь пример пользовательского приложения:
include 'win32ax.inc'
.data
buffer rb 10
.code
callstub:
int 0xF3
ret
start:
; Function 0 test
xor eax, eax
xor ecx, ecx
xor edx, edx
call callstub
; Function 1 test
mov eax, 1
mov ecx, 8
push 0xabcdef01 ; arg2
push 0x12345678 ; arg1
mov edx, esp
call callstub
add esp, 8
; Function 2 test [illegal]
mov eax, 2
mov ecx, 0 ; too short args
push 0
mov edx, esp
call callstub
add esp, 4
; Function 2 test [legal]
mov eax, 2
mov ecx, 8
push 10
push buffer
mov edx, esp
call callstub
add esp, 8
ret
.end start
Функция callstub осуществляет вызов прерывания - я вынес это в отдельную функцию в связи с тем, что OllyDbg, которым мы соберемся отлаживать эту программу, плохо дружит с инструкцией INT, не устанавливая бряка на следующую за ней команду, поэтому придется делать step over через инструкцию call callstub.
В данном коде осуществляется:
1) вызов функции 0, которая только покажет сообщение и все
2) вызов функции 1, которая покажет переданные аргументы. Мы передаем 0x12345678 и 0xabcdef01
3) заведомо неправильный вызов функции 2 - ей нужно передать два параметра (адрес и длина буфера, куда записать число 12345678h), а мы передаем только один. Поэтому она вернет нам в регистре EAX статус STATUS_INFO_LENGTH_MISMATCH, сообщая о том, что аргументов слишком мало для нее.
4) корректный вызов функции 2 с передачей ей двух аргументов - адреса буфера и его длины. Обработчик записывает в первые 4 байта буфера дворд 12345678, что можно непосредственно наблюдать после выполнения этой функции в окне OllyDbg:
00401000 78 56 34 12 xV4
На этом придется завершить сий рассказ и попрощаться. Удачного компилирования и чтобы без BSoD'ов! ;)
PS. Исходники прилагаются
Author: Great
Date: 21.08.2007
Lang: C++ / ASM
Некоторые личности сильно задолбали вопросами как можно вызвать код драйвера из юзермодного приложения, причем не используя DeviceIoControl и вообще не создавая девайсов.
Ответ простой - зарегистрировать в системе свое прерывание. Про палевность этого метода промолчу - в конце концов, спалить из ринг0 можно все, что угодно.
Итак, мы собрались установить новый обработчик в таблице дескрипторов прерываний. Подробно о прерываниях и о том, как это сделать, я описал в своей статье "Прерывания в защищенном режиме процессора IA-32", поэтому подробно останавливаться на этом не буду.
Пусть наш желаемый вектор равен F3. Тогда в юзермоде можно будет выполнить что-то типа
mov eax, 2 ; номер функции
call sys_stub
для вызова функции 2 нашего прерывания (пусть оно имеет много функций), где sys_stub состоит из
sys_stub:
int 0xF3
ret
Удобно, не правда ли, чем создавать девайс, а потом его открывать и делать DeviceIoControl?
Установку прерывания мы осуществим функцией ConnectSoftwareInterrupt(), которая получит регистр IDTR и создаст запись о дескрипторе прерывания.
Выглядит эта функция следующим образом:
PVOID
ConnectSoftwareInterrupt(
IN BYTE Interrupt,
IN PVOID Handler
)
/*
Arguments:
Interrupt - Number of interrupt to connect to
Handler - Address of handler routine
Return Value:
Address of old handler
--*/
{
DWORD OldCr0;
DWORD OldHandler;
IDTR Idtr;
//
// Disable WP and hardware interrupts; get IDTR
//
OldCr0 = DisableWP();
__asm pushfd;
__asm cli;
__asm sidt [Idtr];
//
// Fill IDT entry
//
OldHandler = Idtr.Table[Interrupt].OffsetLow | ( Idtr.Table[Interrupt].OffsetHigh << 16 );
Idtr.Table[Interrupt].OffsetLow = (WORD) ( (DWORD)Handler ) & 0xFFFF;
Idtr.Table[Interrupt].OffsetHigh = (WORD) ( (DWORD)Handler >> 16 ) & 0xFFFF;
Idtr.Table[Interrupt].Present = 1;
Idtr.Table[Interrupt].Default = 1;
Idtr.Table[Interrupt].DPL = 3;
Idtr.Table[Interrupt].Selector = 0x0008;
//
// Restore hardware interrupts and CR0 value
//
__asm popfd;
RestoreWP(OldCr0);
return (PVOID) OldHandler;
}
Эта функция сперва отключает бит WP в регистре CR0, чтобы разрешить запись на системные страницы, на коих распологается IDT - мы ведь собираемся ее модифицировать.
Далее запрещаются прерывания - установка вектора должна быть атомарной операцией. Потом мы получаем регистр IDTR и модифицируем запись в IDT, чтобы она указывала на новый обработчик.
После этих нехитрых манипуляций мы восстаналиваем запрещенные прерывания, восстанавливаем старое значение CR0 и возвращаем адрес старого обработчика.
После этого нетрудно предположить вероятный код DriverEntry и DriverUnload:
#define OUR_INT_NO 0xF3
PVOID OldHandler;
void DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
DPRINT ("[~] DriverUnload()\n");
IntConnectSoftwareInterrupt( OUR_INT_NO, OldHandler );
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
DriverObject->DriverUnload = DriverUnload;
DPRINT("[~] DriverEntry()\n");
OldHandler = IntConnectSoftwareInterrupt( OUR_INT_NO, NewHandler );
DPRINT("[+] Driver initialization successful\n");
return STATUS_SUCCESS;
}
Далее мы определим две функции, которые будут мапировать и размапировать пользовательский буфер - нам не пойдет прямая работа с юзермодными адресами, т.к. код может содержать повышения и понижения IRQL, что немедленно скажется в виде бсода, если соответствующие адреса были выгружены в своп.
Рассмотрим функцию MapUserBuffer подробнее. Чтобы осуществить задуманное, нам придется заблокировать пользовательский буфер в физической памяти - это делает API MmProbeAndLockPages() и отмапировать его в системное адресное пространство (>2 гб) с помощью API MmMapLockedPagesSpecifyCache.
Можно было, конечно, ограничиться блокированием буфера в физической памяти - все равно ошибка страницы не возникнет, т.к. диспетчер памяти не посмеет выгрузить заблокированный буффер. Но некоторые API ядра не любят, когда адреса аргументов лежат ниже 2 гб, поэтому для пущей безопасности, наглядности и важности отмапируем буфер в системное адресное пространство.
Важно понимать, что в данном случае создается вторая проекция того же буфера - если мы изменим значение по полученному отмапированному системному адресу, изменится и пользовательский буфер - поддержка проекций буферов в ОС Windows реализована совершенно прозрачно для нас.
Обе эти API MmProbeAndLockPages и MmMapLockedPagesSpecifyCache требуют, чтобы буфер описывала структура MDL (Memory Descriptor List - Описатель Участка Памяти, перевод не дословный). Эту структуру можно создать функцией IoAllocateMdl, передав ей в качестве первых двух аргументов начало буфера и его длину. Освобождается эта структура вызовом IoFreeMdl после размапирования.
Api MmProbeAndLockPages в случае успеха блокирует страницы буфера в физической памяти (ОЗУ). Но в случае неудачи она выбрасывает исключение, которое нужно поймать в блоке __try/__except и обработать - мы просто уничтожим MDL и вернем NULL, идентифицируя таким образом ошибку.
Ну и последующий вызов MmMapLockedPagesSpecifyCache создает проекцию буфера на системные адреса, возвращая адрес проекции для последуюзего использования.
Функция UnmapUserBuffer() проделывает противоположные операции - уничтожается проекция через MmUnmapLockedPages, снимается блокировка страниц через MmUnlockPages и уничтожается MDL через IoFreeMdl.
Теперь напишем обработчик нашего прерывания. Вот мы попали в начало обработчика.. сначала не помешало бы перезагрузить селекторы ds,es,fs их соответствующими значениями для ring0:
DWORD FunctionNumber, Arguments, ArgumentsLength;
__declspec(naked) void NewHandler( void )
{
// Мы в обработчике прерывания. Инициализируем селекторы значениями селекторов ринг0 сегментов
__asm {
push fs
push es
push ds
push 0x30
pop fs
push 0x23
pop ds
push 0x23
pop es
После этого можно получить параметры прерывания, которые юзермодный код должен был передать в трех регистрах eax, ecx, edx:
//
// Получаем параметры
//
// При вызове прерывания юзермодный код должен поместить в регистры:
// EAX = номер функции
// ECX = размер аргументов
// EDX = указатель на аргументы
//
mov FunctionNumber, eax
mov ArgumentsLength, ecx
mov Arguments, edx
}
Теперь все готово, чтобы обработать прерывание. Вынесем весь код обработки (с логической точки зрения) в функцию ProcessInterrupt(), а здесь лишь вызовем ее и выполним возврат из прерывания:
// Весь код обработки будет ТАМ
ProcessInterrupt( );
// Восстанавливаем старые селекторы и выходим из прерывания
__asm {
pop ds
pop es
pop fs
iretd
}
}
Что ж. С технической частью покончено. Осталось включить фантазию и написать код для обработки прерывания. Мы поступим следующим образом: юзермодный код должен передавать в регистрах eax,ecx,edx параметры - в комментариях выше написано, что должно содержаться в каждом регистре.
Мы реализуем три функции с номерами 0, 1 и 2 для пользовательского кода. Функция 0 будет просто показывать сообщение и всё, функция 1 попытается прочитать аргументы, а функция 2 попробует записать число 12345678h по адресу пользовательского буфера, заданного в первом аргументе.
Естественно, аргументы и буфера нужно отмапировать по обозначенным выше причинам нашей функцией MapUserBuffer().
Приступим:
// Тут код обработчика нашего прерывания
ULONG ProcessInterrupt( )
{
ULONG Status = STATUS_UNSUCCESSFUL;
// debug break;
//debugbreak();
DPRINT("INT F3 call, FunctionNumber=0x%08x, Arguments=0x%08x, ArgumentsLength=0x%08x\n", FunctionNumber, Arguments, ArgumentsLength);
switch( FunctionNumber )
{
case 0: // Функция 0 - покажем сообщение. Аргументов нет
DPRINT("Function 0 invoked!\n");
Status = STATUS_SUCCESS;
break;
case 1: // Функция 1 - прочитаем пользовательские аргументы
{
PMDL Mdl;
PVOID MappedArgs = MapUserBuffer( (PVOID)Arguments, ArgumentsLength, FALSE /*read*/, &Mdl );
DPRINT("Function 1 invoked, arguments mapped at address 0x%08x\n", MappedArgs);
if( MappedArgs )
{
if( ArgumentsLength >= 4 )
{
for( ULONG i=0; i<ArgumentsLength; i+=4 ) {
DPRINT("Argument[%d]: 0x%08x\n", i/4, ((ULONG*)MappedArgs)[i/4]);
}
Status = STATUS_SUCCESS;
}
else
{
DPRINT("Too small args\n");
Status = STATUS_INFO_LENGTH_MISMATCH;
}
UnmapUserBuffer( MappedArgs, Mdl );
}
else
{
DPRINT("Arguments mapping failed\n");
Status = STATUS_ACCESS_VIOLATION;
}
break;
}
case 2: // Функция 2 - запишем чтонибудь в пользовательский буфер, его адрес задается первым аргументом, а длина - вторым
{
if( ArgumentsLength != sizeof(ULONG)*2 ) // не 2 аргумента?
{
DPRINT("Arguments length mismatch: 0x%08x\n", ArgumentsLength);
Status = STATUS_INFO_LENGTH_MISMATCH;
break;
}
PMDL ArgMdl;
PVOID MappedArgs = MapUserBuffer( (PVOID)Arguments, ArgumentsLength, FALSE /*read*/, &ArgMdl );
if( MappedArgs )
{
DPRINT("Arguments mapped at address 0x%08x\n", MappedArgs);
PMDL BufMdl;
PVOID MappedBuffer = MapUserBuffer( ((PVOID*)MappedArgs)[0], ((ULONG*)MappedArgs)[1], TRUE /*write*/, &BufMdl );
if( MappedBuffer )
{
DPRINT("Buffer mapped at address 0x%08x\n", MappedBuffer);
if( ((ULONG*)MappedArgs)[1] >= 4 ) // длина буфера больше 4? да - пишем дворд
{
*(ULONG*)(((ULONG*)MappedArgs)[0]) = 0x12345678;
DPRINT("Data written\n");
Status = STATUS_SUCCESS;
}
else
{
DPRINT("Too small args\n");
Status = STATUS_INFO_LENGTH_MISMATCH;
}
UnmapUserBuffer( MappedBuffer, BufMdl );
}
else
{
DPRINT("Buffer mapping failed\n");
Status = STATUS_ACCESS_VIOLATION;
}
UnmapUserBuffer( MappedArgs, ArgMdl );
}
break;
}
default:
DPRINT("Unknown function number: 0x%08x\n", FunctionNumber);
Status = STATUS_INVALID_PARAMETER;
}
return Status;
}
Поскольку этот код содержит в основном логику, то в технических пояснениях он, я думаю, не нуждается.
Рассмотрим теперь пример пользовательского приложения:
include 'win32ax.inc'
.data
buffer rb 10
.code
callstub:
int 0xF3
ret
start:
; Function 0 test
xor eax, eax
xor ecx, ecx
xor edx, edx
call callstub
; Function 1 test
mov eax, 1
mov ecx, 8
push 0xabcdef01 ; arg2
push 0x12345678 ; arg1
mov edx, esp
call callstub
add esp, 8
; Function 2 test [illegal]
mov eax, 2
mov ecx, 0 ; too short args
push 0
mov edx, esp
call callstub
add esp, 4
; Function 2 test [legal]
mov eax, 2
mov ecx, 8
push 10
push buffer
mov edx, esp
call callstub
add esp, 8
ret
.end start
Функция callstub осуществляет вызов прерывания - я вынес это в отдельную функцию в связи с тем, что OllyDbg, которым мы соберемся отлаживать эту программу, плохо дружит с инструкцией INT, не устанавливая бряка на следующую за ней команду, поэтому придется делать step over через инструкцию call callstub.
В данном коде осуществляется:
1) вызов функции 0, которая только покажет сообщение и все
2) вызов функции 1, которая покажет переданные аргументы. Мы передаем 0x12345678 и 0xabcdef01
3) заведомо неправильный вызов функции 2 - ей нужно передать два параметра (адрес и длина буфера, куда записать число 12345678h), а мы передаем только один. Поэтому она вернет нам в регистре EAX статус STATUS_INFO_LENGTH_MISMATCH, сообщая о том, что аргументов слишком мало для нее.
4) корректный вызов функции 2 с передачей ей двух аргументов - адреса буфера и его длины. Обработчик записывает в первые 4 байта буфера дворд 12345678, что можно непосредственно наблюдать после выполнения этой функции в окне OllyDbg:
00401000 78 56 34 12 xV4
На этом придется завершить сий рассказ и попрощаться. Удачного компилирования и чтобы без BSoD'ов! ;)
PS. Исходники прилагаются