c++ - wintun:注册环形缓冲区时出现ERROR_INVALID_PARAMETER

标签 c++ c windows tun wintun

我目前正在尝试使wintun驱动程序与我的程序一起使用,以进行简单的隧道传输(请参阅:https://www.wintun.net/)。
我成功找到并打开了网络设备,但是在注册缓冲区时,得到的结果是ERROR_INVALID_PARAMETER(87)。就像我说的那样,打开工作就很好,并且注册具有SYSTEM特权(如果未完成,则得到ERROR_ACCESS_DENIED(5))。
第一次尝试是对环形缓冲区进行malloc编码,但是在此之后不起作用了,我研究了OpenVPN的工作方式(是的,它增加了对Wintun的支持),并且它们似乎与CreateFileMapping有关。
首先,这是我的结构:

typedef struct _TUN_RING {
    volatile ULONG Head;
    volatile ULONG Tail;
    volatile LONG Alertable;
    UCHAR Data[(1024 * 1024) + 0x10000];
} TUN_RING;
这是根据文档(https://git.zx2c4.com/wintun/about/部分“环形布局”)进行的。它也与OpenVPN相同。
之后,我创建文件映射
send_ring_handle_ = CreateFileMapping(INVALID_HANDLE_VALUE,
                                            nullptr,
                                            PAGE_READWRITE,
                                            0,
                                            sizeof(TUN_RING),
                                            nullptr);
recv_ring_handle_ = CreateFileMapping(INVALID_HANDLE_VALUE,
                                            nullptr,
                                            PAGE_READWRITE,
                                            0,
                                            sizeof(TUN_RING),
                                            nullptr);
然后创建映射:
send_ring_ = (TUN_RING *)MapViewOfFile(send_ring_handle_,
                                         FILE_MAP_ALL_ACCESS,
                                         0,
                                         0,
                                         sizeof(TUN_RING));
recv_ring_ = (TUN_RING *)MapViewOfFile(recv_ring_handle_,
                                         FILE_MAP_ALL_ACCESS,
                                         0,
                                         0,
                                         sizeof(TUN_RING));
最后(在模拟系统用户之后)尝试向DeviceIoControl注册它:
    TUN_REGISTER_RINGS reg_rings;
    memset(&reg_rings, 0, sizeof(TUN_REGISTER_RINGS));
    reg_rings.Send.RingSize = sizeof(TUN_RING);
    reg_rings.Send.Ring = send_ring_;
    reg_rings.Send.TailMoved = CreateEvent(0, TRUE, FALSE, 0);
    reg_rings.Receive.RingSize = sizeof(TUN_RING);
    reg_rings.Receive.Ring = recv_ring_;
    reg_rings.Receive.TailMoved = CreateEvent(0, TRUE, FALSE, 0);

    DWORD len;
    if (!DeviceIoControl(tun_fd_,
                            TUN_IOCTL_REGISTER_RINGS,
                            &reg_rings,
                            sizeof(reg_rings),
                            nullptr,
                            0,
                            &len,
                            nullptr))
    {
        printf("Could not register ring buffers (%d).", ::GetLastError());
        return false;
    }
有人可以指出我错了吗?就像我说的,用malloc而不是文件映射相同的错误白羊座。
我现在使用malloc编写了一个完整的示例:
#include <windows.h>
#include <winioctl.h>
#include <IPHlpApi.h>
#include <ndisguid.h>
#include <TlHelp32.h>
#include <tchar.h>
#include <securitybaseapi.h>
#include <cfgmgr32.h>

#include <stdint.h>
#include <stdio.h>
#include <string>
#include <assert.h>

#pragma pack(push, 1)
typedef struct _TUN_PACKET_PROTO {
    ULONG Size;
    UCHAR Data[]; // max packet size as defined by the driver.
} TUN_PACKET_PROTO;

typedef struct _TUN_RING_PROTO {
    volatile ULONG Head;
    volatile ULONG Tail;
    volatile LONG Alertable;
    UCHAR Data[];
} TUN_RING_PROTO;


#define TUN_IOCTL_REGISTER_RINGS CTL_CODE(51820U, 0x970U, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA)
#define TUN_IOCTL_FORCE_CLOSE_HANDLES CTL_CODE(51820U, 0x971U, METHOD_NEITHER, FILE_READ_DATA | FILE_WRITE_DATA)

#define WINTUN_RING_CAPACITY        0x800000
#define WINTUN_RING_TRAILING_BYTES  0x10000
#define WINTUN_MAX_PACKET_SIZE      0xffff
#define WINTUN_PACKET_ALIGN         4

/* Memory alignment of packets and rings */
#define TUN_ALIGNMENT sizeof(ULONG)
#define TUN_ALIGN(Size) (((ULONG)(Size) + ((ULONG)TUN_ALIGNMENT - 1)) & ~((ULONG)TUN_ALIGNMENT - 1))
#define TUN_IS_ALIGNED(Size) (!((ULONG)(Size) & ((ULONG)TUN_ALIGNMENT - 1)))
/* Maximum IP packet size */
#define TUN_MAX_IP_PACKET_SIZE 0xFFFF
/* Maximum packet size */
#define TUN_MAX_PACKET_SIZE TUN_ALIGN(sizeof(TUN_PACKET_PROTO) + TUN_MAX_IP_PACKET_SIZE)
/* Minimum ring capacity. */
#define TUN_MIN_RING_CAPACITY 0x20000 /* 128kiB */
/* Maximum ring capacity. */
#define TUN_MAX_RING_CAPACITY 0x4000000 /* 64MiB */
/* Calculates ring capacity */
#define TUN_RING_CAPACITY(Size) ((Size) - sizeof(TUN_RING_PROTO) - (TUN_MAX_PACKET_SIZE - TUN_ALIGNMENT))
/* Calculates ring offset modulo capacity */
#define TUN_RING_WRAP(Value, Capacity) ((Value) & (Capacity - 1))

#define IS_POW2(x) ((x) && !((x) & ((x)-1)))



typedef struct _TUN_RING {
    volatile ULONG Head;
    volatile ULONG Tail;
    volatile LONG Alertable;
    UCHAR Data[WINTUN_RING_CAPACITY + (TUN_MAX_PACKET_SIZE-TUN_ALIGNMENT)];
} TUN_RING;

typedef struct _TUN_PACKET {
    ULONG Size;
    UCHAR Data[WINTUN_MAX_PACKET_SIZE]; // max packet size as defined by the driver.
} TUN_PACKET;

typedef struct _TUN_REGISTER_RINGS {
    struct {
        ULONG RingSize;
        TUN_RING *Ring;
        HANDLE TailMoved;
    } Send, Receive;
} TUN_REGISTER_RINGS;
#pragma pack(pop)

class regkey_t
{
public:
    regkey_t(void);
    regkey_t(HKEY handle);
    ~regkey_t(void);

    void attach(HKEY handle);
    void release(void);

    HKEY detach(void);
    operator HKEY (void) const;
    HKEY &get(void);
    HKEY *operator &(void);

private:
    regkey_t(const regkey_t &);
    regkey_t &operator = (const regkey_t &);

    HKEY handle_;
};

regkey_t::regkey_t():
    handle_(0)
{
}

regkey_t::regkey_t(HKEY handle):
    handle_(handle)
{
}

regkey_t::~regkey_t(void)
{
    release();
}

void regkey_t::attach(HKEY handle)
{
    release();
    handle_ = handle;
}

void regkey_t::release(void)
{
    if (handle_)
    {
        const LONG res (::RegCloseKey(handle_));
        if (res != ERROR_SUCCESS)
        {
            printf("Couldn't close a reg handle (%lu).\n", res);
        }

        handle_ = 0;
    }
}

HKEY regkey_t::detach(void)
{
    const HKEY result (handle_);
    handle_ = 0;
    return result;
}

HKEY &regkey_t::get(void)
{
    return handle_;
}

HKEY *regkey_t::operator &(void)
{
    return &handle_;
}

regkey_t::operator HKEY(void) const
{
    return handle_;
}

bool impersonate_as_system()
{
    HANDLE thread_token, process_snapshot, winlogon_process, winlogon_token, duplicated_token;
    PROCESSENTRY32 entry;
    BOOL ret;
    DWORD pid = 0;
    TOKEN_PRIVILEGES privileges;

    ::memset(&entry, 0, sizeof(entry));
    ::memset(&privileges, 0, sizeof(privileges));

    entry.dwSize = sizeof(PROCESSENTRY32);

    privileges.PrivilegeCount = 1;
    privileges.Privileges->Attributes = SE_PRIVILEGE_ENABLED;

    if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &privileges.Privileges[0].Luid))
    {
        return false;
    }

    if (!ImpersonateSelf(SecurityImpersonation))
    {
        return false;
    }

    if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &thread_token))
    {
        RevertToSelf();
        return false;
    }
    if (!AdjustTokenPrivileges(thread_token, FALSE, &privileges, sizeof(privileges), NULL, NULL))
    {
        CloseHandle(thread_token);
        RevertToSelf();
        return false;
    }
    CloseHandle(thread_token);

    process_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (process_snapshot == INVALID_HANDLE_VALUE)
    {
        RevertToSelf();
        return false;
    }
    for (ret = Process32First(process_snapshot, &entry); ret; ret = Process32Next(process_snapshot, &entry))
    {
        if (::strcmp(entry.szExeFile, "winlogon.exe") == 0)
        {
            pid = entry.th32ProcessID;
            break;
        }
    }
    CloseHandle(process_snapshot);
    if (!pid)
    {
        RevertToSelf();
        return false;
    }

    winlogon_process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
    if (!winlogon_process)
    {
        RevertToSelf();
        return false;
    }

    if (!OpenProcessToken(winlogon_process, TOKEN_IMPERSONATE | TOKEN_DUPLICATE, &winlogon_token))
    {
        CloseHandle(winlogon_process);
        RevertToSelf();
        return false;
    }
    CloseHandle(winlogon_process);

    if (!DuplicateToken(winlogon_token, SecurityImpersonation, &duplicated_token))
    {
        CloseHandle(winlogon_token);
        RevertToSelf();
        return false;
    }
    CloseHandle(winlogon_token);

    if (!SetThreadToken(NULL, duplicated_token))
    {
        CloseHandle(duplicated_token);
        RevertToSelf();
        return false;
    }
    CloseHandle(duplicated_token);

    return true;
}

