情報工学実験2「TCP/IPプログラミング」

055738C 張 暁雪

*目次

課題1:telnetコマンドによるWWWサーバへのアクセス
課題2:inetdを使用するサーバプログラムの作成
課題3:inetdを使用しないサーバプログラムの作成
課題4:HTTPクライアントの作成
課題5:ポートスキャンの実験
課題6:バッファオーバーフローの実験

*課題1*:telnetコマンドによるWWWサーバへのアクセス
telnetコマンドを使って任意のWWWサーバにアクセスし、任意のURLのページデータ(htmlソースプログラム)を画面に表示せよ。(報告書にはそのURLとページデータの先頭の20行程度を添付せよ)
実行結果

*[nw0538:~]*(^_^)v j05038% telnet culture.163.com 80 Trying 202.108.9.17... Connected to channel.cache.163.com. Escape character is '^]'. GET /index.html HTTP/1.0 HTTP/1.0 403 Forbidden Server: Cache/ 2.0 Mime-Version: 1.0 Date: Thu, 30 Nov 2006 00:45:11 GMT Content-Type: text/html Content-Length: 1123 Expires: Thu, 30 Nov 2006 00:45:11 GMT X-Squid-Error: ERR_ACCESS_DENIED 0 X-Cache: MISS from channel.163.com Connection: close <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML>⁢HEAD> <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=gb2312"> 〜略〜 <BR clear="all"> <HR noshade size="1px"> <ADDRESS> Generated Thu, 30 Nov 2006 00:45:11 GMT by channel.163.com (Cache/ 2.0) </ADDRESS> </BODY>⁢/HTML> Connection closed by foreign host. *[nw0538:~]*(^_^)v j05038%

*課題2*:inetdを使用するサーバプログラムの作成
サンプルプログラム(1)はサーバ(server.c)をinetdから起動することで、サーバはクライアント(client.c)の標準入力から入力された文字に対応するキーワードをクライアントに返すプログラムである。サンプルプログラム(1)を自分の実験環境で動作するようにし、実行結果を示すとともに、プログラムの中で使われている関数(Connect,Disconnect, Send Data, Recv Dataなど)の動作を説明し、サーバ・クライアント動作全体をフローを示して説明せよ。
実行結果

*[nw0538:~]*(^_^)v j05038% sudo xinetd -inetd_compat *[nw0538:~]*(^_^)v j05038% ./client Connected. Input Keyword = warning: this program uses gets(), which is unsafe. yama Keyword = [yama] / Data = [kawa] Input Keyword = xyz Keyword = [xyz] / Data = [XYZ] Input Keyword = 123 Keyword = [123] / Data = [456] Input Keyword = xxxx Keyword = [xxxx] / Data = [yyyy] Input Keyword = shiro Keyword = [shiro] / Data = [kuro] Input Keyword = ^X^C *[nw0538:~]*(^_^)v j05038%

考察
◎関数についての説明:
・Connect:ソケットを生成し、コネクションの準備をするための関数。
・Disconnect:ソケット接続を解除し、コネクションを終了するための関数。
・SendData:入力したデータをサーバに書き込むための関数。
・RecvData:データの結果をサーバから受け取るための関数。
・GetLineFromPeer:クライアントから入力されたデータを受け取るための関数。
◎フロー:
*課題3*:inetdを使用しないサーバプログラムの作成
サンプルプログラム(1)のサーバプログラム(server.c)はinetdから起動するものであるが、inetdを使用せずに同じ動作をするデーモン型のサーバプログラムを作成し、実行結果を示すとともに、inetdを使用するサーバプログラムとそうでないものとの実装上の違いを説明せよ。
a-1.プログラム

