Как узнать версию dll

Обновлено: 06.07.2024

В процессе отладки в Visual Studio окно Модули отображает список используемых приложением библиотек DLL и исполняемых файлов ( .exe), а также сведения о них.

Окно "Модули" недоступно при отладке SQL и скриптов.

Использование окна модулей

Чтобы открыть окно "Модули" во время отладки, выберите Отладка > Окна > Модули или нажмите клавиши CTRL+ALT+U.

По умолчанию модули в окне Модули упорядочены в порядке загрузки. Чтобы выполнить сортировку по любому столбцу окна, щелкните заголовок соответствующего столбца.

Загрузить символы

В столбце Состояние символов в окне Модули показано, для каких модулей загружены отладочные символы. Если здесь указано состояние Загрузка символов пропущена, Невозможно найти или открыть PDB-файл или Загрузка отключена параметром включения и исключения, вы можете загрузить символы вручную. Дополнительные сведения о загрузке и использовании символов см. в статье Указание файлов символов (.pdb) и исходных файлов.

Загрузка символов вручную

В окне Модули щелкните правой кнопкой модуль, для которого не загружены символы.

Выберите Сведения о загрузке символов, чтобы узнать, почему символы не были загружены.

Щелкните Загрузить символы, чтобы загрузить символы вручную.

Если символы не загружаются, выберите Параметры символов, чтобы открыть диалоговое окно Параметры и указать или изменить расположение для загрузки символов.

Вы можете скачать символы с общедоступных серверов символов Майкрософт или других аналогичных серверов, а также загрузить их из локальной папки на компьютере. Дополнительные сведения см. в разделе об указании расположения символов и поведения при загрузке.

Изменение параметров поведения при загрузке символов

В окне Модули щелкните правой кнопкой мыши любой модуль.

Выберите Параметры символов.

Выберите Загрузить все символы или выберите конкретные модули.

Изменение поведения загрузки символов для конкретного модуля

В окне Модули щелкните правой кнопкой мыши требуемый модуль.

В контекстном меню установите или снимите флажок Всегда загружать автоматически. Изменения вступят в силу при следующем сеансе отладки.

у нас есть пакетный файл, который устанавливает несколько программ в рамках установки разработчиков. Это выполняется периодически, когда мы получаем новые версии используемых компонентов. Поэтому было бы неплохо установить, если версии разные.

в командной строке я могу запустить это и вернуть установленную версию:

который дает выход Version=12.1.369.0 .

однако, когда я помещаю это в пакетный файл, подобный этому, и пытаюсь извлечь версия:

Я получаю ответ Common was unexpected at this time.

что я пропустила?