std::string get_instance_id(uint32_t device_index)
{
    const std::string key_name("SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}");

    std::string device_id("");

    regkey_t adapters;
    DWORD ret = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, key_name.c_str(), 0, KEY_READ, &adapters);
    if (ret != ERROR_SUCCESS)
    {
        printf("Could not open registry key %s (%d).\n", key_name.c_str(), ret);

        return device_id;
    }

    DWORD sub_keys(0);
    ret = ::RegQueryInfoKey(adapters, NULL, NULL, NULL, &sub_keys, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
    if (ret != ERROR_SUCCESS)
    {
        printf("Could not get info from %s (%d).\n", key_name.c_str(), ret);
        return device_id;
    }

    if (sub_keys <= 0)
    {
        printf("Wrong registry key %s.\n", key_name.c_str());
        return device_id;
    }

    if (device_index >= sub_keys)
    {
        return device_id;
    }

    uint32_t index(0);
    for (DWORD i = 0; i < sub_keys; i++)
    {
        const uint32_t max_key_length = 255;
        TCHAR key[max_key_length];
        DWORD keylen(max_key_length);

        // Get the adapter name
        ret = ::RegEnumKeyEx(adapters, i, key, &keylen, NULL, NULL, NULL, NULL);
        if (ret != ERROR_SUCCESS)
        {
            continue;
        }

        // Append it to NETWORK_ADAPTERS and open it
        regkey_t device;
        const std::string new_key(key_name + "\\" + std::string(key));

        ret = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, new_key.c_str(), 0, KEY_READ, &device);
        if (ret != ERROR_SUCCESS)
        {
            continue;
        }

        TCHAR data[256];
        DWORD len(sizeof(data));
        ret = ::RegQueryValueEx(device, "ComponentId", NULL, NULL, (LPBYTE)data, &len);
        if (ret != ERROR_SUCCESS)
        {
            continue;
        }

        std::string device_name("wintun");
        if (::_tcsnccmp(data, device_name.c_str(), sizeof(TCHAR) * device_name.length()) == 0)
        {
            if (device_index != index)
            {
                index++;
                continue;
            }

            DWORD type;
            len = sizeof(data);

            ret = ::RegQueryValueEx(device, "DeviceInstanceID", NULL, &type, (LPBYTE)data, &len);
            if (ret != ERROR_SUCCESS)
            {
                printf("Could not get info from %s (%d).\n", key_name.c_str(), ret);

            }

            device_id = data;
            break;
        }
    }

    return device_id;
}