#include <stdio.h> #include <sys/types.h> #include <sys/errno.h> #include <string.h> #include <ctype.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdlib.h> #define DATAFILE "data.txt" #define DELIMITS " \t\n\r" char datavalue[256]; int GetLineFromPeer(char *line, int hostsoc) { int i; for(i = 0 ; ; ) { if(1 != read(hostsoc,line + i,1)) continue; else { i++; *(line + i) = (char)0; } if(((char)('\n')) == (*(line + i - 1))) return i; } } char *GetKeywordData(char *key) { FILE*fp; char buff[256]; if((FILE *)NULL == (fp = fopen(DATAFILE,"r"))) return (char *)NULL; for(;;) { char*p; char*pp; fgets(buff,256,fp); if(feof(fp)) break; if(ferror(fp)) break; if((char *)NULL == (p = strtok(buff,DELIMITS))) continue; if((char *)NULL == (pp = strtok((char *)NULL,DELIMITS))) continue; if(0 == strcmp(p,key)) { fclose(fp); strcpy(datavalue,pp); return datavalue; } } fclose(fp); return (char *)NULL; } int main() { /**********deamon********/ unsigned short port = 5683; int servsoc; int hostsoc; struct sockaddr_in srcAddr; struct sockaddr_in dstAddr; int dstAddrSize = sizeof(dstAddr); memset(&srcAddr, 0, sizeof(srcAddr)); srcAddr.sin_port = htons(port); srcAddr.sin_family = AF_INET; srcAddr.sin_addr.s_addr = htonl(INADDR_ANY); servsoc = socket(AF_INET, SOCK_STREAM, 0); bind(servsoc, (struct sockaddr *) &srcAddr, sizeof(srcAddr)); listen(servsoc, 1); printf("now waiting for client connect\n"); hostsoc = accept(servsoc, (struct sockaddr *) &dstAddr, &dstAddrSize); printf("Connected from %s\n", inet_ntoa(dstAddr.sin_addr)); /**********daemon-ed**********/ char RecvBuff[256]; for(;;) { char*p; char SendBuff[256]; char*pp; (void)GetLineFromPeer(RecvBuff, hostsoc);/* Get 1 line from peer */ if(((char)('=')) == *(RecvBuff))/* If End Mark */ { close(0); exit(0); } if((char *)NULL != (p = strchr(RecvBuff,'='))) { *p = (char)0; if((char *)NULL == (pp = GetKeywordData(RecvBuff))) sprintf(SendBuff,"%s=\n",RecvBuff); else sprintf(SendBuff,"%s=%s\n",RecvBuff,pp); (void)write(hostsoc,SendBuff,strlen(SendBuff)); } } }

a-2.プログラム

#include <stdio.h> #include <ctype.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #defineSERVICE_NAME"tcpinetd" #definePROTOCOL"tcp" #defineHOST_NAME"localhost" intConnect() { intSocketNumber;/* Socket descripter */ structservent*ServiceEntry;/* service entry */ structhostent*HostEntry;/* host entry */ structsockaddr_insin;/* Socket Entry */ /* Get Service Entry by Service-Name */ /****** * if((struct servent *)NULL == (ServiceEntry = *getservbyname(SERVICE_NAME,PROTOCOL))) * * { * printf("\7No service [%s].\n",SERVICE_NAME); * exit(1); * } * printf("%d\n",ServiceEntry->s_port); *****/ /* Get Host Entry by Host-Name */ /********** *if((struct hostent *)NULL == (HostEntry = gethostbyname(HOST_NAME))) *{ *printf("\7No Hosts [%s].\n",HOST_NAME); *exit(1); *} *********/ /* Get Socket */ /******deamon*******/ HostEntry = gethostbyname("localhost"); if(!HostEntry){ printf("error:failed to serch the host\n"); exit(1); } /******deamon-ed******/ if(0 > (SocketNumber = (socket(AF_INET,SOCK_STREAM,0)))) { printf("\7Cannot get Socket.\n"); perror("\n"); exit(1); } /* Set protocol family name */ sin.sin_family = AF_INET; /* Set Host Address */ bcopy(HostEntry->h_addr,&sin.sin_addr,HostEntry->h_length); /* Set Port N.o. */ sin.sin_port = 5683; //ServiceEntry->s_port; if(0 > connect(SocketNumber,(struct sockaddr *)(&sin),sizeof(sin))) { printf("\7Cannot Connect.\n"); perror("\n"); exit(1); } return SocketNumber; } voidDisconnect(int SocketNumber) { close(SocketNumber); } voidSendData(int SocketNumber,char *line) { if(strlen(line) != write(SocketNumber,line,strlen(line))) { printf("\7Send Failed.\n"); exit(1); } } intRecvData(int SocketNumber,char *line) { inti; for(i = 0 ;;) { if(1 != read(SocketNumber,line + i,1)) continue; else { i++; *(line + i) = (char)0; if(((char)('\n')) == *(line + i - 1)) return i; } } } intmain() { intSock; charKey[256]; charData[256]; charbuff[256]; Sock = Connect();/* Connect */ printf("Connected.\n"); for(;;) { char*p; printf("Input Keyword = "); fflush(stdout); gets(Key); sprintf(buff,"%s=\n",Key); SendData(Sock,buff);/* Send It ! */ if(0 == strlen(Key)) break;/* If End Mark */ /* ---- Waiting for Server response Here --- */ (void)RecvData(Sock,buff);/* Recv It ! */ if((char *)NULL != (p = strchr(buff,'\n'))) *p = (char)0; if((char *)NULL != (p = strchr(buff,'='))) p++; printf("Keyword = [%s] / Data = [%s]\n\n",Key,p); } Disconnect(Sock);/* Disconnect */ printf("Disocnnected.\n"); }

b.実行結果

*[nw0538:~]*(^_^)v j05038% ./a-2 Connected. Input Keyword = warning: this program uses gets(), which is unsafe. yama Keyword = [yama] / Data = [kawa] Input Keyword = xyz Keyword = [xyz] / Data = [XYZ] Input Keyword = 123 Keyword = [123] / Data = [456] Input Keyword = xxxx Keyword = [xxxx] / Data = [yyyy] Input Keyword = shiro Keyword = [shiro] / Data = [kuro] Input Keyword = ^X^C *[nw0538:~]*(^_^)v j05038%

考察
◎inetdを使用するサーバプログラムとそうでないものの違いについて
普通はクライアントからの要求に答えるためなので、デーモン型では、サーバプログラムの中にソケットを用意する必要が
ある。 inetdを用いた場合はソケットを用いる必要がなく、クライアントからの要求には随時サーバプログラムが立ち上がること
である。
*課題4*:HTTPクライアントの作成 ソケットおよびHTMLを使ってWWWサーバから任意のURLのページを取得し、標準出力に出
力するプログラムを作成せよ。
a.プログラム

#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <netinet/in.h> #define BUF 256 int main(int argc, char *argv[]){ int p; struct hostent *servhost; struct sockaddr_in server; struct servent *service; char send_buf[BUF]; char host[BUF] = "ホスト"; char path[BUF] = "/"; unsigned short port = 80; if ( argc > 1 ){ char host_path[BUF]; if ( strstr(argv[1], "http://") && sscanf(argv[1], "http://%s", host_path) && strcmp(argv[1], "http://" ) ){ char *t; t = strchr(host_path, '/'); if ( t != NULL ){ strcpy(path, t); *t = '\0'; strcpy(host, host_path); } } } servhost = gethostbyname(host); if ( servhost == NULL ){ fprintf(stderr, "[%s] には接続できません\n", host); return 0; } bzero(&server, sizeof(server)); server.sin_family = AF_INET; bcopy(servhost->h_addr, &server.sin_addr, servhost->h_length); if ( port != 0 ){ server.sin_port = htons(port); } else { service = getservbyname("http", "tcp"); if ( service != NULL ){ server.sin_port = service->s_port; } else { server.sin_port = htons(80); } } /*ソケット生成*/ if ( ( p = socket(AF_INET, SOCK_STREAM, 0) ) < 0 ){ fprintf(stderr, "ソケットの生成に失敗しました。\n"); return 1; } /*サーバに接続*/ if ( connect(p, (struct sockaddr *)&server, sizeof(server)) == -1 ){ fprintf(stderr, "接続に失敗しました。\n"); return 1; } /*HTTPプロトコルを生成し、サーバに送信*/ sprintf(send_buf, "GET %s HTTP/1.0\r\n", path); write(p, send_buf, strlen(send_buf)); sprintf(send_buf, "Host: %s:%d\r\n", host, port); write(p, send_buf, strlen(send_buf)); sprintf(send_buf, "\r\n"); write(p, send_buf, strlen(send_buf)); /*受信&表示*/ while (1){ char buf[BUF]; int size; size = read(p, buf, BUF); if ( size > 0 ){ write(1, buf, size); } else { break; } } close(p); return 0; }

b.実行結果

*[nw0538:~]*(^_^)v j05038% ./httpget http://image.baidu.com/ HTTP/1.1 200 OK Date: Mon, 04 Dec 2006 03:03:10 GMT Server: apache 1.4.9.0/httpd 1.3.27 (Unix) BAIDU_UENCODE v1.0.0 mod_gzip/1.3.19.1a mod_image/2.0.1 mod_cache/1.0.0 mod_baidu/4.1.1 Set-Cookie: BAIDUID=EEEB21DB95A3A87A22E7F1D4BE6E3E1F; expires=Mon, 04-Dec-36 03:03:10 GMT; path=/; domain=.baidu.com Status: 0 Connection: close Content-Type: text/html <html><head><title>為業夕頭!畠白恷寄嶄猟夕頭垂 </title><meta http-equiv=Content-Type content="text/html; charset=gb2312"> <style type=text/css> body{text-align:center;font-family:卜悶;} td,div{font-size:12px} .ff{font-family:Verdana;font-size:16px} #n {margin:0px auto;font-size:12px;padding:0px;border-bottom:1px solid #0000CC;BACKGROUND:#EEEEEE;width:600px;height:18px;} #n li{float:left;display:block;margin:0px;padding:0px;width:67px;} #n li a{display:block;text-decoration:none;padding:4px 0px 0px 0px;margin:0px;width:100%;} 〜略〜 <div id="ft">©2006 Baidu <a href=http://www.baidu.com/duty/>聞喘為業念駅響</a>:</div></center> </body></html> *[nw0538:~]*(^_^)v j05038%

*課題5*:ポートスキャンの実験 自分の実験環境(端末)の使用/未使用ポート(ウェルノウンポートのみでok)を確認する
ポートスキャンプログラムを作成せよ。さらに、任意のリモート端末の使用/未使用ポート
を確認するように改良せよ(加点ポイント)。なお、スクリプトを使って内部で'netstat
-l'コマンドを実行し、その結果を利用するのは不可とする(ソケットプログラムを作成す
ること)。
a.プログラム

#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <arpa/inet.h> main(int argc, char **argv) { struct hostent *h; struct sockaddr_in host; if(argc != 2){ printf("Usage:scan hostname\n"); } h = gethostbyname(argv[1]); if(!h){ printf("error:failed to serch the host\n"); exit(1); } int wellp; bzero((char *)&host, sizeof(host)); host.sin_family = AF_INET; host.sin_addr = *(struct in_addr*)h->h_addr; for(wellp=0; wellp <= 1023; wellp++){ host.sin_port = htons(wellp); int s = socket(AF_INET, SOCK_STREAM, 0); if(s==-1) printf("failed to socket\n"); if(connect(s, (struct sockaddr *)(&host), sizeof(host)) == 0) /*{ printf("failed to connect %s port %d\n", argv[1], wellp); perror(""); } else*/ printf("success to connecct %s port %d\n", argv[1], wellp); close(s); } exit(1); }

b-1.実行結果(ローカルホスト)

*[nw0538:~]*(^_^)v j05038% ./b 127.0.0.1 success to connecct 127.0.0.1 port 427 success to connecct 127.0.0.1 port 548 success to connecct 127.0.0.1 port 631 *[nw0538:~]*(^_^)v j05038%

b-2.実行結果(リモート端末(nw017))

*[nw0538:~]*(^_^)v j05038% ./b 133.13.59.17 success to connecct 133.13.59.17 port 22 success to connecct 133.13.59.17 port 139 success to connecct 133.13.59.17 port 427 success to connecct 133.13.59.17 port 445 success to connecct 133.13.59.17 port 548 *[nw0538:~]*(^_^)v j05038%

*課題6*:バッファオーバーフローの実験 サンプルプログラム(2)を実行せよ。このプログラムはgets()関数を用いて、標準入力
からの入力をバッファにデータを読み込むものであるが、結果を見ると、プログラム中で操
作していないバッファdmy[]に値が入ることがある。この原因を考察し、解決策を示せ。
また、この問題によって引き起こされるTCP/IP通信におけるセキュリティ上の欠陥はどの
ようなものが考えられるか、具体例を挙げて述べよ。
a.プログラム

