网络编程之编写LSP进行Winsock API监控拦截或LSP注入
【1】工具介绍:
用到的工具:VS2015
语言:C/C++
需要系统提供的动态链接库:1、 sporder.dll //很多系统不自带着个dll,导致编译时缺少dll无法编译. (发布时必须将此dll放到程序目录)
本人只提供: WIN7 64位的sporder.dll :http://download.csdn.net/download/aaron133/10153240其他系统自行网上下载.
安装、移除LSP、编写分层提供者DLL、测试程序的源码:(申明:本人只在Win7 32/64位 和 Win10 64测试过)
http://download.csdn.net/download/aaron133/10152873
(除了文章中的源码之外,包含了测试程序的源码)
【2】编写LSP分层服务提供者需知的概念:
1、先看我写的SPI接口的概念:http://blog.csdn.net/aaron133/article/details/78005779
2、本章就是介绍安装SPI的分层协议提供者(LSP),即第三方系统网络组件。
3、当Windows程序想要使用网络时,必须加载SPI的基础提供者(TCP、UDP、原始)才能进行网络通讯。
4、安装LSP分层服务提供者就是写一个DLL,让网络程序先加载进去调用,然后再我们的DLL内,再调用基础服务提供者,进行网络通讯,所以在这过程中,我们可以对系统上所有使用特定协议的网络程序,在用户模式下进行Winsock API调用监控,HOOK拦截,甚至利用LSP注入DLL。
5、LSP一般是对网络进行更高级的通讯服务管理、过滤,黑客常用它来进行浏览器劫持、监控用户信息等等.
6、360所谓的修复LSP或CMD的netsh winsock reset命令,就是清除第三方的LSP提供者,并清除它的DLL,留下系统的基础服务提供者.
【3】不能拦截的Winsock API函数:
1、htonl,htons仅在ws2_32.dll中实现.
2、inet_addr,inet_ntoa,gethostname,WSACreateEvent,WSACloseEvent等等都不在SPI中.
3、如果程序直接使用传输驱动接口(TDI)进行TCP/IP发送数据包,那么拦截不了.
4、所以在用户模式下,使用LSP过滤网络封包是一个很好的选择.
【4】LSP分层服务提供者的编写:(DLL)
一、简述:
1、编写LSP提供者就是写一个DLL.
2、WSPStartup是LSP必须导出的函数.
3、加载下层提供者的DLL,并调用它的初始化WSPStartup是LSP必须做的事情.
4、拦截API函数就是将下层提供者(基础协议提供者)的函数地址记录下来,将我们自定义的函数地址替换上去,执行到如send时就会先调用我们的自定义函数,再由我们的自定义函数,考虑要不要调用真正的send.
二、开始编写LSP分层服务提供者的DLL:
【开始编写】
1、步骤 :创建Win32程序 --> DLL开发
2、新建一个.def文件:
EXPORTS
WSPStartup @2
【代码步骤分析】
1、网络程序加载LSP分层服务提供者的DLL,并调用了DLL里的WSPStartup初始化函数.
2、在LSP的WSPStartup函数中,加载它的下层服务提供者的DLL,并调用它的WSPStartup初始化函数.
3、对下层提供者的函数表地址进行修改,修改感兴趣的网络函数指向我们的自定义函数,进行拦截、监视Winsock API.
4、下面的例子中拦截了connect函数、sendto函数.
头文件: //在讲解SPI篇的时候,用到的函数,用于遍历系统所有协议,包括分层协议
- #include <WinSock2.h>
- #include <WS2spi.h>
- #include <tchar.h>
- LPWSAPROTOCOL_INFOW GetProvider(LPINT lpnTotalProtocols)
- {//遍历所有协议
- int nError = 0;
- DWORD dwSize = 0;
- LPWSAPROTOCOL_INFOW pProtoInfo = NULL;
- if (WSCEnumProtocols(NULL, pProtoInfo, &dwSize, &nError) == SOCKET_ERROR)
- {
- if (nError != WSAENOBUFS)
- return NULL;
- }
- pProtoInfo = (LPWSAPROTOCOL_INFOW)new WSAPROTOCOL_INFOW[dwSize / sizeof(WSAPROTOCOL_INFOW)];
- if (!pProtoInfo)
- return NULL;
- ZeroMemory(pProtoInfo, dwSize);
- lpnTotalProtocols = WSAEnumProtocols(NULL, pProtoInfo, &dwSize);
- return pProtoInfo;
- }
- void FreeProvider(LPWSAPROTOCOL_INFOW pProtoInfo)
- {
- delete[] pProtoInfo;
- }
源文件:
- WSPPROC_TABLE g_NextProcTable; //下层提供者的函数表 全局
- //LSP的初始化函数(唯一的导出函数)
- int WSPAPI WSPStartup(
- WORD wVersionRequested, //用户程序加载套接字库的版本号(in)
- LPWSPDATA lpWSPData, //用于取得Winsock服务的详细信息
- LPWSAPROTOCOL_INFO lpProtocolInfo, //指定想得到的协议的特征
- WSPUPCALLTABLE UpcallTable, //Ws2_32.dll向上调用转发的函数表
- LPWSPPROC_TABLE lpProTable //下层提供者的函数表(一般为基础协议,共30个服务函数)
- )
- { //如果协议位分层协议或基础协议,那么返回错误
- if (lpProtocolInfo->ProtocolChain.ChainLen <= 1)
- { //无法加载或初始化请求的服务提供程序
- return WSAEPROVIDERFAILEDINIT;
- }
- //找到下层协议的WSAPROTOCOL_INFOW结构体
- WSAPROTOCOL_INFOW NextProtocolInfo;
- int nTotalProtols;
- LPWSAPROTOCOL_INFOW pProtoInfo = GetProvider(&nTotalProtols);
- //下层提供者的入口ID
- DWORD dwBaseEntryId = lpProtocolInfo->ProtocolChain.ChainEntries[1];
- //遍历所有协议
- int i = 0;
- for (; i < nTotalProtols; i++)
- {//找到下层提供者协议
- if (pProtoInfo[i].dwCatalogEntryId == dwBaseEntryId)
- {
- memcpy(&NextProtocolInfo, &pProtoInfo[i], sizeof(WSAPROTOCOL_INFOW));
- break;
- }
- }
- //如果没找到
- if (i >= nTotalProtols)
- return WSAEPROVIDERFAILEDINIT;
- //加载下层协议的Dll
- int nError = 0;
- TCHAR szBaseProviderDll[MAX_PATH];
- int nLen = MAX_PATH;
- //取得下层提供者的DLL路径(可能包含坏境变量)
- if(WSCGetProviderPath(&NextProtocolInfo.ProviderId, szBaseProviderDll, &nLen, &nError) == SOCKET_ERROR)
- return WSAEPROVIDERFAILEDINIT;
- //坏境变量转换字符串
- if(!ExpandEnvironmentStrings(szBaseProviderDll, szBaseProviderDll, MAX_PATH))
- return WSAEPROVIDERFAILEDINIT;
- //加载dll
- HMODULE hModdule = LoadLibrary(szBaseProviderDll);
- if(hModdule == NULL)
- return WSAEPROVIDERFAILEDINIT;
- //取出下层提供者的WSPStartup函数
- LPWSPSTARTUP pfnWSPStartup = (LPWSPSTARTUP)GetProcAddress(hModdule, “WSPStartup”);
- if(NULL == pfnWSPStartup )
- return WSAEPROVIDERFAILEDINIT;
- LPWSAPROTOCOL_INFOW pInfo = lpProtocolInfo;
- if (NextProtocolInfo.ProtocolChain.ChainLen == BASE_PROTOCOL)//如果下层提供者是基础协议
- pInfo = &NextProtocolInfo; //赋给pInfo指针
- //调用下层提供者的初始化函数
- int nRet = pfnWSPStartup(wVersionRequested, lpWSPData, lpProtocolInfo, UpcallTable, lpProTable);
- //初始化失败
- if (nRet != ERROR_SUCCESS)
- return nRet;
- //初始化完成后,复制下层提供者(基础协议)的整个函数表
- g_NextProcTable = lpProTable;
- //将基础协议的SendTo函数指针,指向我们的WSPSendTo函数,在我们的函数内,再确定要不要调用回基础协议的Sendto函数
- lpProTable->lpWSPSendTo = WSPSendTo;
- lpProTable->lpWSPConnect = WSPConnect;
- FreeProvider(pProtoInfo, nTotalProtols);
- return nRet;
- }
//下面对sendto、connect函数的8888端口进行拦截:
- int WSPAPI WSPConnect( //自定义的WSPConnect函数
- SOCKET s,
- const struct sockaddr FAR * name,
- int namelen,
- LPWSABUF lpCallerData,
- LPWSABUF lpCalleeData,
- LPQOS lpSQOS,
- LPQOS lpGQOS,
- LPINT lpErrno
- )
- {
- sockaddr_in info = (sockaddr_in)name;
- USHORT port = ntohs(info->sin_port);
- if (port == 8888) //如果是8888端口,那么拦截
- {
- int nError = 0;
- //因为整个dll已经加载进程序里,这里对我的控制台程序进行测试
- SetConsoleTitle(_T(“sorry,we shutdown you tcp protocol port<8888>!”));
- g_NextProcTable.lpWSPShutdown(s, SD_BOTH, &nError);
- //设置错误信息
- lpErrno = WSAECONNABORTED;
- return SOCKET_ERROR;
- }
- //如果不是,调用下层提供者的函数表中的WSPConnect函数
- return g_NextProcTable.lpWSPConnect(s, name, namelen, lpCallerData, lpCalleeData, lpSQOS, lpGQOS, lpErrno);
- }
- int WSPAPI WSPSendTo //自定义的WSPSendTo函数
- (
- SOCKET s,
- LPWSABUF lpBuffers,
- DWORD dwBufferCount,
- LPDWORD lpNumberOfBytesSent,
- DWORD dwFlags,
- const struct sockaddr FAR * lpTo,
- int iTolen,
- LPWSAOVERLAPPED lpOverlapped,
- LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine,
- LPWSATHREADID lpThreadId,
- LPINT lpErrno
- )
- {
- sockaddr_in info = (sockaddr_in*)lpTo;
- USHORT port = ntohs(info->sin_port);
- if (port == 8888) //如果是8888端口,那么拦截
- {
- int nError = 0;
- SetConsoleTitle(_T(“sorry,we shutdown you udp protocol port<8888>!”));
- g_NextProcTable.lpWSPShutdown(s, SD_BOTH, &nError);
- //设置错误信息
- lpErrno = WSAECONNABORTED;
- return SOCKET_ERROR;
- }
- //如果不是,调用下层提供者的函数表中的WSPSendTo函数
- return g_NextProcTable.lpWSPSendTo(s, lpBuffers, dwBufferCount, lpNumberOfBytesSent, dwFlags,
- lpTo, iTolen, lpOverlapped, lpCompletionRoutine, lpThreadId, lpErrno);
- }
【5】LSP的DLL编写完成后,编写安装与卸载LSP的程序:
- #include <WS2spi.h>
- #include <winsock2.h>
- #include <process.h>
- #include <ws2tcpip.h>
- #include <mstcpip.h>
- #include <Windows.h>
- #include <iostream>
- #include <tchar.h>
- using namespace std;
- #pragma warning(disable:4996)
- #pragma comment(lib,“Sporder.lib”)
- #pragma comment(lib, “Ws2_32.lib”)
- #include <sporder.h>
- //安装LSP
- class installLSP
- {
- public:
- installLSP()
- {
- WSADATA wsa;
- WSAStartup(MAKEWORD(2, 2), &wsa);
- CoCreateGuid(&this->Layered_guid);
- CoCreateGuid(&this->AgreementChain_guid);
- }
- ~installLSP()
- {
- WSACleanup();
- }
- public:
- //安装LSP,并安装3个协议链
- BOOL InstallProvider(WCHAR wszDllPath) //参数:LSP的DLL的地址
- {
- WCHAR wszLSPName[] = _T(“AaronLSP”);
- LPWSAPROTOCOL_INFOW pProtoInfo = NULL;
- int nProtocols = 0; //分层协议 取出来的模板
- WSAPROTOCOL_INFOW OriginalProtocolInfo[3]; //数组成员为TCP、UDP、原始的目录入口信息
- DWORD dwOrigCatalogId[3]; //记录入口ID号
- int nArrayCount = 0; //数组个数索引
- DWORD dwLayeredCatalogId; //分层协议的入口ID号
- int nError;
- pProtoInfo = GetProvider(&nProtocols);
- if (nProtocols < 1 || pProtoInfo == NULL)
- return FALSE;
- BOOL bFindUdp = FALSE;
- BOOL bFindTcp = FALSE;
- BOOL bFindRaw = FALSE;
- for (int i = 0; i < nProtocols; i++)
- { //查找地址族为AF_INET的协议
- if (pProtoInfo[i].iAddressFamily == AF_INET)
- {
- if (!bFindUdp && pProtoInfo[i].iProtocol == IPPROTO_UDP)
- {
- memcpy(&OriginalProtocolInfo[nArrayCount], &pProtoInfo[i], sizeof(WSAPROTOCOL_INFOW));
- //去除XP1_IFS_HANDLES标志,防止提供者返回的句柄是真正的操作系统句柄
- OriginalProtocolInfo[nArrayCount].dwServiceFlags1 &= (~XP1_IFS_HANDLES);
- //记录目录入口ID
- dwOrigCatalogId[nArrayCount++] = pProtoInfo[i].dwCatalogEntryId;
- bFindUdp = TRUE;
- }
- if (!bFindTcp && pProtoInfo[i].iProtocol == IPPROTO_TCP)
- {
- memcpy(&OriginalProtocolInfo[nArrayCount], &pProtoInfo[i], sizeof(WSAPROTOCOL_INFOW));
- //去除XP1_IFS_HANDLES标志,防止提供者返回的句柄是真正的操作系统句柄
- OriginalProtocolInfo[nArrayCount].dwServiceFlags1 &= (~XP1_IFS_HANDLES);
- //记录目录入口ID
- dwOrigCatalogId[nArrayCount++] = pProtoInfo[i].dwCatalogEntryId;
- bFindTcp = TRUE;
- }
- if (!bFindRaw && pProtoInfo[i].iProtocol == IPPROTO_IP)
- {
- memcpy(&OriginalProtocolInfo[nArrayCount], &pProtoInfo[i], sizeof(WSAPROTOCOL_INFOW));
- //去除XP1_IFS_HANDLES标志,防止提供者返回的句柄是真正的操作系统句柄
- OriginalProtocolInfo[nArrayCount].dwServiceFlags1 &= (~XP1_IFS_HANDLES);
- //记录目录入口ID
- dwOrigCatalogId[nArrayCount++] = pProtoInfo[i].dwCatalogEntryId;
- bFindRaw = TRUE;
- }
- }
- }
- if (nArrayCount == 0)
- {
- FreeProvider(pProtoInfo);
- return FALSE;
- }
- //安装LSP分层协议
- WSAPROTOCOL_INFOW LayeredProtocolInfo;
- memcpy(&LayeredProtocolInfo, &OriginalProtocolInfo[0], sizeof(WSAPROTOCOL_INFOW));
- //修改协议名称的字符串
- wcscpy(LayeredProtocolInfo.szProtocol, wszLSPName);
- //表示分层协议
- LayeredProtocolInfo.ProtocolChain.ChainLen = LAYERED_PROTOCOL;//0
- //表示方式为由提供者自己设置
- LayeredProtocolInfo.dwProviderFlags = PFL_HIDDEN;
- //安装分层协议
- if (SOCKET_ERROR == WSCInstallProvider(&Layered_guid, wszDllPath, &LayeredProtocolInfo, 1, &nError))
- {
- FreeProvider(pProtoInfo);
- return FALSE;
- }
- FreeProvider(pProtoInfo);
- //重新遍历协议,获取分层协议的目录ID号
- pProtoInfo = GetProvider(&nProtocols);
- if (nProtocols < 1 || pProtoInfo == NULL)
- return FALSE;
- for (int i = 0; i < nProtocols; i++)//一般安装新入口后,会排在最低部
- {
- if (memcmp(&pProtoInfo[i].ProviderId, &Layered_guid, sizeof(GUID)) == 0)
- {
- //取出分层协议的目录入口ID
- dwLayeredCatalogId = pProtoInfo[i].dwCatalogEntryId;
- break;
- }
- }
- //安装协议链 256
- WCHAR wszChainName[WSAPROTOCOL_LEN + 1];//新分层协议的名称 over 取出来的入口模板的名称
- for (int i = 0; i < nArrayCount; i++)
- {
- swprintf(wszChainName, _T("%s over %s"), wszLSPName, OriginalProtocolInfo[i].szProtocol);
- wcscpy(OriginalProtocolInfo[i].szProtocol, wszChainName); //将这个模板的名称改成新名称↑
- if (OriginalProtocolInfo[i].ProtocolChain.ChainLen == 1)//这是基础协议的模板
- { //修改基础协议模板的协议链, 在协议链[1]写入真正UDP[基础协议]的入口ID
- OriginalProtocolInfo[i].ProtocolChain.ChainEntries[1] = dwOrigCatalogId[i];
- }
- else
- {//如果大于1,相当于是个协议链,表示:将协议链中的入口ID,全部向后退一格,留出[0]
- for (int j = OriginalProtocolInfo[i].ProtocolChain.ChainLen; j > 0; j–)
- OriginalProtocolInfo[i].ProtocolChain.ChainEntries[j] = OriginalProtocolInfo[i].ProtocolChain.ChainEntries[j - 1];
- }
- //让新分层协议排在基础协议的前面(如果为协议链排就排在开头了)
- OriginalProtocolInfo[i].ProtocolChain.ChainLen++;
- OriginalProtocolInfo[i].ProtocolChain.ChainEntries[0] = dwLayeredCatalogId;
- }
- //一次安装3个协议链
- if (SOCKET_ERROR == WSCInstallProvider(&AgreementChain_guid, wszDllPath, OriginalProtocolInfo, nArrayCount, &nError))
- {
- FreeProvider(pProtoInfo);
- return FALSE;
- }
- //第三步:将所有3种协议进行重新排序,以让系统先调用我们的协议(让协议链排第一,协议链中[0]是新分层协议,[1]基础UDP协议)
- //重新遍历所有协议
- FreeProvider(pProtoInfo);
- pProtoInfo = GetProvider(&nProtocols);
- if (nProtocols < 1 || pProtoInfo == NULL)
- return FALSE;
- DWORD dwIds[20];
- int nIndex = 0;
- //添加我们的协议链
- for (int i = 0; i < nProtocols; i++)
- {//如果是我们新创建的协议链
- if (pProtoInfo[i].ProtocolChain.ChainLen > 1 && pProtoInfo[i].ProtocolChain.ChainEntries[0] == dwLayeredCatalogId)
- dwIds[nIndex++] = pProtoInfo[i].dwCatalogEntryId;//将3个协议链排在前3
- }
- //添加其他协议
- for (int i = 0; i < nProtocols; i++)
- {//如果是基础协议,分层协议(不包括我们的协议链,但包括我们的分层协议)
- if (pProtoInfo[i].ProtocolChain.ChainLen <= 1 || pProtoInfo[i].ProtocolChain.ChainEntries[0] != dwLayeredCatalogId)
- dwIds[nIndex++] = pProtoInfo[i].dwCatalogEntryId;
- }
- //重新排序Winsock目录
- if (WSCWriteProviderOrder(dwIds, nIndex) != ERROR_SUCCESS)
- return FALSE;
- FreeProvider(pProtoInfo);
- return TRUE;
- }
- //卸载LSP
- void RemoveProvider()
- {
- LPWSAPROTOCOL_INFOW pProtoInfo = NULL;
- int nProtocols = 0;
- DWORD dwLayeredCatalogId = 0; //分层协议提供者的入口ID号
- //遍历出所有协议
- pProtoInfo = GetProvider(&nProtocols);
- if (nProtocols < 1 || pProtoInfo == NULL)
- return;
- int nError = 0;
- int i = 0;
- for (i = 0; i < nProtocols; i++)
- { //查找分层协议提供者
- if (memcmp(&Layered_guid, &pProtoInfo[i].ProviderId, sizeof(GUID)) == 0)
- {
- dwLayeredCatalogId = pProtoInfo[i].dwCatalogEntryId;
- break;
- }
- }
- if (i < nProtocols)
- {
- for (i = 0; i < nProtocols; i++)
- {//查找协议链(这个协议链的[0]为分层协议提供者)
- if (pProtoInfo[i].ProtocolChain.ChainLen > 1 && pProtoInfo[i].ProtocolChain.ChainEntries[0] == dwLayeredCatalogId)
- {//先卸载协议链
- WSCDeinstallProvider(&pProtoInfo[i].ProviderId, &nError);
- break;
- }
- }
- WSCDeinstallProvider(&Layered_guid, &nError);
- }
- }
- private:
- //这两个函数是遍历所有协议函数,在编写DLL时,已经把源代码放出来了,这里就不放出来了.
- LPWSAPROTOCOL_INFOW GetProvider(LPINT lpnTotalProtocols);
- void FreeProvider(LPWSAPROTOCOL_INFOW pProtoInfo);
- private:
- GUID Layered_guid; //分层协议GUID
- GUID AgreementChain_guid; //协议链GUID
- };
- #include “Hello.h”
- #define PATH _T(“C:\Users\Administrator\Desktop\实例\网络实验\安装LSP服务提供程序\LSPDll\Debug\LSPDll.dll”)
- int main(int argc,char** argv)
- {
- system(“color 4e”);
- SetConsoleTitle(_T(“安装LSP提供者程序实验”));
- ProtocolTraversestheExperiment2 s;
- printf(“安装LSP前的所有协议:\r\n”);
- s.ShowAllProtocol();
- installLSP LSP;
- LSP.InstallProvider(PATH);
- printf(“安装LSP后的所有协议:\r\n”);
- s.ShowAllProtocol();
- getchar();
- LSP.RemoveProvider();
- printf(“清除LSP完成\r\n”);
- getchar();
- return 0;
- }
【测试】
![](/assets/blank.gif)
![](/assets/blank.gif)
网络编程之编写LSP进行Winsock API监控拦截或LSP注入相关推荐
- 《Unix网络编程卷1-套接字联网API》第一个例子编译 不通过问题解决
<Unix网络编程卷1-套接字联网API>是本好书. 但是第一个例子不是很好编译. 需要如下步骤: 本人机器CentOS 5.4 1.下载源码 unpv13e解压到任意目录 然后按其rea ...
- 【网络编程】之二、socket API学习
套接字API函数: 1.socket函数:The socket function creates a socket that is bound to a specific transport serv ...
- 《UNIX网络编程:套接字联网API》啃书笔记(第8UDP套接字编程、11章地址转换)
基本UDP套接字编程 下图为UDP客户/服务器程序的函数调用: 注意客户不与服务器建立连接,而是只管使用sendto函数给服务器发送数据报,其中必须指定目的地的地址作为参数.类似的,服务器不接受来自客 ...
- 基于Winsock API的VC网络编程实战
基于Winsock API的VC网络编程实战 随着计算机信息技术的飞速发展,互联网与人类社会的工作.生活越来越紧密相关,它已经成为人类获取.交流信息的重要途径和手段.所以当前对于开发人员来说,网络编程 ...
- WinSock网络编程实用宝典(一)
一.TCP/IP 体系结构与特点 1.TCP/IP体系结构 TCP/IP协议实际上就是在物理网上的一组完整的网络协议.其中TCP是提供传输层服务,而IP则是提供网络层服务.TCP/IP包括以 ...
- Windows下的网络编程Winsock
文章目录 前言 1.服务器下的Winsock 1.1.构建编程环境: 1.2.WSAData结构体 1.3.WSAStartup初始化Winsock 1.4.WSACleanup释放Winsock 1 ...
- Winsock网络编程头文件及库文件的设置
Winsock是Windows下网络编程的规范.使用Winsock可以实现基于TCP或UDP的通信. 1 Winsock版本 Winsock主要包含两个版本,即Winsock1和Winsock2.Wi ...
- windows多线程和网络编程
第 10 章 多线程与网络编程初步 教学提示:Windows 是一个支持多任务的操作系统.当在一个程序中需要启动另外一 个程序时,需要用到多进程的编程方式.如果一个进程中有一些相似的任务需要同时推 ...
- python怎么编程输入坐标_python编程之API入门: (一)使用百度地图API查地理坐标...
在网络编程中,我们会和API打交道.那么,什么是API?如何使用API呢?本文分享了一下我对API的理解以及百度地图API的使用. API是"Application Programming ...
最新文章
- 交叉验证分析每一折(fold of Kfold)验证数据的评估指标并绘制综合ROC曲线
- android 实现全屏代码
- 何恺明CVPR演讲:深入理解ResNet和视觉识别的表示学习(41 PPT)
- jsp mysql 换行_jsp 操作 mysql 数据库
- 中国象棋程序的设计与实现(四)-- 一次“流产”的写书计划
- VTK:Utilities之LUTUtilities
- 什么是数据的完整性约束
- mysql备份表恢复数据库_mysql备份恢复数据库据/表
- c++: size_type与 size_t一些概念
- 牛客寒假算法基础训练营5
- Cygwin ssh
- IE中getElementById的Bug
- 盖世无双之国产数据库风云榜-2022年02月
- 卡巴斯基2010激活码
- 企业需要关注的零信任 24 问
- 解决Postman报错Could not send request
- 互联网巨头的2B市场变革
- 【搞笑】新闻联播熏陶下的小学生作文
- Win10取消文件默认打开方式
- 计算机utp,UTP网线
热门文章
- 鸟人的Android揭秘(3)——Android 编译环境搭建
- byte数组转blob类型_Jfinal 存byte[] 到mysql数据库中blob类型
- 5、Squid代理服务
- html a text decoration,你未必知道的CSS小知识:text-decoration属性变成了属性简写
- 微信公众号怎么做地推活动?效果极佳又安全!
- 博客园有一段时间登不上
- 工作站和台式机的区别是什么
- 参考资料来自 懒兔子 的公众号
- Unity 0.Interactive Tutorials
- 为什么有些人赚钱那么容易,有些人却赚不到钱?