bool open_tun_device()
{
    HANDLE tun_fd_ = INVALID_HANDLE_VALUE;
    std::string device_id;
    uint32_t device_index;
    {
        TCHAR *interface_list = nullptr;
        for (device_index = 0; device_index < 256; ++device_index)
        {
            device_id = get_instance_id(device_index);
            if (device_id.empty())
            {
                continue;
            }

            CONFIGRET status = CR_SUCCESS;

            // This loop is recommended as "robust code" by MSDN. See the Remarks of CM_Get_Device_Interface_list.
            do
            {
                DWORD required_chars(0);
                if ((status = ::CM_Get_Device_Interface_List_Size(&required_chars,
                                                        (LPGUID)&GUID_DEVINTERFACE_NET,
                                                        (char *)device_id.c_str(),
                                                        CM_GET_DEVICE_INTERFACE_LIST_PRESENT)) != CR_SUCCESS || !required_chars)
                {
                    break;
                }

                assert(required_chars > 0);
                interface_list = (TCHAR *)::malloc(sizeof(TCHAR) * required_chars);

                status = ::CM_Get_Device_Interface_List((LPGUID)&GUID_DEVINTERFACE_NET,
                                                                  (char *)device_id.c_str(),
                                                                  interface_list,
                                                                  required_chars,
                                                                  CM_GET_DEVICE_INTERFACE_LIST_PRESENT);

                if (status == CR_SUCCESS)
                {
                    break;
                }

                ::free(interface_list);
                interface_list = nullptr;
            } while(status == CR_BUFFER_SMALL);

            if (interface_list)
            {
                break;
            }
        }

        if (!interface_list)
        {
            printf("Could not find wintun interface.\n");
            return false;
        }
        else
        {
            tun_fd_ = ::CreateFile(interface_list,
                                   GENERIC_READ | GENERIC_WRITE,
                                   FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                   nullptr,
                                   OPEN_EXISTING, 0, nullptr);
        }


        ::free(interface_list);
    }

    if (!tun_fd_ || tun_fd_ == INVALID_HANDLE_VALUE)
    {
        printf("Could not open wintun device.\n");
        return false;
    }

    printf("Opened wintun device.\n");
    ::Sleep(1000);

    TUN_RING * send_ring_ = (TUN_RING *)::malloc(sizeof(TUN_RING));
    TUN_RING * recv_ring_ = (TUN_RING *)::malloc(sizeof(TUN_RING));

    if (!recv_ring_ || !send_ring_)
    {
        printf("Could not malloc.\n");
        return false;
    }

    ::memset(send_ring_, 0, sizeof(*send_ring_));
    ::memset(recv_ring_, 0, sizeof(*recv_ring_));

    recv_ring_->Alertable = TRUE;
    recv_ring_->Head = 0;
    recv_ring_->Tail = 0;
    send_ring_->Alertable = TRUE;
    send_ring_->Head = 0;
    send_ring_->Tail = 0;

    HANDLE send_event = ::CreateEvent(0, FALSE, FALSE, 0);
    HANDLE recv_event = ::CreateEvent(0, FALSE, FALSE, 0);
    // register the rings
    if (impersonate_as_system())
    {
        TUN_REGISTER_RINGS reg_rings;
        ::memset(&reg_rings, 0, sizeof(TUN_REGISTER_RINGS));
        reg_rings.Send.RingSize = sizeof(TUN_RING);
        reg_rings.Send.Ring = send_ring_;
        reg_rings.Send.TailMoved = send_event;
        reg_rings.Receive.RingSize = sizeof(TUN_RING);
        reg_rings.Receive.Ring = recv_ring_;
        reg_rings.Receive.TailMoved = recv_event;


        int send_capacity = TUN_RING_CAPACITY(reg_rings.Send.RingSize);

        if ((send_capacity < TUN_MIN_RING_CAPACITY || send_capacity > TUN_MAX_RING_CAPACITY ||
             !IS_POW2(send_capacity) || !reg_rings.Send.TailMoved || !reg_rings.Send.Ring))
        {
            printf("Fuck this shit I am out...\n");
        }


        DWORD len;

        DWORD fuckyou = 0;
        if (!::DeviceIoControl(tun_fd_, TUN_IOCTL_FORCE_CLOSE_HANDLES,
                               &fuckyou, sizeof(fuckyou), nullptr, 0, &len, nullptr))
        {
            printf("Error releasing handles (%d).\n", ::GetLastError());
        }


        if (!::DeviceIoControl(tun_fd_,
                                TUN_IOCTL_REGISTER_RINGS,
                                &reg_rings,
                                sizeof(reg_rings),
                                nullptr,
                                0,
                                &len,
                                nullptr))
        {
            printf("Could not register ring buffers (%d).\n", ::GetLastError());
            ::Sleep(10000);
            RevertToSelf();
            return false;
        }
        ::Sleep(10000);
        RevertToSelf();
    }
    else
    {
        printf("Could not elevate to SYSTEM\n");
        return false;
    }



    return true;
}