у вас есть набор неуместных двойных кавычек, а также дополнительный ( .

WMIC использует синтаксис SQL, а строки заключены в одинарные кавычки.Внутренние одинарные кавычки не мешают команде, заключающей одинарные кавычки.

вы можете поместить двойные кавычки вокруг предложения WHERE (не включая ключевое слово WHERE), чтобы избежать некоторых проблем с escape в предложении for DO ().

но это может быть не совсем все решение. Ты не видишь этого. с вышеуказанным кодом, но результат фактически содержит возврат завершающей каретки (0x0D). Это связано с причудой с тем, как FOR /F обрабатывает вывод wmic unicode. Каждая строка вывода WMIC будет иметь дополнительный возврат трейлинг-каретки.

пока вы всегда получаете доступ к результату с помощью %RESULT% (нормальное расширение), тогда у вас не будет никаких проблем. Но если вам когда-либо понадобится отложенное расширение, у вас могут возникнуть проблемы, как показано ниже.

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

вот подпрограмма, которую я использую для этого в моем собственном пакетном скрипте обновления программного обеспечения:

он может получить любой атрибут файла, поддерживаемый wmic datafile get . Например, вот как вы можете получить версию файла для установленного Adobe Reader:

после этого переменная среды version будет содержать запрошенную строку версии. Если :getfattr не, version гарантированно будет unset.

трассировка выполнения теста для этого пример выглядит так (отложенное расширение уже включено, хотя это не предполагается :getfattr):

как вы можете видеть, это довольно прямо и не слишком много. Тем не менее, он на цыпочках проходит через минное поле cmd и wmic подводных камней.

во-первых, имя атрибута, который вы хотите получить, также является именем, используемым для переменной, в которой вы хотите получить результат ( version в тесте выше). Внутри подпрограммы это имя %1 , так что set %1= очищает его.

имя файла, которое вы передаете, нуждается в предварительной обработке, прежде чем его можно будет безопасно передать wmic и для этого требуется переменная оболочки, поэтому setlocal выдается, чтобы избежать топтания переменных вызывающего абонента.

f2" копирует имя в переменную среды после удаления всех окружающих двойных кавычек и расширения его до полного пути. Двойные кавычки окружают весь set аргумент для предотвращения горя, вызванного амперсандами или круглые скобки в именах путей.

wmic запросы используют SQL-подобный синтаксис, где строковые значения окружены одинарной кавычкой ' символы и \ - это побег, который подавляет любое особое значение следующего символа. Поскольку оба они являются законными в именах путей Windows, все вхождения либо нуждаются в \ префикс. set "name=%name:\=\%" escapes встроенные обратные косые черты и '%name:'=\'%' построить в wmic командная строка экранирует встроенные одинарные кавычки и добавляет требуются окружающие.

cmd парсер не отключает специальную обработку между одинарными кавычками, и имя больше не имеет никаких окружающих двойных кавычек, поэтому встроенные пробелы, круглые скобки или амперсанды могут потенциально нарушать вещи. Чтобы защититься от этого, wmic всю name= аргумент получает двойную кавычку. Нет необходимости в специальной обработке для двойных кавычек уже внутри имени, потому что двойные кавычки are запрещено в именах файлов Windows так их не может быть.

на for командная строка, содержащая заканчивается @^ последовательности. The ^ служит для присоединения следующей строки в качестве полезной нагрузки внешнего ; С @ предотвращает эхо этой полезной нагрузки в трассировке выполнения, даже если Эхо включено.

это подавление Эха делается главным образом потому, что внутреннее for существует только для того, чтобы избавиться от ложных символов CR, введенных cmd багги преобразование wmic вывод из Unicode в ASCII (тот же метод, что и в ответе @dbenham), и если разрешено Эхо, эти CRs просто загрязняют трассировку с запутанными перезаписями. Как побочное преимущество, внутреннее for не будет выполнять свою собственную полезную нагрузку, когда линия передана из внешнего for содержит только CR, зависящий от версии номер которого wmic настаивает на ИСП. Внутреннее for С нагрузкой тут получить Эхо, если Эхо включено, поэтому трассировка все еще захватывает все полезные события.

что груз состоит из трех разделенных команд, которые for будет расширяться как одна командная строка перед cmd получает для обработки отдельных команд. В частности, это означает, что set "%%B" расширяется до endlocal выполняется, что ставит переменную, созданную этим set за пределами setlocal / endlocal объем и делает его доступным для абонента.

%%B всегда будет расширяться в формате name=value из-за /format:list переключатель перешел в wmic ; имя будет таким же, как указано в get глагол, и вот как имя, которое вы передаете, в конечном итоге выбирает переменную, которую вы возвращаете. Весь name=value до set цитируется в случае, если запрошенный атрибут содержит специальные символы оболочки. Это делает: getfattr безопасным, но вы можете использовать !отложено! расширение, а не % преждевременное % расширение везде, где вы на самом деле используйте переменную, которую он возвращает вам.

на & goto :eof на этой же строке разрывается от обоих for петли и возвращается к: вызывающий getfattr, как только внутренний на самом деле что-то делает, на случай, если вы передадите какое-то странное имя и wmic get производит больше чем одну non-пустую линию выхода.

последние три строки только когда-либо работать, если wmic не производит никакого вывода вообще, что и происходит, когда он терпит неудачу.

Столкнулась я с такой проблемкой — как получить версию файла (exe и dll) на java. Ведь обычными стандартными средствами определить это нельзя, а только лишь можно узнать размер, дату последней модификации и некоторые другие свойства файла. Это описано тут.
Покопавшись на форумах и всё хорошенько прогуглив — нашла два таких выхода — JNI и JNA. Остановилась на втором варианте, так как на С++ писать специальную dll ну уж очень не хотелось, хотелось всё решить с помощью java и обращаться напрямую к version.dll (C:\WINDOWS\system32).
В итоге решение сводится к такой связке GetFileVersionInfo — GetFileVersionInfoSize — VerQueryValue. Долго пришлось поразбираться с функцией VerQueryValue — сложность была в том, что версии файлы зависят от языка и необходимо было хитрым способом получить LANGANDCODEPAGE. В итоге получился такой класс:

import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;
import com.sun.jna.win32.StdCallLibrary;
import java.io.IOException;

/**
*
* @author samozvanka
*/
public class Win32GetFileInformation
//+ declare before using
private static byte [] Hexhars = "«0123456789abcdef" .getBytes();

public String FileVersion;
private String LanguageCodePage;
private PointerByReference InfoData;

public interface Win32VersionDLL extends StdCallLibrary
Win32VersionDLL INSTANCE = (Win32VersionDLL) Native.loadLibrary(
"Version" , Win32VersionDLL. class );

Integer GetFileVersionInfoSizeA( String FilePath, IntByReference Handle);

Boolean GetFileVersionInfoA( String FilePath, int Handle, int InfoSize,
PointerByReference InfoData);

Boolean VerQueryValueA(PointerByReference InfoData,
String VersionInformation, PointerByReference VersionData,
IntByReference DataSize);
>

public void Win32GetFileInformation( String FilePath) throws IOException
IntByReference unusedParam = new IntByReference();

int infoSize = Win32VersionDLL.INSTANCE.GetFileVersionInfoSizeA(FilePath, unusedParam);
if (infoSize == 0)
throw new IOException( "File does not exist or has no information." );
>

this .InfoData = new PointerByReference();

Boolean success = Win32VersionDLL.INSTANCE.GetFileVersionInfoA
(
FilePath,
unusedParam.getValue(),
infoSize,
this .InfoData
);

//+ Assert(success, "GetFileVersionInfoA in Win32GetFileInformation is failed")

PointerByReference versionDataByRef = new PointerByReference();
IntByReference dataSize = new IntByReference();
Pointer versionDataPointer = null ;

// Retrieve the language information
success = Win32VersionDLL.INSTANCE.VerQueryValueA
(
this .InfoData,
"\\VarFileInfo\\Translation" ,
versionDataByRef,
dataSize
);

//+ Assert(success, "VerQueryValueA in Win32GetFileInformation is failed")

this .LanguageCodePage = decode(codePageBytes).toUpperCase();

//// Retrieve file information
this .FileVersion = QueryValue( "FileVersion" );

private String QueryValue( String ValueName)
IntByReference dataSize = new IntByReference();
PointerByReference versionDataByRef = new PointerByReference();
Pointer versionDataPointer = null ;
Boolean success = Win32VersionDLL.INSTANCE.VerQueryValueA
(
this .InfoData,
"\\StringFileInfo\\" + this.LanguageCodePage + " \\ " + ValueName, //"
versionDataByRef,
dataSize
);

//+ Assert(success, "VerQueryValueA in Win32GetFileInformation is failed")

versionDataPointer = versionDataByRef.getValue();
if (versionDataPointer == null )
return "" ;
>
else
versionDataPointer = versionDataByRef.getValue();
return versionDataPointer.getString();
>
>

private static String decode( byte [] encodedString)
StringBuilder result = new StringBuilder (2 * encodedString.length);

for ( int i = 0; i < encodedString.length; i++)
int v = encodedString[i] & 0xff;

result.append(( char ) Hexhars[v >> 4]);
result.append(( char ) Hexhars[v & 0xf]);
>

return result.toString();
>
>

* This source code was highlighted with Source Code Highlighter .

Вдруг кому пригодится. И если есть другие какие то несложные реализации этого, то хотелось бы их узнать.


В этой статье мы рассмотрим концепцию перехвата порядка поиска динамически подключаемых библиотек (DLL hijacking) и то, как она может быть использована для достижения устойчивости (persistence) в юзерленде в системах Windows. Этот метод описан в MITER ATT&CK в разделе: «Перехват порядка поиска DLL (T1038)».

Подмена DLL может быть использована злоумышленниками для множества разных целей, но в этой статье основное внимание будет уделено достижению устойчивости с помощью приложений с автозапуском. Например, поскольку Slack и Microsoft Teams запускаются при загрузке (по умолчанию), подмена DLL в одном из этих приложений позволит злоумышленнику получить устойчивый доступ к своей цели — всякий раз, когда пользователь входит в систему.

После представления концепции DLL, порядка поиска DLL, и подмены DLL, я раскрою процесс автоматизации обнаружения возможности перехвата DLL. В этой статье будет рассказано об обнаружении путей перехвата DLL в Slack, Microsoft Teams и Visual Studio Code.

Наконец, я обнаружил несколько путей перехвата DLL, которые использовались разными приложениями, исследовал основную причину и обнаружил, что приложения, использующие определенные вызовы API Windows, подвержены перехвату DLL, когда не работают из C:\Windows\System32\ .

Я хочу поблагодарить своего коллегу Джозайю Массари ( @Airzero24 ) за то, что он первым обнаружил некоторые из этих перехватов DLL, объяснил их методологию и вдохновил меня автоматизировать обнаружение.

Что такое DLL?

DLL — это библиотека, содержащая код и данные, которые могут использоваться одновременно более чем одной программой. (Сурс)

Функциональные возможности DLL могут быть использованы приложением Windows с помощью одной из функций LoadLibrary* . Приложения могут ссылаться на DLL, разработанные специально для этих приложений, или DLL Windows, уже находящиеся на диске в System32. Разработчики могут загружать DLL из System32, чтобы использовать в своих приложениях функционал, уже реализованный в Windows, без необходимости писать этот функционал с нуля.

Порядок поиска DLL и перехват

Поскольку DLL существуют в виде файлов на диске, вы можете задаться вопросом, как приложение узнает, откуда загружать DLL? Microsoft подробно задокументировала порядок поиска DLL здесь.

Начиная с Windows XP SP2, безопасный режим поиска DLL включен по умолчанию ( HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode ). При включенном безопасном режиме порядок поиска DLL следующий:

  1. Каталог, из которого загружено приложение.
  2. Системный каталог. Используйте функцию GetSystemDirectory, чтобы получить путь к этому каталогу.
  3. 16-битный системный каталог. Функции, которая предоставляет путь к этому каталогу, нет, но в нем происходит поиск.
  4. Каталог Windows. Используйте функцию GetWindowsDirectory, чтобы получить путь к этому каталогу.
  5. Текущий каталог.
  6. Каталоги, перечисленные в переменной среды PATH. Обратите внимание, что сюда не входят пути для каждого приложения, указанные в разделе реестра App Paths. Ключ App Paths не используется при вычислении пути поиска DLL.

Если приложение не указывает, откуда загружать DLL, Windows использует порядок поиска DLL по умолчанию, приведенный выше. Первая позиция в порядке поиска DLL (каталог, из которого загружается приложение) представляет интерес для злоумышленников.

Если разработчик приложения предполагает загрузку DLL из C:\Windows\System32 , но явно не прописал это в приложении, вредоносная DLL, помещенная в каталог приложения, будет загружена раньше, чем легитимная DLL из System32. Загрузка вредоносной DLL называется подменой (или перехватом) DLL и используется злоумышленниками для загрузки вредоносного кода в доверенные/подписанные приложения.

Использование подмены DLL для достижения устойчивости

Подмена DLL может использоваться для достижения устойчивости, когда уязвимое приложение/служба запускается и вредоносная DLL размещается в уязвимом месте. Мой коллега, @Airzero24 , обнаружил подмену DLL в Microsoft OneDrive, Microsoft Teams и Slack в виде userenv.dll .

Именно эти программы стали целью перехвата, потому что по умолчанию они настроены на запуск при загрузке Windows. Это можно увидеть ниже в диспетчере задач:



Приложения Windows, настроенные на автозапуск

Чтобы проверить подмену DLL, я создал загрузчик шеллкода DLL, который запускал Cobalt Strike Beacon. Я переименовал вредоносную DLL в userenv.dll и скопировал ее в каталог уязвимого приложения. Я запустил приложение и увидел свой новый Beacon коллбек.



Cobalt Strike Beacon через перехват DLL

Используя Process Explorer, я могу проверить, действительно ли моя вредоносная DLL была загружена уязвимым приложением.



Обозреватель процессов, показывающий загруженную вредоносную DLL

Автоматическое обнаружение возможности перехвата DLL

После подтверждения ранее известного перехвата DLL, я хотел проверить, смогу ли я найти другие возможности для подмены DLL, которые можно было бы эксплуатировать.

Код, использованный в моих проверка, можно найти здесь.

На примере Slack

Чтобы начать этот процесс, я запустил Process Monitor (ProcMon) со следующими фильтрами:

Затем я запустил Slack и изучил ProcMon на предмет любых DLL, которые Slack искал, но не смог найти.



Возможные пути перехвата DLL, обнаруженные с помощью ProcMon

Я экспортировал эти данные из ProcMon в виде CSV файла для облегчения парсинга в PowerShell.

С моей текущей DLL загрузчика шелл-кода я не смог бы легко определить имена DLL, которые были успешно загружены Slack. Я создал новую DLL, которая использовала GetModuleHandleEx и GetModuleFileName для определения имени загруженной DLL и записи его в текстовый файл.

Моя следующая цель состояла в том, чтобы проанализировать CSV-файл на наличие в списке путей к DLL, просмотреть этот список, скопировать мою тестовую DLL по указанному пути, запустить целевой процесс, остановить целевой процесс и удалить тестовую DLL. Если тестовая DLL была успешно загружена, она запишет свое имя в результирующий файл.

Когда этот процесс завершится, у меня будет список возможных перехватов DLL (я надеюсь), записанных в текстовый файл.

Всю магию в моем проекте DLLHijackTest творит скрипт PowerShell. Он принимает путь к CSV-файлу, сгенерированному ProcMon, путь к вашей вредоносной DLL, путь к процессу, который вы хотите запустить, и любые аргументы, которые вы хотите передать процессу.



Параметры Get-PotentialDLLHijack



Get-PotentialDLLHijack.ps1

Через несколько минут я проверяю текстовый файл, указанный в моей «вредоносной» DLL, на предмет возможных перехватов DLL. Я обнаружил следующие возможные пути перехвата для Slack:

На примере Microsoft Teams

Выполняем описанный выше процесс еще раз:

  1. Используйте ProcMon для выявления потенциальных путей перехвата DLL, экспортируйте эти данные в виде CSV файла.
  2. Определите путь запуска процесса.
  3. Определите любые аргументы, которые вы хотите передать процессу.
  4. Запустите Get-PotentialDLLHijack.ps1 с соответствующими аргументами.
Примечание: Мне пришлось внести небольшие изменения в скрипт PowerShell, чтобы завершить Teams.exe , так как мой скрипт пытается завершить процесс, который он пытался запустить, в данном случае это Update.exe .

На примере Visual Studio Code

Повторяя описанный выше процесс, я обнаружил следующие потенциальные пути перехвата для Visual Studio Code:

Подмена общих (shared) DLL

Я заметил, что Slack, Microsoft Teams и Visual Studio Code совместно используют следующие DLL:

  • WINSTA.dll
  • LINKINFO.dll
  • ntshrui.dll
  • srvcli.dll
  • cscapi.dll

Методология: понимание путей перехвата общих DLL

Я следил за стек трейсом, когда Slack пытался загрузить WINSTA.dll , LINKINFO.dll , ntshrui.dll , srvcli.dll и cscapi.dll .

DLL с отложенной загрузкой

Я заметил сходство в стек трейсе при загрузке WINSTA.dll , LINKINFO.dll , ntshrui.dll и srvcli.dll .



Стек трейс, когда Code.exe пытается загрузить WINSTA.dll



Стек трейс, когда Teams.exe пытается загрузить LINKINFO.dll ,



Стек трейс, когда Slack пытается загрузить ntshrui.dll

Стек трейс постоянно содержит вызов _tailMerge_< dllname>_dll , delayLoadHelper2 за которым следует LdrResolveDelayLoadedAPI . Такое поведение было одинаковым для всех трех приложений.

Я определил, что это поведение связано с отложенной загрузкой DLL. Из стек трейса при загрузке WINSTA.dll я мог видеть, что модулем, ответственным за эту отложенную загрузку, был wtsapi32.dll .

Я открыл wtsapi32.dll в Ghidra и использовал Search -> For Strings -> Filter: WINSTA.dll . Двойной клик по найденной строке приведет вас к ее локации в памяти.



Строка « WINSTA.dll » в wtsapi32.dll

Кликнув правой кнопкой мыши по локации в памяти, мы сможем найти любые ссылки на этот адрес.



Ссылки на WINSTA.dll

Следуя ссылкам, мы видим, что строка WINSTA.dll передается в структуру с именем ImgDelayDescr . Глядя на документацию по этой структуре, мы можем подтвердить, что она связана с отложенной загрузкой DLL.


Эту структуру можно передать в __delayLoadHelper2 , который будет использовать LoadLibrary / GetProcAddress для загрузки указанной DLL и исправления адреса импортируемой функции в таблице адресов импорта отложенной загрузки (IAT).


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


__delayLoadHelper2 и ResolveDelayLoadedAPI в Ghidra

Отлично! Это соответствует тому, что мы видели в нашем ProcMon стек трейсе, когда Slack пытался загрузить WINSTA.dll .



__delayLoadHelper2 и ResolveDelayLoadedAPI в ProcMon.

Такое поведение было единообразно для WINSTA.dll , LINKINFO.dll , ntshrui.dll и srvcli.dll . Основным отличием каждой DLL с отложенной загрузкой была «родительская» DLL. Во всех трех приложениях:

  • wtsapi32.dll отложено загружала WINSTA.dll
  • shell32.dll отложенно загружала LINKINFO.dll
  • LINKINFO.dll отложено загружала ntshrui.dll
  • ntshrui.dll отложено загружала srvcli.dll

Подмена DLL в NetShareGetInfo и NetShareEnum

Я следил за стек трейсом, когда Slack пытался загрузить cscapi.dll и видел вызов LoadLibraryExW , который, по-видимому, исходил из srvcli.dll .



Стек трейс при загрузке cscapi.dll

Я открыл srvcli.dll в Ghidra и использовал Search -> For Strings -> Filter: cscapi.dll . Двойной клик по найденной строке и переход по ссылкам приводит к ожидаемому LoadLibrary вызову.



srvcli.dll вызывает LoadLibrary для cscapi.dll

Переименовав функцию, содержащую вызов LoadLibrary , и проследовав по ссылкам, я получил два места использования функции:



NetShareGetInfo загружает cscapi.dll

Я проверил это с помощью РоС программ, которые вызывали NetShareEnum и NetShareGetInfo :



NetShareEnum.exe загружает cscapi.dll



NetShareGetInfo.exe загружает cscapi.dll

Результаты

В Slack доступны следующие пути подмены DLL:


В Microsoft Teams доступны следующие пути подмены DLL:


В Visual Studio Code доступны следующие пути подмены DLL:


Кроме того, я обнаружил, что программы, использующие NetShareEnum и NetShareGetInfo , предоставляют возможность подмены DLL в форме cscapi.dll из-за жестко захардкоженного вызова LoadLibrary . Я подтвердил это поведение с помощью Ghidra и PoC.

Заключение

Напомним, что перехват DLL — это метод, с помощью которого злоумышленники могут повлиять на выполнение кода в подписанных/доверенных приложениях. Я создал инструменты, помогающие автоматизировать обнаружение путей перехвата DLL. Используя этот инструмент, я обнаружил пути перехвата DLL в Slack, Microsoft Teams и Visual Studio Code.

Я заметил, что пути перехвата DLL этих трех приложений частично совпадают, и исследовал причину. Я осветил свою методику понимания данного совпадения. Я узнал о DLL с отложенной загрузкой и обнаружил два вызова API, которые делают возможным перехват DLL в любой программе, которая их вызывает:

  • NetShareEnum загружает cscapi.dll
  • NetShareGetInfo загружает cscapi.dll

Ссылки

Читайте также: