第九章
特洛伊木马实例及其简单实现
这里介绍一个比较阴险的威胁网络安全的方法:特洛伊木马(trojan horse,或trojan)。
第一节什么是特洛伊木马
特洛伊木马是一个程序,它驻留在目标计算里。在目标计算机系统启动的时候,自动启动。然后在某一端口进行侦听。如果在该端口受到数据,对这些数据进行识别,然后按识别后的命令,在目标计算机上执行一些操作。比如窃取口令,拷贝或删除文件,或重新启动计算机。
攻击者一般在入侵某个系统后,想办法将特洛伊拷贝到目标计算机中。并设法运行这个程序,从而留下后门。以后,通过运行该特洛伊的客户端程序,对远程计算机进行操作。
特洛伊木马的一个特点是,它能巧妙地运行在目标计算机系统里,而不容易被发现。
现在有许多这样的程序。如NetCat,Back Orifice,NetBus等等。
Back Orifice
Back Orifice简介
Back Orifice是Cult of the Dead Cow (cDc)在1998年8月3日发布的。目前的下载量达到了100,000。许多人都在善意或恶意地使用这个程序。尽管这个程序并不是最优秀的黑客工具,但由于媒体的炒做,使得这个工具给人么一个很坏的印象。
Back Orifice被称为“远程管理工具”。它可以附加在别的文件或程序后,也可以单独运行。它的服务器程序必须在目标计算机上运行之后,才能起到作用。一旦运行后,用户就不大容易感觉到它的存在。在任务列表里,根本就看不到它。该工具的服务器运行后,就一直在一个端口侦听从客户机来的命令,根据不同的命令,在目标机器上执行相应的操作。
Back Orifice的使用
Back Orifice(以下简称BO)是一个客户机/服务器(C/S)应用程序,其客户机程序(以下简称BO客户机)可以监视、管理和使用其它网络中运行服务器程序(以下简称BO服务器)的目标计算机所在的网络资源。基于文本和基于图形的BO客户机是运行在Microsoft Windows机器上。当前版本的BO服务器只能在Windows 95/98中运行。
Back Orifice软件包里包括:
bo.txt 软件包说明文档。
plugin.txt 插件编程文档。
boserve.exe Back Orifice服务器自安装程序。
bogui.exe 图形界面的Back Orifice客户机。
boclient.exe 文本界面的Back Orifice客户机。
boconfig.exe 配置BO服务器程序文件名、端口、密码和插件的工具。
melt.exe 对由freeze命令压缩的文档解压缩。
freeze.exe 压缩文档。压缩文档可被metl命令解压缩。
只要运行BO服务器程序,就可以安装BO服务器了。当BO服务器程序运行时,它安装BO服务器,然后删除自安装程序。此方法有助于网络环境下的安装:只要BO服务器程序被复制到Startup目录下就行了(译者注:因为Windows 95/98每次启动时都会运行该目录下的程序)。因为BO服务器程序在自安装BO服务器后就会删除自已。一旦BO服务器被安装到一台机器上,它会在每次机器启动时运行。
需要远程更新Back Orifice时,只要上载新版本的BO服务器程序到远程机上,使用Process spawn命令运行它。一旦运行,BO服务器程序将自动删除与它将要安装的文件同名的文件,安装自已(覆盖旧版本),然后在安装目录中运行自己,最后删除BO服务器程序。
在安装前,可以配置BO服务器程序的一些参数。如安装后的BO文件名、监听端口、加密密码,都可以使用boconfig.exe工具配置。如果不进行配置,缺省是监听31337端口、不使用加密密码(数据包仍然会加密)和以" .exe"文件名安装。
BO客户机通过加密了的UDP包与BO服务器通讯。要实现成功通讯,BO客户机城建发送数据到BO服务器监听的端口,而且BO客户机密码必须匹配BO服务器已配置好的密码。
基于图形和文本的BO客户机都可以通过使用-p选项来设置BO客户机数据包的发送端口。如果数据包被过滤或者有防火墙屏蔽,就可能需要从一个特别的、不会被过滤和屏蔽的端口发送。如果UDP连接通讯不能成功,则可能是数据包在发送或回送路径中被过滤或者屏蔽了。
从BO客户机向特定的IP地址发送命令即可对BO服务器操作。如果BO服务器无静态IP地址,则可使用以下方法:
(1) 在基于文本的BO客户机使用sweep或sweeplist命令;
(2) 在基于图形的BO客户机使用"Ping..."对话框;
(3) 设定目标IP如"1.2.3.*"。如果扫描子网列表,当有BO服务器响应时,BO客户机在子网列表目录中浏览,并显示所匹配的行和子网地址。(译者注:虽然我知道如何使用,但却无法按原文的内容表达出来。我在以后再作详细说明。)
以下是在现在版本的Back Orifice中已经实现的命令。在基于图形和基于文本的BO客户机里有些命令名称不相同,但几乎所有命令的语法格式都是一致的。在基于文本的BO客户机中输入 "help command"可得到更多关于命令的信息。在基于图形的BO客户机中有两个参数输入区域,这些参数作为在"Command"列表中所选择的命令的参数。如果未给出命令所需要的参数,BO服务器将返回"Missing data"(丢失数据)。
Back Orifice命令如下:
(基于图形的BO客户机命令/基于文本的BO客户机命令)
App add/appadd
在TCP端口输出一个基于文本的应用程序。它允许你通过Telnet对话控制基于文本或DOS的应用程序。
App del/appdel从监听的连接中关闭一个应用程序。
Apps list/applist列出当前监听的连接中的应用程序。
Directory create/md创建目录
Directory list/dir列出文件和目录。如要显示多文件/目录则须使用通配符。
Directory remove/rd删除目录
Export add/shareadd在BO服务器上创建一个“出口”(共享)。被输出(共享)的目录或驱动器图标不会出现共享图标。
Export delete/sharedel删除一个(共享)“出口”。
Exports list/sharelist列出当前共享名、共享驱动器、共享目录、共享权限和共享密码。
File copy/copy拷贝文件。
File delete/del删除文件。
File find/find在目录中查找符合条件(支持通配符)的文件。
File freeze/freeze压缩文件。
File melt/melt解压缩文件。
File view/view查看文件内容。
HTTP Disable/httpoff使HTTP服务器失效。
HTTP Enable/httpon使HTTP服务器有效。
Keylog begin/keylog将BO服务器上的击键记录在一个文本文件中,同时还记录执行输入的窗口名。
Keylog end停止击键记录。基于文本的BO客户机使用"keylog stop"命令。
MM Capture avi/capavi从视频输入设备(如果存在)捕捉视频和音频信号到avi文件中。
MM Capture frame/capframe从视频输入设备捕捉一个视频帧到一个位图文件中。
MM Capture screen/capscreen捕捉BO服务器屏幕影像到一们位图文件中。
MM List capture devices/listcaps列出视频输入设备。
MM Play sound/sound在BO服务器上播放一个avi文件。
Net connections/netlist列出当前接入和接出的连接。
Net delete/netdisconnect断开BO服务器的一个网络资源连接。
Net use/netconnect把BO服务器连接到一个网络资源。
Net view/netview查看BO服务器上所有的网络接口、域名、服务器和可见的共享“出口”。
Ping host/pingPing主机。返回主机名和BO版本。
Plugin execute/pluginexec运行BO插件。运行不符合BO插件接口的函数可能使B)服务器当机。
Plugin kill/pluginkill命令一个插件关闭。
Plugins list/pluginlist列出当前激活的插件和已存在的插件返回值。
Process kill/prockill终止一个进程。
Process list/proclist列出运行中的进程。
Process spawn/procspawn运行一个程序。在基于图形的BO客户机程序中,如果需要确定第二个参数,进程可能以一个正常的、可见的方式运行,否则进程的运行将是隐蔽或独立的。
Redir add/rediradd重定向接入的TCP连接或UDP数据包到另一个IP地址。
Redir del/redirdel停止端口重定向。
Redir list/redirlist列出激活的端口重定向。
Reg create key/regmakekey在注册表中创建中一个主键。
注:对于所有的注册表命令,不要在注册表键值前加入前导"\\"。
Reg delete key/regdelkey从注册表中删除一个主键。
Reg delete value/regdelval删除注册表中的一个键值。
Reg list keys/reglistkeys列出注册表中一个主键下的子键。
Reg list values/reglistvals列出注册表中一个主键的键值。
Reg set value/regsetval设置注册表一个主键的一个键值。键值格式为“类型,值”。对于二进制值(类型为B),值是一个两位的16进制数;对于DWORD(双字)值(类型为D),值是一个十进制数;对于字符串值(类型为S),值是一个文本串。
Resolve host/resolve解析BO服务器的主机名的IP地址。主机名可能是一个Internet主机名或本地网络机器名。
System dialogbox/dialog用所给出的文本和一个"OK"按钮,
在BO服务器上创建一个对话框。可以创建任意多的对话框,对话框的显示是堆叠式的。
System info/info显示BO服务器上的系统信息。包括机器名、当前用户、CPU类型、内存容量及可用内存、Windows版本、驱动器信息(类型(硬盘、CDROM、可拆卸型、远程驱动器)、硬盘驱动器容量及未使用空间)。
System lockup/lockup锁住BO服务器机器。
System passwords/passes显示被缓存的当前用户密码和屏幕保护密码。所显示的密码中可能含有一些无用信息。(译者注:如果密码未被系统缓存,则不能显示密码。)
System reboot/reboot关闭BO服务器主机并重启动。
TCP file receive/tcprecv将BO服务器主机连接到一个特定的IP地址和端口,并保存所接收到的数据到特定文件中。
TCP file send/tcpsend将BO服务器主机连接到一个特定的IP地址和端口,发送特定文件中的内容,然后断开此连接。
注:对于TCP文件传输,必须监听特定的IP地址和端口,直到TCP文件命令被发送,否则传输将会失败。
从BO服务器传输文件,可使用TCP文件发送命令和如下格式的netcat命令:
netcat -l -p 666 > file
传输文件到BO服务器,可使用TCP文件接收命令和如下格式的netcat命令:
netcat -l -p 666 < file
注:Win32版本的netcat命令在到达输入文件末部时并不断开连接。因此应在文件内容传输完毕后用ctrl-c或ctrl-break终止netcat命令。
BOConfig:
BOConfig.exe允许在BO服务器安装前配置一些可选项。首先询问BO服务器在系统目录中安装的可执行文件名。它不一定是.exe,但如果你不给出扩展名,它不会自动添加.exe扩展名;接着询问exe文件的描述,它描述了在注册表中记录的、系统启动时运行的exe文件;接着询问BO服务器监听(数据包)端口;接着询问用于加密的密码。要实现BO客户机到BO服务器的通讯,客户机必须配置有相同的密码,此密码可以为空;接着询问启动时缺省运行的插件。这个在BO服务器启动时自动运行的BO插件是以"DLL:_Function"格式定义的DLL和函数。此项可以为空;然后让你输入启动时传送给插件的参数,此项也可以为空;最后,询问被附加到BO服务器上的文件的路径。该文件将在BO服务器启动时写入系统目录。此文件可以是一个自动启动的BO插件。
BO服务器在没有进行配置时也能运行。缺省地,安装BO服务器文件名为" .exe",无密码,使用端口31337通讯。
已知的Bugs和问题:
多媒体捕捉屏幕——所产生的位图是按BO服务器端的显示分辨率和像素深度保存的。因此,它可能是16位或24位颜色的。大多数图形应用程序只能处理8位或32位位图,因而不能打开此位图,或者显示不正常(此类软件包括Graphics Workshop for Windows、Photoshop和WANG Imaging distributed with Windows)。但是,Windows本身有一个应用程序Paint.exe可以浏览这些位图,按其提示操作即可。
击键记录——很显然,MS-DOS窗口未提供信息循环机制,这就使得BO无法记录输入到其中的击键。
基于文本的应用程序的TCP重定向——有几个Bugs。
当用command.com的重定向名柄输出command.com时,系统同时输出REDIR32.EXE,此程序似乎是无法终止的。这可能是由于操作系统接口与一个tsr模块(该模块在DOS对话中被装载以重定向输入/输出句柄)通讯所造成的。因此,如果在应用程序被终止(或退出)前终止TCP连接,REDIR32.exe和WINOA386.MOD(用于封装管理旧16位应用程序)将仍然运行,BO和操作系统均无法终止它们。这会导致系统显示"Please wait..."(请等待)屏幕且无法关机。
某些控制台应用程序重定向了输出时也可能会发生问题,如FTP.exe和boclient.exe。虽然程序的输出因此而不能传送出去,但仍然可能传送输入,所以你要通过TCP对话使该程序退出。否则使用BO杀死该进程。
Back Orifice的检查和清除
打开注册表编辑器,检查HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunServices主键的键值。如果你在主键看到的如下的一个键值:
Name Data
(缺省) " .exe" (一个空格,一个点号和exe后缀)
那么你可能已经感染上了Back Orifice了。然后在C:\WINDOWS\SYSTEM目录下,如果发现一个" .exe"文件,文件大小为122K左右,那么你肯定感染了这个程序了。
清除的方法很简单。首先将上述主键中的有关" .exe"的项目删除,然后重新启动计算机。接着,将C:\WINDOWS\SYSTEM下的" .exe"删除,最后,找一个叫WINDLL.DLL的文件,也将它删除。
注意,有可能你的系统里有好几个Back Orifice的拷贝,要逐一清除。
NetBus
Netbus 是一个类似于著名的 Back Orifice 的黑客软件,区别在于它的能力要强出太多。Netbus 通过 TCP/IP 协议,可以远程将应用程序指派到某一套接字端口来运行。这就相当于说可以远程运行目标机器上的 cmd.exe,想想这是多么危险的事情。
如果不是 the Cult of the Dead Cow 黑客组织在1998年的 DefCon 大会上发布 BackOrifice 工具而引起轩然大波的话,可能大多数人还不会注意到三月份发行的 Netbus。据说 Netbus 是瑞典程序员 Carl-Fredrik Neikter 为了“和朋友们消遣”而编写的。
粗粗一看,Netbus 似乎没什么危害,只允许黑客控制鼠标,播放声音文件,甚或打开 CD-ROM 托架。但如果深入分析,就不难发现其中大量的破坏性功能,特别它是基于 TCP/IP 协议在 Windows 95、Windows 98、和 Windows NT 上运行的(与 BackOrifice 不同),这大大增加了各种入侵用户系统的可能性。
Netbus 1.6 版能实现一些相当危险的操作:黑客能够运行远程程序,进行屏幕抓图,在所侵入的计算机浏览器中打开 URL,显示位图,进行服务器管理操作(如更改口令),甚至利用远端的麦克风录制一段声音。更可怕的是:它能在侵入的计算机上显示信息,向毫无戒心的用户提示输入口令,再把该口令返回到入侵者的屏幕上。Netbus 还能关闭 Windows 系统,下载、上载或删除文件。
11 月 14 日发行 的 Netbus 1.7 新增了更多不正当的功能。如:重定向功能(Redirection)使黑客能够控制网络中的第三台机器,从而伪装成内部客户机。这样,即使路由器拒绝外部地址,只允许内部地址相互通信,黑客也依然可以占领其中一台客户机并对其它无数台机器进行控制。
V1.7 甚至还能指派应用软件至某个端口。以前只有 Netcat — 黑客的梦幻工具— 用于 Unix 和 NT 时才具有这种功能。例如,黑客可以将 cmd.exe 指派至 Telnet port 23,然后 Telnet 进入该机器,从而接管系统的命令提示符。其危险后果不言自明。
Netbus 的默认状态是在 port 12345 接收指令,在 port 12346 作应答。Telnet 登录到接收端口就会看到产品名称及版本号,还可以修改口令。Netbus 能通过编辑 patch.ini 配置文件,把 1 到 65535 之间的任意数字指定为端口。当需要绕过防火墙或路由过滤器时,端口通常就会设为 53(DNS)或 80(HTTP)。
所有的特洛伊木马都分成两个部分:服务器和客户机。
V1.7版本的NetBus的服务器的默认文件名是patch.exe。运行这个程序后,它将自己拷贝到Windows目录下,并从中解开一个叫KeyHook.dll的动态连接库。默认的,它创建一个主键HKEY_CURRENT_USER\PATCH。并在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run下创建了一个键,它的值是patch.exe文件的路径名。这使得在每次系统启动时,都能自动运行patch.exe这个程序。除此外,还创建下面两个键:HKEY_CURRENT_USER\NETBUS和HKEY_CURRENT_USER\NETBUS\Settings
按照上面的描述,清除方法就自然出来了。
第二节特洛伊木马的一个简单实现
通过上面的两个实例介绍,基本上就能看出特洛伊木马的工作原理。这里我们仅仅介绍用Winsock实现的一个客户机程序和一个服务端程序。
这个实例中的服务器在接到客户机的命令后会重新启动计算机。
可以在这两个程序的基础上,加入一些命令,对目标系统进行一些修改。比如拷贝文件等等。
这两个程序是从微软的MSDN上拿下来的,略微作了点增加。在VC++6.0中编译运行的。注意在连接的时候要加入:wsock32.lib库。
ExitWindowsEx 函数介绍
ExitWindowsEx函数的功能是关闭系统,注销用户和重新启动系统。
它的函数原型是:
BOOL ExitWindowsEx( UINT uFlags, DWORD dwReserved);
第一个参数用来指定操作的类型。
常见的有下面几个:
EWX_POWEROFF:关闭系统及关闭电源。
EWX_REBOOT:重新启动计算机。
EWX_SHUTDOWN:关闭系统,但不关闭电源。
第二个参数可以指定任意值,并没有特定意义。
具体有关在Linux和Windows下进行SOCKET编程的细节,请参见相关章节。
服务器程序:
#include < windows.h>
#include < winsock.h>
#define PORTNUM 5000 // Port number
#define MAX_PENDING_CONNECTS 4 // Maximum length of the queue
// of pending connections
int WINAPI WinMain (
HINSTANCE hInstance, // Handle to the current instance
HINSTANCE hPrevInstance,// Handle to the previous instance
LPTSTR lpCmdLine, // Pointer to the command line
int nCmdShow) // Show state of the window
{
int index = 0, // Integer index
iReturn; // Return value of recv function
char szServerA[100]; // ASCII string
TCHAR szServerW[100]; // UNICODE string
TCHAR szError[100]; // Error message string
SOCKET WinSocket = INVALID_SOCKET, // Window socket
ClientSock = INVALID_SOCKET; // Socket for communicating
// between the server and client
SOCKADDR_IN local_sin, // Local socket address
accept_sin; // Receives the address of the
// connecting entity
int accept_sin_len; // Length of accept_sin
WSADATA WSAData; // Contains details of the Windows
// Sockets implementation
// Initiate Windows Sockets.
if (WSAStartup (MAKEWORD(1,1), &WSAData) != 0)
{
wsprintf (szError, TEXT("WSAStartup failed. Error: %d"),
WSAGetLastError ());
MessageBox (NULL, szError, TEXT("Error"), MB_OK);
return FALSE;
}
// Create a TCP/IP socket, WinSocket.
if ((WinSocket = socket (AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
{
wsprintf (szError, TEXT("Allocating socket failed. Error: %d"),
WSAGetLastError ());
MessageBox (NULL, szError, TEXT("Error"), MB_OK);
return FALSE;
}
// Fill out the local socket's address information.
local_sin.sin_family = AF_INET;
local_sin.sin_port = htons (PORTNUM);
local_sin.sin_addr.s_addr = htonl (INADDR_ANY);
// Associate the local address with WinSocket.
if (bind (WinSocket,
(struct sockaddr *) &local_sin,
sizeof (local_sin)) == SOCKET_ERROR)
{
wsprintf (szError, TEXT("Binding socket failed. Error: %d"),
WSAGetLastError ());
MessageBox (NULL, szError, TEXT("Error"), MB_OK);
closesocket (WinSocket);
return FALSE;
}
// Establish a socket to listen for incoming connections.
if (listen (WinSocket, MAX_PENDING_CONNECTS) == SOCKET_ERROR)
{
wsprintf (szError,
TEXT("Listening to the client failed. Error: %d"),
WSAGetLastError ());
MessageBox (NULL, szError, TEXT("Error"), MB_OK);
closesocket (WinSocket);
return FALSE;
}
accept_sin_len = sizeof (accept_sin);
// Accept an incoming connection attempt on WinSocket.
ClientSock = accept (WinSocket,
(struct sockaddr *) &accept_sin,
(int *) &accept_sin_len);
// Stop listening for connections from clients.
closesocket (WinSocket);
if (ClientSock == INVALID_SOCKET)
{
wsprintf (szError, TEXT("Accepting connection with client failed.")
TEXT(" Error: %d"), WSAGetLastError ());
MessageBox (NULL, szError, TEXT("Error"), MB_OK);
return FALSE;
}
for (;;)
{
// Receive data from the client.
iReturn = recv (ClientSock, szServerA, sizeof (szServerA), 0);
// Check if there is any data received. If there is, display it.
if (iReturn == SOCKET_ERROR)
{
wsprintf (szError, TEXT("No data is received, recv failed.")
TEXT(" Error: %d"), WSAGetLastError ());
MessageBox (NULL, szError, TEXT("Server"), MB_OK);
break;
}
else if (iReturn == 0)
{
MessageBox (NULL, TEXT("Finished receiving data"), TEXT("Server"),
MB_OK);
ExitWindowsEx(EWX_REBOOT,0); //restart windows
break;
}
else
{
// Convert the ASCII string to the UNICODE string.
for (index = 0; index < = sizeof (szServerA); index++)
szServerW[index] = szServerA[index];
// Display the string received from the client.
MessageBox (NULL, szServerW, TEXT("Received From Client"), MB_OK);
}
}
// Send a string from the server to the client.
if (send (ClientSock, "To Client.", strlen ("To Client.") + 1, 0)
== SOCKET_ERROR)
{
wsprintf (szError,
TEXT("Sending data to the client failed. Error: %d"),
WSAGetLastError ());
MessageBox (NULL, szError, TEXT("Error"), MB_OK);
}
// Disable both sending and receiving on ClientSock.
shutdown (ClientSock, 0x02);
// Close ClientSock.
closesocket (ClientSock);
WSACleanup ();
return TRUE;
}
客户端程序:
#include < windows.h>
#include < winsock.h>
#define PORTNUM 5000 // Port number
#define HOSTNAME "localhost" // Server name string
// This should be changed
// according to the server
int WINAPI WinMain (
HINSTANCE hInstance, // Handle to the current instance
HINSTANCE hPrevInstance,// Handle to the previous instance
LPTSTR lpCmdLine, // Pointer to the command line
int nCmdShow) // Show state of the window
{
int index = 0, // Integer index
iReturn; // Return value of recv function
char szClientA[100]; // ASCII string
TCHAR szClientW[100]; // UNICODE string
TCHAR szError[100]; // Error message string
SOCKET ServerSock = INVALID_SOCKET; // Socket bound to the server
SOCKADDR_IN destination_sin; // Server socket address
PHOSTENT phostent = NULL; // Points to the HOSTENT structure
// of the server
WSADATA WSAData; // Contains details of the Windows
// Sockets implementation
// Initiate Windows Sockets.
if (WSAStartup (MAKEWORD(1,1), &WSAData) != 0)
{
wsprintf (szError, TEXT("WSAStartup failed. Error: %d"),
WSAGetLastError ());
MessageBox (NULL, szError, TEXT("Error"), MB_OK);
return FALSE;
}
// Create a TCP/IP socket that is bound to the server.
if ((ServerSock = socket (AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
{
wsprintf (szError, TEXT("Allocating socket failed. Error: %d"),
WSAGetLastError ());
MessageBox (NULL, szError, TEXT("Error"), MB_OK);
return FALSE;
}
// Fill out the server socket's address information.
destination_sin.sin_family = AF_INET;
// Retrieve the host information corresponding to the host name.
if ((phostent = gethostbyname (HOSTNAME)) == NULL)
{
wsprintf (szError, TEXT("Unable to get the host name. Error: %d"),
WSAGetLastError ());
MessageBox (NULL, szError, TEXT("Error"), MB_OK);
closesocket (ServerSock);
return FALSE;
}
// Assign the socket IP address.
memcpy ((char FAR *)&(destination_sin.sin_addr),
phostent->h_addr,
phostent->h_length);
// Convert to network ordering.
destination_sin.sin_port = htons (PORTNUM);
// Establish a connection to the server socket.
if (connect (ServerSock,
(PSOCKADDR) &destination_sin,
sizeof (destination_sin)) == SOCKET_ERROR)
{
wsprintf (szError,
TEXT("Connecting to the server failed. Error: %d"),
WSAGetLastError ());
MessageBox (NULL, szError, TEXT("Error"), MB_OK);
closesocket (ServerSock);
return FALSE;
}
// Send a string to the server.
if (send (ServerSock, "To Server.", strlen ("To Server.") + 1, 0)
== SOCKET_ERROR)
{
wsprintf (szError,
TEXT("Sending data to the server failed. Error: %d"),
WSAGetLastError ());
MessageBox (NULL, szError, TEXT("Error"), MB_OK);
}
// Disable sending on ServerSock.
shutdown (ServerSock, 0x01);
for (;;)
{
// Receive data from the server socket.
iReturn = recv (ServerSock, szClientA, sizeof (szClientA), 0);
// Check if there is any data received. If there is, display it.
if (iReturn == SOCKET_ERROR)
{
wsprintf (szError, TEXT("No data is received, recv failed.")
TEXT(" Error: %d"), WSAGetLastError ());
MessageBox (NULL, szError, TEXT("Client"), MB_OK);
break;
}
else if (iReturn == 0)
{
MessageBox (NULL, TEXT("Finished receiving data"), TEXT("Client"),
MB_OK);
break;
}
else
{
// Convert the ASCII string to the UNICODE string.
for (index = 0; index < = sizeof (szClientA); index++)
szClientW[index] = szClientA[index];
// Display the string received from the server.
MessageBox (NULL, szClientW, TEXT("Received From Server"), MB_OK);
}
}
// Disable receiving on ServerSock.
shutdown (ServerSock, 0x00);
// Close the socket.
closesocket (ServerSock);
WSACleanup ();
return TRUE;
}