int main()
{
    if (!open_tun_device())
    {
        printf("Experiment failed.\n");
    }

    printf("Size TUNRING: %d (%d)\n", sizeof(TUN_RING),  0x800000 + 0x010000 + 0x0C);
    printf("Capacity: %d\n", TUN_RING_CAPACITY(sizeof(TUN_RING)));
    if (!IS_POW2(TUN_RING_CAPACITY(sizeof(TUN_RING))))
    {
        printf("Shit gone wrong...\n");
    }

    return 0;
}
请确保运行此升级的,否则将出现错误5 ERROR_ACCESS_DENIED

最佳答案

注册戒指时,我可以看到您的代码有所不同。

您正在执行:

reg_rings.Send.RingSize = sizeof(TUN_RING);
reg_rings.Receive.RingSize = sizeof(TUN_RING);
虽然文档说:

Send.RingSize, Receive.RingSize: Sizes of the rings (sizeof(TUN_RING) + capacity + 0x10000, as above)


您的戒指是sizeof(TUN_RING) + UCHAR[(1024 * 1024) + 0x10000]我猜它不能接受没有数据空间的环吗?

抱歉,我看到您的TUN_RING包含数据...

可能是事件不好:

If an event is created from a service or a thread that is impersonating a different user, you can either apply a security descriptor to the event when you create it, or change the default security descriptor for the creating process by changing its default DACL

