+ Reply to Thread
Results 1 to 1 of 1

Thread: Приготовление вкусного Shellcode под Windows. Часть 0x00

  1. #1
    JKornev's Avatar

    Default Приготовление вкусного Shellcode под Windows. Часть 0x00

    Привет хакеру! Меня завут Ж0ра К0рнев, решил накидать статейку о технике разработки базонезависимого шеллкода и приземлился на вашем форуме. Тема была уложена в 2 статьи:

    Часть 0x00. Низкоуровневый шеллкод
    Часть 0x01. Высокоуровневый шеллкод (в процессе написания)

    И так ...


    Поехали

    Начнём пожалуй с определения, под шеллкодом будем понимать машинный код обладающий свойством базонезависимости и выполняющий какие-либо системные операции. Т.е. какой-то набор инструкций составляющих алгоритм, который может корректно выполнятся независимо от его текущего расположения в памяти процесса. Где пременим такой шеллкод? В основном в областях связанных с ИБ: для написания вирусов, эксплойтов, защит(упаковщики\потекторы \крипторы).

    В контексте данной статьи мы рассмотрим классический низкоуровневый шеллкод написанный на ассемблере. Отличительной чертой такого шеллкода является его маленький размер, что в некоторых ситуациях играет ключевую роль, в частности при написании эксплойтов. Будем программировать в usermode под Windows(x86).


    Подготовка к высадке

    Прежде чем начинать обгладывать особенности реализации, подготовим програмку-песочницу на С, суть которой заключается в том что она будет считывать бинарник в виртуальную память и передавать ему управление, обзавём её Loader'ом. Исходник будет представлен вместе с примерами шеллкода, в силу того что иногда шеллкод будем грузить по разному.

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


    Запуск и передача информации

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

    - Через динамическую память
    - В теле шеллкода

    Передача данных через динамическую память наверно самый простой и прозрачный способ передачи информации. Обычно если есть возможность выделить память динамически и записать туда информацию, то есть и возможность передать указатель через стек, будь то прямой вызов инструкцией call, либо CreateThread\CreateRemoteThread и т.п. Тут достаточно того чтобы создающая и читающая сторона работали с согласованной структурой. Останавливатся на этом способе мы не будем и так всё очевидно.

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


    Точкой входа будет считатся начало данных, а сами данные будут распологатся в хвосте, их мы поделим на внутренние и входные. Под внутренними будем понимать константные данные которые просто хранятся внутри шеллкода(строки и т.п.). Адрес входных данных можно легко получить расчётом:

    PHP Code:
    input_structpinput shellcode_addr shellcode_size sizeof(input_struct);//pseudocode 
    Теперь чуть изменим нашу песочницу, допишем запись в хвост шеллкода и реализуем Hellow World демонстрирующий сею передачу:




    Как видите всё бонально просто (если что-то непонятно, то ниже будет пояснение). Такая модель укладки избавляет нас от дополнительных расчётов смещений и динамического выделения памяти, которое не всегда возможно.


    Исполнение и захват системных данных

    Очевидно что в шеллкоде нельзя использовать инструкции с прямыми адресами(напр. JMP 0x004021341), поэтому используем только относительные смещения (JMP +10bytes). Для функционирования алгоритма этого вполне достаточно, но что если мы хотим передать к примеру строку из шеллкода в функцию MessageBoxA? Возникает необходимость преобразовать относительное смещение к некоторым ресурсам шеллкода непосредственно в виртуальный адрес.


    Дельта смещение
    Попав в процесс первоначально мы можем не знать о нём ничего, наша задача сориентироватся, получить нужные для работы ресурсы и выполнить заложенный алгоритм, если это конечно возможно. И начнём мы с получения делта смещения, т.е виртаульного адреса шеллкода внутри процесса. В примере выше дельта расчитывалась следующим образом:
    Code:
    entry_point:
    ...
    call get_eip			
    get_eip:
    pop ebp
    sub ebp, get_eip - entry_point
    ...
    С помощью команды call делается вызов следующей инструкции, после чего адрес возврата равный адресу текущей инструкции помещается в регистр eax и нормализуется относительно начала шеллкода. Последнее действие необходимо чтобы мы могли расчитывать смещение от начала шеллкода до внутренних данных.
    Таким образом дельта смещение открывает перед нами возможность расчёта виртуального адреса данных расположенных в собственном теле.


    Захват системной библиотеки на примере Kernel32.dll
    Теперь владея дельта смещением мы переходим ко второму этапу, захвату адреса системных библиотек, а следовательно и их функций. Для начала вполне достаточно заполучить LoadLibrary которые находятся в библиотеке Kernel32.dll. Узнав адрес расположения библиотеки мы бы смогли получить из PE заголовка адреса любых интересующих нас функций. Мне известны 2 способа заполучения адреса Kernel32.dll:

    - по статическому смещению (запоминание адреcа Kernel32)
    - через Process Environment Block(PEB)

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




    Вкратце алгоритм следующий:
    1. Сперва мы расчитываем делта смещение.
    2. Пользуясь тем, что сегментный регистр FS всегда указывает на TEB, мы оттуда узнаём адрес PEB(offset:0x30), затем обращаемся к структуре PEB_LDR_DATA, содержащей указатель на колцевй список структур LDR_MODULE. Этот список описывает модули загруженные в память и содержит их виртуальный адреса, имена и т.п. информацию. Перебирая элементы этого списка мы ищем kernel32.dll.
    3. Получив адрес библиотеки парсим PE заголовок и ищем адреса экспортируемых функций LoadLibraryA, GetProcAddress
    4. Выполняем нужные нам действия
    5. Профит

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


    Захват системы средствами Ntdll.dll
    Данная техника чуть сложнее и менее надёжна за счёт применения еще большего числа недокументированных фитч, однако она имеет полное право на жизнь. Более полное описание техники(исходник на С) вы можете найти тут.

    Для поиска адреса ntdll.dll будем использовать всё ту же технику с TEB/PEB. В качестве функции загрузки библиотеки будем использовать примитив LdrLoadDll который имеет следующую сигнатуру:

    Code:
    NTSYSAPI NTSTATUS NTAPI LdrLoadDll(
    	IN PWCHAR PathToFile OPTIONAL,
    	IN ULONG Flags OPTIONAL,
    	IN PUNICODE_STRING ModuleFileName,
    	OUT PHANDLE ModuleHandle );
    Поскольку функцию аналог GetProcAddress мы уже разработали в примере выше, то теперь нам абсолютно не нужна библиотека Kernel32.dll.




    Вот собственно мы и превратили наш hello world в более менее полноценный шеллкод с плюшками.


    Несколько слов об оптимизации размера
    Асм код представленный в примерах выше (~500kb), на самом деле является очень большим для шеллкода, его можно сократить, но я не стал этого делать в силу того что читабельность такого кода очень тяжелая. Поэтому напишу пару строк об оптимизации размера. В отличии от высокоуровневых компиляторов, NASM делает ровно то что ему скажут без каких-либо супер оптимизаций. Мы можем уменьшить код:

    1. Алгоритмически, простые компактные алгоритмы без повторений и лишник проверок
    2. На уровне опкодов, выбирая команды с более маланьким размером
    3. На уровне данных

    1 и 2 пункты думаю всем ясны, а вот про данные немного расскажу подробнее. Например для того чтобы сократить место отведённое под названия WinAPI функций часто используются их хеши. Поскольку алгоритм функции GetProcAddress мы посути реализовали когда искали адреса из Kernel32.dll в ручную, мы можем полностью отказатся от GetProcAddress в пользу самописанного варианта, функцию strcmp заменить на хеширование(причём даже с большой вероятностью коллизии для строк пойдёт), а строки на хеши. Еще как вариант избавления от строк экспортируемых функций, это хранение их WORD'овских Ordinal Name(см. доки по PE), по которым и осуществлять поиск.


    Завершение исполнения
    Очевидно что наш шеллкод не будет выполнятся вечно, поэтому необходимо предусмотреть вариант завершения его работы. Если работоспособность программы необходимо сохранить, то верным решением будет сохранения состояния регистров в стек, а по окончанию работы восстановление(PUSHAD\POPAD) и возврат управления. Однако в таком случае обязательно необходимо соблюдать целостность стека, иначе результат получится не предсказуемый. Если восстановления работы программы невозможно, то лучше всего воспользоватся несуществующей инструкцией или JMP в 0x00000000.


    Подведём итоги

    У такого шеллкода есть свои достоинства и недостатки, которые вобщем-то и определяют его область применения, выделим их:

    Достоинства:
    - Возможность максимального уменьшения размера, до экстремальности
    - Возможность составления произвольной структуры

    Недостатки:
    - Сложность разработки, особенно после оптимизации хорошо если вы сможете понять что написали :)
    - Сложность отладки, из-за отсутствия IDE необходимо либо искуственное воссоздание условий выполнения, либо аттачить дебагер к целевому процессу
    - Непереносимость на другие архитектуры

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

    Спасибо за внимание.
    High tech, low life

  2. 2 пользователя(ей) сказали cпасибо:
    root (01-08-2015) ximera (23-12-2015)
+ Reply to Thread

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
All times are GMT. The time now is 01:25
vBulletin® Copyright ©2000 - 2018
www.reverse4you.org