Winny解析
Winnyの利用者は未だに多いようですが,事実上開発者不在となっており,今後の発展は見込めない状態になっています(そんな状態でも,未だにユーザが多くネットワークも維持しているWinnyの完成度には驚きですが).Winnyをターゲットとしたワームも蔓延しているらしいことを考えると,そろそろ次のバージョンが欲しくなるところです.しかし,仕様が公開されていないWinnyの場合,誰か他の人が互換性のあるファイル共有ソフトを作ることが難しくなっています.
2003年にWinnyユーザが逮捕され話題になった時期にWinnyを解析してみましたが,解析結果を公開するのはWinnyのネットワークに対して害はあっても利は無いと思い,しばらく自粛していました.
しかし,開発者逮捕に続き「Winnyの技術」が発売されたことだし,そろそろ公開しても問題は少なくなったと判断して,これを公開します.
注意
2年以上前に書いたメモを元にした文章なので,正確性は保障できません.また,この文書によって生じたいかなる存在も保証しません.全ての作業は自己責任で行ってください.
あと,Winnyには解析を禁止するようなライセンスは存在しませんが,コードを難読化していることも考えると,解析させたくなかったという可能性もあります.なので,各自の判断でこれを読んでください.
逆アセンブル
Winnyは解析防止のためにプログラム本体である実行ファイルのコード領域が暗号化されています.そのため,そのまま逆アセンブラに放り込んでも逆アセンブルできません.
実行ファイルのエントリポイントを調べると,そこに暗号を解除するためのコードらしきものがあることが分かります.その部分を解析してみると分かりますが,かなりコードが難読化されダミーのバイトが挟んであったりして,普通のツールでは逆アセンブルコードは得られません.ちゃんと調べてませんが,どういうわけかデバッガから実行していると途中で止まるようです.
そこで,実行中のプロセスにアタッチして,デバッグ用のAPIであるReadProcessMemoryを用いてメモリ内の情報を読み込んで保存することにしました.実行ファイルのヘッダを見れば,展開されるアドレスが分かるので,簡単なツールを作って暗号化されて無い実行ファイルを得ました.
解析
逆アセンブルしたコードは膨大な量になるので,通信に関係する部分を探し出して解析することにします.多くの場合システムのDLLは同じアドレスに読み込まれるのでそれを利用します.
まず,適当なプログラムを書いて,WinSockが何処にロードされるのか調べます.それが分かったら,Winny実行中にそのDLL内の関数を呼び出している箇所を探します.予想通りインポートテーブルらしきものが見つかりました.あとは,そのテーブルにアクセスしている箇所を探していけば,通信に関係する部分がどの辺りに分布しているのかが分かります.
あとは,ひたすら読んでいくだけです.私は関数ごとに分けてCで書き直していって,中身が分かった時点でアドレスに関数名を付けるという風に解析していきました.関数名が付いたアドレスが増えてくると,そこを呼び出している部分の解析が楽になるので,後になるほど効率が上がります.
関数リスト
メモからコピーしただけです.何処にどの関数があって,何を呼び出しているか少し書いてあります.自分で解析してみるときに参考になれば幸いです.
0042A5DC fopen? 00427BC4 Open(){ fopen } 004287B4 int CKey::Load_Chk(){ call 00528620 => class 00427BC4 => Open() } 004289FC int CKey::Load(){ call 00528620 00427BC4 => Open() } 0042A4FC hash2str 0042AB68 void CKey::Nodeendecode(const BYTE *A,BYTE *B) 00456DF0 (){ ;0045710C 68336E5400 PUSH 00546E33H > opiewf6ascxlv } 0046CA28 rc4_key(tbl,key) 0046CC54 rc4_crypt(tbl,dat,len) 00423F70 () 00426124 void CConnection::(a,b) 004262D8 00426340 00426394 void CConnection::SendRC4init(key) 004264AC char CConnection::SendRC4Crypt_byte() 00426588 void CConnection::SendDatCrypt(dst,src,len) 004265C0 void CConnection::RecvRC4init(key); 004266E4 004267C0 void CConnection::RecvDatCrypt(dat?,len?); 004267F8 004268F0 void CConnection::RecvDatAdd?(dat,len) 004269D8 void CConnection::RecvDatAdd2?(dat,len) 00426AC0 00426B14 00426B5C 00426BEC void CConnection::RecvBlock(dat,len=0x20000) 00426CC0 void CConnection::SendAdd2(data,len); Crypt 00426E6C void CConnection::SendAdd(data,len); local BBS? 00426F78 void CConnection::Send() 00427224 void CConnection::Close(int B) 004273C8 void CConnection::lWSAAsyncGetHostByName() 00427444 f(){ gethostbyname } 004296D0 Key::Save? 0042AB68 (d,key,a)? 0042EB9C f(){ WSACleanup } 00434ED0 (){ ;init gethostname gethostbyname } 00435078 (){ } 004351D4 f(){ WSAStartup } 00435840 (){ //Connect(a,b,c); 00435E4 (){ Listen; } 00436554 (){ Listen2 } 00436F30 H::Recv(a,b,c) 00435388 Recv2{ H->Recv(a,b,c); } 004371C4 void Host::SendBlock(cnum,dat,len); 00449AAC Host::ConnectInit??(cnum) 0045D878 BbsHost{ //◆認証警告 host[a].SendAdd(d,l) }//0045DC94 0045DC98{ } 0045EFD4 BBsHostErr?{ host[a].SendAdd(d,l) //00426E6C host[a].SendAdd(d,l) host[a].SendAdd(d,l) host[a].SendAdd(d,l) //... }//0045FE0C 0045FE10(){ host[a].SendAdd(d,l) host[a].SendAdd(d,l) host[a].SendAdd(d,l) //... }//004609B6 004609B8{ } 00460B3C{ } //_STL::pair<System::AnsiString,int> 00461350 BbsMenu?{ //<title>Winny 板分類一覧</title> host[a].SendAdd(d,l) }//00461585 004617CC{ } 004618C4{ //<title>%sスレッド一覧</title> host[a].SendAdd(d,l) }//00462300 0046230A?9{ HTTP/1.0 200 OK host[a].SendAdd(d,l) } 00462E3C 00463220 00463514 00463798 BBS?E{ host[a].SendAdd(d,l) } 00463B28 00463BC4 BBS_Form{ host[a].SendAdd(d,l) } 00463D60 00463EE8 00463FD4 ... 0046471C BBS_H{ host[a].SendAdd(d,l) } // stdlib 00527A78 realloc? 00528024 memcpy 005281C8 strcpy??? 00528214 strcpy 00528244 strlen 00528620 ??classm?? 0052BF58 sprintf? //wsock32.dll func //call 00538608 -> jmp [00EDF564] 00EDF564:719e7707 00538608 WSAAsyncGetHostByName(q,b) 0053860E WSAAsyncSelect(a,b,c) ->err? 00538614 WSACleanup ? 0053861A WSAGetLastError 00538620 WSAStartup(101,+8 +405C??) 00538626 accept() 0053862C sockclose(sock) 00538632 connect(?,?,10h)->err? 00538638 gethostbyname(a144) 0053863E gethostname(a144,100h)? 00538644 closesocket(a?) 0053864A listen(sock,40) 00538650 int recv(SOCKET sock, char* buf, int len, int flag) 00538656 int send(SOCKET sock, char* buf, int len, int flag) 0053865C setsockopt(sock ,0000FFFF,0FFFFFF7F,??,4) 00538662 shutdown(sock,1) 00538668 socket(0,1,2) 0053866E bind
この他に,一部をC言語もどきで書き下したテキストがありますが,あまり役に立つとは思えないので,関数リストだけにしておきます.
暗号
Winnyの暗号は殆どの場所でRC4を使っているようです.RC4の関数は比較的早い段階で見つかりました.
ちなみに,上のリストでRC4の暗号化&復号化をしているのは以下の2つの関数です.
0046CA28 rc4_key(tbl,key) 0046CC54 rc4_crypt(tbl,dat,len)
使っている暗号鍵についてちょっと説明.
- キャッシュヘッダ "header","adsfu6"
- キャッシュデータ [ハッシュ文字列]+[ブロック番号]
- バージョン情報 "98789asj"
- ノード情報 "opiewf6ascxlv"
メモに書いてあったものだけなので,他にもありかもしれませんが….文字の配列を見ると,どうもキーボードを適当に叩いて入力して作ったみたいですね.
プロトコル解析
プログラム内の解析は少し手を止めて,今度は通信の内容を調べてみます.どんな内容の通信をしているのかが分かれば,解析を楽にすることが出来ます.
まず,Winny同士の通信は暗号化されているため,パケットをダンプしても意味不明な内容です.そこで,通信を暗号化しないWinnyを作ることにします.当時ダウンロードしたWinnyでは,0x0046CC54あたりにデータをRC4で暗号化する関数がありました.その関数を呼び出しているコードを弄っても構いませんが,0x004265A5付近にRC4で作った数字と,データ列をXORで排他的理論和を取っている箇所があるので,そこをつぶしてしまいます.
他にも色々弄ったみたいですが,メモが解読不能なため分かりません.
すると,Winnyが通信などを暗号化しなくなるので,プロトコルの解析が容易になります.
パケットを解析しているとすぐに気付くのが,4バイトのブロック長の後に,その長さのデータが続いたブロックに分かれているということです.数値はリトルエンディアンです.そうすると,通信開始時の先頭6バイトに意味不明なデータがあるのに気付きます.なにやら匂いますね.
とりあえず
これで,解析のために必用なものは全てそろいました.(本当に必要なのは,Winny本体と逆アセンブルする環境だけですが…)
この文書が,何かに役に立てば幸いです.
この文書の履歴
- 2005-11-15 公開