reg_rings.Send.TailMoved = CreateEvent(0, TRUE, FALSE, 0);
reg_rings.Receive.TailMoved = CreateEvent(0, TRUE, FALSE, 0);
您似乎正在使用默认的DACL。

可能存在对齐问题。如果malloc没有为您的缓冲区返回对齐的地址(可能在debug模式下,因为存在内存管理字节),则该数据包的Data成员可能无法对齐。
您可以根据地址检查对齐方式:
template <unsigned int alignment>
struct IsAligned
{
    static_assert((alignment & (alignment - 1)) == 0, "Alignment must be a power of 2");

    static inline bool Value(const void * ptr)
    {
        return (((uintptr_t)ptr) & (alignment - 1)) == 0;
    }
};

std::cout << IsAligned<32>::Value(ptr + i) << std::endl;
提供第一个数据包地址&(TUN_RING.Data[0])(我想是)
正如您在评论中所说,情况确实如此,这是不统一的。
您可以尝试两件事。
首先使用aligned_alloc保留内存,这将为TUN_RING提供对齐的缓冲区。
其次,如果TUN_RING已经对齐并且packet对齐是问题所在,那么您应该给头部和尾部提供正确的偏移量:
recv_ring_->Head = 0; // <- aligned byte offset
recv_ring_->Tail = 0;
send_ring_->Head = 0;
send_ring_->Tail = 0;
记得:

Head: Byte offset of the first packet in the ring. Its value must be a multiple of 4 and less than ring capacity.

Tail: Byte offset of the start of free space in the ring. Its value must be multiple of 4 and less than ring capacity.


字节偏移必须是4的倍数。
您必须将那些跳过的字节增加到缓冲区大小。为此,您可能需要分配不会使用的额外空间,但我认为不会太多。
在事件的第二种观点中,在文档中它说事件必须是auto-reset:

Send.TailMoved: A handle to an auto-reset event created by the client that Wintun signals after it moves the Tail member of the send ring.

Receive.TailMoved: A handle to an auto-reset event created by the client that the client will signal when it changes Receive.Ring->Tail and Receive.Ring->Alertable is non-zero.


在您的示例中,事件为auto-reset:
HANDLE send_event = ::CreateEvent(0, FALSE, FALSE, 0);
HANDLE recv_event = ::CreateEvent(0, FALSE, FALSE, 0);
但是您显示的代码(最重要的问题)不是:
reg_rings.Send.TailMoved = CreateEvent(0, TRUE, FALSE, 0);
reg_rings.Receive.TailMoved = CreateEvent(0, TRUE, FALSE, 0);
我不知道参数检查是否可以验证事件auto-reset的设置(即使这样做是不可能的。)而且,openvpn代码会创建它们为非auto-reset的代码(尽管在注册之前可能会有一些代码来向他们发出信号)。 )

关于c++ - wintun:注册环形缓冲区时出现ERROR_INVALID_PARAMETER,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62670308/

相关文章:

c++ - c++ 中 pthread 库中的互斥体是否与 Java 中的同步关键字相同

valgrind 中的条件跳转或移动错误

php - 使用 PHP 和 DOTNET 类访问 Windows 注册表

windows - TPath.GetTempFileName 回复 "The directory name is invalid"

c++ - 如何检查最后一个子字符串是否是一个值,如果是则将其删除

c++ - 在 C++ 中隐藏 int 变量的名称

c++ - 什么在调用 void();正在做?

c - 为什么以下 OpenMP 程序无法减少我的变量?

windows - 确定推荐的系统要求

c++ - 如何从 AVX 寄存器中获取数据?