#include <stdio.h> #define BUFLEN 20 int main(int argc, char **argv) { char *rtn; char dmy[BUFLEN]; char buf[BUFLEN]; memset(dmy, '\0', BUFLEN); memset(buf, '\0', BUFLEN); printf("before\n"); printf("buf(Len:%d) = %s\n", strlen(buf), buf); printf("dmy(Len:%d) = %s\n", strlen(dmy), dmy); if ((rtn = gets(buf)) == NULL) { exit(-1); } printf("after\n"); printf("buf(Len:%d) = %s\n", strlen(buf), buf); printf("dmy(Len:%d) = %s\n", strlen(dmy), dmy); }

b.実行結果

*[nw0538:~]*(^_^)v j05038% ./bufovf before buf(Len:0) = dmy(Len:0) = warning: this program uses gets(), which is unsafe. qweertyyhghgggfgfgghhjjjjjjjjjkkkklllqweiueuoiwirrfiwrjeoiwfhhfhfkdshfkdhfkdhfkdhfkhfkdhfkdhfkhkhdfkhflhahjoakhiohiofhighiehgohgoihjeholohglhgolsholoholahlahalhlahlofhjoliehiohfoihoiwhoiiheihjlhflsjelf after buf(Len:201) = qweertyyhghgggfgfgghhjjjjjjjjjkkkklllqweiueuoiwirrfiwrjeoiwfhhfhfkdshfkdhfkdhfkdhfkhfkdhfkdhfkhkhdfkhflhahjoakhiohiofhighiehgohgoihjeholohglhgolsholoholahlahalhlahlofhjoliehiohfoihoiwhoiiheihjlhflsjelf dmy(Len:0) = Segmentation fault *[nw0538:~]*(^_^)v j05038%

考察
◎原因について
gets関数は配列の長さを指定する事ができない文字配列だけ渡すことである。したがって、原因はgets関数である。
◎解決策について
・プログラム

#include <stdio.h> #define BUFLEN 20 int main(int argc, char **argv) { char *rtn; char dmy[BUFLEN]; char buf[BUFLEN]; memset(dmy, '\0', BUFLEN); memset(buf, '\0', BUFLEN); printf("before\n"); printf("buf(Len:%d) = %s\n", strlen(buf), buf); printf("dmy(Len:%d) = %s\n", strlen(dmy), dmy); if ((rtn = fgets(buf,BUFLEN,stdin)) == NULL) { exit(-1); } printf("after\n"); printf("buf(Len:%d) = %s\n", strlen(buf), buf); printf("dmy(Len:%d) = %s\n", strlen(dmy), dmy); }

・実行結果

*[Zhang-Xiaoxue:~]*(^_^)v j05038% ./f before buf(Len:0) = dmy(Len:0) = qweertyyhghgggfgfgghhjjjjjjjjjkkkklllqweiueuoiwirrfiwrjeoiwfhhfhfkdshfkdhfkdhfkdhfkhfkdhfkdhfkhkhdfkhflhahjoakhiohiofhighiehgohgoihjeholohglhgolsholoholahlahalhlahlofhjoliehiohfoihoiwhoiiheihjlhflsjelf after buf(Len:19) = qweertyyhghgggfgfgg dmy(Len:0) = *[Zhang-Xiaoxue:~]*(^_^)v j05038%

◎TCP/IP通信におけるセキュリティ上の欠陥と例について
 プログラミング言語で書かれたプログラムでは、メモリサイズを超えて文字列が入力されるとオーバーフローしてしまうこと
である。これにより、オーバーフローしたプログラムのアクセス権で任意の動作させることが可能である。  もしこれを悪用すると、ウィルスプログラムを作成し実行させることがある。

*感想



 今度の課題はとても難しいと思います。特に、課題3と課題6です、すごく時間がかかりました。難し
いけど、面白いと思います。別のは、ホームページでレポートを発表するから、ホームページの作り方も
もう一度復習しました。